diff --git a/include/config.h b/include/config.h index 88e6e029f..a8c14a7e1 100644 --- a/include/config.h +++ b/include/config.h @@ -679,7 +679,8 @@ typedef unsigned char uchar; /* #define LIVELOG */ #endif /* CHRONICLE */ #else -#undef LIVELOG*/#endif /* NO_CHRONICLE */ +#undef LIVELOG +#endif /* NO_CHRONICLE */ /* #define DUMPLOG */ /* End-of-game dump logs */ diff --git a/include/extern.h b/include/extern.h index 94046fca0..2345de729 100644 --- a/include/extern.h +++ b/include/extern.h @@ -2382,7 +2382,8 @@ extern const char *get_portable_device(void); #endif /* ### pcsys.c, windsys.c ### */ -#if defined(MICRO) || defined(WIN32) +/* Also applies for NLE */ +#if defined(MICRO) || defined(WIN32) || defined(RL_GRAPHICS) ATTRNORETURN extern void nethack_exit(int) NORETURN; #else #define nethack_exit exit diff --git a/src/allmain.c b/src/allmain.c index c73efe728..6d8d5cb40 100644 --- a/src/allmain.c +++ b/src/allmain.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 allmain.c $NHDT-Date: 1555552624 2019/04/18 01:57:04 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.100 $ */ +/* NetHack 5.0 allmain.c $NHDT-Date: 1771213100 2026/02/15 19:38:20 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.286 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2012. */ /* NetHack may be freely redistributed. See license for details. */ @@ -6,47 +6,57 @@ /* various code that was replicated in *main.c */ #include "hack.h" -#include #ifndef NO_SIGNAL #include #endif +staticfn void moveloop_preamble(boolean); +staticfn void u_calc_moveamt(int); +staticfn void maybe_generate_rnd_mon(void); +staticfn void maybe_do_tutorial(void); #ifdef POSITIONBAR -STATIC_DCL void NDECL(do_positionbar); +staticfn void do_positionbar(void); #endif -STATIC_DCL void FDECL(regen_hp, (int)); -STATIC_DCL void FDECL(interrupt_multi, (const char *)); -STATIC_DCL void FDECL(debug_fields, (const char *)); +staticfn void regen_pw(int); +staticfn void regen_hp(int); +staticfn void interrupt_multi(const char *); /* * nle_spawn_monsters() indicates whether to spawn monsters randomly * after every step with some probability (true by default). */ -extern int FDECL(nle_spawn_monsters, ()); +extern int nle_spawn_monsters(void); /* NLE RNG selection functions */ -extern void FDECL(nle_swap_to_lgen, (int)); -extern void FDECL(nle_swap_to_core, (int)); +extern void nle_swap_to_lgen(int); +extern void nle_swap_to_core(int); +#ifdef CRASHREPORT +#define USED_FOR_CRASHREPORT +#else +#define USED_FOR_CRASHREPORT UNUSED +#endif + +/*ARGSUSED*/ void -moveloop(resuming) -boolean resuming; +early_init(int argc USED_FOR_CRASHREPORT, char *argv[] USED_FOR_CRASHREPORT) { -#if defined(MICRO) || defined(WIN32) - char ch; - int abort_lev; + program_state_init(); +#ifdef CRASHREPORT + /* Do this as early as possible, but let ports do other things first. */ + crashreport_init(argc, argv); #endif - int moveamt = 0, wtcap = 0, change = 0; - boolean monscanmove = FALSE; - - /* Note: these initializers don't do anything except guarantee that - we're linked properly. - */ - decl_init(); - monst_init(); - objects_init(); + decl_globals_init(); + objects_globals_init(); + monst_globals_init(); + sys_early_init(); + runtime_info_init(); +} +staticfn void +moveloop_preamble(boolean resuming) +{ /* if a save file created in normal mode is now being restored in explore mode, treat it as normal restore followed by 'X' command to use up the save file and require confirmation for explore mode */ @@ -68,406 +78,568 @@ boolean resuming; } if (!resuming) { /* new game */ - context.rndencode = rnd(9000); + program_state.beyond_savefile_load = 1; /* for TTY_PERM_INVENT */ + svc.context.rndencode = rnd(9000); set_wear((struct obj *) 0); /* for side-effects of starting gear */ + reset_justpicked(gi.invent); (void) pickup(1); /* autopickup at initial location */ + /* only matters if someday a character is able to start with + clairvoyance (wizard with cornuthaum perhaps?); without this, + first "random" occurrence would always kick in on turn 1 */ + svc.context.seer_turn = (long) rnd(30); + /* give hero initial movement points; new game only--for restore, + pending movement points were included in the save file */ + u.umovement = NORMAL_SPEED; + initrack(); } - context.botlx = TRUE; /* for STATUS_HILITES */ - update_inventory(); /* for perm_invent */ + disp.botlx = TRUE; /* for STATUS_HILITES */ if (resuming) { /* restoring old game */ read_engr_at(u.ux, u.uy); /* subset of pickup() */ + fix_shop_damage(); } - (void) encumber_msg(); /* in case they auto-picked up something */ - if (defer_see_monsters) { - defer_see_monsters = FALSE; + encumber_msg(); /* in case they auto-picked up something */ + if (gd.defer_see_monsters) { + gd.defer_see_monsters = FALSE; see_monsters(); } - initrack(); u.uz0.dlevel = u.uz.dlevel; - youmonst.movement = NORMAL_SPEED; /* give the hero some movement points */ - context.move = 0; + svc.context.move = 0; + + /* finish processing "--debug:fuzzer" from the command line */ + if (iflags.fuzzerpending) { + iflags.debug_fuzzer = fuzzer_impossible_panic; + iflags.fuzzerpending = FALSE; + } program_state.in_moveloop = 1; - for (;;) { + /* for perm_invent preset at startup, display persistent inventory after + invent is fully populated and the in_moveloop flag has been set */ + if (iflags.perm_invent) + update_inventory(); +} + +staticfn void +u_calc_moveamt(int wtcap) +{ + int moveamt = 0; + + /* calculate how much time passed. */ + if (u.usteed && u.umoved) { + /* your speed doesn't augment steed's speed */ + moveamt = mcalcmove(u.usteed, TRUE); + } else { + moveamt = gy.youmonst.data->mmove; + + if (Very_fast) { /* speed boots, potion, or spell */ + /* gain a free action on 2/3 of turns */ + if (rn2(3) != 0) + moveamt += NORMAL_SPEED; + } else if (Fast) { /* intrinsic */ + /* gain a free action on 1/3 of turns */ + if (rn2(3) == 0) + moveamt += NORMAL_SPEED; + } + } + + switch (wtcap) { + case UNENCUMBERED: + break; + case SLT_ENCUMBER: + moveamt -= (moveamt / 4); + break; + case MOD_ENCUMBER: + moveamt -= (moveamt / 2); + break; + case HVY_ENCUMBER: + moveamt -= ((moveamt * 3) / 4); + break; + case EXT_ENCUMBER: + moveamt -= ((moveamt * 7) / 8); + break; + default: + break; + } + + u.umovement += moveamt; + if (u.umovement < 0) + u.umovement = 0; +} + +/* small chance of generating a new random monster */ +staticfn void +maybe_generate_rnd_mon(void) +{ + /* Change for NLE: Optionally disable monster spawning */ + if (nle_spawn_monsters() && !rn2(u.uevent.udemigod ? 25 + : (depth(&u.uz) > depth(&stronghold_level)) ? 50 + : 70)) + (void) makemon((struct permonst *) 0, 0, 0, NO_MM_FLAGS); +} + +#if defined(MICRO) || defined(WIN32) +static int mvl_abort_lev; +#endif +static int mvl_wtcap = 0; +static int mvl_change = 0; + +void +moveloop_core(void) +{ + boolean monscanmove = FALSE; + #ifdef SAFERHANGUP - if (program_state.done_hup) - end_of_input(); + if (program_state.done_hup) + end_of_input(); #endif - get_nh_event(); + get_nh_event(); #ifdef POSITIONBAR - do_positionbar(); + do_positionbar(); #endif + if (iflags.pending_customizations) + maybe_shuffle_customizations(); + + dobjsfree(); + + if (svc.context.bypasses) + clear_bypasses(); + + if (iflags.sanity_check || iflags.debug_fuzzer) + sanity_check(); + + if (svc.context.resume_wish) + makewish(); /* clears resume_wish */ + + if (svc.context.move) { + /* actual time passed */ + u.umovement -= NORMAL_SPEED; + + do { /* hero can't move this turn loop */ + encumber_msg(); + + svc.context.mon_moving = TRUE; + do { + monscanmove = movemon(); + if (u.umovement >= NORMAL_SPEED) + break; /* it's now your turn */ + } while (monscanmove); + svc.context.mon_moving = FALSE; + + /* this needs to be after the monster movement loop in + case monster actions affected burden, e.g. rehumanize */ + mvl_wtcap = near_capacity(); + + if (!monscanmove && u.umovement < NORMAL_SPEED) { + /* both hero and monsters are out of steam this round */ + struct monst *mtmp; + + /* set up for a new turn */ + gw.were_changes = 0L; + mcalcdistress(); /* adjust monsters' trap, blind, etc */ + + /* reallocate movement rations to monsters; don't need + to skip dead monsters here because they will have + been purged at end of their previous round of moving */ + for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) + mtmp->movement += mcalcmove(mtmp, TRUE); + + /* occasionally add another monster; since this takes + place after movement has been allotted, the new + monster effectively loses its first turn */ + maybe_generate_rnd_mon(); + + u_calc_moveamt(mvl_wtcap); + settrack(); + + svm.moves++; + /* + * Never allow 'moves' to grow big enough to wrap. + * We don't care what the maximum possible 'long int' + * is for the current configuration, we want a value + * that is the same for all viable configurations. + * When imposing the limit, use a mystic decimal value + * instead of a magic binary one such as 0x7fffffffL. + */ + if (svm.moves >= 1000000000L) { + display_nhwindow(WIN_MESSAGE, TRUE); + urgent_pline("The dungeon capitulates."); + done(ESCAPED); + } + /* 'moves' is misnamed; it represents turns; hero_seq is + a value that is distinct every time the hero moves */ + gh.hero_seq = svm.moves << 3; + + if (flags.time && !svc.context.run) + disp.time_botl = TRUE; /* 'moves' just changed */ + + /********************************/ + /* once-per-turn things go here */ + /********************************/ + + l_nhcore_call(NHCORE_MOVELOOP_TURN); + + if (Glib) + glibr(); + nh_timeout(); + run_regions(); + + if (u.ublesscnt) + u.ublesscnt--; + + /* One possible result of prayer is healing. Whether or + * not you get healed depends on your current hit points. + * If you are allowed to regenerate during the prayer, + * the end-of-prayer calculation messes up on this. + * Another possible result is rehumanization, which + * requires that encumbrance and movement rate be + * recalculated. + */ + if (u.uinvulnerable) { + /* for the moment at least, you're in tiptop shape */ + mvl_wtcap = UNENCUMBERED; + } else if (!Upolyd ? (u.uhp < u.uhpmax) + : (u.mh < u.mhmax + || gy.youmonst.data->mlet == S_EEL)) { + /* maybe heal */ + regen_hp(mvl_wtcap); + } - if (context.move) { - /* actual time passed */ - youmonst.movement -= NORMAL_SPEED; - - do { /* hero can't move this turn loop */ - wtcap = encumber_msg(); - - context.mon_moving = TRUE; - do { - monscanmove = movemon(); - if (youmonst.movement >= NORMAL_SPEED) - break; /* it's now your turn */ - } while (monscanmove); - context.mon_moving = FALSE; - - if (!monscanmove && youmonst.movement < NORMAL_SPEED) { - /* both hero and monsters are out of steam this round */ - struct monst *mtmp; - - /* set up for a new turn */ - mcalcdistress(); /* adjust monsters' trap, blind, etc */ - - /* reallocate movement rations to monsters; don't need - to skip dead monsters here because they will have - been purged at end of their previous round of moving */ - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) - mtmp->movement += mcalcmove(mtmp); - - /* occasionally add another monster; since this takes - place after movement has been allotted, the new - monster effectively loses its first turn */ - /* Change for NLE: Optionally disable monster spawning */ - if (nle_spawn_monsters() && !rn2(u.uevent.udemigod ? 25 - : (depth(&u.uz) > depth(&stronghold_level)) ? 50 - : 70)) - (void) makemon((struct permonst *) 0, 0, 0, - NO_MM_FLAGS); - - /* calculate how much time passed. */ - if (u.usteed && u.umoved) { - /* your speed doesn't augment steed's speed */ - moveamt = mcalcmove(u.usteed); - } else { - moveamt = youmonst.data->mmove; - - if (Very_fast) { /* speed boots, potion, or spell */ - /* gain a free action on 2/3 of turns */ - if (rn2(3) != 0) - moveamt += NORMAL_SPEED; - } else if (Fast) { /* intrinsic */ - /* gain a free action on 1/3 of turns */ - if (rn2(3) == 0) - moveamt += NORMAL_SPEED; - } + /* moving around while encumbered is hard work */ + if (mvl_wtcap > MOD_ENCUMBER && u.umoved) { + if (!(mvl_wtcap < EXT_ENCUMBER ? svm.moves % 30 + : svm.moves % 10)) { + overexert_hp(); } + } - switch (wtcap) { - case UNENCUMBERED: - break; - case SLT_ENCUMBER: - moveamt -= (moveamt / 4); - break; - case MOD_ENCUMBER: - moveamt -= (moveamt / 2); - break; - case HVY_ENCUMBER: - moveamt -= ((moveamt * 3) / 4); - break; - case EXT_ENCUMBER: - moveamt -= ((moveamt * 7) / 8); - break; - default: - break; - } + regen_pw(mvl_wtcap); - youmonst.movement += moveamt; - if (youmonst.movement < 0) - youmonst.movement = 0; - settrack(); - - monstermoves++; - moves++; - - /********************************/ - /* once-per-turn things go here */ - /********************************/ - - if (Glib) - glibr(); - nh_timeout(); - run_regions(); - - if (u.ublesscnt) - u.ublesscnt--; - if (flags.time && !context.run) - iflags.time_botl = TRUE; - - /* One possible result of prayer is healing. Whether or - * not you get healed depends on your current hit points. - * If you are allowed to regenerate during the prayer, - * the end-of-prayer calculation messes up on this. - * Another possible result is rehumanization, which - * requires that encumbrance and movement rate be - * recalculated. - */ - if (u.uinvulnerable) { - /* for the moment at least, you're in tiptop shape */ - wtcap = UNENCUMBERED; - } else if (!Upolyd ? (u.uhp < u.uhpmax) - : (u.mh < u.mhmax - || youmonst.data->mlet == S_EEL)) { - /* maybe heal */ - regen_hp(wtcap); - } + if (!u.uinvulnerable) { + if (Teleportation && !rn2(85)) { + coordxy old_ux = u.ux, old_uy = u.uy; - /* moving around while encumbered is hard work */ - if (wtcap > MOD_ENCUMBER && u.umoved) { - if (!(wtcap < EXT_ENCUMBER ? moves % 30 - : moves % 10)) { - if (Upolyd && u.mh > 1) { - u.mh--; - context.botl = TRUE; - } else if (!Upolyd && u.uhp > 1) { - u.uhp--; - context.botl = TRUE; - } else { - You("pass out from exertion!"); - exercise(A_CON, FALSE); - fall_asleep(-10, FALSE); + tele(); + if (u.ux != old_ux || u.uy != old_uy) { + if (!next_to_u()) { + check_leash(old_ux, old_uy); } + /* clear doagain keystrokes */ + cmdq_clear(CQ_CANNED); + cmdq_clear(CQ_REPEAT); } } - - if (u.uen < u.uenmax - && ((wtcap < MOD_ENCUMBER - && (!(moves % ((MAXULEV + 8 - u.ulevel) - * (Role_if(PM_WIZARD) ? 3 : 4) - / 6)))) || Energy_regeneration)) { - u.uen += rn1( - (int) (ACURR(A_WIS) + ACURR(A_INT)) / 15 + 1, 1); - if (u.uen > u.uenmax) - u.uen = u.uenmax; - context.botl = TRUE; - if (u.uen == u.uenmax) - interrupt_multi("You feel full of energy."); - } - - if (!u.uinvulnerable) { - if (Teleportation && !rn2(85)) { - xchar old_ux = u.ux, old_uy = u.uy; - - tele(); - if (u.ux != old_ux || u.uy != old_uy) { - if (!next_to_u()) { - check_leash(old_ux, old_uy); - } - /* clear doagain keystrokes */ - pushch(0); - savech(0); - } - } - /* delayed change may not be valid anymore */ - if ((change == 1 && !Polymorph) - || (change == 2 && u.ulycn == NON_PM)) - change = 0; - if (Polymorph && !rn2(100)) - change = 1; - else if (u.ulycn >= LOW_PM && !Upolyd - && !rn2(80 - (20 * night()))) - change = 2; - if (change && !Unchanging) { - if (multi >= 0) { - stop_occupation(); - if (change == 1) - polyself(0); - else - you_were(); - change = 0; - } + /* delayed change may not be valid anymore */ + if ((mvl_change == 1 && !Polymorph) + || (mvl_change == 2 && u.ulycn == NON_PM)) + mvl_change = 0; + if (Polymorph && !rn2(100)) + mvl_change = 1; + else if (ismnum(u.ulycn) && !Upolyd + && !rn2(80 - (20 * night()))) + mvl_change = 2; + if (mvl_change && !Unchanging) { + if (gm.multi >= 0) { + stop_occupation(); + if (mvl_change == 1) + polyself(POLY_NOFLAGS); + else + you_were(); + mvl_change = 0; } } + } - if (Searching && multi >= 0) - (void) dosearch0(1); - if (Warning) - warnreveal(); - mkot_trap_warn(); - dosounds(); - do_storms(); - gethungry(); - age_spells(); - exerchk(); - invault(); - if (u.uhave.amulet) - amulet(); - if (!rn2(40 + (int) (ACURR(A_DEX) * 3))) - u_wipe_engr(rnd(3)); - if (u.uevent.udemigod && !u.uinvulnerable) { - if (u.udg_cnt) - u.udg_cnt--; - if (!u.udg_cnt) { - intervene(); - u.udg_cnt = rn1(200, 50); - } + if (Searching && !svl.level.flags.noautosearch + && gm.multi >= 0) + (void) dosearch0(1); + if (Warning) + warnreveal(); + if (gw.were_changes) { + /* update innate intrinsics (mainly Drain_resistance) */ + set_uasmon(); + } + mkot_trap_warn(); + dosounds(); + do_storms(); + gethungry(); + age_spells(); + exerchk(); + invault(); + if (u.uhave.amulet) + amulet(); + if (!rn2(40 + (int) (ACURR(A_DEX) * 3))) + u_wipe_engr(rnd(3)); + if (u.uevent.udemigod && !u.uinvulnerable) { + if (u.udg_cnt) + u.udg_cnt--; + if (!u.udg_cnt) { + intervene(); + u.udg_cnt = rn1(200, 50); } - restore_attrib(); - /* underwater and waterlevel vision are done here */ - if (Is_waterlevel(&u.uz) || Is_airlevel(&u.uz)) - movebubbles(); - else if (Is_firelevel(&u.uz)) - fumaroles(); - else if (Underwater) - under_water(0); - /* vision while buried done here */ - else if (u.uburied) - under_ground(0); - - /* when immobile, count is in turns */ - if (multi < 0) { - if (++multi == 0) { /* finished yet? */ - unmul((char *) 0); - /* if unmul caused a level change, take it now */ - if (u.utotype) - deferred_goto(); - } + } +/* XXX This should be recoded to use something like regions - a list of + * things that are active and need to be handled that is dynamically + * maintained and not a list of special cases. */ + /* vision will be updated as bubbles move */ + if (Is_waterlevel(&u.uz) || Is_airlevel(&u.uz)) + movebubbles(); + else if (svl.level.flags.fumaroles) + fumaroles(); + + /* when immobile, count is in turns */ + if (gm.multi < 0) { + runmode_delay_output(); + if (++gm.multi == 0) { /* finished yet? */ + unmul((char *) 0); + /* if unmul caused a level change, take it now */ + if (u.utotype) + deferred_goto(); } } - } while (youmonst.movement < NORMAL_SPEED); /* hero can't move */ + } + } while (u.umovement < NORMAL_SPEED); /* hero can't move */ + + /******************************************/ + /* once-per-hero-took-time things go here */ + /******************************************/ - /******************************************/ - /* once-per-hero-took-time things go here */ - /******************************************/ + gh.hero_seq++; /* moves*8 + n for n == 1..7 */ + + /* although we checked for encumbrance above, we need to + check again for message purposes, as the weight of + inventory may have changed in, e.g., nh_timeout(); we do + need two checks here so that the player gets feedback + immediately if their own action encumbered them */ + encumber_msg(); #ifdef STATUS_HILITES - if (iflags.hilite_delta) - status_eval_next_unhilite(); + if (iflags.hilite_delta) + status_eval_next_unhilite(); #endif - if (context.bypasses) - clear_bypasses(); + if (svm.moves >= svc.context.seer_turn) { if ((u.uhave.amulet || Clairvoyant) && !In_endgame(&u.uz) - && !BClairvoyant && !(moves % 15) && !rn2(2)) + && !BClairvoyant) do_vicinity_map((struct obj *) 0); - if (u.utrap && u.utraptype == TT_LAVA) - sink_into_lava(); - /* when/if hero escapes from lava, he can't just stay there */ - else if (!u.umoved) - (void) pooleffects(FALSE); - - } /* actual time passed */ - - /****************************************/ - /* once-per-player-input things go here */ - /****************************************/ - - clear_splitobjs(); - find_ac(); - if (!context.mv || Blind) { - /* redo monsters if hallu or wearing a helm of telepathy */ - if (Hallucination) { /* update screen randomly */ - see_monsters(); - see_objects(); - see_traps(); - if (u.uswallow) - swallowed(0); - } else if (Unblind_telepat) { - see_monsters(); - } else if (Warning || Warn_of_mon) - see_monsters(); - - if (vision_full_recalc) - vision_recalc(0); /* vision! */ + /* we maintain this counter even when clairvoyance isn't + taking place; on average, go again 30 turns from now */ + svc.context.seer_turn = svm.moves + (long) rn1(31, 15); /*15..45*/ + /* [it used to be that on every 15th turn, there was a 50% + chance of farsight, so it could happen as often as every + 15 turns or theoretically never happen at all; but when + a fast hero got multiple moves on that 15th turn, it + could actually happen more than once on the same turn!] */ } - if (context.botl || context.botlx) { - bot(); - curs_on_u(); - } else if (iflags.time_botl) { - timebot(); - curs_on_u(); + /* [fast hero who gets multiple moves per turn ends up sinking + multiple times per turn; is that what we really want?] */ + if (u.utrap && u.utraptype == TT_LAVA) + sink_into_lava(); + /* when/if hero escapes from lava, he can't just stay there */ + else if (!u.umoved) + (void) pooleffects(FALSE); + + /* vision while buried or underwater is updated here */ + if (Underwater) + under_water(0); + else if (u.uburied) + under_ground(0); + + see_nearby_monsters(); + } /* actual time passed */ + + /****************************************/ + /* once-per-player-input things go here */ + /****************************************/ + + clear_splitobjs(); + + /* the Amulet of Yendor gives a wish when initially picked up */ + if (u.uhave.amulet && !u.uevent.amulet_wish) { + u.uevent.amulet_wish = 1; + display_nhwindow(WIN_MESSAGE, TRUE); + urgent_pline("The Amulet is bestowing a wish upon you!"); + makewish(); + } + + find_ac(); + if (!svc.context.mv || Blind) { + /* redo monsters if hallu or wearing a helm of telepathy */ + if (Hallucination) { /* update screen randomly */ + see_monsters(); + see_objects(); + see_traps(); + if (u.uswallow) + swallowed(0); + } else if (Unblind_telepat || Warning || Warn_of_mon + /* this is needed for the case where you saw a monster + due to being next to it while it's in a gas cloud + and then you moved away; it should no longer be seen + when that happens, even if it hasn't moved */ + || any_visible_region()) { /* TODO: optimize this */ + see_monsters(); } + if (gv.vision_full_recalc) + vision_recalc(0); /* vision! */ + } + if (disp.botl || disp.botlx) { + bot(); + curs_on_u(); + } else if (disp.time_botl) { + timebot(); + curs_on_u(); + } - context.move = 1; + m_everyturn_effect(&gy.youmonst); - if (multi >= 0 && occupation) { -#if defined(MICRO) || defined(WIN32) - abort_lev = 0; - if (kbhit()) { - if ((ch = pgetchar()) == ABORT) - abort_lev++; - else - pushch(ch); - } - if (!abort_lev && (*occupation)() == 0) + svc.context.move = 1; + + if (gm.multi >= 0 && go.occupation) { +#if defined(MICRO) || defined(WIN32CON) + mvl_abort_lev = 0; + if (kbhit()) { + char ch; + + if ((ch = pgetchar()) == ABORT) + mvl_abort_lev++; + else + cmdq_add_key(CQ_CANNED, ch); + } + if (!mvl_abort_lev && (*go.occupation)() == 0) #else - if ((*occupation)() == 0) + if ((*go.occupation)() == 0) #endif - occupation = 0; - if ( + go.occupation = 0; + if ( #if defined(MICRO) || defined(WIN32) - abort_lev || + mvl_abort_lev || #endif - monster_nearby()) { - stop_occupation(); - reset_eat(); - } -#if defined(MICRO) || defined(WIN32) - if (!(++occtime % 7)) - display_nhwindow(WIN_MAP, FALSE); -#endif - continue; + monster_nearby()) { + stop_occupation(); + reset_eat(); } + runmode_delay_output(); + return; + } + + u.umoved = FALSE; - if (iflags.sanity_check || iflags.debug_fuzzer) - sanity_check(); + if (gm.multi > 0) { + lookaround(); + runmode_delay_output(); + if (!gm.multi) { + /* lookaround may clear multi */ + svc.context.move = 0; + return; + } + if (svc.context.mv) { + if (gm.multi < COLNO && !--gm.multi) + end_running(TRUE); + domove(); + } else { + --gm.multi; + nhassert(gc.command_count != 0); + rhack(gc.cmd_key); + } + } else if (gm.multi == 0) { +#ifdef MAIL + ckmailstatus(); +#endif + rhack(0); + } + if (u.utotype) /* change dungeon level */ + deferred_goto(); /* after rhack() */ + if (gv.vision_full_recalc) + vision_recalc(0); /* vision! */ #ifdef CLIPPING - /* just before rhack */ - cliparound(u.ux, u.uy); + /* after rhack() and vision_recalc() so that the map is redrawn + once with correct vision data, not twice (overshoot+correct) */ + cliparound(u.ux, u.uy); #endif + /* when running in non-tport mode, this gets done through domove() */ + if ((!svc.context.run || flags.runmode == RUN_TPORT) + && (gm.multi && (!svc.context.travel ? !(gm.multi % 7) + : !(svm.moves % 7L)))) { + if (flags.time && svc.context.run) + disp.botl = TRUE; + /* [should this be flush_screen() instead?] */ + display_nhwindow(WIN_MAP, FALSE); + } - u.umoved = FALSE; + if (gl.luacore && nhcb_counts[NHCB_END_TURN]) { + lua_getglobal(gl.luacore, "nh_callback_run"); + lua_pushstring(gl.luacore, nhcb_name[NHCB_END_TURN]); + nhl_pcall_handle(gl.luacore, 1, 0, "moveloop_core", NHLpa_panic); + lua_settop(gl.luacore, 0); + } +} - if (multi > 0) { - lookaround(); - if (!multi) { - /* lookaround may clear multi */ - context.move = 0; - if (flags.time) - context.botl = TRUE; - continue; - } - if (context.mv) { - if (multi < COLNO && !--multi) - context.travel = context.travel1 = context.mv = - context.run = 0; - domove(); - } else { - --multi; - rhack(save_cm); - } - } else if (multi == 0) { -#ifdef MAIL - ckmailstatus(); -#endif - rhack((char *) 0); - } - if (u.utotype) /* change dungeon level */ - deferred_goto(); /* after rhack() */ - /* !context.move here: multiple movement command stopped */ - else if (flags.time && (!context.move || !context.mv)) - context.botl = TRUE; +staticfn void +maybe_do_tutorial(void) +{ + s_level *sp = find_level("tut-1"); - if (vision_full_recalc) - vision_recalc(0); /* vision! */ - /* when running in non-tport mode, this gets done through domove() */ - if ((!context.run || flags.runmode == RUN_TPORT) - && (multi && (!context.travel ? !(multi % 7) : !(moves % 7L)))) { - if (flags.time && context.run) - context.botl = TRUE; - /* [should this be flush_screen() instead?] */ - display_nhwindow(WIN_MAP, FALSE); - } + if (!sp) + return; + + if (ask_do_tutorial()) { + assign_level(&u.ucamefrom, &u.uz); + iflags.nofollowers = TRUE; + schedule_goto(&sp->dlevel, UTOTYPE_NONE, + "Entering the tutorial.", (char *) 0); + deferred_goto(); + vision_recalc(0); + docrt(); + iflags.nofollowers = FALSE; + } else { + /* no tutorial, so okay to process mention_decor now */ + rcfile_only_this_option(opt_mention_decor); + } +} + +void +moveloop(boolean resuming) +{ + moveloop_preamble(resuming); + + if (!resuming) + maybe_do_tutorial(); + + /* process one deferred option post-tutorial */ + rcfile_only_this_option(opt_mention_decor); + + for (;;) { + moveloop_core(); } } +staticfn void +regen_pw(int wtcap) +{ + if (u.uen < u.uenmax + && ((wtcap < MOD_ENCUMBER + && (!(svm.moves % ((MAXULEV + 8 - u.ulevel) + * (Role_if(PM_WIZARD) ? 3 : 4) + / 6)))) || Energy_regeneration)) { + int upper = (int) (ACURR(A_WIS) + ACURR(A_INT)) / 15 + 1; + + if (EMagical_breathing) + upper += 2; + + u.uen += rn1(upper, 1); + if (u.uen > u.uenmax) + u.uen = u.uenmax; + disp.botl = TRUE; + if (u.uen == u.uenmax) + interrupt_multi("You feel full of energy."); + } +} + +#define U_CAN_REGEN() (Regeneration || (Sleepy && u.usleep)) + /* maybe recover some lost health (or lose some when an eel out of water) */ -STATIC_OVL void -regen_hp(wtcap) -int wtcap; +staticfn void +regen_hp(int wtcap) { int heal = 0; boolean reached_full = FALSE, @@ -476,19 +648,20 @@ int wtcap; if (Upolyd) { if (u.mh < 1) { /* shouldn't happen... */ rehumanize(); - } else if (youmonst.data->mlet == S_EEL - && !is_pool(u.ux, u.uy) && !Is_waterlevel(&u.uz)) { + } else if (gy.youmonst.data->mlet == S_EEL + && !is_pool(u.ux, u.uy) && !Is_waterlevel(&u.uz) + && !Breathless) { /* eel out of water loses hp, similar to monster eels; as hp gets lower, rate of further loss slows down */ if (u.mh > 1 && !Regeneration && rn2(u.mh) > rn2(8) - && (!Half_physical_damage || !(moves % 2L))) + && (!Half_physical_damage || !(svm.moves % 2L))) heal = -1; } else if (u.mh < u.mhmax) { - if (Regeneration || (encumbrance_ok && !(moves % 20L))) + if (U_CAN_REGEN() || (encumbrance_ok && !(svm.moves % 20L))) heal = 1; } if (heal) { - context.botl = TRUE; + disp.botl = TRUE; u.mh += heal; reached_full = (u.mh == u.mhmax); } @@ -499,28 +672,16 @@ int wtcap; no !Upolyd check here, so poly'd hero recovered lost u.uhp once u.mh reached u.mhmax; that may have been convenient for the player, but it didn't make sense for gameplay...] */ - if (u.uhp < u.uhpmax && (encumbrance_ok || Regeneration)) { - if (u.ulevel > 9) { - if (!(moves % 3L)) { - int Con = (int) ACURR(A_CON); - - if (Con <= 12) { - heal = 1; - } else { - heal = rnd(Con); - if (heal > u.ulevel - 9) - heal = u.ulevel - 9; - } - } - } else { /* u.ulevel <= 9 */ - if (!(moves % (long) ((MAXULEV + 12) / (u.ulevel + 2) + 1))) - heal = 1; - } - if (Regeneration && !heal) - heal = 1; + if (u.uhp < u.uhpmax && (encumbrance_ok || U_CAN_REGEN())) { + heal = (u.ulevel + (int)ACURR(A_CON)) > rn2(100); + + if (U_CAN_REGEN()) + heal += 1; + if (Sleepy && u.usleep) + heal++; if (heal) { - context.botl = TRUE; + disp.botl = TRUE; u.uhp += heal; if (u.uhp > u.uhpmax) u.uhp = u.uhpmax; @@ -534,40 +695,69 @@ int wtcap; interrupt_multi("You are in full health."); } +#undef U_CAN_REGEN + void -stop_occupation() +stop_occupation(void) { - if (occupation) { + if (go.occupation) { if (!maybe_finished_meal(TRUE)) - You("stop %s.", occtxt); - occupation = 0; - context.botl = TRUE; /* in case u.uhs changed */ + You("stop %s.", go.occtxt); + go.occupation = (int (*)(void)) 0; + disp.botl = TRUE; /* in case u.uhs changed */ nomul(0); - pushch(0); - } else if (multi >= 0) { + } else if (gm.multi >= 0) { nomul(0); } + cmdq_clear(CQ_CANNED); } void -display_gamewindows() +init_sound_disp_gamewindows(void) { + int menu_behavior = MENU_BEHAVE_STANDARD; + + activate_chosen_soundlib(); + + if (iflags.wc_splash_screen && !flags.randomall) { + SoundAchievement(0, sa2_splashscreen, 0); + /* ToDo: new splash screen invocation will go here */ + } else { + SoundAchievement(0, sa2_newgame_nosplash, 0); + } + +#ifdef CHANGE_COLOR + /* init_nhwindows() has already been called, so before + creating the windows, check to see if there are any + palette entries to alter */ + change_palette(); +#endif + WIN_MESSAGE = create_nhwindow(NHW_MESSAGE); if (VIA_WINDOWPORT()) { - status_initialize(0); + status_initialize(FALSE); } else { WIN_STATUS = create_nhwindow(NHW_STATUS); } WIN_MAP = create_nhwindow(NHW_MAP); WIN_INVEN = create_nhwindow(NHW_MENU); + if (WIN_INVEN != WIN_ERR) + adjust_menu_promptstyle(WIN_INVEN, &iflags.menu_headings); + +#ifdef TTY_PERM_INVENT + if (WINDOWPORT(tty) && WIN_INVEN != WIN_ERR) { + menu_behavior = MENU_BEHAVE_PERMINV; + prepare_perminvent(WIN_INVEN); + } +#endif /* in case of early quit where WIN_INVEN could be destroyed before ever having been used, use it here to pacify the Qt interface */ - start_menu(WIN_INVEN), end_menu(WIN_INVEN, (char *) 0); + start_menu(WIN_INVEN, menu_behavior), end_menu(WIN_INVEN, (char *) 0); -#ifdef MAC +#ifdef MACOS9 /* This _is_ the right place for this - maybe we will - * have to split display_gamewindows into create_gamewindows - * and show_gamewindows to get rid of this ifdef... + * have to split init_sound_disp_gamewindows into + * create_gamewindows and show_gamewindows to get rid of this ifdef... */ if (!strcmp(windowprocs.name, "mac")) SanePositions(); @@ -583,27 +773,29 @@ display_gamewindows() display_nhwindow(WIN_MESSAGE, FALSE); clear_glyph_buffer(); display_nhwindow(WIN_MAP, FALSE); +#ifdef TTY_PERM_INVENT + if (iflags.perm_invent_pending) + check_perm_invent_again(); +#endif } void -newgame() +newgame(void) { int i; -#ifdef MFLOPPY - gameDiskPrompt(); -#endif - - context.botlx = TRUE; - context.ident = 1; - context.stethoscope_move = -1L; - context.warnlevel = 1; - context.next_attrib_check = 600L; /* arbitrary first setting */ - context.tribute.enabled = TRUE; /* turn on 3.6 tributes */ - context.tribute.tributesz = sizeof(struct tribute_info); + /* make sure welcome messages are given before noticing monsters */ + notice_mon_off(); + disp.botlx = TRUE; + svc.context.ident = 2; /* id 1 is reserved for gy.youmonst */ + svc.context.warnlevel = 1; + svc.context.next_attrib_check = 600L; /* arbitrary first setting */ + svc.context.tribute.enabled = TRUE; /* turn on 3.6 tributes */ + svc.context.tribute.tributesz = sizeof(struct tribute_info); + get_nhuuid(); for (i = LOW_PM; i < NUMMONS; i++) - mvitals[i].mvflags = mons[i].geno & G_NOCORPSE; + svm.mvitals[i].mvflags = mons[i].geno & G_NOCORPSE; /* Use lgen for game / dungeon initialisation */ nle_swap_to_lgen(0); @@ -619,8 +811,10 @@ newgame() * in hero's initial inventory */ init_artifacts(); /* before u_init() in case $WIZKIT specifies * any artifacts */ - u_init(); + u_init_misc(); + l_nhcore_init(); /* create a Lua state that lasts until end of game */ + reset_glyphmap(gm_newgame); #ifndef NO_SIGNAL (void) signal(SIGINT, (SIG_RET_TYPE) done1); #endif @@ -628,24 +822,34 @@ newgame() if (iflags.news) display_file(NEWS, FALSE); #endif - load_qtlist(); /* load up the quest text info */ /* quest_init(); -- Now part of role_init() */ mklev(); u_on_upstairs(); - if (wizard) - obj_delivery(FALSE); /* finish wizkit */ vision_reset(); /* set up internals for level (after mklev) */ check_special_room(FALSE); if (MON_AT(u.ux, u.uy)) - mnexto(m_at(u.ux, u.uy)); + mnexto(m_at(u.ux, u.uy), RLOC_NOMSG); (void) makedog(); + + u_init_inventory_attrs(); docrt(); + flush_screen(1); + bot(); + while (u.uroleplay.reroll && reroll_menu()) { + u_init_inventory_attrs(); + bot(); + } + u_init_skills_discoveries(); + + if (wizard) { + read_wizkit(); + obj_delivery(FALSE); /* finish wizkit */ + } if (flags.legacy) { - flush_screen(1); - com_pager(1); + com_pager(u.uroleplay.pauper ? "pauper_legacy" : "legacy"); } urealtime.realtime = 0L; @@ -657,20 +861,27 @@ newgame() /* Success! */ welcome(TRUE); + notice_mon_on(); /* now we can notice monsters */ + if (a11y.glyph_updates) + (void) dolookaround(); + else + notice_all_mons(TRUE); /* Restore CORE RNG */ nle_swap_to_core(0); - + return; } -/* show "welcome [back] to nethack" message at program startup */ +/* show "welcome [back] to NetHack" message at program startup */ void -welcome(new_game) -boolean new_game; /* false => restoring an old game */ +welcome(boolean new_game) /* false => restoring an old game */ { char buf[BUFSZ]; - boolean currentgend = Upolyd ? u.mfemale : flags.female; + boolean currentgend = Upolyd ? u.mfemale : flags.female, + adrift = (u.ualign.type != u.ualignbase[A_CURRENT]); + + l_nhcore_call(new_game ? NHCORE_START_NEW_GAME : NHCORE_RESTORE_OLD_GAME); /* skip "welcome back" if restoring a doomed character */ if (!new_game && Upolyd && ugenocided()) { @@ -679,6 +890,9 @@ boolean new_game; /* false => restoring an old game */ return; } + if (Hallucination) + pline("NetHack is filmed in front of an undead studio audience."); + /* * The "welcome back" message always describes your innate form * even when polymorphed or wearing a helm of opposite alignment. @@ -688,63 +902,87 @@ boolean new_game; /* false => restoring an old game */ * restores it's only shown if different from its original value. */ *buf = '\0'; +#if 0 if (new_game || u.ualignbase[A_ORIGINAL] != u.ualignbase[A_CURRENT]) Sprintf(eos(buf), " %s", align_str(u.ualignbase[A_ORIGINAL])); - if (!urole.name.f +#else + /* + * 2026-04-24 + * GitHub issue https://github.com/NetHack/NetHack/issues/537 + * "Judging by the comment above, it should display your new alignment + * if it was changed, so align_str(u.ualignbase[A_CURRENT]) would + * probably be more appropriate. This won't affect the new game message." + * + * That is followed by a suggestion to revisit the matter (paraphrased): + * "That's actually intentional; the comment oversimplifies. + * When it was implemented, it may have been the only way to tell that + * you had converted alignment. Now ^X mentions your starting alignment + * if base alignment has been changed, so revisiting this welcome back + * message." + */ + if (new_game || u.ualignbase[A_ORIGINAL] != u.ualignbase[A_CURRENT] || adrift) + Sprintf(eos(buf), " %s%s", + adrift ? "adrift " : "", + adrift ? align_str(u.ualign.type) + : align_str(u.ualignbase[A_CURRENT])); +#endif + if (!gu.urole.name.f && (new_game - ? (urole.allow & ROLE_GENDMASK) == (ROLE_MALE | ROLE_FEMALE) - : currentgend != flags.initgend)) + ? (gu.urole.allow & ROLE_GENDMASK) == (ROLE_MALE | ROLE_FEMALE) + : currentgend != flags.initgend)) Sprintf(eos(buf), " %s", genders[currentgend].adj); - - pline(new_game ? "%s %s, welcome to NetHack! You are a%s %s %s." - : "%s %s, the%s %s %s, welcome back to NetHack!", - Hello((struct monst *) 0), plname, buf, urace.adj, - (currentgend && urole.name.f) ? urole.name.f : urole.name.m); + Sprintf(eos(buf), " %s %s", gu.urace.adj, + (currentgend && gu.urole.name.f) ? gu.urole.name.f + : gu.urole.name.m); + + pline(new_game ? "%s %s, welcome to NetHack! You are a%s." + : "%s %s, the%s, welcome back to NetHack!", + Hello((struct monst *) 0), svp.plname, buf); + + if (new_game) { + /* guarantee that 'major' event category is never empty */ + livelog_printf(LL_ACHIEVE, "%s the%s entered the dungeon", + svp.plname, buf); + } else { + /* if restoring in Gehennom, give same hot/smoky message as when + first entering it */ + hellish_smoke_mesg(); + /* remind player of the level annotation, like in goto_level() */ + print_level_annotation(); + } } #ifdef POSITIONBAR -STATIC_DCL void -do_positionbar() +staticfn void +do_positionbar(void) { + /* FIXME: this will break if any coordinate is too big for (char); + the sys/msdos/vid*.c code uses (unsigned char) which is less + vulnerable but not guaranteed to be able to hold coordxy values; + also, there doesn't appear to be any need for this to be static, + nor to contain pairs of (> or <) and x; it could just be a full + line of spaces and > or < characters with update_positionbar() + revised to reconstruct the x values for non-space characters */ static char pbar[COLNO]; char *p; + stairway *stway; + coordxy x, y; + int glyph, symbol; p = pbar; - /* up stairway */ - if (upstair.sx - && (glyph_to_cmap(level.locations[upstair.sx][upstair.sy].glyph) - == S_upstair - || glyph_to_cmap(level.locations[upstair.sx][upstair.sy].glyph) - == S_upladder)) { - *p++ = '<'; - *p++ = upstair.sx; - } - if (sstairs.sx - && (glyph_to_cmap(level.locations[sstairs.sx][sstairs.sy].glyph) - == S_upstair - || glyph_to_cmap(level.locations[sstairs.sx][sstairs.sy].glyph) - == S_upladder)) { - *p++ = '<'; - *p++ = sstairs.sx; - } - - /* down stairway */ - if (dnstair.sx - && (glyph_to_cmap(level.locations[dnstair.sx][dnstair.sy].glyph) - == S_dnstair - || glyph_to_cmap(level.locations[dnstair.sx][dnstair.sy].glyph) - == S_dnladder)) { - *p++ = '>'; - *p++ = dnstair.sx; - } - if (sstairs.sx - && (glyph_to_cmap(level.locations[sstairs.sx][sstairs.sy].glyph) - == S_dnstair - || glyph_to_cmap(level.locations[sstairs.sx][sstairs.sy].glyph) - == S_dnladder)) { - *p++ = '>'; - *p++ = sstairs.sx; - } + /* TODO: use the same method as getpos() so objects don't cover stairs */ + /* FIXME: traversing 'stairs' list ignores mimics that pose as stairs */ + for (stway = gs.stairs; stway; stway = stway->next) { + x = stway->sx; + y = stway->sy; + glyph = levl[x][y].glyph; + symbol = glyph_to_cmap(glyph); + + if (is_cmap_stairs(symbol)) { + *p++ = (stway->up ? '<' : '>'); + *p++ = (char) x; + } + } /* hero location */ if (u.ux) { @@ -758,186 +996,32 @@ do_positionbar() } #endif -STATIC_DCL void -interrupt_multi(msg) -const char *msg; +staticfn void +interrupt_multi(const char *msg) { - if (multi > 0 && !context.travel && !context.run) { + if (gm.multi > 0 && !svc.context.travel && !svc.context.run) { nomul(0); if (flags.verbose && msg) Norep("%s", msg); } } -/* - * Argument processing helpers - for xxmain() to share - * and call. - * - * These should return TRUE if the argument matched, - * whether the processing of the argument was - * successful or not. - * - * Most of these do their thing, then after returning - * to xxmain(), the code exits without starting a game. - * - */ - -static struct early_opt earlyopts[] = { - {ARG_DEBUG, "debug", 5, TRUE}, - {ARG_VERSION, "version", 4, TRUE}, - {ARG_SHOWPATHS, "showpaths", 9, FALSE}, -#ifdef WIN32 - {ARG_WINDOWS, "windows", 4, TRUE}, -#endif -}; - -#ifdef WIN32 -extern int FDECL(windows_early_options, (const char *)); -#endif - -/* - * Returns: - * 0 = no match - * 1 = found and skip past this argument - * 2 = found and trigger immediate exit - */ - -int -argcheck(argc, argv, e_arg) -int argc; -char *argv[]; -enum earlyarg e_arg; +/* convert from time_t to number of seconds */ +long +timet_to_seconds(time_t ttim) { - int i, idx; - boolean match = FALSE; - char *userea = (char *)0; - const char *dashdash = ""; - - for (idx = 0; idx < SIZE(earlyopts); idx++) { - if (earlyopts[idx].e == e_arg) - break; - } - if ((idx >= SIZE(earlyopts)) || (argc <= 1)) - return FALSE; - - for (i = 0; i < argc; ++i) { - if (argv[i][0] != '-') - continue; - if (argv[i][1] == '-') { - userea = &argv[i][2]; - dashdash = "-"; - } else { - userea = &argv[i][1]; - } - match = match_optname(userea, earlyopts[idx].name, - earlyopts[idx].minlength, - earlyopts[idx].valallowed); - if (match) break; - } - - if (match) { - const char *extended_opt = index(userea, ':'); - - if (!extended_opt) - extended_opt = index(userea, '='); - switch(e_arg) { - case ARG_DEBUG: - if (extended_opt) { - extended_opt++; - debug_fields(extended_opt); - } - return 1; - case ARG_VERSION: { - boolean insert_into_pastebuf = FALSE; - - if (extended_opt) { - extended_opt++; - if (match_optname(extended_opt, "paste", 5, FALSE)) { - insert_into_pastebuf = TRUE; - } else { - raw_printf( - "-%sversion can only be extended with -%sversion:paste.\n", - dashdash, dashdash); - return TRUE; - } - } - early_version_info(insert_into_pastebuf); - return 2; - } - case ARG_SHOWPATHS: { - return 2; - } -#ifdef WIN32 - case ARG_WINDOWS: { - if (extended_opt) { - extended_opt++; - return windows_early_options(extended_opt); - } - } -#endif - default: - break; - } - }; - return FALSE; + /* for Unix-based and Posix-compliant systems, a cast to 'long' would + suffice but the C Standard doesn't require time_t to be that simple */ + return timet_delta(ttim, (time_t) 0); } -/* - * These are internal controls to aid developers with - * testing and debugging particular aspects of the code. - * They are not player options and the only place they - * are documented is right here. No gameplay is altered. - * - * test - test whether this parser is working - * ttystatus - TTY: - * immediateflips - WIN32: turn off display performance - * optimization so that display output - * can be debugged without buffering. - */ -STATIC_OVL void -debug_fields(opts) -const char *opts; +/* calculate the difference in seconds between two time_t values */ +long +timet_delta(time_t etim, time_t stim) /* end and start times */ { - char *op; - boolean negated = FALSE; - - while ((op = index(opts, ',')) != 0) { - *op++ = 0; - /* recurse */ - debug_fields(op); - } - if (strlen(opts) > BUFSZ / 2) - return; - - - /* strip leading and trailing white space */ - while (isspace((uchar) *opts)) - opts++; - op = eos((char *) opts); - while (--op >= opts && isspace((uchar) *op)) - *op = '\0'; - - if (!*opts) { - /* empty */ - return; - } - while ((*opts == '!') || !strncmpi(opts, "no", 2)) { - if (*opts == '!') - opts++; - else - opts += 2; - negated = !negated; - } - if (match_optname(opts, "test", 4, FALSE)) - iflags.debug.test = negated ? FALSE : TRUE; -#ifdef TTY_GRAPHICS - if (match_optname(opts, "ttystatus", 9, FALSE)) - iflags.debug.ttystatus = negated ? FALSE : TRUE; -#endif -#ifdef WIN32 - if (match_optname(opts, "immediateflips", 14, FALSE)) - iflags.debug.immediateflips = negated ? FALSE : TRUE; -#endif - return; + /* difftime() is a STDC routine which returns the number of seconds + between two time_t values as a 'double' */ + return (long) difftime(etim, stim); } + /*allmain.c*/ diff --git a/src/alloc.c b/src/alloc.c index c6944aba4..84ee660f9 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -1,55 +1,101 @@ -/* NetHack 3.6 alloc.c $NHDT-Date: 1454376505 2016/02/02 01:28:25 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.16 $ */ +/* NetHack 5.0 alloc.c $NHDT-Date: 1737281026 2025/01/19 02:03:46 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.38 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2012. */ /* NetHack may be freely redistributed. See license for details. */ -/* to get the malloc() prototype from system.h */ #define ALLOC_C /* comment line for pre-compiled headers */ /* since this file is also used in auxiliary programs, don't include all the function declarations for all of nethack */ #define EXTERN_H /* comment line for pre-compiled headers */ + #include "config.h" +#ifndef LUA_INTEGER +#include "nhlua.h" +#endif + -char *FDECL(fmt_ptr, (const genericptr)); +/*#define FITSint(x) FITSint_(x, __func__, __LINE__)*/ +extern int FITSint_(LUA_INTEGER, const char *, int) NONNULLARG2; +/*#define FITSuint(x) FITSuint_(x, __func__, __LINE__)*/ +extern unsigned FITSuint_(unsigned long long, const char *, int) NONNULLARG2; + +char *fmt_ptr(const genericptr) NONNULL; #ifdef MONITOR_HEAP #undef alloc +#undef re_alloc #undef free -extern void FDECL(free, (genericptr_t)); -static void NDECL(heapmon_init); +extern void free(genericptr_t); +staticfn void heapmon_init(void); static FILE *heaplog = 0; static boolean tried_heaplog = FALSE; #endif -long *FDECL(alloc, (unsigned int)); -extern void VDECL(panic, (const char *, ...)) PRINTF_F(1, 2); +/* + * For historical reasons, nethack's alloc() returns 'long *' rather + * than 'void *' or 'char *'. + * + * Some static analysis complains if it can't deduce that the number + * of bytes being allocated is a multiple of 'sizeof (long)'. It + * recognizes that the following manipulation overcomes that via + * rounding the requested length up to the next long. NetHack doesn't + * make a lot of tiny allocations, so this shouldn't waste much memory + * regardless of whether malloc() does something similar. NetHack + * isn't expected to call alloc(0), but if that happens treat it as + * alloc(sizeof (long)) instead. + */ +#define ForceAlignedLength(LTH) \ + do { \ + if (!(LTH) || (LTH) % sizeof (long) != 0) \ + (LTH) += sizeof (long) - (LTH) % sizeof (long); \ + } while (0) + +#ifndef MONITOR_HEAP +long *alloc(unsigned int) NONNULL; +long *re_alloc(long *, unsigned int) NONNULL; +#else + /* for #if MONITOR_HEAP, alloc() might return Null but only nhalloc() + should be calling it; nhalloc() never returns Null */ +long *alloc(unsigned int); +long *re_alloc(long *, unsigned int); +long *nhalloc(unsigned int, const char *, int) NONNULL; +long *nhrealloc(long *, unsigned int, const char *, int) NONNULL; +#endif +ATTRNORETURN extern void panic(const char *, ...) PRINTF_F(1, 2) NORETURN; long * -alloc(lth) -register unsigned int lth; +alloc(unsigned int lth) { -#ifdef LINT - /* - * a ridiculous definition, suppressing - * "possible pointer alignment problem" for (long *) malloc() - * from lint - */ - long dummy = ftell(stderr); - - if (lth) - dummy = 0; /* make sure arg is used */ - return &dummy; -#else - register genericptr_t ptr; + genericptr_t ptr; + ForceAlignedLength(lth); ptr = malloc(lth); #ifndef MONITOR_HEAP if (!ptr) panic("Memory allocation failure; cannot get %u bytes", lth); +#else + /* for #if MONITOR_HEAP, failure is handled in nhalloc() */ #endif return (long *) ptr; +} + +/* realloc() call that might get substituted by nhrealloc(p,n,file,line) */ +long * +re_alloc(long *oldptr, unsigned int newlth) +{ + long *newptr; + + ForceAlignedLength(newlth); + newptr = (long *) realloc((genericptr_t) oldptr, (size_t) newlth); +#ifndef MONITOR_HEAP + /* "extend to": assume it won't ever fail if asked to shrink */ + if (newlth && !newptr) + panic("Memory allocation failure; cannot extend to %u bytes", newlth); +#else + /* for #if MONITOR_HEAP, failure is handled in nhrealloc() */ #endif + return newptr; } #ifdef HAS_PTR_FMT @@ -76,8 +122,7 @@ static int ptrbufidx = 0; /* format a pointer for display purposes; returns a static buffer */ char * -fmt_ptr(ptr) -const genericptr ptr; +fmt_ptr(const genericptr ptr) { char *buf; @@ -93,8 +138,8 @@ const genericptr ptr; /* If ${NH_HEAPLOG} is defined and we can create a file by that name, then we'll log the allocation and release information to that file. */ -static void -heapmon_init() +staticfn void +heapmon_init(void) { char *logname = getenv("NH_HEAPLOG"); @@ -104,10 +149,7 @@ heapmon_init() } long * -nhalloc(lth, file, line) -unsigned int lth; -const char *file; -int line; +nhalloc(unsigned int lth, const char *file, int line) { long *ptr = alloc(lth); @@ -123,11 +165,44 @@ int line; return ptr; } +/* re_alloc() with heap logging; we lack access to the old alloc size */ +long * +nhrealloc( + long *oldptr, + unsigned int newlth, + const char *file, + int line) +{ + long *newptr = re_alloc(oldptr, newlth); + + if (!tried_heaplog) + heapmon_init(); + if (heaplog) { + char op = '*'; /* assume realloc() will change size of previous + * allocation rather than make a new one */ + + if (newptr != oldptr) { + /* if oldptr wasn't Null, realloc() freed it */ + if (oldptr) + (void) fprintf(heaplog, "%c%5s %s %4d %s\n", '<', "", + fmt_ptr((genericptr_t) oldptr), line, file); + op = '>'; /* new allocation rather than size-change of old one */ + } + (void) fprintf(heaplog, "%c%5u %s %4d %s\n", op, newlth, + fmt_ptr((genericptr_t) newptr), line, file); + } + /* potential panic in re_alloc() was deferred til here; + "extend to": assume it won't ever fail if asked to shrink; + even if that assumption happens to be wrong, we lack access to + the old size so can't use alternate phrasing for that case */ + if (newlth && !newptr) + panic("Cannot extend to %u bytes, line %d of %s", newlth, line, file); + + return newptr; +} + void -nhfree(ptr, file, line) -genericptr_t ptr; -const char *file; -int line; +nhfree(genericptr_t ptr, const char *file, int line) { if (!tried_heaplog) heapmon_init(); @@ -141,12 +216,16 @@ int line; /* strdup() which uses our alloc() rather than libc's malloc(), with caller tracking */ char * -nhdupstr(string, file, line) -const char *string; -const char *file; -int line; +nhdupstr(const char *string, const char *file, int line) { - return strcpy((char *) nhalloc(strlen(string) + 1, file, line), string); + /* we've got some info about the caller, so use it instead of __func__ */ + unsigned len = FITSuint_(strlen(string), file, line); + + if (FITSuint_(len + 1, file, line) < len) + panic("nhdupstr: string length overflow, line %d of %s", + line, file); + + return strcpy((char *) nhalloc(len + 1, file, line), string); } #undef dupstr @@ -156,10 +235,51 @@ int line; not used when MONITOR_HEAP is enabled, but included unconditionally in case utility programs get built using a different setting for that */ char * -dupstr(string) -const char *string; +dupstr(const char *string) { - return strcpy((char *) alloc(strlen(string) + 1), string); + size_t len = strlen(string); + + /* make sure len+1 doesn't overflow plain unsigned (for alloc()) */ + if (len > (unsigned) (~0U - 1U)) + panic("dupstr: string length overflow"); + + return strcpy((char *) alloc(len + 1), string); +} + +#if 0 /* suppress this; if included, it will need a MONITOR_HEAP edition */ + +/* similar for reasonable size strings, but return length of input as well */ +char * +dupstr_n(const char *string, unsigned int *lenout) +{ + size_t len = strlen(string); + + if (len >= LARGEST_INT) + panic("dupstr_n: string too long"); + *lenout = (unsigned int) len; + return strcpy((char *) alloc(len + 1), string); +} +#endif + +/* cast to int or panic on overflow; use via macro */ +int +FITSint_(LUA_INTEGER i, const char *file, int line) +{ + int iret = (int) i; + + if (iret != i) + panic("Overflow at %s:%d", file, line); + return iret; +} + +unsigned +FITSuint_(unsigned long long ull, const char *file, int line) +{ + unsigned uret = (unsigned) ull; + + if (uret != ull) + panic("Overflow at %s:%d", file, line); + return uret; } /*alloc.c*/ diff --git a/src/apply.c b/src/apply.c index 8c76e1638..2f4db65c5 100644 --- a/src/apply.c +++ b/src/apply.c @@ -1,71 +1,93 @@ -/* NetHack 3.6 apply.c $NHDT-Date: 1573778560 2019/11/15 00:42:40 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.284 $ */ +/* NetHack 5.0 apply.c $NHDT-Date: 1769342601 2026/01/25 04:03:21 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.475 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2012. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -extern boolean notonhead; /* for long worms */ - -STATIC_DCL int FDECL(use_camera, (struct obj *)); -STATIC_DCL int FDECL(use_towel, (struct obj *)); -STATIC_DCL boolean FDECL(its_dead, (int, int, int *)); -STATIC_DCL int FDECL(use_stethoscope, (struct obj *)); -STATIC_DCL void FDECL(use_whistle, (struct obj *)); -STATIC_DCL void FDECL(use_magic_whistle, (struct obj *)); -STATIC_DCL int FDECL(use_leash, (struct obj *)); -STATIC_DCL int FDECL(use_mirror, (struct obj *)); -STATIC_DCL void FDECL(use_bell, (struct obj **)); -STATIC_DCL void FDECL(use_candelabrum, (struct obj *)); -STATIC_DCL void FDECL(use_candle, (struct obj **)); -STATIC_DCL void FDECL(use_lamp, (struct obj *)); -STATIC_DCL void FDECL(light_cocktail, (struct obj **)); -STATIC_PTR void FDECL(display_jump_positions, (int)); -STATIC_DCL void FDECL(use_tinning_kit, (struct obj *)); -STATIC_DCL void FDECL(use_figurine, (struct obj **)); -STATIC_DCL void FDECL(use_grease, (struct obj *)); -STATIC_DCL void FDECL(use_trap, (struct obj *)); -STATIC_DCL void FDECL(use_stone, (struct obj *)); -STATIC_PTR int NDECL(set_trap); /* occupation callback */ -STATIC_DCL int FDECL(use_whip, (struct obj *)); -STATIC_PTR void FDECL(display_polearm_positions, (int)); -STATIC_DCL int FDECL(use_pole, (struct obj *)); -STATIC_DCL int FDECL(use_cream_pie, (struct obj *)); -STATIC_DCL int FDECL(use_grapple, (struct obj *)); -STATIC_DCL int FDECL(do_break_wand, (struct obj *)); -STATIC_DCL boolean FDECL(figurine_location_checks, (struct obj *, - coord *, BOOLEAN_P)); -STATIC_DCL void FDECL(add_class, (char *, CHAR_P)); -STATIC_DCL void FDECL(setapplyclasses, (char *)); -STATIC_PTR boolean FDECL(check_jump, (genericptr_t, int, int)); -STATIC_DCL boolean FDECL(is_valid_jump_pos, (int, int, int, BOOLEAN_P)); -STATIC_DCL boolean FDECL(get_valid_jump_position, (int, int)); -STATIC_DCL boolean FDECL(get_valid_polearm_position, (int, int)); -STATIC_DCL boolean FDECL(find_poleable_mon, (coord *, int, int)); - -#ifdef AMIGA -void FDECL(amii_speaker, (struct obj *, char *, int)); -#endif +staticfn int use_camera(struct obj *); +staticfn int use_towel(struct obj *); +staticfn boolean its_dead(coordxy, coordxy, int *); +staticfn int use_stethoscope(struct obj *); +staticfn void use_whistle(struct obj *); +staticfn void use_magic_whistle(struct obj *); +staticfn void magic_whistled(struct obj *); +staticfn int use_leash(struct obj *); +staticfn void use_leash_core(struct obj *, struct monst *, coord *, int); +staticfn boolean mleashed_next2u(struct monst *); +staticfn int use_mirror(struct obj *); +staticfn void use_bell(struct obj **); +staticfn void use_candelabrum(struct obj *); +staticfn void use_candle(struct obj **); +staticfn void use_lamp(struct obj *); +staticfn void light_cocktail(struct obj **); +staticfn int rub_ok(struct obj *); +staticfn void display_jump_positions(boolean); +staticfn void use_tinning_kit(struct obj *); +staticfn int use_figurine(struct obj **); +staticfn int grease_ok(struct obj *); +staticfn int use_grease(struct obj *); +staticfn void use_trap(struct obj *); +staticfn int touchstone_ok(struct obj *); +staticfn int use_stone(struct obj *); +staticfn int set_trap(void); /* occupation callback */ +staticfn void display_polearm_positions(boolean); +staticfn void calc_pole_range(int *, int *); +staticfn boolean snickersnee_used_dist_attk(struct obj *); +staticfn int use_cream_pie(struct obj *); +staticfn int jelly_ok(struct obj *); +staticfn int use_royal_jelly(struct obj **); +staticfn int grapple_range(void); +staticfn boolean can_grapple_location(coordxy, coordxy); +staticfn void display_grapple_positions(boolean); +staticfn int use_grapple(struct obj *); +staticfn void discard_broken_wand(void); +staticfn void broken_wand_explode(struct obj *, int, int); +staticfn int do_break_wand(struct obj *); +staticfn int apply_ok(struct obj *); +staticfn int flip_through_book(struct obj *); +staticfn int flip_coin(struct obj *); +staticfn boolean figurine_location_checks(struct obj *, coord *, boolean); +staticfn boolean check_jump(genericptr_t, coordxy, coordxy); +staticfn boolean is_valid_jump_pos(coordxy, coordxy, int, boolean); +staticfn boolean get_valid_jump_position(coordxy, coordxy); +staticfn boolean get_valid_polearm_position(coordxy, coordxy); +staticfn boolean find_poleable_mon(coord *); -static const char no_elbow_room[] = - "don't have enough elbow-room to maneuver."; +static const char + no_elbow_room[] = "don't have enough elbow-room to maneuver."; -STATIC_OVL int -use_camera(obj) -struct obj *obj; +void +do_blinding_ray(struct obj *obj) { - struct monst *mtmp; + struct monst *mtmp = bhit(u.dx, u.dy, COLNO, FLASHED_LIGHT, + (int (*) (MONST_P, OBJ_P)) 0, + (int (*) (OBJ_P, OBJ_P)) 0, &obj); + + obj->ox = u.ux, obj->oy = u.uy; /* flash_hits_mon() wants this */ + if (mtmp) { + (void) flash_hits_mon(mtmp, obj); + if (obj->otyp == EXPENSIVE_CAMERA) + see_monster_closeup(mtmp, TRUE); /* TRUE for photo */ + } + /* normally bhit() would do this but for FLASHED_LIGHT we want it + to be deferred until after flash_hits_mon() */ + transient_light_cleanup(); +} +staticfn int +use_camera(struct obj *obj) +{ if (Underwater) { pline("Using your camera underwater would void the warranty."); - return 0; + return ECMD_OK; } if (!getdir((char *) 0)) - return 0; + return ECMD_CANCEL; if (obj->spe <= 0) { pline1(nothing_happens); - return 1; + return ECMD_TIME; } consume_obj_charge(obj, TRUE); @@ -78,28 +100,25 @@ struct obj *obj; You("take a picture of the %s.", (u.dz > 0) ? surface(u.ux, u.uy) : ceiling(u.ux, u.uy)); } else if (!u.dx && !u.dy) { + /* TODO: we ought to have a "selfie" joke here... */ (void) zapyourself(obj, TRUE); - } else if ((mtmp = bhit(u.dx, u.dy, COLNO, FLASHED_LIGHT, - (int FDECL((*), (MONST_P, OBJ_P))) 0, - (int FDECL((*), (OBJ_P, OBJ_P))) 0, &obj)) != 0) { - obj->ox = u.ux, obj->oy = u.uy; - (void) flash_hits_mon(mtmp, obj); + } else { + do_blinding_ray(obj); } - return 1; + return ECMD_TIME; } -STATIC_OVL int -use_towel(obj) -struct obj *obj; +staticfn int +use_towel(struct obj *obj) { boolean drying_feedback = (obj == uwep); if (!freehand()) { You("have no free %s!", body_part(HAND)); - return 0; + return ECMD_OK; } else if (obj == ublindf) { You("cannot use it while you're wearing it!"); - return 0; + return ECMD_OK; } else if (obj->cursed) { long old; @@ -111,14 +130,14 @@ struct obj *obj; (old ? "are filthier than ever" : "get slimy")); if (is_wet_towel(obj)) dry_a_towel(obj, -1, drying_feedback); - return 1; + return ECMD_TIME; case 1: if (!ublindf) { old = u.ucreamed; u.ucreamed += rn1(10, 3); pline("Yecch! Your %s %s gunk on it!", body_part(FACE), (old ? "has more" : "now has")); - make_blinded(Blinded + (long) u.ucreamed - old, TRUE); + make_blinded(BlindedTimeout + (long) u.ucreamed - old, TRUE); } else { const char *what; @@ -138,7 +157,7 @@ struct obj *obj; } if (is_wet_towel(obj)) dry_a_towel(obj, -1, drying_feedback); - return 1; + return ECMD_TIME; case 0: break; } @@ -150,14 +169,14 @@ struct obj *obj; !uarmg ? makeplural(body_part(HAND)) : gloves_simple_name(uarmg)); if (is_wet_towel(obj)) dry_a_towel(obj, -1, drying_feedback); - return 1; + return ECMD_TIME; } else if (u.ucreamed) { - Blinded -= u.ucreamed; + incr_itimeout(&HBlinded, (-1 * (int) u.ucreamed)); u.ucreamed = 0; if (!Blinded) { pline("You've got the glop off."); if (!gulp_blnd_check()) { - Blinded = 1; + set_itimeout(&HBlinded, 1L); make_blinded(0L, TRUE); } } else { @@ -165,19 +184,18 @@ struct obj *obj; } if (is_wet_towel(obj)) dry_a_towel(obj, -1, drying_feedback); - return 1; + return ECMD_TIME; } Your("%s and %s are already clean.", body_part(FACE), makeplural(body_part(HAND))); - return 0; + return ECMD_OK; } /* maybe give a stethoscope message based on floor objects */ -STATIC_OVL boolean -its_dead(rx, ry, resp) -int rx, ry, *resp; +staticfn boolean +its_dead(coordxy rx, coordxy ry, int *resp) { char buf[BUFSZ]; boolean more_corpses; @@ -216,9 +234,8 @@ int rx, ry, *resp; /* (most corpses don't retain the monster's sex, so we're usually forced to use generic pronoun here) */ if (mtmp) { - mptr = mtmp->data = &mons[mtmp->mnum]; - /* TRUE: override visibility check--it's not on the map */ - gndr = pronoun_gender(mtmp, TRUE); + mtmp->data = &mons[mtmp->mnum]; + gndr = pronoun_gender(mtmp, PRONOUN_NO_IT); } else { mptr = &mons[corpse->corpsenm]; if (is_female(mptr)) @@ -233,11 +250,11 @@ int rx, ry, *resp; } /* variations on "He's dead, Jim." (Star Trek's Dr McCoy) */ You_hear("a voice say, \"%s, Jim.\"", buf); - *resp = 1; + *resp = ECMD_TIME; return TRUE; } else if (corpse) { - boolean here = (rx == u.ux && ry == u.uy), + boolean here = u_at(rx, ry), one = (corpse->quan == 1L && !more_corpses), reviver = FALSE; int visglyph, corpseglyph; @@ -267,11 +284,11 @@ int rx, ry, *resp; mptr = &mons[statue->corpsenm]; if (Blind) { /* ignore statue->dknown; it'll always be set */ Sprintf(buf, "%s %s", - (rx == u.ux && ry == u.uy) ? "This" : "That", + u_at(rx, ry) ? "This" : "That", humanoid(mptr) ? "person" : "creature"); what = buf; } else { - what = mptr->mname; + what = obj_pmname(statue); if (!type_is_pname(mptr)) what = The(what); } @@ -297,36 +314,34 @@ static const char hollow_str[] = "a hollow sound. This must be a secret %s!"; not take any time; however, unless it did, the stethoscope would be almost useless. As a compromise, one use per turn is free, another uses up the turn; this makes curse status have a tangible effect. */ -STATIC_OVL int -use_stethoscope(obj) -register struct obj *obj; +staticfn int +use_stethoscope(struct obj *obj) { struct monst *mtmp; struct rm *lev; - int rx, ry, res; + int res; + coordxy rx, ry; boolean interference = (u.uswallow && is_whirly(u.ustuck->data) && !rn2(Role_if(PM_HEALER) ? 10 : 3)); - if (nohands(youmonst.data)) { + if (nohands(gy.youmonst.data)) { You("have no hands!"); /* not `body_part(HAND)' */ - return 0; + return ECMD_OK; } else if (Deaf) { You_cant("hear anything!"); - return 0; + return ECMD_OK; } else if (!freehand()) { You("have no free %s.", body_part(HAND)); - return 0; + return ECMD_OK; } if (!getdir((char *) 0)) - return 0; + return ECMD_CANCEL; - res = (moves == context.stethoscope_move) - && (youmonst.movement == context.stethoscope_movement); - context.stethoscope_move = moves; - context.stethoscope_movement = youmonst.movement; + res = (gh.hero_seq == svc.context.stethoscope_seq) ? ECMD_TIME : ECMD_OK; + svc.context.stethoscope_seq = gh.hero_seq; - bhitpos.x = u.ux, bhitpos.y = u.uy; /* tentative, reset below */ - notonhead = u.uswallow; + gb.bhitpos.x = u.ux, gb.bhitpos.y = u.uy; /* tentative, reset below */ + gn.notonhead = u.uswallow; if (u.usteed && u.dz > 0) { if (interference) { pline("%s interferes.", Monnam(u.ustuck)); @@ -342,23 +357,26 @@ register struct obj *obj; mstatusline(u.ustuck); return res; } else if (u.dz) { - if (Underwater) + if (Underwater) { + Soundeffect(se_faint_splashing, 35); You_hear("faint splashing."); - else if (u.dz < 0 || !can_reach_floor(TRUE)) - cant_reach_floor(u.ux, u.uy, (u.dz < 0), TRUE); - else if (its_dead(u.ux, u.uy, &res)) + } else if (u.dz < 0 || !can_reach_floor(TRUE)) { + cant_reach_floor(u.ux, u.uy, (u.dz < 0), TRUE, FALSE); + } else if (its_dead(u.ux, u.uy, &res)) { ; /* message already given */ - else if (Is_stronghold(&u.uz)) + } else if (Is_stronghold(&u.uz)) { + Soundeffect(se_crackling_of_hellfire, 35); You_hear("the crackling of hellfire."); - else + } else { pline_The("%s seems healthy enough.", surface(u.ux, u.uy)); + } return res; } else if (obj->cursed && !rn2(2)) { + Soundeffect(se_heart_beat, 100); You_hear("your heart beat."); return res; } - if (Stunned || (Confusion && !rn2(5))) - confdir(); + confdir(FALSE); if (!u.dx && !u.dy) { ustatusline(); return res; @@ -366,16 +384,17 @@ register struct obj *obj; rx = u.ux + u.dx; ry = u.uy + u.dy; if (!isok(rx, ry)) { + Soundeffect(se_typing_noise, 100); You_hear("a faint typing noise."); - return 0; + return ECMD_OK; } if ((mtmp = m_at(rx, ry)) != 0) { const char *mnm = x_monnam(mtmp, ARTICLE_A, (const char *) 0, SUPPRESS_IT | SUPPRESS_INVISIBLE, FALSE); - /* bhitpos needed by mstatusline() iff mtmp is a long worm */ - bhitpos.x = rx, bhitpos.y = ry; - notonhead = (mtmp->mx != rx || mtmp->my != ry); + /* gb.bhitpos needed by mstatusline() iff mtmp is a long worm */ + gb.bhitpos.x = rx, gb.bhitpos.y = ry; + gn.notonhead = (mtmp->mx != rx || mtmp->my != ry); if (mtmp->mundetected) { if (!canspotmon(mtmp)) @@ -396,8 +415,7 @@ register struct obj *obj; /* simple_typename() yields "fruit" for any named fruit; we want the same thing '//' or ';' shows: "slime mold" or "grape" or "slice of pizza" */ - if (odummy->otyp == SLIME_MOLD - && has_mcorpsenm(mtmp) && MCORPSENM(mtmp) != NON_PM) { + if (odummy->otyp == SLIME_MOLD && has_mcorpsenm(mtmp)) { odummy->spe = MCORPSENM(mtmp); what = simpleonames(odummy); } else { @@ -407,7 +425,7 @@ register struct obj *obj; || odummy->otyp == LENSES); break; case M_AP_MONSTER: /* ignore Hallucination here */ - what = mons[mtmp->mappearance].mname; + what = pmname(&mons[mtmp->mappearance], Mgender(mtmp)); break; case M_AP_FURNITURE: what = defsyms[mtmp->mappearance].explanation; @@ -432,8 +450,10 @@ register struct obj *obj; lev = &levl[rx][ry]; switch (lev->typ) { case SDOOR: + Soundeffect(se_hollow_sound, 100); You_hear(hollow_str, "door"); cvt_sdoor_to_door(lev); /* ->typ = DOOR */ + recalc_block_point(rx, ry); feel_newsym(rx, ry); return res; case SCORR: @@ -452,11 +472,10 @@ register struct obj *obj; static const char whistle_str[] = "produce a %s whistling sound.", alt_whistle_str[] = "produce a %s, sharp vibration."; -STATIC_OVL void -use_whistle(obj) -struct obj *obj; +staticfn void +use_whistle(struct obj *obj) { - if (!can_blow(&youmonst)) { + if (!can_blow(&gy.youmonst)) { You("are incapable of using the whistle."); } else if (Underwater) { You("blow bubbles through %s.", yname(obj)); @@ -465,80 +484,223 @@ struct obj *obj; You_feel("rushing air tickle your %s.", body_part(NOSE)); else You(whistle_str, obj->cursed ? "shrill" : "high"); - wake_nearby(); + Soundeffect(se_shrill_whistle, 50); + wake_nearby(TRUE); if (obj->cursed) vault_summon_gd(); } } -STATIC_OVL void -use_magic_whistle(obj) -struct obj *obj; +staticfn void +use_magic_whistle(struct obj *obj) { - register struct monst *mtmp, *nextmon; - - if (!can_blow(&youmonst)) { + if (!can_blow(&gy.youmonst)) { You("are incapable of using the whistle."); } else if (obj->cursed && !rn2(2)) { You("produce a %shigh-%s.", Underwater ? "very " : "", Deaf ? "frequency vibration" : "pitched humming noise"); - wake_nearby(); + wake_nearby(TRUE); + if (!rn2(2) && !noteleport_level(&gy.youmonst)) + tele_to_rnd_pet(); } else { - int pet_cnt = 0, omx, omy; - /* it's magic! it works underwater too (at a higher pitch) */ You(Deaf ? alt_whistle_str : whistle_str, Hallucination ? "normal" : (Underwater && !Deaf) ? "strange, high-pitched" : "strange"); - for (mtmp = fmon; mtmp; mtmp = nextmon) { - nextmon = mtmp->nmon; /* trap might kill mon */ - if (DEADMONSTER(mtmp)) - continue; - /* steed is already at your location, so not affected; - this avoids trap issues if you're on a trap location */ - if (mtmp == u.usteed) + Soundeffect(se_shrill_whistle, 80); + magic_whistled(obj); + } +} + +/* 'obj' is assumed to be a magic whistle */ +staticfn void +magic_whistled(struct obj *obj) +{ + struct monst *mtmp, *nextmon; + char buf[BUFSZ], *mnam = 0, + shiftbuf[BUFSZ + sizeof "shifts location"], + appearbuf[BUFSZ + sizeof "appears"], + disappearbuf[BUFSZ + sizeof "disappears"]; + boolean oseen, nseen, + already_discovered = objects[obj->otyp].oc_name_known != 0; + int omx, omy, shift = 0, appear = 0, disappear = 0, trapped = 0; + + /* stasis prevents magic-whistling */ + if (svl.level.flags.stasis_until >= svm.moves) + return; + + /* need to copy (up to 3) names as they're collected rather than just + save pointers to them, otherwise churning through every mbuf[] might + clobber the ones we care about */ + shiftbuf[0] = appearbuf[0] = disappearbuf[0] = '\0'; + + for (mtmp = fmon; mtmp; mtmp = nextmon) { + nextmon = mtmp->nmon; /* trap might kill mon */ + if (DEADMONSTER(mtmp)) + continue; + /* only tame monsters are affected; + steed is already at your location, so not affected; + this avoids trap issues if you're on a trap location */ + if (!mtmp->mtame || mtmp == u.usteed) + continue; + if (mtmp->mtrapped) { + /* no longer in previous trap (affects mintrap) */ + mtmp->mtrapped = 0; + fill_pit(mtmp->mx, mtmp->my); + } + + oseen = canspotmon(mtmp); /* old 'seen' status */ + if (oseen) /* get name in case it's one we'll remember */ + mnam = y_monnam(mtmp); /* before mnexto(); it might disappear */ + /* mimic must be revealed before we know whether it + actually moves because line-of-sight may change */ + if (M_AP_TYPE(mtmp)) + seemimic(mtmp); + omx = mtmp->mx, omy = mtmp->my; + mnexto(mtmp, !already_discovered ? RLOC_MSG : RLOC_NONE); + + if (mtmp->mx != omx || mtmp->my != omy) { + if (mtmp->mundetected) { /* reveal non-mimic hider that moved */ + mtmp->mundetected = 0; + newsym(mtmp->mx, mtmp->my); + } + /* + * FIXME: + * All relocated monsters should change positions essentially + * simultaneously but we're dealing with them sequentially. + * That could kill some off in the process, each time leaving + * their target position (which should be occupied at least + * momentarily) available as a potential death trap for others. + * + * Also, teleporting onto a trap introduces message sequencing + * issues. We try to avoid the most obvious non sequiturs by + * checking whether pline() got called during mintrap(). + * iflags.last_msg will be changed from the value we set here + * to PLNMSG_UNKNOWN in that situation. + */ + iflags.last_msg = PLNMSG_enum; /* not a specific message */ + if (mintrap(mtmp, NO_TRAP_FLAGS) == Trap_Killed_Mon) + change_luck(-1); + if (iflags.last_msg != PLNMSG_enum) { + ++trapped; continue; - if (mtmp->mtame) { - if (mtmp->mtrapped) { - /* no longer in previous trap (affects mintrap) */ - mtmp->mtrapped = 0; - fill_pit(mtmp->mx, mtmp->my); - } - /* mimic must be revealed before we know whether it - actually moves because line-of-sight may change */ - if (M_AP_TYPE(mtmp)) - seemimic(mtmp); - omx = mtmp->mx, omy = mtmp->my; - mnexto(mtmp); - if (mtmp->mx != omx || mtmp->my != omy) { - mtmp->mundetected = 0; /* reveal non-mimic hider */ - if (canspotmon(mtmp)) - ++pet_cnt; - if (mintrap(mtmp) == 2) - change_luck(-1); + } + /* dying while seen would have issued a message and not get here; + being sent to an unseen location and dying there should be + included in the disappeared case */ + nseen = DEADMONSTER(mtmp) ? FALSE : canspotmon(mtmp); + + if (nseen) { + mnam = y_monnam(mtmp); + if (oseen) { + if (++shift == 1) + Sprintf(shiftbuf, "%s shifts location", mnam); + } else { + if (++appear == 1) + Sprintf(appearbuf, "%s appears", mnam); } + } else if (oseen) { + if (++disappear == 1) + Sprintf(disappearbuf, "%s disappears", mnam); } } - if (pet_cnt > 0) + } + + /* + * If any pets changed location, (1) they might have been in view + * before and still in view after, (2) out of view before but in + * view after, (3) in view before but out of view after (perhaps + * on the far side of a boulder/door/wall), or (4) out of view + * before and still out of view after. The first two cases are + * the usual ones; the fourth will happen if the hero can't see. + * + * If the magic whistle hasn't been discovered yet, rloc() issued + * any applicable vanishing and/or appearing messages, and we make + * it become discovered now if any pets moved within or into view. + * If it has already been discovered, we told rloc() not to issue + * messages and will issue one cumulative message now (for any of + * the first three cases, not the fourth) to reduce verbosity for + * the first case of a single pet (avoid "vanishes and reappears") + * and greatly reduce verbosity for multiple pets regardless of + * each one's case. + */ + buf[0] = '\0'; + if (!already_discovered) { + /* message(s) were handled by rloc(); if only noticeable change was + pet(s) disappearing, the magic whistle won't become discovered */ + if (shift + appear + trapped > 0) makeknown(obj->otyp); + } else { + /* could use array of cardinal number names like wishcmdassist() but + extra precision above 3 or 4 seems pedantic; not used for 0 or 1 */ +#define HowMany(n) (((n) < 2) ? "sqrt(-1)" \ + : ((n) == 2) ? "two" \ + : ((n) == 3) ? "three" \ + : ((n) == 4) ? "four" \ + : ((n) <= 7) ? "several" \ + : "many") + /* magic whistle is already discovered so rloc() message(s) + were suppressed above; if any discernible relocation occurred, + construct a message now and issue it below */ + if (shift > 0) { + if (shift > 1) + Sprintf(shiftbuf, "%s creatures shift locations", + HowMany(shift)); + copynchars(buf, upstart(shiftbuf), (int) sizeof buf - 1); + } + if (appear > 0) { + if (appear > 1) + /* shift==0: N creatures appear; + shift==1: Foo shifts location and N other creatures appear; + shift >1: M creatures shift locations and N others appear */ + Sprintf(appearbuf, "%s %s appear", HowMany(appear), + (shift == 0) ? "creatures" + : (shift == 1) ? "other creatures" + : "others"); + if (shift == 0) + copynchars(buf, upstart(appearbuf), (int) sizeof buf - 1); + else + Snprintf(eos(buf), sizeof buf - strlen(buf), "%s %s", + /* to get here: appear > 0 and shift != 0, + so "shifters, appearers" if disappear != 0 + with ", and disappearers" yet to be appended, + or "shifters and appearers" otherwise */ + disappear ? "," : " and", appearbuf); + } + if (disappear > 0) { + if (disappear > 1) + Sprintf(disappearbuf, "%s %s disappear", HowMany(disappear), + (shift == 0 && appear == 0) ? "creatures" + : (shift < 2 && appear < 2) ? "other creatures" + : "others"); + if (shift + appear == 0) + copynchars(buf, upstart(disappearbuf), (int) sizeof buf - 1); + else + Snprintf(eos(buf), sizeof buf - strlen(buf), "%s and %s", + (shift && appear) ? "," : "", disappearbuf); + } } + if (*buf) + pline("%s.", buf); + return; } +#undef HowMany + boolean -um_dist(x, y, n) -xchar x, y, n; +um_dist(coordxy x, coordxy y, xint16 n) { return (boolean) (abs(u.ux - x) > n || abs(u.uy - y) > n); } int -number_leashed() +number_leashed(void) { int i = 0; struct obj *obj; - for (obj = invent; obj; obj = obj->nobj) + for (obj = gi.invent; obj; obj = obj->nobj) if (obj->otyp == LEASH && obj->leashmon != 0) i++; return i; @@ -546,47 +708,47 @@ number_leashed() /* otmp is about to be destroyed or stolen */ void -o_unleash(otmp) -register struct obj *otmp; +o_unleash(struct obj *otmp) { - register struct monst *mtmp; + struct monst *mtmp; for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) - if (mtmp->m_id == (unsigned) otmp->leashmon) + if (mtmp->m_id == (unsigned) otmp->leashmon) { mtmp->mleashed = 0; + break; + } otmp->leashmon = 0; + update_inventory(); } /* mtmp is about to die, or become untame */ void -m_unleash(mtmp, feedback) -register struct monst *mtmp; -boolean feedback; +m_unleash(struct monst *mtmp, boolean feedback) { - register struct obj *otmp; + struct obj *otmp; if (feedback) { if (canseemon(mtmp)) - pline("%s pulls free of %s leash!", Monnam(mtmp), mhis(mtmp)); + pline_mon(mtmp, "%s pulls free of %s leash!", + Monnam(mtmp), mhis(mtmp)); else Your("leash falls slack."); } - for (otmp = invent; otmp; otmp = otmp->nobj) - if (otmp->otyp == LEASH && otmp->leashmon == (int) mtmp->m_id) { - otmp->leashmon = 0; - update_inventory(); - } + if ((otmp = get_mleash(mtmp)) != 0) { + otmp->leashmon = 0; + update_inventory(); + } mtmp->mleashed = 0; } /* player is about to die (for bones) */ void -unleash_all() +unleash_all(void) { - register struct obj *otmp; - register struct monst *mtmp; + struct obj *otmp; + struct monst *mtmp; - for (otmp = invent; otmp; otmp = otmp->nobj) + for (otmp = gi.invent; otmp; otmp = otmp->nobj) if (otmp->otyp == LEASH) otmp->leashmon = 0; for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) @@ -596,22 +758,18 @@ unleash_all() #define MAXLEASHED 2 boolean -leashable(mtmp) -struct monst *mtmp; +leashable(struct monst *mtmp) { return (boolean) (mtmp->mnum != PM_LONG_WORM && !unsolid(mtmp->data) && (!nolimbs(mtmp->data) || has_head(mtmp->data))); } -/* ARGSUSED */ -STATIC_OVL int -use_leash(obj) -struct obj *obj; +staticfn int +use_leash(struct obj *obj) { coord cc; struct monst *mtmp; - int spotmon; if (u.uswallow) { /* if the leash isn't in use, assume we're trying to leash @@ -625,24 +783,24 @@ struct obj *obj; ? "unleash %s from inside." : "unleash anything from inside %s."), noit_mon_nam(u.ustuck)); - return 0; + return ECMD_OK; } if (!obj->leashmon && number_leashed() >= MAXLEASHED) { You("cannot leash any more pets."); - return 0; + return ECMD_OK; } if (!get_adjacent_loc((char *) 0, (char *) 0, u.ux, u.uy, &cc)) - return 0; + return ECMD_OK; - if (cc.x == u.ux && cc.y == u.uy) { + if (u_at(cc.x, cc.y)) { if (u.usteed && u.dz > 0) { mtmp = u.usteed; - spotmon = 1; - goto got_target; + use_leash_core(obj, mtmp, &cc, 1); + return ECMD_TIME; } pline("Leash yourself? Very funny..."); - return 0; + return ECMD_OK; } /* @@ -652,19 +810,23 @@ struct obj *obj; if (!(mtmp = m_at(cc.x, cc.y))) { There("is no creature there."); (void) unmap_invisible(cc.x, cc.y); - return 1; + return ECMD_TIME; } - spotmon = canspotmon(mtmp); - got_target: + use_leash_core(obj, mtmp, &cc, canspotmon(mtmp)); + return ECMD_TIME; +} - if (!spotmon && !glyph_is_invisible(levl[cc.x][cc.y].glyph)) { +staticfn void +use_leash_core(struct obj *obj, struct monst *mtmp, coord *cc, int spotmon) +{ + if (!spotmon && !glyph_is_invisible(levl[cc->x][cc->y].glyph)) { /* for the unleash case, we don't verify whether this unseen monster is the creature attached to the current leash */ You("fail to %sleash something.", obj->leashmon ? "un" : ""); /* trying again will work provided the monster is tame (and also that it doesn't change location by retry time) */ - map_invisible(cc.x, cc.y); + map_invisible(cc->x, cc->y); } else if (!mtmp->mtame) { pline("%s %s leashed!", Monnam(mtmp), (!obj->leashmon) ? "cannot be" : "is not"); @@ -679,14 +841,22 @@ struct obj *obj; pline("%s has no extremities the leash would fit.", Monnam(mtmp)); } else if (!leashable(mtmp)) { + char lmonbuf[BUFSZ]; + char *lmonnam = l_monnam(mtmp); + + if (cc->x != mtmp->mx || cc->y != mtmp->my) { + Sprintf(lmonbuf, "%s tail", s_suffix(lmonnam)); + lmonnam = lmonbuf; + } pline("The leash won't fit onto %s%s.", spotmon ? "your " : "", - l_monnam(mtmp)); + lmonnam); } else { You("slip the leash around %s%s.", spotmon ? "your " : "", l_monnam(mtmp)); mtmp->mleashed = 1; obj->leashmon = (int) mtmp->m_id; mtmp->msleeping = 0; + update_inventory(); } } else { /* applying a leash which is currently in use */ @@ -698,55 +868,59 @@ struct obj *obj; } else { mtmp->mleashed = 0; obj->leashmon = 0; + update_inventory(); You("remove the leash from %s%s.", spotmon ? "your " : "", l_monnam(mtmp)); } } - return 1; } /* assuming mtmp->mleashed has been checked */ struct obj * -get_mleash(mtmp) -struct monst *mtmp; +get_mleash(struct monst *mtmp) { struct obj *otmp; - otmp = invent; - while (otmp) { - if (otmp->otyp == LEASH && otmp->leashmon == (int) mtmp->m_id) - return otmp; - otmp = otmp->nobj; - } - return (struct obj *) 0; + for (otmp = gi.invent; otmp; otmp = otmp->nobj) + if (otmp->otyp == LEASH && (unsigned) otmp->leashmon == mtmp->m_id) + break; + return otmp; } -boolean -next_to_u() +staticfn boolean +mleashed_next2u(struct monst *mtmp) { - register struct monst *mtmp; - register struct obj *otmp; - - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) - continue; - if (mtmp->mleashed) { - if (distu(mtmp->mx, mtmp->my) > 2) - mnexto(mtmp); - if (distu(mtmp->mx, mtmp->my) > 2) { - for (otmp = invent; otmp; otmp = otmp->nobj) - if (otmp->otyp == LEASH - && otmp->leashmon == (int) mtmp->m_id) { - if (otmp->cursed) - return FALSE; - You_feel("%s leash go slack.", - (number_leashed() > 1) ? "a" : "the"); - mtmp->mleashed = 0; - otmp->leashmon = 0; - } + if (mtmp->mleashed) { + if (!m_next2u(mtmp)) + mnexto(mtmp, RLOC_NOMSG); + if (!m_next2u(mtmp)) { + struct obj *otmp = get_mleash(mtmp); + + if (!otmp) { + impossible("leashed-unleashed mon?"); + return TRUE; } + + if (otmp->cursed) + return TRUE; + mtmp->mleashed = 0; + otmp->leashmon = 0; + update_inventory(); + You_feel("%s leash go slack.", + (number_leashed() > 1) ? "a" : "the"); } } + return FALSE; +} + +#undef MAXLEASHED + +boolean +next_to_u(void) +{ + if (get_iter_mons(mleashed_next2u)) + return FALSE; + /* no pack mules for the Amulet */ if (u.usteed && mon_has_amulet(u.usteed)) return FALSE; @@ -754,21 +928,15 @@ next_to_u() } void -check_leash(x, y) -register xchar x, y; +check_leash(coordxy x, coordxy y) { - register struct obj *otmp; - register struct monst *mtmp; + struct obj *otmp; + struct monst *mtmp; - for (otmp = invent; otmp; otmp = otmp->nobj) { + for (otmp = gi.invent; otmp; otmp = otmp->nobj) { if (otmp->otyp != LEASH || otmp->leashmon == 0) continue; - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) - continue; - if ((int) mtmp->m_id == otmp->leashmon) - break; - } + mtmp = find_mid(otmp->leashmon, FM_FMON); if (!mtmp) { impossible("leash in use isn't attached to anything?"); otmp->leashmon = 0; @@ -793,7 +961,8 @@ register xchar x, y; if (!DEADMONSTER(mtmp)) u.uconduct.killer = save_pacifism; } else { - pline("%s is choked by the leash!", Monnam(mtmp)); + pline_mon(mtmp, "%s is choked by the leash!", + Monnam(mtmp)); /* tameness eventually drops to 1 here (never 0) */ if (mtmp->mtame && rn2(mtmp->mtame)) mtmp->mtame--; @@ -822,21 +991,31 @@ register xchar x, y; } } +/* charisma is supposed to include qualities like leadership and personal + magnetism rather than just appearance, but it has devolved to this... */ const char * -beautiful() +beautiful(void) { - return ((ACURR(A_CHA) > 14) - ? ((poly_gender() == 1) - ? "beautiful" - : "handsome") - : "ugly"); + const char *res; + int cha = ACURR(A_CHA); + + /* don't bother complaining about the sexism; NetHack is not real life */ + res = ((cha >= 25) ? "sublime" /* 25 is the maximum possible */ + : (cha >= 19) ? "splendorous" /* note: not "splendiferous" */ + : (cha >= 16) ? ((poly_gender() == 1) ? "beautiful" : "handsome") + : (cha >= 14) ? ((poly_gender() == 1) ? "winsome" : "amiable") + : (cha >= 11) ? "cute" + : (cha >= 9) ? "plain" + : (cha >= 6) ? "homely" + : (cha >= 4) ? "ugly" + : "hideous"); /* 3 is the minimum possible */ + return res; } static const char look_str[] = "look %s."; -STATIC_OVL int -use_mirror(obj) -struct obj *obj; +staticfn int +use_mirror(struct obj *obj) { const char *mirror, *uvisage; struct monst *mtmp; @@ -845,7 +1024,7 @@ struct obj *obj; boolean vis, invis_mirror, useeit, monable; if (!getdir((char *) 0)) - return 0; + return ECMD_CANCEL; invis_mirror = Invis; useeit = !Blind && (!invis_mirror || See_invisible); uvisage = beautiful(); @@ -853,7 +1032,9 @@ struct obj *obj; if (obj->cursed && !rn2(2)) { if (!Blind) pline_The("%s fogs up and doesn't reflect!", mirror); - return 1; + else + pline("%s", nothing_seems_to_happen); + return ECMD_TIME; } if (!u.dx && !u.dy && !u.dz) { if (!useeit) { @@ -869,55 +1050,60 @@ struct obj *obj; pline("Yikes! You've frozen yourself!"); if (!Hallucination || !rn2(4)) { nomul(-rnd(MAXULEV + 6 - u.ulevel)); - multi_reason = "gazing into a mirror"; + gm.multi_reason = "gazing into a mirror"; } - nomovemsg = 0; /* default, "you can move again" */ + gn.nomovemsg = 0; /* default, "you can move again" */ } - } else if (youmonst.data->mlet == S_VAMPIRE) + } else if (is_vampire(gy.youmonst.data) + || is_vampshifter(&gy.youmonst)) { You("don't have a reflection."); - else if (u.umonnum == PM_UMBER_HULK) { + } else if (u.umonnum == PM_UMBER_HULK) { pline("Huh? That doesn't look like you!"); make_confused(HConfusion + d(3, 4), FALSE); - } else if (Hallucination) + } else if (Hallucination) { You(look_str, hcolor((char *) 0)); - else if (Sick) + } else if (Sick) { You(look_str, "peaked"); - else if (u.uhs >= WEAK) + } else if (u.uhs >= WEAK) { You(look_str, "undernourished"); - else + } else if (Upolyd) { + You("look like %s.", an(pmname(&mons[u.umonnum], Ugender))); + } else { You("look as %s as ever.", uvisage); + } } - return 1; + return ECMD_TIME; } if (u.uswallow) { if (useeit) You("reflect %s %s.", s_suffix(mon_nam(u.ustuck)), mbodypart(u.ustuck, STOMACH)); - return 1; + return ECMD_TIME; } if (Underwater) { if (useeit) - You(Hallucination ? "give the fish a chance to fix their makeup." - : "reflect the murky water."); - return 1; + You("%s.", + Hallucination ? "give the fish a chance to fix their makeup" + : "reflect the murky water"); + return ECMD_TIME; } if (u.dz) { if (useeit) You("reflect the %s.", (u.dz > 0) ? surface(u.ux, u.uy) : ceiling(u.ux, u.uy)); - return 1; + return ECMD_TIME; } mtmp = bhit(u.dx, u.dy, COLNO, INVIS_BEAM, - (int FDECL((*), (MONST_P, OBJ_P))) 0, - (int FDECL((*), (OBJ_P, OBJ_P))) 0, &obj); - if (!mtmp || !haseyes(mtmp->data) || notonhead) - return 1; + (int (*) (MONST_P, OBJ_P)) 0, + (int (*) (OBJ_P, OBJ_P)) 0, &obj); + if (!mtmp || !haseyes(mtmp->data) || gn.notonhead) + return ECMD_TIME; /* couldsee(mtmp->mx, mtmp->my) is implied by the fact that bhit() - targetted it, so we can ignore possibility of X-ray vision */ + targeted it, so we can ignore possibility of X-ray vision */ vis = canseemon(mtmp); -/* ways to directly see monster (excludes X-ray vision, telepathy, - extended detection, type-specific warning) */ + /* ways to directly see monster (excludes X-ray vision, telepathy, + extended detection, type-specific warning) */ #define SEENMON (MONSEEN_NORMAL | MONSEEN_SEEINVIS | MONSEEN_INFRAVIS) how_seen = vis ? howmonseen(mtmp) : 0; /* whether monster is able to use its vision-based capabilities */ @@ -936,18 +1122,19 @@ struct obj *obj; /* infravision doesn't produce an image in the mirror */ } else if ((how_seen & SEENMON) == MONSEEN_INFRAVIS) { if (vis) /* (redundant) */ - pline("%s is too far away to see %sself in the dark.", - Monnam(mtmp), mhim(mtmp)); + pline("%s in the dark.", + monverbself(mtmp, Monnam(mtmp), "are", + "too far away to see")); /* some monsters do special things */ } else if (mlet == S_VAMPIRE || mlet == S_GHOST || is_vampshifter(mtmp)) { if (vis) pline("%s doesn't have a reflection.", Monnam(mtmp)); } else if (monable && mtmp->data == &mons[PM_MEDUSA]) { if (mon_reflects(mtmp, "The gaze is reflected away by %s %s!")) - return 1; + return ECMD_TIME; if (vis) pline("%s is turned to stone!", Monnam(mtmp)); - stoned = TRUE; + gs.stoned = TRUE; killed(mtmp); } else if (monable && mtmp->data == &mons[PM_FLOATING_EYE]) { int tmp = d((int) mtmp->m_lev, (int) mtmp->data->mattk[0].damd); @@ -962,12 +1149,13 @@ struct obj *obj; if (vis) pline("%s confuses itself!", Monnam(mtmp)); mtmp->mconf = 1; - } else if (monable && (mlet == S_NYMPH || mtmp->data == &mons[PM_SUCCUBUS] - || mtmp->data == &mons[PM_INCUBUS])) { + } else if (monable && (mlet == S_NYMPH + || mtmp->data == &mons[PM_AMOROUS_DEMON])) { if (vis) { char buf[BUFSZ]; /* "She" or "He" */ - pline("%s admires %sself in your %s.", Monnam(mtmp), mhim(mtmp), + pline("%s in your %s.", /* " admires self in your mirror " */ + monverbself(mtmp, Monnam(mtmp), "admire", (char *) 0), mirror); pline("%s takes it!", upstart(strcpy(buf, mhe(mtmp)))); } else @@ -976,8 +1164,9 @@ struct obj *obj; freeinv(obj); (void) mpickobj(mtmp, obj); if (!tele_restrict(mtmp)) - (void) rloc(mtmp, TRUE); + (void) rloc(mtmp, RLOC_MSG); } else if (!is_unicorn(mtmp->data) && !humanoid(mtmp->data) + && !is_demon(mtmp->data) && (!mtmp->minvis || perceives(mtmp->data)) && rn2(5)) { boolean do_react = TRUE; @@ -985,7 +1174,8 @@ struct obj *obj; if (vis) You("discern no obvious reaction from %s.", mon_nam(mtmp)); else - You_feel("a bit silly gesturing the mirror in that direction."); + You_feel( + "a bit silly gesturing the mirror in that direction."); do_react = FALSE; } if (do_react) { @@ -998,33 +1188,31 @@ struct obj *obj; ; else if ((mtmp->minvis && !perceives(mtmp->data)) /* redundant: can't get here if these are true */ - || !haseyes(mtmp->data) || notonhead || !mtmp->mcansee) + || !haseyes(mtmp->data) || gn.notonhead || !mtmp->mcansee) pline("%s doesn't seem to notice %s reflection.", Monnam(mtmp), mhis(mtmp)); else pline("%s ignores %s reflection.", Monnam(mtmp), mhis(mtmp)); } - return 1; + return ECMD_TIME; +#undef SEENMON } -STATIC_OVL void -use_bell(optr) -struct obj **optr; +staticfn void +use_bell(struct obj **optr) { - register struct obj *obj = *optr; + struct obj *obj = *optr; struct monst *mtmp; boolean wakem = FALSE, learno = FALSE, ordinary = (obj->otyp != BELL_OF_OPENING || !obj->spe), - invoking = - (obj->otyp == BELL_OF_OPENING && invocation_pos(u.ux, u.uy) - && !On_stairs(u.ux, u.uy)); + invoking = (obj->otyp == BELL_OF_OPENING + && invocation_pos(u.ux, u.uy) + && !On_stairs(u.ux, u.uy)); + Hero_playnotes(obj_to_instr(obj), "C", 100); You("ring %s.", the(xname(obj))); if (Underwater || (u.uswallow && ordinary)) { -#ifdef AMIGA - amii_speaker(obj, "AhDhGqEqDhEhAqDqFhGw", AMII_MUFFLED_VOLUME); -#endif pline("But the sound is muffled."); } else if (invoking && ordinary) { @@ -1033,16 +1221,13 @@ struct obj **optr; learno = TRUE; /* help player figure out why */ } else if (ordinary) { -#ifdef AMIGA - amii_speaker(obj, "ahdhgqeqdhehaqdqfhgw", AMII_MUFFLED_VOLUME); -#endif if (obj->cursed && !rn2(4) /* note: once any of them are gone, we stop all of them */ - && !(mvitals[PM_WOOD_NYMPH].mvflags & G_GONE) - && !(mvitals[PM_WATER_NYMPH].mvflags & G_GONE) - && !(mvitals[PM_MOUNTAIN_NYMPH].mvflags & G_GONE) - && (mtmp = makemon(mkclass(S_NYMPH, 0), u.ux, u.uy, NO_MINVENT)) - != 0) { + && !(svm.mvitals[PM_WOOD_NYMPH].mvflags & G_GONE) + && !(svm.mvitals[PM_WATER_NYMPH].mvflags & G_GONE) + && !(svm.mvitals[PM_MOUNTAIN_NYMPH].mvflags & G_GONE) + && (mtmp = makemon(mkclass(S_NYMPH, 0), u.ux, u.uy, + NO_MINVENT | MM_NOMSG)) != 0) { You("summon %s!", a_monnam(mtmp)); if (!obj_resists(obj, 93, 100)) { pline("%s shattered!", Tobjnam(obj, "have")); @@ -1056,8 +1241,8 @@ struct obj **optr; mon_adjust_speed(mtmp, 2, (struct obj *) 0); break; case 2: /* no explanation; it just happens... */ - nomovemsg = ""; - multi_reason = NULL; + gn.nomovemsg = ""; + gm.multi_reason = NULL; nomul(-rnd(2)); break; } @@ -1084,19 +1269,13 @@ struct obj **optr; } else if (invoking) { pline("%s an unsettling shrill sound...", Tobjnam(obj, "issue")); -#ifdef AMIGA - amii_speaker(obj, "aefeaefeaefeaefeaefe", AMII_LOUDER_VOLUME); -#endif - obj->age = moves; + obj->age = svm.moves; learno = TRUE; wakem = TRUE; } else if (obj->blessed) { int res = 0; -#ifdef AMIGA - amii_speaker(obj, "ahahahDhEhCw", AMII_SOFT_VOLUME); -#endif if (uchain) { unpunish(); res = 1; @@ -1120,9 +1299,6 @@ struct obj **optr; } } else { /* uncursed */ -#ifdef AMIGA - amii_speaker(obj, "AeFeaeFeAefegw", AMII_OKAY_VOLUME); -#endif if (findit() != 0) learno = TRUE; else @@ -1136,12 +1312,11 @@ struct obj **optr; obj->known = 1; } if (wakem) - wake_nearby(); + wake_nearby(TRUE); } -STATIC_OVL void -use_candelabrum(obj) -register struct obj *obj; +staticfn void +use_candelabrum(struct obj *obj) { const char *s = (obj->spe != 1) ? "candles" : "candle"; @@ -1151,7 +1326,16 @@ register struct obj *obj; return; } if (obj->spe <= 0) { + struct obj *otmp; + pline("This %s has no %s.", xname(obj), s); + /* only output tip if candles are in inventory */ + for (otmp = gi.invent; otmp; otmp = otmp->nobj) + if (Is_candle(otmp)) + break; + if (otmp) + pline("To attach candles, apply them instead of the %s.", + xname(obj)); return; } if (Underwater) { @@ -1180,6 +1364,13 @@ register struct obj *obj; 1 would yield 0, confusing begin_burn() and producing an unlightable, unrefillable candelabrum; round up instead */ obj->age = (obj->age + 1L) / 2L; + + /* to make absolutely sure the game doesn't become unwinnable as + a consequence of a broken candelabrum */ + if (obj->age == 0) { + impossible("Candelabrum with candles but no fuel?"); + obj->age = 1; + } } else { if (obj->spe == 7) { if (Blind) @@ -1192,20 +1383,21 @@ register struct obj *obj; begin_burn(obj, FALSE); } -STATIC_OVL void -use_candle(optr) -struct obj **optr; +staticfn void +use_candle(struct obj **optr) { - register struct obj *obj = *optr; - register struct obj *otmp; + struct obj *obj = *optr; + struct obj *otmp; const char *s = (obj->quan != 1) ? "candles" : "candle"; char qbuf[QBUFSZ], qsfx[QBUFSZ], *q; + boolean was_lamplit; if (u.uswallow) { You(no_elbow_room); return; } + /* obj is the candle; otmp is the candelabrum */ otmp = carrying(CANDELABRUM_OF_INVOCATION); if (!otmp || otmp->spe == 7) { use_lamp(obj); @@ -1220,7 +1412,7 @@ struct obj **optr; if ((q = strstri(qbuf, " to\033")) != 0) Strcpy(q, " to "); /* last, format final "attach candles to candelabrum?" query */ - if (yn(safe_qbuf(qbuf, qbuf, "?", otmp, yname, thesimpleoname, "it")) + if (y_n(safe_qbuf(qbuf, qbuf, "?", otmp, yname, thesimpleoname, "it")) == 'n') { use_lamp(obj); return; @@ -1232,20 +1424,34 @@ struct obj **optr; s = (obj->quan != 1) ? "candles" : "candle"; } else *optr = 0; + + /* The candle's age field doesn't correctly reflect the amount + of fuel in it while it's lit, because the fuel is measured + by the timer. So to get accurate age updating, we need to + end the burn temporarily while attaching the candle. */ + was_lamplit = obj->lamplit; + if (was_lamplit) + end_burn(obj, TRUE); + You("attach %ld%s %s to %s.", obj->quan, !otmp->spe ? "" : " more", s, the(xname(otmp))); if (!otmp->spe || otmp->age > obj->age) otmp->age = obj->age; otmp->spe += (int) obj->quan; - if (otmp->lamplit && !obj->lamplit) + if (otmp->lamplit && !was_lamplit) pline_The("new %s magically %s!", s, vtense(s, "ignite")); - else if (!otmp->lamplit && obj->lamplit) + else if (!otmp->lamplit && was_lamplit) pline("%s out.", (obj->quan > 1L) ? "They go" : "It goes"); - if (obj->unpaid) + if (obj->unpaid) { + struct monst *shkp VOICEONLY + = shop_keeper(*in_rooms(u.ux, u.uy, SHOPBASE)); + + SetVoice(shkp, 0, 80, 0); verbalize("You %s %s, you bought %s!", otmp->lamplit ? "burn" : "use", (obj->quan > 1L) ? "them" : "it", (obj->quan > 1L) ? "them" : "it"); + } if (obj->quan < 7L && otmp->spe == 7) pline("%s now has seven%s candles attached.", The(xname(otmp)), otmp->lamplit ? " lit" : ""); @@ -1253,8 +1459,6 @@ struct obj **optr; if (otmp->lamplit) obj_merge_light_sources(otmp, otmp); /* candles are no longer a separate light source */ - if (obj->lamplit) - end_burn(obj, TRUE); /* candles are now gone */ useupall(obj); /* candelabrum's weight is changing */ @@ -1265,15 +1469,14 @@ struct obj **optr; /* call in drop, throw, and put in box, etc. */ boolean -snuff_candle(otmp) -struct obj *otmp; +snuff_candle(struct obj *otmp) { boolean candle = Is_candle(otmp); if ((candle || otmp->otyp == CANDELABRUM_OF_INVOCATION) && otmp->lamplit) { char buf[BUFSZ]; - xchar x, y; + coordxy x, y; boolean many = candle ? (otmp->quan > 1L) : (otmp->spe > 1); (void) get_obj_location(otmp, &x, &y, 0); @@ -1291,10 +1494,9 @@ struct obj *otmp; you've been swallowed by a monster; obj might be in transit while being thrown or dropped so don't assume that its location is valid */ boolean -snuff_lit(obj) -struct obj *obj; +snuff_lit(struct obj *obj) { - xchar x, y; + coordxy x, y; if (obj->lamplit) { if (obj->otyp == OIL_LAMP || obj->otyp == MAGIC_LAMP @@ -1311,36 +1513,108 @@ struct obj *obj; return FALSE; } -/* Called when potentially lightable object is affected by fire_damage(). - Return TRUE if object was lit and FALSE otherwise --ALI */ +/* called when lit object is hit by water */ boolean -catch_lit(obj) -struct obj *obj; +splash_lit(struct obj *obj) { - xchar x, y; + boolean result, dunk = FALSE; + + /* lantern won't be extinguished by a rust trap or rust monster attack + but will be if submerged or placed into a container or swallowed by + a monster (for mobile light source handling, not because it ought + to stop being lit in all those situations...) */ + if (obj->lamplit && obj->otyp == BRASS_LANTERN) { + struct monst *mtmp; + boolean useeit = FALSE, uhearit = FALSE, snuff = TRUE; + + if (obj->where == OBJ_INVENT) { + useeit = !Blind; + uhearit = !Deaf; + /* underwater light sources aren't allowed but if hero + is just entering water, Underwater won't be set yet */ + dunk = (is_pool(u.ux, u.uy) + && ((!Levitation && !Flying && !Wwalking) + || Is_waterlevel(&u.uz))); + snuff = FALSE; + } else if (obj->where == OBJ_MINVENT + /* don't assume that lit lantern has been swallowed; + a nymph might have stolen it or picked it up */ + && ((mtmp = obj->ocarry), humanoid(mtmp->data))) { + coordxy x, y; + + useeit = get_obj_location(obj, &x, &y, 0) && cansee(x, y); + uhearit = couldsee(x, y) && distu(x, y) < 5 * 5; + dunk = (is_pool(mtmp->mx, mtmp->my) + && ((!is_flyer(mtmp->data) && !is_floater(mtmp->data)) + || Is_waterlevel(&u.uz))); + snuff = FALSE; + if (useeit) + set_msg_xy(x, y); + } - if (!obj->lamplit && (obj->otyp == MAGIC_LAMP || ignitable(obj))) { - if ((obj->otyp == MAGIC_LAMP - || obj->otyp == CANDELABRUM_OF_INVOCATION) && obj->spe == 0) - return FALSE; - else if (obj->otyp != MAGIC_LAMP && obj->age == 0) + if (useeit || uhearit) + pline("%s %s%s%s.", Yname2(obj), + uhearit ? "crackles" : "", + (uhearit && useeit) ? " and " : "", + useeit ? "flickers" : ""); + if (!dunk && !snuff) return FALSE; - if (!get_obj_location(obj, &x, &y, 0)) + } + + result = snuff_lit(obj); + + /* this is simpler when we wait until after lantern has been snuffed */ + if (dunk) { + /* drain some of the battery but don't short it out entirely */ + obj->age -= (obj->age > 200L) ? 100L : (obj->age / 2L); + } + return result; +} + +/* Called when potentially lightable object is affected by fire_damage(). + Return TRUE if object becomes lit and FALSE otherwise --ALI */ +boolean +catch_lit(struct obj *obj) +{ + coordxy x, y; + + if (!obj->lamplit && ignitable(obj) && get_obj_location(obj, &x, &y, 0)) { + if (((obj->otyp == MAGIC_LAMP /* spe==0 => no djinni inside */ + /* spe==0 => no candles attached */ + || obj->otyp == CANDELABRUM_OF_INVOCATION) && obj->spe == 0) + /* age_is_relative && age==0 && still-exists means out of fuel */ + || (age_is_relative(obj) && obj->age == 0) + /* lantern is classified as ignitable() but not by fire */ + || obj->otyp == BRASS_LANTERN) return FALSE; if (obj->otyp == CANDELABRUM_OF_INVOCATION && obj->cursed) return FALSE; - if ((obj->otyp == OIL_LAMP || obj->otyp == MAGIC_LAMP - || obj->otyp == BRASS_LANTERN) && obj->cursed && !rn2(2)) + if ((obj->otyp == OIL_LAMP || obj->otyp == MAGIC_LAMP) + /* once lit, cursed lamp is as good as non-cursed one, so failure + to light is a minor inconvenience to make cursed be worse */ + && obj->cursed && !rn2(2)) return FALSE; - if (obj->where == OBJ_MINVENT ? cansee(x, y) : !Blind) - pline("%s %s light!", Yname2(obj), otense(obj, "catch")); + + if (obj->where == OBJ_INVENT || cansee(x, y)) { + if (obj->where == OBJ_FLOOR && cansee(x, y)) + set_msg_xy(x, y); + pline("%s %s %s", Yname2(obj), + /* "catches light!" or "feels warm." */ + otense(obj, Blind ? "feel" : "catch"), + Blind ? "warm." : "light!"); + } if (obj->otyp == POT_OIL) makeknown(obj->otyp); if (carried(obj) && obj->unpaid && costly_spot(u.ux, u.uy)) { + struct monst *shkp VOICEONLY + = shop_keeper(*in_rooms(u.ux, u.uy, SHOPBASE)); + /* if it catches while you have it, then it's your tough luck */ check_unpaid(obj); + SetVoice(shkp, 0, 80, 0); verbalize("That's in addition to the cost of %s %s, of course.", - yname(obj), obj->quan == 1L ? "itself" : "themselves"); + yname(obj), + (obj->quan == 1L) ? "itself" : "themselves"); bill_dummy_object(obj); } begin_burn(obj, FALSE); @@ -1349,51 +1623,74 @@ struct obj *obj; return FALSE; } -STATIC_OVL void -use_lamp(obj) -struct obj *obj; +/* light a lamp or candle */ +staticfn void +use_lamp(struct obj *obj) { char buf[BUFSZ]; + const char *lamp = (obj->otyp == OIL_LAMP + || obj->otyp == MAGIC_LAMP) ? "lamp" + : (obj->otyp == BRASS_LANTERN) ? "lantern" + : NULL; + + /* + * When blind, lamps' and candles' on/off state can be distinguished + * by heat. For brass lantern assume that there is an on/off switch + * that can be felt. + */ if (obj->lamplit) { - if (obj->otyp == OIL_LAMP || obj->otyp == MAGIC_LAMP - || obj->otyp == BRASS_LANTERN) - pline("%slamp is now off.", Shk_Your(buf, obj)); + if (lamp) /* lamp or lantern */ + pline("%s%s is now off.", Shk_Your(buf, obj), lamp); else You("snuff out %s.", yname(obj)); end_burn(obj, TRUE); return; } if (Underwater) { - pline(!Is_candle(obj) ? "This is not a diving lamp" - : "Sorry, fire and water don't mix."); + pline("%s.", + !Is_candle(obj) ? "This is not a diving lamp" + : "Sorry, fire and water don't mix"); return; } /* magic lamps with an spe == 0 (wished for) cannot be lit */ if ((!Is_candle(obj) && obj->age == 0) || (obj->otyp == MAGIC_LAMP && obj->spe == 0)) { - if (obj->otyp == BRASS_LANTERN) - Your("lamp has run out of power."); - else + if (obj->otyp == BRASS_LANTERN) { + if (!Blind) + Your("lantern is out of power."); + else + pline("%s", nothing_seems_to_happen); + } else { pline("This %s has no oil.", xname(obj)); + } return; } if (obj->cursed && !rn2(2)) { - if (!Blind) + if ((obj->otyp == OIL_LAMP || obj->otyp == MAGIC_LAMP) && !rn2(3)) { + pline_The("lamp spills and covers your %s with oil.", + fingers_or_gloves(TRUE)); + make_glib((int) (Glib & TIMEOUT) + d(2, 10)); + } else if (!Blind) { pline("%s for a moment, then %s.", Tobjnam(obj, "flicker"), otense(obj, "die")); + } else { + pline("%s", nothing_seems_to_happen); + } } else { - if (obj->otyp == OIL_LAMP || obj->otyp == MAGIC_LAMP - || obj->otyp == BRASS_LANTERN) { + if (lamp) { /* lamp or lantern */ check_unpaid(obj); - pline("%slamp is now on.", Shk_Your(buf, obj)); + pline("%s%s is now on.", Shk_Your(buf, obj), lamp); } else { /* candle(s) */ pline("%s flame%s %s%s", s_suffix(Yname2(obj)), plur(obj->quan), otense(obj, "burn"), Blind ? "." : " brightly!"); if (obj->unpaid && costly_spot(u.ux, u.uy) && obj->age == 20L * (long) objects[obj->otyp].oc_cost) { const char *ithem = (obj->quan > 1L) ? "them" : "it"; + struct monst *shkp VOICEONLY + = shop_keeper(*in_rooms(u.ux, u.uy, SHOPBASE)); + SetVoice(shkp, 0, 80, 0); verbalize("You burn %s, you bought %s!", ithem, ithem); bill_dummy_object(obj); } @@ -1402,9 +1699,8 @@ struct obj *obj; } } -STATIC_OVL void -light_cocktail(optr) -struct obj **optr; +staticfn void +light_cocktail(struct obj **optr) { struct obj *obj = *optr; /* obj is a potion of oil */ char buf[BUFSZ]; @@ -1421,10 +1717,14 @@ struct obj **optr; /* * Free & add to re-merge potion. This will average the * age of the potions. Not exactly the best solution, - * but its easy. + * but its easy. Don't do that unless obj is not worn (uwep, + * uswapwep, or uquiver) because if wielded and other oil is + * quivered a "null obj after quiver merge" panic will occur. */ - freeinv(obj); - *optr = addinv(obj); + if (!obj->owornmask) { + freeinv(obj); + *optr = addinv(obj); + } return; } else if (Underwater) { There("is not enough oxygen to sustain a fire."); @@ -1439,10 +1739,14 @@ struct obj **optr; Blind ? "" : " It gives off a dim light."); if (obj->unpaid && costly_spot(u.ux, u.uy)) { + struct monst *shkp VOICEONLY = shop_keeper(*in_rooms(u.ux, u.uy, + SHOPBASE)); + /* Normally, we shouldn't both partially and fully charge * for an item, but (Yendorian Fuel) Taxes are inevitable... */ check_unpaid(obj); + SetVoice(shkp, 0, 80, 0); verbalize("That's in addition to the cost of the potion, of course."); bill_dummy_object(obj); } @@ -1460,25 +1764,53 @@ struct obj **optr; *optr = obj; } -static NEARDATA const char cuddly[] = { TOOL_CLASS, GEM_CLASS, 0 }; +/* getobj callback for object to be rubbed - not selecting a secondary object + to rub on a gray stone or rub jelly on */ +staticfn int +rub_ok(struct obj *obj) +{ + if (!obj) + return GETOBJ_EXCLUDE; + + if (obj->otyp == OIL_LAMP || obj->otyp == MAGIC_LAMP + || obj->otyp == BRASS_LANTERN || is_graystone(obj) + || obj->otyp == LUMP_OF_ROYAL_JELLY) + return GETOBJ_SUGGEST; + + return GETOBJ_EXCLUDE; +} +/* the #rub command */ int -dorub() +dorub(void) { - struct obj *obj = getobj(cuddly, "rub"); + struct obj *obj; - if (obj && obj->oclass == GEM_CLASS) { + if (nohands(gy.youmonst.data)) { + You("aren't able to rub anything without hands."); + return ECMD_OK; + } + obj = getobj("rub", rub_ok, GETOBJ_NOFLAGS); + if (!obj) + return ECMD_CANCEL; + if (obj->oclass == GEM_CLASS || obj->oclass == FOOD_CLASS) { if (is_graystone(obj)) { - use_stone(obj); - return 1; + return use_stone(obj); + } else if (obj->otyp == LUMP_OF_ROYAL_JELLY) { + return use_royal_jelly(&obj); } else { pline("Sorry, I don't know how to use that."); - return 0; + return ECMD_OK; } } - - if (!obj || !wield_tool(obj, "rub")) - return 0; + if (obj != uwep) { + if (wield_tool(obj, "rub")) { + cmdq_add_ec(CQ_CANNED, dorub); + cmdq_add_key(CQ_CANNED, obj->invlet); + return ECMD_TIME; + } + return ECMD_OK; + } /* now uwep is obj */ if (uwep->otyp == MAGIC_LAMP) { @@ -1507,11 +1839,12 @@ dorub() pline("Anyway, nothing exciting happens."); } else pline1(nothing_happens); - return 1; + return ECMD_TIME; } +/* the #jump command */ int -dojump() +dojump(void) { /* Physical jump */ return jump(0); @@ -1525,10 +1858,8 @@ enum jump_trajectory { }; /* callback routine for walk_path() */ -STATIC_PTR boolean -check_jump(arg, x, y) -genericptr arg; -int x, y; +staticfn boolean +check_jump(genericptr arg, coordxy x, coordxy y) { int traj = *(int *) arg; struct rm *lev = &levl[x][y]; @@ -1553,15 +1884,13 @@ int x, y; /* let giants jump over boulders (what about Flying? and is there really enough head room for giants to jump at all, let alone over something tall?) */ - if (sobj_at(BOULDER, x, y) && !throws_rocks(youmonst.data)) + if (sobj_at(BOULDER, x, y) && !throws_rocks(gy.youmonst.data)) return FALSE; return TRUE; } -STATIC_OVL boolean -is_valid_jump_pos(x, y, magic, showmsg) -int x, y, magic; -boolean showmsg; +staticfn boolean +is_valid_jump_pos(coordxy x, coordxy y, int magic, boolean showmsg) { if (!magic && !(HJumping & ~INTRINSIC) && !EJumping && distu(x, y) != 5) { /* The Knight jumping restriction still applies when riding a @@ -1586,14 +1915,14 @@ boolean showmsg; coord uc, tc; struct rm *lev = &levl[u.ux][u.uy]; /* we want to categorize trajectory for use in determining - passage through doorways: horizonal, vertical, or diagonal; + passage through doorways: horizontal, vertical, or diagonal; since knight's jump and other irregular directions are possible, we flatten those out to simplify door checks */ - int diag, traj, - dx = x - u.ux, dy = y - u.uy, - ax = abs(dx), ay = abs(dy); + int diag, traj; + coordxy dx = x - u.ux, dy = y - u.uy, + ax = abs(dx), ay = abs(dy); - /* diag: any non-orthogonal destination classifed as diagonal */ + /* diag: any non-orthogonal destination classified as diagonal */ diag = (magic || Passes_walls || (!dx && !dy)) ? jAny : !dy ? jHorz : !dx ? jVert : jDiag; /* traj: flatten out the trajectory => some diagonals re-classified */ @@ -1626,149 +1955,134 @@ boolean showmsg; return TRUE; } -static int jumping_is_magic; - -STATIC_OVL boolean -get_valid_jump_position(x,y) -int x,y; +staticfn boolean +get_valid_jump_position(coordxy x, coordxy y) { return (isok(x, y) && (ACCESSIBLE(levl[x][y].typ) || Passes_walls) - && is_valid_jump_pos(x, y, jumping_is_magic, FALSE)); + && is_valid_jump_pos(x, y, gj.jumping_is_magic, FALSE)); } -STATIC_OVL void -display_jump_positions(state) -int state; +staticfn void +display_jump_positions(boolean on_off) { - if (state == 0) { - tmp_at(DISP_BEAM, cmap_to_glyph(S_goodpos)); - } else if (state == 1) { - int x, y, dx, dy; + coordxy x, y, dx, dy; + if (on_off) { + /* on */ + tmp_at(DISP_BEAM, cmap_to_glyph(S_goodpos)); for (dx = -4; dx <= 4; dx++) for (dy = -4; dy <= 4; dy++) { - x = dx + (int) u.ux; - y = dy + (int) u.uy; - if (get_valid_jump_position(x, y)) + x = dx + u.ux; + y = dy + u.uy; + if (get_valid_jump_position(x, y) && !u_at(x, y)) tmp_at(x, y); } } else { + /* off */ tmp_at(DISP_END, 0); } } int -jump(magic) -int magic; /* 0=Physical, otherwise skill level */ +jump(int magic) /* 0=Physical, otherwise skill level */ { coord cc; /* attempt "jumping" spell if hero has no innate jumping ability */ - if (!magic && !Jumping) { - int sp_no; - - for (sp_no = 0; sp_no < MAXSPELL; ++sp_no) - if (spl_book[sp_no].sp_id == NO_SPELL) - break; - else if (spl_book[sp_no].sp_id == SPE_JUMPING) - return spelleffects(sp_no, FALSE); - } + if (!magic && !Jumping && known_spell(SPE_JUMPING) >= spe_Fresh) + return spelleffects(SPE_JUMPING, FALSE, FALSE); - if (!magic && (nolimbs(youmonst.data) || slithy(youmonst.data))) { + if (!magic && (nolimbs(gy.youmonst.data) || slithy(gy.youmonst.data))) { /* normally (nolimbs || slithy) implies !Jumping, but that isn't necessarily the case for knights */ You_cant("jump; you have no legs!"); - return 0; + return ECMD_OK; } else if (!magic && !Jumping) { You_cant("jump very far."); - return 0; + return ECMD_OK; + /* if steed is immobile, can't do physical jump but can do spell one */ } else if (!magic && u.usteed && stucksteed(FALSE)) { /* stucksteed gave " won't move" message */ - return 0; + return ECMD_OK; } else if (u.uswallow) { if (magic) { You("bounce around a little."); - return 1; + return ECMD_TIME; } pline("You've got to be kidding!"); - return 0; + return ECMD_OK; } else if (u.uinwater) { if (magic) { You("swish around a little."); - return 1; + return ECMD_TIME; } pline("This calls for swimming, not jumping!"); - return 0; + return ECMD_OK; } else if (u.ustuck) { if (u.ustuck->mtame && !Conflict && !u.ustuck->mconf) { - You("pull free from %s.", mon_nam(u.ustuck)); - u.ustuck = 0; - return 1; + struct monst *mtmp = u.ustuck; + + set_ustuck((struct monst *) 0); + You("pull free from %s.", mon_nam(mtmp)); + return ECMD_TIME; } if (magic) { You("writhe a little in the grasp of %s!", mon_nam(u.ustuck)); - return 1; + return ECMD_TIME; } You("cannot escape from %s!", mon_nam(u.ustuck)); - return 0; + return ECMD_OK; } else if (Levitation || Is_airlevel(&u.uz) || Is_waterlevel(&u.uz)) { if (magic) { You("flail around a little."); - return 1; + return ECMD_TIME; } You("don't have enough traction to jump."); - return 0; + return ECMD_OK; } else if (!magic && near_capacity() > UNENCUMBERED) { You("are carrying too much to jump!"); - return 0; + return ECMD_OK; } else if (!magic && (u.uhunger <= 100 || ACURR(A_STR) < 6)) { You("lack the strength to jump!"); - return 0; + return ECMD_OK; } else if (!magic && Wounded_legs) { - long wl = (Wounded_legs & BOTH_SIDES); - const char *bp = body_part(LEG); - - if (wl == BOTH_SIDES) - bp = makeplural(bp); - if (u.usteed) - pline("%s is in no shape for jumping.", Monnam(u.usteed)); - else - Your("%s%s %s in no shape for jumping.", - (wl == LEFT_SIDE) ? "left " : (wl == RIGHT_SIDE) ? "right " - : "", - bp, (wl == BOTH_SIDES) ? "are" : "is"); - return 0; + legs_in_no_shape("jumping", u.usteed != 0); + return ECMD_OK; } else if (u.usteed && u.utrap) { pline("%s is stuck in a trap.", Monnam(u.usteed)); - return 0; + return ECMD_OK; } pline("Where do you want to jump?"); cc.x = u.ux; cc.y = u.uy; - jumping_is_magic = magic; + gj.jumping_is_magic = magic; getpos_sethilite(display_jump_positions, get_valid_jump_position); if (getpos(&cc, TRUE, "the desired position") < 0) - return 0; /* user pressed ESC */ + return ECMD_CANCEL; /* user pressed ESC */ if (!is_valid_jump_pos(cc.x, cc.y, magic, TRUE)) { - return 0; + return ECMD_FAIL; + } else if (u.usteed && u_at(cc.x, cc.y)) { + pline("%s isn't capable of jumping in place.", YMonnam(u.usteed)); + return ECMD_FAIL; } else { coord uc; + long side; int range, temp; + boolean wastrapped = FALSE; - if (u.utrap) + if (u.utrap) { + wastrapped = TRUE; switch (u.utraptype) { - case TT_BEARTRAP: { - long side = rn2(3) ? LEFT_SIDE : RIGHT_SIDE; - + case TT_BEARTRAP: + side = rn2(3) ? LEFT_SIDE : RIGHT_SIDE; You("rip yourself free of the bear trap! Ouch!"); losehp(Maybe_Half_Phys(rnd(10)), "jumping out of a bear trap", KILLED_BY); set_wounded_legs(side, rn1(1000, 500)); break; - } case TT_PIT: You("leap from the pit!"); break; @@ -1778,8 +2092,8 @@ int magic; /* 0=Physical, otherwise skill level */ break; case TT_LAVA: You("pull yourself above the %s!", hliquid("lava")); - reset_utrap(TRUE); - return 1; + cc.x = u.ux, cc.y = u.uy; /* take u_at() 'if' below */ + break; case TT_BURIEDBALL: case TT_INFLOOR: You("strain your %s, but you're still %s.", @@ -1789,8 +2103,35 @@ int magic; /* 0=Physical, otherwise skill level */ : "attached to the buried ball"); set_wounded_legs(LEFT_SIDE, rn1(10, 11)); set_wounded_legs(RIGHT_SIDE, rn1(10, 11)); - return 1; + return ECMD_TIME; + default: + impossible("Jumping out of strange trap (%d)?", u.utraptype); + break; + } + /* if we reach here, hero is no longer trapped */ + reset_utrap(TRUE); + } + /* jumping on hero's same spot doesn't use walk_path() and isn't + allowed when riding (handled above) */ + if (u_at(cc.x, cc.y)) { + struct trap *t; + + /* escaping from a trap takes precedence over jumping in place */ + if (wastrapped) { + morehungry(rnd(10)); + return ECMD_TIME; + } + /* jumping in place on a trap will trigger it */ + if ((t = t_at(cc.x, cc.y)) != 0) { + You("jump up and %s back down.", !Flying ? "come" : "fly"); + dotrap(t, FORCETRAP | TOOKPLUNGE); + return ECMD_TIME; } + /* jumping in place takes no time and doesn't exercise anything */ + You("%s.", Hallucination ? "hop up and down a bit" + : "decide not to jump after all"); + return ECMD_OK; + } /* * Check the path from uc to cc, calling hurtle_step at each @@ -1813,19 +2154,17 @@ int magic; /* 0=Physical, otherwise skill level */ * and usually moves the ball if punished, but does not handle all * the effects of landing on the final position. */ - teleds(cc.x, cc.y, FALSE); - sokoban_guilt(); + teleds(cc.x, cc.y, TELEDS_NO_FLAGS); nomul(-1); - multi_reason = "jumping around"; - nomovemsg = ""; + gm.multi_reason = "jumping around"; + gn.nomovemsg = ""; morehungry(rnd(25)); - return 1; + return ECMD_TIME; } } boolean -tinnable(corpse) -struct obj *corpse; +tinnable(struct obj *corpse) { if (corpse->oeaten) return 0; @@ -1834,11 +2173,11 @@ struct obj *corpse; return 1; } -STATIC_OVL void -use_tinning_kit(obj) -struct obj *obj; +staticfn void +use_tinning_kit(struct obj *obj) { struct obj *corpse, *can; + struct permonst *mptr; /* This takes only 1 move. If this is to be changed to take many * moves, we've got to deal with decaying corpses... @@ -1853,29 +2192,29 @@ struct obj *obj; You("cannot tin %s which is partly eaten.", something); return; } - if (touch_petrifies(&mons[corpse->corpsenm]) && !Stone_resistance - && !uarmg) { + mptr = &mons[corpse->corpsenm]; + if (touch_petrifies(mptr) && !Stone_resistance && !uarmg) { char kbuf[BUFSZ]; + const char *corpse_name = an(cxname(corpse)); - if (poly_when_stoned(youmonst.data)) - You("tin %s without wearing gloves.", - an(mons[corpse->corpsenm].mname)); - else { + if (poly_when_stoned(gy.youmonst.data)) { + You("tin %s without wearing gloves.", corpse_name); + kbuf[0] = '\0'; + } else { pline("Tinning %s without wearing gloves is a fatal mistake...", - an(mons[corpse->corpsenm].mname)); - Sprintf(kbuf, "trying to tin %s without gloves", - an(mons[corpse->corpsenm].mname)); + corpse_name); + Sprintf(kbuf, "trying to tin %s without gloves", corpse_name); } instapetrify(kbuf); } - if (is_rider(&mons[corpse->corpsenm])) { + if (is_rider(mptr)) { if (revive_corpse(corpse)) verbalize("Yes... But War does not preserve its enemies..."); else pline_The("corpse evades your grasp."); return; } - if (mons[corpse->corpsenm].cnutrit == 0) { + if (mptr->cnutrit == 0) { pline("That's too insubstantial to tin."); return; } @@ -1892,12 +2231,22 @@ struct obj *obj; /* Mark tinned tins. No spinach allowed... */ set_tin_variety(can, HOMEMADE_TIN); if (carried(corpse)) { - if (corpse->unpaid) + if (corpse->unpaid) { + struct monst *shkp VOICEONLY = shop_keeper(*in_rooms( + u.ux, u.uy, SHOPBASE)); + + SetVoice(shkp, 0, 80, 0); verbalize(you_buy_it); + } useup(corpse); } else { - if (costly_spot(corpse->ox, corpse->oy) && !corpse->no_charge) + if (costly_spot(corpse->ox, corpse->oy) && !corpse->no_charge) { + struct monst *shkp VOICEONLY + = shop_keeper(*in_rooms(corpse->ox, corpse->oy, SHOPBASE)); + + SetVoice(shkp, 0, 80, 0); verbalize(you_buy_it); + } useupf(corpse, 1L); } (void) hold_another_object(can, "You make, but cannot pick up, %s.", @@ -1907,14 +2256,12 @@ struct obj *obj; } void -use_unicorn_horn(obj) -struct obj *obj; +use_unicorn_horn(struct obj **optr) { #define PROP_COUNT 7 /* number of properties we're dealing with */ -#define ATTR_COUNT (A_MAX * 3) /* number of attribute points we might fix */ - int idx, val, val_limit, trouble_count, unfixable_trbl, did_prop, - did_attr; - int trouble_list[PROP_COUNT + ATTR_COUNT]; + int idx, val, val_limit, trouble_count, unfixable_trbl, did_prop; + int trouble_list[PROP_COUNT]; + struct obj *obj = (optr ? *optr : (struct obj *) 0); if (obj && obj->cursed) { long lcount = (long) rn1(90, 10); @@ -1926,7 +2273,7 @@ struct obj *obj; xname(obj), TRUE, SICK_NONVOMITABLE); break; case 1: - make_blinded((Blinded & TIMEOUT) + lcount, TRUE); + make_blinded(BlindedTimeout + lcount, TRUE); break; case 2: if (!Confusion) @@ -1938,7 +2285,10 @@ struct obj *obj; make_stunned((HStun & TIMEOUT) + lcount, TRUE); break; case 4: - (void) adjattrib(rn2(A_MAX), -1, FALSE); + if (Vomiting) + vomit(); + else + make_vomiting(14L, FALSE); break; case 5: (void) make_hallucinated((HHallucination & TIMEOUT) + lcount, @@ -1946,28 +2296,22 @@ struct obj *obj; break; case 6: if (Deaf) /* make_deaf() won't give feedback when already deaf */ - pline("Nothing seems to happen."); + pline("%s", nothing_seems_to_happen); make_deaf((HDeaf & TIMEOUT) + lcount, TRUE); break; } return; } -/* - * Entries in the trouble list use a very simple encoding scheme. - */ -#define prop2trbl(X) ((X) + A_MAX) -#define attr2trbl(Y) (Y) -#define prop_trouble(X) trouble_list[trouble_count++] = prop2trbl(X) -#define attr_trouble(Y) trouble_list[trouble_count++] = attr2trbl(Y) +#define prop_trouble(X) trouble_list[trouble_count++] = (X) #define TimedTrouble(P) (((P) && !((P) & ~TIMEOUT)) ? ((P) & TIMEOUT) : 0L) - trouble_count = unfixable_trbl = did_prop = did_attr = 0; + trouble_count = unfixable_trbl = did_prop = 0; /* collect property troubles */ if (TimedTrouble(Sick)) prop_trouble(SICK); - if (TimedTrouble(Blinded) > (long) u.ucreamed + if (TimedTrouble(HBlinded) > (long) u.ucreamed && !(u.uswallow && attacktype_fordmg(u.ustuck->data, AT_ENGL, AD_BLND))) prop_trouble(BLINDED); @@ -1982,44 +2326,11 @@ struct obj *obj; if (TimedTrouble(HDeaf)) prop_trouble(DEAF); - unfixable_trbl = unfixable_trouble_count(TRUE); - - /* collect attribute troubles */ - for (idx = 0; idx < A_MAX; idx++) { - if (ABASE(idx) >= AMAX(idx)) - continue; - val_limit = AMAX(idx); - /* this used to adjust 'val_limit' for A_STR when u.uhs was - WEAK or worse, but that's handled via ATEMP(A_STR) now */ - if (Fixed_abil) { - /* potion/spell of restore ability override sustain ability - intrinsic but unicorn horn usage doesn't */ - unfixable_trbl += val_limit - ABASE(idx); - continue; - } - /* don't recover more than 3 points worth of any attribute */ - if (val_limit > ABASE(idx) + 3) - val_limit = ABASE(idx) + 3; - - for (val = ABASE(idx); val < val_limit; val++) - attr_trouble(idx); - /* keep track of unfixed trouble, for message adjustment below */ - unfixable_trbl += (AMAX(idx) - val_limit); - } - if (trouble_count == 0) { pline1(nothing_happens); return; - } else if (trouble_count > 1) { /* shuffle */ - int i, j, k; - - for (i = trouble_count - 1; i > 0; i--) - if ((j = rn2(i + 1)) != i) { - k = trouble_list[j]; - trouble_list[j] = trouble_list[i]; - trouble_list[i] = k; - } - } + } else if (trouble_count > 1) + shuffle_int_array(trouble_list, trouble_count); /* * Chances for number of troubles to be fixed @@ -2036,60 +2347,47 @@ struct obj *obj; idx = trouble_list[val]; switch (idx) { - case prop2trbl(SICK): + case SICK: make_sick(0L, (char *) 0, TRUE, SICK_ALL); did_prop++; break; - case prop2trbl(BLINDED): + case BLINDED: make_blinded((long) u.ucreamed, TRUE); did_prop++; break; - case prop2trbl(HALLUC): + case HALLUC: (void) make_hallucinated(0L, TRUE, 0L); did_prop++; break; - case prop2trbl(VOMITING): + case VOMITING: make_vomiting(0L, TRUE); did_prop++; break; - case prop2trbl(CONFUSION): + case CONFUSION: make_confused(0L, TRUE); did_prop++; break; - case prop2trbl(STUNNED): + case STUNNED: make_stunned(0L, TRUE); did_prop++; break; - case prop2trbl(DEAF): + case DEAF: make_deaf(0L, TRUE); did_prop++; break; default: - if (idx >= 0 && idx < A_MAX) { - ABASE(idx) += 1; - did_attr++; - } else - panic("use_unicorn_horn: bad trouble? (%d)", idx); + impossible("use_unicorn_horn: bad trouble? (%d)", idx); break; } } - if (did_attr || did_prop) - context.botl = TRUE; - if (did_attr) - pline("This makes you feel %s!", - (did_prop + did_attr) == (trouble_count + unfixable_trbl) - ? "great" - : "better"); - else if (!did_prop) - pline("Nothing seems to happen."); + if (did_prop) + disp.botl = TRUE; + else + pline("%s", nothing_seems_to_happen); #undef PROP_COUNT -#undef ATTR_COUNT -#undef prop2trbl -#undef attr2trbl #undef prop_trouble -#undef attr_trouble #undef TimedTrouble } @@ -2097,9 +2395,7 @@ struct obj *obj; * Timer callback routine: turn figurine into monster */ void -fig_transform(arg, timeout) -anything *arg; -long timeout; +fig_transform(anything *arg, long timeout) { struct obj *figurine = arg->a_obj; struct monst *mtmp; @@ -2110,10 +2406,10 @@ long timeout; char monnambuf[BUFSZ], carriedby[BUFSZ]; if (!figurine) { - debugpline0("null figurine in fig_transform()"); + impossible("null figurine in fig_transform()"); return; } - silent = (timeout != monstermoves); /* happened while away */ + silent = (timeout != svm.moves); /* happened while away */ okay_spot = get_obj_location(figurine, &cc.x, &cc.y, 0); if (figurine->where == OBJ_INVENT || figurine->where == OBJ_MINVENT) okay_spot = enexto(&cc, cc.x, cc.y, &mons[figurine->corpsenm]); @@ -2128,7 +2424,7 @@ long timeout; mtmp = make_familiar(figurine, cc.x, cc.y, TRUE); if (mtmp) { char and_vanish[BUFSZ]; - struct obj *mshelter = level.objects[mtmp->mx][mtmp->my]; + struct obj *mshelter = svl.level.objects[mtmp->mx][mtmp->my]; /* [m_monnam() yields accurate mon type, overriding hallucination] */ Sprintf(monnambuf, "%s", an(m_monnam(mtmp))); @@ -2161,6 +2457,7 @@ long timeout; case OBJ_FLOOR: if (cansee_spot && !silent) { + set_msg_xy(cc.x, cc.y); if (suppress_see) pline("%s suddenly vanishes!", an(xname(figurine))); else @@ -2210,13 +2507,10 @@ long timeout; newsym(cc.x, cc.y); } -STATIC_OVL boolean -figurine_location_checks(obj, cc, quietly) -struct obj *obj; -coord *cc; -boolean quietly; +staticfn boolean +figurine_location_checks(struct obj *obj, coord *cc, boolean quietly) { - xchar x, y; + coordxy x, y; if (carried(obj) && u.uswallow) { if (!quietly) @@ -2230,7 +2524,7 @@ boolean quietly; You("cannot put the figurine there."); return FALSE; } - if (IS_ROCK(levl[x][y].typ) + if (IS_OBSTRUCTED(levl[x][y].typ) && !(passes_walls(&mons[obj->corpsenm]) && may_passwall(x, y))) { if (!quietly) You("cannot place a figurine in %s!", @@ -2246,22 +2540,21 @@ boolean quietly; return TRUE; } -STATIC_OVL void -use_figurine(optr) -struct obj **optr; +staticfn int +use_figurine(struct obj **optr) { - register struct obj *obj = *optr; - xchar x, y; + struct obj *obj = *optr; + coordxy x, y; coord cc; if (u.uswallow) { /* can't activate a figurine while swallowed */ if (!figurine_location_checks(obj, (coord *) 0, FALSE)) - return; + return ECMD_OK; } if (!getdir((char *) 0)) { - context.move = multi = 0; - return; + svc.context.move = gm.multi = 0; + return ECMD_CANCEL; } x = u.ux + u.dx; y = u.uy + u.dy; @@ -2269,7 +2562,7 @@ struct obj **optr; cc.y = y; /* Passing FALSE arg here will result in messages displayed */ if (!figurine_location_checks(obj, &cc, FALSE)) - return; + return ECMD_TIME; You("%s and it %stransforms.", (u.dx || u.dy) ? "set the figurine beside you" : (Is_airlevel(&u.uz) || Is_waterlevel(&u.uz) @@ -2284,13 +2577,31 @@ struct obj **optr; if (Blind) map_invisible(cc.x, cc.y); *optr = 0; + return ECMD_TIME; } -static NEARDATA const char lubricables[] = { ALL_CLASSES, ALLOW_NONE, 0 }; +/* getobj callback for object to apply grease to */ +staticfn int +grease_ok(struct obj *obj) +{ + if (!obj) + return GETOBJ_SUGGEST; + + /* note: if changing the list of ungreasable objects, also change + special_throne_effect in sit.c */ + if (obj->oclass == COIN_CLASS) + return GETOBJ_EXCLUDE; -STATIC_OVL void -use_grease(obj) -struct obj *obj; + if (inaccessible_equipment(obj, (const char *) 0, FALSE)) + return GETOBJ_EXCLUDE_INACCESS; + + /* Possible extension: don't suggest greasing objects which are already + * greased. */ + return GETOBJ_SUGGEST; +} + +staticfn int +use_grease(struct obj *obj) { struct obj *otmp; @@ -2298,7 +2609,7 @@ struct obj *obj; pline("%s from your %s.", Tobjnam(obj, "slip"), fingers_or_gloves(FALSE)); dropx(obj); - return; + return ECMD_TIME; } if (obj->spe > 0) { @@ -2310,20 +2621,20 @@ struct obj *obj; pline("%s from your %s.", Tobjnam(obj, "slip"), fingers_or_gloves(FALSE)); dropx(obj); - return; + return ECMD_TIME; } - otmp = getobj(lubricables, "grease"); + otmp = getobj("grease", grease_ok, GETOBJ_PROMPT); if (!otmp) - return; + return ECMD_CANCEL; if (inaccessible_equipment(otmp, "grease", FALSE)) - return; + return ECMD_OK; consume_obj_charge(obj, TRUE); oldglib = (int) (Glib & TIMEOUT); - if (otmp != &zeroobj) { + if (otmp != &hands_obj) { You("cover %s with a thick layer of grease.", yname(otmp)); otmp->greased = 1; - if (obj->cursed && !nohands(youmonst.data)) { + if (obj->cursed && !nohands(gy.youmonst.data)) { make_glib(oldglib + rn1(6, 10)); /* + 10..15 */ pline("Some of the grease gets all over your %s.", fingers_or_gloves(TRUE)); @@ -2339,60 +2650,80 @@ struct obj *obj; pline("%s to be empty.", Tobjnam(obj, "seem")); } update_inventory(); + return ECMD_TIME; +} + +/* getobj callback for object to rub on a known touchstone */ +staticfn int +touchstone_ok(struct obj *obj) +{ + if (!obj) + return GETOBJ_EXCLUDE; + + /* Gold being suggested as a rub target is questionable - it fits the + * real-world historic use of touchstones, but doesn't do anything + * significant in the game. */ + if (obj->oclass == COIN_CLASS) + return GETOBJ_SUGGEST; + + /* don't suggest identified gems */ + if (obj->oclass == GEM_CLASS + && !(obj->dknown && objects[obj->otyp].oc_name_known)) + return GETOBJ_SUGGEST; + + return GETOBJ_DOWNPLAY; } + /* touchstones - by Ken Arnold */ -STATIC_OVL void -use_stone(tstone) -struct obj *tstone; +staticfn int +use_stone(struct obj *tstone) { static const char scritch[] = "\"scritch, scritch\""; - static const char allowall[3] = { COIN_CLASS, ALL_CLASSES, 0 }; - static const char coins_gems[3] = { COIN_CLASS, GEM_CLASS, 0 }; struct obj *obj; boolean do_scratch; - const char *streak_color, *choices; + const char *streak_color; char stonebuf[QBUFSZ]; int oclass; + boolean known; /* in case it was acquired while blinded */ if (!Blind) - tstone->dknown = 1; + observe_object(tstone); + known = (tstone->otyp == TOUCHSTONE && tstone->dknown + && objects[TOUCHSTONE].oc_name_known); + Sprintf(stonebuf, "rub on the stone%s", plur(tstone->quan)); /* when the touchstone is fully known, don't bother listing extra junk as likely candidates for rubbing */ - choices = (tstone->otyp == TOUCHSTONE && tstone->dknown - && objects[TOUCHSTONE].oc_name_known) - ? coins_gems - : allowall; - Sprintf(stonebuf, "rub on the stone%s", plur(tstone->quan)); - if ((obj = getobj(choices, stonebuf)) == 0) - return; + if ((obj = getobj(stonebuf, known ? touchstone_ok : any_obj_ok, + GETOBJ_PROMPT)) == 0) + return ECMD_CANCEL; if (obj == tstone && obj->quan == 1L) { You_cant("rub %s on itself.", the(xname(obj))); - return; + return ECMD_OK; } if (tstone->otyp == TOUCHSTONE && tstone->cursed && obj->oclass == GEM_CLASS && !is_graystone(obj) && !obj_resists(obj, 80, 100)) { if (Blind) - pline("You feel something shatter."); + You_feel("something shatter."); else if (Hallucination) pline("Oh, wow, look at the pretty shards."); else pline("A sharp crack shatters %s%s.", (obj->quan > 1L) ? "one of " : "", the(xname(obj))); useup(obj); - return; + return ECMD_TIME; } if (Blind) { pline(scritch); - return; + return ECMD_TIME; } else if (Hallucination) { pline("Oh wow, man: Fractals!"); - return; + return ECMD_TIME; } do_scratch = FALSE; @@ -2417,7 +2748,7 @@ struct obj *tstone; makeknown(TOUCHSTONE); makeknown(obj->otyp); prinv((char *) 0, obj, 0L); - return; + return ECMD_TIME; } else { /* either a ring or the touchstone was not effective */ if (objects[obj->otyp].oc_material == GLASS) { @@ -2432,13 +2763,13 @@ struct obj *tstone; switch (objects[obj->otyp].oc_material) { case CLOTH: pline("%s a little more polished now.", Tobjnam(tstone, "look")); - return; + return ECMD_TIME; case LIQUID: if (!obj->known) /* note: not "whetstone" */ You("must think this is a wetstone, do you?"); else pline("%s a little wetter now.", Tobjnam(tstone, "are")); - return; + return ECMD_TIME; case WAX: streak_color = "waxy"; break; /* okay even if not touchstone */ @@ -2475,27 +2806,19 @@ struct obj *tstone; You_see("%s streaks on the %s.", streak_color, stonebuf); else pline(scritch); - return; + return ECMD_TIME; } -static struct trapinfo { - struct obj *tobj; - xchar tx, ty; - int time_needed; - boolean force_bungle; -} trapinfo; - void -reset_trapset() +reset_trapset(void) { - trapinfo.tobj = 0; - trapinfo.force_bungle = 0; + gt.trapinfo.tobj = 0; + gt.trapinfo.force_bungle = 0; } /* Place a landmine/bear trap. Helge Hafting */ -STATIC_OVL void -use_trap(otmp) -struct obj *otmp; +staticfn void +use_trap(struct obj *otmp) { int ttyp, tmp; const char *what = (char *) 0; @@ -2503,13 +2826,12 @@ struct obj *otmp; int levtyp = levl[u.ux][u.uy].typ; const char *occutext = "setting the trap"; - if (nohands(youmonst.data)) + if (nohands(gy.youmonst.data)) what = "without hands"; else if (Stunned) what = "while stunned"; else if (u.uswallow) - what = - is_animal(u.ustuck->data) ? "while swallowed" : "while engulfed"; + what = digests(u.ustuck->data) ? "while swallowed" : "while engulfed"; else if (Underwater) what = "underwater"; else if (Levitation) @@ -2518,10 +2840,10 @@ struct obj *otmp; what = "in water"; else if (is_lava(u.ux, u.uy)) what = "in lava"; - else if (On_stairs(u.ux, u.uy)) - what = (u.ux == xdnladder || u.ux == xupladder) ? "on the ladder" - : "on the stairs"; - else if (IS_FURNITURE(levtyp) || IS_ROCK(levtyp) + else if (On_stairs(u.ux, u.uy)) { + stairway *stway = stairway_at(u.ux, u.uy); + what = stway->isladder ? "on the ladder" : "on the stairs"; + } else if (IS_FURNITURE(levtyp) || IS_OBSTRUCTED(levtyp) || closed_door(u.ux, u.uy) || t_at(u.ux, u.uy)) what = "here"; else if (Is_airlevel(&u.uz) || Is_waterlevel(&u.uz)) @@ -2536,22 +2858,22 @@ struct obj *otmp; return; } ttyp = (otmp->otyp == LAND_MINE) ? LANDMINE : BEAR_TRAP; - if (otmp == trapinfo.tobj && u.ux == trapinfo.tx && u.uy == trapinfo.ty) { + if (otmp == gt.trapinfo.tobj && u_at(gt.trapinfo.tx, gt.trapinfo.ty)) { You("resume setting %s%s.", shk_your(buf, otmp), - defsyms[trap_to_defsym(what_trap(ttyp, rn2))].explanation); + trapname(ttyp, FALSE)); set_occupation(set_trap, occutext, 0); return; } - trapinfo.tobj = otmp; - trapinfo.tx = u.ux, trapinfo.ty = u.uy; + gt.trapinfo.tobj = otmp; + gt.trapinfo.tx = u.ux, gt.trapinfo.ty = u.uy; tmp = ACURR(A_DEX); - trapinfo.time_needed = + gt.trapinfo.time_needed = (tmp > 17) ? 2 : (tmp > 12) ? 3 : (tmp > 7) ? 4 : 5; if (Blind) - trapinfo.time_needed *= 2; + gt.trapinfo.time_needed *= 2; tmp = ACURR(A_STR); if (ttyp == BEAR_TRAP && tmp < 18) - trapinfo.time_needed += (tmp > 12) ? 1 : (tmp > 7) ? 2 : 4; + gt.trapinfo.time_needed += (tmp > 12) ? 1 : (tmp > 7) ? 2 : 4; /*[fumbling and/or confusion and/or cursed object check(s) should be incorporated here instead of in set_trap]*/ if (u.usteed && P_SKILL(P_RIDING) < P_BASIC) { @@ -2563,20 +2885,17 @@ struct obj *otmp; chance = (rnl(10) > 5); You("aren't very skilled at reaching from %s.", mon_nam(u.usteed)); Sprintf(buf, "Continue your attempt to set %s?", - the(defsyms[trap_to_defsym(what_trap(ttyp, rn2))] - .explanation)); - if (yn(buf) == 'y') { + the(trapname(ttyp, FALSE))); + if (y_n(buf) == 'y') { if (chance) { switch (ttyp) { case LANDMINE: /* set it off */ - trapinfo.time_needed = 0; - trapinfo.force_bungle = TRUE; + gt.trapinfo.time_needed = 0; + gt.trapinfo.force_bungle = TRUE; break; case BEAR_TRAP: /* drop it without arming it */ reset_trapset(); - You("drop %s!", - the(defsyms[trap_to_defsym(what_trap(ttyp, rn2))] - .explanation)); + You("drop %s!", the(trapname(ttyp, FALSE))); dropx(otmp); return; } @@ -2586,28 +2905,27 @@ struct obj *otmp; return; } } - You("begin setting %s%s.", shk_your(buf, otmp), - defsyms[trap_to_defsym(what_trap(ttyp, rn2))].explanation); + You("begin setting %s%s.", shk_your(buf, otmp), trapname(ttyp, FALSE)); + use_unpaid_trapobj(otmp, u.ux, u.uy); set_occupation(set_trap, occutext, 0); return; } -STATIC_PTR -int -set_trap() +/* occupation routine called each turn while arming a beartrap or landmine */ +staticfn int +set_trap(void) { - struct obj *otmp = trapinfo.tobj; + struct obj *otmp = gt.trapinfo.tobj; struct trap *ttmp; int ttyp; - if (!otmp || !carried(otmp) || u.ux != trapinfo.tx - || u.uy != trapinfo.ty) { - /* ?? */ + if (!otmp || !carried(otmp) || !u_at(gt.trapinfo.tx, gt.trapinfo.ty)) { + /* trap object might have been stolen or hero teleported */ reset_trapset(); return 0; } - if (--trapinfo.time_needed > 0) + if (--gt.trapinfo.time_needed > 0) return 1; /* still busy */ ttyp = (otmp->otyp == LAND_MINE) ? LANDMINE : BEAR_TRAP; @@ -2618,13 +2936,12 @@ set_trap() if (*in_rooms(u.ux, u.uy, SHOPBASE)) { add_damage(u.ux, u.uy, 0L); /* schedule removal */ } - if (!trapinfo.force_bungle) - You("finish arming %s.", - the(defsyms[trap_to_defsym(what_trap(ttyp, rn2))].explanation)); + if (!gt.trapinfo.force_bungle) + You("finish arming %s.", the(trapname(ttyp, FALSE))); if (((otmp->cursed || Fumbling) && (rnl(10) > 5)) - || trapinfo.force_bungle) + || gt.trapinfo.force_bungle) dotrap(ttmp, - (unsigned) (trapinfo.force_bungle ? FORCEBUNGLE : 0)); + (unsigned) (gt.trapinfo.force_bungle ? FORCEBUNGLE : 0)); } else { /* this shouldn't happen */ Your("trap setting attempt fails."); @@ -2634,33 +2951,33 @@ set_trap() return 0; } -STATIC_OVL int -use_whip(obj) -struct obj *obj; +int +use_whip(struct obj *obj) { char buf[BUFSZ]; struct monst *mtmp; struct obj *otmp; - int rx, ry, proficient, res = 0; + int rx, ry, proficient, res = ECMD_OK; const char *msg_slipsfree = "The bullwhip slips free."; const char *msg_snap = "Snap!"; if (obj != uwep) { - if (!wield_tool(obj, "lash")) - return 0; - else - res = 1; + if (wield_tool(obj, "lash")) { + cmdq_add_ec(CQ_CANNED, doapply); + cmdq_add_key(CQ_CANNED, obj->invlet); + return ECMD_TIME; + } + return ECMD_OK; } if (!getdir((char *) 0)) - return res; + return (res|ECMD_CANCEL); if (u.uswallow) { mtmp = u.ustuck; rx = mtmp->mx; ry = mtmp->my; } else { - if (Stunned || (Confusion && !rn2(5))) - confdir(); + confdir(FALSE); rx = u.ux + u.dx; ry = u.uy + u.dy; if (!isok(rx, ry)) { @@ -2685,7 +3002,7 @@ struct obj *obj; if (proficient < 0) proficient = 0; - if (u.uswallow && attack(u.ustuck)) { + if (u.uswallow) { There("is not enough room to flick your bullwhip."); } else if (Underwater) { @@ -2694,6 +3011,12 @@ struct obj *obj; } else if (u.dz < 0) { You("flick a bug off of the %s.", ceiling(u.ux, u.uy)); + } else if (!u.dz && (IS_WATERWALL(levl[rx][ry].typ) + || levl[rx][ry].typ == LAVAWALL)) { + You("cause a small splash."); + if (levl[rx][ry].typ == LAVAWALL) + (void) fire_damage(uwep, FALSE, rx, ry); + return ECMD_TIME; } else if ((!u.dx && !u.dy) || (u.dz > 0)) { int dam; @@ -2701,21 +3024,34 @@ struct obj *obj; if (u.usteed && !rn2(proficient + 2)) { You("whip %s!", mon_nam(u.usteed)); kick_steed(); - return 1; + return ECMD_TIME; } - if (Levitation || u.usteed) { - /* Have a shot at snaring something on the floor */ - otmp = level.objects[u.ux][u.uy]; - if (otmp && otmp->otyp == CORPSE && otmp->corpsenm == PM_HORSE) { + if (is_pool_or_lava(u.ux, u.uy) + || IS_WATERWALL(levl[rx][ry].typ) + || levl[rx][ry].typ == LAVAWALL) { + You("cause a small splash."); + if (is_lava(u.ux, u.uy)) + (void) fire_damage(uwep, FALSE, u.ux, u.uy); + return ECMD_TIME; + } + if (Levitation || u.usteed || Flying) { + /* Have a shot at snaring something on the floor. A flyer + can reach the floor so could just pick an item up, but + allow snagging by whip too. */ + otmp = svl.level.objects[u.ux][u.uy]; + if (otmp && otmp->otyp == CORPSE + && (otmp->corpsenm == PM_HORSE + || otmp->corpsenm == little_to_big(PM_HORSE) /* warhorse */ + || otmp->corpsenm == big_to_little(PM_HORSE))) { /* pony */ pline("Why beat a dead horse?"); - return 1; + return ECMD_TIME; } if (otmp && proficient) { You("wrap your bullwhip around %s on the %s.", an(singular(otmp, xname)), surface(u.ux, u.uy)); if (rnl(6) || pickup_object(otmp, 1L, TRUE) < 1) pline1(msg_slipsfree); - return 1; + return ECMD_TIME; } } dam = rnd(2) + dbon() + obj->spe; @@ -2724,7 +3060,7 @@ struct obj *obj; You("hit your %s with your bullwhip.", body_part(FOOT)); Sprintf(buf, "killed %sself with %s bullwhip", uhim(), uhis()); losehp(Maybe_Half_Phys(dam), buf, NO_KILLER_PREFIX); - return 1; + return ECMD_TIME; } else if ((Fumbling || Glib) && !rn2(5)) { pline_The("bullwhip slips out of your %s.", body_part(HAND)); @@ -2736,34 +3072,34 @@ struct obj *obj; * * if you're in a pit * - you are attempting to get out of the pit - * or, if you are applying it towards a small monster - * - then it is assumed that you are trying to hit it - * else if the monster is wielding a weapon - * - you are attempting to disarm a monster - * else - * - you are attempting to hit the monster. + * - if there is no suitable boulder or furniture to target, + * target a big monster for that, or if a small or medium + * monster is present, attack it + * [if both boulder and furniture are present, target the + * former because it is on top of the latter] + * else if you are applying it towards a monster + * - if monster is concealed, reveal it and proceed; + * - if it was not concealed and is wielding a weapon, attempt + * to disarm it; + * - otherwise attack it. * * if you're confused (and thus off the mark) * - you only end up hitting. - * */ - const char *wrapped_what = (char *) 0; + const char *wrapped_what = sobj_at(BOULDER, rx, ry) ? "a boulder" + : IS_FURNITURE(levl[rx][ry].typ) + ? something : (char *) 0; if (mtmp) { - if (bigmonst(mtmp->data)) { + /* if a big monster is known to be present, target it in + preference to boulder or furniture; if any small or medium + monster is present, or an unseen big one, use the boulder + or furniture if available, otherwise attack */ + if (bigmonst(mtmp->data) && canspotmon(mtmp)) wrapped_what = strcpy(buf, mon_nam(mtmp)); - } else if (proficient) { - if (attack(mtmp)) - return 1; - else - pline1(msg_snap); - } - } - if (!wrapped_what) { - if (IS_FURNITURE(levl[rx][ry].typ)) - wrapped_what = something; - else if (sobj_at(BOULDER, rx, ry)) - wrapped_what = "a boulder"; + + if (!wrapped_what) + goto whipattack; } if (wrapped_what) { coord cc; @@ -2772,11 +3108,13 @@ struct obj *obj; cc.y = ry; You("wrap your bullwhip around %s.", wrapped_what); if (proficient && rn2(proficient + 2)) { - if (!mtmp || enexto(&cc, rx, ry, youmonst.data)) { + if (!mtmp || enexto(&cc, rx, ry, gy.youmonst.data)) { You("yank yourself out of the pit!"); - teleds(cc.x, cc.y, TRUE); - reset_utrap(TRUE); - vision_full_recalc = 1; + reset_utrap(TRUE); /* [was after teleds(); do this before + * in case it has no alternative other + * than to put hero in another trap] */ + teleds(cc.x, cc.y, TELEDS_ALLOW_DRAG); + gv.vision_full_recalc = 1; } } else { pline1(msg_slipsfree); @@ -2787,11 +3125,31 @@ struct obj *obj; pline1(msg_snap); } else if (mtmp) { - if (!canspotmon(mtmp) && !glyph_is_invisible(levl[rx][ry].glyph)) { - pline("A monster is there that you couldn't see."); - map_invisible(rx, ry); + whipattack: + otmp = 0; /* if monster is unseen, can't attempt to disarm it */ + if (!canspotmon(mtmp)) { + boolean spotitnow; + + mtmp->mundetected = 0; /* bring non-mimic hider out of hiding */ + /* check visibility again after mundetected=0 in case being + brought out of hiding has exposed it (might not if hero is + blind or formerly hidden monster is also invisible) */ + spotitnow = canspotmon(mtmp); + if (spotitnow || !glyph_is_invisible(levl[rx][ry].glyph)) { + pline("%s is there that you %s.", + !spotitnow ? "A monster" : Amonnam(mtmp), + !Blind ? "couldn't see" : "hadn't noticed"); + if (!spotitnow) + map_invisible(rx, ry); + else + newsym(rx, ry); + } + } else { + /* monster is known so if it is wielding something, try to + disarm it rather than make a direct attack */ + otmp = MON_WEP(mtmp); } - otmp = MON_WEP(mtmp); /* can be null */ + if (otmp) { char onambuf[BUFSZ]; const char *mon_hand; @@ -2832,11 +3190,12 @@ struct obj *obj; if (!rn2(25)) { /* proficient with whip, but maybe not so proficient at catching weapons */ - int hitu, hitvalu; + int dam, hitvalu, hitu; + dam = dmgval(otmp, &gy.youmonst); hitvalu = 8 + otmp->spe; - hitu = thitu(hitvalu, dmgval(otmp, &youmonst), - &otmp, (char *)0); + hitu = thitu(hitvalu, Maybe_Half_Phys(dam), + &otmp, (char *) 0); if (hitu) { pline_The("%s hits you as you try to snatch it!", the(onambuf)); @@ -2851,14 +3210,22 @@ struct obj *obj; if (otmp->otyp == CORPSE && touch_petrifies(&mons[otmp->corpsenm]) && !uarmg && !Stone_resistance - && !(poly_when_stoned(youmonst.data) + && !(poly_when_stoned(gy.youmonst.data) && polymon(PM_STONE_GOLEM))) { char kbuf[BUFSZ]; - Sprintf(kbuf, "%s corpse", - an(mons[otmp->corpsenm].mname)); + Strcpy(kbuf, (otmp->quan == 1L) ? an(onambuf) + : onambuf); pline("Snatching %s is a fatal mistake.", kbuf); + /* corpse probably has a rot timer but is now + OBJ_FREE; end of game cleanup will panic if + it isn't part of current level; plus it would + be missing from bones, so put it on the floor */ + place_object(otmp, u.ux, u.uy); /* but don't stack */ + instapetrify(kbuf); + /* life-saved; free the corpse again */ + obj_extract_self(otmp); } (void) hold_another_object(otmp, "You drop %s!", doname(otmp), (const char *) 0); @@ -2875,20 +3242,23 @@ struct obj *obj; } else { pline1(msg_slipsfree); } - wakeup(mtmp, TRUE); - } else { + } else { /* mtmp isn't wielding a weapon; attack it */ + boolean do_snap = TRUE; + if (M_AP_TYPE(mtmp) && !Protection_from_shape_changers - && !sensemon(mtmp)) + && !sensemon(mtmp)) { stumble_onto_mimic(mtmp); - else + do_snap = FALSE; + } else { You("flick your bullwhip towards %s.", mon_nam(mtmp)); - if (proficient) { - if (attack(mtmp)) - return 1; - else - pline1(msg_snap); } + if (proficient && force_attack(mtmp, FALSE)) + return ECMD_TIME; + if (do_snap) + pline1(msg_snap); } + /* regardless of mtmp's weapon or hero's proficiency */ + wakeup(mtmp, TRUE); } else if (Is_airlevel(&u.uz) || Is_waterlevel(&u.uz)) { /* it must be air -- water checked above */ @@ -2897,7 +3267,7 @@ struct obj *obj; } else { pline1(msg_snap); } - return 1; + return ECMD_TIME; } static const char @@ -2906,28 +3276,26 @@ static const char cant_see_spot[] = "won't hit anything if you can't see that spot.", cant_reach[] = "can't reach that spot from here."; +#define glyph_is_poleable(G) \ + (glyph_is_monster(G) || glyph_is_invisible(G) || glyph_is_statue(G)) + /* find pos of monster in range, if only one monster */ -STATIC_OVL boolean -find_poleable_mon(pos, min_range, max_range) -coord *pos; -int min_range, max_range; +staticfn boolean +find_poleable_mon(coord *pos) { struct monst *mtmp; - coord mpos; + coord mpos = { 0, 0 }; /* no candidate location yet */ boolean impaired; - int x, y, lo_x, hi_x, lo_y, hi_y, rt, glyph; + coordxy x, y, lo_x, hi_x, lo_y, hi_y, rt; + int glyph; - if (Blind) - return FALSE; /* must be able to see target location */ impaired = (Confusion || Stunned || Hallucination); - mpos.x = mpos.y = 0; /* no candidate location yet */ - rt = isqrt(max_range); + rt = isqrt(gp.polearm_range_max); lo_x = max(u.ux - rt, 1), hi_x = min(u.ux + rt, COLNO - 1); lo_y = max(u.uy - rt, 0), hi_y = min(u.uy + rt, ROWNO - 1); for (x = lo_x; x <= hi_x; ++x) { for (y = lo_y; y <= hi_y; ++y) { - if (distu(x, y) < min_range || distu(x, y) > max_range - || !isok(x, y) || !cansee(x, y)) + if (!get_valid_polearm_position(x, y)) continue; glyph = glyph_at(x, y); if (!impaired @@ -2935,10 +3303,8 @@ int min_range, max_range; && (mtmp = m_at(x, y)) != 0 && (mtmp->mtame || (mtmp->mpeaceful && flags.confirm))) continue; - if (glyph_is_monster(glyph) - || glyph_is_warning(glyph) - || glyph_is_invisible(glyph) - || (glyph_is_statue(glyph) && impaired)) { + if (glyph_is_poleable(glyph) + && (!glyph_is_statue(glyph) || impaired)) { if (mpos.x) return FALSE; /* more than one candidate location */ mpos.x = x, mpos.y = y; @@ -2951,29 +3317,29 @@ int min_range, max_range; return TRUE; } -static int polearm_range_min = -1; -static int polearm_range_max = -1; - -STATIC_OVL boolean -get_valid_polearm_position(x, y) -int x, y; +staticfn boolean +get_valid_polearm_position(coordxy x, coordxy y) { - return (isok(x, y) && ACCESSIBLE(levl[x][y].typ) - && distu(x, y) >= polearm_range_min - && distu(x, y) <= polearm_range_max); + int glyph; + + glyph = glyph_at(x, y); + + return (isok(x, y) && distu(x, y) >= gp.polearm_range_min + && distu(x, y) <= gp.polearm_range_max + && (cansee(x, y) || (couldsee(x, y) + && glyph_is_poleable(glyph)))); } -STATIC_OVL void -display_polearm_positions(state) -int state; +staticfn void +display_polearm_positions(boolean on_off) { - if (state == 0) { - tmp_at(DISP_BEAM, cmap_to_glyph(S_goodpos)); - } else if (state == 1) { - int x, y, dx, dy; + coordxy x, y, dx, dy; - for (dx = -4; dx <= 4; dx++) - for (dy = -4; dy <= 4; dy++) { + if (on_off) { + /* on */ + tmp_at(DISP_BEAM, cmap_to_glyph(S_goodpos)); + for (dx = -3; dx <= 3; dx++) + for (dy = -3; dy <= 3; dy++) { x = dx + (int) u.ux; y = dy + (int) u.uy; if (get_valid_polearm_position(x, y)) { @@ -2981,106 +3347,181 @@ int state; } } } else { + /* off */ tmp_at(DISP_END, 0); } } +/* + * Calculate allowable range (pole's reach is always 2 steps): + * unskilled and basic: orthogonal direction, 4..4; + * skilled: as basic, plus knight's jump position, 4..5; + * expert: as skilled, plus diagonal, 4..8. + * ...9... + * .85458. + * .52125. + * 9410149 + * .52125. + * .85458. + * ...9... + * (Note: no roles in NetHack can become expert or better + * for polearm skill; Yeoman in slash'em can become expert.) + */ +staticfn void +calc_pole_range(int *min_range, int *max_range) +{ + int typ = uwep_skill_type(); + + *min_range = 4; + if (typ == P_NONE || P_SKILL(typ) <= P_BASIC) + *max_range = 4; + else if (P_SKILL(typ) == P_SKILLED) + *max_range = 5; + else + *max_range = 8; /* (P_SKILL(typ) >= P_EXPERT) */ + + gp.polearm_range_min = *min_range; + gp.polearm_range_max = *max_range; + +} + +/* return TRUE if hero is wielding a polearm and there's + at least one monster they could hit with it */ +boolean +could_pole_mon(void) +{ + int min_range, max_range; + coord cc; + struct monst *hitm = svc.context.polearm.hitmon; + + if (!uwep || !is_pole(uwep)) + return FALSE; + + calc_pole_range(&min_range, &max_range); + + cc.x = u.ux; + cc.y = u.uy; + if (!find_poleable_mon(&cc)) { + if (hitm && !DEADMONSTER(hitm) && sensemon(hitm) + && mdistu(hitm) <= max_range && mdistu(hitm) >= min_range) + return TRUE; + } else { + return TRUE; + } + return FALSE; +} + +/* was Snickersnee used to attack at distance this turn already? */ +staticfn boolean +snickersnee_used_dist_attk(struct obj *obj) +{ + if (obj && obj == uwep && u_wield_art(ART_SNICKERSNEE) + && svc.context.snickersnee_turn == svm.moves) + return TRUE; + return FALSE; +} + /* Distance attacks by pole-weapons */ -STATIC_OVL int -use_pole(obj) -struct obj *obj; +int +use_pole(struct obj *obj, boolean autohit) { - int res = 0, typ, max_range, min_range, glyph; + const char thump[] = "Thump! Your blow bounces harmlessly off the %s."; + int res = ECMD_OK, max_range, min_range, glyph; coord cc; struct monst *mtmp; - struct monst *hitm = context.polearm.hitmon; + struct monst *hitm = svc.context.polearm.hitmon; + boolean freehit = FALSE; /* Are you allowed to use the pole? */ if (u.uswallow) { pline(not_enough_room); - return 0; + return ECMD_OK; } if (obj != uwep) { - if (!wield_tool(obj, "swing")) - return 0; - else - res = 1; + if (wield_tool(obj, "swing")) { + cmdq_add_ec(CQ_CANNED, doapply); + cmdq_add_key(CQ_CANNED, obj->invlet); + return ECMD_TIME; + } + return ECMD_OK; } /* assert(obj == uwep); */ - /* - * Calculate allowable range (pole's reach is always 2 steps): - * unskilled and basic: orthogonal direction, 4..4; - * skilled: as basic, plus knight's jump position, 4..5; - * expert: as skilled, plus diagonal, 4..8. - * ...9... - * .85458. - * .52125. - * 9410149 - * .52125. - * .85458. - * ...9... - * (Note: no roles in nethack can become expert or better - * for polearm skill; Yeoman in slash'em can become expert.) - */ - min_range = 4; - typ = uwep_skill_type(); - if (typ == P_NONE || P_SKILL(typ) <= P_BASIC) - max_range = 4; - else if (P_SKILL(typ) == P_SKILLED) - max_range = 5; - else - max_range = 8; /* (P_SKILL(typ) >= P_EXPERT) */ - - polearm_range_min = min_range; - polearm_range_max = max_range; + calc_pole_range(&min_range, &max_range); /* Prompt for a location */ - pline(where_to_hit); + if (!autohit) + pline(where_to_hit); cc.x = u.ux; cc.y = u.uy; - if (!find_poleable_mon(&cc, min_range, max_range) && hitm - && !DEADMONSTER(hitm) && cansee(hitm->mx, hitm->my) - && distu(hitm->mx, hitm->my) <= max_range - && distu(hitm->mx, hitm->my) >= min_range) { + if (!find_poleable_mon(&cc) && hitm + && !DEADMONSTER(hitm) && sensemon(hitm) + && mdistu(hitm) <= max_range && mdistu(hitm) >= min_range) { cc.x = hitm->mx; cc.y = hitm->my; } - getpos_sethilite(display_polearm_positions, get_valid_polearm_position); - if (getpos(&cc, TRUE, "the spot to hit") < 0) - return res; /* ESC; uses turn iff polearm became wielded */ + if (!autohit) { + getpos_sethilite(display_polearm_positions, + get_valid_polearm_position); + if (getpos(&cc, TRUE, "the spot to hit") < 0) + /* ESC; uses turn iff polearm became wielded */ + return (res | ECMD_CANCEL); + } glyph = glyph_at(cc.x, cc.y); if (distu(cc.x, cc.y) > max_range) { pline("Too far!"); - return res; + return ECMD_FAIL; } else if (distu(cc.x, cc.y) < min_range) { - pline("Too close!"); - return res; - } else if (!cansee(cc.x, cc.y) && !glyph_is_monster(glyph) - && !glyph_is_invisible(glyph) && !glyph_is_statue(glyph)) { + if (autohit && u_at(cc.x, cc.y)) + pline("Don't know what to hit."); + else + pline("Too close!"); + return ECMD_FAIL; + } else if (!cansee(cc.x, cc.y) && !glyph_is_poleable(glyph)) { You(cant_see_spot); - return res; + return ECMD_FAIL; } else if (!couldsee(cc.x, cc.y)) { /* Eyes of the Overworld */ You(cant_reach); - return res; + return ECMD_FAIL; } - context.polearm.hitmon = (struct monst *) 0; + svc.context.polearm.hitmon = (struct monst *) 0; /* Attack the monster there */ - bhitpos = cc; - if ((mtmp = m_at(bhitpos.x, bhitpos.y)) != (struct monst *) 0) { - if (attack_checks(mtmp, uwep)) - return res; + gb.bhitpos = cc; + if ((mtmp = m_at(gb.bhitpos.x, gb.bhitpos.y)) != (struct monst *) 0) { + if (attack_checks(mtmp, uwep)) /* can attack proceed? */ + /* no, abort the attack attempt; result depends on + res: 1 => polearm became wielded, 0 => already wielded; + svc.context.move: 1 => discovered hidden monster at target spot, + 0 => answered 'n' to "Really attack?" prompt */ + return res | (svc.context.move ? ECMD_TIME : ECMD_OK); if (overexertion()) - return 1; /* burn nutrition; maybe pass out */ - context.polearm.hitmon = mtmp; + return ECMD_TIME; /* burn nutrition; maybe pass out */ + svc.context.polearm.hitmon = mtmp; + + if (snickersnee_used_dist_attk(obj)) { + pline_The("blade doesn't reach there!"); + return ECMD_FAIL; + } + check_caitiff(mtmp); - notonhead = (bhitpos.x != mtmp->mx || bhitpos.y != mtmp->my); + gn.notonhead = (gb.bhitpos.x != mtmp->mx || gb.bhitpos.y != mtmp->my); + + /* Snickersnee allows one free hit from a distance per turn */ + if (obj == uwep && u_wield_art(ART_SNICKERSNEE)) { + freehit = (svm.moves != svc.context.snickersnee_turn); + svc.context.snickersnee_turn = svm.moves; + if (freehit && !Deaf) { + Soundeffect(se_sword_blade_rings, 100); + pline("Shkinng!"); /* /sha-kin!/ */ + } + } + (void) thitmonst(mtmp, uwep); } else if (glyph_is_statue(glyph) /* might be hallucinatory */ - && sobj_at(STATUE, bhitpos.x, bhitpos.y)) { - struct trap *t = t_at(bhitpos.x, bhitpos.y); + && sobj_at(STATUE, gb.bhitpos.x, gb.bhitpos.y)) { + struct trap *t = t_at(gb.bhitpos.x, gb.bhitpos.y); if (t && t->ttyp == STATUE_TRAP && activate_statue_trap(t, t->tx, t->ty, FALSE)) { @@ -3091,21 +3532,40 @@ struct obj *obj; Note: we only do this when a statue is displayed here, because the player is probably attempting to attack it; other statues obscured by anything are just ignored. */ - pline("Thump! Your blow bounces harmlessly off the statue."); - wake_nearto(bhitpos.x, bhitpos.y, 25); + pline(thump, "statue"); + wake_nearto(gb.bhitpos.x, gb.bhitpos.y, 25); } } else { /* no monster here and no statue seen or remembered here */ - (void) unmap_invisible(bhitpos.x, bhitpos.y); - You("miss; there is no one there to hit."); + (void) unmap_invisible(gb.bhitpos.x, gb.bhitpos.y); + + if (glyph_to_obj(glyph) == BOULDER + && sobj_at(BOULDER, gb.bhitpos.x, gb.bhitpos.y)) { + pline(thump, "boulder"); + wake_nearto(gb.bhitpos.x, gb.bhitpos.y, 25); + } else if (!accessible(gb.bhitpos.x, gb.bhitpos.y) + || IS_FURNITURE(levl[gb.bhitpos.x][gb.bhitpos.y].typ)) { + /* similar to 'F'orcefight with a melee weapon; we know that + the spot can be seen or we wouldn't have gotten this far */ + You("uselessly attack %s.", + (levl[gb.bhitpos.x][gb.bhitpos.y].typ == STONE + || levl[gb.bhitpos.x][gb.bhitpos.y].typ == SCORR) + ? "stone" + : glyph_is_cmap(glyph) + ? the(defsyms[glyph_to_cmap(glyph)].explanation) + : (const char *) "an unknown obstacle"); + } else { + You("miss; there is no one there to hit."); + } } u_wipe_engr(2); /* same as for melee or throwing */ - return 1; + return freehit ? ECMD_OK : ECMD_TIME; } -STATIC_OVL int -use_cream_pie(obj) -struct obj *obj; +#undef glyph_is_poleable + +staticfn int +use_cream_pie(struct obj *obj) { boolean wasblind = Blind; boolean wascreamed = u.ucreamed; @@ -3118,13 +3578,14 @@ struct obj *obj; if (Hallucination) You("give yourself a facial."); else - pline("You immerse your %s in %s%s.", body_part(FACE), + You("immerse your %s in %s%s.", body_part(FACE), several ? "one of " : "", several ? makeplural(the(xname(obj))) : the(xname(obj))); - if (can_blnd((struct monst *) 0, &youmonst, AT_WEAP, obj)) { + if (can_blnd((struct monst *) 0, &gy.youmonst, AT_WEAP, obj)) { int blindinc = rnd(25); + u.ucreamed += blindinc; - make_blinded(Blinded + (long) blindinc, FALSE); + make_blinded(BlindedTimeout + (long) blindinc, FALSE); if (!Blind || (Blind && wasblind)) pline("There's %ssticky goop all over your %s.", wascreamed ? "more " : "", body_part(FACE)); @@ -3138,14 +3599,136 @@ struct obj *obj; costly_alteration(obj, COST_SPLAT); obj_extract_self(obj); delobj(obj); - return 0; + return ECMD_OK; +} + +/* getobj callback for object to rub royal jelly on */ +staticfn int +jelly_ok(struct obj *obj) +{ + if (obj && obj->otyp == EGG) + return GETOBJ_SUGGEST; + + return GETOBJ_EXCLUDE; } -STATIC_OVL int -use_grapple(obj) -struct obj *obj; +staticfn int +use_royal_jelly(struct obj **optr) { - int res = 0, typ, max_range = 4, tohit; + int oldcorpsenm; + unsigned was_timed; + struct obj *eobj, *obj = *optr; + boolean splitit = (obj->quan > 1L); + + if (splitit) + obj = splitobj(obj, 1L); + /* remove from inventory so that it won't be offered as a choice + to rub on itself */ + freeinv(obj); + + /* right now you can rub one royal jelly on an entire stack of eggs */ + eobj = getobj("rub the royal jelly on", jelly_ok, GETOBJ_PROMPT); + if (!eobj) { + if (splitit) { + (void) unsplitobj(obj); + update_inventory(); /* freeinv() updated perminv w/ obj omitted */ + } else { + /* this lump was already separate; pervent merge */ + addinv_nomerge(obj); /* put unused lump back; updates perminv */ + } + return ECMD_CANCEL; + } + + You("smear royal jelly all over %s.", yname(eobj)); + if (eobj->otyp != EGG) { + pline1(nothing_happens); + goto useup_jelly; + } + + oldcorpsenm = eobj->corpsenm; + if (eobj->corpsenm == PM_KILLER_BEE) + eobj->corpsenm = PM_QUEEN_BEE; + + if (obj->cursed) { + if (eobj->timed || eobj->corpsenm != oldcorpsenm) + pline("The %s %s feebly.", xname(eobj), otense(eobj, "quiver")); + else + pline("%s", nothing_seems_to_happen); + kill_egg(eobj); + goto useup_jelly; + } + + was_timed = eobj->timed; + if (eobj->corpsenm != NON_PM) { + if (!eobj->timed) + attach_egg_hatch_timeout(eobj, 0L); + /* blessed royal jelly will make the hatched creature think + you're the parent - but has no effect if you laid the egg */ + if (obj->blessed && !eobj->spe) + eobj->spe = 2; + } + + if ((eobj->timed && !was_timed) || eobj->spe == 2 + || eobj->corpsenm != oldcorpsenm) + pline("The %s %s briefly.", xname(eobj), otense(eobj, "quiver")); + else + pline("%s", nothing_seems_to_happen); + + useup_jelly: + /* not useup() because we've already done freeinv() */ + setnotworn(obj); + obfree(obj, (struct obj *) 0); + *optr = 0; + return ECMD_TIME; +} + +staticfn int +grapple_range(void) +{ + int typ = uwep_skill_type(); + int max_range = 4; + + if (typ == P_NONE || P_SKILL(typ) <= P_BASIC) + max_range = 4; + else if (P_SKILL(typ) == P_SKILLED) + max_range = 5; + else + max_range = 8; + return max_range; +} + +staticfn boolean +can_grapple_location(coordxy x, coordxy y) +{ + return (isok(x, y) && cansee(x, y) && distu(x, y) <= grapple_range()); +} + +staticfn void +display_grapple_positions(boolean on_off) +{ + coordxy x, y, dx, dy; + + if (on_off) { + /* on */ + tmp_at(DISP_BEAM, cmap_to_glyph(S_goodpos)); + for (dx = -3; dx <= 3; dx++) + for (dy = -3; dy <= 3; dy++) { + x = dx + (int) u.ux; + y = dy + (int) u.uy; + if (can_grapple_location(x, y) && !u_at(x, y)) { + tmp_at(x, y); + } + } + } else { + /* off */ + tmp_at(DISP_END, 0); + } +} + +staticfn int +use_grapple(struct obj *obj) +{ + int res = ECMD_OK, typ, tohit; boolean save_confirm; coord cc; struct monst *mtmp; @@ -3154,13 +3737,16 @@ struct obj *obj; /* Are you allowed to use the hook? */ if (u.uswallow) { pline(not_enough_room); - return 0; + return ECMD_OK; } if (obj != uwep) { - if (!wield_tool(obj, "cast")) - return 0; - else - res = 1; + /* "cast": grappling hook evolved from slash'em's fishing pole */ + if (wield_tool(obj, "cast")) { + cmdq_add_ec(CQ_CANNED, doapply); + cmdq_add_key(CQ_CANNED, obj->invlet); + return ECMD_TIME; + } + return ECMD_OK; } /* assert(obj == uwep); */ @@ -3168,18 +3754,14 @@ struct obj *obj; pline(where_to_hit); cc.x = u.ux; cc.y = u.uy; + getpos_sethilite(display_grapple_positions, can_grapple_location); if (getpos(&cc, TRUE, "the spot to hit") < 0) - return res; /* ESC; uses turn iff grapnel became wielded */ + /* ESC; uses turn iff grapnel became wielded */ + return (res | ECMD_CANCEL); /* Calculate range; unlike use_pole(), there's no minimum for range */ typ = uwep_skill_type(); - if (typ == P_NONE || P_SKILL(typ) <= P_BASIC) - max_range = 4; - else if (P_SKILL(typ) == P_SKILLED) - max_range = 5; - else - max_range = 8; - if (distu(cc.x, cc.y) > max_range) { + if (distu(cc.x, cc.y) > grapple_range()) { pline("Too far!"); return res; } else if (!cansee(cc.x, cc.y)) { @@ -3197,21 +3779,22 @@ struct obj *obj; anything any; char buf[BUFSZ]; menu_item *selected; + int clr = NO_COLOR; - any = zeroany; /* set all bits to zero */ - any.a_int = 1; /* use index+1 (cant use 0) as identifier */ - start_menu(tmpwin); + any = cg.zeroany; /* set all bits to zero */ + any.a_int = 1; /* use index+1 (can't use 0) as identifier */ + start_menu(tmpwin, MENU_BEHAVE_STANDARD); any.a_int++; Sprintf(buf, "an object on the %s", surface(cc.x, cc.y)); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, - MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, + clr, buf, MENU_ITEMFLAGS_NONE); any.a_int++; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "a monster", - MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, + clr, "a monster", MENU_ITEMFLAGS_NONE); any.a_int++; Sprintf(buf, "the %s", surface(cc.x, cc.y)); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, - MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr, + buf, MENU_ITEMFLAGS_NONE); end_menu(tmpwin, "Aim for what?"); tohit = rn2(4); if (select_menu(tmpwin, PICK_ONE, &selected) > 0 @@ -3232,19 +3815,19 @@ struct obj *obj; /* FIXME -- untrap needs to deal with non-adjacent traps */ break; case 1: /* Object */ - if ((otmp = level.objects[cc.x][cc.y]) != 0) { + if ((otmp = svl.level.objects[cc.x][cc.y]) != 0) { You("snag an object from the %s!", surface(cc.x, cc.y)); (void) pickup_object(otmp, 1L, FALSE); /* If pickup fails, leave it alone */ newsym(cc.x, cc.y); - return 1; + return ECMD_TIME; } break; case 2: /* Monster */ - bhitpos = cc; + gb.bhitpos = cc; if ((mtmp = m_at(cc.x, cc.y)) == (struct monst *) 0) break; - notonhead = (bhitpos.x != mtmp->mx || bhitpos.y != mtmp->my); + gn.notonhead = (gb.bhitpos.x != mtmp->mx || gb.bhitpos.y != mtmp->my); save_confirm = flags.confirm; if (verysmall(mtmp->data) && !rn2(4) && enexto(&cc, u.ux, u.uy, (struct permonst *) 0)) { @@ -3255,7 +3838,7 @@ struct obj *obj; You("pull in %s!", mon_nam(mtmp)); mtmp->mundetected = 0; rloc_to(mtmp, cc.x, cc.y); - return 1; + return ECMD_TIME; } else if ((!bigmonst(mtmp->data) && !strongmonst(mtmp->data)) || rn2(4)) { flags.confirm = FALSE; @@ -3263,8 +3846,9 @@ struct obj *obj; flags.confirm = save_confirm; check_caitiff(mtmp); (void) thitmonst(mtmp, uwep); - return 1; + return ECMD_TIME; } + FALLTHROUGH; /*FALLTHRU*/ case 3: /* Surface */ if (IS_AIR(levl[cc.x][cc.y].typ) || is_pool(cc.x, cc.y)) @@ -3274,51 +3858,84 @@ struct obj *obj; hurtle(sgn(cc.x - u.ux), sgn(cc.y - u.uy), 1, FALSE); spoteffects(TRUE); } - return 1; + return ECMD_TIME; default: /* Yourself (oops!) */ if (P_SKILL(typ) <= P_BASIC) { You("hook yourself!"); losehp(Maybe_Half_Phys(rn1(10, 10)), "a grappling hook", KILLED_BY); - return 1; + return ECMD_TIME; } break; } pline1(nothing_happens); - return 1; + return ECMD_TIME; } -#define BY_OBJECT ((struct monst *) 0) +staticfn void +discard_broken_wand(void) +{ + struct obj *obj; + + obj = gc.current_wand; /* [see dozap() and destroy_items()] */ + gc.current_wand = 0; + if (obj) + delobj(obj); + nomul(0); +} + +staticfn void +broken_wand_explode(struct obj *obj, int dmg, int expltype) +{ + explode(u.ux, u.uy, -(obj->otyp), dmg, WAND_CLASS, expltype); + makeknown(obj->otyp); /* explode describes the effect */ + discard_broken_wand(); +} + +/* if x,y has lava or water, dunk any boulders at that location into it */ +void +maybe_dunk_boulders(coordxy x, coordxy y) +{ + struct obj *otmp; + + while (is_pool_or_lava(x, y) && (otmp = sobj_at(BOULDER, x, y)) != 0) { + obj_extract_self(otmp); + (void) boulder_hits_pool(otmp, x,y, FALSE); + } +} /* return 1 if the wand is broken, hence some time elapsed */ -STATIC_OVL int -do_break_wand(obj) -struct obj *obj; +staticfn int +do_break_wand(struct obj *obj) { +#define BY_OBJECT ((struct monst *) 0) static const char nothing_else_happens[] = "But nothing else happens..."; - register int i, x, y; - register struct monst *mon; + int i; + coordxy x, y; + struct monst *mon; int dmg, damage; boolean affects_objects; boolean shop_damage = FALSE; boolean fillmsg = FALSE; - int expltype = EXPL_MAGICAL; char confirm[QBUFSZ], buf[BUFSZ]; - boolean is_fragile = (!strcmp(OBJ_DESCR(objects[obj->otyp]), "balsa")); + boolean is_fragile = (objdescr_is(obj, "balsa") + || objdescr_is(obj, "glass")); - if (!paranoid_query(ParanoidBreakwand, - safe_qbuf(confirm, - "Are you really sure you want to break ", - "?", obj, yname, ysimple_name, "the wand"))) - return 0; - - if (nohands(youmonst.data)) { + if (nohands(gy.youmonst.data)) { You_cant("break %s without hands!", yname(obj)); - return 0; + return ECMD_OK; + } else if (!freehand()) { + Your("%s are occupied!", makeplural(body_part(HAND))); + return ECMD_OK; } else if (ACURR(A_STR) < (is_fragile ? 5 : 10)) { You("don't have the strength to break %s!", yname(obj)); - return 0; + return ECMD_OK; } + if (!paranoid_query(ParanoidBreakwand, + safe_qbuf(confirm, + "Are you really sure you want to break ", + "?", obj, yname, ysimple_name, "the wand"))) + return ECMD_OK; pline("Raising %s high above your %s, you %s it in two!", yname(obj), body_part(HEAD), is_fragile ? "snap" : "break"); @@ -3330,13 +3947,14 @@ struct obj *obj; costly_alteration(obj, COST_DSTROY); } - current_wand = obj; /* destroy_item might reset this */ - freeinv(obj); /* hide it from destroy_item instead... */ + gc.current_wand = obj; /* destroy_items might reset this */ + freeinv(obj); /* hide it from destroy_items instead... */ setnotworn(obj); /* so we need to do this ourselves */ if (!zappable(obj)) { pline(nothing_else_happens); - goto discard_broken_wand; + discard_broken_wand(); + return ECMD_TIME; } /* successful call to zappable() consumes a charge; put it back */ obj->spe++; @@ -3354,36 +3972,45 @@ struct obj *obj; affects_objects = FALSE; switch (obj->otyp) { + case WAN_OPENING: + if (u.ustuck) { + release_hold(); + if (obj->dknown) + makeknown(WAN_OPENING); + discard_broken_wand(); + return ECMD_TIME; + } + FALLTHROUGH; + /*FALLTHRU*/ case WAN_WISHING: case WAN_NOTHING: case WAN_LOCKING: case WAN_PROBING: case WAN_ENLIGHTENMENT: - case WAN_OPENING: case WAN_SECRET_DOOR_DETECTION: + case WAN_STASIS: pline(nothing_else_happens); - goto discard_broken_wand; + discard_broken_wand(); + return ECMD_TIME; case WAN_DEATH: case WAN_LIGHTNING: - dmg *= 4; - goto wanexpl; + broken_wand_explode(obj, dmg * 4, EXPL_MAGICAL); + return ECMD_TIME; case WAN_FIRE: - expltype = EXPL_FIERY; - /*FALLTHRU*/ + broken_wand_explode(obj, dmg * 2, EXPL_FIERY); + return ECMD_TIME; case WAN_COLD: - if (expltype == EXPL_MAGICAL) - expltype = EXPL_FROSTY; - dmg *= 2; - /*FALLTHRU*/ + broken_wand_explode(obj, dmg * 2, EXPL_FROSTY); + return ECMD_TIME; case WAN_MAGIC_MISSILE: - wanexpl: - explode(u.ux, u.uy, -(obj->otyp), dmg, WAND_CLASS, expltype); - makeknown(obj->otyp); /* explode describes the effect */ - goto discard_broken_wand; + broken_wand_explode(obj, dmg, EXPL_MAGICAL); + return ECMD_TIME; case WAN_STRIKING: /* we want this before the explosion instead of at the very end */ + Soundeffect(se_wall_of_force, 65); pline("A wall of force smashes down around you!"); dmg = d(1 + obj->spe, 6); /* normally 2d12 */ + FALLTHROUGH; /*FALLTHRU*/ case WAN_CANCELLATION: case WAN_POLYMORPH: @@ -3407,16 +4034,17 @@ struct obj *obj; zapsetup(); /* this makes it hit us last, so that we can see the action first */ - for (i = 0; i <= 8; i++) { - bhitpos.x = x = obj->ox + xdir[i]; - bhitpos.y = y = obj->oy + ydir[i]; + for (i = 0; i <= N_DIRS; i++) { + gb.bhitpos.x = x = obj->ox + xdir[i]; + gb.bhitpos.y = y = obj->oy + ydir[i]; if (!isok(x, y)) continue; if (obj->otyp == WAN_DIGGING) { schar typ; + enum digcheck_result dcres = dig_check(BY_OBJECT, x, y); - if (dig_check(BY_OBJECT, FALSE, x, y)) { + if (dcres < DIGCHECK_FAILED || dcres == DIGCHECK_FAIL_BOULDER) { if (IS_WALL(levl[x][y].typ) || IS_DOOR(levl[x][y].typ)) { /* normally, pits and holes don't anger guards, but they * do if it's a wall or door that's being dug */ @@ -3424,6 +4052,8 @@ struct obj *obj; if (*in_rooms(x, y, SHOPBASE)) shop_damage = TRUE; } + if (levl[x][y].typ == ICE) + spot_stop_timers(x, y, MELT_ICE_AWAY); /* * Let liquid flow into the newly created pits. * Adjust corresponding code in music.c for @@ -3437,13 +4067,16 @@ struct obj *obj; ? (char *) 0 : "Some holes are quickly filled with %s!"); fillmsg = TRUE; - } else - digactualhole(x, y, BY_OBJECT, (rn2(obj->spe) < 3 - || (!Can_dig_down(&u.uz) - && !levl[x][y].candig)) - ? PIT - : HOLE); + } else { + digactualhole(x, y, BY_OBJECT, + (rn2(obj->spe) < 3 + || (!Can_dig_down(&u.uz) + && !levl[x][y].candig)) ? PIT : HOLE); + } } + fill_pit(x, y); + maybe_dunk_boulders(x, y); + recalc_block_point(x, y); continue; } else if (obj->otyp == WAN_CREATE_MONSTER) { /* u.ux,u.uy creates it near you--x,y might create it in rock */ @@ -3451,7 +4084,7 @@ struct obj *obj; continue; } else if (x != u.ux || y != u.uy) { /* - * Wand breakage is targetting a square adjacent to the hero, + * Wand breakage is targeting a square adjacent to the hero, * which might contain a monster or a pile of objects or both. * Handle objects last; avoids having undead turning raise an * undead's corpse and then attack resulting undead monster. @@ -3461,16 +4094,16 @@ struct obj *obj; */ if ((mon = m_at(x, y)) != 0) { (void) bhitm(mon, obj); - /* if (context.botl) bot(); */ + /* if (disp.botl) bot(); */ } - if (affects_objects && level.objects[x][y]) { + if (affects_objects && svl.level.objects[x][y]) { (void) bhitpile(obj, bhito, x, y, 0); - if (context.botl) + if (disp.botl) bot(); /* potion effects */ } } else { /* - * Wand breakage is targetting the hero. Using xdir[]+ydir[] + * Wand breakage is targeting the hero. Using xdir[]+ydir[] * deltas for location selection causes this case to happen * after all the surrounding squares have been handled. * Process objects first, in case damage is fatal and leaves @@ -3481,9 +4114,9 @@ struct obj *obj; * of obj->bypass in the zap code to accomplish that last case * since it's also used by retouch_equipment() for polyself.) */ - if (affects_objects && level.objects[x][y]) { + if (affects_objects && svl.level.objects[x][y]) { (void) bhitpile(obj, bhito, x, y, 0); - if (context.botl) + if (disp.botl) bot(); /* potion effects */ } damage = zapyourself(obj, FALSE); @@ -3491,7 +4124,7 @@ struct obj *obj; Sprintf(buf, "killed %sself by breaking a wand", uhim()); losehp(Maybe_Half_Phys(damage), buf, NO_KILLER_PREFIX); } - if (context.botl) + if (disp.botl) bot(); /* blindness */ } } @@ -3507,92 +4140,106 @@ struct obj *obj; if (obj->otyp == WAN_LIGHT) litroom(TRUE, obj); /* only needs to be done once */ -discard_broken_wand: - obj = current_wand; /* [see dozap() and destroy_item()] */ - current_wand = 0; - if (obj) - delobj(obj); - nomul(0); - return 1; + discard_broken_wand(); + return ECMD_TIME; +#undef BY_OBJECT } -STATIC_OVL void -add_class(cl, class) -char *cl; -char class; -{ - char tmp[2]; - - tmp[0] = class; - tmp[1] = '\0'; - Strcat(cl, tmp); -} - -static const char tools[] = { TOOL_CLASS, WEAPON_CLASS, WAND_CLASS, 0 }; - -/* augment tools[] if various items are carried */ -STATIC_OVL void -setapplyclasses(class_list) -char class_list[]; -{ - register struct obj *otmp; - int otyp; - boolean knowoil, knowtouchstone, addpotions, addstones, addfood; - - knowoil = objects[POT_OIL].oc_name_known; - knowtouchstone = objects[TOUCHSTONE].oc_name_known; - addpotions = addstones = addfood = FALSE; - for (otmp = invent; otmp; otmp = otmp->nobj) { - otyp = otmp->otyp; - if (otyp == POT_OIL - || (otmp->oclass == POTION_CLASS - && (!otmp->dknown - || (!knowoil && !objects[otyp].oc_name_known)))) - addpotions = TRUE; - if (otyp == TOUCHSTONE - || (is_graystone(otmp) - && (!otmp->dknown - || (!knowtouchstone && !objects[otyp].oc_name_known)))) - addstones = TRUE; - if (otyp == CREAM_PIE || otyp == EUCALYPTUS_LEAF) - addfood = TRUE; - } - - class_list[0] = '\0'; - if (addpotions || addstones) - add_class(class_list, ALL_CLASSES); - Strcat(class_list, tools); - if (addpotions) - add_class(class_list, POTION_CLASS); - if (addstones) - add_class(class_list, GEM_CLASS); - if (addfood) - add_class(class_list, FOOD_CLASS); -} - -/* the 'a' command */ +/* getobj callback for object to apply - this is more complex than most other + * callbacks because there are a lot of appliables */ +staticfn int +apply_ok(struct obj *obj) +{ + if (!obj) + return GETOBJ_EXCLUDE; + + /* all tools, all wands (breaking), all spellbooks (flipping through - + including blank/novel/Book of the Dead) */ + if (obj->oclass == TOOL_CLASS || obj->oclass == WAND_CLASS + || obj->oclass == SPBOOK_CLASS) + return GETOBJ_SUGGEST; + + /* applying coins to flip them is a minor easter egg, so do not suggest + coin application to the player */ + if (obj->oclass == COIN_CLASS) + return GETOBJ_DOWNPLAY; + + /* certain weapons */ + if (obj->oclass == WEAPON_CLASS + && (is_pick(obj) || is_axe(obj) || is_pole(obj) + || obj->otyp == BULLWHIP)) + return GETOBJ_SUGGEST; + + if (obj->oclass == POTION_CLASS) { + /* permit applying unknown potions, but don't suggest them */ + if (!obj->dknown || !objects[obj->otyp].oc_name_known) + return GETOBJ_DOWNPLAY; + + /* only applicable potion is oil, and it will only be suggested as a + choice when already discovered */ + if (obj->otyp == POT_OIL) + return GETOBJ_SUGGEST; + } + + /* certain foods */ + if (obj->otyp == CREAM_PIE || obj->otyp == EUCALYPTUS_LEAF + || obj->otyp == LUMP_OF_ROYAL_JELLY) + return GETOBJ_SUGGEST; + + if (obj->otyp == BANANA && Hallucination) + return GETOBJ_DOWNPLAY; + + if (is_graystone(obj)) { + /* The only case where we don't suggest a gray stone is if we KNOW it + isn't a touchstone. */ + if (!obj->dknown) + return GETOBJ_SUGGEST; + + if (obj->otyp != TOUCHSTONE + && (objects[TOUCHSTONE].oc_name_known + || objects[obj->otyp].oc_name_known)) + return GETOBJ_EXCLUDE_SELECTABLE; + + return GETOBJ_SUGGEST; + } + + /* item can't be applied; if picked anyway, + _EXCLUDE would yield "That is a silly thing to apply.", + _EXCLUDE_SELECTABLE yields "Sorry, I don't know how to use that." */ + return GETOBJ_EXCLUDE_SELECTABLE; +} + +/* the #apply command, 'a' */ int -doapply() +doapply(void) { struct obj *obj; - register int res = 1; - char class_list[MAXOCLASSES + 2]; + int res = ECMD_TIME; + if (nohands(gy.youmonst.data)) { + You("aren't able to use or apply tools in your current form."); + return ECMD_OK; + } if (check_capacity((char *) 0)) - return 0; + return ECMD_OK; - setapplyclasses(class_list); /* tools[] */ - obj = getobj(class_list, "use or apply"); + obj = getobj("use or apply", apply_ok, GETOBJ_NOFLAGS); if (!obj) - return 0; + return ECMD_CANCEL; if (!retouch_object(&obj, FALSE)) - return 1; /* evading your grasp costs a turn; just be - grateful that you don't drop it as well */ + return ECMD_TIME; /* evading your grasp costs a turn; just be + grateful that you don't drop it as well */ if (obj->oclass == WAND_CLASS) return do_break_wand(obj); + if (obj->oclass == SPBOOK_CLASS) + return flip_through_book(obj); + + if (obj->oclass == COIN_CLASS) + return flip_coin(obj); + switch (obj->otyp) { case BLINDFOLD: case LENSES: @@ -3602,15 +4249,18 @@ doapply() } else if (!ublindf) { Blindf_on(obj); } else { - You("are already %s.", ublindf->otyp == TOWEL - ? "covered by a towel" - : ublindf->otyp == BLINDFOLD - ? "wearing a blindfold" - : "wearing lenses"); + You("are already %s.", + (ublindf->otyp == TOWEL) ? "covered by a towel" + : (ublindf->otyp == BLINDFOLD) ? "wearing a blindfold" + : "wearing lenses"); } break; case CREAM_PIE: res = use_cream_pie(obj); + obj = (struct obj *) 0; + break; + case LUMP_OF_ROYAL_JELLY: + res = use_royal_jelly(&obj); break; case BULLWHIP: res = use_whip(obj); @@ -3624,18 +4274,18 @@ doapply() case SACK: case BAG_OF_HOLDING: case OILSKIN_SACK: - res = use_container(&obj, 1, FALSE); + res = use_container(&obj, TRUE, FALSE); break; case BAG_OF_TRICKS: (void) bagotricks(obj, FALSE, (int *) 0); break; case CAN_OF_GREASE: - use_grease(obj); + res = use_grease(obj); break; case LOCK_PICK: case CREDIT_CARD: case SKELETON_KEY: - res = (pick_lock(obj) != 0); + res = (pick_lock(obj, 0, 0, NULL) != 0) ? ECMD_TIME : ECMD_OK; break; case PICK_AXE: case DWARVISH_MATTOCK: @@ -3715,10 +4365,10 @@ doapply() res = use_tin_opener(obj); break; case FIGURINE: - use_figurine(&obj); + res = use_figurine(&obj); break; case UNICORN_HORN: - use_unicorn_horn(obj); + use_unicorn_horn(&obj); break; case WOODEN_FLUTE: case MAGIC_FLUTE: @@ -3733,34 +4383,44 @@ doapply() res = do_play_instrument(obj); break; case HORN_OF_PLENTY: /* not a musical instrument */ - (void) hornoplenty(obj, FALSE); + (void) hornoplenty(obj, FALSE, (struct obj *) 0); break; case LAND_MINE: case BEARTRAP: use_trap(obj); + if (go.occupation == set_trap) + obj = (struct obj *) 0; /* not gone yet but behave as if it was */ break; case FLINT: case LUCKSTONE: case LOADSTONE: case TOUCHSTONE: - use_stone(obj); + res = use_stone(obj); break; + case BANANA: + if (Hallucination) { + pline("It rings! ... But no-one answers."); + break; + } + FALLTHROUGH; + /*FALLTHRU*/ default: /* Pole-weapons can strike at a distance */ if (is_pole(obj)) { - res = use_pole(obj); + res = use_pole(obj, FALSE); break; } else if (is_pick(obj) || is_axe(obj)) { res = use_pick_axe(obj); break; } pline("Sorry, I don't know how to use that."); - nomul(0); - return 0; + return ECMD_FAIL; + } + /* This assumes that anything that potentially destroyed obj has kept + * track of it and set obj to null before this point. */ + if (obj && obj->oartifact) { + res |= arti_speak(obj); /* sets ECMD_TIME bit if artifact speaks */ } - if (res && obj && obj->oartifact) - arti_speak(obj); - nomul(0); return res; } @@ -3768,8 +4428,7 @@ doapply() * great. */ int -unfixable_trouble_count(is_horn) -boolean is_horn; +unfixable_trouble_count(boolean is_horn) { int unfixable_trbl = 0; @@ -3779,7 +4438,9 @@ boolean is_horn; unfixable_trbl++; if (Strangled) unfixable_trbl++; - if (Wounded_legs && !u.usteed) + if (ATEMP(A_DEX) < 0 && Wounded_legs) + unfixable_trbl++; + if (ATEMP(A_STR) < 0 && u.uhs >= WEAK) unfixable_trbl++; /* lycanthropy is undesirable, but it doesn't actually make you feel bad so don't count it as a trouble which can't be fixed */ @@ -3808,4 +4469,90 @@ boolean is_horn; return unfixable_trbl; } +staticfn int +flip_through_book(struct obj *obj) +{ + if (Underwater) { + You("don't want to get the pages even more soggy, do you?"); + return ECMD_OK; + } + + You("flip through the pages of %s.", thesimpleoname(obj)); + + if (obj->otyp == SPE_BOOK_OF_THE_DEAD) { + if (!Deaf) { + if (!Hallucination) { + Soundeffect(se_rustling_paper, 50); + } + You_hear("the pages make an unpleasant %s sound.", + Hallucination ? "chuckling" + : "rustling"); + } else if (!Blind) { + You_see("the pages glow faintly %s.", hcolor(NH_RED)); + } else { + You_feel("the pages tremble."); + } + } else if (Blind) { + pline("The pages feel %s.", + Hallucination ? "freshly picked" + : "rough and dry"); + } else if (obj->otyp == SPE_BLANK_PAPER) { + pline("This spellbook %s.", + Hallucination ? "doesn't have much of a plot" + : "has nothing written in it"); + makeknown(obj->otyp); + } else if (Hallucination) { + You("enjoy the animated initials."); + } else if (obj->otyp == SPE_NOVEL) { + pline("This looks like it might be interesting to read."); + } else { + static const char *const fadeness[] = { + "fresh", + "slightly faded", + "very faded", + "extremely faded", + "barely visible" + }; + int findx = min(obj->spestudied, MAX_SPELL_STUDY); + + pline("The%s ink in this spellbook is %s.", + objects[obj->otyp].oc_magic ? " magical" : "", + fadeness[findx]); + } + + return ECMD_TIME; +} + +staticfn int +flip_coin(struct obj *obj) +{ + struct obj *otmp = obj; + boolean lose_coin = FALSE; + + You("flip %s.", an(singular(obj, xname))); + if (Underwater) { + pline("It tumbles away."); + lose_coin = TRUE; + } else if (Glib || Fumbling + || (ACURR(A_DEX) < 10 && !rn2(ACURR(A_DEX)))) { + pline("It slips between your %s.", fingers_or_gloves(FALSE)); + lose_coin = TRUE; + } + + if (lose_coin) { + if (otmp->quan > 1L) + otmp = splitobj(otmp, 1L); + dropx(otmp); + return ECMD_TIME; + } + if (Hallucination) { + pline(rn2(100) ? "Wow, a double header!" + /* edge case */ + : "The coin miraculously lands on its edge!"); + } else { + pline("It comes up %s.", rn2(2) ? "heads" : "tails"); + } + return ECMD_TIME; +} + /*apply.c*/ diff --git a/src/artifact.c b/src/artifact.c index 03573fe1d..8abe196d7 100644 --- a/src/artifact.c +++ b/src/artifact.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 artifact.c $NHDT-Date: 1553363416 2019/03/23 17:50:16 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.129 $ */ +/* NetHack 5.0 artifact.c $NHDT-Date: 1715889721 2024/05/16 20:02:01 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.236 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2013. */ /* NetHack may be freely redistributed. See license for details. */ @@ -7,6 +7,8 @@ #include "artifact.h" #include "artilist.h" +#ifndef SFCTOOL + /* * Note: both artilist[] and artiexist[] have a dummy element #0, * so loops over them should normally start at #1. The primary @@ -14,22 +16,41 @@ * the contents, just the total size. */ -extern boolean notonhead; /* for long worms */ - -#define get_artifact(o) \ - (((o) && (o)->oartifact) ? &artilist[(int) (o)->oartifact] : 0) - -STATIC_DCL boolean FDECL(bane_applies, (const struct artifact *, - struct monst *)); -STATIC_DCL int FDECL(spec_applies, (const struct artifact *, struct monst *)); -STATIC_DCL int FDECL(arti_invoke, (struct obj *)); -STATIC_DCL boolean FDECL(Mb_hit, (struct monst * magr, struct monst *mdef, - struct obj *, int *, int, BOOLEAN_P, char *)); -STATIC_DCL unsigned long FDECL(abil_to_spfx, (long *)); -STATIC_DCL uchar FDECL(abil_to_adtyp, (long *)); -STATIC_DCL int FDECL(glow_strength, (int)); -STATIC_DCL boolean FDECL(untouchable, (struct obj *, BOOLEAN_P)); -STATIC_DCL int FDECL(count_surround_traps, (int, int)); +staticfn struct artifact *get_artifact(struct obj *) NONNULL; + +/* #define get_artifact(o) \ + (((o) && ((o)->artifact > 0 && (o)->artifact < AFTER_LAST_ARTIFACT)) \ + ? &artilist[(int) (o)->oartifact] \ + : &artilist[ART_NONARTIFACT]) */ + +staticfn boolean bane_applies(const struct artifact *, struct monst *) + NONNULLARG12; +staticfn int spec_applies(const struct artifact *, struct monst *) + NONNULLARG12; +staticfn int invoke_ok(struct obj *); +staticfn void nothing_special(struct obj *) NONNULLARG1; +staticfn int invoke_taming(struct obj *) NONNULLARG1; +staticfn int invoke_healing(struct obj *) NONNULLARG1; +staticfn int invoke_energy_boost(struct obj *) NONNULLARG1; +staticfn int invoke_untrap(struct obj *) NONNULLARG1; +staticfn int invoke_charge_obj(struct obj *) NONNULLARG1; +staticfn int invoke_create_portal(struct obj *) NONNULLARG1; +staticfn int invoke_create_ammo(struct obj *) NONNULLARG1; +staticfn int invoke_banish(struct obj *) NONNULLARG1; +staticfn int invoke_fling_poison(struct obj *) NONNULLARG1; +staticfn int invoke_storm_spell(struct obj *) NONNULLARG1; +staticfn int invoke_blinding_ray(struct obj *) NONNULLARG1; +staticfn int arti_invoke_cost_pw(struct obj *) NONNULLARG1; +staticfn boolean arti_invoke_cost(struct obj *) NONNULLARG1; +staticfn int arti_invoke(struct obj *); +staticfn boolean Mb_hit(struct monst * magr, struct monst *mdef, + struct obj *, int *, int, boolean, char *); +staticfn unsigned long abil_to_spfx(long *) NONNULLARG1; +staticfn uchar abil_to_adtyp(long *) NONNULLARG1; +staticfn int glow_strength(int); +staticfn boolean untouchable(struct obj *, boolean); +staticfn int count_surround_traps(coordxy, coordxy); +staticfn void dispose_of_orig_obj(struct obj *); /* The amount added to the victim's total hit points to insure that the victim will be killed even after damage bonus/penalty adjustments. @@ -40,21 +61,30 @@ STATIC_DCL int FDECL(count_surround_traps, (int, int)); Note: this will still break if they have more than about half the number of hit points that will fit in a 15 bit integer. */ #define FATAL_DAMAGE_MODIFIER 200 +#endif /* SFCTOOL */ + +/* arti_info struct definition moved to artifact.h */ + +/* array of flags tracking which artifacts exist, indexed by ART_xx; + ART_xx values are 1..N, element [0] isn't used; no terminator needed */ +static struct arti_info artiexist[1 + NROFARTIFACTS]; +/* discovery list; for N discovered artifacts, the first N entries are ART_xx + values in discovery order, the remaining (NROFARTIFACTS-N) slots are 0 */ +static xint16 artidisco[NROFARTIFACTS]; +/* note: artiexist[] and artidisco[] don't need to be in struct ga; they + * get explicitly initialized at game start so don't need to be part of + * bulk re-init if game restart ever gets implemented. They are saved + * and restored but that is done through this file so they can be local. + */ -/* coordinate effects from spec_dbon() with messages in artifact_hit() */ -STATIC_OVL int spec_dbon_applies = 0; - -/* flags including which artifacts have already been created */ -static boolean artiexist[1 + NROFARTIFACTS + 1]; -/* and a discovery list for them (no dummy first entry here) */ -STATIC_OVL xchar artidisco[NROFARTIFACTS]; +#ifndef SFCTOOL +static const struct arti_info zero_artiexist = {0}; /* all bits zero */ -STATIC_DCL void NDECL(hack_artifacts); -STATIC_DCL boolean FDECL(attacks, (int, struct obj *)); +staticfn void hack_artifacts(void); /* handle some special cases; must be called after u_init() */ -STATIC_OVL void -hack_artifacts() +staticfn void +hack_artifacts(void) { struct artifact *art; int alignmnt = aligns[flags.initalign].value; @@ -69,16 +99,16 @@ hack_artifacts() artilist[ART_EXCALIBUR].role = NON_PM; /* Fix up the quest artifact */ - if (urole.questarti) { - artilist[urole.questarti].alignment = alignmnt; - artilist[urole.questarti].role = Role_switch; + if (gu.urole.questarti) { + artilist[gu.urole.questarti].alignment = alignmnt; + artilist[gu.urole.questarti].role = Role_switch; } return; } /* zero out the artifact existence list */ void -init_artifacts() +init_artifacts(void) { (void) memset((genericptr_t) artiexist, 0, sizeof artiexist); (void) memset((genericptr_t) artidisco, 0, sizeof artidisco); @@ -86,25 +116,39 @@ init_artifacts() } void -save_artifacts(fd) -int fd; +save_artifacts(NHFILE *nhfp) { - bwrite(fd, (genericptr_t) artiexist, sizeof artiexist); - bwrite(fd, (genericptr_t) artidisco, sizeof artidisco); + int i; + + for (i = 0; i < (NROFARTIFACTS + 1); ++i) + Sfo_arti_info(nhfp, &artiexist[i], "artiexist"); + + for (i = 0; i < NROFARTIFACTS; ++i) + Sfo_xint16(nhfp, &artidisco[i], "artidisco"); } +#endif /* SFCTOOL */ + void -restore_artifacts(fd) -int fd; +restore_artifacts(NHFILE *nhfp) { - mread(fd, (genericptr_t) artiexist, sizeof artiexist); - mread(fd, (genericptr_t) artidisco, sizeof artidisco); - hack_artifacts(); /* redo non-saved special cases */ + int i; + + for (i = 0; i < (NROFARTIFACTS + 1); ++i) + Sfi_arti_info(nhfp, &artiexist[i], "artiexist"); + for (i = 0; i < NROFARTIFACTS; ++i) + Sfi_short(nhfp, &artidisco[i], "artidisco"); +#ifndef SFCTOOL + hack_artifacts(); /* redo non-saved special cases */ +#else + nhUse(artilist); +#endif } +#ifndef SFCTOOL + const char * -artiname(artinum) -int artinum; +artiname(int artinum) { if (artinum <= 0 || artinum > NROFARTIFACTS) return ""; @@ -119,13 +163,18 @@ int artinum; If no alignment is given, then 'otmp' is converted into an artifact of matching type, or returned as-is if that's not possible. - For the 2nd case, caller should use ``obj = mk_artifact(obj, A_NONE);'' - for the 1st, ``obj = mk_artifact((struct obj *)0, some_alignment);''. + For the 2nd case, caller should use ``obj = mk_artifact(obj, A_NONE, 99);'' + For the 1st, ``obj = mk_artifact((struct obj *) 0, some_alignment, ...);''. + The max_giftvalue is the value of the sacrifice, for an artifact obtained + by sacrificing, or 99 otherwise. */ struct obj * -mk_artifact(otmp, alignment) -struct obj *otmp; /* existing object; ignored if alignment specified */ -aligntyp alignment; /* target alignment, or A_NONE */ +mk_artifact( + struct obj *otmp, /* existing object; ignored and disposed of + * if alignment specified */ + aligntyp alignment, /* target alignment, or A_NONE */ + uchar max_giftvalue, /* cap on generated giftvalue */ + boolean adjust_spe) /* whether to add spe to situational artifacts */ { const struct artifact *a; int m, n, altn; @@ -133,15 +182,18 @@ aligntyp alignment; /* target alignment, or A_NONE */ short o_typ = (by_align || !otmp) ? 0 : otmp->otyp; boolean unique = !by_align && otmp && objects[o_typ].oc_unique; short eligible[NROFARTIFACTS]; + xint16 skill_compatibility; n = altn = 0; /* no candidates found yet */ eligible[0] = 0; /* lint suppression */ /* gather eligible artifacts */ for (m = 1, a = &artilist[m]; a->otyp; a++, m++) { - if (artiexist[m]) + if (artiexist[m].exists) continue; if ((a->spfx & SPFX_NOGEN) || unique) continue; + if (a->gift_value > max_giftvalue && !Role_if(a->role)) + continue; if (!by_align) { /* looking for a particular type of item; not producing a @@ -163,17 +215,33 @@ aligntyp alignment; /* target alignment, or A_NONE */ n = 1; break; /* skip all other candidates */ } + + /* check if this is skill-compatible */ + skill_compatibility = P_SKILLED; + if (objects[a->otyp].oc_class == WEAPON_CLASS) { + schar skill = objects[a->otyp].oc_skill; + if (skill < 0) + skill_compatibility = P_MAX_SKILL(-skill); + else + skill_compatibility = P_MAX_SKILL(skill); + } + /* found something to consider for random selection */ - if (a->alignment != A_NONE || u.ugifts > 0) { + if ((a->alignment != A_NONE || u.ugifts > 0 || !rn2(3)) && + (!rn2(4) || skill_compatibility >= P_SKILLED || + (skill_compatibility >= P_BASIC && rn2(2)))) { /* right alignment, or non-aligned with at least 1 - previous gift bestowed, makes this one viable */ + previous gift bestowed, makes this one viable; + unaligned artifacts are possible even as the first + gift, but less likely; if it's a bad weapon type + for the role that also makes it less likely */ eligible[n++] = m; } else { - /* non-aligned with no previous gifts; - if no candidates have been found yet, record + /* if no candidates have been found yet, record this one as a[nother] fallback possibility in case all aligned candidates have been used up - (via wishing, naming, bones, random generation) */ + (via wishing, naming, bones, random generation) + or failed the randomized compatibility checks */ if (!n) eligible[altn++] = m; /* [once a regular candidate is found, the list @@ -192,22 +260,64 @@ aligntyp alignment; /* target alignment, or A_NONE */ a = &artilist[m]; /* make an appropriate object if necessary, then christen it */ - if (by_align) - otmp = mksobj((int) a->otyp, TRUE, FALSE); - - if (otmp) { - otmp = oname(otmp, a->name); - otmp->oartifact = m; - artiexist[m] = TRUE; + if (by_align) { + /* 'by_align' indicates that an alignment was passed as + * an argument, but also that the 'otmp' argument is not + * relevant */ + struct obj *artiobj = mksobj((int) a->otyp, TRUE, FALSE); + + /* nonnull value of 'otmp' is unexpected. Cope. */ + if (otmp) /* just in case; avoid orphaning */ + dispose_of_orig_obj(otmp); + otmp = artiobj; + } + /* + * otmp should be nonnull at this point: + * either the passed argument (if !by_align == A_NONE), or + * the result of mksobj() just above if by_align is an alignment. */ + assert(otmp != 0); + /* prevent erosion from generating */ + otmp->oeroded = otmp->oeroded2 = 0; + otmp = oname(otmp, a->name, ONAME_NO_FLAGS); + otmp->oartifact = m; /* probably already set by this point, but */ + /* set existence and reason for creation bits */ + artifact_origin(otmp, ONAME_RANDOM); /* 'random' is default */ + if (adjust_spe) { + int new_spe; + + /* Adjust artiobj->spe by a->gen_spe. (This is a no-op for + non-weapons, which always have a gen_spe of 0, and for many + weapons, too.) The result is clamped into the "normal" range to + prevent an outside chance of +12 artifacts generating. */ + new_spe = (int) otmp->spe + a->gen_spe; + if (new_spe >= -10 && new_spe < 10) + otmp->spe = new_spe; } } else { /* nothing appropriate could be found; return original object */ - if (by_align) - otmp = 0; /* (there was no original object) */ + if (by_align && otmp) { + /* (there shouldn't have been an original object). Deal with it. + * The callers that passed an alignment and a NULL otmp are + * prepared to get a potential NULL return value, so this is okay */ + dispose_of_orig_obj(otmp); + otmp = 0; + } /* otherwise, otmp has not changed; just fallthrough to return it */ } + if (otmp && permapoisoned(otmp)) + otmp->opoisoned = 1; return otmp; } +staticfn void +dispose_of_orig_obj(struct obj *obj) +{ + if (!obj) + return; + + obj_extract_self(obj); + obfree(obj, (struct obj *) 0); +} + /* * Returns the full name (with articles and correct capitalization) of an * artifact named "name" if one exists, or NULL, it not. @@ -216,12 +326,13 @@ aligntyp alignment; /* target alignment, or A_NONE */ * is non-NULL. */ const char * -artifact_name(name, otyp) -const char *name; -short *otyp; +artifact_name( + const char *name, /* string from player that might be an artifact name */ + short *otyp_p, /* secondary output */ + boolean fuzzy) /* whether to allow extra or omitted spaces or dashes */ { - register const struct artifact *a; - register const char *aname; + const struct artifact *a; + const char *aname; if (!strncmpi(name, "the ", 4)) name += 4; @@ -230,8 +341,10 @@ short *otyp; aname = a->name; if (!strncmpi(aname, "the ", 4)) aname += 4; - if (!strcmpi(name, aname)) { - *otyp = a->otyp; + if (!fuzzy ? !strcmpi(name, aname) + : fuzzymatch(name, aname, " -", TRUE)) { + if (otyp_p) + *otyp_p = a->otyp; return a->name; } } @@ -240,69 +353,177 @@ short *otyp; } boolean -exist_artifact(otyp, name) -int otyp; -const char *name; +exist_artifact(int otyp, const char *name) { - register const struct artifact *a; - boolean *arex; + const struct artifact *a; + struct arti_info *arex; if (otyp && *name) for (a = artilist + 1, arex = artiexist + 1; a->otyp; a++, arex++) if ((int) a->otyp == otyp && !strcmp(a->name, name)) - return *arex; + return arex->exists ? TRUE : FALSE; return FALSE; } +/* an artifact has just been created or is being "un-created" for a chance + to be created again later */ void -artifact_exists(otmp, name, mod) -struct obj *otmp; -const char *name; -boolean mod; +artifact_exists( + struct obj *otmp, + const char *name, + boolean mod, /* True: exists, False: being un-created */ + unsigned flgs) /* ONAME_xyz flags; not relevant if !mod */ { - register const struct artifact *a; + const struct artifact *a; if (otmp && *name) for (a = artilist + 1; a->otyp; a++) if (a->otyp == otmp->otyp && !strcmp(a->name, name)) { - register int m = (int) (a - artilist); + int m = (int) (a - artilist); + otmp->oartifact = (char) (mod ? m : 0); otmp->age = 0; if (otmp->otyp == RIN_INCREASE_DAMAGE) otmp->spe = 0; - artiexist[m] = mod; + if (mod) { /* means being created rather than un-created */ + /* one--and only one--of these should always be set */ + if ((flgs & (ONAME_VIA_NAMING | ONAME_WISH | ONAME_GIFT + | ONAME_VIA_DIP | ONAME_LEVEL_DEF + | ONAME_BONES | ONAME_RANDOM)) == 0) + flgs |= ONAME_RANDOM; /* the default origin */ + /* 'exists' bit will become set (in artifact_origin(); + there's no ONAME_ flag) and flgs might also contain + the know_arti bit (hero knows that artifact exists) */ + artifact_origin(otmp, flgs); + } else { /* uncreate */ + /* clear all the flag bits */ + artiexist[m] = zero_artiexist; + } break; } return; } +/* mark an artifact as 'found' */ +void +found_artifact(int a) +{ + if (a < 1 || a > NROFARTIFACTS) + impossible("found_artifact: invalid artifact index! (%d)", a); + else if (!artiexist[a].exists) + impossible("found_artifact: artifact doesn't exist yet? (%d)", a); + else + artiexist[a].found = 1; +} + +/* if an artifact hasn't already been designated 'found', do that now + and generate a livelog event about finding it */ +void +find_artifact(struct obj *otmp) +{ + int a = otmp->oartifact; + + if (a && !artiexist[a].found) { + const char *where; + + found_artifact(a); /* artiexist[a].found = 1 */ + /* + * Unlike costly_spot(), inside_shop() includes the "free spot" + * in front of the door. And it doesn't care whether or not + * there is a shopkeeper present. + * + * If hero sees a monster pick up a not-yet-found artifact, it + * will have its dknown flag set even if far away and will be + * described as 'found on the floor'. Similarly for dropping + * (possibly upon monster's death), dknown will be set and the + * artifact will be described as 'carried by a monster'. + * That's handled by caller: dog_invent(), mpickstuff(), or + * mdrop_obj() so that we get called before obj->where changes. + */ + where = ((otmp->where == OBJ_FLOOR) + ? ((inside_shop(otmp->ox, otmp->oy) != NO_ROOM) + ? " in a shop" + : " on the floor") + /* artifacts aren't created in containers but could be + inside one if it comes from a bones level */ + : (otmp->where == OBJ_CONTAINED) ? " in a container" + /* perhaps probing, or seeing monster wield artifact */ + : (otmp->where == OBJ_MINVENT) ? " carried by a monster" + /* catchall: probably in inventory, picked up while + blind but now seen; there's no previous_where to + figure out how it got here */ + : ""); + livelog_printf(LL_ARTIFACT, "found %s%s", + bare_artifactname(otmp), where); + } +} + int -nartifact_exist() +nartifact_exist(void) { - int a = 0; - int n = SIZE(artiexist); + int i, a = 0; - while (n > 1) - if (artiexist[--n]) - a++; + for (i = 1; i <= NROFARTIFACTS; ++i) + if (artiexist[i].exists) + ++a; return a; } +/* set artifact tracking flags; + calling sequence: oname() -> artifact_exists() -> artifact_origin() or + mksobj(),others -> mk_artifact() -> artifact_origin(random) possibly + followed by mksobj(),others -> artifact_origin(non-random origin) */ +void +artifact_origin( + struct obj *arti, /* new artifact */ + unsigned aflags) /* ONAME_xxx flags, shared by artifact_exists() */ +{ + int ct, a = arti->oartifact; + + if (a) { + /* start by clearing all bits; most are mutually exclusive */ + artiexist[a] = zero_artiexist; + /* set 'exists' bit back on; not specified via flag bit in aflags */ + artiexist[a].exists = 1; + /* 'hero knows it exists' is expected for wish, gift, viadip, or + named and could eventually become set for any of the others */ + if ((aflags & ONAME_KNOW_ARTI) != 0) + artiexist[a].found = 1; + /* should be exactly one of wish, gift, via_dip, via_naming, + level_def (quest), bones, and random (floor or monst's minvent) */ + ct = 0; + if ((aflags & ONAME_WISH) != 0) + artiexist[a].wish = 1, ++ct; + if ((aflags & ONAME_GIFT) != 0) + artiexist[a].gift = 1, ++ct; + if ((aflags & ONAME_VIA_DIP) != 0) + artiexist[a].viadip = 1, ++ct; + if ((aflags & ONAME_VIA_NAMING) != 0) + artiexist[a].named = 1, ++ct; + if ((aflags & ONAME_LEVEL_DEF) != 0) + artiexist[a].lvldef = 1, ++ct; + if ((aflags & ONAME_BONES) != 0) + artiexist[a].bones = 1, ++ct; + if ((aflags & ONAME_RANDOM) != 0) + artiexist[a].rndm = 1, ++ct; + if (ct != 1) + impossible("invalid artifact origin: %4o", aflags); + } +} + boolean -spec_ability(otmp, abil) -struct obj *otmp; -unsigned long abil; +spec_ability(struct obj *otmp, unsigned long abil) { const struct artifact *arti = get_artifact(otmp); - return (boolean) (arti && (arti->spfx & abil) != 0L); + return (boolean) (arti != &artilist[ART_NONARTIFACT] + && (arti->spfx & abil) != 0L); } /* used so that callers don't need to known about SPFX_ codes */ boolean -confers_luck(obj) -struct obj *obj; +confers_luck(struct obj *obj) { /* might as well check for this too */ if (obj->otyp == LUCKSTONE) @@ -313,12 +534,11 @@ struct obj *obj; /* used to check whether a monster is getting reflection from an artifact */ boolean -arti_reflects(obj) -struct obj *obj; +arti_reflects(struct obj *obj) { const struct artifact *arti = get_artifact(obj); - if (arti) { + if (arti != &artilist[ART_NONARTIFACT]) { /* while being worn */ if ((obj->owornmask & ~W_ART) && (arti->spfx & SPFX_REFLECT)) return TRUE; @@ -332,8 +552,7 @@ struct obj *obj; /* decide whether this obj is effective when attacking against shades; does not consider the bonus for blessed objects versus undead */ boolean -shade_glare(obj) -struct obj *obj; +shade_glare(struct obj *obj) { const struct artifact *arti; @@ -342,7 +561,8 @@ struct obj *obj; return TRUE; /* non-silver artifacts with bonus against undead also are effective */ arti = get_artifact(obj); - if (arti && (arti->spfx & SPFX_DFLAG2) && arti->mtype == M2_UNDEAD) + if (arti != &artilist[ART_NONARTIFACT] && (arti->spfx & SPFX_DFLAG2) + && arti->mtype == M2_UNDEAD) return TRUE; /* [if there was anything with special bonus against noncorporeals, it would be effective too] */ @@ -352,11 +572,9 @@ struct obj *obj; /* returns 1 if name is restricted for otmp->otyp */ boolean -restrict_name(otmp, name) -struct obj *otmp; -const char *name; +restrict_name(struct obj *otmp, const char *name) { - register const struct artifact *a; + const struct artifact *a; const char *aname, *odesc, *other; boolean sametype[NUM_OBJECTS]; int i, lo, hi, otyp = otmp->otyp, ocls = objects[otyp].oc_class; @@ -376,7 +594,7 @@ const char *name; if (!objects[otyp].oc_name_known && (odesc = OBJ_DESCR(objects[otyp])) != 0) { obj_shuffle_range(otyp, &lo, &hi); - for (i = bases[ocls]; i < NUM_OBJECTS; i++) { + for (i = svb.bases[ocls]; i < NUM_OBJECTS; i++) { if (objects[i].oc_class != ocls) break; if (!objects[i].oc_name_known @@ -404,55 +622,87 @@ const char *name; return FALSE; } -STATIC_OVL boolean -attacks(adtyp, otmp) -int adtyp; -struct obj *otmp; +boolean +attacks(int adtyp, struct obj *otmp) { - register const struct artifact *weap; + const struct artifact *weap; - if ((weap = get_artifact(otmp)) != 0) + if ((weap = get_artifact(otmp)) != &artilist[ART_NONARTIFACT]) return (boolean) (weap->attk.adtyp == adtyp); return FALSE; } boolean -defends(adtyp, otmp) -int adtyp; -struct obj *otmp; +defends(int adtyp, struct obj *otmp) { - register const struct artifact *weap; + const struct artifact *weap; - if ((weap = get_artifact(otmp)) != 0) + if (!otmp) + return FALSE; + if ((weap = get_artifact(otmp)) != &artilist[ART_NONARTIFACT]) return (boolean) (weap->defn.adtyp == adtyp); + if (Is_dragon_armor(otmp)) { + int otyp = otmp->otyp; + + /* convert mail to scales to simplify testing */ + if (Is_dragon_mail(otmp)) + otyp += GRAY_DRAGON_SCALES - GRAY_DRAGON_SCALE_MAIL; + + switch (adtyp) { + case AD_MAGM: /* magic missiles => general magic resistance */ + return (otyp == GRAY_DRAGON_SCALES); + case AD_HALU: /* confers hallucination resistance */ + return (otyp == GOLD_DRAGON_SCALES); + case AD_FIRE: + /*case AD_BLND: -- gives infravision but does not prevent blindness */ + return (otyp == RED_DRAGON_SCALES); /* red but not gold */ + case AD_COLD: + /*case AD_FAMN: -- slows digestion but does not override Famine */ + return (otyp == WHITE_DRAGON_SCALES); /* white but not silver */ + case AD_DRST: /* drain strength => poison */ + case AD_DISE: /* blocks disease but not slime */ + return (otyp == GREEN_DRAGON_SCALES); + case AD_SLEE: /* sleep */ + case AD_PLYS: /* paralysis => free action */ + return (otyp == ORANGE_DRAGON_SCALES); + case AD_DISN: /* disintegration */ + case AD_DRLI: /* level drain resistance */ + return (otyp == BLACK_DRAGON_SCALES); + case AD_ELEC: /* electricity == lightning */ + case AD_SLOW: /* confers speed so blocks speed removal */ + return (otyp == BLUE_DRAGON_SCALES); + case AD_ACID: + case AD_STON: /* petrification resistance */ + return (otyp == YELLOW_DRAGON_SCALES); + default: + /* SILVER_DRAGON_SCALES don't resist any particular attack type */ + break; + } + } return FALSE; } /* used for monsters */ boolean -defends_when_carried(adtyp, otmp) -int adtyp; -struct obj *otmp; +defends_when_carried(int adtyp, struct obj *otmp) { - register const struct artifact *weap; + const struct artifact *weap; - if ((weap = get_artifact(otmp)) != 0) + if ((weap = get_artifact(otmp)) != &artilist[ART_NONARTIFACT]) return (boolean) (weap->cary.adtyp == adtyp); return FALSE; } /* determine whether an item confers Protection */ boolean -protects(otmp, being_worn) -struct obj *otmp; -boolean being_worn; +protects(struct obj *otmp, boolean being_worn) { const struct artifact *arti; if (being_worn && objects[otmp->otyp].oc_oprop == PROTECTION) return TRUE; arti = get_artifact(otmp); - if (!arti) + if (arti == &artilist[ART_NONARTIFACT]) return FALSE; return (boolean) ((arti->cspfx & SPFX_PROTECT) != 0 || (being_worn && (arti->spfx & SPFX_PROTECT) != 0)); @@ -463,18 +713,18 @@ boolean being_worn; * unworn/unwielded/dropped. Pickup/drop only set/reset the W_ART mask. */ void -set_artifact_intrinsic(otmp, on, wp_mask) -struct obj *otmp; -boolean on; -long wp_mask; +set_artifact_intrinsic( + struct obj *otmp, + boolean on, + long wp_mask) { long *mask = 0; - register const struct artifact *art, *oart = get_artifact(otmp); - register struct obj *obj; - register uchar dtyp; - register long spfx; + const struct artifact *art, *oart = get_artifact(otmp); + struct obj *obj; + uchar dtyp; + long spfx; - if (!oart) + if (oart == &artilist[ART_NONARTIFACT]) return; /* effects from the defn field */ @@ -498,10 +748,11 @@ long wp_mask; if (mask && wp_mask == W_ART && !on) { /* find out if some other artifact also confers this intrinsic; if so, leave the mask alone */ - for (obj = invent; obj; obj = obj->nobj) { + for (obj = gi.invent; obj; obj = obj->nobj) { if (obj != otmp && obj->oartifact) { art = get_artifact(obj); - if (art && art->cary.adtyp == dtyp) { + if (art != &artilist[ART_NONARTIFACT] + && art->cary.adtyp == dtyp) { mask = (long *) 0; break; } @@ -519,10 +770,10 @@ long wp_mask; spfx = (wp_mask != W_ART) ? oart->spfx : oart->cspfx; if (spfx && wp_mask == W_ART && !on) { /* don't change any spfx also conferred by other artifacts */ - for (obj = invent; obj; obj = obj->nobj) + for (obj = gi.invent; obj; obj = obj->nobj) if (obj != otmp && obj->oartifact) { art = get_artifact(obj); - if (art) + if (art != &artilist[ART_NONARTIFACT]) spfx &= ~art->cspfx; } } @@ -540,7 +791,8 @@ long wp_mask; * that can print a message--need to guard against being printed * when restoring a game */ - (void) make_hallucinated((long) !on, restoring ? FALSE : TRUE, + (void) make_hallucinated((long) !on, + program_state.restoring ? FALSE : TRUE, wp_mask); } if (spfx & SPFX_ESP) { @@ -548,6 +800,7 @@ long wp_mask; ETelepat |= wp_mask; else ETelepat &= ~wp_mask; + recalc_telepat_range(); see_monsters(); } if (spfx & SPFX_STLTH) { @@ -572,10 +825,10 @@ long wp_mask; if (spec_m2(otmp)) { if (on) { EWarn_of_mon |= wp_mask; - context.warntype.obj |= spec_m2(otmp); + svc.context.warntype.obj |= spec_m2(otmp); } else { EWarn_of_mon &= ~wp_mask; - context.warntype.obj &= ~spec_m2(otmp); + svc.context.warntype.obj &= ~spec_m2(otmp); } see_monsters(); } else { @@ -609,7 +862,7 @@ long wp_mask; u.xray_range = 3; else u.xray_range = -1; - vision_full_recalc = 1; + gv.vision_full_recalc = 1; } if ((spfx & SPFX_REFLECT) && (wp_mask & W_WEP)) { if (on) @@ -630,12 +883,19 @@ long wp_mask; && (u.uprops[oart->inv_prop].extrinsic & W_ARTI)) (void) arti_invoke(otmp); } + + if (wp_mask == W_WEP && is_art(otmp, ART_SUNSWORD)) { + if (on) + EBlnd_resist |= wp_mask; + else + EBlnd_resist &= ~wp_mask; + } } /* touch_artifact()'s return value isn't sufficient to tell whether it dished out damage, and tracking changes to u.uhp, u.mh, Lifesaved when trying to avoid second wounding is too cumbersome */ -STATIC_VAR boolean touch_blasted; /* for retouch_object() */ +static boolean touch_blasted; /* for retouch_object() */ /* * creature (usually hero) tries to touch (pick up or wield) an artifact obj. @@ -645,18 +905,16 @@ STATIC_VAR boolean touch_blasted; /* for retouch_object() */ * fooled by such trappings. */ int -touch_artifact(obj, mon) -struct obj *obj; -struct monst *mon; +touch_artifact(struct obj *obj, struct monst *mon) { - register const struct artifact *oart = get_artifact(obj); + const struct artifact *oart = get_artifact(obj); boolean badclass, badalign, self_willed, yours; touch_blasted = FALSE; - if (!oart) + if (oart == &artilist[ART_NONARTIFACT]) return 1; - yours = (mon == &youmonst); + yours = (mon == &gy.youmonst); /* all quest artifacts are self-willed; if this ever changes, `badclass' will have to be extended to explicitly include quest artifacts */ self_willed = ((oart->spfx & SPFX_INTEL) != 0); @@ -718,13 +976,11 @@ struct monst *mon; /* decide whether an artifact itself is vulnerable to a particular type of erosion damage, independent of the properties of its bearer */ boolean -arti_immune(obj, dtyp) -struct obj *obj; -int dtyp; +arti_immune(struct obj *obj, int dtyp) { - register const struct artifact *weap = get_artifact(obj); + const struct artifact *weap = get_artifact(obj); - if (!weap) + if (weap == &artilist[ART_NONARTIFACT]) return FALSE; if (dtyp == AD_PHYS) return FALSE; /* nothing is immune to phys dmg */ @@ -733,14 +989,13 @@ int dtyp; || weap->cary.adtyp == dtyp); } -STATIC_OVL boolean -bane_applies(oart, mon) -const struct artifact *oart; -struct monst *mon; +staticfn boolean +bane_applies(const struct artifact *oart, struct monst *mon) { struct artifact atmp; - if (oart && (oart->spfx & SPFX_DBONUS) != 0) { + if (oart != &artilist[ART_NONARTIFACT] + && (oart->spfx & SPFX_DBONUS) != 0) { atmp = *oart; atmp.spfx &= SPFX_DBONUS; /* clear other spfx fields */ if (spec_applies(&atmp, mon)) @@ -750,10 +1005,8 @@ struct monst *mon; } /* decide whether an artifact's special attacks apply against mtmp */ -STATIC_OVL int -spec_applies(weap, mtmp) -register const struct artifact *weap; -struct monst *mtmp; +staticfn int +spec_applies(const struct artifact *weap, struct monst *mtmp) { struct permonst *ptr; boolean yours; @@ -761,7 +1014,7 @@ struct monst *mtmp; if (!(weap->spfx & (SPFX_DBONUS | SPFX_ATTK))) return (weap->attk.adtyp == AD_PHYS); - yours = (mtmp == &youmonst); + yours = (mtmp == &gy.youmonst); ptr = mtmp->data; if (weap->spfx & SPFX_DMONS) { @@ -773,18 +1026,16 @@ struct monst *mtmp; } else if (weap->spfx & SPFX_DFLAG2) { return ((ptr->mflags2 & weap->mtype) || (yours - && ((!Upolyd && (urace.selfmask & weap->mtype)) - || ((weap->mtype & M2_WERE) && u.ulycn >= LOW_PM)))); + && ((!Upolyd && (gu.urace.selfmask & weap->mtype)) + || ((weap->mtype & M2_WERE) && ismnum(u.ulycn))))); } else if (weap->spfx & SPFX_DALIGN) { return yours ? (u.ualign.type != weap->alignment) : (ptr->maligntyp == A_NONE || sgn(ptr->maligntyp) != weap->alignment); } else if (weap->spfx & SPFX_ATTK) { - struct obj *defending_weapon = (yours ? uwep : MON_WEP(mtmp)); - - if (defending_weapon && defending_weapon->oartifact - && defends((int) weap->attk.adtyp, defending_weapon)) + if (defended(mtmp, (int) weap->attk.adtyp)) return FALSE; + switch (weap->attk.adtyp) { case AD_FIRE: return !(yours ? Fire_resistance : resists_fire(mtmp)); @@ -811,60 +1062,55 @@ struct monst *mtmp; /* return the M2 flags of monster that an artifact's special attacks apply * against */ long -spec_m2(otmp) -struct obj *otmp; +spec_m2(struct obj *otmp) { const struct artifact *artifact = get_artifact(otmp); - if (artifact) + if (artifact != &artilist[ART_NONARTIFACT]) return artifact->mtype; return 0L; } /* special attack bonus */ int -spec_abon(otmp, mon) -struct obj *otmp; -struct monst *mon; +spec_abon(struct obj *otmp, struct monst *mon) { const struct artifact *weap = get_artifact(otmp); /* no need for an extra check for `NO_ATTK' because this will always return 0 for any artifact which has that attribute */ - if (weap && weap->attk.damn && spec_applies(weap, mon)) + if (weap != &artilist[ART_NONARTIFACT] + && weap->attk.damn && spec_applies(weap, mon)) return rnd((int) weap->attk.damn); return 0; } /* special damage bonus */ int -spec_dbon(otmp, mon, tmp) -struct obj *otmp; -struct monst *mon; -int tmp; +spec_dbon(struct obj *otmp, struct monst *mon, int tmp) { - register const struct artifact *weap = get_artifact(otmp); + const struct artifact *weap = get_artifact(otmp); - if (!weap || (weap->attk.adtyp == AD_PHYS /* check for `NO_ATTK' */ + if ((weap == &artilist[ART_NONARTIFACT]) + || (weap->attk.adtyp == AD_PHYS /* check for `NO_ATTK' */ && weap->attk.damn == 0 && weap->attk.damd == 0)) - spec_dbon_applies = FALSE; - else if (otmp->oartifact == ART_GRIMTOOTH) + gs.spec_dbon_applies = FALSE; + else if (is_art(otmp, ART_GRIMTOOTH)) /* Grimtooth has SPFX settings to warn against elves but we want its damage bonus to apply to all targets, so bypass spec_applies() */ - spec_dbon_applies = TRUE; + gs.spec_dbon_applies = TRUE; else - spec_dbon_applies = spec_applies(weap, mon); + gs.spec_dbon_applies = spec_applies(weap, mon); - if (spec_dbon_applies) + if (gs.spec_dbon_applies) return weap->attk.damd ? rnd((int) weap->attk.damd) : max(tmp, 1); return 0; } /* add identified artifact to discoveries list */ void -discover_artifact(m) -xchar m; +discover_artifact(xint16 m) { int i; @@ -882,8 +1128,7 @@ xchar m; /* used to decide whether an artifact has been fully identified */ boolean -undiscovered_artifact(m) -xchar m; +undiscovered_artifact(xint16 m) { int i; @@ -899,10 +1144,11 @@ xchar m; /* display a list of discovered artifacts; return their count */ int -disp_artifact_discoveries(tmpwin) -winid tmpwin; /* supplied by dodiscover() */ +disp_artifact_discoveries( + winid tmpwin) /* supplied by dodiscover(); type is NHW_TEXT */ { int i, m, otyp; + const char *algnstr; char buf[BUFSZ]; for (i = 0; i < NROFARTIFACTS; i++) { @@ -912,16 +1158,56 @@ winid tmpwin; /* supplied by dodiscover() */ continue; /* for WIN_ERR, we just count */ if (i == 0) - putstr(tmpwin, iflags.menu_headings, "Artifacts"); + putstr(tmpwin, iflags.menu_headings.attr, "Artifacts"); m = artidisco[i]; otyp = artilist[m].otyp; + algnstr = align_str(artilist[m].alignment); + if (!strcmp(algnstr, "unaligned")) + algnstr = "non-aligned"; + Sprintf(buf, " %s [%s %s]", artiname(m), - align_str(artilist[m].alignment), simple_typename(otyp)); + algnstr, simple_typename(otyp)); putstr(tmpwin, 0, buf); } return i; } +/* (wizard mode only) show all artifacts and their flags */ +void +dump_artifact_info(winid tmpwin) +{ + int m; + char buf[BUFSZ], buf2[BUFSZ]; + + /* not a menu, but header uses same bold or whatever attribute as such */ + putstr(tmpwin, iflags.menu_headings.attr, "Artifacts"); + for (m = 1; m <= NROFARTIFACTS; ++m) { + Snprintf(buf2, sizeof buf2, + "[%s%s%s%s%s%s%s%s%s]", /* 9 bits overall */ + artiexist[m].exists ? "exists;" : "", + artiexist[m].found ? " hero knows;" : "", + /* .exists and .found have different punctuation because + they're expected to be combined with one of these */ + artiexist[m].gift ? " gift" : "", + artiexist[m].wish ? " wish" : "", + artiexist[m].named ? " named" : "", + artiexist[m].viadip ? " viadip" : "", + artiexist[m].lvldef ? " lvldef" : "", + artiexist[m].bones ? " bones" : "", + artiexist[m].rndm ? " random" : ""); +#if 0 /* 'tmpwin' here is a text window, not a menu */ + if (iflags.menu_tab_sep) + Sprintf(buf, " %s\t%s", artiname(m), buf2); + else +#else + /* "The Platinum Yendorian Express Card" is 35 characters */ + Snprintf(buf, sizeof buf, " %-36.36s%s", artiname(m), buf2); +#endif + putstr(tmpwin, 0, buf); + } + return; +} + /* * Magicbane's intrinsic magic is incompatible with normal * enchantment magic. Thus, its effects have a negative @@ -959,18 +1245,19 @@ static const char *const mb_verb[2][NUM_MB_INDICES] = { }; /* called when someone is being hit by Magicbane */ -STATIC_OVL boolean -Mb_hit(magr, mdef, mb, dmgptr, dieroll, vis, hittee) -struct monst *magr, *mdef; /* attacker and defender */ -struct obj *mb; /* Magicbane */ -int *dmgptr; /* extra damage target will suffer */ -int dieroll; /* d20 that has already scored a hit */ -boolean vis; /* whether the action can be seen */ -char *hittee; /* target's name: "you" or mon_nam(mdef) */ -{ - struct permonst *old_uasmon; +staticfn boolean +Mb_hit(struct monst *magr, /* attacker */ + struct monst *mdef, /* defender */ + struct obj *mb, /* Magicbane */ + int *dmgptr, /* extra damage target will suffer */ + int dieroll, /* d20 that has already scored a hit */ + boolean vis, /* whether the action can be seen */ + char *hittee) /* target's name: "you" or mon_nam(mdef) */ +{ + struct permonst *old_mdat; const char *verb; - boolean youattack = (magr == &youmonst), youdefend = (mdef == &youmonst), + boolean youattack = (magr == &gy.youmonst), + youdefend = (mdef == &gy.youmonst), resisted = FALSE, do_stun, do_confuse, result; int attack_indx, fakeidx, scare_dieroll = MB_MAX_DIEROLL / 2; @@ -980,14 +1267,14 @@ char *hittee; /* target's name: "you" or mon_nam(mdef) */ scare_dieroll /= (1 << (mb->spe / 3)); /* if target successfully resisted the artifact damage bonus, reduce overall likelihood of the assorted special effects */ - if (!spec_dbon_applies) + if (!gs.spec_dbon_applies) dieroll += 1; /* might stun even when attempting a more severe effect, but in that case it will only happen if the other effect fails; extra damage will apply regardless; 3.4.1: sometimes might just probe even when it hasn't been enchanted */ - do_stun = (max(mb->spe, 0) < rn2(spec_dbon_applies ? 11 : 7)); + do_stun = (max(mb->spe, 0) < rn2(gs.spec_dbon_applies ? 11 : 7)); /* the special effects also boost physical damage; increments are generally cumulative, but since the stun effect is based on a @@ -1026,7 +1313,7 @@ char *hittee; /* target's name: "you" or mon_nam(mdef) */ /* now perform special effects */ switch (attack_indx) { case MB_INDEX_CANCEL: - old_uasmon = youmonst.data; + old_mdat = youdefend ? gy.youmonst.data : mdef->data; /* No mdef->mcan check: even a cancelled monster can be polymorphed * into a golem, and the "cancel" effect acts as if some magical * energy remains in spellcasting defenders to be absorbed later. @@ -1036,22 +1323,28 @@ char *hittee; /* target's name: "you" or mon_nam(mdef) */ } else { do_stun = FALSE; if (youdefend) { - if (youmonst.data != old_uasmon) + if (gy.youmonst.data != old_mdat) *dmgptr = 0; /* rehumanized, so no more damage */ if (u.uenmax > 0) { u.uenmax--; if (u.uen > 0) u.uen--; - context.botl = TRUE; + disp.botl = TRUE; You("lose magical energy!"); } } else { + /* canceled shapeshifter/vamp may have changed forms, so + update its name if necessary */ + if (mdef->data != old_mdat) + Strcpy(hittee, mon_nam(mdef)); if (mdef->data == &mons[PM_CLAY_GOLEM]) mdef->mhp = 1; /* cancelled clay golems will die */ if (youattack && attacktype(mdef->data, AT_MAGC)) { u.uenmax++; + if (u.uenmax > u.uenpeak) + u.uenpeak = u.uenmax; u.uen++; - context.botl = TRUE; + disp.botl = TRUE; You("absorb magical energy!"); } } @@ -1064,10 +1357,10 @@ char *hittee; /* target's name: "you" or mon_nam(mdef) */ resisted = TRUE; } else { nomul(-3); - multi_reason = "being scared stiff"; - nomovemsg = ""; - if (magr && magr == u.ustuck && sticks(youmonst.data)) { - u.ustuck = (struct monst *) 0; + gm.multi_reason = "being scared stiff"; + gn.nomovemsg = ""; + if (magr && magr == u.ustuck && sticks(gy.youmonst.data)) { + set_ustuck((struct monst *) 0); You("release %s!", mon_nam(magr)); } } @@ -1140,6 +1433,8 @@ char *hittee; /* target's name: "you" or mon_nam(mdef) */ return result; } +DISABLE_WARNING_FORMAT_NONLITERAL + /* Function used when someone attacks someone else with an artifact * weapon. Only adds the special (artifact) damage, and returns a 1 if it * did something special (in which case the caller won't print the normal @@ -1149,17 +1444,18 @@ char *hittee; /* target's name: "you" or mon_nam(mdef) */ * Stormbringer it's "killed by Stormbringer" instead of "killed by an orc". */ boolean -artifact_hit(magr, mdef, otmp, dmgptr, dieroll) -struct monst *magr, *mdef; -struct obj *otmp; -int *dmgptr; -int dieroll; /* needed for Magicbane and vorpal blades */ -{ - boolean youattack = (magr == &youmonst); - boolean youdefend = (mdef == &youmonst); +artifact_hit( + struct monst *magr, /* attacker; might be Null if 'mdef' is youmonst */ + struct monst *mdef, /* defender */ + struct obj *otmp, /* artifact weapon */ + int *dmgptr, /* output */ + int dieroll) /* needed for Magicbane and vorpal blades */ +{ + boolean youattack = (magr == &gy.youmonst); + boolean youdefend = (mdef == &gy.youmonst); boolean vis = (!youattack && magr && cansee(magr->mx, magr->my)) || (!youdefend && cansee(mdef->mx, mdef->my)) - || (youattack && u.uswallow && mdef == u.ustuck && !Blind); + || (youattack && engulfing_u(mdef) && !Blind); boolean realizes_damage; const char *wepdesc; static const char you[] = "you"; @@ -1186,18 +1482,18 @@ int dieroll; /* needed for Magicbane and vorpal blades */ if (attacks(AD_FIRE, otmp)) { if (realizes_damage) pline_The("fiery blade %s %s%c", - !spec_dbon_applies + !gs.spec_dbon_applies ? "hits" : (mdef->data == &mons[PM_WATER_ELEMENTAL]) ? "vaporizes part of" : "burns", - hittee, !spec_dbon_applies ? '.' : '!'); - if (!rn2(4)) - (void) destroy_mitem(mdef, POTION_CLASS, AD_FIRE); - if (!rn2(4)) - (void) destroy_mitem(mdef, SCROLL_CLASS, AD_FIRE); - if (!rn2(7)) - (void) destroy_mitem(mdef, SPBOOK_CLASS, AD_FIRE); + hittee, !gs.spec_dbon_applies ? '.' : '!'); + if (!rn2(4)) { + int itemdmg = destroy_items(mdef, AD_FIRE, *dmgptr); + if (!youdefend) + *dmgptr += itemdmg; /* item destruction dmg */ + ignite_items(mdef->minvent); + } if (youdefend && Slimed) burn_away_slime(); return realizes_damage; @@ -1205,32 +1501,36 @@ int dieroll; /* needed for Magicbane and vorpal blades */ if (attacks(AD_COLD, otmp)) { if (realizes_damage) pline_The("ice-cold blade %s %s%c", - !spec_dbon_applies ? "hits" : "freezes", hittee, - !spec_dbon_applies ? '.' : '!'); - if (!rn2(4)) - (void) destroy_mitem(mdef, POTION_CLASS, AD_COLD); + !gs.spec_dbon_applies ? "hits" : "freezes", hittee, + !gs.spec_dbon_applies ? '.' : '!'); + if (!rn2(4)) { + int itemdmg = destroy_items(mdef, AD_COLD, *dmgptr); + if (!youdefend) + *dmgptr += itemdmg; /* item destruction dmg */ + } return realizes_damage; } if (attacks(AD_ELEC, otmp)) { if (realizes_damage) pline_The("massive hammer hits%s %s%c", - !spec_dbon_applies ? "" : "! Lightning strikes", - hittee, !spec_dbon_applies ? '.' : '!'); - if (spec_dbon_applies) + !gs.spec_dbon_applies ? "" : "! Lightning strikes", + hittee, !gs.spec_dbon_applies ? '.' : '!'); + if (gs.spec_dbon_applies) wake_nearto(mdef->mx, mdef->my, 4 * 4); - if (!rn2(5)) - (void) destroy_mitem(mdef, RING_CLASS, AD_ELEC); - if (!rn2(5)) - (void) destroy_mitem(mdef, WAND_CLASS, AD_ELEC); + if (!rn2(5)) { + int itemdmg = destroy_items(mdef, AD_ELEC, *dmgptr); + if (!youdefend) + *dmgptr += itemdmg; /* item destruction dmg */ + } return realizes_damage; } if (attacks(AD_MAGM, otmp)) { if (realizes_damage) pline_The("imaginary widget hits%s %s%c", - !spec_dbon_applies + !gs.spec_dbon_applies ? "" : "! A hail of magic missiles strikes", - hittee, !spec_dbon_applies ? '.' : '!'); + hittee, !gs.spec_dbon_applies ? '.' : '!'); return realizes_damage; } @@ -1239,7 +1539,7 @@ int dieroll; /* needed for Magicbane and vorpal blades */ return Mb_hit(magr, mdef, otmp, dmgptr, dieroll, vis, hittee); } - if (!spec_dbon_applies) { + if (!gs.spec_dbon_applies) { /* since damage bonus didn't apply, nothing more to do; no further attacks have side-effects on inventory */ return FALSE; @@ -1248,17 +1548,17 @@ int dieroll; /* needed for Magicbane and vorpal blades */ /* We really want "on a natural 20" but Nethack does it in */ /* reverse from AD&D. */ if (spec_ability(otmp, SPFX_BEHEAD)) { - if (otmp->oartifact == ART_TSURUGI_OF_MURAMASA && dieroll == 1) { + if (is_art(otmp, ART_TSURUGI_OF_MURAMASA) && dieroll == 1) { wepdesc = "The razor-sharp blade"; /* not really beheading, but so close, why add another SPFX */ - if (youattack && u.uswallow && mdef == u.ustuck) { + if (youattack && engulfing_u(mdef)) { You("slice %s wide open!", mon_nam(mdef)); *dmgptr = 2 * mdef->mhp + FATAL_DAMAGE_MODIFIER; return TRUE; } if (!youdefend) { /* allow normal cutworm() call to add extra damage */ - if (notonhead) + if (gn.notonhead) return FALSE; if (bigmonst(mdef->data)) { @@ -1272,10 +1572,10 @@ int dieroll; /* needed for Magicbane and vorpal blades */ } *dmgptr = 2 * mdef->mhp + FATAL_DAMAGE_MODIFIER; pline("%s cuts %s in half!", wepdesc, mon_nam(mdef)); - otmp->dknown = TRUE; + observe_object(otmp); return TRUE; } else { - if (bigmonst(youmonst.data)) { + if (bigmonst(gy.youmonst.data)) { pline("%s cuts deeply into you!", magr ? Monnam(magr) : wepdesc); *dmgptr *= 2; @@ -1289,19 +1589,19 @@ int dieroll; /* needed for Magicbane and vorpal blades */ */ *dmgptr = 2 * (Upolyd ? u.mh : u.uhp) + FATAL_DAMAGE_MODIFIER; pline("%s cuts you in half!", wepdesc); - otmp->dknown = TRUE; + observe_object(otmp); return TRUE; } - } else if (otmp->oartifact == ART_VORPAL_BLADE + } else if (is_art(otmp, ART_VORPAL_BLADE) && (dieroll == 1 || mdef->data == &mons[PM_JABBERWOCK])) { static const char *const behead_msg[2] = { "%s beheads %s!", "%s decapitates %s!" }; - if (youattack && u.uswallow && mdef == u.ustuck) + if (youattack && engulfing_u(mdef)) return FALSE; wepdesc = artilist[ART_VORPAL_BLADE].name; if (!youdefend) { - if (!has_head(mdef->data) || notonhead || u.uswallow) { + if (!has_head(mdef->data) || gn.notonhead || u.uswallow) { if (youattack) pline("Somehow, you miss %s wildly.", mon_nam(mdef)); else if (vis) @@ -1315,79 +1615,104 @@ int dieroll; /* needed for Magicbane and vorpal blades */ return TRUE; } *dmgptr = 2 * mdef->mhp + FATAL_DAMAGE_MODIFIER; - pline(behead_msg[rn2(SIZE(behead_msg))], wepdesc, + pline(ROLL_FROM(behead_msg), wepdesc, mon_nam(mdef)); if (Hallucination && !flags.female) pline("Good job Henry, but that wasn't Anne."); - otmp->dknown = TRUE; + observe_object(otmp); return TRUE; } else { - if (!has_head(youmonst.data)) { + if (!has_head(gy.youmonst.data)) { pline("Somehow, %s misses you wildly.", magr ? mon_nam(magr) : wepdesc); *dmgptr = 0; return TRUE; } - if (noncorporeal(youmonst.data) || amorphous(youmonst.data)) { + if (noncorporeal(gy.youmonst.data) + || amorphous(gy.youmonst.data)) { pline("%s slices through your %s.", wepdesc, body_part(NECK)); return TRUE; } *dmgptr = 2 * (Upolyd ? u.mh : u.uhp) + FATAL_DAMAGE_MODIFIER; - pline(behead_msg[rn2(SIZE(behead_msg))], wepdesc, "you"); - otmp->dknown = TRUE; + pline(ROLL_FROM(behead_msg), wepdesc, "you"); + observe_object(otmp); /* Should amulets fall off? */ return TRUE; } } } if (spec_ability(otmp, SPFX_DRLI)) { - /* some non-living creatures (golems, vortices) are - vulnerable to life drain effects */ + /* some non-living creatures (golems, vortices) are vulnerable to + life drain effects so can get " draws the " feedback */ const char *life = nonliving(mdef->data) ? "animating force" : "life"; if (!youdefend) { + int m_lev = (int) mdef->m_lev, /* will be 0 for 1d4 mon */ + mhpmax = mdef->mhpmax, + drain = monhp_per_lvl(mdef); /* usually 1d8 */ + /* note: DRLI attack uses 2d6, attacker doesn't get healed */ + + /* stop draining HP if it drops too low (still drains level; + also caller still inflicts regular weapon damage) */ + if (mhpmax - drain <= m_lev) + drain = (mhpmax > m_lev) ? (mhpmax - (m_lev + 1)) : 0; + if (vis) { - if (otmp->oartifact == ART_STORMBRINGER) + /* call distant_name() for possible side-effects even if + the result won't be printed */ + char *otmpname = distant_name(otmp, xname); + + if (is_art(otmp, ART_STORMBRINGER)) pline_The("%s blade draws the %s from %s!", hcolor(NH_BLACK), life, mon_nam(mdef)); else pline("%s draws the %s from %s!", - The(distant_name(otmp, xname)), life, - mon_nam(mdef)); + The(otmpname), life, mon_nam(mdef)); } if (mdef->m_lev == 0) { + /* losing a level when at 0 is fatal */ *dmgptr = 2 * mdef->mhp + FATAL_DAMAGE_MODIFIER; } else { - int drain = monhp_per_lvl(mdef); - *dmgptr += drain; mdef->mhpmax -= drain; mdef->m_lev--; - drain /= 2; - if (drain) + } + + if (drain > 0) { + /* drain: was target's damage, now heal attacker by half */ + drain = (drain + 1) / 2; /* drain/2 rounded up */ + if (youattack) { healup(drain, 0, FALSE, FALSE); + } else { + assert(magr != 0); + healmon(magr, drain, 0); + } } return vis; } else { /* youdefend */ int oldhpmax = u.uhpmax; - if (Blind) + if (Blind) { You_feel("an %s drain your %s!", - (otmp->oartifact == ART_STORMBRINGER) + is_art(otmp, ART_STORMBRINGER) ? "unholy blade" : "object", life); - else if (otmp->oartifact == ART_STORMBRINGER) - pline_The("%s blade drains your %s!", hcolor(NH_BLACK), life); - else - pline("%s drains your %s!", The(distant_name(otmp, xname)), - life); + } else { + /* call distant_name() for possible side-effects even if + the result won't be printed */ + char *otmpname = distant_name(otmp, xname); + + if (is_art(otmp, ART_STORMBRINGER)) + pline_The("%s blade drains your %s!", + hcolor(NH_BLACK), life); + else + pline("%s drains your %s!", The(otmpname), life); + } losexp("life drainage"); if (magr && magr->mhp < magr->mhpmax) { - magr->mhp += (oldhpmax - u.uhpmax) / 2; - if (magr->mhp > magr->mhpmax) - magr->mhp = magr->mhpmax; + healmon(magr, (abs(oldhpmax - u.uhpmax) + 1) / 2, 0); } return TRUE; } @@ -1395,239 +1720,484 @@ int dieroll; /* needed for Magicbane and vorpal blades */ return FALSE; } -static NEARDATA const char recharge_type[] = { ALLOW_COUNT, ALL_CLASSES, 0 }; -static NEARDATA const char invoke_types[] = { ALL_CLASSES, 0 }; -/* #invoke: an "ugly check" filters out most objects */ +RESTORE_WARNING_FORMAT_NONLITERAL + +/* getobj callback for object to be invoked */ +staticfn int +invoke_ok(struct obj *obj) +{ + if (!obj) + return GETOBJ_EXCLUDE; + + /* artifacts and other special items */ + if (obj->oartifact || objects[obj->otyp].oc_unique + || (obj->otyp == FAKE_AMULET_OF_YENDOR && !obj->known)) + return GETOBJ_SUGGEST; + + /* synonym for apply, though actually invoking it will do different things + * depending if it's a regular crystal ball, an artifact one that has an + * invoke power, and a (theoretical) artifact one that doesn't have an + * invoke power */ + if (obj->otyp == CRYSTAL_BALL) + return GETOBJ_SUGGEST; + + return GETOBJ_EXCLUDE; +} /* the #invoke command */ int -doinvoke() +doinvoke(void) { struct obj *obj; - obj = getobj(invoke_types, "invoke"); + obj = getobj("invoke", invoke_ok, GETOBJ_PROMPT); if (!obj) - return 0; + return ECMD_CANCEL; if (!retouch_object(&obj, FALSE)) - return 1; + return ECMD_TIME; return arti_invoke(obj); } -STATIC_OVL int -arti_invoke(obj) -struct obj *obj; +staticfn void +nothing_special(struct obj *obj) { - register const struct artifact *oart = get_artifact(obj); - if (!obj) { - impossible("arti_invoke without obj"); - return 0; + if (carried(obj)) + You_feel("a surge of power, but nothing seems to happen."); +} + +staticfn int +invoke_taming(struct obj *obj UNUSED) +{ + struct obj pseudo; + + pseudo = cg.zeroobj; /* neither cursed nor blessed, zero oextra too */ + pseudo.otyp = SCR_TAMING; + (void) seffects(&pseudo); + return ECMD_TIME; +} + +staticfn int +invoke_healing(struct obj *obj) +{ + int healamt = (u.uhpmax + 1 - u.uhp) / 2; + long creamed = (long) u.ucreamed; + + if (Upolyd) + healamt = (u.mhmax + 1 - u.mh) / 2; + if (healamt || Sick || Slimed || Blinded > creamed) + You_feel("better."); + if (healamt || Sick || Slimed || BlindedTimeout > creamed) + You_feel("%sbetter.", + (!healamt && !Sick && !Slimed + /* when healing temporary blindness (aside from + goop covering face), might still be blind + due to PermaBlind or eyeless polymorph; + vary the message in that situation */ + && (HBlinded & ~TIMEOUT) != 0L) ? "slightly " : ""); + else { + nothing_special(obj); + return ECMD_TIME; } - if (!oart || !oart->inv_prop) { - if (obj->otyp == CRYSTAL_BALL) - use_crystal_ball(&obj); + if (healamt > 0) { + if (Upolyd) + u.mh += healamt; else - pline1(nothing_happens); - return 1; + u.uhp += healamt; } + if (Sick) + make_sick(0L, (char *) 0, FALSE, SICK_ALL); + if (Slimed) + make_slimed(0L, (char *) 0); + if (BlindedTimeout > creamed) + make_blinded(creamed, FALSE); + disp.botl = TRUE; + return ECMD_TIME; +} - if (oart->inv_prop > LAST_PROP) { - /* It's a special power, not "just" a property */ - if (obj->age > monstermoves) { - /* 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; - } - obj->age = monstermoves + rnz(100); +staticfn int +invoke_energy_boost(struct obj *obj) +{ + int epboost = (u.uenmax + 1 - u.uen) / 2; + + if (epboost > 120) + epboost = 120; /* arbitrary */ + else if (epboost < 12) + epboost = u.uenmax - u.uen; + if (epboost) { + u.uen += epboost; + disp.botl = TRUE; + You_feel("re-energized."); + } else { + nothing_special(obj); + return ECMD_TIME; + } + return ECMD_TIME; +} - switch (oart->inv_prop) { - case TAMING: { - struct obj pseudo; +staticfn int +invoke_untrap(struct obj *obj) +{ + if (!untrap(TRUE, 0, 0, (struct obj *) 0)) { + obj->age = 0; /* don't charge for changing their mind */ + return ECMD_CANCEL; + } + return ECMD_TIME; +} - pseudo = - zeroobj; /* neither cursed nor blessed, zero oextra too */ - pseudo.otyp = SCR_TAMING; - (void) seffects(&pseudo); - break; - } - case HEALING: { - int healamt = (u.uhpmax + 1 - u.uhp) / 2; - long creamed = (long) u.ucreamed; - - if (Upolyd) - healamt = (u.mhmax + 1 - u.mh) / 2; - if (healamt || Sick || Slimed || Blinded > creamed) - You_feel("better."); - else - goto nothing_special; - if (healamt > 0) { - if (Upolyd) - u.mh += healamt; - else - u.uhp += healamt; - } - if (Sick) - make_sick(0L, (char *) 0, FALSE, SICK_ALL); - if (Slimed) - make_slimed(0L, (char *) 0); - if (Blinded > creamed) - make_blinded(creamed, FALSE); - context.botl = TRUE; - break; - } - case ENERGY_BOOST: { - int epboost = (u.uenmax + 1 - u.uen) / 2; - - if (epboost > 120) - epboost = 120; /* arbitrary */ - else if (epboost < 12) - epboost = u.uenmax - u.uen; - if (epboost) { - u.uen += epboost; - context.botl = TRUE; - You_feel("re-energized."); - } else - goto nothing_special; - break; +staticfn int +invoke_charge_obj(struct obj *obj) +{ + const struct artifact *oart = get_artifact(obj); + struct obj *otmp = getobj("charge", charge_ok, + GETOBJ_PROMPT | GETOBJ_ALLOWCNT); + boolean b_effect; + + if (!otmp) { + obj->age = 0; + return ECMD_CANCEL; + } + b_effect = (obj->blessed && (oart->role == Role_switch + || oart->role == NON_PM)); + recharge(otmp, b_effect ? 1 : obj->cursed ? -1 : 0); + update_inventory(); + return ECMD_TIME; +} + +staticfn int +invoke_create_portal(struct obj *obj) +{ + int i, num_ok_dungeons, last_ok_dungeon = 0; + d_level newlev; + winid tmpwin = create_nhwindow(NHW_MENU); + anything any; + int clr = NO_COLOR; + + any = cg.zeroany; /* set all bits to zero */ + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + /* use index+1 (can't use 0) as identifier */ + for (i = num_ok_dungeons = 0; i < svn.n_dgns; i++) { + if (!svd.dungeons[i].dunlev_ureached) + continue; + if (i == tutorial_dnum) /* can't portal into tutorial */ + continue; + any.a_int = i + 1; + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, + ATR_NONE, clr, + svd.dungeons[i].dname, MENU_ITEMFLAGS_NONE); + num_ok_dungeons++; + last_ok_dungeon = i; + } + end_menu(tmpwin, "Open a portal to which dungeon?"); + if (num_ok_dungeons > 1) { + /* more than one entry; display menu for choices */ + menu_item *selected; + int n; + + n = select_menu(tmpwin, PICK_ONE, &selected); + if (n <= 0) { + destroy_nhwindow(tmpwin); + nothing_special(obj); + return ECMD_TIME; } - case UNTRAP: { - if (!untrap(TRUE)) { - obj->age = 0; /* don't charge for changing their mind */ - return 0; + i = selected[0].item.a_int - 1; + free((genericptr_t) selected); + } else + i = last_ok_dungeon; /* also first & only OK dungeon */ + destroy_nhwindow(tmpwin); + + /* + * i is now index into dungeon structure for the new dungeon. + * Find the closest level in the given dungeon, open + * a use-once portal to that dungeon and go there. + * The closest level is either the entry or dunlev_ureached. + */ + newlev.dnum = i; + if (svd.dungeons[i].depth_start >= depth(&u.uz)) + newlev.dlevel = svd.dungeons[i].entry_lev; + else + newlev.dlevel = svd.dungeons[i].dunlev_ureached; + + if (u.uhave.amulet || In_endgame(&u.uz) || In_endgame(&newlev) + || newlev.dnum == u.uz.dnum || !next_to_u()) { + You_feel("very disoriented for a moment."); + } else { + if (!Blind) + You("are surrounded by a shimmering sphere!"); + else + You_feel("weightless for a moment."); + goto_level(&newlev, FALSE, FALSE, FALSE); + } + return ECMD_TIME; +} + +staticfn int +invoke_create_ammo(struct obj *obj) +{ + struct obj *otmp = mksobj(ARROW, TRUE, FALSE); + + if (!otmp) { + nothing_special(obj); + return ECMD_TIME; + } + otmp->blessed = obj->blessed; + otmp->cursed = obj->cursed; + otmp->bknown = obj->bknown; + otmp->oeroded = otmp->oeroded2 = 0; + if (obj->blessed) { + if (otmp->spe < 0) + otmp->spe = 0; + otmp->quan += rnd(10); + } else if (obj->cursed) { + if (otmp->spe > 0) + otmp->spe = 0; + } else + otmp->quan += rnd(5); + otmp->owt = weight(otmp); + otmp = hold_another_object(otmp, "Suddenly %s out.", + aobjnam(otmp, "fall"), (char *) 0); + nhUse(otmp); + return ECMD_TIME; +} + +staticfn int +invoke_banish(struct obj *obj UNUSED) +{ + int nvanished = 0, nstayed = 0; + struct monst *mtmp, *mtmp2; + d_level dest; + + find_hell(&dest); + + for (mtmp = fmon; mtmp; mtmp = mtmp2) { + int chance = 1; + + mtmp2 = mtmp->nmon; + if (DEADMONSTER(mtmp) || !isok(mtmp->mx, mtmp->my)) + continue; + if (!is_demon(mtmp->data) && mtmp->data->mlet != S_IMP) + continue; + if (!couldsee(mtmp->mx, mtmp->my)) + continue; + if (mtmp->data->msound == MS_NEMESIS) + continue; + + if (In_quest(&u.uz) && !svq.quest_status.killed_nemesis) + chance += 10; + if (is_dprince(mtmp->data)) + chance += 2; + if (is_dlord(mtmp->data)) + chance++; + + mtmp->msleeping = mtmp->mtame = mtmp->mpeaceful = 0; + if (chance <= 1 || !rn2(chance)) { + if (!Inhell) { + nvanished++; + /* banish to a random level in Gehennom */ + dest.dlevel = rn2(dunlevs_in_dungeon(&dest)); + migrate_mon(mtmp, ledger_no(&dest), MIGR_RANDOM); + } else { + u_teleport_mon(mtmp, FALSE); } - break; + } else { + nstayed++; } - case CHARGE_OBJ: { - struct obj *otmp = getobj(recharge_type, "charge"); - boolean b_effect; + } - if (!otmp) { - obj->age = 0; - return 0; - } - b_effect = (obj->blessed && (oart->role == Role_switch - || oart->role == NON_PM)); - recharge(otmp, b_effect ? 1 : obj->cursed ? -1 : 0); - update_inventory(); - break; + if (nvanished) { + char subject[] = "demons"; + + if (nvanished == 1) + *(eos(subject) - 1) = '\0'; /* remove 's' */ + pline("%s %s %s in a cloud of brimstone!", + nstayed ? ((nvanished > nstayed) + ? "Most of the" + : "Some of the") + : "The", + subject, vtense(subject, "disappear")); + } + return ECMD_TIME; +} + +staticfn int +invoke_fling_poison(struct obj *obj) +{ + if (getdir((char *) 0)) { + int venom = rn2(2) ? BLINDING_VENOM : ACID_VENOM; + struct obj *otmp = mksobj(venom, TRUE, FALSE); + + otmp->spe = 1; /* the poison is yours */ + throwit(otmp, 0L, FALSE, (struct obj *) 0); + } else { + /* no direction picked */ + pline("%s", Never_mind); + obj->age = svm.moves; + return ECMD_CANCEL; + } + return ECMD_TIME; +} + +staticfn int +invoke_storm_spell(struct obj *obj) +{ + const struct artifact *oart = get_artifact(obj); + int storm = oart->inv_prop == SNOWSTORM ? SPE_CONE_OF_COLD : SPE_FIREBALL; + int skill = spell_skilltype(storm); + int expertise = P_SKILL(skill); + + P_SKILL(skill) = P_EXPERT; + (void) spelleffects(storm, FALSE, TRUE); + P_SKILL(skill) = expertise; + return ECMD_TIME; +} + +staticfn int +invoke_blinding_ray(struct obj *obj) +{ + if (getdir((char *) 0)) { + if (u.dx || u.dy) { + do_blinding_ray(obj); + } else if (u.dz) { + /* up or down => light this map spot; litroom() uses + radius 0 for Sunsword, except on Rogue level where + whole room gets lit and corridor spots remain unlit */ + litroom(TRUE, obj); + pline("%s", ((!Blind && levl[u.ux][u.uy].lit + && !levl[u.ux][u.uy].waslit) + ? "It is lit here now." + : nothing_seems_to_happen)); + } else { /* zapyourself() */ + boolean vulnerable = (u.umonnum == PM_GREMLIN); + int damg = obj->blessed ? 15 : !obj->cursed ? 10 : 5; + + if (vulnerable) /* could be fatal if Unchanging */ + (void) lightdamage(obj, TRUE, 2 * damg); + + if (!flashburn((long) (damg + rnd(damg)), FALSE) + && !vulnerable) + pline("%s", nothing_seems_to_happen); } - case LEV_TELE: - level_tele(); - break; - case CREATE_PORTAL: { - int i, num_ok_dungeons, last_ok_dungeon = 0; - d_level newlev; - extern int n_dgns; /* from dungeon.c */ - winid tmpwin = create_nhwindow(NHW_MENU); - anything any; - - any = zeroany; /* set all bits to zero */ - start_menu(tmpwin); - /* use index+1 (cant use 0) as identifier */ - for (i = num_ok_dungeons = 0; i < n_dgns; i++) { - if (!dungeons[i].dunlev_ureached) - continue; - any.a_int = i + 1; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - dungeons[i].dname, MENU_UNSELECTED); - num_ok_dungeons++; - last_ok_dungeon = i; - } - end_menu(tmpwin, "Open a portal to which dungeon?"); - if (num_ok_dungeons > 1) { - /* more than one entry; display menu for choices */ - menu_item *selected; - int n; - - n = select_menu(tmpwin, PICK_ONE, &selected); - if (n <= 0) { - destroy_nhwindow(tmpwin); - goto nothing_special; - } - i = selected[0].item.a_int - 1; - free((genericptr_t) selected); - } else - i = last_ok_dungeon; /* also first & only OK dungeon */ - destroy_nhwindow(tmpwin); + } else { + /* no direction picked */ + pline("%s", Never_mind); + obj->age = svm.moves; + return ECMD_CANCEL; + } + return ECMD_TIME; +} - /* - * i is now index into dungeon structure for the new dungeon. - * Find the closest level in the given dungeon, open - * a use-once portal to that dungeon and go there. - * The closest level is either the entry or dunlev_ureached. - */ - newlev.dnum = i; - if (dungeons[i].depth_start >= depth(&u.uz)) - newlev.dlevel = dungeons[i].entry_lev; - else - newlev.dlevel = dungeons[i].dunlev_ureached; +/* return the amount of Pw invoking an object costs. + return a negative value, if obj invoking cannot be paid with Pw */ +staticfn int +arti_invoke_cost_pw(struct obj *obj) +{ + const struct artifact *oart = get_artifact(obj); - if (u.uhave.amulet || In_endgame(&u.uz) || In_endgame(&newlev) - || newlev.dnum == u.uz.dnum || !next_to_u()) { - You_feel("very disoriented for a moment."); - } else { - if (!Blind) - You("are surrounded by a shimmering sphere!"); - else - You_feel("weightless for a moment."); - goto_level(&newlev, FALSE, FALSE, FALSE); - } - break; + if (oart->inv_prop == FLING_POISON + || oart->inv_prop == BLINDING_RAY) { + /* pretend it's a level 5 spell */ + return SPELL_LEV_PW(5); + } + + return -1; +} + +/* return TRUE if artifact object's invoke cost can be paid (and pay it) */ +staticfn boolean +arti_invoke_cost(struct obj *obj) +{ + if (obj->age > svm.moves) { + int pw_cost = arti_invoke_cost_pw(obj); + + if (pw_cost < 0 || u.uen < pw_cost) { + /* 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 FALSE; + } else { + /* you pay invoke cost with your own magic */ + You_feel("drained..."); + u.uen -= pw_cost; + disp.botl = TRUE; } + } else { + obj->age = svm.moves + rnz(100); + } + return TRUE; +} + +staticfn int +arti_invoke(struct obj *obj) +{ + const struct artifact *oart; + int res = ECMD_OK; + + if (!obj) { + impossible("arti_invoke without obj"); + return ECMD_OK; + } + oart = get_artifact(obj); + if (oart == &artilist[ART_NONARTIFACT] || !oart->inv_prop) { + if (obj->otyp == CRYSTAL_BALL) + use_crystal_ball(&obj); + else + pline1(nothing_happens); + return ECMD_TIME; + } + + /* It's a special power, not "just" a property */ + if (oart->inv_prop > LAST_PROP) { + if (!arti_invoke_cost(obj)) + return ECMD_TIME; + + switch (oart->inv_prop) { + case TAMING: res = invoke_taming(obj); break; + case HEALING: res = invoke_healing(obj); break; + case ENERGY_BOOST: res = invoke_energy_boost(obj); break; + case UNTRAP: res = invoke_untrap(obj); break; + case CHARGE_OBJ: res = invoke_charge_obj(obj); break; + case LEV_TELE: level_tele(); res = ECMD_TIME; break; + case CREATE_PORTAL: res = invoke_create_portal(obj); break; case ENLIGHTENING: enlightenment(MAGICENLIGHTENMENT, ENL_GAMEINPROGRESS); + res = ECMD_TIME; break; - case CREATE_AMMO: { - struct obj *otmp = mksobj(ARROW, TRUE, FALSE); - - if (!otmp) - goto nothing_special; - otmp->blessed = obj->blessed; - otmp->cursed = obj->cursed; - otmp->bknown = obj->bknown; - if (obj->blessed) { - if (otmp->spe < 0) - otmp->spe = 0; - otmp->quan += rnd(10); - } else if (obj->cursed) { - if (otmp->spe > 0) - otmp->spe = 0; - } else - otmp->quan += rnd(5); - otmp->owt = weight(otmp); - otmp = hold_another_object(otmp, "Suddenly %s out.", - aobjnam(otmp, "fall"), (char *) 0); - nhUse(otmp); + case CREATE_AMMO: res = invoke_create_ammo(obj); break; + case BANISH: res = invoke_banish(obj); break; + case FLING_POISON: res = invoke_fling_poison(obj); break; + case SNOWSTORM: + /*FALLTHRU*/ + case FIRESTORM: res = invoke_storm_spell(obj); break; + case BLINDING_RAY: res = invoke_blinding_ray(obj); break; + default: + impossible("Unknown invoke power %d.", oart->inv_prop); break; } - } + return res; } else { long eprop = (u.uprops[oart->inv_prop].extrinsic ^= W_ARTI), iprop = u.uprops[oart->inv_prop].intrinsic; boolean on = (eprop & W_ARTI) != 0; /* true if prop just set */ - if (on && obj->age > monstermoves) { + if (on && obj->age > svm.moves) { /* the artifact is tired :-) */ 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; + return ECMD_TIME; } else if (!on) { /* when turning off property, determine downtime */ /* arbitrary for now until we can tune this -dlc */ - obj->age = monstermoves + rnz(100); + obj->age = svm.moves + rnz(100); } if ((eprop & ~W_ARTI) || iprop) { - nothing_special: /* you had the property from some other source too */ - if (carried(obj)) - You_feel("a surge of power, but nothing seems to happen."); - return 1; + nothing_special(obj); + return ECMD_TIME; } switch (oart->inv_prop) { case CONFLICT: @@ -1644,8 +2214,10 @@ struct obj *obj; (void) float_down(I_SPECIAL | TIMEOUT, W_ARTI); break; case INVIS: - if (BInvis || Blind) - goto nothing_special; + if (BInvis || Blind) { + nothing_special(obj); + return ECMD_TIME; + } newsym(u.ux, u.uy); if (on) Your("body takes on a %s transparency...", @@ -1656,13 +2228,12 @@ struct obj *obj; } } - return 1; + return ECMD_TIME; } /* will freeing this object from inventory cause levitation to end? */ boolean -finesse_ahriman(obj) -struct obj *obj; +finesse_ahriman(struct obj *obj) { const struct artifact *oart; struct prop save_Lev; @@ -1670,7 +2241,8 @@ struct obj *obj; /* if we aren't levitating or this isn't an artifact which confers levitation via #invoke then freeinv() won't toggle levitation */ - if (!Levitation || (oart = get_artifact(obj)) == 0 + if (!Levitation + || (oart = get_artifact(obj)) == &artilist[ART_NONARTIFACT] || oart->inv_prop != LEVITATION || !(ELevitation & W_ARTI)) return FALSE; @@ -1689,47 +2261,52 @@ struct obj *obj; /* WAC return TRUE if artifact is always lit */ boolean -artifact_light(obj) -struct obj *obj; +artifact_light(struct obj *obj) { - return (boolean) (get_artifact(obj) && obj->oartifact == ART_SUNSWORD); + /* not artifacts but treat them as if they were because they emit + light without burning */ + if (obj && (obj->otyp == GOLD_DRAGON_SCALE_MAIL + || obj->otyp == GOLD_DRAGON_SCALES) + && (obj->owornmask & W_ARM) != 0L) + return TRUE; + + return (boolean) ((get_artifact(obj) != &artilist[ART_NONARTIFACT]) + && is_art(obj, ART_SUNSWORD)); } /* KMH -- Talking artifacts are finally implemented */ -void -arti_speak(obj) -struct obj *obj; +int +arti_speak(struct obj *obj) { - register const struct artifact *oart = get_artifact(obj); + const struct artifact *oart = get_artifact(obj); const char *line; char buf[BUFSZ]; /* Is this a speaking artifact? */ - if (!oart || !(oart->spfx & SPFX_SPEAK)) - return; + if (oart == &artilist[ART_NONARTIFACT] || !(oart->spfx & SPFX_SPEAK)) + return ECMD_OK; /* nothing happened */ line = getrumor(bcsign(obj), buf, TRUE); if (!*line) line = "NetHack rumors file closed for renovation."; pline("%s:", Tobjnam(obj, "whisper")); + SetVoice((struct monst *) 0, 0, 80, voice_talking_artifact); verbalize1(line); - return; + return ECMD_TIME; } boolean -artifact_has_invprop(otmp, inv_prop) -struct obj *otmp; -uchar inv_prop; +artifact_has_invprop(struct obj *otmp, uchar inv_prop) { const struct artifact *arti = get_artifact(otmp); - return (boolean) (arti && (arti->inv_prop == inv_prop)); + return (boolean) ((arti != &artilist[ART_NONARTIFACT]) + && (arti->inv_prop == inv_prop)); } /* Return the price sold to the hero of a given artifact or unique item */ long -arti_cost(otmp) -struct obj *otmp; +arti_cost(struct obj *otmp) { if (!otmp->oartifact) return (long) objects[otmp->otyp].oc_cost; @@ -1739,9 +2316,8 @@ struct obj *otmp; return (100L * (long) objects[otmp->otyp].oc_cost); } -STATIC_OVL uchar -abil_to_adtyp(abil) -long *abil; +staticfn uchar +abil_to_adtyp(long *abil) { struct abil2adtyp_tag { long *abil; @@ -1764,9 +2340,8 @@ long *abil; return 0; } -STATIC_OVL unsigned long -abil_to_spfx(abil) -long *abil; +staticfn unsigned long +abil_to_spfx(long *abil) { static const struct abil2spfx_tag { long *abil; @@ -1798,8 +2373,7 @@ long *abil; * Return the first item that is conveying a particular intrinsic. */ struct obj * -what_gives(abil) -long *abil; +what_gives(long *abil) { struct obj *obj; uchar dtyp; @@ -1816,12 +2390,12 @@ long *abil; spfx = abil_to_spfx(abil); wornbits = (wornmask & *abil); - for (obj = invent; obj; obj = obj->nobj) { + for (obj = gi.invent; obj; obj = obj->nobj) { if (obj->oartifact - && (abil != &EWarn_of_mon || context.warntype.obj)) { + && (abil != &EWarn_of_mon || svc.context.warntype.obj)) { const struct artifact *art = get_artifact(obj); - if (art) { + if (art != &artilist[ART_NONARTIFACT]) { if (dtyp) { if (art->cary.adtyp == dtyp /* carried */ || (art->defn.adtyp == dtyp /* defends while worn */ @@ -1836,6 +2410,10 @@ long *abil; if ((art->spfx & spfx) == spfx && obj->owornmask) return obj; } + if (obj == uwep && abil == &EBlnd_resist + && (*abil & W_WEP) != 0L) { + return obj; /* Sunsword */ + } } } else { if (wornbits && wornbits == (wornmask & obj->owornmask)) @@ -1846,8 +2424,7 @@ long *abil; } const char * -glow_color(arti_indx) -int arti_indx; +glow_color(int arti_indx) { int colornum = artilist[arti_indx].acolor; const char *colorstr = clr2colorname(colornum); @@ -1856,14 +2433,13 @@ int arti_indx; } /* glow verb; [0] holds the value used when blind */ -static const char *glow_verbs[] = { +static const char *const glow_verbs[] = { "quiver", "flicker", "glimmer", "gleam" }; /* relative strength that Sting is glowing (0..3), to select verb */ -STATIC_OVL int -glow_strength(count) -int count; +staticfn int +glow_strength(int count) { /* glow strength should also be proportional to proximity and probably difficulty, but we don't have that information and @@ -1872,9 +2448,8 @@ int count; } const char * -glow_verb(count, ingsfx) -int count; /* 0 means blind rather than no applicable creatures */ -boolean ingsfx; +glow_verb(int count, /* 0 means blind rather than no applicable creatures */ + boolean ingsfx) { static char resbuf[20]; @@ -1888,22 +2463,27 @@ boolean ingsfx; /* use for warning "glow" for Sting, Orcrist, and Grimtooth */ void -Sting_effects(orc_count) -int orc_count; /* new count (warn_obj_cnt is old count); -1 is a flag value */ -{ - if (uwep - && (uwep->oartifact == ART_STING - || uwep->oartifact == ART_ORCRIST - || uwep->oartifact == ART_GRIMTOOTH)) { - int oldstr = glow_strength(warn_obj_cnt), +Sting_effects( + int orc_count) /* new count (warn_obj_cnt is old count); + * -1 is a flag value */ +{ + if (u_wield_art(ART_STING) + || u_wield_art(ART_ORCRIST) + || u_wield_art(ART_GRIMTOOTH)) { + int oldstr = glow_strength(gw.warn_obj_cnt), newstr = glow_strength(orc_count); - if (orc_count == -1 && warn_obj_cnt > 0) { + if (orc_count == -1 && gw.warn_obj_cnt > 0) { /* -1 means that blindness has just been toggled; give a 'continue' message that eventual 'stop' message will match */ pline("%s is %s.", bare_artifactname(uwep), - glow_verb(Blind ? 0 : warn_obj_cnt, TRUE)); + glow_verb(Blind ? 0 : gw.warn_obj_cnt, TRUE)); } else if (newstr > 0 && newstr != oldstr) { + /* goto_level() -> docrt() -> see_monsters() -> Sting_effects(); + if "you materialize on a different level" is pending, give + it now so that start-glowing message comes after it */ + maybe_lvltport_feedback(); /* usually called by goto_level() */ + /* 'start' message */ if (!Blind) pline("%s %s %s%c", bare_artifactname(uwep), @@ -1913,10 +2493,10 @@ int orc_count; /* new count (warn_obj_cnt is old count); -1 is a flag value */ else if (oldstr == 0) /* quivers */ pline("%s %s slightly.", bare_artifactname(uwep), otense(uwep, glow_verb(0, FALSE))); - } else if (orc_count == 0 && warn_obj_cnt > 0) { + } else if (orc_count == 0 && gw.warn_obj_cnt > 0) { /* 'stop' message */ pline("%s stops %s.", bare_artifactname(uwep), - glow_verb(Blind ? 0 : warn_obj_cnt, TRUE)); + glow_verb(Blind ? 0 : gw.warn_obj_cnt, TRUE)); } } } @@ -1925,17 +2505,23 @@ int orc_count; /* new count (warn_obj_cnt is old count); -1 is a flag value */ after undergoing a transformation (alignment change, lycanthropy, polymorph) which might affect item access */ int -retouch_object(objp, loseit) -struct obj **objp; /* might be destroyed or unintentionally dropped */ -boolean loseit; /* whether to drop it if hero can longer touch it */ +retouch_object( + struct obj **objp, /* might be destroyed or unintentionally dropped */ + boolean loseit) /* whether to drop it if hero can longer touch it */ { struct obj *obj = *objp; - if (touch_artifact(obj, &youmonst)) { + /* allow hero in silver-hating form to try to perform invocation ritual */ + if (obj->otyp == BELL_OF_OPENING + && invocation_pos(u.ux, u.uy) && !On_stairs(u.ux, u.uy)) { + return 1; + } + + if (touch_artifact(obj, &gy.youmonst)) { char buf[BUFSZ]; int dmg = 0, tmp; boolean ag = (objects[obj->otyp].oc_material == SILVER && Hate_silver), - bane = bane_applies(get_artifact(obj), &youmonst); + bane = bane_applies(get_artifact(obj), &gy.youmonst); /* nothing else to do if hero can successfully handle this object */ if (!ag && !bane) @@ -1947,13 +2533,26 @@ boolean loseit; /* whether to drop it if hero can longer touch it */ obj->owornmask ? " anymore" : ""); /* also inflict damage unless touch_artifact() already did so */ if (!touch_blasted) { + const char *what = killer_xname(obj); + + if (ag && !obj->oartifact && !bane) { + /* 'obj' is silver; for rings and wands it ended up that + way due to randomization at start of game; showing this + game's silver item without stating that it is silver + potentially leads to confusion about cause of death */ + if (obj->oclass == RING_CLASS) + what = "a silver ring"; + else if (obj->oclass == WAND_CLASS) + what = "a silver wand"; + /* for anything else, stick with killer_xname() */ + } /* damage is somewhat arbitrary; half the usual 1d20 physical for silver, 1d10 magical for bane, potentially both */ if (ag) tmp = rnd(10), dmg += Maybe_Half_Phys(tmp); if (bane) dmg += rnd(10); - Sprintf(buf, "handling %s", killer_xname(obj)); + Sprintf(buf, "handling %s", what); losehp(dmg, buf, KILLED_BY); exercise(A_CON, FALSE); } @@ -1966,7 +2565,7 @@ boolean loseit; /* whether to drop it if hero can longer touch it */ struct obj *otmp; remove_worn_item(obj, FALSE); - for (otmp = invent; otmp; otmp = otmp->nobj) + for (otmp = gi.invent; otmp; otmp = otmp->nobj) if (otmp == obj) break; if (!otmp) @@ -1979,7 +2578,8 @@ boolean loseit; /* whether to drop it if hero can longer touch it */ freeinv(obj); hitfloor(obj, TRUE); } else { - /* dropx gives a message iff item lands on an altar */ + /* dropx gives a message if a dropped item lands on an altar; + we provide one for other terrain */ if (!IS_ALTAR(levl[u.ux][u.uy].typ)) pline("%s to the %s.", Tobjnam(obj, "fall"), surface(u.ux, u.uy)); @@ -1990,26 +2590,29 @@ boolean loseit; /* whether to drop it if hero can longer touch it */ return 0; } -/* an item which is worn/wielded or an artifact which conveys - something via being carried or which has an #invoke effect - currently in operation undergoes a touch test; if it fails, - it will be unworn/unwielded and revoked but not dropped */ -STATIC_OVL boolean -untouchable(obj, drop_untouchable) -struct obj *obj; -boolean drop_untouchable; +/* hero has changed form or alignment; an item which is worn/wielded + or an artifact which conveys something via being carried or which + has an #invoke effect currently in operation undergoes a touch test; + if it fails, it will be unworn/unwielded and maybe dropped */ +staticfn boolean +untouchable( + struct obj *obj, /* object to test; in invent or is steed's saddle */ + boolean drop_untouchable) /* whether to drop it if it can't be touched */ { struct artifact *art; boolean beingworn, carryeffect, invoked; long wearmask = ~(W_QUIVER | (u.twoweap ? 0L : W_SWAPWEP) | W_BALL); - beingworn = ((obj->owornmask & wearmask) != 0L - /* some items in use don't have any wornmask setting */ - || (obj->oclass == TOOL_CLASS - && (obj->lamplit || (obj->otyp == LEASH && obj->leashmon) - || (Is_container(obj) && Has_contents(obj))))); + beingworn = (obj /* never Null; this pacifies static analysis when + * the get_artifact() macro tests 'obj' for Null */ + && ((obj->owornmask & wearmask) != 0L + /* some items in use don't have any wornmask setting */ + || (obj->oclass == TOOL_CLASS + && (obj->lamplit + || (obj->otyp == LEASH && obj->leashmon) + || (Is_container(obj) && Has_contents(obj)))))); - if ((art = get_artifact(obj)) != 0) { + if ((art = get_artifact(obj)) != &artilist[ART_NONARTIFACT]) { carryeffect = (art->cary.adtyp || art->cspfx); invoked = (art->inv_prop > 0 && art->inv_prop <= LAST_PROP && (u.uprops[art->inv_prop].extrinsic & W_ARTI) != 0L); @@ -2025,7 +2628,7 @@ boolean drop_untouchable; carried effect was turned off, else we leave that alone; we turn off invocation property here if still carried */ if (invoked && obj) - arti_invoke(obj); /* reverse #invoke */ + (void) arti_invoke(obj); /* reverse #invoke */ return TRUE; } } @@ -2034,8 +2637,8 @@ boolean drop_untouchable; /* check all items currently in use (mostly worn) for touchability */ void -retouch_equipment(dropflag) -int dropflag; /* 0==don't drop, 1==drop all, 2==drop weapon */ +retouch_equipment( + int dropflag) /* 0==don't drop, 1==drop all, 2==drop weapon */ { static int nesting = 0; /* recursion control */ struct obj *obj; @@ -2087,9 +2690,9 @@ int dropflag; /* 0==don't drop, 1==drop all, 2==drop weapon */ /* loss of levitation (silver ring, or Heart of Ahriman invocation) might cause hero to lose inventory items (by dropping into lava, for instance), so inventory traversal needs to rescan the whole - invent chain each time it moves on to another object; we use bypass + gi.invent chain each time it moves on to another object; we use bypass handling to keep track of which items have already been processed */ - while ((obj = nxt_unbypassed_obj(invent)) != 0) + while ((obj = nxt_unbypassed_obj(gi.invent)) != 0) (void) untouchable(obj, dropit); if (had_rings != (!!uleft + !!uright) && uarmg && uarmg->cursed) @@ -2101,16 +2704,13 @@ int dropflag; /* 0==don't drop, 1==drop all, 2==drop weapon */ clear_bypasses(); /* reset upon final exit */ } -static int mkot_trap_warn_count = 0; - -STATIC_OVL int -count_surround_traps(x, y) -int x, y; +staticfn int +count_surround_traps(coordxy x, coordxy y) { struct rm *levp; - struct obj *otmp; - struct trap *ttmp; - int dx, dy, glyph, ret = 0; + struct obj *o; + coordxy dx, dy; + int glyph, ret = 0; for (dx = x - 1; dx < x + 2; ++dx) for (dy = y - 1; dy < y + 2; ++dy) { @@ -2124,7 +2724,7 @@ int x, y; glyph = glyph_at(dx, dy); if (glyph_is_trap(glyph)) continue; - if ((ttmp = t_at(dx, dy)) != 0) { + if (t_at(dx, dy)) { ++ret; continue; } @@ -2133,8 +2733,8 @@ int x, y; ++ret; continue; } - for (otmp = level.objects[dx][dy]; otmp; otmp = otmp->nexthere) - if (Is_container(otmp) && otmp->otrapped) { + for (o = svl.level.objects[dx][dy]; o; o = o->nexthere) + if (Is_container(o) && o->otrapped) { ++ret; /* we're counting locations, so just */ break; /* count the first one in a pile */ } @@ -2150,51 +2750,51 @@ int x, y; /* sense adjacent traps if wielding MKoT without wearing gloves */ void -mkot_trap_warn() +mkot_trap_warn(void) { static const char *const heat[7] = { "cool", "slightly warm", "warm", "very warm", "hot", "very hot", "like fire" }; - if (!uarmg && uwep && uwep->oartifact == ART_MASTER_KEY_OF_THIEVERY) { + if (!uarmg && u_wield_art(ART_MASTER_KEY_OF_THIEVERY)) { int idx, ntraps = count_surround_traps(u.ux, u.uy); - if (ntraps != mkot_trap_warn_count) { + if (ntraps != gm.mkot_trap_warn_count) { idx = min(ntraps, SIZE(heat) - 1); pline_The("Key feels %s%c", heat[idx], (ntraps > 3) ? '!' : '.'); } - mkot_trap_warn_count = ntraps; + gm.mkot_trap_warn_count = ntraps; } else - mkot_trap_warn_count = 0; + gm.mkot_trap_warn_count = 0; } /* Master Key is magic key if its bless/curse state meets our criteria: not cursed for rogues or blessed for non-rogues */ boolean -is_magic_key(mon, obj) -struct monst *mon; /* if null, non-rogue is assumed */ -struct obj *obj; -{ - if (((obj && obj->oartifact == ART_MASTER_KEY_OF_THIEVERY) - && ((mon == &youmonst) ? Role_if(PM_ROGUE) - : (mon && mon->data == &mons[PM_ROGUE]))) - ? !obj->cursed : obj->blessed) - return TRUE; +is_magic_key(struct monst *mon, /* if null, non-rogue is assumed */ + struct obj *obj) +{ + if (is_art(obj, ART_MASTER_KEY_OF_THIEVERY)) { + if ((mon == &gy.youmonst) ? Role_if(PM_ROGUE) + : (mon && mon->data == &mons[PM_ROGUE])) + return !obj->cursed; /* a rogue; non-cursed suffices for magic */ + /* not a rogue; key must be blessed to behave as a magic one */ + return obj->blessed; + } return FALSE; } /* figure out whether 'mon' (usually youmonst) is carrying the magic key */ struct obj * -has_magic_key(mon) -struct monst *mon; /* if null, hero assumed */ +has_magic_key(struct monst *mon) /* if null, hero assumed */ { struct obj *o; short key = artilist[ART_MASTER_KEY_OF_THIEVERY].otyp; if (!mon) - mon = &youmonst; - for (o = ((mon == &youmonst) ? invent : mon->minvent); o; + mon = &gy.youmonst; + for (o = ((mon == &gy.youmonst) ? gi.invent : mon->minvent); o; o = nxtobj(o, key, FALSE)) { if (is_magic_key(mon, o)) return o; @@ -2202,4 +2802,42 @@ struct monst *mon; /* if null, hero assumed */ return (struct obj *) 0; } +/* #define is_art(o,art) ((o) && (o)->oartifact == (art)) */ + +boolean +is_art(struct obj *obj, int art) +{ + if (obj && obj->oartifact == art) + return TRUE; + return FALSE; +} + +/* #define get_artifact(o) \ + (((o) && ((o)->artifact > 0 && (o)->artifact < AFTER_LAST_ARTIFACT)) \ + ? &artilist[(int) (o)->oartifact] \ + : &artilist[ART_NONARTIFACT]) */ + +staticfn struct artifact * +get_artifact(struct obj *obj) +{ + if (obj) { + int artidx = (int) obj->oartifact; + + /* skip 0, 1st artifact at 1 */ + /* SIZE(artilist) would include the terminator, + so use AFTER_LAST_ARTIFACT instead */ + if (artidx > 0 && artidx < AFTER_LAST_ARTIFACT) + return &artilist[artidx]; + } + return &artilist[ART_NONARTIFACT]; +} + +/* is object permanently poisoned? (currently only Grimtooth) */ +boolean +permapoisoned(struct obj *obj) +{ + return (obj && is_art(obj, ART_GRIMTOOTH)); +} +#endif /* SFCTOOL */ + /*artifact.c*/ diff --git a/src/attrib.c b/src/attrib.c index 028eebf83..aaad2dbd0 100644 --- a/src/attrib.c +++ b/src/attrib.c @@ -1,11 +1,10 @@ -/* NetHack 3.6 attrib.c $NHDT-Date: 1575245050 2019/12/02 00:04:10 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.66 $ */ +/* NetHack 5.0 attrib.c $NHDT-Date: 1777000050 2026/04/23 19:07:30 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.137 $ */ /* Copyright 1988, 1989, 1990, 1992, M. Stephenson */ /* NetHack may be freely redistributed. See license for details. */ /* attribute modification routines. */ #include "hack.h" -#include /* part of the output on gain or loss of attribute */ static const char @@ -14,7 +13,9 @@ static const char *const minusattr[] = { "weak", "stupid", "foolish", "clumsy", "fragile", "repulsive" }; -/* also used by enlightenment for non-abbreviated status info */ +/* also used by enlightenment in insight.c for non-abbreviated status info */ +extern const char *const attrname[6]; + const char *const attrname[] = { "strength", "intelligence", "wisdom", "dexterity", "constitution", "charisma" }; @@ -23,9 +24,9 @@ static const struct innate { schar ulevel; long *ability; const char *gainstr, *losestr; -} arc_abil[] = { { 1, &(HStealth), "", "" }, - { 1, &(HFast), "", "" }, - { 10, &(HSearching), "perceptive", "" }, +} arc_abil[] = { { 1, &(HSearching), "", "" }, + { 5, &(HStealth), "stealthy", "" }, + { 10, &(HFast), "quick", "slow" }, { 0, 0, 0, 0 } }, bar_abil[] = { { 1, &(HPoison_resistance), "", "" }, @@ -78,7 +79,7 @@ static const struct innate { { 0, 0, 0, 0 } }, val_abil[] = { { 1, &(HCold_resistance), "", "" }, - { 1, &(HStealth), "", "" }, + { 3, &(HStealth), "stealthy", "" }, { 7, &(HFast), "quick", "slow" }, { 0, 0, 0, 0 } }, @@ -103,18 +104,21 @@ static const struct innate { hum_abil[] = { { 0, 0, 0, 0 } }; -STATIC_DCL void NDECL(exerper); -STATIC_DCL void FDECL(postadjabil, (long *)); -STATIC_DCL const struct innate *FDECL(role_abil, (int)); -STATIC_DCL const struct innate *FDECL(check_innate_abil, (long *, long)); -STATIC_DCL int FDECL(innately, (long *)); +staticfn void exerper(void); +staticfn int rnd_attr(void); +staticfn int init_attr_role_redist(int, boolean); +staticfn void postadjabil(long *) NONNULLARG1; +staticfn const struct innate *role_abil(int); +staticfn const struct innate *check_innate_abil(long *, long); +staticfn int innately(long *); /* adjust an attribute; return TRUE if change is made, FALSE otherwise */ boolean -adjattrib(ndx, incr, msgflg) -int ndx, incr; -int msgflg; /* positive => no message, zero => message, and */ -{ /* negative => conditional (msg if change made) */ +adjattrib( + int ndx, /* which characteristic */ + int incr, /* amount of change */ + int msgflg) /* positive => no message, zero => message, and */ +{ /* negative => conditional (msg if change made) */ int old_acurr, old_abase, old_amax, decr; boolean abonflg; const char *attrstr; @@ -183,19 +187,20 @@ int msgflg; /* positive => no message, zero => message, and */ return FALSE; } + /* Any successful change also resets abuse / exercise level */ + AEXE(ndx) = 0; + + disp.botl = TRUE; if (msgflg <= 0) You_feel("%s%s!", (incr > 1 || incr < -1) ? "very " : "", attrstr); - context.botl = TRUE; if (program_state.in_moveloop && (ndx == A_STR || ndx == A_CON)) - (void) encumber_msg(); + encumber_msg(); return TRUE; } +/* strength gain */ void -gainstr(otmp, incr, givemsg) -struct obj *otmp; -int incr; -boolean givemsg; +gainstr(struct obj *otmp, int incr, boolean givemsg) { int num = incr; @@ -211,29 +216,69 @@ boolean givemsg; givemsg ? -1 : 1); } -/* may kill you; cause may be poison or monster like 'a' */ +/* strength loss, may kill you; cause may be poison or monster like 'a' */ void -losestr(num) -register int num; +losestr(int num, const char *knam, schar k_format) { - int ustr = ABASE(A_STR) - num; + int uhpmin = minuhpmax(1), olduhpmax = u.uhpmax; + int ustr = ABASE(A_STR) - num, amt, dmg; + boolean waspolyd = Upolyd; - while (ustr < 3) { + if (num <= 0 || ABASE(A_STR) < ATTRMIN(A_STR)) { + impossible("losestr: %d - %d", ABASE(A_STR), num); + return; + } + dmg = 0; + while (ustr < ATTRMIN(A_STR)) { ++ustr; --num; + amt = rn1(4, 3); /* (0..(4-1))+3 => 3..6; used to use flat 6 here */ + dmg += amt; + } + if (dmg) { + /* in case damage is fatal and caller didn't supply killer reason */ + if (!knam || !*knam) { + knam = "terminal frailty"; + k_format = KILLED_BY; + } + losehp(dmg, knam, k_format); + if (Upolyd) { - u.mh -= 6; - u.mhmax -= 6; - } else { - u.uhp -= 6; - u.uhpmax -= 6; + /* when still poly'd, reduce you-as-monst maxHP; never below 1 */ + setuhpmax(max(u.mhmax - dmg, 1), FALSE); /* acts as setmhmax() */ + } else if (!waspolyd) { + /* not polymorphed now and didn't rehumanize when taking damage; + reduce max HP, but not below uhpmin */ + if (u.uhpmax > uhpmin) + setuhpmax(max(u.uhpmax - dmg, uhpmin), FALSE); } + disp.botl = TRUE; + } +#if 0 /* only possible if uhpmax was already less than uhpmin */ + if (!Upolyd && u.uhpmax < uhpmin) { + setuhpmax(min(olduhpmax, uhpmin), FALSE); + if (!Drain_resistance) + losexp(NULL); /* won't be fatal when no 'drainer' is supplied */ } - (void) adjattrib(A_STR, -num, 1); +#else + nhUse(olduhpmax); +#endif + /* 'num' could have been reduced to 0 in the minimum strength loop; + '(Upolyd || !waspolyd)' is True unless damage caused rehumanization */ + if (num > 0 && (Upolyd || !waspolyd)) + (void) adjattrib(A_STR, -num, 1); +} + +/* combined strength loss and damage from some poisons */ +void +poison_strdmg(int strloss, int dmg, const char *knam, schar k_format) +{ + losestr(strloss, knam, k_format); + losehp(dmg, knam, k_format); } static const struct poison_effect_message { - void VDECL((*delivery_func), (const char *, ...)); + void (*delivery_func)(const char *, ...); const char *effect_msg; } poiseff[] = { { You_feel, "weaker" }, /* A_STR */ @@ -246,11 +291,10 @@ static const struct poison_effect_message { /* feedback for attribute loss due to poisoning */ void -poisontell(typ, exclaim) -int typ; /* which attribute */ -boolean exclaim; /* emphasis */ +poisontell(int typ, /* which attribute */ + boolean exclaim) /* emphasis */ { - void VDECL((*func), (const char *, ...)) = poiseff[typ].delivery_func; + void (*func)(const char *, ...) = poiseff[typ].delivery_func; const char *msg_txt = poiseff[typ].effect_msg; /* @@ -270,18 +314,20 @@ boolean exclaim; /* emphasis */ /* called when an attack or trap has poisoned hero (used to be in mon.c) */ void -poisoned(reason, typ, pkiller, fatal, thrown_weapon) -const char *reason, /* controls what messages we display */ - *pkiller; /* for score+log file if fatal */ -int typ, fatal; /* if fatal is 0, limit damage to adjattrib */ -boolean thrown_weapon; /* thrown weapons are less deadly */ +poisoned( + const char *reason, /* controls what messages we display */ + int typ, + const char *pkiller, /* for score+log file if fatal */ + int fatal, /* if fatal is 0, limit damage to adjattrib */ + boolean thrown_weapon) /* thrown weapons are less deadly */ { int i, loss, kprefix = KILLED_BY_AN; + boolean blast = !strcmp(reason, "blast"); /* inform player about being poisoned unless that's already been done; "blast" has given a "blast of poison gas" message; "poison arrow", "poison dart", etc have implicitly given poison messages too... */ - if (strcmp(reason, "blast") && !strstri(reason, "poison")) { + if (!blast && !strstri(reason, "poison")) { boolean plural = (reason[strlen(reason) - 1] == 's') ? 1 : 0; /* avoid "The" Orcus's sting was poisoned... */ @@ -290,15 +336,15 @@ boolean thrown_weapon; /* thrown weapons are less deadly */ plural ? "were" : "was"); } if (Poison_resistance) { - if (!strcmp(reason, "blast")) + if (blast) shieldeff(u.ux, u.uy); pline_The("poison doesn't seem to affect you."); return; } /* suppress killer prefix if it already has one */ - i = name_to_mon(pkiller); - if (i >= LOW_PM && (mons[i].geno & G_UNIQ)) { + i = name_to_mon(pkiller, (int *) 0); + if (ismnum(i) && (mons[i].geno & G_UNIQ)) { kprefix = KILLED_BY; if (!type_is_pname(&mons[i])) pkiller = the(pkiller); @@ -308,15 +354,40 @@ boolean thrown_weapon; /* thrown weapons are less deadly */ kprefix = KILLED_BY; } + /* + * FIXME: + * this operates on u.uhp[max] even when hero is polymorphed.... + */ + i = !fatal ? 1 : rn2(fatal + (thrown_weapon ? 20 : 0)); if (i == 0 && typ != A_CHA) { - /* instant kill */ - u.uhp = -1; - context.botl = TRUE; - pline_The("poison was deadly..."); + /* sometimes survivable instant kill */ + loss = 6 + d(4, 6); /* 6 + 4d6 => 10..34 */ + if (u.uhp <= loss) { + u.uhp = -1; + disp.botl = TRUE; + pline_The("poison was deadly..."); + } else { + /* survived, but with severe reaction */ + int olduhp = u.uhp, + newuhpmax = u.uhpmax - (loss / 2); + + setuhpmax(max(newuhpmax, minuhpmax(3)), TRUE); /*True: see FIXME*/ + loss = adjuhploss(loss, olduhp); + + losehp(loss, pkiller, kprefix); /* poison damage */ + if (adjattrib(A_CON, (typ != A_CON) ? -1 : -3, TRUE)) + poisontell(A_CON, TRUE); + if (typ != A_CON && adjattrib(typ, -3, 1)) + poisontell(typ, TRUE); + } } else if (i > 5) { + boolean cloud = !strcmp(reason, "gas cloud"); + /* HP damage; more likely--but less severe--with missiles */ loss = thrown_weapon ? rnd(6) : rn1(10, 6); + if ((blast || cloud) && Half_gas_damage) /* worn towel */ + loss = (loss + 1) / 2; losehp(loss, pkiller, kprefix); /* poison damage */ } else { /* attribute loss; if typ is A_STR, reduction in current and @@ -328,17 +399,16 @@ boolean thrown_weapon; /* thrown weapons are less deadly */ } if (u.uhp < 1) { - killer.format = kprefix; - Strcpy(killer.name, pkiller); + svk.killer.format = kprefix; + Strcpy(svk.killer.name, pkiller); /* "Poisoned by a poisoned ___" is redundant */ done(strstri(pkiller, "poison") ? DIED : POISONING); } - (void) encumber_msg(); + encumber_msg(); } void -change_luck(n) -register schar n; +change_luck(schar n) { u.uluck += n; if (u.uluck < 0 && u.uluck < LUCKMIN) @@ -347,20 +417,19 @@ register schar n; u.uluck = LUCKMAX; } +/* decide whether there are more blessed luckstones (plus luck-conferring + artifacts) than cursed ones; optionally combine uncursed with blessed */ int -stone_luck(parameter) -boolean parameter; /* So I can't think up of a good name. So sue me. --KAA */ +stone_luck(boolean include_uncursed) { - register struct obj *otmp; - register long bonchance = 0; + struct obj *otmp; + long bonchance = 0; - for (otmp = invent; otmp; otmp = otmp->nobj) + for (otmp = gi.invent; otmp; otmp = otmp->nobj) if (confers_luck(otmp)) { if (otmp->cursed) bonchance -= otmp->quan; - else if (otmp->blessed) - bonchance += otmp->quan; - else if (parameter) + else if (otmp->blessed || include_uncursed) bonchance += otmp->quan; } @@ -369,7 +438,7 @@ boolean parameter; /* So I can't think up of a good name. So sue me. --KAA */ /* there has just been an inventory change affecting a luck-granting item */ void -set_moreluck() +set_moreluck(void) { int luckbon = stone_luck(TRUE); @@ -381,38 +450,43 @@ set_moreluck() u.moreluck = -LUCKADD; } +/* (not used) */ void -restore_attrib() +restore_attrib(void) { int i, equilibrium;; /* - * Note: this gets called on every turn but ATIME() is never set - * to non-zero anywhere, and ATEMP() is only used for strength loss - * from hunger, so it doesn't actually do anything. + * Note: this used to get called by moveloop() on every turn but + * ATIME() is never set to non-zero anywhere so didn't do anything. + * Presumably it once supported something like potion of heroism + * which conferred temporary characteristics boost(s). + * + * ATEMP() is used for strength loss from hunger, which doesn't + * time out, and for dexterity loss from wounded legs, which has + * its own timeout routine. */ for (i = 0; i < A_MAX; i++) { /* all temporary losses/gains */ - equilibrium = (i == A_STR && u.uhs >= WEAK) ? -1 : 0; + equilibrium = ((i == A_STR && u.uhs >= WEAK) + || (i == A_DEX && Wounded_legs)) ? -1 : 0; if (ATEMP(i) != equilibrium && ATIME(i) != 0) { if (!(--(ATIME(i)))) { /* countdown for change */ ATEMP(i) += (ATEMP(i) > 0) ? -1 : 1; - context.botl = TRUE; + disp.botl = TRUE; if (ATEMP(i)) /* reset timer */ ATIME(i) = 100 / ACURR(A_CON); } } } - if (context.botl) - (void) encumber_msg(); + if (disp.botl) + encumber_msg(); } #define AVAL 50 /* tune value for exercise gains */ void -exercise(i, inc_or_dec) -int i; -boolean inc_or_dec; +exercise(int i, boolean inc_or_dec) { debugpline0("Exercise:"); if (i == A_INT || i == A_CHA) @@ -439,23 +513,20 @@ boolean inc_or_dec; : "Con", (inc_or_dec) ? "inc" : "dec", AEXE(i)); } - if (moves > 0 && (i == A_STR || i == A_CON)) - (void) encumber_msg(); + if (svm.moves > 0 && (i == A_STR || i == A_CON)) + encumber_msg(); } -STATIC_OVL void -exerper() +staticfn void +exerper(void) { - if (!(moves % 10)) { + if (!(svm.moves % 10)) { /* Hunger Checks */ - - int hs = (u.uhunger > 1000) ? SATIATED : (u.uhunger > 150) - ? NOT_HUNGRY - : (u.uhunger > 50) - ? HUNGRY - : (u.uhunger > 0) - ? WEAK - : FAINTING; + int hs = (u.uhunger > 1000) ? SATIATED + : (u.uhunger > 150) ? NOT_HUNGRY + : (u.uhunger > 50) ? HUNGRY + : (u.uhunger > 0) ? WEAK + : FAINTING; debugpline0("exerper: Hunger checks"); switch (hs) { @@ -496,7 +567,7 @@ exerper() } /* status checks */ - if (!(moves % 5)) { + if (!(svm.moves % 5)) { debugpline0("exerper: Status checks"); if ((HClairvoyant & (INTRINSIC | TIMEOUT)) && !BClairvoyant) exercise(A_WIS, TRUE); @@ -524,18 +595,18 @@ static NEARDATA const char *const exertext[A_MAX][2] = { }; void -exerchk() +exerchk(void) { int i, ax, mod_val, lolim, hilim; /* Check out the periodic accumulations */ exerper(); - if (moves >= context.next_attrib_check) { - debugpline1("exerchk: ready to test. multi = %d.", multi); + if (svm.moves >= svc.context.next_attrib_check) { + debugpline1("exerchk: ready to test. multi = %ld.", gm.multi); } /* Are we ready for a test? */ - if (moves >= context.next_attrib_check && !multi) { + if (svm.moves >= svc.context.next_attrib_check && !gm.multi) { debugpline0("exerchk: testing."); /* * Law of diminishing returns (Part II): @@ -567,19 +638,13 @@ exerchk() goto nextattrib; debugpline2("exerchk: testing %s (%d).", - (i == A_STR) - ? "Str" - : (i == A_INT) - ? "Int?" - : (i == A_WIS) - ? "Wis" - : (i == A_DEX) - ? "Dex" - : (i == A_CON) - ? "Con" - : (i == A_CHA) - ? "Cha?" - : "???", + (i == A_STR) ? "Str" + : (i == A_INT) ? "Int?" + : (i == A_WIS) ? "Wis" + : (i == A_DEX) ? "Dex" + : (i == A_CON) ? "Con" + : (i == A_CHA) ? "Cha?" + : "???", ax); /* * Law of diminishing returns (Part III): @@ -605,65 +670,76 @@ exerchk() platform-dependent rounding/truncation for negative vals */ AEXE(i) = (abs(ax) / 2) * mod_val; } - context.next_attrib_check += rn1(200, 800); - debugpline1("exerchk: next check at %ld.", context.next_attrib_check); + svc.context.next_attrib_check += rn1(200, 800); + debugpline1("exerchk: next check at %ld.", + svc.context.next_attrib_check); } } -void -init_attr(np) -register int np; +/* return random hero attribute (by role's attr distribution). + returns A_MAX if failed. */ +staticfn int +rnd_attr(void) { - register int i, x, tryct; + int i, x = rn2(100); - for (i = 0; i < A_MAX; i++) { - ABASE(i) = AMAX(i) = urole.attrbase[i]; - ATEMP(i) = ATIME(i) = 0; - np -= urole.attrbase[i]; - } + /* 5.0: the x -= ... calculation used to have an off by 1 error that + resulted in the values being biased toward Str and away from Cha */ + for (i = 0; i < A_MAX; ++i) + if ((x -= gu.urole.attrdist[i]) < 0) + break; + return i; +} + +/* add or subtract np points from random attributes, + adjusting the base and maximum values of the attributes. + if subtracting, np must be negative. + returns the left over points. */ +staticfn int +init_attr_role_redist(int np, boolean addition) +{ + int tryct = 0; + int adj = addition ? 1 : -1; - tryct = 0; - while (np > 0 && tryct < 100) { - x = rn2(100); - for (i = 0; (i < A_MAX) && ((x -= urole.attrdist[i]) > 0); i++) - ; - if (i >= A_MAX) - continue; /* impossible */ + while ((addition ? (np > 0) : (np < 0)) && tryct < 100) { + int i = rnd_attr(); - if (ABASE(i) >= ATTRMAX(i)) { + if (i >= A_MAX + || (addition ? (ABASE(i) >= ATTRMAX(i)) + : (ABASE(i) <= ATTRMIN(i)))) { tryct++; continue; } tryct = 0; - ABASE(i)++; - AMAX(i)++; - np--; + ABASE(i) += adj; + AMAX(i) += adj; + np -= adj; } + return np; +} - tryct = 0; - while (np < 0 && tryct < 100) { /* for redistribution */ - - x = rn2(100); - for (i = 0; (i < A_MAX) && ((x -= urole.attrdist[i]) > 0); i++) - ; - if (i >= A_MAX) - continue; /* impossible */ +/* allocate hero's initial characteristics */ +void +init_attr(int np) +{ + int i; - if (ABASE(i) <= ATTRMIN(i)) { - tryct++; - continue; - } - tryct = 0; - ABASE(i)--; - AMAX(i)--; - np++; + for (i = 0; i < A_MAX; i++) { + ABASE(i) = AMAX(i) = gu.urole.attrbase[i]; + ATEMP(i) = ATIME(i) = 0; + np -= gu.urole.attrbase[i]; } + + /* distribute leftover points */ + np = init_attr_role_redist(np, TRUE); + /* if we went over, remove points */ + np = init_attr_role_redist(np, FALSE); } void -redist_attr() +redist_attr(void) { - register int i, tmp; + int i, tmp; for (i = 0; i < A_MAX; i++) { if (i == A_INT || i == A_WIS) @@ -680,23 +756,37 @@ redist_attr() if (ABASE(i) < ATTRMIN(i)) ABASE(i) = ATTRMIN(i); } - (void) encumber_msg(); + /* encumber_msg(); -- caller needs to do this */ } -STATIC_OVL +/* apply minor variation to attributes */ void -postadjabil(ability) -long *ability; +vary_init_attr(void) { - if (!ability) + int i; + + for (i = 0; i < A_MAX; i++) + if (!rn2(20)) { + int xd = rn2(7) - 2; /* biased variation */ + + (void) adjattrib(i, xd, TRUE); + if (ABASE(i) < AMAX(i)) + AMAX(i) = ABASE(i); + } +} + +staticfn +void +postadjabil(long *ability) +{ + if (!u.ulevel) /* initializing hero; don't attempt screen update yet */ return; if (ability == &(HWarning) || ability == &(HSee_invisible)) see_monsters(); } -STATIC_OVL const struct innate * -role_abil(r) -int r; +staticfn const struct innate * +role_abil(int r) { const struct { short role; @@ -704,11 +794,11 @@ int r; } roleabils[] = { { PM_ARCHEOLOGIST, arc_abil }, { PM_BARBARIAN, bar_abil }, - { PM_CAVEMAN, cav_abil }, + { PM_CAVE_DWELLER, cav_abil }, { PM_HEALER, hea_abil }, { PM_KNIGHT, kni_abil }, { PM_MONK, mon_abil }, - { PM_PRIEST, pri_abil }, + { PM_CLERIC, pri_abil }, { PM_RANGER, ran_abil }, { PM_ROGUE, rog_abil }, { PM_SAMURAI, sam_abil }, @@ -724,10 +814,8 @@ int r; return roleabils[i].abil; } -STATIC_OVL const struct innate * -check_innate_abil(ability, frommask) -long *ability; -long frommask; +staticfn const struct innate * +check_innate_abil(long *ability, long frommask) { const struct innate *abil = 0; @@ -772,9 +860,8 @@ long frommask; #define FROM_LYCN 6 /* check whether particular ability has been obtained via innate attribute */ -STATIC_OVL int -innately(ability) -long *ability; +staticfn int +innately(long *ability) { const struct innate *iptr; @@ -790,13 +877,12 @@ long *ability; } int -is_innate(propidx) -int propidx; +is_innate(int propidx) { int innateness; /* innately() would report FROM_FORM for this; caller wants specificity */ - if (propidx == DRAIN_RES && u.ulycn >= LOW_PM) + if (propidx == DRAIN_RES && ismnum(u.ulycn)) return FROM_LYCN; if (propidx == FAST && Very_fast) return FROM_NONE; /* can't become very fast innately */ @@ -807,14 +893,17 @@ int propidx; ignore innateness if equipment is going to claim responsibility */ && !u.uprops[propidx].extrinsic) return FROM_ROLE; - if (propidx == BLINDED && !haseyes(youmonst.data)) + if ((propidx == BLINDED && !haseyes(gy.youmonst.data)) + || (propidx == BLND_RES && (HBlnd_resist & FROMFORM) != 0)) return FROM_FORM; return FROM_NONE; } +DISABLE_WARNING_FORMAT_NONLITERAL + char * -from_what(propidx) -int propidx; /* special cases can have negative values */ +from_what( + int propidx) /* special cases can have negative values */ { static char buf[BUFSZ]; @@ -844,7 +933,8 @@ int propidx; /* special cases can have negative values */ * There are exceptions. Versatile jumping from spell or boots * takes priority over knight's innate but limited jumping. */ - if (propidx == BLINDED && u.uroleplay.blind) + if ((propidx == BLINDED && u.uroleplay.blind) + || (propidx == DEAF && u.uroleplay.deaf)) Sprintf(buf, " from birth"); else if (innateness == FROM_ROLE || innateness == FROM_RACE) Strcpy(buf, " innately"); @@ -855,7 +945,7 @@ int propidx; /* special cases can have negative values */ else if (innateness == FROM_LYCN) Strcpy(buf, " due to your lycanthropy"); else if (innateness == FROM_FORM) - Strcpy(buf, " from current creature form"); + Strcpy(buf, " from your creature form"); else if (propidx == FAST && Very_fast) Sprintf(buf, because_of, ((HFast & TIMEOUT) != 0L) ? "a potion or spell" @@ -871,6 +961,11 @@ int propidx; /* special cases can have negative values */ : ysimple_name(obj)); else if (propidx == BLINDED && Blindfolded_only) Sprintf(buf, because_of, ysimple_name(ublindf)); + else if (propidx == BLINDED && u.ucreamed + && BlindedTimeout == (long) u.ucreamed + && !EBlinded && !(HBlinded & ~TIMEOUT)) + Sprintf(buf, "due to goop covering your %s", + body_part(FACE)); /* remove some verbosity and/or redundancy */ if ((p = strstri(buf, " pair of ")) != 0) @@ -884,8 +979,8 @@ int propidx; /* special cases can have negative values */ replace this with what_blocks() comparable to what_gives() */ switch (-propidx) { case BLINDED: - if (ublindf - && ublindf->oartifact == ART_EYES_OF_THE_OVERWORLD) + /* wearing the Eyes of the Overworld overrides blindness */ + if (BBlinded && is_art(ublindf, ART_EYES_OF_THE_OVERWORLD)) Sprintf(buf, because_of, bare_artifactname(ublindf)); break; case INVIS: @@ -905,11 +1000,12 @@ int propidx; /* special cases can have negative values */ return buf; } +RESTORE_WARNING_FORMAT_NONLITERAL + void -adjabil(oldlevel, newlevel) -int oldlevel, newlevel; +adjabil(int oldlevel, int newlevel) { - register const struct innate *abil, *rabil; + const struct innate *abil, *rabil; long prevabil, mask = FROMEXPER; abil = role_abil(Role_switch); @@ -977,37 +1073,40 @@ int oldlevel, newlevel; } } +/* called when gaining a level (before u.ulevel gets incremented); + also called with u.ulevel==0 during hero initialization or for + re-init if hero turns into a "new man/woman/elf/&c" */ int -newhp() +newhp(void) { int hp, conplus; if (u.ulevel == 0) { /* Initialize hit points */ - hp = urole.hpadv.infix + urace.hpadv.infix; - if (urole.hpadv.inrnd > 0) - hp += rnd(urole.hpadv.inrnd); - if (urace.hpadv.inrnd > 0) - hp += rnd(urace.hpadv.inrnd); - if (moves <= 1L) { /* initial hero; skip for polyself to new man */ + hp = gu.urole.hpadv.infix + gu.urace.hpadv.infix; + if (gu.urole.hpadv.inrnd > 0) + hp += rnd(gu.urole.hpadv.inrnd); + if (gu.urace.hpadv.inrnd > 0) + hp += rnd(gu.urace.hpadv.inrnd); + if (svm.moves == 0) { /* initial hero; skip for polyself to new man */ /* Initialize alignment stuff */ u.ualign.type = aligns[flags.initalign].value; - u.ualign.record = urole.initrecord; + u.ualign.record = gu.urole.initrecord; } /* no Con adjustment for initial hit points */ } else { - if (u.ulevel < urole.xlev) { - hp = urole.hpadv.lofix + urace.hpadv.lofix; - if (urole.hpadv.lornd > 0) - hp += rnd(urole.hpadv.lornd); - if (urace.hpadv.lornd > 0) - hp += rnd(urace.hpadv.lornd); + if (u.ulevel < gu.urole.xlev) { + hp = gu.urole.hpadv.lofix + gu.urace.hpadv.lofix; + if (gu.urole.hpadv.lornd > 0) + hp += rnd(gu.urole.hpadv.lornd); + if (gu.urace.hpadv.lornd > 0) + hp += rnd(gu.urace.hpadv.lornd); } else { - hp = urole.hpadv.hifix + urace.hpadv.hifix; - if (urole.hpadv.hirnd > 0) - hp += rnd(urole.hpadv.hirnd); - if (urace.hpadv.hirnd > 0) - hp += rnd(urace.hpadv.hirnd); + hp = gu.urole.hpadv.hifix + gu.urace.hpadv.hifix; + if (gu.urole.hpadv.hirnd > 0) + hp += rnd(gu.urole.hpadv.hirnd); + if (gu.urace.hpadv.hirnd > 0) + hp += rnd(gu.urace.hpadv.hirnd); } if (ACURR(A_CON) <= 3) conplus = -2; @@ -1027,69 +1126,147 @@ newhp() } if (hp <= 0) hp = 1; - if (u.ulevel < MAXULEV) - u.uhpinc[u.ulevel] = (xchar) hp; + if (u.ulevel < MAXULEV) { + /* remember increment; future level drain could take it away again */ + u.uhpinc[u.ulevel] = (xint16) hp; + } else { + /* after level 30, throttle hit point gains from extra experience; + once max reaches 1200, further increments will be just 1 more */ + char lim = 5 - u.uhpmax / 300; + + lim = max(lim, 1); + if (hp > lim) + hp = lim; + } return hp; } -schar -acurr(x) -int x; +/* minimum value for uhpmax is ulevel but for life-saving it is always at + least 10 if ulevel is less than that */ +int +minuhpmax(int altmin) +{ + if (altmin < 1) + altmin = 1; + return max(u.ulevel, altmin); +} + +/* update u.uhpmax or u.mhmax and values of other things that depend upon + whichever of them is relevant */ +void +setuhpmax(int newmax, boolean even_when_polyd) +{ + if (!Upolyd || even_when_polyd) { + if (newmax != u.uhpmax) { + u.uhpmax = newmax; + if (u.uhpmax > u.uhppeak) + u.uhppeak = u.uhpmax; + disp.botl = TRUE; + } + if (u.uhp > u.uhpmax) + u.uhp = u.uhpmax, disp.botl = TRUE; + } else { /* Upolyd */ + if (newmax != u.mhmax) { + u.mhmax = newmax; + disp.botl = TRUE; + } + if (u.mh > u.mhmax) + u.mh = u.mhmax, disp.botl = TRUE; + } +} + +/* called after setuhpmax() when damage is pending; + if uhpmax (or mhmax) has been reduced, it might have caused uhp (or mh) + to be reduced too; if so, recalculate pending loss to account for that */ +int +adjuhploss( + int loss, /* pending hp loss */ + int olduhp) /* does double duty as oldmh when Upolyd */ { - register int tmp = (u.abon.a[x] + u.atemp.a[x] + u.acurr.a[x]); + if (!Upolyd) { + if (u.uhp < olduhp) + loss -= (olduhp - u.uhp); + } else { + if (u.mh < olduhp) + loss -= (olduhp - u.mh); + } + return max(loss, 1); +} - if (x == A_STR) { - if (tmp >= 125 || (uarmg && uarmg->otyp == GAUNTLETS_OF_POWER)) - return (schar) 125; +/* return the current effective value of a specific characteristic + (the 'a' in 'acurr()' comes from outdated use of "attribute" for the + six Str/Dex/&c characteristics; likewise for u.abon, u.atemp, u.acurr) */ +schar +acurr(int chridx) +{ + int tmp, result = 0; /* 'result' will always be reset to positive value */ + + assert(chridx >= 0 && chridx < A_MAX); + tmp = u.abon.a[chridx] + u.atemp.a[chridx] + u.acurr.a[chridx]; + + /* for Strength: 3 <= result <= 125; + for all others: 3 <= result <= 25 */ + if (chridx == A_STR) { + /* strength value is encoded: 3..18 normal, 19..118 for 18/xx (with + 1 <= xx <= 100), and 119..125 for other characteristics' 19..25; + STR18(x) yields 18 + x (intended for 0 <= x <= 100; not used here); + STR19(y) yields 100 + y (intended for 19 <= y <= 25) */ + if (tmp >= STR19(25) || (uarmg && uarmg->otyp == GAUNTLETS_OF_POWER)) + result = STR19(25); /* 125 */ else -#ifdef WIN32_BUG - return (x = ((tmp <= 3) ? 3 : tmp)); -#else - return (schar) ((tmp <= 3) ? 3 : tmp); -#endif - } else if (x == A_CHA) { - if (tmp < 18 - && (youmonst.data->mlet == S_NYMPH || u.umonnum == PM_SUCCUBUS - || u.umonnum == PM_INCUBUS)) - return (schar) 18; - } else if (x == A_CON) { - if (uwep && uwep->oartifact == ART_OGRESMASHER) - return (schar) 25; - } else if (x == A_INT || x == A_WIS) { - /* yes, this may raise int/wis if player is sufficiently - * stupid. there are lower levels of cognition than "dunce". - */ + /* need non-zero here to avoid 'if(result==0)' below because + that doesn't deal with Str encoding; the cap of 25 applied + there would limit Str to 18/07 [18 + 7] */ + result = max(tmp, 3); + } else if (chridx == A_CHA) { + if (tmp < 18 && (gy.youmonst.data->mlet == S_NYMPH + || u.umonnum == PM_AMOROUS_DEMON)) + result = 18; + } else if (chridx == A_CON) { + if (u_wield_art(ART_OGRESMASHER)) + result = 25; + } else if (chridx == A_INT || chridx == A_WIS) { + /* Yes, this may raise Int and/or Wis if hero is sufficiently + stupid. There are lower levels of cognition than "dunce". */ if (uarmh && uarmh->otyp == DUNCE_CAP) - return (schar) 6; + result = 6; + } else if (chridx == A_DEX) { + ; /* there aren't any special cases for dexterity */ } -#ifdef WIN32_BUG - return (x = ((tmp >= 25) ? 25 : (tmp <= 3) ? 3 : tmp)); -#else - return (schar) ((tmp >= 25) ? 25 : (tmp <= 3) ? 3 : tmp); -#endif + + if (result == 0) /* none of the special cases applied */ + result = (tmp >= 25) ? 25 : (tmp <= 3) ? 3 : tmp; + + return (schar) result; } -/* condense clumsy ACURR(A_STR) value into value that fits into game formulas - */ +/* condense clumsy ACURR(A_STR) value into value that fits into formulas */ schar -acurrstr() +acurrstr(void) { - register int str = ACURR(A_STR); - - if (str <= 18) - return (schar) str; - if (str <= 121) - return (schar) (19 + str / 50); /* map to 19..21 */ - else - return (schar) (min(str, 125) - 100); /* 22..25 */ + int str = ACURR(A_STR), /* 3..125 after massaging by acurr() */ + result; /* 3..25 */ + + if (str <= STR18(0)) /* <= 18; max(,3) here is redundant */ + result = max(str, 3); /* 3..18 */ + else if (str <= STR19(21)) /* <= 121 */ + /* this converts + 18/01..18/31 into 19, + 18/32..18/81 into 20, + 18/82..18/100 and 19..21 into 21 */ + result = 19 + str / 50; /* map to 19..21 */ + else /* convert 122..125; min(,125) here is redundant */ + result = min(str, 125) - 100; /* 22..25 */ + + return (schar) result; } /* when wearing (or taking off) an unID'd item, this routine is used to distinguish between observable +0 result and no-visible-effect due to an attribute not being able to exceed maximum or minimum */ boolean -extremeattr(attrindx) /* does attrindx's value match its max or min? */ -int attrindx; +extremeattr( + int attrindx) /* does attrindx's value match its max or min? */ { /* Fixed_abil and racial MINATTR/MAXATTR aren't relevant here */ int lolimit = 3, hilimit = 25, curval = ACURR(attrindx); @@ -1101,7 +1278,7 @@ int attrindx; if (uarmg && uarmg->otyp == GAUNTLETS_OF_POWER) lolimit = hilimit; } else if (attrindx == A_CON) { - if (uwep && uwep->oartifact == ART_OGRESMASHER) + if (u_wield_art(ART_OGRESMASHER)) lolimit = hilimit; } /* this exception is hypothetical; the only other worn item affecting @@ -1118,35 +1295,42 @@ int attrindx; /* avoid possible problems with alignment overflow, and provide a centralized location for any future alignment limits */ void -adjalign(n) -int n; +adjalign(int n) { int newalign = u.ualign.record + n; if (n < 0) { + unsigned newabuse = u.ualign.abuse - n; + if (newalign < u.ualign.record) u.ualign.record = newalign; + if (newabuse > u.ualign.abuse) { + u.ualign.abuse = newabuse; + adj_erinys(newabuse); + } } else if (newalign > u.ualign.record) { u.ualign.record = newalign; if (u.ualign.record > ALIGNLIM) - u.ualign.record = ALIGNLIM; + u.ualign.record = (int)ALIGNLIM; } } /* change hero's alignment type, possibly losing use of artifacts */ void -uchangealign(newalign, reason) -int newalign; -int reason; /* 0==conversion, 1==helm-of-OA on, 2==helm-of-OA off */ +uchangealign( + int newalign, + int reason) /* A_CG_CONVERT, A_CG_HELM_ON, or A_CG_HELM_OFF */ { aligntyp oldalign = u.ualign.type; u.ublessed = 0; /* lose divine protection */ /* You/Your/pline message with call flush_screen(), triggering bot(), so the actual data change needs to come before the message */ - context.botl = TRUE; /* status line needs updating */ - if (reason == 0) { + disp.botl = TRUE; /* status line needs updating */ + if (reason == A_CG_CONVERT) { /* conversion via altar */ + livelog_printf(LL_ALIGNMENT, "permanently converted to %s", + aligns[1 - newalign].adj); u.ualignbase[A_CURRENT] = (aligntyp) newalign; /* worn helm of opposite alignment might block change */ if (!uarmh || uarmh->otyp != HELM_OF_OPPOSITE_ALIGNMENT) @@ -1156,12 +1340,20 @@ int reason; /* 0==conversion, 1==helm-of-OA on, 2==helm-of-OA off */ } else { /* putting on or taking off a helm of opposite alignment */ u.ualign.type = (aligntyp) newalign; - if (reason == 1) + if (reason == A_CG_HELM_ON) { + adjalign(-7); /* for abuse -- record will be cleared shortly */ Your("mind oscillates %s.", Hallucination ? "wildly" : "briefly"); - else if (reason == 2) + make_confused(rn1(2, 3), FALSE); + if (Is_astralevel(&u.uz) || ((unsigned) rn2(50) < u.ualign.abuse)) + summon_furies(Is_astralevel(&u.uz) ? 0 : 1); + /* don't livelog taking it back off */ + livelog_printf(LL_ALIGNMENT, "used a helm to turn %s", + aligns[1 - newalign].adj); + } else if (reason == A_CG_HELM_OFF) { Your("mind is %s.", Hallucination ? "much of a muchness" : "back in sync with your body"); + } } if (u.ualign.type != oldalign) { u.ualign.record = 0; /* slate is wiped clean */ diff --git a/src/ball.c b/src/ball.c index c02e6d2f5..b622cb691 100644 --- a/src/ball.c +++ b/src/ball.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 ball.c $NHDT-Date: 1573940835 2019/11/16 21:47:15 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.44 $ */ +/* NetHack 5.0 ball.c $NHDT-Date: 1596498150 2020/08/03 23:42:30 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.51 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) David Cohrs, 2006. */ /* NetHack may be freely redistributed. See license for details. */ @@ -8,11 +8,11 @@ #include "hack.h" -STATIC_DCL int NDECL(bc_order); -STATIC_DCL void NDECL(litter); -STATIC_OVL void NDECL(placebc_core); -STATIC_OVL void NDECL(unplacebc_core); -STATIC_DCL boolean FDECL(check_restriction, (int)); +staticfn int bc_order(void); +staticfn void litter(void); +staticfn void placebc_core(void); +staticfn void unplacebc_core(void); +staticfn boolean check_restriction(int); static int bcrestriction = 0; #ifdef BREADCRUMBS @@ -20,10 +20,9 @@ static struct breadcrumbs bcpbreadcrumbs = {0}, bcubreadcrumbs = {0}; #endif void -ballrelease(showmsg) -boolean showmsg; +ballrelease(boolean showmsg) { - if (carried(uball)) { + if (carried(uball) && !welded(uball)) { if (showmsg) pline("Startled, you drop the iron ball."); if (uwep == uball) @@ -41,10 +40,13 @@ boolean showmsg; /* ball&chain might hit hero when falling through a trap door */ void -ballfall() +ballfall(void) { boolean gets_hit; + if (!uball || (uball && carried(uball) && welded(uball))) + return; + gets_hit = (((uball->ox != u.ux) || (uball->oy != u.uy)) && ((uwep == uball) ? FALSE : (boolean) rn2(5))); ballrelease(TRUE); @@ -53,7 +55,7 @@ ballfall() pline_The("iron ball falls on your %s.", body_part(HEAD)); if (uarmh) { - if (is_metallic(uarmh)) { + if (hard_helmet(uarmh)) { pline("Fortunately, you are wearing a hard helmet."); dmg = 3; } else if (flags.verbose) @@ -114,8 +116,8 @@ ballfall() * * Should not be called while swallowed except on waterlevel. */ -STATIC_OVL void -placebc_core() +staticfn void +placebc_core(void) { if (!uchain || !uball) { impossible("Where are your ball and chain?"); @@ -141,8 +143,8 @@ placebc_core() bcrestriction = 0; } -STATIC_OVL void -unplacebc_core() +staticfn void +unplacebc_core(void) { if (u.uswallow) { if (Is_waterlevel(&u.uz)) { @@ -162,20 +164,20 @@ unplacebc_core() obj_extract_self(uball); if (Blind && (u.bc_felt & BC_BALL)) /* drop glyph */ levl[uball->ox][uball->oy].glyph = u.bglyph; - + maybe_unhide_at(uball->ox, uball->oy); newsym(uball->ox, uball->oy); } obj_extract_self(uchain); if (Blind && (u.bc_felt & BC_CHAIN)) /* drop glyph */ levl[uchain->ox][uchain->oy].glyph = u.cglyph; + maybe_unhide_at(uchain->ox, uchain->oy); newsym(uchain->ox, uchain->oy); u.bc_felt = 0; /* feel nothing */ } -STATIC_OVL boolean -check_restriction(restriction) -int restriction; +staticfn boolean +check_restriction(int restriction) { boolean ret = FALSE; @@ -188,7 +190,7 @@ int restriction; #ifndef BREADCRUMBS void -placebc() +placebc(void) { if (!check_restriction(0)) { #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) @@ -207,7 +209,7 @@ placebc() } void -unplacebc() +unplacebc(void) { if (bcrestriction) { impossible("unplacebc denied, restriction in place"); @@ -217,7 +219,7 @@ unplacebc() } int -unplacebc_and_covet_placebc() +unplacebc_and_covet_placebc(void) { int restriction = 0; @@ -231,8 +233,7 @@ unplacebc_and_covet_placebc() } void -lift_covet_and_placebc(pin) -int pin; +lift_covet_and_placebc(int pin) { if (!check_restriction(pin)) { #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) @@ -255,9 +256,7 @@ int pin; #else /* BREADCRUMBS */ void -Placebc(funcnm, linenum) -const char *funcnm; -int linenum; +Placebc(const char *funcnm, int linenum) { if (!check_restriction(0)) { #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) @@ -285,9 +284,7 @@ int linenum; } void -Unplacebc(funcnm, linenum) -const char *funcnm; -int linenum; +Unplacebc(const char *funcnm, int linenum) { if (bcrestriction) { @@ -306,9 +303,7 @@ int linenum; } int -Unplacebc_and_covet_placebc(funcnm, linenum) -const char *funcnm; -int linenum; +Unplacebc_and_covet_placebc(const char *funcnm, int linenum) { int restriction = 0; @@ -329,10 +324,7 @@ int linenum; } void -Lift_covet_and_placebc(pin, funcnm, linenum) -int pin; -char *funcnm; -int linenum; +Lift_covet_and_placebc(int pin, char *funcnm, int linenum) { if (!check_restriction(pin)) { #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) @@ -358,8 +350,8 @@ int linenum; * Return the stacking of the hero's ball & chain. This assumes that the * hero is being punished. */ -STATIC_OVL int -bc_order() +staticfn int +bc_order(void) { struct obj *obj; @@ -367,7 +359,7 @@ bc_order() || u.uswallow) return BCPOS_DIFFER; - for (obj = level.objects[uball->ox][uball->oy]; obj; + for (obj = svl.level.objects[uball->ox][uball->oy]; obj; obj = obj->nexthere) { if (obj == uchain) return BCPOS_CHAIN; @@ -385,8 +377,7 @@ bc_order() * Set up the ball and chain variables so that the ball and chain are "felt". */ void -set_bc(already_blind) -int already_blind; +set_bc(int already_blind) { int ball_on_floor = !carried(uball); @@ -443,9 +434,8 @@ int already_blind; * Should not be called while swallowed. */ void -move_bc(before, control, ballx, bally, chainx, chainy) -int before, control; -xchar ballx, bally, chainx, chainy; /* only matter !before */ +move_bc(int before, int control, coordxy ballx, coordxy bally, + coordxy chainx, coordxy chainy) { if (Blind) { /* @@ -536,9 +526,11 @@ xchar ballx, bally, chainx, chainy; /* only matter !before */ } remove_object(uchain); + maybe_unhide_at(uchain->ox, uchain->oy); newsym(uchain->ox, uchain->oy); if (!carried(uball)) { remove_object(uball); + maybe_unhide_at(uball->ox, uball->oy); newsym(uball->ox, uball->oy); } } else { @@ -565,13 +557,9 @@ xchar ballx, bally, chainx, chainy; /* only matter !before */ /* return TRUE if the caller needs to place the ball and chain down again */ boolean -drag_ball(x, y, bc_control, ballx, bally, chainx, chainy, cause_delay, - allow_drag) -xchar x, y; -int *bc_control; -xchar *ballx, *bally, *chainx, *chainy; -boolean *cause_delay; -boolean allow_drag; +drag_ball(coordxy x, coordxy y, int *bc_control, + coordxy *ballx, coordxy *bally, coordxy *chainx, coordxy *chainy, + boolean *cause_delay, boolean allow_drag) { struct trap *t = (struct trap *) 0; boolean already_in_rock; @@ -604,7 +592,7 @@ boolean allow_drag; /* only need to move the chain? */ if (carried(uball) || distmin(x, y, uball->ox, uball->oy) <= 2) { - xchar oldchainx = uchain->ox, oldchainy = uchain->oy; + coordxy oldchainx = uchain->ox, oldchainy = uchain->oy; *bc_control = BC_CHAIN; move_bc(1, *bc_control, *ballx, *bally, *chainx, *chainy); @@ -621,7 +609,7 @@ boolean allow_drag; (distmin(x, y, chx, chy) <= 1 \ && distmin(chx, chy, uball->ox, uball->oy) <= 1) #define IS_CHAIN_ROCK(x, y) \ - (IS_ROCK(levl[x][y].typ) \ + (IS_OBSTRUCTED(levl[x][y].typ) \ || (IS_DOOR(levl[x][y].typ) \ && (levl[x][y].doormask & (D_CLOSED | D_LOCKED)))) /* @@ -646,7 +634,7 @@ boolean allow_drag; already_in_rock = FALSE; switch (dist2(x, y, uball->ox, uball->oy)) { - /* two spaces diagonal from ball, move chain inbetween */ + /* two spaces diagonal from ball, move chain in-between */ case 8: *chainx = (uball->ox + x) / 2; *chainy = (uball->oy + y) / 2; @@ -661,7 +649,7 @@ boolean allow_drag; * 0 */ case 5: { - xchar tempx, tempy, tempx2, tempy2; + coordxy tempx, tempy, tempx2, tempy2; /* find position closest to current position of chain; no effect if current position is already OK */ @@ -726,7 +714,7 @@ boolean allow_drag; } /* ball is two spaces horizontal or vertical from player; move*/ - /* chain inbetween *unless* current chain position is OK */ + /* chain in-between *unless* current chain position is OK */ case 4: if (CHAIN_IN_MIDDLE(uchain->ox, uchain->oy)) break; @@ -755,7 +743,8 @@ boolean allow_drag; SKIP_TO_DRAG; break; } - /* fall through */ + FALLTHROUGH; + /* FALLTHRU */ case 1: case 0: /* do nothing if possible */ @@ -786,7 +775,7 @@ boolean allow_drag; if (near_capacity() > SLT_ENCUMBER && dist2(x, y, u.ux, u.uy) <= 2) { You("cannot %sdrag the heavy iron ball.", - invent ? "carry all that and also " : ""); + gi.invent ? "carry all that and also " : ""); nomul(0); return FALSE; } @@ -850,7 +839,7 @@ boolean allow_drag; *ballx = *chainx = x; *bally = *chainy = y; } else { - xchar newchainx = u.ux, newchainy = u.uy; + coordxy newchainx = u.ux, newchainy = u.uy; /* * Generally, chain moves to hero's previous location and ball @@ -890,8 +879,7 @@ boolean allow_drag; * Should not be called while swallowed. */ void -drop_ball(x, y) -xchar x, y; +drop_ball(coordxy x, coordxy y) { if (Blind) { /* get the order */ @@ -901,7 +889,7 @@ xchar x, y; } if (x != u.ux || y != u.uy) { - static const char *pullmsg = "The ball pulls you out of the %s!"; + static const char pullmsg[] = "The ball pulls you out of the "; struct trap *t; long side; @@ -909,19 +897,20 @@ xchar x, y; && u.utraptype != TT_INFLOOR && u.utraptype != TT_BURIEDBALL) { switch (u.utraptype) { case TT_PIT: - pline(pullmsg, "pit"); + pline("%s%s!", pullmsg, "pit"); break; case TT_WEB: - pline(pullmsg, "web"); + pline("%s%s!", pullmsg, "web"); + Soundeffect(se_destroy_web, 30); pline_The("web is destroyed!"); deltrap(t_at(u.ux, u.uy)); break; case TT_LAVA: - pline(pullmsg, hliquid("lava")); + pline("%s%s!", pullmsg, hliquid("lava")); break; case TT_BEARTRAP: side = rn2(3) ? LEFT_SIDE : RIGHT_SIDE; - pline(pullmsg, "bear trap"); + pline("%s%s!", pullmsg, "bear trap"); set_wounded_legs(side, rn1(1000, 500)); if (!u.usteed) { Your("%s %s is severely damaged.", @@ -950,7 +939,7 @@ xchar x, y; u.ux = x - u.dx; u.uy = y - u.dy; } - vision_full_recalc = 1; /* hero has moved, recalculate vision later */ + gv.vision_full_recalc = 1; /* hero has moved, recalc vision later */ if (Blind) { /* drop glyph under the chain */ @@ -967,34 +956,34 @@ xchar x, y; newsym(u.ux0, u.uy0); /* clean up old position */ if (u.ux0 != u.ux || u.uy0 != u.uy) { spoteffects(TRUE); - sokoban_guilt(); } } } /* ball&chain cause hero to randomly lose stuff from inventory */ -STATIC_OVL void -litter() +staticfn void +litter(void) { struct obj *otmp, *nextobj = 0; int capacity = weight_cap(); - for (otmp = invent; otmp; otmp = nextobj) { + for (otmp = gi.invent; otmp; otmp = nextobj) { nextobj = otmp->nobj; - if ((otmp != uball) && (rnd(capacity) <= (int) otmp->owt)) { + if (otmp != uball && rnd(capacity) <= (int) otmp->owt) { if (canletgo(otmp, "")) { You("drop %s and %s %s down the stairs with you.", yname(otmp), (otmp->quan == 1L) ? "it" : "they", otense(otmp, "fall")); - dropx(otmp); - encumber_msg(); /* drop[xyz]() probably ought to to this... */ + setnotworn(otmp); + freeinv(otmp); + hitfloor(otmp, FALSE); } } } } void -drag_down() +drag_down(void) { boolean forward; uchar dragchance = 3; @@ -1009,7 +998,7 @@ drag_down() */ forward = carried(uball) && (uwep == uball || !uwep || !rn2(3)); - if (carried(uball)) + if (carried(uball) && !welded(uball)) You("lose your grip on the iron ball."); cls(); /* previous level is still displayed although you @@ -1024,6 +1013,7 @@ drag_down() } } else { if (rn2(2)) { + Soundeffect(se_iron_ball_hits_you, 25); pline_The("iron ball smacks into you!"); losehp(Maybe_Half_Phys(rnd(20)), "iron ball collision", KILLED_BY_AN); @@ -1041,7 +1031,7 @@ drag_down() } void -bc_sanity_check() +bc_sanity_check(void) { int otyp, freeball, freechain; const char *onam; diff --git a/src/bones.c b/src/bones.c index da0de0f41..d7837536a 100644 --- a/src/bones.c +++ b/src/bones.c @@ -1,33 +1,29 @@ -/* NetHack 3.6 bones.c $NHDT-Date: 1571363147 2019/10/18 01:45:47 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.76 $ */ +/* NetHack 5.0 bones.c $NHDT-Date: 1701500709 2023/12/02 07:05:09 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.129 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985,1993. */ /*-Copyright (c) Robert Patrick Rankin, 2012. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -#include "lev.h" -extern char bones[]; /* from files.c */ -#ifdef MFLOPPY -extern long bytes_counted; -#endif - -STATIC_DCL boolean FDECL(no_bones_level, (d_level *)); -STATIC_DCL void FDECL(goodfruit, (int)); -STATIC_DCL void FDECL(resetobjs, (struct obj *, BOOLEAN_P)); -STATIC_DCL boolean FDECL(fixuporacle, (struct monst *)); - -STATIC_OVL boolean -no_bones_level(lev) -d_level *lev; +#ifndef SFCTOOL +staticfn boolean no_bones_level(d_level *); +staticfn void goodfruit(int); +staticfn void resetobjs(struct obj *, boolean); +staticfn void give_to_nearby_mon(struct obj *, coordxy, coordxy) NONNULLARG1; +staticfn boolean fixuporacle(struct monst *) NONNULLARG1; +staticfn void remove_mon_from_bones(struct monst *) NONNULLARG1; +staticfn void set_ghostly_objlist(struct obj *objchain); + +staticfn boolean +no_bones_level(d_level *lev) { - extern d_level save_dlevel; /* in do.c */ s_level *sptr; - if (ledger_no(&save_dlevel)) - assign_level(lev, &save_dlevel); + if (ledger_no(&gs.save_dlevel)) + assign_level(lev, &gs.save_dlevel); return (boolean) (((sptr = Is_special(lev)) != 0 && !sptr->boneid) - || !dungeons[lev->dnum].boneid + || !svd.dungeons[lev->dnum].boneid /* no bones on the last or multiway branch levels in any dungeon (level 1 isn't multiway) */ || Is_botlevel(lev) @@ -42,9 +38,8 @@ d_level *lev; * ID is positive instead of negative). This way, when we later save the * chain of fruit types, we know to only save the types that exist. */ -STATIC_OVL void -goodfruit(id) -int id; +staticfn void +goodfruit(int id) { struct fruit *f = fruit_from_indx(-id); @@ -52,10 +47,8 @@ int id; f->fid = id; } -STATIC_OVL void -resetobjs(ochain, restore) -struct obj *ochain; -boolean restore; +staticfn void +resetobjs(struct obj *ochain, boolean restore) { struct obj *otmp, *nobj; @@ -80,7 +73,8 @@ boolean restore; if (has_oname(otmp)) free_oname(otmp); } else { - artifact_exists(otmp, safe_oname(otmp), TRUE); + artifact_exists(otmp, safe_oname(otmp), TRUE, + ONAME_BONES); } } else if (has_oname(otmp)) { sanitize_name(ONAME(otmp)); @@ -90,7 +84,7 @@ boolean restore; if (otmp->oclass == FOOD_CLASS && otmp->oeaten) { struct obj *top; char *p; - xchar ox, oy; + coordxy ox, oy; for (top = otmp; top->where == OBJ_CONTAINED; top = top->ocontainer) @@ -101,7 +95,7 @@ boolean restore; result depends upon hero's location */ && inside_shop(ox, oy) && *(p = in_rooms(ox, oy, SHOPBASE)) - && tended_shop(&rooms[*p - ROOMOFFSET])); + && tended_shop(&svr.rooms[*p - ROOMOFFSET])); } } else { /* saving */ /* do not zero out o_ids for ghost levels anymore */ @@ -112,9 +106,10 @@ boolean restore; otmp->rknown = 0; otmp->lknown = 0; otmp->cknown = 0; + otmp->tknown = 0; otmp->invlet = 0; otmp->no_charge = 0; - otmp->was_thrown = 0; + otmp->how_lost = LOST_NONE; /* strip user-supplied names */ /* Statue and some corpse names are left intact, @@ -135,7 +130,7 @@ boolean restore; if (otmp->otyp == SLIME_MOLD) { goodfruit(otmp->spe); -#ifdef MAIL +#ifdef MAIL_STRUCTURES } else if (otmp->otyp == SCR_MAIL) { /* 0: delivered in-game via external event; 1: from bones or wishing; 2: written with marker */ @@ -146,7 +141,7 @@ boolean restore; otmp->spe = 0; /* not "laid by you" in next game */ } else if (otmp->otyp == TIN) { /* make tins of unique monster's meat be empty */ - if (otmp->corpsenm >= LOW_PM + if (ismnum(otmp->corpsenm) && unique_corpstat(&mons[otmp->corpsenm])) otmp->corpsenm = NON_PM; } else if (otmp->otyp == CORPSE || otmp->otyp == STATUE) { @@ -168,14 +163,10 @@ boolean restore; if (mnum == PM_DOPPELGANGER && otmp->otyp == CORPSE) set_corpsenm(otmp, mnum); } - } else if ((otmp->otyp == iflags.mines_prize_type - && !Is_mineend_level(&u.uz)) - || ((otmp->otyp == iflags.soko_prize_type1 - || otmp->otyp == iflags.soko_prize_type2) - && !Is_sokoend_level(&u.uz))) { - /* "special prize" in this game becomes ordinary object - if loaded into another game */ - otmp->record_achieve_special = NON_PM; + } else if (is_mines_prize(otmp) || is_soko_prize(otmp)) { + /* achievement tracking; in case prize was moved off its + original level (which is always a no-bones level) */ + otmp->nomerge = 0; } else if (otmp->otyp == AMULET_OF_YENDOR) { /* no longer the real Amulet */ otmp->otyp = FAKE_AMULET_OF_YENDOR; @@ -204,11 +195,10 @@ boolean restore; /* while loading bones, strip out text possibly supplied by old player that might accidentally or maliciously disrupt new player's display */ void -sanitize_name(namebuf) -char *namebuf; +sanitize_name(char *namebuf) { int c; - boolean strip_8th_bit = (WINDOWPORT("tty") + boolean strip_8th_bit = (WINDOWPORT(tty) && !iflags.wc_eight_bit_input); /* it's tempting to skip this for single-user platforms, since @@ -229,17 +219,59 @@ char *namebuf; } } +/* Give object to a random object-liking monster on or adjacent to x,y + but skipping hero's location. + If no such monster, place object on floor at x,y. */ +staticfn void +give_to_nearby_mon(struct obj *otmp, coordxy x, coordxy y) +{ + struct monst *mtmp; + struct monst *selected = (struct monst *) 0; + int nmon = 0, xx, yy; + + for (xx = x - 1; xx <= x + 1; ++xx) { + for (yy = y - 1; yy <= y + 1; ++yy) { + if (!isok(xx, yy)) + continue; + if (u_at(xx, yy)) + continue; + if (!(mtmp = m_at(xx, yy))) + continue; + /* This doesn't do any checks on otmp to see that it matches the + * likes_* property, intentionally. Assume that the monster is + * rifling through and taking things that look interesting. */ + if (!(likes_gold(mtmp->data) || likes_gems(mtmp->data) + || likes_objs(mtmp->data) || likes_magic(mtmp->data))) + continue; + nmon++; + if (!rn2(nmon)) + selected = mtmp; + } + } + if (selected && can_carry(selected, otmp)) + add_to_minv(selected, otmp); + else + place_object(otmp, x, y); +} + /* called by savebones(); also by finish_paybill(shk.c) */ void -drop_upon_death(mtmp, cont, x, y) -struct monst *mtmp; /* monster if hero turned into one (other than ghost) */ -struct obj *cont; /* container if hero is turned into a statue */ -int x, y; +drop_upon_death( + struct monst *mtmp, /* monster if hero rises as one (non ghost) */ + struct obj *cont, /* container if hero is turned into a statue */ + coordxy x, coordxy y) { struct obj *otmp; - u.twoweap = 0; /* ensure curse() won't cause swapwep to drop twice */ - while ((otmp = invent) != 0) { + /* when dual-wielding, the second weapon gets dropped rather than + welded if it becomes cursed; ensure that that won't happen here + by ending dual-wield */ + u.twoweap = FALSE; /* bypass set_twoweap() */ + + /* all inventory is dropped (for the normal case), even non-droppable + things like worn armor and accessories, welded weapon, or cursed + loadstones */ + while ((otmp = gi.invent) != 0) { obj_extract_self(otmp); /* when turning into green slime, all gear remains held; other types "arise from the dead" do aren't holding @@ -247,10 +279,10 @@ int x, y; if (!mtmp || is_undead(mtmp->data)) obj_no_longer_held(otmp); - otmp->owornmask = 0L; /* lamps don't go out when dropped */ if ((cont || artifact_light(otmp)) && obj_is_burning(otmp)) end_burn(otmp, TRUE); /* smother in statue */ + otmp->owornmask = 0L; if (otmp->otyp == SLIME_MOLD) goodfruit(otmp->spe); @@ -261,6 +293,8 @@ int x, y; (void) add_to_minv(mtmp, otmp); else if (cont) (void) add_to_container(cont, otmp); + else if (!rn2(8)) + give_to_nearby_mon(otmp, x, y); else place_object(otmp, x, y); } @@ -270,9 +304,8 @@ int x, y; /* possibly restore oracle's room and/or put her back inside it; returns False if she's on the wrong level and should be removed, True otherwise */ -STATIC_OVL boolean -fixuporacle(oracle) -struct monst *oracle; +staticfn boolean +fixuporacle(struct monst *oracle) { coord cc; int ridx, o_ridx; @@ -284,9 +317,9 @@ struct monst *oracle; if (!Is_oracle_level(&u.uz)) return FALSE; - oracle->mpeaceful = 1; + oracle->mpeaceful = 1; /* for behavior toward next character */ o_ridx = levl[oracle->mx][oracle->my].roomno - ROOMOFFSET; - if (o_ridx >= 0 && rooms[o_ridx].rtype == DELPHI) + if (o_ridx >= 0 && svr.rooms[o_ridx].rtype == DELPHI) return TRUE; /* no fixup needed */ /* @@ -297,14 +330,14 @@ struct monst *oracle; */ /* find original delphi chamber; should always succeed */ - for (ridx = 0; ridx < SIZE(rooms); ++ridx) - if (rooms[ridx].orig_rtype == DELPHI) + for (ridx = 0; ridx < SIZE(svr.rooms); ++ridx) + if (svr.rooms[ridx].orig_rtype == DELPHI) break; - if (o_ridx != ridx && ridx < SIZE(rooms)) { - /* room found and she's not not in it, so try to move her there */ - cc.x = (rooms[ridx].lx + rooms[ridx].hx) / 2; - cc.y = (rooms[ridx].ly + rooms[ridx].hy) / 2; + if (o_ridx != ridx && ridx < SIZE(svr.rooms)) { + /* room found and she's not in it, so try to move her there */ + cc.x = (svr.rooms[ridx].lx + svr.rooms[ridx].hx) / 2; + cc.y = (svr.rooms[ridx].ly + svr.rooms[ridx].hy) / 2; if (enexto(&cc, cc.x, cc.y, oracle->data)) { rloc_to(oracle, cc.x, cc.y); o_ridx = levl[oracle->mx][oracle->my].roomno - ROOMOFFSET; @@ -314,15 +347,15 @@ struct monst *oracle; same as used to happen before this fixup was introduced] */ } if (ridx == o_ridx) /* if she's in her room, mark it as such */ - rooms[ridx].rtype = DELPHI; + svr.rooms[ridx].rtype = DELPHI; return TRUE; /* keep oracle in new bones file */ } /* check whether bones are feasible */ boolean -can_make_bones() +can_make_bones(void) { - register struct trap *ttmp; + struct trap *ttmp; if (!flags.bones) return FALSE; @@ -335,7 +368,7 @@ can_make_bones() } if (!Is_branchlev(&u.uz)) { /* no bones on non-branches with portals */ - for (ttmp = ftrap; ttmp; ttmp = ttmp->ntrap) + for (ttmp = gf.ftrap; ttmp; ttmp = ttmp->ntrap) if (ttmp->ttyp == MAGIC_PORTAL) return FALSE; } @@ -351,30 +384,41 @@ can_make_bones() return TRUE; } +/* monster might need to be removed before saving a bones file, + in case these characters are not in their home bases */ +staticfn void +remove_mon_from_bones(struct monst *mtmp) +{ + struct permonst *mptr = mtmp->data; + + if (mtmp->iswiz || mptr == &mons[PM_MEDUSA] + || mptr->msound == MS_NEMESIS || mptr->msound == MS_LEADER + || is_Vlad(mtmp) /* mptr == &mons[VLAD_THE_IMPALER] || cham == VLAD */ + || (mptr == &mons[PM_ORACLE] && !fixuporacle(mtmp))) + mongone(mtmp); +} + /* save bones and possessions of a deceased adventurer */ void -savebones(how, when, corpse) -int how; -time_t when; -struct obj *corpse; +savebones(int how, time_t when, struct obj *corpse) { - int fd, x, y; + coordxy x, y; struct trap *ttmp; struct monst *mtmp; - struct permonst *mptr; struct fruit *f; struct cemetery *newbones; char c, *bonesid; char whynot[BUFSZ]; + NHFILE *nhfp; /* caller has already checked `can_make_bones()' */ clear_bypasses(); - fd = open_bonesfile(&u.uz, &bonesid); - if (fd >= 0) { - (void) nhclose(fd); + nhfp = open_bonesfile(&u.uz, &bonesid); + if (nhfp) { + close_nhfile(nhfp); if (wizard) { - if (yn("Bones file already exists. Replace it?") == 'y') { + if (y_n("Bones file already exists. Replace it?") == 'y') { if (delete_bonesfile(&u.uz)) goto make_bones; else @@ -389,97 +433,129 @@ struct obj *corpse; make_bones: unleash_all(); - /* in case these characters are not in their home bases */ - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) - continue; - mptr = mtmp->data; - if (mtmp->iswiz || mptr == &mons[PM_MEDUSA] - || mptr->msound == MS_NEMESIS || mptr->msound == MS_LEADER - || mptr == &mons[PM_VLAD_THE_IMPALER] - || (mptr == &mons[PM_ORACLE] && !fixuporacle(mtmp))) - mongone(mtmp); - } + /* new ghost or other undead isn't punished even if hero was; + end-of-game disclosure has already had a chance to report the + Punished status so we don't need to preserve it any further */ + if (Punished) + unpunish(); /* unwear uball, destroy uchain */ + /* in case dismounting kills steed [is that even possible?], do so + before cleaning up dead monsters */ if (u.usteed) dismount_steed(DISMOUNT_BONES); - dmonsfree(); /* discard dead or gone monsters */ - /* mark all fruits as nonexistent; when we come to them we'll mark - * them as existing (using goodfruit()) - */ - for (f = ffruit; f; f = f->nextf) - f->fid = -f->fid; + iter_mons(remove_mon_from_bones); /* send various unique monsters away, */ + dmonsfree(); /* then discard dead or gone monsters */ - /* check iron balls separately--maybe they're not carrying it */ - if (uball) - uball->owornmask = uchain->owornmask = 0L; + forget_engravings(); /* next hero won't have read any engravings yet */ + /* mark all named fruits as nonexistent; if/when we come to instances + of any of them we'll mark those as existing (using goodfruit()) */ + for (f = gf.ffruit; f; f = f->nextf) + f->fid = -f->fid; + set_ghostly_objlist(gi.invent); /* dispose of your possessions, usually cursed */ - if (u.ugrave_arise == (NON_PM - 1)) { + if (ismnum(u.ugrave_arise)) { + /* give your possessions to the monster you become */ + gi.in_mklev = TRUE; /* use as-is */ + mtmp = makemon(&mons[u.ugrave_arise], u.ux, u.uy, NO_MINVENT); + gi.in_mklev = FALSE; + if (!mtmp) { /* arise-type might have been genocided */ + drop_upon_death((struct monst *) 0, (struct obj *) 0, u.ux, u.uy); + u.ugrave_arise = NON_PM; /* in case caller cares */ + return; + } + give_u_to_m_resistances(mtmp); + mtmp = christen_monst(mtmp, svp.plname); + newsym(u.ux, u.uy); + /* ["Your body rises from the dead as an ..." used + to be given here, but it has been moved to done() so that + it gets delivered even when savebones() isn't called] */ + drop_upon_death(mtmp, (struct obj *) 0, u.ux, u.uy); + /* 'mtmp' now has hero's inventory; if 'mtmp' is a mummy, give it + a wrapping unless already carrying one */ + if (mtmp->data->mlet == S_MUMMY && !m_carrying(mtmp, MUMMY_WRAPPING)) + (void) mongets(mtmp, MUMMY_WRAPPING); + m_dowear(mtmp, TRUE); + } else if (u.ugrave_arise == LEAVESTATUE) { struct obj *otmp; /* embed your possessions in your statue */ - otmp = mk_named_object(STATUE, &mons[u.umonnum], u.ux, u.uy, plname); + otmp = mk_named_object(STATUE, &mons[u.umonnum], u.ux, u.uy, + svp.plname); drop_upon_death((struct monst *) 0, otmp, u.ux, u.uy); if (!otmp) return; /* couldn't make statue */ mtmp = (struct monst *) 0; - } else if (u.ugrave_arise < LOW_PM) { + } else { /* u.ugrave_arise < LEAVESTATUE */ /* drop everything */ drop_upon_death((struct monst *) 0, (struct obj *) 0, u.ux, u.uy); /* trick makemon() into allowing monster creation * on your location */ - in_mklev = TRUE; + gi.in_mklev = TRUE; mtmp = makemon(&mons[PM_GHOST], u.ux, u.uy, MM_NONAME); - in_mklev = FALSE; + gi.in_mklev = FALSE; if (!mtmp) return; - mtmp = christen_monst(mtmp, plname); + mtmp = christen_monst(mtmp, svp.plname); if (corpse) (void) obj_attach_mid(corpse, mtmp->m_id); - } else { - /* give your possessions to the monster you become */ - in_mklev = TRUE; /* use as-is */ - mtmp = makemon(&mons[u.ugrave_arise], u.ux, u.uy, NO_MINVENT); - in_mklev = FALSE; - if (!mtmp) { /* arise-type might have been genocided */ - drop_upon_death((struct monst *) 0, (struct obj *) 0, u.ux, u.uy); - u.ugrave_arise = NON_PM; /* in case caller cares */ - return; - } - mtmp = christen_monst(mtmp, plname); - newsym(u.ux, u.uy); - /* ["Your body rises from the dead as an ..." used - to be given here, but it has been moved to done() so that - it gets delivered even when savebones() isn't called] */ - drop_upon_death(mtmp, (struct obj *) 0, u.ux, u.uy); - /* 'mtmp' now has hero's inventory; if 'mtmp' is a mummy, give it - a wrapping unless already carrying one */ - if (mtmp->data->mlet == S_MUMMY && !m_carrying(mtmp, MUMMY_WRAPPING)) - (void) mongets(mtmp, MUMMY_WRAPPING); - m_dowear(mtmp, TRUE); } if (mtmp) { + int i; + mtmp->m_lev = (u.ulevel ? u.ulevel : 1); mtmp->mhp = mtmp->mhpmax = u.uhpmax; mtmp->female = flags.female; mtmp->msleeping = 1; + + if (!has_ebones(mtmp)) + newebones(mtmp); + if (has_ebones(mtmp)) { + for (i = 0; i <= NUM_ROLES; ++i) { + if (!strcmp(gu.urole.name.m, roles[i].name.m)) { + EBONES(mtmp)->role = i; + break; + } + /* impossible("savebones: bad gu.urole.name.m \"%s\"", + gu.urole.name.m); */ + } + for (i = 0; i <= NUM_RACES; ++i) { + if (!strcmp(gu.urace.noun, races[i].noun)) { + EBONES(mtmp)->race = i; + break; + } + /* impossible("savebones: bad gu.urace.noun \"%s\"", + gu.urace.noun); */ + } + EBONES(mtmp)->oldalign = u.ualign; + EBONES(mtmp)->deathlevel = u.ulevel; + EBONES(mtmp)->luck = u.uluck; /* moreluck not included */ + EBONES(mtmp)->mnum = Role_switch; + EBONES(mtmp)->female = flags.female; + EBONES(mtmp)->demigod = u.uevent.udemigod; + EBONES(mtmp)->crowned = u.uevent.uhand_of_elbereth; + } } for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { + set_ghostly_objlist(mtmp->minvent); resetobjs(mtmp->minvent, FALSE); /* do not zero out m_ids for bones levels any more */ mtmp->mlstmv = 0L; if (mtmp->mtame) mtmp->mtame = mtmp->mpeaceful = 0; + /* observations about the current hero won't apply to future game */ + mtmp->seen_resistance = M_SEEN_NOTHING; } - for (ttmp = ftrap; ttmp; ttmp = ttmp->ntrap) { + for (ttmp = gf.ftrap; ttmp; ttmp = ttmp->ntrap) { ttmp->madeby_u = 0; - ttmp->tseen = (ttmp->ttyp == HOLE); + ttmp->tseen = unhideable_trap(ttmp->ttyp); } + set_ghostly_objlist(fobj); resetobjs(fobj, FALSE); - resetobjs(level.buriedobjlist, FALSE); + set_ghostly_objlist(svl.level.buriedobjlist); + resetobjs(svl.level.buriedobjlist, FALSE); /* Hero is no longer on the map. */ u.ux0 = u.ux, u.uy0 = u.uy; @@ -490,8 +566,8 @@ struct obj *corpse; for (y = 0; y < ROWNO; y++) { levl[x][y].seenv = 0; levl[x][y].waslit = 0; - levl[x][y].glyph = cmap_to_glyph(S_stone); - lastseentyp[x][y] = 0; + levl[x][y].glyph = GLYPH_UNEXPLORED; + svl.lastseentyp[x][y] = 0; } /* Attach bones info to the current level before saving. */ @@ -502,8 +578,9 @@ struct obj *corpse; /* format name+role,&c, death reason, and date+time; gender and alignment reflect final values rather than what the character started out as, same as topten and logfile entries */ - Sprintf(newbones->who, "%s-%.3s-%.3s-%.3s-%.3s", plname, urole.filecode, - urace.filecode, genders[flags.female].filecode, + Sprintf(newbones->who, "%s-%.3s-%.3s-%.3s-%.3s", + svp.plname, gu.urole.filecode, + gu.urace.filecode, genders[flags.female].filecode, aligns[1 - u.ualign.type].filecode); formatkiller(newbones->how, sizeof newbones->how, how, TRUE); Strcpy(newbones->when, yyyymmddhhmmss(when)); @@ -512,16 +589,16 @@ struct obj *corpse; newbones->bonesknown = FALSE; /* if current character died on a bones level, the cemetery list will have multiple entries, most recent (this dead hero) first */ - newbones->next = level.bonesinfo; - level.bonesinfo = newbones; + newbones->next = svl.level.bonesinfo; + svl.level.bonesinfo = newbones; /* flag these bones if they are being created in wizard mode; they might already be flagged as such, even when we're playing in normal mode, if this level came from a previous bones file */ if (wizard) - level.flags.wizard_bones = 1; + svl.level.flags.wizard_bones = 1; - fd = create_bonesfile(&u.uz, &bonesid, whynot); - if (fd < 0) { + nhfp = create_bonesfile(&u.uz, &bonesid, whynot); + if (!nhfp) { if (wizard) pline1(whynot); /* bones file creation problems are silent to the player. @@ -532,55 +609,33 @@ struct obj *corpse; } c = (char) (strlen(bonesid) + 1); -#ifdef MFLOPPY /* check whether there is room */ - if (iflags.checkspace) { - savelev(fd, ledger_no(&u.uz), COUNT_SAVE); - /* savelev() initializes bytes_counted to 0, so it must come - * first here even though it does not in the real save. the - * resulting extra bflush() at the end of savelev() may increase - * bytes_counted by a couple over what the real usage will be. - * - * note it is safe to call store_version() here only because - * bufon() is null for ZEROCOMP, which MFLOPPY uses -- otherwise - * this code would have to know the size of the version - * information itself. - */ - store_version(fd); - store_savefileinfo(fd); - bwrite(fd, (genericptr_t) &c, sizeof c); - bwrite(fd, (genericptr_t) bonesid, (unsigned) c); /* DD.nnn */ - savefruitchn(fd, COUNT_SAVE); - bflush(fd); - if (bytes_counted > freediskspace(bones)) { /* not enough room */ - if (wizard) - pline("Insufficient space to create bones file."); - (void) nhclose(fd); - cancel_bonesfile(); - return; - } - co_false(); /* make sure stuff before savelev() gets written */ - } -#endif /* MFLOPPY */ - - store_version(fd); - store_savefileinfo(fd); - bwrite(fd, (genericptr_t) &c, sizeof c); - bwrite(fd, (genericptr_t) bonesid, (unsigned) c); /* DD.nnn */ - savefruitchn(fd, WRITE_SAVE | FREE_SAVE); + nhfp->mode = WRITING; + store_version(nhfp); + Sfo_char(nhfp, &svn.nhuuid[0], "ancestor-nhuuid", sizeof svn.nhuuid); + /* if a bones pool digit is in use, it precedes the bonesid + string and isn't recorded in the file */ + Sfo_char(nhfp, &c, "bones_count", 1); + Sfo_char(nhfp, bonesid, "bonesid", (int) c); /* DD.nnn */ + savefruitchn(nhfp); update_mlstmv(); /* update monsters for eventual restoration */ - savelev(fd, ledger_no(&u.uz), WRITE_SAVE | FREE_SAVE); - bclose(fd); + savelev(nhfp, ledger_no(&u.uz)); + close_nhfile(nhfp); commit_bonesfile(&u.uz); compress_bonesfile(); } +#endif /* !SFCTOOL */ + int -getbones() +getbones(void) { - register int fd; - register int ok; - char c, *bonesid, oldbonesid[40]; /* was [10]; more should be safer */ + int ok; + NHFILE *nhfp = (NHFILE *) 0; + char c = 0, *bonesid, + oldbonesid[40] = { 0 }; /* was [10]; more should be safer */ + char ancestor_nhuuid[SIZE(svn.nhuuid)]; +#ifndef SFCTOOL if (discover) /* save bones files for real games */ return 0; @@ -592,47 +647,64 @@ getbones() return 0; if (no_bones_level(&u.uz)) return 0; - fd = open_bonesfile(&u.uz, &bonesid); - if (fd < 0) +#endif /* !SFCTOOL */ + + nhfp = open_bonesfile(&u.uz, &bonesid); + if (!nhfp) return 0; + if (nhfp && nhfp->structlevel && nhfp->fd < 0) + return 0; + if (nhfp && nhfp->fieldlevel) { + if (nhfp->style.deflt && !nhfp->fpdef) + return 0; + } - if (validate(fd, bones) != 0) { + program_state.reading_bonesfile = 1; + if (validate(nhfp, gb.bones, FALSE) != SF_UPTODATE) { if (!wizard) pline("Discarding unusable bones; no need to panic..."); ok = FALSE; + program_state.reading_bonesfile = 0; } else { ok = TRUE; if (wizard) { - if (yn("Get bones?") == 'n') { - (void) nhclose(fd); + if (y_n("Get bones?") == 'n') { + close_nhfile(nhfp); compress_bonesfile(); + program_state.reading_bonesfile = 0; return 0; } } - mread(fd, (genericptr_t) &c, sizeof c); /* length incl. '\0' */ - mread(fd, (genericptr_t) oldbonesid, (unsigned) c); /* DD.nnn */ - if (strcmp(bonesid, oldbonesid) != 0 - /* from 3.3.0 through 3.6.0, bones in the quest branch stored - a bogus bonesid in the file; 3.6.1 fixed that, but for - 3.6.0 bones to remain compatible, we need an extra test; - once compatibility with 3.6.x goes away, this can too - (we don't try to make this conditional upon the value of - VERSION_COMPATIBILITY because then we'd need patchlevel.h) */ - && (strlen(bonesid) <= 2 - || strcmp(bonesid + 2, oldbonesid) != 0)) { + Sfi_char(nhfp, &ancestor_nhuuid[0], "ancestor-nhuuid", + sizeof ancestor_nhuuid); + Sfi_char(nhfp, &c, "bones_count", 1); /* length incl. '\0' */ + if ((unsigned) c <= sizeof oldbonesid) { + Sfi_char(nhfp, oldbonesid, "bonesid", (int) c); + } else { + if (wizard) + debugpline2("Abandoning bones , %u > %u.", + (unsigned) c, (unsigned) sizeof oldbonesid); + close_nhfile(nhfp); + compress_bonesfile(); + /* ToDo: maybe unlink these problematic bones? */ + program_state.reading_bonesfile = 0; + return 0; + } + if (strcmp(bonesid, oldbonesid) != 0) { char errbuf[BUFSZ]; - Sprintf(errbuf, "This is bones level '%s', not '%s'!", oldbonesid, - bonesid); + Sprintf(errbuf, "This is bones level '%s', not '%s'!", + oldbonesid, bonesid); if (wizard) { pline1(errbuf); ok = FALSE; /* won't die of trickery */ } + program_state.reading_bonesfile = 0; trickery(errbuf); } else { - register struct monst *mtmp; + struct monst *mtmp; - getlev(fd, 0, 0, TRUE); + getlev(nhfp, 0, 0); /* Note that getlev() now keeps tabs on unique * monsters such as demon lords, and tracks the @@ -642,12 +714,12 @@ getbones() * set to the magic DEFUNCT_MONSTER cookie value. */ for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (has_mname(mtmp)) - sanitize_name(MNAME(mtmp)); + if (has_mgivenname(mtmp)) + sanitize_name(MGIVENNAME(mtmp)); if (mtmp->mhpmax == DEFUNCT_MONSTER) { if (wizard) { debugpline1("Removing defunct monster %s from bones.", - mtmp->data->mname); + mtmp->data->pmnames[NEUTRAL]); } mongone(mtmp); } else @@ -655,15 +727,17 @@ getbones() resetobjs(mtmp->minvent, TRUE); } resetobjs(fobj, TRUE); - resetobjs(level.buriedobjlist, TRUE); + resetobjs(svl.level.buriedobjlist, TRUE); + fix_shop_damage(); } } - (void) nhclose(fd); + close_nhfile(nhfp); + program_state.reading_bonesfile = 0; sanitize_engravings(); u.uroleplay.numbones++; if (wizard) { - if (yn("Unlink bones?") == 'n') { + if (y_n("Unlink bones?") == 'n') { compress_bonesfile(); return ok; } @@ -681,4 +755,89 @@ getbones() return ok; } +#ifndef SFCTOOL + +/* check whether current level contains bones from a particular player */ +boolean +bones_include_name(const char *name) +{ + struct cemetery *bp; + size_t len; + char buf[BUFSZ]; + + /* prepare buffer by appending terminal hyphen to name, to avoid partial + * matches producing false positives */ + Strcpy(buf, name); + Strcat(buf, "-"); + len = strlen(buf); + + for (bp = svl.level.bonesinfo; bp; bp = bp->next) { + if (!strncmp(bp->who, buf, len)) + return TRUE; + } + + return FALSE; +} + +/* set the ghostly bit in a list of objects */ +staticfn void +set_ghostly_objlist(struct obj *objchain) +{ + while (objchain) { + objchain->ghostly = 1; + objchain = objchain->nobj; + } +} + +/* This is called when a marked object from a bones file is picked-up. + Some could result in a message, and the obj->ghostly flag is always + cleared. obj->ghostly has no other usage at this time. */ +void +fix_ghostly_obj(struct obj *obj) +{ + if (!obj->ghostly) + return; + switch(obj->otyp) { + /* asymmetrical weapons */ + case BOW: + case ELVEN_BOW: + case ORCISH_BOW: + case YUMI: + case BOOMERANG: + You("make adjustments to %s to suit your %s hand.", + the(xname(obj)), + URIGHTY ? "right" : "left"); + break; + default: + break; + } + obj->ghostly = 0; +} + +void +newebones(struct monst *mtmp) +{ + if (!mtmp->mextra) + mtmp->mextra = newmextra(); + if (!EBONES(mtmp)) { + EBONES(mtmp) = (struct ebones *) alloc( + sizeof (struct ebones)); + (void) memset((genericptr_t) EBONES(mtmp), 0, + sizeof (struct ebones)); + EBONES(mtmp)->parentmid = mtmp->m_id; + } +} + +/* this is not currently used */ +void +free_ebones(struct monst *mtmp) +{ + if (mtmp->mextra && EBONES(mtmp)) { + free((genericptr_t) EBONES(mtmp)); + EBONES(mtmp) = (struct ebones *) 0; + } +} + +#endif /* SFCTOOL */ + /*bones.c*/ diff --git a/src/botl.c b/src/botl.c index 4c7dbf33a..22f447ea4 100644 --- a/src/botl.c +++ b/src/botl.c @@ -1,25 +1,24 @@ -/* NetHack 3.6 botl.c $NHDT-Date: 1573178085 2019/11/08 01:54:45 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.148 $ */ +/* NetHack 5.0 botl.c $NHDT-Date: 1769839231 2026/01/30 22:00:31 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.277 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Michael Allison, 2006. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -#ifndef LONG_MAX -#include -#endif -extern const char *hu_stat[]; /* defined in eat.c */ +extern const char *const hu_stat[]; /* defined in eat.c */ -const char *const enc_stat[] = { "", "Burdened", "Stressed", - "Strained", "Overtaxed", "Overloaded" }; +/* also used in insight.c */ +const char *const enc_stat[] = { + "", "Burdened", "Stressed", + "Strained", "Overtaxed", "Overloaded" +}; -STATIC_OVL NEARDATA int mrank_sz = 0; /* loaded by max_rank_sz (from u_init) */ -STATIC_DCL const char *NDECL(rank); -STATIC_DCL void NDECL(bot_via_windowport); -STATIC_DCL void NDECL(stat_update_time); +staticfn const char *rank(void); +staticfn void bot_via_windowport(void); +staticfn void stat_update_time(void); -static char * -get_strength_str() +char * +get_strength_str(void) { static char buf[32]; int st = ACURR(A_STR); @@ -38,31 +37,34 @@ get_strength_str() } void -check_gold_symbol() +check_gold_symbol(void) { - nhsym goldch = showsyms[COIN_CLASS + SYM_OFF_O]; + nhsym goldch = gs.showsyms[COIN_CLASS + SYM_OFF_O]; iflags.invis_goldsym = (goldch <= (nhsym) ' '); } char * -do_statusline1() +do_statusline1(void) { static char newbot1[BUFSZ]; - register char *nb; - register int i, j; + char *nb; + int i, j; + + if (suppress_map_output()) + return strcpy(newbot1, ""); - Strcpy(newbot1, plname); + Strcpy(newbot1, svp.plname); if ('a' <= newbot1[0] && newbot1[0] <= 'z') newbot1[0] += 'A' - 'a'; - newbot1[10] = 0; + newbot1[BOTL_NSIZ] = 0; Sprintf(nb = eos(newbot1), " the "); if (Upolyd) { char mbot[BUFSZ]; int k = 0; - Strcpy(mbot, mons[u.umonnum].mname); + Strcpy(mbot, pmname(&mons[u.umonnum], Ugender)); while (mbot[k] != 0) { if ((k == 0 || (k > 0 && mbot[k - 1] == ' ')) && 'a' <= mbot[k] && mbot[k] <= 'z') @@ -70,11 +72,12 @@ do_statusline1() k++; } Strcpy(nb = eos(nb), mbot); - } else + } else { Strcpy(nb = eos(nb), rank()); + } Sprintf(nb = eos(nb), " "); - i = mrank_sz + 15; + i = gm.mrank_sz + 15; j = (int) ((nb + 2) - newbot1); /* strlen(newbot1) but less computation */ if ((i - j) > 0) Sprintf(nb = eos(nb), "%*s", i - j, " "); /* pad with spaces */ @@ -83,10 +86,10 @@ do_statusline1() get_strength_str(), ACURR(A_DEX), ACURR(A_CON), ACURR(A_INT), ACURR(A_WIS), ACURR(A_CHA)); - Sprintf(nb = eos(nb), - (u.ualign.type == A_CHAOTIC) - ? " Chaotic" - : (u.ualign.type == A_NEUTRAL) ? " Neutral" : " Lawful"); + Sprintf(nb = eos(nb), "%s", + (u.ualign.type == A_CHAOTIC) ? " Chaotic" + : (u.ualign.type == A_NEUTRAL) ? " Neutral" + : " Lawful"); #ifdef SCORE_ON_BOTL if (flags.showscore) Sprintf(nb = eos(nb), " S:%ld", botl_score()); @@ -95,18 +98,22 @@ do_statusline1() } char * -do_statusline2() +do_statusline2(void) { static char newbot2[BUFSZ], /* MAXCO: botl.h */ /* dungeon location (and gold), hero health (HP, PW, AC), experience (HD if poly'd, else Exp level and maybe Exp points), time (in moves), varying number of status conditions */ - dloc[QBUFSZ], hlth[QBUFSZ], expr[QBUFSZ], tmmv[QBUFSZ], cond[QBUFSZ]; - register char *nb; - unsigned dln, dx, hln, xln, tln, cln; + dloc[QBUFSZ], hlth[QBUFSZ], expr[QBUFSZ], + tmmv[QBUFSZ], cond[QBUFSZ], vers[QBUFSZ]; + char *nb; + size_t dln, dx, hln, xln, tln, cln, vrn; int hp, hpmax, cap; long money; + if (suppress_map_output()) + return strcpy(newbot2, ""); + /* * Various min(x,9999)'s are to avoid having excessive values * violate the field width assumptions in botl.h and should not @@ -117,8 +124,8 @@ do_statusline2() */ /* dungeon location plus gold */ - (void) describe_level(dloc); /* includes at least one trailing space */ - if ((money = money_cnt(invent)) < 0L) + (void) describe_level(dloc, 1); /* includes at least one trailing space */ + if ((money = money_cnt(gi.invent)) < 0L) money = 0L; /* ought to issue impossible() and then discard gold */ Sprintf(eos(dloc), "%s:%-2ld", /* strongest hero can lift ~300000 gold */ (iflags.in_dumplog || iflags.invis_goldsym) ? "$" @@ -144,12 +151,12 @@ do_statusline2() else if (flags.showexp) Sprintf(expr, "Xp:%d/%-1ld", u.ulevel, u.uexp); else - Sprintf(expr, "Exp:%d", u.ulevel); + Sprintf(expr, "Xp:%d", u.ulevel); xln = strlen(expr); /* time/move counter */ if (flags.time) - Sprintf(tmmv, "T:%ld", moves); + Sprintf(tmmv, "T:%ld", svm.moves); else tmmv[0] = '\0'; tln = strlen(tmmv); @@ -198,6 +205,13 @@ do_statusline2() Strcpy(nb = eos(nb), " Ride"); cln = strlen(cond); + /* version on status line, with leading space */ + if (flags.showvers) + (void) status_version(vers, sizeof vers, TRUE); + else + vers[0] = '\0'; + vrn = strlen(vers); + /* * Put the pieces together. If they all fit, keep the traditional * sequence. Otherwise, move least important parts to the end in @@ -210,18 +224,24 @@ do_statusline2() * wider displays can still show wider status than the map if the * interface supports that. */ - if ((dln - dx) + 1 + hln + 1 + xln + 1 + tln + 1 + cln <= COLNO) { - Sprintf(newbot2, "%s %s %s %s %s", dloc, hlth, expr, tmmv, cond); + if ((dln - dx) + 1 + hln + 1 + xln + 1 + tln + 1 + cln + vrn <= COLNO) { + Snprintf(newbot2, sizeof newbot2, "%s %s %s %s %s%s", dloc, hlth, + expr, tmmv, cond, vers); } else { - if (dln + 1 + hln + 1 + xln + 1 + tln + 1 + cln + 1 > MAXCO) { + if (dln + 1 + hln + 1 + xln + 1 + tln + 1 + cln + vrn > MAXCO) { panic("bot2: second status line exceeds MAXCO (%u > %d)", - (dln + 1 + hln + 1 + xln + 1 + tln + 1 + cln + 1), MAXCO); + (unsigned) (dln + 1 + hln + 1 + xln + 1 + tln + 1 + cln + + vrn), + MAXCO); } else if ((dln - dx) + 1 + hln + 1 + xln + 1 + cln <= COLNO) { - Sprintf(newbot2, "%s %s %s %s %s", dloc, hlth, expr, cond, tmmv); + Snprintf(newbot2, sizeof newbot2, "%s %s %s %s %s%s", dloc, hlth, + expr, cond, tmmv, vers); } else if ((dln - dx) + 1 + hln + 1 + cln <= COLNO) { - Sprintf(newbot2, "%s %s %s %s %s", dloc, hlth, cond, expr, tmmv); + Snprintf(newbot2, sizeof newbot2, "%s %s %s %s %s%s", dloc, hlth, + cond, expr, tmmv, vers); } else { - Sprintf(newbot2, "%s %s %s %s %s", hlth, cond, dloc, expr, tmmv); + Snprintf(newbot2, sizeof newbot2, "%s %s %s %s %s%s", hlth, cond, + dloc, expr, tmmv, vers); } /* only two or three consecutive spaces available to squeeze out */ mungspaces(newbot2); @@ -230,10 +250,14 @@ do_statusline2() } void -bot() +bot(void) { - /* dosave() flags completion by setting u.uhp to -1 */ - if ((u.uhp != -1) && youmonst.data && iflags.status_updates) { + if (gb.bot_disabled) + return; + /* dosave() flags completion by setting u.uhp to -1; suppress_map_output() + covers program_state.restoring and is used for status as well as map */ + if (u.uhp != -1 && gy.youmonst.data + && iflags.status_updates && !suppress_map_output()) { if (VIA_WINDOWPORT()) { bot_via_windowport(); } else { @@ -243,13 +267,22 @@ bot() putmixed(WIN_STATUS, 0, do_statusline2()); } } - context.botl = context.botlx = iflags.time_botl = FALSE; + disp.botl = disp.botlx = disp.time_botl = FALSE; } +/* special purpose status update: move counter ('time' status) only */ void -timebot() +timebot(void) { - if (flags.time && iflags.status_updates) { + if (gb.bot_disabled) + return; + /* we're called when disp.time_botl is set and general disp.botl + is clear; disp.time_botl gets set whenever svm.moves changes value + so there's no benefit in tracking previous value to decide whether + to skip update; suppress_map_output() handles program_state.restoring + and program_state.done_hup (tty hangup => no further output at all) + and we use it for maybe skipping status as well as for the map */ + if (flags.time && iflags.status_updates && !suppress_map_output()) { if (VIA_WINDOWPORT()) { stat_update_time(); } else { @@ -257,42 +290,56 @@ timebot() bot(); } } - iflags.time_botl = FALSE; + disp.time_botl = FALSE; } /* convert experience level (1..30) to rank index (0..8) */ int -xlev_to_rank(xlev) -int xlev; +xlev_to_rank(int xlev) { + /* + * 1..2 => 0 + * 3..5 => 1 + * 6..9 => 2 + * 10..13 => 3 + * ... + * 26..29 => 7 + * 30 => 8 + * Conversion is precise but only partially reversible. + */ return (xlev <= 2) ? 0 : (xlev <= 30) ? ((xlev + 2) / 4) : 8; } -#if 0 /* not currently needed */ /* convert rank index (0..8) to experience level (1..30) */ int -rank_to_xlev(rank) -int rank; +rank_to_xlev(int rank) { - return (rank <= 0) ? 1 : (rank <= 8) ? ((rank * 4) - 2) : 30; + /* + * 0 => 1..2 + * 1 => 3..5 + * 2 => 6..9 + * 3 => 10..13 + * ... + * 7 => 26..29 + * 8 => 30 + * We return the low end of each range. + */ + return (rank < 1) ? 1 : (rank < 2) ? 3 + : (rank < 8) ? ((rank * 4) - 2) : 30; } -#endif const char * -rank_of(lev, monnum, female) -int lev; -short monnum; -boolean female; +rank_of(int lev, short monnum, boolean female) { - register const struct Role *role; - register int i; + const struct Role *role; + int i; /* Find the role */ for (role = roles; role->name.m; role++) - if (monnum == role->malenum || monnum == role->femalenum) + if (monnum == role->mnum) break; if (!role->name.m) - role = &urole; + role = &gu.urole; /* Find the rank */ for (i = xlev_to_rank((int) lev); i >= 0; i--) { @@ -310,188 +357,320 @@ boolean female; return "Player"; } -STATIC_OVL const char * -rank() +staticfn const char * +rank(void) { return rank_of(u.ulevel, Role_switch, flags.female); } int -title_to_mon(str, rank_indx, title_length) -const char *str; -int *rank_indx, *title_length; +title_to_mon( + const char *str, + int *rank_indx, + int *title_length) { - register int i, j; + int i, j; /* Loop through each of the roles */ - for (i = 0; roles[i].name.m; i++) + for (i = 0; roles[i].name.m; i++) { + /* loop through each of the rank titles for role #i */ for (j = 0; j < 9; j++) { if (roles[i].rank[j].m - && !strncmpi(str, roles[i].rank[j].m, - strlen(roles[i].rank[j].m))) { + && str_start_is(str, roles[i].rank[j].m, TRUE)) { if (rank_indx) *rank_indx = j; if (title_length) - *title_length = strlen(roles[i].rank[j].m); - return roles[i].malenum; + *title_length = Strlen(roles[i].rank[j].m); + return roles[i].mnum; } if (roles[i].rank[j].f - && !strncmpi(str, roles[i].rank[j].f, - strlen(roles[i].rank[j].f))) { + && str_start_is(str, roles[i].rank[j].f, TRUE)) { if (rank_indx) *rank_indx = j; if (title_length) - *title_length = strlen(roles[i].rank[j].f); - return (roles[i].femalenum != NON_PM) ? roles[i].femalenum - : roles[i].malenum; + *title_length = Strlen(roles[i].rank[j].f); + return roles[i].mnum; } } + } + if (title_length) + *title_length = 0; return NON_PM; } void -max_rank_sz() +max_rank_sz(void) { - register int i, r, maxr = 0; + int i; + size_t r, maxr = 0; + for (i = 0; i < 9; i++) { - if (urole.rank[i].m && (r = strlen(urole.rank[i].m)) > maxr) + if (gu.urole.rank[i].m && (r = strlen(gu.urole.rank[i].m)) > maxr) maxr = r; - if (urole.rank[i].f && (r = strlen(urole.rank[i].f)) > maxr) + if (gu.urole.rank[i].f && (r = strlen(gu.urole.rank[i].f)) > maxr) maxr = r; } - mrank_sz = maxr; + gm.mrank_sz = (int) maxr; return; } #ifdef SCORE_ON_BOTL long -botl_score() +botl_score(void) { - long deepest = deepest_lev_reached(FALSE); - long utotal; - - utotal = money_cnt(invent) + hidden_gold(); - if ((utotal -= u.umoney0) < 0L) - utotal = 0L; - utotal += u.urexp + (50 * (deepest - 1)) - + (deepest > 30 ? 10000 : deepest > 20 ? 1000 * (deepest - 20) : 0); - if (utotal < u.urexp) - utotal = LONG_MAX; /* wrap around */ - return utotal; + long deepest = (long) deepest_lev_reached(FALSE); + long umoney, depthbonus; + + /* hidden_gold(False): only gold in containers whose contents are known */ + umoney = money_cnt(gi.invent) + hidden_gold(FALSE); + /* don't include initial gold; don't impose penalty if it's all gone */ + if ((umoney -= u.umoney0) < 0L) + umoney = 0L; + depthbonus = (50L * (deepest - 1L)) + + ((deepest > 30L) ? 10000L + : (deepest > 20L) ? (1000L * (deepest - 20L)) + : 0L); + /* neither umoney nor depthbonus can grow unusually big (gold due to + weight); u.urexp might */ + return nowrap_add(u.urexp, umoney + depthbonus); } #endif /* SCORE_ON_BOTL */ /* provide the name of the current level for display by various ports */ int -describe_level(buf) -char *buf; +describe_level( + char *buf, /* output buffer */ + int dflgs) /* 1: append trailing space; 2: include dungeon branch name */ { + boolean addspace = (dflgs & 1) != 0, /* (used to be unconditional) */ + addbranch = (dflgs & 2) != 0; /* False: status, True: livelog */ int ret = 1; - /* TODO: Add in dungeon name */ if (Is_knox(&u.uz)) { - Sprintf(buf, "%s ", dungeons[u.uz.dnum].dname); + Sprintf(buf, "%s", svd.dungeons[u.uz.dnum].dname); + addbranch = FALSE; } else if (In_quest(&u.uz)) { - Sprintf(buf, "Home %d ", dunlev(&u.uz)); + Sprintf(buf, "Home %d", dunlev(&u.uz)); } else if (In_endgame(&u.uz)) { /* [3.6.2: this used to be "Astral Plane" or generic "End Game"] */ (void) endgamelevelname(buf, depth(&u.uz)); - (void) strsubst(buf, "Plane of ", ""); /* just keep */ - Strcat(buf, " "); + if (!addbranch) + (void) strsubst(buf, "Plane of ", ""); /* just keep */ + addbranch = FALSE; } else { /* ports with more room may expand this one */ - Sprintf(buf, "Dlvl:%-2d ", depth(&u.uz)); + if (!addbranch) + Sprintf(buf, "%s:%-2d", /* "Dlvl:n" (grep fodder) */ + In_tutorial(&u.uz) ? "Tutorial" : "Dlvl", depth(&u.uz)); + else + Sprintf(buf, "level %d", depth(&u.uz)); ret = 0; } + if (addbranch) { + Sprintf(eos(buf), ", %s", svd.dungeons[u.uz.dnum].dname); + (void) strsubst(buf, "The ", "the "); + } + if (addspace) + Strcat(buf, " "); return ret; } +/* weapon description for status lines; started as a terser version of + what ^X shows but has diverged to some extent */ +char * +weapon_status(char *outbuf) +{ + const char *res = 0; + + *outbuf = '\0'; /* lint suppression */ + if (!uwep) { + /* no weapon; gloves imply hands; humanoid also implies hands; + otherwise make no assumptions */ + res = uarmg ? "Empty-hnd" /* empty handed means "gloves only" */ + : humanoid(gy.youmonst.data) ? "Bare-hnds" /* bare hands */ + : "No-weapon"; + } else if (u.twoweap) { + /* two-weaponing implies hands and a weapon or wep-tool + (not other odd stuff) in each hand */ + res = "Dual-weps"; + /* note: dual wielding two lances doesn't produce double joust */ + if (u.usteed && (weapon_type(uwep) == P_LANCE + || weapon_type(uswapwep) == P_LANCE)) + res = "Dual+joust"; /* lance behaves specially when mounted */ + } else { + /* report most weapons by their skill class (so a katana will be + described as a long sword, for instance; mattock and hook are + exceptions), or wielded non-weapon item by its object class */ + char *p; + int skill = weapon_type(uwep); + + if (u.usteed && skill == P_LANCE) { + /* lance behaves specially when hero is mounted */ + res = "joust"; + } else if (uwep->otyp == AKLYS) { + /* aklys behaves specially when thrown while wielded, so + give it a distinct name instead of skill name of "club"; + [maybe FIXME?] for the time being + use real name even if 'obj' is undiscovered "thonged club" */ + res = "aklys"; + } else if (is_sword(uwep)) { + /* simplify short short/broad sword/long sword/two-handed sword + (similar to messages when dropped due to slippery fingers) */ + res = "sword"; + } else { + /* shorten several */ + switch (skill) { + case P_QUARTERSTAFF: + res = "staff"; + break; + case P_MORNING_STAR: + res = "mrng-star"; /* still pretty long */ + break; + case P_POLEARMS: + res = "pole"; + break; + case P_UNICORN_HORN: + res = "unihorn"; + break; + default: + res = weapon_descr(uwep); + /* [should this be moved into weapon_descr()?] */ + if (!strcmpi(res, "food") && uwep->otyp == CREAM_PIE) + res = "pie"; + break; + } + } + + if ((uwep->oclass == WEAPON_CLASS || is_weptool(uwep)) + && bimanual(uwep) && *res != '2' && strncmpi(res, "two", 3)) + Strcat(outbuf, "2H-"); + Strcpy(p = eos(outbuf), res), res = outbuf; + *p = highc(*p); + /* avoid embedded spaces since its designed to appear as part + of a space-separated status line */ + (void) strNsubst(outbuf, " ", "-", 0); + } + + return (outbuf == res) ? outbuf : strcpy(outbuf, res); +} + +/* armor description for status lines */ +char * +armor_status(char *armbuf) +{ + int n = !!uarmg + !!uarmc + !!uarm + !!uarmu + !!uarmh + !!uarmf + !!uarms; + + /* + * FIXME: ^X needs to provide non-abbreviated version of this info. + * At present it just reports the "no armor" case. + */ + if (n == 0) { /* no armor */ + Strcpy(armbuf, "naked"); + } else if (n == 1) { /* just one piece; spell it out */ + Strcpy(armbuf, uarmg ? "gloves" + : uarmc ? "cloak" + : uarm ? "suit" + : uarmu ? "shirt" + : uarmh ? helm_simple_name(uarmh) /* hat|helm */ + : uarmf ? "boots" + : uarms ? "shield" + : ""); /* not possible */ + } else { /* more than one piece */ + char *p = armbuf; + + /* gloves first since this is expected to follow weapon_status(); + cloak next since it tends to provide the most protection + aside from raw AC */ + if (uarmg) + *p++ = 'G'; /* gloves */ + if (uarmc) + *p++ = 'C'; /* cloak */ + if (uarm) + *p++ = 'A'; /* suit but 's' is for shield */ + if (uarmu) + *p++ = 'U'; /* underwear? => shirt */ + if (uarmh) + *p++ = 'H'; /* hat/helm */ + if (uarmf) + *p++ = 'B'; /* footwear => boots */ + if (uarms) + *p++ = 'S'; /* shield */ + *p = '\0'; + } + /* + * Add a hint about MC by appending a plus sign if that's augmented. + * Bug: we should modfiy magical_negation() to return extra info and + * call it to scan whole inventory looking for sources of protection. + * This is a hack for efficiency to avoid that during status updates. + */ + if ((uright && uright->otyp == RIN_PROTECTION) + || (uleft && uleft->otyp == RIN_PROTECTION) + || (uamul && uamul->otyp == AMULET_OF_GUARDING) + || (uarmc && uarmc->otyp == CLOAK_OF_PROTECTION) + || (uarmh && uarmh->oartifact == ART_MITRE_OF_HOLINESS) + || (uwep && uwep->oartifact == ART_TSURUGI_OF_MURAMASA)) + (void) strkitten(armbuf, '+'); + + return upstart(armbuf); +} + /* =======================================================================*/ /* statusnew routines */ /* =======================================================================*/ /* structure that tracks the status details in the core */ -#define MAXVALWIDTH 80 /* actually less, but was using 80 to allocate title - * and leveldesc then using QBUFSZ everywhere else */ #ifdef STATUS_HILITES -struct hilite_s { - enum statusfields fld; - boolean set; - unsigned anytype; - anything value; - int behavior; - char textmatch[MAXVALWIDTH]; - enum relationships rel; - int coloridx; - struct hilite_s *next; -}; #endif /* STATUS_HILITES */ -struct istat_s { - const char *fldname; - const char *fldfmt; - long time; /* moves when this field hilite times out */ - boolean chg; /* need to recalc time? */ - boolean percent_matters; - short percent_value; - unsigned anytype; - anything a; - char *val; - int valwidth; - enum statusfields idxmax; - enum statusfields fld; -#ifdef STATUS_HILITES - struct hilite_s *hilite_rule; /* the entry, if any, in 'thresholds' - * list that currently applies */ - struct hilite_s *thresholds; -#endif -}; - -STATIC_DCL boolean FDECL(eval_notify_windowport_field, (int, boolean *, int)); -STATIC_DCL void FDECL(evaluate_and_notify_windowport, (boolean *, int)); -STATIC_DCL void NDECL(init_blstats); -STATIC_DCL int FDECL(compare_blstats, (struct istat_s *, struct istat_s *)); -STATIC_DCL char *FDECL(anything_to_s, (char *, anything *, int)); -STATIC_DCL int FDECL(percentage, (struct istat_s *, struct istat_s *)); -STATIC_DCL int NDECL(exp_percentage); +staticfn boolean eval_notify_windowport_field(int, boolean *, int); +staticfn void evaluate_and_notify_windowport(boolean *, int); +staticfn void init_blstats(void); +staticfn int compare_blstats(struct istat_s *, struct istat_s *); +staticfn char *anything_to_s(char *, anything *, int); +staticfn int percentage(struct istat_s *, struct istat_s *); +staticfn int exp_percentage(void); +staticfn int QSORTCALLBACK cond_cmp(const genericptr, const genericptr); +staticfn int QSORTCALLBACK menualpha_cmp(const genericptr, const genericptr); #ifdef STATUS_HILITES -STATIC_DCL void FDECL(s_to_anything, (anything *, char *, int)); -STATIC_DCL enum statusfields FDECL(fldname_to_bl_indx, (const char *)); -STATIC_DCL boolean FDECL(hilite_reset_needed, (struct istat_s *, long)); -STATIC_DCL boolean FDECL(noneoftheabove, (const char *)); -STATIC_DCL struct hilite_s *FDECL(get_hilite, (int, int, genericptr_t, - int, int, int *)); -STATIC_DCL void FDECL(split_clridx, (int, int *, int *)); -STATIC_DCL boolean FDECL(is_ltgt_percentnumber, (const char *)); -STATIC_DCL boolean FDECL(has_ltgt_percentnumber, (const char *)); -STATIC_DCL int FDECL(splitsubfields, (char *, char ***, int)); -STATIC_DCL boolean FDECL(is_fld_arrayvalues, (const char *, - const char *const *, - int, int, int *)); -STATIC_DCL int FDECL(query_arrayvalue, (const char *, const char *const *, - int, int)); -STATIC_DCL void FDECL(status_hilite_add_threshold, (int, struct hilite_s *)); -STATIC_DCL boolean FDECL(parse_status_hl2, (char (*)[QBUFSZ], BOOLEAN_P)); -STATIC_DCL char *FDECL(conditionbitmask2str, (unsigned long)); -STATIC_DCL unsigned long FDECL(match_str2conditionbitmask, (const char *)); -STATIC_DCL unsigned long FDECL(str2conditionbitmask, (char *)); -STATIC_DCL boolean FDECL(parse_condition, (char (*)[QBUFSZ], int)); -STATIC_DCL char *FDECL(hlattr2attrname, (int, char *, int)); -STATIC_DCL void FDECL(status_hilite_linestr_add, (int, struct hilite_s *, - unsigned long, const char *)); -STATIC_DCL void NDECL(status_hilite_linestr_done); -STATIC_DCL int FDECL(status_hilite_linestr_countfield, (int)); -STATIC_DCL void NDECL(status_hilite_linestr_gather_conditions); -STATIC_DCL void NDECL(status_hilite_linestr_gather); -STATIC_DCL char *FDECL(status_hilite2str, (struct hilite_s *)); -STATIC_DCL int NDECL(status_hilite_menu_choose_field); -STATIC_DCL int FDECL(status_hilite_menu_choose_behavior, (int)); -STATIC_DCL int FDECL(status_hilite_menu_choose_updownboth, (int, const char *, - BOOLEAN_P, BOOLEAN_P)); -STATIC_DCL boolean FDECL(status_hilite_menu_add, (int)); -#define has_hilite(i) (blstats[0][(i)].thresholds) +staticfn void s_to_anything(anything *, char *, int); +staticfn enum statusfields fldname_to_bl_indx(const char *); +staticfn boolean hilite_reset_needed(struct istat_s *, long); +staticfn boolean noneoftheabove(const char *); +staticfn struct hilite_s *get_hilite(int, int, genericptr_t, int, int, int *); +staticfn void split_clridx(int, int *, int *); +staticfn boolean is_ltgt_percentnumber(const char *); +staticfn boolean has_ltgt_percentnumber(const char *); +staticfn int splitsubfields(char *, char ***, int); +staticfn boolean is_fld_arrayvalues(const char *, const char *const *, + int, int, int *); +staticfn int query_arrayvalue(const char *, const char *const *, int, int); +staticfn void status_hilite_add_threshold(int, struct hilite_s *); +staticfn boolean parse_status_hl2(char (*)[QBUFSZ], boolean); +staticfn unsigned long query_conditions(void); +staticfn char *conditionbitmask2str(unsigned long); +staticfn unsigned long match_str2conditionbitmask(const char *); +staticfn unsigned long str2conditionbitmask(char *); +staticfn boolean parse_condition(char (*)[QBUFSZ], int); +staticfn char *hlattr2attrname(int, char *, size_t); +staticfn void status_hilite_linestr_add(int, struct hilite_s *, unsigned long, + const char *); +staticfn void status_hilite_linestr_done(void); +staticfn int status_hilite_linestr_countfield(int); +staticfn void status_hilite_linestr_gather_conditions(void); +staticfn void status_hilite_linestr_gather(void); +staticfn char *status_hilite2str(struct hilite_s *); +staticfn int status_hilite_menu_choose_field(void); +staticfn int status_hilite_menu_choose_behavior(int); +staticfn int status_hilite_menu_choose_updownboth(int, const char *, boolean, + boolean); +staticfn boolean status_hilite_menu_add(int); +staticfn boolean status_hilite_remove(int); +staticfn boolean status_hilite_menu_fld(int); +staticfn void status_hilites_viewall(void); + +#define has_hilite(i) (gb.blstats[0][(i)].thresholds) /* TH_UPDOWN encompasses specific 'up' and 'down' also general 'changed' */ #define Is_Temp_Hilite(rule) ((rule) && (rule)->behavior == BL_TH_UPDOWN) @@ -503,17 +682,25 @@ STATIC_DCL boolean FDECL(status_hilite_menu_add, (int)); #define INIT_BLSTAT(name, fmtstr, anytyp, wid, fld) \ { name, fmtstr, 0L, FALSE, FALSE, 0, anytyp, \ - { (genericptr_t) 0 }, (char *) 0, \ - wid, -1, fld INIT_THRESH } + { (genericptr_t) 0 }, { (genericptr_t) 0 }, (char *) 0, \ + wid, -1, fld INIT_THRESH } #define INIT_BLSTATP(name, fmtstr, anytyp, wid, maxfld, fld) \ { name, fmtstr, 0L, FALSE, TRUE, 0, anytyp, \ - { (genericptr_t) 0 }, (char *) 0, \ - wid, maxfld, fld INIT_THRESH } + { (genericptr_t) 0 }, { (genericptr_t) 0 }, (char *) 0, \ + wid, maxfld, fld INIT_THRESH } -/* If entries are added to this, botl.h will require updating too. - 'max' value of BL_EXP gets special handling since the percentage - involved isn't a direct 100*current/maximum calculation. */ -STATIC_VAR struct istat_s initblstats[MAXBLSTATS] = { +/* + * If entries are added to this, botl.h will require updating too. + * + * 'max' values of BL_XP and BL_EXP get special handling since the + * percentage involved isn't a direct 100*current/maximum calculation. + * + * long int fields are given a buffer size of 30 which is guaranteed to + * be big enough for short prefix followed by a 20+ digit 64-bit long + * even though the actual values will be much smaller. The gold field + * is even bigger due to its encoded dollar sign prefix. + */ +static struct istat_s initblstats[MAXBLSTATS] = { INIT_BLSTAT("title", "%s", ANY_STR, MAXVALWIDTH, BL_TITLE), INIT_BLSTAT("strength", " St:%s", ANY_INT, 10, BL_STR), INIT_BLSTAT("dexterity", " Dx:%s", ANY_INT, 10, BL_DX), @@ -521,35 +708,247 @@ STATIC_VAR struct istat_s initblstats[MAXBLSTATS] = { INIT_BLSTAT("intelligence", " In:%s", ANY_INT, 10, BL_IN), INIT_BLSTAT("wisdom", " Wi:%s", ANY_INT, 10, BL_WI), INIT_BLSTAT("charisma", " Ch:%s", ANY_INT, 10, BL_CH), - INIT_BLSTAT("alignment", " %s", ANY_STR, 40, BL_ALIGN), - INIT_BLSTAT("score", " S:%s", ANY_LONG, 20, BL_SCORE), + INIT_BLSTAT("alignment", " %s", ANY_STR, 20, BL_ALIGN), + INIT_BLSTAT("score", " S:%s", ANY_LONG, 30, BL_SCORE), INIT_BLSTAT("carrying-capacity", " %s", ANY_INT, 20, BL_CAP), - INIT_BLSTAT("gold", " %s", ANY_LONG, 30, BL_GOLD), + INIT_BLSTAT("gold", " %s", ANY_LONG, 40, BL_GOLD), INIT_BLSTATP("power", " Pw:%s", ANY_INT, 10, BL_ENEMAX, BL_ENE), INIT_BLSTAT("power-max", "(%s)", ANY_INT, 10, BL_ENEMAX), INIT_BLSTATP("experience-level", " Xp:%s", ANY_INT, 10, BL_EXP, BL_XP), INIT_BLSTAT("armor-class", " AC:%s", ANY_INT, 10, BL_AC), INIT_BLSTAT("HD", " HD:%s", ANY_INT, 10, BL_HD), - INIT_BLSTAT("time", " T:%s", ANY_LONG, 20, BL_TIME), + INIT_BLSTAT("time", " T:%s", ANY_LONG, 30, BL_TIME), /* hunger used to be 'ANY_UINT'; see note below in bot_via_windowport() */ - INIT_BLSTAT("hunger", " %s", ANY_INT, 40, BL_HUNGER), + INIT_BLSTAT("hunger", " %s", ANY_INT, 20, BL_HUNGER), INIT_BLSTATP("hitpoints", " HP:%s", ANY_INT, 10, BL_HPMAX, BL_HP), INIT_BLSTAT("hitpoints-max", "(%s)", ANY_INT, 10, BL_HPMAX), INIT_BLSTAT("dungeon-level", "%s", ANY_STR, MAXVALWIDTH, BL_LEVELDESC), - INIT_BLSTATP("experience", "/%s", ANY_LONG, 20, BL_EXP, BL_EXP), - INIT_BLSTAT("condition", "%s", ANY_MASK32, 0, BL_CONDITION) + INIT_BLSTATP("experience", "/%s", ANY_LONG, 30, BL_EXP, BL_EXP), + INIT_BLSTAT("condition", "%s", ANY_MASK32, 0, BL_CONDITION), + /* optional; once set it doesn't change unless 'showvers' option is + toggled or player modifies the 'versinfo' option; + available mostly for screenshots or someone looking over shoulder; + blstat[][BL_VERS] is actually an int copy of flags.versinfo (0...7) */ + INIT_BLSTAT("version", " %s", ANY_STR, MAXVALWIDTH, BL_VERS), + /* weapon and armor are constructed strings with no particular numeric + equivalent */ + INIT_BLSTAT("weapon", " %s", ANY_STR, 20, BL_WEAPON), + INIT_BLSTAT("armor", " %s", ANY_STR, 20, BL_ARMOR), + /* terrain is tracked by a number but designating it as type 'int' + isn't useful; using type 'string' allows highlighting based on text + matching which is potentially useful */ + INIT_BLSTAT("terrain", " %s", ANY_STR, 20, BL_TERRAIN), }; #undef INIT_BLSTATP #undef INIT_BLSTAT #undef INIT_THRESH -struct istat_s blstats[2][MAXBLSTATS]; -static boolean blinit = FALSE, update_all = FALSE; -static boolean valset[MAXBLSTATS]; #ifdef STATUS_HILITES -static long bl_hilite_moves = 0L; -#endif + +static const struct condmap condition_aliases[] = { + { "strangled", BL_MASK_STRNGL }, + { "all", BL_MASK_BAREH | BL_MASK_BLIND | BL_MASK_BUSY + | BL_MASK_CONF | BL_MASK_DEAF | BL_MASK_ELF_IRON + | BL_MASK_FLY | BL_MASK_FOODPOIS | BL_MASK_GLOWHANDS + | BL_MASK_GRAB | BL_MASK_HALLU | BL_MASK_HELD + | BL_MASK_ICY | BL_MASK_INLAVA | BL_MASK_LEV + | BL_MASK_PARLYZ | BL_MASK_RIDE | BL_MASK_SLEEPING + | BL_MASK_SLIME | BL_MASK_SLIPPERY | BL_MASK_STONE + | BL_MASK_STRNGL | BL_MASK_STUN | BL_MASK_SUBMERGED + | BL_MASK_TERMILL | BL_MASK_TETHERED + | BL_MASK_TRAPPED | BL_MASK_UNCONSC + | BL_MASK_WOUNDEDL | BL_MASK_HOLDING }, + { "major_troubles", BL_MASK_FOODPOIS | BL_MASK_GRAB | BL_MASK_INLAVA + | BL_MASK_SLIME | BL_MASK_STONE | BL_MASK_STRNGL + | BL_MASK_TERMILL }, + { "minor_troubles", BL_MASK_BLIND | BL_MASK_CONF | BL_MASK_DEAF + | BL_MASK_HALLU | BL_MASK_PARLYZ | BL_MASK_SUBMERGED + | BL_MASK_STUN }, + { "movement", BL_MASK_LEV | BL_MASK_FLY | BL_MASK_RIDE }, + { "opt_in", BL_MASK_BAREH | BL_MASK_BUSY | BL_MASK_GLOWHANDS + | BL_MASK_HELD | BL_MASK_ICY | BL_MASK_PARLYZ + | BL_MASK_SLEEPING | BL_MASK_SLIPPERY + | BL_MASK_SUBMERGED | BL_MASK_TETHERED + | BL_MASK_TRAPPED + | BL_MASK_UNCONSC | BL_MASK_WOUNDEDL + | BL_MASK_HOLDING }, +}; + +#endif /* STATUS_HILITES */ + +/* condition names and their abbreviations are used by windowport code */ +const struct conditions_t conditions[] = { + /* ranking, mask, identifier, txt1, txt2, txt3 */ + { 20, BL_MASK_BAREH, bl_bareh, { "Bare", "Bar", "Bh" } }, + { 10, BL_MASK_BLIND, bl_blind, { "Blind", "Blnd", "Bl" } }, + { 20, BL_MASK_BUSY, bl_busy, { "Busy", "Bsy", "By" } }, + { 10, BL_MASK_CONF, bl_conf, { "Conf", "Cnf", "Cf" } }, + { 10, BL_MASK_DEAF, bl_deaf, { "Deaf", "Def", "Df" } }, + { 15, BL_MASK_ELF_IRON, bl_elf_iron, { "Iron", "Irn", "Fe" } }, + { 10, BL_MASK_FLY, bl_fly, { "Fly", "Fly", "Fl" } }, + { 6, BL_MASK_FOODPOIS, bl_foodpois, { "FoodPois", "Fpois", "Poi" } }, + { 20, BL_MASK_GLOWHANDS, bl_glowhands, { "Glow", "Glo", "Gl" } }, + { 2, BL_MASK_GRAB, bl_grab, { "Grab", "Grb", "Gr" } }, + { 10, BL_MASK_HALLU, bl_hallu, { "Hallu", "Hal", "Hl" } }, + { 20, BL_MASK_HELD, bl_held, { "Held", "Hld", "Hd" } }, + { 20, BL_MASK_ICY, bl_icy, { "Icy", "Icy", "Ic" } }, + { 8, BL_MASK_INLAVA, bl_inlava, { "InLava", "Lav", "La" } }, + { 10, BL_MASK_LEV, bl_lev, { "Lev", "Lev", "Lv" } }, + { 20, BL_MASK_PARLYZ, bl_parlyz, { "Parlyz", "Para", "Par" } }, + { 10, BL_MASK_RIDE, bl_ride, { "Ride", "Rid", "Rd" } }, + { 20, BL_MASK_SLEEPING, bl_sleeping, { "Zzz", "Zzz", "Zz" } }, + { 6, BL_MASK_SLIME, bl_slime, { "Slime", "Slim", "Slm" } }, + { 20, BL_MASK_SLIPPERY, bl_slippery, { "Slip", "Slp", "Sl" } }, + { 6, BL_MASK_STONE, bl_stone, { "Stone", "Ston", "Sto" } }, + { 4, BL_MASK_STRNGL, bl_strngl, { "Strngl", "Stngl", "Str" } }, + { 10, BL_MASK_STUN, bl_stun, { "Stun", "Stun", "St" } }, + { 15, BL_MASK_SUBMERGED, bl_submerged, { "Submrg", "Subm", "Sm" } }, + { 6, BL_MASK_TERMILL, bl_termill, { "TermIll", "Ill", "Ill" } }, + { 20, BL_MASK_TETHERED, bl_tethered, { "Teth", "Tth", "Te" } }, + { 20, BL_MASK_TRAPPED, bl_trapped, { "Trap", "Trp", "Tr" } }, + { 20, BL_MASK_UNCONSC, bl_unconsc, { "Out", "Out", "KO" } }, + { 20, BL_MASK_WOUNDEDL, bl_woundedl, { "WLegs", "Leg", "Lg" } }, + { 20, BL_MASK_HOLDING, bl_holding, { "UHold", "UHld", "UHd" } }, +}; + +/* [perhaps these should all be opt_out with default of 'in'; + otherwise some players may never learn about them] */ +struct condtests_t condtests[CONDITION_COUNT] = { + /* id, useropt, opt_in or out, enabled, configchoice, testresult; + default value for enabled is !opt_in but can get changed via options */ + { bl_bareh, "barehanded", opt_in, FALSE, FALSE, FALSE }, + { bl_blind, "blind", opt_out, TRUE, FALSE, FALSE }, + { bl_busy, "busy", opt_in, FALSE, FALSE, FALSE }, + { bl_conf, "conf", opt_out, TRUE, FALSE, FALSE }, + { bl_deaf, "deaf", opt_out, TRUE, FALSE, FALSE }, + { bl_elf_iron, "iron", opt_out, TRUE, FALSE, FALSE }, + { bl_fly, "fly", opt_out, TRUE, FALSE, FALSE }, + { bl_foodpois, "foodPois", opt_out, TRUE, FALSE, FALSE }, + { bl_glowhands, "glowhands", opt_in, FALSE, FALSE, FALSE }, + { bl_grab, "grab", opt_out, TRUE, FALSE, FALSE }, + { bl_hallu, "hallucinat", opt_out, TRUE, FALSE, FALSE }, + { bl_held, "held", opt_in, FALSE, FALSE, FALSE }, + { bl_icy, "ice", opt_in, FALSE, FALSE, FALSE }, + { bl_inlava, "lava", opt_out, TRUE, FALSE, FALSE }, + { bl_lev, "levitate", opt_out, TRUE, FALSE, FALSE }, + { bl_parlyz, "paralyzed", opt_in, FALSE, FALSE, FALSE }, + { bl_ride, "ride", opt_out, TRUE, FALSE, FALSE }, + { bl_sleeping, "sleep", opt_in, FALSE, FALSE, FALSE }, + { bl_slime, "slime", opt_out, TRUE, FALSE, FALSE }, + { bl_slippery, "slip", opt_in, FALSE, FALSE, FALSE }, + { bl_stone, "stone", opt_out, TRUE, FALSE, FALSE }, + { bl_strngl, "strngl", opt_out, TRUE, FALSE, FALSE }, + { bl_stun, "stun", opt_out, TRUE, FALSE, FALSE }, + { bl_submerged, "submerged", opt_in, FALSE, FALSE, FALSE }, + { bl_termill, "termIll", opt_out, TRUE, FALSE, FALSE }, + { bl_tethered, "tethered", opt_in, FALSE, FALSE, FALSE }, + { bl_trapped, "trap", opt_in, FALSE, FALSE, FALSE }, + { bl_unconsc, "unconscious", opt_in, FALSE, FALSE, FALSE }, + { bl_woundedl, "woundedlegs", opt_in, FALSE, FALSE, FALSE }, + { bl_holding, "holding", opt_in, FALSE, FALSE, FALSE }, +}; +/* condition indexing */ +int cond_idx[CONDITION_COUNT] = { 0 }; + +static const char c_Wall[] = "Wall"; +/* + * Terrain descriptions for flags.terrainstatus; simplified from + * def_syms[].name and indexed by iflags.terrain_typ; should be + * kept in sync with rm.h types and the first half of def_syms[]. + * The extra pseudo-types are specified by classify_terrain() + * when it sets up iflags.terrain_typ. Walls and a few of the + * others can only occur when hero has the Passes_walls ability. + */ +const char *terrain_descr[] = { +/* 0*/ "Stone", /* stone */ + c_Wall, /* vwall */ + c_Wall, /* hwall */ + c_Wall, /* tlcorner */ + c_Wall, /* trcorner */ + c_Wall, /* blcorner */ + c_Wall, /* brcorner */ + c_Wall, /* crosswall */ + c_Wall, /* tuwall */ + c_Wall, /* tdwall */ +/*10*/ c_Wall, /* tlwall */ + c_Wall, /* trwall */ + "Portcullis", /* dbwall, closed drawbridge 'door' */ + "Tree", + c_Wall, /* sdoor: secret door */ + "Stone", /* scorr: secret corridor */ + "Pool", /* pool or non-moat water; can be boiled away */ + "Moat", /* water that can't be boiled away */ + "Water", /* water on Water level; can't be boiled or frozen */ + "(gap)", /* drawbridge_up; replaced by whatever is under */ +/*20*/ "Lava", /* lavapool */ + "LavaWall", /* lava that extends to ceiling */ + "Bars", /* ironbars */ + "Doorway", /* doorless or broken door; diagonal movement is ok */ + "Corridor", /* replaced by "Floor" */ + "Room", /* also replaced by "Floor" */ + "Stairs", + "Ladder", + "Fountain", + "Throne", +/*30*/ "Sink", + "Grave", + "Altar", + "Ice", + "Bridge", /* drawbridge_down, span across moat/ice/lava/floor */ + "Air", /* open air on Air level or bubble on Water level */ + "Cloud", /* [part of] a cloud or Air level */ + /* + */ +/*37*/ "", /* MAX_TYPE; skipped ratther than overloaded */ +/*38*/ c_Wall, /* MATCH_WALL for special levels; shouldn't happen */ + /* + * additional terrain names that aren't simple levl[][].typ values + */ +/*39*/ "Floor", /* substituted for room or corridor */ +/*40*/ "Ground", /* 'room' on Earth level */ + "Open-door", /* open (not broken or doorless) */ + "Shut-door", /* closed or locked (or trapped) */ + "Swamp", /* Juiblex level */ + "Submerged", /* under water */ + "Sea", /* moat terrain on Medusa's level: "shallow sea" */ + "WaterWall", /* water that extends to the ceiling */ +}; + +/* cache-related */ +static boolean cache_avail[3] = { FALSE, FALSE, FALSE }; +static boolean cache_reslt[3] = { FALSE, FALSE, FALSE }; +static const char *cache_nomovemsg = NULL, *cache_multi_reason = NULL; + +#define cond_cache_prepA() \ +do { \ + boolean clear_cache = FALSE, refresh_cache = FALSE; \ + \ + if (gm.multi < 0) { \ + if (gn.nomovemsg || gm.multi_reason) { \ + if (cache_nomovemsg != gn.nomovemsg) \ + refresh_cache = TRUE; \ + if (cache_multi_reason != gm.multi_reason) \ + refresh_cache = TRUE; \ + } else { \ + clear_cache = TRUE; \ + } \ + } else { \ + clear_cache = TRUE; \ + } \ + if (clear_cache) { \ + cache_nomovemsg = (const char *) 0; \ + cache_multi_reason = (const char *) 0; \ + } \ + if (refresh_cache) { \ + cache_nomovemsg = gn.nomovemsg; \ + cache_multi_reason = gm.multi_reason; \ + } \ + if (clear_cache || refresh_cache) { \ + cache_reslt[0] = cache_avail[0] = FALSE; \ + cache_reslt[1] = cache_avail[1] = FALSE; \ + } \ +} while (0) /* we don't put this next declaration in #ifdef STATUS_HILITES. * In the absence of STATUS_HILITES, each array @@ -558,27 +957,25 @@ static long bl_hilite_moves = 0L; * the final argument of status_update, with or * without STATUS_HILITES. */ -static unsigned long cond_hilites[BL_ATTCLR_MAX]; -static int now_or_before_idx = 0; /* 0..1 for array[2][] first index */ -STATIC_OVL void -bot_via_windowport() +staticfn void +bot_via_windowport(void) { char buf[BUFSZ]; const char *titl; - register char *nb; + char *nb; int i, idx, cap; long money; - if (!blinit) + if (!gb.blinit) panic("bot before init."); /* toggle from previous iteration */ - idx = 1 - now_or_before_idx; /* 0 -> 1, 1 -> 0 */ - now_or_before_idx = idx; + idx = 1 - gn.now_or_before_idx; /* 0 -> 1, 1 -> 0 */ + gn.now_or_before_idx = idx; /* clear the "value set" indicators */ - (void) memset((genericptr_t) valset, 0, MAXBLSTATS * sizeof (boolean)); + (void) memset((genericptr_t) gv.valset, 0, MAXBLSTATS * sizeof (boolean)); /* * Note: min(x,9999) - we enforce the same maximum on hp, maxhp, @@ -589,16 +986,16 @@ bot_via_windowport() /* * Player name and title. */ - Strcpy(nb = buf, plname); + Strcpy(nb = buf, svp.plname); nb[0] = highc(nb[0]); - titl = !Upolyd ? rank() : mons[u.umonnum].mname; + titl = !Upolyd ? rank() : pmname(&mons[u.umonnum], Ugender); i = (int) (strlen(buf) + sizeof " the " + strlen(titl) - sizeof ""); - /* if "Name the Rank/monster" is too long, we truncate the name - but always keep at least 10 characters of it; when hitpintbar is + /* if "Name the Rank/monster" is too long, we truncate the name but + always keep at least BOTL_NSIZ characters of it; when hitpointbar is enabled, anything beyond 30 (long monster name) will be truncated */ if (i > 30) { i = 30 - (int) (sizeof " the " + strlen(titl) - sizeof ""); - nb[max(i, 10)] = '\0'; + nb[max(i, BOTL_NSIZ)] = '\0'; } Strcpy(nb = eos(nb), " the "); Strcpy(nb = eos(nb), titl); @@ -607,30 +1004,30 @@ bot_via_windowport() if (i == 0 || nb[i - 1] == ' ') nb[i] = highc(nb[i]); } - Sprintf(blstats[idx][BL_TITLE].val, "%-30s", buf); - valset[BL_TITLE] = TRUE; /* indicate val already set */ + Sprintf(gb.blstats[idx][BL_TITLE].val, "%-30s", buf); + gv.valset[BL_TITLE] = TRUE; /* indicate val already set */ /* Strength */ - blstats[idx][BL_STR].a.a_int = ACURR(A_STR); - Strcpy(blstats[idx][BL_STR].val, get_strength_str()); - valset[BL_STR] = TRUE; /* indicate val already set */ + gb.blstats[idx][BL_STR].a.a_int = ACURR(A_STR); + Strcpy(gb.blstats[idx][BL_STR].val, get_strength_str()); + gv.valset[BL_STR] = TRUE; /* indicate val already set */ /* Dexterity, constitution, intelligence, wisdom, charisma. */ - blstats[idx][BL_DX].a.a_int = ACURR(A_DEX); - blstats[idx][BL_CO].a.a_int = ACURR(A_CON); - blstats[idx][BL_IN].a.a_int = ACURR(A_INT); - blstats[idx][BL_WI].a.a_int = ACURR(A_WIS); - blstats[idx][BL_CH].a.a_int = ACURR(A_CHA); + gb.blstats[idx][BL_DX].a.a_int = ACURR(A_DEX); + gb.blstats[idx][BL_CO].a.a_int = ACURR(A_CON); + gb.blstats[idx][BL_IN].a.a_int = ACURR(A_INT); + gb.blstats[idx][BL_WI].a.a_int = ACURR(A_WIS); + gb.blstats[idx][BL_CH].a.a_int = ACURR(A_CHA); /* Alignment */ - Strcpy(blstats[idx][BL_ALIGN].val, (u.ualign.type == A_CHAOTIC) + Strcpy(gb.blstats[idx][BL_ALIGN].val, (u.ualign.type == A_CHAOTIC) ? "Chaotic" : (u.ualign.type == A_NEUTRAL) ? "Neutral" : "Lawful"); /* Score */ - blstats[idx][BL_SCORE].a.a_long = + gb.blstats[idx][BL_SCORE].a.a_long = #ifdef SCORE_ON_BOTL flags.showscore ? botl_score() : #endif @@ -638,20 +1035,23 @@ bot_via_windowport() /* Hit points */ i = Upolyd ? u.mh : u.uhp; - if (i < 0) + if (i < 0) /* gameover sets u.uhp to -1 */ i = 0; - blstats[idx][BL_HP].a.a_int = min(i, 9999); + gb.blstats[idx][BL_HP].rawval.a_int = i; + gb.blstats[idx][BL_HP].a.a_int = min(i, 9999); i = Upolyd ? u.mhmax : u.uhpmax; - blstats[idx][BL_HPMAX].a.a_int = min(i, 9999); + gb.blstats[idx][BL_HPMAX].rawval.a_int = i; + gb.blstats[idx][BL_HPMAX].a.a_int = min(i, 9999); /* Dungeon level. */ - (void) describe_level(blstats[idx][BL_LEVELDESC].val); - valset[BL_LEVELDESC] = TRUE; /* indicate val already set */ + (void) describe_level(gb.blstats[idx][BL_LEVELDESC].val, 1); + gv.valset[BL_LEVELDESC] = TRUE; /* indicate val already set */ /* Gold */ - if ((money = money_cnt(invent)) < 0L) + if ((money = money_cnt(gi.invent)) < 0L) money = 0L; /* ought to issue impossible() and then discard gold */ - blstats[idx][BL_GOLD].a.a_long = min(money, 999999L); + gb.blstats[idx][BL_GOLD].rawval.a_long = money; + gb.blstats[idx][BL_GOLD].a.a_long = min(money, 999999L); /* * The tty port needs to display the current symbol for gold * as a field header, so to accommodate that we pass gold with @@ -667,103 +1067,433 @@ bot_via_windowport() * The currency prefix is encoded as ten character \GXXXXNNNN * sequence. */ - Sprintf(blstats[idx][BL_GOLD].val, "%s:%ld", + Sprintf(gb.blstats[idx][BL_GOLD].val, "%s:%ld", (iflags.in_dumplog || iflags.invis_goldsym) ? "$" : encglyph(objnum_to_glyph(GOLD_PIECE)), - blstats[idx][BL_GOLD].a.a_long); - valset[BL_GOLD] = TRUE; /* indicate val already set */ + gb.blstats[idx][BL_GOLD].a.a_long); + gv.valset[BL_GOLD] = TRUE; /* indicate val already set */ /* Power (magical energy) */ - blstats[idx][BL_ENE].a.a_int = min(u.uen, 9999); - blstats[idx][BL_ENEMAX].a.a_int = min(u.uenmax, 9999); + gb.blstats[idx][BL_ENE].rawval.a_int = u.uen; + gb.blstats[idx][BL_ENE].a.a_int = min(u.uen, 9999); + gb.blstats[idx][BL_ENEMAX].rawval.a_int = u.uenmax; + gb.blstats[idx][BL_ENEMAX].a.a_int = min(u.uenmax, 9999); /* Armor class */ - blstats[idx][BL_AC].a.a_int = u.uac; + gb.blstats[idx][BL_AC].a.a_int = u.uac; /* Monster level (if Upolyd) */ - blstats[idx][BL_HD].a.a_int = Upolyd ? (int) mons[u.umonnum].mlevel : 0; + gb.blstats[idx][BL_HD].a.a_int = Upolyd ? (int) mons[u.umonnum].mlevel : 0; /* Experience */ - blstats[idx][BL_XP].a.a_int = u.ulevel; - blstats[idx][BL_EXP].a.a_long = u.uexp; + gb.blstats[idx][BL_XP].a.a_int = u.ulevel; + gb.blstats[idx][BL_EXP].a.a_long = u.uexp; /* Time (moves) */ - blstats[idx][BL_TIME].a.a_long = moves; + gb.blstats[idx][BL_TIME].a.a_long = svm.moves; /* Hunger */ /* note: u.uhs is unsigned, and 3.6.1's STATUS_HILITE defined BL_HUNGER to be ANY_UINT, but that was the only non-int/non-long numeric field so it's far simpler to treat it as plain int and not need ANY_UINT handling at all */ - blstats[idx][BL_HUNGER].a.a_int = (int) u.uhs; - Strcpy(blstats[idx][BL_HUNGER].val, + gb.blstats[idx][BL_HUNGER].a.a_int = (int) u.uhs; + Strcpy(gb.blstats[idx][BL_HUNGER].val, (u.uhs != NOT_HUNGRY) ? hu_stat[u.uhs] : ""); - valset[BL_HUNGER] = TRUE; + gv.valset[BL_HUNGER] = TRUE; /* Carrying capacity */ cap = near_capacity(); - blstats[idx][BL_CAP].a.a_int = cap; - Strcpy(blstats[idx][BL_CAP].val, + gb.blstats[idx][BL_CAP].a.a_int = cap; + Strcpy(gb.blstats[idx][BL_CAP].val, (cap > UNENCUMBERED) ? enc_stat[cap] : ""); - valset[BL_CAP] = TRUE; + gv.valset[BL_CAP] = TRUE; + + /* Version; unchanging unless player toggles 'showvers' option or + modifies 'versinfo' option; toggling showvers off will clear it */ + if (gb.blstats[idx][BL_VERS].a.a_int != (int) flags.versinfo) { + gb.blstats[idx][BL_VERS].a.a_int = (int) flags.versinfo; + gv.valset[BL_VERS] = FALSE; + } + if (!gv.valset[BL_VERS]) { + (void) status_version(gb.blstats[idx][BL_VERS].val, + gb.blstats[idx][BL_VERS].valwidth, FALSE); + gv.valset[BL_VERS] = TRUE; + } /* Conditions */ - blstats[idx][BL_CONDITION].a.a_ulong = 0L; - if (Stoned) - blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_STONE; - if (Slimed) - blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_SLIME; - if (Strangled) - blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_STRNGL; - if (Sick && (u.usick_type & SICK_VOMITABLE) != 0) - blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_FOODPOIS; - if (Sick && (u.usick_type & SICK_NONVOMITABLE) != 0) - blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_TERMILL; + + gb.blstats[idx][BL_CONDITION].a.a_ulong = 0L; + /* - * basic formatting puts hunger status and encumbrance here + * Avoid anything that does string comparisons in here because this + * is called *extremely* often, for every screen update and the same + * string comparisons would be repeated, thus contributing toward + * performance degradation. If it is essential that string comparisons + * are needed for a particular condition, consider adding a caching + * mechanism to limit the string comparisons to the first occurrence + * for that cache lifetime. There is caching of that nature done for + * unconsc (1) and parlyz (2) because the suggested way of being able + * to distinguish unconsc, parlyz, sleeping, and busy involves multiple + * string comparisons. + * + * [Rebuttal: it's called a lot for Windows and MS-DOS because their + * sample run-time configuration file enables 'time' (move counter). + * The optimization to bypass full status update when only 'time' + * has changed (via timebot(), only effective for VIA_WINDOWPORT() + * configurations) should ameliorate that.] */ - if (Blind) - blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_BLIND; - if (Deaf) - blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_DEAF; - if (Stunned) - blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_STUN; - if (Confusion) - blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_CONF; - if (Hallucination) - blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_HALLU; - /* levitation and flying are mututally exclusive */ - if (Levitation) - blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_LEV; - if (Flying) - blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_FLY; - if (u.usteed) - blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_RIDE; - evaluate_and_notify_windowport(valset, idx); + +#define test_if_enabled(c) if (condtests[(c)].enabled) condtests[(c)].test + + condtests[bl_foodpois].test = condtests[bl_termill].test = FALSE; + if (Sick) { + test_if_enabled(bl_foodpois) = (u.usick_type & SICK_VOMITABLE) != 0; + test_if_enabled(bl_termill) = (u.usick_type & SICK_NONVOMITABLE) != 0; + } + condtests[bl_inlava].test = condtests[bl_tethered].test + = condtests[bl_trapped].test = FALSE; + if (u.utrap) { + test_if_enabled(bl_inlava) = (u.utraptype == TT_LAVA); + test_if_enabled(bl_tethered) = (u.utraptype == TT_BURIEDBALL); + /* if in-lava or tethered is disabled and the condition applies, + lump it in with trapped */ + test_if_enabled(bl_trapped) = (!condtests[bl_inlava].test + && !condtests[bl_tethered].test); + } + condtests[bl_grab].test = condtests[bl_held].test +#if 0 + = condtests[bl_engulfed].test +#endif + = condtests[bl_holding].test = FALSE; + if (u.ustuck) { + /* it is possible for a hero in sticks() form to be swallowed, + so swallowed needs to be checked first; it is not possible for + a hero in sticks() form to be held--sticky hero does the holding + even if u.ustuck is also a holder */ + if (u.uswallow) { + /* engulfed/swallowed isn't currently a tracked status condition; + "held" might look odd for it but seems better than blank */ +#if 0 + test_if_enabled(bl_engulfed) = TRUE; +#else + test_if_enabled(bl_held) = TRUE; +#endif + } else if (Upolyd && sticks(gy.youmonst.data)) { + test_if_enabled(bl_holding) = TRUE; + } else { + /* grab == hero is held by sea monster and about to be drowned; + held == hero is held by something else and can't move away */ + test_if_enabled(bl_grab) = (u.ustuck->data->mlet == S_EEL); + test_if_enabled(bl_held) = !condtests[bl_grab].test; + } + } + condtests[bl_blind].test = (Blind) ? TRUE : FALSE; + condtests[bl_conf].test = (Confusion) ? TRUE : FALSE; + condtests[bl_deaf].test = (Deaf) ? TRUE : FALSE; + condtests[bl_fly].test = (Flying) ? TRUE : FALSE; + condtests[bl_glowhands].test = (u.umconf) ? TRUE : FALSE; + condtests[bl_hallu].test = (Hallucination) ? TRUE : FALSE; + condtests[bl_lev].test = (Levitation) ? TRUE : FALSE; + condtests[bl_ride].test = (u.usteed) ? TRUE : FALSE; + condtests[bl_slime].test = (Slimed) ? TRUE : FALSE; + condtests[bl_stone].test = (Stoned) ? TRUE : FALSE; + condtests[bl_strngl].test = (Strangled) ? TRUE : FALSE; + condtests[bl_stun].test = (Stunned) ? TRUE : FALSE; + condtests[bl_submerged].test = (Underwater) ? TRUE : FALSE; + test_if_enabled(bl_elf_iron) = (FALSE); + test_if_enabled(bl_bareh) = (!uarmg && !uwep); + test_if_enabled(bl_icy) = (levl[u.ux][u.uy].typ == ICE); + test_if_enabled(bl_slippery) = (Glib) ? TRUE : FALSE; + test_if_enabled(bl_woundedl) = (Wounded_legs) ? TRUE : FALSE; + + if (gm.multi < 0) { + cond_cache_prepA(); + if (condtests[bl_unconsc].enabled + && cache_nomovemsg && !cache_avail[0]) { + cache_reslt[0] = (!u.usleep && unconscious()); + cache_avail[0] = TRUE; + } + if (condtests[bl_parlyz].enabled + && cache_multi_reason && !cache_avail[1]) { + cache_reslt[1] = (!strncmp(cache_multi_reason, "paralyzed", 9) + || !strncmp(cache_multi_reason, "frozen", 6)); + cache_avail[1] = TRUE; + } + if (cache_avail[0] && cache_reslt[0]) { + condtests[bl_unconsc].test = cache_reslt[0]; + } else if (cache_avail[1] && cache_reslt[1]) { + condtests[bl_parlyz].test = cache_reslt[1]; + } else if (condtests[bl_sleeping].enabled && u.usleep) { + condtests[bl_sleeping].test = TRUE; + } else if (condtests[bl_busy].enabled) { + condtests[bl_busy].test = TRUE; + } + } else { + condtests[bl_unconsc].test = condtests[bl_parlyz].test = + condtests[bl_sleeping].test = condtests[bl_busy].test = FALSE; + } + +#define cond_setbit(c) \ + gb.blstats[idx][BL_CONDITION].a.a_ulong |= conditions[(c)].mask + + for (i = 0; i < CONDITION_COUNT; ++i) { + if (condtests[i].enabled + /* && i != bl_holding */ /* uncomment to suppress UHold */ + && condtests[i].test) + cond_setbit(i); + } +#undef cond_bitset + + /* + * Optionally displayed weapon(s), armor, and terrain. + */ + if (flags.weaponstatus) + (void) weapon_status(gb.blstats[idx][BL_WEAPON].val); + else + *gb.blstats[idx][BL_WEAPON].val = '\0'; + + if (flags.armorstatus) + (void) armor_status(gb.blstats[idx][BL_ARMOR].val); + else + *gb.blstats[idx][BL_ARMOR].val = '\0'; + + if (flags.terrainstatus) { + if (iflags.terrain_typ == MAX_TYPE) + classify_terrain(); + i = iflags.terrain_typ; + if (gb.blstats[idx][BL_TERRAIN].a.a_int != i) { + Strcpy(gb.blstats[idx][BL_TERRAIN].val, terrain_descr[i]); + gb.blstats[idx][BL_TERRAIN].a.a_int = i; + } + } else { + *gb.blstats[idx][BL_TERRAIN].val = '\0'; + /* MAX_TYPE is "none of the above" for levl[][].typ */ + gb.blstats[idx][BL_TERRAIN].a.a_int = MAX_TYPE; + } + gv.valset[BL_TERRAIN] = TRUE; + + /* now request rendering */ + evaluate_and_notify_windowport(gv.valset, idx); +#undef test_if_enabled } +#undef cond_cache_prepA + /* update just the status lines' 'time' field */ -STATIC_OVL void -stat_update_time() +staticfn void +stat_update_time(void) { - int idx = now_or_before_idx; /* no 0/1 toggle */ + int idx = gn.now_or_before_idx; /* no 0/1 toggle */ int fld = BL_TIME; /* Time (moves) */ - blstats[idx][fld].a.a_long = moves; - valset[fld] = FALSE; + gb.blstats[idx][fld].a.a_long = svm.moves; + gv.valset[fld] = FALSE; - eval_notify_windowport_field(fld, valset, idx); + eval_notify_windowport_field(fld, gv.valset, idx); if ((windowprocs.wincap2 & WC2_FLUSH_STATUS) != 0L) status_update(BL_FLUSH, (genericptr_t) 0, 0, 0, NO_COLOR, (unsigned long *) 0); return; } -STATIC_OVL boolean -eval_notify_windowport_field(fld, valsetlist, idx) -int fld, idx; -boolean *valsetlist; +/* deal with player's choice to change processing of a condition */ +void +condopt(int idx, boolean *addr, boolean negated) +{ + int i; + + /* sanity check */ + if ((idx < 0 || idx >= CONDITION_COUNT) + || (addr && addr != &condtests[idx].choice)) + return; + + if (!addr) { + /* special: indicates a request to init so + set the choice values to match the defaults */ + gc.condmenu_sortorder = 0; + for (i = 0; i < CONDITION_COUNT; ++i) { + cond_idx[i] = i; + condtests[i].choice = condtests[i].enabled; + } + qsort((genericptr_t) cond_idx, CONDITION_COUNT, + sizeof cond_idx[0], cond_cmp); + } else { + /* (addr == &condtests[idx].choice) */ + condtests[idx].enabled = negated ? FALSE : TRUE; + condtests[idx].choice = condtests[idx].enabled; + /* avoid lingering false positives if test is no longer run */ + condtests[idx].test = FALSE; + } +} + +/* qsort callback routine for sorting the condition index */ +staticfn int QSORTCALLBACK +cond_cmp(const genericptr vptr1, const genericptr vptr2) +{ + int indx1 = *(int *) vptr1, indx2 = *(int *) vptr2, + c1 = conditions[indx1].ranking, c2 = conditions[indx2].ranking; + + if (c1 != c2) + return c1 - c2; + /* tie-breaker - visible alpha by name */ + return strcmpi(condtests[indx1].useroption, condtests[indx2].useroption); +} + +/* qsort callback routine for alphabetical sorting of index */ +staticfn int QSORTCALLBACK +menualpha_cmp(const genericptr vptr1, const genericptr vptr2) +{ + int indx1 = *(int *) vptr1, indx2 = *(int *) vptr2; + + return strcmpi(condtests[indx1].useroption, condtests[indx2].useroption); +} + +int +parse_cond_option(boolean negated, char *opts) +{ + int i, sl; + const char *compareto, *uniqpart, prefix[] = "cond_"; + + if (!opts || strlen(opts) <= sizeof prefix - 1) + return 2; + uniqpart = opts + (sizeof prefix - 1); + for (i = 0; i < CONDITION_COUNT; ++i) { + compareto = condtests[i].useroption; + sl = Strlen(compareto); + if (match_optname(uniqpart, compareto, (sl >= 4) ? 4 : sl, FALSE)) { + condopt(i, &condtests[i].choice, negated); + return 0; + } + } + return 1; /* !0 indicates error */ +} + +/* display a menu of all available status condition options and let player + toggled them on or off; returns True iff any changes are made */ +boolean +cond_menu(void) +{ + static const char *const menutitle[2] = { + "alphabetically", "by ranking" + }; + int i, res, idx = 0; + int sequence[CONDITION_COUNT]; + winid tmpwin; + anything any; + menu_item *picks = (menu_item *) 0; + char mbuf[QBUFSZ]; + boolean showmenu = TRUE; + int clr = NO_COLOR; + boolean changed = FALSE; + + do { + for (i = 0; i < CONDITION_COUNT; ++i) { + sequence[i] = i; + } + qsort((genericptr_t) sequence, CONDITION_COUNT, + sizeof sequence[0], + (gc.condmenu_sortorder) ? cond_cmp : menualpha_cmp); + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + + any = cg.zeroany; + any.a_int = 1; + Sprintf(mbuf, "change sort order from \"%s\" to \"%s\"", + menutitle[gc.condmenu_sortorder], + menutitle[1 - gc.condmenu_sortorder]); + add_menu(tmpwin, &nul_glyphinfo, &any, 'S', 0, ATR_NONE, + clr, mbuf, MENU_ITEMFLAGS_SKIPINVERT); + any = cg.zeroany; + Sprintf(mbuf, "sorted %s", menutitle[gc.condmenu_sortorder]); + add_menu_heading(tmpwin, mbuf); + for (i = 0; i < SIZE(condtests); i++) { + idx = sequence[i]; + Sprintf(mbuf, "cond_%-14s", condtests[idx].useroption); + any = cg.zeroany; + any.a_int = idx + 2; /* avoid zero and the sort change pick */ + condtests[idx].choice = FALSE; + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr, mbuf, + condtests[idx].enabled + ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); + } + + end_menu(tmpwin, "Choose status conditions to toggle"); + + res = select_menu(tmpwin, PICK_ANY, &picks); + destroy_nhwindow(tmpwin); + showmenu = FALSE; + if (res > 0) { + for (i = 0; i < res; i++) { + idx = picks[i].item.a_int; + if (idx == 1) { + /* sort change requested */ + gc.condmenu_sortorder = 1 - gc.condmenu_sortorder; + showmenu = TRUE; + break; /* for loop */ + } else { + idx -= 2; + condtests[idx].choice = TRUE; + } + } + free((genericptr_t) picks); + } + } while (showmenu); + + if (res >= 0) { + for (i = 0; i < CONDITION_COUNT; ++i) + if (condtests[i].enabled != condtests[i].choice) { + condtests[i].enabled = condtests[i].choice; + condtests[idx].test = FALSE; + disp.botl = changed = TRUE; + } + } + return changed; +} + +/* called by all_options_conds() to get value for next cond_xyz option + so that #saveoptions can collect it and write the set into new RC file. + returns zero-length string if the option is the default value. */ +boolean +opt_next_cond(int indx, char *outbuf) +{ + *outbuf = '\0'; + if (indx >= CONDITION_COUNT) + return FALSE; + + /* + * The entries are returned in internal order which requires the + * least code. It would be easy to sort them into alphabetic order + * (just sort all over again for every requested entry: + * int i, sequence[CONDITION_COUNT] + * for (i = 0; i < CONDITION_COUNT; ++i) sequence[i] = i; + * qsort(sequence, ..., menualpha_cmp); + * indx = sequence[indx]; + * Sprintf(outbuf, ...); + * with no need to hang on to 'sequence[]' between calls). + * + * But using 'severity order' isn't feasible unless the player has + * used 'mO' on conditions in this session. Even then, they would + * revert to the default order (whether internal or alphabetical) + * if #saveoptions got used in some later session where doset() + * wasn't used to choose their preferred order. + */ + + if ((condtests[indx].opt == opt_in && condtests[indx].enabled) + || (condtests[indx].opt == opt_out && !condtests[indx].enabled)) { + Sprintf(outbuf, "%scond_%s", condtests[indx].enabled ? "" : "!", + condtests[indx].useroption); + } + return TRUE; +} + +staticfn boolean +eval_notify_windowport_field( + int fld, + boolean *valsetlist, + int idx) { static int oldrndencode = 0; static nhsym oldgoldsym = 0; @@ -776,22 +1506,26 @@ boolean *valsetlist; /* * Now pass the changed values to window port. */ - anytype = blstats[idx][fld].anytype; - curr = &blstats[idx][fld]; - prev = &blstats[1 - idx][fld]; + anytype = gb.blstats[idx][fld].anytype; + curr = &gb.blstats[idx][fld]; + prev = &gb.blstats[1 - idx][fld]; color = NO_COLOR; - chg = update_all ? 0 : compare_blstats(prev, curr); + chg = gu.update_all ? 0 : compare_blstats(prev, curr); /* * TODO: * Dynamically update 'percent_matters' as rules are added or - * removed to track whether any of them are precentage rules. + * removed to track whether any of them are percentage rules. * Then there'll be no need to assume that non-Null 'thresholds' * means that percentages need to be kept up to date. * [Affects exp_percent_changing() too.] */ - if (((chg || update_all || fld == BL_XP) - && curr->percent_matters && curr->thresholds) + if (((chg || gu.update_all || fld == BL_XP) + && curr->percent_matters +#ifdef STATUS_HILITES + && curr->thresholds +#endif + ) /* when 'hitpointbar' is On, percent matters even if HP hasn't changed and has no percentage rules (in case HPmax has changed when HP hasn't, where we ordinarily wouldn't @@ -799,51 +1533,55 @@ boolean *valsetlist; || (fld == BL_HP && iflags.wc2_hitpointbar)) { fldmax = curr->idxmax; pc = (fldmax == BL_EXP) ? exp_percentage() - : (fldmax >= 0) ? percentage(curr, &blstats[idx][fldmax]) - : 0; /* bullet proofing; can't get here */ + : (fldmax >= 0 && fldmax < MAXBLSTATS) + ? percentage(curr, &gb.blstats[idx][fldmax]) + : 0; /* bullet proofing; can't get here */ if (pc != prev->percent_value) - chg = 1; + chg = (pc < prev->percent_value) ? -1 : 1; curr->percent_value = pc; } else { pc = 0; } /* Temporary? hack: moveloop()'s prolog for a new game sets - * context.rndencode after the status window has been init'd, + * svc.context.rndencode after the status window has been init'd, * so $:0 has already been encoded and cached by the window * port. Without this hack, gold's \G sequence won't be - * recognized and ends up being displayed as-is for 'update_all'. + * recognized and ends up being displayed as-is for 'gu.update_all'. * - * Also, even if context.rndencode hasn't changed and the + * Also, even if svc.context.rndencode hasn't changed and the * gold amount itself hasn't changed, the glyph portion of the * encoding may have changed if a new symset was put into effect. * * \GXXXXNNNN:25 - * XXXX = the context.rndencode portion + * XXXX = the svc.context.rndencode portion * NNNN = the glyph portion * 25 = the gold amount * * Setting 'chg = 2' is enough to render the field properly, but - * not to honor an initial highlight, so force 'update_all = TRUE'. + * not to honor an initial highlight, so force 'gu.update_all = TRUE'. */ if (fld == BL_GOLD - && (context.rndencode != oldrndencode - || showsyms[COIN_CLASS + SYM_OFF_O] != oldgoldsym)) { - update_all = TRUE; /* chg = 2; */ - oldrndencode = context.rndencode; - oldgoldsym = showsyms[COIN_CLASS + SYM_OFF_O]; + && (svc.context.rndencode != oldrndencode + || gs.showsyms[COIN_CLASS + SYM_OFF_O] != oldgoldsym)) { + gu.update_all = TRUE; /* chg = 2; */ + oldrndencode = svc.context.rndencode; + oldgoldsym = gs.showsyms[COIN_CLASS + SYM_OFF_O]; } reset = FALSE; #ifdef STATUS_HILITES - if (!update_all && !chg && curr->time) { - reset = hilite_reset_needed(prev, bl_hilite_moves); + if (gu.update_all) { + chg = 0; + curr->time = prev->time = 0L; + } else if (!chg && curr->time) { + reset = hilite_reset_needed(prev, gb.bl_hilite_moves); if (reset) curr->time = prev->time = 0L; } #endif - if (update_all || chg || reset) { + if (gu.update_all || chg || reset) { if (!valsetlist[fld]) (void) anything_to_s(curr->val, &curr->a, anytype); @@ -869,9 +1607,9 @@ boolean *valsetlist; status_update(fld, (genericptr_t) curr->val, chg, pc, color, (unsigned long *) 0); } else { - /* Color for conditions is done through cond_hilites[] */ + /* Color for conditions is done through gc.cond_hilites[] */ status_update(fld, (genericptr_t) &curr->a.a_ulong, - chg, pc, color, cond_hilites); + chg, pc, color, gc.cond_hilites); } curr->chg = prev->chg = TRUE; updated = TRUE; @@ -879,26 +1617,31 @@ boolean *valsetlist; return updated; } -STATIC_OVL void -evaluate_and_notify_windowport(valsetlist, idx) -int idx; -boolean *valsetlist; +staticfn void +evaluate_and_notify_windowport( + boolean *valsetlist, + int idx) { - int i, updated = 0, notpresent = 0; + int i, fld, updated = 0; /* * Now pass the changed values to window port. */ for (i = 0; i < MAXBLSTATS; i++) { - if (((i == BL_SCORE) && !flags.showscore) - || ((i == BL_EXP) && !flags.showexp) - || ((i == BL_TIME) && !flags.time) - || ((i == BL_HD) && !Upolyd) - || ((i == BL_XP || i == BL_EXP) && Upolyd)) { - notpresent++; + fld = initblstats[i].fld; + if (((fld == BL_SCORE) && !flags.showscore) + || ((fld == BL_EXP) && !flags.showexp) + || ((fld == BL_TIME) && !flags.time) + || ((fld == BL_HD) && !Upolyd) + || ((fld == BL_XP || fld == BL_EXP) && Upolyd) + || ((fld == BL_VERS) && !flags.showvers) + || ((fld == BL_TERRAIN) && !flags.terrainstatus) + || ((fld == BL_WEAPON) && !flags.weaponstatus) + || ((fld == BL_ARMOR) && !flags.armorstatus) + ) { continue; } - if (eval_notify_windowport_field(i, valsetlist, idx)) + if (eval_notify_windowport_field(fld, valsetlist, idx)) updated++; } /* @@ -910,9 +1653,9 @@ boolean *valsetlist; * fields that have changed since the previous update. * * In both of those situations, we need to force updates to - * all of the fields when context.botlx is set. The tty port in + * all of the fields when disp.botlx is set. The tty port in * particular has a problem if that isn't done, since the core sets - * context.botlx when a menu or text display obliterates the status + * disp.botlx when a menu or text display obliterates the status * line. * * For those situations, to trigger the full update of every field @@ -924,21 +1667,21 @@ boolean *valsetlist; * the display, call status_update() with BL_FLUSH. * */ - if (context.botlx && (windowprocs.wincap2 & WC2_RESET_STATUS) != 0L) + if (disp.botlx && (windowprocs.wincap2 & WC2_RESET_STATUS) != 0L) status_update(BL_RESET, (genericptr_t) 0, 0, 0, NO_COLOR, (unsigned long *) 0); - else if ((updated || context.botlx) + else if ((updated || disp.botlx) && (windowprocs.wincap2 & WC2_FLUSH_STATUS) != 0L) status_update(BL_FLUSH, (genericptr_t) 0, 0, 0, NO_COLOR, (unsigned long *) 0); - context.botl = context.botlx = iflags.time_botl = FALSE; - update_all = FALSE; + disp.botl = disp.botlx = disp.time_botl = FALSE; + gu.update_all = FALSE; } void -status_initialize(reassessment) -boolean reassessment; /* TRUE: just recheck fields w/o other initialization */ +status_initialize( + boolean reassessment) /* True: just recheck fields without other init */ { enum statusfields fld; boolean fldenabl; @@ -946,12 +1689,12 @@ boolean reassessment; /* TRUE: just recheck fields w/o other initialization */ const char *fieldfmt, *fieldname; if (!reassessment) { - if (blinit) + if (gb.blinit) impossible("2nd status_initialize with full init."); init_blstats(); (*windowprocs.win_status_init)(); - blinit = TRUE; - } else if (!blinit) { + gb.blinit = TRUE; + } else if (!gb.blinit) { panic("status 'reassess' before init"); } for (i = 0; i < MAXBLSTATS; ++i) { @@ -961,19 +1704,23 @@ boolean reassessment; /* TRUE: just recheck fields w/o other initialization */ : (fld == BL_EXP) ? (boolean) (flags.showexp && !Upolyd) : (fld == BL_XP) ? (boolean) !Upolyd : (fld == BL_HD) ? (boolean) Upolyd - : TRUE; + : (fld == BL_VERS) ? flags.showvers + : (fld == BL_WEAPON) ? flags.weaponstatus + : (fld == BL_ARMOR) ? flags.armorstatus + : (fld == BL_TERRAIN) ? flags.terrainstatus + : TRUE; fieldname = initblstats[i].fldname; fieldfmt = (fld == BL_TITLE && iflags.wc2_hitpointbar) ? "%-30.30s" : initblstats[i].fldfmt; status_enablefield(fld, fieldname, fieldfmt, fldenabl); } - update_all = TRUE; - context.botlx = TRUE; + gu.update_all = TRUE; + disp.botlx = TRUE; } void -status_finish() +status_finish(void) { int i; @@ -983,29 +1730,33 @@ status_finish() /* free memory that we alloc'd now */ for (i = 0; i < MAXBLSTATS; ++i) { - if (blstats[0][i].val) - free((genericptr_t) blstats[0][i].val), blstats[0][i].val = 0; - if (blstats[1][i].val) - free((genericptr_t) blstats[1][i].val), blstats[1][i].val = 0; + if (gb.blstats[0][i].val) + free((genericptr_t) gb.blstats[0][i].val), + gb.blstats[0][i].val = (char *) NULL; + if (gb.blstats[1][i].val) + free((genericptr_t) gb.blstats[1][i].val), + gb.blstats[1][i].val = (char *) NULL; #ifdef STATUS_HILITES /* pointer to an entry in thresholds list; Null it out since that list is about to go away */ - blstats[0][i].hilite_rule = blstats[1][i].hilite_rule = 0; - if (blstats[0][i].thresholds) { + gb.blstats[0][i].hilite_rule = gb.blstats[1][i].hilite_rule = 0; + if (gb.blstats[0][i].thresholds) { struct hilite_s *temp, *next; - for (temp = blstats[0][i].thresholds; temp; temp = next) { + for (temp = gb.blstats[0][i].thresholds; temp; temp = next) { next = temp->next; free((genericptr_t) temp); } - blstats[0][i].thresholds = blstats[1][i].thresholds = 0; + gb.blstats[0][i].thresholds + = gb.blstats[1][i].thresholds + = (struct hilite_s *) NULL; } #endif /* STATUS_HILITES */ } } -STATIC_OVL void -init_blstats() +staticfn void +init_blstats(void) { static boolean initalready = FALSE; int i, j; @@ -1017,18 +1768,19 @@ init_blstats() for (i = 0; i <= 1; ++i) { for (j = 0; j < MAXBLSTATS; ++j) { #ifdef STATUS_HILITES - struct hilite_s *keep_hilite_chain = blstats[i][j].thresholds; + struct hilite_s *keep_hilite_chain = gb.blstats[i][j].thresholds; #endif - blstats[i][j] = initblstats[j]; - blstats[i][j].a = zeroany; - if (blstats[i][j].valwidth) { - blstats[i][j].val = (char *) alloc(blstats[i][j].valwidth); - blstats[i][j].val[0] = '\0'; + gb.blstats[i][j] = initblstats[j]; + gb.blstats[i][j].a = cg.zeroany; + if (gb.blstats[i][j].valwidth) { + gb.blstats[i][j].val + = (char *) alloc(gb.blstats[i][j].valwidth); + gb.blstats[i][j].val[0] = '\0'; } else - blstats[i][j].val = (char *) 0; + gb.blstats[i][j].val = (char *) 0; #ifdef STATUS_HILITES - blstats[i][j].thresholds = keep_hilite_chain; + gb.blstats[i][j].thresholds = keep_hilite_chain; #endif } } @@ -1053,11 +1805,12 @@ init_blstats() * - for strings, 0 = stayed the same, 1 = changed * */ -STATIC_OVL int -compare_blstats(bl1, bl2) -struct istat_s *bl1, *bl2; +staticfn int +compare_blstats(struct istat_s *bl1, struct istat_s *bl2) { - int anytype, result = 0; + anything *a1, *a2; + boolean use_rawval; + int anytype, fld, result = 0; if (!bl1 || !bl2) { panic("compare_blstat: bad istat pointer %s, %s", @@ -1066,59 +1819,62 @@ struct istat_s *bl1, *bl2; anytype = bl1->anytype; if ((!bl1->a.a_void || !bl2->a.a_void) - && (anytype == ANY_IPTR || anytype == ANY_UPTR || anytype == ANY_LPTR - || anytype == ANY_ULPTR)) { + && (anytype == ANY_IPTR || anytype == ANY_UPTR + || anytype == ANY_LPTR || anytype == ANY_ULPTR)) { panic("compare_blstat: invalid pointer %s, %s", fmt_ptr((genericptr_t) bl1->a.a_void), fmt_ptr((genericptr_t) bl2->a.a_void)); } + /* cheat; terrain is highlighted as a string but we have a handy int + reflecting its value to use when checking for changes */ + if (bl1->fld == BL_TERRAIN) + anytype = ANY_INT; + + fld = bl1->fld; + use_rawval = (fld == BL_HP || fld == BL_HPMAX + || fld == BL_ENE || fld == BL_ENEMAX + || fld == BL_GOLD); + a1 = use_rawval ? &bl1->rawval : &bl1->a; + a2 = use_rawval ? &bl2->rawval : &bl2->a; switch (anytype) { case ANY_INT: - result = (bl1->a.a_int < bl2->a.a_int) - ? 1 - : (bl1->a.a_int > bl2->a.a_int) ? -1 : 0; + result = (a1->a_int < a2->a_int) ? 1 + : (a1->a_int > a2->a_int) ? -1 : 0; break; case ANY_IPTR: - result = (*bl1->a.a_iptr < *bl2->a.a_iptr) - ? 1 - : (*bl1->a.a_iptr > *bl2->a.a_iptr) ? -1 : 0; + result = (*a1->a_iptr < *a2->a_iptr) ? 1 + : (*a1->a_iptr > *a2->a_iptr) ? -1 : 0; break; case ANY_LONG: - result = (bl1->a.a_long < bl2->a.a_long) - ? 1 - : (bl1->a.a_long > bl2->a.a_long) ? -1 : 0; + result = (a1->a_long < a2->a_long) ? 1 + : (a1->a_long > a2->a_long) ? -1 : 0; break; case ANY_LPTR: - result = (*bl1->a.a_lptr < *bl2->a.a_lptr) - ? 1 - : (*bl1->a.a_lptr > *bl2->a.a_lptr) ? -1 : 0; + result = (*a1->a_lptr < *a2->a_lptr) ? 1 + : (*a1->a_lptr > *a2->a_lptr) ? -1 : 0; break; case ANY_UINT: - result = (bl1->a.a_uint < bl2->a.a_uint) - ? 1 - : (bl1->a.a_uint > bl2->a.a_uint) ? -1 : 0; + result = (a1->a_uint < a2->a_uint) ? 1 + : (a1->a_uint > a2->a_uint) ? -1 : 0; break; case ANY_UPTR: - result = (*bl1->a.a_uptr < *bl2->a.a_uptr) - ? 1 - : (*bl1->a.a_uptr > *bl2->a.a_uptr) ? -1 : 0; + result = (*a1->a_uptr < *a2->a_uptr) ? 1 + : (*a1->a_uptr > *a2->a_uptr) ? -1 : 0; break; case ANY_ULONG: - result = (bl1->a.a_ulong < bl2->a.a_ulong) - ? 1 - : (bl1->a.a_ulong > bl2->a.a_ulong) ? -1 : 0; + result = (a1->a_ulong < a2->a_ulong) ? 1 + : (a1->a_ulong > a2->a_ulong) ? -1 : 0; break; case ANY_ULPTR: - result = (*bl1->a.a_ulptr < *bl2->a.a_ulptr) - ? 1 - : (*bl1->a.a_ulptr > *bl2->a.a_ulptr) ? -1 : 0; + result = (*a1->a_ulptr < *a2->a_ulptr) ? 1 + : (*a1->a_ulptr > *a2->a_ulptr) ? -1 : 0; break; case ANY_STR: result = sgn(strcmp(bl1->val, bl2->val)); break; case ANY_MASK32: - result = (bl1->a.a_ulong != bl2->a.a_ulong); + result = (a1->a_ulong != a2->a_ulong); break; default: result = 1; @@ -1126,11 +1882,8 @@ struct istat_s *bl1, *bl2; return result; } -STATIC_OVL char * -anything_to_s(buf, a, anytype) -char *buf; -anything *a; -int anytype; +staticfn char * +anything_to_s(char *buf, anything *a, int anytype) { if (!buf) return (char *) 0; @@ -1173,11 +1926,8 @@ int anytype; } #ifdef STATUS_HILITES -STATIC_OVL void -s_to_anything(a, buf, anytype) -anything *a; -char *buf; -int anytype; +staticfn void +s_to_anything(anything *a, char *buf, int anytype) { if (!buf || !a) return; @@ -1222,16 +1972,18 @@ int anytype; } #endif /* STATUS_HILITES */ -STATIC_OVL int -percentage(bl, maxbl) -struct istat_s *bl, *maxbl; +/* integer percentage is 100 * bl->a / maxbl->a */ +staticfn int +percentage(struct istat_s *bl, struct istat_s *maxbl) { int result = 0; int anytype; - int ival; + int ival, mval; long lval; unsigned uval; unsigned long ulval; + int fld; + boolean use_rawval; if (!bl || !maxbl) { impossible("percentage: bad istat pointer %s, %s", @@ -1239,13 +1991,19 @@ struct istat_s *bl, *maxbl; return 0; } + fld = bl->fld; + use_rawval = (fld == BL_HP || fld == BL_ENE); ival = 0, lval = 0L, uval = 0U, ulval = 0UL; anytype = bl->anytype; if (maxbl->a.a_void) { switch (anytype) { case ANY_INT: - ival = bl->a.a_int; - result = ((100 * ival) / maxbl->a.a_int); + /* HP and energy are int so this is the only case that cares + about 'rawval'; for them, we use that rather than their + potentially truncated (to 9999) display value */ + ival = use_rawval ? bl->rawval.a_int : bl->a.a_int; + mval = use_rawval ? maxbl->rawval.a_int : maxbl->a.a_int; + result = ((100 * ival) / mval); break; case ANY_LONG: lval = bl->a.a_long; @@ -1281,7 +2039,8 @@ struct istat_s *bl, *maxbl; from a non-zero input; note: if we ever change to something like ((((1000 * val) / max) + 5) / 10) for a rounded result, we'll also need to check for and convert false 100 to 99 */ - if (result == 0 && (ival != 0 || lval != 0L || uval != 0U || ulval != 0UL)) + if (result == 0 + && (ival != 0 || lval != 0L || uval != 0U || ulval != 0UL)) result = 1; return result; @@ -1289,8 +2048,8 @@ struct istat_s *bl, *maxbl; /* percentage for both xp (level) and exp (points) is the percentage for (curr_exp - this_level_start) in (next_level_start - this_level_start) */ -STATIC_OVL int -exp_percentage() +staticfn int +exp_percentage(void) { int res = 0; @@ -1313,9 +2072,10 @@ exp_percentage() struct istat_s curval, maxval; curval.anytype = maxval.anytype = ANY_LONG; - curval.a = maxval.a = zeroany; + curval.a = maxval.a = cg.zeroany; curval.a.a_long = exp_val; maxval.a.a_long = nxt_exp_val; + curval.fld = maxval.fld = BL_EXP; /* (neither BL_HP nor BL_ENE) */ /* maximum delta between levels is 10000000; calculation of 100 * (10000000 - N) / 10000000 fits within 32-bit long */ res = percentage(&curval, &maxval); @@ -1327,30 +2087,38 @@ exp_percentage() /* experience points have changed but experience level hasn't; decide whether botl update is needed for a different percentage highlight rule for Xp */ boolean -exp_percent_changing() +exp_percent_changing(void) { - int pc, color_dummy; + int pc; anything a; +#ifdef STATUS_HILITES + int color_dummy; struct hilite_s *rule; +#endif struct istat_s *curr; /* if status update is already requested, skip this processing */ - if (!context.botl) { + if (!disp.botl) { /* * Status update is warranted iff percent integer changes and the new * percentage results in a different highlighting rule being selected. */ - curr = &blstats[now_or_before_idx][BL_XP]; + curr = &gb.blstats[gn.now_or_before_idx][BL_XP]; /* TODO: [see eval_notify_windowport_field() about percent_matters and the check against 'thresholds'] */ - if (curr->percent_matters && curr->thresholds + if (curr->percent_matters +#ifdef STATUS_HILITES + && curr->thresholds +#endif && (pc = exp_percentage()) != curr->percent_value) { - a = zeroany; + a = cg.zeroany; a.a_int = (int) u.ulevel; - rule = get_hilite(now_or_before_idx, BL_XP, +#ifdef STATUS_HILITES + rule = get_hilite(gn.now_or_before_idx, BL_XP, (genericptr_t) &a, 0, pc, &color_dummy); if (rule != curr->hilite_rule) - return TRUE; /* caller should set 'context.botl' to True */ + return TRUE; /* caller should set 'disp.botl' to True */ +#endif } } return FALSE; @@ -1360,12 +2128,12 @@ exp_percent_changing() to reconstruct that from the encumbrance string or asking the general core what the value is */ int -stat_cap_indx() +stat_cap_indx(void) { int cap; #ifdef STATUS_HILITES - cap = blstats[now_or_before_idx][BL_CAP].a.a_int; + cap = gb.blstats[gn.now_or_before_idx][BL_CAP].a.a_int; #else cap = near_capacity(); #endif @@ -1375,12 +2143,12 @@ stat_cap_indx() /* callback so that interface can get hunger index rather than trying to reconstruct that from the hunger string or dipping into core internals */ int -stat_hunger_indx() +stat_hunger_indx(void) { int uhs; #ifdef STATUS_HILITES - uhs = blstats[now_or_before_idx][BL_HUNGER].a.a_int; + uhs = gb.blstats[gn.now_or_before_idx][BL_HUNGER].a.a_int; #else uhs = (int) u.uhs; #endif @@ -1389,23 +2157,33 @@ stat_hunger_indx() /* used by X11 for "tty status" even when STATUS_HILITES is disabled */ const char * -bl_idx_to_fldname(idx) -int idx; +bl_idx_to_fldname(int idx) { if (idx >= 0 && idx < MAXBLSTATS) return initblstats[idx].fldname; return (const char *) 0; } +/* used when rendering hitpointbar; inoutbuf[] has been padded with + trailing spaces; replace pairs of spaces with pairs of space+dash */ +void +repad_with_dashes(char *inoutbuf) +{ + char *p = eos(inoutbuf); + + while (p >= inoutbuf + 2 && p[-1] == ' ' && p[-2] == ' ') { + p[-1] = '-'; + p -= 2; + } +} + #ifdef STATUS_HILITES /****************************************************************************/ /* Core status hiliting support */ /****************************************************************************/ -struct hilite_s status_hilites[MAXBLSTATS]; - -static struct fieldid_t { +static const struct fieldid_t { const char *fieldname; enum statusfields fldid; } fieldids_alias[] = { @@ -1430,7 +2208,7 @@ static struct fieldid_t { { "xp", BL_EXP }, { "exp", BL_EXP }, { "flags", BL_CONDITION }, - {0, BL_FLUSH } + { NULL, BL_FLUSH } }; /* format arguments */ @@ -1439,9 +2217,8 @@ static const char threshold_value[] = "hilite_status threshold ", /* field name to bottom line index */ -STATIC_OVL enum statusfields -fldname_to_bl_indx(name) -const char *name; +staticfn enum statusfields +fldname_to_bl_indx(const char *name) { int i, nmatches = 0, fld = 0; @@ -1452,7 +2229,6 @@ const char *name; fld = initblstats[i].fld; nmatches++; } - if (!nmatches) { /* check aliases */ for (i = 0; fieldids_alias[i].fieldname; i++) @@ -1462,7 +2238,6 @@ const char *name; nmatches++; } } - if (!nmatches) { /* check partial matches to canonical names */ int len = (int) strlen(name); @@ -1478,16 +2253,16 @@ const char *name; return (nmatches == 1) ? fld : BL_FLUSH; } -STATIC_OVL boolean -hilite_reset_needed(bl_p, augmented_time) -struct istat_s *bl_p; -long augmented_time; /* no longer augmented; it once encoded fractional - * amounts for multiple moves within same turn */ +staticfn boolean +hilite_reset_needed( + struct istat_s *bl_p, + long augmented_time) /* no longer augmented; it once encoded fractional + * amounts for multiple moves within same turn */ { /* * This 'multi' handling may need some tuning... */ - if (multi) + if (gm.multi) return FALSE; if (!Is_Temp_Hilite(bl_p->hilite_rule)) @@ -1501,64 +2276,64 @@ long augmented_time; /* no longer augmented; it once encoded fractional /* called from moveloop(); sets context.botl if temp hilites have timed out */ void -status_eval_next_unhilite() +status_eval_next_unhilite(void) { int i; struct istat_s *curr; long next_unhilite, this_unhilite; - bl_hilite_moves = moves; /* simpllfied; used to try to encode fractional - * amounts for multiple moves within same turn */ + gb.bl_hilite_moves = svm.moves; /* simplified; at one point we used to + * try to encode fractional amounts for + * multiple moves within same turn */ /* figure out whether an unhilight needs to be performed now */ next_unhilite = 0L; for (i = 0; i < MAXBLSTATS; ++i) { - curr = &blstats[0][i]; /* blstats[0][*].time == blstats[1][*].time */ + curr = &gb.blstats[0][i]; /* blstats[0][*].time==blstats[1][*].time */ if (curr->chg) { - struct istat_s *prev = &blstats[1][i]; + struct istat_s *prev = &gb.blstats[1][i]; if (Is_Temp_Hilite(curr->hilite_rule)) - curr->time = prev->time = (bl_hilite_moves - + iflags.hilite_delta); + curr->time = (gb.bl_hilite_moves + iflags.hilite_delta); else - curr->time = prev->time = 0L; + curr->time = 0L; + prev->time = curr->time; curr->chg = prev->chg = FALSE; - context.botl = TRUE; + disp.botl = TRUE; } - if (context.botl) - continue; /* just process other blstats[][].time and .chg */ + if (disp.botl) + continue; /* just process other gb.blstats[][].time and .chg */ this_unhilite = curr->time; if (this_unhilite > 0L && (next_unhilite == 0L || this_unhilite < next_unhilite) && hilite_reset_needed(curr, this_unhilite + 1L)) { next_unhilite = this_unhilite; - if (next_unhilite < bl_hilite_moves) - context.botl = TRUE; + if (next_unhilite < gb.bl_hilite_moves) + disp.botl = TRUE; } } } /* called by options handling when 'statushilites' value is changed */ void -reset_status_hilites() +reset_status_hilites(void) { if (iflags.hilite_delta) { int i; for (i = 0; i < MAXBLSTATS; ++i) - blstats[0][i].time = blstats[1][i].time = 0L; - update_all = TRUE; + gb.blstats[0][i].time = gb.blstats[1][i].time = 0L; + gu.update_all = TRUE; } - context.botlx = TRUE; + disp.botlx = TRUE; } /* test whether the text from a title rule matches the string for title-while-polymorphed in the 'textmatch' menu */ -STATIC_OVL boolean -noneoftheabove(hl_text) -const char *hl_text; +staticfn boolean +noneoftheabove(const char *hl_text) { if (fuzzymatch(hl_text, "none of the above", "\" -_", TRUE) || fuzzymatch(hl_text, "(polymorphed)", "\"()", TRUE) @@ -1585,11 +2360,12 @@ const char *hl_text; * Get back: * pointer to rule that applies; Null if no rule does. */ -STATIC_OVL struct hilite_s * -get_hilite(idx, fldidx, vp, chg, pc, colorptr) -int idx, fldidx, chg, pc; -genericptr_t vp; -int *colorptr; +staticfn struct hilite_s * +get_hilite( + int idx, int fldidx, + genericptr_t vp, + int chg, int pc, + int *colorptr) { struct hilite_s *hl, *rule = 0; anything *value = (anything *) vp; @@ -1609,11 +2385,20 @@ int *colorptr; ancient configurations; we don't need LONG_MIN */ long max_lval = -LONG_MAX, min_lval = LONG_MAX; boolean exactmatch = FALSE, updown = FALSE, changed = FALSE, - perc_or_abs = FALSE; + perc_or_abs = FALSE, crit_hp = FALSE; /* min_/max_ are used to track best fit */ - for (hl = blstats[0][fldidx].thresholds; hl; hl = hl->next) { + for (hl = gb.blstats[0][fldidx].thresholds; hl; hl = hl->next) { dt = initblstats[fldidx].anytype; /* only needed for 'absolute' */ + /* for HP, if we already have a critical-hp rule then we ignore + other HP rules unless we hit another critical-hp one (last + one found wins); critical-hp takes precedence over temporary + HP highlights, otherwise a hero with regeneration and an up + or changed rule for HP would always show that up or changed + highlight even when within the critical-hp threshold because + the value will go up by at least one on every move */ + if (crit_hp && hl->behavior != BL_TH_CRITICALHP) + continue; /* if we've already matched a temporary highlight, it takes precedence over all persistent ones; we still process updown rules to get the last one which qualifies */ @@ -1747,10 +2532,10 @@ int *colorptr; } break; case BL_TH_TEXTMATCH: /* ANY_STR */ - txtstr = blstats[idx][fldidx].val; + txtstr = gb.blstats[idx][fldidx].val; if (fldidx == BL_TITLE) /* " the ", skip past " the " */ - txtstr += (strlen(plname) + sizeof " the " - sizeof ""); + txtstr += strlen(svp.plname) + sizeof " the " - sizeof ""; if (hl->rel == TXT_VALUE && hl->textmatch[0]) { if (fuzzymatch(hl->textmatch, txtstr, "\" -_", TRUE)) { rule = hl; @@ -1766,6 +2551,13 @@ int *colorptr; case BL_TH_ALWAYS_HILITE: rule = hl; break; + case BL_TH_CRITICALHP: + if (fldidx == BL_HP && critically_low_hp(FALSE)) { + rule = hl; + crit_hp = TRUE; + updown = changed = perc_or_abs = FALSE; + } + break; case BL_TH_NONE: break; default: @@ -1777,10 +2569,11 @@ int *colorptr; return rule; } -STATIC_OVL void -split_clridx(idx, coloridx, attrib) -int idx; -int *coloridx, *attrib; +#undef has_hilite +#undef Is_Temp_Hilite + +staticfn void +split_clridx(int idx, int *coloridx, int *attrib) { if (coloridx) *coloridx = idx & 0x00FF; @@ -1797,9 +2590,7 @@ int *coloridx, *attrib; * and configure the hilite. */ boolean -parse_status_hl1(op, from_configfile) -char *op; -boolean from_configfile; +parse_status_hl1(char *op, boolean from_configfile) { #define MAX_THRESH 21 char hsbuf[MAX_THRESH][QBUFSZ]; @@ -1849,13 +2640,16 @@ boolean from_configfile; } if (badopt) return FALSE; + /* make sure highlighting is On; use short duration for temp highlights */ + if (!iflags.hilite_delta) + iflags.hilite_delta = 3L; return TRUE; +#undef MAX_THRESH } /* is str in the format of "[<>]?=?[-+]?[0-9]+%?" regex */ -STATIC_OVL boolean -is_ltgt_percentnumber(str) -const char *str; +staticfn boolean +is_ltgt_percentnumber(const char *str) { const char *s = str; @@ -1875,14 +2669,13 @@ const char *str; } /* does str only contain "<>=-+0-9%" chars */ -STATIC_OVL boolean -has_ltgt_percentnumber(str) -const char *str; +staticfn boolean +has_ltgt_percentnumber(const char *str) { const char *s = str; while (*s) { - if (!index("<>=-+0123456789%", *s)) + if (!strchr("<>=-+0123456789%", *s)) return FALSE; s++; } @@ -1890,15 +2683,11 @@ const char *str; } /* splitsubfields(): splits str in place into '+' or '&' separated strings. - * returns number of strings, or -1 if more than maxsf or MAX_SUBFIELDS - */ -#define MAX_SUBFIELDS 16 -STATIC_OVL int -splitsubfields(str, sfarr, maxsf) -char *str; -char ***sfarr; -int maxsf; + returns number of strings, or -1 if more than maxsf or MAX_SUBFIELDS */ +staticfn int +splitsubfields(char *str, char ***sfarr, int maxsf) { +#define MAX_SUBFIELDS 16 static char *subfields[MAX_SUBFIELDS]; char *st = (char *) 0; int sf = 0; @@ -1910,7 +2699,7 @@ int maxsf; maxsf = (maxsf == 0) ? MAX_SUBFIELDS : min(maxsf, MAX_SUBFIELDS); - if (index(str, '+') || index(str, '&')) { + if (strchr(str, '+') || strchr(str, '&')) { char *c = str; sf = 0; @@ -1934,15 +2723,15 @@ int maxsf; } *sfarr = subfields; return sf; -} #undef MAX_SUBFIELDS +} -STATIC_OVL boolean -is_fld_arrayvalues(str, arr, arrmin, arrmax, retidx) -const char *str; -const char *const *arr; -int arrmin, arrmax; -int *retidx; +staticfn boolean +is_fld_arrayvalues( + const char *str, + const char *const *arr, + int arrmin, int arrmax, + int *retidx) { int i; @@ -1954,26 +2743,29 @@ int *retidx; return FALSE; } -STATIC_OVL int -query_arrayvalue(querystr, arr, arrmin, arrmax) -const char *querystr; -const char *const *arr; -int arrmin, arrmax; +staticfn int +query_arrayvalue( + const char *querystr, + const char *const *arr, + int arrmin, int arrmax) { int i, res, ret = arrmin - 1; winid tmpwin; anything any; menu_item *picks = (menu_item *) 0; int adj = (arrmin > 0) ? 1 : arrmax; + int clr = NO_COLOR; tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); for (i = arrmin; i < arrmax; i++) { - any = zeroany; + if (!arr[i]) /* the array of hunger status values has a gap ...*/ + continue; /*... set to Null between Satiated and Hungry */ + any = cg.zeroany; any.a_int = i + adj; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - arr[i], MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, + clr, arr[i], MENU_ITEMFLAGS_NONE); } end_menu(tmpwin, querystr); @@ -1988,12 +2780,10 @@ int arrmin, arrmax; return ret; } -STATIC_OVL void -status_hilite_add_threshold(fld, hilite) -int fld; -struct hilite_s *hilite; +staticfn void +status_hilite_add_threshold(int fld, struct hilite_s *hilite) { - struct hilite_s *new_hilite; + struct hilite_s *new_hilite, *old_hilite; if (!hilite) return; @@ -2004,39 +2794,46 @@ struct hilite_s *hilite; new_hilite->set = TRUE; new_hilite->fld = fld; - new_hilite->next = blstats[0][fld].thresholds; - blstats[0][fld].thresholds = new_hilite; + new_hilite->next = (struct hilite_s *) 0; + /* insert new entry at the end of the list */ + if (!gb.blstats[0][fld].thresholds) { + gb.blstats[0][fld].thresholds = new_hilite; + } else { + for (old_hilite = gb.blstats[0][fld].thresholds; old_hilite->next; + old_hilite = old_hilite->next) + continue; + old_hilite->next = new_hilite; + } /* sort_hilites(fld) */ /* current and prev must both point at the same hilites */ - blstats[1][fld].thresholds = blstats[0][fld].thresholds; + gb.blstats[1][fld].thresholds = gb.blstats[0][fld].thresholds; } - -STATIC_OVL boolean -parse_status_hl2(s, from_configfile) -char (*s)[QBUFSZ]; -boolean from_configfile; +staticfn boolean +parse_status_hl2(char (*s)[QBUFSZ], boolean from_configfile) { + static const char *const aligntxt[] = { "chaotic", "neutral", "lawful" }; + /* hu_stat[] from eat.c has trailing spaces which foul up comparisons; + for the "not hungry" case, there's no text hence no way to highlight */ + static const char *const hutxt[] = { + "Satiated", "", "Hungry", "Weak", "Fainting", "Fainted", "Starved" + }; char *tmp, *how; - int sidx = 0, i = -1, dt = -1; + int sidx = 0, i = -1, dt = ANY_INVALID; int coloridx = -1, successes = 0; int disp_attrib = 0; boolean percent, changed, numeric, down, up, - gt, lt, ge, le, eq, txtval, always; + grt, lt, gte, le, eq, txtval, always, criticalhp; const char *txt; enum statusfields fld = BL_FLUSH; struct hilite_s hilite; char tmpbuf[BUFSZ]; - static const char *aligntxt[] = { "chaotic", "neutral", "lawful" }; - /* hu_stat[] from eat.c has trailing spaces which foul up comparisons */ - static const char *hutxt[] = { "Satiated", "", "Hungry", "Weak", - "Fainting", "Fainted", "Starved" }; /* Examples: 3.6.1: OPTION=hilite_status: hitpoints/<10%/red - OPTION=hilite_status: hitpoints/<10%/red/<5%/purple/1/red+blink+inverse + OPTION=hilite_status: hitpoints/<10%/red/<5%/purple/1/red&blink+inverse OPTION=hilite_status: experience/down/red/up/green OPTION=hilite_status: cap/strained/yellow/overtaxed/orange OPTION=hilite_status: title/always/blue @@ -2066,20 +2863,21 @@ boolean from_configfile; return parse_condition(s, sidx); ++sidx; - while (s[sidx]) { + while (s[sidx][0]) { char buf[BUFSZ], **subfields; int sf = 0; /* subfield count */ int kidx; - txt = (const char *)0; + txt = (const char *) 0; percent = numeric = always = FALSE; down = up = changed = FALSE; - gt = ge = eq = le = lt = txtval = FALSE; - - /* threshold value */ + criticalhp = FALSE; + grt = gte = eq = le = lt = txtval = FALSE; +#if 0 + /* threshold value - return on empty string */ if (!s[sidx][0]) return TRUE; - +#endif memset((genericptr_t) &hilite, 0, sizeof (struct hilite_s)); hilite.set = FALSE; /* mark it "unset" */ hilite.fld = fld; @@ -2118,6 +2916,8 @@ boolean from_configfile; txtval = TRUE; } else if (!strcmpi(s[sidx], "changed")) { changed = TRUE; + } else if (fld == BL_HP && !strcmpi(s[sidx], "criticalhp")) { + criticalhp = TRUE; } else if (is_ltgt_percentnumber(s[sidx])) { const char *op; @@ -2131,9 +2931,9 @@ boolean from_configfile; lt = TRUE; } else if (*tmp == '>') { if (tmp[1] == '=') - ge = TRUE; + gte = TRUE; else - gt = TRUE; + grt = TRUE; } /* '%', '<', '>' have served their purpose, '=' is either part of '<' or '>' or optional for '=N', unary '+' is @@ -2143,12 +2943,12 @@ boolean from_configfile; dt = percent ? ANY_INT : initblstats[fld].anytype; (void) s_to_anything(&hilite.value, tmp, dt); - op = gt ? ">" : ge ? ">=" : lt ? "<" : le ? "<=" : "="; + op = grt ? ">" : gte ? ">=" : lt ? "<" : le ? "<=" : "="; if (dt == ANY_INT /* AC is the only field where negative values make sense but accept >-1 for other fields; reject <0 for non-AC */ && (hilite.value.a_int - < ((fld == BL_AC) ? -128 : gt ? -1 : lt ? 1 : 0) + < ((fld == BL_AC) ? -128 : grt ? -1 : lt ? 1 : 0) /* percentages have another more comprehensive check below */ || hilite.value.a_int > (percent ? (lt ? 101 : 100) : LARGEST_INT))) { @@ -2157,7 +2957,7 @@ boolean from_configfile; is_out_of_range); return FALSE; } else if (dt == ANY_LONG - && (hilite.value.a_long < (gt ? -1L : lt ? 1L : 0L))) { + && hilite.value.a_long < (grt ? -1L : lt ? 1L : 0L)) { config_error_add("%s'%s%ld'%s", threshold_value, op, hilite.value.a_long, is_out_of_range); return FALSE; @@ -2174,11 +2974,11 @@ boolean from_configfile; } /* relationships {LT_VALUE, LE_VALUE, EQ_VALUE, GE_VALUE, GT_VALUE} */ - if (gt || up) + if (grt || up) hilite.rel = GT_VALUE; else if (lt || down) hilite.rel = LT_VALUE; - else if (ge) + else if (gte) hilite.rel = GE_VALUE; else if (le) hilite.rel = LE_VALUE; @@ -2241,23 +3041,27 @@ boolean from_configfile; for (i = 0; i < sf; ++i) { int a = match_str2attr(subfields[i], FALSE); - if (a == ATR_DIM) + if (a == ATR_BOLD) + disp_attrib |= HL_BOLD; + else if (a == ATR_DIM) disp_attrib |= HL_DIM; - else if (a == ATR_BLINK) - disp_attrib |= HL_BLINK; + else if (a == ATR_ITALIC) + disp_attrib |= HL_ITALIC; else if (a == ATR_ULINE) disp_attrib |= HL_ULINE; + else if (a == ATR_BLINK) + disp_attrib |= HL_BLINK; else if (a == ATR_INVERSE) disp_attrib |= HL_INVERSE; - else if (a == ATR_BOLD) - disp_attrib |= HL_BOLD; else if (a == ATR_NONE) disp_attrib = HL_NONE; else { - int c = match_str2clr(subfields[i]); + int c = match_str2clr(subfields[i], FALSE); - if (c >= CLR_MAX || coloridx != -1) + if (c >= CLR_MAX || coloridx != -1) { + config_error_add("bad color '%d %d'", c, coloridx); return FALSE; + } coloridx = c; } } @@ -2279,7 +3083,9 @@ boolean from_configfile; hilite.behavior = BL_TH_TEXTMATCH; else if (hilite.value.a_void) hilite.behavior = BL_TH_VAL_ABSOLUTE; - else + else if (criticalhp) + hilite.behavior = BL_TH_CRITICALHP; + else hilite.behavior = BL_TH_NONE; hilite.anytype = dt; @@ -2296,59 +3102,27 @@ boolean from_configfile; sidx++; } - return TRUE; + return (successes > 0); } -#endif /* STATUS_HILITES */ - -const struct condmap valid_conditions[] = { - { "stone", BL_MASK_STONE }, - { "slime", BL_MASK_SLIME }, - { "strngl", BL_MASK_STRNGL }, - { "foodPois", BL_MASK_FOODPOIS }, - { "termIll", BL_MASK_TERMILL }, - { "blind", BL_MASK_BLIND }, - { "deaf", BL_MASK_DEAF }, - { "stun", BL_MASK_STUN }, - { "conf", BL_MASK_CONF }, - { "hallu", BL_MASK_HALLU }, - { "lev", BL_MASK_LEV }, - { "fly", BL_MASK_FLY }, - { "ride", BL_MASK_RIDE }, -}; - -#ifdef STATUS_HILITES - -const struct condmap condition_aliases[] = { - { "strangled", BL_MASK_STRNGL }, - { "all", BL_MASK_STONE | BL_MASK_SLIME | BL_MASK_STRNGL - | BL_MASK_FOODPOIS | BL_MASK_TERMILL - | BL_MASK_BLIND | BL_MASK_DEAF | BL_MASK_STUN - | BL_MASK_CONF | BL_MASK_HALLU - | BL_MASK_LEV | BL_MASK_FLY | BL_MASK_RIDE }, - { "major_troubles", BL_MASK_STONE | BL_MASK_SLIME | BL_MASK_STRNGL - | BL_MASK_FOODPOIS | BL_MASK_TERMILL }, - { "minor_troubles", BL_MASK_BLIND | BL_MASK_DEAF | BL_MASK_STUN - | BL_MASK_CONF | BL_MASK_HALLU }, - { "movement", BL_MASK_LEV | BL_MASK_FLY | BL_MASK_RIDE } -}; -unsigned long -query_conditions() +staticfn unsigned long +query_conditions(void) { int i,res; unsigned long ret = 0UL; winid tmpwin; anything any; menu_item *picks = (menu_item *) 0; + int clr = NO_COLOR; tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); - for (i = 0; i < SIZE(valid_conditions); i++) { - any = zeroany; - any.a_ulong = valid_conditions[i].bitmask; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - valid_conditions[i].id, MENU_UNSELECTED); + for (i = 0; i < SIZE(conditions); i++) { + any = cg.zeroany; + any.a_ulong = conditions[i].mask; + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, + clr, conditions[i].text[0], MENU_ITEMFLAGS_NONE); } end_menu(tmpwin, "Choose status conditions"); @@ -2363,9 +3137,8 @@ query_conditions() return ret; } -STATIC_OVL char * -conditionbitmask2str(ul) -unsigned long ul; +staticfn char * +conditionbitmask2str(unsigned long ul) { static char buf[BUFSZ]; int i; @@ -2381,10 +3154,10 @@ unsigned long ul; if (condition_aliases[i].bitmask == ul) alias = condition_aliases[i].id; - for (i = 0; i < SIZE(valid_conditions); i++) - if ((valid_conditions[i].bitmask & ul) != 0UL) { + for (i = 0; i < SIZE(conditions); i++) + if ((conditions[i].mask & ul) != 0UL) { Sprintf(eos(buf), "%s%s", (first) ? "" : "+", - valid_conditions[i].id); + conditions[i].text[0]); first = FALSE; } @@ -2394,18 +3167,17 @@ unsigned long ul; return buf; } -STATIC_OVL unsigned long -match_str2conditionbitmask(str) -const char *str; +staticfn unsigned long +match_str2conditionbitmask(const char *str) { int i, nmatches = 0; unsigned long mask = 0UL; if (str && *str) { /* check matches to canonical names */ - for (i = 0; i < SIZE(valid_conditions); i++) - if (fuzzymatch(valid_conditions[i].id, str, " -_", TRUE)) { - mask |= valid_conditions[i].bitmask; + for (i = 0; i < SIZE(conditions); i++) + if (fuzzymatch(conditions[i].text[0], str, " -_", TRUE)) { + mask |= conditions[i].mask; nmatches++; } @@ -2433,15 +3205,14 @@ const char *str; return mask; } -STATIC_OVL unsigned long -str2conditionbitmask(str) -char *str; +staticfn unsigned long +str2conditionbitmask(char *str) { unsigned long conditions_bitmask = 0UL; char **subfields; int i, sf; - sf = splitsubfields(str, &subfields, SIZE(valid_conditions)); + sf = splitsubfields(str, &subfields, SIZE(conditions)); if (sf < 1) return 0UL; @@ -2458,16 +3229,14 @@ char *str; return conditions_bitmask; } -STATIC_OVL boolean -parse_condition(s, sidx) -char (*s)[QBUFSZ]; -int sidx; +staticfn boolean +parse_condition(char (*s)[QBUFSZ], int sidx) { int i; int coloridx = NO_COLOR; char *tmp, *how; unsigned long conditions_bitmask = 0UL; - boolean success = FALSE; + boolean result = FALSE; if (!s) return FALSE; @@ -2485,17 +3254,15 @@ int sidx; */ sidx++; - while(s[sidx]) { + if (!s[sidx][0]) { + config_error_add("Missing condition(s)"); + return FALSE; + } + while (s[sidx][0]) { int sf = 0; /* subfield count */ char buf[BUFSZ], **subfields; tmp = s[sidx]; - if (!*tmp) { - if (!success) - config_error_add("Missing condition(s)"); - return success; - } - Strcpy(buf, tmp); conditions_bitmask = str2conditionbitmask(buf); @@ -2509,7 +3276,7 @@ int sidx; * bitmasks indexed by the color chosen * (0 to (CLR_MAX - 1)) * and/or attributes chosen - * (HL_ATTCLR_DIM to (BL_ATTCLR_MAX - 1)) + * (HL_ATTCLR_NONE to (BL_ATTCLR_MAX - 1)) * We still have to parse the colors and attributes out. */ @@ -2535,78 +3302,76 @@ int sidx; * We have the following additional array offsets to * use for storing the attributes beyond the end of * the color indexes, all of which are less than CLR_MAX. - * HL_ATTCLR_DIM = CLR_MAX - * HL_ATTCLR_BLINK = CLR_MAX + 1 - * HL_ATTCLR_ULINE = CLR_MAX + 2 - * HL_ATTCLR_INVERSE = CLR_MAX + 3 - * HL_ATTCLR_BOLD = CLR_MAX + 4 - * HL_ATTCLR_MAX = CLR_MAX + 5 (this is past array boundary) * */ for (i = 0; i < sf; ++i) { int a = match_str2attr(subfields[i], FALSE); - if (a == ATR_DIM) - cond_hilites[HL_ATTCLR_DIM] |= conditions_bitmask; - else if (a == ATR_BLINK) - cond_hilites[HL_ATTCLR_BLINK] |= conditions_bitmask; + if (a == ATR_BOLD) + gc.cond_hilites[HL_ATTCLR_BOLD] |= conditions_bitmask; + else if (a == ATR_DIM) + gc.cond_hilites[HL_ATTCLR_DIM] |= conditions_bitmask; + else if (a == ATR_ITALIC) + gc.cond_hilites[HL_ATTCLR_ITALIC] |= conditions_bitmask; else if (a == ATR_ULINE) - cond_hilites[HL_ATTCLR_ULINE] |= conditions_bitmask; + gc.cond_hilites[HL_ATTCLR_ULINE] |= conditions_bitmask; + else if (a == ATR_BLINK) + gc.cond_hilites[HL_ATTCLR_BLINK] |= conditions_bitmask; else if (a == ATR_INVERSE) - cond_hilites[HL_ATTCLR_INVERSE] |= conditions_bitmask; - else if (a == ATR_BOLD) - cond_hilites[HL_ATTCLR_BOLD] |= conditions_bitmask; + gc.cond_hilites[HL_ATTCLR_INVERSE] |= conditions_bitmask; else if (a == ATR_NONE) { - cond_hilites[HL_ATTCLR_DIM] &= ~conditions_bitmask; - cond_hilites[HL_ATTCLR_BLINK] &= ~conditions_bitmask; - cond_hilites[HL_ATTCLR_ULINE] &= ~conditions_bitmask; - cond_hilites[HL_ATTCLR_INVERSE] &= ~conditions_bitmask; - cond_hilites[HL_ATTCLR_BOLD] &= ~conditions_bitmask; + gc.cond_hilites[HL_ATTCLR_BOLD] &= ~conditions_bitmask; + gc.cond_hilites[HL_ATTCLR_DIM] &= ~conditions_bitmask; + gc.cond_hilites[HL_ATTCLR_ITALIC] &= ~conditions_bitmask; + gc.cond_hilites[HL_ATTCLR_ULINE] &= ~conditions_bitmask; + gc.cond_hilites[HL_ATTCLR_BLINK] &= ~conditions_bitmask; + gc.cond_hilites[HL_ATTCLR_INVERSE] &= ~conditions_bitmask; } else { - int k = match_str2clr(subfields[i]); + int k = match_str2clr(subfields[i], FALSE); - if (k >= CLR_MAX) + if (k >= CLR_MAX) { + config_error_add("bad color %d", k); return FALSE; + } coloridx = k; } } /* set the bits in the appropriate member of the condition array according to color chosen as index */ - cond_hilites[coloridx] |= conditions_bitmask; - success = TRUE; + gc.cond_hilites[coloridx] |= conditions_bitmask; + result = TRUE; sidx++; } - return TRUE; + return result; } void -clear_status_hilites() +clear_status_hilites(void) { int i; for (i = 0; i < MAXBLSTATS; ++i) { struct hilite_s *temp, *next; - for (temp = blstats[0][i].thresholds; temp; temp = next) { + for (temp = gb.blstats[0][i].thresholds; temp; temp = next) { next = temp->next; free(temp); } - blstats[0][i].thresholds = blstats[1][i].thresholds = 0; + gb.blstats[0][i].thresholds = gb.blstats[1][i].thresholds = 0; /* pointer into thresholds list, now stale */ - blstats[0][i].hilite_rule = blstats[1][i].hilite_rule = 0; + gb.blstats[0][i].hilite_rule = gb.blstats[1][i].hilite_rule = 0; } } -STATIC_OVL char * -hlattr2attrname(attrib, buf, bufsz) -int attrib, bufsz; -char *buf; +staticfn char * +hlattr2attrname(int attrib, char *buf, size_t bufsz) { if (attrib && buf) { char attbuf[BUFSZ]; - int k, first = 0; + int first = 0; + size_t k; attbuf[0] = '\0'; if (attrib == HL_NONE) { @@ -2616,24 +3381,25 @@ char *buf; if (attrib & HL_BOLD) Strcat(attbuf, first++ ? "+bold" : "bold"); - if (attrib & HL_INVERSE) - Strcat(attbuf, first++ ? "+inverse" : "inverse"); + if (attrib & HL_DIM) + Strcat(attbuf, first++ ? "+dim" : "dim"); + if (attrib & HL_ITALIC) + Strcat(attbuf, first++ ? "+italic" : "italic"); if (attrib & HL_ULINE) Strcat(attbuf, first++ ? "+underline" : "underline"); if (attrib & HL_BLINK) Strcat(attbuf, first++ ? "+blink" : "blink"); - if (attrib & HL_DIM) - Strcat(attbuf, first++ ? "+dim" : "dim"); + if (attrib & HL_INVERSE) + Strcat(attbuf, first++ ? "+inverse" : "inverse"); k = strlen(attbuf); - if (k < (bufsz - 1)) + if (k < (size_t)(bufsz - 1)) Strcpy(buf, attbuf); return buf; } return (char *) 0; } - struct _status_hilite_line_str { int id; int fld; @@ -2643,15 +3409,16 @@ struct _status_hilite_line_str { struct _status_hilite_line_str *next; }; +/* these don't need to be in 'struct g' */ static struct _status_hilite_line_str *status_hilite_str = 0; static int status_hilite_str_id = 0; -STATIC_OVL void -status_hilite_linestr_add(fld, hl, mask, str) -int fld; -struct hilite_s *hl; -unsigned long mask; -const char *str; +staticfn void +status_hilite_linestr_add( + int fld, + struct hilite_s *hl, + unsigned long mask, + const char *str) { struct _status_hilite_line_str *tmp, *nxt; @@ -2677,8 +3444,8 @@ const char *str; } } -STATIC_OVL void -status_hilite_linestr_done() +staticfn void +status_hilite_linestr_done(void) { struct _status_hilite_line_str *nxt, *tmp = status_hilite_str; @@ -2691,9 +3458,8 @@ status_hilite_linestr_done() status_hilite_str_id = 0; } -STATIC_OVL int -status_hilite_linestr_countfield(fld) -int fld; +staticfn int +status_hilite_linestr_countfield(int fld) { struct _status_hilite_line_str *tmp; boolean countall = (fld == BL_FLUSH); @@ -2708,7 +3474,7 @@ int fld; /* used by options handling, doset(options.c) */ int -count_status_hilites(VOID_ARGS) +count_status_hilites(void) { int count; @@ -2718,55 +3484,57 @@ count_status_hilites(VOID_ARGS) return count; } -STATIC_OVL void -status_hilite_linestr_gather_conditions() +staticfn void +status_hilite_linestr_gather_conditions(void) { int i; struct _cond_map { unsigned long bm; - unsigned long clratr; - } cond_maps[SIZE(valid_conditions)]; + unsigned int clratr; + } cond_maps[SIZE(conditions)]; (void) memset(cond_maps, 0, - SIZE(valid_conditions) * sizeof (struct _cond_map)); + SIZE(conditions) * sizeof (struct _cond_map)); - for (i = 0; i < SIZE(valid_conditions); i++) { + for (i = 0; i < SIZE(conditions); i++) { int clr = NO_COLOR; int atr = HL_NONE; int j; for (j = 0; j < CLR_MAX; j++) - if (cond_hilites[j] & valid_conditions[i].bitmask) { + if (gc.cond_hilites[j] & conditions[i].mask) { clr = j; break; } - if (cond_hilites[HL_ATTCLR_DIM] & valid_conditions[i].bitmask) - atr |= HL_DIM; - if (cond_hilites[HL_ATTCLR_BOLD] & valid_conditions[i].bitmask) + if (gc.cond_hilites[HL_ATTCLR_BOLD] & conditions[i].mask) atr |= HL_BOLD; - if (cond_hilites[HL_ATTCLR_BLINK] & valid_conditions[i].bitmask) - atr |= HL_BLINK; - if (cond_hilites[HL_ATTCLR_ULINE] & valid_conditions[i].bitmask) + if (gc.cond_hilites[HL_ATTCLR_DIM] & conditions[i].mask) + atr |= HL_DIM; + if (gc.cond_hilites[HL_ATTCLR_ITALIC] & conditions[i].mask) + atr |= HL_ITALIC; + if (gc.cond_hilites[HL_ATTCLR_ULINE] & conditions[i].mask) atr |= HL_ULINE; - if (cond_hilites[HL_ATTCLR_INVERSE] & valid_conditions[i].bitmask) + if (gc.cond_hilites[HL_ATTCLR_BLINK] & conditions[i].mask) + atr |= HL_BLINK; + if (gc.cond_hilites[HL_ATTCLR_INVERSE] & conditions[i].mask) atr |= HL_INVERSE; if (atr != HL_NONE) atr &= ~HL_NONE; if (clr != NO_COLOR || atr != HL_NONE) { - unsigned long ca = clr | (atr << 8); + unsigned int ca = clr | (atr << 8); boolean added_condmap = FALSE; - for (j = 0; j < SIZE(valid_conditions); j++) + for (j = 0; j < SIZE(conditions); j++) if (cond_maps[j].clratr == ca) { - cond_maps[j].bm |= valid_conditions[i].bitmask; + cond_maps[j].bm |= conditions[i].mask; added_condmap = TRUE; break; } if (!added_condmap) { - for (j = 0; j < SIZE(valid_conditions); j++) + for (j = 0; j < SIZE(conditions); j++) if (!cond_maps[j].bm) { - cond_maps[j].bm = valid_conditions[i].bitmask; + cond_maps[j].bm = conditions[i].mask; cond_maps[j].clratr = ca; break; } @@ -2774,7 +3542,7 @@ status_hilite_linestr_gather_conditions() } } - for (i = 0; i < SIZE(valid_conditions); i++) + for (i = 0; i < SIZE(conditions); i++) if (cond_maps[i].bm) { int clr = NO_COLOR, atr = HL_NONE; @@ -2790,16 +3558,16 @@ status_hilite_linestr_gather_conditions() tmpattr = hlattr2attrname(atr, attrbuf, BUFSZ); if (tmpattr) Sprintf(eos(clrbuf), "&%s", tmpattr); - Sprintf(condbuf, "condition/%s/%s", - conditionbitmask2str(cond_maps[i].bm), clrbuf); + Snprintf(condbuf, sizeof(condbuf), "condition/%s/%s", + conditionbitmask2str(cond_maps[i].bm), clrbuf); status_hilite_linestr_add(BL_CONDITION, 0, cond_maps[i].bm, condbuf); } } } -STATIC_OVL void -status_hilite_linestr_gather() +staticfn void +status_hilite_linestr_gather(void) { int i; struct hilite_s *hl; @@ -2807,7 +3575,7 @@ status_hilite_linestr_gather() status_hilite_linestr_done(); for (i = 0; i < MAXBLSTATS; i++) { - hl = blstats[0][i].thresholds; + hl = gb.blstats[0][i].thresholds; while (hl) { status_hilite_linestr_add(i, hl, 0UL, status_hilite2str(hl)); hl = hl->next; @@ -2818,12 +3586,11 @@ status_hilite_linestr_gather() } -STATIC_OVL char * -status_hilite2str(hl) -struct hilite_s *hl; +staticfn char * +status_hilite2str(struct hilite_s *hl) { static char buf[BUFSZ]; - int clr = 0, attr = 0; + int clr = NO_COLOR, attr = ATR_NONE; char behavebuf[BUFSZ]; char clrbuf[BUFSZ]; char attrbuf[BUFSZ]; @@ -2880,6 +3647,9 @@ struct hilite_s *hl; case BL_TH_ALWAYS_HILITE: Sprintf(behavebuf, "always"); break; + case BL_TH_CRITICALHP: + Sprintf(behavebuf, "criticalhp"); + break; case BL_TH_NONE: break; default: @@ -2892,32 +3662,34 @@ struct hilite_s *hl; if ((tmpattr = hlattr2attrname(attr, attrbuf, BUFSZ)) != 0) Sprintf(eos(clrbuf), "&%s", tmpattr); } - Sprintf(buf, "%s/%s/%s", initblstats[hl->fld].fldname, behavebuf, clrbuf); + Snprintf(buf, sizeof(buf), "%s/%s/%s", initblstats[hl->fld].fldname, + behavebuf, clrbuf); return buf; } -STATIC_OVL int -status_hilite_menu_choose_field() +staticfn int +status_hilite_menu_choose_field(void) { winid tmpwin; int i, res, fld = BL_FLUSH; anything any; menu_item *picks = (menu_item *) 0; + int clr = NO_COLOR; tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); for (i = 0; i < MAXBLSTATS; i++) { #ifndef SCORE_ON_BOTL if (initblstats[i].fld == BL_SCORE - && !blstats[0][BL_SCORE].thresholds) + && !gb.blstats[0][BL_SCORE].thresholds) continue; #endif - any = zeroany; + any = cg.zeroany; any.a_int = (i + 1); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - initblstats[i].fldname, MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, + clr, initblstats[i].fldname, MENU_ITEMFLAGS_NONE); } end_menu(tmpwin, "Select a hilite field:"); @@ -2931,9 +3703,8 @@ status_hilite_menu_choose_field() return fld; } -STATIC_OVL int -status_hilite_menu_choose_behavior(fld) -int fld; +staticfn int +status_hilite_menu_choose_behavior(int fld) { winid tmpwin; int res = 0, beh = BL_TH_NONE-1; @@ -2942,6 +3713,7 @@ int fld; char buf[BUFSZ]; int at; int onlybeh = BL_TH_NONE, nopts = 0; + int clr = NO_COLOR; if (fld < 0 || fld >= MAXBLSTATS) return BL_TH_NONE; @@ -2949,62 +3721,73 @@ int fld; at = initblstats[fld].anytype; tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); if (fld != BL_CONDITION) { - any = zeroany; + any = cg.zeroany; any.a_int = onlybeh = BL_TH_ALWAYS_HILITE; Sprintf(buf, "Always highlight %s", initblstats[fld].fldname); - add_menu(tmpwin, NO_GLYPH, &any, 'a', 0, ATR_NONE, - buf, MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, 'a', 0, ATR_NONE, + clr, buf, MENU_ITEMFLAGS_NONE); nopts++; } if (fld == BL_CONDITION) { - any = zeroany; + any = cg.zeroany; any.a_int = onlybeh = BL_TH_CONDITION; - add_menu(tmpwin, NO_GLYPH, &any, 'b', 0, ATR_NONE, - "Bitmask of conditions", MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, 'b', 0, ATR_NONE, + clr, "Bitmask of conditions", MENU_ITEMFLAGS_NONE); nopts++; } - if (fld != BL_CONDITION) { - any = zeroany; + if (fld != BL_CONDITION && fld != BL_VERS) { + any = cg.zeroany; any.a_int = onlybeh = BL_TH_UPDOWN; Sprintf(buf, "%s value changes", initblstats[fld].fldname); - add_menu(tmpwin, NO_GLYPH, &any, 'c', 0, ATR_NONE, - buf, MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, 'c', 0, ATR_NONE, + clr, buf, MENU_ITEMFLAGS_NONE); nopts++; } if (fld != BL_CAP && fld != BL_HUNGER && (at == ANY_INT || at == ANY_LONG)) { - any = zeroany; + any = cg.zeroany; any.a_int = onlybeh = BL_TH_VAL_ABSOLUTE; - add_menu(tmpwin, NO_GLYPH, &any, 'n', 0, ATR_NONE, - "Number threshold", MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, 'n', 0, ATR_NONE, + clr, "Number threshold", MENU_ITEMFLAGS_NONE); nopts++; } if (initblstats[fld].idxmax >= 0) { - any = zeroany; + any = cg.zeroany; any.a_int = onlybeh = BL_TH_VAL_PERCENTAGE; - add_menu(tmpwin, NO_GLYPH, &any, 'p', 0, ATR_NONE, - "Percentage threshold", MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, 'p', 0, ATR_NONE, + clr, "Percentage threshold", MENU_ITEMFLAGS_NONE); + nopts++; + } + + if (fld == BL_HP) { + any = cg.zeroany; + any.a_int = onlybeh = BL_TH_CRITICALHP; + Sprintf(buf, "Highlight critically low %s", + initblstats[fld].fldname); + add_menu(tmpwin, &nul_glyphinfo, &any, 'C', 0, ATR_NONE, + clr, buf, MENU_ITEMFLAGS_NONE); nopts++; } if (initblstats[fld].anytype == ANY_STR || fld == BL_CAP || fld == BL_HUNGER) { - any = zeroany; + any = cg.zeroany; any.a_int = onlybeh = BL_TH_TEXTMATCH; Sprintf(buf, "%s text match", initblstats[fld].fldname); - add_menu(tmpwin, NO_GLYPH, &any, 't', 0, ATR_NONE, - buf, MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, 't', 0, ATR_NONE, + clr, buf, MENU_ITEMFLAGS_NONE); nopts++; } - Sprintf(buf, "Select %s field hilite behavior:", initblstats[fld].fldname); + Sprintf(buf, "Select %s field hilite behavior:", + initblstats[fld].fldname); end_menu(tmpwin, buf); if (nopts > 1) { @@ -3013,8 +3796,9 @@ int fld; beh = BL_TH_NONE; else if (res == -1) /* menu cancelled */ beh = (BL_TH_NONE - 1); - } else if (onlybeh != BL_TH_NONE) + } else if (onlybeh != BL_TH_NONE) { beh = onlybeh; + } destroy_nhwindow(tmpwin); if (res > 0) { beh = picks->item.a_int; @@ -3023,20 +3807,21 @@ int fld; return beh; } -STATIC_OVL int -status_hilite_menu_choose_updownboth(fld, str, ltok, gtok) -int fld; -const char *str; -boolean ltok, gtok; +staticfn int +status_hilite_menu_choose_updownboth( + int fld, + const char *str, + boolean ltok, boolean gtok) { int res, ret = NO_LTEQGT; winid tmpwin; char buf[BUFSZ]; anything any; menu_item *picks = (menu_item *) 0; + int clr = NO_COLOR; tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); if (ltok) { if (str) @@ -3044,18 +3829,18 @@ boolean ltok, gtok; (fld == BL_AC) ? "Better (lower)" : "Less", str); else Sprintf(buf, "Value goes down"); - any = zeroany; + any = cg.zeroany; any.a_int = 10 + LT_VALUE; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - buf, MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, + clr, buf, MENU_ITEMFLAGS_NONE); if (str) { Sprintf(buf, "%s or %s", str, (fld == BL_AC) ? "better (lower)" : "less"); - any = zeroany; + any = cg.zeroany; any.a_int = 10 + LE_VALUE; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - buf, MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, + clr, buf, MENU_ITEMFLAGS_NONE); } } @@ -3063,19 +3848,19 @@ boolean ltok, gtok; Sprintf(buf, "Exactly %s", str); else Sprintf(buf, "Value changes"); - any = zeroany; + any = cg.zeroany; any.a_int = 10 + EQ_VALUE; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - buf, MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, + clr, buf, MENU_ITEMFLAGS_NONE); if (gtok) { if (str) { Sprintf(buf, "%s or %s", str, (fld == BL_AC) ? "worse (higher)" : "more"); - any = zeroany; + any = cg.zeroany; any.a_int = 10 + GE_VALUE; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - buf, MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, + clr, buf, MENU_ITEMFLAGS_NONE); } if (str) @@ -3083,10 +3868,10 @@ boolean ltok, gtok; (fld == BL_AC) ? "Worse (higher)" : "More", str); else Sprintf(buf, "Value goes up"); - any = zeroany; + any = cg.zeroany; any.a_int = 10 + GT_VALUE; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - buf, MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr, + buf, MENU_ITEMFLAGS_NONE); } Sprintf(buf, "Select field %s value:", initblstats[fld].fldname); end_menu(tmpwin, buf); @@ -3101,9 +3886,8 @@ boolean ltok, gtok; return ret; } -STATIC_OVL boolean -status_hilite_menu_add(origfld) -int origfld; +staticfn boolean +status_hilite_menu_add(int origfld) { int fld; int behavior; @@ -3113,8 +3897,9 @@ int origfld; unsigned long cond = 0UL; char colorqry[BUFSZ]; char attrqry[BUFSZ]; + int retry = 0; -choose_field: + choose_field: fld = origfld; if (fld == BL_FLUSH) { fld = status_hilite_menu_choose_field(); @@ -3134,7 +3919,7 @@ int origfld; hilite.set = FALSE; /* mark it "unset" */ hilite.fld = fld; -choose_behavior: + choose_behavior: behavior = status_hilite_menu_choose_behavior(fld); if (behavior == (BL_TH_NONE - 1)) { @@ -3147,7 +3932,11 @@ int origfld; hilite.behavior = behavior; -choose_value: + choose_value: + if (retry++ > 5) { + pline("That's enough tries."); + return FALSE; + } if (behavior == BL_TH_VAL_PERCENTAGE || behavior == BL_TH_VAL_ABSOLUTE) { char inbuf[BUFSZ], buf[BUFSZ]; @@ -3215,7 +4004,7 @@ int origfld; : (lt_gt_eq == EQ_VALUE) ? "=" : ""; /* didn't specify lt_gt_eq with number */ - aval = zeroany; + aval = cg.zeroany; dt = percent ? ANY_INT : initblstats[fld].anytype; (void) s_to_anything(&aval, numstart, dt); @@ -3240,7 +4029,7 @@ int origfld; goto choose_value; } /* restore suffix for use in color and attribute prompts */ - if (!index(numstart, '%')) + if (!strchr(numstart, '%')) Strcat(numstart, "%"); /* reject negative values except for AC and >-1; reject 0 for < */ @@ -3296,7 +4085,7 @@ int origfld; if (initblstats[fld].anytype != ANY_STR) { boolean ltok = (fld != BL_TIME), gtok = TRUE; - lt_gt_eq = status_hilite_menu_choose_updownboth(fld, (char *)0, + lt_gt_eq = status_hilite_menu_choose_updownboth(fld, (char *) 0, ltok, gtok); if (lt_gt_eq == NO_LTEQGT) goto choose_behavior; @@ -3327,9 +4116,11 @@ int origfld; goto choose_field; return FALSE; } - Sprintf(colorqry, "Choose a color for conditions %s:", + Snprintf(colorqry, sizeof(colorqry), + "Choose a color for conditions %s:", conditionbitmask2str(cond)); - Sprintf(attrqry, "Choose attribute for conditions %s:", + Snprintf(attrqry, sizeof(attrqry), + "Choose attribute for conditions %s:", conditionbitmask2str(cond)); } else if (behavior == BL_TH_TEXTMATCH) { char qry_buf[BUFSZ]; @@ -3351,7 +4142,9 @@ int origfld; hilite.rel = TXT_VALUE; Strcpy(hilite.textmatch, enc_stat[rv]); } else if (fld == BL_ALIGN) { - static const char *aligntxt[] = { "chaotic", "neutral", "lawful" }; + static const char *const aligntxt[] = { + "chaotic", "neutral", "lawful" + }; int rv = query_arrayvalue(qry_buf, aligntxt, 0, 2 + 1); @@ -3361,12 +4154,11 @@ int origfld; hilite.rel = TXT_VALUE; Strcpy(hilite.textmatch, aligntxt[rv]); } else if (fld == BL_HUNGER) { - static const char *hutxt[] = { "Satiated", (char *) 0, "Hungry", - "Weak", "Fainting", "Fainted", - "Starved" }; - int rv = query_arrayvalue(qry_buf, - hutxt, - SATIATED, STARVED + 1); + static const char *const hutxt[] = { + "Satiated", (char *) 0, "Hungry", "Weak", + "Fainting", "Fainted", "Starved" + }; + int rv = query_arrayvalue(qry_buf, hutxt, SATIATED, STARVED + 1); if (rv < SATIATED) goto choose_behavior; @@ -3379,10 +4171,10 @@ int origfld; int i, j, rv; for (i = j = 0; i < 9; i++) { - Sprintf(mbuf, "\"%s\"", urole.rank[i].m); - if (urole.rank[i].f) { - Sprintf(fbuf, "\"%s\"", urole.rank[i].f); - Sprintf(obuf, "%s or %s", + Sprintf(mbuf, "\"%s\"", gu.urole.rank[i].m); + if (gu.urole.rank[i].f) { + Sprintf(fbuf, "\"%s\"", gu.urole.rank[i].f); + Snprintf(obuf, sizeof obuf, "%s or %s", flags.female ? fbuf : mbuf, flags.female ? mbuf : fbuf); } else { @@ -3438,15 +4230,15 @@ int origfld; initblstats[fld].fldname); } -choose_color: - clr = query_color(colorqry); + choose_color: + clr = query_color(colorqry, NO_COLOR); if (clr == -1) { if (behavior != BL_TH_ALWAYS_HILITE) goto choose_value; else goto choose_behavior; } - atr = query_attr(attrqry); + atr = query_attr(attrqry, ATR_NONE); if (atr == -1) goto choose_color; @@ -3455,24 +4247,27 @@ int origfld; char attrbuf[BUFSZ]; char *tmpattr; + if (atr & HL_BOLD) + gc.cond_hilites[HL_ATTCLR_BOLD] |= cond; if (atr & HL_DIM) - cond_hilites[HL_ATTCLR_DIM] |= cond; - if (atr & HL_BLINK) - cond_hilites[HL_ATTCLR_BLINK] |= cond; + gc.cond_hilites[HL_ATTCLR_DIM] |= cond; + if (atr & HL_ITALIC) + gc.cond_hilites[HL_ATTCLR_ITALIC] |= cond; if (atr & HL_ULINE) - cond_hilites[HL_ATTCLR_ULINE] |= cond; + gc.cond_hilites[HL_ATTCLR_ULINE] |= cond; + if (atr & HL_BLINK) + gc.cond_hilites[HL_ATTCLR_BLINK] |= cond; if (atr & HL_INVERSE) - cond_hilites[HL_ATTCLR_INVERSE] |= cond; - if (atr & HL_BOLD) - cond_hilites[HL_ATTCLR_BOLD] |= cond; + gc.cond_hilites[HL_ATTCLR_INVERSE] |= cond; if (atr == HL_NONE) { - cond_hilites[HL_ATTCLR_DIM] &= ~cond; - cond_hilites[HL_ATTCLR_BLINK] &= ~cond; - cond_hilites[HL_ATTCLR_ULINE] &= ~cond; - cond_hilites[HL_ATTCLR_INVERSE] &= ~cond; - cond_hilites[HL_ATTCLR_BOLD] &= ~cond; + gc.cond_hilites[HL_ATTCLR_BOLD] &= ~cond; + gc.cond_hilites[HL_ATTCLR_DIM] &= ~cond; + gc.cond_hilites[HL_ATTCLR_ITALIC] &= ~cond; + gc.cond_hilites[HL_ATTCLR_ULINE] &= ~cond; + gc.cond_hilites[HL_ATTCLR_BLINK] &= ~cond; + gc.cond_hilites[HL_ATTCLR_INVERSE] &= ~cond; } - cond_hilites[clr] |= cond; + gc.cond_hilites[clr] |= cond; (void) strNsubst(strcpy(clrbuf, clr2colorname(clr)), " ", "-", 0); tmpattr = hlattr2attrname(atr, attrbuf, BUFSZ); if (tmpattr) @@ -3506,9 +4301,8 @@ int origfld; return TRUE; } -boolean -status_hilite_remove(id) -int id; +staticfn boolean +status_hilite_remove(int id) { struct _status_hilite_line_str *hlstr = status_hilite_str; @@ -3523,29 +4317,32 @@ int id; int i; for (i = 0; i < CLR_MAX; i++) - cond_hilites[i] &= ~hlstr->mask; - cond_hilites[HL_ATTCLR_DIM] &= ~hlstr->mask; - cond_hilites[HL_ATTCLR_BOLD] &= ~hlstr->mask; - cond_hilites[HL_ATTCLR_BLINK] &= ~hlstr->mask; - cond_hilites[HL_ATTCLR_ULINE] &= ~hlstr->mask; - cond_hilites[HL_ATTCLR_INVERSE] &= ~hlstr->mask; + gc.cond_hilites[i] &= ~hlstr->mask; + gc.cond_hilites[HL_ATTCLR_BOLD] &= ~hlstr->mask; + gc.cond_hilites[HL_ATTCLR_DIM] &= ~hlstr->mask; + gc.cond_hilites[HL_ATTCLR_ITALIC] &= ~hlstr->mask; + gc.cond_hilites[HL_ATTCLR_ULINE] &= ~hlstr->mask; + gc.cond_hilites[HL_ATTCLR_BLINK] &= ~hlstr->mask; + gc.cond_hilites[HL_ATTCLR_INVERSE] &= ~hlstr->mask; return TRUE; } else { int fld = hlstr->fld; struct hilite_s *hl, *hlprev = (struct hilite_s *) 0; - for (hl = blstats[0][fld].thresholds; hl; hl = hl->next) { + for (hl = gb.blstats[0][fld].thresholds; hl; hl = hl->next) { if (hlstr->hl == hl) { if (hlprev) { hlprev->next = hl->next; } else { - blstats[0][fld].thresholds = hl->next; - blstats[1][fld].thresholds = blstats[0][fld].thresholds; + gb.blstats[0][fld].thresholds = hl->next; + gb.blstats[1][fld].thresholds + = gb.blstats[0][fld].thresholds; } - if (blstats[0][fld].hilite_rule == hl) { - blstats[0][fld].hilite_rule - = blstats[1][fld].hilite_rule = (struct hilite_s *) 0; - blstats[0][fld].time = blstats[1][fld].time = 0L; + if (gb.blstats[0][fld].hilite_rule == hl) { + gb.blstats[0][fld].hilite_rule + = gb.blstats[1][fld].hilite_rule + = (struct hilite_s *) 0; + gb.blstats[0][fld].time = gb.blstats[1][fld].time = 0L; } free((genericptr_t) hl); return TRUE; @@ -3556,9 +4353,8 @@ int id; return FALSE; } -boolean -status_hilite_menu_fld(fld) -int fld; +staticfn boolean +status_hilite_menu_fld(int fld) { winid tmpwin; int i, res; @@ -3567,7 +4363,8 @@ int fld; int count = status_hilite_linestr_countfield(fld); struct _status_hilite_line_str *hlstr; char buf[BUFSZ]; - boolean acted = FALSE; + boolean acted; + int clr = NO_COLOR; if (!count) { if (status_hilite_menu_add(fld)) { @@ -3579,34 +4376,32 @@ int fld; } tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); if (count) { hlstr = status_hilite_str; while (hlstr) { if (hlstr->fld == fld) { - any = zeroany; + any = cg.zeroany; any.a_int = hlstr->id; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - hlstr->str, MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, + clr, hlstr->str, MENU_ITEMFLAGS_NONE); } hlstr = hlstr->next; } } else { - any = zeroany; Sprintf(buf, "No current hilites for %s", initblstats[fld].fldname); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, MENU_UNSELECTED); + add_menu_str(tmpwin, buf); } /* separator line */ - any = zeroany; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED); + add_menu_str(tmpwin, ""); if (count) { - any = zeroany; + any = cg.zeroany; any.a_int = -1; - add_menu(tmpwin, NO_GLYPH, &any, 'X', 0, ATR_NONE, - "Remove selected hilites", MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, 'X', 0, ATR_NONE, clr, + "Remove selected hilites", MENU_ITEMFLAGS_NONE); } #ifndef SCORE_ON_BOTL @@ -3619,64 +4414,46 @@ int fld; } else #endif { - any = zeroany; + any = cg.zeroany; any.a_int = -2; - add_menu(tmpwin, NO_GLYPH, &any, 'Z', 0, ATR_NONE, - "Add a new hilite", MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, 'Z', 0, ATR_NONE, + clr, "Add new hilites", MENU_ITEMFLAGS_NONE); } Sprintf(buf, "Current %s hilites:", initblstats[fld].fldname); end_menu(tmpwin, buf); + acted = FALSE; if ((res = select_menu(tmpwin, PICK_ANY, &picks)) > 0) { - int mode = 0; + int idx; + unsigned mode = 0; for (i = 0; i < res; i++) { - int idx = picks[i].item.a_int; - - if (idx == -1) { - /* delete selected hilites */ - if (mode) - goto shlmenu_free; - mode = -1; - break; - } else if (idx == -2) { - /* create a new hilite */ - if (mode) - goto shlmenu_free; - mode = -2; - break; - } + idx = picks[i].item.a_int; + if (idx == -1) + mode |= 1; /* delete selected hilites */ + else if (idx == -2) + mode |= 2; /* create new hilites */ } - - if (mode == -1) { - /* delete selected hilites */ + if (mode & 1) { /* delete selected hilites */ for (i = 0; i < res; i++) { - int idx = picks[i].item.a_int; - - if (idx > 0) - (void) status_hilite_remove(idx); + idx = picks[i].item.a_int; + if (idx > 0 && status_hilite_remove(idx)) + acted = TRUE; } - reset_status_hilites(); - acted = TRUE; - } else if (mode == -2) { - /* create a new hilite */ - if (status_hilite_menu_add(fld)) + } + if (mode & 2) { /* create new hilites */ + while (status_hilite_menu_add(fld)) acted = TRUE; } - - free((genericptr_t) picks); + free((genericptr_t) picks), picks = 0; } - -shlmenu_free: - - picks = (menu_item *) 0; destroy_nhwindow(tmpwin); return acted; } -void -status_hilites_viewall() +staticfn void +status_hilites_viewall(void) { winid datawin; struct _status_hilite_line_str *hlstr = status_hilite_str; @@ -3696,62 +4473,88 @@ status_hilites_viewall() destroy_nhwindow(datawin); } +void +all_options_statushilites(strbuf_t *sbuf) +{ + struct _status_hilite_line_str *hlstr; + char buf[BUFSZ]; + + status_hilite_linestr_done(); + status_hilite_linestr_gather(); + + hlstr = status_hilite_str; + + while (hlstr) { + Sprintf(buf, "OPTIONS=hilite_status: %.*s\n", + (int) (BUFSZ - sizeof "OPTIONS=hilite_status: " - 1), + hlstr->str); + strbuf_append(sbuf, buf); + hlstr = hlstr->next; + } + status_hilite_linestr_done(); +} + boolean -status_hilite_menu() +status_hilite_menu(void) { winid tmpwin; - int i, res; + int i, fld, res; menu_item *picks = (menu_item *) 0; anything any; boolean redo; int countall; + int clr = NO_COLOR; -shlmenu_redo: + shlmenu_redo: redo = FALSE; tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); status_hilite_linestr_gather(); countall = status_hilite_linestr_countfield(BL_FLUSH); if (countall) { - any = zeroany; + any = cg.zeroany; any.a_int = -1; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - "View all hilites in config format", MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, + clr, "View all hilites in config format", + MENU_ITEMFLAGS_NONE); - any = zeroany; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED); + add_menu_str(tmpwin, ""); } for (i = 0; i < MAXBLSTATS; i++) { - int count = status_hilite_linestr_countfield(i); + int count; char buf[BUFSZ]; + fld = initblstats[i].fld; + count = status_hilite_linestr_countfield(fld); #ifndef SCORE_ON_BOTL /* config file might contain rules for highlighting 'score' even when SCORE_ON_BOTL is disabled; if so, 'O' command menus will show them and allow deletions but not additions, otherwise, it won't show 'score' at all */ - if (initblstats[i].fld == BL_SCORE && !count) + if (fld == BL_SCORE && !count) continue; #endif - any = zeroany; - any.a_int = i + 1; + any = cg.zeroany; + any.a_int = fld + 1; Sprintf(buf, "%-18s", initblstats[i].fldname); if (count) Sprintf(eos(buf), " (%d defined)", count); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - buf, MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, + clr, buf, MENU_ITEMFLAGS_NONE); } end_menu(tmpwin, "Status hilites:"); if ((res = select_menu(tmpwin, PICK_ONE, &picks)) > 0) { - i = picks->item.a_int - 1; - if (i < 0) + fld = picks->item.a_int - 1; + if (fld < 0) { status_hilites_viewall(); - else - (void) status_hilite_menu_fld(i); + } else { + if (status_hilite_menu_fld(fld)) + reset_status_hilites(); + } free((genericptr_t) picks), picks = (menu_item *) 0; redo = TRUE; } @@ -3760,15 +4563,16 @@ status_hilite_menu() countall = status_hilite_linestr_countfield(BL_FLUSH); status_hilite_linestr_done(); - if (redo) + /* fuzzer is unlikely to pick something useful within nested menus; + limit it to one try */ + if (redo && !iflags.debug_fuzzer) goto shlmenu_redo; /* hilite_delta=='statushilites' does double duty: it is the number of turns for temporary highlights to remain visible and also when non-zero it is the flag to enable highlighting */ if (countall > 0 && !iflags.hilite_delta) - pline( - "To have highlights become active, set 'statushilites' option to non-zero."); + iflags.hilite_delta = 3L; return TRUE; } diff --git a/src/calendar.c b/src/calendar.c new file mode 100644 index 000000000..7c74b1521 --- /dev/null +++ b/src/calendar.c @@ -0,0 +1,250 @@ +/* NetHack 5.0 calendar.c $NHDT-Date: 1706213796 2024/01/25 20:16:36 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.116 $ */ +/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ +/*-Copyright (c) Michael Allison, 2007. */ +/* Copyright (c) Robert Patrick Rankin, 1991 */ +/* NetHack may be freely redistributed. See license for details. */ + +#include "hack.h" + +/* + * Time routines + * + * The time is used for: + * - seed for rand() + * - year on tombstone and yyyymmdd in record file + * - phase of the moon (various monsters react to NEW_MOON or FULL_MOON) + * - night and midnight (the undead are dangerous at midnight) + * - determination of what files are "very old" + */ + +/* TIME_type: type of the argument to time(); we actually use &(time_t); + you might need to define either or both of these to 'long *' in *conf.h */ +#ifndef TIME_type +#define TIME_type time_t * +#endif +#ifndef LOCALTIME_type +#define LOCALTIME_type time_t * +#endif + +staticfn struct tm *getlt(void); + +time_t +getnow(void) +{ + time_t datetime = 0; + + (void) time((TIME_type) &datetime); + return datetime; +} + +staticfn struct tm * +getlt(void) +{ + time_t date = getnow(); + + return localtime((LOCALTIME_type) &date); +} + +/* + * NLE: Return a deterministic struct tm when fix_moon_phase is enabled + * and seeds have been set. Otherwise fall back to real system time. + * The actual RNG work is done by nle_fill_fixed_tm() in nlernd.c. + */ +STATIC_OVL struct tm * +nle_getlt_maybe_fixed() +{ + static struct tm fixed_tm; + + if (!settings.fix_moon_phase || !settings.time_seed_is_set) + return getlt(); + + nle_fill_fixed_tm(&fixed_tm, settings.time_seed); + return &fixed_tm; +} + +int +getyear(void) +{ + return (1900 + getlt()->tm_year); +} + + +long +yyyymmdd(time_t date) +{ + long datenum; + struct tm *lt; + + if (date == 0) + lt = getlt(); + else + lt = localtime((LOCALTIME_type) &date); + + /* just in case somebody's localtime supplies (year % 100) + rather than the expected (year - 1900) */ + if (lt->tm_year < 70) + datenum = (long) lt->tm_year + 2000L; + else + datenum = (long) lt->tm_year + 1900L; + /* yyyy --> yyyymm */ + datenum = datenum * 100L + (long) (lt->tm_mon + 1); + /* yyyymm --> yyyymmdd */ + datenum = datenum * 100L + (long) lt->tm_mday; + return datenum; +} + +long +hhmmss(time_t date) +{ + long timenum; + struct tm *lt; + + if (date == 0) + lt = getlt(); + else + lt = localtime((LOCALTIME_type) &date); + + timenum = lt->tm_hour * 10000L + lt->tm_min * 100L + lt->tm_sec; + return timenum; +} + +char * +yyyymmddhhmmss(time_t date) +{ + long datenum; + static char datestr[15]; + struct tm *lt; + + if (date == 0) + lt = getlt(); + else + lt = localtime((LOCALTIME_type) &date); + + /* just in case somebody's localtime supplies (year % 100) + rather than the expected (year - 1900) */ + if (lt->tm_year < 70) + datenum = (long) lt->tm_year + 2000L; + else + datenum = (long) lt->tm_year + 1900L; + Snprintf(datestr, sizeof datestr, "%04ld%02d%02d%02d%02d%02d", + datenum, lt->tm_mon + 1, + lt->tm_mday, lt->tm_hour, lt->tm_min, lt->tm_sec); + //debugpline1("yyyymmddhhmmss() produced date string %s", datestr); + return datestr; +} + +time_t +time_from_yyyymmddhhmmss(char *buf) +{ + int k; + time_t timeresult = (time_t) 0; + struct tm t, *lt; + char *d, *p, y[5], mo[3], md[3], h[3], mi[3], s[3]; + + if (buf && strlen(buf) == 14) { + d = buf; + p = y; /* year */ + for (k = 0; k < 4; ++k) + *p++ = *d++; + *p = '\0'; + p = mo; /* month */ + for (k = 0; k < 2; ++k) + *p++ = *d++; + *p = '\0'; + p = md; /* day */ + for (k = 0; k < 2; ++k) + *p++ = *d++; + *p = '\0'; + p = h; /* hour */ + for (k = 0; k < 2; ++k) + *p++ = *d++; + *p = '\0'; + p = mi; /* minutes */ + for (k = 0; k < 2; ++k) + *p++ = *d++; + *p = '\0'; + p = s; /* seconds */ + for (k = 0; k < 2; ++k) + *p++ = *d++; + *p = '\0'; + lt = getlt(); + if (lt) { + t = *lt; + t.tm_year = atoi(y) - 1900; + t.tm_mon = atoi(mo) - 1; + t.tm_mday = atoi(md); + t.tm_hour = atoi(h); + t.tm_min = atoi(mi); + t.tm_sec = atoi(s); + timeresult = mktime(&t); + } + if (timeresult == (time_t) -1) + ; +#if 0 +TODO: set_debugpline1, debugpline1 -> function pointer + debugpline1("time_from_yyyymmddhhmmss(%s) would have returned -1", + buf ? buf : ""); +#endif + else + return timeresult; + } + return (time_t) 0; +} + +/* + * moon period = 29.53058 days ~= 30, year = 365.2422 days + * days moon phase advances on first day of year compared to preceding year + * = 365.2422 - 12*29.53058 ~= 11 + * years in Metonic cycle (time until same phases fall on the same days of + * the month) = 18.6 ~= 19 + * moon phase on first day of year (epact) ~= (11*(year%19) + 29) % 30 + * (29 as initial condition) + * current phase in days = first day phase + days elapsed in year + * 6 moons ~= 177 days + * 177 ~= 8 reported phases * 22 + * + 11/22 for rounding + */ +int +phase_of_the_moon(void) /* 0-7, with 0: new, 4: full */ +{ + /* NLE: added for deterministic moon phase behaviour */ + struct tm *lt = nle_getlt_maybe_fixed(); + int epact, diy, goldn; + + diy = lt->tm_yday; + goldn = (lt->tm_year % 19) + 1; + epact = (11 * goldn + 18) % 30; + if ((epact == 25 && goldn > 11) || epact == 24) + epact++; + + return ((((((diy + epact) * 6) + 11) % 177) / 22) & 7); +} + +boolean +friday_13th(void) +{ + /* NLE: added for deterministic friday the 13th behaviour*/ + struct tm *lt = nle_getlt_maybe_fixed(); + + /* tm_wday (day of week; 0==Sunday) == 5 => Friday */ + return (boolean) (lt->tm_wday == 5 && lt->tm_mday == 13); +} + +int +night(void) +{ + /* NLE: added for deterministic night behaviour */ + int hour = nle_getlt_maybe_fixed()->tm_hour; + + return (hour < 6 || hour > 21); +} + +int +midnight(void) +{ + /* NLE: added for deterministic midnight behaviour */ + return (nle_getlt_maybe_fixed()->tm_hour == 0); +} + +/* calendar.c */ + diff --git a/src/cfgfiles.c b/src/cfgfiles.c new file mode 100644 index 000000000..ef7dc7207 --- /dev/null +++ b/src/cfgfiles.c @@ -0,0 +1,2098 @@ +/* NetHack 5.0 cfgfiles.c $NHDT-Date: 1740532826 2025/02/25 17:20:26 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.417 $ */ +/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ +/*-Copyright (c) Derek S. Ray, 2015. */ +/* NetHack may be freely redistributed. See license for details. */ + +#define NEED_VARARGS + +#include "hack.h" +#include "dlb.h" +#include + +#if (!defined(MACOS9) && !defined(O_WRONLY) && !defined(AZTEC_C)) \ + || defined(USE_FCNTL) +#include +#endif + +#define BIGBUFSZ (5 * BUFSZ) /* big enough to format a 4*BUFSZ string (from + * config file parsing) with modest decoration; + * result will then be truncated to BUFSZ-1 */ + +#ifdef USER_SOUNDS +extern char *sounddir; /* defined in sounds.c */ +#endif + +staticfn void vconfig_error_add(const char *, va_list); +staticfn FILE *fopen_config_file(const char *, int); +staticfn int get_uchars(char *, uchar *, boolean, int, const char *); +#ifdef NOCWD_ASSUMPTIONS +staticfn void adjust_prefix(char *, int); +#endif +staticfn char *choose_random_part(char *, char); +staticfn boolean config_error_nextline(const char *); +staticfn void free_config_sections(void); +staticfn char *is_config_section(char *); +staticfn boolean handle_config_section(char *); +boolean parse_config_line(char *); +staticfn char *find_optparam(char *); +#ifndef SFCTOOL +staticfn boolean cnf_line_OPTIONS(char *); +staticfn boolean cnf_line_AUTOPICKUP_EXCEPTION(char *); +staticfn boolean cnf_line_BINDINGS(char *); +staticfn boolean cnf_line_AUTOCOMPLETE(char *); +staticfn boolean cnf_line_MSGTYPE(char *); +staticfn boolean cnf_line_HACKDIR(char *); +staticfn boolean cnf_line_LEVELDIR(char *); +staticfn boolean cnf_line_SAVEDIR(char *); +staticfn boolean cnf_line_BONESDIR(char *); +staticfn boolean cnf_line_DATADIR(char *); +staticfn boolean cnf_line_SCOREDIR(char *); +staticfn boolean cnf_line_LOCKDIR(char *); +staticfn boolean cnf_line_CONFIGDIR(char *); +staticfn boolean cnf_line_TROUBLEDIR(char *); +staticfn boolean cnf_line_NAME(char *); +staticfn boolean cnf_line_ROLE(char *); +staticfn boolean cnf_line_dogname(char *); +staticfn boolean cnf_line_catname(char *); +#endif /* SFCTOOL */ +#ifdef SYSCF +staticfn boolean cnf_line_WIZARDS(char *); +staticfn boolean cnf_line_SHELLERS(char *); +staticfn boolean cnf_line_MSGHANDLER(char *); +staticfn boolean cnf_line_EXPLORERS(char *); +staticfn boolean cnf_line_DEBUGFILES(char *); +staticfn boolean cnf_line_DUMPLOGFILE(char *); +staticfn boolean cnf_line_GENERICUSERS(char *); +staticfn boolean cnf_line_BONES_POOLS(char *); +staticfn boolean cnf_line_SUPPORT(char *); +staticfn boolean cnf_line_RECOVER(char *); +staticfn boolean cnf_line_CHECK_SAVE_UID(char *); +staticfn boolean cnf_line_CHECK_PLNAME(char *); +staticfn boolean cnf_line_SEDUCE(char *); +staticfn boolean cnf_line_HIDEUSAGE(char *); +staticfn boolean cnf_line_MAXPLAYERS(char *); +staticfn boolean cnf_line_PERSMAX(char *); +staticfn boolean cnf_line_PERS_IS_UID(char *); +staticfn boolean cnf_line_ENTRYMAX(char *); +staticfn boolean cnf_line_POINTSMIN(char *); +staticfn boolean cnf_line_MAX_STATUENAME_RANK(char *); +staticfn boolean cnf_line_LIVELOG(char *); +staticfn boolean cnf_line_PANICTRACE_LIBC(char *); +staticfn boolean cnf_line_PANICTRACE_GDB(char *); +staticfn boolean cnf_line_GDBPATH(char *); +staticfn boolean cnf_line_GREPPATH(char *); +staticfn boolean cnf_line_CRASHREPORTURL(char *); +staticfn boolean cnf_line_ACCESSIBILITY(char *); + +staticfn boolean cnf_line_PORTABLE_DEVICE_PATHS(char *); +#endif /* SYSCF */ +#ifndef SFCTOOL +staticfn boolean cnf_line_BOULDER(char *); +staticfn boolean cnf_line_MENUCOLOR(char *); +staticfn boolean cnf_line_HILITE_STATUS(char *); +staticfn boolean cnf_line_WARNINGS(char *); +staticfn boolean cnf_line_ROGUESYMBOLS(char *); +staticfn boolean cnf_line_SYMBOLS(char *); +staticfn boolean cnf_line_WIZKIT(char *); +#ifdef USER_SOUNDS +staticfn boolean cnf_line_SOUNDDIR(char *); +staticfn boolean cnf_line_SOUND(char *); +#endif +staticfn boolean cnf_line_QT_TILEWIDTH(char *); +staticfn boolean cnf_line_QT_TILEHEIGHT(char *); +staticfn boolean cnf_line_QT_FONTSIZE(char *); +staticfn boolean cnf_line_QT_COMPACT(char *); +#endif /* SFCTOOL */ +struct _cnf_parser_state; /* defined below (far below...) */ +staticfn void cnf_parser_init(struct _cnf_parser_state *parser); +staticfn void cnf_parser_done(struct _cnf_parser_state *parser); +staticfn void parse_conf_buf(struct _cnf_parser_state *parser, + boolean (*proc)(char *arg)); +/* next one is in extern.h; why here too? */ +boolean parse_conf_str(const char *str, boolean (*proc)(char *arg)); +static boolean ignore_errors_on_unmatched = FALSE, + ignore_statement_errors = FALSE; + +#ifdef SFCTOOL +#ifdef wait_synch +#undef wait_synch +#endif +#define wait_synch() +#endif /* SFCTOOL */ + +/* ---------- BEGIN CONFIG FILE HANDLING ----------- */ + +/* used for messaging. Also used in options.c */ +static const char *default_configfile = +#ifdef UNIX + ".nethackrc"; +#else +#if defined(MACOS9) || defined(__BEOS__) + "NetHack Defaults"; +#else +#if defined(MSDOS) || defined(WIN32) + CONFIG_FILE; +#else + "NetHack.cnf"; +#endif +#endif +#endif +static char configfile[BUFSZ]; + +char * +get_configfile(void) +{ + return configfile; +} + +const char * +get_default_configfile(void) +{ + return default_configfile; +} + +#ifdef MSDOS +/* conflict with speed-dial under windows + * for XXX.cnf file so support of NetHack.cnf + * is for backward compatibility only. + * Preferred name (and first tried) is now defaults.nh but + * the game will try the old name if there + * is no defaults.nh. + */ +const char *backward_compat_configfile = "nethack.cnf"; +#endif + +#ifndef SFCTOOL + +/* #saveoptions - save config options into file */ +int +do_write_config_file(void) +{ + FILE *fp; + char tmp[BUFSZ]; + + if (!configfile[0]) { + pline("Strange, could not figure out config file name."); + return ECMD_OK; + } + if (flags.suppress_alert < FEATURE_NOTICE_VER(3,7,0)) { + pline("Warning: saveoptions is highly experimental!"); + wait_synch(); + pline("Some settings are not saved!"); + wait_synch(); + pline("All manual customization and comments are removed" + " from the file!"); + wait_synch(); + } +#define overwrite_prompt "Overwrite config file %.*s?" + Sprintf(tmp, overwrite_prompt, + (int) (BUFSZ - sizeof overwrite_prompt - 2), configfile); +#undef overwrite_prompt + if (!paranoid_query(TRUE, tmp)) + return ECMD_OK; + + fp = fopen(configfile, "w"); + if (fp) { + size_t len, wrote; + strbuf_t buf; + + strbuf_init(&buf); + all_options_strbuf(&buf); + len = strlen(buf.str); + wrote = fwrite(buf.str, 1, len, fp); + fclose(fp); + strbuf_empty(&buf); + if (wrote != len) + pline("An error occurred, wrote only partial data (%zu/%zu).", + wrote, len); + } + return ECMD_OK; +} +#endif /* SFCTOOL */ + +/* remember the name of the file we're accessing; + if may be used in option reject messages */ +void +set_configfile_name(const char *fname) +{ + (void) strncpy(configfile, fname, sizeof configfile - 1); + configfile[sizeof configfile - 1] = '\0'; +} + +staticfn FILE * +fopen_config_file(const char *filename, int src) +{ + FILE *fp; +#if defined(UNIX) || defined(VMS) + char tmp_config[BUFSZ]; + char *envp; +#endif + + if (src == set_in_sysconf) { + /* SYSCF_FILE; if we can't open it, caller will bail */ + if (filename && *filename) { + set_configfile_name(fqname(filename, SYSCONFPREFIX, 0)); + fp = fopen(configfile, "r"); + } else + fp = (FILE *) 0; + return fp; + } + /* If src != set_in_sysconf, "filename" is an environment variable, so it + * should hang around. If set, it is expected to be a full path name + * (if relevant) + */ + if (filename && *filename) { + set_configfile_name(filename); +#ifdef UNIX + if (!strncmp(configfile, "~/", 2) && (envp = nh_getenv("HOME")) != 0) { + /* support for command line '--nethackrc=~/path' (or for + NETHACKOPTIONS='@~/path'; we don't support ~user/path) */ + Snprintf(tmp_config, sizeof tmp_config, "%s/%s", + envp, configfile + 2); /* insert $HOME/ and remove ~/ */ + set_configfile_name(tmp_config); + } + if (access(configfile, 4) == -1) { /* 4 is R_OK on newer systems */ + /* nasty sneaky attempt to read file through + * NetHack's setuid permissions -- this is the only + * place a file name may be wholly under the player's + * control (but SYSCF_FILE is not under the player's + * control so it's OK). + */ + raw_printf("Access to %s denied (%d).", configfile, errno); + wait_synch(); + /* fall through to standard names */ + } else +#endif + if ((fp = fopen(configfile, "r")) != (FILE *) 0) { + return fp; +#if defined(UNIX) || defined(VMS) + } else { + /* access() above probably caught most problems for UNIX */ + raw_printf("Couldn't open requested config file %s (%d).", + configfile, errno); + wait_synch(); +#endif + } + } + /* fall through to standard names */ + + return (FILE *) 0; /* NLE: Stop here, don't read .nethackrc etc. */ + +#if defined(MICRO) || defined(MACOS9) || defined(__BEOS__) || defined(WIN32) + set_configfile_name(fqname(default_configfile, CONFIGPREFIX, 0)); + if ((fp = fopen(configfile, "r")) != (FILE *) 0) { + return fp; + } else if (strcmp(default_configfile, configfile)) { + set_configfile_name(default_configfile); + if ((fp = fopen(configfile, "r")) != (FILE *) 0) + return fp; + } +#ifdef MSDOS + set_configfile_name(fqname(backward_compat_configfile, CONFIGPREFIX, 0)); + if ((fp = fopen(configfile, "r")) != (FILE *) 0) { + return fp; + } else if (strcmp(backward_compat_configfile, configfile)) { + set_configfile_name(backward_compat_configfile); + if ((fp = fopen(configfile, "r")) != (FILE *) 0) + return fp; + } +#endif +#else +/* constructed full path names don't need fqname() */ +#ifdef VMS + /* no punctuation, so might be a logical name */ + set_configfile_name("nethackini"); + if ((fp = fopen(configfile, "r")) != (FILE *) 0) + return fp; + set_configfile_name("sys$login:nethack.ini"); + if ((fp = fopen(configfile, "r")) != (FILE *) 0) + return fp; + + envp = nh_getenv("HOME"); + if (!envp || !*envp) + Strcpy(tmp_config, "NetHack.cnf"); + else + Sprintf(tmp_config, "%s%s%s", envp, + !strchr(":]>/", envp[strlen(envp) - 1]) ? "/" : "", + "NetHack.cnf"); + set_configfile_name(tmp_config); + if ((fp = fopen(configfile, "r")) != (FILE *) 0) + return fp; +#else /* should be only UNIX left */ + envp = nh_getenv("HOME"); + if (!envp) + Strcpy(tmp_config, ".nethackrc"); + else + Sprintf(tmp_config, "%s/%s", envp, ".nethackrc"); + + set_configfile_name(tmp_config); + if ((fp = fopen(configfile, "r")) != (FILE *) 0) + return fp; +#if defined(__APPLE__) /* UNIX+__APPLE__ => OSX || MacOS */ + /* try an alternative */ + if (envp) { + /* keep 'tmp_config' intact here; if alternates fail, use it to + restore configfile[] to its preferred setting (".nethackrc") */ + char alt_config[sizeof tmp_config]; + + /* OSX-style configuration settings */ + Snprintf(alt_config, sizeof alt_config, "%s/%s", envp, + "Library/Preferences/NetHack Defaults"); + set_configfile_name(alt_config); + if ((fp = fopen(configfile, "r")) != (FILE *) 0) + return fp; + /* may be easier for user to edit if filename has '.txt' suffix */ + Snprintf(alt_config, sizeof alt_config, "%s/%s", envp, + "Library/Preferences/NetHack Defaults.txt"); + set_configfile_name(alt_config); + if ((fp = fopen(configfile, "r")) != (FILE *) 0) + return fp; + /* couldn't open either of the alternate names; for use in + messages, put 'configfile' back to the normal value rather than + leaving it set to last alternate; retry open() to reset 'errno' */ + set_configfile_name(tmp_config); + if ((fp = fopen(configfile, "r")) != (FILE *) 0) + return fp; + } +#endif /*__APPLE__*/ + if (errno != ENOENT) { + const char *details; + + /* e.g., problems when setuid NetHack can't search home + directory restricted to user */ +#if defined(NHSTDC) && !defined(NOTSTDC) + if ((details = strerror(errno)) == 0) +#endif + details = ""; + raw_printf("Couldn't open default config file %s %s(%d).", + configfile, details, errno); + wait_synch(); + } +#endif /* !VMS => Unix */ +#endif /* !(MICRO || MACOS9 || __BEOS__ || WIN32) */ + return (FILE *) 0; +} + +/* + * Retrieve a list of integers from buf into a uchar array. + * + * NOTE: zeros are inserted unless modlist is TRUE, in which case the list + * location is unchanged. Callers must handle zeros if modlist is FALSE. + */ +staticfn int +get_uchars(char *bufp, /* current pointer */ + uchar *list, /* return list */ + boolean modlist, /* TRUE: list is being modified in place */ + int size, /* return list size */ + const char *name) /* name of option for error message */ +{ + unsigned int num = 0; + int count = 0; + boolean havenum = FALSE; + + while (1) { + switch (*bufp) { + case ' ': + case '\0': + case '\t': + case '\n': + if (havenum) { + /* if modifying in place, don't insert zeros */ + if (num || !modlist) + list[count] = num; + count++; + num = 0; + havenum = FALSE; + } + if (count == size || !*bufp) + return count; + bufp++; + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + havenum = TRUE; + num = num * 10 + (*bufp - '0'); + bufp++; + break; + + case '\\': + goto gi_error; + break; + + default: + gi_error: + raw_printf("Syntax error in %s", name); + wait_synch(); + return count; + } + } + /*NOTREACHED*/ +} + +#ifdef NOCWD_ASSUMPTIONS +staticfn void +adjust_prefix(char *bufp, int prefixid) +{ + char *ptr; + + if (!bufp) + return; +#ifdef WIN32 + if (fqn_prefix_locked[prefixid]) + return; +#endif + /* Backward compatibility, ignore trailing ;n */ + if ((ptr = strchr(bufp, ';')) != 0) + *ptr = '\0'; + if (strlen(bufp) > 0) { + gf.fqn_prefix[prefixid] = (char *) alloc(strlen(bufp) + 2); + Strcpy(gf.fqn_prefix[prefixid], bufp); + append_slash(gf.fqn_prefix[prefixid]); + } +} +#endif + +/* Choose at random one of the sep separated parts from str. Mangles str. */ +staticfn char * +choose_random_part(char *str, char sep) +{ + int nsep = 1; + int csep; + int len = 0; + char *begin = str; + + if (!str) + return (char *) 0; + + while (*str) { + if (*str == sep) + nsep++; + str++; + } +#ifndef SFCTOOL + csep = rn2(nsep); +#else + nhUse(nsep); + csep = 1; +#endif + str = begin; + while ((csep > 0) && *str) { + str++; + if (*str == sep) + csep--; + } + if (*str) { + if (*str == sep) + str++; + begin = str; + while (*str && *str != sep) { + str++; + len++; + } + *str = '\0'; + if (len) + return begin; + } + return (char *) 0; +} + +staticfn void +free_config_sections(void) +{ + if (gc.config_section_chosen) { + free(gc.config_section_chosen); + gc.config_section_chosen = NULL; + } + if (gc.config_section_current) { + free(gc.config_section_current); + gc.config_section_current = NULL; + } +} + +/* check for " [ anything-except-bracket-or-empty ] # arbitrary-comment" + with spaces optional; returns pointer to "anything-except..." (with + trailing " ] #..." stripped) if ok, otherwise Null */ +staticfn char * +is_config_section( + char *str) /* trailing spaces are stripped, ']' too iff result is good */ +{ + char *a, *c, *z; + + /* remove any spaces at start and end; won't significantly interfere + with echoing the string in a config error message, if warranted */ + a = trimspaces(str); + /* first character should be open square bracket; set pointer past it */ + if (*a++ != '[') + return (char *) 0; + /* last character should be close bracket, ignoring any comment */ + z = strchr(a, ']'); + if (!z) + return (char *) 0; + /* comment, if present, can be preceded by spaces */ + for (c = z + 1; *c == ' '; ++c) + continue; + if (*c && *c != '#') + return (char *) 0; + /* we now know that result is good; there won't be a config error + message so we can modify the input string */ + *z = '\0'; + /* 'a' points past '[' and the string ends where ']' was; remove any + spaces between '[' and choice-start and between choice-end and ']' */ + return trimspaces(a); +} + +staticfn boolean +handle_config_section(char *buf) +{ + char *sect = is_config_section(buf); + + if (sect) { + if (gc.config_section_current) + free(gc.config_section_current), gc.config_section_current = 0; + /* is_config_section() removed brackets from 'sect' */ + if (!gc.config_section_chosen) { + config_error_add("Section \"[%s]\" without CHOOSE", sect); + return TRUE; + } + if (*sect) { /* got a section name */ + gc.config_section_current = dupstr(sect); + debugpline1("set config section: '%s'", + gc.config_section_current); + } else { /* empty section name => end of sections */ + free_config_sections(); + debugpline0("unset config section"); + } + return TRUE; + } + + if (gc.config_section_current) { + if (!gc.config_section_chosen) + return TRUE; + if (strcmp(gc.config_section_current, gc.config_section_chosen)) + return TRUE; + } + return FALSE; +} + +#define match_varname(INP, NAM, LEN) match_optname(INP, NAM, LEN, TRUE) + +/* find the '=' or ':' */ +staticfn char * +find_optparam(char *buf) +{ + char *bufp, *altp; + + bufp = strchr(buf, '='); + altp = strchr(buf, ':'); + if (!bufp || (altp && altp < bufp)) + bufp = altp; + + return bufp; +} + +#ifndef SFCTOOL + +staticfn boolean +cnf_line_OPTIONS(char *origbuf) +{ + char *bufp = find_optparam(origbuf); + + ++bufp; /* skip '='; parseoptions() handles spaces */ + return parseoptions(bufp, TRUE, TRUE); +} + +staticfn boolean +cnf_line_AUTOPICKUP_EXCEPTION(char *bufp) +{ + add_autopickup_exception(bufp); + return TRUE; +} + +staticfn boolean +cnf_line_BINDINGS(char *bufp) +{ + return parsebindings(bufp); +} + +staticfn boolean +cnf_line_AUTOCOMPLETE(char *bufp) +{ + parseautocomplete(bufp, TRUE); + return TRUE; +} + +staticfn boolean +cnf_line_MSGTYPE(char *bufp) +{ + return msgtype_parse_add(bufp); +} + +staticfn boolean +cnf_line_HACKDIR(char *bufp) +{ +#ifdef NOCWD_ASSUMPTIONS + adjust_prefix(bufp, HACKPREFIX); +#else /*NOCWD_ASSUMPTIONS*/ +#ifdef MICRO + (void) strncpy(gh.hackdir, bufp, PATHLEN - 1); +#else /* MICRO */ + nhUse(bufp); +#endif /* MICRO */ +#endif /*NOCWD_ASSUMPTIONS*/ + return TRUE; +} + +staticfn boolean +cnf_line_LEVELDIR(char *bufp) +{ +#ifdef NOCWD_ASSUMPTIONS + adjust_prefix(bufp, LEVELPREFIX); +#else /*NOCWD_ASSUMPTIONS*/ +#ifdef MICRO + if (strlen(bufp) >= PATHLEN) + bufp[PATHLEN - 1] = '\0'; + Strcpy(g.permbones, bufp); + if (!ramdisk_specified || !*levels) + Strcpy(levels, bufp); + gr.ramdisk = (strcmp(g.permbones, levels) != 0); +#else /* MICRO */ + nhUse(bufp); +#endif /* MICRO */ +#endif /*NOCWD_ASSUMPTIONS*/ + return TRUE; +} + +staticfn boolean +cnf_line_SAVEDIR(char *bufp) +{ +#ifdef NOCWD_ASSUMPTIONS + adjust_prefix(bufp, SAVEPREFIX); +#else /*NOCWD_ASSUMPTIONS*/ +#ifdef MICRO + char *ptr; + + if ((ptr = strchr(bufp, ';')) != 0) { + *ptr = '\0'; + } + + (void) strncpy(gs.SAVEP, bufp, SAVESIZE - 1); + append_slash(gs.SAVEP); +#else /* MICRO */ + nhUse(bufp); +#endif /* MICRO */ +#endif /*NOCWD_ASSUMPTIONS*/ + return TRUE; +} + +staticfn boolean +cnf_line_BONESDIR(char *bufp) +{ +#ifdef NOCWD_ASSUMPTIONS + adjust_prefix(bufp, BONESPREFIX); +#else + nhUse(bufp); +#endif + return TRUE; +} + +staticfn boolean +cnf_line_DATADIR(char *bufp) +{ +#ifdef NOCWD_ASSUMPTIONS + adjust_prefix(bufp, DATAPREFIX); +#else + nhUse(bufp); +#endif + return TRUE; +} + +staticfn boolean +cnf_line_SCOREDIR(char *bufp) +{ +#ifdef NOCWD_ASSUMPTIONS + adjust_prefix(bufp, SCOREPREFIX); +#else + nhUse(bufp); +#endif + return TRUE; +} + +staticfn boolean +cnf_line_LOCKDIR(char *bufp) +{ +#ifdef NOCWD_ASSUMPTIONS + adjust_prefix(bufp, LOCKPREFIX); +#else + nhUse(bufp); +#endif + return TRUE; +} + +staticfn boolean +cnf_line_CONFIGDIR(char *bufp) +{ +#ifdef NOCWD_ASSUMPTIONS + adjust_prefix(bufp, CONFIGPREFIX); +#else + nhUse(bufp); +#endif + return TRUE; +} + +staticfn boolean +cnf_line_TROUBLEDIR(char *bufp) +{ +#ifdef NOCWD_ASSUMPTIONS + adjust_prefix(bufp, TROUBLEPREFIX); +#else + nhUse(bufp); +#endif + return TRUE; +} + +staticfn boolean +cnf_line_NAME(char *bufp) +{ + (void) strncpy(svp.plname, bufp, PL_NSIZ - 1); + return TRUE; +} + +staticfn boolean +cnf_line_ROLE(char *bufp) +{ + int len; + + if ((len = str2role(bufp)) >= 0) + flags.initrole = len; + return TRUE; +} + +staticfn boolean +cnf_line_dogname(char *bufp) +{ + (void) strncpy(gd.dogname, bufp, PL_PSIZ - 1); + return TRUE; +} + +staticfn boolean +cnf_line_catname(char *bufp) +{ + (void) strncpy(gc.catname, bufp, PL_PSIZ - 1); + return TRUE; +} +#endif /* SFCTOOL */ + +#ifdef SYSCF + +staticfn boolean +cnf_line_WIZARDS(char *bufp) +{ + if (sysopt.wizards) + free((genericptr_t) sysopt.wizards); + sysopt.wizards = dupstr(bufp); + if (strlen(sysopt.wizards) && strcmp(sysopt.wizards, "*")) { + /* pre-format WIZARDS list now; it's displayed during a panic + and since that panic might be due to running out of memory, + we don't want to risk attempting to allocate any memory then */ + if (sysopt.fmtd_wizard_list) + free((genericptr_t) sysopt.fmtd_wizard_list); + sysopt.fmtd_wizard_list = build_english_list(sysopt.wizards); + } + return TRUE; +} + +staticfn boolean +cnf_line_SHELLERS(char *bufp) +{ + if (sysopt.shellers) + free((genericptr_t) sysopt.shellers); + sysopt.shellers = dupstr(bufp); + return TRUE; +} + +staticfn boolean +cnf_line_MSGHANDLER(char *bufp) +{ + if (sysopt.msghandler) + free((genericptr_t) sysopt.msghandler); + sysopt.msghandler = dupstr(bufp); + return TRUE; +} + +staticfn boolean +cnf_line_EXPLORERS(char *bufp) +{ + if (sysopt.explorers) + free((genericptr_t) sysopt.explorers); + sysopt.explorers = dupstr(bufp); + return TRUE; +} + +staticfn boolean +cnf_line_DEBUGFILES(char *bufp) +{ + /* might already have a vaule from getenv("DEBUGFILES"); + if so, ignore this value from SYSCF */ + if (!sysopt.env_dbgfl) { + if (sysopt.debugfiles) + free((genericptr_t) sysopt.debugfiles); + sysopt.debugfiles = dupstr(bufp); + } + return TRUE; +} + +staticfn boolean +cnf_line_DUMPLOGFILE(char *bufp) +{ +#ifdef DUMPLOG + if (sysopt.dumplogfile) + free((genericptr_t) sysopt.dumplogfile); + sysopt.dumplogfile = dupstr(bufp); +#else + nhUse(bufp); +#endif /*DUMPLOG*/ + return TRUE; +} + +staticfn boolean +cnf_line_GENERICUSERS(char *bufp) +{ + if (sysopt.genericusers) + free((genericptr_t) sysopt.genericusers); + sysopt.genericusers = dupstr(bufp); + return TRUE; +} + +staticfn boolean +cnf_line_BONES_POOLS(char *bufp) +{ + /* max value of 10 guarantees (N % bones.pools) will be one digit + so we don't lose control of the length of bones file names */ + int n = atoi(bufp); + + sysopt.bones_pools = (n <= 0) ? 0 : min(n, 10); + /* note: right now bones_pools==0 is the same as bones_pools==1, + but we could change that and make bones_pools==0 become an + indicator to suppress bones usage altogether */ + return TRUE; +} + +staticfn boolean +cnf_line_SUPPORT(char *bufp) +{ + if (sysopt.support) + free((genericptr_t) sysopt.support); + sysopt.support = dupstr(bufp); + return TRUE; +} + +staticfn boolean +cnf_line_RECOVER(char *bufp) +{ + if (sysopt.recover) + free((genericptr_t) sysopt.recover); + sysopt.recover = dupstr(bufp); + return TRUE; +} + +staticfn boolean +cnf_line_CHECK_SAVE_UID(char *bufp) +{ + int n = atoi(bufp); + + sysopt.check_save_uid = n; + return TRUE; +} + +staticfn boolean +cnf_line_CHECK_PLNAME(char *bufp) +{ + int n = atoi(bufp); + + sysopt.check_plname = n; + return TRUE; +} + +staticfn boolean +cnf_line_SEDUCE(char *bufp) +{ + int n = !!atoi(bufp); /* XXX this could be tighter */ +#ifdef SYSCF + int src = iflags.parse_config_file_src; + boolean in_sysconf = (src == set_in_sysconf); +#else + boolean in_sysconf = FALSE; +#endif + + /* allow anyone to disable it but can only enable it in sysconf + or as a no-op for the user when sysconf hasn't disabled it */ + if (!in_sysconf && !sysopt.seduce && n != 0) { + config_error_add("Illegal value in SEDUCE"); + n = 0; + } + sysopt.seduce = n; + sysopt_seduce_set(sysopt.seduce); + return TRUE; +} + +staticfn boolean +cnf_line_HIDEUSAGE(char *bufp) +{ + int n = !!atoi(bufp); + + sysopt.hideusage = n; + return TRUE; +} + +staticfn boolean +cnf_line_MAXPLAYERS(char *bufp) +{ + int n = atoi(bufp); + + /* XXX to get more than 25, need to rewrite all lock code */ + if (n < 0 || n > 25) { + config_error_add("Illegal value in MAXPLAYERS (maximum is 25)"); + n = 5; + } + sysopt.maxplayers = n; + return TRUE; +} + +staticfn boolean +cnf_line_PERSMAX(char *bufp) +{ + int n = atoi(bufp); + + if (n < 1) { + config_error_add("Illegal value in PERSMAX (minimum is 1)"); + n = 0; + } + sysopt.persmax = n; + return TRUE; +} + +staticfn boolean +cnf_line_PERS_IS_UID(char *bufp) +{ + int n = atoi(bufp); + + if (n != 0 && n != 1) { + config_error_add("Illegal value in PERS_IS_UID (must be 0 or 1)"); + n = 0; + } + sysopt.pers_is_uid = n; + return TRUE; +} + +staticfn boolean +cnf_line_ENTRYMAX(char *bufp) +{ + int n = atoi(bufp); + + if (n < 10) { + config_error_add("Illegal value in ENTRYMAX (minimum is 10)"); + n = 10; + } + sysopt.entrymax = n; + return TRUE; +} + +staticfn boolean +cnf_line_POINTSMIN(char *bufp) +{ + int n = atoi(bufp); + + if (n < 1) { + config_error_add("Illegal value in POINTSMIN (minimum is 1)"); + n = 100; + } + sysopt.pointsmin = n; + return TRUE; +} + +staticfn boolean +cnf_line_MAX_STATUENAME_RANK(char *bufp) +{ + int n = atoi(bufp); + + if (n < 1) { + config_error_add("Illegal value in MAX_STATUENAME_RANK" + " (minimum is 1)"); + n = 10; + } + sysopt.tt_oname_maxrank = n; + return TRUE; +} + +staticfn boolean +cnf_line_LIVELOG(char *bufp) +{ + /* using 0 for base accepts "dddd" as decimal provided that first 'd' + isn't '0', "0xhhhh" as hexadecimal, and "0oooo" as octal; ignores + any trailing junk, including '8' or '9' for leading '0' octal */ + long L = strtol(bufp, NULL, 0); + + if (L < 0L || L > 0xffffL) { + config_error_add("Illegal value for LIVELOG" + " (must be between 0 and 0xFFFF)."); + return 0; + } + sysopt.livelog = L; + return TRUE; +} + +staticfn boolean +cnf_line_PANICTRACE_LIBC(char *bufp) +{ + int n = atoi(bufp); + +#if defined(PANICTRACE) && defined(PANICTRACE_LIBC) + if (n < 0 || n > 2) { + config_error_add("Illegal value in PANICTRACE_LIBC (not 0,1,2)"); + n = 0; + } +#endif + sysopt.panictrace_libc = n; + return TRUE; +} + +staticfn boolean +cnf_line_PANICTRACE_GDB(char *bufp) +{ + int n = atoi(bufp); + +#if defined(PANICTRACE) + if (n < 0 || n > 2) { + config_error_add("Illegal value in PANICTRACE_GDB (not 0,1,2)"); + n = 0; + } +#endif + sysopt.panictrace_gdb = n; + return TRUE; +} + +staticfn boolean +cnf_line_GDBPATH(char *bufp) +{ +#if defined(PANICTRACE) && !defined(VMS) + if (!file_exists(bufp)) { + config_error_add("File specified in GDBPATH does not exist"); + return FALSE; + } +#endif + if (sysopt.gdbpath) + free((genericptr_t) sysopt.gdbpath); + sysopt.gdbpath = dupstr(bufp); + return TRUE; +} + +staticfn boolean +cnf_line_GREPPATH(char *bufp) +{ +#if defined(PANICTRACE) && !defined(VMS) + if (!file_exists(bufp)) { + config_error_add("File specified in GREPPATH does not exist"); + return FALSE; + } +#endif + if (sysopt.greppath) + free((genericptr_t) sysopt.greppath); + sysopt.greppath = dupstr(bufp); + return TRUE; +} + +staticfn boolean +cnf_line_CRASHREPORTURL(char *bufp) +{ + if (sysopt.crashreporturl) + free((genericptr_t) sysopt.crashreporturl); + sysopt.crashreporturl = dupstr(bufp); + return TRUE; +} + +staticfn boolean +cnf_line_ACCESSIBILITY(char *bufp) +{ + int n = atoi(bufp); + + if (n < 0 || n > 1) { + config_error_add("Illegal value in ACCESSIBILITY (not 0,1)"); + n = 0; + } + sysopt.accessibility = n; + return TRUE; +} + +staticfn boolean +cnf_line_PORTABLE_DEVICE_PATHS(char *bufp) +{ +#ifdef WIN32 + int n = atoi(bufp); + + if (n < 0 || n > 1) { + config_error_add("Illegal value in PORTABLE_DEVICE_PATHS" + " (not 0 or 1)"); + n = 0; + } + sysopt.portable_device_paths = n; +#else /* Windows-only directive encountered by non-Windows config */ + nhUse(bufp); + config_error_add("PORTABLE_DEVICE_PATHS is not supported"); +#endif + return TRUE; +} +#endif /* SYSCF */ + +#ifndef SFCTOOL + +staticfn boolean +cnf_line_BOULDER(char *bufp) +{ + (void) get_uchars(bufp, &go.ov_primary_syms[SYM_BOULDER + SYM_OFF_X], + TRUE, 1, "BOULDER"); + return TRUE; +} + +staticfn boolean +cnf_line_MENUCOLOR(char *bufp) +{ + return add_menu_coloring(bufp); +} + +staticfn boolean +cnf_line_HILITE_STATUS(char *bufp) +{ +#ifdef STATUS_HILITES + return parse_status_hl1(bufp, TRUE); +#else + nhUse(bufp); + return TRUE; +#endif +} + +staticfn boolean +cnf_line_WARNINGS(char *bufp) +{ + uchar translate[MAXPCHARS]; + + (void) get_uchars(bufp, translate, FALSE, WARNCOUNT, "WARNINGS"); + assign_warnings(translate); + return TRUE; +} + +staticfn boolean +cnf_line_ROGUESYMBOLS(char *bufp) +{ + if (parsesymbols(bufp, ROGUESET)) { + switch_symbols(TRUE); + return TRUE; + } + config_error_add("Error in ROGUESYMBOLS definition '%s'", bufp); + return FALSE; +} + +staticfn boolean +cnf_line_SYMBOLS(char *bufp) +{ + if (parsesymbols(bufp, PRIMARYSET)) { + switch_symbols(TRUE); + return TRUE; + } + if (!config_unmatched_ignored()) + config_error_add("Error in SYMBOLS definition '%s'", bufp); + return FALSE; +} + +staticfn boolean +cnf_line_WIZKIT(char *bufp) +{ + (void) strncpy(gw.wizkit, bufp, WIZKIT_MAX - 1); + return TRUE; +} + +#ifdef USER_SOUNDS +staticfn boolean +cnf_line_SOUNDDIR(char *bufp) +{ + if (sounddir) + free((genericptr_t) sounddir); + sounddir = dupstr(bufp); + return TRUE; +} + +staticfn boolean +cnf_line_SOUND(char *bufp) +{ + add_sound_mapping(bufp); + return TRUE; +} +#endif /*USER_SOUNDS*/ + +staticfn boolean +cnf_line_QT_TILEWIDTH(char *bufp) +{ +#ifdef QT_GRAPHICS + extern char *qt_tilewidth; + + if (qt_tilewidth == NULL) + qt_tilewidth = dupstr(bufp); +#else + nhUse(bufp); +#endif + return TRUE; +} + +staticfn boolean +cnf_line_QT_TILEHEIGHT(char *bufp) +{ +#ifdef QT_GRAPHICS + extern char *qt_tileheight; + + if (qt_tileheight == NULL) + qt_tileheight = dupstr(bufp); +#else + nhUse(bufp); +#endif + return TRUE; +} + +staticfn boolean +cnf_line_QT_FONTSIZE(char *bufp) +{ +#ifdef QT_GRAPHICS + extern char *qt_fontsize; + + if (qt_fontsize == NULL) + qt_fontsize = dupstr(bufp); +#else + nhUse(bufp); +#endif + return TRUE; +} + +staticfn boolean +cnf_line_QT_COMPACT(char *bufp) +{ +#ifdef QT_GRAPHICS + extern int qt_compact_mode; + + qt_compact_mode = atoi(bufp); +#else + nhUse(bufp); +#endif + return TRUE; +} +#endif /* SFCTOOL */ + +typedef boolean (*config_line_stmt_func)(char *); + +/* normal */ +#define CNFL_N(n, l) { #n, l, FALSE, FALSE, cnf_line_##n } +/* normal, alias */ +#define CNFL_NA(n, l, f) { #n, l, FALSE, FALSE, cnf_line_##f } +/* sysconf only */ +#define CNFL_S(n, l) { #n, l, TRUE, FALSE, cnf_line_##n } + +static const struct match_config_line_stmt { + const char *name; + int len; + boolean syscnf_only; + boolean origbuf; + config_line_stmt_func fn; +} config_line_stmt[] = { +#ifndef SFCTOOL + /* OPTIONS handled separately */ + { "OPTIONS", 4, FALSE, TRUE, cnf_line_OPTIONS }, + CNFL_N(AUTOPICKUP_EXCEPTION, 5), + CNFL_N(BINDINGS, 4), + CNFL_N(AUTOCOMPLETE, 5), + CNFL_N(MSGTYPE, 7), + CNFL_N(HACKDIR, 4), + CNFL_N(LEVELDIR, 4), + CNFL_NA(LEVELS, 4, LEVELDIR), + CNFL_N(SAVEDIR, 4), + CNFL_N(BONESDIR, 5), + CNFL_N(DATADIR, 4), + CNFL_N(SCOREDIR, 4), + CNFL_N(LOCKDIR, 4), + CNFL_N(CONFIGDIR, 4), + CNFL_N(TROUBLEDIR, 4), + CNFL_N(NAME, 4), + CNFL_N(ROLE, 4), + CNFL_NA(CHARACTER, 4, ROLE), + CNFL_N(dogname, 3), + CNFL_N(catname, 3), +#endif /* SFCTOOL */ +#ifdef SYSCF + CNFL_S(WIZARDS, 7), + CNFL_S(SHELLERS, 8), + CNFL_S(MSGHANDLER, 9), + CNFL_S(EXPLORERS, 7), + CNFL_S(DEBUGFILES, 5), + CNFL_S(DUMPLOGFILE, 7), + CNFL_S(GENERICUSERS, 12), + CNFL_S(BONES_POOLS, 10), + CNFL_S(SUPPORT, 7), + CNFL_S(RECOVER, 7), + CNFL_S(CHECK_SAVE_UID, 14), + CNFL_S(CHECK_PLNAME, 12), + CNFL_S(SEDUCE, 6), + CNFL_S(HIDEUSAGE, 9), + CNFL_S(MAXPLAYERS, 10), + CNFL_S(PERSMAX, 7), + CNFL_S(PERS_IS_UID, 11), + CNFL_S(ENTRYMAX, 8), + CNFL_S(POINTSMIN, 9), + CNFL_S(MAX_STATUENAME_RANK, 10), + CNFL_S(LIVELOG, 7), + CNFL_S(PANICTRACE_LIBC, 15), + CNFL_S(PANICTRACE_GDB, 14), + CNFL_S(CRASHREPORTURL, 13), + CNFL_S(GDBPATH, 7), + CNFL_S(GREPPATH, 7), + CNFL_S(ACCESSIBILITY, 13), + CNFL_S(PORTABLE_DEVICE_PATHS, 8), +#endif /*SYSCF*/ +#ifndef SFCTOOL + CNFL_N(BOULDER, 3), + CNFL_N(MENUCOLOR, 9), + CNFL_N(HILITE_STATUS, 6), + CNFL_N(WARNINGS, 5), + CNFL_N(ROGUESYMBOLS, 4), + CNFL_N(SYMBOLS, 4), + CNFL_N(WIZKIT, 6), +#ifdef USER_SOUNDS + CNFL_N(SOUNDDIR, 8), + CNFL_N(SOUND, 5), +#endif /*USER_SOUNDS*/ + CNFL_N(QT_TILEWIDTH, 12), + CNFL_N(QT_TILEHEIGHT, 13), + CNFL_N(QT_FONTSIZE, 11), + CNFL_N(QT_COMPACT, 10) +#endif /* SFCTOOL */ +}; + +#undef CNFL_N +#undef CNFL_NA +#undef CNFL_S + +static boolean disregarded_config_lines[SIZE(config_line_stmt)]; + +boolean +parse_config_line(char *origbuf) +{ +#if defined(MICRO) && !defined(NOCWD_ASSUMPTIONS) + static boolean ramdisk_specified = FALSE; +#endif +#ifdef SYSCF + int src = iflags.parse_config_file_src; + boolean in_sysconf = (src == set_in_sysconf); +#endif + char *bufp, buf[4 * BUFSZ]; + int i; + + while (*origbuf == ' ' || *origbuf == '\t') /* skip leading whitespace */ + ++origbuf; /* (caller probably already did this) */ + (void) strncpy(buf, origbuf, sizeof buf - 1); + buf[sizeof buf - 1] = '\0'; /* strncpy not guaranteed to NUL terminate */ + /* convert any tab to space, condense consecutive spaces into one, + remove leading and trailing spaces (exception: if there is nothing + but spaces, one of them will be kept even though it leads/trails) */ + mungspaces(buf); + + /* find the '=' or ':' */ + bufp = find_optparam(buf); + if (!bufp) { + if (!ignore_statement_errors) + config_error_add("Not a config statement, missing '='"); + return FALSE; + } + /* skip past '=', then space between it and value, if any */ + ++bufp; + if (*bufp == ' ') + ++bufp; + + for (i = 0; i < SIZE(config_line_stmt); i++) { +#ifdef SYSCF + if (config_line_stmt[i].syscnf_only && !in_sysconf) + continue; +#endif + if (match_varname(buf, config_line_stmt[i].name, + config_line_stmt[i].len)) { + char *parm = config_line_stmt[i].origbuf ? origbuf : bufp; + + if (!disregarded_config_lines[i]) + return config_line_stmt[i].fn(parm); + } + } + + if (!ignore_errors_on_unmatched) + config_error_add("Unknown config statement"); + return FALSE; +} + +#ifdef USER_SOUNDS +boolean +can_read_file(const char *filename) +{ + return (boolean) (access(filename, 4) == 0); +} +#endif /* USER_SOUNDS */ + +struct _config_error_errmsg { + int line_num; + char *errormsg; + struct _config_error_errmsg *next; +}; + +struct _config_error_frame { + int line_num; + int num_errors; + boolean origline_shown; + boolean fromfile; + boolean secure; + char origline[4 * BUFSZ]; + char source[BUFSZ]; + struct _config_error_frame *next; +}; + +static struct _config_error_frame *config_error_data = 0; +static struct _config_error_errmsg *config_error_msg = 0; + +void +config_error_init(boolean from_file, const char *sourcename, boolean secure) +{ + struct _config_error_frame *tmp = (struct _config_error_frame *) + alloc(sizeof *tmp); + + tmp->line_num = 0; + tmp->num_errors = 0; + tmp->origline_shown = FALSE; + tmp->fromfile = from_file; + tmp->secure = secure; + tmp->origline[0] = '\0'; + if (sourcename && sourcename[0]) { + (void) strncpy(tmp->source, sourcename, sizeof (tmp->source) - 1); + tmp->source[sizeof (tmp->source) - 1] = '\0'; + } else + tmp->source[0] = '\0'; + + tmp->next = config_error_data; + config_error_data = tmp; + program_state.config_error_ready = TRUE; +} + +staticfn boolean +config_error_nextline(const char *line) +{ + struct _config_error_frame *ced = config_error_data; + + if (!ced) + return FALSE; + + if (ced->num_errors && ced->secure) + return FALSE; + + ced->line_num++; + ced->origline_shown = FALSE; + if (line && line[0]) { + (void) strncpy(ced->origline, line, sizeof (ced->origline) - 1); + ced->origline[sizeof (ced->origline) - 1] = '\0'; + } else + ced->origline[0] = '\0'; + + return TRUE; +} + +#ifndef SFCTOOL +int +l_get_config_errors(lua_State *L) +{ + struct _config_error_errmsg *dat = config_error_msg; + struct _config_error_errmsg *tmp; + int idx = 1; + + lua_newtable(L); + + while (dat) { + lua_pushinteger(L, idx++); + lua_newtable(L); + nhl_add_table_entry_int(L, "line", dat->line_num); + nhl_add_table_entry_str(L, "error", dat->errormsg); + lua_settable(L, -3); + tmp = dat->next; + free(dat->errormsg); + dat->errormsg = (char *) 0; + free(dat); + dat = tmp; + } + config_error_msg = (struct _config_error_errmsg *) 0; + + return 1; +} +#endif /* SFCTOOL */ + +/* varargs 'config_error_add()' moved to pline.c */ +void +config_erradd(const char *buf) +{ + char lineno[QBUFSZ]; + const char *punct; + + if (!buf || !*buf) + buf = "Unknown error"; + + /* if buf[] doesn't end in a period, exclamation point, or question mark, + we'll include a period (in the message, not appended to buf[]) */ + punct = c_eos((char *) buf) - 1; /* eos(buf)-1 is valid */ + punct = strchr(".!?", *punct) ? "" : "."; + + if (!program_state.config_error_ready) { + /* either very early, where pline() will use raw_print(), or + player gave bad value when prompted by interactive 'O' command */ + pline("%s%s%s", !iflags.window_inited ? "config_error_add: " : "", + buf, punct); + wait_synch(); + return; + } + + if (iflags.in_lua) { + struct _config_error_errmsg *dat + = (struct _config_error_errmsg *) alloc(sizeof *dat); + + dat->next = config_error_msg; + dat->line_num = config_error_data->line_num; + dat->errormsg = dupstr(buf); + config_error_msg = dat; + return; + } + + config_error_data->num_errors++; + if (!config_error_data->origline_shown && !config_error_data->secure) { + pline("\n%s", config_error_data->origline); + config_error_data->origline_shown = TRUE; + } + if (config_error_data->line_num > 0 && !config_error_data->secure) { + Sprintf(lineno, "Line %d: ", config_error_data->line_num); + } else + lineno[0] = '\0'; + + pline("%s %s%s%s", config_error_data->secure ? "Error:" : " *", + lineno, buf, punct); +} + +int +config_error_done(void) +{ + int n; + struct _config_error_frame *tmp = config_error_data; + + if (!config_error_data) + return 0; + n = config_error_data->num_errors; +#ifndef USER_SOUNDS + if (gn.no_sound_notified > 0) { + /* no USER_SOUNDS; config_error_add() was called once for first + SOUND or SOUNDDIR entry seen, then skipped for any others; + include those skipped ones in the total error count */ + n += (gn.no_sound_notified - 1); + gn.no_sound_notified = 0; + } +#endif + if (n) { + boolean cmdline = !strcmp(config_error_data->source, "command line"); + + pline("\n%d error%s %s %s.\n", n, plur(n), cmdline ? "on" : "in", + *config_error_data->source ? config_error_data->source + : configfile); + wait_synch(); + } + config_error_data = tmp->next; + free(tmp); + program_state.config_error_ready = (config_error_data != 0); + return n; +} + +boolean +read_config_file(const char *filename, int src) +{ + FILE *fp; + boolean rv = TRUE; + + if (!(fp = fopen_config_file(filename, src))) + return FALSE; +#ifndef SFCTOOL + /* begin detection of duplicate configfile options */ + reset_duplicate_opt_detection(); +#endif /* SFCTOOL */ + free_config_sections(); + iflags.parse_config_file_src = src; + + rv = parse_conf_file(fp, parse_config_line); + (void) fclose(fp); + + free_config_sections(); +#ifndef SFCTOOL + /* turn off detection of duplicate configfile options */ + reset_duplicate_opt_detection(); +#endif /* SFCTOOL */ + return rv; +} + +struct _cnf_parser_state { + char *inbuf; + unsigned inbufsz; + int rv; + char *ep; + char *buf; + boolean skip, morelines; + boolean cont; + boolean pbreak; +}; + +/* Initialize config parser data */ +staticfn void +cnf_parser_init(struct _cnf_parser_state *parser) +{ + parser->rv = TRUE; /* assume successful parse */ + parser->ep = parser->buf = (char *) 0; + parser->skip = FALSE; + parser->morelines = FALSE; + parser->inbufsz = 4 * BUFSZ; + parser->inbuf = (char *) alloc(parser->inbufsz); + parser->cont = FALSE; + parser->pbreak = FALSE; + memset(parser->inbuf, 0, parser->inbufsz); +} + +/* caller has finished with 'parser' (except for 'rv' so leave that intact) */ +staticfn void +cnf_parser_done(struct _cnf_parser_state *parser) +{ + parser->ep = 0; /* points into parser->inbuf, so becoming stale */ + if (parser->inbuf) + free(parser->inbuf), parser->inbuf = 0; + if (parser->buf) + free(parser->buf), parser->buf = 0; +} + +/* + * Parse config buffer, handling comments, empty lines, config sections, + * CHOOSE, and line continuation, calling proc for every valid line. + * + * Continued lines are merged together with one space in between. + */ +staticfn void +parse_conf_buf(struct _cnf_parser_state *p, boolean (*proc)(char *arg)) +{ + p->cont = FALSE; + p->pbreak = FALSE; + p->ep = strchr(p->inbuf, '\n'); + if (p->skip) { /* in case previous line was too long */ + if (p->ep) + p->skip = FALSE; /* found newline; next line is normal */ + } else { + if (!p->ep) { /* newline missing */ + if (strlen(p->inbuf) < (p->inbufsz - 2)) { + /* likely the last line of file is just + missing a newline; process it anyway */ + p->ep = eos(p->inbuf); + } else { + config_error_add("Line too long, skipping"); + p->skip = TRUE; /* discard next fgets */ + } + } else { + *p->ep = '\0'; /* remove newline */ + } + if (p->ep) { + char *tmpbuf = (char *) 0; + int len; + boolean ignoreline = FALSE; + boolean oldline = FALSE; + + /* line continuation (trailing '\') */ + p->morelines = (--p->ep >= p->inbuf && *p->ep == '\\'); + if (p->morelines) + *p->ep = '\0'; + + /* trim off spaces at end of line */ + while (p->ep >= p->inbuf + && (*p->ep == ' ' || *p->ep == '\t' || *p->ep == '\r')) + *p->ep-- = '\0'; + + if (!config_error_nextline(p->inbuf)) { + p->rv = FALSE; + if (p->buf) + free(p->buf), p->buf = (char *) 0; + p->pbreak = TRUE; + return; + } + + p->ep = p->inbuf; + while (*p->ep == ' ' || *p->ep == '\t') + ++p->ep; + + /* ignore empty lines and full-line comment lines */ + if (!*p->ep || *p->ep == '#') + ignoreline = TRUE; + + if (p->buf) + oldline = TRUE; + + /* merge now read line with previous ones, if necessary */ + if (!ignoreline) { + len = (int) strlen(p->ep) + 1; /* +1: final '\0' */ + if (p->buf) + len += (int) strlen(p->buf) + 1; /* +1: space */ + tmpbuf = (char *) alloc(len); + *tmpbuf = '\0'; + if (p->buf) { + Strcat(strcpy(tmpbuf, p->buf), " "); + free(p->buf), p->buf = 0; + } + p->buf = strcat(tmpbuf, p->ep); + if (strlen(p->buf) >= p->inbufsz) + p->buf[p->inbufsz - 1] = '\0'; + } + + if (p->morelines || (ignoreline && !oldline)) + return; + + if (handle_config_section(p->buf)) { + free(p->buf), p->buf = (char *) 0; + return; + } + + /* from here onwards, we'll handle buf only */ + + if (match_varname(p->buf, "CHOOSE", 6)) { + char *section; + char *bufp = find_optparam(p->buf); + + if (!bufp) { + config_error_add("Format is CHOOSE=section1" + ",section2,..."); + p->rv = FALSE; + free(p->buf), p->buf = (char *) 0; + return; + } + bufp++; + if (gc.config_section_chosen) + free(gc.config_section_chosen), + gc.config_section_chosen = 0; + section = choose_random_part(bufp, ','); + if (section) { + gc.config_section_chosen = dupstr(section); + } else { + config_error_add("No config section to choose"); + p->rv = FALSE; + } + free(p->buf), p->buf = (char *) 0; + return; + } + + if (!(*proc)(p->buf)) + p->rv = FALSE; + + free(p->buf), p->buf = (char *) 0; + } + } +} + +boolean +parse_conf_str(const char *str, boolean (*proc)(char *arg)) +{ + size_t len; + struct _cnf_parser_state parser; + + cnf_parser_init(&parser); + free_config_sections(); + config_error_init(FALSE, "parse_conf_str", FALSE); + while (str && *str) { + len = 0; + while (*str && len < (parser.inbufsz-1)) { + parser.inbuf[len] = *str; + len++; + str++; + if (parser.inbuf[len-1] == '\n') + break; + } + parser.inbuf[len] = '\0'; + parse_conf_buf(&parser, proc); + if (parser.pbreak) + break; + } + cnf_parser_done(&parser); + + free_config_sections(); + config_error_done(); + return parser.rv; +} + +/* parse_conf_file + * + * Read from file fp, calling parse_conf_buf for each line. + */ +boolean +parse_conf_file(FILE *fp, boolean (*proc)(char *arg)) +{ + struct _cnf_parser_state parser; + + cnf_parser_init(&parser); + free_config_sections(); + + while (fgets(parser.inbuf, parser.inbufsz, fp)) { + parse_conf_buf(&parser, proc); + if (parser.pbreak) + break; + } + cnf_parser_done(&parser); + + free_config_sections(); + return parser.rv; +} + +DISABLE_WARNING_FORMAT_NONLITERAL + +void +config_error_add(const char *str, ...) +{ + va_list the_args; + + va_start(the_args, str); + vconfig_error_add(str, the_args); + va_end(the_args); +} + +staticfn void +vconfig_error_add(const char *str, va_list the_args) +{ /* start of vconf...() or of nested block in USE_OLDARG's conf...() */ + int vlen = 0; + char buf[BIGBUFSZ]; /* will be chopped down to BUFSZ-1 if longer */ + + vlen = vsnprintf(buf, sizeof buf, str, the_args); +#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) && defined(DEBUG) + if (vlen >= (int) sizeof buf) + panic("%s: truncation of buffer at %zu of %d bytes", + "config_error_add", sizeof buf, vlen); +#else + nhUse(vlen); +#endif + buf[BUFSZ - 1] = '\0'; + config_erradd(buf); +} + +/* Added for NLE. */ +extern char * nle_getenv(const char *); + +#ifndef SFCTOOL +void +rcfile(void) +{ + char *opts = 0, *xtraopts = 0; + const char *envname, *namesrc, *nameval; + + go.opt_phase = environ_opt; + /* getenv() instead of nhgetenv(): let total length of options be long; + parseoptions() will check each individually */ + envname = "NETHACKOPTIONS"; + /* NLE: Use nle specific function */ + opts = nle_getenv(envname); + if (!opts) { + /* fall back to original name; discouraged */ + envname = "HACKOPTIONS"; + opts = getenv(envname); + } + + if (gc.cmdline_rcfile) { + namesrc = "command line"; + nameval = gc.cmdline_rcfile; + xtraopts = opts; + if (opts && (*opts == '/' || *opts == '\\' || *opts == '@')) + xtraopts = 0; /* NETHACKOPTIONS is a file name; ignore it */ + } else if (opts && (*opts == '/' || *opts == '\\' || *opts == '@')) { + /* NETHACKOPTIONS is a file name; use that instead of the default */ + if (*opts == '@') + ++opts; /* @filename */ + namesrc = envname; + nameval = opts; + xtraopts = 0; + } else { + /* either no NETHACKOPTIONS or it wasn't a file name; + read the default configuration file */ + nameval = namesrc = 0; + xtraopts = opts; + } + + go.opt_phase = rc_file_opt; + /* seemingly arbitrary name length restriction is to prevent error + messages, if any were to be delivered while accessing the file, + from potentially overflowing buffers */ + if (nameval && (int) strlen(nameval) >= BUFSZ / 2) { + config_error_init(TRUE, namesrc, FALSE); + config_error_add( + "nethackrc file name \"%.40s\"... too long; using default", + nameval); + config_error_done(); + nameval = namesrc = 0; /* revert to default nethackrc */ + } + + config_error_init(TRUE, nameval, nameval ? CONFIG_ERROR_SECURE : FALSE); + (void) read_config_file(nameval, set_in_config); + config_error_done(); + if (xtraopts) { + /* NETHACKOPTIONS is present and not a file name */ + go.opt_phase = environ_opt; + config_error_init(FALSE, envname, FALSE); + (void) parseoptions(xtraopts, TRUE, FALSE); + config_error_done(); + } + + if (gc.cmdline_rcfile) + free((genericptr_t) gc.cmdline_rcfile), gc.cmdline_rcfile = 0; + /*[end of nethackrc handling]*/ +} + +void +rcfile_interface_options(void) +{ + allopt_array_init(); + disregard_all_options(); + disregard_all_config_statements(); + heed_this_option(opt_windowtype); + heed_this_option(opt_soundlib); + set_ignore_errors_on_unmatched(); + ignore_statement_errors = TRUE; + rcfile(); + heed_all_config_statements(); + heed_all_options(); + disregard_this_option(opt_windowtype); + disregard_this_option(opt_soundlib); + clear_ignore_errors_on_unmatched(); + ignore_statement_errors = FALSE; +} + +void +rcfile_only_this_option(enum opt heeded_option) +{ + allopt_array_init(); + disregard_all_options(); + disregard_all_config_statements(); + heed_this_option(heeded_option); + set_ignore_errors_on_unmatched(); + ignore_statement_errors = TRUE; + rcfile(); + heed_all_config_statements(); + heed_all_options(); + clear_ignore_errors_on_unmatched(); + ignore_statement_errors = FALSE; +} + +void +heed_all_config_statements(void) +{ + int i; + + for (i = 0; i < SIZE(disregarded_config_lines); i++) { + disregarded_config_lines[i] = FALSE; + } +} +void +disregard_all_config_statements(void) +{ + int i; + + for (i = 0; i < SIZE(disregarded_config_lines); i++) { + disregarded_config_lines[i] = TRUE; + } +} +void +heed_this_config_statement(int statement_idx) +{ + if (statement_idx >= 0 && statement_idx < SIZE(disregarded_config_lines)) + disregarded_config_lines[statement_idx] = FALSE; +} +void +disregard_this_config_statement(int statement_idx) +{ + if (statement_idx >= 0 && statement_idx < SIZE(disregarded_config_lines)) + disregarded_config_lines[statement_idx] = TRUE; +} + +void +clear_ignore_errors_on_unmatched(void) +{ + ignore_errors_on_unmatched = FALSE; +} +void +set_ignore_errors_on_unmatched(void) +{ + ignore_errors_on_unmatched = TRUE; +} +boolean +config_unmatched_ignored(void) +{ + if (ignore_errors_on_unmatched) + return TRUE; + return FALSE; +} +#endif /* SFCTOOL */ + +#ifdef SYSCF +#ifdef SYSCF_FILE +void +assure_syscf_file(void) +{ + int fd; + +#ifdef WIN32 + /* We are checking that the sysconf exists ... lock the path */ + fqn_prefix_locked[SYSCONFPREFIX] = TRUE; +#endif + /* + * All we really care about is the end result - can we read the file? + * So just check that directly. + * + * Not tested on most of the old platforms (which don't attempt + * to implement SYSCF). + * Some ports don't like open()'s optional third argument; + * VMS overrides open() usage with a macro which requires it. + */ +#ifndef VMS +#if defined(NOCWD_ASSUMPTIONS) && defined(WIN32) + fd = open(fqname(SYSCF_FILE, SYSCONFPREFIX, 0), O_RDONLY); +#else + fd = open(SYSCF_FILE, O_RDONLY); +#endif +#else /* VMS */ + fd = open(SYSCF_FILE, O_RDONLY, 0); +#endif /* VMS */ + if (fd >= 0) { + /* readable */ + close(fd); + return; + } +#ifndef SFCTOOL + if (gd.deferred_showpaths) + do_deferred_showpaths(1); /* does not return */ +#endif + raw_printf("Unable to open SYSCF_FILE.\n"); + exit(EXIT_FAILURE); +} + +#endif /* SYSCF_FILE */ +#endif /* SYSCF */ + +/* ---------- END CONFIG FILE HANDLING ----------- */ + +/*cfgfiles.c*/ + diff --git a/src/cmd.c b/src/cmd.c index 657233570..0ad65d3a0 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -1,40 +1,11 @@ -/* NetHack 3.6 cmd.c $NHDT-Date: 1575245052 2019/12/02 00:04:12 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.350 $ */ +/* NetHack 5.0 cmd.c $NHDT-Date: 1762680996 2025/11/09 01:36:36 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.755 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2013. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -#include "lev.h" #include "func_tab.h" -/* Macros for meta and ctrl modifiers: - * M and C return the meta/ctrl code for the given character; - * e.g., (C('c') is ctrl-c - */ -#ifndef M -#ifndef NHSTDC -#define M(c) (0x80 | (c)) -#else -#define M(c) ((c) - 128) -#endif /* NHSTDC */ -#endif - -#ifndef C -#define C(c) (0x1f & (c)) -#endif - -#define unctrl(c) ((c) <= C('z') ? (0x60 | (c)) : (c)) -#define unmeta(c) (0x7f & (c)) - -#ifdef ALTMETA -STATIC_VAR boolean alt_esc = FALSE; -#endif - -struct cmd Cmd = { 0 }; /* flag.h */ - -extern const char *hu_stat[]; /* hunger status from eat.c */ -extern const char *enc_stat[]; /* encumbrance status from botl.c */ - #ifdef UNIX /* * Some systems may have getchar() return EOF for various reasons, and @@ -45,186 +16,165 @@ extern const char *enc_stat[]; /* encumbrance status from botl.c */ #endif #endif -#define CMD_TRAVEL (char) 0x90 -#define CMD_CLICKLOOK (char) 0x8F - -#ifdef DEBUG -extern int NDECL(wiz_debug_cmd_bury); -#endif - #ifdef DUMB /* stuff commented out in extern.h, but needed here */ -extern int NDECL(doapply); /**/ -extern int NDECL(dorub); /**/ -extern int NDECL(dojump); /**/ -extern int NDECL(doextlist); /**/ -extern int NDECL(enter_explore_mode); /**/ -extern int NDECL(dodrop); /**/ -extern int NDECL(doddrop); /**/ -extern int NDECL(dodown); /**/ -extern int NDECL(doup); /**/ -extern int NDECL(donull); /**/ -extern int NDECL(dowipe); /**/ -extern int NDECL(docallcnd); /**/ -extern int NDECL(dotakeoff); /**/ -extern int NDECL(doremring); /**/ -extern int NDECL(dowear); /**/ -extern int NDECL(doputon); /**/ -extern int NDECL(doddoremarm); /**/ -extern int NDECL(dokick); /**/ -extern int NDECL(dofire); /**/ -extern int NDECL(dothrow); /**/ -extern int NDECL(doeat); /**/ -extern int NDECL(done2); /**/ -extern int NDECL(vanquished); /**/ -extern int NDECL(doengrave); /**/ -extern int NDECL(dopickup); /**/ -extern int NDECL(ddoinv); /**/ -extern int NDECL(dotypeinv); /**/ -extern int NDECL(dolook); /**/ -extern int NDECL(doprgold); /**/ -extern int NDECL(doprwep); /**/ -extern int NDECL(doprarm); /**/ -extern int NDECL(doprring); /**/ -extern int NDECL(dopramulet); /**/ -extern int NDECL(doprtool); /**/ -extern int NDECL(dosuspend); /**/ -extern int NDECL(doforce); /**/ -extern int NDECL(doopen); /**/ -extern int NDECL(doclose); /**/ -extern int NDECL(dosh); /**/ -extern int NDECL(dodiscovered); /**/ -extern int NDECL(doclassdisco); /**/ -extern int NDECL(doset); /**/ -extern int NDECL(dotogglepickup); /**/ -extern int NDECL(dowhatis); /**/ -extern int NDECL(doquickwhatis); /**/ -extern int NDECL(dowhatdoes); /**/ -extern int NDECL(dohelp); /**/ -extern int NDECL(dohistory); /**/ -extern int NDECL(doloot); /**/ -extern int NDECL(dodrink); /**/ -extern int NDECL(dodip); /**/ -extern int NDECL(dosacrifice); /**/ -extern int NDECL(dopray); /**/ -extern int NDECL(dotip); /**/ -extern int NDECL(doturn); /**/ -extern int NDECL(doredraw); /**/ -extern int NDECL(doread); /**/ -extern int NDECL(dosave); /**/ -extern int NDECL(dosearch); /**/ -extern int NDECL(doidtrap); /**/ -extern int NDECL(dopay); /**/ -extern int NDECL(dosit); /**/ -extern int NDECL(dotalk); /**/ -extern int NDECL(docast); /**/ -extern int NDECL(dovspell); /**/ -extern int NDECL(dotelecmd); /**/ -extern int NDECL(dountrap); /**/ -extern int NDECL(doversion); /**/ -extern int NDECL(doextversion); /**/ -extern int NDECL(doswapweapon); /**/ -extern int NDECL(dowield); /**/ -extern int NDECL(dowieldquiver); /**/ -extern int NDECL(dozap); /**/ -extern int NDECL(doorganize); /**/ +extern int doapply(void); /**/ +extern int dorub(void); /**/ +extern int dojump(void); /**/ +extern int doextlist(void); /**/ +extern int enter_explore_mode(void); /**/ +extern int dodrop(void); /**/ +extern int doddrop(void); /**/ +extern int dodown(void); /**/ +extern int doup(void); /**/ +extern int donull(void); /**/ +extern int dowipe(void); /**/ +extern int docallcnd(void); /**/ +extern int dotakeoff(void); /**/ +extern int doremring(void); /**/ +extern int dowear(void); /**/ +extern int doputon(void); /**/ +extern int doddoremarm(void); /**/ +extern int dokick(void); /**/ +extern int dofire(void); /**/ +extern int dothrow(void); /**/ +extern int doeat(void); /**/ +extern int done2(void); /**/ +extern int dovanquished(void); /**/ +extern int doengrave(void); /**/ +extern int dopickup(void); /**/ +extern int ddoinv(void); /**/ +extern int dotypeinv(void); /**/ +extern int dolook(void); /**/ +extern int doprgold(void); /**/ +extern int doprwep(void); /**/ +extern int doprarm(void); /**/ +extern int doprring(void); /**/ +extern int dopramulet(void); /**/ +extern int doprtool(void); /**/ +extern int dosuspend(void); /**/ +extern int doforce(void); /**/ +extern int doopen(void); /**/ +extern int doclose(void); /**/ +extern int dosh(void); /**/ +extern int dodiscovered(void); /**/ +extern int doclassdisco(void); /**/ +extern int doset_simple(void); /**/ +extern int doset(void); /**/ +extern int dotogglepickup(void); /**/ +extern int dowhatis(void); /**/ +extern int doquickwhatis(void); /**/ +extern int dowhatdoes(void); /**/ +extern int dohelp(void); /**/ +extern int dohistory(void); /**/ +extern int doloot(void); /**/ +extern int dodrink(void); /**/ +extern int dodip(void); /**/ +extern int dosacrifice(void); /**/ +extern int dopray(void); /**/ +extern int dotip(void); /**/ +extern int doturn(void); /**/ +extern int doredraw(void); /**/ +extern int doread(void); /**/ +extern int dosave(void); /**/ +extern int dosearch(void); /**/ +extern int doidtrap(void); /**/ +extern int dopay(void); /**/ +extern int dosit(void); /**/ +extern int dotalk(void); /**/ +extern int docast(void); /**/ +extern int dovspell(void); /**/ +extern int dotelecmd(void); /**/ +extern int dountrap(void); /**/ +extern int doversion(void); /**/ +extern int doextversion(void); /**/ +extern int doswapweapon(void); /**/ +extern int dowield(void); /**/ +extern int dowieldquiver(void); /**/ +extern int dozap(void); /**/ +extern int doorganize(void); /**/ #endif /* DUMB */ -static int NDECL((*timed_occ_fn)); - -STATIC_PTR int NDECL(dosuspend_core); -STATIC_PTR int NDECL(dosh_core); -STATIC_PTR int NDECL(doherecmdmenu); -STATIC_PTR int NDECL(dotherecmdmenu); -STATIC_PTR int NDECL(doprev_message); -STATIC_PTR int NDECL(timed_occupation); -STATIC_PTR int NDECL(doextcmd); -STATIC_PTR int NDECL(dotravel); -STATIC_PTR int NDECL(doterrain); -STATIC_PTR int NDECL(wiz_wish); -STATIC_PTR int NDECL(wiz_identify); -STATIC_PTR int NDECL(wiz_intrinsic); -STATIC_PTR int NDECL(wiz_map); -STATIC_PTR int NDECL(wiz_makemap); -STATIC_PTR int NDECL(wiz_genesis); -STATIC_PTR int NDECL(wiz_where); -STATIC_PTR int NDECL(wiz_detect); -STATIC_PTR int NDECL(wiz_panic); -STATIC_PTR int NDECL(wiz_polyself); -STATIC_PTR int NDECL(wiz_level_tele); -STATIC_PTR int NDECL(wiz_level_change); -STATIC_PTR int NDECL(wiz_show_seenv); -STATIC_PTR int NDECL(wiz_show_vision); -STATIC_PTR int NDECL(wiz_smell); -STATIC_PTR int NDECL(wiz_show_wmodes); -STATIC_DCL void NDECL(wiz_map_levltyp); -STATIC_DCL void NDECL(wiz_levltyp_legend); +staticfn struct Cmd_bind *cmdbind_get(uchar); +staticfn void cmdbind_add(uchar, const struct ext_func_tab *, boolean); +staticfn void cmdbind_remove(uchar); +staticfn void cmdbind_swapkeys(uchar, uchar); +staticfn int dosuspend_core(void); +staticfn int dosh_core(void); +staticfn int doherecmdmenu(void); +staticfn int dotherecmdmenu(void); +staticfn int doprev_message(void); +staticfn int timed_occupation(void); +staticfn boolean can_do_extcmd(const struct ext_func_tab *); +staticfn int dotravel(void); +staticfn int dotravel_target(void); +staticfn int doclicklook(void); +staticfn boolean yn_menuable_resp(const char *); +staticfn void yn_func_menu_opt(winid, char, const char *, char); +staticfn boolean yn_function_menu(const char *, const char *, char, char *); +staticfn int domouseaction(void); +staticfn int doterrain(void); +staticfn boolean u_have_seen_whole_selection(struct selectionvar *); +staticfn boolean u_have_seen_bounds_selection(struct selectionvar *); +staticfn boolean u_can_see_whole_selection(struct selectionvar *); +staticfn int dolookaround_floodfill_findroom(coordxy, coordxy); +staticfn void lookaround_known_room(coordxy, coordxy); + #if defined(__BORLANDC__) && !defined(_WIN32) -extern void FDECL(show_borlandc_stats, (winid)); -#endif -#ifdef DEBUG_MIGRATING_MONS -STATIC_PTR int NDECL(wiz_migrate_mons); +extern void show_borlandc_stats(winid); #endif -STATIC_DCL int FDECL(size_monst, (struct monst *, BOOLEAN_P)); -STATIC_DCL int FDECL(size_obj, (struct obj *)); -STATIC_DCL void FDECL(count_obj, (struct obj *, long *, long *, - BOOLEAN_P, BOOLEAN_P)); -STATIC_DCL void FDECL(obj_chain, (winid, const char *, struct obj *, - BOOLEAN_P, long *, long *)); -STATIC_DCL void FDECL(mon_invent_chain, (winid, const char *, struct monst *, - long *, long *)); -STATIC_DCL void FDECL(mon_chain, (winid, const char *, struct monst *, - BOOLEAN_P, long *, long *)); -STATIC_DCL void FDECL(contained_stats, (winid, const char *, long *, long *)); -STATIC_DCL void FDECL(misc_stats, (winid, long *, long *)); -STATIC_PTR int NDECL(wiz_show_stats); -STATIC_DCL boolean FDECL(accept_menu_prefix, (int NDECL((*)))); -STATIC_PTR int NDECL(wiz_rumor_check); -STATIC_PTR int NDECL(doattributes); - -STATIC_DCL void FDECL(enlght_out, (const char *)); -STATIC_DCL void FDECL(enlght_line, (const char *, const char *, const char *, - const char *)); -STATIC_DCL char *FDECL(enlght_combatinc, (const char *, int, int, char *)); -STATIC_DCL void FDECL(enlght_halfdmg, (int, int)); -STATIC_DCL boolean NDECL(walking_on_water); -STATIC_DCL boolean FDECL(cause_known, (int)); -STATIC_DCL char *FDECL(attrval, (int, int, char *)); -STATIC_DCL void FDECL(background_enlightenment, (int, int)); -STATIC_DCL void FDECL(basics_enlightenment, (int, int)); -STATIC_DCL void FDECL(characteristics_enlightenment, (int, int)); -STATIC_DCL void FDECL(one_characteristic, (int, int, int)); -STATIC_DCL void FDECL(status_enlightenment, (int, int)); -STATIC_DCL void FDECL(attributes_enlightenment, (int, int)); - -STATIC_DCL void FDECL(add_herecmd_menuitem, (winid, int NDECL((*)), - const char *)); -STATIC_DCL char FDECL(here_cmd_menu, (BOOLEAN_P)); -STATIC_DCL char FDECL(there_cmd_menu, (BOOLEAN_P, int, int)); -STATIC_DCL char *NDECL(parse); -STATIC_DCL void FDECL(show_direction_keys, (winid, CHAR_P, BOOLEAN_P)); -STATIC_DCL boolean FDECL(help_dir, (CHAR_P, int, const char *)); +staticfn boolean accept_menu_prefix(const struct ext_func_tab *); +staticfn void reset_cmd_vars(boolean); + +staticfn void mcmd_addmenu(winid, int, const char *); +staticfn int there_cmd_menu_self(winid, coordxy, coordxy, int *); +staticfn int there_cmd_menu_next2u(winid, coordxy, coordxy, int, int *); +staticfn int there_cmd_menu_far(winid, coordxy, coordxy, int); +staticfn int there_cmd_menu_common(winid, coordxy, coordxy, int, int *); +staticfn void act_on_act(int, coordxy, coordxy); +staticfn char there_cmd_menu(coordxy, coordxy, int); +staticfn char here_cmd_menu(void); + +staticfn char readchar_core(coordxy *, coordxy *, int *); +staticfn int parse(void); +staticfn void show_direction_keys(winid, char, boolean); +staticfn boolean help_dir(char, uchar, const char *); + +staticfn void handler_rebind_keys_add(boolean); +staticfn boolean bind_key_fn(uchar, int (*)(void)); +staticfn void commands_init(void); +staticfn boolean keylist_func_has_key(const struct ext_func_tab *, boolean *); +staticfn int keylist_putcmds(winid, boolean, int, int, boolean *); +staticfn const char *spkey_name(int); + +staticfn int (*timed_occ_fn)(void); +staticfn char *doc_extcmd_flagstr(winid, const struct ext_func_tab *); +staticfn int dummyfunction(void); static const char *readchar_queue = ""; -static coord clicklook_cc; -/* for rejecting attempts to use wizard mode commands */ -static const char unavailcmd[] = "Unavailable command '%s'."; + +/* for rejecting attempts to use wizard mode commands + * Also used in wizcmds.c */ +const char unavailcmd[] = "Unavailable command '%s'."; + /* for rejecting #if !SHELL, !SUSPEND */ static const char cmdnotavail[] = "'%s' command not available."; -STATIC_PTR int -doprev_message(VOID_ARGS) +/* the #prevmsg command */ +staticfn int +doprev_message(void) { - return nh_doprev_message(); + (void) nh_doprev_message(); + return ECMD_OK; } /* Count down by decrementing multi */ -STATIC_PTR int -timed_occupation(VOID_ARGS) +staticfn int +timed_occupation(void) { (*timed_occ_fn)(); - if (multi > 0) - multi--; - return multi > 0; + if (gm.multi > 0) + gm.multi--; + return gm.multi > 0; } /* If you have moved since initially setting some occupations, they @@ -242,7 +192,7 @@ timed_occupation(VOID_ARGS) * Setting traps. */ void -reset_occupations() +reset_occupations(void) { reset_remarm(); reset_pick(); @@ -253,154 +203,392 @@ reset_occupations() * function times out by its own means. */ void -set_occupation(fn, txt, xtime) -int NDECL((*fn)); -const char *txt; -int xtime; +set_occupation(int (*fn)(void), const char *txt, cmdcount_nht xtime) { if (xtime) { - occupation = timed_occupation; + go.occupation = timed_occupation; timed_occ_fn = fn; } else - occupation = fn; - occtxt = txt; - occtime = 0; + go.occupation = fn; + go.occtxt = txt; + go.occtime = 0; return; } -STATIC_DCL char NDECL(popch); +/* +void +cmdq_print(int q) +{ + char buf[QBUFSZ]; + struct _cmd_queue *cq = gc.command_queue[q]; + + pline("CQ:%i", q); + while (cq) { + switch (cq->typ) { + case CMDQ_KEY: + pline("(key:%s)", key2txt(cq->key, buf)); + break; + case CMDQ_EXTCMD: + pline("(extcmd:#%s)", cq->ec_entry->ef_txt); + break; + case CMDQ_DIR: + pline("(dir:%i,%i,%i)", cq->dirx, cq->diry, cq->dirz); + break; + case CMDQ_USER_INPUT: + pline("(userinput)"); + break; + case CMDQ_INT: + pline("(int:%i)", cq->intval); + break; + default: + pline("(ERROR:%i)",cq->typ); + break; + } + cq = cq->next; + } +} +*/ + +/* add extended command function to the command queue */ +void +cmdq_add_ec(int q, int (*fn)(void)) +{ + struct _cmd_queue *tmp = (struct _cmd_queue *) alloc(sizeof *tmp); + struct _cmd_queue *cq = gc.command_queue[q]; + + tmp->typ = CMDQ_EXTCMD; + tmp->ec_entry = ext_func_tab_from_func(fn); + tmp->next = NULL; -/* Provide a means to redo the last command. The flag `in_doagain' is set - * to true while redoing the command. This flag is tested in commands that - * require additional input (like `throw' which requires a thing and a - * direction), and the input prompt is not shown. Also, while in_doagain is - * TRUE, no keystrokes can be saved into the saveq. - */ -#define BSIZE 20 -static char pushq[BSIZE], saveq[BSIZE]; -static NEARDATA int phead, ptail, shead, stail; + while (cq && cq->next) + cq = cq->next; + + if (cq) + cq->next = tmp; + else + gc.command_queue[q] = tmp; +} -STATIC_OVL char -popch() +/* add a key to the command queue */ +void +cmdq_add_key(int q, char key) { - /* If occupied, return '\0', letting tgetch know a character should - * be read from the keyboard. If the character read is not the - * ABORT character (as checked in pcmain.c), that character will be - * pushed back on the pushq. - */ - if (occupation) - return '\0'; - if (in_doagain) - return (char) ((shead != stail) ? saveq[stail++] : '\0'); + struct _cmd_queue *tmp = (struct _cmd_queue *) alloc(sizeof *tmp); + struct _cmd_queue *cq = gc.command_queue[q]; + + tmp->typ = CMDQ_KEY; + tmp->key = key; + tmp->next = NULL; + + while (cq && cq->next) + cq = cq->next; + + if (cq) + cq->next = tmp; + else + gc.command_queue[q] = tmp; +} + +/* add a direction to the command queue */ +void +cmdq_add_dir(int q, schar dx, schar dy, schar dz) +{ + struct _cmd_queue *tmp = (struct _cmd_queue *) alloc(sizeof *tmp); + struct _cmd_queue *cq = gc.command_queue[q]; + + tmp->typ = CMDQ_DIR; + tmp->dirx = dx; + tmp->diry = dy; + tmp->dirz = dz; + tmp->next = NULL; + + while (cq && cq->next) + cq = cq->next; + + if (cq) + cq->next = tmp; + else + gc.command_queue[q] = tmp; +} + +/* add placeholder to the command queue, allows user input there */ +void +cmdq_add_userinput(int q) +{ + struct _cmd_queue *tmp = (struct _cmd_queue *) alloc(sizeof *tmp); + struct _cmd_queue *cq = gc.command_queue[q]; + + tmp->typ = CMDQ_USER_INPUT; + tmp->next = NULL; + + while (cq && cq->next) + cq = cq->next; + + if (cq) + cq->next = tmp; else - return (char) ((phead != ptail) ? pushq[ptail++] : '\0'); + gc.command_queue[q] = tmp; +} + +/* add integer to the command queue */ +void +cmdq_add_int(int q, int val) +{ + struct _cmd_queue *tmp = (struct _cmd_queue *) alloc(sizeof *tmp); + struct _cmd_queue *cq = gc.command_queue[q]; + + tmp->typ = CMDQ_INT; + tmp->intval = val; + tmp->next = NULL; + + while (cq && cq->next) + cq = cq->next; + + if (cq) + cq->next = tmp; + else + gc.command_queue[q] = tmp; +} + +/* shift the last entry in command queue to first */ +void +cmdq_shift(int q) +{ + struct _cmd_queue *tmp = NULL; + struct _cmd_queue *cq = gc.command_queue[q]; + + while (cq && cq->next && cq->next->next) + cq = cq->next; + + if (cq) + tmp = cq->next; + if (tmp) { + tmp->next = gc.command_queue[q]; + gc.command_queue[q] = tmp; + cq->next = NULL; + } +} + +struct _cmd_queue * +cmdq_reverse(struct _cmd_queue *head) +{ + struct _cmd_queue *prev = NULL, *curr = head, *next; + + while (curr) { + next = curr->next; + curr->next = prev; + prev = curr; + curr = next; + } + return prev; +} + +struct _cmd_queue * +cmdq_copy(int q) +{ + struct _cmd_queue *tmp = NULL; + struct _cmd_queue *cq = gc.command_queue[q]; + + while (cq) { + struct _cmd_queue *tmp2 = (struct _cmd_queue *) alloc(sizeof *tmp2); + + *tmp2 = *cq; + tmp2->next = tmp; + tmp = tmp2; + cq = cq->next; + } + + tmp = cmdq_reverse(tmp); + + return tmp; +} + +/* pop off the topmost command from the command queue. + * caller is responsible for freeing the returned _cmd_queue. + */ +struct _cmd_queue * +cmdq_pop(void) +{ + int q = (gi.in_doagain) ? CQ_REPEAT : CQ_CANNED; + struct _cmd_queue *tmp = gc.command_queue[q]; + + if (tmp) { + gc.command_queue[q] = tmp->next; + tmp->next = NULL; + } + return tmp; +} + +/* get the top entry without popping it */ +struct _cmd_queue * +cmdq_peek(int q) +{ + return gc.command_queue[q]; +} + +/* clear all commands from the command queue */ +void +cmdq_clear(int q) +{ + struct _cmd_queue *tmp = gc.command_queue[q]; + struct _cmd_queue *tmp2; + + while (tmp) { + tmp2 = tmp->next; + free(tmp); + tmp = tmp2; + } + gc.command_queue[q] = NULL; } char -pgetchar() /* courtesy of aeb@cwi.nl */ +pgetchar(void) /* courtesy of aeb@cwi.nl */ { - register int ch; + int ch = '\0'; if (iflags.debug_fuzzer) return randomkey(); - if (!(ch = popch())) - ch = nhgetch(); + ch = nhgetch(); return (char) ch; } -/* A ch == 0 resets the pushq */ -void -pushch(ch) -char ch; +/* '#' or whatever has been bound to doextcmd() in its place */ +char +extcmd_initiator(void) { - if (!ch) - phead = ptail = 0; - if (phead < BSIZE) - pushq[phead++] = ch; - return; + return gc.Cmd.extcmd_char; } -/* A ch == 0 resets the saveq. Only save keystrokes when not - * replaying a previous command. - */ -void -savech(ch) -char ch; +staticfn boolean +can_do_extcmd(const struct ext_func_tab *extcmd) { - if (!in_doagain) { - if (!ch) - phead = ptail = shead = stail = 0; - else if (shead < BSIZE) - saveq[shead++] = ch; + int ecflags = extcmd->flags; + + if (gl.luacore && nhcb_counts[NHCB_CMD_BEFORE]) { + lua_getglobal(gl.luacore, "nh_callback_run"); + lua_pushstring(gl.luacore, nhcb_name[NHCB_CMD_BEFORE]); + lua_pushstring(gl.luacore, extcmd->ef_txt); + nhl_pcall_handle(gl.luacore, 2, 1, "can_do_extcmd", NHLpa_panic); + if (!lua_toboolean(gl.luacore, -1)) { + lua_settop(gl.luacore, 0); + return FALSE; + } + lua_settop(gl.luacore, 0); } - return; + + if (!wizard && (ecflags & WIZMODECMD)) { + pline(unavailcmd, extcmd->ef_txt); + return FALSE; + } else if (u.uburied && !(ecflags & IFBURIED)) { + You_cant("do that while you are buried!"); + return FALSE; + } else if (iflags.debug_fuzzer && (ecflags & NOFUZZERCMD)) { + return FALSE; + } + return TRUE; } /* here after # - now read a full-word command */ -STATIC_PTR int -doextcmd(VOID_ARGS) +int +doextcmd(void) { int idx, retval; - int NDECL((*func)); + int (*func)(void); /* keep repeating until we don't run help or quit */ do { idx = get_ext_cmd(); if (idx < 0) - return 0; /* quit */ + return ECMD_OK; /* quit */ func = extcmdlist[idx].ef_funct; - if (!wizard && (extcmdlist[idx].flags & WIZMODECMD)) { - You("can't do that."); - return 0; - } - if (iflags.menu_requested && !accept_menu_prefix(func)) { + if (!can_do_extcmd(&extcmdlist[idx])) + return ECMD_OK; + if (iflags.menu_requested && !accept_menu_prefix(&extcmdlist[idx])) { pline("'%s' prefix has no effect for the %s command.", - visctrl(Cmd.spkeys[NHKF_REQMENU]), + visctrl(cmd_from_func(do_reqmenu)), extcmdlist[idx].ef_txt); iflags.menu_requested = FALSE; } + /* tell rhack() what command is actually executing */ + ge.ext_tlist = &extcmdlist[idx]; + retval = (*func)(); } while (func == doextlist); return retval; } -/* here after #? - now list all full-word commands and provid +/* format extended command flags for display */ +staticfn char * +doc_extcmd_flagstr( + winid menuwin, + const struct ext_func_tab *efp) /* if Null, add a footnote to the menu */ +{ + static char Abuf[10]; /* 5 would suffice: {'[','m','A',']','\0'} */ + + /* note: tag shown for menu prefix is 'm' even if m-prefix action + has been bound to some other key */ + if (!efp) { + char qbuf[QBUFSZ]; + + add_menu_str(menuwin, "[A] Command autocompletes"); + Sprintf(qbuf, "[m] Command accepts '%s' prefix", + visctrl(cmd_from_func(do_reqmenu))); + add_menu_str(menuwin, qbuf); + return (char *) 0; + } else { + boolean mprefix = accept_menu_prefix(efp), + autocomplete = (efp->flags & AUTOCOMPLETE) != 0; + char *p = Abuf; + + /* "" or "[m]" or "[A]" or "[mA]" */ + if (mprefix || autocomplete) { + *p++ = '['; + if (mprefix) + *p++ = 'm'; + if (autocomplete) + *p++ = 'A'; + *p++ = ']'; + } + *p = '\0'; + return Abuf; + } +} + +/* here after #? - now list all full-word commands and provide some navigation capability through the long list */ int -doextlist(VOID_ARGS) +doextlist(void) { - register const struct ext_func_tab *efp; - char buf[BUFSZ], searchbuf[BUFSZ], promptbuf[QBUFSZ]; + const struct ext_func_tab *efp = (struct ext_func_tab *) 0; + char buf[BUFSZ], searchbuf[BUFSZ], descbuf[BUFSZ], promptbuf[QBUFSZ]; + const char *cmd_desc; winid menuwin; anything any; menu_item *selected; int n, pass; int menumode = 0, menushown[2], onelist = 0; boolean redisplay = TRUE, search = FALSE; - static const char *headings[] = { "Extended commands", + static const char *const headings[] = { "Extended commands", "Debugging Extended Commands" }; + int clr = NO_COLOR; searchbuf[0] = '\0'; menuwin = create_nhwindow(NHW_MENU); while (redisplay) { redisplay = FALSE; - any = zeroany; - start_menu(menuwin); - add_menu(menuwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - "Extended Commands List", MENU_UNSELECTED); - add_menu(menuwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - "", MENU_UNSELECTED); - - Strcpy(buf, menumode ? "Show" : "Hide"); - Strcat(buf, " commands that don't autocomplete"); - if (!menumode) - Strcat(buf, " (those not marked with [A])"); + any = cg.zeroany; + start_menu(menuwin, MENU_BEHAVE_STANDARD); + add_menu_str(menuwin, "Extended Commands List"); + add_menu_str(menuwin, ""); + + Sprintf(buf, "Switch to %s commands that don't autocomplete", + menumode ? "including" : "excluding"); any.a_int = 1; - add_menu(menuwin, NO_GLYPH, &any, 'a', 0, ATR_NONE, buf, - MENU_UNSELECTED); + add_menu(menuwin, &nul_glyphinfo, &any, 'a', 0, ATR_NONE, clr, buf, + MENU_ITEMFLAGS_NONE); if (!*searchbuf) { any.a_int = 2; @@ -409,10 +597,11 @@ doextlist(VOID_ARGS) actual list of extended commands shown via separator lines; having ':' as an explicit selector overrides the default menu behavior for it; we retain 's' as a group accelerator */ - add_menu(menuwin, NO_GLYPH, &any, ':', 's', ATR_NONE, - "Search extended commands", MENU_UNSELECTED); + add_menu(menuwin, &nul_glyphinfo, &any, ':', 's', ATR_NONE, + clr, "Search extended commands", + MENU_ITEMFLAGS_NONE); } else { - Strcpy(buf, "Show all, clear search"); + Strcpy(buf, "Switch back from search"); if (strlen(buf) + strlen(searchbuf) + strlen(" (\"\")") < QBUFSZ) Sprintf(eos(buf), " (\"%s\")", searchbuf); any.a_int = 3; @@ -421,19 +610,17 @@ doextlist(VOID_ARGS) also want to hide it from general menu use) because it won't work for interfaces which support ':' to search; use as a general menu command takes precedence over group accelerator */ - add_menu(menuwin, NO_GLYPH, &any, 's', ':', ATR_NONE, - buf, MENU_UNSELECTED); + add_menu(menuwin, &nul_glyphinfo, &any, 's', ':', ATR_NONE, + clr, buf, MENU_ITEMFLAGS_NONE); } if (wizard) { any.a_int = 4; - add_menu(menuwin, NO_GLYPH, &any, 'z', 0, ATR_NONE, - onelist ? "Show debugging commands in separate section" - : "Show all alphabetically, including debugging commands", - MENU_UNSELECTED); + add_menu(menuwin, &nul_glyphinfo, &any, 'z', 0, ATR_NONE, clr, + onelist ? "Switch to showing debugging commands in separate section" + : "Switch to showing all alphabetically, including debugging commands", + MENU_ITEMFLAGS_NONE); } - any = zeroany; - add_menu(menuwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - "", MENU_UNSELECTED); + add_menu_str(menuwin, ""); menushown[0] = menushown[1] = 0; n = 0; for (pass = 0; pass <= 1; ++pass) { @@ -444,21 +631,11 @@ doextlist(VOID_ARGS) for (efp = extcmdlist; efp->ef_txt; efp++) { int wizc; - if ((efp->flags & CMD_NOT_AVAILABLE) != 0) + if ((efp->flags & (CMD_NOT_AVAILABLE | INTERNALCMD)) != 0) continue; /* if hiding non-autocomplete commands, skip such */ if (menumode == 1 && (efp->flags & AUTOCOMPLETE) == 0) continue; - /* if searching, skip this command if it doesn't match */ - if (*searchbuf - /* first try case-insensitive substring match */ - && !strstri(efp->ef_txt, searchbuf) - && !strstri(efp->ef_desc, searchbuf) - /* wildcard support; most interfaces use case-insensitve - pmatch rather than regexp for menu searching */ - && !pmatchi(searchbuf, efp->ef_txt) - && !pmatchi(searchbuf, efp->ef_desc)) - continue; /* skip wizard mode commands if not in wizard mode; when showing two sections, skip wizard mode commands in pass==0 and skip other commands in pass==1 */ @@ -467,6 +644,26 @@ doextlist(VOID_ARGS) continue; if (!onelist && pass != wizc) continue; + /* command description might get modified on the fly */ + cmd_desc = efp->ef_desc; + /* suppress part of the description for #genocided if it + doesn't apply during the current game */ + if (!wizard && !discover + && (efp->flags & GENERALCMD) != 0 /* minor optimization */ + && strstri(cmd_desc, "extinct")) + cmd_desc = strsubst(strcpy(descbuf, cmd_desc), + " been genocided or become extinct", + " been genocided"); + /* if searching, skip this command if it doesn't match */ + if (*searchbuf + /* first try case-insensitive substring match */ + && !strstri(efp->ef_txt, searchbuf) + && !strstri(cmd_desc, searchbuf) + /* wildcard support; most interfaces use case-insensitive + pmatch rather than regexp for menu searching */ + && !pmatchi(searchbuf, efp->ef_txt) + && !pmatchi(searchbuf, cmd_desc)) + continue; /* We're about to show an item, have we shown the menu yet? Doing menu in inner loop like this on demand avoids a @@ -474,25 +671,23 @@ doextlist(VOID_ARGS) results menu. */ if (!menushown[pass]) { Strcpy(buf, headings[pass]); - add_menu(menuwin, NO_GLYPH, &any, 0, 0, - iflags.menu_headings, buf, MENU_UNSELECTED); + add_menu_heading(menuwin, buf); menushown[pass] = 1; } - Sprintf(buf, " %-14s %-3s %s", - efp->ef_txt, - (efp->flags & AUTOCOMPLETE) ? "[A]" : " ", - efp->ef_desc); - add_menu(menuwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - buf, MENU_UNSELECTED); + /* longest ef_txt at present is "wizrumorcheck" (13 chars); + 2nd field will be " " or " [A]" or " [m]" or "[mA]" */ + Sprintf(buf, " %-14s %4s %s", efp->ef_txt, + doc_extcmd_flagstr(menuwin, efp), cmd_desc); + add_menu_str(menuwin, buf); ++n; } if (n) - add_menu(menuwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - "", MENU_UNSELECTED); + add_menu_str(menuwin, ""); } if (*searchbuf && !n) - add_menu(menuwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - "no matches", MENU_UNSELECTED); + add_menu_str(menuwin, "no matches"); + else + (void) doc_extcmd_flagstr(menuwin, (struct ext_func_tab *) 0); end_menu(menuwin, (char *) 0); n = select_menu(menuwin, PICK_ONE, &selected); @@ -535,12 +730,14 @@ doextlist(VOID_ARGS) } } destroy_nhwindow(menuwin); - return 0; + return ECMD_OK; } #if defined(TTY_GRAPHICS) || defined(CURSES_GRAPHICS) #define MAX_EXT_CMD 200 /* Change if we ever have more ext cmds */ +DISABLE_WARNING_FORMAT_NONLITERAL + /* * This is currently used only by the tty interface and is * controlled via runtime option 'extmenu'. (Most other interfaces @@ -552,7 +749,7 @@ doextlist(VOID_ARGS) * Here after # - now show pick-list of possible commands. */ int -extcmd_via_menu() +extcmd_via_menu(void) { const struct ext_func_tab *efp; menu_item *pick_list = (menu_item *) 0; @@ -566,16 +763,17 @@ extcmd_via_menu() int accelerator, prevaccelerator; int matchlevel = 0; boolean wastoolong, one_per_line; + int clr = NO_COLOR; ret = 0; cbuf[0] = '\0'; biggest = 0; while (!ret) { i = n = 0; - any = zeroany; + any = cg.zeroany; /* populate choices */ for (efp = extcmdlist; efp->ef_txt; efp++) { - if ((efp->flags & CMD_NOT_AVAILABLE) + if ((efp->flags & (CMD_NOT_AVAILABLE|INTERNALCMD)) || !(efp->flags & AUTOCOMPLETE) || (!wizard && (efp->flags & WIZMODECMD))) continue; @@ -589,7 +787,7 @@ extcmd_via_menu() "Exceeded %d extended commands in doextcmd() menu; 'extmenu' disabled.", MAX_EXT_CMD); #endif /* NH_DEVEL_STATUS != NH_STATUS_RELEASED */ - iflags.extmenu = 0; + iflags.extmenu = FALSE; return -1; } } @@ -604,14 +802,14 @@ extcmd_via_menu() /* otherwise... */ win = create_nhwindow(NHW_MENU); - start_menu(win); + start_menu(win, MENU_BEHAVE_STANDARD); Sprintf(fmtstr, "%%-%ds", biggest + 15); prompt[0] = '\0'; wastoolong = FALSE; /* True => had to wrap due to line width * ('w' in wizard mode) */ /* -3: two line menu header, 1 line menu footer (for prompt) */ one_per_line = (nchoices < ROWNO - 3); - accelerator = prevaccelerator = 0; + prevaccelerator = 0; acount = 0; for (i = 0; choices[i]; ++i) { accelerator = choices[i]->ef_txt[matchlevel]; @@ -628,8 +826,8 @@ extcmd_via_menu() /* flush extended cmds for that letter already in buf */ Sprintf(buf, fmtstr, prompt); any.a_char = prevaccelerator; - add_menu(win, NO_GLYPH, &any, any.a_char, 0, ATR_NONE, - buf, FALSE); + add_menu(win, &nul_glyphinfo, &any, any.a_char, + 0, ATR_NONE, clr, buf, MENU_ITEMFLAGS_NONE); acount = 0; if (!(accelerator != prevaccelerator || one_per_line)) wastoolong = TRUE; @@ -652,10 +850,10 @@ extcmd_via_menu() /* flush buf */ Sprintf(buf, fmtstr, prompt); any.a_char = prevaccelerator; - add_menu(win, NO_GLYPH, &any, any.a_char, 0, ATR_NONE, buf, - FALSE); + add_menu(win, &nul_glyphinfo, &any, any.a_char, 0, + ATR_NONE, clr, buf, MENU_ITEMFLAGS_NONE); } - Sprintf(prompt, "Extended Command: %s", cbuf); + Snprintf(prompt, sizeof(prompt), "Extended Command: %s", cbuf); end_menu(win, prompt); n = select_menu(win, PICK_ONE, &pick_list); destroy_nhwindow(win); @@ -682,143 +880,132 @@ extcmd_via_menu() } return ret; } + +RESTORE_WARNING_FORMAT_NONLITERAL + #endif /* TTY_GRAPHICS */ /* #monster command - use special monster ability while polymorphed */ int -domonability(VOID_ARGS) +domonability(void) { - if (can_breathe(youmonst.data)) + struct permonst *uptr = gy.youmonst.data; + boolean might_hide = (is_hider(uptr) || hides_under(uptr)); + char c = '\0'; + + if (might_hide && webmaker(uptr)) { + c = yn_function("Hide [h] or spin a web [s]?", + hidespinchars, 'q', TRUE); + if (c == 'q' || c == '\033') + return ECMD_OK; + } + if (can_breathe(uptr)) return dobreathe(); - else if (attacktype(youmonst.data, AT_SPIT)) + else if (attacktype(uptr, AT_SPIT)) return dospit(); - else if (youmonst.data->mlet == S_NYMPH) + else if (uptr->mlet == S_NYMPH) return doremove(); - else if (attacktype(youmonst.data, AT_GAZE)) + else if (attacktype(uptr, AT_GAZE)) return dogaze(); - else if (is_were(youmonst.data)) + else if (is_were(uptr)) return dosummon(); - else if (webmaker(youmonst.data)) - return dospinweb(); - else if (is_hider(youmonst.data)) + else if (c ? c == 'h' : might_hide) return dohide(); - else if (is_mind_flayer(youmonst.data)) + else if (c ? c == 's' : webmaker(uptr)) + return dospinweb(); + else if (is_mind_flayer(uptr)) return domindblast(); else if (u.umonnum == PM_GREMLIN) { if (IS_FOUNTAIN(levl[u.ux][u.uy].typ)) { - if (split_mon(&youmonst, (struct monst *) 0)) + if (split_mon(&gy.youmonst, (struct monst *) 0)) dryup(u.ux, u.uy, TRUE); - } else + } else if (is_pool(u.ux, u.uy)) { + /* is_pool: might be wearing water walking boots or amulet of + magical breathing */ + (void) split_mon(&gy.youmonst, (struct monst *) 0); + } else { There("is no fountain here."); - } else if (is_unicorn(youmonst.data)) { - use_unicorn_horn((struct obj *) 0); - return 1; - } else if (youmonst.data->msound == MS_SHRIEK) { + } + } else if (is_unicorn(uptr)) { + use_unicorn_horn((struct obj **) 0); + return ECMD_TIME; + } else if (uptr->msound == MS_SHRIEK) { You("shriek."); if (u.uburied) pline("Unfortunately sound does not carry well through rock."); else aggravate(); - } else if (youmonst.data->mlet == S_VAMPIRE) + } else if (is_vampire(uptr) || is_vampshifter(&gy.youmonst)) { return dopoly(); - else if (Upolyd) + } else if (u.usteed && can_breathe(u.usteed->data)) { + (void) pet_ranged_attk(u.usteed, TRUE); + return ECMD_TIME; + } else if (Upolyd) { pline("Any special ability you may have is purely reflexive."); - else + } else { You("don't have a special ability in your normal form!"); - return 0; + } + return ECMD_OK; } int -enter_explore_mode(VOID_ARGS) +enter_explore_mode(void) { - if (wizard) { - You("are in debug mode."); - } else if (discover) { + if (discover) { You("are already in explore mode."); } else { -#ifdef SYSCF -#if defined(UNIX) - if (!sysopt.explorers || !sysopt.explorers[0] - || !check_user_string(sysopt.explorers)) { - You("cannot access explore mode."); - return 0; + const char *oldmode = !wizard ? "normal game" : "debug mode"; + + if (!authorize_explore_mode()) { + if (!wizard) { + You("cannot access explore mode."); + return ECMD_OK; + } else { + pline( + "Note: normally you wouldn't be allowed into explore mode."); + /* keep going */ + } } -#endif -#endif - pline( - "Beware! From explore mode there will be no return to normal game."); + pline("Beware! From explore mode there will be no return to %s,", + oldmode); if (paranoid_query(ParanoidQuit, "Do you want to enter explore mode?")) { + discover = TRUE; + wizard = FALSE; clear_nhwindow(WIN_MESSAGE); You("are now in non-scoring explore mode."); - discover = TRUE; } else { clear_nhwindow(WIN_MESSAGE); - pline("Resuming normal game."); + pline("Continuing with %s.", oldmode); } } - return 0; -} - -/* ^W command - wish for something */ -STATIC_PTR int -wiz_wish(VOID_ARGS) /* Unlimited wishes for debug mode by Paul Polderman */ -{ - if (wizard) { - boolean save_verbose = flags.verbose; - - flags.verbose = FALSE; - makewish(); - flags.verbose = save_verbose; - (void) encumber_msg(); - } else - pline(unavailcmd, visctrl((int) cmd_from_func(wiz_wish))); - return 0; -} - -/* ^I command - reveal and optionally identify hero's inventory */ -STATIC_PTR int -wiz_identify(VOID_ARGS) -{ - if (wizard) { - iflags.override_ID = (int) cmd_from_func(wiz_identify); - /* command remapping might leave #wizidentify as the only way - to invoke us, in which case cmd_from_func() will yield NUL; - it won't matter to display_inventory()/display_pickinv() - if ^I invokes some other command--what matters is that - display_pickinv() and xname() see override_ID as nonzero */ - if (!iflags.override_ID) - iflags.override_ID = C('I'); - (void) display_inventory((char *) 0, FALSE); - iflags.override_ID = 0; - } else - pline(unavailcmd, visctrl((int) cmd_from_func(wiz_identify))); - return 0; + return ECMD_OK; } -/* #wizmakemap - discard current dungeon level and replace with a new one */ -STATIC_PTR int -wiz_makemap(VOID_ARGS) +void +makemap_prepost(boolean pre, boolean wiztower) { - if (wizard) { - struct monst *mtmp; - boolean was_in_W_tower = In_W_tower(u.ux, u.uy, &u.uz); + NHFILE *tmpnhfp; + struct monst *mtmp; - rm_mapseen(ledger_no(&u.uz)); - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (mtmp->isgd) { /* vault is going away; get rid of guard */ - mtmp->isgd = 0; - mongone(mtmp); + if (pre) { + makemap_remove_mons(); + rm_mapseen(ledger_no(&u.uz)); /* discard overview info for level */ + { + static const char Unachieve[] = "%s achievement revoked."; + + /* achievement tracking; if replacing a level that has a + special prize, lose credit for previously finding it and + reset for the new instance of that prize */ + if (Is_mineend_level(&u.uz)) { + if (remove_achievement(ACH_MINE_PRIZE)) + pline(Unachieve, "Mine's-end"); + svc.context.achieveo.mines_prize_oid = 0; + } else if (Is_sokoend_level(&u.uz)) { + if (remove_achievement(ACH_SOKO_PRIZE)) + pline(Unachieve, "Soko-prize"); + svc.context.achieveo.soko_prize_oid = 0; } - if (DEADMONSTER(mtmp)) - continue; - if (mtmp->isshk) - setpaid(mtmp); - /* TODO? - * Reduce 'born' tally for each monster about to be discarded - * by savelev(), otherwise replacing heavily populated levels - * tends to make their inhabitants become extinct. - */ } if (Punished) { ballrelease(FALSE); @@ -827,38 +1014,36 @@ wiz_makemap(VOID_ARGS) /* reset lock picking unless it's for a carried container */ maybe_reset_pick((struct obj *) 0); /* reset interrupted digging if it was taking place on this level */ - if (on_level(&context.digging.level, &u.uz)) - (void) memset((genericptr_t) &context.digging, 0, + if (on_level(&svc.context.digging.level, &u.uz)) + (void) memset((genericptr_t) &svc.context.digging, 0, sizeof (struct dig_info)); /* reset cached targets */ iflags.travelcc.x = iflags.travelcc.y = 0; /* travel destination */ - context.polearm.hitmon = (struct monst *) 0; /* polearm target */ + svc.context.polearm.hitmon = (struct monst *) 0; /* polearm target */ /* escape from trap */ reset_utrap(FALSE); check_special_room(TRUE); /* room exit */ + (void) memset((genericptr_t) &svd.dndest, 0, sizeof (dest_area)); + (void) memset((genericptr_t) &svu.updest, 0, sizeof (dest_area)); u.ustuck = (struct monst *) 0; - u.uswallow = 0; - u.uinwater = 0; + u.uswallow = u.uswldtim = 0; + set_uinwater(0); /* u.uinwater = 0 */ u.uundetected = 0; /* not hidden, even if means are available */ dmonsfree(); /* purge dead monsters from 'fmon' */ - /* keep steed and other adjacent pets after releasing them - from traps, stopping eating, &c as if hero were ascending */ - keepdogs(TRUE); /* (pets-only; normally we'd be using 'FALSE' here) */ + dobjsfree(); /* discard current level; "saving" is used to release dynamic data */ - savelev(-1, ledger_no(&u.uz), FREE_SAVE); - /* create a new level; various things like bestowing a guardian - angel on Astral or setting off alarm on Ft.Ludios are handled - by goto_level(do.c) so won't occur for replacement levels */ - mklev(); - + tmpnhfp = get_freeing_nhfile(); + savelev(tmpnhfp, ledger_no(&u.uz)); + close_nhfile(tmpnhfp); + } else { vision_reset(); - vision_full_recalc = 1; + gv.vision_full_recalc = 1; cls(); /* was using safe_teleds() but that doesn't honor arrival region on levels which have such; we don't force stairs, just area */ u_on_rndspot((u.uhave.amulet ? 1 : 0) /* 'going up' flag */ - | (was_in_W_tower ? 2 : 0)); + | (wiztower ? 2 : 0)); losedogs(); kill_genocided_monsters(); /* u_on_rndspot() might pick a spot that has a monster, or losedogs() @@ -878,2780 +1063,1674 @@ wiz_makemap(VOID_ARGS) #ifdef INSURANCE save_currentstate(); #endif - } else { - pline(unavailcmd, "#wizmakemap"); } - return 0; -} - -/* ^F command - reveal the level map and any traps on it */ -STATIC_PTR int -wiz_map(VOID_ARGS) -{ - if (wizard) { - struct trap *t; - long save_Hconf = HConfusion, save_Hhallu = HHallucination; - - HConfusion = HHallucination = 0L; - for (t = ftrap; t != 0; t = t->ntrap) { - t->tseen = 1; - map_trap(t, TRUE); - } - do_mapping(); - HConfusion = save_Hconf; - HHallucination = save_Hhallu; - } else - pline(unavailcmd, visctrl((int) cmd_from_func(wiz_map))); - return 0; } -/* ^G command - generate monster(s); a count prefix will be honored */ -STATIC_PTR int -wiz_genesis(VOID_ARGS) -{ - if (wizard) - (void) create_particular(); - else - pline(unavailcmd, visctrl((int) cmd_from_func(wiz_genesis))); - return 0; -} - -/* ^O command - display dungeon layout */ -STATIC_PTR int -wiz_where(VOID_ARGS) -{ - if (wizard) - (void) print_dungeon(FALSE, (schar *) 0, (xchar *) 0); - else - pline(unavailcmd, visctrl((int) cmd_from_func(wiz_where))); - return 0; -} - -/* ^E command - detect unseen (secret doors, traps, hidden monsters) */ -STATIC_PTR int -wiz_detect(VOID_ARGS) -{ - if (wizard) - (void) findit(); - else - pline(unavailcmd, visctrl((int) cmd_from_func(wiz_detect))); - return 0; -} +/* temporary? hack, since level type codes aren't the same as screen + symbols and only the latter have easily accessible descriptions. + Also used by wizcmds.c */ +const char *levltyp[MAX_TYPE + 2] = { + "stone", "vertical wall", "horizontal wall", "top-left corner wall", + "top-right corner wall", "bottom-left corner wall", + "bottom-right corner wall", "cross wall", "tee-up wall", "tee-down wall", + "tee-left wall", "tee-right wall", "drawbridge wall", "tree", + "secret door", "secret corridor", "pool", "moat", "water", + "drawbridge up", "lava pool", "lava wall", "iron bars", "door", + "corridor", "room", "stairs", "ladder", "fountain", "throne", "sink", + "grave", "altar", "ice", "drawbridge down", "air", "cloud", + /* not a real terrain type, but used for undiggable stone + by wiz_map_levltyp() */ + "unreachable/undiggable", + /* padding in case the number of entries above is odd */ + "" +}; -/* ^V command - level teleport */ -STATIC_PTR int -wiz_level_tele(VOID_ARGS) +const char * +levltyp_to_name(int typ) { - if (wizard) - level_tele(); - else - pline(unavailcmd, visctrl((int) cmd_from_func(wiz_level_tele))); - return 0; + if (typ >= 0 && typ < MAX_TYPE) + return levltyp[typ]; + return NULL; } -/* #levelchange command - adjust hero's experience level */ -STATIC_PTR int -wiz_level_change(VOID_ARGS) +/* #terrain command -- show known map, inspired by crawl's '|' command */ +staticfn int +doterrain(void) { - char buf[BUFSZ] = DUMMY; - int newlevel = 0; - int ret; + winid men; + menu_item *sel; + anything any; + int n; + int which; + int clr = NO_COLOR; - getlin("To what experience level do you want to be set?", buf); - (void) mungspaces(buf); - if (buf[0] == '\033' || buf[0] == '\0') - ret = 0; - else - ret = sscanf(buf, "%d", &newlevel); + /* this used to be done each time vision was recalculated, so would + always be up to date (hopefully); now we do it on demand instead */ + recalc_mapseen(); - if (ret != 1) { - pline1(Never_mind); - return 0; - } - if (newlevel == u.ulevel) { - You("are already that experienced."); - } else if (newlevel < u.ulevel) { - if (u.ulevel == 1) { - You("are already as inexperienced as you can get."); - return 0; - } - if (newlevel < 1) - newlevel = 1; - while (u.ulevel > newlevel) - losexp("#levelchange"); - } else { - if (u.ulevel >= MAXULEV) { - You("are already as experienced as you can get."); - return 0; + /* + * normal play: choose between known map without mons, obj, and traps + * (to see underlying terrain only), or + * known map without mons and objs (to see traps under mons and objs), or + * known map without mons (to see objects under monsters); + * explore mode: normal choices plus full map (w/o mons, objs, traps); + * wizard mode: normal and explore choices plus + * a dump of the internal levl[][].typ codes w/ level flags, or + * a legend for the levl[][].typ codes dump + */ + men = create_nhwindow(NHW_MENU); + start_menu(men, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + any.a_int = 1; + add_menu(men, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr, + "known map without monsters, objects, and traps", + MENU_ITEMFLAGS_SELECTED); + any.a_int = 2; + add_menu(men, &nul_glyphinfo, &any, 0, 0, ATR_NONE, + clr, "known map without monsters and objects", + MENU_ITEMFLAGS_NONE); + any.a_int = 3; + add_menu(men, &nul_glyphinfo, &any, 0, 0, ATR_NONE, + clr, "known map without monsters", + MENU_ITEMFLAGS_NONE); + if (discover || wizard) { + any.a_int = 4; + add_menu(men, &nul_glyphinfo, &any, 0, 0, ATR_NONE, + clr, "full map without monsters, objects, and traps", + MENU_ITEMFLAGS_NONE); + if (wizard) { + any.a_int = 5; + add_menu(men, &nul_glyphinfo, &any, 0, 0, ATR_NONE, + clr, "internal levl[][].typ codes in base-36", + MENU_ITEMFLAGS_NONE); + any.a_int = 6; + add_menu(men, &nul_glyphinfo, &any, 0, 0, ATR_NONE, + clr, "legend of base-36 levl[][].typ codes", + MENU_ITEMFLAGS_NONE); } - if (newlevel > MAXULEV) - newlevel = MAXULEV; - while (u.ulevel < newlevel) - pluslvl(FALSE); } - u.ulevelmax = u.ulevel; - return 0; -} + end_menu(men, "View which?"); -/* #panic command - test program's panic handling */ -STATIC_PTR int -wiz_panic(VOID_ARGS) + n = select_menu(men, PICK_ONE, &sel); + destroy_nhwindow(men); + /* + * n < 0: player used ESC to cancel; + * n == 0: preselected entry was explicitly chosen and got toggled off; + * n == 1: preselected entry was implicitly chosen via |; + * n == 2: another entry was explicitly chosen, so skip preselected one. + */ + which = (n < 0) ? -1 : (n == 0) ? 1 : sel[0].item.a_int; + if (n > 1 && which == 1) + which = sel[1].item.a_int; + if (n > 0) + free((genericptr_t) sel); + + switch (which) { + case 1: /* known map */ + reveal_terrain(TER_MAP); + break; + case 2: /* known map with known traps */ + reveal_terrain(TER_MAP | TER_TRP); + break; + case 3: /* known map with known traps and objects */ + reveal_terrain(TER_MAP | TER_TRP | TER_OBJ); + break; + case 4: /* full map */ + reveal_terrain(TER_MAP | TER_FULL); + break; + case 5: /* map internals */ + wiz_map_levltyp(); + break; + case 6: /* internal details */ + wiz_levltyp_legend(); + break; + default: + break; + } + return ECMD_OK; /* no time elapses */ +} + +/* has hero seen all locations in selection? */ +staticfn boolean +u_have_seen_whole_selection(struct selectionvar *sel) { - if (iflags.debug_fuzzer) { - u.uhp = u.uhpmax = 1000; - u.uen = u.uenmax = 1000; - return 0; + coordxy x, y; + NhRect rect = cg.zeroNhRect; + + selection_getbounds(sel, &rect); + + for (x = rect.lx; x <= rect.hx; x++) + for (y = rect.ly; y <= rect.hy; y++) + if (isok(x,y) && selection_getpoint(x, y, sel) + && glyph_at(x, y) == GLYPH_UNEXPLORED) + return FALSE; + + return TRUE; +} + +/* has hero seen all location of the rectangular outline in the selection */ +staticfn boolean +u_have_seen_bounds_selection(struct selectionvar *sel) +{ + coordxy x, y; + NhRect rect = cg.zeroNhRect; + + selection_getbounds(sel, &rect); + + for (x = rect.lx; x <= rect.hx; x++) { + y = rect.ly; + if (isok(x,y) && selection_getpoint(x, y, sel) + && glyph_at(x, y) == GLYPH_UNEXPLORED) + return FALSE; + y = rect.hy; + if (isok(x,y) && selection_getpoint(x, y, sel) + && glyph_at(x, y) == GLYPH_UNEXPLORED) + return FALSE; + } + for (y = rect.ly; y <= rect.hy; y++) { + x = rect.lx; + if (isok(x,y) && selection_getpoint(x, y, sel) + && glyph_at(x, y) == GLYPH_UNEXPLORED) + return FALSE; + x = rect.hx; + if (isok(x,y) && selection_getpoint(x, y, sel) + && glyph_at(x, y) == GLYPH_UNEXPLORED) + return FALSE; } - if (paranoid_query(ParanoidQuit, - "Do you want to call panic() and end your game?")) - panic("Crash test."); - return 0; + + return TRUE; } -/* #polyself command - change hero's form */ -STATIC_PTR int -wiz_polyself(VOID_ARGS) +/* can hero currently see all locations in the selection */ +staticfn boolean +u_can_see_whole_selection(struct selectionvar *sel) { - polyself(1); - return 0; + coordxy x, y; + NhRect rect = cg.zeroNhRect; + + selection_getbounds(sel, &rect); + + for (x = rect.lx; x <= rect.hx; x++) + for (y = rect.ly; y <= rect.hy; y++) + if (isok(x,y) && selection_getpoint(x, y, sel) && !cansee(x, y)) + return FALSE; + + return TRUE; } -/* #seenv command */ -STATIC_PTR int -wiz_show_seenv(VOID_ARGS) +/* selection_floofill callback to get all locations in a room */ +staticfn int +dolookaround_floodfill_findroom(coordxy x, coordxy y) { - winid win; - int x, y, v, startx, stopx, curx; - char row[COLNO + 1]; + schar typ = levl[x][y].typ; - win = create_nhwindow(NHW_TEXT); - /* - * Each seenv description takes up 2 characters, so center - * the seenv display around the hero. - */ - startx = max(1, u.ux - (COLNO / 4)); - stopx = min(startx + (COLNO / 2), COLNO); - /* can't have a line exactly 80 chars long */ - if (stopx - startx == COLNO / 2) - startx++; - - for (y = 0; y < ROWNO; y++) { - for (x = startx, curx = 0; x < stopx; x++, curx += 2) { - if (x == u.ux && y == u.uy) { - row[curx] = row[curx + 1] = '@'; - } else { - v = levl[x][y].seenv & 0xff; - if (v == 0) - row[curx] = row[curx + 1] = ' '; - else - Sprintf(&row[curx], "%02x", v); - } - } - /* remove trailing spaces */ - for (x = curx - 1; x >= 0; x--) - if (row[x] != ' ') - break; - row[x + 1] = '\0'; + if (IS_STWALL(typ) || IS_DOOR(typ) || IS_TREE(typ) + || IS_WATERWALL(typ) || typ == LAVAWALL || typ == IRONBARS + || typ == SCORR || typ == SDOOR || typ == DRAWBRIDGE_UP) + return FALSE; + return TRUE; +} + +/* describe the room at x,y */ +staticfn void +lookaround_known_room(coordxy x, coordxy y) +{ + struct selectionvar *sel = selection_new(); + int rmno = u.urooms[0] - ROOMOFFSET; + char qbuf[QBUFSZ]; - putstr(win, 0, row); + set_selection_floodfillchk(dolookaround_floodfill_findroom); + selection_floodfill(sel, x, y, TRUE); + + if (!u_at(x, y)) + set_msg_xy(x, y); + + if (u_have_seen_whole_selection(sel)) { + boolean u_in = (boolean) selection_getpoint(x, y, sel); + + You("%s %s %s.", + u_at(x, y) && u_in && u_can_see_whole_selection(sel) ? "are in" + : (u_at(x, y)) ? "remember this as" : "remember that as", + an(selection_size_description(sel, qbuf)), + rmno >= 0 ? "room" : "area"); + } else if (u_have_seen_bounds_selection(sel)) { + You("guess %s to be %s %s.", + u_at(x, y) ? "this" : "that", + an(selection_size_description(sel, qbuf)), + rmno >= 0 ? "room" : "area"); + } else { + You("can't guess the size of %s area.", + u_at(x, y) ? "this" : "that"); } - display_nhwindow(win, TRUE); - destroy_nhwindow(win); - return 0; + selection_free(sel, TRUE); } -/* #vision command */ -STATIC_PTR int -wiz_show_vision(VOID_ARGS) +/* #lookaround - describe what the hero can see, in text */ +int +dolookaround(void) { - winid win; - int x, y, v; - char row[COLNO + 1]; + coordxy x, y; + int tmp_getloc_filter = iflags.getloc_filter; + boolean tmp_accessiblemsg = a11y.accessiblemsg; + boolean corr_next2u = FALSE; + + a11y.accessiblemsg = TRUE; + if (levl[u.ux][u.uy].typ == CORR) { + /* In a corridor, mention corridors next to you. */ + corr_next2u = TRUE; + /* TODO: if we know, describe where the corridor goes, + perhaps by describing the rooms? */ + } else if (IS_DOOR(levl[u.ux][u.uy].typ)) { + /* In a doorway, describe the rooms next to you */ + int i; - win = create_nhwindow(NHW_TEXT); - Sprintf(row, "Flags: 0x%x could see, 0x%x in sight, 0x%x temp lit", - COULD_SEE, IN_SIGHT, TEMP_LIT); - putstr(win, 0, row); - putstr(win, 0, ""); - for (y = 0; y < ROWNO; y++) { + for (i = DIR_W; i < N_DIRS; i += 2) { + x = u.ux + xdir[i]; + y = u.uy + ydir[i]; + if (isok(x, y) && IS_ROOM(levl[x][y].typ)) + lookaround_known_room(x, y); + } + corr_next2u = TRUE; + } else { + lookaround_known_room(u.ux, u.uy); + } + + /* TODO: maybe describe stuff outside the current room differently? */ + + iflags.getloc_filter = GFILTER_VIEW; + for (y = 0; y < ROWNO; y++) for (x = 1; x < COLNO; x++) { - if (x == u.ux && y == u.uy) - row[x] = '@'; - else { - v = viz_array[y][x]; /* data access should be hidden */ - if (v == 0) - row[x] = ' '; - else - row[x] = '0' + viz_array[y][x]; + int glyph, mapsym; + boolean iscorr = (corr_next2u + && (glyph = glyph_at(x, y)) >= 0 + && glyph_is_cmap(glyph) + && ((mapsym = glyph_to_cmap(glyph)) == S_corr + || mapsym == S_litcorr)); + + if (!u_at(x, y) + && (gather_locs_interesting(x, y, GLOC_INTERESTING) + /* note: GLOC_INTERESTING catches S_engrcorr */ + || iscorr)) { + char buf[BUFSZ]; + coord cc; + int sym = 0; + const char *firstmatch = 0; + + cc.x = x, cc.y = y; + do_screen_description(cc, TRUE, sym, buf, &firstmatch, NULL); + pline_xy(x, y, "%s.", firstmatch); } } - /* remove trailing spaces */ - for (x = COLNO - 1; x >= 1; x--) - if (row[x] != ' ') - break; - row[x + 1] = '\0'; - putstr(win, 0, &row[1]); - } - display_nhwindow(win, TRUE); - destroy_nhwindow(win); - return 0; + iflags.getloc_filter = tmp_getloc_filter; + a11y.accessiblemsg = tmp_accessiblemsg; + + return ECMD_OK; } -/* #wmode command */ -STATIC_PTR int -wiz_show_wmodes(VOID_ARGS) +/* #toggle extended command + + BIND=':toggle(price_quotes) + BIND=@:toggle(autopickup) */ +int +dotoggleoption(void) { - winid win; - int x, y; - char row[COLNO + 1]; - struct rm *lev; - boolean istty = WINDOWPORT("tty"); + if (gc.cmd_bind && gc.cmd_bind->param) { + return toggle_bool_option(gc.cmd_bind->param); + } else { + pline("Use #optionsfull to set any option instead."); + return ECMD_OK; + } +} - win = create_nhwindow(NHW_TEXT); - if (istty) - putstr(win, 0, ""); /* tty only: blank top line */ - for (y = 0; y < ROWNO; y++) { - for (x = 0; x < COLNO; x++) { - lev = &levl[x][y]; - if (x == u.ux && y == u.uy) - row[x] = '@'; - else if (IS_WALL(lev->typ) || lev->typ == SDOOR) - row[x] = '0' + (lev->wall_info & WM_MASK); - else if (lev->typ == CORR) - row[x] = '#'; - else if (IS_ROOM(lev->typ) || IS_DOOR(lev->typ)) - row[x] = '.'; - else - row[x] = 'x'; - } - row[COLNO] = '\0'; - /* map column 0, levl[0][], is off the left edge of the screen */ - putstr(win, 0, &row[1]); +void +set_move_cmd(int dir, int run) +{ + u.dz = zdir[dir]; + u.dx = xdir[dir]; + u.dy = ydir[dir]; + /* #reqmenu -prefix disables autopickup during movement */ + if (iflags.menu_requested) + svc.context.nopick = 1; + svc.context.travel = svc.context.travel1 = 0; + if (!gd.domove_attempting && !u.dz) { + svc.context.run = run; + gd.domove_attempting |= (!run ? DOMOVE_WALK : DOMOVE_RUSH); } - display_nhwindow(win, TRUE); - destroy_nhwindow(win); - return 0; } -/* wizard mode variant of #terrain; internal levl[][].typ values in base-36 */ -STATIC_OVL void -wiz_map_levltyp(VOID_ARGS) +/* move or attack */ +int +do_move_west(void) { - winid win; - int x, y, terrain; - char row[COLNO + 1]; - boolean istty = !strcmp(windowprocs.name, "tty"); + set_move_cmd(DIR_W, 0); + return ECMD_TIME; +} - win = create_nhwindow(NHW_TEXT); - /* map row 0, levl[][0], is drawn on the second line of tty screen */ - if (istty) - putstr(win, 0, ""); /* tty only: blank top line */ - for (y = 0; y < ROWNO; y++) { - /* map column 0, levl[0][], is off the left edge of the screen; - it should always have terrain type "undiggable stone" */ - for (x = 1; x < COLNO; x++) { - terrain = levl[x][y].typ; - /* assumes there aren't more than 10+26+26 terrain types */ - row[x - 1] = (char) ((terrain == STONE && !may_dig(x, y)) - ? '*' - : (terrain < 10) - ? '0' + terrain - : (terrain < 36) - ? 'a' + terrain - 10 - : 'A' + terrain - 36); - } - x--; - if (levl[0][y].typ != STONE || may_dig(0, y)) - row[x++] = '!'; - row[x] = '\0'; - putstr(win, 0, row); - } +int +do_move_northwest(void) +{ + set_move_cmd(DIR_NW, 0); + return ECMD_TIME; +} - { - char dsc[BUFSZ]; - s_level *slev = Is_special(&u.uz); - - Sprintf(dsc, "D:%d,L:%d", u.uz.dnum, u.uz.dlevel); - /* [dungeon branch features currently omitted] */ - /* special level features */ - if (slev) { - Sprintf(eos(dsc), " \"%s\"", slev->proto); - /* special level flags (note: dungeon.def doesn't set `maze' - or `hell' for any specific levels so those never show up) */ - if (slev->flags.maze_like) - Strcat(dsc, " mazelike"); - if (slev->flags.hellish) - Strcat(dsc, " hellish"); - if (slev->flags.town) - Strcat(dsc, " town"); - if (slev->flags.rogue_like) - Strcat(dsc, " roguelike"); - /* alignment currently omitted to save space */ - } - /* level features */ - if (level.flags.nfountains) - Sprintf(eos(dsc), " %c:%d", defsyms[S_fountain].sym, - (int) level.flags.nfountains); - if (level.flags.nsinks) - Sprintf(eos(dsc), " %c:%d", defsyms[S_sink].sym, - (int) level.flags.nsinks); - if (level.flags.has_vault) - Strcat(dsc, " vault"); - if (level.flags.has_shop) - Strcat(dsc, " shop"); - if (level.flags.has_temple) - Strcat(dsc, " temple"); - if (level.flags.has_court) - Strcat(dsc, " throne"); - if (level.flags.has_zoo) - Strcat(dsc, " zoo"); - if (level.flags.has_morgue) - Strcat(dsc, " morgue"); - if (level.flags.has_barracks) - Strcat(dsc, " barracks"); - if (level.flags.has_beehive) - Strcat(dsc, " hive"); - if (level.flags.has_swamp) - Strcat(dsc, " swamp"); - /* level flags */ - if (level.flags.noteleport) - Strcat(dsc, " noTport"); - if (level.flags.hardfloor) - Strcat(dsc, " noDig"); - if (level.flags.nommap) - Strcat(dsc, " noMMap"); - if (!level.flags.hero_memory) - Strcat(dsc, " noMem"); - if (level.flags.shortsighted) - Strcat(dsc, " shortsight"); - if (level.flags.graveyard) - Strcat(dsc, " graveyard"); - if (level.flags.is_maze_lev) - Strcat(dsc, " maze"); - if (level.flags.is_cavernous_lev) - Strcat(dsc, " cave"); - if (level.flags.arboreal) - Strcat(dsc, " tree"); - if (Sokoban) - Strcat(dsc, " sokoban-rules"); - /* non-flag info; probably should include dungeon branching - checks (extra stairs and magic portals) here */ - if (Invocation_lev(&u.uz)) - Strcat(dsc, " invoke"); - if (On_W_tower_level(&u.uz)) - Strcat(dsc, " tower"); - /* append a branch identifier for completeness' sake */ - if (u.uz.dnum == 0) - Strcat(dsc, " dungeon"); - else if (u.uz.dnum == mines_dnum) - Strcat(dsc, " mines"); - else if (In_sokoban(&u.uz)) - Strcat(dsc, " sokoban"); - else if (u.uz.dnum == quest_dnum) - Strcat(dsc, " quest"); - else if (Is_knox(&u.uz)) - Strcat(dsc, " ludios"); - else if (u.uz.dnum == 1) - Strcat(dsc, " gehennom"); - else if (u.uz.dnum == tower_dnum) - Strcat(dsc, " vlad"); - else if (In_endgame(&u.uz)) - Strcat(dsc, " endgame"); - else { - /* somebody's added a dungeon branch we're not expecting */ - const char *brname = dungeons[u.uz.dnum].dname; - - if (!brname || !*brname) - brname = "unknown"; - if (!strncmpi(brname, "the ", 4)) - brname += 4; - Sprintf(eos(dsc), " %s", brname); - } - /* limit the line length to map width */ - if (strlen(dsc) >= COLNO) - dsc[COLNO - 1] = '\0'; /* truncate */ - putstr(win, 0, dsc); - } +int +do_move_north(void) +{ + set_move_cmd(DIR_N, 0); + return ECMD_TIME; +} - display_nhwindow(win, TRUE); - destroy_nhwindow(win); - return; +int +do_move_northeast(void) +{ + set_move_cmd(DIR_NE, 0); + return ECMD_TIME; } -/* temporary? hack, since level type codes aren't the same as screen - symbols and only the latter have easily accessible descriptions */ -static const char *levltyp[] = { - "stone", "vertical wall", "horizontal wall", "top-left corner wall", - "top-right corner wall", "bottom-left corner wall", - "bottom-right corner wall", "cross wall", "tee-up wall", "tee-down wall", - "tee-left wall", "tee-right wall", "drawbridge wall", "tree", - "secret door", "secret corridor", "pool", "moat", "water", - "drawbridge up", "lava pool", "iron bars", "door", "corridor", "room", - "stairs", "ladder", "fountain", "throne", "sink", "grave", "altar", "ice", - "drawbridge down", "air", "cloud", - /* not a real terrain type, but used for undiggable stone - by wiz_map_levltyp() */ - "unreachable/undiggable", - /* padding in case the number of entries above is odd */ - "" -}; +int +do_move_east(void) +{ + set_move_cmd(DIR_E, 0); + return ECMD_TIME; +} -/* explanation of base-36 output from wiz_map_levltyp() */ -STATIC_OVL void -wiz_levltyp_legend(VOID_ARGS) +int +do_move_southeast(void) { - winid win; - int i, j, last, c; - const char *dsc, *fmt; - char buf[BUFSZ]; + set_move_cmd(DIR_SE, 0); + return ECMD_TIME; +} - win = create_nhwindow(NHW_TEXT); - putstr(win, 0, "#terrain encodings:"); - putstr(win, 0, ""); - fmt = " %c - %-28s"; /* TODO: include tab-separated variant for win32 */ - *buf = '\0'; - /* output in pairs, left hand column holds [0],[1],...,[N/2-1] - and right hand column holds [N/2],[N/2+1],...,[N-1]; - N ('last') will always be even, and may or may not include - the empty string entry to pad out the final pair, depending - upon how many other entries are present in levltyp[] */ - last = SIZE(levltyp) & ~1; - for (i = 0; i < last / 2; ++i) - for (j = i; j < last; j += last / 2) { - dsc = levltyp[j]; - c = !*dsc ? ' ' - : !strncmp(dsc, "unreachable", 11) ? '*' - /* same int-to-char conversion as wiz_map_levltyp() */ - : (j < 10) ? '0' + j - : (j < 36) ? 'a' + j - 10 - : 'A' + j - 36; - Sprintf(eos(buf), fmt, c, dsc); - if (j > i) { - putstr(win, 0, buf); - *buf = '\0'; - } - } - display_nhwindow(win, TRUE); - destroy_nhwindow(win); - return; +int +do_move_south(void) +{ + set_move_cmd(DIR_S, 0); + return ECMD_TIME; } -/* #wizsmell command - test usmellmon(). */ -STATIC_PTR int -wiz_smell(VOID_ARGS) +int +do_move_southwest(void) { - int ans = 0; - int mndx; /* monster index */ - coord cc; /* screen pos of unknown glyph */ - int glyph; /* glyph at selected position */ + set_move_cmd(DIR_SW, 0); + return ECMD_TIME; +} - cc.x = u.ux; - cc.y = u.uy; - mndx = 0; /* gcc -Wall lint */ - if (!olfaction(youmonst.data)) { - You("are incapable of detecting odors in your present form."); - return 0; - } +/* rush */ +int +do_rush_west(void) +{ + set_move_cmd(DIR_W, 3); + return ECMD_TIME; +} - pline("You can move the cursor to a monster that you want to smell."); - do { - pline("Pick a monster to smell."); - ans = getpos(&cc, TRUE, "a monster"); - if (ans < 0 || cc.x < 0) { - return 0; /* done */ - } - /* Convert the glyph at the selected position to a mndxbol. */ - glyph = glyph_at(cc.x, cc.y); - if (glyph_is_monster(glyph)) - mndx = glyph_to_mon(glyph); - else - mndx = 0; - /* Is it a monster? */ - if (mndx) { - if (!usmellmon(&mons[mndx])) - pline("That monster seems to give off no smell."); - } else - pline("That is not a monster."); - } while (TRUE); - return 0; +int +do_rush_northwest(void) +{ + set_move_cmd(DIR_NW, 3); + return ECMD_TIME; } -/* #wizinstrinsic command to set some intrinsics for testing */ -STATIC_PTR int -wiz_intrinsic(VOID_ARGS) -{ - if (wizard) { - extern const struct propname { - int prop_num; - const char *prop_name; - } propertynames[]; /* timeout.c */ - static const char wizintrinsic[] = "#wizintrinsic"; - static const char fmt[] = "You are%s %s."; - winid win; - anything any; - char buf[BUFSZ]; - int i, j, n, p, amt, typ; - long oldtimeout, newtimeout; - const char *propname; - menu_item *pick_list = (menu_item *) 0; - - any = zeroany; - win = create_nhwindow(NHW_MENU); - start_menu(win); - for (i = 0; (propname = propertynames[i].prop_name) != 0; ++i) { - p = propertynames[i].prop_num; - if (p == HALLUC_RES) { - /* Grayswandir vs hallucination; ought to be redone to - use u.uprops[HALLUC].blocked instead of being treated - as a separate property; letting in be manually toggled - even only in wizard mode would be asking for trouble... */ - continue; - } - if (p == FIRE_RES) { - any.a_int = 0; - add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, "--", FALSE); - } - any.a_int = i + 1; /* +1: avoid 0 */ - oldtimeout = u.uprops[p].intrinsic & TIMEOUT; - if (oldtimeout) - Sprintf(buf, "%-27s [%li]", propname, oldtimeout); - else - Sprintf(buf, "%s", propname); - add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE); - } - end_menu(win, "Which intrinsics?"); - n = select_menu(win, PICK_ANY, &pick_list); - destroy_nhwindow(win); +int +do_rush_north(void) +{ + set_move_cmd(DIR_N, 3); + return ECMD_TIME; +} - amt = 30; /* TODO: prompt for duration */ - for (j = 0; j < n; ++j) { - i = pick_list[j].item.a_int - 1; /* -1: reverse +1 above */ - p = propertynames[i].prop_num; - oldtimeout = u.uprops[p].intrinsic & TIMEOUT; - newtimeout = oldtimeout + (long) amt; - switch (p) { - case SICK: - case SLIMED: - case STONED: - if (oldtimeout > 0L && newtimeout > oldtimeout) - newtimeout = oldtimeout; - break; - } +int +do_rush_northeast(void) +{ + set_move_cmd(DIR_NE, 3); + return ECMD_TIME; +} - switch (p) { - case BLINDED: - make_blinded(newtimeout, TRUE); - break; -#if 0 /* make_confused() only gives feedback when confusion is - * ending so use the 'default' case for it instead */ - case CONFUSION: - make_confused(newtimeout, TRUE); - break; -#endif /*0*/ - case DEAF: - make_deaf(newtimeout, TRUE); - break; - case HALLUC: - make_hallucinated(newtimeout, TRUE, 0L); - break; - case SICK: - typ = !rn2(2) ? SICK_VOMITABLE : SICK_NONVOMITABLE; - make_sick(newtimeout, wizintrinsic, TRUE, typ); - break; - case SLIMED: - Sprintf(buf, fmt, - !Slimed ? "" : " still", "turning into slime"); - make_slimed(newtimeout, buf); - break; - case STONED: - Sprintf(buf, fmt, - !Stoned ? "" : " still", "turning into stone"); - make_stoned(newtimeout, buf, KILLED_BY, wizintrinsic); - break; - case STUNNED: - make_stunned(newtimeout, TRUE); - break; - case VOMITING: - Sprintf(buf, fmt, !Vomiting ? "" : " still", "vomiting"); - make_vomiting(newtimeout, FALSE); - pline1(buf); - break; - case WARN_OF_MON: - if (!Warn_of_mon) { - context.warntype.speciesidx = PM_GRID_BUG; - context.warntype.species - = &mons[context.warntype.speciesidx]; - } - goto def_feedback; - case GLIB: - /* slippery fingers applies to gloves if worn at the time - so persistent inventory might need updating */ - make_glib((int) newtimeout); - goto def_feedback; - case LEVITATION: - case FLYING: - float_vs_flight(); - /*FALLTHRU*/ - default: - def_feedback: - pline("Timeout for %s %s %d.", propertynames[i].prop_name, - oldtimeout ? "increased by" : "set to", amt); - if (p != GLIB) - incr_itimeout(&u.uprops[p].intrinsic, amt); - break; - } - context.botl = 1; /* probably not necessary... */ - } - if (n >= 1) - free((genericptr_t) pick_list); - doredraw(); - } else - pline(unavailcmd, visctrl((int) cmd_from_func(wiz_intrinsic))); - return 0; +int +do_rush_east(void) +{ + set_move_cmd(DIR_E, 3); + return ECMD_TIME; } -/* #wizrumorcheck command - verify each rumor access */ -STATIC_PTR int -wiz_rumor_check(VOID_ARGS) +int +do_rush_southeast(void) { - rumor_check(); - return 0; + set_move_cmd(DIR_SE, 3); + return ECMD_TIME; } -/* #terrain command -- show known map, inspired by crawl's '|' command */ -STATIC_PTR int -doterrain(VOID_ARGS) +int +do_rush_south(void) { - winid men; - menu_item *sel; - anything any; - int n; - int which; + set_move_cmd(DIR_S, 3); + return ECMD_TIME; +} - /* - * normal play: choose between known map without mons, obj, and traps - * (to see underlying terrain only), or - * known map without mons and objs (to see traps under mons and objs), or - * known map without mons (to see objects under monsters); - * explore mode: normal choices plus full map (w/o mons, objs, traps); - * wizard mode: normal and explore choices plus - * a dump of the internal levl[][].typ codes w/ level flags, or - * a legend for the levl[][].typ codes dump - */ - men = create_nhwindow(NHW_MENU); - start_menu(men); - any = zeroany; - any.a_int = 1; - add_menu(men, NO_GLYPH, &any, 0, 0, ATR_NONE, - "known map without monsters, objects, and traps", - MENU_SELECTED); - any.a_int = 2; - add_menu(men, NO_GLYPH, &any, 0, 0, ATR_NONE, - "known map without monsters and objects", - MENU_UNSELECTED); - any.a_int = 3; - add_menu(men, NO_GLYPH, &any, 0, 0, ATR_NONE, - "known map without monsters", - MENU_UNSELECTED); - if (discover || wizard) { - any.a_int = 4; - add_menu(men, NO_GLYPH, &any, 0, 0, ATR_NONE, - "full map without monsters, objects, and traps", - MENU_UNSELECTED); - if (wizard) { - any.a_int = 5; - add_menu(men, NO_GLYPH, &any, 0, 0, ATR_NONE, - "internal levl[][].typ codes in base-36", - MENU_UNSELECTED); - any.a_int = 6; - add_menu(men, NO_GLYPH, &any, 0, 0, ATR_NONE, - "legend of base-36 levl[][].typ codes", - MENU_UNSELECTED); - } - } - end_menu(men, "View which?"); +int +do_rush_southwest(void) +{ + set_move_cmd(DIR_SW, 3); + return ECMD_TIME; +} - n = select_menu(men, PICK_ONE, &sel); - destroy_nhwindow(men); - /* - * n < 0: player used ESC to cancel; - * n == 0: preselected entry was explicitly chosen and got toggled off; - * n == 1: preselected entry was implicitly chosen via |; - * n == 2: another entry was explicitly chosen, so skip preselected one. - */ - which = (n < 0) ? -1 : (n == 0) ? 1 : sel[0].item.a_int; - if (n > 1 && which == 1) - which = sel[1].item.a_int; - if (n > 0) - free((genericptr_t) sel); +/* run */ +int +do_run_west(void) +{ + set_move_cmd(DIR_W, 1); + return ECMD_TIME; +} - switch (which) { - case 1: /* known map */ - reveal_terrain(0, TER_MAP); - break; - case 2: /* known map with known traps */ - reveal_terrain(0, TER_MAP | TER_TRP); - break; - case 3: /* known map with known traps and objects */ - reveal_terrain(0, TER_MAP | TER_TRP | TER_OBJ); - break; - case 4: /* full map */ - reveal_terrain(1, TER_MAP); - break; - case 5: /* map internals */ - wiz_map_levltyp(); - break; - case 6: /* internal details */ - wiz_levltyp_legend(); - break; - default: - break; +int +do_run_northwest(void) +{ + set_move_cmd(DIR_NW, 1); + return ECMD_TIME; +} + +int +do_run_north(void) +{ + set_move_cmd(DIR_N, 1); + return ECMD_TIME; +} + +int +do_run_northeast(void) +{ + set_move_cmd(DIR_NE, 1); + return ECMD_TIME; +} + +int +do_run_east(void) +{ + set_move_cmd(DIR_E, 1); + return ECMD_TIME; +} + +int +do_run_southeast(void) +{ + set_move_cmd(DIR_SE, 1); + return ECMD_TIME; +} + +int +do_run_south(void) +{ + set_move_cmd(DIR_S, 1); + return ECMD_TIME; +} + +int +do_run_southwest(void) +{ + set_move_cmd(DIR_SW, 1); + return ECMD_TIME; +} + +/* #reqmenu, prefix command to modify some others */ +int +do_reqmenu(void) +{ + if (iflags.menu_requested) { + Norep("Double %s prefix, canceled.", + visctrl(cmd_from_func(do_reqmenu))); + iflags.menu_requested = FALSE; + return ECMD_CANCEL; } - return 0; /* no time elapses */ -} - -/* -enlightenment and conduct- */ -static winid en_win = WIN_ERR; -static boolean en_via_menu = FALSE; -static const char You_[] = "You ", are[] = "are ", were[] = "were ", - have[] = "have ", had[] = "had ", can[] = "can ", - could[] = "could "; -static const char have_been[] = "have been ", have_never[] = "have never ", - never[] = "never "; - -#define enl_msg(prefix, present, past, suffix, ps) \ - enlght_line(prefix, final ? past : present, suffix, ps) -#define you_are(attr, ps) enl_msg(You_, are, were, attr, ps) -#define you_have(attr, ps) enl_msg(You_, have, had, attr, ps) -#define you_can(attr, ps) enl_msg(You_, can, could, attr, ps) -#define you_have_been(goodthing) enl_msg(You_, have_been, were, goodthing, "") -#define you_have_never(badthing) \ - enl_msg(You_, have_never, never, badthing, "") -#define you_have_X(something) \ - enl_msg(You_, have, (const char *) "", something, "") - -static void -enlght_out(buf) -const char *buf; -{ - if (en_via_menu) { - anything any; - - any = zeroany; - add_menu(en_win, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE); - } else - putstr(en_win, 0, buf); + + iflags.menu_requested = TRUE; + return ECMD_OK; } -static void -enlght_line(start, middle, end, ps) -const char *start, *middle, *end, *ps; +/* #rush */ +int +do_rush(void) { - char buf[BUFSZ]; + if ((gd.domove_attempting & DOMOVE_RUSH)) { + Norep("Double rush prefix, canceled."); + svc.context.run = 0; + gd.domove_attempting = 0; + return ECMD_CANCEL; + } - Sprintf(buf, " %s%s%s%s.", start, middle, end, ps); - enlght_out(buf); -} - -/* format increased chance to hit or damage or defense (Protection) */ -static char * -enlght_combatinc(inctyp, incamt, final, outbuf) -const char *inctyp; -int incamt, final; -char *outbuf; -{ - const char *modif, *bonus; - boolean invrt; - int absamt; - - absamt = abs(incamt); - /* Protection amount is typically larger than damage or to-hit; - reduce magnitude by a third in order to stretch modifier ranges - (small:1..5, moderate:6..10, large:11..19, huge:20+) */ - if (!strcmp(inctyp, "defense")) - absamt = (absamt * 2) / 3; - - if (absamt <= 3) - modif = "small"; - else if (absamt <= 6) - modif = "moderate"; - else if (absamt <= 12) - modif = "large"; - else - modif = "huge"; + svc.context.run = 2; + gd.domove_attempting |= DOMOVE_RUSH; + return ECMD_OK; +} + +/* #run */ +int +do_run(void) +{ + if ((gd.domove_attempting & DOMOVE_RUSH)) { + Norep("Double run prefix, canceled."); + svc.context.run = 0; + gd.domove_attempting = 0; + return ECMD_CANCEL; + } - modif = !incamt ? "no" : an(modif); /* ("no" case shouldn't happen) */ - bonus = (incamt >= 0) ? "bonus" : "penalty"; - /* "bonus " (to hit) vs " bonus" (damage, defense) */ - invrt = strcmp(inctyp, "to hit") ? TRUE : FALSE; + svc.context.run = 3; + gd.domove_attempting |= DOMOVE_RUSH; + return ECMD_OK; +} - Sprintf(outbuf, "%s %s %s", modif, invrt ? inctyp : bonus, - invrt ? bonus : inctyp); - if (final || wizard) - Sprintf(eos(outbuf), " (%s%d)", (incamt > 0) ? "+" : "", incamt); +/* #fight */ +int +do_fight(void) +{ + if (svc.context.forcefight) { + Norep("Double fight prefix, canceled."); + svc.context.forcefight = 0; + gd.domove_attempting = 0; + return ECMD_CANCEL; + } - return outbuf; + svc.context.forcefight = 1; + gd.domove_attempting |= DOMOVE_WALK; + return ECMD_OK; } -/* report half physical or half spell damage */ -STATIC_OVL void -enlght_halfdmg(category, final) -int category; -int final; +/* #repeat */ +int +do_repeat(void) { - const char *category_name; - char buf[BUFSZ]; + int res = ECMD_OK; - switch (category) { - case HALF_PHDAM: - category_name = "physical"; - break; - case HALF_SPDAM: - category_name = "spell"; - break; - default: - category_name = "unknown"; - break; + if (!gi.in_doagain) { + struct _cmd_queue *repeat_copy; + + if (!cmdq_peek(CQ_REPEAT)) { + Norep("There is no command available to repeat."); + return ECMD_FAIL; + } + repeat_copy = cmdq_copy(CQ_REPEAT); + gi.in_doagain = TRUE; + rhack(0); /* read and execute command */ + gi.in_doagain = FALSE; + cmdq_clear(CQ_REPEAT); + gc.command_queue[CQ_REPEAT] = repeat_copy; + iflags.menu_requested = FALSE; + if (svc.context.move) + res = ECMD_TIME; } - Sprintf(buf, " %s %s damage", (final || wizard) ? "half" : "reduced", - category_name); - enl_msg(You_, "take", "took", buf, from_what(category)); + return res; +} + +/* extcmdlist: full command list, ordered by command name; + commands with no keystroke or with only a meta keystroke generally + need to be flagged as autocomplete and ones with a regular keystroke + or control keystroke generally should not be; there are a few exceptions + such as ^O/#overview and C/N/#name */ +struct ext_func_tab extcmdlist[] = { + { '#', "#", "enter and perform an extended command", + doextcmd, IFBURIED | GENERALCMD | CMD_M_PREFIX, NULL }, + { M('?'), "?", "list all extended commands", + doextlist, IFBURIED | AUTOCOMPLETE | GENERALCMD | CMD_M_PREFIX, + NULL }, + { M('a'), "adjust", "adjust inventory letters", + doorganize, IFBURIED | AUTOCOMPLETE | GENERALCMD, NULL }, + { M('A'), "annotate", "name current level", + donamelevel, IFBURIED | AUTOCOMPLETE | GENERALCMD | CMD_M_PREFIX, NULL }, + { 'a', "apply", "apply (use) a tool (pick-axe, key, lamp...)", + doapply, CMD_M_PREFIX, NULL }, + { C('x'), "attributes", "show your attributes", + doattributes, IFBURIED | GENERALCMD, NULL }, + { '@', "autopickup", "toggle the 'autopickup' option on/off", + dotogglepickup, IFBURIED | GENERALCMD, NULL }, +#ifdef CRASHREPORT + { '\0', "bugreport", "file a bug report", + dobugreport, GENERALCMD | NOFUZZERCMD, NULL }, +#endif + { 'C', "call", "name a monster, specific object, or type of object", + docallcmd, IFBURIED | GENERALCMD, NULL }, + { 'Z', "cast", "zap (cast) a spell", + docast, IFBURIED, NULL }, + { M('c'), "chat", "talk to someone", + dotalk, IFBURIED | AUTOCOMPLETE, NULL }, + { 'v', "chronicle", "show journal of major events", + do_gamelog, IFBURIED | AUTOCOMPLETE | GENERALCMD, NULL }, + { 'c', "close", "close a door", + doclose, 0, NULL }, + { M('C'), "conduct", "list voluntary challenges you have maintained", + doconduct, IFBURIED | AUTOCOMPLETE | GENERALCMD, NULL }, + { '\0', "debugfuzzer", "start the fuzz tester", + wiz_fuzzer, IFBURIED | WIZMODECMD | NOFUZZERCMD, NULL }, + { M('d'), "dip", "dip an object into something", + dodip, AUTOCOMPLETE | CMD_M_PREFIX, NULL }, + { '>', "down", "go down a staircase", + /* allows 'm' prefix (for move without autopickup) but not the + g/G/F movement modifiers; not flagged as MOVEMENTCMD because + that would suppress it from dokeylist output */ + dodown, CMD_M_PREFIX, NULL }, + { 'd', "drop", "drop an item", + dodrop, 0, NULL }, + { 'D', "droptype", "drop specific item types", + doddrop, 0, NULL }, + { 'e', "eat", "eat something", + doeat, CMD_M_PREFIX, NULL }, + { 'E', "engrave", "engrave writing on the floor", + doengrave, 0, NULL }, + { M('e'), "enhance", "advance or check weapon and spell skills", + enhance_weapon_skill, IFBURIED | AUTOCOMPLETE | GENERALCMD, NULL }, + /* #exploremode should be flagged AUTOCOMPETE but that would negatively + impact frequently used #enhance by making #e become ambiguous */ + { M('X'), "exploremode", "enter explore (discovery) mode", + enter_explore_mode, IFBURIED | GENERALCMD | NOFUZZERCMD, NULL }, + { 'F', "fight", "prefix: force fight even if you don't see a monster", + do_fight, PREFIXCMD, NULL }, + { 'f', "fire", "fire ammunition from quiver", + dofire, 0, NULL }, + { M('f'), "force", "force a lock", + doforce, AUTOCOMPLETE, NULL }, + { M('g'), "genocided", + "list monsters that have been genocided or become extinct", + dogenocided, + IFBURIED | AUTOCOMPLETE | GENERALCMD | CMD_M_PREFIX, NULL }, + { ';', "glance", "show what type of thing a map symbol corresponds to", + doquickwhatis, IFBURIED | GENERALCMD, NULL }, + { '?', "help", "give a help message", + dohelp, IFBURIED | GENERALCMD, NULL }, + { '\0', "herecmdmenu", "show menu of commands you can do here", + doherecmdmenu, IFBURIED | AUTOCOMPLETE | GENERALCMD, NULL }, + { '\0', "history", "show a summary of the game's development", + nle_noop /* dohistory */, IFBURIED | AUTOCOMPLETE | GENERALCMD, NULL }, + { 'i', "inventory", "show your inventory", + ddoinv, IFBURIED | GENERALCMD, NULL }, + { 'I', "inventtype", "show inventory of one specific item class", + dotypeinv, IFBURIED | GENERALCMD, NULL }, + { M('i'), "invoke", "invoke an object's special powers", + doinvoke, IFBURIED | AUTOCOMPLETE, NULL }, + { M('j'), "jump", "jump to another location", + dojump, AUTOCOMPLETE, NULL }, + { C('d'), "kick", "kick something", + dokick, 0, NULL }, + { '\\', "known", "show what object types have been discovered", + dodiscovered, IFBURIED | GENERALCMD | CMD_M_PREFIX, NULL }, + { '`', "knownclass", "show discovered types for one class of objects", + doclassdisco, IFBURIED | GENERALCMD | CMD_M_PREFIX, NULL }, + { '\0', "levelchange", "change experience level", + wiz_level_change, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL }, + { '\0', "lightsources", "show mobile light sources", + wiz_light_sources, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL }, + { ':', "look", "look at what is here", + dolook, IFBURIED, NULL }, + { '\0', "lookaround", "describe what you can see", + dolookaround, IFBURIED | GENERALCMD, NULL }, + { M('l'), "loot", "loot a box on the floor", + doloot, AUTOCOMPLETE | CMD_M_PREFIX, NULL }, + { '\0', "migratemons", +#ifdef DEBUG_MIGRATING_MONS + "show migrating monsters and migrate N random ones", +#else + "show migrating monsters", +#endif + wiz_migrate_mons, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL }, + { M('m'), "monster", "use monster's special ability", + domonability, IFBURIED | AUTOCOMPLETE, NULL }, + { M('n'), "name", "same as call; name a monster or object or object type", + docallcmd, IFBURIED | AUTOCOMPLETE | GENERALCMD, NULL }, + { M('o'), "offer", "offer a sacrifice to the gods", + dosacrifice, AUTOCOMPLETE | CMD_M_PREFIX, NULL }, + { 'o', "open", "open a door", + doopen, 0, NULL }, + /* 'm #options' runs doset() */ + { 'O', "options", "show option settings", + nle_doset /* doset_simple */, IFBURIED | GENERALCMD | CMD_M_PREFIX, NULL }, + /* 'm #optionsfull' runs doset_simple() */ + { '\0', "optionsfull", "show all option settings, possibly change them", + nle_doset /* doset */, IFBURIED | GENERALCMD | CMD_M_PREFIX, NULL }, + /* #overview used to need autocomplete and has retained that even + after being assigned to ^O [old wizard mode ^O is now #wizwhere]; + 'm' prefix displays overview as a menu where player can choose a + level to supply with an annotation */ + { C('o'), "overview", "show a summary of the explored dungeon", + dooverview, + IFBURIED | AUTOCOMPLETE | GENERALCMD | CMD_M_PREFIX, NULL }, + /* [should #panic actually autocomplete?] */ + { '\0', "panic", "test panic routine (fatal to game)", + wiz_panic, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL }, + { 'p', "pay", "pay your shopping bill", + dopay, CMD_M_PREFIX, NULL }, + { '|', "perminv", "scroll persistent inventory display", + doperminv, IFBURIED | GENERALCMD | NOFUZZERCMD, NULL }, + { ',', "pickup", "pick up things at the current location", + dopickup, CMD_M_PREFIX, NULL }, + { '\0', "polyself", "polymorph self", + wiz_polyself, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL }, + { M('p'), "pray", "pray to the gods for help", + dopray, IFBURIED | AUTOCOMPLETE, NULL }, + { C('p'), "prevmsg", "view recent game messages", + nle_noop /* doprev_message */, IFBURIED | GENERALCMD | CMD_INSANE, NULL }, + { 'P', "puton", "put on an accessory (ring, amulet, etc)", + doputon, 0, NULL }, + { 'q', "quaff", "quaff (drink) something", + dodrink, CMD_M_PREFIX, NULL }, + { '\0', "quit", "exit without saving current game", + done2, IFBURIED | AUTOCOMPLETE | GENERALCMD | NOFUZZERCMD, + NULL }, + { 'Q', "quiver", "select ammunition for quiver", + dowieldquiver, 0, NULL }, + { 'r', "read", "read a scroll or spellbook", + doread, 0, NULL }, + { C('r'), "redraw", "redraw screen", + doredraw, IFBURIED | GENERALCMD | CMD_INSANE, NULL }, + { 'R', "remove", "remove an accessory (ring, amulet, etc)", + doremring, 0, NULL }, + { C('a'), "repeat", "repeat a previous command", + do_repeat, IFBURIED | GENERALCMD, NULL }, + /* "modify command" is a vague description for use as no-autopickup, + no-attack movement as well as miscellaneous non-movement things; + key2extcmddesc() constructs a more explicit two line description + for display by the '&' command and expects to find "prefix:" as + the start of the text here */ + { 'm', "reqmenu", "prefix: request menu or modify command", + do_reqmenu, PREFIXCMD, NULL }, + { C('_'), "retravel", "travel to previously selected travel location", + dotravel_target, 0, NULL }, + { M('R'), "ride", "mount or dismount a saddled steed", + doride, AUTOCOMPLETE, NULL }, + { M('r'), "rub", "rub a lamp or a stone", + dorub, AUTOCOMPLETE, NULL }, + { 'G', "run", "prefix: run until something interesting is seen", + do_run, PREFIXCMD, NULL }, + { 'g', "rush", "prefix: rush until something interesting is seen", + do_rush, PREFIXCMD, NULL }, + { 'S', "save", "save the game and exit", + nle_dosave /* dosave */, IFBURIED | GENERALCMD | NOFUZZERCMD, NULL }, + { '\0', "saveoptions", "save the game configuration", + do_write_config_file, + IFBURIED | GENERALCMD | NOFUZZERCMD, NULL }, + { 's', "search", "search for traps and secret doors", + dosearch, IFBURIED | CMD_M_PREFIX, "searching" }, + { '*', "seeall", "show all equipment in use", + doprinuse, IFBURIED | GENERALCMD | CMD_M_PREFIX, NULL }, + { AMULET_SYM, "seeamulet", "show the amulet currently worn", + dopramulet, IFBURIED | GENERALCMD | CMD_M_PREFIX, NULL }, + { ARMOR_SYM, "seearmor", "show the armor currently worn", + doprarm, IFBURIED | GENERALCMD | CMD_M_PREFIX, NULL }, + { RING_SYM, "seerings", "show the ring(s) currently worn", + doprring, IFBURIED | GENERALCMD | CMD_M_PREFIX, NULL }, + { TOOL_SYM, "seetools", "show the tools currently in use", + doprtool, IFBURIED | GENERALCMD | CMD_M_PREFIX, NULL }, + { WEAPON_SYM, "seeweapon", "show the weapon currently wielded", + doprwep, IFBURIED | GENERALCMD | CMD_M_PREFIX, NULL }, + { '!', "shell", + "leave game to enter a sub-shell ('exit' to come back)", + dosh_core, (IFBURIED | GENERALCMD | NOFUZZERCMD +#ifndef SHELL + | CMD_NOT_AVAILABLE +#endif /* SHELL */ + ), NULL }, + /* $ is like ),=,&c but is not included with *, so not called "seegold" */ + { GOLD_SYM, "showgold", "show gold, possibly shop credit or debt", + doprgold, IFBURIED | GENERALCMD | CMD_M_PREFIX, NULL }, + { SPBOOK_SYM, "showspells", "list and reorder known spells", + dovspell, IFBURIED | GENERALCMD, NULL }, + { '^', "showtrap", "describe an adjacent, discovered trap", + doidtrap, IFBURIED | GENERALCMD, NULL }, + { M('s'), "sit", "sit down", + dosit, AUTOCOMPLETE, NULL }, + { '\0', "stats", "show memory statistics", + wiz_show_stats, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL }, + { C('z'), "suspend", "push game to background ('fg' to come back)", + dosuspend_core, (IFBURIED | GENERALCMD | NOFUZZERCMD +#ifndef SUSPEND + | CMD_NOT_AVAILABLE +#endif /* SUSPEND */ + ), NULL }, + { 'x', "swap", "swap wielded and secondary weapons", + doswapweapon, 0, NULL }, + { 'T', "takeoff", "take off one piece of armor", + dotakeoff, 0, NULL }, + { 'A', "takeoffall", "remove all armor", + doddoremarm, 0, NULL }, + { C('t'), "teleport", "teleport around the level", + dotelecmd, IFBURIED | CMD_M_PREFIX, NULL }, + /* \177 == aka aka ; some terminals have an + option to swap it with so if there's a key labeled + it may or may not actually invoke the #terrain command */ + { '\177', "terrain", + "view map without monsters or objects obstructing it", + doterrain, IFBURIED | GENERALCMD | AUTOCOMPLETE, NULL }, + { '\0', "therecmdmenu", + "menu of commands you can do from here to adjacent spot", + dotherecmdmenu, AUTOCOMPLETE | GENERALCMD | MOUSECMD, NULL }, + { 't', "throw", "throw something", + dothrow, 0, NULL }, + { '\0', "timeout", "look at timeout queue and hero's timed intrinsics", + wiz_timeout_queue, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL }, + { M('T'), "tip", "empty a container", + dotip, AUTOCOMPLETE | CMD_M_PREFIX, NULL }, + { '\0', "toggle", "toggle boolean option", + dotoggleoption, IFBURIED | GENERALCMD | CMD_PARAM, NULL }, + { '_', "travel", "travel to a specific location on the map", + dotravel, CMD_M_PREFIX, NULL }, + { M('t'), "turn", "turn undead away", + doturn, IFBURIED | AUTOCOMPLETE, NULL }, + { 'X', "twoweapon", "toggle two-weapon combat", + dotwoweapon, 0, NULL }, + { M('u'), "untrap", "untrap something", + dountrap, AUTOCOMPLETE, NULL }, + { '<', "up", "go up a staircase", + /* (see comment for dodown() above */ + doup, CMD_M_PREFIX, NULL }, + { M('V'), "vanquished", "list vanquished monsters", + dovanquished, + IFBURIED | AUTOCOMPLETE | GENERALCMD | CMD_M_PREFIX, NULL }, + { M('v'), "version", + "list compile time options for this version of NetHack", + doextversion, IFBURIED | AUTOCOMPLETE | GENERALCMD, NULL }, + { 'V', "versionshort", "show version and date+time program was built", + doversion, IFBURIED | GENERALCMD | CMD_M_PREFIX, NULL }, + { '\0', "vision", "show vision array", + wiz_show_vision, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL }, + { '.', "wait", "rest one move while doing nothing", + donull, IFBURIED | CMD_M_PREFIX, "waiting" }, + { 'W', "wear", "wear a piece of armor", + dowear, 0, NULL }, + { '&', "whatdoes", "tell what a command does", + dowhatdoes, IFBURIED | GENERALCMD, NULL }, + { '/', "whatis", "show what type of thing a symbol corresponds to", + dowhatis, IFBURIED | GENERALCMD, NULL }, + { 'w', "wield", "wield (put in use) a weapon", + dowield, 0, NULL }, + { M('w'), "wipe", "wipe off your face", + dowipe, AUTOCOMPLETE, NULL }, + { '\0', "wizborn", "show stats of monsters created", + doborn, IFBURIED | WIZMODECMD, NULL }, +#ifdef DEBUG + { '\0', "wizbury", "bury objs under and around you", + wiz_debug_cmd_bury, IFBURIED | AUTOCOMPLETE | WIZMODECMD, + NULL }, +#endif + { '\0', "wizcast", "cast any spell", + dowizcast, IFBURIED | WIZMODECMD, NULL }, + { '\0', "wizcustom", "show customized glyphs", + wiz_custom, IFBURIED | WIZMODECMD | NOFUZZERCMD, NULL }, + { C('e'), "wizdetect", "reveal hidden things within a small radius", + wiz_detect, IFBURIED | WIZMODECMD, NULL }, +#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) || defined(DEBUG) + { '\0', "wizdispmacros", "validate the display macro ranges", + wiz_display_macros, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL }, +#endif + { '\0', "wizfliplevel", "flip the level", + wiz_flip_level, IFBURIED | WIZMODECMD, NULL }, + { C('g'), "wizgenesis", "create a monster", + wiz_genesis, IFBURIED | WIZMODECMD, NULL }, + { C('i'), "wizidentify", "identify all items in inventory", + wiz_identify, IFBURIED | WIZMODECMD, NULL }, + { '\0', "wizintrinsic", "set an intrinsic", + wiz_intrinsic, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL }, + { '\0', "wizkill", "slay a monster", + wiz_kill, (IFBURIED | AUTOCOMPLETE | WIZMODECMD + | CMD_M_PREFIX | NOFUZZERCMD), NULL }, + { C('v'), "wizlevelport", "teleport to another level", + wiz_level_tele, IFBURIED | WIZMODECMD | CMD_M_PREFIX, NULL }, + { '\0', "wizloaddes", "load and execute a des-file lua script", + wiz_load_splua, IFBURIED | WIZMODECMD | NOFUZZERCMD, NULL }, + { '\0', "wizloadlua", "load and execute a lua script", + wiz_load_lua, IFBURIED | WIZMODECMD | NOFUZZERCMD, NULL }, +#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) || defined(DEBUG) + { '\0', "wizobjprobs", "list object generation probabilities", + wiz_objprobs, IFBURIED | WIZMODECMD, NULL }, +#endif + { '\0', "wizmakemap", "recreate the current level", + wiz_makemap, IFBURIED | WIZMODECMD, NULL }, + { C('f'), "wizmap", "map the level", + wiz_map, IFBURIED | WIZMODECMD, NULL }, +#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) || defined(DEBUG) + { '\0', "wizmondiff", "validate the difficulty ratings of monsters", + wiz_mon_diff, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL }, +#endif + { '\0', "wizrumorcheck", "verify rumor boundaries", + wiz_rumor_check, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL }, + { '\0', "wizseenv", "show map locations' seen vectors", + wiz_show_seenv, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL }, + { '\0', "wizshownhuuid", "show NHUUID for this game", + wiz_show_nhuuid, AUTOCOMPLETE | WIZMODECMD, NULL }, + { '\0', "wizsmell", "smell monster", + wiz_smell, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL }, + { '\0', "wiztelekinesis", "telekinesis", + wiz_telekinesis, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL }, + { '\0', "wizwhere", "show locations of special levels", + wiz_where, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL }, + { C('w'), "wizwish", "wish for something", + wiz_wish, IFBURIED | CMD_M_PREFIX | WIZMODECMD, NULL }, + { '\0', "wmode", "show wall modes", + wiz_show_wmodes, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL }, + { 'z', "zap", "zap a wand", + dozap, 0, NULL }, + /* movement commands will be bound by reset_commands() */ + /* move or attack; accept m/g/G/F prefixes */ + { '\0', "movewest", "move west (screen left)", + do_move_west, MOVEMENTCMD | CMD_MOVE_PREFIXES, NULL }, + { '\0', "movenorthwest", "move northwest (screen upper left)", + do_move_northwest, MOVEMENTCMD | CMD_MOVE_PREFIXES, NULL }, + { '\0', "movenorth", "move north (screen up)", + do_move_north, MOVEMENTCMD | CMD_MOVE_PREFIXES, NULL }, + { '\0', "movenortheast", "move northeast (screen upper right)", + do_move_northeast, MOVEMENTCMD | CMD_MOVE_PREFIXES, NULL }, + { '\0', "moveeast", "move east (screen right)", + do_move_east, MOVEMENTCMD | CMD_MOVE_PREFIXES, NULL }, + { '\0', "movesoutheast", "move southeast (screen lower right)", + do_move_southeast, MOVEMENTCMD | CMD_MOVE_PREFIXES, NULL }, + { '\0', "movesouth", "move south (screen down)", + do_move_south, MOVEMENTCMD | CMD_MOVE_PREFIXES, NULL }, + { '\0', "movesouthwest", "move southwest (screen lower left)", + do_move_southwest, MOVEMENTCMD | CMD_MOVE_PREFIXES, NULL }, + /* rush; accept m prefix but not g/G/F */ + { '\0', "rushwest", "rush west (screen left)", + do_rush_west, MOVEMENTCMD | CMD_M_PREFIX, NULL }, + { '\0', "rushnorthwest", "rush northwest (screen upper left)", + do_rush_northwest, MOVEMENTCMD | CMD_M_PREFIX, NULL }, + { '\0', "rushnorth", "rush north (screen up)", + do_rush_north, MOVEMENTCMD | CMD_M_PREFIX, NULL }, + { '\0', "rushnortheast", "rush northeast (screen upper right)", + do_rush_northeast, MOVEMENTCMD | CMD_M_PREFIX, NULL }, + { '\0', "rusheast", "rush east (screen right)", + do_rush_east, MOVEMENTCMD | CMD_M_PREFIX, NULL }, + { '\0', "rushsoutheast", "rush southeast (screen lower right)", + do_rush_southeast, MOVEMENTCMD | CMD_M_PREFIX, NULL }, + { '\0', "rushsouth", "rush south (screen down)", + do_rush_south, MOVEMENTCMD | CMD_M_PREFIX, NULL }, + { '\0', "rushsouthwest", "rush southwest (screen lower left)", + do_rush_southwest, MOVEMENTCMD | CMD_M_PREFIX, NULL }, + /* run; accept m prefix but not g/G/F */ + { '\0', "runwest", "run west (screen left)", + do_run_west, MOVEMENTCMD | CMD_M_PREFIX, NULL }, + { '\0', "runnorthwest", "run northwest (screen upper left)", + do_run_northwest, MOVEMENTCMD | CMD_M_PREFIX, NULL }, + { '\0', "runnorth", "run north (screen up)", + do_run_north, MOVEMENTCMD | CMD_M_PREFIX, NULL }, + { '\0', "runnortheast", "run northeast (screen upper right)", + do_run_northeast, MOVEMENTCMD | CMD_M_PREFIX, NULL }, + { '\0', "runeast", "run east (screen right)", + do_run_east, MOVEMENTCMD | CMD_M_PREFIX, NULL }, + { '\0', "runsoutheast", "run southeast (screen lower right)", + do_run_southeast, MOVEMENTCMD | CMD_M_PREFIX, NULL }, + { '\0', "runsouth", "run south (screen down)", + do_run_south, MOVEMENTCMD | CMD_M_PREFIX, NULL }, + { '\0', "runsouthwest", "run southwest (screen lower left)", + do_run_southwest, MOVEMENTCMD | CMD_M_PREFIX, NULL }, + + /* internal commands: only used by game core, not available for user */ + { '\0', "clicklook", NULL, doclicklook, INTERNALCMD | MOUSECMD, NULL }, + { '\0', "mouseaction", NULL, domouseaction, INTERNALCMD | MOUSECMD, NULL }, + { '\0', "altadjust", NULL, adjust_split, INTERNALCMD, NULL }, + { '\0', "altdip", NULL, dip_into, INTERNALCMD, NULL }, + { '\0', "alttakeoff", NULL, ia_dotakeoff, INTERNALCMD, NULL }, + { '\0', "altunwield", NULL, remarm_swapwep, INTERNALCMD, NULL }, + { '\0', (char *) 0, (char *) 0, donull, 0, (char *) 0 } /* sentinel */ +}; + +/* mapping direction and move mode to extended command function */ +static int (*move_funcs[N_DIRS_Z][N_MOVEMODES])(void) = { + { do_move_west, do_run_west, do_rush_west }, + { do_move_northwest, do_run_northwest, do_rush_northwest }, + { do_move_north, do_run_north, do_rush_north }, + { do_move_northeast, do_run_northeast, do_rush_northeast }, + { do_move_east, do_run_east, do_rush_east }, + { do_move_southeast, do_run_southeast, do_rush_southeast }, + { do_move_south, do_run_south, do_rush_south }, + { do_move_southwest, do_run_southwest, do_rush_southwest }, + /* misleading; rush and run for down or up are rejected by rhack() + because dodown() and doup() lack the CMD_gGF_PREFIX flag */ + { dodown, dodown, dodown }, + { doup, doup, doup }, +}; + +/* used by dokeylist() and by key2extcmddesc() for dowhatdoes() */ +static const struct { + int nhkf; + const char *desc; + boolean numpad; +} misc_keys[] = { + { NHKF_ESC, "cancel current prompt or pending prefix", FALSE }, + { NHKF_COUNT, + "Prefix: for digits when preceding a command with a count", TRUE }, + { 0, (const char *) 0, FALSE } +}; + +static int extcmdlist_length = SIZE(extcmdlist) - 1; + +/* get entry i in the extended commands list. for windowport use. */ +struct ext_func_tab * +extcmds_getentry(int i) +{ + if (i < 0 || i > extcmdlist_length) + return 0; + return &extcmdlist[i]; } -/* is hero actively using water walking capability on water (or lava)? */ -STATIC_OVL boolean -walking_on_water() +/* get the command bound to a key */ +staticfn struct Cmd_bind * +cmdbind_get(uchar key) { - if (u.uinwater || Levitation || Flying) - return FALSE; - return (boolean) (Wwalking - && (is_pool(u.ux, u.uy) || is_lava(u.ux, u.uy))); + struct Cmd_bind *bind = gc.Cmd.cmdbinds; + + if (!key) + return NULL; + + while (bind) { + if (bind->key == key) + return bind; + bind = bind->next; + } + return bind; } -/* check whether hero is wearing something that player definitely knows - confers the target property; item must have been seen and its type - discovered but it doesn't necessarily have to be fully identified */ -STATIC_OVL boolean -cause_known(propindx) -int propindx; /* index of a property which can be conveyed by worn item */ +staticfn void +cmdbind_add(uchar key, const struct ext_func_tab *extcmd, boolean user) { - register struct obj *o; - long mask = W_ARMOR | W_AMUL | W_RING | W_TOOL; + struct Cmd_bind *bind = cmdbind_get(key); - /* simpler than from_what()/what_gives(); we don't attempt to - handle artifacts and we deliberately ignore wielded items */ - for (o = invent; o; o = o->nobj) { - if (!(o->owornmask & mask)) - continue; - if ((int) objects[o->otyp].oc_oprop == propindx - && objects[o->otyp].oc_name_known && o->dknown) - return TRUE; + if (!key) + return; + if (!extcmd && bind) { + cmdbind_remove(key); + return; + } + + /* binding exists, set it to this command */ + if (bind) { + bind->cmd = extcmd; + bind->userbind = user; + if (bind->param) { + free(bind->param); + bind->param = NULL; + } + return; + } else { + bind = (struct Cmd_bind *) alloc(sizeof(struct Cmd_bind)); + bind->key = key; + bind->userbind = user; + bind->param = NULL; + bind->cmd = extcmd; + bind->next = gc.Cmd.cmdbinds; + gc.Cmd.cmdbinds = bind; } - return FALSE; } -/* format a characteristic value, accommodating Strength's strangeness */ -STATIC_OVL char * -attrval(attrindx, attrvalue, resultbuf) -int attrindx, attrvalue; -char resultbuf[]; /* should be at least [7] to hold "18/100\0" */ +staticfn void +cmdbind_remove(uchar key) { - if (attrindx != A_STR || attrvalue <= 18) - Sprintf(resultbuf, "%d", attrvalue); - else if (attrvalue > STR18(100)) /* 19 to 25 */ - Sprintf(resultbuf, "%d", attrvalue - 100); - else /* simplify "18/ **" to be "18/100" */ - Sprintf(resultbuf, "18/%02d", attrvalue - 18); - return resultbuf; + struct Cmd_bind *bind = gc.Cmd.cmdbinds; + struct Cmd_bind *prev = (struct Cmd_bind *) 0; + + while (bind) { + if (bind->key == key) { + if (prev) + prev->next = bind->next; + else + gc.Cmd.cmdbinds = bind->next; + if (bind->param) + free(bind->param); + free(bind); + return; + } + prev = bind; + bind = bind->next; + } } void -enlightenment(mode, final) -int mode; /* BASICENLIGHTENMENT | MAGICENLIGHTENMENT (| both) */ -int final; /* ENL_GAMEINPROGRESS:0, ENL_GAMEOVERALIVE, ENL_GAMEOVERDEAD */ -{ - char buf[BUFSZ], tmpbuf[BUFSZ]; - - en_win = create_nhwindow(NHW_MENU); - en_via_menu = !final; - if (en_via_menu) - start_menu(en_win); - - Strcpy(tmpbuf, plname); - *tmpbuf = highc(*tmpbuf); /* same adjustment as bottom line */ - /* as in background_enlightenment, when poly'd we need to use the saved - gender in u.mfemale rather than the current you-as-monster gender */ - Sprintf(buf, "%s the %s's attributes:", tmpbuf, - ((Upolyd ? u.mfemale : flags.female) && urole.name.f) - ? urole.name.f - : urole.name.m); - - /* title */ - enlght_out(buf); /* "Conan the Archeologist's attributes:" */ - /* background and characteristics; ^X or end-of-game disclosure */ - if (mode & BASICENLIGHTENMENT) { - /* role, race, alignment, deities, dungeon level, time, experience */ - background_enlightenment(mode, final); - /* hit points, energy points, armor class, gold */ - basics_enlightenment(mode, final); - /* strength, dexterity, &c */ - characteristics_enlightenment(mode, final); - } - /* expanded status line information, including things which aren't - included there due to space considerations--such as obvious - alternative movement indicators (riding, levitation, &c), and - various troubles (turning to stone, trapped, confusion, &c); - shown for both basic and magic enlightenment */ - status_enlightenment(mode, final); - /* remaining attributes; shown for potion,&c or wizard mode and - explore mode ^X or end of game disclosure */ - if (mode & MAGICENLIGHTENMENT) { - /* intrinsics and other traditional enlightenment feedback */ - attributes_enlightenment(mode, final); - } - - if (!en_via_menu) { - display_nhwindow(en_win, TRUE); - } else { - menu_item *selected = 0; +cmdbind_freeall(void) +{ + struct Cmd_bind *next; - end_menu(en_win, (char *) 0); - if (select_menu(en_win, PICK_NONE, &selected) > 0) - free((genericptr_t) selected); - en_via_menu = FALSE; - } - destroy_nhwindow(en_win); - en_win = WIN_ERR; -} - -/*ARGSUSED*/ -/* display role, race, alignment and such to en_win */ -STATIC_OVL void -background_enlightenment(unused_mode, final) -int unused_mode UNUSED; -int final; -{ - const char *role_titl, *rank_titl; - int innategend, difgend, difalgn; - char buf[BUFSZ], tmpbuf[BUFSZ]; - - /* note that if poly'd, we need to use u.mfemale instead of flags.female - to access hero's saved gender-as-human/elf/&c rather than current one */ - innategend = (Upolyd ? u.mfemale : flags.female) ? 1 : 0; - role_titl = (innategend && urole.name.f) ? urole.name.f : urole.name.m; - rank_titl = rank_of(u.ulevel, Role_switch, innategend); - - enlght_out(""); /* separator after title */ - enlght_out("Background:"); - - /* if polymorphed, report current shape before underlying role; - will be repeated as first status: "you are transformed" and also - among various attributes: "you are in beast form" (after being - told about lycanthropy) or "you are polymorphed into " - (with countdown timer appended for wizard mode); we really want - the player to know he's not a samurai at the moment... */ - if (Upolyd) { - struct permonst *uasmon = youmonst.data; - - tmpbuf[0] = '\0'; - /* here we always use current gender, not saved role gender */ - if (!is_male(uasmon) && !is_female(uasmon) && !is_neuter(uasmon)) - Sprintf(tmpbuf, "%s ", genders[flags.female ? 1 : 0].adj); - Sprintf(buf, "%sin %s%s form", !final ? "currently " : "", tmpbuf, - uasmon->mname); - you_are(buf, ""); - } - - /* report role; omit gender if it's redundant (eg, "female priestess") */ - tmpbuf[0] = '\0'; - if (!urole.name.f - && ((urole.allow & ROLE_GENDMASK) == (ROLE_MALE | ROLE_FEMALE) - || innategend != flags.initgend)) - Sprintf(tmpbuf, "%s ", genders[innategend].adj); - buf[0] = '\0'; - if (Upolyd) - Strcpy(buf, "actually "); /* "You are actually a ..." */ - if (!strcmpi(rank_titl, role_titl)) { - /* omit role when rank title matches it */ - Sprintf(eos(buf), "%s, level %d %s%s", an(rank_titl), u.ulevel, - tmpbuf, urace.noun); - } else { - Sprintf(eos(buf), "%s, a level %d %s%s %s", an(rank_titl), u.ulevel, - tmpbuf, urace.adj, role_titl); - } - you_are(buf, ""); - - /* report alignment (bypass you_are() in order to omit ending period); - adverb is used to distinguish between temporary change (helm of opp. - alignment), permanent change (one-time conversion), and original */ - Sprintf(buf, " %s%s%s, %son a mission for %s", - You_, !final ? are : were, - align_str(u.ualign.type), - /* helm of opposite alignment (might hide conversion) */ - (u.ualign.type != u.ualignbase[A_CURRENT]) - /* what's the past tense of "currently"? if we used "formerly" - it would sound like a reference to the original alignment */ - ? (!final ? "currently " : "temporarily ") - /* permanent conversion */ - : (u.ualign.type != u.ualignbase[A_ORIGINAL]) - /* and what's the past tense of "now"? certainly not "then" - in a context like this...; "belatedly" == weren't that - way sooner (in other words, didn't start that way) */ - ? (!final ? "now " : "belatedly ") - /* atheist (ignored in very early game) */ - : (!u.uconduct.gnostic && moves > 1000L) - ? "nominally " - /* lastly, normal case */ - : "", - u_gname()); - enlght_out(buf); - /* show the rest of this game's pantheon (finishes previous sentence) - [appending "also Moloch" at the end would allow for straightforward - trailing "and" on all three aligned entries but looks too verbose] */ - Sprintf(buf, " who %s opposed by", !final ? "is" : "was"); - if (u.ualign.type != A_LAWFUL) - Sprintf(eos(buf), " %s (%s) and", align_gname(A_LAWFUL), - align_str(A_LAWFUL)); - if (u.ualign.type != A_NEUTRAL) - Sprintf(eos(buf), " %s (%s)%s", align_gname(A_NEUTRAL), - align_str(A_NEUTRAL), - (u.ualign.type != A_CHAOTIC) ? " and" : ""); - if (u.ualign.type != A_CHAOTIC) - Sprintf(eos(buf), " %s (%s)", align_gname(A_CHAOTIC), - align_str(A_CHAOTIC)); - Strcat(buf, "."); /* terminate sentence */ - enlght_out(buf); - - /* show original alignment,gender,race,role if any have been changed; - giving separate message for temporary alignment change bypasses need - for tricky phrasing otherwise necessitated by possibility of having - helm of opposite alignment mask a permanent alignment conversion */ - difgend = (innategend != flags.initgend); - difalgn = (((u.ualign.type != u.ualignbase[A_CURRENT]) ? 1 : 0) - + ((u.ualignbase[A_CURRENT] != u.ualignbase[A_ORIGINAL]) - ? 2 : 0)); - if (difalgn & 1) { /* have temporary alignment so report permanent one */ - Sprintf(buf, "actually %s", align_str(u.ualignbase[A_CURRENT])); - you_are(buf, ""); - difalgn &= ~1; /* suppress helm from "started out " message */ - } - if (difgend || difalgn) { /* sex change or perm align change or both */ - Sprintf(buf, " You started out %s%s%s.", - difgend ? genders[flags.initgend].adj : "", - (difgend && difalgn) ? " and " : "", - difalgn ? align_str(u.ualignbase[A_ORIGINAL]) : ""); - enlght_out(buf); - } - - /* As of 3.6.2: dungeon level, so that ^X really has all status info as - claimed by the comment below; this reveals more information than - the basic status display, but that's one of the purposes of ^X; - similar information is revealed by #overview; the "You died in - " given by really_done() is more rudimentary than this */ - *buf = *tmpbuf = '\0'; - if (In_endgame(&u.uz)) { - int egdepth = observable_depth(&u.uz); - - (void) endgamelevelname(tmpbuf, egdepth); - Sprintf(buf, "in the endgame, on the %s%s", - !strncmp(tmpbuf, "Plane", 5) ? "Elemental " : "", tmpbuf); - } else if (Is_knox(&u.uz)) { - /* this gives away the fact that the knox branch is only 1 level */ - Sprintf(buf, "on the %s level", dungeons[u.uz.dnum].dname); - /* TODO? maybe phrase it differently when actually inside the fort, - if we're able to determine that (not trivial) */ - } else { - char dgnbuf[QBUFSZ]; - - Strcpy(dgnbuf, dungeons[u.uz.dnum].dname); - if (!strncmpi(dgnbuf, "The ", 4)) - *dgnbuf = lowc(*dgnbuf); - Sprintf(tmpbuf, "level %d", - In_quest(&u.uz) ? dunlev(&u.uz) : depth(&u.uz)); - /* TODO? maybe extend this bit to include various other automatic - annotations from the dungeon overview code */ - if (Is_rogue_level(&u.uz)) - Strcat(tmpbuf, ", a primitive area"); - else if (Is_bigroom(&u.uz) && !Blind) - Strcat(tmpbuf, ", a very big room"); - Sprintf(buf, "in %s, on %s", dgnbuf, tmpbuf); - } - you_are(buf, ""); - - /* this is shown even if the 'time' option is off */ - if (moves == 1L) { - you_have("just started your adventure", ""); - } else { - /* 'turns' grates on the nerves in this context... */ - Sprintf(buf, "the dungeon %ld turn%s ago", moves, plur(moves)); - /* same phrasing for current and final: "entered" is unconditional */ - enlght_line(You_, "entered ", buf, ""); - } - - /* for gameover, these have been obtained in really_done() so that they - won't vary if user leaves a disclosure prompt or --More-- unanswered - long enough for the dynamic value to change between then and now */ - if (final ? iflags.at_midnight : midnight()) { - enl_msg("It ", "is ", "was ", "the midnight hour", ""); - } else if (final ? iflags.at_night : night()) { - enl_msg("It ", "is ", "was ", "nighttime", ""); - } - /* other environmental factors */ - if (flags.moonphase == FULL_MOON || flags.moonphase == NEW_MOON) { - /* [This had "tonight" but has been changed to "in effect". - There is a similar issue to Friday the 13th--it's the value - at the start of the current session but that session might - have dragged on for an arbitrary amount of time. We want to - report the values that currently affect play--or affected - play when game ended--rather than actual outside situation.] */ - Sprintf(buf, "a %s moon in effect%s", - (flags.moonphase == FULL_MOON) ? "full" - : (flags.moonphase == NEW_MOON) ? "new" - /* showing these would probably just lead to confusion - since they have no effect on game play... */ - : (flags.moonphase < FULL_MOON) ? "first quarter" - : "last quarter", - /* we don't have access to 'how' here--aside from survived - vs died--so settle for general platitude */ - final ? " when your adventure ended" : ""); - enl_msg("There ", "is ", "was ", buf, ""); - } - if (flags.friday13) { - /* let player know that friday13 penalty is/was in effect; - we don't say "it is/was Friday the 13th" because that was at - the start of the session and it might be past midnight (or - days later if the game has been paused without save/restore), - so phrase this similar to the start up message */ - Sprintf(buf, " Bad things %s on Friday the 13th.", - !final ? "can happen" - : (final == ENL_GAMEOVERALIVE) ? "could have happened" - /* there's no may to tell whether -1 Luck made a - difference but hero has died... */ - : "happened"); - enlght_out(buf); - } - - if (!Upolyd) { - int ulvl = (int) u.ulevel; - /* [flags.showexp currently does not matter; should it?] */ - - /* experience level is already shown above */ - Sprintf(buf, "%-1ld experience point%s", u.uexp, plur(u.uexp)); - /* TODO? - * Remove wizard-mode restriction since patient players can - * determine the numbers needed without resorting to spoilers - * (even before this started being disclosed for 'final'; - * just enable 'showexp' and look at normal status lines - * after drinking gain level potions or eating wraith corpses - * or being level-drained by vampires). - */ - if (ulvl < 30 && (final || wizard)) { - long nxtlvl = newuexp(ulvl), delta = nxtlvl - u.uexp; - - Sprintf(eos(buf), ", %ld %s%sneeded %s level %d", - delta, (u.uexp > 0) ? "more " : "", - /* present tense=="needed", past tense=="were needed" */ - !final ? "" : (delta == 1L) ? "was " : "were ", - /* "for": grammatically iffy but less likely to wrap */ - (ulvl < 18) ? "to attain" : "for", (ulvl + 1)); - } - you_have(buf, ""); - } -#ifdef SCORE_ON_BOTL - if (flags.showscore) { - /* describes what's shown on status line, which is an approximation; - only show it here if player has the 'showscore' option enabled */ - Sprintf(buf, "%ld%s", botl_score(), - !final ? "" : " before end-of-game adjustments"); - enl_msg("Your score ", "is ", "was ", buf, ""); + while (gc.Cmd.cmdbinds) { + next = gc.Cmd.cmdbinds->next; + if (gc.Cmd.cmdbinds->param) + free(gc.Cmd.cmdbinds->param); + free(gc.Cmd.cmdbinds); + gc.Cmd.cmdbinds = next; } -#endif } -/* hit points, energy points, armor class -- essential information which - doesn't fit very well in other categories */ -/*ARGSUSED*/ -STATIC_OVL void -basics_enlightenment(mode, final) -int mode UNUSED; -int final; +/* swap key bindings for key1 and key2. both bindings must exist. */ +staticfn void +cmdbind_swapkeys(uchar key1, uchar key2) { - static char Power[] = "energy points (spell power)"; - char buf[BUFSZ]; - int pw = u.uen, hp = (Upolyd ? u.mh : u.uhp), - pwmax = u.uenmax, hpmax = (Upolyd ? u.mhmax : u.uhpmax); + struct Cmd_bind *bind1 = cmdbind_get(key1); + struct Cmd_bind *bind2 = cmdbind_get(key2); - enlght_out(""); /* separator after background */ - enlght_out("Basics:"); - - if (hp < 0) - hp = 0; - /* "1 out of 1" rather than "all" if max is only 1; should never happen */ - if (hp == hpmax && hpmax > 1) - Sprintf(buf, "all %d hit points", hpmax); - else - Sprintf(buf, "%d out of %d hit point%s", hp, hpmax, plur(hpmax)); - you_have(buf, ""); - - /* low max energy is feasible, so handle couple of extra special cases */ - if (pwmax == 0 || (pw == pwmax && pwmax == 2)) /* both: "all 2" is silly */ - Sprintf(buf, "%s %s", !pwmax ? "no" : "both", Power); - else if (pw == pwmax && pwmax > 2) - Sprintf(buf, "all %d %s", pwmax, Power); - else - Sprintf(buf, "%d out of %d %s", pw, pwmax, Power); - you_have(buf, ""); - - if (Upolyd) { - switch (mons[u.umonnum].mlevel) { - case 0: - /* status line currently being explained shows "HD:0" */ - Strcpy(buf, "0 hit dice (actually 1/2)"); - break; - case 1: - Strcpy(buf, "1 hit die"); - break; - default: - Sprintf(buf, "%d hit dice", mons[u.umonnum].mlevel); - break; - } - you_have(buf, ""); + if (bind1 && bind2) { + bind1->key = key2; + bind2->key = key1; } +} - Sprintf(buf, "%d", u.uac); - enl_msg("Your armor class ", "is ", "was ", buf, ""); +/* return number of extended commands bound to a non-default key */ +int +count_bind_keys(void) +{ + struct Cmd_bind *bind = gc.Cmd.cmdbinds; + int i, nbinds = 0; + uchar keys[256]; - /* gold; similar to doprgold(#seegold) but without shop billing info; - same amount as shown on status line which ignores container contents */ - { - static const char Your_wallet[] = "Your wallet "; - long umoney = money_cnt(invent); + (void) memset(keys, 0, sizeof(uchar) * 256); - if (!umoney) { - enl_msg(Your_wallet, "is ", "was ", "empty", ""); - } else { - Sprintf(buf, "%ld %s", umoney, currency(umoney)); - enl_msg(Your_wallet, "contains ", "contained ", buf, ""); + /* commands bound to different key */ + while (bind) { + keys[bind->key] = 1; + if (bind->userbind && bind->cmd && bind->cmd->key != bind->key) { + nbinds++; } + bind = bind->next; } - if (flags.pickup) { - char ocl[MAXOCLASSES + 1]; + /* commands which should be bound to a key, but aren't */ + for (i = 0; i < extcmdlist_length; i++) + if (extcmdlist[i].key && !keys[extcmdlist[i].key]) + nbinds++; - Strcpy(buf, "on"); - oc_to_str(flags.pickup_types, ocl); - Sprintf(eos(buf), " for %s%s%s", - *ocl ? "'" : "", *ocl ? ocl : "all types", *ocl ? "'" : ""); - if (flags.pickup_thrown && *ocl) /* *ocl: don't show if 'all types' */ - Strcat(buf, " plus thrown"); - if (apelist) - Strcat(buf, ", with exceptions"); - } else - Strcpy(buf, "off"); - enl_msg("Autopickup ", "is ", "was ", buf, ""); + return nbinds; } -/* characteristics: expanded version of bottom line strength, dexterity, &c */ -STATIC_OVL void -characteristics_enlightenment(mode, final) -int mode; -int final; +/* show changed key bindings in text, or if sbuf is non-null, append to it */ +void +get_changed_key_binds(strbuf_t *sbuf) { + winid win = WIN_ERR; + int i; char buf[BUFSZ]; - - enlght_out(""); - Sprintf(buf, "%s Characteristics:", !final ? "Current" : "Final"); - enlght_out(buf); - - /* bottom line order */ - one_characteristic(mode, final, A_STR); /* strength */ - one_characteristic(mode, final, A_DEX); /* dexterity */ - one_characteristic(mode, final, A_CON); /* constitution */ - one_characteristic(mode, final, A_INT); /* intelligence */ - one_characteristic(mode, final, A_WIS); /* wisdom */ - one_characteristic(mode, final, A_CHA); /* charisma */ -} - -/* display one attribute value for characteristics_enlightenment() */ -STATIC_OVL void -one_characteristic(mode, final, attrindx) -int mode, final, attrindx; -{ - extern const char *const attrname[]; /* attrib.c */ - boolean hide_innate_value = FALSE, interesting_alimit; - int acurrent, abase, apeak, alimit; - const char *paren_pfx; - char subjbuf[BUFSZ], valubuf[BUFSZ], valstring[32]; - - /* being polymorphed or wearing certain cursed items prevents - hero from reliably tracking changes to characteristics so - we don't show base & peak values then; when the items aren't - cursed, hero could take them off to check underlying values - and we show those in such case so that player doesn't need - to actually resort to doing that */ - if (Upolyd) { - hide_innate_value = TRUE; - } else if (Fixed_abil) { - if (stuck_ring(uleft, RIN_SUSTAIN_ABILITY) - || stuck_ring(uright, RIN_SUSTAIN_ABILITY)) - hide_innate_value = TRUE; - } - switch (attrindx) { - case A_STR: - if (uarmg && uarmg->otyp == GAUNTLETS_OF_POWER && uarmg->cursed) - hide_innate_value = TRUE; - break; - case A_DEX: - break; - case A_CON: - if (uwep && uwep->oartifact == ART_OGRESMASHER && uwep->cursed) - hide_innate_value = TRUE; - break; - case A_INT: - if (uarmh && uarmh->otyp == DUNCE_CAP && uarmh->cursed) - hide_innate_value = TRUE; - break; - case A_WIS: - if (uarmh && uarmh->otyp == DUNCE_CAP && uarmh->cursed) - hide_innate_value = TRUE; - break; - case A_CHA: - break; - default: - return; /* impossible */ - }; - /* note: final disclosure includes MAGICENLIGHTENTMENT */ - if ((mode & MAGICENLIGHTENMENT) && !Upolyd) - hide_innate_value = FALSE; - - acurrent = ACURR(attrindx); - (void) attrval(attrindx, acurrent, valubuf); /* Sprintf(valubuf,"%d",) */ - Sprintf(subjbuf, "Your %s ", attrname[attrindx]); - - if (!hide_innate_value) { - /* show abase, amax, and/or attrmax if acurr doesn't match abase - (a magic bonus or penalty is in effect) or abase doesn't match - amax (some points have been lost to poison or exercise abuse - and are restorable) or attrmax is different from normal human - (while game is in progress; trying to reduce dependency on - spoilers to keep track of such stuff) or attrmax was different - from abase (at end of game; this attribute wasn't maxed out) */ - abase = ABASE(attrindx); - apeak = AMAX(attrindx); - alimit = ATTRMAX(attrindx); - /* criterium for whether the limit is interesting varies */ - interesting_alimit = - final ? TRUE /* was originally `(abase != alimit)' */ - : (alimit != (attrindx != A_STR ? 18 : STR18(100))); - paren_pfx = final ? " (" : " (current; "; - if (acurrent != abase) { - Sprintf(eos(valubuf), "%sbase:%s", paren_pfx, - attrval(attrindx, abase, valstring)); - paren_pfx = ", "; - } - if (abase != apeak) { - Sprintf(eos(valubuf), "%speak:%s", paren_pfx, - attrval(attrindx, apeak, valstring)); - paren_pfx = ", "; - } - if (interesting_alimit) { - Sprintf(eos(valubuf), "%s%slimit:%s", paren_pfx, - /* more verbose if exceeding 'limit' due to magic bonus */ - (acurrent > alimit) ? "innate " : "", - attrval(attrindx, alimit, valstring)); - /* paren_pfx = ", "; */ - } - if (acurrent != abase || abase != apeak || interesting_alimit) - Strcat(valubuf, ")"); - } - enl_msg(subjbuf, "is ", "was ", valubuf, ""); -} - -/* status: selected obvious capabilities, assorted troubles */ -STATIC_OVL void -status_enlightenment(mode, final) -int mode; -int final; -{ - boolean magic = (mode & MAGICENLIGHTENMENT) ? TRUE : FALSE; - int cap, wtype; - char buf[BUFSZ], youtoo[BUFSZ]; - boolean Riding = (u.usteed - /* if hero dies while dismounting, u.usteed will still - be set; we want to ignore steed in that situation */ - && !(final == ENL_GAMEOVERDEAD - && !strcmp(killer.name, "riding accident"))); - const char *steedname = (!Riding ? (char *) 0 - : x_monnam(u.usteed, - u.usteed->mtame ? ARTICLE_YOUR : ARTICLE_THE, - (char *) 0, - (SUPPRESS_SADDLE | SUPPRESS_HALLUCINATION), - FALSE)); - - /*\ - * Status (many are abbreviated on bottom line; others are or - * should be discernible to the hero hence to the player) - \*/ - enlght_out(""); /* separator after title or characteristics */ - enlght_out(final ? "Final Status:" : "Current Status:"); - - Strcpy(youtoo, You_); - /* not a traditional status but inherently obvious to player; more - detail given below (attributes section) for magic enlightenment */ - if (Upolyd) { - Strcpy(buf, "transformed"); - if (ugenocided()) - Sprintf(eos(buf), " and %s %s inside", - final ? "felt" : "feel", udeadinside()); - you_are(buf, ""); - } - /* not a trouble, but we want to display riding status before maybe - reporting steed as trapped or hero stuck to cursed saddle */ - if (Riding) { - Sprintf(buf, "riding %s", steedname); - you_are(buf, ""); - Sprintf(eos(youtoo), "and %s ", steedname); - } - /* other movement situations that hero should always know */ - if (Levitation) { - if (Lev_at_will && magic) - you_are("levitating, at will", ""); - else - enl_msg(youtoo, are, were, "levitating", from_what(LEVITATION)); - } else if (Flying) { /* can only fly when not levitating */ - enl_msg(youtoo, are, were, "flying", from_what(FLYING)); - } - if (Underwater) { - you_are("underwater", ""); - } else if (u.uinwater) { - you_are(Swimming ? "swimming" : "in water", from_what(SWIMMING)); - } else if (walking_on_water()) { - /* show active Wwalking here, potential Wwalking elsewhere */ - Sprintf(buf, "walking on %s", - is_pool(u.ux, u.uy) ? "water" - : is_lava(u.ux, u.uy) ? "lava" - : surface(u.ux, u.uy)); /* catchall; shouldn't happen */ - you_are(buf, from_what(WWALKING)); - } - if (Upolyd && (u.uundetected || U_AP_TYPE != M_AP_NOTHING)) - youhiding(TRUE, final); - - /* internal troubles, mostly in the order that prayer ranks them */ - if (Stoned) { - if (final && (Stoned & I_SPECIAL)) - enlght_out(" You turned into stone."); - else - you_are("turning to stone", ""); - } - if (Slimed) { - if (final && (Slimed & I_SPECIAL)) - enlght_out(" You turned into slime."); - else - you_are("turning into slime", ""); - } - if (Strangled) { - if (u.uburied) { - you_are("buried", ""); - } else { - if (final && (Strangled & I_SPECIAL)) { - enlght_out(" You died from strangulation."); - } else { - Strcpy(buf, "being strangled"); - if (wizard) - Sprintf(eos(buf), " (%ld)", (Strangled & TIMEOUT)); - you_are(buf, from_what(STRANGLED)); - } + char buf2[QBUFSZ]; + struct Cmd_bind *bind = gc.Cmd.cmdbinds; + uchar keys[256]; + + (void) memset(keys, 0, sizeof(uchar) * 256); + + if (!sbuf) + win = create_nhwindow(NHW_TEXT); + + /* commands bound to different key */ + while (bind) { + keys[bind->key] = 1; + if (bind->userbind && bind->cmd && bind->cmd->key != bind->key) { + if ((bind->cmd->flags & CMD_PARAM) != 0) + Sprintf(buf, "BIND=%s:%s(%s)%s", key2txt(bind->key, buf2), + bind->cmd->ef_txt, + bind->param, + sbuf ? "\n" : ""); + else + Sprintf(buf, "BIND=%s:%s%s", key2txt(bind->key, buf2), + bind->cmd->ef_txt, + sbuf ? "\n" : ""); + if (sbuf) + strbuf_append(sbuf, buf); + else + putstr(win, 0, buf); } + bind = bind->next; } - if (Sick) { - /* the two types of sickness are lumped together; hero can be - afflicted by both but there is only one timeout; botl status - puts TermIll before FoodPois and death due to timeout reports - terminal illness if both are in effect, so do the same here */ - if (final && (Sick & I_SPECIAL)) { - Sprintf(buf, " %sdied from %s.", You_, /* has trailing space */ - (u.usick_type & SICK_NONVOMITABLE) - ? "terminal illness" : "food poisoning"); - enlght_out(buf); - } else { - /* unlike death due to sickness, report the two cases separately - because it is possible to cure one without curing the other */ - if (u.usick_type & SICK_NONVOMITABLE) - you_are("terminally sick from illness", ""); - if (u.usick_type & SICK_VOMITABLE) - you_are("terminally sick from food poisoning", ""); + + /* commands which should be bound to a key, but aren't */ + for (i = 0; i < extcmdlist_length; i++) { + struct ext_func_tab *ec = &extcmdlist[i]; + + if (ec->key && !keys[ec->key]) { + Sprintf(buf, "BIND=%s:nothing%s", key2txt(ec->key, buf2), + sbuf ? "\n" : ""); + if (sbuf) + strbuf_append(sbuf, buf); + else + putstr(win, 0, buf); } } - if (Vomiting) - you_are("nauseated", ""); - if (Stunned) - you_are("stunned", ""); - if (Confusion) - you_are("confused", ""); - if (Hallucination) - you_are("hallucinating", ""); - if (Blind) { - /* from_what() (currently wizard-mode only) checks !haseyes() - before u.uroleplay.blind, so we should too */ - Sprintf(buf, "%s blind", - !haseyes(youmonst.data) ? "innately" - : u.uroleplay.blind ? "permanently" - /* better phrasing desperately wanted... */ - : Blindfolded_only ? "deliberately" - : "temporarily"); - if (wizard && (Blinded & TIMEOUT) != 0L - && !u.uroleplay.blind && haseyes(youmonst.data)) - Sprintf(eos(buf), " (%ld)", (Blinded & TIMEOUT)); - /* !haseyes: avoid "you are innately blind innately" */ - you_are(buf, !haseyes(youmonst.data) ? "" : from_what(BLINDED)); - } - if (Deaf) - you_are("deaf", from_what(DEAF)); - - /* external troubles, more or less */ - if (Punished) { - if (uball) { - Sprintf(buf, "chained to %s", ansimpleoname(uball)); - } else { - impossible("Punished without uball?"); - Strcpy(buf, "punished"); - } - you_are(buf, ""); + if (!sbuf) { + display_nhwindow(win, TRUE); + destroy_nhwindow(win); } - if (u.utrap) { - char predicament[BUFSZ]; - struct trap *t; - boolean anchored = (u.utraptype == TT_BURIEDBALL); +} - if (anchored) { - Strcpy(predicament, "tethered to something buried"); - } else if (u.utraptype == TT_INFLOOR || u.utraptype == TT_LAVA) { - Sprintf(predicament, "stuck in %s", the(surface(u.ux, u.uy))); - } else { - Strcpy(predicament, "trapped"); - if ((t = t_at(u.ux, u.uy)) != 0) - Sprintf(eos(predicament), " in %s", - an(defsyms[trap_to_defsym(t->ttyp)].explanation)); - } - if (u.usteed) { /* not `Riding' here */ - Sprintf(buf, "%s%s ", anchored ? "you and " : "", steedname); - *buf = highc(*buf); - enl_msg(buf, (anchored ? "are " : "is "), - (anchored ? "were " : "was "), predicament, ""); - } else - you_are(predicament, ""); - } /* (u.utrap) */ - if (u.uswallow) { - Sprintf(buf, "swallowed by %s", a_monnam(u.ustuck)); - if (wizard) - Sprintf(eos(buf), " (%u)", u.uswldtim); - you_are(buf, ""); - } else if (u.ustuck) { - Sprintf(buf, "%s %s", - (Upolyd && sticks(youmonst.data)) ? "holding" : "held by", - a_monnam(u.ustuck)); - you_are(buf, ""); - } - if (Riding) { - struct obj *saddle = which_armor(u.usteed, W_SADDLE); +/* interactive key binding */ +staticfn void +handler_rebind_keys_add(boolean keyfirst) +{ + struct ext_func_tab *ec; + winid win; + anything any; + int i, npick; + menu_item *picks = (menu_item *) 0; + char buf[BUFSZ]; + char buf2[QBUFSZ]; + uchar key = '\0'; + int clr = NO_COLOR; - if (saddle && saddle->cursed) { - Sprintf(buf, "stuck to %s %s", s_suffix(steedname), - simpleonames(saddle)); - you_are(buf, ""); - } + if (keyfirst) { + pline("Bind which key? "); + key = pgetchar(); + + if (!key || key == '\033') + return; } - if (Wounded_legs) { - /* when mounted, Wounded_legs applies to steed rather than to - hero; we only report steed's wounded legs in wizard mode */ - if (u.usteed) { /* not `Riding' here */ - if (wizard && steedname) { - Strcpy(buf, steedname); - *buf = highc(*buf); - enl_msg(buf, " has", " had", " wounded legs", ""); - } + + win = create_nhwindow(NHW_MENU); + start_menu(win, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + + if (key) { + struct Cmd_bind *bind = cmdbind_get(key); + + if (bind && bind->cmd) { + Sprintf(buf, "Key '%s' is currently bound to \"%s\".", + key2txt(key, buf2), bind->cmd->ef_txt); } else { - Sprintf(buf, "wounded %s", makeplural(body_part(LEG))); - you_have(buf, ""); - } - } - if (Glib) { - Sprintf(buf, "slippery %s", fingers_or_gloves(TRUE)); - if (wizard) - Sprintf(eos(buf), " (%ld)", (Glib & TIMEOUT)); - you_have(buf, ""); - } - if (Fumbling) { - if (magic || cause_known(FUMBLING)) - enl_msg(You_, "fumble", "fumbled", "", from_what(FUMBLING)); - } - if (Sleepy) { - if (magic || cause_known(SLEEPY)) { - Strcpy(buf, from_what(SLEEPY)); - if (wizard) - Sprintf(eos(buf), " (%ld)", (HSleepy & TIMEOUT)); - enl_msg("You ", "fall", "fell", " asleep uncontrollably", buf); + Sprintf(buf, "Key '%s' is not bound to anything.", + key2txt(key, buf2)); } + add_menu_str(win, buf); + add_menu_str(win, ""); } - /* hunger/nutrition */ - if (Hunger) { - if (magic || cause_known(HUNGER)) - enl_msg(You_, "hunger", "hungered", " rapidly", - from_what(HUNGER)); - } - Strcpy(buf, hu_stat[u.uhs]); /* hunger status; omitted if "normal" */ - mungspaces(buf); /* strip trailing spaces */ - if (*buf) { - *buf = lowc(*buf); /* override capitalization */ - if (!strcmp(buf, "weak")) - Strcat(buf, " from severe hunger"); - else if (!strncmp(buf, "faint", 5)) /* fainting, fainted */ - Strcat(buf, " due to starvation"); - you_are(buf, ""); - } - /* encumbrance */ - if ((cap = near_capacity()) > UNENCUMBERED) { - const char *adj = "?_?"; /* (should always get overridden) */ - - Strcpy(buf, enc_stat[cap]); - *buf = lowc(*buf); - switch (cap) { - case SLT_ENCUMBER: - adj = "slightly"; - break; /* burdened */ - case MOD_ENCUMBER: - adj = "moderately"; - break; /* stressed */ - case HVY_ENCUMBER: - adj = "very"; - break; /* strained */ - case EXT_ENCUMBER: - adj = "extremely"; - break; /* overtaxed */ - case OVERLOADED: - adj = "not possible"; - break; - } - Sprintf(eos(buf), "; movement %s %s%s", !final ? "is" : "was", adj, - (cap < OVERLOADED) ? " slowed" : ""); - you_are(buf, ""); - } else { - /* last resort entry, guarantees Status section is non-empty - (no longer needed for that purpose since weapon status added; - still useful though) */ - you_are("unencumbered", ""); - } - - /* report being weaponless; distinguish whether gloves are worn */ - if (!uwep) { - you_are(uarmg ? "empty handed" /* gloves imply hands */ - : humanoid(youmonst.data) - /* hands but no weapon and no gloves */ - ? "bare handed" - /* alternate phrasing for paws or lack of hands */ - : "not wielding anything", - ""); - /* two-weaponing implies hands (can't be polymorphed) and - a weapon or wep-tool (not other odd stuff) in each hand */ - } else if (u.twoweap) { - you_are("wielding two weapons at once", ""); - /* report most weapons by their skill class (so a katana will be - described as a long sword, for instance; mattock and hook are - exceptions), or wielded non-weapon item by its object class */ - } else { - const char *what = weapon_descr(uwep); - if (!strcmpi(what, "armor") || !strcmpi(what, "food") - || !strcmpi(what, "venom")) - Sprintf(buf, "wielding some %s", what); - else - Sprintf(buf, "wielding %s", - (uwep->quan == 1L) ? an(what) : makeplural(what)); - you_are(buf, ""); - } - /* - * Skill with current weapon. Might help players who've never - * noticed #enhance or decided that it was pointless. - * - * TODO? Maybe merge wielding line and skill line into one sentence. - */ - if ((wtype = uwep_skill_type()) != P_NONE) { - char sklvlbuf[20]; - int sklvl = P_SKILL(wtype); - boolean hav = (sklvl != P_UNSKILLED && sklvl != P_SKILLED); + any.a_int = -1; + add_menu(win, &nul_glyphinfo, &any, '\0', 0, ATR_NONE, clr, + "nothing: unbind the key", + MENU_ITEMFLAGS_NONE); - if (sklvl == P_ISRESTRICTED) - Strcpy(sklvlbuf, "no"); - else - (void) lcase(skill_level_name(wtype, sklvlbuf)); - /* "you have no/basic/expert/master/grand-master skill with " - or "you are unskilled/skilled in " */ - Sprintf(buf, "%s %s %s", sklvlbuf, - hav ? "skill with" : "in", skill_name(wtype)); - if (can_advance(wtype, FALSE)) - Sprintf(eos(buf), " and %s that", - !final ? "can enhance" : "could have enhanced"); - if (hav) - you_have(buf, ""); - else - you_are(buf, ""); - } - /* report 'nudity' */ - if (!uarm && !uarmu && !uarmc && !uarms && !uarmg && !uarmf && !uarmh) { - if (u.uroleplay.nudist) - enl_msg(You_, "do", "did", " not wear any armor", ""); - else - you_are("not wearing any armor", ""); - } -} + add_menu_str(win, ""); -/* attributes: intrinsics and the like, other non-obvious capabilities */ -STATIC_OVL void -attributes_enlightenment(unused_mode, final) -int unused_mode UNUSED; -int final; -{ - static NEARDATA const char if_surroundings_permitted[] = - " if surroundings permitted"; - int ltmp, armpro; - char buf[BUFSZ]; + for (i = 0; i < extcmdlist_length; i++) { + ec = &extcmdlist[i]; - /*\ - * Attributes - \*/ - enlght_out(""); - enlght_out(final ? "Final Attributes:" : "Current Attributes:"); + if ((ec->flags & (MOVEMENTCMD|INTERNALCMD|CMD_NOT_AVAILABLE)) != 0) + continue; - if (u.uevent.uhand_of_elbereth) { - static const char *const hofe_titles[3] = { "the Hand of Elbereth", - "the Envoy of Balance", - "the Glory of Arioch" }; - you_are(hofe_titles[u.uevent.uhand_of_elbereth - 1], ""); + any.a_int = (i + 1); + Sprintf(buf, "%s: %s", ec->ef_txt, ec->ef_desc); + add_menu(win, &nul_glyphinfo, &any, '\0', 0, ATR_NONE, clr, buf, + MENU_ITEMFLAGS_NONE); } - - Sprintf(buf, "%s", piousness(TRUE, "aligned")); - if (u.ualign.record >= 0) - you_are(buf, ""); + if (key) + Sprintf(buf, "Bind '%s' to what command?", key2txt(key, buf2)); else - you_have(buf, ""); - - if (wizard) { - Sprintf(buf, " %d", u.ualign.record); - enl_msg("Your alignment ", "is", "was", buf, ""); - } - - /*** Resistances to troubles ***/ - if (Invulnerable) - you_are("invulnerable", from_what(INVULNERABLE)); - if (Antimagic) - you_are("magic-protected", from_what(ANTIMAGIC)); - if (Fire_resistance) - you_are("fire resistant", from_what(FIRE_RES)); - if (Cold_resistance) - you_are("cold resistant", from_what(COLD_RES)); - if (Sleep_resistance) - you_are("sleep resistant", from_what(SLEEP_RES)); - if (Disint_resistance) - you_are("disintegration-resistant", from_what(DISINT_RES)); - if (Shock_resistance) - you_are("shock resistant", from_what(SHOCK_RES)); - if (Poison_resistance) - you_are("poison resistant", from_what(POISON_RES)); - if (Acid_resistance) - you_are("acid resistant", from_what(ACID_RES)); - if (Drain_resistance) - you_are("level-drain resistant", from_what(DRAIN_RES)); - if (Sick_resistance) - you_are("immune to sickness", from_what(SICK_RES)); - if (Stone_resistance) - you_are("petrification resistant", from_what(STONE_RES)); - if (Halluc_resistance) - enl_msg(You_, "resist", "resisted", " hallucinations", - from_what(HALLUC_RES)); - if (u.uedibility) - you_can("recognize detrimental food", ""); - - /*** Vision and senses ***/ - if (!Blind && (Blinded || !haseyes(youmonst.data))) - you_can("see", from_what(-BLINDED)); /* Eyes of the Overworld */ - if (See_invisible) { - if (!Blind) - enl_msg(You_, "see", "saw", " invisible", from_what(SEE_INVIS)); - else - enl_msg(You_, "will see", "would have seen", - " invisible when not blind", from_what(SEE_INVIS)); - } - if (Blind_telepat) - you_are("telepathic", from_what(TELEPAT)); - if (Warning) - you_are("warned", from_what(WARNING)); - if (Warn_of_mon && context.warntype.obj) { - Sprintf(buf, "aware of the presence of %s", - (context.warntype.obj & M2_ORC) ? "orcs" - : (context.warntype.obj & M2_ELF) ? "elves" - : (context.warntype.obj & M2_DEMON) ? "demons" : something); - you_are(buf, from_what(WARN_OF_MON)); - } - if (Warn_of_mon && context.warntype.polyd) { - Sprintf(buf, "aware of the presence of %s", - ((context.warntype.polyd & (M2_HUMAN | M2_ELF)) - == (M2_HUMAN | M2_ELF)) - ? "humans and elves" - : (context.warntype.polyd & M2_HUMAN) - ? "humans" - : (context.warntype.polyd & M2_ELF) - ? "elves" - : (context.warntype.polyd & M2_ORC) - ? "orcs" - : (context.warntype.polyd & M2_DEMON) - ? "demons" - : "certain monsters"); - you_are(buf, ""); - } - if (Warn_of_mon && context.warntype.speciesidx >= LOW_PM) { - Sprintf(buf, "aware of the presence of %s", - makeplural(mons[context.warntype.speciesidx].mname)); - you_are(buf, from_what(WARN_OF_MON)); - } - if (Undead_warning) - you_are("warned of undead", from_what(WARN_UNDEAD)); - if (Searching) - you_have("automatic searching", from_what(SEARCHING)); - if (Clairvoyant) - you_are("clairvoyant", from_what(CLAIRVOYANT)); - else if ((HClairvoyant || EClairvoyant) && BClairvoyant) { - Strcpy(buf, from_what(-CLAIRVOYANT)); - if (!strncmp(buf, " because of ", 12)) - /* overwrite substring; strncpy doesn't add terminator */ - (void) strncpy(buf, " if not for ", 12); - enl_msg(You_, "could be", "could have been", " clairvoyant", buf); - } - if (Infravision) - you_have("infravision", from_what(INFRAVISION)); - if (Detect_monsters) - you_are("sensing the presence of monsters", ""); - if (u.umconf) - you_are("going to confuse monsters", ""); - - /*** Appearance and behavior ***/ - if (Adornment) { - int adorn = 0; - - if (uleft && uleft->otyp == RIN_ADORNMENT) - adorn += uleft->spe; - if (uright && uright->otyp == RIN_ADORNMENT) - adorn += uright->spe; - /* the sum might be 0 (+0 ring or two which negate each other); - that yields "you are charismatic" (which isn't pointless - because it potentially impacts seduction attacks) */ - Sprintf(buf, "%scharismatic", - (adorn > 0) ? "more " : (adorn < 0) ? "less " : ""); - you_are(buf, from_what(ADORNED)); - } - if (Invisible) - you_are("invisible", from_what(INVIS)); - else if (Invis) - you_are("invisible to others", from_what(INVIS)); - /* ordinarily "visible" is redundant; this is a special case for - the situation when invisibility would be an expected attribute */ - else if ((HInvis || EInvis) && BInvis) - you_are("visible", from_what(-INVIS)); - if (Displaced) - you_are("displaced", from_what(DISPLACED)); - if (Stealth) - you_are("stealthy", from_what(STEALTH)); - if (Aggravate_monster) - enl_msg("You aggravate", "", "d", " monsters", - from_what(AGGRAVATE_MONSTER)); - if (Conflict) - enl_msg("You cause", "", "d", " conflict", from_what(CONFLICT)); - - /*** Transportation ***/ - if (Jumping) - you_can("jump", from_what(JUMPING)); - if (Teleportation) - you_can("teleport", from_what(TELEPORT)); - if (Teleport_control) - you_have("teleport control", from_what(TELEPORT_CONTROL)); - /* actively levitating handled earlier as a status condition */ - if (BLevitation) { /* levitation is blocked */ - long save_BLev = BLevitation; - - BLevitation = 0L; - if (Levitation) { - /* either trapped in the floor or inside solid rock - (or both if chained to buried iron ball and have - moved one step into solid rock somehow) */ - boolean trapped = (save_BLev & I_SPECIAL) != 0L, - terrain = (save_BLev & FROMOUTSIDE) != 0L; - - Sprintf(buf, "%s%s%s", - trapped ? " if not trapped" : "", - (trapped && terrain) ? " and" : "", - terrain ? if_surroundings_permitted : ""); - enl_msg(You_, "would levitate", "would have levitated", buf, ""); - } - BLevitation = save_BLev; - } - /* actively flying handled earlier as a status condition */ - if (BFlying) { /* flight is blocked */ - long save_BFly = BFlying; - - BFlying = 0L; - if (Flying) { - enl_msg(You_, "would fly", "would have flown", - /* wording quibble: for past tense, "hadn't been" - would sound better than "weren't" (and - "had permitted" better than "permitted"), but - "weren't" and "permitted" are adequate so the - extra complexity to handle that isn't worth it */ - Levitation - ? " if you weren't levitating" - : (save_BFly == I_SPECIAL) - /* this is an oversimpliction; being trapped - might also be blocking levitation so flight - would still be blocked after escaping trap */ - ? " if you weren't trapped" - : (save_BFly == FROMOUTSIDE) - ? if_surroundings_permitted - /* two or more of levitation, surroundings, - and being trapped in the floor */ - : " if circumstances permitted", - ""); - } - BFlying = save_BFly; - } - /* actively walking on water handled earlier as a status condition */ - if (Wwalking && !walking_on_water()) - you_can("walk on water", from_what(WWALKING)); - /* actively swimming (in water but not under it) handled earlier */ - if (Swimming && (Underwater || !u.uinwater)) - you_can("swim", from_what(SWIMMING)); - if (Breathless) - you_can("survive without air", from_what(MAGICAL_BREATHING)); - else if (Amphibious) - you_can("breathe water", from_what(MAGICAL_BREATHING)); - if (Passes_walls) - you_can("walk through walls", from_what(PASSES_WALLS)); - - /*** Physical attributes ***/ - if (Regeneration) - enl_msg("You regenerate", "", "d", "", from_what(REGENERATION)); - if (Slow_digestion) - you_have("slower digestion", from_what(SLOW_DIGESTION)); - if (u.uhitinc) - you_have(enlght_combatinc("to hit", u.uhitinc, final, buf), ""); - if (u.udaminc) - you_have(enlght_combatinc("damage", u.udaminc, final, buf), ""); - if (u.uspellprot || Protection) { - int prot = 0; - - if (uleft && uleft->otyp == RIN_PROTECTION) - prot += uleft->spe; - if (uright && uright->otyp == RIN_PROTECTION) - prot += uright->spe; - if (HProtection & INTRINSIC) - prot += u.ublessed; - prot += u.uspellprot; - if (prot) - you_have(enlght_combatinc("defense", prot, final, buf), ""); - } - if ((armpro = magic_negation(&youmonst)) > 0) { - /* magic cancellation factor, conferred by worn armor */ - static const char *const mc_types[] = { - "" /*ordinary*/, "warded", "guarded", "protected", - }; - /* sanity check */ - if (armpro >= SIZE(mc_types)) - armpro = SIZE(mc_types) - 1; - you_are(mc_types[armpro], ""); - } - if (Half_physical_damage) - enlght_halfdmg(HALF_PHDAM, final); - if (Half_spell_damage) - enlght_halfdmg(HALF_SPDAM, final); - /* polymorph and other shape change */ - if (Protection_from_shape_changers) - you_are("protected from shape changers", - from_what(PROT_FROM_SHAPE_CHANGERS)); - if (Unchanging) { - const char *what = 0; - - if (!Upolyd) /* Upolyd handled below after current form */ - you_can("not change from your current form", - from_what(UNCHANGING)); - /* blocked shape changes */ - if (Polymorph) - what = !final ? "polymorph" : "have polymorphed"; - else if (u.ulycn >= LOW_PM) - what = !final ? "change shape" : "have changed shape"; - if (what) { - Sprintf(buf, "would %s periodically", what); - /* omit from_what(UNCHANGING); too verbose */ - enl_msg(You_, buf, buf, " if not locked into your current form", - ""); - } - } else if (Polymorph) { - you_are("polymorphing periodically", from_what(POLYMORPH)); - } - if (Polymorph_control) - you_have("polymorph control", from_what(POLYMORPH_CONTROL)); - if (Upolyd && u.umonnum != u.ulycn - /* if we've died from turning into slime, we're polymorphed - right now but don't want to list it as a temporary attribute - [we need a more reliable way to detect this situation] */ - && !(final == ENL_GAMEOVERDEAD - && u.umonnum == PM_GREEN_SLIME && !Unchanging)) { - /* foreign shape (except were-form which is handled below) */ - Sprintf(buf, "polymorphed into %s", an(youmonst.data->mname)); - if (wizard) - Sprintf(eos(buf), " (%d)", u.mtimedone); - you_are(buf, ""); - } - if (lays_eggs(youmonst.data) && flags.female) /* Upolyd */ - you_can("lay eggs", ""); - if (u.ulycn >= LOW_PM) { - /* "you are a werecreature [in beast form]" */ - Strcpy(buf, an(mons[u.ulycn].mname)); - if (u.umonnum == u.ulycn) { - Strcat(buf, " in beast form"); - if (wizard) - Sprintf(eos(buf), " (%d)", u.mtimedone); - } - you_are(buf, ""); - } - if (Unchanging && Upolyd) /* !Upolyd handled above */ - you_can("not change from your current form", from_what(UNCHANGING)); - if (Hate_silver) - you_are("harmed by silver", ""); - /* movement and non-armor-based protection */ - if (Fast) - you_are(Very_fast ? "very fast" : "fast", from_what(FAST)); - if (Reflecting) - you_have("reflection", from_what(REFLECTING)); - if (Free_action) - you_have("free action", from_what(FREE_ACTION)); - if (Fixed_abil) - you_have("fixed abilities", from_what(FIXED_ABIL)); - if (Lifesaved) - enl_msg("Your life ", "will be", "would have been", " saved", ""); - - /*** Miscellany ***/ - if (Luck) { - ltmp = abs((int) Luck); - Sprintf(buf, "%s%slucky", - ltmp >= 10 ? "extremely " : ltmp >= 5 ? "very " : "", - Luck < 0 ? "un" : ""); - if (wizard) - Sprintf(eos(buf), " (%d)", Luck); - you_are(buf, ""); - } else if (wizard) - enl_msg("Your luck ", "is", "was", " zero", ""); - if (u.moreluck > 0) - you_have("extra luck", ""); - else if (u.moreluck < 0) - you_have("reduced luck", ""); - if (carrying(LUCKSTONE) || stone_luck(TRUE)) { - ltmp = stone_luck(FALSE); - if (ltmp <= 0) - enl_msg("Bad luck ", "does", "did", " not time out for you", ""); - if (ltmp >= 0) - enl_msg("Good luck ", "does", "did", " not time out for you", ""); - } - - if (u.ugangr) { - Sprintf(buf, " %sangry with you", - u.ugangr > 6 ? "extremely " : u.ugangr > 3 ? "very " : ""); - if (wizard) - Sprintf(eos(buf), " (%d)", u.ugangr); - enl_msg(u_gname(), " is", " was", buf, ""); - } else { - /* - * We need to suppress this when the game is over, because death - * can change the value calculated by can_pray(), potentially - * resulting in a false claim that you could have prayed safely. - */ - if (!final) { -#if 0 - /* "can [not] safely pray" vs "could [not] have safely prayed" */ - Sprintf(buf, "%s%ssafely pray%s", can_pray(FALSE) ? "" : "not ", - final ? "have " : "", final ? "ed" : ""); -#else - Sprintf(buf, "%ssafely pray", can_pray(FALSE) ? "" : "not "); -#endif - if (wizard) - Sprintf(eos(buf), " (%d)", u.ublesscnt); - you_can(buf, ""); + Sprintf(buf, "Bind what command?"); + end_menu(win, buf); + npick = select_menu(win, PICK_ONE, &picks); + destroy_nhwindow(win); + if (npick > 0) { + struct Cmd_bind *prevcmd; + char cmdstr[BUFSZ]; + + i = picks->item.a_int; + free((genericptr_t) picks); + + if (i == -1) { + ec = NULL; + Strcat(cmdstr, "nothing"); + goto bindit; + } else { + ec = &extcmdlist[i-1]; + + if ((ec->flags & CMD_PARAM) != 0) { + char parambuf[BUFSZ]; + char querybuf[BUFSZ]; + + parambuf[0] = '\0'; + Sprintf(querybuf, "Command %s requires a parameter:", ec->ef_txt); + getlin(querybuf, parambuf); + (void) mungspaces(parambuf); + Snprintf(cmdstr, BUFSZ-1, "%s(%s)", ec->ef_txt, parambuf); + cmdstr[BUFSZ-1] = '\0'; + } else { + Strcat(cmdstr, ec->ef_txt); + } } - } + bindit: + if (!key) { + pline("Bind which key? "); + key = pgetchar(); -#ifdef DEBUG - /* named fruit debugging (doesn't really belong here...); to enable, - include 'fruit' in DEBUGFILES list (even though it isn't a file...) */ - if (wizard && explicitdebug("fruit")) { - struct fruit *f; - - reorder_fruit(TRUE); /* sort by fruit index, from low to high; - * this modifies the ffruit chain, so could - * possibly mask or even introduce a problem, - * but it does useful sanity checking */ - for (f = ffruit; f; f = f->nextf) { - Sprintf(buf, "Fruit #%d ", f->fid); - enl_msg(buf, "is ", "was ", f->fname, ""); + if (!key || key == '\033') + return; } - enl_msg("The current fruit ", "is ", "was ", pl_fruit, ""); - Sprintf(buf, "%d", flags.made_fruit); - enl_msg("The made fruit flag ", "is ", "was ", buf, ""); - } -#endif - { - const char *p; + prevcmd = cmdbind_get(key); - buf[0] = '\0'; - if (final < 2) { /* still in progress, or quit/escaped/ascended */ - p = "survived after being killed "; - switch (u.umortality) { - case 0: - p = !final ? (char *) 0 : "survived"; - break; - case 1: - Strcpy(buf, "once"); - break; - case 2: - Strcpy(buf, "twice"); - break; - case 3: - Strcpy(buf, "thrice"); - break; - default: - Sprintf(buf, "%d times", u.umortality); - break; - } - } else { /* game ended in character's death */ - p = "are dead"; - switch (u.umortality) { - case 0: - impossible("dead without dying?"); - case 1: - break; /* just "are dead" */ - default: - Sprintf(buf, " (%d%s time!)", u.umortality, - ordin(u.umortality)); - break; + if (bind_key(key, cmdstr, TRUE)) { + if (prevcmd && prevcmd->cmd != ec) { + pline("Changed key '%s' from \"%s\" to \"%s\".", + key2txt(key, buf2), prevcmd->cmd->ef_txt, cmdstr); + } else if (!prevcmd) { + pline("Bound key '%s' to \"%s\".", + key2txt(key, buf2), cmdstr); } + } else { + pline("Key binding failed?!"); } - if (p) - enl_msg(You_, "have been killed ", p, buf, ""); } } -#if 0 /* no longer used */ -STATIC_DCL boolean NDECL(minimal_enlightenment); - -/* - * Courtesy function for non-debug, non-explorer mode players - * to help refresh them about who/what they are. - * Returns FALSE if menu cancelled (dismissed with ESC), TRUE otherwise. - */ -STATIC_OVL boolean -minimal_enlightenment() +void +handler_rebind_keys(void) { - winid tmpwin; - menu_item *selected; + winid win; anything any; - int genidx, n; - char buf[BUFSZ], buf2[BUFSZ]; - static const char untabbed_fmtstr[] = "%-15s: %-12s"; - static const char untabbed_deity_fmtstr[] = "%-17s%s"; - static const char tabbed_fmtstr[] = "%s:\t%-12s"; - static const char tabbed_deity_fmtstr[] = "%s\t%s"; - static const char *fmtstr; - static const char *deity_fmtstr; - - fmtstr = iflags.menu_tab_sep ? tabbed_fmtstr : untabbed_fmtstr; - deity_fmtstr = iflags.menu_tab_sep ? tabbed_deity_fmtstr - : untabbed_deity_fmtstr; - any = zeroany; - buf[0] = buf2[0] = '\0'; - tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, iflags.menu_headings, - "Starting", FALSE); - - /* Starting name, race, role, gender */ - Sprintf(buf, fmtstr, "name", plname); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE); - Sprintf(buf, fmtstr, "race", urace.noun); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE); - Sprintf(buf, fmtstr, "role", - (flags.initgend && urole.name.f) ? urole.name.f : urole.name.m); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE); - Sprintf(buf, fmtstr, "gender", genders[flags.initgend].adj); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE); - - /* Starting alignment */ - Sprintf(buf, fmtstr, "alignment", align_str(u.ualignbase[A_ORIGINAL])); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE); - - /* Current name, race, role, gender */ - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", FALSE); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, iflags.menu_headings, - "Current", FALSE); - Sprintf(buf, fmtstr, "race", Upolyd ? youmonst.data->mname : urace.noun); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE); - if (Upolyd) { - Sprintf(buf, fmtstr, "role (base)", - (u.mfemale && urole.name.f) ? urole.name.f - : urole.name.m); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE); - } else { - Sprintf(buf, fmtstr, "role", - (flags.female && urole.name.f) ? urole.name.f - : urole.name.m); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE); - } - /* don't want poly_gender() here; it forces `2' for non-humanoids */ - genidx = is_neuter(youmonst.data) ? 2 : flags.female; - Sprintf(buf, fmtstr, "gender", genders[genidx].adj); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE); - if (Upolyd && (int) u.mfemale != genidx) { - Sprintf(buf, fmtstr, "gender (base)", genders[u.mfemale].adj); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE); - } - - /* Current alignment */ - Sprintf(buf, fmtstr, "alignment", align_str(u.ualign.type)); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE); - - /* Deity list */ - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", FALSE); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, iflags.menu_headings, - "Deities", FALSE); - Sprintf(buf2, deity_fmtstr, align_gname(A_CHAOTIC), - (u.ualignbase[A_ORIGINAL] == u.ualign.type - && u.ualign.type == A_CHAOTIC) ? " (s,c)" - : (u.ualignbase[A_ORIGINAL] == A_CHAOTIC) ? " (s)" - : (u.ualign.type == A_CHAOTIC) ? " (c)" : ""); - Sprintf(buf, fmtstr, "Chaotic", buf2); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE); - - Sprintf(buf2, deity_fmtstr, align_gname(A_NEUTRAL), - (u.ualignbase[A_ORIGINAL] == u.ualign.type - && u.ualign.type == A_NEUTRAL) ? " (s,c)" - : (u.ualignbase[A_ORIGINAL] == A_NEUTRAL) ? " (s)" - : (u.ualign.type == A_NEUTRAL) ? " (c)" : ""); - Sprintf(buf, fmtstr, "Neutral", buf2); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE); - - Sprintf(buf2, deity_fmtstr, align_gname(A_LAWFUL), - (u.ualignbase[A_ORIGINAL] == u.ualign.type - && u.ualign.type == A_LAWFUL) ? " (s,c)" - : (u.ualignbase[A_ORIGINAL] == A_LAWFUL) ? " (s)" - : (u.ualign.type == A_LAWFUL) ? " (c)" : ""); - Sprintf(buf, fmtstr, "Lawful", buf2); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE); - - end_menu(tmpwin, "Base Attributes"); - n = select_menu(tmpwin, PICK_NONE, &selected); - destroy_nhwindow(tmpwin); - return (boolean) (n != -1); -} -#endif /*0*/ - -/* ^X command */ -STATIC_PTR int -doattributes(VOID_ARGS) -{ - int mode = BASICENLIGHTENMENT; - - /* show more--as if final disclosure--for wizard and explore modes */ - if (wizard || discover) - mode |= MAGICENLIGHTENMENT; - - enlightenment(mode, ENL_GAMEINPROGRESS); - return 0; -} - -void -youhiding(via_enlghtmt, msgflag) -boolean via_enlghtmt; /* englightment line vs topl message */ -int msgflag; /* for variant message phrasing */ -{ - char *bp, buf[BUFSZ]; - - Strcpy(buf, "hiding"); - if (U_AP_TYPE != M_AP_NOTHING) { - /* mimic; hero is only able to mimic a strange object or gold - or hallucinatory alternative to gold, so we skip the details - for the hypothetical furniture and monster cases */ - bp = eos(strcpy(buf, "mimicking")); - if (U_AP_TYPE == M_AP_OBJECT) { - Sprintf(bp, " %s", an(simple_typename(youmonst.mappearance))); - } else if (U_AP_TYPE == M_AP_FURNITURE) { - Strcpy(bp, " something"); - } else if (U_AP_TYPE == M_AP_MONSTER) { - Strcpy(bp, " someone"); - } else { - ; /* something unexpected; leave 'buf' as-is */ - } - } else if (u.uundetected) { - bp = eos(buf); /* points past "hiding" */ - if (youmonst.data->mlet == S_EEL) { - if (is_pool(u.ux, u.uy)) - Sprintf(bp, " in the %s", waterbody_name(u.ux, u.uy)); - } else if (hides_under(youmonst.data)) { - struct obj *o = level.objects[u.ux][u.uy]; - - if (o) - Sprintf(bp, " underneath %s", ansimpleoname(o)); - } else if (is_clinger(youmonst.data) || Flying) { - /* Flying: 'lurker above' hides on ceiling but doesn't cling */ - Sprintf(bp, " on the %s", ceiling(u.ux, u.uy)); - } else { - /* on floor; is_hider() but otherwise not special: 'trapper' */ - if (u.utrap && u.utraptype == TT_PIT) { - struct trap *t = t_at(u.ux, u.uy); + int i, npick; + menu_item *picks = (menu_item *) 0; + int clr = NO_COLOR; - Sprintf(bp, " in a %spit", - (t && t->ttyp == SPIKED_PIT) ? "spiked " : ""); - } else - Sprintf(bp, " on the %s", surface(u.ux, u.uy)); - } - } else { - ; /* shouldn't happen; will result in generic "you are hiding" */ - } + redo_rebind: + win = create_nhwindow(NHW_MENU); + start_menu(win, MENU_BEHAVE_STANDARD); + any = cg.zeroany; - if (via_enlghtmt) { - int final = msgflag; /* 'final' is used by you_are() macro */ + any.a_int = 1; + add_menu(win, &nul_glyphinfo, &any, '\0', 0, ATR_NONE, clr, + "bind key to a command", MENU_ITEMFLAGS_NONE); + any.a_int = 2; + add_menu(win, &nul_glyphinfo, &any, '\0', 0, ATR_NONE, clr, + "bind command to a key", MENU_ITEMFLAGS_NONE); + if (count_bind_keys()) { + any.a_int = 3; + add_menu(win, &nul_glyphinfo, &any, '\0', 0, ATR_NONE, clr, + "view changed key binds", MENU_ITEMFLAGS_NONE); + } + end_menu(win, "Do what?"); + npick = select_menu(win, PICK_ONE, &picks); + destroy_nhwindow(win); + if (npick > 0) { + i = picks->item.a_int; + free((genericptr_t) picks); - you_are(buf, ""); - } else { - /* for dohide(), when player uses '#monster' command */ - You("are %s %s.", msgflag ? "already" : "now", buf); + if (i == 1 || i == 2) { + handler_rebind_keys_add((i == 1)); + } else if (i == 3) { + get_changed_key_binds(NULL); + } + goto redo_rebind; } } -/* KMH, #conduct - * (shares enlightenment's tense handling) - */ -int -doconduct(VOID_ARGS) -{ - show_conduct(0); - return 0; -} - void -show_conduct(final) -int final; +handler_change_autocompletions(void) { + winid win; + anything any; + int i, n; + menu_item *picks = (menu_item *) 0; + int clr = NO_COLOR; + struct ext_func_tab *ec; char buf[BUFSZ]; - int ngenocided; - - /* Create the conduct window */ - en_win = create_nhwindow(NHW_MENU); - putstr(en_win, 0, "Voluntary challenges:"); - - if (u.uroleplay.blind) - you_have_been("blind from birth"); - if (u.uroleplay.nudist) - you_have_been("faithfully nudist"); - - if (!u.uconduct.food) - enl_msg(You_, "have gone", "went", " without food", ""); - /* but beverages are okay */ - else if (!u.uconduct.unvegan) - you_have_X("followed a strict vegan diet"); - else if (!u.uconduct.unvegetarian) - you_have_been("vegetarian"); - - if (!u.uconduct.gnostic) - you_have_been("an atheist"); - - if (!u.uconduct.weaphit) { - you_have_never("hit with a wielded weapon"); - } else if (wizard) { - Sprintf(buf, "used a wielded weapon %ld time%s", u.uconduct.weaphit, - plur(u.uconduct.weaphit)); - you_have_X(buf); - } - if (!u.uconduct.killer) - you_have_been("a pacifist"); - - if (!u.uconduct.literate) { - you_have_been("illiterate"); - } else if (wizard) { - Sprintf(buf, "read items or engraved %ld time%s", u.uconduct.literate, - plur(u.uconduct.literate)); - you_have_X(buf); - } - - ngenocided = num_genocides(); - if (ngenocided == 0) { - you_have_never("genocided any monsters"); - } else { - Sprintf(buf, "genocided %d type%s of monster%s", ngenocided, - plur(ngenocided), plur(ngenocided)); - you_have_X(buf); - } - if (!u.uconduct.polypiles) { - you_have_never("polymorphed an object"); - } else if (wizard) { - Sprintf(buf, "polymorphed %ld item%s", u.uconduct.polypiles, - plur(u.uconduct.polypiles)); - you_have_X(buf); - } - - if (!u.uconduct.polyselfs) { - you_have_never("changed form"); - } else if (wizard) { - Sprintf(buf, "changed form %ld time%s", u.uconduct.polyselfs, - plur(u.uconduct.polyselfs)); - you_have_X(buf); - } + win = create_nhwindow(NHW_MENU); + start_menu(win, MENU_BEHAVE_STANDARD); + any = cg.zeroany; - if (!u.uconduct.wishes) { - you_have_X("used no wishes"); - } else { - Sprintf(buf, "used %ld wish%s", u.uconduct.wishes, - (u.uconduct.wishes > 1L) ? "es" : ""); - if (u.uconduct.wisharti) { - /* if wisharti == wishes - * 1 wish (for an artifact) - * 2 wishes (both for artifacts) - * N wishes (all for artifacts) - * else (N is at least 2 in order to get here; M < N) - * N wishes (1 for an artifact) - * N wishes (M for artifacts) - */ - if (u.uconduct.wisharti == u.uconduct.wishes) - Sprintf(eos(buf), " (%s", - (u.uconduct.wisharti > 2L) ? "all " - : (u.uconduct.wisharti == 2L) ? "both " : ""); - else - Sprintf(eos(buf), " (%ld ", u.uconduct.wisharti); + for (i = 0; i < extcmdlist_length; i++) { + ec = &extcmdlist[i]; - Sprintf(eos(buf), "for %s)", - (u.uconduct.wisharti == 1L) ? "an artifact" - : "artifacts"); - } - you_have_X(buf); + if ((ec->flags & (INTERNALCMD|CMD_NOT_AVAILABLE)) != 0) + continue; + if (strlen(ec->ef_txt) < 2) + continue; - if (!u.uconduct.wisharti) - enl_msg(You_, "have not wished", "did not wish", - " for any artifacts", ""); + any.a_int = (i + 1); + Sprintf(buf, "%c %s: %s", + (ec->flags & AUTOCOMP_ADJ) ? '*' : ' ', + ec->ef_txt, ec->ef_desc); + add_menu(win, &nul_glyphinfo, &any, '\0', 0, ATR_NONE, clr, buf, + (ec->flags & AUTOCOMPLETE) + ? MENU_ITEMFLAGS_SELECTED : + MENU_ITEMFLAGS_NONE); } - /* Pop up the window and wait for a key */ - display_nhwindow(en_win, TRUE); - destroy_nhwindow(en_win); - en_win = WIN_ERR; -} + end_menu(win, "Which commands autocomplete?"); + n = select_menu(win, PICK_ANY, &picks); + if (n >= 0) { + int j; -int nle_dosave() { - pline("You get the feeling there's only one way to save yourself..."); - return 1; -} + for (i = 0; i < extcmdlist_length; i++) { + boolean setit = FALSE; -int nle_done2() { - pline("You can't quit now, you're having so much fun!"); - return 1; -} + ec = &extcmdlist[i]; -int nle_doset() { - pline("The options are already set perfectly for you!"); - return 1; -} + if ((ec->flags & (INTERNALCMD|CMD_NOT_AVAILABLE)) != 0) + continue; + if (strlen(ec->ef_txt) < 2) + continue; -int nle_noop() { - pline("Noop"); - return 1; -} + Sprintf(buf, "%s", ec->ef_txt); -/* ordered by command name */ -struct ext_func_tab extcmdlist[] = { - { '#', "#", "perform an extended command", - doextcmd, IFBURIED | GENERALCMD }, - { M('?'), "?", "list all extended commands", - doextlist, IFBURIED | AUTOCOMPLETE | GENERALCMD }, - { M('a'), "adjust", "adjust inventory letters", - doorganize, IFBURIED | AUTOCOMPLETE }, - { M('A'), "annotate", "name current level", - donamelevel, IFBURIED | AUTOCOMPLETE }, - { 'a', "apply", "apply (use) a tool (pick-axe, key, lamp...)", - doapply }, - { C('x'), "attributes", "show your attributes", - doattributes, IFBURIED }, - { '@', "autopickup", "toggle the pickup option on/off", - dotogglepickup, IFBURIED }, - { 'C', "call", "call (name) something", docallcmd, IFBURIED }, - { 'Z', "cast", "zap (cast) a spell", docast, IFBURIED }, - { M('c'), "chat", "talk to someone", dotalk, IFBURIED | AUTOCOMPLETE }, - { 'c', "close", "close a door", doclose }, - { M('C'), "conduct", "list voluntary challenges you have maintained", - doconduct, IFBURIED | AUTOCOMPLETE }, - { M('d'), "dip", "dip an object into something", dodip, AUTOCOMPLETE }, - { '>', "down", "go down a staircase", dodown }, - { 'd', "drop", "drop an item", dodrop }, - { 'D', "droptype", "drop specific item types", doddrop }, - { 'e', "eat", "eat something", doeat }, - { 'E', "engrave", "engrave writing on the floor", doengrave }, - { M('e'), "enhance", "advance or check weapon and spell skills", - enhance_weapon_skill, IFBURIED | AUTOCOMPLETE }, - { '\0', "exploremode", "enter explore (discovery) mode", - enter_explore_mode, IFBURIED }, - { 'f', "fire", "fire ammunition from quiver", dofire }, - { M('f'), "force", "force a lock", doforce, AUTOCOMPLETE }, - { ';', "glance", "show what type of thing a map symbol corresponds to", - doquickwhatis, IFBURIED | GENERALCMD }, - { '?', "help", "give a help message", dohelp, IFBURIED | GENERALCMD }, - { '\0', "herecmdmenu", "show menu of commands you can do here", - doherecmdmenu, IFBURIED }, - { 'V', "history", "show long version and game history", - nle_noop /* dohistory */, IFBURIED | GENERALCMD }, - { 'i', "inventory", "show your inventory", ddoinv, IFBURIED }, - { 'I', "inventtype", "inventory specific item types", - dotypeinv, IFBURIED }, - { M('i'), "invoke", "invoke an object's special powers", - doinvoke, IFBURIED | AUTOCOMPLETE }, - { M('j'), "jump", "jump to another location", dojump, AUTOCOMPLETE }, - { C('d'), "kick", "kick something", dokick }, - { '\\', "known", "show what object types have been discovered", - dodiscovered, IFBURIED | GENERALCMD }, - { '`', "knownclass", "show discovered types for one class of objects", - doclassdisco, IFBURIED | GENERALCMD }, - { '\0', "levelchange", "change experience level", - wiz_level_change, IFBURIED | AUTOCOMPLETE | WIZMODECMD }, - { '\0', "lightsources", "show mobile light sources", - wiz_light_sources, IFBURIED | AUTOCOMPLETE | WIZMODECMD }, - { ':', "look", "look at what is here", dolook, IFBURIED }, - { M('l'), "loot", "loot a box on the floor", doloot, AUTOCOMPLETE }, -#ifdef DEBUG_MIGRATING_MONS - { '\0', "migratemons", "migrate N random monsters", - wiz_migrate_mons, IFBURIED | AUTOCOMPLETE | WIZMODECMD }, -#endif - { M('m'), "monster", "use monster's special ability", - domonability, IFBURIED | AUTOCOMPLETE }, - { 'N', "name", "name a monster or an object", - docallcmd, IFBURIED | AUTOCOMPLETE }, - { M('o'), "offer", "offer a sacrifice to the gods", - dosacrifice, AUTOCOMPLETE }, - { 'o', "open", "open a door", doopen }, - { 'O', "options", "show option settings, possibly change them", - nle_doset /* doset */, IFBURIED | GENERALCMD }, - { C('o'), "overview", "show a summary of the explored dungeon", - dooverview, IFBURIED | AUTOCOMPLETE }, - { '\0', "panic", "test panic routine (fatal to game)", - wiz_panic, IFBURIED | AUTOCOMPLETE | WIZMODECMD }, - { 'p', "pay", "pay your shopping bill", dopay }, - { ',', "pickup", "pick up things at the current location", dopickup }, - { '\0', "polyself", "polymorph self", - wiz_polyself, IFBURIED | AUTOCOMPLETE | WIZMODECMD }, - { M('p'), "pray", "pray to the gods for help", - dopray, IFBURIED | AUTOCOMPLETE }, - { C('p'), "prevmsg", "view recent game messages", - nle_noop /* doprev_message */, IFBURIED | GENERALCMD }, - { 'P', "puton", "put on an accessory (ring, amulet, etc)", doputon }, - { 'q', "quaff", "quaff (drink) something", dodrink }, - { M('q'), "quit", "exit without saving current game", - done2, IFBURIED | AUTOCOMPLETE | GENERALCMD }, - { 'Q', "quiver", "select ammunition for quiver", dowieldquiver }, - { 'r', "read", "read a scroll or spellbook", doread }, - { C('r'), "redraw", "redraw screen", doredraw, IFBURIED | GENERALCMD }, - { 'R', "remove", "remove an accessory (ring, amulet, etc)", doremring }, - { M('R'), "ride", "mount or dismount a saddled steed", - doride, AUTOCOMPLETE }, - { M('r'), "rub", "rub a lamp or a stone", dorub, AUTOCOMPLETE }, - { 'S', "save", "save the game and exit", nle_dosave /* dosave */, IFBURIED | GENERALCMD }, - { 's', "search", "search for traps and secret doors", - dosearch, IFBURIED, "searching" }, - { '*', "seeall", "show all equipment in use", doprinuse, IFBURIED }, - { AMULET_SYM, "seeamulet", "show the amulet currently worn", - dopramulet, IFBURIED }, - { ARMOR_SYM, "seearmor", "show the armor currently worn", - doprarm, IFBURIED }, - { GOLD_SYM, "seegold", "count your gold", doprgold, IFBURIED }, - { '\0', "seenv", "show seen vectors", - wiz_show_seenv, IFBURIED | AUTOCOMPLETE | WIZMODECMD }, - { RING_SYM, "seerings", "show the ring(s) currently worn", - doprring, IFBURIED }, - { SPBOOK_SYM, "seespells", "list and reorder known spells", - dovspell, IFBURIED }, - { TOOL_SYM, "seetools", "show the tools currently in use", - doprtool, IFBURIED }, - { '^', "seetrap", "show the type of adjacent trap", doidtrap, IFBURIED }, - { WEAPON_SYM, "seeweapon", "show the weapon currently wielded", - doprwep, IFBURIED }, - { '!', "shell", "do a shell escape", - dosh_core, IFBURIED | GENERALCMD -#ifndef SHELL - | CMD_NOT_AVAILABLE -#endif /* SHELL */ - }, - { M('s'), "sit", "sit down", dosit, AUTOCOMPLETE }, - { '\0', "stats", "show memory statistics", - wiz_show_stats, IFBURIED | AUTOCOMPLETE | WIZMODECMD }, - { C('z'), "suspend", "suspend the game", - dosuspend_core, IFBURIED | GENERALCMD -#ifndef SUSPEND - | CMD_NOT_AVAILABLE -#endif /* SUSPEND */ - }, - { 'x', "swap", "swap wielded and secondary weapons", doswapweapon }, - { 'T', "takeoff", "take off one piece of armor", dotakeoff }, - { 'A', "takeoffall", "remove all armor", doddoremarm }, - { C('t'), "teleport", "teleport around the level", dotelecmd, IFBURIED }, - { '\0', "terrain", "show map without obstructions", - doterrain, IFBURIED | AUTOCOMPLETE }, - { '\0', "therecmdmenu", - "menu of commands you can do from here to adjacent spot", - dotherecmdmenu }, - { 't', "throw", "throw something", dothrow }, - { '\0', "timeout", "look at timeout queue and hero's timed intrinsics", - wiz_timeout_queue, IFBURIED | AUTOCOMPLETE | WIZMODECMD }, - { M('T'), "tip", "empty a container", dotip, AUTOCOMPLETE }, - { '_', "travel", "travel to a specific location on the map", dotravel }, - { M('t'), "turn", "turn undead away", doturn, IFBURIED | AUTOCOMPLETE }, - { 'X', "twoweapon", "toggle two-weapon combat", - dotwoweapon, AUTOCOMPLETE }, - { M('u'), "untrap", "untrap something", dountrap, AUTOCOMPLETE }, - { '<', "up", "go up a staircase", doup }, - { '\0', "vanquished", "list vanquished monsters", - dovanquished, IFBURIED | AUTOCOMPLETE | WIZMODECMD }, - { M('v'), "version", - "list compile time options for this version of NetHack", - doextversion, IFBURIED | AUTOCOMPLETE | GENERALCMD }, - { 'v', "versionshort", "show version", doversion, IFBURIED | GENERALCMD }, - { '\0', "vision", "show vision array", - wiz_show_vision, IFBURIED | AUTOCOMPLETE | WIZMODECMD }, - { '.', "wait", "rest one move while doing nothing", - donull, IFBURIED, "waiting" }, - { 'W', "wear", "wear a piece of armor", dowear }, - { '&', "whatdoes", "tell what a command does", dowhatdoes, IFBURIED }, - { '/', "whatis", "show what type of thing a symbol corresponds to", - dowhatis, IFBURIED | GENERALCMD }, - { 'w', "wield", "wield (put in use) a weapon", dowield }, - { M('w'), "wipe", "wipe off your face", dowipe, AUTOCOMPLETE }, -#ifdef DEBUG - { '\0', "wizbury", "bury objs under and around you", - wiz_debug_cmd_bury, IFBURIED | AUTOCOMPLETE | WIZMODECMD }, -#endif - { C('e'), "wizdetect", "reveal hidden things within a small radius", - wiz_detect, IFBURIED | AUTOCOMPLETE | WIZMODECMD }, - { C('g'), "wizgenesis", "create a monster", - wiz_genesis, IFBURIED | AUTOCOMPLETE | WIZMODECMD }, - { C('i'), "wizidentify", "identify all items in inventory", - wiz_identify, IFBURIED | AUTOCOMPLETE | WIZMODECMD }, - { '\0', "wizintrinsic", "set an intrinsic", - wiz_intrinsic, IFBURIED | AUTOCOMPLETE | WIZMODECMD }, - { C('v'), "wizlevelport", "teleport to another level", - wiz_level_tele, IFBURIED | AUTOCOMPLETE | WIZMODECMD }, - { '\0', "wizmakemap", "recreate the current level", - wiz_makemap, IFBURIED | WIZMODECMD }, - { C('f'), "wizmap", "map the level", - wiz_map, IFBURIED | AUTOCOMPLETE | WIZMODECMD }, - { '\0', "wizrumorcheck", "verify rumor boundaries", - wiz_rumor_check, IFBURIED | AUTOCOMPLETE | WIZMODECMD }, - { '\0', "wizsmell", "smell monster", - wiz_smell, IFBURIED | AUTOCOMPLETE | WIZMODECMD }, - { '\0', "wizwhere", "show locations of special levels", - wiz_where, IFBURIED | AUTOCOMPLETE | WIZMODECMD }, - { C('w'), "wizwish", "wish for something", - wiz_wish, IFBURIED | AUTOCOMPLETE | WIZMODECMD }, - { '\0', "wmode", "show wall modes", - wiz_show_wmodes, IFBURIED | AUTOCOMPLETE | WIZMODECMD }, - { 'z', "zap", "zap a wand", dozap }, - { '\0', (char *) 0, (char *) 0, donull, 0, (char *) 0 } /* sentinel */ -}; + for (j = 0; j < n; ++j) { + if (ec == &extcmdlist[(picks[j].item.a_int - 1)]) { + parseautocomplete(buf, TRUE); + setit = TRUE; + break; + } + } + + if (!setit) { + parseautocomplete(buf, FALSE); + } + } + if (n > 0) + free((genericptr_t) picks); + } + + destroy_nhwindow(win); +} -/* for key2extcmddesc() to support dowhatdoes() */ -struct movcmd { - uchar k1, k2, k3, k4; /* 'normal', 'qwertz', 'numpad', 'phone' */ - const char *txt, *alt; /* compass direction, screen direction */ -}; -static const struct movcmd movtab[] = { - { 'h', 'h', '4', '4', "west", "left" }, - { 'j', 'j', '2', '8', "south", "down" }, - { 'k', 'k', '8', '2', "north", "up" }, - { 'l', 'l', '6', '6', "east", "right" }, - { 'b', 'b', '1', '7', "southwest", "lower left" }, - { 'n', 'n', '3', '9', "southeast", "lower right" }, - { 'u', 'u', '9', '3', "northeast", "upper right" }, - { 'y', 'z', '7', '1', "northwest", "upper left" }, - { 0, 0, 0, 0, (char *) 0, (char *) 0 } -}; +/* find extended command entries matching findstr. + if findstr is NULL, returns all available entries. + returns: number of matching extended commands, + and the entry indexes in matchlist. + for windowport use. */ +int +extcmds_match(const char *findstr, int ecmflags, int **matchlist) +{ + static int retmatchlist[SIZE(extcmdlist)] = DUMMY; + int i, mi = 0; + int fslen = findstr ? Strlen(findstr) : 0; + boolean ignoreac = (ecmflags & ECM_IGNOREAC) != 0; + boolean exactmatch = (ecmflags & ECM_EXACTMATCH) != 0; + boolean no1charcmd = (ecmflags & ECM_NO1CHARCMD) != 0; + + for (i = 0; extcmdlist[i].ef_txt; i++) { + if (extcmdlist[i].flags & (CMD_NOT_AVAILABLE|INTERNALCMD)) + continue; + if (!wizard && (extcmdlist[i].flags & WIZMODECMD)) + continue; + if (!ignoreac && !(extcmdlist[i].flags & AUTOCOMPLETE)) + continue; + if (no1charcmd && (strlen(extcmdlist[i].ef_txt) == 1)) + continue; + if (!findstr) { + retmatchlist[mi++] = i; + } else if (exactmatch) { + if (!strcmpi(findstr, extcmdlist[i].ef_txt)) { + retmatchlist[mi++] = i; + } + } else { + if (!strncmpi(findstr, extcmdlist[i].ef_txt, fslen)) { + retmatchlist[mi++] = i; + } + } + } + + if (matchlist) + *matchlist = retmatchlist; + + return mi; +} + +int nle_dosave() { + pline("You get the feeling there's only one way to save yourself..."); + return 1; +} + +int nle_done2() { + pline("You can't quit now, you're having so much fun!"); + return 1; +} + +int nle_doset() { + pline("The options are already set perfectly for you!"); + return 1; +} -int extcmdlist_length = SIZE(extcmdlist) - 1; +int nle_noop() { + pline("Noop"); + return 1; +} const char * -key2extcmddesc(key) -uchar key; +key2extcmddesc(uchar key) { - static char key2cmdbuf[48]; - const struct movcmd *mov; - int k, c; + static char key2cmdbuf[QBUFSZ]; + const char *txt; + int k, i, j; uchar M_5 = (uchar) M('5'), M_0 = (uchar) M('0'); + struct Cmd_bind *cmdbind; /* need to check for movement commands before checking the extended commands table because it contains entries for number_pad commands that match !number_pad movement (like 'j' for "jump") */ key2cmdbuf[0] = '\0'; - if (movecmd(k = key)) + if (movecmd(k = key, MV_WALK)) Strcpy(key2cmdbuf, "move"); /* "move or attack"? */ - else if (movecmd(k = unctrl(key))) + else if (movecmd(k = key, MV_RUSH)) Strcpy(key2cmdbuf, "rush"); - else if (movecmd(k = (Cmd.num_pad ? unmeta(key) : lowc(key)))) + else if (movecmd(k = key, MV_RUN)) Strcpy(key2cmdbuf, "run"); - if (*key2cmdbuf) { - for (mov = &movtab[0]; mov->k1; ++mov) { - c = !Cmd.num_pad ? (!Cmd.swap_yz ? mov->k1 : mov->k2) - : (!Cmd.phone_layout ? mov->k3 : mov->k4); - if (c == k) { - Sprintf(eos(key2cmdbuf), " %s (screen %s)", - mov->txt, mov->alt); - return key2cmdbuf; - } - } - } else if (digit(key) || (Cmd.num_pad && digit(unmeta(key)))) { + if (digit(key) || (gc.Cmd.num_pad && digit(unmeta(key)))) { key2cmdbuf[0] = '\0'; - if (!Cmd.num_pad) + if (!gc.Cmd.num_pad) Strcpy(key2cmdbuf, "start of, or continuation of, a count"); else if (key == '5' || key == M_5) Sprintf(key2cmdbuf, "%s prefix", - (!!Cmd.pcHack_compat ^ (key == M_5)) ? "run" : "rush"); - else if (key == '0' || (Cmd.pcHack_compat && key == M_0)) + (!!gc.Cmd.pcHack_compat ^ (key == M_5)) ? "run" : "rush"); + else if (key == '0' || (gc.Cmd.pcHack_compat && key == M_0)) Strcpy(key2cmdbuf, "synonym for 'i'"); if (*key2cmdbuf) return key2cmdbuf; } - if (Cmd.commands[key]) { - if (Cmd.commands[key]->ef_txt) - return Cmd.commands[key]->ef_desc; - + /* check prefixes before regular commands; includes ^A pseudo-command */ + for (i = 0; misc_keys[i].desc; ++i) { + if (misc_keys[i].numpad && !iflags.num_pad) + continue; + j = misc_keys[i].nhkf; + if (key == (uchar) gc.Cmd.spkeys[j]) + return misc_keys[i].desc; + } + /* finally, check whether 'key' is a command */ + if ((cmdbind = cmdbind_get(key)) != 0 + && cmdbind->cmd + && (txt = cmdbind->cmd->ef_txt) != 0) { + Sprintf(key2cmdbuf, "%s (#%s)", cmdbind->cmd->ef_desc, txt); + + /* special case: for reqmenu prefix (normally 'm'), replace + "prefix: request menu or modify command (#reqmenu)" + with two-line "movement prefix:...\nnon-movement prefix:..." */ + if (!strncmpi(key2cmdbuf, "prefix:", 7) && !strcmpi(txt, "reqmenu")) + (void) strsubst(key2cmdbuf, "prefix:", + /* relies on implicit concatenation of literal strings */ + "movement prefix:" + " move without autopickup and without attacking" + "\n" + "non-movement prefix:"); /* and rest of buf */ + + /* another special case: 'txt' for '#' is "#" and showing that as + "perform an extended command (##)" looks silly; strip "(##)" off */ + return strsubst(key2cmdbuf, " (##)", ""); } return (char *) 0; } boolean -bind_key(key, command) -uchar key; -const char *command; +bind_mousebtn(int btn, const char *command) +{ + struct ext_func_tab *extcmd; + + if (btn < 1 || btn > NUM_MOUSE_BUTTONS) { + config_error_add("Wrong mouse button, valid are 1-%i", + NUM_MOUSE_BUTTONS); + return FALSE; + } + btn--; + + /* special case: "nothing" is reserved for unbinding */ + if (!strcmpi(command, "nothing")) { + gc.Cmd.mousebtn[btn] = (struct ext_func_tab *) 0; + return TRUE; + } + + for (extcmd = extcmdlist; extcmd->ef_txt; extcmd++) { + if (strcmpi(command, extcmd->ef_txt)) + continue; + if (!(extcmd->flags & MOUSECMD)) + continue; + gc.Cmd.mousebtn[btn] = extcmd; +#if 0 /* silently accept key binding for unavailable command (!SHELL,&c) */ + if ((extcmd->flags & CMD_NOT_AVAILABLE) != 0) { + char buf[BUFSZ]; + + Sprintf(buf, cmdnotavail, extcmd->ef_txt); + config_error_add("%s", buf); + } +#endif + return TRUE; + } + + return FALSE; +} + +boolean +bind_key(uchar key, const char *command, boolean user) { struct ext_func_tab *extcmd; + long len; + char *buf, *p = NULL, *lastp = NULL; /* special case: "nothing" is reserved for unbinding */ - if (!strcmp(command, "nothing")) { - Cmd.commands[key] = (struct ext_func_tab *) 0; + if (!strcmpi(command, "nothing")) { + cmdbind_remove(key); return TRUE; } + /* copy command to buf for modification */ + len = strlen(command) + 1; + buf = (char *)alloc(len); + (void) strncpy(buf, command, len); + + /* does buf have a parameter in parenthesis? */ + if ((p = strchr(buf, '(')) != 0 + && (lastp = strrchr(buf, ')')) != 0 + && (lastp > p)) { + *p = '\0'; + *lastp = '\0'; + /* p points to the parameter */ + p++; + } + for (extcmd = extcmdlist; extcmd->ef_txt; extcmd++) { - if (strcmp(command, extcmd->ef_txt)) + if (strcmpi(buf, extcmd->ef_txt)) + continue; + if ((extcmd->flags & INTERNALCMD) != 0) continue; - Cmd.commands[key] = extcmd; + cmdbind_add(key, extcmd, user); + + if ((extcmd->flags & CMD_PARAM) != 0) { + if (!p) { + config_error_add("'%s' requires a parameter", buf); + } else { + struct Cmd_bind *bind = cmdbind_get(key); + int maxlen = min(30, strlen(p)) + 1; + + if (maxlen <= 1) { + config_error_add("Required parameter cannot be empty"); + } else { + bind->param = (char *) alloc(maxlen); + (void) strncpy(bind->param, p, maxlen); + bind->param[maxlen-1] = '\0'; + } + } + } else if (p && strlen(p) > 0) + config_error_add("'%s' does not take a parameter", buf); + #if 0 /* silently accept key binding for unavailable command (!SHELL,&c) */ if ((extcmd->flags & CMD_NOT_AVAILABLE) != 0) { char buf[BUFSZ]; @@ -3660,6 +2739,26 @@ const char *command; config_error_add("%s", buf); } #endif + free(buf); + return TRUE; + } + + free(buf); + return FALSE; +} + +/* bind key by ext cmd function */ +staticfn boolean +bind_key_fn(uchar key, int (*fn)(void)) +{ + struct ext_func_tab *extcmd; + + for (extcmd = extcmdlist; extcmd->ef_txt; extcmd++) { + if (extcmd->ef_funct != fn) + continue; + if ((extcmd->flags & INTERNALCMD) != 0) + continue; + cmdbind_add(key, extcmd, FALSE); return TRUE; } @@ -3667,664 +2766,424 @@ const char *command; } /* initialize all keyboard commands */ -void -commands_init() +staticfn void +commands_init(void) { struct ext_func_tab *extcmd; for (extcmd = extcmdlist; extcmd->ef_txt; extcmd++) if (extcmd->key) - Cmd.commands[extcmd->key] = extcmd; - - (void) bind_key(C('l'), "redraw"); /* if number_pad is set */ - /* 'b', 'B' : go sw */ - /* 'F' : fight (one time) */ - /* 'g', 'G' : multiple go */ - /* 'h', 'H' : go west */ - (void) bind_key('h', "help"); /* if number_pad is set */ - (void) bind_key('j', "jump"); /* if number_pad is on */ - /* 'j', 'J', 'k', 'K', 'l', 'L', 'm', 'M', 'n', 'N' move commands */ - (void) bind_key('k', "kick"); /* if number_pad is on */ - (void) bind_key('l', "loot"); /* if number_pad is on */ - (void) bind_key(C('n'), "annotate"); /* if number_pad is on */ - (void) bind_key(M('n'), "name"); - (void) bind_key(M('N'), "name"); - (void) bind_key('u', "untrap"); /* if number_pad is on */ + cmdbind_add(extcmd->key, extcmd, FALSE); + + (void) bind_mousebtn(1, "therecmdmenu"); + (void) bind_mousebtn(2, "clicklook"); + + /* number_pad */ + (void) bind_key(C('l'), "redraw", FALSE); + (void) bind_key('h', "help", FALSE); + (void) bind_key('j', "jump", FALSE); + (void) bind_key('k', "kick", FALSE); + (void) bind_key('l', "loot", FALSE); + (void) bind_key(C('n'), "annotate", FALSE); + (void) bind_key('N', "name", FALSE); + (void) bind_key('u', "untrap", FALSE); + (void) bind_key('5', "run", FALSE); + (void) bind_key(M('5'), "rush", FALSE); + (void) bind_key('-', "fight", FALSE); /* alt keys: */ - (void) bind_key(M('O'), "overview"); - (void) bind_key(M('2'), "twoweapon"); + (void) bind_key(M('O'), "overview", FALSE); + (void) bind_key(M('2'), "twoweapon", FALSE); + (void) bind_key(M('N'), "name", FALSE); +#if 0 + /* don't do this until the rest_on_space option is set or cleared */ + (void) bind_key(' ', "wait", FALSE); +#endif +} + +staticfn boolean +keylist_func_has_key(const struct ext_func_tab *extcmd, + boolean *skip_keys_used) /* boolean keys_used[256] */ +{ + int i; + struct Cmd_bind *bind; - /* wait_on_space */ - (void) bind_key(' ', "wait"); + for (i = 0; i < 256; ++i) { + if (skip_keys_used[i]) + continue; + + if (((bind = cmdbind_get(i)) != 0) && (bind->cmd == extcmd)) + return TRUE; + } + return FALSE; } -int -dokeylist_putcmds(datawin, docount, cmdflags, exflags, keys_used) -winid datawin; -boolean docount; -int cmdflags, exflags; -boolean *keys_used; /* boolean keys_used[256] */ +staticfn int +keylist_putcmds(winid datawin, boolean docount, + int incl_flags, int excl_flags, + boolean *keys_used) /* boolean keys_used[256] */ { + const struct ext_func_tab *extcmd; int i; - char buf[BUFSZ]; - char buf2[QBUFSZ]; + char buf[BUFSZ], buf2[QBUFSZ]; + boolean keys_already_used[256]; /* copy of keys_used[] before updates */ int count = 0; + struct Cmd_bind *bind; for (i = 0; i < 256; i++) { - const struct ext_func_tab *extcmd; uchar key = (uchar) i; + keys_already_used[i] = keys_used[i]; if (keys_used[i]) continue; if (key == ' ' && !flags.rest_on_space) continue; - if ((extcmd = Cmd.commands[i]) != (struct ext_func_tab *) 0) { - if ((cmdflags && !(extcmd->flags & cmdflags)) - || (exflags && (extcmd->flags & exflags))) + bind = cmdbind_get(key); + if (bind && bind->cmd != (struct ext_func_tab *) 0) { + if ((incl_flags && !(bind->cmd->flags & incl_flags)) + || (excl_flags && (bind->cmd->flags & excl_flags))) continue; if (docount) { count++; continue; } - Sprintf(buf, "%-8s %-12s %s", key2txt(key, buf2), - extcmd->ef_txt, - extcmd->ef_desc); + if ((bind->cmd->flags & CMD_PARAM) != 0) + Sprintf(buf, "%-7s %-13s %s \"%s\"", key2txt(key, buf2), + bind->cmd->ef_txt, bind->cmd->ef_desc, + bind->param); + else + Sprintf(buf, "%-7s %-13s %s", key2txt(key, buf2), + bind->cmd->ef_txt, bind->cmd->ef_desc); putstr(datawin, 0, buf); keys_used[i] = TRUE; } } + /* also list commands that lack key assignments; most are wizard mode */ + for (extcmd = extcmdlist; extcmd->ef_txt; ++extcmd) { + if ((incl_flags && !(extcmd->flags & incl_flags)) + || (excl_flags && (extcmd->flags & excl_flags))) + continue; + /* can't just check for non-Null extcmd->key; it holds the + default assignment and a user-specified binding might hijack + this command's default key for some other command; or this + command might have been assigned a key being used for + movement or as a prefix, intercepting that keystroke */ + if (keylist_func_has_key(extcmd, keys_already_used)) + continue; + /* found a command for current category without any key assignment */ + if (docount) { + count++; + continue; + } + /* '#'+20 for one column here == 7+' '+13 for two columns above */ + Sprintf(buf, "#%-20s %s", extcmd->ef_txt, extcmd->ef_desc); + putstr(datawin, 0, buf); + } return count; } /* list all keys and their bindings, like dat/hh but dynamic */ void -dokeylist(VOID_ARGS) +dokeylist(void) { + const struct ext_func_tab *extcmd; + winid datawin; char buf[BUFSZ], buf2[BUFSZ]; uchar key; - boolean keys_used[256] = {0}; - winid datawin; - int i; - static const char - run_desc[] = "Prefix: run until something very interesting is seen", - forcefight_desc[] = - "Prefix: force fight even if you don't see a monster"; - static const struct { - int nhkf; - const char *desc; - boolean numpad; - } misc_keys[] = { - { NHKF_ESC, "escape from the current query/action", FALSE }, - { NHKF_RUSH, - "Prefix: rush until something interesting is seen", FALSE }, - { NHKF_RUN, run_desc, FALSE }, - { NHKF_RUN2, run_desc, TRUE }, - { NHKF_FIGHT, forcefight_desc, FALSE }, - { NHKF_FIGHT2, forcefight_desc, TRUE } , - { NHKF_NOPICKUP, - "Prefix: move without picking up objects/fighting", FALSE }, - { NHKF_RUN_NOPICKUP, - "Prefix: run without picking up objects/fighting", FALSE }, - { NHKF_DOINV, "view inventory", TRUE }, - { NHKF_REQMENU, "Prefix: request a menu", FALSE }, -#ifdef REDO - { NHKF_DOAGAIN , "re-do: perform the previous command again", FALSE }, + boolean spkey_gap, keys_used[256], mov_seen[256]; + int i, j, pfx_seen[256]; + + (void) memset((genericptr_t) keys_used, 0, sizeof keys_used); + (void) memset((genericptr_t) pfx_seen, 0, sizeof pfx_seen); + +#ifndef NO_SIGNAL + /* this is actually ambiguous; tty raw mode will override SIGINT; + when enabled, treat it like a movement command since assigning + other commands to this keystroke would be unwise... */ + key = (uchar) C('c'); + keys_used[key] = TRUE; #endif - { 0, (const char *) 0, FALSE } - }; + + /* movement keys have been flagged in keys_used[]; clone them */ + (void) memcpy((genericptr_t) mov_seen, (genericptr_t) keys_used, + sizeof mov_seen); + + spkey_gap = FALSE; + for (i = 0; misc_keys[i].desc; ++i) { + if (misc_keys[i].numpad && !iflags.num_pad) + continue; + j = misc_keys[i].nhkf; + key = (uchar) gc.Cmd.spkeys[j]; + if (key && !mov_seen[key] && !pfx_seen[key]) { + keys_used[key] = TRUE; + pfx_seen[key] = j; + } else + spkey_gap = TRUE; + } datawin = create_nhwindow(NHW_TEXT); putstr(datawin, 0, ""); - putstr(datawin, 0, " Full Current Key Bindings List"); + Sprintf(buf, "%7s %s", "", " Full Current Key Bindings List"); + putstr(datawin, 0, buf); + for (extcmd = extcmdlist; extcmd->ef_txt; ++extcmd) + if (spkey_gap || !keylist_func_has_key(extcmd, keys_used)) { + Sprintf(buf, "%7s %s", "", + "(also commands with no key assignment)"); + putstr(datawin, 0, buf); + break; + } /* directional keys */ putstr(datawin, 0, ""); putstr(datawin, 0, "Directional keys:"); - show_direction_keys(datawin, '.', FALSE); /* '.'==self in direction grid */ - - keys_used[(uchar) Cmd.move_NW] = keys_used[(uchar) Cmd.move_N] - = keys_used[(uchar) Cmd.move_NE] = keys_used[(uchar) Cmd.move_W] - = keys_used[(uchar) Cmd.move_E] = keys_used[(uchar) Cmd.move_SW] - = keys_used[(uchar) Cmd.move_S] = keys_used[(uchar) Cmd.move_SE] - = TRUE; + show_direction_keys(datawin, '.', FALSE); /* '.'==self in direct'n grid */ if (!iflags.num_pad) { - keys_used[(uchar) highc(Cmd.move_NW)] - = keys_used[(uchar) highc(Cmd.move_N)] - = keys_used[(uchar) highc(Cmd.move_NE)] - = keys_used[(uchar) highc(Cmd.move_W)] - = keys_used[(uchar) highc(Cmd.move_E)] - = keys_used[(uchar) highc(Cmd.move_SW)] - = keys_used[(uchar) highc(Cmd.move_S)] - = keys_used[(uchar) highc(Cmd.move_SE)] = TRUE; - keys_used[(uchar) C(Cmd.move_NW)] - = keys_used[(uchar) C(Cmd.move_N)] - = keys_used[(uchar) C(Cmd.move_NE)] - = keys_used[(uchar) C(Cmd.move_W)] - = keys_used[(uchar) C(Cmd.move_E)] - = keys_used[(uchar) C(Cmd.move_SW)] - = keys_used[(uchar) C(Cmd.move_S)] - = keys_used[(uchar) C(Cmd.move_SE)] = TRUE; putstr(datawin, 0, ""); putstr(datawin, 0, - "Shift- will move in specified direction until you hit"); - putstr(datawin, 0, " a wall or run into something."); - putstr(datawin, 0, - "Ctrl- will run in specified direction until something"); - putstr(datawin, 0, " very interesting is seen."); + "Ctrl+ will run in specified direction until something very"); + Sprintf(buf, "%7s %s", "", "interesting is seen."); + putstr(datawin, 0, buf); + Strcpy(buf, "Shift"); /* append the rest below */ + } else { + /* num_pad */ + putstr(datawin, 0, ""); + Strcpy(buf, "Meta"); /* append the rest next */ } + Strcat(buf, + "+ will run in specified direction until you encounter"); + putstr(datawin, 0, buf); + Sprintf(buf, "%7s %s", "", "an obstacle."); + putstr(datawin, 0, buf); putstr(datawin, 0, ""); putstr(datawin, 0, "Miscellaneous keys:"); - for (i = 0; misc_keys[i].desc; i++) { - key = Cmd.spkeys[misc_keys[i].nhkf]; - if (key && ((misc_keys[i].numpad && iflags.num_pad) - || !misc_keys[i].numpad)) { - keys_used[(uchar) key] = TRUE; - Sprintf(buf, "%-8s %s", key2txt(key, buf2), misc_keys[i].desc); + for (i = 0; misc_keys[i].desc; ++i) { + if (misc_keys[i].numpad && !iflags.num_pad) + continue; + j = misc_keys[i].nhkf; + key = (uchar) gc.Cmd.spkeys[j]; + if (key && !mov_seen[key] + && (pfx_seen[key] == j)) { + Sprintf(buf, "%-7s %s", key2txt(key, buf2), misc_keys[i].desc); putstr(datawin, 0, buf); } } + /* (see above) */ + key = (uchar) C('c'); #ifndef NO_SIGNAL - putstr(datawin, 0, "^c break out of NetHack (SIGINT)"); - keys_used[(uchar) C('c')] = TRUE; + /* last of the special keys */ + Sprintf(buf, "%-7s", key2txt(key, buf2)); +#else + /* first of the keyless commands */ + Sprintf(buf2, "[%s]", key2txt(key, buf)); + Sprintf(buf, "%-21s", buf2); #endif + Strcat(buf, " interrupt: break out of NetHack (SIGINT)"); + putstr(datawin, 0, buf); + /* keyless special key commands, if any */ + if (spkey_gap) { + for (i = 0; misc_keys[i].desc; ++i) { + if (misc_keys[i].numpad && !iflags.num_pad) + continue; + j = misc_keys[i].nhkf; + key = (uchar) gc.Cmd.spkeys[j]; + if (!key || (pfx_seen[key] != j)) { + Sprintf(buf2, "[%s]", spkey_name(j)); + /* lines up with the other unassigned commands which use + "#%-20s ", but not with the other special keys */ + Snprintf(buf, sizeof(buf), "%-21s %s", buf2, + misc_keys[i].desc); + putstr(datawin, 0, buf); + } + } + } + +#define IGNORECMD (WIZMODECMD | INTERNALCMD | MOVEMENTCMD) putstr(datawin, 0, ""); show_menu_controls(datawin, TRUE); - if (dokeylist_putcmds(datawin, TRUE, GENERALCMD, WIZMODECMD, keys_used)) { + if (keylist_putcmds(datawin, TRUE, GENERALCMD, IGNORECMD, keys_used)) { putstr(datawin, 0, ""); putstr(datawin, 0, "General commands:"); - (void) dokeylist_putcmds(datawin, FALSE, GENERALCMD, WIZMODECMD, - keys_used); + (void) keylist_putcmds(datawin, FALSE, GENERALCMD, + IGNORECMD, keys_used); } - if (dokeylist_putcmds(datawin, TRUE, 0, WIZMODECMD, keys_used)) { + if (keylist_putcmds(datawin, TRUE, 0, GENERALCMD | IGNORECMD, keys_used)) { putstr(datawin, 0, ""); putstr(datawin, 0, "Game commands:"); - (void) dokeylist_putcmds(datawin, FALSE, 0, WIZMODECMD, keys_used); + (void) keylist_putcmds(datawin, FALSE, 0, + GENERALCMD | IGNORECMD, + keys_used); } - if (wizard - && dokeylist_putcmds(datawin, TRUE, WIZMODECMD, 0, keys_used)) { + if (wizard && keylist_putcmds(datawin, TRUE, + WIZMODECMD, INTERNALCMD, keys_used)) { putstr(datawin, 0, ""); - putstr(datawin, 0, "Wizard-mode commands:"); - (void) dokeylist_putcmds(datawin, FALSE, WIZMODECMD, 0, keys_used); + putstr(datawin, 0, "Debug mode commands:"); + (void) keylist_putcmds(datawin, FALSE, + WIZMODECMD, INTERNALCMD, keys_used); } display_nhwindow(datawin, FALSE); destroy_nhwindow(datawin); +#undef IGNORECMD } -char -cmd_from_func(fn) -int NDECL((*fn)); +const struct ext_func_tab * +ext_func_tab_from_func(int (*fn)(void)) { - int i; - - for (i = 0; i < 256; ++i) - if (Cmd.commands[i] && Cmd.commands[i]->ef_funct == fn) - return (char) i; - return '\0'; -} + const struct ext_func_tab *extcmd; -/* - * wizard mode sanity_check code - */ + for (extcmd = extcmdlist; extcmd->ef_txt; ++extcmd) + if (extcmd->ef_funct == fn) + return extcmd; -static const char template[] = "%-27s %4ld %6ld"; -static const char stats_hdr[] = " count bytes"; -static const char stats_sep[] = "--------------------------- ----- -------"; - -STATIC_OVL int -size_obj(otmp) -struct obj *otmp; -{ - int sz = (int) sizeof (struct obj); - - if (otmp->oextra) { - sz += (int) sizeof (struct oextra); - if (ONAME(otmp)) - sz += (int) strlen(ONAME(otmp)) + 1; - if (OMONST(otmp)) - sz += size_monst(OMONST(otmp), FALSE); - if (OMID(otmp)) - sz += (int) sizeof (unsigned); - if (OLONG(otmp)) - sz += (int) sizeof (long); - if (OMAILCMD(otmp)) - sz += (int) strlen(OMAILCMD(otmp)) + 1; - } - return sz; -} - -STATIC_OVL void -count_obj(chain, total_count, total_size, top, recurse) -struct obj *chain; -long *total_count; -long *total_size; -boolean top; -boolean recurse; -{ - long count, size; - struct obj *obj; - - for (count = size = 0, obj = chain; obj; obj = obj->nobj) { - if (top) { - count++; - size += size_obj(obj); - } - if (recurse && obj->cobj) - count_obj(obj->cobj, total_count, total_size, TRUE, TRUE); - } - *total_count += count; - *total_size += size; + return NULL; } -STATIC_OVL void -obj_chain(win, src, chain, force, total_count, total_size) -winid win; -const char *src; -struct obj *chain; -boolean force; -long *total_count; -long *total_size; +/* returns the key bound to a movement command for given DIR_ and MV_ mode */ +char +cmd_from_dir(int dir, int mode) { - char buf[BUFSZ]; - long count = 0L, size = 0L; - - count_obj(chain, &count, &size, TRUE, FALSE); - - if (count || size || force) { - *total_count += count; - *total_size += size; - Sprintf(buf, template, src, count, size); - putstr(win, 0, buf); - } + return cmd_from_func(move_funcs[dir][mode]); } -STATIC_OVL void -mon_invent_chain(win, src, chain, total_count, total_size) -winid win; -const char *src; -struct monst *chain; -long *total_count; -long *total_size; +/* return the key bound to extended command */ +char +cmd_from_func(int (*fn)(void)) { - char buf[BUFSZ]; - long count = 0, size = 0; - struct monst *mon; - - for (mon = chain; mon; mon = mon->nmon) - count_obj(mon->minvent, &count, &size, TRUE, FALSE); - - if (count || size) { - *total_count += count; - *total_size += size; - Sprintf(buf, template, src, count, size); - putstr(win, 0, buf); - } -} + int i; + char ret = '\0'; + struct Cmd_bind *bind; + + for (bind = gc.Cmd.cmdbinds; bind; bind = bind->next) { + i = bind->key; + /* skip space; we'll use it below as last resort if no other + keystroke invokes space's command */ + if (i == ' ') + continue; + /* skip digits if number_pad is Off; also skip '-' unless it has + been bound to something other than what number_pad assigns */ + if (((i >= '0' && i <= '9') || (i == '-' && fn == do_fight)) + && !gc.Cmd.num_pad) + continue; -STATIC_OVL void -contained_stats(win, src, total_count, total_size) -winid win; -const char *src; -long *total_count; -long *total_size; -{ - char buf[BUFSZ]; - long count = 0, size = 0; - struct monst *mon; - - count_obj(invent, &count, &size, FALSE, TRUE); - count_obj(fobj, &count, &size, FALSE, TRUE); - count_obj(level.buriedobjlist, &count, &size, FALSE, TRUE); - count_obj(migrating_objs, &count, &size, FALSE, TRUE); - /* DEADMONSTER check not required in this loop since they have no - * inventory */ - for (mon = fmon; mon; mon = mon->nmon) - count_obj(mon->minvent, &count, &size, FALSE, TRUE); - for (mon = migrating_mons; mon; mon = mon->nmon) - count_obj(mon->minvent, &count, &size, FALSE, TRUE); - - if (count || size) { - *total_count += count; - *total_size += size; - Sprintf(buf, template, src, count, size); - putstr(win, 0, buf); + if (bind->cmd && bind->cmd->ef_funct == fn) { + if (i >= ' ' && i <= '~') + return (char) i; + else { + ret = (char) i; + } + } } + if ((bind = cmdbind_get(' ')) != 0 && bind->cmd + && bind->cmd->ef_funct == fn) + return ' '; + return ret; } -STATIC_OVL int -size_monst(mtmp, incl_wsegs) -struct monst *mtmp; -boolean incl_wsegs; +/* return visual interpretation of the key bound to extended command, + or the ext cmd name if not bound to any key. */ +char * +cmd_from_ecname(const char *ecname) { - int sz = (int) sizeof (struct monst); + static char cmdnamebuf[QBUFSZ]; + const struct ext_func_tab *extcmd; - if (mtmp->wormno && incl_wsegs) - sz += size_wseg(mtmp); + for (extcmd = extcmdlist; extcmd->ef_txt; ++extcmd) + if (!strcmp(extcmd->ef_txt, ecname)) { + char key = cmd_from_func(extcmd->ef_funct); - if (mtmp->mextra) { - sz += (int) sizeof (struct mextra); - if (MNAME(mtmp)) - sz += (int) strlen(MNAME(mtmp)) + 1; - if (EGD(mtmp)) - sz += (int) sizeof (struct egd); - if (EPRI(mtmp)) - sz += (int) sizeof (struct epri); - if (ESHK(mtmp)) - sz += (int) sizeof (struct eshk); - if (EMIN(mtmp)) - sz += (int) sizeof (struct emin); - if (EDOG(mtmp)) - sz += (int) sizeof (struct edog); - /* mextra->mcorpsenm doesn't point to more memory */ - } - return sz; -} + if (key) + Sprintf(cmdnamebuf, "%s", visctrl(key)); + else + Sprintf(cmdnamebuf, "#%s", ecname); + return cmdnamebuf; + } -STATIC_OVL void -mon_chain(win, src, chain, force, total_count, total_size) -winid win; -const char *src; -struct monst *chain; -boolean force; -long *total_count; -long *total_size; -{ - char buf[BUFSZ]; - long count, size; - struct monst *mon; - /* mon->wormno means something different for migrating_mons and mydogs */ - boolean incl_wsegs = !strcmpi(src, "fmon"); - - count = size = 0L; - for (mon = chain; mon; mon = mon->nmon) { - count++; - size += size_monst(mon, incl_wsegs); - } - if (count || size || force) { - *total_count += count; - *total_size += size; - Sprintf(buf, template, src, count, size); - putstr(win, 0, buf); - } + cmdnamebuf[0] = '\0'; + return cmdnamebuf; } -STATIC_OVL void -misc_stats(win, total_count, total_size) -winid win; -long *total_count; -long *total_size; +const char * +ecname_from_fn(int (*fn)(void)) { - char buf[BUFSZ], hdrbuf[QBUFSZ]; - long count, size; - int idx; - struct trap *tt; - struct damage *sd; /* shop damage */ - struct kinfo *k; /* delayed killer */ - struct cemetery *bi; /* bones info */ - - /* traps and engravings are output unconditionally; - * others only if nonzero - */ - count = size = 0L; - for (tt = ftrap; tt; tt = tt->ntrap) { - ++count; - size += (long) sizeof *tt; - } - *total_count += count; - *total_size += size; - Sprintf(hdrbuf, "traps, size %ld", (long) sizeof (struct trap)); - Sprintf(buf, template, hdrbuf, count, size); - putstr(win, 0, buf); - - count = size = 0L; - engr_stats("engravings, size %ld+text", hdrbuf, &count, &size); - *total_count += count; - *total_size += size; - Sprintf(buf, template, hdrbuf, count, size); - putstr(win, 0, buf); - - count = size = 0L; - light_stats("light sources, size %ld", hdrbuf, &count, &size); - if (count || size) { - *total_count += count; - *total_size += size; - Sprintf(buf, template, hdrbuf, count, size); - putstr(win, 0, buf); - } - - count = size = 0L; - timer_stats("timers, size %ld", hdrbuf, &count, &size); - if (count || size) { - *total_count += count; - *total_size += size; - Sprintf(buf, template, hdrbuf, count, size); - putstr(win, 0, buf); - } + const struct ext_func_tab *extcmd, *cmdptr = 0; - count = size = 0L; - for (sd = level.damagelist; sd; sd = sd->next) { - ++count; - size += (long) sizeof *sd; - } - if (count || size) { - *total_count += count; - *total_size += size; - Sprintf(hdrbuf, "shop damage, size %ld", - (long) sizeof (struct damage)); - Sprintf(buf, template, hdrbuf, count, size); - putstr(win, 0, buf); - } - - count = size = 0L; - region_stats("regions, size %ld+%ld*rect+N", hdrbuf, &count, &size); - if (count || size) { - *total_count += count; - *total_size += size; - Sprintf(buf, template, hdrbuf, count, size); - putstr(win, 0, buf); - } - - count = size = 0L; - for (k = killer.next; k; k = k->next) { - ++count; - size += (long) sizeof *k; - } - if (count || size) { - *total_count += count; - *total_size += size; - Sprintf(hdrbuf, "delayed killer%s, size %ld", - plur(count), (long) sizeof (struct kinfo)); - Sprintf(buf, template, hdrbuf, count, size); - putstr(win, 0, buf); - } - - count = size = 0L; - for (bi = level.bonesinfo; bi; bi = bi->next) { - ++count; - size += (long) sizeof *bi; - } - if (count || size) { - *total_count += count; - *total_size += size; - Sprintf(hdrbuf, "bones history, size %ld", - (long) sizeof (struct cemetery)); - Sprintf(buf, template, hdrbuf, count, size); - putstr(win, 0, buf); - } - - count = size = 0L; - for (idx = 0; idx < NUM_OBJECTS; ++idx) - if (objects[idx].oc_uname) { - ++count; - size += (long) (strlen(objects[idx].oc_uname) + 1); + for (extcmd = extcmdlist; extcmd->ef_txt; ++extcmd) + if (extcmd->ef_funct == fn) { + cmdptr = extcmd; + return cmdptr->ef_txt; } - if (count || size) { - *total_count += count; - *total_size += size; - Strcpy(hdrbuf, "object type names, text"); - Sprintf(buf, template, hdrbuf, count, size); - putstr(win, 0, buf); - } + return (char *) 0; } -/* - * Display memory usage of all monsters and objects on the level. - */ -static int -wiz_show_stats() +/* return extended command name (without leading '#') for command (*fn)() */ +const char * +cmdname_from_func( + int (*fn)(void), /* function whose command name is wanted */ + char outbuf[], /* place to store the result */ + boolean fullname) /* False: just enough to disambiguate */ { - char buf[BUFSZ]; - winid win; - long total_obj_size, total_obj_count, - total_mon_size, total_mon_count, - total_ovr_size, total_ovr_count, - total_misc_size, total_misc_count; - - win = create_nhwindow(NHW_TEXT); - putstr(win, 0, "Current memory statistics:"); - - total_obj_count = total_obj_size = 0L; - putstr(win, 0, stats_hdr); - Sprintf(buf, " Objects, base size %ld", (long) sizeof (struct obj)); - putstr(win, 0, buf); - obj_chain(win, "invent", invent, TRUE, &total_obj_count, &total_obj_size); - obj_chain(win, "fobj", fobj, TRUE, &total_obj_count, &total_obj_size); - obj_chain(win, "buried", level.buriedobjlist, FALSE, - &total_obj_count, &total_obj_size); - obj_chain(win, "migrating obj", migrating_objs, FALSE, - &total_obj_count, &total_obj_size); - obj_chain(win, "billobjs", billobjs, FALSE, - &total_obj_count, &total_obj_size); - mon_invent_chain(win, "minvent", fmon, &total_obj_count, &total_obj_size); - mon_invent_chain(win, "migrating minvent", migrating_mons, - &total_obj_count, &total_obj_size); - contained_stats(win, "contained", &total_obj_count, &total_obj_size); - putstr(win, 0, stats_sep); - Sprintf(buf, template, " Obj total", total_obj_count, total_obj_size); - putstr(win, 0, buf); - - total_mon_count = total_mon_size = 0L; - putstr(win, 0, ""); - Sprintf(buf, " Monsters, base size %ld", (long) sizeof (struct monst)); - putstr(win, 0, buf); - mon_chain(win, "fmon", fmon, TRUE, &total_mon_count, &total_mon_size); - mon_chain(win, "migrating", migrating_mons, FALSE, - &total_mon_count, &total_mon_size); - /* 'mydogs' is only valid during level change or end of game disclosure, - but conceivably we've been called from within debugger at such time */ - if (mydogs) /* monsters accompanying hero */ - mon_chain(win, "mydogs", mydogs, FALSE, - &total_mon_count, &total_mon_size); - putstr(win, 0, stats_sep); - Sprintf(buf, template, " Mon total", total_mon_count, total_mon_size); - putstr(win, 0, buf); - - total_ovr_count = total_ovr_size = 0L; - putstr(win, 0, ""); - putstr(win, 0, " Overview"); - overview_stats(win, template, &total_ovr_count, &total_ovr_size); - putstr(win, 0, stats_sep); - Sprintf(buf, template, " Over total", total_ovr_count, total_ovr_size); - putstr(win, 0, buf); - - total_misc_count = total_misc_size = 0L; - putstr(win, 0, ""); - putstr(win, 0, " Miscellaneous"); - misc_stats(win, &total_misc_count, &total_misc_size); - putstr(win, 0, stats_sep); - Sprintf(buf, template, " Misc total", total_misc_count, total_misc_size); - putstr(win, 0, buf); - - putstr(win, 0, ""); - putstr(win, 0, stats_sep); - Sprintf(buf, template, " Grand total", - (total_obj_count + total_mon_count - + total_ovr_count + total_misc_count), - (total_obj_size + total_mon_size - + total_ovr_size + total_misc_size)); - putstr(win, 0, buf); - -#if defined(__BORLANDC__) && !defined(_WIN32) - show_borlandc_stats(win); -#endif + const struct ext_func_tab *extcmd, *cmdptr = 0; + const char *res = 0; - display_nhwindow(win, FALSE); - destroy_nhwindow(win); - return 0; -} - -void -sanity_check() -{ - obj_sanity_check(); - timer_sanity_check(); - mon_sanity_check(); - light_sources_sanity_check(); - bc_sanity_check(); -} + for (extcmd = extcmdlist; extcmd->ef_txt; ++extcmd) + if (extcmd->ef_funct == fn) { + cmdptr = extcmd; + res = cmdptr->ef_txt; + break; + } -#ifdef DEBUG_MIGRATING_MONS -static int -wiz_migrate_mons() -{ - int mcount = 0; - char inbuf[BUFSZ] = DUMMY; - struct permonst *ptr; - struct monst *mtmp; - d_level tolevel; + if (!res) { + /* make sure output buffer doesn't contain junk or stale data; + return Null below */ + outbuf[0] = '\0'; + } else if (fullname) { + /* easy; the entire command name */ + res = strcpy(outbuf, res); + } else { + const struct ext_func_tab *matchcmd = extcmdlist; + unsigned len = 0, maxlen = Strlen(res); - getlin("How many random monsters to migrate? [0]", inbuf); - if (*inbuf == '\033') - return 0; - mcount = atoi(inbuf); - if (mcount < 0 || mcount > (COLNO * ROWNO) || Is_botlevel(&u.uz)) - return 0; - while (mcount > 0) { - if (Is_stronghold(&u.uz)) - assign_level(&tolevel, &valley_level); - else - get_level(&tolevel, depth(&u.uz) + 1); - ptr = rndmonst(); - mtmp = makemon(ptr, 0, 0, NO_MM_FLAGS); - if (mtmp) - migrate_to_level(mtmp, ledger_no(&tolevel), MIGR_RANDOM, - (coord *) 0); - mcount--; + /* find the shortest leading substring which is unambiguous */ + do { + if (++len >= maxlen) + break; + for (extcmd = matchcmd; extcmd->ef_txt; ++extcmd) { + if (extcmd == cmdptr) + continue; + if ((extcmd->flags & CMD_NOT_AVAILABLE) != 0 + || ((extcmd->flags & WIZMODECMD) != 0 && !wizard)) + continue; + if (!strncmp(res, extcmd->ef_txt, len)) { + matchcmd = extcmd; + break; + } + } + } while (extcmd->ef_txt); + copynchars(outbuf, res, len); + /* [note: for Qt, this debugpline writes a couple dozen lines to + stdout during menu setup when message window isn't ready yet] */ + debugpline2("shortened %s: \"%s\"", res, outbuf); + res = outbuf; } - return 0; + return res; } -#endif -struct { +static struct { int nhkf; - char key; + uchar key; const char *name; } const spkeys_binds[] = { { NHKF_ESC, '\033', (char *) 0 }, /* no binding */ - { NHKF_DOAGAIN, DOAGAIN, "repeat" }, - { NHKF_REQMENU, 'm', "reqmenu" }, - { NHKF_RUN, 'G', "run" }, - { NHKF_RUN2, '5', "run.numpad" }, - { NHKF_RUSH, 'g', "rush" }, - { NHKF_FIGHT, 'F', "fight" }, - { NHKF_FIGHT2, '-', "fight.numpad" }, - { NHKF_NOPICKUP, 'm', "nopickup" }, - { NHKF_RUN_NOPICKUP, 'M', "run.nopickup" }, - { NHKF_DOINV, '0', "doinv" }, - { NHKF_TRAVEL, CMD_TRAVEL, (char *) 0 }, /* no binding */ - { NHKF_CLICKLOOK, CMD_CLICKLOOK, (char *) 0 }, /* no binding */ - { NHKF_REDRAW, C('r'), "redraw" }, - { NHKF_REDRAW2, C('l'), "redraw.numpad" }, { NHKF_GETDIR_SELF, '.', "getdir.self" }, { NHKF_GETDIR_SELF2, 's', "getdir.self2" }, { NHKF_GETDIR_HELP, '?', "getdir.help" }, + { NHKF_GETDIR_MOUSE, '_', "getdir.mouse" }, { NHKF_COUNT, 'n', "count" }, { NHKF_GETPOS_SELF, '@', "getpos.self" }, { NHKF_GETPOS_PICK, '.', "getpos.pick" }, @@ -4352,94 +3211,47 @@ struct { }; boolean -bind_specialkey(key, command) -uchar key; -const char *command; +bind_specialkey(uchar key, const char *command) { int i; + for (i = 0; i < SIZE(spkeys_binds); i++) { if (!spkeys_binds[i].name || strcmp(command, spkeys_binds[i].name)) continue; - Cmd.spkeys[spkeys_binds[i].nhkf] = key; + gc.Cmd.spkeys[spkeys_binds[i].nhkf] = key; return TRUE; } return FALSE; } -/* returns a one-byte character from the text (it may massacre the txt - * buffer) */ -char -txt2key(txt) -char *txt; +staticfn const char * +spkey_name(int nhkf) { - txt = trimspaces(txt); - if (!*txt) - return '\0'; - - /* simple character */ - if (!txt[1]) - return txt[0]; - - /* a few special entries */ - if (!strcmp(txt, "")) - return '\n'; - if (!strcmp(txt, "")) - return ' '; - if (!strcmp(txt, "")) - return '\033'; - - /* control and meta keys */ - switch (*txt) { - case 'm': /* can be mx, Mx, m-x, M-x */ - case 'M': - txt++; - if (*txt == '-' && txt[1]) - txt++; - if (txt[1]) - return '\0'; - return M(*txt); - case 'c': /* can be cx, Cx, ^x, c-x, C-x, ^-x */ - case 'C': - case '^': - txt++; - if (*txt == '-' && txt[1]) - txt++; - if (txt[1]) - return '\0'; - return C(*txt); - } - - /* ascii codes: must be three-digit decimal */ - if (*txt >= '0' && *txt <= '9') { - uchar key = 0; - int i; + const char *name = 0; + int i; - for (i = 0; i < 3; i++) { - if (txt[i] < '0' || txt[i] > '9') - return '\0'; - key = 10 * key + txt[i] - '0'; + for (i = 0; i < SIZE(spkeys_binds); i++) { + if (spkeys_binds[i].nhkf == nhkf) { + name = (nhkf == NHKF_ESC) ? "escape" : spkeys_binds[i].name; + break; } - return key; } - - return '\0'; + return name; } /* returns the text for a one-byte encoding; * must be shorter than a tab for proper formatting */ char * -key2txt(c, txt) -uchar c; -char *txt; /* sufficiently long buffer */ +key2txt(uchar c, char *txt) /* sufficiently long buffer */ { /* should probably switch to "SPC", "ESC", "RET" since nethack's documentation uses ESC for */ if (c == ' ') Sprintf(txt, ""); else if (c == '\033') - Sprintf(txt, ""); + Sprintf(txt, ""); /* "" won't fit */ else if (c == '\n') - Sprintf(txt, ""); + Sprintf(txt, ""); /* "" won't fit */ else if (c == '\177') Sprintf(txt, ""); /* "" won't fit */ else @@ -4449,16 +3261,14 @@ char *txt; /* sufficiently long buffer */ void -parseautocomplete(autocomplete, condition) -char *autocomplete; -boolean condition; +parseautocomplete(char *autocomplete, boolean condition) { struct ext_func_tab *efp; - register char *autoc; + char *autoc; /* break off first autocomplete from the rest; parse the rest */ - if ((autoc = index(autocomplete, ',')) != 0 - || (autoc = index(autocomplete, ':')) != 0) { + if ((autoc = strchr(autocomplete, ',')) != 0 + || (autoc = strchr(autocomplete, ':')) != 0) { *autoc++ = '\0'; parseautocomplete(autoc, condition); } @@ -4481,6 +3291,12 @@ boolean condition; /* find and modify the extended command */ for (efp = extcmdlist; efp->ef_txt; efp++) { if (!strcmp(autocomplete, efp->ef_txt)) { + if (condition == ((efp->flags & AUTOCOMPLETE) ? FALSE : TRUE)) { + if ((efp->flags & AUTOCOMP_ADJ)) + efp->flags &= ~AUTOCOMP_ADJ; + else + efp->flags |= AUTOCOMP_ADJ; + } if (condition) efp->flags |= AUTOCOMPLETE; else @@ -4495,10 +3311,57 @@ boolean condition; wait_synch(); } +/* add changed autocompletions to the string buffer in config file format */ +void +all_options_autocomplete(strbuf_t *sbuf) +{ + struct ext_func_tab *efp; + char buf[BUFSZ]; + + for (efp = extcmdlist; efp->ef_txt; efp++) + if ((efp->flags & AUTOCOMP_ADJ) != 0) { + Sprintf(buf, "AUTOCOMPLETE=%s%s\n", + (efp->flags & AUTOCOMPLETE) ? "" : "!", + efp->ef_txt); + strbuf_append(sbuf, buf); + } +} + +/* return the number of changed autocompletions */ +int +count_autocompletions(void) +{ + struct ext_func_tab *efp; + int n = 0; + + for (efp = extcmdlist; efp->ef_txt; efp++) + if ((efp->flags & AUTOCOMP_ADJ) != 0) + n++; + + return n; +} + +/* save&clear the mouse button actions, or restore the saved ones */ +void +lock_mouse_buttons(boolean savebtns) +{ + static const struct ext_func_tab *mousebtn[NUM_MOUSE_BUTTONS] = { 0 }; + int i; + + if (savebtns) { + for (i = 0; i < NUM_MOUSE_BUTTONS; i++) { + mousebtn[i] = gc.Cmd.mousebtn[i]; + gc.Cmd.mousebtn[i] = NULL; + } + } else { + for (i = 0; i < NUM_MOUSE_BUTTONS; i++) + gc.Cmd.mousebtn[i] = mousebtn[i]; + } +} + /* called at startup and after number_pad is twiddled */ void -reset_commands(initial) -boolean initial; +reset_commands(boolean initial) { static const char sdir[] = "hykulnjb><", sdir_swap_yz[] = "hzkulnjb><", @@ -4507,137 +3370,181 @@ boolean initial; static const int ylist[] = { 'y', 'Y', C('y'), M('y'), M('Y'), M(C('y')) }; - static struct ext_func_tab *back_dir_cmd[8]; - const struct ext_func_tab *cmdtmp; + static struct ext_func_tab *back_dir_cmd[N_DIRS][N_MOVEMODES]; + static uchar back_dir_key[N_DIRS][N_MOVEMODES]; + static boolean backed_dir_cmd = FALSE; boolean flagtemp; int c, i, updated = 0; - static boolean backed_dir_cmd = FALSE; + int dir, mode; if (initial) { updated = 1; - Cmd.num_pad = FALSE; - Cmd.pcHack_compat = Cmd.phone_layout = Cmd.swap_yz = FALSE; + gc.Cmd.num_pad = FALSE; + gc.Cmd.pcHack_compat = gc.Cmd.phone_layout = gc.Cmd.swap_yz = FALSE; for (i = 0; i < SIZE(spkeys_binds); i++) - Cmd.spkeys[spkeys_binds[i].nhkf] = spkeys_binds[i].key; + gc.Cmd.spkeys[spkeys_binds[i].nhkf] = spkeys_binds[i].key; commands_init(); } else { - if (backed_dir_cmd) { - for (i = 0; i < 8; i++) { - Cmd.commands[(uchar) Cmd.dirchars[i]] = back_dir_cmd[i]; + for (dir = 0; dir < N_DIRS; dir++) { + for (mode = 0; mode < N_MOVEMODES; mode++) { + cmdbind_add(back_dir_key[dir][mode], back_dir_cmd[dir][mode], FALSE); + } } } /* basic num_pad */ flagtemp = iflags.num_pad; - if (flagtemp != Cmd.num_pad) { - Cmd.num_pad = flagtemp; + if (flagtemp != gc.Cmd.num_pad) { + gc.Cmd.num_pad = flagtemp; ++updated; } /* swap_yz mode (only applicable for !num_pad); intended for QWERTZ keyboard used in Central Europe, particularly Germany */ - flagtemp = (iflags.num_pad_mode & 1) ? !Cmd.num_pad : FALSE; - if (flagtemp != Cmd.swap_yz) { - Cmd.swap_yz = flagtemp; + flagtemp = (iflags.num_pad_mode & 1) ? !gc.Cmd.num_pad : FALSE; + if (flagtemp != gc.Cmd.swap_yz) { + gc.Cmd.swap_yz = flagtemp; ++updated; - /* Cmd.swap_yz has been toggled; + /* FIXME? should Cmd.spkeys[] be scanned for y and/or z to swap? + Cmd.swap_yz has been toggled; perform the swap (or reverse previous one) */ for (i = 0; i < SIZE(ylist); i++) { c = ylist[i] & 0xff; - cmdtmp = Cmd.commands[c]; /* tmp = [y] */ - Cmd.commands[c] = Cmd.commands[c + 1]; /* [y] = [z] */ - Cmd.commands[c + 1] = cmdtmp; /* [z] = tmp */ + cmdbind_swapkeys(c, c + 1); } } /* MSDOS compatibility mode (only applicable for num_pad) */ - flagtemp = (iflags.num_pad_mode & 1) ? Cmd.num_pad : FALSE; - if (flagtemp != Cmd.pcHack_compat) { - Cmd.pcHack_compat = flagtemp; + flagtemp = (iflags.num_pad_mode & 1) ? gc.Cmd.num_pad : FALSE; + if (flagtemp != gc.Cmd.pcHack_compat) { + gc.Cmd.pcHack_compat = flagtemp; ++updated; /* pcHack_compat has been toggled */ +#if 0 c = M('5') & 0xff; - cmdtmp = Cmd.commands['5']; - Cmd.commands['5'] = Cmd.commands[c]; - Cmd.commands[c] = cmdtmp; + cmdtmp = gc.Cmd.commands['5']; + gc.Cmd.commands['5'] = gc.Cmd.commands[c]; + gc.Cmd.commands[c] = cmdtmp; +#endif + /* FIXME: NHKF_DOINV2 ought to be implemented instead of this */ c = M('0') & 0xff; - Cmd.commands[c] = Cmd.pcHack_compat ? Cmd.commands['I'] : 0; + if (gc.Cmd.pcHack_compat) + cmdbind_add(c, ext_func_tab_from_func(dotypeinv), FALSE); + else + cmdbind_remove(c); } /* phone keypad layout (only applicable for num_pad) */ - flagtemp = (iflags.num_pad_mode & 2) ? Cmd.num_pad : FALSE; - if (flagtemp != Cmd.phone_layout) { - Cmd.phone_layout = flagtemp; + flagtemp = (iflags.num_pad_mode & 2) ? gc.Cmd.num_pad : FALSE; + if (flagtemp != gc.Cmd.phone_layout) { + gc.Cmd.phone_layout = flagtemp; ++updated; /* phone_layout has been toggled */ for (i = 0; i < 3; i++) { c = '1' + i; /* 1,2,3 <-> 7,8,9 */ - cmdtmp = Cmd.commands[c]; /* tmp = [1] */ - Cmd.commands[c] = Cmd.commands[c + 6]; /* [1] = [7] */ - Cmd.commands[c + 6] = cmdtmp; /* [7] = tmp */ + cmdbind_swapkeys(c, c + 6); c = (M('1') & 0xff) + i; /* M-1,M-2,M-3 <-> M-7,M-8,M-9 */ - cmdtmp = Cmd.commands[c]; /* tmp = [M-1] */ - Cmd.commands[c] = Cmd.commands[c + 6]; /* [M-1] = [M-7] */ - Cmd.commands[c + 6] = cmdtmp; /* [M-7] = tmp */ + cmdbind_swapkeys(c, c + 6); } } } /*?initial*/ + /* choose updated movement keys */ if (updated) - Cmd.serialno++; - Cmd.dirchars = !Cmd.num_pad - ? (!Cmd.swap_yz ? sdir : sdir_swap_yz) - : (!Cmd.phone_layout ? ndir : ndir_phone_layout); - Cmd.alphadirchars = !Cmd.num_pad ? Cmd.dirchars : sdir; - - Cmd.move_W = Cmd.dirchars[0]; - Cmd.move_NW = Cmd.dirchars[1]; - Cmd.move_N = Cmd.dirchars[2]; - Cmd.move_NE = Cmd.dirchars[3]; - Cmd.move_E = Cmd.dirchars[4]; - Cmd.move_SE = Cmd.dirchars[5]; - Cmd.move_S = Cmd.dirchars[6]; - Cmd.move_SW = Cmd.dirchars[7]; - - if (!initial) { - for (i = 0; i < 8; i++) { - back_dir_cmd[i] = - (struct ext_func_tab *) Cmd.commands[(uchar) Cmd.dirchars[i]]; - Cmd.commands[(uchar) Cmd.dirchars[i]] = (struct ext_func_tab *) 0; + gc.Cmd.serialno++; + gc.Cmd.dirchars = !gc.Cmd.num_pad + ? (!gc.Cmd.swap_yz ? sdir : sdir_swap_yz) + : (!gc.Cmd.phone_layout ? ndir : ndir_phone_layout); + gc.Cmd.alphadirchars = !gc.Cmd.num_pad ? gc.Cmd.dirchars : sdir; + + /* back up the commands & keys overwritten by new movement keys */ + for (dir = 0; dir < N_DIRS; dir++) { + for (mode = MV_WALK; mode < N_MOVEMODES; mode++) { + uchar di = (uchar) gc.Cmd.dirchars[dir]; + struct Cmd_bind *bind; + + if (!gc.Cmd.num_pad) { + if (mode == MV_RUN) di = highc(di); + else if (mode == MV_RUSH) di = C(di); + } else { + if (mode == MV_RUN) di = M(di); + else if (mode == MV_RUSH) di = M(di); + } + back_dir_key[dir][mode] = di; + if ((bind = cmdbind_get(di)) != 0) + back_dir_cmd[dir][mode] = (struct ext_func_tab *) bind->cmd; + else + back_dir_cmd[dir][mode] = (struct ext_func_tab *) 0; + cmdbind_remove(di); } - backed_dir_cmd = TRUE; - for (i = 0; i < 8; i++) - (void) bind_key(Cmd.dirchars[i], "nothing"); - } -} - -/* non-movement commands which accept 'm' prefix to request menu operation */ -STATIC_OVL boolean -accept_menu_prefix(cmd_func) -int NDECL((*cmd_func)); -{ - if (cmd_func == dopickup || cmd_func == dotip - /* eat, #offer, and apply tinning-kit all use floorfood() to pick - an item on floor or in invent; 'm' skips picking from floor - (ie, inventory only) rather than request use of menu operation */ - || cmd_func == doeat || cmd_func == dosacrifice || cmd_func == doapply - /* 'm' for removing saddle from adjacent monster without checking - for containers at */ - || cmd_func == doloot - /* travel: pop up a menu of interesting targets in view */ - || cmd_func == dotravel - /* wizard mode ^V and ^T */ - || cmd_func == wiz_level_tele || cmd_func == dotelecmd - /* 'm' prefix allowed for some extended commands */ - || cmd_func == doextcmd || cmd_func == doextlist) - return TRUE; - return FALSE; + } + backed_dir_cmd = TRUE; + + /* bind the new keys to movement commands */ + for (i = 0; i < N_DIRS; i++) { + (void) bind_key_fn(gc.Cmd.dirchars[i], move_funcs[i][MV_WALK]); + if (!gc.Cmd.num_pad) { + (void) bind_key_fn(highc(gc.Cmd.dirchars[i]), + move_funcs[i][MV_RUN]); + (void) bind_key_fn(C(gc.Cmd.dirchars[i]), move_funcs[i][MV_RUSH]); + } else { + /* M(number) works when altmeta is on */ + (void) bind_key_fn(M(gc.Cmd.dirchars[i]), move_funcs[i][MV_RUN]); + /* can't bind highc() or C() of digits. just use the 5 prefix. */ + } + } + update_rest_on_space(); + gc.Cmd.extcmd_char = cmd_from_func(doextcmd); +} + +/* called when 'rest_on_space' is toggled, also called by reset_commands() + from initoptions_init() which takes place before key bindings have been + processed, and by initoptions_finish() after key bindings so that we + can remember anything bound to in 'unrestonspace' */ +void +update_rest_on_space(void) +{ + /* cloned from extcmdlist['.'], then slightly modified to be distinct; + donull is all that's needed for it to operate; command name and + description get shown by help menu's "Info on what a given key does" + (which runs the '&' command) and "Full list of keyboard commands" */ + static const struct ext_func_tab restonspace = { + ' ', "wait", "rest one move via 'rest_on_space' option", + donull, (IFBURIED | CMD_M_PREFIX), "waiting" + }; + static const struct ext_func_tab *unrestonspace = 0; + struct Cmd_bind *bind = cmdbind_get(' '); + + /* when 'rest_on_space' is On, will run the #wait command; + when it is Off, will use 'unrestonspace' which will either + be Null and elicit "Unknown command ' '." or have some non-Null + command bound in player's RC file */ + if (bind && bind->cmd != &restonspace) + unrestonspace = bind->cmd; + cmdbind_add(' ', flags.rest_on_space ? &restonspace : unrestonspace, FALSE); +} + +/* commands which accept 'm' prefix to request menu operation or other + alternate behavior; it's also overloaded for move-without-autopickup; + there is no overlap between the two groups of commands */ +staticfn boolean +accept_menu_prefix(const struct ext_func_tab *ec) +{ + return (ec && ((ec->flags & CMD_M_PREFIX) != 0)); } +/* choose a random character, biased towards movement commands, primarily + for debug-fuzzer testing */ char -randomkey() +randomkey(void) { static unsigned i = 0; + static char last_c = '\0'; char c; + /* give ^A and ^P a high probability of being repeated */ + if ((last_c == C('a') || last_c == C('p')) + && program_state.input_state == commandInp && rn2(5)) + return last_c; + switch (rn2(16)) { default: c = '\033'; @@ -4669,9 +3576,12 @@ randomkey() case 10: case 11: case 12: - c = Cmd.dirchars[rn2(8)]; - if (!rn2(7)) - c = !Cmd.num_pad ? (!rn2(3) ? C(c) : (c + 'A' - 'a')) : M(c); + { + int d = rn2(N_DIRS); + int m = rn2(7) ? MV_WALK : (!rn2(3) ? MV_RUSH : MV_RUN); + + c = cmd_from_dir(d, m); + } break; case 13: c = (char) rn1('9' - '0' + 1, '0'); @@ -4682,13 +3592,13 @@ randomkey() break; } + if (program_state.input_state == commandInp) + last_c = c; return c; } void -random_response(buf, sz) -char *buf; -int sz; +random_response(char *buf, int sz) { char c; int count = 0; @@ -4708,229 +3618,231 @@ int sz; } int -rnd_extcmd_idx(VOID_ARGS) +rnd_extcmd_idx(void) { return rn2(extcmdlist_length + 1) - 1; } -int -ch2spkeys(c, start, end) -char c; -int start,end; +staticfn void +reset_cmd_vars(boolean reset_cmdq) { - int i; - - for (i = start; i <= end; i++) - if (Cmd.spkeys[i] == c) - return i; - return NHKF_ESC; + svc.context.run = 0; + svc.context.nopick = svc.context.forcefight = FALSE; + svc.context.move = svc.context.mv = FALSE; + gd.domove_attempting = 0; + gm.multi = 0; + iflags.menu_requested = FALSE; + svc.context.travel = svc.context.travel1 = 0; + if (gt.travelmap) { + selection_free(gt.travelmap, TRUE); + gt.travelmap = NULL; + } + if (reset_cmdq) { + cmdq_clear(CQ_CANNED); + cmdq_clear(CQ_REPEAT); + } } void -rhack(cmd) -register char *cmd; +rhack(int key) { - int spkey; - boolean prefix_seen, bad_command, - firsttime = (cmd == 0); + boolean bad_command, firsttime = (key == 0); + struct _cmd_queue cq, *cmdq = NULL; + const struct ext_func_tab *cmdq_ec = 0, *prefix_seen = 0; + boolean was_m_prefix = FALSE; + int (*func)(void) = dummyfunction; iflags.menu_requested = FALSE; + svc.context.nopick = 0; + got_prefix_input: #ifdef SAFERHANGUP if (program_state.done_hup) end_of_input(); #endif - if (firsttime) { - context.nopick = 0; - cmd = parse(); - } - if (*cmd == Cmd.spkeys[NHKF_ESC]) { - context.move = FALSE; - return; - } - if (*cmd == DOAGAIN && !in_doagain && saveq[0]) { - in_doagain = TRUE; - stail = 0; - rhack((char *) 0); /* read and execute command */ - in_doagain = FALSE; + if ((cmdq = cmdq_pop()) != 0) { + /* doing queued commands */ + cq = *cmdq; + free(cmdq); + if (cq.typ == CMDQ_EXTCMD && (cmdq_ec = cq.ec_entry) != 0) + goto do_cmdq_extcmd; + /* already handled a queued command (goto do_cmdq_extcmd); + if something other than a key is queued, we'll drop down + to the !*cmd handling which clears out the command-queue */ + key = (cq.typ == CMDQ_KEY) ? cq.key : 0; + } else if (firsttime) { + key = parse(); + /* parse() pushed a cmd but didn't return any key */ + if (!key && cmdq_peek(CQ_CANNED)) + goto got_prefix_input; + } + + /* if there's no command, there's nothing to do except reset */ + if (!key || key == (char) 0377 + || key == gc.Cmd.spkeys[NHKF_ESC]) { + if (key == gc.Cmd.spkeys[NHKF_ESC]) + /* don't perform next sanity check if player typed ESC for + the current command, similar to handling for CMD_INSANE + flag below (^P and ^R) */ + iflags.sanity_no_check = iflags.sanity_check; + else + nhbell(); + reset_cmd_vars(TRUE); return; } - /* Special case of *cmd == ' ' handled better below */ - if (!*cmd || *cmd == (char) 0377) { - nhbell(); - context.move = FALSE; - return; /* probably we just had an interrupt */ - } /* handle most movement commands */ - prefix_seen = FALSE; - context.travel = context.travel1 = 0; - spkey = ch2spkeys(*cmd, NHKF_RUN, NHKF_CLICKLOOK); - - switch (spkey) { - case NHKF_RUSH: - if (movecmd(cmd[1])) { - context.run = 2; - domove_attempting |= DOMOVE_RUSH; - } else - prefix_seen = TRUE; - break; - case NHKF_RUN2: - if (!Cmd.num_pad) - break; - /*FALLTHRU*/ - case NHKF_RUN: - if (movecmd(lowc(cmd[1]))) { - context.run = 3; - domove_attempting |= DOMOVE_RUSH; - } else - prefix_seen = TRUE; - break; - case NHKF_FIGHT2: - if (!Cmd.num_pad) - break; - /*FALLTHRU*/ - /* Effects of movement commands and invisible monsters: - * m: always move onto space (even if 'I' remembered) - * F: always attack space (even if 'I' not remembered) - * normal movement: attack if 'I', move otherwise. - */ - case NHKF_FIGHT: - if (movecmd(cmd[1])) { - context.forcefight = 1; - domove_attempting |= DOMOVE_WALK; - } else - prefix_seen = TRUE; - break; - case NHKF_NOPICKUP: - if (movecmd(cmd[1]) || u.dz) { - context.run = 0; - context.nopick = 1; - if (!u.dz) - domove_attempting |= DOMOVE_WALK; - else - cmd[0] = cmd[1]; /* "m<" or "m>" */ - } else - prefix_seen = TRUE; - break; - case NHKF_RUN_NOPICKUP: - if (movecmd(lowc(cmd[1]))) { - context.run = 1; - context.nopick = 1; - domove_attempting |= DOMOVE_RUSH; - } else - prefix_seen = TRUE; - break; - case NHKF_DOINV: - if (!Cmd.num_pad) - break; - (void) ddoinv(); /* a convenience borrowed from the PC */ - context.move = FALSE; - multi = 0; - return; - case NHKF_CLICKLOOK: - if (iflags.clicklook) { - context.move = FALSE; - do_look(2, &clicklook_cc); - } - return; - case NHKF_TRAVEL: - if (flags.travelcmd) { - context.travel = 1; - context.travel1 = 1; - context.run = 8; - context.nopick = 1; - domove_attempting |= DOMOVE_RUSH; - break; - } - /*FALLTHRU*/ - default: - if (movecmd(*cmd)) { /* ordinary movement */ - context.run = 0; /* only matters here if it was 8 */ - domove_attempting |= DOMOVE_WALK; - } else if (movecmd(Cmd.num_pad ? unmeta(*cmd) : lowc(*cmd))) { - context.run = 1; - domove_attempting |= DOMOVE_RUSH; - } else if (movecmd(unctrl(*cmd))) { - context.run = 3; - domove_attempting |= DOMOVE_RUSH; - } - break; - } - - /* some special prefix handling */ - /* overload 'm' prefix to mean "request a menu" */ - if (prefix_seen && cmd[0] == Cmd.spkeys[NHKF_REQMENU]) { - /* (for func_tab cast, see below) */ - const struct ext_func_tab *ft = Cmd.commands[cmd[1] & 0xff]; - int NDECL((*func)) = ft ? ((struct ext_func_tab *) ft)->ef_funct : 0; - - if (func && accept_menu_prefix(func)) { - iflags.menu_requested = TRUE; - ++cmd; - } - } + svc.context.travel = svc.context.travel1 = 0; + { + const struct ext_func_tab *tlist; + int res; - if (((domove_attempting & (DOMOVE_RUSH | DOMOVE_WALK)) != 0L) - && !context.travel && !dxdy_moveok()) { - /* trying to move diagonally as a grid bug; - this used to be treated by movecmd() as not being - a movement attempt, but that didn't provide for any - feedback and led to strangeness if the key pressed - ('u' in particular) was overloaded for num_pad use */ - You_cant("get there from here..."); - context.run = 0; - context.nopick = context.forcefight = FALSE; - context.move = context.mv = FALSE; - multi = 0; - return; - } + gc.cmd_bind = cmdbind_get(key & 0xFF); - if ((domove_attempting & DOMOVE_WALK) != 0L) { - if (multi) - context.mv = TRUE; - domove(); - context.forcefight = 0; - return; - } else if ((domove_attempting & DOMOVE_RUSH) != 0L) { - if (firsttime) { - if (!multi) - multi = max(COLNO, ROWNO); - u.last_str_turn = 0; - } - context.mv = TRUE; - domove(); - return; - } else if (prefix_seen && cmd[1] == Cmd.spkeys[NHKF_ESC]) { - /* */ - /* don't report "unknown command" for change of heart... */ - bad_command = FALSE; - } else if (*cmd == ' ' && !flags.rest_on_space) { - bad_command = TRUE; /* skip cmdlist[] loop */ - - /* handle all other commands */ - } else { - register const struct ext_func_tab *tlist; - int res, NDECL((*func)); - - /* current - use *cmd to directly index cmdlist array */ - if ((tlist = Cmd.commands[*cmd & 0xff]) != 0) { - if (!wizard && (tlist->flags & WIZMODECMD)) { - You_cant("do that!"); - res = 0; - } else if (u.uburied && !(tlist->flags & IFBURIED)) { - You_cant("do that while you are buried!"); - res = 0; + do_cmdq_extcmd: + if (cmdq_ec) + tlist = cmdq_ec; + else + tlist = gc.cmd_bind ? gc.cmd_bind->cmd : NULL; + + /* current - use key to directly index cmdlist array */ + if (tlist != 0) { + if (!can_do_extcmd(tlist)) { + /* can_do_extcmd() already gave a message */ + reset_cmd_vars(TRUE); + res = ECMD_OK; + } else if (prefix_seen && !(tlist->flags & PREFIXCMD) + && !(tlist->flags & (was_m_prefix ? CMD_M_PREFIX + : CMD_gGF_PREFIX))) { + char pfxidx = cmd_from_func(prefix_seen->ef_funct); + const char *which = (pfxidx != 0) ? visctrl(pfxidx) + : (prefix_seen->ef_funct == do_reqmenu) + ? "move-no-pickup or request-menu" + : prefix_seen->ef_txt; + + /* + * We got a prefix previously and looped for another + * command instead of returning, but the command we got + * doesn't accept a prefix. The feedback here supersedes + * the former call to help_dir() (for 'bad_command' below). + */ + if (was_m_prefix) { + custompline(SUPPRESS_HISTORY, + "The %s command does not accept '%s' prefix.", + tlist->ef_txt, which); + } else { + uchar ch = tlist->key; + boolean up = (ch == '<' || tlist->ef_funct == doup), + down = (ch == '>' || tlist->ef_funct == dodown); + + pline( + "The '%s' prefix should be followed by a movement command%s.", + which, + (up || down) ? " other than up or down" : ""); + } + res = ECMD_FAIL; + prefix_seen = 0; } else { /* we discard 'const' because some compilers seem to have trouble with the pointer passed to set_occupation() */ func = ((struct ext_func_tab *) tlist)->ef_funct; - if (tlist->f_text && !occupation && multi) - set_occupation(func, tlist->f_text, multi); + if (tlist->f_text && !go.occupation && gm.multi) + set_occupation(func, tlist->f_text, gm.multi); + ge.ext_tlist = NULL; + + if (!gi.in_doagain && func != do_repeat && func != doextcmd) { + if (!prefix_seen) + cmdq_clear(CQ_REPEAT); + cmdq_add_ec(CQ_REPEAT, + ((struct ext_func_tab *) tlist)->ef_funct); + } else { + if (func == doextcmd) { + cmdq_clear(CQ_REPEAT); + } + } + /* some commands shouldn't trigger sanity_check() because + if it produces output that might interfere with them; + note: if sanity_check is False, this has no effect */ + if ((tlist->flags & CMD_INSANE) != 0) + iflags.sanity_no_check = iflags.sanity_check; + res = (*func)(); /* perform the command */ + /* if 'func' is doextcmd(), 'tlist' is for Cmd.commands['#'] + rather than for the command that doextcmd() just ran; + doextcmd() notifies us what that was via ext_tlist; + other commands leave it Null */ + if (ge.ext_tlist) { + tlist = ge.ext_tlist, ge.ext_tlist = NULL; + /* Add the command post-execution */ + cmdq_add_ec(CQ_REPEAT, + ((struct ext_func_tab *) tlist)->ef_funct); + /* shift the command to first */ + cmdq_shift(CQ_REPEAT); + } + + if ((tlist->flags & PREFIXCMD) != 0) { + /* it was a prefix command, mark and get another cmd */ + if ((res & ECMD_CANCEL) != 0) { + /* prefix commands cancel if pressed twice */ + reset_cmd_vars(TRUE); + return; + } + prefix_seen = tlist; + cmdq_ec = NULL; + if (func == do_reqmenu) + was_m_prefix = TRUE; + goto got_prefix_input; + } else if (!(tlist->flags & MOVEMENTCMD) + && gd.domove_attempting) { + /* not a movement command, but a move prefix earlier? */ + ; /* just do nothing */ + } else if (((gd.domove_attempting + & (DOMOVE_RUSH | DOMOVE_WALK)) != 0L) + && !svc.context.travel && !dxdy_moveok()) { + /* trying to move diagonally as a grid bug */ + You_cant("get there from here..."); + reset_cmd_vars(TRUE); + return; + } else if ((gd.domove_attempting & DOMOVE_WALK) != 0L) { + if (gm.multi) + svc.context.mv = TRUE; + domove(); + svc.context.forcefight = 0; + iflags.menu_requested = FALSE; + return; + } else if ((gd.domove_attempting & DOMOVE_RUSH) != 0L) { + if (firsttime) { + if (!gm.multi) + gm.multi = max(COLNO, ROWNO); + u.last_str_turn = 0; + } + svc.context.mv = TRUE; + domove(); + iflags.menu_requested = FALSE; + return; + } + prefix_seen = 0; + } + /* it is possible to have a result of (ECMD_TIME|ECMD_CANCEL) + [for example, using 'f'ire, manually filling quiver with + wielded weapon or dual-wielded swap-weapon, then cancelling + at the direction prompt; using time to unwield should take + precedence over general cancellation] */ + if ((res & (ECMD_CANCEL | ECMD_FAIL)) != 0) { + /* command was canceled by user, maybe they declined to + pick an object to act on, or command failed to finish */ + reset_cmd_vars(TRUE); + } else if ((res & (ECMD_OK | ECMD_TIME)) == ECMD_OK) { + reset_cmd_vars(gm.multi < 0); } - if (!res) { - context.move = FALSE; - multi = 0; + /* reset_cmd_vars() sets context.move to False so we might + need to change it [back] to True */ + if ((res & ECMD_TIME) != 0) { + svc.context.move = TRUE; + if (func != dokick) { + /* hero did something else than kicking a location; + reset the location, so pets don't avoid it */ + gk.kickedloc.x = 0, gk.kickedloc.y = 0; + } } return; } @@ -4939,97 +3851,90 @@ register char *cmd; } if (bad_command) { - char expcmd[20]; /* we expect 'cmd' to point to 1 or 2 chars */ - char c, c1 = cmd[1]; - - expcmd[0] = '\0'; - while ((c = *cmd++) != '\0') - Strcat(expcmd, visctrl(c)); /* add 1..4 chars plus terminator */ - - if (!prefix_seen || !help_dir(c1, spkey, "Invalid direction key!")) - Norep("Unknown command '%s'.", expcmd); + custompline(SUPPRESS_HISTORY, "Unknown command '%s'.", visctrl(key)); + cmdq_clear(CQ_CANNED); + cmdq_clear(CQ_REPEAT); + iflags.sanity_no_check = iflags.sanity_check; /* skip sanity check */ } /* didn't move */ - context.move = FALSE; - multi = 0; + svc.context.move = FALSE; + gm.multi = 0; return; } /* convert an x,y pair into a direction code */ int -xytod(x, y) -schar x, y; +xytodir(int x, int y) { - register int dd; + int dd; - for (dd = 0; dd < 8; dd++) + for (dd = 0; dd < N_DIRS; dd++) if (x == xdir[dd] && y == ydir[dd]) return dd; - return -1; + return DIR_ERR; } /* convert a direction code into an x,y pair */ void -dtoxy(cc, dd) -coord *cc; -register int dd; +dirtocoord(coord *cc, int dd) { - cc->x = xdir[dd]; - cc->y = ydir[dd]; - return; + if (dd > DIR_ERR && dd < N_DIRS_Z) { + cc->x = xdir[dd]; + cc->y = ydir[dd]; + } } /* also sets u.dz, but returns false for <> */ int -movecmd(sym) -char sym; +movecmd(char sym, int mode) { - register const char *dp = index(Cmd.dirchars, sym); + int d = DIR_ERR; + struct Cmd_bind *bind = cmdbind_get(sym); + + if (bind && bind->cmd) { + int (*fnc)(void) = bind->cmd->ef_funct; + + if (mode == MV_ANY) { + for (d = N_DIRS_Z - 1; d > DIR_ERR; d--) + if (fnc == move_funcs[d][MV_WALK] + || fnc == move_funcs[d][MV_RUN] + || fnc == move_funcs[d][MV_RUSH]) + break; + } else { + for (d = N_DIRS_Z - 1; d > DIR_ERR; d--) + if (fnc == move_funcs[d][mode]) + break; + } + } - u.dz = 0; - if (!dp || !*dp) - return 0; - u.dx = xdir[dp - Cmd.dirchars]; - u.dy = ydir[dp - Cmd.dirchars]; - u.dz = zdir[dp - Cmd.dirchars]; -#if 0 /* now handled elsewhere */ - if (u.dx && u.dy && NODIAG(u.umonnum)) { - u.dx = u.dy = 0; - return 0; + if (d != DIR_ERR) { + u.dx = xdir[d]; + u.dy = ydir[d]; + u.dz = zdir[d]; + return !u.dz; } -#endif - return !u.dz; + u.dz = 0; + return 0; } -/* grid bug handling which used to be in movecmd() */ +/* grid bug handling */ int -dxdy_moveok() +dxdy_moveok(void) { if (u.dx && u.dy && NODIAG(u.umonnum)) u.dx = u.dy = 0; return u.dx || u.dy; } -/* decide whether a character (user input keystroke) requests screen repaint */ +/* decide whether character (user input keystroke) requests screen repaint */ boolean -redraw_cmd(c) -char c; +redraw_cmd(char c) { - return (boolean) (c == Cmd.spkeys[NHKF_REDRAW] - || (Cmd.num_pad && c == Cmd.spkeys[NHKF_REDRAW2])); -} + uchar uc = (uchar) c; + struct Cmd_bind *bind = cmdbind_get(uc); -boolean -prefix_cmd(c) -char c; -{ - return (c == Cmd.spkeys[NHKF_RUSH] - || c == Cmd.spkeys[NHKF_RUN] - || c == Cmd.spkeys[NHKF_NOPICKUP] - || c == Cmd.spkeys[NHKF_RUN_NOPICKUP] - || c == Cmd.spkeys[NHKF_FIGHT] - || (Cmd.num_pad && (c == Cmd.spkeys[NHKF_RUN2] - || c == Cmd.spkeys[NHKF_FIGHT2]))); + return (boolean) (bind && bind->cmd + && bind->cmd->ef_funct == doredraw); } /* @@ -5043,12 +3948,13 @@ char c; * Returns non-zero if coordinates in cc are valid. */ int -get_adjacent_loc(prompt, emsg, x, y, cc) -const char *prompt, *emsg; -xchar x, y; -coord *cc; +get_adjacent_loc( + const char *prompt, + const char *emsg, + coordxy x, coordxy y, + coord *cc) { - xchar new_x, new_y; + coordxy new_x, new_y; if (!getdir(prompt)) { pline1(Never_mind); return 0; @@ -5066,41 +3972,156 @@ coord *cc; return 1; } +/* prompt for a direction (specified via movement keystroke) and return it + in u.dx, u.dy, and u.dz; function return value is 1 for ok, 0 otherwise */ int -getdir(s) -const char *s; +getdir(const char *s) { char dirsym; int is_mov; + struct _cmd_queue *cmdq = cmdq_pop(); + + if (cmdq) { + if (cmdq->typ == CMDQ_DIR) { + if (!cmdq->dirz) { + dirsym = gc.Cmd.dirchars[xytodir(cmdq->dirx, cmdq->diry)]; + } else { + dirsym = gc.Cmd.dirchars[(cmdq->dirz > 0) ? DIR_DOWN + : DIR_UP]; + } + } else if (cmdq->typ == CMDQ_KEY) { + dirsym = cmdq->key; + } else { + cmdq_clear(CQ_CANNED); + dirsym = '\0'; + impossible("getdir: command queue had no dir?"); + } + free(cmdq); + goto got_dirsym; + } retry: - if (in_doagain || *readchar_queue) + program_state.input_state = getdirInp; + if (gi.in_doagain || *readchar_queue) { dirsym = readchar(); - else + } else { dirsym = yn_function((s && *s != '^') ? s : "In what direction?", - (char *) 0, '\0'); + (char *) 0, '\0', FALSE); + + /* for the fuzzer, usually force the result to be a valid direction, + but sometimes let it exercise the invalid direction code; we + don't try to enforce no-diagonal for hero in grid bug form since + things like '^' to look at adjacent trap shouldn't be bound by + that (caller is expected to handle situations where it matters) */ + if (iflags.debug_fuzzer && rn2(20)) { + switch (rn2(20)) { + case 0: + dirsym = gc.Cmd.spkeys[rn2(2) ? NHKF_GETDIR_SELF : NHKF_ESC]; + break; + case 1: + dirsym = gc.Cmd.dirchars[rn2(2) ? DIR_DOWN : DIR_UP]; + break; + default: + dirsym = gc.Cmd.dirchars[rn2(N_DIRS)]; + break; + } + } + } /* remove the prompt string so caller won't have to */ clear_nhwindow(WIN_MESSAGE); if (redraw_cmd(dirsym)) { /* ^R */ - docrt(); /* redraw */ + docrt_flags(docrtRefresh); /* redraw */ goto retry; } - savech(dirsym); + if (!gi.in_doagain) + cmdq_add_key(CQ_REPEAT, dirsym); - if (dirsym == Cmd.spkeys[NHKF_GETDIR_SELF] - || dirsym == Cmd.spkeys[NHKF_GETDIR_SELF2]) { + got_dirsym: + if (dirsym == gc.Cmd.spkeys[NHKF_GETDIR_SELF] + || dirsym == gc.Cmd.spkeys[NHKF_GETDIR_SELF2]) { u.dx = u.dy = u.dz = 0; - } else if (!(is_mov = movecmd(dirsym)) && !u.dz) { + } else if (dirsym == gc.Cmd.spkeys[NHKF_GETDIR_MOUSE]) { + char qbuf[QBUFSZ]; + coord cc; + int pos, mod; + + /* + * For #therecmdmenu: + * Player has entered the 'simulated mouse' key ('_' by default) + * at the "which direction?" prompt so we use getpos() to get a + * simulated click after moving cursor to the desired location. + * + * getpos() returns 0..3 for period, comma, semi-colon, colon. + * We treat "," as left click and "." as right click due to + * their positions relative to each other on the keyboard. + * Using ";" as synonym for "," and ":" for "." is due to their + * shapes rather than to their keyboard location. + * + * Those keys aren't separately bindable for being treated as + * clicks but we do honor their getpos bindings if player has + * changed them. (Bound values might have scrambled keyboard + * locations relative to each other so ruin the memory aid of + * "," being left of ".".) + */ + Sprintf(qbuf, + "desired location, then type '%s' for left click, '%s' for right", + /* visctrl() cycles through several static buffers for its + return value so using two in the same expression is ok */ + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_PICK_Q]), /* ',' */ + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_PICK])); /* '.' */ + cc.x = u.ux, cc.y = u.uy; /* starting cursor location for getpos() */ + pos = getpos(&cc, TRUE, qbuf); + + if (pos < 0) { + /* ESC or other rejection */ + u.dx = u.dy = u.dz = 0; + mod = 0; /* neither CLICK_1 nor CLICK_2 */ + } else { + /* caller expects simulated click to be relative to hero's spot */ + u.dx = cc.x - u.ux; + u.dy = cc.y - u.uy; + /* non-zero getdir_click actually means ok to click farther than + one spot away from hero; adjacent click is always allowed */ + if (!iflags.getdir_click) { + u.dx = sgn(u.dx); + u.dy = sgn(u.dy); + } + u.dz = 0; + + switch (pos + NHKF_GETPOS_PICK) { + case NHKF_GETPOS_PICK_Q: /* 1: quick: ',' */ + case NHKF_GETPOS_PICK_O: /* 2: once: ';' */ + mod = CLICK_1; + break; + case NHKF_GETPOS_PICK: /* 0: normal: '.' */ + case NHKF_GETPOS_PICK_V: /* 3: verbose: ':' */ + mod = CLICK_2; + break; + default: + /* could plug in bound values for spkeys[NHKF_GETPOS_PICK],&c + but that feels like overkill for something which should + never happen; just show their default values */ + impossible("getpos successful but not one of [.,;:] (%d)", + pos); + mod = 0; /* neither CLICK_1 nor CLICK_2 */ + pos = -1; /* return failure */ + break; + } + } + if (iflags.getdir_click) + iflags.getdir_click = mod; + return (pos >= 0); + } else if (!(is_mov = movecmd(dirsym, MV_ANY)) && !u.dz) { boolean did_help = FALSE, help_requested; - if (!index(quitchars, dirsym)) { - help_requested = (dirsym == Cmd.spkeys[NHKF_GETDIR_HELP]); + if (!strchr(quitchars, dirsym)) { + help_requested = (dirsym == gc.Cmd.spkeys[NHKF_GETDIR_HELP]); if (help_requested || iflags.cmdassist) { did_help = help_dir((s && *s == '^') ? dirsym : '\0', - NHKF_ESC, + gc.Cmd.spkeys[NHKF_ESC], help_requested ? (const char *) 0 - : "Invalid direction key!"); + : "Invalid direction key!"); if (help_requested) goto retry; } @@ -5112,16 +4133,16 @@ const char *s; You_cant("orient yourself that direction."); return 0; } - if (!u.dz && (Stunned || (Confusion && !rn2(5)))) - confdir(); + if (!u.dz) + confdir(FALSE); return 1; } -STATIC_OVL void -show_direction_keys(win, centerchar, nodiag) -winid win; /* should specify a window which is using a fixed-width font... */ -char centerchar; /* '.' or '@' or ' ' */ -boolean nodiag; +staticfn void +show_direction_keys( + winid win, /* should specify a window which is using a fixed-width font */ + char centerchar, /* '.' or '@' or ' ' */ + boolean nodiag) { char buf[BUFSZ]; @@ -5129,26 +4150,36 @@ boolean nodiag; centerchar = ' '; if (nodiag) { - Sprintf(buf, " %c ", Cmd.move_N); + Sprintf(buf, " %s ", + visctrl(cmd_from_func(do_move_north))); putstr(win, 0, buf); putstr(win, 0, " | "); - Sprintf(buf, " %c- %c -%c", - Cmd.move_W, centerchar, Cmd.move_E); + Sprintf(buf, " %s- %c -%s", + visctrl(cmd_from_func(do_move_west)), + centerchar, + visctrl(cmd_from_func(do_move_east))); putstr(win, 0, buf); putstr(win, 0, " | "); - Sprintf(buf, " %c ", Cmd.move_S); + Sprintf(buf, " %s ", + visctrl(cmd_from_func(do_move_south))); putstr(win, 0, buf); } else { - Sprintf(buf, " %c %c %c", - Cmd.move_NW, Cmd.move_N, Cmd.move_NE); + Sprintf(buf, " %s %s %s", + visctrl(cmd_from_func(do_move_northwest)), + visctrl(cmd_from_func(do_move_north)), + visctrl(cmd_from_func(do_move_northeast))); putstr(win, 0, buf); putstr(win, 0, " \\ | / "); - Sprintf(buf, " %c- %c -%c", - Cmd.move_W, centerchar, Cmd.move_E); + Sprintf(buf, " %s- %c -%s", + visctrl(cmd_from_func(do_move_west)), + centerchar, + visctrl(cmd_from_func(do_move_east))); putstr(win, 0, buf); putstr(win, 0, " / | \\ "); - Sprintf(buf, " %c %c %c", - Cmd.move_SW, Cmd.move_S, Cmd.move_SE); + Sprintf(buf, " %s %s %s", + visctrl(cmd_from_func(do_move_southwest)), + visctrl(cmd_from_func(do_move_south)), + visctrl(cmd_from_func(do_move_southeast))); putstr(win, 0, buf); }; } @@ -5156,63 +4187,39 @@ boolean nodiag; /* explain choices if player has asked for getdir() help or has given an invalid direction after a prefix key ('F', 'g', 'm', &c), which might be bogus but could be up, down, or self when not applicable */ -STATIC_OVL boolean -help_dir(sym, spkey, msg) -char sym; -int spkey; /* NHKF_ code for prefix key, if one was used, or for ESC */ -const char *msg; +staticfn boolean +help_dir( + char sym, + uchar spkey, /* actual key; either prefix or ESC */ + const char *msg) { static const char wiz_only_list[] = "EFGIVW"; char ctrl; winid win; char buf[BUFSZ], buf2[BUFSZ], *explain; - const char *dothat, *how; - boolean prefixhandling, viawindow; + const char *dothat /*, *how */; + boolean prefixhandling /*, viawindow */; /* NHKF_ESC indicates that player asked for help at getdir prompt */ - viawindow = (spkey == NHKF_ESC || iflags.cmdassist); - prefixhandling = (spkey != NHKF_ESC); + /* viawindow = (spkey == gc.Cmd.spkeys[NHKF_ESC] || iflags.cmdassist); */ + prefixhandling = (spkey != gc.Cmd.spkeys[NHKF_ESC]); /* * Handling for prefix keys that don't want special directions. * Delivered via pline if 'cmdassist' is off, or instead of the * general message if it's on. */ dothat = "do that"; - how = " at"; /* for " at yourself"; not used for up/down */ - switch (spkey) { - case NHKF_NOPICKUP: - dothat = "move"; - break; - case NHKF_RUSH: - dothat = "rush"; - break; - case NHKF_RUN2: - if (!Cmd.num_pad) - break; - /*FALLTHRU*/ - case NHKF_RUN: - case NHKF_RUN_NOPICKUP: - dothat = "run"; - break; - case NHKF_FIGHT2: - if (!Cmd.num_pad) - break; - /*FALLTHRU*/ - case NHKF_FIGHT: - dothat = "fight"; - how = ""; /* avoid "fight at yourself" */ - break; - default: - prefixhandling = FALSE; - break; - } + /* how = " at"; */ /* for " at yourself"; not used for up/down */ buf[0] = '\0'; +#if 0 /* Since prefix keys got 'promoted' to commands, feedback for + * invalid prefix is done in rhack() these days. + */ /* for movement prefix followed by '.' or (numpad && 's') to mean 'self'; note: '-' for hands (inventory form of 'self') is not handled here */ if (prefixhandling - && (sym == Cmd.spkeys[NHKF_GETDIR_SELF] - || (Cmd.num_pad && sym == Cmd.spkeys[NHKF_GETDIR_SELF2]))) { + && (sym == gc.Cmd.spkeys[NHKF_GETDIR_SELF] + || (gc.Cmd.num_pad && sym == gc.Cmd.spkeys[NHKF_GETDIR_SELF2]))) { Sprintf(buf, "You can't %s%s yourself.", dothat, how); /* for movement prefix followed by up or down */ } else if (prefixhandling && (sym == '<' || sym == '>')) { @@ -5228,13 +4235,16 @@ const char *msg; if (prefixhandling) { if (!*buf) Sprintf(buf, "Invalid direction for '%s' prefix.", - visctrl(Cmd.spkeys[spkey])); + visctrl(spkey)); pline("%s", buf); return TRUE; } /* when 'cmdassist' is off and caller doesn't insist, do nothing */ return FALSE; } +#else + nhUse(prefixhandling); +#endif win = create_nhwindow(NHW_TEXT); if (!win) @@ -5255,9 +4265,9 @@ const char *msg; sym = highc(sym); /* @A-Z[ (note: letter() accepts '@') */ ctrl = (sym - 'A') + 1; /* 0-27 (note: 28-31 aren't applicable) */ if ((explain = dowhatdoes_core(ctrl, buf2)) != 0 - && (!index(wiz_only_list, sym) || wizard)) { + && (!strchr(wiz_only_list, sym) || wizard)) { Sprintf(buf, "Are you trying to use ^%c%s?", sym, - index(wiz_only_list, sym) ? "" + strchr(wiz_only_list, sym) ? "" : " as specified in the Guidebook"); putstr(win, 0, buf); putstr(win, 0, ""); @@ -5277,7 +4287,7 @@ const char *msg; putstr(win, 0, buf); show_direction_keys(win, !prefixhandling ? '.' : ' ', NODIAG(u.umonnum)); - if (!prefixhandling || spkey == NHKF_NOPICKUP) { + if (!prefixhandling) { /* NOPICKUP: unlike the other prefix keys, 'm' allows up/down for stair traversal; we won't get here when "m<" or "m>" has been given but we include up and down for 'm'+invalid_direction; @@ -5286,10 +4296,10 @@ const char *msg; putstr(win, 0, " < up"); putstr(win, 0, " > down"); if (!prefixhandling) { - int selfi = Cmd.num_pad ? NHKF_GETDIR_SELF2 : NHKF_GETDIR_SELF; + int selfi = gc.Cmd.num_pad ? NHKF_GETDIR_SELF2 : NHKF_GETDIR_SELF; Sprintf(buf, " %4s direct at yourself", - visctrl(Cmd.spkeys[selfi])); + visctrl(gc.Cmd.spkeys[selfi])); putstr(win, 0, buf); } } @@ -5305,103 +4315,253 @@ const char *msg; return TRUE; } +/* if hero is impaired, pick random movement direction */ void -confdir() +confdir(boolean force_impairment) { - register int x = NODIAG(u.umonnum) ? 2 * rn2(4) : rn2(8); + if (force_impairment || u_maybe_impaired()) { + int kmax = NODIAG(u.umonnum) ? (N_DIRS / 2) : N_DIRS, + k = (int) dirs_ord[rn2(kmax)]; - u.dx = xdir[x]; - u.dy = ydir[x]; + u.dx = xdir[k]; + u.dy = ydir[k]; + } return; } const char * -directionname(dir) -int dir; +directionname(int dir) { - static NEARDATA const char *const dirnames[] = { + static NEARDATA const char *const dirnames[N_DIRS_Z] = { "west", "northwest", "north", "northeast", "east", "southeast", "south", "southwest", "down", "up", }; - if (dir < 0 || dir >= SIZE(dirnames)) + if (dir < 0 || dir >= N_DIRS_Z) return "invalid"; return dirnames[dir]; } int -isok(x, y) -register int x, y; +isok(coordxy x, coordxy y) { /* x corresponds to curx, so x==1 is the first column. Ach. %% */ return x >= 1 && x <= COLNO - 1 && y >= 0 && y <= ROWNO - 1; } /* #herecmdmenu command */ -STATIC_PTR int -doherecmdmenu(VOID_ARGS) +staticfn int +doherecmdmenu(void) { - char ch = here_cmd_menu(TRUE); + char ch = here_cmd_menu(); - return ch ? 1 : 0; + return (ch && ch != '\033') ? ECMD_TIME : ECMD_OK; } /* #therecmdmenu command, a way to test there_cmd_menu without mouse */ -STATIC_PTR int -dotherecmdmenu(VOID_ARGS) +staticfn int +dotherecmdmenu(void) { char ch; + int dir, click; + coordxy x = gc.clicklook_cc.x; + coordxy y = gc.clicklook_cc.y; - if (!getdir((const char *) 0) || !isok(u.ux + u.dx, u.uy + u.dy)) - return 0; + iflags.getdir_click = CLICK_1 | CLICK_2; /* allow 'far' click */ + + if (isok(x, y)) { + if (x == u.ux && y == u.uy) + ch = here_cmd_menu(); + else + ch = there_cmd_menu(x, y, iflags.getdir_click); + gc.clicklook_cc.x = gc.clicklook_cc.y = -1; + iflags.getdir_click = 0; + return (ch && ch != '\033') ? ECMD_TIME : ECMD_OK; + } + + dir = getdir((const char *) 0); + click = iflags.getdir_click; + iflags.getdir_click = 0; + + if (!dir || !isok(u.ux + u.dx, u.uy + u.dy)) + return ECMD_CANCEL; if (u.dx || u.dy) - ch = there_cmd_menu(TRUE, u.ux + u.dx, u.uy + u.dy); + ch = there_cmd_menu(u.ux + u.dx, u.uy + u.dy, click); else - ch = here_cmd_menu(TRUE); + ch = here_cmd_menu(); - return ch ? 1 : 0; + return (ch && ch != '\033') ? ECMD_TIME : ECMD_OK; } -STATIC_OVL void -add_herecmd_menuitem(win, func, text) -winid win; -int NDECL((*func)); -const char *text; +/* commands for [t]herecmdmenu */ +enum menucmd { + MCMD_NOTHING = 0, + MCMD_OPEN_DOOR, + MCMD_LOCK_DOOR, + MCMD_UNTRAP_DOOR, + MCMD_KICK_DOOR, + MCMD_CLOSE_DOOR, + MCMD_SEARCH, + MCMD_LOOK_TRAP, + MCMD_UNTRAP_TRAP, + MCMD_MOVE_DIR, + MCMD_RIDE, + MCMD_REMOVE_SADDLE, + MCMD_APPLY_SADDLE, + MCMD_TALK, + MCMD_NAME, + + MCMD_QUAFF, + MCMD_DIP, + MCMD_SIT, + MCMD_UP, + MCMD_DOWN, + MCMD_DISMOUNT, + MCMD_MONABILITY, + MCMD_PICKUP, + MCMD_LOOT, + MCMD_TIP, + MCMD_EAT, + MCMD_DROP, + MCMD_REST, + MCMD_LOOK_HERE, + MCMD_LOOK_AT, + MCMD_ATTACK_NEXT2U, + MCMD_UNTRAP_HERE, + MCMD_OFFER, + MCMD_INVENTORY, + MCMD_CAST_SPELL, + + MCMD_THROW_OBJ, + MCMD_TRAVEL, +}; + +staticfn void +mcmd_addmenu(winid win, int act, const char *txt) { - char ch; anything any; + int clr = NO_COLOR; + + /* TODO: fixed letters for the menu entries? */ + any = cg.zeroany; + any.a_int = act; + add_menu(win, &nul_glyphinfo, &any, '\0', 0, ATR_NONE, clr, txt, + MENU_ITEMFLAGS_NONE); +} + +/* command menu entries when targeting self */ +staticfn int +there_cmd_menu_self(winid win, coordxy x, coordxy y, int *act UNUSED) +{ + int K = 0; + char buf[BUFSZ]; + schar typ = levl[x][y].typ; + stairway *stway = stairway_at(x, y); + struct trap *ttmp; + + if (!u_at(x, y)) + return K; + + if ((IS_FOUNTAIN(typ) || IS_SINK(typ)) && can_reach_floor(FALSE)) { + Sprintf(buf, "Drink from the %s", + defsyms[IS_FOUNTAIN(typ) ? S_fountain : S_sink].explanation); + mcmd_addmenu(win, MCMD_QUAFF, buf), ++K; + } + if (IS_FOUNTAIN(typ) && can_reach_floor(FALSE)) + mcmd_addmenu(win, MCMD_DIP, "Dip something into the fountain"), ++K; + if (IS_THRONE(typ)) + mcmd_addmenu(win, MCMD_SIT, "Sit on the throne"), ++K; + if (IS_ALTAR(typ)) + mcmd_addmenu(win, MCMD_OFFER, "Sacrifice something on the altar"), ++K; + + if (stway && stway->up) { + Sprintf(buf, "Go up the %s", + stway->isladder ? "ladder" : "stairs"); + mcmd_addmenu(win, MCMD_UP, buf), ++K; + } + if (stway && !stway->up) { + Sprintf(buf, "Go down the %s", + stway->isladder ? "ladder" : "stairs"); + mcmd_addmenu(win, MCMD_DOWN, buf), ++K; + } + if (u.usteed) { /* another movement choice */ + Sprintf(buf, "Dismount %s", + x_monnam(u.usteed, ARTICLE_THE, (char *) 0, + SUPPRESS_SADDLE, FALSE)); + mcmd_addmenu(win, MCMD_DISMOUNT, buf), ++K; + } + +#if 0 + if (Upolyd) { /* before objects */ + Sprintf(buf, "Use %s special ability", + s_suffix(pmname(&mons[u.umonnum], Ugender))); + mcmd_addmenu(win, MCMD_MONABILITY, buf), ++K; + } +#endif + + if (OBJ_AT(x, y)) { + struct obj *otmp = svl.level.objects[x][y]; + + Sprintf(buf, "Pick up %s", otmp->nexthere ? "items" : doname(otmp)); + mcmd_addmenu(win, MCMD_PICKUP, buf), ++K; + + if (Is_container(otmp)) { + Sprintf(buf, "Loot %s", doname(otmp)); + mcmd_addmenu(win, MCMD_LOOT, buf), ++K; + + Sprintf(buf, "Tip %s", doname(otmp)); + mcmd_addmenu(win, MCMD_TIP, buf), ++K; + } + if (otmp->oclass == FOOD_CLASS) { + Sprintf(buf, "Eat %s", doname(otmp)); + mcmd_addmenu(win, MCMD_EAT, buf), ++K; + } + } + + + if (gi.invent) { + mcmd_addmenu(win, MCMD_INVENTORY, "Inventory"), ++K; + mcmd_addmenu(win, MCMD_DROP, "Drop items"), ++K; + } + mcmd_addmenu(win, MCMD_REST, "Rest one turn"), ++K; + mcmd_addmenu(win, MCMD_SEARCH, "Search around you"), ++K; + mcmd_addmenu(win, MCMD_LOOK_HERE, "Look at what is here"), ++K; + + if (num_spells() > 0) + mcmd_addmenu(win, MCMD_CAST_SPELL, "Cast a spell"), ++K; - if ((ch = cmd_from_func(func)) != '\0') { - any = zeroany; - any.a_nfunc = func; - add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, text, MENU_UNSELECTED); + if ((ttmp = t_at(x, y)) != 0 && ttmp->tseen) { + if (ttmp->ttyp != VIBRATING_SQUARE) + mcmd_addmenu(win, MCMD_UNTRAP_HERE, + "Attempt to disarm trap"), ++K; } + return K; } -STATIC_OVL char -there_cmd_menu(doit, x, y) -boolean doit; -int x, y; +/* add entries to there_cmd_menu, when x,y is next to hero */ +staticfn int +there_cmd_menu_next2u( + winid win, + coordxy x, coordxy y, + int mod, + int *act) { - winid win; - char ch; + int K = 0; char buf[BUFSZ]; schar typ = levl[x][y].typ; - int npick, K = 0; - menu_item *picks = (menu_item *) 0; struct trap *ttmp; struct monst *mtmp; - win = create_nhwindow(NHW_MENU); - start_menu(win); + if (!next2u(x, y)) + return K; if (IS_DOOR(typ)) { boolean key_or_pick, card; int dm = levl[x][y].doormask; if ((dm & (D_CLOSED | D_LOCKED))) { - add_herecmd_menuitem(win, doopen, "Open the door"), ++K; + mcmd_addmenu(win, MCMD_OPEN_DOOR, "Open the door"), ++K; /* unfortunately there's no lknown flag for doors to remember the locked/unlocked state */ key_or_pick = (carrying(SKELETON_KEY) || carrying(LOCK_PICK)); @@ -5409,28 +4569,33 @@ int x, y; if (key_or_pick || card) { Sprintf(buf, "%sunlock the door", key_or_pick ? "lock or " : ""); - add_herecmd_menuitem(win, doapply, upstart(buf)), ++K; + mcmd_addmenu(win, MCMD_LOCK_DOOR, upstart(buf)), ++K; } /* unfortunately there's no tknown flag for doors (or chests) to remember whether a trap had been found */ - add_herecmd_menuitem(win, dountrap, - "Search the door for a trap"), ++K; + mcmd_addmenu(win, MCMD_UNTRAP_DOOR, + "Search the door for a trap"), ++K; /* [what about #force?] */ - add_herecmd_menuitem(win, dokick, "Kick the door"), ++K; - } else if ((dm & D_ISOPEN)) { - add_herecmd_menuitem(win, doclose, "Close the door"), ++K; + mcmd_addmenu(win, MCMD_KICK_DOOR, "Kick the door"), ++K; + } else if ((dm & D_ISOPEN) && (mod == CLICK_2)) { + mcmd_addmenu(win, MCMD_CLOSE_DOOR, "Close the door"), ++K; } } if (typ <= SCORR) - add_herecmd_menuitem(win, dosearch, "Search for secret doors"), ++K; + mcmd_addmenu(win, MCMD_SEARCH, "Search for secret doors"), ++K; if ((ttmp = t_at(x, y)) != 0 && ttmp->tseen) { - add_herecmd_menuitem(win, doidtrap, "Examine trap"), ++K; + mcmd_addmenu(win, MCMD_LOOK_TRAP, "Examine trap"), ++K; if (ttmp->ttyp != VIBRATING_SQUARE) - add_herecmd_menuitem(win, dountrap, "Attempt to disarm trap"), ++K; + mcmd_addmenu(win, MCMD_UNTRAP_TRAP, + "Attempt to disarm trap"), ++K; + mcmd_addmenu(win, MCMD_MOVE_DIR, "Move on the trap"), ++K; } + if (levl[x][y].glyph == objnum_to_glyph(BOULDER)) + mcmd_addmenu(win, MCMD_MOVE_DIR, "Push the boulder"), ++K; + mtmp = m_at(x, y); if (mtmp && !canspotmon(mtmp)) mtmp = 0; @@ -5440,249 +4605,399 @@ int x, y; if (!u.usteed) { Sprintf(buf, "Ride %s", mnam); - add_herecmd_menuitem(win, doride, buf), ++K; + mcmd_addmenu(win, MCMD_RIDE, buf), ++K; } Sprintf(buf, "Remove saddle from %s", mnam); - add_herecmd_menuitem(win, doloot, buf), ++K; + mcmd_addmenu(win, MCMD_REMOVE_SADDLE, buf), ++K; } if (mtmp && can_saddle(mtmp) && !which_armor(mtmp, W_SADDLE) - && carrying(SADDLE)) { - Sprintf(buf, "Put saddle on %s", mon_nam(mtmp)), ++K; - add_herecmd_menuitem(win, doapply, buf); - } -#if 0 - if (mtmp || glyph_is_invisible(glyph_at(x, y))) { - /* "Attack %s", mtmp ? mon_nam(mtmp) : "unseen creature" */ - } else { - /* "Move %s", direction */ - } -#endif - - if (K) { - end_menu(win, "What do you want to do?"); - npick = select_menu(win, PICK_ONE, &picks); - } else { - pline("No applicable actions."); - npick = 0; + && carrying(SADDLE)) { + Sprintf(buf, "Put saddle on %s", mon_nam(mtmp)); + mcmd_addmenu(win, MCMD_APPLY_SADDLE, buf), ++K; } - destroy_nhwindow(win); - ch = '\0'; - if (npick > 0) { - int NDECL((*func)) = picks->item.a_nfunc; - free((genericptr_t) picks); + if (mtmp && (mtmp->mpeaceful || mtmp->mtame)) { + Sprintf(buf, "Talk to %s", mon_nam(mtmp)); + mcmd_addmenu(win, MCMD_TALK, buf), ++K; - if (doit) { - int ret = (*func)(); + Sprintf(buf, "Swap places with %s", mon_nam(mtmp)); + mcmd_addmenu(win, MCMD_MOVE_DIR, buf), ++K; - ch = (char) ret; - } else { - ch = cmd_from_func(func); - } + Sprintf(buf, "%s %s", + !has_mgivenname(mtmp) ? "Name" : "Rename", + mon_nam(mtmp)); + mcmd_addmenu(win, MCMD_NAME, buf), ++K; } - return ch; + + if ((mtmp && !(mtmp->mpeaceful || mtmp->mtame)) + || glyph_is_invisible(glyph_at(x, y))) { + Sprintf(buf, "Attack %s", mtmp ? mon_nam(mtmp) : "unseen creature"); + mcmd_addmenu(win, MCMD_ATTACK_NEXT2U, buf), ++K; + /* attacking overrides any other automatic action */ + *act = MCMD_ATTACK_NEXT2U; + } else { + /* "Move %s", direction - handled below */ + } + return K; } -STATIC_OVL char -here_cmd_menu(doit) -boolean doit; +staticfn int +there_cmd_menu_far(winid win, coordxy x, coordxy y, int mod) { - winid win; - char ch; - char buf[BUFSZ]; - schar typ = levl[u.ux][u.uy].typ; - int npick; - menu_item *picks = (menu_item *) 0; + int K = 0; - win = create_nhwindow(NHW_MENU); - start_menu(win); + if (mod == CLICK_1) { + if (linedup(u.ux, u.uy, x, y, 1) + && dist2(u.ux, u.uy, x, y) < 18*18) + mcmd_addmenu(win, MCMD_THROW_OBJ, "Throw something"), ++K; - if (IS_FOUNTAIN(typ) || IS_SINK(typ)) { - Sprintf(buf, "Drink from the %s", - defsyms[IS_FOUNTAIN(typ) ? S_fountain : S_sink].explanation); - add_herecmd_menuitem(win, dodrink, buf); + mcmd_addmenu(win, MCMD_TRAVEL, "Travel here"), ++K; } - if (IS_FOUNTAIN(typ)) - add_herecmd_menuitem(win, dodip, - "Dip something into the fountain"); - if (IS_THRONE(typ)) - add_herecmd_menuitem(win, dosit, - "Sit on the throne"); + return K; +} - if ((u.ux == xupstair && u.uy == yupstair) - || (u.ux == sstairs.sx && u.uy == sstairs.sy && sstairs.up) - || (u.ux == xupladder && u.uy == yupladder)) { - Sprintf(buf, "Go up the %s", - (u.ux == xupladder && u.uy == yupladder) - ? "ladder" : "stairs"); - add_herecmd_menuitem(win, doup, buf); - } - if ((u.ux == xdnstair && u.uy == ydnstair) - || (u.ux == sstairs.sx && u.uy == sstairs.sy && !sstairs.up) - || (u.ux == xdnladder && u.uy == ydnladder)) { - Sprintf(buf, "Go down the %s", - (u.ux == xupladder && u.uy == yupladder) - ? "ladder" : "stairs"); - add_herecmd_menuitem(win, dodown, buf); - } - if (u.usteed) { /* another movement choice */ - Sprintf(buf, "Dismount %s", - x_monnam(u.usteed, ARTICLE_THE, (char *) 0, - SUPPRESS_SADDLE, FALSE)); - add_herecmd_menuitem(win, doride, buf); - } +staticfn int +there_cmd_menu_common( + winid win, + coordxy x, coordxy y, + int mod, + int *act UNUSED) +{ + int K = 0; -#if 0 - if (Upolyd) { /* before objects */ - Sprintf(buf, "Use %s special ability", - s_suffix(mons[u.umonnum].mname)); - add_herecmd_menuitem(win, domonability, buf); + if (mod == CLICK_1 || mod == CLICK_2) { /* ignore iflags.clicklook here */ + /* for self, only include "look at map symbol" if it isn't the + ordinary hero symbol (steed, invisible w/o see invisible, ?) */ + if (!u_at(x, y) || Upolyd || glyph_at(x, y) != hero_glyph) + mcmd_addmenu(win, MCMD_LOOK_AT, "Look at map symbol"), ++K; } -#endif + return K; +} - if (OBJ_AT(u.ux, u.uy)) { - struct obj *otmp = level.objects[u.ux][u.uy]; +/* queue up command(s) to perform #therecmdmenu action */ +staticfn void +act_on_act( + int act, /* action */ + coordxy dx, coordxy dy) /* delta to adjacent spot (farther sometimes) */ +{ + struct obj *otmp; + int dir; - Sprintf(buf, "Pick up %s", otmp->nexthere ? "items" : doname(otmp)); - add_herecmd_menuitem(win, dopickup, buf); + /* a few there_cmd_menu_far() actions use dx,dy differently */ + switch (act) { + case MCMD_THROW_OBJ: + case MCMD_TRAVEL: + case MCMD_LOOK_AT: + /* keep dx,dy as-is */ + break; + default: + /* force dx and dy to be +1, 0, or -1 */ + dx = sgn(dx); + dy = sgn(dy); + break; + } - if (Is_container(otmp)) { - Sprintf(buf, "Loot %s", doname(otmp)); - add_herecmd_menuitem(win, doloot, buf); + switch (act) { + case MCMD_TRAVEL: + /* FIXME: player has explicitly picked "travel to this location" + from the menu but it will only work if flags.travelcmd is True. + That option is intended as way to guard against stray mouse + clicks and shouldn't inhibit explicit travel. */ + iflags.travelcc.x = u.tx = u.ux + dx; + iflags.travelcc.y = u.ty = u.uy + dy; + cmdq_add_ec(CQ_CANNED, dotravel_target); + break; + case MCMD_THROW_OBJ: + cmdq_add_ec(CQ_CANNED, dothrow); + cmdq_add_userinput(CQ_CANNED); + cmdq_add_dir(CQ_CANNED, dx, dy, 0); + break; + case MCMD_OPEN_DOOR: + cmdq_add_ec(CQ_CANNED, doopen); + cmdq_add_dir(CQ_CANNED, dx, dy, 0); + break; + case MCMD_LOCK_DOOR: + otmp = carrying(SKELETON_KEY); + if (!otmp) + otmp = carrying(LOCK_PICK); + if (!otmp) + otmp = carrying(CREDIT_CARD); + if (otmp) { + cmdq_add_ec(CQ_CANNED, doapply); + cmdq_add_key(CQ_CANNED, otmp->invlet); + cmdq_add_dir(CQ_CANNED, dx, dy, 0); + cmdq_add_key(CQ_CANNED, 'y'); /* "Lock it?" */ } - if (otmp->oclass == FOOD_CLASS) { - Sprintf(buf, "Eat %s", doname(otmp)); - add_herecmd_menuitem(win, doeat, buf); + break; + case MCMD_UNTRAP_DOOR: + cmdq_add_ec(CQ_CANNED, dountrap); + cmdq_add_dir(CQ_CANNED, dx, dy, 0); + break; + case MCMD_KICK_DOOR: + cmdq_add_ec(CQ_CANNED, dokick); + cmdq_add_dir(CQ_CANNED, dx, dy, 0); + break; + case MCMD_CLOSE_DOOR: + cmdq_add_ec(CQ_CANNED, doclose); + cmdq_add_dir(CQ_CANNED, dx, dy, 0); + break; + case MCMD_SEARCH: + cmdq_add_ec(CQ_CANNED, dosearch); + break; + case MCMD_LOOK_TRAP: + cmdq_add_ec(CQ_CANNED, doidtrap); + cmdq_add_dir(CQ_CANNED, dx, dy, 0); + break; + case MCMD_UNTRAP_TRAP: + cmdq_add_ec(CQ_CANNED, dountrap); + cmdq_add_dir(CQ_CANNED, dx, dy, 0); + break; + case MCMD_MOVE_DIR: + dir = xytodir(dx, dy); + cmdq_add_ec(CQ_CANNED, move_funcs[dir][MV_WALK]); + break; + case MCMD_RIDE: + cmdq_add_ec(CQ_CANNED, doride); + cmdq_add_dir(CQ_CANNED, dx, dy, 0); + break; + case MCMD_REMOVE_SADDLE: + /* m-prefix for #loot: skip any floor containers */ + cmdq_add_ec(CQ_CANNED, do_reqmenu); + cmdq_add_ec(CQ_CANNED, doloot); + cmdq_add_dir(CQ_CANNED, dx, dy, 0); + cmdq_add_key(CQ_CANNED, 'y'); /* "Do you want to remove saddle? */ + break; + case MCMD_APPLY_SADDLE: + if ((otmp = carrying(SADDLE)) != 0) { + cmdq_add_ec(CQ_CANNED, doapply); + cmdq_add_key(CQ_CANNED, otmp->invlet); + cmdq_add_dir(CQ_CANNED, dx, dy, 0); } + break; + case MCMD_ATTACK_NEXT2U: + dir = xytodir(dx, dy); + cmdq_add_ec(CQ_CANNED, move_funcs[dir][MV_WALK]); + break; + case MCMD_TALK: + cmdq_add_ec(CQ_CANNED, dotalk); + cmdq_add_dir(CQ_CANNED, dx, dy, 0); + break; + case MCMD_NAME: + cmdq_add_ec(CQ_CANNED, docallcmd); + cmdq_add_key(CQ_CANNED, 'm'); /* name a monster */ + cmdq_add_dir(CQ_CANNED, dx, dy, 0); /* getpos() uses u.ux+dx,u.uy+dy */ + break; + case MCMD_QUAFF: + cmdq_add_ec(CQ_CANNED, dodrink); + cmdq_add_key(CQ_CANNED, 'y'); /* "Drink from the fountain?" */ + break; + case MCMD_DIP: + cmdq_add_ec(CQ_CANNED, dodip); + cmdq_add_userinput(CQ_CANNED); + cmdq_add_key(CQ_CANNED, 'y'); /* "Dip foo into the fountain?" */ + break; + case MCMD_SIT: + cmdq_add_ec(CQ_CANNED, dosit); + break; + case MCMD_UP: + cmdq_add_ec(CQ_CANNED, doup); + break; + case MCMD_DOWN: + cmdq_add_ec(CQ_CANNED, dodown); + break; + case MCMD_DISMOUNT: + cmdq_add_ec(CQ_CANNED, doride); + break; + case MCMD_MONABILITY: + cmdq_add_ec(CQ_CANNED, domonability); + break; + case MCMD_PICKUP: + cmdq_add_ec(CQ_CANNED, dopickup); + break; + case MCMD_LOOT: + cmdq_add_ec(CQ_CANNED, doloot); + break; + case MCMD_TIP: + cmdq_add_ec(CQ_CANNED, dotip); + cmdq_add_key(CQ_CANNED, 'y'); /* "There is foo here; tip it?" */ + break; + case MCMD_EAT: + cmdq_add_ec(CQ_CANNED, doeat); + cmdq_add_key(CQ_CANNED, 'y'); /* "There is foo here; eat it?" */ + break; + case MCMD_DROP: + cmdq_add_ec(CQ_CANNED, dodrop); + break; + case MCMD_INVENTORY: + cmdq_add_ec(CQ_CANNED, ddoinv); + break; + case MCMD_REST: + cmdq_add_ec(CQ_CANNED, donull); + break; + case MCMD_LOOK_HERE: + cmdq_add_ec(CQ_CANNED, dolook); + break; + case MCMD_LOOK_AT: + gc.clicklook_cc.x = u.ux + dx; + gc.clicklook_cc.y = u.uy + dy; + cmdq_add_ec(CQ_CANNED, doclicklook); + break; + case MCMD_UNTRAP_HERE: + cmdq_add_ec(CQ_CANNED, dountrap); + cmdq_add_dir(CQ_CANNED, 0, 0, 1); + break; + case MCMD_OFFER: + cmdq_add_ec(CQ_CANNED, dosacrifice); + cmdq_add_userinput(CQ_CANNED); + break; + case MCMD_CAST_SPELL: + cmdq_add_ec(CQ_CANNED, docast); + break; + default: + break; } +} + +/* offer choice of actions to perform at adjacent location ; + a few choices can be farther away */ +staticfn char +there_cmd_menu(coordxy x, coordxy y, int mod) +{ + winid win; + char ch = '\0'; + int npick = 0, K = 0; + menu_item *picks = (menu_item *) 0; + /*int dx = sgn(x - u.ux), dy = sgn(y - u.uy);*/ + coordxy dx = x - u.ux, dy = y - u.uy; + int act = MCMD_NOTHING; - if (invent) - add_herecmd_menuitem(win, dodrop, "Drop items"); + win = create_nhwindow(NHW_MENU); + start_menu(win, MENU_BEHAVE_STANDARD); - add_herecmd_menuitem(win, donull, "Rest one turn"); - add_herecmd_menuitem(win, dosearch, "Search around you"); - add_herecmd_menuitem(win, dolook, "Look at what is here"); + if (u_at(x, y)) + K += there_cmd_menu_self(win, x, y, &act); + else if (next2u(x, y)) + K += there_cmd_menu_next2u(win, x, y, mod, &act); + else + K += there_cmd_menu_far(win, x, y, mod); + K += there_cmd_menu_common(win, x, y, mod, &act); + + if (!K) { + /* no menu options, try to move */ + if (next2u(x, y) && test_move(u.ux, u.uy, dx, dy, TEST_MOVE)) { + int dir = xytodir(dx, dy); + + cmdq_add_ec(CQ_CANNED, move_funcs[dir][MV_WALK]); + } else if (flags.travelcmd) { + iflags.travelcc.x = u.tx = x; + iflags.travelcc.y = u.ty = y; + cmdq_add_ec(CQ_CANNED, dotravel_target); + } + npick = 0; + ch = '\0'; + } else if (K == 1 && act != MCMD_NOTHING && act != MCMD_TRAVEL) { + destroy_nhwindow(win); - end_menu(win, "What do you want to do?"); - npick = select_menu(win, PICK_ONE, &picks); + act_on_act(act, dx, dy); + return '\0'; + } else { + end_menu(win, "What do you want to do?"); + npick = select_menu(win, PICK_ONE, &picks); + ch = '\033'; + } destroy_nhwindow(win); - ch = '\0'; if (npick > 0) { - int NDECL((*func)) = picks->item.a_nfunc; + act = picks->item.a_int; free((genericptr_t) picks); - if (doit) { - int ret = (*func)(); - - ch = (char) ret; - } else { - ch = cmd_from_func(func); - } + act_on_act(act, dx, dy); + return '\0'; } return ch; } +staticfn char +here_cmd_menu(void) +{ + there_cmd_menu(u.ux, u.uy, CLICK_1); + return '\0'; +} -static NEARDATA int last_multi; +void +click_to_cmd(coordxy x, coordxy y, int mod) +{ + gc.clicklook_cc.x = x; + gc.clicklook_cc.y = y; -/* - * convert a MAP window position into a movecmd - */ -const char * -click_to_cmd(x, y, mod) -int x, y, mod; + if (gc.Cmd.mousebtn[mod-1]) + cmdq_add_ec(CQ_CANNED, gc.Cmd.mousebtn[mod-1]->ef_funct); +} + +staticfn int +domouseaction(void) { + coordxy x, y; + struct obj *o; int dir; - static char cmd[4]; - cmd[1] = 0; - - if (iflags.clicklook && mod == CLICK_2) { - clicklook_cc.x = x; - clicklook_cc.y = y; - cmd[0] = Cmd.spkeys[NHKF_CLICKLOOK]; - return cmd; - } - x -= u.ux; - y -= u.uy; + x = gc.clicklook_cc.x - u.ux; + y = gc.clicklook_cc.y - u.uy; if (flags.travelcmd) { if (abs(x) <= 1 && abs(y) <= 1) { x = sgn(x), y = sgn(y); } else { - u.tx = u.ux + x; - u.ty = u.uy + y; - cmd[0] = Cmd.spkeys[NHKF_TRAVEL]; - return cmd; + iflags.travelcc.x = u.tx = u.ux + x; + iflags.travelcc.y = u.ty = u.uy + y; + cmdq_add_ec(CQ_CANNED, dotravel_target); + return ECMD_OK; } if (x == 0 && y == 0) { - if (iflags.herecmd_menu) { - cmd[0] = here_cmd_menu(FALSE); - return cmd; - } - /* here */ if (IS_FOUNTAIN(levl[u.ux][u.uy].typ) || IS_SINK(levl[u.ux][u.uy].typ)) { - cmd[0] = cmd_from_func(mod == CLICK_1 ? dodrink : dodip); - return cmd; + cmdq_add_ec(CQ_CANNED, dodrink); + return ECMD_OK; } else if (IS_THRONE(levl[u.ux][u.uy].typ)) { - cmd[0] = cmd_from_func(dosit); - return cmd; - } else if ((u.ux == xupstair && u.uy == yupstair) - || (u.ux == sstairs.sx && u.uy == sstairs.sy - && sstairs.up) - || (u.ux == xupladder && u.uy == yupladder)) { - cmd[0] = cmd_from_func(doup); - return cmd; - } else if ((u.ux == xdnstair && u.uy == ydnstair) - || (u.ux == sstairs.sx && u.uy == sstairs.sy - && !sstairs.up) - || (u.ux == xdnladder && u.uy == ydnladder)) { - cmd[0] = cmd_from_func(dodown); - return cmd; - } else if (OBJ_AT(u.ux, u.uy)) { - cmd[0] = cmd_from_func(Is_container(level.objects[u.ux][u.uy]) - ? doloot : dopickup); - return cmd; + cmdq_add_ec(CQ_CANNED, dosit); + return ECMD_OK; + } else if (On_stairs_up(u.ux, u.uy)) { + cmdq_add_ec(CQ_CANNED, doup); + return ECMD_OK; + } else if (On_stairs_dn(u.ux, u.uy)) { + cmdq_add_ec(CQ_CANNED, dodown); + return ECMD_OK; + } else if ((o = vobj_at(u.ux, u.uy)) != 0) { + cmdq_add_ec(CQ_CANNED, Is_container(o) ? doloot : dopickup); + return ECMD_OK; } else { - cmd[0] = cmd_from_func(donull); /* just rest */ - return cmd; + cmdq_add_ec(CQ_CANNED, donull); /* just rest */ + return ECMD_OK; } } /* directional commands */ - dir = xytod(x, y); - + dir = xytodir(x, y); if (!m_at(u.ux + x, u.uy + y) && !test_move(u.ux, u.uy, x, y, TEST_MOVE)) { - cmd[1] = Cmd.dirchars[dir]; - cmd[2] = '\0'; - if (iflags.herecmd_menu) { - cmd[0] = there_cmd_menu(FALSE, u.ux + x, u.uy + y); - if (cmd[0] == '\0') - cmd[1] = '\0'; - return cmd; - } - if (IS_DOOR(levl[u.ux + x][u.uy + y].typ)) { - /* slight assistance to the player: choose kick/open for them - */ + /* slight assistance to player: choose kick/open for them */ if (levl[u.ux + x][u.uy + y].doormask & D_LOCKED) { - cmd[0] = cmd_from_func(dokick); - return cmd; + cmdq_add_ec(CQ_CANNED, dokick); + return ECMD_OK; } if (levl[u.ux + x][u.uy + y].doormask & D_CLOSED) { - cmd[0] = cmd_from_func(doopen); - return cmd; + cmdq_add_ec(CQ_CANNED, doopen); + return ECMD_OK; } } if (levl[u.ux + x][u.uy + y].typ <= SCORR) { - cmd[0] = cmd_from_func(dosearch); - cmd[1] = 0; - return cmd; + cmdq_add_ec(CQ_CANNED, dosearch); + return ECMD_OK; } + cmdq_add_ec(CQ_CANNED, move_funcs[dir][MV_WALK]); + return ECMD_OK; } } else { /* convert without using floating point, allowing sloppy clicking */ @@ -5699,67 +5014,82 @@ int x, y, mod; if (x == 0 && y == 0) { /* map click on player to "rest" command */ - cmd[0] = cmd_from_func(donull); - return cmd; + cmdq_add_ec(CQ_CANNED, donull); + return ECMD_OK; } - dir = xytod(x, y); + dir = xytodir(x, y); } /* move, attack, etc. */ - cmd[1] = 0; - if (mod == CLICK_1) { - cmd[0] = Cmd.dirchars[dir]; - } else { - cmd[0] = (Cmd.num_pad - ? M(Cmd.dirchars[dir]) - : (Cmd.dirchars[dir] - 'a' + 'A')); /* run command */ - } - - return cmd; + cmdq_add_ec(CQ_CANNED, move_funcs[dir][MV_WALK]); + return ECMD_OK; } +/* gather typed digits into a number in *count; return the next non-digit */ char -get_count(allowchars, inkey, maxcount, count, historical) -char *allowchars; -char inkey; -long maxcount; -long *count; -boolean historical; /* whether to include in message history: True => yes */ +get_count( + const char *allowchars, /* what comes after digits; if Null, anything */ + char inkey, /* if caller already got first digit, this is it */ + long maxcount, /* if user tries to enter a bigger count, use this */ + cmdcount_nht *count, /* primary output */ + unsigned gc_flags) /* control flags: GC_SAVEHIST, GC_ECHOFIRST */ { char qbuf[QBUFSZ]; - int key; - long cnt = 0L; - boolean backspaced = FALSE; + int key, save_input_state = program_state.input_state; + long cnt = 0L, first = inkey ? (long) (inkey - '0') : 0L; + boolean backspaced = FALSE, showzero = TRUE, + /* should "Count: 123" go into message history? */ + historicmsg = (gc_flags & GC_SAVEHIST) != 0, + /* put "Count: N" into mesg hist unless N is the same as the + [first digit] value passed in via 'inkey' */ + conditionalmsg = (gc_flags & GC_CONDHIST) != 0, + /* normally "Count: 12" isn't echoed until the second digit */ + echoalways = (gc_flags & GC_ECHOFIRST) != 0; /* this should be done in port code so that we have erase_char and kill_char available; we can at least fake erase_char */ #define STANDBY_erase_char '\177' + *count = 0; for (;;) { if (inkey) { key = inkey; inkey = '\0'; - } else + } else { + /* if readchar() has already been called in this loop, it will + have reset input_state; put that back to its previous value */ + program_state.input_state = save_input_state; key = readchar(); + } if (digit(key)) { - cnt = 10L * cnt + (long) (key - '0'); - if (cnt < 0) - cnt = 0; - else if (maxcount > 0 && cnt > maxcount) + long dgt = (long) (key - '0'); + + /* cnt = (10 * cnt) + (key - '0'); */ + cnt = AppendLongDigit(cnt, dgt); + if (cnt < 0L) + cnt = 0L; + else if (maxcount > 0L && cnt > maxcount) cnt = maxcount; - } else if (cnt && (key == '\b' || key == STANDBY_erase_char)) { - cnt = cnt / 10; + /* if we've backed up to nothing, then typed 0, show that 0 */ + showzero = (key == '0'); + } else if (key == '\b' || key == STANDBY_erase_char) { + if (!cnt && !echoalways) + break; + showzero = FALSE; + cnt = cnt / 10L; backspaced = TRUE; - } else if (key == Cmd.spkeys[NHKF_ESC]) { + } else if (key == gc.Cmd.spkeys[NHKF_ESC]) { break; - } else if (!allowchars || index(allowchars, key)) { - *count = cnt; + } else if (!allowchars || strchr(allowchars, key)) { + *count = (cmdcount_nht) cnt; + if ((long) *count != cnt) + impossible("get_count: cmdcount_nht"); break; } - if (cnt > 9 || backspaced) { + if (cnt > 9 || backspaced || echoalways) { clear_nhwindow(WIN_MESSAGE); - if (backspaced && !cnt) { + if (backspaced && !cnt && !showzero) { Sprintf(qbuf, "Count: "); } else { Sprintf(qbuf, "Count: %ld", cnt); @@ -5770,7 +5100,7 @@ boolean historical; /* whether to include in message history: True => yes */ } } - if (historical) { + if (historicmsg || (conditionalmsg && *count != first)) { Sprintf(qbuf, "Count: %ld ", *count); (void) key2txt((uchar) key, eos(qbuf)); putmsghistory(qbuf, FALSE); @@ -5779,91 +5109,65 @@ boolean historical; /* whether to include in message history: True => yes */ return key; } - -STATIC_OVL char * -parse() +/* main command input routine when not repeating and not executing canned + commands; input comes via get_count() which collects repeat count if one + is present and returns next non-digit to us */ +staticfn int +parse(void) { -#ifdef LINT /* static char in_line[COLNO]; */ - char in_line[COLNO]; -#else - static char in_line[COLNO]; -#endif - register int foo; + int foo; + struct Cmd_bind *bind; iflags.in_parse = TRUE; - multi = 0; - context.move = 1; + gc.command_count = 0; + svc.context.move = TRUE; /* assume next command will take game time */ flush_screen(1); /* Flush screen buffer. Put the cursor on the hero. */ -#ifdef ALTMETA - alt_esc = iflags.altmeta; /* readchar() hack */ -#endif - if (!Cmd.num_pad || (foo = readchar()) == Cmd.spkeys[NHKF_COUNT]) { - long tmpmulti = multi; - - foo = get_count((char *) 0, '\0', LARGEST_INT, &tmpmulti, FALSE); - last_multi = multi = tmpmulti; - } -#ifdef ALTMETA - alt_esc = FALSE; /* readchar() reset */ -#endif - - if (iflags.debug_fuzzer /* if fuzzing, override '!' and ^Z */ - && (Cmd.commands[foo & 0x0ff] - && (Cmd.commands[foo & 0x0ff]->ef_funct == dosuspend_core - || Cmd.commands[foo & 0x0ff]->ef_funct == dosh_core))) - foo = Cmd.spkeys[NHKF_ESC]; + /* affects readchar() behavior for ESC iff 'altmeta' option is On; + is always reset to otherInp by readchar() */ + program_state.input_state = commandInp; - if (foo == Cmd.spkeys[NHKF_ESC]) { /* esc cancels count (TH) */ - clear_nhwindow(WIN_MESSAGE); - multi = last_multi = 0; - } else if (foo == Cmd.spkeys[NHKF_DOAGAIN] || in_doagain) { - multi = last_multi; - } else { - last_multi = multi; - savech(0); /* reset input queue */ - savech((char) foo); - } + if (!gc.Cmd.num_pad || (foo = readchar()) == gc.Cmd.spkeys[NHKF_COUNT]) { + /* if 'num_pad' is On then readchar() has just reset input_state; + set it back to commandInp, so that get_count() supports 'altmeta'; + otherwise "nESC" becomes "nESC" (with + not read from keyboard yet) rather than intended count + and meta keystroke "nM-" */ + program_state.input_state = commandInp; - if (multi) { - multi--; - save_cm = in_line; - } else { - save_cm = (char *) 0; - } - /* in 3.4.3 this was in rhack(), where it was too late to handle M-5 */ - if (Cmd.pcHack_compat) { - /* This handles very old inconsistent DOS/Windows behaviour - in a different way: earlier, the keyboard handler mapped - these, which caused counts to be strange when entered - from the number pad. Now do not map them until here. */ - switch (foo) { - case '5': - foo = Cmd.spkeys[NHKF_RUSH]; - break; - case M('5'): - foo = Cmd.spkeys[NHKF_RUN]; - break; - case M('0'): - foo = Cmd.spkeys[NHKF_DOINV]; - break; - default: - break; /* as is */ - } + foo = get_count((char *) 0, '\0', LARGEST_INT, + &gc.command_count, GC_NOFLAGS); } + gl.last_command_count = gc.command_count; - in_line[0] = foo; - in_line[1] = '\0'; - if (prefix_cmd(foo)) { - foo = readchar(); - savech((char) foo); - in_line[1] = foo; - in_line[2] = 0; - } + if (foo == gc.Cmd.spkeys[NHKF_ESC]) { /* esc cancels count (TH) */ + clear_nhwindow(WIN_MESSAGE); + gc.command_count = 0; + gl.last_command_count = 0; + } else if (gi.in_doagain) { + gc.command_count = gl.last_command_count; + } else if (foo && (bind = cmdbind_get(foo & 0xFF)) != 0 + /* these shouldn't go into the do-again buffer */ + && bind && bind->cmd + && (bind->cmd->ef_funct == do_repeat + || bind->cmd->ef_funct == doprev_message + /* this one might get put into the do-again buffer but + only if the interface code tells the core to do it */ + || bind->cmd->ef_funct == doextcmd)) { + /* gc.command_count will be set again when we + re-enter with gi.in_doagain set true */ + gc.command_count = gl.last_command_count; + } + + gm.multi = gc.command_count; + if (gm.multi) + gm.multi--; + + gc.cmd_key = foo; clear_nhwindow(WIN_MESSAGE); iflags.in_parse = FALSE; - return in_line; + return gc.cmd_key; } #ifdef HANGUPHANDLING @@ -5872,14 +5176,15 @@ parse() the return value so we should be safe using `void' unconditionally */ /*ARGUSED*/ void -hangup(sig_unused) /* called as signal() handler, so sent at least one arg */ -int sig_unused UNUSED; +hangup( + int sig_unused UNUSED) /* called as signal() handler, so sent + * at least one arg */ { if (program_state.exiting) program_state.in_moveloop = 0; nhwindows_hangup(); #ifdef SAFERHANGUP - /* When using SAFERHANGUP, the done_hup flag it tested in rhack + /* When using SAFERHANGUP, the done_hup flag is tested in rhack and a couple of other places; actual hangup handling occurs then. This is 'safer' because it disallows certain cheats and also protects against losing objects in the process of being thrown, @@ -5887,14 +5192,15 @@ int sig_unused UNUSED; must continue running longer before attempting a hangup save. */ program_state.done_hup++; /* defer hangup iff game appears to be in progress */ - if (program_state.in_moveloop && program_state.something_worth_saving) + if (program_state.in_moveloop + && program_state.something_worth_saving) return; #endif /* SAFERHANGUP */ end_of_input(); } void -end_of_input() +end_of_input(void) { #ifdef NOSAVEONHANGUP #ifdef INSURANCE @@ -5904,11 +5210,16 @@ end_of_input() program_state.something_worth_saving = 0; /* don't save */ #endif + if (In_tutorial(&u.uz)) + program_state.something_worth_saving = 0; /* don't save in tutorial */ + #ifndef SAFERHANGUP if (!program_state.done_hup++) #endif if (program_state.something_worth_saving) (void) dosave0(); + if (soundprocs.sound_exit_nhsound) + (*soundprocs.sound_exit_nhsound)("end_of_input"); if (iflags.window_inited) exit_nhwindows((char *) 0); clearlocks(); @@ -5918,22 +5229,25 @@ end_of_input() } #endif /* HANGUPHANDLING */ -char -readchar() +staticfn char +readchar_core(coordxy *x, coordxy *y, int *mod) { - register int sym; - int x = u.ux, y = u.uy, mod = 0; + int sym; - if (iflags.debug_fuzzer) - return randomkey(); + if (iflags.debug_fuzzer) { + sym = randomkey(); + goto readchar_done; + } if (*readchar_queue) sym = *readchar_queue++; + else if (gi.in_doagain) + sym = pgetchar(); else - sym = in_doagain ? pgetchar() : nh_poskey(&x, &y, &mod); + sym = nh_poskey(x, y, mod); #ifdef NR_OF_EOFS if (sym == EOF) { - register int cnt = NR_OF_EOFS; + int cnt = NR_OF_EOFS; /* * Some SYSV systems seem to return EOFs for various reasons * (?like when one hits break or for interrupted systemcalls?), @@ -5952,8 +5266,12 @@ readchar() #endif sym = '\033'; #ifdef ALTMETA - } else if (sym == '\033' && alt_esc) { - /* iflags.altmeta: treat two character ``ESC c'' as single `M-c' */ + } else if (sym == '\033' && iflags.altmeta + && program_state.input_state != otherInp) { + /* iflags.altmeta: treat two character ``ESC c'' as single `M-c' but + only when we're called by parse() [possibly via get_count()] + or getpos() [to support Alt+digit] or getdir() [for arrow keys + under curses] */ sym = *readchar_queue ? *readchar_queue++ : pgetchar(); if (sym == EOF || sym == 0) sym = '\033'; @@ -5962,29 +5280,57 @@ readchar() #endif /*ALTMETA*/ } else if (sym == 0) { /* click event */ - readchar_queue = click_to_cmd(x, y, mod); - sym = *readchar_queue++; + gc.clicklook_cc.x = gc.clicklook_cc.y = -1; + click_to_cmd(*x, *y, *mod); } + + readchar_done: + /* next readchar() will be for an ordinary char unless parse() + sets this back to non-zero */ + program_state.input_state = otherInp; return (char) sym; } +/* get a character */ +char +readchar(void) +{ + char ch; + coordxy x = u.ux, y = u.uy; + int mod = 0; + + ch = readchar_core(&x, &y, &mod); + return ch; +} + +/* used by getpos() to accept mouse input as well as keyboard input */ +char +readchar_poskey(coordxy *x, coordxy *y, int *mod) +{ + char ch; + + program_state.input_state = getposInp; + ch = readchar_core(x, y, mod); + return ch; +} + /* '_' command, #travel, via keyboard rather than mouse click */ -STATIC_PTR int -dotravel(VOID_ARGS) +staticfn int +dotravel(void) { - static char cmd[2]; coord cc; - /* [FIXME? Supporting the ability to disable traveling via mouse - click makes some sense, depending upon overall mouse usage. - Disabling '_' on a user by user basis makes no sense at all since - even if it is typed by accident, aborting when picking a target - destination is trivial. Travel via mouse predates travel via '_', - and this use of OPTION=!travel is probably just a mistake....] */ - if (!flags.travelcmd) - return 0; + /* + * Traveling used to be a no-op if user toggled 'travel' option + * Off. However, travel was initially implemented as a mouse-only + * command and the original purpose of the option was to be able + * to prevent clicks on the map from initiating travel. + * + * Travel via '_' came later. Since it requires a destination-- + * which offers the user a chance to cancel if it was accidental-- + * there's no reason for the option to disable travel-by-keys. + */ - cmd[1] = 0; cc.x = iflags.travelcc.x; cc.y = iflags.travelcc.y; if (cc.x == 0 && cc.y == 0) { @@ -5994,28 +5340,146 @@ dotravel(VOID_ARGS) } iflags.getloc_travelmode = TRUE; if (iflags.menu_requested) { - int gf = iflags.getloc_filter; + int gfilt = iflags.getloc_filter; + iflags.getloc_filter = GFILTER_VIEW; if (!getpos_menu(&cc, GLOC_INTERESTING)) { - iflags.getloc_filter = gf; + iflags.getloc_filter = gfilt; iflags.getloc_travelmode = FALSE; - return 0; + return ECMD_OK; } - iflags.getloc_filter = gf; + iflags.getloc_filter = gfilt; } else { pline("Where do you want to travel to?"); if (getpos(&cc, TRUE, "the desired destination") < 0) { /* user pressed ESC */ iflags.getloc_travelmode = FALSE; - return 0; + return ECMD_CANCEL; } } - iflags.getloc_travelmode = FALSE; iflags.travelcc.x = u.tx = cc.x; iflags.travelcc.y = u.ty = cc.y; - cmd[0] = Cmd.spkeys[NHKF_TRAVEL]; - readchar_queue = cmd; - return 0; + + return dotravel_target(); +} + +/* #retravel, travel to iflags.travelcc, which must be set */ +staticfn int +dotravel_target(void) +{ + if (!isok(iflags.travelcc.x, iflags.travelcc.y)) { + /* assume <0,0>, the value assigned when travel reaches destination */ + pline("No travel destination set."); + return ECMD_OK; + } else if (u_at(iflags.travelcc.x, iflags.travelcc.y)) { + /* maybe interrupted while traveling then just walked rest of way + so destination hasn't been reset yet */ + You("are already here."); + iflags.travelcc.x = iflags.travelcc.y = 0; + return ECMD_OK; + } + + iflags.getloc_travelmode = FALSE; + + svc.context.travel = 1; + svc.context.travel1 = 1; + svc.context.run = 8; + svc.context.nopick = 1; + gd.domove_attempting |= DOMOVE_RUSH; + + if (!gm.multi) + gm.multi = max(COLNO, ROWNO); + u.last_str_turn = 0; + svc.context.mv = TRUE; + + domove(); + return ECMD_TIME; +} + +/* mouse click look command */ +staticfn int +doclicklook(void) +{ + if (!isok(gc.clicklook_cc.x, gc.clicklook_cc.y)) + return ECMD_OK; + + svc.context.move = FALSE; + auto_describe(gc.clicklook_cc.x, gc.clicklook_cc.y); + + return ECMD_OK; +} + +/* can we use menu entries to respond to a query? */ +staticfn boolean +yn_menuable_resp(const char *resp) +{ + return iflags.query_menu && iflags.window_inited + && (resp == ynchars || resp == ynqchars || resp == ynaqchars + || resp == rightleftchars || resp == hidespinchars); +} + +staticfn void +yn_func_menu_opt(winid win, char key, const char *text, char def) +{ + anything any; + + any = cg.zeroany; + any.a_char = key; + add_menu(win, &nul_glyphinfo, &any, key, 0, + ATR_NONE, NO_COLOR, text, + (def == key) ? MENU_ITEMFLAGS_SELECTED + : MENU_ITEMFLAGS_NONE); + +} + +/* use a menu to ask a specific response to a query. + returns TRUE if the menu was shown to the user. + puts the response char into res. */ +staticfn boolean +yn_function_menu( + const char *query, + const char *resp, + char def, + char *res) +{ + if (yn_menuable_resp(resp)) { + winid win = create_nhwindow(NHW_MENU); + menu_item *sel; + int n; + char keybuf[QBUFSZ]; + + start_menu(win, MENU_BEHAVE_STANDARD); + if (resp == rightleftchars) { + yn_func_menu_opt(win, 'r', "Right", def); + yn_func_menu_opt(win, 'l', "Left", def); + } else if (resp == hidespinchars) { + yn_func_menu_opt(win, 'h', "Hide", def); + yn_func_menu_opt(win, 's', "Spin a web", def); + } else { + yn_func_menu_opt(win, 'y', "Yes", def); + yn_func_menu_opt(win, 'n', "No", def); + } + if (resp == ynaqchars) + yn_func_menu_opt(win, 'a', "All", def); + if (resp == ynqchars || resp == ynaqchars || resp == hidespinchars) + yn_func_menu_opt(win, 'q', "Quit", def); + end_menu(win, query); + n = select_menu(win, PICK_ONE, &sel); + destroy_nhwindow(win); + if (n > 0) { + *res = sel[0].item.a_char; + /* two were selected? use the one that wasn't the default */ + if (n > 1 && *res == def) + *res = sel[1].item.a_char; + free((genericptr_t) sel); + } else { + *res = def; + } + pline("%s %s", query, key2txt(*res, keybuf)); + clear_nhwindow(WIN_MESSAGE); + return TRUE; + } + return FALSE; } /* @@ -6024,14 +5488,16 @@ dotravel(VOID_ARGS) * window port causing a buffer overflow there. */ char -yn_function(query, resp, def) -const char *query, *resp; -char def; -{ - char res, qbuf[QBUFSZ]; -#ifdef DUMPLOG - extern unsigned saved_pline_index; /* pline.c */ - unsigned idx = saved_pline_index; +yn_function( + const char *query, + const char *resp, + char def, + boolean addcmdq) +{ + char res = '\033', qbuf[QBUFSZ]; + struct _cmd_queue cq, *cmdq; +#ifdef DUMPLOG_CORE + unsigned idx = gs.saved_pline_index; /* buffer to hold query+space+formatted_single_char_response */ char dumplog_buf[QBUFSZ + 1 + 15]; /* [QBUFSZ+1+7] should suffice */ #endif @@ -6046,35 +5512,118 @@ char def; Strcpy(&qbuf[QBUFSZ - 1 - 3], "..."); query = qbuf; } - res = (*windowprocs.win_yn_function)(query, resp, def); -#ifdef DUMPLOG - if (idx == saved_pline_index) { - /* when idx is still the same as saved_pline_index, the interface - didn't put the prompt into saved_plines[]; we put a simplified + + if (addcmdq && (cmdq = cmdq_pop()) != 0) { + cq = *cmdq; + free(cmdq); + } else { + cq.typ = CMDQ_USER_INPUT; + cq.key = '\0'; /* lint suppression */ + } + + if (cq.typ != CMDQ_USER_INPUT) { + if (cq.typ == CMDQ_KEY) + res = cq.key; + else + cmdq_clear(CQ_CANNED); /* 'res' is ESC */ + addcmdq = FALSE; + + /* for the fuzzer, usually force a valid response, but sometimes let + it exercise windowport yn_function and invalid response handling */ + } else if (iflags.debug_fuzzer && resp && *resp && rn2(20)) { + int ln = (int) strlen(resp), ridx = rn2(ln); + + res = resp[ridx]; + /* if valid-responses includes ESC followed by unshown candidates + and we randomly picked the ESC, try again with only whatever is + before it; be careful to avoid rn2(0) */ + if (res == '\033') { + if (ln > 1) { + /* if ESC is at start (ridx==0), pick something after it */ + ridx = (ridx == 0) ? (1 + rn2(ln - 1)) : rn2(ridx); + res = resp[ridx]; + } else { + /* ESC is the only thing (ln==1); something is strange... */ + res = def; /* might be '\0' */ + } + } + + } else { +#ifdef SND_SPEECH + if ((gp.pline_flags & PLINE_SPEECH) != 0) { + sound_speak(query); + gp.pline_flags &= ~PLINE_SPEECH; + } +#endif + if (!yn_function_menu(query, resp, def, &res)) { + res = (*windowprocs.win_yn_function)(query, resp, def); + } + } + if (addcmdq) + cmdq_add_key(CQ_REPEAT, res); + +#ifdef DUMPLOG_CORE + if (idx == gs.saved_pline_index) { + /* when idx is still the same as gs.saved_pline_index, the interface + didn't put the prompt into gs.saved_plines[]; we put a simplified version in there now (without response choices or default) */ Sprintf(dumplog_buf, "%s ", query); (void) key2txt((uchar) res, eos(dumplog_buf)); dumplogmsg(dumplog_buf); } #endif + /* should not happen but cq.key has been observed to not obey 'resp'; + it is most likely caused by saving a keystroke that was just used + to answer a context-sensitive prompt, then using the do-again + command with context that has changed */ + if (resp && *resp && res && !strchr(resp, res)) { + /* this probably needs refinement since caller is expecting something + within 'resp' and ESC won't be (it could be present, but as a flag + for unshown possibilities rather than as acceptable input) */ + int altres = def ? def : '\033'; + + if (!gi.in_doagain || wizard) { +/*TEMP*/ xint8 fuzzing = iflags.debug_fuzzer; + char dbg_buf[BUFSZ]; + + Snprintf(dbg_buf, sizeof dbg_buf, "%s [%s] (%s)", + query, resp ? resp : "", def ? visctrl(def) : ""); + paniclog("yn debug", dbg_buf); +/*TEMP*/ /* don't let this known problem kill the fuzzer */ +/*TEMP*/ iflags.debug_fuzzer = fuzzer_impossible_continue; + impossible("yn_function() returned '%s'; using '%s' instead", + visctrl(res), visctrl(altres)); +/*TEMP*/ iflags.debug_fuzzer = fuzzing; + } + res = altres; + } + /* in case we're called via getdir() which sets input_state */ + program_state.input_state = otherInp; return res; } -/* for paranoid_confirm:quit,die,attack prompting */ -boolean -paranoid_query(be_paranoid, prompt) -boolean be_paranoid; -const char *prompt; +/* for paranoid_confirm:quit,die,attack,&c prompting; allows yes, n|no, + or q|quit; result is one of 'y' or 'n' or 'q'; ESC yields 'q' */ +char +paranoid_ynq( + boolean be_paranoid, + const char *prompt, + boolean accept_q) { - boolean confirmed_ok; + char c = 'n'; /* default result */ /* when paranoid, player must respond with "yes" rather than just 'y' to give the go-ahead for this query; default is "no" unless the ParanoidConfirm flag is set in which case there's no default */ if (be_paranoid) { char pbuf[BUFSZ], qbuf[QBUFSZ], ans[BUFSZ]; - const char *promptprefix = "", - *responsetype = ParanoidConfirm ? "(yes|no)" : "(yes) [no]"; + const char *promptprefix = "", /* empty for first iteration */ + *responsetype = ParanoidConfirm ? (accept_q ? "[yes|no|quit]" + : "[yes|no]") + /* default of 'n' is shown for + * the !ParanoidConfirm cases */ + : (accept_q ? "[yes|n|q] (n)" + : "[yes|n] (n)"); int k, trylimit = 6; /* 1 normal, 5 more with "Yes or No:" prefix */ copynchars(pbuf, prompt, BUFSZ - 1); @@ -6091,47 +5640,85 @@ const char *prompt; Strcpy(pbuf + (QBUFSZ - 1) - k - 4, "...?"); /* -4: "...?" */ } - Sprintf(qbuf, "%s%s %s", promptprefix, pbuf, responsetype); + Snprintf(qbuf, sizeof qbuf, "%s%s %s", promptprefix, pbuf, + responsetype); *ans = '\0'; getlin(qbuf, ans); (void) mungspaces(ans); - confirmed_ok = !strcmpi(ans, "yes"); - if (confirmed_ok || *ans == '\033') + if (!strcmpi(ans, "yes")) { + c = 'y'; + break; + } + if (!strcmpi(ans, "quit") || *ans == '\033') { + c = 'q'; break; + } + /* we don't bother adding "or \"Quit\"" for the accept_q case */ promptprefix = "\"Yes\" or \"No\": "; + /* for empty input, return value c will already be 'n' */ } while (ParanoidConfirm && strcmpi(ans, "no") && --trylimit); - } else - confirmed_ok = (yn(prompt) == 'y'); + } else if (accept_q) { + /* 'y', 'n', or 'q' */ + c = yn_function(prompt, ynqchars, 'n', FALSE); + } else { + /* 'y' or 'n' */ + c = yn_function(prompt, ynchars, 'n', FALSE); + } + if (c != 'y' && (c != 'q' || !accept_q)) + c = 'n'; + return c; +} - return confirmed_ok; +/* for paranoid_confirm:quit,die,attack,&c prompting; allows yes or n|no; + result is True for yes; n|no and ESC yield False */ +boolean +paranoid_query(boolean be_paranoid, const char *prompt) +{ + return (paranoid_ynq(be_paranoid, prompt, FALSE) == 'y'); } /* ^Z command, #suspend */ -STATIC_PTR int -dosuspend_core(VOID_ARGS) +staticfn int +dosuspend_core(void) { #ifdef SUSPEND /* Does current window system support suspend? */ if ((*windowprocs.win_can_suspend)()) { + time_t now = getnow(); + + urealtime.realtime += timet_delta(now, urealtime.start_timing); + urealtime.start_timing = now; /* as a safeguard against panic save */ /* NB: SYSCF SHELLERS handled in port code. */ dosuspend(); + urealtime.start_timing = getnow(); /* resume keeping track of time */ } else #endif Norep(cmdnotavail, "#suspend"); - return 0; + return ECMD_OK; } /* '!' command, #shell */ -STATIC_PTR int -dosh_core(VOID_ARGS) +staticfn int +dosh_core(void) { #ifdef SHELL + time_t now = getnow(); + + urealtime.realtime += timet_delta(now, urealtime.start_timing); + urealtime.start_timing = now; /* (see dosuspend_core) */ /* access restrictions, if any, are handled in port code */ dosh(); + urealtime.start_timing = getnow(); #else Norep(cmdnotavail, "#shell"); #endif - return 0; + return ECMD_OK; +} + +staticfn int +dummyfunction(void) +{ + return ECMD_CANCEL; } /*cmd.c*/ diff --git a/src/coloratt.c b/src/coloratt.c new file mode 100644 index 000000000..10f216ba6 --- /dev/null +++ b/src/coloratt.c @@ -0,0 +1,1168 @@ +/* NetHack 5.0 coloratt.c $NHDT-Date: 1737286550 2025/01/19 03:35:50 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.14 $ */ +/* Copyright (c) Pasi Kallinen, 2024 */ +/* NetHack may be freely redistributed. See license for details. */ + +#include "hack.h" + +struct color_names { + const char *name; + int color; +}; + +static const struct color_names colornames[] = { + { "black", CLR_BLACK }, + { "red", CLR_RED }, + { "green", CLR_GREEN }, + { "brown", CLR_BROWN }, + { "blue", CLR_BLUE }, + { "magenta", CLR_MAGENTA }, + { "cyan", CLR_CYAN }, + { "gray", CLR_GRAY }, + { "orange", CLR_ORANGE }, + { "light green", CLR_BRIGHT_GREEN }, + { "yellow", CLR_YELLOW }, + { "light blue", CLR_BRIGHT_BLUE }, + { "light magenta", CLR_BRIGHT_MAGENTA }, + { "light cyan", CLR_BRIGHT_CYAN }, + { "white", CLR_WHITE }, + { "no color", NO_COLOR }, + { (const char *) 0, CLR_BLACK }, /* everything after this is an alias */ + { "transparent", NO_COLOR }, + { "purple", CLR_MAGENTA }, + { "light purple", CLR_BRIGHT_MAGENTA }, + { "bright purple", CLR_BRIGHT_MAGENTA }, + { "grey", CLR_GRAY }, + { "bright red", CLR_ORANGE }, + { "bright green", CLR_BRIGHT_GREEN }, + { "bright blue", CLR_BRIGHT_BLUE }, + { "bright magenta", CLR_BRIGHT_MAGENTA }, + { "bright cyan", CLR_BRIGHT_CYAN } +}; + +struct attr_names { + const char *name; + int attr; +}; + +static const struct attr_names attrnames[] = { + { "none", ATR_NONE }, + { "bold", ATR_BOLD }, + { "dim", ATR_DIM }, + { "italic", ATR_ITALIC }, + { "underline", ATR_ULINE }, + { "blink", ATR_BLINK }, + { "inverse", ATR_INVERSE }, + { (const char *) 0, ATR_NONE }, /* everything after this is an alias */ + { "normal", ATR_NONE }, + { "uline", ATR_ULINE }, + { "reverse", ATR_INVERSE }, +}; + +/* { colortyp, tableindex, rgbindx, name, r, g, b }, */ + +#define NHC nh_color +#define NOC no_color +#define RGBC rgb_color + +const struct nethack_color colortable[] = { + { NHC, 0, 0, "black", 0x00, 0x00, 0x00 }, /* CLR_BLACK */ + { NHC, 1, 0, "red", 0xFF, 0x00, 0x00 }, /* CLR_RED */ + { NHC, 2, 0, "green", 0x22, 0x8B, 0x22 }, /* CLR_GREEN */ + { NHC, 3, 0, "brown", 0xA5, 0x2A, 0x2A }, /* CLR_BROWN */ + { NHC, 4, 0, "blue", 0x00, 0x00, 0xFF }, /* CLR_BLUE */ + { NHC, 5, 0, "magenta", 0xFF, 0x00, 0xFF }, /* CLR_MAGENTA */ + { NHC, 6, 0, "cyan", 0x00, 0xFF, 0xFF }, /* CLR_CYAN */ + { NHC, 7, 0, "gray", 0x80, 0x80, 0x80 }, /* CLR_GRAY */ + { NOC, 8, 0, "nocolor", 0x00, 0x00, 0x00 }, /* NO_COLOR */ + { NHC, 9, 0, "orange", 0xFF, 0xA5, 0x00 }, /* CLR_ORANGE */ + { NHC, 10, 0, "bright-green", + 0x00, 0x80, 0x00 }, /* CLR_BRIGHT_GREEN */ + { NHC, 11, 0, "yellow", 0xFF, 0xFF, 0x00 }, /* CLR_YELLOW */ + { NHC, 12, 0, "bright-blue", 0xAD, 0xD8, 0xE6 }, /* CLR_BRIGHT_BLUE */ + { NHC, 13, 0, "bright-magenta", + 0x93, 0x70, 0xDB }, /* CLR_BRIGHT_MAGENTA */ + { NHC, 14, 0, "light-cyan", 0xE0, 0xFF, 0xFF }, /* CLR_BRIGHT_CYAN */ + { NHC, 15, 0, "white", 0xFF, 0xFF, 0xFF }, /* CLR_WHITE */ + { RGBC, 16, 0, "maroon", 0x80, 0x00, 0x00 }, /* #800000 */ + { RGBC, 17, 1, "dark-red", 0x8B, 0x00, 0x00 }, /* #8B0000 */ + { RGBC, 18, 2, "brown", 0xA5, 0x2A, 0x2A }, /* #A52A2A */ + { RGBC, 19, 3, "firebrick", 0xB2, 0x22, 0x22 }, /* #B22222 */ + { RGBC, 20, 4, "crimson", 0xDC, 0x14, 0x3C }, /* #DC143C */ + { RGBC, 21, 5, "red", 0xFF, 0x00, 0x00 }, /* #FF0000 */ + { RGBC, 22, 6, "tomato", 0xFF, 0x63, 0x47 }, /* #FF6347 */ + { RGBC, 23, 7, "coral", 0xFF, 0x7F, 0x50 }, /* #FF7F50 */ + { RGBC, 24, 8, "indian-red", 0xCD, 0x5C, 0x5C }, /* #CD5C5C */ + { RGBC, 25, 9, "light-coral", 0xF0, 0x80, 0x80 }, /* #F08080 */ + { RGBC, 26, 10, "dark-salmon", 0xE9, 0x96, 0x7A }, /* #E9967A */ + { RGBC, 27, 11, "salmon", 0xFA, 0x80, 0x72 }, /* #FA8072 */ + { RGBC, 28, 12, "light-salmon", 0xFF, 0xA0, 0x7A }, /* #FFA07A */ + { RGBC, 29, 13, "orange-red", 0xFF, 0x45, 0x00 }, /* #FF4500 */ + { RGBC, 30, 14, "dark-orange", 0xFF, 0x8C, 0x00 }, /* #FF8C00 */ + { RGBC, 31, 15, "orange", 0xFF, 0xA5, 0x00 }, /* #FFA500 */ + { RGBC, 32, 16, "gold", 0xFF, 0xD7, 0x00 }, /* #FFD700 */ + { RGBC, 33, 17, "dark-golden-rod", 0xB8, 0x86, 0x0B }, /* #B8860B */ + { RGBC, 34, 18, "golden-rod", 0xDA, 0xA5, 0x20 }, /* #DAA520 */ + { RGBC, 35, 19, "pale-golden-rod", 0xEE, 0xE8, 0xAA }, /* #EEE8AA */ + { RGBC, 36, 20, "dark-khaki", 0xBD, 0xB7, 0x6B }, /* #BDB76B */ + { RGBC, 37, 21, "khaki", 0xF0, 0xE6, 0x8C }, /* #F0E68C */ + { RGBC, 38, 22, "olive", 0x80, 0x80, 0x00 }, /* #808000 */ + { RGBC, 39, 23, "yellow", 0xFF, 0xFF, 0x00 }, /* #FFFF00 */ + { RGBC, 40, 24, "yellow-green", 0x9A, 0xCD, 0x32 }, /* #9ACD32 */ + { RGBC, 41, 25, "dark-olive-green", 0x55, 0x6B, 0x2F }, /* #556B2F */ + { RGBC, 42, 26, "olive-drab", 0x6B, 0x8E, 0x23 }, /* #6B8E23 */ + { RGBC, 43, 27, "lawn-green", 0x7C, 0xFC, 0x00 }, /* #7CFC00 */ + { RGBC, 44, 28, "chart-reuse", 0x7F, 0xFF, 0x00 }, /* #7FFF00 */ + { RGBC, 45, 29, "green-yellow", 0xAD, 0xFF, 0x2F }, /* #ADFF2F */ + { RGBC, 46, 30, "dark-green", 0x00, 0x64, 0x00 }, /* #006400 */ + { RGBC, 47, 31, "green", 0x00, 0x80, 0x00 }, /* #008000 */ + { RGBC, 48, 32, "forest-green", 0x22, 0x8B, 0x22 }, /* #228B22 */ + { RGBC, 49, 33, "lime", 0x00, 0xFF, 0x00 }, /* #00FF00 */ + { RGBC, 50, 34, "lime-green", 0x32, 0xCD, 0x32 }, /* #32CD32 */ + { RGBC, 51, 35, "light-green", 0x90, 0xEE, 0x90 }, /* #90EE90 */ + { RGBC, 52, 36, "pale-green", 0x98, 0xFB, 0x98 }, /* #98FB98 */ + { RGBC, 53, 37, "dark-sea-green", 0x8F, 0xBC, 0x8F }, /* #8FBC8F */ + { RGBC, 54, 38, "medium-spring-green", 0x00, 0xFA, 0x9A }, /* #00FA9A */ + { RGBC, 55, 39, "spring-green", 0x00, 0xFF, 0x7F }, /* #00FF7F */ + { RGBC, 56, 40, "sea-green", 0x2E, 0x8B, 0x57 }, /* #2E8B57 */ + { RGBC, 57, 41, "medium-aqua-marine", 0x66, 0xCD, 0xAA }, /* #66CDAA */ + { RGBC, 58, 42, "medium-sea-green", 0x3C, 0xB3, 0x71 }, /* #3CB371 */ + { RGBC, 59, 43, "light-sea-green", 0x20, 0xB2, 0xAA }, /* #20B2AA */ + { RGBC, 60, 44, "dark-slate-gray", 0x2F, 0x4F, 0x4F }, /* #2F4F4F */ + { RGBC, 61, 45, "teal", 0x00, 0x80, 0x80 }, /* #008080 */ + { RGBC, 62, 46, "dark-cyan", 0x00, 0x8B, 0x8B }, /* #008B8B */ + { RGBC, 63, 47, "aqua", 0x00, 0xFF, 0xFF }, /* #00FFFF */ + { RGBC, 64, 48, "cyan", 0x00, 0xFF, 0xFF }, /* #00FFFF */ + { RGBC, 65, 49, "light-cyan", 0xE0, 0xFF, 0xFF }, /* #E0FFFF */ + { RGBC, 66, 50, "dark-turquoise", 0x00, 0xCE, 0xD1 }, /* #00CED1 */ + { RGBC, 67, 51, "turquoise", 0x40, 0xE0, 0xD0 }, /* #40E0D0 */ + { RGBC, 68, 52, "medium-turquoise", 0x48, 0xD1, 0xCC }, /* #48D1CC */ + { RGBC, 69, 53, "pale-turquoise", 0xAF, 0xEE, 0xEE }, /* #AFEEEE */ + { RGBC, 70, 54, "aqua-marine", 0x7F, 0xFF, 0xD4 }, /* #7FFFD4 */ + { RGBC, 71, 55, "powder-blue", 0xB0, 0xE0, 0xE6 }, /* #B0E0E6 */ + { RGBC, 72, 56, "cadet-blue", 0x5F, 0x9E, 0xA0 }, /* #5F9EA0 */ + { RGBC, 73, 57, "steel-blue", 0x46, 0x82, 0xB4 }, /* #4682B4 */ + { RGBC, 74, 58, "corn-flower-blue", 0x64, 0x95, 0xED }, /* #6495ED */ + { RGBC, 75, 59, "deep-sky-blue", 0x00, 0xBF, 0xFF }, /* #00BFFF */ + { RGBC, 76, 60, "dodger-blue", 0x1E, 0x90, 0xFF }, /* #1E90FF */ + { RGBC, 77, 61, "light-blue", 0xAD, 0xD8, 0xE6 }, /* #ADD8E6 */ + { RGBC, 78, 62, "sky-blue", 0x87, 0xCE, 0xEB }, /* #87CEEB */ + { RGBC, 79, 63, "light-sky-blue", 0x87, 0xCE, 0xFA }, /* #87CEFA */ + { RGBC, 80, 64, "midnight-blue", 0x19, 0x19, 0x70 }, /* #191970 */ + { RGBC, 81, 65, "navy", 0x00, 0x00, 0x80 }, /* #000080 */ + { RGBC, 82, 66, "dark-blue", 0x00, 0x00, 0x8B }, /* #00008B */ + { RGBC, 83, 67, "medium-blue", 0x00, 0x00, 0xCD }, /* #0000CD */ + { RGBC, 84, 68, "blue", 0x00, 0x00, 0xFF }, /* #0000FF */ + { RGBC, 85, 69, "royal-blue", 0x41, 0x69, 0xE1 }, /* #4169E1 */ + { RGBC, 86, 70, "blue-violet", 0x8A, 0x2B, 0xE2 }, /* #8A2BE2 */ + { RGBC, 87, 71, "indigo", 0x4B, 0x00, 0x82 }, /* #4B0082 */ + { RGBC, 88, 72, "dark-slate-blue", 0x48, 0x3D, 0x8B }, /* #483D8B */ + { RGBC, 89, 73, "slate-blue", 0x6A, 0x5A, 0xCD }, /* #6A5ACD */ + { RGBC, 90, 74, "medium-slate-blue", 0x7B, 0x68, 0xEE }, /* #7B68EE */ + { RGBC, 91, 75, "medium-purple", 0x93, 0x70, 0xDB }, /* #9370DB */ + { RGBC, 92, 76, "dark-magenta", 0x8B, 0x00, 0x8B }, /* #8B008B */ + { RGBC, 93, 77, "dark-violet", 0x94, 0x00, 0xD3 }, /* #9400D3 */ + { RGBC, 94, 78, "dark-orchid", 0x99, 0x32, 0xCC }, /* #9932CC */ + { RGBC, 95, 79, "medium-orchid", 0xBA, 0x55, 0xD3 }, /* #BA55D3 */ + { RGBC, 96, 80, "purple", 0x80, 0x00, 0x80 }, /* #800080 */ + { RGBC, 97, 81, "thistle", 0xD8, 0xBF, 0xD8 }, /* #D8BFD8 */ + { RGBC, 98, 82, "plum", 0xDD, 0xA0, 0xDD }, /* #DDA0DD */ + { RGBC, 99, 83, "violet", 0xEE, 0x82, 0xEE }, /* #EE82EE */ + { RGBC, 100, 84, "magenta", 0xFF, 0x00, 0xFF }, /* #FF00FF */ + { RGBC, 101, 85, "orchid", 0xDA, 0x70, 0xD6 }, /* #DA70D6 */ + { RGBC, 102, 86, "medium-violet-red", 0xC7, 0x15, 0x85 }, /* #C71585 */ + { RGBC, 103, 87, "pale-violet-red", 0xDB, 0x70, 0x93 }, /* #DB7093 */ + { RGBC, 104, 88, "deep-pink", 0xFF, 0x14, 0x93 }, /* #FF1493 */ + { RGBC, 105, 89, "hot-pink", 0xFF, 0x69, 0xB4 }, /* #FF69B4 */ + { RGBC, 106, 90, "light-pink", 0xFF, 0xB6, 0xC1 }, /* #FFB6C1 */ + { RGBC, 107, 91, "pink", 0xFF, 0xC0, 0xCB }, /* #FFC0CB */ + { RGBC, 108, 92, "antique-white", 0xFA, 0xEB, 0xD7 }, /* #FAEBD7 */ + { RGBC, 109, 93, "beige", 0xF5, 0xF5, 0xDC }, /* #F5F5DC */ + { RGBC, 110, 94, "bisque", 0xFF, 0xE4, 0xC4 }, /* #FFE4C4 */ + { RGBC, 111, 95, "blanched-almond", 0xFF, 0xEB, 0xCD }, /* #FFEBCD */ + { RGBC, 112, 96, "wheat", 0xF5, 0xDE, 0xB3 }, /* #F5DEB3 */ + { RGBC, 113, 97, "corn-silk", 0xFF, 0xF8, 0xDC }, /* #FFF8DC */ + { RGBC, 114, 98, "lemon-chiffon", 0xFF, 0xFA, 0xCD }, /* #FFFACD */ + { RGBC, 115, 99, "light-golden-rod-yellow", + 0xFA, 0xFA, 0xD2 }, /* #FAFAD2 */ + { RGBC, 116, 100, "light-yellow", 0xFF, 0xFF, 0xE0 }, /* #FFFFE0 */ + { RGBC, 117, 101, "saddle-brown", 0x8B, 0x45, 0x13 }, /* #8B4513 */ + { RGBC, 118, 102, "sienna", 0xA0, 0x52, 0x2D }, /* #A0522D */ + { RGBC, 119, 103, "chocolate", 0xD2, 0x69, 0x1E }, /* #D2691E */ + { RGBC, 120, 104, "peru", 0xCD, 0x85, 0x3F }, /* #CD853F */ + { RGBC, 121, 105, "sandy-brown", 0xF4, 0xA4, 0x60 }, /* #F4A460 */ + { RGBC, 122, 106, "burly-wood", 0xDE, 0xB8, 0x87 }, /* #DEB887 */ + { RGBC, 123, 107, "tan", 0xD2, 0xB4, 0x8C }, /* #D2B48C */ + { RGBC, 124, 108, "rosy-brown", 0xBC, 0x8F, 0x8F }, /* #BC8F8F */ + { RGBC, 125, 109, "moccasin", 0xFF, 0xE4, 0xB5 }, /* #FFE4B5 */ + { RGBC, 126, 110, "navajo-white", 0xFF, 0xDE, 0xAD }, /* #FFDEAD */ + { RGBC, 127, 111, "peach-puff", 0xFF, 0xDA, 0xB9 }, /* #FFDAB9 */ + { RGBC, 128, 112, "misty-rose", 0xFF, 0xE4, 0xE1 }, /* #FFE4E1 */ + { RGBC, 129, 113, "lavender-blush", 0xFF, 0xF0, 0xF5 }, /* #FFF0F5 */ + { RGBC, 130, 114, "linen", 0xFA, 0xF0, 0xE6 }, /* #FAF0E6 */ + { RGBC, 131, 115, "old-lace", 0xFD, 0xF5, 0xE6 }, /* #FDF5E6 */ + { RGBC, 132, 116, "papaya-whip", 0xFF, 0xEF, 0xD5 }, /* #FFEFD5 */ + { RGBC, 133, 117, "sea-shell", 0xFF, 0xF5, 0xEE }, /* #FFF5EE */ + { RGBC, 134, 118, "mint-cream", 0xF5, 0xFF, 0xFA }, /* #F5FFFA */ + { RGBC, 135, 119, "slate-gray", 0x70, 0x80, 0x90 }, /* #708090 */ + { RGBC, 136, 120, "light-slate-gray", 0x77, 0x88, 0x99 }, /* #778899 */ + { RGBC, 137, 121, "light-steel-blue", 0xB0, 0xC4, 0xDE }, /* #B0C4DE */ + { RGBC, 138, 122, "lavender", 0xE6, 0xE6, 0xFA }, /* #E6E6FA */ + { RGBC, 139, 123, "floral-white", 0xFF, 0xFA, 0xF0 }, /* #FFFAF0 */ + { RGBC, 140, 124, "alice-blue", 0xF0, 0xF8, 0xFF }, /* #F0F8FF */ + { RGBC, 141, 125, "ghost-white", 0xF8, 0xF8, 0xFF }, /* #F8F8FF */ + { RGBC, 142, 126, "honeydew", 0xF0, 0xFF, 0xF0 }, /* #F0FFF0 */ + { RGBC, 143, 127, "ivory", 0xFF, 0xFF, 0xF0 }, /* #FFFFF0 */ + { RGBC, 144, 128, "azure", 0xF0, 0xFF, 0xFF }, /* #F0FFFF */ + { RGBC, 145, 129, "snow", 0xFF, 0xFA, 0xFA }, /* #FFFAFA */ + { RGBC, 146, 130, "black", 0x00, 0x00, 0x00 }, /* #000000 */ + { RGBC, 147, 131, "dim-gray", 0x69, 0x69, 0x69 }, /* #696969 */ + { RGBC, 148, 132, "gray", 0x80, 0x80, 0x80 }, /* #808080 */ + { RGBC, 149, 133, "dark-gray", 0xA9, 0xA9, 0xA9 }, /* #A9A9A9 */ + { RGBC, 150, 134, "silver", 0xC0, 0xC0, 0xC0 }, /* #C0C0C0 */ + { RGBC, 151, 135, "light-gray", 0xD3, 0xD3, 0xD3 }, /* #D3D3D3 */ + { RGBC, 152, 136, "gainsboro", 0xDC, 0xDC, 0xDC }, /* #DCDCDC */ + { RGBC, 153, 137, "white-smoke", 0xF5, 0xF5, 0xF5 }, /* #F5F5F5 */ + { RGBC, 154, 138, "white", 0xFF, 0xFF, 0xFF }, /* #FFFFFF */ +}; + +#undef NHC +#undef NOC +#undef RGBC + +#ifdef CHANGE_COLOR +staticfn int32 alt_color_spec(const char *cp); +#endif + +int32 +colortable_to_int32(const struct nethack_color *cte) +{ + int32 clr = NO_COLOR | NH_BASIC_COLOR; + + if (cte->colortyp == rgb_color) + clr = (cte->r << 16) | (cte->g << 8) | cte->b; + else if (cte->colortyp == nh_color) + clr = cte->tableindex | NH_BASIC_COLOR; + return clr; +} + +char * +color_attr_to_str(color_attr *ca) +{ + static char buf[BUFSZ]; + + Sprintf(buf, "%s&%s", + clr2colorname(ca->color), + attr2attrname(ca->attr)); + return buf; +} + +/* parse string like "color&attr" into color_attr */ +boolean +color_attr_parse_str(color_attr *ca, char *str) +{ + char buf[BUFSZ]; + char *amp = NULL; + int tmp, c = NO_COLOR, a = ATR_NONE; + + (void) strncpy(buf, str, sizeof buf - 1); + buf[sizeof buf - 1] = '\0'; + + if ((amp = strchr(buf, '&')) != 0) + *amp = '\0'; + + if (amp) { + amp++; + c = match_str2clr(buf, FALSE); + a = match_str2attr(amp, TRUE); + /* FIXME: match_str2clr & match_str2attr give config_error_add(), + so this is useless */ + if (c >= CLR_MAX && a == -1) { + /* try other way around */ + c = match_str2clr(amp, FALSE); + a = match_str2attr(buf, TRUE); + } + if (c >= CLR_MAX || a == -1) + return FALSE; + } else { + /* one param only */ + tmp = match_str2attr(buf, FALSE); + if (tmp == -1) { + tmp = match_str2clr(buf, FALSE); + if (tmp >= CLR_MAX) + return FALSE; + c = tmp; + } else { + a = tmp; + } + } + ca->attr = a; + ca->color = c; + return TRUE; +} + +boolean +query_color_attr(color_attr *ca, const char *prompt) +{ + int c, a; + + c = query_color(prompt, ca->color); + if (c == -1) + return FALSE; + a = query_attr(prompt, ca->attr); + if (a == -1) + return FALSE; + ca->color = c; + ca->attr = a; + return TRUE; +} + +const char * +attr2attrname(int attr) +{ + int i; + + for (i = 0; i < SIZE(attrnames); i++) + if (attrnames[i].attr == attr) + return attrnames[i].name; + return (char *) 0; +} + +/* + * Color support functions and data for "color" + * + * Used by: optfn_() + * + */ + +const char * +clr2colorname(int clr) +{ + int i; + + for (i = 0; i < SIZE(colornames); i++) + if (colornames[i].name && colornames[i].color == clr) + return colornames[i].name; + return (char *) 0; +} + +int +match_str2clr(char *str, boolean suppress_msg) +{ + int i, c = CLR_MAX; + + /* allow "lightblue", "light blue", and "light-blue" to match "light blue" + (also junk like "_l i-gh_t---b l u e" but we won't worry about that); + also copes with trailing space; caller has removed any leading space */ + for (i = 0; i < SIZE(colornames); i++) + if (colornames[i].name + && fuzzymatch(str, colornames[i].name, " -_", TRUE)) { + c = colornames[i].color; + break; + } + if (i == SIZE(colornames) && digit(*str)) + c = atoi(str); + + if (c < 0 || c >= CLR_MAX) { + if (!suppress_msg) + config_error_add("Unknown color '%.60s'", str); + c = CLR_MAX; /* "none of the above" */ + } + return c; +} + +int +match_str2attr(const char *str, boolean complain) +{ + int i, a = -1; + + for (i = 0; i < SIZE(attrnames); i++) + if (attrnames[i].name + && fuzzymatch(str, attrnames[i].name, " -_", TRUE)) { + a = attrnames[i].attr; + break; + } + + if (a == -1 && complain) + config_error_add("Unknown text attribute '%.50s'", str); + + return a; +} + +/* ask about highlighting attribute; for menu headers and menu + coloring patterns, only one attribute at a time is allowed; + for status highlighting, multiple attributes are allowed [overkill; + life would be much simpler if that were restricted to one also...] */ +int +query_attr(const char *prompt, int dflt_attr) +{ + winid tmpwin; + anything any; + int i, pick_cnt; + menu_item *picks = (menu_item *) 0; + boolean allow_many = (prompt && !strncmpi(prompt, "Choose", 6)); + int clr = NO_COLOR; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + for (i = 0; i < SIZE(attrnames); i++) { + if (!attrnames[i].name) + break; + any.a_int = i + 1; + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, + attrnames[i].attr, clr, attrnames[i].name, + (attrnames[i].attr == dflt_attr) ? MENU_ITEMFLAGS_SELECTED + : MENU_ITEMFLAGS_NONE); + } + end_menu(tmpwin, (prompt && *prompt) ? prompt : "Pick an attribute"); + pick_cnt = select_menu(tmpwin, allow_many ? PICK_ANY : PICK_ONE, &picks); + destroy_nhwindow(tmpwin); + if (pick_cnt > 0) { + int j, k = 0; + + if (allow_many) { + /* PICK_ANY, with one preselected entry (ATR_NONE) which + should be excluded if any other choices were picked */ + for (i = 0; i < pick_cnt; ++i) { + j = picks[i].item.a_int - 1; + if (attrnames[j].attr != ATR_NONE || pick_cnt == 1) { + switch (attrnames[j].attr) { + case ATR_NONE: + k = HL_NONE; + break; + case ATR_BOLD: + k |= HL_BOLD; + break; + case ATR_DIM: + k |= HL_DIM; + break; + case ATR_ITALIC: + k |= HL_ITALIC; + break; + case ATR_ULINE: + k |= HL_ULINE; + break; + case ATR_BLINK: + k |= HL_BLINK; + break; + case ATR_INVERSE: + k |= HL_INVERSE; + break; + } + } + } + } else { + /* PICK_ONE, but might get 0 or 2 due to preselected entry */ + j = picks[0].item.a_int - 1; + /* pick_cnt==2: explicitly picked something other than the + preselected entry */ + if (pick_cnt == 2 && attrnames[j].attr == dflt_attr) + j = picks[1].item.a_int - 1; + k = attrnames[j].attr; + } + free((genericptr_t) picks); + return k; + } else if (pick_cnt == 0 && !allow_many) { + /* PICK_ONE, preselected entry explicitly chosen */ + return dflt_attr; + } + /* either ESC to explicitly cancel (pick_cnt==-1) or + PICK_ANY with preselected entry toggled off and nothing chosen */ + return -1; +} + +int +query_color(const char *prompt, int dflt_color) +{ + winid tmpwin; + anything any; + int i, pick_cnt; + menu_item *picks = (menu_item *) 0; + + /* replace user patterns with color name ones and force 'menucolors' On */ + basic_menu_colors(TRUE); + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + for (i = 0; i < SIZE(colornames); i++) { + if (!colornames[i].name) + break; + any.a_int = i + 1; + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, + ATR_NONE, NO_COLOR, colornames[i].name, + (colornames[i].color == dflt_color) ? MENU_ITEMFLAGS_SELECTED + : MENU_ITEMFLAGS_NONE); + } + end_menu(tmpwin, (prompt && *prompt) ? prompt : "Pick a color"); + pick_cnt = select_menu(tmpwin, PICK_ONE, &picks); + destroy_nhwindow(tmpwin); + + /* remove temporary color name patterns and restore user-specified ones; + reset 'menucolors' option to its previous value */ + basic_menu_colors(FALSE); + + if (pick_cnt > 0) { + i = colornames[picks[0].item.a_int - 1].color; + /* pick_cnt==2: explicitly picked something other than the + preselected entry */ + if (pick_cnt == 2 && i == NO_COLOR) + i = colornames[picks[1].item.a_int - 1].color; + free((genericptr_t) picks); + return i; + } else if (pick_cnt == 0) { + /* pick_cnt==0: explicitly picking preselected entry toggled it off */ + return dflt_color; + } + return -1; +} + +DISABLE_WARNING_FORMAT_NONLITERAL + +extern const char regex_id[]; /* from sys/share/regex.{c,cpp} */ + +/* set up a menu for picking a color, one that shows each name in its color; + overrides player's MENUCOLORS with a set of "blue"=blue, "red"=red, and + so forth; suppresses color for black and white because one of those will + likely be invisible due to matching the background; the alternate set of + MENUCOLORS is kept around for potential re-use */ +void +basic_menu_colors( + boolean load_colors) /* True: temporarily replace menu color entries with + * a fake set of menu colors which match their names; + * False: restore user-specified colorings */ +{ + if (load_colors) { + /* replace normal menu colors with a set specifically for colors */ + gs.save_menucolors = iflags.use_menu_color; + gs.save_colorings = gm.menu_colorings; + + iflags.use_menu_color = TRUE; + if (gc.color_colorings) { + /* use the alternate colorings which were set up previously */ + gm.menu_colorings = gc.color_colorings; + } else { + /* create the alternate colorings once */ + char cnm[QBUFSZ]; + int i, c; + boolean pmatchregex = !strcmpi(regex_id, "pmatchregex"); + const char *patternfmt = pmatchregex ? "*%s" : "%s"; + + /* menu_colorings pointer has been saved; clear it in order + to add the alternate entries as if from scratch */ + gm.menu_colorings = (struct menucoloring *) 0; + + /* this orders the patterns last-in/first-out; that means + that the "light " variations come before the basic + "" ones, which is exactly what we want (so that the + shorter basic names won't get false matches as substrings + of the longer ones) */ + for (i = 0; i < SIZE(colornames); ++i) { + if (!colornames[i].name) /* first alias entry has no name */ + break; + c = colornames[i].color; + if (c == CLR_BLACK || c == CLR_WHITE || c == NO_COLOR) + continue; /* skip these */ + Sprintf(cnm, patternfmt, colornames[i].name); + add_menu_coloring_parsed(cnm, c, ATR_NONE); + } + + /* right now, menu_colorings contains the alternate color list; + remember that list for future pick-a-color instances and + also keep it as is for this instance */ + gc.color_colorings = gm.menu_colorings; + } + } else { + /* restore normal user-specified menu colors */ + iflags.use_menu_color = gs.save_menucolors; + gm.menu_colorings = gs.save_colorings; + } +} + +RESTORE_WARNING_FORMAT_NONLITERAL + +boolean +add_menu_coloring_parsed(const char *str, int c, int a) +{ + static const char re_error[] = "Menucolor regex error"; + struct menucoloring *tmp; + + if (!str) + return FALSE; + tmp = (struct menucoloring *) alloc(sizeof *tmp); + tmp->match = regex_init(); + /* test_regex_pattern() has already validated this regexp but parsing + it again could conceivably run out of memory */ + if (!regex_compile(str, tmp->match)) { + char errbuf[BUFSZ]; + char *re_error_desc = regex_error_desc(tmp->match, errbuf); + + /* free first in case reason for regcomp failure was out-of-memory */ + regex_free(tmp->match); + free((genericptr_t) tmp); + config_error_add("%s: %s", re_error, re_error_desc); + return FALSE; + } + tmp->next = gm.menu_colorings; + tmp->origstr = dupstr(str); + tmp->color = c; + tmp->attr = a; + gm.menu_colorings = tmp; + iflags.use_menu_color = TRUE; + return TRUE; +} + +/* parse '"regex_string"=color&attr' and add it to menucoloring */ +boolean +add_menu_coloring(char *tmpstr) /* never Null but could be empty */ +{ + int c = NO_COLOR, a = ATR_NONE; + char *tmps, *cs, *amp; + char str[BUFSZ]; + + (void) strncpy(str, tmpstr, sizeof str - 1); + str[sizeof str - 1] = '\0'; + + if ((cs = strchr(str, '=')) == 0) { + config_error_add("Malformed MENUCOLOR"); + return FALSE; + } + + tmps = cs + 1; /* advance past '=' */ + mungspaces(tmps); + if ((amp = strchr(tmps, '&')) != 0) + *amp = '\0'; + + c = match_str2clr(tmps, FALSE); + if (c >= CLR_MAX) + return FALSE; + + if (amp) { + tmps = amp + 1; /* advance past '&' */ + a = match_str2attr(tmps, TRUE); + if (a == -1) + return FALSE; + } + + /* the regexp portion here has not been condensed by mungspaces() */ + *cs = '\0'; + tmps = str; + if (*tmps == '"' || *tmps == '\'') { + cs--; + while (isspace((uchar) *cs)) + cs--; + if (*cs == *tmps) { + *cs = '\0'; + tmps++; + } + } + return add_menu_coloring_parsed(tmps, c, a); +} + +/* release all menu color patterns */ +void +free_menu_coloring(void) +{ + /* either menu_colorings or color_colorings or both might need to + be freed or already be Null; do-loop will iterate at most twice */ + do { + struct menucoloring *tmp, *tmp2; + + for (tmp = gm.menu_colorings; tmp; tmp = tmp2) { + tmp2 = tmp->next; + regex_free(tmp->match); + free((genericptr_t) tmp->origstr); + free((genericptr_t) tmp); + } + gm.menu_colorings = gc.color_colorings; + gc.color_colorings = (struct menucoloring *) 0; + } while (gm.menu_colorings); +} + +/* release a specific menu color pattern; not used for color_colorings */ +void +free_one_menu_coloring(int idx) /* 0 .. */ +{ + struct menucoloring *tmp = gm.menu_colorings; + struct menucoloring *prev = NULL; + + while (tmp) { + if (idx == 0) { + struct menucoloring *next = tmp->next; + + regex_free(tmp->match); + free((genericptr_t) tmp->origstr); + free((genericptr_t) tmp); + if (prev) + prev->next = next; + else + gm.menu_colorings = next; + return; + } + idx--; + prev = tmp; + tmp = tmp->next; + } +} + +int +count_menucolors(void) +{ + struct menucoloring *tmp; + int count = 0; + + for (tmp = gm.menu_colorings; tmp; tmp = tmp->next) + count++; + return count; +} + +/* returns -1 on no-match. + * buf is NONNULLARG1 + */ +int32 +check_enhanced_colors(char *buf) +{ + char xtra = '\0'; /* used to catch trailing junk after "#rrggbb" */ + unsigned r, g, b; + int32 retcolor = -1, color; + + if ((color = match_str2clr(buf, TRUE)) != CLR_MAX) { + retcolor = color | NH_BASIC_COLOR; + } else if (sscanf(buf, "#%02x%02x%02x%c", &r, &g, &b, &xtra) >= 3) { + retcolor = !xtra ? (int32) ((r << 16) | (g << 8) | b) : -1; + } else { + /* altbuf: allow user's "grey" to match colortable[]'s "gray"; + * fuzzymatch(): ignore spaces, hyphens, and underscores so that + * space or underscore in user-supplied name will match hyphen + * [note: caller splits text at spaces so we won't see any here] + */ + char *altbuf = NULL, *grey = strstri(buf, "grey"); + ptrdiff_t greyoffset = grey ? (grey - buf) : -1; + + if (greyoffset >= 0) { + altbuf = dupstr(buf); + /* use direct copy because strsubst() is case-sensitive */ + /*(void) strncpy(&altbuf[greyoffset], "gray", 4);*/ + (void) memcpy(altbuf + greyoffset, "gray", 4); + } + for (color = 0; color < SIZE(colortable); ++color) { + if (fuzzymatch(buf, colortable[color].name, " -_", TRUE) + || (altbuf && fuzzymatch(altbuf, colortable[color].name, + " -_", TRUE))) { + retcolor = colortable_to_int32(&colortable[color]); + break; + } + } + if (altbuf) + free(altbuf); + } + return retcolor; +} + +/* return the canonical name of a particular color */ +const char * +wc_color_name(int32 colorindx) +{ + static char hexcolor[sizeof "#rrggbb"]; /* includes room for '\0' */ + const char *result = "no-color"; + + if (colorindx >= 0) { + int32 basicindx = colorindx & ~NH_BASIC_COLOR; + + /* if colorindx has NH_BASIC_COLOR bit set, basicindx won't, + so differing implies a basic color */ + if (basicindx != colorindx) { + assert(basicindx < 16); + result = colortable[basicindx].name; + } else { + int indx; + long r = (colorindx >> 16) & 0x0000ff, /* shift rrXXXX to rr */ + g = (colorindx >> 8) & 0x0000ff, /* shift XXggXX to gg */ + b = colorindx & 0x0000ff; /* mask XXXXbb to bb */ + + Snprintf(hexcolor, sizeof hexcolor, "#%02x%02x%02x", + (uint8) r, (uint8) g, (uint8) b); + result = hexcolor; + /* override hex value if this is a named color */ + for (indx = 16; indx < SIZE(colortable); ++indx) + if (colortable[indx].r == r + && colortable[indx].g == g + && colortable[indx].b == b) { + result = colortable[indx].name; + break; + } + } + } + return result; +} + +/* hexdd[] is defined in decl.c */ +boolean +onlyhexdigits(const char *buf) +{ + const char *dp = buf; + + for (dp = buf; *dp; ++dp) { + if (!(strchr(hexdd, *dp) || *dp == '-')) + return FALSE; + } + return TRUE; +} + +int32_t +rgbstr_to_int32(const char *rgbstr) +{ + int r, g, b, milestone = 0; + char *cp, *c_r, *c_g, *c_b; + int32_t rgb = 0; + char buf[BUFSZ]; + boolean dash = FALSE; + + + Snprintf(buf, sizeof buf, "%s", + rgbstr ? rgbstr : ""); + + if (*buf && onlyhexdigits(buf)) { + c_g = c_b = (char *) 0; + c_r = cp = buf; + while (*cp) { + if (digit(*cp) || *cp == '-') { + if (*cp == '-') { + *cp = '\0'; + milestone++; + dash = TRUE; + } + cp++; + if (dash) { + if (milestone < 2) + c_g = cp; + else + c_b = cp; + dash = FALSE; + } + } else { + return -1L; + } + } + /* sanity checks */ + if (c_r && c_g && c_b + && (strlen(c_r) > 0 && strlen(c_r) < 4) + && (strlen(c_g) > 0 && strlen(c_g) < 4) + && (strlen(c_b) > 0 && strlen(c_b) < 4)) { + r = atoi(c_r); + g = atoi(c_g); + b = atoi(c_b); + rgb = (r << 16) | (g << 8) | (b << 0); + return rgb; + } + } else if (*buf) { + /* perhaps an enhanced color name was used instead of rgb value? */ + if ((rgb = check_enhanced_colors(buf)) != -1) { + return rgb; + } + } + return -1; +} + +int +set_map_customcolor(glyph_map *gmap, uint32 nhcolor) +{ + glyph_map *tmpgm = gmap; + uint32 closecolor = 0; + uint16 clridx = 0; + + if (!tmpgm) + return 0; + + gmap->customcolor = nhcolor; + if (closest_color(nhcolor, &closecolor, &clridx)) + gmap->color256idx = clridx; + else + gmap->color256idx = 0; + return 1; +} + +static struct { + int index; + uint32 value; +} color_256_definitions[] = { + /* color values are from unnethack */ + { 16, 0x000000 }, { 17, 0x00005f }, { 18, 0x000087 }, + { 19, 0x0000af }, { 20, 0x0000d7 }, { 21, 0x0000ff }, + { 22, 0x005f00 }, { 23, 0x005f5f }, { 24, 0x005f87 }, + { 25, 0x005faf }, { 26, 0x005fd7 }, { 27, 0x005fff }, + { 28, 0x008700 }, { 29, 0x00875f }, { 30, 0x008787 }, + { 31, 0x0087af }, { 32, 0x0087d7 }, { 33, 0x0087ff }, + { 34, 0x00af00 }, { 35, 0x00af5f }, { 36, 0x00af87 }, + { 37, 0x00afaf }, { 38, 0x00afd7 }, { 39, 0x00afff }, + { 40, 0x00d700 }, { 41, 0x00d75f }, { 42, 0x00d787 }, + { 43, 0x00d7af }, { 44, 0x00d7d7 }, { 45, 0x00d7ff }, + { 46, 0x00ff00 }, { 47, 0x00ff5f }, { 48, 0x00ff87 }, + { 49, 0x00ffaf }, { 50, 0x00ffd7 }, { 51, 0x00ffff }, + { 52, 0x5f0000 }, { 53, 0x5f005f }, { 54, 0x5f0087 }, + { 55, 0x5f00af }, { 56, 0x5f00d7 }, { 57, 0x5f00ff }, + { 58, 0x5f5f00 }, { 59, 0x5f5f5f }, { 60, 0x5f5f87 }, + { 61, 0x5f5faf }, { 62, 0x5f5fd7 }, { 63, 0x5f5fff }, + { 64, 0x5f8700 }, { 65, 0x5f875f }, { 66, 0x5f8787 }, + { 67, 0x5f87af }, { 68, 0x5f87d7 }, { 69, 0x5f87ff }, + { 70, 0x5faf00 }, { 71, 0x5faf5f }, { 72, 0x5faf87 }, + { 73, 0x5fafaf }, { 74, 0x5fafd7 }, { 75, 0x5fafff }, + { 76, 0x5fd700 }, { 77, 0x5fd75f }, { 78, 0x5fd787 }, + { 79, 0x5fd7af }, { 80, 0x5fd7d7 }, { 81, 0x5fd7ff }, + { 82, 0x5fff00 }, { 83, 0x5fff5f }, { 84, 0x5fff87 }, + { 85, 0x5fffaf }, { 86, 0x5fffd7 }, { 87, 0x5fffff }, + { 88, 0x870000 }, { 89, 0x87005f }, { 90, 0x870087 }, + { 91, 0x8700af }, { 92, 0x8700d7 }, { 93, 0x8700ff }, + { 94, 0x875f00 }, { 95, 0x875f5f }, { 96, 0x875f87 }, + { 97, 0x875faf }, { 98, 0x875fd7 }, { 99, 0x875fff }, + { 100, 0x878700 }, { 101, 0x87875f }, { 102, 0x878787 }, + { 103, 0x8787af }, { 104, 0x8787d7 }, { 105, 0x8787ff }, + { 106, 0x87af00 }, { 107, 0x87af5f }, { 108, 0x87af87 }, + { 109, 0x87afaf }, { 110, 0x87afd7 }, { 111, 0x87afff }, + { 112, 0x87d700 }, { 113, 0x87d75f }, { 114, 0x87d787 }, + { 115, 0x87d7af }, { 116, 0x87d7d7 }, { 117, 0x87d7ff }, + { 118, 0x87ff00 }, { 119, 0x87ff5f }, { 120, 0x87ff87 }, + { 121, 0x87ffaf }, { 122, 0x87ffd7 }, { 123, 0x87ffff }, + { 124, 0xaf0000 }, { 125, 0xaf005f }, { 126, 0xaf0087 }, + { 127, 0xaf00af }, { 128, 0xaf00d7 }, { 129, 0xaf00ff }, + { 130, 0xaf5f00 }, { 131, 0xaf5f5f }, { 132, 0xaf5f87 }, + { 133, 0xaf5faf }, { 134, 0xaf5fd7 }, { 135, 0xaf5fff }, + { 136, 0xaf8700 }, { 137, 0xaf875f }, { 138, 0xaf8787 }, + { 139, 0xaf87af }, { 140, 0xaf87d7 }, { 141, 0xaf87ff }, + { 142, 0xafaf00 }, { 143, 0xafaf5f }, { 144, 0xafaf87 }, + { 145, 0xafafaf }, { 146, 0xafafd7 }, { 147, 0xafafff }, + { 148, 0xafd700 }, { 149, 0xafd75f }, { 150, 0xafd787 }, + { 151, 0xafd7af }, { 152, 0xafd7d7 }, { 153, 0xafd7ff }, + { 154, 0xafff00 }, { 155, 0xafff5f }, { 156, 0xafff87 }, + { 157, 0xafffaf }, { 158, 0xafffd7 }, { 159, 0xafffff }, + { 160, 0xd70000 }, { 161, 0xd7005f }, { 162, 0xd70087 }, + { 163, 0xd700af }, { 164, 0xd700d7 }, { 165, 0xd700ff }, + { 166, 0xd75f00 }, { 167, 0xd75f5f }, { 168, 0xd75f87 }, + { 169, 0xd75faf }, { 170, 0xd75fd7 }, { 171, 0xd75fff }, + { 172, 0xd78700 }, { 173, 0xd7875f }, { 174, 0xd78787 }, + { 175, 0xd787af }, { 176, 0xd787d7 }, { 177, 0xd787ff }, + { 178, 0xd7af00 }, { 179, 0xd7af5f }, { 180, 0xd7af87 }, + { 181, 0xd7afaf }, { 182, 0xd7afd7 }, { 183, 0xd7afff }, + { 184, 0xd7d700 }, { 185, 0xd7d75f }, { 186, 0xd7d787 }, + { 187, 0xd7d7af }, { 188, 0xd7d7d7 }, { 189, 0xd7d7ff }, + { 190, 0xd7ff00 }, { 191, 0xd7ff5f }, { 192, 0xd7ff87 }, + { 193, 0xd7ffaf }, { 194, 0xd7ffd7 }, { 195, 0xd7ffff }, + { 196, 0xff0000 }, { 197, 0xff005f }, { 198, 0xff0087 }, + { 199, 0xff00af }, { 200, 0xff00d7 }, { 201, 0xff00ff }, + { 202, 0xff5f00 }, { 203, 0xff5f5f }, { 204, 0xff5f87 }, + { 205, 0xff5faf }, { 206, 0xff5fd7 }, { 207, 0xff5fff }, + { 208, 0xff8700 }, { 209, 0xff875f }, { 210, 0xff8787 }, + { 211, 0xff87af }, { 212, 0xff87d7 }, { 213, 0xff87ff }, + { 214, 0xffaf00 }, { 215, 0xffaf5f }, { 216, 0xffaf87 }, + { 217, 0xffafaf }, { 218, 0xffafd7 }, { 219, 0xffafff }, + { 220, 0xffd700 }, { 221, 0xffd75f }, { 222, 0xffd787 }, + { 223, 0xffd7af }, { 224, 0xffd7d7 }, { 225, 0xffd7ff }, + { 226, 0xffff00 }, { 227, 0xffff5f }, { 228, 0xffff87 }, + { 229, 0xffffaf }, { 230, 0xffffd7 }, { 231, 0xffffff }, + { 232, 0x080808 }, { 233, 0x121212 }, { 234, 0x1c1c1c }, + { 235, 0x262626 }, { 236, 0x303030 }, { 237, 0x3a3a3a }, + { 238, 0x444444 }, { 239, 0x4e4e4e }, { 240, 0x585858 }, + { 241, 0x626262 }, { 242, 0x6c6c6c }, { 243, 0x767676 }, + { 244, 0x808080 }, { 245, 0x8a8a8a }, { 246, 0x949494 }, + { 247, 0x9e9e9e }, { 248, 0xa8a8a8 }, { 249, 0xb2b2b2 }, + { 250, 0xbcbcbc }, { 251, 0xc6c6c6 }, { 252, 0xd0d0d0 }, + { 253, 0xdadada }, { 254, 0xe4e4e4 }, { 255, 0xeeeeee }, +}; + +/** Calculate the color distance between two colors. + * + * Algorithm taken from UnNetHack which took it from + * https://www.compuphase.com/cmetric.htm + **/ + +int +color_distance(uint32_t rgb1, uint32_t rgb2) +{ + int r1 = (rgb1 >> 16) & 0xFF; + int g1 = (rgb1 >> 8) & 0xFF; + int b1 = (rgb1) & 0xFF; + int r2 = (rgb2 >> 16) & 0xFF; + int g2 = (rgb2 >> 8) & 0xFF; + int b2 = (rgb2) & 0xFF; + + int rmean = (r1 + r2) / 2; + int r = r1 - r2; + int g = g1 - g2; + int b = b1 - b2; + return ((((512 + rmean) * r * r) >> 8) + 4 * g * g + + (((767 - rmean) * b * b) >> 8)); +} + +boolean +closest_color(uint32 lcolor, uint32 *closecolor, uint16 *clridx) +{ + int i, color_index = -1, similar = INT_MAX, current; + boolean retbool = FALSE; + + for (i = 0; i < SIZE(color_256_definitions); i++) { + /* look for an exact match */ + if (lcolor == color_256_definitions[i].value) { + color_index = i; + break; + } + /* find a close color match */ + current = color_distance(lcolor, color_256_definitions[i].value); + if (current < similar) { + color_index = i; + similar = current; + } + } + if (closecolor && clridx && color_index >= 0) { + *closecolor = color_256_definitions[color_index].value; + *clridx = color_256_definitions[color_index].index; + retbool = TRUE; + } + return retbool; +} + +uint32 +get_nhcolor_from_256_index(int idx) +{ + uint32 retcolor = NO_COLOR | NH_BASIC_COLOR; + + if (IndexOk(idx, color_256_definitions)) + retcolor = color_256_definitions[idx].value; + return retcolor; +} + +#ifdef CHANGE_COLOR + +int +count_alt_palette(void) +{ + int clr, clrcount = 0; + + for (clr = 0; clr < CLR_MAX; ++clr) { + if (ga.altpalette[clr] != 0U) + clrcount++; + } + return clrcount; +} + +int +alternative_palette(char *op) +{ + char buf[BUFSZ], *c_colorid, *c_colorval, *cp; + int reslt = 0, coloridx = CLR_MAX; + long rgb = 0L; + boolean slash = FALSE; + + if (!op) + return 0; + + Snprintf(buf, sizeof buf, "%s", op); + c_colorval = (char *) 0; + c_colorid = cp = buf; + while (*cp) { + if (*cp == ':' || *cp == '/') { + if (*cp == '/') { + slash = TRUE; + *cp = '\0'; + } + } + cp++; + if (slash) { + c_colorval = cp; + slash = FALSE; + } + } + /* some sanity checks */ + if (c_colorid && *c_colorid == ' ') + c_colorid++; + if (c_colorval && *c_colorval == ' ') + c_colorval++; + if (c_colorid) + coloridx = match_str2clr(c_colorid, TRUE); + + if (c_colorval && coloridx >= 0 && coloridx < CLR_MAX) { + rgb = rgbstr_to_int32(c_colorval); + if (rgb == -1) { + rgb = alt_color_spec(c_colorval); + } + if (rgb != -1) { + ga.altpalette[coloridx] = (uint32) rgb | NH_ALTPALETTE; + /* use COLORVAL(ga.altpalette[coloridx]) to get + the actual rgb value out of ga.altpalette[] */ + reslt = 1; + } + } + return reslt; +} + +void +change_palette(void) +{ + int clridx; + + for (clridx = 0; clridx < CLR_MAX; ++clridx) { + if (ga.altpalette[clridx] != 0) { + long rgb = (long) COLORVAL(ga.altpalette[clridx]); + (*windowprocs.win_change_color)(clridx, rgb, 0); + } + } +} + +staticfn int32 +alt_color_spec(const char *str) +{ + static NEARDATA const char oct[] = "01234567", dec[] = "0123456789"; + /* hexdd[] is defined in decl.c */ + + const char *dp, *cp = str; + int32 cval = -1; + int dcount, dlimit = 6; + boolean hexescape = FALSE, octescape = FALSE; + + dcount = 0; /* for decimal, octal, hexadecimal cases */ + hexescape = + (*cp == '\\' && cp[1] && (cp[1] == 'x' || cp[1] == 'X') && cp[2]); + if (!hexescape) { + octescape = + (*cp == '\\' && cp[1] && (cp[1] == 'o' || cp[1] == 'O') && cp[2]); + } + + if (hexescape || octescape) { + cval = 0; + cp += 2; + if (octescape) + dlimit = 8; + } else if (*cp == '#' && cp[1]) { + hexescape = TRUE; + cval = 0; + cp += 1; + } else if (cp[1]) { + cval = 0; + dlimit = 8; + } else if (!cp[1]) { + if (strchr(dec, *cp) != 0) { + /* simple val, or nothing left for \ to escape */ + cval = (*cp - '0'); + } + dlimit = 1; + cp++; + } + + while (*cp) { + if (!hexescape && !octescape && strchr(dec, *cp)) { + cval = (cval * 10) + (*cp - '0'); + } else if (octescape && strchr(oct, *cp)) { + cval = (cval * 8) + (*cp - '0'); + } else if (hexescape && (dp = strchr(hexdd, *cp)) != 0) { + cval = (cval * 16) + ((int) (dp - hexdd) / 2); + } + ++cp; + if (++dcount > dlimit) { + cval = -1; + break; + } + } + return cval; +} +#endif /* CHANGE_COLOR */ + +/*coloratt.c*/ diff --git a/src/date.c b/src/date.c new file mode 100644 index 000000000..a1c80a385 --- /dev/null +++ b/src/date.c @@ -0,0 +1,178 @@ +/* NetHack 5.0 date.c $NHDT-Date: 1655402414 2022/06/16 18:00:14 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.3 $ */ +/* Copyright (c) Michael Allison, 2021. */ +/* NetHack may be freely redistributed. See license for details. */ + +#include "config.h" +#include "hacklib.h" +#ifdef Snprintf +#undef Snprintf +#endif +#define Snprintf(str, size, ...) \ + nh_snprintf(__func__, __LINE__, str, size, __VA_ARGS__) + +/* these are in extern.h but we don't include hack.h */ +void populate_nomakedefs(struct version_info *) NONNULLARG1; +void free_nomakedefs(void); + +extern unsigned long md_ignored_features(void); +extern char *mdlib_version_string(char *, const char *); +extern char *version_id_string(char *, size_t, const char *); +extern char *bannerc_string(char *, size_t, const char *); + +/* nomakedefs_populated: flag for whether 'nomakedefs' should be freed */ +static int nomakedefs_populated = 0; + +struct nomakedefs_s nomakedefs = { + /* https://groups.google.com/forum/#!original/ + comp.sources.games/91SfKYg_xzI/dGnR3JnspFkJ */ + "Tue, 28-Jul-87 13:18:57 EDT", + "Version 1.0, built Jul 28 13:18:57 1987.", + (const char *) 0, /* git_sha */ + (const char *) 0, /* git_branch */ + (const char *) 0, /* git_prefix */ + "1.0.0-0", + "NetHack Version 1.0.0-0 - last build Tue Jul 28 13:18:57 1987.", + 0x01010000UL, + 0x00000000UL, + 0x00000000UL, + 0x00000000UL, + 554476737UL, +}; + +#if defined(__DATE__) && defined(__TIME__) + +#define extract_field(t,s,n,z) \ + do { \ + for (i = 0; i < n; ++i) \ + t[i] = s[i + z]; \ + t[i] = '\0'; \ + } while (0) + +void +populate_nomakedefs(struct version_info *version) +{ + int i; + char tmpbuf1[BUFSZ], tmpbuf2[BUFSZ], *strp; + const char *mth[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + struct tm t = {0}; + time_t timeresult; + + /* + * In a cross-compiled environment, you can't execute + * the target binaries during the build, so we can't + * use makedefs to write the values of the build + * date and time to a file for retrieval. Not for + * information meaningful to the target execution + * environment. + * + * How can we capture the build date/time of the target + * binaries in such a situation? We need to rely on the + * cross-compiler itself to do it for us during the + * cross-compile. + * + * To that end, we are going to make use of the + * following pre-defined preprocessor macros for this: + * gcc, msvc, clang __DATE__ "Feb 12 1996" + * gcc, msvc, clang __TIME__ "23:59:01" + * + */ + + Snprintf(tmpbuf1, sizeof tmpbuf1, "%s %s", __DATE__, __TIME__); + /* "Feb 12 1996 23:59:01" + 01234567890123456789 */ + if ((int) strlen(tmpbuf1) == 20) { + extract_field(tmpbuf2, tmpbuf1, 4, 7); /* year */ + t.tm_year = atoi(tmpbuf2) - 1900; + extract_field(tmpbuf2, tmpbuf1, 3, 0); /* mon */ + for (i = 0; i < SIZE(mth); ++i) + if (!case_insensitive_comp(tmpbuf2, mth[i])) { + t.tm_mon = i; + break; + } + extract_field(tmpbuf2, tmpbuf1, 2, 4); /* mday */ + strp = tmpbuf2; + if (*strp == ' ') + strp++; + t.tm_mday = atoi(strp); + extract_field(tmpbuf2, tmpbuf1, 2, 12); /* hour */ + t.tm_hour = atoi(tmpbuf2); + extract_field(tmpbuf2, tmpbuf1, 2, 15); /* min */ + t.tm_min = atoi(tmpbuf2); + extract_field(tmpbuf2, tmpbuf1, 2, 18); /* sec */ + t.tm_sec = atoi(tmpbuf2); + timeresult = mktime(&t); + nomakedefs.build_time = (unsigned long) timeresult; + nomakedefs.build_date = dupstr(tmpbuf1); + } + + nomakedefs.version_number = version->incarnation; + nomakedefs.version_features = version->feature_set; + nomakedefs.ignored_features = md_ignored_features(); + nomakedefs.version_sanity1 = version->entity_count; + nomakedefs.version_string = dupstr(mdlib_version_string(tmpbuf2, ".")); + nomakedefs.version_id = dupstr( + version_id_string(tmpbuf2, sizeof tmpbuf2, nomakedefs.build_date)); + nomakedefs.copyright_banner_c = dupstr( + bannerc_string(tmpbuf2, sizeof tmpbuf2, nomakedefs.build_date)); +#ifdef NETHACK_GIT_SHA + nomakedefs.git_sha = dupstr(NETHACK_GIT_SHA); +#endif +#ifdef NETHACK_GIT_BRANCH + nomakedefs.git_branch = dupstr(NETHACK_GIT_BRANCH); +#endif +#ifdef NETHACK_GIT_PREFIX + nomakedefs.git_prefix = dupstr(NETHACK_GIT_PREFIX); +#endif + + nomakedefs_populated = 1; + return; +} + +void +free_nomakedefs(void) +{ + /* can't just free non-Null values because they're initialized at + compile-time with static strings and won't have dynamic values + unless populate_nomakedefs() has been called */ + if (!nomakedefs_populated) + return; + + if (nomakedefs.build_date) + free((genericptr_t) nomakedefs.build_date), + nomakedefs.build_date = 0; + if (nomakedefs.version_string) + free((genericptr_t) nomakedefs.version_string), + nomakedefs.version_string = 0; + if (nomakedefs.version_id) + free((genericptr_t) nomakedefs.version_id), + nomakedefs.version_id = 0; + if (nomakedefs.copyright_banner_c) + free((genericptr_t) nomakedefs.copyright_banner_c), + nomakedefs.copyright_banner_c = 0; +#ifdef NETHACK_GIT_SHA + if (nomakedefs.git_sha) + free((genericptr_t) nomakedefs.git_sha), + nomakedefs.git_sha = 0; +#endif +#ifdef NETHACK_GIT_BRANCH + if (nomakedefs.git_branch) + free((genericptr_t) nomakedefs.git_branch), + nomakedefs.git_branch = 0; +#endif +#ifdef NETHACK_GIT_PREFIX + if (nomakedefs.git_prefix) + free((genericptr_t) nomakedefs.git_prefix), + nomakedefs.git_prefix = 0; +#endif + + /* values are Null now; dynamic vs static doesn't really matter anymore */ + nomakedefs_populated = 0; + return; +} + +#endif /* __DATE__ && __TIME__ */ + + +/*date.c*/ diff --git a/src/dbridge.c b/src/dbridge.c index bf03f8d4a..52fd4aa56 100644 --- a/src/dbridge.c +++ b/src/dbridge.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 dbridge.c $NHDT-Date: 1503355815 2017/08/21 22:50:15 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.39 $ */ +/* NetHack 5.0 dbridge.c $NHDT-Date: 1772771734 2026/03/05 20:35:34 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.70 $ */ /* Copyright (c) 1989 by Jean-Christophe Collet */ /* NetHack may be freely redistributed. See license for details. */ @@ -19,23 +19,31 @@ #include "hack.h" -STATIC_DCL void FDECL(get_wall_for_db, (int *, int *)); -STATIC_DCL struct entity *FDECL(e_at, (int, int)); -STATIC_DCL void FDECL(m_to_e, (struct monst *, int, int, struct entity *)); -STATIC_DCL void FDECL(u_to_e, (struct entity *)); -STATIC_DCL void FDECL(set_entity, (int, int, struct entity *)); -STATIC_DCL const char *FDECL(e_nam, (struct entity *)); -STATIC_DCL const char *FDECL(E_phrase, (struct entity *, const char *)); -STATIC_DCL boolean FDECL(e_survives_at, (struct entity *, int, int)); -STATIC_DCL void FDECL(e_died, (struct entity *, int, int)); -STATIC_DCL boolean FDECL(automiss, (struct entity *)); -STATIC_DCL boolean FDECL(e_missed, (struct entity *, BOOLEAN_P)); -STATIC_DCL boolean FDECL(e_jumps, (struct entity *)); -STATIC_DCL void FDECL(do_entity, (struct entity *)); +staticfn void get_wall_for_db(coordxy *, coordxy *); +staticfn struct entity *e_at(coordxy, coordxy); +staticfn void m_to_e(struct monst *, coordxy, coordxy, struct entity *); +staticfn void u_to_e(struct entity *); +staticfn void set_entity(coordxy, coordxy, struct entity *); +staticfn const char *e_nam(struct entity *); +staticfn const char *E_phrase(struct entity *, const char *); +staticfn boolean e_survives_at(struct entity *, coordxy, coordxy); +staticfn void e_died(struct entity *, int, int); +staticfn boolean automiss(struct entity *); +staticfn boolean e_missed(struct entity *, boolean); +staticfn boolean e_jumps(struct entity *); +staticfn void do_entity(struct entity *); +staticfn void nokiller(void); boolean -is_pool(x, y) -int x, y; +is_waterwall(coordxy x, coordxy y) +{ + if (isok(x, y) && IS_WATERWALL(levl[x][y].typ)) + return TRUE; + return FALSE; +} + +boolean +is_pool(coordxy x, coordxy y) { schar ltyp; @@ -51,15 +59,14 @@ int x, y; } boolean -is_lava(x, y) -int x, y; +is_lava(coordxy x, coordxy y) { schar ltyp; if (!isok(x, y)) return FALSE; ltyp = levl[x][y].typ; - if (ltyp == LAVAPOOL + if (ltyp == LAVAPOOL || ltyp == LAVAWALL || (ltyp == DRAWBRIDGE_UP && (levl[x][y].drawbridgemask & DB_UNDER) == DB_LAVA)) return TRUE; @@ -67,8 +74,7 @@ int x, y; } boolean -is_pool_or_lava(x, y) -int x, y; +is_pool_or_lava(coordxy x, coordxy y) { if (is_pool(x, y) || is_lava(x, y)) return TRUE; @@ -77,8 +83,7 @@ int x, y; } boolean -is_ice(x, y) -int x, y; +is_ice(coordxy x, coordxy y) { schar ltyp; @@ -92,8 +97,7 @@ int x, y; } boolean -is_moat(x, y) -int x, y; +is_moat(coordxy x, coordxy y) { schar ltyp; @@ -109,8 +113,7 @@ int x, y; } schar -db_under_typ(mask) -int mask; +db_under_typ(int mask) { switch (mask & DB_UNDER) { case DB_ICE: @@ -128,29 +131,30 @@ int mask; * We want to know whether a wall (or a door) is the portcullis (passageway) * of an eventual drawbridge. * - * Return value: the direction of the drawbridge. + * Return value: the direction of the drawbridge, or -1 if not valid */ - int -is_drawbridge_wall(x, y) -int x, y; +is_drawbridge_wall(coordxy x, coordxy y) { struct rm *lev; + if (!isok(x, y)) + return -1; + lev = &levl[x][y]; if (lev->typ != DOOR && lev->typ != DBWALL) return -1; - if (IS_DRAWBRIDGE(levl[x + 1][y].typ) + if (isok(x + 1, y) && IS_DRAWBRIDGE(levl[x + 1][y].typ) && (levl[x + 1][y].drawbridgemask & DB_DIR) == DB_WEST) return DB_WEST; - if (IS_DRAWBRIDGE(levl[x - 1][y].typ) + if (isok(x - 1, y) && IS_DRAWBRIDGE(levl[x - 1][y].typ) && (levl[x - 1][y].drawbridgemask & DB_DIR) == DB_EAST) return DB_EAST; - if (IS_DRAWBRIDGE(levl[x][y - 1].typ) + if (isok(x, y - 1) && IS_DRAWBRIDGE(levl[x][y - 1].typ) && (levl[x][y - 1].drawbridgemask & DB_DIR) == DB_SOUTH) return DB_SOUTH; - if (IS_DRAWBRIDGE(levl[x][y + 1].typ) + if (isok(x, y + 1) && IS_DRAWBRIDGE(levl[x][y + 1].typ) && (levl[x][y + 1].drawbridgemask & DB_DIR) == DB_NORTH) return DB_NORTH; @@ -163,8 +167,7 @@ int x, y; * (instead of UP or DOWN, as with is_drawbridge_wall). */ boolean -is_db_wall(x, y) -int x, y; +is_db_wall(coordxy x, coordxy y) { return (boolean) (levl[x][y].typ == DBWALL); } @@ -174,8 +177,7 @@ int x, y; * a drawbridge or drawbridge wall. */ boolean -find_drawbridge(x, y) -int *x, *y; +find_drawbridge(coordxy *x, coordxy *y) { int dir; @@ -205,9 +207,8 @@ int *x, *y; /* * Find the drawbridge wall associated with a drawbridge. */ -STATIC_OVL void -get_wall_for_db(x, y) -int *x, *y; +staticfn void +get_wall_for_db(coordxy *x, coordxy *y) { switch (levl[*x][*y].drawbridgemask & DB_DIR) { case DB_NORTH: @@ -231,11 +232,9 @@ int *x, *y; * flag must be put to TRUE if we want the drawbridge to be opened. */ boolean -create_drawbridge(x, y, dir, flag) -int x, y, dir; -boolean flag; +create_drawbridge(coordxy x, coordxy y, int dir, boolean flag) { - int x2, y2; + coordxy x2, y2; boolean horiz; boolean lava = levl[x][y].typ == LAVAPOOL; /* assume initialized map */ @@ -256,6 +255,7 @@ boolean flag; break; default: impossible("bad direction in create_drawbridge"); + FALLTHROUGH; /*FALLTHRU*/ case DB_WEST: horiz = FALSE; @@ -282,40 +282,26 @@ boolean flag; return TRUE; } -struct entity { - struct monst *emon; /* youmonst for the player */ - struct permonst *edata; /* must be non-zero for record to be valid */ - int ex, ey; -}; - -#define ENTITIES 2 - -static NEARDATA struct entity occupants[ENTITIES]; - -STATIC_OVL -struct entity * -e_at(x, y) -int x, y; +staticfn struct entity * +e_at(coordxy x, coordxy y) { int entitycnt; for (entitycnt = 0; entitycnt < ENTITIES; entitycnt++) - if ((occupants[entitycnt].edata) && (occupants[entitycnt].ex == x) - && (occupants[entitycnt].ey == y)) + if (go.occupants[entitycnt].edata + && go.occupants[entitycnt].ex == x + && go.occupants[entitycnt].ey == y) break; debugpline1("entitycnt = %d", entitycnt); #ifdef D_DEBUG wait_synch(); #endif return (entitycnt == ENTITIES) ? (struct entity *) 0 - : &(occupants[entitycnt]); + : &(go.occupants[entitycnt]); } -STATIC_OVL void -m_to_e(mtmp, x, y, etmp) -struct monst *mtmp; -int x, y; -struct entity *etmp; +staticfn void +m_to_e(struct monst *mtmp, coordxy x, coordxy y, struct entity *etmp) { etmp->emon = mtmp; if (mtmp) { @@ -325,47 +311,44 @@ struct entity *etmp; etmp->edata = &mons[PM_LONG_WORM_TAIL]; else etmp->edata = mtmp->data; - } else + } else { etmp->edata = (struct permonst *) 0; + etmp->ex = etmp->ey = 0; + } } -STATIC_OVL void -u_to_e(etmp) -struct entity *etmp; +staticfn void +u_to_e(struct entity *etmp) { - etmp->emon = &youmonst; + etmp->emon = &gy.youmonst; etmp->ex = u.ux; etmp->ey = u.uy; - etmp->edata = youmonst.data; + etmp->edata = gy.youmonst.data; } -STATIC_OVL void -set_entity(x, y, etmp) -int x, y; -struct entity *etmp; +staticfn void +set_entity( + coordxy x, coordxy y, /* location of span or portcullis */ + struct entity *etmp) /* pointer to occupants[0] or occupants[1] */ { - if ((x == u.ux) && (y == u.uy)) + if (u_at(x, y)) u_to_e(etmp); - else if (MON_AT(x, y)) + else /* m_at() might yield Null; that's ok */ m_to_e(m_at(x, y), x, y, etmp); - else - etmp->edata = (struct permonst *) 0; } -#define is_u(etmp) (etmp->emon == &youmonst) -#define e_canseemon(etmp) \ - (is_u(etmp) ? (boolean) TRUE : canseemon(etmp->emon)) +#define is_u(etmp) (etmp->emon == &gy.youmonst) +#define e_canseemon(etmp) (is_u(etmp) || canseemon(etmp->emon)) /* * e_strg is a utility routine which is not actually in use anywhere, since * the specialized routines below suffice for all current purposes. */ -/* #define e_strg(etmp, func) (is_u(etmp)? (char *)0 : func(etmp->emon)) */ +/* #define e_strg(etmp, func) (is_u(etmp) ? (char *) 0 : func(etmp->emon)) */ -STATIC_OVL const char * -e_nam(etmp) -struct entity *etmp; +staticfn const char * +e_nam(struct entity *etmp) { return is_u(etmp) ? "you" : mon_nam(etmp->emon); } @@ -374,10 +357,8 @@ struct entity *etmp; * Generates capitalized entity name, makes 2nd -> 3rd person conversion on * verb, where necessary. */ -STATIC_OVL const char * -E_phrase(etmp, verb) -struct entity *etmp; -const char *verb; +staticfn const char * +E_phrase(struct entity *etmp, const char *verb) { static char wholebuf[80]; @@ -395,16 +376,14 @@ const char *verb; /* * Simple-minded "can it be here?" routine */ -STATIC_OVL boolean -e_survives_at(etmp, x, y) -struct entity *etmp; -int x, y; +staticfn boolean +e_survives_at(struct entity *etmp, coordxy x, coordxy y) { if (noncorporeal(etmp->edata)) return TRUE; if (is_pool(x, y)) - return (boolean) ((is_u(etmp) && (Wwalking || Amphibious || Swimming - || Flying || Levitation)) + return (boolean) ((is_u(etmp) && (Wwalking || Amphibious || Breathless + || Swimming || Flying || Levitation)) || is_swimmer(etmp->edata) || is_flyer(etmp->edata) || is_floater(etmp->edata)); @@ -419,25 +398,25 @@ int x, y; return TRUE; } -STATIC_OVL void -e_died(etmp, xkill_flags, how) -struct entity *etmp; -int xkill_flags, how; +staticfn void +e_died( + struct entity *etmp, + int xkill_flags, int how) { if (is_u(etmp)) { if (how == DROWNING) { - killer.name[0] = 0; /* drown() sets its own killer */ + svk.killer.name[0] = 0; /* drown() sets its own killer */ (void) drown(); } else if (how == BURNING) { - killer.name[0] = 0; /* lava_effects() sets own killer */ + svk.killer.name[0] = 0; /* lava_effects() sets own killer */ (void) lava_effects(); } else { coord xy; /* use more specific killer if specified */ - if (!killer.name[0]) { - killer.format = KILLED_BY_AN; - Strcpy(killer.name, "falling drawbridge"); + if (!svk.killer.name[0]) { + svk.killer.format = KILLED_BY_AN; + Strcpy(svk.killer.name, "falling drawbridge"); } done(how); /* So, you didn't die */ @@ -445,7 +424,7 @@ int xkill_flags, how; if (enexto(&xy, etmp->ex, etmp->ey, etmp->edata)) { pline("A %s force teleports you away...", Hallucination ? "normal" : "strange"); - teleds(xy.x, xy.y, FALSE); + teleds(xy.x, xy.y, TELEDS_NO_FLAGS); } /* otherwise on top of the drawbridge is the * only viable spot in the dungeon, so stay there @@ -457,23 +436,43 @@ int xkill_flags, how; } else { int entitycnt; - killer.name[0] = 0; + svk.killer.name[0] = 0; /* fake "digested to death" damage-type suppresses corpse */ #define mk_message(dest) (((dest & XKILL_NOMSG) != 0) ? (char *) 0 : "") #define mk_corpse(dest) (((dest & XKILL_NOCORPSE) != 0) ? AD_DGST : AD_PHYS) /* if monsters are moving, one of them caused the destruction */ - if (context.mon_moving) + if (svc.context.mon_moving) monkilled(etmp->emon, mk_message(xkill_flags), mk_corpse(xkill_flags)); else /* you caused it */ xkilled(etmp->emon, xkill_flags); + + /* if etmp gets life-saved, kill it again; otherwise we might end up + trying to place another monster (probably a xorn) on same spot */ + if (!DEADMONSTER(etmp->emon)) { + int seeit = canspotmon(etmp->emon); + + xkill_flags |= XKILL_NOMSG | XKILL_NOCONDUCT; + if (svc.context.mon_moving) + monkilled(etmp->emon, "", mk_corpse(xkill_flags)); + else /* you caused it */ + xkilled(etmp->emon, xkill_flags); + + if (DEADMONSTER(etmp->emon)) { + if (seeit) + pline("Unfortunately for %s, %s is still crushed.", + mon_nam(etmp->emon), mhe(etmp->emon)); + } else { + ; /* FIXME: still not dead? What should we do now? */ + } + } etmp->edata = (struct permonst *) 0; /* dead long worm handling */ for (entitycnt = 0; entitycnt < ENTITIES; entitycnt++) { - if (etmp != &(occupants[entitycnt]) - && etmp->emon == occupants[entitycnt].emon) - occupants[entitycnt].edata = (struct permonst *) 0; + if (etmp != &(go.occupants[entitycnt]) + && etmp->emon == go.occupants[entitycnt].emon) + go.occupants[entitycnt].edata = (struct permonst *) 0; } #undef mk_message #undef mk_corpse @@ -483,9 +482,8 @@ int xkill_flags, how; /* * These are never directly affected by a bridge or portcullis. */ -STATIC_OVL boolean -automiss(etmp) -struct entity *etmp; +staticfn boolean +automiss(struct entity *etmp) { return (boolean) ((is_u(etmp) ? Passes_walls : passes_walls(etmp->edata)) || noncorporeal(etmp->edata)); @@ -494,10 +492,8 @@ struct entity *etmp; /* * Does falling drawbridge or portcullis miss etmp? */ -STATIC_OVL boolean -e_missed(etmp, chunks) -struct entity *etmp; -boolean chunks; +staticfn boolean +e_missed(struct entity *etmp, boolean chunks) { int misses; @@ -509,7 +505,7 @@ boolean chunks; if (is_flyer(etmp->edata) && (is_u(etmp) ? !Unaware - : (etmp->emon->mcanmove && !etmp->emon->msleeping))) + : !helpless(etmp->emon))) /* flying requires mobility */ misses = 5; /* out of 8 */ else if (is_floater(etmp->edata) @@ -531,14 +527,13 @@ boolean chunks; /* * Can etmp jump from death? */ -STATIC_OVL boolean -e_jumps(etmp) -struct entity *etmp; +staticfn boolean +e_jumps(struct entity *etmp) { int tmp = 4; /* out of 10 */ if (is_u(etmp) ? (Unaware || Fumbling) - : (!etmp->emon->mcanmove || etmp->emon->msleeping + : (helpless(etmp->emon) || !etmp->edata->mmove || etmp->emon->wormno)) return FALSE; @@ -555,11 +550,11 @@ struct entity *etmp; return (tmp >= rnd(10)) ? TRUE : FALSE; } -STATIC_OVL void -do_entity(etmp) -struct entity *etmp; +staticfn void +do_entity(struct entity *etmp) { - int newx, newy, at_portcullis, oldx, oldy; + coordxy newx, newy, oldx, oldy; + int at_portcullis; boolean must_jump = FALSE, relocates = FALSE, e_inview; struct rm *crm; @@ -599,8 +594,8 @@ struct entity *etmp; } else { if (crm->typ == DRAWBRIDGE_DOWN) { if (is_u(etmp)) { - killer.format = NO_KILLER_PREFIX; - Strcpy(killer.name, + svk.killer.format = NO_KILLER_PREFIX; + Strcpy(svk.killer.name, "crushed to death underneath a drawbridge"); } pline("%s crushed underneath the drawbridge.", @@ -618,11 +613,13 @@ struct entity *etmp; relocates = TRUE; debugpline0("Jump succeeds!"); } else { - if (e_inview) + if (e_inview) { pline("%s crushed by the falling portcullis!", E_phrase(etmp, "are")); - else if (!Deaf) + } else if (!Deaf) { + Soundeffect(se_crushing_sound, 100); You_hear("a crushing sound."); + } e_died(etmp, XKILL_NOCORPSE | (e_inview ? XKILL_GIVEMSG : XKILL_NOMSG), @@ -717,8 +714,8 @@ struct entity *etmp; E_phrase(etmp, "disappear")); } if (!e_survives_at(etmp, etmp->ex, etmp->ey)) { - killer.format = KILLED_BY_AN; - Strcpy(killer.name, "closing drawbridge"); + svk.killer.format = KILLED_BY_AN; + Strcpy(svk.killer.name, "closing drawbridge"); e_died(etmp, XKILL_NOMSG, CRUSHING); return; } @@ -726,8 +723,10 @@ struct entity *etmp; } else { debugpline1("%s on drawbridge square", E_phrase(etmp, "are")); if (is_pool(etmp->ex, etmp->ey) && !e_inview) - if (!Deaf) + if (!Deaf) { + Soundeffect(se_splash, 100); You_hear("a splash."); + } if (e_survives_at(etmp, etmp->ex, etmp->ey)) { if (e_inview && !is_flyer(etmp->edata) && !is_floater(etmp->edata)) @@ -748,8 +747,8 @@ struct entity *etmp; pline("%s into the %s.", E_phrase(etmp, "fall"), lava ? hliquid("lava") : "moat"); } - killer.format = NO_KILLER_PREFIX; - Strcpy(killer.name, "fell from a drawbridge"); + svk.killer.format = NO_KILLER_PREFIX; + Strcpy(svk.killer.name, "fell from a drawbridge"); e_died(etmp, /* CRUSHING is arbitrary */ XKILL_NOCORPSE | (e_inview ? XKILL_GIVEMSG : XKILL_NOMSG), is_pool(etmp->ex, etmp->ey) ? DROWNING @@ -759,19 +758,25 @@ struct entity *etmp; } } -/* clear stale reason for death before returning */ -#define nokiller() (killer.name[0] = '\0', killer.format = 0) +/* clear stale reason for death and both 'entities' before returning */ +staticfn void +nokiller(void) +{ + svk.killer.name[0] = '\0'; + svk.killer.format = 0; + m_to_e((struct monst *) 0, 0, 0, &go.occupants[0]); + m_to_e((struct monst *) 0, 0, 0, &go.occupants[1]); +} /* * Close the drawbridge located at x,y */ void -close_drawbridge(x, y) -int x, y; +close_drawbridge(coordxy x, coordxy y) { - register struct rm *lev1, *lev2; + struct rm *lev1, *lev2; struct trap *t; - int x2, y2; + coordxy x2, y2; lev1 = &levl[x][y]; if (lev1->typ != DRAWBRIDGE_DOWN) @@ -779,14 +784,16 @@ int x, y; x2 = x; y2 = y; get_wall_for_db(&x2, &y2); - if (cansee(x, y) || cansee(x2, y2)) + if (cansee(x, y) || cansee(x2, y2)) { You_see("a drawbridge %s up!", (((u.ux == x || u.uy == y) && !Underwater) || distu(x2, y2) < distu(x, y)) ? "coming" : "going"); - else /* "5 gears turn" for castle drawbridge tune */ + } else { /* "5 gears turn" for castle drawbridge tune */ + Soundeffect(se_chains_rattling_gears_turning, 75); You_hear("chains rattling and gears turning."); + } lev1->typ = DRAWBRIDGE_UP; lev2 = &levl[x2][y2]; lev2->typ = DBWALL; @@ -801,13 +808,15 @@ int x, y; break; } lev2->wall_info = W_NONDIGGABLE; - set_entity(x, y, &(occupants[0])); - set_entity(x2, y2, &(occupants[1])); - do_entity(&(occupants[0])); /* Do set_entity after first */ - set_entity(x2, y2, &(occupants[1])); /* do_entity for worm tail */ - do_entity(&(occupants[1])); - if (OBJ_AT(x, y) && !Deaf) + set_entity(x, y, &(go.occupants[0])); + set_entity(x2, y2, &(go.occupants[1])); + do_entity(&(go.occupants[0])); /* Do set_entity after first */ + set_entity(x2, y2, &(go.occupants[1])); /* do_entity for worm tail */ + do_entity(&(go.occupants[1])); + if (OBJ_AT(x, y) && !Deaf) { + Soundeffect(se_smashing_and_crushing, 75); You_hear("smashing and crushing."); + } (void) revive_nasty(x, y, (char *) 0); (void) revive_nasty(x2, y2, (char *) 0); delallobj(x, y); @@ -828,12 +837,11 @@ int x, y; * Open the drawbridge located at x,y */ void -open_drawbridge(x, y) -int x, y; +open_drawbridge(coordxy x, coordxy y) { - register struct rm *lev1, *lev2; + struct rm *lev1, *lev2; struct trap *t; - int x2, y2; + coordxy x2, y2; lev1 = &levl[x][y]; if (lev1->typ != DRAWBRIDGE_UP) @@ -841,20 +849,22 @@ int x, y; x2 = x; y2 = y; get_wall_for_db(&x2, &y2); - if (cansee(x, y) || cansee(x2, y2)) + if (cansee(x, y) || cansee(x2, y2)) { You_see("a drawbridge %s down!", (distu(x2, y2) < distu(x, y)) ? "going" : "coming"); - else /* "5 gears turn" for castle drawbridge tune */ + } else { /* "5 gears turn" for castle drawbridge tune */ + Soundeffect(se_gears_turning_chains_rattling, 100); You_hear("gears turning and chains rattling."); + } lev1->typ = DRAWBRIDGE_DOWN; lev2 = &levl[x2][y2]; lev2->typ = DOOR; lev2->doormask = D_NODOOR; - set_entity(x, y, &(occupants[0])); - set_entity(x2, y2, &(occupants[1])); - do_entity(&(occupants[0])); /* do set_entity after first */ - set_entity(x2, y2, &(occupants[1])); /* do_entity for worm tails */ - do_entity(&(occupants[1])); + set_entity(x, y, &(go.occupants[0])); + set_entity(x2, y2, &(go.occupants[1])); + do_entity(&(go.occupants[0])); /* do set_entity after first */ + set_entity(x2, y2, &(go.occupants[1])); /* do_entity for worm tails */ + do_entity(&(go.occupants[1])); (void) revive_nasty(x, y, (char *) 0); delallobj(x, y); if ((t = t_at(x, y)) != 0) @@ -875,15 +885,15 @@ int x, y; * Let's destroy the drawbridge located at x,y */ void -destroy_drawbridge(x, y) -int x, y; +destroy_drawbridge(coordxy x, coordxy y) { - register struct rm *lev1, *lev2; + struct rm *lev1, *lev2; struct trap *t; struct obj *otmp; - int x2, y2, i; + coordxy x2, y2; + int i; boolean e_inview; - struct entity *etmp1 = &(occupants[0]), *etmp2 = &(occupants[1]); + struct entity *etmp1 = &(go.occupants[0]), *etmp2 = &(go.occupants[1]); lev1 = &levl[x][y]; if (!IS_DRAWBRIDGE(lev1->typ)) @@ -897,18 +907,19 @@ int x, y; struct obj *otmp2; boolean lava = (lev1->drawbridgemask & DB_UNDER) == DB_LAVA; + Soundeffect(se_loud_splash, 100); /* Deaf-aware */ if (lev1->typ == DRAWBRIDGE_UP) { - if (cansee(x2, y2)) + if (cansee(x2, y2) || u_at(x2, y2)) pline_The("portcullis of the drawbridge falls into the %s!", lava ? hliquid("lava") : "moat"); - else if (!Deaf) - You_hear("a loud *SPLASH*!"); + else + You_hear("a loud *SPLASH*!"); /* Deaf-aware */ } else { - if (cansee(x, y)) + if (cansee(x, y) || u_at(x, y)) pline_The("drawbridge collapses into the %s!", lava ? hliquid("lava") : "moat"); - else if (!Deaf) - You_hear("a loud *SPLASH*!"); + else + You_hear("a loud *SPLASH*!"); /* Deaf-aware */ } lev1->typ = lava ? LAVAPOOL : MOAT; lev1->drawbridgemask = 0; @@ -917,10 +928,12 @@ int x, y; (void) flooreffects(otmp2, x, y, "fall"); } } else { - if (cansee(x, y)) + /* no moat beneath */ + Soundeffect(se_loud_crash, 100); /* Deaf-aware */ + if (cansee(x, y) || u_at(x, y)) pline_The("drawbridge disintegrates!"); else - You_hear("a loud *CRASH*!"); + You_hear("a loud *CRASH*!"); /* Deaf-aware */ lev1->typ = ((lev1->drawbridgemask & DB_ICE) ? ICE : ROOM); lev1->icedpool = ((lev1->drawbridgemask & DB_ICE) ? ICED_MOAT : 0); } @@ -947,6 +960,7 @@ int x, y; newsym(x2, y2); if (!does_block(x2, y2, lev2)) unblock_point(x2, y2); /* vision */ + vision_recalc(0); if (Is_stronghold(&u.uz)) u.uevent.uopened_dbridge = TRUE; @@ -957,8 +971,8 @@ int x, y; if (e_inview) pline("%s blown apart by flying debris.", E_phrase(etmp2, "are")); - killer.format = KILLED_BY_AN; - Strcpy(killer.name, "exploding drawbridge"); + svk.killer.format = KILLED_BY_AN; + Strcpy(svk.killer.name, "exploding drawbridge"); e_died(etmp2, XKILL_NOCORPSE | (e_inview ? XKILL_GIVEMSG : XKILL_NOMSG), CRUSHING); /*no corpse*/ @@ -984,13 +998,14 @@ int x, y; E_phrase(etmp1, "are")); } else { if (!Deaf && !is_u(etmp1) && !is_pool(x, y)) { + Soundeffect(se_crushing_sound, 75); You_hear("a crushing sound."); } else { debugpline1("%s from shrapnel", E_phrase(etmp1, "die")); } } - killer.format = KILLED_BY_AN; - Strcpy(killer.name, "collapsing drawbridge"); + svk.killer.format = KILLED_BY_AN; + Strcpy(svk.killer.name, "collapsing drawbridge"); e_died(etmp1, XKILL_NOCORPSE | (e_inview ? XKILL_GIVEMSG : XKILL_NOMSG), CRUSHING); /*no corpse*/ @@ -999,6 +1014,8 @@ int x, y; } } nokiller(); + if (Is_stronghold(&u.uz)) + u.uevent.uheard_tune = 3; /* bridge is gone so tune is now useless */ } /*dbridge.c*/ diff --git a/src/decl.c b/src/decl.c index 4ae704d41..4fe4585f0 100644 --- a/src/decl.c +++ b/src/decl.c @@ -1,235 +1,22 @@ -/* NetHack 3.6 decl.c $NHDT-Date: 1573869062 2019/11/16 01:51:02 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.149 $ */ +/* NetHack 5.0 decl.c $NHDT-Date: 1736530208 2025/01/10 09:30:08 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.341 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Michael Allison, 2009. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -int NDECL((*afternmv)); -int NDECL((*occupation)); - -/* from xxxmain.c */ -const char *hname = 0; /* name of the game (argv[0] of main) */ -int hackpid = 0; /* current process id */ -#if defined(UNIX) || defined(VMS) -int locknum = 0; /* max num of simultaneous users */ -#endif -#ifdef DEF_PAGER -char *catmore = 0; /* default pager */ -#endif -char chosen_windowtype[WINTYPELEN]; - -NEARDATA int bases[MAXOCLASSES] = DUMMY; - -NEARDATA int multi = 0; -const char *multi_reason = NULL; -NEARDATA int nroom = 0; -NEARDATA int nsubroom = 0; -NEARDATA int occtime = 0; - -/* maze limits must be even; masking off lowest bit guarantees that */ -int x_maze_max = (COLNO - 1) & ~1, y_maze_max = (ROWNO - 1) & ~1; - -int otg_temp; /* used by object_to_glyph() [otg] */ - -NEARDATA int in_doagain = 0; - -/* - * The following structure will be initialized at startup time with - * the level numbers of some "important" things in the game. - */ -struct dgn_topology dungeon_topology = { DUMMY }; - -struct q_score quest_status = DUMMY; - -NEARDATA int warn_obj_cnt = 0; -NEARDATA int smeq[MAXNROFROOMS + 1] = DUMMY; -NEARDATA int doorindex = 0; -NEARDATA char *save_cm = 0; - -NEARDATA struct kinfo killer = DUMMY; -NEARDATA long done_money = 0; -const char *nomovemsg = 0; -NEARDATA char plname[PL_NSIZ] = DUMMY; /* player name */ -NEARDATA char pl_character[PL_CSIZ] = DUMMY; -NEARDATA char pl_race = '\0'; - -NEARDATA char pl_fruit[PL_FSIZ] = DUMMY; -NEARDATA struct fruit *ffruit = (struct fruit *) 0; - -NEARDATA char tune[6] = DUMMY; -NEARDATA boolean ransacked = 0; - -const char *occtxt = DUMMY; -const char quitchars[] = " \r\n\033"; -const char vowels[] = "aeiouAEIOU"; -const char ynchars[] = "yn"; -const char ynqchars[] = "ynq"; -const char ynaqchars[] = "ynaq"; -const char ynNaqchars[] = "yn#aq"; -NEARDATA long yn_number = 0L; - -const char disclosure_options[] = "iavgco"; - -#if defined(MICRO) || defined(WIN32) -char hackdir[PATHLEN]; /* where rumors, help, record are */ -#ifdef MICRO -char levels[PATHLEN]; /* where levels are */ -#endif -#endif /* MICRO || WIN32 */ - -#ifdef MFLOPPY -char permbones[PATHLEN]; /* where permanent copy of bones go */ -int ramdisk = FALSE; /* whether to copy bones to levels or not */ -int saveprompt = TRUE; -const char *alllevels = "levels.*"; -const char *allbones = "bones*.*"; -#endif - -struct linfo level_info[MAXLINFO]; - -NEARDATA struct sinfo program_state; - -/* x/y/z deltas for the 10 movement directions (8 compass pts, 2 up/down) */ -const schar xdir[10] = { -1, -1, 0, 1, 1, 1, 0, -1, 0, 0 }; -const schar ydir[10] = { 0, -1, -1, -1, 0, 1, 1, 1, 0, 0 }; -const schar zdir[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, -1 }; - -NEARDATA schar tbx = 0, tby = 0; /* mthrowu: target */ - -/* for xname handling of multiple shot missile volleys: - number of shots, index of current one, validity check, shoot vs throw */ -NEARDATA struct multishot m_shot = { 0, 0, STRANGE_OBJECT, FALSE }; - -NEARDATA dungeon dungeons[MAXDUNGEON]; /* ini'ed by init_dungeon() */ -NEARDATA s_level *sp_levchn; -NEARDATA stairway upstair = { 0, 0, { 0, 0 }, 0 }, - dnstair = { 0, 0, { 0, 0 }, 0 }; -NEARDATA stairway upladder = { 0, 0, { 0, 0 }, 0 }, - dnladder = { 0, 0, { 0, 0 }, 0 }; -NEARDATA stairway sstairs = { 0, 0, { 0, 0 }, 0 }; -NEARDATA dest_area updest = { 0, 0, 0, 0, 0, 0, 0, 0 }; -NEARDATA dest_area dndest = { 0, 0, 0, 0, 0, 0, 0, 0 }; -NEARDATA coord inv_pos = { 0, 0 }; - -NEARDATA boolean defer_see_monsters = FALSE; -NEARDATA boolean in_mklev = FALSE; -NEARDATA boolean stoned = FALSE; /* done to monsters hit by 'c' */ -NEARDATA boolean unweapon = FALSE; -NEARDATA boolean mrg_to_wielded = FALSE; -/* weapon picked is merged with wielded one */ - -NEARDATA boolean in_steed_dismounting = FALSE; -NEARDATA boolean has_strong_rngseed = FALSE; - -NEARDATA coord bhitpos = DUMMY; -NEARDATA coord doors[DOORMAX] = { DUMMY }; - -NEARDATA struct mkroom rooms[(MAXNROFROOMS + 1) * 2] = { DUMMY }; -NEARDATA struct mkroom *subrooms = &rooms[MAXNROFROOMS + 1]; -struct mkroom *upstairs_room, *dnstairs_room, *sstairs_room; - -dlevel_t level; /* level map */ -struct trap *ftrap = (struct trap *) 0; -NEARDATA struct monst youmonst = DUMMY; -NEARDATA struct context_info context = DUMMY; -NEARDATA struct flag flags = DUMMY; -#ifdef SYSFLAGS -NEARDATA struct sysflag sysflags = DUMMY; -#endif -NEARDATA struct instance_flags iflags = DUMMY; -NEARDATA struct you u = DUMMY; -NEARDATA time_t ubirthday = DUMMY; -NEARDATA struct u_realtime urealtime = DUMMY; - -schar lastseentyp[COLNO][ROWNO] = { - DUMMY -}; /* last seen/touched dungeon typ */ - -NEARDATA struct obj - *invent = (struct obj *) 0, - *uwep = (struct obj *) 0, *uarm = (struct obj *) 0, - *uswapwep = (struct obj *) 0, - *uquiver = (struct obj *) 0, /* quiver */ - *uarmu = (struct obj *) 0, /* under-wear, so to speak */ - *uskin = (struct obj *) 0, /* dragon armor, if a dragon */ - *uarmc = (struct obj *) 0, *uarmh = (struct obj *) 0, - *uarms = (struct obj *) 0, *uarmg = (struct obj *) 0, - *uarmf = (struct obj *) 0, *uamul = (struct obj *) 0, - *uright = (struct obj *) 0, *uleft = (struct obj *) 0, - *ublindf = (struct obj *) 0, *uchain = (struct obj *) 0, - *uball = (struct obj *) 0; -/* some objects need special handling during destruction or placement */ -NEARDATA struct obj - *current_wand = 0, /* wand currently zapped/applied */ - *thrownobj = 0, /* object in flight due to throwing */ - *kickedobj = 0; /* object in flight due to kicking */ - -#ifdef TEXTCOLOR -/* - * This must be the same order as used for buzz() in zap.c. - * (They're only used in mapglyph.c so probably shouldn't be here.) - */ -const int zapcolors[NUM_ZAP] = { - HI_ZAP, /* 0 - missile */ - CLR_ORANGE, /* 1 - fire */ - CLR_WHITE, /* 2 - frost */ - HI_ZAP, /* 3 - sleep */ - CLR_BLACK, /* 4 - death */ - CLR_WHITE, /* 5 - lightning */ - /* 3.6.3: poison gas zap used to be yellow and acid zap was green, - which conflicted with the corresponding dragon colors */ - CLR_GREEN, /* 6 - poison gas */ - CLR_YELLOW, /* 7 - acid */ -}; -#endif /* text color */ - -const int shield_static[SHIELD_COUNT] = { - S_ss1, S_ss2, S_ss3, S_ss2, S_ss1, S_ss2, S_ss4, /* 7 per row */ - S_ss1, S_ss2, S_ss3, S_ss2, S_ss1, S_ss2, S_ss4, - S_ss1, S_ss2, S_ss3, S_ss2, S_ss1, S_ss2, S_ss4, +const char * const nhcb_name[NUM_NHCB] = { + "cmd_before", + "level_enter", + "level_leave", + "end_turn", }; -NEARDATA struct spell spl_book[MAXSPELL + 1] = { DUMMY }; - -NEARDATA long moves = 1L, monstermoves = 1L; -/* These diverge when player is Fast */ -NEARDATA long wailmsg = 0L; - -/* objects that are moving to another dungeon level */ -NEARDATA struct obj *migrating_objs = (struct obj *) 0; -/* objects not yet paid for */ -NEARDATA struct obj *billobjs = (struct obj *) 0; - -/* used to zero all elements of a struct obj and a struct monst */ -NEARDATA const struct obj zeroobj = DUMMY; -NEARDATA const struct monst zeromonst = DUMMY; -/* used to zero out union any; initializer deliberately omitted */ -NEARDATA const anything zeroany; - -/* originally from dog.c */ -NEARDATA char dogname[PL_PSIZ] = DUMMY; -NEARDATA char catname[PL_PSIZ] = DUMMY; -NEARDATA char horsename[PL_PSIZ] = DUMMY; -char preferred_pet; /* '\0', 'c', 'd', 'n' (none) */ -/* monsters that went down/up together with @ */ -NEARDATA struct monst *mydogs = (struct monst *) 0; -/* monsters that are moving to another dungeon level */ -NEARDATA struct monst *migrating_mons = (struct monst *) 0; -NEARDATA struct autopickup_exception *apelist = - (struct autopickup_exception *)0; - -NEARDATA struct mvitals mvitals[NUMMONS]; -NEARDATA long domove_attempting = 0L; -NEARDATA long domove_succeeded = 0L; - -NEARDATA struct c_color_names c_color_names = { +int nhcb_counts[NUM_NHCB] = DUMMY; +NEARDATA const struct c_color_names c_color_names = { "black", "amber", "golden", "light blue", "red", "green", "silver", "blue", "purple", "white", "orange" }; - -struct menucoloring *menu_colorings = NULL; - const char *c_obj_colors[] = { "black", /* CLR_BLACK */ "red", /* CLR_RED */ @@ -249,50 +36,33 @@ const char *c_obj_colors[] = { "white", /* CLR_WHITE */ }; -struct c_common_strings c_common_strings = { "Nothing happens.", - "That's enough tries!", - "That is a silly thing to %s.", - "shudder for a moment.", - "something", - "Something", - "You can move again.", - "Never mind.", - "vision quickly clears.", - { "the", "your" }, - { "mon", "you" } }; +const struct c_common_strings c_common_strings = + { "Nothing happens.", + "Nothing seems to happen.", + "That's enough tries!", + "That is a silly thing to %s.", + "shudder for a moment.", + "something", + "Something", + "You can move again.", + "Never mind.", + "vision quickly clears.", + { "the", "your" }, + { "mon", "you" } +}; -/* NOTE: the order of these words exactly corresponds to the - order of oc_material values #define'd in objclass.h. */ -const char *materialnm[] = { "mysterious", "liquid", "wax", "organic", - "flesh", "paper", "cloth", "leather", - "wooden", "bone", "dragonhide", "iron", - "metal", "copper", "silver", "gold", - "platinum", "mithril", "plastic", "glass", - "gemstone", "stone" }; +const char disclosure_options[] = "iavgco"; +char emptystr[] = {0}; /* non-const */ -/* Vision */ -NEARDATA boolean vision_full_recalc = 0; -NEARDATA char **viz_array = 0; /* used in cansee() and couldsee() macros */ +NEARDATA struct flag flags; /* extern declaration is in flag.h, not decl.h */ /* Global windowing data, defined here for multi-window-system support */ -NEARDATA winid WIN_MESSAGE = WIN_ERR; -NEARDATA winid WIN_STATUS = WIN_ERR; -NEARDATA winid WIN_MAP = WIN_ERR, WIN_INVEN = WIN_ERR; -char toplines[TBUFSZ]; -/* Windowing stuff that's really tty oriented, but present for all ports */ -struct tc_gbl_data tc_gbl_data = { 0, 0, 0, 0 }; /* AS,AE, LI,CO */ - -char *fqn_prefix[PREFIX_COUNT] = { (char *) 0, (char *) 0, (char *) 0, - (char *) 0, (char *) 0, (char *) 0, - (char *) 0, (char *) 0, (char *) 0, - (char *) 0 }; #ifdef WIN32 boolean fqn_prefix_locked[PREFIX_COUNT] = { FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE }; #endif - #ifdef PREFIXES_IN_USE const char *fqn_prefix_names[PREFIX_COUNT] = { "hackdir", "leveldir", "savedir", "bonesdir", "datadir", @@ -300,64 +70,1134 @@ const char *fqn_prefix_names[PREFIX_COUNT] = { }; #endif -NEARDATA struct savefile_info sfcap = { -#ifdef NHSTDC - 0x00000000UL -#else - 0x00000000L -#endif -#if defined(COMPRESS) || defined(ZLIB_COMP) - | SFI1_EXTERNALCOMP +/* used by coloratt.c, options.c, utf8map.c, windows.c */ +const char hexdd[33] = "00112233445566778899aAbBcCdDeEfF"; + +/* x/y/z deltas for the 10 movement directions (8 compass pts, 2 down/up) */ +const schar xdir[N_DIRS_Z] = { -1, -1, 0, 1, 1, 1, 0, -1, 0, 0 }; +const schar ydir[N_DIRS_Z] = { 0, -1, -1, -1, 0, 1, 1, 1, 0, 0 }; +const schar zdir[N_DIRS_Z] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, -1 }; +/* reordered directions, cardinals first */ +const schar dirs_ord[N_DIRS] = + { DIR_W, DIR_N, DIR_E, DIR_S, DIR_NW, DIR_NE, DIR_SE, DIR_SW }; + +NEARDATA boolean has_strong_rngseed = FALSE; +struct engr *head_engr; +NEARDATA struct instance_flags iflags; +NEARDATA struct accessibility_data a11y; +/* NOTE: the order of these words exactly corresponds to the + order of oc_material values #define'd in objclass.h. */ +const char *materialnm[] = { "mysterious", "liquid", "wax", "organic", + "flesh", "paper", "cloth", "leather", + "wooden", "bone", "dragonhide", "iron", + "metal", "copper", "silver", "gold", + "platinum", "mithril", "plastic", "glass", + "gemstone", "stone" }; +const char quitchars[] = " \r\n\033"; +const int shield_static[SHIELD_COUNT] = { + S_ss1, S_ss2, S_ss3, S_ss2, S_ss1, S_ss2, S_ss4, /* 7 per row */ + S_ss1, S_ss2, S_ss3, S_ss2, S_ss1, S_ss2, S_ss4, + S_ss1, S_ss2, S_ss3, S_ss2, S_ss1, S_ss2, S_ss4, +}; +NEARDATA struct you u; +NEARDATA time_t ubirthday; +NEARDATA struct u_realtime urealtime; +NEARDATA struct obj *uwep, *uarm, *uswapwep, + *uquiver, /* quiver */ + *uarmu, /* under-wear, so to speak */ + *uskin, /* dragon armor, if a dragon */ + *uarmc, *uarmh, *uarms, *uarmg,*uarmf, *uamul, + *uright, *uleft, *ublindf, *uchain, *uball; +const char vowels[] = "aeiouAEIOU"; +NEARDATA winid WIN_MESSAGE, WIN_STATUS, WIN_MAP, WIN_INVEN; +const char ynchars[] = "yn"; +const char ynqchars[] = "ynq"; +const char ynaqchars[] = "ynaq"; +const char ynNaqchars[] = "yn#aq"; +const char rightleftchars[] = "rl"; +const char hidespinchars[] = "hsq"; +NEARDATA long yn_number = 0L; +#ifdef PANICTRACE +const char *ARGV0; #endif -#if defined(ZEROCOMP) - | SFI1_ZEROCOMP + +static const struct Role urole_init_data = { + { "Undefined", 0 }, + { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } }, + "L", "N", "C", + "Xxx", "home", "locate", + NON_PM, NON_PM, NON_PM, NON_PM, NON_PM, NON_PM, NON_PM, + 0, 0, 0, 0, + /* Str Int Wis Dex Con Cha */ + { 7, 7, 7, 7, 7, 7 }, + { 20, 15, 15, 20, 20, 10 }, + /* Init Lower Higher */ + { 10, 0, 0, 8, 1, 0 }, /* Hit points */ + { 2, 0, 0, 2, 0, 3 }, + 14, /* Energy */ + 0, + 10, + 0, + 0, + 4, + A_INT, + 0, + -3 +}; + +static const struct Race urace_init_data = { + "something", + "undefined", + "something", + "Xxx", + { 0, 0 }, + NON_PM, + NON_PM, + NON_PM, + 0, + 0, + 0, + 0, + /* Str Int Wis Dex Con Cha */ + { 3, 3, 3, 3, 3, 3 }, + { STR18(100), 18, 18, 18, 18, 18 }, + /* Init Lower Higher */ + { 2, 0, 0, 2, 1, 0 }, /* Hit points */ + { 1, 0, 2, 0, 2, 0 } /* Energy */ +}; + +struct display_hints disp = { 0 }; + +static const struct instance_globals_a g_init_a = { + /* artifact.c */ + /* decl.c */ + UNDEFINED_PTR, /* afternmv */ + /* detect.c */ + 0, /* already_found_flag */ + /* do.c */ + FALSE, /* at_ladder */ + /* dog.c */ + UNDEFINED_PTR, /* apelist */ + /* end.c */ + { UNDEFINED_VALUES }, /* amulets */ + /* mon.c */ + UNDEFINED_PTR, /* animal_list */ + UNDEFINED_VALUE, /* animal_list_count */ +#ifdef CHANGE_COLOR + /* options.c */ + { 0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U, + 0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U }, /* altpalette[CLR_MAX] */ #endif -#if defined(RLECOMP) - | SFI1_RLECOMP + /* pickup.c */ + 0, /* A_first_hint */ + 0, /* A_second_hint */ + UNDEFINED_VALUE, /* abort_looting */ + /* shk.c */ + FALSE, /* auto_credit */ + /* sounds.c */ + soundlib_nosound, /* enum soundlib_ids active_soundlib */ + + /* trap.c */ + { 0, 0, FALSE }, /* acid_ctx */ + TRUE, /* havestate*/ +}; + +static const struct instance_globals_b g_init_b = { + /* botl.c */ + { { { NULL, NULL, 0L, FALSE, FALSE, 0, ANY_INVALID, { 0 }, { 0 }, NULL, 0, 0, 0 +#ifdef STATUS_HILITES + , UNDEFINED_PTR, UNDEFINED_PTR #endif - , -#ifdef NHSTDC - 0x00000000UL, 0x00000000UL -#else - 0x00000000L, 0x00000000L + } } + }, /* blstats */ + FALSE, /* blinit */ +#ifdef STATUS_HILITES + 0L, /* bl_hilite_moves */ #endif + /* decl.c */ + { 0, 0 }, /* bhitpos */ + UNDEFINED_PTR, /* billobjs */ + /* files.c */ + BONESINIT, /* bones */ + /* hack.c */ + 0U, /* bldrpush_oid - last boulder pushed */ + 0L, /* bldrpushtime - turn message was given about pushing that boulder */ + /* mkmaze.c */ + { {COLNO, ROWNO, 0, 0}, {COLNO, ROWNO, 0, 0}, + FALSE, FALSE, 0, 0, { 0 } }, /* bughack */ + /* pickup.c */ + FALSE, /* bucx_filter */ + /* zap.c */ + NULL, /* buzzer -- monst that zapped/cast/breathed to initiate buzz() */ + FALSE, /* bot_disabled */ + + TRUE, /* havestate*/ }; -NEARDATA struct savefile_info sfrestinfo, sfsaveinfo = { -#ifdef NHSTDC - 0x00000000UL -#else - 0x00000000L -#endif -#if defined(COMPRESS) || defined(ZLIB_COMP) - | SFI1_EXTERNALCOMP +static const struct instance_globals_c g_init_c = { + UNDEFINED_VALUES, /* command_queue */ + /* botl.c */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 }, /* cond_hilites */ + 0, /* condmenu_sortorder */ + /* cmd.c */ + UNDEFINED_VALUES, /* Cmd */ + { 0, 0 }, /* clicklook_cc */ + /* decl.c */ + UNDEFINED_VALUES, /* chosen_windowtype */ + 0, /* cmd_key */ + NULL, /* cmd_bind */ + 0L, /* command_count */ + UNDEFINED_PTR, /* current_wand */ +#ifdef DEF_PAGER + NULL, /* catmore */ #endif -#if defined(ZEROCOMP) - | SFI1_ZEROCOMP + /* dog.c */ + DUMMY, /* catname */ + /* end.c */ + NULL, /* crash_email */ + NULL, /* crash_name */ + -1, /* crash_urlmax */ + /* symbols.c */ + 0, /* currentgraphics */ + /* files.c */ + NULL, /* cmdline_rcfile */ + NULL, /* config_section_chosen */ + NULL, /* config_section_current */ + FALSE, /* chosen_symset_start */ + FALSE, /* chosen_symset_end */ + /* invent.c */ + WIN_ERR, /* cached_pickinv_win */ + 0, /* core_invent_state */ + /* options.c */ + NULL, /* cmdline_windowsys */ + (struct menucoloring *) 0, /* color_colorings */ + /* pickup.c */ + (struct obj *) 0, /* current_container */ + FALSE, /* class_filter */ + /* questpgr.c */ + UNDEFINED_VALUES, /* cvt_buf */ + /* sounds.c */ + soundlib_nosound, /* chosen_soundlib */ + UNDEFINED_PTR, /* coder */ + /* uhitm.c */ + NON_PM, /* corpsenm_digested */ + FALSE, /* converted_savefile_loaded */ + TRUE, /* havestate*/ +}; + +static const struct instance_globals_d g_init_d = { + /* decl.c */ + 0, /* doorindex */ + 0L, /* done_money */ + 0L, /* domove_attempting */ + 0L, /* domove_succeeded */ + FALSE, /* defer_see_monsters */ + /* dig.c */ + UNDEFINED_VALUE, /* did_dig_msg */ + /* do.c */ + NULL, /* dfr_pre_msg */ + NULL, /* dfr_post_msg */ + 0, /* did_nothing_flag */ + /* dog.c */ + DUMMY, /* dogname */ + /* end.c */ + 0L, /* done_seq */ + /* mon.c */ + FALSE, /* disintegested */ + /* objname.c */ + 0, /* distantname */ + /* pickup.c */ + FALSE, /* decor_fumble_override */ + FALSE, /* decor_levitate_override */ + FALSE, /* deferred_showpaths */ + NULL, /* deferred_showpaths_dir */ + TRUE, /* havestate*/ +}; + +static const struct instance_globals_e g_init_e = { + /* cmd.c */ + WIN_ERR, /* en_win */ + FALSE, /* en_via_menu */ + UNDEFINED_VALUE, /* ext_tlist */ + /* eat.c */ + NULL, /* eatmbuf */ + /* mkmaze.c */ + UNDEFINED_PTR, /* ebubbles */ + /* new */ + 0, /* early_raw_messages */ + TRUE, /* havestate*/ +}; + +static const struct instance_globals_f g_init_f = { + /* decl.c */ + UNDEFINED_PTR, /* ftrap */ + { NULL }, /* fqn_prefix */ + NULL, /* ffruit */ + /* eat.c */ + FALSE, /* force_save_hs */ + /* mhitm.c */ + FALSE, /* far_noise */ + /* rumors.c */ + 0L, /* false_rumor_size */ + 0UL, /* false_rumor_start*/ + 0L, /* false_rumor_end */ + /* shk.c */ + 0L, /* followmsg */ + TRUE, /* havestate*/ +}; + +static const struct instance_globals_g g_init_g = { + /* display.c */ + { { { 0 } } }, /* gbuf */ + UNDEFINED_VALUES, /* gbuf_start */ + UNDEFINED_VALUES, /* gbug_stop */ + + /* do_name.c */ + 0, 0, /* getposx, getposy */ + UNDEFINED_PTR, /* gloc_filter_map */ + UNDEFINED_VALUE, /* gloc_filter_floodfill_match_glyph */ + /* dog.c */ + UNDEFINED_VALUE, /* gtyp */ + 0, /* gx */ + 0, /* gy */ + /* dokick.c */ + NULL, /* gate_str */ + /* end.c */ + { UNDEFINED_VALUES }, /* gems */ + /* invent.c */ + 0L, /* glyph_reset_timestamp */ + /* nhlua.c */ + FALSE, /* gmst_stored */ + 0L, /* gmst_moves */ + NULL, /* gmst_invent */ + NULL, NULL, NULL, /* gmst_ubak, gmst_disco, gmst_mvitals */ + { DUMMY }, /* gmst_spl_book */ + /* pline.c */ + UNDEFINED_PTR, /* gamelog */ + /* region.c */ + FALSE, /* gas_cloud_diss_within */ + 0, /* gas_cloud_diss_seen */ + /* new */ + /* per-level glyph mapping flags */ + 0L, /* glyphmap_perlevel_flags */ + TRUE, /* havestate*/ +}; + +static const struct instance_globals_h g_init_h = { + /* decl.c */ + NULL, /* hname */ +#if defined(MICRO) || defined(WIN32) + UNDEFINED_VALUES, /* hackdir */ +#endif /* MICRO || WIN32 */ + 1L << 3, /* hero_seq: sequence number for hero movement, 'moves*8 + n' + * where n is usually 1, sometimes 2 when Fast/Very_fast, maybe + * higher if polymorphed into something that's even faster */ + /* dog.c */ + DUMMY, /* horsename */ + /* mhitu.c */ + 0U, /* hitmsg_mid */ + NULL, /* hitmsg_prev */ + /* save.c */ + TRUE, /* havestate*/ +}; + +static const struct instance_globals_i g_init_i = { + /* decl.c */ + 0, /* in_doagain */ + FALSE, /* in_mklev */ + FALSE, /* in_steed_dismounting */ + UNDEFINED_PTR, /* invent */ + /* do_wear.c */ + FALSE, /* initial_don */ + /* invent.c */ + NULL, /* invbuf */ + 0U, /* invbufsize */ + FALSE, /* item_action_in_progress */ + 0, /* in_sync_perminvent */ + /* mon.c */ + NULL, /* itermonarr */ + /* restore.c */ + UNDEFINED_PTR, /* id_map */ + /* sp_lev.c */ + FALSE, /* in_mk_themerooms */ + + TRUE, /* havestate*/ +}; + +static const struct instance_globals_j g_init_j = { + /* apply.c */ + 0, /* jumping_is_magic */ + TRUE, /* havestate*/ +}; + +static const struct instance_globals_k g_init_k = { + { 0, 0 }, /* kickedloc */ + /* decl.c */ + UNDEFINED_PTR, /* kickedobj */ + /* read.c */ + UNDEFINED_VALUE, /* known */ + TRUE, /* havestate*/ +}; + +static const struct instance_globals_l g_init_l = { + /* cmd.c */ + UNDEFINED_VALUE, /* last_command_count */ + /* decl.c */ +#if defined(UNIX) || defined(VMS) + 0, /* locknum */ #endif -#if defined(RLECOMP) - | SFI1_RLECOMP +#ifdef MICRO + UNDEFINED_VALUES, /* levels */ +#endif /* MICRO */ + /* files.c */ + UNDEFINED_VALUE, /* lockptr */ + LOCKNAMEINIT, /* lock */ + /* invent.c */ + 51, /* lastinvr */ + /* light.c */ + UNDEFINED_PTR, /* light_base */ + /* mklev.c */ + { UNDEFINED_PTR }, /* luathemes[] */ + /* mon.c */ + 0U, /* last_hider */ + /* nhlan.c */ +#ifdef MAX_LAN_USERNAME + UNDEFINED_VALUES, /* lusername */ + MAX_LAN_USERNAME, /* lusername_size */ +#endif /* MAX_LAN_USERNAME */ + /* nhlua.c */ + UNDEFINED_VALUE, /* luacore */ + DUMMY, /* lua_warnbuf[] */ + 0, /* loglua */ + 0, /* lua_sid */ + /* options.c */ + FALSE, /* loot_reset_justpicked */ + /* save.c */ + (struct obj *) 0, /* looseball */ + (struct obj *) 0, /* loosechain */ + /* sp_lev.c */ + NULL, /* lev_message */ + UNDEFINED_PTR, /* lregions */ + /* trap.c */ + { UNDEFINED_PTR, 0, 0 }, /* launchplace */ + /* windows.c */ + UNDEFINED_PTR, /* last_winchoice */ + /* new */ + DUMMY, /* lua_ver[LUA_VER_BUFSIZ] */ + DUMMY, /* lua_copyright[LUA_COPYRIGHT_BUFSIZ] */ + TRUE, /* havestate*/ +}; + +static const struct instance_globals_m g_init_m = { + /* apply.c */ + 0, /* mkot_trap_warn_count */ + /* botl.c */ + 0, /* mrank_sz */ + /* decl.c */ + 0, /* multi */ + NULL, /* multi_reason */ + /* multi_reason usually points to a string literal (when not Null) + but multireasonbuf[] is available for when it needs to be dynamic */ + DUMMY, /* multireasonbuf[] */ + { 0, 0, STRANGE_OBJECT, FALSE }, /* m_shot */ + FALSE, /* mrg_to_wielded */ + UNDEFINED_PTR, /* menu_colorings */ + UNDEFINED_PTR, /* migrating_objs */ + /* dog.c */ + UNDEFINED_PTR, /* mydogs */ + UNDEFINED_PTR, /* migrating_mons */ + /* dokick.c */ + UNDEFINED_PTR, /* maploc */ + /* mhitm.c */ + UNDEFINED_PTR, /* mswallower */ + /* mhitu.c */ + UNDEFINED_VALUE, /* mhitu_dieroll */ + /* mklev.c */ + FALSE, /* made_branch */ + /* mkmap.c */ + UNDEFINED_VALUE, /* min_rx */ + UNDEFINED_VALUE, /* max_rx */ + UNDEFINED_VALUE, /* min_ry */ + UNDEFINED_VALUE, /* max_ry */ + /* mkobj.c */ + FALSE, /* mkcorpstat_norevive */ + /* mthrowu.c */ + UNDEFINED_VALUE, /* mesg_given */ + UNDEFINED_PTR, /* mtarget */ + UNDEFINED_PTR, /* marcher */ + /* muse.c */ + FALSE, /* m_using */ + UNDEFINED_VALUES, /* m */ + /* options.c */ + UNDEFINED_VALUES, /* mapped_menu_cmds */ + UNDEFINED_VALUES, /* mapped_menu_op */ + /* region.c */ + 0, /* max_regions */ + /* trap.c */ + FALSE, /* mentioned_water */ + TRUE, /* havestate*/ +}; + +static const struct instance_globals_n g_init_n = { + /* botl.c */ + 0, /* now_or_before_idx */ + /* decl.c */ + NULL, /* nomovemsg */ + 0, /* nsubroom */ + /* dokick.c */ + UNDEFINED_VALUES, /* nowhere */ + /* files.c */ + 0, /* nesting */ + 0, /* no_sound_notified */ + /* mhitm.c */ + 0L, /* noisetime */ + /* mkmap.c */ + UNDEFINED_PTR, /* new_locations */ + UNDEFINED_VALUE, /* n_loc_filled */ + /* options.c */ + 0, /* n_menu_mapped */ + /* potion.c */ + FALSE, /* notonhead */ + /* questpgr.c */ + UNDEFINED_VALUES, /* nambuf */ + /* restore.c */ + 0, /* n_ids_mapped */ + /* sp_lev.c */ + 0, /* num_lregions */ + /* u_init.c */ + STRANGE_OBJECT, /* nocreate */ + STRANGE_OBJECT, /* nocreate2 */ + STRANGE_OBJECT, /* nocreate3 */ + STRANGE_OBJECT, /* nocreate4 */ + TRUE, /* havestate*/ +}; + +static const struct instance_globals_o g_init_o = { + NULL, /* objs_deleted */ + /* dbridge.c */ + { { 0 } }, /* occupants */ + /* decl.c */ + UNDEFINED_PTR, /* occupation */ + 0, /* occtime */ + UNDEFINED_VALUE, /* otg_temp */ + NULL, /* otg_otmp */ + NULL, /* occtxt */ + /* symbols.c */ + DUMMY, /* ov_primary_syms */ + DUMMY, /* ov_rogue_syms */ + /* invent.c */ + UNDEFINED_VALUES, /* only (coord) */ + /* o_init.c */ + DUMMY, /* oclass_prob_totals */ + /* options.c */ + phase_not_set, /* opt_phase */ + FALSE, /* opt_initial */ + FALSE, /* opt_from_file */ + FALSE, /* opt_need_redraw */ + FALSE, /* opt_need_glyph_reset */ + FALSE, /* opt_need_promptstyle */ + FALSE, /* opt_reset_customcolors */ + FALSE, /* opt_reset_customsymbols */ + FALSE, /* opt_update_basic_palette */ + FALSE, /* opt_symset_changed */ + /* pickup.c */ + 0, /* oldcap */ + /* restore.c */ + UNDEFINED_PTR, /* oldfruit */ + /* rumors.c */ + 0, /* oracle_flag */ + /* uhitm.c */ + FALSE, /* override_confirmation */ + /* zap.c */ + FALSE, /* obj_zapped */ + TRUE, /* havestate*/ +}; + +static const struct instance_globals_p g_init_p = { + /* apply.c */ + -1, /* polearm_range_min */ + -1, /* polearm_range_max */ + /* decl.c */ + 0, /* plnamelen */ + '\0', /* pl_race */ + UNDEFINED_PTR, /* plinemsg_types */ + /* dog.c */ + 0, /* petname_used */ + UNDEFINED_VALUE, /* preferred_pet */ + /* symbols.c */ + DUMMY, /* primary_syms */ + /* invent.c */ + 0, /* perm_invent_toggling_direction */ + /* pickup.c */ + FALSE, /* picked_filter */ + 0, /* pickup_encumbrance */ + /* pline.c */ + 0U, /* pline_flags */ + UNDEFINED_VALUES, /* prevmsg */ + /* potion.c */ + UNDEFINED_VALUE, /* potion_nothing */ + UNDEFINED_VALUE, /* potion_unkn */ + /* pray.c */ + UNDEFINED_VALUE, /* p_aligntyp */ + UNDEFINED_VALUE, /* p_trouble */ + UNDEFINED_VALUE, /* p_type */ + /* weapon.c */ + UNDEFINED_PTR, /* propellor */ + /* zap.c */ + UNDEFINED_VALUE, /* poly_zap */ + TRUE, /* havestate*/ +}; + +static const struct instance_globals_q g_init_q = { + TRUE, /* havestate*/ +}; + +static const struct instance_globals_r g_init_r = { + /* symbols.c */ + DUMMY, /* rogue_syms */ + /* extralev.c */ + { { UNDEFINED_VALUES } }, /* r */ + /* mkmaze.c */ + FALSE, /* ransacked */ + /* region.c */ + UNDEFINED_PTR, /* regions */ + /* rip.c */ + UNDEFINED_PTR, /* rip */ + /* role.c */ + UNDEFINED_VALUES, /* role_pa */ + UNDEFINED_VALUE, /* role_post_attrib */ + { { 0 }, 0 }, /* rfilter */ + /* shk.c */ + UNDEFINED_VALUES, /* repo */ + TRUE, /* havestate*/ +}; + +static const struct instance_globals_s g_init_s = { + /* artifact.c */ + 0, /* spec_dbon_applies */ + /* decl.c */ + UNDEFINED_PTR, /* stairs */ + DUMMY, /* smeq */ + FALSE, /* stoned */ + UNDEFINED_PTR, /* subrooms */ + /* do.c */ + { 0, 0 }, /* save_dlevel */ + /* symbols.c */ + { DUMMY }, /* symset */ + { { { 0 } }, { { 0 } } }, /* symset_customizations */ + DUMMY, /* showsyms */ + /* files.c */ + 0, /* symset_count */ + 0, /* symset_which_set */ + DUMMY, /* SAVEF */ +#ifdef MICRO + DUMMY, /* SAVEP */ #endif - , -#ifdef NHSTDC - 0x00000000UL, 0x00000000UL -#else - 0x00000000L, 0x00000000L + /* invent.c */ + 0, /* sortloogmode */ + /* mhitm.c */ + FALSE, /* skipdrin */ + /* mon.c */ + FALSE, /* somebody_can_move */ + /* options.c */ + (struct symsetentry *) 0, /* symset_list */ + FALSE, /* save_menucolors */ + (struct menucoloring *) 0, /* save_colorings */ + FALSE, /* simple_options_help */ + /* pickup.c */ + FALSE, /* sellobj_first */ + FALSE, /* shop_filter */ + /* pline.c */ +#ifdef DUMPLOG_CORE + 0U, /* saved_pline_index */ + { NULL }, /* saved_plines */ #endif + /* polyself.c */ + 0, /* sex_change_ok */ + /* shk.c */ + 'a', /* sell_response */ + SELL_NORMAL, /* sell_how */ + /* spells.c */ + 0, /* spl_sortmode */ + UNDEFINED_PTR, /* spl_orderindx */ + /* steal.c */ + 0U, /* stealoid */ + 0U, /* stealmid */ + /* vision.c */ + 0, /* seethru */ + TRUE, /* havestate*/ }; -struct plinemsg_type *plinemsg_types = (struct plinemsg_type *) 0; +static const struct instance_globals_t g_init_t = { + /* apply.c */ + UNDEFINED_VALUES, /* trapinfo */ + /* decl.c */ + 0, /* tbx */ + 0, /* tby */ + UNDEFINED_VALUES, /* toplines */ + UNDEFINED_PTR, /* thrownobj */ + DUMMY, /* tc_gbl_data */ + /* hack.c */ + UNDEFINED_VALUES, /* tmp_anything */ + UNDEFINED_PTR, /* travelmap */ + /* invent.c */ + 0, /* this_type */ + NULL, /* this_title */ + /* muse.c */ + UNDEFINED_VALUE, /* trapx */ + UNDEFINED_VALUE, /* trapy */ + /* rumors.c */ + 0L, /* true_rumor_size */ + 0UL, /* true_rumor_start*/ + 0L, /* true_rumor_end */ + /* sp_lev.c */ + FALSE, /* themeroom_failed */ + /* timeout.c */ + UNDEFINED_PTR, /* timer_base */ + /* topten.c */ + WIN_ERR, /* toptenwin */ + /* uhitm.c */ + 0, /* twohits */ + /**/ + TRUE, /* havestate*/ +}; -#ifdef PANICTRACE -const char *ARGV0; +static const struct instance_globals_u g_init_u = { + /* botl.c */ + FALSE, /* update_all */ + /* decl.c */ + FALSE, /* unweapon */ + /* role.c */ + UNDEFINED_ROLE, /* urole */ + UNDEFINED_RACE, /* urace */ + /* save.c */ + { 0, 0 }, /* uz_save */ + TRUE, /* havestate*/ +}; + +static const struct instance_globals_v g_init_v = { + /* botl.c */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* valset */ + /* end.c */ + { UNDEFINED_VALUES }, /* valuables */ + /* mhitm.c */ + FALSE, /* vis */ + /* mklev.c */ + UNDEFINED_VALUE, /* vault_x */ + UNDEFINED_VALUE, /* vault_y */ + /* mon.c */ + FALSE, /* vamp_rise_msg */ + /* pickup.c */ + 0L, /* val_for_n_or_more */ + UNDEFINED_VALUES, /* valid_menu_classes */ + /* vision.c */ + UNDEFINED_PTR, /* viz_array */ + UNDEFINED_PTR, /* viz_rmin */ + UNDEFINED_PTR, /* viz_rmax */ + FALSE, /* vision_full_recalc */ + UNDEFINED_VALUES, /* voice */ + TRUE, /* havestate*/ +}; + +static const struct instance_globals_w g_init_w = { + /* decl.c */ + 0, /* warn_obj_cnt */ + 0L, /* wailmsg */ + /* do_wear.c */ + 0U, /* wasinwater */ + /* symbols.c */ + DUMMY, /* warnsyms */ + /* files.c */ + UNDEFINED_VALUES, /* wizkit */ + /* hack.c */ + UNDEFINED_VALUE, /* wc */ + /* mkmaze.c */ + UNDEFINED_PTR, /* wportal */ + /* new */ + { wdmode_traditional, NO_COLOR }, /* wsettings */ + 0L, /* were.c, allmain.c */ + TRUE, /* havestate*/ +}; + +static const struct instance_globals_x g_init_x = { + /* decl.c */ + (COLNO - 1) & ~1, /* x_maze_max */ + /* lock.c */ + UNDEFINED_VALUES, /* xlock */ + /* objnam.c */ + NULL, /* xnamep */ + /* sp_lev.c */ + UNDEFINED_VALUE, /* xstart */ + UNDEFINED_VALUE, /* xsize */ + TRUE, /* havestate*/ +}; + +static const struct instance_globals_y g_init_y = { + /* decl.c */ + (ROWNO - 1) & ~1, /* y_maze_max */ + DUMMY, /* youmonst */ + /* pline.c */ + NULL, /* you_buf */ + 0, /* you_buf_siz */ + /* sp_lev.c */ + UNDEFINED_VALUE, /* ystart */ + UNDEFINED_VALUE, /* ysize */ + TRUE, /* havestate*/ +}; + +static const struct instance_globals_z g_init_z = { + /* mon.c */ + FALSE, /* zombify */ + /* muse.c */ + FALSE, /* zap_oseen */ + TRUE, /* havestate*/ +}; + +static const struct instance_globals_saved_b init_svb = { + /* dungeon.c */ + UNDEFINED_PTR, /* branches */ + /* mkmaze.c */ + UNDEFINED_PTR, /* bbubbles */ + DUMMY /* bases */ +}; + +static const struct instance_globals_saved_c init_svc = { + /* decl.c */ + DUMMY, /* context */ +}; + +static const struct instance_globals_saved_d init_svd = { + /* dungeon.c */ + { { {0},{0},{0},{0}, 0, {0}, 0, 0, 0, 0, 0 } }, /* dungeons */ + { {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, + {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, + 0, 0, 0, 0, 0, + {0}, {0}, {0}, + {0}, {0}, {0} }, /* dungeon_topology */ + /* decl.c */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* dndest */ + NULL, /* doors */ + 0, /* doors_alloc */ + /* o_init.c */ + DUMMY, /* disco */ +}; + +static const struct instance_globals_saved_e init_sve = { + /* decl.c */ + NULL /* exclusion_zones */ +}; + +static const struct instance_globals_saved_h init_svh = { + /* decl.c */ + 0 /* hackpid */ +}; + +static const struct instance_globals_saved_i init_svi = { + /* decl.c */ + { 0, 0 } /* inv_pos */ +}; + +static const struct instance_globals_saved_k init_svk = { + /* decl.c */ + DUMMY /* killer */ +}; + +static const struct instance_globals_saved_l init_svl = { + /* decl.c */ + { { 0 } }, /* lastseentyp */ + { { { UNDEFINED_VALUES } }, /* level.locations */ + { { UNDEFINED_PTR } }, /* level.objects */ + { { UNDEFINED_PTR } }, /* level.monsters */ + NULL, NULL, NULL, NULL, NULL, {0} }, /* level */ + { UNDEFINED_VALUES } /* level_info */ +}; + +static const struct instance_globals_saved_m init_svm = { + /* dungeon.c */ + UNDEFINED_PTR, /* mapseenchn */ + /* decl.c */ + 0L, /* moves; misnamed turn counter */ + { UNDEFINED_VALUES } /* mvitals */ +}; + +static const struct instance_globals_saved_n init_svn = { + /* dungeon.c */ + 0, /* n_dgns */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0 + }, /* nhuuid */ + /* mkroom.c */ + 0, /* nroom */ + /* region.c */ + 0 /* n_regions */ +}; + +static const struct instance_globals_saved_o init_svo = { + /* rumors.c */ + 0U, /* oracle_cnt */ + UNDEFINED_PTR, /* oracle_loc */ + + /* other */ + 0L /* omoves */ +}; + +static const struct instance_globals_saved_p init_svp = { + /* decl.c */ + DUMMY, /* plname */ + DUMMY, /* pl_character */ + DUMMY, /* pl_fruit */ +}; + +static const struct instance_globals_saved_q init_svq = { + /* quest.c */ + DUMMY /* quest_status */ +}; + +static const struct instance_globals_saved_r init_svr = { + /* mkroom.c */ + { DUMMY }, /* rooms */ +}; + +static const struct instance_globals_saved_s init_svs = { + /* decl.c */ + { DUMMY }, /* spl_book */ + UNDEFINED_PTR /* sp_levchn */ +}; + +static const struct instance_globals_saved_t init_svt = { + /* decl.c */ + DUMMY, /* tune */ + /* timeout.c */ + 1UL, /* timer_id */ +}; + +static const struct instance_globals_saved_u init_svu = { + /* decl.c */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* updest */ +}; + +static const struct instance_globals_saved_x init_svx = { + /* mkmaze.c */ + UNDEFINED_VALUE, /* xmin */ + UNDEFINED_VALUE /* xmax */ +}; + +static const struct instance_globals_saved_w init_svw = { + 0, /* wreserve */ + 100, /* wtreserved, not used currently */ +}; + +static const struct instance_globals_saved_y init_svy = { + /* mkmaze.c */ + UNDEFINED_VALUE, /* ymin */ + UNDEFINED_VALUE /* ymax */ +}; + +static const struct sinfo init_program_state = { 0 }; + +#if 0 +struct instance_globals g; +#endif /* 0 */ + +struct instance_globals_a ga; +struct instance_globals_b gb; +struct instance_globals_c gc; +struct instance_globals_d gd; +struct instance_globals_e ge; +struct instance_globals_f gf; +struct instance_globals_g gg; +struct instance_globals_h gh; +struct instance_globals_i gi; +struct instance_globals_j gj; +struct instance_globals_k gk; +struct instance_globals_l gl; +struct instance_globals_m gm; +struct instance_globals_n gn; +struct instance_globals_o go; +struct instance_globals_p gp; +struct instance_globals_q gq; +struct instance_globals_r gr; +struct instance_globals_s gs; +struct instance_globals_t gt; +struct instance_globals_u gu; +struct instance_globals_v gv; +struct instance_globals_w gw; +struct instance_globals_x gx; +struct instance_globals_y gy; +struct instance_globals_z gz; +struct instance_globals_saved_b svb; +struct instance_globals_saved_c svc; +struct instance_globals_saved_d svd; +struct instance_globals_saved_e sve; +struct instance_globals_saved_h svh; +struct instance_globals_saved_i svi; +struct instance_globals_saved_k svk; +struct instance_globals_saved_l svl; +struct instance_globals_saved_m svm; +struct instance_globals_saved_n svn; +struct instance_globals_saved_o svo; +struct instance_globals_saved_p svp; +struct instance_globals_saved_q svq; +struct instance_globals_saved_r svr; +struct instance_globals_saved_s svs; +struct instance_globals_saved_t svt; +struct instance_globals_saved_u svu; +struct instance_globals_saved_w svw; +struct instance_globals_saved_x svx; +struct instance_globals_saved_y svy; +struct sinfo program_state; + +const struct const_globals cg = { + DUMMY, /* zeroobj */ + DUMMY, /* zeromonst */ + DUMMY, /* zeroany */ + DUMMY, /* zeroNhRect */ +}; + +#define ZERO(x) memset(&x, 0, sizeof(x)) + +#define MAGICCHECK(xx) \ + do { \ + if ((xx).havestate != TRUE) { \ + raw_printf( \ + "decl_globals_init: %s.havestate not True.", #xx); \ + exit(1); \ + } \ + } while(0); + +void +program_state_init(void) +{ + program_state = init_program_state; +} + +void +decl_globals_init(void) +{ +#if 0 + g = g_init; +#endif + ga = g_init_a; + gb = g_init_b; + gc = g_init_c; + gd = g_init_d; + ge = g_init_e; + gf = g_init_f; + gg = g_init_g; + gh = g_init_h; + gi = g_init_i; + gj = g_init_j; + gk = g_init_k; + gl = g_init_l; + gm = g_init_m; + gn = g_init_n; + go = g_init_o; + gp = g_init_p; + gq = g_init_q; + gr = g_init_r; + gs = g_init_s; + gt = g_init_t; + gu = g_init_u; + gv = g_init_v; + gw = g_init_w; + gx = g_init_x; + gy = g_init_y; + gz = g_init_z; + svb = init_svb; + svc = init_svc; + svd = init_svd; + sve = init_sve; + svh = init_svh; + svi = init_svi; + svk = init_svk; + svl = init_svl; + svm = init_svm; + svn = init_svn; + svo = init_svo; + svp = init_svp; + svq = init_svq; + svr = init_svr; + svs = init_svs; + svt = init_svt; + svu = init_svu; + svw = init_svw; + svx = init_svx; + svy = init_svy; + + gv.valuables[0].list = gg.gems; + gv.valuables[0].size = SIZE(gg.gems); + gv.valuables[1].list = ga.amulets; + gv.valuables[1].size = SIZE(ga.amulets); + gv.valuables[2].list = NULL; + gv.valuables[2].size = 0; + +#if 0 + MAGICCHECK(g_init); #endif + MAGICCHECK(g_init_a); + MAGICCHECK(g_init_b); + MAGICCHECK(g_init_c); + MAGICCHECK(g_init_d); + MAGICCHECK(g_init_e); + MAGICCHECK(g_init_f); + MAGICCHECK(g_init_g); + MAGICCHECK(g_init_h); + MAGICCHECK(g_init_i); + MAGICCHECK(g_init_j); + MAGICCHECK(g_init_k); + MAGICCHECK(g_init_l); + MAGICCHECK(g_init_m); + MAGICCHECK(g_init_n); + MAGICCHECK(g_init_o); + MAGICCHECK(g_init_p); + MAGICCHECK(g_init_q); + MAGICCHECK(g_init_r); + MAGICCHECK(g_init_s); + MAGICCHECK(g_init_t); + MAGICCHECK(g_init_u); + MAGICCHECK(g_init_v); + MAGICCHECK(g_init_w); + MAGICCHECK(g_init_x); + MAGICCHECK(g_init_y); + MAGICCHECK(g_init_z); + + gs.subrooms = &svr.rooms[MAXNROFROOMS + 1]; + + ZERO(flags); + ZERO(iflags); + ZERO(a11y); + ZERO(disp); + ZERO(u); + ZERO(ubirthday); + ZERO(urealtime); + + uwep = uarm = uswapwep = uquiver = uarmu = uskin = uarmc = NULL; + uarmh = uarms = uarmg = uarmf = uamul = uright = uleft = NULL; + ublindf = uchain = uball = NULL; + + WIN_MESSAGE = WIN_STATUS = WIN_MAP = WIN_INVEN = WIN_ERR; + + gu.urole = urole_init_data; + gu.urace = urace_init_data; +} -/* support for lint.h */ -unsigned nhUse_dummy = 0; +/* fields in 'hands_obj' don't matter, just its distinct address */ +struct obj hands_obj = DUMMY; -/* dummy routine used to force linkage */ +/* gcc 12.2's static analyzer thinks that some fields of svc.context.victual + are uninitialized when compiling 'bite(eat.c)' but that's impossible; + it is defined at global scope so guaranteed to be given implicit + initialization for fields that aren't explicitly initialized (all of + 'context'); having bite() pass &svc.context.victual to this no-op + eliminates the analyzer's very verbose complaint */ void -decl_init() +sa_victual( + volatile struct victual_info *context_victual UNUSED) { return; } diff --git a/src/detect.c b/src/detect.c index dfac26a76..11c370a05 100644 --- a/src/detect.c +++ b/src/detect.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 detect.c $NHDT-Date: 1575245054 2019/12/02 00:04:14 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.100 $ */ +/* NetHack 5.0 detect.c $NHDT-Date: 1763708572 2025/11/20 23:02:52 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.191 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2018. */ /* NetHack may be freely redistributed. See license for details. */ @@ -11,32 +11,68 @@ #include "hack.h" #include "artifact.h" -extern boolean known; /* from read.c */ - -STATIC_DCL boolean NDECL(unconstrain_map); -STATIC_DCL void NDECL(reconstrain_map); -STATIC_DCL void FDECL(browse_map, (int, const char *)); -STATIC_DCL void FDECL(map_monst, (struct monst *, BOOLEAN_P)); -STATIC_DCL void FDECL(do_dknown_of, (struct obj *)); -STATIC_DCL boolean FDECL(check_map_spot, (int, int, CHAR_P, unsigned)); -STATIC_DCL boolean FDECL(clear_stale_map, (CHAR_P, unsigned)); -STATIC_DCL void FDECL(sense_trap, (struct trap *, XCHAR_P, XCHAR_P, int)); -STATIC_DCL int FDECL(detect_obj_traps, (struct obj *, BOOLEAN_P, int)); -STATIC_DCL void FDECL(show_map_spot, (int, int)); -STATIC_PTR void FDECL(findone, (int, int, genericptr_t)); -STATIC_PTR void FDECL(openone, (int, int, genericptr_t)); -STATIC_DCL int FDECL(mfind0, (struct monst *, BOOLEAN_P)); -STATIC_DCL int FDECL(reveal_terrain_getglyph, (int, int, int, - unsigned, int, int)); +#ifndef FOUND_FLASH_COUNT +/* for screen alert shown to player when secret door detection or ^E + finds stuff; to use tmp_at() instead of flash_glyph_at(), define as 0; + extra code for tmp_at() will be included and the flash_glyph_at() + calls will execute but won't do anything */ +#define FOUND_FLASH_COUNT 6 +#endif + +struct found_things; + +staticfn boolean unconstrain_map(void); +staticfn void reconstrain_map(void); +staticfn void map_redisplay(void); +staticfn void browse_map(unsigned, const char *); +staticfn void map_monst(struct monst *, boolean); +staticfn void observe_recursively(struct obj *); +staticfn boolean check_map_spot(coordxy, coordxy, char, unsigned); +staticfn boolean clear_stale_map(char, unsigned); +staticfn void sense_trap(struct trap *, coordxy, coordxy, int); +staticfn int detect_obj_traps(struct obj *, boolean, int, + struct found_things *) NO_NNARGS; +staticfn void display_trap_map(int); +staticfn int furniture_detect(void); +staticfn void foundone(coordxy, coordxy, int); +staticfn void findone(coordxy, coordxy, genericptr_t); +staticfn void openone(coordxy, coordxy, genericptr_t); +staticfn int mfind0(struct monst *, boolean); +staticfn boolean skip_premap_detect(coordxy, coordxy); +staticfn int reveal_terrain_getglyph(coordxy, coordxy, unsigned, int, + unsigned); + +/* dummytrap: used when detecting traps finds a door or chest trap; the + couple of fields that matter are always re-initialized during use so + this does not need to be part of 'struct instance_globals g'; fields + that aren't used are compile-/link-/load-time initialized to 0 */ +static struct trap dummytrap; + +/* data for enhanced feedback from findone() */ +struct found_things { + coord ft_cc; /* for passing extra info to detect_obj_traps() */ + uchar num_sdoors; + uchar num_scorrs; + uchar num_traps; + uchar num_mons; + uchar num_invis; + uchar num_cleared_invis; + uchar num_kept_invis; +}; + +/* wildcard class for clear_stale_map - this used to be used as a getobj() + input but it's no longer used for that function */ +#define ALL_CLASSES (MAXOCLASSES + 1) /* bring hero out from underwater or underground or being engulfed; return True iff any change occurred */ -STATIC_OVL boolean -unconstrain_map() +staticfn boolean +unconstrain_map(void) { boolean res = u.uinwater || u.uburied || u.uswallow; - /* bring Underwater, buried, or swallowed hero to normal map */ + /* bring Underwater, buried, or swallowed hero to normal map; + bypass set_uinwater() */ iflags.save_uinwater = u.uinwater, u.uinwater = 0; iflags.save_uburied = u.uburied, u.uburied = 0; iflags.save_uswallow = u.uswallow, u.uswallow = 0; @@ -45,19 +81,29 @@ unconstrain_map() } /* put hero back underwater or underground or engulfed */ -STATIC_OVL void -reconstrain_map() +staticfn void +reconstrain_map(void) { + /* if was in water and taken out, put back; bypass set_uinwater() */ u.uinwater = iflags.save_uinwater, iflags.save_uinwater = 0; u.uburied = iflags.save_uburied, iflags.save_uburied = 0; u.uswallow = iflags.save_uswallow, iflags.save_uswallow = 0; } +staticfn void +map_redisplay(void) +{ + reconstrain_map(); + docrt(); /* redraw the screen to remove unseen traps from the map */ + if (Underwater) + under_water(2); + if (u.uburied) + under_ground(2); +} + /* use getpos()'s 'autodescribe' to view whatever is currently shown on map */ -STATIC_DCL void -browse_map(ter_typ, ter_explain) -int ter_typ; -const char *ter_explain; +staticfn void +browse_map(unsigned ter_typ, const char *ter_explain) { coord dummy_pos; /* don't care whether player actually picks a spot */ boolean save_autodescribe; @@ -66,24 +112,22 @@ const char *ter_explain; save_autodescribe = iflags.autodescribe; iflags.autodescribe = TRUE; iflags.terrainmode = ter_typ; - getpos(&dummy_pos, FALSE, ter_explain); + (void) getpos(&dummy_pos, FALSE, ter_explain); iflags.terrainmode = 0; iflags.autodescribe = save_autodescribe; } /* extracted from monster_detection() so can be shared by do_vicinity_map() */ -STATIC_DCL void -map_monst(mtmp, showtail) -struct monst *mtmp; -boolean showtail; +staticfn void +map_monst(struct monst *mtmp, boolean showtail) { - if (def_monsyms[(int) mtmp->data->mlet].sym == ' ') - show_glyph(mtmp->mx, mtmp->my, - detected_mon_to_glyph(mtmp, newsym_rn2)); - else - show_glyph(mtmp->mx, mtmp->my, mtmp->mtame - ? pet_to_glyph(mtmp, newsym_rn2) - : mon_to_glyph(mtmp, newsym_rn2)); + int glyph = (monsym(mtmp->data) == ' ') + ? detected_mon_to_glyph(mtmp, newsym_rn2) + : mtmp->mtame + ? pet_to_glyph(mtmp, newsym_rn2) + : mon_to_glyph(mtmp, newsym_rn2); + + show_glyph(mtmp->mx, mtmp->my, glyph); if (showtail && mtmp->data == &mons[PM_LONG_WORM]) detect_wsegs(mtmp, 0); @@ -92,16 +136,14 @@ boolean showtail; /* this is checking whether a trap symbol represents a trapped chest, not whether a trapped chest is actually present */ boolean -trapped_chest_at(ttyp, x, y) -int ttyp; -int x, y; +trapped_chest_at(int ttyp, coordxy x, coordxy y) { struct monst *mtmp; struct obj *otmp; if (!glyph_is_trap(glyph_at(x, y))) return FALSE; - if (ttyp != BEAR_TRAP || (Hallucination && rn2(20))) + if (ttyp != TRAPPED_CHEST || (Hallucination && rn2(20))) return FALSE; /* @@ -117,8 +159,8 @@ int x, y; if (sobj_at(CHEST, x, y) || sobj_at(LARGE_BOX, x, y)) return TRUE; /* in inventory, we need to find one which is actually trapped */ - if (x == u.ux && y == u.uy) { - for (otmp = invent; otmp; otmp = otmp->nobj) + if (u_at(x, y)) { + for (otmp = gi.invent; otmp; otmp = otmp->nobj) if (Is_box(otmp) && otmp->otrapped) return TRUE; if (u.usteed) { /* steed isn't on map so won't be found by m_at() */ @@ -137,15 +179,13 @@ int x, y; /* this is checking whether a trap symbol represents a trapped door, not whether the door here is actually trapped */ boolean -trapped_door_at(ttyp, x, y) -int ttyp; -int x, y; +trapped_door_at(int ttyp, coordxy x, coordxy y) { struct rm *lev; if (!glyph_is_trap(glyph_at(x, y))) return FALSE; - if (ttyp != BEAR_TRAP || (Hallucination && rn2(20))) + if (ttyp != TRAPPED_DOOR || (Hallucination && rn2(20))) return FALSE; lev = &levl[x][y]; if (!IS_DOOR(lev->typ)) @@ -158,11 +198,9 @@ int x, y; /* recursively search obj for an object in class oclass, return 1st found */ struct obj * -o_in(obj, oclass) -struct obj *obj; -char oclass; +o_in(struct obj *obj, char oclass) { - register struct obj *otmp; + struct obj *otmp; struct obj *temp; if (obj->oclass == oclass) @@ -188,11 +226,9 @@ char oclass; * Return first found. */ struct obj * -o_material(obj, material) -struct obj *obj; -unsigned material; +o_material(struct obj *obj, unsigned material) { - register struct obj *otmp; + struct obj *otmp; struct obj *temp; if (objects[obj->otyp].oc_material == material) @@ -209,41 +245,38 @@ unsigned material; return (struct obj *) 0; } -STATIC_OVL void -do_dknown_of(obj) -struct obj *obj; +staticfn void +observe_recursively(struct obj *obj) { struct obj *otmp; - obj->dknown = 1; + observe_object(obj); if (Has_contents(obj)) { for (otmp = obj->cobj; otmp; otmp = otmp->nobj) - do_dknown_of(otmp); + observe_recursively(otmp); } } /* Check whether the location has an outdated object displayed on it. */ -STATIC_OVL boolean -check_map_spot(x, y, oclass, material) -int x, y; -char oclass; -unsigned material; +staticfn boolean +check_map_spot(coordxy x, coordxy y, char oclass, unsigned material) { int glyph; - register struct obj *otmp; - register struct monst *mtmp; + struct obj *otmp; + struct monst *mtmp; glyph = glyph_at(x, y); if (glyph_is_object(glyph)) { /* there's some object shown here */ if (oclass == ALL_CLASSES) { - return (boolean) !(level.objects[x][y] /* stale if nothing here */ - || ((mtmp = m_at(x, y)) != 0 && mtmp->minvent)); + return !(svl.level.objects[x][y] /* stale if nothing here */ + || ((mtmp = m_at(x, y)) != 0 && mtmp->minvent)); } else { if (material && objects[glyph_to_obj(glyph)].oc_material == material) { /* object shown here is of interest because material matches */ - for (otmp = level.objects[x][y]; otmp; otmp = otmp->nexthere) + for (otmp = svl.level.objects[x][y]; otmp; + otmp = otmp->nexthere) if (o_material(otmp, GOLD)) return FALSE; /* didn't find it; perhaps a monster is carrying it */ @@ -257,7 +290,8 @@ unsigned material; } if (oclass && objects[glyph_to_obj(glyph)].oc_class == oclass) { /* obj shown here is of interest because its class matches */ - for (otmp = level.objects[x][y]; otmp; otmp = otmp->nexthere) + for (otmp = svl.level.objects[x][y]; otmp; + otmp = otmp->nexthere) if (o_in(otmp, oclass)) return FALSE; /* didn't find it; perhaps a monster is carrying it */ @@ -280,12 +314,10 @@ unsigned material; * reappear after the detection has completed. Return true if noticeable * change occurs. */ -STATIC_OVL boolean -clear_stale_map(oclass, material) -char oclass; -unsigned material; +staticfn boolean +clear_stale_map(char oclass, unsigned material) { - register int zx, zy; + coordxy zx, zy; boolean change_made = FALSE; for (zx = 1; zx < COLNO; zx++) @@ -300,27 +332,26 @@ unsigned material; /* look for gold, on the floor or in monsters' possession */ int -gold_detect(sobj) -register struct obj *sobj; +gold_detect(struct obj *sobj) { - register struct obj *obj; - register struct monst *mtmp; + struct obj *obj; + struct monst *mtmp; struct obj gold, *temp = 0; boolean stale, ugold = FALSE, steedgold = FALSE; int ter_typ = TER_DETECT | TER_OBJ; - known = stale = clear_stale_map(COIN_CLASS, - (unsigned) (sobj->blessed ? GOLD : 0)); + gk.known = stale = clear_stale_map(COIN_CLASS, + (unsigned) (sobj->blessed ? GOLD : 0)); /* look for gold carried by monsters (might be in a container) */ for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) - continue; /* probably not needed in this case but... */ + if (DEADMONSTER(mtmp) || (mtmp->isgd && !mtmp->mx)) + continue; if (findgold(mtmp->minvent) || monsndx(mtmp->data) == PM_GOLD_GOLEM) { if (mtmp == u.usteed) { steedgold = TRUE; } else { - known = TRUE; + gk.known = TRUE; goto outgoldmap; /* skip further searching */ } } else { @@ -330,7 +361,7 @@ register struct obj *sobj; if (mtmp == u.usteed) { steedgold = TRUE; } else { - known = TRUE; + gk.known = TRUE; goto outgoldmap; /* skip further searching */ } } @@ -340,39 +371,37 @@ register struct obj *sobj; /* look for gold objects */ for (obj = fobj; obj; obj = obj->nobj) { if (sobj->blessed && o_material(obj, GOLD)) { - known = TRUE; + gk.known = TRUE; if (obj->ox != u.ux || obj->oy != u.uy) goto outgoldmap; } else if (o_in(obj, COIN_CLASS)) { - known = TRUE; + gk.known = TRUE; if (obj->ox != u.ux || obj->oy != u.uy) goto outgoldmap; } } - if (!known) { + if (!gk.known) { /* no gold found on floor or monster's inventory. adjust message if you have gold in your inventory */ - if (sobj) { - char buf[BUFSZ]; + char buf[BUFSZ]; - if (youmonst.data == &mons[PM_GOLD_GOLEM]) - Sprintf(buf, "You feel like a million %s!", currency(2L)); - else if (money_cnt(invent) || hidden_gold()) - Strcpy(buf, - "You feel worried about your future financial situation."); - else if (steedgold) - Sprintf(buf, "You feel interested in %s financial situation.", - s_suffix(x_monnam(u.usteed, - u.usteed->mtame ? ARTICLE_YOUR - : ARTICLE_THE, - (char *) 0, - SUPPRESS_SADDLE, FALSE))); - else - Strcpy(buf, "You feel materially poor."); + if (gy.youmonst.data == &mons[PM_GOLD_GOLEM]) + Sprintf(buf, "You feel like a million %s!", currency(2L)); + else if (money_cnt(gi.invent) || hidden_gold(TRUE)) + Strcpy(buf, + "You feel worried about your future financial situation."); + else if (steedgold) + Sprintf(buf, "You feel interested in %s financial situation.", + s_suffix(x_monnam(u.usteed, + u.usteed->mtame ? ARTICLE_YOUR + : ARTICLE_THE, + (char *) 0, + SUPPRESS_SADDLE, FALSE))); + else + Strcpy(buf, "You feel materially poor."); - strange_feeling(sobj, buf); - } + strange_feeling(sobj, buf); return 1; } /* only under me - no separate display required */ @@ -400,15 +429,15 @@ register struct obj *sobj; } map_object(temp, 1); } - if (temp && temp->ox == u.ux && temp->oy == u.uy) + if (temp && u_at(temp->ox, temp->oy)) ugold = TRUE; } for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) - continue; /* probably overkill here */ + if (DEADMONSTER(mtmp) || (mtmp->isgd && !mtmp->mx)) + continue; temp = 0; if (findgold(mtmp->minvent) || monsndx(mtmp->data) == PM_GOLD_GOLEM) { - gold = zeroobj; /* ensure oextra is cleared too */ + gold = cg.zeroobj; /* ensure oextra is cleared too */ gold.otyp = GOLD_PIECE; gold.quan = (long) rnd(10); /* usually more than 1 */ gold.ox = mtmp->mx; @@ -429,7 +458,7 @@ register struct obj *sobj; break; } } - if (temp && temp->ox == u.ux && temp->oy == u.uy) + if (temp && u_at(temp->ox, temp->oy)) ugold = TRUE; } if (!ugold) { @@ -441,23 +470,17 @@ register struct obj *sobj; browse_map(ter_typ, "gold"); - reconstrain_map(); - docrt(); - if (Underwater) - under_water(2); - if (u.uburied) - under_ground(2); + map_redisplay(); return 0; } /* returns 1 if nothing was detected, 0 if something was detected */ int -food_detect(sobj) -register struct obj *sobj; +food_detect(struct obj *sobj) { - register struct obj *obj; - register struct monst *mtmp; - register int ct = 0, ctu = 0; + struct obj *obj; + struct monst *mtmp; + int ct = 0, ctu = 0; boolean confused = (Confusion || (sobj && sobj->cursed)), stale; char oclass = confused ? POTION_CLASS : FOOD_CLASS; const char *what = confused ? something : "food"; @@ -468,16 +491,17 @@ register struct obj *sobj; for (obj = fobj; obj; obj = obj->nobj) if (o_in(obj, oclass)) { - if (obj->ox == u.ux && obj->oy == u.uy) + if (u_at(obj->ox, obj->oy)) ctu++; else ct++; } for (mtmp = fmon; mtmp && (!ct || !ctu); mtmp = mtmp->nmon) { - /* no DEADMONSTER(mtmp) check needed -- dmons never have inventory */ + if (DEADMONSTER(mtmp) || (mtmp->isgd && !mtmp->mx)) + continue; for (obj = mtmp->minvent; obj; obj = obj->nobj) if (o_in(obj, oclass)) { - if (mtmp->mx == u.ux && mtmp->my == u.uy) + if (u_at(mtmp->mx, mtmp->my)) ctu++; /* steed or an engulfer with inventory */ else ct++; @@ -486,7 +510,7 @@ register struct obj *sobj; } if (!ct && !ctu) { - known = stale && !confused; + gk.known = stale && !confused; if (stale) { docrt(); You("sense a lack of %s nearby.", what); @@ -514,18 +538,18 @@ register struct obj *sobj; } return !stale; } else if (!ct) { - known = TRUE; + gk.known = TRUE; You("%s %s nearby.", sobj ? "smell" : "sense", what); if (sobj && sobj->blessed) { if (!u.uedibility) - pline("Your %s starts to tingle.", body_part(NOSE)); + Your("%s starts to tingle.", body_part(NOSE)); u.uedibility = 1; } } else { struct obj *temp; int ter_typ = TER_DETECT | TER_OBJ; - known = TRUE; + gk.known = TRUE; cls(); (void) unconstrain_map(); for (obj = fobj; obj; obj = obj->nobj) @@ -536,8 +560,9 @@ register struct obj *sobj; } map_object(temp, 1); } - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) - /* no DEADMONSTER() check needed -- dmons never have inventory */ + for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { + if (DEADMONSTER(mtmp) || (mtmp->isgd && !mtmp->mx)) + continue; for (obj = mtmp->minvent; obj; obj = obj->nobj) if ((temp = o_in(obj, oclass)) != 0) { temp->ox = mtmp->mx; @@ -545,6 +570,7 @@ register struct obj *sobj; map_object(temp, 1); break; /* skip rest of this monster's inventory */ } + } if (!ctu) { newsym(u.ux, u.uy); ter_typ |= TER_MON; /* for autodescribe of self */ @@ -562,12 +588,7 @@ register struct obj *sobj; browse_map(ter_typ, "food"); - reconstrain_map(); - docrt(); - if (Underwater) - under_water(2); - if (u.uburied) - under_ground(2); + map_redisplay(); } return 0; } @@ -579,19 +600,18 @@ register struct obj *sobj; * 0 - something was detected */ int -object_detect(detector, class) -struct obj *detector; /* object doing the detecting */ -int class; /* an object class, 0 for all */ +object_detect(struct obj *detector, /* object doing the detecting */ + int class) /* an object class, 0 for all */ { - register int x, y; + coordxy x, y; char stuff[BUFSZ]; int is_cursed = (detector && detector->cursed); int do_dknown = (detector && (detector->oclass == POTION_CLASS || detector->oclass == SPBOOK_CLASS) && detector->blessed); int ct = 0, ctu = 0; - register struct obj *obj, *otmp = (struct obj *) 0; - register struct monst *mtmp; + struct obj *obj, *otmp = (struct obj *) 0; + struct monst *mtmp; int sym, boulder = 0, ter_typ = TER_DETECT | TER_OBJ; if (class < 0 || class >= MAXOCLASSES) { @@ -606,7 +626,7 @@ int class; /* an object class, 0 for all */ * We can exclude checking the buried obj chain for boulders below. */ sym = class ? def_oc_syms[class].sym : 0; - if (sym && showsyms[SYM_BOULDER + SYM_OFF_X] && sym == showsyms[SYM_BOULDER + SYM_OFF_X]) + if (sym && sym == gs.showsyms[SYM_BOULDER + SYM_OFF_X]) boulder = ROCK_CLASS; if (Hallucination || (Confusion && class == SCROLL_CLASS)) @@ -617,43 +637,43 @@ int class; /* an object class, 0 for all */ Strcat(stuff, " and/or large stones"); if (do_dknown) - for (obj = invent; obj; obj = obj->nobj) - do_dknown_of(obj); + for (obj = gi.invent; obj; obj = obj->nobj) + observe_recursively(obj); for (obj = fobj; obj; obj = obj->nobj) { if ((!class && !boulder) || o_in(obj, class) || o_in(obj, boulder)) { - if (obj->ox == u.ux && obj->oy == u.uy) + if (u_at(obj->ox, obj->oy)) ctu++; else ct++; } if (do_dknown) - do_dknown_of(obj); + observe_recursively(obj); } - for (obj = level.buriedobjlist; obj; obj = obj->nobj) { + for (obj = svl.level.buriedobjlist; obj; obj = obj->nobj) { if (!class || o_in(obj, class)) { - if (obj->ox == u.ux && obj->oy == u.uy) + if (u_at(obj->ox, obj->oy)) ctu++; else ct++; } if (do_dknown) - do_dknown_of(obj); + observe_recursively(obj); } if (u.usteed) u.usteed->mx = u.ux, u.usteed->my = u.uy; for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) + if (DEADMONSTER(mtmp) || (mtmp->isgd && !mtmp->mx)) continue; for (obj = mtmp->minvent; obj; obj = obj->nobj) { if ((!class && !boulder) || o_in(obj, class) || o_in(obj, boulder)) ct++; if (do_dknown) - do_dknown_of(obj); + observe_recursively(obj); } if ((is_cursed && M_AP_TYPE(mtmp) == M_AP_OBJECT && (!class || class == objects[mtmp->mappearance].oc_class)) @@ -669,7 +689,6 @@ int class; /* an object class, 0 for all */ strange_feeling(detector, "You feel a lack of something."); return 1; } - You("sense %s nearby.", stuff); return 0; } @@ -680,7 +699,7 @@ int class; /* an object class, 0 for all */ /* * Map all buried objects first. */ - for (obj = level.buriedobjlist; obj; obj = obj->nobj) + for (obj = svl.level.buriedobjlist; obj; obj = obj->nobj) if (!class || (otmp = o_in(obj, class)) != 0) { if (class) { if (otmp != obj) { @@ -701,7 +720,7 @@ int class; /* an object class, 0 for all */ */ for (x = 1; x < COLNO; x++) for (y = 0; y < ROWNO; y++) - for (obj = level.objects[x][y]; obj; obj = obj->nexthere) + for (obj = svl.level.objects[x][y]; obj; obj = obj->nexthere) if ((!class && !boulder) || (otmp = o_in(obj, class)) != 0 || (otmp = o_in(obj, boulder)) != 0) { if (class || boulder) { @@ -717,7 +736,7 @@ int class; /* an object class, 0 for all */ /* Objects in the monster's inventory override floor objects. */ for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) + if (DEADMONSTER(mtmp) || (mtmp->isgd && !mtmp->mx)) continue; for (obj = mtmp->minvent; obj; obj = obj->nobj) if ((!class && !boulder) || (otmp = o_in(obj, class)) != 0 @@ -734,18 +753,19 @@ int class; /* an object class, 0 for all */ && (!class || class == objects[mtmp->mappearance].oc_class)) { struct obj temp; - temp = zeroobj; + temp = cg.zeroobj; temp.otyp = mtmp->mappearance; /* needed for obj_to_glyph() */ temp.quan = 1L; temp.ox = mtmp->mx; temp.oy = mtmp->my; - temp.corpsenm = PM_TENGU; /* if mimicing a corpse */ + /* used for mimicking a corpse or statue */ + temp.corpsenm = has_mcorpsenm(mtmp) ? MCORPSENM(mtmp) : PM_TENGU; map_object(&temp, 1); } else if (findgold(mtmp->minvent) && (!class || class == COIN_CLASS)) { struct obj gold; - gold = zeroobj; /* ensure oextra is cleared too */ + gold = cg.zeroobj; /* ensure oextra is cleared too */ gold.otyp = GOLD_PIECE; gold.quan = (long) rnd(10); /* usually more than 1 */ gold.ox = mtmp->mx; @@ -764,12 +784,7 @@ int class; /* an object class, 0 for all */ else browse_map(ter_typ, "object"); - reconstrain_map(); - docrt(); /* this will correctly reset vision */ - if (Underwater) - under_water(2); - if (u.uburied) - under_ground(2); + map_redisplay(); return 0; } @@ -780,11 +795,10 @@ int class; /* an object class, 0 for all */ * Returns 0 if something was detected. */ int -monster_detect(otmp, mclass) -register struct obj *otmp; /* detecting object (if any) */ -int mclass; /* monster class, 0 for all */ +monster_detect(struct obj *otmp, /* detecting object (if any) */ + int mclass) /* monster class, 0 for all */ { - register struct monst *mtmp; + struct monst *mtmp; int mcnt = 0; /* Note: This used to just check fmon for a non-zero value @@ -792,11 +806,12 @@ int mclass; /* monster class, 0 for all */ * presence of dmons, so we have to find at least one * with positive hit-points to know for sure. */ - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) - if (!DEADMONSTER(mtmp)) { - mcnt++; - break; - } + for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { + if (DEADMONSTER(mtmp) || (mtmp->isgd && !mtmp->mx)) + continue; + ++mcnt; + break; /* no need for full count, just 1 or more vs 0 */ + } if (!mcnt) { if (otmp) @@ -811,15 +826,14 @@ int mclass; /* monster class, 0 for all */ cls(); unconstrained = unconstrain_map(); for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) + if (DEADMONSTER(mtmp) || (mtmp->isgd && !mtmp->mx)) continue; if (!mclass || mtmp->data->mlet == mclass || (mtmp->data == &mons[PM_LONG_WORM] && mclass == S_WORM_TAIL)) map_monst(mtmp, TRUE); - if (otmp && otmp->cursed - && (mtmp->msleeping || !mtmp->mcanmove)) { + if (otmp && otmp->cursed && helpless(mtmp)) { mtmp->msleeping = mtmp->mfrozen = 0; mtmp->mcanmove = 1; woken = TRUE; @@ -842,26 +856,18 @@ int mclass; /* monster class, 0 for all */ EDetect_monsters &= ~I_SPECIAL; } - reconstrain_map(); - docrt(); /* redraw the screen to remove unseen monsters from map */ - if (Underwater) - under_water(2); - if (u.uburied) - under_ground(2); + map_redisplay(); } return 0; } -STATIC_OVL void -sense_trap(trap, x, y, src_cursed) -struct trap *trap; -xchar x, y; -int src_cursed; +staticfn void +sense_trap(struct trap *trap, coordxy x, coordxy y, int src_cursed) { if (Hallucination || src_cursed) { struct obj obj; /* fake object */ - obj = zeroobj; + obj = cg.zeroobj; if (trap) { obj.ox = trap->tx; obj.oy = trap->ty; @@ -877,14 +883,16 @@ int src_cursed; } else if (trap) { map_trap(trap, 1); trap->tseen = 1; - } else { /* trapped door or trapped chest */ - struct trap temp_trap; /* fake trap */ - - (void) memset((genericptr_t) &temp_trap, 0, sizeof temp_trap); - temp_trap.tx = x; - temp_trap.ty = y; - temp_trap.ttyp = BEAR_TRAP; /* some kind of trap */ - map_trap(&temp_trap, 1); + } else { + /* + * OBSOLETE; this was for trapped door or trapped chest + * but those are handled by 'if (trap) {map_trap()}' now + * and this block of code shouldn't be reachable anymore. + */ + dummytrap.tx = x; + dummytrap.ty = y; + dummytrap.ttyp = BEAR_TRAP; /* some kind of trap */ + map_trap(&dummytrap, 1); } } @@ -895,46 +903,117 @@ int src_cursed; /* check a list of objects for chest traps; return 1 if found at , 2 if found at some other spot, 3 if both, 0 otherwise; optionally update the map to show where such traps were found */ -STATIC_OVL int -detect_obj_traps(objlist, show_them, how) -struct obj *objlist; -boolean show_them; -int how; /* 1 for misleading map feedback */ +staticfn int +detect_obj_traps( + struct obj *objlist, + boolean show_them, + int how, /* 1 for misleading map feedback */ + struct found_things *ft) /* being called by findone() when non-Null */ { struct obj *otmp; - xchar x, y; - int result = OTRAP_NONE; + coordxy x, y; + int trapglyph, result = OTRAP_NONE; /* * TODO? Display locations of unarmed land mine and beartrap objects. * If so, should they be displayed as objects or as traps? */ + dummytrap.ttyp = TRAPPED_CHEST; + trapglyph = ft ? trap_to_glyph(&dummytrap) : GLYPH_NOTHING; for (otmp = objlist; otmp; otmp = otmp->nobj) { - if (Is_box(otmp) && otmp->otrapped - && get_obj_location(otmp, &x, &y, BURIED_TOO | CONTAINED_TOO)) { - result |= (x == u.ux && y == u.uy) ? OTRAP_HERE : OTRAP_THERE; - if (show_them) - sense_trap((struct trap *) 0, x, y, how); + x = y = 0; /* lint suppression */ + if ((Is_box(otmp) && otmp->otrapped) || Has_contents(otmp)) { + /* !get_obj_location and !isok should both be impossible here */ + if (!get_obj_location(otmp, &x, &y, BURIED_TOO | CONTAINED_TOO) + || !isok(x, y) + || (ft && (x != ft->ft_cc.x || y != ft->ft_cc.y))) + continue; + } + if (Is_box(otmp) && otmp->otrapped) { + otmp->tknown = 1; + observe_object(otmp); + result |= u_at(x, y) ? OTRAP_HERE : OTRAP_THERE; + if (ft) { + flash_glyph_at(x, y, trapglyph, FOUND_FLASH_COUNT); + } + if (show_them) { + dummytrap.tx = x, dummytrap.ty = y; + sense_trap(&dummytrap, x, y, how); + } + if (ft) { + foundone(x, y, trapglyph); + ft->num_traps++; + } } if (Has_contents(otmp)) - result |= detect_obj_traps(otmp->cobj, show_them, how); + result |= detect_obj_traps(otmp->cobj, show_them, how, ft); } return result; } +staticfn void +display_trap_map(int cursed_src) +{ + struct monst *mon; + struct trap *ttmp; + int door, glyph, ter_typ = TER_DETECT | (cursed_src ? TER_OBJ : TER_TRP); + coord cc; + + cls(); + + (void) unconstrain_map(); + /* show chest traps first, first buried chests then floor chests, so + that subsequent floor trap display will override if both types are + present at the same location */ + (void) detect_obj_traps(svl.level.buriedobjlist, TRUE, cursed_src, NULL); + (void) detect_obj_traps(fobj, TRUE, cursed_src, NULL); + for (mon = fmon; mon; mon = mon->nmon) { + if (DEADMONSTER(mon) || (mon->isgd && !mon->mx)) + continue; + (void) detect_obj_traps(mon->minvent, TRUE, cursed_src, NULL); + } + (void) detect_obj_traps(gi.invent, TRUE, cursed_src, NULL); + + for (ttmp = gf.ftrap; ttmp; ttmp = ttmp->ntrap) + sense_trap(ttmp, 0, 0, cursed_src); + + dummytrap.ttyp = TRAPPED_DOOR; + for (door = 0; door < gd.doorindex; door++) { + cc = svd.doors[door]; + if (levl[cc.x][cc.y].typ == SDOOR) /* can't be trapped; see above */ + continue; + if (levl[cc.x][cc.y].doormask & D_TRAPPED) { + dummytrap.tx = cc.x, dummytrap.ty = cc.y; + sense_trap(&dummytrap, cc.x, cc.y, cursed_src); + } + } + + /* redisplay hero unless sense_trap() revealed something at */ + glyph = glyph_at(u.ux, u.uy); + if (!(glyph_is_trap(glyph) || glyph_is_object(glyph))) { + newsym(u.ux, u.uy); + ter_typ |= TER_MON; /* for autodescribe at */ + } + You_feel("%s.", cursed_src ? "very greedy" : "entrapped"); + + browse_map(ter_typ, cursed_src ? "gold" : "trap of interest"); + + map_redisplay(); +} + /* the detections are pulled out so they can * also be used in the crystal ball routine * returns 1 if nothing was detected * returns 0 if something was detected */ int -trap_detect(sobj) -struct obj *sobj; /* null if crystal ball, *scroll if gold detection scroll */ +trap_detect( + struct obj *sobj) /* Null if crystal ball, scroll if gold detection */ { - register struct trap *ttmp; + struct trap *ttmp; struct monst *mon; - int door, glyph, tr, ter_typ = TER_DETECT | TER_TRP; + int door, tr; int cursed_src = sobj && sobj->cursed; boolean found = FALSE; coord cc; @@ -943,45 +1022,57 @@ struct obj *sobj; /* null if crystal ball, *scroll if gold detection scroll */ u.usteed->mx = u.ux, u.usteed->my = u.uy; /* floor/ceiling traps */ - for (ttmp = ftrap; ttmp; ttmp = ttmp->ntrap) { - if (ttmp->tx != u.ux || ttmp->ty != u.uy) - goto outtrapmap; - else - found = TRUE; + for (ttmp = gf.ftrap; ttmp; ttmp = ttmp->ntrap) { + if (ttmp->tx != u.ux || ttmp->ty != u.uy) { + display_trap_map(cursed_src); + return 0; + } + found = TRUE; } /* chest traps (might be buried or carried) */ - if ((tr = detect_obj_traps(fobj, FALSE, 0)) != OTRAP_NONE) { - if (tr & OTRAP_THERE) - goto outtrapmap; - else - found = TRUE; + if ((tr = detect_obj_traps(fobj, FALSE, 0, NULL)) != OTRAP_NONE) { + if (tr & OTRAP_THERE) { + display_trap_map(cursed_src); + return 0; + } + found = TRUE; } - if ((tr = detect_obj_traps(level.buriedobjlist, FALSE, 0)) != OTRAP_NONE) { - if (tr & OTRAP_THERE) - goto outtrapmap; - else - found = TRUE; + if ((tr = detect_obj_traps(svl.level.buriedobjlist, FALSE, 0, NULL)) + != OTRAP_NONE) { + if (tr & OTRAP_THERE) { + display_trap_map(cursed_src); + return 0; + } + found = TRUE; } for (mon = fmon; mon; mon = mon->nmon) { - if (DEADMONSTER(mon)) + if (DEADMONSTER(mon) || (mon->isgd && !mon->mx)) continue; - if ((tr = detect_obj_traps(mon->minvent, FALSE, 0)) != OTRAP_NONE) { - if (tr & OTRAP_THERE) - goto outtrapmap; - else - found = TRUE; + if ((tr = detect_obj_traps(mon->minvent, FALSE, 0, NULL)) + != OTRAP_NONE) { + if (tr & OTRAP_THERE) { + display_trap_map(cursed_src); + return 0; + } + found = TRUE; } } - if (detect_obj_traps(invent, FALSE, 0) != OTRAP_NONE) + if (detect_obj_traps(gi.invent, FALSE, 0, NULL) != OTRAP_NONE) found = TRUE; /* door traps */ - for (door = 0; door < doorindex; door++) { - cc = doors[door]; + for (door = 0; door < gd.doorindex; door++) { + cc = svd.doors[door]; + /* levl[][].doormask and .wall_info both overlay levl[][].flags; + the bit in doormask for D_TRAPPED is also a bit in wall_info; + secret doors use wall_info so can't be marked as trapped */ + if (levl[cc.x][cc.y].typ == SDOOR) + continue; if (levl[cc.x][cc.y].doormask & D_TRAPPED) { - if (cc.x != u.ux || cc.y != u.uy) - goto outtrapmap; - else - found = TRUE; + if (cc.x != u.ux || cc.y != u.uy) { + display_trap_map(cursed_src); + return 0; + } + found = TRUE; } } if (!found) { @@ -994,94 +1085,114 @@ struct obj *sobj; /* null if crystal ball, *scroll if gold detection scroll */ /* traps exist, but only under me - no separate display required */ Your("%s itch.", makeplural(body_part(TOE))); return 0; +} - outtrapmap: - cls(); +staticfn int +furniture_detect(void) +{ + struct monst *mon; + coordxy x, y; + int glyph, sym, found = 0, revealed = 0; (void) unconstrain_map(); - /* show chest traps first, so that subsequent floor trap display - will override if both types are present at the same location */ - (void) detect_obj_traps(fobj, TRUE, cursed_src); - (void) detect_obj_traps(level.buriedobjlist, TRUE, cursed_src); - for (mon = fmon; mon; mon = mon->nmon) { - if (DEADMONSTER(mon)) - continue; - (void) detect_obj_traps(mon->minvent, TRUE, cursed_src); - } - (void) detect_obj_traps(invent, TRUE, cursed_src); - for (ttmp = ftrap; ttmp; ttmp = ttmp->ntrap) - sense_trap(ttmp, 0, 0, cursed_src); - - for (door = 0; door < doorindex; door++) { - cc = doors[door]; - if (levl[cc.x][cc.y].doormask & D_TRAPPED) - sense_trap((struct trap *) 0, cc.x, cc.y, cursed_src); - } + for (y = 0; y < ROWNO; ++y) + for (x = 1; x < COLNO; ++x) { + glyph = glyph_at(x, y); + sym = glyph_to_cmap(glyph); + if (IS_FURNITURE(levl[x][y].typ)) { + ++found; + magic_map_background(x, y, 1); + } else if (is_cmap_furniture(sym)) { + ++found; + if ((mon = m_at(x, y)) != 0 + && M_AP_TYPE(mon) == M_AP_FURNITURE) + seemimic(mon); + if (!mon || !canspotmon(mon)) + map_invisible(x, y); + } + if (glyph_at(x, y) != glyph) + ++revealed; + } - /* redisplay hero unless sense_trap() revealed something at */ - glyph = glyph_at(u.ux, u.uy); - if (!(glyph_is_trap(glyph) || glyph_is_object(glyph))) { - newsym(u.ux, u.uy); - ter_typ |= TER_MON; /* for autodescribe at */ - } - You_feel("%s.", cursed_src ? "very greedy" : "entrapped"); + if (!found) + There("seems to be nothing of interest on this level."); + else if (!revealed) + /* [what about clipped map with points of interest outside of the + currently shown area?] */ + Your("map already shows all relevant locations."); - browse_map(ter_typ, "trap of interest"); + if (!revealed) + display_nhwindow(WIN_MAP, TRUE); + else /* we need to browse all types because we haven't redrawn the map + * with only points of interest */ + browse_map(TER_DETECT | TER_MAP | TER_TRP | TER_OBJ | TER_MON, + "location"); - reconstrain_map(); - docrt(); /* redraw the screen to remove unseen traps from the map */ - if (Underwater) - under_water(2); - if (u.uburied) - under_ground(2); + map_redisplay(); return 0; } +/* way back in 3.0plN and/or 2.x, you could use a crystal ball to find out + where the wizard was relative to your current location; that was when the + Wizard guarded the Amulet and was located on a random maze level, and you + were expected to level teleport deep into Hell and hunt for him while + working your way up; this isn't of much use anymore */ const char * -level_distance(where) -d_level *where; +level_distance(d_level *where) { - register schar ll = depth(&u.uz) - depth(where); - register boolean indun = (u.uz.dnum == where->dnum); + schar ll = depth(&u.uz) - depth(where); + boolean indun = (u.uz.dnum == where->dnum); + const char *res = ""; /* always replaced by some other non-Null value */ if (ll < 0) { if (ll < (-8 - rn2(3))) if (!indun) - return "far away"; + res = "far away"; else - return "far below"; + res = "far below"; else if (ll < -1) if (!indun) - return "away below you"; + res = "away below you"; else - return "below you"; + res = "below you"; else if (!indun) - return "in the distance"; + res = "in the distance"; else - return "just below"; + res = "just below"; } else if (ll > 0) { if (ll > (8 + rn2(3))) if (!indun) - return "far away"; + res = "far away"; else - return "far above"; + res = "far above"; else if (ll > 1) if (!indun) - return "away above you"; + res = "away above you"; else - return "above you"; + res = "above you"; else if (!indun) - return "in the distance"; + res = "in the distance"; else - return "just above"; - } else if (!indun) - return "in the distance"; - else - return "near you"; + res = "just above"; + } else { /* l1 == 0 */ + if (!indun) + res = "in the distance"; + else + res = "near you"; + } + return res; } -static const struct { + /* + * This could be made a lot more useful. Especially now that + * amnesia no longer causes levels to be forgotten. Perhaps a + * menu, and it ought to include the entrance to Vlad's Tower, + * one of the few things that requires active searching/mapping + * to find. And once the Wizard is in play, he is easy for the + * game to locate but not necessarily for the player. + */ +static const struct crystalballlevels { const char *what; d_level *where; } level_detects[] = { @@ -1092,31 +1203,33 @@ static const struct { }; void -use_crystal_ball(optr) -struct obj **optr; +use_crystal_ball(struct obj **optr) { char ch; int oops; struct obj *obj = *optr; + boolean charged = (obj->spe > 0); if (Blind) { pline("Too bad you can't see %s.", the(xname(obj))); return; } - oops = (rnd(20) > ACURR(A_INT) || obj->cursed); - if (oops && (obj->spe > 0)) { - switch (rnd(obj->oartifact ? 4 : 5)) { + oops = is_quest_artifact(obj) ? 8 : obj->blessed ? 16 : 20; + if (charged && (obj->cursed || rnd(oops) > ACURR(A_INT))) { + long impair = (long) rnd(100 - 3 * ACURR(A_INT)); + + switch (rnd((obj->oartifact || obj->blessed) ? 4 : 5)) { case 1: pline("%s too much to comprehend!", Tobjnam(obj, "are")); break; case 2: pline("%s you!", Tobjnam(obj, "confuse")); - make_confused((HConfusion & TIMEOUT) + (long) rnd(100), FALSE); + make_confused((HConfusion & TIMEOUT) + impair, FALSE); break; case 3: - if (!resists_blnd(&youmonst)) { + if (!resists_blnd(&gy.youmonst)) { pline("%s your vision!", Tobjnam(obj, "damage")); - make_blinded((Blinded & TIMEOUT) + (long) rnd(100), FALSE); + make_blinded(BlindedTimeout + impair, FALSE); if (!Blind) Your1(vision_clears); } else { @@ -1126,8 +1239,8 @@ struct obj **optr; break; case 4: pline("%s your mind!", Tobjnam(obj, "zap")); - (void) make_hallucinated( - (HHallucination & TIMEOUT) + (long) rnd(100), FALSE, 0L); + (void) make_hallucinated((HHallucination & TIMEOUT) + impair, + FALSE, 0L); break; case 5: pline("%s!", Tobjnam(obj, "explode")); @@ -1144,8 +1257,14 @@ struct obj **optr; } if (Hallucination) { - if (!obj->spe) { + nomul(-rnd(charged ? 4 : 2)); + gm.multi_reason = "gazing into a Magic 8-Ball (tm)"; + gn.nomovemsg = ""; + + if (!charged) { pline("All you see is funky %s haze.", hcolor((char *) 0)); + if (obj->spe < 0) + goto implode; /* destroy it when it has been cancelled */ } else { switch (rnd(6)) { case 1: @@ -1177,20 +1296,34 @@ struct obj **optr; /* read a single character */ if (flags.verbose) - You("may look for an object or monster symbol."); - ch = yn_function("What do you look for?", (char *) 0, '\0'); + You("may look for an object, monster, or special map symbol."); + ch = yn_function("What do you look for?", (char *) 0, '\0', TRUE); /* Don't filter out ' ' here; it has a use */ - if ((ch != def_monsyms[S_GHOST].sym) && index(quitchars, ch)) { + if ((ch != def_monsyms[S_GHOST].sym) && strchr(quitchars, ch)) { if (flags.verbose) pline1(Never_mind); return; } + /* Possible extension: + * If ch=='?', ask whether player wants to find scrolls or is asking + * for help in using the crystal ball. + */ + You("peer into %s...", the(xname(obj))); - nomul(-rnd(10)); - multi_reason = "gazing into a crystal ball"; - nomovemsg = ""; - if (obj->spe <= 0) { + nomul(-rnd(charged ? 10 : 2)); + gm.multi_reason = "gazing into a crystal ball"; + gn.nomovemsg = ""; + + if (!charged) { pline_The("vision is unclear."); + + if (obj->spe < 0) { /* destroy ball if used after being cancelled */ + implode: /* no damage to hero but 'multi' has a small negative value */ + pline("%s!", Tobjnam(obj, "implode")); + useup(obj); + *optr = obj = (struct obj *) 0; /* it's gone */ + return; + } } else { int class, i; int ret = 0; @@ -1204,25 +1337,25 @@ struct obj **optr; if (ch == DEF_MIMIC_DEF) ch = DEF_MIMIC; - if ((class = def_char_to_objclass(ch)) != MAXOCLASSES) + /* checking furniture before objects allows '_' to find altars + (along with other furniture) instead of finding iron chains */ + if (def_char_is_furniture(ch) >= 0) { + ret = furniture_detect(); + } else if ((class = def_char_to_objclass(ch)) != MAXOCLASSES) { ret = object_detect((struct obj *) 0, class); - else if ((class = def_char_to_monclass(ch)) != MAXMCLASSES) + } else if ((class = def_char_to_monclass(ch)) != MAXMCLASSES) { ret = monster_detect((struct obj *) 0, class); - else if (showsyms[SYM_BOULDER + SYM_OFF_X] - && (ch == showsyms[SYM_BOULDER + SYM_OFF_X])) + } else if (gs.showsyms[SYM_BOULDER + SYM_OFF_X] + && (ch == gs.showsyms[SYM_BOULDER + SYM_OFF_X])) { ret = object_detect((struct obj *) 0, ROCK_CLASS); - else - switch (ch) { - case '^': - ret = trap_detect((struct obj *) 0); - break; - default: - i = rn2(SIZE(level_detects)); - You_see("%s, %s.", level_detects[i].what, - level_distance(level_detects[i].where)); - ret = 0; - break; - } + } else if (ch == '^') { + ret = trap_detect((struct obj *) 0); + } else { + i = rn2(SIZE(level_detects)); + You_see("%s, %s.", level_detects[i].what, + level_distance(level_detects[i].where)); + ret = 0; + } if (ret) { if (!rn2(100)) /* make them nervous */ @@ -1234,15 +1367,16 @@ struct obj **optr; return; } -STATIC_OVL void -show_map_spot(x, y) -register int x, y; +/* used by magic mapping, clairvoyance, and wand of probing */ +void +show_map_spot(coordxy x, coordxy y, boolean cnf) { struct rm *lev; struct trap *t; + struct engr *ep; int oldglyph; - if (Confusion && rn2(7)) + if (cnf && rn2(7)) return; lev = &levl[x][y]; @@ -1262,7 +1396,7 @@ register int x, y; * opposite to how normal vision behaves. */ oldglyph = glyph_at(x, y); - if (level.flags.hero_memory) { + if (svl.level.flags.hero_memory) { magic_map_background(x, y, 0); newsym(x, y); /* show it, if not blocked */ } else { @@ -1271,42 +1405,50 @@ register int x, y; if (!IS_FURNITURE(lev->typ)) { if ((t = t_at(x, y)) != 0 && t->tseen) { map_trap(t, 1); + } else if ((ep = engr_at(x, y)) != 0 && !cnf) { + map_engraving(ep, 1); } else if (glyph_is_trap(oldglyph) || glyph_is_object(oldglyph)) { show_glyph(x, y, oldglyph); - if (level.flags.hero_memory) + if (svl.level.flags.hero_memory) lev->glyph = oldglyph; } } + /* possibly update #overview */ + if (!cnf && lev->roomno >= ROOMOFFSET) + room_discovered(lev->roomno - ROOMOFFSET); } void -do_mapping() +do_mapping(void) { - register int zx, zy; + int zx, zy; boolean unconstrained; unconstrained = unconstrain_map(); for (zx = 1; zx < COLNO; zx++) for (zy = 0; zy < ROWNO; zy++) - show_map_spot(zx, zy); + show_map_spot(zx, zy, Confusion); - if (!level.flags.hero_memory || unconstrained) { + if (!svl.level.flags.hero_memory || unconstrained) { flush_screen(1); /* flush temp screen */ /* browse_map() instead of display_nhwindow(WIN_MAP, TRUE) */ browse_map(TER_DETECT | TER_MAP | TER_TRP | TER_OBJ, "anything of interest"); - docrt(); + map_redisplay(); /* calls reconstrain_map() and docrt() */ + } else { + /* we only get here when unconstrained is False, so reconstrain_map + will be a no-op; call it anyway */ + reconstrain_map(); } - reconstrain_map(); exercise(A_WIS, TRUE); } /* clairvoyance */ void -do_vicinity_map(sobj) -struct obj *sobj; /* scroll--actually fake spellbook--object */ +do_vicinity_map( + struct obj *sobj) /* scroll--actually fake spellbook--object */ { - register int zx, zy; + int zx, zy; struct monst *mtmp; struct obj *otmp; long save_EDetect_mons; @@ -1316,7 +1458,8 @@ struct obj *sobj; /* scroll--actually fake spellbook--object */ /* fake spellbook 'sobj' implies hero has cast the spell; when book is blessed, casting is skilled or expert level; if already clairvoyant, non-skilled spell acts like skilled */ - extended = (sobj && (sobj->blessed || Clairvoyant)); + extended = (sobj && (sobj->blessed || Clairvoyant)), + random_farsight = !sobj; int newglyph, oldglyph, lo_y = ((u.uy - 5 < 0) ? 0 : u.uy - 5), hi_y = ((u.uy + 6 >= ROWNO) ? ROWNO - 1 : u.uy + 6), @@ -1333,7 +1476,7 @@ struct obj *sobj; /* scroll--actually fake spellbook--object */ * issuing --More-- and then regular vision update, but we want * to avoid that when having a clairvoyant episode every N turns * (from donating to a temple priest or by carrying the Amulet). - * Unlike when casting the spell, it is much too intrustive when + * Unlike when casting the spell, it is much too intrusive when * in the midst of walking around or combatting monsters. * * As of 3.6.2, show terrain, then object, then monster like regular @@ -1346,9 +1489,9 @@ struct obj *sobj; /* scroll--actually fake spellbook--object */ */ /* if hero is engulfed, show engulfer at */ - save_viz_uyux = viz_array[u.uy][u.ux]; + save_viz_uyux = gv.viz_array[u.uy][u.ux]; if (u.uswallow) - viz_array[u.uy][u.ux] |= IN_SIGHT; /* are reversed to [y][x] */ + gv.viz_array[u.uy][u.ux] |= IN_SIGHT; /* are reversed, [y][x] */ save_EDetect_mons = EDetect_monsters; /* for skilled spell, getpos() scanning of the map will display all monsters within range; otherwise, "unseen creature" will be shown */ @@ -1358,14 +1501,14 @@ struct obj *sobj; /* scroll--actually fake spellbook--object */ for (zy = lo_y; zy <= hi_y; zy++) { oldglyph = glyph_at(zx, zy); /* this will remove 'remembered, unseen mon' (and objects) */ - show_map_spot(zx, zy); + show_map_spot(zx, zy, Confusion); /* if there are any objects here, see the top one */ if (OBJ_AT(zx, zy)) { /* not vobj_at(); this is not vision-based access; unlike object detection, we don't notice buried items */ - otmp = level.objects[zx][zy]; + otmp = svl.level.objects[zx][zy]; if (extended) - otmp->dknown = 1; + observe_object(otmp); map_object(otmp, TRUE); newglyph = glyph_at(zx, zy); /* if otmp is underwater, we'll need to redisplay the water */ @@ -1380,7 +1523,7 @@ struct obj *sobj; /* scroll--actually fake spellbook--object */ the map and we're not doing extended/blessed clairvoyance (hence must be swallowed or underwater), show "unseen creature" unless map already displayed a monster here */ - if ((unconstrained || !level.flags.hero_memory) + if ((unconstrained || !svl.level.flags.hero_memory) && !extended && (zx != u.ux || zy != u.uy) && !glyph_is_monster(oldglyph)) map_invisible(zx, zy); @@ -1393,7 +1536,18 @@ struct obj *sobj; /* scroll--actually fake spellbook--object */ } } - if (!level.flags.hero_memory || unconstrained || mdetected || odetected) { + /* when this instance of clairvoyance is random (see allmain()) and + the only reason to browse the map is that previously undetected + monster(s) or object(s) have been revealed, player can prevent + the you-sense-your-surroundings message and browse operation from + happening by setting 'quick_farsight' option; for clairvoyance + spell, that option is ignored because the message and the pause + for map browsing isn't as intrusive in that circumstance */ + if (random_farsight && flags.quick_farsight) + mdetected = odetected = FALSE; + + if (!svl.level.flags.hero_memory || unconstrained + || mdetected || odetected) { flush_screen(1); /* flush temp screen */ /* the getpos() prompt from browse_map() is only shown when flags.verbose is set, but make this unconditional so that @@ -1406,13 +1560,13 @@ struct obj *sobj; /* scroll--actually fake spellbook--object */ } reconstrain_map(); EDetect_monsters = save_EDetect_mons; - viz_array[u.uy][u.ux] = save_viz_uyux; + gv.viz_array[u.uy][u.ux] = save_viz_uyux; /* replace monsters with remembered,unseen monster, then run see_monsters() to update visible ones and warned-of ones */ for (zx = lo_x; zx <= hi_x; zx++) for (zy = lo_y; zy <= hi_y; zy++) { - if (zx == u.ux && zy == u.uy) + if (u_at(zx, zy)) continue; newglyph = glyph_at(zx, zy); if (glyph_is_monster(newglyph) @@ -1430,94 +1584,156 @@ struct obj *sobj; /* scroll--actually fake spellbook--object */ docrt(); } -/* convert a secret door into a normal door */ +/* convert a secret door into a normal door; it might be trapped */ void -cvt_sdoor_to_door(lev) -struct rm *lev; +cvt_sdoor_to_door(struct rm *lev) { int newmask = lev->doormask & ~WM_MASK; - if (Is_rogue_level(&u.uz)) + if (Is_rogue_level(&u.uz)) { /* rogue didn't have doors, only doorways */ newmask = D_NODOOR; - else + } else { /* newly exposed door is closed */ if (!(newmask & D_LOCKED)) - newmask |= D_CLOSED; - + newmask |= D_CLOSED; + } lev->typ = DOOR; lev->doormask = newmask; + lev->arboreal_sdoor = 0; /* clears 'candig' */ } -/* find something at one location; it should find all somethings there - since it is used for magical detection rather than physical searching */ -STATIC_PTR void -findone(zx, zy, num) -int zx, zy; -genericptr_t num; +/* update the map for something which has just been found by wand of secret + door detection or wizard mode ^E; will be called multiple times during a + single operation if multiple things of interest are discovered */ +staticfn void +foundone(coordxy zx, coordxy zy, int glyph) { - register struct trap *ttmp; - register struct monst *mtmp; + if (glyph_is_cmap(glyph) || glyph_is_unexplored(glyph)) + levl[zx][zy].seenv = SVALL; + + { + seenV save_viz = gv.viz_array[zy][zx]; + + if (!Blind) + gv.viz_array[zy][zx] = COULD_SEE | IN_SIGHT; + newsym(zx, zy); + gv.viz_array[zy][zx] = save_viz; + } +#if FOUND_FLASH_COUNT == 0 /* - * This used to use if/else-if/else-if/else/end-if but that only - * found the first hidden thing at the location. Two hidden things - * at the same spot is uncommon, but it's possible for an undetected - * monster to be hiding at the location of an unseen trap. + * This works [for non-monsters at present] but flash_glyph_at() + * seems preferrable because the tmp_at() variation requires that + * the player respond to --More-- at the end, the flash_glyph + * variation doesn't. */ + tmp_at(DISP_CHANGE, glyph); + tmp_at(zx, zy); +#endif +} - if (levl[zx][zy].typ == SDOOR) { - cvt_sdoor_to_door(&levl[zx][zy]); /* .typ = DOOR */ +/* find something at one location; this should find all somethings there + since it is used for magical detection rather than physical searching */ +staticfn void +findone(coordxy zx, coordxy zy, genericptr_t whatfound) +{ + struct rm *lev = &levl[zx][zy]; + struct trap *ttmp = t_at(zx, zy); + struct monst *mtmp = m_at(zx, zy); + struct found_things *found_p = (struct found_things *) whatfound; + + if (mtmp && (DEADMONSTER(mtmp) || (mtmp->isgd && !mtmp->mx))) + mtmp = (struct monst *) NULL; + found_p->ft_cc.x = zx; /* needed by detect_obj_traps() */ + found_p->ft_cc.y = zy; + + if (lev->typ == SDOOR) { + nhsym sym = lev->horizontal ? S_hcdoor : S_vcdoor; + + flash_glyph_at(zx, zy, cmap_to_glyph(sym), FOUND_FLASH_COUNT); + cvt_sdoor_to_door(lev); /* set lev->typ = DOOR */ + recalc_block_point(zx, zy); magic_map_background(zx, zy, 0); - newsym(zx, zy); - (*(int *) num)++; - } else if (levl[zx][zy].typ == SCORR) { - levl[zx][zy].typ = CORR; + foundone(zx, zy, back_to_glyph(zx, zy)); + found_p->num_sdoors++; + } else if (lev->typ == SCORR) { + flash_glyph_at(zx, zy, cmap_to_glyph(S_corr), FOUND_FLASH_COUNT); + lev->typ = CORR; unblock_point(zx, zy); magic_map_background(zx, zy, 0); - newsym(zx, zy); - (*(int *) num)++; + foundone(zx, zy, cmap_to_glyph(S_corr)); + found_p->num_scorrs++; } - if ((ttmp = t_at(zx, zy)) != 0 && !ttmp->tseen + if (ttmp && !ttmp->tseen /* [shouldn't successful 'find' reveal and activate statue traps?] */ && ttmp->ttyp != STATUE_TRAP) { + flash_glyph_at(zx, zy, trap_to_glyph(ttmp), FOUND_FLASH_COUNT); ttmp->tseen = 1; - newsym(zx, zy); - (*(int *) num)++; + sense_trap(ttmp, zx, zy, 0); /* handles Hallucination */ + foundone(zx, zy, trap_to_glyph(ttmp)); + found_p->num_traps++; } - - if ((mtmp = m_at(zx, zy)) != 0 - /* brings hidden monster out of hiding even if already sensed */ - && (!canspotmon(mtmp) || mtmp->mundetected || M_AP_TYPE(mtmp))) { + if (closed_door(zx, zy) && (lev->doormask & D_TRAPPED) != 0) { + dummytrap.ttyp = TRAPPED_DOOR; + dummytrap.tx = zx, dummytrap.ty = zy; + flash_glyph_at(zx, zy, trap_to_glyph(&dummytrap), FOUND_FLASH_COUNT); + dummytrap.tseen = 1; + sense_trap(&dummytrap, zx, zy, 0); /* handles Hallucination */ + foundone(zx, zy, trap_to_glyph(&dummytrap)); + found_p->num_traps++; + } + /* trapped chests */ + (void) detect_obj_traps(svl.level.buriedobjlist, TRUE, 0, found_p); + (void) detect_obj_traps(fobj, TRUE, 0, found_p); + if (mtmp) + (void) detect_obj_traps(mtmp->minvent, TRUE, 0, found_p); + if (u_at(zx, zy)) + (void) detect_obj_traps(gi.invent, TRUE, 0, found_p); + + if (mtmp && (!canspotmon(mtmp) || mtmp->mundetected || M_AP_TYPE(mtmp))) { if (M_AP_TYPE(mtmp)) { + flash_glyph_at(zx, zy, mon_to_glyph(mtmp, rn2_on_display_rng), + FOUND_FLASH_COUNT); seemimic(mtmp); - (*(int *) num)++; + /*foundone(zx, zy, mon_to_glyph(mtmp, rn2_on_display_rng);*/ + found_p->num_mons++; } else if (mtmp->mundetected && (is_hider(mtmp->data) || hides_under(mtmp->data) || mtmp->data->mlet == S_EEL)) { + flash_glyph_at(zx, zy, mon_to_glyph(mtmp, rn2_on_display_rng), + FOUND_FLASH_COUNT); mtmp->mundetected = 0; + /*foundone(zx, zy, mon_to_glyph(mtmp, rn2_on_display_rng);*/ newsym(zx, zy); - (*(int *) num)++; + found_p->num_mons++; + } + if (!glyph_is_invisible(lev->glyph)) { + if (!canspotmon(mtmp)) { + flash_glyph_at(zx, zy, GLYPH_INVISIBLE, FOUND_FLASH_COUNT); + map_invisible(zx, zy); + found_p->num_invis++; + } + } else { + found_p->num_kept_invis++; } - if (!canspotmon(mtmp) && !glyph_is_invisible(levl[zx][zy].glyph)) - map_invisible(zx, zy); } else if (unmap_invisible(zx, zy)) { - (*(int *) num)++; + /* flash the invisible monster glyph because it is already gone */ + flash_glyph_at(zx, zy, GLYPH_INVISIBLE, FOUND_FLASH_COUNT); + found_p->num_cleared_invis++; } } -STATIC_PTR void -openone(zx, zy, num) -int zx, zy; -genericptr_t num; +staticfn void +openone(coordxy zx, coordxy zy, genericptr_t num) { - register struct trap *ttmp; - register struct obj *otmp; + struct trap *ttmp; + struct obj *otmp; int *num_p = (int *) num; if (OBJ_AT(zx, zy)) { - for (otmp = level.objects[zx][zy]; otmp; otmp = otmp->nexthere) { + for (otmp = svl.level.objects[zx][zy]; otmp; otmp = otmp->nexthere) { if (Is_box(otmp) && otmp->olocked) { otmp->olocked = 0; (*num_p)++; @@ -1525,6 +1741,8 @@ genericptr_t num; } /* let it fall to the next cases. could be on trap. */ } + /* note: secret doors can't be trapped; they use levl[][].wall_info; + see rm.h for the troublesome overlay of doormask and wall_info */ if (levl[zx][zy].typ == SDOOR || (levl[zx][zy].typ == DOOR && (levl[zx][zy].doormask & (D_CLOSED | D_LOCKED)))) { @@ -1532,7 +1750,7 @@ genericptr_t num; cvt_sdoor_to_door(&levl[zx][zy]); /* .typ = DOOR */ if (levl[zx][zy].doormask & D_TRAPPED) { if (distu(zx, zy) < 3) - b_trapped("door", 0); + b_trapped("door", NO_PART); else Norep("You %s an explosion!", cansee(zx, zy) ? "see" : (!Deaf ? "hear" @@ -1558,7 +1776,7 @@ genericptr_t num; newsym(zx, zy); (*num_p)++; } - mon = (zx == u.ux && zy == u.uy) ? &youmonst : m_at(zx, zy); + mon = u_at(zx, zy) ? &gy.youmonst : m_at(zx, zy); if (openholdingtrap(mon, &dummy) || openfallingtrap(mon, TRUE, &dummy)) (*num_p)++; @@ -1571,28 +1789,132 @@ genericptr_t num; /* returns number of things found */ int -findit() +findit(void) { - int num = 0; + int num = 0, k; + char buf[BUFSZ]; + struct found_things found; + + /* + * findit() -> do_clear_area(findone) -> findone() -> foundone() + * is used to notify player where various things have been found. + * Changing FOUND_FLASH_COUNT to 0 will switch to tmp_at() to + * highlight all discoveries for the current operation, but requires + * player to respond to --More-- when done. Neither allows browsing + * the map via getpos() autodescribe (until after it has reverted to + * normal display, where found traps might be covered by objects). + */ if (u.uswallow) return 0; - do_clear_area(u.ux, u.uy, BOLT_LIM, findone, (genericptr_t) &num); + +#if FOUND_FLASH_COUNT == 0 /* _COUNT > 0 doesn't need to init tmp_at() */ + tmp_at(DISP_ALL, GLYPH_NOTHING); +#endif + (void) memset((genericptr_t) &found, 0, sizeof found); + do_clear_area(u.ux, u.uy, BOLT_LIM, findone, (genericptr_t) &found); + /* count that controls "reveal" punctuation; 0..4 */ + k = !!found.num_sdoors + !!found.num_scorrs + !!found.num_traps + + !!found.num_mons; + + buf[0] = '\0'; + if (found.num_sdoors) { + if (found.num_sdoors > 1) + Sprintf(eos(buf), "%d secret doors", found.num_sdoors); + else + Strcat(buf, "a secret door"); + num += found.num_sdoors; + } + /* note: non-\0 *buf implies that at least one previous type is present */ + if (found.num_scorrs) { + if (*buf) /* "doors and corrs" or "doors, corrs ..." */ + Strcat(buf, (k == 2) ? " and " : ", "); + if (found.num_scorrs > 1) + Sprintf(eos(buf), "%d secret corridors", found.num_scorrs); + else + Strcat(buf, "a secret corridor"); + num += found.num_scorrs; + } + if (found.num_traps) { + if (*buf) /* "doors, corrs, and traps" or "{doors|corrs} and traps" + * or "..., traps ..." */ + Strcat(buf, (k == 3 && !found.num_mons) ? ", and " + : (k == 2) ? " and " : ", "); + if (found.num_traps > 1) + Sprintf(eos(buf), "%d traps", found.num_traps); + else + Strcat(buf, "a trap"); + num += found.num_traps; + } + +#if FOUND_FLASH_COUNT == 0 + int tmp_num; + tmp_num = num; /* sdoors, scorrs, and traps call tmp_at() */ +#endif + + if (found.num_mons) { + if (*buf) + Strcat(buf, (k > 2) ? ", and " : " and "); + if (found.num_mons > 1) + Sprintf(eos(buf), "%d hidden monsters", found.num_mons); + else + Strcat(buf, "a hidden monster"); + num += found.num_mons; + } + if (*buf) + You("reveal %s!", buf); + + if (found.num_invis) { + if (found.num_invis > 1) + Sprintf(buf, "%d%s unseen monsters", found.num_invis, + found.num_kept_invis ? " other" : ""); + else + Sprintf(buf, "%s unseen monster", + found.num_kept_invis ? "another" : "an"); + You("detect %s!", buf); + num += found.num_invis; + } + + if (found.num_cleared_invis) { + /* at least 1 "remembered, unseen monster" marker has been removed */ + if (!num) + You_feel("%sless paranoid.", + found.num_kept_invis ? "somewhat " : ""); + num += found.num_cleared_invis; + } + /* note: num_kept_invis is not included in the final result */ + + if (!num) + You("don't find anything."); +#if FOUND_FLASH_COUNT == 0 + else if (tmp_num) { + flush_screen(1); + display_nhwindow(WIN_MAP, TRUE); + } + tmp_at(DISP_END, GLYPH_NOTHING); /* note: outside of 'if (tmp_num) { }' */ +#endif + return num; } /* returns number of things found and opened */ int -openit() +openit(void) { int num = 0; if (u.uswallow) { - if (is_animal(u.ustuck->data)) { + if (digests(u.ustuck->data)) { + /* purple worm */ if (Blind) pline("Its mouth opens!"); else pline("%s opens its mouth!", Monnam(u.ustuck)); +#if 0 /* expels() will take care of this */ + } else if (enfolds(u.ustuck->data)) { + /* trapper or lurker above */ + pline("%s unfolds!", Monnam(u.ustuck)); +#endif } expels(u.ustuck, u.ustuck->data, TRUE); return -1; @@ -1604,29 +1926,25 @@ openit() /* callback hack for overriding vision in do_clear_area() */ boolean -detecting(func) -void FDECL((*func), (int, int, genericptr_t)); +detecting(void (*func)(coordxy, coordxy, genericptr_t)) { return (func == findone || func == openone); } void -find_trap(trap) -struct trap *trap; +find_trap(struct trap *trap) { - int tt = what_trap(trap->ttyp, rn2); boolean cleared = FALSE; trap->tseen = 1; exercise(A_WIS, TRUE); feel_newsym(trap->tx, trap->ty); - /* The "Hallucination ||" is to preserve 3.6.1 behaviour, but this - behaviour might need a rework in the hallucination case + /* The "Hallucination ||" is to preserve 3.6.1 behavior, but this + behavior might need a rework in the hallucination case (e.g. to not prompt if any trap glyph appears on the square). */ if (Hallucination || - levl[trap->tx][trap->ty].glyph != - trap_to_glyph(trap, rn2_on_display_rng)) { + levl[trap->tx][trap->ty].glyph != trap_to_glyph(trap)) { /* There's too much clutter to see your find otherwise */ cls(); map_trap(trap, 1); @@ -1634,7 +1952,8 @@ struct trap *trap; cleared = TRUE; } - You("find %s.", an(defsyms[trap_to_defsym(tt)].explanation)); + set_msg_xy(trap->tx, trap->ty); + You("find %s.", an(trapname(trap->ttyp, FALSE))); if (cleared) { display_nhwindow(WIN_MAP, TRUE); /* wait */ @@ -1642,12 +1961,10 @@ struct trap *trap; } } -STATIC_OVL int -mfind0(mtmp, via_warning) -struct monst *mtmp; -boolean via_warning; +staticfn int +mfind0(struct monst *mtmp, boolean via_warning) { - int x = mtmp->mx, y = mtmp->my; + coordxy x = mtmp->mx, y = mtmp->my; boolean found_something = FALSE; if (via_warning && !warning_of(mtmp)) @@ -1663,8 +1980,9 @@ boolean via_warning; if (mtmp->mundetected && (is_hider(mtmp->data) || hides_under(mtmp->data) || mtmp->data->mlet == S_EEL)) { - if (via_warning) { - Your("warning senses cause you to take a second %s.", + if (via_warning && found_something) { + set_msg_xy(x, y); + Your("danger sense causes you to take a second %s.", Blind ? "to check nearby" : "look close by"); display_nhwindow(WIN_MESSAGE, FALSE); /* flush messages */ } @@ -1683,8 +2001,10 @@ boolean via_warning; exercise(A_WIS, TRUE); if (!canspotmon(mtmp)) { map_invisible(x, y); + set_msg_xy(x, y); You_feel("an unseen monster!"); } else if (!sensemon(mtmp)) { + set_msg_xy(x, y); You("find %s.", mtmp->mtame ? y_monnam(mtmp) : a_monnam(mtmp)); } return 1; @@ -1693,25 +2013,15 @@ boolean via_warning; } int -dosearch0(aflag) -register int aflag; /* intrinsic autosearch vs explicit searching */ +dosearch0(int aflag) /* intrinsic autosearch vs explicit searching */ { -#ifdef GCC_BUG - /* Some old versions of gcc seriously muck up nested loops. If you get - * strange crashes while searching in a version compiled with gcc, try - * putting #define GCC_BUG in *conf.h (or adding -DGCC_BUG to CFLAGS in - * the makefile). - */ - volatile xchar x, y; -#else - register xchar x, y; -#endif - register struct trap *trap; - register struct monst *mtmp; + coordxy x, y; + struct trap *trap; + struct monst *mtmp; if (u.uswallow) { if (!aflag) - pline("What are you looking for? The exit?"); + Norep("What are you looking for? The exit?"); } else { int fund = (uwep && uwep->oartifact && spec_ability(uwep, SPFX_SEARCH)) ? uwep->spe : 0; @@ -1724,18 +2034,20 @@ register int aflag; /* intrinsic autosearch vs explicit searching */ for (y = u.uy - 1; y < u.uy + 2; y++) { if (!isok(x, y)) continue; - if (x == u.ux && y == u.uy) + if (u_at(x, y)) continue; - if (Blind && !aflag) + if (!aflag && (Blind || visible_region_at(x, y))) feel_location(x, y); if (levl[x][y].typ == SDOOR) { if (rnl(7 - fund)) continue; cvt_sdoor_to_door(&levl[x][y]); /* .typ = DOOR */ + recalc_block_point(x, y); exercise(A_WIS, TRUE); nomul(0); feel_location(x, y); /* make sure it shows up */ + set_msg_xy(x, y); You("find a hidden door."); } else if (levl[x][y].typ == SCORR) { if (rnl(7 - fund)) @@ -1745,6 +2057,7 @@ register int aflag; /* intrinsic autosearch vs explicit searching */ exercise(A_WIS, TRUE); nomul(0); feel_newsym(x, y); /* make sure it shows up */ + set_msg_xy(x, y); You("find a hidden passage."); } else { /* Be careful not to find anything in an SCORR or SDOOR */ @@ -1779,22 +2092,26 @@ register int aflag; /* intrinsic autosearch vs explicit searching */ return 1; } -/* the 's' command -- explicit searching */ +/* the #search command -- explicit searching */ int -dosearch() +dosearch(void) { - return dosearch0(0); + if (cmd_safety_prevention("Searching", "another search", + "You already found a monster.", + &ga.already_found_flag)) + return ECMD_OK; + return dosearch0(0) ? ECMD_TIME : ECMD_OK; } void -warnreveal() +warnreveal(void) { - int x, y; + coordxy x, y; struct monst *mtmp; for (x = u.ux - 1; x <= u.ux + 1; x++) for (y = u.uy - 1; y <= u.uy + 1; y++) { - if (!isok(x, y) || (x == u.ux && y == u.uy)) + if (!isok(x, y) || u_at(x, y)) continue; if ((mtmp = m_at(x, y)) != 0 && warning_of(mtmp) && mtmp->mundetected) @@ -1802,85 +2119,134 @@ warnreveal() } } -/* Pre-map the sokoban levels */ +/* skip premap detection of areas outside Sokoban map */ +staticfn boolean +skip_premap_detect(coordxy x, coordxy y) +{ + if ((levl[x][y].typ == STONE) + && (levl[x][y].wall_info & (W_NONDIGGABLE | W_NONPASSWALL)) != 0) + return TRUE; + return FALSE; +} + +/* Pre-map (the sokoban) levels */ void -sokoban_detect() +premap_detect(void) { - register int x, y; - register struct trap *ttmp; - register struct obj *obj; + coordxy x, y; + struct trap *ttmp; + struct obj *obj; /* Map the background and boulders */ for (x = 1; x < COLNO; x++) for (y = 0; y < ROWNO; y++) { + if (skip_premap_detect(x, y)) + continue; levl[x][y].seenv = SVALL; levl[x][y].waslit = TRUE; + if (levl[x][y].typ == SDOOR) + levl[x][y].wall_info = 0; /* see rm.h for explanation */ map_background(x, y, 1); if ((obj = sobj_at(BOULDER, x, y)) != 0) map_object(obj, 1); } /* Map the traps */ - for (ttmp = ftrap; ttmp; ttmp = ttmp->ntrap) { + for (ttmp = gf.ftrap; ttmp; ttmp = ttmp->ntrap) { ttmp->tseen = 1; map_trap(ttmp, 1); - /* set sokoban_rules when there is at least one pit or hole */ - if (ttmp->ttyp == PIT || ttmp->ttyp == HOLE) - Sokoban = 1; } } -STATIC_DCL int -reveal_terrain_getglyph(x, y, full, swallowed, default_glyph, which_subset) -int x, y, full; -unsigned swallowed; -int default_glyph, which_subset; +/* used to see under visible gas/cloud regions; caller must declare cmaptmp */ +#define glyph_is_gascloud(glyph) \ + (glyph_is_cmap(glyph) && ((cmaptmp = glyph_to_cmap(glyph)) == S_cloud \ + || cmaptmp == S_poisoncloud)) + +staticfn int +reveal_terrain_getglyph( + coordxy x, coordxy y, + unsigned swallowed, + int default_glyph, + unsigned which_subset) { + struct trap *t; + struct monst *mtmp; int glyph, levl_glyph; uchar seenv; - boolean keep_traps = (which_subset & TER_TRP) !=0, + boolean keep_traps = (which_subset & TER_TRP) != 0, keep_objs = (which_subset & TER_OBJ) != 0, - keep_mons = (which_subset & TER_MON) != 0; - struct monst *mtmp; - struct trap *t; + keep_mons = (which_subset & TER_MON) != 0, + full = (which_subset & TER_FULL) != 0; + + /* + * FIXME: + * travel treats discovered vibrating square as if it were terrain + * rather than a trap so this should do so too. + */ /* for 'full', show the actual terrain for the entire level, otherwise what the hero remembers for seen locations with monsters, objects, and/or traps removed as caller dictates */ - seenv = (full || level.flags.hero_memory) + seenv = (full || svl.level.flags.hero_memory) ? levl[x][y].seenv : cansee(x, y) ? SVALL : 0; if (full) { levl[x][y].seenv = SVALL; glyph = back_to_glyph(x, y); levl[x][y].seenv = seenv; } else { - levl_glyph = level.flags.hero_memory - ? levl[x][y].glyph - : seenv ? back_to_glyph(x, y): default_glyph; + int cmaptmp = 0; /* used by glyph_is_gascloud() macro */ + NhRegion *reg = visible_region_at(x, y); + boolean was_mon = FALSE; + + levl_glyph = svl.level.flags.hero_memory ? levl[x][y].glyph + : seenv ? back_to_glyph(x, y) + : default_glyph; /* glyph_at() returns the displayed glyph, which might be a monster. levl[][].glyph contains the remembered glyph, which will never be a monster (unless it is the invisible monster glyph, which is handled like an object, replacing any object or trap at its spot) */ glyph = !swallowed ? glyph_at(x, y) : levl_glyph; - if (keep_mons && x == u.ux && y == u.uy && swallowed) + if (keep_mons && u_at(x, y) && swallowed) { glyph = mon_to_glyph(u.ustuck, rn2_on_display_rng); - else if (((glyph_is_monster(glyph) - || glyph_is_warning(glyph)) && !keep_mons) - || glyph_is_swallow(glyph)) + } else if ((!keep_mons && (glyph_is_monster(glyph) + || glyph_is_warning(glyph))) + || glyph_is_swallow(glyph)) { glyph = levl_glyph; - if (((glyph_is_object(glyph) && !keep_objs) + was_mon = TRUE; + } + if (((!keep_objs && glyph_is_object(glyph)) || glyph_is_invisible(glyph)) && keep_traps && !covers_traps(x, y)) { if ((t = t_at(x, y)) != 0 && t->tseen) - glyph = trap_to_glyph(t, rn2_on_display_rng); + glyph = trap_to_glyph(t); } - if ((glyph_is_object(glyph) && !keep_objs) - || (glyph_is_trap(glyph) && !keep_traps) + if ((!keep_objs && glyph_is_object(glyph)) + /* we either show both traps and visible regions (trap if both + are present at the same spot) or neither traps nor regions */ + || (!keep_traps && (glyph_is_trap(glyph) + || (reg && glyph_is_gascloud(glyph)))) + || (reg && was_mon) || glyph_is_invisible(glyph)) { if (!seenv) { - glyph = default_glyph; - } else if (lastseentyp[x][y] == levl[x][y].typ) { + /* it's possible to have a visible region shown at an + otherwise unexplored location (cast stinking cloud + through unexplored corridor into lit room, then approach + far enough to be adjacent to the cloud without having + seen the corridor underneath it) */ + glyph = !reg ? default_glyph : GLYPH_UNEXPLORED; + } else if (keep_traps && reg + && (glyph_is_gascloud(glyph) || was_mon)) { + t = t_at(x, y); + /* we need reg->glyph here when there's a monster shown + at a region spot; the region glyph isn't the remembered + background glyph or the current glyph */ + glyph = (t && t->tseen) ? trap_to_glyph(t) : reg->glyph; + /* FIXME? what about objects temporarily hidden by regions? + when objects are being shown, shouldn't showing them take + precedence over showing the region, just like traps? */ + } else if (svl.lastseentyp[x][y] == levl[x][y].typ) { glyph = back_to_glyph(x, y); } else { /* look for a mimic here posing as furniture; @@ -1889,39 +2255,56 @@ int default_glyph, which_subset; && M_AP_TYPE(mtmp) == M_AP_FURNITURE) { glyph = cmap_to_glyph(mtmp->mappearance); } else { - /* we have a topology type but we want a screen - symbol in order to derive a glyph; some screen - symbols need the flags field of levl[][] in - addition to the type (to disambiguate STAIRS to - S_upstair or S_dnstair, for example; current - flags might not be intended for remembered type, - but we've got no other choice) */ - schar save_typ = levl[x][y].typ; - - levl[x][y].typ = lastseentyp[x][y]; + struct rm save_spot; + + /* + * We have a topology type but we want a screen symbol + * in order to derive a glyph. Some screen symbols need + * the flags field of levl[][] in addition to the type + * (to disambiguate STAIRS to S_upstair or S_dnstair, + * for example). Current flags might not be intended + * for remembered type, but we've got no other choice. + * An exception is wall_info which can be recalculated and + * needs to be. Otherwise back_to_glyph() -> wall_angle() + * might issue an impossible() for it if it is currently + * doormask==D_OPEN for an open door remembered as a wall. + */ + save_spot = levl[x][y]; + levl[x][y].typ = svl.lastseentyp[x][y]; + if (IS_WALL(levl[x][y].typ) || levl[x][y].typ == SDOOR) + xy_set_wall_state(x, y); /* levl[x][y].wall_info */ glyph = back_to_glyph(x, y); - levl[x][y].typ = save_typ; + levl[x][y] = save_spot; } } } } + /* FIXME: dirty hack */ if (glyph == cmap_to_glyph(S_darkroom)) - glyph = cmap_to_glyph(S_room); /* FIXME: dirty hack */ + glyph = cmap_to_glyph(S_room); + else if (glyph == cmap_to_glyph(S_litcorr)) + glyph = cmap_to_glyph(S_corr); return glyph; } +#undef glyph_is_gascloud + #ifdef DUMPLOG void -dump_map() +dump_map(void) { - int x, y, glyph, skippedrows, lastnonblank; - int subset = TER_MAP | TER_TRP | TER_OBJ | TER_MON; - int default_glyph = cmap_to_glyph(level.flags.arboreal ? S_tree : S_stone); - char buf[BUFSZ]; + char buf[COLBUFSZ]; + coordxy x, y; + int glyph, skippedrows, lastnonblank; boolean blankrow, toprow; + unsigned subset = TER_MAP | TER_TRP | TER_OBJ | TER_MON; + /* cmap_to_glyph() evaluates its argument multiple times, so pull the + tree vs stone conditional out of it */ + nhsym default_sym = svl.level.flags.arboreal ? S_tree : S_stone; + int default_glyph = cmap_to_glyph(default_sym); /* - * Squeeze out excess vertial space when dumping the map. + * Squeeze out excess vertical space when dumping the map. * If there are any blank map rows at the top, suppress them * (our caller has already printed a separator). If there is * more than one blank map row at the bottom, keep just one. @@ -1934,12 +2317,13 @@ dump_map() blankrow = TRUE; /* assume blank until we discover otherwise */ lastnonblank = -1; /* buf[] index rather than map's x */ for (x = 1; x < COLNO; x++) { - int ch, color; - unsigned special; + int ch; + glyph_info glyphinfo; - glyph = reveal_terrain_getglyph(x, y, FALSE, u.uswallow, + glyph = reveal_terrain_getglyph(x, y, u.uswallow, default_glyph, subset); - (void) mapglyph(glyph, &ch, &color, &special, x, y, 0); + map_glyphinfo(x, y, glyph, 0, &glyphinfo); + ch = glyphinfo.ttychar; buf[x - 1] = ch; if (ch != ' ') { blankrow = FALSE; @@ -1966,30 +2350,35 @@ dump_map() #endif /* DUMPLOG */ /* idea from crawl; show known portion of map without any monsters, - objects, or traps occluding the view of the underlying terrain */ + objects, or traps occluding the view of the underlying terrain; + in explore or wizard modes, can also display unexplored portion */ void -reveal_terrain(full, which_subset) -int full; /* wizard|explore modes allow player to request full map */ -int which_subset; /* when not full, whether to suppress objs and/or traps */ +reveal_terrain( + unsigned which_subset) /* TER_TRP | TER_OBJ | TER_MON | TER_FULL */ { + /* 'full' overrides impairment and implies no-traps, no-objs, no-mons */ + boolean full = (which_subset & TER_FULL) != 0; /* show whole map */ + if ((Hallucination || Stunned || Confusion) && !full) { You("are too disoriented for this."); } else { - int x, y, glyph, default_glyph; + coordxy x, y; + int glyph, default_glyph; char buf[BUFSZ]; /* there is a TER_MAP bit too; we always show map regardless of it */ - boolean keep_traps = (which_subset & TER_TRP) !=0, + boolean keep_traps = (which_subset & TER_TRP) != 0, keep_objs = (which_subset & TER_OBJ) != 0, keep_mons = (which_subset & TER_MON) != 0; /* not used */ unsigned swallowed = u.uswallow; /* before unconstrain_map() */ + nhsym default_sym = svl.level.flags.arboreal ? S_tree : S_stone; if (unconstrain_map()) docrt(); - default_glyph = cmap_to_glyph(level.flags.arboreal ? S_tree : S_stone); + default_glyph = cmap_to_glyph(default_sym); for (x = 1; x < COLNO; x++) for (y = 0; y < ROWNO; y++) { - glyph = reveal_terrain_getglyph(x,y, full, swallowed, + glyph = reveal_terrain_getglyph(x, y, swallowed, default_glyph, which_subset); show_glyph(x, y, glyph); } @@ -2019,14 +2408,11 @@ int which_subset; /* when not full, whether to suppress objs and/or traps */ which_subset |= TER_MAP; /* guarantee non-zero */ browse_map(which_subset, "anything of interest"); - reconstrain_map(); - docrt(); /* redraw the screen, restoring regular map */ - if (Underwater) - under_water(2); - if (u.uburied) - under_ground(2); + map_redisplay(); } return; } +#undef FOUND_FLASH_COUNT + /*detect.c*/ diff --git a/src/dig.c b/src/dig.c index 9b91f982c..29a242fa8 100644 --- a/src/dig.c +++ b/src/dig.c @@ -1,20 +1,20 @@ -/* NetHack 3.6 dig.c $NHDT-Date: 1547421446 2019/01/13 23:17:26 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.117 $ */ +/* NetHack 5.0 dig.c $NHDT-Date: 1740629713 2025/02/26 20:15:13 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.227 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Michael Allison, 2012. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -static NEARDATA boolean did_dig_msg; - -STATIC_DCL boolean NDECL(rm_waslit); -STATIC_DCL void FDECL(mkcavepos, - (XCHAR_P, XCHAR_P, int, BOOLEAN_P, BOOLEAN_P)); -STATIC_DCL void FDECL(mkcavearea, (BOOLEAN_P)); -STATIC_DCL int NDECL(dig); -STATIC_DCL void FDECL(dig_up_grave, (coord *)); -STATIC_DCL int FDECL(adj_pit_checks, (coord *, char *)); -STATIC_DCL void FDECL(pit_flow, (struct trap *, SCHAR_P)); +staticfn boolean rm_waslit(void); +staticfn void mkcavepos(coordxy, coordxy, int, boolean, boolean); +staticfn void mkcavearea(boolean); +staticfn boolean pick_can_reach(struct obj *, coordxy, coordxy) NONNULLARG1; +staticfn int dig(void); +staticfn void dig_up_grave(coord *); +staticfn boolean watchman_canseeu(struct monst *) NONNULLARG1; +staticfn int adj_pit_checks(coord *, char *) NONNULLARG2; +staticfn void pit_flow(struct trap *, schar); +staticfn boolean furniture_handled(coordxy, coordxy, boolean); /* Indices returned by dig_typ() */ enum dig_types { @@ -26,10 +26,10 @@ enum dig_types { DIGTYP_TREE }; -STATIC_OVL boolean -rm_waslit() +staticfn boolean +rm_waslit(void) { - register xchar x, y; + coordxy x, y; if (levl[u.ux][u.uy].typ == ROOM && levl[u.ux][u.uy].waslit) return TRUE; @@ -44,28 +44,25 @@ rm_waslit() * boulders in the name of a nice effect. Vision will get fixed up again * immediately after the effect is complete. */ -STATIC_OVL void -mkcavepos(x, y, dist, waslit, rockit) -xchar x, y; -int dist; -boolean waslit, rockit; +staticfn void +mkcavepos(coordxy x, coordxy y, int dist, boolean waslit, boolean rockit) { - register struct rm *lev; + struct rm *lev; if (!isok(x, y)) return; lev = &levl[x][y]; if (rockit) { - register struct monst *mtmp; + struct monst *mtmp; - if (IS_ROCK(lev->typ)) + if (IS_OBSTRUCTED(lev->typ)) return; if (t_at(x, y)) return; /* don't cover the portal */ if ((mtmp = m_at(x, y)) != 0) /* make sure crucial monsters survive */ if (!passes_walls(mtmp->data)) - (void) rloc(mtmp, TRUE); + (void) rloc(mtmp, RLOC_NOMSG); } else if (lev->typ == ROOM) return; @@ -80,28 +77,29 @@ boolean waslit, rockit; lev->waslit = (rockit ? FALSE : TRUE); lev->horizontal = FALSE; /* short-circuit vision recalc */ - viz_array[y][x] = (dist < 3) ? (IN_SIGHT | COULD_SEE) : COULD_SEE; + gv.viz_array[y][x] = (dist < 3) ? (IN_SIGHT | COULD_SEE) : COULD_SEE; lev->typ = (rockit ? STONE : ROOM); /* flags set via doormask above */ if (dist >= 3) impossible("mkcavepos called with dist %d", dist); feel_newsym(x, y); } -STATIC_OVL void -mkcavearea(rockit) -register boolean rockit; +staticfn void +mkcavearea(boolean rockit) { int dist; - xchar xmin = u.ux, xmax = u.ux; - xchar ymin = u.uy, ymax = u.uy; - register xchar i; - register boolean waslit = rm_waslit(); + coordxy xmin = u.ux, xmax = u.ux; + coordxy ymin = u.uy, ymax = u.uy; + coordxy i; + boolean waslit = rm_waslit(); - if (rockit) + if (rockit) { + Soundeffect(se_crashing_rock, 100); pline("Crash! The ceiling collapses around you!"); - else + } else { pline("A mysterious force %s cave around you!", (levl[u.ux][u.uy].typ == CORR) ? "creates a" : "extends the"); + } display_nhwindow(WIN_MESSAGE, TRUE); for (dist = 1; dist <= 2; dist++) { @@ -125,7 +123,7 @@ register boolean rockit; } flush_screen(1); /* make sure the new glyphs shows up */ - delay_output(); + nh_delay_output(); } if (!rockit && levl[u.ux][u.uy].typ == CORR) { @@ -135,135 +133,200 @@ register boolean rockit; newsym(u.ux, u.uy); /* in case player is invisible */ } - vision_full_recalc = 1; /* everything changed */ + gv.vision_full_recalc = 1; /* everything changed */ +} + +/* called when attempting to break a statue or boulder with a pick */ +staticfn boolean +pick_can_reach(struct obj *pick, coordxy x, coordxy y) +{ + struct trap *t = t_at(x, y); + /* tseen: pit only affects item positioning when it is known */ + boolean target_in_pit = t && is_pit(t->ttyp) && t->tseen; + + /* if hero is in a pit, pick can only reach if the statue is too and + the two pits are conjoined or the statue isn't and pick is two-handed; + this applies to hero in pit trying to reach an adjcacent boulder too */ + if (u.utrap && u.utraptype == TT_PIT) { + if (target_in_pit) + return conjoined_pits(t, t_at(u.ux, u.uy), FALSE); + return bimanual(pick); + } + + /* when hero isn't in a pit, a mattock or flying hero w/ pick can reach + whether or not the statue is in a pit */ + if (bimanual(pick) || Flying) + return TRUE; + /* one-handed pick-axe can reach if statue isn't in a pit */ + if (!target_in_pit) + return TRUE; + + return FALSE; } /* When digging into location , what are you actually digging into? */ int -dig_typ(otmp, x, y) -struct obj *otmp; -xchar x, y; +dig_typ(struct obj *otmp, coordxy x, coordxy y) { - boolean ispick; + int ltyp; - if (!otmp) - return DIGTYP_UNDIGGABLE; - ispick = is_pick(otmp); - if (!ispick && !is_axe(otmp)) + if (!isok(x, y) || !otmp || (!is_pick(otmp) && !is_axe(otmp))) return DIGTYP_UNDIGGABLE; - return ((ispick && sobj_at(STATUE, x, y)) - ? DIGTYP_STATUE - : (ispick && sobj_at(BOULDER, x, y)) - ? DIGTYP_BOULDER - : closed_door(x, y) - ? DIGTYP_DOOR - : IS_TREE(levl[x][y].typ) - ? (ispick ? DIGTYP_UNDIGGABLE : DIGTYP_TREE) - : (ispick && IS_ROCK(levl[x][y].typ) - && (!level.flags.arboreal - || IS_WALL(levl[x][y].typ))) - ? DIGTYP_ROCK - : DIGTYP_UNDIGGABLE); + ltyp = levl[x][y].typ; + if (is_axe(otmp)) + return closed_door(x, y) ? DIGTYP_DOOR + : IS_TREE(ltyp) ? DIGTYP_TREE /* axe vs tree */ + : DIGTYP_UNDIGGABLE; + /*assert(is_pick(otmp));*/ + return (sobj_at(STATUE, x, y) && pick_can_reach(otmp, x, y)) + ? DIGTYP_STATUE + : (sobj_at(BOULDER, x, y) && pick_can_reach(otmp, x, y)) + ? DIGTYP_BOULDER + : closed_door(x, y) ? DIGTYP_DOOR + : IS_TREE(ltyp) ? DIGTYP_UNDIGGABLE /* pick vs tree */ + : (IS_OBSTRUCTED(ltyp) + && (!svl.level.flags.arboreal || IS_WALL(ltyp))) + ? DIGTYP_ROCK + : DIGTYP_UNDIGGABLE; } boolean -is_digging() +is_digging(void) { - if (occupation == dig) { + if (go.occupation == dig) { return TRUE; } return FALSE; } -#define BY_YOU (&youmonst) +#define BY_YOU (&gy.youmonst) #define BY_OBJECT ((struct monst *) 0) -boolean -dig_check(madeby, verbose, x, y) -struct monst *madeby; -boolean verbose; -int x, y; +enum digcheck_result +dig_check(struct monst *madeby, coordxy x, coordxy y) { struct trap *ttmp = t_at(x, y); - const char *verb = - (madeby == BY_YOU && uwep && is_axe(uwep)) ? "chop" : "dig in"; if (On_stairs(x, y)) { - if (x == xdnladder || x == xupladder) { - if (verbose) - pline_The("ladder resists your effort."); - } else if (verbose) - pline_The("stairs are too hard to %s.", verb); - return FALSE; + stairway *stway = stairway_at(x, y); + if (stway->isladder) { + return DIGCHECK_FAIL_ONLADDER; + } else { + return DIGCHECK_FAIL_ONSTAIRS; + } } else if (IS_THRONE(levl[x][y].typ) && madeby != BY_OBJECT) { - if (verbose) - pline_The("throne is too hard to break apart."); - return FALSE; + return DIGCHECK_FAIL_THRONE; } else if (IS_ALTAR(levl[x][y].typ) - && (madeby != BY_OBJECT || Is_astralevel(&u.uz) - || Is_sanctum(&u.uz))) { - if (verbose) - pline_The("altar is too hard to break apart."); - return FALSE; + && (madeby != BY_OBJECT + || (altarmask_at(x, y) & AM_SANCTUM) != 0)) { + return DIGCHECK_FAIL_ALTAR; } else if (Is_airlevel(&u.uz)) { - if (verbose) - You("cannot %s thin air.", verb); - return FALSE; + return DIGCHECK_FAIL_AIRLEVEL; } else if (Is_waterlevel(&u.uz)) { - if (verbose) - pline_The("%s splashes and subsides.", hliquid("water")); - return FALSE; - } else if ((IS_ROCK(levl[x][y].typ) && levl[x][y].typ != SDOOR - && (levl[x][y].wall_info & W_NONDIGGABLE) != 0) - || (ttmp - && (ttmp->ttyp == MAGIC_PORTAL - || ttmp->ttyp == VIBRATING_SQUARE - || (!Can_dig_down(&u.uz) && !levl[x][y].candig)))) { - if (verbose) - pline_The("%s here is too hard to %s.", surface(x, y), verb); - return FALSE; + return DIGCHECK_FAIL_WATERLEVEL; + } else if ((IS_OBSTRUCTED(levl[x][y].typ) && levl[x][y].typ != SDOOR + && (levl[x][y].wall_info & W_NONDIGGABLE) != 0)) { + return DIGCHECK_FAIL_TOOHARD; + } else if (ttmp && undestroyable_trap(ttmp->ttyp)) { + return DIGCHECK_FAIL_UNDESTROYABLETRAP; + } else if (!Can_dig_down(&u.uz) && !levl[x][y].candig) { + if (ttmp) { + if (!is_hole(ttmp->ttyp) && !is_pit(ttmp->ttyp)) + return DIGCHECK_PASSED_DESTROY_TRAP; + else + return DIGCHECK_FAIL_CANTDIG; + } else { + return DIGCHECK_PASSED_PITONLY; + } } else if (sobj_at(BOULDER, x, y)) { - if (verbose) - There("isn't enough room to %s here.", verb); - return FALSE; + return DIGCHECK_FAIL_BOULDER; } else if (madeby == BY_OBJECT /* the block against existing traps is mainly to prevent broken wands from turning holes into pits */ && (ttmp || is_pool_or_lava(x, y))) { /* digging by player handles pools separately */ - return FALSE; + return DIGCHECK_FAIL_OBJ_POOL_OR_TRAP; } - return TRUE; + return DIGCHECK_PASSED; } -STATIC_OVL int -dig(VOID_ARGS) +void +digcheck_fail_message(enum digcheck_result digresult, struct monst *madeby, + coordxy x, coordxy y) { - register struct rm *lev; - register xchar dpx = context.digging.pos.x, dpy = context.digging.pos.y; - register boolean ispick = uwep && is_pick(uwep); + const char *verb = + (madeby == BY_YOU && uwep && is_axe(uwep)) ? "chop" : "dig in"; + + if (digresult < DIGCHECK_FAILED) + return; + + switch (digresult) { + case DIGCHECK_FAIL_AIRLEVEL: + You("cannot %s thin air.", verb); + break; + case DIGCHECK_FAIL_ALTAR: + pline_The("altar is too hard to break apart."); + break; + case DIGCHECK_FAIL_BOULDER: + There("isn't enough room to %s here.", verb); + break; + case DIGCHECK_FAIL_ONLADDER: + pline_The("ladder resists your effort."); + break; + case DIGCHECK_FAIL_ONSTAIRS: + pline_The("stairs are too hard to %s.", verb); + break; + case DIGCHECK_FAIL_THRONE: + pline_The("throne is too hard to break apart."); + break; + case DIGCHECK_FAIL_CANTDIG: + case DIGCHECK_FAIL_TOOHARD: + case DIGCHECK_FAIL_UNDESTROYABLETRAP: + pline_The("%s here is too hard to %s.", surface(x, y), verb); + break; + case DIGCHECK_FAIL_WATERLEVEL: + pline_The("%s splashes and subsides.", hliquid("water")); + break; + case DIGCHECK_FAIL_OBJ_POOL_OR_TRAP: + case DIGCHECK_PASSED: + case DIGCHECK_PASSED_PITONLY: + case DIGCHECK_PASSED_DESTROY_TRAP: + break; + } +} + +staticfn int +dig(void) +{ + struct rm *lev; + coordxy dpx = svc.context.digging.pos.x, dpy = svc.context.digging.pos.y; + boolean ispick = uwep && is_pick(uwep); const char *verb = (!uwep || is_pick(uwep)) ? "dig into" : "chop through"; + enum digcheck_result dcresult = DIGCHECK_PASSED; lev = &levl[dpx][dpy]; /* perhaps a nymph stole your pick-axe while you were busy digging */ /* or perhaps you teleported away */ if (u.uswallow || !uwep || (!ispick && !is_axe(uwep)) - || !on_level(&context.digging.level, &u.uz) - || ((context.digging.down ? (dpx != u.ux || dpy != u.uy) - : (distu(dpx, dpy) > 2)))) + || !on_level(&svc.context.digging.level, &u.uz) + || ((svc.context.digging.down ? (dpx != u.ux || dpy != u.uy) + : !next2u(dpx, dpy)))) return 0; - if (context.digging.down) { - if (!dig_check(BY_YOU, TRUE, u.ux, u.uy)) + if (svc.context.digging.down) { + dcresult = dig_check(BY_YOU, u.ux, u.uy); + if (dcresult >= DIGCHECK_FAILED) { + digcheck_fail_message(dcresult, BY_YOU, u.ux, u.uy); return 0; - } else { /* !context.digging.down */ + } + } else { /* !svc.context.digging.down */ if (IS_TREE(lev->typ) && !may_dig(dpx, dpy) && dig_typ(uwep, dpx, dpy) == DIGTYP_TREE) { pline("This tree seems to be petrified."); return 0; } - if (IS_ROCK(lev->typ) && !may_dig(dpx, dpy) + if (IS_OBSTRUCTED(lev->typ) && !may_dig(dpx, dpy) && dig_typ(uwep, dpx, dpy) == DIGTYP_ROCK) { pline("This %s is too hard to %s.", is_db_wall(dpx, dpy) ? "drawbridge" : "wall", verb); @@ -287,8 +350,10 @@ dig(VOID_ARGS) } break; case 1: + Soundeffect(se_bang_weapon_side, 100); pline("Bang! You hit with the broad side of %s!", the(xname(uwep))); + wake_nearby(FALSE); break; default: Your("swing misses its mark."); @@ -297,21 +362,22 @@ dig(VOID_ARGS) return 0; } - context.digging.effort += + svc.context.digging.effort += 10 + rn2(5) + abon() + uwep->spe - greatest_erosion(uwep) + u.udaminc; if (Race_if(PM_DWARF)) - context.digging.effort *= 2; - if (context.digging.down) { + svc.context.digging.effort *= 2; + if (svc.context.digging.down) { struct trap *ttmp = t_at(dpx, dpy); - if (context.digging.effort > 250 || (ttmp && ttmp->ttyp == HOLE)) { + if (svc.context.digging.effort > 250 + || (ttmp && ttmp->ttyp == HOLE)) { (void) dighole(FALSE, FALSE, (coord *) 0); - (void) memset((genericptr_t) &context.digging, 0, - sizeof context.digging); + (void) memset((genericptr_t) &svc.context.digging, 0, + sizeof svc.context.digging); return 0; /* done with digging */ } - if (context.digging.effort <= 50 + if (svc.context.digging.effort <= 50 || (ttmp && (ttmp->ttyp == TRAPDOOR || is_pit(ttmp->ttyp)))) { return 1; } else if (ttmp && (ttmp->ttyp == LANDMINE @@ -320,13 +386,13 @@ dig(VOID_ARGS) hero should have used #untrap first */ dotrap(ttmp, FORCETRAP); /* restart completely from scratch if we resume digging */ - (void) memset((genericptr_t) &context.digging, 0, - sizeof context.digging); + (void) memset((genericptr_t) &svc.context.digging, 0, + sizeof svc.context.digging); return 0; } else if (ttmp && ttmp->ttyp == BEAR_TRAP && u.utrap) { if (rnl(7) > (Fumbling ? 1 : 4)) { char kbuf[BUFSZ]; - int dmg = dmgval(uwep, &youmonst) + dbon(); + int dmg = dmgval(uwep, &gy.youmonst) + dbon(); if (dmg < 1) dmg = 1; @@ -343,7 +409,18 @@ dig(VOID_ARGS) reset_utrap(TRUE); /* release from trap, maybe Lev or Fly */ } /* we haven't made any progress toward a pit yet */ - context.digging.effort = 0; + svc.context.digging.effort = 0; + return 0; + } else if (ttmp && dcresult == DIGCHECK_PASSED_DESTROY_TRAP) { + const char *ttmpname = trapname(ttmp->ttyp, FALSE); + + if (ispick) + You("destroy %s with %s.", + ttmp->tseen ? the(ttmpname) : an(ttmpname), + yobjnam(uwep, (const char *) 0)); + deltrap(ttmp); + /* we haven't made any progress toward a pit yet */ + svc.context.digging.effort = 0; return 0; } @@ -354,29 +431,31 @@ dig(VOID_ARGS) /* make pit at */ if (dighole(TRUE, FALSE, (coord *) 0)) { - context.digging.level.dnum = 0; - context.digging.level.dlevel = -1; + svc.context.digging.level.dnum = 0; + svc.context.digging.level.dlevel = -1; } return 0; } - if (context.digging.effort > 100) { + if (svc.context.digging.effort > 100) { + char digbuf[BUFSZ]; const char *digtxt, *dmgtxt = (const char *) 0; - struct obj *obj; + struct obj *obj, *bobj; boolean shopedge = *in_rooms(dpx, dpy, SHOPBASE); + int digtyp = dig_typ(uwep, dpx, dpy); - if ((obj = sobj_at(STATUE, dpx, dpy)) != 0) { + if (digtyp == DIGTYP_STATUE + && (obj = sobj_at(STATUE, dpx, dpy)) != 0) { if (break_statue(obj)) digtxt = "The statue shatters."; else /* it was a statue trap; break_statue() - * printed a message and updated the screen - */ + printed a message and updated the screen */ digtxt = (char *) 0; - } else if ((obj = sobj_at(BOULDER, dpx, dpy)) != 0) { - struct obj *bobj; - + } else if (digtyp == DIGTYP_BOULDER + && (obj = sobj_at(BOULDER, dpx, dpy)) != 0) { fracture_rock(obj); + /*[5.0: this probably isn't necessary anymore]*/ if ((bobj = sobj_at(BOULDER, dpx, dpy)) != 0) { /* another boulder here, restack it to the top */ obj_extract_self(bobj); @@ -395,11 +474,13 @@ dig(VOID_ARGS) goto cleanup; } } - if (IS_TREE(lev->typ)) { + if (digtyp == DIGTYP_TREE) { digtxt = "You cut down the tree."; lev->typ = ROOM, lev->flags = 0; if (!rn2(5)) (void) rnd_treefruit_at(dpx, dpy); + if (Race_if(PM_ELF) || Role_if(PM_RANGER)) + adjalign(-1); } else { digtxt = "You succeed in cutting away some rock."; lev->typ = CORR, lev->flags = 0; @@ -409,9 +490,10 @@ dig(VOID_ARGS) add_damage(dpx, dpy, SHOP_WALL_DMG); dmgtxt = "damage"; } - if (level.flags.is_maze_lev) { + if (svl.level.flags.is_maze_lev) { lev->typ = ROOM, lev->flags = 0; - } else if (level.flags.is_cavernous_lev && !in_town(dpx, dpy)) { + } else if (svl.level.flags.is_cavernous_lev + && !in_town(dpx, dpy)) { lev->typ = CORR, lev->flags = 0; } else { lev->typ = DOOR, lev->doormask = D_NODOOR; @@ -423,7 +505,9 @@ dig(VOID_ARGS) if (!(lev->doormask & D_TRAPPED)) lev->doormask = D_BROKEN; } else if (closed_door(dpx, dpy)) { - digtxt = "You break through the door."; + Sprintf(digbuf, "You break through the door with your %s.", + simpleonames(uwep)); + digtxt = digbuf; if (shopedge) { add_damage(dpx, dpy, SHOP_DOOR_COST); dmgtxt = "break"; @@ -436,36 +520,28 @@ dig(VOID_ARGS) if (!does_block(dpx, dpy, &levl[dpx][dpy])) unblock_point(dpx, dpy); /* vision: can see through */ feel_newsym(dpx, dpy); - if (digtxt && !context.digging.quiet) + if (digtxt && !svc.context.digging.quiet) pline1(digtxt); /* after newsym */ if (dmgtxt) pay_for_damage(dmgtxt, FALSE); if (Is_earthlevel(&u.uz) && !rn2(3)) { - register struct monst *mtmp; + int mndx = rn2(2) ? PM_EARTH_ELEMENTAL : PM_XORN; - switch (rn2(2)) { - case 0: - mtmp = makemon(&mons[PM_EARTH_ELEMENTAL], dpx, dpy, - NO_MM_FLAGS); - break; - default: - mtmp = makemon(&mons[PM_XORN], dpx, dpy, NO_MM_FLAGS); - break; - } - if (mtmp) + if (makemon(&mons[mndx], dpx, dpy, MM_NOMSG)) pline_The("debris from your digging comes to life!"); } if (IS_DOOR(lev->typ) && (lev->doormask & D_TRAPPED)) { lev->doormask = D_NODOOR; - b_trapped("door", 0); + b_trapped("door", NO_PART); + recalc_block_point(dpx, dpy); newsym(dpx, dpy); } - cleanup: - context.digging.lastdigtime = moves; - context.digging.quiet = FALSE; - context.digging.level.dnum = 0; - context.digging.level.dlevel = -1; + cleanup: + svc.context.digging.lastdigtime = svm.moves; + svc.context.digging.quiet = FALSE; + svc.context.digging.level.dnum = 0; + svc.context.digging.level.dlevel = -1; return 0; } else { /* not enough effort has been spent yet */ static const char *const d_target[6] = { "", "rock", "statue", @@ -479,35 +555,60 @@ dig(VOID_ARGS) return 0; } } else if (dig_target == DIGTYP_UNDIGGABLE - || (dig_target == DIGTYP_ROCK && !IS_ROCK(lev->typ))) + || (dig_target == DIGTYP_ROCK && !IS_OBSTRUCTED(lev->typ))) return 0; /* statue or boulder got taken */ - if (!did_dig_msg) { + if (!gd.did_dig_msg) { You("hit the %s with all your might.", d_target[dig_target]); - did_dig_msg = TRUE; + wake_nearby(FALSE); + gd.did_dig_msg = TRUE; } } return 1; } +staticfn boolean +furniture_handled(coordxy x, coordxy y, boolean madeby_u) +{ + struct rm *lev = &levl[x][y]; + + if (IS_FOUNTAIN(lev->typ)) { + dogushforth(FALSE); + SET_FOUNTAIN_WARNED(x, y); /* force dryup */ + dryup(x, y, madeby_u); + } else if (IS_SINK(lev->typ)) { + breaksink(x, y); + } else if (lev->typ == DRAWBRIDGE_DOWN + || (is_drawbridge_wall(x, y) >= 0)) { + coordxy bx = x, by = y; + + /* if under the portcullis, the bridge is adjacent */ + (void) find_drawbridge(&bx, &by); + destroy_drawbridge(bx, by); + } else { + return FALSE; + } + return TRUE; +} + + /* When will hole be finished? Very rough indication used by shopkeeper. */ int -holetime() +holetime(void) { - if (occupation != dig || !*u.ushops) + if (go.occupation != dig || !*u.ushops) return -1; - return ((250 - context.digging.effort) / 20); + return ((250 - svc.context.digging.effort) / 20); } /* Return typ of liquid to fill a hole with, or ROOM, if no liquid nearby */ schar -fillholetyp(x, y, fill_if_any) -int x, y; -boolean fill_if_any; /* force filling if it exists at all */ +fillholetyp(coordxy x, coordxy y, + boolean fill_if_any) /* force filling if it exists at all */ { - register int x1, y1; - int lo_x = max(1, x - 1), hi_x = min(x + 1, COLNO - 1), - lo_y = max(0, y - 1), hi_y = min(y + 1, ROWNO - 1); + coordxy x1, y1; + coordxy lo_x = max(1, x - 1), hi_x = min(x + 1, COLNO - 1), + lo_y = max(0, y - 1), hi_y = min(y + 1, ROWNO - 1); int pool_cnt = 0, moat_cnt = 0, lava_cnt = 0; for (x1 = lo_x; x1 <= hi_x; x1++) @@ -536,21 +637,19 @@ boolean fill_if_any; /* force filling if it exists at all */ } void -digactualhole(x, y, madeby, ttyp) -register int x, y; -struct monst *madeby; -int ttyp; +digactualhole(coordxy x, coordxy y, struct monst *madeby, int ttyp) { struct obj *oldobjs, *newobjs; - register struct trap *ttmp; - char surface_type[BUFSZ]; + struct trap *ttmp; + const char *surface_type, *tname, *in_thru; + char furniture[BUFSZ]; struct rm *lev = &levl[x][y]; - boolean shopdoor; struct monst *mtmp = m_at(x, y); /* may be madeby */ - boolean madeby_u = (madeby == BY_YOU); - boolean madeby_obj = (madeby == BY_OBJECT); - boolean at_u = (x == u.ux) && (y == u.uy); - boolean wont_fall = Levitation || Flying; + boolean madeby_u = (madeby == BY_YOU), madeby_obj = (madeby == BY_OBJECT), + /* BY_OBJECT means the hero broke a wand, so blame her for it */ + heros_fault = (madeby_u || madeby_obj); + boolean shopdoor, at_u = u_at(x, y), wont_fall = Levitation || Flying; + int old_typ, old_aligntyp = A_NONE; if (at_u && u.utrap) { if (u.utraptype == TT_BURIEDBALL) @@ -559,64 +658,76 @@ int ttyp; reset_utrap(FALSE); } - /* these furniture checks were in dighole(), but wand - breaking bypasses that routine and calls us directly */ - if (IS_FOUNTAIN(lev->typ)) { - dogushforth(FALSE); - SET_FOUNTAIN_WARNED(x, y); /* force dryup */ - dryup(x, y, madeby_u); - return; - } else if (IS_SINK(lev->typ)) { - breaksink(x, y); - return; - } else if (lev->typ == DRAWBRIDGE_DOWN - || (is_drawbridge_wall(x, y) >= 0)) { - int bx = x, by = y; - - /* if under the portcullis, the bridge is adjacent */ - (void) find_drawbridge(&bx, &by); - destroy_drawbridge(bx, by); + if (furniture_handled(x, y, madeby_u)) return; - } if (ttyp != PIT && (!Can_dig_down(&u.uz) && !lev->candig)) { impossible("digactualhole: can't dig %s on this level.", - defsyms[trap_to_defsym(ttyp)].explanation); + trapname(ttyp, TRUE)); ttyp = PIT; } - /* maketrap() might change it, also, in this situation, - surface() returns an inappropriate string for a grave */ - if (IS_GRAVE(lev->typ)) - Strcpy(surface_type, "grave"); - else - Strcpy(surface_type, surface(x, y)); + /* maketrap() might change terrain type but we deliver messages after + that, so prepare in advance */ + old_typ = lev->typ; + furniture[0] = '\0'; + if (IS_FURNITURE(lev->typ)) { + /* should mirror the word used by surface() for normal floor */ + surface_type = (IS_ROOM(lev->typ) && !Is_earthlevel(&u.uz) + ? "floor" : "ground"); + if (IS_ALTAR(lev->typ)) { + old_aligntyp = Amask2align(levl[x][y].altarmask & AM_MASK); + Strcpy(furniture, align_str(old_aligntyp)); + Strcat(furniture, " "); + } + Strcat(furniture, surface(x, y)); + } else { + surface_type = surface(x, y); + } shopdoor = IS_DOOR(lev->typ) && *in_rooms(x, y, SHOPBASE); - oldobjs = level.objects[x][y]; + oldobjs = svl.level.objects[x][y]; + ttmp = maketrap(x, y, ttyp); if (!ttmp) return; - newobjs = level.objects[x][y]; - ttmp->madeby_u = madeby_u; + newobjs = svl.level.objects[x][y]; + ttmp->madeby_u = heros_fault; ttmp->tseen = 0; if (cansee(x, y)) seetrap(ttmp); else if (madeby_u) feeltrap(ttmp); + tname = trapname(ttyp, TRUE); + in_thru = (ttyp == HOLE ? "through" : "in"); + if (madeby_u) { + if (x != u.ux || y != u.uy) + You("dig an adjacent %s.", tname); + else + You("dig %s %s the %s.", an(tname), in_thru, surface_type); + } else if (!madeby_obj && canseemon(madeby)) { + pline("%s digs %s %s the %s.", Monnam(madeby), an(tname), in_thru, + surface_type); + } else if (cansee(x, y) && flags.verbose) { + if (IS_STWALL(old_typ)) + pline_The("%s crumbles into %s.", surface_type, an(tname)); + else + pline("%s appears in the %s.", An(tname), surface_type); + } + if (IS_FURNITURE(old_typ) && cansee(x, y)) + pline_The("%s falls into the %s!", furniture, tname); + /* wrath should immediately follow altar destruction message */ + if (heros_fault && old_typ == ALTAR) + desecrate_altar(FALSE, old_aligntyp); + + /* now deal with actual post-trap creation effects */ if (ttyp == PIT) { - if (madeby_u) { - if (x != u.ux || y != u.uy) - You("dig an adjacent pit."); - else - You("dig a pit in the %s.", surface_type); - if (shopdoor) - pay_for_damage("ruin", FALSE); - } else if (!madeby_obj && canseemon(madeby)) { - pline("%s digs a pit in the %s.", Monnam(madeby), surface_type); - } else if (cansee(x, y) && flags.verbose) { - pline("A pit appears in the %s.", surface_type); - } + if (shopdoor && heros_fault) + pay_for_damage("ruin", FALSE); + else + add_damage(x, y, heros_fault ? SHOP_PIT_COST : 0L); + if (madeby_u) + wake_nearby(FALSE); /* in case we're digging down while encased in solid rock which is blocking levitation or flight */ switch_terrain(); @@ -626,7 +737,7 @@ int ttyp; if (at_u) { if (!wont_fall) { set_utrap(rn1(4, 2), TT_PIT); - vision_full_recalc = 1; /* vision limits change */ + gv.vision_full_recalc = 1; /* vision limits change */ } else reset_utrap(TRUE); if (oldobjs != newobjs) /* something unearthed */ @@ -637,18 +748,9 @@ int ttyp; pline("%s %s over the pit.", Monnam(mtmp), (is_flyer(mtmp->data)) ? "flies" : "floats"); } else if (mtmp != madeby) - (void) mintrap(mtmp); + (void) mintrap(mtmp, NO_TRAP_FLAGS); } } else { /* was TRAPDOOR now a HOLE*/ - - if (madeby_u) - You("dig a hole through the %s.", surface_type); - else if (!madeby_obj && canseemon(madeby)) - pline("%s digs a hole through the %s.", Monnam(madeby), - surface_type); - else if (cansee(x, y) && flags.verbose) - pline("A hole appears in the %s.", surface_type); - if (at_u) { /* in case we're digging down while encased in solid rock which is blocking levitation or flight */ @@ -671,18 +773,15 @@ int ttyp; impact_drop((struct obj *) 0, x, y, 0); if (oldobjs != newobjs) (void) pickup(1); - if (shopdoor && madeby_u) + if (shopdoor && heros_fault) pay_for_damage("ruin", FALSE); - } else { d_level newlevel; - if (*u.ushops && madeby_u) + if (*u.ushops && heros_fault) shopdig(1); /* shk might snatch pack */ - /* handle earlier damage, eg breaking wand of digging */ - else if (!madeby_u) + else /* handle any earlier hero-caused damage */ pay_for_damage("dig into", TRUE); - You("fall through..."); /* Earlier checks must ensure that the destination * level exists and is in the present dungeon. @@ -694,14 +793,13 @@ int ttyp; spoteffects(FALSE); } } else { - if (shopdoor && madeby_u) + if (shopdoor && heros_fault) pay_for_damage("ruin", FALSE); if (newobjs) impact_drop((struct obj *) 0, x, y, 0); if (mtmp) { /*[don't we need special sokoban handling here?]*/ - if (is_flyer(mtmp->data) || is_floater(mtmp->data) - || mtmp->data == &mons[PM_WUMPUS] + if (!grounded(mtmp->data) || (mtmp->wormno && count_wsegs(mtmp) > 5) || mtmp->data->msize >= MZ_HUGE) return; @@ -730,46 +828,69 @@ int ttyp; } } +DISABLE_WARNING_FORMAT_NONLITERAL + /* - * Called from dighole(), but also from do_break_wand() - * in apply.c. + * Called from dighole(); also from do_break_wand() in apply.c + * and do_earthquake() in music.c. */ void -liquid_flow(x, y, typ, ttmp, fillmsg) -xchar x, y; -schar typ; -struct trap *ttmp; -const char *fillmsg; +liquid_flow( + coordxy x, coordxy y, + schar typ, + struct trap *ttmp, + const char *fillmsg) { - boolean u_spot = (x == u.ux && y == u.uy); + struct obj *objchain; + struct monst *mon; + boolean u_spot = u_at(x, y); + + /* caller should have changed levl[x][y].typ to POOL, MOAT, or LAVA */ + if (!is_pool_or_lava(x, y)) { + if (iflags.sanity_check) { + impossible("Insane liquid_flow(%d,%d,%s,%s).", x, y, + ttmp ? trapname(ttmp->ttyp, TRUE) : "no trap", + fillmsg ? fillmsg : "no mesg"); + } + return; + } if (ttmp) - (void) delfloortrap(ttmp); + (void) delfloortrap(ttmp); /* will untrap monster if one is here */ /* if any objects were frozen here, they're released now */ + obj_ice_effects(x, y, TRUE); unearth_objs(x, y); if (fillmsg) pline(fillmsg, hliquid(typ == LAVAPOOL ? "lava" : "water")); - if (u_spot && !(Levitation || Flying)) { + /* handle object damage before hero damage; affects potential bones */ + if ((objchain = svl.level.objects[x][y]) != 0) { if (typ == LAVAPOOL) - (void) lava_effects(); - else if (!Wwalking) - (void) drown(); + fire_damage_chain(objchain, TRUE, TRUE, x, y); + else + water_damage_chain(objchain, TRUE); + } + /* damage to the hero */ + if (u_spot) { + (void) pooleffects(FALSE); + } else if ((mon = m_at(x, y)) != 0) { + (void) minliquid(mon); } } +RESTORE_WARNING_FORMAT_NONLITERAL + /* return TRUE if digging succeeded, FALSE otherwise */ boolean -dighole(pit_only, by_magic, cc) -boolean pit_only, by_magic; -coord *cc; +dighole(boolean pit_only, boolean by_magic, coord *cc) { - register struct trap *ttmp; + struct trap *ttmp; struct rm *lev; struct obj *boulder_here; - schar typ; - xchar dig_x, dig_y; - boolean nohole; + schar typ, old_typ; + coordxy dig_x, dig_y; + boolean nohole, retval = FALSE; + enum digcheck_result dig_check_result; if (!cc) { dig_x = u.ux; @@ -783,34 +904,39 @@ coord *cc; ttmp = t_at(dig_x, dig_y); lev = &levl[dig_x][dig_y]; - nohole = (!Can_dig_down(&u.uz) && !lev->candig); - - if ((ttmp && (ttmp->ttyp == MAGIC_PORTAL - || ttmp->ttyp == VIBRATING_SQUARE || nohole)) - || (IS_ROCK(lev->typ) && lev->typ != SDOOR + dig_check_result = dig_check(BY_YOU, dig_x, dig_y); + /* nohole = (!Can_dig_down(&u.uz) && !lev->candig); */ + nohole = (dig_check_result == DIGCHECK_FAIL_CANTDIG + || dig_check_result == DIGCHECK_FAIL_TOOHARD); + old_typ = lev->typ; + + if ((ttmp && (undestroyable_trap(ttmp->ttyp) || nohole)) + || (IS_OBSTRUCTED(old_typ) && old_typ != SDOOR && (lev->wall_info & W_NONDIGGABLE) != 0)) { pline_The("%s %shere is too hard to dig in.", surface(dig_x, dig_y), (dig_x != u.ux || dig_y != u.uy) ? "t" : ""); - + } else if (ttmp && is_magical_trap(ttmp->ttyp)) { + explode(dig_x, dig_y, 0, 20 + d(3, 6), TRAP_EXPLODE, EXPL_MAGICAL); + deltrap(ttmp); + newsym(dig_x, dig_y); } else if (is_pool_or_lava(dig_x, dig_y)) { pline_The("%s sloshes furiously for a moment, then subsides.", hliquid(is_lava(dig_x, dig_y) ? "lava" : "water")); - wake_nearby(); /* splashing */ + wake_nearby(FALSE); /* splashing */ - } else if (lev->typ == DRAWBRIDGE_DOWN + } else if (old_typ == DRAWBRIDGE_DOWN || (is_drawbridge_wall(dig_x, dig_y) >= 0)) { /* drawbridge_down is the platform crossing the moat when the bridge is extended; drawbridge_wall is the open "doorway" or closed "door" where the portcullis/mechanism is located */ if (pit_only) { pline_The("drawbridge seems too hard to dig through."); - return FALSE; } else { - int x = dig_x, y = dig_y; + coordxy x = dig_x, y = dig_y; /* if under the portcullis, the bridge is adjacent */ (void) find_drawbridge(&x, &y); destroy_drawbridge(x, y); - return TRUE; + retval = TRUE; } } else if ((boulder_here = sobj_at(BOULDER, dig_x, dig_y)) != 0) { @@ -824,17 +950,17 @@ coord *cc; * digging makes a hole, but the boulder immediately * fills it. Final outcome: no hole, no boulder. */ + Soundeffect(se_kadoom_boulder_falls_in, 60); pline("KADOOM! The boulder falls in!"); + wake_nearby(FALSE); (void) delfloortrap(ttmp); } delobj(boulder_here); - return TRUE; - - } else if (IS_GRAVE(lev->typ)) { + } else if (IS_GRAVE(old_typ)) { digactualhole(dig_x, dig_y, BY_YOU, PIT); dig_up_grave(cc); - return TRUE; - } else if (lev->typ == DRAWBRIDGE_UP) { + retval = TRUE; + } else if (old_typ == DRAWBRIDGE_UP) { /* must be floor or ice, other cases handled above */ /* dig "pit" and let fluid flow in (if possible) */ typ = fillholetyp(dig_x, dig_y, FALSE); @@ -847,20 +973,19 @@ coord *cc; pline_The("%s %shere is too hard to dig in.", surface(dig_x, dig_y), (dig_x != u.ux || dig_y != u.uy) ? "t" : ""); - return FALSE; + } else { + lev->drawbridgemask &= ~DB_UNDER; + lev->drawbridgemask |= (typ == LAVAPOOL) ? DB_LAVA : DB_MOAT; + liquid_flow(dig_x, dig_y, typ, ttmp, + "As you dig, the hole fills with %s!"); + retval = TRUE; } - lev->drawbridgemask &= ~DB_UNDER; - lev->drawbridgemask |= (typ == LAVAPOOL) ? DB_LAVA : DB_MOAT; - liquid_flow(dig_x, dig_y, typ, ttmp, - "As you dig, the hole fills with %s!"); - return TRUE; - /* the following two are here for the wand of digging */ - } else if (IS_THRONE(lev->typ)) { + } else if (IS_THRONE(old_typ)) { pline_The("throne is too hard to break apart."); - } else if (IS_ALTAR(lev->typ)) { + } else if (IS_ALTAR(old_typ)) { pline_The("altar is too hard to break apart."); } else { @@ -868,39 +993,42 @@ coord *cc; lev->flags = 0; if (typ != ROOM) { - lev->typ = typ; - liquid_flow(dig_x, dig_y, typ, ttmp, - "As you dig, the hole fills with %s!"); - return TRUE; - } + if (!furniture_handled((int) dig_x, (int) dig_y, TRUE)) { + lev->typ = typ; + liquid_flow(dig_x, dig_y, typ, ttmp, + "As you dig, the hole fills with %s!"); + } + retval = TRUE; + } else { + /* magical digging disarms settable traps */ + if (by_magic && ttmp + && (ttmp->ttyp == LANDMINE || ttmp->ttyp == BEAR_TRAP)) { + int otyp = (ttmp->ttyp == LANDMINE) ? LAND_MINE : BEARTRAP; - /* magical digging disarms settable traps */ - if (by_magic && ttmp - && (ttmp->ttyp == LANDMINE || ttmp->ttyp == BEAR_TRAP)) { - int otyp = (ttmp->ttyp == LANDMINE) ? LAND_MINE : BEARTRAP; + /* convert trap into buried object (deletes trap) */ + cnv_trap_obj(otyp, 1, ttmp, TRUE); + } - /* convert trap into buried object (deletes trap) */ - cnv_trap_obj(otyp, 1, ttmp, TRUE); + /* finally we get to make a hole */ + if (nohole || pit_only + || dig_check_result == DIGCHECK_PASSED_DESTROY_TRAP + || dig_check_result == DIGCHECK_PASSED_PITONLY) + digactualhole(dig_x, dig_y, BY_YOU, PIT); + else + digactualhole(dig_x, dig_y, BY_YOU, HOLE); + retval = TRUE; } - - /* finally we get to make a hole */ - if (nohole || pit_only) - digactualhole(dig_x, dig_y, BY_YOU, PIT); - else - digactualhole(dig_x, dig_y, BY_YOU, HOLE); - - return TRUE; } - - return FALSE; + spot_checks(dig_x, dig_y, old_typ); + return retval; } -STATIC_OVL void -dig_up_grave(cc) -coord *cc; +staticfn void +dig_up_grave(coord *cc) { struct obj *otmp; - xchar dig_x, dig_y; + int what_happens; + coordxy dig_x, dig_y; if (!cc) { dig_x = u.ux; @@ -920,56 +1048,64 @@ coord *cc; } else if (Role_if(PM_SAMURAI)) { adjalign(-sgn(u.ualign.type)); You("disturb the honorable dead!"); - } else if ((u.ualign.type == A_LAWFUL) && (u.ualign.record > -10)) { - adjalign(-sgn(u.ualign.type)); + } else if (u.ualign.type == A_LAWFUL) { + if (u.ualign.record > -10) + adjalign(-1); You("have violated the sanctity of this grave!"); } - switch (rn2(5)) { + /* -1: force default case for empty grave */ + what_happens = levl[dig_x][dig_y].emptygrave ? -1 : rn2(5); + switch (what_happens) { case 0: case 1: You("unearth a corpse."); if ((otmp = mk_tt_object(CORPSE, dig_x, dig_y)) != 0) - otmp->age -= 100; /* this is an *OLD* corpse */ + otmp->age -= (TAINT_AGE + 1); /* this is an *OLD* corpse */ break; case 2: if (!Blind) - pline(Hallucination ? "Dude! The living dead!" - : "The grave's owner is very upset!"); - (void) makemon(mkclass(S_ZOMBIE, 0), dig_x, dig_y, NO_MM_FLAGS); + pline("%s!", Hallucination ? "Dude! The living dead" + : "The grave's owner is very upset"); + (void) makemon(mkclass(S_ZOMBIE, 0), dig_x, dig_y, MM_NOMSG); break; case 3: if (!Blind) - pline(Hallucination ? "I want my mummy!" - : "You've disturbed a tomb!"); - (void) makemon(mkclass(S_MUMMY, 0), dig_x, dig_y, NO_MM_FLAGS); + pline("%s!", Hallucination ? "I want my mummy" + : "You've disturbed a tomb"); + (void) makemon(mkclass(S_MUMMY, 0), dig_x, dig_y, MM_NOMSG); break; default: /* No corpse */ - pline_The("grave seems unused. Strange...."); + pline_The("grave is unoccupied. Strange..."); break; } - levl[dig_x][dig_y].typ = ROOM, levl[dig_x][dig_y].flags = 0; + levl[dig_x][dig_y].typ = ROOM; + levl[dig_x][dig_y].emptygrave = 0; /* clear 'flags' */ + levl[dig_x][dig_y].disturbed = 0; /* clear 'horizontal' */ del_engr_at(dig_x, dig_y); newsym(dig_x, dig_y); return; } int -use_pick_axe(obj) -struct obj *obj; +use_pick_axe(struct obj *obj) { - const char *sdp, *verb; + const char *verb; char *dsp, dirsyms[12], qbuf[BUFSZ]; boolean ispick; - int rx, ry, downok, res = 0; + int rx, ry, downok, res = ECMD_OK; + int dir; /* Check tool */ if (obj != uwep) { - if (!wield_tool(obj, "swing")) - return 0; - else - res = 1; + if (wield_tool(obj, "swing")) { + /* we're now wielding it. next turn, apply to dig. */ + cmdq_add_ec(CQ_CANNED, doapply); + cmdq_add_key(CQ_CANNED, obj->invlet); + return ECMD_TIME; + } + return ECMD_OK; } ispick = is_pick(obj); verb = ispick ? "dig" : "chop"; @@ -985,13 +1121,14 @@ struct obj *obj; /* construct list of directions to show player for likely choices */ downok = !!can_reach_floor(FALSE); dsp = dirsyms; - for (sdp = Cmd.dirchars; *sdp; ++sdp) { + for (dir = 0; dir < N_DIRS_Z; dir++) { + char dirch = cmd_from_dir(dir, MV_WALK); + /* filter out useless directions */ if (u.uswallow) { ; /* all directions are viable when swallowed */ - } else if (movecmd(*sdp)) { - /* normal direction, within plane of the level map; - movecmd() sets u.dx, u.dy, u.dz and returns !u.dz */ + } else if (movecmd(dirch, MV_WALK)) { + /* normal direction, within plane of the level map */ if (!dxdy_moveok()) continue; /* handle NODIAG */ rx = u.ux + u.dx; @@ -1008,12 +1145,12 @@ struct obj *obj; continue; } /* include this direction */ - *dsp++ = *sdp; + *dsp++ = dirch; } *dsp = 0; Sprintf(qbuf, "In what direction do you want to %s? [%s]", verb, dirsyms); if (!getdir(qbuf)) - return res; + return (res|ECMD_CANCEL); return use_pick_axe2(obj); } @@ -1022,17 +1159,16 @@ struct obj *obj; /* the "In what direction do you want to dig?" query. */ /* use_pick_axe2() uses the existing u.dx, u.dy and u.dz */ int -use_pick_axe2(obj) -struct obj *obj; +use_pick_axe2(struct obj *obj) { - register int rx, ry; - register struct rm *lev; + coordxy rx, ry; + struct rm *lev; struct trap *trap, *trap_with_u; int dig_target; boolean ispick = is_pick(obj); const char *verbing = ispick ? "digging" : "chopping"; - if (u.uswallow && attack(u.ustuck)) { + if (u.uswallow && do_attack(u.ustuck)) { ; /* return 1 */ } else if (Underwater) { pline("Turbulence torpedoes your %s attempts.", verbing); @@ -1051,22 +1187,24 @@ struct obj *obj; You("hit yourself with %s.", yname(uwep)); Sprintf(buf, "%s own %s", uhis(), OBJ_NAME(objects[obj->otyp])); losehp(Maybe_Half_Phys(dam), buf, KILLED_BY); - context.botl = 1; - return 1; + disp.botl = TRUE; + return ECMD_TIME; } else if (u.dz == 0) { - if (Stunned || (Confusion && !rn2(5))) - confdir(); + confdir(FALSE); rx = u.ux + u.dx; ry = u.uy + u.dy; if (!isok(rx, ry)) { + Soundeffect(se_clash, 40); pline("Clash!"); - return 1; + return ECMD_TIME; } lev = &levl[rx][ry]; - if (MON_AT(rx, ry) && attack(m_at(rx, ry))) - return 1; + if (MON_AT(rx, ry) && do_attack(m_at(rx, ry))) + return ECMD_TIME; dig_target = dig_typ(obj, rx, ry); if (dig_target == DIGTYP_UNDIGGABLE) { + struct obj *boulder; + /* ACCESSIBLE or POOL */ trap = t_at(rx, ry); if (trap && trap->ttyp == WEB) { @@ -1078,42 +1216,54 @@ struct obj *obj; /* you ought to be able to let go; tough luck */ /* (maybe `move_into_trap()' would be better) */ nomul(-d(2, 2)); - multi_reason = "stuck in a spider web"; - nomovemsg = "You pull free."; + gm.multi_reason = "stuck in a spider web"; + gn.nomovemsg = "You pull free."; } else if (lev->typ == IRONBARS) { pline("Clang!"); - wake_nearby(); + wake_nearby(FALSE); + } else if (IS_WATERWALL(lev->typ)) { + pline("Splash!"); + } else if (lev->typ == LAVAWALL) { + pline("Splash!"); + (void) fire_damage(uwep, FALSE, rx, ry); } else if (IS_TREE(lev->typ)) { You("need an axe to cut down a tree."); - } else if (IS_ROCK(lev->typ)) { + } else if (IS_OBSTRUCTED(lev->typ)) { You("need a pick to dig rock."); - } else if (!ispick && (sobj_at(STATUE, rx, ry) - || sobj_at(BOULDER, rx, ry))) { - boolean vibrate = !rn2(3); - - pline("Sparks fly as you whack the %s.%s", - sobj_at(STATUE, rx, ry) ? "statue" : "boulder", - vibrate ? " The axe-handle vibrates violently!" : ""); - if (vibrate) - losehp(Maybe_Half_Phys(2), "axing a hard object", - KILLED_BY); + } else if ((boulder = sobj_at(BOULDER, rx, ry)) != 0 + || sobj_at(STATUE, rx, ry)) { + /* if both boulders and statues are present, the topmost + boulder will be shown on the map so treat it as target */ + const char *what = boulder ? "boulder" : "statue"; + + if (!ispick) { + boolean vibrate = !rn2(3); + + pline("Sparks fly as you whack the %s.%s", what, + vibrate ? " The axe-handle vibrates violently!" + : ""); + if (vibrate) + losehp(Maybe_Half_Phys(2), "axing a hard object", + KILLED_BY); + wake_nearby(FALSE); + } else { + /* using a pick but dig_target is DIGTYPE_UNDIGGABLE + and there is at least one boulder or statue or both + present; pick_can_reach() returned false */ + You_cant("reach the %s.", what); + } } else if (u.utrap && u.utraptype == TT_PIT && trap && (trap_with_u = t_at(u.ux, u.uy)) && is_pit(trap->ttyp) && !conjoined_pits(trap, trap_with_u, FALSE)) { - int idx; + int idx = xytodir(u.dx, u.dy); - for (idx = 0; idx < 8; idx++) { - if (xdir[idx] == u.dx && ydir[idx] == u.dy) - break; - } - /* idx is valid if < 8 */ - if (idx < 8) { - int adjidx = (idx + 4) % 8; + if (idx != DIR_ERR) { + int adjidx = DIR_180(idx); trap_with_u->conjoined |= (1 << idx); trap->conjoined |= (1 << adjidx); - pline("You clear some debris from between the pits."); + You("clear some debris from between the pits."); } } else if (u.utrap && u.utraptype == TT_PIT && (trap_with_u = t_at(u.ux, u.uy)) != 0) { @@ -1129,33 +1279,34 @@ struct obj *obj; "chopping at the door", "cutting the tree" }; - did_dig_msg = FALSE; - context.digging.quiet = FALSE; - if (context.digging.pos.x != rx || context.digging.pos.y != ry - || !on_level(&context.digging.level, &u.uz) - || context.digging.down) { + gd.did_dig_msg = FALSE; + svc.context.digging.quiet = FALSE; + if (svc.context.digging.pos.x != rx + || svc.context.digging.pos.y != ry + || !on_level(&svc.context.digging.level, &u.uz) + || svc.context.digging.down) { if (flags.autodig && dig_target == DIGTYP_ROCK - && !context.digging.down - && context.digging.pos.x == u.ux - && context.digging.pos.y == u.uy - && (moves <= context.digging.lastdigtime + 2 - && moves >= context.digging.lastdigtime)) { + && !svc.context.digging.down + && u_at(svc.context.digging.pos.x, + svc.context.digging.pos.y) + && (svm.moves <= svc.context.digging.lastdigtime + 2 + && svm.moves >= svc.context.digging.lastdigtime)) { /* avoid messages if repeated autodigging */ - did_dig_msg = TRUE; - context.digging.quiet = TRUE; + gd.did_dig_msg = TRUE; + svc.context.digging.quiet = TRUE; } - context.digging.down = context.digging.chew = FALSE; - context.digging.warned = FALSE; - context.digging.pos.x = rx; - context.digging.pos.y = ry; - assign_level(&context.digging.level, &u.uz); - context.digging.effort = 0; - if (!context.digging.quiet) + svc.context.digging.down = svc.context.digging.chew = FALSE; + svc.context.digging.warned = FALSE; + svc.context.digging.pos.x = rx; + svc.context.digging.pos.y = ry; + assign_level(&svc.context.digging.level, &u.uz); + svc.context.digging.effort = 0; + if (!svc.context.digging.quiet) You("start %s.", d_action[dig_target]); } else { - You("%s %s.", context.digging.chew ? "begin" : "continue", + You("%s %s.", svc.context.digging.chew ? "begin" : "continue", d_action[dig_target]); - context.digging.chew = FALSE; + svc.context.digging.chew = FALSE; } set_occupation(dig, verbing, 0); } @@ -1163,7 +1314,7 @@ struct obj *obj; /* it must be air -- water checked above */ You("swing %s through thin air.", yobjnam(obj, (char *) 0)); } else if (!can_reach_floor(FALSE)) { - cant_reach_floor(u.ux, u.uy, FALSE, FALSE); + cant_reach_floor(u.ux, u.uy, FALSE, FALSE, FALSE); } else if (is_pool_or_lava(u.ux, u.uy)) { /* Monsters which swim also happen not to be able to dig */ You("cannot stay under%s long enough.", @@ -1173,7 +1324,7 @@ struct obj *obj; dotrap(trap, FORCEBUNGLE); /* might escape trap and still be teetering at brink */ if (!u.utrap) - cant_reach_floor(u.ux, u.uy, FALSE, TRUE); + cant_reach_floor(u.ux, u.uy, FALSE, TRUE, FALSE); } else if (!ispick /* can only dig down with an axe when doing so will trigger or disarm a trap here */ @@ -1183,25 +1334,37 @@ struct obj *obj; surface(u.ux, u.uy)); u_wipe_engr(3); } else { - if (context.digging.pos.x != u.ux || context.digging.pos.y != u.uy - || !on_level(&context.digging.level, &u.uz) - || !context.digging.down) { - context.digging.chew = FALSE; - context.digging.down = TRUE; - context.digging.warned = FALSE; - context.digging.pos.x = u.ux; - context.digging.pos.y = u.uy; - assign_level(&context.digging.level, &u.uz); - context.digging.effort = 0; + if (svc.context.digging.pos.x != u.ux + || svc.context.digging.pos.y != u.uy + || !on_level(&svc.context.digging.level, &u.uz) + || !svc.context.digging.down) { + svc.context.digging.chew = FALSE; + svc.context.digging.down = TRUE; + svc.context.digging.warned = FALSE; + svc.context.digging.pos.x = u.ux; + svc.context.digging.pos.y = u.uy; + assign_level(&svc.context.digging.level, &u.uz); + svc.context.digging.effort = 0; You("start %s downward.", verbing); - if (*u.ushops) + if (*u.ushops) { shopdig(0); + add_damage(u.ux, u.uy, SHOP_PIT_COST); + } } else You("continue %s downward.", verbing); - did_dig_msg = FALSE; + gd.did_dig_msg = FALSE; set_occupation(dig, verbing, 0); } - return 1; + return ECMD_TIME; +} + +staticfn boolean +watchman_canseeu(struct monst *mtmp) +{ + if (is_watch(mtmp->data) && mtmp->mcansee && m_canseeu(mtmp) + && mtmp->mpeaceful) + return TRUE; + return FALSE; } /* @@ -1211,28 +1374,19 @@ struct obj *obj; * zap == TRUE if wand/spell of digging, FALSE otherwise (chewing) */ void -watch_dig(mtmp, x, y, zap) -struct monst *mtmp; -xchar x, y; -boolean zap; +watch_dig(struct monst *mtmp, coordxy x, coordxy y, boolean zap) { struct rm *lev = &levl[x][y]; if (in_town(x, y) && (closed_door(x, y) || lev->typ == SDOOR || IS_WALL(lev->typ) || IS_FOUNTAIN(lev->typ) || IS_TREE(lev->typ))) { - if (!mtmp) { - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) - continue; - if (is_watch(mtmp->data) && mtmp->mcansee && m_canseeu(mtmp) - && couldsee(mtmp->mx, mtmp->my) && mtmp->mpeaceful) - break; - } - } + if (!mtmp) + mtmp = get_iter_mons(watchman_canseeu); if (mtmp) { - if (zap || context.digging.warned) { + SetVoice(mtmp, 0, 80, 0); + if (zap || svc.context.digging.warned) { verbalize("Halt, vandal! You're under arrest!"); (void) angry_guards(!!Deaf); } else { @@ -1242,12 +1396,12 @@ boolean zap; str = "door"; else if (IS_TREE(lev->typ)) str = "tree"; - else if (IS_ROCK(lev->typ)) + else if (IS_OBSTRUCTED(lev->typ)) str = "wall"; else str = "fountain"; verbalize("Hey, stop damaging that %s!", str); - context.digging.warned = TRUE; + svc.context.digging.warned = TRUE; } if (is_digging()) stop_occupation(); @@ -1257,10 +1411,10 @@ boolean zap; /* Return TRUE if monster died, FALSE otherwise. Called from m_move(). */ boolean -mdig_tunnel(mtmp) -register struct monst *mtmp; +mdig_tunnel(struct monst *mtmp) { - register struct rm *here; + struct rm *here; + boolean sawit, seeit, trapped; int pile = rnd(12); here = &levl[mtmp->mx][mtmp->my]; @@ -1271,19 +1425,24 @@ register struct monst *mtmp; if (closed_door(mtmp->mx, mtmp->my)) { if (*in_rooms(mtmp->mx, mtmp->my, SHOPBASE)) add_damage(mtmp->mx, mtmp->my, 0L); - unblock_point(mtmp->mx, mtmp->my); /* vision */ - if (here->doormask & D_TRAPPED) { - here->doormask = D_NODOOR; - if (mb_trapped(mtmp)) { /* mtmp is killed */ + /* sawit: closed door location is more visible than an open one */ + sawit = canseemon(mtmp); /* before door state change and unblock_pt */ + trapped = (here->doormask & D_TRAPPED) ? TRUE : FALSE; + here->doormask = trapped ? D_NODOOR : D_BROKEN; + recalc_block_point(mtmp->mx, mtmp->my); /* vision */ + newsym(mtmp->mx, mtmp->my); + if (trapped) { + seeit = canseemon(mtmp); + if (mb_trapped(mtmp, sawit || seeit)) { /* mtmp is killed */ newsym(mtmp->mx, mtmp->my); return TRUE; } } else { - if (!rn2(3) && flags.verbose) /* not too often.. */ - draft_message(TRUE); /* "You feel an unexpected draft." */ - here->doormask = D_BROKEN; + if (flags.verbose) { + if (!Unaware && !rn2(3)) /* not too often.. */ + draft_message(TRUE); /* "You feel an unexpected draft." */ + } } - newsym(mtmp->mx, mtmp->my); return FALSE; } else if (here->typ == SCORR) { here->typ = CORR, here->flags = 0; @@ -1291,7 +1450,7 @@ register struct monst *mtmp; newsym(mtmp->mx, mtmp->my); draft_message(FALSE); /* "You feel a draft." */ return FALSE; - } else if (!IS_ROCK(here->typ) && !IS_TREE(here->typ)) { /* no dig */ + } else if (!IS_OBSTRUCTED(here->typ) && !IS_TREE(here->typ)) { /* no dig */ return FALSE; } @@ -1306,13 +1465,15 @@ register struct monst *mtmp; if (IS_WALL(here->typ)) { /* KMH -- Okay on arboreal levels (room walls are still stone) */ - if (flags.verbose && !rn2(5)) + if (flags.verbose && !rn2(5)) { + Soundeffect(se_crashing_rock, 75); You_hear("crashing rock."); + } if (*in_rooms(mtmp->mx, mtmp->my, SHOPBASE)) add_damage(mtmp->mx, mtmp->my, 0L); - if (level.flags.is_maze_lev) { + if (svl.level.flags.is_maze_lev) { here->typ = ROOM, here->flags = 0; - } else if (level.flags.is_cavernous_lev + } else if (svl.level.flags.is_cavernous_lev && !in_town(mtmp->mx, mtmp->my)) { here->typ = CORR, here->flags = 0; } else { @@ -1340,8 +1501,7 @@ register struct monst *mtmp; /* draft refers to air currents, but can be a pun on "draft" as conscription for military service (probably not a good pun if it has to be explained) */ void -draft_message(unexpected) -boolean unexpected; +draft_message(boolean unexpected) { /* * [Bug or TODO? Have caller pass coordinates and use the travel @@ -1368,7 +1528,7 @@ boolean unexpected; } else { /* "marching" is deliberately ambiguous; it might mean drills after entering military service or mean engaging in protests */ - static const char *draft_reaction[] = { + static const char *const draft_reaction[] = { "enlisting", "marching", "protesting", "fleeing", }; int dridx; @@ -1385,13 +1545,14 @@ boolean unexpected; /* digging via wand zap or spell cast */ void -zap_dig() +zap_dig(void) { struct rm *room; struct monst *mtmp; struct obj *otmp; struct trap *trap_with_u = (struct trap *) 0; - int zx, zy, diridx = 8, digdepth, flow_x = -1, flow_y = -1; + coordxy zx, zy, flow_x = -1, flow_y = -1; + int diridx = 8, digdepth; boolean shopdoor, shopwall, maze_dig, pitdig = FALSE, pitflow = FALSE; /* @@ -1408,11 +1569,14 @@ zap_dig() mtmp = u.ustuck; if (!is_whirly(mtmp->data)) { - if (is_animal(mtmp->data)) + if (digests(mtmp->data)) You("pierce %s %s wall!", s_suffix(mon_nam(mtmp)), mbodypart(mtmp, STOMACH)); - mtmp->mhp = 1; /* almost dead */ - expels(mtmp, mtmp->data, !is_animal(mtmp->data)); + if (unique_corpstat(mtmp->data)) + mtmp->mhp = (mtmp->mhp + 1) / 2; + else + mtmp->mhp = 1; /* almost dead */ + expels(mtmp, mtmp->data, !digests(mtmp->data)); } return; } /* swallowed */ @@ -1421,15 +1585,15 @@ zap_dig() if (!Is_airlevel(&u.uz) && !Is_waterlevel(&u.uz) && !Underwater) { if (u.dz < 0 || On_stairs(u.ux, u.uy)) { int dmg; - if (On_stairs(u.ux, u.uy)) + if (On_stairs(u.ux, u.uy)) { + stairway *stway = stairway_at(u.ux, u.uy); pline_The("beam bounces off the %s and hits the %s.", - (u.ux == xdnladder || u.ux == xupladder) - ? "ladder" - : "stairs", + stway->isladder ? "ladder" : "stairs", ceiling(u.ux, u.uy)); + } You("loosen a rock from the %s.", ceiling(u.ux, u.uy)); pline("It falls on your %s!", body_part(HEAD)); - dmg = rnd((uarmh && is_metallic(uarmh)) ? 2 : 6); + dmg = rnd(hard_helmet(uarmh) ? 2 : 6); losehp(Maybe_Half_Phys(dmg), "falling rock", KILLED_BY_AN); otmp = mksobj_at(ROCK, u.ux, u.uy, FALSE, FALSE); if (otmp) { @@ -1447,17 +1611,13 @@ zap_dig() /* normal case: digging across the level */ shopdoor = shopwall = FALSE; - maze_dig = level.flags.is_maze_lev && !Is_earthlevel(&u.uz); + maze_dig = svl.level.flags.is_maze_lev && !Is_earthlevel(&u.uz); zx = u.ux + u.dx; zy = u.uy + u.dy; if (u.utrap && u.utraptype == TT_PIT && (trap_with_u = t_at(u.ux, u.uy))) { pitdig = TRUE; - for (diridx = 0; diridx < 8; diridx++) { - if (xdir[diridx] == u.dx && ydir[diridx] == u.dy) - break; - /* diridx is valid if < 8 */ - } + diridx = xytodir(u.dx, u.dy); } digdepth = rn1(18, 8); tmp_at(DISP_BEAM, cmap_to_glyph(S_digbeam)); @@ -1466,15 +1626,19 @@ zap_dig() break; room = &levl[zx][zy]; tmp_at(zx, zy); - delay_output(); /* wait a little bit */ + nh_delay_output(); /* wait a little bit */ if (pitdig) { /* we are already in a pit if this is true */ coord cc; struct trap *adjpit = t_at(zx, zy); - if ((diridx < 8) && !conjoined_pits(adjpit, trap_with_u, FALSE)) { + + if (diridx != DIR_ERR + && !conjoined_pits(adjpit, trap_with_u, FALSE)) { digdepth = 0; /* limited to the adjacent location only */ + nhUse(digdepth); if (!(adjpit && is_pit(adjpit->ttyp))) { char buf[BUFSZ]; + cc.x = zx; cc.y = zy; if (!adj_pit_checks(&cc, buf)) { @@ -1486,9 +1650,9 @@ zap_dig() adjpit = t_at(zx, zy); } } - if (adjpit - && is_pit(adjpit->ttyp)) { - int adjidx = (diridx + 4) % 8; + if (adjpit && is_pit(adjpit->ttyp)) { + int adjidx = DIR_180(diridx); + trap_with_u->conjoined |= (1 << diridx); adjpit->conjoined |= (1 << adjidx); flow_x = zx; @@ -1513,7 +1677,7 @@ zap_dig() pline_The("door is razed!"); watch_dig((struct monst *) 0, zx, zy, TRUE); room->doormask = D_NODOOR; - unblock_point(zx, zy); /* vision */ + recalc_block_point(zx, zy); /* vision */ digdepth -= 2; if (maze_dig) break; @@ -1544,7 +1708,7 @@ zap_dig() pline_The("rock glows then fades."); break; } - } else if (IS_ROCK(room->typ)) { + } else if (IS_OBSTRUCTED(room->typ)) { if (!may_dig(zx, zy)) break; if (IS_WALL(room->typ) || room->typ == SDOOR) { @@ -1553,7 +1717,7 @@ zap_dig() shopwall = TRUE; } watch_dig((struct monst *) 0, zx, zy, TRUE); - if (level.flags.is_cavernous_lev && !in_town(zx, zy)) { + if (svl.level.flags.is_cavernous_lev && !in_town(zx, zy)) { room->typ = CORR, room->flags = 0; } else { room->typ = DOOR, room->doormask = D_NODOOR; @@ -1562,7 +1726,7 @@ zap_dig() } else if (IS_TREE(room->typ)) { room->typ = ROOM, room->flags = 0; digdepth -= 2; - } else { /* IS_ROCK but not IS_WALL or SDOOR */ + } else { /* IS_OBSTRUCTED but not IS_WALL or SDOOR */ room->typ = CORR, room->flags = 0; digdepth--; } @@ -1595,10 +1759,8 @@ zap_dig() * you're zapping a wand of digging laterally while * down in the pit. */ -STATIC_OVL int -adj_pit_checks(cc, msg) -coord *cc; -char *msg; +staticfn int +adj_pit_checks(coord *cc, char *msg) { int ltyp; struct rm *room; @@ -1637,6 +1799,7 @@ char *msg; } else if (ltyp == IRONBARS) { /* "set of iron bars" */ Strcpy(msg, "The bars go much deeper than your pit."); + return FALSE; #if 0 } else if (is_lava(cc->x, cc->y)) { } else if (is_ice(cc->x, cc->y)) { @@ -1646,8 +1809,7 @@ char *msg; } else if (IS_SINK(ltyp)) { Strcpy(msg, "A tangled mass of plumbing remains below the sink."); return FALSE; - } else if ((cc->x == xupladder && cc->y == yupladder) /* ladder up */ - || (cc->x == xdnladder && cc->y == ydnladder)) { /* " down */ + } else if (On_ladder(cc->x, cc->y)) { Strcpy(msg, "The ladder is unaffected."); return FALSE; } else { @@ -1659,24 +1821,16 @@ char *msg; supporting = "throne"; else if (IS_ALTAR(ltyp)) supporting = "altar"; - else if ((cc->x == xupstair && cc->y == yupstair) - || (cc->x == sstairs.sx && cc->y == sstairs.sy - && sstairs.up)) - /* "staircase up" */ - supporting = "stairs"; - else if ((cc->x == xdnstair && cc->y == ydnstair) - || (cc->x == sstairs.sx && cc->y == sstairs.sy - && !sstairs.up)) - /* "staircase down" */ + else if (On_stairs(cc->x, cc->y)) + /* staircase up or down. On_ladder handled above. */ supporting = "stairs"; else if (ltyp == DRAWBRIDGE_DOWN /* "lowered drawbridge" */ || ltyp == DBWALL) /* "raised drawbridge" */ supporting = "drawbridge"; if (supporting) { - Sprintf(msg, "The %s%ssupporting structures remain intact.", - supporting ? s_suffix(supporting) : "", - supporting ? " " : ""); + Sprintf(msg, "The %s supporting structures remain intact.", + s_suffix(supporting)); return FALSE; } } @@ -1686,11 +1840,15 @@ char *msg; /* * Ensure that all conjoined pits fill up. */ -STATIC_OVL void -pit_flow(trap, filltyp) -struct trap *trap; -schar filltyp; +staticfn void +pit_flow(struct trap *trap, schar filltyp) { + /* + * FIXME? + * liquid_flow() -> pooleffects() -> {drown(),lava_effects()} + * might kill the hero; the game will end and if that leaves bones, + * remaining conjoined pits will be left unprocessed. + */ if (trap && filltyp != ROOM && is_pit(trap->ttyp)) { struct trap t; int idx; @@ -1698,12 +1856,12 @@ schar filltyp; t = *trap; levl[t.tx][t.ty].typ = filltyp, levl[t.tx][t.ty].flags = 0; liquid_flow(t.tx, t.ty, filltyp, trap, - (t.tx == u.ux && t.ty == u.uy) + u_at(t.tx, t.ty) ? "Suddenly %s flows in from the adjacent pit!" : (char *) 0); - for (idx = 0; idx < 8; ++idx) { + for (idx = 0; idx < N_DIRS; ++idx) { if (t.conjoined & (1 << idx)) { - int x, y; + coordxy x, y; struct trap *t2; x = t.tx + xdir[idx]; @@ -1714,7 +1872,7 @@ schar filltyp; * called deltrap() which cleaned up the * conjoined fields on both pits. */ - if (t2 && (t2->conjoined & (1 << ((idx + 4) % 8)))) + if (t2 && (t2->conjoined & (1 << DIR_180(idx)))) #endif /* recursion */ pit_flow(t2, filltyp); @@ -1724,8 +1882,7 @@ schar filltyp; } struct obj * -buried_ball(cc) -coord *cc; +buried_ball(coord *cc) { int odist, bdist = COLNO; struct obj *otmp, *ball = 0; @@ -1735,7 +1892,7 @@ coord *cc; * criterium (within 2 steps of tethered hero's present location) * it will find an arbitrary one rather than the one which used * to be uball. Once 3.6.{0,1} save file compatibility is broken, - * we should add context.buriedball_oid and then we can find the + * we should add svc.context.buriedball_oid and then we can find the * actual former uball, which might be extra heavy or christened * or not the one buried directly underneath the target spot. * @@ -1743,8 +1900,11 @@ coord *cc; * only lets hero get one step away from the buried ball?] */ - if (u.utrap && u.utraptype == TT_BURIEDBALL) - for (otmp = level.buriedobjlist; otmp; otmp = otmp->nobj) { + /* u.utrap might have already been cleared, in which case the value + of u.utraptype is no longer meaningful; if u.utrap is still set + then u.utraptype needs to be for buried ball */ + if (!u.utrap || u.utraptype == TT_BURIEDBALL) { + for (otmp = svl.level.buriedobjlist; otmp; otmp = otmp->nobj) { if (otmp->otyp != HEAVY_IRON_BALL) continue; /* if found at the target spot, we're done */ @@ -1762,6 +1922,7 @@ coord *cc; bdist = odist; } } + } if (ball) { /* found, but not at < cc->x, cc->y > */ cc->x = ball->ox; @@ -1771,7 +1932,7 @@ coord *cc; } void -buried_ball_to_punishment() +buried_ball_to_punishment(void) { coord cc; struct obj *ball; @@ -1794,7 +1955,7 @@ buried_ball_to_punishment() } void -buried_ball_to_freedom() +buried_ball_to_freedom(void) { coord cc; struct obj *ball; @@ -1820,9 +1981,7 @@ buried_ball_to_freedom() /* move objects from fobj/nexthere lists to buriedobjlist, keeping position information */ struct obj * -bury_an_obj(otmp, dealloced) -struct obj *otmp; -boolean *dealloced; +bury_an_obj(struct obj *otmp, boolean *dealloced) { struct obj *otmp2; boolean under_ice; @@ -1832,8 +1991,7 @@ boolean *dealloced; *dealloced = FALSE; if (otmp == uball) { unpunish(); - u.utrap = rn1(50, 20); - u.utraptype = TT_BURIEDBALL; + set_utrap((unsigned) rn1(50, 20), TT_BURIEDBALL); pline_The("iron ball gets buried!"); } /* after unpunish(), or might get deallocated chain */ @@ -1858,8 +2016,8 @@ boolean *dealloced; obj_extract_self(otmp); under_ice = is_ice(otmp->ox, otmp->oy); - if (otmp->otyp == ROCK && !under_ice) { - /* merges into burying material */ + if ((otmp->otyp == ROCK && !under_ice) || otmp->otyp == BOULDER) { + /* merges into burying material; boulder removal is for #wizbury */ if (dealloced) *dealloced = TRUE; obfree(otmp, (struct obj *) 0); @@ -1871,7 +2029,7 @@ boolean *dealloced; */ if (otmp->otyp == CORPSE) { ; /* should cancel timer if under_ice */ - } else if ((under_ice ? otmp->oclass == POTION_CLASS : is_organic(otmp)) + } else if ((under_ice ? (otmp->oclass == POTION_CLASS) : is_organic(otmp)) && !obj_resists(otmp, 5, 95)) { (void) start_timer((under_ice ? 0L : 250L) + (long) rnd(250), TIMER_OBJECT, ROT_ORGANIC, obj_to_any(otmp)); @@ -1889,8 +2047,7 @@ boolean *dealloced; } void -bury_objs(x, y) -int x, y; +bury_objs(int x, int y) { struct obj *otmp, *otmp2; struct monst *shkp; @@ -1900,11 +2057,11 @@ int x, y; costly = ((shkp = shop_keeper(*in_rooms(x, y, SHOPBASE))) && costly_spot(x, y)); - if (level.objects[x][y] != (struct obj *) 0) { + if (svl.level.objects[x][y] != (struct obj *) 0) { debugpline2("bury_objs: at <%d,%d>", x, y); } - for (otmp = level.objects[x][y]; otmp; otmp = otmp2) { - if (costly) { + for (otmp = svl.level.objects[x][y]; otmp; otmp = otmp2) { + if (costly && !svc.context.mon_moving) { loss += stolen_value(otmp, x, y, (boolean) shkp->mpeaceful, TRUE); if (otmp->oclass != COIN_CLASS) otmp->no_charge = 1; @@ -1915,17 +2072,18 @@ int x, y; /* don't expect any engravings here, but just in case */ del_engr_at(x, y); newsym(x, y); + maybe_unhide_at(x, y); if (costly && loss) { - You("owe %s %ld %s for burying merchandise.", mon_nam(shkp), loss, + You("owe %s %ld %s for burying merchandise.", shkname(shkp), loss, currency(loss)); } } -/* move objects from buriedobjlist to fobj/nexthere lists */ +/* move objects from buriedobjlist to fobj/nexthere lists; if caller + converts terrain from ice to something, it should call obj_ice_effects() */ void -unearth_objs(x, y) -int x, y; +unearth_objs(int x, int y) { struct obj *otmp, *otmp2, *bball; coord cc; @@ -1934,7 +2092,7 @@ int x, y; cc.x = x; cc.y = y; bball = buried_ball(&cc); - for (otmp = level.buriedobjlist; otmp; otmp = otmp2) { + for (otmp = svl.level.buriedobjlist; otmp; otmp = otmp2) { otmp2 = otmp->nobj; if (otmp->ox == x && otmp->oy == y) { if (bball && otmp == bball @@ -1955,7 +2113,7 @@ int x, y; /* * The organic material has rotted away while buried. As an expansion, - * we could add add partial damage. A damage count is kept in the object + * we could add partial damage. A damage count is kept in the object * and every time we are called we increment the count and reschedule another * timeout. Eventually the object rots away. * @@ -1964,9 +2122,7 @@ int x, y; */ /* ARGSUSED */ void -rot_organic(arg, timeout) -anything *arg; -long timeout UNUSED; +rot_organic(anything *arg, long timeout UNUSED) { struct obj *obj = arg->a_obj; @@ -1987,11 +2143,9 @@ long timeout UNUSED; * Called when a corpse has rotted completely away. */ void -rot_corpse(arg, timeout) -anything *arg; -long timeout; +rot_corpse(anything *arg, long timeout) { - xchar x = 0, y = 0; + coordxy x = 0, y = 0; struct obj *obj = arg->a_obj; boolean on_floor = obj->where == OBJ_FLOOR, in_invent = obj->where == OBJ_INVENT; @@ -2006,19 +2160,13 @@ long timeout; Your("%s%s %s away%c", obj == uwep ? "wielded " : "", cname, otense(obj, "rot"), obj == uwep ? '!' : '.'); } - if (obj == uwep) { - uwepgone(); /* now bare handed */ - stop_occupation(); - } else if (obj == uswapwep) { - uswapwepgone(); - stop_occupation(); - } else if (obj == uquiver) { - uqwepgone(); + if (obj->owornmask) { + remove_worn_item(obj, TRUE); stop_occupation(); } - } else if (obj->where == OBJ_MINVENT && obj->owornmask) { - if (obj == MON_WEP(obj->ocarry)) - setmnotwielded(obj->ocarry, obj); + } else if (obj->where == OBJ_MINVENT) { + if (obj->owornmask && obj == MON_WEP(obj->ocarry)) + setmnotwielded(obj->ocarry, obj); /* clears owornmask */ } else if (obj->where == OBJ_MIGRATING) { /* clear destination flag so that obfree()'s check for freeing a worn object doesn't get a false hit */ @@ -2032,8 +2180,9 @@ long timeout; if (mtmp && !OBJ_AT(x, y) && mtmp->mundetected && hides_under(mtmp->data)) { mtmp->mundetected = 0; - } else if (x == u.ux && y == u.uy && u.uundetected && hides_under(youmonst.data)) - (void) hideunder(&youmonst); + } else if (u_at(x, y) + && u.uundetected && hides_under(gy.youmonst.data)) + (void) hideunder(&gy.youmonst); newsym(x, y); } else if (in_invent) update_inventory(); @@ -2041,8 +2190,7 @@ long timeout; #if 0 void -bury_monst(mtmp) -struct monst *mtmp; +bury_monst(struct monst *mtmp) { debugpline1("bury_monst: %s", mon_nam(mtmp)); if (canseemon(mtmp)) { @@ -2061,7 +2209,7 @@ struct monst *mtmp; } void -bury_you() +bury_you(void) { debugpline0("bury_you"); if (!Levitation && !Flying) { @@ -2079,7 +2227,7 @@ bury_you() } void -unearth_you() +unearth_you(void) { debugpline0("unearth_you"); u.uburied = FALSE; @@ -2090,31 +2238,31 @@ unearth_you() } void -escape_tomb() +escape_tomb(void) { debugpline0("escape_tomb"); - if ((Teleportation || can_teleport(youmonst.data)) + if ((Teleportation || can_teleport(gy.youmonst.data)) && (Teleport_control || rn2(3) < Luck+2)) { You("attempt a teleport spell."); (void) dotele(FALSE); /* calls unearth_you() */ } else if (u.uburied) { /* still buried after 'port attempt */ boolean good; - if (amorphous(youmonst.data) || Passes_walls - || noncorporeal(youmonst.data) - || (unsolid(youmonst.data) - && youmonst.data != &mons[PM_WATER_ELEMENTAL]) - || (tunnels(youmonst.data) && !needspick(youmonst.data))) { + if (amorphous(gy.youmonst.data) || Passes_walls + || noncorporeal(gy.youmonst.data) + || (unsolid(gy.youmonst.data) + && gy.youmonst.data != &mons[PM_WATER_ELEMENTAL]) + || (tunnels(gy.youmonst.data) && !needspick(gy.youmonst.data))) { You("%s up through the %s.", - (tunnels(youmonst.data) && !needspick(youmonst.data)) + (tunnels(gy.youmonst.data) && !needspick(gy.youmonst.data)) ? "try to tunnel" - : (amorphous(youmonst.data)) + : (amorphous(gy.youmonst.data)) ? "ooze" : "phase", surface(u.ux, u.uy)); - good = (tunnels(youmonst.data) && !needspick(youmonst.data)) - ? dighole(TRUE, FALSE, (coord *)0) : TRUE; + good = (tunnels(gy.youmonst.data) && !needspick(gy.youmonst.data)) + ? dighole(TRUE, FALSE, (coord *) 0) : TRUE; if (good) unearth_you(); } @@ -2135,18 +2283,47 @@ struct obj *otmp; #endif /*0*/ #ifdef DEBUG -/* bury everything at your loc and around */ +/* the #wizbury command - bury everything at your loc and around */ int -wiz_debug_cmd_bury() +wiz_debug_cmd_bury(void) { - int x, y; + struct obj *otmp; + int x, y, before = 0, after = 0, diff; for (x = u.ux - 1; x <= u.ux + 1; x++) - for (y = u.uy - 1; y <= u.uy + 1; y++) - if (isok(x, y)) - bury_objs(x, y); - return 0; + for (y = u.uy - 1; y <= u.uy + 1; y++) { + if (!isok(x, y)) + continue; + for (otmp = svl.level.objects[x][y]; otmp; otmp = otmp->nexthere) + ++before; + + bury_objs(x, y); + + for (otmp = svl.level.objects[x][y]; otmp; otmp = otmp->nexthere) + ++after; + } + + diff = before - after; + if (before == 0) + /* there was nothing here */ + pline("No objects here or adjacent to bury."); + else if (diff == 0) + /* before and after will be the same if only unburiable objects are + present (The Amulet, invocation items, Rider corpses, uchain when + uball doesn't get buried: carried or floor beyond burial range) */ + pline("No objects buried."); + else + /* usual case; if uball got buried, uchain went away and won't be + counted as buried */ + pline("%d object%s buried.", diff, plur(diff)); + return ECMD_OK; } #endif /* DEBUG */ +#undef BY_YOU +#undef BY_OBJECT +/* for 'onefile' testing, leave STRIDENT defined so that the other instance + of it in pray.c will trigger a complaint if someone changes its value */ +/*#undef STRIDENT*/ + /*dig.c*/ diff --git a/src/display.c b/src/display.c index 722c25818..6f4f5f74a 100644 --- a/src/display.c +++ b/src/display.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 display.c $NHDT-Date: 1574882660 2019/11/27 19:24:20 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.108 $ */ +/* NetHack 5.0 display.c $NHDT-Date: 1777000050 2026/04/23 19:07:30 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.263 $ */ /* Copyright (c) Dean Luick, with acknowledgements to Kevin Darcy */ /* and Dave Cohrs, 1990. */ /* NetHack may be freely redistributed. See license for details. */ @@ -15,7 +15,7 @@ * what to draw at a given location. The routines for the vision system * can be found in vision.c and vision.h. The routines for display can * be found in this file (display.c) and display.h. The drawing routines - * are part of the window port. See doc/window.doc for the drawing + * are part of the window port. See doc/window.txt for the drawing * interface. * * The display system deals with an abstraction called a glyph. Anything @@ -60,7 +60,7 @@ * * map_background * map_object - * map_trap + * map_trap or map_engraving * map_invisible * unmap_object * @@ -123,30 +123,105 @@ */ #include "hack.h" -STATIC_DCL void FDECL(show_mon_or_warn, (int, int, int)); -STATIC_DCL void FDECL(display_monster, - (XCHAR_P, XCHAR_P, struct monst *, int, XCHAR_P)); -STATIC_DCL int FDECL(swallow_to_glyph, (int, int)); -STATIC_DCL void FDECL(display_warning, (struct monst *)); - -STATIC_DCL int FDECL(check_pos, (int, int, int)); -STATIC_DCL int FDECL(get_bk_glyph, (XCHAR_P, XCHAR_P)); -STATIC_DCL int FDECL(tether_glyph, (int, int)); +staticfn void show_mon_or_warn(coordxy, coordxy, int); +staticfn void display_monster(coordxy, coordxy, + struct monst *, int, boolean) NONNULLPTRS; +staticfn int swallow_to_glyph(int, int); +staticfn void display_warning(struct monst *) NONNULLARG1; +staticfn boolean mon_overrides_region(struct monst *, coordxy, coordxy); +staticfn int check_pos(coordxy, coordxy, int); +staticfn void get_bkglyph_and_framecolor(coordxy x, coordxy y, int *, + uint32 *); +staticfn int tether_glyph(coordxy, coordxy); +staticfn void mimic_light_blocking(struct monst *) NONNULLARG1; /*#define WA_VERBOSE*/ /* give (x,y) locations for all "bad" spots */ #ifdef WA_VERBOSE -STATIC_DCL boolean FDECL(more_than_one, (int, int, int, int, int)); +staticfn boolean more_than_one(coordxy, coordxy, coordxy, coordxy, coordxy); #endif -STATIC_DCL int FDECL(set_twall, (int, int, int, int, int, int, int, int)); -STATIC_DCL int FDECL(set_wall, (int, int, int)); -STATIC_DCL int FDECL(set_corn, (int, int, int, int, int, int, int, int)); -STATIC_DCL int FDECL(set_crosswall, (int, int)); -STATIC_DCL void FDECL(set_seenv, (struct rm *, int, int, int, int)); -STATIC_DCL void FDECL(t_warn, (struct rm *)); -STATIC_DCL int FDECL(wall_angle, (struct rm *)); +staticfn int set_twall(coordxy, coordxy, coordxy, coordxy, + coordxy, coordxy, coordxy, coordxy); +staticfn int set_wall(coordxy, coordxy, int); +staticfn int set_corn(coordxy, coordxy, coordxy, coordxy, + coordxy, coordxy, coordxy, coordxy); +staticfn int set_crosswall(coordxy, coordxy); +staticfn void set_seenv(struct rm *, coordxy, coordxy, coordxy, coordxy); +staticfn void t_warn(struct rm *); +staticfn int wall_angle(struct rm *); + +#define _glyph_at(x, y) gg.gbuf[y][x].glyphinfo.glyph + +/* + * See display.h for descriptions of tp_sensemon() through + * is_safemon(). Some of these were generating an awful lot of + * code "behind the curtain", particularly canspotmon() (which is + * still a macro but one that now expands to a pair of function + * calls rather than to a ton of special case checks). Return + * values are all int 0 or 1, not boolean. + * + * They're still implemented as macros within this file. + */ +int +tp_sensemon(struct monst *mon) +{ + return _tp_sensemon(mon); +} +#define tp_sensemon(mon) _tp_sensemon(mon) + +int +sensemon(struct monst *mon) +{ + return _sensemon(mon); +} +#define sensemon(mon) _sensemon(mon) + +int +mon_warning(struct monst *mon) +{ + return _mon_warning(mon); +} +#define mon_warning(mon) _mon_warning(mon) + +int +mon_visible(struct monst *mon) +{ + return _mon_visible(mon); +} +#define mon_visible(mon) _mon_visible(mon) + +int +see_with_infrared(struct monst *mon) +{ + return _see_with_infrared(mon); +} +#define see_with_infrared(mon) _see_with_infrared(mon) + +int +canseemon(struct monst *mon) +{ + return _canseemon(mon); +} +#define canseemon(mon) _canseemon(mon) + +int +knowninvisible(struct monst *mon) +{ + return _knowninvisible(mon); +} +/* #define knowninvisible() isn't useful here */ + +int +is_safemon(struct monst *mon) +{ + return _is_safemon(mon); +} +/* #define is_safemon() isn't useful here */ -#define remember_topology(x, y) (lastseentyp[x][y] = levl[x][y].typ) +/* + * End of former macro-only vision related (mostly) routines + * converted to functions. + */ /* * magic_map_background() @@ -155,9 +230,7 @@ STATIC_DCL int FDECL(wall_angle, (struct rm *)); * attention to and correct unexplored, lit ROOM and CORR spots. */ void -magic_map_background(x, y, show) -xchar x, y; -int show; +magic_map_background(coordxy x, coordxy y, int show) { int glyph = back_to_glyph(x, y); /* assumes hero can see x,y */ struct rm *lev = &levl[x][y]; @@ -169,18 +242,19 @@ int show; if (!cansee(x, y) && !lev->waslit) { /* Floor spaces are dark if unlit. Corridors are dark if unlit. */ if (lev->typ == ROOM && glyph == cmap_to_glyph(S_room)) - glyph = cmap_to_glyph((flags.dark_room && iflags.use_color) - ? (DARKROOMSYM) - : S_stone); + glyph = (flags.dark_room && iflags.use_color) + ? cmap_to_glyph(DARKROOMSYM) + : GLYPH_NOTHING; else if (lev->typ == CORR && glyph == cmap_to_glyph(S_litcorr)) glyph = cmap_to_glyph(S_corr); } - if (level.flags.hero_memory) + if (svl.level.flags.hero_memory + && (glyph_is_unexplored(lev->glyph) || glyph_is_cmap(lev->glyph))) lev->glyph = glyph; if (show) show_glyph(x, y, glyph); - remember_topology(x, y); + update_lastseentyp(x, y); } /* @@ -194,9 +268,6 @@ int show; * having to create fake objects and traps. However, I am reluctant to * make this change. */ -/* FIXME: some of these use xchars for x and y, and some use ints. Make - * this consistent. - */ /* * map_background() @@ -205,13 +276,11 @@ int show; * the hero can physically see the location. Update the screen if directed. */ void -map_background(x, y, show) -register xchar x, y; -register int show; +map_background(coordxy x, coordxy y, int show) { - register int glyph = back_to_glyph(x, y); + int glyph = back_to_glyph(x, y); - if (level.flags.hero_memory) + if (svl.level.flags.hero_memory) levl[x][y].glyph = glyph; if (show) show_glyph(x, y, glyph); @@ -224,14 +293,29 @@ register int show; * hero can physically see the location. */ void -map_trap(trap, show) -register struct trap *trap; -register int show; +map_trap(struct trap *trap, int show) +{ + coordxy x = trap->tx, y = trap->ty; + int glyph = trap_to_glyph(trap); + + if (svl.level.flags.hero_memory) + levl[x][y].glyph = glyph; + if (show) + show_glyph(x, y, glyph); +} + +/* + * map_engraving() + * + * Map the engraving and print it out if directed. + */ +void +map_engraving(struct engr *ep, int show) { - register int x = trap->tx, y = trap->ty; - register int glyph = trap_to_glyph(trap, newsym_rn2); + coordxy x = ep->engr_x, y = ep->engr_y; + int glyph = engraving_to_glyph(ep); - if (level.flags.hero_memory) + if (svl.level.flags.hero_memory) levl[x][y].glyph = glyph; if (show) show_glyph(x, y, glyph); @@ -242,16 +326,32 @@ register int show; * * Map the given object. This routine assumes that the hero can physically * see the location of the object. Update the screen if directed. + * [Note: feel_location() -> map_location() -> map_object() contradicts + * the claim here that the hero can see obj's .] */ void -map_object(obj, show) -register struct obj *obj; -register int show; +map_object(struct obj *obj, int show) { - register int x = obj->ox, y = obj->oy; - register int glyph = obj_to_glyph(obj, newsym_rn2); + coordxy x = obj->ox, y = obj->oy; + int glyph = obj_to_glyph(obj, newsym_rn2); + + /* if this object is already displayed as a generic object, it might + become a specific one now */ + if (glyph_is_generic_object(glyph) && cansee(x, y) && !Hallucination) { + /* these 'r' and 'neardist' calculations match distant_name(objnam.c) + and see_nearby_objects(below); we assume that this is a lone + object or a pile-top, not something below the top of a pile */ + int r = (u.xray_range > 2) ? u.xray_range : 2, + /* neardist produces a small square with rounded corners */ + neardist = (r * r) * 2 - r; /* same as r*r + r*(r-1) */ + + if (distu(x, y) <= neardist) { + observe_object(obj); + glyph = obj_to_glyph(obj, newsym_rn2); + } + } - if (level.flags.hero_memory) { + if (svl.level.flags.hero_memory) { /* MRKR: While hallucinating, statues are seen as random monsters */ /* but remembered as random objects. */ @@ -275,19 +375,17 @@ register int show; * by newsym() if necessary. */ void -map_invisible(x, y) -register xchar x, y; +map_invisible(coordxy x, coordxy y) { if (x != u.ux || y != u.uy) { /* don't display I at hero's location */ - if (level.flags.hero_memory) + if (svl.level.flags.hero_memory) levl[x][y].glyph = GLYPH_INVISIBLE; show_glyph(x, y, GLYPH_INVISIBLE); } } boolean -unmap_invisible(x, y) -int x, y; +unmap_invisible(coordxy x, coordxy y) { if (isok(x,y) && glyph_is_invisible(levl[x][y].glyph)) { unmap_object(x, y); @@ -301,19 +399,19 @@ int x, y; /* * unmap_object() * - * Remove something from the map when the hero realizes it's not there any - * more. Replace it with background or known trap, but not with any other - * If this is used for detection, a full screen update is imminent anyway; - * if this is used to get rid of an invisible monster notation, we might have - * to call newsym(). + * Remove something from the map when the hero realizes it's not there + * anymore. Replace it with background or known trap, but not with + * any other remembered object. If this is used for detection, a full + * screen update is imminent anyway; if this is used to get rid of an + * invisible monster notation, we might have to call newsym(). */ void -unmap_object(x, y) -register int x, y; +unmap_object(coordxy x, coordxy y) { - register struct trap *trap; + struct trap *trap; + struct engr *ep; - if (!level.flags.hero_memory) + if (!svl.level.flags.hero_memory) return; if ((trap = t_at(x, y)) != 0 && trap->tseen && !covers_traps(x, y)) { @@ -321,7 +419,14 @@ register int x, y; } else if (levl[x][y].seenv) { struct rm *lev = &levl[x][y]; - map_background(x, y, 0); + if (spot_shows_engravings(x, y) + && (ep = engr_at(x, y)) != 0 && !covers_traps(x, y)) { + if (cansee(x, y)) + ep->erevealed = 1; + map_engraving(ep, 0); + } else { + map_background(x, y, 0); + } /* turn remembered dark room squares dark */ if (!lev->waslit && lev->glyph == cmap_to_glyph(S_room) @@ -341,31 +446,40 @@ register int x, y; * Internal to display.c, this is a #define for speed. */ #define _map_location(x, y, show) \ - { \ - register struct obj *obj; \ - register struct trap *trap; \ - \ - if ((obj = vobj_at(x, y)) && !covers_objects(x, y)) \ - map_object(obj, show); \ - else if ((trap = t_at(x, y)) && trap->tseen && !covers_traps(x, y)) \ - map_trap(trap, show); \ - else \ - map_background(x, y, show); \ - \ - remember_topology(x, y); \ - } + do { \ + struct obj *obj; \ + struct trap *trap; \ + struct engr *ml_ep; \ + NhRegion *_ml_reg; \ + \ + if ((obj = vobj_at(x, y)) && !covers_objects(x, y)) { \ + map_object(obj, show); \ + } else if ((trap = t_at(x, y)) \ + && trap->tseen && !covers_traps(x, y)) { \ + map_trap(trap, show); \ + } else if (spot_shows_engravings(x, y) \ + && (ml_ep = engr_at(x, y)) != 0 \ + && ml_ep->erevealed \ + && !covers_traps(x, y)) { \ + map_engraving(ml_ep, show); \ + } else { \ + map_background(x, y, show); \ + } \ + \ + update_lastseentyp(x, y); \ + if (show && !Blind && (_ml_reg = visible_region_at(x, y)) != 0) \ + show_region(_ml_reg, x, y); \ + } while (0) void -map_location(x, y, show) -int x, y, show; +map_location(coordxy x, coordxy y, int show) { _map_location(x, y, show); } /* display something on monster layer; may need to fixup object layer */ -STATIC_OVL void -show_mon_or_warn(x, y, monglyph) -int x, y, monglyph; +staticfn void +show_mon_or_warn(coordxy x, coordxy y, int monglyph) { struct obj *o; @@ -396,29 +510,31 @@ int x, y, monglyph; * a worm tail. * */ -STATIC_OVL void -display_monster(x, y, mon, sightflags, worm_tail) -register xchar x, y; /* display position */ -register struct monst *mon; /* monster to display */ -int sightflags; /* 1 if the monster is physically seen; - 2 if detected using Detect_monsters */ -xchar worm_tail; /* mon is actually a worm tail */ +staticfn void +display_monster( + coordxy x, coordxy y, /* display position */ + struct monst *mon, /* monster to display */ + int sightflags, /* 1 if the monster is physically seen; + * 2 if detected using Detect_monsters */ + boolean worm_tail) /* mon is actually a worm tail */ { boolean mon_mimic = (M_AP_TYPE(mon) != M_AP_NOTHING); int sensed = (mon_mimic && (Protection_from_shape_changers - || sensemon(mon))); + || sensemon(mon))), + mgendercode = mon->female ? FEMALE : MALE; + /* - * We must do the mimic check first. If the mimic is mimicing something, + * We must do the mimic check first. If the mimic is mimicking something, * and the location is in sight, we have to change the hero's memory * so that when the position is out of sight, the hero remembers what - * the mimic was mimicing. + * the mimic was mimicking. */ - if (mon_mimic && (sightflags == PHYSICALLY_SEEN)) { switch (M_AP_TYPE(mon)) { default: impossible("display_monster: bad m_ap_type value [ = %d ]", (int) mon->m_ap_type); + FALLTHROUGH; /*FALLTHRU*/ case M_AP_NOTHING: show_glyph(x, y, mon_to_glyph(mon, newsym_rn2)); @@ -440,7 +556,7 @@ xchar worm_tail; /* mon is actually a worm tail */ if (!sensed) { show_glyph(x, y, glyph); /* override real topology with mimic's fake one */ - lastseentyp[x][y] = cmap_to_type(sym); + svl.lastseentyp[x][y] = cmap_to_type(sym); } break; } @@ -449,25 +565,26 @@ xchar worm_tail; /* mon is actually a worm tail */ /* Make a fake object to send to map_object(). */ struct obj obj; - obj = zeroobj; + obj = cg.zeroobj; obj.ox = x; obj.oy = y; obj.otyp = mon->mappearance; - /* might be mimicing a corpse or statue */ + /* might be mimicking a corpse or statue */ obj.corpsenm = has_mcorpsenm(mon) ? MCORPSENM(mon) : PM_TENGU; map_object(&obj, !sensed); break; } - case M_AP_MONSTER: - show_glyph(x, y, - monnum_to_glyph(what_mon((int) mon->mappearance, - rn2_on_display_rng))); + case M_AP_MONSTER: { + int mndx = what_mon((int) mon->mappearance, rn2_on_display_rng); + + show_glyph(x, y, monnum_to_glyph(mndx, mgendercode)); break; - } + } /* case M_AP_MONSTER */ + } /* switch M_AP_TYPE() */ } - /* If mimic is unsuccessfully mimicing something, display the monster. */ + /* If mimic is unsuccessfully mimicking something, display the monster. */ if (!mon_mimic || sensed) { int num; @@ -481,23 +598,26 @@ xchar worm_tail; /* mon is actually a worm tail */ */ if (mon->mtame && !Hallucination) { if (worm_tail) - num = petnum_to_glyph(PM_LONG_WORM_TAIL); + num = petnum_to_glyph(PM_LONG_WORM_TAIL, mgendercode); else num = pet_to_glyph(mon, rn2_on_display_rng); } else if (sightflags == DETECTED) { if (worm_tail) - num = detected_monnum_to_glyph( - what_mon(PM_LONG_WORM_TAIL, rn2_on_display_rng)); + num = detected_monnum_to_glyph(what_mon(PM_LONG_WORM_TAIL, + rn2_on_display_rng), + mgendercode); else num = detected_mon_to_glyph(mon, rn2_on_display_rng); } else { if (worm_tail) - num = monnum_to_glyph( - what_mon(PM_LONG_WORM_TAIL, rn2_on_display_rng)); + num = monnum_to_glyph(what_mon(PM_LONG_WORM_TAIL, + rn2_on_display_rng), + mgendercode); else num = mon_to_glyph(mon, rn2_on_display_rng); } show_mon_or_warn(x, y, num); + mon->meverseen = 1; } } @@ -510,16 +630,16 @@ xchar worm_tail; /* mon is actually a worm tail */ * * Do not call for worm tails. */ -STATIC_OVL void -display_warning(mon) -register struct monst *mon; +staticfn void +display_warning(struct monst *mon) { - int x = mon->mx, y = mon->my; + coordxy x = mon->mx, y = mon->my; int glyph; if (mon_warning(mon)) { - int wl = Hallucination ? - rn2_on_display_rng(WARNCOUNT - 1) + 1 : warning_of(mon); + int wl = Hallucination ? rn2_on_display_rng(WARNCOUNT - 1) + 1 + : warning_of(mon); + glyph = warning_to_glyph(wl); } else if (MATCH_WARN_OF_MON(mon)) { glyph = mon_to_glyph(mon, rn2_on_display_rng); @@ -531,8 +651,7 @@ register struct monst *mon; } int -warning_of(mon) -struct monst *mon; +warning_of(struct monst *mon) { int wl = 0, tmp = 0; @@ -543,6 +662,60 @@ struct monst *mon; return wl; } +/* used by newsym() to decide whether to show a monster or a visible gas + cloud region when both are at the same spot; caller deals with region */ +staticfn boolean +mon_overrides_region( + struct monst *mon, /* might be Null */ + coordxy mx, coordxy my) /* won't match mon->mx,my if long worm's tail */ +{ + int r; + + /* this is redundant because newsym() doesn't call us when swallowed */ + if (u.uswallow && (!mon || mon != u.ustuck)) + return FALSE; + + if (mon) { + /* when not a worm tail, show mon if sensed rather than seen */ + if (mx == mon->mx && my == mon->my + && (sensemon(mon) || mon_warning(mon))) + return TRUE; + + /* even if worm tail; + check whether the spot is adjacent and 'mon' would be visible + there if the gas cloud wasn't interfering with normal vision; + _mon_visible() handles mon->mundetected; don't need to check + infravision when monster is adjacent */ + r = (u.xray_range > 1) ? u.xray_range : 1; + if (!Blind && _mon_visible(mon) + && M_AP_TYPE(mon) != M_AP_FURNITURE + && M_AP_TYPE(mon) != M_AP_OBJECT + && distu(mx, my) <= r * (r + 1)) + return TRUE; + } + + /* if not overriding region for current mon, propagate "remembered, + unseen monster" */ + return glyph_is_invisible(levl[mx][my].glyph) ? TRUE : FALSE; +} + +#ifdef HANGUPHANDLING +#define _suppress_map_output() \ + (gi.in_mklev || program_state.saving || program_state.restoring \ + || program_state.done_hup) +#else +#define _suppress_map_output() \ + (gi.in_mklev || program_state.saving || program_state.restoring) +#endif + +/* map or status window might not be ready for output during level creation + or game restoration (something like u.usteed which affects display of + the hero and also a status condition might not be set up yet) */ +boolean +suppress_map_output(void) +{ + return _suppress_map_output(); +} /* * feel_newsym() @@ -550,8 +723,7 @@ struct monst *mon; * When hero knows what happened to location, even when blind. */ void -feel_newsym(x, y) -xchar x, y; +feel_newsym(coordxy x, coordxy y) { if (Blind) feel_location(x, y); @@ -571,16 +743,19 @@ xchar x, y; * searching only finds one monster per turn so we must check that separately. */ void -feel_location(x, y) -xchar x, y; +feel_location(coordxy x, coordxy y) { struct rm *lev; struct obj *boulder; - register struct monst *mon; + struct monst *mon; + struct engr *ep; + /* replicate safeguards used by newsym(); might not be required here */ + if (_suppress_map_output()) + return; if (!isok(x, y)) return; - lev = &(levl[x][y]); + lev = &levl[x][y]; /* If hero's memory of an invisible monster is accurate, we want to keep * him from detecting the same monster over and over again on each turn. * We must return (so we don't erase the monster). (We must also, in the @@ -611,11 +786,11 @@ xchar x, y; * * + Stone, walls, and closed doors. * + Boulders. [see a boulder before a doorway] - * + Doors. + * + doors. * + Room/water positions * + Everything else (hallways!) */ - if (IS_ROCK(lev->typ) + if (IS_OBSTRUCTED(lev->typ) || (IS_DOOR(lev->typ) && (lev->doormask & (D_LOCKED | D_CLOSED)))) { map_background(x, y, 1); @@ -682,6 +857,9 @@ xchar x, y; show_glyph(x, y, lev->glyph = cmap_to_glyph(S_darkroom)); } } else { + if ((ep = engr_at(x, y)) != 0 && engr_can_be_felt(ep)) + ep->erevealed = 1; + _map_location(x, y, 1); if (Punished) { @@ -691,19 +869,25 @@ xchar x, y; * something has been dropped on the ball/chain. If the bit is * not cleared, then when the ball/chain is moved it will drop * the wrong glyph. + * + * Note: during unpunish() we can be called by delobj() when + * destroying uchain while uball hasn't been cleared yet (so + * Punished will still yield True but uchain might not be part + * of the floor list anymore). */ - if (uchain->ox == x && uchain->oy == y) { - if (level.objects[x][y] == uchain) - u.bc_felt |= BC_CHAIN; - else - u.bc_felt &= ~BC_CHAIN; /* do not feel the chain */ - } - if (!carried(uball) && uball->ox == x && uball->oy == y) { - if (level.objects[x][y] == uball) - u.bc_felt |= BC_BALL; - else - u.bc_felt &= ~BC_BALL; /* do not feel the ball */ - } + if (uchain && uchain->where == OBJ_FLOOR + && uchain->ox == x && uchain->oy == y + && svl.level.objects[x][y] == uchain) + u.bc_felt |= BC_CHAIN; + else + u.bc_felt &= ~BC_CHAIN; /* do not feel the chain */ + + if (uball && uball->where == OBJ_FLOOR + && uball->ox == x && uball->oy == y + && svl.level.objects[x][y] == uball) + u.bc_felt |= BC_BALL; + else + u.bc_felt &= ~BC_BALL; /* do not feel the ball */ } /* Floor spaces are dark if unlit. Corridors are dark if unlit. */ @@ -716,7 +900,7 @@ xchar x, y; show_glyph(x, y, lev->glyph = cmap_to_glyph(S_corr)); } /* draw monster on top if we can sense it */ - if ((x != u.ux || y != u.uy) && (mon = m_at(x, y)) != 0 && sensemon(mon)) + if (!u_at(x, y) && (mon = m_at(x, y)) != 0 && sensemon(mon)) display_monster(x, y, mon, (tp_sensemon(mon) || MATCH_WARN_OF_MON(mon)) ? PHYSICALLY_SEEN @@ -730,33 +914,40 @@ xchar x, y; * Possibly put a new glyph at the given location. */ void -newsym(x, y) -register int x, y; +newsym(coordxy x, coordxy y) { - register struct monst *mon; - register struct rm *lev = &(levl[x][y]); - register int see_it; - register xchar worm_tail; + struct rm *lev; + struct engr *ep; + struct monst *mon; + int see_it; + boolean worm_tail; - if (in_mklev) + /* don't try to produce map output when level is in a state of flux */ + if (_suppress_map_output()) return; -#ifdef HANGUPHANDLING - if (program_state.done_hup) + /* should never happen; same error handling as u_on_newpos() */ + if (!isok(x, y)) { + void (*errfunc)(const char *, ...) PRINTF_F_PTR(1, 2); + + errfunc = (x < 0 || y < 0 || x > COLNO - 1 || y > ROWNO - 1) ? panic + : impossible; /* misuse of column 0 is less severe */ + (*errfunc)("newsym: attempting screen update for <%d,%d>", x, y); return; -#endif + } /* only permit updating the hero when swallowed */ if (u.uswallow) { - if (x == u.ux && y == u.uy) + if (u_at(x, y)) display_self(); return; } if (Underwater && !Is_waterlevel(&u.uz)) { /* when underwater, don't do anything unless is an adjacent water or lava or ice position */ - if (!(is_pool_or_lava(x, y) || is_ice(x, y)) || distu(x, y) > 2) + if (!(is_pool_or_lava(x, y) || is_ice(x, y)) || !next2u(x, y)) return; } + lev = &levl[x][y]; /* Can physically see the location. */ if (cansee(x, y)) { @@ -778,21 +969,36 @@ register int x, y; mon = m_at(x, y); worm_tail = is_worm_tail(mon); + if ((ep = engr_at(x, y)) != 0) + ep->erevealed = 1; /* even when covered by objects or a monster */ /* * Normal region shown only on accessible positions, but - * poison clouds also shown above lava, pools and moats. - * However, sensed monsters take precedence over all regions. + * poison clouds and steam clouds also shown above lava, + * pools and moats. + * However, sensed monsters (via detection or telepathy or + * warning) take precedence over all regions. + * Adjacent monsters also take precedence if they would be + * seen when there's no gas region. + * + * FIXME: + * The adjacency checking [in mon_overrides_region()] works + * when the hero is outside the region and the monster is + * inside, and when they're both inside, but not when the + * hero is inside and monster outside (because 'reg' will be + * Null for mon's ). Checking whether hero is inside + * a region for every newsym() seems excessive. The hero is + * usually blind when in a gas cloud so the problem is less + * noticeable then it might otherwise be. */ - if (reg - && (ACCESSIBLE(lev->typ) - || (reg->glyph == cmap_to_glyph(S_poisoncloud) - && is_pool_or_lava(x, y))) - && (!mon || worm_tail || !sensemon(mon))) { - show_region(reg, x, y); - return; + if (reg && (ACCESSIBLE(lev->typ) + || (reg->visible && is_pool_or_lava(x, y)))) { + if (!mon_overrides_region(mon, x, y)) { + show_region(reg, x, y); + return; + } } - if (x == u.ux && y == u.uy) { + if (u_at(x, y)) { int see_self = canspotself(); /* update map information for (remembered topology @@ -802,6 +1008,8 @@ register int x, y; if (see_self) display_self(); } else { + boolean show = FALSE; + see_it = mon && (mon_visible(mon) || (!worm_tail && (tp_sensemon(mon) || MATCH_WARN_OF_MON(mon)))); @@ -814,22 +1022,23 @@ register int x, y; if (tt == BEAR_TRAP || is_pit(tt) || tt == WEB) trap->tseen = 1; } - _map_location(x, y, 0); /* map under the monster */ + _map_location(x, y, show); /* map under the monster */ /* also gets rid of any invisibility glyph */ display_monster(x, y, mon, see_it ? PHYSICALLY_SEEN : DETECTED, worm_tail); - } else if (mon && mon_warning(mon) && !is_worm_tail(mon)) { + } else if (mon && mon_warning(mon) && !worm_tail) { display_warning(mon); } else if (glyph_is_invisible(lev->glyph)) { map_invisible(x, y); - } else - _map_location(x, y, 1); /* map the location */\ + } else { + _map_location(x, y, 1); /* map the location */ + } } /* Can't see the location. */ } else { - if (x == u.ux && y == u.uy) { + if (u_at(x, y)) { feel_location(u.ux, u.uy); /* forces an update */ if (canspotself()) @@ -838,7 +1047,7 @@ register int x, y; && ((see_it = (tp_sensemon(mon) || MATCH_WARN_OF_MON(mon) || (see_with_infrared(mon) && mon_visible(mon)))) != 0 - || Detect_monsters)) { + || (Detect_monsters && !is_worm_tail(mon)))) { /* Seen or sensed monsters are printed every time. This also gets rid of any invisibility glyph. */ display_monster(x, y, mon, see_it ? 0 : DETECTED, @@ -898,10 +1107,9 @@ register int x, y; * pulled into a platform dependent routine for fancier graphics if desired. */ void -shieldeff(x, y) -xchar x, y; +shieldeff(coordxy x, coordxy y) { - register int i; + int i; if (!flags.sparkle) return; @@ -909,15 +1117,14 @@ xchar x, y; for (i = 0; i < SHIELD_COUNT; i++) { show_glyph(x, y, cmap_to_glyph(shield_static[i])); flush_screen(1); /* make sure the glyph shows up */ - delay_output(); + nh_delay_output(); } newsym(x, y); /* restore the old information */ } } -STATIC_OVL int -tether_glyph(x, y) -int x, y; +staticfn int +tether_glyph(coordxy x, coordxy y) { int tdx, tdy; tdx = u.ux - x; @@ -928,7 +1135,7 @@ int x, y; /* * tmp_at() * - * Temporarily place glyphs on the screen. Do not call delay_output(). It + * Temporarily place glyphs on the screen. Do not call nh_delay_output(). It * is up to the caller to decide if it wants to wait [presently, everyone * but explode() wants to delay]. * @@ -943,6 +1150,8 @@ int x, y; * * DISP_BEAM - Display the given glyph at each location, but do not erase * any until the close call. + * DISP_ALL - Same as DISP_BEAM except glyph is shown at the specified + * spot even when that spot can't be seen. * DISP_TETHER - Display a tether glyph at each location, and the tethered * object at the farthest location, but do not erase any * until the return trip or close. @@ -962,8 +1171,7 @@ static struct tmp_glyph { } tgfirst; void -tmp_at(x, y) -int x, y; +tmp_at(coordxy x, coordxy y) { static struct tmp_glyph *tglyph = (struct tmp_glyph *) 0; struct tmp_glyph *tmp; @@ -999,90 +1207,92 @@ int x, y; break; } - if (!tglyph) + if (!tglyph) { panic("tmp_at: tglyph not initialized"); + } else { + switch (x) { + case DISP_CHANGE: + tglyph->glyph = y; + break; - switch (x) { - case DISP_CHANGE: - tglyph->glyph = y; - break; - - case DISP_END: - if (tglyph->style == DISP_BEAM || tglyph->style == DISP_ALL) { - register int i; - - /* Erase (reset) from source to end */ - for (i = 0; i < tglyph->sidx; i++) - newsym(tglyph->saved[i].x, tglyph->saved[i].y); - } else if (tglyph->style == DISP_TETHER) { - int i; + case DISP_END: + if (tglyph->style == DISP_BEAM || tglyph->style == DISP_ALL) { + int i; - if (y == BACKTRACK && tglyph->sidx > 1) { - /* backtrack */ - for (i = tglyph->sidx - 1; i > 0; i--) { + /* Erase (reset) from source to end */ + for (i = 0; i < tglyph->sidx; i++) newsym(tglyph->saved[i].x, tglyph->saved[i].y); - show_glyph(tglyph->saved[i - 1].x, - tglyph->saved[i - 1].y, tglyph->glyph); - flush_screen(0); /* make sure it shows up */ - delay_output(); + } else if (tglyph->style == DISP_TETHER) { + int i; + + if (y == BACKTRACK && tglyph->sidx > 1) { + /* backtrack */ + for (i = tglyph->sidx - 1; i > 0; i--) { + newsym(tglyph->saved[i].x, tglyph->saved[i].y); + show_glyph(tglyph->saved[i - 1].x, + tglyph->saved[i - 1].y, tglyph->glyph); + flush_screen(0); /* make sure it shows up */ + nh_delay_output(); + } + tglyph->sidx = 1; } - tglyph->sidx = 1; + for (i = 0; i < tglyph->sidx; i++) + newsym(tglyph->saved[i].x, tglyph->saved[i].y); + } else { /* DISP_FLASH or DISP_ALWAYS */ + if (tglyph->sidx) /* been called at least once */ + newsym(tglyph->saved[0].x, tglyph->saved[0].y); } - for (i = 0; i < tglyph->sidx; i++) - newsym(tglyph->saved[i].x, tglyph->saved[i].y); - } else { /* DISP_FLASH or DISP_ALWAYS */ - if (tglyph->sidx) /* been called at least once */ - newsym(tglyph->saved[0].x, tglyph->saved[0].y); - } - /* tglyph->sidx = 0; -- about to be freed, so not necessary */ - tmp = tglyph->prev; - if (tglyph != &tgfirst) - free((genericptr_t) tglyph); - tglyph = tmp; - break; - - default: /* do it */ - if (!isok(x, y)) + /* tglyph->sidx = 0; -- about to be freed, so not necessary */ + tmp = tglyph->prev; + if (tglyph != &tgfirst) + free((genericptr_t) tglyph); + tglyph = tmp; break; - if (tglyph->style == DISP_BEAM || tglyph->style == DISP_ALL) { - if (tglyph->style != DISP_ALL && !cansee(x, y)) + + default: /* do it */ + if (!isok(x, y)) break; - if (tglyph->sidx >= TMP_AT_MAX_GLYPHS) - break; /* too many locations */ - /* save pos for later erasing */ - tglyph->saved[tglyph->sidx].x = x; - tglyph->saved[tglyph->sidx].y = y; - tglyph->sidx += 1; - } else if (tglyph->style == DISP_TETHER) { - if (tglyph->sidx >= TMP_AT_MAX_GLYPHS) - break; /* too many locations */ - if (tglyph->sidx) { - int px, py; - - px = tglyph->saved[tglyph->sidx-1].x; - py = tglyph->saved[tglyph->sidx-1].y; - show_glyph(px, py, tether_glyph(px, py)); - } - /* save pos for later use or erasure */ - tglyph->saved[tglyph->sidx].x = x; - tglyph->saved[tglyph->sidx].y = y; - tglyph->sidx += 1; - } else { /* DISP_FLASH/ALWAYS */ - if (tglyph->sidx) { /* not first call, so reset previous pos */ - newsym(tglyph->saved[0].x, tglyph->saved[0].y); - tglyph->sidx = 0; /* display is presently up to date */ + if (tglyph->style == DISP_BEAM || tglyph->style == DISP_ALL) { + if (tglyph->style != DISP_ALL && !cansee(x, y)) + break; + if (tglyph->sidx >= TMP_AT_MAX_GLYPHS) + break; /* too many locations */ + /* save pos for later erasing */ + tglyph->saved[tglyph->sidx].x = x; + tglyph->saved[tglyph->sidx].y = y; + tglyph->sidx += 1; + } else if (tglyph->style == DISP_TETHER) { + if (tglyph->sidx >= TMP_AT_MAX_GLYPHS) + break; /* too many locations */ + if (tglyph->sidx) { + int px, py; + + px = tglyph->saved[tglyph->sidx - 1].x; + py = tglyph->saved[tglyph->sidx - 1].y; + show_glyph(px, py, tether_glyph(px, py)); + } + /* save pos for later use or erasure */ + tglyph->saved[tglyph->sidx].x = x; + tglyph->saved[tglyph->sidx].y = y; + tglyph->sidx += 1; + } else { /* DISP_FLASH/ALWAYS */ + if (tglyph + ->sidx) { /* not first call, so reset previous pos */ + newsym(tglyph->saved[0].x, tglyph->saved[0].y); + tglyph->sidx = 0; /* display is presently up to date */ + } + if (!cansee(x, y) && tglyph->style != DISP_ALWAYS) + break; + tglyph->saved[0].x = x; + tglyph->saved[0].y = y; + tglyph->sidx = 1; } - if (!cansee(x, y) && tglyph->style != DISP_ALWAYS) - break; - tglyph->saved[0].x = x; - tglyph->saved[0].y = y; - tglyph->sidx = 1; - } - show_glyph(x, y, tglyph->glyph); /* show it */ - flush_screen(0); /* make sure it shows up */ - break; - } /* end case */ + show_glyph(x, y, tglyph->glyph); /* show it */ + flush_screen(0); /* make sure it shows up */ + break; + } /* end switch */ + } } /* @@ -1092,23 +1302,21 @@ int x, y; * meant to be at the location. */ void -flash_glyph_at(x, y, tg, rpt) -int x, y; -int tg, rpt; +flash_glyph_at(coordxy x, coordxy y, int tg, int rpt) { int i, glyph[2]; rpt *= 2; /* two loop iterations per 'count' */ glyph[0] = tg; - glyph[1] = (level.flags.hero_memory) ? levl[x][y].glyph - : back_to_glyph(x, y); + glyph[1] = (svl.level.flags.hero_memory) ? levl[x][y].glyph + : back_to_glyph(x, y); /* even iteration count (guaranteed) ends with glyph[1] showing; caller might want to override that, but no newsym() calls here in case caller has tinkered with location visibility */ for (i = 0; i < rpt; i++) { show_glyph(x, y, glyph[i % 2]); flush_screen(1); - delay_output(); + nh_delay_output(); } } @@ -1121,23 +1329,22 @@ int tg, rpt; * being swallowed. */ void -swallowed(first) -int first; +swallowed(int first) { - static xchar lastx, lasty; /* last swallowed position */ + static coordxy lastx, lasty; /* last swallowed position */ int swallower, left_ok, rght_ok; if (first) { cls(); bot(); } else { - register int x, y; + coordxy x, y; /* Clear old location */ for (y = lasty - 1; y <= lasty + 1; y++) for (x = lastx - 1; x <= lastx + 1; x++) if (isok(x, y)) - show_glyph(x, y, cmap_to_glyph(S_stone)); + show_glyph(x, y, GLYPH_UNEXPLORED); } swallower = monsndx(u.ustuck->data); @@ -1185,12 +1392,11 @@ int first; * except when in water level. Special routines exist for that. */ void -under_water(mode) -int mode; +under_water(int mode) { - static xchar lastx, lasty; + static coordxy lastx, lasty; static boolean dela; - register int x, y; + coordxy x, y; /* swallowing has a higher precedence than under water */ if (Is_waterlevel(&u.uz) || u.uswallow) @@ -1211,7 +1417,7 @@ int mode; for (y = lasty - 1; y <= lasty + 1; y++) for (x = lastx - 1; x <= lastx + 1; x++) if (isok(x, y)) - show_glyph(x, y, cmap_to_glyph(S_stone)); + show_glyph(x, y, GLYPH_UNEXPLORED); } /* @@ -1221,8 +1427,8 @@ int mode; for (x = u.ux - 1; x <= u.ux + 1; x++) for (y = u.uy - 1; y <= u.uy + 1; y++) if (isok(x, y) && (is_pool_or_lava(x, y) || is_ice(x, y))) { - if (Blind && !(x == u.ux && y == u.uy)) - show_glyph(x, y, cmap_to_glyph(S_stone)); + if (Blind && !u_at(x, y)) + show_glyph(x, y, GLYPH_UNEXPLORED); else newsym(x, y); } @@ -1236,8 +1442,7 @@ int mode; * Very restricted display. You can only see yourself. */ void -under_ground(mode) -int mode; +under_ground(int mode) { static boolean dela; @@ -1271,6 +1476,7 @@ int mode; * + hallucinating * + doing a full screen redraw * + see invisible times out or a ring of see invisible is taken off + * or intrinsic see invisible is stolen by a gremlin * + when a potion of see invisible is quaffed or a ring of see * invisible is put on * + gaining telepathy when blind [givit() in eat.c, pleased() in pray.c] @@ -1278,29 +1484,43 @@ int mode; * sit.c] */ void -see_monsters() +see_monsters(void) { - register struct monst *mon; + struct monst *mon; int new_warn_obj_cnt = 0; - if (defer_see_monsters) + if (gd.defer_see_monsters) return; + /* steed and unseen engulfer/holder/holdee are recognized via touch + even if they aren't going to be rendered; other monsters + may get flagged as having been seen by display_monster() if it's + called by newsym() */ + if (u.usteed) + u.usteed->meverseen = 1; + if (u.ustuck) + u.ustuck->meverseen = 1; + + /* loop through level.monsters (aka fmon) */ for (mon = fmon; mon; mon = mon->nmon) { if (DEADMONSTER(mon)) continue; + if ((mon->mstate & MON_STILL_ARRIVING) != 0) + continue; newsym(mon->mx, mon->my); if (mon->wormno) see_wsegs(mon); - if (Warn_of_mon && (context.warntype.obj & mon->data->mflags2) != 0L) + if (Warn_of_mon + && (svc.context.warntype.obj & mon->data->mflags2) != 0L) new_warn_obj_cnt++; } + /* * Make Sting glow blue or stop glowing if required. */ - if (new_warn_obj_cnt != warn_obj_cnt) { + if (new_warn_obj_cnt != gw.warn_obj_cnt) { Sting_effects(new_warn_obj_cnt); - warn_obj_cnt = new_warn_obj_cnt; + gw.warn_obj_cnt = new_warn_obj_cnt; } /* when mounted, hero's location gets caught by monster loop */ @@ -1308,26 +1528,26 @@ see_monsters() newsym(u.ux, u.uy); } +staticfn void +mimic_light_blocking(struct monst *mtmp) +{ + if (mtmp->minvis && is_lightblocker_mappear(mtmp)) { + if (See_invisible) + block_point(mtmp->mx, mtmp->my); + else + unblock_point(mtmp->mx, mtmp->my); + } +} + /* - * Block/unblock light depending on what a mimic is mimicing and if it's + * Block/unblock light depending on what a mimic is mimicking and if it's * invisible or not. Should be called only when the state of See_invisible * changes. */ void -set_mimic_blocking() +set_mimic_blocking(void) { - register struct monst *mon; - - for (mon = fmon; mon; mon = mon->nmon) { - if (DEADMONSTER(mon)) - continue; - if (mon->minvis && is_lightblocker_mappear(mon)) { - if (See_invisible) - block_point(mon->mx, mon->my); - else - unblock_point(mon->mx, mon->my); - } - } + iter_mons(mimic_light_blocking); } /* @@ -1335,55 +1555,174 @@ set_mimic_blocking() * + hallucinating. */ void -see_objects() +see_objects(void) { - register struct obj *obj; + struct obj *obj; + for (obj = fobj; obj; obj = obj->nobj) if (vobj_at(obj->ox, obj->oy) == obj) newsym(obj->ox, obj->oy); + + /* Qt's "paper doll" subset of persistent inventory shows map tiles + for objects which aren't on the floor so not handled by above loop; + inventory which includes glyphs should also be affected, so do this + for all interfaces in case any feature that for persistent inventory */ + update_inventory(); +} + +/* mark the top object of nearby stacks as having been seen, and if + that object was being displayed as generic, redisplay it as specific */ +void +see_nearby_objects(void) +{ + struct obj *obj; + int glyph; + coordxy ix, iy, x = u.ux, y = u.uy; + /* these 'r' and 'neardist' calculations match distant_name(objnam.c) */ + int r = (u.xray_range > 2) ? u.xray_range : 2, + /* neardist produces a small square with rounded corners */ + neardist = (r * r) * 2 - r; /* same as r*r + r*(r-1) */ + + /* [note: this could be optimized to avoid the distu() calculations] */ + for (iy = y - r; iy <= y + r; ++iy) + for (ix = x - r; ix <= x + r; ++ix) { + if (!isok(ix, iy)) + continue; + /* skip if no object or the object has already been marked as + having been seen up close */ + if ((obj = vobj_at(ix, iy)) == 0 || obj->dknown) + continue; + /* skip if the spot can't be seen or is too far (diagonal) */ + if (!cansee(ix, iy) || distu(ix, iy) > neardist) + continue; + + observe_object(obj); + /* operate on remembered glyph rather than current one */ + glyph = levl[ix][iy].glyph; + if (glyph_is_generic_object(glyph)) + newsym_force(ix, iy); + } } /* * Update hallucinated traps. */ void -see_traps() +see_traps(void) { struct trap *trap; int glyph; - for (trap = ftrap; trap; trap = trap->ntrap) { - glyph = glyph_at(trap->tx, trap->ty); + for (trap = gf.ftrap; trap; trap = trap->ntrap) { + glyph = _glyph_at(trap->tx, trap->ty); if (glyph_is_trap(glyph)) newsym(trap->tx, trap->ty); } } +/* glyph, ttychar, framecolor, + { glyphflags, { NO_COLOR, sym.symidx }, + customcolor, color256idx, tileidx, u } */ +static glyph_info no_ginfo = { + NO_GLYPH, ' ', NO_COLOR, + { MG_BADXY, { NO_COLOR, 0 }, + 0, + 0U, 0U +#ifdef ENHANCED_SYMBOLS + , 0 +#endif + } +}; + +#ifndef UNBUFFERED_GLYPHINFO +/* Note that the 'glyph' argument is not used in the expansion + * of this !UNBUFFERED_GLYPHINFO (default) variation, but is + * a requirement for the UNBUFFERED_GLYPHINFO variation */ +#define Glyphinfo_at(x, y, glyph) \ + (((x) < 0 || (y) < 0 || (x) >= COLNO || (y) >= ROWNO) ? &no_ginfo \ + : &gg.gbuf[(y)][(x)].glyphinfo) +#else +static glyph_info ginfo; +staticfn glyph_info *glyphinfo_at(coordxy, coordxy, int); +#define Glyphinfo_at(x, y, glyph) glyphinfo_at((x), (y), (glyph)) +#endif + +#ifdef TILES_IN_GLYPHMAP +extern const glyph_info nul_glyphinfo; /* tile.c */ +#else +/* glyph, ttychar, framecolor, { glyphflags, { sym.symidx }, nhcolor, + tileidx, 0} */ +const glyph_info nul_glyphinfo = { + NO_GLYPH, ' ', NO_COLOR, + { /* glyph_map */ + MG_UNEXPL, + { NO_COLOR, SYM_UNEXPLORED + SYM_OFF_X }, + 0, + 0U, 0U +#ifdef ENHANCED_SYMBOLS + , 0 +#endif + } +}; +#endif + +#ifdef TILES_IN_GLYPHMAP +extern glyph_map glyphmap[MAX_GLYPH]; /* from tile.c */ +#else +glyph_map glyphmap[MAX_GLYPH] = { + { 0U, { NO_COLOR, 0 }, + 0, + 0U, 0U +#ifdef ENHANCED_SYMBOLS + , 0 +#endif + } +}; +#endif + /* * Put the cursor on the hero. Flush all accumulated glyphs before doing it. */ void -curs_on_u() +curs_on_u(void) { flush_screen(1); /* Flush waiting glyphs & put cursor on hero */ } +/* the #redraw command */ int -doredraw() +doredraw(void) { docrt(); - return 0; + return ECMD_OK; +} + +/* the main refresh-the-screen routine */ +void +docrt(void) +{ + docrt_flags(docrtRecalc); } +/* docrt() with finer control */ void -docrt() +docrt_flags(int refresh_flags) { - register int x, y; - register struct rm *lev; + coordxy x, y; + struct rm *lev; + boolean maponly = (refresh_flags & docrtMapOnly) != 0, + redrawonly = (refresh_flags & docrtRefresh) != 0, + nocls = (refresh_flags & docrtNocls) != 0; - if (!u.ux) + if (!u.ux || program_state.in_docrt) return; /* display isn't ready yet */ + program_state.in_docrt = TRUE; + + if (redrawonly) { + redraw_map(FALSE); + goto post_map; + } if (u.uswallow) { swallowed(1); goto post_map; @@ -1392,7 +1731,7 @@ docrt() under_water(1); goto post_map; } - if (u.uburied) { + if (u.uburied) { /* [not implemented] */ under_ground(1); goto post_map; } @@ -1405,14 +1744,14 @@ docrt() * + fills the physical screen with the symbol for rock * + clears the glyph buffer */ - cls(); + if (!nocls) + cls(); /* display memory */ for (x = 1; x < COLNO; x++) { lev = &levl[x][0]; for (y = 0; y < ROWNO; y++, lev++) - if (lev->glyph != cmap_to_glyph(S_stone)) - show_glyph(x, y, lev->glyph); + show_glyph(x, y, lev->glyph); } /* see what is to be seen */ @@ -1423,18 +1762,24 @@ docrt() post_map: - /* perm_invent */ - update_inventory(); - - context.botlx = 1; /* force a redraw of the bottom line */ + if (!maponly) { + /* perm_invent */ + update_inventory(); + /* status */ + disp.botlx = TRUE; /* force a redraw of the bottom lines */ + /* note: caller needs to call bot() to actually redraw status */ + } + program_state.in_docrt = FALSE; } /* for panning beyond a clipped region; resend the current map data to the interface rather than use docrt()'s regeneration of that data */ void -redraw_map() +redraw_map(boolean cursor_on_u) { - int x, y, glyph; + coordxy x, y; + int glyph; + glyph_info bkglyphinfo = nul_glyphinfo; /* * Not sure whether this is actually necessary; save and restore did @@ -1444,7 +1789,7 @@ redraw_map() * !u.ux: display isn't ready yet; (restoring || !on_level()): was part * of cliparound() but interface shouldn't access this much internals */ - if (!u.ux || restoring || !on_level(&u.uz0, &u.uz)) + if (!u.ux || suppress_map_output() || !on_level(&u.uz0, &u.uz)) return; /* @@ -1454,52 +1799,101 @@ redraw_map() */ for (y = 0; y < ROWNO; ++y) for (x = 1; x < COLNO; ++x) { - glyph = glyph_at(x, y); /* not levl[x][y].glyph */ - print_glyph(WIN_MAP, x, y, glyph, get_bk_glyph(x, y)); + glyph = _glyph_at(x, y); /* not levl[x][y].glyph */ + get_bkglyph_and_framecolor(x, y, &bkglyphinfo.glyph, + &bkglyphinfo.framecolor); + print_glyph(WIN_MAP, x, y, + Glyphinfo_at(x, y, glyph), &bkglyphinfo); } - flush_screen(1); + flush_screen(cursor_on_u); +#ifndef UNBUFFERED_GLYPHINFO + nhUse(glyph); +#endif } -/* ======================================================================== */ -/* Glyph Buffering (3rd screen) =========================================== */ +/* + * ======================================================= + */ +void +reglyph_darkroom(void) +{ + coordxy x, y; -typedef struct { - xchar new; /* perhaps move this bit into the rm structure. */ - int glyph; -} gbuf_entry; + for (x = 1; x < COLNO; x++) + for (y = 0; y < ROWNO; y++) { + struct rm *lev = &levl[x][y]; + + if (!flags.dark_room) { + if (lev->glyph == cmap_to_glyph(S_corr) + && lev->waslit) + lev->glyph = cmap_to_glyph(S_litcorr); + } else { + if (lev->glyph == cmap_to_glyph(S_litcorr) + && !cansee(x, y)) + lev->glyph = cmap_to_glyph(S_corr); + } + + if (!flags.dark_room || !iflags.use_color + || Is_rogue_level(&u.uz)) { + if (lev->glyph == cmap_to_glyph(S_darkroom)) + lev->glyph = lev->waslit ? cmap_to_glyph(S_room) + : GLYPH_NOTHING; + } else { + if (lev->glyph == cmap_to_glyph(S_room) && lev->seenv + && lev->waslit && !cansee(x, y)) + lev->glyph = cmap_to_glyph(S_darkroom); + else if (lev->glyph == GLYPH_NOTHING + && lev->typ == ROOM && lev->seenv && !cansee(x, y)) + lev->glyph = cmap_to_glyph(S_darkroom); + } + } + if (flags.dark_room && iflags.use_color) + gs.showsyms[S_darkroom] = gs.showsyms[S_room]; + else + gs.showsyms[S_darkroom] = gs.showsyms[SYM_NOTHING + SYM_OFF_X]; +} -static gbuf_entry gbuf[ROWNO][COLNO]; -static char gbuf_start[ROWNO]; -static char gbuf_stop[ROWNO]; +/* ======================================================================== */ +/* Glyph Buffering (3rd screen) =========================================== */ /* FIXME: This is a dirty hack, because newsym() doesn't distinguish * between object piles and single objects, it doesn't mark the location * for update. */ void -newsym_force(x, y) -register int x, y; +newsym_force(coordxy x, coordxy y) { - newsym(x,y); - gbuf[y][x].new = 1; - if (gbuf_start[y] > x) - gbuf_start[y] = x; - if (gbuf_stop[y] < x) - gbuf_stop[y] = x; + newsym(x, y); + gg.gbuf[y][x].gnew = 1; + if (gg.gbuf_start[y] > x) + gg.gbuf_start[y] = x; + if (gg.gbuf_stop[y] < x) + gg.gbuf_stop[y] = x; } /* * Store the glyph in the 3rd screen for later flushing. */ void -show_glyph(x, y, glyph) -int x, y, glyph; +show_glyph(coordxy x, coordxy y, int glyph) { +#ifndef UNBUFFERED_GLYPHINFO + glyph_info glyphinfo; +#endif + boolean show_glyph_change = FALSE; + int oldglyph; + + /* don't process map glyphs when saving, restoring, or in_mklev */ + if (_suppress_map_output()) + return; + + //if (glyph == 3972 || glyph == 3988) + // __debugbreak(); /* * Check for bad positions and glyphs. */ if (!isok(x, y)) { - const char *text; - int offset; + const char *text = ""; + int offset = -1; /* column 0 is invalid, but it's often used as a flag, so ignore it */ if (x == 0) @@ -1509,64 +1903,171 @@ int x, y, glyph; * This assumes an ordering of the offsets. See display.h for * the definition. */ - - if (glyph >= GLYPH_WARNING_OFF - && glyph < GLYPH_STATUE_OFF) { /* a warning */ - text = "warning"; - offset = glyph - GLYPH_WARNING_OFF; - } else if (glyph >= GLYPH_SWALLOW_OFF) { /* swallow border */ - text = "swallow border"; - offset = glyph - GLYPH_SWALLOW_OFF; - } else if (glyph >= GLYPH_ZAP_OFF) { /* zap beam */ - text = "zap beam"; - offset = glyph - GLYPH_ZAP_OFF; - } else if (glyph >= GLYPH_EXPLODE_OFF) { /* explosion */ - text = "explosion"; - offset = glyph - GLYPH_EXPLODE_OFF; - } else if (glyph >= GLYPH_CMAP_OFF) { /* cmap */ - text = "cmap_index"; - offset = glyph - GLYPH_CMAP_OFF; - } else if (glyph >= GLYPH_OBJ_OFF) { /* object */ - text = "object"; - offset = glyph - GLYPH_OBJ_OFF; - } else if (glyph >= GLYPH_RIDDEN_OFF) { /* ridden mon */ - text = "ridden mon"; - offset = glyph - GLYPH_RIDDEN_OFF; - } else if (glyph >= GLYPH_BODY_OFF) { /* a corpse */ - text = "corpse"; - offset = glyph - GLYPH_BODY_OFF; - } else if (glyph >= GLYPH_DETECT_OFF) { /* detected mon */ - text = "detected mon"; - offset = glyph - GLYPH_DETECT_OFF; - } else if (glyph >= GLYPH_INVIS_OFF) { /* invisible mon */ - text = "invisible mon"; - offset = glyph - GLYPH_INVIS_OFF; - } else if (glyph >= GLYPH_PET_OFF) { /* a pet */ - text = "pet"; - offset = glyph - GLYPH_PET_OFF; - } else { /* a monster */ - text = "monster"; - offset = glyph; + if (glyph < 0 || glyph >= MAX_GLYPH) { + /* invalid location and also invalid glyph */ + text = "invalid"; + } else if ((offset = (glyph - GLYPH_NOTHING_OFF)) >= 0) { + text = "nothing"; + } else if ((offset = (glyph - GLYPH_UNEXPLORED_OFF)) >= 0) { + text = "unexplored"; + } else if ((offset = (glyph - GLYPH_STATUE_FEM_PILETOP_OFF)) >= 0) { + text = "statue of a female monster at top of a pile"; + } else if ((offset = (glyph - GLYPH_STATUE_MALE_PILETOP_OFF)) >= 0) { + text = "statue of a male monster at top of a pile"; + } else if ((offset = (glyph - GLYPH_BODY_PILETOP_OFF)) >= 0) { + text = "body at top of a pile"; + } else if ((offset = (glyph - GLYPH_OBJ_PILETOP_OFF)) >= 0) { + text = (glyph_is_piletop_generic_obj(glyph) + ? "generic object at top of a pile" + : "object at top of a pile"); + } else if ((offset = (glyph - GLYPH_STATUE_FEM_OFF)) >= 0) { + text = "statue of female monster"; + } else if ((offset = (glyph - GLYPH_STATUE_MALE_OFF)) >= 0) { + text = "statue of male monster"; + } else if ((offset = (glyph - GLYPH_WARNING_OFF)) >= 0) { + /* warn flash */ + text = "warning explosion"; + } else if ((offset = (glyph - GLYPH_EXPLODE_FROSTY_OFF)) >= 0) { + text = "frosty explosion"; + } else if ((offset = (glyph - GLYPH_EXPLODE_FIERY_OFF)) >= 0) { + text = "fiery explosion"; + } else if ((offset = (glyph - GLYPH_EXPLODE_MAGICAL_OFF)) >= 0) { + text = "magical explosion"; + } else if ((offset = (glyph - GLYPH_EXPLODE_WET_OFF)) >= 0) { + text = "wet explosion"; + } else if ((offset = (glyph - GLYPH_EXPLODE_MUDDY_OFF)) >= 0) { + text = "muddy explosion"; + } else if ((offset = (glyph - GLYPH_EXPLODE_NOXIOUS_OFF)) >= 0) { + text = "noxious explosion"; + } else if ((offset = (glyph - GLYPH_EXPLODE_DARK_OFF)) >= 0) { + text = "dark explosion"; + } else if ((offset = (glyph - GLYPH_SWALLOW_OFF)) >= 0) { + text = "swallow"; + } else if ((offset = (glyph - GLYPH_CMAP_C_OFF)) >= 0) { + text = "cmap C"; + } else if ((offset = (glyph - GLYPH_ZAP_OFF)) >= 0) { + text = "zap"; + } else if ((offset = (glyph - GLYPH_CMAP_B_OFF)) >= 0) { + text = "cmap B"; + } else if ((offset = (glyph - GLYPH_ALTAR_OFF)) >= 0) { + text = "altar"; + } else if ((offset = (glyph - GLYPH_CMAP_A_OFF)) >= 0) { + text = "cmap A"; + } else if ((offset = (glyph - GLYPH_CMAP_SOKO_OFF)) >= 0) { + text = "sokoban dungeon walls"; + } else if ((offset = (glyph - GLYPH_CMAP_KNOX_OFF)) >= 0) { + text = "knox dungeon walls"; + } else if ((offset = (glyph - GLYPH_CMAP_GEH_OFF)) >= 0) { + text = "gehennom dungeon walls"; + } else if ((offset = (glyph - GLYPH_CMAP_MINES_OFF)) >= 0) { + text = "gnomish mines dungeon walls"; + } else if ((offset = (glyph - GLYPH_CMAP_MAIN_OFF)) >= 0) { + text = "main dungeon walls"; + } else if ((offset = (glyph - GLYPH_CMAP_STONE_OFF)) >= 0) { + text = "stone"; + } else if ((offset = (glyph - GLYPH_OBJ_OFF)) >= 0) { + text = (glyph_is_normal_generic_obj(glyph) + ? "generic object" + : "object"); + } else if ((offset = (glyph - GLYPH_RIDDEN_FEM_OFF)) >= 0) { + text = "ridden female monster"; + } else if ((offset = (glyph - GLYPH_RIDDEN_MALE_OFF)) >= 0) { + text = "ridden male monster"; + } else if ((offset = (glyph - GLYPH_BODY_OFF)) >= 0) { + text = "body"; + } else if ((offset = (glyph - GLYPH_DETECT_FEM_OFF)) >= 0) { + text = "detected female monster"; + } else if ((offset = (glyph - GLYPH_DETECT_MALE_OFF)) >= 0) { + text = "detected male monster"; + } else if ((offset = (glyph - GLYPH_INVIS_OFF)) >= 0) { + text = "invisible monster"; + } else if ((offset = (glyph - GLYPH_PET_FEM_OFF)) >= 0) { + text = "female pet"; + } else if ((offset = (glyph - GLYPH_PET_MALE_OFF)) >= 0) { + text = "male pet"; + } else if ((offset = (glyph - GLYPH_MON_FEM_OFF)) >= 0) { + text = "female monster"; + } else if ((offset = (glyph - GLYPH_MON_MALE_OFF)) >= 0) { + text = "male monster"; } - - impossible("show_glyph: bad pos %d %d with glyph %d [%s %d].", x, y, - glyph, text, offset); + impossible("show_glyph: bad pos <%d,%d> with glyph %d [%s %d].", + x, y, glyph, text, offset); + return; + } else if (glyph < 0 || glyph >= MAX_GLYPH) { + /* valid location but invalid glyph */ + impossible("show_glyph: bad glyph %d [max %d] at <%d,%d>.", + glyph, MAX_GLYPH, x, y); return; } +#ifndef UNBUFFERED_GLYPHINFO + /* without UNBUFFERED_GLYPHINFO defined the glyphinfo values are buffered + alongside the glyphs themselves for better performance, increased + buffer size */ + map_glyphinfo(x, y, glyph, 0, &glyphinfo); +#endif - if (glyph >= MAX_GLYPH) { - impossible("show_glyph: bad glyph %d [max %d] at (%d,%d).", glyph, - MAX_GLYPH, x, y); - return; + oldglyph = gg.gbuf[y][x].glyphinfo.glyph; + + if (a11y.glyph_updates && !a11y.mon_notices_blocked + && !program_state.in_docrt && !program_state.gameover + && !program_state.in_getlev && !program_state.stopprint + && !_suppress_map_output() + && (oldglyph != glyph || gg.gbuf[y][x].gnew)) { + int c = glyph_to_cmap(glyph); + + if ((glyph_is_nothing(oldglyph) || glyph_is_unexplored(oldglyph) + || is_cmap_furniture(c)) + && !is_cmap_wall(c) && !is_cmap_room(c)) { + if ((a11y.mon_notices && glyph_is_monster(glyph)) + || (glyph_is_monster(oldglyph)) + || u_at(x, y)) { + ; /* nothing */ + } else { + show_glyph_change = TRUE; + } + } + } + + if (gg.gbuf[y][x].glyphinfo.glyph != glyph +#ifndef UNBUFFERED_GLYPHINFO + /* flags might change (single object vs pile, monster tamed or pet + gone feral), color might change (altar's alignment converted by + invisible hero), but ttychar normally won't change unless the + glyph does too (changing boulder symbol would be an exception, + but that triggers full redraw so doesn't matter here); still, + be thorough and check everything */ + || gg.gbuf[y][x].glyphinfo.ttychar != glyphinfo.ttychar + || gg.gbuf[y][x].glyphinfo.gm.customcolor != glyphinfo.gm.customcolor + || gg.gbuf[y][x].glyphinfo.gm.glyphflags != glyphinfo.gm.glyphflags + || gg.gbuf[y][x].glyphinfo.gm.sym.color != glyphinfo.gm.sym.color + || gg.gbuf[y][x].glyphinfo.gm.tileidx != glyphinfo.gm.tileidx +#endif + || iflags.use_background_glyph) { + gg.gbuf[y][x].glyphinfo.glyph = glyph; + gg.gbuf[y][x].gnew = 1; +#ifndef UNBUFFERED_GLYPHINFO + gg.gbuf[y][x].glyphinfo.glyph = glyphinfo.glyph; + gg.gbuf[y][x].glyphinfo.ttychar = glyphinfo.ttychar; + gg.gbuf[y][x].glyphinfo.gm = glyphinfo.gm; +#endif + if (gg.gbuf_start[y] > x) + gg.gbuf_start[y] = x; + if (gg.gbuf_stop[y] < x) + gg.gbuf_stop[y] = x; } - if (gbuf[y][x].glyph != glyph || iflags.use_background_glyph) { - gbuf[y][x].glyph = glyph; - gbuf[y][x].new = 1; - if (gbuf_start[y] > x) - gbuf_start[y] = x; - if (gbuf_stop[y] < x) - gbuf_stop[y] = x; + if (show_glyph_change) { + char buf[BUFSZ]; + coord cc; + int sym = 0; + const char *firstmatch = 0; + boolean tmp_accessiblemsg = a11y.accessiblemsg; + + a11y.accessiblemsg = TRUE; + cc.x = x, cc.y = y; + do_screen_description(cc, TRUE, sym, buf, &firstmatch, NULL); + pline_xy(x, y, "%s.", firstmatch); + a11y.accessiblemsg = tmp_accessiblemsg; } } @@ -1574,51 +2075,118 @@ int x, y, glyph; * Reset the changed glyph borders so that none of the 3rd screen has * changed. */ -#define reset_glyph_bbox() \ - { \ - int i; \ - \ - for (i = 0; i < ROWNO; i++) { \ - gbuf_start[i] = COLNO - 1; \ - gbuf_stop[i] = 0; \ - } \ +#define reset_glyph_bbox() \ + { \ + int i; \ + \ + for (i = 0; i < ROWNO; i++) { \ + gg.gbuf_start[i] = COLNO - 1; \ + gg.gbuf_stop[i] = 0; \ + } \ } -static gbuf_entry nul_gbuf = { 0, cmap_to_glyph(S_stone) }; +static gbuf_entry nul_gbuf = { + 0, /* gnew */ + { GLYPH_UNEXPLORED, (unsigned) ' ', NO_COLOR, + /* glyphinfo.glyph, glyphinfo.ttychar */ + /* glyphinfo.gm */ + { MG_UNEXPL, { NO_COLOR, 0 }, + 0, + 0U, 0U +#ifdef ENHANCED_SYMBOLS + , 0 +#endif + } + } +}; + /* - * Turn the 3rd screen into stone. + * Turn the 3rd screen into UNEXPLORED that needs to be refreshed. */ void -clear_glyph_buffer() +clear_glyph_buffer(void) { - register int x, y; - register gbuf_entry *gptr; + coordxy x, y; + gbuf_entry *gptr = &gg.gbuf[0][0]; + glyph_info *giptr = +#ifndef UNBUFFERED_GLYPHINFO + &gptr->glyphinfo; +#else + &ginfo; + map_glyphinfo(0, 0, GLYPH_UNEXPLORED, 0, giptr); +#endif +#ifndef UNBUFFERED_GLYPHINFO + nul_gbuf.gnew = (giptr->ttychar != nul_gbuf.glyphinfo.ttychar + || giptr->gm.sym.color != nul_gbuf.glyphinfo.gm.sym.color + || giptr->gm.glyphflags + != nul_gbuf.glyphinfo.gm.glyphflags + || giptr->gm.customcolor + != nul_gbuf.glyphinfo.gm.customcolor + || giptr->gm.tileidx != nul_gbuf.glyphinfo.gm.tileidx) +#else + nul_gbuf.gnew = (giptr->ttychar != ' ' + || giptr->gm.sym.color != NO_COLOR + || giptr->gm.customcolor != 0 + || (giptr->gm.glyphflags & ~MG_UNEXPL) != 0) +#endif + ? 1 : 0; for (y = 0; y < ROWNO; y++) { - gptr = &gbuf[y][0]; + gptr = &gg.gbuf[y][0]; for (x = COLNO; x; x--) { *gptr++ = nul_gbuf; } + gg.gbuf_start[y] = 1; + gg.gbuf_stop[y] = COLNO - 1; } - reset_glyph_bbox(); } -/* - * Assumes that the indicated positions are filled with S_stone glyphs. - */ +/* used by tty after menu or text popup has temporarily overwritten the map + and it has been erased so shows spaces, not necessarily S_unexplored */ void -row_refresh(start, stop, y) -int start, stop, y; +row_refresh(coordxy start, coordxy stop, coordxy y) { - register int x; + coordxy x; + int glyph; + boolean force; + gbuf_entry *gptr = &gg.gbuf[0][0]; + glyph_info bkglyphinfo = nul_glyphinfo; + glyph_info *giptr = +#ifndef UNBUFFERED_GLYPHINFO + &gptr->glyphinfo; +#else + &ginfo; - for (x = start; x <= stop; x++) - if (gbuf[y][x].glyph != cmap_to_glyph(S_stone)) - print_glyph(WIN_MAP, x, y, gbuf[y][x].glyph, get_bk_glyph(x, y)); + map_glyphinfo(0, 0, GLYPH_UNEXPLORED, 0U, giptr); +#endif +#ifndef UNBUFFERED_GLYPHINFO + force = (giptr->ttychar != nul_gbuf.glyphinfo.ttychar + || giptr->gm.sym.color != nul_gbuf.glyphinfo.gm.sym.color + || giptr->gm.glyphflags != nul_gbuf.glyphinfo.gm.glyphflags + || giptr->gm.customcolor != nul_gbuf.glyphinfo.gm.customcolor + || giptr->gm.tileidx != nul_gbuf.glyphinfo.gm.tileidx) +#else + force = (giptr->ttychar != ' ' + || giptr->gm.sym.color != NO_COLOR + || giptr->gm.gm.customcolor != 0 + || (giptr->gm.glyphflags & ~MG_UNEXPL) != 0) +#endif + ? 1 : 0; + for (x = start; x <= stop; x++) { + gptr = &gg.gbuf[y][x]; + glyph = gptr->glyphinfo.glyph; + get_bkglyph_and_framecolor(x, y, &bkglyphinfo.glyph, + &bkglyphinfo.framecolor); + if (force || glyph != GLYPH_UNEXPLORED + || bkglyphinfo.framecolor != NO_COLOR) { + print_glyph(WIN_MAP, x, y, + Glyphinfo_at(x, y, glyph), &bkglyphinfo); + } + } } void -cls() +cls(void) { static boolean in_cls = 0; @@ -1626,10 +2194,10 @@ cls() return; in_cls = TRUE; display_nhwindow(WIN_MESSAGE, FALSE); /* flush messages */ - context.botlx = 1; /* force update of botl window */ + disp.botlx = TRUE; /* force update of botl window */ clear_nhwindow(WIN_MAP); /* clear physical screen */ - clear_glyph_buffer(); /* this is sort of an extra effort, but OK */ + clear_glyph_buffer(); /* force gbuf[][].glyph to unexplored */ in_cls = FALSE; } @@ -1637,15 +2205,20 @@ cls() * Synch the third screen with the display. */ void -flush_screen(cursor_on_u) -int cursor_on_u; +flush_screen(int cursor_on_u) { /* Prevent infinite loops on errors: * flush_screen->print_glyph->impossible->pline->flush_screen */ static int flushing = 0; static int delay_flushing = 0; - register int x, y; + coordxy x, y; + glyph_info bkglyphinfo = nul_glyphinfo; + int bkglyph; + + /* 5.0: don't update map, status, or perm_invent during save/restore */ + if (_suppress_map_output()) + return; if (cursor_on_u == -1) delay_flushing = !delay_flushing; @@ -1659,25 +2232,38 @@ int cursor_on_u; return; #endif - for (y = 0; y < ROWNO; y++) { - register gbuf_entry *gptr = &gbuf[y][x = gbuf_start[y]]; + /* get this done now, before we place the cursor on the hero */ + if (disp.botl || disp.botlx) + bot(); + else if (disp.time_botl) + timebot(); - for (; x <= gbuf_stop[y]; gptr++, x++) - if (gptr->new) { - print_glyph(WIN_MAP, x, y, gptr->glyph, get_bk_glyph(x, y)); - gptr->new = 0; + for (y = 0; y < ROWNO; y++) { + gbuf_entry *gptr = &gg.gbuf[y][x = gg.gbuf_start[y]]; + + for (; x <= gg.gbuf_stop[y]; gptr++, x++) { + get_bkglyph_and_framecolor(x, y, &bkglyph, + &bkglyphinfo.framecolor); + if (gptr->gnew + || (gw.wsettings.map_frame_color != NO_COLOR + && bkglyphinfo.framecolor != NO_COLOR)) { + /* map_glyphinfo() won't touch framecolor */ + map_glyphinfo(x, y, bkglyph, 0, &bkglyphinfo); + print_glyph(WIN_MAP, x, y, + Glyphinfo_at(x, y, gptr->glyphinfo.glyph), + &bkglyphinfo); + gptr->gnew = 0; } + } } + reset_glyph_bbox(); + /* after map update, before display_nhwindow(WIN_MAP) */ if (cursor_on_u) curs(WIN_MAP, u.ux, u.uy); /* move cursor to the hero */ + display_nhwindow(WIN_MAP, FALSE); - reset_glyph_bbox(); flushing = 0; - if (context.botl || context.botlx) - bot(); - else if (iflags.time_botl) - timebot(); } /* ======================================================================== */ @@ -1698,16 +2284,16 @@ int cursor_on_u; * variables. */ int -back_to_glyph(x, y) -xchar x, y; +back_to_glyph(coordxy x, coordxy y) { - int idx; + int idx, bypass_glyph = NO_GLYPH; struct rm *ptr = &(levl[x][y]); + struct stairway *sway; switch (ptr->typ) { case SCORR: case STONE: - idx = level.flags.arboreal ? S_tree : S_stone; + idx = svl.level.flags.arboreal ? S_tree : S_stone; break; case ROOM: idx = S_room; @@ -1715,6 +2301,13 @@ xchar x, y; case CORR: idx = (ptr->waslit || flags.lit_corridor) ? S_litcorr : S_corr; break; + case SDOOR: + if (ptr->arboreal_sdoor) { + idx = S_tree; + break; + } + FALLTHROUGH; + /*FALLTHRU*/ case HWALL: case VWALL: case TLCORNER: @@ -1726,7 +2319,6 @@ xchar x, y; case TDWALL: case TLWALL: case TRWALL: - case SDOOR: idx = ptr->seenv ? wall_angle(ptr) : S_stone; break; case DOOR: @@ -1751,10 +2343,18 @@ xchar x, y; idx = S_pool; break; case STAIRS: - idx = (ptr->ladder & LA_DOWN) ? S_dnstair : S_upstair; + sway = stairway_at(x, y); + if (known_branch_stairs(sway)) + idx = (ptr->ladder & LA_DOWN) ? S_brdnstair : S_brupstair; + else + idx = (ptr->ladder & LA_DOWN) ? S_dnstair : S_upstair; break; case LADDER: - idx = (ptr->ladder & LA_DOWN) ? S_dnladder : S_upladder; + sway = stairway_at(x, y); + if (known_branch_stairs(sway)) + idx = (ptr->ladder & LA_DOWN) ? S_brdnladder : S_brupladder; + else + idx = (ptr->ladder & LA_DOWN) ? S_dnladder : S_upladder; break; case FOUNTAIN: idx = S_fountain; @@ -1763,7 +2363,8 @@ xchar x, y; idx = S_sink; break; case ALTAR: - idx = S_altar; + idx = S_altar; /* not really used */ + bypass_glyph = altar_to_glyph(ptr->altarmask); break; case GRAVE: idx = S_grave; @@ -1774,6 +2375,9 @@ xchar x, y; case LAVAPOOL: idx = S_lava; break; + case LAVAWALL: + idx = S_lavawall; + break; case ICE: idx = S_ice; break; @@ -1819,7 +2423,7 @@ xchar x, y; break; } - return cmap_to_glyph(idx); + return (bypass_glyph != NO_GLYPH) ? bypass_glyph : cmap_to_glyph(idx); } /* @@ -1829,17 +2433,16 @@ xchar x, y; * If you don't want a patchwork monster while hallucinating, decide on * a random monster in swallowed() and don't use what_mon() here. */ -STATIC_OVL int -swallow_to_glyph(mnum, loc) -int mnum; -int loc; +staticfn int +swallow_to_glyph(int mnum, int loc) { + int m_3 = what_mon(mnum, rn2_on_display_rng) << 3; + if (loc < S_sw_tl || S_sw_br < loc) { impossible("swallow_to_glyph: bad swallow location"); loc = S_sw_br; } - return ((int) (what_mon(mnum, rn2_on_display_rng) << 3) | - (loc - S_sw_tl)) + GLYPH_SWALLOW_OFF; + return (m_3 | (loc - S_sw_tl)) + GLYPH_SWALLOW_OFF; } /* @@ -1847,7 +2450,7 @@ int loc; * * Change the given zap direction and beam type into a glyph. Each beam * type has four glyphs, one for each of the symbols below. The order of - * the zap symbols [0-3] as defined in rm.h are: + * the zap symbols [0-3] as defined in defsym.h are: * * | S_vbeam ( 0, 1) or ( 0,-1) * - S_hbeam ( 1, 0) or (-1, 0) @@ -1855,9 +2458,7 @@ int loc; * / S_rslant (-1, 1) or ( 1,-1) */ int -zapdir_to_glyph(dx, dy, beam_type) -register int dx, dy; -int beam_type; +zapdir_to_glyph(int dx, int dy, int beam_type) { if (beam_type >= NUM_ZAP) { impossible("zapdir_to_glyph: illegal beam type"); @@ -1874,40 +2475,49 @@ int beam_type; * structure, so we must check the "third screen". */ int -glyph_at(x, y) -xchar x, y; +glyph_at(coordxy x, coordxy y) { if (x < 0 || y < 0 || x >= COLNO || y >= ROWNO) return cmap_to_glyph(S_room); /* XXX */ - return gbuf[y][x].glyph; + return gg.gbuf[y][x].glyphinfo.glyph; /* _glyph_at(x,y) */ +} + +#ifdef UNBUFFERED_GLYPHINFO +glyph_info * +glyphinfo_at(coordxy x, coordxy y, int glyph) +{ + map_glyphinfo(x, y, glyph, 0, &ginfo); /* ginfo declared at file scope */ + return &ginfo; } +#endif /* - * This will be used to get the glyph for the background so that - * it can potentially be merged into graphical window ports to - * improve the appearance of stuff on dark room squares and the - * plane of air etc. - * - * Until that is working correctly in the branch, however, for now - * we just return NO_GLYPH as an indicator to ignore it. - * + * Get the glyph for the background so that it can potentially + * be merged into graphical window ports to improve the appearance + * of stuff on dark room squares and the plane of air etc. * [This should be using background as recorded for #overview rather * than current data from the map.] + * + * Also get the frame color to use for highlighting the map location + * for some purpose. + * */ -STATIC_OVL int -get_bk_glyph(x, y) -xchar x, y; +staticfn void +get_bkglyph_and_framecolor( + coordxy x, coordxy y, + int *bkglyph, + uint32 *framecolor) { - int idx, bkglyph = NO_GLYPH; + int idx, tmp_bkglyph = GLYPH_UNEXPLORED; struct rm *lev = &levl[x][y]; if (iflags.use_background_glyph && lev->seenv != 0 - && gbuf[y][x].glyph != cmap_to_glyph(S_stone)) { + && (gg.gbuf[y][x].glyphinfo.glyph != GLYPH_UNEXPLORED)) { switch (lev->typ) { case SCORR: case STONE: - idx = level.flags.arboreal ? S_tree : S_stone; + idx = svl.level.flags.arboreal ? S_tree : S_stone; break; case ROOM: idx = S_room; @@ -1934,24 +2544,545 @@ xchar x, y; case LAVAPOOL: idx = S_lava; break; + case LAVAWALL: + idx = S_lavawall; + break; default: idx = S_room; break; } if (!cansee(x, y) && (!lev->waslit || flags.dark_room)) { - /* Floor spaces are dark if unlit. Corridors are dark if unlit. */ + /* Floor spaces and corridors are dark if unlit. */ if (lev->typ == CORR && idx == S_litcorr) idx = S_corr; else if (idx == S_room) idx = (flags.dark_room && iflags.use_color) ? DARKROOMSYM : S_stone; } - if (idx != S_room) - bkglyph = cmap_to_glyph(idx); + tmp_bkglyph = cmap_to_glyph(idx); + } + *bkglyph = tmp_bkglyph; +#if 0 + /* this guard should be unnecessary */ + if (!framecolor) { + impossible("null framecolor passed to get_bkglyph_and_framecolor"); + return; + } +#endif + if (iflags.bgcolors + && gw.wsettings.map_frame_color != NO_COLOR && mapxy_valid(x, y)) + *framecolor = gw.wsettings.map_frame_color; + else + *framecolor = NO_COLOR; +} + +#if defined(TILES_IN_GLYPHMAP) && defined(MSDOS) +#define HAS_ROGUE_IBM_GRAPHICS \ + (gc.currentgraphics == ROGUESET && SYMHANDLING(H_IBM) && !iflags.grmode) +#else +#define HAS_ROGUE_IBM_GRAPHICS \ + (gc.currentgraphics == ROGUESET && SYMHANDLING(H_IBM)) +#endif + +/* masks for per-level variances kept in gg.glyphmap_perlevel_flags */ +#define GMAP_SET 0x0001 +#define GMAP_ROGUELEVEL 0x0002 + +void +map_glyphinfo( + coordxy x, coordxy y, + int glyph, + unsigned mgflags, + glyph_info *glyphinfo) +{ + int offset; + boolean is_you = (u_at(x, y) + /* verify hero or steed (not something underneath + when hero is invisible without see invisible, + or a temporary display effect like an explosion); + this is only approximate, because hero might be + mimicking an object (after eating mimic corpse or + if polyd into mimic) or furniture (only if polyd) */ + && glyph_is_monster(glyph)); + const glyph_map *gmap = &glyphmap[glyph]; + + glyphinfo->gm = *gmap; /* glyphflags, sym.symidx, sym.color, tileidx */ + /* + * Only hero tinkering permitted on-the-fly (who). + * Unique glyphs in glyphmap[] determine everything else (what). + * + * (Note: if hero is invisible without see invisible, he/she usually + * can't see himself/herself. This applies to accessibility hacks + * as well as to regular display.) + */ + if (is_you) { + if (!iflags.use_color || Upolyd || glyph != hero_glyph) { + ; /* color tweak not needed (!use_color) or not wanted (poly'd + or riding--which uses steed's color, not hero's) */ + } else if (HAS_ROGUE_IBM_GRAPHICS + && gs.symset[gc.currentgraphics].nocolor == 0) { + /* actually player should be yellow-on-gray if in corridor */ + glyphinfo->gm.sym.color = CLR_YELLOW; + } else if (flags.showrace) { + /* for showrace, non-human hero is displayed by the symbol of + corresponding type of monster rather than by '@' (handled + by newsym()); we change the color to same as human hero */ + glyphinfo->gm.sym.color = HI_DOMESTIC; + } + /* accessibility + This unchanging display character for hero was requested by + a blind player to enhance screen reader use. + Turn on override symbol if caller hasn't specified NOOVERRIDE. */ + if (sysopt.accessibility == 1 && !(mgflags & MG_FLAG_NOOVERRIDE)) { + offset = SYM_HERO_OVERRIDE + SYM_OFF_X; + if ((gg.glyphmap_perlevel_flags & GMAP_ROGUELEVEL) + ? go.ov_rogue_syms[offset] + : go.ov_primary_syms[offset]) + glyphinfo->gm.sym.symidx = offset; + } + glyphinfo->gm.glyphflags |= MG_HERO; } - return bkglyph; + if (sysopt.accessibility == 1 + && (mgflags & MG_FLAG_NOOVERRIDE) && glyph_is_pet(glyph)) { + /* one more accessibility kludge; + turn off override symbol if caller has specified NOOVERRIDE */ + glyphinfo->gm.sym.symidx = mons[glyph_to_mon(glyph)].mlet + SYM_OFF_M; + } + glyphinfo->ttychar = gs.showsyms[glyphinfo->gm.sym.symidx]; + glyphinfo->glyph = glyph; +} + +/* + * This must be the same order as used for buzz() in zap.c. + * The zap_color_ and altar_color_ enums are in decl.h. + */ +const int zapcolors[NUM_ZAP] = { + zap_color_missile, zap_color_fire, zap_color_frost, + zap_color_sleep, zap_color_death, zap_color_lightning, + zap_color_poison_gas, zap_color_acid, +}; +const int altarcolors[] = { + altar_color_unaligned, altar_color_chaotic, altar_color_neutral, + altar_color_lawful, altar_color_other +}; +const int explodecolors[EXPL_MAX] = { + explode_color_dark, explode_color_noxious, explode_color_muddy, + explode_color_wet, explode_color_magical, explode_color_fiery, + explode_color_frosty, +}; + +/* main_walls, mines_walls, gehennom_walls, knox_walls, sokoban_walls */ +int wallcolors[sokoban_walls + 1] = { + /* default init value is to match defsym[S_vwall + n].color (CLR_GRAY) */ + CLR_GRAY, CLR_GRAY, CLR_GRAY, CLR_GRAY, CLR_GRAY, + /* CLR_GRAY, CLR_BROWN, CLR_RED, CLR_GRAY, CLR_BRIGHT_BLUE, */ +}; + +#define zap_color(n) color = iflags.use_color ? zapcolors[n] : NO_COLOR +#define cmap_color(n) color = iflags.use_color ? defsyms[n].color : NO_COLOR +#define obj_color(n) color = iflags.use_color ? objects[n].oc_color : NO_COLOR +#define mon_color(n) color = iflags.use_color ? mons[n].mcolor : NO_COLOR +#define invis_color(n) color = NO_COLOR +#define pet_color(n) color = iflags.use_color ? mons[n].mcolor : NO_COLOR +#define warn_color(n) \ + color = iflags.use_color ? def_warnsyms[n].color : NO_COLOR +#define explode_color(n) \ + color = iflags.use_color ? explodecolors[n] : NO_COLOR +#define wall_color(n) color = iflags.use_color ? wallcolors[n] : NO_COLOR +#define altar_color(n) color = iflags.use_color ? altarcolors[n] : NO_COLOR + +staticfn int cmap_to_roguecolor(int); + +staticfn int +cmap_to_roguecolor(int cmap) +{ + int color = NO_COLOR; + + if (gs.symset[gc.currentgraphics].nocolor) + return NO_COLOR; + + if (cmap >= S_vwall && cmap <= S_hcdoor) + color = CLR_BROWN; + else if (cmap >= S_arrow_trap && cmap <= S_polymorph_trap) + color = CLR_MAGENTA; + else if (cmap == S_corr || cmap == S_litcorr) + color = CLR_GRAY; + else if (cmap >= S_room && cmap <= S_water + && cmap != S_darkroom) + color = CLR_GREEN; + else + color = NO_COLOR; + + return color; +} + +/* + reset_glyphmap(trigger) + + The glyphmap likely needs to be re-calculated for the following triggers: + + gm_levelchange called when the player has gone to a new level. + + gm_symchange called if someone has interactively altered the symbols + for the game, most likely at the options menu. + + gm_optionchange The game settings have been toggled and some of them + are configured to trigger a reset_glyphmap() + + gm_accessibility_change One of the accessibility flags has changed. + +*/ + +void +reset_glyphmap(enum glyphmap_change_triggers trigger) +{ + int glyph; + int offset; + int color = NO_COLOR; + + /* condense multiple tests in macro version down to single */ + boolean has_rogue_ibm_graphics = HAS_ROGUE_IBM_GRAPHICS, + has_rogue_color = (has_rogue_ibm_graphics + && gs.symset[gc.currentgraphics].nocolor == 0); + if (trigger == gm_levelchange) + gg.glyphmap_perlevel_flags = 0; + + if (!gg.glyphmap_perlevel_flags) { + /* + * GMAP_SET 0x00000001 + * GMAP_ROGUELEVEL 0x00000002 + */ + gg.glyphmap_perlevel_flags |= GMAP_SET; + + if (Is_rogue_level(&u.uz)) { + gg.glyphmap_perlevel_flags |= GMAP_ROGUELEVEL; + } + } + + for (glyph = 0; glyph < MAX_GLYPH; ++glyph) { + glyph_map *gmap = &glyphmap[glyph]; + + gmap->glyphflags = 0U; + /* + * Map the glyph to a character and color. + * + * Warning: For speed, this makes an assumption on the order of + * offsets. The order is set in display.h. + */ + if ((offset = (glyph - GLYPH_NOTHING_OFF)) >= 0) { + gmap->sym.symidx = SYM_NOTHING + SYM_OFF_X; + color = NO_COLOR; + gmap->glyphflags |= MG_NOTHING; + } else if ((offset = (glyph - GLYPH_UNEXPLORED_OFF)) >= 0) { + gmap->sym.symidx = SYM_UNEXPLORED + SYM_OFF_X; + color = NO_COLOR; + gmap->glyphflags |= MG_UNEXPL; + } else if ((offset = (glyph - GLYPH_STATUE_FEM_PILETOP_OFF)) >= 0) { + gmap->sym.symidx = mons[offset].mlet + SYM_OFF_M; + if (has_rogue_color) + color = CLR_RED; + else + obj_color(STATUE); + gmap->glyphflags |= (MG_STATUE | MG_FEMALE | MG_OBJPILE); + } else if ((offset = (glyph - GLYPH_STATUE_MALE_PILETOP_OFF)) >= 0) { + gmap->sym.symidx = mons[offset].mlet + SYM_OFF_M; + if (has_rogue_color) + color = CLR_RED; + else + obj_color(STATUE); + gmap->glyphflags |= (MG_STATUE | MG_MALE | MG_OBJPILE); + } else if ((offset = (glyph - GLYPH_BODY_PILETOP_OFF)) >= 0) { + gmap->sym.symidx = objects[CORPSE].oc_class + SYM_OFF_O; + if (has_rogue_color) + color = CLR_RED; + else + mon_color(offset); + gmap->glyphflags |= (MG_CORPSE | MG_OBJPILE); + } else if ((offset = (glyph - GLYPH_OBJ_PILETOP_OFF)) >= 0) { + gmap->sym.symidx = objects[offset].oc_class + SYM_OFF_O; + if (offset == BOULDER) + gmap->sym.symidx = SYM_BOULDER + SYM_OFF_X; + if (has_rogue_color) { + switch (objects[offset].oc_class) { + case COIN_CLASS: + color = CLR_YELLOW; + break; + case FOOD_CLASS: + color = CLR_RED; + break; + default: + color = CLR_BRIGHT_BLUE; + break; + } + } else + obj_color(offset); + gmap->glyphflags |= MG_OBJPILE; + } else if ((offset = (glyph - GLYPH_STATUE_FEM_OFF)) >= 0) { + gmap->sym.symidx = mons[offset].mlet + SYM_OFF_M; + if (has_rogue_color) + color = CLR_RED; + else + obj_color(STATUE); + gmap->glyphflags |= (MG_STATUE | MG_FEMALE); + } else if ((offset = (glyph - GLYPH_STATUE_MALE_OFF)) >= 0) { + gmap->sym.symidx = mons[offset].mlet + SYM_OFF_M; + if (has_rogue_color) + color = CLR_RED; + else + obj_color(STATUE); + gmap->glyphflags |= (MG_STATUE | MG_MALE); + } else if ((offset = (glyph - GLYPH_WARNING_OFF)) + >= 0) { /* warn flash */ + gmap->sym.symidx = offset + SYM_OFF_W; + if (has_rogue_color) + color = NO_COLOR; + else + warn_color(offset); + } else if ((offset = (glyph - GLYPH_EXPLODE_FROSTY_OFF)) >= 0) { + gmap->sym.symidx = S_expl_tl + offset + SYM_OFF_P; + explode_color(expl_frosty); + } else if ((offset = (glyph - GLYPH_EXPLODE_FIERY_OFF)) >= 0) { + gmap->sym.symidx = S_expl_tl + offset + SYM_OFF_P; + explode_color(expl_fiery); + } else if ((offset = (glyph - GLYPH_EXPLODE_MAGICAL_OFF)) >= 0) { + gmap->sym.symidx = S_expl_tl + offset + SYM_OFF_P; + explode_color(expl_magical); + } else if ((offset = (glyph - GLYPH_EXPLODE_WET_OFF)) >= 0) { + gmap->sym.symidx = S_expl_tl + offset + SYM_OFF_P; + explode_color(expl_wet); + } else if ((offset = (glyph - GLYPH_EXPLODE_MUDDY_OFF)) >= 0) { + gmap->sym.symidx = S_expl_tl + offset + SYM_OFF_P; + explode_color(expl_muddy); + } else if ((offset = (glyph - GLYPH_EXPLODE_NOXIOUS_OFF)) >= 0) { + gmap->sym.symidx = S_expl_tl + offset + SYM_OFF_P; + explode_color(expl_noxious); + } else if ((offset = (glyph - GLYPH_EXPLODE_DARK_OFF)) >= 0) { + gmap->sym.symidx = S_expl_tl + offset + SYM_OFF_P; + explode_color(expl_dark); + } else if ((offset = (glyph - GLYPH_SWALLOW_OFF)) >= 0) { + /* see swallow_to_glyph() in display.c */ + gmap->sym.symidx = (S_sw_tl + (offset & 0x7)) + SYM_OFF_P; + if (has_rogue_color) + color = NO_COLOR; + else + mon_color(offset >> 3); + } else if ((offset = (glyph - GLYPH_CMAP_C_OFF)) >= 0) { + gmap->sym.symidx = S_digbeam + offset + SYM_OFF_P; + if (has_rogue_color) + color = cmap_to_roguecolor(S_digbeam + offset); + else + cmap_color(S_digbeam + offset); + } else if ((offset = (glyph - GLYPH_ZAP_OFF)) >= 0) { + /* see zapdir_to_glyph() in display.c */ + gmap->sym.symidx = (S_vbeam + (offset & 0x3)) + SYM_OFF_P; + if (has_rogue_color) + color = NO_COLOR; + else + zap_color((offset >> 2)); + } else if ((offset = (glyph - GLYPH_CMAP_B_OFF)) >= 0) { + int cmap = S_grave + offset; + int sym = gs.showsyms[cmap + SYM_OFF_P]; + + gmap->sym.symidx = cmap + SYM_OFF_P; + cmap_color(cmap); + if (!iflags.use_color) { + unsigned spec_cmap = 0; + + /* try to provide a visible difference between water and lava + if they use the same symbol and color is disabled; + similar for floor and ice, for fountain vs sink, and for + corridor engravings (CMAP_A below) */ + switch (cmap) { + case S_lava: + case S_lavawall: + if (sym == gs.showsyms[S_pool + SYM_OFF_P] + || sym == gs.showsyms[S_water + SYM_OFF_P]) + spec_cmap = MG_BW_LAVA; + break; + case S_ice: + if (sym == gs.showsyms[S_room + SYM_OFF_P] + || sym == gs.showsyms[S_darkroom + SYM_OFF_P]) + spec_cmap = MG_BW_ICE; + break; + case S_sink: + if (sym == gs.showsyms[S_fountain + SYM_OFF_P]) + spec_cmap = MG_BW_SINK; + break; + } + gmap->glyphflags |= spec_cmap; + } else if (has_rogue_color) { + color = cmap_to_roguecolor(cmap); + } + } else if ((offset = (glyph - GLYPH_ALTAR_OFF)) >= 0) { + /* unaligned, chaotic, neutral, lawful, other altar */ + gmap->sym.symidx = S_altar + SYM_OFF_P; + if (has_rogue_color) + color = cmap_to_roguecolor(S_altar); + else + altar_color(offset); + } else if ((offset = (glyph - GLYPH_CMAP_A_OFF)) >= 0) { + int sym, cmap = S_ndoor + offset; + + gmap->sym.symidx = cmap + SYM_OFF_P; + cmap_color(cmap); + sym = gs.showsyms[gmap->sym.symidx]; + /* + * Some specialty color mappings not hardcoded in data init + */ + if (has_rogue_color) { + color = cmap_to_roguecolor(cmap); + /* provide a visible difference if normal and lit corridor + use the same symbol */ + } else if (cmap == S_litcorr + && sym == gs.showsyms[S_corr + SYM_OFF_P]) { + color = CLR_WHITE; + /* likewise for corridor and engraving-in-corridor */ + } else if (cmap == S_engrcorr + && (sym == gs.showsyms[S_corr + SYM_OFF_P] + || sym == gs.showsyms[S_litcorr + SYM_OFF_P])) { + gmap->glyphflags |= MG_BW_ENGR; + } + } else if ((offset = (glyph - GLYPH_CMAP_SOKO_OFF)) >= 0) { + gmap->sym.symidx = S_vwall + offset + SYM_OFF_P; + wall_color(sokoban_walls); + } else if ((offset = (glyph - GLYPH_CMAP_KNOX_OFF)) >= 0) { + gmap->sym.symidx = S_vwall + offset + SYM_OFF_P; + wall_color(knox_walls); + } else if ((offset = (glyph - GLYPH_CMAP_GEH_OFF)) >= 0) { + gmap->sym.symidx = S_vwall + offset + SYM_OFF_P; + wall_color(gehennom_walls); + } else if ((offset = (glyph - GLYPH_CMAP_MINES_OFF)) >= 0) { + gmap->sym.symidx = S_vwall + offset + SYM_OFF_P; + wall_color(mines_walls); + } else if ((offset = (glyph - GLYPH_CMAP_MAIN_OFF)) >= 0) { + gmap->sym.symidx = S_vwall + offset + SYM_OFF_P; + if (has_rogue_color) + color = cmap_to_roguecolor(S_vwall + offset); + else + wall_color(main_walls); + } else if ((offset = (glyph - GLYPH_CMAP_STONE_OFF)) >= 0) { + gmap->sym.symidx = SYM_OFF_P; + cmap_color(S_stone); + } else if ((offset = (glyph - GLYPH_OBJ_OFF)) >= 0) { + gmap->sym.symidx = objects[offset].oc_class + SYM_OFF_O; + if (offset == BOULDER) + gmap->sym.symidx = SYM_BOULDER + SYM_OFF_X; + if (has_rogue_color) { + switch (objects[offset].oc_class) { + case COIN_CLASS: + color = CLR_YELLOW; + break; + case FOOD_CLASS: + color = CLR_RED; + break; + default: + color = CLR_BRIGHT_BLUE; + break; + } + } else + obj_color(offset); + } else if ((offset = (glyph - GLYPH_RIDDEN_FEM_OFF)) >= 0) { + gmap->sym.symidx = mons[offset].mlet + SYM_OFF_M; + if (has_rogue_color) + /* This currently implies that the hero is here -- monsters */ + /* don't ride (yet...). Should we set it to yellow like in */ + /* the monster case below? There is no equivalent in rogue. + */ + color = NO_COLOR; + else + mon_color(offset); + gmap->glyphflags |= (MG_RIDDEN | MG_FEMALE); + } else if ((offset = (glyph - GLYPH_RIDDEN_MALE_OFF)) >= 0) { + gmap->sym.symidx = mons[offset].mlet + SYM_OFF_M; + if (has_rogue_color) + color = NO_COLOR; + else + mon_color(offset); + gmap->glyphflags |= (MG_RIDDEN | MG_MALE); + } else if ((offset = (glyph - GLYPH_BODY_OFF)) >= 0) { + gmap->sym.symidx = objects[CORPSE].oc_class + SYM_OFF_O; + if (has_rogue_color) + color = CLR_RED; + else + mon_color(offset); + gmap->glyphflags |= MG_CORPSE; + } else if ((offset = (glyph - GLYPH_DETECT_FEM_OFF)) >= 0) { + gmap->sym.symidx = mons[offset].mlet + SYM_OFF_M; + if (has_rogue_color) + color = NO_COLOR; + else + mon_color(offset); + /* Disabled for now; anyone want to get reverse video to work? */ + /* is_reverse = TRUE; */ + gmap->glyphflags |= (MG_DETECT | MG_FEMALE); + } else if ((offset = (glyph - GLYPH_DETECT_MALE_OFF)) >= 0) { + gmap->sym.symidx = mons[offset].mlet + SYM_OFF_M; + if (has_rogue_color) + color = NO_COLOR; + else + mon_color(offset); + /* Disabled for now; anyone want to get reverse video to work? */ + /* is_reverse = TRUE; */ + gmap->glyphflags |= (MG_DETECT | MG_MALE); + } else if ((offset = (glyph - GLYPH_INVIS_OFF)) >= 0) { + gmap->sym.symidx = SYM_INVISIBLE + SYM_OFF_X; + if (has_rogue_color) + color = NO_COLOR; + else + invis_color(offset); + gmap->glyphflags |= MG_INVIS; + } else if ((offset = (glyph - GLYPH_PET_FEM_OFF)) >= 0) { + gmap->sym.symidx = mons[offset].mlet + SYM_OFF_M; + if (has_rogue_color) + color = NO_COLOR; + else + pet_color(offset); + gmap->glyphflags |= (MG_PET | MG_FEMALE); + } else if ((offset = (glyph - GLYPH_PET_MALE_OFF)) >= 0) { + gmap->sym.symidx = mons[offset].mlet + SYM_OFF_M; + if (has_rogue_color) + color = NO_COLOR; + else + pet_color(offset); + gmap->glyphflags |= (MG_PET | MG_MALE); + } else if ((offset = (glyph - GLYPH_MON_FEM_OFF)) >= 0) { + gmap->sym.symidx = mons[offset].mlet + SYM_OFF_M; + if (has_rogue_color) { + color = NO_COLOR; + } else { + mon_color(offset); + } + gmap->glyphflags |= MG_FEMALE; + } else if ((offset = (glyph - GLYPH_MON_MALE_OFF)) >= 0) { + gmap->sym.symidx = mons[offset].mlet + SYM_OFF_M; + if (has_rogue_color) { + color = CLR_YELLOW; + } else { + mon_color(offset); + } + gmap->glyphflags |= MG_MALE; + } + /* This was requested by a blind player to enhance screen reader use + */ + if (sysopt.accessibility == 1 && (gmap->glyphflags & MG_PET) != 0) { + int pet_override = ((gg.glyphmap_perlevel_flags & GMAP_ROGUELEVEL) + ? go.ov_rogue_syms[SYM_PET_OVERRIDE + SYM_OFF_X] + : go.ov_primary_syms[SYM_PET_OVERRIDE + SYM_OFF_X]); + + if (gs.showsyms[pet_override] != ' ') + gmap->sym.symidx = SYM_PET_OVERRIDE + SYM_OFF_X; + } + /* Turn off color if no color defined, or rogue level w/o PC graphics. + */ + if ((!has_color(color) + || ((gg.glyphmap_perlevel_flags & GMAP_ROGUELEVEL) + && !has_rogue_color)) || !iflags.use_color) + color = NO_COLOR; + gmap->sym.color = color; + } + gg.glyph_reset_timestamp = svm.moves; } /* ------------------------------------------------------------------------ */ @@ -1959,29 +3090,28 @@ xchar x, y; #ifdef WA_VERBOSE -static const char *FDECL(type_to_name, (int)); -static void FDECL(error4, (int, int, int, int, int, int)); +staticfn const char *type_to_name(int); +staticfn void error4(coordxy, coordxy, int, int, int, int); static int bad_count[MAX_TYPE]; /* count of positions flagged as bad */ -static const char *type_names[MAX_TYPE] = { +static const char *const type_names[MAX_TYPE] = { "STONE", "VWALL", "HWALL", "TLCORNER", "TRCORNER", "BLCORNER", "BRCORNER", "CROSSWALL", "TUWALL", "TDWALL", "TLWALL", "TRWALL", "DBWALL", "TREE", "SDOOR", "SCORR", "POOL", "MOAT", "WATER", "DRAWBRIDGE_UP", "LAVAPOOL", + "LAVAWALL", "IRON_BARS", "DOOR", "CORR", "ROOM", "STAIRS", "LADDER", "FOUNTAIN", "THRONE", "SINK", "GRAVE", "ALTAR", "ICE", "DRAWBRIDGE_DOWN", "AIR", "CLOUD" }; -static const char * -type_to_name(type) -int type; +staticfn const char * +type_to_name(int type) { return (type < 0 || type >= MAX_TYPE) ? "unknown" : type_names[type]; } -static void -error4(x, y, a, b, c, dd) -int x, y, a, b, c, dd; +staticfn void +error4(coordxy x, coordxy y, int a, int b, int c, int dd) { pline("set_wall_state: %s @ (%d,%d) %s%s%s%s", type_to_name(levl[x][y].typ), x, y, @@ -1996,16 +3126,16 @@ int x, y, a, b, c, dd; * * Things that are ambiguous: lava */ -STATIC_OVL int -check_pos(x, y, which) -int x, y, which; +staticfn int +check_pos(coordxy x, coordxy y, int which) { int type; if (!isok(x, y)) return which; type = levl[x][y].typ; - if (IS_ROCK(type) || type == CORR || type == SCORR) + /* Everything below POOL, excluding TREE */ + if (IS_STWALL(type) || type == CORR || type == SCORR || IS_SDOOR(type)) return which; return 0; } @@ -2013,9 +3143,8 @@ int x, y, which; /* Return TRUE if more than one is non-zero. */ /*ARGSUSED*/ #ifdef WA_VERBOSE -STATIC_OVL boolean -more_than_one(x, y, a, b, c) -int x, y, a, b, c; +staticfn boolean +more_than_one(coordxy x, coordxy y, coordxy a, coordxy b, coordxy c) { if ((a && (b | c)) || (b && (a | c)) || (c && (a | b))) { error4(x, y, a, b, c, 0); @@ -2029,19 +3158,19 @@ int x, y, a, b, c; #endif /* Return the wall mode for a T wall. */ -STATIC_OVL int -set_twall(x0, y0, x1, y1, x2, y2, x3, y3) -int x0, y0; /* used #if WA_VERBOSE */ -int x1, y1, x2, y2, x3, y3; +staticfn int +set_twall( +#ifdef WA_VERBOSE + coordxy x0, coordxy y0, /* used #if WA_VERBOSE */ +#else + coordxy x0 UNUSED, coordxy y0 UNUSED, +#endif + coordxy x1, coordxy y1, + coordxy x2, coordxy y2, + coordxy x3, coordxy y3) { int wmode, is_1, is_2, is_3; -#ifndef WA_VERBOSE - /* non-verbose more_than_one() doesn't use these */ - nhUse(x0); - nhUse(y0); -#endif - is_1 = check_pos(x1, y1, WM_T_LONG); is_2 = check_pos(x2, y2, WM_T_BL); is_3 = check_pos(x3, y3, WM_T_BR); @@ -2054,9 +3183,8 @@ int x1, y1, x2, y2, x3, y3; } /* Return wall mode for a horizontal or vertical wall. */ -STATIC_OVL int -set_wall(x, y, horiz) -int x, y, horiz; +staticfn int +set_wall(coordxy x, coordxy y, int horiz) { int wmode, is_1, is_2; @@ -2076,11 +3204,14 @@ int x, y, horiz; } /* Return a wall mode for a corner wall. (x4,y4) is the "inner" position. */ -STATIC_OVL int -set_corn(x1, y1, x2, y2, x3, y3, x4, y4) -int x1, y1, x2, y2, x3, y3, x4, y4; +staticfn int +set_corn( + coordxy x1, coordxy y1, + coordxy x2, coordxy y2, + coordxy x3, coordxy y3, + coordxy x4, coordxy y4) { - int wmode, is_1, is_2, is_3, is_4; + coordxy wmode, is_1, is_2, is_3, is_4; is_1 = check_pos(x1, y1, 1); is_2 = check_pos(x2, y2, 1); @@ -2105,11 +3236,10 @@ int x1, y1, x2, y2, x3, y3, x4, y4; } /* Return mode for a crosswall. */ -STATIC_OVL int -set_crosswall(x, y) -int x, y; +staticfn int +set_crosswall(coordxy x, coordxy y) { - int wmode, is_1, is_2, is_3, is_4; + coordxy wmode, is_1, is_2, is_3, is_4; is_1 = check_pos(x - 1, y - 1, 1); is_2 = check_pos(x + 1, y - 1, 1); @@ -2140,13 +3270,66 @@ int x, y; return wmode; } +/* called for every by set_wall_state() and for specific during + vault wall repair */ +void +xy_set_wall_state(coordxy x, coordxy y) +{ + coordxy wmode; + struct rm *lev = &levl[x][y]; + + switch (lev->typ) { + case SDOOR: + wmode = set_wall(x, y, (coordxy) lev->horizontal); + break; + case VWALL: + wmode = set_wall(x, y, 0); + break; + case HWALL: + wmode = set_wall(x, y, 1); + break; + case TDWALL: + wmode = set_twall(x, y, x, y - 1, x - 1, y + 1, x + 1, y + 1); + break; + case TUWALL: + wmode = set_twall(x, y, x, y + 1, x + 1, y - 1, x - 1, y - 1); + break; + case TLWALL: + wmode = set_twall(x, y, x + 1, y, x - 1, y - 1, x - 1, y + 1); + break; + case TRWALL: + wmode = set_twall(x, y, x - 1, y, x + 1, y + 1, x + 1, y - 1); + break; + case TLCORNER: + wmode = set_corn(x - 1, y - 1, x, y - 1, x - 1, y, x + 1, y + 1); + break; + case TRCORNER: + wmode = set_corn(x, y - 1, x + 1, y - 1, x + 1, y, x - 1, y + 1); + break; + case BLCORNER: + wmode = set_corn(x, y + 1, x - 1, y + 1, x - 1, y, x + 1, y - 1); + break; + case BRCORNER: + wmode = set_corn(x + 1, y, x + 1, y + 1, x, y + 1, x - 1, y - 1); + break; + case CROSSWALL: + wmode = set_crosswall(x, y); + break; + + default: + wmode = -1; /* don't set wall info */ + break; + } + + if (wmode >= 0) + lev->wall_info = (lev->wall_info & ~WM_MASK) | wmode; +} + /* Called from mklev. Scan the level and set the wall modes. */ void -set_wall_state() +set_wall_state(void) { - int x, y; - int wmode; - struct rm *lev; + coordxy x, y; #ifdef WA_VERBOSE for (x = 0; x < MAX_TYPE; x++) @@ -2154,64 +3337,15 @@ set_wall_state() #endif for (x = 0; x < COLNO; x++) - for (lev = &levl[x][0], y = 0; y < ROWNO; y++, lev++) { - switch (lev->typ) { - case SDOOR: - wmode = set_wall(x, y, (int) lev->horizontal); - break; - case VWALL: - wmode = set_wall(x, y, 0); - break; - case HWALL: - wmode = set_wall(x, y, 1); - break; - case TDWALL: - wmode = set_twall(x, y, x, y - 1, x - 1, y + 1, x + 1, y + 1); - break; - case TUWALL: - wmode = set_twall(x, y, x, y + 1, x + 1, y - 1, x - 1, y - 1); - break; - case TLWALL: - wmode = set_twall(x, y, x + 1, y, x - 1, y - 1, x - 1, y + 1); - break; - case TRWALL: - wmode = set_twall(x, y, x - 1, y, x + 1, y + 1, x + 1, y - 1); - break; - case TLCORNER: - wmode = - set_corn(x - 1, y - 1, x, y - 1, x - 1, y, x + 1, y + 1); - break; - case TRCORNER: - wmode = - set_corn(x, y - 1, x + 1, y - 1, x + 1, y, x - 1, y + 1); - break; - case BLCORNER: - wmode = - set_corn(x, y + 1, x - 1, y + 1, x - 1, y, x + 1, y - 1); - break; - case BRCORNER: - wmode = - set_corn(x + 1, y, x + 1, y + 1, x, y + 1, x - 1, y - 1); - break; - case CROSSWALL: - wmode = set_crosswall(x, y); - break; - - default: - wmode = -1; /* don't set wall info */ - break; - } - - if (wmode >= 0) - lev->wall_info = (lev->wall_info & ~WM_MASK) | wmode; - } + for (y = 0; y < ROWNO; y++) + xy_set_wall_state(x, y); #ifdef WA_VERBOSE /* check if any bad positions found */ for (x = y = 0; x < MAX_TYPE; x++) if (bad_count[x]) { if (y == 0) { - y = 1; /* only print once */ + y = 1; /* only prcoordxy once */ pline("set_wall_type: wall mode problems with: "); } pline("%s %d;", type_names[x], bad_count[x]); @@ -2221,31 +3355,39 @@ set_wall_state() /* ------------------------------------------------------------------------ */ /* This matrix is used here and in vision.c. */ -unsigned char seenv_matrix[3][3] = { { SV2, SV1, SV0 }, - { SV3, SVALL, SV7 }, - { SV4, SV5, SV6 } }; +const seenV seenv_matrix[3][3] = { + { SV2, SV1, SV0 }, + { SV3, SVALL, SV7 }, + { SV4, SV5, SV6 } +}; -#define sign(z) ((z) < 0 ? -1 : ((z) > 0 ? 1 : 0)) +/* negative: -1, zero: 0, positive +1; same as sgn() function (hacklib.c) */ +#define sign(z) ((z) < 0 ? -1 : ((z) != 0)) /* Set the seen vector of lev as if seen from (x0,y0) to (x,y). */ -STATIC_OVL void -set_seenv(lev, x0, y0, x, y) -struct rm *lev; -int x0, y0, x, y; /* from, to */ +staticfn void +set_seenv( + struct rm *lev, + coordxy x0, coordxy y0, /* from */ + coordxy x, coordxy y) /* to */ { - int dx = x - x0, dy = y0 - y; + coordxy dx = x - x0, dy = y0 - y; lev->seenv |= seenv_matrix[sign(dy) + 1][sign(dx) + 1]; } +#undef sign + /* Called by blackout(vault.c) when vault guard removes temporary corridor, turning spot back into stone; is an adjacent spot. */ void -unset_seenv(lev, x0, y0, x1, y1) -struct rm *lev; /* &levl[x1][y1] */ -int x0, y0, x1, y1; /* from, to; abs(x1-x0)==1 && abs(y0-y1)==1 */ +unset_seenv( + struct rm *lev, /* &levl[x1][y1] */ + coordxy x0, coordxy y0, /* from */ + coordxy x1, coordxy y1) /* to; abs(x1-x0)==1 && abs(y0-y1)==1 */ + { - int dx = x1 - x0, dy = y0 - y1; + coordxy dx = x1 - x0, dy = y0 - y1; lev->seenv &= ~seenv_matrix[dy + 1][dx + 1]; } @@ -2290,7 +3432,7 @@ static const int wall_matrix[4][5] = { * in C_br (bottom right) terms. All crosswalls with a single solid * quarter are rotated so the solid section is at the bottom right. * We pattern match on that, but return the correct result depending - * on which row we'ere looking at. + * on which row we're looking at. */ #define C_trcorn 0 #define C_brcorn 1 @@ -2307,23 +3449,50 @@ static const int cross_matrix[4][6] = { }; /* Print out a T wall warning and all interesting info. */ -STATIC_OVL void -t_warn(lev) -struct rm *lev; +staticfn void +t_warn(struct rm *lev) { static const char warn_str[] = "wall_angle: %s: case %d: seenv = 0x%x"; const char *wname; - if (lev->typ == TUWALL) + /* 5.0: non-T_wall cases added after shop repair (via breaching a wall, + using locking magic to put a door there, then unlocking the door; + D_CLOSED carried over to the wall) triggered warning for "unknown" */ + switch (lev->typ) { + case TUWALL: wname = "tuwall"; - else if (lev->typ == TLWALL) + break; + case TLWALL: wname = "tlwall"; - else if (lev->typ == TRWALL) + break; + case TRWALL: wname = "trwall"; - else if (lev->typ == TDWALL) + break; + case TDWALL: wname = "tdwall"; - else + break; + case VWALL: + wname = "vwall"; + break; + case HWALL: + wname = "hwall"; + break; + case TLCORNER: + wname = "tlcorner"; + break; + case TRCORNER: + wname = "trcorner"; + break; + case BLCORNER: + wname = "blcorner"; + break; + case BRCORNER: + wname = "brcorner"; + break; + default: wname = "unknown"; + break; + } impossible(warn_str, wname, lev->wall_info & WM_MASK, (unsigned int) lev->seenv); } @@ -2340,11 +3509,10 @@ struct rm *lev; * draw diagrams. See rm.h for more details on the wall modes and * seen vector (SV). */ -STATIC_OVL int -wall_angle(lev) -struct rm *lev; +staticfn int +wall_angle(struct rm *lev) { - register unsigned int seenv = lev->seenv & 0xff; + unsigned int seenv = lev->seenv & 0xff; const int *row; int col, idx; @@ -2397,22 +3565,6 @@ struct rm *lev; } break; case WM_T_BL: -#if 0 /* older method, fixed */ - if (only(seenv, SV4 | SV5)) { - col = T_tlcorn; - } else if ((seenv & (SV0 | SV1 | SV2)) - && only(seenv, SV0 | SV1 | SV2 | SV6 | SV7)) { - col = T_hwall; - } else if ((seenv & SV3) - || ((seenv & (SV0 | SV1 | SV2)) - && (seenv & (SV4 | SV5)))) { - col = T_tdwall; - } else { - if (seenv != SV6) - t_warn(lev); - col = T_stone; - } -#endif /* 0 */ if (only(seenv, SV4 | SV5)) col = T_tlcorn; else if ((seenv & (SV0 | SV1 | SV2 | SV7)) @@ -2424,22 +3576,6 @@ struct rm *lev; col = T_tdwall; break; case WM_T_BR: -#if 0 /* older method, fixed */ - if (only(seenv, SV5 | SV6)) { - col = T_trcorn; - } else if ((seenv & (SV0 | SV1 | SV2)) - && only(seenv, SV0 | SV1 | SV2 | SV3 | SV4)) { - col = T_hwall; - } else if ((seenv & SV7) - || ((seenv & (SV0 | SV1 | SV2)) - && (seenv & (SV5 | SV6)))) { - col = T_tdwall; - } else { - if (seenv != SV4) - t_warn(lev); - col = T_stone; - } -#endif /* 0 */ if (only(seenv, SV5 | SV6)) col = T_trcorn; else if ((seenv & (SV0 | SV1 | SV2 | SV3)) @@ -2461,8 +3597,13 @@ struct rm *lev; break; case SDOOR: + if (lev->arboreal_sdoor) { + idx = S_tree; + break; + } if (lev->horizontal) goto horiz; + FALLTHROUGH; /*FALLTHRU*/ case VWALL: switch (lev->wall_info & WM_MASK) { @@ -2645,4 +3786,37 @@ struct rm *lev; return idx; } +/* + * c++ 20 has problems with some of the display.h macros because + * comparisons and bit-fiddling and math between different enums + * is deprecated. + * Create function versions of some of the macros used in some + * NetHack c++ source files (Qt) for use there. + */ +int +fn_cmap_to_glyph(int cmap) +{ + return cmap_to_glyph(cmap); +} + +/* for 'onefile' processing where end of this file isn't necessarily the + end of the source code seen by the compiler (there are lots of other + macros defined above...) */ +#undef _glyph_at +#undef DETECTED +#undef PHYSICALLY_SEEN +#undef is_worm_tail +#undef _suppress_map_output +#undef TMP_AT_MAX_GLYPHS +#undef Glyphinfo_at +#undef reset_glyph_bbox +#undef HAS_ROGUE_IBM_GRAPHICS +#undef GMAP_SET +#undef GMAP_ROGUELEVEL +#ifndef WA_VERBOSE +#undef more_than_one +#endif +#undef only +#undef set_corner + /*display.c*/ diff --git a/src/dlb.c b/src/dlb.c index 8987be0a0..a68126dca 100644 --- a/src/dlb.c +++ b/src/dlb.c @@ -1,26 +1,15 @@ -/* NetHack 3.6 dlb.c $NHDT-Date: 1446975464 2015/11/08 09:37:44 $ $NHDT-Branch: master $:$NHDT-Revision: 1.15 $ */ +/* NetHack 5.0 dlb.c $NHDT-Date: 1596498157 2020/08/03 23:42:37 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.20 $ */ /* Copyright (c) Kenneth Lorber, Bethesda, Maryland, 1993. */ /* NetHack may be freely redistributed. See license for details. */ #include "config.h" #include "dlb.h" -#if defined(VERSION_IN_DLB_FILENAME) -#include "patchlevel.h" -#endif #ifdef __DJGPP__ #include #endif -#define DATAPREFIX 4 - -#if defined(OVERLAY) -#define STATIC_DCL extern -#define STATIC_OVL -#else /* !OVERLAY */ -#define STATIC_DCL static -#define STATIC_OVL static -#endif /* OVERLAY */ +#define DATAPREFIX 4 /* see decl.h */ #ifdef DLB /* @@ -30,15 +19,15 @@ */ typedef struct dlb_procs { - boolean NDECL((*dlb_init_proc)); - void NDECL((*dlb_cleanup_proc)); - boolean FDECL((*dlb_fopen_proc), (DLB_P, const char *, const char *)); - int FDECL((*dlb_fclose_proc), (DLB_P)); - int FDECL((*dlb_fread_proc), (char *, int, int, DLB_P)); - int FDECL((*dlb_fseek_proc), (DLB_P, long, int)); - char *FDECL((*dlb_fgets_proc), (char *, int, DLB_P)); - int FDECL((*dlb_fgetc_proc), (DLB_P)); - long FDECL((*dlb_ftell_proc), (DLB_P)); + boolean (*dlb_init_proc)(void); + void (*dlb_cleanup_proc)(void); + boolean (*dlb_fopen_proc)(DLB_P, const char *, const char *); + int (*dlb_fclose_proc)(DLB_P); + int (*dlb_fread_proc)(char *, int, int, DLB_P); + int (*dlb_fseek_proc)(DLB_P, long, int); + char *(*dlb_fgets_proc)(char *, int, DLB_P); + int (*dlb_fgetc_proc)(DLB_P); + long (*dlb_ftell_proc)(DLB_P); } dlb_procs_t; #if defined(VERSION_IN_DLB_FILENAME) @@ -46,7 +35,11 @@ char dlbfilename[MAX_DLB_FILENAME]; #endif /* without extern.h via hack.h, these haven't been declared for us */ -extern FILE *FDECL(fopen_datafile, (const char *, const char *, int)); +extern FILE *fopen_datafile(const char *, const char *, int); +#define FITSuint(x) FITSuint_((x), __func__, __LINE__) +/* implementation will be in either dlb_main.c or the core */ +extern unsigned FITSuint_(unsigned long long, const char *, int); + #ifdef DLBLIB /* @@ -66,27 +59,27 @@ extern FILE *FDECL(fopen_datafile, (const char *, const char *, int)); */ #define MAX_LIBS 4 -static library dlb_libs[MAX_LIBS]; - -STATIC_DCL boolean FDECL(readlibdir, (library * lp)); -STATIC_DCL boolean FDECL(find_file, (const char *name, library **lib, - long *startp, long *sizep)); -STATIC_DCL boolean NDECL(lib_dlb_init); -STATIC_DCL void NDECL(lib_dlb_cleanup); -STATIC_DCL boolean FDECL(lib_dlb_fopen, (dlb *, const char *, const char *)); -STATIC_DCL int FDECL(lib_dlb_fclose, (dlb *)); -STATIC_DCL int FDECL(lib_dlb_fread, (char *, int, int, dlb *)); -STATIC_DCL int FDECL(lib_dlb_fseek, (dlb *, long, int)); -STATIC_DCL char *FDECL(lib_dlb_fgets, (char *, int, dlb *)); -STATIC_DCL int FDECL(lib_dlb_fgetc, (dlb *)); -STATIC_DCL long FDECL(lib_dlb_ftell, (dlb *)); +staticfn library dlb_libs[MAX_LIBS]; + +staticfn boolean readlibdir(library * lp); +staticfn boolean find_file(const char *name, library **lib, long *startp, + long *sizep); +staticfn boolean lib_dlb_init(void); +staticfn void lib_dlb_cleanup(void); +staticfn boolean lib_dlb_fopen(dlb *, const char *, const char *); +staticfn int lib_dlb_fclose(dlb *); +staticfn int lib_dlb_fread(char *, int, int, dlb *); +staticfn int lib_dlb_fseek(dlb *, long, int); +staticfn char *lib_dlb_fgets(char *, int, dlb *); +staticfn int lib_dlb_fgetc(dlb *); +staticfn long lib_dlb_ftell(dlb *); /* not static because shared with dlb_main.c */ -boolean FDECL(open_library, (const char *lib_name, library *lp)); -void FDECL(close_library, (library * lp)); +boolean open_library(const char *lib_name, library *lp); +void close_library(library * lp); /* without extern.h via hack.h, these haven't been declared for us */ -extern char *FDECL(eos, (char *)); +extern char *eos(char *); /* * Read the directory out of the library. Return 1 if successful, @@ -130,9 +123,8 @@ extern char *FDECL(eos, (char *)); * * Return TRUE on success, FALSE on failure. */ -STATIC_OVL boolean -readlibdir(lp) -library *lp; /* library pointer to fill in */ +staticfn boolean +readlibdir(library *lp) /* library pointer to fill in */ { int i; char *sp; @@ -144,8 +136,8 @@ library *lp; /* library pointer to fill in */ if (lp->rev > DLB_MAX_VERS || lp->rev < DLB_MIN_VERS) return FALSE; - lp->dir = (libdir *) alloc(lp->nentries * sizeof(libdir)); - lp->sspace = (char *) alloc(lp->strsize); + lp->dir = (libdir *) alloc(FITSuint(lp->nentries * sizeof(libdir))); + lp->sspace = (char *) alloc(FITSuint(lp->strsize)); /* read in each directory entry */ for (i = 0, sp = lp->sspace; i < lp->nentries; i++) { @@ -179,11 +171,8 @@ library *lp; /* library pointer to fill in */ * Look for the file in our directory structure. Return 1 if successful, * 0 if not found. Fill in the size and starting position. */ -STATIC_OVL boolean -find_file(name, lib, startp, sizep) -const char *name; -library **lib; -long *startp, *sizep; +staticfn boolean +find_file(const char *name, library **lib, long *startp, long *sizep) { int i, j; library *lp; @@ -209,9 +198,7 @@ long *startp, *sizep; * structure. Return TRUE if successful, FALSE otherwise. */ boolean -open_library(lib_name, lp) -const char *lib_name; -library *lp; +open_library(const char *lib_name, library *lp) { boolean status = FALSE; @@ -228,8 +215,7 @@ library *lp; } void -close_library(lp) -library *lp; +close_library(library *lp) { (void) fclose(lp->fdata); free((genericptr_t) lp->dir); @@ -242,8 +228,8 @@ library *lp; * Open the library file once using stdio. Keep it open, but * keep track of the file position. */ -STATIC_OVL boolean -lib_dlb_init(VOID_ARGS) +staticfn boolean +lib_dlb_init(void) { /* zero out array */ (void) memset((char *) &dlb_libs[0], 0, sizeof(dlb_libs)); @@ -262,8 +248,8 @@ lib_dlb_init(VOID_ARGS) return TRUE; } -STATIC_OVL void -lib_dlb_cleanup(VOID_ARGS) +staticfn void +lib_dlb_cleanup(void) { int i; @@ -274,8 +260,7 @@ lib_dlb_cleanup(VOID_ARGS) #ifdef VERSION_IN_DLB_FILENAME char * -build_dlb_filename(lf) -const char *lf; +build_dlb_filename(const char *lf) { Sprintf(dlbfilename, "%s%d%d%d", lf ? lf : DLBBASENAME, VERSION_MAJOR, VERSION_MINOR, PATCHLEVEL); @@ -284,11 +269,8 @@ const char *lf; #endif /*ARGSUSED*/ -STATIC_OVL boolean -lib_dlb_fopen(dp, name, mode) -dlb *dp; -const char *name; -const char *mode UNUSED; +staticfn boolean +lib_dlb_fopen(dlb *dp, const char *name, const char *mode UNUSED) { long start, size; library *lp; @@ -306,25 +288,21 @@ const char *mode UNUSED; } /*ARGUSED*/ -STATIC_OVL int -lib_dlb_fclose(dp) -dlb *dp UNUSED; +staticfn int +lib_dlb_fclose(dlb *dp UNUSED) { /* nothing needs to be done */ return 0; } -STATIC_OVL int -lib_dlb_fread(buf, size, quan, dp) -char *buf; -int size, quan; -dlb *dp; +staticfn int +lib_dlb_fread(char *buf, int size, int quan, dlb *dp) { long pos, nread, nbytes; /* make sure we don't read into the next file */ if ((dp->size - dp->mark) < (size * quan)) - quan = (dp->size - dp->mark) / size; + quan = (int)((dp->size - dp->mark) / size); if (quan == 0) return 0; @@ -339,14 +317,11 @@ dlb *dp; dp->mark += nbytes; dp->lib->fmark += nbytes; - return nread; + return (int) nread; } -STATIC_OVL int -lib_dlb_fseek(dp, pos, whence) -dlb *dp; -long pos; -int whence; +staticfn int +lib_dlb_fseek(dlb *dp, long pos, int whence) { long curpos; @@ -370,11 +345,8 @@ int whence; return 0; } -STATIC_OVL char * -lib_dlb_fgets(buf, len, dp) -char *buf; -int len; -dlb *dp; +staticfn char * +lib_dlb_fgets(char *buf, int len, dlb *dp) { int i; char *bp, c = 0; @@ -396,7 +368,7 @@ dlb *dp; *bp = '\0'; #if defined(MSDOS) || defined(WIN32) - if ((bp = index(buf, '\r')) != 0) { + if ((bp = strchr(buf, '\r')) != 0) { *bp++ = '\n'; *bp = '\0'; } @@ -405,9 +377,8 @@ dlb *dp; return buf; } -STATIC_OVL int -lib_dlb_fgetc(dp) -dlb *dp; +staticfn int +lib_dlb_fgetc(dlb *dp) { char c; @@ -416,14 +387,13 @@ dlb *dp; return (int) c; } -STATIC_OVL long -lib_dlb_ftell(dp) -dlb *dp; +staticfn long +lib_dlb_ftell(dlb *dp) { return dp->mark; } -const dlb_procs_t lib_dlb_procs = { lib_dlb_init, lib_dlb_cleanup, +static const dlb_procs_t lib_dlb_procs = { lib_dlb_init, lib_dlb_cleanup, lib_dlb_fopen, lib_dlb_fclose, lib_dlb_fread, lib_dlb_fseek, lib_dlb_fgets, lib_dlb_fgetc, @@ -432,7 +402,7 @@ const dlb_procs_t lib_dlb_procs = { lib_dlb_init, lib_dlb_cleanup, #endif /* DLBLIB */ #ifdef DLBRSRC -const dlb_procs_t rsrc_dlb_procs = { rsrc_dlb_init, rsrc_dlb_cleanup, +static const dlb_procs_t rsrc_dlb_procs = { rsrc_dlb_init, rsrc_dlb_cleanup, rsrc_dlb_fopen, rsrc_dlb_fclose, rsrc_dlb_fread, rsrc_dlb_fseek, rsrc_dlb_fgets, rsrc_dlb_fgetc, @@ -456,7 +426,7 @@ static const dlb_procs_t *dlb_procs; static boolean dlb_initialized = FALSE; boolean -dlb_init() +dlb_init(void) { if (!dlb_initialized) { #ifdef DLBLIB @@ -474,7 +444,7 @@ dlb_init() } void -dlb_cleanup() +dlb_cleanup(void) { if (dlb_initialized) { do_dlb_cleanup(); @@ -483,8 +453,7 @@ dlb_cleanup() } dlb * -dlb_fopen(name, mode) -const char *name, *mode; +dlb_fopen(const char *name, const char *mode) { FILE *fp; dlb *dp; @@ -511,8 +480,7 @@ const char *name, *mode; } int -dlb_fclose(dp) -dlb *dp; +dlb_fclose(dlb *dp) { int ret = 0; @@ -528,10 +496,7 @@ dlb *dp; } int -dlb_fread(buf, size, quan, dp) -char *buf; -int size, quan; -dlb *dp; +dlb_fread(char *buf, int size, int quan, dlb *dp) { if (!dlb_initialized || size <= 0 || quan <= 0) return 0; @@ -541,10 +506,7 @@ dlb *dp; } int -dlb_fseek(dp, pos, whence) -dlb *dp; -long pos; -int whence; +dlb_fseek(dlb *dp, long pos, int whence) { if (!dlb_initialized) return EOF; @@ -554,10 +516,7 @@ int whence; } char * -dlb_fgets(buf, len, dp) -char *buf; -int len; -dlb *dp; +dlb_fgets(char *buf, int len, dlb *dp) { if (!dlb_initialized) return (char *) 0; @@ -567,8 +526,7 @@ dlb *dp; } int -dlb_fgetc(dp) -dlb *dp; +dlb_fgetc(dlb *dp) { if (!dlb_initialized) return EOF; @@ -578,8 +536,7 @@ dlb *dp; } long -dlb_ftell(dp) -dlb *dp; +dlb_ftell(dlb *dp) { if (!dlb_initialized) return 0; diff --git a/src/do.c b/src/do.c index 8bbed39de..a7ad93903 100644 --- a/src/do.c +++ b/src/do.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 do.c $NHDT-Date: 1576638499 2019/12/18 03:08:19 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.198 $ */ +/* NetHack 5.0 do.c $NHDT-Date: 1774269965 2026/03/23 04:46:05 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.404 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Derek S. Ray, 2015. */ /* NetHack may be freely redistributed. See license for details. */ @@ -6,33 +6,34 @@ /* Contains code for 'd', 'D' (drop), '>', '<' (up, down) */ #include "hack.h" -#include "lev.h" -STATIC_DCL void FDECL(trycall, (struct obj *)); -STATIC_DCL void NDECL(polymorph_sink); -STATIC_DCL boolean NDECL(teleport_sink); -STATIC_DCL void FDECL(dosinkring, (struct obj *)); -STATIC_PTR int FDECL(drop, (struct obj *)); -STATIC_PTR int NDECL(wipeoff); -STATIC_DCL int FDECL(menu_drop, (int)); -STATIC_DCL int NDECL(currentlevel_rewrite); -STATIC_DCL void NDECL(final_level); -/* static boolean FDECL(badspot, (XCHAR_P,XCHAR_P)); */ - -extern int n_dgns; /* number of dungeons, from dungeon.c */ - -static NEARDATA const char drop_types[] = { ALLOW_COUNT, COIN_CLASS, - ALL_CLASSES, 0 }; - -/* 'd' command: drop one inventory item */ +staticfn boolean teleport_sink(void); +staticfn void dosinkring(struct obj *); +staticfn int drop(struct obj *); +staticfn int menudrop_split(struct obj *, long); +staticfn boolean engulfer_digests_food(struct obj *); +staticfn boolean danger_uprops(void); +staticfn int wipeoff(void); +staticfn int menu_drop(int); +staticfn boolean u_stuck_cannot_go(const char *); +staticfn NHFILE *currentlevel_rewrite(void); +staticfn void familiar_level_msg(void); +staticfn void final_level(void); +staticfn void temperature_change_msg(schar); +staticfn boolean better_not_try_to_drop_that(struct obj *); + + /* static boolean badspot(coordxy,coordxy); */ + +/* the #drop command: drop one inventory item */ int -dodrop() +dodrop(void) { - int result, i = (invent) ? 0 : (SIZE(drop_types) - 1); + int result; if (*u.ushops) sellobj_state(SELL_DELIBERATE); - result = drop(getobj(&drop_types[i], "drop")); + result = drop(getobj("drop", any_obj_ok, + GETOBJ_PROMPT | GETOBJ_ALLOWCNT)); if (*u.ushops) sellobj_state(SELL_NORMAL); if (result) @@ -46,19 +47,25 @@ dodrop() * it's gone for good... If the destination is not a pool, returns FALSE. */ boolean -boulder_hits_pool(otmp, rx, ry, pushing) -struct obj *otmp; -register int rx, ry; -boolean pushing; +boulder_hits_pool( + struct obj *otmp, /* the object falling into a pool or water or lava */ + coordxy rx, coordxy ry, /* coordinates of the pool */ + boolean pushing) /* for a boulder, whether or not it is being pushed */ { if (!otmp || otmp->otyp != BOULDER) { impossible("Not a boulder?"); - } else if (!Is_waterlevel(&u.uz) && is_pool_or_lava(rx, ry)) { + } else if (is_pool_or_lava(rx, ry)) { boolean lava = is_lava(rx, ry), fills_up; const char *what = waterbody_name(rx, ry); schar ltyp = levl[rx][ry].typ; int chance = rn2(10); /* water: 90%; lava: 10% */ - fills_up = lava ? chance == 0 : chance != 0; + struct monst *mtmp; + + /* chance for boulder to fill pool: Plane of Water==0%, + lava 10%, wall of water==50%, other water==90% */ + fills_up = Is_waterlevel(&u.uz) ? FALSE + : IS_WATERWALL(ltyp) ? (chance < 5) + : lava ? (chance == 0) : (chance != 0); if (fills_up) { struct trap *ttmp = t_at(rx, ry); @@ -66,8 +73,21 @@ boolean pushing; if (ltyp == DRAWBRIDGE_UP) { levl[rx][ry].drawbridgemask &= ~DB_UNDER; /* clear lava */ levl[rx][ry].drawbridgemask |= DB_FLOOR; - } else + } else { levl[rx][ry].typ = ROOM, levl[rx][ry].flags = 0; + recalc_block_point(rx, ry); + } + /* 5.0: normally DEADMONSTER() is used when traversing the fmon + list--dead monsters usually aren't still at specific map + locations; however, if ice melts causing a giant to drown, + that giant would still be on the map when it drops inventory; + if it was carrying a boulder which now fills the pool, 'mtmp' + will be dead here; killing it again would yield impossible + "dmonsfree: N removed doesn't match N+1 pending" when other + monsters have finished their current turn */ + if ((mtmp = m_at(rx, ry)) != 0 && !DEADMONSTER(mtmp) + && !m_in_air(mtmp)) + mondied(mtmp); if (ttmp) (void) delfloortrap(ttmp); @@ -93,18 +113,25 @@ boolean pushing; There("is a large splash as %s %s the %s.", the(xname(otmp)), fills_up ? "fills" : "falls into", what); - } else if (!Deaf) + } else if (!Deaf) { + if (lava) { + Soundeffect(se_sizzling, 100); + } else { + Soundeffect(se_splash, 100); + } You_hear("a%s splash.", lava ? " sizzling" : ""); + } wake_nearto(rx, ry, 40); } if (fills_up && u.uinwater && distu(rx, ry) == 0) { - u.uinwater = 0; + set_uinwater(0); /* u.uinwater = 0 */ docrt(); - vision_full_recalc = 1; + gv.vision_full_recalc = 1; You("find yourself on dry land again!"); - } else if (lava && distu(rx, ry) <= 2) { + } else if (lava && next2u(rx, ry)) { int dmg; + You("are hit by molten %s%c", hliquid("lava"), Fire_resistance ? '.' : '!'); burn_away_slime(); @@ -112,13 +139,14 @@ boolean pushing; losehp(Maybe_Half_Phys(dmg), /* lava damage */ "molten lava", KILLED_BY); } else if (!fills_up && flags.verbose - && (pushing ? !Blind : cansee(rx, ry))) + && (pushing ? !Blind : cansee(rx, ry))) { pline("It sinks without a trace!"); + } } /* boulder is now gone */ if (pushing) - delobj(otmp); + useupf(otmp, otmp->quan); else obfree(otmp, (struct obj *) 0); return TRUE; @@ -131,31 +159,37 @@ boolean pushing; * away. */ boolean -flooreffects(obj, x, y, verb) -struct obj *obj; -int x, y; -const char *verb; +flooreffects( + struct obj *obj, /* the object landing on the floor */ + coordxy x, coordxy y, /* map coordinates for spot where it is landing */ + const char *verb) /* "fall", "drop", "land", &c */ { struct trap *t; struct monst *mtmp; struct obj *otmp; + coord save_bhitpos; boolean tseen; - int ttyp = NO_TRAP; + int ttyp = NO_TRAP, res = FALSE; if (obj->where != OBJ_FREE) panic("flooreffects: obj not free"); /* make sure things like water_damage() have no pointers to follow */ obj->nobj = obj->nexthere = (struct obj *) 0; + /* erode_obj() (called from water_damage() or lava_damage()) needs + bhitpos, but that was screwing up wand zapping that called us from + rloco(), so we now restore bhitpos before we return */ + save_bhitpos = gb.bhitpos; + gb.bhitpos.x = x, gb.bhitpos.y = y; if (obj->otyp == BOULDER && boulder_hits_pool(obj, x, y, FALSE)) { - return TRUE; + res = TRUE; } else if (obj->otyp == BOULDER && (t = t_at(x, y)) != 0 && (is_pit(t->ttyp) || is_hole(t->ttyp))) { ttyp = t->ttyp; tseen = t->tseen ? TRUE : FALSE; if (((mtmp = m_at(x, y)) && mtmp->mtrapped) - || (u.utrap && u.ux == x && u.uy == y)) { + || (u.utrap && u_at(x,y))) { if (*verb && (cansee(x, y) || distu(x, y) == 0)) pline("%s boulder %s into the pit%s.", Blind ? "A" : "The", @@ -172,7 +206,7 @@ const char *verb; might have been thrown by a giant or launched by a rolling boulder trap triggered by a monster or dropped by a scroll of earth read by a monster */ - if (context.mon_moving) { + if (svc.context.mon_moving) { /* normally we'd use ohitmon() but it can call drop_throw() which calls flooreffects() */ damage = dmgval(obj, mtmp); @@ -189,30 +223,31 @@ const char *verb; (void) hmon(mtmp, obj, HMON_THROWN, dieroll); } if (!DEADMONSTER(mtmp) && !is_whirly(mtmp->data)) - return FALSE; /* still alive */ + res = FALSE; /* still alive, boulder still intact */ + nhUse(res); } mtmp->mtrapped = 0; } else { - if (!Passes_walls && !throws_rocks(youmonst.data)) { + if (!Passes_walls && !throws_rocks(gy.youmonst.data)) { losehp(Maybe_Half_Phys(rnd(15)), "squished under a boulder", NO_KILLER_PREFIX); - return FALSE; /* player remains trapped */ + goto deletedwithboulder; } else reset_utrap(TRUE); } } if (*verb) { - if (Blind && (x == u.ux) && (y == u.uy)) { + if (Blind && u_at(x, y)) { + Soundeffect(se_crashing_boulder, 100); You_hear("a CRASH! beneath you."); } else if (!Blind && cansee(x, y)) { pline_The("boulder %s%s.", - (ttyp == TRAPDOOR && !tseen) - ? "triggers and " : "", - (ttyp == TRAPDOOR) - ? "plugs a trap door" - : (ttyp == HOLE) ? "plugs a hole" - : "fills a pit"); + (ttyp == TRAPDOOR && !tseen) ? "triggers and " : "", + (ttyp == TRAPDOOR) ? "plugs a trap door" + : (ttyp == HOLE) ? "plugs a hole" + : "fills a pit"); } else { + Soundeffect(se_boulder_drop, 100); You_hear("a boulder %s.", verb); } } @@ -220,22 +255,27 @@ const char *verb; * Note: trap might have gone away via ((hmon -> killed -> xkilled) * || mondied) -> mondead -> m_detach -> fill_pit. */ - if ((t = t_at(x, y)) != 0) - deltrap(t); + deletedwithboulder: + /* creating a pit in ice results in that ice being turned into + floor so we shouldn't need any special ice handing here */ + if ((t = t_at(x, y)) != 0) { + (void) delfloortrap(t); + if (u.utrap && u_at(x, y)) + reset_utrap(FALSE); + } useupf(obj, 1L); bury_objs(x, y); newsym(x, y); - return TRUE; + res = TRUE; } else if (is_lava(x, y)) { - return lava_damage(obj, x, y); + res = lava_damage(obj, x, y); } else if (is_pool(x, y)) { /* Reasonably bulky objects (arbitrary) splash when dropped. * If you're floating above the water even small things make * noise. Stuff dropped near fountains always misses */ - if ((Blind || (Levitation || Flying)) && !Deaf - && ((x == u.ux) && (y == u.uy))) { + if ((Blind || (Levitation || Flying)) && !Deaf && u_at(x, y)) { if (!Underwater) { - if (weight(obj) > 9) { + if (weight(obj) > WT_SPLASH_THRESHOLD) { pline("Splash!"); } else if (Levitation || Flying) { pline("Plop!"); @@ -244,39 +284,93 @@ const char *verb; map_background(x, y, 0); newsym(x, y); } - return water_damage(obj, NULL, FALSE) == ER_DESTROYED; - } else if (u.ux == x && u.uy == y && (t = t_at(x, y)) != 0 + res = water_damage(obj, NULL, FALSE) == ER_DESTROYED; + } else if (u_at(x, y) && (t = t_at(x, y)) != 0 && (uteetering_at_seen_pit(t) || uescaped_shaft(t))) { - if (Blind && !Deaf) - You_hear("%s tumble downwards.", the(xname(obj))); - else - pline("%s %s into %s %s.", The(xname(obj)), - otense(obj, "tumble"), the_your[t->madeby_u], - is_pit(t->ttyp) ? "pit" : "hole"); + if (is_pit(t->ttyp)) { + if (Blind && !Deaf) { + Soundeffect(se_item_tumble_downwards, 50); + You_hear("%s tumble downwards.", the(xname(obj))); + } else { + pline("%s into %s pit.", Tobjnam(obj, "tumble"), + the_your[t->madeby_u]); + } + } else if (ship_object(obj, x, y, FALSE)) { + /* ship_object will print an appropriate "the item falls + * through the hole" message, so no need to do it here. */ + res = TRUE; + } } else if (obj->globby) { + struct obj *globbyobj = obj; /* allow obj to be nonnull arg */ + /* Globby things like puddings might stick together */ - while (obj && (otmp = obj_nexto_xy(obj, x, y, TRUE)) != 0) { - pudding_merge_message(obj, otmp); + while (globbyobj + && (otmp = obj_nexto_xy(globbyobj, x, y, TRUE)) != 0) { + pudding_merge_message(globbyobj, otmp); /* intentionally not getting the melded object; obj_meld may set * obj to null. */ - (void) obj_meld(&obj, &otmp); + (void) obj_meld(&globbyobj, &otmp); + } + res = (boolean) !globbyobj; + } else if (svc.context.mon_moving && IS_ALTAR(levl[x][y].typ) + && cansee(x,y)) { + doaltarobj(obj); + } else if (obj->oclass == POTION_CLASS && svl.level.flags.temperature > 0 + && (levl[x][y].typ == ROOM || levl[x][y].typ == CORR)) { + /* Potions are sometimes destroyed when landing on very hot + ground. The basic odds are 50% for nonblessed potions and + 30% for blessed potions; if you have handled the object + (i.e. it is or was yours), these odds are adjusted by Luck + (each Luck point affects them by 2%). Artifact potions + would not be affected, if any existed. + + Oil is not affected because its boiling point (and flash + point) are higher than that of water. For example, whale + oil, one of the substances traditionally used in oil lamps, + can survive over 100 degrees Centigrade more heat than + water can.*/ + if (cansee(x,y)) { + /* unconditional "ground" is safe as this only runs for + room and corridor tiles */ + pline("%s up as %s the hot ground.", Tobjnam(obj, "heat"), + is_plural(obj) ? "they hit" : "it hits"); + } + + int survival_chance = obj->blessed ? 70 : 50; + if (obj->invlet) + survival_chance += Luck * 2; + if (obj->otyp == POT_OIL) + survival_chance = 100; + + if (!obj_resists(obj, survival_chance, 100)) { + if (cansee(x,y)) { + pline("%s from the heat!", + is_plural(obj) ? "They shatter" : "It shatters"); + } else { + You_hear("a shattering noise."); + } + breakobj(obj, x, y, FALSE, FALSE); + res = TRUE; } - return (boolean) !obj; } - return FALSE; + + gb.bhitpos = save_bhitpos; + return res; } /* obj is an object dropped on an altar */ void -doaltarobj(obj) -register struct obj *obj; +doaltarobj(struct obj *obj) { if (Blind) return; if (obj->oclass != COIN_CLASS) { /* KMH, conduct */ - u.uconduct.gnostic++; + if (!svc.context.mon_moving && !u.uconduct.gnostic++) + livelog_printf(LL_CONDUCT, + "eschewed atheism, by dropping %s on an altar", + doname(obj)); } else { /* coins don't have bless/curse status */ obj->blessed = obj->cursed = 0; @@ -295,9 +389,10 @@ register struct obj *obj; } } -STATIC_OVL void -trycall(obj) -register struct obj *obj; +/* If obj is neither formally identified nor informally called something + * already, prompt the player to call its object type. */ +void +trycall(struct obj *obj) { if (!objects[obj->otyp].oc_name_known && !objects[obj->otyp].oc_uname) docall(obj); @@ -305,8 +400,8 @@ register struct obj *obj; /* Transforms the sink at the player's position into a fountain, throne, altar or grave. */ -STATIC_DCL void -polymorph_sink() +void +polymorph_sink(void) { uchar sym = S_sink; boolean sinklooted; @@ -316,27 +411,26 @@ polymorph_sink() return; sinklooted = levl[u.ux][u.uy].looted != 0; - level.flags.nsinks--; - levl[u.ux][u.uy].doormask = 0; /* levl[][].flags */ + /* svl.level.flags.nsinks--; // set_levltyp() will update this */ + levl[u.ux][u.uy].flags = 0; switch (rn2(4)) { default: case 0: sym = S_fountain; - levl[u.ux][u.uy].typ = FOUNTAIN; + set_levltyp(u.ux, u.uy, FOUNTAIN); /* updates level.flags.nfountains */ levl[u.ux][u.uy].blessedftn = 0; if (sinklooted) SET_FOUNTAIN_LOOTED(u.ux, u.uy); - level.flags.nfountains++; break; case 1: sym = S_throne; - levl[u.ux][u.uy].typ = THRONE; + set_levltyp(u.ux, u.uy, THRONE); if (sinklooted) levl[u.ux][u.uy].looted = T_LOOTED; break; case 2: sym = S_altar; - levl[u.ux][u.uy].typ = ALTAR; + set_levltyp(u.ux, u.uy, ALTAR); /* 3.6.3: this used to pass 'rn2(A_LAWFUL + 2) - 1' to Align2amask() but that evaluates its argument more than once */ algn = rn2(3) - 1; /* -1 (A_Cha) or 0 (A_Neu) or +1 (A_Law) */ @@ -345,7 +439,7 @@ polymorph_sink() break; case 3: sym = S_room; - levl[u.ux][u.uy].typ = ROOM; + set_levltyp(u.ux, u.uy, ROOM); make_grave(u.ux, u.uy, (char *) 0); if (levl[u.ux][u.uy].typ == GRAVE) sym = S_grave; @@ -362,40 +456,46 @@ polymorph_sink() /* Teleports the sink at the player's position; return True if sink teleported. */ -STATIC_DCL boolean -teleport_sink() +staticfn boolean +teleport_sink(void) { - int cx, cy; - int cnt = 0; - struct trap *trp; - struct engr *eng; + coordxy cx, cy; + unsigned alreadylooted; + int trycnt = 0; do { - cx = rnd(COLNO - 1); - cy = rn2(ROWNO); - trp = t_at(cx, cy); - eng = engr_at(cx, cy); - } while ((levl[cx][cy].typ != ROOM || trp || eng || cansee(cx, cy)) - && cnt++ < 200); - - if (levl[cx][cy].typ == ROOM && !trp && !eng) { - /* create sink at new position */ - levl[cx][cy].typ = SINK; - levl[cx][cy].looted = levl[u.ux][u.uy].looted; - newsym(cx, cy); - /* remove old sink */ - levl[u.ux][u.uy].typ = ROOM; - levl[u.ux][u.uy].looted = 0; - newsym(u.ux, u.uy); - return TRUE; - } +#if 0 /* this isn't incorrect but it is extremely unlikely that spots + * on the level's edge will be ROOM so picking such wastes tries */ + cx = rnd(COLNO - 1); /* 1..COLNO-1 */ + cy = rn2(ROWNO); /* 0..ROWNO-1 */ +#else /* use this instead */ + cx = 1 + rnd((COLNO - 1) - 2); /* 2..COLNO-2 */ + cy = 1 + rn2(ROWNO - 2); /* 1..ROWNO-2 */ +#endif + if (levl[cx][cy].typ == ROOM + && !t_at(cx, cy) && !engr_at(cx, cy) + && (!cansee(cx, cy) || distu(cx, cy) > 3 * 3)) { + /* this ends up having set_levltyp() count all sinks and + fountains on the level twice but that is not a problem */ + alreadylooted = levl[u.ux][u.uy].looted; + /* remove old sink */ + set_levltyp(u.ux, u.uy, ROOM); /* was SINK so updates nsinks */ + levl[u.ux][u.uy].looted = 0; + newsym(u.ux, u.uy); + /* create sink at new position */ + set_levltyp(cx, cy, SINK); /* now SINK so also updates nsinks */ + levl[cx][cy].looted = alreadylooted ? 1 : 0; + newsym(cx, cy); + return TRUE; + } + } while (++trycnt < 200); + return FALSE; } /* obj is a ring being dropped over a kitchen sink */ -STATIC_OVL void -dosinkring(obj) -register struct obj *obj; +staticfn void +dosinkring(struct obj *obj) { struct obj *otmp, *otmp2; boolean ideed = TRUE; @@ -428,6 +528,7 @@ register struct obj *obj; pline("Static electricity surrounds the sink."); break; case RIN_CONFLICT: + Soundeffect(se_drain_noises, 50); You_hear("loud noises coming from the drain."); break; case RIN_SUSTAIN_ABILITY: /* KMH */ @@ -454,7 +555,7 @@ register struct obj *obj; break; case RIN_HUNGER: ideed = FALSE; - for (otmp = level.objects[u.ux][u.uy]; otmp; otmp = otmp2) { + for (otmp = svl.level.objects[u.ux][u.uy]; otmp; otmp = otmp2) { otmp2 = otmp->nexthere; if (otmp != uball && otmp != uchain && !obj_resists(otmp, 1, 99)) { @@ -539,11 +640,12 @@ register struct obj *obj; break; } } - if (ideed) + if (ideed) { trycall(obj); - else if (!nosink) + } else if (!nosink) { + Soundeffect(se_ring_in_drain, 50); You_hear("the ring bouncing down the drainpipe."); - + } if (!rn2(20) && !nosink) { pline_The("sink backs up, leaving %s.", doname(obj)); obj->in_use = FALSE; @@ -560,15 +662,26 @@ register struct obj *obj; /* some common tests when trying to drop or throw items */ boolean -canletgo(obj, word) -struct obj *obj; -const char *word; +canletgo(struct obj *obj, const char *word) { if (obj->owornmask & (W_ARMOR | W_ACCESSORY)) { if (*word) Norep("You cannot %s %s you are wearing.", word, something); return FALSE; } + if (obj == uwep && welded(uwep)) { + /* no weldmsg(), so uwep->bknown might become set silently + if word is "" */ + if (*word) { + const char *hand = body_part(HAND); + + if (bimanual(uwep)) + hand = makeplural(hand); + Norep("You cannot %s %s welded to your %s.", word, something, + hand); + } + return FALSE; + } if (obj->otyp == LOADSTONE && obj->cursed) { /* getobj() kludge sets corpsenm to user's specified count when refusing to split a stack of cursed loadstones */ @@ -597,18 +710,19 @@ const char *word; return TRUE; } -STATIC_PTR int -drop(obj) -register struct obj *obj; +staticfn int +drop(struct obj *obj) { if (!obj) - return 0; + return ECMD_FAIL; if (!canletgo(obj, "drop")) - return 0; + return ECMD_FAIL; + if (obj->otyp == CORPSE && better_not_try_to_drop_that(obj)) + return ECMD_FAIL; if (obj == uwep) { if (welded(uwep)) { weldmsg(obj); - return 0; + return ECMD_FAIL; } setuwep((struct obj *) 0); } @@ -622,19 +736,24 @@ register struct obj *obj; if (u.uswallow) { /* barrier between you and the floor */ if (flags.verbose) { - char *onam_p, monbuf[BUFSZ]; + char *onam_p, *mnam_p, monbuf[BUFSZ]; + mnam_p = mon_nam(u.ustuck); /* doname can call s_suffix, reusing its buffer */ - Strcpy(monbuf, s_suffix(mon_nam(u.ustuck))); + if (digests(u.ustuck->data)) { + Sprintf(monbuf, "%s %s", s_suffix(mnam_p), + mbodypart(u.ustuck, STOMACH)); + mnam_p = monbuf; + } onam_p = is_unpaid(obj) ? yobjnam(obj, (char *) 0) : doname(obj); - You("drop %s into %s %s.", onam_p, monbuf, - mbodypart(u.ustuck, STOMACH)); + + You("drop %s into %s.", onam_p, mnam_p); } } else { if ((obj->oclass == RING_CLASS || obj->otyp == MEAT_RING) && IS_SINK(levl[u.ux][u.uy].typ)) { dosinkring(obj); - return 1; + return ECMD_TIME; } if (!can_reach_floor(TRUE)) { /* we might be levitating due to #invoke Heart of Ahriman; @@ -646,32 +765,26 @@ register struct obj *obj; ELevitation = W_ART; /* other than W_ARTI */ if (flags.verbose) You("drop %s.", doname(obj)); - /* Ensure update when we drop gold objects */ - if (obj->oclass == COIN_CLASS) - context.botl = 1; freeinv(obj); hitfloor(obj, TRUE); if (levhack) float_down(I_SPECIAL | TIMEOUT, W_ARTI | W_ART); - return 1; + return ECMD_TIME; } if (!IS_ALTAR(levl[u.ux][u.uy].typ) && flags.verbose) You("drop %s.", doname(obj)); } + obj->how_lost = LOST_DROPPED; dropx(obj); - return 1; + return ECMD_TIME; } /* dropx - take dropped item out of inventory; called in several places - may produce output (eg ship_object() and dropy() -> sellobj() both produce output) */ void -dropx(obj) -register struct obj *obj; +dropx(struct obj *obj) { - /* Ensure update when we drop gold objects */ - if (obj->oclass == COIN_CLASS) - context.botl = 1; freeinv(obj); if (!u.uswallow) { if (ship_object(obj, u.ux, u.uy, FALSE)) @@ -684,17 +797,14 @@ register struct obj *obj; /* dropy - put dropped object at destination; called from lots of places */ void -dropy(obj) -struct obj *obj; +dropy(struct obj *obj) { dropz(obj, FALSE); } /* dropz - really put dropped object at its destination... */ void -dropz(obj, with_impact) -struct obj *obj; -boolean with_impact; +dropz(struct obj *obj, boolean with_impact) { if (obj == uwep) setuwep((struct obj *) 0); @@ -703,68 +813,84 @@ boolean with_impact; if (obj == uswapwep) setuswapwep((struct obj *) 0); - if (!u.uswallow && flooreffects(obj, u.ux, u.uy, "drop")) - return; - /* uswallow check done by GAN 01/29/87 */ if (u.uswallow) { - boolean could_petrify = FALSE; - boolean could_poly = FALSE; - boolean could_slime = FALSE; - boolean could_grow = FALSE; - boolean could_heal = FALSE; - + /* hero has dropped an item while inside an engulfer */ if (obj != uball) { /* mon doesn't pick up ball */ - if (obj->otyp == CORPSE) { - could_petrify = touch_petrifies(&mons[obj->corpsenm]); - could_poly = polyfodder(obj); - could_slime = (obj->corpsenm == PM_GREEN_SLIME); - could_grow = (obj->corpsenm == PM_WRAITH); - could_heal = (obj->corpsenm == PM_NURSE); - } + /* moving shop item into engulfer's inventory treated as theft */ if (is_unpaid(obj)) (void) stolen_value(obj, u.ux, u.uy, TRUE, FALSE); - (void) mpickobj(u.ustuck, obj); - if (is_animal(u.ustuck->data)) { - if (could_poly || could_slime) { - (void) newcham(u.ustuck, - could_poly ? (struct permonst *) 0 - : &mons[PM_GREEN_SLIME], - FALSE, could_slime); - delobj(obj); /* corpse is digested */ - } else if (could_petrify) { - minstapetrify(u.ustuck, TRUE); - /* Don't leave a cockatrice corpse in a statue */ - if (!u.uswallow) - delobj(obj); - } else if (could_grow) { - (void) grow_up(u.ustuck, (struct monst *) 0); - delobj(obj); /* corpse is digested */ - } else if (could_heal) { - u.ustuck->mhp = u.ustuck->mhpmax; - delobj(obj); /* corpse is digested */ - } - } + /* add to engulfer's inventory if not immediately eaten */ + if (!engulfer_digests_food(obj)) + (void) mpickobj(u.ustuck, obj); } } else { + if (flooreffects(obj, u.ux, u.uy, "drop")) + return; place_object(obj, u.ux, u.uy); if (with_impact) container_impact_dmg(obj, u.ux, u.uy); + impact_disturbs_zombies(obj, with_impact); if (obj == uball) drop_ball(u.ux, u.uy); - else if (level.flags.has_shop) + else if (svl.level.flags.has_shop) sellobj(obj, u.ux, u.uy); stackobj(obj); if (Blind && Levitation) map_object(obj, 0); newsym(u.ux, u.uy); /* remap location under self */ } + encumber_msg(); +} + +/* when swallowed, move dropped object from OBJ_FREE to u.ustuck's inventory; + for purple worm, immediately eat any corpse, glob, or special meat item + from object polymorph; return True if object is used up, False otherwise */ +staticfn boolean +engulfer_digests_food(struct obj *obj) +{ + /* animal swallower (purple worn) eats any + corpse, glob, or meat but not other types of food */ + if (digests(u.ustuck->data) + && (obj->otyp == CORPSE || obj->globby + || obj->otyp == MEATBALL || obj->otyp == ENORMOUS_MEATBALL + || obj->otyp == MEAT_RING || obj->otyp == MEAT_STICK)) { + boolean could_petrify = FALSE, + could_poly = FALSE, could_slime = FALSE, + could_grow = FALSE, could_heal = FALSE; + + if (obj->otyp == CORPSE) { + could_petrify = touch_petrifies(&mons[obj->corpsenm]); + could_poly = polyfood(obj); + could_grow = (obj->corpsenm == PM_WRAITH); + could_heal = (obj->corpsenm == PM_NURSE); + } else if (obj->otyp == GLOB_OF_GREEN_SLIME) { + could_slime = TRUE; + } + /* see or feel the effect */ + pline("%s instantly digested!", Tobjnam(obj, "are")); + + if (could_poly || could_slime) { + (void) newcham(u.ustuck, could_slime ? &mons[PM_GREEN_SLIME] : 0, + could_slime ? NC_SHOW_MSG : NO_NC_FLAGS); + } else if (could_petrify) { + minstapetrify(u.ustuck, TRUE); + } else if (could_grow) { + (void) grow_up(u.ustuck, (struct monst *) 0); + } else if (could_heal) { + healmon(u.ustuck, u.ustuck->mhpmax, 0); + /* False: don't realize that sight is cured from inside */ + mcureblindness(u.ustuck, FALSE); + } + delobj(obj); /* always used up */ + return TRUE; + } + return FALSE; } /* things that must change when not held; recurse into containers. Called for both player and monsters */ void -obj_no_longer_held(obj) -struct obj *obj; +obj_no_longer_held(struct obj *obj) { if (!obj) { return; @@ -784,7 +910,7 @@ struct obj *obj; */ if (!obj->oerodeproof || !rn2(10)) { /* if monsters aren't moving, assume player is responsible */ - if (!context.mon_moving && !program_state.gameover) + if (!svc.context.mon_moving && !program_state.gameover) costly_alteration(obj, COST_DEGRD); obj->otyp = WORM_TOOTH; obj->oerodeproof = 0; @@ -793,15 +919,15 @@ struct obj *obj; } } -/* 'D' command: drop several things */ +/* the #droptype command: drop several things */ int -doddrop() +doddrop(void) { - int result = 0; + int result = ECMD_OK; - if (!invent) { + if (!gi.invent) { You("have nothing to drop."); - return 0; + return ECMD_OK; } add_valid_menu_class(0); /* clear any classes already there */ if (*u.ushops) @@ -817,35 +943,80 @@ doddrop() return result; } +staticfn boolean +better_not_try_to_drop_that(struct obj *otmp) +{ + char buf[BUFSZ]; + + /* u_safe_from_fatal_corpse() with st_all checks for gloves and stoning + * resistance before bothering to prompt you. + */ + if (otmp->otyp == CORPSE && !u_safe_from_fatal_corpse(otmp, st_all)) { + Snprintf( + buf, sizeof buf, + "Drop the %s corpse without %s protection on?", + obj_pmname(otmp), body_part(HAND)); + return (paranoid_ynq(TRUE, buf, FALSE) != 'y'); + } + return FALSE; +} +staticfn int /* check callers */ +menudrop_split(struct obj *otmp, long cnt) +{ + if (cnt && cnt < otmp->quan) { + if (welded(otmp)) { + ; /* don't split */ + } else if (otmp->otyp == LOADSTONE && otmp->cursed) { + /* same kludge as getobj(), for canletgo()'s use */ + otmp->corpsenm = (int) cnt; /* don't split */ + } else { + otmp = splitobj(otmp, cnt); + } + } + return drop(otmp); +} + /* Drop things from the hero's inventory, using a menu. */ -STATIC_OVL int -menu_drop(retry) -int retry; +staticfn int +menu_drop(int retry) { int n, i, n_dropped = 0; - long cnt; struct obj *otmp, *otmp2; menu_item *pick_list; - boolean all_categories = TRUE; - boolean drop_everything = FALSE; + boolean all_categories = TRUE, drop_everything = FALSE, autopick = FALSE; + boolean drop_justpicked = FALSE; + long justpicked_quan = 0; if (retry) { all_categories = (retry == -2); } else if (flags.menu_style == MENU_FULL) { all_categories = FALSE; - n = query_category("Drop what type of items?", invent, - UNPAID_TYPES | ALL_TYPES | CHOOSE_ALL | BUC_BLESSED - | BUC_CURSED | BUC_UNCURSED | BUC_UNKNOWN, + n = query_category("Drop what type of items?", gi.invent, + (UNPAID_TYPES | ALL_TYPES | CHOOSE_ALL + | BUC_BLESSED | BUC_CURSED | BUC_UNCURSED + | BUC_UNKNOWN | JUSTPICKED | INCLUDE_VENOM), &pick_list, PICK_ANY); + /* when paranoid_confirm:A is set, 'A' by itself implies + 'A'+'a' which will be followed by a confirmation prompt; + when that option isn't set, 'A' by itself is rejected + by query_categorry() and result here will be n==0 */ if (!n) - goto drop_done; + goto drop_done; /* no non-autopick category filters specified */ + for (i = 0; i < n; i++) { - if (pick_list[i].item.a_int == ALL_TYPES_SELECTED) + if (pick_list[i].item.a_int == ALL_TYPES_SELECTED) { all_categories = TRUE; - else if (pick_list[i].item.a_int == 'A') - drop_everything = TRUE; - else + } else if (pick_list[i].item.a_int == 'A') { + drop_everything = autopick = TRUE; + } else if (pick_list[i].item.a_int == 'P') { + justpicked_quan = max(0, pick_list[i].count); + drop_justpicked = TRUE; + drop_everything = FALSE; + add_valid_menu_class(pick_list[i].item.a_int); + } else { add_valid_menu_class(pick_list[i].item.a_int); + drop_everything = FALSE; + } } free((genericptr_t) pick_list); } else if (flags.menu_style == MENU_COMBINATION) { @@ -856,18 +1027,18 @@ int retry; i = ggetobj("drop", drop, 0, TRUE, &ggoresults); if (i == -2) all_categories = TRUE; - if (ggoresults & ALL_FINISHED) { + if ((ggoresults & ALL_FINISHED) != 0) { n_dropped = i; goto drop_done; } } - if (drop_everything) { + if (autopick) { /* * Dropping a burning potion of oil while levitating can cause * an explosion which might destroy some of hero's inventory, * so the old code - * for (otmp = invent; otmp; otmp = otmp2) { + * for (otmp = gi.invent; otmp; otmp = otmp2) { * otmp2 = otmp->nobj; * n_dropped += drop(otmp); * } @@ -876,81 +1047,108 @@ int retry; * Use the bypass bit to mark items already processed (hence * not droppable) and rescan inventory until no unbypassed * items remain. + * + * FIXME? if something explodes, or even breaks, we probably + * ought to halt the traversal or perhaps ask player whether + * to halt it. */ - bypass_objlist(invent, FALSE); /* clear bypass bit for invent */ - while ((otmp = nxt_unbypassed_obj(invent)) != 0) - n_dropped += drop(otmp); + bypass_objlist(gi.invent, FALSE); /* clear bypass bit for invent */ + while ((otmp = nxt_unbypassed_obj(gi.invent)) != 0) { + if (drop_everything || all_categories || allow_category(otmp)) + n_dropped += ((drop(otmp) & ECMD_TIME) != 0) ? 1 : 0; + } /* we might not have dropped everything (worn armor, welded weapon, cursed loadstones), so reset any remaining inventory to normal */ - bypass_objlist(invent, FALSE); + bypass_objlist(gi.invent, FALSE); + } else if (drop_justpicked && count_justpicked(gi.invent) == 1) { + /* drop the just picked item automatically, if only one stack */ + otmp = find_justpicked(gi.invent); + if (otmp) + n_dropped += ((menudrop_split(otmp, justpicked_quan) + & ECMD_TIME) != 0) ? 1 : 0; } else { /* should coordinate with perm invent, maybe not show worn items */ - n = query_objlist("What would you like to drop?", &invent, - (USE_INVLET | INVORDER_SORT), &pick_list, PICK_ANY, + n = query_objlist("What would you like to drop?", &gi.invent, + (USE_INVLET | INVORDER_SORT | INCLUDE_VENOM), + &pick_list, PICK_ANY, all_categories ? allow_all : allow_category); if (n > 0) { /* * picklist[] contains a set of pointers into inventory, but * as soon as something gets dropped, they might become stale - * (see the drop_everything code above for an explanation). - * Just checking to see whether one is still in the invent + * (see the autopick code above for an explanation). + * Just checking to see whether one is still in the gi.invent * chain is not sufficient validation since destroyed items * will be freed and items we've split here might have already * reused that memory and put the same pointer value back into - * invent. Ditto for using invlet to validate. So we start - * by setting bypass on all of invent, then check each pointer - * to verify that it is in invent and has that bit set. + * gi.invent. Ditto for using invlet to validate. So we start + * by setting bypass on all of gi.invent, then check each pointer + * to verify that it is in gi.invent and has that bit set. */ - bypass_objlist(invent, TRUE); + bypass_objlist(gi.invent, TRUE); for (i = 0; i < n; i++) { otmp = pick_list[i].item.a_obj; - for (otmp2 = invent; otmp2; otmp2 = otmp2->nobj) + for (otmp2 = gi.invent; otmp2; otmp2 = otmp2->nobj) if (otmp2 == otmp) break; if (!otmp2 || !otmp2->bypass) continue; /* found next selected invent item */ - cnt = pick_list[i].count; - if (cnt < otmp->quan) { - if (welded(otmp)) { - ; /* don't split */ - } else if (otmp->otyp == LOADSTONE && otmp->cursed) { - /* same kludge as getobj(), for canletgo()'s use */ - otmp->corpsenm = (int) cnt; /* don't split */ - } else { - otmp = splitobj(otmp, cnt); - } - } - n_dropped += drop(otmp); + n_dropped += ((menudrop_split(otmp, pick_list[i].count) + & ECMD_TIME) != 0) ? 1 : 0; } - bypass_objlist(invent, FALSE); /* reset invent to normal */ + bypass_objlist(gi.invent, FALSE); /* reset gi.invent to normal */ free((genericptr_t) pick_list); } } drop_done: - return n_dropped; + return (n_dropped ? ECMD_TIME : ECMD_OK); } -/* on a ladder, used in goto_level */ -static NEARDATA boolean at_ladder = FALSE; +staticfn boolean +u_stuck_cannot_go(const char *updn) +{ + if (u.ustuck) { + if (u.uswallow || !sticks(gy.youmonst.data)) { + You("are %s, and cannot go %s.", + !u.uswallow ? "being held" + : digests(u.ustuck->data) ? "swallowed" + : "engulfed", updn); + return TRUE; + } else { + struct monst *mtmp = u.ustuck; + + set_ustuck((struct monst *) 0); + You("release %s.", mon_nam(mtmp)); + } + } + return FALSE; +} -/* the '>' command */ +/* the #down command */ int -dodown() +dodown(void) { struct trap *trap = 0; - boolean stairs_down = ((u.ux == xdnstair && u.uy == ydnstair) - || (u.ux == sstairs.sx && u.uy == sstairs.sy - && !sstairs.up)), - ladder_down = (u.ux == xdnladder && u.uy == ydnladder); + stairway *stway; + boolean stairs_down, ladder_down; + + set_move_cmd(DIR_DOWN, 0); if (u_rooted()) - return 1; + return ECMD_TIME; if (stucksteed(TRUE)) { - return 0; + return ECMD_OK; } + + stairs_down = ladder_down = FALSE; + if ((stway = stairway_at(u.ux, u.uy)) != 0 && !stway->up) { + stairs_down = !stway->isladder; + ladder_down = !stairs_down; + } + /* Levitation might be blocked, but player can still use '>' to turn off controlled levitation */ if (HLevitation || ELevitation) { @@ -959,35 +1157,37 @@ dodown() if (ELevitation & W_ARTI) { struct obj *obj; - for (obj = invent; obj; obj = obj->nobj) { + for (obj = gi.invent; obj; obj = obj->nobj) { if (obj->oartifact && artifact_has_invprop(obj, LEVITATION)) { - if (obj->age < monstermoves) - obj->age = monstermoves; + if (obj->age < svm.moves) + obj->age = svm.moves; obj->age += rnz(100); } } } if (float_down(I_SPECIAL | TIMEOUT, W_ARTI)) { - return 1; /* came down, so moved */ + return ECMD_TIME; /* came down, so moved */ } else if (!HLevitation && !ELevitation) { Your("latent levitation ceases."); - return 1; /* did something, effectively moved */ + return ECMD_TIME; /* did something, effectively moved */ } } if (BLevitation) { ; /* weren't actually floating after all */ } else if (Blind) { + /* glyph_to_cmap() is a macro which expands its argument many + times; use this to do part of its work just once */ + int glyph_at_uxuy = levl[u.ux][u.uy].glyph; + /* Avoid alerting player to an unknown stair or ladder. * Changes the message for a covered, known staircase * too; staircase knowledge is not stored anywhere. */ if (stairs_down) - stairs_down = - (glyph_to_cmap(levl[u.ux][u.uy].glyph) == S_dnstair); + stairs_down = (glyph_to_cmap(glyph_at_uxuy) == S_dnstair); else if (ladder_down) - ladder_down = - (glyph_to_cmap(levl[u.ux][u.uy].glyph) == S_dnladder); + ladder_down = (glyph_to_cmap(glyph_at_uxuy) == S_dnladder); } if (Is_airlevel(&u.uz)) You("are floating in the %s.", surface(u.ux, u.uy)); @@ -995,10 +1195,10 @@ dodown() You("are floating in %s.", is_pool(u.ux, u.uy) ? "the water" : "a bubble of air"); else - floating_above(stairs_down ? "stairs" : ladder_down - ? "ladder" - : surface(u.ux, u.uy)); - return 0; /* didn't move */ + floating_above(stairs_down ? "stairs" + : ladder_down ? "ladder" + : surface(u.ux, u.uy)); + return ECMD_OK; /* didn't move */ } if (Upolyd && ceiling_hider(&mons[u.umonnum]) && u.uundetected) { @@ -1015,66 +1215,64 @@ dodown() dotrap(trap, TOOKPLUNGE); } } - return 1; /* came out of hiding; might need '>' again to go down */ + return ECMD_TIME; /* came out of hiding; need '>' again to go down */ } + if (u_stuck_cannot_go("down")) + return ECMD_TIME; + if (!stairs_down && !ladder_down) { trap = t_at(u.ux, u.uy); if (trap && (uteetering_at_seen_pit(trap) || uescaped_shaft(trap))) { dotrap(trap, TOOKPLUNGE); - return 1; + return ECMD_TIME; } else if (!trap || !is_hole(trap->ttyp) || !Can_fall_thru(&u.uz) || !trap->tseen) { - if (flags.autodig && !context.nopick && uwep && is_pick(uwep)) { + if (flags.autodig && !svc.context.nopick + && uwep && is_pick(uwep)) { return use_pick_axe2(uwep); } else { - You_cant("go down here."); - return 0; + You_cant("go down here%s.", + (trap && trap->ttyp == VIBRATING_SQUARE) ? " yet" + : ""); + return ECMD_OK; } } } - if (u.ustuck) { - You("are %s, and cannot go down.", - !u.uswallow ? "being held" : is_animal(u.ustuck->data) - ? "swallowed" - : "engulfed"); - return 1; - } if (on_level(&valley_level, &u.uz) && !u.uevent.gehennom_entered) { You("are standing at the gate to Gehennom."); pline("Unspeakable cruelty and harm lurk down there."); - if (yn("Are you sure you want to enter?") != 'y') - return 0; - else - pline("So be it."); + if (y_n("Are you sure you want to enter?") != 'y') + return ECMD_OK; + pline("So be it."); u.uevent.gehennom_entered = 1; /* don't ask again */ } if (!next_to_u()) { You("are held back by your pet!"); - return 0; + return ECMD_OK; } if (trap) { const char *down_or_thru = trap->ttyp == HOLE ? "down" : "through"; - const char *actn = Flying ? "fly" : locomotion(youmonst.data, "jump"); + const char *actn = u_locomotion("jump"); - if (youmonst.data->msize >= MZ_HUGE) { + if (gy.youmonst.data->msize >= MZ_HUGE) { char qbuf[QBUFSZ]; You("don't fit %s easily.", down_or_thru); Sprintf(qbuf, "Try to squeeze %s?", down_or_thru); - if (yn(qbuf) == 'y') { + if (y_n(qbuf) == 'y') { if (!rn2(3)) { actn = "manage to squeeze"; losehp(Maybe_Half_Phys(rnd(4)), "contusion from a small passage", KILLED_BY); } else { You("were unable to fit %s.", down_or_thru); - return 0; + return ECMD_OK; } } else { - return 0; + return ECMD_OK; } } You("%s %s the %s.", actn, down_or_thru, @@ -1082,81 +1280,82 @@ dodown() } if (trap && Is_stronghold(&u.uz)) { goto_hell(FALSE, TRUE); + } else if (trap && trap->dst.dlevel != -1) { + d_level tdst; + assign_level(&tdst, &(trap->dst)); + (void) clamp_hole_destination(&tdst); + goto_level(&tdst, FALSE, FALSE, FALSE); } else { - at_ladder = (boolean) (levl[u.ux][u.uy].typ == LADDER); + ga.at_ladder = (boolean) (levl[u.ux][u.uy].typ == LADDER); next_level(!trap); - at_ladder = FALSE; + ga.at_ladder = FALSE; } - return 1; + return ECMD_TIME; } -/* the '<' command */ +/* the #up command - move up a staircase */ int -doup() +doup(void) { + stairway *stway = stairway_at(u.ux,u.uy); + + set_move_cmd(DIR_UP, 0); + if (u_rooted()) - return 1; + return ECMD_TIME; /* "up" to get out of a pit... */ if (u.utrap && u.utraptype == TT_PIT) { climb_pit(); - return 1; + return ECMD_TIME; } - if ((u.ux != xupstair || u.uy != yupstair) - && (!xupladder || u.ux != xupladder || u.uy != yupladder) - && (!sstairs.sx || u.ux != sstairs.sx || u.uy != sstairs.sy - || !sstairs.up)) { + if (!stway || (stway && !stway->up)) { You_cant("go up here."); - return 0; + return ECMD_OK; } if (stucksteed(TRUE)) { - return 0; - } - if (u.ustuck) { - You("are %s, and cannot go up.", - !u.uswallow ? "being held" : is_animal(u.ustuck->data) - ? "swallowed" - : "engulfed"); - return 1; + return ECMD_OK; } + + if (u_stuck_cannot_go("up")) + return ECMD_TIME; + if (near_capacity() > SLT_ENCUMBER) { /* No levitation check; inv_weight() already allows for it */ Your("load is too heavy to climb the %s.", levl[u.ux][u.uy].typ == STAIRS ? "stairs" : "ladder"); - return 1; + return ECMD_TIME; } if (ledger_no(&u.uz) == 1) { if (iflags.debug_fuzzer) - return 0; - if (yn("Beware, there will be no return! Still climb?") != 'y') - return 0; + return ECMD_OK; + if (y_n("Beware, there will be no return! Still climb?") != 'y') + return ECMD_OK; } if (!next_to_u()) { You("are held back by your pet!"); - return 0; + return ECMD_OK; } - at_ladder = (boolean) (levl[u.ux][u.uy].typ == LADDER); + ga.at_ladder = (boolean) (levl[u.ux][u.uy].typ == LADDER); prev_level(TRUE); - at_ladder = FALSE; - return 1; + ga.at_ladder = FALSE; + return ECMD_TIME; } -d_level save_dlevel = { 0, 0 }; - /* check that we can write out the current level */ -STATIC_OVL int -currentlevel_rewrite() +staticfn NHFILE * +currentlevel_rewrite(void) { - register int fd; + NHFILE *nhfp; char whynot[BUFSZ]; /* since level change might be a bit slow, flush any buffered screen * output (like "you fall through a trap door") */ mark_synch(); - fd = create_levelfile(ledger_no(&u.uz), whynot); - if (fd < 0) { + nhfp = create_levelfile(ledger_no(&u.uz), whynot); + if (!nhfp) { /* * This is not quite impossible: e.g., we may have * exceeded our quota. If that is the case then we @@ -1165,46 +1364,40 @@ currentlevel_rewrite() * writable. */ pline1(whynot); - return -1; + return (NHFILE *) 0; } -#ifdef MFLOPPY - if (!savelev(fd, ledger_no(&u.uz), COUNT_SAVE)) { - (void) nhclose(fd); - delete_levelfile(ledger_no(&u.uz)); - pline("NetHack is out of disk space for making levels!"); - You("can save, quit, or continue playing."); - return -1; - } -#endif - return fd; + return nhfp; } #ifdef INSURANCE void -save_currentstate() +save_currentstate(void) { - int fd; + NHFILE *nhfp; + program_state.in_checkpoint++; if (flags.ins_chkpt) { /* write out just-attained level, with pets and everything */ - fd = currentlevel_rewrite(); - if (fd < 0) + nhfp = currentlevel_rewrite(); + if (!nhfp) return; - bufon(fd); - savelev(fd, ledger_no(&u.uz), WRITE_SAVE); - bclose(fd); + if (nhfp->structlevel) + bufon(nhfp->fd); + nhfp->mode = WRITING; + savelev(nhfp,ledger_no(&u.uz)); + close_nhfile(nhfp); } /* write out non-level state */ savestateinlock(); + program_state.in_checkpoint--; } #endif /* static boolean -badspot(x, y) -register xchar x, y; +badspot(coordxy x, coordxy y) { return (boolean) ((levl[x][y].typ != ROOM && levl[x][y].typ != AIR @@ -1216,8 +1409,7 @@ register xchar x, y; /* when arriving on a level, if hero and a monster are trying to share same spot, move one; extracted from goto_level(); also used by wiz_makemap() */ void -u_collide_m(mtmp) -struct monst *mtmp; +u_collide_m(struct monst *mtmp) { coord cc; @@ -1234,11 +1426,11 @@ struct monst *mtmp; 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, youmonst.data) - && distu(cc.x, cc.y) <= 2) + if (!rn2(2) && enexto(&cc, u.ux, u.uy, gy.youmonst.data) + && next2u(cc.x, cc.y)) u_on_newpos(cc.x, cc.y); /*[maybe give message here?]*/ else - mnexto(mtmp); + mnexto(mtmp, RLOC_NOMSG); if ((mtmp = m_at(u.ux, u.uy)) != 0) { /* there was an unconditional impossible("mnexto failed") @@ -1246,36 +1438,81 @@ struct monst *mtmp; with the situation, so only say something when debugging */ if (wizard) pline("(monster in hero's way)"); - if (!rloc(mtmp, TRUE) || (mtmp = m_at(u.ux, u.uy)) != 0) + if (!rloc(mtmp, RLOC_NOMSG) || (mtmp = m_at(u.ux, u.uy)) != 0) /* no room to move it; send it away, to return later */ m_into_limbo(mtmp); } } +staticfn void +familiar_level_msg(void) +{ + static const char *const fam_msgs[4] = { + "You have a sense of deja vu.", + "You feel like you've been here before.", + "This place %s familiar...", 0 /* no message */ + }; + static const char *const halu_fam_msgs[4] = { + "Whoa! Everything %s different.", + "You are surrounded by twisty little passages, all alike.", + "Gee, this %s like uncle Conan's place...", 0 /* no message */ + }; + const char *mesg; + char buf[BUFSZ]; + int which = rn2(4); + + if (Hallucination) + mesg = halu_fam_msgs[which]; + else + mesg = fam_msgs[which]; + if (mesg && strchr(mesg, '%')) { + DISABLE_WARNING_FORMAT_NONLITERAL + Sprintf(buf, mesg, !Blind ? "looks" : "seems"); + RESTORE_WARNING_FORMAT_NONLITERAL + mesg = buf; + } + if (mesg) + pline1(mesg); +} + void -goto_level(newlevel, at_stairs, falling, portal) -d_level *newlevel; -boolean at_stairs, falling, portal; +goto_level( + d_level *newlevel, /* destination */ + boolean at_stairs, /* True if arriving via stairs/ladder */ + boolean falling, /* when falling to level, objects might tag along */ + boolean portal) /* True if arriving via magic portal */ { - int fd, l_idx; - xchar new_ledger; + int l_idx, save_mode; + NHFILE *nhfp; + xint16 new_ledger; boolean cant_go_back, great_effort, up = (depth(newlevel) < depth(&u.uz)), newdungeon = (u.uz.dnum != newlevel->dnum), + leaving_tutorial = FALSE, was_in_W_tower = In_W_tower(u.ux, u.uy, &u.uz), familiar = FALSE, new = FALSE; /* made a new level? */ struct monst *mtmp; char whynot[BUFSZ]; - char *annotation; + int dist = depth(newlevel) - depth(&u.uz); + boolean do_fall_dmg = FALSE; + schar prev_temperature = svl.level.flags.temperature; if (dunlev(newlevel) > dunlevs_in_dungeon(newlevel)) newlevel->dlevel = dunlevs_in_dungeon(newlevel); - if (newdungeon && In_endgame(newlevel)) { /* 1st Endgame Level !!! */ - if (!u.uhave.amulet) - return; /* must have the Amulet */ - if (!wizard) /* wizard ^V can bypass Earth level */ - assign_level(newlevel, &earth_level); /* (redundant) */ + if (newdungeon) { + if (In_endgame(newlevel)) { /* 1st Endgame Level !!! */ + if (!u.uhave.amulet) + return; /* must have the Amulet */ + if (!wizard) /* wizard ^V can bypass Earth level */ + assign_level(newlevel, &earth_level); /* (redundant) */ + } else if (In_tutorial(newlevel)) { + tutorial(TRUE); /* entering tutorial */ + } else if (In_tutorial(&u.uz)) { + tutorial(FALSE); /* leaving tutorial */ + up = FALSE; /* re-enter level 1 as if starting new game */ + leaving_tutorial = TRUE; + } } new_ledger = ledger_no(newlevel); if (new_ledger <= 0) @@ -1295,15 +1532,22 @@ boolean at_stairs, falling, portal; * -1 11.46 12.50 12.5 * -2 5.21 4.17 0.0 * -3 2.08 0.0 0.0 + * + * 5.0.0: the chance for the "mysterious force" to kick in goes down + * as it kicks in, starting at 25% per climb attempt and dropping off + * gradually but substantially. The drop off is greater when hero is + * sent down farther so benefits lawfuls more than chaotics this time. */ if (Inhell && up && u.uhave.amulet && !newdungeon && !portal && (dunlev(&u.uz) < dunlevs_in_dungeon(&u.uz) - 3)) { - if (!rn2(4)) { + if (!rn2(4 + svc.context.mysteryforce)) { int odds = 3 + (int) u.ualign.type, /* 2..4 */ - diff = odds <= 1 ? 0 : rn2(odds); /* paranoia */ + diff = (odds <= 1) ? 0 : rn2(odds); /* paranoia */ if (diff != 0) { assign_rnd_level(newlevel, &u.uz, diff); + /* assign_rnd_level() may have used a value less than diff */ + diff = newlevel->dlevel - u.uz.dlevel; /* actual descent */ /* if inside the tower, stay inside */ if (was_in_W_tower && !On_W_tower_level(newlevel)) diff = 0; @@ -1311,15 +1555,20 @@ boolean at_stairs, falling, portal; if (diff == 0) assign_level(newlevel, &u.uz); - new_ledger = ledger_no(newlevel); - pline("A mysterious force momentarily surrounds you..."); + /* each time it kicks in, the chance of doing so again may drop; + that drops faster, on average, when being sent down farther so + while the impact is reduced for everybody compared to earlier + versions, it is reduced least for chaotics, most for lawfuls */ + svc.context.mysteryforce += rn2(diff + 2); /* L:0-4,N:0-3,C:0-2 */ + if (on_level(newlevel, &u.uz)) { - (void) safe_teleds(FALSE); + (void) safe_teleds(TELEDS_NO_FLAGS); (void) next_to_u(); return; - } else - at_stairs = at_ladder = FALSE; + } + new_ledger = ledger_no(newlevel); + at_stairs = ga.at_ladder = FALSE; } } @@ -1334,12 +1583,19 @@ boolean at_stairs, falling, portal; if (on_level(newlevel, &u.uz)) return; /* this can happen */ + if (gl.luacore && nhcb_counts[NHCB_LVL_LEAVE]) { + lua_getglobal(gl.luacore, "nh_callback_run"); + lua_pushstring(gl.luacore, nhcb_name[NHCB_LVL_LEAVE]); + nhl_pcall_handle(gl.luacore, 1, 0, "goto_level", NHLpa_panic); + lua_settop(gl.luacore, 0); + } + /* tethered movement makes level change while trapped feasible */ if (u.utrap && u.utraptype == TT_BURIEDBALL) buried_ball_to_punishment(); /* (before we save/leave old level) */ - fd = currentlevel_rewrite(); - if (fd < 0) + nhfp = currentlevel_rewrite(); + if (!nhfp) return; /* discard context which applies to the level we're leaving; @@ -1349,7 +1605,7 @@ boolean at_stairs, falling, portal; maybe_reset_pick((struct obj *) 0); reset_trapset(); /* even if to-be-armed trap obj is accompanying hero */ iflags.travelcc.x = iflags.travelcc.y = 0; /* travel destination cache */ - context.polearm.hitmon = (struct monst *) 0; /* polearm target */ + svc.context.polearm.hitmon = (struct monst *) 0; /* polearm target */ /* digging context is level-aware and can actually be resumed if hero returns to the previous level without any intervening dig */ @@ -1361,12 +1617,11 @@ boolean at_stairs, falling, portal; unplacebc(); reset_utrap(FALSE); /* needed in level_tele */ fill_pit(u.ux, u.uy); - u.ustuck = 0; /* idem */ - u.uinwater = 0; + set_ustuck((struct monst *) 0); /* clear u.ustuck and u.uswallow */ + set_uinwater(0); /* u.uinwater = 0 */ u.uundetected = 0; /* not hidden, even if means are available */ - keepdogs(FALSE); - if (u.uswallow) /* idem */ - u.uswldtim = u.uswallow = 0; + if (!iflags.nofollowers) + keepdogs(FALSE); recalc_mapseen(); /* recalculate map overview before we leave the level */ /* * We no longer see anything on the level. Make sure that this @@ -1382,38 +1637,34 @@ boolean at_stairs, falling, portal; * for the level being left, to recover dynamic memory in use and * to avoid dangling timers and light sources. */ - cant_go_back = (newdungeon && In_endgame(newlevel)); + cant_go_back = ((newdungeon && In_endgame(newlevel)) || leaving_tutorial); if (!cant_go_back) { update_mlstmv(); /* current monsters are becoming inactive */ - bufon(fd); /* use buffered output */ - } - savelev(fd, ledger_no(&u.uz), - cant_go_back ? FREE_SAVE : (WRITE_SAVE | FREE_SAVE)); - /* air bubbles and clouds are saved in game-state rather than with the - level they're used on; in normal play, you can't leave and return - to any endgame level--bubbles aren't needed once you move to the - next level so used to be discarded when the next special level was - loaded; but in wizard mode you can leave and return, and since they - aren't saved with the level and restored upon return (new ones are - created instead), we need to discard them to avoid a memory leak; - so bubbles are now discarded as we leave the level they're used on */ - if (Is_waterlevel(&u.uz) || Is_airlevel(&u.uz)) - save_waterlevel(-1, FREE_SAVE); - bclose(fd); + if (nhfp->structlevel) + bufon(nhfp->fd); /* use buffered output */ + } else { + free_luathemes(leaving_tutorial ? tut_themes : most_themes); + } + save_mode = nhfp->mode; + nhfp->mode = cant_go_back ? FREEING : (WRITING | FREEING); + savelev(nhfp, ledger_no(&u.uz)); + nhfp->mode = save_mode; + close_nhfile(nhfp); if (cant_go_back) { /* discard unreachable levels; keep #0 */ for (l_idx = maxledgerno(); l_idx > 0; --l_idx) - delete_levelfile(l_idx); + if (!leaving_tutorial || ledger_to_dnum(l_idx) == tutorial_dnum) + delete_levelfile(l_idx); /* mark #overview data for all dungeon branches as uninteresting */ - for (l_idx = 0; l_idx < n_dgns; ++l_idx) - remdun_mapseen(l_idx); + for (l_idx = 0; l_idx < svn.n_dgns; ++l_idx) + if (!leaving_tutorial || l_idx == tutorial_dnum) + remdun_mapseen(l_idx); + /* get rid of mons & objs scheduled to migrate to discarded levels */ + discard_migrations(); } if (Is_rogue_level(newlevel) || Is_rogue_level(&u.uz)) - assign_graphics(Is_rogue_level(newlevel) ? ROGUESET : PRIMARY); -#ifdef USE_TILES - substitute_tiles(newlevel); -#endif + assign_graphics(Is_rogue_level(newlevel) ? ROGUESET : PRIMARYSET); check_gold_symbol(); /* record this level transition as a potential seen branch unless using * some non-standard means of transportation (level teleport). @@ -1423,7 +1674,7 @@ boolean at_stairs, falling, portal; assign_level(&u.uz0, &u.uz); assign_level(&u.uz, newlevel); assign_level(&u.utolev, newlevel); - u.utotype = 0; + u.utotype = UTOTYPE_NONE; if (!builds_up(&u.uz)) { /* usual case */ if (dunlev(&u.uz) > dunlev_reached(&u.uz)) dunlev_reached(&u.uz) = dunlev(&u.uz); @@ -1432,64 +1683,73 @@ boolean at_stairs, falling, portal; || dunlev(&u.uz) < dunlev_reached(&u.uz)) dunlev_reached(&u.uz) = dunlev(&u.uz); } - reset_rndmonst(NON_PM); /* u.uz change affects monster generation */ + stairway_free_all(); /* set default level change destination areas */ /* the special level code may override these */ - (void) memset((genericptr_t) &updest, 0, sizeof updest); - (void) memset((genericptr_t) &dndest, 0, sizeof dndest); + (void) memset((genericptr_t) &svu.updest, 0, sizeof svu.updest); + (void) memset((genericptr_t) &svd.dndest, 0, sizeof svd.dndest); - if (!(level_info[new_ledger].flags & LFILE_EXISTS)) { + if (!(svl.level_info[new_ledger].flags & LFILE_EXISTS)) { /* entering this level for first time; make it now */ - if (level_info[new_ledger].flags & (FORGOTTEN | VISITED)) { + if (svl.level_info[new_ledger].flags & (VISITED)) { impossible("goto_level: returning to discarded level?"); - level_info[new_ledger].flags &= ~(FORGOTTEN | VISITED); + svl.level_info[new_ledger].flags &= ~(VISITED); } mklev(); new = TRUE; /* made the level */ + familiar = bones_include_name(svp.plname); } else { /* returning to previously visited level; reload it */ - fd = open_levelfile(new_ledger, whynot); - if (tricked_fileremoved(fd, whynot)) { + nhfp = open_levelfile(new_ledger, whynot); + if (tricked_fileremoved(nhfp, whynot)) { /* we'll reach here if running in wizard mode */ error("Cannot continue this game."); } reseed_random(rn2); reseed_random(rn2_on_display_rng); - minit(); /* ZEROCOMP */ - getlev(fd, hackpid, new_ledger, FALSE); - /* when in wizard mode, it is possible to leave from and return to - any level in the endgame; above, we discarded bubble/cloud info - when leaving Plane of Water or Air so recreate some now */ - if (Is_waterlevel(&u.uz) || Is_airlevel(&u.uz)) - restore_waterlevel(-1); - (void) nhclose(fd); + getlev(nhfp, svh.hackpid, new_ledger); + close_nhfile(nhfp); oinit(); /* reassign level dependent obj probabilities */ } reglyph_darkroom(); - u.uinwater = 0; + set_uinwater(0); /* u.uinwater = 0 */ /* do this prior to level-change pline messages */ vision_reset(); /* clear old level's line-of-sight */ - vision_full_recalc = 0; /* don't let that reenable vision yet */ + gv.vision_full_recalc = 0; /* don't let that reenable vision yet */ flush_screen(-1); /* ensure all map flushes are postponed */ if (portal && !In_endgame(&u.uz)) { /* find the portal on the new level */ - register struct trap *ttrap; + struct trap *ttrap; - for (ttrap = ftrap; ttrap; ttrap = ttrap->ntrap) + for (ttrap = gf.ftrap; ttrap; ttrap = ttrap->ntrap) if (ttrap->ttyp == MAGIC_PORTAL) break; - if (!ttrap) - panic("goto_level: no corresponding portal!"); - seetrap(ttrap); - u_on_newpos(ttrap->tx, ttrap->ty); + if (!ttrap) { + if (u.uevent.qexpelled + && (Is_qstart(&u.uz0) || Is_qstart(&u.uz))) { + /* we're coming back from or going into the quest home level, + after already getting expelled once. The portal back + doesn't exist anymore - see expulsion(). */ + u_on_rndspot(0); + } else { + if (!iflags.debug_fuzzer) + impossible("goto_level: no corresponding portal!"); + u_on_rndspot(0); + } + } else { + seetrap(ttrap); + u_on_newpos(ttrap->tx, ttrap->ty); + } } else if (at_stairs && !In_endgame(&u.uz)) { if (up) { - if (at_ladder) - u_on_newpos(xdnladder, ydnladder); - else if (newdungeon) + stairway *stway = stairway_find_from(&u.uz0, ga.at_ladder); + if (stway) { + u_on_newpos(stway->sx, stway->sy); + stway->u_traversed = TRUE; + } else if (newdungeon) u_on_sstairs(1); else u_on_dnstairs(); @@ -1499,13 +1759,15 @@ boolean at_stairs, falling, portal; if (flags.verbose || great_effort) pline("%s %s up%s the %s.", great_effort ? "With great effort, you" : "You", - Levitation ? "float" : Flying ? "fly" : "climb", - (Flying && at_ladder) ? " along" : "", - at_ladder ? "ladder" : "stairs"); + u_locomotion("climb"), + (Flying && ga.at_ladder) ? " along" : "", + ga.at_ladder ? "ladder" : "stairs"); } else { /* down */ - if (at_ladder) - u_on_newpos(xupladder, yupladder); - else if (newdungeon) + stairway *stway = stairway_find_from(&u.uz0, ga.at_ladder); + if (stway) { + u_on_newpos(stway->sx, stway->sy); + stway->u_traversed = TRUE; + } else if (newdungeon) u_on_sstairs(0); else u_on_upstairs(); @@ -1514,35 +1776,37 @@ boolean at_stairs, falling, portal; } else if (Flying) { if (flags.verbose) You("fly down %s.", - at_ladder ? "along the ladder" : "the stairs"); + ga.at_ladder ? "along the ladder" : "the stairs"); } else if (near_capacity() > UNENCUMBERED || Punished || Fumbling) { - You("fall down the %s.", at_ladder ? "ladder" : "stairs"); + You("fall down the %s.", ga.at_ladder ? "ladder" : "stairs"); if (Punished) { drag_down(); - ballrelease(FALSE); + if (!welded(uball)) + ballrelease(FALSE); } /* falling off steed has its own losehp() call */ if (u.usteed) dismount_steed(DISMOUNT_FELL); else losehp(Maybe_Half_Phys(rnd(3)), - at_ladder ? "falling off a ladder" + ga.at_ladder ? "falling off a ladder" : "tumbling down a flight of stairs", KILLED_BY); selftouch("Falling, you"); } else { /* ordinary descent */ if (flags.verbose) - You("%s.", at_ladder ? "climb down the ladder" + You("%s.", ga.at_ladder ? "climb down the ladder" : "descend the stairs"); } } } else { /* trap door or level_tele or In_endgame */ u_on_rndspot((up ? 1 : 0) | (was_in_W_tower ? 2 : 0)); if (falling) { - if (Punished) + if (Punished && !welded(uball)) ballfall(); selftouch("Falling, you"); + do_fall_dmg = TRUE; } } @@ -1563,39 +1827,36 @@ boolean at_stairs, falling, portal; if ((mtmp = m_at(u.ux, u.uy)) != 0) u_collide_m(mtmp); - initrack(); - /* initial movement of bubbles just before vision_recalc */ if (Is_waterlevel(&u.uz) || Is_airlevel(&u.uz)) movebubbles(); - else if (Is_firelevel(&u.uz)) + else if (svl.level.flags.fumaroles) fumaroles(); - if (level_info[new_ledger].flags & FORGOTTEN) { - forget_map(ALL_MAP); /* forget the map */ - forget_traps(); /* forget all traps too */ - familiar = TRUE; - level_info[new_ledger].flags &= ~FORGOTTEN; - } - /* Reset the screen. */ vision_reset(); /* reset the blockages */ - docrt(); /* does a full vision recalc */ + reset_glyphmap(gm_levelchange); + notice_mon_off(); /* not noticing monsters yet! */ + docrt(); /* does a full vision recalc */ flush_screen(-1); /* * Move all plines beyond the screen reset. */ + /* deferred arrival message for level teleport looks odd if given + after the various messages below, so give it before them; + [it might have already been delivered via docrt() -> see_monsters() + -> Sting_effects() -> maybe_lvltport_feedback(), in which case + 'dfr_post_msg' has already been reset to Null]; + if 'dfr_post_msg' is "you materialize on a different level" then + maybe_lvltport_feedback() will deliver it now and then free it */ + if (gd.dfr_post_msg) + maybe_lvltport_feedback(); /* potentially called by Sting_effects() */ + /* special levels can have a custom arrival message */ deliver_splev_message(); - /* give room entrance message, if any */ - check_special_room(FALSE); - - /* deliver objects traveling with player */ - obj_delivery(TRUE); - /* Check whether we just entered Gehennom. */ if (!In_hell(&u.uz0) && Inhell) { if (Is_valley(&u.uz)) { @@ -1604,57 +1865,36 @@ boolean at_stairs, falling, portal; #ifdef MICRO display_nhwindow(WIN_MESSAGE, FALSE); #endif + Soundeffect(se_groans_and_moans, 25); You_hear("groans and moans everywhere."); - } else - pline("It is hot here. You smell smoke..."); - u.uachieve.enter_gehennom = 1; + } + + record_achievement(ACH_HELL); /* reached Gehennom */ } /* in case we've managed to bypass the Valley's stairway down */ if (Inhell && !Is_valley(&u.uz)) u.uevent.gehennom_entered = 1; - if (familiar) { - static const char *const fam_msgs[4] = { - "You have a sense of deja vu.", - "You feel like you've been here before.", - "This place %s familiar...", 0 /* no message */ - }; - static const char *const halu_fam_msgs[4] = { - "Whoa! Everything %s different.", - "You are surrounded by twisty little passages, all alike.", - "Gee, this %s like uncle Conan's place...", 0 /* no message */ - }; - const char *mesg; - char buf[BUFSZ]; - int which = rn2(4); - - if (Hallucination) - mesg = halu_fam_msgs[which]; - else - mesg = fam_msgs[which]; - if (mesg && index(mesg, '%')) { - Sprintf(buf, mesg, !Blind ? "looks" : "seems"); - mesg = buf; - } - if (mesg) - pline1(mesg); - } + if (familiar) + familiar_level_msg(); /* special location arrival messages/events */ if (In_endgame(&u.uz)) { - if (new &&on_level(&u.uz, &astral_level)) + if (newdungeon) + record_achievement(ACH_ENDG); /* reached endgame */ + if (new && on_level(&u.uz, &astral_level)) { final_level(); /* guardian angel,&c */ - else if (newdungeon && u.uhave.amulet) + record_achievement(ACH_ASTR); /* reached Astral level */ + } else if (newdungeon && u.uhave.amulet) { resurrect(); /* force confrontation with Wizard */ + } } else if (In_quest(&u.uz)) { onquest(); /* might be reaching locate|goal level */ - } else if (In_V_tower(&u.uz)) { - if (newdungeon && In_hell(&u.uz0)) - pline_The("heat and smoke are gone."); } else if (Is_knox(&u.uz)) { /* alarm stops working once Croesus has died */ - if (new || !mvitals[PM_CROESUS].died) { + if (new || !svm.mvitals[PM_CROESUS].died) { You("have penetrated a high security area!"); + Soundeffect(se_alarm, 100); pline("An alarm sounds!"); for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { if (DEADMONSTER(mtmp)) @@ -1662,46 +1902,148 @@ boolean at_stairs, falling, portal; mtmp->msleeping = 0; } } + } else if (In_mines(&u.uz)) { + if (newdungeon) + record_achievement(ACH_MINE); + } else if (In_sokoban(&u.uz)) { + if (newdungeon) + record_achievement(ACH_SOKO); } else { - if (new && Is_rogue_level(&u.uz)) + if (new && Is_rogue_level(&u.uz)) { You("enter what seems to be an older, more primitive world."); + } else if (new && Is_bigroom(&u.uz)) { + record_achievement(ACH_BGRM); + } /* main dungeon message from your quest leader */ if (!In_quest(&u.uz0) && at_dgn_entrance("The Quest") && !(u.uevent.qcompleted || u.uevent.qexpelled - || quest_status.leader_is_dead)) { + || svq.quest_status.leader_is_dead)) { + /* [TODO: copy of same TODO below; if an achievement for + receiving quest call from leader gets added, that should + come after logging new level entry] */ if (!u.uevent.qcalled) { u.uevent.qcalled = 1; - com_pager(2); /* main "leader needs help" message */ - } else { /* reminder message */ - com_pager(Role_if(PM_ROGUE) ? 4 : 3); + /* main "leader needs help" message */ + com_pager("quest_portal"); + } else { /* reminder message */ + com_pager(Role_if(PM_ROGUE) ? "quest_portal_demand" + : "quest_portal_again"); } } } + temperature_change_msg(prev_temperature); + + /* this was originally done earlier; moved here to be logged after + any achievement related to entering a dungeon branch + [TODO: if an achievement for receiving quest call from leader + gets added, that should come after this rather than take place + where the message is delivered above] */ + if (new) { + char dloc[QBUFSZ]; + /* Astral is excluded as a major event here because entry to it + is already one such due to that being an achievement; + for the quest, listing the start, locate, and goal levels would + seem reasonable but all quest levels are included for simplicity-- + level 2 (or 3 if hero level teleports after obtaining permission + to enter) is useful to show since it indicates that hero has + actually entered the quest rather than just received permission + to do so, and listing the goal level could be used to figure out + whether level 5 is the end or there's another level (ESP reveals + the same thing, but is part of normal game play as opposed to + #chronicle leaking information that hero hasn't discovered yet) */ + boolean major = ((In_endgame(&u.uz) && !Is_astralevel(&u.uz)) + || In_quest(&u.uz)); + + (void) describe_level(dloc, 2); + livelog_printf(major ? LL_ACHIEVE : LL_DEBUG, "entered %s", dloc); + + if (Role_if(PM_TOURIST)) { + more_experienced(level_difficulty(), 0); + newexplevel(); + } + } + assign_level(&u.uz0, &u.uz); /* reset u.uz0 */ #ifdef INSURANCE save_currentstate(); #endif + notice_mon_on(); + notice_all_mons(TRUE); - if ((annotation = get_annotation(&u.uz)) != 0) - You("remember this level as %s.", annotation); + print_level_annotation(); + /* give room entrance message, if any */ + check_special_room(FALSE); + /* deliver objects traveling with player */ + obj_delivery(TRUE); /* assume this will always return TRUE when changing level */ (void) in_out_region(u.ux, u.uy); + /* shop repair is normally done when shopkeepers move, but we may + need to catch up for lost time here; do this before maybe dying + so bones map will include it */ + if (!new) + fix_shop_damage(); + + /* fall damage? */ + if (do_fall_dmg) { + int dmg = d(max(dist, 1), 6); + + dmg = Maybe_Half_Phys(dmg); + losehp(dmg, "falling down a mine shaft", KILLED_BY); + } + (void) pickup(1); + return; } -STATIC_OVL void -final_level() +/* give a message when entering a Gehennom level other than the Valley; + also given if restoring a game in that situation */ +void +hellish_smoke_mesg(void) { - struct monst *mtmp; + if (svl.level.flags.temperature) + pline("It is %s here.", + svl.level.flags.temperature > 0 ? "hot" : "cold"); - /* reset monster hostility relative to player */ - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) - continue; - reset_hostility(mtmp); + if (In_hell(&u.uz) && svl.level.flags.temperature > 0) + You("%s smoke...", + olfaction(gy.youmonst.data) ? "smell" : "sense"); +} + +/* give a message when the level temperature is different from previous */ +staticfn void +temperature_change_msg(schar prev_temperature) +{ + if (prev_temperature != svl.level.flags.temperature) { + if (svl.level.flags.temperature) + hellish_smoke_mesg(); + else if (prev_temperature > 0) + pline_The("heat %s gone.", + In_hell(&u.uz0) + ? "and smoke are" : "is"); + else if (prev_temperature < 0) + You("are out of the cold."); } +} + +/* usually called from goto_level(); might be called from Sting_effects() */ +void +maybe_lvltport_feedback(void) +{ + if (gd.dfr_post_msg + && !strncmpi(gd.dfr_post_msg, "You materialize", 15)) { + /* "You materialize on a different level." */ + pline("%s", gd.dfr_post_msg); + free((genericptr_t) gd.dfr_post_msg), gd.dfr_post_msg = 0; + } +} + +staticfn void +final_level(void) +{ + /* reset monster hostility relative to player */ + iter_mons(reset_hostility); /* create some player-monsters */ create_mplayers(rn1(4, 3), TRUE); @@ -1710,51 +2052,40 @@ final_level() gain_guardian_angel(); } -static char *dfr_pre_msg = 0, /* pline() before level change */ - *dfr_post_msg = 0; /* pline() after level change */ - /* change levels at the end of this turn, after monsters finish moving */ void -schedule_goto(tolev, at_stairs, falling, portal_flag, pre_msg, post_msg) -d_level *tolev; -boolean at_stairs, falling; -int portal_flag; -const char *pre_msg, *post_msg; +schedule_goto( + d_level *tolev, + int utotype_flags, + const char *pre_msg, const char *post_msg) { - int typmask = 0100; /* non-zero triggers `deferred_goto' */ - - /* destination flags (`goto_level' args) */ - if (at_stairs) - typmask |= 1; - if (falling) - typmask |= 2; - if (portal_flag) - typmask |= 4; - if (portal_flag < 0) - typmask |= 0200; /* flag for portal removal */ - u.utotype = typmask; + /* UTOTYPE_DEFERRED is used, so UTOTYPE_NONE can trigger deferred_goto() */ + u.utotype = utotype_flags | UTOTYPE_DEFERRED; /* destination level */ assign_level(&u.utolev, tolev); if (pre_msg) - dfr_pre_msg = dupstr(pre_msg); + gd.dfr_pre_msg = dupstr(pre_msg); if (post_msg) - dfr_post_msg = dupstr(post_msg); + gd.dfr_post_msg = dupstr(post_msg); } /* handle something like portal ejection */ void -deferred_goto() +deferred_goto(void) { if (!on_level(&u.uz, &u.utolev)) { - d_level dest; + d_level dest, oldlev; int typmask = u.utotype; /* save it; goto_level zeroes u.utotype */ assign_level(&dest, &u.utolev); - if (dfr_pre_msg) - pline1(dfr_pre_msg); - goto_level(&dest, !!(typmask & 1), !!(typmask & 2), !!(typmask & 4)); - if (typmask & 0200) { /* remove portal */ + assign_level(&oldlev, &u.uz); + if (gd.dfr_pre_msg) + pline1(gd.dfr_pre_msg); + goto_level(&dest, !!(typmask & UTOTYPE_ATSTAIRS), + !!(typmask & UTOTYPE_FALLING), + !!(typmask & UTOTYPE_PORTAL)); + if (typmask & UTOTYPE_RMPORTAL) { /* remove portal */ struct trap *t = t_at(u.ux, u.uy); if (t) { @@ -1762,14 +2093,14 @@ deferred_goto() newsym(u.ux, u.uy); } } - if (dfr_post_msg) - pline1(dfr_post_msg); - } - u.utotype = 0; /* our caller keys off of this */ - if (dfr_pre_msg) - free((genericptr_t) dfr_pre_msg), dfr_pre_msg = 0; - if (dfr_post_msg) - free((genericptr_t) dfr_post_msg), dfr_post_msg = 0; + if (gd.dfr_post_msg && !on_level(&u.uz, &oldlev)) + pline1(gd.dfr_post_msg); + } + u.utotype = UTOTYPE_NONE; /* our caller keys off of this */ + if (gd.dfr_pre_msg) + free((genericptr_t) gd.dfr_pre_msg), gd.dfr_pre_msg = 0; + if (gd.dfr_post_msg) + free((genericptr_t) gd.dfr_post_msg), gd.dfr_post_msg = 0; } /* @@ -1777,31 +2108,42 @@ deferred_goto() * corpse is gone. */ boolean -revive_corpse(corpse) -struct obj *corpse; +revive_corpse(struct obj *corpse) { struct monst *mtmp, *mcarry; boolean is_uwep, chewed; - xchar where; + xint16 where; char cname[BUFSZ]; struct obj *container = (struct obj *) 0; int container_where = 0; + int montype; + boolean is_zomb; + coordxy corpsex, corpsey; where = corpse->where; + montype = corpse->corpsenm; + /* treat buried auto-reviver (troll, Rider?) like a zombie + so that it can dig itself out of the ground if it revives */ + is_zomb = (mons[montype].mlet == S_ZOMBIE + || (where == OBJ_BURIED && is_reviver(&mons[montype]))); is_uwep = (corpse == uwep); chewed = (corpse->oeaten != 0); Strcpy(cname, corpse_xname(corpse, chewed ? "bite-covered" : (const char *) 0, CXN_SINGULAR)); mcarry = (where == OBJ_MINVENT) ? corpse->ocarry : 0; + /* mcarry is NULL for (where == OBJ_BURIED and OBJ_CONTAINED) now */ + + (void) get_obj_location(corpse, &corpsex, &corpsey, + CONTAINED_TOO | BURIED_TOO); if (where == OBJ_CONTAINED) { struct monst *mtmp2; container = corpse->ocontainer; - mtmp2 = get_container_location(container, &container_where, (int *) 0); - /* container_where is the outermost container's location even if - * nested */ + mtmp2 = get_container_location(container, &container_where, + (int *) 0); + /* container_where is outermost container's location even if nested */ if (container_where == OBJ_MINVENT && mtmp2) mcarry = mtmp2; } @@ -1817,18 +2159,34 @@ struct obj *corpse; break; case OBJ_FLOOR: - if (cansee(mtmp->mx, mtmp->my)) - pline("%s rises from the dead!", - chewed ? Adjmonnam(mtmp, "bite-covered") - : Monnam(mtmp)); + if (cansee(corpsex, corpsey) || canseemon(mtmp)) { + const char *effect = ""; + + if (mtmp->data == &mons[PM_DEATH]) + effect = " in a whirl of spectral skulls"; + else if (mtmp->data == &mons[PM_PESTILENCE]) + effect = " in a churning pillar of flies"; + else if (mtmp->data == &mons[PM_FAMINE]) + effect = " in a ring of withered crops"; + + if (canseemon(mtmp)) { + pline("%s rises from the dead%s!", + chewed ? Adjmonnam(mtmp, "bite-covered") + : Monnam(mtmp), + effect); + } else { + pline("%s disappears%s!", The(cname), effect); + } + } break; case OBJ_MINVENT: /* probably a nymph's */ if (cansee(mtmp->mx, mtmp->my)) { - if (canseemon(mcarry)) - pline("Startled, %s drops %s as it revives!", - mon_nam(mcarry), an(cname)); - else + if (mcarry && canseemon(mcarry)) + pline("Startled, %s drops %s as it %s!", + mon_nam(mcarry), an(cname), + canspotmon(mtmp) ? "revives" : "disappears"); + else if (canspotmon(mtmp)) pline("%s suddenly appears!", chewed ? Adjmonnam(mtmp, "bite-covered") : Monnam(mtmp)); @@ -1836,23 +2194,47 @@ struct obj *corpse; break; case OBJ_CONTAINED: { char sackname[BUFSZ]; - - if (container_where == OBJ_MINVENT && cansee(mtmp->mx, mtmp->my) - && mcarry && canseemon(mcarry) && container) { - pline("%s writhes out of %s!", Amonnam(mtmp), - yname(container)); - } else if (container_where == OBJ_INVENT && container) { + /* Could use x_monnam(..., AUGMENT_IT) but that'd say "someone" + for humanoid monsters, which seems like a distinction the hero + doesn't have knowledge to make here. */ + const char *mnam = canspotmon(mtmp) ? Amonnam(mtmp) : Something; + + if (!container) { + impossible("reviving corpse from non-existent container"); + } else if (mcarry && canseemon(mcarry)) { + pline("%s writhes out of %s!", mnam, yname(container)); + } else if (container_where == OBJ_INVENT) { Strcpy(sackname, an(xname(container))); - pline("%s %s out of %s in your pack!", - Blind ? Something : Amonnam(mtmp), + pline("%s %s out of %s in your pack!", mnam, locomotion(mtmp->data, "writhes"), sackname); - } else if (container_where == OBJ_FLOOR && container - && cansee(mtmp->mx, mtmp->my)) { + } else if (container_where == OBJ_FLOOR + && cansee(corpsex, corpsey)) { Strcpy(sackname, an(xname(container))); - pline("%s escapes from %s!", Amonnam(mtmp), sackname); + pline("%s escapes from %s!", mnam, sackname); } break; } + case OBJ_BURIED: + if (is_zomb) { + maketrap(mtmp->mx, mtmp->my, PIT); + if (cansee(mtmp->mx, mtmp->my)) { + struct trap *ttmp; + + ttmp = t_at(mtmp->mx, mtmp->my); + if (ttmp) + ttmp->tseen = TRUE; + pline("%s claws itself out of the ground!", + canspotmon(mtmp) ? Amonnam(mtmp) : Something); + newsym(mtmp->mx, mtmp->my); + } else if (mdistu(mtmp) < 5*5) { + Soundeffect(se_scratching, 50); + You_hear("scratching noises."); + } + fill_pit(mtmp->mx, mtmp->my); + break; + } + FALLTHROUGH; + /*FALLTHRU*/ default: /* we should be able to handle the other cases... */ impossible("revive_corpse: lost corpse @ %d", where); @@ -1866,23 +2248,22 @@ struct obj *corpse; /* Revive the corpse via a timeout. */ /*ARGSUSED*/ void -revive_mon(arg, timeout) -anything *arg; -long timeout UNUSED; +revive_mon(anything *arg, long timeout UNUSED) { struct obj *body = arg->a_obj; struct permonst *mptr = &mons[body->corpsenm]; struct monst *mtmp; - xchar x, y; + coordxy x, y; /* corpse will revive somewhere else if there is a monster in the way; Riders get a chance to try to bump the obstacle out of their way */ - if ((mptr->mflags3 & M3_DISPLACES) != 0 && body->where == OBJ_FLOOR - && get_obj_location(body, &x, &y, 0) && (mtmp = m_at(x, y)) != 0) { + if (is_displacer(mptr) && body->where == OBJ_FLOOR + && get_obj_location(body, &x, &y, 0) && (mtmp = m_at(x, y)) != 0 && + svl.level.flags.stasis_until < svm.moves) { boolean notice_it = canseemon(mtmp); /* before rloc() */ char *monname = Monnam(mtmp); - if (rloc(mtmp, TRUE)) { + if (rloc(mtmp, RLOC_NOMSG)) { if (notice_it && !canseemon(mtmp)) pline("%s vanishes.", monname); else if (!notice_it && canseemon(mtmp)) @@ -1899,42 +2280,101 @@ long timeout UNUSED; if (is_rider(mptr) && rn2(99)) { /* Rider usually tries again */ action = REVIVE_MON; - for (when = 3L; when < 67L; when++) - if (!rn2(3)) - break; + when = rider_revival_time(body, TRUE); } else { /* rot this corpse away */ - You_feel("%sless hassled.", is_rider(mptr) ? "much " : ""); + if (!obj_has_timer(body, ROT_CORPSE)) + You_feel("%sless hassled.", is_rider(mptr) ? "much " : ""); action = ROT_CORPSE; - when = 250L - (monstermoves - body->age); + when = (long) d(5, 50) - (svm.moves - body->age); if (when < 1L) when = 1L; } - (void) start_timer(when, TIMER_OBJECT, action, arg); + if (!obj_has_timer(body, action)) + (void) start_timer(when, TIMER_OBJECT, action, arg); + } +} + +/* Timeout callback. Revive the corpse as a zombie. */ +void +zombify_mon(anything *arg, long timeout) +{ + struct obj *body = arg->a_obj; + int zmon = zombie_form(&mons[body->corpsenm]); + + if (zmon != NON_PM && !(svm.mvitals[zmon].mvflags & G_GENOD)) { + if (has_omid(body)) + free_omid(body); + if (has_omonst(body)) + free_omonst(body); + + set_corpsenm(body, zmon); + revive_mon(arg, timeout); + } else { + rot_corpse(arg, timeout); + } +} + +/* return TRUE if hero properties are dangerous to hero */ +staticfn boolean +danger_uprops(void) +{ + return (Stoned || Slimed || Strangled || Sick); +} + +boolean +cmd_safety_prevention(const char *ucverb, const char *cmddesc, + const char *act, int *flagcounter) +{ + if (flags.safe_wait && !iflags.menu_requested && !gm.multi) { + char buf[QBUFSZ]; + + buf[0] = '\0'; + if (iflags.cmdassist || !(*flagcounter)++) + Sprintf(buf, " Use '%s' prefix to force %s.", + visctrl(cmd_from_func(do_reqmenu)), cmddesc); + + if (monster_nearby()) { + Norep("%s%s", act, buf); + return TRUE; + } else if (danger_uprops()) { + Norep("%s doesn't feel like a good idea right now.", ucverb); + return TRUE; + } } + *flagcounter = 0; + return FALSE; } +/* '.' command: do nothing == rest; also the + ' ' command iff 'rest_on_space' option is On */ int -donull() +donull(void) { - return 1; /* Do nothing, but let other things happen */ + if (cmd_safety_prevention("Waiting", "a no-op (to rest)", + "Are you waiting to get hit?", + &gd.did_nothing_flag)) + return ECMD_OK; + return ECMD_TIME; /* Do nothing, but let other things happen */ } -STATIC_PTR int -wipeoff(VOID_ARGS) +staticfn int +wipeoff(void) { - if (u.ucreamed < 4) - u.ucreamed = 0; - else - u.ucreamed -= 4; - if (Blinded < 4) - Blinded = 0; - else - Blinded -= 4; - if (!Blinded) { + unsigned udelta = u.ucreamed; + long ldelta = BlindedTimeout; + + if (udelta > 4) + udelta = 4; + u.ucreamed -= udelta; /*u.ucreamed -= min(u.ucreamed,4);*/ + if (ldelta > 4L) + ldelta = 4L; + incr_itimeout(&HBlinded, -ldelta); /*HBlinded -= min(BlindedTimeout,4L);*/ + + if (!HBlinded) { pline("You've got the glop off."); u.ucreamed = 0; if (!gulp_blnd_check()) { - Blinded = 1; + set_itimeout(&HBlinded, 1L); make_blinded(0L, TRUE); } return 0; @@ -1945,8 +2385,9 @@ wipeoff(VOID_ARGS) return 1; /* still busy */ } +/* the #wipe command - wipe off your face */ int -dowipe() +dowipe(void) { if (u.ucreamed) { static NEARDATA char buf[39]; @@ -1956,43 +2397,62 @@ dowipe() /* Not totally correct; what if they change back after now * but before they're finished wiping? */ - return 1; + return ECMD_TIME; } Your("%s is already clean.", body_part(FACE)); - return 1; + return ECMD_TIME; } +/* common wounded legs feedback */ void -set_wounded_legs(side, timex) -register long side; -register int timex; +legs_in_no_shape(const char *for_what, /* jumping, kicking, riding */ + boolean by_steed) +{ + if (by_steed && u.usteed) { + pline("%s is in no shape for %s.", Monnam(u.usteed), for_what); + } else { + long wl = (EWounded_legs & BOTH_SIDES); + const char *bp = body_part(LEG); + + if (wl == BOTH_SIDES) + bp = makeplural(bp); + Your("%s%s %s in no shape for %s.", + (wl == LEFT_SIDE) ? "left " : (wl == RIGHT_SIDE) ? "right " : "", + bp, (wl == BOTH_SIDES) ? "are" : "is", for_what); + } +} + +void +set_wounded_legs(long side, int timex) { /* KMH -- STEED * If you are riding, your steed gets the wounded legs instead. * You still call this function, but don't lose hp. * Caller is also responsible for adjusting messages. */ - - if (!Wounded_legs) { + disp.botl = TRUE; + if (!Wounded_legs) ATEMP(A_DEX)--; - context.botl = 1; - } - if (!Wounded_legs || (HWounded_legs & TIMEOUT)) - HWounded_legs = timex; - EWounded_legs = side; - (void) encumber_msg(); + if (!Wounded_legs || (HWounded_legs & TIMEOUT) < (long) timex) + set_itimeout(&HWounded_legs, (long) timex); + /* the leg being wounded and its timeout might differ from one + attack to the next, but we don't track the legs separately; + 5.0: both legs will ultimately heal together; this used to use + direct assignment instead of bitwise-OR so getting wounded in + one leg mysteriously healed the other */ + EWounded_legs |= side; + encumber_msg(); } void -heal_legs(how) -int how; /* 0: ordinary, 1: dismounting steed, 2: limbs turn to stone */ +heal_legs( + int how) /* 0: ordinary, 1: dismounting steed, 2: limbs turn to stone */ { if (Wounded_legs) { - if (ATEMP(A_DEX) < 0) { + disp.botl = TRUE; + if (ATEMP(A_DEX) < 0) ATEMP(A_DEX)++; - context.botl = 1; - } /* when mounted, wounded legs applies to the steed; during petrification countdown, "your limbs turn to stone" @@ -2021,7 +2481,7 @@ int how; /* 0: ordinary, 1: dismounting steed, 2: limbs turn to stone */ more when steed becomes healthy, then possible floor feedback, then able to carry less when back on foot]. */ if (how == 0) - (void) encumber_msg(); + encumber_msg(); } } diff --git a/src/do_name.c b/src/do_name.c index 2c3f4e6a2..75f33815f 100644 --- a/src/do_name.c +++ b/src/do_name.c @@ -1,32 +1,23 @@ -/* NetHack 3.6 do_name.c $NHDT-Date: 1674864731 2023/01/28 00:12:11 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.154 $ */ +/* NetHack 5.0 do_name.c $NHDT-Date: 1737013431 2025/01/15 23:43:51 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.326 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Pasi Kallinen, 2018. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -STATIC_DCL char *NDECL(nextmbuf); -STATIC_DCL void FDECL(getpos_help, (BOOLEAN_P, const char *)); -STATIC_DCL int FDECL(CFDECLSPEC cmp_coord_distu, (const void *, const void *)); -STATIC_DCL boolean FDECL(gather_locs_interesting, (int, int, int)); -STATIC_DCL char *FDECL(name_from_player, (char *, const char *, const char *)); -STATIC_DCL void FDECL(gather_locs, (coord **, int *, int)); -STATIC_DCL int FDECL(gloc_filter_floodfill_matcharea, (int, int)); -STATIC_DCL void FDECL(auto_describe, (int, int)); -STATIC_DCL void NDECL(do_mname); -STATIC_DCL boolean FDECL(alreadynamed, (struct monst *, char *, char *)); -STATIC_DCL void FDECL(do_oname, (struct obj *)); -STATIC_PTR char *FDECL(docall_xname, (struct obj *)); -STATIC_DCL void NDECL(namefloorobj); -STATIC_DCL char *FDECL(bogusmon, (char *,char *)); - -extern const char what_is_an_unknown_object[]; /* from pager.c */ +staticfn char *nextmbuf(void); +staticfn char *name_from_player(char *, const char *, const char *); +staticfn void do_mgivenname(void); +staticfn boolean alreadynamed(struct monst *, char *, char *) NONNULLPTRS; +staticfn void do_oname(struct obj *) NONNULLARG1; +staticfn char *docall_xname(struct obj *) NONNULLARG1; +staticfn void namefloorobj(void); #define NUMMBUF 5 /* manage a pool of BUFSZ buffers, so callers don't have to */ -STATIC_OVL char * -nextmbuf() +staticfn char * +nextmbuf(void) { static char NEARDATA bufs[NUMMBUF][BUFSZ]; static int bufidx = 0; @@ -35,993 +26,41 @@ nextmbuf() return bufs[bufidx]; } -/* function for getpos() to highlight desired map locations. - * parameter value 0 = initialize, 1 = highlight, 2 = done - */ -static void FDECL((*getpos_hilitefunc), (int)) = (void FDECL((*), (int))) 0; -static boolean FDECL((*getpos_getvalid), (int, int)) = - (boolean FDECL((*), (int, int))) 0; - -void -getpos_sethilite(gp_hilitef, gp_getvalidf) -void FDECL((*gp_hilitef), (int)); -boolean FDECL((*gp_getvalidf), (int, int)); -{ - getpos_hilitefunc = gp_hilitef; - getpos_getvalid = gp_getvalidf; -} - -static const char *const gloc_descr[NUM_GLOCS][4] = { - { "any monsters", "monster", "next/previous monster", "monsters" }, - { "any items", "item", "next/previous object", "objects" }, - { "any doors", "door", "next/previous door or doorway", "doors or doorways" }, - { "any unexplored areas", "unexplored area", "unexplored location", - "unexplored locations" }, - { "anything interesting", "interesting thing", "anything interesting", - "anything interesting" }, - { "any valid locations", "valid location", "valid location", - "valid locations" } -}; - -static const char *const gloc_filtertxt[NUM_GFILTER] = { - "", - " in view", - " in this area" -}; - -void -getpos_help_keyxhelp(tmpwin, k1, k2, gloc) -winid tmpwin; -const char *k1; -const char *k2; -int gloc; -{ - char sbuf[BUFSZ]; - - Sprintf(sbuf, "Use '%s'/'%s' to %s%s%s.", - k1, k2, - iflags.getloc_usemenu ? "get a menu of " - : "move the cursor to ", - gloc_descr[gloc][2 + iflags.getloc_usemenu], - gloc_filtertxt[iflags.getloc_filter]); - putstr(tmpwin, 0, sbuf); -} - -/* the response for '?' help request in getpos() */ -STATIC_OVL void -getpos_help(force, goal) -boolean force; -const char *goal; -{ - static const char *const fastmovemode[2] = { "8 units at a time", - "skipping same glyphs" }; - char sbuf[BUFSZ]; - boolean doing_what_is; - winid tmpwin = create_nhwindow(NHW_MENU); - - Sprintf(sbuf, - "Use '%c', '%c', '%c', '%c' to move the cursor to %s.", /* hjkl */ - Cmd.move_W, Cmd.move_S, Cmd.move_N, Cmd.move_E, goal); - putstr(tmpwin, 0, sbuf); - Sprintf(sbuf, - "Use 'H', 'J', 'K', 'L' to fast-move the cursor, %s.", - fastmovemode[iflags.getloc_moveskip]); - putstr(tmpwin, 0, sbuf); - putstr(tmpwin, 0, "Or enter a background symbol (ex. '<')."); - Sprintf(sbuf, "Use '%s' to move the cursor on yourself.", - visctrl(Cmd.spkeys[NHKF_GETPOS_SELF])); - putstr(tmpwin, 0, sbuf); - if (!iflags.terrainmode || (iflags.terrainmode & TER_MON) != 0) { - getpos_help_keyxhelp(tmpwin, - visctrl(Cmd.spkeys[NHKF_GETPOS_MON_NEXT]), - visctrl(Cmd.spkeys[NHKF_GETPOS_MON_PREV]), - GLOC_MONS); - } - if (!iflags.terrainmode || (iflags.terrainmode & TER_OBJ) != 0) { - getpos_help_keyxhelp(tmpwin, - visctrl(Cmd.spkeys[NHKF_GETPOS_OBJ_NEXT]), - visctrl(Cmd.spkeys[NHKF_GETPOS_OBJ_PREV]), - GLOC_OBJS); - } - if (!iflags.terrainmode || (iflags.terrainmode & TER_MAP) != 0) { - /* these are primarily useful when choosing a travel - destination for the '_' command */ - getpos_help_keyxhelp(tmpwin, - visctrl(Cmd.spkeys[NHKF_GETPOS_DOOR_NEXT]), - visctrl(Cmd.spkeys[NHKF_GETPOS_DOOR_PREV]), - GLOC_DOOR); - getpos_help_keyxhelp(tmpwin, - visctrl(Cmd.spkeys[NHKF_GETPOS_UNEX_NEXT]), - visctrl(Cmd.spkeys[NHKF_GETPOS_UNEX_PREV]), - GLOC_EXPLORE); - getpos_help_keyxhelp(tmpwin, - visctrl(Cmd.spkeys[NHKF_GETPOS_INTERESTING_NEXT]), - visctrl(Cmd.spkeys[NHKF_GETPOS_INTERESTING_PREV]), - GLOC_INTERESTING); - } - Sprintf(sbuf, "Use '%s' to change fast-move mode to %s.", - visctrl(Cmd.spkeys[NHKF_GETPOS_MOVESKIP]), - fastmovemode[!iflags.getloc_moveskip]); - putstr(tmpwin, 0, sbuf); - if (!iflags.terrainmode || (iflags.terrainmode & TER_DETECT) == 0) { - Sprintf(sbuf, "Use '%s' to toggle menu listing for possible targets.", - visctrl(Cmd.spkeys[NHKF_GETPOS_MENU])); - putstr(tmpwin, 0, sbuf); - Sprintf(sbuf, - "Use '%s' to change the mode of limiting possible targets.", - visctrl(Cmd.spkeys[NHKF_GETPOS_LIMITVIEW])); - putstr(tmpwin, 0, sbuf); - } - if (!iflags.terrainmode) { - char kbuf[BUFSZ]; - - if (getpos_getvalid) { - Sprintf(sbuf, "Use '%s' or '%s' to move to valid locations.", - visctrl(Cmd.spkeys[NHKF_GETPOS_VALID_NEXT]), - visctrl(Cmd.spkeys[NHKF_GETPOS_VALID_PREV])); - putstr(tmpwin, 0, sbuf); - } - if (getpos_hilitefunc) { - Sprintf(sbuf, "Use '%s' to display valid locations.", - visctrl(Cmd.spkeys[NHKF_GETPOS_SHOWVALID])); - putstr(tmpwin, 0, sbuf); - } - Sprintf(sbuf, "Use '%s' to toggle automatic description.", - visctrl(Cmd.spkeys[NHKF_GETPOS_AUTODESC])); - putstr(tmpwin, 0, sbuf); - if (iflags.cmdassist) { /* assisting the '/' command, I suppose... */ - Sprintf(sbuf, - (iflags.getpos_coords == GPCOORDS_NONE) - ? "(Set 'whatis_coord' option to include coordinates with '%s' text.)" - : "(Reset 'whatis_coord' option to omit coordinates from '%s' text.)", - visctrl(Cmd.spkeys[NHKF_GETPOS_AUTODESC])); - } - /* disgusting hack; the alternate selection characters work for any - getpos call, but only matter for dowhatis (and doquickwhatis) */ - doing_what_is = (goal == what_is_an_unknown_object); - if (doing_what_is) { - Sprintf(kbuf, "'%s' or '%s' or '%s' or '%s'", - visctrl(Cmd.spkeys[NHKF_GETPOS_PICK]), - visctrl(Cmd.spkeys[NHKF_GETPOS_PICK_Q]), - visctrl(Cmd.spkeys[NHKF_GETPOS_PICK_O]), - visctrl(Cmd.spkeys[NHKF_GETPOS_PICK_V])); - } else { - Sprintf(kbuf, "'%s'", visctrl(Cmd.spkeys[NHKF_GETPOS_PICK])); - } - Sprintf(sbuf, "Type a %s when you are at the right place.", kbuf); - putstr(tmpwin, 0, sbuf); - if (doing_what_is) { - Sprintf(sbuf, - " '%s' describe current spot, show 'more info', move to another spot.", - visctrl(Cmd.spkeys[NHKF_GETPOS_PICK_V])); - putstr(tmpwin, 0, sbuf); - Sprintf(sbuf, - " '%s' describe current spot,%s move to another spot;", - visctrl(Cmd.spkeys[NHKF_GETPOS_PICK]), - flags.help ? " prompt if 'more info'," : ""); - putstr(tmpwin, 0, sbuf); - Sprintf(sbuf, - " '%s' describe current spot, move to another spot;", - visctrl(Cmd.spkeys[NHKF_GETPOS_PICK_Q])); - putstr(tmpwin, 0, sbuf); - Sprintf(sbuf, - " '%s' describe current spot, stop looking at things;", - visctrl(Cmd.spkeys[NHKF_GETPOS_PICK_O])); - putstr(tmpwin, 0, sbuf); - } - } - if (!force) - putstr(tmpwin, 0, "Type Space or Escape when you're done."); - putstr(tmpwin, 0, ""); - display_nhwindow(tmpwin, TRUE); - destroy_nhwindow(tmpwin); -} - -STATIC_OVL int -cmp_coord_distu(a, b) -const void *a; -const void *b; -{ - const coord *c1 = a; - const coord *c2 = b; - int dx, dy, dist_1, dist_2; - - dx = u.ux - c1->x; - dy = u.uy - c1->y; - dist_1 = max(abs(dx), abs(dy)); - dx = u.ux - c2->x; - dy = u.uy - c2->y; - dist_2 = max(abs(dx), abs(dy)); - - if (dist_1 == dist_2) - return (c1->y != c2->y) ? (c1->y - c2->y) : (c1->x - c2->x); - - return dist_1 - dist_2; -} - -#define IS_UNEXPLORED_LOC(x,y) \ - (isok((x), (y)) \ - && glyph_is_cmap(levl[(x)][(y)].glyph) \ - && glyph_to_cmap(levl[(x)][(y)].glyph) == S_stone \ - && !levl[(x)][(y)].seenv) - -static struct opvar *gloc_filter_map = (struct opvar *) 0; - -#define GLOC_SAME_AREA(x,y) \ - (isok((x), (y)) \ - && (selection_getpoint((x),(y), gloc_filter_map))) - -static int gloc_filter_floodfill_match_glyph; - -int -gloc_filter_classify_glyph(glyph) -int glyph; -{ - int c; - - if (!glyph_is_cmap(glyph)) - return 0; - - c = glyph_to_cmap(glyph); - - if (is_cmap_room(c) || is_cmap_furniture(c)) - return 1; - else if (is_cmap_wall(c) || c == S_tree) - return 2; - else if (is_cmap_corr(c)) - return 3; - else if (is_cmap_water(c)) - return 4; - else if (is_cmap_lava(c)) - return 5; - return 0; -} - -STATIC_OVL int -gloc_filter_floodfill_matcharea(x, y) -int x, y; -{ - int glyph = back_to_glyph(x, y); - - if (!levl[x][y].seenv) - return FALSE; - - if (glyph == gloc_filter_floodfill_match_glyph) - return TRUE; - - if (gloc_filter_classify_glyph(glyph) - == gloc_filter_classify_glyph(gloc_filter_floodfill_match_glyph)) - return TRUE; - - return FALSE; -} - -void -gloc_filter_floodfill(x, y) -int x, y; -{ - gloc_filter_floodfill_match_glyph = back_to_glyph(x, y); - - set_selection_floodfillchk(gloc_filter_floodfill_matcharea); - selection_floodfill(gloc_filter_map, x, y, FALSE); -} - -void -gloc_filter_init() -{ - if (iflags.getloc_filter == GFILTER_AREA) { - if (!gloc_filter_map) { - gloc_filter_map = selection_opvar((char *) 0); - } - /* special case: if we're in a doorway, try to figure out which - direction we're moving, and use that side of the doorway */ - if (IS_DOOR(levl[u.ux][u.uy].typ)) { - if (u.dx || u.dy) { - gloc_filter_floodfill(u.ux + u.dx, u.uy + u.dy); - } else { - /* TODO: maybe add both sides of the doorway? */ - } - } else { - gloc_filter_floodfill(u.ux, u.uy); - } - } -} - -void -gloc_filter_done() -{ - if (gloc_filter_map) { - opvar_free_x(gloc_filter_map); - gloc_filter_map = (struct opvar *) 0; - } -} - -STATIC_OVL boolean -gather_locs_interesting(x, y, gloc) -int x, y, gloc; -{ - /* TODO: if glyph is a pile glyph, convert to ordinary one - * in order to keep tail/boulder/rock check simple. - */ - int glyph = glyph_at(x, y); - - if (iflags.getloc_filter == GFILTER_VIEW && !cansee(x, y)) - return FALSE; - if (iflags.getloc_filter == GFILTER_AREA && !GLOC_SAME_AREA(x, y) - && !GLOC_SAME_AREA(x - 1, y) && !GLOC_SAME_AREA(x, y - 1) - && !GLOC_SAME_AREA(x + 1, y) && !GLOC_SAME_AREA(x, y + 1)) - return FALSE; - - switch (gloc) { - default: - case GLOC_MONS: - /* unlike '/M', this skips monsters revealed by - warning glyphs and remembered unseen ones */ - return (glyph_is_monster(glyph) - && glyph != monnum_to_glyph(PM_LONG_WORM_TAIL)); - case GLOC_OBJS: - return (glyph_is_object(glyph) - && glyph != objnum_to_glyph(BOULDER) - && glyph != objnum_to_glyph(ROCK)); - case GLOC_DOOR: - return (glyph_is_cmap(glyph) - && (is_cmap_door(glyph_to_cmap(glyph)) - || is_cmap_drawbridge(glyph_to_cmap(glyph)) - || glyph_to_cmap(glyph) == S_ndoor)); - case GLOC_EXPLORE: - return (glyph_is_cmap(glyph) - && (is_cmap_door(glyph_to_cmap(glyph)) - || is_cmap_drawbridge(glyph_to_cmap(glyph)) - || glyph_to_cmap(glyph) == S_ndoor - || glyph_to_cmap(glyph) == S_room - || glyph_to_cmap(glyph) == S_darkroom - || glyph_to_cmap(glyph) == S_corr - || glyph_to_cmap(glyph) == S_litcorr) - && (IS_UNEXPLORED_LOC(x + 1, y) - || IS_UNEXPLORED_LOC(x - 1, y) - || IS_UNEXPLORED_LOC(x, y + 1) - || IS_UNEXPLORED_LOC(x, y - 1))); - case GLOC_VALID: - if (getpos_getvalid) - return (*getpos_getvalid)(x,y); - /*FALLTHRU*/ - case GLOC_INTERESTING: - return gather_locs_interesting(x,y, GLOC_DOOR) - || !(glyph_is_cmap(glyph) - && (is_cmap_wall(glyph_to_cmap(glyph)) - || glyph_to_cmap(glyph) == S_tree - || glyph_to_cmap(glyph) == S_bars - || glyph_to_cmap(glyph) == S_ice - || glyph_to_cmap(glyph) == S_air - || glyph_to_cmap(glyph) == S_cloud - || glyph_to_cmap(glyph) == S_lava - || glyph_to_cmap(glyph) == S_water - || glyph_to_cmap(glyph) == S_pool - || glyph_to_cmap(glyph) == S_ndoor - || glyph_to_cmap(glyph) == S_room - || glyph_to_cmap(glyph) == S_darkroom - || glyph_to_cmap(glyph) == S_corr - || glyph_to_cmap(glyph) == S_litcorr)); - } - /*NOTREACHED*/ - return FALSE; -} - -/* gather locations for monsters or objects shown on the map */ -STATIC_OVL void -gather_locs(arr_p, cnt_p, gloc) -coord **arr_p; -int *cnt_p; -int gloc; -{ - int x, y, pass, idx; - - /* - * We always include the hero's location even if there is no monster - * (invisible hero without see invisible) or object (usual case) - * displayed there. That way, the count will always be at least 1, - * and player has a visual indicator (cursor returns to hero's spot) - * highlighting when successive 'm's or 'o's have cycled all the way - * through all monsters or objects. - * - * Hero's spot will always sort to array[0] because it will always - * be the shortest distance (namely, 0 units) away from . - */ - - gloc_filter_init(); - - *cnt_p = idx = 0; - for (pass = 0; pass < 2; pass++) { - for (x = 1; x < COLNO; x++) - for (y = 0; y < ROWNO; y++) { - if ((x == u.ux && y == u.uy) - || gather_locs_interesting(x, y, gloc)) { - if (!pass) { - ++*cnt_p; - } else { - (*arr_p)[idx].x = x; - (*arr_p)[idx].y = y; - ++idx; - } - } - } - - if (!pass) /* end of first pass */ - *arr_p = (coord *) alloc(*cnt_p * sizeof (coord)); - else /* end of second pass */ - qsort(*arr_p, *cnt_p, sizeof (coord), cmp_coord_distu); - } /* pass */ - - gloc_filter_done(); -} - -char * -dxdy_to_dist_descr(dx, dy, fulldir) -int dx, dy; -boolean fulldir; -{ - static char buf[30]; - int dst; - - if (!dx && !dy) { - Sprintf(buf, "here"); - } else if ((dst = xytod(dx, dy)) != -1) { - /* explicit direction; 'one step' is implicit */ - Sprintf(buf, "%s", directionname(dst)); - } else { - static const char *dirnames[4][2] = { - { "n", "north" }, - { "s", "south" }, - { "w", "west" }, - { "e", "east" } }; - buf[0] = '\0'; - /* 9999: protect buf[] against overflow caused by invalid values */ - if (dy) { - if (abs(dy) > 9999) - dy = sgn(dy) * 9999; - Sprintf(eos(buf), "%d%s%s", abs(dy), dirnames[(dy > 0)][fulldir], - dx ? "," : ""); - } - if (dx) { - if (abs(dx) > 9999) - dx = sgn(dx) * 9999; - Sprintf(eos(buf), "%d%s", abs(dx), - dirnames[2 + (dx > 0)][fulldir]); - } - } - return buf; -} - -/* coordinate formatting for 'whatis_coord' option */ -char * -coord_desc(x, y, outbuf, cmode) -int x, y; -char *outbuf, cmode; -{ - static char screen_fmt[16]; /* [12] suffices: "[%02d,%02d]" */ - int dx, dy; - - outbuf[0] = '\0'; - switch (cmode) { - default: - break; - case GPCOORDS_COMFULL: - case GPCOORDS_COMPASS: - /* "east", "3s", "2n,4w" */ - dx = x - u.ux; - dy = y - u.uy; - Sprintf(outbuf, "(%s)", - dxdy_to_dist_descr(dx, dy, cmode == GPCOORDS_COMFULL)); - break; - case GPCOORDS_MAP: /* x,y */ - /* upper left corner of map is <1,0>; - with default COLNO,ROWNO lower right corner is <79,20> */ - Sprintf(outbuf, "<%d,%d>", x, y); - break; - case GPCOORDS_SCREEN: /* y+2,x */ - /* for normal map sizes, force a fixed-width formatting so that - /m, /M, /o, and /O output lines up cleanly; map sizes bigger - than Nx999 or 999xM will still work, but not line up like normal - when displayed in a column setting */ - if (!*screen_fmt) - Sprintf(screen_fmt, "[%%%sd,%%%sd]", - (ROWNO - 1 + 2 < 100) ? "02" : "03", - (COLNO - 1 < 100) ? "02" : "03"); - /* map line 0 is screen row 2; - map column 0 isn't used, map column 1 is screen column 1 */ - Sprintf(outbuf, screen_fmt, y + 2, x); - break; - } - return outbuf; -} - -STATIC_OVL void -auto_describe(cx, cy) -int cx, cy; -{ - coord cc; - int sym = 0; - char tmpbuf[BUFSZ]; - const char *firstmatch = "unknown"; - - cc.x = cx; - cc.y = cy; - if (do_screen_description(cc, TRUE, sym, tmpbuf, &firstmatch, - (struct permonst **) 0)) { - (void) coord_desc(cx, cy, tmpbuf, iflags.getpos_coords); - custompline(SUPPRESS_HISTORY, - "%s%s%s%s%s", firstmatch, *tmpbuf ? " " : "", tmpbuf, - (iflags.autodescribe - && getpos_getvalid && !(*getpos_getvalid)(cx, cy)) - ? " (illegal)" : "", - (iflags.getloc_travelmode && !is_valid_travelpt(cx, cy)) - ? " (no travel path)" : ""); - curs(WIN_MAP, cx, cy); - flush_screen(0); - } -} - -boolean -getpos_menu(ccp, gloc) -coord *ccp; -int gloc; -{ - coord *garr = DUMMY; - int gcount = 0; - winid tmpwin; - anything any; - int i, pick_cnt; - menu_item *picks = (menu_item *) 0; - char tmpbuf[BUFSZ]; - - gather_locs(&garr, &gcount, gloc); - - if (gcount < 2) { /* gcount always includes the hero */ - free((genericptr_t) garr); - You("cannot %s %s.", - iflags.getloc_filter == GFILTER_VIEW ? "see" : "detect", - gloc_descr[gloc][0]); - return FALSE; - } - - tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - any = zeroany; - - /* gather_locs returns array[0] == you. skip it. */ - for (i = 1; i < gcount; i++) { - char fullbuf[BUFSZ]; - coord tmpcc; - const char *firstmatch = "unknown"; - int sym = 0; - - any.a_int = i + 1; - tmpcc.x = garr[i].x; - tmpcc.y = garr[i].y; - if (do_screen_description(tmpcc, TRUE, sym, tmpbuf, - &firstmatch, (struct permonst **)0)) { - (void) coord_desc(garr[i].x, garr[i].y, tmpbuf, - iflags.getpos_coords); - Sprintf(fullbuf, "%s%s%s", firstmatch, - (*tmpbuf ? " " : ""), tmpbuf); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, fullbuf, - MENU_UNSELECTED); - } - } - - Sprintf(tmpbuf, "Pick %s%s%s", - an(gloc_descr[gloc][1]), - gloc_filtertxt[iflags.getloc_filter], - iflags.getloc_travelmode ? " for travel destination" : ""); - end_menu(tmpwin, tmpbuf); - pick_cnt = select_menu(tmpwin, PICK_ONE, &picks); - destroy_nhwindow(tmpwin); - if (pick_cnt > 0) { - ccp->x = garr[picks->item.a_int - 1].x; - ccp->y = garr[picks->item.a_int - 1].y; - free((genericptr_t) picks); - } - free((genericptr_t) garr); - return (pick_cnt > 0); -} - -int -getpos(ccp, force, goal) -coord *ccp; -boolean force; -const char *goal; -{ - const char *cp; - static struct { - int nhkf, ret; - } const pick_chars_def[] = { - { NHKF_GETPOS_PICK, LOOK_TRADITIONAL }, - { NHKF_GETPOS_PICK_Q, LOOK_QUICK }, - { NHKF_GETPOS_PICK_O, LOOK_ONCE }, - { NHKF_GETPOS_PICK_V, LOOK_VERBOSE } - }; - static const int mMoOdDxX_def[] = { - NHKF_GETPOS_MON_NEXT, - NHKF_GETPOS_MON_PREV, - NHKF_GETPOS_OBJ_NEXT, - NHKF_GETPOS_OBJ_PREV, - NHKF_GETPOS_DOOR_NEXT, - NHKF_GETPOS_DOOR_PREV, - NHKF_GETPOS_UNEX_NEXT, - NHKF_GETPOS_UNEX_PREV, - NHKF_GETPOS_INTERESTING_NEXT, - NHKF_GETPOS_INTERESTING_PREV, - NHKF_GETPOS_VALID_NEXT, - NHKF_GETPOS_VALID_PREV - }; - char pick_chars[6]; - char mMoOdDxX[13]; - int result = 0; - int cx, cy, i, c; - int sidx, tx, ty; - boolean msg_given = TRUE; /* clear message window by default */ - boolean show_goal_msg = FALSE; - boolean hilite_state = FALSE; - coord *garr[NUM_GLOCS] = DUMMY; - int gcount[NUM_GLOCS] = DUMMY; - int gidx[NUM_GLOCS] = DUMMY; - - for (i = 0; i < SIZE(pick_chars_def); i++) - pick_chars[i] = Cmd.spkeys[pick_chars_def[i].nhkf]; - pick_chars[SIZE(pick_chars_def)] = '\0'; - - for (i = 0; i < SIZE(mMoOdDxX_def); i++) - mMoOdDxX[i] = Cmd.spkeys[mMoOdDxX_def[i]]; - mMoOdDxX[SIZE(mMoOdDxX_def)] = '\0'; - - if (!goal) - goal = "desired location"; - if (flags.verbose) { - pline("(For instructions type a '%s')", - visctrl(Cmd.spkeys[NHKF_GETPOS_HELP])); - msg_given = TRUE; - } - cx = ccp->x; - cy = ccp->y; -#ifdef CLIPPING - cliparound(cx, cy); -#endif - curs(WIN_MAP, cx, cy); - flush_screen(0); -#ifdef MAC - lock_mouse_cursor(TRUE); -#endif - for (;;) { - if (show_goal_msg) { - pline("Move cursor to %s:", goal); - curs(WIN_MAP, cx, cy); - flush_screen(0); - show_goal_msg = FALSE; - } else if (iflags.autodescribe && !msg_given && !hilite_state) { - auto_describe(cx, cy); - } - - c = nh_poskey(&tx, &ty, &sidx); - - if (hilite_state) { - (*getpos_hilitefunc)(2); - hilite_state = FALSE; - curs(WIN_MAP, cx, cy); - flush_screen(0); - } - - if (iflags.autodescribe) - msg_given = FALSE; - - if (c == Cmd.spkeys[NHKF_ESC]) { - cx = cy = -10; - msg_given = TRUE; /* force clear */ - result = -1; - break; - } - if (c == 0) { - if (!isok(tx, ty)) - continue; - /* a mouse click event, just assign and return */ - cx = tx; - cy = ty; - break; - } - if ((cp = index(pick_chars, c)) != 0) { - /* '.' => 0, ',' => 1, ';' => 2, ':' => 3 */ - result = pick_chars_def[(int) (cp - pick_chars)].ret; - break; - } - for (i = 0; i < 8; i++) { - int dx, dy; - - if (Cmd.dirchars[i] == c) { - /* a normal movement letter or digit */ - dx = xdir[i]; - dy = ydir[i]; - } else if (Cmd.alphadirchars[i] == lowc((char) c) - || (Cmd.num_pad && Cmd.dirchars[i] == (c & 0177))) { - /* a shifted movement letter or Meta-digit */ - if (iflags.getloc_moveskip) { - /* skip same glyphs */ - int glyph = glyph_at(cx, cy); - - dx = xdir[i]; - dy = ydir[i]; - while (isok(cx + dx, cy + dy) - && glyph == glyph_at(cx + dx, cy + dy) - && isok(cx + dx + xdir[i], cy + dy + ydir[i]) - && glyph == glyph_at(cx + dx + xdir[i], - cy + dy + ydir[i])) { - dx += xdir[i]; - dy += ydir[i]; - } - } else { - dx = 8 * xdir[i]; - dy = 8 * ydir[i]; - } - } else - continue; - - /* truncate at map edge; diagonal moves complicate this... */ - if (cx + dx < 1) { - dy -= sgn(dy) * (1 - (cx + dx)); - dx = 1 - cx; /* so that (cx+dx == 1) */ - } else if (cx + dx > COLNO - 1) { - dy += sgn(dy) * ((COLNO - 1) - (cx + dx)); - dx = (COLNO - 1) - cx; - } - if (cy + dy < 0) { - dx -= sgn(dx) * (0 - (cy + dy)); - dy = 0 - cy; /* so that (cy+dy == 0) */ - } else if (cy + dy > ROWNO - 1) { - dx += sgn(dx) * ((ROWNO - 1) - (cy + dy)); - dy = (ROWNO - 1) - cy; - } - cx += dx; - cy += dy; - goto nxtc; - } - - if (c == Cmd.spkeys[NHKF_GETPOS_HELP] || redraw_cmd(c)) { - if (c == Cmd.spkeys[NHKF_GETPOS_HELP]) - getpos_help(force, goal); - else /* ^R */ - docrt(); /* redraw */ - /* update message window to reflect that we're still targetting */ - show_goal_msg = TRUE; - msg_given = TRUE; - } else if (c == Cmd.spkeys[NHKF_GETPOS_SHOWVALID] - && getpos_hilitefunc) { - if (!hilite_state) { - (*getpos_hilitefunc)(0); - (*getpos_hilitefunc)(1); - hilite_state = TRUE; - } - goto nxtc; - } else if (c == Cmd.spkeys[NHKF_GETPOS_AUTODESC]) { - iflags.autodescribe = !iflags.autodescribe; - pline("Automatic description %sis %s.", - flags.verbose ? "of features under cursor " : "", - iflags.autodescribe ? "on" : "off"); - if (!iflags.autodescribe) - show_goal_msg = TRUE; - msg_given = TRUE; - goto nxtc; - } else if (c == Cmd.spkeys[NHKF_GETPOS_LIMITVIEW]) { - static const char *const view_filters[NUM_GFILTER] = { - "Not limiting targets", - "Limiting targets to those in sight", - "Limiting targets to those in same area" - }; - - iflags.getloc_filter = (iflags.getloc_filter + 1) % NUM_GFILTER; - for (i = 0; i < NUM_GLOCS; i++) { - if (garr[i]) { - free((genericptr_t) garr[i]); - garr[i] = NULL; - } - gidx[i] = gcount[i] = 0; - } - pline("%s.", view_filters[iflags.getloc_filter]); - msg_given = TRUE; - goto nxtc; - } else if (c == Cmd.spkeys[NHKF_GETPOS_MENU]) { - iflags.getloc_usemenu = !iflags.getloc_usemenu; - pline("%s a menu to show possible targets%s.", - iflags.getloc_usemenu ? "Using" : "Not using", - iflags.getloc_usemenu - ? " for 'm|M', 'o|O', 'd|D', and 'x|X'" : ""); - msg_given = TRUE; - goto nxtc; - } else if (c == Cmd.spkeys[NHKF_GETPOS_SELF]) { - /* reset 'm&M', 'o&O', &c; otherwise, there's no way for player - to achieve that except by manually cycling through all spots */ - for (i = 0; i < NUM_GLOCS; i++) - gidx[i] = 0; - cx = u.ux; - cy = u.uy; - goto nxtc; - } else if (c == Cmd.spkeys[NHKF_GETPOS_MOVESKIP]) { - iflags.getloc_moveskip = !iflags.getloc_moveskip; - pline("%skipping over similar terrain when fastmoving the cursor.", - iflags.getloc_moveskip ? "S" : "Not s"); - } else if ((cp = index(mMoOdDxX, c)) != 0) { /* 'm|M', 'o|O', &c */ - /* nearest or farthest monster or object or door or unexplored */ - int gtmp = (int) (cp - mMoOdDxX), /* 0..7 */ - gloc = gtmp >> 1; /* 0..3 */ - - if (iflags.getloc_usemenu) { - coord tmpcrd; - - if (getpos_menu(&tmpcrd, gloc)) { - cx = tmpcrd.x; - cy = tmpcrd.y; - } - goto nxtc; - } - - if (!garr[gloc]) { - gather_locs(&garr[gloc], &gcount[gloc], gloc); - gidx[gloc] = 0; /* garr[][0] is hero's spot */ - } - if (!(gtmp & 1)) { /* c=='m' || c=='o' || c=='d' || c=='x') */ - gidx[gloc] = (gidx[gloc] + 1) % gcount[gloc]; - } else { /* c=='M' || c=='O' || c=='D' || c=='X') */ - if (--gidx[gloc] < 0) - gidx[gloc] = gcount[gloc] - 1; - } - cx = garr[gloc][gidx[gloc]].x; - cy = garr[gloc][gidx[gloc]].y; - goto nxtc; - } else { - if (!index(quitchars, c)) { - char matching[MAXPCHARS]; - int pass, lo_x, lo_y, hi_x, hi_y, k = 0; - - (void) memset((genericptr_t) matching, 0, sizeof matching); - for (sidx = 1; sidx < MAXPCHARS; sidx++) { /* [0] left as 0 */ - if (IS_DOOR(sidx) || IS_WALL(sidx) - || sidx == SDOOR || sidx == SCORR - || glyph_to_cmap(k) == S_room - || glyph_to_cmap(k) == S_darkroom - || glyph_to_cmap(k) == S_corr - || glyph_to_cmap(k) == S_litcorr) - continue; - if (c == defsyms[sidx].sym || c == (int) showsyms[sidx]) - matching[sidx] = (char) ++k; - } - if (k) { - for (pass = 0; pass <= 1; pass++) { - /* pass 0: just past current pos to lower right; - pass 1: upper left corner to current pos */ - lo_y = (pass == 0) ? cy : 0; - hi_y = (pass == 0) ? ROWNO - 1 : cy; - for (ty = lo_y; ty <= hi_y; ty++) { - lo_x = (pass == 0 && ty == lo_y) ? cx + 1 : 1; - hi_x = (pass == 1 && ty == hi_y) ? cx : COLNO - 1; - for (tx = lo_x; tx <= hi_x; tx++) { - /* first, look at what is currently visible - (might be monster) */ - k = glyph_at(tx, ty); - if (glyph_is_cmap(k) - && matching[glyph_to_cmap(k)]) - goto foundc; - /* next, try glyph that's remembered here - (might be trap or object) */ - if (level.flags.hero_memory - /* !terrainmode: don't move to remembered - trap or object if not currently shown */ - && !iflags.terrainmode) { - k = levl[tx][ty].glyph; - if (glyph_is_cmap(k) - && matching[glyph_to_cmap(k)]) - goto foundc; - } - /* last, try actual terrain here (shouldn't - we be using lastseentyp[][] instead?) */ - if (levl[tx][ty].seenv) { - k = back_to_glyph(tx, ty); - if (glyph_is_cmap(k) - && matching[glyph_to_cmap(k)]) - goto foundc; - } - continue; - foundc: - cx = tx, cy = ty; - if (msg_given) { - clear_nhwindow(WIN_MESSAGE); - msg_given = FALSE; - } - goto nxtc; - } /* column */ - } /* row */ - } /* pass */ - pline("Can't find dungeon feature '%c'.", c); - msg_given = TRUE; - goto nxtc; - } else { - char note[QBUFSZ]; - - if (!force) - Strcpy(note, "aborted"); - else /* hjkl */ - Sprintf(note, "use '%c', '%c', '%c', '%c' or '%s'", - Cmd.move_W, Cmd.move_S, Cmd.move_N, Cmd.move_E, - visctrl(Cmd.spkeys[NHKF_GETPOS_PICK])); - pline("Unknown direction: '%s' (%s).", visctrl((char) c), - note); - msg_given = TRUE; - } /* k => matching */ - } /* !quitchars */ - if (force) - goto nxtc; - pline("Done."); - msg_given = FALSE; /* suppress clear */ - cx = -1; - cy = 0; - result = 0; /* not -1 */ - break; - } - nxtc: - ; -#ifdef CLIPPING - cliparound(cx, cy); -#endif - curs(WIN_MAP, cx, cy); - flush_screen(0); - } -#ifdef MAC - lock_mouse_cursor(FALSE); -#endif - if (msg_given) - clear_nhwindow(WIN_MESSAGE); - ccp->x = cx; - ccp->y = cy; - for (i = 0; i < NUM_GLOCS; i++) - if (garr[i]) - free((genericptr_t) garr[i]); - getpos_hilitefunc = (void FDECL((*), (int))) 0; - getpos_getvalid = (boolean FDECL((*), (int, int))) 0; - return result; -} - /* allocate space for a monster's name; removes old name if there is one */ void -new_mname(mon, lth) -struct monst *mon; -int lth; /* desired length (caller handles adding 1 for terminator) */ +new_mgivenname( + struct monst *mon, + int lth) /* desired length (caller handles adding 1 for terminator) */ { if (lth) { /* allocate mextra if necessary; otherwise get rid of old name */ if (!mon->mextra) mon->mextra = newmextra(); else - free_mname(mon); /* already has mextra, might also have name */ - MNAME(mon) = (char *) alloc((unsigned) lth); + free_mgivenname(mon); /* has mextra, might also have name */ + MGIVENNAME(mon) = (char *) alloc((unsigned) lth); } else { /* zero length: the new name is empty; get rid of the old name */ - if (has_mname(mon)) - free_mname(mon); + if (has_mgivenname(mon)) + free_mgivenname(mon); } } /* release a monster's name; retains mextra even if all fields are now null */ void -free_mname(mon) -struct monst *mon; +free_mgivenname(struct monst *mon) { - if (has_mname(mon)) { - free((genericptr_t) MNAME(mon)); - MNAME(mon) = (char *) 0; + if (has_mgivenname(mon)) { + free((genericptr_t) MGIVENNAME(mon)); + MGIVENNAME(mon) = (char *) 0; } } /* allocate space for an object's name; removes old name if there is one */ void -new_oname(obj, lth) -struct obj *obj; -int lth; /* desired length (caller handles adding 1 for terminator) */ +new_oname( + struct obj *obj, + int lth) /* desired length (caller handles adding 1 for terminator) */ { if (lth) { /* allocate oextra if necessary; otherwise get rid of old name */ @@ -1039,8 +78,7 @@ int lth; /* desired length (caller handles adding 1 for terminator) */ /* release an object's name; retains oextra even if all fields are now null */ void -free_oname(obj) -struct obj *obj; +free_oname(struct obj *obj) { if (has_oname(obj)) { free((genericptr_t) ONAME(obj)); @@ -1054,8 +92,7 @@ struct obj *obj; * if it doesn't. */ const char * -safe_oname(obj) -struct obj *obj; +safe_oname(struct obj *obj) { if (has_oname(obj)) return ONAME(obj); @@ -1064,13 +101,13 @@ struct obj *obj; /* get a name for a monster or an object from player; truncate if longer than PL_PSIZ, then return it */ -static char * -name_from_player(outbuf, prompt, defres) -char *outbuf; /* output buffer, assumed to be at least BUFSZ long; - * anything longer than PL_PSIZ will be truncated */ -const char *prompt; -const char *defres; /* only used if EDIT_GETLIN is enabled; only useful - * if windowport xxx's xxx_getlin() supports that */ +staticfn char * +name_from_player( + char *outbuf, /* output buffer, assumed to be at least BUFSZ long; + * anything longer than PL_PSIZ will be truncated */ + const char *prompt, + const char *defres) /* only used if EDIT_GETLIN is enabled; only useful + * if windowport xxx's xxx_getlin() supports that */ { outbuf[0] = '\0'; #ifdef EDIT_GETLIN @@ -1093,9 +130,7 @@ const char *defres; /* only used if EDIT_GETLIN is enabled; only useful /* historical note: this returns a monster pointer because it used to allocate a new bigger block of memory to hold the monster and its name */ struct monst * -christen_monst(mtmp, name) -struct monst *mtmp; -const char *name; +christen_monst(struct monst *mtmp, const char *name) { int lth; char buf[PL_PSIZ]; @@ -1107,33 +142,48 @@ const char *name; name = strncpy(buf, name, PL_PSIZ - 1); buf[PL_PSIZ - 1] = '\0'; } - new_mname(mtmp, lth); /* removes old name if one is present */ + new_mgivenname(mtmp, lth); /* removes old name if one is present */ if (lth) - Strcpy(MNAME(mtmp), name); + Strcpy(MGIVENNAME(mtmp), name); + /* if 'mtmp' is leashed, persistent inventory window needs updating */ + if (mtmp->mleashed) + update_inventory(); /* x - leash (attached to Fido) */ return mtmp; } /* check whether user-supplied name matches or nearly matches an unnameable - monster's name; if so, give an alternate reject message for do_mname() */ -STATIC_OVL boolean -alreadynamed(mtmp, monnambuf, usrbuf) -struct monst *mtmp; -char *monnambuf, *usrbuf; + monster's name, or is an attempt to delete the monster's name; if so, give + alternate reject message for do_mgivenname() */ +staticfn boolean +alreadynamed(struct monst *mtmp, char *monnambuf, char *usrbuf) { char pronounbuf[10], *p; - if (fuzzymatch(usrbuf, monnambuf, " -_", TRUE) - /* catch trying to name "the Oracle" as "Oracle" */ - || (!strncmpi(monnambuf, "the ", 4) - && fuzzymatch(usrbuf, monnambuf + 4, " -_", TRUE)) - /* catch trying to name "invisible Orcus" as "Orcus" */ - || ((p = strstri(monnambuf, "invisible ")) != 0 - && fuzzymatch(usrbuf, p + 10, " -_", TRUE)) - /* catch trying to name "the {priest,Angel} of Crom" as "Crom" */ - || ((p = strstri(monnambuf, " of ")) != 0 - && fuzzymatch(usrbuf, p + 4, " -_", TRUE))) { - pline("%s is already called %s.", - upstart(strcpy(pronounbuf, mhe(mtmp))), monnambuf); + if (!*usrbuf) { /* attempt to erase existing name */ + boolean name_not_title = (has_mgivenname(mtmp) + || type_is_pname(mtmp->data) + || mtmp->isshk); + pline("%s would rather keep %s existing %s.", upstart(monnambuf), + is_rider(mtmp->data) ? "its" : mhis(mtmp), + name_not_title ? "name" : "title"); + return TRUE; + } else if (fuzzymatch(usrbuf, monnambuf, " -_", TRUE) + /* catch trying to name "the Oracle" as "Oracle" */ + || (!strncmpi(monnambuf, "the ", 4) + && fuzzymatch(usrbuf, monnambuf + 4, " -_", TRUE)) + /* catch trying to name "invisible Orcus" as "Orcus" */ + || ((p = strstri(monnambuf, "invisible ")) != 0 + && fuzzymatch(usrbuf, p + 10, " -_", TRUE)) + /* catch trying to name "the priest of Crom" as "Crom" */ + || ((p = strstri(monnambuf, " of ")) != 0 + && fuzzymatch(usrbuf, p + 4, " -_", TRUE))) { + if (is_rider(mtmp->data)) { + /* avoid gendered pronoun for riders */ + pline("%s is already called that.", upstart(monnambuf)); + } else { + pline("%s is already called %s.", + upstart(strcpy(pronounbuf, mhe(mtmp))), monnambuf); + } return TRUE; } else if (mtmp->data == &mons[PM_JUIBLEX] && strstri(monnambuf, "Juiblex") @@ -1145,13 +195,14 @@ char *monnambuf, *usrbuf; } /* allow player to assign a name to some chosen monster */ -STATIC_OVL void -do_mname() +staticfn void +do_mgivenname(void) { char buf[BUFSZ], monnambuf[BUFSZ], qbuf[QBUFSZ]; coord cc; int cx, cy; struct monst *mtmp = 0; + boolean do_swallow = FALSE; if (Hallucination) { You("would never recognize it anyway."); @@ -1164,23 +215,34 @@ do_mname() return; cx = cc.x, cy = cc.y; - if (cx == u.ux && cy == u.uy) { + if (u_at(cx, cy)) { if (u.usteed && canspotmon(u.usteed)) { mtmp = u.usteed; } else { pline("This %s creature is called %s and cannot be renamed.", - beautiful(), plname); + beautiful(), svp.plname); return; } } else mtmp = m_at(cx, cy); - if (!mtmp + /* Allow you to name the monster that has swallowed you */ + if (!mtmp && u.uswallow) { + int glyph = glyph_at(cx, cy); + + if (glyph_is_swallow(glyph)) { + mtmp = u.ustuck; + do_swallow = TRUE; + } + } + + if (!do_swallow && (!mtmp || (!sensemon(mtmp) && (!(cansee(cx, cy) || see_with_infrared(mtmp)) || mtmp->mundetected || M_AP_TYPE(mtmp) == M_AP_FURNITURE || M_AP_TYPE(mtmp) == M_AP_OBJECT - || (mtmp->minvis && !See_invisible)))) { + || (mtmp->minvis && !See_invisible))))) { + pline("I see no monster there."); return; } @@ -1188,49 +250,48 @@ do_mname() Sprintf(qbuf, "What do you want to call %s?", distant_monnam(mtmp, ARTICLE_THE, monnambuf)); /* use getlin() to get a name string from the player */ - if (!name_from_player(buf, qbuf, has_mname(mtmp) ? MNAME(mtmp) : NULL)) + if (!name_from_player(buf, qbuf, + has_mgivenname(mtmp) ? MGIVENNAME(mtmp) : NULL)) return; /* Unique monsters have their own specific names or titles. * Shopkeepers, temple priests and other minions use alternate * name formatting routines which ignore any user-supplied name. * - * Don't say the name is being rejected if it happens to match - * the existing name. - * - * TODO: should have an alternate message when the attempt is to - * remove existing name without assigning a new one. + * Don't say a new name is being rejected if it happens to match + * the existing name, or if the player is trying to remove the + * monster's existing name without assigning a new one. */ if ((mtmp->data->geno & G_UNIQ) && !mtmp->ispriest) { if (!alreadynamed(mtmp, monnambuf, buf)) pline("%s doesn't like being called names!", upstart(monnambuf)); } else if (mtmp->isshk - && !(Deaf || mtmp->msleeping || !mtmp->mcanmove + && !(Deaf || helpless(mtmp) || mtmp->data->msound <= MS_ANIMAL)) { - if (!alreadynamed(mtmp, monnambuf, buf)) + if (!alreadynamed(mtmp, monnambuf, buf)) { + SetVoice(mtmp, 0, 80, 0); verbalize("I'm %s, not %s.", shkname(mtmp), buf); - } else if (mtmp->ispriest || mtmp->isminion || mtmp->isshk) { + } + } else if (mtmp->ispriest || mtmp->isminion || mtmp->isshk + || mtmp->data == &mons[PM_GHOST] || has_ebones(mtmp)) { if (!alreadynamed(mtmp, monnambuf, buf)) pline("%s will not accept the name %s.", upstart(monnambuf), buf); - } else + } else { (void) christen_monst(mtmp, buf); + } } -STATIC_VAR int via_naming = 0; - /* * This routine used to change the address of 'obj' so be unsafe if not * used with extreme care. Applying a name to an object no longer * allocates a replacement object, so that old risk is gone. */ -STATIC_OVL -void -do_oname(obj) -register struct obj *obj; +staticfn void +do_oname(struct obj *obj) { char *bufp, buf[BUFSZ], bufcpy[BUFSZ], qbuf[QBUFSZ]; const char *aname; - short objtyp; + short objtyp = STRANGE_OBJECT; /* Do this now because there's no point in even asking for a name */ if (obj->otyp == SPE_NOVEL) { @@ -1255,14 +316,21 @@ register struct obj *obj; * Orcrist, clearly being literate (no pun intended...). */ - /* relax restrictions over proper capitalization for artifacts */ - if ((aname = artifact_name(buf, &objtyp)) != 0 && objtyp == obj->otyp) - Strcpy(buf, aname); - if (obj->oartifact) { - pline_The("artifact seems to resist the attempt."); + /* this used to give "The artifact seems to resist the attempt." + but resisting is definite, no "seems to" about it */ + pline("%s resists the attempt.", + /* any artifact should always pass the has_oname() test + but be careful just in case */ + has_oname(obj) ? ONAME(obj) : "The artifact"); return; - } else if (restrict_name(obj, buf) || exist_artifact(obj->otyp, buf)) { + } + + /* relax restrictions over proper capitalization for artifacts */ + if ((aname = artifact_name(buf, &objtyp, TRUE)) != 0 + && (restrict_name(obj, aname) || exist_artifact(obj->otyp, aname))) { + /* substitute canonical spelling before slippage */ + Strcpy(buf, aname); /* this used to change one letter, substituting a value of 'a' through 'y' (due to an off by one error, 'z' would never be selected) and then force that to @@ -1271,14 +339,14 @@ register struct obj *obj; the text had been trodden upon, sometimes picking punctuation instead of an arbitrary letter; unfortunately, we have to cover the possibility of - it targetting spaces so failing to make any change + it targeting spaces so failing to make any change (we know that it must eventually target a nonspace because buf[] matches a valid artifact name) */ Strcpy(bufcpy, buf); /* for "the Foo of Bar", only scuff "Foo of Bar" part */ - bufp = !strncmpi(bufcpy, "the ", 4) ? (buf + 4) : buf; + bufp = !strncmpi(buf, "the ", 4) ? (buf + 4) : buf; do { - wipeout_text(bufp, rn2_on_display_rng(2), (unsigned) 0); + wipeout_text(bufp, rnd_on_display_rng(2), (unsigned) 0); } while (!strcmp(buf, bufcpy)); pline("While engraving, your %s slips.", body_part(HAND)); display_nhwindow(WIN_MESSAGE, FALSE); @@ -1286,19 +354,30 @@ register struct obj *obj; /* violate illiteracy conduct since hero attempted to write a valid artifact name */ u.uconduct.literate++; + } else if (obj->otyp == objtyp) { + /* artifact_name() always returns non-Null when it sets objtyp */ + assert(aname != 0); + + /* artifact_name() found a match and restrict_name() didn't reject + it; since 'obj' is the right type, naming will change it into an + artifact so use canonical capitalization (Sting or Orcrist) */ + Strcpy(buf, aname); } - ++via_naming; /* This ought to be an argument rather than a static... */ - obj = oname(obj, buf); - --via_naming; /* ...but oname() is used in a lot of places, so defer. */ + + obj = oname(obj, buf, ONAME_VIA_NAMING | ONAME_KNOW_ARTI); + nhUse(obj); } struct obj * -oname(obj, name) -struct obj *obj; -const char *name; +oname( + struct obj *obj, /* item to assign name to */ + const char *name, /* name to assign */ + unsigned oflgs) /* flags, mostly for artifact creation */ { int lth; char buf[PL_PSIZ]; + boolean via_naming = (oflgs & ONAME_VIA_NAMING) != 0, + skip_inv_update = (oflgs & ONAME_SKIP_INVUPD) != 0; lth = *name ? (int) (strlen(name) + 1) : 0; if (lth > PL_PSIZ) { @@ -1307,9 +386,9 @@ const char *name; buf[PL_PSIZ - 1] = '\0'; } /* If named artifact exists in the game, do not create another. - * Also trying to create an artifact shouldn't de-artifact - * it (e.g. Excalibur from prayer). In this case the object - * will retain its current name. */ + Also trying to create an artifact shouldn't de-artifact + it (e.g. Excalibur from prayer). In this case the object + will retain its current name. */ if (obj->oartifact || (lth && exist_artifact(obj->otyp, name))) return obj; @@ -1318,7 +397,7 @@ const char *name; Strcpy(ONAME(obj), name); if (lth) - artifact_exists(obj, name, TRUE); + artifact_exists(obj, name, TRUE, oflgs); if (obj->oartifact) { /* can't dual-wield with artifact as secondary weapon */ if (obj == uswapwep) @@ -1331,65 +410,143 @@ const char *name; alter_cost(obj, 0L); if (via_naming) { /* violate illiteracy conduct since successfully wrote arti-name */ - u.uconduct.literate++; + if (!u.uconduct.literate++) + livelog_printf(LL_CONDUCT | LL_ARTIFACT, + "became literate by naming %s", + bare_artifactname(obj)); + else + livelog_printf(LL_ARTIFACT, + "chose %s to be named \"%s\"", + ansimpleoname(obj), bare_artifactname(obj)); } } - if (carried(obj)) + if (carried(obj) && !skip_inv_update) update_inventory(); return obj; } -static NEARDATA const char callable[] = { - SCROLL_CLASS, POTION_CLASS, WAND_CLASS, RING_CLASS, AMULET_CLASS, - GEM_CLASS, SPBOOK_CLASS, ARMOR_CLASS, TOOL_CLASS, 0 -}; - boolean -objtyp_is_callable(i) -int i; +objtyp_is_callable(int i) +{ + if (objects[i].oc_uname) + return TRUE; + + switch(objects[i].oc_class) { + case AMULET_CLASS: + /* 5.0: calling these used to be allowed but that enabled the + player to tell whether two unID'd amulets of yendor were both + fake or one was real by calling them distinct names and then + checking discoveries to see whether first name was replaced + by second or both names stuck; with more than two available + to work with, if they weren't all fake it was possible to + determine which one was the real one */ + if (i == AMULET_OF_YENDOR || i == FAKE_AMULET_OF_YENDOR) + break; /* return FALSE */ + FALLTHROUGH; + /*FALLTHRU*/ + case SCROLL_CLASS: + case POTION_CLASS: + case WAND_CLASS: + case RING_CLASS: + case GEM_CLASS: + case SPBOOK_CLASS: + case ARMOR_CLASS: + case TOOL_CLASS: + case VENOM_CLASS: + if (OBJ_DESCR(objects[i])) + return TRUE; + break; + default: + break; + } + return FALSE; +} + +/* getobj callback for object to name (specific item) - anything but gold */ +int +name_ok(struct obj *obj) { - return (boolean) (objects[i].oc_uname - || (OBJ_DESCR(objects[i]) - && index(callable, objects[i].oc_class))); + if (!obj || obj->oclass == COIN_CLASS) + return GETOBJ_EXCLUDE; + + if (!obj->dknown || obj->oartifact || obj->otyp == SPE_NOVEL) + return GETOBJ_DOWNPLAY; + + return GETOBJ_SUGGEST; +} + +/* getobj callback for object to call (name its type) */ +int +call_ok(struct obj *obj) +{ + if (!obj || !objtyp_is_callable(obj->otyp)) + return GETOBJ_EXCLUDE; + + /* not a likely candidate if not seen yet since naming will fail, + or if it has been discovered and doesn't already have a name; + when something has been named and then becomes discovered, it + remains a likely candidate until player renames it to + to remove that no longer needed name */ + if (!obj->dknown || (objects[obj->otyp].oc_name_known + && !objects[obj->otyp].oc_uname)) + return GETOBJ_DOWNPLAY; + + return GETOBJ_SUGGEST; } -/* C and #name commands - player can name monster or object or type of obj */ +/* #call / #name command - player can name monster or object or type of obj */ int -docallcmd() +docallcmd(void) { struct obj *obj; winid win; anything any; menu_item *pick_list = 0; - char ch, allowall[2]; + struct _cmd_queue cq, *cmdq; + char ch = 0; /* if player wants a,b,c instead of i,o when looting, do that here too */ boolean abc = flags.lootabc; + int clr = NO_COLOR; + if ((cmdq = cmdq_pop()) != 0) { + cq = *cmdq; + free((genericptr_t) cmdq); + if (cq.typ == CMDQ_KEY) + ch = cq.key; + else + cmdq_clear(CQ_CANNED); + goto docallcmd; + } win = create_nhwindow(NHW_MENU); - start_menu(win); - any = zeroany; + start_menu(win, MENU_BEHAVE_STANDARD); + any = cg.zeroany; any.a_char = 'm'; /* group accelerator 'C' */ - add_menu(win, NO_GLYPH, &any, abc ? 0 : any.a_char, 'C', ATR_NONE, - "a monster", MENU_UNSELECTED); - if (invent) { + add_menu(win, &nul_glyphinfo, &any, abc ? 0 : any.a_char, 'C', + ATR_NONE, clr, "a monster", MENU_ITEMFLAGS_NONE); + if (gi.invent) { /* we use y and n as accelerators so that we can accept user's response keyed to old "name an individual object?" prompt */ any.a_char = 'i'; /* group accelerator 'y' */ - add_menu(win, NO_GLYPH, &any, abc ? 0 : any.a_char, 'y', ATR_NONE, - "a particular object in inventory", MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, abc ? 0 : any.a_char, 'y', + ATR_NONE, clr, "a particular object in inventory", + MENU_ITEMFLAGS_NONE); any.a_char = 'o'; /* group accelerator 'n' */ - add_menu(win, NO_GLYPH, &any, abc ? 0 : any.a_char, 'n', ATR_NONE, - "the type of an object in inventory", MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, abc ? 0 : any.a_char, 'n', + ATR_NONE, clr, "the type of an object in inventory", + MENU_ITEMFLAGS_NONE); } any.a_char = 'f'; /* group accelerator ',' (or ':' instead?) */ - add_menu(win, NO_GLYPH, &any, abc ? 0 : any.a_char, ',', ATR_NONE, - "the type of an object upon the floor", MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, abc ? 0 : any.a_char, ',', + ATR_NONE, clr, "the type of an object upon the floor", + MENU_ITEMFLAGS_NONE); any.a_char = 'd'; /* group accelerator '\' */ - add_menu(win, NO_GLYPH, &any, abc ? 0 : any.a_char, '\\', ATR_NONE, - "the type of an object on discoveries list", MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, abc ? 0 : any.a_char, '\\', + ATR_NONE, clr, "the type of an object on discoveries list", + MENU_ITEMFLAGS_NONE); any.a_char = 'a'; /* group accelerator 'l' */ - add_menu(win, NO_GLYPH, &any, abc ? 0 : any.a_char, 'l', ATR_NONE, - "record an annotation for the current level", MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, abc ? 0 : any.a_char, 'l', + ATR_NONE, clr, "record an annotation for the current level", + MENU_ITEMFLAGS_NONE); end_menu(win, "What do you want to name?"); if (select_menu(win, PICK_ONE, &pick_list) > 0) { ch = pick_list[0].item.a_char; @@ -1398,22 +555,21 @@ docallcmd() ch = 'q'; destroy_nhwindow(win); + docallcmd: switch (ch) { default: case 'q': break; case 'm': /* name a visible monster */ - do_mname(); + do_mgivenname(); break; case 'i': /* name an individual object in inventory */ - allowall[0] = ALL_CLASSES; - allowall[1] = '\0'; - obj = getobj(allowall, "name"); + obj = getobj("name", name_ok, GETOBJ_PROMPT); if (obj) do_oname(obj); break; case 'o': /* name a type of object in inventory */ - obj = getobj(callable, "call"); + obj = getobj("call", call_ok, GETOBJ_NOFLAGS); if (obj) { /* behave as if examining it in inventory; this might set dknown if it was picked up @@ -1423,7 +579,7 @@ docallcmd() if (!obj->dknown) { You("would never recognize another one."); #if 0 - } else if (!objtyp_is_callable(obj->otyp)) { + } else if (call_ok(obj) == GETOBJ_EXCLUDE) { You("know those as well as you ever will."); #endif } else { @@ -1441,13 +597,12 @@ docallcmd() donamelevel(); break; } - return 0; + return ECMD_OK; } /* for use by safe_qbuf() */ -STATIC_PTR char * -docall_xname(obj) -struct obj *obj; +staticfn char * +docall_xname(struct obj *obj) { struct obj otemp; @@ -1478,50 +633,50 @@ struct obj *obj; } void -docall(obj) -struct obj *obj; +docall(struct obj *obj) { char buf[BUFSZ], qbuf[QBUFSZ]; - char **str1; + char **uname_p; + boolean had_name = FALSE; if (!obj->dknown) - return; /* probably blind */ + return; /* probably blind; Blind || Hallucination for 'fromsink' */ flush_screen(1); /* buffered updates might matter to player's response */ if (obj->oclass == POTION_CLASS && obj->fromsink) - /* kludge, meaning it's sink water */ + /* fromsink: kludge, meaning it's sink water */ Sprintf(qbuf, "Call a stream of %s fluid:", OBJ_DESCR(objects[obj->otyp])); else (void) safe_qbuf(qbuf, "Call ", ":", obj, docall_xname, simpleonames, "thing"); /* pointer to old name */ - str1 = &(objects[obj->otyp].oc_uname); + uname_p = &(objects[obj->otyp].oc_uname); /* use getlin() to get a name string from the player */ - if (!name_from_player(buf, qbuf, *str1)) + if (!name_from_player(buf, qbuf, *uname_p)) return; /* clear old name */ - if (*str1) - free((genericptr_t) *str1); + if (*uname_p) { + had_name = TRUE; + free((genericptr_t) *uname_p), *uname_p = NULL; /* clear oc_uname */ + } /* strip leading and trailing spaces; uncalls item if all spaces */ (void) mungspaces(buf); if (!*buf) { - if (*str1) { /* had name, so possibly remove from disco[] */ - /* strip name first, for the update_inventory() call - from undiscover_object() */ - *str1 = (char *) 0; + if (had_name) /* possibly remove from disco[]; old *uname_p is gone */ undiscover_object(obj->otyp); - } } else { - *str1 = dupstr(buf); - discover_object(obj->otyp, FALSE, TRUE); /* possibly add to disco[] */ + *uname_p = dupstr(buf); + discover_object(obj->otyp, FALSE, TRUE, TRUE); /* possibly add to disco[] */ } + if (obj->where == OBJ_INVENT || carrying(obj->otyp)) + update_inventory(); } -STATIC_OVL void -namefloorobj() +staticfn void +namefloorobj(void) { coord cc; int glyph; @@ -1534,10 +689,11 @@ namefloorobj() been moved off the hero's '@' yet, but there's no way to adjust the help text once getpos() has started */ Sprintf(buf, "object on map (or '.' for one %s you)", - (u.uundetected && hides_under(youmonst.data)) ? "over" : "under"); + (u.uundetected && hides_under(gy.youmonst.data)) + ? "over" : "under"); if (getpos(&cc, FALSE, buf) < 0 || cc.x <= 0) return; - if (cc.x == u.ux && cc.y == u.uy) { + if (u_at(cc.x, cc.y)) { obj = vobj_at(u.ux, u.uy); } else { glyph = glyph_at(cc.x, cc.y); @@ -1547,8 +703,8 @@ namefloorobj() } if (!obj) { /* "under you" is safe here since there's no object to hide under */ - pline("There doesn't seem to be any object %s.", - (cc.x == u.ux && cc.y == u.uy) ? "under you" : "there"); + There("doesn't seem to be any object %s.", + u_at(cc.x, cc.y) ? "under you" : "there"); return; } /* note well: 'obj' might be an instance of STRANGE_OBJECT if target @@ -1565,9 +721,9 @@ namefloorobj() char tmpbuf[BUFSZ]; /* straight role name */ - unames[0] = ((Upolyd ? u.mfemale : flags.female) && urole.name.f) - ? urole.name.f - : urole.name.m; + unames[0] = ((Upolyd ? u.mfemale : flags.female) && gu.urole.name.f) + ? gu.urole.name.f + : gu.urole.name.m; /* random rank title for hero's role note: the 30 is hardcoded in xlev_to_rank, so should be @@ -1585,7 +741,7 @@ namefloorobj() pline("%s %s to call you \"%s.\"", The(buf), use_plural ? "decide" : "decides", unames[rn2_on_display_rng(SIZE(unames))]); - } else if (!objtyp_is_callable(obj->otyp)) { + } else if (call_ok(obj) == GETOBJ_EXCLUDE) { pline("%s %s can't be assigned a type name.", use_plural ? "Those" : "That", buf); } else if (!obj->dknown) { @@ -1596,7 +752,7 @@ namefloorobj() } if (fakeobj) { obj->where = OBJ_FREE; /* object_from_map() sets it to OBJ_FLOOR */ - dealloc_obj(obj); + dealloc_obj(obj); /* has no contents */ } } @@ -1608,14 +764,15 @@ static const char *const ghostnames[] = { "John", "Jon", "Karnov", "Kay", "Kenny", "Kevin", "Maud", "Michiel", "Mike", "Peter", "Robert", "Ron", "Tom", "Wilmar", "Nick Danger", "Phoenix", "Jiro", "Mizue", - "Stephan", "Lance Braccus", "Shadowhawk" + "Stephan", "Lance Braccus", "Shadowhawk", "Murphy" }; /* ghost names formerly set by x_monnam(), now by makemon() instead */ const char * -rndghostname() +rndghostname(void) { - return rn2(7) ? ghostnames[rn2(SIZE(ghostnames))] : (const char *) plname; + return rn2(7) ? ROLL_FROM(ghostnames) + : (const char *) svp.plname; } /* @@ -1623,72 +780,111 @@ rndghostname() * x_monnam is the generic monster-naming function. * seen unseen detected named * mon_nam: the newt it the invisible orc Fido - * noit_mon_nam:the newt (as if detected) the invisible orc Fido + * noit_mon_nam:your newt (as if detected) your invisible orc Fido + * some_mon_nam:the newt someone the invisible orc Fido + * or something * l_monnam: newt it invisible orc dog called Fido * Monnam: The newt It The invisible orc Fido - * noit_Monnam: The newt (as if detected) The invisible orc Fido + * noit_Monnam: Your newt (as if detected) Your invisible orc Fido + * Some_Monnam: The newt Someone The invisible orc Fido + * or Something * Adjmonnam: The poor newt It The poor invisible orc The poor Fido * Amonnam: A newt It An invisible orc Fido * a_monnam: a newt it an invisible orc Fido * m_monnam: newt xan orc Fido * y_monnam: your newt your xan your invisible orc Fido + * YMonnam: Your newt Your xan Your invisible orc Fido * noname_monnam(mon,article): * article newt art xan art invisible orc art dog */ -/* Bug: if the monster is a priest or shopkeeper, not every one of these - * options works, since those are special cases. - */ -char * -x_monnam(mtmp, article, adjective, suppress, called) -register struct monst *mtmp; -int article; -/* ARTICLE_NONE, ARTICLE_THE, ARTICLE_A: obvious +/* + * article + * + * ARTICLE_NONE, ARTICLE_THE, ARTICLE_A: obvious * ARTICLE_YOUR: "your" on pets, "the" on everything else * * If the monster would be referred to as "it" or if the monster has a name * _and_ there is no adjective, "invisible", "saddled", etc., override this * and always use no article. - */ -const char *adjective; -int suppress; -/* SUPPRESS_IT, SUPPRESS_INVISIBLE, SUPPRESS_HALLUCINATION, SUPPRESS_SADDLE. + * + * suppress + * + * SUPPRESS_IT, SUPPRESS_INVISIBLE, SUPPRESS_HALLUCINATION, SUPPRESS_SADDLE. + * SUPPRESS_MAPPEARANCE: if monster is mimicking another monster (cloned + * Wizard or quickmimic pet), describe the real monster rather + * than its current form; * EXACT_NAME: combination of all the above * SUPPRESS_NAME: omit monster's assigned name (unless uniq w/ pname). + * AUGMENT_IT: not suppression but shares suppression bitmask; if result + * would have been "it", return "someone" if humanoid or + * "something" otherwise. + * + * Bug: if the monster is a priest or shopkeeper, not every one of these + * options works, since those are special cases. */ -boolean called; +char * +x_monnam( + struct monst *mtmp, + int article, + const char *adjective, + int suppress, + boolean called) { char *buf = nextmbuf(); struct permonst *mdat = mtmp->data; - const char *pm_name = mdat->mname; - boolean do_hallu, do_invis, do_it, do_saddle, do_name; - boolean name_at_start, has_adjectives; - char *bp; + const char *pm_name; + boolean do_hallu, do_invis, do_it, do_saddle, do_mappear, + do_exact, do_name, augment_it; + boolean name_at_start, has_adjectives, insertbuf2, + mappear_as_mon = (M_AP_TYPE(mtmp) == M_AP_MONSTER); + char *bp, buf2[BUFSZ]; + + if (mtmp == &gy.youmonst) + return strcpy(buf, "you"); /* ignore article, "invisible", &c */ if (program_state.gameover) suppress |= SUPPRESS_HALLUCINATION; if (article == ARTICLE_YOUR && !mtmp->mtame) article = ARTICLE_THE; + if (u.uswallow && mtmp == u.ustuck) { + /* + * This monster has become important, for the moment anyway. + * As the hero's consumer, it is worthy of ARTICLE_THE. + * Also, suppress invisible as that particular characteristic + * is unimportant now and you can see its interior anyway. + */ + article = ARTICLE_THE; + suppress |= SUPPRESS_INVISIBLE; + } do_hallu = Hallucination && !(suppress & SUPPRESS_HALLUCINATION); do_invis = mtmp->minvis && !(suppress & SUPPRESS_INVISIBLE); do_it = !canspotmon(mtmp) && article != ARTICLE_YOUR && !program_state.gameover && mtmp != u.usteed - && !(u.uswallow && mtmp == u.ustuck) && !(suppress & SUPPRESS_IT); + && !engulfing_u(mtmp) && !(suppress & SUPPRESS_IT); do_saddle = !(suppress & SUPPRESS_SADDLE); + do_mappear = mappear_as_mon && !(suppress & SUPPRESS_MAPPEARANCE); + do_exact = (suppress & EXACT_NAME) == EXACT_NAME; do_name = !(suppress & SUPPRESS_NAME) || type_is_pname(mdat); + augment_it = (suppress & AUGMENT_IT) != 0; buf[0] = '\0'; - /* unseen monsters, etc. Use "it" */ + /* unseen monsters, etc.; usually "it" but sometimes more specific; + when hallucinating, the more specific values might be inverted */ if (do_it) { - Strcpy(buf, "it"); + /* !is_animal excludes all Y; !mindless excludes Z, M, \' */ + boolean s_one = humanoid(mdat) && !is_animal(mdat) && !mindless(mdat); + + Strcpy(buf, !augment_it ? "it" + : (!do_hallu ? s_one : !rn2(2)) ? "someone" + : "something"); return buf; } /* priests and minions: don't even use this function */ - if (mtmp->ispriest || mtmp->isminion) { - char priestnambuf[BUFSZ]; + if ((mtmp->ispriest || mtmp->isminion) && !do_mappear) { char *name; long save_prop = EHalluc_resistance; unsigned save_invis = mtmp->minvis; @@ -1698,41 +894,44 @@ boolean called; EHalluc_resistance = 1L; if (!do_invis) mtmp->minvis = 0; - name = priestname(mtmp, priestnambuf); + /* EXACT_NAME will force "of " on the Astral Plane */ + name = priestname(mtmp, article, do_exact, buf2); EHalluc_resistance = save_prop; mtmp->minvis = save_invis; if (article == ARTICLE_NONE && !strncmp(name, "the ", 4)) name += 4; return strcpy(buf, name); } - /* an "aligned priest" not flagged as a priest or minion should be - "priest" or "priestess" (normally handled by priestname()) */ - if (mdat == &mons[PM_ALIGNED_PRIEST]) - pm_name = mtmp->female ? "priestess" : "priest"; - else if (mdat == &mons[PM_HIGH_PRIEST] && mtmp->female) - pm_name = "high priestess"; + + /* 'pm_name' is the base part of most names */ + if (do_mappear) { + /*assert(ismnum(mtmp->mappearance));*/ + pm_name = pmname(&mons[mtmp->mappearance], Mgender(mtmp)); + } else { + pm_name = mon_pmname(mtmp); + } /* Shopkeepers: use shopkeeper name. For normal shopkeepers, just * "Asidonhopo"; for unusual ones, "Asidonhopo the invisible * shopkeeper" or "Asidonhopo the blue dragon". If hallucinating, * none of this applies. */ - if (mtmp->isshk && !do_hallu) { + if (mtmp->isshk && !do_hallu && !do_mappear) { if (adjective && article == ARTICLE_THE) { /* pathological case: "the angry Asidonhopo the blue dragon" sounds silly */ Strcpy(buf, "the "); Strcat(strcat(buf, adjective), " "); Strcat(buf, shkname(mtmp)); - return buf; + } else { + Strcat(buf, shkname(mtmp)); + if (mdat != &mons[PM_SHOPKEEPER] || do_invis){ + Strcat(buf, " the "); + if (do_invis) + Strcat(buf, "invisible "); + Strcat(buf, pm_name); + } } - Strcat(buf, shkname(mtmp)); - if (mdat == &mons[PM_SHOPKEEPER] && !do_invis) - return buf; - Strcat(buf, " the "); - if (do_invis) - Strcat(buf, "invisible "); - Strcat(buf, pm_name); return buf; } @@ -1754,9 +953,13 @@ boolean called; Strcat(buf, rname); name_at_start = bogon_is_pname(rnamecode); - } else if (do_name && has_mname(mtmp)) { - char *name = MNAME(mtmp); + } else if (do_name && has_mgivenname(mtmp)) { + char *name = MGIVENNAME(mtmp); +#if 0 + /* hardfought */ + if (has_ebones(mtmp)) { +#endif if (mdat == &mons[PM_GHOST]) { Sprintf(eos(buf), "%s ghost", s_suffix(name)); name_at_start = TRUE; @@ -1779,6 +982,9 @@ boolean called; Strcat(buf, name); name_at_start = TRUE; } +#if 0 /* hardfought */ + } +#endif } else if (is_mplayer(mdat) && !In_endgame(&u.uz)) { char pbuf[BUFSZ]; @@ -1796,88 +1002,104 @@ boolean called; article = ARTICLE_THE; else article = ARTICLE_NONE; - } else if ((mdat->geno & G_UNIQ) && article == ARTICLE_A) { + } else if ((mdat->geno & G_UNIQ) != 0 && article == ARTICLE_A) { article = ARTICLE_THE; } - { - char buf2[BUFSZ]; - - switch (article) { - case ARTICLE_YOUR: - Strcpy(buf2, "your "); - Strcat(buf2, buf); - Strcpy(buf, buf2); - return buf; - case ARTICLE_THE: - Strcpy(buf2, "the "); - Strcat(buf2, buf); - Strcpy(buf, buf2); - return buf; - case ARTICLE_A: - return an(buf); - case ARTICLE_NONE: - default: - return buf; - } + insertbuf2 = TRUE; + buf2[0] = '\0'; /* lint suppression */ + switch (article) { + case ARTICLE_YOUR: + Strcpy(buf2, "your "); + break; + case ARTICLE_THE: + Strcpy(buf2, "the "); + break; + case ARTICLE_A: + /* avoid an() here */ + (void) just_an(buf2, buf); /* copy "a " or "an " into buf2[] */ + break; + case ARTICLE_NONE: + default: + insertbuf2 = FALSE; + break; + } + if (insertbuf2) { + Strcat(buf2, buf); /* buf2[] isn't viable to return, */ + Strcpy(buf, buf2); /* so transfer the result to buf[] */ } + return buf; } char * -l_monnam(mtmp) -struct monst *mtmp; +l_monnam(struct monst *mtmp) { return x_monnam(mtmp, ARTICLE_NONE, (char *) 0, - (has_mname(mtmp)) ? SUPPRESS_SADDLE : 0, TRUE); + (has_mgivenname(mtmp)) ? SUPPRESS_SADDLE : 0, TRUE); } char * -mon_nam(mtmp) -struct monst *mtmp; +mon_nam(struct monst *mtmp) { return x_monnam(mtmp, ARTICLE_THE, (char *) 0, - (has_mname(mtmp)) ? SUPPRESS_SADDLE : 0, FALSE); + (has_mgivenname(mtmp)) ? SUPPRESS_SADDLE : 0, FALSE); } -/* print the name as if mon_nam() was called, but assume that the player - * can always see the monster--used for probing and for monsters aggravating - * the player with a cursed potion of invisibility - */ +/* print the name as if mon_nam() (y_monnam() if tame) was called, but + assume that the player can always see the monster--used for probing and + for monsters aggravating the player with a cursed potion of invisibility; + also used for pet moving "reluctantly" onto cursed object when that pet + can be seen either before or after it moves */ char * -noit_mon_nam(mtmp) -struct monst *mtmp; +noit_mon_nam(struct monst *mtmp) +{ + return x_monnam(mtmp, ARTICLE_YOUR, (char *) 0, + (has_mgivenname(mtmp) ? (SUPPRESS_SADDLE | SUPPRESS_IT) + : SUPPRESS_IT), + FALSE); +} + +/* in between noit_mon_nam() and mon_nam(); if the latter would pick "it", + use "someone" (for humanoids) or "something" (for others) instead */ +char * +some_mon_nam(struct monst *mtmp) { return x_monnam(mtmp, ARTICLE_THE, (char *) 0, - (has_mname(mtmp)) ? (SUPPRESS_SADDLE | SUPPRESS_IT) - : SUPPRESS_IT, + (has_mgivenname(mtmp) ? (SUPPRESS_SADDLE | AUGMENT_IT) + : AUGMENT_IT), FALSE); } char * -Monnam(mtmp) -struct monst *mtmp; +Monnam(struct monst *mtmp) { - register char *bp = mon_nam(mtmp); + char *bp = mon_nam(mtmp); *bp = highc(*bp); - return bp; + return bp; } char * -noit_Monnam(mtmp) -struct monst *mtmp; +noit_Monnam(struct monst *mtmp) { - register char *bp = noit_mon_nam(mtmp); + char *bp = noit_mon_nam(mtmp); *bp = highc(*bp); - return bp; + return bp; +} + +char * +Some_Monnam(struct monst *mtmp) +{ + char *bp = some_mon_nam(mtmp); + + *bp = highc(*bp); + return bp; } /* return "a dog" rather than "Fido", honoring hallucination and visibility */ char * -noname_monnam(mtmp, article) -struct monst *mtmp; -int article; +noname_monnam(struct monst *mtmp, int article) { return x_monnam(mtmp, article, (char *) 0, SUPPRESS_NAME, FALSE); } @@ -1885,21 +1107,19 @@ int article; /* monster's own name -- overrides hallucination and [in]visibility so shouldn't be used in ordinary messages (mainly for disclosure) */ char * -m_monnam(mtmp) -struct monst *mtmp; +m_monnam(struct monst *mtmp) { return x_monnam(mtmp, ARTICLE_NONE, (char *) 0, EXACT_NAME, FALSE); } /* pet name: "your little dog" */ char * -y_monnam(mtmp) -struct monst *mtmp; +y_monnam(struct monst *mtmp) { int prefix, suppression_flag; prefix = mtmp->mtame ? ARTICLE_YOUR : ARTICLE_THE; - suppression_flag = (has_mname(mtmp) + suppression_flag = (has_mgivenname(mtmp) /* "saddled" is redundant when mounted */ || mtmp == u.usteed) ? SUPPRESS_SADDLE @@ -1908,49 +1128,55 @@ struct monst *mtmp; return x_monnam(mtmp, prefix, (char *) 0, suppression_flag, FALSE); } +/* y_monnam() for start of sentence */ char * -Adjmonnam(mtmp, adj) -struct monst *mtmp; -const char *adj; +YMonnam(struct monst *mtmp) +{ + char *bp = y_monnam(mtmp); + + *bp = highc(*bp); + return bp; +} + +char * +Adjmonnam(struct monst *mtmp, const char *adj) { char *bp = x_monnam(mtmp, ARTICLE_THE, adj, - has_mname(mtmp) ? SUPPRESS_SADDLE : 0, FALSE); + has_mgivenname(mtmp) ? SUPPRESS_SADDLE : 0, FALSE); *bp = highc(*bp); - return bp; + return bp; } char * -a_monnam(mtmp) -struct monst *mtmp; +a_monnam(struct monst *mtmp) { return x_monnam(mtmp, ARTICLE_A, (char *) 0, - has_mname(mtmp) ? SUPPRESS_SADDLE : 0, FALSE); + has_mgivenname(mtmp) ? SUPPRESS_SADDLE : 0, FALSE); } char * -Amonnam(mtmp) -struct monst *mtmp; +Amonnam(struct monst *mtmp) { char *bp = a_monnam(mtmp); *bp = highc(*bp); - return bp; + return bp; } /* used for monster ID by the '/', ';', and 'C' commands to block remote identification of the endgame altars via their attending priests */ char * -distant_monnam(mon, article, outbuf) -struct monst *mon; -int article; /* only ARTICLE_NONE and ARTICLE_THE are handled here */ -char *outbuf; +distant_monnam( + struct monst *mon, + int article, /* only ARTICLE_NONE and ARTICLE_THE are handled here */ + char *outbuf) { /* high priest(ess)'s identity is concealed on the Astral Plane, unless you're adjacent (overridden for hallucination which does its own obfuscation) */ - if (mon->data == &mons[PM_HIGH_PRIEST] && !Hallucination - && Is_astralevel(&u.uz) && distu(mon->mx, mon->my) > 2) { + if (mon->data == &mons[PM_HIGH_CLERIC] && !Hallucination + && Is_astralevel(&u.uz) && !m_next2u(mon)) { Strcpy(outbuf, article == ARTICLE_THE ? "the " : ""); Strcat(outbuf, mon->female ? "high priestess" : "high priest"); } else { @@ -1962,8 +1188,7 @@ char *outbuf; /* returns mon_nam(mon) relative to other_mon; normal name unless they're the same, in which case the reference is to {him|her|it} self */ char * -mon_nam_too(mon, other_mon) -struct monst *mon, *other_mon; +mon_nam_too(struct monst *mon, struct monst *other_mon) { char *outbuf; @@ -1971,7 +1196,7 @@ struct monst *mon, *other_mon; outbuf = mon_nam(mon); } else { outbuf = nextmbuf(); - switch (pronoun_gender(mon, FALSE)) { + switch (pronoun_gender(mon, PRONOUN_HALLU)) { case 0: Strcpy(outbuf, "himself"); break; @@ -1979,19 +1204,54 @@ struct monst *mon, *other_mon; Strcpy(outbuf, "herself"); break; default: + case 2: Strcpy(outbuf, "itself"); break; + case 3: /* could happen when hallucinating */ + Strcpy(outbuf, "themselves"); + break; } } return outbuf; } +/* construct " {him|her|it}self" which might + be distorted by Hallu; if that's plural, adjust monnamtext and verb */ +char * +monverbself( + struct monst *mon, + char *monnamtext, /* modifiable 'mbuf' with adequate room at end */ + const char *verb, + const char *othertext) +{ + char *verbs, selfbuf[40]; /* sizeof "themselves" suffices */ + + /* "himself"/"herself"/"itself", maybe "themselves" if hallucinating */ + Strcpy(selfbuf, mon_nam_too(mon, mon)); + /* verb starts plural; this will yield singular except for "themselves" */ + verbs = vtense(selfbuf, verb); + if (!strcmp(verb, verbs)) { /* a match indicates that it stayed plural */ + monnamtext = makeplural(monnamtext); + /* for "it", makeplural() produces "them" but we want "they" */ + if (!strcmpi(monnamtext, genders[3].he)) { + boolean capitaliz = (monnamtext[0] == highc(monnamtext[0])); + + Strcpy(monnamtext, genders[3].him); + if (capitaliz) + monnamtext[0] = highc(monnamtext[0]); + } + } + Strcat(strcat(monnamtext, " "), verbs); + if (othertext && *othertext) + Strcat(strcat(monnamtext, " "), othertext); + Strcat(strcat(monnamtext, " "), selfbuf); + return monnamtext; +} + /* for debugging messages, where data might be suspect and we aren't taking what the hero does or doesn't know into consideration */ char * -minimal_monnam(mon, ckloc) -struct monst *mon; -boolean ckloc; +minimal_monnam(struct monst *mon, boolean ckloc) { struct permonst *ptr; char *outbuf = nextmbuf(); @@ -2008,49 +1268,128 @@ boolean ckloc; Sprintf(outbuf, "[Invalid mon->data %s >= %s]", fmt_ptr((genericptr_t) mon->data), fmt_ptr((genericptr_t) &mons[NUMMONS])); - } else if (ckloc && ptr == &mons[PM_LONG_WORM] - && level.monsters[mon->mx][mon->my] != mon) { + } else if (ckloc && ptr == &mons[PM_LONG_WORM] && mon->mx + && svl.level.monsters[mon->mx][mon->my] != mon) { Sprintf(outbuf, "%s <%d,%d>", - mons[PM_LONG_WORM_TAIL].mname, mon->mx, mon->my); + pmname(&mons[PM_LONG_WORM_TAIL], Mgender(mon)), + mon->mx, mon->my); } else { Sprintf(outbuf, "%s%s <%d,%d>", mon->mtame ? "tame " : mon->mpeaceful ? "peaceful " : "", - mon->data->mname, mon->mx, mon->my); + mon_pmname(mon), mon->mx, mon->my); if (mon->cham != NON_PM) - Sprintf(eos(outbuf), "{%s}", mons[mon->cham].mname); + Sprintf(eos(outbuf), "{%s}", + pmname(&mons[mon->cham], Mgender(mon))); } return outbuf; } +#ifndef PMNAME_MACROS +int +Mgender(struct monst *mtmp) +{ + int mgender = MALE; + + if (mtmp == &gy.youmonst) { + if (Upolyd ? u.mfemale : flags.female) + mgender = FEMALE; + } else if (mtmp->female) { + mgender = FEMALE; + } + return mgender; +} + +const char * +pmname(struct permonst *pm, int mgender) +{ + if (mgender < MALE || mgender >= NUM_MGENDERS || !pm->pmnames[mgender]) + mgender = NEUTRAL; + return pm->pmnames[mgender]; +} +#endif /* PMNAME_MACROS */ + +/* mons[]->pmname for a monster */ +const char * +mon_pmname(struct monst *mon) +{ + /* for neuter, mon->data->pmnames[MALE] will be Null and use [NEUTRAL] */ + return pmname(mon->data, Mgender(mon)); +} + +/* mons[]->pmname for a corpse or statue or figurine */ +const char * +obj_pmname(struct obj *obj) +{ +#if 0 /* ignore saved montraits even when they're available; they determine + * what a corpse would revive as if resurrected (human corpse from + * slain vampire revives as vampire rather than as human, for example) + * and don't necessarily reflect the state of the corpse itself */ + if (has_omonst(obj)) { + struct monst *m = OMONST(obj); + + /* obj->oextra->omonst->data is Null but ...->mnum is set */ + if (ismnum(m->mnum)) + return pmname(&mons[m->mnum], Mgender(m)); + } +#endif + if ((obj->otyp == CORPSE || obj->otyp == STATUE || obj->otyp == FIGURINE) + && ismnum(obj->corpsenm)) { + int cgend = (obj->spe & CORPSTAT_GENDER), + mgend = ((cgend == CORPSTAT_MALE) ? MALE + : (cgend == CORPSTAT_FEMALE) ? FEMALE + : NEUTRAL), + mndx = obj->corpsenm; + + /* mons[].pmnames[] for monster cleric uses "priest" or "priestess" + or "aligned cleric"; we want to avoid "aligned cleric [corpse]" + unless it has been explicitly flagged as neuter rather than + defaulting to random (which fails male or female check above); + role monster cleric uses "priest" or "priestess" or "cleric" + without "aligned" prefix so we switch to that; [can't force + random gender to be chosen here because splitting a stack of + corpses could cause the split-off portion to change gender, so + settle for avoiding "aligned"] */ + if (mndx == PM_ALIGNED_CLERIC && cgend == CORPSTAT_RANDOM) + mndx = PM_CLERIC; + + return pmname(&mons[mndx], mgend); + } + impossible("obj_pmname otyp:%i,corpsenm:%i", obj->otyp, obj->corpsenm); + return "two-legged glorkum-seeker"; +} + +/* used by bogusmon(next) and also by init_CapMons(rumors.c); + bogon_is_pname(below) checks a hard-coded subset of these rather than + use this list. + Also used in rumors.c */ +const char bogon_codes[] = "-_+|="; /* see dat/bonusmon.txt */ + /* fake monsters used to be in a hard-coded array, now in a data file */ -STATIC_OVL char * -bogusmon(buf, code) -char *buf, *code; +char * +bogusmon(char *buf, char *code) { - static const char bogon_codes[] = "-_+|="; /* see dat/bonusmon.txt */ - char *mname = buf; + char *mnam = buf; if (code) *code = '\0'; /* might fail (return empty buf[]) if the file isn't available */ - get_rnd_text(BOGUSMONFILE, buf, rn2_on_display_rng); - if (!*mname) { + get_rnd_text(BOGUSMONFILE, buf, rn2_on_display_rng, MD_PAD_BOGONS); + if (!*mnam) { Strcpy(buf, "bogon"); - } else if (index(bogon_codes, *mname)) { /* strip prefix if present */ + } else if (strchr(bogon_codes, *mnam)) { /* strip prefix if present */ if (code) - *code = *mname; - ++mname; + *code = *mnam; + ++mnam; } - return mname; + return mnam; } /* return a random monster name, for hallucination */ char * -rndmonnam(code) -char *code; +rndmonnam(char *code) { static char buf[BUFSZ]; - char *mname; + char *mnam; int name; #define BOGUSMONSIZE 100 /* arbitrary */ @@ -2063,27 +1402,26 @@ char *code; && (type_is_pname(&mons[name]) || (mons[name].geno & G_NOGEN))); if (name >= SPECIAL_PM) { - mname = bogusmon(buf, code); + mnam = bogusmon(buf, code); } else { - mname = strcpy(buf, mons[name].mname); + mnam = strcpy(buf, pmname(&mons[name], rn2_on_display_rng(2))); } - return mname; + return mnam; #undef BOGUSMONSIZE } /* check bogusmon prefix to decide whether it's a personal name */ boolean -bogon_is_pname(code) -char code; +bogon_is_pname(char code) { if (!code) return FALSE; - return index("-+=", code) ? TRUE : FALSE; + return strchr("-+=", code) ? TRUE : FALSE; } /* name of a Rogue player */ const char * -roguename() +roguename(void) { char *i, *opts; @@ -2091,7 +1429,7 @@ roguename() for (i = opts; *i; i++) if (!strncmp("name=", i, 5)) { char *j; - if ((j = index(i + 5, ',')) != 0) + if ((j = strchr(i + 5, ',')) != 0) *j = (char) 0; return i + 5; } @@ -2102,17 +1440,25 @@ roguename() static NEARDATA const char *const hcolors[] = { "ultraviolet", "infrared", "bluish-orange", "reddish-green", "dark white", - "light black", "sky blue-pink", "salty", "sweet", "sour", "bitter", + "light black", "sky blue-pink", "pinkish-cyan", "indigo-chartreuse", + "salty", "sweet", "sour", "bitter", "umami", /* basic tastes */ "striped", "spiral", "swirly", "plaid", "checkered", "argyle", "paisley", "blotchy", "guernsey-spotted", "polka-dotted", "square", "round", "triangular", "cabernet", "sangria", "fuchsia", "wisteria", "lemon-lime", "strawberry-banana", "peppermint", "romantic", "incandescent", "octarine", /* Discworld: the Colour of Magic */ + "excitingly dull", "mauve", "electric", + "neon", "fluorescent", "phosphorescent", "translucent", "opaque", + "psychedelic", "iridescent", "rainbow-colored", "polychromatic", + "colorless", "colorless green", + "dancing", "singing", "loving", "loudy", "noisy", "clattery", "silent", + "apocyan", "infra-pink", "opalescent", "violant", "tuneless", + "viridian", "aureolin", "cinnabar", "purpurin", "gamboge", "madder", + "bistre", "ecru", "fulvous", "tekhelet", "selective yellow", }; const char * -hcolor(colorpref) -const char *colorpref; +hcolor(const char *colorpref) { return (Hallucination || !colorpref) ? hcolors[rn2_on_display_rng(SIZE(hcolors))] @@ -2121,7 +1467,7 @@ const char *colorpref; /* return a random real color unless hallucinating */ const char * -rndcolor() +rndcolor(void) { int k = rn2(CLR_MAX); @@ -2138,14 +1484,29 @@ static NEARDATA const char *const hliquids[] = { "caramel sauce", "ink", "aqueous humour", "milk substitute", "fruit juice", "glowing lava", "gastric acid", "mineral water", "cough syrup", "quicksilver", "sweet vitriol", "grey goo", "pink slime", + "cosmic latte", "bone oil", "custard", "lard", "vinegar", "creosote", + /* "new coke (tm)", --better not */ }; +/* if hallucinating, return a random liquid instead of 'liquidpref' */ const char * -hliquid(liquidpref) -const char *liquidpref; +hliquid( + const char *liquidpref) /* use as-is when not hallucintg (unless empty) */ { - return (Hallucination || !liquidpref) ? hliquids[rn2(SIZE(hliquids))] - : liquidpref; + boolean hallucinate = Hallucination && !program_state.gameover; + + if (hallucinate || !liquidpref || !*liquidpref) { + int indx, count = SIZE(hliquids); + + /* if we have a non-hallucinatory default value, include it + among the choices */ + if (liquidpref && *liquidpref) + ++count; + indx = rn2_on_display_rng(count); + if (IndexOk(indx, hliquids)) + return hliquids[indx]; + } + return liquidpref; } /* Aliases for road-runner nemesis @@ -2162,9 +1523,7 @@ static const char *const coynames[] = { }; char * -coyotename(mtmp, buf) -struct monst *mtmp; -char *buf; +coyotename(struct monst *mtmp, char *buf) { if (mtmp && buf) { Sprintf(buf, "%s - %s", @@ -2176,11 +1535,10 @@ char *buf; } char * -rndorcname(s) -char *s; +rndorcname(char *s) { - static const char *v[] = { "a", "ai", "og", "u" }; - static const char *snd[] = { "gor", "gris", "un", "bane", "ruk", + static const char *const v[] = { "a", "ai", "og", "u" }; + static const char *const snd[] = { "gor", "gris", "un", "bane", "ruk", "oth","ul", "z", "thos","akh","hai" }; int i, iend = rn1(2, 3), vstart = rn2(2); @@ -2189,21 +1547,20 @@ char *s; for (i = 0; i < iend; ++i) { vstart = 1 - vstart; /* 0 -> 1, 1 -> 0 */ Sprintf(eos(s), "%s%s", (i > 0 && !rn2(30)) ? "-" : "", - vstart ? v[rn2(SIZE(v))] : snd[rn2(SIZE(snd))]); + vstart ? ROLL_FROM(v) : ROLL_FROM(snd)); } } return s; } struct monst * -christen_orc(mtmp, gang, other) -struct monst *mtmp; -const char *gang, *other; +christen_orc(struct monst *mtmp, const char *gang, const char *other) { int sz = 0; char buf[BUFSZ], buf2[BUFSZ], *orcname; orcname = rndorcname(buf2); + /* rndorcname() won't return NULL */ sz = (int) strlen(orcname); if (gang) sz += (int) (strlen(gang) + sizeof " of " - sizeof ""); @@ -2214,11 +1571,11 @@ const char *gang, *other; char gbuf[BUFSZ]; boolean nameit = FALSE; - if (gang && orcname) { + if (gang) { Sprintf(buf, "%s of %s", upstart(orcname), upstart(strcpy(gbuf, gang))); nameit = TRUE; - } else if (other && orcname) { + } else if (other) { Sprintf(buf, "%s%s", upstart(orcname), other); nameit = TRUE; } @@ -2228,7 +1585,9 @@ const char *gang, *other; return mtmp; } -/* make sure "The Colour of Magic" remains the first entry in here */ +/* Discworld novel titles, in the order that they were published; a subset + of them have index macros used for variant spellings; if the titles are + reordered for some reason, make sure that those get renumbered to match */ static const char *const sir_Terry_novels[] = { "The Colour of Magic", "The Light Fantastic", "Equal Rites", "Mort", "Sourcery", "Wyrd Sisters", "Pyramids", "Guards! Guards!", "Eric", @@ -2242,10 +1601,14 @@ static const char *const sir_Terry_novels[] = { "Making Money", "Unseen Academicals", "I Shall Wear Midnight", "Snuff", "Raising Steam", "The Shepherd's Crown" }; +#define NVL_COLOUR_OF_MAGIC 0 +#define NVL_SOURCERY 4 +#define NVL_MASKERADE 17 +#define NVL_AMAZING_MAURICE 27 +#define NVL_THUD 33 const char * -noveltitle(novidx) -int *novidx; +noveltitle(int *novidx) { int j, k = SIZE(sir_Terry_novels); @@ -2259,16 +1622,28 @@ int *novidx; return sir_Terry_novels[j]; } +/* figure out canonical novel title from player-specified one */ const char * -lookup_novel(lookname, idx) -const char *lookname; -int *idx; +lookup_novel(const char *lookname, int *idx) { int k; - /* Take American or U.K. spelling of this one */ + /* + * Accept variant spellings: + * _The_Colour_of_Magic_ uses British spelling, and American + * editions keep that, but we also recognize American spelling; + * _Sourcery_ is a joke rather than British spelling of "sorcery". + */ if (!strcmpi(The(lookname), "The Color of Magic")) - lookname = sir_Terry_novels[0]; + lookname = sir_Terry_novels[NVL_COLOUR_OF_MAGIC]; + else if (!strcmpi(lookname, "Sorcery")) + lookname = sir_Terry_novels[NVL_SOURCERY]; + else if (!strcmpi(lookname, "Masquerade")) + lookname = sir_Terry_novels[NVL_MASKERADE]; + else if (!strcmpi(The(lookname), "The Amazing Maurice")) + lookname = sir_Terry_novels[NVL_AMAZING_MAURICE]; + else if (!strcmpi(lookname, "Thud")) + lookname = sir_Terry_novels[NVL_THUD]; for (k = 0; k < SIZE(sir_Terry_novels); ++k) { if (!strcmpi(lookname, sir_Terry_novels[k]) @@ -2279,7 +1654,7 @@ int *idx; } } /* name not found; if novelidx is already set, override the name */ - if (idx && *idx >= 0 && *idx < SIZE(sir_Terry_novels)) + if (idx && IndexOk(*idx, sir_Terry_novels)) return sir_Terry_novels[*idx]; return (const char *) 0; diff --git a/src/do_wear.c b/src/do_wear.c index b48c69dcf..b5593ab36 100644 --- a/src/do_wear.c +++ b/src/do_wear.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 do_wear.c $NHDT-Date: 1575214670 2019/12/01 15:37:50 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.116 $ */ +/* NetHack 5.0 do_wear.c $NHDT-Date: 1737343372 2025/01/19 19:22:52 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.201 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2012. */ /* NetHack may be freely redistributed. See license for details. */ @@ -20,34 +20,44 @@ static NEARDATA const long takeoff_order[] = { WORN_SHIRT, WORN_BOOTS, W_SWAPWEP, W_QUIVER, 0L }; -STATIC_DCL void FDECL(on_msg, (struct obj *)); -STATIC_DCL void FDECL(toggle_stealth, (struct obj *, long, BOOLEAN_P)); -STATIC_DCL void FDECL(toggle_displacement, (struct obj *, long, BOOLEAN_P)); -STATIC_PTR int NDECL(Armor_on); -/* int NDECL(Boots_on); -- moved to extern.h */ -STATIC_PTR int NDECL(Cloak_on); -STATIC_PTR int NDECL(Helmet_on); -STATIC_PTR int NDECL(Gloves_on); -STATIC_DCL void FDECL(wielding_corpse, (struct obj *, BOOLEAN_P)); -STATIC_PTR int NDECL(Shield_on); -STATIC_PTR int NDECL(Shirt_on); -STATIC_DCL void NDECL(Amulet_on); -STATIC_DCL void FDECL(learnring, (struct obj *, BOOLEAN_P)); -STATIC_DCL void FDECL(Ring_off_or_gone, (struct obj *, BOOLEAN_P)); -STATIC_PTR int FDECL(select_off, (struct obj *)); -STATIC_DCL struct obj *NDECL(do_takeoff); -STATIC_PTR int NDECL(take_off); -STATIC_DCL int FDECL(menu_remarm, (int)); -STATIC_DCL void FDECL(count_worn_stuff, (struct obj **, BOOLEAN_P)); -STATIC_PTR int FDECL(armor_or_accessory_off, (struct obj *)); -STATIC_PTR int FDECL(accessory_or_armor_on, (struct obj *)); -STATIC_DCL void FDECL(already_wearing, (const char *)); -STATIC_DCL void FDECL(already_wearing2, (const char *, const char *)); +staticfn void on_msg(struct obj *); +staticfn void toggle_stealth(struct obj *, long, boolean); +staticfn int Armor_on(void); +/* int Boots_on(void); -- moved to extern.h */ +staticfn int Cloak_on(void); +staticfn int Helmet_on(void); +staticfn int Gloves_on(void); +staticfn int Shield_on(void); +staticfn int Shirt_on(void); +staticfn void dragon_armor_handling(struct obj *, boolean, boolean); +staticfn void Amulet_on(struct obj *) NONNULLARG1; +staticfn void learnring(struct obj *, boolean); +staticfn void adjust_attrib(struct obj *, int, int); +staticfn void Ring_off_or_gone(struct obj *, boolean); +staticfn int select_off(struct obj *); +staticfn struct obj *do_takeoff(void); +staticfn int take_off(void); +staticfn int menu_remarm(int); +staticfn void wornarm_destroyed(struct obj *); +staticfn void count_worn_stuff(struct obj **, boolean); +staticfn int armor_or_accessory_off(struct obj *); +staticfn int accessory_or_armor_on(struct obj *); +staticfn void already_wearing(const char *); +staticfn void already_wearing2(const char *, const char *); +staticfn int equip_ok(struct obj *, boolean, boolean); +staticfn int puton_ok(struct obj *); +staticfn int remove_ok(struct obj *); +staticfn int wear_ok(struct obj *); +staticfn int takeoff_ok(struct obj *); +/* maybe_destroy_armor() may return NULL */ +staticfn struct obj *maybe_destroy_armor(struct obj *, struct obj *, + boolean *) NONNULLARG3; +staticfn int obj_erode_type(struct obj *) NONNULLARG1; +staticfn boolean better_not_take_that_off(struct obj *) NONNULLARG1; /* plural "fingers" or optionally "gloves" */ const char * -fingers_or_gloves(check_gloves) -boolean check_gloves; +fingers_or_gloves(boolean check_gloves) { return ((check_gloves && uarmg) ? gloves_simple_name(uarmg) /* "gloves" or "gauntlets" */ @@ -55,18 +65,25 @@ boolean check_gloves; } void -off_msg(otmp) -struct obj *otmp; +off_msg(struct obj *otmp) { if (flags.verbose) You("were wearing %s.", doname(otmp)); } /* for items that involve no delay */ -STATIC_OVL void -on_msg(otmp) -struct obj *otmp; +staticfn void +on_msg(struct obj *otmp) { + /* on_msg() for rings and amulets just shows add-to-invent feedback + [after caller calls setworn(), for suffix: "(on {left|right} hand)" + or "(being worn)"]; eyewear too unless giving verbose message below */ + if ((otmp->owornmask & (W_RING | W_AMUL)) != 0L + || ((otmp->owornmask & W_TOOL) != 0L && !flags.verbose)) { + prinv((char *) NULL, otmp, 0L); + return; + } + if (flags.verbose) { char how[BUFSZ]; /* call xname() before obj_is_pname(); formatting obj's name @@ -81,20 +98,18 @@ struct obj *otmp; } } -/* starting equipment gets auto-worn at beginning of new game, - and we don't want stealth or displacement feedback then */ -static boolean initial_don = FALSE; /* manipulated in set_wear() */ - /* putting on or taking off an item which confers stealth; - give feedback and discover it iff stealth state is changing */ -STATIC_OVL + give feedback and discover it iff stealth state is changing; + stealth is blocked by riding unless hero+steed fly (handled with + BStealth by mount and dismount routines) */ +staticfn void -toggle_stealth(obj, oldprop, on) -struct obj *obj; -long oldprop; /* prop[].extrinsic, with obj->owornmask stripped by caller */ -boolean on; +toggle_stealth( + struct obj *obj, + long oldprop, /* prop[].extrinsic, with obj->owornmask pre-stripped */ + boolean on) { - if (on ? initial_don : context.takeoff.cancelled_don) + if (on ? gi.initial_don : svc.context.takeoff.cancelled_don) return; if (!oldprop /* extrinsic stealth from something else */ @@ -102,7 +117,7 @@ boolean on; && !BStealth) { /* stealth blocked by something */ if (obj->otyp == RIN_STEALTH) learnring(obj, TRUE); - else + else /* discover elven cloak or elven boots */ makeknown(obj->otyp); if (on) { @@ -113,27 +128,35 @@ boolean on; else You("walk very quietly."); } else { - You("sure are noisy."); + boolean riding = (u.usteed != NULL); + + You("%s%s are noisy.", riding ? "and " : "sure", + riding ? x_monnam(u.usteed, ARTICLE_YOUR, (char *) NULL, + (SUPPRESS_SADDLE | SUPPRESS_HALLUCINATION), + FALSE) + : ""); } } } -/* putting on or taking off an item which confers displacement; +/* putting on or taking off an item which confers displacement, or gaining + or losing timed displacement after eating a displacer beast corpse or tin; give feedback and discover it iff displacement state is changing *and* - hero is able to see self (or sense monsters) */ -STATIC_OVL + hero is able to see self (or sense monsters); for timed, 'obj' is Null + and this is only called for the message */ void -toggle_displacement(obj, oldprop, on) -struct obj *obj; -long oldprop; /* prop[].extrinsic, with obj->owornmask stripped by caller */ -boolean on; +toggle_displacement( + struct obj *obj, + long oldprop, /* prop[].extrinsic, with obj->owornmask + stripped by caller */ + boolean on) { - if (on ? initial_don : context.takeoff.cancelled_don) + if (on ? gi.initial_don : svc.context.takeoff.cancelled_don) return; if (!oldprop /* extrinsic displacement from something else */ - && !(u.uprops[DISPLACED].intrinsic) /* (theoretical) */ - && !(u.uprops[DISPLACED].blocked) /* (also theoretical) */ + && !(u.uprops[DISPLACED].intrinsic) /* timed, from eating */ + && !(u.uprops[DISPLACED].blocked) /* (theoretical) */ /* we don't use canseeself() here because it augments vision with touch, which isn't appropriate for deciding whether we'll notice that monsters have trouble spotting the hero */ @@ -146,7 +169,8 @@ boolean on; || (Unblind_telepat || (Blind_telepat && Blind) || Detect_monsters))) { - makeknown(obj->otyp); + if (obj) + makeknown(obj->otyp); You_feel("that monsters%s have difficulty pinpointing your location.", on ? "" : " no longer"); @@ -160,7 +184,7 @@ boolean on; */ int -Boots_on(VOID_ARGS) +Boots_on(void) { long oldprop = u.uprops[objects[uarmf->otyp].oc_oprop].extrinsic & ~WORN_BOOTS; @@ -173,8 +197,22 @@ Boots_on(VOID_ARGS) case KICKING_BOOTS: break; case WATER_WALKING_BOOTS: + /* + * Sequencing issue? If underwater (perhaps via magical breathing), + * putting on water walking boots produces "you slowly rise above + * the surface" then "you finish your dressing maneuver". + */ + + /* spoteffects() doesn't get called here; pooleffects() is called + during movement and u.uinwater is already False after setworn() */ if (u.uinwater) spoteffects(TRUE); + /* init'd in accessory_or_armor_on() and only used here */ + if (gw.wasinwater) { + if (!u.uinwater) + makeknown(WATER_WALKING_BOOTS); + gw.wasinwater = 0U; + } /* (we don't need a lava check here since boots can't be put on while feet are stuck) */ break; @@ -200,7 +238,7 @@ Boots_on(VOID_ARGS) * so uarmf could be Null below; status line * gets updated during brief interval they're * worn so hero and player learn enchantment */ - context.botl = 1; /* status hilites might mark AC changed */ + disp.botl = TRUE; /* status hilites might mark AC changed */ makeknown(uarmf->otyp); float_up(); if (Levitation) @@ -212,26 +250,29 @@ Boots_on(VOID_ARGS) default: impossible(unknown_type, c_boots, uarmf->otyp); } - if (uarmf) /* could be Null here (levitation boots put on over a sink) */ + /* uarmf could be Null here (levitation boots put on over a sink) */ + if (uarmf && !uarmf->known) { uarmf->known = 1; /* boots' +/- evident because of status line AC */ + update_inventory(); + } return 0; } int -Boots_off(VOID_ARGS) +Boots_off(void) { struct obj *otmp = uarmf; int otyp = otmp->otyp; long oldprop = u.uprops[objects[otyp].oc_oprop].extrinsic & ~WORN_BOOTS; - context.takeoff.mask &= ~W_ARMF; + svc.context.takeoff.mask &= ~W_ARMF; /* For levitation, float_down() returns if Levitation, so we * must do a setworn() _before_ the levitation case. */ setworn((struct obj *) 0, W_ARMF); switch (otyp) { case SPEED_BOOTS: - if (!Very_fast && !context.takeoff.cancelled_don) { + if (!Very_fast && !svc.context.takeoff.cancelled_don) { makeknown(otyp); You_feel("yourself slow down%s.", Fast ? " a bit" : ""); } @@ -239,8 +280,9 @@ Boots_off(VOID_ARGS) case WATER_WALKING_BOOTS: /* check for lava since fireproofed boots make it viable */ if ((is_pool(u.ux, u.uy) || is_lava(u.ux, u.uy)) - && !Levitation && !Flying && !is_clinger(youmonst.data) - && !context.takeoff.cancelled_don + && !Levitation && !Flying + && !(is_clinger(gy.youmonst.data) && has_ceiling(&u.uz)) + && !svc.context.takeoff.cancelled_don /* avoid recursive call to lava_effects() */ && !iflags.in_lava_effects) { /* make boots known in case you survive the drowning */ @@ -257,8 +299,11 @@ Boots_off(VOID_ARGS) break; case LEVITATION_BOOTS: if (!oldprop && !HLevitation && !(BLevitation & FROMOUTSIDE) - && !context.takeoff.cancelled_don) { - (void) float_down(0L, 0L); + && !svc.context.takeoff.cancelled_don) { + /* lava_effects() sets in_lava_effects and calls Boots_off() + so hero is already in midst of floating down */ + if (!iflags.in_lava_effects) + (void) float_down(0L, 0L); makeknown(otyp); } else { float_vs_flight(); /* maybe toggle (BFlying & I_SPECIAL) */ @@ -273,12 +318,12 @@ Boots_off(VOID_ARGS) default: impossible(unknown_type, c_boots, otyp); } - context.takeoff.cancelled_don = FALSE; + svc.context.takeoff.cancelled_don = FALSE; return 0; } -STATIC_PTR int -Cloak_on(VOID_ARGS) +staticfn int +Cloak_on(void) { long oldprop = u.uprops[objects[uarmc->otyp].oc_oprop].extrinsic & ~WORN_CLOAK; @@ -327,19 +372,21 @@ Cloak_on(VOID_ARGS) default: impossible(unknown_type, c_cloak, uarmc->otyp); } - if (uarmc) /* no known instance of !uarmc here but play it safe */ + if (uarmc && !uarmc->known) { /* no known instance of !uarmc here */ uarmc->known = 1; /* cloak's +/- evident because of status line AC */ + update_inventory(); + } return 0; } int -Cloak_off(VOID_ARGS) +Cloak_off(void) { struct obj *otmp = uarmc; int otyp = otmp->otyp; long oldprop = u.uprops[objects[otyp].oc_oprop].extrinsic & ~WORN_CLOAK; - context.takeoff.mask &= ~W_ARMC; + svc.context.takeoff.mask &= ~W_ARMC; /* For mummy wrapping, taking it off first resets `Invisible'. */ setworn((struct obj *) 0, W_ARMC); switch (otyp) { @@ -383,12 +430,14 @@ Cloak_off(VOID_ARGS) return 0; } -STATIC_PTR -int -Helmet_on(VOID_ARGS) +staticfn int +Helmet_on(void) { switch (uarmh->otyp) { case FEDORA: + if (Role_if(PM_ARCHEOLOGIST)) + change_luck(1); + break; case HELMET: case DENTED_POT: case ELVEN_LEATHER_HELM: @@ -396,6 +445,9 @@ Helmet_on(VOID_ARGS) case ORCISH_HELM: case HELM_OF_TELEPATHY: break; + case HELM_OF_CAUTION: + see_monsters(); + break; case HELM_OF_BRILLIANCE: adj_abon(uarmh, uarmh->spe); break; @@ -404,7 +456,7 @@ Helmet_on(VOID_ARGS) but it takes trained arrogance to pull it off, and the actual enchantment of the hat is irrelevant */ ABON(A_CHA) += (Role_if(PM_WIZARD) ? 1 : -1); - context.botl = 1; + disp.botl = TRUE; makeknown(uarmh->otyp); break; case HELM_OF_OPPOSITE_ALIGNMENT: @@ -416,8 +468,9 @@ Helmet_on(VOID_ARGS) uchangealign((u.ualign.type != A_NEUTRAL) ? -u.ualign.type : (uarmh->o_id % 2) ? A_CHAOTIC : A_LAWFUL, - 1); + A_CG_HELM_ON); /* makeknown(HELM_OF_OPPOSITE_ALIGNMENT); -- below, after Tobjnam() */ + FALLTHROUGH; /*FALLTHRU*/ case DUNCE_CAP: if (uarmh && !uarmh->cursed) { @@ -427,8 +480,16 @@ Helmet_on(VOID_ARGS) pline("%s %s for a moment.", Tobjnam(uarmh, "glow"), hcolor(NH_BLACK)); curse(uarmh); + /* curse() doesn't touch bknown so doesn't update persistent + inventory; do so now [set_bknown() calls update_inventory()] */ + if (Blind) + set_bknown(uarmh, 0); /* lose bknown if previously set */ + else if (Role_if(PM_CLERIC)) + set_bknown(uarmh, 1); /* (bknown should already be set) */ + else if (uarmh->bknown) + update_inventory(); /* keep bknown as-is; display the curse */ } - context.botl = 1; /* reveal new alignment or INT & WIS */ + disp.botl = TRUE; /* reveal new alignment or INT & WIS */ if (Hallucination) { pline("My brain hurts!"); /* Monty Python's Flying Circus */ } else if (uarmh && uarmh->otyp == DUNCE_CAP) { @@ -446,18 +507,23 @@ Helmet_on(VOID_ARGS) impossible(unknown_type, c_helmet, uarmh->otyp); } /* uarmh could be Null due to uchangealign() */ - if (uarmh) + if (uarmh && !uarmh->known) { uarmh->known = 1; /* helmet's +/- evident because of status line AC */ + update_inventory(); + } return 0; } int -Helmet_off(VOID_ARGS) +Helmet_off(void) { - context.takeoff.mask &= ~W_ARMH; + svc.context.takeoff.mask &= ~W_ARMH; switch (uarmh->otyp) { case FEDORA: + if (Role_if(PM_ARCHEOLOGIST)) + change_luck(-1); + break; case HELMET: case DENTED_POT: case ELVEN_LEATHER_HELM: @@ -465,40 +531,49 @@ Helmet_off(VOID_ARGS) case ORCISH_HELM: break; case DUNCE_CAP: - context.botl = 1; + disp.botl = TRUE; break; case CORNUTHAUM: - if (!context.takeoff.cancelled_don) { + if (!svc.context.takeoff.cancelled_don) { ABON(A_CHA) += (Role_if(PM_WIZARD) ? -1 : 1); - context.botl = 1; + disp.botl = TRUE; } break; case HELM_OF_TELEPATHY: + case HELM_OF_CAUTION: /* need to update ability before calling see_monsters() */ setworn((struct obj *) 0, W_ARMH); see_monsters(); return 0; case HELM_OF_BRILLIANCE: - if (!context.takeoff.cancelled_don) + if (!svc.context.takeoff.cancelled_don) adj_abon(uarmh, -uarmh->spe); break; case HELM_OF_OPPOSITE_ALIGNMENT: /* changing alignment can toggle off active artifact properties, including levitation; uarmh could get dropped or destroyed here */ - uchangealign(u.ualignbase[A_CURRENT], 2); + uchangealign(u.ualignbase[A_CURRENT], A_CG_HELM_OFF); break; default: impossible(unknown_type, c_helmet, uarmh->otyp); } setworn((struct obj *) 0, W_ARMH); - context.takeoff.cancelled_don = FALSE; + svc.context.takeoff.cancelled_don = FALSE; return 0; } -STATIC_PTR -int -Gloves_on(VOID_ARGS) +/* hard helms provide better protection against falling rocks */ +boolean +hard_helmet(struct obj *obj) +{ + if (!obj || !is_helmet(obj)) + return FALSE; + return (is_metallic(obj) || is_crackable(obj)) ? TRUE : FALSE; +} + +staticfn int +Gloves_on(void) { long oldprop = u.uprops[objects[uarmg->otyp].oc_oprop].extrinsic & ~WORN_GLOVES; @@ -512,7 +587,7 @@ Gloves_on(VOID_ARGS) break; case GAUNTLETS_OF_POWER: makeknown(uarmg->otyp); - context.botl = 1; /* taken care of in attrib.c */ + disp.botl = TRUE; /* taken care of in attrib.c */ break; case GAUNTLETS_OF_DEXTERITY: adj_abon(uarmg, uarmg->spe); @@ -520,43 +595,62 @@ Gloves_on(VOID_ARGS) default: impossible(unknown_type, c_gloves, uarmg->otyp); } - if (uarmg) /* no known instance of !uarmg here but play it safe */ + if (!uarmg->known) { uarmg->known = 1; /* gloves' +/- evident because of status line AC */ + update_inventory(); + } return 0; } -STATIC_OVL void -wielding_corpse(obj, voluntary) -struct obj *obj; -boolean voluntary; /* taking gloves off on purpose? */ +/* check for wielding cockatrice corpse after taking off gloves or yellow + dragon scales/mail or having temporary stoning resistance time out */ +void +wielding_corpse( + struct obj *obj, /* uwep, potentially a wielded cockatrice corpse */ + struct obj *how, /* gloves or dragon armor or Null (resist timeout) */ + boolean voluntary) /* True: taking protective armor off on purpose */ { - char kbuf[BUFSZ]; - - if (!obj || obj->otyp != CORPSE) + if (!obj || obj->otyp != CORPSE || uarmg) return; + /* note: can't dual-wield with non-weapons/weapon-tools so u.twoweap + will always be false if uswapwep happens to be a corpse */ if (obj != uwep && (obj != uswapwep || !u.twoweap)) return; if (touch_petrifies(&mons[obj->corpsenm]) && !Stone_resistance) { - You("now wield %s in your bare %s.", + char kbuf[BUFSZ], hbuf[BUFSZ]; + + You("%s %s in your bare %s.", + (how && is_gloves(how)) ? "now wield" : "are wielding", corpse_xname(obj, (const char *) 0, CXN_ARTICLE), makeplural(body_part(HAND))); - Sprintf(kbuf, "%s gloves while wielding %s", - voluntary ? "removing" : "losing", killer_xname(obj)); + /* "removing" ought to be "taking off" but that makes the + tombstone text more likely to be truncated */ + if (how) + Sprintf(hbuf, "%s %s", voluntary ? "removing" : "losing", + is_gloves(how) ? gloves_simple_name(how) + : strsubst(simpleonames(how), "set of ", "")); + else + Strcpy(hbuf, "resistance timing out"); + Snprintf(kbuf, sizeof kbuf, "%s while wielding %s", + hbuf, killer_xname(obj)); instapetrify(kbuf); - /* life-saved; can't continue wielding cockatrice corpse though */ - remove_worn_item(obj, FALSE); + /* life-saved or got poly'd into a stone golem; can't continue + wielding cockatrice corpse unless have now become resistant */ + if (!Stone_resistance) + remove_worn_item(obj, FALSE); } } int -Gloves_off(VOID_ARGS) +Gloves_off(void) { + struct obj *gloves = uarmg; /* needed after uarmg has been set to Null */ long oldprop = u.uprops[objects[uarmg->otyp].oc_oprop].extrinsic & ~WORN_GLOVES; - boolean on_purpose = !context.mon_moving && !uarmg->in_use; + boolean on_purpose = !svc.context.mon_moving && !uarmg->in_use; - context.takeoff.mask &= ~W_ARMG; + svc.context.takeoff.mask &= ~W_ARMG; switch (uarmg->otyp) { case LEATHER_GLOVES: @@ -567,18 +661,18 @@ Gloves_off(VOID_ARGS) break; case GAUNTLETS_OF_POWER: makeknown(uarmg->otyp); - context.botl = 1; /* taken care of in attrib.c */ + disp.botl = TRUE; /* taken care of in attrib.c */ break; case GAUNTLETS_OF_DEXTERITY: - if (!context.takeoff.cancelled_don) + if (!svc.context.takeoff.cancelled_don) adj_abon(uarmg, -uarmg->spe); break; default: impossible(unknown_type, c_gloves, uarmg->otyp); } setworn((struct obj *) 0, W_ARMG); - context.takeoff.cancelled_don = FALSE; - (void) encumber_msg(); /* immediate feedback for GoP */ + svc.context.takeoff.cancelled_don = FALSE; + encumber_msg(); /* immediate feedback for GoP */ /* usually can't remove gloves when they're slippery but it can be done by having them fall off (polymorph), stolen, or @@ -590,26 +684,34 @@ Gloves_off(VOID_ARGS) /* prevent wielding cockatrice when not wearing gloves */ if (uwep && uwep->otyp == CORPSE) - wielding_corpse(uwep, on_purpose); - + wielding_corpse(uwep, gloves, on_purpose); /* KMH -- ...or your secondary weapon when you're wielding it - [This case can't actually happen; twoweapon mode won't - engage if a corpse has been set up as the alternate weapon.] */ + [This case can't actually happen; twoweapon mode won't engage + if a corpse has been set up as either the primary or alternate + weapon. If it could happen and /both/ uwep and uswapwep could + be cockatrice corpses, life-saving for the first would need to + prevent the second from being fatal since conceptually they'd + be being touched simultaneously.] */ if (u.twoweap && uswapwep && uswapwep->otyp == CORPSE) - wielding_corpse(uswapwep, on_purpose); + wielding_corpse(uswapwep, gloves, on_purpose); + + if (condtests[bl_bareh].enabled) + disp.botl = TRUE; return 0; } -STATIC_PTR int -Shield_on(VOID_ARGS) +staticfn int +Shield_on(void) { /* no shield currently requires special handling when put on, but we keep this uncommented in case somebody adds a new one which does - [reflection is handled by setting u.uprops[REFLECTION].extrinsic + [the magical shields are handled by setting u.uprops[*].extrinsic in setworn() called by armor_or_accessory_on() before Shield_on()] */ switch (uarms->otyp) { case SMALL_SHIELD: + case SHIELD_OF_DRAIN_RESISTANCE: + case SHIELD_OF_SHOCK_RESISTANCE: case ELVEN_SHIELD: case URUK_HAI_SHIELD: case ORCISH_SHIELD: @@ -620,20 +722,24 @@ Shield_on(VOID_ARGS) default: impossible(unknown_type, c_shield, uarms->otyp); } - if (uarms) /* no known instance of !uarmgs here but play it safe */ + if (!uarms->known) { uarms->known = 1; /* shield's +/- evident because of status line AC */ + update_inventory(); + } return 0; } int -Shield_off(VOID_ARGS) +Shield_off(void) { - context.takeoff.mask &= ~W_ARMS; + svc.context.takeoff.mask &= ~W_ARMS; /* no shield currently requires special handling when taken off, but we keep this uncommented in case somebody adds a new one which does */ switch (uarms->otyp) { case SMALL_SHIELD: + case SHIELD_OF_DRAIN_RESISTANCE: + case SHIELD_OF_SHOCK_RESISTANCE: case ELVEN_SHIELD: case URUK_HAI_SHIELD: case ORCISH_SHIELD: @@ -649,8 +755,8 @@ Shield_off(VOID_ARGS) return 0; } -STATIC_PTR int -Shirt_on(VOID_ARGS) +staticfn int +Shirt_on(void) { /* no shirt currently requires special handling when put on, but we keep this uncommented in case somebody adds a new one which does */ @@ -661,15 +767,17 @@ Shirt_on(VOID_ARGS) default: impossible(unknown_type, c_shirt, uarmu->otyp); } - if (uarmu) /* no known instances of !uarmu here but play it safe */ + if (!uarmu->known) { uarmu->known = 1; /* shirt's +/- evident because of status line AC */ + update_inventory(); + } return 0; } int -Shirt_off(VOID_ARGS) +Shirt_off(void) { - context.takeoff.mask &= ~W_ARMU; + svc.context.takeoff.mask &= ~W_ARMU; /* no shirt currently requires special handling when taken off, but we keep this uncommented in case somebody adds a new one which does */ @@ -685,26 +793,139 @@ Shirt_off(VOID_ARGS) return 0; } -STATIC_PTR -int -Armor_on(VOID_ARGS) +/* handle extra abilities for hero wearing dragon scale armor */ +staticfn void +dragon_armor_handling( + struct obj *otmp, /* armor being put on or taken off */ + boolean puton, /* True: on, False: off */ + boolean on_purpose) /* voluntary removal; not applicable for putting on */ { - /* - * No suits require special handling. Special properties conferred by - * suits are set up as intrinsics (actually 'extrinsics') by setworn() - * which is called by armor_or_accessory_on() before Armor_on(). - */ - if (uarm) /* no known instances of !uarm here but play it safe */ + if (!otmp) + return; + + switch (otmp->otyp) { + /* grey: no extra effect */ + /* silver: no extra effect */ + case BLACK_DRAGON_SCALES: + case BLACK_DRAGON_SCALE_MAIL: + if (puton) { + EDrain_resistance |= W_ARM; + } else { + EDrain_resistance &= ~W_ARM; + } + break; + case BLUE_DRAGON_SCALES: + case BLUE_DRAGON_SCALE_MAIL: + if (puton) { + if (!Very_fast) + You("speed up%s.", Fast ? " a bit more" : ""); + EFast |= W_ARM; + } else { + EFast &= ~W_ARM; + if (!Very_fast && !svc.context.takeoff.cancelled_don) + You("slow down."); + } + break; + case GREEN_DRAGON_SCALES: + case GREEN_DRAGON_SCALE_MAIL: + if (puton) { + ESick_resistance |= W_ARM; + } else { + ESick_resistance &= ~W_ARM; + } + break; + case RED_DRAGON_SCALES: + case RED_DRAGON_SCALE_MAIL: + if (puton) { + EInfravision |= W_ARM; + } else { + EInfravision &= ~W_ARM; + } + see_monsters(); + break; + case GOLD_DRAGON_SCALES: + case GOLD_DRAGON_SCALE_MAIL: + (void) make_hallucinated((long) !puton, + program_state.restoring ? FALSE : TRUE, + W_ARM); + break; + case ORANGE_DRAGON_SCALES: + case ORANGE_DRAGON_SCALE_MAIL: + if (puton) { + Free_action |= W_ARM; + } else { + Free_action &= ~W_ARM; + } + break; + case YELLOW_DRAGON_SCALES: + case YELLOW_DRAGON_SCALE_MAIL: + if (puton) { + EStone_resistance |= W_ARM; + } else { + EStone_resistance &= ~W_ARM; + + /* prevent wielding cockatrice after losing stoning resistance + when not wearing gloves; the uswapwep case is always a no-op */ + wielding_corpse(uwep, otmp, on_purpose); + wielding_corpse(uswapwep, otmp, on_purpose); + } + break; + case WHITE_DRAGON_SCALES: + case WHITE_DRAGON_SCALE_MAIL: + if (puton) { + ESlow_digestion |= W_ARM; + } else { + ESlow_digestion &= ~W_ARM; + } + break; + default: + break; + } +} + +staticfn int +Armor_on(void) +{ + if (!uarm) /* no known instances of !uarm here but play it safe */ + return 0; + if (!uarm->known) { uarm->known = 1; /* suit's +/- evident because of status line AC */ + update_inventory(); + } + dragon_armor_handling(uarm, TRUE, TRUE); + /* gold DSM requires extra handling since it emits light when worn; + do that after the special armor handling */ + if (artifact_light(uarm) && !uarm->lamplit) { + begin_burn(uarm, FALSE); + if (!Blind) + pline("%s %s to shine %s!", + Yname2(uarm), otense(uarm, "begin"), + arti_light_description(uarm)); + } return 0; } int -Armor_off(VOID_ARGS) +Armor_off(void) { - context.takeoff.mask &= ~W_ARM; + struct obj *otmp = uarm; + boolean was_arti_light = otmp && otmp->lamplit && artifact_light(otmp); + + svc.context.takeoff.mask &= ~W_ARM; setworn((struct obj *) 0, W_ARM); - context.takeoff.cancelled_don = FALSE; + svc.context.takeoff.cancelled_don = FALSE; + + /* taking off yellow dragon scales/mail might be fatal; arti_light + comes from gold dragon scales/mail so they don't overlap, but + conceptually the non-fatal change should be done before the + potentially fatal change in case the latter results in bones */ + if (was_arti_light && !artifact_light(otmp)) { + end_burn(otmp, FALSE); + if (!Blind) + pline("%s shining.", Tobjnam(otmp, "stop")); + } + dragon_armor_handling(otmp, FALSE, TRUE); + return 0; } @@ -715,94 +936,173 @@ Armor_off(VOID_ARGS) * repeating.] */ int -Armor_gone() +Armor_gone(void) { - context.takeoff.mask &= ~W_ARM; + struct obj *otmp = uarm; + boolean was_arti_light = otmp && otmp->lamplit && artifact_light(otmp); + + svc.context.takeoff.mask &= ~W_ARM; setnotworn(uarm); - context.takeoff.cancelled_don = FALSE; + svc.context.takeoff.cancelled_don = FALSE; + + /* losing yellow dragon scales/mail might be fatal; arti_light + comes from gold dragon scales/mail so they don't overlap, but + conceptually the non-fatal change should be done before the + potentially fatal change in case the latter results in bones */ + if (was_arti_light && !artifact_light(otmp)) { + end_burn(otmp, FALSE); + if (!Blind) + pline("%s shining.", Tobjnam(otmp, "stop")); + } + dragon_armor_handling(otmp, FALSE, FALSE); + return 0; } -STATIC_OVL void -Amulet_on() +staticfn void +Amulet_on(struct obj *amul) { - /* make sure amulet isn't wielded; can't use remove_worn_item() - here because it has already been set worn in amulet slot */ - if (uamul == uwep) - setuwep((struct obj *) 0); - else if (uamul == uswapwep) - setuswapwep((struct obj *) 0); - else if (uamul == uquiver) - setuqwep((struct obj *) 0); + boolean on_msg_done = FALSE; + + /* make sure amulet isn't wielded/alt-wielded/quivered, before wearing */ + remove_worn_item(amul, FALSE); + setworn(amul, W_AMUL); switch (uamul->otyp) { case AMULET_OF_ESP: case AMULET_OF_LIFE_SAVING: case AMULET_VERSUS_POISON: case AMULET_OF_REFLECTION: - case AMULET_OF_MAGICAL_BREATHING: case FAKE_AMULET_OF_YENDOR: break; + case AMULET_OF_MAGICAL_BREATHING: { + boolean was_in_poison_gas; + + /* amulet is already on; we need to check hero's gas-cloud status + when it was off */ + EMagical_breathing &= ~W_AMUL; + was_in_poison_gas = region_danger(); + EMagical_breathing |= W_AMUL; + if (was_in_poison_gas) { + makeknown(AMULET_OF_MAGICAL_BREATHING); + on_msg(uamul); + on_msg_done = TRUE; + You("are no longer bothered by the poison gas."); + } + /* no need to check for becoming able to breathe underwater; + if we are underwater, we already can or we would have drowned */ + break; + } case AMULET_OF_UNCHANGING: if (Slimed) make_slimed(0L, (char *) 0); break; case AMULET_OF_CHANGE: { - int orig_sex = poly_gender(); + boolean call_it = FALSE; + int new_sex, orig_sex = poly_gender(); - if (Unchanging) - break; - change_sex(); - /* Don't use same message as polymorph */ - if (orig_sex != poly_gender()) { + /* in normal play it's not possible to put on an amulet of change + while already wearing an amulet of unchanging, but in wizard + mode the Unchanging attribute can be set via #wizintrinsic */ + if (!Unchanging) + change_sex(); + + new_sex = poly_gender(); + if (new_sex != orig_sex) makeknown(AMULET_OF_CHANGE); + on_msg(uamul); /* show 'z - amulet of change (being worn)' */ + on_msg_done = TRUE; + + /* Don't use same message as polymorph */ + if (new_sex != orig_sex) { + newsym(u.ux, u.uy); /* glyphmon flag and tile have changed */ + disp.botl = TRUE; /* role name or rank title might have changed */ You("are suddenly very %s!", flags.female ? "feminine" : "masculine"); - context.botl = 1; - } else + } else { /* already polymorphed into single-gender monster; only changed the character's base sex */ You("don't feel like yourself."); + /* checking dknown is redundant--amulets always have dknown set */ + call_it = (uamul->dknown != 0); + } + livelog_newform(FALSE, orig_sex, new_sex); pline_The("amulet disintegrates!"); - if (orig_sex == poly_gender() && uamul->dknown - && !objects[AMULET_OF_CHANGE].oc_name_known - && !objects[AMULET_OF_CHANGE].oc_uname) - docall(uamul); + if (call_it) + trycall(uamul); useup(uamul); break; } case AMULET_OF_STRANGULATION: - if (can_be_strangled(&youmonst)) { + /* note: might already be Strangled (via #wizintrinsic) */ + if (can_be_strangled(&gy.youmonst) && !Strangled) { makeknown(AMULET_OF_STRANGULATION); Strangled = 6L; - context.botl = TRUE; + disp.botl = TRUE; + on_msg(uamul); + on_msg_done = TRUE; pline("It constricts your throat!"); } break; case AMULET_OF_RESTFUL_SLEEP: { - long newnap = (long) rnd(100), oldnap = (HSleepy & TIMEOUT); + long newnap = (long) rnd(98) + 2L, oldnap = (HSleepy & TIMEOUT); - /* avoid clobbering FROMOUTSIDE bit, which might have - gotten set by previously eating one of these amulets */ if (newnap < oldnap || oldnap == 0L) + /* avoid clobbering FROMOUTSIDE bit, which might have + gotten set by previously eating one of these amulets */ HSleepy = (HSleepy & ~TIMEOUT) | newnap; - } break; + break; + } + case AMULET_OF_FLYING: + /* setworn() has already set extrinsic flying */ + float_vs_flight(); /* block flying if levitating */ + if (Flying) { + boolean already_flying; + + /* to determine whether this flight is new we have to muck + about in the Flying intrinsic (actually extrinsic) */ + EFlying &= ~W_AMUL; + already_flying = !!Flying; + EFlying |= W_AMUL; + + if (!already_flying) { + makeknown(AMULET_OF_FLYING); + on_msg(uamul); + on_msg_done = TRUE; + disp.botl = TRUE; /* status: 'Fly' On */ + You("are now in flight."); + } + } + break; + case AMULET_OF_GUARDING: + makeknown(AMULET_OF_GUARDING); + find_ac(); + break; case AMULET_OF_YENDOR: break; } + + if (!on_msg_done) + on_msg(uamul); } void -Amulet_off() +Amulet_off(void) { - context.takeoff.mask &= ~W_AMUL; + struct obj *amul = uamul; /* for off_msg() after setworn(NULL,W_AMUL) */ + boolean mkn = FALSE, early_off_msg = FALSE; + + svc.context.takeoff.mask &= ~W_AMUL; switch (uamul->otyp) { case AMULET_OF_ESP: /* need to update ability before calling see_monsters() */ setworn((struct obj *) 0, W_AMUL); + off_msg(amul); + early_off_msg = TRUE; + see_monsters(); - return; + break; case AMULET_OF_LIFE_SAVING: case AMULET_VERSUS_POISON: case AMULET_OF_REFLECTION: @@ -811,27 +1111,39 @@ Amulet_off() case FAKE_AMULET_OF_YENDOR: break; case AMULET_OF_MAGICAL_BREATHING: + /* amulet is currently still on; take it off before calling drown() + and region_danger(); call off_msg() before specific messages */ + setworn((struct obj *) 0, W_AMUL); + off_msg(amul); /* 'uamul' has been set to Null */ + early_off_msg = TRUE; + if (Underwater) { - /* HMagical_breathing must be set off - before calling drown() */ - setworn((struct obj *) 0, W_AMUL); - if (!breathless(youmonst.data) && !amphibious(youmonst.data) - && !Swimming) { + if (!cant_drown(gy.youmonst.data) && !Swimming) { You("suddenly inhale an unhealthy amount of %s!", hliquid("water")); + mkn = TRUE; /* in case of life-saving */ (void) drown(); } - return; + } + if (region_danger()) { + /* "breathing": wouldn't get here otherwise */ + You("are breathing poison gas!"); + mkn = TRUE; } break; case AMULET_OF_STRANGULATION: + setworn((struct obj *) 0, W_AMUL); + off_msg(amul); + early_off_msg = TRUE; + if (Strangled) { Strangled = 0L; - context.botl = TRUE; + disp.botl = TRUE; if (Breathless) Your("%s is no longer constricted!", body_part(NECK)); else You("can breathe more easily!"); + mkn = TRUE; } break; case AMULET_OF_RESTFUL_SLEEP: @@ -839,35 +1151,62 @@ Amulet_off() /* HSleepy = 0L; -- avoid clobbering FROMOUTSIDE bit */ if (!ESleepy && !(HSleepy & ~TIMEOUT)) HSleepy &= ~TIMEOUT; /* clear timeout bits */ - return; + break; + case AMULET_OF_FLYING: { + boolean was_flying = !!Flying; + + /* remove amulet 'early' to determine whether Flying changes; + also in case spoteffects() does something with the amulet */ + setworn((struct obj *) 0, W_AMUL); + off_msg(amul); + early_off_msg = TRUE; + + float_vs_flight(); /* probably not needed here */ + if (was_flying && !Flying) { + disp.botl = TRUE; /* status: 'Fly' Off */ + You("%s.", (is_pool_or_lava(u.ux, u.uy) + || Is_waterlevel(&u.uz) || Is_airlevel(&u.uz)) + ? "stop flying" + : "land"); + mkn = TRUE; /* makeknown(AMULET_OF_FLYING) */ + spoteffects(TRUE); + } + break; + } + case AMULET_OF_GUARDING: + find_ac(); + break; case AMULET_OF_YENDOR: break; } + setworn((struct obj *) 0, W_AMUL); + if (!early_off_msg) + off_msg(amul); /* (not 'uamul'; it's Null now) */ + if (mkn) + makeknown(amul->otyp); return; } /* handle ring discovery; comparable to learnwand() */ -STATIC_OVL void -learnring(ring, observed) -struct obj *ring; -boolean observed; +staticfn void +learnring(struct obj *ring, boolean observed) { int ringtype = ring->otyp; - /* if effect was observeable then we usually discover the type */ + /* if effect was observable then we usually discover the type */ if (observed) { /* if we already know the ring type which accomplishes this effect (assumes there is at most one type for each effect), mark this ring as having been seen (no need for makeknown); otherwise if we have seen this ring, discover its type */ if (objects[ringtype].oc_name_known) - ring->dknown = 1; + observe_object(ring); else if (ring->dknown) makeknown(ringtype); #if 0 /* see learnwand() */ else - ring->eknown = 1; + observe_object(ring); #endif } @@ -880,12 +1219,29 @@ boolean observed; } } +staticfn void +adjust_attrib(struct obj *obj, int which, int val) +{ + int old_attrib; + boolean observable; + + old_attrib = ACURR(which); + ABON(which) += val; + observable = (old_attrib != ACURR(which)); + /* if didn't change, usually means ring is +0 but might + be because nonzero couldn't go below min or above max; + learn +0 enchantment if attribute value is not stuck + at a limit [and ring has been seen and its type is + already discovered, both handled by learnring()] */ + if (observable || !extremeattr(which)) + learnring(obj, observable); + disp.botl = TRUE; +} + void -Ring_on(obj) -register struct obj *obj; +Ring_on(struct obj *obj) { long oldprop = u.uprops[objects[obj->otyp].oc_oprop].extrinsic; - int old_attrib, which; boolean observable; /* make sure ring isn't wielded; can't use remove_worn_item() @@ -919,7 +1275,9 @@ register struct obj *obj; case RIN_FREE_ACTION: case RIN_SLOW_DIGESTION: case RIN_SUSTAIN_ABILITY: + break; case MEAT_RING: + /* wearing a meat ring does not affect vegan conduct */ break; case RIN_STEALTH: toggle_stealth(obj, oldprop, TRUE); @@ -956,25 +1314,13 @@ register struct obj *obj; } break; case RIN_GAIN_STRENGTH: - which = A_STR; - goto adjust_attrib; + adjust_attrib(obj, A_STR, obj->spe); + break; case RIN_GAIN_CONSTITUTION: - which = A_CON; - goto adjust_attrib; + adjust_attrib(obj, A_CON, obj->spe); + break; case RIN_ADORNMENT: - which = A_CHA; - adjust_attrib: - old_attrib = ACURR(which); - ABON(which) += obj->spe; - observable = (old_attrib != ACURR(which)); - /* if didn't change, usually means ring is +0 but might - be because nonzero couldn't go below min or above max; - learn +0 enchantment if attribute value is not stuck - at a limit [and ring has been seen and its type is - already discovered, both handled by learnring()] */ - if (observable || !extremeattr(which)) - learnring(obj, observable); - context.botl = 1; + adjust_attrib(obj, A_CHA, obj->spe); break; case RIN_INCREASE_ACCURACY: /* KMH */ u.uhitinc += obj->spe; @@ -997,16 +1343,13 @@ register struct obj *obj; } } -STATIC_OVL void -Ring_off_or_gone(obj, gone) -register struct obj *obj; -boolean gone; +staticfn void +Ring_off_or_gone(struct obj *obj, boolean gone) { long mask = (obj->owornmask & W_RING); - int old_attrib, which; boolean observable; - context.takeoff.mask &= ~mask; + svc.context.takeoff.mask &= ~mask; if (!(u.uprops[objects[obj->otyp].oc_oprop].extrinsic & mask)) impossible("Strange... I didn't know you had that ring."); if (gone) @@ -1070,21 +1413,13 @@ boolean gone; } break; case RIN_GAIN_STRENGTH: - which = A_STR; - goto adjust_attrib; + adjust_attrib(obj, A_STR, -obj->spe); + break; case RIN_GAIN_CONSTITUTION: - which = A_CON; - goto adjust_attrib; + adjust_attrib(obj, A_CON, -obj->spe); + break; case RIN_ADORNMENT: - which = A_CHA; - adjust_attrib: - old_attrib = ACURR(which); - ABON(which) -= obj->spe; - observable = (old_attrib != ACURR(which)); - /* same criteria as Ring_on() */ - if (observable || !extremeattr(which)) - learnring(obj, observable); - context.botl = 1; + adjust_attrib(obj, A_CHA, -obj->spe); break; case RIN_INCREASE_ACCURACY: /* KMH */ u.uhitinc -= obj->spe; @@ -1101,37 +1436,34 @@ boolean gone; find_ac(); /* updates botl */ break; case RIN_PROTECTION_FROM_SHAPE_CHAN: - /* If you're no longer protected, let the chameleons - * change shape again -dgk - */ - restartcham(); + /* if you're no longer protected, let the chameleons change + shape again; however, might still be protected if wearing + 2nd ring of this type (or via #wizintrinsic) */ + if (!Protection_from_shape_changers) + restartcham(); break; } } void -Ring_off(obj) -struct obj *obj; +Ring_off(struct obj *obj) { Ring_off_or_gone(obj, FALSE); } void -Ring_gone(obj) -struct obj *obj; +Ring_gone(struct obj *obj) { Ring_off_or_gone(obj, TRUE); } void -Blindf_on(otmp) -struct obj *otmp; +Blindf_on(struct obj *otmp) { boolean already_blind = Blind, changed = FALSE; /* blindfold might be wielded; release it for wearing */ - if (otmp->owornmask & W_WEAPONS) - remove_worn_item(otmp, FALSE); + remove_worn_item(otmp, FALSE); setworn(otmp, W_TOOL); on_msg(otmp); @@ -1160,18 +1492,21 @@ struct obj *otmp; } void -Blindf_off(otmp) -struct obj *otmp; +Blindf_off(struct obj *otmp) { - boolean was_blind = Blind, changed = FALSE; + boolean was_blind = Blind, changed = FALSE, + nooffmsg = !otmp; + if (!otmp) + otmp = ublindf; if (!otmp) { - impossible("Blindf_off without otmp"); + impossible("Blindf_off without eyewear?"); return; } - context.takeoff.mask &= ~W_TOOL; + svc.context.takeoff.mask &= ~W_TOOL; setworn((struct obj *) 0, otmp->owornmask); - off_msg(otmp); + if (!nooffmsg) + off_msg(otmp); if (Blind) { if (was_blind) { @@ -1201,10 +1536,10 @@ struct obj *otmp; /* called in moveloop()'s prologue to set side-effects of worn start-up items; also used by poly_obj() when a worn item gets transformed */ void -set_wear(obj) -struct obj *obj; /* if null, do all worn items; otherwise just obj itself */ +set_wear( + struct obj *obj) /* if Null, do all worn items; otherwise just obj */ { - initial_don = !obj; + gi.initial_don = !obj; if (!obj ? ublindf != 0 : (obj == ublindf)) (void) Blindf_on(ublindf); @@ -1213,7 +1548,7 @@ struct obj *obj; /* if null, do all worn items; otherwise just obj itself */ if (!obj ? uleft != 0 : (obj == uleft)) (void) Ring_on(uleft); if (!obj ? uamul != 0 : (obj == uamul)) - (void) Amulet_on(); + (void) Amulet_on(uamul); if (!obj ? uarmu != 0 : (obj == uarmu)) (void) Shirt_on(); @@ -1230,34 +1565,33 @@ struct obj *obj; /* if null, do all worn items; otherwise just obj itself */ if (!obj ? uarms != 0 : (obj == uarms)) (void) Shield_on(); - initial_don = FALSE; + gi.initial_don = FALSE; } /* check whether the target object is currently being put on (or taken off-- also checks for doffing--[why?]) */ boolean -donning(otmp) -struct obj *otmp; +donning(struct obj *otmp) { boolean result = FALSE; - /* 'W' (or 'P' used for armor) sets afternmv */ + /* 'W' (or 'P' used for armor) sets ga.afternmv */ if (doffing(otmp)) result = TRUE; else if (otmp == uarm) - result = (afternmv == Armor_on); + result = (ga.afternmv == Armor_on); else if (otmp == uarmu) - result = (afternmv == Shirt_on); + result = (ga.afternmv == Shirt_on); else if (otmp == uarmc) - result = (afternmv == Cloak_on); + result = (ga.afternmv == Cloak_on); else if (otmp == uarmf) - result = (afternmv == Boots_on); + result = (ga.afternmv == Boots_on); else if (otmp == uarmh) - result = (afternmv == Helmet_on); + result = (ga.afternmv == Helmet_on); else if (otmp == uarmg) - result = (afternmv == Gloves_on); + result = (ga.afternmv == Gloves_on); else if (otmp == uarms) - result = (afternmv == Shield_on); + result = (ga.afternmv == Shield_on); return result; } @@ -1266,28 +1600,27 @@ struct obj *otmp; so that stop_donning() and steal() can vary messages and doname() can vary "(being worn)" suffix */ boolean -doffing(otmp) -struct obj *otmp; +doffing(struct obj *otmp) { - long what = context.takeoff.what; + long what = svc.context.takeoff.what; boolean result = FALSE; - /* 'T' (or 'R' used for armor) sets afternmv, 'A' sets takeoff.what */ + /* 'T' (or 'R' used for armor) sets ga.afternmv, 'A' sets takeoff.what */ if (otmp == uarm) - result = (afternmv == Armor_off || what == WORN_ARMOR); + result = (ga.afternmv == Armor_off || what == WORN_ARMOR); else if (otmp == uarmu) - result = (afternmv == Shirt_off || what == WORN_SHIRT); + result = (ga.afternmv == Shirt_off || what == WORN_SHIRT); else if (otmp == uarmc) - result = (afternmv == Cloak_off || what == WORN_CLOAK); + result = (ga.afternmv == Cloak_off || what == WORN_CLOAK); else if (otmp == uarmf) - result = (afternmv == Boots_off || what == WORN_BOOTS); + result = (ga.afternmv == Boots_off || what == WORN_BOOTS); else if (otmp == uarmh) - result = (afternmv == Helmet_off || what == WORN_HELMET); + result = (ga.afternmv == Helmet_off || what == WORN_HELMET); else if (otmp == uarmg) - result = (afternmv == Gloves_off || what == WORN_GLOVES); + result = (ga.afternmv == Gloves_off || what == WORN_GLOVES); else if (otmp == uarms) - result = (afternmv == Shield_off || what == WORN_SHIELD); - /* these 1-turn items don't need 'afternmv' checks */ + result = (ga.afternmv == Shield_off || what == WORN_SHIELD); + /* these 1-turn items don't need 'ga.afternmv' checks */ else if (otmp == uamul) result = (what == WORN_AMUL); else if (otmp == uleft) @@ -1309,54 +1642,58 @@ struct obj *otmp; /* despite their names, cancel_don() and cancel_doff() both apply to both donning and doffing... */ void -cancel_doff(obj, slotmask) -struct obj *obj; -long slotmask; +cancel_doff(struct obj *obj, long slotmask) { /* Called by setworn() for old item in specified slot or by setnotworn() * for specified item. We don't want to call cancel_don() if we got - * here via _off() -> setworn((struct obj *)0) -> cancel_doff() + * here via _off() -> setworn((struct obj *) 0) -> cancel_doff() * because that would stop the 'A' command from continuing with next * selected item. So do_takeoff() sets a flag in takeoff.mask for us. * [For taking off an individual item with 'T'/'R'/'w-', it doesn't * matter whether cancel_don() gets called here--the item has already * been removed by now.] */ - if (!(context.takeoff.mask & I_SPECIAL) && donning(obj)) + if (!(svc.context.takeoff.mask & I_SPECIAL) && donning(obj)) cancel_don(); /* applies to doffing too */ - context.takeoff.mask &= ~slotmask; + svc.context.takeoff.mask &= ~slotmask; } /* despite their names, cancel_don() and cancel_doff() both apply to both donning and doffing... */ void -cancel_don() +cancel_don(void) { /* the piece of armor we were donning/doffing has vanished, so stop * wasting time on it (and don't dereference it when donning would - * otherwise finish) + * otherwise finish); afternmv never has some of these values because + * every item of the corresponding armor category takes 1 turn to wear, + * but check all of them anyway */ - context.takeoff.cancelled_don = - (afternmv == Boots_on || afternmv == Helmet_on - || afternmv == Gloves_on || afternmv == Armor_on); - afternmv = (int NDECL((*))) 0; - nomovemsg = (char *) 0; - multi = 0; - context.takeoff.delay = 0; - context.takeoff.what = 0L; + svc.context.takeoff.cancelled_don = (ga.afternmv == Cloak_on + || ga.afternmv == Armor_on + || ga.afternmv == Shirt_on + || ga.afternmv == Helmet_on + || ga.afternmv == Gloves_on + || ga.afternmv == Boots_on + || ga.afternmv == Shield_on); + ga.afternmv = (int (*)(void)) 0; + gn.nomovemsg = (char *) 0; + gm.multi = 0; + svc.context.takeoff.delay = 0; + svc.context.takeoff.what = 0L; } /* called by steal() during theft from hero; interrupt donning/doffing */ int -stop_donning(stolenobj) -struct obj *stolenobj; /* no message if stolenobj is already being doffing */ +stop_donning( + struct obj *stolenobj) /* no mesg if stolenobj is already being doffed */ { char buf[BUFSZ]; struct obj *otmp; boolean putting_on; int result = 0; - for (otmp = invent; otmp; otmp = otmp->nobj) + for (otmp = gi.invent; otmp; otmp = otmp->nobj) if ((otmp->owornmask & W_ARMOR) && donning(otmp)) break; /* at most one item will pass donning() test at any given time */ @@ -1369,14 +1706,14 @@ struct obj *stolenobj; /* no message if stolenobj is already being doffing */ cancel_don(); /* don't want _on() or _off() being called by unmul() since the on or off action isn't completing */ - afternmv = (int NDECL((*))) 0; + ga.afternmv = (int (*)(void)) 0; if (putting_on || otmp != stolenobj) { Sprintf(buf, "You stop %s %s.", putting_on ? "putting on" : "taking off", thesimpleoname(otmp)); } else { buf[0] = '\0'; /* silently stop doffing stolenobj */ - result = -multi; /* remember this before calling unmul() */ + result = (int) -gm.multi; /* remember this before calling unmul() */ } unmul(buf); /* while putting on, item becomes worn immediately but side-effects are @@ -1389,21 +1726,13 @@ struct obj *stolenobj; /* no message if stolenobj is already being doffing */ return result; } -/* both 'clothes' and 'accessories' now include both armor and accessories; - TOOL_CLASS is for eyewear, FOOD_CLASS is for MEAT_RING */ -static NEARDATA const char clothes[] = { - ARMOR_CLASS, RING_CLASS, AMULET_CLASS, TOOL_CLASS, FOOD_CLASS, 0 -}; -static NEARDATA const char accessories[] = { - RING_CLASS, AMULET_CLASS, TOOL_CLASS, FOOD_CLASS, ARMOR_CLASS, 0 -}; -STATIC_VAR NEARDATA int Narmorpieces, Naccessories; +static NEARDATA int Narmorpieces, Naccessories; /* assign values to Narmorpieces and Naccessories */ -STATIC_OVL void -count_worn_stuff(which, accessorizing) -struct obj **which; /* caller wants this when count is 1 */ -boolean accessorizing; +staticfn void +count_worn_stuff( + struct obj **which, /* caller wants this when count is 1 */ + boolean accessorizing) { struct obj *otmp; @@ -1438,13 +1767,12 @@ boolean accessorizing; /* take off one piece or armor or one accessory; shared by dotakeoff('T') and doremring('R') */ -STATIC_OVL int -armor_or_accessory_off(obj) -struct obj *obj; +staticfn int +armor_or_accessory_off(struct obj *obj) { if (!(obj->owornmask & (W_ARMOR | W_ACCESSORY))) { You("are not wearing that."); - return 0; + return ECMD_OK; } if (obj == uskin || ((obj == uarm) && uarmc) @@ -1460,18 +1788,19 @@ struct obj *obj; Strcat(what, " and "); Strcat(what, suit_simple_name(uarm)); } - Sprintf(why, " without taking off your %s first", what); + Snprintf(why, sizeof why, " without taking off your %s first", + what); } else { Strcpy(why, "; it's embedded"); } You_cant("take that off%s.", why); - return 0; + return ECMD_OK; } reset_remarm(); /* clear context.takeoff.mask and context.takeoff.what */ (void) select_off(obj); - if (!context.takeoff.mask) - return 0; + if (!svc.context.takeoff.mask) + return ECMD_OK; /* none of armoroff()/Ring_/Amulet/Blindf_off() use context.takeoff.mask */ reset_remarm(); @@ -1487,21 +1816,21 @@ struct obj *obj; off_msg(obj); Ring_off(obj); } else if (obj == uamul) { - Amulet_off(); - off_msg(obj); + Amulet_off(); /* does its own off_msg */ } else if (obj == ublindf) { Blindf_off(obj); /* does its own off_msg */ } else { - impossible("removing strange accessory?"); + impossible("removing strange accessory: %s", + safe_typename(obj->otyp)); if (obj->owornmask) remove_worn_item(obj, FALSE); } - return 1; + return ECMD_TIME; } -/* the 'T' command */ +/* the #takeoff command - remove worn armor */ int -dotakeoff() +dotakeoff(void) { struct obj *otmp = (struct obj *) 0; @@ -1515,39 +1844,53 @@ dotakeoff() : "dragon scale mail is"); else pline("Not wearing any armor or accessories."); - return 0; + return ECMD_OK; } - if (Narmorpieces != 1 || ParanoidRemove) - otmp = getobj(clothes, "take off"); + if (Narmorpieces != 1 || ParanoidRemove || gi.item_action_in_progress) + otmp = getobj("take off", takeoff_ok, GETOBJ_NOFLAGS); if (!otmp) - return 0; + return ECMD_CANCEL; return armor_or_accessory_off(otmp); } -/* the 'R' command */ +/* 'i' or 'I[' followed by and then 'T'; + plain dotakeoff() would not give any feedback when picking suit + covered by cloak, or shirt covered by suit and/or cloak, due to the + default behavior of equip_ok() (skipping inaccessible items) */ +int +ia_dotakeoff(void) +{ + int res; + + gi.item_action_in_progress = TRUE; + res = dotakeoff(); + gi.item_action_in_progress = FALSE; + return res; +} + +/* the #remove command - take off ring or other accessory */ int -doremring() +doremring(void) { struct obj *otmp = 0; count_worn_stuff(&otmp, TRUE); if (!Naccessories && !Narmorpieces) { pline("Not wearing any accessories or armor."); - return 0; + return ECMD_OK; } - if (Naccessories != 1 || ParanoidRemove) - otmp = getobj(accessories, "remove"); + if (Naccessories != 1 || ParanoidRemove || cmdq_peek(CQ_CANNED)) + otmp = getobj("remove", remove_ok, GETOBJ_NOFLAGS); if (!otmp) - return 0; + return ECMD_CANCEL; return armor_or_accessory_off(otmp); } /* Check if something worn is cursed _and_ unremovable. */ int -cursed(otmp) -struct obj *otmp; +cursed(struct obj *otmp) { if (!otmp) { impossible("cursed without otmp"); @@ -1574,8 +1917,7 @@ struct obj *otmp; } int -armoroff(otmp) -struct obj *otmp; +armoroff(struct obj *otmp) { static char offdelaybuf[60]; int delay = -objects[otmp->otyp].oc_delay; @@ -1587,87 +1929,92 @@ struct obj *otmp; delays and which didn't; now both are handled for all types */ if (delay) { nomul(delay); - multi_reason = "disrobing"; - if (is_helmet(otmp)) { + gm.multi_reason = "disrobing"; + switch (objects[otmp->otyp].oc_armcat) { + case ARM_SUIT: + what = suit_simple_name(otmp); + ga.afternmv = Armor_off; + break; + case ARM_SHIELD: + what = shield_simple_name(otmp); + ga.afternmv = Shield_off; + break; + case ARM_HELM: what = helm_simple_name(otmp); - afternmv = Helmet_off; - } else if (is_gloves(otmp)) { + ga.afternmv = Helmet_off; + break; + case ARM_GLOVES: what = gloves_simple_name(otmp); - afternmv = Gloves_off; - } else if (is_boots(otmp)) { - what = c_boots; - afternmv = Boots_off; - } else if (is_suit(otmp)) { - what = suit_simple_name(otmp); - afternmv = Armor_off; - } else if (is_cloak(otmp)) { + ga.afternmv = Gloves_off; + break; + case ARM_BOOTS: + what = boots_simple_name(otmp); + ga.afternmv = Boots_off; + break; + case ARM_CLOAK: what = cloak_simple_name(otmp); - afternmv = Cloak_off; - } else if (is_shield(otmp)) { - what = c_shield; - afternmv = Shield_off; - } else if (is_shirt(otmp)) { - what = c_shirt; - afternmv = Shirt_off; - } else { + ga.afternmv = Cloak_off; + break; + case ARM_SHIRT: + what = shirt_simple_name(otmp); + ga.afternmv = Shirt_off; + break; + default: impossible("Taking off unknown armor (%d: %d), delay %d", otmp->otyp, objects[otmp->otyp].oc_armcat, delay); + break; } if (what) { - Sprintf(offdelaybuf, "You finish taking off your %s.", what); - nomovemsg = offdelaybuf; + /* sizeof offdelaybuf == 60; increase it if this becomes longer */ + Snprintf(offdelaybuf, sizeof offdelaybuf, + "You finish taking off your %s.", what); + gn.nomovemsg = offdelaybuf; } } else { - /* Be warned! We want off_msg after removing the item to - * avoid "You were wearing ____ (being worn)." However, an - * item which grants fire resistance might cause some trouble - * if removed in Hell and lifesaving puts it back on; in this - * case the message will be printed at the wrong time (after - * the messages saying you died and were lifesaved). Luckily, - * no cloak, shield, or fast-removable armor grants fire - * resistance, so we can safely do the off_msg afterwards. - * Rings do grant fire resistance, but for rings we want the - * off_msg before removal anyway so there's no problem. Take - * care in adding armors granting fire resistance; this code - * might need modification. - * 3.2 (actually 3.1 even): that comment is obsolete since - * fire resistance is not required for Gehennom so setworn() - * doesn't force the resistance granting item to be re-worn - * after being lifesaved anymore. - */ - if (is_cloak(otmp)) - (void) Cloak_off(); - else if (is_shield(otmp)) + /* no delay so no '(*afternmv)()' or 'nomovemsg' */ + switch (objects[otmp->otyp].oc_armcat) { + case ARM_SUIT: + (void) Armor_off(); + break; + case ARM_SHIELD: (void) Shield_off(); - else if (is_helmet(otmp)) + break; + case ARM_HELM: (void) Helmet_off(); - else if (is_gloves(otmp)) + break; + case ARM_GLOVES: (void) Gloves_off(); - else if (is_boots(otmp)) + break; + case ARM_BOOTS: (void) Boots_off(); - else if (is_shirt(otmp)) + break; + case ARM_CLOAK: + (void) Cloak_off(); + break; + case ARM_SHIRT: (void) Shirt_off(); - else if (is_suit(otmp)) - (void) Armor_off(); - else + break; + default: impossible("Taking off unknown armor (%d: %d), no delay", otmp->otyp, objects[otmp->otyp].oc_armcat); + break; + } + /* We want off_msg() after removing the item to + avoid "You were wearing ____ (being worn)." */ off_msg(otmp); } - context.takeoff.mask = context.takeoff.what = 0L; + svc.context.takeoff.mask = svc.context.takeoff.what = 0L; return 1; } -STATIC_OVL void -already_wearing(cc) -const char *cc; +staticfn void +already_wearing(const char *cc) { You("are already wearing %s%c", cc, (cc == c_that_) ? '!' : '.'); } -STATIC_OVL void -already_wearing2(cc1, cc2) -const char *cc1, *cc2; +staticfn void +already_wearing2(const char *cc1, const char *cc2) { You_cant("wear %s because you're wearing %s there already.", cc1, cc2); } @@ -1680,33 +2027,30 @@ const char *cc1, *cc2; * output: mask (otmp's armor type) */ int -canwearobj(otmp, mask, noisy) -struct obj *otmp; -long *mask; -boolean noisy; +canwearobj(struct obj *otmp, long *mask, boolean noisy) { int err = 0; const char *which; /* this is the same check as for 'W' (dowear), but different message, in case we get here via 'P' (doputon) */ - if (verysmall(youmonst.data) || nohands(youmonst.data)) { + if (verysmall(gy.youmonst.data) || nohands(gy.youmonst.data)) { if (noisy) You("can't wear any armor in your current form."); return 0; } - which = is_cloak(otmp) - ? c_cloak - : is_shirt(otmp) - ? c_shirt - : is_suit(otmp) - ? c_suit - : 0; - if (which && cantweararm(youmonst.data) + which = is_cloak(otmp) ? c_cloak + : is_shirt(otmp) ? c_shirt + : is_suit(otmp) ? c_suit + : 0; + if (which && cantweararm(gy.youmonst.data) /* same exception for cloaks as used in m_dowear() */ - && (which != c_cloak || youmonst.data->msize != MZ_SMALL) - && (racial_exception(&youmonst, otmp) < 1)) { + && (which != c_cloak + || ((otmp->otyp != MUMMY_WRAPPING) + ? gy.youmonst.data->msize != MZ_SMALL + : !WrappingAllowed(gy.youmonst.data))) + && (racial_exception(&gy.youmonst, otmp) < 1)) { if (noisy) pline_The("%s will not fit on your body.", which); return 0; @@ -1728,12 +2072,12 @@ boolean noisy; if (noisy) already_wearing(an(helm_simple_name(uarmh))); err++; - } else if (Upolyd && has_horns(youmonst.data) && !is_flimsy(otmp)) { + } else if (Upolyd && has_horns(gy.youmonst.data) && !is_flimsy(otmp)) { /* (flimsy exception matches polyself handling) */ if (noisy) pline_The("%s won't fit over your horn%s.", helm_simple_name(otmp), - plur(num_horns(youmonst.data))); + plur(num_horns(gy.youmonst.data))); err++; } else *mask = W_ARMH; @@ -1760,17 +2104,17 @@ boolean noisy; if (noisy) already_wearing(c_boots); err++; - } else if (Upolyd && slithy(youmonst.data)) { + } else if (Upolyd && slithy(gy.youmonst.data)) { if (noisy) You("have no feet..."); /* not body_part(FOOT) */ err++; - } else if (Upolyd && youmonst.data->mlet == S_CENTAUR) { - /* break_armor() pushes boots off for centaurs, - so don't let dowear() put them back on... */ + } else if (Upolyd && gy.youmonst.data->mlet == S_CENTAUR) { + /* break_armor() pushes boots off for centaurs, so don't let + dowear() put them back on; + makeplural(body_part(FOOT)) would yield "rear hooves" here, + which sounds odd, so use hard-coded "hooves" */ if (noisy) - pline("You have too many hooves to wear %s.", - c_boots); /* makeplural(body_part(FOOT)) yields - "rear hooves" which sounds odd */ + You("have too many hooves to wear %s.", c_boots); err++; } else if (u.utrap && (u.utraptype == TT_BEARTRAP || u.utraptype == TT_INFLOOR @@ -1861,25 +2205,25 @@ boolean noisy; return !err; } -STATIC_OVL int -accessory_or_armor_on(obj) -struct obj *obj; +staticfn int +accessory_or_armor_on(struct obj *obj) { long mask = 0L; - boolean armor, ring, eyewear; + boolean armor, ring, amulet, eyewear; if (obj->owornmask & (W_ACCESSORY | W_ARMOR)) { already_wearing(c_that_); - return 0; + return ECMD_OK; } armor = (obj->oclass == ARMOR_CLASS); ring = (obj->oclass == RING_CLASS || obj->otyp == MEAT_RING); + amulet = (obj->oclass == AMULET_CLASS); eyewear = (obj->otyp == BLINDFOLD || obj->otyp == TOWEL || obj->otyp == LENSES); /* checks which are performed prior to actually touching the item */ if (armor) { if (!canwearobj(obj, &mask, TRUE)) - return 0; + return ECMD_OK; if (obj->otyp == HELM_OF_OPPOSITE_ALIGNMENT && qstart_level.dnum == u.uz.dnum) { /* in quest */ @@ -1889,24 +2233,33 @@ struct obj *obj; You("are suddenly overcome with shame and change your mind."); u.ublessed = 0; /* lose your god's protection */ makeknown(obj->otyp); - context.botl = 1; /*for AC after zeroing u.ublessed */ - return 1; + disp.botl = TRUE; /* for AC after zeroing u.ublessed */ + return ECMD_TIME; } } else { + /* + * FIXME: + * except for the rings/nolimbs case, this allows you to put on + * accessories without having any hands to manipulate them, and + * to put them on when poly'd into a tiny or huge form where + * they shouldn't fit. [If the latter situation changes, make + * comparable change to break_armor(polyself.c).] + */ + /* accessory */ if (ring) { char answer, qbuf[QBUFSZ]; int res = 0; - if (nolimbs(youmonst.data)) { + if (nolimbs(gy.youmonst.data)) { You("cannot make the ring stick to your body."); - return 0; + return ECMD_OK; } if (uleft && uright) { There("are no more %s%s to fill.", - humanoid(youmonst.data) ? "ring-" : "", + humanoid(gy.youmonst.data) ? "ring-" : "", fingers_or_gloves(FALSE)); - return 0; + return ECMD_OK; } if (uleft) { mask = RIGHT_RING; @@ -1915,12 +2268,13 @@ struct obj *obj; } else { do { Sprintf(qbuf, "Which %s%s, Right or Left?", - humanoid(youmonst.data) ? "ring-" : "", + humanoid(gy.youmonst.data) ? "ring-" : "", body_part(FINGER)); - answer = yn_function(qbuf, "rl", '\0'); + answer = yn_function(qbuf, rightleftchars, '\0', TRUE); switch (answer) { case '\0': - return 0; + case '\033': + return ECMD_OK; case 'l': case 'L': mask = LEFT_RING; @@ -1936,17 +2290,20 @@ struct obj *obj; Your( "%s are too slippery to remove, so you cannot put on the ring.", gloves_simple_name(uarmg)); - return 1; /* always uses move */ + return ECMD_TIME; /* always uses move */ } if (uarmg && uarmg->cursed) { res = !uarmg->bknown; set_bknown(uarmg, 1); You("cannot remove your %s to put on the ring.", c_gloves); - return res; /* uses move iff we learned gloves are cursed */ + /* uses move iff we learned gloves are cursed */ + return res ? ECMD_TIME : ECMD_OK; } if (uwep) { res = !uwep->bknown; /* check this before calling welded() */ - if ((mask == RIGHT_RING || bimanual(uwep)) && welded(uwep)) { + if (((mask == RIGHT_RING && URIGHTY) + || (mask == LEFT_RING && ULEFTY) + || bimanual(uwep)) && welded(uwep)) { const char *hand = body_part(HAND); /* welded will set bknown */ @@ -1954,15 +2311,21 @@ struct obj *obj; hand = makeplural(hand); You("cannot free your weapon %s to put on the ring.", hand); - return res; /* uses move iff we learned weapon is cursed */ + /* uses move iff we learned weapon is cursed */ + return res ? ECMD_TIME : ECMD_OK; } } - } else if (obj->oclass == AMULET_CLASS) { + } else if (amulet) { if (uamul) { already_wearing("an amulet"); - return 0; + return ECMD_OK; } } else if (eyewear) { + if (!has_head(gy.youmonst.data)) { + You("have no head to wear %s on.", ansimpleoname(obj)); + return ECMD_OK; + } + if (ublindf) { if (ublindf->otyp == TOWEL) Your("%s is already covered by a towel.", @@ -1980,17 +2343,17 @@ struct obj *obj; } else { already_wearing(something); /* ??? */ } - return 0; + return ECMD_OK; } } else { /* neither armor nor accessory */ You_cant("wear that!"); - return 0; + return ECMD_OK; } } if (!retouch_object(&obj, FALSE)) - return 1; /* costs a turn even though it didn't get worn */ + return ECMD_TIME; /* costs a turn even though it didn't get worn */ if (armor) { int delay; @@ -2004,90 +2367,91 @@ struct obj *obj; * to change so armor's +/- value is evident via the status line. * We used to set it here because of that, but then it would stick * if a nymph stole the armor before it was fully worn. Delay it - * until the aftermv action. The player may still know this armor's + * until the afternmv action. The player may still know this armor's * +/- amount if donning gets interrupted, but the hero won't. * obj->known = 1; */ + gw.wasinwater = u.uinwater; /* for WWALKING; Boots_on() is too late */ setworn(obj, mask); - /* if there's no delay, we'll execute 'aftermv' immediately */ + /* if there's no delay, we'll execute 'afternmv' immediately */ if (obj == uarm) - afternmv = Armor_on; + ga.afternmv = Armor_on; else if (obj == uarmh) - afternmv = Helmet_on; + ga.afternmv = Helmet_on; else if (obj == uarmg) - afternmv = Gloves_on; + ga.afternmv = Gloves_on; else if (obj == uarmf) - afternmv = Boots_on; + ga.afternmv = Boots_on; else if (obj == uarms) - afternmv = Shield_on; + ga.afternmv = Shield_on; else if (obj == uarmc) - afternmv = Cloak_on; + ga.afternmv = Cloak_on; else if (obj == uarmu) - afternmv = Shirt_on; + ga.afternmv = Shirt_on; else panic("wearing armor not worn as armor? [%08lx]", obj->owornmask); delay = -objects[obj->otyp].oc_delay; if (delay) { nomul(delay); - multi_reason = "dressing up"; - nomovemsg = "You finish your dressing maneuver."; + gm.multi_reason = "dressing up"; + gn.nomovemsg = "You finish your dressing maneuver."; } else { - unmul(""); /* call (*aftermv)(), clear it+nomovemsg+multi_reason */ + unmul(""); /* call afternmv, clear it+nomovemsg+multi_reason */ on_msg(obj); } - context.takeoff.mask = context.takeoff.what = 0L; + svc.context.takeoff.mask = svc.context.takeoff.what = 0L; + /* gw.wasinwater = 0U; // can't clear this yet; Boots_on() needs it + * and gets called via afternmv() after this routine has returned */ } else { /* not armor */ - boolean give_feedback = FALSE; - - /* [releasing wielded accessory handled in Xxx_on()] */ if (ring) { + /* Ring_on() expects ring to already be worn as uleft or uright */ setworn(obj, mask); Ring_on(obj); - give_feedback = TRUE; - } else if (obj->oclass == AMULET_CLASS) { - setworn(obj, W_AMUL); - Amulet_on(); - /* no feedback here if amulet of change got used up */ - give_feedback = (uamul != 0); + /* is_worn(): 'obj' will always be worn here except when putting + on a ring of levitation while at a sink location */ + if (is_worn(obj)) + on_msg(obj); + } else if (amulet) { + /* setworn() and on_msg() handled by Amulet_on() */ + Amulet_on(obj); } else if (eyewear) { - /* setworn() handled by Blindf_on() */ + /* setworn() and on_msg() handled by Blindf_on() */ Blindf_on(obj); - /* message handled by Blindf_on(); leave give_feedback False */ + } else { + impossible("putting on unexpected type of accessory: %s", + safe_typename(obj->otyp)); } - /* feedback for ring or for amulet other than 'change' */ - if (give_feedback && is_worn(obj)) - prinv((char *) 0, obj, 0L); } - return 1; + return ECMD_TIME; } -/* the 'W' command */ +/* the #wear command */ int -dowear() +dowear(void) { struct obj *otmp; /* cantweararm() checks for suits of armor, not what we want here; verysmall() or nohands() checks for shields, gloves, etc... */ - if (verysmall(youmonst.data) || nohands(youmonst.data)) { + if (verysmall(gy.youmonst.data) || nohands(gy.youmonst.data)) { pline("Don't even bother."); - return 0; + return ECMD_OK; } if (uarm && uarmu && uarmc && uarmh && uarms && uarmg && uarmf && uleft && uright && uamul && ublindf) { /* 'W' message doesn't mention accessories */ You("are already wearing a full complement of armor."); - return 0; + return ECMD_OK; } - otmp = getobj(clothes, "wear"); - return otmp ? accessory_or_armor_on(otmp) : 0; + otmp = getobj("wear", wear_ok, GETOBJ_NOFLAGS); + return otmp ? accessory_or_armor_on(otmp) : ECMD_CANCEL; } -/* the 'P' command */ +/* the #puton command */ int -doputon() +doputon(void) { struct obj *otmp; @@ -2095,18 +2459,18 @@ doputon() && uarm && uarmu && uarmc && uarmh && uarms && uarmg && uarmf) { /* 'P' message doesn't mention armor */ Your("%s%s are full, and you're already wearing an amulet and %s.", - humanoid(youmonst.data) ? "ring-" : "", + humanoid(gy.youmonst.data) ? "ring-" : "", fingers_or_gloves(FALSE), (ublindf->otyp == LENSES) ? "some lenses" : "a blindfold"); - return 0; + return ECMD_OK; } - otmp = getobj(accessories, "put on"); - return otmp ? accessory_or_armor_on(otmp) : 0; + otmp = getobj("put on", puton_ok, GETOBJ_NOFLAGS); + return otmp ? accessory_or_armor_on(otmp) : ECMD_CANCEL; } /* calculate current armor class */ void -find_ac() +find_ac(void) { int uac = mons[u.umonnum].ac; /* base armor class for current form */ @@ -2129,40 +2493,58 @@ find_ac() uac -= uleft->spe; if (uright && uright->otyp == RIN_PROTECTION) uac -= uright->spe; + if (uamul && uamul->otyp == AMULET_OF_GUARDING) + uac -= 2; /* fixed amount; main benefit is to MC */ /* armor class from other sources */ if (HProtection & INTRINSIC) uac -= u.ublessed; uac -= u.uspellprot; - /* [The magic binary numbers 127 and -128 should be replaced with the - * mystic decimal numbers 99 and -99 which require no explanation to - * the uninitiated and would cap the width of a status line value at - * one less character.] - */ - if (uac < -128) - uac = -128; /* u.uac is an schar */ - else if (uac > 127) - uac = 127; /* for completeness */ + /* put a cap on armor class [5.0: was +127,-128, now reduced to +/- 99 */ + if (abs(uac) > AC_MAX) + uac = sgn(uac) * AC_MAX; if (uac != u.uac) { u.uac = uac; - context.botl = 1; + disp.botl = TRUE; +#if 0 + /* these could conceivably be achieved out of order (by being near + threshold and putting on +N dragon scale mail from bones, for + instance), but if that happens, that's the order it happened; + also, testing for these in the usual order would result in more + record_achievement() attempts and rejects for duplication */ + if (u.uac <= -20) + record_achievement(ACH_AC_20); + else if (u.uac <= -10) + record_achievement(ACH_AC_10); + else if (u.uac <= 0) + record_achievement(ACH_AC_00); +#endif } } void -glibr() +glibr(void) { - register struct obj *otmp; + struct obj *otmp; int xfl = 0; boolean leftfall, rightfall, wastwoweap = FALSE; const char *otherwep = 0, *thiswep, *which, *hand; + leftfall = (uleft && !uleft->cursed + && (!uwep || !(welded(uwep) && ULEFTY) + || !bimanual(uwep))); + rightfall = (uright && !uright->cursed + && (!uwep || !(welded(uwep) && URIGHTY) + || !bimanual(uwep))); +/* leftfall = (uleft && !uleft->cursed && (!uwep || !welded(uwep) || !bimanual(uwep))); rightfall = (uright && !uright->cursed && (!welded(uwep))); - if (!uarmg && (leftfall || rightfall) && !nolimbs(youmonst.data)) { +*/ + + if (!uarmg && (leftfall || rightfall) && !nolimbs(gy.youmonst.data)) { /* changed so cursed rings don't fall off, GAN 10/30/86 */ Your("%s off your %s.", (leftfall && rightfall) ? "rings slip" : "ring slips", @@ -2173,11 +2555,13 @@ glibr() otmp = uleft; Ring_off(uleft); dropx(otmp); + cmdq_clear(CQ_CANNED); } if (rightfall) { otmp = uright; Ring_off(uright); dropx(otmp); + cmdq_clear(CQ_CANNED); } } @@ -2192,17 +2576,18 @@ glibr() if (otmp->quan > 1L) otherwep = makeplural(otherwep); hand = body_part(HAND); - which = "left "; + which = URIGHTY ? "left " : "right "; /* text for the off hand */ Your("%s %s%s from your %s%s.", otherwep, xfl ? "also " : "", otense(otmp, "slip"), which, hand); xfl++; wastwoweap = TRUE; setuswapwep((struct obj *) 0); /* clears u.twoweap */ + cmdq_clear(CQ_CANNED); if (canletgo(otmp, "")) dropx(otmp); } otmp = uwep; - if (otmp && !welded(otmp)) { + if (otmp && otmp->otyp != AKLYS && !welded(otmp)) { long savequan = otmp->quan; /* nice wording if both weapons are the same type */ @@ -2222,10 +2607,12 @@ glibr() } hand = body_part(HAND); which = ""; - if (bimanual(otmp)) + if (bimanual(otmp)) { hand = makeplural(hand); - else if (wastwoweap) - which = "right "; /* preceding msg was about left */ + } else if (wastwoweap) { + /* preceding msg was about non-dominant hand */ + which = URIGHTY ? "right " : "left "; + } pline("%s %s%s %s%s from your %s%s.", !strncmp(thiswep, "corpse", 6) ? "The" : "Your", otherwep ? "other " : "", thiswep, xfl ? "also " : "", @@ -2233,33 +2620,33 @@ glibr() /* xfl++; */ otmp->quan = savequan; setuwep((struct obj *) 0); + cmdq_clear(CQ_CANNED); if (canletgo(otmp, "")) dropx(otmp); } } struct obj * -some_armor(victim) -struct monst *victim; +some_armor(struct monst *victim) { - register struct obj *otmph, *otmp; + struct obj *otmph, *otmp; - otmph = (victim == &youmonst) ? uarmc : which_armor(victim, W_ARMC); + otmph = (victim == &gy.youmonst) ? uarmc : which_armor(victim, W_ARMC); if (!otmph) - otmph = (victim == &youmonst) ? uarm : which_armor(victim, W_ARM); + otmph = (victim == &gy.youmonst) ? uarm : which_armor(victim, W_ARM); if (!otmph) - otmph = (victim == &youmonst) ? uarmu : which_armor(victim, W_ARMU); + otmph = (victim == &gy.youmonst) ? uarmu : which_armor(victim, W_ARMU); - otmp = (victim == &youmonst) ? uarmh : which_armor(victim, W_ARMH); + otmp = (victim == &gy.youmonst) ? uarmh : which_armor(victim, W_ARMH); if (otmp && (!otmph || !rn2(4))) otmph = otmp; - otmp = (victim == &youmonst) ? uarmg : which_armor(victim, W_ARMG); + otmp = (victim == &gy.youmonst) ? uarmg : which_armor(victim, W_ARMG); if (otmp && (!otmph || !rn2(4))) otmph = otmp; - otmp = (victim == &youmonst) ? uarmf : which_armor(victim, W_ARMF); + otmp = (victim == &gy.youmonst) ? uarmf : which_armor(victim, W_ARMF); if (otmp && (!otmph || !rn2(4))) otmph = otmp; - otmp = (victim == &youmonst) ? uarms : which_armor(victim, W_ARMS); + otmp = (victim == &gy.youmonst) ? uarms : which_armor(victim, W_ARMS); if (otmp && (!otmph || !rn2(4))) otmph = otmp; return otmph; @@ -2267,9 +2654,7 @@ struct monst *victim; /* used for praying to check and fix levitation trouble */ struct obj * -stuck_ring(ring, otyp) -struct obj *ring; -int otyp; +stuck_ring(struct obj *ring, int otyp) { if (ring != uleft && ring != uright) { impossible("stuck_ring: neither left nor right?"); @@ -2279,10 +2664,10 @@ int otyp; if (ring && ring->otyp == otyp) { /* reasons ring can't be removed match those checked by select_off(); limbless case has extra checks because ordinarily it's temporary */ - if (nolimbs(youmonst.data) && uamul + if (nolimbs(gy.youmonst.data) && uamul && uamul->otyp == AMULET_OF_UNCHANGING && uamul->cursed) return uamul; - if (welded(uwep) && (ring == uright || bimanual(uwep))) + if (welded(uwep) && ((ring == RING_ON_PRIMARY) || bimanual(uwep))) return uwep; if (uarmg && uarmg->cursed) return uarmg; @@ -2299,17 +2684,16 @@ int otyp; /* also for praying; find worn item that confers "Unchanging" attribute */ struct obj * -unchanger() +unchanger(void) { if (uamul && uamul->otyp == AMULET_OF_UNCHANGING) return uamul; return 0; } -STATIC_PTR +staticfn int -select_off(otmp) -register struct obj *otmp; +select_off(struct obj *otmp) { struct obj *why; char buf[BUFSZ]; @@ -2322,13 +2706,13 @@ register struct obj *otmp; if (otmp == uright || otmp == uleft) { struct obj glibdummy; - if (nolimbs(youmonst.data)) { + if (nolimbs(gy.youmonst.data)) { pline_The("ring is stuck."); return 0; } - glibdummy = zeroobj; + glibdummy = cg.zeroobj; why = 0; /* the item which prevents ring removal */ - if (welded(uwep) && (otmp == uright || bimanual(uwep))) { + if (welded(uwep) && ((otmp == RING_ON_PRIMARY) || bimanual(uwep))) { Sprintf(buf, "free a weapon %s", body_part(HAND)); why = uwep; } else if (uarmg && (uarmg->cursed || Glib)) { @@ -2355,6 +2739,8 @@ register struct obj *otmp; gloves_simple_name(uarmg)); return 0; } + if (better_not_take_that_off(otmp)) + return 0; } /* special boot checks */ if (otmp == uarmf) { @@ -2400,33 +2786,33 @@ register struct obj *otmp; } if (otmp == uarm) - context.takeoff.mask |= WORN_ARMOR; + svc.context.takeoff.mask |= WORN_ARMOR; else if (otmp == uarmc) - context.takeoff.mask |= WORN_CLOAK; + svc.context.takeoff.mask |= WORN_CLOAK; else if (otmp == uarmf) - context.takeoff.mask |= WORN_BOOTS; + svc.context.takeoff.mask |= WORN_BOOTS; else if (otmp == uarmg) - context.takeoff.mask |= WORN_GLOVES; + svc.context.takeoff.mask |= WORN_GLOVES; else if (otmp == uarmh) - context.takeoff.mask |= WORN_HELMET; + svc.context.takeoff.mask |= WORN_HELMET; else if (otmp == uarms) - context.takeoff.mask |= WORN_SHIELD; + svc.context.takeoff.mask |= WORN_SHIELD; else if (otmp == uarmu) - context.takeoff.mask |= WORN_SHIRT; + svc.context.takeoff.mask |= WORN_SHIRT; else if (otmp == uleft) - context.takeoff.mask |= LEFT_RING; + svc.context.takeoff.mask |= LEFT_RING; else if (otmp == uright) - context.takeoff.mask |= RIGHT_RING; + svc.context.takeoff.mask |= RIGHT_RING; else if (otmp == uamul) - context.takeoff.mask |= WORN_AMUL; + svc.context.takeoff.mask |= WORN_AMUL; else if (otmp == ublindf) - context.takeoff.mask |= WORN_BLINDF; + svc.context.takeoff.mask |= WORN_BLINDF; else if (otmp == uwep) - context.takeoff.mask |= W_WEP; + svc.context.takeoff.mask |= W_WEP; else if (otmp == uswapwep) - context.takeoff.mask |= W_SWAPWEP; + svc.context.takeoff.mask |= W_SWAPWEP; else if (otmp == uquiver) - context.takeoff.mask |= W_QUIVER; + svc.context.takeoff.mask |= W_QUIVER; else impossible("select_off: %s???", doname(otmp)); @@ -2434,23 +2820,27 @@ register struct obj *otmp; return 0; } -STATIC_OVL struct obj * -do_takeoff() +staticfn struct obj * +do_takeoff(void) { struct obj *otmp = (struct obj *) 0; - struct takeoff_info *doff = &context.takeoff; + boolean was_twoweap = u.twoweap; + struct takeoff_info *doff = &svc.context.takeoff; - context.takeoff.mask |= I_SPECIAL; /* set flag for cancel_doff() */ + svc.context.takeoff.mask |= I_SPECIAL; /* set flag for cancel_doff() */ if (doff->what == W_WEP) { if (!cursed(uwep)) { setuwep((struct obj *) 0); - You("are empty %s.", body_part(HANDED)); - u.twoweap = FALSE; + if (was_twoweap) + You("are no longer wielding either weapon."); + else + You("are %s.", empty_handed()); } } else if (doff->what == W_SWAPWEP) { setuswapwep((struct obj *) 0); - You("no longer have a second weapon readied."); - u.twoweap = FALSE; + You("%sno longer %s.", was_twoweap ? "are " : "", + was_twoweap ? "wielding two weapons at once" + : "have a second weapon readied"); } else if (doff->what == W_QUIVER) { setuqwep((struct obj *) 0); You("no longer have ammunition readied."); @@ -2500,19 +2890,18 @@ do_takeoff() } else { impossible("do_takeoff: taking off %lx", doff->what); } - context.takeoff.mask &= ~I_SPECIAL; /* clear cancel_doff() flag */ + svc.context.takeoff.mask &= ~I_SPECIAL; /* clear cancel_doff() flag */ return otmp; } /* occupation callback for 'A' */ -STATIC_PTR -int -take_off(VOID_ARGS) +staticfn int +take_off(void) { - register int i; - register struct obj *otmp; - struct takeoff_info *doff = &context.takeoff; + int i; + struct obj *otmp; + struct takeoff_info *doff = &svc.context.takeoff; if (doff->what) { if (doff->delay > 0) { @@ -2597,56 +2986,108 @@ take_off(VOID_ARGS) return 1; /* get busy */ } +staticfn boolean +better_not_take_that_off(struct obj *otmp) +{ + struct obj *corpse = carrying_stoning_corpse(); + char buf[BUFSZ]; + + /* u_safe_from_fatal_corpse() with + (st_corpse | st_petrifies | st_resists) instead of + (st_corpse | st_petrifies) + would also check for no stoning resistance before + bothering to prompt, but losing stoning resistance + later, without the gloves on could prove dangerous, + so we won't factor that in */ + if (corpse + && !u_safe_from_fatal_corpse(corpse, st_corpse | st_petrifies)) { + Snprintf(buf, sizeof buf, + "Take off your %s despite carrying a dead %s?", + gloves_simple_name(otmp), obj_pmname(corpse)); + return (paranoid_ynq(TRUE, buf, FALSE) != 'y'); + } + return FALSE; +} + /* clear saved context to avoid inappropriate resumption of interrupted 'A' */ void -reset_remarm() +reset_remarm(void) { - context.takeoff.what = context.takeoff.mask = 0L; - context.takeoff.disrobing[0] = '\0'; + svc.context.takeoff.what = svc.context.takeoff.mask = 0L; + svc.context.takeoff.disrobing[0] = '\0'; } -/* the 'A' command -- remove multiple worn items */ +/* the #takeoffall command -- remove multiple worn items */ int -doddoremarm() +doddoremarm(void) { int result = 0; - if (context.takeoff.what || context.takeoff.mask) { - You("continue %s.", context.takeoff.disrobing); - set_occupation(take_off, context.takeoff.disrobing, 0); - return 0; - } else if (!uwep && !uswapwep && !uquiver && !uamul && !ublindf && !uleft - && !uright && !wearing_armor()) { + if (svc.context.takeoff.what || svc.context.takeoff.mask) { + You("continue %s.", svc.context.takeoff.disrobing); + set_occupation(take_off, svc.context.takeoff.disrobing, 0); + return ECMD_OK; + } else if (!uwep && !uswapwep && !uquiver && !uamul && !ublindf + && !uleft && !uright && !wearing_armor()) { You("are not wearing anything."); - return 0; + return ECMD_OK; } add_valid_menu_class(0); /* reset */ if (flags.menu_style != MENU_TRADITIONAL || (result = ggetobj("take off", select_off, 0, FALSE, (unsigned *) 0)) < -1) - result = menu_remarm(result); - - if (context.takeoff.mask) { - /* default activity for armor and/or accessories, - possibly combined with weapons */ - (void) strncpy(context.takeoff.disrobing, "disrobing", CONTEXTVERBSZ); - /* specific activity when handling weapons only */ - if (!(context.takeoff.mask & ~W_WEAPONS)) - (void) strncpy(context.takeoff.disrobing, "disarming", - CONTEXTVERBSZ); + (void) menu_remarm(result); + + if (svc.context.takeoff.mask) { + (void) strncpy(svc.context.takeoff.disrobing, + (((svc.context.takeoff.mask & ~W_WEAPONS) != 0) + /* default activity for armor and/or accessories, + possibly combined with weapons */ + ? "disrobing" + /* specific activity when handling weapons only */ + : "disarming"), CONTEXTVERBSZ); (void) take_off(); } /* The time to perform the command is already completely accounted for * in take_off(); if we return 1, that would add an extra turn to each * disrobe. */ - return 0; + return ECMD_OK; +} + +/* #altunwield - just unwield alternate weapon, item-action '-' when picking + uswapwep from context-sensitive inventory */ +int +remarm_swapwep(void) +{ + struct _cmd_queue cq, *cmdq; + unsigned oldbknown; + + if ((cmdq = cmdq_pop()) != 0) { + /* '-' uswapwep item-action picked from context-sensitive invent */ + cq = *cmdq; + free(cmdq); + } else { + cq.typ = CMDQ_KEY; + cq.key = '\0'; /* something other than '-' */ + } + if (cq.typ != CMDQ_KEY || cq.key != '-' || !uswapwep) + return ECMD_FAIL; + + oldbknown = uswapwep->bknown; /* when deciding whether this command + * has done something that takes time, + * behave as if a cursed secondary weapon + * can't be unwielded even though things + * don't work that way... */ + reset_remarm(); + svc.context.takeoff.what = svc.context.takeoff.mask = W_SWAPWEP; + (void) do_takeoff(); + return (!uswapwep || uswapwep->bknown != oldbknown) ? ECMD_TIME : ECMD_OK; } -STATIC_OVL int -menu_remarm(retry) -int retry; +staticfn int +menu_remarm(int retry) { int n, i = 0; menu_item *pick_list; @@ -2657,7 +3098,7 @@ int retry; } else if (flags.menu_style == MENU_FULL) { all_worn_categories = FALSE; n = query_category("What type of things do you want to take off?", - invent, (WORN_TYPES | ALL_TYPES + gi.invent, (WORN_TYPES | ALL_TYPES | UNPAID_TYPES | BUCX_TYPES), &pick_list, PICK_ANY); if (!n) @@ -2682,7 +3123,7 @@ int retry; || menu_class_present('C') || menu_class_present('X')) all_worn_categories = FALSE; - n = query_objlist("What do you want to take off?", &invent, + n = query_objlist("What do you want to take off?", &gi.invent, (SIGNAL_NOMENU | USE_INVLET | INVORDER_SORT), &pick_list, PICK_ANY, all_worn_categories ? is_worn : is_worn_by_type); @@ -2696,81 +3137,193 @@ int retry; return 0; } -/* hit by destroy armor scroll/black dragon breath/monster spell */ -int -destroy_arm(atmp) -register struct obj *atmp; -{ - register struct obj *otmp; -#define DESTROY_ARM(o) \ - ((otmp = (o)) != 0 && (!atmp || atmp == otmp) \ - && (!obj_resists(otmp, 0, 90)) \ - ? (otmp->in_use = TRUE) \ - : FALSE) - - if (DESTROY_ARM(uarmc)) { - if (donning(otmp)) - cancel_don(); - Your("%s crumbles and turns to dust!", cloak_simple_name(uarmc)); +/* take off the specific worn object and if it still exists after that, + destroy it (taking off the item might already destroy it by dunking + hero into lava) */ +staticfn void +wornarm_destroyed(struct obj *wornarm) +{ + struct obj *invobj, *nextobj; + unsigned wornoid = wornarm->o_id; + + /* cancel_don() resets 'afternmv' when appropriate but doesn't reset + uarmc/uarm/&c so doing this now won't interfere with the tests in + 'if (wornarm==uarmc) ... else if (wornarm==uarm) ... else ...' */ + if (donning(wornarm)) + cancel_don(); + + if (wornarm == uarmc) (void) Cloak_off(); - useup(otmp); - } else if (DESTROY_ARM(uarm)) { - if (donning(otmp)) - cancel_don(); - Your("armor turns to dust and falls to the %s!", surface(u.ux, u.uy)); - (void) Armor_gone(); - useup(otmp); - } else if (DESTROY_ARM(uarmu)) { - if (donning(otmp)) - cancel_don(); - Your("shirt crumbles into tiny threads and falls apart!"); + else if (wornarm == uarm) + (void) Armor_off(); + else if (wornarm == uarmu) (void) Shirt_off(); - useup(otmp); - } else if (DESTROY_ARM(uarmh)) { - if (donning(otmp)) - cancel_don(); - Your("%s turns to dust and is blown away!", helm_simple_name(uarmh)); + else if (wornarm == uarmh) (void) Helmet_off(); - useup(otmp); - } else if (DESTROY_ARM(uarmg)) { - if (donning(otmp)) - cancel_don(); - Your("gloves vanish!"); + else if (wornarm == uarmg) (void) Gloves_off(); - useup(otmp); - selftouch("You"); - } else if (DESTROY_ARM(uarmf)) { - if (donning(otmp)) - cancel_don(); - Your("boots disintegrate!"); + else if (wornarm == uarmf) (void) Boots_off(); - useup(otmp); - } else if (DESTROY_ARM(uarms)) { - if (donning(otmp)) - cancel_don(); - Your("shield crumbles away!"); + else if (wornarm == uarms) (void) Shield_off(); - useup(otmp); + + /* 'wornarm' might be destroyed as a side-effect of xxx_off() so + using carried() to check wornarm->where==OBJ_INVENT is not viable; + scan invent instead; if already freed it shouldn't be possible to + have re-used the stale memory for a new item yet but verify o_id + just in case */ + for (invobj = gi.invent; invobj; invobj = nextobj) { + nextobj = invobj->nobj; + if (invobj == wornarm && invobj->o_id == wornoid) { + useup(wornarm); + break; + } + } +} + +/* + * returns impacted armor with its in_use bit set, + * or Null. *resisted is updated to reflect whether + * it resisted or not */ +staticfn struct obj * +maybe_destroy_armor(struct obj *armor, struct obj *atmp, boolean *resisted) +{ + if ((armor != 0) && (!atmp || atmp == armor) + && ((*resisted = obj_resists(armor, 0, 90)) == FALSE)) { + armor->in_use = 1; + return armor; + } + return (struct obj *) 0; +} + +/* hit by destroy armor scroll/black dragon breath */ +int +disintegrate_arm(struct obj *atmp) +{ + struct obj *otmp = (struct obj *) 0; + boolean losing_gloves = FALSE, resisted = FALSE, + resistedc = FALSE, resistedsuit = FALSE; + /* + * Note: if the cloak resisted, then the suit or shirt underneath + * wouldn't be impacted either. Likewise, if the suit resisted, the + * shirt underneath wouldn't be impacted. Since there are no artifact + * cloaks or suits right now, this is unlikely to come into effect, + * but it should behave appropriately if/when the situation changes. + */ + + if ((otmp = maybe_destroy_armor(uarmc, atmp, &resistedc)) != 0) { + urgent_pline("Your %s crumbles and turns to dust!", + /* cloak/robe/apron/smock (ID'd apron)/wrapping */ + cloak_simple_name(otmp)); + } else if (!resistedc + && (otmp = maybe_destroy_armor(uarm, atmp, &resistedsuit)) != 0) { + const char *suit = suit_simple_name(otmp); + + /* for gold DSM, we don't want Armor_gone() to report that it + stops shining _after_ we've been told that it is destroyed */ + if (otmp->lamplit) + end_burn(otmp, FALSE); + urgent_pline("Your %s %s to dust and %s to the %s!", + /* suit might be "dragon scales" so vtense() is needed */ + suit, vtense(suit, "turn"), vtense(suit, "fall"), + surface(u.ux, u.uy)); + } else if (!resistedc && !resistedsuit + && (otmp = maybe_destroy_armor(uarmu, atmp, &resisted)) != 0) { + urgent_pline("Your %s crumbles into tiny threads and falls apart!", + shirt_simple_name(otmp)); /* always "shirt" */ + } else if ((otmp = maybe_destroy_armor(uarmh, atmp, &resisted)) != 0) { + urgent_pline("Your %s turns to dust and is blown away!", + helm_simple_name(otmp)); /* "helm" or "hat" */ + } else if ((otmp = maybe_destroy_armor(uarmg, atmp, &resisted)) != 0) { + urgent_pline("Your %s vanish!", gloves_simple_name(otmp)); + losing_gloves = TRUE; + } else if ((otmp = maybe_destroy_armor(uarmf, atmp, &resisted)) != 0) { + urgent_pline("Your %s disintegrate!", boots_simple_name(otmp)); + } else if ((otmp = maybe_destroy_armor(uarms, atmp, &resisted)) != 0) { + urgent_pline("Your %s crumbles away!", shield_simple_name(otmp)); } else { return 0; /* could not destroy anything */ } -#undef DESTROY_ARM + /* cancel_don() if applicable, Cloak_off()/Armor_off()/&c, and useup() */ + wornarm_destroyed(otmp); + /* glove loss means wielded weapon will be touched */ + if (losing_gloves) + selftouch("You"); + stop_occupation(); return 1; } +/* return ERODE_foo erosion type which can apply to object */ +staticfn int +obj_erode_type(struct obj *otmp) +{ + if (is_flammable(otmp)) + return ERODE_BURN; + else if (is_rustprone(otmp)) + return ERODE_RUST; + else if (is_crackable(otmp)) + return ERODE_CRACK; + else if (is_rottable(otmp)) + return ERODE_ROT; + else if (is_corrodeable(otmp)) + return ERODE_CORRODE; + return ERODE_NONE; +} + +/* erode a number of worn armor(s). + if the armor is hit when max eroded, destroys it. */ +int +destroy_arm(void) +{ + struct obj *armors[7] = { NULL }; + struct obj *otmp; + int i, idx = 0, hits = rn2(4) + 1; + int ret = 0; + + /* gather worn armor; include non-erodeable ones */ + if (uarm) armors[idx++] = uarm; + if (uarmc) armors[idx++] = uarmc; + if (uarmh) armors[idx++] = uarmh; + if (uarms) armors[idx++] = uarms; + if (uarmg) armors[idx++] = uarmg; + if (uarmf) armors[idx++] = uarmf; + if (uarmu) armors[idx++] = uarmu; + if (!idx) + return 0; + + for (i = 0; i < hits; i++) { + otmp = armors[rn2(idx)]; + + if (erosion_matters(otmp) && is_damageable(otmp) && !otmp->oerodeproof) { + int erosion = obj_erode_type(otmp); + + if (erosion != ERODE_NONE) { + int r = erode_obj(otmp, xname(otmp), erosion, EF_PAY|EF_DESTROY); + + if (r != ER_NOTHING) + ret = 1; + if (r == ER_DESTROYED) + break; + } + } + } + + if (ret) + stop_occupation(); + return ret; +} + void -adj_abon(otmp, delta) -register struct obj *otmp; -register schar delta; +adj_abon(struct obj *otmp, schar delta) { if (uarmg && uarmg == otmp && otmp->otyp == GAUNTLETS_OF_DEXTERITY) { if (delta) { makeknown(uarmg->otyp); ABON(A_DEX) += (delta); } - context.botl = 1; + disp.botl = TRUE; } if (uarmh && uarmh == otmp && otmp->otyp == HELM_OF_BRILLIANCE) { if (delta) { @@ -2778,18 +3331,19 @@ register schar delta; ABON(A_INT) += (delta); ABON(A_WIS) += (delta); } - context.botl = 1; + disp.botl = TRUE; } } /* decide whether a worn item is covered up by some other worn item, - used for dipping into liquid and applying grease; + used for dipping into liquid and applying grease and takeoff_ok(); some criteria are different than select_off()'s */ boolean -inaccessible_equipment(obj, verb, only_if_known_cursed) -struct obj *obj; -const char *verb; /* "dip" or "grease", or null to avoid messages */ -boolean only_if_known_cursed; /* ignore covering unless known to be cursed */ +inaccessible_equipment( + struct obj *obj, + const char *verb, /* "dip" or "grease", or null to avoid messages */ + boolean only_if_known_cursed) /* ignore covering unless it is known to + * be cursed */ { static NEARDATA const char need_to_take_off_outer_armor[] = "need to take off %s to %s %s."; @@ -2841,6 +3395,110 @@ boolean only_if_known_cursed; /* ignore covering unless known to be cursed */ } /* item is not inaccessible */ return FALSE; + +#undef BLOCKSACCESS +} + +/* not a getobj callback - unifies code among the other 4 getobj callbacks */ +staticfn int +equip_ok(struct obj *obj, boolean removing, boolean accessory) +{ + boolean is_worn; + long dummymask = 0; + + if (!obj) + return GETOBJ_EXCLUDE; + + /* ignore for putting on if already worn, or removing if not worn */ + is_worn = ((obj->owornmask & (W_ARMOR | W_ACCESSORY)) != 0); + if (removing ^ is_worn) + return GETOBJ_EXCLUDE_INACCESS; + + /* exclude most object classes outright */ + if (obj->oclass != ARMOR_CLASS && obj->oclass != RING_CLASS + && obj->oclass != AMULET_CLASS) { + /* ... except for a few wearable exceptions outside these classes */ + if (obj->otyp != MEAT_RING && obj->otyp != BLINDFOLD + && obj->otyp != TOWEL && obj->otyp != LENSES) + return GETOBJ_EXCLUDE; + } + + /* armor with 'P' or 'R' or accessory with 'W' or 'T' */ + if (accessory ^ (obj->oclass != ARMOR_CLASS)) + return GETOBJ_DOWNPLAY; + + /* armor we can't wear, e.g. from polyform */ + if (obj->oclass == ARMOR_CLASS && !removing + && !canwearobj(obj, &dummymask, FALSE)) + return GETOBJ_DOWNPLAY; + + /* Possible extension: downplay items (both accessories and armor) which + * can't be worn because the slot is filled with something else. */ + + /* removing inaccessible equipment */ + if (removing && !gi.item_action_in_progress) { + if (inaccessible_equipment(obj, (const char *) 0, + (obj->oclass == RING_CLASS))) + return GETOBJ_EXCLUDE_INACCESS; + } + + /* all good to go */ + return GETOBJ_SUGGEST; +} + +/* getobj callback for P command */ +staticfn int +puton_ok(struct obj *obj) +{ + return equip_ok(obj, FALSE, TRUE); +} + +/* getobj callback for R command */ +staticfn int +remove_ok(struct obj *obj) +{ + return equip_ok(obj, TRUE, TRUE); +} + +/* getobj callback for W command */ +staticfn int +wear_ok(struct obj *obj) +{ + return equip_ok(obj, FALSE, FALSE); +} + +/* getobj callback for T command */ +staticfn int +takeoff_ok(struct obj *obj) +{ + return equip_ok(obj, TRUE, FALSE); +} + +/* getobj callback for blessed destroy armor. + suggest any worn armor, even if covered by other armor */ +int +any_worn_armor_ok(struct obj *obj) +{ + if (obj && (obj->owornmask & W_ARMOR)) + return GETOBJ_SUGGEST; + return GETOBJ_EXCLUDE; +} + +/* number of armor pieces worn by hero */ +int +count_worn_armor(void) +{ + int ret = 0; + + if (uarm) ret++; + if (uarmc) ret++; + if (uarmh) ret++; + if (uarms) ret++; + if (uarmg) ret++; + if (uarmf) ret++; + if (uarmu) ret++; + + return ret; } /*do_wear.c*/ diff --git a/src/dog.c b/src/dog.c index 5c50eb69c..13ea0b137 100644 --- a/src/dog.c +++ b/src/dog.c @@ -1,27 +1,38 @@ -/* NetHack 3.6 dog.c $NHDT-Date: 1554580624 2019/04/06 19:57:04 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.85 $ */ +/* NetHack 5.0 dog.c $NHDT-Date: 1753856387 2025/07/29 22:19:47 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.190 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2011. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -STATIC_DCL int NDECL(pet_type); +staticfn int pet_type(void); +staticfn struct permonst * pick_familiar_pm(struct obj *, boolean); +staticfn void set_mon_lastmove(struct monst *); +staticfn int mon_leave(struct monst *) NONNULLARG1; +staticfn boolean keep_mon_accessible(struct monst *); + +enum arrival { + Before_you = 0, /* monsters kept on migrating_mons for accessibility; + * they haven't actually left their level */ + With_you = 1, /* pets and level followers */ + After_you = 2, /* regular migrating monsters */ + Wiz_arrive = -1 /* resurrect(wizard.c) */ +}; void -newedog(mtmp) -struct monst *mtmp; +newedog(struct monst *mtmp) { if (!mtmp->mextra) mtmp->mextra = newmextra(); if (!EDOG(mtmp)) { EDOG(mtmp) = (struct edog *) alloc(sizeof(struct edog)); (void) memset((genericptr_t) EDOG(mtmp), 0, sizeof(struct edog)); + EDOG(mtmp)->parentmid = mtmp->m_id; } } void -free_edog(mtmp) -struct monst *mtmp; +free_edog(struct monst *mtmp) { if (mtmp->mextra && EDOG(mtmp)) { free((genericptr_t) EDOG(mtmp)); @@ -31,84 +42,138 @@ struct monst *mtmp; } void -initedog(mtmp) -register struct monst *mtmp; +initedog(struct monst *mtmp, boolean everything) { - mtmp->mtame = is_domestic(mtmp->data) ? 10 : 5; + struct edog *edogp = EDOG(mtmp); + long minhungry = svm.moves + 1000L; + schar minimumtame = is_domestic(mtmp->data) ? 10 : 5; + + mtmp->mtame = max(minimumtame, mtmp->mtame); mtmp->mpeaceful = 1; mtmp->mavenge = 0; set_malign(mtmp); /* recalc alignment now that it's tamed */ - mtmp->mleashed = 0; - mtmp->meating = 0; - EDOG(mtmp)->droptime = 0; - EDOG(mtmp)->dropdist = 10000; - EDOG(mtmp)->apport = ACURR(A_CHA); - EDOG(mtmp)->whistletime = 0; - EDOG(mtmp)->hungrytime = 1000 + monstermoves; - EDOG(mtmp)->ogoal.x = -1; /* force error if used before set */ - EDOG(mtmp)->ogoal.y = -1; - EDOG(mtmp)->abuse = 0; - EDOG(mtmp)->revivals = 0; - EDOG(mtmp)->mhpmax_penalty = 0; - EDOG(mtmp)->killed_by_u = 0; + if (everything) { + mtmp->mleashed = 0; + mtmp->meating = 0; + edogp->droptime = 0; + edogp->dropdist = 10000; + edogp->apport = ACURR(A_CHA); + edogp->whistletime = 0; + /* edogp->hungrytime = 0L; // set below */ + edogp->ogoal.x = -1; /* force error if used before set */ + edogp->ogoal.y = -1; + edogp->abuse = 0; + edogp->revivals = 0; + edogp->mhpmax_penalty = 0; + edogp->killed_by_u = 0; + } else { + if (edogp->apport <= 0) + edogp->apport = 1; + } + /* always set for newly tamed pet or feral former pet; hungrytime might + already be higher when taming magic affects already tame monst */ + if (edogp->hungrytime < minhungry) + edogp->hungrytime = minhungry; + /* livelog first pet, but only if you didn't start with one (the starting + * pet will be initialized before in_moveloop is true) */ + if (!u.uconduct.pets && program_state.in_moveloop) { + /* "obtained" a pet rather than "tamed" it because it might have come + * from a figurine or some other method in which it was created tame + * using an() is safe unless it somehow becomes possible to tame a + * unique monster */ + livelog_printf(LL_CONDUCT, "obtained %s first pet (%s)", + uhis(), an(mon_pmname(mtmp))); + } + u.uconduct.pets++; } -STATIC_OVL int -pet_type() +staticfn int +pet_type(void) { - if (urole.petnum != NON_PM) - return urole.petnum; - else if (preferred_pet == 'c') + if (gu.urole.petnum != NON_PM) + return gu.urole.petnum; + else if (gp.preferred_pet == 'c') return PM_KITTEN; - else if (preferred_pet == 'd') + else if (gp.preferred_pet == 'd') return PM_LITTLE_DOG; else return rn2(2) ? PM_KITTEN : PM_LITTLE_DOG; } +staticfn struct permonst * +pick_familiar_pm(struct obj *otmp, boolean quietly) +{ + struct permonst *pm = (struct permonst *) 0; + + if (otmp) { /* figurine; otherwise spell */ + int mndx = otmp->corpsenm; + + assert(ismnum(mndx)); + pm = &mons[mndx]; + /* activating a figurine provides one way to exceed the + maximum number of the target critter created--unless + it has a special limit (erinys, Nazgul) */ + if ((svm.mvitals[mndx].mvflags & G_EXTINCT) + && mbirth_limit(mndx) != MAXMONNO) { + if (!quietly) + /* have just been given "You + the figurine and it transforms." message */ + pline("... into a pile of dust."); + return (struct permonst *) 0; + } + } else if (!rn2(3)) { + pm = &mons[pet_type()]; + } else { + int skill = spell_skilltype(SPE_CREATE_FAMILIAR); + int max = 3 * P_SKILL(skill); + + pm = rndmonst_adj(0, max); + if (!pm && !quietly) + There("seems to be nothing available for a familiar."); + } + return pm; +} + struct monst * -make_familiar(otmp, x, y, quietly) -register struct obj *otmp; -xchar x, y; -boolean quietly; +make_familiar(struct obj *otmp, coordxy x, coordxy y, boolean quietly) { struct permonst *pm; struct monst *mtmp = 0; int chance, trycnt = 100; + boolean reallytame = TRUE; do { - if (otmp) { /* figurine; otherwise spell */ - int mndx = otmp->corpsenm; - - pm = &mons[mndx]; - /* activating a figurine provides one way to exceed the - maximum number of the target critter created--unless - it has a special limit (erinys, Nazgul) */ - if ((mvitals[mndx].mvflags & G_EXTINCT) - && mbirth_limit(mndx) != MAXMONNO) { - if (!quietly) - /* have just been given "You - the figurine and it transforms." message */ - pline("... into a pile of dust."); - break; /* mtmp is null */ - } - } else if (!rn2(3)) { - pm = &mons[pet_type()]; - } else { - pm = rndmonst(); - if (!pm) { + mmflags_nht mmflags; + int cgend; + + if (!(pm = pick_familiar_pm(otmp, quietly))) + break; + + mmflags = MM_EDOG | MM_IGNOREWATER | NO_MINVENT | MM_NOMSG; + cgend = otmp ? (otmp->spe & CORPSTAT_GENDER) : 0; + mmflags |= ((cgend == CORPSTAT_FEMALE) ? MM_FEMALE + : (cgend == CORPSTAT_MALE) ? MM_MALE : 0L); + + mtmp = makemon(pm, x, y, mmflags); + if (otmp) { /* figurine */ + if (!mtmp) { + /* monster has been genocided or target spot is occupied */ if (!quietly) - There("seems to be nothing available for a familiar."); + pline_The( + "figurine writhes and then shatters into pieces!"); break; + } else if (mtmp->isminion) { + /* Fixup for figurine of an Angel: makemon() is willing to + create a random Angel as either an ordinary monster or as + a minion of random allegiance. We don't want the latter + here in case it successfully becomes a pet. */ + mtmp->isminion = 0; + free_emin(mtmp); + /* [This could and possibly should be redone as a new + MM_flag passed to makemon() to suppress making a minion + so that no post-creation fixup would be needed.] */ } } - - mtmp = makemon(pm, x, y, MM_EDOG | MM_IGNOREWATER | NO_MINVENT); - if (otmp && !mtmp) { /* monster was genocided or square occupied */ - if (!quietly) - pline_The("figurine writhes and then shatters into pieces!"); - break; - } } while (!mtmp && --trycnt > 0); if (!mtmp) @@ -117,16 +182,14 @@ boolean quietly; if (is_pool(mtmp->mx, mtmp->my) && minliquid(mtmp)) return (struct monst *) 0; - initedog(mtmp); - mtmp->msleeping = 0; if (otmp) { /* figurine; resulting monster might not become a pet */ chance = rn2(10); /* 0==tame, 1==peaceful, 2==hostile */ if (chance > 2) chance = otmp->blessed ? 0 : !otmp->cursed ? 1 : 2; /* 0,1,2: b=80%,10,10; nc=10%,80,10; c=10%,10,80 */ if (chance > 0) { - mtmp->mtame = 0; /* not tame after all */ - if (chance == 2) { /* hostile (cursed figurine) */ + reallytame = FALSE; /* not tame after all */ + if (chance == 2) { /* hostile (cursed figurine) */ if (!quietly) You("get a bad feeling about this."); mtmp->mpeaceful = 0; @@ -137,6 +200,9 @@ boolean quietly; if (has_oname(otmp)) mtmp = christen_monst(mtmp, ONAME(otmp)); } + if (reallytame) + initedog(mtmp, TRUE); + mtmp->msleeping = 0; set_malign(mtmp); /* more alignment changes */ newsym(mtmp->mx, mtmp->my); @@ -148,30 +214,30 @@ boolean quietly; return mtmp; } +/* despite rather general name, used exclusively for hero's starting pet */ struct monst * -makedog() +makedog(void) { - register struct monst *mtmp; - register struct obj *otmp; + struct monst *mtmp; const char *petname; int pettype; - static int petname_used = 0; - if (preferred_pet == 'n') + if (gp.preferred_pet == 'n') { + /* static init yields 0 (PM_GIANT_ANT); fix that up now */ + svc.context.startingpet_typ = NON_PM; return ((struct monst *) 0); + } - pettype = pet_type(); - if (pettype == PM_LITTLE_DOG) - petname = dogname; - else if (pettype == PM_PONY) - petname = horsename; - else - petname = catname; + pettype = svc.context.startingpet_typ = pet_type(); + petname = (pettype == PM_LITTLE_DOG) ? gd.dogname + : (pettype == PM_KITTEN) ? gc.catname + : (pettype == PM_PONY) ? gh.horsename + : ""; /* default pet names */ if (!*petname && pettype == PM_LITTLE_DOG) { /* All of these names were for dogs. */ - if (Role_if(PM_CAVEMAN)) + if (Role_if(PM_CAVE_DWELLER)) petname = "Slasher"; /* The Warrior */ if (Role_if(PM_SAMURAI)) petname = "Hachi"; /* Shibuya Station */ @@ -181,52 +247,73 @@ makedog() petname = "Sirius"; /* Orion's dog */ } - mtmp = makemon(&mons[pettype], u.ux, u.uy, MM_EDOG); + /* specifying NO_MINVENT prevents makemon() from having a 1% chance + of creating a pony with an already worn saddle; dogs and cats + aren't affected because they don't have any initial inventory + [if anybody adds stranger pets that are expected to have such, + they'll need to modify this] */ + mtmp = makemon(&mons[pettype], u.ux, u.uy, MM_EDOG | NO_MINVENT); if (!mtmp) - return ((struct monst *) 0); /* pets were genocided */ - - context.startingpet_mid = mtmp->m_id; - /* Horses already wear a saddle */ - if (pettype == PM_PONY && !!(otmp = mksobj(SADDLE, TRUE, FALSE))) { - otmp->dknown = otmp->bknown = otmp->rknown = 1; - put_saddle_on_mon(otmp, mtmp); + return ((struct monst *) 0); /* pets were genocided [how?] */ + + if (!svc.context.startingpet_mid) { + svc.context.startingpet_mid = mtmp->m_id; + if (!u.uroleplay.pauper) { + /* initial horses start wearing a saddle (pauper hero excluded) */ + if (pettype == PM_PONY) { + /* NULL obj arg means put_saddle_on_mon() + * will carry out the saddle creation */ + put_saddle_on_mon((struct obj *) 0, mtmp); + } + } + /* starting pet's type has been seen up close (unless PermaBlind) + and for tourist treat it as having already been photographed */ + gb.bhitpos.x = mtmp->mx, gb.bhitpos.y = mtmp->my; + gn.notonhead = FALSE; + see_monster_closeup(mtmp, carrying(EXPENSIVE_CAMERA) ? TRUE : FALSE); + } else { + impossible("makedog() when startingpet_mid is already non-zero?"); } - if (!petname_used++ && *petname) + if (!gp.petname_used++ && *petname) mtmp = christen_monst(mtmp, petname); - initedog(mtmp); + initedog(mtmp, TRUE); return mtmp; } +staticfn void +set_mon_lastmove(struct monst *mtmp) +{ + mtmp->mlstmv = svm.moves; +} + /* record `last move time' for all monsters prior to level save so that mon_arrive() can catch up for lost time when they're restored later */ void -update_mlstmv() +update_mlstmv(void) { - struct monst *mon; - - /* monst->mlstmv used to be updated every time `monst' actually moved, - but that is no longer the case so we just do a blanket assignment */ - for (mon = fmon; mon; mon = mon->nmon) { - if (DEADMONSTER(mon)) - continue; - mon->mlstmv = monstermoves; - } + iter_mons(set_mon_lastmove); } +/* note: always reset when used so doesn't need to be part of struct 'g' */ +static struct monst *failed_arrivals = 0; + void -losedogs() +losedogs(void) { - register struct monst *mtmp, *mtmp0, *mtmp2; - int dismissKops = 0; + struct monst *mtmp, **mprev; + int dismissKops = 0, xyloc; + failed_arrivals = 0; /* - * First, scan migrating_mons for shopkeepers who want to dismiss Kops, - * and scan mydogs for shopkeepers who want to retain kops. + * First, scan gm.migrating_mons for shopkeepers who want to dismiss Kops, + * and scan gm.mydogs for shopkeepers who want to retain kops. * Second, dismiss kops if warranted, making more room for arrival. - * Third, place monsters accompanying the hero. + * Third, replace monsters who went onto migrating_mons in order to + * be accessible from other levels but didn't actually leave the level. + * Fourth, place monsters accompanying the hero. * Last, place migrating monsters coming to this level. * * Hero might eventually be displaced (due to the third step, but @@ -236,7 +323,7 @@ losedogs() */ /* check for returning shk(s) */ - for (mtmp = migrating_mons; mtmp; mtmp = mtmp->nmon) { + for (mtmp = gm.migrating_mons; mtmp; mtmp = mtmp->nmon) { if (mtmp->mux != u.uz.dnum || mtmp->muy != u.uz.dlevel) continue; if (mtmp->isshk) { @@ -252,11 +339,11 @@ losedogs() } } } - /* make the same check for mydogs */ - for (mtmp = mydogs; mtmp && dismissKops >= 0; mtmp = mtmp->nmon) { + /* make the same check for gm.mydogs */ + for (mtmp = gm.mydogs; mtmp && dismissKops >= 0; mtmp = mtmp->nmon) { if (mtmp->isshk) { /* hostile shk might accompany hero [ESHK(mtmp)->dismiss_kops - can't be set here; it's only used for migrating_mons] */ + can't be set here; it's only used for gm.migrating_mons] */ if (!mtmp->mpeaceful) dismissKops = -1; } @@ -268,46 +355,79 @@ losedogs() if (dismissKops > 0) make_happy_shoppers(TRUE); - /* place pets and/or any other monsters who accompany hero */ - while ((mtmp = mydogs) != 0) { - mydogs = mtmp->nmon; - mon_arrive(mtmp, TRUE); + /* put monsters who went onto migrating_mons in order to be accessible + when other levels are active back to their positions on this level; + they're handled before mydogs so that monsters accompanying the + hero can't steal the spot that belongs to them; these migraters + should always be able to arrive because they were present on the + level at the time the hero left [if they can't arrive for some + reason, mon_arrive() will put them on the 'failed_arrivals' list] */ + for (mprev = &gm.migrating_mons; (mtmp = *mprev) != 0; ) { + xyloc = mtmp->mtrack[0].x; /* (for legibility) */ + if (mtmp->mux == u.uz.dnum && mtmp->muy == u.uz.dlevel + && xyloc == MIGR_EXACT_XY) { + /* remove mtmp from migrating_mons */ + *mprev = mtmp->nmon; + mon_arrive(mtmp, Before_you); + } else { + mprev = &mtmp->nmon; + } } - /* time for migrating monsters to arrive; - monsters who belong on this level but fail to arrive get put - back onto the list (at head), so traversing it is tricky */ - for (mtmp = migrating_mons; mtmp; mtmp = mtmp2) { - mtmp2 = mtmp->nmon; - if (mtmp->mux == u.uz.dnum && mtmp->muy == u.uz.dlevel) { - /* remove mtmp from migrating_mons list */ - if (mtmp == migrating_mons) { - migrating_mons = mtmp->nmon; - } else { - for (mtmp0 = migrating_mons; mtmp0; mtmp0 = mtmp0->nmon) - if (mtmp0->nmon == mtmp) { - mtmp0->nmon = mtmp->nmon; - break; - } - if (!mtmp0) - panic("losedogs: can't find migrating mon"); - } - mon_arrive(mtmp, FALSE); + /* place pets and/or any other monsters who accompany hero; + any that fail to arrive (level may be full) will be moved + first to failed_arrivals, then to migrating_mons scheduled + to arrive back on this level if hero leaves and returns */ + while ((mtmp = gm.mydogs) != 0) { + gm.mydogs = mtmp->nmon; + mon_arrive(mtmp, With_you); + } + + /* time for migrating monsters to arrive; monsters who belong on + this level but fail to arrive get put on the failed_arrivals list + temporarily [by mon_arrive()], then back onto the migrating_mons + list below */ + for (mprev = &gm.migrating_mons; (mtmp = *mprev) != 0; ) { + xyloc = mtmp->mtrack[0].x; + if (mtmp->mux == u.uz.dnum && mtmp->muy == u.uz.dlevel + && xyloc != MIGR_EXACT_XY) { + /* remove mtmp from migrating_mons */ + *mprev = mtmp->nmon; + /* note: if there's no room, it ends up on failed_arrivals list */ + mon_arrive(mtmp, After_you); + } else { + mprev = &mtmp->nmon; } } + + /* put any monsters who couldn't arrive back on migrating_mons, + clearing out the temporary 'failed_arrivals' list in the process */ + while ((mtmp = failed_arrivals) != 0) { + failed_arrivals = mtmp->nmon; + /* mon_arrive() put mtmp onto fmon, but if there wasn't room to + arrive, relmon() was used to take it off again; put it back now + because m_into_limbo() expects it to be there */ + mtmp->nmon = fmon; + fmon = mtmp; + /* set this monster to migrate back to this level if hero leaves + and then returns */ + m_into_limbo(mtmp); + } } /* called from resurrect() in addition to losedogs() */ void -mon_arrive(mtmp, with_you) -struct monst *mtmp; -boolean with_you; +mon_arrive(struct monst *mtmp, int when) { struct trap *t; - xchar xlocale, ylocale, xyloc, xyflags, wander; + coordxy xlocale, ylocale, xyloc, xyflags; + xint16 wander; int num_segs; boolean failed_to_place = FALSE; + stairway *stway; + d_level fromdlev; + mtmp->mstate |= MON_STILL_ARRIVING; mtmp->nmon = fmon; fmon = mtmp; if (mtmp->isshk) @@ -325,6 +445,7 @@ boolean with_you; /* some monsters might need to do something special upon arrival _after_ the current level has been fully set up; see dochug() */ mtmp->mstrategy |= STRAT_ARRIVE; + mtmp->mstate &= ~(MON_MIGRATING | MON_LIMBO); /* make sure mnexto(rloc_to(set_apparxy())) doesn't use stale data */ mtmp->mux = u.ux, mtmp->muy = u.uy; @@ -332,11 +453,19 @@ boolean with_you; xyflags = mtmp->mtrack[0].y; xlocale = mtmp->mtrack[1].x; ylocale = mtmp->mtrack[1].y; - memset(mtmp->mtrack, 0, sizeof mtmp->mtrack); + fromdlev.dnum = mtmp->mtrack[2].x; + fromdlev.dlevel = mtmp->mtrack[2].y; + mon_track_clear(mtmp); + /* in case Protection_from_shape_changers is different now from when + 'mtmp' went onto the migrating monsters list; that's handled in + getlev() when returning to a previously visited level and by the + special level code for monsters specified in the level, but needed + here for monsters migrating to a newly created level */ + restore_cham(mtmp); if (mtmp == u.usteed) return; /* don't place steed on the map */ - if (with_you) { + if (when == With_you) { /* When a monster accompanies you, sometimes it will arrive at your intended destination and you'll end up next to that spot. This code doesn't control the final outcome; @@ -346,8 +475,12 @@ boolean with_you; && !rn2(mtmp->mtame ? 10 : mtmp->mpeaceful ? 5 : 2)) rloc_to(mtmp, u.ux, u.uy); else - mnexto(mtmp); + mnexto(mtmp, RLOC_NOMSG); + mtmp->mstate &= ~MON_STILL_ARRIVING; return; + } else if (when == Wiz_arrive) { + /* resurrect() is bringing existing wizard to harass the hero */ + xyloc = MIGR_WITH_HERO; } /* * The monster arrived on this level independently of the player. @@ -355,15 +488,14 @@ boolean with_you; * specify its final destination. */ - if (mtmp->mlstmv < monstermoves - 1L) { + if (mtmp->mlstmv < svm.moves - 1L) { /* heal monster for time spent in limbo */ - long nmv = monstermoves - 1L - mtmp->mlstmv; + long nmv = svm.moves - 1L - mtmp->mlstmv; mon_catchup_elapsed_time(mtmp, nmv); - mtmp->mlstmv = monstermoves - 1L; /* let monster move a bit on new level (see placement code below) */ - wander = (xchar) min(nmv, 8); + wander = (xint16) min(nmv, 8L); } else wander = 0; @@ -377,19 +509,34 @@ boolean with_you; xlocale = u.ux, ylocale = u.uy; break; case MIGR_STAIRS_UP: - xlocale = xupstair, ylocale = yupstair; + if ((stway = stairway_find_from(&fromdlev, FALSE)) != 0) { + xlocale = stway->sx; + ylocale = stway->sy; + } break; case MIGR_STAIRS_DOWN: - xlocale = xdnstair, ylocale = ydnstair; + if ((stway = stairway_find_from(&fromdlev, FALSE)) != 0) { + xlocale = stway->sx; + ylocale = stway->sy; + } break; case MIGR_LADDER_UP: - xlocale = xupladder, ylocale = yupladder; + if ((stway = stairway_find_from(&fromdlev, TRUE)) != 0) { + xlocale = stway->sx; + ylocale = stway->sy; + } break; case MIGR_LADDER_DOWN: - xlocale = xdnladder, ylocale = ydnladder; + if ((stway = stairway_find_from(&fromdlev, TRUE)) != 0) { + xlocale = stway->sx; + ylocale = stway->sy; + } break; case MIGR_SSTAIRS: - xlocale = sstairs.sx, ylocale = sstairs.sy; + if ((stway = stairway_find(&fromdlev)) != 0) { + xlocale = stway->sx; + ylocale = stway->sy; + } break; case MIGR_PORTAL: if (In_endgame(&u.uz)) { @@ -398,29 +545,37 @@ boolean with_you; that we know that the current endgame levels always build upwards and never have any exclusion subregion inside their TELEPORT_REGION settings. */ - xlocale = rn1(updest.hx - updest.lx + 1, updest.lx); - ylocale = rn1(updest.hy - updest.ly + 1, updest.ly); + xlocale = rn1(svu.updest.hx - svu.updest.lx + 1, svu.updest.lx); + ylocale = rn1(svu.updest.hy - svu.updest.ly + 1, svu.updest.ly); break; } /* find the arrival portal */ - for (t = ftrap; t; t = t->ntrap) + for (t = gf.ftrap; t; t = t->ntrap) if (t->ttyp == MAGIC_PORTAL) break; if (t) { xlocale = t->tx, ylocale = t->ty; break; - } else { + } else if (iflags.debug_fuzzer + && (stway = stairway_find_dir(!builds_up(&u.uz))) != 0) { + /* debugfuzzer returns from or enters another branch */ + xlocale = stway->sx, ylocale = stway->sy; + break; + } else if (!(u.uevent.qexpelled + && (Is_qstart(&u.uz0) || Is_qstart(&u.uz)))) { impossible("mon_arrive: no corresponding portal?"); - } /*FALLTHRU*/ + } + FALLTHROUGH; + /*FALLTHRU*/ default: case MIGR_RANDOM: xlocale = ylocale = 0; break; } - if ((mtmp->mspare1 & MIGR_LEFTOVERS) != 0L) { + if ((mtmp->migflags & MIGR_LEFTOVERS) != 0L) { /* Pick up the rest of the MIGR_TO_SPECIES objects */ - if (migrating_objs) + if (gm.migrating_objs) deliver_obj_to_mon(mtmp, 0, DF_ALL); } @@ -433,7 +588,7 @@ boolean with_you; coord c; /* somexy() handles irregular rooms */ - if (somexy(&rooms[*r - ROOMOFFSET], &c)) + if (somexy(&svr.rooms[*r - ROOMOFFSET], &c)) xlocale = c.x, ylocale = c.y; else xlocale = ylocale = 0; @@ -451,20 +606,27 @@ boolean with_you; mtmp->mx = 0; /*(already is 0)*/ mtmp->my = xyflags; + if (xlocale) - failed_to_place = !mnearto(mtmp, xlocale, ylocale, FALSE); + failed_to_place = !mnearto(mtmp, xlocale, ylocale, FALSE, RLOC_NOMSG); else - failed_to_place = !rloc(mtmp, TRUE); - - if (failed_to_place) - m_into_limbo(mtmp); /* try again next time hero comes to this level */ + failed_to_place = !rloc(mtmp, RLOC_NOMSG); + + if (failed_to_place) { + if (when != Wiz_arrive) + /* losedogs() will deal with this */ + relmon(mtmp, &failed_arrivals); + else /* when==Wiz_arrive => not being called by losedogs() */ + m_into_limbo(mtmp); + } + mtmp->mstate &= ~MON_STILL_ARRIVING; } /* heal monster for time spent elsewhere */ void -mon_catchup_elapsed_time(mtmp, nmv) -struct monst *mtmp; -long nmv; /* number of moves */ +mon_catchup_elapsed_time( + struct monst *mtmp, + long nmv) /* number of moves */ { int imv = 0; /* avoid zillions of casts and lint warnings */ @@ -474,7 +636,7 @@ long nmv; /* number of moves */ panic("catchup from future time?"); /*NOTREACHED*/ return; - } else if (nmv == 0L) { /* safe, but should'nt happen */ + } else if (nmv == 0L) { /* safe, but shouldn't happen */ impossible("catchup from now?"); } else #endif @@ -513,10 +675,12 @@ long nmv; /* number of moves */ mtmp->mstun = 0; /* might finish eating or be able to use special ability again */ - if (imv > mtmp->meating) - finish_meating(mtmp); - else - mtmp->meating -= imv; + if (mtmp->meating) { + if (imv > mtmp->meating) + finish_meating(mtmp); + else + mtmp->meating -= imv; + } if (imv > mtmp->mspec_used) mtmp->mspec_used = 0; else @@ -539,8 +703,8 @@ long nmv; /* number of moves */ && (carnivorous(mtmp->data) || herbivorous(mtmp->data))) { struct edog *edog = EDOG(mtmp); - if ((monstermoves > edog->hungrytime + 500 && mtmp->mhp < 3) - || (monstermoves > edog->hungrytime + 750)) + if ((svm.moves > edog->hungrytime + 500 && mtmp->mhp < 3) + || (svm.moves > edog->hungrytime + 750)) mtmp->mtame = mtmp->mpeaceful = 0; } @@ -554,21 +718,78 @@ long nmv; /* number of moves */ /* recover lost hit points */ if (!regenerates(mtmp->data)) imv /= 20; - if (mtmp->mhp + imv >= mtmp->mhpmax) - mtmp->mhp = mtmp->mhpmax; - else - mtmp->mhp += imv; + healmon(mtmp, imv, 0); + + set_mon_lastmove(mtmp); +} + +/* bookkeeping when mtmp is about to leave the current level; + common to keepdogs() and migrate_to_level() */ +staticfn int +mon_leave(struct monst *mtmp) +{ + struct obj *obj; + int num_segs = 0; /* return value */ + + /* set minvent's obj->no_charge to 0 */ + for (obj = mtmp->minvent; obj; obj = obj->nobj) { + if (Has_contents(obj)) + picked_container(obj); /* does the right thing */ + obj->no_charge = 0; + } + + /* if this is a shopkeeper, clear the 'resident' field of her shop; + if/when she returns, it will be set back by mon_arrive() */ + if (mtmp->isshk) + set_residency(mtmp, TRUE); + + /* if this is a long worm, handle its tail segments before mtmp itself; + we pass possibly truncated segment count to caller via return value */ + if (mtmp->wormno) { + int cnt = count_wsegs(mtmp), mx = mtmp->mx, my = mtmp->my; + + /* since monst->wormno is overloaded to hold the number of + tail segments during migration, a very long worm with + more segments than can fit in that field gets truncated */ + num_segs = min(cnt, MAX_NUM_WORMS - 1); + wormgone(mtmp); + /* put the head back; note: mtmp might not be on the map if this + is happening during a failed attempt to migrate to this level */ + if (mx) + place_monster(mtmp, mx, my); + } + + return num_segs; +} + +/* when hero leaves a level, some monsters should be placed on the + migrating_mons list instead of being stashed inside the level's file */ +staticfn boolean +keep_mon_accessible(struct monst *mon) +{ + /* the Wizard is kept accessible so that his harassment can fetch + him instead of creating a new instance but also so that he can + be put back at his current location if hero returns to his level */ + if (mon->iswiz) + return TRUE; + /* monsters with special attachment to a particular level only need + to be kept accessible when on some other level */ + if (mon->mextra + && ((mon->isshk && !on_level(&u.uz, &ESHK(mon)->shoplevel)) + || (mon->ispriest && !on_level(&u.uz, &EPRI(mon)->shrlevel)) + || (mon->isgd && !on_level(&u.uz, &EGD(mon)->gdlevel)))) + return TRUE; + /* normal monsters go into the level save file instead of being held + on the migrating_mons list for off-level accessibility */ + return FALSE; } /* called when you move to another level */ void -keepdogs(pets_only) -boolean pets_only; /* true for ascension or final escape */ +keepdogs( + boolean pets_only) /* true for ascension or final escape */ { - register struct monst *mtmp, *mtmp2; - register struct obj *obj; - int num_segs; - boolean stay_behind; + struct monst *mtmp, *mtmp2; for (mtmp = fmon; mtmp; mtmp = mtmp2) { mtmp2 = mtmp->nmon; @@ -582,7 +803,7 @@ boolean pets_only; /* true for ascension or final escape */ unlike level change for steed, don't bother trying to achieve a normal trap escape first */ mtmp->mtrapped = 0; - mtmp->meating = 0; + finish_meating(mtmp); mtmp->msleeping = 0; mtmp->mfrozen = 0; mtmp->mcanmove = 1; @@ -592,15 +813,17 @@ boolean pets_only; /* true for ascension or final escape */ the amulet; if you don't have it, will chase you only if in range. -3. */ || (u.uhave.amulet && mtmp->iswiz)) - && ((!mtmp->msleeping && mtmp->mcanmove) + && (!helpless(mtmp) /* eg if level teleport or new trap, steed has no control to avoid following */ || (mtmp == u.usteed)) /* monster won't follow if it hasn't noticed you yet */ && !(mtmp->mstrategy & STRAT_WAITFORU)) { - stay_behind = FALSE; + int num_segs; + boolean stay_behind = FALSE; + if (mtmp->mtrapped) - (void) mintrap(mtmp); /* try to escape */ + (void) mintrap(mtmp, NO_TRAP_FLAGS); /* try to escape */ if (mtmp == u.usteed) { /* make sure steed is eligible to accompany hero */ mtmp->mtrapped = 0; /* escape trap */ @@ -608,8 +831,8 @@ boolean pets_only; /* true for ascension or final escape */ mdrop_special_objs(mtmp); /* drop Amulet */ } else if (mtmp->meating || mtmp->mtrapped) { if (canseemon(mtmp)) - pline("%s is still %s.", Monnam(mtmp), - mtmp->meating ? "eating" : "trapped"); + pline_mon(mtmp, "%s is still %s.", Monnam(mtmp), + mtmp->meating ? "eating" : "trapped"); stay_behind = TRUE; } else if (mon_has_amulet(mtmp)) { if (canseemon(mtmp)) @@ -633,34 +856,22 @@ boolean pets_only; /* true for ascension or final escape */ } continue; } - if (mtmp->isshk) - set_residency(mtmp, TRUE); - if (mtmp->wormno) { - register int cnt; - /* NOTE: worm is truncated to # segs = max wormno size */ - cnt = count_wsegs(mtmp); - num_segs = min(cnt, MAX_NUM_WORMS - 1); - wormgone(mtmp); - place_monster(mtmp, mtmp->mx, mtmp->my); - } else - num_segs = 0; - - /* set minvent's obj->no_charge to 0 */ - for (obj = mtmp->minvent; obj; obj = obj->nobj) { - if (Has_contents(obj)) - picked_container(obj); /* does the right thing */ - obj->no_charge = 0; - } - - relmon(mtmp, &mydogs); /* move it from map to mydogs */ - mtmp->mx = mtmp->my = 0; /* avoid mnexto()/MON_AT() problem */ + /* prepare to take mtmp off the map */ + num_segs = mon_leave(mtmp); + /* take off map and move mtmp from fmon list to mydogs */ + relmon(mtmp, &gm.mydogs); /* mtmp->mx,my retain current value */ + mtmp->mx = mtmp->my = 0; /* mx==0 implies migrating */ mtmp->wormno = num_segs; - mtmp->mlstmv = monstermoves; - } else if (mtmp->iswiz) { - /* we want to be able to find him when his next resurrection - chance comes up, but have him resume his present location - if player returns to this level before that time */ + mtmp->mlstmv = svm.moves; + } else if (keep_mon_accessible(mtmp)) { + /* we want to be able to find the Wizard when his next + resurrection chance comes up, but have him resume his + present location if player returns to this level before + that time; also needed for monsters (shopkeeper, temple + priest, vault guard) who have level data in mon->mextra + in case #wizmakemap is used to replace their home level + while they're away from it */ migrate_to_level(mtmp, ledger_no(&u.uz), MIGR_EXACT_XY, (coord *) 0); } else if (mtmp->mleashed) { @@ -673,87 +884,150 @@ boolean pets_only; /* true for ascension or final escape */ } void -migrate_to_level(mtmp, tolev, xyloc, cc) -register struct monst *mtmp; -xchar tolev; /* destination level */ -xchar xyloc; /* MIGR_xxx destination xy location: */ -coord *cc; /* optional destination coordinates */ +migrate_to_level( + struct monst *mtmp, + xint16 tolev, /* destination level */ + xint16 xyloc, /* MIGR_xxx destination xy location: */ + coord *cc) /* optional destination coordinates */ { - struct obj *obj; d_level new_lev; - xchar xyflags; - int num_segs = 0; /* count of worm segments */ - - if (mtmp->isshk) - set_residency(mtmp, TRUE); - - if (mtmp->wormno) { - int cnt = count_wsegs(mtmp); - - /* **** NOTE: worm is truncated to # segs = max wormno size **** */ - num_segs = min(cnt, MAX_NUM_WORMS - 1); /* used below */ - wormgone(mtmp); /* destroys tail and takes head off map */ - /* there used to be a place_monster() here for the relmon() below, - but it doesn't require the monster to be on the map anymore */ - } - - /* set minvent's obj->no_charge to 0 */ - for (obj = mtmp->minvent; obj; obj = obj->nobj) { - if (Has_contents(obj)) - picked_container(obj); /* does the right thing */ - obj->no_charge = 0; - } + coordxy xyflags; + coordxy mx = mtmp->mx, my = mtmp->my; /* needed below */ + int num_segs; /* count of worm segments */ if (mtmp->mleashed) { mtmp->mtame--; m_unleash(mtmp, TRUE); } - relmon(mtmp, &migrating_mons); /* move it from map to migrating_mons */ - new_lev.dnum = ledger_to_dnum((xchar) tolev); - new_lev.dlevel = ledger_to_dlev((xchar) tolev); - /* overload mtmp->[mx,my], mtmp->[mux,muy], and mtmp->mtrack[] as */ - /* destination codes (setup flag bits before altering mx or my) */ + /* prepare to take mtmp off the map */ + num_segs = mon_leave(mtmp); + /* take off map and move mtmp from fmon list to migrating_mons */ + relmon(mtmp, &gm.migrating_mons); /* mtmp->mx,my retain their value */ + mtmp->mstate |= MON_MIGRATING; + + new_lev.dnum = ledger_to_dnum((xint16) tolev); + new_lev.dlevel = ledger_to_dlev((xint16) tolev); + /* overload mtmp->[mx,my], mtmp->[mux,muy], and mtmp->mtrack[] as + destination codes */ xyflags = (depth(&new_lev) < depth(&u.uz)); /* 1 => up */ - if (In_W_tower(mtmp->mx, mtmp->my, &u.uz)) + if (In_W_tower(mx, my, &u.uz)) xyflags |= 2; mtmp->wormno = num_segs; - mtmp->mlstmv = monstermoves; - mtmp->mtrack[1].x = cc ? cc->x : mtmp->mx; - mtmp->mtrack[1].y = cc ? cc->y : mtmp->my; + mtmp->mlstmv = svm.moves; + mtmp->mtrack[2].x = u.uz.dnum; /* migrating from this dungeon */ + mtmp->mtrack[2].y = u.uz.dlevel; /* migrating from this dungeon level */ + mtmp->mtrack[1].x = cc ? cc->x : mx; + mtmp->mtrack[1].y = cc ? cc->y : my; mtmp->mtrack[0].x = xyloc; mtmp->mtrack[0].y = xyflags; mtmp->mux = new_lev.dnum; mtmp->muy = new_lev.dlevel; - mtmp->mx = mtmp->my = 0; /* this implies migration */ - if (mtmp == context.polearm.hitmon) - context.polearm.hitmon = (struct monst *) 0; + mtmp->mx = mtmp->my = 0; /* mx==0 implies migrating */ + + /* don't extinguish a mobile light; it still exists but has changed + from local (monst->mx > 0) to global (mx==0, not on this level) */ + if (emits_light(mtmp->data)) + vision_recalc(0); +} + +/* when entering the endgame, levels from the dungeon and its branches are + discarded because they can't be reached again; do the same for monsters + and objects scheduled to migrate to those levels */ +void +discard_migrations(void) +{ + struct monst *mtmp, **mprev; + struct obj *otmp, **oprev; + d_level dest; + + for (mprev = &gm.migrating_mons; (mtmp = *mprev) != 0; ) { + dest.dnum = mtmp->mux; + dest.dlevel = mtmp->muy; + /* the Wizard is kept regardless of location so that he is + ready to be brought back; nothing should be scheduled to + migrate to the endgame but if we find such, we'll keep it */ + if (mtmp->iswiz || In_endgame(&dest)) { + mprev = &mtmp->nmon; /* keep mtmp on migrating_mons */ + } else { + *mprev = mtmp->nmon; /* remove mtmp from migrating_mons */ + mtmp->nmon = 0; + discard_minvent(mtmp, FALSE); + /* bypass mongone() and its call to m_detach() plus dmonsfree() */ + if (emits_light(mtmp->data)) + del_light_source(LS_MONSTER, monst_to_any(mtmp)); + dealloc_monst(mtmp); + } + } + + /* objects get similar treatment */ + for (oprev = &gm.migrating_objs; (otmp = *oprev) != 0; ) { + dest.dnum = otmp->ox; + dest.dlevel = otmp->oy; + /* there is no special case like the Wizard (certainly not the + Amulet; the hero has to be carrying it to enter the endgame + which triggers the call to this routine); again we don't + expect any objects to be migrating to the endgame but will + keep any we find so that they could be delivered */ + if (In_endgame(&dest)) { + oprev = &otmp->nobj; /* keep otmp on migrating_objs */ + } else { + /* bypass obj_extract_self() */ + *oprev = otmp->nobj; /* remove otmp from migrating_objs */ + otmp->nobj = 0; + otmp->where = OBJ_FREE; + otmp->owornmask = 0L; /* overloaded for destination usage; + * obfree() will complain if nonzero */ + /* + * obfree(otmp,) + * -> dealloc_obj(otmp) + * -> obj_stop_timers(otmp) + * -> del_light_source(LS_OBJECT, obj_to_any(otmp)) + */ + obfree(otmp, (struct obj *) 0); /* releases any contents too */ + } + } } -/* return quality of food; the lower the better */ -/* fungi will eat even tainted food */ +/* returns the quality of an item of food; the lower the better; + fungi and ghouls will eat even tainted food */ int -dogfood(mon, obj) -struct monst *mon; -register struct obj *obj; +dogfood(struct monst *mon, struct obj *obj) { - struct permonst *mptr = mon->data, *fptr = 0; + struct permonst *mptr = mon->data, *fptr; boolean carni = carnivorous(mptr), herbi = herbivorous(mptr), starving, mblind; + int fx; + if (obj->opoisoned && !resists_poison(mon)) + return POISON; if (is_quest_artifact(obj) || obj_resists(obj, 0, 95)) return obj->cursed ? TABU : APPORT; switch (obj->oclass) { case FOOD_CLASS: - if (obj->otyp == CORPSE || obj->otyp == TIN || obj->otyp == EGG) - fptr = &mons[obj->corpsenm]; + fx = (obj->otyp == CORPSE || obj->otyp == TIN || obj->otyp == EGG) + /* corpsenm might be NON_PM (special tin, unhatchable egg) */ + ? obj->corpsenm + : NON_PM; + /* mons[NUMMONS] is a valid array entry, though not a valid monster; + * predicate tests against it will fail */ + fptr = &mons[(ismnum(fx)) ? fx : NUMMONS]; if (obj->otyp == CORPSE && is_rider(fptr)) return TABU; - if ((obj->otyp == CORPSE || obj->otyp == EGG) && touch_petrifies(fptr) + if ((obj->otyp == CORPSE || obj->otyp == EGG) + && flesh_petrifies(fptr) /* c*ckatrice or Medusa */ && !resists_ston(mon)) return POISON; + if (obj->otyp == LUMP_OF_ROYAL_JELLY + && mon->data == &mons[PM_KILLER_BEE]) { + struct monst *mtmp = find_pmmonst(PM_QUEEN_BEE); + + /* if there's a queen bee on the level, don't eat royal jelly; + if there isn't, do eat it and grow into a queen */ + return !mtmp ? DOGFOOD : TABU; + } if (!carni && !herbi) return obj->cursed ? UNDEF : APPORT; @@ -768,13 +1042,10 @@ register struct obj *obj; when starving; they never eat stone-to-flesh'd meat */ if (mptr == &mons[PM_GHOUL]) { if (obj->otyp == CORPSE) - return (peek_at_iced_corpse_age(obj) + 50L <= monstermoves - && fptr != &mons[PM_LIZARD] - && fptr != &mons[PM_LICHEN]) - ? DOGFOOD - : (starving && !vegan(fptr)) - ? ACCFOOD - : POISON; + return (peek_at_iced_corpse_age(obj) + 50L <= svm.moves + && !(fx == PM_LIZARD || fx == PM_LICHEN)) ? DOGFOOD + : (starving && !vegan(fptr)) ? ACCFOOD + : POISON; if (obj->otyp == EGG) return stale_egg(obj) ? CADAVER : starving ? ACCFOOD : POISON; return TABU; @@ -785,20 +1056,23 @@ register struct obj *obj; case MEATBALL: case MEAT_RING: case MEAT_STICK: - case HUGE_CHUNK_OF_MEAT: + case ENORMOUS_MEATBALL: return carni ? DOGFOOD : MANFOOD; case EGG: + if (obj->corpsenm == PM_PYROLISK && !likes_fire(mptr)) + return POISON; return carni ? CADAVER : MANFOOD; case CORPSE: - if ((peek_at_iced_corpse_age(obj) + 50L <= monstermoves - && obj->corpsenm != PM_LIZARD && obj->corpsenm != PM_LICHEN + if ((peek_at_iced_corpse_age(obj) + 50L <= svm.moves + && !(fx == PM_LIZARD || fx == PM_LICHEN) && mptr->mlet != S_FUNGUS) || (acidic(fptr) && !resists_acid(mon)) || (poisonous(fptr) && !resists_poison(mon))) return POISON; - /* turning into slime is preferable to starvation */ - else if (fptr == &mons[PM_GREEN_SLIME] && !slimeproof(mon->data)) - return starving ? ACCFOOD : POISON; + /* avoid polymorph unless starving or abused (in which case the + pet will consider it for a chance to become more powerful) */ + else if (polyfood(obj) && mon->mtame > 1 && !starving) + return MANFOOD; else if (vegan(fptr)) return herbi ? CADAVER : MANFOOD; /* most humanoids will avoid cannibalism unless starving; @@ -809,12 +1083,13 @@ register struct obj *obj; return (starving && carni && !is_elf(mptr)) ? ACCFOOD : TABU; else return carni ? CADAVER : MANFOOD; + case GLOB_OF_GREEN_SLIME: /* other globs use the default case */ + /* turning into slime is preferable to starvation */ + return (starving || slimeproof(mon->data)) ? ACCFOOD : POISON; case CLOVE_OF_GARLIC: - return (is_undead(mptr) || is_vampshifter(mon)) - ? TABU - : (herbi || starving) - ? ACCFOOD - : MANFOOD; + return (is_undead(mptr) || is_vampshifter(mon)) ? TABU + : (herbi || starving) ? ACCFOOD + : MANFOOD; case TIN: return metallivorous(mptr) ? ACCFOOD : MANFOOD; case APPLE: @@ -822,11 +1097,11 @@ register struct obj *obj; case CARROT: return (herbi || mblind) ? DOGFOOD : starving ? ACCFOOD : MANFOOD; case BANANA: - return (mptr->mlet == S_YETI && herbi) - ? DOGFOOD /* for monkey and ape (tameable), sasquatch */ - : (herbi || starving) - ? ACCFOOD - : MANFOOD; + /* monkeys and apes (tamable) plus sasquatch prefer these, + yetis will only will only eat them if starving */ + return (mptr->mlet == S_YETI && herbi) ? DOGFOOD + : (herbi || starving) ? ACCFOOD + : MANFOOD; default: if (starving) return ACCFOOD; @@ -843,7 +1118,7 @@ register struct obj *obj; return ACCFOOD; if (metallivorous(mptr) && is_metallic(obj) && (is_rustprone(obj) || mptr != &mons[PM_RUST_MONSTER])) { - /* Non-rustproofed ferrous based metals are preferred. */ + /* Non-rustproofed ferrous-based metals are preferred. */ return (is_rustprone(obj) && !obj->oerodeproof) ? DOGFOOD : ACCFOOD; } @@ -851,6 +1126,7 @@ register struct obj *obj; && obj->oclass != BALL_CLASS && obj->oclass != CHAIN_CLASS) return APPORT; + FALLTHROUGH; /*FALLTHRU*/ case ROCK_CLASS: return UNDEF; @@ -858,21 +1134,43 @@ register struct obj *obj; } /* - * With the separate mextra structure added in 3.6.x this always - * operates on the original mtmp. It now returns TRUE if the taming - * succeeded. + * tamedog() used to return the monster, which might have changed address + * if a new one was created in order to allocate the edog extension. + * With the separate mextra structure added in 3.6.x it always operates + * on the original mtmp. It now returns TRUE if the taming succeeded. */ boolean -tamedog(mtmp, obj) -register struct monst *mtmp; -register struct obj *obj; +tamedog( + struct monst *mtmp, + struct obj *obj, /* food or scroll/spell */ + boolean givemsg) { + boolean blessed_scroll = FALSE; + + if (obj && (obj->oclass == SCROLL_CLASS || obj->oclass == SPBOOK_CLASS)) { + blessed_scroll = obj->blessed ? TRUE : FALSE; + /* the rest of this routine assumes 'obj' represents food */ + obj = (struct obj *) NULL; + } + /* reduce timed sleep or paralysis, leaving mtmp->mcanmove as-is + (note: if mtmp is donning armor, this will reduce its busy time) */ + if (mtmp->mfrozen) + mtmp->mfrozen = (mtmp->mfrozen + 1) / 2; + /* end indefinite sleep; using distance==1 limits the waking to mtmp */ + if (mtmp->msleeping) + wake_nearto(mtmp->mx, mtmp->my, 1); /* [different from wakeup()] */ + /* The Wiz, Medusa and the quest nemeses aren't even made peaceful. */ if (mtmp->iswiz || mtmp->data == &mons[PM_MEDUSA] || (mtmp->data->mflags3 & M3_WANTSARTI)) return FALSE; /* worst case, at least it'll be peaceful. */ + if (givemsg && !mtmp->mpeaceful && canspotmon(mtmp)) { + pline_mon(mtmp, "%s seems %s.", Monnam(mtmp), + Hallucination ? "really chill" : "more amiable"); + givemsg = FALSE; /* don't give another message below */ + } mtmp->mpeaceful = 1; set_malign(mtmp); if (flags.moonphase == FULL_MOON && night() && rn2(6) && obj @@ -887,7 +1185,7 @@ register struct obj *obj; if (mtmp == u.ustuck) { if (u.uswallow) expels(mtmp, mtmp->data, TRUE); - else if (!(Upolyd && sticks(youmonst.data))) + else if (!(Upolyd && sticks(gy.youmonst.data))) unstuck(mtmp); } @@ -898,14 +1196,15 @@ register struct obj *obj; if (mtmp->mcanmove && !mtmp->mconf && !mtmp->meating && ((tasty = dogfood(mtmp, obj)) == DOGFOOD || (tasty <= ACCFOOD - && EDOG(mtmp)->hungrytime <= monstermoves))) { + && EDOG(mtmp)->hungrytime <= svm.moves))) { /* pet will "catch" and eat this thrown food */ if (canseemon(mtmp)) { boolean big_corpse = - (obj->otyp == CORPSE && obj->corpsenm >= LOW_PM + (obj->otyp == CORPSE && ismnum(obj->corpsenm) && mons[obj->corpsenm].msize > mtmp->data->msize); - pline("%s catches %s%s", Monnam(mtmp), the(xname(obj)), - !big_corpse ? "." : ", or vice versa!"); + pline_mon(mtmp, "%s catches %s%s", + Monnam(mtmp), the(xname(obj)), + !big_corpse ? "." : ", or vice versa!"); } else if (cansee(mtmp->mx, mtmp->my)) pline("%s.", Tobjnam(obj, "stop")); /* dog_eat expects a floor object */ @@ -919,20 +1218,45 @@ register struct obj *obj; return FALSE; } - if (mtmp->mtame || !mtmp->mcanmove - /* monsters with conflicting structures cannot be tamed */ + /* maximum tameness is 20, only reachable via eating; if already tame but + less than 10, taming magic might make it become tamer; blessed scroll + or skilled spell raises low tameness by 2 or 3, uncursed by 0 or 1 */ + if (mtmp->mtame && mtmp->mtame < 10) { + if (mtmp->mtame < rnd(10)) + mtmp->mtame++; + if (blessed_scroll) { + mtmp->mtame += 2; + if (mtmp->mtame > 10) + mtmp->mtame = 10; + } + return FALSE; /* didn't just get tamed */ + } + /* pacify angry shopkeeper but don't tame him/her/it/them */ + if (mtmp->isshk) { + make_happy_shk(mtmp, FALSE); + return FALSE; + } + + if (!mtmp->mcanmove + /* monsters with conflicting structures cannot be tamed + [note: the various mextra structures don't actually conflict + with each other anymore] */ || mtmp->isshk || mtmp->isgd || mtmp->ispriest || mtmp->isminion || is_covetous(mtmp->data) || is_human(mtmp->data) - || (is_demon(mtmp->data) && !is_demon(youmonst.data)) + || (is_demon(mtmp->data) && !is_demon(gy.youmonst.data)) || (obj && dogfood(mtmp, obj) >= MANFOOD)) return FALSE; - if (mtmp->m_id == quest_status.leader_m_id) + if (mtmp->m_id == svq.quest_status.leader_m_id) return FALSE; /* add the pet extension */ - newedog(mtmp); - initedog(mtmp); + if (!has_edog(mtmp)) { + newedog(mtmp); + initedog(mtmp, TRUE); + } else { + initedog(mtmp, FALSE); + } if (obj) { /* thrown food */ /* defer eating until the edog extension has been set up */ @@ -943,7 +1267,13 @@ register struct obj *obj; /* `obj' is now obsolete */ } + if (givemsg && canspotmon(mtmp)) + pline_mon(mtmp, "%s seems quite %s.", Monnam(mtmp), + Hallucination ? "approachable" : "friendly"); + newsym(mtmp->mx, mtmp->my); + if (mtmp->wormno) + redraw_worm(mtmp); if (attacktype(mtmp->data, AT_WEAP)) { mtmp->weapon_check = NEED_HTH_WEAPON; (void) mon_wield_item(mtmp); @@ -959,9 +1289,7 @@ register struct obj *obj; * If the pet wasn't abused and was very tame, it might revive tame. */ void -wary_dog(mtmp, was_dead) -struct monst *mtmp; -boolean was_dead; +wary_dog(struct monst *mtmp, boolean was_dead) { struct edog *edog; boolean quietly = was_dead; @@ -985,13 +1313,14 @@ boolean was_dead; if (!rn2(edog->abuse + 1)) mtmp->mpeaceful = 1; if (!quietly && cansee(mtmp->mx, mtmp->my)) { - if (haseyes(youmonst.data)) { + if (haseyes(gy.youmonst.data)) { if (haseyes(mtmp->data)) - pline("%s %s to look you in the %s.", Monnam(mtmp), - mtmp->mpeaceful ? "seems unable" : "refuses", - body_part(EYE)); + pline_mon(mtmp, + "%s %s to look you in the %s.", Monnam(mtmp), + mtmp->mpeaceful ? "seems unable" : "refuses", + body_part(EYE)); else - pline("%s avoids your gaze.", Monnam(mtmp)); + pline_mon(mtmp, "%s avoids your gaze.", Monnam(mtmp)); } } } else { @@ -1003,7 +1332,7 @@ boolean was_dead; if (!mtmp->mtame) { if (!quietly && canspotmon(mtmp)) - pline("%s %s.", Monnam(mtmp), + pline_mon(mtmp, "%s %s.", Monnam(mtmp), mtmp->mpeaceful ? "is no longer tame" : "has become feral"); newsym(mtmp->mx, mtmp->my); /* a life-saved monster might be leashed; @@ -1018,8 +1347,8 @@ boolean was_dead; edog->killed_by_u = 0; edog->abuse = 0; edog->ogoal.x = edog->ogoal.y = -1; - if (was_dead || edog->hungrytime < monstermoves + 500L) - edog->hungrytime = monstermoves + 500L; + if (was_dead || edog->hungrytime < svm.moves + 500L) + edog->hungrytime = svm.moves + 500L; if (was_dead) { edog->droptime = 0L; edog->dropdist = 10000; @@ -1030,8 +1359,7 @@ boolean was_dead; } void -abuse_dog(mtmp) -struct monst *mtmp; +abuse_dog(struct monst *mtmp) { if (!mtmp->mtame) return; @@ -1055,8 +1383,12 @@ struct monst *mtmp; else growl(mtmp); /* give them a moment's worry */ - if (!mtmp->mtame) + if (!mtmp->mtame) { newsym(mtmp->mx, mtmp->my); + if (mtmp->wormno) { + redraw_worm(mtmp); + } + } } } diff --git a/src/dogmove.c b/src/dogmove.c index df3452faa..6f2882100 100644 --- a/src/dogmove.c +++ b/src/dogmove.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 dogmove.c $NHDT-Date: 1557094801 2019/05/05 22:20:01 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.74 $ */ +/* NetHack 5.0 dogmove.c $NHDT-Date: 1725733007 2024/09/07 18:16:47 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.156 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2012. */ /* NetHack may be freely redistributed. See license for details. */ @@ -7,28 +7,41 @@ #include "mfndpos.h" -extern boolean notonhead; - -STATIC_DCL boolean FDECL(dog_hunger, (struct monst *, struct edog *)); -STATIC_DCL int FDECL(dog_invent, (struct monst *, struct edog *, int)); -STATIC_DCL int FDECL(dog_goal, (struct monst *, struct edog *, int, int, int)); -STATIC_DCL struct monst *FDECL(find_targ, (struct monst *, int, int, int)); -STATIC_OVL int FDECL(find_friends, (struct monst *, struct monst *, int)); -STATIC_DCL struct monst *FDECL(best_target, (struct monst *)); -STATIC_DCL long FDECL(score_targ, (struct monst *, struct monst *)); -STATIC_DCL boolean FDECL(can_reach_location, (struct monst *, XCHAR_P, - XCHAR_P, XCHAR_P, XCHAR_P)); -STATIC_DCL boolean FDECL(could_reach_item, (struct monst *, XCHAR_P, XCHAR_P)); -STATIC_DCL void FDECL(quickmimic, (struct monst *)); +#define DOG_HUNGRY 300 +#define DOG_WEAK 500 +#define DOG_STARVE 750 + +staticfn void dog_starve(struct monst *); +staticfn boolean dog_hunger(struct monst *, struct edog *); +staticfn int dog_invent(struct monst *, struct edog *, int); +staticfn int dog_goal(struct monst *, struct edog *, int, int, int); +staticfn struct monst *find_targ(struct monst *, int, int, int); +staticfn int find_friends(struct monst *, struct monst *, int); +staticfn struct monst *best_target(struct monst *, boolean); +staticfn long score_targ(struct monst *, struct monst *); +staticfn boolean can_reach_location(struct monst *, coordxy, coordxy, coordxy, + coordxy) NONNULLARG1; +staticfn boolean mnum_leashable(int); /* pick a carried item for pet to drop */ struct obj * -droppables(mon) -struct monst *mon; +droppables(struct monst *mon) { - struct obj *obj, *wep, dummy, *pickaxe, *unihorn, *key; + /* + * 'key|pickaxe|&c = &dummy' is used to make various creatures + * that can't use a key/pick-axe/&c behave as if they are already + * holding one so that any other such item in their inventory will + * be considered a duplicate and get treated as a normal candidate + * for dropping. + * + * This could be 'auto', but then 'gcc -O2' warns that this function + * might return the address of a local variable. It's mistaken, + * &dummy is never returned. 'static' is simplest way to shut it up. + */ + static struct obj dummy; + struct obj *obj, *wep, *pickaxe, *unihorn, *key; - dummy = zeroobj; + dummy = cg.zeroobj; dummy.otyp = GOLD_PIECE; /* not STRANGE_OBJECT or tools of interest */ dummy.oartifact = 1; /* so real artifact won't override "don't keep it" */ pickaxe = unihorn = key = (struct obj *) 0; @@ -64,6 +77,7 @@ struct monst *mon; if (pickaxe && pickaxe->otyp == PICK_AXE && pickaxe != wep && (!pickaxe->oartifact || obj->oartifact)) return pickaxe; /* drop the one we earlier decided to keep */ + FALLTHROUGH; /*FALLTHRU*/ case PICK_AXE: if (!pickaxe || (obj->oartifact && !pickaxe->oartifact)) { @@ -92,12 +106,14 @@ struct monst *mon; if (key && key->otyp == LOCK_PICK && (!key->oartifact || obj->oartifact)) return key; /* drop the one we earlier decided to keep */ + FALLTHROUGH; /*FALLTHRU*/ case LOCK_PICK: /* keep lock-pick in preference to credit card */ if (key && key->otyp == CREDIT_CARD && (!key->oartifact || obj->oartifact)) return key; + FALLTHROUGH; /*FALLTHRU*/ case CREDIT_CARD: if (!key || (obj->oartifact && !key->oartifact)) { @@ -122,26 +138,22 @@ struct monst *mon; static NEARDATA const char nofetch[] = { BALL_CLASS, CHAIN_CLASS, ROCK_CLASS, 0 }; -STATIC_VAR xchar gtyp, gx, gy; /* type and position of dog's current goal */ -STATIC_PTR void FDECL(wantdoor, (int, int, genericptr_t)); +staticfn void wantdoor(coordxy, coordxy, genericptr_t); boolean -cursed_object_at(x, y) -int x, y; +cursed_object_at(coordxy x, coordxy y) { struct obj *otmp; - for (otmp = level.objects[x][y]; otmp; otmp = otmp->nexthere) + for (otmp = svl.level.objects[x][y]; otmp; otmp = otmp->nexthere) if (otmp->cursed) return TRUE; return FALSE; } int -dog_nutrition(mtmp, obj) -struct monst *mtmp; -struct obj *obj; +dog_nutrition(struct monst *mtmp, struct obj *obj) { int nutrit; @@ -203,32 +215,22 @@ struct obj *obj; /* returns 2 if pet dies, otherwise 1 */ int -dog_eat(mtmp, obj, x, y, devour) -register struct monst *mtmp; -register struct obj *obj; /* if unpaid, then thrown or kicked by hero */ -int x, y; /* dog's starting location, might be different from current */ -boolean devour; +dog_eat(struct monst *mtmp, + struct obj *obj, /* if unpaid, then thrown or kicked by hero */ + coordxy x, /* dog's starting location, */ + coordxy y, /* might be different from current */ + boolean devour) { - register struct edog *edog = EDOG(mtmp); - boolean poly, grow, heal, eyes, slimer, deadmimic; - int nutrit; + struct edog *edog = EDOG(mtmp); + int nutrit, res; long oprice; - char objnambuf[BUFSZ]; + char objnambuf[BUFSZ], *obj_name; objnambuf[0] = '\0'; - if (edog->hungrytime < monstermoves) - edog->hungrytime = monstermoves; + if (edog->hungrytime < svm.moves) + edog->hungrytime = svm.moves; nutrit = dog_nutrition(mtmp, obj); - deadmimic = (obj->otyp == CORPSE && (obj->corpsenm == PM_SMALL_MIMIC - || obj->corpsenm == PM_LARGE_MIMIC - || obj->corpsenm == PM_GIANT_MIMIC)); - slimer = (obj->otyp == CORPSE && obj->corpsenm == PM_GREEN_SLIME); - poly = polyfodder(obj); - grow = mlevelgain(obj); - heal = mhealup(obj); - eyes = (obj->otyp == CARROT); - if (devour) { if (mtmp->meating > 1) mtmp->meating /= 2; @@ -250,13 +252,18 @@ boolean devour; newsym(x, y); newsym(mtmp->mx, mtmp->my); } + if (mtmp->data == &mons[PM_KILLER_BEE] + && obj->otyp == LUMP_OF_ROYAL_JELLY + && (res = bee_eat_jelly(mtmp, obj)) >= 0) + /* bypass most of dog_eat(), including apport update */ + return (res + 1); /* 1 -> 2, 0 -> 1; -1, keep going */ /* food items are eaten one at a time; entire stack for other stuff */ if (obj->quan > 1L && obj->oclass == FOOD_CLASS) obj = splitobj(obj, 1L); if (obj->unpaid) iflags.suppress_price++; - if (is_pool(x, y) && !Underwater) { + if (is_pool(mtmp->mx, mtmp->my) && !Underwater) { /* Don't print obj */ /* TODO: Reveal presence of sea monster (especially sharks) */ } else { @@ -273,94 +280,90 @@ boolean devour; pet as "it". However, we want "it" if invisible/unsensed pet eats visible food. */ if (sawpet || (seeobj && canspotmon(mtmp))) { + /* call distant_name() for possible side-effects even if the + result won't be printed */ + obj_name = distant_name(obj, doname); if (tunnels(mtmp->data)) - pline("%s digs in.", noit_Monnam(mtmp)); + pline_mon(mtmp, "%s digs in.", noit_Monnam(mtmp)); else - pline("%s %s %s.", noit_Monnam(mtmp), - devour ? "devours" : "eats", distant_name(obj, doname)); - } else if (seeobj) - pline("It %s %s.", devour ? "devours" : "eats", - distant_name(obj, doname)); + pline_mon(mtmp, "%s %s %s.", noit_Monnam(mtmp), + devour ? "devours" : "eats", obj_name); + } else if (seeobj) { + obj_name = distant_name(obj, doname); + pline("It %s %s.", devour ? "devours" : "eats", obj_name); + } } if (obj->unpaid) { Strcpy(objnambuf, xname(obj)); iflags.suppress_price--; } - /* It's a reward if it's DOGFOOD and the player dropped/threw it. - We know the player had it if invlet is set. -dlc */ - if (dogfood(mtmp, obj) == DOGFOOD && obj->invlet) -#ifdef LINT - edog->apport = 0; -#else - edog->apport += (int) (200L / ((long) edog->dropdist + monstermoves - - edog->droptime)); -#endif if (mtmp->data == &mons[PM_RUST_MONSTER] && obj->oerodeproof) { /* The object's rustproofing is gone now */ if (obj->unpaid) costly_alteration(obj, COST_DEGRD); obj->oerodeproof = 0; mtmp->mstun = 1; - if (canseemon(mtmp) && flags.verbose) { - pline("%s spits %s out in disgust!", Monnam(mtmp), - distant_name(obj, doname)); + if (canseemon(mtmp)) { + obj_name = distant_name(obj, doname); /* (see above) */ + if (flags.verbose) + pline("%s spits %s out in disgust!", + Monnam(mtmp), obj_name); } - } else if (obj == uball) { - unpunish(); - delobj(obj); /* we assume this can't be unpaid */ - } else if (obj == uchain) { - unpunish(); } else { + /* It's a reward if it's DOGFOOD and the player dropped/threw it. + We know the player had it if invlet is set. -dlc */ + if (dogfood(mtmp, obj) == DOGFOOD && obj->invlet) { + int prior_apport = edog->apport; + + edog->apport += (int) (200L / ((long) edog->dropdist + svm.moves + - edog->droptime)); + if (edog->apport <= 0) { + impossible("dog_eat: pet apport <= 0 (%d, %d, %ld, %ld, %d, %u, %u)", + edog->apport, edog->dropdist, edog->droptime, + svm.moves, + prior_apport, + /* check whether edog struct got clobbered; + these two values should always match if + edog content is still intact */ + mtmp->m_id, edog->parentmid); + edog->apport = 1; + } + } if (obj->unpaid) { /* edible item owned by shop has been thrown or kicked by hero and caught by tame or food-tameable monst */ - oprice = unpaid_cost(obj, TRUE); + oprice = unpaid_cost(obj, COST_CONTENTS); pline("That %s will cost you %ld %s.", objnambuf, oprice, currency(oprice)); - /* delobj->obfree will handle actual shop billing update */ + /* m_consume_obj() -> delobj() -> obfree() will handle the shop + billing update */ } - delobj(obj); - } - -#if 0 /* pet is eating, so slime recovery is not feasible... */ - /* turning into slime might be cureable */ - if (slimer && munslime(mtmp, FALSE)) { - /* but the cure (fire directed at self) might be fatal */ - if (DEADMONSTER(mtmp)) - return 2; - slimer = FALSE; /* sliming is avoided, skip polymorph */ + m_consume_obj(mtmp, obj); } -#endif - - if (poly || slimer) { - struct permonst *ptr = slimer ? &mons[PM_GREEN_SLIME] : 0; - (void) newcham(mtmp, ptr, FALSE, cansee(mtmp->mx, mtmp->my)); - } + return (DEADMONSTER(mtmp)) ? 2 : 1; +} - /* limit "instant" growth to prevent potential abuse */ - if (grow && (int) mtmp->m_lev < (int) mtmp->data->mlevel + 15) { - if (!grow_up(mtmp, (struct monst *) 0)) - return 2; - } - if (heal) - mtmp->mhp = mtmp->mhpmax; - if ((eyes || heal) && !mtmp->mcansee) - mcureblindness(mtmp, canseemon(mtmp)); - if (deadmimic) - quickmimic(mtmp); - return 1; +staticfn void +dog_starve(struct monst *mtmp) +{ + if (mtmp->mleashed && mtmp != u.usteed) + Your("leash goes slack."); + else if (cansee(mtmp->mx, mtmp->my)) + pline_mon(mtmp, "%s starves.", Monnam(mtmp)); + else + You_feel("%s for a moment.", + Hallucination ? "bummed" : "sad"); + mondied(mtmp); } /* hunger effects -- returns TRUE on starvation */ -STATIC_OVL boolean -dog_hunger(mtmp, edog) -struct monst *mtmp; -struct edog *edog; +staticfn boolean +dog_hunger(struct monst *mtmp, struct edog *edog) { - if (monstermoves > edog->hungrytime + 500) { + if (svm.moves > edog->hungrytime + DOG_WEAK) { if (!carnivorous(mtmp->data) && !herbivorous(mtmp->data)) { - edog->hungrytime = monstermoves + 500; + edog->hungrytime = svm.moves + DOG_WEAK; /* but not too high; it might polymorph */ } else if (!edog->mhpmax_penalty) { /* starving pets are limited in healing */ @@ -370,27 +373,21 @@ struct edog *edog; mtmp->mhpmax = newmhpmax; if (mtmp->mhp > mtmp->mhpmax) mtmp->mhp = mtmp->mhpmax; - if (DEADMONSTER(mtmp)) - goto dog_died; + if (DEADMONSTER(mtmp)) { + dog_starve(mtmp); + return TRUE; + } if (cansee(mtmp->mx, mtmp->my)) - pline("%s is confused from hunger.", Monnam(mtmp)); + pline_mon(mtmp, "%s is confused from hunger.", Monnam(mtmp)); else if (couldsee(mtmp->mx, mtmp->my)) beg(mtmp); else You_feel("worried about %s.", y_monnam(mtmp)); stop_occupation(); - } else if (monstermoves > edog->hungrytime + 750 + } else if (svm.moves > edog->hungrytime + DOG_STARVE || DEADMONSTER(mtmp)) { - dog_died: - if (mtmp->mleashed && mtmp != u.usteed) - Your("leash goes slack."); - else if (cansee(mtmp->mx, mtmp->my)) - pline("%s starves.", Monnam(mtmp)); - else - You_feel("%s for a moment.", - Hallucination ? "bummed" : "sad"); - mondied(mtmp); - return TRUE; + dog_starve(mtmp); + return TRUE; } } return FALSE; @@ -399,41 +396,42 @@ struct edog *edog; /* do something with object (drop, pick up, eat) at current position * returns 1 if object eaten (since that counts as dog's move), 2 if died */ -STATIC_OVL int -dog_invent(mtmp, edog, udist) -register struct monst *mtmp; -register struct edog *edog; -int udist; +staticfn int +dog_invent(struct monst *mtmp, struct edog *edog, int udist) { - register int omx, omy, carryamt = 0; + coordxy omx, omy; + int carryamt = 0; struct obj *obj, *otmp; - if (mtmp->msleeping || !mtmp->mcanmove) + if (helpless(mtmp) || mtmp->meating) return 0; omx = mtmp->mx; omy = mtmp->my; /* If we are carrying something then we drop it (perhaps near @). - * Note: if apport == 1 then our behaviour is independent of udist. + * Note: if apport == 1 then our behavior is independent of udist. * Use udist+1 so steed won't cause divide by zero. */ if (droppables(mtmp)) { + assert(edog->apport > 0); if (!rn2(udist + 1) || !rn2(edog->apport)) if (rn2(10) < edog->apport) { relobj(mtmp, (int) mtmp->minvis, TRUE); if (edog->apport > 1) edog->apport--; edog->dropdist = udist; /* hpscdi!jon */ - edog->droptime = monstermoves; + edog->droptime = svm.moves; } } else { - if ((obj = level.objects[omx][omy]) != 0 - && !index(nofetch, obj->oclass) -#ifdef MAIL + if ((obj = svl.level.objects[omx][omy]) != 0 + && !strchr(nofetch, obj->oclass) +#ifdef MAIL_STRUCTURES && obj->otyp != SCR_MAIL #endif - ) { + /* avoid special items; once hero picks them up, they'll cease + being special and become eligible for normal monst activity */ + && !(is_mines_prize(obj) || is_soko_prize(obj))) { int edible = dogfood(mtmp, obj); if ((edible <= CADAVER @@ -450,9 +448,18 @@ int udist; otmp = obj; if (carryamt != obj->quan) otmp = splitobj(obj, carryamt); - if (cansee(omx, omy) && flags.verbose) - pline("%s picks up %s.", Monnam(mtmp), - distant_name(otmp, doname)); + if (cansee(omx, omy)) { + /* call distant_name() for possible side-effects + even if the result won't be printed; should be + done before extract+pickup for distant_name() + -> doname() -> xname() -> find_artifact() + while otmp is still on floor */ + char *otmpname = distant_name(otmp, doname); + + if (flags.verbose) + pline_xy(omx, omy, "%s picks up %s.", + Monnam(mtmp), otmpname); + } obj_extract_self(otmp); newsym(omx, omy); (void) mpickobj(mtmp, otmp); @@ -461,7 +468,7 @@ int udist; mtmp->weapon_check = NEED_HTH_WEAPON; (void) mon_wield_item(mtmp); } - m_dowear(mtmp, FALSE); + check_gear_next_turn(mtmp); } } } @@ -472,16 +479,16 @@ int udist; /* set dog's goal -- gtyp, gx, gy; returns -1/0/1 (dog's desire to approach player) or -2 (abort move) */ -STATIC_OVL int -dog_goal(mtmp, edog, after, udist, whappr) -register struct monst *mtmp; -struct edog *edog; -int after, udist, whappr; +staticfn int +dog_goal( + struct monst *mtmp, + struct edog *edog, + int after, int udist, int whappr) { - register int omx, omy; + coordxy omx, omy; boolean in_masters_sight, dog_has_minvent; - register struct obj *obj; - xchar otyp; + struct obj *obj; + xint16 otyp; int appr; /* Steeds don't move on their own will */ @@ -495,17 +502,17 @@ int after, udist, whappr; dog_has_minvent = (droppables(mtmp) != 0); if (!edog || mtmp->mleashed) { /* he's not going anywhere... */ - gtyp = APPORT; - gx = u.ux; - gy = u.uy; + gg.gtyp = APPORT; + gg.gx = u.ux; + gg.gy = u.uy; } else { #define DDIST(x, y) (dist2(x, y, omx, omy)) #define SQSRCHRADIUS 5 int min_x, max_x, min_y, max_y; - register int nx, ny; + coordxy nx, ny; - gtyp = UNDEF; /* no goal as yet */ - gx = gy = 0; /* suppress 'used before set' message */ + gg.gtyp = UNDEF; /* no goal as yet */ + gg.gx = gg.gy = 0; /* suppress 'used before set' message */ if ((min_x = omx - SQSRCHRADIUS) < 1) min_x = 1; @@ -523,7 +530,7 @@ int after, udist, whappr; if (nx >= min_x && nx <= max_x && ny >= min_y && ny <= max_y) { otyp = dogfood(mtmp, obj); /* skip inferior goals */ - if (otyp > gtyp || otyp == UNDEF) + if (otyp > gg.gtyp || otyp == UNDEF) continue; /* avoid cursed items unless starving */ if (cursed_object_at(nx, ny) @@ -534,31 +541,34 @@ int after, udist, whappr; || !can_reach_location(mtmp, mtmp->mx, mtmp->my, nx, ny)) continue; if (otyp < MANFOOD) { - if (otyp < gtyp || DDIST(nx, ny) < DDIST(gx, gy)) { - gx = nx; - gy = ny; - gtyp = otyp; + if (otyp < gg.gtyp + || DDIST(nx, ny) < DDIST(gg.gx, gg.gy)) { + gg.gx = nx; + gg.gy = ny; + gg.gtyp = otyp; } - } else if (gtyp == UNDEF && in_masters_sight + } else if (gg.gtyp == UNDEF && in_masters_sight && !dog_has_minvent && (!levl[omx][omy].lit || levl[u.ux][u.uy].lit) && (otyp == MANFOOD || m_cansee(mtmp, nx, ny)) && edog->apport > rn2(8) && can_carry(mtmp, obj) > 0) { - gx = nx; - gy = ny; - gtyp = APPORT; + gg.gx = nx; + gg.gy = ny; + gg.gtyp = APPORT; } } } +#undef DDIST +#undef SQSRCHRADIUS } /* follow player if appropriate */ - if (gtyp == UNDEF || (gtyp != DOGFOOD && gtyp != APPORT - && monstermoves < edog->hungrytime)) { - gx = u.ux; - gy = u.uy; - if (after && udist <= 4 && gx == u.ux && gy == u.uy) + if (gg.gtyp == UNDEF || (gg.gtyp != DOGFOOD && gg.gtyp != APPORT + && svm.moves < edog->hungrytime)) { + gg.gx = u.ux; + gg.gy = u.uy; + if (after && udist <= 4 && u_at(gg.gx, gg.gy)) return -2; appr = (udist >= 9) ? 1 : (mtmp->mflee) ? -1 : 0; if (udist > 1) { @@ -566,47 +576,66 @@ int after, udist, whappr; || (dog_has_minvent && rn2(edog->apport))) appr = 1; } - /* if you have dog food it'll follow you more closely */ - if (appr == 0) - for (obj = invent; obj; obj = obj->nobj) - if (dogfood(mtmp, obj) == DOGFOOD) { - appr = 1; - break; + /* if you have dog food it'll follow you more closely; if you are + on stairs (or ladder) or on or next to a magic portal, it will + behave as if you have dog food */ + if (appr == 0) { + if (On_stairs(u.ux, u.uy)) { + appr = 1; + } else { + for (obj = gi.invent; obj; obj = obj->nobj) + if (dogfood(mtmp, obj) == DOGFOOD) { + appr = 1; + break; + } + if (appr == 0) { + struct trap *t; + + /* assume at most one magic portal per level; + [should this be limited to known portals?] */ + for (t = gf.ftrap; t; t = t->ntrap) + if (t->ttyp == MAGIC_PORTAL) { + if (/*t->tseen &&*/ distu(t->tx, t->ty) <= 2) + appr = 1; + break; + } } + } + } } else appr = 1; /* gtyp != UNDEF */ if (mtmp->mconf) appr = 0; #define FARAWAY (COLNO + 2) /* position outside screen */ - if (gx == u.ux && gy == u.uy && !in_masters_sight) { - register coord *cp; + if (u_at(gg.gx, gg.gy) && !in_masters_sight) { + coord *cp; cp = gettrack(omx, omy); if (cp) { - gx = cp->x; - gy = cp->y; + gg.gx = cp->x; + gg.gy = cp->y; if (edog) edog->ogoal.x = 0; } else { /* assume master hasn't moved far, and reuse previous goal */ if (edog && edog->ogoal.x && (edog->ogoal.x != omx || edog->ogoal.y != omy)) { - gx = edog->ogoal.x; - gy = edog->ogoal.y; + gg.gx = edog->ogoal.x; + gg.gy = edog->ogoal.y; edog->ogoal.x = 0; } else { int fardist = FARAWAY * FARAWAY; - gx = gy = FARAWAY; /* random */ + gg.gx = gg.gy = FARAWAY; /* random */ do_clear_area(omx, omy, 9, wantdoor, (genericptr_t) &fardist); /* here gx == FARAWAY e.g. when dog is in a vault */ - if (gx == FARAWAY || (gx == omx && gy == omy)) { - gx = u.ux; - gy = u.uy; + if (gg.gx == FARAWAY || (gg.gx == omx && gg.gy == omy)) { + gg.gx = u.ux; + gg.gy = u.uy; } else if (edog) { - edog->ogoal.x = gx; - edog->ogoal.y = gy; + edog->ogoal.x = gg.gx; + edog->ogoal.y = gg.gy; } } } @@ -614,13 +643,14 @@ int after, udist, whappr; edog->ogoal.x = 0; } return appr; +#undef FARAWAY } -STATIC_OVL struct monst * -find_targ(mtmp, dx, dy, maxdist) -register struct monst *mtmp; -int dx, dy; -int maxdist; +staticfn struct monst * +find_targ( + struct monst *mtmp, + int dx, int dy, + int maxdist) { struct monst *targ = 0; int curx = mtmp->mx, cury = mtmp->my; @@ -645,24 +675,23 @@ int maxdist; break; if (curx == mtmp->mux && cury == mtmp->muy) - return &youmonst; + return &gy.youmonst; if ((targ = m_at(curx, cury)) != 0) { /* Is the monster visible to the pet? */ - if ((!targ->minvis || perceives(mtmp->data)) - && !targ->mundetected) + if ((!targ->minvis || perceives(mtmp->data)) && !targ->mundetected + /* if a long worm, only accept the head as a target */ + && targ->mx == curx && targ->my == cury) /* not tail */ break; - /* If the pet can't see it, it assumes it aint there */ + /* If the pet can't see it, it assumes it ain't there */ targ = 0; } } return targ; } -STATIC_OVL int -find_friends(mtmp, mtarg, maxdist) -struct monst *mtmp, *mtarg; -int maxdist; +staticfn int +find_friends(struct monst *mtmp, struct monst *mtarg, int maxdist) { struct monst *pal; int dx = sgn(mtarg->mx - mtmp->mx), @@ -705,9 +734,8 @@ int maxdist; return 0; } -STATIC_OVL long -score_targ(mtmp, mtarg) -struct monst *mtmp, *mtarg; +staticfn long +score_targ(struct monst *mtmp, struct monst *mtarg) { long score = 0L; @@ -750,7 +778,7 @@ struct monst *mtmp, *mtarg; return score; } /* Is the monster peaceful or tame? */ - if (/*mtarg->mpeaceful ||*/ mtarg->mtame || mtarg == &youmonst) { + if (/*mtarg->mpeaceful ||*/ mtarg->mtame || mtarg == &gy.youmonst) { /* Pets will never be targeted */ score -= 3000L; return score; @@ -806,9 +834,8 @@ struct monst *mtmp, *mtarg; return score; } -STATIC_OVL struct monst * -best_target(mtmp) -struct monst *mtmp; /* Pet */ +staticfn struct monst * +best_target(struct monst *mtmp, boolean forced) /* Pet */ { int dx, dy; long bestscore = -40000L, currscore; @@ -851,33 +878,121 @@ struct monst *mtmp; /* Pet */ } /* Filter out targets the pet doesn't like */ - if (bestscore < 0L) + if (!forced && bestscore < 0L) best_targ = 0; return best_targ; } -/* return 0 (no move), 1 (move) or 2 (dead) */ +/* Pet considers and maybe executes a ranged attack */ +int +pet_ranged_attk(struct monst *mtmp, boolean forced) +{ + struct monst *mtarg; + int hungry = 0; + + /* How hungry is the pet? */ + if (!mtmp->isminion) { + struct edog *dog = EDOG(mtmp); + + hungry = (svm.moves > (dog->hungrytime + DOG_HUNGRY)); + } + + /* Identify the best target in a straight line from the pet; + * if there is such a target, we'll let the pet attempt an attack. + */ + mtarg = best_target(mtmp, forced); + + /* Hungry pets are unlikely to use breath/spit attacks */ + if (mtarg && (!hungry || !rn2(5))) { + int mstatus = M_ATTK_MISS; + + if (mtarg == &gy.youmonst) { + if (mattacku(mtmp)) + return MMOVE_DIED; + /* Treat this as the pet having initiated an attack even if it + * didn't, so it will lose its move. This isn't entirely fair, + * but mattacku doesn't distinguish between "did not attack" + * and "attacked but didn't die" cases, and this is preferable + * to letting the pet attack the player and continuing to move. + */ + mstatus = M_ATTK_HIT; + } else { + gb.bhitpos.x = mtmp->mx, gb.bhitpos.y = mtmp->my; + gn.notonhead = FALSE; + mstatus = mattackm(mtmp, mtarg); + + /* Shouldn't happen, really */ + if (mstatus & M_ATTK_AGR_DIED) + return MMOVE_DIED; + + /* Allow the targeted nasty to strike back - if + * the targeted beast doesn't have a ranged attack, + * nothing will happen. + */ + if ((mstatus & M_ATTK_HIT) && !(mstatus & M_ATTK_DEF_DIED) + && rn2(4) && mtarg != &gy.youmonst) { + + /* Can monster see? If it can, it can retaliate + * even if the pet is invisible, since it'll see + * the direction from which the ranged attack came; + * if it's blind or unseeing, it can't retaliate + */ + if (mtarg->mcansee && haseyes(mtarg->data)) { + int mresp; + + gb.bhitpos.x = mtmp->mx, gb.bhitpos.y = mtmp->my; + gn.notonhead = FALSE; + mresp = mattackm(mtarg, mtmp); + if (mresp & M_ATTK_DEF_DIED) + return MMOVE_DIED; + } + } + } + /* Only return 3 if the pet actually made a ranged attack, and + * thus should lose the rest of its move. + * There's a chain of assumptions here: + * 1. score_targ and best_target will never select a monster + * that can be attacked in melee, so the mattackm call can + * only ever try ranged options + * 2. if the only attacks available to mattackm are ranged + * options, and the monster cannot make a ranged attack, it + * will return M_ATTK_MISS. + */ + if (mstatus != M_ATTK_MISS) + return MMOVE_DONE; + } else if (forced) + (void) domonnoise(mtmp); + return MMOVE_NOTHING; +} + +/* Return values (same as m_move): + * 0: did not move, but can still attack and do other stuff. + * 1: moved, possibly can attack. + * 2: monster died. + * 3: did not move, and can't do anything else either. + * (may have attacked something) + */ int -dog_move(mtmp, after) -register struct monst *mtmp; -int after; /* this is extra fast monster movement */ +dog_move( + struct monst *mtmp, /* pet */ + int after) /* this is extra fast monster movement */ { int omx, omy; /* original mtmp position */ int appr, whappr, udist; int i, j, k; - register struct edog *edog = EDOG(mtmp); + struct edog *edog = (mtmp->mtame && has_edog(mtmp)) ? EDOG(mtmp) : 0; struct obj *obj = (struct obj *) 0; - xchar otyp; - boolean has_edog, cursemsg[9], do_eat = FALSE; - boolean better_with_displacing = FALSE; - xchar nix, niy; /* position mtmp is (considering) moving to */ - register int nx, ny; /* temporary coordinates */ - xchar cnt, uncursedcnt, chcnt; + xint16 otyp; + boolean cursemsg[9], do_eat = FALSE; + boolean better_with_displacing = FALSE, ranged_only; + coordxy nix, niy; /* position mtmp is (considering) moving to */ + coordxy nx, ny; /* temporary coordinates */ + xint16 cnt, uncursedcnt, chcnt; int chi = -1, nidist, ndist; - coord poss[9]; - long info[9], allowflags; -#define GDIST(x, y) (dist2(x, y, gx, gy)) + long allowflags; + struct mfndposdata mfp; +#define GDIST(x, y) (dist2(x, y, gg.gx, gg.gy)) /* * Tame Angels have isminion set and an ispriest structure instead of @@ -886,85 +1001,66 @@ int after; /* this is extra fast monster movement */ * spend all their energy defending the player. (They are the only * monsters with other structures that can be tame.) */ - has_edog = !mtmp->isminion; + if (!edog && !mtmp->isminion) { + impossible("dog_move for non-pet?"); + return MMOVE_NOTHING; + } omx = mtmp->mx; omy = mtmp->my; - if (has_edog && dog_hunger(mtmp, edog)) - return 2; /* starved */ + if (edog && dog_hunger(mtmp, edog)) + return MMOVE_DIED; /* starved */ udist = distu(omx, omy); /* Let steeds eat and maybe throw rider during Conflict */ if (mtmp == u.usteed) { - if (Conflict && !resist(mtmp, RING_CLASS, 0, 0)) { + if (Conflict && !resist_conflict(mtmp)) { dismount_steed(DISMOUNT_THROWN); - return 1; + return MMOVE_MOVED; } udist = 1; - } else if (!udist) + } else if (!udist) { /* maybe we tamed him while being swallowed --jgm */ - return 0; + return MMOVE_NOTHING; + } nix = omx; /* set before newdogpos */ niy = omy; cursemsg[0] = FALSE; /* lint suppression */ - info[0] = 0; /* ditto */ - if (has_edog) { + if (edog) { j = dog_invent(mtmp, edog, udist); - if (j == 2) - return 2; /* died */ + if (j == 2 || mon_offmap(mtmp)) + return DEADMONSTER(mtmp) ? MMOVE_DIED : MMOVE_DONE; else if (j == 1) goto newdogpos; /* eating something */ - whappr = (monstermoves - edog->whistletime < 5); + whappr = (svm.moves - edog->whistletime < 5); } else whappr = 0; - appr = dog_goal(mtmp, has_edog ? edog : (struct edog *) 0, after, udist, - whappr); + appr = dog_goal(mtmp, edog, after, udist, whappr); if (appr == -2) - return 0; + return MMOVE_NOTHING; - allowflags = ALLOW_M | ALLOW_TRAPS | ALLOW_SSM | ALLOW_SANCT; - if (passes_walls(mtmp->data)) - allowflags |= (ALLOW_ROCK | ALLOW_WALL); - if (passes_bars(mtmp->data)) - allowflags |= ALLOW_BARS; - if (throws_rocks(mtmp->data)) - allowflags |= ALLOW_ROCK; - if (is_displacer(mtmp->data)) - allowflags |= ALLOW_MDISP; - if (Conflict && !resist(mtmp, RING_CLASS, 0, 0)) { - allowflags |= ALLOW_U; - if (!has_edog) { + if (Conflict && !resist_conflict(mtmp)) { + if (!edog) { /* Guardian angel refuses to be conflicted; rather, * it disappears, angrily, and sends in some nasties */ lose_guardian_angel(mtmp); - return 2; /* current monster is gone */ + return MMOVE_DIED; /* current monster is gone */ } } #if 0 /* [this is now handled in dochug()] */ if (!Conflict && !mtmp->mconf - && mtmp == u.ustuck && !sticks(youmonst.data)) { + && mtmp == u.ustuck && !sticks(gy.youmonst.data)) { unstuck(mtmp); /* swallowed case handled above */ You("get released!"); } #endif - if (!nohands(mtmp->data) && !verysmall(mtmp->data)) { - allowflags |= OPENDOOR; - if (monhaskey(mtmp, TRUE)) - allowflags |= UNLOCKDOOR; - /* note: the Wizard and Riders can unlock doors without a key; - they won't use that ability if someone manages to tame them */ - } - if (is_giant(mtmp->data)) - allowflags |= BUSTDOOR; - if (tunnels(mtmp->data) - && !Is_rogue_level(&u.uz)) /* same restriction as m_move() */ - allowflags |= ALLOW_DIG; - cnt = mfndpos(mtmp, poss, info, allowflags); + allowflags = mon_allowflags(mtmp); + cnt = mfndpos(mtmp, &mfp, allowflags); /* Normally dogs don't step on cursed items, but if they have no * other choice they will. This requires checking ahead of time @@ -972,24 +1068,25 @@ int after; /* this is extra fast monster movement */ */ uncursedcnt = 0; for (i = 0; i < cnt; i++) { - nx = poss[i].x; - ny = poss[i].y; - if (MON_AT(nx, ny) && !((info[i] & ALLOW_M) || info[i] & ALLOW_MDISP)) + nx = mfp.poss[i].x; + ny = mfp.poss[i].y; + if (MON_AT(nx, ny) && !((mfp.info[i] & ALLOW_M) || mfp.info[i] & ALLOW_MDISP)) continue; if (cursed_object_at(nx, ny)) continue; uncursedcnt++; } - better_with_displacing = should_displace(mtmp, poss, info, cnt, gx, gy); + better_with_displacing = should_displace(mtmp, &mfp, + gg.gx, gg.gy); chcnt = 0; chi = -1; nidist = GDIST(nix, niy); for (i = 0; i < cnt; i++) { - nx = poss[i].x; - ny = poss[i].y; + nx = mfp.poss[i].x; + ny = mfp.poss[i].y; cursemsg[i] = FALSE; /* if leashed, we drag him along. */ @@ -997,58 +1094,97 @@ int after; /* this is extra fast monster movement */ continue; /* if a guardian, try to stay close by choice */ - if (!has_edog && (j = distu(nx, ny)) > 16 && j >= udist) + if (!edog && (j = distu(nx, ny)) > 16 && j >= udist) continue; - if ((info[i] & ALLOW_M) && MON_AT(nx, ny)) { + ranged_only = FALSE; + + if ((mfp.info[i] & ALLOW_M) && MON_AT(nx, ny)) { int mstatus; - register struct monst *mtmp2 = m_at(nx, ny); + struct monst *mtmp2 = m_at(nx, ny); + /* weight the audacity of the pet to attack a differently-leveled + * foe based on its fraction of max HP: + * 100%: up to level + 2 + * 80% and up: up to level + 1 + * 60% to 80%: up to level + * 40% to 60%: up to level - 1 + * 25% to 40%: up to level - 2 + * below 25%: won't attack peacefuls of any level (different case) + * below 20%: up to level - 3 + * + * note that balk's maximum value is +3, as it is the lowest level + * the pet will balk at attacking rather than the highest level + * they are willing to attack; note the >= used when comparing it. + */ + int balk = mtmp->m_lev + ((5 * mtmp->mhp) / mtmp->mhpmax) - 2; - if ((int) mtmp2->m_lev >= (int) mtmp->m_lev + 2 - || (mtmp2->data == &mons[PM_FLOATING_EYE] && rn2(10) - && mtmp->mcansee && haseyes(mtmp->data) && mtmp2->mcansee - && (perceives(mtmp->data) || !mtmp2->minvis)) - || (mtmp2->data == &mons[PM_GELATINOUS_CUBE] && rn2(10)) + if ((int) mtmp2->m_lev >= balk + || (mtmp2->mtame && mtmp->mtame && !Conflict) || (max_passive_dmg(mtmp2, mtmp) >= mtmp->mhp) || ((mtmp->mhp * 4 < mtmp->mhpmax || mtmp2->data->msound == MS_GUARDIAN - || mtmp2->data->msound == MS_LEADER) && mtmp2->mpeaceful - && !Conflict) - || (touch_petrifies(mtmp2->data) && !resists_ston(mtmp))) + || mtmp2->data->msound == MS_LEADER) + && mtmp2->mpeaceful && !Conflict)) { + continue; + } + if ((mtmp2->data == &mons[PM_FLOATING_EYE] && rn2(10) + && mtmp->mcansee && haseyes(mtmp->data) && mtmp2->mcansee + && (!mtmp2->minvis || perceives(mtmp->data)) + && !mon_reflects(mtmp, (char *) NULL)) + || (mtmp2->data == &mons[PM_GELATINOUS_CUBE] && rn2(10)) + || (touch_petrifies(mtmp2->data) && !resists_ston(mtmp))) { + /* only skip this foe if a ranged attack isn't viable */ + if (dist2(mtmp->mx, mtmp->my, mtmp2->mx, mtmp2->my) <= 2 + || best_target(mtmp, FALSE) != mtmp2) + continue; + ranged_only = TRUE; + } + /** FIXME: 'ranged_only' isn't used as intended yet **/ + if (ranged_only) continue; if (after) - return 0; /* hit only once each move */ + return MMOVE_NOTHING; /* hit only once each move */ - notonhead = 0; + gb.bhitpos.x = nx, gb.bhitpos.y = ny; + gn.notonhead = mtmp2->mx != nx || mtmp2->my != ny; mstatus = mattackm(mtmp, mtmp2); /* aggressor (pet) died */ - if (mstatus & MM_AGR_DIED) - return 2; + if (mstatus & M_ATTK_AGR_DIED) + return MMOVE_DIED; - if ((mstatus & MM_HIT) && !(mstatus & MM_DEF_DIED) && rn2(4) - && mtmp2->mlstmv != monstermoves + if ((mstatus & (M_ATTK_HIT | M_ATTK_DEF_DIED)) == M_ATTK_HIT + && rn2(4) + && mtmp2->mlstmv != svm.moves && !onscary(mtmp->mx, mtmp->my, mtmp2) /* monnear check needed: long worms hit on tail */ && monnear(mtmp2, mtmp->mx, mtmp->my)) { + gb.bhitpos.x = mtmp->mx, gb.bhitpos.y = mtmp->my; + gn.notonhead = FALSE; mstatus = mattackm(mtmp2, mtmp); /* return attack */ - if (mstatus & MM_DEF_DIED) - return 2; + if (mstatus & M_ATTK_DEF_DIED) + return MMOVE_DIED; } - return 0; + return MMOVE_DONE; } - if ((info[i] & ALLOW_MDISP) && MON_AT(nx, ny) + if ((mfp.info[i] & ALLOW_MDISP) && MON_AT(nx, ny) && better_with_displacing && !undesirable_disp(mtmp, nx, ny)) { int mstatus; - register struct monst *mtmp2 = m_at(nx, ny); + struct monst *mtmp2 = m_at(nx, ny); mstatus = mdisplacem(mtmp, mtmp2, FALSE); /* displace monster */ - if (mstatus & MM_DEF_DIED) - return 2; - return 0; + if (mstatus & M_ATTK_DEF_DIED) + return MMOVE_DIED; + return MMOVE_NOTHING; } + /* avoid a location hero just kicked */ + if (m_avoid_kicked_loc(mtmp, nx, ny)) + continue; + if (m_avoid_soko_push_loc(mtmp, nx, ny)) + continue; + { /* Dog avoids harmful traps, but perhaps it has to pass one * in order to follow player. (Non-harmful traps do not @@ -1059,7 +1195,7 @@ int after; /* this is extra fast monster movement */ */ struct trap *trap; - if ((info[i] & ALLOW_TRAPS) && (trap = t_at(nx, ny))) { + if ((mfp.info[i] & ALLOW_TRAPS) && (trap = t_at(nx, ny))) { if (mtmp->mleashed) { if (!Deaf) whimper(mtmp); @@ -1075,13 +1211,16 @@ int after; /* this is extra fast monster movement */ /* dog eschews cursed objects, but likes dog food */ /* (minion isn't interested; `cursemsg' stays FALSE) */ - if (has_edog) - for (obj = level.objects[nx][ny]; obj; obj = obj->nexthere) { + if (edog) { + boolean can_reach_food = could_reach_item(mtmp, nx, ny); + + for (obj = svl.level.objects[nx][ny]; obj; obj = obj->nexthere) { if (obj->cursed) { cursemsg[i] = TRUE; - } else if ((otyp = dogfood(mtmp, obj)) < MANFOOD - && (otyp < ACCFOOD - || edog->hungrytime <= monstermoves)) { + } else if (can_reach_food + && (otyp = dogfood(mtmp, obj)) < MANFOOD + && (otyp < ACCFOOD + || edog->hungrytime <= svm.moves)) { /* Note: our dog likes the food so much that he * might eat it even when it conceals a cursed object */ nix = nx; @@ -1092,18 +1231,20 @@ int after; /* this is extra fast monster movement */ goto newdogpos; } } + } /* didn't find something to eat; if we saw a cursed item and aren't being forced to walk on it, usually keep looking */ if (cursemsg[i] && !mtmp->mleashed && uncursedcnt > 0 && rn2(13 * uncursedcnt)) continue; - /* lessen the chance of backtracking to previous position(s) */ - /* This causes unintended issues for pets trying to follow - the hero. Thus, only run it if not leashed and >5 tiles - away. */ + /* + * Lessen the chance of backtracking to previous position(s). + * This causes unintended issues for pets trying to follow the + * hero. Thus, only run it if not leashed and >5 tiles away. + */ if (!mtmp->mleashed && distmin(mtmp->mx, mtmp->my, u.ux, u.uy) > 5) { - k = has_edog ? uncursedcnt : cnt; + k = edog ? uncursedcnt : cnt; for (j = 0; j < MTSZ && j < k - 1; j++) if (nx == mtmp->mtrack[j].x && ny == mtmp->mtrack[j].y) if (rn2(MTSZ * (k - j))) @@ -1129,76 +1270,26 @@ int after; /* this is extra fast monster movement */ * now's the time for ranged attacks. Note that the pet can move * after it performs its ranged attack. Should this be changed? */ - { - struct monst *mtarg; - int hungry = 0; - - /* How hungry is the pet? */ - if (!mtmp->isminion) { - struct edog *dog = EDOG(mtmp); - - hungry = (monstermoves > (dog->hungrytime + 300)); - } - - /* Identify the best target in a straight line from the pet; - * if there is such a target, we'll let the pet attempt an - * attack. - */ - mtarg = best_target(mtmp); - - /* Hungry pets are unlikely to use breath/spit attacks */ - if (mtarg && (!hungry || !rn2(5))) { - int mstatus; - - if (mtarg == &youmonst) { - if (mattacku(mtmp)) - return 2; - } else { - mstatus = mattackm(mtmp, mtarg); - - /* Shouldn't happen, really */ - if (mstatus & MM_AGR_DIED) - return 2; - - /* Allow the targeted nasty to strike back - if - * the targeted beast doesn't have a ranged attack, - * nothing will happen. - */ - if ((mstatus & MM_HIT) && !(mstatus & MM_DEF_DIED) - && rn2(4) && mtarg != &youmonst) { - - /* Can monster see? If it can, it can retaliate - * even if the pet is invisible, since it'll see - * the direction from which the ranged attack came; - * if it's blind or unseeing, it can't retaliate - */ - if (mtarg->mcansee && haseyes(mtarg->data)) { - mstatus = mattackm(mtarg, mtmp); - if (mstatus & MM_DEF_DIED) - return 2; - } - } - } - } - } + if ((i = pet_ranged_attk(mtmp, FALSE)) != MMOVE_NOTHING) + return i; newdogpos: if (nix != omx || niy != omy) { boolean wasseen; - if (info[chi] & ALLOW_U) { + if (mfp.info[chi] & ALLOW_U) { if (mtmp->mleashed) { /* play it safe */ - pline("%s breaks loose of %s leash!", Monnam(mtmp), - mhis(mtmp)); + pline_mon(mtmp, "%s breaks loose of %s leash!", + Monnam(mtmp), mhis(mtmp)); m_unleash(mtmp, FALSE); } (void) mattacku(mtmp); - return 0; + return MMOVE_DONE; } if (!m_in_out_region(mtmp, nix, niy)) - return 1; + return MMOVE_MOVED; if (m_digweapon_check(mtmp, nix,niy)) - return 0; + return MMOVE_NOTHING; /* insert a worm_move() if worms ever begin to eat things */ wasseen = canseemon(mtmp); @@ -1208,25 +1299,25 @@ int after; /* this is extra fast monster movement */ /* describe top item of pile, not necessarily cursed item itself; don't use glyph_at() here--it would return the pet but we want to know whether an object is remembered at this map location */ - struct obj *o = (!Hallucination && level.flags.hero_memory + struct obj *o = (!Hallucination && svl.level.flags.hero_memory && glyph_is_object(levl[nix][niy].glyph)) ? vobj_at(nix, niy) : 0; const char *what = o ? distant_name(o, doname) : something; - pline("%s %s reluctantly over %s.", noit_Monnam(mtmp), - vtense((char *) 0, locomotion(mtmp->data, "step")), what); + pline_mon(mtmp, "%s %s reluctantly %s %s.", noit_Monnam(mtmp), + vtense((char *) 0, locomotion(mtmp->data, "step")), + (is_flyer(mtmp->data) || is_floater(mtmp->data)) ? "over" + : "onto", + what); } - for (j = MTSZ - 1; j > 0; j--) - mtmp->mtrack[j] = mtmp->mtrack[j - 1]; - mtmp->mtrack[0].x = omx; - mtmp->mtrack[0].y = omy; + mon_track_add(mtmp, omx, omy); /* We have to know if the pet's going to do a combined eat and * move before moving it, but it can't eat until after being * moved. Thus the do_eat flag. */ if (do_eat) { if (dog_eat(mtmp, obj, omx, omy, FALSE) == 2) - return 2; + return MMOVE_DIED; } } else if (mtmp->mleashed && distu(omx, omy) > 4) { /* an incredible kludge, but the only way to keep pooch near @@ -1241,14 +1332,14 @@ int after; /* this is extra fast monster movement */ if (goodpos(cc.x, cc.y, mtmp, 0)) goto dognext; - i = xytod(nx, ny); - for (j = (i + 7) % 8; j < (i + 1) % 8; j++) { - dtoxy(&cc, j); + i = xytodir(nx, ny); + for (j = DIR_LEFT(i); j < DIR_RIGHT(i); j++) { + dirtocoord(&cc, j); if (goodpos(cc.x, cc.y, mtmp, 0)) goto dognext; } - for (j = (i + 6) % 8; j < (i + 2) % 8; j++) { - dtoxy(&cc, j); + for (j = DIR_LEFT2(i); j < DIR_RIGHT2(i); j++) { + dirtocoord(&cc, j); if (goodpos(cc.x, cc.y, mtmp, 0)) goto dognext; } @@ -1256,20 +1347,19 @@ int after; /* this is extra fast monster movement */ cc.y = mtmp->my; dognext: if (!m_in_out_region(mtmp, nix, niy)) - return 1; + return MMOVE_MOVED; remove_monster(mtmp->mx, mtmp->my); place_monster(mtmp, cc.x, cc.y); newsym(cc.x, cc.y); set_apparxy(mtmp); } - return 1; + return MMOVE_MOVED; +#undef GDIST } /* check if a monster could pick up objects from a location */ -STATIC_OVL boolean -could_reach_item(mon, nx, ny) -struct monst *mon; -xchar nx, ny; +boolean +could_reach_item(struct monst *mon, coordxy nx, coordxy ny) { if ((!is_pool(nx, ny) || is_swimmer(mon->data)) && (!is_lava(nx, ny) || likes_lava(mon->data)) @@ -1285,10 +1375,11 @@ xchar nx, ny; * Since the maximum food distance is 5, this should never be more than 5 * calls deep. */ -STATIC_OVL boolean -can_reach_location(mon, mx, my, fx, fy) -struct monst *mon; -xchar mx, my, fx, fy; +staticfn boolean +can_reach_location( + struct monst *mon, + coordxy mx, coordxy my, + coordxy fx, coordxy fy) { int i, j; int dist; @@ -1305,8 +1396,10 @@ xchar mx, my, fx, fy; continue; if (dist2(i, j, fx, fy) >= dist) continue; - if (IS_ROCK(levl[i][j].typ) && !passes_walls(mon->data) - && (!may_dig(i, j) || !tunnels(mon->data))) + if (IS_OBSTRUCTED(levl[i][j].typ) && !passes_walls(mon->data) + && (!may_dig(i, j) || !tunnels(mon->data) + /* tunnelling monsters can't do that on the rogue level */ + || Is_rogue_level(&u.uz))) continue; if (IS_DOOR(levl[i][j].typ) && (levl[i][j].doormask & (D_CLOSED | D_LOCKED))) @@ -1321,25 +1414,23 @@ xchar mx, my, fx, fy; } /* do_clear_area client */ -STATIC_PTR void -wantdoor(x, y, distance) -int x, y; -genericptr_t distance; +staticfn void +wantdoor(coordxy x, coordxy y, genericptr_t distance) { int ndist, *dist_ptr = (int *) distance; if (*dist_ptr > (ndist = distu(x, y))) { - gx = x; - gy = y; + gg.gx = x; + gg.gy = y; *dist_ptr = ndist; } } -static struct qmchoices { +static const struct qmchoices { int mndx; /* type of pet, 0 means any */ char mlet; /* symbol of pet, 0 means any */ unsigned mappearance; /* mimic this */ - uchar m_ap_type; /* what is the thing it is mimicing? */ + uchar m_ap_type; /* what is the thing it is mimicking? */ } qm[] = { /* Things that some pets might be thinking about at the time */ { PM_LITTLE_DOG, 0, PM_KITTEN, M_AP_MONSTER }, @@ -1349,29 +1440,39 @@ static struct qmchoices { { PM_HOUSECAT, 0, PM_DOG, M_AP_MONSTER }, { PM_LARGE_CAT, 0, PM_LARGE_DOG, M_AP_MONSTER }, { PM_HOUSECAT, 0, PM_GIANT_RAT, M_AP_MONSTER }, - { 0, S_DOG, SINK, - M_AP_FURNITURE }, /* sorry, no fire hydrants in NetHack */ + { 0, S_DOG, S_sink, M_AP_FURNITURE }, /* sorry, no fire hydrants */ { 0, 0, TRIPE_RATION, M_AP_OBJECT }, /* leave this at end */ }; void -finish_meating(mtmp) -struct monst *mtmp; +finish_meating(struct monst *mtmp) { mtmp->meating = 0; - if (M_AP_TYPE(mtmp) && mtmp->mappearance && mtmp->cham == NON_PM) { + if (M_AP_TYPE(mtmp) != M_AP_NOTHING && mtmp->data->mlet != S_MIMIC) { /* was eating a mimic and now appearance needs resetting */ - mtmp->m_ap_type = 0; + mtmp->m_ap_type = M_AP_NOTHING; mtmp->mappearance = 0; newsym(mtmp->mx, mtmp->my); } } -STATIC_OVL void -quickmimic(mtmp) -struct monst *mtmp; +/* + * variation of leashable() that takes a PM_ index */ +staticfn boolean +mnum_leashable(int mnum) { - int idx = 0, trycnt = 5, spotted; + return ((mnum >= LOW_PM && mnum <= HIGH_PM) + && mnum != PM_LONG_WORM && !unsolid(&mons[mnum]) + && (!nolimbs(&mons[mnum]) || has_head(&mons[mnum]))) + ? TRUE + : FALSE; +} + +void +quickmimic(struct monst *mtmp) +{ + int idx = 0, trycnt = 5, spotted, seeloc; + boolean was_leashed = mtmp->mleashed; char buf[BUFSZ]; if (Protection_from_shape_changers || !mtmp->meating) @@ -1398,37 +1499,49 @@ struct monst *mtmp; if (trycnt == 0) idx = SIZE(qm) - 1; - Strcpy(buf, mon_nam(mtmp)); + Strcpy(buf, y_monnam(mtmp)); /* "your " or "the " or "Fang" */ spotted = canspotmon(mtmp); + seeloc = cansee(mtmp->mx, mtmp->my); mtmp->m_ap_type = qm[idx].m_ap_type; mtmp->mappearance = qm[idx].mappearance; - if (spotted || cansee(mtmp->mx, mtmp->my) || canspotmon(mtmp)) { - /* this isn't quite right; if sensing a monster without being - able to see its location, you really shouldn't be told you - sense it becoming furniture or an object that you can't see - (on the other hand, perhaps you're sensing a brief glimpse - of its mind as it changes form) */ + if (spotted || seeloc || canspotmon(mtmp)) { + int prev_glyph = glyph_at(mtmp->mx, mtmp->my); + const char *what = (M_AP_TYPE(mtmp) == M_AP_FURNITURE) + ? defsyms[mtmp->mappearance].explanation + : (M_AP_TYPE(mtmp) == M_AP_OBJECT + && OBJ_DESCR(objects[mtmp->mappearance])) + ? OBJ_DESCR(objects[mtmp->mappearance]) + : (M_AP_TYPE(mtmp) == M_AP_OBJECT + && OBJ_NAME(objects[mtmp->mappearance])) + ? OBJ_NAME(objects[mtmp->mappearance]) + : (M_AP_TYPE(mtmp) == M_AP_MONSTER) + ? pmname(&mons[mtmp->mappearance], + Mgender(mtmp)) + : something; + newsym(mtmp->mx, mtmp->my); - You("%s %s %sappear%s where %s was!", - cansee(mtmp->mx, mtmp->my) ? "see" : "sense that", - (M_AP_TYPE(mtmp) == M_AP_FURNITURE) - ? an(defsyms[mtmp->mappearance].explanation) - : (M_AP_TYPE(mtmp) == M_AP_OBJECT - && OBJ_DESCR(objects[mtmp->mappearance])) - ? an(OBJ_DESCR(objects[mtmp->mappearance])) - : (M_AP_TYPE(mtmp) == M_AP_OBJECT - && OBJ_NAME(objects[mtmp->mappearance])) - ? an(OBJ_NAME(objects[mtmp->mappearance])) - : (M_AP_TYPE(mtmp) == M_AP_MONSTER) - ? an(mons[mtmp->mappearance].mname) - : something, - cansee(mtmp->mx, mtmp->my) ? "" : "has ", - cansee(mtmp->mx, mtmp->my) ? "" : "ed", - buf); + if (was_leashed + && (M_AP_TYPE(mtmp) != M_AP_MONSTER + || !mnum_leashable(mtmp->mappearance))) { + Your("leash goes slack."); + m_unleash(mtmp, FALSE); + } + if (glyph_at(mtmp->mx, mtmp->my) != prev_glyph) + You("%s %s %s where %s was!", + seeloc ? "see" : "sense that", + (what != something) ? an(what) : what, + seeloc ? "appear" : "has appeared", buf); + else + You("sense that %s feels rather %s-ish.", buf, what); + display_nhwindow(WIN_MAP, TRUE); } } +#undef DOG_HUNGRY +#undef DOG_WEAK +#undef DOG_STARVE + /*dogmove.c*/ diff --git a/src/dokick.c b/src/dokick.c index d9e275fbd..a399aca03 100644 --- a/src/dokick.c +++ b/src/dokick.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 dokick.c $NHDT-Date: 1575245057 2019/12/02 00:04:17 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.136 $ */ +/* NetHack 5.0 dokick.c $NHDT-Date: 1712453347 2024/04/07 01:29:07 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.223 $ */ /* Copyright (c) Izchak Miller, Mike Stephenson, Steve Linhart, 1989. */ /* NetHack may be freely redistributed. See license for details. */ @@ -6,33 +6,32 @@ #define is_bigfoot(x) ((x) == &mons[PM_SASQUATCH]) #define martial() \ - (martial_bonus() || is_bigfoot(youmonst.data) \ + (martial_bonus() || is_bigfoot(gy.youmonst.data) \ || (uarmf && uarmf->otyp == KICKING_BOOTS)) -static NEARDATA struct rm *maploc, nowhere; -static NEARDATA const char *gate_str; - -/* kickedobj (decl.c) tracks a kicked object until placed or destroyed */ - -extern boolean notonhead; /* for long worms */ - -STATIC_DCL void FDECL(kickdmg, (struct monst *, BOOLEAN_P)); -STATIC_DCL boolean FDECL(maybe_kick_monster, (struct monst *, - XCHAR_P, XCHAR_P)); -STATIC_DCL void FDECL(kick_monster, (struct monst *, XCHAR_P, XCHAR_P)); -STATIC_DCL int FDECL(kick_object, (XCHAR_P, XCHAR_P, char *)); -STATIC_DCL int FDECL(really_kick_object, (XCHAR_P, XCHAR_P)); -STATIC_DCL char *FDECL(kickstr, (char *, const char *)); -STATIC_DCL void FDECL(otransit_msg, (struct obj *, BOOLEAN_P, long)); -STATIC_DCL void FDECL(drop_to, (coord *, SCHAR_P)); +/* gk.kickedobj (decl.c) tracks a kicked object until placed or destroyed */ + +staticfn void kickdmg(struct monst *, boolean); +staticfn boolean maybe_kick_monster(struct monst *, coordxy, coordxy); +staticfn void kick_monster(struct monst *, coordxy, coordxy); +staticfn int kick_object(coordxy, coordxy, char *) NONNULLARG3; +staticfn int really_kick_object(coordxy, coordxy); +staticfn char *kickstr(char *, const char *) NONNULLPTRS; +staticfn boolean watchman_thief_arrest(struct monst *) NONNULLPTRS; +staticfn boolean watchman_door_damage(struct monst *, + coordxy, coordxy) NONNULLARG1; +staticfn void kick_dumb(coordxy, coordxy); +staticfn void kick_ouch(coordxy, coordxy, const char *) NONNULLARG3; +staticfn void kick_door(coordxy, coordxy, int); +staticfn int kick_nondoor(coordxy, coordxy, int); +staticfn void otransit_msg(struct obj *, boolean, boolean, long); +staticfn void drop_to(coord *, schar, coordxy, coordxy) NONNULLARG1; static const char kick_passes_thru[] = "kick passes harmlessly through"; /* kicking damage when not poly'd into a form with a kick attack */ -STATIC_OVL void -kickdmg(mon, clumsy) -struct monst *mon; -boolean clumsy; +staticfn void +kickdmg(struct monst *mon, boolean clumsy) { int mdx, mdy; int dmg = (ACURRSTR + ACURR(A_DEX) + ACURR(A_CON)) / 15; @@ -54,7 +53,7 @@ boolean clumsy; if (mon->data == &mons[PM_SHADE]) dmg = 0; - specialdmg = special_dmgval(&youmonst, mon, W_ARMF, (long *) 0); + specialdmg = special_dmgval(&gy.youmonst, mon, W_ARMF, (long *) 0); if (mon->data == &mons[PM_SHADE] && !specialdmg) { pline_The("%s.", kick_passes_thru); @@ -99,6 +98,7 @@ boolean clumsy; /* see if the monster has a place to move into */ mdx = mon->mx + u.dx; mdy = mon->my + u.dy; + /* TODO: replace with mhurtle? */ if (goodpos(mdx, mdy, mon, 0)) { pline("%s reels from the blow.", Monnam(mon)); if (m_in_out_region(mon, mdx, mdy)) { @@ -107,7 +107,7 @@ boolean clumsy; place_monster(mon, mdx, mdy); newsym(mon->mx, mon->my); set_apparxy(mon); - if (mintrap(mon) == 2) + if (mintrap(mon, NO_TRAP_FLAGS) == Trap_Killed_Mon) trapkilled = TRUE; } } @@ -122,32 +122,28 @@ boolean clumsy; use_skill(kick_skill, 1); } -STATIC_OVL boolean -maybe_kick_monster(mon, x, y) -struct monst *mon; -xchar x, y; +staticfn boolean +maybe_kick_monster(struct monst *mon, coordxy x, coordxy y) { if (mon) { - boolean save_forcefight = context.forcefight; + boolean save_forcefight = svc.context.forcefight; - bhitpos.x = x; - bhitpos.y = y; + gb.bhitpos.x = x; + gb.bhitpos.y = y; if (!mon->mpeaceful || !canspotmon(mon)) - context.forcefight = TRUE; /* attack even if invisible */ + svc.context.forcefight = TRUE; /* attack even if invisible */ /* kicking might be halted by discovery of hidden monster, by player declining to attack peaceful monster, or by passing out due to encumbrance */ if (attack_checks(mon, (struct obj *) 0) || overexertion()) mon = 0; /* don't kick after all */ - context.forcefight = save_forcefight; + svc.context.forcefight = save_forcefight; } return (boolean) (mon != 0); } -STATIC_OVL void -kick_monster(mon, x, y) -struct monst *mon; -xchar x, y; +staticfn void +kick_monster(struct monst *mon, coordxy x, coordxy y) { boolean clumsy = FALSE; int i, j; @@ -184,26 +180,28 @@ xchar x, y; * normally, getting all your attacks _including_ all your kicks. * If you have >1 kick attack, you get all of them. */ - if (Upolyd && attacktype(youmonst.data, AT_KICK)) { + if (Upolyd && attacktype(gy.youmonst.data, AT_KICK)) { struct attack *uattk; int sum, kickdieroll, armorpenalty, specialdmg, attknum = 0, tmp = find_roll_to_hit(mon, AT_KICK, (struct obj *) 0, &attknum, &armorpenalty); + mon_maybe_unparalyze(mon); for (i = 0; i < NATTK; i++) { /* first of two kicks might have provoked counterattack that has incapacitated the hero (ie, floating eye) */ - if (multi < 0) + if (gm.multi < 0) break; - uattk = &youmonst.data->mattk[i]; + uattk = &gy.youmonst.data->mattk[i]; /* we only care about kicking attacks here */ if (uattk->aatyp != AT_KICK) continue; kickdieroll = rnd(20); - specialdmg = special_dmgval(&youmonst, mon, W_ARMF, (long *) 0); + specialdmg = special_dmgval(&gy.youmonst, mon, W_ARMF, + (long *) 0); if (mon->data == &mons[PM_SHADE] && !specialdmg) { /* doesn't matter whether it would have hit or missed, and shades have no passive counterattack */ @@ -212,9 +210,9 @@ xchar x, y; } else if (tmp > kickdieroll) { You("kick %s.", mon_nam(mon)); sum = damageum(mon, uattk, specialdmg); - (void) passive(mon, uarmf, (boolean) (sum > 0), - (sum != 2), AT_KICK, FALSE); - if (sum == 2) + (void) passive(mon, uarmf, (sum != M_ATTK_MISS), + !(sum & M_ATTK_DEF_DIED), AT_KICK, FALSE); + if ((sum & M_ATTK_DEF_DIED)) break; /* Defender died */ } else { missum(mon, uattk, (tmp + armorpenalty > kickdieroll)); @@ -227,9 +225,17 @@ xchar x, y; i = -inv_weight(); j = weight_cap(); + /* What the following confusing if statements mean: + * If you are over 70% of carrying capacity, you go through a "deal no + * damage" check, and if that fails, a "clumsy kick" check. + * At this % of carrycap | Chance of no damage | Chance of clumsiness + * [70%-80%) | 1/4 | 1/3 + * [80%-90%) | 1/3 | 1/2 + * [90%-100%) | 1/2 | 1 + */ if (i < (j * 3) / 10) { if (!rn2((i < j / 10) ? 2 : (i < j / 5) ? 3 : 4)) { - if (martial() && !rn2(2)) + if (martial()) goto doit; Your("clumsy kick does no damage."); (void) passive(mon, uarmf, FALSE, 1, AT_KICK, FALSE); @@ -263,7 +269,7 @@ xchar x, y; if (mon->mx != x || mon->my != y) { (void) unmap_invisible(x, y); pline("%s %s, %s evading your %skick.", Monnam(mon), - (!level.flags.noteleport && can_teleport(mon->data)) + (can_teleport(mon->data) && !noteleport_level(mon)) ? "teleports" : is_floater(mon->data) ? "floats" @@ -286,9 +292,7 @@ xchar x, y; * The gold object is *not* attached to the fobj chain! */ boolean -ghitm(mtmp, gold) -register struct monst *mtmp; -register struct obj *gold; +ghitm(struct monst *mtmp, struct obj *gold) { boolean msg_given = FALSE; @@ -303,15 +307,19 @@ register struct obj *gold; msg_given = TRUE; } } else { + unsigned was_sleeping = mtmp->msleeping; long umoney, value = gold->quan * objects[gold->otyp].oc_cost; - mtmp->msleeping = 0; + mtmp->msleeping = 0; /* end indeterminate sleep (won't get here + * for temporary--counted--sleep since that + * uses mfrozen and mfrozen implies !mcanmove) */ finish_meating(mtmp); if (!mtmp->isgd && !rn2(4)) /* not always pleasing */ setmangry(mtmp, TRUE); /* greedy monsters catch gold */ if (cansee(mtmp->mx, mtmp->my)) - pline("%s catches the gold.", Monnam(mtmp)); + pline("%s %scatches the gold.", Monnam(mtmp), + was_sleeping ? "awakens and " : ""); (void) mpickobj(mtmp, gold); gold = (struct obj *) 0; /* obj has been freed */ if (mtmp->isshk) { @@ -327,6 +335,7 @@ register struct obj *gold; if (!robbed) make_happy_shk(mtmp, FALSE); } else { + SetVoice(mtmp, 0, 80, 0); if (mtmp->mpeaceful) { ESHK(mtmp)->credit += value; You("have %ld %s in credit.", ESHK(mtmp)->credit, @@ -335,50 +344,59 @@ register struct obj *gold; verbalize("Thanks, scum!"); } } else if (mtmp->ispriest) { + SetVoice(mtmp, 0, 80, 0); if (mtmp->mpeaceful) verbalize("Thank you for your contribution."); else verbalize("Thanks, scum!"); } else if (mtmp->isgd) { - umoney = money_cnt(invent); + umoney = money_cnt(gi.invent); /* Some of these are iffy, because a hostile guard won't become peaceful and resume leading hero out of the vault. If he did do that, player - could try fighting, then weasle out of being + could try fighting, then weasel out of being killed by throwing his/her gold when losing. */ - verbalize( - umoney - ? "Drop the rest and follow me." - : hidden_gold() - ? "You still have hidden gold. Drop it now." - : mtmp->mpeaceful - ? "I'll take care of that; please move along." - : "I'll take that; now get moving."); + SetVoice(mtmp, 0, 80, 0); + verbalize(umoney ? "Drop the rest and follow me." + : hidden_gold(TRUE) + ? "You still have hidden gold. Drop it now." + : mtmp->mpeaceful + ? "I'll take care of that; please move along." + : "I'll take that; now get moving."); } else if (is_mercenary(mtmp->data)) { + boolean was_angry = !mtmp->mpeaceful; long goldreqd = 0L; - if (rn2(3)) { - if (mtmp->data == &mons[PM_SOLDIER]) - goldreqd = 100L; - else if (mtmp->data == &mons[PM_SERGEANT]) - goldreqd = 250L; - else if (mtmp->data == &mons[PM_LIEUTENANT]) - goldreqd = 500L; - else if (mtmp->data == &mons[PM_CAPTAIN]) - goldreqd = 750L; - - if (goldreqd) { - umoney = money_cnt(invent); - if (value - > goldreqd - + (umoney + u.ulevel * rn2(5)) / ACURR(A_CHA)) - mtmp->mpeaceful = TRUE; - } + if (mtmp->data == &mons[PM_SOLDIER]) + goldreqd = 100L; + else if (mtmp->data == &mons[PM_SERGEANT]) + goldreqd = 250L; + else if (mtmp->data == &mons[PM_LIEUTENANT]) + goldreqd = 500L; + else if (mtmp->data == &mons[PM_CAPTAIN]) + goldreqd = 750L; + + if (goldreqd && rn2(3)) { + umoney = money_cnt(gi.invent); + goldreqd += (umoney + u.ulevel * rn2(5)) / ACURR(A_CHA); + if (value > goldreqd) + mtmp->mpeaceful = TRUE; } - if (mtmp->mpeaceful) + + if (!mtmp->mpeaceful) { + SetVoice(mtmp, 0, 80, 0); + if (goldreqd) + verbalize("That's not enough, coward!"); + else /* unbribable (watchman) */ + verbalize("I don't take bribes from scum like you!"); + } else if (was_angry) { + SetVoice(mtmp, 0, 80, 0); verbalize("That should do. Now beat it!"); - else - verbalize("That's not enough, coward!"); + } else { + SetVoice(mtmp, 0, 80, 0); + verbalize("Thanks for the tip, %s.", + flags.female ? "lady" : "buddy"); + } } return TRUE; } @@ -391,14 +409,15 @@ register struct obj *gold; /* container is kicked, dropped, thrown or otherwise impacted by player. * Assumes container is on floor. Checks contents for possible damage. */ void -container_impact_dmg(obj, x, y) -struct obj *obj; -xchar x, y; /* coordinates where object was before the impact, not after */ +container_impact_dmg( + struct obj *obj, + coordxy x, /* coordinates where object was */ + coordxy y) /* before the impact, not after */ { struct monst *shkp; struct obj *otmp, *otmp2; long loss = 0L; - boolean costly, insider, frominv; + boolean costly, insider, frominv, wchange = FALSE; /* only consider normal containers */ if (!Is_container(obj) || !Has_contents(obj) || Is_mbag(obj)) @@ -409,7 +428,7 @@ xchar x, y; /* coordinates where object was before the impact, not after */ insider = (*u.ushops && inside_shop(u.ux, u.uy) && *in_rooms(x, y, SHOPBASE) == *u.ushops); /* if dropped or thrown, shop ownership flags are set on this obj */ - frominv = (obj != kickedobj); + frominv = (obj != gk.kickedobj); for (otmp = obj->cobj; otmp; otmp = otmp2) { const char *result = (char *) 0; @@ -427,8 +446,13 @@ xchar x, y; /* coordinates where object was before the impact, not after */ /* eggs laid by you. penalty is -1 per egg, max 5, * but it's always exactly 1 that breaks */ - if (otmp->otyp == EGG && otmp->spe && otmp->corpsenm >= LOW_PM) + if (otmp->otyp == EGG && otmp->spe && ismnum(otmp->corpsenm)) change_luck(-1); + if (otmp->otyp == EGG) { + Soundeffect(se_egg_cracking, 25); + } else { + Soundeffect(se_glass_shattering, 25); + } You_hear("a muffled %s.", result); if (costly) { if (frominv && !otmp->unpaid) @@ -444,43 +468,44 @@ xchar x, y; /* coordinates where object was before the impact, not after */ } /* contents of this container are no longer known */ obj->cknown = 0; + wchange = TRUE; } } + if (wchange) + obj->owt = weight(obj); if (costly && loss) { if (!insider) { You("caused %ld %s worth of damage!", loss, currency(loss)); make_angry_shk(shkp, x, y); } else { - You("owe %s %ld %s for objects destroyed.", mon_nam(shkp), loss, + You("owe %s %ld %s for objects destroyed.", shkname(shkp), loss, currency(loss)); } } } /* jacket around really_kick_object */ -STATIC_OVL int -kick_object(x, y, kickobjnam) -xchar x, y; -char *kickobjnam; +staticfn int +kick_object(coordxy x, coordxy y, char *kickobjnam) { int res = 0; *kickobjnam = '\0'; /* if a pile, the "top" object gets kicked */ - kickedobj = level.objects[x][y]; - if (kickedobj) { - /* kick object; if doing is fatal, done() will clean up kickedobj */ - Strcpy(kickobjnam, killer_xname(kickedobj)); /* matters iff res==0 */ + gk.kickedobj = svl.level.objects[x][y]; + if (gk.kickedobj) { + /* formatted object name matters iff res==0 */ + Strcpy(kickobjnam, killer_xname(gk.kickedobj)); + /* kick object; if fatal, done() will clean up kickedobj */ res = really_kick_object(x, y); - kickedobj = (struct obj *) 0; + gk.kickedobj = (struct obj *) 0; } return res; } /* guts of kick_object */ -STATIC_OVL int -really_kick_object(x, y) -xchar x, y; +staticfn int +really_kick_object(coordxy x, coordxy y) { int range; struct monst *mon, *shkp = 0; @@ -488,9 +513,9 @@ xchar x, y; char bhitroom; boolean costly, isgold, slide = FALSE; - /* kickedobj should always be set due to conditions of call */ - if (!kickedobj || kickedobj->otyp == BOULDER || kickedobj == uball - || kickedobj == uchain) + /* gk.kickedobj should always be set due to conditions of call */ + if (!gk.kickedobj || gk.kickedobj->otyp == BOULDER + || gk.kickedobj == uball || gk.kickedobj == uchain) return 0; if ((trap = t_at(x, y)) != 0) { @@ -504,7 +529,7 @@ xchar x, y; return 1; } if (trap->ttyp == STATUE_TRAP) { - activate_statue_trap(trap, x,y, FALSE); + activate_statue_trap(trap, x, y, FALSE); return 1; } } @@ -514,34 +539,35 @@ xchar x, y; return 1; } - if (!uarmf && kickedobj->otyp == CORPSE - && touch_petrifies(&mons[kickedobj->corpsenm]) && !Stone_resistance) { + if (!uarmf && gk.kickedobj->otyp == CORPSE + && touch_petrifies(&mons[gk.kickedobj->corpsenm]) + && !Stone_resistance) { You("kick %s with your bare %s.", - corpse_xname(kickedobj, (const char *) 0, CXN_PFX_THE), + corpse_xname(gk.kickedobj, (const char *) 0, CXN_PFX_THE), makeplural(body_part(FOOT))); - if (poly_when_stoned(youmonst.data) && polymon(PM_STONE_GOLEM)) { + if (poly_when_stoned(gy.youmonst.data) && polymon(PM_STONE_GOLEM)) { ; /* hero has been transformed but kick continues */ } else { /* normalize body shape here; foot, not body_part(FOOT) */ - Sprintf(killer.name, "kicking %s barefoot", - killer_xname(kickedobj)); - instapetrify(killer.name); + Sprintf(svk.killer.name, "kicking %s barefoot", + killer_xname(gk.kickedobj)); + instapetrify(svk.killer.name); } } - isgold = (kickedobj->oclass == COIN_CLASS); + isgold = (gk.kickedobj->oclass == COIN_CLASS); { - int k_owt = (int) kickedobj->owt; + int k_owt = (int) gk.kickedobj->owt; /* for non-gold stack, 1 item will be split off below (unless an early return occurs, so we aren't moving the split to here); calculate the range for that 1 rather than for the whole stack */ - if (kickedobj->quan > 1L && !isgold) { - long save_quan = kickedobj->quan; + if (gk.kickedobj->quan > 1L && !isgold) { + long save_quan = gk.kickedobj->quan; - kickedobj->quan = 1L; - k_owt = weight(kickedobj); - kickedobj->quan = save_quan; + gk.kickedobj->quan = 1L; + k_owt = weight(gk.kickedobj); + gk.kickedobj->quan = save_quan; } /* range < 2 means the object will not move @@ -561,73 +587,86 @@ xchar x, y; } else { if (is_ice(x, y)) range += rnd(3), slide = TRUE; - if (kickedobj->greased) + if (gk.kickedobj->greased) range += rnd(3), slide = TRUE; } /* Mjollnir is magically too heavy to kick */ - if (kickedobj->oartifact == ART_MJOLLNIR) + if (is_art(gk.kickedobj, ART_MJOLLNIR)) range = 1; /* see if the object has a place to move into */ - if (!ZAP_POS(levl[x + u.dx][y + u.dy].typ) + if (!isok(x + u.dx, y + u.dy) + || !ZAP_POS(levl[x + u.dx][y + u.dy].typ) || closed_door(x + u.dx, y + u.dy)) range = 1; - costly = (!(kickedobj->no_charge && !Has_contents(kickedobj)) - && (shkp = shop_keeper(*in_rooms(x, y, SHOPBASE))) != 0 - && costly_spot(x, y)); - - if (IS_ROCK(levl[x][y].typ) || closed_door(x, y)) { + /* 5.0: this used to skip 'costly' handling if kickedobj->no_charge + was set but that optimization could result in no_charge staying set + for objects kicked out of the shop */ + shkp = find_objowner(gk.kickedobj, x, y); + costly = (shkp && (costly_spot(x, y) || (costly_adjacent(shkp, x, y) + && gk.kickedobj->unpaid))); + /* 5.0: give feedback about the item being kicked; some follow-on + messages refer to "it" */ + Norep("You kick %s.", + !isgold ? singular(gk.kickedobj, doname) : doname(gk.kickedobj)); + + if (IS_OBSTRUCTED(levl[x][y].typ) || closed_door(x, y)) { if ((!martial() && rn2(20) > ACURR(A_DEX)) - || IS_ROCK(levl[u.ux][u.uy].typ) || closed_door(u.ux, u.uy)) { + || IS_OBSTRUCTED(levl[u.ux][u.uy].typ) || closed_door(u.ux, u.uy)) { if (Blind) pline("It doesn't come loose."); else pline("%s %sn't come loose.", - The(distant_name(kickedobj, xname)), - otense(kickedobj, "do")); + The(distant_name(gk.kickedobj, xname)), + otense(gk.kickedobj, "do")); return (!rn2(3) || martial()); } if (Blind) pline("It comes loose."); else - pline("%s %s loose.", The(distant_name(kickedobj, xname)), - otense(kickedobj, "come")); - obj_extract_self(kickedobj); + pline("%s %s loose.", The(distant_name(gk.kickedobj, xname)), + otense(gk.kickedobj, "come")); + obj_extract_self(gk.kickedobj); newsym(x, y); if (costly && (!costly_spot(u.ux, u.uy) - || !index(u.urooms, *in_rooms(x, y, SHOPBASE)))) - addtobill(kickedobj, FALSE, FALSE, FALSE); - if (!flooreffects(kickedobj, u.ux, u.uy, "fall")) { - place_object(kickedobj, u.ux, u.uy); - stackobj(kickedobj); + || !strchr(u.urooms, *in_rooms(x, y, SHOPBASE)))) { + if (!gk.kickedobj->no_charge) + addtobill(gk.kickedobj, FALSE, FALSE, FALSE); + else /* don't leave no_charge set when outside shop */ + gk.kickedobj->no_charge = 0; + } + if (!flooreffects(gk.kickedobj, u.ux, u.uy, "fall")) { + place_object(gk.kickedobj, u.ux, u.uy); + impact_disturbs_zombies(gk.kickedobj, TRUE); + stackobj(gk.kickedobj); newsym(u.ux, u.uy); } return 1; } /* a box gets a chance of breaking open here */ - if (Is_box(kickedobj)) { - boolean otrp = kickedobj->otrapped; + if (Is_box(gk.kickedobj)) { + boolean otrp = gk.kickedobj->otrapped; if (range < 2) pline("THUD!"); - container_impact_dmg(kickedobj, x, y); - if (kickedobj->olocked) { + container_impact_dmg(gk.kickedobj, x, y); + if (gk.kickedobj->olocked) { if (!rn2(5) || (martial() && !rn2(2))) { You("break open the lock!"); - breakchestlock(kickedobj, FALSE); + breakchestlock(gk.kickedobj, FALSE); if (otrp) - (void) chest_trap(kickedobj, LEG, FALSE); + (void) chest_trap(gk.kickedobj, LEG, FALSE); return 1; } } else { if (!rn2(3) || (martial() && !rn2(2))) { pline_The("lid slams open, then falls shut."); - kickedobj->lknown = 1; + gk.kickedobj->lknown = 1; if (otrp) - (void) chest_trap(kickedobj, LEG, FALSE); + (void) chest_trap(gk.kickedobj, LEG, FALSE); return 1; } } @@ -637,7 +676,7 @@ xchar x, y; } /* fragile objects should not be kicked */ - if (hero_breaks(kickedobj, kickedobj->ox, kickedobj->oy, FALSE)) + if (hero_breaks(gk.kickedobj, gk.kickedobj->ox, gk.kickedobj->oy, 0)) return 1; /* too heavy to move. range is calculated as potential distance from @@ -645,14 +684,14 @@ xchar x, y; * from its current position */ if (range < 2) { - if (!Is_box(kickedobj)) + if (!Is_box(gk.kickedobj)) pline("Thump!"); return (!rn2(3) || martial()); } - if (kickedobj->quan > 1L) { + if (gk.kickedobj->quan > 1L) { if (!isgold) { - kickedobj = splitobj(kickedobj, 1L); + gk.kickedobj = splitobj(gk.kickedobj, 1L); } else { if (rn2(20)) { static NEARDATA const char *const flyingcoinmsg[] = { @@ -660,14 +699,15 @@ xchar x, y; "send coins flying in all directions", }; - pline("Thwwpingg!"); - You("%s!", flyingcoinmsg[rn2(SIZE(flyingcoinmsg))]); - (void) scatter(x, y, rn2(3) + 1, VIS_EFFECTS | MAY_HIT, - kickedobj); + if (!Deaf) + pline1("Thwwpingg!"); + You("%s!", ROLL_FROM(flyingcoinmsg)); + (void) scatter(x, y, rnd(3), VIS_EFFECTS | MAY_HIT, + gk.kickedobj); newsym(x, y); return 1; } - if (kickedobj->quan > 300L) { + if (gk.kickedobj->quan > 300L) { pline("Thump!"); return (!rn2(3) || martial()); } @@ -675,138 +715,574 @@ xchar x, y; } if (slide && !Blind) - pline("Whee! %s %s across the %s.", Doname2(kickedobj), - otense(kickedobj, "slide"), surface(x, y)); + pline("Whee! %s %s across the %s.", Doname2(gk.kickedobj), + otense(gk.kickedobj, "slide"), surface(x, y)); + +#if 0 /* now that 'costly' above includes no_charge items, this would + * clear their no_charge state (while declining to add to bill) + * and then costly handling below would end up charging for them + * + * [fixme? if there is anything which won't break when kicked + * but will be used up with hitting a monster, suppressing this + * results in the used-up item not being charged for] + */ - if (costly && !isgold) - addtobill(kickedobj, FALSE, FALSE, TRUE); - obj_extract_self(kickedobj); - (void) snuff_candle(kickedobj); + if (costly && !isgold) /* && !gk.kickedobj->no_charge) */ + addtobill(gk.kickedobj, FALSE, FALSE, TRUE); +#endif + obj_extract_self(gk.kickedobj); + (void) snuff_candle(gk.kickedobj); newsym(x, y); mon = bhit(u.dx, u.dy, range, KICKED_WEAPON, - (int FDECL((*), (MONST_P, OBJ_P))) 0, - (int FDECL((*), (OBJ_P, OBJ_P))) 0, &kickedobj); - if (!kickedobj) + (int (*) (struct monst *, struct obj *)) 0, + (int (*) (struct obj *, struct obj *)) 0, &gk.kickedobj); + if (!gk.kickedobj) return 1; /* object broken */ if (mon) { - if (mon->isshk && kickedobj->where == OBJ_MINVENT - && kickedobj->ocarry == mon) + if (mon->isshk && gk.kickedobj->where == OBJ_MINVENT + && gk.kickedobj->ocarry == mon) return 1; /* alert shk caught it */ - notonhead = (mon->mx != bhitpos.x || mon->my != bhitpos.y); - if (isgold ? ghitm(mon, kickedobj) /* caught? */ - : thitmonst(mon, kickedobj)) /* hit && used up? */ + gn.notonhead = (mon->mx != gb.bhitpos.x || mon->my != gb.bhitpos.y); + if (isgold ? ghitm(mon, gk.kickedobj) /* caught? */ + : thitmonst(mon, gk.kickedobj)) /* hit && used up? */ return 1; } /* the object might have fallen down a hole; ship_object() will have taken care of shop billing */ - if (kickedobj->where == OBJ_MIGRATING) + if (gk.kickedobj->where == OBJ_MIGRATING) return 1; - bhitroom = *in_rooms(bhitpos.x, bhitpos.y, SHOPBASE); - if (costly && (!costly_spot(bhitpos.x, bhitpos.y) + bhitroom = *in_rooms(gb.bhitpos.x, gb.bhitpos.y, SHOPBASE); + /* if obj is marked no_charge, stolen_value() won't blame hero for + theft but will clear that flag */ + if (costly && (!costly_spot(gb.bhitpos.x, gb.bhitpos.y) || *in_rooms(x, y, SHOPBASE) != bhitroom)) { + /* kicked from inside shop to somewhere outside shop */ if (isgold) - costly_gold(x, y, kickedobj->quan); + costly_gold(x, y, gk.kickedobj->quan, FALSE); else - (void) stolen_value(kickedobj, x, y, (boolean) shkp->mpeaceful, + (void) stolen_value(gk.kickedobj, x, y, (boolean) shkp->mpeaceful, FALSE); + costly = FALSE; /* already billed */ } - if (flooreffects(kickedobj, bhitpos.x, bhitpos.y, "fall")) + if (flooreffects(gk.kickedobj, gb.bhitpos.x, gb.bhitpos.y, "fall")) return 1; - if (kickedobj->unpaid) - subfrombill(kickedobj, shkp); - place_object(kickedobj, bhitpos.x, bhitpos.y); - stackobj(kickedobj); - newsym(kickedobj->ox, kickedobj->oy); + if (costly) { + long gtg = 0L; + + /* costly + landed outside shop handled above; must be inside shop */ + if (gk.kickedobj->unpaid) + subfrombill(gk.kickedobj, shkp); + + /* if billed for contained gold during kick, get a refund now */ + if (Has_contents(gk.kickedobj) + && (gtg = contained_gold(gk.kickedobj, TRUE)) > 0L) + donate_gold(gtg, shkp, FALSE); + } + place_object(gk.kickedobj, gb.bhitpos.x, gb.bhitpos.y); + impact_disturbs_zombies(gk.kickedobj, TRUE); + stackobj(gk.kickedobj); + newsym(gk.kickedobj->ox, gk.kickedobj->oy); return 1; } /* cause of death if kicking kills kicker */ -STATIC_OVL char * -kickstr(buf, kickobjnam) -char *buf; -const char *kickobjnam; +staticfn char * +kickstr(char *buf, const char *kickobjnam) { const char *what; if (*kickobjnam) what = kickobjnam; - else if (maploc == &nowhere) + else if (gm.maploc == &gn.nowhere) what = "nothing"; - else if (IS_DOOR(maploc->typ)) + else if (IS_DOOR(gm.maploc->typ)) what = "a door"; - else if (IS_TREE(maploc->typ)) + else if (IS_TREE(gm.maploc->typ)) what = "a tree"; - else if (IS_STWALL(maploc->typ)) + else if (IS_STWALL(gm.maploc->typ)) what = "a wall"; - else if (IS_ROCK(maploc->typ)) + else if (IS_OBSTRUCTED(gm.maploc->typ)) what = "a rock"; - else if (IS_THRONE(maploc->typ)) + else if (IS_THRONE(gm.maploc->typ)) what = "a throne"; - else if (IS_FOUNTAIN(maploc->typ)) + else if (IS_FOUNTAIN(gm.maploc->typ)) what = "a fountain"; - else if (IS_GRAVE(maploc->typ)) + else if (IS_GRAVE(gm.maploc->typ)) what = "a headstone"; - else if (IS_SINK(maploc->typ)) + else if (IS_SINK(gm.maploc->typ)) what = "a sink"; - else if (IS_ALTAR(maploc->typ)) + else if (IS_ALTAR(gm.maploc->typ)) what = "an altar"; - else if (IS_DRAWBRIDGE(maploc->typ)) + else if (IS_DRAWBRIDGE(gm.maploc->typ)) what = "a drawbridge"; - else if (maploc->typ == STAIRS) + else if (gm.maploc->typ == STAIRS) what = "the stairs"; - else if (maploc->typ == LADDER) + else if (gm.maploc->typ == LADDER) what = "a ladder"; - else if (maploc->typ == IRONBARS) + else if (gm.maploc->typ == IRONBARS) what = "an iron bar"; else what = "something weird"; return strcat(strcpy(buf, "kicking "), what); } +staticfn boolean +watchman_thief_arrest(struct monst *mtmp) +{ + if (is_watch(mtmp->data) && couldsee(mtmp->mx, mtmp->my) + && mtmp->mpeaceful) { + mon_yells(mtmp, "Halt, thief! You're under arrest!"); + (void) angry_guards(FALSE); + return TRUE; + } + return FALSE; +} + +staticfn boolean +watchman_door_damage(struct monst *mtmp, coordxy x, coordxy y) +{ + if (is_watch(mtmp->data) && mtmp->mpeaceful + && couldsee(mtmp->mx, mtmp->my)) { + if (levl[x][y].looted & D_WARNED) { + mon_yells(mtmp, + "Halt, vandal! You're under arrest!"); + (void) angry_guards(FALSE); + } else { + mon_yells(mtmp, "Hey, stop damaging that door!"); + levl[x][y].looted |= D_WARNED; + } + return TRUE; + } + return FALSE; +} + +staticfn void +kick_dumb(coordxy x, coordxy y) +{ + exercise(A_DEX, FALSE); + if (martial() || ACURR(A_DEX) >= 16 || rn2(3)) { + You("kick at empty space."); + if (Blind) + feel_location(x, y); + } else { + pline("Dumb move! You strain a muscle."); + exercise(A_STR, FALSE); + set_wounded_legs(RIGHT_SIDE, 5 + rnd(5)); + } + if ((Is_airlevel(&u.uz) || Levitation) && rn2(2)) + hurtle(-u.dx, -u.dy, 1, TRUE); +} + +staticfn void +kick_ouch(coordxy x, coordxy y, const char *kickobjnam) +{ + int dmg; + char buf[BUFSZ]; + + pline("Ouch! That hurts!"); + exercise(A_DEX, FALSE); + exercise(A_STR, FALSE); + if (isok(x, y)) { + if (Blind) + feel_location(x, y); /* we know we hit it */ + if (is_drawbridge_wall(x, y) >= 0) { + pline_The("drawbridge is unaffected."); + /* update maploc to refer to the drawbridge */ + (void) find_drawbridge(&x, &y); + gm.maploc = &levl[x][y]; + } + wake_nearto(x, y, 5 * 5); + } + if (!rn2(3)) + set_wounded_legs(RIGHT_SIDE, 5 + rnd(5)); + dmg = rnd(ACURR(A_CON) > 15 ? 3 : 5); + losehp(Maybe_Half_Phys(dmg), kickstr(buf, kickobjnam), KILLED_BY); + if (Is_airlevel(&u.uz) || Levitation) + hurtle(-u.dx, -u.dy, rn1(2, 4), TRUE); /* assume it's heavy */ +} + +/* kick a door */ +staticfn void +kick_door(coordxy x, coordxy y, int avrg_attrib) +{ + boolean doorbuster; + + if (gm.maploc->doormask == D_ISOPEN || gm.maploc->doormask == D_BROKEN + || gm.maploc->doormask == D_NODOOR) { + kick_dumb(x, y); + return; /* uses a turn */ + } + + /* not enough leverage to kick open doors while levitating */ + if (Levitation) { + kick_ouch(x, y, ""); + return; + } + + exercise(A_DEX, TRUE); + doorbuster = Upolyd && is_giant(gy.youmonst.data); + /* door is known to be CLOSED or LOCKED */ + if (doorbuster + || (rnl(35) < avrg_attrib + (!martial() ? 0 : ACURR(A_DEX)))) { + boolean shopdoor = *in_rooms(x, y, SHOPBASE) ? TRUE : FALSE; + + /* break the door */ + if (gm.maploc->doormask & D_TRAPPED) { + if (flags.verbose) + You("kick the door."); + exercise(A_STR, FALSE); + gm.maploc->doormask = D_NODOOR; + b_trapped("door", FOOT); + } else if (ACURR(A_STR) > 18 && !rn2(5) && !shopdoor) { + Soundeffect(se_kick_door_it_shatters, 50); + pline("As you kick the door, it shatters to pieces!"); + exercise(A_STR, TRUE); + gm.maploc->doormask = D_NODOOR; + } else { + Soundeffect(se_kick_door_it_crashes_open, 50); + pline("As you kick the door, it crashes open!"); + exercise(A_STR, TRUE); + gm.maploc->doormask = D_BROKEN; + } + feel_newsym(x, y); /* we know we broke it */ + recalc_block_point(x, y); /* vision */ + if (shopdoor) { + add_damage(x, y, SHOP_DOOR_COST); + pay_for_damage("break", FALSE); + } + if (in_town(x, y)) + (void) get_iter_mons(watchman_thief_arrest); + } else { + if (Blind) + feel_location(x, y); /* we know we hit it */ + exercise(A_STR, TRUE); + /* note: this used to be unconditional "WHAMMM!!!" but that has a + fairly strong connotation of noise that a deaf hero shouldn't + hear; we've kept the extra 'm's and one of the extra '!'s */ + pline("%s!!", (Deaf || !rn2(3)) ? "Thwack" : "Whammm"); + if (in_town(x, y)) + (void) get_iter_mons_xy(watchman_door_damage, x, y); + } +} + +/* kick non-door terrain */ +staticfn int +kick_nondoor(coordxy x, coordxy y, int avrg_attrib) +{ + if (gm.maploc->typ == SDOOR) { + if (!Levitation && rn2(30) < avrg_attrib) { + cvt_sdoor_to_door(gm.maploc); /* ->typ = DOOR */ + Soundeffect(se_crash_door, 40); + pline("Crash! %s a secret door!", + /* don't "kick open" when it's locked + unless it also happens to be trapped */ + ((gm.maploc->doormask & (D_LOCKED | D_TRAPPED)) + == D_LOCKED) ? "Your kick uncovers" : "You kick open"); + exercise(A_DEX, TRUE); + if (gm.maploc->doormask & D_TRAPPED) { + gm.maploc->doormask = D_NODOOR; + b_trapped("door", FOOT); + } else if (gm.maploc->doormask != D_NODOOR + && !(gm.maploc->doormask & D_LOCKED)) + gm.maploc->doormask = D_ISOPEN; + feel_newsym(x, y); /* we know it's gone */ + if (gm.maploc->doormask == D_ISOPEN + || gm.maploc->doormask == D_NODOOR) + unblock_point(x, y); /* vision */ + return ECMD_TIME; + } else { + kick_ouch(x, y, ""); + return ECMD_TIME; + } + } + if (gm.maploc->typ == SCORR) { + if (!Levitation && rn2(30) < avrg_attrib) { + Soundeffect(se_crash_door, 40); + pline("Crash! You kick open a secret passage!"); + exercise(A_DEX, TRUE); + gm.maploc->typ = CORR; + feel_newsym(x, y); /* we know it's gone */ + unblock_point(x, y); /* vision */ + return ECMD_TIME; + } else { + kick_ouch(x, y, ""); + return ECMD_TIME; + } + } + if (IS_THRONE(gm.maploc->typ)) { + int i; + if (Levitation) { + kick_dumb(x, y); + return ECMD_TIME; + } + if ((Luck < 0 || gm.maploc->looted) && !rn2(3)) { + gm.maploc->looted = 0; /* don't leave loose ends.. */ + gm.maploc->typ = ROOM; + (void) mkgold((long) rnd(200), x, y); + Soundeffect(se_crash_throne_destroyed, 60); + if (Blind) + pline("CRASH! You destroy it."); + else { + pline("CRASH! You destroy the throne."); + newsym(x, y); + } + exercise(A_DEX, TRUE); + return ECMD_TIME; + } else if (Luck > 0 && !rn2(3) && !gm.maploc->looted) { + (void) mkgold((long) rn1(201, 300), x, y); + i = Luck + 1; + if (i > 6) + i = 6; + while (i--) + (void) mksobj_at( + rnd_class(DILITHIUM_CRYSTAL, LUCKSTONE - 1), x, y, + FALSE, TRUE); + if (Blind) + You("kick %s loose!", something); + else { + You("kick loose some ornamental coins and gems!"); + newsym(x, y); + } + /* prevent endless milking */ + gm.maploc->looted = T_LOOTED; + return ECMD_TIME; + } else if (!rn2(4)) { + if (dunlev(&u.uz) < dunlevs_in_dungeon(&u.uz)) { + fall_through(FALSE, 0); + return ECMD_TIME; + } else { + kick_ouch(x, y, ""); + return ECMD_TIME; + } + } + kick_ouch(x, y, ""); + return ECMD_TIME; + } + if (IS_ALTAR(gm.maploc->typ)) { + if (Levitation) { + kick_dumb(x, y); + return ECMD_TIME; + } + You("kick %s.", (Blind ? something : "the altar")); + altar_wrath(x, y); + if (!rn2(3)) { + kick_ouch(x, y, ""); + return ECMD_TIME; + } + exercise(A_DEX, TRUE); + return ECMD_TIME; + } + if (IS_FOUNTAIN(gm.maploc->typ)) { + if (Levitation) { + kick_dumb(x, y); + return ECMD_TIME; + } + You("kick %s.", (Blind ? something : "the fountain")); + if (!rn2(3)) { + kick_ouch(x, y, ""); + return ECMD_TIME; + } + /* make metal boots rust */ + if (uarmf && rn2(3)) + if (water_damage(uarmf, "metal boots", TRUE) == ER_NOTHING) { + Your("boots get wet."); + /* could cause short-lived fumbling here */ + } + exercise(A_DEX, TRUE); + return ECMD_TIME; + } + if (IS_GRAVE(gm.maploc->typ)) { + if (Levitation) { + kick_dumb(x, y); + } else if (rn2(4)) { + /* minor injury */ + kick_ouch(x, y, ""); + } else if (!gm.maploc->disturbed && !rn2(2)) { + /* disturb the grave: summon a ghoul (once only), same as + when engraving */ + disturb_grave(x, y); + } else { + /* destroy the headstone, implicitly destroying any + not-yet-created contents (including zombie or mummy); + any already created contents will still be buried here */ + exercise(A_WIS, FALSE); + if (Role_if(PM_ARCHEOLOGIST) || Role_if(PM_SAMURAI) + || (u.ualign.type == A_LAWFUL && u.ualign.record > -10)) + adjalign(-sgn(u.ualign.type)); + gm.maploc->typ = ROOM; + gm.maploc->emptygrave = 0; /* clear 'flags' */ + gm.maploc->disturbed = 0; /* clear 'horizontal' */ + (void) mksobj_at(ROCK, x, y, TRUE, FALSE); + del_engr_at(x, y); + if (Blind) { + /* [feel this happen if Deaf?] */ + pline("Crack! %s broke!", Something); + } else { + pline_The("headstone topples over and breaks!"); + newsym(x, y); + } + } + return ECMD_TIME; + } + if (gm.maploc->typ == IRONBARS) { + kick_ouch(x, y, ""); + return ECMD_TIME; + } + if (IS_TREE(gm.maploc->typ)) { + struct obj *treefruit; + + /* nothing, fruit or trouble? 75:23.5:1.5% */ + if (rn2(3)) { + if (!rn2(6) && !(svm.mvitals[PM_KILLER_BEE].mvflags & G_GONE)) + You_hear("a low buzzing."); /* a warning */ + kick_ouch(x, y, ""); + return ECMD_TIME; + } + if (rn2(15) && !(gm.maploc->looted & TREE_LOOTED) + && (treefruit = rnd_treefruit_at(x, y))) { + long nfruit = 8L - rnl(7), nfall; + short frtype = treefruit->otyp; + + treefruit->quan = nfruit; + treefruit->owt = weight(treefruit); + if (is_plural(treefruit)) + pline("Some %s fall from the tree!", xname(treefruit)); + else + pline("%s falls from the tree!", An(xname(treefruit))); + nfall = scatter(x, y, 2, MAY_HIT, treefruit); + if (nfall != nfruit) { + /* scatter left some in the tree, but treefruit + * may not refer to the correct object */ + treefruit = mksobj(frtype, TRUE, FALSE); + treefruit->quan = nfruit - nfall; + pline("%ld %s got caught in the branches.", + nfruit - nfall, xname(treefruit)); + dealloc_obj(treefruit); + } + exercise(A_DEX, TRUE); + exercise(A_WIS, TRUE); /* discovered a new food source! */ + newsym(x, y); + gm.maploc->looted |= TREE_LOOTED; + return ECMD_TIME; + } else if (!(gm.maploc->looted & TREE_SWARM)) { + int cnt = rnl(4) + 2; + int made = 0; + coord mm; + + mm.x = x; + mm.y = y; + while (cnt--) { + if (enexto(&mm, mm.x, mm.y, &mons[PM_KILLER_BEE]) + && makemon(&mons[PM_KILLER_BEE], mm.x, mm.y, + MM_ANGRY|MM_NOMSG)) + made++; + } + if (made) + pline("You've attracted the tree's former occupants!"); + else + You("smell stale honey."); + gm.maploc->looted |= TREE_SWARM; + return ECMD_TIME; + } + kick_ouch(x, y, ""); + return ECMD_TIME; + } + if (IS_SINK(gm.maploc->typ)) { + int gend = poly_gender(); + + if (Levitation) { + kick_dumb(x, y); + return ECMD_TIME; + } + if (rn2(5)) { + Soundeffect(se_klunk_pipe, 60); + if (!Deaf) + pline("Klunk! The pipes vibrate noisily."); + else + pline("Klunk!"); + exercise(A_DEX, TRUE); + return ECMD_TIME; + } else if (!(gm.maploc->looted & S_LPUDDING) && !rn2(3) + && !(svm.mvitals[PM_BLACK_PUDDING].mvflags & G_GONE)) { + Soundeffect(se_gushing_sound, 100); + if (Blind) { + if (!Deaf) + You_hear("a gushing sound."); + } else { + pline("A %s ooze gushes up from the drain!", + hcolor(NH_BLACK)); + } + (void) makemon(&mons[PM_BLACK_PUDDING], x, y, MM_NOMSG); + exercise(A_DEX, TRUE); + newsym(x, y); + gm.maploc->looted |= S_LPUDDING; + return ECMD_TIME; + } else if (!(gm.maploc->looted & S_LDWASHER) && !rn2(3) + && !(svm.mvitals[PM_AMOROUS_DEMON].mvflags & G_GONE)) { + /* can't resist... */ + pline("%s returns!", (Blind ? Something : "The dish washer")); + if (makemon(&mons[PM_AMOROUS_DEMON], x, y, + MM_NOMSG | ((gend == 1 || (gend == 2 && rn2(2))) + ? MM_MALE : MM_FEMALE))) + newsym(x, y); + gm.maploc->looted |= S_LDWASHER; + exercise(A_DEX, TRUE); + return ECMD_TIME; + } else if (!rn2(3)) { + sink_backs_up(x, y); + return ECMD_TIME; + } + kick_ouch(x, y, ""); + return ECMD_TIME; + } + if (gm.maploc->typ == STAIRS || gm.maploc->typ == LADDER + || IS_STWALL(gm.maploc->typ)) { + if (!IS_STWALL(gm.maploc->typ) && gm.maploc->ladder == LA_DOWN) { + kick_dumb(x, y); + return ECMD_TIME; + } + kick_ouch(x, y, ""); + return ECMD_TIME; + } + kick_dumb(x, y); + return ECMD_TIME; +} + +/* the #kick command */ int -dokick() +dokick(void) { - int x, y; + coordxy x, y; int avrg_attrib; - int dmg = 0, glyph, oldglyph = -1; - register struct monst *mtmp; + int glyph, oldglyph = -1; + struct monst *mtmp; boolean no_kick = FALSE; - char buf[BUFSZ], kickobjnam[BUFSZ]; - kickobjnam[0] = '\0'; - if (nolimbs(youmonst.data) || slithy(youmonst.data)) { + if (nolimbs(gy.youmonst.data) || slithy(gy.youmonst.data)) { You("have no legs to kick with."); no_kick = TRUE; - } else if (verysmall(youmonst.data)) { + } else if (verysmall(gy.youmonst.data)) { You("are too small to do any kicking."); no_kick = TRUE; } else if (u.usteed) { - if (yn_function("Kick your steed?", ynchars, 'y') == 'y') { + if (yn_function("Kick your steed?", ynchars, 'y', TRUE) == 'y') { You("kick %s.", mon_nam(u.usteed)); kick_steed(); - return 1; + return ECMD_TIME; } else { - return 0; + return ECMD_OK; } } else if (Wounded_legs) { - /* note: jump() has similar code */ - long wl = (EWounded_legs & BOTH_SIDES); - const char *bp = body_part(LEG); - - if (wl == BOTH_SIDES) - bp = makeplural(bp); - Your("%s%s %s in no shape for kicking.", - (wl == LEFT_SIDE) ? "left " : (wl == RIGHT_SIDE) ? "right " : "", - bp, (wl == BOTH_SIDES) ? "are" : "is"); + legs_in_no_shape("kicking", FALSE); no_kick = TRUE; } else if (near_capacity() > SLT_ENCUMBER) { Your("load is too heavy to balance yourself for a kick."); no_kick = TRUE; - } else if (youmonst.data->mlet == S_LIZARD) { + } else if (gy.youmonst.data->mlet == S_LIZARD) { Your("legs cannot kick effectively."); no_kick = TRUE; } else if (u.uinwater && !rn2(2)) { @@ -828,21 +1304,25 @@ dokick() default: break; } + } else if (sobj_at(BOULDER, u.ux, u.uy) && !Passes_walls) { + pline("There's not enough room to kick in here."); + no_kick = TRUE; } if (no_kick) { /* ignore direction typed before player notices kick failed */ display_nhwindow(WIN_MESSAGE, TRUE); /* --More-- */ - return 0; + return ECMD_FAIL; } if (!getdir((char *) 0)) - return 0; + return ECMD_CANCEL; if (!u.dx && !u.dy) - return 0; + return ECMD_CANCEL; x = u.ux + u.dx; y = u.uy + u.dy; + gk.kickedloc.x = x, gk.kickedloc.y = y; /* KMH -- Kicking boots always succeed */ if (uarmf && uarmf->otyp == KICKING_BOOTS) @@ -856,23 +1336,24 @@ dokick() You_cant("move your %s!", body_part(LEG)); break; case 1: - if (is_animal(u.ustuck->data)) { + if (digests(u.ustuck->data)) { pline("%s burps loudly.", Monnam(u.ustuck)); break; } + FALLTHROUGH; /*FALLTHRU*/ default: Your("feeble kick has no effect."); break; } - return 1; + return ECMD_TIME; } else if (u.utrap && u.utraptype == TT_PIT) { /* must be Passes_walls */ You("kick at the side of the pit."); - return 1; + return ECMD_TIME; } if (Levitation) { - int xx, yy; + coordxy xx, yy; xx = u.ux - u.dx; yy = u.uy - u.dy; @@ -880,33 +1361,34 @@ dokick() * reachable for bracing purposes * Possible extension: allow bracing against stuff on the side? */ - if (isok(xx, yy) && !IS_ROCK(levl[xx][yy].typ) + if (isok(xx, yy) && !IS_OBSTRUCTED(levl[xx][yy].typ) && !IS_DOOR(levl[xx][yy].typ) && (!Is_airlevel(&u.uz) || !OBJ_AT(xx, yy))) { You("have nothing to brace yourself against."); - return 0; + return ECMD_OK; } } mtmp = isok(x, y) ? m_at(x, y) : 0; /* might not kick monster if it is hidden and becomes revealed, if it is peaceful and player declines to attack, or if the - hero passes out due to encumbrance with low hp; context.move + hero passes out due to encumbrance with low hp; svc.context.move will be 1 unless player declines to kick peaceful monster */ if (mtmp) { oldglyph = glyph_at(x, y); if (!maybe_kick_monster(mtmp, x, y)) - return context.move; + return (svc.context.move ? ECMD_TIME : ECMD_OK); } - wake_nearby(); + wake_nearby(FALSE); u_wipe_engr(2); if (!isok(x, y)) { - maploc = &nowhere; - goto ouch; + gm.maploc = &gn.nowhere; + kick_ouch(x, y, ""); + return ECMD_TIME; } - maploc = &levl[x][y]; + gm.maploc = &levl[x][y]; /* * The next five tests should stay in their present order: @@ -939,15 +1421,15 @@ dokick() to an unseen square doesn't leave an I behind */ && mtmp->mx == x && mtmp->my == y && !glyph_is_invisible(glyph) - && !(u.uswallow && mtmp == u.ustuck)) { + && !engulfing_u(mtmp)) { map_invisible(x, y); } /* recoil if floating */ - if ((Is_airlevel(&u.uz) || Levitation) && context.move) { + if ((Is_airlevel(&u.uz) || Levitation) && svc.context.move) { int range; range = - ((int) youmonst.data->cwt + (weight_cap() + inv_weight())); + ((int) gy.youmonst.data->cwt + (weight_cap() + inv_weight())); if (range < 1) range = 1; /* divide by zero avoidance */ range = (3 * (int) mdat->cwt) / range; @@ -956,389 +1438,42 @@ dokick() range = 1; hurtle(-u.dx, -u.dy, range, TRUE); } - return 1; + return ECMD_TIME; } (void) unmap_invisible(x, y); - if (is_pool(x, y) ^ !!u.uinwater) { + if ((is_pool(x, y) || gm.maploc->typ == LAVAWALL) ^ !!u.uinwater) { /* objects normally can't be removed from water by kicking */ - You("splash some %s around.", hliquid("water")); - return 1; + You("splash some %s around.", + hliquid(is_pool(x, y) ? "water" : "lava")); + /* pretend the kick is fast enough for lava not to burn */ + return ECMD_TIME; } if (OBJ_AT(x, y) && (!Levitation || Is_airlevel(&u.uz) || Is_waterlevel(&u.uz) || sobj_at(BOULDER, x, y))) { + char kickobjnam[BUFSZ]; + if (kick_object(x, y, kickobjnam)) { if (Is_airlevel(&u.uz)) hurtle(-u.dx, -u.dy, 1, TRUE); /* assume it's light */ - return 1; - } - goto ouch; - } - - if (!IS_DOOR(maploc->typ)) { - if (maploc->typ == SDOOR) { - if (!Levitation && rn2(30) < avrg_attrib) { - cvt_sdoor_to_door(maploc); /* ->typ = DOOR */ - pline("Crash! %s a secret door!", - /* don't "kick open" when it's locked - unless it also happens to be trapped */ - (maploc->doormask & (D_LOCKED | D_TRAPPED)) == D_LOCKED - ? "Your kick uncovers" - : "You kick open"); - exercise(A_DEX, TRUE); - if (maploc->doormask & D_TRAPPED) { - maploc->doormask = D_NODOOR; - b_trapped("door", FOOT); - } else if (maploc->doormask != D_NODOOR - && !(maploc->doormask & D_LOCKED)) - maploc->doormask = D_ISOPEN; - feel_newsym(x, y); /* we know it's gone */ - if (maploc->doormask == D_ISOPEN - || maploc->doormask == D_NODOOR) - unblock_point(x, y); /* vision */ - return 1; - } else - goto ouch; - } - if (maploc->typ == SCORR) { - if (!Levitation && rn2(30) < avrg_attrib) { - pline("Crash! You kick open a secret passage!"); - exercise(A_DEX, TRUE); - maploc->typ = CORR; - feel_newsym(x, y); /* we know it's gone */ - unblock_point(x, y); /* vision */ - return 1; - } else - goto ouch; - } - if (IS_THRONE(maploc->typ)) { - register int i; - if (Levitation) - goto dumb; - if ((Luck < 0 || maploc->doormask) && !rn2(3)) { - maploc->typ = ROOM; - maploc->doormask = 0; /* don't leave loose ends.. */ - (void) mkgold((long) rnd(200), x, y); - if (Blind) - pline("CRASH! You destroy it."); - else { - pline("CRASH! You destroy the throne."); - newsym(x, y); - } - exercise(A_DEX, TRUE); - return 1; - } else if (Luck > 0 && !rn2(3) && !maploc->looted) { - (void) mkgold((long) rn1(201, 300), x, y); - i = Luck + 1; - if (i > 6) - i = 6; - while (i--) - (void) mksobj_at( - rnd_class(DILITHIUM_CRYSTAL, LUCKSTONE - 1), x, y, - FALSE, TRUE); - if (Blind) - You("kick %s loose!", something); - else { - You("kick loose some ornamental coins and gems!"); - newsym(x, y); - } - /* prevent endless milking */ - maploc->looted = T_LOOTED; - return 1; - } else if (!rn2(4)) { - if (dunlev(&u.uz) < dunlevs_in_dungeon(&u.uz)) { - fall_through(FALSE, 0); - return 1; - } else - goto ouch; - } - goto ouch; - } - if (IS_ALTAR(maploc->typ)) { - if (Levitation) - goto dumb; - You("kick %s.", (Blind ? something : "the altar")); - altar_wrath(x, y); - if (!rn2(3)) - goto ouch; - exercise(A_DEX, TRUE); - return 1; - } - if (IS_FOUNTAIN(maploc->typ)) { - if (Levitation) - goto dumb; - You("kick %s.", (Blind ? something : "the fountain")); - if (!rn2(3)) - goto ouch; - /* make metal boots rust */ - if (uarmf && rn2(3)) - if (water_damage(uarmf, "metal boots", TRUE) == ER_NOTHING) { - Your("boots get wet."); - /* could cause short-lived fumbling here */ - } - exercise(A_DEX, TRUE); - return 1; - } - if (IS_GRAVE(maploc->typ)) { - if (Levitation) - goto dumb; - if (rn2(4)) - goto ouch; - exercise(A_WIS, FALSE); - if (Role_if(PM_ARCHEOLOGIST) || Role_if(PM_SAMURAI) - || ((u.ualign.type == A_LAWFUL) && (u.ualign.record > -10))) { - adjalign(-sgn(u.ualign.type)); - } - maploc->typ = ROOM; - maploc->doormask = 0; - (void) mksobj_at(ROCK, x, y, TRUE, FALSE); - del_engr_at(x, y); - if (Blind) - pline("Crack! %s broke!", Something); - else { - pline_The("headstone topples over and breaks!"); - newsym(x, y); - } - return 1; - } - if (maploc->typ == IRONBARS) - goto ouch; - if (IS_TREE(maploc->typ)) { - struct obj *treefruit; - - /* nothing, fruit or trouble? 75:23.5:1.5% */ - if (rn2(3)) { - if (!rn2(6) && !(mvitals[PM_KILLER_BEE].mvflags & G_GONE)) - You_hear("a low buzzing."); /* a warning */ - goto ouch; - } - if (rn2(15) && !(maploc->looted & TREE_LOOTED) - && (treefruit = rnd_treefruit_at(x, y))) { - long nfruit = 8L - rnl(7), nfall; - short frtype = treefruit->otyp; - - treefruit->quan = nfruit; - treefruit->owt = weight(treefruit); - if (is_plural(treefruit)) - pline("Some %s fall from the tree!", xname(treefruit)); - else - pline("%s falls from the tree!", An(xname(treefruit))); - nfall = scatter(x, y, 2, MAY_HIT, treefruit); - if (nfall != nfruit) { - /* scatter left some in the tree, but treefruit - * may not refer to the correct object */ - treefruit = mksobj(frtype, TRUE, FALSE); - treefruit->quan = nfruit - nfall; - pline("%ld %s got caught in the branches.", - nfruit - nfall, xname(treefruit)); - dealloc_obj(treefruit); - } - exercise(A_DEX, TRUE); - exercise(A_WIS, TRUE); /* discovered a new food source! */ - newsym(x, y); - maploc->looted |= TREE_LOOTED; - return 1; - } else if (!(maploc->looted & TREE_SWARM)) { - int cnt = rnl(4) + 2; - int made = 0; - coord mm; - - mm.x = x; - mm.y = y; - while (cnt--) { - if (enexto(&mm, mm.x, mm.y, &mons[PM_KILLER_BEE]) - && makemon(&mons[PM_KILLER_BEE], mm.x, mm.y, - MM_ANGRY)) - made++; - } - if (made) - pline("You've attracted the tree's former occupants!"); - else - You("smell stale honey."); - maploc->looted |= TREE_SWARM; - return 1; - } - goto ouch; - } - if (IS_SINK(maploc->typ)) { - int gend = poly_gender(); - short washerndx = (gend == 1 || (gend == 2 && rn2(2))) - ? PM_INCUBUS - : PM_SUCCUBUS; - - if (Levitation) - goto dumb; - if (rn2(5)) { - if (!Deaf) - pline("Klunk! The pipes vibrate noisily."); - else - pline("Klunk!"); - exercise(A_DEX, TRUE); - return 1; - } else if (!(maploc->looted & S_LPUDDING) && !rn2(3) - && !(mvitals[PM_BLACK_PUDDING].mvflags & G_GONE)) { - if (Blind) - You_hear("a gushing sound."); - else - pline("A %s ooze gushes up from the drain!", - hcolor(NH_BLACK)); - (void) makemon(&mons[PM_BLACK_PUDDING], x, y, NO_MM_FLAGS); - exercise(A_DEX, TRUE); - newsym(x, y); - maploc->looted |= S_LPUDDING; - return 1; - } else if (!(maploc->looted & S_LDWASHER) && !rn2(3) - && !(mvitals[washerndx].mvflags & G_GONE)) { - /* can't resist... */ - pline("%s returns!", (Blind ? Something : "The dish washer")); - if (makemon(&mons[washerndx], x, y, NO_MM_FLAGS)) - newsym(x, y); - maploc->looted |= S_LDWASHER; - exercise(A_DEX, TRUE); - return 1; - } else if (!rn2(3)) { - if (Blind && Deaf) - Sprintf(buf, " %s", body_part(FACE)); - else - buf[0] = '\0'; - pline("%s%s%s.", !Deaf ? "Flupp! " : "", - !Blind - ? "Muddy waste pops up from the drain" - : !Deaf - ? "You hear a sloshing sound" /* Deaf-aware */ - : "Something splashes you in the", buf); - if (!(maploc->looted & S_LRING)) { /* once per sink */ - 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! */ - maploc->looted |= S_LRING; - } - return 1; - } - goto ouch; - } - if (maploc->typ == STAIRS || maploc->typ == LADDER - || IS_STWALL(maploc->typ)) { - if (!IS_STWALL(maploc->typ) && maploc->ladder == LA_DOWN) - goto dumb; - ouch: - pline("Ouch! That hurts!"); - exercise(A_DEX, FALSE); - exercise(A_STR, FALSE); - if (isok(x, y)) { - if (Blind) - feel_location(x, y); /* we know we hit it */ - if (is_drawbridge_wall(x, y) >= 0) { - pline_The("drawbridge is unaffected."); - /* update maploc to refer to the drawbridge */ - (void) find_drawbridge(&x, &y); - maploc = &levl[x][y]; - } - } - if (!rn2(3)) - set_wounded_legs(RIGHT_SIDE, 5 + rnd(5)); - dmg = rnd(ACURR(A_CON) > 15 ? 3 : 5); - losehp(Maybe_Half_Phys(dmg), kickstr(buf, kickobjnam), KILLED_BY); - if (Is_airlevel(&u.uz) || Levitation) - hurtle(-u.dx, -u.dy, rn1(2, 4), TRUE); /* assume it's heavy */ - return 1; + return ECMD_TIME; } - goto dumb; + kick_ouch(x, y, kickobjnam); + return ECMD_TIME; } - if (maploc->doormask == D_ISOPEN || maploc->doormask == D_BROKEN - || maploc->doormask == D_NODOOR) { - dumb: - exercise(A_DEX, FALSE); - if (martial() || ACURR(A_DEX) >= 16 || rn2(3)) { - You("kick at empty space."); - if (Blind) - feel_location(x, y); - } else { - pline("Dumb move! You strain a muscle."); - exercise(A_STR, FALSE); - set_wounded_legs(RIGHT_SIDE, 5 + rnd(5)); - } - if ((Is_airlevel(&u.uz) || Levitation) && rn2(2)) - hurtle(-u.dx, -u.dy, 1, TRUE); - return 1; /* uses a turn */ - } - - /* not enough leverage to kick open doors while levitating */ - if (Levitation) - goto ouch; - - exercise(A_DEX, TRUE); - /* door is known to be CLOSED or LOCKED */ - if (rnl(35) < avrg_attrib + (!martial() ? 0 : ACURR(A_DEX))) { - boolean shopdoor = *in_rooms(x, y, SHOPBASE) ? TRUE : FALSE; - /* break the door */ - if (maploc->doormask & D_TRAPPED) { - if (flags.verbose) - You("kick the door."); - exercise(A_STR, FALSE); - maploc->doormask = D_NODOOR; - b_trapped("door", FOOT); - } else if (ACURR(A_STR) > 18 && !rn2(5) && !shopdoor) { - pline("As you kick the door, it shatters to pieces!"); - exercise(A_STR, TRUE); - maploc->doormask = D_NODOOR; - } else { - pline("As you kick the door, it crashes open!"); - exercise(A_STR, TRUE); - maploc->doormask = D_BROKEN; - } - feel_newsym(x, y); /* we know we broke it */ - unblock_point(x, y); /* vision */ - if (shopdoor) { - add_damage(x, y, SHOP_DOOR_COST); - pay_for_damage("break", FALSE); - } - if (in_town(x, y)) - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) - continue; - if (is_watch(mtmp->data) && couldsee(mtmp->mx, mtmp->my) - && mtmp->mpeaceful) { - mon_yells(mtmp, "Halt, thief! You're under arrest!"); - (void) angry_guards(FALSE); - break; - } - } - } else { - if (Blind) - feel_location(x, y); /* we know we hit it */ - exercise(A_STR, TRUE); - pline("WHAMMM!!!"); - if (in_town(x, y)) - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) - continue; - if (is_watch(mtmp->data) && mtmp->mpeaceful - && couldsee(mtmp->mx, mtmp->my)) { - if (levl[x][y].looted & D_WARNED) { - mon_yells(mtmp, - "Halt, vandal! You're under arrest!"); - (void) angry_guards(FALSE); - } else { - mon_yells(mtmp, "Hey, stop damaging that door!"); - levl[x][y].looted |= D_WARNED; - } - break; - } - } - } - return 1; + if (IS_DOOR(gm.maploc->typ)) + kick_door(x, y, avrg_attrib); + else + return kick_nondoor(x, y, avrg_attrib); + return ECMD_TIME; } -STATIC_OVL void -drop_to(cc, loc) -coord *cc; -schar loc; +staticfn void +drop_to(coord *cc, schar loc, coordxy x, coordxy y) { + stairway *stway = stairway_at(x, y); + /* cover all the MIGR_xxx choices generated by down_gate() */ switch (loc) { case MIGR_RANDOM: /* trap door or hole */ @@ -1350,15 +1485,18 @@ schar loc; cc->y = cc->x = 0; break; } + FALLTHROUGH; /*FALLTHRU*/ case MIGR_STAIRS_UP: case MIGR_LADDER_UP: - cc->x = u.uz.dnum; - cc->y = u.uz.dlevel + 1; - break; case MIGR_SSTAIRS: - cc->x = sstairs.tolev.dnum; - cc->y = sstairs.tolev.dlevel; + if (stway) { + cc->x = stway->tolev.dnum; + cc->y = stway->tolev.dlevel; + } else { + cc->x = u.uz.dnum; + cc->y = u.uz.dlevel + 1; + } break; default: case MIGR_NOWHERE: @@ -1370,14 +1508,14 @@ schar loc; /* player or missile impacts location, causing objects to fall down */ void -impact_drop(missile, x, y, dlev) -struct obj *missile; /* caused impact, won't drop itself */ -xchar x, y; /* location affected */ -xchar dlev; /* if !0 send to dlev near player */ +impact_drop( + struct obj *missile, /* caused impact, won't drop itself */ + coordxy x, coordxy y, /* location affected */ + xint16 dlev) /* if !0 send to dlev near player */ { schar toloc; - register struct obj *obj, *obj2; - register struct monst *shkp; + struct obj *obj, *obj2; + struct monst *shkp; long oct, dct, price, debit, robbed; boolean angry, costly, isrock; coord cc; @@ -1386,7 +1524,7 @@ xchar dlev; /* if !0 send to dlev near player */ return; toloc = down_gate(x, y); - drop_to(&cc, toloc); + drop_to(&cc, toloc, x, y); if (!cc.y) return; @@ -1418,7 +1556,7 @@ xchar dlev; /* if !0 send to dlev near player */ isrock = (missile && missile->otyp == ROCK); oct = dct = 0L; - for (obj = level.objects[x][y]; obj; obj = obj2) { + for (obj = svl.level.objects[x][y]; obj; obj = obj2) { obj2 = obj->nexthere; if (obj == missile) continue; @@ -1435,7 +1573,7 @@ xchar dlev; /* if !0 send to dlev near player */ if (costly) { price += stolen_value(obj, x, y, (costly_spot(u.ux, u.uy) - && index(u.urooms, + && strchr(u.urooms, *in_rooms(x, y, SHOPBASE))), TRUE); /* set obj->no_charge to 0 */ @@ -1462,11 +1600,11 @@ xchar dlev; /* if !0 send to dlev near player */ dct == oct ? "the " : dct == 1L ? "an" : "", what); else if (oct == dct) pline("%s adjacent %s %s.", dct == 1L ? "The" : "All the", what, - gate_str); + gg.gate_str); else pline("%s adjacent %s %s.", dct == 1L ? "One of the" : "Some of the", - dct == 1L ? "objects falls" : what, gate_str); + dct == 1L ? "objects falls" : what, gg.gate_str); } if (costly && shkp && price) { @@ -1474,11 +1612,11 @@ xchar dlev; /* if !0 send to dlev near player */ You("removed %ld %s worth of goods!", price, currency(price)); if (cansee(shkp->mx, shkp->my)) { if (ESHK(shkp)->customer[0] == 0) - (void) strncpy(ESHK(shkp)->customer, plname, PL_NSIZ); + (void) strncpy(ESHK(shkp)->customer, svp.plname, PL_NSIZ); if (angry) - pline("%s is infuriated!", Monnam(shkp)); + pline("%s is infuriated!", Shknam(shkp)); else - pline("\"%s, you are a thief!\"", plname); + pline("\"%s, you are a thief!\"", svp.plname); } else You_hear("a scream, \"Thief!\""); hot_pursuit(shkp); @@ -1487,7 +1625,7 @@ xchar dlev; /* if !0 send to dlev near player */ } if (ESHK(shkp)->debit > debit) { long amt = (ESHK(shkp)->debit - debit); - You("owe %s %ld %s for goods lost.", Monnam(shkp), amt, + You("owe %s %ld %s for goods lost.", shkname(shkp), amt, currency(amt)); } } @@ -1498,24 +1636,21 @@ xchar dlev; /* if !0 send to dlev near player */ * otmp is either a kicked, dropped, or thrown object. */ boolean -ship_object(otmp, x, y, shop_floor_obj) -xchar x, y; -struct obj *otmp; -boolean shop_floor_obj; +ship_object(struct obj *otmp, coordxy x, coordxy y, boolean shop_floor_obj) { schar toloc; - xchar ox, oy; + coordxy ox, oy; coord cc; struct obj *obj; struct trap *t; - boolean nodrop, unpaid, container, impact = FALSE; + boolean nodrop, unpaid, container, impact = FALSE, chainthere = FALSE; long n = 0L; if (!otmp) return FALSE; if ((toloc = down_gate(x, y)) == MIGR_NOWHERE) return FALSE; - drop_to(&cc, toloc); + drop_to(&cc, toloc, x, y); if (!cc.y) return FALSE; @@ -1528,9 +1663,12 @@ boolean shop_floor_obj; unpaid = is_unpaid(otmp); if (OBJ_AT(x, y)) { - for (obj = level.objects[x][y]; obj; obj = obj->nexthere) - if (obj != otmp) + for (obj = svl.level.objects[x][y]; obj; obj = obj->nexthere) { + if (obj == uchain) + chainthere = TRUE; + else if (obj != otmp) n += obj->quan; + } if (n) impact = TRUE; } @@ -1544,11 +1682,13 @@ boolean shop_floor_obj; } if (cansee(x, y)) - otransit_msg(otmp, nodrop, n); + otransit_msg(otmp, nodrop, chainthere, n); if (nodrop) { - if (impact) + if (impact) { impact_drop(otmp, x, y, 0); + maybe_unhide_at(x, y); + } return FALSE; } @@ -1561,7 +1701,7 @@ boolean shop_floor_obj; (void) stolen_value( otmp, ox, oy, (costly_spot(u.ux, u.uy) - && index(u.urooms, *in_rooms(ox, oy, SHOPBASE))), + && strchr(u.urooms, *in_rooms(ox, oy, SHOPBASE))), FALSE); } /* set otmp->no_charge to 0 */ @@ -1585,10 +1725,15 @@ boolean shop_floor_obj; result = "crash"; } else { /* penalty for breaking eggs laid by you */ - if (otmp->otyp == EGG && otmp->spe && otmp->corpsenm >= LOW_PM) + if (otmp->otyp == EGG && otmp->spe && ismnum(otmp->corpsenm)) change_luck((schar) -min(otmp->quan, 5L)); result = "splat"; } + if (otmp->otyp == EGG) { + Soundeffect(se_egg_splatting, 25); + } else { + Soundeffect(se_glass_crashing, 25); + } You_hear("a muffled %s.", result); obj_extract_self(otmp); obfree(otmp, (struct obj *) 0); @@ -1599,6 +1744,7 @@ boolean shop_floor_obj; otmp->ox = cc.x; otmp->oy = cc.y; otmp->owornmask = (long) toloc; + /* boulder from rolling boulder trap, no longer part of the trap */ if (otmp->otyp == BOULDER) otmp->otrapped = 0; @@ -1620,15 +1766,17 @@ boolean shop_floor_obj; } void -obj_delivery(near_hero) -boolean near_hero; +obj_delivery(boolean near_hero) { - register struct obj *otmp, *otmp2; - register int nx, ny; + struct obj *otmp, *otmp2; + int nx = 0, ny = 0; int where; boolean nobreak, noscatter; + stairway *stway; + d_level fromdlev; + boolean isladder; - for (otmp = migrating_objs; otmp; otmp = otmp2) { + for (otmp = gm.migrating_objs; otmp; otmp = otmp2) { otmp2 = otmp->nobj; if (otmp->ox != u.uz.dnum || otmp->oy != u.uz.dlevel) continue; @@ -1646,16 +1794,22 @@ boolean near_hero; obj_extract_self(otmp); otmp->owornmask = 0L; + fromdlev.dnum = otmp->omigr_from_dnum; + fromdlev.dlevel = otmp->omigr_from_dlevel; + + isladder = FALSE; switch (where) { - case MIGR_STAIRS_UP: - nx = xupstair, ny = yupstair; - break; case MIGR_LADDER_UP: - nx = xupladder, ny = yupladder; - break; + isladder = TRUE; + FALLTHROUGH; + /*FALLTHRU*/ + case MIGR_STAIRS_UP: case MIGR_SSTAIRS: - nx = sstairs.sx, ny = sstairs.sy; + if ((stway = stairway_find_from(&fromdlev, isladder)) != 0) { + nx = stway->sx; + ny = stway->sy; + } break; case MIGR_WITH_HERO: nx = u.ux, ny = u.uy; @@ -1665,6 +1819,8 @@ boolean near_hero; nx = ny = 0; break; } + otmp->omigr_from_dnum = 0; + otmp->omigr_from_dlevel = 0; if (nx > 0) { place_object(otmp, nx, ny); if (!nobreak && !IS_SOFT(levl[nx][ny].typ)) { @@ -1695,10 +1851,7 @@ boolean near_hero; } void -deliver_obj_to_mon(mtmp, cnt, deliverflags) -int cnt; -struct monst *mtmp; -unsigned long deliverflags; +deliver_obj_to_mon(struct monst *mtmp, int cnt, unsigned long deliverflags) { struct obj *otmp, *otmp2; int where, maxobj = 1; @@ -1711,21 +1864,26 @@ unsigned long deliverflags; else maxobj = 1; +#define DELIVER_PM (M2_UNDEAD | M2_WERE | M2_HUMAN | M2_ELF | M2_DWARF \ + | M2_GNOME | M2_ORC | M2_DEMON | M2_GIANT) + cnt = 0; - for (otmp = migrating_objs; otmp; otmp = otmp2) { + for (otmp = gm.migrating_objs; otmp; otmp = otmp2) { otmp2 = otmp->nobj; where = (int) (otmp->owornmask & 0x7fffL); /* destination code */ if ((where & MIGR_TO_SPECIES) == 0) continue; - if ((mtmp->data->mflags2 & otmp->corpsenm) != 0) { + if (otmp->migr_species != NON_PM + && ((mtmp->data->mflags2 & DELIVER_PM) + == (unsigned) otmp->migr_species)) { obj_extract_self(otmp); otmp->owornmask = 0L; otmp->ox = otmp->oy = 0; /* special treatment for orcs and their kind */ if ((otmp->corpsenm & M2_ORC) != 0 && has_oname(otmp)) { - if (!has_mname(mtmp)) { + if (!has_mgivenname(mtmp)) { if (at_crime_scene || !rn2(2)) mtmp = christen_orc(mtmp, at_crime_scene ? ONAME(otmp) @@ -1735,7 +1893,9 @@ unsigned long deliverflags; } free_oname(otmp); } - otmp->corpsenm = 0; + otmp->migr_species = NON_PM; + otmp->omigr_from_dnum = 0; + otmp->omigr_from_dlevel = 0; (void) add_to_minv(mtmp, otmp); cnt++; if (maxobj && cnt >= maxobj) @@ -1745,11 +1905,8 @@ unsigned long deliverflags; } } -STATIC_OVL void -otransit_msg(otmp, nodrop, num) -register struct obj *otmp; -register boolean nodrop; -long num; +staticfn void +otransit_msg(struct obj *otmp, boolean nodrop, boolean chainthere, long num) { char *optr = 0, obuf[BUFSZ], xbuf[BUFSZ]; @@ -1762,47 +1919,50 @@ long num; } Strcpy(obuf, optr); - if (num) { /* means: other objects are impacted */ + if (num || chainthere) { /* As of 3.6.2: use a separate buffer for the suffix to avoid risk of overrunning obuf[] (let pline() handle truncation if necessary) */ - Sprintf(xbuf, " %s %s object%s", otense(otmp, "hit"), - (num == 1L) ? "another" : "other", (num > 1L) ? "s" : ""); + if (num) { /* means: other objects are impacted */ + Sprintf(xbuf, " %s %s object%s", otense(otmp, "hit"), + (num == 1L) ? "another" : "other", (num > 1L) ? "s" : ""); + } else { /* chain-only msg */ + Sprintf(xbuf, " %s your chain", otense(otmp, "rattle")); + } if (nodrop) Sprintf(eos(xbuf), "."); else - Sprintf(eos(xbuf), " and %s %s.", otense(otmp, "fall"), gate_str); + Sprintf(eos(xbuf), " and %s %s.", + otense(otmp, "fall"), gg.gate_str); pline("%s%s", obuf, xbuf); } else if (!nodrop) - pline("%s %s %s.", obuf, otense(otmp, "fall"), gate_str); + pline("%s %s %s.", obuf, otense(otmp, "fall"), gg.gate_str); } /* migration destination for objects which fall down to next level */ schar -down_gate(x, y) -xchar x, y; +down_gate(coordxy x, coordxy y) { struct trap *ttmp; + stairway *stway = stairway_at(x, y); - gate_str = 0; + gg.gate_str = 0; /* this matches the player restriction in goto_level() */ - if (on_level(&u.uz, &qstart_level) && !ok_to_quest()) + if (on_level(&u.uz, &qstart_level) && !ok_to_quest()) { return MIGR_NOWHERE; - - if ((xdnstair == x && ydnstair == y) - || (sstairs.sx == x && sstairs.sy == y && !sstairs.up)) { - gate_str = "down the stairs"; - return (xdnstair == x && ydnstair == y) ? MIGR_STAIRS_UP + } + if (stway && !stway->up && !stway->isladder) { + gg.gate_str = "down the stairs"; + return (stway->tolev.dnum == u.uz.dnum) ? MIGR_STAIRS_UP : MIGR_SSTAIRS; } - if (xdnladder == x && ydnladder == y) { - gate_str = "down the ladder"; + if (stway && !stway->up && stway->isladder) { + gg.gate_str = "down the ladder"; return MIGR_LADDER_UP; } - - if (((ttmp = t_at(x, y)) != 0 && ttmp->tseen) - && is_hole(ttmp->ttyp)) { - gate_str = (ttmp->ttyp == TRAPDOOR) ? "through the trap door" - : "through the hole"; + /* hole will always be flagged as seen; trap drop might or might not */ + if ((ttmp = t_at(x, y)) != 0 && ttmp->tseen && is_hole(ttmp->ttyp)) { + gg.gate_str = (ttmp->ttyp == TRAPDOOR) ? "through the trap door" + : "through the hole"; return MIGR_RANDOM; } return MIGR_NOWHERE; diff --git a/src/dothrow.c b/src/dothrow.c index c1a415a58..1d729ef69 100644 --- a/src/dothrow.c +++ b/src/dothrow.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 dothrow.c $NHDT-Date: 1573688688 2019/11/13 23:44:48 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.164 $ */ +/* NetHack 5.0 dothrow.c $NHDT-Date: 1737343372 2025/01/19 19:22:52 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.300 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2013. */ /* NetHack may be freely redistributed. See license for details. */ @@ -7,81 +7,133 @@ #include "hack.h" -STATIC_DCL int FDECL(throw_obj, (struct obj *, int)); -STATIC_DCL boolean FDECL(ok_to_throw, (int *)); -STATIC_DCL void NDECL(autoquiver); -STATIC_DCL int FDECL(gem_accept, (struct monst *, struct obj *)); -STATIC_DCL void FDECL(tmiss, (struct obj *, struct monst *, BOOLEAN_P)); -STATIC_DCL int FDECL(throw_gold, (struct obj *)); -STATIC_DCL void FDECL(check_shop_obj, (struct obj *, XCHAR_P, XCHAR_P, - BOOLEAN_P)); -STATIC_DCL void FDECL(breakmsg, (struct obj *, BOOLEAN_P)); -STATIC_DCL boolean FDECL(toss_up, (struct obj *, BOOLEAN_P)); -STATIC_DCL void FDECL(sho_obj_return_to_u, (struct obj * obj)); -STATIC_DCL boolean FDECL(mhurtle_step, (genericptr_t, int, int)); - -static NEARDATA const char toss_objs[] = { ALLOW_COUNT, COIN_CLASS, - ALL_CLASSES, WEAPON_CLASS, 0 }; -/* different default choices when wielding a sling (gold must be included) */ -static NEARDATA const char bullets[] = { ALLOW_COUNT, COIN_CLASS, ALL_CLASSES, - GEM_CLASS, 0 }; - -/* thrownobj (decl.c) tracks an object until it lands */ - -extern boolean notonhead; /* for long worms */ +staticfn int throw_obj(struct obj *, int); +staticfn boolean ok_to_throw(int *); +staticfn int throw_ok(struct obj *); +staticfn void autoquiver(void); +staticfn struct obj *find_launcher(struct obj *); +staticfn int gem_accept(struct monst *, struct obj *); +staticfn boolean toss_up(struct obj *, boolean) NONNULLARG1; +staticfn void sho_obj_return_to_u(struct obj * obj); +staticfn void throwit_return(boolean); +staticfn void swallowit(struct obj *); +staticfn struct obj *return_throw_to_inv(struct obj *, long, boolean, + struct obj *); +staticfn void tmiss(struct obj *, struct monst *, boolean); +staticfn int throw_gold(struct obj *); +staticfn void check_shop_obj(struct obj *, coordxy, coordxy, boolean); +staticfn void breakmsg(struct obj *, boolean); +staticfn boolean mhurtle_step(genericptr_t, coordxy, coordxy); + +/* uwep might already be removed from inventory so test for W_WEP instead; + for Valk+Mjollnir, caller needs to validate the strength requirement */ +#define AutoReturn(o,wmsk) \ + ((((wmsk) & W_WEP) != 0 \ + && ((o)->otyp == AKLYS \ + || ((o)->oartifact == ART_MJOLLNIR && Role_if(PM_VALKYRIE)))) \ + || (o)->otyp == BOOMERANG) + +/* gt.thrownobj (decl.c) tracks an object until it lands */ + +int +multishot_class_bonus( + int pm, + struct obj *ammo, + struct obj *launcher) /* can be NULL */ +{ + int multishot = 0; + schar skill = objects[ammo->otyp].oc_skill; + + switch (pm) { + case PM_CAVE_DWELLER: + /* give bonus for low-tech gear */ + if (skill == -P_SLING || skill == P_SPEAR) + multishot++; + break; + case PM_MONK: + /* allow higher volley count despite skill limitation */ + if (skill == -P_SHURIKEN) + multishot++; + break; + case PM_RANGER: + /* arbitrary; encourage use of other missiles beside daggers */ + if (skill != P_DAGGER) + multishot++; + break; + case PM_ROGUE: + /* possibly should add knives... */ + if (skill == P_DAGGER) + multishot++; + break; + case PM_NINJA: + if (skill == -P_SHURIKEN || skill == -P_DART) + multishot++; + FALLTHROUGH; + /*FALLTHRU*/ + case PM_SAMURAI: + /* role-specific launcher and its ammo */ + if (ammo->otyp == YA && launcher && launcher->otyp == YUMI) + multishot++; + break; + default: + break; /* No bonus */ + } + + return multishot; +} /* Throw the selected object, asking for direction */ -STATIC_OVL int -throw_obj(obj, shotlimit) -struct obj *obj; -int shotlimit; +staticfn int +throw_obj(struct obj *obj, int shotlimit) { - struct obj *otmp; + struct obj *otmp, *oldslot; int multishot; schar skill; long wep_mask; boolean twoweap, weakmultishot; + int res = ECMD_TIME; + struct obj_split save_osplit = svc.context.objsplit; /* ask "in what direction?" */ if (!getdir((char *) 0)) { - /* No direction specified, so cancel the throw; - * might need to undo an object split. - * We used to use freeinv(obj),addinv(obj) here, but that can - * merge obj into another stack--usually quiver--even if it hadn't - * been split from there (possibly triggering a panic in addinv), - * and freeinv+addinv potentially has other side-effects. - */ - if (obj->o_id == context.objsplit.parent_oid - || obj->o_id == context.objsplit.child_oid) - (void) unsplitobj(obj); - return 0; /* no time passes */ + /* No direction specified, so cancel the throw */ + res = ECMD_CANCEL; /* no time passes */ + goto unsplit_stack; } /* - * Throwing money is usually for getting rid of it when + * Throwing gold is usually for getting rid of it when * a leprechaun approaches, or for bribing an oncoming * angry monster. So throw the whole object. * - * If the money is in quiver, throw one coin at a time, + * If the gold is in quiver, throw one coin at a time, * possibly using a sling. */ - if (obj->oclass == COIN_CLASS && obj != uquiver) - return throw_gold(obj); + if (obj->oclass == COIN_CLASS && obj != uquiver) { + /* throw_gold will unsplit the stack itself if necessary and may have + freed the object, so don't route through unsplit_stack here */ + return throw_gold(obj); /* check */ + } - if (!canletgo(obj, "throw")) - return 0; - if (obj->oartifact == ART_MJOLLNIR && obj != uwep) { + if (!canletgo(obj, "throw")) { + res = ECMD_OK; + goto unsplit_stack; + } + if (is_art(obj, ART_MJOLLNIR) && obj != uwep) { pline("%s must be wielded before it can be thrown.", The(xname(obj))); - return 0; + res = ECMD_OK; + goto unsplit_stack; } - if ((obj->oartifact == ART_MJOLLNIR && ACURR(A_STR) < STR19(25)) - || (obj->otyp == BOULDER && !throws_rocks(youmonst.data))) { + if ((is_art(obj, ART_MJOLLNIR) && ACURR(A_STR) < STR19(25)) + || (obj->otyp == BOULDER && !throws_rocks(gy.youmonst.data))) { pline("It's too heavy."); - return 1; + res = ECMD_TIME; + goto unsplit_stack; } if (!u.dx && !u.dy && !u.dz) { You("cannot throw an object at yourself."); - return 0; + res = ECMD_OK; + goto unsplit_stack; } u_wipe_engr(2); if (!uarmg && obj->otyp == CORPSE && touch_petrifies(&mons[obj->corpsenm]) @@ -91,12 +143,14 @@ int shotlimit; /* throwing with one hand, but pluralize since the expression "with your bare hands" sounds better */ makeplural(body_part(HAND))); - Sprintf(killer.name, "throwing %s bare-handed", killer_xname(obj)); - instapetrify(killer.name); + Sprintf(svk.killer.name, "throwing %s bare-handed", + killer_xname(obj)); + instapetrify(svk.killer.name); } if (welded(obj)) { weldmsg(obj); - return 1; + res = ECMD_TIME; + goto unsplit_stack; } if (is_wet_towel(obj)) dry_a_towel(obj, -1, FALSE); @@ -113,7 +167,7 @@ int shotlimit; : obj->oclass == WEAPON_CLASS) && !(Confusion || Stunned)) { /* some roles don't get a volley bonus until becoming expert */ - weakmultishot = (Role_if(PM_WIZARD) || Role_if(PM_PRIEST) + weakmultishot = (Role_if(PM_WIZARD) || Role_if(PM_CLERIC) || (Role_if(PM_HEALER) && skill != P_KNIFE) || (Role_if(PM_TOURIST) && skill != -P_DART) /* poor dexterity also inhibits multishot */ @@ -123,6 +177,7 @@ int shotlimit; switch (P_SKILL(weapon_type(obj))) { case P_EXPERT: multishot++; + FALLTHROUGH; /*FALLTHRU*/ case P_SKILLED: if (!weakmultishot) @@ -132,37 +187,10 @@ int shotlimit; break; } /* ...or is using a special weapon for their role... */ - switch (Role_switch) { - case PM_CAVEMAN: - /* give bonus for low-tech gear */ - if (skill == -P_SLING || skill == P_SPEAR) - multishot++; - break; - case PM_MONK: - /* allow higher volley count despite skill limitation */ - if (skill == -P_SHURIKEN) - multishot++; - break; - case PM_RANGER: - /* arbitrary; encourage use of other missiles beside daggers */ - if (skill != P_DAGGER) - multishot++; - break; - case PM_ROGUE: - /* possibly should add knives... */ - if (skill == P_DAGGER) - multishot++; - break; - case PM_SAMURAI: - /* role-specific launcher and its ammo */ - if (obj->otyp == YA && uwep && uwep->otyp == YUMI) - multishot++; - break; - default: - break; /* No bonus */ - } + multishot += multishot_class_bonus(Role_switch, obj, uwep); + /* ...or using their race's special bow; no bonus for spears */ - if (!weakmultishot) + if (!weakmultishot) { switch (Race_switch) { case PM_ELF: if (obj->otyp == ELVEN_ARROW && uwep @@ -185,6 +213,15 @@ int shotlimit; break; /* No bonus */ } + /* when launcher is own quest artifact, give extra +1 with any + type of ammo appropriate for that launcher (compensates for + elven and orcish rangers loss of bonus for use of racial bow + plus racial arrows if they switch to the Longbow of Diana) */ + if (uwep && is_quest_artifact(uwep) + && ammo_and_launcher(obj, uwep)) + ++multishot; + } + /* crossbows are slow to load and probably shouldn't allow multiple shots at all, but that would result in players never using them; instead, high strength is necessary to load and shoot quickly */ @@ -200,21 +237,23 @@ int shotlimit; multishot = shotlimit; } - m_shot.s = ammo_and_launcher(obj, uwep) ? TRUE : FALSE; + gm.m_shot.s = ammo_and_launcher(obj, uwep) ? TRUE : FALSE; /* give a message if shooting more than one, or if player attempted to specify a count */ if (multishot > 1 || shotlimit > 0) { /* "You shoot N arrows." or "You throw N daggers." */ - You("%s %d %s.", m_shot.s ? "shoot" : "throw", + You("%s %d %s.", gm.m_shot.s ? "shoot" : "throw", multishot, /* (might be 1 if player gave shotlimit) */ (multishot == 1) ? singular(obj, xname) : xname(obj)); } wep_mask = obj->owornmask; - m_shot.o = obj->otyp; - m_shot.n = multishot; - for (m_shot.i = 1; m_shot.i <= m_shot.n; m_shot.i++) { + oldslot = 0; + gm.m_shot.o = obj->otyp; + gm.m_shot.n = multishot; + for (gm.m_shot.i = 1; gm.m_shot.i <= gm.m_shot.n; gm.m_shot.i++) { twoweap = u.twoweap; + assert(obj != NULL); /* m_shot.i <= m_shot.n guarantees this */ /* split this object off from its slot if necessary */ if (obj->quan > 1L) { otmp = splitobj(obj, 1L); @@ -222,30 +261,48 @@ int shotlimit; otmp = obj; if (otmp->owornmask) remove_worn_item(otmp, FALSE); + oldslot = obj->nobj; + /* obj will leave inventory and may be freed by throwit, don't + try to unsplit it from potential parent stack below */ + obj = (struct obj *) 0; } freeinv(otmp); - throwit(otmp, wep_mask, twoweap); + throwit(otmp, wep_mask, twoweap, oldslot); + encumber_msg(); } - m_shot.n = m_shot.i = 0; - m_shot.o = STRANGE_OBJECT; - m_shot.s = FALSE; - - return 1; + gm.m_shot.n = gm.m_shot.i = 0; + gm.m_shot.o = STRANGE_OBJECT; + gm.m_shot.s = FALSE; + + unsplit_stack: + /* might need to undo an object split. + * We used to use freeinv(obj),addinv(obj) here, but that can + * merge obj into another stack--usually quiver--even if it hadn't + * been split from there (possibly triggering a panic in addinv), + * and freeinv+addinv potentially has other side-effects. + */ + if (obj && obj != uquiver + && (obj->o_id == save_osplit.parent_oid + || obj->o_id == save_osplit.child_oid)) { + /* futureproofing: objsplit will have been affected if partial stack + was thrown; objects will have been split off stack to throw. */ + svc.context.objsplit = save_osplit; + (void) unsplitobj(obj); + } + return res; } /* common to dothrow() and dofire() */ -STATIC_OVL boolean -ok_to_throw(shotlimit_p) -int *shotlimit_p; /* (see dothrow()) */ +staticfn boolean +ok_to_throw(int *shotlimit_p) /* (see dothrow()) */ { - /* kludge to work around parse()'s pre-decrement of `multi' */ - *shotlimit_p = (multi || save_cm) ? multi + 1 : 0; - multi = 0; /* reset; it's been used up */ + *shotlimit_p = LIMIT_TO_RANGE_INT(0, LARGEST_INT, gc.command_count); + gm.multi = 0; /* reset; it's been used up */ - if (notake(youmonst.data)) { + if (notake(gy.youmonst.data)) { You("are physically incapable of throwing or shooting anything."); return FALSE; - } else if (nohands(youmonst.data)) { + } else if (nohands(gy.youmonst.data)) { You_cant("throw or shoot without hands."); /* not body_part(HAND) */ return FALSE; /*[what about !freehand(), aside from cursed missile launcher?]*/ @@ -255,11 +312,46 @@ int *shotlimit_p; /* (see dothrow()) */ return TRUE; } -/* t command - throw */ +/* getobj callback for object to be thrown */ +staticfn int +throw_ok(struct obj *obj) +{ + if (!obj) + return GETOBJ_EXCLUDE; + + if (obj->bknown && welded(obj)) /* not a candidate if known to be stuck */ + return GETOBJ_DOWNPLAY; + + if (AutoReturn(obj, obj->owornmask) + /* to get here, obj is boomerang or is uwep and (alkys or Mjollnir) */ + && (obj->oartifact != ART_MJOLLNIR || ACURR(A_STR) >= STR19(25))) + return GETOBJ_SUGGEST; + + if (obj->quan == 1 && (obj == uwep || (obj == uswapwep && u.twoweap))) + return GETOBJ_DOWNPLAY; + + if (obj->oclass == COIN_CLASS) + return GETOBJ_SUGGEST; + + if (!uslinging() && obj->oclass == WEAPON_CLASS) + return GETOBJ_SUGGEST; + /* Possible extension: exclude weapons that make no sense to throw, + such as whips, bows, slings, rubber hoses. */ + + if (uslinging() && obj->oclass == GEM_CLASS) + return GETOBJ_SUGGEST; + + if (throws_rocks(gy.youmonst.data) && obj->otyp == BOULDER) + return GETOBJ_SUGGEST; + + return GETOBJ_DOWNPLAY; +} + +/* the #throw command */ int -dothrow() +dothrow(void) { - register struct obj *obj; + struct obj *obj; int shotlimit; /* @@ -274,19 +366,19 @@ dothrow() * [3.6.0: shot count setup has been moved into ok_to_throw().] */ if (!ok_to_throw(&shotlimit)) - return 0; + return ECMD_OK; - obj = getobj(uslinging() ? bullets : toss_objs, "throw"); + obj = getobj("throw", throw_ok, GETOBJ_PROMPT | GETOBJ_ALLOWCNT); /* it is also possible to throw food */ /* (or jewels, or iron balls... ) */ - return obj ? throw_obj(obj, shotlimit) : 0; + return obj ? throw_obj(obj, shotlimit) : ECMD_CANCEL; } /* KMH -- Automatically fill quiver */ /* Suggested by Jeffrey Bay */ -static void -autoquiver() +staticfn void +autoquiver(void) { struct obj *otmp, *oammo = 0, *omissile = 0, *omisc = 0, *altammo = 0; @@ -294,7 +386,7 @@ autoquiver() return; /* Scan through the inventory */ - for (otmp = invent; otmp; otmp = otmp->nobj) { + for (otmp = gi.invent; otmp; otmp = otmp->nobj) { if (otmp->owornmask || otmp->oartifact || !otmp->dknown) { ; /* Skip it */ } else if (otmp->otyp == ROCK @@ -329,6 +421,8 @@ autoquiver() /* Ordinary weapon */ if (objects[otmp->otyp].oc_skill == P_DAGGER && !omissile) omissile = otmp; + else if (otmp->otyp == AKLYS) + continue; else omisc = otmp; } @@ -347,96 +441,205 @@ autoquiver() return; } -/* f command -- fire: throw from the quiver */ +/* look through hero inventory for launcher matching ammo, + avoiding known cursed items. Returns NULL if no match. */ +staticfn struct obj * +find_launcher(struct obj *ammo) +{ + struct obj *otmp, *oX; + + if (!ammo) + return (struct obj *) 0; + + for (oX = 0, otmp = gi.invent; otmp; otmp = otmp->nobj) { + if (otmp->cursed && otmp->bknown) + continue; /* known to be cursed, so skip */ + if (ammo_and_launcher(ammo, otmp)) { + if (otmp->bknown) + return otmp; /* known-B or known-U (known-C won't get here) */ + if (!oX) + oX = otmp; /* unknown-BUC; used if no known-BU item found */ + } + } + return oX; +} + +/* the #fire command -- throw from the quiver or use wielded polearm */ int -dofire() +dofire(void) { int shotlimit; struct obj *obj; + /* AutoReturn() verifies Valkyrie if weapon is Mjollnir, but it relies + on its caller to make sure hero is strong enough to throw that */ + boolean uwep_Throw_and_Return = (uwep && AutoReturn(uwep, uwep->owornmask) + && (uwep->oartifact != ART_MJOLLNIR + || ACURR(A_STR) >= STR19(25))), + skip_fireassist = FALSE; + int altres, res = ECMD_OK; /* * Same as dothrow(), except we use quivered missile instead - * of asking what to throw/shoot. + * of asking what to throw/shoot. [Note: with the advent of + * fireassist that is no longer accurate...] * - * If quiver is empty, we use autoquiver to fill it when the - * corresponding option is on. If the option is off or if - * autoquiver doesn't select anything, we ask what to throw. + * If hero is wielding a thrown-and-return weapon and quiver + * is empty or contains ammo, use the wielded weapon (won't + * have any ammo's launcher wielded due to the weapon). + * If quiver is empty, use autoquiver to fill it when the + * corresponding option is on. + * If option is off or autoquiver doesn't select anything, + * we ask what to throw. * Then we put the chosen item into the quiver slot unless * it is already in another slot. [Matters most if it is a * stack but also matters for single item if this throw gets - * aborted (ESC at the direction prompt). Already wielded - * item is excluded because wielding might be necessary - * (Mjollnir) or make the throw behave differently (aklys), - * and alt-wielded item is excluded because switching slots - * would end two-weapon combat even if throw gets aborted.] + * aborted (ESC at the direction prompt).] */ if (!ok_to_throw(&shotlimit)) - return 0; - - if ((obj = uquiver) == 0) { + return ECMD_OK; + + obj = uquiver; + /* if wielding a throw-and-return weapon, throw it if quiver is empty + or has ammo rather than missiles [since the throw/return weapon is + wielded, the ammo's launcher isn't; the ammo-only policy avoids + throwing Mjollnir if quiver contains daggers] */ + if (uwep_Throw_and_Return && (!obj || is_ammo(obj))) { + obj = uwep; + skip_fireassist = TRUE; + + } else if (!obj) { if (!flags.autoquiver) { - You("have no ammunition readied."); + /* if we're wielding a polearm, apply it */ + if (uwep && is_pole(uwep)) { + return use_pole(uwep, TRUE); + /* if we're wielding a bullwhip, apply it */ + } else if (uwep && uwep->otyp == BULLWHIP) { + return use_whip(uwep); + } else if (iflags.fireassist + && uswapwep && is_pole(uswapwep) + && !(uswapwep->cursed && uswapwep->bknown)) { + /* we have a known not-cursed polearm as swap weapon. + swap to it and retry */ + cmdq_add_ec(CQ_CANNED, doswapweapon); + cmdq_add_ec(CQ_CANNED, dofire); + return ECMD_OK; /* haven't taken any time yet */ + } else { + You("have no ammunition readied."); + } } else { autoquiver(); - if ((obj = uquiver) == 0) + obj = uquiver; + if (obj) { + /* give feedback if quiver has now been filled */ + uquiver->owornmask &= ~W_QUIVER; /* less verbose */ + prinv("You ready:", obj, 0L); + uquiver->owornmask |= W_QUIVER; + } else { You("have nothing appropriate for your quiver."); + } } - /* if autoquiver is disabled or has failed, prompt for missile; - fill quiver with it if it's not wielded or worn */ - if (!obj) { - /* in case we're using ^A to repeat prior 'f' command, don't - use direction of previous throw as getobj()'s choice here */ - in_doagain = 0; - /* choose something from inventory, then usually quiver it */ - obj = getobj(uslinging() ? bullets : toss_objs, "throw"); - /* Q command doesn't allow gold in quiver */ - if (obj && !obj->owornmask && obj->oclass != COIN_CLASS) - setuqwep(obj); /* demi-autoquiver */ - } - /* give feedback if quiver has now been filled */ - if (uquiver) { - uquiver->owornmask &= ~W_QUIVER; /* less verbose */ - prinv("You ready:", uquiver, 0L); - uquiver->owornmask |= W_QUIVER; + } + + /* if autoquiver is disabled or has failed, prompt for missile */ + if (!obj) { + /* in case we're using ^A to repeat prior 'f' command, don't + use direction of previous throw as getobj()'s choice here */ + gi.in_doagain = 0; + + /* this gives its own feedback about populating the quiver slot */ + res = doquiver_core("fire"); + if (res != ECMD_OK && res != ECMD_TIME) + return res; + + obj = uquiver; + } + + if (uquiver && is_ammo(uquiver) && iflags.fireassist + && !skip_fireassist) { + struct obj *olauncher; + + if (uwep && is_pole(uwep) && could_pole_mon()) + return use_pole(uwep, TRUE); + /* Try to find a launcher */ + if (ammo_and_launcher(uquiver, uwep)) { + obj = uquiver; + } else if (ammo_and_launcher(uquiver, uswapwep)) { + /* swap weapons and retry fire */ + cmdq_add_ec(CQ_CANNED, doswapweapon); + cmdq_add_ec(CQ_CANNED, dofire); + return res; + } else if ((olauncher = find_launcher(uquiver)) != 0) { + /* wield launcher, retry fire */ + if (uwep && !flags.pushweapon) + cmdq_add_ec(CQ_CANNED, doswapweapon); + cmdq_add_ec(CQ_CANNED, dowield); + cmdq_add_key(CQ_CANNED, olauncher->invlet); + cmdq_add_ec(CQ_CANNED, dofire); + return res; } } - return obj ? throw_obj(obj, shotlimit) : 0; + altres = obj ? throw_obj(obj, shotlimit) : ECMD_CANCEL; + /* fire can take time by filling quiver (if that causes something which + was wielded to be unwielded) even if the throw itself gets cancelled */ + return (res == ECMD_TIME) ? res : altres; } /* if in midst of multishot shooting/throwing, stop early */ void -endmultishot(verbose) -boolean verbose; +endmultishot(boolean verbose) { - if (m_shot.i < m_shot.n) { - if (verbose && !context.mon_moving) { + if (gm.m_shot.i < gm.m_shot.n) { + if (verbose && !svc.context.mon_moving) { You("stop %s after the %d%s %s.", - m_shot.s ? "firing" : "throwing", m_shot.i, ordin(m_shot.i), - m_shot.s ? "shot" : "toss"); + gm.m_shot.s ? "firing" : "throwing", + gm.m_shot.i, ordin(gm.m_shot.i), + gm.m_shot.s ? "shot" : "toss"); } - m_shot.n = m_shot.i; /* make current shot be the last */ + gm.m_shot.n = gm.m_shot.i; /* make current shot be the last */ } } /* Object hits floor at hero's feet. - Called from drop(), throwit(), hold_another_object(). */ + Called from drop(), throwit(), hold_another_object(), litter(). */ void -hitfloor(obj, verbosely) -struct obj *obj; -boolean verbosely; /* usually True; False if caller has given drop message */ +hitfloor( + struct obj *obj, + boolean verbosely) /* usually True; False if caller has given drop mesg */ { if (IS_SOFT(levl[u.ux][u.uy].typ) || u.uinwater || u.uswallow) { dropy(obj); return; } - if (IS_ALTAR(levl[u.ux][u.uy].typ)) + if (IS_ALTAR(levl[u.ux][u.uy].typ)) { doaltarobj(obj); - else if (verbosely) - pline("%s %s the %s.", Doname2(obj), otense(obj, "hit"), - surface(u.ux, u.uy)); + } else if (verbosely) { + const char *verb = (obj->otyp == WAN_STRIKING) ? "strike" : "hit"; + const char *surf = surface(u.ux, u.uy); + struct trap *t = t_at(u.ux, u.uy); + + /* describe something that might keep the object where it is + or precede next message stating that it falls */ + if (t && t->tseen) { + switch (t->ttyp) { + case TRAPDOOR: + surf = "trap door"; + break; + case HOLE: + surf = "edge of the hole"; + break; + case PIT: + case SPIKED_PIT: + surf = "edge of the pit"; + break; + default: + break; + } + } + pline("%s %s the %s.", Doname2(obj), otense(obj, verb), surf); + } - if (hero_breaks(obj, u.ux, u.uy, TRUE)) + if (hero_breaks(obj, u.ux, u.uy, BRK_FROM_INV)) return; if (ship_object(obj, u.ux, u.uy, FALSE)) return; @@ -450,13 +653,13 @@ boolean verbosely; /* usually True; False if caller has given drop message */ * before the failed callback. */ boolean -walk_path(src_cc, dest_cc, check_proc, arg) -coord *src_cc; -coord *dest_cc; -boolean FDECL((*check_proc), (genericptr_t, int, int)); -genericptr_t arg; +walk_path( + coord *src_cc, coord *dest_cc, + boolean (*check_proc)(genericptr_t, coordxy, coordxy), + genericptr_t arg) { - int x, y, dx, dy, x_change, y_change, err, i, prev_x, prev_y; + int err; + coordxy x, y, dx, dy, x_change, y_change, i, prev_x, prev_y; boolean keep_going = TRUE; /* Use Bresenham's Line Algorithm to walk from src to dest. @@ -464,7 +667,7 @@ genericptr_t arg; * This should be replaced with a more versatile algorithm * since it handles slanted moves in a suboptimal way. * Going from 'x' to 'y' needs to pass through 'z', and will - * fail if there's an obstable there, but it could choose to + * fail if there's an obstacle there, but it could choose to * pass through 'Z' instead if that way imposes no obstacle. * ..y .Zy * xz. vs x.. @@ -483,14 +686,15 @@ genericptr_t arg; if (dx < 0) { x_change = -1; dx = -dx; - } else + } else { x_change = 1; + } if (dy < 0) { y_change = -1; dy = -dy; - } else + } else { y_change = 1; - + } i = err = 0; if (dx < dy) { while (i++ < dy) { @@ -535,9 +739,7 @@ genericptr_t arg; vs drag-to-dest; original callers use first mode, jumping wants second, grappling hook backfire and thrown chained ball need third */ boolean -hurtle_jump(arg, x, y) -genericptr_t arg; -int x, y; +hurtle_jump(genericptr_t arg, coordxy x, coordxy y) { boolean res; long save_EWwalking = EWwalking; @@ -568,16 +770,16 @@ int x, y; * o let jumps go over boulders */ boolean -hurtle_step(arg, x, y) -genericptr_t arg; -int x, y; +hurtle_step(genericptr_t arg, coordxy x, coordxy y) { - int ox, oy, *range = (int *) arg; + coordxy ox, oy; + int *range = (int *) arg; struct obj *obj; struct monst *mon; boolean may_pass = TRUE, via_jumping, stopping_short; struct trap *ttmp; - int dmg = 0; + struct rm *lev; + int ltyp, dmg = 0; if (!isok(x, y)) { You_feel("the spirits holding you back."); @@ -589,68 +791,52 @@ int x, y; } via_jumping = (EWwalking & I_SPECIAL) != 0L; stopping_short = (via_jumping && *range < 2); + lev = &levl[x][y]; + ltyp = lev->typ; if (!Passes_walls || !(may_pass = may_passwall(x, y))) { - boolean odoor_diag = (IS_DOOR(levl[x][y].typ) - && (levl[x][y].doormask & D_ISOPEN) - && (u.ux - x) && (u.uy - y)); - - if (IS_ROCK(levl[x][y].typ) || closed_door(x, y) || odoor_diag) { - const char *s; - + const char *why = NULL; + boolean diagonal = (u.ux - x) != 0 && (u.uy - y) != 0, + open_door = IS_DOOR(ltyp) && (lev->doormask & D_ISOPEN) != 0, + odoor_diag = open_door && diagonal; + + if (IS_OBSTRUCTED(levl[x][y].typ) + || closed_door(x, y) || odoor_diag) { + why = IS_TREE(ltyp) ? "bumping into a tree" + : IS_OBSTRUCTED(ltyp) ? "bumping into a wall" + : odoor_diag ? "bumping into a door frame" + : "bumping into a closed door"; if (odoor_diag) - You("hit the door edge!"); + You("hit the door frame!"); pline("Ouch!"); - if (IS_TREE(levl[x][y].typ)) - s = "bumping into a tree"; - else if (IS_ROCK(levl[x][y].typ)) - s = "bumping into a wall"; - else - s = "bumping into a door"; - dmg = rnd(2 + *range); - losehp(Maybe_Half_Phys(dmg), s, KILLED_BY); - wake_nearto(x,y, 10); - return FALSE; - } - if (levl[x][y].typ == IRONBARS) { + } else if (ltyp == IRONBARS) { + why = "crashing into iron bars"; You("crash into some iron bars. Ouch!"); - dmg = rnd(2 + *range); - losehp(Maybe_Half_Phys(dmg), "crashing into iron bars", - KILLED_BY); - wake_nearto(x,y, 20); - return FALSE; - } - if ((obj = sobj_at(BOULDER, x, y)) != 0) { + } else if ((obj = sobj_at(BOULDER, x, y)) != 0) { + why = "bumping into a boulder"; You("bump into a %s. Ouch!", xname(obj)); - dmg = rnd(2 + *range); - losehp(Maybe_Half_Phys(dmg), "bumping into a boulder", KILLED_BY); - wake_nearto(x,y, 10); - return FALSE; - } - if (!may_pass) { + } else if (!may_pass) { /* did we hit a no-dig non-wall position? */ + why = "touching the edge of the universe"; You("smack into something!"); - dmg = rnd(2 + *range); - losehp(Maybe_Half_Phys(dmg), "touching the edge of the universe", - KILLED_BY); - wake_nearto(x,y, 10); - return FALSE; - } - if ((u.ux - x) && (u.uy - y) && bad_rock(youmonst.data, u.ux, y) - && bad_rock(youmonst.data, x, u.uy)) { - boolean too_much = (invent && (inv_weight() + weight_cap() > 600)); - - /* Move at a diagonal. */ - if (bigmonst(youmonst.data) || too_much) { + } else if (diagonal + && bad_rock(gy.youmonst.data, u.ux, y) + && bad_rock(gy.youmonst.data, x, u.uy)) { + boolean too_much = (gi.invent + && (inv_weight() + weight_cap() > WT_TOOMUCH_DIAGONAL)); + + if (bigmonst(gy.youmonst.data) || too_much) { + why = "wedging into a narrow crevice"; You("%sget forcefully wedged into a crevice.", too_much ? "and all your belongings " : ""); - dmg = rnd(2 + *range); - losehp(Maybe_Half_Phys(dmg), "wedging into a narrow crevice", - KILLED_BY); - wake_nearto(x,y, 10); - return FALSE; } } + if (why) { + dmg = rnd(2 + *range); + losehp(Maybe_Half_Phys(dmg), why, KILLED_BY); + wake_nearto(x, y, 10); + return FALSE; + } } if ((mon = m_at(x, y)) != 0 @@ -663,30 +849,42 @@ int x, y; && (Flying || Levitation || Wwalking)) #endif ) { - const char *mnam, *pronoun; + const char *mnam; int glyph = glyph_at(x, y); mon->mundetected = 0; /* wakeup() will handle mimic */ - mnam = a_monnam(mon); /* after unhiding */ - pronoun = noit_mhim(mon); - if (!strcmp(mnam, "it")) { - mnam = !strcmp(pronoun, "it") ? "something" : "someone"; - } + /* after unhiding; combination of a_monnam() and some_mon_nam(); + yields "someone" or "something" instead of "it" for unseen mon */ + mnam = x_monnam(mon, ARTICLE_A, (char *) 0, + ((has_mgivenname(mon) ? SUPPRESS_SADDLE : 0) + | AUGMENT_IT), + FALSE); if (!glyph_is_monster(glyph) && !glyph_is_invisible(glyph)) - You("find %s by bumping into %s.", mnam, pronoun); + You("find %s by bumping into %s.", mnam, noit_mhim(mon)); else You("bump into %s.", mnam); wakeup(mon, FALSE); if (!canspotmon(mon)) map_invisible(mon->mx, mon->my); setmangry(mon, FALSE); + if (touch_petrifies(mon->data) + /* this is a bodily collision, so check for body armor */ + && !uarmu && !uarm && !uarmc) { + Sprintf(svk.killer.name, "bumping into %s", + an(pmname(mon->data, NEUTRAL))); + instapetrify(svk.killer.name); + } + if (touch_petrifies(gy.youmonst.data) + && !which_armor(mon, W_ARMU | W_ARM | W_ARMC)) { + minstapetrify(mon, TRUE); + } wake_nearto(x, y, 10); return FALSE; } if ((u.ux - x) && (u.uy - y) - && bad_rock(youmonst.data, u.ux, y) - && bad_rock(youmonst.data, x, u.uy)) { + && bad_rock(gy.youmonst.data, u.ux, y) + && bad_rock(gy.youmonst.data, x, u.uy)) { /* Move at a diagonal. */ if (Sokoban) { You("come to an abrupt halt!"); @@ -694,10 +892,11 @@ int x, y; } } - /* Caller has already determined that dragging the ball is allowed */ - if (Punished && uball->where == OBJ_FLOOR) { + /* caller has already determined that dragging the ball is allowed; + if ball is carried we might still need to drag the chain */ + if (Punished) { int bc_control; - xchar ballx, bally, chainx, chainy; + coordxy ballx, bally, chainx, chainy; boolean cause_delay; if (drag_ball(x, y, &bc_control, &ballx, &bally, &chainx, @@ -714,18 +913,23 @@ int x, y; /* if terrain type changes, levitation or flying might become blocked or unblocked; might issue message, so do this after map+vision has been updated for new location instead of right after u_on_newpos() */ - if (levl[u.ux][u.uy].typ != levl[ox][oy].typ) + if (ltyp != levl[ox][oy].typ) switch_terrain(); + /* might be entering a special room (treasure zoo, thrown room, &c) that + has a first-time entry message, or leaving shop with unpaid goods */ + check_special_room(FALSE); + if (is_pool(x, y) && !u.uinwater) { - if ((Is_waterlevel(&u.uz) && levl[x][y].typ == WATER) - || !(Levitation || Flying || Wwalking)) { - multi = 0; /* can move, so drown() allows crawling out of water */ + if (is_waterwall(x, y) || !(Levitation || Flying || Wwalking)) { + /* couldn't move while hurtling; allow movement now so that + drown() will give a chance to crawl out of pool and survive */ + gm.multi = 0; (void) drown(); return FALSE; } else if (!Is_waterlevel(&u.uz) && !stopping_short) { Norep("You move over %s.", an(is_moat(x, y) ? "moat" : "pool")); - } + } } else if (is_lava(x, y) && !stopping_short) { Norep("You move over some lava."); } @@ -741,53 +945,125 @@ int x, y; if (stopping_short) { ; /* see the comment above hurtle_jump() */ } else if (ttmp->ttyp == MAGIC_PORTAL) { - dotrap(ttmp, 0); + dotrap(ttmp, NO_TRAP_FLAGS); return FALSE; } else if (ttmp->ttyp == VIBRATING_SQUARE) { pline("The ground vibrates as you pass it."); - dotrap(ttmp, 0); /* doesn't print messages */ + dotrap(ttmp, NO_TRAP_FLAGS); /* doesn't print messages */ } else if (ttmp->ttyp == FIRE_TRAP) { - dotrap(ttmp, 0); - } else if ((is_pit(ttmp->ttyp) || is_hole(ttmp->ttyp)) - && Sokoban) { + dotrap(ttmp, NO_TRAP_FLAGS); + } else if ((is_pit(ttmp->ttyp) || is_hole(ttmp->ttyp)) && Sokoban) { /* air currents overcome the recoil in Sokoban; when jumping, caller performs last step and enters trap */ if (!via_jumping) - dotrap(ttmp, 0); + dotrap(ttmp, NO_TRAP_FLAGS); *range = 0; return TRUE; } else { if (ttmp->tseen) - You("pass right over %s.", - an(defsyms[trap_to_defsym(ttmp->ttyp)].explanation)); + You("pass right over %s.", an(trapname(ttmp->ttyp, FALSE))); } } if (--*range < 0) /* make sure our range never goes negative */ *range = 0; if (*range != 0) - delay_output(); + nh_delay_output(); return TRUE; } -STATIC_OVL boolean -mhurtle_step(arg, x, y) -genericptr_t arg; -int x, y; +/* used by mhurtle_step() for actual hurtling and also to vary message + if target will/won't change location when knocked back */ +boolean +will_hurtle(struct monst *mon, coordxy x, coordxy y) { - struct monst *mon = (struct monst *) arg; - - /* TODO: Treat walls, doors, iron bars, pools, lava, etc. specially + if (!isok(x, y)) + return FALSE; + /* redundant when called by mhurtle() but needed for mhitm_knockback() */ + if (mon->data->msize >= MZ_HUGE || mon == u.ustuck || mon->mtrapped) + return FALSE; + /* + * TODO: Treat walls, doors, iron bars, etc. specially * rather than just stopping before. */ - if (goodpos(x, y, mon, 0) && m_in_out_region(mon, x, y)) { - remove_monster(mon->mx, mon->my); - newsym(mon->mx, mon->my); - place_monster(mon, x, y); - newsym(mon->mx, mon->my); + return goodpos(x, y, mon, MM_IGNOREWATER | MM_IGNORELAVA); +} + +staticfn boolean +mhurtle_step(genericptr_t arg, coordxy x, coordxy y) +{ + struct monst *mon = (struct monst *) arg; + struct monst *mtmp; + + if (!isok(x, y)) + return FALSE; + + if (will_hurtle(mon, x, y) && m_in_out_region(mon, x, y)) { + int res; + + if (mon != u.usteed) { + remove_monster(mon->mx, mon->my); + newsym(mon->mx, mon->my); + place_monster(mon, x, y); + newsym(mon->mx, mon->my); + } else { + /* steed is hurtling, move hero which will also move steed */ + u.ux0 = u.ux, u.uy0 = u.uy; + u_on_newpos(x, y); + newsym(u.ux0, u.uy0); /* update old position */ + vision_recalc(0); /* new location => different lines of sight */ + } + flush_screen(1); + nh_delay_output(); set_apparxy(mon); - (void) mintrap(mon); + if (is_waterwall(x, y)) + return FALSE; + res = mintrap(mon, HURTLING); + if (res == Trap_Killed_Mon + || res == Trap_Caught_Mon + || res == Trap_Moved_Mon) + return FALSE; return TRUE; } + if ((mtmp = m_at(x, y)) != 0 && mtmp != mon) { + if (canseemon(mon) || canseemon(mtmp)) + pline("%s bumps into %s.", Monnam(mon), a_monnam(mtmp)); + wakeup(mtmp, !svc.context.mon_moving); + /* check whether 'mon' is turned to stone by touching 'mtmp' */ + if (touch_petrifies(mtmp->data) + && !which_armor(mon, W_ARMU | W_ARM | W_ARMC)) { + minstapetrify(mon, !svc.context.mon_moving); + newsym(mon->mx, mon->my); + } + /* and whether 'mtmp' is turned to stone by being touched by 'mon' */ + if (touch_petrifies(mon->data) + && !which_armor(mtmp, W_ARMU | W_ARM | W_ARMC)) { + minstapetrify(mtmp, !svc.context.mon_moving); + newsym(mtmp->mx, mtmp->my); + } + } else if (u_at(x, y)) { + /* a monster has caused 'mon' to hurtle against hero */ + pline("%s bumps into you.", Some_Monnam(mon)); + stop_occupation(); + /* check whether 'mon' is turned to stone by touching poly'd hero */ + if (Upolyd && touch_petrifies(gy.youmonst.data) + && !which_armor(mon, W_ARMU | W_ARM | W_ARMC)) { + /* give poly'd hero credit/blame despite a monster causing it */ + minstapetrify(mon, TRUE); + newsym(mon->mx, mon->my); + } + /* and whether hero is turned to stone by being touched by 'mon' */ + if (touch_petrifies(mon->data) && !(uarmu || uarm || uarmc)) { + Snprintf(svk.killer.name, sizeof svk.killer.name, + "being hit by %s", + /* combine m_monnam() and noname_monnam(): + "{your,a} hurtling cockatrice" w/o assigned name */ + x_monnam(mon, mon->mtame ? ARTICLE_YOUR : ARTICLE_A, + "hurtling", EXACT_NAME | SUPPRESS_NAME, FALSE)); + instapetrify(svk.killer.name); + newsym(u.ux, u.uy); + } + } + return FALSE; } @@ -796,12 +1072,10 @@ int x, y; * throwing or kicking something. * * dx and dy should be the direction of the hurtle, not of the original - * kick or throw and be only. + * kick or throw. */ void -hurtle(dx, dy, range, verbose) -int dx, dy, range; -boolean verbose; +hurtle(int dx, int dy, int range, boolean verbose) { coord uc, cc; @@ -819,14 +1093,11 @@ boolean verbose; return; } else if (u.utrap) { You("are anchored by the %s.", - u.utraptype == TT_WEB - ? "web" - : u.utraptype == TT_LAVA - ? hliquid("lava") - : u.utraptype == TT_INFLOOR - ? surface(u.ux, u.uy) - : u.utraptype == TT_BURIEDBALL ? "buried ball" - : "trap"); + (u.utraptype == TT_WEB) ? "web" + : (u.utraptype == TT_LAVA) ? hliquid("lava") + : (u.utraptype == TT_INFLOOR) ? surface(u.ux, u.uy) + : (u.utraptype == TT_BURIEDBALL) ? "buried ball" + : "trap"); nomul(0); return; } @@ -839,13 +1110,13 @@ boolean verbose; return; /* paranoia */ nomul(-range); - multi_reason = "moving through the air"; - nomovemsg = ""; /* it just happens */ + gm.multi_reason = "moving through the air"; + gn.nomovemsg = ""; /* it just happens */ if (verbose) - You("%s in the opposite direction.", range > 1 ? "hurtle" : "float"); + You("%s in the opposite direction.", + (range > 1) ? "hurtle" : "float"); /* if we're in the midst of shooting multiple projectiles, stop */ endmultishot(TRUE); - sokoban_guilt(); uc.x = u.ux; uc.y = u.uy; /* this setting of cc is only correct if dx and dy are [-1,0,1] only */ @@ -856,12 +1127,11 @@ boolean verbose; /* Move a monster through the air for a few squares. */ void -mhurtle(mon, dx, dy, range) -struct monst *mon; -int dx, dy, range; +mhurtle(struct monst *mon, int dx, int dy, int range) { coord mc, cc; + wakeup(mon, !svc.context.mon_moving); /* At the very least, debilitate the monster */ mon->movement = 0; mon->mstun = 1; @@ -869,8 +1139,11 @@ int dx, dy, range; /* Is the monster stuck or too heavy to push? * (very large monsters have too much inertia, even floaters and flyers) */ - if (mon->data->msize >= MZ_HUGE || mon == u.ustuck || mon->mtrapped) + if (mon->data->msize >= MZ_HUGE || mon == u.ustuck || mon->mtrapped) { + if (canseemon(mon)) + pline("%s doesn't budge!", Monnam(mon)); return; + } /* Make sure dx and dy are [-1,0,1] */ dx = sgn(dx); @@ -881,20 +1154,31 @@ int dx, dy, range; if (dx && dy && NODIAG(monsndx(mon->data))) return; + /* undetected monster can be moved by your strike */ + if (mon->mundetected) { + mon->mundetected = 0; + newsym(mon->mx, mon->my); + } + if (M_AP_TYPE(mon)) + seemimic(mon); + /* Send the monster along the path */ mc.x = mon->mx; mc.y = mon->my; cc.x = mon->mx + (dx * range); cc.y = mon->my + (dy * range); (void) walk_path(&mc, &cc, mhurtle_step, (genericptr_t) mon); + if (!DEADMONSTER(mon)) { + if (t_at(mon->mx, mon->my)) + (void) mintrap(mon, FORCEBUNGLE); + else + (void) minliquid(mon); + } return; } -STATIC_OVL void -check_shop_obj(obj, x, y, broken) -struct obj *obj; -xchar x, y; -boolean broken; +staticfn void +check_shop_obj(struct obj *obj, coordxy x, coordxy y, boolean broken) { boolean costly_xy; struct monst *shkp = shop_keeper(*u.ushops); @@ -916,26 +1200,65 @@ boolean broken; /* ushops0: in case we threw while levitating and recoiled out of shop (most likely to the shk's spot in front of door) */ if (*oshops == *u.ushops || *oshops == *u.ushops0) { - if (is_unpaid(obj)) + if (is_unpaid(obj)) { + long gtg = Has_contents(obj) ? contained_gold(obj, TRUE) : 0L; + subfrombill(obj, shkp); - else if (x != shkp->mx || y != shkp->my) + if (gtg > 0L) + donate_gold(gtg, shkp, TRUE); + } else if (x != shkp->mx || y != shkp->my) { sellobj(obj, x, y); + } } } } +/* Will 'obj' cause damage if it falls on hero's head when thrown upward? + Not used to handle things which break when they hit. + Stone missile hitting hero w/ Passes_walls attribute handled separately. */ +boolean +harmless_missile(struct obj *obj) +{ + int otyp = obj->otyp; + + /* this list is fairly arbitrary */ + switch (otyp) { + case SLING: + case EUCALYPTUS_LEAF: + case KELP_FROND: + case SPRIG_OF_WOLFSBANE: + case FORTUNE_COOKIE: + case PANCAKE: + return TRUE; + case RUBBER_HOSE: + case BAG_OF_TRICKS: + return (obj->spe < 1); + case SACK: + case OILSKIN_SACK: + case BAG_OF_HOLDING: + return !Has_contents(obj); + default: + if (obj->oclass == SCROLL_CLASS) /* scrolls but not all paper objs */ + return TRUE; + if (objects[otyp].oc_material == CLOTH) + return TRUE; + break; + } + return FALSE; +} + /* * Hero tosses an object upwards with appropriate consequences. * * Returns FALSE if the object is gone. */ -STATIC_OVL boolean -toss_up(obj, hitsroof) -struct obj *obj; -boolean hitsroof; +staticfn boolean +toss_up(struct obj *obj, boolean hitsroof) { const char *action; - boolean petrifier = ((obj->otyp == EGG || obj->otyp == CORPSE) + int otyp = obj->otyp; + boolean petrifier = ((otyp == EGG || otyp == CORPSE) + && ismnum(obj->corpsenm) && touch_petrifies(&mons[obj->corpsenm])); /* note: obj->quan == 1 */ @@ -945,7 +1268,13 @@ boolean hitsroof; if (breaktest(obj)) { pline("%s hits the %s.", Doname2(obj), ceiling(u.ux, u.uy)); breakmsg(obj, !Blind); - breakobj(obj, u.ux, u.uy, TRUE, TRUE); + /* crackable armor will return True for breaktest() but will + usually return False for breakobj() */ + if (!breakobj(obj, u.ux, u.uy, TRUE, TRUE)) { + hitfloor(obj, FALSE); + gt.thrownobj = 0; + return TRUE; + } return FALSE; } action = "hits"; @@ -958,24 +1287,24 @@ boolean hitsroof; /* object now hits you */ if (obj->oclass == POTION_CLASS) { - potionhit(&youmonst, obj, POTHIT_HERO_THROW); + potionhit(&gy.youmonst, obj, POTHIT_HERO_THROW); } else if (breaktest(obj)) { - int otyp = obj->otyp; int blindinc; /* need to check for blindness result prior to destroying obj */ blindinc = ((otyp == CREAM_PIE || otyp == BLINDING_VENOM) /* AT_WEAP is ok here even if attack type was AT_SPIT */ - && can_blnd(&youmonst, &youmonst, AT_WEAP, obj)) + && can_blnd(&gy.youmonst, &gy.youmonst, AT_WEAP, obj)) ? rnd(25) : 0; breakmsg(obj, !Blind); - breakobj(obj, u.ux, u.uy, TRUE, TRUE); - obj = 0; /* it's now gone */ + if (breakobj(obj, u.ux, u.uy, TRUE, TRUE)) + obj = 0; /* it's now gone */ + switch (otyp) { case EGG: if (petrifier && !Stone_resistance - && !(poly_when_stoned(youmonst.data) + && !(poly_when_stoned(gy.youmonst.data) && polymon(PM_STONE_GOLEM))) { /* egg ends up "all over your face"; perhaps visored helmet should still save you here */ @@ -983,6 +1312,7 @@ boolean hitsroof; Your("%s fails to protect you.", helm_simple_name(uarmh)); goto petrify; } + FALLTHROUGH; /*FALLTHRU*/ case CREAM_PIE: case BLINDING_VENOM: @@ -991,7 +1321,7 @@ boolean hitsroof; if (otyp == BLINDING_VENOM && !Blind) pline("It blinds you!"); u.ucreamed += blindinc; - make_blinded(Blinded + (long) blindinc, FALSE); + make_blinded(BlindedTimeout + (long) blindinc, FALSE); if (!Blind) Your1(vision_clears); } @@ -999,25 +1329,47 @@ boolean hitsroof; default: break; } - return FALSE; + if (!obj) + return FALSE; + /* 'obj' still exists, so drop it and return True */ + hitfloor(obj, FALSE); + gt.thrownobj = 0; + } else if (harmless_missile(obj)) { + pline("It doesn't hurt."); + hitfloor(obj, FALSE); + gt.thrownobj = 0; } else { /* neither potion nor other breaking object */ - boolean less_damage = uarmh && is_metallic(uarmh), artimsg = FALSE; - int dmg = dmgval(obj, &youmonst); - - if (obj->oartifact) + int material = objects[otyp].oc_material; + boolean is_silver = (material == SILVER), + less_damage = (hard_helmet(uarmh) + && (!is_silver || !Hate_silver)), + harmless = (stone_missile(obj) + && passes_rocks(gy.youmonst.data)), + artimsg = FALSE; + int dmg = dmgval(obj, &gy.youmonst); + + if (obj->oartifact && !harmless) /* need a fake die roll here; rn1(18,2) avoids 1 and 20 */ - artimsg = artifact_hit((struct monst *) 0, &youmonst, obj, &dmg, - rn1(18, 2)); + artimsg = artifact_hit((struct monst *) 0, &gy.youmonst, obj, + &dmg, rn1(18, 2)); if (!dmg) { /* probably wasn't a weapon; base damage on weight */ - dmg = (int) obj->owt / 100; - if (dmg < 1) - dmg = 1; - else if (dmg > 6) + dmg = ((int) obj->owt + (WT_TO_DMG - 1)) / WT_TO_DMG; + dmg = (dmg <= 1) ? 1 : rnd(dmg); + if (dmg > 6) dmg = 6; - if (youmonst.data == &mons[PM_SHADE] - && objects[obj->otyp].oc_material != SILVER) + /* since obj is a non-weapon, bonuses for silver and blessed + haven't been applied (otherwise '!dmg' test will fail when + they're applicable here); we don't have to worry about + dmgval()'s artifact light against gremlin or axe against + woody creature since both involve weapons; hero-as-shade is + hypothetical because hero can't polymorph into that form */ + if (gy.youmonst.data == &mons[PM_SHADE] && !is_silver) dmg = 0; + if (obj->blessed && mon_hates_blessings(&gy.youmonst)) + dmg += rnd(4); + if (is_silver && Hate_silver) + dmg += rnd(20); } if (dmg > 1 && less_damage) dmg = 1; @@ -1028,39 +1380,54 @@ boolean hitsroof; dmg = Maybe_Half_Phys(dmg); if (uarmh) { - if (less_damage && dmg < (Upolyd ? u.mh : u.uhp)) { - if (!artimsg) - pline("Fortunately, you are wearing a hard helmet."); - /* helmet definitely protects you when it blocks petrification - */ + /* note: 'harmless' and 'petrifier' are mutually exclusive */ + if ((less_damage && dmg < (Upolyd ? u.mh : u.uhp)) || harmless) { + if (!artimsg) { + if (!harmless) /* !harmless => less_damage here */ + pline("Fortunately, you are wearing a hard helmet."); + else + pline("Unfortunately, you are wearing %s.", + an(helm_simple_name(uarmh))); /* helm or hat */ + } + + /* helmet definitely protects you when it blocks petrification */ } else if (!petrifier) { if (flags.verbose) Your("%s does not protect you.", helm_simple_name(uarmh)); } + /* stone missile against hero in xorn form would have been + harmless, but hitting a worn helmet negates that */ + harmless = FALSE; } else if (petrifier && !Stone_resistance - && !(poly_when_stoned(youmonst.data) + && !(poly_when_stoned(gy.youmonst.data) && polymon(PM_STONE_GOLEM))) { petrify: - killer.format = KILLED_BY; - Strcpy(killer.name, "elementary physics"); /* "what goes up..." */ + svk.killer.format = KILLED_BY; + /* what goes up... */ + Strcpy(svk.killer.name, "elementary physics"); You("turn to stone."); if (obj) dropy(obj); /* bypass most of hitfloor() */ - thrownobj = 0; /* now either gone or on floor */ + gt.thrownobj = 0; /* now either gone or on floor */ done(STONING); return obj ? TRUE : FALSE; } + if (is_silver && Hate_silver) + pline_The("silver sears you!"); + if (harmless) + hit(thesimpleoname(obj), &gy.youmonst, " but doesn't hurt."); + hitfloor(obj, TRUE); - thrownobj = 0; - losehp(Maybe_Half_Phys(dmg), "falling object", KILLED_BY_AN); + gt.thrownobj = 0; + if (!harmless) + losehp(dmg, "falling object", KILLED_BY_AN); } return TRUE; } /* return true for weapon meant to be thrown; excludes ammo */ boolean -throwing_weapon(obj) -struct obj *obj; +throwing_weapon(struct obj *obj) { return (boolean) (is_missile(obj) || is_spear(obj) /* daggers and knife (excludes scalpel) */ @@ -1071,18 +1438,17 @@ struct obj *obj; } /* the currently thrown object is returning to you (not for boomerangs) */ -STATIC_OVL void -sho_obj_return_to_u(obj) -struct obj *obj; +staticfn void +sho_obj_return_to_u(struct obj *obj) { /* might already be our location (bounced off a wall) */ - if ((u.dx || u.dy) && (bhitpos.x != u.ux || bhitpos.y != u.uy)) { - int x = bhitpos.x - u.dx, y = bhitpos.y - u.dy; + if ((u.dx || u.dy) && (gb.bhitpos.x != u.ux || gb.bhitpos.y != u.uy)) { + int x = gb.bhitpos.x - u.dx, y = gb.bhitpos.y - u.dy; tmp_at(DISP_FLASH, obj_to_glyph(obj, rn2_on_display_rng)); while (isok(x,y) && (x != u.ux || y != u.uy)) { tmp_at(x, y); - delay_output(); + nh_delay_output(); x -= u.dx; y -= u.dy; } @@ -1090,21 +1456,73 @@ struct obj *obj; } } +staticfn void +throwit_return(boolean clear_thrownobj) +{ + iflags.returning_missile = (genericptr_t) 0; + if (clear_thrownobj) + gt.thrownobj = (struct obj *) 0; +} + +staticfn void +swallowit(struct obj *obj) +{ + if (obj != uball) { + (void) mpickobj(u.ustuck, obj); /* clears 'gt.thrownobj' */ + throwit_return(FALSE); + } else + throwit_return(TRUE); +} + +/* thrown object hits a monster. + mon may be NULL. + returns TRUE if shopkeeper caught the object. + may delete object, clearing gt.thrownobj */ +boolean +throwit_mon_hit(struct obj *obj, struct monst *mon) +{ + if (mon) { + boolean obj_gone; + + if (mon->isshk && obj->where == OBJ_MINVENT && obj->ocarry == mon) { + return TRUE; + } + (void) snuff_candle(obj); + gn.notonhead = (gb.bhitpos.x != mon->mx || gb.bhitpos.y != mon->my); + obj_gone = thitmonst(mon, obj); + /* Monster may have been tamed; this frees old mon [obsolete] */ + mon = m_at(gb.bhitpos.x, gb.bhitpos.y); + + /* [perhaps this should be moved into thitmonst or hmon] */ + if (mon && mon->isshk + && (!inside_shop(u.ux, u.uy) + || !strchr(in_rooms(mon->mx, mon->my, SHOPBASE), *u.ushops))) + hot_pursuit(mon); + + if (obj_gone) + gt.thrownobj = (struct obj *) 0; + } + return FALSE; +} + /* throw an object, NB: obj may be consumed in the process */ void -throwit(obj, wep_mask, twoweap) -struct obj *obj; -long wep_mask; /* used to re-equip returning boomerang */ -boolean twoweap; /* used to restore twoweapon mode if wielded weapon returns */ +throwit( + struct obj *obj, + long wep_mask, /* used to re-equip returning boomerang */ + boolean twoweap, /* used to restore twoweapon mode if + * wielded weapon returns */ + struct obj *oldslot) /* for thrown-and-return used with !fixinv */ { - register struct monst *mon; + struct monst *mon; int range, urange; - boolean crossbowing, clear_thrownobj = FALSE, + const struct throw_and_return_weapon *arw = autoreturn_weapon(obj); + boolean crossbowing, impaired = (Confusion || Stunned || Blind || Hallucination || Fumbling), - tethered_weapon = (obj->otyp == AKLYS && (wep_mask & W_WEP) != 0); + tethered_weapon = (arw && arw->tethered && (wep_mask & W_WEP) != 0); - notonhead = FALSE; /* reset potentially stale value */ + gn.notonhead = FALSE; /* reset potentially stale value */ if ((obj->cursed || obj->greased) && (u.dx || u.dy) && !rn2(7)) { boolean slipok = TRUE; @@ -1141,12 +1559,10 @@ boolean twoweap; /* used to restore twoweapon mode if wielded weapon returns */ u.dz = 1; } - thrownobj = obj; - thrownobj->was_thrown = 1; - iflags.returning_missile = ((obj->oartifact == ART_MJOLLNIR - && Role_if(PM_VALKYRIE)) - || tethered_weapon) ? (genericptr_t) obj - : (genericptr_t) 0; + gt.thrownobj = obj; + gt.thrownobj->how_lost = LOST_THROWN; + iflags.returning_missile = AutoReturn(obj, wep_mask) ? (genericptr_t) obj + : (genericptr_t) 0; /* NOTE: No early returns after this point or returning_missile will be left with a stale pointer. */ @@ -1156,8 +1572,8 @@ boolean twoweap; /* used to restore twoweapon mode if wielded weapon returns */ uball->oy = uchain->oy = u.uy; } mon = u.ustuck; - bhitpos.x = mon->mx; - bhitpos.y = mon->my; + gb.bhitpos.x = mon->mx; + gb.bhitpos.y = mon->my; if (tethered_weapon) tmp_at(DISP_TETHER, obj_to_glyph(obj, rn2_on_display_rng)); } else if (u.dz) { @@ -1168,12 +1584,7 @@ boolean twoweap; /* used to restore twoweapon mode if wielded weapon returns */ && !impaired) { pline("%s the %s and returns to your hand!", Tobjnam(obj, "hit"), ceiling(u.ux, u.uy)); - obj = addinv(obj); - (void) encumber_msg(); - if (obj->owornmask & W_QUIVER) /* in case addinv() autoquivered */ - setuqwep((struct obj *) 0); - setuwep(obj); - u.twoweap = twoweap; + obj = return_throw_to_inv(obj, wep_mask, twoweap, oldslot); } else if (u.dz < 0) { (void) toss_up(obj, rn2(5) && !Underwater); } else if (u.dz > 0 && u.usteed && obj->oclass == POTION_CLASS @@ -1184,23 +1595,19 @@ boolean twoweap; /* used to restore twoweapon mode if wielded weapon returns */ } else { hitfloor(obj, TRUE); } - clear_thrownobj = TRUE; - goto throwit_return; + throwit_return(TRUE); + return; } else if (obj->otyp == BOOMERANG && !Underwater) { if (Is_airlevel(&u.uz) || Levitation) hurtle(-u.dx, -u.dy, 1, TRUE); mon = boomhit(obj, u.dx, u.dy); - if (mon == &youmonst) { /* the thing was caught */ + iflags.returning_missile = 0; /* has returned or isn't going to */ + if (mon == &gy.youmonst) { /* the thing was caught */ exercise(A_DEX, TRUE); - obj = addinv(obj); - (void) encumber_msg(); - if (wep_mask && !(obj->owornmask & wep_mask)) { - setworn(obj, wep_mask); - u.twoweap = twoweap; - } - clear_thrownobj = TRUE; - goto throwit_return; + obj = return_throw_to_inv(obj, wep_mask, twoweap, oldslot); + throwit_return(TRUE); + return; } } else { /* crossbow range is independent of strength */ @@ -1231,8 +1638,13 @@ boolean twoweap; /* used to restore twoweapon mode if wielded weapon returns */ range = BOLT_LIM; else range++; - } else if (obj->oclass != GEM_CLASS) + } else if (obj->oclass != GEM_CLASS) { range /= 2; + pline("You aren't wielding %s, so you throw your %s by %s.", + an(skill_name(weapon_type(obj))), + weapon_descr(obj), + body_part(HAND)); + } } if (Is_airlevel(&u.uz) || Levitation) { @@ -1247,12 +1659,12 @@ boolean twoweap; /* used to restore twoweapon mode if wielded weapon returns */ if (obj->otyp == BOULDER) range = 20; /* you must be giant */ - else if (obj->oartifact == ART_MJOLLNIR) + else if (is_art(obj, ART_MJOLLNIR)) range = (range + 1) / 2; /* it's heavy */ else if (tethered_weapon) /* primary weapon is aklys */ - /* if an aklys is going to return, range is limited by the + /* range of a tethered_weapon is limited by the length of the attached cord [implicit aspect of item] */ - range = min(range, BOLT_LIM / 2); + range = min(range, isqrt(arw->range)); else if (obj == uball && u.utrap && u.utraptype == TT_INFLOOR) range = 1; @@ -1261,9 +1673,9 @@ boolean twoweap; /* used to restore twoweapon mode if wielded weapon returns */ mon = bhit(u.dx, u.dy, range, tethered_weapon ? THROWN_TETHERED_WEAPON : THROWN_WEAPON, - (int FDECL((*), (MONST_P, OBJ_P))) 0, - (int FDECL((*), (OBJ_P, OBJ_P))) 0, &obj); - thrownobj = obj; /* obj may be null now */ + (int (*)(MONST_P, OBJ_P)) 0, + (int (*)(OBJ_P, OBJ_P)) 0, &obj); + gt.thrownobj = obj; /* obj may be null now */ /* have to do this after bhit() so u.ux & u.uy are correct */ if (Is_airlevel(&u.uz) || Levitation) @@ -1275,44 +1687,23 @@ boolean twoweap; /* used to restore twoweapon mode if wielded weapon returns */ we're about to return */ if (tethered_weapon) tmp_at(DISP_END, 0); - goto throwit_return; + throwit_return(FALSE); + return; } } - if (mon) { - boolean obj_gone; - - if (mon->isshk && obj->where == OBJ_MINVENT && obj->ocarry == mon) { - clear_thrownobj = TRUE; - goto throwit_return; /* alert shk caught it */ - } - (void) snuff_candle(obj); - notonhead = (bhitpos.x != mon->mx || bhitpos.y != mon->my); - obj_gone = thitmonst(mon, obj); - /* Monster may have been tamed; this frees old mon [obsolete] */ - mon = m_at(bhitpos.x, bhitpos.y); - - /* [perhaps this should be moved into thitmonst or hmon] */ - if (mon && mon->isshk - && (!inside_shop(u.ux, u.uy) - || !index(in_rooms(mon->mx, mon->my, SHOPBASE), *u.ushops))) - hot_pursuit(mon); - - if (obj_gone) - thrownobj = (struct obj *) 0; + if (throwit_mon_hit(obj, mon)) { + throwit_return(TRUE); /* alert shk caught it */ + return; } - if (!thrownobj) { + if (!gt.thrownobj) { /* missile has already been handled */ if (tethered_weapon) tmp_at(DISP_END, 0); } else if (u.uswallow && !iflags.returning_missile) { - swallowit: - if (obj != uball) - (void) mpickobj(u.ustuck, obj); /* clears 'thrownobj' */ - else - clear_thrownobj = TRUE; - goto throwit_return; + swallowit(obj); + return; } else { /* Mjollnir must be wielded to be thrown--caller verifies this; aklys must be wielded as primary to return when thrown */ @@ -1325,16 +1716,16 @@ boolean twoweap; /* used to restore twoweapon mode if wielded weapon returns */ if (!impaired && rn2(100)) { pline("%s to your hand!", Tobjnam(obj, "return")); - obj = addinv(obj); - (void) encumber_msg(); + obj = addinv_before(obj, oldslot); + encumber_msg(); /* addinv autoquivers an aklys if quiver is empty; if obj is quivered, remove it before wielding */ if (obj->owornmask & W_QUIVER) setuqwep((struct obj *) 0); setuwep(obj); - u.twoweap = twoweap; - if (cansee(bhitpos.x, bhitpos.y)) - newsym(bhitpos.x, bhitpos.y); + set_twoweap(twoweap); /* u.twoweap = twoweap */ + if (cansee(gb.bhitpos.x, gb.bhitpos.y)) + newsym(gb.bhitpos.x, gb.bhitpos.y); } else { int dmg = rn2(2); @@ -1351,24 +1742,26 @@ boolean twoweap; /* used to restore twoweapon mode if wielded weapon returns */ Tobjnam(obj, Blind ? "hit" : "fly"), body_part(ARM)); if (obj->oartifact) - (void) artifact_hit((struct monst *) 0, &youmonst, - obj, &dmg, 0); + (void) artifact_hit((struct monst *) 0, + &gy.youmonst, obj, &dmg, 0); losehp(Maybe_Half_Phys(dmg), killer_xname(obj), KILLED_BY); } - if (u.uswallow) - goto swallowit; + if (u.uswallow) { + swallowit(obj); + return; + } if (!ship_object(obj, u.ux, u.uy, FALSE)) dropy(obj); } - clear_thrownobj = TRUE; - goto throwit_return; + throwit_return(TRUE); + return; } else { if (tethered_weapon) tmp_at(DISP_END, 0); /* when this location is stepped on, the weapon will be - auto-picked up due to 'obj->was_thrown' of 1; + auto-picked up due to 'obj->how_lost' of LOST_THROWN; addinv() prevents thrown Mjollnir from being placed into the quiver slot, but an aklys will end up there if that slot is empty at the time; since hero will need to @@ -1376,79 +1769,148 @@ boolean twoweap; /* used to restore twoweapon mode if wielded weapon returns */ capability back anyway, quivered or not shouldn't matter */ pline("%s to return!", Tobjnam(obj, "fail")); - if (u.uswallow) - goto swallowit; + if (u.uswallow) { + swallowit(obj); + return; + } /* continue below with placing 'obj' at target location */ } } - if ((!IS_SOFT(levl[bhitpos.x][bhitpos.y].typ) && breaktest(obj)) + if ((!IS_SOFT(levl[gb.bhitpos.x][gb.bhitpos.y].typ) && breaktest(obj)) /* venom [via #monster to spit while poly'd] fails breaktest() but we want to force breakage even when location IS_SOFT() */ || obj->oclass == VENOM_CLASS) { tmp_at(DISP_FLASH, obj_to_glyph(obj, rn2_on_display_rng)); - tmp_at(bhitpos.x, bhitpos.y); - delay_output(); + tmp_at(gb.bhitpos.x, gb.bhitpos.y); + nh_delay_output(); tmp_at(DISP_END, 0); - breakmsg(obj, cansee(bhitpos.x, bhitpos.y)); - breakobj(obj, bhitpos.x, bhitpos.y, TRUE, TRUE); - clear_thrownobj = TRUE; - goto throwit_return; + breakmsg(obj, cansee(gb.bhitpos.x, gb.bhitpos.y)); + if (breakobj(obj, gb.bhitpos.x, gb.bhitpos.y, TRUE, TRUE)) { + throwit_return(TRUE); + return; + } + } + if (!Deaf && !Underwater) { + /* Some sound effects when item lands in water or lava */ + if (is_pool(gb.bhitpos.x, gb.bhitpos.y) + || (is_lava(gb.bhitpos.x, gb.bhitpos.y) + && !is_flammable(obj))) { + Soundeffect(se_splash, 50); + pline((weight(obj) > WT_SPLASH_THRESHOLD) + ? "Splash!" : "Plop!"); + } } - if (flooreffects(obj, bhitpos.x, bhitpos.y, "fall")) { - clear_thrownobj = TRUE; - goto throwit_return; + if (flooreffects(obj, gb.bhitpos.x, gb.bhitpos.y, "fall")) { + throwit_return(TRUE); + return; } obj_no_longer_held(obj); if (mon && mon->isshk && is_pick(obj)) { - if (cansee(bhitpos.x, bhitpos.y)) + if (cansee(gb.bhitpos.x, gb.bhitpos.y)) pline("%s snatches up %s.", Monnam(mon), the(xname(obj))); if (*u.ushops || obj->unpaid) - check_shop_obj(obj, bhitpos.x, bhitpos.y, FALSE); + check_shop_obj(obj, gb.bhitpos.x, gb.bhitpos.y, FALSE); (void) mpickobj(mon, obj); /* may merge and free obj */ - clear_thrownobj = TRUE; - goto throwit_return; + throwit_return(TRUE); + return; } (void) snuff_candle(obj); - if (!mon && ship_object(obj, bhitpos.x, bhitpos.y, FALSE)) { - clear_thrownobj = TRUE; - goto throwit_return; + if (!mon && ship_object(obj, gb.bhitpos.x, gb.bhitpos.y, FALSE)) { + throwit_return(TRUE); + return; } - thrownobj = (struct obj *) 0; - place_object(obj, bhitpos.x, bhitpos.y); + gt.thrownobj = (struct obj *) 0; + place_object(obj, gb.bhitpos.x, gb.bhitpos.y); /* container contents might break; - do so before turning ownership of thrownobj over to shk + do so before turning ownership of gt.thrownobj over to shk (container_impact_dmg handles item already owned by shop) */ - if (!IS_SOFT(levl[bhitpos.x][bhitpos.y].typ)) - /* is spot where you initiated throw, not bhitpos */ + if (!IS_SOFT(levl[gb.bhitpos.x][gb.bhitpos.y].typ)) { + /* is spot where you initiated throw, not gb.bhitpos */ container_impact_dmg(obj, u.ux, u.uy); + impact_disturbs_zombies(obj, TRUE); + } /* charge for items thrown out of shop; shk takes possession for items thrown into one */ if ((*u.ushops || obj->unpaid) && obj != uball) - check_shop_obj(obj, bhitpos.x, bhitpos.y, FALSE); + check_shop_obj(obj, gb.bhitpos.x, gb.bhitpos.y, FALSE); stackobj(obj); if (obj == uball) - drop_ball(bhitpos.x, bhitpos.y); - if (cansee(bhitpos.x, bhitpos.y)) - newsym(bhitpos.x, bhitpos.y); + drop_ball(gb.bhitpos.x, gb.bhitpos.y); + if (cansee(gb.bhitpos.x, gb.bhitpos.y)) + newsym(gb.bhitpos.x, gb.bhitpos.y); if (obj_sheds_light(obj)) - vision_full_recalc = 1; + gv.vision_full_recalc = 1; } - throwit_return: - iflags.returning_missile = (genericptr_t) 0; - if (clear_thrownobj) - thrownobj = (struct obj *) 0; + throwit_return(FALSE); return; } +/* handle a throw-and-return missile coming back into inventory; makes sure + that if it was wielded, it will be re-wielded; if it was split off of a + stack (boomerang), don't let it merge with a different compatible stack */ +staticfn struct obj * +return_throw_to_inv( + struct obj *obj, /* object to add to invent */ + long wep_mask, /* its owornmask before it was removed from invent */ + boolean twoweap, /* True if hero was dual-wielding before removal */ + struct obj *oldslot) /* following item in invent in case of '!fixinv' */ +{ + struct obj *otmp = NULL; + + /* if 'obj' is from a stack split, we can put it back by undoing split + so there's no chance of merging with some other compatible stack */ + if (obj->o_id == svc.context.objsplit.parent_oid + || obj->o_id == svc.context.objsplit.child_oid) { + obj->nobj = gi.invent; + gi.invent = obj; + obj->where = OBJ_INVENT; + otmp = unsplitobj(obj); + if (!otmp) { + gi.invent = obj->nobj; + obj->nobj = 0; + obj->where = OBJ_FREE; + } else { + obj = otmp; + } + } + + /* if 'obj' wasn't from a stack split or if it wouldn't merge back + (maybe new erosion damage?) then it needs to be added to invent; + don't merge with any other stack even if there is a compatible one + (others with similar erosion?); can't use addinv_nomerge() here */ + if (!otmp) { + obj->nomerge = 1; /* redundant unless 'oldslot' somehow went away */ + obj = addinv_before(obj, oldslot); + obj->nomerge = 0; + + /* in case addinv() autoquivered */ + if ((obj->owornmask & W_QUIVER) != 0 + && ((obj->owornmask | wep_mask) & (W_WEP | W_SWAPWEP)) != 0) + setuqwep((struct obj *) 0); + + if ((wep_mask & W_WEP) && !uwep) + setuwep(obj); + else if ((wep_mask & W_SWAPWEP) && !uswapwep) + setuswapwep(obj); + else if ((wep_mask & W_QUIVER) && !uquiver) + setuqwep(obj); + + /* in case the throw ended dual-wielding, reinstate it after + successful catch (not needed for boomerang split/unsplit) */ + if (twoweap && !u.twoweap) + set_twoweap(TRUE); /* u.twoweap = TRUE */ + } + + encumber_msg(); + return obj; +} + /* an object may hit a monster; various factors adjust chance of hitting */ int -omon_adj(mon, obj, mon_notices) -struct monst *mon; -struct obj *obj; -boolean mon_notices; +omon_adj(struct monst *mon, struct obj *obj, boolean mon_notices) { int tmp = 0; @@ -1457,8 +1919,6 @@ boolean mon_notices; /* sleeping target is more likely to be hit */ if (mon->msleeping) { tmp += 2; - if (mon_notices) - mon->msleeping = 0; } /* ditto for immobilized target */ if (!mon->mcanmove || !mon->data->mmove) { @@ -1487,11 +1947,8 @@ boolean mon_notices; } /* thrown object misses target monster */ -STATIC_OVL void -tmiss(obj, mon, maybe_wakeup) -struct obj *obj; -struct monst *mon; -boolean maybe_wakeup; +staticfn void +tmiss(struct obj *obj, struct monst *mon, boolean maybe_wakeup) { const char *missile = mshot_xname(obj); @@ -1509,9 +1966,40 @@ boolean maybe_wakeup; return; } -#define quest_arti_hits_leader(obj, mon) \ - (obj->oartifact && is_quest_artifact(obj) \ - && mon->m_id == quest_status.leader_m_id) +#define special_obj_hits_leader(obj, mon) \ + ((is_quest_artifact(obj) || objects[obj->otyp].oc_unique \ + || (obj->otyp == FAKE_AMULET_OF_YENDOR && !obj->known)) \ + && mon->m_id == svq.quest_status.leader_m_id) + +/* whether or not object should be destroyed when it hits its target */ +boolean +should_mulch_missile(struct obj *obj) +{ + boolean broken; + int chance; + + /* only ammo (excluding magic stones) or missiles will break */ + if (!obj || !(is_ammo(obj) || is_missile(obj)) + || obj->otyp == BOOMERANG + || objects[obj->otyp].oc_magic) + return FALSE; + + /* we had been breaking 2/3 of everything unconditionally. we still don't + want anything to survive unconditionally, but we need ammo to stay + around longer on average. */ + chance = 3 + greatest_erosion(obj) - obj->spe; + broken = chance > 1 ? rn2(chance) : !rn2(4); + if (obj->blessed && (svc.context.mon_moving ? !rn2(3) : !rnl(4))) + broken = FALSE; + + /* Flint and hard gems don't break easily */ + if (((obj->oclass == GEM_CLASS && objects[obj->otyp].oc_tough) + || obj->otyp == FLINT) + && !rn2(2)) + broken = FALSE; + + return broken; +} /* * Object thrown by player arrives at monster's location. @@ -1520,18 +2008,18 @@ boolean maybe_wakeup; * Also used for kicked objects and for polearms/grapnel applied at range. */ int -thitmonst(mon, obj) -register struct monst *mon; -register struct obj *obj; /* thrownobj or kickedobj or uwep */ +thitmonst( + struct monst *mon, + struct obj *obj) /* gt.thrownobj or gk.kickedobj or uwep */ { - register int tmp; /* Base chance to hit */ - register int disttmp; /* distance modifier */ + int tmp; /* Base chance to hit */ + int disttmp; /* distance modifier */ int otyp = obj->otyp, hmode; - boolean guaranteed_hit = (u.uswallow && mon == u.ustuck); + boolean guaranteed_hit = engulfing_u(mon); int dieroll; hmode = (obj == uwep) ? HMON_APPLIED - : (obj == kickedobj) ? HMON_KICKED + : (obj == gk.kickedobj) ? HMON_KICKED : HMON_THROWN; /* Differences from melee weapons: @@ -1546,7 +2034,7 @@ register struct obj *obj; /* thrownobj or kickedobj or uwep */ * Distance and monster size affect chance to hit. */ tmp = -1 + Luck + find_mac(mon) + u.uhitinc - + maybe_polyd(youmonst.data->mlevel, u.ulevel); + + maybe_polyd(gy.youmonst.data->mlevel, u.ulevel); if (ACURR(A_DEX) < 4) tmp -= 3; else if (ACURR(A_DEX) < 6) @@ -1585,14 +2073,20 @@ register struct obj *obj; /* thrownobj or kickedobj or uwep */ tmp += omon_adj(mon, obj, TRUE); if (is_orc(mon->data) - && maybe_polyd(is_elf(youmonst.data), Race_if(PM_ELF))) + && maybe_polyd(is_elf(gy.youmonst.data), Race_if(PM_ELF))) tmp++; if (guaranteed_hit) { tmp += 1000; /* Guaranteed hit */ } - if (obj->oclass == GEM_CLASS && is_unicorn(mon->data)) { - if (mon->msleeping || !mon->mcanmove) { + /* throwing real gems to co-aligned unicorns boosts Luck, + to cross-aligned unicorns changes Luck by random amount; + throwing worthless glass doesn't affect Luck but doesn't anger them; + 5.0: treat rocks and gray stones as attacks rather than like glass + and also treat gems or glass shot via sling as attacks */ + if (obj->oclass == GEM_CLASS && is_unicorn(mon->data) + && objects[obj->otyp].oc_material != MINERAL && !uslinging()) { + if (helpless(mon)) { tmiss(obj, mon, FALSE); return 0; } else if (mon->mtame) { @@ -1607,29 +2101,48 @@ register struct obj *obj; /* thrownobj or kickedobj or uwep */ /* don't make game unwinnable if naive player throws artifact at leader... (kicked artifact is ok too; HMON_APPLIED could occur if quest artifact polearm or grapnel ever gets added) */ - if (hmode != HMON_APPLIED && quest_arti_hits_leader(obj, mon)) { - /* AIS: changes to wakeup() means that it's now less inappropriate here - than it used to be, but the manual version works just as well */ + if (hmode != HMON_APPLIED && special_obj_hits_leader(obj, mon)) { + /* AIS: changes to wakeup() means that it's now less inappropriate + here than it used to be, but manual version works just as well */ mon->msleeping = 0; mon->mstrategy &= ~STRAT_WAITMASK; if (mon->mcanmove) { - pline("%s catches %s.", Monnam(mon), the(xname(obj))); - if (mon->mpeaceful) { + pline("%s catches %s.", Some_Monnam(mon), the(xname(obj))); + /* leader will keep tossed invocation item after you've done the + invocation and it's become unnecessary for completion.. */ + if ((u.uevent.invoked && objects[obj->otyp].oc_unique + && obj->otyp != AMULET_OF_YENDOR) + /* ...or any special item, if you've made him angry */ + || !mon->mpeaceful) { + /* give an explanation for keeping the item only if leader is + not doing it out of anger */ + if (mon->mpeaceful && !Deaf) { + /* just in case, identify the object so its name will + appear in the message */ + fully_identify_obj(obj); + verbalize("%s part in this is finished.", + s_suffix(The(xname(obj)))); + verbalize( + "We will guard it in case it is ever needed again, %s forbid.", + align_gname(u.ualignbase[A_ORIGINAL])); + } + if (*u.ushops || obj->unpaid) /* not very likely... */ + check_shop_obj(obj, mon->mx, mon->my, FALSE); + (void) mpickobj(mon, obj); + } else { + /* under normal circumstances, leader will say something and + then return the item to the hero */ boolean next2u = monnear(mon, u.ux, u.uy); finish_quest(obj); /* acknowledge quest completion */ - pline("%s %s %s back to you.", Monnam(mon), + pline("%s %s %s back to you.", Some_Monnam(mon), (next2u ? "hands" : "tosses"), the(xname(obj))); if (!next2u) sho_obj_return_to_u(obj); obj = addinv(obj); /* back into your inventory */ - (void) encumber_msg(); - } else { - /* angry leader caught it and isn't returning it */ - if (*u.ushops || obj->unpaid) /* not very likely... */ - check_shop_obj(obj, mon->mx, mon->my, FALSE); - (void) mpickobj(mon, obj); + nhUse(obj); + encumber_msg(); } return 1; /* caller doesn't need to place it */ } @@ -1657,13 +2170,12 @@ register struct obj *obj; /* thrownobj or kickedobj or uwep */ * Polymorphing won't make you a bow expert. */ if ((Race_if(PM_ELF) || Role_if(PM_SAMURAI)) - && (!Upolyd || your_race(youmonst.data)) + && (!Upolyd || your_race(gy.youmonst.data)) && objects[uwep->otyp].oc_skill == P_BOW) { - tmp++; - if (Race_if(PM_ELF) && uwep->otyp == ELVEN_BOW) - tmp++; - else if (Role_if(PM_SAMURAI) && uwep->otyp == YUMI) - tmp++; + ++tmp; + if ((Race_if(PM_ELF) && uwep->otyp == ELVEN_BOW) + || (Role_if(PM_SAMURAI) && uwep->otyp == YUMI)) + ++tmp; } } } else { /* thrown non-ammo or applied polearm/grapnel */ @@ -1671,7 +2183,7 @@ register struct obj *obj; /* thrownobj or kickedobj or uwep */ tmp += 4; else if (throwing_weapon(obj)) /* meant to be thrown */ tmp += 2; - else if (obj == thrownobj) /* not meant to be thrown */ + else if (obj == gt.thrownobj) /* not meant to be thrown */ tmp -= 2; /* we know we're dealing with a weapon or weptool handled by WEAPON_SKILLS once ammo objects have been excluded */ @@ -1679,51 +2191,37 @@ register struct obj *obj; /* thrownobj or kickedobj or uwep */ } if (tmp >= dieroll) { - boolean wasthrown = (thrownobj != 0), + boolean wasthrown = (gt.thrownobj != 0), /* remember weapon attribute; hmon() might destroy obj */ chopper = is_axe(obj); /* attack hits mon */ - if (hmode == HMON_APPLIED) + if (hmode == HMON_APPLIED) { /* ranged hit with wielded polearm */ + /* hmon()'s caller is expected to do this; however, hmon() + delivers the "hit with wielded weapon for first time" + gamelog message when applicable */ u.uconduct.weaphit++; + } if (hmon(mon, obj, hmode, dieroll)) { /* mon still alive */ if (mon->wormno) - cutworm(mon, bhitpos.x, bhitpos.y, chopper); + cutworm(mon, gb.bhitpos.x, gb.bhitpos.y, chopper); } exercise(A_DEX, TRUE); /* if hero was swallowed and projectile killed the engulfer, 'obj' got added to engulfer's inventory and then dropped, so we can't safely use that pointer anymore; it escapes the chance to be used up here... */ - if (wasthrown && !thrownobj) + if (wasthrown && !gt.thrownobj) return 1; /* projectiles other than magic stones sometimes disappear when thrown; projectiles aren't among the types of weapon that hmon() might have destroyed so obj is intact */ - if (objects[otyp].oc_skill < P_NONE - && objects[otyp].oc_skill > -P_BOOMERANG - && !objects[otyp].oc_magic) { - /* we were breaking 2/3 of everything unconditionally. - * we still don't want anything to survive unconditionally, - * but we need ammo to stay around longer on average. - */ - int broken, chance; - - chance = 3 + greatest_erosion(obj) - obj->spe; - if (chance > 1) - broken = rn2(chance); - else - broken = !rn2(4); - if (obj->blessed && !rnl(4)) - broken = 0; - - if (broken) { - if (*u.ushops || obj->unpaid) - check_shop_obj(obj, bhitpos.x, bhitpos.y, TRUE); - obfree(obj, (struct obj *) 0); - return 1; - } + if (should_mulch_missile(obj)) { + if (*u.ushops || obj->unpaid) + check_shop_obj(obj, gb.bhitpos.x, gb.bhitpos.y, TRUE); + obfree(obj, (struct obj *) 0); + return 1; } passive_obj(mon, obj, (struct attack *) 0); } else { @@ -1768,7 +2266,7 @@ register struct obj *obj; /* thrownobj or kickedobj or uwep */ } else if (befriend_with_obj(mon->data, obj) || (mon->mtame && dogfood(mon, obj) <= ACCFOOD)) { - if (tamedog(mon, obj)) { + if (tamedog(mon, obj, TRUE)) { return 1; /* obj is gone */ } else { tmiss(obj, mon, FALSE); @@ -1776,10 +2274,14 @@ register struct obj *obj; /* thrownobj or kickedobj or uwep */ mon->mstrategy &= ~STRAT_WAITMASK; } } else if (guaranteed_hit) { + char trail[BUFSZ]; + char *monname; + struct permonst *md = u.ustuck->data; + /* this assumes that guaranteed_hit is due to swallowing */ wakeup(mon, TRUE); if (obj->otyp == CORPSE && touch_petrifies(&mons[obj->corpsenm])) { - if (is_animal(u.ustuck->data)) { + if (is_animal(md)) { minstapetrify(u.ustuck, TRUE); /* Don't leave a cockatrice corpse available in a statue */ if (!u.uswallow) { @@ -1788,9 +2290,12 @@ register struct obj *obj; /* thrownobj or kickedobj or uwep */ } } } - pline("%s into %s %s.", Tobjnam(obj, "vanish"), - s_suffix(mon_nam(mon)), - is_animal(u.ustuck->data) ? "entrails" : "currents"); + Strcpy(trail, + digests(md) ? " entrails" : is_whirly(md) ? " currents" : ""); + monname = mon_nam(mon); + if (*trail) + monname = s_suffix(monname); + pline("%s into %s%s.", Tobjnam(obj, "vanish"), monname, trail); } else { tmiss(obj, mon, TRUE); } @@ -1798,20 +2303,21 @@ register struct obj *obj; /* thrownobj or kickedobj or uwep */ return 0; } -STATIC_OVL int -gem_accept(mon, obj) -register struct monst *mon; -register struct obj *obj; +#undef special_obj_hits_leader + +staticfn int +gem_accept(struct monst *mon, struct obj *obj) { + static NEARDATA const char + nogood[] = " is not interested in your junk.", + acceptgift[] = " accepts your gift.", + maybeluck[] = " hesitatingly", + noluck[] = " graciously", + addluck[] = " gratefully"; char buf[BUFSZ]; boolean is_buddy = sgn(mon->data->maligntyp) == sgn(u.ualign.type); boolean is_gem = objects[obj->otyp].oc_material == GEMSTONE; int ret = 0; - static NEARDATA const char nogood[] = " is not interested in your junk."; - static NEARDATA const char acceptgift[] = " accepts your gift."; - static NEARDATA const char maybeluck[] = " hesitatingly"; - static NEARDATA const char noluck[] = " graciously"; - static NEARDATA const char addluck[] = " gratefully"; Strcpy(buf, Monnam(mon)); mon->mpeaceful = 1; @@ -1831,7 +2337,8 @@ register struct obj *obj; Strcat(buf, nogood); goto nopick; } - /* making guesses */ + + /* making guesses */ } else if (has_oname(obj) || objects[obj->otyp].oc_uname) { if (is_gem) { if (is_buddy) { @@ -1845,7 +2352,8 @@ register struct obj *obj; Strcat(buf, nogood); goto nopick; } - /* value completely unknown to @ */ + + /* value completely unknown to @ */ } else { if (is_gem) { if (is_buddy) { @@ -1869,7 +2377,7 @@ register struct obj *obj; if (!Blind) pline1(buf); if (!tele_restrict(mon)) - (void) rloc(mon, TRUE); + (void) rloc(mon, RLOC_MSG); return ret; } @@ -1906,18 +2414,25 @@ register struct obj *obj; * Return 0 if the object didn't break, 1 if the object broke. */ int -hero_breaks(obj, x, y, from_invent) -struct obj *obj; -xchar x, y; /* object location (ox, oy may not be right) */ -boolean from_invent; /* thrown or dropped by player; maybe on shop bill */ +hero_breaks( + struct obj *obj, + coordxy x, coordxy y, /* object location (ox, oy may not be right) */ + unsigned breakflags) { - boolean in_view = Blind ? FALSE : (from_invent || cansee(x, y)); - - if (!breaktest(obj)) + /* from_invent: thrown or dropped by player; maybe on shop bill; + by-hero is implicit so callers don't need to specify BRK_BY_HERO */ + boolean from_invent = (breakflags & BRK_FROM_INV) != 0, + in_view = Blind ? FALSE : (from_invent || cansee(x, y)); + unsigned brk = (breakflags & BRK_KNOWN_OUTCOME); + + /* only call breaktest if caller hasn't already specified the outcome */ + if (!brk) + brk = breaktest(obj) ? BRK_KNOWN2BREAK : BRK_KNOWN2NOTBREAK; + if (brk == BRK_KNOWN2NOTBREAK) return 0; + breakmsg(obj, in_view); - breakobj(obj, x, y, TRUE, from_invent); - return 1; + return breakobj(obj, x, y, TRUE, from_invent); } /* @@ -1926,28 +2441,25 @@ boolean from_invent; /* thrown or dropped by player; maybe on shop bill */ * Return 0 if the object doesn't break, 1 if the object broke. */ int -breaks(obj, x, y) -struct obj *obj; -xchar x, y; /* object location (ox, oy may not be right) */ +breaks( + struct obj *obj, + coordxy x, coordxy y) /* object location (ox, oy may not be right) */ { boolean in_view = Blind ? FALSE : cansee(x, y); if (!breaktest(obj)) return 0; breakmsg(obj, in_view); - breakobj(obj, x, y, FALSE, FALSE); - return 1; + return breakobj(obj, x, y, FALSE, FALSE); } void -release_camera_demon(obj, x, y) -struct obj *obj; -xchar x, y; +release_camera_demon(struct obj *obj, coordxy x, coordxy y) { struct monst *mtmp; if (!rn2(3) && (mtmp = makemon(&mons[rn2(3) ? PM_HOMUNCULUS : PM_IMP], x, y, - NO_MM_FLAGS)) != 0) { + MM_NOMSG)) != 0) { if (canspotmon(mtmp)) pline("%s is released!", Hallucination ? An(rndmonnam(NULL)) @@ -1958,17 +2470,25 @@ xchar x, y; } /* - * Unconditionally break an object. Assumes all resistance checks + * Break an object. Breakable armor goes through erosion steps; other + * items break unconditionally. Assumes all resistance checks * and break messages have been delivered prior to getting here. + * (No longer true; breakmsg() is silent for crackable armor and we + * call erode_obj() for it and that delivers a damaged-the-item message.) */ -void -breakobj(obj, x, y, hero_caused, from_invent) -struct obj *obj; -xchar x, y; /* object location (ox, oy may not be right) */ -boolean hero_caused; /* is this the hero's fault? */ -boolean from_invent; +int +breakobj( + struct obj *obj, + coordxy x, coordxy y, /* object location (ox, oy may not be right) */ + boolean hero_caused, /* is this the hero's fault? */ + boolean from_invent) { boolean fracture = FALSE; + boolean explosion = FALSE; + + if (is_crackable(obj)) /* if erodeproof, erode_obj() will say so */ + return (erode_obj(obj, armor_simple_name(obj), ERODE_CRACK, + EF_DESTROY | EF_VERBOSE) == ER_DESTROYED); switch (obj->oclass == POTION_CLASS ? POT_WATER : obj->otyp) { case MIRROR: @@ -1979,16 +2499,17 @@ boolean from_invent; obj->in_use = 1; /* in case it's fatal */ if (obj->otyp == POT_OIL && obj->lamplit) { explode_oil(obj, x, y); - } else if (distu(x, y) <= 2) { - if (!breathless(youmonst.data) || haseyes(youmonst.data)) { - if (obj->otyp != POT_WATER) { - if (!breathless(youmonst.data)) { + } else if (next2u(x, y)) { + if (!breathless(gy.youmonst.data) || haseyes(gy.youmonst.data)) { + /* wet towel protects both eyes and breathing */ + if (obj->otyp != POT_WATER && !Half_gas_damage) { + if (!breathless(gy.youmonst.data)) { /* [what about "familiar odor" when known?] */ You("smell a peculiar odor..."); } else { const char *eyes = body_part(EYE); - if (eyecount(youmonst.data) != 1) + if (eyecount(gy.youmonst.data) != 1) eyes = makeplural(eyes); Your("%s %s.", eyes, vtense(eyes, "water")); } @@ -2003,8 +2524,10 @@ boolean from_invent; break; case EGG: /* breaking your own eggs is bad luck */ - if (hero_caused && obj->spe && obj->corpsenm >= LOW_PM) + if (hero_caused && obj->spe && ismnum(obj->corpsenm)) change_luck((schar) -min(obj->quan, 5L)); + if (obj->corpsenm == PM_PYROLISK) + explosion = TRUE; break; case BOULDER: case STATUE: @@ -2026,39 +2549,51 @@ boolean from_invent; struct monst *shkp = shop_keeper(*o_shop); if (shkp) { /* (implies *o_shop != '\0') */ - static NEARDATA long lastmovetime = 0L; - static NEARDATA boolean peaceful_shk = FALSE; - /* We want to base shk actions on her peacefulness - at start of this turn, so that "simultaneous" - multiple breakage isn't drastically worse than - single breakage. (ought to be done via ESHK) */ - if (moves != lastmovetime) - peaceful_shk = shkp->mpeaceful; - if (stolen_value(obj, x, y, peaceful_shk, FALSE) > 0L + struct eshk *eshkp = ESHK(shkp); + + /* base shk actions on her peacefulness at start of + this turn, so that "simultaneous" multiple breakage + isn't drastically worse than single breakage */ + if (gh.hero_seq != eshkp->break_seq) + eshkp->seq_peaceful = shkp->mpeaceful; + if ((stolen_value(obj, x, y, eshkp->seq_peaceful, FALSE) > 0L) && (*o_shop != u.ushops[0] || !inside_shop(u.ux, u.uy)) - && moves != lastmovetime) + && gh.hero_seq != eshkp->break_seq) make_angry_shk(shkp, x, y); - lastmovetime = moves; + /* make_angry_shk() is only called on the first instance + of breakage during any particular hero move */ + eshkp->break_seq = gh.hero_seq; } } } if (!fracture) delobj(obj); + if (explosion) + explode(x, y, -11, d(3, 6), 0, EXPL_FIERY); + return 1; } /* - * Check to see if obj is going to break, but don't actually break it. - * Return 0 if the object isn't going to break, 1 if it is. + * Check to see if obj (which has just hit hard something at speed, e.g. + * thrown or dropped from height) is going to break, but don't actually + * break it. Return 0 if the object isn't going to break, 1 if it is. */ boolean -breaktest(obj) -struct obj *obj; +breaktest(struct obj *obj) { - if (obj_resists(obj, 1, 99)) - return 0; + int nonbreakchance = 1; /* chance for non-artifacts to resist */ + + /* this may need to be changed if actual glass armor gets added someday; + for now, it affects crystal plate mail and helm of brilliance; + either of them will have to be cracked 4 times before breaking */ + if (obj->oclass == ARMOR_CLASS && objects[obj->otyp].oc_material == GLASS) + nonbreakchance = 90; + + if (obj_resists(obj, nonbreakchance, 99)) + return FALSE; if (objects[obj->otyp].oc_material == GLASS && !obj->oartifact && obj->oclass != GEM_CLASS) - return 1; + return TRUE; switch (obj->oclass == POTION_CLASS ? POT_WATER : obj->otyp) { case EXPENSIVE_CAMERA: case POT_WATER: /* really, all potions */ @@ -2067,31 +2602,33 @@ struct obj *obj; case MELON: case ACID_VENOM: case BLINDING_VENOM: - return 1; + return TRUE; default: - return 0; + return FALSE; } } -STATIC_OVL void -breakmsg(obj, in_view) -struct obj *obj; -boolean in_view; +staticfn void +breakmsg(struct obj *obj, boolean in_view) { const char *to_pieces; + if (is_crackable(obj)) /* breakobj() will call erode_obj() for message */ + return; + to_pieces = ""; switch (obj->oclass == POTION_CLASS ? POT_WATER : obj->otyp) { default: /* glass or crystal wand */ if (obj->oclass != WAND_CLASS) - impossible("breaking odd object?"); + impossible("breaking odd object (%d)?", obj->otyp); + FALLTHROUGH; /*FALLTHRU*/ - case CRYSTAL_PLATE_MAIL: case LENSES: case MIRROR: case CRYSTAL_BALL: case EXPENSIVE_CAMERA: to_pieces = " into a thousand pieces"; + FALLTHROUGH; /*FALLTHRU*/ case POT_WATER: /* really, all potions */ if (!in_view) @@ -2115,24 +2652,31 @@ boolean in_view; } } -STATIC_OVL int -throw_gold(obj) -struct obj *obj; +staticfn int +throw_gold(struct obj *obj) { int range, odx, ody; - register struct monst *mon; + struct monst *mon; if (!u.dx && !u.dy && !u.dz) { You("cannot throw gold at yourself."); - return 0; + /* If we tried to throw part of a stack, force it to merge back + together (same as in throw_obj). Essential for gold. */ + if (obj->o_id == svc.context.objsplit.parent_oid + || obj->o_id == svc.context.objsplit.child_oid) + (void) unsplitobj(obj); + return ECMD_CANCEL; } freeinv(obj); if (u.uswallow) { - pline(is_animal(u.ustuck->data) ? "%s in the %s's entrails." - : "%s into %s.", - "The money disappears", mon_nam(u.ustuck)); + const char *swallower = mon_nam(u.ustuck); + + if (digests(u.ustuck->data)) + /* note: s_suffix() returns a modifiable buffer */ + swallower = strcat(s_suffix(swallower), " entrails"); + pline_The("gold disappears into %s.", swallower); add_to_minv(u.ustuck, obj); - return 1; + return ECMD_TIME; } if (u.dz) { @@ -2145,8 +2689,8 @@ struct obj *obj; pline("Fortunately, you are wearing %s!", an(helm_simple_name(uarmh))); } - bhitpos.x = u.ux; - bhitpos.y = u.uy; + gb.bhitpos.x = u.ux; + gb.bhitpos.y = u.uy; } else { /* consistent with range for normal objects */ range = (int) ((ACURRSTR) / 2 - obj->owt / 40); @@ -2154,35 +2698,38 @@ struct obj *obj; /* see if the gold has a place to move into */ odx = u.ux + u.dx; ody = u.uy + u.dy; - if (!ZAP_POS(levl[odx][ody].typ) || closed_door(odx, ody)) { - bhitpos.x = u.ux; - bhitpos.y = u.uy; + if (!isok(odx, ody) + || !ZAP_POS(levl[odx][ody].typ) || closed_door(odx, ody)) { + gb.bhitpos.x = u.ux; + gb.bhitpos.y = u.uy; } else { mon = bhit(u.dx, u.dy, range, THROWN_WEAPON, - (int FDECL((*), (MONST_P, OBJ_P))) 0, - (int FDECL((*), (OBJ_P, OBJ_P))) 0, &obj); + (int (*)(MONST_P, OBJ_P)) 0, + (int (*)(OBJ_P, OBJ_P)) 0, &obj); if (!obj) - return 1; /* object is gone */ + return ECMD_TIME; /* object is gone */ if (mon) { if (ghitm(mon, obj)) /* was it caught? */ - return 1; + return ECMD_TIME; } else { - if (ship_object(obj, bhitpos.x, bhitpos.y, FALSE)) - return 1; + if (ship_object(obj, gb.bhitpos.x, gb.bhitpos.y, FALSE)) + return ECMD_TIME; } } } - if (flooreffects(obj, bhitpos.x, bhitpos.y, "fall")) - return 1; + if (flooreffects(obj, gb.bhitpos.x, gb.bhitpos.y, "fall")) + return ECMD_TIME; if (u.dz > 0) - pline_The("gold hits the %s.", surface(bhitpos.x, bhitpos.y)); - place_object(obj, bhitpos.x, bhitpos.y); + pline_The("gold hits the %s.", surface(gb.bhitpos.x, gb.bhitpos.y)); + place_object(obj, gb.bhitpos.x, gb.bhitpos.y); if (*u.ushops) - sellobj(obj, bhitpos.x, bhitpos.y); + sellobj(obj, gb.bhitpos.x, gb.bhitpos.y); stackobj(obj); - newsym(bhitpos.x, bhitpos.y); - return 1; + newsym(gb.bhitpos.x, gb.bhitpos.y); + return ECMD_TIME; } +#undef AutoReturn + /*dothrow.c*/ diff --git a/src/drawing.c b/src/drawing.c index ca5283835..375db072b 100644 --- a/src/drawing.c +++ b/src/drawing.c @@ -1,35 +1,21 @@ -/* NetHack 3.6 drawing.c $NHDT-Date: 1573943500 2019/11/16 22:31:40 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.64 $ */ +/* NetHack 5.0 drawing.c $NHDT-Date: 1596498163 2020/08/03 23:42:43 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.78 $ */ /* Copyright (c) NetHack Development Team 1992. */ /* NetHack may be freely redistributed. See license for details. */ -#include "hack.h" -#include "tcap.h" +#include "config.h" +#include "color.h" +#include "rm.h" +#include "objclass.h" +#include "wintype.h" +#include "sym.h" -/* Relevant header information in rm.h, objclass.h, and monsym.h. */ +extern const struct symparse loadsyms[]; +extern const struct class_sym def_oc_syms[MAXOCLASSES]; +extern const struct class_sym def_monsyms[MAXMCLASSES]; +extern const uchar def_r_oc_syms[MAXOCLASSES]; -#ifdef C -#undef C -#endif +/* Relevant header information in rm.h, objclass.h, sym.h, defsym.h. */ -#ifdef TEXTCOLOR -#define C(n) n -#else -#define C(n) -#endif - -struct symsetentry symset[NUM_GRAPHICS]; - -int currentgraphics = 0; - -nhsym showsyms[SYM_MAX] = DUMMY; /* symbols to be displayed */ -nhsym primary_syms[SYM_MAX] = DUMMY; /* primary symbols */ -nhsym rogue_syms[SYM_MAX] = DUMMY; /* rogue symbols */ -nhsym ov_primary_syms[SYM_MAX] = DUMMY; /* overides via config SYMBOL */ -nhsym ov_rogue_syms[SYM_MAX] = DUMMY; /* overides via config ROGUESYMBOL */ -nhsym warnsyms[WARNCOUNT] = DUMMY; /* the current warning display symbols */ -const char invisexplain[] = "remembered, unseen, creature", - altinvisexplain[] = "unseen creature"; /* for clairvoyance */ - /* Default object class symbols. See objclass.h. * {symbol, name, explain} * name: used in object_detect(). @@ -37,212 +23,53 @@ const char invisexplain[] = "remembered, unseen, creature", */ const struct class_sym def_oc_syms[MAXOCLASSES] = { { '\0', "", "" }, /* placeholder for the "random class" */ - { ILLOBJ_SYM, "illegal objects", "strange object" }, - { WEAPON_SYM, "weapons", "weapon" }, - { ARMOR_SYM, "armor", "suit or piece of armor" }, - { RING_SYM, "rings", "ring" }, - { AMULET_SYM, "amulets", "amulet" }, - { TOOL_SYM, "tools", "useful item (pick-axe, key, lamp...)" }, - { FOOD_SYM, "food", "piece of food" }, - { POTION_SYM, "potions", "potion" }, - { SCROLL_SYM, "scrolls", "scroll" }, - { SPBOOK_SYM, "spellbooks", "spellbook" }, - { WAND_SYM, "wands", "wand" }, - { GOLD_SYM, "coins", "pile of coins" }, - { GEM_SYM, "rocks", "gem or rock" }, - { ROCK_SYM, "large stones", "boulder or statue" }, - { BALL_SYM, "iron balls", "iron ball" }, - { CHAIN_SYM, "chains", "iron chain" }, - { VENOM_SYM, "venoms", "splash of venom" } +#define OBJCLASS_DRAWING +#include "defsym.h" +#undef OBJCLASS_DRAWING }; -/* Default monster class symbols. See monsym.h. */ +/* Default monster class symbols. See sym.h and defsym.h. */ const struct class_sym def_monsyms[MAXMCLASSES] = { { '\0', "", "" }, - { DEF_ANT, "", "ant or other insect" }, - { DEF_BLOB, "", "blob" }, - { DEF_COCKATRICE, "", "cockatrice" }, - { DEF_DOG, "", "dog or other canine" }, - { DEF_EYE, "", "eye or sphere" }, - { DEF_FELINE, "", "cat or other feline" }, - { DEF_GREMLIN, "", "gremlin" }, - { DEF_HUMANOID, "", "humanoid" }, - { DEF_IMP, "", "imp or minor demon" }, - { DEF_JELLY, "", "jelly" }, - { DEF_KOBOLD, "", "kobold" }, - { DEF_LEPRECHAUN, "", "leprechaun" }, - { DEF_MIMIC, "", "mimic" }, - { DEF_NYMPH, "", "nymph" }, - { DEF_ORC, "", "orc" }, - { DEF_PIERCER, "", "piercer" }, - { DEF_QUADRUPED, "", "quadruped" }, - { DEF_RODENT, "", "rodent" }, - { DEF_SPIDER, "", "arachnid or centipede" }, - { DEF_TRAPPER, "", "trapper or lurker above" }, - { DEF_UNICORN, "", "unicorn or horse" }, - { DEF_VORTEX, "", "vortex" }, - { DEF_WORM, "", "worm" }, - { DEF_XAN, "", "xan or other mythical/fantastic insect" }, - { DEF_LIGHT, "", "light" }, - { DEF_ZRUTY, "", "zruty" }, - { DEF_ANGEL, "", "angelic being" }, - { DEF_BAT, "", "bat or bird" }, - { DEF_CENTAUR, "", "centaur" }, - { DEF_DRAGON, "", "dragon" }, - { DEF_ELEMENTAL, "", "elemental" }, - { DEF_FUNGUS, "", "fungus or mold" }, - { DEF_GNOME, "", "gnome" }, - { DEF_GIANT, "", "giant humanoid" }, - { '\0', "", "invisible monster" }, - { DEF_JABBERWOCK, "", "jabberwock" }, - { DEF_KOP, "", "Keystone Kop" }, - { DEF_LICH, "", "lich" }, - { DEF_MUMMY, "", "mummy" }, - { DEF_NAGA, "", "naga" }, - { DEF_OGRE, "", "ogre" }, - { DEF_PUDDING, "", "pudding or ooze" }, - { DEF_QUANTMECH, "", "quantum mechanic" }, - { DEF_RUSTMONST, "", "rust monster or disenchanter" }, - { DEF_SNAKE, "", "snake" }, - { DEF_TROLL, "", "troll" }, - { DEF_UMBER, "", "umber hulk" }, - { DEF_VAMPIRE, "", "vampire" }, - { DEF_WRAITH, "", "wraith" }, - { DEF_XORN, "", "xorn" }, - { DEF_YETI, "", "apelike creature" }, - { DEF_ZOMBIE, "", "zombie" }, - { DEF_HUMAN, "", "human or elf" }, - { DEF_GHOST, "", "ghost" }, - { DEF_GOLEM, "", "golem" }, - { DEF_DEMON, "", "major demon" }, - { DEF_EEL, "", "sea monster" }, - { DEF_LIZARD, "", "lizard" }, - { DEF_WORM_TAIL, "", "long worm tail" }, - { DEF_MIMIC_DEF, "", "mimic" }, +#define MONSYMS_DRAWING +#include "defsym.h" +#undef MONSYMS_DRAWING }; const struct symdef def_warnsyms[WARNCOUNT] = { /* white warning */ - { '0', "unknown creature causing you worry", C(CLR_WHITE) }, + { '0', "unknown creature causing you worry", CLR_WHITE }, /* pink warning */ - { '1', "unknown creature causing you concern", C(CLR_RED) }, + { '1', "unknown creature causing you concern", CLR_RED }, /* red warning */ - { '2', "unknown creature causing you anxiety", C(CLR_RED) }, + { '2', "unknown creature causing you anxiety", CLR_RED }, /* ruby warning */ - { '3', "unknown creature causing you disquiet", C(CLR_RED) }, + { '3', "unknown creature causing you disquiet", CLR_RED }, /* purple warning */ - { '4', "unknown creature causing you alarm", C(CLR_MAGENTA) }, + { '4', "unknown creature causing you alarm", CLR_MAGENTA }, /* black warning */ - { '5', "unknown creature causing you dread", C(CLR_BRIGHT_MAGENTA) }, + { '5', "unknown creature causing you dread", CLR_BRIGHT_MAGENTA }, }; /* * Default screen symbols with explanations and colors. + * + * If adding to or removing from this list, please note that, + * for builds with tile support, there is an array called altlabels[] in + * win/share/tilemap.c that requires the same number of elements as + * this, in the same order. It is used for tile name matching when + * parsing other.txt because some of the useful tile names don't exist + * within NetHack itself. */ -const struct symdef defsyms[MAXPCHARS] = { -/* 0*/ { ' ', "dark part of a room", C(NO_COLOR) }, /* stone */ - { '|', "wall", C(CLR_GRAY) }, /* vwall */ - { '-', "wall", C(CLR_GRAY) }, /* hwall */ - { '-', "wall", C(CLR_GRAY) }, /* tlcorn */ - { '-', "wall", C(CLR_GRAY) }, /* trcorn */ - { '-', "wall", C(CLR_GRAY) }, /* blcorn */ - { '-', "wall", C(CLR_GRAY) }, /* brcorn */ - { '-', "wall", C(CLR_GRAY) }, /* crwall */ - { '-', "wall", C(CLR_GRAY) }, /* tuwall */ - { '-', "wall", C(CLR_GRAY) }, /* tdwall */ -/*10*/ { '|', "wall", C(CLR_GRAY) }, /* tlwall */ - { '|', "wall", C(CLR_GRAY) }, /* trwall */ - { '.', "doorway", C(CLR_GRAY) }, /* ndoor */ - { '-', "open door", C(CLR_BROWN) }, /* vodoor */ - { '|', "open door", C(CLR_BROWN) }, /* hodoor */ - { '+', "closed door", C(CLR_BROWN) }, /* vcdoor */ - { '+', "closed door", C(CLR_BROWN) }, /* hcdoor */ - { '#', "iron bars", C(HI_METAL) }, /* bars */ - { '#', "tree", C(CLR_GREEN) }, /* tree */ - { '.', "floor of a room", C(CLR_GRAY) }, /* room */ -/*20*/ { '.', "dark part of a room", C(CLR_BLACK) }, /* dark room */ - { '#', "corridor", C(CLR_GRAY) }, /* dark corr */ - { '#', "lit corridor", C(CLR_GRAY) }, /* lit corr (see mapglyph.c) */ - { '<', "staircase up", C(CLR_GRAY) }, /* upstair */ - { '>', "staircase down", C(CLR_GRAY) }, /* dnstair */ - { '<', "ladder up", C(CLR_BROWN) }, /* upladder */ - { '>', "ladder down", C(CLR_BROWN) }, /* dnladder */ - { '_', "altar", C(CLR_GRAY) }, /* altar */ - { '|', "grave", C(CLR_WHITE) }, /* grave */ - { '\\', "opulent throne", C(HI_GOLD) }, /* throne */ -/*30*/ { '#', "sink", C(CLR_GRAY) }, /* sink */ - { '{', "fountain", C(CLR_BRIGHT_BLUE) }, /* fountain */ - { '}', "water", C(CLR_BLUE) }, /* pool */ - { '.', "ice", C(CLR_CYAN) }, /* ice */ - { '}', "molten lava", C(CLR_RED) }, /* lava */ - { '.', "lowered drawbridge", C(CLR_BROWN) }, /* vodbridge */ - { '.', "lowered drawbridge", C(CLR_BROWN) }, /* hodbridge */ - { '#', "raised drawbridge", C(CLR_BROWN) }, /* vcdbridge */ - { '#', "raised drawbridge", C(CLR_BROWN) }, /* hcdbridge */ - { ' ', "air", C(CLR_CYAN) }, /* open air */ -/*40*/ { '#', "cloud", C(CLR_GRAY) }, /* [part of] a cloud */ - { '}', "water", C(CLR_BLUE) }, /* under water */ - { '^', "arrow trap", C(HI_METAL) }, /* trap */ - { '^', "dart trap", C(HI_METAL) }, /* trap */ - { '^', "falling rock trap", C(CLR_GRAY) }, /* trap */ - { '^', "squeaky board", C(CLR_BROWN) }, /* trap */ - { '^', "bear trap", C(HI_METAL) }, /* trap */ - { '^', "land mine", C(CLR_RED) }, /* trap */ - { '^', "rolling boulder trap", C(CLR_GRAY) }, /* trap */ - { '^', "sleeping gas trap", C(HI_ZAP) }, /* trap */ -/*50*/ { '^', "rust trap", C(CLR_BLUE) }, /* trap */ - { '^', "fire trap", C(CLR_ORANGE) }, /* trap */ - { '^', "pit", C(CLR_BLACK) }, /* trap */ - { '^', "spiked pit", C(CLR_BLACK) }, /* trap */ - { '^', "hole", C(CLR_BROWN) }, /* trap */ - { '^', "trap door", C(CLR_BROWN) }, /* trap */ - { '^', "teleportation trap", C(CLR_MAGENTA) }, /* trap */ - { '^', "level teleporter", C(CLR_MAGENTA) }, /* trap */ - { '^', "magic portal", C(CLR_BRIGHT_MAGENTA) }, /* trap */ - { '"', "web", C(CLR_GRAY) }, /* web */ -/*60*/ { '^', "statue trap", C(CLR_GRAY) }, /* trap */ - { '^', "magic trap", C(HI_ZAP) }, /* trap */ - { '^', "anti-magic field", C(HI_ZAP) }, /* trap */ - { '^', "polymorph trap", C(CLR_BRIGHT_GREEN) }, /* trap */ - { '~', "vibrating square", C(CLR_MAGENTA) }, /* "trap" */ - /* zap colors are changed by mapglyph() to match type of beam */ - { '|', "", C(CLR_GRAY) }, /* vbeam */ - { '-', "", C(CLR_GRAY) }, /* hbeam */ - { '\\', "", C(CLR_GRAY) }, /* lslant */ - { '/', "", C(CLR_GRAY) }, /* rslant */ - { '*', "", C(CLR_WHITE) }, /* dig beam */ - { '!', "", C(CLR_WHITE) }, /* camera flash beam */ - { ')', "", C(HI_WOOD) }, /* boomerang open left */ -/*70*/ { '(', "", C(HI_WOOD) }, /* boomerang open right */ - { '0', "", C(HI_ZAP) }, /* 4 magic shield symbols */ - { '#', "", C(HI_ZAP) }, - { '@', "", C(HI_ZAP) }, - { '*', "", C(HI_ZAP) }, - { '#', "poison cloud", C(CLR_BRIGHT_GREEN) }, /* part of a cloud */ - { '?', "valid position", C(CLR_BRIGHT_GREEN) }, /* target position */ - /* swallow colors are changed by mapglyph() to match engulfing monst */ - { '/', "", C(CLR_GREEN) }, /* swallow top left */ - { '-', "", C(CLR_GREEN) }, /* swallow top center */ - { '\\', "", C(CLR_GREEN) }, /* swallow top right */ -/*80*/ { '|', "", C(CLR_GREEN) }, /* swallow middle left */ - { '|', "", C(CLR_GREEN) }, /* swallow middle right */ - { '\\', "", C(CLR_GREEN) }, /* swallow bottom left */ - { '-', "", C(CLR_GREEN) }, /* swallow bottom center */ - { '/', "", C(CLR_GREEN) }, /* swallow bottom right */ - /* explosion colors are changed by mapglyph() to match type of expl. */ - { '/', "", C(CLR_ORANGE) }, /* explosion top left */ - { '-', "", C(CLR_ORANGE) }, /* explosion top center */ - { '\\', "", C(CLR_ORANGE) }, /* explosion top right */ - { '|', "", C(CLR_ORANGE) }, /* explosion middle left */ - { ' ', "", C(CLR_ORANGE) }, /* explosion middle center*/ -/*90*/ { '|', "", C(CLR_ORANGE) }, /* explosion middle right */ - { '\\', "", C(CLR_ORANGE) }, /* explosion bottom left */ - { '-', "", C(CLR_ORANGE) }, /* explosion bottom center*/ - { '/', "", C(CLR_ORANGE) }, /* explosion bottom right */ +const struct symdef defsyms[MAXPCHARS + 1] = { +#define PCHAR_DRAWING +#include "defsym.h" +#undef PCHAR_DRAWING + { 0, NULL, NO_COLOR } }; /* default rogue level symbols */ -static const uchar def_r_oc_syms[MAXOCLASSES] = { +const uchar def_r_oc_syms[MAXOCLASSES] = { /* 0*/ '\0', ILLOBJ_SYM, WEAPON_SYM, ']', /* armor */ RING_SYM, /* 5*/ ',', /* amulet */ @@ -254,31 +81,17 @@ static const uchar def_r_oc_syms[MAXOCLASSES] = { /*15*/ BALL_SYM, CHAIN_SYM, VENOM_SYM }; -#undef C - -#if defined(TERMLIB) || defined(CURSES_GRAPHICS) -void NDECL((*decgraphics_mode_callback)) = 0; /* set in tty_start_screen() */ -#endif /* TERMLIB || CURSES */ - -#ifdef PC9800 -void NDECL((*ibmgraphics_mode_callback)) = 0; /* set in tty_start_screen() */ -void NDECL((*ascgraphics_mode_callback)) = 0; /* set in tty_start_screen() */ -#endif - -#ifdef CURSES_GRAPHICS -void NDECL((*cursesgraphics_mode_callback)) = 0; -#endif - /* * Convert the given character to an object class. If the character is not - * recognized, then MAXOCLASSES is returned. Used in detect.c, invent.c, - * objnam.c, options.c, pickup.c, sp_lev.c, lev_main.c, and tilemap.c. + * recognized, then MAXOCLASSES is returned. Used in detect.c, drawing.c, + * invent.c, o_init.c, objnam.c, options.c, pickup.c, sp_lev.c, and + * windows.c. */ int -def_char_to_objclass(ch) -char ch; +def_char_to_objclass(char ch) { int i; + for (i = 1; i < MAXOCLASSES; i++) if (ch == def_oc_syms[i].sym) break; @@ -287,538 +100,45 @@ char ch; /* * Convert a character into a monster class. This returns the _first_ - * match made. If there are are no matches, return MAXMCLASSES. - * Used in detect.c, options.c, read.c, sp_lev.c, and lev_main.c + * match made. If there are no matches, return MAXMCLASSES. + * Used in detect.c, drawing.c, mondata.c, options.c, pickup.c, + * sp_lev.c, and windows.c. */ int -def_char_to_monclass(ch) -char ch; +def_char_to_monclass(char ch) { int i; + for (i = 1; i < MAXMCLASSES; i++) if (ch == def_monsyms[i].sym) break; return i; } -/* - * Explanations of the functions found below: - * - * init_symbols() - * Sets the current display symbols, the - * loadable symbols to the default NetHack - * symbols, including the rogue_syms rogue level - * symbols. This would typically be done - * immediately after execution begins. Any - * previously loaded external symbol sets are - * discarded. - * - * switch_symbols(arg) - * Called to swap in new current display symbols - * (showsyms) from either the default symbols, - * or from the loaded symbols. - * - * If (arg == 0) then showsyms are taken from - * defsyms, def_oc_syms, and def_monsyms. - * - * If (arg != 0), which is the normal expected - * usage, then showsyms are taken from the - * adjustable display symbols found in primary_syms. - * primary_syms may have been loaded from an external - * symbol file by config file options or interactively - * in the Options menu. - * - * assign_graphics(arg) - * - * This is used in the game core to toggle in and - * out of other {rogue} level display modes. - * - * If arg is ROGUESET, this places the rogue level - * symbols from rogue_syms into showsyms. - * - * If arg is PRIMARY, this places the symbols - * from l_monsyms into showsyms. - * - * update_primary_symset() - * Update a member of the primary(primary_*) symbol set. - * - * update_rogue_symset() - * Update a member of the rogue (rogue_*) symbol set. - * - * update_ov_primary_symset() - * Update a member of the overrides for primary symbol set. - * - * update_ov_rogue_symset() - * Update a member of the overrides for rogue symbol set. - * - */ - -void -init_symbols() -{ - init_ov_primary_symbols(); - init_ov_rogue_symbols(); - init_primary_symbols(); - init_showsyms(); - init_rogue_symbols(); -} - -void -init_showsyms() -{ - register int i; - - for (i = 0; i < MAXPCHARS; i++) - showsyms[i + SYM_OFF_P] = defsyms[i].sym; - for (i = 0; i < MAXOCLASSES; i++) - showsyms[i + SYM_OFF_O] = def_oc_syms[i].sym; - for (i = 0; i < MAXMCLASSES; i++) - showsyms[i + SYM_OFF_M] = def_monsyms[i].sym; - for (i = 0; i < WARNCOUNT; i++) - showsyms[i + SYM_OFF_W] = def_warnsyms[i].sym; - for (i = 0; i < MAXOTHER; i++) - showsyms[i + SYM_OFF_X] = get_othersym(i, PRIMARY); -} - -/* initialize defaults for the overrides to the rogue symset */ -void -init_ov_rogue_symbols() -{ - register int i; - - for (i = 0; i < SYM_MAX; i++) - ov_rogue_syms[i] = (nhsym) 0; -} -/* initialize defaults for the overrides to the primary symset */ -void -init_ov_primary_symbols() -{ - register int i; - - for (i = 0; i < SYM_MAX; i++) - ov_primary_syms[i] = (nhsym) 0; -} - -nhsym -get_othersym(idx, which_set) -int idx, which_set; +/* does 'ch' represent a furniture character? returns index into defsyms[] */ +int +def_char_is_furniture(char ch) { - nhsym sym = (nhsym) 0; - int oidx = idx + SYM_OFF_X; + /* note: these refer to defsyms[] order which is much different from + levl[][].typ order but both keep furniture in a contiguous block */ + static const char first_furniture[] = "stair", /* "staircase up" */ + last_furniture[] = "fountain"; + int i; + boolean furniture = FALSE; - if (which_set == ROGUESET) - sym = ov_rogue_syms[oidx] ? ov_rogue_syms[oidx] - : rogue_syms[oidx]; - else - sym = ov_primary_syms[oidx] ? ov_primary_syms[oidx] - : primary_syms[oidx]; - if (!sym) { - switch(idx) { - case SYM_BOULDER: - sym = def_oc_syms[ROCK_CLASS].sym; - break; - case SYM_INVISIBLE: - sym = DEF_INVISIBLE; - break; + for (i = 0; i < MAXPCHARS; ++i) { + if (!furniture) { + if (!strncmp(defsyms[i].explanation, first_furniture, 5)) + furniture = TRUE; + } + if (furniture) { + if (defsyms[i].sym == (uchar) ch) + return i; + if (!strcmp(defsyms[i].explanation, last_furniture)) + break; /* reached last furniture */ } } - return sym; -} - -/* initialize defaults for the primary symset */ -void -init_primary_symbols() -{ - register int i; - - for (i = 0; i < MAXPCHARS; i++) - primary_syms[i + SYM_OFF_P] = defsyms[i].sym; - for (i = 0; i < MAXOCLASSES; i++) - primary_syms[i + SYM_OFF_O] = def_oc_syms[i].sym; - for (i = 0; i < MAXMCLASSES; i++) - primary_syms[i + SYM_OFF_M] = def_monsyms[i].sym; - for (i = 0; i < WARNCOUNT; i++) - primary_syms[i + SYM_OFF_W] = def_warnsyms[i].sym; - for (i = 0; i < MAXOTHER; i++) - primary_syms[i + SYM_OFF_X] = get_othersym(i, PRIMARY); - - clear_symsetentry(PRIMARY, FALSE); -} - -/* initialize defaults for the rogue symset */ -void -init_rogue_symbols() -{ - register int i; - - /* These are defaults that can get overwritten - later by the roguesymbols option */ - - for (i = 0; i < MAXPCHARS; i++) - rogue_syms[i + SYM_OFF_P] = defsyms[i].sym; - rogue_syms[S_vodoor] = rogue_syms[S_hodoor] = rogue_syms[S_ndoor] = '+'; - rogue_syms[S_upstair] = rogue_syms[S_dnstair] = '%'; - - for (i = 0; i < MAXOCLASSES; i++) - rogue_syms[i + SYM_OFF_O] = def_r_oc_syms[i]; - for (i = 0; i < MAXMCLASSES; i++) - rogue_syms[i + SYM_OFF_M] = def_monsyms[i].sym; - for (i = 0; i < WARNCOUNT; i++) - rogue_syms[i + SYM_OFF_W] = def_warnsyms[i].sym; - for (i = 0; i < MAXOTHER; i++) - rogue_syms[i + SYM_OFF_X] = get_othersym(i, ROGUESET); - - clear_symsetentry(ROGUESET, FALSE); - /* default on Rogue level is no color - * but some symbol sets can override that - */ - symset[ROGUESET].nocolor = 1; -} - -void -assign_graphics(whichset) -int whichset; -{ - register int i; - - switch (whichset) { - case ROGUESET: - /* Adjust graphics display characters on Rogue levels */ - - for (i = 0; i < SYM_MAX; i++) - showsyms[i] = ov_rogue_syms[i] ? ov_rogue_syms[i] - : rogue_syms[i]; - -#if defined(MSDOS) && defined(USE_TILES) - if (iflags.grmode) - tileview(FALSE); -#endif - currentgraphics = ROGUESET; - break; - - case PRIMARY: - default: - for (i = 0; i < SYM_MAX; i++) - showsyms[i] = ov_primary_syms[i] ? ov_primary_syms[i] - : primary_syms[i]; - -#if defined(MSDOS) && defined(USE_TILES) - if (iflags.grmode) - tileview(TRUE); -#endif - currentgraphics = PRIMARY; - break; - } -} - -void -switch_symbols(nondefault) -int nondefault; -{ - register int i; - - if (nondefault) { - for (i = 0; i < SYM_MAX; i++) - showsyms[i] = ov_primary_syms[i] ? ov_primary_syms[i] - : primary_syms[i]; -#ifdef PC9800 - if (SYMHANDLING(H_IBM) && ibmgraphics_mode_callback) - (*ibmgraphics_mode_callback)(); - else if (SYMHANDLING(H_UNK) && ascgraphics_mode_callback) - (*ascgraphics_mode_callback)(); -#endif -#if defined(TERMLIB) || defined(CURSES_GRAPHICS) - /* curses doesn't assign any routine to dec..._callback but - probably does the expected initialization under the hood - for terminals capable of rendering DECgraphics */ - if (SYMHANDLING(H_DEC) && decgraphics_mode_callback) - (*decgraphics_mode_callback)(); -# ifdef CURSES_GRAPHICS - /* there aren't any symbol sets with CURS handling, and the - curses interface never assigns a routine to curses..._callback */ - if (SYMHANDLING(H_CURS) && cursesgraphics_mode_callback) - (*cursesgraphics_mode_callback)(); -# endif -#endif - } else { - init_primary_symbols(); - init_showsyms(); - } -} - -void -update_ov_primary_symset(symp, val) -struct symparse *symp; -int val; -{ - ov_primary_syms[symp->idx] = val; -} - -void -update_ov_rogue_symset(symp, val) -struct symparse *symp; -int val; -{ - ov_rogue_syms[symp->idx] = val; -} - -void -update_primary_symset(symp, val) -struct symparse *symp; -int val; -{ - primary_syms[symp->idx] = val; -} - -void -update_rogue_symset(symp, val) -struct symparse *symp; -int val; -{ - rogue_syms[symp->idx] = val; -} - -void -clear_symsetentry(which_set, name_too) -int which_set; -boolean name_too; -{ - if (symset[which_set].desc) - free((genericptr_t) symset[which_set].desc); - symset[which_set].desc = (char *) 0; - - symset[which_set].handling = H_UNK; - symset[which_set].nocolor = 0; - /* initialize restriction bits */ - symset[which_set].primary = 0; - symset[which_set].rogue = 0; - - if (name_too) { - if (symset[which_set].name) - free((genericptr_t) symset[which_set].name); - symset[which_set].name = (char *) 0; - } + return -1; } -/* - * If you are adding code somewhere to be able to recognize - * particular types of symset "handling", define a - * H_XXX macro in include/rm.h and add the name - * to this array at the matching offset. - */ -const char *known_handling[] = { - "UNKNOWN", /* H_UNK */ - "IBM", /* H_IBM */ - "DEC", /* H_DEC */ - "CURS", /* H_CURS */ - "MAC", /* H_MAC -- pre-OSX MACgraphics */ - (const char *) 0, -}; - -/* - * Accepted keywords for symset restrictions. - * These can be virtually anything that you want to - * be able to test in the code someplace. - * Be sure to: - * - add a corresponding Bitfield to the symsetentry struct in rm.h - * - initialize the field to zero in parse_sym_line in the SYM_CONTROL - * case 0 section of the idx switch. The location is prefaced with - * with a comment stating "initialize restriction bits". - * - set the value appropriately based on the index of your keyword - * under the case 5 sections of the same SYM_CONTROL idx switches. - * - add the field to clear_symsetentry() - */ -const char *known_restrictions[] = { - "primary", "rogue", (const char *) 0, -}; - -struct symparse loadsyms[] = { - { SYM_CONTROL, 0, "start" }, - { SYM_CONTROL, 0, "begin" }, - { SYM_CONTROL, 1, "finish" }, - { SYM_CONTROL, 2, "handling" }, - { SYM_CONTROL, 3, "description" }, - { SYM_CONTROL, 4, "color" }, - { SYM_CONTROL, 4, "colour" }, - { SYM_CONTROL, 5, "restrictions" }, - { SYM_PCHAR, S_stone, "S_stone" }, - { SYM_PCHAR, S_vwall, "S_vwall" }, - { SYM_PCHAR, S_hwall, "S_hwall" }, - { SYM_PCHAR, S_tlcorn, "S_tlcorn" }, - { SYM_PCHAR, S_trcorn, "S_trcorn" }, - { SYM_PCHAR, S_blcorn, "S_blcorn" }, - { SYM_PCHAR, S_brcorn, "S_brcorn" }, - { SYM_PCHAR, S_crwall, "S_crwall" }, - { SYM_PCHAR, S_tuwall, "S_tuwall" }, - { SYM_PCHAR, S_tdwall, "S_tdwall" }, - { SYM_PCHAR, S_tlwall, "S_tlwall" }, - { SYM_PCHAR, S_trwall, "S_trwall" }, - { SYM_PCHAR, S_ndoor, "S_ndoor" }, - { SYM_PCHAR, S_vodoor, "S_vodoor" }, - { SYM_PCHAR, S_hodoor, "S_hodoor" }, - { SYM_PCHAR, S_vcdoor, "S_vcdoor" }, - { SYM_PCHAR, S_hcdoor, "S_hcdoor" }, - { SYM_PCHAR, S_bars, "S_bars" }, - { SYM_PCHAR, S_tree, "S_tree" }, - { SYM_PCHAR, S_room, "S_room" }, - { SYM_PCHAR, S_darkroom, "S_darkroom" }, - { SYM_PCHAR, S_corr, "S_corr" }, - { SYM_PCHAR, S_litcorr, "S_litcorr" }, - { SYM_PCHAR, S_upstair, "S_upstair" }, - { SYM_PCHAR, S_dnstair, "S_dnstair" }, - { SYM_PCHAR, S_upladder, "S_upladder" }, - { SYM_PCHAR, S_dnladder, "S_dnladder" }, - { SYM_PCHAR, S_altar, "S_altar" }, - { SYM_PCHAR, S_grave, "S_grave" }, - { SYM_PCHAR, S_throne, "S_throne" }, - { SYM_PCHAR, S_sink, "S_sink" }, - { SYM_PCHAR, S_fountain, "S_fountain" }, - { SYM_PCHAR, S_pool, "S_pool" }, - { SYM_PCHAR, S_ice, "S_ice" }, - { SYM_PCHAR, S_lava, "S_lava" }, - { SYM_PCHAR, S_vodbridge, "S_vodbridge" }, - { SYM_PCHAR, S_hodbridge, "S_hodbridge" }, - { SYM_PCHAR, S_vcdbridge, "S_vcdbridge" }, - { SYM_PCHAR, S_hcdbridge, "S_hcdbridge" }, - { SYM_PCHAR, S_air, "S_air" }, - { SYM_PCHAR, S_cloud, "S_cloud" }, - { SYM_PCHAR, S_poisoncloud, "S_poisoncloud" }, - { SYM_PCHAR, S_water, "S_water" }, - { SYM_PCHAR, S_arrow_trap, "S_arrow_trap" }, - { SYM_PCHAR, S_dart_trap, "S_dart_trap" }, - { SYM_PCHAR, S_falling_rock_trap, "S_falling_rock_trap" }, - { SYM_PCHAR, S_squeaky_board, "S_squeaky_board" }, - { SYM_PCHAR, S_bear_trap, "S_bear_trap" }, - { SYM_PCHAR, S_land_mine, "S_land_mine" }, - { SYM_PCHAR, S_rolling_boulder_trap, "S_rolling_boulder_trap" }, - { SYM_PCHAR, S_sleeping_gas_trap, "S_sleeping_gas_trap" }, - { SYM_PCHAR, S_rust_trap, "S_rust_trap" }, - { SYM_PCHAR, S_fire_trap, "S_fire_trap" }, - { SYM_PCHAR, S_pit, "S_pit" }, - { SYM_PCHAR, S_spiked_pit, "S_spiked_pit" }, - { SYM_PCHAR, S_hole, "S_hole" }, - { SYM_PCHAR, S_trap_door, "S_trap_door" }, - { SYM_PCHAR, S_teleportation_trap, "S_teleportation_trap" }, - { SYM_PCHAR, S_level_teleporter, "S_level_teleporter" }, - { SYM_PCHAR, S_magic_portal, "S_magic_portal" }, - { SYM_PCHAR, S_web, "S_web" }, - { SYM_PCHAR, S_statue_trap, "S_statue_trap" }, - { SYM_PCHAR, S_magic_trap, "S_magic_trap" }, - { SYM_PCHAR, S_anti_magic_trap, "S_anti_magic_trap" }, - { SYM_PCHAR, S_polymorph_trap, "S_polymorph_trap" }, - { SYM_PCHAR, S_vibrating_square, "S_vibrating_square" }, - { SYM_PCHAR, S_vbeam, "S_vbeam" }, - { SYM_PCHAR, S_hbeam, "S_hbeam" }, - { SYM_PCHAR, S_lslant, "S_lslant" }, - { SYM_PCHAR, S_rslant, "S_rslant" }, - { SYM_PCHAR, S_digbeam, "S_digbeam" }, - { SYM_PCHAR, S_flashbeam, "S_flashbeam" }, - { SYM_PCHAR, S_boomleft, "S_boomleft" }, - { SYM_PCHAR, S_boomright, "S_boomright" }, - { SYM_PCHAR, S_goodpos, "S_goodpos" }, - { SYM_PCHAR, S_ss1, "S_ss1" }, - { SYM_PCHAR, S_ss2, "S_ss2" }, - { SYM_PCHAR, S_ss3, "S_ss3" }, - { SYM_PCHAR, S_ss4, "S_ss4" }, - { SYM_PCHAR, S_sw_tl, "S_sw_tl" }, - { SYM_PCHAR, S_sw_tc, "S_sw_tc" }, - { SYM_PCHAR, S_sw_tr, "S_sw_tr" }, - { SYM_PCHAR, S_sw_ml, "S_sw_ml" }, - { SYM_PCHAR, S_sw_mr, "S_sw_mr" }, - { SYM_PCHAR, S_sw_bl, "S_sw_bl" }, - { SYM_PCHAR, S_sw_bc, "S_sw_bc" }, - { SYM_PCHAR, S_sw_br, "S_sw_br" }, - { SYM_PCHAR, S_explode1, "S_explode1" }, - { SYM_PCHAR, S_explode2, "S_explode2" }, - { SYM_PCHAR, S_explode3, "S_explode3" }, - { SYM_PCHAR, S_explode4, "S_explode4" }, - { SYM_PCHAR, S_explode5, "S_explode5" }, - { SYM_PCHAR, S_explode6, "S_explode6" }, - { SYM_PCHAR, S_explode7, "S_explode7" }, - { SYM_PCHAR, S_explode8, "S_explode8" }, - { SYM_PCHAR, S_explode9, "S_explode9" }, - { SYM_OC, ILLOBJ_CLASS + SYM_OFF_O, "S_strange_obj" }, - { SYM_OC, WEAPON_CLASS + SYM_OFF_O, "S_weapon" }, - { SYM_OC, ARMOR_CLASS + SYM_OFF_O, "S_armor" }, - { SYM_OC, ARMOR_CLASS + SYM_OFF_O, "S_armour" }, - { SYM_OC, RING_CLASS + SYM_OFF_O, "S_ring" }, - { SYM_OC, AMULET_CLASS + SYM_OFF_O, "S_amulet" }, - { SYM_OC, TOOL_CLASS + SYM_OFF_O, "S_tool" }, - { SYM_OC, FOOD_CLASS + SYM_OFF_O, "S_food" }, - { SYM_OC, POTION_CLASS + SYM_OFF_O, "S_potion" }, - { SYM_OC, SCROLL_CLASS + SYM_OFF_O, "S_scroll" }, - { SYM_OC, SPBOOK_CLASS + SYM_OFF_O, "S_book" }, - { SYM_OC, WAND_CLASS + SYM_OFF_O, "S_wand" }, - { SYM_OC, COIN_CLASS + SYM_OFF_O, "S_coin" }, - { SYM_OC, GEM_CLASS + SYM_OFF_O, "S_gem" }, - { SYM_OC, ROCK_CLASS + SYM_OFF_O, "S_rock" }, - { SYM_OC, BALL_CLASS + SYM_OFF_O, "S_ball" }, - { SYM_OC, CHAIN_CLASS + SYM_OFF_O, "S_chain" }, - { SYM_OC, VENOM_CLASS + SYM_OFF_O, "S_venom" }, - { SYM_MON, S_ANT + SYM_OFF_M, "S_ant" }, - { SYM_MON, S_BLOB + SYM_OFF_M, "S_blob" }, - { SYM_MON, S_COCKATRICE + SYM_OFF_M, "S_cockatrice" }, - { SYM_MON, S_DOG + SYM_OFF_M, "S_dog" }, - { SYM_MON, S_EYE + SYM_OFF_M, "S_eye" }, - { SYM_MON, S_FELINE + SYM_OFF_M, "S_feline" }, - { SYM_MON, S_GREMLIN + SYM_OFF_M, "S_gremlin" }, - { SYM_MON, S_HUMANOID + SYM_OFF_M, "S_humanoid" }, - { SYM_MON, S_IMP + SYM_OFF_M, "S_imp" }, - { SYM_MON, S_JELLY + SYM_OFF_M, "S_jelly" }, - { SYM_MON, S_KOBOLD + SYM_OFF_M, "S_kobold" }, - { SYM_MON, S_LEPRECHAUN + SYM_OFF_M, "S_leprechaun" }, - { SYM_MON, S_MIMIC + SYM_OFF_M, "S_mimic" }, - { SYM_MON, S_NYMPH + SYM_OFF_M, "S_nymph" }, - { SYM_MON, S_ORC + SYM_OFF_M, "S_orc" }, - { SYM_MON, S_PIERCER + SYM_OFF_M, "S_piercer" }, - { SYM_MON, S_QUADRUPED + SYM_OFF_M, "S_quadruped" }, - { SYM_MON, S_RODENT + SYM_OFF_M, "S_rodent" }, - { SYM_MON, S_SPIDER + SYM_OFF_M, "S_spider" }, - { SYM_MON, S_TRAPPER + SYM_OFF_M, "S_trapper" }, - { SYM_MON, S_UNICORN + SYM_OFF_M, "S_unicorn" }, - { SYM_MON, S_VORTEX + SYM_OFF_M, "S_vortex" }, - { SYM_MON, S_WORM + SYM_OFF_M, "S_worm" }, - { SYM_MON, S_XAN + SYM_OFF_M, "S_xan" }, - { SYM_MON, S_LIGHT + SYM_OFF_M, "S_light" }, - { SYM_MON, S_ZRUTY + SYM_OFF_M, "S_zruty" }, - { SYM_MON, S_ANGEL + SYM_OFF_M, "S_angel" }, - { SYM_MON, S_BAT + SYM_OFF_M, "S_bat" }, - { SYM_MON, S_CENTAUR + SYM_OFF_M, "S_centaur" }, - { SYM_MON, S_DRAGON + SYM_OFF_M, "S_dragon" }, - { SYM_MON, S_ELEMENTAL + SYM_OFF_M, "S_elemental" }, - { SYM_MON, S_FUNGUS + SYM_OFF_M, "S_fungus" }, - { SYM_MON, S_GNOME + SYM_OFF_M, "S_gnome" }, - { SYM_MON, S_GIANT + SYM_OFF_M, "S_giant" }, - { SYM_MON, S_JABBERWOCK + SYM_OFF_M, "S_jabberwock" }, - { SYM_MON, S_KOP + SYM_OFF_M, "S_kop" }, - { SYM_MON, S_LICH + SYM_OFF_M, "S_lich" }, - { SYM_MON, S_MUMMY + SYM_OFF_M, "S_mummy" }, - { SYM_MON, S_NAGA + SYM_OFF_M, "S_naga" }, - { SYM_MON, S_OGRE + SYM_OFF_M, "S_ogre" }, - { SYM_MON, S_PUDDING + SYM_OFF_M, "S_pudding" }, - { SYM_MON, S_QUANTMECH + SYM_OFF_M, "S_quantmech" }, - { SYM_MON, S_RUSTMONST + SYM_OFF_M, "S_rustmonst" }, - { SYM_MON, S_SNAKE + SYM_OFF_M, "S_snake" }, - { SYM_MON, S_TROLL + SYM_OFF_M, "S_troll" }, - { SYM_MON, S_UMBER + SYM_OFF_M, "S_umber" }, - { SYM_MON, S_VAMPIRE + SYM_OFF_M, "S_vampire" }, - { SYM_MON, S_WRAITH + SYM_OFF_M, "S_wraith" }, - { SYM_MON, S_XORN + SYM_OFF_M, "S_xorn" }, - { SYM_MON, S_YETI + SYM_OFF_M, "S_yeti" }, - { SYM_MON, S_ZOMBIE + SYM_OFF_M, "S_zombie" }, - { SYM_MON, S_HUMAN + SYM_OFF_M, "S_human" }, - { SYM_MON, S_GHOST + SYM_OFF_M, "S_ghost" }, - { SYM_MON, S_GOLEM + SYM_OFF_M, "S_golem" }, - { SYM_MON, S_DEMON + SYM_OFF_M, "S_demon" }, - { SYM_MON, S_EEL + SYM_OFF_M, "S_eel" }, - { SYM_MON, S_LIZARD + SYM_OFF_M, "S_lizard" }, - { SYM_MON, S_WORM_TAIL + SYM_OFF_M, "S_worm_tail" }, - { SYM_MON, S_MIMIC_DEF + SYM_OFF_M, "S_mimic_def" }, - { SYM_OTH, SYM_BOULDER + SYM_OFF_X, "S_boulder" }, - { SYM_OTH, SYM_INVISIBLE + SYM_OFF_X, "S_invisible" }, - { SYM_OTH, SYM_PET_OVERRIDE + SYM_OFF_X, "S_pet_override" }, - { SYM_OTH, SYM_HERO_OVERRIDE + SYM_OFF_X, "S_hero_override" }, - { 0, 0, (const char *) 0 } /* fence post */ -}; - /*drawing.c*/ diff --git a/src/dungeon.c b/src/dungeon.c index 27b748104..89afa002b 100644 --- a/src/dungeon.c +++ b/src/dungeon.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 dungeon.c $NHDT-Date: 1559476918 2019/06/02 12:01:58 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.95 $ */ +/* NetHack 5.0 dungeon.c $NHDT-Date: 1737343478 2025/01/19 19:24:38 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.228 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2012. */ /* NetHack may be freely redistributed. See license for details. */ @@ -6,9 +6,8 @@ #include "hack.h" #include "dgn_file.h" #include "dlb.h" -#include "lev.h" -#define DUNGEON_FILE "dungeon" +#define DUNGEON_FILE "dungeon.lua" #define X_START "x-strt" #define X_LOCATE "x-loca" @@ -25,62 +24,73 @@ struct proto_dungeon { int n_brs; /* number of tmpbranch entries */ }; -int n_dgns; /* number of dungeons (also used in mklev.c and do.c) */ -static branch *branches = (branch *) 0; /* dungeon branch list */ - -mapseen *mapseenchn = (struct mapseen *) 0; /*DUNGEON_OVERVIEW*/ - struct lchoice { int idx; schar lev[MAXLINFO]; schar playerlev[MAXLINFO]; - xchar dgn[MAXLINFO]; + xint16 dgn[MAXLINFO]; char menuletter; }; -static void FDECL(Fread, (genericptr_t, int, int, dlb *)); -STATIC_DCL xchar FDECL(dname_to_dnum, (const char *)); -STATIC_DCL int FDECL(find_branch, (const char *, struct proto_dungeon *)); -STATIC_DCL xchar FDECL(parent_dnum, (const char *, struct proto_dungeon *)); -STATIC_DCL int FDECL(level_range, (XCHAR_P, int, int, int, - struct proto_dungeon *, int *)); -STATIC_DCL xchar FDECL(parent_dlevel, (const char *, struct proto_dungeon *)); -STATIC_DCL int FDECL(correct_branch_type, (struct tmpbranch *)); -STATIC_DCL branch *FDECL(add_branch, (int, int, struct proto_dungeon *)); -STATIC_DCL void FDECL(add_level, (s_level *)); -STATIC_DCL void FDECL(init_level, (int, int, struct proto_dungeon *)); -STATIC_DCL int FDECL(possible_places, (int, boolean *, - struct proto_dungeon *)); -STATIC_DCL xchar FDECL(pick_level, (boolean *, int)); -STATIC_DCL boolean FDECL(place_level, (int, struct proto_dungeon *)); -STATIC_DCL boolean FDECL(unplaced_floater, (struct dungeon *)); -STATIC_DCL boolean FDECL(unreachable_level, (d_level *, BOOLEAN_P)); -STATIC_DCL void FDECL(tport_menu, (winid, char *, struct lchoice *, d_level *, - BOOLEAN_P)); -STATIC_DCL const char *FDECL(br_string, (int)); -STATIC_DCL char FDECL(chr_u_on_lvl, (d_level *)); -STATIC_DCL void FDECL(print_branch, (winid, int, int, int, BOOLEAN_P, - struct lchoice *)); -STATIC_DCL mapseen *FDECL(load_mapseen, (int)); -STATIC_DCL void FDECL(save_mapseen, (int, mapseen *)); -STATIC_DCL mapseen *FDECL(find_mapseen, (d_level *)); -STATIC_DCL mapseen *FDECL(find_mapseen_by_str, (const char *)); -STATIC_DCL void FDECL(print_mapseen, (winid, mapseen *, int, int, BOOLEAN_P)); -STATIC_DCL boolean FDECL(interest_mapseen, (mapseen *)); -STATIC_DCL void FDECL(traverse_mapseenchn, (BOOLEAN_P, winid, - int, int, int *)); -STATIC_DCL const char *FDECL(seen_string, (XCHAR_P, const char *)); -STATIC_DCL const char *FDECL(br_string2, (branch *)); -STATIC_DCL const char *FDECL(shop_string, (int)); -STATIC_DCL char *FDECL(tunesuffix, (mapseen *, char *)); +static mapseen *load_mapseen(NHFILE *); + +#ifndef SFCTOOL +#if 0 +staticfn void Fread(genericptr_t, int, int, dlb *); +#endif +staticfn xint16 dname_to_dnum(const char *); +staticfn int find_branch(const char *, struct proto_dungeon *); +staticfn xint16 parent_dnum(const char *, struct proto_dungeon *); +staticfn int level_range(xint16, int, int, int, struct proto_dungeon *, + int *); +staticfn xint16 parent_dlevel(const char *, struct proto_dungeon *); +staticfn int correct_branch_type(struct tmpbranch *); +staticfn branch *add_branch(int, int, struct proto_dungeon *); +staticfn void add_level(s_level *); +staticfn void init_level(int, int, struct proto_dungeon *); +staticfn int possible_places(int, boolean *, struct proto_dungeon *); +staticfn xint16 pick_level(boolean *, int); +staticfn boolean place_level(int, struct proto_dungeon *); +staticfn int get_dgn_flags(lua_State *); +staticfn int get_dgn_align(lua_State *); +staticfn void init_dungeon_levels(lua_State *, struct proto_dungeon *, int); +staticfn void init_dungeon_branches(lua_State *, struct proto_dungeon *, int); +staticfn void init_dungeon_set_entry(struct proto_dungeon *, int); +staticfn void init_dungeon_set_depth(struct proto_dungeon *, int); +staticfn void init_castle_tune(void); +staticfn void fixup_level_locations(void); +staticfn void free_proto_dungeon(struct proto_dungeon *); +staticfn void earth_sense(void); +staticfn boolean init_dungeon_dungeons(lua_State *, struct proto_dungeon *, + int); +staticfn boolean unplaced_floater(struct dungeon *); +staticfn boolean unreachable_level(d_level *, boolean); +staticfn void tport_menu(winid, char *, struct lchoice *, d_level *, boolean); +staticfn const char *br_string(int) NONNULL; +staticfn char chr_u_on_lvl(d_level *); +staticfn void print_branch(winid, int, int, int, boolean, struct lchoice *); +staticfn char *get_annotation(d_level *); +staticfn void query_annotation(d_level *); +staticfn mapseen *load_mapseen(NHFILE *); +staticfn void save_mapseen(NHFILE *, mapseen *); +staticfn mapseen *find_mapseen(d_level *); +staticfn mapseen *find_mapseen_by_str(const char *); +staticfn void print_mapseen(winid, mapseen *, int, int, boolean); +staticfn boolean interest_mapseen(mapseen *); +staticfn void count_feat_lastseentyp(mapseen *, coordxy, coordxy); +staticfn void traverse_mapseenchn(int, winid, int, int, int *); +staticfn const char *seen_string(xint16, const char *) NONNULL NONNULLARG2; +staticfn const char *br_string2(branch *) NONNULL NONNULLARG1; +staticfn const char *shop_string(int) NONNULL; +staticfn char *tunesuffix(mapseen *, char *, size_t) NONNULL NONNULLPTRS; #ifdef DEBUG -#define DD dungeons[i] -STATIC_DCL void NDECL(dumpit); +staticfn void dumpit(void); -STATIC_OVL void -dumpit() +staticfn void +dumpit(void) { +#define DD svd.dungeons[i] int i; s_level *x; branch *br; @@ -88,7 +98,7 @@ dumpit() if (!explicitdebug(__FILE__)) return; - for (i = 0; i < n_dgns; i++) { + for (i = 0; i < svn.n_dgns; i++) { fprintf(stderr, "\n#%d \"%s\" (%s):\n", i, DD.dname, DD.proto); fprintf(stderr, " num_dunlevs %d, dunlev_ureached %d\n", DD.num_dunlevs, DD.dunlev_ureached); @@ -98,10 +108,10 @@ dumpit() DD.flags.rogue_like ? " rogue_like" : "", DD.flags.maze_like ? " maze_like" : "", DD.flags.hellish ? " hellish" : ""); - getchar(); + (void) getchar(); } fprintf(stderr, "\nSpecial levels:\n"); - for (x = sp_levchn; x; x = x->next) { + for (x = svs.sp_levchn; x; x = x->next) { fprintf(stderr, "%s (%d): ", x->proto, x->rndlevs); fprintf(stderr, "on %d, %d; ", x->dlevel.dnum, x->dlevel.dlevel); fprintf(stderr, "flags:%s%s%s%s\n", @@ -109,10 +119,10 @@ dumpit() x->flags.maze_like ? " maze_like" : "", x->flags.hellish ? " hellish" : "", x->flags.town ? " town" : ""); - getchar(); + (void) getchar(); } fprintf(stderr, "\nBranches:\n"); - for (br = branches; br; br = br->next) { + for (br = svb.branches; br; br = br->next) { fprintf(stderr, "%d: %s, end1 %d %d, end2 %d %d, %s\n", br->id, br->type == BR_STAIR ? "stair" @@ -126,123 +136,136 @@ dumpit() br->end1.dnum, br->end1.dlevel, br->end2.dnum, br->end2.dlevel, br->end1_up ? "end1 up" : "end1 down"); } - getchar(); + (void) getchar(); fprintf(stderr, "\nDone\n"); - getchar(); + (void) getchar(); + return; +#undef DD } #endif /* Save the dungeon structures. */ void -save_dungeon(fd, perform_write, free_data) -int fd; -boolean perform_write, free_data; +save_dungeon( + NHFILE *nhfp, + boolean perform_write, + boolean free_data) { + int i, count; branch *curr, *next; mapseen *curr_ms, *next_ms; - int count; if (perform_write) { - bwrite(fd, (genericptr_t) &n_dgns, sizeof n_dgns); - bwrite(fd, (genericptr_t) dungeons, - sizeof(dungeon) * (unsigned) n_dgns); - bwrite(fd, (genericptr_t) &dungeon_topology, sizeof dungeon_topology); - bwrite(fd, (genericptr_t) tune, sizeof tune); - - for (count = 0, curr = branches; curr; curr = curr->next) + Sfo_int(nhfp, &svn.n_dgns, "dungeon_count"); + for (i = 0; i < svn.n_dgns; ++i) { + Sfo_dungeon(nhfp, &svd.dungeons[i], "dungeon"); + } + Sfo_dgn_topology(nhfp, &svd.dungeon_topology, "svd.dungeon_topology"); + Sfo_char(nhfp, svt.tune, "tune", (int) sizeof tune); + for (count = 0, curr = svb.branches; curr; curr = curr->next) count++; - bwrite(fd, (genericptr_t) &count, sizeof(count)); - - for (curr = branches; curr; curr = curr->next) - bwrite(fd, (genericptr_t) curr, sizeof(branch)); + Sfo_int(nhfp, &count, "branch_count"); + for (curr = svb.branches; curr; curr = curr->next) { + Sfo_branch(nhfp, curr, "branch"); + } count = maxledgerno(); - bwrite(fd, (genericptr_t) &count, sizeof count); - bwrite(fd, (genericptr_t) level_info, - (unsigned) count * sizeof(struct linfo)); - bwrite(fd, (genericptr_t) &inv_pos, sizeof inv_pos); + Sfo_int(nhfp, &count, "level_info_count"); + for (i = 0; i < count; ++i) { + Sfo_linfo(nhfp, &svl.level_info[i], "svl.level_info"); + } + Sfo_nhcoord(nhfp, &svi.inv_pos, "svi.inv_pos"); - for (count = 0, curr_ms = mapseenchn; curr_ms; + for (count = 0, curr_ms = svm.mapseenchn; curr_ms; curr_ms = curr_ms->next) count++; - bwrite(fd, (genericptr_t) &count, sizeof(count)); - for (curr_ms = mapseenchn; curr_ms; curr_ms = curr_ms->next) - save_mapseen(fd, curr_ms); + Sfo_int(nhfp, &count, "mapseen_count"); + + for (curr_ms = svm.mapseenchn; curr_ms; curr_ms = curr_ms->next) { + save_mapseen(nhfp, curr_ms); + } } if (free_data) { - for (curr = branches; curr; curr = next) { + for (curr = svb.branches; curr; curr = next) { next = curr->next; free((genericptr_t) curr); } - branches = 0; - for (curr_ms = mapseenchn; curr_ms; curr_ms = next_ms) { + svb.branches = 0; + for (curr_ms = svm.mapseenchn; curr_ms; curr_ms = next_ms) { next_ms = curr_ms->next; if (curr_ms->custom) free((genericptr_t) curr_ms->custom); if (curr_ms->final_resting_place) - savecemetery(fd, FREE_SAVE, &curr_ms->final_resting_place); + savecemetery(nhfp, &curr_ms->final_resting_place); free((genericptr_t) curr_ms); } - mapseenchn = 0; + svm.mapseenchn = 0; } } +#endif /* !SFCTOOL */ /* Restore the dungeon structures. */ void -restore_dungeon(fd) -int fd; +restore_dungeon(NHFILE *nhfp) { branch *curr, *last; - int count, i; + int count = 0; + int i; mapseen *curr_ms, *last_ms; - mread(fd, (genericptr_t) &n_dgns, sizeof(n_dgns)); - mread(fd, (genericptr_t) dungeons, sizeof(dungeon) * (unsigned) n_dgns); - mread(fd, (genericptr_t) &dungeon_topology, sizeof dungeon_topology); - mread(fd, (genericptr_t) tune, sizeof tune); + Sfi_int(nhfp, &svn.n_dgns, "dungeon_count"); + for (i = 0; i < svn.n_dgns; ++i) { + Sfi_dungeon(nhfp, &svd.dungeons[i], "dungeon"); + } + Sfi_dgn_topology(nhfp, &svd.dungeon_topology, "svd.dungeon_topology"); + Sfi_char(nhfp, svt.tune, "tune", (int) sizeof tune); + + last = svb.branches = (branch *) 0; - last = branches = (branch *) 0; + Sfi_int(nhfp, &count, "branch_count"); - mread(fd, (genericptr_t) &count, sizeof(count)); for (i = 0; i < count; i++) { - curr = (branch *) alloc(sizeof(branch)); - mread(fd, (genericptr_t) curr, sizeof(branch)); + curr = (branch *) alloc(sizeof *curr); + Sfi_branch(nhfp, curr, "branch"); curr->next = (branch *) 0; if (last) last->next = curr; else - branches = curr; + svb.branches = curr; last = curr; } - mread(fd, (genericptr_t) &count, sizeof(count)); + Sfi_int(nhfp, &count, "level_info_count"); + if (count >= MAXLINFO) panic("level information count larger (%d) than allocated size", count); - mread(fd, (genericptr_t) level_info, - (unsigned) count * sizeof(struct linfo)); - mread(fd, (genericptr_t) &inv_pos, sizeof inv_pos); + for (i = 0; i < count; ++i) { + Sfi_linfo(nhfp, &svl.level_info[i], "svl.level_info"); + } + + Sfi_nhcoord(nhfp, &svi.inv_pos, "svi.inv_pos"); + + Sfi_int(nhfp, &count, "mapseen_count"); - mread(fd, (genericptr_t) &count, sizeof(count)); last_ms = (mapseen *) 0; for (i = 0; i < count; i++) { - curr_ms = load_mapseen(fd); + curr_ms = load_mapseen(nhfp); curr_ms->next = (mapseen *) 0; if (last_ms) last_ms->next = curr_ms; else - mapseenchn = curr_ms; + svm.mapseenchn = curr_ms; last_ms = curr_ms; } } -static void -Fread(ptr, size, nitems, stream) -genericptr_t ptr; -int size, nitems; -dlb *stream; +#ifndef SFCTOOL +#if 0 +staticfn void +Fread(genericptr_t ptr, int size, int nitems, dlb *stream) { int cnt; @@ -253,38 +276,41 @@ dlb *stream; nh_terminate(EXIT_FAILURE); } } +#endif + +DISABLE_WARNING_UNREACHABLE_CODE -STATIC_OVL xchar -dname_to_dnum(s) -const char *s; +staticfn xint16 +dname_to_dnum(const char *s) { - xchar i; + xint16 i; - for (i = 0; i < n_dgns; i++) - if (!strcmp(dungeons[i].dname, s)) + for (i = 0; i < svn.n_dgns; i++) + if (!strcmp(svd.dungeons[i].dname, s)) return i; panic("Couldn't resolve dungeon number for name \"%s\".", s); /*NOT REACHED*/ - return (xchar) 0; + return (xint16) 0; } +RESTORE_WARNING_UNREACHABLE_CODE + s_level * -find_level(s) -const char *s; +find_level(const char *s) { s_level *curr; - for (curr = sp_levchn; curr; curr = curr->next) + for (curr = svs.sp_levchn; curr; curr = curr->next) if (!strcmpi(s, curr->proto)) break; return curr; } /* Find the branch that links the named dungeon. */ -STATIC_OVL int -find_branch(s, pd) -const char *s; /* dungeon name */ -struct proto_dungeon *pd; +staticfn int +find_branch( + const char *s, /* dungeon name */ + struct proto_dungeon *pd) { int i; @@ -299,8 +325,8 @@ struct proto_dungeon *pd; branch *br; const char *dnam; - for (br = branches; br; br = br->next) { - dnam = dungeons[br->end2.dnum].dname; + for (br = svb.branches; br; br = br->next) { + dnam = svd.dungeons[br->end2.dnum].dname; if (!strcmpi(dnam, s) || (!strncmpi(dnam, "The ", 4) && !strcmpi(dnam + 4, s))) break; @@ -310,17 +336,19 @@ struct proto_dungeon *pd; return i; } +DISABLE_WARNING_UNREACHABLE_CODE + /* * Find the "parent" by searching the prototype branch list for the branch * listing, then figuring out to which dungeon it belongs. */ -STATIC_OVL xchar -parent_dnum(s, pd) -const char *s; /* dungeon name */ -struct proto_dungeon *pd; +staticfn xint16 +parent_dnum( + const char *s, /* dungeon name */ + struct proto_dungeon *pd) { int i; - xchar pdnum; + xint16 pdnum; i = find_branch(s, pd); /* @@ -333,9 +361,11 @@ struct proto_dungeon *pd; panic("parent_dnum: couldn't resolve branch."); /*NOT REACHED*/ - return (xchar) 0; + return (xint16) 0; } +RESTORE_WARNING_UNREACHABLE_CODE + /* * Return a starting point and number of successive positions a level * or dungeon entrance can occupy. @@ -346,14 +376,14 @@ struct proto_dungeon *pd; * a negative random component means from the (adjusted) base to the * end of the dungeon. */ -STATIC_OVL int -level_range(dgn, base, randc, chain, pd, adjusted_base) -xchar dgn; -int base, randc, chain; -struct proto_dungeon *pd; -int *adjusted_base; +staticfn int +level_range( + xint16 dgn, + int base, int randc, int chain, + struct proto_dungeon *pd, + int *adjusted_base) { - int lmax = dungeons[dgn].num_dunlevs; + int lmax = svd.dungeons[dgn].num_dunlevs; if (chain >= 0) { /* relative to a special level */ s_level *levtmp = pd->final_lev[chain]; @@ -381,10 +411,8 @@ int *adjusted_base; return 1; } -STATIC_OVL xchar -parent_dlevel(s, pd) -const char *s; -struct proto_dungeon *pd; +staticfn xint16 +parent_dlevel(const char *s, struct proto_dungeon *pd) { int i, j, num, base, dnum = parent_dnum(s, pd); branch *curr; @@ -399,7 +427,7 @@ struct proto_dungeon *pd; do { if (++i >= num) i = 0; - for (curr = branches; curr; curr = curr->next) + for (curr = svb.branches; curr; curr = curr->next) if ((curr->end1.dnum == dnum && curr->end1.dlevel == base + i) || (curr->end2.dnum == dnum && curr->end2.dlevel == base + i)) break; @@ -408,9 +436,8 @@ struct proto_dungeon *pd; } /* Convert from the temporary branch type to the dungeon branch type. */ -STATIC_OVL int -correct_branch_type(tbr) -struct tmpbranch *tbr; +staticfn int +correct_branch_type(struct tmpbranch *tbr) { switch (tbr->type) { case TBR_STAIR: @@ -433,15 +460,14 @@ struct tmpbranch *tbr; * but needs to be repositioned. */ void -insert_branch(new_branch, extract_first) -branch *new_branch; -boolean extract_first; +insert_branch(branch *new_branch, boolean extract_first) { branch *curr, *prev; long new_val, curr_val, prev_val; if (extract_first) { - for (prev = 0, curr = branches; curr; prev = curr, curr = curr->next) + for (prev = 0, curr = svb.branches; curr; + prev = curr, curr = curr->next) if (curr == new_branch) break; @@ -450,12 +476,12 @@ boolean extract_first; if (prev) prev->next = curr->next; else - branches = curr->next; + svb.branches = curr->next; } new_branch->next = (branch *) 0; /* Convert the branch into a unique number so we can sort them. */ -#define branch_val(bp) \ +#define branch_val(bp) \ ((((long) (bp)->end1.dnum * (MAXLEVEL + 1) + (long) (bp)->end1.dlevel) \ * (MAXDUNGEON + 1) * (MAXLEVEL + 1)) \ + ((long) (bp)->end2.dnum * (MAXLEVEL + 1) + (long) (bp)->end2.dlevel)) @@ -466,7 +492,7 @@ boolean extract_first; prev = (branch *) 0; prev_val = -1; new_val = branch_val(new_branch); - for (curr = branches; curr; + for (curr = svb.branches; curr; prev_val = curr_val, prev = curr, curr = curr->next) { curr_val = branch_val(curr); if (prev_val < new_val && new_val <= curr_val) @@ -476,30 +502,31 @@ boolean extract_first; new_branch->next = curr; prev->next = new_branch; } else { - new_branch->next = branches; - branches = new_branch; + new_branch->next = svb.branches; + svb.branches = new_branch; } } +#undef branch_val + /* Add a dungeon branch to the branch list. */ -STATIC_OVL branch * -add_branch(dgn, child_entry_level, pd) -int dgn; -int child_entry_level; -struct proto_dungeon *pd; +staticfn branch * +add_branch( + int dgn, int child_entry_level, + struct proto_dungeon *pd) { static int branch_id = 0; int branch_num; branch *new_branch; - branch_num = find_branch(dungeons[dgn].dname, pd); + branch_num = find_branch(svd.dungeons[dgn].dname, pd); new_branch = (branch *) alloc(sizeof(branch)); - (void) memset((genericptr_t)new_branch, 0, sizeof(branch)); + (void) memset((genericptr_t) new_branch, 0, sizeof(branch)); new_branch->next = (branch *) 0; new_branch->id = branch_id++; new_branch->type = correct_branch_type(&pd->tmpbranch[branch_num]); - new_branch->end1.dnum = parent_dnum(dungeons[dgn].dname, pd); - new_branch->end1.dlevel = parent_dlevel(dungeons[dgn].dname, pd); + new_branch->end1.dnum = parent_dnum(svd.dungeons[dgn].dname, pd); + new_branch->end1.dlevel = parent_dlevel(svd.dungeons[dgn].dname, pd); new_branch->end2.dnum = dgn; new_branch->end2.dlevel = child_entry_level; new_branch->end1_up = pd->tmpbranch[branch_num].up ? TRUE : FALSE; @@ -514,32 +541,29 @@ struct proto_dungeon *pd; * level that has a dungeon number less than the dungeon number of the * last entry. */ -STATIC_OVL void -add_level(new_lev) -s_level *new_lev; +staticfn void +add_level(s_level *new_lev) { s_level *prev, *curr; prev = (s_level *) 0; - for (curr = sp_levchn; curr; curr = curr->next) { + for (curr = svs.sp_levchn; curr; curr = curr->next) { if (curr->dlevel.dnum == new_lev->dlevel.dnum && curr->dlevel.dlevel > new_lev->dlevel.dlevel) break; prev = curr; } if (!prev) { - new_lev->next = sp_levchn; - sp_levchn = new_lev; + new_lev->next = svs.sp_levchn; + svs.sp_levchn = new_lev; } else { new_lev->next = curr; prev->next = new_lev; } } -STATIC_OVL void -init_level(dgn, proto_index, pd) -int dgn, proto_index; -struct proto_dungeon *pd; +staticfn void +init_level(int dgn, int proto_index, struct proto_dungeon *pd) { s_level *new_level; struct tmplevel *tlevel = &pd->tmplevel[proto_index]; @@ -550,7 +574,7 @@ struct proto_dungeon *pd; pd->final_lev[proto_index] = new_level = (s_level *) alloc(sizeof(s_level)); - (void) memset((genericptr_t)new_level, 0, sizeof(s_level)); + (void) memset((genericptr_t) new_level, 0, sizeof(s_level)); /* load new level with data */ Strcpy(new_level->proto, tlevel->name); new_level->boneid = tlevel->boneschar; @@ -570,11 +594,11 @@ struct proto_dungeon *pd; new_level->next = (s_level *) 0; } -STATIC_OVL int -possible_places(idx, map, pd) -int idx; /* prototype index */ -boolean *map; /* array MAXLEVEL+1 in length */ -struct proto_dungeon *pd; +staticfn int +possible_places( + int idx, /* prototype index */ + boolean *map, /* array MAXLEVEL+1 in length */ + struct proto_dungeon *pd) { int i, start, count; s_level *lev = pd->final_lev[idx]; @@ -601,26 +625,30 @@ struct proto_dungeon *pd; return count; } +DISABLE_WARNING_UNREACHABLE_CODE + /* Pick the nth TRUE entry in the given boolean array. */ -STATIC_OVL xchar -pick_level(map, nth) -boolean *map; /* an array MAXLEVEL+1 in size */ -int nth; +staticfn xint16 +pick_level( + boolean *map, /* an array MAXLEVEL+1 in size */ + int nth) { - int i; + xint16 i; for (i = 1; i <= MAXLEVEL; i++) if (map[i] && !nth--) - return (xchar) i; + return i; panic("pick_level: ran out of valid levels"); + /*NOTREACHED*/ return 0; } +RESTORE_WARNING_UNREACHABLE_CODE + #ifdef DDEBUG -static void FDECL(indent, (int)); +staticfn void indent(int); -static void -indent(d) -int d; +staticfn void +indent(int d) { while (d-- > 0) fputs(" ", stderr); @@ -634,10 +662,8 @@ int d; * all possible places have been tried. If all possible places have * been exhausted, return false. */ -STATIC_OVL boolean -place_level(proto_index, pd) -int proto_index; -struct proto_dungeon *pd; +staticfn boolean +place_level(int proto_index, struct proto_dungeon *pd) { boolean map[MAXLEVEL + 1]; /* valid levels are 1..MAXLEVEL inclusive */ s_level *lev; @@ -678,7 +704,7 @@ struct proto_dungeon *pd; return FALSE; } -struct level_map { +static struct level_map { const char *lev_name; d_level *lev_spec; } level_map[] = { { "air", &air_level }, @@ -707,216 +733,397 @@ struct level_map { { X_START, &qstart_level }, { X_LOCATE, &qlocate_level }, { X_GOAL, &nemesis_level }, - { "", (d_level *) 0 } }; - -/* initialize the "dungeon" structs */ -void -init_dungeons() -{ - dlb *dgn_file; - register int i, cl = 0, cb = 0; - register s_level *x; - struct proto_dungeon pd; - struct level_map *lev_map; - struct version_info vers_info; - - pd.n_levs = pd.n_brs = 0; + { "", (d_level *) 0 } +}; - dgn_file = dlb_fopen(DUNGEON_FILE, RDBMODE); - if (!dgn_file) { - char tbuf[BUFSZ]; - Sprintf(tbuf, "Cannot open dungeon description - \"%s", DUNGEON_FILE); -#ifdef DLBRSRC /* using a resource from the executable */ - Strcat(tbuf, "\" resource!"); -#else /* using a file or DLB file */ -#if defined(DLB) - Strcat(tbuf, "\" from "); -#ifdef PREFIXES_IN_USE - Strcat(tbuf, "\n\""); - if (fqn_prefix[DATAPREFIX]) - Strcat(tbuf, fqn_prefix[DATAPREFIX]); -#else - Strcat(tbuf, "\""); -#endif - Strcat(tbuf, DLBFILE); -#endif - Strcat(tbuf, "\" file!"); -#endif -#ifdef WIN32 - interject_assistance(1, INTERJECT_PANIC, (genericptr_t) tbuf, - (genericptr_t) fqn_prefix[DATAPREFIX]); -#endif - panic1(tbuf); +#undef X_START +#undef X_LOCATE +#undef X_GOAL + +staticfn int +get_dgn_flags(lua_State *L) +{ + int dgn_flags = 0; + static const char *const flagstrs[] = { + "town", "hellish", "mazelike", "roguelike", "unconnected", NULL + }; + static const int flagstrs2i[] = { + TOWN, HELLISH, MAZELIKE, ROGUELIKE, UNCONNECTED, 0 + }; + + lua_getfield(L, -1, "flags"); + if (lua_type(L, -1) == LUA_TTABLE) { + int f, nflags; + + lua_len(L, -1); + nflags = (int) lua_tointeger(L, -1); + lua_pop(L, 1); + for (f = 0; f < nflags; f++) { + lua_pushinteger(L, f + 1); + lua_gettable(L, -2); + if (lua_type(L, -1) == LUA_TSTRING) { + dgn_flags |= flagstrs2i[luaL_checkoption(L, -1, NULL, + flagstrs)]; + lua_pop(L, 1); + } else + impossible("flags[%i] is not a string", f); + } + } else if (lua_type(L, -1) == LUA_TSTRING) { + dgn_flags |= flagstrs2i[luaL_checkoption(L, -1, NULL, flagstrs)]; + } else if (lua_type(L, -1) != LUA_TNIL) + impossible("flags is not an array or string"); + lua_pop(L, 1); + + return dgn_flags; +} + +staticfn int +get_dgn_align(lua_State *L) +{ + static const char *const dgnaligns[] = { + "unaligned", "noalign", "lawful", "neutral", "chaotic", NULL + }; + static const int dgnaligns2i[] = { + D_ALIGN_NONE, D_ALIGN_NONE, D_ALIGN_LAWFUL, + D_ALIGN_NEUTRAL, D_ALIGN_CHAOTIC, D_ALIGN_NONE + }; + + int a = dgnaligns2i[get_table_option(L, "alignment", + "unaligned", dgnaligns)]; + return a; +} + +staticfn void +init_dungeon_levels( + lua_State *L, + struct proto_dungeon *pd, + int dngidx) +{ + char *lvl_name, *lvl_bonetag, *lvl_chain; + int lvl_base, lvl_range, lvl_nlevels, lvl_chance, + lvl_align, lvl_flags; + struct tmplevel *tmpl; + int bi, f, nlevels; + + lua_len(L, -1); + nlevels = (int) lua_tointeger(L, -1); + pd->tmpdungeon[dngidx].levels = nlevels; + lua_pop(L, 1); + for (f = 0; f < nlevels; f++) { + lua_pushinteger(L, f + 1); + lua_gettable(L, -2); + if (lua_type(L, -1) == LUA_TTABLE) { + lvl_name = get_table_str(L, "name"); + lvl_bonetag = get_table_str_opt(L, "bonetag", emptystr); + lvl_chain = get_table_str_opt(L, "chainlevel", NULL); + lvl_base = get_table_int(L, "base"); + lvl_range = get_table_int_opt(L, "range", 0); + lvl_nlevels = get_table_int_opt(L, "nlevels", 0); + lvl_chance = get_table_int_opt(L, "chance", 100); + lvl_align = get_dgn_align(L); + lvl_flags = get_dgn_flags(L); + /* array index is offset by cumulative number of levels + defined for preceding branches (iterations of 'while' + loop we're inside, not branch connections below) */ + tmpl = &pd->tmplevel[pd->n_levs + f]; + + debugpline4("LEVEL[%i]:%s,(%i,%i)", + f, lvl_name, lvl_base, lvl_range); + tmpl->name = lvl_name; + tmpl->chainlvl = lvl_chain; + tmpl->lev.base = lvl_base; + tmpl->lev.rand = lvl_range; + tmpl->chance = lvl_chance; + tmpl->rndlevs = lvl_nlevels; + tmpl->flags = lvl_flags | lvl_align; + tmpl->boneschar = *lvl_bonetag ? *lvl_bonetag : 0; + free(lvl_bonetag); + tmpl->chain = -1; + if (lvl_chain) { + debugpline1("CHAINLEVEL: %s", lvl_chain); + for (bi = 0; bi < pd->n_levs + f; bi++) { + debugpline2("checking(%i):%s", + bi, pd->tmplevel[bi].name); + if (!strcmp(pd->tmplevel[bi].name, lvl_chain)) { + tmpl->chain = bi; + break; + } + } + if (tmpl->chain == -1) + panic("Could not chain level %s to %s", + lvl_name, lvl_chain); + /* free(lvl_chain); -- recorded in pd.tmplevel[] */ + } + } else + panic("dungeon[%i].levels[%i] is not a hash", dngidx, f); + lua_pop(L, 1); + } + pd->n_levs += nlevels; + if (pd->n_levs > LEV_LIMIT) + panic("init_dungeon: too many special levels"); +} + +staticfn void +init_dungeon_branches( + lua_State *L, + struct proto_dungeon *pd, + int dngidx) +{ + static const char *const brdirstr[] = { "up", "down", 0 }; + static const int brdirstr2i[] = { TRUE, FALSE, FALSE }; + static const char *const brtypes[] = { + "stair", "portal", "no_down", "no_up", 0 + }; + static const int brtypes2i[] = { + TBR_STAIR, TBR_PORTAL, TBR_NO_DOWN, TBR_NO_UP, TBR_STAIR + }; + char *br_name, *br_chain; + int br_base, br_range, br_type, br_up; + struct tmpbranch *tmpb; + int bi, f, nbranches; + + lua_len(L, -1); + nbranches = (int) lua_tointeger(L, -1); + pd->tmpdungeon[dngidx].branches = nbranches; + lua_pop(L, 1); + for (f = 0; f < nbranches; f++) { + lua_pushinteger(L, f + 1); + lua_gettable(L, -2); + if (lua_type(L, -1) == LUA_TTABLE) { + br_name = get_table_str(L, "name"); + br_chain = get_table_str_opt(L, "chainlevel", NULL); + br_base = get_table_int(L, "base"); + br_range = get_table_int_opt(L, "range", 0); + br_type = brtypes2i[get_table_option(L, "branchtype", + "stair", brtypes)]; + br_up = brdirstr2i[get_table_option(L, "direction", + "down", brdirstr)]; + tmpb = &(pd->tmpbranch[pd->n_brs + f]); + + debugpline4("BRANCH[%i]:%s,(%i,%i)", + f, br_name, br_base, br_range); + tmpb->name = br_name; + tmpb->lev.base = br_base; + tmpb->lev.rand = br_range; + tmpb->type = br_type; + tmpb->up = br_up; + tmpb->chain = -1; + if (br_chain) { + debugpline1("CHAINBRANCH:%s", br_chain); + for (bi = 0; bi < pd->n_levs + f - 1; bi++) + if (!strcmp(pd->tmplevel[bi].name, br_chain)) { + tmpb->chain = bi; + break; + } + if (tmpb->chain == -1) + panic("Could not chain branch %s to level %s", + br_name, br_chain); + free(br_chain); + } + } else + panic("dungeon[%i].branches[%i] is not a hash", dngidx, f); + lua_pop(L, 1); } + pd->n_brs += nbranches; + if (pd->n_brs > BRANCH_LIMIT) + panic("init_dungeon: too many branches"); +} - /* validate the data's version against the program's version */ - Fread((genericptr_t) &vers_info, sizeof vers_info, 1, dgn_file); - /* we'd better clear the screen now, since when error messages come from - * check_version() they will be printed using pline(), which doesn't - * mix with the raw messages that might be already on the screen - */ - if (iflags.window_inited) - clear_nhwindow(WIN_MAP); - if (!check_version(&vers_info, DUNGEON_FILE, TRUE)) - panic("Dungeon description not valid."); - +staticfn void +init_dungeon_set_entry(struct proto_dungeon *pd, int dngidx) +{ + int dgn_entry = pd->tmpdungeon[dngidx].entry_lev; /* - * Read in each dungeon and transfer the results to the internal - * dungeon arrays. + * Set the entry level for this dungeon. The entry value means: + * < 0 from bottom (-1 == bottom level) + * 0 default (top) + * > 0 actual level (1 = top) + * + * Note that the entry_lev field in the dungeon structure is + * redundant. It is used only here and in print_dungeon(). */ - sp_levchn = (s_level *) 0; - Fread((genericptr_t) &n_dgns, sizeof(int), 1, dgn_file); - if (n_dgns >= MAXDUNGEON) - panic("init_dungeons: too many dungeons"); + if (dgn_entry < 0) { + svd.dungeons[dngidx].entry_lev = + svd.dungeons[dngidx].num_dunlevs + dgn_entry + 1; + if (svd.dungeons[dngidx].entry_lev <= 0) + svd.dungeons[dngidx].entry_lev = 1; + } else if (dgn_entry > 0) { + svd.dungeons[dngidx].entry_lev = dgn_entry; + if (svd.dungeons[dngidx].entry_lev > svd.dungeons[dngidx].num_dunlevs) + svd.dungeons[dngidx].entry_lev = svd.dungeons[dngidx].num_dunlevs; + } else { /* default */ + svd.dungeons[dngidx].entry_lev = 1; /* defaults to top level */ + } +} - for (i = 0; i < n_dgns; i++) { - Fread((genericptr_t) &pd.tmpdungeon[i], sizeof(struct tmpdungeon), 1, - dgn_file); - if (!wizard && pd.tmpdungeon[i].chance - && (pd.tmpdungeon[i].chance <= rn2(100))) { - int j; - - /* skip over any levels or branches */ - for (j = 0; j < pd.tmpdungeon[i].levels; j++) - Fread((genericptr_t) &pd.tmplevel[cl], - sizeof(struct tmplevel), 1, dgn_file); - - for (j = 0; j < pd.tmpdungeon[i].branches; j++) - Fread((genericptr_t) &pd.tmpbranch[cb], - sizeof(struct tmpbranch), 1, dgn_file); - n_dgns--; - i--; - continue; - } +staticfn void +init_dungeon_set_depth(struct proto_dungeon *pd, int dngidx) +{ + branch *br; + schar from_depth; + boolean from_up; - Strcpy(dungeons[i].dname, pd.tmpdungeon[i].name); - Strcpy(dungeons[i].proto, pd.tmpdungeon[i].protoname); - dungeons[i].boneid = pd.tmpdungeon[i].boneschar; + br = add_branch(dngidx, svd.dungeons[dngidx].entry_lev, pd); - if (pd.tmpdungeon[i].lev.rand) - dungeons[i].num_dunlevs = (xchar) rn1(pd.tmpdungeon[i].lev.rand, - pd.tmpdungeon[i].lev.base); - else - dungeons[i].num_dunlevs = (xchar) pd.tmpdungeon[i].lev.base; + /* Get the depth of the connecting end. */ + if (br->end1.dnum == dngidx) { + from_depth = depth(&br->end2); + from_up = !br->end1_up; + } else { + from_depth = depth(&br->end1); + from_up = br->end1_up; + } - if (!i) { - dungeons[i].ledger_start = 0; - dungeons[i].depth_start = 1; - dungeons[i].dunlev_ureached = 1; - } else { - dungeons[i].ledger_start = - dungeons[i - 1].ledger_start + dungeons[i - 1].num_dunlevs; - dungeons[i].dunlev_ureached = 0; - } + /* + * Calculate the depth of the top of the dungeon via + * its branch. First, the depth of the entry point: + * + * depth of branch from "parent" dungeon + * + -1 or 1 depending on an up or down stair or + * 0 if portal + * + * Followed by the depth of the top of the dungeon: + * + * - (entry depth - 1) + * + * We'll say that portals stay on the same depth. + */ + svd.dungeons[dngidx].depth_start = + from_depth + (br->type == BR_PORTAL ? 0 : (from_up ? -1 : 1)) + - (svd.dungeons[dngidx].entry_lev - 1); +} + +staticfn boolean +init_dungeon_dungeons( + lua_State *L, + struct proto_dungeon *pd, + int dngidx) +{ + char *dgn_name, *dgn_bonetag, *dgn_protoname, *dgn_fill; + char *dgn_themerms; + int dgn_base, dgn_range, dgn_align, dgn_entry, dgn_chance, dgn_flags; + + dgn_name = get_table_str(L, "name"); + /* TODO: accept single char or "none" for bonetag */ + dgn_bonetag = get_table_str_opt(L, "bonetag", emptystr); + dgn_protoname = get_table_str_opt(L, "protofile", emptystr); + dgn_base = get_table_int(L, "base"); + dgn_range = get_table_int_opt(L, "range", 0); + dgn_align = get_dgn_align(L); + dgn_entry = get_table_int_opt(L, "entry", 0); + dgn_chance = get_table_int_opt(L, "chance", 100); + dgn_flags = get_dgn_flags(L); + dgn_fill = get_table_str_opt(L, "lvlfill", emptystr); + dgn_themerms = get_table_str_opt(L, "themerooms", emptystr); + + debugpline4("DUNGEON[%i]: %s, base=(%i,%i)", + dngidx, dgn_name, dgn_base, dgn_range); + + if (!wizard && dgn_chance && (dgn_chance <= rn2(100))) { + debugpline1("IGNORING %s", dgn_name); + svn.n_dgns--; + free((genericptr_t) dgn_name); + free((genericptr_t) dgn_bonetag); + free((genericptr_t) dgn_protoname); + free((genericptr_t) dgn_fill); + free((genericptr_t) dgn_themerms); + return FALSE; + } - dungeons[i].flags.hellish = !!(pd.tmpdungeon[i].flags & HELLISH); - dungeons[i].flags.maze_like = !!(pd.tmpdungeon[i].flags & MAZELIKE); - dungeons[i].flags.rogue_like = !!(pd.tmpdungeon[i].flags & ROGUELIKE); - dungeons[i].flags.align = - ((pd.tmpdungeon[i].flags & D_ALIGN_MASK) >> 4); - /* - * Set the entry level for this dungeon. The pd.tmpdungeon entry - * value means: - * < 0 from bottom (-1 == bottom level) - * 0 default (top) - * > 0 actual level (1 = top) - * - * Note that the entry_lev field in the dungeon structure is - * redundant. It is used only here and in print_dungeon(). - */ - if (pd.tmpdungeon[i].entry_lev < 0) { - dungeons[i].entry_lev = - dungeons[i].num_dunlevs + pd.tmpdungeon[i].entry_lev + 1; - if (dungeons[i].entry_lev <= 0) - dungeons[i].entry_lev = 1; - } else if (pd.tmpdungeon[i].entry_lev > 0) { - dungeons[i].entry_lev = pd.tmpdungeon[i].entry_lev; - if (dungeons[i].entry_lev > dungeons[i].num_dunlevs) - dungeons[i].entry_lev = dungeons[i].num_dunlevs; - } else { /* default */ - dungeons[i].entry_lev = 1; /* defaults to top level */ - } + /* levels begin */ + lua_getfield(L, -1, "levels"); + if (lua_type(L, -1) == LUA_TTABLE) { + init_dungeon_levels(L, pd, dngidx); + } else if (lua_type(L, -1) != LUA_TNIL) + panic("dungeon[%i].levels is not an array of hashes", dngidx); + lua_pop(L, 1); + /* levels end */ + + /* branches begin */ + lua_getfield(L, -1, "branches"); + if (lua_type(L, -1) == LUA_TTABLE) { + init_dungeon_branches(L, pd, dngidx); + } else if (lua_type(L, -1) != LUA_TNIL) + panic("dungeon[%i].branches is not an array of hashes", dngidx); + lua_pop(L, 1); + /* branches end */ + + pd->tmpdungeon[dngidx].name = dgn_name; + pd->tmpdungeon[dngidx].protoname = dgn_protoname; + pd->tmpdungeon[dngidx].boneschar = *dgn_bonetag ? *dgn_bonetag : 0; + pd->tmpdungeon[dngidx].lev.base = dgn_base; + pd->tmpdungeon[dngidx].lev.rand = dgn_range; + pd->tmpdungeon[dngidx].flags = dgn_flags; + pd->tmpdungeon[dngidx].align = dgn_align; + pd->tmpdungeon[dngidx].chance = dgn_chance; + pd->tmpdungeon[dngidx].entry_lev = dgn_entry; + + /* FIXME: these should have length checks */ + Strcpy(svd.dungeons[dngidx].fill_lvl, dgn_fill); + Strcpy(svd.dungeons[dngidx].dname, dgn_name); + Strcpy(svd.dungeons[dngidx].proto, dgn_protoname); + Strcpy(svd.dungeons[dngidx].themerms, dgn_themerms); + /* FIXME: accept "none", convert that to '\0' */ + svd.dungeons[dngidx].boneid = *dgn_bonetag ? *dgn_bonetag : 0; + free((genericptr) dgn_fill); + /* free((genericptr) dgn_protoname); -- stored in pd.tmpdungeon[] */ + free((genericptr) dgn_bonetag); + free((genericptr) dgn_themerms); + + if (dgn_range) + svd.dungeons[dngidx].num_dunlevs = (xint16) rn1(dgn_range, dgn_base); + else + svd.dungeons[dngidx].num_dunlevs = (xint16) dgn_base; - if (i) { /* set depth */ - branch *br; - schar from_depth; - boolean from_up; + if (!dngidx) { + svd.dungeons[dngidx].ledger_start = 0; + svd.dungeons[dngidx].depth_start = 1; + svd.dungeons[dngidx].dunlev_ureached = 1; + } else { + svd.dungeons[dngidx].ledger_start + = svd.dungeons[dngidx - 1].ledger_start + + svd.dungeons[dngidx - 1].num_dunlevs; + svd.dungeons[dngidx].dunlev_ureached = 0; + } - br = add_branch(i, dungeons[i].entry_lev, &pd); + svd.dungeons[dngidx].flags.hellish = !!(dgn_flags & HELLISH); + svd.dungeons[dngidx].flags.maze_like = !!(dgn_flags & MAZELIKE); + svd.dungeons[dngidx].flags.rogue_like = !!(dgn_flags & ROGUELIKE); + svd.dungeons[dngidx].flags.align = dgn_align; + svd.dungeons[dngidx].flags.unconnected = !!(dgn_flags & UNCONNECTED); - /* Get the depth of the connecting end. */ - if (br->end1.dnum == i) { - from_depth = depth(&br->end2); - from_up = !br->end1_up; - } else { - from_depth = depth(&br->end1); - from_up = br->end1_up; - } + init_dungeon_set_entry(pd, dngidx); - /* - * Calculate the depth of the top of the dungeon via - * its branch. First, the depth of the entry point: - * - * depth of branch from "parent" dungeon - * + -1 or 1 depending on an up or down stair or - * 0 if portal - * - * Followed by the depth of the top of the dungeon: - * - * - (entry depth - 1) - * - * We'll say that portals stay on the same depth. - */ - dungeons[i].depth_start = - from_depth + (br->type == BR_PORTAL ? 0 : (from_up ? -1 : 1)) - - (dungeons[i].entry_lev - 1); - } + if (svd.dungeons[dngidx].flags.unconnected) { + svd.dungeons[dngidx].depth_start = 1; + } else if (dngidx) { /* set depth */ + init_dungeon_set_depth(pd, dngidx); + } - /* this is redundant - it should have been flagged by dgn_comp */ - if (dungeons[i].num_dunlevs > MAXLEVEL) - dungeons[i].num_dunlevs = MAXLEVEL; + if (svd.dungeons[dngidx].num_dunlevs > MAXLEVEL) + svd.dungeons[dngidx].num_dunlevs = MAXLEVEL; - pd.start = pd.n_levs; /* save starting point */ - pd.n_levs += pd.tmpdungeon[i].levels; - if (pd.n_levs > LEV_LIMIT) - panic("init_dungeon: too many special levels"); - /* - * Read in the prototype special levels. Don't add generated - * special levels until they are all placed. - */ - for (; cl < pd.n_levs; cl++) { - Fread((genericptr_t) &pd.tmplevel[cl], sizeof(struct tmplevel), 1, - dgn_file); - init_level(i, cl, &pd); - } - /* - * Recursively place the generated levels for this dungeon. This - * routine will attempt all possible combinations before giving - * up. - */ - if (!place_level(pd.start, &pd)) - panic("init_dungeon: couldn't place levels"); -#ifdef DDEBUG - fprintf(stderr, "--- end of dungeon %d ---\n", i); - fflush(stderr); - getchar(); -#endif - for (; pd.start < pd.n_levs; pd.start++) - if (pd.final_lev[pd.start]) - add_level(pd.final_lev[pd.start]); + return TRUE; +} - pd.n_brs += pd.tmpdungeon[i].branches; - if (pd.n_brs > BRANCH_LIMIT) - panic("init_dungeon: too many branches"); - for (; cb < pd.n_brs; cb++) - Fread((genericptr_t) &pd.tmpbranch[cb], sizeof(struct tmpbranch), - 1, dgn_file); - } - (void) dlb_fclose(dgn_file); +/* initialize the Castle drawbridge tune */ +staticfn void +init_castle_tune(void) +{ + int i; for (i = 0; i < 5; i++) - tune[i] = 'A' + rn2(7); - tune[5] = 0; + svt.tune[i] = 'A' + rn2(7); + svt.tune[5] = 0; +} + +/* fix up the special level names and locations for quick access */ +staticfn void +fixup_level_locations(void) +{ + int i; + s_level *x; + struct level_map *lev_map; /* * Find most of the special levels and dungeons so we can access their @@ -930,24 +1137,24 @@ init_dungeons() /* This is where the name substitution on the * levels of the quest dungeon occur. */ - Sprintf(x->proto, "%s%s", urole.filecode, + Sprintf(x->proto, "%s%s", gu.urole.filecode, &lev_map->lev_name[1]); } else if (lev_map->lev_spec == &knox_level) { branch *br; /* * Kludge to allow floating Knox entrance. We - * specify a floating entrance by the fact that - * its entrance (end1) has a bogus dnum, namely - * n_dgns. + * specify a floating entrance by the fact that its + * entrance (end1) has a bogus dnum, namely n_dgns. */ - for (br = branches; br; br = br->next) + for (br = svb.branches; br; br = br->next) if (on_level(&br->end2, &knox_level)) break; - if (br) - br->end1.dnum = n_dgns; - /* adjust the branch's position on the list */ - insert_branch(br, TRUE); + if (br) { + br->end1.dnum = svn.n_dgns; + /* adjust the branch's position on the list */ + insert_branch(br, TRUE); + } } } } @@ -958,6 +1165,7 @@ init_dungeons() sokoban_dnum = dname_to_dnum("Sokoban"); mines_dnum = dname_to_dnum("The Gnomish Mines"); tower_dnum = dname_to_dnum("Vlad's Tower"); + tutorial_dnum = dname_to_dnum("The Tutorial"); /* one special fixup for dummy surface level */ if ((x = find_level("dummy")) != 0) { @@ -966,37 +1174,169 @@ init_dungeons() making the dummy level overlay level 1; but the whole reason for having the dummy level is to make earth have depth -1 instead of 0, so adjust the start point to shift endgame up */ - if (dunlevs_in_dungeon(&x->dlevel) > 1 - dungeons[i].depth_start) - dungeons[i].depth_start -= 1; + if (dunlevs_in_dungeon(&x->dlevel) > 1 - svd.dungeons[i].depth_start) + svd.dungeons[i].depth_start -= 1; /* TODO: strip "dummy" out all the way here, so that it's hidden from '#wizwhere' feedback. */ } +} + +staticfn void +free_proto_dungeon(struct proto_dungeon *pd) +{ + int i; + + for (i = 0; i < pd->n_brs; i++) { + free((genericptr_t) pd->tmpbranch[i].name); + } + for (i = 0; i < pd->n_levs; i++) { + free((genericptr_t) pd->tmplevel[i].name); + if (pd->tmplevel[i].chainlvl) + free((genericptr_t) pd->tmplevel[i].chainlvl); + } + for (i = 0; i < svn.n_dgns; i++) { + free((genericptr_t) pd->tmpdungeon[i].name); + free((genericptr_t) pd->tmpdungeon[i].protoname); + } +} + +/* initialize the "dungeon" structs */ +void +init_dungeons(void) +{ + lua_State *L; + int i, cl = 0; + struct proto_dungeon pd; + int tidx; + nhl_sandbox_info sbi = {NHL_SB_SAFE, 1*1024*1024, 0, 1*1024*1024}; + + (void) memset(&pd, 0, sizeof (struct proto_dungeon)); + pd.n_levs = pd.n_brs = 0; + + L = nhl_init(&sbi); /* private Lua state for this function */ + if (!L) { + panic1("'nhl_init' failed; can't continue."); + /*NOTREACHED*/ + } + if (!nhl_loadlua(L, DUNGEON_FILE)) { + char tbuf[BUFSZ]; + Sprintf(tbuf, "Cannot open dungeon description - \"%s", DUNGEON_FILE); +#ifdef DLBRSRC /* using a resource from the executable */ + Strcat(tbuf, "\" resource!"); +#else /* using a file or DLB file */ +#if defined(DLB) + Strcat(tbuf, "\" from "); +#ifdef PREFIXES_IN_USE + Strcat(tbuf, "\n\""); + if (gf.fqn_prefix[DATAPREFIX]) + Strcat(tbuf, gf.fqn_prefix[DATAPREFIX]); +#else + Strcat(tbuf, "\""); +#endif + Strcat(tbuf, DLBFILE); +#endif + Strcat(tbuf, "\" file!"); +#endif +#ifdef WIN32 + interject_assistance(1, INTERJECT_PANIC, (genericptr_t) tbuf, + (genericptr_t) gf.fqn_prefix[DATAPREFIX]); +#endif + panic1(tbuf); + } + + if (iflags.window_inited) + clear_nhwindow(WIN_MAP); + + svs.sp_levchn = (s_level *) 0; + + lua_settop(L, 0); + + lua_getglobal(L, "dungeon"); + if (!lua_istable(L, -1)) + panic("dungeon is not a lua table"); + + lua_len(L, -1); + svn.n_dgns = (int) lua_tointeger(L, -1); + lua_pop(L, 1); + + pd.start = 0; + pd.n_levs = 0; + pd.n_brs = 0; + /* + * Read in each dungeon and transfer the results to the internal + * dungeon arrays. + */ + + if (svn.n_dgns >= MAXDUNGEON) + panic("init_dungeons: too many dungeons"); + + tidx = lua_gettop(L); + + lua_pushnil(L); /* first key */ + i = 0; + while (lua_next(L, tidx) != 0) { + + if (!lua_istable(L, -1)) + panic("dungeon[%i] is not a lua table", i); + + if (init_dungeon_dungeons(L, &pd, i)) { + for (; cl < pd.n_levs; cl++) { + init_level(i, cl, &pd); + } + /* + * Recursively place the generated levels for this dungeon. This + * routine will attempt all possible combinations before giving + * up. + */ + if (!place_level(pd.start, &pd)) + panic("init_dungeon: couldn't place levels"); +#ifdef DDEBUG + fprintf(stderr, "--- end of dungeon %d ---\n", i); + fflush(stderr); + getchar(); +#endif + for (; pd.start < pd.n_levs; pd.start++) + if (pd.final_lev[pd.start]) + add_level(pd.final_lev[pd.start]); + /* levels handling end */ + i++; + } + lua_pop(L, 1); /* pop the dungeon table */ + } + + lua_pop(L, 1); /* get rid of the dungeon global */ + debugpline2("init_dungeon lua DONE (n_levs=%i, n_brs=%i)", + pd.n_levs, pd.n_brs); + + init_castle_tune(); + fixup_level_locations(); + nhl_done(L); + free_proto_dungeon(&pd); #ifdef DEBUG dumpit(); #endif } +#undef DUNGEON_FILE + /* return the level number for lev in *this* dungeon */ -xchar -dunlev(lev) -d_level *lev; +xint16 +dunlev(d_level *lev) { return lev->dlevel; } /* return the lowest level number for *this* dungeon */ -xchar -dunlevs_in_dungeon(lev) -d_level *lev; +xint16 +dunlevs_in_dungeon(d_level *lev) { - return dungeons[lev->dnum].num_dunlevs; + return svd.dungeons[lev->dnum].num_dunlevs; } /* return the lowest level explored in the game*/ -xchar -deepest_lev_reached(noquest) -boolean noquest; +xint16 +deepest_lev_reached(boolean noquest) { /* this function is used for three purposes: to provide a factor * of difficulty in monster generation; to provide a factor of @@ -1013,35 +1353,34 @@ boolean noquest; * calculation. _However_ the Quest is a difficult dungeon, so we * include it in the factor of difficulty calculations. */ - register int i; + int i; d_level tmp; - register schar ret = 0; + xint16 ret = 0; - for (i = 0; i < n_dgns; i++) { + for (i = 0; i < svn.n_dgns; i++) { if (noquest && i == quest_dnum) continue; - tmp.dlevel = dungeons[i].dunlev_ureached; + tmp.dlevel = svd.dungeons[i].dunlev_ureached; if (tmp.dlevel == 0) continue; tmp.dnum = i; if (depth(&tmp) > ret) ret = depth(&tmp); } - return (xchar) ret; + return ret; } /* return a bookkeeping level number for purpose of comparisons and save/restore */ -xchar -ledger_no(lev) -d_level *lev; +xint16 +ledger_no(d_level *lev) { - return (xchar) (lev->dlevel + dungeons[lev->dnum].ledger_start); + return (xint16) (lev->dlevel + svd.dungeons[lev->dnum].ledger_start); } /* * The last level in the bookkeeping list of level is the bottom of the last - * dungeon in the dungeons[] array. + * dungeon in the svd.dungeons[] array. * * Maxledgerno() -- which is the max number of levels in the bookkeeping * list, should not be confused with dunlevs_in_dungeon(lev) -- which @@ -1049,66 +1388,68 @@ d_level *lev; * not be confused with deepest_lev_reached() -- which returns the lowest * depth visited by the player. */ -xchar -maxledgerno() +xint16 +maxledgerno(void) { - return (xchar) (dungeons[n_dgns - 1].ledger_start - + dungeons[n_dgns - 1].num_dunlevs); + return (xint16) (svd.dungeons[svn.n_dgns - 1].ledger_start + + svd.dungeons[svn.n_dgns - 1].num_dunlevs); } +DISABLE_WARNING_UNREACHABLE_CODE + /* return the dungeon that this ledgerno exists in */ -xchar -ledger_to_dnum(ledgerno) -xchar ledgerno; +xint16 +ledger_to_dnum(xint16 ledgerno) { - register int i; + xint16 i; /* find i such that (i->base + 1) <= ledgerno <= (i->base + i->count) */ - for (i = 0; i < n_dgns; i++) - if (dungeons[i].ledger_start < ledgerno - && ledgerno <= dungeons[i].ledger_start + dungeons[i].num_dunlevs) - return (xchar) i; + for (i = 0; i < svn.n_dgns; i++) + if (svd.dungeons[i].ledger_start < ledgerno + && ledgerno <= (svd.dungeons[i].ledger_start + + svd.dungeons[i].num_dunlevs)) + return i; panic("level number out of range [ledger_to_dnum(%d)]", (int) ledgerno); /*NOT REACHED*/ - return (xchar) 0; + return (xint16) 0; } +RESTORE_WARNING_UNREACHABLE_CODE + /* return the level of the dungeon this ledgerno exists in */ -xchar -ledger_to_dlev(ledgerno) -xchar ledgerno; +xint16 +ledger_to_dlev(xint16 ledgerno) { - return (xchar) (ledgerno - - dungeons[ledger_to_dnum(ledgerno)].ledger_start); + return (xint16) (ledgerno + - svd.dungeons[ledger_to_dnum(ledgerno)].ledger_start); } /* returns the depth of a level, in floors below the surface (note levels in different dungeons can have the same depth) */ schar -depth(lev) -d_level *lev; +depth(d_level *lev) { - return (schar) (dungeons[lev->dnum].depth_start + lev->dlevel - 1); + return (schar) (svd.dungeons[lev->dnum].depth_start + lev->dlevel - 1); } +#endif /* !SFCTOOL */ /* are "lev1" and "lev2" actually the same? */ boolean -on_level(lev1, lev2) -d_level *lev1, *lev2; +on_level(d_level *lev1, d_level *lev2) { return (boolean) (lev1->dnum == lev2->dnum && lev1->dlevel == lev2->dlevel); } +#ifndef SFCTOOL /* is this level referenced in the special level chain? */ s_level * -Is_special(lev) -d_level *lev; +Is_special(d_level *lev) { s_level *levtmp; - for (levtmp = sp_levchn; levtmp; levtmp = levtmp->next) + for (levtmp = svs.sp_levchn; levtmp; levtmp = levtmp->next) if (on_level(lev, &levtmp->dlevel)) return levtmp; @@ -1120,12 +1461,11 @@ d_level *lev; * branch. Otherwise, return null. */ branch * -Is_branchlev(lev) -d_level *lev; +Is_branchlev(d_level *lev) { branch *curr; - for (curr = branches; curr; curr = curr->next) { + for (curr = svb.branches; curr; curr = curr->next) { if (on_level(lev, &curr->end1) || on_level(lev, &curr->end2)) return curr; } @@ -1134,30 +1474,39 @@ d_level *lev; /* returns True iff the branch 'lev' is in a branch which builds up */ boolean -builds_up(lev) -d_level *lev; +builds_up(d_level *lev) { - dungeon *dptr = &dungeons[lev->dnum]; - /* - * FIXME: this misclassifies a single level branch reached via stairs - * from below. Saving grace is that no such branches currently exist. - */ - return (boolean) (dptr->num_dunlevs > 1 - && dptr->entry_lev == dptr->num_dunlevs); + dungeon *dptr = &svd.dungeons[lev->dnum]; + branch *br; + if (dptr->num_dunlevs > 1) + return (boolean) (dptr->entry_lev == dptr->num_dunlevs); + /* else, single-level branch; find branch connection that connects this + * dungeon from a parent dungeon and determine whether it builds up from + * that */ + for (br = svb.branches; br; br = br->next) { + if (on_level(lev, &br->end2)) { + return br->end1_up; + } + } + impossible("builds_up: can't find branch for dungeon %d", lev->dnum); + return FALSE; } /* goto the next level (or appropriate dungeon) */ void -next_level(at_stairs) -boolean at_stairs; +next_level(boolean at_stairs) { - if (at_stairs && u.ux == sstairs.sx && u.uy == sstairs.sy) { - /* Taking a down dungeon branch. */ - goto_level(&sstairs.tolev, at_stairs, FALSE, FALSE); - } else { - /* Going down a stairs or jump in a trap door. */ - d_level newlevel; + stairway *stway = stairway_at(u.ux, u.uy); + d_level newlevel; + if (at_stairs && stway) + stway->u_traversed = TRUE; + + if (at_stairs && stway) { + newlevel.dnum = stway->tolev.dnum; + newlevel.dlevel = stway->tolev.dlevel; + goto_level(&newlevel, at_stairs, FALSE, FALSE); + } else { newlevel.dnum = u.uz.dnum; newlevel.dlevel = u.uz.dlevel + 1; goto_level(&newlevel, at_stairs, !at_stairs, FALSE); @@ -1166,32 +1515,60 @@ boolean at_stairs; /* goto the previous level (or appropriate dungeon) */ void -prev_level(at_stairs) -boolean at_stairs; +prev_level(boolean at_stairs) { - if (at_stairs && u.ux == sstairs.sx && u.uy == sstairs.sy) { + stairway *stway = stairway_at(u.ux, u.uy); + d_level newlevel; + + if (at_stairs && stway) + stway->u_traversed = TRUE; + + if (at_stairs && stway && stway->tolev.dnum != u.uz.dnum) { /* Taking an up dungeon branch. */ /* KMH -- Upwards branches are okay if not level 1 */ /* (Just make sure it doesn't go above depth 1) */ if (!u.uz.dnum && u.uz.dlevel == 1 && !u.uhave.amulet) done(ESCAPED); - else - goto_level(&sstairs.tolev, at_stairs, FALSE, FALSE); + else { + newlevel.dnum = stway->tolev.dnum; + newlevel.dlevel = stway->tolev.dlevel; + goto_level(&newlevel, at_stairs, FALSE, FALSE); + } } else { /* Going up a stairs or rising through the ceiling. */ - d_level newlevel; newlevel.dnum = u.uz.dnum; newlevel.dlevel = u.uz.dlevel - 1; goto_level(&newlevel, at_stairs, FALSE, FALSE); } } +/* Dwarves have "earth sense", + able to sense if something is buried under their feet */ +staticfn void +earth_sense(void) +{ + struct obj *otmp; + + if (!Race_if(PM_DWARF)) + return; + if (u.usteed || Flying || Levitation || Upolyd) + return; + if (levl[u.ux][u.uy].typ != CORR + && levl[u.ux][u.uy].typ != ROOM) + return; + + for (otmp = svl.level.buriedobjlist; otmp; otmp = otmp->nobj) + if (u_at(otmp->ox, otmp->oy)) { + You("sense something below your %s.", makeplural(body_part(FOOT))); + return; + } +} + void -u_on_newpos(x, y) -int x, y; +u_on_newpos(coordxy x, coordxy y) { if (!isok(x, y)) { /* validate location */ - void VDECL((*func), (const char *, ...)) PRINTF_F(1, 2); + void (*func)(const char *, ...) PRINTF_F_PTR(1, 2); func = (x < 0 || y < 0 || x > COLNO - 1 || y > ROWNO - 1) ? panic : impossible; @@ -1202,19 +1579,30 @@ int x, y; #ifdef CLIPPING cliparound(u.ux, u.uy); #endif + u.uundetected = 0; /* ridden steed always shares hero's location */ if (u.usteed) u.usteed->mx = u.ux, u.usteed->my = u.uy; /* when changing levels, don't leave old position set with stale values from previous level */ - if (!on_level(&u.uz, &u.uz0)) + if (!on_level(&u.uz, &u.uz0)) { u.ux0 = u.ux, u.uy0 = u.uy; + /* sets lastseentyp[u.ux][u.uy]; needed for switch_terrain() + somewhere back up the call chain */ + map_location(u.ux, u.uy, FALSE); + iflags.terrain_typ = MAX_TYPE; /* "none of the above" value */ + } else { + /* still on same level; might have come close enough to + generic object(s) to redisplay them as specific objects */ + if (!Blind && !Hallucination && !u.uswallow) + see_nearby_objects(); + } + earth_sense(); } /* place you on a random location when arriving on a level */ void -u_on_rndspot(upflag) -int upflag; +u_on_rndspot(int upflag) { int up = (upflag & 1), was_in_W_tower = (upflag & 2); @@ -1229,75 +1617,38 @@ int upflag; destination instead of its enclosing region. Note: up vs down doesn't matter in this case because both specify the same exclusion area. */ - place_lregion(dndest.nlx, dndest.nly, dndest.nhx, dndest.nhy, + place_lregion(svd.dndest.nlx, svd.dndest.nly, + svd.dndest.nhx, svd.dndest.nhy, 0, 0, 0, 0, LR_DOWNTELE, (d_level *) 0); else if (up) - place_lregion(updest.lx, updest.ly, updest.hx, updest.hy, - updest.nlx, updest.nly, updest.nhx, updest.nhy, + place_lregion(svu.updest.lx, svu.updest.ly, + svu.updest.hx, svu.updest.hy, + svu.updest.nlx, svu.updest.nly, + svu.updest.nhx, svu.updest.nhy, LR_UPTELE, (d_level *) 0); else - place_lregion(dndest.lx, dndest.ly, dndest.hx, dndest.hy, - dndest.nlx, dndest.nly, dndest.nhx, dndest.nhy, + place_lregion(svd.dndest.lx, svd.dndest.ly, + svd.dndest.hx, svd.dndest.hy, + svd.dndest.nlx, svd.dndest.nly, + svd.dndest.nhx, svd.dndest.nhy, LR_DOWNTELE, (d_level *) 0); /* might have just left solid rock and unblocked levitation */ switch_terrain(); } -/* place you on the special staircase */ -void -u_on_sstairs(upflag) -int upflag; -{ - if (sstairs.sx) - u_on_newpos(sstairs.sx, sstairs.sy); - else - u_on_rndspot(upflag); -} - -/* place you on upstairs (or special equivalent) */ -void -u_on_upstairs() -{ - if (xupstair) - u_on_newpos(xupstair, yupstair); - else - u_on_sstairs(0); /* destination upstairs implies moving down */ -} - -/* place you on dnstairs (or special equivalent) */ -void -u_on_dnstairs() -{ - if (xdnstair) - u_on_newpos(xdnstair, ydnstair); - else - u_on_sstairs(1); /* destination dnstairs implies moving up */ -} - +#endif /* !SFCTOOL */ +#ifndef SFCTOOL boolean -On_stairs(x, y) -xchar x, y; +Is_botlevel(d_level *lev) { - return (boolean) ((x == xupstair && y == yupstair) - || (x == xdnstair && y == ydnstair) - || (x == xdnladder && y == ydnladder) - || (x == xupladder && y == yupladder) - || (x == sstairs.sx && y == sstairs.sy)); + return (boolean) (lev->dlevel == svd.dungeons[lev->dnum].num_dunlevs); } boolean -Is_botlevel(lev) -d_level *lev; +Can_dig_down(d_level *lev) { - return (boolean) (lev->dlevel == dungeons[lev->dnum].num_dunlevs); -} - -boolean -Can_dig_down(lev) -d_level *lev; -{ - return (boolean) (!level.flags.hardfloor + return (boolean) (!svl.level.flags.hardfloor && !Is_botlevel(lev) && !Invocation_lev(lev)); } @@ -1308,8 +1659,7 @@ d_level *lev; * both digging and falling. */ boolean -Can_fall_thru(lev) -d_level *lev; +Can_fall_thru(d_level *lev) { return (boolean) (Can_dig_down(lev) || Is_stronghold(lev)); } @@ -1321,27 +1671,120 @@ d_level *lev; * Checks for amulets and such must be done elsewhere. */ boolean -Can_rise_up(x, y, lev) -int x, y; -d_level *lev; +Can_rise_up(coordxy x, coordxy y, d_level *lev) { + stairway *stway = stairway_find_special_dir(FALSE); + /* can't rise up from inside the top of the Wizard's tower */ /* KMH -- or in sokoban */ if (In_endgame(lev) || In_sokoban(lev) || (Is_wiz1_level(lev) && In_W_tower(x, y, lev))) return FALSE; return (boolean) (lev->dlevel > 1 - || (dungeons[lev->dnum].entry_lev == 1 + || (svd.dungeons[lev->dnum].entry_lev == 1 && ledger_no(lev) != 1 - && sstairs.sx && sstairs.up)); + && stway && stway->up)); } boolean -has_ceiling(lev) -d_level *lev; +has_ceiling(d_level *lev) { - /* [what about level 1 of the quest?] */ - return (boolean) (!Is_airlevel(lev) && !Is_waterlevel(lev)); + /* FIXME: some (most? all?) of the quest home levels are conceptually + above ground and don't have ceilings outside of their buildings + but we don't presently check for that */ + if (In_endgame(lev) && !Is_earthlevel(lev)) + return FALSE; + return TRUE; +} + +boolean +avoid_ceiling(d_level *lev) +{ + /* The quest is challenging since parts of the level + may have ceilings and other parts may not; Avoid + the ambiguity there by testing with avoid_ceiling() + and using alternative messaging that avoids the term + ceiling altogether there */ + if (In_quest(lev) || !has_ceiling(lev)) + return TRUE; + return FALSE; +} + +const char * +ceiling(coordxy x, coordxy y) +{ + struct rm *lev = &levl[x][y]; + const char *what; + + /* other room types will no longer exist when we're interested -- + * see check_special_room() + */ + if (*in_rooms(x, y, VAULT)) + what = "vault's ceiling"; + else if (*in_rooms(x, y, TEMPLE)) + what = "temple's ceiling"; + else if (*in_rooms(x, y, SHOPBASE)) + what = "shop's ceiling"; + else if (Is_waterlevel(&u.uz)) + /* water plane has no surface; its air bubbles aren't below sky */ + what = "water above"; + else if (IS_AIR(lev->typ)) + what = "sky"; + else if (Is_firelevel(&u.uz)) + what = "flames above"; + else if (In_quest(&u.uz)) + /* just in case; try to avoid in caller if you can */ + what = "expanse above"; + else if (Underwater) + what = "water's surface"; + else if ((IS_ROOM(lev->typ) && !Is_earthlevel(&u.uz)) + || IS_WALL(lev->typ) || IS_DOOR(lev->typ) || lev->typ == SDOOR) + what = "ceiling"; + else + what = "rock cavern"; + + return what; +} + +const char * +surface(coordxy x, coordxy y) +{ + struct rm *lev = &levl[x][y]; + int levtyp = SURFACE_AT(x, y); + + if (u_at(x, y) && u.uswallow && is_animal(u.ustuck->data)) + /* 'husk' is iffy but maw is wrong for 't' class */ + return digests(u.ustuck->data) ? "maw" + : enfolds(u.ustuck->data) ? "husk" + : "nonesuch"; /* can't happen (fingers crossed...) */ + else if (IS_AIR(levtyp)) + return Is_waterlevel(&u.uz) ? "air bubble" + : (levtyp == CLOUD) ? "cloud" : "air"; + else if (is_pool(x, y)) + return (Underwater && !Is_waterlevel(&u.uz)) + ? "bottom" : hliquid("water"); + else if (is_ice(x, y)) + return "ice"; + else if (is_lava(x, y)) + return hliquid("lava"); + else if (lev->typ == DRAWBRIDGE_DOWN) + return "bridge"; + else if (IS_ALTAR(levtyp)) + return "altar"; + else if (IS_GRAVE(levtyp)) + return "headstone"; + else if (IS_FOUNTAIN(levtyp)) + return "fountain"; + else if (On_stairs(x, y)) + return "stairs"; + else if (IS_WALL(levtyp) || levtyp == SDOOR) + return "wall"; /* 'surface' during Passes_walls */ + else if (IS_DOOR(levtyp)) + return "doorway"; /* even for closed door */ + else if (IS_ROOM(levtyp) && !Is_earthlevel(&u.uz)) + return "floor"; + else + return "ground"; } /* @@ -1356,20 +1799,18 @@ d_level *lev; * in dungeons that build up is confined within them. */ void -get_level(newlevel, levnum) -d_level *newlevel; -int levnum; +get_level(d_level *newlevel, int levnum) { branch *br; - xchar dgn = u.uz.dnum; + xint16 dgn = u.uz.dnum; if (levnum <= 0) { /* can only currently happen in endgame */ levnum = u.uz.dlevel; - } else if (levnum - > dungeons[dgn].depth_start + dungeons[dgn].num_dunlevs - 1) { + } else if (levnum > (svd.dungeons[dgn].depth_start + + svd.dungeons[dgn].num_dunlevs - 1)) { /* beyond end of dungeon, jump to last level */ - levnum = dungeons[dgn].num_dunlevs; + levnum = svd.dungeons[dgn].num_dunlevs; } else { /* The desired level is in this dungeon or a "higher" one. */ @@ -1377,7 +1818,7 @@ int levnum; * Branch up the tree until we reach a dungeon that contains the * levnum. */ - if (levnum < dungeons[dgn].depth_start) { + if (levnum < svd.dungeons[dgn].depth_start) { do { /* * Find the parent dungeon of this dungeon. @@ -1385,18 +1826,18 @@ int levnum; * This assumes that end2 is always the "child" and it is * unique. */ - for (br = branches; br; br = br->next) + for (br = svb.branches; br; br = br->next) if (br->end2.dnum == dgn) break; if (!br) panic("get_level: can't find parent dungeon"); dgn = br->end1.dnum; - } while (levnum < dungeons[dgn].depth_start); + } while (levnum < svd.dungeons[dgn].depth_start); } /* We're within the same dungeon; calculate the level. */ - levnum = levnum - dungeons[dgn].depth_start + 1; + levnum = levnum - svd.dungeons[dgn].depth_start + 1; } newlevel->dnum = dgn; @@ -1405,16 +1846,14 @@ int levnum; /* are you in the quest dungeon? */ boolean -In_quest(lev) -d_level *lev; +In_quest(d_level *lev) { return (boolean) (lev->dnum == quest_dnum); } /* are you in the mines dungeon? */ boolean -In_mines(lev) -d_level *lev; +In_mines(d_level *lev) { return (boolean) (lev->dnum == mines_dnum); } @@ -1428,16 +1867,15 @@ d_level *lev; * + Field end2 is the "child" dungeon. */ branch * -dungeon_branch(s) -const char *s; +dungeon_branch(const char *s) { branch *br; - xchar dnum; + xint16 dnum; dnum = dname_to_dnum(s); /* Find the branch that connects to dungeon i's branch. */ - for (br = branches; br; br = br->next) + for (br = svb.branches; br; br = br->next) if (br->end2.dnum == dnum) break; @@ -1456,8 +1894,7 @@ const char *s; * Assumes that end1 is always the "parent". */ boolean -at_dgn_entrance(s) -const char *s; +at_dgn_entrance(const char *s) { branch *br; @@ -1467,16 +1904,14 @@ const char *s; /* is `lev' part of Vlad's tower? */ boolean -In_V_tower(lev) -d_level *lev; +In_V_tower(d_level *lev) { return (boolean) (lev->dnum == tower_dnum); } /* is `lev' a level containing the Wizard's tower? */ boolean -On_W_tower_level(lev) -d_level *lev; +On_W_tower_level(d_level *lev) { return (boolean) (Is_wiz1_level(lev) || Is_wiz2_level(lev) @@ -1485,37 +1920,33 @@ d_level *lev; /* is of `lev' inside the Wizard's tower? */ boolean -In_W_tower(x, y, lev) -int x, y; -d_level *lev; +In_W_tower(coordxy x, coordxy y, d_level *lev) { if (!On_W_tower_level(lev)) return FALSE; + if (!svd.dndest.nlx) { + impossible("No boundary for Wizard's Tower?"); + return FALSE; + } /* * Both of the exclusion regions for arriving via level teleport * (from above or below) define the tower's boundary. - * assert( updest.nIJ == dndest.nIJ for I={l|h},J={x|y} ); + * assert( svu.updest.nIJ == svd.dndest.nIJ for I={l|h},J={x|y} ); */ - if (dndest.nlx > 0) - return (boolean) within_bounded_area(x, y, dndest.nlx, dndest.nly, - dndest.nhx, dndest.nhy); - else - impossible("No boundary for Wizard's Tower?"); - return FALSE; + return (boolean) within_bounded_area(x, y, svd.dndest.nlx, svd.dndest.nly, + svd.dndest.nhx, svd.dndest.nhy); } /* are you in one of the Hell levels? */ boolean -In_hell(lev) -d_level *lev; +In_hell(d_level *lev) { - return (boolean) (dungeons[lev->dnum].flags.hellish); + return (boolean) (svd.dungeons[lev->dnum].flags.hellish); } /* sets *lev to be the gateway to Gehennom... */ void -find_hell(lev) -d_level *lev; +find_hell(d_level *lev) { lev->dnum = valley_level.dnum; lev->dlevel = 1; @@ -1523,8 +1954,7 @@ d_level *lev; /* go directly to hell... */ void -goto_hell(at_stairs, falling) -boolean at_stairs, falling; +goto_hell(boolean at_stairs, boolean falling) { d_level lev; @@ -1532,10 +1962,20 @@ boolean at_stairs, falling; goto_level(&lev, at_stairs, falling, FALSE); } +/* is 'lev' the only level in its branch? affects level teleporters */ +boolean +single_level_branch(d_level *lev) +{ + /* + * TODO: this should be generalized instead of assuming that + * Fort Ludios is the only single level branch in the dungeon. + */ + return Is_knox(lev); +} + /* equivalent to dest = source */ void -assign_level(dest, src) -d_level *dest, *src; +assign_level(d_level *dest, d_level *src) { dest->dnum = src->dnum; dest->dlevel = src->dlevel; @@ -1543,9 +1983,7 @@ d_level *dest, *src; /* dest = src + rn1(range) */ void -assign_rnd_level(dest, src, range) -d_level *dest, *src; -int range; +assign_rnd_level(d_level *dest, d_level *src, int range) { dest->dnum = src->dnum; dest->dlevel = src->dlevel + ((range > 0) ? rnd(range) : -rnd(-range)); @@ -1556,9 +1994,9 @@ int range; dest->dlevel = 1; } -int -induced_align(pct) -int pct; +/* return an alignment mask */ +unsigned int +induced_align(int pct) { s_level *lev = Is_special(&u.uz); aligntyp al; @@ -1567,29 +2005,28 @@ int pct; if (rn2(100) < pct) return lev->flags.align; - if (dungeons[u.uz.dnum].flags.align) + if (svd.dungeons[u.uz.dnum].flags.align) if (rn2(100) < pct) - return dungeons[u.uz.dnum].flags.align; + return svd.dungeons[u.uz.dnum].flags.align; al = rn2(3) - 1; return Align2amask(al); } boolean -Invocation_lev(lev) -d_level *lev; +Invocation_lev(d_level *lev) { return (boolean) (In_hell(lev) - && lev->dlevel == dungeons[lev->dnum].num_dunlevs - 1); + && lev->dlevel == svd.dungeons[lev->dnum].num_dunlevs - 1); } /* use instead of depth() wherever a degree of difficulty is made * dependent on the location in the dungeon (eg. monster creation). */ -xchar -level_difficulty() +xint16 +level_difficulty(void) { - int res; + xint16 res; if (In_endgame(&u.uz)) { res = depth(&sanctum_level) + u.ulevel / 2; @@ -1603,7 +2040,7 @@ level_difficulty() they were easier; adjust for the extra effort involved in going down to the entrance and then up to the location */ if (builds_up(&u.uz)) - res += 2 * (dungeons[u.uz.dnum].entry_lev - u.uz.dlevel + 1); + res += 2 * (svd.dungeons[u.uz.dnum].entry_lev - u.uz.dlevel + 1); /* * 'Proof' by example: suppose the entrance to sokoban is * on dungeon level 9, leading up to bottom sokoban level @@ -1627,7 +2064,7 @@ level_difficulty() * below rather than stairs 1 level beneath the entry level. */ else if (On_W_tower_level(&u.uz) && In_W_tower(some_X, some_Y, &u.uz)) - res += (fakewiz1.dlev - u.uz.dlev); + res += (fakewiz1.dlevel - u.uz.dlevel); /* * Handling this properly would need more information here: * an inside/outside flag, or coordinates to calculate it. @@ -1640,18 +2077,28 @@ level_difficulty() */ #endif /*0*/ } - return (xchar) res; + /* ring of aggravate monster */ + if (EAggravate_monster) + res = res > 25 ? 50 : res * 2; + return res; } +/* within same branch, or else main dungeon <-> gehennom */ +#define dlev_in_current_branch(dlev) \ + (dlev.dnum == u.uz.dnum \ + || (u.uz.dnum == valley_level.dnum \ + && dlev.dnum == medusa_level.dnum) \ + || (u.uz.dnum == medusa_level.dnum \ + && dlev.dnum == valley_level.dnum)) + /* Take one word and try to match it to a level. * Recognized levels are as shown by print_dungeon(). */ schar -lev_by_name(nam) -const char *nam; +lev_by_name(const char *nam) { schar lev = 0; - s_level *slev = (s_level *)0; + s_level *slev = (s_level *) 0; d_level dlev; const char *p; int idx, idxtoo; @@ -1678,6 +2125,9 @@ const char *nam; nam = " to Vlad's tower"; /* branch to... */ else nam = "valley"; + } else if (!strcmpi(nam, "delphi")) { + /* Oracle says "welcome to Delphi" so recognize that name too */ + nam = "oracle"; } if ((slev = find_level(nam)) != 0) @@ -1686,16 +2136,11 @@ const char *nam; if (mseen || slev) { idx = ledger_no(&dlev); - if ((dlev.dnum == u.uz.dnum - /* within same branch, or else main dungeon <-> gehennom */ - || (u.uz.dnum == valley_level.dnum - && dlev.dnum == medusa_level.dnum) - || (u.uz.dnum == medusa_level.dnum - && dlev.dnum == valley_level.dnum)) - && (/* either wizard mode or else seen and not forgotten */ - wizard - || (level_info[idx].flags & (FORGOTTEN | VISITED)) - == VISITED)) { + if (dlev_in_current_branch(dlev) + /* either wizard mode or else seen and not forgotten; + note: used to be '(flags & (FORGOTTEN|VISITED)) == VISITED' + back when amnesia could cause levels to be forgotten */ + && (wizard || (svl.level_info[idx].flags & VISITED) == VISITED)) { lev = depth(&dlev); } } else { /* not a specific level; try branch names */ @@ -1707,42 +2152,42 @@ const char *nam; if (idx >= 0) { idxtoo = (idx >> 8) & 0x00FF; idx &= 0x00FF; - if (/* either wizard mode, or else _both_ sides of branch seen */ - wizard - || ((level_info[idx].flags & (FORGOTTEN | VISITED)) == VISITED - && (level_info[idxtoo].flags & (FORGOTTEN | VISITED)) - == VISITED)) { + /* either wizard mode, or else _both_ sides of branch seen; */ + /* (flags & VISITED)==VISITED: see comment about amnesia above */ + if (wizard || (((svl.level_info[idx].flags & VISITED) == VISITED) + && ((svl.level_info[idxtoo].flags & VISITED) + == VISITED))) { if (ledger_to_dnum(idxtoo) == u.uz.dnum) idx = idxtoo; dlev.dnum = ledger_to_dnum(idx); dlev.dlevel = ledger_to_dlev(idx); - lev = depth(&dlev); + if (dlev_in_current_branch(dlev)) + lev = depth(&dlev); } } } return lev; } -STATIC_OVL boolean -unplaced_floater(dptr) -struct dungeon *dptr; +#undef dlev_in_current_branch + +staticfn boolean +unplaced_floater(struct dungeon *dptr) { branch *br; - int idx = (int) (dptr - dungeons); + int idx = (int) (dptr - svd.dungeons); /* if other floating branches are added, this will need to change */ if (idx != knox_level.dnum) return FALSE; - for (br = branches; br; br = br->next) - if (br->end1.dnum == n_dgns && br->end2.dnum == idx) + for (br = svb.branches; br; br = br->next) + if (br->end1.dnum == svn.n_dgns && br->end2.dnum == idx) return TRUE; return FALSE; } -STATIC_OVL boolean -unreachable_level(lvl_p, unplaced) -d_level *lvl_p; -boolean unplaced; +staticfn boolean +unreachable_level(d_level *lvl_p, boolean unplaced) { s_level *dummy; @@ -1755,22 +2200,23 @@ boolean unplaced; return FALSE; } -static void -tport_menu(win, entry, lchoices, lvl_p, unreachable) -winid win; -char *entry; -struct lchoice *lchoices; -d_level *lvl_p; -boolean unreachable; +staticfn void +tport_menu( + winid win, + char *entry, + struct lchoice *lchoices, + d_level *lvl_p, + boolean cannotreach) { char tmpbuf[BUFSZ]; anything any; + int clr = NO_COLOR; lchoices->lev[lchoices->idx] = lvl_p->dlevel; lchoices->dgn[lchoices->idx] = lvl_p->dnum; lchoices->playerlev[lchoices->idx] = depth(lvl_p); - any = zeroany; - if (unreachable) { + any = cg.zeroany; + if (cannotreach) { /* not selectable, but still consumes next menuletter; prepend padding in place of missing menu selector */ Sprintf(tmpbuf, " %s", entry); @@ -1778,8 +2224,8 @@ boolean unreachable; } else { any.a_int = lchoices->idx + 1; } - add_menu(win, NO_GLYPH, &any, lchoices->menuletter, 0, ATR_NONE, entry, - MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, lchoices->menuletter, 0, + ATR_NONE, clr, entry, MENU_ITEMFLAGS_NONE); /* this assumes there are at most 52 interesting levels */ if (lchoices->menuletter == 'z') lchoices->menuletter = 'A'; @@ -1790,9 +2236,8 @@ boolean unreachable; } /* Convert a branch type to a string usable by print_dungeon(). */ -STATIC_OVL const char * -br_string(type) -int type; +staticfn const char * +br_string(int type) { switch (type) { case BR_PORTAL: @@ -1807,34 +2252,30 @@ int type; return " (unknown)"; } -STATIC_OVL char -chr_u_on_lvl(dlev) -d_level *dlev; +staticfn char +chr_u_on_lvl(d_level *dlev) { return u.uz.dnum == dlev->dnum && u.uz.dlevel == dlev->dlevel ? '*' : ' '; } /* Print all child branches between the lower and upper bounds. */ -STATIC_OVL void -print_branch(win, dnum, lower_bound, upper_bound, bymenu, lchoices_p) -winid win; -int dnum; -int lower_bound; -int upper_bound; -boolean bymenu; -struct lchoice *lchoices_p; +staticfn void +print_branch( + winid win, + int dnum, int lower_bound, int upper_bound, + boolean bymenu, struct lchoice *lchoices_p) { branch *br; char buf[BUFSZ]; /* This assumes that end1 is the "parent". */ - for (br = branches; br; br = br->next) { + for (br = svb.branches; br; br = br->next) { if (br->end1.dnum == dnum && lower_bound < br->end1.dlevel && br->end1.dlevel <= upper_bound) { Sprintf(buf, "%c %s to %s: %d", bymenu ? chr_u_on_lvl(&br->end1) : ' ', br_string(br->type), - dungeons[br->end2.dnum].dname, depth(&br->end1)); + svd.dungeons[br->end2.dnum].dname, depth(&br->end1)); if (bymenu) tport_menu(win, buf, lchoices_p, &br->end1, unreachable_level(&br->end1, FALSE)); @@ -1846,10 +2287,7 @@ struct lchoice *lchoices_p; /* Print available dungeon information. */ schar -print_dungeon(bymenu, rlev, rdgn) -boolean bymenu; -schar *rlev; -xchar *rdgn; +print_dungeon(boolean bymenu, schar *rlev, xint16 *rdgn) { int i, last_level, nlev; char buf[BUFSZ]; @@ -1858,27 +2296,28 @@ xchar *rdgn; s_level *slev; dungeon *dptr; branch *br; - anything any; struct lchoice lchoices; winid win = create_nhwindow(NHW_MENU); if (bymenu) { - start_menu(win); + start_menu(win, MENU_BEHAVE_STANDARD); lchoices.idx = 0; lchoices.menuletter = 'a'; } - for (i = 0, dptr = dungeons; i < n_dgns; i++, dptr++) { + for (i = 0, dptr = svd.dungeons; i < svn.n_dgns; i++, dptr++) { if (bymenu && In_endgame(&u.uz) && i != astral_level.dnum) continue; unplaced = unplaced_floater(dptr); descr = unplaced ? "depth" : "level"; nlev = dptr->num_dunlevs; if (nlev > 1) - Sprintf(buf, "%s: %s %d to %d", dptr->dname, makeplural(descr), - dptr->depth_start, dptr->depth_start + nlev - 1); + Snprintf(buf, sizeof buf, "%s: %s %d to %d", dptr->dname, + makeplural(descr), dptr->depth_start, + dptr->depth_start + nlev - 1); else - Sprintf(buf, "%s: %s %d", dptr->dname, descr, dptr->depth_start); + Snprintf(buf, sizeof buf, "%s: %s %d", dptr->dname, + descr, dptr->depth_start); /* Most entrances are uninteresting. */ if (dptr->entry_lev != 1) { @@ -1889,9 +2328,7 @@ xchar *rdgn; dptr->depth_start + dptr->entry_lev - 1); } if (bymenu) { - any = zeroany; - add_menu(win, NO_GLYPH, &any, 0, 0, iflags.menu_headings, buf, - MENU_UNSELECTED); + add_menu_heading(win, buf); } else putstr(win, 0, buf); @@ -1899,7 +2336,7 @@ xchar *rdgn; * Circle through the special levels to find levels that are in * this dungeon. */ - for (slev = sp_levchn, last_level = 0; slev; slev = slev->next) { + for (slev = svs.sp_levchn, last_level = 0; slev; slev = slev->next) { if (slev->dlevel.dnum != i) continue; @@ -1911,7 +2348,7 @@ xchar *rdgn; chr_u_on_lvl(&slev->dlevel), slev->proto, depth(&slev->dlevel)); if (Is_stronghold(&slev->dlevel)) - Sprintf(eos(buf), " (tune %s)", tune); + Sprintf(eos(buf), " (tune %s)", svt.tune); if (bymenu) tport_menu(win, buf, &lchoices, &slev->dlevel, unreachable_level(&slev->dlevel, unplaced)); @@ -1945,15 +2382,15 @@ xchar *rdgn; } /* Print out floating branches (if any). */ - for (first = TRUE, br = branches; br; br = br->next) { - if (br->end1.dnum == n_dgns) { + for (first = TRUE, br = svb.branches; br; br = br->next) { + if (br->end1.dnum == svn.n_dgns) { if (first) { putstr(win, 0, ""); putstr(win, 0, "Floating branches"); first = FALSE; } Sprintf(buf, " %s to %s", br_string(br->type), - dungeons[br->end2.dnum].dname); + svd.dungeons[br->end2.dnum].dname); putstr(win, 0, buf); } } @@ -1962,7 +2399,7 @@ xchar *rdgn; if (Invocation_lev(&u.uz)) { putstr(win, 0, ""); Sprintf(buf, "Invocation position @ (%d,%d), hero @ (%d,%d)", - inv_pos.x, inv_pos.y, u.ux, u.uy); + svi.inv_pos.x, svi.inv_pos.y, u.ux, u.uy); putstr(win, 0, buf); } else { struct trap *trap; @@ -1973,7 +2410,7 @@ xchar *rdgn; dungeon matched with one in the corresponding branch), the elemental planes have singletons (connection to next plane) */ *buf = '\0'; - for (trap = ftrap; trap; trap = trap->ntrap) + for (trap = gf.ftrap; trap; trap = trap->ntrap) if (trap->ttyp == MAGIC_PORTAL) break; @@ -2006,9 +2443,7 @@ xchar *rdgn; * teleport or via the Eye. */ void -recbranch_mapseen(source, dest) -d_level *source; -d_level *dest; +recbranch_mapseen(d_level *source, d_level *dest) { mapseen *mptr; branch *br; @@ -2018,7 +2453,7 @@ d_level *dest; return; /* we only care about forward branches */ - for (br = branches; br; br = br->next) { + for (br = svb.branches; br; br = br->next) { if (on_level(source, &br->end1) && on_level(dest, &br->end2)) break; if (on_level(source, &br->end2) && on_level(dest, &br->end1)) @@ -2039,9 +2474,8 @@ d_level *dest; } } -char * -get_annotation(lev) -d_level *lev; +staticfn char * +get_annotation(d_level *lev) { mapseen *mptr; @@ -2050,15 +2484,26 @@ d_level *lev; return NULL; } -/* #annotate command - add a custom name to the current level */ -int -donamelevel() +/* print the annotation for the current level, if it exists */ +void +print_level_annotation(void) +{ + const char *annotation; + + if ((annotation = get_annotation(&u.uz)) != 0) + You("remember this level as %s.", annotation); +} + +/* ask user to annotate level lev. + if lev is NULL, uses current level. */ +staticfn void +query_annotation(d_level *lev) { mapseen *mptr; char nbuf[BUFSZ]; /* Buffer for response */ - if (!(mptr = find_mapseen(&u.uz))) - return 0; + if (!(mptr = find_mapseen(lev ? lev : &u.uz))) + return; nbuf[0] = '\0'; #ifdef EDIT_GETLIN @@ -2075,12 +2520,34 @@ donamelevel() getlin(tmpbuf, nbuf); } else #endif - getlin("What do you want to call this dungeon level?", nbuf); + { + char qbuf[QBUFSZ], lbuf[QBUFSZ]; /* level description */ + + if (!lev || on_level(&u.uz, lev)) { + Strcpy(lbuf, "this dungeon level"); + } else { + int dflgs = (lev->dnum == u.uz.dnum) ? 0 : 2; + d_level save_uz = u.uz; + + u.uz = *lev; + (void) describe_level(lbuf, dflgs); + u.uz = save_uz; + + (void) strsubst(lbuf, "Dlvl:", "level "); + /* even though we've told describe_level() not to append + a trailing space (by not including '1' in dflgs), the + level number is formatted with %-2d so single digit + values will end up with one anyway; remove it */ + (void) trimspaces(lbuf); + } + Snprintf(qbuf, sizeof qbuf, "What do you want to call %s?", lbuf); + getlin(qbuf, nbuf); + } /* empty input or ESC means don't add or change annotation; space-only means discard current annotation without adding new one */ if (!*nbuf || *nbuf == '\033') - return 0; + return; /* strip leading and trailing spaces, compress out consecutive spaces */ (void) mungspaces(nbuf); @@ -2094,172 +2561,215 @@ donamelevel() empty string after mungspaces() above) */ if (*nbuf && strcmp(nbuf, " ")) { mptr->custom = dupstr(nbuf); - mptr->custom_lth = strlen(mptr->custom); + /* _lth field does not include trailing '\0' in the count */ + mptr->custom_lth = (unsigned) strlen(mptr->custom); } - return 0; } -/* find the particular mapseen object in the chain; may return null */ -STATIC_OVL mapseen * -find_mapseen(lev) -d_level *lev; +/* #annotate command - add a custom name to the current level */ +int +donamelevel(void) { - mapseen *mptr; + if (iflags.menu_requested) + return dooverview(); + query_annotation((d_level *) 0); + return ECMD_OK; +} +#endif /* !SFCTOOL */ - for (mptr = mapseenchn; mptr; mptr = mptr->next) - if (on_level(&(mptr->lev), lev)) - break; +/* exclusion zones */ +void +free_exclusions(void) +{ + struct exclusion_zone *ez = sve.exclusion_zones; - return mptr; + while (ez) { + struct exclusion_zone *nxtez = ez->next; + + free(ez); + ez = nxtez; + } + sve.exclusion_zones = (struct exclusion_zone *) 0; +} + +void +save_exclusions(NHFILE *nhfp) +{ + struct exclusion_zone *ez; + int nez; + + for (nez = 0, ez = sve.exclusion_zones; ez; ez = ez->next, ++nez) + ; + + if (update_file(nhfp)) { + Sfo_int(nhfp, &nez, "exclusion_count"); + for (ez = sve.exclusion_zones; ez; ez = ez->next) { + Sfo_xint16(nhfp, &ez->zonetype, "exclusion-zonetype"); + Sfo_coordxy(nhfp, &ez->lx, "exclusion-lx"); + Sfo_coordxy(nhfp, &ez->ly, "exclusion-ly"); + Sfo_coordxy(nhfp, &ez->hx, "exclusion-hx"); + Sfo_coordxy(nhfp, &ez->hy, "exclusion-hy"); + } + } +} + +void +load_exclusions(NHFILE *nhfp) +{ + struct exclusion_zone *ez; + int nez = 0; + + Sfi_int(nhfp, &nez, "exclusion_count"); + + while (nez-- > 0) { + ez = (struct exclusion_zone *) alloc(sizeof *ez); + Sfi_xint16(nhfp, &ez->zonetype, "exclusion-zonetype"); + Sfi_coordxy(nhfp, &ez->lx, "exclusion-lx"); + Sfi_coordxy(nhfp, &ez->ly, "exclusion-ly"); + Sfi_coordxy(nhfp, &ez->hx, "exclusion-hx"); + Sfi_coordxy(nhfp, &ez->hy, "exclusion-hy"); + ez->next = sve.exclusion_zones; + sve.exclusion_zones = ez; + } } -STATIC_OVL mapseen * -find_mapseen_by_str(s) -const char *s; +#ifndef SFCTOOL + +/* find the particular mapseen object in the chain; may return null */ +staticfn mapseen * +find_mapseen(d_level *lev) { mapseen *mptr; - for (mptr = mapseenchn; mptr; mptr = mptr->next) - if (mptr->custom && !strcmpi(s, mptr->custom)) + for (mptr = svm.mapseenchn; mptr; mptr = mptr->next) + if (on_level(&(mptr->lev), lev)) break; return mptr; } - -void -forget_mapseen(ledger_num) -int ledger_num; +staticfn mapseen * +find_mapseen_by_str(const char *s) { mapseen *mptr; - struct cemetery *bp; - for (mptr = mapseenchn; mptr; mptr = mptr->next) - if (dungeons[mptr->lev.dnum].ledger_start + mptr->lev.dlevel - == ledger_num) + for (mptr = svm.mapseenchn; mptr; mptr = mptr->next) + if (mptr->custom && !strcmpi(s, mptr->custom)) break; - /* if not found, then nothing to forget */ - if (mptr) { - mptr->flags.forgot = 1; - mptr->br = (branch *) 0; - - /* custom names are erased, not just forgotten until revisited */ - if (mptr->custom) { - mptr->custom_lth = 0; - free((genericptr_t) mptr->custom); - mptr->custom = (char *) 0; - } - (void) memset((genericptr_t) mptr->msrooms, 0, sizeof mptr->msrooms); - for (bp = mptr->final_resting_place; bp; bp = bp->next) - bp->bonesknown = FALSE; - } + return mptr; } + void -rm_mapseen(ledger_num) -int ledger_num; +rm_mapseen(int ledger_num) { - mapseen *mptr, *mprev = (mapseen *)0; + mapseen *mptr, *mprev = (mapseen *) 0; struct cemetery *bp, *bpnext; - for (mptr = mapseenchn; mptr; mprev = mptr, mptr = mptr->next) - if (dungeons[mptr->lev.dnum].ledger_start + mptr->lev.dlevel + for (mptr = svm.mapseenchn; mptr; mprev = mptr, mptr = mptr->next) + if (svd.dungeons[mptr->lev.dnum].ledger_start + mptr->lev.dlevel == ledger_num) break; - if (!mptr) return; if (mptr->custom) - free((genericptr_t) mptr->custom); + free((genericptr_t) mptr->custom), mptr->custom = (char *) NULL; - bp = mptr->final_resting_place; - while (bp) { + bpnext = mptr->final_resting_place; + while ((bp = bpnext) != NULL) { bpnext = bp->next; - free(bp); - bp = bpnext; + free((genericptr_t) bp); } if (mprev) { mprev->next = mptr->next; - free(mptr); } else { - mapseenchn = mptr->next; - free(mptr); + svm.mapseenchn = mptr->next; } + free(mptr); } -STATIC_OVL void -save_mapseen(fd, mptr) -int fd; -mapseen *mptr; +staticfn void +save_mapseen(NHFILE *nhfp, mapseen *mptr) { branch *curr; - int brindx; + int i, brindx; - for (brindx = 0, curr = branches; curr; curr = curr->next, ++brindx) + for (brindx = 0, curr = svb.branches; curr; curr = curr->next, ++brindx) if (curr == mptr->br) break; - bwrite(fd, (genericptr_t) &brindx, sizeof brindx); + Sfo_int(nhfp, &brindx, "mapseen-branch_index"); + Sfo_d_level(nhfp, &mptr->lev, "mapseen-d_level"); + Sfo_mapseen_feat(nhfp, &mptr->feat, "mapseen-feat"); + Sfo_mapseen_flags(nhfp, &mptr->flags, "mapseen-flags"); + Sfo_unsigned(nhfp, &mptr->custom_lth, "mapseen-custom_lth"); - bwrite(fd, (genericptr_t) &mptr->lev, sizeof mptr->lev); - bwrite(fd, (genericptr_t) &mptr->feat, sizeof mptr->feat); - bwrite(fd, (genericptr_t) &mptr->flags, sizeof mptr->flags); - bwrite(fd, (genericptr_t) &mptr->custom_lth, sizeof mptr->custom_lth); - if (mptr->custom_lth) - bwrite(fd, (genericptr_t) mptr->custom, mptr->custom_lth); - bwrite(fd, (genericptr_t) mptr->msrooms, sizeof mptr->msrooms); - savecemetery(fd, WRITE_SAVE, &mptr->final_resting_place); + if (mptr->custom_lth) { + Sfo_char(nhfp, mptr->custom, "mapseen-custom", + (int) mptr->custom_lth); + } + for (i = 0; i < ((MAXNROFROOMS + 1) * 2); ++i) { + Sfo_mapseen_rooms(nhfp, &mptr->msrooms[i], "mapseen-msrooms"); + } + savecemetery(nhfp, &mptr->final_resting_place); } +#endif /* !SFCTOOL */ -STATIC_OVL mapseen * -load_mapseen(fd) -int fd; +staticfn mapseen * +load_mapseen(NHFILE *nhfp) { - int branchnum, brindx; + int i, branchnum = 0, brindx; mapseen *load; branch *curr; load = (mapseen *) alloc(sizeof *load); - mread(fd, (genericptr_t) &branchnum, sizeof branchnum); - for (brindx = 0, curr = branches; curr; curr = curr->next, ++brindx) + Sfi_int(nhfp, &branchnum, "mapseen-branch_index"); + for (brindx = 0, curr = svb.branches; curr; curr = curr->next, ++brindx) if (brindx == branchnum) break; load->br = curr; - mread(fd, (genericptr_t) &load->lev, sizeof load->lev); - mread(fd, (genericptr_t) &load->feat, sizeof load->feat); - mread(fd, (genericptr_t) &load->flags, sizeof load->flags); - mread(fd, (genericptr_t) &load->custom_lth, sizeof load->custom_lth); + Sfi_d_level(nhfp, &load->lev, "mapseen-d_level"); + Sfi_mapseen_feat(nhfp, &load->feat, "mapseen-feat"); + Sfi_mapseen_flags(nhfp, &load->flags, "mapseen-flags"); + Sfi_unsigned(nhfp, &load->custom_lth, "mapseen-custom_lth"); + if (load->custom_lth) { /* length doesn't include terminator (which isn't saved & restored) */ load->custom = (char *) alloc(load->custom_lth + 1); - mread(fd, (genericptr_t) load->custom, load->custom_lth); + Sfi_char(nhfp, load->custom, "mapseen-custom", + (int) load->custom_lth); load->custom[load->custom_lth] = '\0'; - } else + } else { load->custom = 0; - mread(fd, (genericptr_t) load->msrooms, sizeof load->msrooms); - restcemetery(fd, &load->final_resting_place); - + } + for (i = 0; i < ((MAXNROFROOMS + 1) * 2); ++i) { + Sfi_mapseen_rooms(nhfp, &load->msrooms[i], "mapseen-msrooms"); + } + restcemetery(nhfp, &load->final_resting_place); return load; } +#ifndef SFCTOOL + +DISABLE_WARNING_FORMAT_NONLITERAL -/* to support '#stats' wizard-mode command */ +/* for '#stats' wizard-mode command, to show memory used for #overview data */ void -overview_stats(win, statsfmt, total_count, total_size) -winid win; -const char *statsfmt; -long *total_count, *total_size; +overview_stats( + winid win, /* output window */ + const char *statsfmt, /* format */ + long *total_count, long *total_size) /* args for the format */ { char buf[BUFSZ], hdrbuf[QBUFSZ]; long ocount, osize, bcount, bsize, acount, asize; struct cemetery *ce; - mapseen *mptr = find_mapseen(&u.uz); + mapseen *mptr; ocount = bcount = acount = osize = bsize = asize = 0L; - for (mptr = mapseenchn; mptr; mptr = mptr->next) { + for (mptr = svm.mapseenchn; mptr; mptr = mptr->next) { ++ocount; osize += (long) sizeof *mptr; for (ce = mptr->final_resting_place; ce; ce = ce->next) { @@ -2290,22 +2800,23 @@ long *total_count, *total_size; *total_size += osize + bsize + asize; } +RESTORE_WARNING_FORMAT_NONLITERAL + /* Remove all mapseen objects for a particular dnum. * Useful during quest expulsion to remove quest levels. - * [No longer deleted, just marked as unreachable. #overview will + * [No longer deleted, just marked as notreachable. #overview will * ignore such levels, end of game disclosure will include them.] */ void -remdun_mapseen(dnum) -int dnum; +remdun_mapseen(int dnum) { mapseen *mptr, **mptraddr; - mptraddr = &mapseenchn; + mptraddr = &svm.mapseenchn; while ((mptr = *mptraddr) != 0) { if (mptr->lev.dnum == dnum) { #if 1 /* use this... */ - mptr->flags.unreachable = 1; + mptr->flags.notreachable = 1; } #else /* old deletion code */ *mptraddr = mptr->next; @@ -2321,8 +2832,7 @@ int dnum; } void -init_mapseen(lev) -d_level *lev; +init_mapseen(d_level *lev) { /* Create a level and insert in "sorted" order. This is an insertion * sort first by dungeon (in order of discovery) and then by level number. @@ -2335,22 +2845,24 @@ d_level *lev; explicitly initialize pointers to null */ init->next = 0, init->br = 0, init->custom = 0; init->final_resting_place = 0; - /* lastseentyp[][] is reused for each level, so get rid of + /* svl.lastseentyp[][] is reused for each level, so get rid of previous level's data */ - (void) memset((genericptr_t) lastseentyp, 0, sizeof lastseentyp); + (void) memset((genericptr_t) svl.lastseentyp, 0, sizeof svl.lastseentyp); init->lev.dnum = lev->dnum; init->lev.dlevel = lev->dlevel; /* walk until we get to the place where we should insert init */ - for (mptr = mapseenchn, prev = 0; mptr; prev = mptr, mptr = mptr->next) + for (mptr = svm.mapseenchn, prev = 0; mptr; + prev = mptr, mptr = mptr->next) { if (mptr->lev.dnum > init->lev.dnum || (mptr->lev.dnum == init->lev.dnum && mptr->lev.dlevel > init->lev.dlevel)) break; + } if (!prev) { - init->next = mapseenchn; - mapseenchn = init; + init->next = svm.mapseenchn; + svm.mapseenchn = init; } else { mptr = prev->next; prev->next = init; @@ -2358,23 +2870,32 @@ d_level *lev; } } -#define INTEREST(feat) \ - ((feat).nfount || (feat).nsink || (feat).nthrone || (feat).naltar \ +#define OF_INTEREST(feat) \ + ((feat).nfount || (feat).nsink || (feat).nthrone || (feat).naltar \ || (feat).ngrave || (feat).ntree || (feat).nshop || (feat).ntemple) /* || (feat).water || (feat).ice || (feat).lava */ /* returns true if this level has something interesting to print out */ -STATIC_OVL boolean -interest_mapseen(mptr) -mapseen *mptr; +staticfn boolean +interest_mapseen(mapseen *mptr) { if (on_level(&u.uz, &mptr->lev)) return TRUE; - if (mptr->flags.unreachable || mptr->flags.forgot) + if (mptr->flags.notreachable || mptr->flags.forgot) return FALSE; + /* when in tutorial, show all tutorial levels visited whether interesting + or not and don't show any other levels; when outside tutorial, don't + show any tutorial levels even if they're considered interesting */ + if (In_tutorial(&u.uz)) { + return In_tutorial(&mptr->lev); + } else { + if (In_tutorial(&mptr->lev)) + return FALSE; + } /* level is of interest if it has an auto-generated annotation */ if (mptr->flags.oracle || mptr->flags.bigroom || mptr->flags.roguelevel - || mptr->flags.castle || mptr->flags.valley || mptr->flags.msanctum + || mptr->flags.castle || mptr->flags.valley + || mptr->flags.msanctum || mptr->flags.vibrating_square || mptr->flags.quest_summons || mptr->flags.questing) return TRUE; /* when in Sokoban, list all sokoban levels visited; when not in it, @@ -2387,29 +2908,180 @@ mapseen *mptr; return TRUE; /* when in the endgame, list all endgame levels visited, whether they have annotations or not, so that #overview doesn't become extremely - sparse once the rest of the dungeon has been flagged as unreachable */ + sparse once the rest of the dungeon has been flagged as notreachable */ if (In_endgame(&u.uz)) return (boolean) In_endgame(&mptr->lev); /* level is of interest if it has non-zero feature count or known bones or user annotation or known connection to another dungeon branch or is the furthest level reached in its branch */ - return (boolean) (INTEREST(mptr->feat) + return (boolean) (OF_INTEREST(mptr->feat) || (mptr->final_resting_place && (mptr->flags.knownbones || wizard)) || mptr->custom || mptr->br || (mptr->lev.dlevel - == dungeons[mptr->lev.dnum].dunlev_ureached)); + == svd.dungeons[mptr->lev.dnum].dunlev_ureached)); +} + +/* update the lastseentyp at x,y */ +void +update_lastseentyp(coordxy x, coordxy y) +{ + struct monst *mtmp; + int ltyp = levl[x][y].typ; + + if (ltyp == DRAWBRIDGE_UP) + ltyp = db_under_typ(levl[x][y].drawbridgemask); + if ((mtmp = m_at(x, y)) != 0 + && M_AP_TYPE(mtmp) == M_AP_FURNITURE && canseemon(mtmp)) + ltyp = cmap_to_type(mtmp->mappearance); + svl.lastseentyp[x][y] = ltyp; +} + +/* for some cases where deferred update needs to be done immediately; + hide details from caller */ +int +update_mapseen_for(coordxy x, coordxy y) +{ + recalc_mapseen(); /* whole level */ + return svl.lastseentyp[x][y]; +} + +/* count mapseen feature from lastseentyp at x,y */ +staticfn void +count_feat_lastseentyp( + mapseen *mptr, /* remembered data for a level; update feat.X counts */ + coordxy x, coordxy y) +{ + int count; + unsigned atmp; + + switch (svl.lastseentyp[x][y]) { +#if 0 /* levels that have these tend of have a lot of them */ + /* + * FIXME? due to theme rooms, lots of levels have an increased + * chance of having these so automatic annotations for them may + * have become more worthwhile now. + */ + case ICE: + count = mptr->feat.ice + 1; + if (count <= 3) + mptr->feat.ice = count; + break; + case POOL: + case MOAT: + case WATER: + count = mptr->feat.water + 1; + if (count <= 3) + mptr->feat.water = count; + break; + case LAVAPOOL: + case LAVAWALL: + count = mptr->feat.lava + 1; + if (count <= 3) + mptr->feat.lava = count; + break; +#endif + case TREE: + count = mptr->feat.ntree + 1; + if (count <= 3) + mptr->feat.ntree = count; + break; + case FOUNTAIN: + count = mptr->feat.nfount + 1; + if (count <= 3) + mptr->feat.nfount = count; + break; + case THRONE: + count = mptr->feat.nthrone + 1; + if (count <= 3) + mptr->feat.nthrone = count; + break; + case SINK: + count = mptr->feat.nsink + 1; + if (count <= 3) + mptr->feat.nsink = count; + break; + case GRAVE: + count = mptr->feat.ngrave + 1; + if (count <= 3) + mptr->feat.ngrave = count; + break; + case ALTAR: + /* get the altarmask for this location; might be a mimic */ + atmp = altarmask_at(x, y); + /* convert to index: 0..3 */ + atmp = (Is_astralevel(&u.uz) && (levl[x][y].seenv & SVALL) != SVALL) + ? MSA_NONE + : Amask2msa(atmp); + if (!mptr->feat.naltar) + mptr->feat.msalign = atmp; + else if (mptr->feat.msalign != atmp) + mptr->feat.msalign = MSA_NONE; + count = mptr->feat.naltar + 1; + if (count <= 3) + mptr->feat.naltar = count; + break; + /* An automatic annotation is added to the Castle and + * to Fort Ludios once their structure's main entrance + * has been seen (in person or via magic mapping). + * For the Fort, that entrance is just a secret door + * which will be converted into a regular one when + * located (or destroyed). + * DOOR: possibly a lowered drawbridge's open portcullis; + * DBWALL: a raised drawbridge's "closed door"; + * DRAWBRIDGE_DOWN: the span provided by lowered bridge, + * with moat or other terrain hidden underneath; + * DRAWBRIDGE_UP: moat in front of a raised drawbridge, + * not recognizable as a bridge location unless/until + * the adjacent DBWALL has been seen. + */ + case DOOR: + if (Is_knox(&u.uz)) { + int ty, tx = x - 4; + + /* Throne is four columns to left, either directly in + * line or one row higher or lower, and doesn't have + * to have been seen yet. + * ......|}}}. + * ..\...S}... + * ..\...S}... + * ......|}}}. + * For 3.6.0 and earlier, it was always in direct line: + * both throne and door on the lower of the two rows. + */ + for (ty = y - 1; ty <= y + 1; ++ty) + if (isok(tx, ty) && IS_THRONE(levl[tx][ty].typ)) { + mptr->flags.ludios = 1; + break; + } + break; + } + if (is_drawbridge_wall(x, y) < 0) + break; + FALLTHROUGH; + /*FALLTHRU*/ + case DBWALL: + case DRAWBRIDGE_DOWN: + if (Is_stronghold(&u.uz)) + mptr->flags.castle = 1, mptr->flags.castletune = 1; + break; + default: + break; + } } /* recalculate mapseen for the current level */ void -recalc_mapseen() +recalc_mapseen(void) { - mapseen *mptr; + mapseen *mptr, *oth_mptr; struct monst *mtmp; struct cemetery *bp, **bonesaddr; + struct trap *t; unsigned i, ridx; - int x, y, ltyp, count, atmp; + int count; + coordxy x, y; + char uroom; /* Should not happen in general, but possible if in the process * of being booted from the quest. The mapseen object gets @@ -2423,17 +3095,17 @@ recalc_mapseen() /* reset all features; mptr->feat.* = 0; */ (void) memset((genericptr_t) &mptr->feat, 0, sizeof mptr->feat); /* reset most flags; some level-specific ones are left as-is */ - if (mptr->flags.unreachable) { - mptr->flags.unreachable = 0; /* reached it; Eye of the Aethiopica? */ + if (mptr->flags.notreachable) { + mptr->flags.notreachable = 0; /* reached it; Eye of the Aethiopica? */ if (In_quest(&u.uz)) { - mapseen *mptrtmp = mapseenchn; + mapseen *mptrtmp = svm.mapseenchn; - /* when quest was unreachable due to ejection and portal removal, + /* when quest was notreachable due to ejection and portal removal, getting back to it via arti-invoke should revive annotation data for all quest levels, not just the one we're on now */ do { if (mptrtmp->lev.dnum == mptr->lev.dnum) - mptrtmp->flags.unreachable = 0; + mptrtmp->flags.notreachable = 0; mptrtmp = mptrtmp->next; } while (mptrtmp); } @@ -2448,30 +3120,27 @@ recalc_mapseen() mptr->flags.roguelevel = Is_rogue_level(&u.uz); mptr->flags.oracle = 0; /* recalculated during room traversal below */ mptr->flags.castletune = 0; - /* flags.castle, flags.valley, flags.msanctum retain previous value */ + /* flags.castle retains previous value */ mptr->flags.forgot = 0; /* flags.quest_summons disabled once quest finished */ mptr->flags.quest_summons = (at_dgn_entrance("The Quest") && u.uevent.qcalled && !(u.uevent.qcompleted || u.uevent.qexpelled - || quest_status.leader_is_dead)); + || svq.quest_status.leader_is_dead)); mptr->flags.questing = (on_level(&u.uz, &qstart_level) - && quest_status.got_quest); + && svq.quest_status.got_quest); + /* flags.msanctum, .valley, and .vibrating_square handled below */ /* track rooms the hero is in */ - for (i = 0; i < SIZE(u.urooms); ++i) { - if (!u.urooms[i]) - continue; - - ridx = u.urooms[i] - ROOMOFFSET; + for (i = 0; (uroom = u.urooms[i]) != '\0'; ++i) { + ridx = (unsigned) uroom - ROOMOFFSET; mptr->msrooms[ridx].seen = 1; mptr->msrooms[ridx].untended = - (rooms[ridx].rtype >= SHOPBASE) - ? (!(mtmp = shop_keeper(u.urooms[i])) || !inhishop(mtmp)) - : (rooms[ridx].rtype == TEMPLE) - ? (!(mtmp = findpriest(u.urooms[i])) - || !inhistemple(mtmp)) + (svr.rooms[ridx].rtype >= SHOPBASE) + ? (!(mtmp = shop_keeper(uroom)) || !inhishop(mtmp)) + : (svr.rooms[ridx].rtype == TEMPLE) + ? (!(mtmp = findpriest(uroom)) || !inhistemple(mtmp)) : 0; } @@ -2480,28 +3149,28 @@ recalc_mapseen() */ for (i = 0; i < SIZE(mptr->msrooms); ++i) { if (mptr->msrooms[i].seen) { - if (rooms[i].rtype >= SHOPBASE) { + if (svr.rooms[i].rtype >= SHOPBASE) { if (mptr->msrooms[i].untended) mptr->feat.shoptype = SHOPBASE - 1; else if (!mptr->feat.nshop) - mptr->feat.shoptype = rooms[i].rtype; - else if (mptr->feat.shoptype != (unsigned) rooms[i].rtype) + mptr->feat.shoptype = svr.rooms[i].rtype; + else if (mptr->feat.shoptype != (unsigned) svr.rooms[i].rtype) mptr->feat.shoptype = 0; count = mptr->feat.nshop + 1; if (count <= 3) mptr->feat.nshop = count; - } else if (rooms[i].rtype == TEMPLE) { + } else if (svr.rooms[i].rtype == TEMPLE) { /* altar and temple alignment handled below */ count = mptr->feat.ntemple + 1; if (count <= 3) mptr->feat.ntemple = count; - } else if (rooms[i].orig_rtype == DELPHI) { + } else if (svr.rooms[i].orig_rtype == DELPHI) { mptr->flags.oracle = 1; } } } - /* Update lastseentyp with typ if and only if it is in sight or the + /* Update svl.lastseentyp with typ if and only if it is in sight or the * hero can feel it on their current location (i.e. not levitating). * This *should* give the "last known typ" for each dungeon location. * (At the very least, it's a better assumption than determining what @@ -2512,137 +3181,67 @@ recalc_mapseen() * we could track "features" and then update them all here, and keep * track of when new features are created or destroyed, but this * seemed the most elegant, despite adding more data to struct rm. - * [3.6.0: we're using lastseentyp[][] rather than level.locations + * [3.6.0: we're using svl.lastseentyp[][] rather than level.locations * to track the features seen.] * * Although no current windowing systems (can) do this, this would add * the ability to have non-dungeon glyphs float above the last known * dungeon glyph (i.e. items on fountains). */ + if (!Levitation) + update_lastseentyp(u.ux, u.uy); + for (x = 1; x < COLNO; x++) { for (y = 0; y < ROWNO; y++) { - if (cansee(x, y) || (x == u.ux && y == u.uy && !Levitation)) { - ltyp = levl[x][y].typ; - if (ltyp == DRAWBRIDGE_UP) - ltyp = db_under_typ(levl[x][y].drawbridgemask); - if ((mtmp = m_at(x, y)) != 0 - && M_AP_TYPE(mtmp) == M_AP_FURNITURE && canseemon(mtmp)) - ltyp = cmap_to_type(mtmp->mappearance); - lastseentyp[x][y] = ltyp; - } + count_feat_lastseentyp(mptr, x, y); + } + } - switch (lastseentyp[x][y]) { -#if 0 - case ICE: - count = mptr->feat.ice + 1; - if (count <= 3) - mptr->feat.ice = count; - break; - case POOL: - case MOAT: - case WATER: - count = mptr->feat.water + 1; - if (count <= 3) - mptr->feat.water = count; - break; - case LAVAPOOL: - count = mptr->feat.lava + 1; - if (count <= 3) - mptr->feat.lava = count; - break; -#endif - case TREE: - count = mptr->feat.ntree + 1; - if (count <= 3) - mptr->feat.ntree = count; - break; - case FOUNTAIN: - count = mptr->feat.nfount + 1; - if (count <= 3) - mptr->feat.nfount = count; - break; - case THRONE: - count = mptr->feat.nthrone + 1; - if (count <= 3) - mptr->feat.nthrone = count; - break; - case SINK: - count = mptr->feat.nsink + 1; - if (count <= 3) - mptr->feat.nsink = count; - break; - case GRAVE: - count = mptr->feat.ngrave + 1; - if (count <= 3) - mptr->feat.ngrave = count; - break; - case ALTAR: - atmp = (Is_astralevel(&u.uz) - && (levl[x][y].seenv & SVALL) != SVALL) - ? MSA_NONE - : Amask2msa(levl[x][y].altarmask); - if (!mptr->feat.naltar) - mptr->feat.msalign = atmp; - else if (mptr->feat.msalign != atmp) - mptr->feat.msalign = MSA_NONE; - count = mptr->feat.naltar + 1; - if (count <= 3) - mptr->feat.naltar = count; - break; - /* An automatic annotation is added to the Castle and - * to Fort Ludios once their structure's main entrance - * has been seen (in person or via magic mapping). - * For the Fort, that entrance is just a secret door - * which will be converted into a regular one when - * located (or destroyed). - * DOOR: possibly a lowered drawbridge's open portcullis; - * DBWALL: a raised drawbridge's "closed door"; - * DRAWBRIDGE_DOWN: the span provided by lowered bridge, - * with moat or other terrain hidden underneath; - * DRAWBRIDGE_UP: moat in front of a raised drawbridge, - * not recognizable as a bridge location unless/until - * the adjacent DBWALL has been seen. - */ - case DOOR: - if (Is_knox(&u.uz)) { - int ty, tx = x - 4; - - /* Throne is four columns left, either directly in - * line or one row higher or lower, and doesn't have - * to have been seen yet. - * ......|}}}. - * ..\...S}... - * ..\...S}... - * ......|}}}. - * For 3.6.0 and earlier, it was always in direct line: - * both throne and door on the lower of the two rows. - */ - for (ty = y - 1; ty <= y + 1; ++ty) - if (isok(tx, ty) && IS_THRONE(levl[tx][ty].typ)) { - mptr->flags.ludios = 1; - break; - } - break; - } - if (is_drawbridge_wall(x, y) < 0) - break; - /*FALLTHRU*/ - case DBWALL: - case DRAWBRIDGE_DOWN: - if (Is_stronghold(&u.uz)) - mptr->flags.castle = 1, mptr->flags.castletune = 1; - break; - default: - break; - } + /* Moloch's Sanctum and the Valley of the Dead are normally given an + automatic annotation when you enter a temple attended by a priest, + but it is possible for the priest to be killed prior to that; we + assume that both of those levels only contain one altar, so add the + annotation if that altar has been mapped (seen or magic mapping) */ + if (Is_valley(&u.uz)) { + /* don't clear valley if naltar==0; maybe altar got destroyed? */ + if (mptr->feat.naltar > 0) + mptr->flags.valley = 1; + + /* Sanctum and Gateway-to-Sanctum are mutually exclusive automatic + annotations but handling that is tricky because they're stored + with data for different levels */ + } else if (Is_sanctum(&u.uz)) { + if (mptr->feat.naltar > 0) + mptr->flags.msanctum = 1; + + if (mptr->flags.msanctum) { + d_level invocat_lvl; + + invocat_lvl = u.uz; + invocat_lvl.dlevel -= 1; + if ((oth_mptr = find_mapseen(&invocat_lvl)) != 0) + oth_mptr->flags.vibrating_square = 0; } + } else if (Invocation_lev(&u.uz)) { + /* annotate vibrating square's level if vibr_sqr 'trap' has been + found or if that trap is gone (indicating that invocation has + happened) provided that the sanctum's annotation hasn't been + added (either hero hasn't descended to that level yet or hasn't + mapped its temple) */ + for (t = gf.ftrap; t; t = t->ntrap) + if (t->ttyp == VIBRATING_SQUARE) + break; + mptr->flags.vibrating_square = t ? t->tseen + /* no trap implies that invocation has been performed */ + : ((oth_mptr = find_mapseen(&sanctum_level)) == 0 + || !oth_mptr->flags.msanctum); } - if (level.bonesinfo && !mptr->final_resting_place) { + if (svl.level.bonesinfo && !mptr->final_resting_place) { /* clone the bonesinfo so we aren't dependent upon this level being in memory */ bonesaddr = &mptr->final_resting_place; - bp = level.bonesinfo; + bp = svl.level.bonesinfo; do { *bonesaddr = (struct cemetery *) alloc(sizeof **bonesaddr); **bonesaddr = *bp; @@ -2655,20 +3254,23 @@ recalc_mapseen() guarantee of either a grave or a ghost, so we go by whether the current hero has seen the map location where each old one died */ for (bp = mptr->final_resting_place; bp; bp = bp->next) - if (lastseentyp[bp->frpx][bp->frpy]) { + if (svl.lastseentyp[bp->frpx][bp->frpy]) { bp->bonesknown = TRUE; mptr->flags.knownbones = 1; } } /*ARGUSED*/ -/* valley and sanctum levels get automatic annotation once temple is entered */ +/* valley and sanctum levels get automatic annotation once their temple + is entered */ void -mapseen_temple(priest) -struct monst *priest UNUSED; /* currently unused; might be useful someday */ +mapseen_temple( + struct monst *priest UNUSED) /* not used; might be useful someday */ { mapseen *mptr = find_mapseen(&u.uz); + if (!mptr) + return; if (Is_valley(&u.uz)) mptr->flags.valley = 1; else if (Is_sanctum(&u.uz)) @@ -2677,63 +3279,84 @@ struct monst *priest UNUSED; /* currently unused; might be useful someday */ /* room entry message has just been delivered so learn room even if blind */ void -room_discovered(roomno) -int roomno; +room_discovered(int roomno) { mapseen *mptr = find_mapseen(&u.uz); - mptr->msrooms[roomno].seen = 1; + if (mptr && !mptr->msrooms[roomno].seen) { + mptr->msrooms[roomno].seen = 1; + recalc_mapseen(); + } } /* #overview command */ int -dooverview() +dooverview(void) { - show_overview(0, 0); - return 0; + /* game in progress; 'why' is 0 for normal #overview, -1 if user prefixed + #overview with 'm'; 'reason' for end of game isn't applicable: use 0 */ + show_overview(iflags.menu_requested ? -1 : 0, 0); + iflags.menu_requested = FALSE; + return ECMD_OK; } /* called for #overview or for end of game disclosure */ void -show_overview(why, reason) -int why; /* 0 => #overview command, - 1 or 2 => final disclosure (1: hero lived, 2: hero died) */ -int reason; /* how hero died; used when disclosing end-of-game level */ +show_overview( + int why, /* 0 => normal #overview command, -1 => 'm' prefix #overview; + * 1 or 2 => final disclosure (1: hero lived, 2: hero died) */ + int reason) /* how hero died; used when disclosing end-of-game level */ { winid win; int lastdun = -1; + menu_item *selected; + int n; /* lazy initialization */ (void) recalc_mapseen(); win = create_nhwindow(NHW_MENU); + start_menu(win, MENU_BEHAVE_STANDARD); /* show the endgame levels before the rest of the dungeon, so that the Planes (dnum 5-ish) come out above main dungeon (dnum 0) */ if (In_endgame(&u.uz)) - traverse_mapseenchn(TRUE, win, why, reason, &lastdun); + traverse_mapseenchn(1, win, why, reason, &lastdun); /* if game is over or we're not in the endgame yet, show the dungeon */ if (why > 0 || !In_endgame(&u.uz)) - traverse_mapseenchn(FALSE, win, why, reason, &lastdun); - display_nhwindow(win, TRUE); + traverse_mapseenchn(0, win, why, reason, &lastdun); + end_menu(win, (char *) 0); + n = select_menu(win, (why != -1) ? PICK_NONE : PICK_ONE, &selected); + if (n > 0) { + int ledger; + d_level lev; + + ledger = selected[0].item.a_int - 1; + lev.dnum = ledger_to_dnum(ledger); + lev.dlevel = ledger_to_dlev(ledger); + query_annotation(&lev); + free((genericptr_t) selected); + } destroy_nhwindow(win); } /* display endgame levels or non-endgame levels, not both */ -STATIC_OVL void -traverse_mapseenchn(viewendgame, win, why, reason, lastdun_p) -boolean viewendgame; -winid win; -int why, reason, *lastdun_p; +staticfn void +traverse_mapseenchn( + int viewendgame, /* 0: show endgame branch; 1: show other branches */ + winid win, /* output window */ + int why, /* -1: menu, 0: normal, 1 and 2: end of game disclosure */ + int reason, /* when 'why'==1 or 2, how the game ended */ + int *lastdun_p) /* where to restart if called twice */ { mapseen *mptr; boolean showheader; - for (mptr = mapseenchn; mptr; mptr = mptr->next) { + for (mptr = svm.mapseenchn; mptr; mptr = mptr->next) { if (viewendgame ^ In_endgame(&mptr->lev)) continue; - /* only print out info for a level or a dungeon if interest */ - if (why > 0 || interest_mapseen(mptr)) { + /* only print out info for a level or a dungeon if it's of interest */ + if (why != 0 || interest_mapseen(mptr)) { showheader = (boolean) (mptr->lev.dnum != *lastdun_p); print_mapseen(win, mptr, why, reason, showheader); *lastdun_p = mptr->lev.dnum; @@ -2741,18 +3364,16 @@ int why, reason, *lastdun_p; } } -STATIC_OVL const char * -seen_string(x, obj) -xchar x; -const char *obj; +staticfn const char * +seen_string(xint16 x, const char *obj) { /* players are computer scientists: 0, 1, 2, n */ switch (x) { case 0: return "no"; - /* an() returns too much. index is ok in this case */ + /* an() returns too much. index/strchr is ok in this case */ case 1: - return index(vowels, *obj) ? "an" : "a"; + return strchr(vowels, *obj) ? "an" : "a"; case 2: return "some"; case 3: @@ -2763,9 +3384,8 @@ const char *obj; } /* better br_string */ -STATIC_OVL const char * -br_string2(br) -branch *br; +staticfn const char * +br_string2(branch *br) { /* Special case: quest portal says closed if kicked from quest */ boolean closed_portal = (br->end2.dnum == quest_dnum @@ -2787,9 +3407,7 @@ branch *br; /* get the name of an endgame level; topten.c does something similar */ const char * -endgamelevelname(outbuf, indx) -char *outbuf; -int indx; +endgamelevelname(char *outbuf, int indx) { const char *planename = 0; @@ -2818,72 +3436,41 @@ int indx; return outbuf; } -STATIC_OVL const char * -shop_string(rtype) -int rtype; +/* short shop description */ +staticfn const char * +shop_string(int rtype) { - const char *str = "shop"; /* catchall */ + extern const struct shclass shtypes[]; /* defined in shknam.c */ + int shoptype = rtype - SHOPBASE; /* convert room type to shop type */ + const char *str = "shop?"; /* catchall */ - /* Yuck, redundancy...but shclass.name doesn't cut it as a noun */ - switch (rtype) { - case SHOPBASE - 1: + if (shoptype < 0) { str = "untended shop"; - break; /* see recalc_mapseen */ - case SHOPBASE: - str = "general store"; - break; - case ARMORSHOP: - str = "armor shop"; - break; - case SCROLLSHOP: - str = "scroll shop"; - break; - case POTIONSHOP: - str = "potion shop"; - break; - case WEAPONSHOP: - str = "weapon shop"; - break; - case FOODSHOP: - str = "delicatessen"; - break; - case RINGSHOP: - str = "jewelers"; - break; - case WANDSHOP: - str = "wand shop"; - break; - case BOOKSHOP: - str = "bookstore"; - break; - case FODDERSHOP: - str = "health food store"; - break; - case CANDLESHOP: - str = "lighting shop"; - break; - default: - break; + } else if (shtypes[shoptype].annotation) { + str = shtypes[shoptype].annotation; + } else if (shtypes[shoptype].name) { + str = shtypes[shoptype].name; } return str; } /* if player knows about the mastermind tune, append it to Castle annotation; if drawbridge has been destroyed, flags.castletune will be zero */ -STATIC_OVL char * -tunesuffix(mptr, outbuf) -mapseen *mptr; -char *outbuf; +staticfn char * +tunesuffix( + mapseen *mptr, + char *outbuf, + size_t bsz) /* size of outbuf */ { *outbuf = '\0'; if (mptr->flags.castletune && u.uevent.uheard_tune) { char tmp[BUFSZ]; if (u.uevent.uheard_tune == 2) - Sprintf(tmp, "notes \"%s\"", tune); + Sprintf(tmp, "notes \"%s\"", svt.tune); else Strcpy(tmp, "5-note tune"); - Sprintf(outbuf, " (play %s to open or close drawbridge)", tmp); + Snprintf(outbuf, bsz, " (play %s to open or close drawbridge)", tmp); } return outbuf; } @@ -2899,29 +3486,44 @@ char *outbuf; #endif #define COMMA (i++ > 0 ? ", " : PREFIX) /* "iterate" once; safe to use as ``if (cond) ADDTOBUF(); else whatever;'' */ -#define ADDNTOBUF(nam, var) \ +#define ADDTOBUF(nam, var) \ + do { \ + if (var) \ + Sprintf(eos(buf), "%s%s", COMMA, (nam)); \ + } while (0) +#define ADDNTOBUF(nam, var) \ do { \ if (var) \ Sprintf(eos(buf), "%s%s %s%s", COMMA, seen_string((var), (nam)), \ (nam), plur(var)); \ } while (0) -#define ADDTOBUF(nam, var) \ - do { \ - if (var) \ - Sprintf(eos(buf), "%s%s", COMMA, (nam)); \ +/* ADD2NTOBUF: for "M temples and N altars"; seen_string() is safe to use + multiple times within one expression; so is plur() */ +#define ADD2NTOBUF(nam, var, nam2, var2) \ + do { \ + if (var && var2) { \ + Sprintf(eos(buf), "%s%s %s%s and %s %s%s", COMMA, \ + seen_string((var), (nam)), (nam), plur(var), \ + seen_string((var2), (nam2)), (nam2), plur(var2)); \ + } else if (var) { \ + ADDNTOBUF(nam, var); \ + } else if (var2) { \ + ADDNTOBUF(nam2, var2); \ + } \ } while (0) -STATIC_OVL void -print_mapseen(win, mptr, final, how, printdun) -winid win; -mapseen *mptr; -int final; /* 0: not final; 1: game over, alive; 2: game over, dead */ -int how; /* cause of death; only used if final==2 and mptr->lev==u.uz */ -boolean printdun; +staticfn void +print_mapseen( + winid win, mapseen *mptr, + int final, /* -1: as menu; 0: not final; + * 1: game over, alive; 2: game over, dead */ + int how, /* cause of death; only used if final==2 and mptr->lev==u.uz */ + boolean printdun) { char buf[BUFSZ], tmpbuf[BUFSZ]; int i, depthstart, dnum; boolean died_here = (final == 2 && on_level(&u.uz, &mptr->lev)); + anything any; /* Damnable special cases */ /* The quest and knox should appear to be level 1 to match @@ -2931,31 +3533,33 @@ boolean printdun; if (dnum == quest_dnum || dnum == knox_level.dnum) depthstart = 1; else - depthstart = dungeons[dnum].depth_start; + depthstart = svd.dungeons[dnum].depth_start; if (printdun) { - if (dungeons[dnum].dunlev_ureached == dungeons[dnum].entry_lev + if (svd.dungeons[dnum].dunlev_ureached == svd.dungeons[dnum].entry_lev /* suppress the negative numbers in the endgame */ || In_endgame(&mptr->lev)) - Sprintf(buf, "%s:", dungeons[dnum].dname); + Sprintf(buf, "%s:", svd.dungeons[dnum].dname); else if (builds_up(&mptr->lev)) Sprintf(buf, "%s: levels %d up to %d", - dungeons[dnum].dname, - depthstart + dungeons[dnum].entry_lev - 1, - depthstart + dungeons[dnum].dunlev_ureached - 1); + svd.dungeons[dnum].dname, + depthstart + svd.dungeons[dnum].entry_lev - 1, + depthstart + svd.dungeons[dnum].dunlev_ureached - 1); else Sprintf(buf, "%s: levels %d to %d", - dungeons[dnum].dname, depthstart, - depthstart + dungeons[dnum].dunlev_ureached - 1); - putstr(win, !final ? ATR_INVERSE : 0, buf); + svd.dungeons[dnum].dname, depthstart, + depthstart + svd.dungeons[dnum].dunlev_ureached - 1); + + add_menu_heading(win, buf); } /* calculate level number */ i = depthstart + mptr->lev.dlevel - 1; if (In_endgame(&mptr->lev)) - Sprintf(buf, "%s%s:", TAB, endgamelevelname(tmpbuf, i)); + Sprintf(buf, "%s%s:", (final != -1) ? TAB : "", + endgamelevelname(tmpbuf, i)); else - Sprintf(buf, "%sLevel %d:", TAB, i); + Sprintf(buf, "%sLevel %d:", (final != -1) ? TAB : "", i); /* wizmode prints out proto dungeon names for clarity */ if (wizard) { @@ -2969,15 +3573,20 @@ boolean printdun; Sprintf(eos(buf), " \"%s\"", mptr->custom); if (on_level(&u.uz, &mptr->lev)) Sprintf(eos(buf), " <- You %s here.", - (!final || (final == 1 && how == ASCENDED)) ? "are" + (final <= 0 || (final == 1 && how == ASCENDED)) ? "are" : (final == 1 && how == ESCAPED) ? "left from" : "were"); - putstr(win, !final ? ATR_BOLD : 0, buf); + + any = cg.zeroany; + if (final == -1) + any.a_int = ledger_no(&(mptr->lev)) + 1; + add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, NO_COLOR, + buf, MENU_ITEMFLAGS_NONE); if (mptr->flags.forgot) return; - if (INTEREST(mptr->feat)) { + if (OF_INTEREST(mptr->feat)) { buf[0] = 0; i = 0; /* interest counter */ @@ -2991,15 +3600,21 @@ boolean printdun; Sprintf(eos(buf), "%s%s", COMMA, an(shop_string(mptr->feat.shoptype))); } - if (mptr->feat.naltar > 0) { - /* Temples + non-temple altars get munged into just "altars" */ - if (mptr->feat.ntemple != mptr->feat.naltar) - ADDNTOBUF("altar", mptr->feat.naltar); - else - ADDNTOBUF("temple", mptr->feat.ntemple); + if (mptr->feat.naltar > 0 || mptr->feat.ntemple > 0) { + unsigned atmp; + + /* being aware of a temple doesn't guarantee being aware of its + altar (via entrance message when entering while blinded, or + possibly it being out of view in an irregularly shaped room); + FIXME: if all temples present have been desecrated, we ought + to say so */ + ADD2NTOBUF("temple", mptr->feat.ntemple, + "altar", mptr->feat.naltar); /* only print out altar's god if they are all to your god */ - if (Amask2align(Msa2amask(mptr->feat.msalign)) == u.ualign.type) + atmp = mptr->feat.msalign; /* 0, 1, 2, 3 */ + atmp = Msa2amask(atmp); /* 0, 1, 2, 4 */ + if (Amask2align(atmp) == u.ualign.type) /* -128, -1, 0, +1 */ Sprintf(eos(buf), " to %s", align_gname(u.ualign.type)); } ADDNTOBUF("throne", mptr->feat.nthrone); @@ -3017,7 +3632,7 @@ boolean printdun; buf[i] = highc(buf[i]); /* capitalizing it makes it a sentence; terminate with '.' */ Strcat(buf, "."); - putstr(win, 0, buf); + add_menu_str(win, buf); } /* we assume that these are mutually exclusive */ @@ -3033,7 +3648,7 @@ boolean printdun; Sprintf(buf, "%sA primitive area.", PREFIX); } else if (on_level(&mptr->lev, &qstart_level)) { Sprintf(buf, "%sHome%s.", PREFIX, - mptr->flags.unreachable ? " (no way back...)" : ""); + mptr->flags.notreachable ? " (no way back...)" : ""); if (u.uevent.qcompleted) Sprintf(buf, "%sCompleted quest for %s.", PREFIX, ldrname()); else if (mptr->flags.questing) @@ -3044,24 +3659,28 @@ boolean printdun; indicates that the fort's entrance has been seen (or mapped) */ Sprintf(buf, "%sFort Ludios.", PREFIX); } else if (mptr->flags.castle) { - Sprintf(buf, "%sThe castle%s.", PREFIX, tunesuffix(mptr, tmpbuf)); + Snprintf(buf, sizeof buf, "%sThe castle%s.", PREFIX, + tunesuffix(mptr, tmpbuf, sizeof tmpbuf)); } else if (mptr->flags.valley) { Sprintf(buf, "%sValley of the Dead.", PREFIX); + } else if (mptr->flags.vibrating_square) { + Sprintf(buf, "%sGateway to Moloch's Sanctum.", PREFIX); } else if (mptr->flags.msanctum) { Sprintf(buf, "%sMoloch's Sanctum.", PREFIX); } - if (*buf) - putstr(win, 0, buf); + if (*buf) { + add_menu_str(win, buf); + } /* quest entrance is not mutually-exclusive with bigroom or rogue level */ if (mptr->flags.quest_summons) { Sprintf(buf, "%sSummoned by %s.", PREFIX, ldrname()); - putstr(win, 0, buf); + add_menu_str(win, buf); } /* print out branches */ if (mptr->br) { Sprintf(buf, "%s%s to %s", PREFIX, br_string2(mptr->br), - dungeons[mptr->br->end2.dnum].dname); + svd.dungeons[mptr->br->end2.dnum].dname); /* Since mapseen objects are printed out in increasing order * of dlevel, clarify which level this branch is going to @@ -3070,20 +3689,20 @@ boolean printdun; if (mptr->br->end1_up && !In_endgame(&(mptr->br->end2))) Sprintf(eos(buf), ", level %d", depth(&(mptr->br->end2))); Strcat(buf, "."); - putstr(win, 0, buf); + add_menu_str(win, buf); } /* maybe print out bones details */ - if (mptr->final_resting_place || final) { + if (mptr->final_resting_place || final > 0) { struct cemetery *bp; int kncnt = !died_here ? 0 : 1; for (bp = mptr->final_resting_place; bp; bp = bp->next) - if (bp->bonesknown || wizard || final) + if (bp->bonesknown || wizard || final > 0) ++kncnt; if (kncnt) { Sprintf(buf, "%s%s", PREFIX, "Final resting place for"); - putstr(win, 0, buf); + add_menu_str(win, buf); if (died_here) { /* disclosure occurs before bones creation, so listing dead hero here doesn't give away whether bones are produced */ @@ -3093,19 +3712,23 @@ boolean printdun; (void) strsubst(tmpbuf, " herself", " yourself"); (void) strsubst(tmpbuf, " his ", " your "); (void) strsubst(tmpbuf, " her ", " your "); - Sprintf(buf, "%s%syou, %s%c", PREFIX, TAB, tmpbuf, - --kncnt ? ',' : '.'); - putstr(win, 0, buf); + Snprintf(buf, sizeof(buf), "%s%syou, %s%c", PREFIX, TAB, + tmpbuf, --kncnt ? ',' : '.'); + add_menu_str(win, buf); } for (bp = mptr->final_resting_place; bp; bp = bp->next) { - if (bp->bonesknown || wizard || final) { + if (bp->bonesknown || wizard || final > 0) { Sprintf(buf, "%s%s%s, %s%c", PREFIX, TAB, bp->who, bp->how, --kncnt ? ',' : '.'); - putstr(win, 0, buf); + add_menu_str(win, buf); } } } } } +#endif /* !SFCTOOL */ +#undef OF_INTEREST +#undef ADDNTOBUF +#undef ADDTOBUF /*dungeon.c*/ diff --git a/src/earlyarg.c b/src/earlyarg.c new file mode 100755 index 000000000..e8009a57e --- /dev/null +++ b/src/earlyarg.c @@ -0,0 +1,812 @@ +/* NetHack 5.0 earlyarg.c $NHDT-Date: 1771213100 2026/02/15 19:38:20 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.286 $ */ +/* Copyright (c) Robert Patrick Rankin, 2012. */ +/* NetHack may be freely redistributed. See license for details. */ + +#include "hack.h" +#include "dlb.h" + +staticfn void debug_fields(char *); +#ifndef NODUMPENUMS +staticfn void dump_enums(void); +#endif +ATTRNORETURN staticfn void opt_terminate(void) NORETURN; +ATTRNORETURN staticfn void opt_usage(const char *) NORETURN; +ATTRNORETURN staticfn void scores_only(int, char **, const char *) NORETURN; +staticfn char *lopt(char *, int, const char *, const char *, int *, char ***); +staticfn void consume_arg(int, int *, char ***); +staticfn void consume_two_args(int, int *, char ***); + +#ifdef UNIX +extern boolean whoami(void); +#endif + +/* + * Argument processing helpers - for xxmain() to share + * and call. + * + * These should return TRUE if the argument matched, + * whether the processing of the argument was + * successful or not. + * + * Most of these do their thing, then after returning + * to xxmain(), the code exits without starting a game. + * + */ + +static const struct early_opt earlyopts[] = { + { ARG_DEBUG, "debug", 5, TRUE }, + { ARG_VERSION, "version", 4, TRUE }, + { ARG_SHOWPATHS, "showpaths", 8, FALSE }, +#ifndef NODUMPENUMS + { ARG_DUMPENUMS, "dumpenums", 9, FALSE }, +#endif + { ARG_DUMPGLYPHIDS, "dumpglyphids", 12, FALSE }, + { ARG_DUMPMONGEN, "dumpmongen", 10, FALSE }, + { ARG_DUMPWEIGHTS, "dumpweights", 11, FALSE }, +#ifdef WIN32 + { ARG_WINDOWS, "windows", 4, TRUE }, +#endif +#if defined(CRASHREPORT) + { ARG_BIDSHOW, "bidshow", 7, FALSE }, +#endif +}; + +static char ArgVal_novalue[] = "[nothing]"; /* note: not 'const' */ + +enum cmdlinearg { + ArgValRequired = 0, + ArgValOptional = 1, + ArgValDisallowed = 2, + ArgVal_mask = (1 | 2), + ArgNamOneLetter = 4, + ArgNam_mask = 4, + ArgErrSilent = 0, + ArgErrComplain = 8, + ArgErr_mask = 8 +}; + +/* approximate 'getopt_long()' for one option; all the comments refer to + "-windowtype" but the code isn't specific to that */ +staticfn char * +lopt(char *arg, /* command line token; beginning matches 'optname' */ + int lflags, /* cmdlinearg | errorhandling */ + const char *optname, /* option's name; "-windowtype" in examples below */ + const char *origarg, /* 'arg' might have had a dash prefix removed */ + int *argc_p, /* argc that can have changes passed to caller */ + char ***argv_p) /* argv[] ditto */ +{ + int argc = *argc_p; + char **argv = *argv_p; + char *p, *nextarg = (argc > 1 && argv[1][0] != '-') ? argv[1] : 0; + int l, opttype = (lflags & ArgVal_mask); + boolean oneletterok = ((lflags & ArgNam_mask) == ArgNamOneLetter), + complain = ((lflags & ArgErr_mask) == ArgErrComplain); + + /* first letter must match */ + if (arg[1] != optname[1]) { + loptbail: + if (complain) + config_error_add("Unknown option: %.60s", origarg); + return (char *) 0; + loptnotallowed: + if (complain) + config_error_add("Value not allowed: %.60s", origarg); + return (char *) 0; + loptrequired: + if (complain) + config_error_add("Missing required value: %.60s", origarg); + return (char *) 0; + } + + if ((p = strchr(arg, '=')) == 0) + p = strchr(arg, ':'); + if (p && opttype == ArgValDisallowed) + goto loptnotallowed; + + l = (int) (p ? (long) (p - arg) : (long) strlen(arg)); + if ((l > 2 || oneletterok) && !strncmp(arg, optname, l)) { + /* "-windowtype[=foo]" */ + if (p) + ++p; /* past '=' or ':' */ + else if (opttype == ArgValRequired) + p = eos(arg); /* we have "-w[indowtype]" w/o "=foo" + * so we'll take foo from next element */ + else + return ArgVal_novalue; + } else if (oneletterok) { + /* "-w..." but not "-w[indowtype[=foo]]" */ + if (!p) { + p = &arg[2]; /* past 'w' of "-wfoo" */ +#if 0 /* -x:value could work but is not supported (callers don't expect it) \ + */ + } else if (p == arg + 2) { + ++p; /* past ':' of "-w:foo" */ +#endif + } else { + /* "-w...=foo" but not "-w[indowtype]=foo" */ + goto loptbail; + } + } else { + goto loptbail; + } + if (!p || !*p) { + /* "-w[indowtype]" w/o '='/':' if there is a next element, use + it for "foo"; if not, supply a non-Null bogus value */ + if (nextarg + && (opttype == ArgValRequired || opttype == ArgValOptional)) + p = nextarg, --(*argc_p), ++(*argv_p); + else if (opttype == ArgValRequired) + goto loptrequired; + else + p = ArgVal_novalue; /* there is no next element */ + } + return p; +} +/* move argv[ndx] to end of argv[] array, then reduce argc to hide it; + prevents process_options() from encountering it after early_options() + has processed it; elements get reordered but all remain intact */ +staticfn void +consume_arg(int ndx, int *ac_p, char ***av_p) +{ + char *gone, **av = *av_p; + int i, ac = *ac_p; + + /* "-one -two -three -four" -> "-two -three -four -one" */ + if (ac > 2) { + gone = av[ndx]; + for (i = ndx + 1; i < ac; ++i) + av[i - 1] = av[i]; + av[ac - 1] = gone; + } + --(*ac_p); +} + +/* consume two tokens for '-argname value' w/o '=' or ':' */ +staticfn void +consume_two_args(int ndx, int *ac_p, char ***av_p) +{ + /* when consuming "-two arg" from "-two arg -three -four", + the *ac_p manipulation results in "-three -four -two arg" + rather than the "-three -four arg -two" that would happen + with just two ordinary consume_arg() calls */ + consume_arg(ndx, ac_p, av_p); + ++(*ac_p); /* bring the final slot back into view */ + consume_arg(ndx, ac_p, av_p); + --(*ac_p); /* take away restored slot */ +} + +/* process some command line arguments before loading options */ +void +early_options(int *argc_p, char ***argv_p, char **hackdir_p) +{ + char **argv, *arg, *origarg; + int argc, oldargc, ndx = 0, consumed = 0; + +#ifdef ENHANCED_SYMBOLS + if (argcheck(*argc_p, *argv_p, ARG_DUMPGLYPHIDS) == 2) + opt_terminate(); +#endif + + config_error_init(FALSE, "command line", FALSE); + + /* treat "nethack ?" as a request for usage info; due to shell + processing, player likely has to use "nethack \?" or "nethack '?'" + [won't work if used as "nethack -dpath ?" or "nethack -d path ?"] */ + if (*argc_p > 1 && !strcmp((*argv_p)[1], "?")) + opt_usage(*hackdir_p); /* doesn't return */ + + /* + * Both *argc_p and *argv_p account for the program name as (*argv_p)[0]; + * local argc and argv implicitly discard that (by starting 'ndx' at 1). + * argcheck() doesn't mind, prscore() (via scores_only()) does (for the + * number of args it gets passed, not for the value of argv[0]). + */ + for (ndx = 1; ndx < *argc_p; ndx += (consumed ? 0 : 1)) { + consumed = 0; + argc = *argc_p - ndx; + argv = *argv_p + ndx; + + arg = origarg = argv[0]; + /* skip any args intended for deferred options */ + if (*arg != '-') + continue; + /* allow second dash if arg name is longer than one character */ + if (arg[0] == '-' && arg[1] == '-' && arg[2] != '\0' + && (arg[3] != '\0' && arg[3] != '=' && arg[3] != ':')) + ++arg; + + switch (arg[1]) { /* char after leading dash */ + case 'b': +#ifdef CRASHREPORT + // --bidshow + if (argcheck(argc, argv, ARG_BIDSHOW) == 2) { + opt_terminate(); + /*NOTREACHED*/ + } +#endif + break; + case 'd': + if (argcheck(argc, argv, ARG_DEBUG) == 1) { + consume_arg(ndx, argc_p, argv_p), consumed = 1; +#ifndef NODUMPENUMS + } else if (argcheck(argc, argv, ARG_DUMPENUMS) == 2) { + opt_terminate(); + /*NOTREACHED*/ +#endif + } else if (argcheck(argc, argv, ARG_DUMPMONGEN) == 2) { + opt_terminate(); + /*NOTREACHED*/ + } else if (argcheck(argc, argv, ARG_DUMPWEIGHTS) == 2) { + opt_terminate(); + /*NOTREACHED*/ + } else { +#ifdef CHDIR + oldargc = argc; + arg = lopt(arg, + (ArgValRequired | ArgNamOneLetter | ArgErrSilent), + "-directory", origarg, &argc, &argv); + if (!arg) + error("Flag -d must be followed by a directory name."); + if (*arg != 'e') { /* avoid matching -decgraphics or -debug */ + *hackdir_p = arg; + if (oldargc == argc) + consume_arg(ndx, argc_p, argv_p), consumed = 1; + else + consume_two_args(ndx, argc_p, argv_p), consumed = 2; + } +#endif /* CHDIR */ + } + break; + case 'h': + case '?': + if (lopt(arg, ArgValDisallowed, "-help", origarg, &argc, &argv) + || lopt(arg, ArgValDisallowed | ArgNamOneLetter, "-?", + origarg, &argc, &argv)) + opt_usage(*hackdir_p); /* doesn't return */ + break; + case 'n': + oldargc = argc; + if (!strcmp(arg, "-no-nethackrc")) /* no abbreviation allowed */ + arg = nhStr("/dev/null"); + else + arg = lopt(arg, (ArgValRequired | ArgErrComplain), + "-nethackrc", origarg, &argc, &argv); + if (arg) { + gc.cmdline_rcfile = dupstr(arg); + if (oldargc == argc) + consume_arg(ndx, argc_p, argv_p), consumed = 1; + else + consume_two_args(ndx, argc_p, argv_p), consumed = 2; + } + break; + case 's': + if (argcheck(argc, argv, ARG_SHOWPATHS) == 2) { + gd.deferred_showpaths = TRUE; + gd.deferred_showpaths_dir = *hackdir_p; + config_error_done(); + return; + } + /* check for "-s" request to show scores */ + if (lopt(arg, + ((ArgValDisallowed | ArgErrComplain) + /* only accept one-letter if there is just one + dash; reject "--s" because prscore() via + scores_only() doesn't understand it */ + | ((origarg[1] != '-') ? ArgNamOneLetter : 0)), + /* [ought to omit val-disallowed and accept + --scores=foo since -s foo and -sfoo are + allowed, but -s form can take more than one + space-separated argument and --scores=foo + isn't suited for that] */ + "-scores", origarg, &argc, &argv)) { + /* at this point, argv[0] contains "-scores" or a leading + substring of it; prscore() (via scores_only()) expects + that to be in argv[1] so we adjust the pointer to make + that be the case; if there are any non-early args waiting + to be passed along to process_options(), the resulting + argv[0] will be one of those rather than the program + name but prscore() doesn't care */ + scores_only(argc + 1, argv - 1, *hackdir_p); + /*NOTREACHED*/ + } + break; + case 'u': +#if defined(UNIX) + if (lopt(arg, ArgValDisallowed, "-usage", origarg, &argc, &argv)) + opt_usage(*hackdir_p); +#elif defined(WIN32) || defined(MSDOS) || defined(AMIGA) + if (arg[2]) { + (void) strncpy(svp.plname, arg + 2, sizeof(svp.plname) - 1); + } else if (ndx + 1 < *argc_p) { + const char *nextarg = (*argv_p)[ndx + 1]; + + if (nextarg[0] != '-') { + (void) strncpy(svp.plname, nextarg, sizeof(svp.plname) - 1); + } else { + raw_print("Player name expected after -u\n"); + } + } +#endif + break; + case 'v': + if (argcheck(argc, argv, ARG_VERSION) == 2) { + opt_terminate(); + /*NOTREACHED*/ + } + break; + case 'w': /* windowtype: "-wfoo" or "-w[indowtype]=foo" + * or "-w[indowtype]:foo" or "-w[indowtype] foo" */ + arg = + lopt(arg, (ArgValRequired | ArgNamOneLetter | ArgErrComplain), + "-windowtype", origarg, &argc, &argv); + if (gc.cmdline_windowsys) + free((genericptr_t) gc.cmdline_windowsys); + gc.cmdline_windowsys = arg ? dupstr(arg) : NULL; + break; + #if !defined(UNIX) && !defined(VMS) + case 'D': + wizard = TRUE, discover = FALSE; + break; + case 'X': + discover = TRUE, wizard = FALSE; + break; +#endif + default: + break; + } + } + /* empty or "N errors on command line" */ + config_error_done(); + return; +} +/* for command-line options that perform some immediate action and then + terminate the program without starting play, like 'nethack --version' + or 'nethack -s Zelda'; do some cleanup before that termination */ +ATTRNORETURN staticfn void +opt_terminate(void) +{ + program_state.early_options = 0; + config_error_done(); /* free memory allocated by config_error_init() */ + + nh_terminate(EXIT_SUCCESS); + /*NOTREACHED*/ +} + +ATTRNORETURN staticfn void +opt_usage(const char *hackdir) +{ +#ifdef CHDIR + chdirx(hackdir, TRUE); +#else + nhUse(hackdir); +#endif + dlb_init(); + + genl_display_file(USAGEHELP, TRUE); + opt_terminate(); +} +/* show the sysconf file name, playground directory, run-time configuration + file name, dumplog file name if applicable, and some other things */ +ATTRNORETURN void +after_opt_showpaths(const char *dir) +{ +#ifdef CHDIR + chdirx(dir, FALSE); +#else + nhUse(dir); +#endif + opt_terminate(); + /*NOTREACHED*/ +} + +/* handle "-s [character-names]" to show all the entries + in the high scores file ('record') belonging to particular characters; + nethack will end after doing so without starting play */ +ATTRNORETURN staticfn void +scores_only(int argc, char **argv, const char *dir) +{ + /* do this now rather than waiting for final termination, in case there + is an error summary coming */ + config_error_done(); + +#ifdef CHDIR + chdirx(dir, FALSE); +#else + nhUse(dir); +#endif +#ifdef SYSCF + iflags.initoptions_noterminate = TRUE; + initoptions(); /* sysconf options affect whether panictrace is enabled */ + iflags.initoptions_noterminate = FALSE; +#endif +#ifdef PANICTRACE + ARGV0 = gh.hname; /* save for possible stack trace */ +#ifndef NO_SIGNAL + panictrace_setsignals(TRUE); +#endif +#endif +#ifdef UNIX + (void) whoami(); /* set up default plname[] */ +#endif + prscore(argc, argv); +#ifdef MSWIN_GRAPHICS + /* NetHackW can also support WINDOWPORT(curses) now, so check */ + if (WINDOWPORT(mswin)) { + wait_synch(); + } +#endif + + nh_terminate(EXIT_SUCCESS); /* bypass opt_terminate() */ + /*NOTREACHED*/ +} + +/* + * Returns: + * 0 = no match + * 1 = found and skip past this argument + * 2 = found and trigger immediate exit + */ +int +argcheck(int argc, char *argv[], enum earlyarg e_arg) +{ + int i, idx; + boolean match = FALSE; + char *userea = (char *) 0; + const char *dashdash = ""; + + for (idx = 0; idx < SIZE(earlyopts); idx++) { + if (earlyopts[idx].e == e_arg){ + break; + } + } + if (idx >= SIZE(earlyopts) || argc < 1) + return 0; + + for (i = 0; i < argc; ++i) { + if (argv[i][0] != '-') + continue; + if (argv[i][1] == '-') { + userea = &argv[i][2]; + dashdash = "-"; + } else { + userea = &argv[i][1]; + } + match = match_optname(userea, earlyopts[idx].name, + earlyopts[idx].minlength, + earlyopts[idx].valallowed); + if (match) + break; + } + + if (match) { + const char *extended_opt = strchr(userea, ':'); + + if (!extended_opt) + extended_opt = strchr(userea, '='); + switch(e_arg) { + case ARG_DEBUG: + if (extended_opt) { + char *cpy_extended_opt; + + cpy_extended_opt = dupstr(extended_opt); + debug_fields(cpy_extended_opt + 1); + free((genericptr_t) cpy_extended_opt); + } + return 1; + case ARG_VERSION: { + boolean insert_into_pastebuf = FALSE; + + if (extended_opt) { + extended_opt++; + /* Deprecated in favor of "copy" - remove no later + than next major version */ + if (match_optname(extended_opt, "paste", 5, FALSE)) { + insert_into_pastebuf = TRUE; + } else if (match_optname(extended_opt, "copy", 4, FALSE)) { + insert_into_pastebuf = TRUE; + } else if (match_optname(extended_opt, "dump", 4, FALSE)) { + /* version number plus enabled features and sanity + values that the program compares against the same + thing recorded in save and bones files to check + whether they're being used compatibly */ + dump_version_info(); + return 2; /* done */ + } else if (!match_optname(extended_opt, "show", 4, FALSE)) { + raw_printf("-%sversion can only be extended with" + " -%sversion:copy or :dump or :show.\n", + dashdash, dashdash); + /* exit after we've reported bad command line argument */ + return 2; + } + } + early_version_info(insert_into_pastebuf); + return 2; + } + case ARG_SHOWPATHS: + return 2; +#ifndef NODUMPENUMS + case ARG_DUMPENUMS: + dump_enums(); + return 2; +#endif + case ARG_DUMPGLYPHIDS: + dump_glyphids(); + return 2; + case ARG_DUMPMONGEN: + dump_mongen(); + return 2; + case ARG_DUMPWEIGHTS: + dump_weights(); + return 2; +#ifdef CRASHREPORT + case ARG_BIDSHOW: + crashreport_bidshow(); + return 2; +#endif +#ifdef WIN32 + case ARG_WINDOWS: + if (extended_opt) { + extended_opt++; + return windows_early_options(extended_opt); + } + FALLTHROUGH; + /*FALLTHRU*/ +#endif + default: + break; + } + }; + return 0; +} + +/* + * These are internal controls to aid developers with + * testing and debugging particular aspects of the code. + * They are not player options and the only place they + * are documented is right here. No gameplay is altered. + * + * test - test whether this parser is working + * ttystatus - TTY: + * immediateflips - WIN32: turn off display performance + * optimization so that display output + * can be debugged without buffering. + * fuzzer - enable fuzzer without debugger intervention. + */ +staticfn void +debug_fields(char *opts) +{ + char *op; + boolean negated = FALSE; + + while ((op = strchr(opts, ',')) != 0) { + *op++ = 0; + /* recurse */ + debug_fields(op); + } + if (strlen(opts) > BUFSZ / 2) + return; + + + /* strip leading and trailing white space */ + while (isspace((uchar) *opts)) + opts++; + op = eos((char *) opts); + while (--op >= opts && isspace((uchar) *op)) + *op = '\0'; + + if (!*opts) { + /* empty */ + return; + } + while ((*opts == '!') || !strncmpi(opts, "no", 2)) { + if (*opts == '!') + opts++; + else + opts += 2; + negated = !negated; + } + if (match_optname(opts, "test", 4, FALSE)) + iflags.debug.test = negated ? FALSE : TRUE; +#ifdef TTY_GRAPHICS + if (match_optname(opts, "ttystatus", 9, FALSE)) + iflags.debug.ttystatus = negated ? FALSE : TRUE; +#endif +#ifdef WIN32 + if (match_optname(opts, "immediateflips", 14, FALSE)) + iflags.debug.immediateflips = negated ? FALSE : TRUE; +#endif + if (match_optname(opts, "fuzzer", 4, FALSE)) + iflags.fuzzerpending = TRUE; + return; +} + +#if !defined(NODUMPENUMS) +/* monsdump[] and objdump[] are also used in utf8map.c */ + +#define DUMP_ENUMS +#define UNPREFIXED_COUNT (5) +struct enum_dump monsdump[] = { +#include "monsters.h" + { NUMMONS, "NUMMONS" }, + { NON_PM, "NON_PM" }, + { LOW_PM, "LOW_PM" }, + { HIGH_PM, "HIGH_PM" }, + { SPECIAL_PM, "SPECIAL_PM" } +}; +struct enum_dump objdump[] = { +#include "objects.h" + { NUM_OBJECTS, "NUM_OBJECTS" }, +}; + +#define DUMP_ENUMS_PCHAR +static struct enum_dump defsym_cmap_dump[] = { +#include "defsym.h" + { MAXPCHARS, "MAXPCHARS" }, +}; +#undef DUMP_ENUMS_PCHAR + +#define DUMP_ENUMS_MONSYMS +static struct enum_dump defsym_mon_syms_dump[] = { +#include "defsym.h" + { MAXMCLASSES, "MAXMCLASSES" }, +}; +#undef DUMP_ENUMS_MONSYMS + +#define DUMP_ENUMS_MONSYMS_DEFCHAR +static struct enum_dump defsym_mon_defchars_dump[] = { +#include "defsym.h" +}; +#undef DUMP_ENUMS_MONSYMS_DEFCHAR + +#define DUMP_ENUMS_OBJCLASS_DEFCHARS +static struct enum_dump objclass_defchars_dump[] = { +#include "defsym.h" +}; +#undef DUMP_ENUMS_OBJCLASS_DEFCHARS + +#define DUMP_ENUMS_OBJCLASS_CLASSES +static struct enum_dump objclass_classes_dump[] = { +#include "defsym.h" + { MAXOCLASSES, "MAXOCLASSES" }, +}; +#undef DUMP_ENUMS_OBJCLASS_CLASSES + +#define DUMP_ENUMS_OBJCLASS_SYMS +static struct enum_dump objclass_syms_dump[] = { +#include "defsym.h" +}; +#undef DUMP_ENUMS_OBJCLASS_SYMS + +#define DUMP_ARTI_ENUM +static struct enum_dump arti_enum_dump[] = { +#include "artilist.h" + { AFTER_LAST_ARTIFACT, "AFTER_LAST_ARTIFACT" } +}; +#undef DUMP_ARTI_ENUM + +/* the enums are not part of hack.h for this one */ +#define DUMP_MCASTU_ENUM1 +enum mcast_dumpenum_spells { + #include "mcastu.h" +}; +#undef DUMP_MCASTU_ENUM1 + +#define DUMP_MCASTU_ENUM2 +static struct enum_dump mcastu_enum_dump[] = { +#include "mcastu.h" +}; +#undef DUMP_MCASTU_ENUM2 + +#undef DUMP_ENUMS + + +#ifndef NODUMPENUMS + +staticfn void +dump_enums(void) +{ + enum enum_dumps { + monsters_enum, + objects_enum, + objects_misc_enum, + defsym_cmap_enum, + defsym_mon_syms_enum, + defsym_mon_defchars_enum, + objclass_defchars_enum, + objclass_classes_enum, + objclass_syms_enum, + arti_enum, + mcastu_enum, + NUM_ENUM_DUMPS + }; + +#define dump_om(om) { om, #om } + static const struct enum_dump omdump[] = { + dump_om(LAST_GENERIC), + dump_om(OBJCLASS_HACK), + dump_om(FIRST_OBJECT), + dump_om(FIRST_AMULET), + dump_om(LAST_AMULET), + dump_om(FIRST_SPELL), + dump_om(LAST_SPELL), + dump_om(MAXSPELL), + dump_om(FIRST_REAL_GEM), + dump_om(LAST_REAL_GEM), + dump_om(FIRST_GLASS_GEM), + dump_om(LAST_GLASS_GEM), + dump_om(NUM_REAL_GEMS), + dump_om(NUM_GLASS_GEMS), + dump_om(MAX_GLYPH), + }; +#undef dump_om + + static const struct enum_dump *const ed[NUM_ENUM_DUMPS] = { + monsdump, objdump, omdump, + defsym_cmap_dump, defsym_mon_syms_dump, + defsym_mon_defchars_dump, + objclass_defchars_dump, + objclass_classes_dump, + objclass_syms_dump, + arti_enum_dump, + mcastu_enum_dump, + }; + + static const struct de_params { + const char *const title; + const char *const pfx; + int unprefixed_count; + int dumpflgs; /* 0 = dump numerically only, 1 = add 'char' comment */ + int szd; + } edmp[NUM_ENUM_DUMPS] = { + { "monnums", "PM_", UNPREFIXED_COUNT, 0, SIZE(monsdump) }, + { "objects_nums", "", 1, 0, SIZE(objdump) }, + { "misc_object_nums", "", 1, 0, SIZE(omdump) }, + { "cmap_symbols", "", 1, 0, SIZE(defsym_cmap_dump) }, + { "mon_syms", "", 1, 0, SIZE(defsym_mon_syms_dump) }, + { "mon_defchars", "", 1, 1, SIZE(defsym_mon_defchars_dump) }, + { "objclass_defchars", "", 1, 1, SIZE(objclass_defchars_dump) }, + { "objclass_classes", "", 1, 0, SIZE(objclass_classes_dump) }, + { "objclass_syms", "", 1, 0, SIZE(objclass_syms_dump) }, + { "artifacts_nums", "", 1, 0, SIZE(arti_enum_dump) }, + { "mcast_spells", "MCAST_", 0, 0, SIZE(mcastu_enum_dump) }, + }; + + const char *nmprefix; + int i, j, nmwidth; + char comment[BUFSZ]; + + for (i = 0; i < NUM_ENUM_DUMPS; ++ i) { + raw_printf("enum %s = {", edmp[i].title); + for (j = 0; j < edmp[i].szd; ++j) { + nmprefix = (j >= edmp[i].szd - edmp[i].unprefixed_count) + ? "" : edmp[i].pfx; /* "" or "PM_" */ + nmwidth = 27 - (int) strlen(nmprefix); /* 27 or 24 */ + if (edmp[i].dumpflgs > 0) { + Snprintf(comment, sizeof comment, + " /* '%c' */", + (ed[i][j].val >= 32 && ed[i][j].val <= 126) + ? ed[i][j].val : ' '); + } else { + comment[0] = '\0'; + } + raw_printf(" %s%*s = %3d,%s", + nmprefix, -nmwidth, + ed[i][j].nm, ed[i][j].val, + comment); + } + raw_print("};"); + raw_print(""); + } + raw_print(""); +} +#undef UNPREFIXED_COUNT +#endif /* NODUMPENUMS */ + +void +dump_glyphids(void) +{ + dump_all_glyphids(stdout); +} +#endif /* !NODUMPENUMS */ + +/*allmain.c*/ diff --git a/src/eat.c b/src/eat.c index 5e2aa1a09..05b41f62a 100644 --- a/src/eat.c +++ b/src/eat.c @@ -1,87 +1,94 @@ -/* NetHack 3.6 eat.c $NHDT-Date: 1574900825 2019/11/28 00:27:05 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.206 $ */ +/* NetHack 5.0 eat.c $NHDT-Date: 1740534854 2025/02/25 17:54:14 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.344 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2012. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -STATIC_PTR int NDECL(eatmdone); -STATIC_PTR int NDECL(eatfood); -STATIC_PTR struct obj *FDECL(costly_tin, (int)); -STATIC_PTR int NDECL(opentin); -STATIC_PTR int NDECL(unfaint); - -STATIC_DCL const char *FDECL(food_xname, (struct obj *, BOOLEAN_P)); -STATIC_DCL void FDECL(choke, (struct obj *)); -STATIC_DCL void NDECL(recalc_wt); -STATIC_DCL unsigned FDECL(obj_nutrition, (struct obj *)); -STATIC_DCL struct obj *FDECL(touchfood, (struct obj *)); -STATIC_DCL void NDECL(do_reset_eat); -STATIC_DCL void FDECL(done_eating, (BOOLEAN_P)); -STATIC_DCL void FDECL(cprefx, (int)); -STATIC_DCL int FDECL(intrinsic_possible, (int, struct permonst *)); -STATIC_DCL void FDECL(givit, (int, struct permonst *)); -STATIC_DCL void FDECL(cpostfx, (int)); -STATIC_DCL void FDECL(consume_tin, (const char *)); -STATIC_DCL void FDECL(start_tin, (struct obj *)); -STATIC_DCL int FDECL(eatcorpse, (struct obj *)); -STATIC_DCL void FDECL(start_eating, (struct obj *, BOOLEAN_P)); -STATIC_DCL void FDECL(fprefx, (struct obj *)); -STATIC_DCL void FDECL(fpostfx, (struct obj *)); -STATIC_DCL int NDECL(bite); -STATIC_DCL int FDECL(edibility_prompts, (struct obj *)); -STATIC_DCL int FDECL(rottenfood, (struct obj *)); -STATIC_DCL void NDECL(eatspecial); -STATIC_DCL int FDECL(bounded_increase, (int, int, int)); -STATIC_DCL void FDECL(accessory_has_effect, (struct obj *)); -STATIC_DCL void FDECL(eataccessory, (struct obj *)); -STATIC_DCL const char *FDECL(foodword, (struct obj *)); -STATIC_DCL int FDECL(tin_variety, (struct obj *, BOOLEAN_P)); -STATIC_DCL boolean FDECL(maybe_cannibal, (int, BOOLEAN_P)); - -char msgbuf[BUFSZ]; +staticfn int eatmdone(void); +staticfn int eatfood(void); +staticfn struct obj *costly_tin(int); +staticfn int opentin(void); +staticfn int unfaint(void); + +staticfn const char *food_xname(struct obj *, boolean); +staticfn void choke(struct obj *); +staticfn void recalc_wt(void); +staticfn int adj_victual_nutrition(void); +staticfn struct obj *touchfood(struct obj *); +staticfn void do_reset_eat(void); +staticfn void done_eating(boolean); +staticfn void cprefx(int); +staticfn boolean temp_givit(int, struct permonst *); +staticfn void givit(int, struct permonst *); +staticfn void eye_of_newt_buzz(void); +staticfn void cpostfx(int); +staticfn void use_up_tin(struct obj *) NONNULLARG1; +staticfn void consume_tin(const char *); +staticfn void start_tin(struct obj *); +staticfn int eatcorpse(struct obj *); +staticfn void start_eating(struct obj *, boolean); +staticfn void garlic_breath(struct monst *); +staticfn boolean fprefx(struct obj *); +staticfn void fpostfx(struct obj *); +staticfn int bite(void); +staticfn int edibility_prompts(struct obj *); +staticfn int doeat_nonfood(struct obj *); +staticfn int tinopen_ok(struct obj *); +staticfn int rottenfood(struct obj *); +staticfn void eatspecial(void); +staticfn int bounded_increase(int, int, int); +staticfn void accessory_has_effect(struct obj *); +staticfn void eataccessory(struct obj *); +staticfn const char *foodword(struct obj *); +staticfn int tin_variety(struct obj *, boolean); +staticfn boolean maybe_cannibal(int, boolean); +staticfn int eat_ok(struct obj *); +staticfn int offer_ok(struct obj *); +staticfn int tin_ok(struct obj *); /* also used to see if you're allowed to eat cats and dogs */ -#define CANNIBAL_ALLOWED() (Role_if(PM_CAVEMAN) || Race_if(PM_ORC)) - -/* monster types that cause hero to be turned into stone if eaten */ -#define flesh_petrifies(pm) (touch_petrifies(pm) || (pm) == &mons[PM_MEDUSA]) +#define CANNIBAL_ALLOWED() (Role_if(PM_CAVE_DWELLER) || Race_if(PM_ORC)) /* Rider corpses are treated as non-rotting so that attempting to eat one - will be sure to reach the stage of eating where that meal is fatal */ + will be sure to reach the stage of eating where that meal is fatal; + acid blob corpses eventually rot away to nothing but before that happens + they can be sacrificed regardless of age which implies that they never + become rotten */ #define nonrotting_corpse(mnum) \ - ((mnum) == PM_LIZARD || (mnum) == PM_LICHEN || is_rider(&mons[mnum])) + ((mnum) == PM_LIZARD || (mnum) == PM_LICHEN \ + || is_rider(&mons[mnum]) \ + || (mnum) == PM_ACID_BLOB) /* non-rotting non-corpses; unlike lizard corpses, these items will behave as if rotten if they are cursed (fortune cookies handled elsewhere) */ #define nonrotting_food(otyp) \ ((otyp) == LEMBAS_WAFER || (otyp) == CRAM_RATION) -STATIC_OVL NEARDATA const char comestibles[] = { FOOD_CLASS, 0 }; -STATIC_OVL NEARDATA const char offerfodder[] = { FOOD_CLASS, AMULET_CLASS, - 0 }; - -/* Gold must come first for getobj(). */ -STATIC_OVL NEARDATA const char allobj[] = { - COIN_CLASS, WEAPON_CLASS, ARMOR_CLASS, POTION_CLASS, - SCROLL_CLASS, WAND_CLASS, RING_CLASS, AMULET_CLASS, - FOOD_CLASS, TOOL_CLASS, GEM_CLASS, ROCK_CLASS, - BALL_CLASS, CHAIN_CLASS, SPBOOK_CLASS, 0 +/* see hunger states in hack.h - texts used on bottom line + Also used in botl.c and insight.c */ +const char *const hu_stat[] = { + "Satiated", " ", "Hungry ", "Weak ", + "Fainting", "Fainted ", "Starved " }; -STATIC_OVL boolean force_save_hs = FALSE; +static const struct victual_info zero_victual = { 0 }; -/* see hunger states in hack.h - texts used on bottom line */ -const char *hu_stat[] = { "Satiated", " ", "Hungry ", "Weak ", - "Fainting", "Fainted ", "Starved " }; +/* used by getobj() callback routines eat_ok()/offer_ok()/tin_ok() to + indicate whether player was given an opportunity to eat or offer or + tin an item on the floor and declined, in order to insert "else" + into the "you don't have anything [else] to {eat | offer | tin}" + feedback if hero lacks any suitable items in inventory + [reinitialized every time it's used so does not need to be placed + in struct instance_globals g for potential bulk reinitialization] */ +static int getobj_else = 0; /* * Decide whether a particular object can be eaten by the possibly * polymorphed character. Not used for monster checks. */ boolean -is_edible(obj) -register struct obj *obj; +is_edible(struct obj *obj) { /* protect invocation tools but not Rider corpses (handled elsewhere)*/ /* if (obj->oclass != FOOD_CLASS && obj_resists(obj, 0, 0)) */ @@ -90,8 +97,12 @@ register struct obj *obj; /* above also prevents the Amulet from being eaten, so we must never allow fake amulets to be eaten either [which is already the case] */ - if (metallivorous(youmonst.data) && is_metallic(obj) - && (youmonst.data != &mons[PM_RUST_MONSTER] || is_rustprone(obj))) + if (gy.youmonst.data == &mons[PM_FIRE_ELEMENTAL] + && is_flammable(obj)) + return TRUE; + + if (metallivorous(gy.youmonst.data) && is_metallic(obj) + && (gy.youmonst.data != &mons[PM_RUST_MONSTER] || is_rustprone(obj))) return TRUE; /* Ghouls only eat non-veggy corpses or eggs (see dogfood()) */ @@ -101,26 +112,25 @@ register struct obj *obj; || (obj->otyp == EGG)); if (u.umonnum == PM_GELATINOUS_CUBE && is_organic(obj) - /* [g.cubes can eat containers and retain all contents + /* [g-cubes can eat containers and retain all contents as engulfed items, but poly'd player can't do that] */ && !Has_contents(obj)) return TRUE; - /* return (boolean) !!index(comestibles, obj->oclass); */ return (boolean) (obj->oclass == FOOD_CLASS); } /* used for hero init, life saving (if choking), and prayer results of fix starving, fix weak from hunger, or golden glow boon (if u.uhunger < 900) */ void -init_uhunger() +init_uhunger(void) { - context.botl = (u.uhs != NOT_HUNGRY || ATEMP(A_STR) < 0); + disp.botl = (u.uhs != NOT_HUNGRY || ATEMP(A_STR) < 0); u.uhunger = 900; u.uhs = NOT_HUNGRY; if (ATEMP(A_STR) < 0) { ATEMP(A_STR) = 0; - (void) encumber_msg(); + encumber_msg(); } } @@ -148,21 +158,19 @@ static const struct { { "", 0, 0, 0 } }; #define TTSZ SIZE(tintxts) -static char *eatmbuf = 0; /* set by cpostfx() */ - -/* called after mimicing is over */ -STATIC_PTR int -eatmdone(VOID_ARGS) +/* called after mimicking is over */ +staticfn int +eatmdone(void) { /* release `eatmbuf' */ - if (eatmbuf) { - if (nomovemsg == eatmbuf) - nomovemsg = 0; - free((genericptr_t) eatmbuf), eatmbuf = 0; + if (ge.eatmbuf) { + if (gn.nomovemsg == ge.eatmbuf) + gn.nomovemsg = 0; + free((genericptr_t) ge.eatmbuf), ge.eatmbuf = 0; } /* update display */ if (U_AP_TYPE) { - youmonst.m_ap_type = M_AP_NOTHING; + gy.youmonst.m_ap_type = M_AP_NOTHING; newsym(u.ux, u.uy); } return 0; @@ -170,19 +178,19 @@ eatmdone(VOID_ARGS) /* called when hallucination is toggled */ void -eatmupdate() +eatmupdate(void) { const char *altmsg = 0; int altapp = 0; /* lint suppression */ - if (!eatmbuf || nomovemsg != eatmbuf) + if (!ge.eatmbuf || gn.nomovemsg != ge.eatmbuf) return; - if (is_obj_mappear(&youmonst,ORANGE) && !Hallucination) { + if (is_obj_mappear(&gy.youmonst,ORANGE) && !Hallucination) { /* revert from hallucinatory to "normal" mimicking */ altmsg = "You now prefer mimicking yourself."; altapp = GOLD_PIECE; - } else if (is_obj_mappear(&youmonst,GOLD_PIECE) && Hallucination) { + } else if (is_obj_mappear(&gy.youmonst,GOLD_PIECE) && Hallucination) { /* won't happen; anything which might make immobilized hero begin hallucinating (black light attack, theft of Grayswandir) will terminate the mimicry first */ @@ -192,22 +200,21 @@ eatmupdate() if (altmsg) { /* replace end-of-mimicking message */ - if (strlen(altmsg) > strlen(eatmbuf)) { - free((genericptr_t) eatmbuf); - eatmbuf = (char *) alloc(strlen(altmsg) + 1); + unsigned amlen = Strlen(altmsg); + if (amlen > Strlen(ge.eatmbuf)) { + free((genericptr_t) ge.eatmbuf); + ge.eatmbuf = (char *) alloc(amlen + 1); } - nomovemsg = strcpy(eatmbuf, altmsg); + gn.nomovemsg = strcpy(ge.eatmbuf, altmsg); /* update current image */ - youmonst.mappearance = altapp; + gy.youmonst.mappearance = altapp; newsym(u.ux, u.uy); } } /* ``[the(] singular(food, xname) [)]'' */ -STATIC_OVL const char * -food_xname(food, the_pfx) -struct obj *food; -boolean the_pfx; +staticfn const char * +food_xname(struct obj *food, boolean the_pfx) { const char *result; @@ -234,9 +241,8 @@ boolean the_pfx; * * To a full belly all food is bad. (It.) */ -STATIC_OVL void -choke(food) -struct obj *food; +staticfn void +choke(struct obj *food) { /* only happens if you were satiated */ if (u.uhs != SATIATED) { @@ -249,17 +255,17 @@ struct obj *food; exercise(A_CON, FALSE); - if (Breathless || (!Strangled && !rn2(20))) { + if (Breathless || Hunger || (!Strangled && !rn2(20))) { /* choking by eating AoS doesn't involve stuffing yourself */ if (food && food->otyp == AMULET_OF_STRANGULATION) { You("choke, but recover your composure."); return; } You("stuff yourself and then vomit voluminously."); - morehungry(1000); /* you just got *very* sick! */ + morehungry(Hunger ? (u.uhunger - 60) : 1000); /* just got very sick! */ vomit(); } else { - killer.format = KILLED_BY_AN; + svk.killer.format = KILLED_BY_AN; /* * Note all "killer"s below read "Choked on %s" on the * high score list & tombstone. So plan accordingly. @@ -267,86 +273,98 @@ struct obj *food; if (food) { You("choke over your %s.", foodword(food)); if (food->oclass == COIN_CLASS) { - Strcpy(killer.name, "very rich meal"); + Strcpy(svk.killer.name, "very rich meal"); } else { - killer.format = KILLED_BY; - Strcpy(killer.name, killer_xname(food)); + svk.killer.format = KILLED_BY; + Strcpy(svk.killer.name, killer_xname(food)); } } else { You("choke over it."); - Strcpy(killer.name, "quick snack"); + Strcpy(svk.killer.name, "quick snack"); } You("die..."); done(CHOKING); } } -/* modify object wt. depending on time spent consuming it */ -STATIC_OVL void -recalc_wt() +/* modify victual.piece->owt depending on time spent consuming it */ +staticfn void +recalc_wt(void) { - struct obj *piece = context.victual.piece; + struct obj *piece = svc.context.victual.piece; + if (!piece) { impossible("recalc_wt without piece"); return; } debugpline1("Old weight = %d", piece->owt); - debugpline2("Used time = %d, Req'd time = %d", context.victual.usedtime, - context.victual.reqtime); + debugpline2("Used time = %d, Req'd time = %d", + svc.context.victual.usedtime, svc.context.victual.reqtime); piece->owt = weight(piece); debugpline1("New weight = %d", piece->owt); } /* called when eating interrupted by an event */ void -reset_eat() +reset_eat(void) { /* we only set a flag here - the actual reset process is done after * the round is spent eating. */ - if (context.victual.eating && !context.victual.doreset) { + if (svc.context.victual.eating && !svc.context.victual.doreset) { debugpline0("reset_eat..."); - context.victual.doreset = TRUE; + svc.context.victual.doreset = 1; } return; } -/* base nutrition of a food-class object */ -STATIC_OVL unsigned -obj_nutrition(otmp) -struct obj *otmp; +/* base nutrition of a food-class object; this used to include a variation + of the code that is now in adj_victual_nutrition() and was moved due to + its affect on weight() */ +unsigned +obj_nutrition(struct obj *otmp) { unsigned nut = (otmp->otyp == CORPSE) ? mons[otmp->corpsenm].cnutrit : otmp->globby ? otmp->owt : (unsigned) objects[otmp->otyp].oc_nutrition; - if (otmp->otyp == LEMBAS_WAFER) { - if (maybe_polyd(is_elf(youmonst.data), Race_if(PM_ELF))) - nut += nut / 4; /* 800 -> 1000 */ - else if (maybe_polyd(is_orc(youmonst.data), Race_if(PM_ORC))) - nut -= nut / 4; /* 800 -> 600 */ - /* prevent polymorph making a partly eaten wafer - become more nutritious than an untouched one */ - if (otmp->oeaten >= nut) - otmp->oeaten = (otmp->oeaten < objects[LEMBAS_WAFER].oc_nutrition) - ? (nut - 1) : nut; - } else if (otmp->otyp == CRAM_RATION) { - if (maybe_polyd(is_dwarf(youmonst.data), Race_if(PM_DWARF))) - nut += nut / 6; /* 600 -> 700 */ - } return nut; } -STATIC_OVL struct obj * -touchfood(otmp) -struct obj *otmp; +/* nutrition increment for next byte; this used to be factored into + victual.piece->oeaten but that produced weight change if hero + polymorphed to or from one of the races which has nutrition adjusted */ +staticfn int +adj_victual_nutrition(void) +{ + int otyp = svc.context.victual.piece->otyp; + /* note: adj_victual_nutrition() is only called when 'nmod' is negative */ + int nut = -svc.context.victual.nmod; /* convert 'nmod' to positive */ + + assert(nut > 0); + if (otyp == LEMBAS_WAFER) { + if (maybe_polyd(is_elf(gy.youmonst.data), Race_if(PM_ELF))) + nut += (nut + 2) / 4; /* 800 -> 1000 */ + else if (maybe_polyd(is_orc(gy.youmonst.data), Race_if(PM_ORC))) + nut -= (nut + 2) / 4; /* 800 -> 600 */ + } else if (otyp == CRAM_RATION) { + if (maybe_polyd(is_dwarf(gy.youmonst.data), Race_if(PM_DWARF))) + nut += (nut + 3) / 6; /* 600 -> 700 */ + } + nut = max(nut, 1); + return nut; +} + +/* might destroy otmp if hero drops it */ +staticfn struct obj * +touchfood(struct obj *otmp) { if (otmp->quan > 1L) { if (!carried(otmp)) (void) splitobj(otmp, otmp->quan - 1L); else otmp = splitobj(otmp, 1L); - debugpline0("split object,"); + debugpline0("split food,"); } if (!otmp->oeaten) { @@ -356,14 +374,14 @@ struct obj *otmp; if (carried(otmp)) { freeinv(otmp); - if (inv_cnt(FALSE) >= 52) { + if (inv_cnt(FALSE) >= invlet_basic) { sellobj_state(SELL_DONTSELL); dropy(otmp); sellobj_state(SELL_NORMAL); + if (otmp->where == OBJ_DELETED) + otmp = (struct obj *) NULL; } else { - otmp->nomerge = 1; /* used to prevent merge */ - otmp = addinv(otmp); - otmp->nomerge = 0; + otmp = addinv_nomerge(otmp); } } return otmp; @@ -375,13 +393,11 @@ struct obj *otmp; * in do_reset_eat(). */ void -food_disappears(obj) -struct obj *obj; +food_disappears(struct obj *obj) { - if (obj == context.victual.piece) { - context.victual.piece = (struct obj *) 0; - context.victual.o_id = 0; - } + if (obj == svc.context.victual.piece) + svc.context.victual = zero_victual; /* victual.piece = 0, .o_id = 0 */ + if (obj->timed) obj_stop_timers(obj); } @@ -390,32 +406,37 @@ struct obj *obj; so the sequence start eating/opening, get interrupted, name the food, resume eating/opening would restart from scratch */ void -food_substitution(old_obj, new_obj) -struct obj *old_obj, *new_obj; +food_substitution(struct obj *old_obj, struct obj *new_obj) { - if (old_obj == context.victual.piece) { - context.victual.piece = new_obj; - context.victual.o_id = new_obj->o_id; + if (old_obj == svc.context.victual.piece) { + svc.context.victual.piece = new_obj; + svc.context.victual.o_id = new_obj->o_id; } - if (old_obj == context.tin.tin) { - context.tin.tin = new_obj; - context.tin.o_id = new_obj->o_id; + if (old_obj == svc.context.tin.tin) { + svc.context.tin.tin = new_obj; + svc.context.tin.o_id = new_obj->o_id; } } -STATIC_OVL void -do_reset_eat() +staticfn void +do_reset_eat(void) { debugpline0("do_reset_eat..."); - if (context.victual.piece) { - context.victual.o_id = 0; - context.victual.piece = touchfood(context.victual.piece); - if (context.victual.piece) - context.victual.o_id = context.victual.piece->o_id; - recalc_wt(); - } - context.victual.fullwarn = context.victual.eating = - context.victual.doreset = FALSE; + if (svc.context.victual.piece) { + struct obj *otmp; + + svc.context.victual.o_id = 0; + otmp = touchfood(svc.context.victual.piece); + svc.context.victual.piece = otmp; + if (otmp) { + svc.context.victual.o_id = otmp->o_id; + recalc_wt(); + } + } + svc.context.victual.fullwarn + = svc.context.victual.eating + = svc.context.victual.doreset + = 0; /* Do not set canchoke to FALSE; if we continue eating the same object * we need to know if canchoke was set when they started eating it the * previous time. And if we don't continue eating the same object @@ -425,21 +446,91 @@ do_reset_eat() newuhs(FALSE); } +/* if 'prop' is only set because of a timed value (so not an intrinsic + attribute or because of polymorph shape or worn or carried gear), return + its timeout, otherwise return 0; used by enlightenment */ +long +temp_resist(int prop) +{ + struct prop *p = &u.uprops[prop]; + long timeout = p->intrinsic & TIMEOUT; + + if (timeout + /* and if not also protected by polymorph form */ + && (p->intrinsic & ~TIMEOUT) == 0L + /* and not by worn gear (dragon armor) */ + && !p->extrinsic + /* and property is not blocked; we don't expect this, but if it + is then the timeout doesn't matter so we won't extend that */ + && !p->blocked) { + return timeout; + } + return 0L; +} + +/* if temporary acid or stoning resistance is timing out while eating + something which that resistance is protecting against, caller will + extend resistance's duration so that it times out after meal finishes */ +boolean +eating_dangerous_corpse(int res) +{ + struct obj *food; + int mnum; + + if (go.occupation == eatfood + && (food = svc.context.victual.piece) != 0 + && food->otyp == CORPSE + && (mnum = food->corpsenm) >= LOW_PM + && (carried(food) || obj_here(food, u.ux, u.uy))) { + + if (res == ACID_RES && acidic(&mons[mnum])) + return TRUE; + /* flesh_petrifies() includes Medusa as well as touch_petrifies() */ + if (res == STONE_RES && flesh_petrifies(&mons[mnum])) + return TRUE; + } + return FALSE; +} + +#if 0 /* no longer used */ +staticfn void maybe_extend_timed_resist(int); + +/* if temp resist against 'prop' is about to timeout, extend it slightly */ +staticfn void +maybe_extend_timed_resist(int prop) +{ + long timeout = temp_resist(prop); + + /* if hero is being protected from nasty effects of current meal by + temporary resistance (timed acid resist or timed stoning resist), + prevent expiration from occurring while the meal is in progress + so that player doesn't get feedback about becoming more vulnerable + and then have the hero stay unharmed; has a minor side-effect of + also extending the protection against other attacks of the sort + being resisted */ + if (timeout == 1L) { + set_itimeout(&u.uprops[prop].intrinsic, 2L); + } +} +#endif + /* called each move during eating process */ -STATIC_PTR int -eatfood(VOID_ARGS) +staticfn int +eatfood(void) { - if (!context.victual.piece - || (!carried(context.victual.piece) - && !obj_here(context.victual.piece, u.ux, u.uy))) { + struct obj *food = svc.context.victual.piece; + + if (food && !carried(food) && !obj_here(food, u.ux, u.uy)) + food = 0; + if (!food) { /* maybe it was stolen? */ do_reset_eat(); return 0; } - if (!context.victual.eating) + if (!svc.context.victual.eating) return 0; - if (++context.victual.usedtime <= context.victual.reqtime) { + if (++svc.context.victual.usedtime <= svc.context.victual.reqtime) { if (bite()) return 0; return 1; /* still busy */ @@ -449,21 +540,24 @@ eatfood(VOID_ARGS) } } -STATIC_OVL void -done_eating(message) -boolean message; +staticfn void +done_eating(boolean message) { - struct obj *piece = context.victual.piece; + struct obj *piece = svc.context.victual.piece; piece->in_use = TRUE; - occupation = 0; /* do this early, so newuhs() knows we're done */ + go.occupation = 0; /* do this early, so newuhs() knows we're done */ newuhs(FALSE); - if (nomovemsg) { + if (gn.nomovemsg) { if (message) - pline1(nomovemsg); - nomovemsg = 0; - } else if (message) - You("finish eating %s.", food_xname(piece, TRUE)); + pline1(gn.nomovemsg); + gn.nomovemsg = 0; + } else if (message) { + You("finish %s %s.", + (gy.youmonst.data == &mons[PM_FIRE_ELEMENTAL]) ? "consuming" + : "eating", + food_xname(piece, TRUE)); + } if (piece->otyp == CORPSE || piece->globby) cpostfx(piece->corpsenm); @@ -474,42 +568,62 @@ boolean message; useup(piece); else useupf(piece, 1L); - context.victual.piece = (struct obj *) 0; - context.victual.o_id = 0; - context.victual.fullwarn = context.victual.eating = - context.victual.doreset = FALSE; + + svc.context.victual = zero_victual; /* victual.piece = 0, .o_id = 0 */ } void -eating_conducts(pd) -struct permonst *pd; +eating_conducts(struct permonst *pd) { - u.uconduct.food++; - if (!vegan(pd)) - u.uconduct.unvegan++; - if (!vegetarian(pd)) + int ll_conduct = 0; + + if (!u.uconduct.food++) { + livelog_printf(LL_CONDUCT, "ate for the first time - %s", + pd->pmnames[NEUTRAL]); + ll_conduct++; + } + if (!vegan(pd)) { + if (!u.uconduct.unvegan++ && !ll_conduct) { + livelog_printf(LL_CONDUCT, + "consumed animal products (%s) for the first time", + pd->pmnames[NEUTRAL]); + ll_conduct++; + } + } + if (!vegetarian(pd)) { + if (!u.uconduct.unvegetarian && !ll_conduct) + livelog_printf(LL_CONDUCT, "tasted meat (%s) for the first time", + pd->pmnames[NEUTRAL]); violated_vegetarian(); + } } /* handle side-effects of mind flayer's tentacle attack */ int -eat_brains(magr, mdef, visflag, dmg_p) -struct monst *magr, *mdef; -boolean visflag; -int *dmg_p; /* for dishing out extra damage in lieu of Int loss */ +eat_brains( + struct monst *magr, + struct monst *mdef, + boolean visflag, + int *dmg_p) /* for dishing out extra damage in lieu of Int loss */ { struct permonst *pd = mdef->data; boolean give_nutrit = FALSE; - int result = MM_HIT, xtra_dmg = rnd(10); + int result = M_ATTK_HIT, xtra_dmg = rnd(10); + + /* previous tentacle attack might have triggered fatal passive + counterattack [callers ought to be updated to avoid this situation] */ + if (magr != &gy.youmonst && DEADMONSTER(magr)) { + return M_ATTK_AGR_DIED; + } if (noncorporeal(pd)) { if (visflag) pline("%s brain is unharmed.", - (mdef == &youmonst) ? "Your" : s_suffix(Monnam(mdef))); - return MM_MISS; /* side-effects can't occur */ - } else if (magr == &youmonst) { + (mdef == &gy.youmonst) ? "Your" : s_suffix(Monnam(mdef))); + return M_ATTK_MISS; /* side-effects can't occur */ + } else if (magr == &gy.youmonst) { You("eat %s brain!", s_suffix(mon_nam(mdef))); - } else if (mdef == &youmonst) { + } else if (mdef == &gy.youmonst) { Your("brain is eaten!"); } else { /* monster against monster */ if (visflag && canspotmon(mdef)) @@ -520,9 +634,10 @@ int *dmg_p; /* for dishing out extra damage in lieu of Int loss */ /* mind flayer has attempted to eat the brains of a petrification inducing critter (most likely Medusa; attacking a cockatrice via tentacle-touch should have been caught before reaching this far) */ - if (magr == &youmonst) { + if (magr == &gy.youmonst) { if (!Stone_resistance && !Stoned) - make_stoned(5L, (char *) 0, KILLED_BY_AN, pd->mname); + make_stoned(5L, (char *) 0, KILLED_BY_AN, + pmname(pd, Mgender(mdef))); } else { /* no need to check for poly_when_stoned or Stone_resistance; mind flayers don't have those capabilities */ @@ -531,17 +646,17 @@ int *dmg_p; /* for dishing out extra damage in lieu of Int loss */ monstone(magr); if (!DEADMONSTER(magr)) { /* life-saved; don't continue eating the brains */ - return MM_MISS; + return M_ATTK_MISS; } else { if (magr->mtame && !visflag) /* parallels mhitm.c's brief_feeling */ You("have a sad thought for a moment, then it passes."); - return MM_AGR_DIED; + return M_ATTK_AGR_DIED; } } } - if (magr == &youmonst) { + if (magr == &gy.youmonst) { /* * player mind flayer is eating something's brain */ @@ -549,11 +664,12 @@ int *dmg_p; /* for dishing out extra damage in lieu of Int loss */ if (mindless(pd)) { /* (cannibalism not possible here) */ pline("%s doesn't notice.", Monnam(mdef)); /* all done; no extra harm inflicted upon target */ - return MM_MISS; + return M_ATTK_MISS; } else if (is_rider(pd)) { pline("Ingesting that is fatal."); - Sprintf(killer.name, "unwisely ate the brain of %s", pd->mname); - killer.format = NO_KILLER_PREFIX; + Sprintf(svk.killer.name, "unwisely ate the brain of %s", + pmname(pd, Mgender(mdef))); + svk.killer.format = NO_KILLER_PREFIX; done(DIED); /* life-saving needed to reach here */ exercise(A_WIS, FALSE); @@ -565,16 +681,16 @@ int *dmg_p; /* for dishing out extra damage in lieu of Int loss */ ABASE(A_INT) += rnd(4); if (ABASE(A_INT) > AMAX(A_INT)) ABASE(A_INT) = AMAX(A_INT); - context.botl = 1; + disp.botl = TRUE; } exercise(A_WIS, TRUE); *dmg_p += xtra_dmg; } - /* targetting another mind flayer or your own underlying species + /* targeting another mind flayer or your own underlying species is cannibalism */ (void) maybe_cannibal(monsndx(pd), TRUE); - } else if (mdef == &youmonst) { + } else if (mdef == &gy.youmonst) { /* * monster mind flayer is eating hero's brain */ @@ -583,8 +699,8 @@ int *dmg_p; /* for dishing out extra damage in lieu of Int loss */ static NEARDATA const char brainlessness[] = "brainlessness"; if (Lifesaved) { - Strcpy(killer.name, brainlessness); - killer.format = KILLED_BY; + Strcpy(svk.killer.name, brainlessness); + svk.killer.format = KILLED_BY; done(DIED); /* amulet of life saving has now been used up */ pline("Unfortunately your brain is still gone."); @@ -594,8 +710,8 @@ int *dmg_p; /* for dishing out extra damage in lieu of Int loss */ } else { Your("last thought fades away."); } - Strcpy(killer.name, brainlessness); - killer.format = KILLED_BY; + Strcpy(svk.killer.name, brainlessness); + svk.killer.format = KILLED_BY; done(DIED); /* can only get here when in wizard or explore mode and user has explicitly chosen not to die; arbitrarily boost intelligence */ @@ -613,11 +729,11 @@ int *dmg_p; /* for dishing out extra damage in lieu of Int loss */ if (mindless(pd)) { if (visflag && canspotmon(mdef)) pline("%s doesn't notice.", Monnam(mdef)); - return MM_MISS; + return M_ATTK_MISS; } else if (is_rider(pd)) { mondied(magr); if (DEADMONSTER(magr)) - result = MM_AGR_DIED; + result = M_ATTK_AGR_DIED; /* Rider takes extra damage regardless of whether attacker dies */ *dmg_p += xtra_dmg; } else { @@ -638,10 +754,8 @@ int *dmg_p; /* for dishing out extra damage in lieu of Int loss */ } /* eating a corpse or egg of one's own species is usually naughty */ -STATIC_OVL boolean -maybe_cannibal(pm, allowmsg) -int pm; -boolean allowmsg; +staticfn boolean +maybe_cannibal(int pm, boolean allowmsg) { static NEARDATA long ate_brains = 0L; struct permonst *fptr = &mons[pm]; /* food type */ @@ -649,9 +763,9 @@ boolean allowmsg; /* when poly'd into a mind flayer, multiple tentacle hits in one turn cause multiple digestion checks to occur; avoid giving multiple luck penalties for the same attack */ - if (moves == ate_brains) + if (svm.moves == ate_brains) return FALSE; - ate_brains = moves; /* ate_anything, not just brains... */ + ate_brains = svm.moves; /* ate_anything, not just brains... */ if (!CANNIBAL_ALLOWED() /* non-cannibalistic heroes shouldn't eat own species ever @@ -659,8 +773,8 @@ boolean allowmsg; (even if having the form of something which doesn't care about cannibalism--hero's innate traits aren't altered) */ && (your_race(fptr) - || (Upolyd && same_race(youmonst.data, fptr)) - || (u.ulycn >= LOW_PM && were_beastie(pm) == u.ulycn))) { + || (Upolyd && same_race(gy.youmonst.data, fptr)) + || (ismnum(u.ulycn) && were_beastie(pm) == u.ulycn))) { if (allowmsg) { if (Upolyd && your_race(fptr)) You("have a bad feeling deep inside."); @@ -673,21 +787,25 @@ boolean allowmsg; return FALSE; } -STATIC_OVL void -cprefx(pm) -register int pm; +staticfn void +cprefx(int pm) { (void) maybe_cannibal(pm, TRUE); if (flesh_petrifies(&mons[pm])) { if (!Stone_resistance - && !(poly_when_stoned(youmonst.data) + && !(poly_when_stoned(gy.youmonst.data) && polymon(PM_STONE_GOLEM))) { - Sprintf(killer.name, "tasting %s meat", mons[pm].mname); - killer.format = KILLED_BY; + /* if food was a tin, use it up early to keep it out of bones */ + if (svc.context.tin.tin) + use_up_tin(svc.context.tin.tin); + + Sprintf(svk.killer.name, "tasting %s meat", + mons[pm].pmnames[NEUTRAL]); + svk.killer.format = KILLED_BY; You("turn to stone."); done(STONING); - if (context.victual.piece) - context.victual.eating = FALSE; + if (svc.context.victual.piece) + svc.context.victual.eating = 0; return; /* lifesaved */ } } @@ -701,7 +819,8 @@ register int pm; case PM_LARGE_CAT: /* cannibals are allowed to eat domestic animals without penalty */ if (!CANNIBAL_ALLOWED()) { - You_feel("that eating the %s was a bad idea.", mons[pm].mname); + You_feel("that eating the %s was a bad idea.", + mons[pm].pmnames[NEUTRAL]); HAggravate_monster |= FROMOUTSIDE; } break; @@ -713,26 +832,29 @@ register int pm; case PM_PESTILENCE: case PM_FAMINE: { pline("Eating that is instantly fatal."); - Sprintf(killer.name, "unwisely ate the body of %s", mons[pm].mname); - killer.format = NO_KILLER_PREFIX; + Sprintf(svk.killer.name, "unwisely ate the body of %s", + mons[pm].pmnames[NEUTRAL]); + svk.killer.format = NO_KILLER_PREFIX; done(DIED); /* life-saving needed to reach here */ exercise(A_WIS, FALSE); - /* It so happens that since we know these monsters */ - /* cannot appear in tins, context.victual.piece will always */ - /* be what we want, which is not generally true. */ - if (revive_corpse(context.victual.piece)) { - context.victual.piece = (struct obj *) 0; - context.victual.o_id = 0; - } + /* revive an actual corpse; can't do that if it was a tin; + 5.0: this used to assume that such tins were impossible but + they can be wished for in wizard mode; they can't make it + to normal play though because bones creation empties them */ + if (svc.context.victual.piece /* Null for tins */ + && svc.context.victual.piece->otyp == CORPSE /* paranoia */ + && revive_corpse(svc.context.victual.piece)) + svc.context.victual = zero_victual; /* victual.piece=0, .o_id=0 */ return; } case PM_GREEN_SLIME: - if (!Slimed && !Unchanging && !slimeproof(youmonst.data)) { + if (!Slimed && !Unchanging && !slimeproof(gy.youmonst.data)) { You("don't feel very well."); make_slimed(10L, (char *) 0); delayed_killer(SLIMED, KILLED_BY_AN, ""); } + FALLTHROUGH; /* Fall through */ default: if (acidic(&mons[pm]) && Stoned) @@ -742,7 +864,7 @@ register int pm; } void -fix_petrification() +fix_petrification(void) { char buf[BUFSZ]; @@ -764,10 +886,8 @@ fix_petrification() */ /* intrinsic_possible() returns TRUE iff a monster can give an intrinsic. */ -STATIC_OVL int -intrinsic_possible(type, ptr) -int type; -register struct permonst *ptr; +int +intrinsic_possible(int type, struct permonst *ptr) { int res = 0; @@ -805,6 +925,14 @@ register struct permonst *ptr; res = (ptr->mconveys & MR_POISON) != 0; ifdebugresist("can get poison resistance"); break; + case ACID_RES: + res = (ptr->mconveys & MR_ACID) != 0; + ifdebugresist("can get acid resistance temporarily"); + break; + case STONE_RES: + res = (ptr->mconveys & MR_STONE) != 0; + ifdebugresist("can get stoning resistance temporarily"); + break; case TELEPORT: res = can_teleport(ptr); ifdebugresist("can get teleport"); @@ -825,17 +953,15 @@ register struct permonst *ptr; return res; } -/* givit() tries to give you an intrinsic based on the monster's level - * and what type of intrinsic it is trying to give you. +/* The "do we or do we not give the intrinsic" logic from givit(), extracted + * into its own function. Depends on the monster's level and the type of + * intrinsic it is trying to give you. */ -STATIC_OVL void -givit(type, ptr) -int type; -register struct permonst *ptr; +boolean +should_givit(int type, struct permonst *ptr) { - register int chance; + int chance; - debugpline1("Attempting to give intrinsic %d", type); /* some intrinsics are easier to get than others */ switch (type) { case POISON_RES: @@ -859,8 +985,27 @@ register struct permonst *ptr; break; } - if (ptr->mlevel <= rn2(chance)) - return; /* failed die roll */ + return (ptr->mlevel > rn2(chance)); +} + +staticfn boolean +temp_givit(int type, struct permonst *ptr) +{ + int chance = (type == STONE_RES) ? 6 : (type == ACID_RES) ? 3 : 0; + + return chance ? (ptr->mlevel > rn2(chance)) : FALSE; +} + +/* givit() tries to give you an intrinsic based on the monster's level + * and what type of intrinsic it is trying to give you. + */ +staticfn void +givit(int type, struct permonst *ptr) +{ + debugpline1("Attempting to give intrinsic %d", type); + + if (!should_givit(type, ptr) && !temp_givit(type, ptr)) + return; switch (type) { case FIRE_RES: @@ -934,16 +1079,54 @@ register struct permonst *ptr; see_monsters(); } break; + case ACID_RES: + debugpline0("Giving timed acid resistance"); + if (!Acid_resistance) + You_feel("%s.", Hallucination ? "secure from flashbacks" + : "less concerned about being harmed by acid"); + incr_itimeout(&HAcid_resistance, d(3, 6)); + break; + case STONE_RES: + debugpline0("Giving timed stoning resistance"); + if (!Stone_resistance) + You_feel("%s.", Hallucination ? "unusually limber" + : "less concerned about becoming petrified"); + incr_itimeout(&HStone_resistance, d(3, 6)); + break; default: debugpline0("Tried to give an impossible intrinsic"); break; } } +staticfn void +eye_of_newt_buzz(void) +{ + /* 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++; + if (u.uenmax > u.uenpeak) + u.uenpeak = u.uenmax; + } + u.uen = u.uenmax; + } + if (old_uen != u.uen) { + You_feel("a mild buzz."); + disp.botl = TRUE; + } + } +} + +DISABLE_WARNING_FORMAT_NONLITERAL + /* called after completely consuming a corpse */ -STATIC_OVL void -cpostfx(pm) -int pm; +staticfn void +cpostfx(int pm) { int tmp = 0; int catch_lycanthropy = NON_PM; @@ -951,27 +1134,10 @@ int pm; /* in case `afternmv' didn't get called for previously mimicking gold, clean up now to avoid `eatmbuf' memory leak */ - if (eatmbuf) + if (ge.eatmbuf) (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."); - context.botl = 1; - } - } - break; case PM_WRAITH: pluslvl(FALSE); break; @@ -990,7 +1156,7 @@ int pm; else u.uhp = u.uhpmax; make_blinded(0L, !u.ucreamed); - context.botl = 1; + disp.botl = TRUE; check_intrinsics = TRUE; /* might also convey poison resistance */ break; case PM_STALKER: @@ -1005,44 +1171,53 @@ int pm; HSee_invisible |= FROMOUTSIDE; } newsym(u.ux, u.uy); + FALLTHROUGH; /*FALLTHRU*/ case PM_YELLOW_LIGHT: case PM_GIANT_BAT: make_stunned((HStun & TIMEOUT) + 30L, FALSE); + FALLTHROUGH; /*FALLTHRU*/ case PM_BAT: make_stunned((HStun & TIMEOUT) + 30L, FALSE); break; case PM_GIANT_MIMIC: tmp += 10; + FALLTHROUGH; /*FALLTHRU*/ case PM_LARGE_MIMIC: tmp += 20; + FALLTHROUGH; /*FALLTHRU*/ case PM_SMALL_MIMIC: tmp += 20; - if (youmonst.data->mlet != S_MIMIC && !Unchanging) { + if (gy.youmonst.data->mlet != S_MIMIC && !Unchanging) { char buf[BUFSZ]; - - u.uconduct.polyselfs++; /* you're changing form */ - You_cant("resist the temptation to mimic %s.", - Hallucination ? "an orange" : "a pile of gold"); + const char *tempshape = !Hallucination ? "a pile of gold" + : "an orange"; + + if (!u.uconduct.polyselfs++) /* you're changing form */ + livelog_printf(LL_CONDUCT, + "changed form for the first time by mimicking %s", + tempshape); + You_cant("resist the temptation to mimic %s.", tempshape); /* A pile of gold can't ride. */ if (u.usteed) dismount_steed(DISMOUNT_FELL); nomul(-tmp); - multi_reason = "pretending to be a pile of gold"; + gm.multi_reason = "pretending to be a pile of gold"; Sprintf(buf, Hallucination ? "You suddenly dread being peeled and mimic %s again!" : "You now prefer mimicking %s again.", - an(Upolyd ? youmonst.data->mname : urace.noun)); - eatmbuf = dupstr(buf); - nomovemsg = eatmbuf; - afternmv = eatmdone; + an(Upolyd ? pmname(gy.youmonst.data, Ugender) + : gu.urace.noun)); + ge.eatmbuf = dupstr(buf); + gn.nomovemsg = ge.eatmbuf; + ga.afternmv = eatmdone; /* ??? what if this was set before? */ - youmonst.m_ap_type = M_AP_OBJECT; - youmonst.mappearance = Hallucination ? ORANGE : GOLD_PIECE; + gy.youmonst.m_ap_type = M_AP_OBJECT; + gy.youmonst.mappearance = Hallucination ? ORANGE : GOLD_PIECE; newsym(u.ux, u.uy); curs_on_u(); /* make gold symbol show up now */ @@ -1064,22 +1239,44 @@ int pm; make_stunned(2L, FALSE); if ((HConfusion & TIMEOUT) > 2) make_confused(2L, FALSE); + check_intrinsics = TRUE; /* might convey temporary stoning resist */ break; case PM_CHAMELEON: case PM_DOPPELGANGER: case PM_SANDESTIN: /* moot--they don't leave corpses */ + case PM_GENETIC_ENGINEER: if (Unchanging) { You_feel("momentarily different."); /* same as poly trap */ } else { - You_feel("a change coming over you."); - polyself(0); + /* polyself() is potentially fatal; if food is a tin, use it up + early to keep it out of bones */ + if (svc.context.tin.tin) { + use_up_tin(svc.context.tin.tin); + /* most tin effects end up being skipped */ + lesshungry(200 + (metallivorous(gy.youmonst.data) ? 5 : 0)); + } + + You("%s.", (pm == PM_GENETIC_ENGINEER) + ? "undergo a freakish metamorphosis" + : "feel a change coming over you"); + polyself(POLY_NOFLAGS); } break; + case PM_DISPLACER_BEAST: + if (!Displaced) /* give a message (before setting the timeout) */ + toggle_displacement((struct obj *) 0, 0L, TRUE); + incr_itimeout(&HDisplaced, d(6, 6)); + break; case PM_DISENCHANTER: /* picks an intrinsic at random and removes it; there's no feedback if hero already lacks the chosen ability */ debugpline0("using attrcurse to strip an intrinsic"); - attrcurse(); + (void) attrcurse(); + break; + case PM_DEATH: + case PM_PESTILENCE: + case PM_FAMINE: + /* life-saved; don't attempt to confer any intrinsics */ break; case PM_MIND_FLAYER: case PM_MASTER_MIND_FLAYER: @@ -1092,6 +1289,7 @@ int pm; } else { pline("For some reason, that tasted bland."); } + FALLTHROUGH; /*FALLTHRU*/ default: check_intrinsics = TRUE; @@ -1101,8 +1299,6 @@ int pm; /* possibly convey an intrinsic */ if (check_intrinsics) { struct permonst *ptr = &mons[pm]; - boolean conveys_STR = is_giant(ptr); - int i, count; if (dmgtype(ptr, AD_STUN) || dmgtype(ptr, AD_HALU) || pm == PM_VIOLET_FUNGUS) { @@ -1111,36 +1307,12 @@ int pm; 0L); } - /* Check the monster for all of the intrinsics. If this - * monster can give more than one, pick one to try to give - * from among all it can give. - * - * Strength from giants is now treated like an intrinsic - * rather than being given unconditionally. - */ - count = 0; /* number of possible intrinsics */ - tmp = 0; /* which one we will try to give */ - if (conveys_STR) { - count = 1; - tmp = -1; /* use -1 as fake prop index for STR */ - debugpline1("\"Intrinsic\" strength, %d", tmp); - } - for (i = 1; i <= LAST_PROP; i++) { - if (!intrinsic_possible(i, ptr)) - continue; - ++count; - /* a 1 in count chance of replacing the old choice - with this one, and a count-1 in count chance - of keeping the old choice (note that 1 in 1 and - 0 in 1 are what we want for the first candidate) */ - if (!rn2(count)) { - debugpline2("Intrinsic %d replacing %d", i, tmp); - tmp = i; - } - } - /* if strength is the only candidate, give it 50% chance */ - if (conveys_STR && count == 1 && !rn2(2)) - tmp = 0; + /* Eating magical monsters can give you some magical energy. */ + if (attacktype(ptr, AT_MAGC) || pm == PM_NEWT) + eye_of_newt_buzz(); + + tmp = corpse_intrinsic(ptr); + /* if something was chosen, give it now (givit() might fail) */ if (tmp == -1) gainstr((struct obj *) 0, 0, TRUE); @@ -1148,15 +1320,60 @@ int pm; givit(tmp, ptr); } /* check_intrinsics */ - if (catch_lycanthropy >= LOW_PM) { + if (ismnum(catch_lycanthropy)) { set_ulycn(catch_lycanthropy); retouch_equipment(2); } return; } +RESTORE_WARNING_FORMAT_NONLITERAL + +/* Choose (one of) the intrinsics granted by a corpse, and return it. + * If this corpse gives no intrinsics, return 0. + * For the special not-real-prop cases of strength gain from giants + * return fake prop value of -1. + * Non-deterministic; should only be called once per corpse. + */ +int +corpse_intrinsic(struct permonst *ptr) +{ + /* Check the monster for all of the intrinsics. If this + * monster can give more than one, pick one to try to give + * from among all it can give. + */ + boolean conveys_STR = is_giant(ptr); + int i; + int count = 0; /* number of possible intrinsics */ + int prop = 0; /* which one we will try to give */ + + if (conveys_STR) { + count = 1; + prop = -1; /* use -1 as fake prop index for STR */ + debugpline1("\"Intrinsic\" strength, %d", prop); + } + for (i = 1; i <= LAST_PROP; i++) { + if (!intrinsic_possible(i, ptr)) + continue; + ++count; + /* a 1 in count chance of replacing the old choice + with this one, and a count-1 in count chance + of keeping the old choice (note that 1 in 1 and + 0 in 1 are what we want for the first candidate) */ + if (!rn2(count)) { + debugpline2("Intrinsic %d replacing %d", i, prop); + prop = i; + } + } + /* if strength is the only candidate, give it 50% chance */ + if (conveys_STR && count == 1 && !rn2(2)) + prop = 0; + + return prop; +} + void -violated_vegetarian() +violated_vegetarian(void) { u.uconduct.unvegetarian++; if (Role_if(PM_MONK)) { @@ -1166,19 +1383,18 @@ violated_vegetarian() return; } -/* common code to check and possibly charge for 1 context.tin.tin, - * will split() context.tin.tin if necessary */ -STATIC_PTR struct obj * -costly_tin(alter_type) -int alter_type; /* COST_xxx */ +/* common code to check and possibly charge for 1 svc.context.tin.tin, + * will split() svc.context.tin.tin if necessary */ +staticfn struct obj * +costly_tin(int alter_type) /* COST_xxx */ { - struct obj *tin = context.tin.tin; + struct obj *tin = svc.context.tin.tin; if (carried(tin) ? tin->unpaid : (costly_spot(tin->ox, tin->oy) && !tin->no_charge)) { if (tin->quan > 1L) { - tin = context.tin.tin = splitobj(tin, 1L); - context.tin.o_id = tin->o_id; + tin = svc.context.tin.tin = splitobj(tin, 1L); + svc.context.tin.o_id = tin->o_id; } costly_alteration(tin, alter_type); } @@ -1186,9 +1402,7 @@ int alter_type; /* COST_xxx */ } int -tin_variety_txt(s, tinvariety) -char *s; -int *tinvariety; +tin_variety_txt(char *s, int *tinvariety) { int k, l; @@ -1211,50 +1425,47 @@ int *tinvariety; * as is the case with caller xname(). */ void -tin_details(obj, mnum, buf) -struct obj *obj; -int mnum; -char *buf; +tin_details(struct obj *obj, int mnum, char *buf) { char buf2[BUFSZ]; + + if (!obj || !buf) + return; + int r = tin_variety(obj, TRUE); - if (obj && buf) { - if (r == SPINACH_TIN) - Strcat(buf, " of spinach"); - else if (mnum == NON_PM) - Strcpy(buf, "empty tin"); - else { - if ((obj->cknown || iflags.override_ID) && obj->spe < 0) { - if (r == ROTTEN_TIN || r == HOMEMADE_TIN) { - /* put these before the word tin */ - Sprintf(buf2, "%s %s of ", tintxts[r].txt, buf); - Strcpy(buf, buf2); - } else { - Sprintf(eos(buf), " of %s ", tintxts[r].txt); - } + if (r == SPINACH_TIN) + Strcat(buf, " of spinach"); + else if (mnum == NON_PM) + Strcpy(buf, "empty tin"); + else { + if ((obj->cknown || iflags.override_ID) && obj->spe < 0) { + if (r == ROTTEN_TIN || r == HOMEMADE_TIN) { + /* put these before the word tin */ + Sprintf(buf2, "%s %s of ", tintxts[r].txt, buf); + Strcpy(buf, buf2); } else { - Strcpy(eos(buf), " of "); + Sprintf(eos(buf), " of %s ", tintxts[r].txt); } - if (vegetarian(&mons[mnum])) - Sprintf(eos(buf), "%s", mons[mnum].mname); - else - Sprintf(eos(buf), "%s meat", mons[mnum].mname); + } else { + Strcpy(eos(buf), " of "); } + if (vegetarian(&mons[mnum])) + Sprintf(eos(buf), "%s", mons[mnum].pmnames[NEUTRAL]); + else + Sprintf(eos(buf), "%s meat", mons[mnum].pmnames[NEUTRAL]); } } void -set_tin_variety(obj, forcetype) -struct obj *obj; -int forcetype; +set_tin_variety(struct obj *obj, int forcetype) { - register int r; + int r, mnum = obj->corpsenm; if (forcetype == SPINACH_TIN || (forcetype == HEALTHY_TIN - && (obj->corpsenm == NON_PM /* empty or already spinach */ - || !vegetarian(&mons[obj->corpsenm])))) { /* replace meat */ + && (mnum == NON_PM /* empty or already spinach */ + || !vegetarian(&mons[mnum])))) { /* replace meat */ obj->corpsenm = NON_PM; /* not based on any monster */ obj->spe = 1; /* spinach */ return; @@ -1268,18 +1479,18 @@ int forcetype; r = forcetype; } else { /* RANDOM_TIN */ r = rn2(TTSZ - 1); /* take your pick */ - if (r == ROTTEN_TIN && nonrotting_corpse(obj->corpsenm)) + if (r == ROTTEN_TIN && (ismnum(mnum) && nonrotting_corpse(mnum))) r = HOMEMADE_TIN; /* lizards don't rot */ } obj->spe = -(r + 1); /* offset by 1 to allow index 0 */ } -STATIC_OVL int -tin_variety(obj, disp) -struct obj *obj; -boolean disp; /* we're just displaying so leave things alone */ +staticfn int +tin_variety( + struct obj *obj, + boolean displ) /* we're just displaying so leave things alone */ { - register int r; + int r, mnum = obj->corpsenm; if (obj->spe == 1) { r = SPINACH_TIN; @@ -1288,30 +1499,46 @@ boolean disp; /* we're just displaying so leave things alone */ } else if (obj->spe < 0) { r = -(obj->spe); --r; /* get rid of the offset */ - } else + } else { r = rn2(TTSZ - 1); + } - if (!disp && r == HOMEMADE_TIN && !obj->blessed && !rn2(7)) + if (!displ && r == HOMEMADE_TIN && !obj->blessed && !rn2(7)) r = ROTTEN_TIN; /* some homemade tins go bad */ - if (r == ROTTEN_TIN && nonrotting_corpse(obj->corpsenm)) + if (r == ROTTEN_TIN && (ismnum(mnum) && nonrotting_corpse(mnum))) r = HOMEMADE_TIN; /* lizards don't rot */ return r; } -STATIC_OVL void -consume_tin(mesg) -const char *mesg; +/* finish consume_tin(); also potentially used by cprefx() and cpostfx() */ +staticfn void +use_up_tin(struct obj *tin) +{ + if (carried(tin)) + useup(tin); + else + useupf(tin, 1L); + /* reset tin context */ + svc.context.tin.tin = (struct obj *) NULL; + svc.context.tin.o_id = 0; +} + +staticfn void +consume_tin(const char *mesg) { const char *what; - int which, mnum, r; - struct obj *tin = context.tin.tin; + int which, mnum, r, nutamt; + /* if you've eaten tin itself, chance to not eat contents gets bypassed */ + boolean always_eat = metallivorous(gy.youmonst.data); + struct obj *tin = svc.context.tin.tin; r = tin_variety(tin, FALSE); if (tin->otrapped || (tin->cursed && r != HOMEMADE_TIN && !rn2(8))) { - b_trapped("tin", 0); + b_trapped("tin", NO_PART); tin = costly_tin(COST_DSTROY); - goto use_up_tin; + use_up_tin(tin); + return; } pline1(mesg); /* "You succeed in opening the tin." */ @@ -1319,10 +1546,19 @@ const char *mesg; if (r != SPINACH_TIN) { mnum = tin->corpsenm; if (mnum == NON_PM) { - pline("It turns out to be empty."); - tin->dknown = tin->known = 1; + if (Hallucination) + pline("It's full of %s.", + rn2(2) ? "air elemental souffle" + : "dehydrated water"); + else + pline("It turns out to be empty."); + observe_object(tin); + tin->known = 1; tin = costly_tin(COST_OPEN); - goto use_up_tin; + use_up_tin(tin); + if (always_eat) + lesshungry(5); /* metallivorous hero ate the tin itself */ + return; } which = 0; /* 0=>plural, 1=>as-is, 2=>"the" prefix */ @@ -1333,7 +1569,7 @@ const char *mesg; } else if (Hallucination) { what = rndmonnam(NULL); } else { - what = mons[mnum].mname; + what = mons[mnum].pmnames[NEUTRAL]; if (the_unique_pm(&mons[mnum])) which = 2; else if (type_is_pname(&mons[mnum])) @@ -1344,43 +1580,68 @@ const char *mesg; else if (which == 2) what = the(what); - pline("It smells like %s.", what); - if (yn("Eat it?") == 'n') { - if (flags.verbose) - You("discard the open tin."); - if (!Hallucination) - tin->dknown = tin->known = 1; - tin = costly_tin(COST_OPEN); - goto use_up_tin; + if (!always_eat) { + pline("It smells like %s.", what); + if (y_n("Eat it?") == 'n') { + if (flags.verbose) + You("discard the open tin."); + if (!Hallucination) { + observe_object(tin); + tin->known = 1; + } + tin = costly_tin(COST_OPEN); + use_up_tin(tin); + return; + } } /* in case stop_occupation() was called on previous meal */ - context.victual.piece = (struct obj *) 0; - context.victual.o_id = 0; - context.victual.fullwarn = context.victual.eating = - context.victual.doreset = FALSE; + svc.context.victual = zero_victual; /* victual.piece = 0, .o_id = 0 */ - You("consume %s %s.", tintxts[r].txt, mons[mnum].mname); + You("consume %s %s.", tintxts[r].txt, mons[mnum].pmnames[NEUTRAL]); eating_conducts(&mons[mnum]); - tin->dknown = tin->known = 1; - cprefx(mnum); - cpostfx(mnum); - + observe_object(tin); + tin->known = 1; /* charge for one at pre-eating cost */ - tin = costly_tin(COST_OPEN); + tin = svc.context.tin.tin = costly_tin(COST_OPEN); - if (tintxts[r].nut < 0) /* rotten */ + /* cprefx() or cpostfx() might use up tin to keep it out of bones */ + cprefx(mnum); + if (svc.context.tin.tin) + cpostfx(mnum); + if (!svc.context.tin.tin) + return; + + if (tintxts[r].nut < 0) { /* rotten */ make_vomiting((long) rn1(15, 10), FALSE); - else - lesshungry(tintxts[r].nut); + } else { + nutamt = tintxts[r].nut; + /* nutrition from a homemade tin (made from a single corpse) + shouldn't be more than nutrition from the corresponding + corpse; other tinning modes might use more than one corpse + or add extra ingredients so aren't similarly restricted */ + if (r == HOMEMADE_TIN && nutamt > mons[mnum].cnutrit) + nutamt = mons[mnum].cnutrit; + if (always_eat) + nutamt += 5; /* metallivorous hero ate the tin itself */ + /* use up tin now; lesshungry() could be fatal and produce bones */ + use_up_tin(tin), tin = NULL; + lesshungry(nutamt); + } if (tintxts[r].greasy) { - /* Assume !Glib, because you can't open tins when Glib. */ - make_glib(rn1(11, 5)); /* 5..15 */ - pline("Eating %s food made your %s very slippery.", - tintxts[r].txt, fingers_or_gloves(TRUE)); + /* normal hero is !Glib, because you can't open tins when Glib, + but one poly'd into metallivorous form might already be Glib; + it's debatable whether a rock mole should have its paws made + slippery when eating a greasy tin, but we'll go with that... */ + int alreadyglib = (int) (Glib & TIMEOUT); + + make_glib(alreadyglib + rn1(11, 5)); /* 5..15 */ + pline("Eating %s food made your %s %s slippery.", + tintxts[r].txt, fingers_or_gloves(TRUE), + alreadyglib ? "even more" : "very"); } } else { /* spinach... */ @@ -1389,21 +1650,25 @@ const char *mesg; Blind ? "" : " ", Blind ? "" : hcolor(NH_GREEN)); } else { pline("It contains spinach."); - tin->dknown = tin->known = 1; + observe_object(tin); + tin->known = 1; } - if (yn("Eat it?") == 'n') { + if (!always_eat && y_n("Eat it?") == 'n') { if (flags.verbose) You("discard the open tin."); tin = costly_tin(COST_OPEN); - goto use_up_tin; + use_up_tin(tin); + return; } /* * Same order as with non-spinach above: * conduct update, side-effects, shop handling, and nutrition. */ - u.uconduct.food++; /* don't need vegetarian checks for spinach */ + /* don't need vegetarian checks for spinach */ + if (!u.uconduct.food++) + livelog_printf(LL_CONDUCT, "ate for the first time (spinach)"); if (!tin->cursed) pline("This makes you feel like %s!", /* "Swee'pea" is a character from the Popeye cartoons */ @@ -1418,34 +1683,35 @@ const char *mesg; : (flags.female ? "Olive Oyl" : "Bluto")); gainstr(tin, 0, FALSE); - tin = costly_tin(COST_OPEN); - lesshungry(tin->blessed ? 600 /* blessed */ - : !tin->cursed ? (400 + rnd(200)) /* uncursed */ - : (200 + rnd(400))); /* cursed */ - } - - use_up_tin: - if (carried(tin)) - useup(tin); - else - useupf(tin, 1L); - context.tin.tin = (struct obj *) 0; - context.tin.o_id = 0; + tin = svc.context.tin.tin = costly_tin(COST_OPEN); + nutamt = (tin->blessed ? 600 /* blessed */ + : !tin->cursed ? (400 + rnd(200)) /* uncursed */ + : (200 + rnd(400))); /* cursed */ + if (always_eat) + nutamt += 5; /* metallivorous hero also eats the tin itself */ + /* use up tin first; lesshungry() could be fatal and produce bones */ + use_up_tin(tin), tin = NULL; + lesshungry(nutamt); + } + if (tin) + use_up_tin(tin); + return; } /* called during each move whilst opening a tin */ -STATIC_PTR int -opentin(VOID_ARGS) +staticfn int +opentin(void) { /* perhaps it was stolen (although that should cause interruption) */ - if (!carried(context.tin.tin) - && (!obj_here(context.tin.tin, u.ux, u.uy) || !can_reach_floor(TRUE))) + if (!carried(svc.context.tin.tin) + && (!obj_here(svc.context.tin.tin, u.ux, u.uy) + || !can_reach_floor(TRUE))) return 0; /* %% probably we should use tinoid */ - if (context.tin.usedtime++ >= 50) { + if (svc.context.tin.usedtime++ >= 50) { You("give up your attempt to open the tin."); return 0; } - if (context.tin.usedtime < context.tin.reqtime) + if (svc.context.tin.usedtime < svc.context.tin.reqtime) return 1; /* still busy */ consume_tin("You succeed in opening the tin."); @@ -1453,17 +1719,16 @@ opentin(VOID_ARGS) } /* called when starting to open a tin */ -STATIC_OVL void -start_tin(otmp) -struct obj *otmp; +staticfn void +start_tin(struct obj *otmp) { const char *mesg = 0; - register int tmp; + int tmp; - if (metallivorous(youmonst.data)) { + if (metallivorous(gy.youmonst.data)) { mesg = "You bite right into the metal tin..."; tmp = 0; - } else if (cantwield(youmonst.data)) { /* nohands || verysmall */ + } else if (cantwield(gy.youmonst.data)) { /* nohands || verysmall */ You("cannot handle the tin properly to open it."); return; } else if (otmp->blessed) { @@ -1472,7 +1737,8 @@ struct obj *otmp; access); 1 turn delay case is non-deterministic: getting interrupted and retrying might yield another 1 turn delay or might open immediately on 2nd (or 3rd, 4th, ...) try */ - tmp = (uwep && uwep->blessed && uwep->otyp == TIN_OPENER) ? 0 : rn2(2); + tmp = (uwep && uwep->blessed && uwep->otyp == TIN_OPENER) ? 0 + : rn2(2); if (!tmp) mesg = "The tin opens like magic!"; else @@ -1518,13 +1784,13 @@ struct obj *otmp; tmp = rn1(1 + 500 / ((int) (ACURR(A_DEX) + ACURRSTR)), 10); } - context.tin.tin = otmp; - context.tin.o_id = otmp->o_id; + svc.context.tin.tin = otmp; + svc.context.tin.o_id = otmp->o_id; if (!tmp) { consume_tin(mesg); /* begin immediately */ } else { - context.tin.reqtime = tmp; - context.tin.usedtime = 0; + svc.context.tin.reqtime = tmp; + svc.context.tin.usedtime = 0; set_occupation(opentin, "opening the tin", 0); } return; @@ -1532,22 +1798,22 @@ struct obj *otmp; /* called when waking up after fainting */ int -Hear_again(VOID_ARGS) +Hear_again(void) { /* Chance of deafness going away while fainted/sleeping/etc. */ if (!rn2(2)) { make_deaf(0L, FALSE); - context.botl = TRUE; + disp.botl = TRUE; } return 0; } /* called on the "first bite" of rotten food */ -STATIC_OVL int -rottenfood(obj) -struct obj *obj; +staticfn int +rottenfood(struct obj *obj) { - pline("Blecch! Rotten %s!", foodword(obj)); + pline("Blecch! %s %s!", + is_rottable(obj) ? "Rotten" : "Awful", foodword(obj)); if (!rn2(4)) { if (Hallucination) You_feel("rather trippy."); @@ -1558,7 +1824,7 @@ struct obj *obj; pline("Everything suddenly goes dark."); /* hero is not Blind, but Blinded timer might be nonzero if blindness is being overridden by the Eyes of the Overworld */ - make_blinded((Blinded & TIMEOUT) + (long) d(2, 10), FALSE); + make_blinded(BlindedTimeout + (long) d(2, 10), FALSE); if (!Blind) Your1(vision_clears); } else if (!rn2(3)) { @@ -1574,53 +1840,66 @@ struct obj *obj; where = (u.usteed) ? "saddle" : surface(u.ux, u.uy); pline_The("world spins and %s %s.", what, where); incr_itimeout(&HDeaf, duration); - context.botl = TRUE; + disp.botl = TRUE; nomul(-duration); - multi_reason = "unconscious from rotten food"; - nomovemsg = "You are conscious again."; - afternmv = Hear_again; + gm.multi_reason = "unconscious from rotten food"; + gn.nomovemsg = "You are conscious again."; + ga.afternmv = Hear_again; return 1; } return 0; } /* called when a corpse is selected as food */ -STATIC_OVL int -eatcorpse(otmp) -struct obj *otmp; +staticfn int +eatcorpse(struct obj *otmp) { int retcode = 0, tp = 0, mnum = otmp->corpsenm; long rotted = 0L; - boolean stoneable = (flesh_petrifies(&mons[mnum]) && !Stone_resistance - && !poly_when_stoned(youmonst.data)), + int ll_conduct = 0; + boolean stoneable, slimeable = (mnum == PM_GREEN_SLIME && !Slimed && !Unchanging - && !slimeproof(youmonst.data)), + && !slimeproof(gy.youmonst.data)), glob = otmp->globby ? TRUE : FALSE; + assert(ismnum(mnum)); + stoneable = (flesh_petrifies(&mons[mnum]) && !Stone_resistance + && !poly_when_stoned(gy.youmonst.data)); + /* KMH, conduct */ if (!vegan(&mons[mnum])) - u.uconduct.unvegan++; - if (!vegetarian(&mons[mnum])) + if (!u.uconduct.unvegan++) { + livelog_printf(LL_CONDUCT, + "consumed animal products for the first time, by eating %s", + an(food_xname(otmp, FALSE))); + ll_conduct++; + } + if (!vegetarian(&mons[mnum])) { + if (!u.uconduct.unvegetarian && !ll_conduct) + livelog_printf(LL_CONDUCT, + "tasted meat for the first time, by eating %s", + an(food_xname(otmp, FALSE))); violated_vegetarian(); - + } if (!nonrotting_corpse(mnum)) { long age = peek_at_iced_corpse_age(otmp); - rotted = (monstermoves - age) / (10L + rn2(20)); + rotted = (svm.moves - age) / (10L + rn2(20)); if (otmp->cursed) rotted += 2L; else if (otmp->blessed) rotted -= 2L; } - if (mnum != PM_ACID_BLOB && !stoneable && !slimeable && rotted > 5L) { + /* 5.0: globs don't become tainted, they shrink away */ + if (!glob && !stoneable && !slimeable && rotted > 5L) { boolean cannibal = maybe_cannibal(mnum, FALSE); + /* tp++; -- early return makes this unnecessary */ pline("Ulch - that %s was tainted%s!", (mons[mnum].mlet == S_FUNGUS) ? "fungoid vegetation" - : glob ? "glob" - : vegetarian(&mons[mnum]) ? "protoplasm" - : "meat", + : vegetarian(&mons[mnum]) ? "protoplasm" + : "meat", cannibal ? ", you cannibal" : ""); if (Sick_resistance) { pline("It doesn't seem at all sickening, though..."); @@ -1650,11 +1929,12 @@ struct obj *otmp; tp++; pline("Ecch - that must have been poisonous!"); if (!Poison_resistance) { - losestr(rnd(4)); - losehp(rnd(15), !glob ? "poisonous corpse" : "poisonous glob", - KILLED_BY_AN); + poison_strdmg(rnd(4), rnd(15), + !glob ? "poisonous corpse" : "poisonous glob", + KILLED_BY_AN); } else You("seem unaffected by the poison."); + /* now any corpse left too long will make you mildly ill */ } else if ((rotted > 5L || (rotted > 3L && rn2(5))) && !Sick_resistance) { tp++; @@ -1663,12 +1943,15 @@ struct obj *otmp; } /* delay is weight dependent */ - context.victual.reqtime = 3 + ((!glob ? mons[mnum].cwt : otmp->owt) >> 6); + svc.context.victual.reqtime + = 3 + ((!glob ? mons[mnum].cwt : otmp->owt) >> 6); if (!tp && !nonrotting_corpse(mnum) && (otmp->orotten || !rn2(7))) { if (rottenfood(otmp)) { otmp->orotten = TRUE; - (void) touchfood(otmp); + otmp = touchfood(otmp); + if (!otmp) + return 1; retcode = 1; } @@ -1690,19 +1973,29 @@ struct obj *otmp; pline("This tastes just like chicken!"); } else if (mnum == PM_FLOATING_EYE && u.umonnum == PM_RAVEN) { You("peck the eyeball with delight."); + } else if (tp) { + ; /* we've already delivered a message; don't add "it tastes okay" */ } else { /* yummy is always False for omnivores, palatable always True */ boolean yummy = (vegan(&mons[mnum]) - ? (!carnivorous(youmonst.data) - && herbivorous(youmonst.data)) - : (carnivorous(youmonst.data) - && !herbivorous(youmonst.data))), - palatable = ((vegetarian(&mons[mnum]) - ? herbivorous(youmonst.data) - : carnivorous(youmonst.data)) - && rn2(10) - && ((rotted < 1) ? TRUE : !rn2(rotted+1))); + ? (!carnivorous(gy.youmonst.data) + && herbivorous(gy.youmonst.data)) + : (carnivorous(gy.youmonst.data) + && !herbivorous(gy.youmonst.data))), + palatable = ((vegetarian(&mons[mnum]) + ? herbivorous(gy.youmonst.data) + : carnivorous(gy.youmonst.data)) + && rn2(10) + && (rotted < 1 || !rn2((int) rotted + 1))); const char *pmxnam = food_xname(otmp, FALSE); + static const char *const palatable_msgs[] = { + /* first char: T = tastes ... , I = is ... */ + /* veggies are always just "okay" */ + "Tokay", "Istringy", "Igamey", "Ifatty", "Itough" + }; + int idx = vegetarian(&mons[mnum]) ? 0 : rn2(SIZE(palatable_msgs)); + const char *palat_msg = palatable_msgs[idx]; + boolean use_is = (Hallucination || (palatable && *palat_msg == 'I')); if (!strncmpi(pmxnam, "the ", 4)) pmxnam += 4; @@ -1710,13 +2003,14 @@ struct obj *otmp; type_is_pname(&mons[mnum]) ? "" : the_unique_pm(&mons[mnum]) ? "The " : "This ", pmxnam, - Hallucination ? "is" : "tastes", + use_is ? "is" : "tastes", /* tiger reference is to TV ads for "Frosted Flakes", breakfast cereal targeted at kids by "Tony the tiger" */ Hallucination ? (yummy ? ((u.umonnum == PM_TIGER) ? "gr-r-reat" : "gnarly") : palatable ? "copacetic" : "grody") - : (yummy ? "delicious" : palatable ? "okay" : "terrible"), + : (yummy ? "delicious" : palatable ? + &palat_msg[1] : "terrible"), (yummy || !palatable) ? '!' : '.'); } @@ -1724,54 +2018,53 @@ struct obj *otmp; } /* called as you start to eat */ -STATIC_OVL void -start_eating(otmp, already_partly_eaten) -struct obj *otmp; -boolean already_partly_eaten; +staticfn void +start_eating(struct obj *otmp, boolean already_partly_eaten) { const char *old_nomovemsg, *save_nomovemsg; + static char msgbuf[BUFSZ]; debugpline2("start_eating: %s (victual = %s)", /* note: fmt_ptr() returns a static buffer but supports several such so we don't need to copy the first result before calling it a second time */ fmt_ptr((genericptr_t) otmp), - fmt_ptr((genericptr_t) context.victual.piece)); - debugpline1("reqtime = %d", context.victual.reqtime); + fmt_ptr((genericptr_t) svc.context.victual.piece)); + debugpline1("reqtime = %d", svc.context.victual.reqtime); debugpline1("(original reqtime = %d)", objects[otmp->otyp].oc_delay); - debugpline1("nmod = %d", context.victual.nmod); + debugpline1("nmod = %d", svc.context.victual.nmod); debugpline1("oeaten = %d", otmp->oeaten); - context.victual.fullwarn = context.victual.doreset = FALSE; - context.victual.eating = TRUE; + svc.context.victual.fullwarn = svc.context.victual.doreset = 0; + svc.context.victual.eating = 1; if (otmp->otyp == CORPSE || otmp->globby) { - cprefx(context.victual.piece->corpsenm); - if (!context.victual.piece || !context.victual.eating) { - /* rider revived, or died and lifesaved */ + cprefx(svc.context.victual.piece->corpsenm); + if (!svc.context.victual.piece || !svc.context.victual.eating) { + /* rider revived, or hero died and was lifesaved */ return; } } - old_nomovemsg = nomovemsg; + old_nomovemsg = gn.nomovemsg; if (bite()) { /* survived choking, finish off food that's nearly done; need this to handle cockatrice eggs, fortune cookies, etc */ - if (++context.victual.usedtime >= context.victual.reqtime) { - /* don't want done_eating() to issue nomovemsg if it + if (++svc.context.victual.usedtime >= svc.context.victual.reqtime) { + /* don't want done_eating() to issue gn.nomovemsg if it is due to vomit() called by bite() */ - save_nomovemsg = nomovemsg; + save_nomovemsg = gn.nomovemsg; if (!old_nomovemsg) - nomovemsg = 0; + gn.nomovemsg = 0; done_eating(FALSE); if (!old_nomovemsg) - nomovemsg = save_nomovemsg; + gn.nomovemsg = save_nomovemsg; } return; } - if (++context.victual.usedtime >= context.victual.reqtime) { + if (++svc.context.victual.usedtime >= svc.context.victual.reqtime) { /* print "finish eating" message if they just resumed -dlc */ - done_eating((context.victual.reqtime > 1 + done_eating((svc.context.victual.reqtime > 1 || already_partly_eaten) ? TRUE : FALSE); return; } @@ -1780,17 +2073,49 @@ boolean already_partly_eaten; set_occupation(eatfood, msgbuf, 0); } +/* used by shrink_glob() timer routine */ +boolean +eating_glob(struct obj *glob) +{ + return (go.occupation == eatfood && glob == svc.context.victual.piece); +} + +/* scare nearby monster when hero eats garlic */ +staticfn void +garlic_breath(struct monst *mtmp) +{ + if (olfaction(mtmp->data) && distu(mtmp->mx, mtmp->my) < 7) + monflee(mtmp, 0, FALSE, FALSE); +} + /* * Called on "first bite" of (non-corpse) food, after touchfood() has * marked it 'partly eaten'. Used for non-rotten non-tin non-corpse food. * Messages should use present tense since multi-turn food won't be * finishing at the time they're issued. + * Returns FALSE if eating should not succeed for whatever reason. */ -STATIC_OVL void -fprefx(otmp) -struct obj *otmp; +staticfn boolean +fprefx(struct obj *otmp) { switch (otmp->otyp) { + case EGG: + if (otmp->corpsenm == PM_PYROLISK) { + if (carried(otmp)) + useup(otmp); + else + useupf(otmp, 1L); + explode(u.ux, u.uy, -11, d(3, 6), 0, EXPL_FIERY); + return FALSE; + } else if (stale_egg(otmp)) { + pline("Ugh. Rotten egg."); /* perhaps others like it */ + /* increasing existing nausea means that it will take longer + before eventual vomit, but also means that constitution + will be abused more times before illness completes */ + make_vomiting((Vomiting & TIMEOUT) + (long) d(10, 4), TRUE); + } else + goto give_feedback; + break; case FOOD_RATION: /* nutrition 800 */ /* 200+800 remains below 1000+1, the satiation threshold */ if (u.uhunger <= 200) @@ -1804,9 +2129,9 @@ struct obj *otmp; /* [satiation message may be inaccurate if eating gets interrupted] */ break; case TRIPE_RATION: - if (carnivorous(youmonst.data) && !humanoid(youmonst.data)) { + if (carnivorous(gy.youmonst.data) && !humanoid(gy.youmonst.data)) { pline("This tripe ration is surprisingly good!"); - } else if (maybe_polyd(is_orc(youmonst.data), Race_if(PM_ORC))) { + } else if (maybe_polyd(is_orc(gy.youmonst.data), Race_if(PM_ORC))) { pline(Hallucination ? "Tastes great! Less filling!" : "Mmm, tripe... not bad!"); } else { @@ -1816,41 +2141,44 @@ struct obj *otmp; /* not cannibalism, but we use similar criteria for deciding whether to be sickened by this meal */ if (rn2(2) && !CANNIBAL_ALLOWED()) - make_vomiting((long) rn1(context.victual.reqtime, 14), FALSE); + make_vomiting((long) rn1(svc.context.victual.reqtime, 14), + FALSE); } break; case LEMBAS_WAFER: - if (maybe_polyd(is_orc(youmonst.data), Race_if(PM_ORC))) { + if (maybe_polyd(is_orc(gy.youmonst.data), Race_if(PM_ORC))) { pline("%s", "!#?&* elf kibble!"); break; - } else if (maybe_polyd(is_elf(youmonst.data), Race_if(PM_ELF))) { + } else if (maybe_polyd(is_elf(gy.youmonst.data), Race_if(PM_ELF))) { pline("A little goes a long way."); break; } goto give_feedback; case MEATBALL: case MEAT_STICK: - case HUGE_CHUNK_OF_MEAT: + case ENORMOUS_MEATBALL: case MEAT_RING: goto give_feedback; case CLOVE_OF_GARLIC: - if (is_undead(youmonst.data)) { - make_vomiting((long) rn1(context.victual.reqtime, 5), FALSE); + if (is_undead(gy.youmonst.data)) { + make_vomiting((long) rn1(svc.context.victual.reqtime, 5), FALSE); break; } + iter_mons(garlic_breath); + FALLTHROUGH; /*FALLTHRU*/ default: if (otmp->otyp == SLIME_MOLD && !otmp->cursed - && otmp->spe == context.current_fruit) { + && otmp->spe == svc.context.current_fruit) { pline("My, this is a %s %s!", Hallucination ? "primo" : "yummy", singular(otmp, xname)); } else if (otmp->otyp == APPLE && otmp->cursed && !Sleep_resistance) { ; /* skip core joke; feedback deferred til fpostfx() */ -#if defined(MAC) || defined(MACOSX) +#if defined(MACOS9) || defined(MACOS) /* KMH -- Why should Unix have all the fun? - We check MACOSX before UNIX to get the Apple-specific apple + We check MACOS before UNIX to get the Apple-specific apple message; the '#if UNIX' code will still kick in for pear. */ } else if (otmp->otyp == APPLE) { pline("Delicious! Must be a Macintosh!"); @@ -1872,12 +2200,6 @@ struct obj *otmp; : "Yo' mama"); } #endif - } else if (otmp->otyp == EGG && stale_egg(otmp)) { - pline("Ugh. Rotten egg."); /* perhaps others like it */ - /* increasing existing nausea means that it will take longer - before eventual vomit, but also means that constitution - will be abused more times before illness completes */ - make_vomiting((Vomiting & TIMEOUT) + (long) d(10, 4), TRUE); } else { give_feedback: pline("This %s is %s", singular(otmp, xname), @@ -1891,12 +2213,12 @@ struct obj *otmp; } break; /* default */ } /* switch */ + return TRUE; } /* increment a combat intrinsic with limits on its growth */ -STATIC_OVL int -bounded_increase(old, inc, typ) -int old, inc, typ; +staticfn int +bounded_increase(int old, int inc, int typ) { int absold, absinc, sgnold, sgninc; @@ -1932,17 +2254,15 @@ int old, inc, typ; return old + inc; } -STATIC_OVL void -accessory_has_effect(otmp) -struct obj *otmp; +staticfn void +accessory_has_effect(struct obj *otmp) { pline("Magic spreads through your body as you digest the %s.", (otmp->oclass == RING_CLASS) ? "ring" : "amulet"); } -STATIC_OVL void -eataccessory(otmp) -struct obj *otmp; +staticfn void +eataccessory(struct obj *otmp) { int typ = otmp->otyp; long oldprop; @@ -1955,7 +2275,8 @@ struct obj *otmp; if (u.uhp <= 0) return; /* died from sink fall */ } - otmp->known = otmp->dknown = 1; /* by taste */ + observe_object(otmp); + otmp->known = 1; /* by taste */ if (!rn2(otmp->oclass == RING_CLASS ? 3 : 5)) { switch (otmp->otyp) { default: @@ -1972,7 +2293,7 @@ struct obj *otmp; set_mimic_blocking(); see_monsters(); if (Invis && !oldprop && !ESee_invisible - && !perceives(youmonst.data) && !Blind) { + && !perceives(gy.youmonst.data) && !Blind) { newsym(u.ux, u.uy); pline("Suddenly you can see yourself."); makeknown(typ); @@ -2028,11 +2349,14 @@ struct obj *otmp; RIN_INCREASE_DAMAGE); break; case RIN_PROTECTION: + case AMULET_OF_GUARDING: accessory_has_effect(otmp); HProtection |= FROMOUTSIDE; - u.ublessed = bounded_increase(u.ublessed, otmp->spe, - RIN_PROTECTION); - context.botl = 1; + u.ublessed = bounded_increase(u.ublessed, + (typ == RIN_PROTECTION) ? otmp->spe + : 2, /* fixed amount for amulet */ + typ); + disp.botl = TRUE; break; case RIN_FREE_ACTION: /* Give sleep resistance instead */ @@ -2048,7 +2372,7 @@ struct obj *otmp; change_sex(); You("are suddenly very %s!", flags.female ? "feminine" : "masculine"); - context.botl = 1; + disp.botl = TRUE; break; case AMULET_OF_UNCHANGING: /* un-change: it's a pun */ @@ -2075,6 +2399,7 @@ struct obj *otmp; } case RIN_SUSTAIN_ABILITY: case AMULET_OF_LIFE_SAVING: + case AMULET_OF_FLYING: case AMULET_OF_REFLECTION: /* nice try */ /* can't eat Amulet of Yendor or fakes, * and no oc_prop even if you could -3. @@ -2085,18 +2410,17 @@ struct obj *otmp; } /* called after eating non-food */ -STATIC_OVL void -eatspecial() +staticfn void +eatspecial(void) { - struct obj *otmp = context.victual.piece; + struct obj *otmp = svc.context.victual.piece; /* lesshungry wants an occupation to handle choke messages correctly */ set_occupation(eatfood, "eating non-food", 0); - lesshungry(context.victual.nmod); - occupation = 0; - context.victual.piece = (struct obj *) 0; - context.victual.o_id = 0; - context.victual.eating = 0; + lesshungry(svc.context.victual.nmod); + go.occupation = 0; + svc.context.victual = zero_victual; /* victual.piece = 0, .o_id = 0 */ + if (otmp->oclass == COIN_CLASS) { if (carried(otmp)) useupall(otmp); @@ -2106,7 +2430,7 @@ eatspecial() return; } if (objects[otmp->otyp].oc_material == PAPER) { -#ifdef MAIL +#ifdef MAIL_STRUCTURES if (otmp->otyp == SCR_MAIL) /* no nutrition */ pline("This junk mail is less than satisfying."); @@ -2117,7 +2441,7 @@ eatspecial() pline("Yuck%c", otmp->blessed ? '!' : '.'); else if (otmp->oclass == SCROLL_CLASS /* check description after checking for specific scrolls */ - && !strcmpi(OBJ_DESCR(objects[otmp->otyp]), "YUM YUM")) + && objdescr_is(otmp, "YUM YUM")) pline("Yum%c", otmp->blessed ? '!' : '.'); else pline("Needs salt..."); @@ -2163,16 +2487,15 @@ eatspecial() /* NOTE: the order of these words exactly corresponds to the order of oc_material values #define'd in objclass.h. */ -static const char *foodwords[] = { +static const char *const foodwords[] = { "meal", "liquid", "wax", "food", "meat", "paper", "cloth", "leather", "wood", "bone", "scale", "metal", "metal", "metal", "silver", "gold", "platinum", "mithril", "plastic", "glass", "rich food", "stone" }; -STATIC_OVL const char * -foodword(otmp) -struct obj *otmp; +staticfn const char * +foodword(struct obj *otmp) { if (otmp->oclass == FOOD_CLASS) return "food"; @@ -2183,13 +2506,12 @@ struct obj *otmp; } /* called after consuming (non-corpse) food */ -STATIC_OVL void -fpostfx(otmp) -struct obj *otmp; +staticfn void +fpostfx(struct obj *otmp) { switch (otmp->otyp) { case SPRIG_OF_WOLFSBANE: - if (u.ulycn >= LOW_PM || is_were(youmonst.data)) + if (ismnum(u.ulycn) || is_were(gy.youmonst.data)) you_unwere(TRUE); break; case CARROT: @@ -2200,29 +2522,35 @@ struct obj *otmp; case FORTUNE_COOKIE: outrumor(bcsign(otmp), BY_COOKIE); if (!Blind) - u.uconduct.literate++; + if (!u.uconduct.literate++) + livelog_printf(LL_CONDUCT, + "became literate by reading the fortune inside a cookie"); break; case LUMP_OF_ROYAL_JELLY: + if (gy.youmonst.data == &mons[PM_KILLER_BEE] && !Unchanging + && polymon(PM_QUEEN_BEE)) + break; + /* This stuff seems to be VERY healthy! */ gainstr(otmp, 1, TRUE); if (Upolyd) { - u.mh += otmp->cursed ? -rnd(20) : rnd(20); + u.mh += otmp->cursed ? -rnd(20) : rnd(20), disp.botl = TRUE; if (u.mh > u.mhmax) { if (!rn2(17)) - u.mhmax++; + setuhpmax(u.mhmax + 1, FALSE); u.mh = u.mhmax; } else if (u.mh <= 0) { rehumanize(); } } else { - u.uhp += otmp->cursed ? -rnd(20) : rnd(20); + u.uhp += otmp->cursed ? -rnd(20) : rnd(20), disp.botl = TRUE; if (u.uhp > u.uhpmax) { if (!rn2(17)) - u.uhpmax++; + setuhpmax(u.uhpmax + 1, FALSE); u.uhp = u.uhpmax; } else if (u.uhp <= 0) { - killer.format = KILLED_BY_AN; - Strcpy(killer.name, "rotten lump of royal jelly"); + svk.killer.format = KILLED_BY_AN; + Strcpy(svk.killer.name, "rotten lump of royal jelly"); done(POISONING); } } @@ -2230,14 +2558,16 @@ struct obj *otmp; heal_legs(0); break; case EGG: - if (flesh_petrifies(&mons[otmp->corpsenm])) { + if (ismnum(otmp->corpsenm) + && flesh_petrifies(&mons[otmp->corpsenm])) { if (!Stone_resistance - && !(poly_when_stoned(youmonst.data) + && !(poly_when_stoned(gy.youmonst.data) && polymon(PM_STONE_GOLEM))) { if (!Stoned) { - Sprintf(killer.name, "%s egg", - mons[otmp->corpsenm].mname); - make_stoned(5L, (char *) 0, KILLED_BY_AN, killer.name); + Sprintf(svk.killer.name, "%s egg", + mons[otmp->corpsenm].pmnames[NEUTRAL]); + make_stoned(5L, (char *) 0, KILLED_BY_AN, + svk.killer.name); } } /* note: no "tastes like chicken" message for eggs */ @@ -2254,12 +2584,14 @@ struct obj *otmp; /* Snow White; 'poisoned' applies to [a subset of] weapons, not food, so we substitute cursed; fortunately our hero won't have to wait for a prince to be rescued/revived */ - if (Race_if(PM_DWARF) && Hallucination) + if (Race_if(PM_DWARF) && Hallucination) { verbalize("Heigh-ho, ho-hum, I think I'll skip work today."); - else if (Deaf || !flags.acoustics) + } else if (Deaf || !flags.acoustics) { You("fall asleep."); - else + } else { + Soundeffect(se_sinister_laughter, 100); You_hear("sinister laughter as you fall asleep..."); + } fall_asleep(-rn1(11, 20), TRUE); } break; @@ -2271,11 +2603,10 @@ struct obj *otmp; /* intended for eating a spellbook while polymorphed, but not used; "leather" applied to appearance, not composition, and has been changed to "leathery" to reflect that */ -STATIC_DCL boolean FDECL(leather_cover, (struct obj *)); +staticfn boolean leather_cover(struct obj *); -STATIC_OVL boolean -leather_cover(otmp) -struct obj *otmp; +staticfn boolean +leather_cover(struct obj *otmp) { const char *odesc = OBJ_DESCR(objects[otmp->otyp]); @@ -2292,40 +2623,40 @@ struct obj *otmp; * return 1 if the food was dangerous and you chose to stop. * return 2 if the food was dangerous and you chose to eat it anyway. */ -STATIC_OVL int -edibility_prompts(otmp) -struct obj *otmp; +staticfn int +edibility_prompts(struct obj *otmp) { /* Blessed food detection grants hero a one-use * ability to detect food that is unfit for consumption * or dangerous and avoid it. */ char buf[BUFSZ], foodsmell[BUFSZ], - it_or_they[QBUFSZ], eat_it_anyway[QBUFSZ]; - boolean cadaver = (otmp->otyp == CORPSE || otmp->globby), - stoneorslime = FALSE; + it_or_they[QBUFSZ]; + /* 5.0: decaying globs don't become tainted anymore; in 3.6, they did */ + boolean cadaver = (otmp->otyp == CORPSE), stoneorslime = FALSE; int material = objects[otmp->otyp].oc_material, mnum = otmp->corpsenm; long rotted = 0L; Strcpy(foodsmell, Tobjnam(otmp, "smell")); Strcpy(it_or_they, (otmp->quan == 1L) ? "it" : "they"); - Sprintf(eat_it_anyway, "Eat %s anyway?", - (otmp->quan == 1L) ? "it" : "one"); - if (cadaver || otmp->otyp == EGG || otmp->otyp == TIN) { + if (cadaver || otmp->otyp == EGG || otmp->otyp == TIN + || otmp->otyp == GLOB_OF_GREEN_SLIME) { /* These checks must match those in eatcorpse() */ - stoneorslime = (flesh_petrifies(&mons[mnum]) && !Stone_resistance - && !poly_when_stoned(youmonst.data)); + stoneorslime = (ismnum(mnum) + && flesh_petrifies(&mons[mnum]) + && !Stone_resistance + && !poly_when_stoned(gy.youmonst.data)); if (mnum == PM_GREEN_SLIME || otmp->otyp == GLOB_OF_GREEN_SLIME) - stoneorslime = (!Unchanging && !slimeproof(youmonst.data)); + stoneorslime = (!Unchanging && !slimeproof(gy.youmonst.data)); if (cadaver && !nonrotting_corpse(mnum)) { long age = peek_at_iced_corpse_age(otmp); /* worst case rather than random in this calculation to force prompt */ - rotted = (monstermoves - age) / (10L + 0 /* was rn2(20) */); + rotted = (svm.moves - age) / (10L + 0 /* was rn2(20) */); if (otmp->cursed) rotted += 2L; else if (otmp->blessed) @@ -2337,126 +2668,168 @@ struct obj *otmp; * These problems with food should be checked in * order from most detrimental to least detrimental. */ - if (cadaver && mnum != PM_ACID_BLOB && rotted > 5L && !Sick_resistance) { + buf[0] = '\0'; + if (cadaver && rotted > 5L && !Sick_resistance) { /* Tainted meat */ - Sprintf(buf, "%s like %s could be tainted! %s", foodsmell, it_or_they, - eat_it_anyway); - if (yn_function(buf, ynchars, 'n') == 'n') - return 1; - else - return 2; - } - if (stoneorslime) { - Sprintf(buf, "%s like %s could be something very dangerous! %s", - foodsmell, it_or_they, eat_it_anyway); - if (yn_function(buf, ynchars, 'n') == 'n') - return 1; - else - return 2; - } - if (otmp->orotten || (cadaver && rotted > 3L)) { + Snprintf(buf, sizeof buf, "%s like %s could be tainted!", + foodsmell, it_or_they); + } else if (stoneorslime) { + Snprintf(buf, sizeof buf, + "%s like %s could be something very dangerous!", + foodsmell, it_or_they); + } else if (cadaver && rotted > 5L && Sick_resistance) { + /* Tainted meat with Sick_resistance (testing for that is + redundant; we don't get this far for !Sick_resistance) + needs to be done now even though there is no danger because + it can't match after the rotten (cadaver && rotted > 3) test */ + Snprintf(buf, sizeof buf, "%s like %s could be tainted.", + foodsmell, it_or_they); + } else if (otmp->orotten || (cadaver && rotted > 3L)) { /* Rotten */ - Sprintf(buf, "%s like %s could be rotten! %s", foodsmell, it_or_they, - eat_it_anyway); - if (yn_function(buf, ynchars, 'n') == 'n') - return 1; - else - return 2; - } - if (cadaver && poisonous(&mons[mnum]) && !Poison_resistance) { + Snprintf(buf, sizeof buf, "%s like %s could be rotten!", + foodsmell, it_or_they); + } else if (cadaver && poisonous(&mons[mnum]) && !Poison_resistance) { /* poisonous */ - Sprintf(buf, "%s like %s might be poisonous! %s", foodsmell, - it_or_they, eat_it_anyway); - if (yn_function(buf, ynchars, 'n') == 'n') - return 1; - else - return 2; - } - if (otmp->otyp == APPLE && otmp->cursed && !Sleep_resistance) { + Snprintf(buf, sizeof buf, "%s like %s might be poisonous!", + foodsmell, it_or_they); + } else if (otmp->otyp == APPLE && otmp->cursed && !Sleep_resistance) { /* causes sleep, for long enough to be dangerous */ - Sprintf(buf, "%s like %s might have been poisoned. %s", foodsmell, - it_or_they, eat_it_anyway); - return (yn_function(buf, ynchars, 'n') == 'n') ? 1 : 2; - } - if (cadaver && !vegetarian(&mons[mnum]) && !u.uconduct.unvegetarian - && Role_if(PM_MONK)) { - Sprintf(buf, "%s unhealthy. %s", foodsmell, eat_it_anyway); - if (yn_function(buf, ynchars, 'n') == 'n') - return 1; - else - return 2; - } - if (cadaver && acidic(&mons[mnum]) && !Acid_resistance) { - Sprintf(buf, "%s rather acidic. %s", foodsmell, eat_it_anyway); - if (yn_function(buf, ynchars, 'n') == 'n') - return 1; - else - return 2; - } - if (Upolyd && u.umonnum == PM_RUST_MONSTER && is_metallic(otmp) - && otmp->oerodeproof) { - Sprintf(buf, "%s disgusting to you right now. %s", foodsmell, - eat_it_anyway); - if (yn_function(buf, ynchars, 'n') == 'n') - return 1; - else - return 2; - } + Snprintf(buf, sizeof buf, "%s like %s might have been poisoned.", + foodsmell, it_or_they); + } else if (cadaver && !vegetarian(&mons[mnum]) + && !u.uconduct.unvegetarian && Role_if(PM_MONK)) { + Snprintf(buf, sizeof buf, "%s unhealthy.", foodsmell); + } else if (cadaver && acidic(&mons[mnum]) && !Acid_resistance) { + Snprintf(buf, sizeof buf, "%s rather acidic.", foodsmell); + } else if (Upolyd && u.umonnum == PM_RUST_MONSTER && is_metallic(otmp) + && otmp->oerodeproof) { + Snprintf(buf, sizeof buf, "%s disgusting to you right now.", + foodsmell); /* * Breaks conduct, but otherwise safe. */ - if (!u.uconduct.unvegan - && ((material == LEATHER || material == BONE - || material == DRAGON_HIDE || material == WAX) - || (cadaver && !vegan(&mons[mnum])))) { - Sprintf(buf, "%s foul and unfamiliar to you. %s", foodsmell, - eat_it_anyway); - if (yn_function(buf, ynchars, 'n') == 'n') - return 1; - else - return 2; - } - if (!u.uconduct.unvegetarian - && ((material == LEATHER || material == BONE - || material == DRAGON_HIDE) - || (cadaver && !vegetarian(&mons[mnum])))) { - Sprintf(buf, "%s unfamiliar to you. %s", foodsmell, eat_it_anyway); - if (yn_function(buf, ynchars, 'n') == 'n') - return 1; - else - return 2; + } else if (!u.uconduct.unvegan + && ((material == LEATHER || material == BONE + || material == DRAGON_HIDE || material == WAX) + || (cadaver && !vegan(&mons[mnum])))) { + Snprintf(buf, sizeof buf, "%s foul and unfamiliar to you.", + foodsmell); + } else if (!u.uconduct.unvegetarian + && ((material == LEATHER || material == BONE + || material == DRAGON_HIDE) + || (cadaver && !vegetarian(&mons[mnum])))) { + Snprintf(buf, sizeof buf, "%s unfamiliar to you.", foodsmell); + } + + if (*buf) { + Snprintf(eos(buf), sizeof buf - strlen(buf), " Eat %s anyway?", + (otmp->quan == 1L) ? "it" : "one"); + return (yn_function(buf, ynchars, 'n', TRUE) == 'n') ? 1 : 2; } + return 0; +} - if (cadaver && mnum != PM_ACID_BLOB && rotted > 5L && Sick_resistance) { - /* Tainted meat with Sick_resistance */ - Sprintf(buf, "%s like %s could be tainted! %s", - foodsmell, it_or_they, eat_it_anyway); - if (yn_function(buf, ynchars, 'n') == 'n') - return 1; - else - return 2; +staticfn int +doeat_nonfood(struct obj *otmp) +{ + int basenutrit; /* nutrition of full item */ + int ll_conduct = 0; + boolean nodelicious = FALSE; + int material; + + svc.context.victual.reqtime = 1; + svc.context.victual.piece = otmp; + svc.context.victual.o_id = otmp->o_id; + /* Don't split it, we don't need to if it's 1 move */ + svc.context.victual.usedtime = 0; + svc.context.victual.canchoke = (u.uhs == SATIATED); + /* Note: gold weighs 1 pt. for each 1000 pieces (see + pickup.c) so gold and non-gold is consistent. */ + if (otmp->oclass == COIN_CLASS) + basenutrit = ((otmp->quan > 200000L) ? 2000 + : (int) (otmp->quan / 100L)); + else if (otmp->oclass == BALL_CLASS || otmp->oclass == CHAIN_CLASS) + basenutrit = weight(otmp); + /* oc_nutrition is usually weight anyway */ + else + basenutrit = objects[otmp->otyp].oc_nutrition; +#ifdef MAIL_STRUCTURES + if (otmp->otyp == SCR_MAIL) { + basenutrit = 0; + nodelicious = TRUE; } - return 0; +#endif + svc.context.victual.nmod = basenutrit; + svc.context.victual.eating = 1; /* needed for lesshungry() */ + + if (!u.uconduct.food++) { + ll_conduct++; + livelog_printf(LL_CONDUCT, "ate for the first time (%s)", + food_xname(otmp, FALSE)); + } + material = objects[otmp->otyp].oc_material; + if (material == LEATHER || material == BONE + || material == DRAGON_HIDE || material == WAX) { + if (!u.uconduct.unvegan++ && !ll_conduct) { + livelog_printf(LL_CONDUCT, + "consumed animal products for the first time, by eating %s", + an(food_xname(otmp, FALSE))); + ll_conduct++; + } + if (material != WAX) { + if (!u.uconduct.unvegetarian && !ll_conduct) + livelog_printf(LL_CONDUCT, + "tasted meat by-products for the first time, by eating %s", + an(food_xname(otmp, FALSE))); + violated_vegetarian(); + } + } + + if (otmp->cursed) { + (void) rottenfood(otmp); + nodelicious = TRUE; + } else if (objects[otmp->otyp].oc_material == PAPER) + nodelicious = TRUE; + + if (otmp->oclass == WEAPON_CLASS && otmp->opoisoned) { + pline("Ecch - that must have been poisonous!"); + if (!Poison_resistance) { + poison_strdmg(rnd(4), rnd(15), xname(otmp), KILLED_BY_AN); + } else + You("seem unaffected by the poison."); + } else if (!nodelicious) { + pline("%s%s is delicious!", + (obj_is_pname(otmp) + && otmp->oartifact < ART_ORB_OF_DETECTION) + ? "" + : "This ", + (otmp->oclass == COIN_CLASS) + ? foodword(otmp) + : singular(otmp, xname)); + } + eatspecial(); + return ECMD_TIME; } -/* 'e' command */ +/* the #eat command */ int -doeat() +doeat(void) { struct obj *otmp; int basenutrit; /* nutrition of full item */ - boolean dont_start = FALSE, nodelicious = FALSE, + boolean dont_start = FALSE, already_partly_eaten; + int ll_conduct = 0; if (Strangled) { pline("If you can't breathe air, how can you consume solids?"); - return 0; + return ECMD_OK; } if (!(otmp = floorfood("eat", 0))) - return 0; + return ECMD_OK; if (check_capacity((char *) 0)) - return 0; + return ECMD_OK; if (u.uedibility) { int res = edibility_prompts(otmp); @@ -2467,10 +2840,22 @@ doeat() body_part(NOSE)); u.uedibility = 0; if (res == 1) - return 0; + return ECMD_OK; } } + /* from floorfood(), &hands_obj means iron bars at current spot */ + if (otmp == &hands_obj) { + /* hero in metallivore form is eating [diggable] iron bars + at current location so skip the other assorted checks; + operates as if digging rather than via the eat occupation */ + if (still_chewing(u.ux, u.uy) && levl[u.ux][u.uy].typ == IRONBARS) { + /* this is verbose, but player will see the hero rather than the + bars so wouldn't know that more turns of eating are required */ + You("pause to swallow."); + } + return ECMD_TIME; + } /* We have to make non-foods take 1 move to eat, unless we want to * do ridiculous amounts of coding to deal with partly eaten plate * mails, players who polymorph back to human in the middle of their @@ -2478,15 +2863,15 @@ doeat() */ if (!is_edible(otmp)) { You("cannot eat that!"); - return 0; + return ECMD_OK; } else if ((otmp->owornmask & (W_ARMOR | W_TOOL | W_AMUL | W_SADDLE)) != 0) { /* let them eat rings */ You_cant("eat %s you're wearing.", something); - return 0; + return ECMD_OK; } else if (!(carried(otmp) ? retouch_object(&otmp, FALSE) - : touch_artifact(otmp, &youmonst))) { - return 1; /* got blasted so use a turn */ + : touch_artifact(otmp, &gy.youmonst))) { + return ECMD_TIME; /* got blasted so use a turn */ } if (is_metallic(otmp) && u.umonnum == PM_RUST_MONSTER && otmp->oerodeproof) { @@ -2521,99 +2906,48 @@ doeat() } stackobj(otmp); } - return 1; + return ECMD_TIME; } /* KMH -- Slow digestion is... indigestible */ if (otmp->otyp == RIN_SLOW_DIGESTION) { pline("This ring is indigestible!"); (void) rottenfood(otmp); - if (otmp->dknown && !objects[otmp->otyp].oc_name_known - && !objects[otmp->otyp].oc_uname) - docall(otmp); - return 1; + if (otmp->dknown) + trycall(otmp); + return ECMD_TIME; } - if (otmp->oclass != FOOD_CLASS) { - int material; - - context.victual.reqtime = 1; - context.victual.piece = otmp; - context.victual.o_id = otmp->o_id; - /* Don't split it, we don't need to if it's 1 move */ - context.victual.usedtime = 0; - context.victual.canchoke = (u.uhs == SATIATED); - /* Note: gold weighs 1 pt. for each 1000 pieces (see - pickup.c) so gold and non-gold is consistent. */ - if (otmp->oclass == COIN_CLASS) - basenutrit = ((otmp->quan > 200000L) - ? 2000 - : (int) (otmp->quan / 100L)); - else if (otmp->oclass == BALL_CLASS || otmp->oclass == CHAIN_CLASS) - basenutrit = weight(otmp); - /* oc_nutrition is usually weight anyway */ - else - basenutrit = objects[otmp->otyp].oc_nutrition; -#ifdef MAIL - if (otmp->otyp == SCR_MAIL) { - basenutrit = 0; - nodelicious = TRUE; - } -#endif - context.victual.nmod = basenutrit; - context.victual.eating = TRUE; /* needed for lesshungry() */ + if (otmp->oclass != FOOD_CLASS) + return doeat_nonfood(otmp); - material = objects[otmp->otyp].oc_material; - if (material == LEATHER || material == BONE - || material == DRAGON_HIDE) { - u.uconduct.unvegan++; - violated_vegetarian(); - } else if (material == WAX) - u.uconduct.unvegan++; - u.uconduct.food++; - - if (otmp->cursed) { - (void) rottenfood(otmp); - nodelicious = TRUE; - } else if (objects[otmp->otyp].oc_material == PAPER) - nodelicious = TRUE; - - if (otmp->oclass == WEAPON_CLASS && otmp->opoisoned) { - pline("Ecch - that must have been poisonous!"); - if (!Poison_resistance) { - losestr(rnd(4)); - losehp(rnd(15), xname(otmp), KILLED_BY_AN); - } else - You("seem unaffected by the poison."); - } else if (!nodelicious) { - pline("%s%s is delicious!", - (obj_is_pname(otmp) - && otmp->oartifact < ART_ORB_OF_DETECTION) - ? "" - : "This ", - (otmp->oclass == COIN_CLASS) - ? foodword(otmp) - : singular(otmp, xname)); - } - eatspecial(); - return 1; - } - if (otmp == context.victual.piece) { + if (otmp == svc.context.victual.piece) { + boolean one_bite_left = (svc.context.victual.usedtime + 1 + >= svc.context.victual.reqtime); + /* If they weren't able to choke, they don't suddenly become able to * choke just because they were interrupted. On the other hand, if * they were able to choke before, if they lost food it's possible * they shouldn't be able to choke now. */ if (u.uhs != SATIATED) - context.victual.canchoke = FALSE; - context.victual.o_id = 0; - context.victual.piece = touchfood(otmp); - if (context.victual.piece) - context.victual.o_id = context.victual.piece->o_id; - You("resume %syour meal.", - (context.victual.usedtime + 1 >= context.victual.reqtime) - ? "the last bite of " : ""); - start_eating(context.victual.piece, FALSE); - return 1; + svc.context.victual.canchoke = 0; + svc.context.victual.o_id = 0; + otmp = touchfood(otmp); + if (otmp) { + svc.context.victual.piece = otmp; + svc.context.victual.o_id = otmp->o_id; + } else { + do_reset_eat(); + } + /* if there's only one bite left, there sometimes won't be any + "you finish eating" message when done; use different wording + for resuming with one bite remaining instead of trying to + determine whether or not "you finish" is going to be given */ + You("%s your meal.", + !one_bite_left ? "resume" : "consume the last bite of"); + if (otmp) + start_eating(otmp, FALSE); + return ECMD_TIME; } /* nothing in progress - so try to find something. */ @@ -2621,18 +2955,26 @@ doeat() /* tins must also check conduct separately in case they're discarded */ if (otmp->otyp == TIN) { start_tin(otmp); - return 1; + return ECMD_TIME; } /* KMH, conduct */ - u.uconduct.food++; + if (!u.uconduct.food++) { + livelog_printf(LL_CONDUCT, "ate for the first time - %s", + food_xname(otmp, FALSE)); + ll_conduct++; + } already_partly_eaten = otmp->oeaten ? TRUE : FALSE; - context.victual.o_id = 0; - context.victual.piece = otmp = touchfood(otmp); - if (context.victual.piece) - context.victual.o_id = context.victual.piece->o_id; - context.victual.usedtime = 0; + otmp = touchfood(otmp); + if (otmp) { + svc.context.victual.piece = otmp; + svc.context.victual.o_id = otmp->o_id; + svc.context.victual.usedtime = 0; + } else { + do_reset_eat(); + return ECMD_TIME; + } /* Now we need to calculate delay and nutritional info. * The base nutrition calculated here and in eatcorpse() accounts @@ -2644,9 +2986,8 @@ doeat() if (tmp == 2) { /* used up */ - context.victual.piece = (struct obj *) 0; - context.victual.o_id = 0; - return 1; + svc.context.victual = zero_victual; /* victual.piece=0, .o_id=0 */ + return ECMD_TIME; } else if (tmp) dont_start = TRUE; /* if not used up, eatcorpse sets up reqtime and may modify oeaten */ @@ -2656,24 +2997,36 @@ doeat() */ switch (objects[otmp->otyp].oc_material) { case FLESH: - u.uconduct.unvegan++; + if (!u.uconduct.unvegan++ && !ll_conduct) { + livelog_printf(LL_CONDUCT, + "consumed animal products for the first time, by eating %s", + an(food_xname(otmp, FALSE))); + ll_conduct++; + } if (otmp->otyp != EGG) { + if (!u.uconduct.unvegetarian && !ll_conduct) + livelog_printf(LL_CONDUCT, + "tasted meat for the first time, by eating %s", + an(food_xname(otmp, FALSE))); + violated_vegetarian(); } break; - default: if (otmp->otyp == PANCAKE || otmp->otyp == FORTUNE_COOKIE /*eggs*/ || otmp->otyp == CREAM_PIE || otmp->otyp == CANDY_BAR /*milk*/ || otmp->otyp == LUMP_OF_ROYAL_JELLY) - u.uconduct.unvegan++; + if (!u.uconduct.unvegan++ && !ll_conduct) + livelog_printf(LL_CONDUCT, + "consumed animal products (%s) for the first time", + food_xname(otmp, FALSE)); break; } - context.victual.reqtime = objects[otmp->otyp].oc_delay; + svc.context.victual.reqtime = objects[otmp->otyp].oc_delay; if (otmp->otyp != FORTUNE_COOKIE && (otmp->cursed || (!nonrotting_food(otmp->otyp) - && (monstermoves - otmp->age) + && (svm.moves - otmp->age) > (otmp->blessed ? 50L : 30L) && (otmp->orotten || !rn2(7))))) { if (rottenfood(otmp)) { @@ -2682,10 +3035,13 @@ doeat() } consume_oeaten(otmp, 1); /* oeaten >>= 1 */ } else if (!already_partly_eaten) { - fprefx(otmp); + if (!fprefx(otmp)) { + do_reset_eat(); + return ECMD_TIME; + } } else { You("%s %s.", - (context.victual.reqtime == 1) ? "eat" : "begin eating", + (svc.context.victual.reqtime == 1) ? "eat" : "begin eating", doname(otmp)); } } @@ -2695,44 +3051,58 @@ doeat() debugpline3( "before rounddiv: victual.reqtime == %d, oeaten == %d, basenutrit == %d", - context.victual.reqtime, otmp->oeaten, basenutrit); + svc.context.victual.reqtime, otmp->oeaten, basenutrit); - context.victual.reqtime = (basenutrit == 0) ? 0 - : rounddiv(context.victual.reqtime * (long) otmp->oeaten, basenutrit); + svc.context.victual.reqtime + = (basenutrit == 0) ? 0 + : rounddiv(svc.context.victual.reqtime * (long) otmp->oeaten, + basenutrit); debugpline1("after rounddiv: victual.reqtime == %d", - context.victual.reqtime); + svc.context.victual.reqtime); /* * calculate the modulo value (nutrit. units per round eating) * note: this isn't exact - you actually lose a little nutrition due * to this method. * TODO: add in a "remainder" value to be given at the end of the meal. */ - if (context.victual.reqtime == 0 || otmp->oeaten == 0) + if (svc.context.victual.reqtime == 0 || otmp->oeaten == 0) /* possible if most has been eaten before */ - context.victual.nmod = 0; - else if ((int) otmp->oeaten >= context.victual.reqtime) - context.victual.nmod = -((int) otmp->oeaten - / context.victual.reqtime); + svc.context.victual.nmod = 0; + else if ((int) otmp->oeaten >= svc.context.victual.reqtime) + svc.context.victual.nmod = -((int) otmp->oeaten + / svc.context.victual.reqtime); else - context.victual.nmod = context.victual.reqtime % otmp->oeaten; - context.victual.canchoke = (u.uhs == SATIATED); + svc.context.victual.nmod = svc.context.victual.reqtime % otmp->oeaten; + svc.context.victual.canchoke = (u.uhs == SATIATED); if (!dont_start) start_eating(otmp, already_partly_eaten); - return 1; + else + otmp->owt = weight(otmp); + return ECMD_TIME; +} + +/* getobj callback for object to be opened with a tin opener */ +staticfn int +tinopen_ok(struct obj *obj) +{ + if (obj && obj->otyp == TIN) + return GETOBJ_SUGGEST; + + return GETOBJ_EXCLUDE; } + int -use_tin_opener(obj) -struct obj *obj; +use_tin_opener(struct obj *obj) { struct obj *otmp; - int res = 0; + int res = ECMD_OK; if (!carrying(TIN)) { You("have no tin to open."); - return 0; + return ECMD_OK; } if (obj != uwep) { @@ -2741,55 +3111,60 @@ struct obj *obj; if (ynq(safe_qbuf(qbuf, "Really wield ", "?", obj, doname, thesimpleoname, "that")) != 'y') - return 0; + return ECMD_OK; } if (!wield_tool(obj, "use")) - return 0; - res = 1; + return ECMD_OK; + res = ECMD_TIME; } - otmp = getobj(comestibles, "open"); + otmp = getobj("open", tinopen_ok, GETOBJ_NOFLAGS); if (!otmp) - return res; + return (res|ECMD_CANCEL); start_tin(otmp); - return 1; + return ECMD_TIME; } /* Take a single bite from a piece of food, checking for choking and * modifying usedtime. Returns 1 if they choked and survived, 0 otherwise. */ -STATIC_OVL int -bite() +staticfn int +bite(void) { - if (context.victual.canchoke && u.uhunger >= 2000) { - choke(context.victual.piece); + /* hack to pacify static analyzer incorporated into gcc 12.2 */ + sa_victual(&svc.context.victual); + + if (svc.context.victual.canchoke && u.uhunger >= 2000) { + choke(svc.context.victual.piece); return 1; } - if (context.victual.doreset) { + if (svc.context.victual.doreset) { do_reset_eat(); return 0; } - force_save_hs = TRUE; - if (context.victual.nmod < 0) { - lesshungry(-context.victual.nmod); - consume_oeaten(context.victual.piece, - context.victual.nmod); /* -= -nmod */ - } else if (context.victual.nmod > 0 - && (context.victual.usedtime % context.victual.nmod)) { + gf.force_save_hs = TRUE; + if (svc.context.victual.nmod < 0) { + lesshungry(adj_victual_nutrition(/*-svc.context.victual.nmod*/)); + consume_oeaten(svc.context.victual.piece, + svc.context.victual.nmod); /* -= -nmod */ + } else if (svc.context.victual.nmod > 0 + && (svc.context.victual.usedtime % svc.context.victual.nmod)) { lesshungry(1); - consume_oeaten(context.victual.piece, -1); /* -= 1 */ + consume_oeaten(svc.context.victual.piece, -1); /* -= 1 */ } - force_save_hs = FALSE; + gf.force_save_hs = FALSE; recalc_wt(); return 0; } /* as time goes by - called by moveloop(every move) & domove(melee attack) */ void -gethungry() +gethungry(void) { - if (u.uinvulnerable) + int accessorytime; + + if (u.uinvulnerable || iflags.debug_hunger) return; /* you don't feel hungrier */ /* being polymorphed into a creature which doesn't eat prevents @@ -2797,38 +3172,97 @@ gethungry() will need to wear an Amulet of Unchanging so still burn a small amount of nutrition in the 'moves % 20' ring/amulet check below */ if ((!Unaware || !rn2(10)) /* slow metabolic rate while asleep */ - && (carnivorous(youmonst.data) - || herbivorous(youmonst.data) - || metallivorous(youmonst.data)) + && (carnivorous(gy.youmonst.data) + || herbivorous(gy.youmonst.data) + || metallivorous(gy.youmonst.data)) && !Slow_digestion) u.uhunger--; /* ordinary food consumption */ - if (moves % 2) { /* odd turns */ + /* + * 5.0: trigger is randomized instead of (moves % N). Makes + * ring juggling (using the 'time' option to see the turn counter + * in order to time swapping of a pair of rings of slow digestion, + * wearing one on one hand, then putting on the other and taking + * off the first, then vice versa, over and over and over and ... + * to avoid any hunger from wearing a ring) become ineffective. + * Also causes melee-induced hunger to vary from turn-based hunger + * instead of just replicating that. + */ + accessorytime = rn2(20); /* rn2(20) replaces (int) (svm.moves % 20L) */ + if (accessorytime % 2) { /* odd */ /* Regeneration uses up food, unless due to an artifact */ if ((HRegeneration & ~FROMFORM) || (ERegeneration & ~(W_ARTI | W_WEP))) u.uhunger--; if (near_capacity() > SLT_ENCUMBER) u.uhunger--; - } else { /* even turns */ + } else { /* even */ if (Hunger) u.uhunger--; /* Conflict uses up food too */ if (HConflict || (EConflict & (~W_ARTI))) u.uhunger--; - /* +0 charged rings don't do anything, so don't affect hunger. - Slow digestion cancels move hunger but still causes ring hunger. */ - switch ((int) (moves % 20)) { /* note: use even cases only */ + /* + * +0 charged rings don't do anything, so don't affect hunger. + * Slow digestion cancels movement and melee hunger but still + * causes ring hunger. + * Possessing the real Amulet imposes a separate hunger penalty + * from wearing an amulet (so gets a double penalty when worn). + * + * 5.0.0: Worn meat rings don't affect hunger. + * Same with worn cheap plastic imitation of the Amulet. + * +0 ring of protection might do something (enhanced "magical + * cancellation") if hero doesn't have protection from some + * other source (cloak or second ring). + * + * [If wearing duplicate rings whose effects don't stack, + * should they both consume nutrition, or just one of them? + * Two +0 rings of protection are treated as if only one, + * but this could apply to most rings.] + */ + switch (accessorytime) { /* note: use even cases among 0..19 only */ + case 0: + /* 5.0: if not wearing a ring of slow digestion, obtaining + that property from worn armor (white dragon scales/mail) + causes the armor to burn nutrition; since it's not + actually a ring, we don't check for it on the ring + turns; because of that, wearing two (non-slow digestion) + rings plus the armor consumes more nutrition that one + non-slow digestion ring plus ring of slow digestion */ + if (Slow_digestion + && (!uright || uright->otyp != RIN_SLOW_DIGESTION) + && (!uleft || uleft->otyp != RIN_SLOW_DIGESTION)) + u.uhunger--; + break; case 4: - if (uleft && (uleft->spe || !objects[uleft->otyp].oc_charged)) + if (uleft && uleft->otyp != MEAT_RING + /* more hungry if +/- is nonzero or +/- doesn't apply or + +0 ring of protection is only source of protection; + need to check whether both rings are +0 protection or + they'd both slip by the "is there another source?" test, + but don't do that for both rings or they will both be + treated as supplying "MC" when only one matters; + note: amulet of guarding overrides both +0 rings and + is caught by the (EProtection & ~W_RINGx) == 0L tests */ + && (uleft->spe + || !objects[uleft->otyp].oc_charged + || (uleft->otyp == RIN_PROTECTION + && ((EProtection & ~W_RINGL) == 0L + || ((EProtection & ~W_RINGL) == W_RINGR + && uright && uright->otyp == RIN_PROTECTION + && !uright->spe))))) u.uhunger--; break; case 8: - if (uamul) + if (uamul && uamul->otyp != FAKE_AMULET_OF_YENDOR) u.uhunger--; break; case 12: - if (uright && (uright->spe || !objects[uright->otyp].oc_charged)) + if (uright && uright->otyp != MEAT_RING + && (uright->spe + || !objects[uright->otyp].oc_charged + || (uright->otyp == RIN_PROTECTION + && (EProtection & ~W_RINGR) == 0L))) u.uhunger--; break; case 16: @@ -2844,8 +3278,7 @@ gethungry() /* called after vomiting and after performing feats of magic */ void -morehungry(num) -int num; +morehungry(int num) { u.uhunger -= num; newuhs(TRUE); @@ -2853,42 +3286,44 @@ int num; /* called after eating (and after drinking fruit juice) */ void -lesshungry(num) -int num; +lesshungry(int num) { /* See comments in newuhs() for discussion on force_save_hs */ - boolean iseating = (occupation == eatfood) || force_save_hs; + boolean iseating = (go.occupation == eatfood) || gf.force_save_hs; debugpline1("lesshungry(%d)", num); u.uhunger += num; if (u.uhunger >= 2000) { - if (!iseating || context.victual.canchoke) { + if (!iseating || svc.context.victual.canchoke) { if (iseating) { - choke(context.victual.piece); + choke(svc.context.victual.piece); reset_eat(); - } else - choke(occupation == opentin ? context.tin.tin - : (struct obj *) 0); - /* no reset_eat() */ + } else { + choke((go.occupation == opentin) ? svc.context.tin.tin : 0); + /* no reset_eat() */ + } } } else { /* Have lesshungry() report when you're nearly full so all eating * warns when you're about to choke. */ - if (u.uhunger >= 1500 - && (!context.victual.eating - || (context.victual.eating && !context.victual.fullwarn))) { + if (u.uhunger >= 1500 && !Hunger + && (!svc.context.victual.eating + || (svc.context.victual.eating + && !svc.context.victual.fullwarn))) { pline("You're having a hard time getting all of it down."); - nomovemsg = "You're finally finished."; - if (!context.victual.eating) { - multi = -2; + gn.nomovemsg = "You're finally finished."; + if (!svc.context.victual.eating) { + gm.multi = -2; } else { - context.victual.fullwarn = TRUE; - if (context.victual.canchoke && context.victual.reqtime > 1) { - /* a one-gulp food will not survive a stop */ + svc.context.victual.fullwarn = 1; + if (svc.context.victual.canchoke + && (svc.context.victual.reqtime + - svc.context.victual.usedtime) > 1) { + /* food with one bite left will not survive a stop */ if (!paranoid_query(ParanoidEating, "Continue eating?")) { reset_eat(); - nomovemsg = (char *) 0; + gn.nomovemsg = (char *) 0; } } } @@ -2897,36 +3332,34 @@ int num; newuhs(FALSE); } -STATIC_PTR -int -unfaint(VOID_ARGS) +staticfn int +unfaint(void) { (void) Hear_again(); if (u.uhs > FAINTING) u.uhs = FAINTING; stop_occupation(); - context.botl = 1; + disp.botl = TRUE; return 0; } boolean -is_fainted() +is_fainted(void) { return (boolean) (u.uhs == FAINTED); } /* call when a faint must be prematurely terminated */ void -reset_faint() +reset_faint(void) { - if (afternmv == unfaint) + if (ga.afternmv == unfaint) unmul("You revive."); } /* compute and comment on your (new?) hunger status */ void -newuhs(incr) -boolean incr; +newuhs(boolean incr) { unsigned newhs; static unsigned save_hs; @@ -2960,7 +3393,7 @@ boolean incr; * were added or if HUNGRY and WEAK were separated by a big enough * gap to fit two bites. */ - if (occupation == eatfood || force_save_hs) { + if (go.occupation == eatfood || gf.force_save_hs) { if (!saved_hs) { save_hs = u.uhs; saved_hs = TRUE; @@ -2981,18 +3414,18 @@ boolean incr; if (is_fainted()) newhs = FAINTED; if (u.uhs <= WEAK || rn2(20 - uhunger_div_by_10) >= 19) { - if (!is_fainted() && multi >= 0 /* %% */) { + if (!is_fainted() && gm.multi >= 0 /* %% */) { int duration = 10 - uhunger_div_by_10; /* stop what you're doing, then faint */ stop_occupation(); You("faint from lack of food."); incr_itimeout(&HDeaf, duration); - context.botl = TRUE; + disp.botl = TRUE; nomul(-duration); - multi_reason = "fainted from lack of food"; - nomovemsg = "You regain consciousness."; - afternmv = unfaint; + gm.multi_reason = "fainted from lack of food"; + gn.nomovemsg = "You regain consciousness."; + ga.afternmv = unfaint; newhs = FAINTED; if (!Levitation) selftouch("Falling, you"); @@ -3003,11 +3436,11 @@ boolean incr; now uhunger becomes more negative at a slower rate */ } else if (u.uhunger < -(100 + 10 * (int) ACURR(A_CON))) { u.uhs = STARVED; - context.botl = 1; + disp.botl = TRUE; bot(); You("die from starvation."); - killer.format = KILLED_BY; - Strcpy(killer.name, "starvation"); + svk.killer.format = KILLED_BY; + Strcpy(svk.killer.name, "starvation"); done(STARVING); /* if we return, we lifesaved, and that calls newuhs */ return; @@ -3035,75 +3468,138 @@ boolean incr; switch (newhs) { case HUNGRY: if (Hallucination) { - You((!incr) ? "now have a lesser case of the munchies." - : "are getting the munchies."); + You(!incr ? "now have a lesser case of the munchies." + : "are getting the munchies."); } else - You((!incr) ? "only feel hungry now." - : (u.uhunger < 145) - ? "feel hungry." - : "are beginning to feel hungry."); - if (incr && occupation - && (occupation != eatfood && occupation != opentin)) + You("%s.", !incr ? "only feel hungry now" + : (u.uhunger < 145) ? "feel hungry" + : "are beginning to feel hungry"); + if (incr && go.occupation + && (go.occupation != eatfood && go.occupation != opentin)) stop_occupation(); - context.travel = context.travel1 = context.mv = context.run = 0; + end_running(TRUE); break; case WEAK: if (Hallucination) - pline((!incr) ? "You still have the munchies." + pline(!incr ? "You still have the munchies." : "The munchies are interfering with your motor capabilities."); else if (incr && (Role_if(PM_WIZARD) || Race_if(PM_ELF) || Role_if(PM_VALKYRIE))) pline("%s needs food, badly!", (Role_if(PM_WIZARD) || Role_if(PM_VALKYRIE)) - ? urole.name.m + ? gu.urole.name.m : "Elf"); else - You((!incr) - ? "feel weak now." - : (u.uhunger < 45) ? "feel weak." - : "are beginning to feel weak."); - if (incr && occupation - && (occupation != eatfood && occupation != opentin)) + You("%s weak.", !incr ? "are still" + : (u.uhunger < 45) ? "feel" + : "are beginning to feel"); + if (incr && go.occupation + && (go.occupation != eatfood && go.occupation != opentin)) stop_occupation(); - context.travel = context.travel1 = context.mv = context.run = 0; + end_running(TRUE); break; } u.uhs = newhs; - context.botl = 1; + disp.botl = TRUE; bot(); if ((Upolyd ? u.mh : u.uhp) < 1) { You("die from hunger and exhaustion."); - killer.format = KILLED_BY; - Strcpy(killer.name, "exhaustion"); + svk.killer.format = KILLED_BY; + Strcpy(svk.killer.name, "exhaustion"); done(STARVING); return; } } } +/* getobj callback for object to eat - effectively just wraps is_edible() */ +staticfn int +eat_ok(struct obj *obj) +{ + /* 'getobj_else' will be non-zero if floor food is present and + player declined to eat that; used to insert "else" into + "you don't have anything [else] to eat" if not carrying any food */ + if (!obj) + return getobj_else ? GETOBJ_EXCLUDE_NONINVENT : GETOBJ_EXCLUDE; + + if (is_edible(obj)) + return GETOBJ_SUGGEST; + + /* make sure to exclude, not downplay, gold (if not is_edible) in order to + * produce the "You cannot eat gold" message in getobj */ + if (obj->oclass == COIN_CLASS) + return GETOBJ_EXCLUDE; + + return GETOBJ_EXCLUDE_SELECTABLE; +} + +/* getobj callback for object to be offered (corpses and things that look like + * the Amulet only */ +staticfn int +offer_ok(struct obj *obj) +{ + if (!obj) + return getobj_else ? GETOBJ_EXCLUDE_NONINVENT : GETOBJ_EXCLUDE; + + if (obj->oclass != FOOD_CLASS && obj->oclass != AMULET_CLASS) + return GETOBJ_EXCLUDE; + + if (obj->otyp != CORPSE && obj->otyp != AMULET_OF_YENDOR + && obj->otyp != FAKE_AMULET_OF_YENDOR) + return GETOBJ_EXCLUDE_SELECTABLE; + + /* suppress corpses on astral, amulets elsewhere + * (!astral && amulet) || (astral && !amulet) */ + if (Is_astralevel(&u.uz) ^ (obj->oclass == AMULET_CLASS)) + return GETOBJ_DOWNPLAY; + + return GETOBJ_SUGGEST; +} + +/* getobj callback for object to be tinned */ +staticfn int +tin_ok(struct obj *obj) +{ + if (!obj) + return getobj_else ? GETOBJ_EXCLUDE_NONINVENT : GETOBJ_EXCLUDE; + + if (obj->oclass != FOOD_CLASS) + return GETOBJ_EXCLUDE; + + if (obj->otyp != CORPSE || !tinnable(obj)) + return GETOBJ_EXCLUDE_SELECTABLE; + + return GETOBJ_SUGGEST; +} + /* Returns an object representing food. * Object may be either on floor or in inventory. */ struct obj * -floorfood(verb, corpsecheck) -const char *verb; -int corpsecheck; /* 0, no check, 1, corpses, 2, tinnable corpses */ +floorfood( + const char *verb, + int corpsecheck) /* 0, no check, 1, corpses, 2, tinnable corpses */ { - register struct obj *otmp; + struct obj *otmp; char qbuf[QBUFSZ]; char c; - boolean feeding = !strcmp(verb, "eat"), /* corpsecheck==0 */ - offering = !strcmp(verb, "sacrifice"); /* corpsecheck==1 */ - - /* if we can't touch floor objects then use invent food only */ - if (iflags.menu_requested /* command was preceded by 'm' prefix */ + struct permonst *uptr = gy.youmonst.data; + boolean feeding = !strcmp(verb, "eat"), /* corpsecheck==0 */ + offering = !strcmp(verb, "sacrifice"); /* corpsecheck==1 */ + + getobj_else = 0; /* haven't asked about floor food; is used to vary + * "you don't have anything [else] to eat" when + * floor food has been declined and inventory lacks + * any suitable items */ + /* if we can't touch floor objects then use invent food only; + same when 'm' prefix is used--for #eat, it means "skip floor food" */ + if (iflags.menu_requested || !can_reach_floor(TRUE) || (feeding && u.usteed) || (is_pool_or_lava(u.ux, u.uy) - && (Wwalking || is_clinger(youmonst.data) - || (Flying && !Breathless)))) + && (Wwalking || is_clinger(uptr) || (Flying && !Breathless)))) goto skipfloor; - if (feeding && metallivorous(youmonst.data)) { + if (feeding && metallivorous(uptr)) { struct obj *gold; struct trap *ttmp = t_at(u.ux, u.uy); @@ -3115,33 +3611,68 @@ int corpsecheck; /* 0, no check, 1, corpses, 2, tinnable corpses */ then the trap would just get eaten on the _next_ turn... */ Sprintf(qbuf, "There is a bear trap here (%s); eat it?", u_in_beartrap ? "holding you" : "armed"); - if ((c = yn_function(qbuf, ynqchars, 'n')) == 'y') { + if ((c = yn_function(qbuf, ynqchars, 'n', TRUE)) == 'y') { + struct obj *beartrap; + deltrap(ttmp); if (u_in_beartrap) reset_utrap(TRUE); - return mksobj(BEARTRAP, TRUE, FALSE); + beartrap = mksobj(BEARTRAP, TRUE, FALSE); + Sprintf(qbuf,"You only manage to %s the bear trap.", + u_in_beartrap ? "free yourself from" : "disarm"); + if (check_capacity(qbuf) && beartrap) { + obj_extract_self(beartrap); + dropy(beartrap); /* put it on the floor */ + return (struct obj *) 0; + } + return beartrap; } else if (c == 'q') { return (struct obj *) 0; } + ++getobj_else; } + if (levl[u.ux][u.uy].typ == IRONBARS) { + /* already verified that hero is metallivorous above */ + boolean nodig = (levl[u.ux][u.uy].wall_info & W_NONDIGGABLE) != 0; - if (youmonst.data != &mons[PM_RUST_MONSTER] + c = 'n'; + Strcpy(qbuf, "There are iron bars here"); + if (nodig || u.uhunger > 1500) { + pline("%s but you %s eat them.", qbuf, + nodig ? "cannot" : "are too full to"); + } else { + Strcat(qbuf, (!svc.context.digging.chew + || !u_at(svc.context.digging.pos.x, + svc.context.digging.pos.y) + || !on_level(&svc.context.digging.level, &u.uz)) + ? "; eat them?" + : "; resume eating them?"); + c = yn_function(qbuf, ynqchars, 'n', TRUE); + } + if (c == 'y') + return &hands_obj; + else if (c == 'q') + return (struct obj *) 0; + ++getobj_else; + } + if (uptr != &mons[PM_RUST_MONSTER] && (gold = g_at(u.ux, u.uy)) != 0) { if (gold->quan == 1L) Sprintf(qbuf, "There is 1 gold piece here; eat it?"); else Sprintf(qbuf, "There are %ld gold pieces here; eat them?", gold->quan); - if ((c = yn_function(qbuf, ynqchars, 'n')) == 'y') { + if ((c = yn_function(qbuf, ynqchars, 'n', TRUE)) == 'y') { return gold; } else if (c == 'q') { return (struct obj *) 0; } + ++getobj_else; } } /* Is there some food (probably a heavy corpse) here on the ground? */ - for (otmp = level.objects[u.ux][u.uy]; otmp; otmp = otmp->nexthere) { + for (otmp = svl.level.objects[u.ux][u.uy]; otmp; otmp = otmp->nexthere) { if (corpsecheck ? (otmp->otyp == CORPSE && (corpsecheck == 1 || tinnable(otmp))) @@ -3166,33 +3697,47 @@ int corpsecheck; /* 0, no check, 1, corpses, 2, tinnable corpses */ Sprintf(qsfx, " here; %s %s?", verb, one ? "it" : "one"); (void) safe_qbuf(qbuf, qbuf, qsfx, otmp, doname, ansimpleoname, one ? something : (const char *) "things"); - if ((c = yn_function(qbuf, ynqchars, 'n')) == 'y') + if ((c = yn_function(qbuf, ynqchars, 'n', TRUE)) == 'y') return otmp; else if (c == 'q') return (struct obj *) 0; + ++getobj_else; } } skipfloor: - /* We cannot use ALL_CLASSES since that causes getobj() to skip its - * "ugly checks" and we need to check for inedible items. - */ - otmp = getobj(feeding ? allobj : offering ? offerfodder : comestibles, - verb); - if (corpsecheck && otmp && !(offering && otmp->oclass == AMULET_CLASS)) + /* We cannot use GETOBJ_PROMPT since we don't want a prompt in the case + where nothing edible is being carried. */ + if (feeding) { + otmp = getobj("eat", eat_ok, GETOBJ_NOFLAGS); + } else if (offering) { + otmp = getobj("sacrifice", offer_ok, GETOBJ_NOFLAGS); + } else if (corpsecheck == 2) { + otmp = getobj(verb, tin_ok, GETOBJ_NOFLAGS); + } else { + impossible("floorfood: unknown request (%s)", verb); + otmp = (struct obj *) 0; + } + if (otmp && corpsecheck && !(offering && otmp->oclass == AMULET_CLASS)) { if (otmp->otyp != CORPSE || (corpsecheck == 2 && !tinnable(otmp))) { You_cant("%s that!", verb); - return (struct obj *) 0; + otmp = (struct obj *) 0; } + } + /* resetting 'getobj_else' here isn't essential; it will be cleared the + next time it needs to be used */ + getobj_else = 0; return otmp; } /* Side effects of vomiting */ /* added nomul (MRS) - it makes sense, you're too busy being sick! */ void -vomit() /* A good idea from David Neves */ +vomit(void) /* A good idea from David Neves */ { - if (cantvomit(youmonst.data)) { + boolean spewed = FALSE; + + if (cantvomit(gy.youmonst.data)) { /* doesn't cure food poisoning; message assumes that we aren't dealing with some esoteric body_part() */ Your("jaw gapes convulsively."); @@ -3204,22 +3749,43 @@ vomit() /* A good idea from David Neves */ reaches 0, but only if u.uhs < FAINTING (and !cantvomit()) */ if (u.uhs >= FAINTING) Your("%s heaves convulsively!", body_part(STOMACH)); + else + spewed = TRUE; } /* nomul()/You_can_move_again used to be unconditional, which was viable while eating but not for Vomiting countdown where hero might be immobilized for some other reason at the time vomit() is called */ - if (multi >= -2) { + if (gm.multi >= -2) { nomul(-2); - multi_reason = "vomiting"; - nomovemsg = You_can_move_again; + gm.multi_reason = "vomiting"; + gn.nomovemsg = You_can_move_again; + } + + if (spewed) { + struct attack + *mattk = attacktype_fordmg(gy.youmonst.data, AT_BREA, AD_ACID); + + /* currently, only yellow dragons can breathe acid */ + if (mattk) { + You("breathe acid on yourself..."); /* [why?] */ + ubreatheu(mattk); + } + /* vomiting on an altar is, all things considered, rather impolite */ + if (IS_ALTAR(levl[u.ux][u.uy].typ)) + altar_wrath(u.ux, u.uy); + /* if poly'd into acidic form, stomach acid is stronger than normal */ + if (acidic(gy.youmonst.data)) { + /* TODO: if there's a web here, destroy that too (before ice) */ + if (is_ice(u.ux, u.uy)) + melt_ice(u.ux, u.uy, + "Your stomach acid melts straight through the ice!"); + } } } int -eaten_stat(base, obj) -int base; -struct obj *obj; +eaten_stat(int base, struct obj *obj) { long uneaten_amt, full_amount; @@ -3239,17 +3805,33 @@ struct obj *obj; /* reduce obj's oeaten field, making sure it never hits or passes 0 */ void -consume_oeaten(obj, amt) -struct obj *obj; -int amt; +consume_oeaten(struct obj *obj, int amt) { + if (!obj_nutrition(obj)) { + char itembuf[40]; + int otyp = obj->otyp; + + if (otyp == CORPSE || otyp == EGG || otyp == TIN) { + Strcpy(itembuf, (otyp == CORPSE) ? "corpse" + : (otyp == EGG) ? "egg" + : (otyp == TIN) ? "tin" : "other?"); + Sprintf(eos(itembuf), " [%d]", obj->corpsenm); + } else { + Sprintf(itembuf, "%d", otyp); + } + impossible( + "oeaten: attempting to set 0 nutrition food (%s) partially eaten", + itembuf); + return; + } + /* * This is a hack to try to squelch several long standing mystery * food bugs. A better solution would be to rewrite the entire * victual handling mechanism from scratch using a less complex * model. Alternatively, this routine could call done_eating() * or food_disappears() but its callers would need revisions to - * cope with context.victual.piece unexpectedly going away. + * cope with svc.context.victual.piece unexpectedly going away. * * Multi-turn eating operates by setting the food's oeaten field * to its full nutritional value and then running a counter which @@ -3280,10 +3862,11 @@ int amt; obj->oeaten = 0; } + /* mustn't let partly-eaten drop all the way to 0 or the item would + become restored to untouched; set to no bites left */ if (obj->oeaten == 0) { - if (obj == context.victual.piece) /* always true unless wishing... */ - context.victual.reqtime = - context.victual.usedtime; /* no bites left */ + if (obj == svc.context.victual.piece) /* always true unless wishing */ + svc.context.victual.reqtime = svc.context.victual.usedtime; obj->oeaten = 1; /* smallest possible positive value */ } } @@ -3291,36 +3874,57 @@ int amt; /* called when eatfood occupation has been interrupted, or in the case of theft, is about to be interrupted */ boolean -maybe_finished_meal(stopping) -boolean stopping; +maybe_finished_meal(boolean stopping) { /* in case consume_oeaten() has decided that the food is all gone */ - if (occupation == eatfood - && context.victual.usedtime >= context.victual.reqtime) { + if (go.occupation == eatfood + && svc.context.victual.usedtime >= svc.context.victual.reqtime) { if (stopping) - occupation = 0; /* for do_reset_eat */ - (void) eatfood(); /* calls done_eating() to use up - context.victual.piece */ + go.occupation = 0; /* for do_reset_eat */ + /* eatfood() calls done_eating() to use up svc.context.victual.piece */ + (void) eatfood(); return TRUE; } return FALSE; } +/* called by revive(); sort of the opposite of maybe_finished_meal() */ +void +cant_finish_meal(struct obj *corpse) +{ + /* + * When a corpse gets resurrected, the makemon() for that might + * call stop_occupation(). If that happens, prevent it from using + * up the corpse via maybe_finished_meal() when there's not enough + * left for another bite. revive() needs continued access to the + * corpse and will delete it when done. + */ + if (go.occupation == eatfood && svc.context.victual.piece == corpse) { + /* normally performed by done_eating() */ + svc.context.victual = zero_victual; /* victual.piece = 0, .o_id = 0 */ + + if (!corpse->oeaten) + corpse->oeaten = 1; /* [see consume_oeaten()] */ + go.occupation = donull; /* any non-Null other than eatfood() */ + stop_occupation(); + newuhs(FALSE); + } +} + /* Tin of to the rescue? Decide whether current occupation is an attempt to eat a tin of something capable of saving hero's life. We don't care about consumption of non-tinned food here because special effects there take place on first bite rather than at end of occupation. [Popeye the Sailor gets out of trouble by eating tins of spinach. :-] */ boolean -Popeye(threat) -int threat; +Popeye(int threat) { struct obj *otin; int mndx; - if (occupation != opentin) + if (go.occupation != opentin) return FALSE; - otin = context.tin.tin; + otin = svc.context.tin.tin; /* make sure hero still has access to tin */ if (!carried(otin) && (!obj_here(otin, u.ux, u.uy) || !can_reach_floor(TRUE))) @@ -3336,10 +3940,12 @@ int threat; return (boolean) (mndx != NON_PM || otin->spe == 1); /* flesh from lizards and acidic critters stops petrification */ case STONED: - return (boolean) (mndx >= LOW_PM + return (boolean) (ismnum(mndx) && (mndx == PM_LIZARD || acidic(&mons[mndx]))); - /* no tins can cure these (yet?) */ + /* polymorph into a fiery monster */ case SLIMED: + return (boolean) polyfood(otin); + /* no tins can cure these (yet?) */ case SICK: case VOMITING: break; @@ -3349,4 +3955,16 @@ int threat; return FALSE; } +/* the hero has swallowed a monster whole as a purple worm or similar, and has + finished digesting its corpse (called via ga.afternmv) */ +int +Finish_digestion(void) +{ + if (gc.corpsenm_digested != NON_PM) { + cpostfx(gc.corpsenm_digested); + gc.corpsenm_digested = NON_PM; + } + return 0; +} + /*eat.c*/ diff --git a/src/end.c b/src/end.c index 6501169e8..f0776a036 100644 --- a/src/end.c +++ b/src/end.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 end.c $NHDT-Date: 1575245059 2019/12/02 00:04:19 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.181 $ */ +/* NetHack 5.0 end.c $NHDT-Date: 1720397752 2024/07/08 00:15:52 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.315 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2012. */ /* NetHack may be freely redistributed. See license for details. */ @@ -6,288 +6,40 @@ #define NEED_VARARGS /* comment line for pre-compiled headers */ #include "hack.h" -#include "lev.h" #ifndef NO_SIGNAL #include #endif -#include -#ifndef LONG_MAX -#include -#endif #include "dlb.h" -extern void FDECL(nle_done, (int)); - -/* add b to long a, convert wraparound to max value */ -#define nowrap_add(a, b) (a = ((a + b) < 0 ? LONG_MAX : (a + b))) - -/* these probably ought to be generated by makedefs, like LAST_GEM */ -#define FIRST_GEM DILITHIUM_CRYSTAL -#define FIRST_AMULET AMULET_OF_ESP -#define LAST_AMULET AMULET_OF_YENDOR - -struct valuable_data { - long count; - int typ; -}; - -static struct valuable_data - gems[LAST_GEM + 1 - FIRST_GEM + 1], /* 1 extra for glass */ - amulets[LAST_AMULET + 1 - FIRST_AMULET]; - -static struct val_list { - struct valuable_data *list; - int size; -} valuables[] = { { gems, sizeof gems / sizeof *gems }, - { amulets, sizeof amulets / sizeof *amulets }, - { 0, 0 } }; +extern void nle_done(int); +#ifndef SFCTOOL #ifndef NO_SIGNAL -STATIC_PTR void FDECL(done_intr, (int)); -#if defined(UNIX) || defined(VMS) || defined(__EMX__) -static void FDECL(done_hangup, (int)); -#endif +staticfn void done_intr(int); +# if defined(UNIX) || defined(VMS) || defined(__EMX__) +staticfn void done_hangup(int); +# endif #endif -STATIC_DCL void FDECL(disclose, (int, BOOLEAN_P)); -STATIC_DCL void FDECL(get_valuables, (struct obj *)); -STATIC_DCL void FDECL(sort_valuables, (struct valuable_data *, int)); -STATIC_DCL void NDECL(done_object_cleanup); -STATIC_DCL void FDECL(artifact_score, (struct obj *, BOOLEAN_P, winid)); -STATIC_DCL void FDECL(really_done, (int)) NORETURN; -STATIC_DCL void FDECL(savelife, (int)); -STATIC_PTR int FDECL(CFDECLSPEC vanqsort_cmp, (const genericptr, - const genericptr)); -STATIC_DCL int NDECL(set_vanq_order); -STATIC_DCL void FDECL(list_vanquished, (CHAR_P, BOOLEAN_P)); -STATIC_DCL void FDECL(list_genocided, (CHAR_P, BOOLEAN_P)); -STATIC_DCL boolean FDECL(should_query_disclose_option, (int, char *)); +staticfn void disclose(int, boolean); +staticfn void get_valuables(struct obj *) NO_NNARGS; +staticfn void sort_valuables(struct valuable_data *, int); +staticfn void artifact_score(struct obj *, boolean, winid); +staticfn boolean fuzzer_savelife(int); +ATTRNORETURN staticfn void really_done(int) NORETURN; +staticfn void savelife(int); +staticfn boolean should_query_disclose_option(int, char *); #ifdef DUMPLOG -STATIC_DCL void NDECL(dump_plines); -#endif -STATIC_DCL void FDECL(dump_everything, (int, time_t)); -STATIC_DCL int NDECL(num_extinct); - -#if defined(__BEOS__) || defined(MICRO) || defined(OS2) || defined(RL_GRAPHICS) -extern void FDECL(nethack_exit, (int)) NORETURN; -#else -#define nethack_exit exit +staticfn void dump_plines(void); #endif +staticfn void dump_everything(int, time_t); +staticfn void fixup_death(int); +#endif /* SFCTOOL */ +staticfn int wordcount(char *); +staticfn void bel_copy1(char **, char *); +#ifndef SFCTOOL #define done_stopprint program_state.stopprint -#ifndef PANICTRACE -#define NH_abort NH_abort_ -#endif - -#ifdef AMIGA -#define NH_abort_() Abort(0) -#else -#ifdef SYSV -#define NH_abort_() (void) abort() -#else -#ifdef WIN32 -#define NH_abort_() win32_abort() -#else -#define NH_abort_() abort() -#endif -#endif /* !SYSV */ -#endif /* !AMIGA */ - -#ifdef PANICTRACE -#include -#ifdef PANICTRACE_LIBC -#include -#endif - -/* What do we try and in what order? Tradeoffs: - * libc: +no external programs required - * -requires newish libc/glibc - * -requires -rdynamic - * gdb: +gives more detailed information - * +works on more OS versions - * -requires -g, which may preclude -O on some compilers - */ -#ifdef SYSCF -#define SYSOPT_PANICTRACE_GDB sysopt.panictrace_gdb -#ifdef PANICTRACE_LIBC -#define SYSOPT_PANICTRACE_LIBC sysopt.panictrace_libc -#else -#define SYSOPT_PANICTRACE_LIBC 0 -#endif -#else -#define SYSOPT_PANICTRACE_GDB (nh_getenv("NETHACK_USE_GDB") == 0 ? 0 : 2) -#ifdef PANICTRACE_LIBC -#define SYSOPT_PANICTRACE_LIBC 1 -#else -#define SYSOPT_PANICTRACE_LIBC 0 -#endif -#endif - -static void NDECL(NH_abort); -#ifndef NO_SIGNAL -static void FDECL(panictrace_handler, (int)); -#endif -static boolean NDECL(NH_panictrace_libc); -static boolean NDECL(NH_panictrace_gdb); - -#ifndef NO_SIGNAL -/* called as signal() handler, so sent at least one arg */ -/*ARGUSED*/ -void -panictrace_handler(sig_unused) -int sig_unused UNUSED; -{ -#define SIG_MSG "\nSignal received.\n" - int f2; - - f2 = (int) write(2, SIG_MSG, sizeof SIG_MSG - 1); - nhUse(f2); /* what could we do if write to fd#2 (stderr) fails */ - NH_abort(); /* ... and we're already in the process of quitting? */ -} - -void -panictrace_setsignals(set) -boolean set; -{ -#define SETSIGNAL(sig) \ - (void) signal(sig, set ? (SIG_RET_TYPE) panictrace_handler : SIG_DFL); -#ifdef SIGILL - SETSIGNAL(SIGILL); -#endif -#ifdef SIGTRAP - SETSIGNAL(SIGTRAP); -#endif -#ifdef SIGIOT - SETSIGNAL(SIGIOT); -#endif -#ifdef SIGBUS - SETSIGNAL(SIGBUS); -#endif -#ifdef SIGFPE - SETSIGNAL(SIGFPE); -#endif -#ifdef SIGSEGV - SETSIGNAL(SIGSEGV); -#endif -#ifdef SIGSTKFLT - SETSIGNAL(SIGSTKFLT); -#endif -#ifdef SIGSYS - SETSIGNAL(SIGSYS); -#endif -#ifdef SIGEMT - SETSIGNAL(SIGEMT); -#endif -#undef SETSIGNAL -} -#endif /* NO_SIGNAL */ - -static void -NH_abort() -{ - int gdb_prio = SYSOPT_PANICTRACE_GDB; - int libc_prio = SYSOPT_PANICTRACE_LIBC; - static boolean aborting = FALSE; - - if (aborting) - return; - aborting = TRUE; - -#ifndef VMS - if (gdb_prio == libc_prio && gdb_prio > 0) - gdb_prio++; - - if (gdb_prio > libc_prio) { - (void) (NH_panictrace_gdb() || (libc_prio && NH_panictrace_libc())); - } else { - (void) (NH_panictrace_libc() || (gdb_prio && NH_panictrace_gdb())); - } - -#else /* VMS */ - /* overload otherwise unused priority for debug mode: 1 = show - traceback and exit; 2 = show traceback and stay in debugger */ - /* if (wizard && gdb_prio == 1) gdb_prio = 2; */ - vms_traceback(gdb_prio); - nhUse(libc_prio); - -#endif /* ?VMS */ - -#ifndef NO_SIGNAL - panictrace_setsignals(FALSE); -#endif - NH_abort_(); -} - -static boolean -NH_panictrace_libc() -{ -#ifdef PANICTRACE_LIBC - void *bt[20]; - size_t count, x; - char **info; - - raw_print("Generating more information you may report:\n"); - count = backtrace(bt, SIZE(bt)); - info = backtrace_symbols(bt, count); - for (x = 0; x < count; x++) { - raw_printf("[%lu] %s", (unsigned long) x, info[x]); - } - /* free(info); -- Don't risk it. */ - return TRUE; -#else - return FALSE; -#endif /* !PANICTRACE_LIBC */ -} - -/* - * fooPATH file system path for foo - * fooVAR (possibly const) variable containing fooPATH - */ -#ifdef PANICTRACE_GDB -#ifdef SYSCF -#define GDBVAR sysopt.gdbpath -#define GREPVAR sysopt.greppath -#else /* SYSCF */ -#define GDBVAR GDBPATH -#define GREPVAR GREPPATH -#endif /* SYSCF */ -#endif /* PANICTRACE_GDB */ - -static boolean -NH_panictrace_gdb() -{ -#ifdef PANICTRACE_GDB - /* A (more) generic method to get a stack trace - invoke - * gdb on ourself. */ - const char *gdbpath = GDBVAR; - const char *greppath = GREPVAR; - char buf[BUFSZ]; - FILE *gdb; - - if (gdbpath == NULL || gdbpath[0] == 0) - return FALSE; - if (greppath == NULL || greppath[0] == 0) - return FALSE; - - sprintf(buf, "%s -n -q %s %d 2>&1 | %s '^#'", - gdbpath, ARGV0, getpid(), greppath); - gdb = popen(buf, "w"); - if (gdb) { - raw_print("Generating more information you may report:\n"); - fprintf(gdb, "bt\nquit\ny"); - fflush(gdb); - sleep(4); /* ugly */ - pclose(gdb); - return TRUE; - } else { - return FALSE; - } -#else - return FALSE; -#endif /* !PANICTRACE_GDB */ -} -#endif /* PANICTRACE */ - /* * The order of these needs to match the macros in hack.h. */ @@ -312,14 +64,15 @@ static NEARDATA const char *ends[] = { static boolean Schroedingers_cat = FALSE; +/* called as signal() handler, so sent at least one arg */ /*ARGSUSED*/ void -done1(sig_unused) /* called as signal() handler, so sent at least one arg */ -int sig_unused UNUSED; +done1(int sig_unused UNUSED) { #ifndef NO_SIGNAL (void) signal(SIGINT, SIG_IGN); #endif + iflags.debug_fuzzer = fuzzer_off; if (flags.ignintr) { #ifndef NO_SIGNAL (void) signal(SIGINT, (SIG_RET_TYPE) done1); @@ -327,7 +80,7 @@ int sig_unused UNUSED; clear_nhwindow(WIN_MESSAGE); curs_on_u(); wait_synch(); - if (multi > 0) + if (gm.multi > 0) nomul(0); } else { (void) done2(); @@ -336,25 +89,38 @@ int sig_unused UNUSED; /* "#quit" command or keyboard interrupt */ int -done2() +done2(void) { - if (iflags.debug_fuzzer) - return 0; - if (!paranoid_query(ParanoidQuit, "Really quit?")) { + boolean abandon_tutorial = FALSE; + + if (In_tutorial(&u.uz) + && y_n("Switch from the tutorial back to regular play?") == 'y') + abandon_tutorial = TRUE; + + if (abandon_tutorial || !paranoid_query( + ParanoidQuit, "Really quit without saving?")) { #ifndef NO_SIGNAL (void) signal(SIGINT, (SIG_RET_TYPE) done1); #endif clear_nhwindow(WIN_MESSAGE); curs_on_u(); wait_synch(); - if (multi > 0) + if (gm.multi > 0) nomul(0); - if (multi == 0) { + if (gm.multi == 0) { u.uinvulnerable = FALSE; /* avoid ctrl-C bug -dlc */ u.usleep = 0; } - return 0; + + if (abandon_tutorial) { + /* mention_decor can be processed now */ + rcfile_only_this_option(opt_mention_decor); + schedule_goto(&u.ucamefrom, UTOTYPE_ATSTAIRS, + "Resuming regular play.", (char *) 0); + } + return ECMD_OK; } + #if (defined(UNIX) || defined(VMS) || defined(LATTICE)) if (wizard) { int c; @@ -373,56 +139,60 @@ done2() #ifndef NO_SIGNAL (void) signal(SIGINT, (SIG_RET_TYPE) done1); #endif + if (soundprocs.sound_exit_nhsound) + (*soundprocs.sound_exit_nhsound)("done2"); + exit_nhwindows((char *) 0); - NH_abort(); + NH_abort(NULL); } else if (c == 'q') done_stopprint++; } #endif -#ifndef LINT done(QUIT); -#endif - return 0; + return ECMD_OK; } #ifndef NO_SIGNAL +/* called as signal() handler, so sent at least 1 arg */ /*ARGSUSED*/ -STATIC_PTR void -done_intr(sig_unused) /* called as signal() handler, so sent at least 1 arg */ -int sig_unused UNUSED; +staticfn void +done_intr(int sig_unused UNUSED) { done_stopprint++; (void) signal(SIGINT, SIG_IGN); #if defined(UNIX) || defined(VMS) +#ifndef VMSVSI (void) signal(SIGQUIT, SIG_IGN); +#endif #endif return; } #if defined(UNIX) || defined(VMS) || defined(__EMX__) /* signal() handler */ -static void -done_hangup(sig) -int sig; +staticfn void +done_hangup(int sig) { +#ifdef HANGUPHANDLING program_state.done_hup++; - sethanguphandler((void FDECL((*), (int) )) SIG_IGN); +#endif + sethanguphandler((void (*)(int)) SIG_IGN); done_intr(sig); return; } #endif #endif /* NO_SIGNAL */ +DISABLE_WARNING_FORMAT_NONLITERAL /* one compiler warns if the format + string is the result of a ? x : y */ + void -done_in_by(mtmp, how) -struct monst *mtmp; -int how; +done_in_by(struct monst *mtmp, int how) { char buf[BUFSZ]; struct permonst *mptr = mtmp->data, - *champtr = ((mtmp->cham >= LOW_PM) - ? &mons[mtmp->cham] - : mptr); + *champtr = ismnum(mtmp->cham) ? &mons[mtmp->cham] + : mptr; boolean distorted = (boolean) (Hallucination && canspotmon(mtmp)), mimicker = (M_AP_TYPE(mtmp) == M_AP_MONSTER), imitator = (mptr != champtr || mimicker); @@ -430,20 +200,26 @@ int how; You((how == STONING) ? "turn to stone..." : "die..."); mark_synch(); /* flush buffered screen output */ buf[0] = '\0'; - killer.format = KILLED_BY_AN; + svk.killer.format = KILLED_BY_AN; /* "killed by the high priest of Crom" is okay, "killed by the high priest" alone isn't */ if ((mptr->geno & G_UNIQ) != 0 && !(imitator && !mimicker) - && !(mptr == &mons[PM_HIGH_PRIEST] && !mtmp->ispriest)) { + && !(mptr == &mons[PM_HIGH_CLERIC] && !mtmp->ispriest)) { if (!type_is_pname(mptr)) Strcat(buf, "the "); - killer.format = KILLED_BY; + svk.killer.format = KILLED_BY; } /* _the_ ghost of Dudley */ - if (mptr == &mons[PM_GHOST] && has_mname(mtmp)) { +#if 0 + /* hardfought */ + if (has_ebones(mtmp)) { +#else + if (mptr == &mons[PM_GHOST] && has_mgivenname(mtmp)) { +#endif Strcat(buf, "the "); - killer.format = KILLED_BY; + svk.killer.format = KILLED_BY; } + (void) monhealthdescr(mtmp, TRUE, eos(buf)); if (mtmp->minvis) Strcat(buf, "invisible "); if (distorted) @@ -451,14 +227,15 @@ int how; if (imitator) { char shape[BUFSZ]; - const char *realnm = champtr->mname, *fakenm = mptr->mname; + const char *realnm = pmname(champtr, Mgender(mtmp)), + *fakenm = pmname(mptr, Mgender(mtmp)); boolean alt = is_vampshifter(mtmp); if (mimicker) { /* realnm is already correct because champtr==mptr; set up fake mptr for type_is_pname/the_unique_pm */ mptr = &mons[mtmp->mappearance]; - fakenm = mptr->mname; + fakenm = pmname(mptr, Mgender(mtmp)); } else if (alt && strstri(realnm, "vampire") && !strcmp(fakenm, "vampire bat")) { /* special case: use "vampire in bat form" in preference @@ -481,28 +258,67 @@ int how; : "%s imitating %s", realnm, shape); mptr = mtmp->data; /* reset for mimicker case */ +#if 0 /* hardfought */ + } else if (has_ebones(mtmp)) { + Strcpy(buf, m_monnam(mtmp)); +#endif } else if (mptr == &mons[PM_GHOST]) { Strcat(buf, "ghost"); - if (has_mname(mtmp)) - Sprintf(eos(buf), " of %s", MNAME(mtmp)); + if (has_mgivenname(mtmp)) + Sprintf(eos(buf), " of %s", MGIVENNAME(mtmp)); } else if (mtmp->isshk) { const char *shknm = shkname(mtmp), *honorific = shkname_is_pname(mtmp) ? "" : mtmp->female ? "Ms. " : "Mr. "; Sprintf(eos(buf), "%s%s, the shopkeeper", honorific, shknm); - killer.format = KILLED_BY; + svk.killer.format = KILLED_BY; } else if (mtmp->ispriest || mtmp->isminion) { /* m_monnam() suppresses "the" prefix plus "invisible", and it overrides the effect of Hallucination on priestname() */ Strcat(buf, m_monnam(mtmp)); } else { - Strcat(buf, mptr->mname); - if (has_mname(mtmp)) - Sprintf(eos(buf), " called %s", MNAME(mtmp)); + Strcat(buf, pmname(mptr, Mgender(mtmp))); + if (has_mgivenname(mtmp)) { + Sprintf(eos(buf), " %s %s", + has_ebones(mtmp) ? "of" : "called", + MGIVENNAME(mtmp)); + } + } + + Strcpy(svk.killer.name, buf); + + /* might need to fix up multi_reason if 'mtmp' caused the reason */ + if (gm.multi_reason + && gm.multi_reason > gm.multireasonbuf + && gm.multi_reason + < gm.multireasonbuf + sizeof gm.multireasonbuf - 1) { + char reasondummy, *p; + unsigned reasonmid = 0; + + /* + * multireasonbuf[] contains 'm_id:reason' and multi_reason + * points at the text past the colon, so we have something + * like "42:paralyzed by a ghoul"; if mtmp->m_id matches 42 + * then we truncate 'reason' at its first space so that final + * death reason becomes "Killed by a ghoul, while paralyzed." + * instead of "Killed by a ghoul, while paralyzed by a ghoul." + * (3.6.x gave "Killed by a ghoul, while paralyzed by a monster." + * which is potentially misleading when the monster is also + * the killer.) + * + * Note that if the hero is life-saved and then killed again + * before the helplessness has cleared, the second death will + * report the truncated helplessness reason even if some other + * monster performs the /coup de grace/. + */ + if (sscanf(gm.multireasonbuf, "%u:%c", &reasonmid, &reasondummy) == 2 + && mtmp->m_id == reasonmid) { + if ((p = strchr(gm.multireasonbuf, ' ')) != 0) + *p = '\0'; + } } - Strcpy(killer.name, buf); /* * Chicken and egg issue: * Ordinarily Unchanging ought to override something like this, @@ -514,8 +330,10 @@ int how; */ if (mptr->mlet == S_WRAITH) u.ugrave_arise = PM_WRAITH; - else if (mptr->mlet == S_MUMMY && urace.mummynum != NON_PM) - u.ugrave_arise = urace.mummynum; + else if (mptr->mlet == S_MUMMY && gu.urace.mummynum != NON_PM) + u.ugrave_arise = gu.urace.mummynum; + else if (zombie_maker(mtmp) && gu.urace.zombienum != NON_PM) + u.ugrave_arise = gu.urace.zombienum; else if (mptr->mlet == S_VAMPIRE && Race_if(PM_HUMAN)) u.ugrave_arise = PM_VAMPIRE; else if (mptr == &mons[PM_GHOUL]) @@ -523,13 +341,15 @@ int how; /* this could happen if a high-end vampire kills the hero when ordinary vampires are genocided; ditto for wraiths */ if (u.ugrave_arise >= LOW_PM - && (mvitals[u.ugrave_arise].mvflags & G_GENOD)) + && (svm.mvitals[u.ugrave_arise].mvflags & G_GENOD)) u.ugrave_arise = NON_PM; done(how); return; } +RESTORE_WARNING_FORMAT_NONLITERAL + /* some special cases for overriding while-helpless reason */ static const struct { int why, unmulti; @@ -547,22 +367,22 @@ static const struct { /* clear away while-helpless when the cause of death caused that helplessness (ie, "petrified by while getting stoned") */ -STATIC_DCL void -fixup_death(how) -int how; +staticfn void +fixup_death(int how) { int i; - if (multi_reason) { + if (gm.multi_reason) { for (i = 0; i < SIZE(death_fixups); ++i) if (death_fixups[i].why == how - && !strcmp(death_fixups[i].exclude, multi_reason)) { + && !strcmp(death_fixups[i].exclude, gm.multi_reason)) { if (death_fixups[i].include) /* substitute alternate reason */ - multi_reason = death_fixups[i].include; + gm.multi_reason = death_fixups[i].include; else /* remove the helplessness reason */ - multi_reason = (char *) 0; + gm.multi_reason = (char *) 0; + gm.multireasonbuf[0] = '\0'; /* dynamic buf stale either way */ if (death_fixups[i].unmulti) /* possibly hide helplessness */ - multi = 0L; + gm.multi = 0L; break; } } @@ -572,21 +392,27 @@ int how; #define NOTIFY_NETHACK_BUGS #endif +DISABLE_WARNING_FORMAT_NONLITERAL + /*VARARGS1*/ -void panic -VA_DECL(const char *, str) +ATTRNORETURN void +panic VA_DECL(const char *, str) { + char buf[BUFSZ]; VA_START(str); VA_INIT(str, char *); if (program_state.panicking++) - NH_abort(); /* avoid loops - this should never happen*/ + NH_abort(NULL); /* avoid loops - this should never happen*/ + gb.bot_disabled = TRUE; if (iflags.window_inited) { raw_print("\r\nOops..."); wait_synch(); /* make sure all pending output gets flushed */ + if (soundprocs.sound_exit_nhsound) + (*soundprocs.sound_exit_nhsound)("panic"); exit_nhwindows((char *) 0); - iflags.window_inited = 0; /* they're gone; force raw_print()ing */ + iflags.window_inited = FALSE; /* they're gone; force raw_print()ing */ } raw_print(program_state.gameover @@ -607,6 +433,7 @@ VA_DECL(const char *, str) ? "." : "\nand it may be possible to rebuild."; +// XXX this may need an update if defined(CRASHREPORT) TBD if (sysopt.support) raw_printf("To report this error, %s%s", sysopt.support, maybe_rebuild); @@ -630,38 +457,35 @@ VA_DECL(const char *, str) } } #endif /* !MICRO */ - { - char buf[BUFSZ]; -#if !defined(NO_VSNPRINTF) - (void) vsnprintf(buf, sizeof buf, str, VA_ARGS); -#else - Vsprintf(buf, str, VA_ARGS); -#endif - raw_print(buf); - paniclog("panic", buf); - } + (void) vsnprintf(buf, sizeof buf, str, VA_ARGS); + raw_print(buf); + paniclog("panic", buf); + #ifdef WIN32 interject(INTERJECT_PANIC); #endif #if defined(UNIX) || defined(VMS) || defined(LATTICE) || defined(WIN32) +# ifndef CRASHREPORT if (wizard) - NH_abort(); /* generate core dump */ +# endif + NH_abort(buf); /* generate core dump */ #endif VA_END(); really_done(PANICKED); } -STATIC_OVL boolean -should_query_disclose_option(category, defquery) -int category; -char *defquery; +RESTORE_WARNING_FORMAT_NONLITERAL + +staticfn boolean +should_query_disclose_option(int category, char *defquery) { int idx; - char disclose, *dop; + char disclose; + const char *dop; *defquery = 'n'; - if ((dop = index(disclosure_options, category)) != 0) { + if ((dop = strchr(disclosure_options, category)) != 0) { idx = (int) (dop - disclosure_options); if (idx < 0 || idx >= NUM_DISCLOSURE_OPTIONS) { impossible( @@ -696,19 +520,17 @@ char *defquery; } #ifdef DUMPLOG -STATIC_OVL void -dump_plines() +staticfn void +dump_plines(void) { int i, j; char buf[BUFSZ], **strp; - extern char *saved_plines[]; - extern unsigned saved_pline_index; Strcpy(buf, " "); /* one space for indentation */ putstr(0, 0, "Latest messages:"); - for (i = 0, j = (int) saved_pline_index; i < DUMPLOG_MSG_COUNT; + for (i = 0, j = (int) gs.saved_pline_index; i < DUMPLOG_MSG_COUNT; ++i, j = (j + 1) % DUMPLOG_MSG_COUNT) { - strp = &saved_plines[j]; + strp = &gs.saved_plines[j]; if (*strp) { copynchars(&buf[1], *strp, BUFSZ - 1 - 1); putstr(0, 0, buf); @@ -718,13 +540,13 @@ dump_plines() } } } -#endif +#endif /* DUMPLOG */ /*ARGSUSED*/ -STATIC_OVL void -dump_everything(how, when) -int how; -time_t when; /* date+time at end of game */ +staticfn void +dump_everything( + int how, /* ASCENDED, ESCAPED, QUIT, etc */ + time_t when) /* date+time at end of game */ { #ifdef DUMPLOG char pbuf[BUFSZ], datetimebuf[24]; /* [24]: room for 64-bit bogus value */ @@ -739,7 +561,7 @@ time_t when; /* date+time at end of game */ it's conceivable that the game started with a different build date+time or even with an older nethack version, but we only have access to the one it finished under */ - putstr(0, 0, getversionstring(pbuf)); + putstr(0, 0, getversionstring(pbuf, sizeof pbuf)); putstr(0, 0, ""); /* game start and end date+time to disambiguate version date+time */ @@ -755,14 +577,15 @@ time_t when; /* date+time at end of game */ putstr(0, 0, ""); /* character name and basic role info */ - Sprintf(pbuf, "%s, %s %s %s %s", plname, - aligns[1 - u.ualign.type].adj, - genders[flags.female].adj, - urace.adj, - (flags.female && urole.name.f) ? urole.name.f : urole.name.m); + Sprintf(pbuf, "%s, %s %s %s %s", + svp.plname, aligns[1 - u.ualign.type].adj, + genders[flags.female].adj, gu.urace.adj, + (flags.female && gu.urole.name.f) ? gu.urole.name.f + : gu.urole.name.m); putstr(0, 0, pbuf); putstr(0, 0, ""); + /* info about current game state */ dump_map(); putstr(0, 0, do_statusline1()); putstr(0, 0, do_statusline2()); @@ -772,18 +595,24 @@ time_t when; /* date+time at end of game */ putstr(0, 0, ""); putstr(0, 0, "Inventory:"); (void) display_inventory((char *) 0, TRUE); - container_contents(invent, TRUE, TRUE, FALSE); + container_contents(gi.invent, TRUE, TRUE, FALSE); enlightenment((BASICENLIGHTENMENT | MAGICENLIGHTENMENT), (how >= PANICKED) ? ENL_GAMEOVERALIVE : ENL_GAMEOVERDEAD); putstr(0, 0, ""); - list_vanquished('d', FALSE); /* 'd' => 'y' */ - putstr(0, 0, ""); - list_genocided('d', FALSE); /* 'd' => 'y' */ + + /* overview of the game up to this point */ + show_gamelog((how >= PANICKED) ? ENL_GAMEOVERALIVE : ENL_GAMEOVERDEAD); putstr(0, 0, ""); + show_spells(); /* ends with a blank line */ + show_skills(); /* ends with a blank line */ show_conduct((how >= PANICKED) ? 1 : 2); putstr(0, 0, ""); show_overview((how >= PANICKED) ? 1 : 2, how); putstr(0, 0, ""); + list_vanquished('d', FALSE); /* 'd' => 'y' */ + putstr(0, 0, ""); + list_genocided('d', FALSE); /* 'd' => 'y' */ + putstr(0, 0, ""); dump_redirect(FALSE); #else nhUse(how); @@ -791,16 +620,14 @@ time_t when; /* date+time at end of game */ #endif } -STATIC_OVL void -disclose(how, taken) -int how; -boolean taken; +staticfn void +disclose(int how, boolean taken) { char c = '\0', defquery; char qbuf[QBUFSZ]; boolean ask = FALSE; - if (invent && !done_stopprint) { + if (gi.invent && !done_stopprint) { if (taken) Sprintf(qbuf, "Do you want to see what you had when you %s?", (how == QUIT) ? "quit" : "died"); @@ -808,11 +635,13 @@ boolean taken; Strcpy(qbuf, "Do you want your possessions identified?"); ask = should_query_disclose_option('i', &defquery); - c = ask ? yn_function(qbuf, ynqchars, defquery) : defquery; + c = ask ? yn_function(qbuf, ynqchars, defquery, TRUE) : defquery; if (c == 'y') { - /* caller has already ID'd everything */ + /* caller has already ID'd everything; we pass 'want_reply=True' + to force display_pickinv() to avoid using WIN_INVENT */ + iflags.force_invmenu = FALSE; (void) display_inventory((char *) 0, TRUE); - container_contents(invent, TRUE, TRUE, FALSE); + container_contents(gi.invent, TRUE, TRUE, FALSE); } if (c == 'q') done_stopprint++; @@ -821,7 +650,7 @@ boolean taken; if (!done_stopprint) { ask = should_query_disclose_option('a', &defquery); c = ask ? yn_function("Do you want to see your attributes?", ynqchars, - defquery) + defquery, TRUE) : defquery; if (c == 'y') enlightenment((BASICENLIGHTENMENT | MAGICENLIGHTENMENT), @@ -842,10 +671,21 @@ boolean taken; } if (!done_stopprint) { - ask = should_query_disclose_option('c', &defquery); - c = ask ? yn_function("Do you want to see your conduct?", ynqchars, - defquery) - : defquery; + if (should_query_disclose_option('c', &defquery)) { + int acnt = count_achievements(); + + Sprintf(qbuf, "Do you want to see your conduct%s?", + /* this was distinguishing between one achievement and + multiple achievements, but "conduct and achievement" + looked strange if multiple conducts got shown (which + is usual for an early game death); we could switch + to plural vs singular for conducts but the less + specific "conduct and achievements" is sufficient */ + (acnt > 0) ? " and achievements" : ""); + c = yn_function(qbuf, ynqchars, defquery, TRUE); + } else { + c = defquery; + } if (c == 'y') show_conduct((how >= PANICKED) ? 1 : 2); if (c == 'q') @@ -855,7 +695,7 @@ boolean taken; if (!done_stopprint) { ask = should_query_disclose_option('o', &defquery); c = ask ? yn_function("Do you want to see the dungeon overview?", - ynqchars, defquery) + ynqchars, defquery, TRUE) : defquery; if (c == 'y') show_overview((how >= PANICKED) ? 1 : 2, how); @@ -865,43 +705,54 @@ boolean taken; } /* try to get the player back in a viable state after being killed */ -STATIC_OVL void -savelife(how) -int how; +staticfn void +savelife(int how) { - int uhpmin = max(2 * u.ulevel, 10); - + int uhpmin; + int givehp = 50 + 10 * (ACURR(A_CON) / 2); + + /* life-drain/level-loss to experience level 0 kills without actually + reducing ulevel below 1, but include this for bulletproofing */ + if (u.ulevel < 1) + u.ulevel = 1; + uhpmin = minuhpmax(10); if (u.uhpmax < uhpmin) - u.uhpmax = uhpmin; - u.uhp = u.uhpmax; + setuhpmax(uhpmin, TRUE); + u.uhp = min(u.uhpmax, givehp); if (Upolyd) /* Unchanging, or death which bypasses losing hit points */ - u.mh = u.mhmax; + u.mh = min(u.mhmax, givehp); if (u.uhunger < 500 || how == CHOKING) { init_uhunger(); } - /* cure impending doom of sickness hero won't have time to fix */ + /* cure impending doom of sickness hero won't have time to fix + [shouldn't this also be applied to other fatal timeouts?] */ if ((Sick & TIMEOUT) == 1L) { make_sick(0L, (char *) 0, FALSE, SICK_ALL); } - nomovemsg = "You survived that attempt on your life."; - context.move = 0; - if (multi > 0) - multi = 0; - else - multi = -1; + gn.nomovemsg = "You survived that attempt on your life."; + svc.context.move = 0; + + gm.multi = -1; /* can't move again during the current turn */ + /* in case being life-saved is immediately followed by being killed + again (perhaps due to zap rebound); this text will be appended to + "killed by , while " + in high scores entry, if any, and in logfile (but not on tombstone) */ + gm.multi_reason = Role_if(PM_TOURIST) ? "being toyed with by Fate" + : "attempting to cheat Death"; + if (u.utrap && u.utraptype == TT_LAVA) reset_utrap(FALSE); - context.botl = 1; + disp.botl = TRUE; u.ugrave_arise = NON_PM; HUnchanging = 0L; curs_on_u(); - if (!context.mon_moving) + if (!svc.context.mon_moving) endmultishot(FALSE); if (u.uswallow) { /* might drop hero onto a trap that kills her all over again */ expels(u.ustuck, u.ustuck->data, TRUE); } else if (u.ustuck) { - if (Upolyd && sticks(youmonst.data)) + if (Upolyd && sticks(gy.youmonst.data)) You("release %s.", mon_nam(u.ustuck)); else pline("%s releases you.", Monnam(u.ustuck)); @@ -913,12 +764,11 @@ int how; * Get valuables from the given list. Revised code: the list always remains * intact. */ -STATIC_OVL void -get_valuables(list) -struct obj *list; /* inventory or container contents */ +staticfn void +get_valuables(struct obj *list) /* inventory or container contents */ { - register struct obj *obj; - register int i; + struct obj *obj; + int i; /* find amulets and gems, ignoring all artifacts */ for (obj = list; obj; obj = obj->nobj) @@ -928,18 +778,19 @@ struct obj *list; /* inventory or container contents */ continue; } else if (obj->oclass == AMULET_CLASS) { i = obj->otyp - FIRST_AMULET; - if (!amulets[i].count) { - amulets[i].count = obj->quan; - amulets[i].typ = obj->otyp; + if (!ga.amulets[i].count) { + ga.amulets[i].count = obj->quan; + ga.amulets[i].typ = obj->otyp; } else - amulets[i].count += obj->quan; /* always adds one */ - } else if (obj->oclass == GEM_CLASS && obj->otyp < LUCKSTONE) { - i = min(obj->otyp, LAST_GEM + 1) - FIRST_GEM; - if (!gems[i].count) { - gems[i].count = obj->quan; - gems[i].typ = obj->otyp; + ga.amulets[i].count += obj->quan; /* always adds one */ + } else if (obj->oclass == GEM_CLASS && obj->otyp <= LAST_GLASS_GEM) { + /* last+1: combine all glass gems into one slot */ + i = min(obj->otyp, LAST_REAL_GEM + 1) - FIRST_REAL_GEM; + if (!gg.gems[i].count) { + gg.gems[i].count = obj->quan; + gg.gems[i].typ = obj->otyp; } else - gems[i].count += obj->quan; + gg.gems[i].count += obj->quan; } return; } @@ -948,12 +799,12 @@ struct obj *list; /* inventory or container contents */ * Sort collected valuables, most frequent to least. We could just * as easily use qsort, but we don't care about efficiency here. */ -STATIC_OVL void -sort_valuables(list, size) -struct valuable_data list[]; -int size; /* max value is less than 20 */ +staticfn void +sort_valuables( + struct valuable_data list[], + int size) /* max value is less than 20 */ { - register int i, j; + int i, j; struct valuable_data ltmp; /* move greater quantities to the front of the list */ @@ -961,12 +812,11 @@ int size; /* max value is less than 20 */ if (list[i].count == 0) continue; /* empty slot */ ltmp = list[i]; /* structure copy */ - for (j = i; j > 0; --j) + for (j = i; j > 0; --j) { if (list[j - 1].count >= ltmp.count) break; - else { - list[j] = list[j - 1]; - } + list[j] = list[j - 1]; + } list[j] = ltmp; } return; @@ -977,14 +827,12 @@ int size; /* max value is less than 20 */ * odds_and_ends() was used for 3.6.0 and 3.6.1. * Schroedinger's Cat is handled differently as of 3.6.2. */ -STATIC_DCL boolean FDECL(odds_and_ends, (struct obj *, int)); +staticfn boolean odds_and_ends(struct obj *, int); #define CAT_CHECK 2 -STATIC_OVL boolean -odds_and_ends(list, what) -struct obj *list; -int what; +staticfn boolean +odds_and_ends(struct obj *list, int what) { struct obj *otmp; @@ -1004,8 +852,8 @@ int what; #endif /* deal with some objects which may be in an abnormal state at end of game */ -STATIC_OVL void -done_object_cleanup() +void +done_object_cleanup(void) { int ox, oy; @@ -1020,7 +868,7 @@ done_object_cleanup() * any inventory. Saving bones with an active light source in limbo * would trigger an 'object not local' panic. * - * We used to use dealloc_obj() on thrownobj and kickedobj but + * We used to use dealloc_obj() on gt.thrownobj and gk.kickedobj but * that keeps them out of bones and could leave uball in a confused * state (gone but still attached). Place them on the map but * bypass flooreffects(). That could lead to minor anomalies in @@ -1035,13 +883,13 @@ done_object_cleanup() be incorrect (perhaps killed by divine lightning when throwing at a temple priest?) but this should be better than just vanishing (fragile stuff should be taken care of before getting here) */ - if (thrownobj && thrownobj->where == OBJ_FREE) { - place_object(thrownobj, ox, oy); - stackobj(thrownobj), thrownobj = 0; + if (gt.thrownobj && gt.thrownobj->where == OBJ_FREE) { + place_object(gt.thrownobj, ox, oy); + stackobj(gt.thrownobj), gt.thrownobj = 0; } - if (kickedobj && kickedobj->where == OBJ_FREE) { - place_object(kickedobj, ox, oy); - stackobj(kickedobj), kickedobj = 0; + if (gk.kickedobj && gk.kickedobj->where == OBJ_FREE) { + place_object(gk.kickedobj, ox, oy); + stackobj(gk.kickedobj), gk.kickedobj = 0; } /* if Punished hero dies during level change or dies or quits while swallowed, uball and uchain will be in limbo; put them on floor @@ -1054,22 +902,21 @@ done_object_cleanup() a normal popup one; avoids "Bad fruit #n" when saving bones */ if (iflags.perm_invent) { iflags.perm_invent = FALSE; - update_inventory(); /* make interface notice the change */ + perm_invent_toggled(TRUE); /* make interface notice the change */ } return; } /* called twice; first to calculate total, then to list relevant items */ -STATIC_OVL void -artifact_score(list, counting, endwin) -struct obj *list; -boolean counting; /* true => add up points; false => display them */ -winid endwin; +staticfn void +artifact_score( + struct obj *list, + boolean counting, /* true => add up points; false => display them */ + winid endwin) { char pbuf[BUFSZ]; struct obj *otmp; long value, points; - short dummy; /* object type returned by artifact_name() */ for (otmp = list; otmp; otmp = otmp->nobj) { if (otmp->oartifact || otmp->otyp == BELL_OF_OPENING @@ -1078,14 +925,15 @@ winid endwin; value = arti_cost(otmp); /* zorkmid value */ points = value * 5 / 2; /* score value */ if (counting) { - nowrap_add(u.urexp, points); + u.urexp = nowrap_add(u.urexp, points); } else { - discover_object(otmp->otyp, TRUE, FALSE); + discover_object(otmp->otyp, TRUE, TRUE, FALSE); + /* not observe_object; dead characters don't observe */ otmp->known = otmp->dknown = otmp->bknown = otmp->rknown = 1; /* assumes artifacts don't have quan > 1 */ Sprintf(pbuf, "%s%s (worth %ld %s and %ld points)", the_unique_obj(otmp) ? "The " : "", - otmp->oartifact ? artifact_name(xname(otmp), &dummy) + otmp->oartifact ? artiname(otmp->oartifact) : OBJ_NAME(objects[otmp->otyp]), value, currency(value), points); putstr(endwin, 0, pbuf); @@ -1096,21 +944,96 @@ winid endwin; } } +/* when dying while running the debug fuzzer, [almost] always keep going; + True: forced survival; False: doomed unless wearing life-save amulet */ +staticfn boolean +fuzzer_savelife(int how) +{ + /* + * Some debugging code pulled out of done() to unclutter it. + * 'done_seq' is maintained in done(). + */ + if (!program_state.panicking && how != PANICKED && how != TRICKED) { + savelife(how); + + /* periodically restore characteristics plus lost experience + levels or cure lycanthropy or both; those conditions make the + hero vulnerable to repeat deaths (often by becoming surrounded + while being too encumbered to do anything) */ + if (!rn2((gd.done_seq > gh.hero_seq + 2L) ? 2 : 10)) { + struct obj *potion; + int propidx, proptim, remedies = 0; + + /* get rid of temporary potion with obfree() rather than useup() + because it doesn't get entered into inventory */ + if (ismnum(u.ulycn) && !rn2(3)) { + potion = mksobj(POT_WATER, TRUE, FALSE); + bless(potion); + (void) peffects(potion); + obfree(potion, (struct obj *) 0); + ++remedies; + } + if (!remedies || rn2(3)) { + potion = mksobj(POT_RESTORE_ABILITY, TRUE, FALSE); + bless(potion); + (void) peffects(potion); + obfree(potion, (struct obj *) 0); + ++remedies; + } + if (!rn2(3 + 3 * remedies)) { + /* confer temporary resistances for first 8 properties: + fire, cold, sleep, disint, shock, poison, acid, stone */ + for (propidx = 1; propidx <= 8; ++propidx) { + if (!u.uprops[propidx].intrinsic + && !u.uprops[propidx].extrinsic + && (proptim = rn2(3)) > 0) /* 0..2 */ + set_itimeout(&u.uprops[propidx].intrinsic, + (long) (2 * proptim + 1)); /* 3 or 5 */ + } + ++remedies; + } + if (!rn2(5 + 5 * remedies)) { + ; /* might confer temporary Antimagic (magic resistance) + * or even Invulnerable */ + } + } + /* clear stale cause of death info after life-saving */ + svk.killer.name[0] = '\0'; + svk.killer.format = 0; + + /* + * Guard against getting stuck in a loop if we die in one of + * the few ways where life-saving isn't effective (cited case + * was burning in lava when the level was too full to allow + * teleporting to safety). Deal with it by recreating the level + * if we're in wizmode (always the case for debug_fuzzer unless + * player has used a debugger to fiddle with 'iflags' bits). + */ + if (gd.done_seq++ > gh.hero_seq + 100L) { + if (!wizard) + return FALSE; /* can't deal with it */ + cmdq_add_ec(CQ_CANNED, wiz_makemap); + } + + return TRUE; + } + return FALSE; /* panic or too many consecutive deaths */ +} + /* Be careful not to call panic from here! */ void -done(how) -int how; +done(int how) { boolean survive = FALSE; if (how == TRICKED) { - if (killer.name[0]) { - paniclog("trickery", killer.name); - killer.name[0] = '\0'; + if (svk.killer.name[0]) { + paniclog("trickery", svk.killer.name); + svk.killer.name[0] = '\0'; } if (wizard) { You("are a very tricky wizard, it seems."); - killer.format = KILLED_BY_AN; /* reset to 0 */ + svk.killer.format = KILLED_BY_AN; /* reset to 0 */ return; } } @@ -1118,42 +1041,35 @@ int how; #ifdef HANGUPHANDLING || program_state.done_hup #endif - ) { - /* skip status update if panicking or disconnected */ - context.botl = context.botlx = iflags.time_botl = FALSE; + || (how == QUIT && done_stopprint)) { + /* skip status update if panicking or disconnected + or answer of 'q' to "Really quit?" */ + disp.botl = disp.botlx = disp.time_botl = FALSE; } else { /* otherwise force full status update */ - context.botlx = TRUE; + disp.botlx = TRUE; bot(); } - if (iflags.debug_fuzzer) { - if (!(program_state.panicking || how == PANICKED)) { - savelife(how); - /* periodically restore characteristics and lost exp levels - or cure lycanthropy */ - if (!rn2(10)) { - struct obj *potion = mksobj((u.ulycn > LOW_PM && !rn2(3)) - ? POT_WATER : POT_RESTORE_ABILITY, - TRUE, FALSE); + /* hero_seq is (moves<<3 + n) where n is number of moves made + by the hero on the current turn (since the 'moves' variable + actually counts turns); its details shouldn't matter here; + used by fuzzer_savelife() and for hangup below */ + if (gd.done_seq < gh.hero_seq) + gd.done_seq = gh.hero_seq; - bless(potion); - (void) peffects(potion); /* always -1 for restore ability */ - /* not useup(); we haven't put this potion into inventory */ - obfree(potion, (struct obj *) 0); - } - killer.name[0] = '\0'; - killer.format = 0; + if (iflags.debug_fuzzer) { + if (fuzzer_savelife(how)) return; - } - } else - if (how == ASCENDED || (!killer.name[0] && how == GENOCIDED)) - killer.format = NO_KILLER_PREFIX; + } + + if (how == ASCENDED || (!svk.killer.name[0] && how == GENOCIDED)) + svk.killer.format = NO_KILLER_PREFIX; /* Avoid killed by "a" burning or "a" starvation */ - if (!killer.name[0] && (how == STARVING || how == BURNING)) - killer.format = KILLED_BY; - if (!killer.name[0] || how >= PANICKED) - Strcpy(killer.name, deaths[how]); + if (!svk.killer.name[0] && (how == STARVING || how == BURNING)) + svk.killer.format = KILLED_BY; + if (!svk.killer.name[0] || how >= PANICKED) + Strcpy(svk.killer.name, deaths[how]); if (how < PANICKED) { u.umortality++; @@ -1164,11 +1080,12 @@ int how; negative (-1 is used as a flag in some circumstances which don't apply when actually dying due to HP loss) */ u.uhp = u.mh = 0; - context.botl = 1; + disp.botl = TRUE; } } if (Lifesaved && (how <= GENOCIDED)) { pline("But wait..."); + /* assumes that only one type of item confers LifeSaved property */ makeknown(AMULET_OF_LIFE_SAVING); Your("medallion %s!", !Blind ? "begins to glow" : "feels warm"); if (how == CHOKING) @@ -1183,11 +1100,20 @@ int how; if (how == GENOCIDED) { pline("Unfortunately you are still genocided..."); } else { + char killbuf[BUFSZ]; + formatkiller(killbuf, BUFSZ, how, FALSE); + livelog_printf(LL_LIFESAVE, "averted death (%s)", killbuf); survive = TRUE; } } /* explore and wizard modes offer player the option to keep playing */ if (!survive && (wizard || discover) && how <= GENOCIDED +#ifdef HANGUPHANDLING + /* if hangup has occurred, the only possible answer to a paranoid + query is 'no'; we want 'no' as the default for "Die?" but can't + accept it more than once if there's no user supplying it */ + && !(program_state.done_hup && gd.done_seq++ == gh.hero_seq) +#endif && !paranoid_query(ParanoidDie, "Die?")) { pline("OK, so you don't %s.", (how == CHOKING) ? "choke" : "die"); iflags.last_msg = PLNMSG_OK_DONT_DIE; @@ -1196,8 +1122,8 @@ int how; } if (survive) { - killer.name[0] = '\0'; - killer.format = KILLED_BY_AN; /* reset to 0 */ + svk.killer.name[0] = '\0'; + svk.killer.format = KILLED_BY_AN; /* reset to 0 */ return; } really_done(how); @@ -1205,9 +1131,8 @@ int how; } /* separated from done() in order to specify the __noreturn__ attribute */ -STATIC_OVL void -really_done(how) -int how; +staticfn void +really_done(int how) { boolean taken; char pbuf[BUFSZ]; @@ -1229,7 +1154,7 @@ int how; done_stopprint++; #endif /* render vision subsystem inoperative */ - iflags.vision_inited = 0; + iflags.vision_inited = FALSE; /* maybe use up active invent item(s), place thrown/kicked missile, deal with ball and chain possibly being temporarily off the map */ @@ -1242,17 +1167,28 @@ int how; topten figure it out separately and possibly getting different time or even day if player is slow responding to --More-- */ urealtime.finish_time = endtime = getnow(); - urealtime.realtime += (long) (endtime - urealtime.start_timing); + urealtime.realtime += timet_delta(endtime, urealtime.start_timing); /* collect these for end of game disclosure (not used during play) */ iflags.at_night = night(); iflags.at_midnight = midnight(); + /* final achievement tracking; only show blind and nudist if some + tangible progress has been made; always show ascension last */ + if (u.uachieved[0] || !flags.beginner) { + if (u.uroleplay.blind) + record_achievement(ACH_BLND); /* blind the whole game */ + if (u.uroleplay.nudist) + record_achievement(ACH_NUDE); /* never wore armor */ + } + if (how == ASCENDED) + record_achievement(ACH_UWIN); + dump_open_log(endtime); /* Sometimes you die on the first move. Life's not fair. * On those rare occasions you get hosed immediately, go out * smiling... :-) -3. */ - if (moves <= 1 && how < PANICKED && !done_stopprint) + if (svm.moves <= 1 && how < PANICKED && !done_stopprint) pline("Do not pass Go. Do not collect 200 %s.", currency(200L)); if (have_windows) @@ -1260,7 +1196,9 @@ int how; #ifndef NO_SIGNAL (void) signal(SIGINT, (SIG_RET_TYPE) done_intr); #if defined(UNIX) || defined(VMS) || defined(__EMX__) +#ifndef VMSVSI (void) signal(SIGQUIT, (SIG_RET_TYPE) done_intr); +#endif sethanguphandler(done_hangup); #endif #endif /* NO_SIGNAL */ @@ -1276,27 +1214,27 @@ int how; else if (how == BURNING || how == DISSOLVED) /* corpse burns up too */ u.ugrave_arise = (NON_PM - 2); /* leave no corpse */ else if (how == STONING) - u.ugrave_arise = (NON_PM - 1); /* statue instead of corpse */ + u.ugrave_arise = LEAVESTATUE; /* statue instead of corpse */ else if (how == TURNED_SLIME /* it's possible to turn into slime even though green slimes have been genocided: genocide could occur after hero is already infected or hero could eat a glob of one created before genocide; don't try to arise as one if they're gone */ - && !(mvitals[PM_GREEN_SLIME].mvflags & G_GENOD)) + && !(svm.mvitals[PM_GREEN_SLIME].mvflags & G_GENOD)) u.ugrave_arise = PM_GREEN_SLIME; if (how == QUIT) { - killer.format = NO_KILLER_PREFIX; + svk.killer.format = NO_KILLER_PREFIX; if (u.uhp < 1) { how = DIED; u.umortality++; /* skipped above when how==QUIT */ - Strcpy(killer.name, "quit while already on Charon's boat"); + Strcpy(svk.killer.name, "quit while already on Charon's boat"); } } if (how == ESCAPED || how == PANICKED) - killer.format = NO_KILLER_PREFIX; + svk.killer.format = NO_KILLER_PREFIX; - fixup_death(how); /* actually, fixup multi_reason */ + fixup_death(how); /* actually, fixup gm.multi_reason */ if (how != PANICKED) { boolean silently = done_stopprint ? TRUE : FALSE; @@ -1314,18 +1252,19 @@ int how; display_nhwindow(WIN_MESSAGE, FALSE); if (how != PANICKED) { - struct obj *obj; + struct obj *obj, *nextobj; /* * This is needed for both inventory disclosure and dumplog. * Both are optional, so do it once here instead of duplicating * it in both of those places. */ - for (obj = invent; obj; obj = obj->nobj) { - discover_object(obj->otyp, TRUE, FALSE); + for (obj = gi.invent; obj; obj = nextobj) { + nextobj = obj->nobj; + discover_object(obj->otyp, TRUE, TRUE, FALSE); + /* observe_object not necessary after discover_object */ obj->known = obj->bknown = obj->dknown = obj->rknown = 1; - if (Is_container(obj) || obj->otyp == STATUE) - obj->cknown = obj->lknown = 1; + set_cknown_lknown(obj); /* set flags when applicable */ /* we resolve Schroedinger's cat now in case of both disclosure and dumplog, where the 50:50 chance for live cat has to be the same both times */ @@ -1345,10 +1284,18 @@ int how; if (strcmp(flags.end_disclose, "none")) disclose(how, taken); + /* it would be better to do this after killer.name fixups but + that comes too late; included in final dumplog but might be + excluded by active livelog */ + formatkiller(pbuf, (unsigned) sizeof pbuf, how, TRUE); + if (!*pbuf) + Strcpy(pbuf, deaths[how]); + livelog_printf(LL_DUMP, "%s", pbuf); + dump_everything(how, endtime); } - /* if pets will contribute to score, populate mydogs list now + /* if pets will contribute to score, populate gm.mydogs list now (bones creation isn't a factor, but pline() messaging is; used to be done even sooner, but we need it to come after dump_everything() so that any accompanying pets are still on the map during dump) */ @@ -1362,20 +1309,18 @@ int how; /* grave creation should be after disclosure so it doesn't have this grave in the current level's features for #overview */ if (bones_ok && u.ugrave_arise == NON_PM - && !(mvitals[u.umonnum].mvflags & G_NOCORPSE)) { - int mnum = u.umonnum; - - if (!Upolyd) { - /* Base corpse on race when not poly'd since original u.umonnum - is based on role, and all role monsters are human. */ - mnum = (flags.female && urace.femalenum != NON_PM) - ? urace.femalenum - : urace.malenum; - } - corpse = mk_named_object(CORPSE, &mons[mnum], u.ux, u.uy, plname); - Sprintf(pbuf, "%s, ", plname); - formatkiller(eos(pbuf), sizeof pbuf - strlen(pbuf), how, TRUE); + && !(svm.mvitals[u.umonnum].mvflags & G_NOCORPSE)) { + /* Base corpse on race when not poly'd since original u.umonnum + is based on role, and all role monsters are human. */ + int mnum = !Upolyd ? gu.urace.mnum : u.umonnum, + was_already_grave = IS_GRAVE(levl[u.ux][u.uy].typ); + + corpse = mk_named_object(CORPSE, &mons[mnum], u.ux, u.uy, svp.plname); + Sprintf(pbuf, "%s, ", svp.plname); + formatkiller(eos(pbuf), sizeof pbuf - Strlen(pbuf), how, TRUE); make_grave(u.ux, u.uy, pbuf); + if (IS_GRAVE(levl[u.ux][u.uy].typ) && !was_already_grave) + levl[u.ux][u.uy].emptygrave = 1; /* corpse isn't buried */ } pbuf[0] = '\0'; /* clear grave text; also lint suppression */ @@ -1383,10 +1328,10 @@ int how; { int deepest = deepest_lev_reached(FALSE); - umoney = money_cnt(invent); + umoney = money_cnt(gi.invent); tmp = u.umoney0; - umoney += hidden_gold(); /* accumulate gold from containers */ - tmp = umoney - tmp; /* net gain */ + umoney += hidden_gold(TRUE); /* accumulate gold from containers */ + tmp = umoney - tmp; /* net gain */ if (tmp < 0L) tmp = 0L; @@ -1395,7 +1340,7 @@ int how; tmp += 50L * (long) (deepest - 1); if (deepest > 20) tmp += 1000L * (long) ((deepest > 30) ? 10 : deepest - 20); - nowrap_add(u.urexp, tmp); + u.urexp = nowrap_add(u.urexp, tmp); /* ascension gives a score bonus iff offering to original deity */ if (how == ASCENDED && u.ualign.type == u.ualignbase[A_ORIGINAL]) { @@ -1404,11 +1349,11 @@ int how; tmp = (u.ualignbase[A_CURRENT] == u.ualignbase[A_ORIGINAL]) ? u.urexp : (u.urexp / 2L); - nowrap_add(u.urexp, tmp); + u.urexp = nowrap_add(u.urexp, tmp); } } - if (u.ugrave_arise >= LOW_PM && !done_stopprint) { + if (ismnum(u.ugrave_arise) && !done_stopprint) { /* give this feedback even if bones aren't going to be created, so that its presence or absence doesn't tip off the player to new bones or their lack; it might be a lie if makemon fails */ @@ -1416,7 +1361,7 @@ int how; (u.ugrave_arise != PM_GREEN_SLIME) ? "body rises from the dead" : "revenant persists", - an(mons[u.ugrave_arise].mname)); + an(pmname(&mons[u.ugrave_arise], Ugender))); display_nhwindow(WIN_MESSAGE, FALSE); } @@ -1430,7 +1375,7 @@ int how; /* update gold for the rip output, which can't use hidden_gold() (containers will be gone by then if bones just got saved...) */ - done_money = umoney; + gd.done_money = umoney; /* clean up unneeded windows */ if (have_windows) { @@ -1439,7 +1384,7 @@ int how; if (WIN_INVEN != WIN_ERR) { destroy_nhwindow(WIN_INVEN), WIN_INVEN = WIN_ERR; /* precaution in case any late update_inventory() calls occur */ - iflags.perm_invent = 0; + iflags.perm_invent = FALSE; } display_nhwindow(WIN_MESSAGE, TRUE); destroy_nhwindow(WIN_MAP), WIN_MAP = WIN_ERR; @@ -1466,22 +1411,22 @@ int how; } #endif if (u.uhave.amulet) { - Strcat(killer.name, " (with the Amulet)"); + Strcat(svk.killer.name, " (with the Amulet)"); } else if (how == ESCAPED) { if (Is_astralevel(&u.uz)) /* offered Amulet to wrong deity */ - Strcat(killer.name, " (in celestial disgrace)"); + Strcat(svk.killer.name, " (in celestial disgrace)"); else if (carrying(FAKE_AMULET_OF_YENDOR)) - Strcat(killer.name, " (with a fake Amulet)"); + Strcat(svk.killer.name, " (with a fake Amulet)"); /* don't bother counting to see whether it should be plural */ } nle_done(how); - - Sprintf(pbuf, "%s %s the %s...", Goodbye(), plname, + + Sprintf(pbuf, "%s %s the %s...", Goodbye(), svp.plname, (how != ASCENDED) - ? (const char *) ((flags.female && urole.name.f) - ? urole.name.f - : urole.name.m) + ? (const char *) ((flags.female && gu.urole.name.f) + ? gu.urole.name.f + : gu.urole.name.m) : (const char *) (flags.female ? "Demigoddess" : "Demigod")); dump_forward_putstr(endwin, 0, pbuf, done_stopprint); dump_forward_putstr(endwin, 0, "", done_stopprint); @@ -1489,44 +1434,44 @@ int how; if (how == ESCAPED || how == ASCENDED) { struct monst *mtmp; struct obj *otmp; - register struct val_list *val; - register int i; + struct val_list *val; + int i; - for (val = valuables; val->list; val++) + for (val = gv.valuables; val->list; val++) for (i = 0; i < val->size; i++) { val->list[i].count = 0L; } - get_valuables(invent); + get_valuables(gi.invent); /* add points for collected valuables */ - for (val = valuables; val->list; val++) + for (val = gv.valuables; val->list; val++) for (i = 0; i < val->size; i++) if (val->list[i].count != 0L) { tmp = val->list[i].count * (long) objects[val->list[i].typ].oc_cost; - nowrap_add(u.urexp, tmp); + u.urexp = nowrap_add(u.urexp, tmp); } /* count the points for artifacts */ - artifact_score(invent, TRUE, endwin); + artifact_score(gi.invent, TRUE, endwin); - viz_array[0][0] |= IN_SIGHT; /* need visibility for naming */ - mtmp = mydogs; + gv.viz_array[0][0] |= IN_SIGHT; /* need visibility for naming */ + mtmp = gm.mydogs; Strcpy(pbuf, "You"); if (mtmp || Schroedingers_cat) { while (mtmp) { Sprintf(eos(pbuf), " and %s", mon_nam(mtmp)); if (mtmp->mtame) - nowrap_add(u.urexp, mtmp->mhp); + u.urexp = nowrap_add(u.urexp, mtmp->mhp); mtmp = mtmp->nmon; } /* [it might be more robust to create a housecat and add it to - mydogs; it doesn't have to be placed on the map for that] */ + gm.mydogs; it doesn't have to be placed on the map for that] */ if (Schroedingers_cat) { int mhp, m_lev = adj_lev(&mons[PM_HOUSECAT]); mhp = d(m_lev, 8); - nowrap_add(u.urexp, mhp); + u.urexp = nowrap_add(u.urexp, mhp); Strcat(eos(pbuf), " and Schroedinger's cat"); } dump_forward_putstr(endwin, 0, pbuf, done_stopprint); @@ -1535,22 +1480,22 @@ int how; Strcat(pbuf, " "); } Sprintf(eos(pbuf), "%s with %ld point%s,", - how == ASCENDED ? "went to your reward" - : "escaped from the dungeon", + (how == ASCENDED) ? "went to your reward" + : "escaped from the dungeon", u.urexp, plur(u.urexp)); dump_forward_putstr(endwin, 0, pbuf, done_stopprint); if (!done_stopprint) - artifact_score(invent, FALSE, endwin); /* list artifacts */ + artifact_score(gi.invent, FALSE, endwin); /* list artifacts */ #ifdef DUMPLOG dump_redirect(TRUE); if (iflags.in_dumplog) - artifact_score(invent, FALSE, 0); + artifact_score(gi.invent, FALSE, 0); dump_redirect(FALSE); #endif /* list valuables here */ - for (val = valuables; val->list; val++) { + for (val = gv.valuables; val->list; val++) { sort_valuables(val->list, val->size); for (i = 0; i < val->size && !done_stopprint; i++) { int typ = val->list[i].typ; @@ -1558,11 +1503,13 @@ int how; if (count == 0L) continue; - if (objects[typ].oc_class != GEM_CLASS || typ <= LAST_GEM) { + if (objects[typ].oc_class != GEM_CLASS + || typ <= LAST_REAL_GEM) { otmp = mksobj(typ, FALSE, FALSE); - discover_object(otmp->otyp, TRUE, FALSE); - otmp->known = 1; /* for fake amulets */ + discover_object(otmp->otyp, TRUE, TRUE, FALSE); otmp->dknown = 1; /* seen it (blindness fix) */ + /* observe_object not necessary after discover_object */ + otmp->known = 1; /* for fake amulets */ if (has_oname(otmp)) free_oname(otmp); otmp->quan = count; @@ -1587,12 +1534,12 @@ int how; (u.uz.dlevel < 0) ? "passed away" : ends[how]); } else { /* more conventional demise */ - const char *where = dungeons[u.uz.dnum].dname; + const char *where = svd.dungeons[u.uz.dnum].dname; if (Is_astralevel(&u.uz)) where = "The Astral Plane"; Sprintf(pbuf, "You %s in %s", ends[how], where); - if (!In_endgame(&u.uz) && !Is_knox(&u.uz)) + if (!In_endgame(&u.uz) && !single_level_branch(&u.uz)) Sprintf(eos(pbuf), " on dungeon level %d", In_quest(&u.uz) ? dunlev(&u.uz) : depth(&u.uz)); } @@ -1602,7 +1549,7 @@ int how; } Sprintf(pbuf, "and %ld piece%s of gold, after %ld move%s.", umoney, - plur(umoney), moves, plur(moves)); + plur(umoney), svm.moves, plur(svm.moves)); dump_forward_putstr(endwin, 0, pbuf, done_stopprint); Sprintf(pbuf, "You were level %d with a maximum of %d hit point%s when you %s.", @@ -1615,8 +1562,27 @@ int how; destroy_nhwindow(endwin); dump_close_log(); - /* "So when I die, the first thing I will see in Heaven is a - * score list?" */ + + /* shut down soundlib */ + if (soundprocs.sound_exit_nhsound) + (*soundprocs.sound_exit_nhsound)("really_done"); + + /* + * "So when I die, the first thing I will see in Heaven is a score list?" + * + * topten() updates 'logfile' and 'xlogfile', when they're enabled. + * Then the current game's score is shown in its relative position + * within high scores, and 'record' is updated if that makes the cut. + * + * FIXME! + * If writing topten with raw_print(), which will usually be sent to + * stdout, we call exit_nhwindows() first in case it erases the screen. + * But when writing topten to a window, we call exit_nhwindows() + * after topten() because that needs the windowing system to still + * be up. This sequencing is absurd; we need something like + * raw_prompt("--More--") (or "Press to continue.") that + * topten() can call for !toptenwin before returning here. + */ if (have_windows && !iflags.toptenwin) exit_nhwindows((char *) 0), have_windows = FALSE; topten(how, endtime); @@ -1630,12 +1596,15 @@ int how; nh_terminate(EXIT_SUCCESS); } +/* used for disclosure and for the ':' choice when looting a container */ void -container_contents(list, identified, all_containers, reportempty) -struct obj *list; -boolean identified, all_containers, reportempty; +container_contents( + struct obj *list, + boolean identified, + boolean all_containers, + boolean reportempty) { - register struct obj *box, *obj; + struct obj *box, *obj; char buf[BUFSZ]; boolean cat, dumping = iflags.in_dumplog; @@ -1673,12 +1642,12 @@ boolean identified, all_containers, reportempty; ? SORTLOOT_LOOT : 0) | (flags.sortpack ? SORTLOOT_PACK : 0)); sortedcobj = sortloot(&box->cobj, sortflags, FALSE, - (boolean FDECL((*), (OBJ_P))) 0); - for (srtc = sortedcobj; ((obj = srtc->obj) != 0); ++srtc) { + (boolean (*)(OBJ_P)) 0); + for (srtc = sortedcobj; (obj = srtc->obj) != 0; ++srtc) { if (identified) { - discover_object(obj->otyp, TRUE, FALSE); - obj->known = obj->bknown = obj->dknown - = obj->rknown = 1; + discover_object(obj->otyp, TRUE, TRUE, FALSE); + obj->dknown = 1; /* observe_object unnecessary */ + obj->known = obj->bknown = obj->rknown = 1; if (Is_container(obj) || obj->otyp == STATUE) obj->cknown = obj->lknown = 1; } @@ -1708,12 +1677,13 @@ boolean identified, all_containers, reportempty; } /* should be called with either EXIT_SUCCESS or EXIT_FAILURE */ -void -nh_terminate(status) -int status; +ATTRNORETURN void +nh_terminate(int status) { program_state.in_moveloop = 0; /* won't be returning to normal play */ -#ifdef MAC + + l_nhcore_call(NHCORE_GAME_EXIT); +#ifdef MACOS9 getreturn("to exit"); #endif /* don't bother to try to release memory if we're in panic mode, to @@ -1721,6 +1691,7 @@ int status; if (!program_state.panicking) { freedynamicdata(); dlb_cleanup(); + l_nhcore_done(); } #ifdef VMS @@ -1738,401 +1709,9 @@ int status; nethack_exit(status); } -enum vanq_order_modes { - VANQ_MLVL_MNDX = 0, - VANQ_MSTR_MNDX, - VANQ_ALPHA_SEP, - VANQ_ALPHA_MIX, - VANQ_MCLS_HTOL, - VANQ_MCLS_LTOH, - VANQ_COUNT_H_L, - VANQ_COUNT_L_H, - - NUM_VANQ_ORDER_MODES -}; - -static const char *vanqorders[NUM_VANQ_ORDER_MODES] = { - "traditional: by monster level, by internal monster index", - "by monster toughness, by internal monster index", - "alphabetically, first unique monsters, then others", - "alphabetically, unique monsters and others intermixed", - "by monster class, high to low level within class", - "by monster class, low to high level within class", - "by count, high to low, by internal index within tied count", - "by count, low to high, by internal index within tied count", -}; -static int vanq_sortmode = VANQ_MLVL_MNDX; - -STATIC_PTR int CFDECLSPEC -vanqsort_cmp(vptr1, vptr2) -const genericptr vptr1; -const genericptr vptr2; -{ - int indx1 = *(short *) vptr1, indx2 = *(short *) vptr2, - mlev1, mlev2, mstr1, mstr2, uniq1, uniq2, died1, died2, res; - const char *name1, *name2, *punct; - schar mcls1, mcls2; - - switch (vanq_sortmode) { - default: - case VANQ_MLVL_MNDX: - /* sort by monster level */ - mlev1 = mons[indx1].mlevel, mlev2 = mons[indx2].mlevel; - res = mlev2 - mlev1; /* mlevel high to low */ - break; - case VANQ_MSTR_MNDX: - /* sort by monster toughness */ - mstr1 = mons[indx1].difficulty, mstr2 = mons[indx2].difficulty; - res = mstr2 - mstr1; /* monstr high to low */ - break; - case VANQ_ALPHA_SEP: - uniq1 = ((mons[indx1].geno & G_UNIQ) && indx1 != PM_HIGH_PRIEST); - uniq2 = ((mons[indx2].geno & G_UNIQ) && indx2 != PM_HIGH_PRIEST); - if (uniq1 ^ uniq2) { /* one or other uniq, but not both */ - res = uniq2 - uniq1; - break; - } /* else both unique or neither unique */ - /*FALLTHRU*/ - case VANQ_ALPHA_MIX: - name1 = mons[indx1].mname, name2 = mons[indx2].mname; - res = strcmpi(name1, name2); /* caseblind alhpa, low to high */ - break; - case VANQ_MCLS_HTOL: - case VANQ_MCLS_LTOH: - /* mons[].mlet is a small integer, 1..N, of type plain char; - if 'char' happens to be unsigned, (mlet1 - mlet2) would yield - an inappropriate result when mlet2 is greater than mlet1, - so force our copies (mcls1, mcls2) to be signed */ - mcls1 = (schar) mons[indx1].mlet, mcls2 = (schar) mons[indx2].mlet; - /* S_ANT through S_ZRUTY correspond to lowercase monster classes, - S_ANGEL through S_ZOMBIE correspond to uppercase, and various - punctuation characters are used for classes beyond those */ - if (mcls1 > S_ZOMBIE && mcls2 > S_ZOMBIE) { - /* force a specific order to the punctuation classes that's - different from the internal order; - internal order is ok if neither or just one is punctuation - since letters have lower values so come out before punct */ - static const char punctclasses[] = { - S_LIZARD, S_EEL, S_GOLEM, S_GHOST, S_DEMON, S_HUMAN, '\0' - }; - - if ((punct = index(punctclasses, mcls1)) != 0) - mcls1 = (schar) (S_ZOMBIE + 1 + (int) (punct - punctclasses)); - if ((punct = index(punctclasses, mcls2)) != 0) - mcls2 = (schar) (S_ZOMBIE + 1 + (int) (punct - punctclasses)); - } - res = mcls1 - mcls2; /* class */ - if (res == 0) { - mlev1 = mons[indx1].mlevel, mlev2 = mons[indx2].mlevel; - res = mlev1 - mlev2; /* mlevel low to high */ - if (vanq_sortmode == VANQ_MCLS_HTOL) - res = -res; /* mlevel high to low */ - } - break; - case VANQ_COUNT_H_L: - case VANQ_COUNT_L_H: - died1 = mvitals[indx1].died, died2 = mvitals[indx2].died; - res = died2 - died1; /* dead count high to low */ - if (vanq_sortmode == VANQ_COUNT_L_H) - res = -res; /* dead count low to high */ - break; - } - /* tiebreaker: internal mons[] index */ - if (res == 0) - res = indx1 - indx2; /* mndx low to high */ - return res; -} - -/* returns -1 if cancelled via ESC */ -STATIC_OVL int -set_vanq_order() -{ - winid tmpwin; - menu_item *selected; - anything any; - int i, n, choice; - - tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - any = zeroany; /* zero out all bits */ - for (i = 0; i < SIZE(vanqorders); i++) { - if (i == VANQ_ALPHA_MIX || i == VANQ_MCLS_HTOL) /* skip these */ - continue; - any.a_int = i + 1; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, vanqorders[i], - (i == vanq_sortmode) ? MENU_SELECTED : MENU_UNSELECTED); - } - end_menu(tmpwin, "Sort order for vanquished monster counts"); - - n = select_menu(tmpwin, PICK_ONE, &selected); - destroy_nhwindow(tmpwin); - if (n > 0) { - choice = selected[0].item.a_int - 1; - /* skip preselected entry if we have more than one item chosen */ - if (n > 1 && choice == vanq_sortmode) - choice = selected[1].item.a_int - 1; - free((genericptr_t) selected); - vanq_sortmode = choice; - } - return (n < 0) ? -1 : vanq_sortmode; -} - -/* #vanquished command */ -int -dovanquished() -{ - list_vanquished('a', FALSE); - return 0; -} - -/* high priests aren't unique but are flagged as such to simplify something */ -#define UniqCritterIndx(mndx) ((mons[mndx].geno & G_UNIQ) \ - && mndx != PM_HIGH_PRIEST) - -STATIC_OVL void -list_vanquished(defquery, ask) -char defquery; -boolean ask; -{ - register int i; - int pfx, nkilled; - unsigned ntypes, ni; - long total_killed = 0L; - winid klwin; - short mindx[NUMMONS]; - char c, buf[BUFSZ], buftoo[BUFSZ]; - boolean dumping; /* for DUMPLOG; doesn't need to be conditional */ - - dumping = (defquery == 'd'); - if (dumping) - defquery = 'y'; - - /* get totals first */ - ntypes = 0; - for (i = LOW_PM; i < NUMMONS; i++) { - if ((nkilled = (int) mvitals[i].died) == 0) - continue; - mindx[ntypes++] = i; - total_killed += (long) nkilled; - } - - /* vanquished creatures list; - * includes all dead monsters, not just those killed by the player - */ - if (ntypes != 0) { - char mlet, prev_mlet = 0; /* used as small integer, not character */ - boolean class_header, uniq_header, was_uniq = FALSE; - - c = ask ? yn_function( - "Do you want an account of creatures vanquished?", - ynaqchars, defquery) - : defquery; - if (c == 'q') - done_stopprint++; - if (c == 'y' || c == 'a') { - if (c == 'a') { /* ask player to choose sort order */ - /* choose value for vanq_sortmode via menu; ESC cancels list - of vanquished monsters but does not set 'done_stopprint' */ - if (set_vanq_order() < 0) - return; - } - uniq_header = (vanq_sortmode == VANQ_ALPHA_SEP); - class_header = (vanq_sortmode == VANQ_MCLS_LTOH - || vanq_sortmode == VANQ_MCLS_HTOL); - - klwin = create_nhwindow(NHW_MENU); - putstr(klwin, 0, "Vanquished creatures:"); - if (!dumping) - putstr(klwin, 0, ""); - - qsort((genericptr_t) mindx, ntypes, sizeof *mindx, vanqsort_cmp); - for (ni = 0; ni < ntypes; ni++) { - i = mindx[ni]; - nkilled = mvitals[i].died; - mlet = mons[i].mlet; - if (class_header && mlet != prev_mlet) { - Strcpy(buf, def_monsyms[(int) mlet].explain); - putstr(klwin, ask ? 0 : iflags.menu_headings, - upstart(buf)); - prev_mlet = mlet; - } - if (UniqCritterIndx(i)) { - Sprintf(buf, "%s%s", - !type_is_pname(&mons[i]) ? "the " : "", - mons[i].mname); - if (nkilled > 1) { - switch (nkilled) { - case 2: - Sprintf(eos(buf), " (twice)"); - break; - case 3: - Sprintf(eos(buf), " (thrice)"); - break; - default: - Sprintf(eos(buf), " (%d times)", nkilled); - break; - } - } - was_uniq = TRUE; - } else { - if (uniq_header && was_uniq) { - putstr(klwin, 0, ""); - was_uniq = FALSE; - } - /* trolls or undead might have come back, - but we don't keep track of that */ - if (nkilled == 1) - Strcpy(buf, an(mons[i].mname)); - else - Sprintf(buf, "%3d %s", nkilled, - makeplural(mons[i].mname)); - } - /* number of leading spaces to match 3 digit prefix */ - pfx = !strncmpi(buf, "the ", 3) ? 0 - : !strncmpi(buf, "an ", 3) ? 1 - : !strncmpi(buf, "a ", 2) ? 2 - : !digit(buf[2]) ? 4 : 0; - if (class_header) - ++pfx; - Sprintf(buftoo, "%*s%s", pfx, "", buf); - putstr(klwin, 0, buftoo); - } - /* - * if (Hallucination) - * putstr(klwin, 0, "and a partridge in a pear tree"); - */ - if (ntypes > 1) { - if (!dumping) - putstr(klwin, 0, ""); - Sprintf(buf, "%ld creatures vanquished.", total_killed); - putstr(klwin, 0, buf); - } - display_nhwindow(klwin, TRUE); - destroy_nhwindow(klwin); - } - } else if (defquery == 'a') { - /* #dovanquished rather than final disclosure, so pline() is ok */ - pline("No creatures have been vanquished."); -#ifdef DUMPLOG - } else if (dumping) { - putstr(0, 0, "No creatures were vanquished."); /* not pline() */ -#endif - } -} - -/* number of monster species which have been genocided */ -int -num_genocides() -{ - int i, n = 0; - - for (i = LOW_PM; i < NUMMONS; ++i) { - if (mvitals[i].mvflags & G_GENOD) { - ++n; - if (UniqCritterIndx(i)) - impossible("unique creature '%d: %s' genocided?", - i, mons[i].mname); - } - } - return n; -} - -STATIC_OVL int -num_extinct() -{ - int i, n = 0; - - for (i = LOW_PM; i < NUMMONS; ++i) { - if (UniqCritterIndx(i)) - continue; - if ((mvitals[i].mvflags & G_GONE) == G_EXTINCT) - ++n; - } - return n; -} - -STATIC_OVL void -list_genocided(defquery, ask) -char defquery; -boolean ask; -{ - register int i; - int ngenocided, nextinct; - char c; - winid klwin; - char buf[BUFSZ]; - boolean dumping; /* for DUMPLOG; doesn't need to be conditional */ - - dumping = (defquery == 'd'); - if (dumping) - defquery = 'y'; - - ngenocided = num_genocides(); - nextinct = num_extinct(); - - /* genocided or extinct species list */ - if (ngenocided != 0 || nextinct != 0) { - Sprintf(buf, "Do you want a list of %sspecies%s%s?", - (nextinct && !ngenocided) ? "extinct " : "", - (ngenocided) ? " genocided" : "", - (nextinct && ngenocided) ? " and extinct" : ""); - c = ask ? yn_function(buf, ynqchars, defquery) : defquery; - if (c == 'q') - done_stopprint++; - if (c == 'y') { - klwin = create_nhwindow(NHW_MENU); - Sprintf(buf, "%s%s species:", - (ngenocided) ? "Genocided" : "Extinct", - (nextinct && ngenocided) ? " or extinct" : ""); - putstr(klwin, 0, buf); - if (!dumping) - putstr(klwin, 0, ""); - - for (i = LOW_PM; i < NUMMONS; i++) { - /* uniques can't be genocided but can become extinct; - however, they're never reported as extinct, so skip them */ - if (UniqCritterIndx(i)) - continue; - if (mvitals[i].mvflags & G_GONE) { - Sprintf(buf, " %s", makeplural(mons[i].mname)); - /* - * "Extinct" is unfortunate terminology. A species - * is marked extinct when its birth limit is reached, - * but there might be members of the species still - * alive, contradicting the meaning of the word. - */ - if ((mvitals[i].mvflags & G_GONE) == G_EXTINCT) - Strcat(buf, " (extinct)"); - putstr(klwin, 0, buf); - } - } - if (!dumping) - putstr(klwin, 0, ""); - if (ngenocided > 0) { - Sprintf(buf, "%d species genocided.", ngenocided); - putstr(klwin, 0, buf); - } - if (nextinct > 0) { - Sprintf(buf, "%d species extinct.", nextinct); - putstr(klwin, 0, buf); - } - - display_nhwindow(klwin, TRUE); - destroy_nhwindow(klwin); - } -#ifdef DUMPLOG - } else if (dumping) { - putstr(0, 0, "No species were genocided or became extinct."); -#endif - } -} - /* set a delayed killer, ensure non-delayed killer is cleared out */ void -delayed_killer(id, format, killername) -int id; -int format; -const char *killername; +delayed_killer(int id, int format, const char *killername) { struct kinfo *k = find_delayed_killer(id); @@ -2141,22 +1720,21 @@ const char *killername; k = (struct kinfo *) alloc(sizeof (struct kinfo)); (void) memset((genericptr_t) k, 0, sizeof (struct kinfo)); k->id = id; - k->next = killer.next; - killer.next = k; + k->next = svk.killer.next; + svk.killer.next = k; } k->format = format; Strcpy(k->name, killername ? killername : ""); - killer.name[0] = 0; + svk.killer.name[0] = 0; } struct kinfo * -find_delayed_killer(id) -int id; +find_delayed_killer(int id) { struct kinfo *k; - for (k = killer.next; k != (struct kinfo *) 0; k = k->next) { + for (k = svk.killer.next; k != (struct kinfo *) 0; k = k->next) { if (k->id == id) break; } @@ -2164,14 +1742,13 @@ int id; } void -dealloc_killer(kptr) -struct kinfo *kptr; +dealloc_killer(struct kinfo *kptr) { - struct kinfo *prev = &killer, *k; + struct kinfo *prev = &svk.killer, *k; if (kptr == (struct kinfo *) 0) return; - for (k = killer.next; k != (struct kinfo *) 0; k = k->next) { + for (k = svk.killer.next; k != (struct kinfo *) 0; k = k->next) { if (k == kptr) break; prev = k; @@ -2187,43 +1764,40 @@ struct kinfo *kptr; } void -save_killers(fd, mode) -int fd; -int mode; +save_killers(NHFILE *nhfp) { struct kinfo *kptr; - if (perform_bwrite(mode)) { - for (kptr = &killer; kptr != (struct kinfo *) 0; kptr = kptr->next) { - bwrite(fd, (genericptr_t) kptr, sizeof (struct kinfo)); + if (update_file(nhfp)) { + for (kptr = &svk.killer; kptr; kptr = kptr->next) { + Sfo_kinfo(nhfp, kptr, "kinfo"); } } - if (release_data(mode)) { - while (killer.next) { - kptr = killer.next->next; - free((genericptr_t) killer.next); - killer.next = kptr; + if (release_data(nhfp)) { + while (svk.killer.next) { + kptr = svk.killer.next->next; + free((genericptr_t) svk.killer.next); + svk.killer.next = kptr; } } } +#endif /* !SFCTOOL */ void -restore_killers(fd) -int fd; +restore_killers(NHFILE *nhfp) { struct kinfo *kptr; - for (kptr = &killer; kptr != (struct kinfo *) 0; kptr = kptr->next) { - mread(fd, (genericptr_t) kptr, sizeof (struct kinfo)); + for (kptr = &svk.killer; kptr != (struct kinfo *) 0; kptr = kptr->next) { + Sfi_kinfo(nhfp, kptr, "kinfo"); if (kptr->next) { kptr->next = (struct kinfo *) alloc(sizeof (struct kinfo)); } } } -static int -wordcount(p) -char *p; +staticfn int +wordcount(char *p) { int words = 0; @@ -2238,9 +1812,8 @@ char *p; return words; } -static void -bel_copy1(inp, out) -char **inp, *out; +staticfn void +bel_copy1(char **inp, char *out) { char *in = *inp; @@ -2254,8 +1827,7 @@ char **inp, *out; } char * -build_english_list(in) -char *in; +build_english_list(char *in) { char *out, *p = in; int len = (int) strlen(p), words = wordcount(p); @@ -2293,4 +1865,91 @@ char *in; return out; } +/* What do we try to in what order? Tradeoffs: + * libc: +no external programs required + * -requires newish libc/glibc + * -requires -rdynamic + * gdb: +gives more detailed information + * +works on more OS versions + * -requires -g, which may preclude -O on some compilers + * + * And the UI: if sysopt.crashreporturl, and defined(CRASHREPORT) + * we gather the stacktrace (etc) and launch a browser to submit a bug report + * otherwise we just use stdout. Requires libc for now. + */ +# ifdef SYSCF +# define SYSOPT_PANICTRACE_GDB sysopt.panictrace_gdb +# ifdef PANICTRACE_LIBC +# define SYSOPT_PANICTRACE_LIBC sysopt.panictrace_libc +# else +# define SYSOPT_PANICTRACE_LIBC 0 +# endif +# else +# define SYSOPT_PANICTRACE_GDB (nh_getenv("NETHACK_USE_GDB") == 0 ? 0 : 2) +# ifdef PANICTRACE_LIBC +# define SYSOPT_PANICTRACE_LIBC 1 +# else +# define SYSOPT_PANICTRACE_LIBC 0 +# endif +# endif + +# ifdef CRASHREPORT +#define USED_FOR_CRASHREPORT +# else +#define USED_FOR_CRASHREPORT UNUSED +# endif + +void +NH_abort(char *why USED_FOR_CRASHREPORT) +{ +#ifdef PANICTRACE + int gdb_prio = SYSOPT_PANICTRACE_GDB; + int libc_prio = SYSOPT_PANICTRACE_LIBC; +#endif + static volatile boolean aborting = FALSE; + + /* don't execute this code recursively if a second abort is requested + while this routine or the code it calls is executing */ + if (aborting) + return; + aborting = TRUE; + +#ifdef PANICTRACE +#ifdef CRASHREPORT + if(!submit_web_report(1, "Panic", why)) +#endif + { +#ifndef VMS + if (gdb_prio == libc_prio && gdb_prio > 0) + gdb_prio++; + + if (gdb_prio > libc_prio) { + (void) (NH_panictrace_gdb() + || (libc_prio && NH_panictrace_libc())); + } else { + (void) (NH_panictrace_libc() + || (gdb_prio && NH_panictrace_gdb())); + } + +#else /* VMS */ + /* overload otherwise unused priority for debug mode: 1 = show + traceback and exit; 2 = show traceback and stay in debugger */ + /* if (wizard && gdb_prio == 1) gdb_prio = 2; */ + vms_traceback(gdb_prio); + nhUse(libc_prio); + +#endif /* ?VMS */ + } +#ifndef NO_SIGNAL + panictrace_setsignals(FALSE); +#endif +#endif /* PANICTRACE */ +#if defined(WIN32) + win32_abort(); +#else + abort(); +#endif +} + +#undef USED_FOR_CRASHREPORT /*end.c*/ diff --git a/src/engrave.c b/src/engrave.c index d60fd6f1e..2e06ebc12 100644 --- a/src/engrave.c +++ b/src/engrave.c @@ -1,25 +1,63 @@ -/* NetHack 3.6 engrave.c $NHDT-Date: 1570318925 2019/10/05 23:42:05 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.75 $ */ +/* NetHack 5.0 engrave.c $NHDT-Date: 1737345573 2025/01/19 19:59:33 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.165 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2012. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -#include "lev.h" -STATIC_VAR NEARDATA struct engr *head_engr; -STATIC_DCL const char *NDECL(blengr); +/* doengrave() data */ +struct _doengrave_ctx { + boolean dengr; /* TRUE if we wipe out the current engraving */ + boolean doblind; /* TRUE if engraving blinds the player */ + boolean doknown; /* TRUE if we identify the stylus */ + boolean eow; /* TRUE if we are overwriting oep */ + boolean jello; /* TRUE if we are engraving in slime */ + boolean ptext; /* TRUE if we must prompt for engrave text */ + boolean teleengr; /* TRUE if we move the old engraving */ + boolean zapwand; /* TRUE if we remove a wand charge */ + boolean disprefresh; /* TRUE if the display needs a refresh */ + boolean frosted; /* TRUE if engraving on ice */ + boolean adding; /* TRUE if adding to existing engraving */ + + int ret; /* doengrave return value */ + int type; /* Type of engraving made */ + int oetype; /* will be set to type of current engraving */ + + struct obj *otmp; /* Object selected with which to engrave */ + struct engr* oep; /* The 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 post_engr_text[BUFSZ]; /* Text displayed after engraving prompt */ + char *writer; /* text of item used for writing */ + const char *everb; /* Present tense of engraving type */ + const char *eloc; /* Where the engraving is (ie dust/floor/...) */ + + size_t len; /* # of nonspace chars of new engraving text */ +}; +#ifndef SFCTOOL +staticfn int stylus_ok(struct obj *); +staticfn boolean u_can_engrave(void); +staticfn void doengrave_ctx_init(struct _doengrave_ctx *); +staticfn void doengrave_sfx_item_WAN(struct _doengrave_ctx *); +staticfn boolean doengrave_sfx_item(struct _doengrave_ctx *); +staticfn void doengrave_ctx_verb(struct _doengrave_ctx *); +staticfn int engrave(void); +staticfn const char *blengr(void); char * -random_engraving(outbuf) -char *outbuf; +random_engraving(char *outbuf, char *pristine_copy) { const char *rumor; /* a random engraving may come from the "rumors" file, or from the "engrave" file (formerly in an array here) */ - if (!rn2(4) || !(rumor = getrumor(0, outbuf, TRUE)) || !*rumor) - (void) get_rnd_text(ENGRAVEFILE, outbuf, rn2); + if (!rn2(4) || !(rumor = getrumor(0, pristine_copy, TRUE)) || !*rumor) + (void) get_rnd_text(ENGRAVEFILE, pristine_copy, rn2, MD_PAD_RUMORS); + Strcpy(outbuf, pristine_copy); wipeout_text(outbuf, (int) (strlen(outbuf) / 4), 0); return outbuf; } @@ -79,20 +117,21 @@ static const struct { /* degrade some of the characters in a string */ void -wipeout_text(engr, cnt, seed) -char *engr; -int cnt; -unsigned seed; /* for semi-controlled randomization */ +wipeout_text( + char *engr, /* engraving text */ + int cnt, /* number of chars to degrade */ + unsigned seed) /* for semi-controlled randomization */ { char *s; - int i, j, nxt, use_rubout, lth = (int) strlen(engr); + int i, j, nxt, use_rubout; + unsigned lth = (unsigned) strlen(engr); if (lth && cnt > 0) { while (cnt--) { /* pick next character */ if (!seed) { /* random */ - nxt = rn2(lth); + nxt = rn2((int) lth); use_rubout = rn2(4); } else { /* predictable; caller can reproduce the same sequence by @@ -107,28 +146,30 @@ unsigned seed; /* for semi-controlled randomization */ continue; /* rub out unreadable & small punctuation marks */ - if (index("?.,'`-|_", *s)) { + if (strchr("?.,'`-|_", *s)) { *s = ' '; continue; } - if (!use_rubout) + if (!use_rubout) { i = SIZE(rubouts); - else + } else { for (i = 0; i < SIZE(rubouts); i++) if (*s == rubouts[i].wipefrom) { + unsigned ln = (unsigned) strlen(rubouts[i].wipeto); /* * Pick one of the substitutes at random. */ - if (!seed) - j = rn2(strlen(rubouts[i].wipeto)); - else { + if (!seed) { + j = rn2((int) ln); + } else { seed *= 31, seed %= (BUFSZ - 1); - j = seed % (strlen(rubouts[i].wipeto)); + j = seed % ln; } *s = rubouts[i].wipeto[j]; break; } + } /* didn't pick rubout; use '?' for unreadable character */ if (i == SIZE(rubouts)) @@ -143,109 +184,53 @@ unsigned seed; /* for semi-controlled randomization */ /* check whether hero can reach something at ground level */ boolean -can_reach_floor(check_pit) -boolean check_pit; +can_reach_floor(boolean check_pit) { struct trap *t; - if (u.uswallow) + if (u.uswallow + || (u.ustuck && !sticks(gy.youmonst.data) + /* assume that arms are pinned rather than that the hero + has been lifted up above the floor [doesn't explain + how hero can attack the creature holding him or her; + that's life in nethack...] */ + && attacktype(u.ustuck->data, AT_HUGS)) + || (Levitation && !(Is_airlevel(&u.uz) || Is_waterlevel(&u.uz)))) return FALSE; /* Restricted/unskilled riders can't reach the floor */ if (u.usteed && P_SKILL(P_RIDING) < P_BASIC) return FALSE; - if (check_pit && !Flying - && (t = t_at(u.ux, u.uy)) != 0 + if (u.uundetected && ceiling_hider(gy.youmonst.data)) + return FALSE; + + if (Flying || gy.youmonst.data->msize >= MZ_HUGE) + return TRUE; + + if (check_pit && (t = t_at(u.ux, u.uy)) != 0 && (uteetering_at_seen_pit(t) || uescaped_shaft(t))) return FALSE; - return (boolean) ((!Levitation || Is_airlevel(&u.uz) - || Is_waterlevel(&u.uz)) - && (!u.uundetected || !is_hider(youmonst.data) - || u.umonnum == PM_TRAPPER)); + return TRUE; } /* give a message after caller has determined that hero can't reach */ void -cant_reach_floor(x, y, up, check_pit) -int x, y; -boolean up, check_pit; -{ - You("can't reach the %s.", - up ? ceiling(x, y) - : (check_pit && can_reach_floor(FALSE)) - ? "bottom of the pit" - : surface(x, y)); -} - -const char * -surface(x, y) -register int x, y; -{ - register struct rm *lev = &levl[x][y]; - - if (x == u.ux && y == u.uy && u.uswallow && is_animal(u.ustuck->data)) - return "maw"; - else if (IS_AIR(lev->typ) && Is_airlevel(&u.uz)) - return "air"; - else if (is_pool(x, y)) - return (Underwater && !Is_waterlevel(&u.uz)) - ? "bottom" : hliquid("water"); - else if (is_ice(x, y)) - return "ice"; - else if (is_lava(x, y)) - return hliquid("lava"); - else if (lev->typ == DRAWBRIDGE_DOWN) - return "bridge"; - else if (IS_ALTAR(levl[x][y].typ)) - return "altar"; - else if (IS_GRAVE(levl[x][y].typ)) - return "headstone"; - else if (IS_FOUNTAIN(levl[x][y].typ)) - return "fountain"; - else if ((IS_ROOM(lev->typ) && !Is_earthlevel(&u.uz)) - || IS_WALL(lev->typ) || IS_DOOR(lev->typ) || lev->typ == SDOOR) - return "floor"; - else - return "ground"; -} - -const char * -ceiling(x, y) -register int x, y; +cant_reach_floor(coordxy x, coordxy y, boolean up, + boolean check_pit, boolean wand_engraving) { - register struct rm *lev = &levl[x][y]; - const char *what; - - /* other room types will no longer exist when we're interested -- - * see check_special_room() - */ - if (*in_rooms(x, y, VAULT)) - what = "vault's ceiling"; - else if (*in_rooms(x, y, TEMPLE)) - what = "temple's ceiling"; - else if (*in_rooms(x, y, SHOPBASE)) - what = "shop's ceiling"; - else if (Is_waterlevel(&u.uz)) - /* water plane has no surface; its air bubbles aren't below sky */ - what = "water above"; - else if (IS_AIR(lev->typ)) - what = "sky"; - else if (Underwater) - what = "water's surface"; - else if ((IS_ROOM(lev->typ) && !Is_earthlevel(&u.uz)) - || IS_WALL(lev->typ) || IS_DOOR(lev->typ) || lev->typ == SDOOR) - what = "ceiling"; - else - what = "rock cavern"; - - return what; + pline("%s can't reach the %s.", + wand_engraving + ? "The wand does nothing more, and the tip of the wand" + : "You", + up ? ceiling(x, y) + : (check_pit && can_reach_floor(FALSE)) ? "bottom of the pit" + : surface(x, y)); } struct engr * -engr_at(x, y) -xchar x, y; +engr_at(coordxy x, coordxy y) { - register struct engr *ep = head_engr; + struct engr *ep = head_engr; while (ep) { if (x == ep->engr_x && y == ep->engr_y) @@ -262,65 +247,84 @@ xchar x, y; * If strict checking is requested, the word is only considered to be * present if it is intact and is the entire content of the engraving. */ -int -sengr_at(s, x, y, strict) -const char *s; -xchar x, y; -boolean strict; +struct engr * +sengr_at(const char *s, coordxy x, coordxy y, boolean strict) { - register struct engr *ep = engr_at(x, y); + struct engr *ep = engr_at(x, y); - if (ep && ep->engr_type != HEADSTONE && ep->engr_time <= moves) { - return strict ? (fuzzymatch(ep->engr_txt, s, "", TRUE)) - : (strstri(ep->engr_txt, s) != 0); + if (ep && ep->engr_type != HEADSTONE && ep->engr_time <= svm.moves) { + if (strict ? !strcmpi(ep->engr_txt[actual_text], s) + : (strstri(ep->engr_txt[actual_text], s) != 0)) + return ep; } - - return FALSE; + return (struct engr *) NULL; } void -u_wipe_engr(cnt) -int cnt; +u_wipe_engr(int cnt) { if (can_reach_floor(TRUE)) wipe_engr_at(u.ux, u.uy, cnt, FALSE); } void -wipe_engr_at(x, y, cnt, magical) -xchar x, y, cnt; -boolean magical; +wipe_engr_at(coordxy x, coordxy y, xint16 cnt, boolean magical) { - register struct engr *ep = engr_at(x, y); + struct engr *ep = engr_at(x, y); - /* Headstones are indelible */ - if (ep && ep->engr_type != HEADSTONE) { + /* Headstones and some specially marked engravings are indelible */ + if (ep && ep->engr_type != HEADSTONE && !ep->nowipeout) { debugpline1("asked to erode %d characters", cnt); if (ep->engr_type != BURN || is_ice(x, y) || (magical && !rn2(2))) { if (ep->engr_type != DUST && ep->engr_type != ENGR_BLOOD) { cnt = rn2(1 + 50 / (cnt + 1)) ? 0 : 1; debugpline1("actually eroding %d characters", cnt); } - wipeout_text(ep->engr_txt, (int) cnt, 0); - while (ep->engr_txt[0] == ' ') - ep->engr_txt++; - if (!ep->engr_txt[0]) + wipeout_text(ep->engr_txt[actual_text], (int) cnt, 0); + while (ep->engr_txt[actual_text][0] == ' ') + ep->engr_txt[actual_text]++; + if (!ep->engr_txt[actual_text][0]) del_engr(ep); } } } +/* + * Returns: + * non-zero if it can be felt + */ +boolean +engr_can_be_felt(struct engr *ep) +{ + boolean canfeel = FALSE; + + switch (ep->engr_type) { + case ENGRAVE: + case HEADSTONE: + case BURN: + canfeel = TRUE; + break; + case DUST: + case MARK: + case ENGR_BLOOD: + default: + canfeel = FALSE; + break; + } + return canfeel; +} + void -read_engr_at(x, y) -int x, y; +read_engr_at(coordxy x, coordxy y) { - register struct engr *ep = engr_at(x, y); + struct engr *ep = engr_at(x, y); + const char *eloc = surface(x, y); int sensed = 0; - /* Sensing an engraving does not require sight, + /* Sensing an engraving does not require sight for some engraving types, * nor does it necessarily imply comprehension (literacy). */ - if (ep && ep->engr_txt[0]) { + if (ep && ep->engr_txt[actual_text][0]) { switch (ep->engr_type) { case DUST: if (!Blind) { @@ -333,21 +337,20 @@ int x, y; case HEADSTONE: if (!Blind || can_reach_floor(TRUE)) { sensed = 1; - pline("%s is engraved here on the %s.", Something, - surface(x, y)); + pline("%s is engraved here on the %s.", Something, eloc); } break; case BURN: if (!Blind || can_reach_floor(TRUE)) { sensed = 1; pline("Some text has been %s into the %s here.", - is_ice(x, y) ? "melted" : "burned", surface(x, y)); + is_ice(x, y) ? "melted" : "burned", eloc); } break; case MARK: if (!Blind) { sensed = 1; - pline("There's some graffiti on the %s here.", surface(x, y)); + pline("There's some graffiti on the %s here.", eloc); } break; case ENGR_BLOOD: @@ -367,58 +370,97 @@ int x, y; if (sensed) { char *et, buf[BUFSZ]; + const char *endpunct; int maxelen = (int) (sizeof buf /* sizeof "literal" counts terminating \0 */ - - sizeof "You feel the words: \"\"."); + - sizeof "You feel the words: \"\"."), + elen = (int) strlen(ep->engr_txt[actual_text]), + off = (int) (ep->engr_txt[actual_text] - engr_text_space(ep)); - if ((int) strlen(ep->engr_txt) > maxelen) { - (void) strncpy(buf, ep->engr_txt, maxelen); + if (elen > maxelen) { + (void) strncpy(buf, ep->engr_txt[actual_text], maxelen); buf[maxelen] = '\0'; et = buf; + elen = maxelen; } else { - et = ep->engr_txt; + et = ep->engr_txt[actual_text]; + } + endpunct = ""; + if (elen < 2 + /* only skip if punctuation is original, not degraded char */ + || !((ep->engr_txt[pristine_text][off + elen - 1] + == et[elen - 1]) + && strchr(".!?", et[elen - 1]))) { + endpunct = "."; } - You("%s: \"%s\".", (Blind) ? "feel the words" : "read", et); - if (context.run > 0) + You("%s: \"%s\"%s", (Blind) ? "feel the words" : "read", et, + endpunct); + Strcpy(ep->engr_txt[remembered_text], ep->engr_txt[actual_text]); + ep->eread = 1; + ep->erevealed = 1; + if (svc.context.run > 0) nomul(0); } } } void -make_engr_at(x, y, s, e_time, e_type) -int x, y; -const char *s; -long e_time; -xchar e_type; +make_engr_at( + coordxy x, coordxy y, + const char *s, + const char *pristine_s, + long e_time, + int e_type) { + int i; struct engr *ep; - unsigned smem = strlen(s) + 1; - + unsigned smem = Strlen(s) + 1; + boolean havepristine = FALSE; + + if (pristine_s != NULL) { + unsigned prmem = Strlen(pristine_s) + 1; + if (prmem > smem) + smem = prmem; + havepristine = TRUE; + } if ((ep = engr_at(x, y)) != 0) del_engr(ep); - ep = newengr(smem); - (void) memset((genericptr_t)ep, 0, smem + sizeof(struct engr)); + + ep = newengr(smem * 3); + (void) memset((genericptr_t) ep, 0, (smem * 3) + sizeof (struct engr)); ep->nxt_engr = head_engr; head_engr = ep; ep->engr_x = x; ep->engr_y = y; - ep->engr_txt = (char *) (ep + 1); - Strcpy(ep->engr_txt, s); - /* engraving Elbereth shows wisdom */ - if (!in_mklev && !strcmp(s, "Elbereth")) - exercise(A_WIS, TRUE); + ep->engr_txt[actual_text] = engr_text_space(ep); + ep->engr_txt[remembered_text] = ep->engr_txt[actual_text] + smem; + ep->engr_txt[pristine_text] = ep->engr_txt[remembered_text] + smem; + for(i = 0; i < text_states; ++i) + Strcpy(ep->engr_txt[i], s); + if (havepristine) + Strcpy(ep->engr_txt[pristine_text], pristine_s); + if (!strcmp(s, "Elbereth")) { + /* engraving "Elbereth": if done when making a level, it creates + an old-style Elbereth that deters monsters when any objects are + present; otherwise (done by the player), exercises wisdom */ + if (gi.in_mklev) + ep->guardobjects = 1; + else + exercise(A_WIS, TRUE); + } ep->engr_time = e_time; - ep->engr_type = e_type > 0 ? e_type : rnd(N_ENGRAVE - 1); - ep->engr_lth = smem; + ep->engr_type = (xint8) ((e_type > 0) ? e_type : rnd(N_ENGRAVE - 1)); + ep->engr_szeach = smem; + ep->engr_alloc = smem * 3; + /* we do not set ep->eread or ep->erevealed; + * the caller will need to if required */ } /* delete any engraving at location */ void -del_engr_at(x, y) -int x, y; +del_engr_at(coordxy x, coordxy y) { - register struct engr *ep = engr_at(x, y); + struct engr *ep = engr_at(x, y); if (ep) del_engr(ep); @@ -428,171 +470,278 @@ int x, y; * freehand - returns true if player has a free hand */ int -freehand() +freehand(void) { return (!uwep || !welded(uwep) || (!bimanual(uwep) && (!uarms || !uarms->cursed))); } -static NEARDATA const char styluses[] = { ALL_CLASSES, ALLOW_NONE, - TOOL_CLASS, WEAPON_CLASS, - WAND_CLASS, GEM_CLASS, - RING_CLASS, 0 }; - -/* Mohs' Hardness Scale: - * 1 - Talc 6 - Orthoclase - * 2 - Gypsum 7 - Quartz - * 3 - Calcite 8 - Topaz - * 4 - Fluorite 9 - Corundum - * 5 - Apatite 10 - Diamond - * - * Since granite is an igneous rock hardness ~ 7, anything >= 8 should - * probably be able to scratch the rock. - * Devaluation of less hard gems is not easily possible because obj struct - * does not contain individual oc_cost currently. 7/91 - * - * steel - 5-8.5 (usu. weapon) - * diamond - 10 * jade - 5-6 (nephrite) - * ruby - 9 (corundum) * turquoise - 5-6 - * sapphire - 9 (corundum) * opal - 5-6 - * topaz - 8 * glass - ~5.5 - * emerald - 7.5-8 (beryl) * dilithium - 4-5?? - * aquamarine - 7.5-8 (beryl) * iron - 4-5 - * garnet - 7.25 (var. 6.5-8) * fluorite - 4 - * agate - 7 (quartz) * brass - 3-4 - * amethyst - 7 (quartz) * gold - 2.5-3 - * jasper - 7 (quartz) * silver - 2.5-3 - * onyx - 7 (quartz) * copper - 2.5-3 - * moonstone - 6 (orthoclase) * amber - 2-2.5 - */ - -/* return 1 if action took 1 (or more) moves, 0 if error or aborted */ -int -doengrave() +/* getobj callback for an object to engrave with */ +staticfn int +stylus_ok(struct obj *obj) { - 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 */ - 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 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/...) */ - char *sp; /* Place holder for space count of engr text */ - int len; /* # of nonspace chars of new engraving text */ - int maxelen; /* Max allowable length of engraving text */ - struct engr *oep = engr_at(u.ux, u.uy); - /* The current engraving */ - struct obj *otmp; /* Object selected with which to engrave */ - char *writer; - - multi = 0; /* moves consumed */ - nomovemsg = (char *) 0; /* occupation end message */ - - buf[0] = (char) 0; - ebuf[0] = (char) 0; - post_engr_text[0] = (char) 0; - maxelen = BUFSZ - 1; - if (oep) - oetype = oep->engr_type; - if (is_demon(youmonst.data) || youmonst.data->mlet == S_VAMPIRE) - type = ENGR_BLOOD; + if (!obj) + return GETOBJ_SUGGEST; + + /* Potential extension: exclude weapons that don't make any sense (such as + * bullwhips) and downplay rings and gems that wouldn't be good to write + * with (such as glass and non-gem rings) */ + if (obj->oclass == WEAPON_CLASS || obj->oclass == WAND_CLASS + || obj->oclass == GEM_CLASS || obj->oclass == RING_CLASS) + return GETOBJ_SUGGEST; + + /* Only markers and towels are recommended tools. */ + if (obj->oclass == TOOL_CLASS + && (obj->otyp == TOWEL || obj->otyp == MAGIC_MARKER)) + return GETOBJ_SUGGEST; + + return GETOBJ_DOWNPLAY; +} - /* Can the adventurer engrave at all? */ +/* can hero engrave at all (at their location)? */ +staticfn boolean +u_can_engrave(void) +{ + int levtyp = SURFACE_AT(u.ux, u.uy); if (u.uswallow) { if (is_animal(u.ustuck->data)) { pline("What would you write? \"Jonah was here\"?"); - return 0; + return FALSE; } else if (is_whirly(u.ustuck->data)) { - cant_reach_floor(u.ux, u.uy, FALSE, FALSE); - return 0; - } else - jello = TRUE; + cant_reach_floor(u.ux, u.uy, FALSE, FALSE, FALSE); + return FALSE; + } + /* Note: for amorphous engulfers, writing attempt is allowed here + but yields the 'jello' result in doengrave() */ } else if (is_lava(u.ux, u.uy)) { You_cant("write on the %s!", surface(u.ux, u.uy)); - return 0; - } else if (is_pool(u.ux, u.uy) || IS_FOUNTAIN(levl[u.ux][u.uy].typ)) { + return FALSE; + } else if (is_pool(u.ux, u.uy) || IS_FOUNTAIN(levtyp)) { You_cant("write on the %s!", surface(u.ux, u.uy)); - return 0; - } - if (Is_airlevel(&u.uz) || Is_waterlevel(&u.uz) /* in bubble */) { - You_cant("write in thin air!"); - return 0; - } else if (!accessible(u.ux, u.uy)) { + return FALSE; + } else if (IS_AIR(levtyp)) { + /* airlevel or inside bubble on waterlevel */ + You_cant("write in %s!", + (levtyp == CLOUD) ? "cloud vapor" : "thin air"); + return FALSE; + } else if (!ACCESSIBLE(levtyp)) { /* stone, tree, wall, secret corridor, pool, lava, bars */ You_cant("write here."); - return 0; + return FALSE; } - if (cantwield(youmonst.data)) { + + if (cantwield(gy.youmonst.data)) { You_cant("even hold anything!"); - return 0; + return FALSE; } if (check_capacity((char *) 0)) - return 0; - - /* One may write with finger, or weapon, or wand, or..., or... - * Edited by GAN 10/20/86 so as not to change weapon wielded. - */ - - otmp = getobj(styluses, "write with"); - if (!otmp) /* otmp == zeroobj if fingers */ - return 0; - - if (otmp == &zeroobj) { - Strcat(strcpy(fbuf, "your "), body_part(FINGERTIP)); - writer = fbuf; - } else - writer = yname(otmp); + return FALSE; + return TRUE; +} - /* There's no reason you should be able to write with a wand - * while both your hands are tied up. - */ - if (!freehand() && otmp != uwep && !otmp->owornmask) { - You("have no free %s to write with!", body_part(HAND)); - return 0; - } +/* initialize the doengrave data */ +staticfn void +doengrave_ctx_init(struct _doengrave_ctx *de) +{ + de->dengr = FALSE; + de->doblind = FALSE; + de->doknown = FALSE; + de->eow = FALSE; + de->ptext = TRUE; + de->teleengr = FALSE; + de->zapwand = FALSE; + de->disprefresh = FALSE; + de->adding = FALSE; + + de->ret = ECMD_OK; + de->type = DUST; + de->oetype = 0; + + de->otmp = (struct obj *) 0; + de->oep = engr_at(u.ux, u.uy); + + de->buf[0] = (char) 0; + de->ebuf[0] = (char) 0; + de->fbuf[0] = (char) 0; + de->qbuf[0] = (char) 0; + de->post_engr_text[0] = (char) 0; + de->writer = (char *) 0; + + if (de->oep) + de->oetype = de->oep->engr_type; + if (is_demon(gy.youmonst.data) || is_vampire(gy.youmonst.data)) + de->type = ENGR_BLOOD; + + de->jello = (u.uswallow && !(is_animal(u.ustuck->data) + || is_whirly(u.ustuck->data))); + de->frosted = is_ice(u.ux, u.uy); +} - if (jello) { - You("tickle %s with %s.", mon_nam(u.ustuck), writer); - Your("message dissolves..."); - return 0; - } - if (otmp->oclass != WAND_CLASS && !can_reach_floor(TRUE)) { - cant_reach_floor(u.ux, u.uy, FALSE, TRUE); - return 0; - } - if (IS_ALTAR(levl[u.ux][u.uy].typ)) { - You("make a motion towards the altar with %s.", writer); - altar_wrath(u.ux, u.uy); - return 0; - } - if (IS_GRAVE(levl[u.ux][u.uy].typ)) { - if (otmp == &zeroobj) { /* using only finger */ - You("would only make a small smudge on the %s.", - surface(u.ux, u.uy)); - return 0; - } else if (!levl[u.ux][u.uy].disturbed) { - You("disturb the undead!"); - levl[u.ux][u.uy].disturbed = 1; - (void) makemon(&mons[PM_GHOUL], u.ux, u.uy, NO_MM_FLAGS); - exercise(A_WIS, FALSE); - return 1; +/* special engraving effects for WAND objects */ +staticfn void +doengrave_sfx_item_WAN(struct _doengrave_ctx *de) +{ + switch (de->otmp->otyp) { + /* DUST wands */ + default: + break; + /* NODIR wands */ + case WAN_LIGHT: + case WAN_SECRET_DOOR_DETECTION: + case WAN_STASIS: + case WAN_CREATE_MONSTER: + case WAN_WISHING: + case WAN_ENLIGHTENMENT: + zapnodir(de->otmp); + break; + /* IMMEDIATE wands */ + /* If wand is "IMMEDIATE", remember to affect the + * previous engraving even if turning to dust. + */ + case WAN_STRIKING: + Strcpy(de->post_engr_text, + "The wand unsuccessfully fights your attempt to write!"); + break; + case WAN_SLOW_MONSTER: + if (!Blind) { + Sprintf(de->post_engr_text, "The bugs on the %s slow down!", + surface(u.ux, u.uy)); + } + break; + case WAN_SPEED_MONSTER: + if (!Blind) { + Sprintf(de->post_engr_text, "The bugs on the %s speed up!", + surface(u.ux, u.uy)); + } + break; + case WAN_POLYMORPH: + if (de->oep) { + if (!Blind) { + de->type = (xint16) 0; /* random */ + (void) random_engraving(de->buf, de->ebuf); + } else { + /* keep the same type so that feels don't + change and only the text is altered, + but you won't know anyway because + you're a _blind writer_ */ + if (de->oetype) + de->type = de->oetype; + xcrypt(blengr(), de->buf); + } + de->dengr = TRUE; + } + break; + case WAN_NOTHING: + case WAN_UNDEAD_TURNING: + case WAN_OPENING: + case WAN_LOCKING: + case WAN_PROBING: + break; + /* RAY wands */ + case WAN_MAGIC_MISSILE: + de->ptext = TRUE; + if (!Blind) { + Sprintf(de->post_engr_text, + "The %s is riddled by bullet holes!", + surface(u.ux, u.uy)); + } + break; + /* can't tell sleep from death - Eric Backus */ + case WAN_SLEEP: + case WAN_DEATH: + if (!Blind) { + Sprintf(de->post_engr_text, "The bugs on the %s stop moving!", + surface(u.ux, u.uy)); + } + break; + case WAN_COLD: + if (!Blind) + Strcpy(de->post_engr_text, + "A few ice cubes drop from the wand."); + if (!de->oep || (de->oep->engr_type != BURN)) + break; + FALLTHROUGH; + /*FALLTHRU*/ + case WAN_CANCELLATION: + case WAN_MAKE_INVISIBLE: + if (de->oep && de->oep->engr_type != HEADSTONE) { + if (!Blind) + pline_The("engraving on the %s vanishes!", + surface(u.ux, u.uy)); + de->dengr = TRUE; } + break; + case WAN_TELEPORTATION: + if (de->oep && de->oep->engr_type != HEADSTONE) { + if (!Blind) + pline_The("engraving on the %s vanishes!", + surface(u.ux, u.uy)); + de->teleengr = TRUE; + } + break; + /* type = ENGRAVE wands */ + case WAN_DIGGING: + de->ptext = TRUE; + de->type = ENGRAVE; + if (!objects[de->otmp->otyp].oc_name_known) { + if (flags.verbose) + pline("This %s is a wand of digging!", xname(de->otmp)); + de->doknown = TRUE; + } + Strcpy(de->post_engr_text, + (Blind && !Deaf) + ? "You hear drilling!" /* Deaf-aware */ + : Blind + ? "You feel tremors." + : IS_GRAVE(levl[u.ux][u.uy].typ) + ? "Chips fly out from the headstone." + : de->frosted + ? "Ice chips fly up from the ice surface!" + : (svl.level.locations[u.ux][u.uy].typ + == DRAWBRIDGE_DOWN) + ? "Splinters fly up from the bridge." + : "Gravel flies up from the floor."); + break; + /* type = BURN wands */ + case WAN_FIRE: + de->ptext = TRUE; + de->type = BURN; + if (!objects[de->otmp->otyp].oc_name_known) { + if (flags.verbose) + pline("This %s is a wand of fire!", xname(de->otmp)); + de->doknown = TRUE; + } + Strcpy(de->post_engr_text, Blind ? "You feel the wand heat up." + : "Flames fly from the wand."); + break; + case WAN_LIGHTNING: + de->ptext = TRUE; + de->type = BURN; + if (!objects[de->otmp->otyp].oc_name_known) { + if (flags.verbose) + pline("This %s is a wand of lightning!", xname(de->otmp)); + de->doknown = TRUE; + } + if (!Blind) { + Strcpy(de->post_engr_text, "Lightning arcs from the wand."); + de->doblind = TRUE; + } else { + Strcpy(de->post_engr_text, !Deaf + ? "You hear crackling!" /* Deaf-aware */ + : "Your hair stands up!"); + } + break; + /* type = MARK wands */ + /* type = ENGR_BLOOD wands */ } +} - /* SPFX for items */ - - switch (otmp->oclass) { +/* special engraving effects for all objects */ +staticfn boolean +doengrave_sfx_item(struct _doengrave_ctx *de) +{ + switch (de->otmp->oclass) { default: case AMULET_CLASS: case CHAIN_CLASS: @@ -603,30 +752,31 @@ doengrave() /* "diamond" rings and others should work */ case GEM_CLASS: /* diamonds & other hard gems should work */ - if (objects[otmp->otyp].oc_tough) { - type = ENGRAVE; + if (objects[de->otmp->otyp].oc_tough) { + de->type = ENGRAVE; break; } break; case ARMOR_CLASS: - if (is_boots(otmp)) { - type = DUST; + if (is_boots(de->otmp)) { + de->type = DUST; break; } + FALLTHROUGH; /*FALLTHRU*/ /* Objects too large to engrave with */ case BALL_CLASS: case ROCK_CLASS: You_cant("engrave with such a large object!"); - ptext = FALSE; + de->ptext = FALSE; break; /* Objects too silly to engrave with */ case FOOD_CLASS: case SCROLL_CLASS: case SPBOOK_CLASS: - pline("%s would get %s.", Yname2(otmp), - is_ice(u.ux, u.uy) ? "all frosty" : "too dirty"); - ptext = FALSE; + pline("%s would get %s.", Yname2(de->otmp), + de->frosted ? "all frosty" : "too dirty"); + de->ptext = FALSE; break; case RANDOM_CLASS: /* This should mean fingers */ break; @@ -639,176 +789,26 @@ doengrave() * therefore will know they are using a charge. */ case WAND_CLASS: - if (zappable(otmp)) { - check_unpaid(otmp); - if (otmp->cursed && !rn2(WAND_BACKFIRE_CHANCE)) { - wand_explode(otmp, 0); - return 1; + if (zappable(de->otmp)) { + check_unpaid(de->otmp); + if (de->otmp->cursed && !rn2(WAND_BACKFIRE_CHANCE)) { + wand_explode(de->otmp, 0); + de->ret = ECMD_TIME; + return FALSE; } - zapwand = TRUE; + de->zapwand = TRUE; if (!can_reach_floor(TRUE)) - ptext = FALSE; - - switch (otmp->otyp) { - /* DUST wands */ - default: - break; - /* NODIR wands */ - case WAN_LIGHT: - case WAN_SECRET_DOOR_DETECTION: - case WAN_CREATE_MONSTER: - case WAN_WISHING: - case WAN_ENLIGHTENMENT: - zapnodir(otmp); - break; - /* IMMEDIATE wands */ - /* If wand is "IMMEDIATE", remember to affect the - * previous engraving even if turning to dust. - */ - case WAN_STRIKING: - Strcpy(post_engr_text, - "The wand unsuccessfully fights your attempt to write!"); - break; - case WAN_SLOW_MONSTER: - if (!Blind) { - Sprintf(post_engr_text, "The bugs on the %s slow down!", - surface(u.ux, u.uy)); - } - break; - case WAN_SPEED_MONSTER: - if (!Blind) { - Sprintf(post_engr_text, "The bugs on the %s speed up!", - surface(u.ux, u.uy)); - } - break; - case WAN_POLYMORPH: - if (oep) { - if (!Blind) { - type = (xchar) 0; /* random */ - (void) random_engraving(buf); - } else { - /* keep the same type so that feels don't - change and only the text is altered, - but you won't know anyway because - you're a _blind writer_ */ - if (oetype) - type = oetype; - xcrypt(blengr(), buf); - } - dengr = TRUE; - } - break; - case WAN_NOTHING: - case WAN_UNDEAD_TURNING: - case WAN_OPENING: - case WAN_LOCKING: - case WAN_PROBING: - break; - /* RAY wands */ - case WAN_MAGIC_MISSILE: - ptext = TRUE; - if (!Blind) { - Sprintf(post_engr_text, - "The %s is riddled by bullet holes!", - surface(u.ux, u.uy)); - } - break; - /* can't tell sleep from death - Eric Backus */ - case WAN_SLEEP: - case WAN_DEATH: - if (!Blind) { - Sprintf(post_engr_text, "The bugs on the %s stop moving!", - surface(u.ux, u.uy)); - } - break; - case WAN_COLD: - if (!Blind) - Strcpy(post_engr_text, - "A few ice cubes drop from the wand."); - if (!oep || (oep->engr_type != BURN)) - break; - /*FALLTHRU*/ - case WAN_CANCELLATION: - case WAN_MAKE_INVISIBLE: - if (oep && oep->engr_type != HEADSTONE) { - if (!Blind) - pline_The("engraving on the %s vanishes!", - surface(u.ux, u.uy)); - dengr = TRUE; - } - break; - case WAN_TELEPORTATION: - if (oep && oep->engr_type != HEADSTONE) { - if (!Blind) - pline_The("engraving on the %s vanishes!", - surface(u.ux, u.uy)); - teleengr = TRUE; - } - break; - /* type = ENGRAVE wands */ - case WAN_DIGGING: - ptext = TRUE; - type = ENGRAVE; - if (!objects[otmp->otyp].oc_name_known) { - if (flags.verbose) - pline("This %s is a wand of digging!", xname(otmp)); - doknown = TRUE; - } - Strcpy(post_engr_text, - (Blind && !Deaf) - ? "You hear drilling!" /* Deaf-aware */ - : Blind - ? "You feel tremors." - : IS_GRAVE(levl[u.ux][u.uy].typ) - ? "Chips fly out from the headstone." - : is_ice(u.ux, u.uy) - ? "Ice chips fly up from the ice surface!" - : (level.locations[u.ux][u.uy].typ - == DRAWBRIDGE_DOWN) - ? "Splinters fly up from the bridge." - : "Gravel flies up from the floor."); - break; - /* type = BURN wands */ - case WAN_FIRE: - ptext = TRUE; - type = BURN; - if (!objects[otmp->otyp].oc_name_known) { - if (flags.verbose) - pline("This %s is a wand of fire!", xname(otmp)); - doknown = TRUE; - } - Strcpy(post_engr_text, Blind ? "You feel the wand heat up." - : "Flames fly from the wand."); - break; - case WAN_LIGHTNING: - ptext = TRUE; - type = BURN; - if (!objects[otmp->otyp].oc_name_known) { - if (flags.verbose) - pline("This %s is a wand of lightning!", xname(otmp)); - doknown = TRUE; - } - if (!Blind) { - Strcpy(post_engr_text, "Lightning arcs from the wand."); - doblind = TRUE; - } else - Strcpy(post_engr_text, !Deaf - ? "You hear crackling!" /* Deaf-aware */ - : "Your hair stands up!"); - break; - - /* type = MARK wands */ - /* type = ENGR_BLOOD wands */ - } + de->ptext = FALSE; + doengrave_sfx_item_WAN(de); } else { /* end if zappable */ /* failing to wrest one last charge takes time */ - ptext = FALSE; /* use "early exit" below, return 1 */ + de->ptext = FALSE; /* use "early exit" below, return 1 */ /* give feedback here if we won't be getting the "can't reach floor" message below */ if (can_reach_floor(TRUE)) { /* cancelled wand turns to dust */ - if (otmp->spe < 0) - zapwand = TRUE; + if (de->otmp->spe < 0) + de->zapwand = TRUE; /* empty wand just doesn't write */ else pline_The("wand is too worn out to engrave."); @@ -817,47 +817,59 @@ doengrave() break; case WEAPON_CLASS: - if (is_blade(otmp)) { - if ((int) otmp->spe > -3) - type = ENGRAVE; + if (is_art(de->otmp, ART_FIRE_BRAND)) { + de->type = BURN; /* doesn't dull weapon */ + } else if (is_blade(de->otmp)) { + /* if non-blade or welded or too dull, engraving type stays set + to DUST; feedback for that is only given for bladed weapons */ + if (welded(de->otmp)) + pline("%s can only scratch the %s.", + Yname2(de->otmp), surface(u.ux, u.uy)); + else if ((int) de->otmp->spe <= -3) + pline("%s too dull for engraving.", + Yobjnam2(de->otmp, "are")); else - pline("%s too dull for engraving.", Yobjnam2(otmp, "are")); + de->type = ENGRAVE; } break; case TOOL_CLASS: - if (otmp == ublindf) { + if (de->otmp == ublindf) { pline( "That is a bit difficult to engrave with, don't you think?"); - return 0; + de->ret = ECMD_FAIL; + return FALSE; } - switch (otmp->otyp) { + switch (de->otmp->otyp) { case MAGIC_MARKER: - if (otmp->spe <= 0) + if (de->otmp->spe <= 0) Your("marker has dried out."); else - type = MARK; + de->type = MARK; break; case TOWEL: /* Can't really engrave with a towel */ - ptext = FALSE; - if (oep) - if (oep->engr_type == DUST - || oep->engr_type == ENGR_BLOOD - || oep->engr_type == MARK) { - if (is_wet_towel(otmp)) - dry_a_towel(otmp, -1, TRUE); + de->ptext = FALSE; + if (de->oep) { + if (de->oep->engr_type == DUST + || de->oep->engr_type == ENGR_BLOOD + || de->oep->engr_type == MARK) { + if (is_wet_towel(de->otmp)) + dry_a_towel(de->otmp, -1, TRUE); if (!Blind) You("wipe out the message here."); else - pline("%s %s.", Yobjnam2(otmp, "get"), - is_ice(u.ux, u.uy) ? "frosty" : "dusty"); - dengr = TRUE; - } else - pline("%s can't wipe out this engraving.", Yname2(otmp)); - else - pline("%s %s.", Yobjnam2(otmp, "get"), - is_ice(u.ux, u.uy) ? "frosty" : "dusty"); + pline("%s %s.", Yobjnam2(de->otmp, "get"), + de->frosted ? "frosty" : "dusty"); + de->dengr = TRUE; + } else { + pline("%s can't wipe out this engraving.", + Yname2(de->otmp)); + } + } else { + pline("%s %s.", Yobjnam2(de->otmp, "get"), + de->frosted ? "frosty" : "dusty"); + } break; default: break; @@ -865,25 +877,172 @@ doengrave() break; case VENOM_CLASS: - if (wizard) { - pline("Writing a poison pen letter??"); - break; - } - /*FALLTHRU*/ + /* this used to be ``if (wizard)'' and fall through to ILLOBJ_CLASS + for normal play, but splash of venom isn't "illegal" because it + could occur in normal play via wizard mode bones */ + pline("Writing a poison pen letter?"); + break; + case ILLOBJ_CLASS: impossible("You're engraving with an illegal object!"); break; } + return TRUE; +} + +/* which verb phrasing to use for engraving */ +staticfn void +doengrave_ctx_verb(struct _doengrave_ctx *de) +{ + switch (de->type) { + default: + de->everb = de->adding ? "add to the weird writing on" + : "write strangely on"; + break; + case DUST: + de->everb = de->adding ? "add to the writing in" : "write in"; + de->eloc = de->frosted ? "frost" : "dust"; + break; + case HEADSTONE: + de->everb = de->adding ? "add to the epitaph on" : "engrave on"; + break; + case ENGRAVE: + de->everb = de->adding ? "add to the engraving in" : "engrave in"; + break; + case BURN: + de->everb = de->adding ? (de->frosted ? "add to the text melted into" + : "add to the text burned into") + : (de->frosted ? "melt into" : "burn into"); + break; + case MARK: + de->everb = de->adding ? "add to the graffiti on" : "scribble on"; + break; + case ENGR_BLOOD: + de->everb = de->adding ? "add to the scrawl on" : "scrawl on"; + break; + } +} + +/* Mohs' Hardness Scale: + * 1 - Talc 6 - Orthoclase + * 2 - Gypsum 7 - Quartz + * 3 - Calcite 8 - Topaz + * 4 - Fluorite 9 - Corundum + * 5 - Apatite 10 - Diamond + * + * Since granite is an igneous rock hardness ~ 7, anything >= 8 should + * probably be able to scratch the rock. + * Devaluation of less hard gems is not easily possible because obj struct + * does not contain individual oc_cost currently. 7/91 + * + * steel - 5-8.5 (usu. weapon) + * diamond - 10 * jade - 5-6 (nephrite) + * ruby - 9 (corundum) * turquoise - 5-6 + * sapphire - 9 (corundum) * opal - 5-6 + * topaz - 8 * glass - ~5.5 + * emerald - 7.5-8 (beryl) * dilithium - 4-5?? + * aquamarine - 7.5-8 (beryl) * iron - 4-5 + * garnet - 7.25 (var. 6.5-8) * fluorite - 4 + * agate - 7 (quartz) * brass - 3-4 + * amethyst - 7 (quartz) * gold - 2.5-3 + * jasper - 7 (quartz) * silver - 2.5-3 + * onyx - 7 (quartz) * copper - 2.5-3 + * moonstone - 6 (orthoclase) * amber - 2-2.5 + */ + +/* the #engrave command */ +int +doengrave(void) +{ + char *sp; /* Place holder for space count of engr text */ + struct _doengrave_ctx *de; + int retval; + boolean initial_msg_given = FALSE; + + /* Can the adventurer engrave at all? */ + if (!u_can_engrave()) + return ECMD_FAIL; + + de = (struct _doengrave_ctx *) alloc(sizeof (struct _doengrave_ctx)); + doengrave_ctx_init(de); + + gm.multi = 0; /* moves consumed */ + gn.nomovemsg = (char *) 0; /* occupation end message */ + + /* One may write with finger, or weapon, or wand, or..., or... + * Edited by GAN 10/20/86 so as not to change weapon wielded. + */ + + de->otmp = getobj("write with", stylus_ok, GETOBJ_PROMPT); + if (!de->otmp) {/* otmp == &hands_obj if fingers */ + de->ret = ECMD_CANCEL; + goto doengr_exit; + } + + if (de->otmp == &hands_obj) { + Strcat(strcpy(de->fbuf, "your "), body_part(FINGERTIP)); + de->writer = de->fbuf; + } else { + de->writer = yname(de->otmp); + } + + /* There's no reason you should be able to write with a wand + * while both your hands are tied up. + */ + if (!freehand() && de->otmp != uwep && !de->otmp->owornmask) { + You("have no free %s to write with!", body_part(HAND)); + goto doengr_exit; + } + + if (de->jello) { + You("tickle %s with %s.", mon_nam(u.ustuck), de->writer); + Your("message dissolves..."); + goto doengr_exit; + } + if (!can_reach_floor(TRUE)) { + if (de->otmp->oclass != WAND_CLASS) { + cant_reach_floor(u.ux, u.uy, FALSE, TRUE, FALSE); + goto doengr_exit; + } else { + You("gesture, with your wand, towards the %s below you.", + surface(u.ux, u.uy)); + initial_msg_given = TRUE; + } + } + if (IS_ALTAR(levl[u.ux][u.uy].typ)) { + if (!initial_msg_given) + You("make a motion towards the altar with %s.", de->writer); + altar_wrath(u.ux, u.uy); + goto doengr_exit; + } + if (IS_GRAVE(levl[u.ux][u.uy].typ)) { + if (de->otmp == &hands_obj) { /* using only finger */ + You("would only make a small smudge on the %s.", + surface(u.ux, u.uy)); + goto doengr_exit; + } else if (!levl[u.ux][u.uy].disturbed) { + /* disturb the grave: summon a ghoul, same as sometimes + happens when kicking; sets levl[ux][uy]->disturbed so + that it'll only happen once */ + disturb_grave(u.ux, u.uy); + goto doengr_exit; + } + } + + /* SPFX for items */ + if (!doengrave_sfx_item(de)) + goto doengr_exit; + if (IS_GRAVE(levl[u.ux][u.uy].typ)) { - if (type == ENGRAVE || type == 0) { - type = HEADSTONE; + if (de->type == ENGRAVE || de->type == 0) { + de->type = HEADSTONE; } else { /* ensures the "cannot wipe out" case */ - type = DUST; - dengr = FALSE; - teleengr = FALSE; - buf[0] = '\0'; + de->type = DUST; + de->dengr = FALSE; + de->teleengr = FALSE; + de->buf[0] = '\0'; } } @@ -892,167 +1051,176 @@ doengrave() */ /* Identify stylus */ - if (doknown) { - learnwand(otmp); - if (objects[otmp->otyp].oc_name_known) + if (de->doknown) { + learnwand(de->otmp); + if (objects[de->otmp->otyp].oc_name_known) more_experienced(0, 10); } - if (teleengr) { - rloc_engr(oep); - oep = (struct engr *) 0; + if (de->teleengr) { + rloc_engr(de->oep); + de->oep->eread = 0; + de->oep->erevealed = 0; + de->disprefresh = TRUE; + de->oep = (struct engr *) 0; } - if (dengr) { - del_engr(oep); - oep = (struct engr *) 0; + if (de->dengr) { + del_engr(de->oep); + de->oep = (struct engr *) 0; + de->disprefresh = TRUE; } /* Something has changed the engraving here */ - if (*buf) { - make_engr_at(u.ux, u.uy, buf, moves, type); - if (!Blind) - pline_The("engraving now reads: \"%s\".", buf); - ptext = FALSE; + if (*de->buf) { + struct engr *tmp_ep; + + make_engr_at(u.ux, u.uy, de->buf, de->ebuf, svm.moves, de->type); + tmp_ep = engr_at(u.ux, u.uy); + if (!Blind) { + if (tmp_ep != 0) { + pline_The("engraving now reads: \"%s\".", de->buf); + tmp_ep->eread = 1; + tmp_ep->erevealed = 1; + de->disprefresh = TRUE; + } + } + de->ptext = FALSE; } - if (zapwand && (otmp->spe < 0)) { - pline("%s %sturns to dust.", The(xname(otmp)), + if (de->zapwand && (de->otmp->spe < 0)) { + pline("%s %sturns to dust.", The(xname(de->otmp)), Blind ? "" : "glows violently, then "); if (!IS_GRAVE(levl[u.ux][u.uy].typ)) You( "are not going to get anywhere trying to write in the %s with your dust.", - is_ice(u.ux, u.uy) ? "frost" : "dust"); - useup(otmp); - otmp = 0; /* wand is now gone */ - ptext = FALSE; + de->frosted ? "frost" : "dust"); + useup(de->otmp); + de->otmp = 0; /* wand is now gone */ + de->ptext = FALSE; } /* Early exit for some implements. */ - if (!ptext) { - if (otmp && otmp->oclass == WAND_CLASS && !can_reach_floor(TRUE)) - cant_reach_floor(u.ux, u.uy, FALSE, TRUE); - return 1; + if (!de->ptext) { + if (de->otmp && de->otmp->oclass == WAND_CLASS + && !can_reach_floor(TRUE)) + cant_reach_floor(u.ux, u.uy, FALSE, TRUE, TRUE); + de->ret = ECMD_TIME; + goto doengr_exit; } /* * Special effects should have deleted the current engraving (if * possible) by now. */ - if (oep) { - register char c = 'n'; + if (de->oep) { + char c = 'n'; /* Give player the choice to add to engraving. */ - if (type == HEADSTONE) { + if (de->type == HEADSTONE) { /* no choice, only append */ c = 'y'; - } else if (type == oep->engr_type - && (!Blind || oep->engr_type == BURN - || oep->engr_type == ENGRAVE)) { + } else if (de->type == de->oep->engr_type + && (!Blind || de->oep->engr_type == BURN + || de->oep->engr_type == ENGRAVE)) { c = yn_function("Do you want to add to the current engraving?", - ynqchars, 'y'); + ynqchars, 'y', TRUE); if (c == 'q') { pline1(Never_mind); - return 0; + goto doengr_exit; } } if (c == 'n' || Blind) { - if (oep->engr_type == DUST - || oep->engr_type == ENGR_BLOOD - || oep->engr_type == MARK) { + if (de->oep->engr_type == DUST + || de->oep->engr_type == ENGR_BLOOD + || de->oep->engr_type == MARK) { if (!Blind) { You("wipe out the message that was %s here.", - (oep->engr_type == DUST) - ? "written in the dust" - : (oep->engr_type == ENGR_BLOOD) + (de->oep->engr_type == DUST) + ? (de->frosted + ? "written in the frost" + : "written in the dust") + : (de->oep->engr_type == ENGR_BLOOD) ? "scrawled in blood" : "written"); - del_engr(oep); - oep = (struct engr *) 0; - } else - /* Don't delete engr until after we *know* we're engraving - */ - eow = TRUE; - } else if (type == DUST || type == MARK || type == ENGR_BLOOD) { + del_engr(de->oep); + de->oep = (struct engr *) 0; + de->disprefresh = TRUE; + } else { + /* defer deletion until after we *know* we're engraving */ + de->eow = TRUE; + } + } else if (de->type == DUST || de->type == MARK + || de->type == ENGR_BLOOD) { You("cannot wipe out the message that is %s the %s here.", - oep->engr_type == BURN - ? (is_ice(u.ux, u.uy) ? "melted into" : "burned into") + (de->oep->engr_type == BURN) + ? (de->frosted ? "melted into" : "burned into") : "engraved in", surface(u.ux, u.uy)); - return 1; - } else if (type != oep->engr_type || c == 'n') { + de->ret = ECMD_TIME; + goto doengr_exit; + } else if (de->type != de->oep->engr_type || c == 'n') { if (!Blind || can_reach_floor(TRUE)) You("will overwrite the current message."); - eow = TRUE; + de->eow = TRUE; } + } else if (de->oep + && Strlen(de->oep->engr_txt[actual_text]) >= BUFSZ - 1) { + There("is no room to add anything else here."); + de->ret = ECMD_TIME; + goto doengr_exit; } } - eloc = surface(u.ux, u.uy); - switch (type) { - default: - everb = (oep && !eow ? "add to the weird writing on" - : "write strangely on"); - break; - case DUST: - everb = (oep && !eow ? "add to the writing in" : "write in"); - eloc = is_ice(u.ux, u.uy) ? "frost" : "dust"; - break; - case HEADSTONE: - everb = (oep && !eow ? "add to the epitaph on" : "engrave on"); - break; - case ENGRAVE: - everb = (oep && !eow ? "add to the engraving in" : "engrave in"); - break; - case BURN: - everb = (oep && !eow - ? (is_ice(u.ux, u.uy) ? "add to the text melted into" - : "add to the text burned into") - : (is_ice(u.ux, u.uy) ? "melt into" : "burn into")); - break; - case MARK: - everb = (oep && !eow ? "add to the graffiti on" : "scribble on"); - break; - case ENGR_BLOOD: - everb = (oep && !eow ? "add to the scrawl on" : "scrawl on"); - break; - } + de->eloc = surface(u.ux, u.uy); + de->adding = (de->oep && !de->eow); + doengrave_ctx_verb(de); /* Tell adventurer what is going on */ - if (otmp != &zeroobj) - You("%s the %s with %s.", everb, eloc, doname(otmp)); + if (de->otmp != &hands_obj) + You("%s the %s with %s%s.", de->everb, de->eloc, + /* since doname() yields "N items" when quantity is more than + one, match that by using "1 of" rather than "one of" when + informing the player that the stack will be split */ + (de->type == ENGRAVE && de->otmp->quan > 1L) ? "1 of " : "", + doname(de->otmp)); else - You("%s the %s with your %s.", everb, eloc, body_part(FINGERTIP)); + You("%s the %s with your %s.", + de->everb, de->eloc, body_part(FINGERTIP)); /* Prompt for engraving! */ - Sprintf(qbuf, "What do you want to %s the %s here?", everb, eloc); - getlin(qbuf, ebuf); + Sprintf(de->qbuf, "What do you want to %s the %s here?", + de->everb, de->eloc); + getlin(de->qbuf, de->ebuf); /* convert tabs to spaces and condense consecutive spaces to one */ - mungspaces(ebuf); + mungspaces(de->ebuf); /* Count the actual # of chars engraved not including spaces */ - len = strlen(ebuf); - for (sp = ebuf; *sp; sp++) + de->len = strlen(de->ebuf); + for (sp = de->ebuf; *sp; sp++) if (*sp == ' ') - len -= 1; + de->len -= 1; - if (len == 0 || index(ebuf, '\033')) { - if (zapwand) { + if (de->len == 0 || strchr(de->ebuf, '\033')) { + if (de->zapwand) { if (!Blind) - pline("%s, then %s.", Tobjnam(otmp, "glow"), - otense(otmp, "fade")); - return 1; + pline("%s, then %s.", Tobjnam(de->otmp, "glow"), + otense(de->otmp, "fade")); + de->ret = ECMD_TIME; + goto doengr_exit; } else { pline1(Never_mind); - return 0; + goto doengr_exit; } } /* A single `x' is the traditional signature of an illiterate person */ - if (len != 1 || (!index(ebuf, 'x') && !index(ebuf, 'X'))) - u.uconduct.literate++; + if (de->len != 1 || (!strchr(de->ebuf, 'x') && !strchr(de->ebuf, 'X'))) + if (!u.uconduct.literate++) + livelog_printf(LL_CONDUCT, "became literate by engraving \"%s\"", + de->ebuf); /* Mix up engraving if surface or state of mind is unsound. Note: this won't add or remove any spaces. */ - for (sp = ebuf; *sp; sp++) { + for (sp = de->ebuf; *sp; sp++) { if (*sp == ' ') continue; - if (((type == DUST || type == ENGR_BLOOD) && !rn2(25)) + if (((de->type == DUST || de->type == ENGR_BLOOD) && !rn2(25)) || (Blind && !rn2(11)) || (Confusion && !rn2(7)) || (Stunned && !rn2(4)) || (Hallucination && !rn2(2))) *sp = ' ' + rnd(96 - 2); /* ASCII '!' thru '~' @@ -1060,177 +1228,406 @@ doengrave() } /* Previous engraving is overwritten */ - if (eow) { - del_engr(oep); - oep = (struct engr *) 0; + if (de->eow) { + del_engr(de->oep); + de->oep = (struct engr *) 0; + de->disprefresh = TRUE; } - /* Figure out how long it took to engrave, and if player has - * engraved too much. - */ - switch (type) { + Strcpy(svc.context.engraving.text, de->ebuf); + svc.context.engraving.nextc = svc.context.engraving.text; + svc.context.engraving.stylus = de->otmp; + svc.context.engraving.type = de->type; + svc.context.engraving.pos.x = u.ux; + svc.context.engraving.pos.y = u.uy; + svc.context.engraving.actionct = 0; + set_occupation(engrave, "engraving", 0); + + if (de->post_engr_text[0]) + pline("%s", de->post_engr_text); + if (de->doblind && !resists_blnd(&gy.youmonst)) { + You("are blinded by the flash!"); + make_blinded((long) rnd(50), FALSE); + if (!Blind) + Your1(vision_clears); + } + + /* Engraving will always take at least one action via being run as an + occupation, so do not count this setup as taking time. */ + doengr_exit: + if (de->disprefresh) + newsym(u.ux, u.uy); + retval = de->ret; + free(de); + return retval; +} + +/* occupation callback for engraving some text */ +staticfn int +engrave(void) +{ + struct engr *oep; + char buf[BUFSZ]; /* holds the post-this-action engr text, including + * anything already there */ + const char *finishverb; /* "You finish [foo]." */ + struct obj * stylus; /* shorthand for svc.context.engraving.stylus */ + boolean firsttime = (svc.context.engraving.actionct == 0); + int rate = 10; /* # characters that can be engraved in this action */ + boolean truncate = FALSE; + boolean neweng = (svc.context.engraving.actionct == 0); + + boolean carving = (svc.context.engraving.type == ENGRAVE + || svc.context.engraving.type == HEADSTONE); + boolean dulling_wep, marker; + char *endc; /* points at character 1 beyond the last character to engrave + * this action */ + int i, space_left; + + if (svc.context.engraving.pos.x != u.ux + || svc.context.engraving.pos.y != u.uy) { /* teleported? */ + You("are unable to continue engraving."); + return 0; + } + /* Stylus might have been taken out of inventory and destroyed somehow. + * Not safe to dereference stylus until after this. */ + if (svc.context.engraving.stylus == &hands_obj) { /* bare finger */ + stylus = (struct obj *) 0; + } else { + for (stylus = gi.invent; stylus; stylus = stylus->nobj) { + if (stylus == svc.context.engraving.stylus) + break; + } + if (!stylus) { + You("are unable to continue engraving."); + return 0; + } + } + + dulling_wep = (carving && stylus && stylus->oclass == WEAPON_CLASS + && (stylus->otyp != ATHAME || stylus->cursed)); + marker = (stylus && stylus->otyp == MAGIC_MARKER + && svc.context.engraving.type == MARK); + + svc.context.engraving.actionct++; + + /* sanity checks */ + if (dulling_wep && !is_blade(stylus)) { + impossible("carving with non-bladed weapon"); + } else if (svc.context.engraving.type == MARK && !marker) { + impossible("making graffiti with non-marker stylus"); + } + + /* Step 1: Compute rate. */ + if (carving && stylus + && (dulling_wep || stylus->oclass == RING_CLASS + || stylus->oclass == GEM_CLASS)) { + /* slow engraving methods */ + rate = 1; + } else if (marker) { + /* one charge / 2 letters */ + rate = min(rate, stylus->spe * 2); + } + + /* Step 2: Compute last character that can be engraved this action. */ + i = rate; + for (endc = svc.context.engraving.nextc; *endc && i > 0; endc++) { + if (*endc != ' ') { + i--; + } + } + + /* Step 3: affect stylus from engraving - it might wear out. */ + if (dulling_wep) { + boolean splitstack = FALSE, dulled = FALSE; + + /* 'dulling_wep' guarantees that 'stylus' is a weapon which is + not welded to the hero's hand(s) */ + if (stylus->quan > 1L) { + if (firsttime) + pline("One of %s gets dull.", yname(stylus)); + stylus = svc.context.engraving.stylus = splitobj(stylus, 1L); + /* if stack is wielded or quivered, the split-off one isn't */ + stylus->owornmask = 0L; + splitstack = TRUE; + } else { + /* normal case: stylus->quan==1 */ + if (firsttime) + pline("%s gets dull.", Yname2(stylus)); + } + /* Dull the weapon at a rate of -1 enchantment per 2 characters, + * rounding down. + * The number of characters obtainable given starting enchantment: + * -2 => 3, -1 => 5, 0 => 7, +1 => 9, +2 => 11 + * Note: this does not allow a +0 anything (except an athame) to + * engrave "Elbereth" all at once. + * However, you can engrave "Elb", then "ere", then "th", by taking + * advantage of the rounding down. */ + if (svc.context.engraving.actionct % 2 == 1) { /* 1st,3rd,... action */ + /* deduct a point on 1st, 3rd, 5th, ... turns, unless this is the + * last character being engraved (a rather convoluted way to round + * down), but always deduct a point on the 1st turn to prevent + * zero-cost engravings. + * Check for truncation *before* deducting a point - otherwise, + * attempting to e.g. engrave 3 characters with a -2 weapon will + * stop at the 1st. */ + if (stylus->spe <= -3) { + if (firsttime) { + impossible("<= -3 weapon valid for engraving"); + } + truncate = TRUE; + } else if (*endc || svc.context.engraving.actionct == 1) { + stylus->spe -= 1; + dulled = TRUE; + } + } + if (splitstack) { + obj_extract_self(stylus); + stylus = hold_another_object(stylus, "You drop one %s!", + doname(stylus), (char *) NULL); + nhUse(stylus); + } else if (dulled && stylus->known) { + /* reflect change in stylus->spe; not needed for splitstack + since hold_another_object() does this */ + prinv((char *) NULL, stylus, 1L); + update_inventory(); + } + } else if (marker) { + int ink_cost = max(rate / 2, 1); /* Prevent infinite graffiti */ + + if (stylus->spe < ink_cost) { + impossible("overly dry marker valid for graffiti?"); + ink_cost = stylus->spe; + truncate = TRUE; + } + stylus->spe -= ink_cost; + update_inventory(); + if (stylus->spe == 0) { + /* can't engrave any further; truncate the string */ + Your("marker dries out."); + truncate = TRUE; + } + } + + switch (svc.context.engraving.type) { default: - multi = -(len / 10); - if (multi) - nomovemsg = "You finish your weird engraving."; + finishverb = "your weird engraving"; break; case DUST: - multi = -(len / 10); - if (multi) - nomovemsg = "You finish writing in the dust."; + finishverb = is_ice(u.ux, u.uy) ? "writing in the frost" + : "writing in the dust"; break; case HEADSTONE: case ENGRAVE: - multi = -(len / 10); - if (otmp->oclass == WEAPON_CLASS - && (otmp->otyp != ATHAME || otmp->cursed)) { - multi = -len; - maxelen = ((otmp->spe + 3) * 2) + 1; - /* -2 => 3, -1 => 5, 0 => 7, +1 => 9, +2 => 11 - * Note: this does not allow a +0 anything (except an athame) - * to engrave "Elbereth" all at once. - * However, you can engrave "Elb", then "ere", then "th". - */ - pline("%s dull.", Yobjnam2(otmp, "get")); - costly_alteration(otmp, COST_DEGRD); - if (len > maxelen) { - multi = -maxelen; - otmp->spe = -3; - } else if (len > 1) - otmp->spe -= len >> 1; - else - otmp->spe -= 1; /* Prevent infinite engraving */ - } else if (otmp->oclass == RING_CLASS || otmp->oclass == GEM_CLASS) { - multi = -len; - } - if (multi) - nomovemsg = "You finish engraving."; + finishverb = "engraving"; break; case BURN: - multi = -(len / 10); - if (multi) - nomovemsg = is_ice(u.ux, u.uy) - ? "You finish melting your message into the ice." - : "You finish burning your message into the floor."; + finishverb = is_ice(u.ux, u.uy) ? "melting your message into the ice" + : "burning your message into the floor"; break; case MARK: - multi = -(len / 10); - if (otmp->otyp == MAGIC_MARKER) { - maxelen = otmp->spe * 2; /* one charge / 2 letters */ - if (len > maxelen) { - Your("marker dries out."); - otmp->spe = 0; - multi = -(maxelen / 10); - } else if (len > 1) - otmp->spe -= len >> 1; - else - otmp->spe -= 1; /* Prevent infinite graffiti */ - } - if (multi) - nomovemsg = "You finish defacing the dungeon."; + finishverb = "defacing the dungeon"; break; case ENGR_BLOOD: - multi = -(len / 10); - if (multi) - nomovemsg = "You finish scrawling."; - break; + finishverb = "scrawling"; } - /* Chop engraving down to size if necessary */ - if (len > maxelen) { - for (sp = ebuf; maxelen && *sp; sp++) - if (!(*sp == ' ')) - maxelen--; - if (!maxelen && *sp) { - *sp = '\0'; - if (multi) - nomovemsg = "You cannot write any more."; - You("are only able to write \"%s\".", ebuf); - } - } + /* actions that happen at the end of every engraving action go here */ + buf[0] = '\0'; + oep = engr_at(u.ux, u.uy); if (oep) /* add to existing engraving */ - Strcpy(buf, oep->engr_txt); - (void) strncat(buf, ebuf, BUFSZ - (int) strlen(buf) - 1); - /* Put the engraving onto the map */ - make_engr_at(u.ux, u.uy, buf, moves - multi, type); - - if (post_engr_text[0]) - pline("%s", post_engr_text); - if (doblind && !resists_blnd(&youmonst)) { - You("are blinded by the flash!"); - make_blinded((long) rnd(50), FALSE); - if (!Blind) - Your1(vision_clears); + Strcpy(buf, oep->engr_txt[actual_text]); + + space_left = (int) (sizeof buf - strlen(buf) - 1U); + if (endc - svc.context.engraving.nextc > space_left) { + You("run out of room to write."); + endc = svc.context.engraving.nextc + space_left; + truncate = TRUE; + } + + /* If the stylus did wear out mid-engraving, truncate the input so that we + * can't go any further. */ + if (truncate && *endc != '\0') { + *endc = '\0'; + You("are only able to write \"%s\".", svc.context.engraving.text); + } else { + /* input was not truncated; stylus may still have worn out on the last + * character, though */ + truncate = FALSE; + } + + (void) strncat(buf, svc.context.engraving.nextc, + min(space_left, endc - svc.context.engraving.nextc)); + make_engr_at(u.ux, u.uy, buf, NULL, svm.moves - gm.multi, + svc.context.engraving.type); + oep = engr_at(u.ux, u.uy); + if (oep) { + oep->eread = 1; + oep->erevealed = 1; + } + + if (*endc) { + svc.context.engraving.nextc = endc; + if (neweng) { + newsym(svc.context.engraving.pos.x, svc.context.engraving.pos.y); + } + return 1; /* not yet finished this turn */ + } else { /* finished engraving */ + /* actions that happen after the engraving is finished go here */ + + if (truncate) { + /* Now that "You are only able to write 'foo'" also prints at the + * end of engraving, this might be redundant. */ + You("cannot write any more."); + } else if (!firsttime) { + /* only print this if engraving took multiple actions */ + You("finish %s.", finishverb); + } + svc.context.engraving.text[0] = '\0'; + svc.context.engraving.nextc = (char *) 0; + svc.context.engraving.stylus = (struct obj *) 0; } - return 1; + if (neweng) + newsym(svc.context.engraving.pos.x, svc.context.engraving.pos.y); + return 0; } /* while loading bones, clean up text which might accidentally or maliciously disrupt player's terminal when displayed */ void -sanitize_engravings() +sanitize_engravings(void) +{ + struct engr *ep; + + for (ep = head_engr; ep; ep = ep->nxt_engr) { + sanitize_name(ep->engr_txt[actual_text]); + } +} + +/* mark all engravings as not-discovered/not-read when saving bones */ +void +forget_engravings(void) +{ + struct engr *ep; + + for (ep = head_engr; ep; ep = ep->nxt_engr) { + ep->erevealed = ep->eread = 0; + + /* Note: engr_txt[actual_text], engr_txt[rememberd_text], and + * engr_txt[pristine_text] retain their original text rather + * than get updated to reflect each engraving's current text. + * Does it matter? */ + } +} + +void +engraving_sanity_check(void) { struct engr *ep; + int levtyp; + + if (head_engr && (Is_airlevel(&u.uz) || Is_waterlevel(&u.uz))) { + impossible("engraving sanity: on plane of air/water"); + return; + } for (ep = head_engr; ep; ep = ep->nxt_engr) { - sanitize_name(ep->engr_txt); + coordxy x = ep->engr_x, y = ep->engr_y; + + if (!isok(x, y)) { + impossible("engraving sanity: !isok <%i,%i>", x, y); + continue; + } + levtyp = SURFACE_AT(x, y); + if (is_pool_or_lava(x, y) || IS_AIR(levtyp) || !ACCESSIBLE(levtyp)) { + impossible("engraving sanity: illegal surface (%d: \"%s\")", + levtyp, surface(x, y)); + continue; + } } } void -save_engravings(fd, mode) -int fd, mode; +save_engravings(NHFILE *nhfp) { struct engr *ep, *ep2; - unsigned no_more_engr = 0; + unsigned no_more_engr = 0, engr_alloc; + unsigned szeach; for (ep = head_engr; ep; ep = ep2) { ep2 = ep->nxt_engr; - if (ep->engr_lth && ep->engr_txt[0] && perform_bwrite(mode)) { - bwrite(fd, (genericptr_t) &ep->engr_lth, sizeof ep->engr_lth); - bwrite(fd, (genericptr_t) ep, sizeof (struct engr) + ep->engr_lth); + if (ep->engr_alloc + && ep->engr_txt[actual_text][0] && update_file(nhfp)) { + engr_alloc = (unsigned) ep->engr_alloc; + szeach = ep->engr_szeach; + Sfo_unsigned(nhfp, &engr_alloc, "engraving-engr_alloc"); + Sfo_engr(nhfp, ep, "engraving"); + ep->engr_txt[actual_text] = engr_text_space(ep); + ep->engr_txt[remembered_text] = ep->engr_txt[actual_text] + szeach; + ep->engr_txt[pristine_text] = ep->engr_txt[remembered_text] + szeach; + Sfo_char(nhfp, ep->engr_txt[actual_text], "engraving-actual_text", szeach); + Sfo_char(nhfp, ep->engr_txt[remembered_text], "engraving-remembered_text", szeach); + Sfo_char(nhfp, ep->engr_txt[pristine_text], "engraving-pristine_text", szeach); } - if (release_data(mode)) + if (release_data(nhfp)) dealloc_engr(ep); } - if (perform_bwrite(mode)) - bwrite(fd, (genericptr_t) &no_more_engr, sizeof no_more_engr); - if (release_data(mode)) + if (update_file(nhfp)) { + Sfo_unsigned(nhfp, &no_more_engr, "engraving-engr_alloc"); + } + if (release_data(nhfp)) head_engr = 0; } +#endif /* !SFCTOOL */ void -rest_engravings(fd) -int fd; +rest_engravings(NHFILE *nhfp) { struct engr *ep; - unsigned lth; + unsigned lth = 0; + unsigned szeach; head_engr = 0; while (1) { - mread(fd, (genericptr_t) <h, sizeof lth); + Sfi_unsigned(nhfp, <h, "engraving-engr_alloc"); if (lth == 0) return; ep = newengr(lth); - mread(fd, (genericptr_t) ep, sizeof (struct engr) + lth); + Sfi_engr(nhfp, ep, "engraving"); + szeach = ep->engr_szeach; ep->nxt_engr = head_engr; head_engr = ep; - ep->engr_txt = (char *) (ep + 1); /* Andreas Bormann */ - /* Mark as finished for bones levels -- no problem for + ep->engr_txt[actual_text] = engr_text_space(ep); /* Andreas Bormann */ + ep->engr_txt[remembered_text] = ep->engr_txt[actual_text] + szeach; + ep->engr_txt[pristine_text] = ep->engr_txt[remembered_text] + szeach; + Sfi_char(nhfp, ep->engr_txt[actual_text], + "engraving-actual_text", (int) szeach); + Sfi_char(nhfp, ep->engr_txt[remembered_text], + "engraving-remembered_text", (int) szeach); + Sfi_char(nhfp, ep->engr_txt[pristine_text], + "engraving-pristine_text", (int) szeach); + + while (ep->engr_txt[actual_text][0] == ' ') + ep->engr_txt[actual_text]++; + while (ep->engr_txt[remembered_text][0] == ' ') + ep->engr_txt[remembered_text]++; + /* mark as finished for bones levels -- no problem for * normal levels as the player must have finished engraving - * to be able to move again. - */ - ep->engr_time = moves; + * to be able to move again */ + ep->engr_time = svm.moves; } } +#ifndef SFCTOOL +DISABLE_WARNING_FORMAT_NONLITERAL + /* to support '#stats' wizard-mode command */ void -engr_stats(hdrfmt, hdrbuf, count, size) -const char *hdrfmt; -char *hdrbuf; -long *count, *size; +engr_stats( + const char *hdrfmt, + char *hdrbuf, + long *count, + long *size) { struct engr *ep; @@ -1238,18 +1635,19 @@ long *count, *size; *count = *size = 0L; for (ep = head_engr; ep; ep = ep->nxt_engr) { ++*count; - *size += (long) sizeof *ep + (long) ep->engr_lth; + *size += (long) sizeof *ep + (long) ep->engr_alloc; } } +RESTORE_WARNING_FORMAT_NONLITERAL + void -del_engr(ep) -register struct engr *ep; +del_engr(struct engr *ep) { if (ep == head_engr) { head_engr = ep->nxt_engr; } else { - register struct engr *ept; + struct engr *ept; for (ept = head_engr; ept; ept = ept->nxt_engr) if (ept->nxt_engr == ep) { @@ -1266,8 +1664,7 @@ register struct engr *ep; /* randomly relocate an engraving */ void -rloc_engr(ep) -struct engr *ep; +rloc_engr(struct engr *ep) { int tx, ty, tryct = 200; @@ -1280,15 +1677,14 @@ struct engr *ep; ep->engr_x = tx; ep->engr_y = ty; + newsym(tx, ty); /* caller took care of the old location */ } /* Create a headstone at the given location. * The caller is responsible for newsym(x, y). */ void -make_grave(x, y, str) -int x, y; -const char *str; +make_grave(coordxy x, coordxy y, const char *str) { char buf[BUFSZ]; @@ -1296,15 +1692,54 @@ const char *str; if ((levl[x][y].typ != ROOM && levl[x][y].typ != GRAVE) || t_at(x, y)) return; /* Make the grave */ - levl[x][y].typ = GRAVE; + if (!set_levltyp(x, y, GRAVE)) + return; /* Engrave the headstone */ del_engr_at(x, y); if (!str) - str = get_rnd_text(EPITAPHFILE, buf, rn2); - make_engr_at(x, y, str, 0L, HEADSTONE); + str = get_rnd_text(EPITAPHFILE, buf, rn2, MD_PAD_RUMORS); + make_engr_at(x, y, str, NULL, 0L, HEADSTONE); return; } +/* called when kicking or engraving on a grave's headstone */ +void +disturb_grave(coordxy x, coordxy y) +{ + struct rm *lev = &levl[x][y]; + + if (!IS_GRAVE(lev->typ)) { + impossible("Disturbing grave that isn't a grave? (%d)", lev->typ); + } else if (lev->disturbed) { + impossible("Disturbing already disturbed grave?"); + } else { + You("disturb the undead!"); + lev->disturbed = 1; + (void) makemon(&mons[PM_GHOUL], x, y, NO_MM_FLAGS); + exercise(A_WIS, FALSE); + } +} + +void +see_engraving(struct engr *ep) +{ + newsym(ep->engr_x, ep->engr_y); +} + +/* like see_engravings() but overrides vision, but only for some types + of engravings that can be felt [this isn't actually used anywhere?] */ +void +feel_engraving(struct engr *ep) +{ + if (engr_can_be_felt(ep)) { + ep->eread = 1; + ep->erevealed = 1; + map_engraving(ep, 1); + /* in case it's beneath something, redisplay the something */ + newsym(ep->engr_x, ep->engr_y); + } +} + static const char blind_writing[][21] = { {0x44, 0x66, 0x6d, 0x69, 0x62, 0x65, 0x22, 0x45, 0x7b, 0x71, 0x65, 0x6d, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, @@ -1326,10 +1761,10 @@ static const char blind_writing[][21] = { 0x69, 0x76, 0x6b, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, }; -STATIC_OVL const char * -blengr(VOID_ARGS) +staticfn const char * +blengr(void) { - return blind_writing[rn2(SIZE(blind_writing))]; + return ROLL_FROM(blind_writing); } - +#endif /* !SFCTOOL */ /*engrave.c*/ diff --git a/src/exper.c b/src/exper.c index 9b96b5c43..003a5035b 100644 --- a/src/exper.c +++ b/src/exper.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 exper.c $NHDT-Date: 1562114352 2019/07/03 00:39:12 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.33 $ */ +/* NetHack 5.0 exper.c $NHDT-Date: 1706133782 2024/01/24 22:03:02 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.62 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2007. */ /* NetHack may be freely redistributed. See license for details. */ @@ -8,11 +8,10 @@ #include #endif -STATIC_DCL int FDECL(enermod, (int)); +staticfn int enermod(int); long -newuexp(lev) -int lev; +newuexp(int lev) { if (lev < 1) /* for newuexp(u.ulevel - 1) when u.ulevel is 1 */ return 0L; @@ -23,12 +22,11 @@ int lev; return (10000000L * ((long) (lev - 19))); } -STATIC_OVL int -enermod(en) -int en; +staticfn int +enermod(int en) { switch (Role_switch) { - case PM_PRIEST: + case PM_CLERIC: case PM_WIZARD: return (2 * en); case PM_HEALER: @@ -44,41 +42,49 @@ int en; /* calculate spell power/energy points for new level */ int -newpw() +newpw(void) { int en = 0, enrnd, enfix; if (u.ulevel == 0) { - en = urole.enadv.infix + urace.enadv.infix; - if (urole.enadv.inrnd > 0) - en += rnd(urole.enadv.inrnd); - if (urace.enadv.inrnd > 0) - en += rnd(urace.enadv.inrnd); + en = gu.urole.enadv.infix + gu.urace.enadv.infix; + if (gu.urole.enadv.inrnd > 0) + en += rnd(gu.urole.enadv.inrnd); + if (gu.urace.enadv.inrnd > 0) + en += rnd(gu.urace.enadv.inrnd); } else { enrnd = (int) ACURR(A_WIS) / 2; - if (u.ulevel < urole.xlev) { - enrnd += urole.enadv.lornd + urace.enadv.lornd; - enfix = urole.enadv.lofix + urace.enadv.lofix; + if (u.ulevel < gu.urole.xlev) { + enrnd += gu.urole.enadv.lornd + gu.urace.enadv.lornd; + enfix = gu.urole.enadv.lofix + gu.urace.enadv.lofix; } else { - enrnd += urole.enadv.hirnd + urace.enadv.hirnd; - enfix = urole.enadv.hifix + urace.enadv.hifix; + enrnd += gu.urole.enadv.hirnd + gu.urace.enadv.hirnd; + enfix = gu.urole.enadv.hifix + gu.urace.enadv.hifix; } en = enermod(rn1(enrnd, enfix)); } if (en <= 0) en = 1; - if (u.ulevel < MAXULEV) - u.ueninc[u.ulevel] = (xchar) en; + if (u.ulevel < MAXULEV) { + /* remember increment; future level drain could take it away again */ + u.ueninc[u.ulevel] = (xint16) en; + } else { + /* after level 30, throttle energy gains from extra experience; + once max reaches 600, further increments will be just 1 more */ + char lim = 4 - u.uenmax / 200; + + lim = max(lim, 1); + if (en > lim) + en = lim; + } return en; } /* return # of exp points for mtmp after nk killed */ int -experience(mtmp, nk) -register struct monst *mtmp; -register int nk; +experience(struct monst *mtmp, int nk) { - register struct permonst *ptr = mtmp->data; + struct permonst *ptr = mtmp->data; int i, tmp, tmp2; tmp = 1 + mtmp->m_lev * mtmp->m_lev; @@ -128,7 +134,7 @@ register int nk; if (mtmp->m_lev > 8) tmp += 50; -#ifdef MAIL +#ifdef MAIL_STRUCTURES /* Mail daemons put up no fight. */ if (mtmp->data == &mons[PM_MAIL_DAEMON]) tmp = 1; @@ -160,8 +166,7 @@ register int nk; } void -more_experienced(exper, rexp) -register int exper, rexp; +more_experienced(int exper, int rexp) { long oldexp = u.uexp, oldrexp = u.urexp, @@ -178,58 +183,83 @@ register int exper, rexp; if (newexp != oldexp) { u.uexp = newexp; if (flags.showexp) - context.botl = TRUE; + disp.botl = TRUE; /* even when experience points aren't being shown, experience level might be highlighted with a percentage highlight rule and that percentage depends upon experience points */ - if (!context.botl && exp_percent_changing()) - context.botl = TRUE; + if (!disp.botl && exp_percent_changing()) + disp.botl = TRUE; } /* newrexp will always differ from oldrexp unless they're LONG_MAX */ if (newrexp != oldrexp) { u.urexp = newrexp; #ifdef SCORE_ON_BOTL if (flags.showscore) - context.botl = TRUE; + disp.botl = TRUE; #endif } if (u.urexp >= (Role_if(PM_WIZARD) ? 1000 : 2000)) - flags.beginner = 0; + flags.beginner = FALSE; } /* e.g., hit by drain life attack */ void -losexp(drainer) -const char *drainer; /* cause of death, if drain should be fatal */ +losexp( + const char *drainer) /* cause of death, if drain should be fatal */ { - register int num; + int num, uhpmin, olduhpmax; /* override life-drain resistance when handling an explicit wizard mode request to reduce level; never fatal though */ if (drainer && !strcmp(drainer, "#levelchange")) drainer = 0; - else if (resists_drli(&youmonst)) + else if (resists_drli(&gy.youmonst)) return; + /* level-loss message; "Goodbye level 1." is fatal; divine anger + (drainer==NULL) resets a level 1 character to 0 experience points + without reducing level and that isn't fatal so suppress the message + in that situation */ + if (u.ulevel > 1 || drainer) + pline("%s level %d.", Goodbye(), u.ulevel); + if (u.ulevel > 1) { - pline("%s level %d.", Goodbye(), u.ulevel--); + u.ulevel -= 1; /* remove intrinsic abilities */ adjabil(u.ulevel + 1, u.ulevel); - reset_rndmonst(NON_PM); /* new monster selection */ - } else { + livelog_printf(LL_MINORAC, "lost experience level %d", u.ulevel + 1); + SoundAchievement(0, sa2_xpleveldown, 0); + } else { /* u.ulevel==1 */ if (drainer) { - killer.format = KILLED_BY; - if (killer.name != drainer) - Strcpy(killer.name, drainer); + svk.killer.format = KILLED_BY; + if (svk.killer.name != drainer) + Strcpy(svk.killer.name, drainer); done(DIED); } /* no drainer or lifesaved */ + if (u.ulevel > 1) + /* can happen during debug fuzzing if fuzzer_savelife() uses + a blessed potion of restore ability to restore lost levels */ + return; u.uexp = 0; + livelog_printf(LL_MINORAC, "lost all experience"); } + assert(u.ulevel >= 0 && u.ulevel < MAXULEV); /* valid array index */ + + olduhpmax = u.uhpmax; + uhpmin = minuhpmax(10); /* same minimum as is used by life-saving */ num = (int) u.uhpinc[u.ulevel]; u.uhpmax -= num; - if (u.uhpmax < 1) - u.uhpmax = 1; + if (u.uhpmax < uhpmin) + setuhpmax(uhpmin, TRUE); + /* uhpmax might try to go up if it has previously been reduced by + strength loss or by a fire trap or by an attack by Death which + all use a different minimum than life-saving or experience loss; + we don't allow it to go up because that contradicts assumptions + elsewhere (such as healing wielder who drains with Stormbringer) */ + if (u.uhpmax > olduhpmax) + setuhpmax(olduhpmax, TRUE); + u.uhp -= num; if (u.uhp < 1) u.uhp = 1; @@ -250,14 +280,14 @@ const char *drainer; /* cause of death, if drain should be fatal */ u.uexp = newuexp(u.ulevel) - 1; if (Upolyd) { - num = monhp_per_lvl(&youmonst); + num = monhp_per_lvl(&gy.youmonst); u.mhmax -= num; u.mh -= num; if (u.mh <= 0) rehumanize(); } - context.botl = TRUE; + disp.botl = TRUE; } /* @@ -267,16 +297,18 @@ const char *drainer; /* cause of death, if drain should be fatal */ * at a dragon created with a wand of polymorph?? */ void -newexplevel() +newexplevel(void) { if (u.ulevel < MAXULEV && u.uexp >= newuexp(u.ulevel)) pluslvl(TRUE); } void -pluslvl(incr) -boolean incr; /* true iff via incremental experience growth */ -{ /* (false for potion of gain level) */ +pluslvl( + boolean incr) /* True: incremental experience growth; + * False: potion of gain level or wraith corpse + * or wizard mode #levelchange */ +{ int hpinc, eninc; if (!incr) @@ -285,24 +317,30 @@ boolean incr; /* true iff via incremental experience growth */ /* increase hit points (when polymorphed, do monster form first in order to retain normal human/whatever increase for later) */ if (Upolyd) { - hpinc = monhp_per_lvl(&youmonst); - u.mhmax += hpinc; + hpinc = monhp_per_lvl(&gy.youmonst); u.mh += hpinc; + setuhpmax(u.mhmax, FALSE); /* acts as setmhmax() when Upolyd */ } hpinc = newhp(); - u.uhpmax += hpinc; u.uhp += hpinc; + setuhpmax(u.uhpmax + hpinc, TRUE); /* will lower u.uhp if it exceeds + * u.uhpmax */ /* increase spell power/energy points */ eninc = newpw(); u.uenmax += eninc; + if (u.uenmax > u.uenpeak) + u.uenpeak = u.uenmax; u.uen += eninc; /* increase level (unless already maxxed) */ if (u.ulevel < MAXULEV) { + int old_ach_cnt, newrank, oldrank = xlev_to_rank(u.ulevel); + /* increase experience points to reflect new level */ if (incr) { long tmp = newuexp(u.ulevel + 1); + if (u.uexp >= tmp) u.uexp = tmp - 1; } else { @@ -315,17 +353,29 @@ boolean incr; /* true iff via incremental experience growth */ if (u.ulevelmax < u.ulevel) u.ulevelmax = u.ulevel; adjabil(u.ulevel - 1, u.ulevel); /* give new intrinsics */ - reset_rndmonst(NON_PM); /* new monster selection */ + SoundAchievement(0, sa2_xplevelup, 0); + old_ach_cnt = count_achievements(); + newrank = xlev_to_rank(u.ulevel); + if (newrank > oldrank) + record_achievement(achieve_rank(newrank)); + /* a new rank achievement will log its own message; log a simpler + message here if we didn't just get an achievement (so when rank + hasn't changed or hero just regained a lost level and the rank + achievement doesn't get repeated) */ + if (count_achievements() == old_ach_cnt) + livelog_printf(LL_MINORAC, "%sgained experience level %d", + (u.ulevel <= u.ulevelpeak) ? "re" : "", u.ulevel); + if (u.ulevel > u.ulevelpeak) + u.ulevelpeak = u.ulevel; } - context.botl = TRUE; + disp.botl = TRUE; } /* compute a random amount of experience points suitable for the hero's experience level: base number of points needed to reach the current level plus a random portion of what it takes to get to the next level */ long -rndexp(gaining) -boolean gaining; /* gaining XP via potion vs setting XP for polyself */ +rndexp(boolean gaining) /* gaining XP via potion vs setting XP for polyself */ { long minexp, maxexp, diff, factor, result; diff --git a/src/explode.c b/src/explode.c index 31ba7c763..eae1cbb28 100644 --- a/src/explode.c +++ b/src/explode.c @@ -1,13 +1,182 @@ -/* NetHack 3.6 explode.c $NHDT-Date: 1545182146 2018/12/19 01:15:46 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.60 $ */ +/* NetHack 5.0 explode.c $NHDT-Date: 1736530208 2025/01/10 09:30:08 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.122 $ */ /* Copyright (C) 1990 by Ken Arromdee */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" +staticfn int explosionmask(struct monst *, uchar, char) NONNULLARG1; +staticfn void engulfer_explosion_msg(uchar, char); + /* Note: Arrays are column first, while the screen is row first */ -static int explosion[3][3] = { { S_explode1, S_explode4, S_explode7 }, - { S_explode2, S_explode5, S_explode8 }, - { S_explode3, S_explode6, S_explode9 } }; +static const int explosion[3][3] = { + { S_expl_tl, S_expl_ml, S_expl_bl }, + { S_expl_tc, S_expl_mc, S_expl_bc }, + { S_expl_tr, S_expl_mr, S_expl_br } }; + +/* what to do at [x+i][y+j] for i=-1,0,1 and j=-1,0,1 */ +enum explode_action { + EXPL_NONE = 0, /* not specified yet or no shield effect needed */ + EXPL_MON = 1, /* monster is affected */ + EXPL_HERO = 2, /* hero is affected */ + EXPL_SKIP = 4 /* don't apply shield effect (out of bounds) */ +}; + +/* check if shield effects are needed for location affected by explosion */ +staticfn int +explosionmask( + struct monst *m, /* target monster (might be youmonst) */ + uchar adtyp, /* damage type */ + char olet) /* object class (only matters for AD_DISN) */ +{ + int res = EXPL_NONE; + + if (m == &gy.youmonst) { + switch (adtyp) { + case AD_PHYS: + /* leave 'res' with EXPL_NONE */ + break; + case AD_MAGM: + if (Antimagic) + res = EXPL_HERO; + break; + case AD_FIRE: + if (Fire_resistance) + res = EXPL_HERO; + break; + case AD_COLD: + if (Cold_resistance) + res = EXPL_HERO; + break; + case AD_DISN: + if ((olet == WAND_CLASS) + ? (nonliving(m->data) || is_demon(m->data)) + : Disint_resistance) + res = EXPL_HERO; + break; + case AD_ELEC: + if (Shock_resistance) + res = EXPL_HERO; + break; + case AD_DRST: + if (Poison_resistance) + res = EXPL_HERO; + break; + case AD_ACID: + if (Acid_resistance) + res = EXPL_HERO; + break; + default: + impossible("explosion type %d?", adtyp); + break; + } + + } else { + /* 'm' is a monster */ + switch (adtyp) { + case AD_PHYS: + break; + case AD_MAGM: + if (resists_magm(m)) + res = EXPL_MON; + break; + case AD_FIRE: + if (resists_fire(m)) + res = EXPL_MON; + break; + case AD_COLD: + if (resists_cold(m)) + res = EXPL_MON; + break; + case AD_DISN: + if ((olet == WAND_CLASS) + ? (nonliving(m->data) || is_demon(m->data) + || is_vampshifter(m)) + : !!resists_disint(m)) + res = EXPL_MON; + break; + case AD_ELEC: + if (resists_elec(m)) + res = EXPL_MON; + break; + case AD_DRST: + if (resists_poison(m)) + res = EXPL_MON; + break; + case AD_ACID: + if (resists_acid(m)) + res = EXPL_MON; + break; + default: + impossible("explosion type %d?", adtyp); + break; + } + } + return res; +} + +staticfn void +engulfer_explosion_msg(uchar adtyp, char olet) +{ + const char *adj = (char *) 0; + + if (digests(u.ustuck->data)) { + switch (adtyp) { + case AD_FIRE: + adj = "heartburn"; + break; + case AD_COLD: + adj = "chilly"; + break; + case AD_DISN: + if (olet == WAND_CLASS) + adj = "irradiated by pure energy"; + else + adj = "perforated"; + break; + case AD_ELEC: + adj = "shocked"; + break; + case AD_DRST: + adj = "poisoned"; + break; + case AD_ACID: + adj = "an upset stomach"; + break; + default: + adj = "fried"; + break; + } + pline("%s gets %s!", Monnam(u.ustuck), adj); + } else { + switch (adtyp) { + case AD_FIRE: + adj = "toasted"; + break; + case AD_COLD: + adj = "chilly"; + break; + case AD_DISN: + if (olet == WAND_CLASS) + adj = "overwhelmed by pure energy"; + else + adj = "perforated"; + break; + case AD_ELEC: + adj = "shocked"; + break; + case AD_DRST: + adj = "intoxicated"; + break; + case AD_ACID: + adj = "burned"; + break; + default: + adj = "fried"; + break; + } + pline("%s gets slightly %s!", Monnam(u.ustuck), adj); + } +} /* 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. @@ -17,6 +186,8 @@ static int explosion[3][3] = { { S_explode1, S_explode4, S_explode7 }, * did it, and with a wand, spell, or breath weapon? Object types share both * these disadvantages.... * + * Note: anything with a AT_BOOM AD_PHYS attack uses PHYS_EXPL_TYPE for type. + * * Important note about Half_physical_damage: * Unlike losehp(), explode() makes the Half_physical_damage adjustments * itself, so the caller should never have done that ahead of time. @@ -25,27 +196,30 @@ static int explosion[3][3] = { { S_explode1, S_explode4, S_explode7 }, * that Half_physical_damage only affects the damage applied to the hero. */ void -explode(x, y, type, dam, olet, expltype) -int x, y; -int type; /* the same as in zap.c; passes -(wand typ) for some WAND_CLASS */ -int dam; -char olet; -int expltype; +explode( + coordxy x, coordxy y, /* explosion's location; + * adjacent spots are also affected */ + int type, /* same as in zap.c; -(wand typ) for some WAND_CLASS */ + int dam, /* damage amount */ + char olet, /* object class or BURNING_OIL or MON_EXPLODE */ + int expltype) /* explosion type: controls color of explosion glyphs */ { int i, j, k, damu = dam; boolean starting = 1; boolean visible, any_shield; int uhurt = 0; /* 0=unhurt, 1=items damaged, 2=you and items damaged */ const char *str = (const char *) 0; - int idamres, idamnonres; struct monst *mtmp, *mdef = 0; uchar adtyp; int explmask[3][3]; /* 0=normal explosion, 1=do shieldeff, 2=do nothing */ - boolean shopdamage = FALSE, generic = FALSE, physical_dmg = FALSE, + coordxy xx, yy; + boolean shopdamage = FALSE, generic = FALSE, do_hallu = FALSE, inside_engulfer, grabbed, grabbing; coord grabxy; char hallu_buf[BUFSZ], killr_buf[BUFSZ]; short exploding_wand_typ = 0; + boolean you_exploding = (olet == MON_EXPLODE && type >= 0); + boolean didmsg = FALSE; if (olet == WAND_CLASS) { /* retributive strike */ /* 'type' is passed as (wand's object type * -1); save @@ -66,7 +240,7 @@ int expltype; type = 0; } switch (Role_switch) { - case PM_PRIEST: + case PM_CLERIC: case PM_MONK: case PM_WIZARD: damu /= 5; @@ -78,6 +252,14 @@ int expltype; default: break; } + } else if (olet == BURNING_OIL) { + /* used to provide extra information to zap_over_floor() */ + exploding_wand_typ = POT_OIL; + } else if (olet == SCROLL_CLASS) { + /* ditto */ + exploding_wand_typ = SCR_FIRE; + } else if (olet == TRAP_EXPLODE) { + type = 0; /* hardcoded to generic magic explosion */ } /* muse_unslime: SCR_FIRE */ if (expltype < 0) { @@ -92,7 +274,7 @@ int expltype; so might get hit by double damage */ grabbed = grabbing = FALSE; if (u.ustuck && !u.uswallow) { - if (Upolyd && sticks(youmonst.data)) + if (Upolyd && sticks(gy.youmonst.data)) grabbing = TRUE; else grabbed = TRUE; @@ -113,144 +295,93 @@ int expltype; * skip harm to gear of any extended targets when inflicting damage. */ - if (olet == MON_EXPLODE) { - /* when explode() is called recursively, killer.name might change so - we need to retain a copy of the current value for this explosion */ - str = strcpy(killr_buf, killer.name); + if (olet == MON_EXPLODE && !you_exploding) { + /* when explode() is called recursively, svk.killer.name might change + so retain a copy of the current value for this explosion */ + str = strcpy(killr_buf, svk.killer.name); do_hallu = (Hallucination && (strstri(str, "'s explosion") || strstri(str, "s' explosion"))); + } + if (type == PHYS_EXPL_TYPE) { + /* currently only gas spores */ adtyp = AD_PHYS; - } else + } else { + /* If str is e.g. "flaming sphere's explosion" from above, we want to + * still assign adtyp appropriately, but not replace str. */ + const char *adstr = NULL; + switch (abs(type) % 10) { case 0: - str = "magical blast"; + adstr = "magical blast"; adtyp = AD_MAGM; break; case 1: - str = (olet == BURNING_OIL) ? "burning oil" + adstr = (olet == BURNING_OIL) ? "burning oil" : (olet == SCROLL_CLASS) ? "tower of flame" : "fireball"; /* fire damage, not physical damage */ adtyp = AD_FIRE; break; case 2: - str = "ball of cold"; + adstr = "ball of cold"; adtyp = AD_COLD; break; case 4: - str = (olet == WAND_CLASS) ? "death field" - : "disintegration field"; + adstr = (olet == WAND_CLASS) ? "death field" + : "disintegration field"; adtyp = AD_DISN; break; case 5: - str = "ball of lightning"; + adstr = "ball of lightning"; adtyp = AD_ELEC; break; case 6: - str = "poison gas cloud"; + adstr = "poison gas cloud"; adtyp = AD_DRST; break; case 7: - str = "splash of acid"; + adstr = "splash of acid"; adtyp = AD_ACID; break; default: impossible("explosion base type %d?", type); return; } + if (!str) + str = adstr; + } 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; + xx = x + i - 1; + yy = y + j - 1; + if (!isok(xx, yy)) { + explmask[i][j] = EXPL_SKIP; 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] = !!Antimagic; - break; - case AD_FIRE: - explmask[i][j] = !!Fire_resistance; - break; - case AD_COLD: - explmask[i][j] = !!Cold_resistance; - break; - case AD_DISN: - explmask[i][j] = (olet == WAND_CLASS) - ? !!(nonliving(youmonst.data) - || is_demon(youmonst.data)) - : !!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; - physical_dmg = TRUE; - break; - default: - impossible("explosion type %d?", adtyp); - break; - } + } + explmask[i][j] = EXPL_NONE; + + if (u_at(xx, yy)) { + explmask[i][j] = explosionmask(&gy.youmonst, adtyp, olet); } /* can be both you and mtmp if you're swallowed or riding */ - mtmp = m_at(i + x - 1, j + y - 1); - if (!mtmp && i + x - 1 == u.ux && j + y - 1 == u.uy) + mtmp = m_at(xx, yy); + if (!mtmp && u_at(xx, yy)) mtmp = u.usteed; + if (mtmp && DEADMONSTER(mtmp)) + mtmp = 0; if (mtmp) { - if (DEADMONSTER(mtmp)) - explmask[i][j] = 2; - else - switch (adtyp) { - case AD_PHYS: - break; - case AD_MAGM: - explmask[i][j] |= 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_DISN: - explmask[i][j] |= (olet == WAND_CLASS) - ? (nonliving(mtmp->data) - || is_demon(mtmp->data) - || is_vampshifter(mtmp)) - : 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; - } + explmask[i][j] |= explosionmask(mtmp, adtyp, olet); } - if (mtmp && cansee(i + x - 1, j + y - 1) && !canspotmon(mtmp)) - map_invisible(i + x - 1, j + y - 1); + + if (mtmp && cansee(xx, yy) && !canspotmon(mtmp)) + map_invisible(xx, yy); else if (!mtmp) - (void) unmap_invisible(i + x - 1, j + y - 1); - if (cansee(i + x - 1, j + y - 1)) + (void) unmap_invisible(xx, yy); + if (cansee(xx, yy)) visible = TRUE; - if (explmask[i][j] == 1) + if ((explmask[i][j] & (EXPL_MON | EXPL_HERO)) != 0) any_shield = TRUE; } @@ -258,11 +389,13 @@ int expltype; /* Start the explosion */ for (i = 0; i < 3; i++) for (j = 0; j < 3; j++) { - if (explmask[i][j] == 2) + if (explmask[i][j] == EXPL_SKIP) continue; + xx = x + i - 1; + yy = y + j - 1; tmp_at(starting ? DISP_BEAM : DISP_CHANGE, explosion_to_glyph(expltype, explosion[i][j])); - tmp_at(i + x - 1, j + y - 1); + tmp_at(xx, yy); starting = 0; } curs_on_u(); /* will flush screen and output */ @@ -271,65 +404,92 @@ int expltype; for (k = 0; k < SHIELD_COUNT; k++) { for (i = 0; i < 3; i++) for (j = 0; j < 3; j++) { - if (explmask[i][j] == 1) + xx = x + i - 1; + yy = y + j - 1; + if ((explmask[i][j] & (EXPL_MON | EXPL_HERO)) != 0) /* * Bypass tmp_at() and send the shield glyphs * directly to the buffered screen. tmp_at() * will clean up the location for us later. */ - show_glyph(i + x - 1, j + y - 1, + show_glyph(xx, yy, cmap_to_glyph(shield_static[k])); } curs_on_u(); /* will flush screen and output */ - delay_output(); + nh_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) - show_glyph( - i + x - 1, j + y - 1, - explosion_to_glyph(expltype, explosion[i][j])); + xx = x + i - 1; + yy = y + j - 1; + if ((explmask[i][j] & (EXPL_MON | EXPL_HERO)) != 0) + show_glyph(xx, yy, + explosion_to_glyph(expltype, + explosion[i][j])); } } else { /* delay a little bit. */ - delay_output(); - delay_output(); + nh_delay_output(); + nh_delay_output(); } tmp_at(DISP_END, 0); /* clear the explosion */ } else { - if (olet == MON_EXPLODE) { + if (olet == MON_EXPLODE || olet == TRAP_EXPLODE) { str = "explosion"; generic = TRUE; } - if (!Deaf && olet != SCROLL_CLASS) + if (!Deaf && olet != SCROLL_CLASS) { + Soundeffect(se_blast, 75); You_hear("a blast."); + didmsg = TRUE; + } } - if (dam) - for (i = 0; i < 3; i++) + if (!Deaf && !didmsg) + pline("Boom!"); + + /* apply effects to monsters and floor objects first, in case the + damage to the hero is fatal and leaves bones */ + if (dam) { + for (i = 0; i < 3; i++) { for (j = 0; j < 3; j++) { - if (explmask[i][j] == 2) + int itemdmg = 0; + + if (explmask[i][j] == EXPL_SKIP) continue; - if (i + x - 1 == u.ux && j + y - 1 == u.uy) - uhurt = (explmask[i][j] == 1) ? 1 : 2; - /* for inside_engulfer, only is affected */ - else if (inside_engulfer) + xx = x + i - 1; + yy = y + j - 1; + if (u_at(xx, yy)) { + uhurt = ((explmask[i][j] & EXPL_HERO) != 0) ? 1 : 2; + /* If the player is attacking via polyself into something + * with an explosion attack, leave them (and their gear) + * unharmed, to avoid punishing them from using such + * polyforms creatively */ + if (!svc.context.mon_moving && you_exploding) + uhurt = 0; + } else if (inside_engulfer) { + /* for inside_engulfer, only is affected */ continue; - idamres = idamnonres = 0; - if (type >= 0 && !u.uswallow) - (void) zap_over_floor((xchar) (i + x - 1), - (xchar) (j + y - 1), type, - &shopdamage, exploding_wand_typ); - - mtmp = m_at(i + x - 1, j + y - 1); - if (!mtmp && i + x - 1 == u.ux && j + y - 1 == u.uy) + } + + /* Affect the floor unless the player caused the explosion + * from inside their engulfer. */ + if (!(u.uswallow && !svc.context.mon_moving)) + (void) zap_over_floor(xx, yy, type, + &shopdamage, FALSE, + exploding_wand_typ); + + mtmp = m_at(xx, yy); + if (!mtmp && u_at(xx, yy)) mtmp = u.usteed; if (!mtmp) continue; if (do_hallu) { + int tryct = 0; + /* replace "gas spore" with a different description for each target (we can't distinguish personal names like "Barney" here in order to suppress "the" below, @@ -337,101 +497,50 @@ int expltype; do { Sprintf(hallu_buf, "%s explosion", s_suffix(rndmonnam((char *) 0))); - } while (*hallu_buf != lowc(*hallu_buf)); + } while (*hallu_buf != lowc(*hallu_buf) && ++tryct < 20); str = hallu_buf; } - if (u.uswallow && mtmp == u.ustuck) { - const char *adj = (char *) 0; - - if (is_animal(u.ustuck->data)) { - switch (adtyp) { - case AD_FIRE: - adj = "heartburn"; - break; - case AD_COLD: - adj = "chilly"; - break; - case AD_DISN: - if (olet == WAND_CLASS) - adj = "irradiated by pure energy"; - else - adj = "perforated"; - break; - case AD_ELEC: - adj = "shocked"; - break; - case AD_DRST: - adj = "poisoned"; - break; - case AD_ACID: - adj = "an upset stomach"; - break; - default: - adj = "fried"; - break; - } - pline("%s gets %s!", Monnam(u.ustuck), adj); - } else { - switch (adtyp) { - case AD_FIRE: - adj = "toasted"; - break; - case AD_COLD: - adj = "chilly"; - break; - case AD_DISN: - if (olet == WAND_CLASS) - adj = "overwhelmed by pure energy"; - else - adj = "perforated"; - break; - case AD_ELEC: - adj = "shocked"; - break; - case AD_DRST: - adj = "intoxicated"; - break; - case AD_ACID: - adj = "burned"; - break; - default: - adj = "fried"; - break; - } - pline("%s gets slightly %s!", Monnam(u.ustuck), adj); - } - } else if (cansee(i + x - 1, j + y - 1)) { + if (engulfing_u(mtmp)) { + engulfer_explosion_msg(adtyp, olet); + } else if (cansee(xx, yy)) { if (mtmp->m_ap_type) seemimic(mtmp); pline("%s is caught in the %s!", Monnam(mtmp), str); } - 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); + itemdmg = destroy_items(mtmp, (int) adtyp, dam); + if (adtyp == AD_FIRE) { + (void) burnarmor(mtmp); + ignite_items(mtmp->minvent); + } - if (explmask[i][j] == 1) { - golemeffects(mtmp, (int) adtyp, dam + idamres); - mtmp->mhp -= idamnonres; + if ((explmask[i][j] & EXPL_MON) != 0) { + /* Damage from ring/wand explosion isn't itself + * electrical in nature, nor is damage from freezing + * potion really cold in nature, nor is damage from + * boiling potion or exploding oil; only burning items + * damage is the "same type" as the explosion. Because + * this is imperfect and marginal (burning items only + * deal 1 damage), ignore it for golemeffects(). */ + golemeffects(mtmp, (int) adtyp, dam); + mtmp->mhp -= itemdmg; /* item destruction dmg */ } else { - /* call resist with 0 and do damage manually so 1) we can + /* 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 + * can call mondied, not killed, if it's not your blast. */ int mdam = dam; if (resist(mtmp, olet, 0, FALSE)) { - /* inside_engulfer: == */ - if (cansee(i + x - 1, j + y - 1) || inside_engulfer) + /* inside_engulfer: == */ + if (cansee(xx, yy) || inside_engulfer) pline("%s resists the %s!", Monnam(mtmp), str); mdam = (dam + 1) / 2; } /* if grabber is reaching into hero's spot and hero's spot is within explosion radius, grabber gets hit by double damage */ - if (grabbed && mtmp == u.ustuck && distu(x, y) <= 2) + if (grabbed && mtmp == u.ustuck && next2u(x, y)) mdam *= 2; /* being resistant to opposite type of damage makes target more vulnerable to current type of damage @@ -441,15 +550,14 @@ int expltype; mdam *= 2; else if (resists_fire(mtmp) && adtyp == AD_COLD) mdam *= 2; - mtmp->mhp -= mdam; - mtmp->mhp -= (idamres + idamnonres); + mtmp->mhp -= mdam + itemdmg; } if (DEADMONSTER(mtmp)) { int xkflg = ((adtyp == AD_FIRE && completelyburns(mtmp->data)) ? XKILL_NOCORPSE : 0); - if (!context.mon_moving) { + if (!svc.context.mon_moving) { xkilled(mtmp, XKILL_GIVEMSG | xkflg); } else if (mdef && mtmp == mdef) { /* 'mdef' killed self trying to cure being turned @@ -471,11 +579,13 @@ int expltype; adtyp = AD_RBRE; /* no corpse */ monkilled(mtmp, "", (int) adtyp); } - } else if (!context.mon_moving) { + } else if (!svc.context.mon_moving) { /* all affected monsters, even if mdef is set */ setmangry(mtmp, TRUE); } } + } + } /* Do your injury last */ if (uhurt) { @@ -498,15 +608,13 @@ int expltype; if (Invulnerable) { damu = 0; You("are unharmed!"); - } else if (adtyp == AD_PHYS || physical_dmg) + } else if (adtyp == AD_PHYS || adtyp == AD_ACID) damu = Maybe_Half_Phys(damu); - if (adtyp == AD_FIRE) - (void) burnarmor(&youmonst); - 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); + if (adtyp == AD_FIRE) { + (void) burnarmor(&gy.youmonst); + ignite_items(gi.invent); + } + (void) destroy_items(&gy.youmonst, (int) adtyp, dam); ugolemeffects((int) adtyp, damu); if (uhurt == 2) { @@ -521,29 +629,41 @@ int expltype; u.mh -= damu; else u.uhp -= damu; - context.botl = 1; + disp.botl = TRUE; } + /* You resisted the damage, lets not keep that to ourselves */ + if (uhurt == 1) + monstseesu_ad(adtyp); + else + monstunseesu_ad(adtyp); + if (u.uhp <= 0 || (Upolyd && u.mh <= 0)) { if (Upolyd) { rehumanize(); } else { if (olet == MON_EXPLODE) { if (generic) /* explosion was unseen; str=="explosion", */ - ; /* killer.name=="gas spore's explosion" */ - else if (str != killer.name && str != hallu_buf) - Strcpy(killer.name, str); - killer.format = KILLED_BY_AN; + ; /* svk.killer.name=="gas spore's explosion". */ + else if (str != svk.killer.name && str != hallu_buf) + Strcpy(svk.killer.name, str); + svk.killer.format = KILLED_BY_AN; + } else if (olet == TRAP_EXPLODE) { + svk.killer.format = NO_KILLER_PREFIX; + Snprintf(svk.killer.name, sizeof svk.killer.name, + "caught %sself in a %s", uhim(), + str); } else if (type >= 0 && olet != SCROLL_CLASS) { - killer.format = NO_KILLER_PREFIX; - Sprintf(killer.name, "caught %sself in %s own %s", uhim(), - uhis(), str); + svk.killer.format = NO_KILLER_PREFIX; + Snprintf(svk.killer.name, sizeof svk.killer.name, + "caught %sself in %s own %s", uhim(), + uhis(), str); } else { - killer.format = (!strcmpi(str, "tower of flame") + svk.killer.format = (!strcmpi(str, "tower of flame") || !strcmpi(str, "fireball")) ? KILLED_BY_AN : KILLED_BY; - Strcpy(killer.name, str); + Strcpy(svk.killer.name, str); } if (iflags.last_msg == PLNMSG_CAUGHT_IN_EXPLOSION || iflags.last_msg == PLNMSG_TOWER_OF_FLAME) /*seffects()*/ @@ -578,8 +698,8 @@ int expltype; struct scatter_chain { struct scatter_chain *next; /* pointer to next scatter item */ struct obj *obj; /* pointer to the object */ - xchar ox; /* location of */ - xchar oy; /* item */ + coordxy ox; /* location of */ + coordxy oy; /* item */ schar dx; /* direction of */ schar dy; /* travel */ int range; /* range of object */ @@ -598,20 +718,21 @@ struct scatter_chain { /* returns number of scattered objects */ long -scatter(sx, sy, blastforce, scflags, obj) -int sx, sy; /* location of objects to scatter */ -int blastforce; /* force behind the scattering */ -unsigned int scflags; -struct obj *obj; /* only scatter this obj */ +scatter( + coordxy sx, coordxy sy, /* location of objects to scatter */ + int blastforce, /* force behind the scattering */ + unsigned int scflags, + struct obj *obj) /* only scatter this obj */ { - register struct obj *otmp; - register int tmp; + struct obj *otmp; + int tmp; int farthest = 0; uchar typ; long qtmp; boolean used_up; boolean individual_object = obj ? TRUE : FALSE; - struct monst *mtmp; + boolean shop_origin, lostgoods = FALSE; + struct monst *mtmp, *shkp = 0; struct scatter_chain *stmp, *stmp2 = 0; struct scatter_chain *schain = (struct scatter_chain *) 0; long total = 0L; @@ -620,9 +741,17 @@ struct obj *obj; /* only scatter this obj */ impossible("scattered object <%d,%d> not at scatter site <%d,%d>", obj->ox, obj->oy, sx, sy); - while ((otmp = (individual_object ? obj : level.objects[sx][sy])) != 0) { + shop_origin = ((shkp = shop_keeper(*in_rooms(sx, sy, SHOPBASE))) != 0 + && costly_spot(sx, sy)); + if (shop_origin) + credit_report(shkp, 0, TRUE); /* establish baseline, without msgs */ + + while ((otmp = (individual_object ? obj + : svl.level.objects[sx][sy])) != 0) { if (otmp == uball || otmp == uchain) { boolean waschain = (otmp == uchain); + + Soundeffect(se_chain_shatters, 25); pline_The("chain shatters!"); unpunish(); if (waschain) @@ -645,10 +774,12 @@ struct obj *obj; /* only scatter this obj */ && (otmp->otyp == BOULDER || otmp->otyp == STATUE) && rn2(10)) { if (otmp->otyp == BOULDER) { - if (cansee(sx, sy)) + if (cansee(sx, sy)) { pline("%s apart.", Tobjnam(otmp, "break")); - else + } else { + Soundeffect(se_stone_breaking, 100); You_hear("stone breaking."); + } fracture_rock(otmp); place_object(otmp, sx, sy); if ((otmp = sobj_at(BOULDER, sx, sy)) != 0) { @@ -661,10 +792,12 @@ struct obj *obj; /* only scatter this obj */ if ((trap = t_at(sx, sy)) && trap->ttyp == STATUE_TRAP) deltrap(trap); - if (cansee(sx, sy)) + if (cansee(sx, sy)) { pline("%s.", Tobjnam(otmp, "crumble")); - else + } else { + Soundeffect(se_stone_crumbling, 100); You_hear("stone crumbling."); + } (void) break_statue(otmp); place_object(otmp, sx, sy); /* put fragments on floor */ } @@ -675,7 +808,7 @@ struct obj *obj; /* only scatter this obj */ } else if ((scflags & MAY_DESTROY) != 0 && (!rn2(10) || (objects[otmp->otyp].oc_material == GLASS || otmp->otyp == EGG))) { - if (breaks(otmp, (xchar) sx, (xchar) sy)) + if (breaks(otmp, sx, sy)) used_up = TRUE; } @@ -685,7 +818,7 @@ struct obj *obj; /* only scatter this obj */ stmp->obj = otmp; stmp->ox = sx; stmp->oy = sy; - tmp = rn2(8); /* get the direction */ + tmp = rn2(N_DIRS); /* get the direction */ stmp->dx = xdir[tmp]; stmp->dy = ydir[tmp]; tmp = blastforce - (otmp->owt / 40); @@ -706,19 +839,23 @@ struct obj *obj; /* only scatter this obj */ while (farthest-- > 0) { for (stmp = schain; stmp; stmp = stmp->next) { if ((stmp->range-- > 0) && (!stmp->stopped)) { - bhitpos.x = stmp->ox + stmp->dx; - bhitpos.y = stmp->oy + stmp->dy; - typ = levl[bhitpos.x][bhitpos.y].typ; - if (!isok(bhitpos.x, bhitpos.y)) { - bhitpos.x -= stmp->dx; - bhitpos.y -= stmp->dy; + gt.thrownobj = stmp->obj; /* mainly in case it kills hero */ + gb.bhitpos.x = stmp->ox + stmp->dx; + gb.bhitpos.y = stmp->oy + stmp->dy; + if (isok(gb.bhitpos.x, gb.bhitpos.y)) + typ = levl[gb.bhitpos.x][gb.bhitpos.y].typ; + else + typ = STONE; + if (!isok(gb.bhitpos.x, gb.bhitpos.y)) { + gb.bhitpos.x -= stmp->dx; + gb.bhitpos.y -= stmp->dy; stmp->stopped = TRUE; } else if (!ZAP_POS(typ) - || closed_door(bhitpos.x, bhitpos.y)) { - bhitpos.x -= stmp->dx; - bhitpos.y -= stmp->dy; + || closed_door(gb.bhitpos.x, gb.bhitpos.y)) { + gb.bhitpos.x -= stmp->dx; + gb.bhitpos.y -= stmp->dy; stmp->stopped = TRUE; - } else if ((mtmp = m_at(bhitpos.x, bhitpos.y)) != 0) { + } else if ((mtmp = m_at(gb.bhitpos.x, gb.bhitpos.y)) != 0) { if (scflags & MAY_HITMON) { stmp->range--; if (ohitmon(mtmp, stmp->obj, 1, FALSE)) { @@ -726,16 +863,17 @@ struct obj *obj; /* only scatter this obj */ stmp->stopped = TRUE; } } - } else if (bhitpos.x == u.ux && bhitpos.y == u.uy) { + } else if (u_at(gb.bhitpos.x, gb.bhitpos.y)) { if (scflags & MAY_HITYOU) { - int hitvalu, hitu; + int dam, hitvalu, hitu; - if (multi) + if (gm.multi) nomul(0); + dam = dmgval(stmp->obj, &gy.youmonst); hitvalu = 8 + stmp->obj->spe; - if (bigmonst(youmonst.data)) + if (bigmonst(gy.youmonst.data)) hitvalu++; - hitu = thitu(hitvalu, dmgval(stmp->obj, &youmonst), + hitu = thitu(hitvalu, Maybe_Half_Phys(dam), &stmp->obj, (char *) 0); if (!stmp->obj) stmp->stopped = TRUE; @@ -746,34 +884,65 @@ struct obj *obj; /* only scatter this obj */ } } else { if (scflags & VIS_EFFECTS) { - /* tmp_at(bhitpos.x, bhitpos.y); */ - /* delay_output(); */ + /* tmp_at(gb.bhitpos.x, gb.bhitpos.y); */ + /* nh_delay_output(); */ } } - stmp->ox = bhitpos.x; - stmp->oy = bhitpos.y; + stmp->ox = gb.bhitpos.x; + stmp->oy = gb.bhitpos.y; + if (IS_SINK(levl[stmp->ox][stmp->oy].typ)) + stmp->stopped = TRUE; + gt.thrownobj = (struct obj *) 0; } } } for (stmp = schain; stmp; stmp = stmp2) { - int x, y; + coordxy x, y; + boolean obj_left_shop = FALSE; stmp2 = stmp->next; x = stmp->ox; y = stmp->oy; if (stmp->obj) { - if (x != sx || y != sy) + if (x != sx || y != sy) { total += stmp->obj->quan; - place_object(stmp->obj, x, y); - stackobj(stmp->obj); + obj_left_shop = (shop_origin && !costly_spot(x, y)); + } + if (!flooreffects(stmp->obj, x, y, "land")) { + if (obj_left_shop + && strchr(u.urooms, *in_rooms(u.ux, u.uy, SHOPBASE))) { + /* At the moment this only takes on gold. While it is + simple enough to call addtobill for other items that + leave the shop due to scatter(), by default the hero + will get billed for the full shopkeeper asking-price + on the object's way out of shop. That can leave the + hero in a pickle. Even if the hero then manages to + retrieve the item and drop it back inside the shop, + the owed charges will only be reduced at that point + by the lesser shopkeeper buying-price. + The non-gold situation will likely get adjusted + further. + */ + if (stmp->obj->otyp == GOLD_PIECE) { + addtobill(stmp->obj, FALSE, FALSE, TRUE); + lostgoods = TRUE; + } + } + place_object(stmp->obj, x, y); + stackobj(stmp->obj); + } } free((genericptr_t) stmp); newsym(x, y); } newsym(sx, sy); - if (sx == u.ux && sy == u.uy && u.uundetected - && hides_under(youmonst.data)) - (void) hideunder(&youmonst); + if (u_at(sx, sy) && u.uundetected && hides_under(gy.youmonst.data)) + (void) hideunder(&gy.youmonst); + if (((mtmp = m_at(sx, sy)) != 0) && mtmp->mtrapped) + mtmp->mtrapped = 0; + maybe_unhide_at(sx, sy); + if (lostgoods) /* implies shop_origin and therefore shkp valid */ + credit_report(shkp, 1, FALSE); return total; } @@ -790,9 +959,7 @@ struct obj *obj; /* only scatter this obj */ * For now, just perform a "regular" explosion. */ void -splatter_burning_oil(x, y, diluted_oil) -int x, y; -boolean diluted_oil; +splatter_burning_oil(coordxy x, coordxy y, boolean diluted_oil) { int dmg = d(diluted_oil ? 3 : 4, 4); @@ -804,16 +971,99 @@ boolean diluted_oil; /* lit potion of oil is exploding; extinguish it as a light source before possibly killing the hero and attempting to save bones */ void -explode_oil(obj, x, y) -struct obj *obj; -int x, y; +explode_oil(struct obj *obj, coordxy x, coordxy y) { boolean diluted_oil = obj->odiluted; if (!obj->lamplit) impossible("exploding unlit oil"); end_burn(obj, TRUE); + obj->how_lost = LOST_EXPLODING; splatter_burning_oil(x, y, diluted_oil); } +/* Convert a damage type into an explosion display type. */ +int +adtyp_to_expltype(const int adtyp) +{ + switch(adtyp) { + case AD_ELEC: + /* Electricity isn't magical, but there currently isn't an electric + * explosion type. Magical is the next best thing. */ + case AD_SPEL: + case AD_DREN: + case AD_ENCH: + return EXPL_MAGICAL; + case AD_FIRE: + return EXPL_FIERY; + case AD_COLD: + return EXPL_FROSTY; + case AD_DRST: + case AD_DRDX: + case AD_DRCO: + case AD_DISE: + case AD_PEST: + case AD_PHYS: /* gas spore */ + return EXPL_NOXIOUS; + default: + impossible("adtyp_to_expltype: bad explosion type %d", adtyp); + return EXPL_FIERY; + } +} + +/* A monster explodes in a way that produces a real explosion (e.g. a sphere + * or gas spore, not a yellow light or similar). + * This is some common code between explmu() and explmm(). + */ +void +mon_explodes( + struct monst *mon, + struct attack *mattk) +{ + int dmg; + int type; + if (mattk->damn) { + dmg = d((int) mattk->damn, (int) mattk->damd); + } + else if (mattk->damd) { + dmg = d((int) mon->data->mlevel + 1, (int) mattk->damd); + } + else { + dmg = 0; + } + + if (mattk->adtyp == AD_PHYS) { + type = PHYS_EXPL_TYPE; + } + else if (mattk->adtyp >= AD_MAGM && mattk->adtyp <= AD_SPC2) { + /* The -1, +20, *-1 math is to set it up as a 'monster breath' type + * for the explosions (it isn't, but this is the closest analogue). */ + /* FIXME: there are macros for kind of thing... */ + type = -((mattk->adtyp - 1) + 20); + } + else { + impossible("unknown type for mon_explode %d", mattk->adtyp); + return; + } + + /* Kill it now so it won't appear to be caught in its own explosion. + * Must check to see if already dead - which happens if this is called + * from an AT_BOOM attack upon death. */ + if (!DEADMONSTER(mon)) { + mondead(mon); + } + + /* This might end up killing you, too; you never know... + * also, it is used in explode() messages */ + Sprintf(svk.killer.name, "%s explosion", + s_suffix(pmname(mon->data, Mgender(mon)))); + svk.killer.format = KILLED_BY_AN; + + explode(mon->mx, mon->my, type, dmg, MON_EXPLODE, + adtyp_to_expltype(mattk->adtyp)); + + /* reset killer */ + svk.killer.name[0] = '\0'; +} + /*explode.c*/ diff --git a/src/extralev.c b/src/extralev.c index 54e935e08..a7cdccaa9 100644 --- a/src/extralev.c +++ b/src/extralev.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 extralev.c $NHDT-Date: 1446975468 2015/11/08 09:37:48 $ $NHDT-Branch: master $:$NHDT-Revision: 1.12 $ */ +/* NetHack 5.0 extralev.c $NHDT-Date: 1737345573 2025/01/19 19:59:33 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.28 $ */ /* Copyright 1988, 1989 by Ken Arromdee */ /* NetHack may be freely redistributed. See license for details. */ @@ -8,30 +8,20 @@ #include "hack.h" -struct rogueroom { - xchar rlx, rly; - xchar dx, dy; - boolean real; - uchar doortable; - int nroom; /* Only meaningful for "real" rooms */ -}; -#define UP 1 -#define DOWN 2 -#define LEFT 4 -#define RIGHT 8 +#define XL_UP 1 +#define XL_DOWN 2 +#define XL_LEFT 4 +#define XL_RIGHT 8 -static NEARDATA struct rogueroom r[3][3]; -STATIC_DCL void FDECL(roguejoin, (int, int, int, int, int)); -STATIC_DCL void FDECL(roguecorr, (int, int, int)); -STATIC_DCL void FDECL(miniwalk, (int, int)); +staticfn void roguejoin(coordxy, coordxy, coordxy, coordxy, int); +staticfn void roguecorr(coordxy, coordxy, int); +staticfn void miniwalk(coordxy, coordxy); -STATIC_OVL -void -roguejoin(x1, y1, x2, y2, horiz) -int x1, y1, x2, y2; -int horiz; +staticfn void +roguejoin(coordxy x1, coordxy y1, coordxy x2, coordxy y2, int horiz) { - register int x, y, middle; + coordxy x, y, middle; + if (horiz) { middle = x1 + rn2(x2 - x1 + 1); for (x = min(x1, middle); x <= max(x1, middle); x++) @@ -51,28 +41,26 @@ int horiz; } } -STATIC_OVL -void -roguecorr(x, y, dir) -int x, y, dir; +staticfn void +roguecorr(coordxy x, coordxy y, int dir) { - register int fromx, fromy, tox, toy; + coordxy fromx, fromy, tox, toy; - if (dir == DOWN) { - r[x][y].doortable &= ~DOWN; - if (!r[x][y].real) { - fromx = r[x][y].rlx; - fromy = r[x][y].rly; + if (dir == XL_DOWN) { + gr.r[x][y].doortable &= ~XL_DOWN; + if (!gr.r[x][y].real) { + fromx = gr.r[x][y].rlx; + fromy = gr.r[x][y].rly; fromx += 1 + 26 * x; fromy += 7 * y; } else { - fromx = r[x][y].rlx + rn2(r[x][y].dx); - fromy = r[x][y].rly + r[x][y].dy; + fromx = gr.r[x][y].rlx + rn2(gr.r[x][y].dx); + fromy = gr.r[x][y].rly + gr.r[x][y].dy; fromx += 1 + 26 * x; fromy += 7 * y; if (!IS_WALL(levl[fromx][fromy].typ)) impossible("down: no wall at %d,%d?", fromx, fromy); - dodoor(fromx, fromy, &rooms[r[x][y].nroom]); + dodoor(fromx, fromy, &svr.rooms[gr.r[x][y].nroom]); levl[fromx][fromy].doormask = D_NODOOR; fromy++; } @@ -81,40 +69,40 @@ int x, y, dir; return; } y++; - r[x][y].doortable &= ~UP; - if (!r[x][y].real) { - tox = r[x][y].rlx; - toy = r[x][y].rly; + gr.r[x][y].doortable &= ~XL_UP; + if (!gr.r[x][y].real) { + tox = gr.r[x][y].rlx; + toy = gr.r[x][y].rly; tox += 1 + 26 * x; toy += 7 * y; } else { - tox = r[x][y].rlx + rn2(r[x][y].dx); - toy = r[x][y].rly - 1; + tox = gr.r[x][y].rlx + rn2(gr.r[x][y].dx); + toy = gr.r[x][y].rly - 1; tox += 1 + 26 * x; toy += 7 * y; if (!IS_WALL(levl[tox][toy].typ)) impossible("up: no wall at %d,%d?", tox, toy); - dodoor(tox, toy, &rooms[r[x][y].nroom]); + dodoor(tox, toy, &svr.rooms[gr.r[x][y].nroom]); levl[tox][toy].doormask = D_NODOOR; toy--; } roguejoin(fromx, fromy, tox, toy, FALSE); return; - } else if (dir == RIGHT) { - r[x][y].doortable &= ~RIGHT; - if (!r[x][y].real) { - fromx = r[x][y].rlx; - fromy = r[x][y].rly; + } else if (dir == XL_RIGHT) { + gr.r[x][y].doortable &= ~XL_RIGHT; + if (!gr.r[x][y].real) { + fromx = gr.r[x][y].rlx; + fromy = gr.r[x][y].rly; fromx += 1 + 26 * x; fromy += 7 * y; } else { - fromx = r[x][y].rlx + r[x][y].dx; - fromy = r[x][y].rly + rn2(r[x][y].dy); + fromx = gr.r[x][y].rlx + gr.r[x][y].dx; + fromy = gr.r[x][y].rly + rn2(gr.r[x][y].dy); fromx += 1 + 26 * x; fromy += 7 * y; if (!IS_WALL(levl[fromx][fromy].typ)) impossible("down: no wall at %d,%d?", fromx, fromy); - dodoor(fromx, fromy, &rooms[r[x][y].nroom]); + dodoor(fromx, fromy, &svr.rooms[gr.r[x][y].nroom]); levl[fromx][fromy].doormask = D_NODOOR; fromx++; } @@ -123,20 +111,20 @@ int x, y, dir; return; } x++; - r[x][y].doortable &= ~LEFT; - if (!r[x][y].real) { - tox = r[x][y].rlx; - toy = r[x][y].rly; + gr.r[x][y].doortable &= ~XL_LEFT; + if (!gr.r[x][y].real) { + tox = gr.r[x][y].rlx; + toy = gr.r[x][y].rly; tox += 1 + 26 * x; toy += 7 * y; } else { - tox = r[x][y].rlx - 1; - toy = r[x][y].rly + rn2(r[x][y].dy); + tox = gr.r[x][y].rlx - 1; + toy = gr.r[x][y].rly + rn2(gr.r[x][y].dy); tox += 1 + 26 * x; toy += 7 * y; if (!IS_WALL(levl[tox][toy].typ)) impossible("left: no wall at %d,%d?", tox, toy); - dodoor(tox, toy, &rooms[r[x][y].nroom]); + dodoor(tox, toy, &svr.rooms[gr.r[x][y].nroom]); levl[tox][toy].doormask = D_NODOOR; tox--; } @@ -147,28 +135,26 @@ int x, y, dir; } /* Modified walkfrom() from mkmaze.c */ -STATIC_OVL -void -miniwalk(x, y) -int x, y; +staticfn void +miniwalk(coordxy x, coordxy y) { - register int q, dir; + int q, dir; int dirs[4]; while (1) { q = 0; -#define doorhere (r[x][y].doortable) - if (x > 0 && (!(doorhere & LEFT)) - && (!r[x - 1][y].doortable || !rn2(10))) +#define doorhere (gr.r[x][y].doortable) + if (x > 0 && (!(doorhere & XL_LEFT)) + && (!gr.r[x - 1][y].doortable || !rn2(10))) dirs[q++] = 0; - if (x < 2 && (!(doorhere & RIGHT)) - && (!r[x + 1][y].doortable || !rn2(10))) + if (x < 2 && (!(doorhere & XL_RIGHT)) + && (!gr.r[x + 1][y].doortable || !rn2(10))) dirs[q++] = 1; - if (y > 0 && (!(doorhere & UP)) - && (!r[x][y - 1].doortable || !rn2(10))) + if (y > 0 && (!(doorhere & XL_UP)) + && (!gr.r[x][y - 1].doortable || !rn2(10))) dirs[q++] = 2; - if (y < 2 && (!(doorhere & DOWN)) - && (!r[x][y + 1].doortable || !rn2(10))) + if (y < 2 && (!(doorhere & XL_DOWN)) + && (!gr.r[x][y + 1].doortable || !rn2(10))) dirs[q++] = 3; /* Rogue levels aren't just 3 by 3 mazes; they have some extra * connections, thus that 1/10 chance @@ -178,34 +164,35 @@ int x, y; dir = dirs[rn2(q)]; switch (dir) { /* Move in direction */ case 0: - doorhere |= LEFT; + doorhere |= XL_LEFT; x--; - doorhere |= RIGHT; + doorhere |= XL_RIGHT; break; case 1: - doorhere |= RIGHT; + doorhere |= XL_RIGHT; x++; - doorhere |= LEFT; + doorhere |= XL_LEFT; break; case 2: - doorhere |= UP; + doorhere |= XL_UP; y--; - doorhere |= DOWN; + doorhere |= XL_DOWN; break; case 3: - doorhere |= DOWN; + doorhere |= XL_DOWN; y++; - doorhere |= UP; + doorhere |= XL_UP; break; } miniwalk(x, y); } +#undef doorhere } void -makeroguerooms() +makeroguerooms(void) { - register int x, y; + coordxy x, y; /* Rogue levels are structured 3 by 3, with each section containing * a room or an intersection. The minimum width is 2 each way. * One difference between these and "real" Rogue levels: real Rogue @@ -222,15 +209,15 @@ makeroguerooms() * Room height may be 2-4 (2-5 on last row), length 2-23 (not * counting walls). */ -#define here r[x][y] +#define here gr.r[x][y] - nroom = 0; + svn.nroom = 0; for (y = 0; y < 3; y++) for (x = 0; x < 3; x++) { /* Note: we want to insure at least 1 room. So, if the * first 8 are all dummies, force the last to be a room. */ - if (!rn2(5) && (nroom || (x < 2 && y < 2))) { + if (!rn2(5) && (svn.nroom || (x < 2 && y < 2))) { /* Arbitrary: dummy rooms may only go where real * ones do. */ @@ -245,19 +232,19 @@ makeroguerooms() /* boundaries of room floor */ here.rlx = rnd(23 - here.dx + 1); here.rly = rnd(((y == 2) ? 5 : 4) - here.dy + 1); - nroom++; + svn.nroom++; } here.doortable = 0; } miniwalk(rn2(3), rn2(3)); - nroom = 0; + svn.nroom = 0; for (y = 0; y < 3; y++) for (x = 0; x < 3; x++) { if (here.real) { /* Make a room */ - int lowx, lowy, hix, hiy; + coordxy lowx, lowy, hix, hiy; - r[x][y].nroom = nroom; - smeq[nroom] = nroom; + gr.r[x][y].nroom = svn.nroom; + gs.smeq[svn.nroom] = svn.nroom; lowx = 1 + 26 * x + here.rlx; lowy = 7 * y + here.rly; @@ -275,20 +262,20 @@ makeroguerooms() /* Now, add connecting corridors. */ for (y = 0; y < 3; y++) for (x = 0; x < 3; x++) { - if (here.doortable & DOWN) - roguecorr(x, y, DOWN); - if (here.doortable & RIGHT) - roguecorr(x, y, RIGHT); - if (here.doortable & LEFT) + if (here.doortable & XL_DOWN) + roguecorr(x, y, XL_DOWN); + if (here.doortable & XL_RIGHT) + roguecorr(x, y, XL_RIGHT); + if (here.doortable & XL_LEFT) impossible("left end of %d, %d never connected?", x, y); - if (here.doortable & UP) + if (here.doortable & XL_UP) impossible("up end of %d, %d never connected?", x, y); } +#undef here } void -corr(x, y) -int x, y; +corr(coordxy x, coordxy y) { if (rn2(50)) { levl[x][y].typ = CORR; @@ -298,22 +285,23 @@ int x, y; } void -makerogueghost() +makerogueghost(void) { - register struct monst *ghost; + struct monst *ghost; struct obj *ghostobj; struct mkroom *croom; - int x, y; + coordxy x, y; - if (!nroom) + if (!svn.nroom) return; /* Should never happen */ - croom = &rooms[rn2(nroom)]; + croom = &svr.rooms[rn2(svn.nroom)]; x = somex(croom); y = somey(croom); if (!(ghost = makemon(&mons[PM_GHOST], x, y, NO_MM_FLAGS))) return; ghost->msleeping = 1; ghost = christen_monst(ghost, roguename()); + nhUse(ghost); if (rn2(4)) { ghostobj = mksobj_at(FOOD_RATION, x, y, FALSE, FALSE); diff --git a/src/files.c b/src/files.c index 3574f44f5..1891ba604 100644 --- a/src/files.c +++ b/src/files.c @@ -1,18 +1,44 @@ -/* NetHack 3.6 files.c $NHDT-Date: 1576626110 2019/12/17 23:41:50 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.276 $ */ +/* NetHack 5.0 files.c $NHDT-Date: 1740532826 2025/02/25 17:20:26 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.417 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Derek S. Ray, 2015. */ /* NetHack may be freely redistributed. See license for details. */ #define NEED_VARARGS +#if defined(WIN32) +#include "win32api.h" +#endif + #include "hack.h" #include "dlb.h" +#ifdef SFCTOOL +#ifdef TTY_GRAPHICS +#undef TTY_GRAPHICS +#endif +#ifdef mark_synch +#undef mark_synch +#endif +#define mark_synch() +#ifdef raw_print +#undef raw_print +#endif +#define raw_print(a) +#ifdef WINDOWPORT +#undef WINDOWPORT +#endif +#define WINDOWPORT(x) FALSE +#ifdef clear_nhwindow +#undef clear_nhwindow +#endif +#define clear_nhwindow(x) +#endif /* SFCTOOL */ + #ifdef TTY_GRAPHICS #include "wintty.h" /* more() */ #endif -#if (!defined(MAC) && !defined(O_WRONLY) && !defined(AZTEC_C)) \ +#if (!defined(MACOS9) && !defined(O_WRONLY) && !defined(AZTEC_C)) \ || defined(USE_FCNTL) #include #endif @@ -41,10 +67,9 @@ const #endif #endif -#if defined(UNIX) && defined(QT_GRAPHICS) +#if defined(UNIX) && defined(SELECTSAVED) #include #include -#include #endif #if defined(UNIX) || defined(VMS) || !defined(NO_SIGNAL) @@ -63,42 +88,10 @@ const #endif #ifdef PREFIXES_IN_USE -#define FQN_NUMBUF 4 +#define FQN_NUMBUF 8 static char fqn_filename_buffer[FQN_NUMBUF][FQN_MAX_FILENAME]; #endif -#if !defined(MFLOPPY) && !defined(VMS) && !defined(WIN32) -char bones[] = "bonesnn.xxx"; -char lock[PL_NSIZ + 14] = "1lock"; /* long enough for uid+name+.99 */ -#else -#if defined(MFLOPPY) -char bones[FILENAME]; /* pathname of bones files */ -char lock[FILENAME]; /* pathname of level files */ -#endif -#if defined(VMS) -char bones[] = "bonesnn.xxx;1"; -char lock[PL_NSIZ + 17] = "1lock"; /* long enough for _uid+name+.99;1 */ -#endif -#if defined(WIN32) -char bones[] = "bonesnn.xxx"; -char lock[PL_NSIZ + 25]; /* long enough for username+-+name+.99 */ -#endif -#endif - -#if defined(UNIX) || defined(__BEOS__) -#define SAVESIZE (PL_NSIZ + 13) /* save/99999player.e */ -#else -#ifdef VMS -#define SAVESIZE (PL_NSIZ + 22) /* [.save]player.e;1 */ -#else -#if defined(WIN32) -#define SAVESIZE (PL_NSIZ + 40) /* username-player.NetHack-saved-game */ -#else -#define SAVESIZE FILENAME /* from macconf.h or pcconf.h */ -#endif -#endif -#endif - #if !defined(SAVE_EXTENSION) #ifdef MICRO #define SAVE_EXTENSION ".sav" @@ -108,56 +101,43 @@ char lock[PL_NSIZ + 25]; /* long enough for username+-+name+.99 */ #endif #endif -char SAVEF[SAVESIZE]; /* holds relative path of save file from playground */ -#ifdef MICRO -char SAVEP[SAVESIZE]; /* holds path of directory for save file */ -#endif - -#ifdef HOLD_LOCKFILE_OPEN -struct level_ftrack { - int init; - int fd; /* file descriptor for level file */ - int oflag; /* open flags */ - boolean nethack_thinks_it_is_open; /* Does NetHack think it's open? */ -} lftrack; #if defined(WIN32) #include +#include +#define F_OK 0 +#define access _access #endif -#endif /*HOLD_LOCKFILE_OPEN*/ - -#define WIZKIT_MAX 128 -static char wizkit[WIZKIT_MAX]; -STATIC_DCL FILE *NDECL(fopen_wizkit_file); -STATIC_DCL void FDECL(wizkit_addinv, (struct obj *)); #ifdef AMIGA extern char PATH[]; /* see sys/amiga/amidos.c */ extern char bbs_id[]; -static int lockptr; #ifdef __SASC_60 #include #endif #include -extern void FDECL(amii_set_text_font, (char *, int)); +extern void amii_set_text_font(char *, int); #endif #if defined(WIN32) || defined(MSDOS) -static int lockptr; #ifdef MSDOS #define Delay(a) msleep(a) #endif #define Close close #ifndef WIN_CE +#ifdef DeleteFile +#undef DeleteFile +#endif #define DeleteFile unlink #endif #ifdef WIN32 -/*from windmain.c */ -extern char *FDECL(translate_path_variables, (const char *, char *)); +/*from windsys.c */ +extern char *translate_path_variables(const char *, char *); +extern boolean get_user_home_folder(char *, size_t); #endif #endif -#ifdef MAC +#ifdef MACOS9 #undef unlink #define unlink macunlink #endif @@ -167,62 +147,86 @@ extern char *FDECL(translate_path_variables, (const char *, char *)); #define PRAGMA_UNUSED #endif -#ifdef USER_SOUNDS -extern char *sounddir; -#endif - -extern int n_dgns; /* from dungeon.c */ - -#if defined(UNIX) && defined(QT_GRAPHICS) -#define SELECTSAVED -#endif +#ifndef SFCTOOL +staticfn NHFILE *new_nhfile(void); +staticfn void free_nhfile(NHFILE *); +#else +NHFILE *new_nhfile(void); +void free_nhfile(NHFILE *); +#endif /* SFCTOOL */ #ifdef SELECTSAVED -STATIC_PTR int FDECL(CFDECLSPEC strcmp_wrap, (const void *, const void *)); +staticfn int QSORTCALLBACK strcmp_wrap(const void *, const void *); #endif -STATIC_DCL char *FDECL(set_bonesfile_name, (char *, d_level *)); -STATIC_DCL char *NDECL(set_bonestemp_name); +staticfn char *set_bonesfile_name(char *, d_level *); +staticfn char *set_bonestemp_name(void); #ifdef COMPRESS -STATIC_DCL void FDECL(redirect, (const char *, const char *, FILE *, - BOOLEAN_P)); +staticfn void redirect(const char *, const char *, FILE *, boolean); #endif #if defined(COMPRESS) || defined(ZLIB_COMP) -STATIC_DCL void FDECL(docompress_file, (const char *, BOOLEAN_P)); +staticfn void docompress_file(const char *, boolean); #endif #if defined(ZLIB_COMP) -STATIC_DCL boolean FDECL(make_compressed_name, (const char *, char *)); +staticfn boolean make_compressed_name(const char *, char *); #endif -#ifndef USE_FCNTL -STATIC_DCL char *FDECL(make_lockname, (const char *, char *)); -#endif -STATIC_DCL void FDECL(set_configfile_name, (const char *)); -STATIC_DCL FILE *FDECL(fopen_config_file, (const char *, int)); -STATIC_DCL int FDECL(get_uchars, (char *, uchar *, BOOLEAN_P, - int, const char *)); -boolean FDECL(proc_wizkit_line, (char *)); -boolean FDECL(parse_config_line, (char *)); -STATIC_DCL boolean FDECL(parse_conf_file, (FILE *, boolean (*proc)(char *))); -STATIC_DCL FILE *NDECL(fopen_sym_file); -boolean FDECL(proc_symset_line, (char *)); -STATIC_DCL void FDECL(set_symhandling, (char *, int)); -#ifdef NOCWD_ASSUMPTIONS -STATIC_DCL void FDECL(adjust_prefix, (char *, int)); -#endif -STATIC_DCL boolean FDECL(config_error_nextline, (const char *)); -STATIC_DCL void NDECL(free_config_sections); -STATIC_DCL char *FDECL(choose_random_part, (char *, CHAR_P)); -STATIC_DCL boolean FDECL(is_config_section, (const char *)); -STATIC_DCL boolean FDECL(handle_config_section, (char *)); -#ifdef SELF_RECOVER -STATIC_DCL boolean FDECL(copy_bytes, (int, int)); + +staticfn NHFILE *problematic_savefile(int, const char *); +#ifndef SFCTOOL +staticfn int doconvert_file(const char *, int, boolean); +#endif /* SFCTOOL */ +staticfn boolean make_converted_name(const char *); + +#ifndef SFCTOOL +staticfn NHFILE *viable_nhfile(NHFILE *); +#ifdef SELECTSAVED +staticfn int QSORTCALLBACK strcmp_wrap(const void *, const void *); #endif -#ifdef HOLD_LOCKFILE_OPEN -STATIC_DCL int FDECL(open_levelfile_exclusively, (const char *, int, int)); +staticfn char *set_bonesfile_name(char *, d_level *); +staticfn char *set_bonestemp_name(void); +#ifndef USE_FCNTL +staticfn char *make_lockname(const char *, char *); #endif +staticfn FILE *fopen_wizkit_file(void); +staticfn void wizkit_addinv(struct obj *); +boolean proc_wizkit_line(char *buf); +void read_wizkit(void); /* in extern.h; why here too? */ +staticfn FILE *fopen_sym_file(void); +staticfn NHFILE *viable_nhfile(NHFILE *); +#endif /* !SFCTOOL */ + +/* return a file's name without its path and optionally trailing 'type' */ +const char * +nh_basename(const char *fname, boolean keep_suffix) +{ +#ifndef VMS + static char basebuf[80]; + const char *p; + if ((p = strrchr(fname, '/')) != 0) + fname = p + 1; +#if defined(WIN32) || defined(MSDOS) + if ((p = strrchr(fname, '\\')) != 0) + fname = p + 1; +#endif + if ((p = strrchr(fname, '.')) != 0 && !keep_suffix) { + size_t ln = (size_t) (p - fname); + /* note that without path, name should be reasonable length; + it is expected to refer to a source file name or run-time + configuration file name and those aren't arbitrarily long; + if "name" part of "name.suffix" is too long for 'basebuf[]', + just return that as-is without stripping off ".suffix" */ -static char *config_section_chosen = (char *) 0; -static char *config_section_current = (char *) 0; + if (ln < sizeof basebuf) { + strncpy(basebuf, fname, ln); + basebuf[ln] = '\0'; + fname = basebuf; + } + } +#else /* VMS */ + fname = vms_basename(fname, keep_suffix); +#endif + return fname; +} /* * fname_encode() @@ -242,17 +246,18 @@ static char *config_section_current = (char *) 0; * * Sample: * The following call: - * (void)fname_encode("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", + * (void) fname_encode("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", * '%', "This is a % test!", buf, 512); * results in this encoding: * "This%20is%20a%20%25%20test%21" */ char * -fname_encode(legal, quotechar, s, callerbuf, bufsz) -const char *legal; -char quotechar; -char *s, *callerbuf; -int bufsz; +fname_encode( + const char *legal, + char quotechar, + char *s, + char *callerbuf, + int bufsz) { char *sp, *op; int cnt = 0; @@ -271,7 +276,8 @@ int bufsz; (void) sprintf(op, "%c%02X", quotechar, *sp); op += 3; cnt += 3; - } else if ((index(legal, *sp) != 0) || (index(hexdigits, *sp) != 0)) { + } else if ((strchr(legal, *sp) != 0) + || (strchr(hexdigits, *sp) != 0)) { *op++ = *sp; *op = '\0'; cnt++; @@ -296,10 +302,7 @@ int bufsz; * bufsz size of callerbuf */ char * -fname_decode(quotechar, s, callerbuf, bufsz) -char quotechar; -char *s, *callerbuf; -int bufsz; +fname_decode(char quotechar, char *s, char *callerbuf, int bufsz) { char *sp, *op; int k, calc, cnt = 0; @@ -308,7 +311,6 @@ int bufsz; sp = s; op = callerbuf; *op = '\0'; - calc = 0; while (*sp) { /* Do we have room for one more character? */ @@ -349,10 +351,9 @@ int bufsz; /*ARGSUSED*/ const char * -fqname(basenam, whichprefix, buffnum) -const char *basenam; -int whichprefix UNUSED_if_not_PREFIXES_IN_USE; -int buffnum UNUSED_if_not_PREFIXES_IN_USE; +fqname(const char *basenam, + int whichprefix UNUSED_if_not_PREFIXES_IN_USE, + int buffnum UNUSED_if_not_PREFIXES_IN_USE) { #ifdef PREFIXES_IN_USE char *bufptr; @@ -366,17 +367,17 @@ int buffnum UNUSED_if_not_PREFIXES_IN_USE; #else if (!basenam || whichprefix < 0 || whichprefix >= PREFIX_COUNT) return basenam; - if (!fqn_prefix[whichprefix]) + if (!gf.fqn_prefix[whichprefix]) return basenam; if (buffnum < 0 || buffnum >= FQN_NUMBUF) { impossible("Invalid fqn_filename_buffer specified: %d", buffnum); buffnum = 0; } - bufptr = fqn_prefix[whichprefix]; + bufptr = gf.fqn_prefix[whichprefix]; #ifdef WIN32 - if (strchr(fqn_prefix[whichprefix], '%') - || strchr(fqn_prefix[whichprefix], '~')) - bufptr = translate_path_variables(fqn_prefix[whichprefix], tmpbuf); + if (strchr(gf.fqn_prefix[whichprefix], '%') + || strchr(gf.fqn_prefix[whichprefix], '~')) + bufptr = translate_path_variables(gf.fqn_prefix[whichprefix], tmpbuf); #endif if (strlen(bufptr) + strlen(basenam) >= FQN_MAX_FILENAME) { impossible("fqname too long: %s + %s", bufptr, basenam); @@ -387,9 +388,10 @@ int buffnum UNUSED_if_not_PREFIXES_IN_USE; #endif /* !PREFIXES_IN_USE */ } +#ifndef SFCTOOL +/* reasonbuf must be at least BUFSZ, supplied by caller */ int -validate_prefix_locations(reasonbuf) -char *reasonbuf; /* reasonbuf must be at least BUFSZ, supplied by caller */ +validate_prefix_locations(char *reasonbuf) { #if defined(NOCWD_ASSUMPTIONS) FILE *fp; @@ -423,8 +425,8 @@ char *reasonbuf; /* reasonbuf must be at least BUFSZ, supplied by caller */ if (!(details = strerror(errno))) #endif details = ""; - Sprintf(panicbuf2, "\"%s\", (%d) %s", fqn_prefix[prefcnt], errno, - details); + Sprintf(panicbuf2, "\"%s\", (%d) %s", + gf.fqn_prefix[prefcnt], errno, details); paniclog(panicbuf1, panicbuf2); failcount++; } @@ -439,9 +441,7 @@ char *reasonbuf; /* reasonbuf must be at least BUFSZ, supplied by caller */ /* fopen a file, with OS-dependent bells and whistles */ /* NOTE: a simpler version of this routine also exists in util/dlb_main.c */ FILE * -fopen_datafile(filename, mode, prefix) -const char *filename, *mode; -int prefix; +fopen_datafile(const char *filename, const char *mode, int prefix) { FILE *fp; @@ -449,33 +449,153 @@ int prefix; fp = fopen(filename, mode); return fp; } +#endif /* !SFCTOOL */ -/* ---------- BEGIN LEVEL FILE HANDLING ----------- */ +/* ---------- EXTERNAL FILE SUPPORT ----------- */ + +/* determine byte order */ +static const int bei = 1; +#define IS_BIGENDIAN() ( (*(char*)&bei) == 0 ) -#ifdef MFLOPPY -/* Set names for bones[] and lock[] */ void -set_lock_and_bones() +init_nhfile(NHFILE *nhfp) { - if (!ramdisk) { - Strcpy(levels, permbones); - Strcpy(bones, permbones); + if (nhfp->structlevel) { + if (nhfp->fd != -1) { + impossible("Warning - Unclosed structlevel file being reinitialized"); + (void) nhclose(nhfp->fd); + } + } else if (nhfp->fpdef) { + if (nhfp->fpdef) { + impossible("Warning - Unclosed fieldlevel file being reinitialized"); + (void) fclose(nhfp->fpdef); + } } - append_slash(permbones); - append_slash(levels); -#ifdef AMIGA - strncat(levels, bbs_id, PATHLEN); + nhfp->fd = -1; + nhfp->fpdef = (FILE *) 0; + + nhfp->mode = COUNTING; + nhfp->structlevel = TRUE; + nhfp->fieldlevel = FALSE; + nhfp->addinfo = FALSE; + nhfp->bendian = IS_BIGENDIAN(); + nhfp->fplog = (FILE *) 0; + nhfp->fpdebug = (FILE *) 0; + nhfp->rcount = nhfp->wcount = 0; + nhfp->eof = FALSE; + nhfp->fnidx = 0; + nhfp->style.deflt = FALSE; + nhfp->style.binary = TRUE; + nhfp->nhfpconvert = 0; +} + +#ifndef SFCTOOL +staticfn +#endif +NHFILE * +new_nhfile(void) +{ + NHFILE *nhfp = (NHFILE *) alloc(sizeof *nhfp); + + memset((genericptr_t) nhfp, 0, sizeof *nhfp); + init_nhfile(nhfp); + return nhfp; +} + +#ifndef SFCTOOL +staticfn #endif - append_slash(bones); - Strcat(bones, "bonesnn.*"); - Strcpy(lock, levels); -#ifndef AMIGA - Strcat(lock, alllevels); +void +free_nhfile(NHFILE *nhfp) +{ + if (nhfp) { + init_nhfile(nhfp); + free((genericptr_t) nhfp); + } +} + +void +close_nhfile(NHFILE *nhfp) +{ + if (nhfp->structlevel && nhfp->fd != -1) + (void) nhclose(nhfp->fd), nhfp->fd = -1; + else if (nhfp->fpdef) + (void) fclose(nhfp->fpdef), nhfp->fpdef = (FILE *) 0; + if (nhfp->fplog) + (void) fprintf(nhfp->fplog, "# closing\n"); + if (nhfp->fplog) + (void) fclose(nhfp->fplog); + if (nhfp->fpdebug) + (void) fclose(nhfp->fpdebug); + free_nhfile(nhfp); +} + +void +rewind_nhfile(NHFILE *nhfp) +{ + if (nhfp->structlevel) { +#ifdef BSD + (void) lseek(nhfp->fd, 0L, 0); +#else + (void) lseek(nhfp->fd, (off_t) 0, 0); #endif - return; + } else { + rewind(nhfp->fpdef); + } +} + +#ifndef SFCTOOL +staticfn NHFILE * +viable_nhfile(NHFILE *nhfp) +{ + /* perform some sanity checks before returning + the pointer to the nethack file descriptor */ + if (nhfp) { + /* check for no open file at all, + * not a structlevel legacy file, + * nor a fieldlevel file. + */ + if (((nhfp->fd == -1) && !nhfp->fpdef) + || (nhfp->structlevel && nhfp->fd < 0) + || (nhfp->fieldlevel && !nhfp->fpdef)) { + /* not viable, start the cleanup */ + if (nhfp->fieldlevel) { + if (nhfp->fpdef) { + (void) fclose(nhfp->fpdef); + nhfp->fpdef = (FILE *) 0; + } + if (nhfp->fplog) { + (void) fprintf(nhfp->fplog, "# closing, not viable\n"); + (void) fclose(nhfp->fplog); + } + if (nhfp->fpdebug) + (void) fclose(nhfp->fpdebug); + } + free_nhfile(nhfp); + nhfp = (NHFILE *) 0; + } + } + return nhfp; +} +#endif /* !SFCTOOL */ + +int +nhclose(int fd) +{ + int retval = 0; + + if (fd >= 0) { + if (close_check(fd)) + bclose(fd); + else + retval = close(fd); + } + return retval; } -#endif /* MFLOPPY */ +/* ---------- BEGIN LEVEL FILE HANDLING ----------- */ + +#ifndef SFCTOOL /* Construct a file name for a level-type file, which is of the form * something.level (with any old level stripped off). * This assumes there is space on the end of 'file' to append @@ -483,13 +603,11 @@ set_lock_and_bones() * but be careful if you use it for other things -dgk */ void -set_levelfile_name(file, lev) -char *file; -int lev; +set_levelfile_name(char *file, int lev) { char *tf; - tf = rindex(file, '.'); + tf = strrchr(file, '.'); if (!tf) tf = eos(file); Sprintf(tf, ".%d", lev); @@ -499,223 +617,147 @@ int lev; return; } -int -create_levelfile(lev, errbuf) -int lev; -char errbuf[]; +NHFILE * +create_levelfile(int lev, char errbuf[]) { - int fd; const char *fq_lock; + NHFILE *nhfp = (NHFILE *) 0; if (errbuf) *errbuf = '\0'; - set_levelfile_name(lock, lev); - fq_lock = fqname(lock, LEVELPREFIX, 0); - + set_levelfile_name(gl.lock, lev); + fq_lock = fqname(gl.lock, LEVELPREFIX, 0); + + nhfp = new_nhfile(); + if (nhfp) { + nhfp->ftype = NHF_LEVELFILE; + nhfp->mode = WRITING; + nhfp->structlevel = TRUE; /* do set this TRUE for levelfiles */ + nhfp->fieldlevel = FALSE; /* don't set this TRUE for levelfiles */ + nhfp->addinfo = FALSE; + nhfp->style.deflt = FALSE; + nhfp->style.binary = TRUE; + nhfp->fnidx = historical; + nhfp->fd = -1; + nhfp->fpdef = (FILE *) 0; #if defined(MICRO) || defined(WIN32) -/* Use O_TRUNC to force the file to be shortened if it already - * exists and is currently longer. - */ -#ifdef HOLD_LOCKFILE_OPEN - if (lev == 0) - fd = open_levelfile_exclusively( - fq_lock, lev, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY); - else -#endif - fd = open(fq_lock, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, FCMASK); + /* Use O_TRUNC to force the file to be shortened if it already + * exists and is currently longer. + */ + nhfp->fd = open(fq_lock, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, + FCMASK); #else -#ifdef MAC - fd = maccreat(fq_lock, LEVL_TYPE); +#ifdef MACOS9 + nhfp->fd = maccreat(fq_lock, LEVL_TYPE); #else - fd = creat(fq_lock, FCMASK); + nhfp->fd = creat(fq_lock, FCMASK); #endif #endif /* MICRO || WIN32 */ - if (fd >= 0) - level_info[lev].flags |= LFILE_EXISTS; - else if (errbuf) /* failure explanation */ - Sprintf(errbuf, "Cannot create file \"%s\" for level %d (errno %d).", - lock, lev, errno); - - return fd; + if (nhfp->fd >= 0) + svl.level_info[lev].flags |= LFILE_EXISTS; + else if (errbuf) /* failure explanation */ + Sprintf(errbuf, + "Cannot create file \"%s\" for level %d (errno %d).", + gl.lock, lev, errno); +#if defined(MSDOS) || defined(WIN32) + if (nhfp->fd >= 0) + (void) setmode(nhfp->fd, O_BINARY); +#endif + } + nhfp = viable_nhfile(nhfp); + return nhfp; } -int -open_levelfile(lev, errbuf) -int lev; -char errbuf[]; +NHFILE * +open_levelfile(int lev, char errbuf[]) { - int fd; const char *fq_lock; + NHFILE *nhfp = (NHFILE *) 0; if (errbuf) *errbuf = '\0'; - set_levelfile_name(lock, lev); - fq_lock = fqname(lock, LEVELPREFIX, 0); -#ifdef MFLOPPY - /* If not currently accessible, swap it in. */ - if (level_info[lev].where != ACTIVE) - swapin_file(lev); -#endif -#ifdef MAC - fd = macopen(fq_lock, O_RDONLY | O_BINARY, LEVL_TYPE); + set_levelfile_name(gl.lock, lev); + fq_lock = fqname(gl.lock, LEVELPREFIX, 0); + nhfp = new_nhfile(); + if (nhfp) { + nhfp->mode = READING; + nhfp->structlevel = TRUE; /* do set this TRUE for levelfiles */ + nhfp->fieldlevel = FALSE; /* do not set this TRUE for levelfiles */ + nhfp->addinfo = FALSE; + nhfp->style.deflt = FALSE; + nhfp->style.binary = TRUE; + nhfp->ftype = NHF_LEVELFILE; + nhfp->fnidx = historical; + nhfp->fd = -1; + nhfp->fpdef = (FILE *) 0; + } + if (nhfp && nhfp->structlevel) { +#ifdef MACOS9 + nhfp->fd = macopen(fq_lock, O_RDONLY | O_BINARY, LEVL_TYPE); #else -#ifdef HOLD_LOCKFILE_OPEN - if (lev == 0) - fd = open_levelfile_exclusively(fq_lock, lev, O_RDONLY | O_BINARY); - else -#endif - fd = open(fq_lock, O_RDONLY | O_BINARY, 0); + nhfp->fd = open(fq_lock, O_RDONLY | O_BINARY, 0); #endif - /* for failure, return an explanation that our caller can use; - settle for `lock' instead of `fq_lock' because the latter - might end up being too big for nethack's BUFSZ */ - if (fd < 0 && errbuf) - Sprintf(errbuf, "Cannot open file \"%s\" for level %d (errno %d).", - lock, lev, errno); - - return fd; + /* for failure, return an explanation that our caller can use; + settle for `lock' instead of `fq_lock' because the latter + might end up being too big for nethack's BUFSZ */ + if (nhfp->fd < 0 && errbuf) + Sprintf(errbuf, + "Cannot open file \"%s\" for level %d (errno %d).", + gl.lock, lev, errno); +#if defined(MSDOS) || defined(WIN32) + if (nhfp->fd >= 0) + (void) setmode(nhfp->fd, O_BINARY); +#endif + } + nhfp = viable_nhfile(nhfp); + return nhfp; } void -delete_levelfile(lev) -int lev; +delete_levelfile(int lev) { /* * Level 0 might be created by port specific code that doesn't * call create_levfile(), so always assume that it exists. */ - if (lev == 0 || (level_info[lev].flags & LFILE_EXISTS)) { - set_levelfile_name(lock, lev); -#ifdef HOLD_LOCKFILE_OPEN - if (lev == 0) - really_close(); -#endif - (void) unlink(fqname(lock, LEVELPREFIX, 0)); - level_info[lev].flags &= ~LFILE_EXISTS; + if (lev == 0 || (svl.level_info[lev].flags & LFILE_EXISTS)) { + set_levelfile_name(gl.lock, lev); + (void) unlink(fqname(gl.lock, LEVELPREFIX, 0)); + svl.level_info[lev].flags &= ~LFILE_EXISTS; } } void -clearlocks() +clearlocks(void) { + int x; + #ifdef HANGUPHANDLING if (program_state.preserve_locks) return; #endif -#if !defined(PC_LOCKING) && defined(MFLOPPY) && !defined(AMIGA) - eraseall(levels, alllevels); - if (ramdisk) - eraseall(permbones, alllevels); -#else - { - register int x; - #ifndef NO_SIGNAL - (void) signal(SIGINT, SIG_IGN); -#endif + (void) signal(SIGINT, SIG_IGN); #if defined(UNIX) || defined(VMS) - sethanguphandler((void FDECL((*), (int) )) SIG_IGN); + sethanguphandler((void (*)(int)) SIG_IGN); #endif - /* can't access maxledgerno() before dungeons are created -dlc */ - for (x = (n_dgns ? maxledgerno() : 0); x >= 0; x--) - delete_levelfile(x); /* not all levels need be present */ - } -#endif /* ?PC_LOCKING,&c */ +#endif /* NO_SIGNAL */ + /* can't access maxledgerno() before dungeons are created -dlc */ + for (x = (svn.n_dgns ? maxledgerno() : 0); x >= 0; x--) + delete_levelfile(x); /* not all levels need be present */ } #if defined(SELECTSAVED) /* qsort comparison routine */ -STATIC_OVL int CFDECLSPEC -strcmp_wrap(p, q) -const void *p; -const void *q; +staticfn int QSORTCALLBACK +strcmp_wrap(const void *p, const void *q) { -#if defined(UNIX) && defined(QT_GRAPHICS) - return strncasecmp(*(char **) p, *(char **) q, 16); -#else - return strncmpi(*(char **) p, *(char **) q, 16); -#endif + return strcmp(*(char **) p, *(char **) q); } #endif -#ifdef HOLD_LOCKFILE_OPEN -STATIC_OVL int -open_levelfile_exclusively(name, lev, oflag) -const char *name; -int lev, oflag; -{ - int reslt, fd; - - if (!lftrack.init) { - lftrack.init = 1; - lftrack.fd = -1; - } - if (lftrack.fd >= 0) { - /* check for compatible access */ - if (lftrack.oflag == oflag) { - fd = lftrack.fd; - reslt = lseek(fd, 0L, SEEK_SET); - if (reslt == -1L) - panic("open_levelfile_exclusively: lseek failed %d", errno); - lftrack.nethack_thinks_it_is_open = TRUE; - } else { - really_close(); - fd = sopen(name, oflag, SH_DENYRW, FCMASK); - lftrack.fd = fd; - lftrack.oflag = oflag; - lftrack.nethack_thinks_it_is_open = TRUE; - } - } else { - fd = sopen(name, oflag, SH_DENYRW, FCMASK); - lftrack.fd = fd; - lftrack.oflag = oflag; - if (fd >= 0) - lftrack.nethack_thinks_it_is_open = TRUE; - } - return fd; -} - -void -really_close() -{ - int fd; - - if (lftrack.init) { - fd = lftrack.fd; - - lftrack.nethack_thinks_it_is_open = FALSE; - lftrack.fd = -1; - lftrack.oflag = 0; - if (fd != -1) - (void) close(fd); - } - return; -} - -int -nhclose(fd) -int fd; -{ - if (lftrack.fd == fd) { - really_close(); /* close it, but reopen it to hold it */ - fd = open_levelfile(0, (char *) 0); - lftrack.nethack_thinks_it_is_open = FALSE; - return 0; - } - return close(fd); -} -#else /* !HOLD_LOCKFILE_OPEN */ - -int -nhclose(fd) -int fd; -{ - return close(fd); -} -#endif /* ?HOLD_LOCKFILE_OPEN */ - /* ---------- END LEVEL FILE HANDLING ----------- */ /* ---------- BEGIN BONES FILE HANDLING ----------- */ @@ -723,10 +765,8 @@ int fd; /* set up "file" to be file name for retrieving bones, and return a * bonesid to be read/written in the bones file. */ -STATIC_OVL char * -set_bonesfile_name(file, lev) -char *file; -d_level *lev; +staticfn char * +set_bonesfile_name(char *file, d_level *lev) { s_level *sptr; char *dptr; @@ -753,17 +793,11 @@ d_level *lev; Sprintf(eos(file), "%u", poolnum); } #endif - dptr = eos(file); /* this used to be after the following Sprintf() - and the return value was (dptr - 2) */ + dptr = eos(file); /* when this naming scheme was adopted, 'filecode' was one letter; - 3.3.0 turned it into a three letter string (via roles[] in role.c); - from that version through 3.6.0, 'dptr' pointed past the filecode - and the return value of (dptr - 2) was wrong for bones produced - in the quest branch, skipping the boneid character 'Q' and the - first letter of the role's filecode; bones loading still worked - because the bonesid used for validation had the same error */ - Sprintf(dptr, "%c%s", dungeons[lev->dnum].boneid, - In_quest(lev) ? urole.filecode : "0"); + 3.3.0 turned it into a three letter string for quest levels */ + Sprintf(dptr, "%c%s", svd.dungeons[lev->dnum].boneid, + In_quest(lev) ? gu.urole.filecode : "0"); if ((sptr = Is_special(lev)) != 0) Sprintf(eos(dptr), ".%c", sptr->boneid); else @@ -780,52 +814,86 @@ d_level *lev; * (we are not reading or writing level files while writing bones files, so * the same array may be used instead of copying.) */ -STATIC_OVL char * -set_bonestemp_name() +staticfn char * +set_bonestemp_name(void) { char *tf; - tf = rindex(lock, '.'); + tf = strrchr(gl.lock, '.'); if (!tf) - tf = eos(lock); + tf = eos(gl.lock); Sprintf(tf, ".bn"); #ifdef VMS Strcat(tf, ";1"); #endif - return lock; + return gl.lock; } -int -create_bonesfile(lev, bonesid, errbuf) -d_level *lev; -char **bonesid; -char errbuf[]; +NHFILE * +create_bonesfile(d_level *lev, char **bonesid, char errbuf[]) { const char *file; - int fd; + NHFILE *nhfp = (NHFILE *) 0; + int failed = 0; +#if defined(WIN32) + errno_t err; +#endif if (errbuf) *errbuf = '\0'; - *bonesid = set_bonesfile_name(bones, lev); + *bonesid = set_bonesfile_name(gb.bones, lev); file = set_bonestemp_name(); file = fqname(file, BONESPREFIX, 0); -#if defined(MICRO) || defined(WIN32) - /* Use O_TRUNC to force the file to be shortened if it already - * exists and is currently longer. - */ - fd = open(file, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, FCMASK); -#else -#ifdef MAC - fd = maccreat(file, BONE_TYPE); -#else - fd = creat(file, FCMASK); + nhfp = new_nhfile(); + if (nhfp) { + nhfp->ftype = NHF_BONESFILE; + nhfp->mode = WRITING; + nhfp->structlevel = TRUE; + nhfp->fieldlevel = FALSE; + nhfp->addinfo = TRUE; + nhfp->style.deflt = TRUE; + nhfp->style.binary = TRUE; + nhfp->fnidx = historical; + nhfp->fd = -1; + nhfp->fpdef = (FILE *) 0; + if (nhfp->fpdef) { +#ifdef SAVEFILE_DEBUGGING + nhfp->fpdebug = fopen("create_bonesfile-debug.log", "a"); #endif + } else { + failed = errno; + } + if (nhfp->structlevel) { +#if defined(MICRO) + /* Use O_TRUNC to force the file to be shortened if it already + * exists and is currently longer. + */ + nhfp->fd = open(file, + O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, FCMASK); +#elif defined(WIN32) + err = _sopen_s(&nhfp->fd, file, + O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, + _SH_DENYRW, _S_IREAD | _S_IWRITE); +#else /* ?MICRO || WIN32 */ +/* implies UNIX or MACOS9 (MACOS9 is for OS9 or earlier) */ +#ifdef MACOS9 + nhfp->fd = maccreat(file, BONE_TYPE); +#else + nhfp->fd = creat(file, FCMASK); +#endif /* ?MACOS9 */ +#endif /* ?MICRO || WIN32 */ + if (nhfp->fd < 0) + failed = errno; +#if defined(MSDOS) || defined(WIN32) + if (nhfp->fd >= 0) + (void) setmode(nhfp->fd, O_BINARY); #endif - if (fd < 0 && errbuf) /* failure explanation */ - Sprintf(errbuf, "Cannot create bones \"%s\", id %s (errno %d).", lock, - *bonesid, errno); - + } + if (failed && errbuf) /* failure explanation */ + Sprintf(errbuf, "Cannot create bones \"%s\", id %s (errno %d).", + gl.lock, *bonesid, errno); + } #if defined(VMS) && !defined(SECURE) /* Re-protect bones file with world:read+write+execute+delete access. @@ -838,32 +906,19 @@ char errbuf[]; (void) chmod(file, FCMASK | 007); /* allow other users full access */ #endif /* VMS && !SECURE */ - return fd; + nhfp = viable_nhfile(nhfp); + return nhfp; } -#ifdef MFLOPPY -/* remove partial bonesfile in process of creation */ -void -cancel_bonesfile() -{ - const char *tempname; - - tempname = set_bonestemp_name(); - tempname = fqname(tempname, BONESPREFIX, 0); - (void) unlink(tempname); -} -#endif /* MFLOPPY */ - /* move completed bones file to proper name */ void -commit_bonesfile(lev) -d_level *lev; +commit_bonesfile(d_level *lev) { const char *fq_bones, *tempname; int ret; - (void) set_bonesfile_name(bones, lev); - fq_bones = fqname(bones, BONESPREFIX, 0); + (void) set_bonesfile_name(gb.bones, lev); + fq_bones = fqname(gb.bones, BONESPREFIX, 0); tempname = set_bonestemp_name(); tempname = fqname(tempname, BONESPREFIX, 1); @@ -881,147 +936,267 @@ d_level *lev; pline("couldn't rename %s to %s.", tempname, fq_bones); } -int -open_bonesfile(lev, bonesid) -d_level *lev; -char **bonesid; +NHFILE * +open_bonesfile(d_level *lev, char **bonesid) { const char *fq_bones; - int fd; - - *bonesid = set_bonesfile_name(bones, lev); - fq_bones = fqname(bones, BONESPREFIX, 0); - nh_uncompress(fq_bones); /* no effect if nonexistent */ -#ifdef MAC - fd = macopen(fq_bones, O_RDONLY | O_BINARY, BONE_TYPE); + NHFILE *nhfp = (NHFILE *) 0; +#if defined(WIN32) + errno_t err UNUSED; +#endif + + *bonesid = set_bonesfile_name(gb.bones, lev); + fq_bones = fqname(gb.bones, BONESPREFIX, 0); + nh_uncompress(fq_bones); /* no effect if nonexistent */ + + nhfp = new_nhfile(); + if (nhfp) { +#if defined(WIN32) && defined(DEBUG) + if (nhfp->fd >= 0) + impossible("bones file NHFILE * has odd fd (%d)", nhfp->fd); +#endif + nhfp->structlevel = TRUE; + nhfp->fieldlevel = FALSE; + nhfp->ftype = NHF_BONESFILE; + nhfp->mode = READING; + nhfp->addinfo = TRUE; + nhfp->style.deflt = TRUE; + nhfp->style.binary = (sysopt.bonesformat[0] != exportascii); + nhfp->fnidx = sysopt.bonesformat[0]; + nhfp->fd = -1; + nhfp->fpdef = (FILE *) 0; + if (nhfp->fpdef) { +#ifdef SAVEFILE_DEBUGGING + nhfp->fpdebug = fopen("open_bonesfile-debug.log", "a"); +#endif + } + if (nhfp->structlevel) { +#if defined(MACOS9) + nhfp->fd = macopen(fq_bones, O_RDONLY | O_BINARY, BONE_TYPE); +#elif defined(WIN32) + err = _sopen_s(&nhfp->fd, fq_bones, _O_RDONLY | _O_BINARY, + _SH_DENYRW, _S_IREAD | _S_IWRITE); #else - fd = open(fq_bones, O_RDONLY | O_BINARY, 0); + nhfp->fd = open(fq_bones, O_RDONLY | O_BINARY, 0); +#endif +#if defined(MSDOS) || defined(WIN32) + if (nhfp->fd >= 0) + (void) setmode(nhfp->fd, O_BINARY); #endif - return fd; + } + } + nhfp = viable_nhfile(nhfp); + return nhfp; } int -delete_bonesfile(lev) -d_level *lev; +delete_bonesfile(d_level *lev) { - (void) set_bonesfile_name(bones, lev); - return !(unlink(fqname(bones, BONESPREFIX, 0)) < 0); + int reslt; + + (void) set_bonesfile_name(gb.bones, lev); + reslt = unlink(fqname(gb.bones, BONESPREFIX, 0)); + delete_convertedfile(fqname(gb.bones, BONESPREFIX, 0)); + return !(reslt < 0); } /* assume we're compressing the recently read or created bonesfile, so the * file name is already set properly */ void -compress_bonesfile() +compress_bonesfile(void) { - nh_compress(fqname(bones, BONESPREFIX, 0)); + nh_sfconvert(fqname(gb.bones, BONESPREFIX, 0)); + nh_compress(fqname(gb.bones, BONESPREFIX, 0)); } +#endif /* !SFCTOOL */ /* ---------- END BONES FILE HANDLING ----------- */ /* ---------- BEGIN SAVE FILE HANDLING ----------- */ -/* set savefile name in OS-dependent manner from pre-existing plname, +/* set savefile name in OS-dependent manner from pre-existing svp.plname, * avoiding troublesome characters */ void -set_savefile_name(regularize_it) -boolean regularize_it; +set_savefile_name(boolean regularize_it) { + int regoffset = 0, overflow = 0, + indicator_spot = 0; /* 0=no indicator, 1=before ext, 2=after ext */ + const char *postappend = (const char *) 0, + *sfindicator = (const char *) 0; +#if defined(WIN32) + char tmp[BUFSZ]; +#endif + #ifdef VMS - Sprintf(SAVEF, "[.save]%d%s", getuid(), plname); - if (regularize_it) - regularize(SAVEF + 7); - Strcat(SAVEF, ";1"); -#else -#if defined(MICRO) - Strcpy(SAVEF, SAVEP); + Sprintf(gs.SAVEF, "[.save]%d%s", getuid(), svp.plname); + regoffset = 7; + indicator_spot = 1; + postappend = ";1"; +#endif +#if defined(WIN32) + if (regularize_it) { + static const char okchars[] + = "*ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-."; + const char *legal = okchars; + + ++legal; /* skip '*' wildcard character */ + (void) fname_encode(legal, '%', svp.plname, tmp, sizeof tmp); + } else { + Sprintf(tmp, "%s", svp.plname); + } + if (strlen(tmp) < (SAVESIZE - 1)) + Strcpy(gs.SAVEF, tmp); + else + overflow = 1; + indicator_spot = 1; + regularize_it = FALSE; +#endif +#ifdef UNIX + Sprintf(gs.SAVEF, "save/%d%s", (int) getuid(), svp.plname); + regoffset = 5; + indicator_spot = 2; +#endif +#if defined(MSDOS) + if (strlen(gs.SAVEP) < (SAVESIZE - 1)) + Strcpy(gs.SAVEF, gs.SAVEP); + if (strlen(gs.SAVEF) < (SAVESIZE - 1)) + (void) strncat(gs.SAVEF, svp.plname, (SAVESIZE - strlen(gs.SAVEF))); +#endif +#if defined(MICRO) && !defined(WIN32) && !defined(MSDOS) + if (strlen(gs.SAVEP) < (SAVESIZE - 1)) + Strcpy(gs.SAVEF, gs.SAVEP); + else #ifdef AMIGA - strncat(SAVEF, bbs_id, PATHLEN); + if (strlen(gs.SAVEP) + strlen(bbs_id) < (SAVESIZE - 1)) + strncat(gs.SAVEF, bbs_id, PATHLEN); #endif { - int i = strlen(SAVEP); + int i = strlen(gs.SAVEP); #ifdef AMIGA - /* plname has to share space with SAVEP and ".sav" */ - (void) strncat(SAVEF, plname, FILENAME - i - 4); + /* svp.plname has to share space with gs.SAVEP and ".sav" */ + (void) strncat(gs.SAVEF, svp.plname, + FILENAME - i - strlen(SAVE_EXTENSION)); #else - (void) strncat(SAVEF, plname, 8); + (void) strncat(gs.SAVEF, svp.plname, 8); #endif - if (regularize_it) - regularize(SAVEF + i); - } - Strcat(SAVEF, SAVE_EXTENSION); -#else -#if defined(WIN32) - { - static const char okchars[] = - "*ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-."; - const char *legal = okchars; - char fnamebuf[BUFSZ], encodedfnamebuf[BUFSZ]; - - /* Obtain the name of the logged on user and incorporate - * it into the name. */ - Sprintf(fnamebuf, "%s", plname); - if (regularize_it) - ++legal; /* skip '*' wildcard character */ - (void) fname_encode(legal, '%', fnamebuf, encodedfnamebuf, BUFSZ); - Sprintf(SAVEF, "%s%s", encodedfnamebuf, SAVE_EXTENSION); + regoffset = i; } -#else /* not VMS or MICRO or WIN32 */ - Sprintf(SAVEF, "save/%d%s", (int) getuid(), plname); - if (regularize_it) - regularize(SAVEF + 5); /* avoid . or / in name */ -#endif /* WIN32 */ #endif /* MICRO */ -#endif /* VMS */ + + if (regularize_it) + regularize(gs.SAVEF + regoffset); + if (indicator_spot == 1 && sfindicator && !overflow) { + if (strlen(gs.SAVEF) + strlen(sfindicator) < (SAVESIZE - 1)) + Strcat(gs.SAVEF, sfindicator); + else + overflow = 2; + } +#ifdef SAVE_EXTENSION + /* (0) is placed in brackets below so that the [&& !overflow] is + explicit dead code (the ">" comparison is detected as always + FALSE at compile-time). Done to appease clang's -Wunreachable-code */ + if (strlen(SAVE_EXTENSION) > (0) && !overflow) { + if (strlen(gs.SAVEF) + strlen(SAVE_EXTENSION) < (SAVESIZE - 1)) { + Strcat(gs.SAVEF, SAVE_EXTENSION); + } else + overflow = 3; + } +#endif + if (indicator_spot == 2 && sfindicator && !overflow) { + if (strlen(gs.SAVEF) + strlen(sfindicator) < (SAVESIZE - 1)) + Strcat(gs.SAVEF, sfindicator); + else + overflow = 4; + } + if (postappend && !overflow) { + if (strlen(gs.SAVEF) + strlen(postappend) < (SAVESIZE - 1)) + Strcat(gs.SAVEF, postappend); + else + overflow = 5; + } +#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) + if (overflow) + impossible("set_savefile_name() couldn't complete" + " without overflow %d", + overflow); +#endif } +#ifndef SFCTOOL #ifdef INSURANCE void -save_savefile_name(fd) -int fd; +save_savefile_name(NHFILE *nhfp) { - (void) write(fd, (genericptr_t) SAVEF, sizeof(SAVEF)); + Sfo_char(nhfp, gs.SAVEF, "savefile_name", sizeof(gs.SAVEF)); } #endif #ifndef MICRO /* change pre-existing savefile name to indicate an error savefile */ void -set_error_savefile() +set_error_savefile(void) { #ifdef VMS { - char *semi_colon = rindex(SAVEF, ';'); + char *semi_colon = strrchr(gs.SAVEF, ';'); if (semi_colon) *semi_colon = '\0'; } - Strcat(SAVEF, ".e;1"); + Strcat(gs.SAVEF, ".e;1"); #else -#ifdef MAC - Strcat(SAVEF, "-e"); +#ifdef MACOS9 + Strcat(gs.SAVEF, "-e"); #else - Strcat(SAVEF, ".e"); + Strcat(gs.SAVEF, ".e"); #endif #endif } #endif /* create save file, overwriting one if it already exists */ -int -create_savefile() +NHFILE * +create_savefile(void) { const char *fq_save; - int fd; - - fq_save = fqname(SAVEF, SAVEPREFIX, 0); + NHFILE *nhfp = (NHFILE *) 0; + boolean do_historical = TRUE; + + fq_save = fqname(gs.SAVEF, SAVEPREFIX, 0); + nhfp = new_nhfile(); + if (nhfp) { + nhfp->ftype = NHF_SAVEFILE; + nhfp->mode = WRITING; + if (program_state.in_self_recover || do_historical) { + nhUse(do_historical); + nhfp->structlevel = TRUE; + nhfp->fieldlevel = FALSE; + nhfp->addinfo = FALSE; + nhfp->style.deflt = FALSE; + nhfp->style.binary = TRUE; + nhfp->fnidx = historical; + nhfp->fd = -1; + nhfp->fpdef = (FILE *) 0; +#ifdef SAVEFILE_DEBUGGING + nhfp->fplog = fopen("create-savefile.log", "w"); +#endif #if defined(MICRO) || defined(WIN32) - fd = open(fq_save, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, FCMASK); -#else -#ifdef MAC - fd = maccreat(fq_save, SAVE_TYPE); + nhfp->fd = open(fq_save, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, + FCMASK); +#else /* !MICRO && !WIN32 */ +/* UNIX || MACOS9 implied (MACOS9 is OS9 or earlier only) */ +#ifdef MACOS9 + nhfp->fd = maccreat(fq_save, SAVE_TYPE); #else - fd = creat(fq_save, FCMASK); + nhfp->fd = creat(fq_save, FCMASK); #endif +#endif /* MICRO || WIN32 */ +#if defined(MSDOS) || defined(WIN32) + if (nhfp->fd >= 0) + (void) setmode(nhfp->fd, O_BINARY); +#endif + } + } #if defined(VMS) && !defined(SECURE) /* Make sure the save file is owned by the current process. That's @@ -1032,138 +1207,221 @@ create_savefile() (void) chown(fq_save, getuid(), getgid()); #define getuid() vms_getuid() #endif /* VMS && !SECURE */ -#endif /* MICRO */ - return fd; + nhfp = viable_nhfile(nhfp); + return nhfp; } /* open savefile for reading */ -int -open_savefile() +NHFILE * +open_savefile(void) { const char *fq_save; - int fd; - - fq_save = fqname(SAVEF, SAVEPREFIX, 0); -#ifdef MAC - fd = macopen(fq_save, O_RDONLY | O_BINARY, SAVE_TYPE); + NHFILE *nhfp = (NHFILE *) 0; + boolean do_historical = TRUE; + + fq_save = fqname(gs.SAVEF, SAVEPREFIX, 0); + nhfp = new_nhfile(); + if (nhfp) { + nhfp->ftype = NHF_SAVEFILE; + nhfp->mode = READING; + if (program_state.in_self_recover || do_historical) { + do_historical = TRUE; /* force it */ + nhUse(do_historical); + nhfp->structlevel = TRUE; + nhfp->fieldlevel = FALSE; + nhfp->addinfo = FALSE; + nhfp->style.deflt = FALSE; + nhfp->style.binary = TRUE; + nhfp->fnidx = historical; + nhfp->fd = -1; + nhfp->fpdef = (FILE *) 0; +#ifdef SAVEFILE_DEBUGGING + nhfp->fplog = fopen("open-savefile.log", "w"); +#endif + } +#ifdef MACOS9 + nhfp->fd = macopen(fq_save, O_RDONLY | O_BINARY, SAVE_TYPE); #else - fd = open(fq_save, O_RDONLY | O_BINARY, 0); + nhfp->fd = open(fq_save, O_RDONLY | O_BINARY, 0); +#endif +#if defined(MSDOS) || defined(WIN32) + if (nhfp->fd >= 0) + (void) setmode(nhfp->fd, O_BINARY); #endif - return fd; + } + nhfp = viable_nhfile(nhfp); + return nhfp; } /* delete savefile */ int -delete_savefile() +delete_savefile(void) { - (void) unlink(fqname(SAVEF, SAVEPREFIX, 0)); + const char *sfname = fqname(gs.SAVEF, SAVEPREFIX, 0); + + (void) unlink(sfname); + (void) delete_convertedfile(sfname); return 0; /* for restore_saved_game() (ex-xxxmain.c) test */ } /* try to open up a save file and prepare to restore it */ -int -restore_saved_game() +NHFILE * +restore_saved_game(void) { const char *fq_save; - int fd; + NHFILE *nhfp = (NHFILE *) 0; + int sfstatus = 0; - reset_restpref(); set_savefile_name(TRUE); -#ifdef MFLOPPY - if (!saveDiskPrompt(1)) - return -1; -#endif /* MFLOPPY */ - fq_save = fqname(SAVEF, SAVEPREFIX, 0); + fq_save = fqname(gs.SAVEF, SAVEPREFIX, 0); nh_uncompress(fq_save); - if ((fd = open_savefile()) < 0) - return fd; - - if (validate(fd, fq_save) != 0) { - (void) nhclose(fd), fd = -1; - (void) delete_savefile(); + if ((nhfp = open_savefile()) != 0) { + if ((sfstatus = validate(nhfp, fq_save, FALSE)) != SF_UPTODATE) { + close_nhfile(nhfp); + nhfp = problematic_savefile(sfstatus, fq_save); + } } - return fd; + return nhfp; } -#if defined(SELECTSAVED) -char * -plname_from_file(filename) -const char *filename; +/* + * This doesn't open any files. It provides a valid (NHFILE *) + * to provide to functions that take one as a parameter, with + * only the FREEING bit set. + * + * close_nhfile() can, and should, be called on the returned + * (NHFILE *), and it will handle it correctly. + */ + +NHFILE * +get_freeing_nhfile(void) { - int fd; - char *result = 0; + NHFILE *nhfp = (NHFILE *) 0; - Strcpy(SAVEF, filename); -#ifdef COMPRESS_EXTENSION - SAVEF[strlen(SAVEF) - strlen(COMPRESS_EXTENSION)] = '\0'; -#endif - nh_uncompress(SAVEF); - if ((fd = open_savefile()) >= 0) { - if (validate(fd, filename) == 0) { - char tplname[PL_NSIZ]; - get_plname_from_file(fd, tplname); - result = dupstr(tplname); - } - (void) nhclose(fd); + nhfp = new_nhfile(); /* also sets fd to -1 */ + if (nhfp) { + nhfp->mode = FREEING; } - nh_compress(SAVEF); + return nhfp; +} - return result; -#if 0 -/* --------- obsolete - used to be ifndef STORE_PLNAME_IN_FILE ----*/ -#if defined(UNIX) && defined(QT_GRAPHICS) - /* Name not stored in save file, so we have to extract it from - the filename, which loses information - (eg. "/", "_", and "." characters are lost. */ - int k; - int uid; - char name[64]; /* more than PL_NSIZ */ -#ifdef COMPRESS_EXTENSION -#define EXTSTR COMPRESS_EXTENSION -#else -#define EXTSTR "" -#endif +/* called if there is no save file for current character */ +int +check_panic_save(void) +{ + int result = 0; +#ifdef CHECK_PANIC_SAVE + FILE *cf; + const char *savef; - if ( sscanf( filename, "%*[^/]/%d%63[^.]" EXTSTR, &uid, name ) == 2 ) { -#undef EXTSTR - /* "_" most likely means " ", which certainly looks nicer */ - for (k=0; name[k]; k++) - if ( name[k] == '_' ) - name[k] = ' '; - return dupstr(name); - } else -#endif /* UNIX && QT_GRAPHICS */ + set_error_savefile(); + savef = fqname(gs.SAVEF, SAVEPREFIX, 0); + + /* + * This duplicates part of docompress_file(). + * We don't want to start by uncompressing just to check for the + * file's existence and then have to recompress it. + */ + +#ifdef COMPRESS_EXTENSION + unsigned ln = (unsigned) (strlen(savef) + strlen(COMPRESS_EXTENSION)); + char *cfn = (char *) alloc(ln + 1); + + Strcpy(cfn, savef); + Strcat(cfn, COMPRESS_EXTENSION); + if ((cf = fopen(cfn, RDBMODE)) != NULL) { + (void) fclose(cf); + result = 1; + } + free((genericptr_t) cfn); +#endif /* COMPRESS_EXTENSION */ + + if (!result) { + /* maybe it has already been manually uncompressed */ + if ((cf = fopen(savef, RDBMODE)) != NULL) { + (void) fclose(cf); + result = 1; + } + } + + set_savefile_name(TRUE); /* reset to normal */ +#endif /* CHECK_PANIC_SAVE */ + return result; +} + +#if defined(SELECTSAVED) + +char * +plname_from_file( + const char *filename, + boolean without_wait_synch_per_file) +{ + NHFILE *nhfp; + unsigned ln; + char *result = 0; + int sfstatus = 0; + + Strcpy(gs.SAVEF, filename); +#ifdef COMPRESS_EXTENSION { - return 0; + /* if COMPRESS_EXTENSION is present, strip it off */ + int sln = (int) strlen(gs.SAVEF), + xln = (int) strlen(COMPRESS_EXTENSION); + + if (sln > xln && !strcmp(&gs.SAVEF[sln - xln], COMPRESS_EXTENSION)) + gs.SAVEF[sln - xln] = '\0'; } -/* --------- end of obsolete code ----*/ -#endif /* 0 - WAS STORE_PLNAME_IN_FILE*/ +#endif + nh_uncompress(gs.SAVEF); + if ((nhfp = open_savefile()) != 0) { + if ((sfstatus = validate(nhfp, filename, + without_wait_synch_per_file)) == SF_UPTODATE) { + /* room for "name+role+race+gend+algn X" where the space before + X is actually NUL and X is playmode: one of '-', 'X', or 'D' */ + ln = (unsigned) PL_NSIZ_PLUS; + result = memset((genericptr_t) alloc(ln), '\0', ln); + get_plname_from_file(nhfp, result, FALSE); + } + close_nhfile(nhfp); + } + nh_compress(gs.SAVEF); + return result; /* file's plname[]+playmode value */ } #endif /* defined(SELECTSAVED) */ +#define SUPPRESS_WAITSYNCH_PERFILE TRUE +#define ALLOW_WAITSYNCH_PERFILE FALSE + +/* get list of saved games owned by current user */ char ** -get_saved_games() +get_saved_games(void) { + char **result = NULL; #if defined(SELECTSAVED) - int n, j = 0; - char **result = 0; +#if defined(WIN32) || defined(UNIX) + int n; +#endif + int j = 0; + #ifdef WIN32 { char *foundfile; const char *fq_save; +#if 0 const char *fq_new_save; const char *fq_old_save; +#endif char **files = 0; - int i; + int i, count_failures = 0; - Strcpy(plname, "*"); + Strcpy(svp.plname, "*"); set_savefile_name(FALSE); #if defined(ZLIB_COMP) - Strcat(SAVEF, COMPRESS_EXTENSION); + Strcat(gs.SAVEF, COMPRESS_EXTENSION); #endif - fq_save = fqname(SAVEF, SAVEPREFIX, 0); + fq_save = fqname(gs.SAVEF, SAVEPREFIX, 0); n = 0; foundfile = foundfile_buffer(); @@ -1174,45 +1432,53 @@ get_saved_games() } if (n > 0) { - files = (char **) alloc((n + 1) * sizeof(char *)); /* at most */ - (void) memset((genericptr_t) files, 0, (n + 1) * sizeof(char *)); + files = (char **) alloc((n + 1) * sizeof (char *)); /* at most */ + (void) memset((genericptr_t) files, 0, (n + 1) * sizeof (char *)); if (findfirst((char *) fq_save)) { i = 0; do { - files[i++] = strdup(foundfile); + files[i++] = dupstr(foundfile); } while (findnext()); } } if (n > 0) { - result = (char **) alloc((n + 1) * sizeof(char *)); /* at most */ - (void) memset((genericptr_t) result, 0, (n + 1) * sizeof(char *)); + result = (char **) alloc((n + 1) * sizeof (char *)); /* at most */ + (void) memset((genericptr_t) result, 0, (n + 1) * sizeof (char *)); for(i = 0; i < n; i++) { char *r; - r = plname_from_file(files[i]); + r = plname_from_file(files[i], SUPPRESS_WAITSYNCH_PERFILE); if (r) { - + /* this renaming of the savefile is not compatible + * with 1f36b98b, 'selectsaved' extension from + * Oct 10, 2024. Disable the renaming for the time + * being. + */ +#if 0 /* rename file if it is not named as expected */ - Strcpy(plname, r); - set_savefile_name(FALSE); - fq_new_save = fqname(SAVEF, SAVEPREFIX, 0); + Strcpy(svp.plname, r); + set_savefile_name(TRUE); + fq_new_save = fqname(gs.SAVEF, SAVEPREFIX, 0); fq_old_save = fqname(files[i], SAVEPREFIX, 1); - if(strcmp(fq_old_save, fq_new_save) != 0 && - !file_exists(fq_new_save)) - rename(fq_old_save, fq_new_save); - + if (strcmp(fq_old_save, fq_new_save) != 0 + && !file_exists(fq_new_save)) + (void) rename(fq_old_save, fq_new_save); +#endif result[j++] = r; + } else { + count_failures++; } } } free_saved_games(files); - + if (count_failures) + wait_synch(); } -#endif -#if defined(UNIX) && defined(QT_GRAPHICS) +#endif /* WIN32 */ +#ifdef UNIX /* posixly correct version */ int myuid = getuid(); DIR *dir; @@ -1230,7 +1496,7 @@ get_saved_games() (void) memset((genericptr_t) result, 0, (n + 1) * sizeof(char *)); for (i = 0, j = 0; i < n; i++) { int uid; - char name[64]; /* more than PL_NSIZ */ + char name[64]; /* more than PL_NSIZ+1 */ struct dirent *entry = readdir(dir); if (!entry) @@ -1241,7 +1507,8 @@ get_saved_games() char *r; Sprintf(filename, "save/%d%s", uid, name); - r = plname_from_file(filename); + r = plname_from_file(filename, + ALLOW_WAITSYNCH_PERFILE); if (r) result[j++] = r; } @@ -1250,53 +1517,65 @@ get_saved_games() closedir(dir); } } -#endif +#endif /* UNIX */ #ifdef VMS - Strcpy(plname, "*"); + Strcpy(svp.plname, "*"); set_savefile_name(FALSE); - j = vms_get_saved_games(SAVEF, &result); + j = vms_get_saved_games(gs.SAVEF, &result); #endif /* VMS */ if (j > 0) { if (j > 1) qsort(result, j, sizeof (char *), strcmp_wrap); - result[j] = 0; - return result; + result[j] = (char *) NULL; } else if (result) { /* could happen if save files are obsolete */ free_saved_games(result); + result = (char **) NULL; } #endif /* SELECTSAVED */ - return 0; + + return result; } +#undef SUPPRESS_WAITSYNCH_PERFILE +#undef ALLOW_WAITSYNCH_PERFILE void -free_saved_games(saved) -char **saved; +free_saved_games(char **saved) { if (saved) { - int i = 0; + int i; - while (saved[i]) - free((genericptr_t) saved[i++]); + for (i = 0; saved[i]; ++i) + free((genericptr_t) saved[i]); free((genericptr_t) saved); } } +#endif /* !SFCTOOL */ + /* ---------- END SAVE FILE HANDLING ----------- */ /* ---------- BEGIN FILE COMPRESSION HANDLING ----------- */ -#ifdef COMPRESS +#ifdef COMPRESS /* external compression */ -STATIC_OVL void -redirect(filename, mode, stream, uncomp) -const char *filename, *mode; -FILE *stream; -boolean uncomp; +staticfn void +redirect( + const char *filename, + const char *mode, + FILE *stream, + boolean uncomp) { if (freopen(filename, mode, stream) == (FILE *) 0) { - (void) fprintf(stderr, "freopen of %s for %scompress failed\n", - filename, uncomp ? "un" : ""); + const char *details; + +#if defined(NHSTDC) && !defined(NOTSTDC) + if ((details = strerror(errno)) == 0) +#endif + details = ""; + (void) fprintf(stderr, + "freopen of %s for %scompress failed; (%d) %s\n", + filename, uncomp ? "un" : "", errno, details); nh_terminate(EXIT_FAILURE); } } @@ -1308,31 +1587,45 @@ boolean uncomp; * * cf. child() in unixunix.c. */ -STATIC_OVL void -docompress_file(filename, uncomp) -const char *filename; -boolean uncomp; +staticfn void +docompress_file(const char *filename, boolean uncomp) { - char cfn[80]; + char *cfn = 0; + const char *xtra; FILE *cf; const char *args[10]; #ifdef COMPRESS_OPTIONS - char opts[80]; + char opts[sizeof COMPRESS_OPTIONS]; #endif int i = 0; - int f; + int f, childstatus; + unsigned ln; #ifdef TTY_GRAPHICS - boolean istty = WINDOWPORT("tty"); + boolean istty = WINDOWPORT(tty); #endif - Strcpy(cfn, filename); #ifdef COMPRESS_EXTENSION - Strcat(cfn, COMPRESS_EXTENSION); + xtra = COMPRESS_EXTENSION; +#else + xtra = ""; #endif +#ifdef SFCTOOL + ln = strlen(filename) + sizeof COMPRESS_EXTENSION; + cfn = (char *) alloc(ln); +#else /* SFCTOOL */ + ln = (unsigned) (strlen(filename) + strlen(xtra)); + cfn = (char *) alloc(ln + 1); +#endif /* SFCTOOL */ + + Strcpy(cfn, filename); + Strcat(cfn, xtra); + /* when compressing, we know the file exists */ if (uncomp) { - if ((cf = fopen(cfn, RDBMODE)) == (FILE *) 0) + if ((cf = fopen(cfn, RDBMODE)) == (FILE *) 0) { + free((genericptr_t) cfn); return; + } (void) fclose(cf); } @@ -1345,8 +1638,7 @@ boolean uncomp; char *opt; boolean inword = FALSE; - Strcpy(opts, COMPRESS_OPTIONS); - opt = opts; + opt = strcpy(opts, COMPRESS_OPTIONS); while (*opt) { if ((*opt == ' ') || (*opt == '\t')) { if (inword) { @@ -1368,8 +1660,9 @@ boolean uncomp; * there is an error message from the compression, the 'y' or 'n' can * end up being displayed after the error message. */ - if (istty) + if (istty) { mark_synch(); + } #endif f = fork(); if (f == 0) { /* child */ @@ -1379,8 +1672,9 @@ boolean uncomp; * them will have to clear the first line. This should be * invisible if there are no error messages. */ - if (istty) + if (istty) { raw_print(""); + } #endif /* run compressor without privileges, in case other programs * have surprises along the line of gzip once taking filenames @@ -1403,16 +1697,36 @@ boolean uncomp; perror((char *) 0); (void) fprintf(stderr, "Exec to %scompress %s failed.\n", uncomp ? "un" : "", filename); + free((genericptr_t) cfn); nh_terminate(EXIT_FAILURE); } else if (f == -1) { perror((char *) 0); pline("Fork to %scompress %s failed.", uncomp ? "un" : "", filename); + free((genericptr_t) cfn); return; } + + /* + * back in parent... + */ #ifndef NO_SIGNAL + childstatus = 1; /* wait() should update this, ideally setting it to 0 */ (void) signal(SIGINT, SIG_IGN); (void) signal(SIGQUIT, SIG_IGN); - (void) wait((int *) &i); + errno = 0; /* avoid stale details if wait() doesn't set errno */ + /* wait() returns child's pid and sets 'childstatus' to child's + exit status, or returns -1 and leaves 'childstatus' unmodified */ + if ((long) wait((int *) &childstatus) == -1L) { + char numbuf[40]; + const char *details = strerror(errno); + + if (!details) { + Sprintf(numbuf, "(%d)", errno); + details = numbuf; + } + raw_printf("Wait when %scompressing %s failed; %s.", + uncomp ? "un" : "", filename, details); + } (void) signal(SIGINT, (SIG_RET_TYPE) done1); if (wizard) (void) signal(SIGQUIT, SIG_DFL); @@ -1423,9 +1737,9 @@ boolean uncomp; * compression if there are no signals, but we want this for * testing with FailSafeC */ - i = 1; + childstatus = 1; /* non-zero => failure */ #endif - if (i == 0) { + if (childstatus == 0) { /* (un)compress succeeded: remove file left behind */ if (uncomp) (void) unlink(cfn); @@ -1455,8 +1769,12 @@ boolean uncomp; } #endif } + + free((genericptr_t) cfn); + return; } -#endif /* COMPRESS */ + +#endif /* COMPRESS : external compression */ #if defined(COMPRESS) || defined(ZLIB_COMP) #define UNUSED_if_not_COMPRESS /*empty*/ @@ -1466,37 +1784,25 @@ boolean uncomp; /* compress file */ void -nh_compress(filename) -const char *filename UNUSED_if_not_COMPRESS; +nh_compress(const char *filename UNUSED_if_not_COMPRESS) { -#if !defined(COMPRESS) && !defined(ZLIB_COMP) -#ifdef PRAGMA_UNUSED -#pragma unused(filename) -#endif -#else +#if defined(COMPRESS) || defined(ZLIB_COMP) docompress_file(filename, FALSE); #endif } /* uncompress file if it exists */ void -nh_uncompress(filename) -const char *filename UNUSED_if_not_COMPRESS; +nh_uncompress(const char *filename UNUSED_if_not_COMPRESS) { -#if !defined(COMPRESS) && !defined(ZLIB_COMP) -#ifdef PRAGMA_UNUSED -#pragma unused(filename) -#endif -#else +#if defined(COMPRESS) || defined(ZLIB_COMP) docompress_file(filename, TRUE); #endif } #ifdef ZLIB_COMP /* RLC 09 Mar 1999: Support internal ZLIB */ -STATIC_OVL boolean -make_compressed_name(filename, cfn) -const char *filename; -char *cfn; +staticfn boolean +make_compressed_name(const char *filename, char *cfn) { #ifndef SHORT_FILENAMES /* Assume free-form filename with no 8.3 restrictions */ @@ -1526,17 +1832,22 @@ char *cfn; #endif /* SHORT_FILENAMES */ } -STATIC_OVL void -docompress_file(filename, uncomp) -const char *filename; -boolean uncomp; +staticfn void +docompress_file(const char *filename, boolean uncomp) { gzFile compressedfile; FILE *uncompressedfile; +#ifndef SFCTOOL char cfn[256]; +#else + char *cfn; +#endif char buf[1024]; unsigned len, len2; +#ifdef SFCTOOL + cfn = (char *) alloc(strlen(filename) + strlen(COMPRESS_EXTENSION) + 1); +#endif if (!make_compressed_name(filename, cfn)) return; @@ -1557,6 +1868,9 @@ boolean uncomp; } else { panic("Error in docompress_file %d", errno); } +#ifdef SFCTOOL + free(cfn); +#endif fclose(uncompressedfile); return; } @@ -1571,6 +1885,9 @@ boolean uncomp; fclose(uncompressedfile); gzclose(compressedfile); (void) unlink(cfn); +#ifdef SFCTOOL + free(cfn); +#endif return; } if (len == 0) @@ -1583,6 +1900,9 @@ boolean uncomp; fclose(uncompressedfile); gzclose(compressedfile); (void) unlink(cfn); +#ifdef SFCTOOL + free(cfn); +#endif return; } } @@ -1608,12 +1928,18 @@ boolean uncomp; panic("Error in zlib docompress_file %s, %d", filename, errno); } +#ifdef SFCTOOL + free(cfn); +#endif return; } uncompressedfile = fopen(filename, WRBMODE); if (!uncompressedfile) { pline("Error in zlib docompress file uncompress %s", filename); gzclose(compressedfile); +#ifdef SFCTOOL + free(cfn); +#endif return; } @@ -1627,6 +1953,9 @@ boolean uncomp; fclose(uncompressedfile); gzclose(compressedfile); (void) unlink(filename); +#ifdef SFCTOOL + free(cfn); +#endif return; } if (len == 0) @@ -1639,6 +1968,9 @@ boolean uncomp; fclose(uncompressedfile); gzclose(compressedfile); (void) unlink(filename); +#ifdef SFCTOOL + free(cfn); +#endif return; } } @@ -1649,29 +1981,247 @@ boolean uncomp; /* Delete the file left behind */ (void) unlink(cfn); } +#ifdef SFCTOOL + free(cfn); +#endif } #endif /* RLC 09 Mar 1999: End ZLIB patch */ +#undef UNUSED_if_not_COMPRESS + /* ---------- END FILE COMPRESSION HANDLING ----------- */ + +/* ---------- BEGIN PROBLEMATIC SAVEFILE HANDLING ----------- */ +#ifndef SFCTOOL +static struct sfstatus_to_msg { + int sfstatus; + const char *msg; +} sf2msg[] = { + { SF_UPTODATE, "everything matches" }, + { SF_OUTDATED, "outdated savefile" }, + { SF_CRITICAL_BYTE_COUNT_MISMATCH, + "savefile critical byte-count mismatch" }, + { SF_DM_IL32LLP64_ON_ILP32LL64, "Windows x64 savefile on x86" }, + { SF_DM_I32LP64_ON_ILP32LL64, "Unix 64 savefile on x86" }, + { SF_DM_ILP32LL64_ON_I32LP64, "x86 savefile on Unix 64" }, + { SF_DM_ILP32LL64_ON_IL32LLP64, "x86 savefile on Windows x64" }, + { SF_DM_I32LP64_ON_IL32LLP64, "Unix 64 savefile on Windows x64" }, + { SF_DM_IL32LLP64_ON_I32LP64, "Windows x64 savefile on Unix 64" }, + { SF_DM_MISMATCH, "generic savefile mismatch" }, +}; + +staticfn NHFILE * +problematic_savefile(int sfstatus, const char *savefilenm) +{ + int i; + NHFILE *nhfp = (NHFILE *) 0; + + switch (sfstatus) { + case SF_UPTODATE: + break; + case SF_DM_IL32LLP64_ON_ILP32LL64: + case SF_DM_I32LP64_ON_ILP32LL64: + case SF_DM_ILP32LL64_ON_I32LP64: + case SF_DM_ILP32LL64_ON_IL32LLP64: + case SF_DM_I32LP64_ON_IL32LLP64: + case SF_DM_IL32LLP64_ON_I32LP64: + FALLTHROUGH; + /*FALLTHRU*/ + case SF_DM_MISMATCH: + case SF_OUTDATED: + case SF_CRITICAL_BYTE_COUNT_MISMATCH: + default: + for (i = 0; i < SIZE(sf2msg); ++i) { + if (sf2msg[i].sfstatus == sfstatus) { + raw_printf("\n%s is %s %s\n", + savefilenm, + (sfstatus == SF_OUTDATED) ? "an" : "a", + sf2msg[i].msg); + break; + } + } + } + return nhfp; +} +#endif /* !SFCTOOL */ + +/* ---------- END PROBLEMATIC SAVEFILE HANDLING ----------- */ + +/* ---------- BEGIN EXTERNAL CONVERSION HANDLING ----------- */ + +/* static boolean cvtinit = FALSE; */ + +#ifndef SFCTOOL +static char *unconverted_filename = 0, *converted_filename = 0; + +/* + * Returns non-zero if unconvert was successful + */ +staticfn int +doconvert_file(const char *filename, int sfstatus, boolean unconvert) +{ + nhUse(filename); + nhUse(sfstatus); + nhUse(unconvert); + return 1; +} + +/* convert file */ +void +nh_sfconvert(const char *filename) +{ + (void) doconvert_file(filename, 0, FALSE); +} + +/* unconvert file if it exists */ +void +nh_sfunconvert(const char *filename) +{ + (void) doconvert_file(filename, 0, TRUE); +} + +#else /* !SFCTOOL */ +/* in sfctool, these are in sfctool.c, not in here */ +extern char *unconverted_filename, *converted_filename; +#endif /* !SFCTOOL */ + +staticfn boolean +make_converted_name(const char *filename) +{ + unsigned ln; + const char *xtra, *finaldirchar; + const char *dir = NULL; + boolean needsep = FALSE; +#if defined(WIN32) + static char folderbuf[MAX_PATH]; +#endif + + if (!filename) + return FALSE; + + if (unconverted_filename) + free((genericptr_t) unconverted_filename), unconverted_filename = 0; + if (converted_filename) + free((genericptr_t) converted_filename), converted_filename = 0; + +#ifndef SHORT_FILENAMES + /* do we need to do some ms-dos processing here? */ +#endif /* SHORT_FILENAMES */ + + ln = (unsigned) strlen(filename); + if (!contains_directory(filename)) { +#if defined(UNIX) + dir = nh_getenv("NETHACKDIR"); + if (!dir) + dir = nh_getenv("HACKDIR"); +#ifdef HACKDIR + if (!dir) + dir = HACKDIR; +#endif +#elif defined(WIN32) + if (get_user_home_folder(folderbuf, sizeof folderbuf)) { + size_t sz = strlen(folderbuf); + + Snprintf(eos(folderbuf), sizeof folderbuf - sz, + "\\AppData\\Local\\NetHack\\5.0\\"); + dir = (const char *) folderbuf; + } +#endif /* UNIX || WIN32 */ + if (dir) { + finaldirchar = c_eos(dir); + finaldirchar--; + if (!(*finaldirchar == '/' || *finaldirchar == '\\' + || *finaldirchar == ':')) { + needsep = TRUE; + ln += 1; + } + ln += strlen(dir); + } + } + unconverted_filename = (char *) alloc(ln + 1); + Snprintf(unconverted_filename, ln + 1, "%s%s%s", + dir ? dir : "", + (dir && needsep) ? "/" : "", + filename); + xtra = ".exportascii"; + ln += (unsigned) strlen(xtra); + converted_filename = (char *) alloc(ln + 1); + Strcpy(converted_filename, unconverted_filename); + Strcat(converted_filename, xtra); + return TRUE; +} + +/* delete converted savefile as a normal course of action */ +int +delete_convertedfile(const char *basefilename) +{ + if (!converted_filename) + make_converted_name(basefilename); + if (converted_filename) { + (void) unlink(converted_filename); + } + return 0; +} + +void +free_convert_filenames(void) +{ + if (converted_filename) + free((genericptr_t) converted_filename), converted_filename = 0; + if (unconverted_filename) + free((genericptr_t) unconverted_filename), unconverted_filename = 0; +/* cvtinit = FALSE; */ +} + +/* return TRUE if s contains a directory, not just a filespec */ +boolean +contains_directory(const char *s) +{ + int i, slen = strlen(s); + const char *cp = s; + + for (i = 0; i < slen; ++i) { + if (*cp == '\\' || *cp == '/' || *cp == ':') + return TRUE; + cp++; + } + return FALSE; +} + +/* =========================================================================*/ + +/* ---------- END EXTERNAL CONVERSION HANDLING ----------- */ + /* ---------- BEGIN FILE LOCKING HANDLING ----------- */ -static int nesting = 0; +#ifndef SFCTOOL #if defined(NO_FILE_LINKS) || defined(USE_FCNTL) /* implies UNIX */ static int lockfd = -1; /* for lock_file() to pass to unlock_file() */ #endif #ifdef USE_FCNTL -struct flock sflock; /* for unlocking, same as above */ +static struct flock sflock; /* for unlocking, same as above */ #endif +#if defined(HANGUPHANDLING) #define HUP if (!program_state.done_hup) +#else +#define HUP +#endif + + +#if defined(UNIX) || defined(VMS) || defined(AMIGA) || defined(WIN32) \ + || defined(MSDOS) +#define UNUSED_conditional /*empty*/ +#else +#define UNUSED_conditional UNUSED +#endif + #ifndef USE_FCNTL -STATIC_OVL char * -make_lockname(filename, lockname) -const char *filename; -char *lockname; +staticfn char * +make_lockname(const char *filename UNUSED_conditional, char *lockname) { #if defined(UNIX) || defined(VMS) || defined(AMIGA) || defined(WIN32) \ || defined(MSDOS) @@ -1684,7 +2234,7 @@ char *lockname; #endif #ifdef VMS { - char *semi_colon = rindex(lockname, ';'); + char *semi_colon = strrchr(lockname, ';'); if (semi_colon) *semi_colon = '\0'; } @@ -1694,9 +2244,6 @@ char *lockname; #endif return lockname; #else /* !(UNIX || VMS || AMIGA || WIN32 || MSDOS) */ -#ifdef PRAGMA_UNUSED -#pragma unused(filename) -#endif lockname[0] = '\0'; return (char *) 0; #endif @@ -1705,22 +2252,16 @@ char *lockname; /* lock a file */ boolean -lock_file(filename, whichprefix, retryct) -const char *filename; -int whichprefix; -int retryct; +lock_file(const char *filename, int whichprefix, + int retryct UNUSED_conditional) { -#if defined(PRAGMA_UNUSED) && !(defined(UNIX) || defined(VMS)) \ - && !(defined(AMIGA) || defined(WIN32) || defined(MSDOS)) -#pragma unused(retryct) -#endif #ifndef USE_FCNTL char locknambuf[BUFSZ]; const char *lockname; #endif - nesting++; - if (nesting > 1) { + gn.nesting++; + if (gn.nesting > 1) { impossible("TRIED TO NEST LOCKS"); return TRUE; } @@ -1735,9 +2276,10 @@ int retryct; #ifdef USE_FCNTL lockfd = open(filename, O_RDWR); if (lockfd == -1) { - HUP raw_printf("Cannot open file %s. Is NetHack installed correctly?", + HUP raw_printf("Cannot open file %s. " + " Is NetHack installed correctly?", filename); - nesting--; + gn.nesting--; return FALSE; } sflock.l_type = F_WRLCK; @@ -1759,15 +2301,15 @@ int retryct; #ifdef USE_FCNTL if (retryct--) { - HUP raw_printf( - "Waiting for release of fcntl lock on %s. (%d retries left.)", + HUP raw_printf("Waiting for release of fcntl lock on %s. " + " (%d retries left.)", filename, retryct); sleep(1); } else { - HUP(void) raw_print("I give up. Sorry."); + HUP raw_print("I give up. Sorry."); HUP raw_printf("Some other process has an unnatural grip on %s.", filename); - nesting--; + gn.nesting--; return FALSE; } #else @@ -1776,51 +2318,51 @@ int retryct; switch (errnosv) { /* George Barbanis */ case EEXIST: if (retryct--) { - HUP raw_printf( - "Waiting for access to %s. (%d retries left).", filename, - retryct); + HUP raw_printf("Waiting for access to %s. " + " (%d retries left).", + filename, retryct); #if defined(SYSV) || defined(ULTRIX) || defined(VMS) (void) #endif sleep(1); } else { - HUP(void) raw_print("I give up. Sorry."); + HUP raw_print("I give up. Sorry."); HUP raw_printf("Perhaps there is an old %s around?", lockname); - nesting--; + gn.nesting--; return FALSE; } break; case ENOENT: HUP raw_printf("Can't find file %s to lock!", filename); - nesting--; + gn.nesting--; return FALSE; case EACCES: HUP raw_printf("No write permission to lock %s!", filename); - nesting--; + gn.nesting--; return FALSE; #ifdef VMS /* c__translate(vmsfiles.c) */ case EPERM: /* could be misleading, but usually right */ HUP raw_printf("Can't lock %s due to directory protection.", filename); - nesting--; + gn.nesting--; return FALSE; #endif case EROFS: /* take a wild guess at the underlying cause */ HUP perror(lockname); HUP raw_printf("Cannot lock %s.", filename); - HUP raw_printf( - "(Perhaps you are running NetHack from inside the distribution package?)."); - nesting--; + HUP raw_printf("(Perhaps you are running NetHack from" + " inside the distribution package?)."); + gn.nesting--; return FALSE; default: HUP perror(lockname); HUP raw_printf("Cannot lock %s for unknown reason (%d).", filename, errnosv); - nesting--; + gn.nesting--; return FALSE; } #endif /* USE_FCNTL */ @@ -1831,1120 +2373,96 @@ int retryct; && !defined(USE_FCNTL) #ifdef AMIGA #define OPENFAILURE(fd) (!fd) - lockptr = 0; + gl.lockptr = 0; #else #define OPENFAILURE(fd) (fd < 0) - lockptr = -1; + gl.lockptr = -1; #endif - while (--retryct && OPENFAILURE(lockptr)) { + while (--retryct && OPENFAILURE(gl.lockptr)) { #if defined(WIN32) && !defined(WIN_CE) - lockptr = sopen(lockname, O_RDWR | O_CREAT, SH_DENYRW, S_IWRITE); + gl.lockptr = sopen(lockname, O_RDWR | O_CREAT, SH_DENYRW, S_IWRITE); #else (void) DeleteFile(lockname); /* in case dead process was here first */ #ifdef AMIGA - lockptr = Open(lockname, MODE_NEWFILE); + gl.lockptr = Open(lockname, MODE_NEWFILE); #else - lockptr = open(lockname, O_RDWR | O_CREAT | O_EXCL, S_IWRITE); + gl.lockptr = open(lockname, O_RDWR | O_CREAT | O_EXCL, S_IWRITE); #endif #endif - if (OPENFAILURE(lockptr)) { + if (OPENFAILURE(gl.lockptr)) { raw_printf("Waiting for access to %s. (%d retries left).", filename, retryct); Delay(50); } } - if (!retryct) { - raw_printf("I give up. Sorry."); - nesting--; - return FALSE; - } -#endif /* AMIGA || WIN32 || MSDOS */ - return TRUE; -} - -#ifdef VMS /* for unlock_file, use the unlink() routine in vmsunix.c */ -#ifdef unlink -#undef unlink -#endif -#define unlink(foo) vms_unlink(foo) -#endif - -/* unlock file, which must be currently locked by lock_file */ -void -unlock_file(filename) -const char *filename; -{ -#ifndef USE_FCNTL - char locknambuf[BUFSZ]; - const char *lockname; -#endif - - if (nesting == 1) { -#ifdef USE_FCNTL - sflock.l_type = F_UNLCK; - if (lockfd >= 0) { - if (fcntl(lockfd, F_SETLK, &sflock) == -1) - HUP raw_printf("Can't remove fcntl lock on %s.", filename); - (void) close(lockfd), lockfd = -1; - } -#else - lockname = make_lockname(filename, locknambuf); -#ifndef NO_FILE_LINKS /* LOCKDIR should be subsumed by LOCKPREFIX */ - lockname = fqname(lockname, LOCKPREFIX, 2); -#endif - -#if defined(UNIX) || defined(VMS) - if (unlink(lockname) < 0) - HUP raw_printf("Can't unlink %s.", lockname); -#ifdef NO_FILE_LINKS - (void) nhclose(lockfd), lockfd = -1; -#endif - -#endif /* UNIX || VMS */ - -#if defined(AMIGA) || defined(WIN32) || defined(MSDOS) - if (lockptr) - Close(lockptr); - DeleteFile(lockname); - lockptr = 0; -#endif /* AMIGA || WIN32 || MSDOS */ -#endif /* USE_FCNTL */ - } - - nesting--; -} - -/* ---------- END FILE LOCKING HANDLING ----------- */ - -/* ---------- BEGIN CONFIG FILE HANDLING ----------- */ - -const char *default_configfile = -#ifdef UNIX - ".nethackrc"; -#else -#if defined(MAC) || defined(__BEOS__) - "NetHack Defaults"; -#else -#if defined(MSDOS) || defined(WIN32) - CONFIG_FILE; -#else - "NetHack.cnf"; -#endif -#endif -#endif - -/* used for messaging */ -char configfile[BUFSZ]; - -#ifdef MSDOS -/* conflict with speed-dial under windows - * for XXX.cnf file so support of NetHack.cnf - * is for backward compatibility only. - * Preferred name (and first tried) is now defaults.nh but - * the game will try the old name if there - * is no defaults.nh. - */ -const char *backward_compat_configfile = "nethack.cnf"; -#endif - -/* remember the name of the file we're accessing; - if may be used in option reject messages */ -STATIC_OVL void -set_configfile_name(fname) -const char *fname; -{ - (void) strncpy(configfile, fname, sizeof configfile - 1); - configfile[sizeof configfile - 1] = '\0'; -} - -#ifndef MFLOPPY -#define fopenp fopen -#endif - -STATIC_OVL FILE * -fopen_config_file(filename, src) -const char *filename; -int src; -{ - FILE *fp; -#if defined(UNIX) || defined(VMS) - char tmp_config[BUFSZ]; - char *envp; -#endif - - if (src == SET_IN_SYS) { - /* SYSCF_FILE; if we can't open it, caller will bail */ - if (filename && *filename) { - set_configfile_name(fqname(filename, SYSCONFPREFIX, 0)); - fp = fopenp(configfile, "r"); - } else - fp = (FILE *) 0; - return fp; - } - /* If src != SET_IN_SYS, "filename" is an environment variable, so it - * should hang around. If set, it is expected to be a full path name - * (if relevant) - */ - if (filename && *filename) { - set_configfile_name(filename); -#ifdef UNIX - if (access(configfile, 4) == -1) { /* 4 is R_OK on newer systems */ - /* nasty sneaky attempt to read file through - * NetHack's setuid permissions -- this is the only - * place a file name may be wholly under the player's - * control (but SYSCF_FILE is not under the player's - * control so it's OK). - */ - raw_printf("Access to %s denied (%d).", configfile, errno); - wait_synch(); - /* fall through to standard names */ - } else -#endif - if ((fp = fopenp(configfile, "r")) != (FILE *) 0) { - return fp; -#if defined(UNIX) || defined(VMS) - } else { - /* access() above probably caught most problems for UNIX */ - raw_printf("Couldn't open requested config file %s (%d).", - configfile, errno); - wait_synch(); -#endif - } - } - /* fall through to standard names */ - - return (FILE *) 0; /* NLE: Stop here, don't read .nethackrc etc. */ - -#if defined(MICRO) || defined(MAC) || defined(__BEOS__) || defined(WIN32) - set_configfile_name(fqname(default_configfile, CONFIGPREFIX, 0)); - if ((fp = fopenp(configfile, "r")) != (FILE *) 0) { - return fp; - } else if (strcmp(default_configfile, configfile)) { - set_configfile_name(default_configfile); - if ((fp = fopenp(configfile, "r")) != (FILE *) 0) - return fp; - } -#ifdef MSDOS - set_configfile_name(fqname(backward_compat_configfile, CONFIGPREFIX, 0)); - if ((fp = fopenp(configfile, "r")) != (FILE *) 0) { - return fp; - } else if (strcmp(backward_compat_configfile, configfile)) { - set_configfile_name(backward_compat_configfile); - if ((fp = fopenp(configfile, "r")) != (FILE *) 0) - return fp; - } -#endif -#else -/* constructed full path names don't need fqname() */ -#ifdef VMS - /* no punctuation, so might be a logical name */ - set_configfile_name("nethackini"); - if ((fp = fopenp(configfile, "r")) != (FILE *) 0) - return fp; - set_configfile_name("sys$login:nethack.ini"); - if ((fp = fopenp(configfile, "r")) != (FILE *) 0) - return fp; - - envp = nh_getenv("HOME"); - if (!envp || !*envp) - Strcpy(tmp_config, "NetHack.cnf"); - else - Sprintf(tmp_config, "%s%s%s", envp, - !index(":]>/", envp[strlen(envp) - 1]) ? "/" : "", - "NetHack.cnf"); - set_configfile_name(tmp_config); - if ((fp = fopenp(configfile, "r")) != (FILE *) 0) - return fp; -#else /* should be only UNIX left */ - envp = nh_getenv("HOME"); - if (!envp) - Strcpy(tmp_config, ".nethackrc"); - else - Sprintf(tmp_config, "%s/%s", envp, ".nethackrc"); - - set_configfile_name(tmp_config); - if ((fp = fopenp(configfile, "r")) != (FILE *) 0) - return fp; -#if defined(__APPLE__) /* UNIX+__APPLE__ => MacOSX */ - /* try an alternative */ - if (envp) { - /* OSX-style configuration settings */ - Sprintf(tmp_config, "%s/%s", envp, - "Library/Preferences/NetHack Defaults"); - set_configfile_name(tmp_config); - if ((fp = fopenp(configfile, "r")) != (FILE *) 0) - return fp; - /* may be easier for user to edit if filename has '.txt' suffix */ - Sprintf(tmp_config, "%s/%s", envp, - "Library/Preferences/NetHack Defaults.txt"); - set_configfile_name(tmp_config); - if ((fp = fopenp(configfile, "r")) != (FILE *) 0) - return fp; - } -#endif /*__APPLE__*/ - if (errno != ENOENT) { - const char *details; - - /* e.g., problems when setuid NetHack can't search home - directory restricted to user */ -#if defined(NHSTDC) && !defined(NOTSTDC) - if ((details = strerror(errno)) == 0) -#endif - details = ""; - raw_printf("Couldn't open default config file %s %s(%d).", - configfile, details, errno); - wait_synch(); - } -#endif /* !VMS => Unix */ -#endif /* !(MICRO || MAC || __BEOS__ || WIN32) */ - return (FILE *) 0; -} - -/* - * Retrieve a list of integers from buf into a uchar array. - * - * NOTE: zeros are inserted unless modlist is TRUE, in which case the list - * location is unchanged. Callers must handle zeros if modlist is FALSE. - */ -STATIC_OVL int -get_uchars(bufp, list, modlist, size, name) -char *bufp; /* current pointer */ -uchar *list; /* return list */ -boolean modlist; /* TRUE: list is being modified in place */ -int size; /* return list size */ -const char *name; /* name of option for error message */ -{ - unsigned int num = 0; - int count = 0; - boolean havenum = FALSE; - - while (1) { - switch (*bufp) { - case ' ': - case '\0': - case '\t': - case '\n': - if (havenum) { - /* if modifying in place, don't insert zeros */ - if (num || !modlist) - list[count] = num; - count++; - num = 0; - havenum = FALSE; - } - if (count == size || !*bufp) - return count; - bufp++; - break; - - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - havenum = TRUE; - num = num * 10 + (*bufp - '0'); - bufp++; - break; - - case '\\': - goto gi_error; - break; - - default: - gi_error: - raw_printf("Syntax error in %s", name); - wait_synch(); - return count; - } - } - /*NOTREACHED*/ -} - -#ifdef NOCWD_ASSUMPTIONS -STATIC_OVL void -adjust_prefix(bufp, prefixid) -char *bufp; -int prefixid; -{ - char *ptr; - - if (!bufp) - return; -#ifdef WIN32 - if (fqn_prefix_locked[prefixid]) - return; -#endif - /* Backward compatibility, ignore trailing ;n */ - if ((ptr = index(bufp, ';')) != 0) - *ptr = '\0'; - if (strlen(bufp) > 0) { - fqn_prefix[prefixid] = (char *) alloc(strlen(bufp) + 2); - Strcpy(fqn_prefix[prefixid], bufp); - append_slash(fqn_prefix[prefixid]); - } -} -#endif - -/* Choose at random one of the sep separated parts from str. Mangles str. */ -STATIC_OVL char * -choose_random_part(str,sep) -char *str; -char sep; -{ - int nsep = 1; - int csep; - int len = 0; - char *begin = str; - - if (!str) - return (char *) 0; - - while (*str) { - if (*str == sep) - nsep++; - str++; - } - csep = rn2(nsep); - str = begin; - while ((csep > 0) && *str) { - str++; - if (*str == sep) - csep--; - } - if (*str) { - if (*str == sep) - str++; - begin = str; - while (*str && *str != sep) { - str++; - len++; - } - *str = '\0'; - if (len) - return begin; - } - return (char *) 0; -} - -STATIC_OVL void -free_config_sections() -{ - if (config_section_chosen) { - free(config_section_chosen); - config_section_chosen = NULL; - } - if (config_section_current) { - free(config_section_current); - config_section_current = NULL; - } -} - -STATIC_OVL boolean -is_config_section(str) -const char *str; -{ - const char *a = rindex(str, ']'); - - return (a && *str == '[' && *(a+1) == '\0' && (int)(a - str) > 0); -} - -STATIC_OVL boolean -handle_config_section(buf) -char *buf; -{ - if (is_config_section(buf)) { - char *send; - if (config_section_current) { - free(config_section_current); - } - config_section_current = dupstr(&buf[1]); - send = rindex(config_section_current, ']'); - *send = '\0'; - debugpline1("set config section: '%s'", config_section_current); - return TRUE; - } - - if (config_section_current) { - if (!config_section_chosen) - return TRUE; - if (strcmp(config_section_current, config_section_chosen)) - return TRUE; - } - return FALSE; -} - -#define match_varname(INP, NAM, LEN) match_optname(INP, NAM, LEN, TRUE) - -/* find the '=' or ':' */ -char * -find_optparam(buf) -const char *buf; -{ - char *bufp, *altp; - - bufp = index(buf, '='); - altp = index(buf, ':'); - if (!bufp || (altp && altp < bufp)) - bufp = altp; - - return bufp; -} - -boolean -parse_config_line(origbuf) -char *origbuf; -{ -#if defined(MICRO) && !defined(NOCWD_ASSUMPTIONS) - static boolean ramdisk_specified = FALSE; -#endif -#ifdef SYSCF - int n, src = iflags.parse_config_file_src; -#endif - char *bufp, buf[4 * BUFSZ]; - uchar translate[MAXPCHARS]; - int len; - boolean retval = TRUE; - - while (*origbuf == ' ' || *origbuf == '\t') /* skip leading whitespace */ - ++origbuf; /* (caller probably already did this) */ - (void) strncpy(buf, origbuf, sizeof buf - 1); - buf[sizeof buf - 1] = '\0'; /* strncpy not guaranteed to NUL terminate */ - /* convert any tab to space, condense consecutive spaces into one, - remove leading and trailing spaces (exception: if there is nothing - but spaces, one of them will be kept even though it leads/trails) */ - mungspaces(buf); - - /* find the '=' or ':' */ - bufp = find_optparam(buf); - if (!bufp) { - config_error_add("Not a config statement, missing '='"); - return FALSE; - } - /* skip past '=', then space between it and value, if any */ - ++bufp; - if (*bufp == ' ') - ++bufp; - - /* Go through possible variables */ - /* some of these (at least LEVELS and SAVE) should now set the - * appropriate fqn_prefix[] rather than specialized variables - */ - if (match_varname(buf, "OPTIONS", 4)) { - /* hack: un-mungspaces to allow consecutive spaces in - general options until we verify that this is unnecessary; - '=' or ':' is guaranteed to be present */ - bufp = find_optparam(origbuf); - ++bufp; /* skip '='; parseoptions() handles spaces */ - - if (!parseoptions(bufp, TRUE, TRUE)) - retval = FALSE; - } else if (match_varname(buf, "AUTOPICKUP_EXCEPTION", 5)) { - add_autopickup_exception(bufp); - } else if (match_varname(buf, "BINDINGS", 4)) { - if (!parsebindings(bufp)) - retval = FALSE; - } else if (match_varname(buf, "AUTOCOMPLETE", 5)) { - parseautocomplete(bufp, TRUE); - } else if (match_varname(buf, "MSGTYPE", 7)) { - if (!msgtype_parse_add(bufp)) - retval = FALSE; -#ifdef NOCWD_ASSUMPTIONS - } else if (match_varname(buf, "HACKDIR", 4)) { - adjust_prefix(bufp, HACKPREFIX); - } else if (match_varname(buf, "LEVELDIR", 4) - || match_varname(buf, "LEVELS", 4)) { - adjust_prefix(bufp, LEVELPREFIX); - } else if (match_varname(buf, "SAVEDIR", 4)) { - adjust_prefix(bufp, SAVEPREFIX); - } else if (match_varname(buf, "BONESDIR", 5)) { - adjust_prefix(bufp, BONESPREFIX); - } else if (match_varname(buf, "DATADIR", 4)) { - adjust_prefix(bufp, DATAPREFIX); - } else if (match_varname(buf, "SCOREDIR", 4)) { - adjust_prefix(bufp, SCOREPREFIX); - } else if (match_varname(buf, "LOCKDIR", 4)) { - adjust_prefix(bufp, LOCKPREFIX); - } else if (match_varname(buf, "CONFIGDIR", 4)) { - adjust_prefix(bufp, CONFIGPREFIX); - } else if (match_varname(buf, "TROUBLEDIR", 4)) { - adjust_prefix(bufp, TROUBLEPREFIX); -#else /*NOCWD_ASSUMPTIONS*/ -#ifdef MICRO - } else if (match_varname(buf, "HACKDIR", 4)) { - (void) strncpy(hackdir, bufp, PATHLEN - 1); -#ifdef MFLOPPY - } else if (match_varname(buf, "RAMDISK", 3)) { -/* The following ifdef is NOT in the wrong - * place. For now, we accept and silently - * ignore RAMDISK */ -#ifndef AMIGA - if (strlen(bufp) >= PATHLEN) - bufp[PATHLEN - 1] = '\0'; - Strcpy(levels, bufp); - ramdisk = (strcmp(permbones, levels) != 0); - ramdisk_specified = TRUE; -#endif -#endif - } else if (match_varname(buf, "LEVELS", 4)) { - if (strlen(bufp) >= PATHLEN) - bufp[PATHLEN - 1] = '\0'; - Strcpy(permbones, bufp); - if (!ramdisk_specified || !*levels) - Strcpy(levels, bufp); - ramdisk = (strcmp(permbones, levels) != 0); - } else if (match_varname(buf, "SAVE", 4)) { -#ifdef MFLOPPY - extern int saveprompt; -#endif - char *ptr; - - if ((ptr = index(bufp, ';')) != 0) { - *ptr = '\0'; -#ifdef MFLOPPY - if (*(ptr + 1) == 'n' || *(ptr + 1) == 'N') { - saveprompt = FALSE; - } -#endif - } -#if defined(SYSFLAGS) && defined(MFLOPPY) - else - saveprompt = sysflags.asksavedisk; -#endif - - (void) strncpy(SAVEP, bufp, SAVESIZE - 1); - append_slash(SAVEP); -#endif /* MICRO */ -#endif /*NOCWD_ASSUMPTIONS*/ - - } else if (match_varname(buf, "NAME", 4)) { - (void) strncpy(plname, bufp, PL_NSIZ - 1); - } else if (match_varname(buf, "ROLE", 4) - || match_varname(buf, "CHARACTER", 4)) { - if ((len = str2role(bufp)) >= 0) - flags.initrole = len; - } else if (match_varname(buf, "DOGNAME", 3)) { - (void) strncpy(dogname, bufp, PL_PSIZ - 1); - } else if (match_varname(buf, "CATNAME", 3)) { - (void) strncpy(catname, bufp, PL_PSIZ - 1); - -#ifdef SYSCF - } else if (src == SET_IN_SYS && match_varname(buf, "WIZARDS", 7)) { - if (sysopt.wizards) - free((genericptr_t) sysopt.wizards); - sysopt.wizards = dupstr(bufp); - if (strlen(sysopt.wizards) && strcmp(sysopt.wizards, "*")) { - /* pre-format WIZARDS list now; it's displayed during a panic - and since that panic might be due to running out of memory, - we don't want to risk attempting to allocate any memory then */ - if (sysopt.fmtd_wizard_list) - free((genericptr_t) sysopt.fmtd_wizard_list); - sysopt.fmtd_wizard_list = build_english_list(sysopt.wizards); - } - } else if (src == SET_IN_SYS && match_varname(buf, "SHELLERS", 8)) { - if (sysopt.shellers) - free((genericptr_t) sysopt.shellers); - sysopt.shellers = dupstr(bufp); - } else if (src == SET_IN_SYS && match_varname(buf, "EXPLORERS", 7)) { - if (sysopt.explorers) - free((genericptr_t) sysopt.explorers); - sysopt.explorers = dupstr(bufp); - } else if (src == SET_IN_SYS && match_varname(buf, "DEBUGFILES", 5)) { - /* if showdebug() has already been called (perhaps we've added - some debugpline() calls to option processing) and has found - a value for getenv("DEBUGFILES"), don't override that */ - if (sysopt.env_dbgfl <= 0) { - if (sysopt.debugfiles) - free((genericptr_t) sysopt.debugfiles); - sysopt.debugfiles = dupstr(bufp); - } - } else if (src == SET_IN_SYS && match_varname(buf, "DUMPLOGFILE", 7)) { -#ifdef DUMPLOG - if (sysopt.dumplogfile) - free((genericptr_t) sysopt.dumplogfile); - sysopt.dumplogfile = dupstr(bufp); -#endif - } else if (src == SET_IN_SYS && match_varname(buf, "GENERICUSERS", 12)) { - if (sysopt.genericusers) - free((genericptr_t) sysopt.genericusers); - sysopt.genericusers = dupstr(bufp); - } else if (src == SET_IN_SYS && match_varname(buf, "BONES_POOLS", 10)) { - /* max value of 10 guarantees (N % bones.pools) will be one digit - so we don't lose control of the length of bones file names */ - n = atoi(bufp); - sysopt.bones_pools = (n <= 0) ? 0 : min(n, 10); - /* note: right now bones_pools==0 is the same as bones_pools==1, - but we could change that and make bones_pools==0 become an - indicator to suppress bones usage altogether */ - } else if (src == SET_IN_SYS && match_varname(buf, "SUPPORT", 7)) { - if (sysopt.support) - free((genericptr_t) sysopt.support); - sysopt.support = dupstr(bufp); - } else if (src == SET_IN_SYS && match_varname(buf, "RECOVER", 7)) { - if (sysopt.recover) - free((genericptr_t) sysopt.recover); - sysopt.recover = dupstr(bufp); - } else if (src == SET_IN_SYS - && match_varname(buf, "CHECK_SAVE_UID", 14)) { - n = atoi(bufp); - sysopt.check_save_uid = n; - } else if (src == SET_IN_SYS - && match_varname(buf, "CHECK_PLNAME", 12)) { - n = atoi(bufp); - sysopt.check_plname = n; - } else if (match_varname(buf, "SEDUCE", 6)) { - n = !!atoi(bufp); /* XXX this could be tighter */ - /* allow anyone to turn it off, but only sysconf to turn it on*/ - if (src != SET_IN_SYS && n != 0) { - config_error_add("Illegal value in SEDUCE"); - return FALSE; - } - sysopt.seduce = n; - sysopt_seduce_set(sysopt.seduce); - } else if (src == SET_IN_SYS && match_varname(buf, "MAXPLAYERS", 10)) { - n = atoi(bufp); - /* XXX to get more than 25, need to rewrite all lock code */ - if (n < 0 || n > 25) { - config_error_add("Illegal value in MAXPLAYERS (maximum is 25)."); - return FALSE; - } - sysopt.maxplayers = n; - } else if (src == SET_IN_SYS && match_varname(buf, "PERSMAX", 7)) { - n = atoi(bufp); - if (n < 1) { - config_error_add("Illegal value in PERSMAX (minimum is 1)."); - return FALSE; - } - sysopt.persmax = n; - } else if (src == SET_IN_SYS && match_varname(buf, "PERS_IS_UID", 11)) { - n = atoi(bufp); - if (n != 0 && n != 1) { - config_error_add("Illegal value in PERS_IS_UID (must be 0 or 1)."); - return FALSE; - } - sysopt.pers_is_uid = n; - } else if (src == SET_IN_SYS && match_varname(buf, "ENTRYMAX", 8)) { - n = atoi(bufp); - if (n < 10) { - config_error_add("Illegal value in ENTRYMAX (minimum is 10)."); - return FALSE; - } - sysopt.entrymax = n; - } else if ((src == SET_IN_SYS) && match_varname(buf, "POINTSMIN", 9)) { - n = atoi(bufp); - if (n < 1) { - config_error_add("Illegal value in POINTSMIN (minimum is 1)."); - return FALSE; - } - sysopt.pointsmin = n; - } else if (src == SET_IN_SYS - && match_varname(buf, "MAX_STATUENAME_RANK", 10)) { - n = atoi(bufp); - if (n < 1) { - config_error_add( - "Illegal value in MAX_STATUENAME_RANK (minimum is 1)."); - return FALSE; - } - sysopt.tt_oname_maxrank = n; - - /* SYSCF PANICTRACE options */ - } else if (src == SET_IN_SYS - && match_varname(buf, "PANICTRACE_LIBC", 15)) { - n = atoi(bufp); -#if defined(PANICTRACE) && defined(PANICTRACE_LIBC) - if (n < 0 || n > 2) { - config_error_add("Illegal value in PANICTRACE_LIBC (not 0,1,2)."); - return FALSE; - } -#endif - sysopt.panictrace_libc = n; - } else if (src == SET_IN_SYS - && match_varname(buf, "PANICTRACE_GDB", 14)) { - n = atoi(bufp); -#if defined(PANICTRACE) - if (n < 0 || n > 2) { - config_error_add("Illegal value in PANICTRACE_GDB (not 0,1,2)."); - return FALSE; - } -#endif - sysopt.panictrace_gdb = n; - } else if (src == SET_IN_SYS && match_varname(buf, "GDBPATH", 7)) { -#if defined(PANICTRACE) && !defined(VMS) - if (!file_exists(bufp)) { - config_error_add("File specified in GDBPATH does not exist."); - return FALSE; - } -#endif - if (sysopt.gdbpath) - free((genericptr_t) sysopt.gdbpath); - sysopt.gdbpath = dupstr(bufp); - } else if (src == SET_IN_SYS && match_varname(buf, "GREPPATH", 7)) { -#if defined(PANICTRACE) && !defined(VMS) - if (!file_exists(bufp)) { - config_error_add("File specified in GREPPATH does not exist."); - return FALSE; - } -#endif - if (sysopt.greppath) - free((genericptr_t) sysopt.greppath); - sysopt.greppath = dupstr(bufp); - } else if (src == SET_IN_SYS - && match_varname(buf, "ACCESSIBILITY", 13)) { - n = atoi(bufp); - if (n < 0 || n > 1) { - config_error_add("Illegal value in ACCESSIBILITY (not 0,1)."); - return FALSE; - } - sysopt.accessibility = n; -#ifdef WIN32 - } else if (src == SET_IN_SYS - && match_varname(buf, "portable_device_paths", 8)) { - n = atoi(bufp); - if (n < 0 || n > 1) { - config_error_add("Illegal value in portable_device_paths (not 0,1)."); - return FALSE; - } - sysopt.portable_device_paths = n; -#endif -#endif /* SYSCF */ - - } else if (match_varname(buf, "BOULDER", 3)) { - (void) get_uchars(bufp, &ov_primary_syms[SYM_BOULDER + SYM_OFF_X], - TRUE, 1, "BOULDER"); - } else if (match_varname(buf, "MENUCOLOR", 9)) { - if (!add_menu_coloring(bufp)) - retval = FALSE; - } else if (match_varname(buf, "HILITE_STATUS", 6)) { -#ifdef STATUS_HILITES - if (!parse_status_hl1(bufp, TRUE)) - retval = FALSE; -#endif - } else if (match_varname(buf, "WARNINGS", 5)) { - (void) get_uchars(bufp, translate, FALSE, WARNCOUNT, - "WARNINGS"); - assign_warnings(translate); - } else if (match_varname(buf, "ROGUESYMBOLS", 4)) { - if (!parsesymbols(bufp, ROGUESET)) { - config_error_add("Error in ROGUESYMBOLS definition '%s'", bufp); - retval = FALSE; - } - switch_symbols(TRUE); - } else if (match_varname(buf, "SYMBOLS", 4)) { - if (!parsesymbols(bufp, PRIMARY)) { - config_error_add("Error in SYMBOLS definition '%s'", bufp); - retval = FALSE; - } - switch_symbols(TRUE); - } else if (match_varname(buf, "WIZKIT", 6)) { - (void) strncpy(wizkit, bufp, WIZKIT_MAX - 1); -#ifdef AMIGA - } else if (match_varname(buf, "FONT", 4)) { - char *t; - - if (t = strchr(buf + 5, ':')) { - *t = 0; - amii_set_text_font(buf + 5, atoi(t + 1)); - *t = ':'; - } - } else if (match_varname(buf, "PATH", 4)) { - (void) strncpy(PATH, bufp, PATHLEN - 1); - } else if (match_varname(buf, "DEPTH", 5)) { - extern int amii_numcolors; - int val = atoi(bufp); - - amii_numcolors = 1L << min(DEPTH, val); -#ifdef SYSFLAGS - } else if (match_varname(buf, "DRIPENS", 7)) { - int i, val; - char *t; - - for (i = 0, t = strtok(bufp, ",/"); t != (char *) 0; - i < 20 && (t = strtok((char *) 0, ",/")), ++i) { - sscanf(t, "%d", &val); - sysflags.amii_dripens[i] = val; - } -#endif - } else if (match_varname(buf, "SCREENMODE", 10)) { - extern long amii_scrnmode; - - if (!stricmp(bufp, "req")) - amii_scrnmode = 0xffffffff; /* Requester */ - else if (sscanf(bufp, "%x", &amii_scrnmode) != 1) - amii_scrnmode = 0; - } else if (match_varname(buf, "MSGPENS", 7)) { - extern int amii_msgAPen, amii_msgBPen; - char *t = strtok(bufp, ",/"); - - if (t) { - sscanf(t, "%d", &amii_msgAPen); - if (t = strtok((char *) 0, ",/")) - sscanf(t, "%d", &amii_msgBPen); - } - } else if (match_varname(buf, "TEXTPENS", 8)) { - extern int amii_textAPen, amii_textBPen; - char *t = strtok(bufp, ",/"); - - if (t) { - sscanf(t, "%d", &amii_textAPen); - if (t = strtok((char *) 0, ",/")) - sscanf(t, "%d", &amii_textBPen); - } - } else if (match_varname(buf, "MENUPENS", 8)) { - extern int amii_menuAPen, amii_menuBPen; - char *t = strtok(bufp, ",/"); - - if (t) { - sscanf(t, "%d", &amii_menuAPen); - if (t = strtok((char *) 0, ",/")) - sscanf(t, "%d", &amii_menuBPen); - } - } else if (match_varname(buf, "STATUSPENS", 10)) { - extern int amii_statAPen, amii_statBPen; - char *t = strtok(bufp, ",/"); - - if (t) { - sscanf(t, "%d", &amii_statAPen); - if (t = strtok((char *) 0, ",/")) - sscanf(t, "%d", &amii_statBPen); - } - } else if (match_varname(buf, "OTHERPENS", 9)) { - extern int amii_otherAPen, amii_otherBPen; - char *t = strtok(bufp, ",/"); - - if (t) { - sscanf(t, "%d", &amii_otherAPen); - if (t = strtok((char *) 0, ",/")) - sscanf(t, "%d", &amii_otherBPen); - } - } else if (match_varname(buf, "PENS", 4)) { - extern unsigned short amii_init_map[AMII_MAXCOLORS]; - int i; - char *t; - - for (i = 0, t = strtok(bufp, ",/"); - i < AMII_MAXCOLORS && t != (char *) 0; - t = strtok((char *) 0, ",/"), ++i) { - sscanf(t, "%hx", &amii_init_map[i]); - } - amii_setpens(amii_numcolors = i); - } else if (match_varname(buf, "FGPENS", 6)) { - extern int foreg[AMII_MAXCOLORS]; - int i; - char *t; - - for (i = 0, t = strtok(bufp, ",/"); - i < AMII_MAXCOLORS && t != (char *) 0; - t = strtok((char *) 0, ",/"), ++i) { - sscanf(t, "%d", &foreg[i]); - } - } else if (match_varname(buf, "BGPENS", 6)) { - extern int backg[AMII_MAXCOLORS]; - int i; - char *t; - - for (i = 0, t = strtok(bufp, ",/"); - i < AMII_MAXCOLORS && t != (char *) 0; - t = strtok((char *) 0, ",/"), ++i) { - sscanf(t, "%d", &backg[i]); - } -#endif /*AMIGA*/ -#ifdef USER_SOUNDS - } else if (match_varname(buf, "SOUNDDIR", 8)) { - sounddir = dupstr(bufp); - } else if (match_varname(buf, "SOUND", 5)) { - add_sound_mapping(bufp); -#endif - } else if (match_varname(buf, "QT_TILEWIDTH", 12)) { -#ifdef QT_GRAPHICS - extern char *qt_tilewidth; - - if (qt_tilewidth == NULL) - qt_tilewidth = dupstr(bufp); -#endif - } else if (match_varname(buf, "QT_TILEHEIGHT", 13)) { -#ifdef QT_GRAPHICS - extern char *qt_tileheight; - - if (qt_tileheight == NULL) - qt_tileheight = dupstr(bufp); -#endif - } else if (match_varname(buf, "QT_FONTSIZE", 11)) { -#ifdef QT_GRAPHICS - extern char *qt_fontsize; - - if (qt_fontsize == NULL) - qt_fontsize = dupstr(bufp); -#endif - } else if (match_varname(buf, "QT_COMPACT", 10)) { -#ifdef QT_GRAPHICS - extern int qt_compact_mode; - - qt_compact_mode = atoi(bufp); -#endif - } else { - config_error_add("Unknown config statement"); - return FALSE; - } - return retval; -} - -#ifdef USER_SOUNDS -boolean -can_read_file(filename) -const char *filename; -{ - return (boolean) (access(filename, 4) == 0); -} -#endif /* USER_SOUNDS */ - -struct _config_error_frame { - int line_num; - int num_errors; - boolean origline_shown; - boolean fromfile; - boolean secure; - char origline[4 * BUFSZ]; - char source[BUFSZ]; - struct _config_error_frame *next; -}; - -static struct _config_error_frame *config_error_data = 0; - -void -config_error_init(from_file, sourcename, secure) -boolean from_file; -const char *sourcename; -boolean secure; -{ - struct _config_error_frame *tmp = (struct _config_error_frame *) - alloc(sizeof (struct _config_error_frame)); - - tmp->line_num = 0; - tmp->num_errors = 0; - tmp->origline_shown = FALSE; - tmp->fromfile = from_file; - tmp->secure = secure; - tmp->origline[0] = '\0'; - if (sourcename && sourcename[0]) { - (void) strncpy(tmp->source, sourcename, sizeof (tmp->source) - 1); - tmp->source[sizeof (tmp->source) - 1] = '\0'; - } else - tmp->source[0] = '\0'; - - tmp->next = config_error_data; - config_error_data = tmp; -} - -STATIC_OVL boolean -config_error_nextline(line) -const char *line; -{ - struct _config_error_frame *ced = config_error_data; - - if (!ced) - return FALSE; - - if (ced->num_errors && ced->secure) - return FALSE; - - ced->line_num++; - ced->origline_shown = FALSE; - if (line && line[0]) { - (void) strncpy(ced->origline, line, sizeof (ced->origline) - 1); - ced->origline[sizeof (ced->origline) - 1] = '\0'; - } else - ced->origline[0] = '\0'; - - return TRUE; -} - -/* varargs 'config_error_add()' moved to pline.c */ -void -config_erradd(buf) -const char *buf; -{ - char lineno[QBUFSZ]; - - if (!buf || !*buf) - buf = "Unknown error"; - - if (!config_error_data) { - /* either very early, where pline() will use raw_print(), or - player gave bad value when prompted by interactive 'O' command */ - pline("%s%s.", !iflags.window_inited ? "config_error_add: " : "", buf); - wait_synch(); - return; - } - - config_error_data->num_errors++; - if (!config_error_data->origline_shown && !config_error_data->secure) { - pline("\n%s", config_error_data->origline); - config_error_data->origline_shown = TRUE; - } - if (config_error_data->line_num > 0 && !config_error_data->secure) { - Sprintf(lineno, "Line %d: ", config_error_data->line_num); - } else - lineno[0] = '\0'; - - pline("%s %s%s.", config_error_data->secure ? "Error:" : " *", - lineno, buf); -} - -int -config_error_done() -{ - int n; - struct _config_error_frame *tmp = config_error_data; - - if (!config_error_data) - return 0; - n = config_error_data->num_errors; - if (n) { - pline("\n%d error%s in %s.\n", n, - (n > 1) ? "s" : "", - *config_error_data->source - ? config_error_data->source : configfile); - wait_synch(); + if (!retryct) { + raw_printf("I give up. Sorry."); + gn.nesting--; + return FALSE; } - config_error_data = tmp->next; - free(tmp); - return n; +#endif /* AMIGA || WIN32 || MSDOS */ + return TRUE; } -boolean -read_config_file(filename, src) -const char *filename; -int src; +#ifdef VMS /* for unlock_file, use the unlink() routine in vmsunix.c */ +#ifdef unlink +#undef unlink +#endif +#define unlink(foo) vms_unlink(foo) +#endif + +/* unlock file, which must be currently locked by lock_file */ +void +unlock_file(const char *filename) { - FILE *fp; - boolean rv = TRUE; +#ifndef USE_FCNTL + char locknambuf[BUFSZ]; + const char *lockname; +#endif - if (!(fp = fopen_config_file(filename, src))) - return FALSE; + if (gn.nesting == 1) { +#ifdef USE_FCNTL + sflock.l_type = F_UNLCK; + if (lockfd >= 0) { + if (fcntl(lockfd, F_SETLK, &sflock) == -1) + HUP raw_printf("Can't remove fcntl lock on %s.", filename); + (void) close(lockfd), lockfd = -1; + } +#else + lockname = make_lockname(filename, locknambuf); +#ifndef NO_FILE_LINKS /* LOCKDIR should be subsumed by LOCKPREFIX */ + lockname = fqname(lockname, LOCKPREFIX, 2); +#endif - /* begin detection of duplicate configfile options */ - set_duplicate_opt_detection(1); - free_config_sections(); - iflags.parse_config_file_src = src; +#if defined(UNIX) || defined(VMS) + if (unlink(lockname) < 0) + HUP raw_printf("Can't unlink %s.", lockname); +#ifdef NO_FILE_LINKS + (void) nhclose(lockfd), lockfd = -1; +#endif - rv = parse_conf_file(fp, parse_config_line); - (void) fclose(fp); +#endif /* UNIX || VMS */ + +#if defined(AMIGA) || defined(WIN32) || defined(MSDOS) + if (gl.lockptr) + Close(gl.lockptr); + DeleteFile(lockname); + gl.lockptr = 0; +#endif /* AMIGA || WIN32 || MSDOS */ +#endif /* USE_FCNTL */ + } - free_config_sections(); - /* turn off detection of duplicate configfile options */ - set_duplicate_opt_detection(0); - return rv; + gn.nesting--; } -extern FILE* nle_fopen_wizkit_file(); +#undef UNUSED_conditional + +/* ---------- END FILE LOCKING HANDLING ----------- */ + +/* ---------- BEGIN WIZKIT FILE HANDLING ----------- */ -STATIC_OVL FILE * -fopen_wizkit_file() +staticfn FILE * +fopen_wizkit_file(void) { FILE *fp; #if defined(VMS) || defined(UNIX) @@ -2954,59 +2472,59 @@ fopen_wizkit_file() envp = nh_getenv("WIZKIT"); if (envp && *envp) - (void) strncpy(wizkit, envp, WIZKIT_MAX - 1); - if (!wizkit[0]) + (void) strncpy(gw.wizkit, envp, WIZKIT_MAX - 1); + if (!gw.wizkit[0]) return (FILE *) 0; #ifdef UNIX - if (access(wizkit, 4) == -1) { + if (access(gw.wizkit, 4) == -1) { /* 4 is R_OK on newer systems */ /* nasty sneaky attempt to read file through * NetHack's setuid permissions -- this is a * place a file name may be wholly under the player's * control */ - raw_printf("Access to %s denied (%d).", wizkit, errno); + raw_printf("Access to %s denied (%d).", gw.wizkit, errno); wait_synch(); /* fall through to standard names */ } else #endif - if ((fp = fopenp(wizkit, "r")) != (FILE *) 0) { + if ((fp = fopen(gw.wizkit, "r")) != (FILE *) 0) { return fp; #if defined(UNIX) || defined(VMS) } else { /* access() above probably caught most problems for UNIX */ - raw_printf("Couldn't open requested config file %s (%d).", wizkit, + raw_printf("Couldn't open requested wizkit file %s (%d).", gw.wizkit, errno); wait_synch(); #endif } -#if defined(MICRO) || defined(MAC) || defined(__BEOS__) || defined(WIN32) - if ((fp = fopenp(fqname(wizkit, CONFIGPREFIX, 0), "r")) != (FILE *) 0) +#if defined(MICRO) || defined(MACOS9) || defined(__BEOS__) || defined(WIN32) + if ((fp = fopen(fqname(gw.wizkit, CONFIGPREFIX, 0), "r")) != (FILE *) 0) return fp; #else #ifdef VMS envp = nh_getenv("HOME"); if (envp) - Sprintf(tmp_wizkit, "%s%s", envp, wizkit); + Sprintf(tmp_wizkit, "%s%s", envp, gw.wizkit); else - Sprintf(tmp_wizkit, "%s%s", "sys$login:", wizkit); - if ((fp = fopenp(tmp_wizkit, "r")) != (FILE *) 0) + Sprintf(tmp_wizkit, "%s%s", "sys$login:", gw.wizkit); + if ((fp = fopen(tmp_wizkit, "r")) != (FILE *) 0) return fp; #else /* should be only UNIX left */ envp = nh_getenv("HOME"); if (envp) - Sprintf(tmp_wizkit, "%s/%s", envp, wizkit); + Sprintf(tmp_wizkit, "%s/%s", envp, gw.wizkit); else - Strcpy(tmp_wizkit, wizkit); - if ((fp = fopenp(tmp_wizkit, "r")) != (FILE *) 0) + Strcpy(tmp_wizkit, gw.wizkit); + if ((fp = fopen(tmp_wizkit, "r")) != (FILE *) 0) return fp; else if (errno != ENOENT) { /* e.g., problems when setuid NetHack can't search home * directory restricted to user */ - raw_printf("Couldn't open default wizkit file %s (%d).", tmp_wizkit, - errno); + raw_printf("Couldn't open default gw.wizkit file %s (%d).", + tmp_wizkit, errno); wait_synch(); } #endif @@ -3015,20 +2533,19 @@ fopen_wizkit_file() } /* add to hero's inventory if there's room, otherwise put item on floor */ -STATIC_DCL void -wizkit_addinv(obj) -struct obj *obj; +staticfn void +wizkit_addinv(struct obj *obj) { - if (!obj || obj == &zeroobj) + if (!obj || obj == &hands_obj) return; /* subset of starting inventory pre-ID */ - obj->dknown = 1; - if (Role_if(PM_PRIEST)) + observe_object(obj); + if (Role_if(PM_CLERIC)) obj->bknown = 1; /* ok to bypass set_bknown() */ /* same criteria as lift_object()'s check for available inventory slot */ - if (obj->oclass != COIN_CLASS && inv_cnt(FALSE) >= 52 - && !merge_choice(invent, obj)) { + if (obj->oclass != COIN_CLASS && inv_cnt(FALSE) >= invlet_basic + && !merge_choice(gi.invent, obj)) { /* inventory overflow; can't just place & stack object since hero isn't in position yet, so schedule for arrival later */ add_to_migration(obj); @@ -3041,10 +2558,8 @@ struct obj *obj; } } - boolean -proc_wizkit_line(buf) -char *buf; +proc_wizkit_line(char *buf) { struct obj *otmp; @@ -3053,8 +2568,10 @@ char *buf; otmp = readobjnam(buf, (struct obj *) 0); if (otmp) { - if (otmp != &zeroobj) + if (otmp != &hands_obj) { + wish_history_add(buf); wizkit_addinv(otmp); + } } else { /* .60 limits output line width to 79 chars */ config_error_add("Bad wizkit item: \"%.60s\"", buf); @@ -3064,11 +2581,11 @@ char *buf; } void -read_wizkit() +read_wizkit(void) { FILE *fp; - if (!wizard || !(fp = nle_fopen_wizkit_file())) + if (!wizard || !(fp = fopen_wizkit_file())) return; program_state.wizkit_wishing = 1; @@ -3083,159 +2600,15 @@ read_wizkit() return; } -/* parse_conf_file - * - * Read from file fp, handling comments, empty lines, config sections, - * CHOOSE, and line continuation, calling proc for every valid line. - * - * Continued lines are merged together with one space in between. - */ -STATIC_OVL boolean -parse_conf_file(fp, proc) -FILE *fp; -boolean FDECL((*proc), (char *)); -{ - char inbuf[4 * BUFSZ]; - boolean rv = TRUE; /* assume successful parse */ - char *ep; - boolean skip = FALSE, morelines = FALSE; - char *buf = (char *) 0; - size_t inbufsz = sizeof inbuf; - - free_config_sections(); - - while (fgets(inbuf, (int) inbufsz, fp)) { - ep = index(inbuf, '\n'); - if (skip) { /* in case previous line was too long */ - if (ep) - skip = FALSE; /* found newline; next line is normal */ - } else { - if (!ep) { /* newline missing */ - if (strlen(inbuf) < (inbufsz - 2)) { - /* likely the last line of file is just - missing a newline; process it anyway */ - ep = eos(inbuf); - } else { - config_error_add("Line too long, skipping"); - skip = TRUE; /* discard next fgets */ - } - } else { - *ep = '\0'; /* remove newline */ - } - if (ep) { - char *tmpbuf = (char *) 0; - int len; - boolean ignoreline = FALSE; - boolean oldline = FALSE; - - /* line continuation (trailing '\') */ - morelines = (--ep >= inbuf && *ep == '\\'); - if (morelines) - *ep = '\0'; - - /* trim off spaces at end of line */ - while (ep >= inbuf - && (*ep == ' ' || *ep == '\t' || *ep == '\r')) - *ep-- = '\0'; - - if (!config_error_nextline(inbuf)) { - rv = FALSE; - if (buf) - free(buf), buf = (char *) 0; - break; - } - - ep = inbuf; - while (*ep == ' ' || *ep == '\t') - ++ep; - - /* ignore empty lines and full-line comment lines */ - if (!*ep || *ep == '#') - ignoreline = TRUE; - - if (buf) - oldline = TRUE; - - /* merge now read line with previous ones, if necessary */ - if (!ignoreline) { - len = (int) strlen(ep) + 1; /* +1: final '\0' */ - if (buf) - len += (int) strlen(buf) + 1; /* +1: space */ - tmpbuf = (char *) alloc(len); - *tmpbuf = '\0'; - if (buf) { - Strcat(strcpy(tmpbuf, buf), " "); - free(buf); - } - buf = strcat(tmpbuf, ep); - if (strlen(buf) >= sizeof inbuf) - buf[sizeof inbuf - 1] = '\0'; - } - - if (morelines || (ignoreline && !oldline)) - continue; - - if (handle_config_section(buf)) { - free(buf); - buf = (char *) 0; - continue; - } - - /* from here onwards, we'll handle buf only */ - - if (match_varname(buf, "CHOOSE", 6)) { - char *section; - char *bufp = find_optparam(buf); - - if (!bufp) { - config_error_add( - "Format is CHOOSE=section1,section2,..."); - rv = FALSE; - free(buf); - buf = (char *) 0; - continue; - } - bufp++; - if (config_section_chosen) - free(config_section_chosen), config_section_chosen = 0; - section = choose_random_part(bufp, ','); - if (section) { - config_section_chosen = dupstr(section); - } else { - config_error_add("No config section to choose"); - rv = FALSE; - } - free(buf); - buf = (char *) 0; - continue; - } - - if (!proc(buf)) - rv = FALSE; - - free(buf); - buf = (char *) 0; - } - } - } - - if (buf) - free(buf); +/* ---------- END WIZKIT FILE HANDLING ----------- */ - free_config_sections(); - return rv; -} +/* ---------- BEGIN SYMSET FILE HANDLING ----------- */ -extern struct symsetentry *symset_list; /* options.c */ -extern const char *known_handling[]; /* drawing.c */ -extern const char *known_restrictions[]; /* drawing.c */ -static int symset_count = 0; /* for pick-list building only */ -static boolean chosen_symset_start = FALSE, chosen_symset_end = FALSE; -static int symset_which_set = 0; +extern const char *const known_handling[]; /* symbols.c */ +extern const char *const known_restrictions[]; /* symbols.c */ -STATIC_OVL -FILE * -fopen_sym_file() +staticfn FILE * +fopen_sym_file(void) { FILE *fp; @@ -3251,270 +2624,56 @@ fopen_sym_file() } /* - * Returns 1 if the chose symset was found and loaded. + * Returns 1 if the chosen symset was found and loaded. * 0 if it wasn't found in the sym file or other problem. */ int -read_sym_file(which_set) -int which_set; +read_sym_file(int which_set) { FILE *fp; - symset[which_set].explicitly = FALSE; + gs.symset[which_set].explicitly = FALSE; if (!(fp = fopen_sym_file())) return 0; - symset[which_set].explicitly = TRUE; - symset_count = 0; - chosen_symset_start = chosen_symset_end = FALSE; - symset_which_set = which_set; + gs.symset[which_set].explicitly = TRUE; + gc.chosen_symset_start = gc.chosen_symset_end = FALSE; + gs.symset_which_set = which_set; + gs.symset_count = 0; config_error_init(TRUE, "symbols", FALSE); parse_conf_file(fp, proc_symset_line); (void) fclose(fp); - if (!chosen_symset_start && !chosen_symset_end) { + if (!gc.chosen_symset_start && !gc.chosen_symset_end) { /* name caller put in symset[which_set].name was not found; if it looks like "Default symbols", null it out and return success to use the default; otherwise, return failure */ - if (symset[which_set].name - && (fuzzymatch(symset[which_set].name, "Default symbols", + if (gs.symset[which_set].name + && (fuzzymatch(gs.symset[which_set].name, "Default symbols", " -_", TRUE) - || !strcmpi(symset[which_set].name, "default"))) + || !strcmpi(gs.symset[which_set].name, "default"))) clear_symsetentry(which_set, TRUE); config_error_done(); - /* If name was defined, it was invalid... Then we're loading fallback */ - if (symset[which_set].name) { - symset[which_set].explicitly = FALSE; + /* If name was defined, it was invalid. Then we're loading fallback */ + if (gs.symset[which_set].name) { + gs.symset[which_set].explicitly = FALSE; return 0; } return 1; } - if (!chosen_symset_end) + if (!gc.chosen_symset_end) config_error_add("Missing finish for symset \"%s\"", - symset[which_set].name ? symset[which_set].name + gs.symset[which_set].name ? gs.symset[which_set].name : "unknown"); config_error_done(); return 1; } -boolean -proc_symset_line(buf) -char *buf; -{ - return !((boolean) parse_sym_line(buf, symset_which_set)); -} - -/* returns 0 on error */ -int -parse_sym_line(buf, which_set) -char *buf; -int which_set; -{ - int val, i; - struct symparse *symp; - char *bufp, *commentp, *altp; - - if (strlen(buf) >= BUFSZ) - buf[BUFSZ - 1] = '\0'; - /* convert each instance of whitespace (tabs, consecutive spaces) - into a single space; leading and trailing spaces are stripped */ - mungspaces(buf); - - /* remove trailing comment, if any (this isn't strictly needed for - individual symbols, and it won't matter if "X#comment" without - separating space slips through; for handling or set description, - symbol set creator is responsible for preceding '#' with a space - and that comment itself doesn't contain " #") */ - if ((commentp = rindex(buf, '#')) != 0 && commentp[-1] == ' ') - commentp[-1] = '\0'; - - /* find the '=' or ':' */ - bufp = index(buf, '='); - altp = index(buf, ':'); - if (!bufp || (altp && altp < bufp)) - bufp = altp; - if (!bufp) { - if (strncmpi(buf, "finish", 6) == 0) { - /* end current graphics set */ - if (chosen_symset_start) - chosen_symset_end = TRUE; - chosen_symset_start = FALSE; - return 1; - } - config_error_add("No \"finish\""); - return 0; - } - /* skip '=' and space which follows, if any */ - ++bufp; - if (*bufp == ' ') - ++bufp; - - symp = match_sym(buf); - if (!symp) { - config_error_add("Unknown sym keyword"); - return 0; - } - - if (!symset[which_set].name) { - /* A null symset name indicates that we're just - building a pick-list of possible symset - values from the file, so only do that */ - if (symp->range == SYM_CONTROL) { - struct symsetentry *tmpsp, *lastsp; - - for (lastsp = symset_list; lastsp; lastsp = lastsp->next) - if (!lastsp->next) - break; - switch (symp->idx) { - case 0: - tmpsp = (struct symsetentry *) alloc(sizeof *tmpsp); - tmpsp->next = (struct symsetentry *) 0; - if (!lastsp) - symset_list = tmpsp; - else - lastsp->next = tmpsp; - tmpsp->idx = symset_count++; - tmpsp->name = dupstr(bufp); - tmpsp->desc = (char *) 0; - tmpsp->handling = H_UNK; - /* initialize restriction bits */ - tmpsp->nocolor = 0; - tmpsp->primary = 0; - tmpsp->rogue = 0; - break; - case 2: - /* handler type identified */ - tmpsp = lastsp; /* most recent symset */ - for (i = 0; known_handling[i]; ++i) - if (!strcmpi(known_handling[i], bufp)) { - tmpsp->handling = i; - break; /* for loop */ - } - break; - case 3: - /* description:something */ - tmpsp = lastsp; /* most recent symset */ - if (tmpsp && !tmpsp->desc) - tmpsp->desc = dupstr(bufp); - break; - case 5: - /* restrictions: xxxx*/ - tmpsp = lastsp; /* most recent symset */ - for (i = 0; known_restrictions[i]; ++i) { - if (!strcmpi(known_restrictions[i], bufp)) { - switch (i) { - case 0: - tmpsp->primary = 1; - break; - case 1: - tmpsp->rogue = 1; - break; - } - break; /* while loop */ - } - } - break; - } - } - return 1; - } - if (symp->range) { - if (symp->range == SYM_CONTROL) { - switch (symp->idx) { - case 0: - /* start of symset */ - if (!strcmpi(bufp, symset[which_set].name)) { - /* matches desired one */ - chosen_symset_start = TRUE; - /* these init_*() functions clear symset fields too */ - if (which_set == ROGUESET) - init_rogue_symbols(); - else if (which_set == PRIMARY) - init_primary_symbols(); - } - break; - case 1: - /* finish symset */ - if (chosen_symset_start) - chosen_symset_end = TRUE; - chosen_symset_start = FALSE; - break; - case 2: - /* handler type identified */ - if (chosen_symset_start) - set_symhandling(bufp, which_set); - break; - /* case 3: (description) is ignored here */ - case 4: /* color:off */ - if (chosen_symset_start) { - if (bufp) { - if (!strcmpi(bufp, "true") || !strcmpi(bufp, "yes") - || !strcmpi(bufp, "on")) - symset[which_set].nocolor = 0; - else if (!strcmpi(bufp, "false") - || !strcmpi(bufp, "no") - || !strcmpi(bufp, "off")) - symset[which_set].nocolor = 1; - } - } - break; - case 5: /* restrictions: xxxx*/ - if (chosen_symset_start) { - int n = 0; - - while (known_restrictions[n]) { - if (!strcmpi(known_restrictions[n], bufp)) { - switch (n) { - case 0: - symset[which_set].primary = 1; - break; - case 1: - symset[which_set].rogue = 1; - break; - } - break; /* while loop */ - } - n++; - } - } - break; - } - } else { /* !SYM_CONTROL */ - val = sym_val(bufp); - if (chosen_symset_start) { - if (which_set == PRIMARY) { - update_primary_symset(symp, val); - } else if (which_set == ROGUESET) { - update_rogue_symset(symp, val); - } - } - } - } - return 1; -} - -STATIC_OVL void -set_symhandling(handling, which_set) -char *handling; -int which_set; -{ - int i = 0; - - symset[which_set].handling = H_UNK; - while (known_handling[i]) { - if (!strcmpi(known_handling[i], handling)) { - symset[which_set].handling = i; - return; - } - i++; - } -} - -/* ---------- END CONFIG FILE HANDLING ----------- */ +/* ---------- END SYMSET FILE HANDLING ----------- */ /* ---------- BEGIN SCOREBOARD CREATION ----------- */ @@ -3527,12 +2686,8 @@ int which_set; /* verify that we can write to scoreboard file; if not, try to create one */ /*ARGUSED*/ void -check_recordfile(dir) -const char *dir UNUSED_if_not_OS2_CODEVIEW; +check_recordfile(const char *dir UNUSED_if_not_OS2_CODEVIEW) { -#if defined(PRAGMA_UNUSED) && !defined(OS2_CODEVIEW) -#pragma unused(dir) -#endif const char *fq_record; int fd; @@ -3542,8 +2697,8 @@ const char *dir UNUSED_if_not_OS2_CODEVIEW; if (fd >= 0) { #ifdef VMS /* must be stream-lf to use UPDATE_RECORD_IN_PLACE */ if (!file_is_stmlf(fd)) { - raw_printf( - "Warning: scoreboard file '%s' is not in stream_lf format", + raw_printf("Warning: scoreboard file '%s'" + " is not in stream_lf format", fq_record); wait_synch(); } @@ -3564,7 +2719,7 @@ const char *dir UNUSED_if_not_OS2_CODEVIEW; char tmp[PATHLEN]; #ifdef OS2_CODEVIEW /* explicit path on opening for OS/2 */ - /* how does this work when there isn't an explicit path or fopenp + /* how does this work when there isn't an explicit path or fopen * for later access to the file via fopen_datafile? ? */ (void) strncpy(tmp, dir, PATHLEN - 1); tmp[PATHLEN - 1] = '\0'; @@ -3624,30 +2779,31 @@ const char *dir UNUSED_if_not_OS2_CODEVIEW; } #else /* MICRO || WIN32*/ -#ifdef MAC +#ifdef MACOS9 /* Create the "record" file, if necessary */ fq_record = fqname(RECORD, SCOREPREFIX, 0); fd = macopen(fq_record, O_RDWR | O_CREAT, TEXT_TYPE); if (fd != -1) macclose(fd); -#endif /* MAC */ +#endif /* MACOS9 */ #endif /* MICRO || WIN32*/ } +#undef UNUSED_if_not_OS2_CODEVIEW + /* ---------- END SCOREBOARD CREATION ----------- */ /* ---------- BEGIN PANIC/IMPOSSIBLE/TESTING LOG ----------- */ /*ARGSUSED*/ void -paniclog(type, reason) -const char *type; /* panic, impossible, trickery */ -const char *reason; /* explanation */ +paniclog( + const char *type, /* panic, impossible, trickery, [lua] */ + const char *reason) /* explanation */ { #ifdef PANICLOG FILE *lfile; - char buf[BUFSZ]; if (!program_state.in_paniclog) { program_state.in_paniclog = 1; @@ -3655,15 +2811,17 @@ const char *reason; /* explanation */ if (lfile) { #ifdef PANICLOG_FMT2 (void) fprintf(lfile, "%ld %s: %s %s\n", - ubirthday, (plname ? plname : "(none)"), + ubirthday, (svp.plname[0] ? svp.plname : "(none)"), type, reason); #else + char buf[BUFSZ]; time_t now = getnow(); int uid = getuid(); char playmode = wizard ? 'D' : discover ? 'X' : '-'; (void) fprintf(lfile, "%s %08ld %06ld %d %c: %s %s\n", - version_string(buf), yyyymmdd(now), hhmmss(now), + version_string(buf, sizeof buf), + yyyymmdd(now), hhmmss(now), uid, playmode, type, reason); #endif /* !PANICLOG_FMT2 */ (void) fclose(lfile); @@ -3675,10 +2833,9 @@ const char *reason; /* explanation */ } void -testinglog(filenm, type, reason) -const char *filenm; /* ad hoc file name */ -const char *type; -const char *reason; /* explanation */ +testinglog(const char *filenm, /* ad hoc file name */ + const char *type, + const char *reason) /* explanation */ { FILE *lfile; char fnbuf[BUFSZ]; @@ -3686,7 +2843,7 @@ const char *reason; /* explanation */ if (!filenm) return; Strcpy(fnbuf, filenm); - if (index(fnbuf, '.') == 0) + if (strchr(fnbuf, '.') == 0) Strcat(fnbuf, ".log"); lfile = fopen_datafile(fnbuf, "a", TROUBLEPREFIX); if (lfile) { @@ -3701,17 +2858,22 @@ const char *reason; /* explanation */ #ifdef SELF_RECOVER /* ---------- BEGIN INTERNAL RECOVER ----------- */ + +extern uchar critical_sizes[], cscbuf[]; /* version.c */ + boolean -recover_savefile() +recover_savefile(void) { - int gfd, lfd, sfd; - int lev, savelev, hpid, pltmpsiz; - xchar levc; + NHFILE *gnhfp, *lnhfp, *snhfp; + int lev, savelev, hpid, + pltmpsiz, cscount = get_critical_size_count(); + xint8 levc; struct version_info version_data; int processed[256]; - char savename[SAVESIZE], errbuf[BUFSZ]; - struct savefile_info sfi; - char tmpplbuf[PL_NSIZ]; + char savename[SAVESIZE], errbuf[BUFSZ], indicator, file_cscount; + char tmpplbuf[PL_NSIZ_PLUS]; + const char *savewrite_failure = (const char *) 0; + off_t filesz = 0; for (lev = 0; lev < 256; lev++) processed[lev] = 0; @@ -3720,154 +2882,175 @@ recover_savefile() * pid of creating process (ignored here) * level number for current level of save file * name of save file nethack would have created - * savefile info * player name * and game state */ - gfd = open_levelfile(0, errbuf); - if (gfd < 0) { + + gnhfp = open_levelfile(0, errbuf); + if (!gnhfp) { raw_printf("%s\n", errbuf); return FALSE; } - if (read(gfd, (genericptr_t) &hpid, sizeof hpid) != sizeof hpid) { + filesz = lseek(gnhfp->fd, 0L, SEEK_END); + (void) lseek(gnhfp->fd, 0L, SEEK_SET); + if ((size_t) filesz < (sizeof hpid + + sizeof lev + + sizeof savename + + sizeof indicator + + sizeof file_cscount + + sizeof version_data + sizeof pltmpsiz)) { + const char *fq_save; + + /* this indicates a .0 file that was created as part of + * recover that did not complete. There could be an intact + * savefile already there. Check for that and return TRUE + * if there is. */ + set_savefile_name(TRUE); + fq_save = fqname(gs.SAVEF, SAVEPREFIX, 0); + if (access(fq_save, F_OK) == 0) { + close_nhfile(gnhfp); + delete_levelfile(0); + return TRUE; + } else { + /* savefile doesn't exist, so fall through */ + } + } + if (read(gnhfp->fd, (genericptr_t) &hpid, sizeof hpid) != sizeof hpid) { raw_printf("\n%s\n%s\n", - "Checkpoint data incompletely written or subsequently clobbered.", + "Checkpoint data incompletely written" + " or subsequently clobbered.", "Recovery impossible."); - (void) nhclose(gfd); + close_nhfile(gnhfp); return FALSE; } - if (read(gfd, (genericptr_t) &savelev, sizeof(savelev)) + if (read(gnhfp->fd, (genericptr_t) &savelev, sizeof(savelev)) != sizeof(savelev)) { - raw_printf( - "\nCheckpointing was not in effect for %s -- recovery impossible.\n", - lock); - (void) nhclose(gfd); + raw_printf("\n%s %s %s\n", + "Checkpointing was not in effect for", + gl.lock, + "-- recovery impossible."); + close_nhfile(gnhfp); return FALSE; } - if ((read(gfd, (genericptr_t) savename, sizeof savename) + if ((read(gnhfp->fd, (genericptr_t) savename, sizeof savename) != sizeof savename) - || (read(gfd, (genericptr_t) &version_data, sizeof version_data) + || (read(gnhfp->fd, (genericptr_t) &indicator, sizeof indicator) + != sizeof indicator) + || (read(gnhfp->fd, (genericptr_t) &file_cscount, sizeof file_cscount) + != sizeof file_cscount) + || (file_cscount <= cscount + && read(gnhfp->fd, (genericptr_t) &cscbuf, file_cscount) + != file_cscount) + || (read(gnhfp->fd, (genericptr_t) &version_data, sizeof version_data) != sizeof version_data) - || (read(gfd, (genericptr_t) &sfi, sizeof sfi) != sizeof sfi) - || (read(gfd, (genericptr_t) &pltmpsiz, sizeof pltmpsiz) - != sizeof pltmpsiz) || (pltmpsiz > PL_NSIZ) - || (read(gfd, (genericptr_t) &tmpplbuf, pltmpsiz) != pltmpsiz)) { - raw_printf("\nError reading %s -- can't recover.\n", lock); - (void) nhclose(gfd); + || (read(gnhfp->fd, (genericptr_t) &pltmpsiz, sizeof pltmpsiz) + != sizeof pltmpsiz) || (pltmpsiz > PL_NSIZ_PLUS) + || (read(gnhfp->fd, (genericptr_t) &tmpplbuf, pltmpsiz) + != pltmpsiz)) { + raw_printf("\nError reading %s -- can't recover.\n", gl.lock); + close_nhfile(gnhfp); return FALSE; } /* save file should contain: + * format indicator (1 byte) + * n = count of critical size list (1 byte) + * n bytes of critical sizes (n bytes) * version info - * savefile info - * player name + * plnametmp = player name size (int, 2 bytes) + * player name (PL_NSIZ_PLUS) * current level (including pets) * (non-level-based) game state * other levels */ + + /* + * + * Set a flag for the savefile routines to know the + * circumstances and act accordingly: + * program_state.in_self_recover + */ + program_state.in_self_recover = TRUE; set_savefile_name(TRUE); - sfd = create_savefile(); - if (sfd < 0) { - raw_printf("\nCannot recover savefile %s.\n", SAVEF); - (void) nhclose(gfd); + snhfp = create_savefile(); + if (!snhfp) { + raw_printf("\nCannot recover savefile %s.\n", gs.SAVEF); + close_nhfile(gnhfp); return FALSE; } - lfd = open_levelfile(savelev, errbuf); - if (lfd < 0) { + lnhfp = open_levelfile(savelev, errbuf); + if (!lnhfp) { raw_printf("\n%s\n", errbuf); - (void) nhclose(gfd); - (void) nhclose(sfd); + close_nhfile(gnhfp); + close_nhfile(snhfp); delete_savefile(); return FALSE; } - if (write(sfd, (genericptr_t) &version_data, sizeof version_data) - != sizeof version_data) { - raw_printf("\nError writing %s; recovery failed.", SAVEF); - (void) nhclose(gfd); - (void) nhclose(sfd); - (void) nhclose(lfd); - delete_savefile(); - return FALSE; - } + store_version(snhfp); - if (write(sfd, (genericptr_t) &sfi, sizeof sfi) != sizeof sfi) { - raw_printf("\nError writing %s; recovery failed (savefile_info).\n", - SAVEF); - (void) nhclose(gfd); - (void) nhclose(sfd); - (void) nhclose(lfd); - delete_savefile(); - return FALSE; - } + if (savewrite_failure) + goto cleanup; - if (write(sfd, (genericptr_t) &pltmpsiz, sizeof pltmpsiz) - != sizeof pltmpsiz) { - raw_printf("Error writing %s; recovery failed (player name size).\n", - SAVEF); - (void) nhclose(gfd); - (void) nhclose(sfd); - (void) nhclose(lfd); - delete_savefile(); - return FALSE; - } + if (snhfp->structlevel) + bufoff(snhfp->fd); - if (write(sfd, (genericptr_t) &tmpplbuf, pltmpsiz) != pltmpsiz) { - raw_printf("Error writing %s; recovery failed (player name).\n", - SAVEF); - (void) nhclose(gfd); - (void) nhclose(sfd); - (void) nhclose(lfd); - delete_savefile(); - return FALSE; - } + /* TODO: this is not a single byte, so a big-endian byte swap + * might be necessary here, if anyone is concerned about big-endian */ + Sfo_int(snhfp, &pltmpsiz, "plname-size"); + savewrite_failure = (const char *) 0; + if (savewrite_failure) + goto cleanup; + + Sfo_char(snhfp, tmpplbuf, "plname", pltmpsiz); + savewrite_failure = (const char *) 0; + if (savewrite_failure) + goto cleanup; - if (!copy_bytes(lfd, sfd)) { - (void) nhclose(gfd); - (void) nhclose(sfd); - (void) nhclose(lfd); + if (!copy_bytes(lnhfp->fd, snhfp->fd)) { + close_nhfile(gnhfp); + close_nhfile(snhfp); + close_nhfile(lnhfp); delete_savefile(); return FALSE; } - (void) nhclose(lfd); + close_nhfile(lnhfp); processed[savelev] = 1; - if (!copy_bytes(gfd, sfd)) { - (void) nhclose(gfd); - (void) nhclose(sfd); + if (!copy_bytes(gnhfp->fd, snhfp->fd)) { + close_nhfile(gnhfp); + close_nhfile(snhfp); delete_savefile(); return FALSE; } - (void) nhclose(gfd); + close_nhfile(gnhfp); processed[0] = 1; for (lev = 1; lev < 256; lev++) { - /* level numbers are kept in xchars in save.c, so the + /* level numbers are kept in xint8's in save.c, so the * maximum level number (for the endlevel) must be < 256 */ if (lev != savelev) { - lfd = open_levelfile(lev, (char *) 0); - if (lfd >= 0) { + lnhfp = open_levelfile(lev, (char *) 0); + if (lnhfp) { /* any or all of these may not exist */ - levc = (xchar) lev; - write(sfd, (genericptr_t) &levc, sizeof(levc)); - if (!copy_bytes(lfd, sfd)) { - (void) nhclose(lfd); - (void) nhclose(sfd); + levc = (xint8) lev; + (void) write(snhfp->fd, (genericptr_t) &levc, sizeof(levc)); + if (!copy_bytes(lnhfp->fd, snhfp->fd)) { + close_nhfile(lnhfp); + close_nhfile(snhfp); delete_savefile(); return FALSE; } - (void) nhclose(lfd); + close_nhfile(lnhfp); processed[lev] = 1; } } } - (void) nhclose(sfd); - -#ifdef HOLD_LOCKFILE_OPEN - really_close(); -#endif + if (snhfp->structlevel) + bufon(snhfp->fd); + close_nhfile(snhfp); /* * We have a successful savefile! * Only now do we erase the level files. @@ -3876,27 +3059,25 @@ recover_savefile() if (processed[lev]) { const char *fq_lock; - set_levelfile_name(lock, lev); - fq_lock = fqname(lock, LEVELPREFIX, 3); + set_levelfile_name(gl.lock, lev); + fq_lock = fqname(gl.lock, LEVELPREFIX, 3); (void) unlink(fq_lock); } } - return TRUE; -} - -boolean -copy_bytes(ifd, ofd) -int ifd, ofd; -{ - char buf[BUFSIZ]; - int nfrom, nto; - - do { - nfrom = read(ifd, buf, BUFSIZ); - nto = write(ofd, buf, nfrom); - if (nto != nfrom) - return FALSE; - } while (nfrom == BUFSIZ); + cleanup: + if (savewrite_failure) { + raw_printf("\nError writing %s; recovery failed (%s).\n", + gs.SAVEF, savewrite_failure); + close_nhfile(gnhfp); + close_nhfile(snhfp); + close_nhfile(lnhfp); + program_state.in_self_recover = FALSE; + delete_savefile(); + return FALSE; + } + /* we don't clear program_state.in_self_recover here, we + leave it as a flag to reload the structlevel savefile + in the caller. The caller should then clear it. */ return TRUE; } @@ -3905,46 +3086,33 @@ int ifd, ofd; /* ---------- OTHER ----------- */ -#ifdef SYSCF -#ifdef SYSCF_FILE -void -assure_syscf_file() +ATTRNORETURN void +do_deferred_showpaths(int code) { - int fd; + gd.deferred_showpaths = FALSE; + reveal_paths(code); -#ifdef WIN32 - /* We are checking that the sysconf exists ... lock the path */ - fqn_prefix_locked[SYSCONFPREFIX] = TRUE; + /* cleanup before heading to an exit */ + freedynamicdata(); + dlb_cleanup(); + l_nhcore_done(); + +#ifdef UNIX + after_opt_showpaths(gd.deferred_showpaths_dir); +#else +#ifndef WIN32 +#ifdef CHDIR + chdirx(gd.deferred_showpaths_dir, 0); #endif - /* - * All we really care about is the end result - can we read the file? - * So just check that directly. - * - * Not tested on most of the old platforms (which don't attempt - * to implement SYSCF). - * Some ports don't like open()'s optional third argument; - * VMS overrides open() usage with a macro which requires it. - */ -#ifndef VMS -# if defined(NOCWD_ASSUMPTIONS) && defined(WIN32) - fd = open(fqname(SYSCF_FILE, SYSCONFPREFIX, 0), O_RDONLY); -# else - fd = open(SYSCF_FILE, O_RDONLY); -# endif +#endif +#if defined(WIN32) || defined(MICRO) || defined(OS2) + nethack_exit(EXIT_SUCCESS); #else - fd = open(SYSCF_FILE, O_RDONLY, 0); + exit(EXIT_SUCCESS); +#endif #endif - if (fd >= 0) { - /* readable */ - close(fd); - return; - } - raw_printf("Unable to open SYSCF_FILE.\n"); - exit(EXIT_FAILURE); } - -#endif /* SYSCF_FILE */ -#endif /* SYSCF */ +#endif /* !SFCTOOL */ #ifdef DEBUG /* used by debugpline() to decide whether to issue a message @@ -3955,46 +3123,25 @@ assure_syscf_file() * like dungeon.c and questpgr.c, which generate a ridiculous amount of * output if DEBUG is defined and effectively block the use of a wildcard */ boolean -debugcore(filename, wildcards) -const char *filename; -boolean wildcards; +debugcore(const char *filename, boolean wildcards) { const char *debugfiles, *p; + /* debugpline() messages might disclose information that the player + doesn't normally get to see, so only display them in wizard mode */ + if (!wizard) + return FALSE; + if (!filename || !*filename) return FALSE; /* sanity precaution */ - if (sysopt.env_dbgfl == 0) { - /* check once for DEBUGFILES in the environment; - if found, it supersedes the sysconf value - [note: getenv() rather than nh_getenv() since a long value - is valid and doesn't pose any sort of overflow risk here] */ - if ((p = getenv("DEBUGFILES")) != 0) { - if (sysopt.debugfiles) - free((genericptr_t) sysopt.debugfiles); - sysopt.debugfiles = dupstr(p); - sysopt.env_dbgfl = 1; - } else - sysopt.env_dbgfl = -1; - } - debugfiles = sysopt.debugfiles; /* usual case: sysopt.debugfiles will be empty */ if (!debugfiles || !*debugfiles) return FALSE; -/* strip filename's path if present */ -#ifdef UNIX - if ((p = rindex(filename, '/')) != 0) - filename = p + 1; -#endif -#ifdef VMS - filename = vms_basename(filename); - /* vms_basename strips off 'type' suffix as well as path and version; - we want to put suffix back (".c" assumed); since it always returns - a pointer to a static buffer, we can safely modify its result */ - Strcat((char *) filename, ".c"); -#endif + /* strip filename's path if present */ + filename = nh_basename(filename, TRUE); /* * Wildcard match will only work if there's a single pattern (which @@ -4020,48 +3167,53 @@ boolean wildcards; #endif /*DEBUG*/ -#ifdef UNIX -#ifndef PATH_MAX -#include -#endif -#endif +#ifndef SFCTOOL + +#define SYSCONFFILE "system configuration file" void -reveal_paths(VOID_ARGS) +reveal_paths(int code) { +#if defined(SYSCF) + boolean skip_sysopt = FALSE; +#endif const char *fqn, *nodumpreason; + char buf[BUFSZ]; #if defined(SYSCF) || !defined(UNIX) || defined(DLB) const char *filep; #ifdef SYSCF - const char *gamename = (hname && *hname) ? hname : "NetHack"; + const char *gamename = (gh.hname && *gh.hname) ? gh.hname : "NetHack"; #endif #endif +#if defined(PREFIXES_IN_USE) + const char *cstrp; +#endif #ifdef UNIX char *endp, *envp, cwdbuf[PATH_MAX]; #endif #ifdef PREFIXES_IN_USE - const char *strp; int i, maxlen = 0; raw_print("Variable playground locations:"); for (i = 0; i < PREFIX_COUNT; i++) raw_printf(" [%-10s]=\"%s\"", fqn_prefix_names[i], - fqn_prefix[i] ? fqn_prefix[i] : "not set"); + gf.fqn_prefix[i] ? gf.fqn_prefix[i] : "not set"); #endif /* sysconf file */ #ifdef SYSCF #ifdef PREFIXES_IN_USE - strp = fqn_prefix_names[SYSCONFPREFIX]; + cstrp = fqn_prefix_names[SYSCONFPREFIX]; maxlen = BUFSZ - sizeof " (in )"; - if (strp && (int) strlen(strp) < maxlen) - Sprintf(buf, " (in %s)", strp); + if (cstrp && (int) strlen(cstrp) < maxlen) + Sprintf(buf, " (in %s)", cstrp); #else buf[0] = '\0'; #endif - raw_printf("%s system configuration file%s:", s_suffix(gamename), buf); + raw_printf("%s %s%s:", s_suffix(gamename), + SYSCONFFILE, buf); #ifdef SYSCF_FILE filep = SYSCF_FILE; #else @@ -4070,9 +3222,14 @@ reveal_paths(VOID_ARGS) fqn = fqname(filep, SYSCONFPREFIX, 0); if (fqn) { set_configfile_name(fqn); - filep = configfile; + filep = get_configfile(); } raw_printf(" \"%s\"", filep); + if (code == 1) { + raw_printf("NOTE: The %s above is missing or inaccessible!", + SYSCONFFILE); + skip_sysopt = TRUE; + } #else /* !SYSCF */ raw_printf("No system configuration file."); #endif /* ?SYSCF */ @@ -4083,13 +3240,13 @@ reveal_paths(VOID_ARGS) #ifndef UNIX #ifdef PREFIXES_IN_USE #ifdef WIN32 - strp = fqn_prefix_names[SYSCONFPREFIX]; + cstrp = fqn_prefix_names[SYSCONFPREFIX]; #else - strp = fqn_prefix_names[HACKPREFIX]; + cstrp = fqn_prefix_names[HACKPREFIX]; #endif /* WIN32 */ maxlen = BUFSZ - sizeof " (in )"; - if (strp && (int) strlen(strp) < maxlen) - Sprintf(buf, " (in %s)", strp); + if (cstrp && (int) strlen(cstrp) < maxlen) + Sprintf(buf, " (in %s)", cstrp); #endif /* PREFIXES_IN_USE */ raw_printf("The loadable symbols file%s:", buf); #endif /* UNIX */ @@ -4118,10 +3275,10 @@ reveal_paths(VOID_ARGS) buf[0] = '\0'; #ifdef PREFIXES_IN_USE - strp = fqn_prefix_names[DATAPREFIX]; + cstrp = fqn_prefix_names[DATAPREFIX]; maxlen = BUFSZ - sizeof " (in )"; - if (strp && (int) strlen(strp) < maxlen) - Sprintf(buf, " (in %s)", strp); + if (cstrp && (int) strlen(cstrp) < maxlen) + Sprintf(buf, " (in %s)", cstrp); #endif #ifdef DLB raw_printf("Basic data files%s are collected inside:", buf); @@ -4145,17 +3302,22 @@ reveal_paths(VOID_ARGS) /* dumplog */ + fqn = (char *) 0; #ifndef DUMPLOG nodumpreason = "not supported"; #else nodumpreason = "disabled"; #ifdef SYSCF - fqn = sysopt.dumplogfile; + if (!skip_sysopt) { + fqn = sysopt.dumplogfile; + if (!fqn) + nodumpreason = "DUMPLOGFILE is not set in " SYSCONFFILE; + } else { + nodumpreason = SYSCONFFILE " is missing; no DUMPLOGFILE setting"; + } #else /* !SYSCF */ #ifdef DUMPLOG_FILE fqn = DUMPLOG_FILE; -#else - fqn = (char *) 0; #endif #endif /* ?SYSCF */ if (fqn && *fqn) { @@ -4163,32 +3325,37 @@ reveal_paths(VOID_ARGS) (void) dump_fmtstr(fqn, buf, FALSE); buf[sizeof buf - sizeof " \"\""] = '\0'; raw_printf(" \"%s\"", buf); - } else -#endif /* ?DUMPLOG */ + } else { raw_printf("No end-of-game disclosure file (%s).", nodumpreason); + } +#endif /* ?DUMPLOG */ +#ifdef SYSCF #ifdef WIN32 - if (sysopt.portable_device_paths) { - const char *pd = get_portable_device(); - - /* an empty value for pd indicates that portable_device_paths - got set TRUE in a sysconf file other than the one containing - the executable; disregard it */ - if (strlen(pd) > 0) { - raw_printf("portable_device_paths (set in sysconf):"); - raw_printf(" \"%s\"", pd); - } + if (!skip_sysopt) { + if (sysopt.portable_device_paths) { + const char *pd = get_portable_device(); + + /* an empty value for pd indicates that portable_device_paths + got set TRUE in a sysconf file other than the one containing + the executable; disregard it */ + if (strlen(pd) > 0) { + raw_printf("portable_device_paths (set in sysconf):"); + raw_printf(" \"%s\"", pd); + } + } } +#endif #endif /* personal configuration file */ buf[0] = '\0'; #ifdef PREFIXES_IN_USE - strp = fqn_prefix_names[CONFIGPREFIX]; + cstrp = fqn_prefix_names[CONFIGPREFIX]; maxlen = BUFSZ - sizeof " (in )"; - if (strp && (int) strlen(strp) < maxlen) - Sprintf(buf, " (in %s)", strp); + if (cstrp && (int) strlen(cstrp) < maxlen) + Sprintf(buf, " (in %s)", cstrp); #endif /* PREFIXES_IN_USE */ raw_printf("Your personal configuration file%s:", buf); @@ -4199,7 +3366,7 @@ reveal_paths(VOID_ARGS) Strcat(buf, "/"); } endp = eos(buf); - copynchars(endp, default_configfile, + copynchars(endp, get_default_configfile(), (int) (sizeof buf - 1 - strlen(buf))); #if defined(__APPLE__) /* UNIX+__APPLE__ => MacOSX aka OSX aka macOS */ if (envp) { @@ -4217,7 +3384,7 @@ reveal_paths(VOID_ARGS) if (access(buf, 4) == -1) { /* second alternate failed too, so revert to the original default ("$HOME/.nethackrc") for message */ - copynchars(endp, default_configfile, + copynchars(endp, get_default_configfile(), (int) (sizeof buf - 1 - strlen(buf))); } } @@ -4228,12 +3395,21 @@ reveal_paths(VOID_ARGS) #else /* !UNIX */ fqn = (const char *) 0; #ifdef PREFIXES_IN_USE - fqn = fqname(default_configfile, CONFIGPREFIX, 2); + fqn = fqname(get_default_configfile(), CONFIGPREFIX, 2); #endif - raw_printf(" \"%s\"", fqn ? fqn : default_configfile); + raw_printf(" \"%s\"", fqn ? fqn : get_default_configfile()); #endif /* ?UNIX */ raw_print(""); +#if defined(WIN32) && !defined(WIN32CON) + wait_synch(); +#endif +#ifndef DUMPLOG +#ifdef SYSCF + nhUse(skip_sysopt); +#endif + nhUse(nodumpreason); +#endif } /* ---------- BEGIN TRIBUTE ----------- */ @@ -4245,16 +3421,16 @@ reveal_paths(VOID_ARGS) #define TITLESCOPE 2 #define PASSAGESCOPE 3 -#define MAXPASSAGES SIZE(context.novel.pasg) /* 20 */ +#define MAXPASSAGES SIZE(svc.context.novel.pasg) /* 20 */ -static int FDECL(choose_passage, (int, unsigned)); +staticfn int choose_passage(int, unsigned); /* choose a random passage that hasn't been chosen yet; once all have been chosen, reset the tracking to make all passages available again */ -static int -choose_passage(passagecnt, oid) -int passagecnt; /* total of available passages */ -unsigned oid; /* book.o_id, used to determine whether re-reading same book */ +staticfn int +choose_passage(int passagecnt, /* total of available passages */ + unsigned oid) /* book.o_id, used to determine whether + re-reading same book */ { int idx, res; @@ -4263,42 +3439,41 @@ unsigned oid; /* book.o_id, used to determine whether re-reading same book */ /* if a different book or we've used up all the passages already, reset in order to have all 'passagecnt' passages available */ - if (oid != context.novel.id || context.novel.count == 0) { + if (oid != svc.context.novel.id || svc.context.novel.count == 0) { int i, range = passagecnt, limit = MAXPASSAGES; - context.novel.id = oid; + svc.context.novel.id = oid; if (range <= limit) { /* collect all of the N indices */ - context.novel.count = passagecnt; + svc.context.novel.count = passagecnt; for (idx = 0; idx < MAXPASSAGES; idx++) - context.novel.pasg[idx] = (xchar) ((idx < passagecnt) + svc.context.novel.pasg[idx] = (xint16) ((idx < passagecnt) ? idx + 1 : 0); } else { /* collect MAXPASSAGES of the N indices */ - context.novel.count = MAXPASSAGES; + svc.context.novel.count = MAXPASSAGES; for (idx = i = 0; i < passagecnt; ++i, --range) if (range > 0 && rn2(range) < limit) { - context.novel.pasg[idx++] = (xchar) (i + 1); + svc.context.novel.pasg[idx++] = (xint16) (i + 1); --limit; } } } - idx = rn2(context.novel.count); - res = (int) context.novel.pasg[idx]; + idx = rn2(svc.context.novel.count); + res = (int) svc.context.novel.pasg[idx]; /* move the last slot's passage index into the slot just used and reduce the number of passages available */ - context.novel.pasg[idx] = context.novel.pasg[--context.novel.count]; + svc.context.novel.pasg[idx] + = svc.context.novel.pasg[--svc.context.novel.count]; return res; } /* Returns True if you were able to read something. */ boolean -read_tribute(tribsection, tribtitle, tribpassage, nowin_buf, bufsz, oid) -const char *tribsection, *tribtitle; -int tribpassage, bufsz; -char *nowin_buf; -unsigned oid; /* book identifier */ +read_tribute(const char *tribsection, const char *tribtitle, + int tribpassage, char *nowin_buf, int bufsz, + unsigned oid) /* book identifier */ { dlb *fp; char line[BUFSZ], lastline[BUFSZ]; @@ -4328,7 +3503,7 @@ unsigned oid; /* book identifier */ if (!fp) { /* this is actually an error - cannot open tribute file! */ if (!nowin_buf) - pline("You feel too overwhelmed to continue!"); + You_feel("too overwhelmed to continue!"); return grasped; } @@ -4366,10 +3541,10 @@ unsigned oid; /* book identifier */ char *st = &line[7]; /* 7 from "%title " */ char *p1, *p2; - if ((p1 = index(st, '(')) != 0) { + if ((p1 = strchr(st, '(')) != 0) { *p1++ = '\0'; (void) mungspaces(st); - if ((p2 = index(p1, ')')) != 0) { + if ((p2 = strchr(p1, ')')) != 0) { *p2 = '\0'; passagecnt = atoi(p1); scope = TITLESCOPE; @@ -4446,13 +3621,17 @@ unsigned oid; /* book identifier */ if lastline is empty, there were no non-empty lines between "%passage n" and "%e passage" so we leave 'grasped' False */ if (*lastline) { + char *p; + display_nhwindow(tribwin, FALSE); /* put the final attribution line into message history, analogous to the summary line from long quest messages */ - if (index(lastline, '[')) + if (strchr(lastline, '[')) mungspaces(lastline); /* to remove leading spaces */ else /* construct one if necessary */ Sprintf(lastline, "[%s, by Terry Pratchett]", tribtitle); + if ((p = strrchr(lastline, ']')) != 0) + Sprintf(p, "; passage #%d]", targetpassage); putmsghistory(lastline, FALSE); grasped = TRUE; } @@ -4466,9 +3645,7 @@ unsigned oid; /* book identifier */ } boolean -Death_quote(buf, bufsz) -char *buf; -int bufsz; +Death_quote(char *buf, int bufsz) { unsigned death_oid = 1; /* chance of oid #1 being a novel is negligible */ @@ -4476,5 +3653,68 @@ int bufsz; } /* ---------- END TRIBUTE ----------- */ +#endif /* !SFCTOOL */ + +#ifdef LIVELOG +#define LLOG_SEP "\t" /* livelog field separator, as a string literal */ +#define LLOG_EOL "\n" /* end-of-line, for abstraction consistency */ + +/* Locks the live log file and writes 'buffer' + * iff the ll_type matches sysopt.livelog mask. + * lltype is included in LL entry for post-process filtering also. + */ +void +livelog_add(long ll_type, const char *str) +{ + FILE *livelogfile; + time_t now; + int gindx, aindx; + + if (!(ll_type & sysopt.livelog)) + return; + + if (lock_file(LIVELOGFILE, SCOREPREFIX, 10)) { + if (!(livelogfile = fopen_datafile(LIVELOGFILE, "a", SCOREPREFIX))) { + pline("Cannot open live log file!"); + unlock_file(LIVELOGFILE); + return; + } + + now = getnow(); + gindx = flags.female ? 1 : 0; + /* note on alignment designation: + aligns[] uses [0] lawful, [1] neutral, [2] chaotic; + u.ualign.type uses -1 chaotic, 0 neutral, 1 lawful; + so subtracting from 1 converts from either to the other */ + aindx = 1 - u.ualign.type; + /* format relies on STD C's implicit concatenation of + adjacent string literals */ + (void) fprintf(livelogfile, + "lltype=%ld" LLOG_SEP "name=%s" LLOG_SEP + "role=%s" LLOG_SEP "race=%s" LLOG_SEP + "gender=%s" LLOG_SEP "align=%s" LLOG_SEP + "turns=%ld" LLOG_SEP "starttime=%ld" LLOG_SEP + "curtime=%ld" LLOG_SEP "message=%s" LLOG_EOL, + (ll_type & sysopt.livelog), svp.plname, + gu.urole.filecode, gu.urace.filecode, + genders[gindx].filecode, aligns[aindx].filecode, + svm.moves, timet_to_seconds(ubirthday), + timet_to_seconds(now), str); + (void) fclose(livelogfile); + unlock_file(LIVELOGFILE); + } +} +#undef LLOG_SEP +#undef LLOG_EOL + +#else + +void +livelog_add(long ll_type UNUSED, const char *str UNUSED) +{ + /* nothing here */ +} + +#endif /* !LIVELOG */ /*files.c*/ diff --git a/src/fountain.c b/src/fountain.c index 57e914559..c4702782a 100644 --- a/src/fountain.c +++ b/src/fountain.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 fountain.c $NHDT-Date: 1544442711 2018/12/10 11:51:51 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.60 $ */ +/* NetHack 5.0 fountain.c $NHDT-Date: 1699582923 2023/11/10 02:22:03 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.100 $ */ /* Copyright Scott R. Turner, srt@ucla, 10/27/86 */ /* NetHack may be freely redistributed. See license for details. */ @@ -6,17 +6,19 @@ #include "hack.h" -STATIC_DCL void NDECL(dowatersnakes); -STATIC_DCL void NDECL(dowaterdemon); -STATIC_DCL void NDECL(dowaternymph); -STATIC_PTR void FDECL(gush, (int, int, genericptr_t)); -STATIC_DCL void NDECL(dofindgem); +staticfn void dowatersnakes(void); +staticfn void dowaterdemon(void); +staticfn void dowaternymph(void); +staticfn void gush(coordxy, coordxy, genericptr_t) NONNULLARG3; +staticfn void dofindgem(void); +staticfn boolean watchman_warn_fountain(struct monst *) NONNULLARG1; + +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(what) -const char *what; +floating_above(const char *what) { const char *umsg = "are floating high above the %s."; @@ -29,37 +31,43 @@ const char *what; You(umsg, what); } +RESTORE_WARNING_FORMAT_NONLITERAL + /* Fountain of snakes! */ -STATIC_OVL void -dowatersnakes() +staticfn void +dowatersnakes(void) { - register int num = rn1(5, 2); + int num = rn1(5, 2); struct monst *mtmp; - if (!(mvitals[PM_WATER_MOCCASIN].mvflags & G_GONE)) { - if (!Blind) + if (!(svm.mvitals[PM_WATER_MOCCASIN].mvflags & G_GONE)) { + if (!Blind) { pline("An endless stream of %s pours forth!", Hallucination ? makeplural(rndmonnam(NULL)) : "snakes"); - else + } else { + Soundeffect(se_snakes_hissing, 75); You_hear("%s hissing!", something); + } while (num-- > 0) if ((mtmp = makemon(&mons[PM_WATER_MOCCASIN], u.ux, u.uy, - NO_MM_FLAGS)) != 0 + MM_NOMSG)) != 0 && t_at(mtmp->mx, mtmp->my)) - (void) mintrap(mtmp); - } else + (void) mintrap(mtmp, NO_TRAP_FLAGS); + } else { + Soundeffect(se_furious_bubbling, 20); pline_The("fountain bubbles furiously for a moment, then calms."); + } } /* Water demon */ -STATIC_OVL void -dowaterdemon() +staticfn void +dowaterdemon(void) { struct monst *mtmp; - if (!(mvitals[PM_WATER_DEMON].mvflags & G_GONE)) { + if (!(svm.mvitals[PM_WATER_DEMON].mvflags & G_GONE)) { if ((mtmp = makemon(&mons[PM_WATER_DEMON], u.ux, u.uy, - NO_MM_FLAGS)) != 0) { + MM_NOMSG)) != 0) { if (!Blind) You("unleash %s!", a_monnam(mtmp)); else @@ -73,38 +81,43 @@ dowaterdemon() /* give a wish and discard the monster (mtmp set to null) */ mongrantswish(&mtmp); } else if (t_at(mtmp->mx, mtmp->my)) - (void) mintrap(mtmp); + (void) mintrap(mtmp, NO_TRAP_FLAGS); } - } else + } else { + Soundeffect(se_furious_bubbling, 20); pline_The("fountain bubbles furiously for a moment, then calms."); + } } /* Water Nymph */ -STATIC_OVL void -dowaternymph() +staticfn void +dowaternymph(void) { - register struct monst *mtmp; + struct monst *mtmp; - if (!(mvitals[PM_WATER_NYMPH].mvflags & G_GONE) + if (!(svm.mvitals[PM_WATER_NYMPH].mvflags & G_GONE) && (mtmp = makemon(&mons[PM_WATER_NYMPH], u.ux, u.uy, - NO_MM_FLAGS)) != 0) { + MM_NOMSG)) != 0) { if (!Blind) You("attract %s!", a_monnam(mtmp)); else You_hear("a seductive voice."); mtmp->msleeping = 0; if (t_at(mtmp->mx, mtmp->my)) - (void) mintrap(mtmp); - } else if (!Blind) + (void) mintrap(mtmp, NO_TRAP_FLAGS); + } else if (!Blind) { + Soundeffect(se_bubble_rising, 50); + Soundeffect(se_loud_pop, 50); pline("A large bubble rises to the surface and pops."); - else + } else { + Soundeffect(se_loud_pop, 50); You_hear("a loud pop."); + } } /* Gushing forth along LOS from (u.ux, u.uy) */ void -dogushforth(drinking) -int drinking; +dogushforth(int drinking) { int madepool = 0; @@ -117,15 +130,13 @@ int drinking; } } -STATIC_PTR void -gush(x, y, poolcnt) -int x, y; -genericptr_t poolcnt; +staticfn void +gush(coordxy x, coordxy y, genericptr_t poolcnt) { - register struct monst *mtmp; - register struct trap *ttmp; + struct monst *mtmp; + struct trap *ttmp; - if (((x + y) % 2) || (x == u.ux && y == u.uy) + 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; @@ -137,10 +148,11 @@ genericptr_t poolcnt; pline("Water gushes forth from the overflowing fountain!"); /* Put a pool at x, y */ - levl[x][y].typ = POOL, levl[x][y].flags = 0; + set_levltyp(x, y, POOL); + levl[x][y].flags = 0; /* No kelp! */ del_engr_at(x, y); - water_damage_chain(level.objects[x][y], TRUE); + water_damage_chain(svl.level.objects[x][y], TRUE); if ((mtmp = m_at(x, y)) != 0) (void) minliquid(mtmp); @@ -149,8 +161,8 @@ genericptr_t poolcnt; } /* Find a gem in the sparkling waters. */ -STATIC_OVL void -dofindgem() +staticfn void +dofindgem(void) { if (!Blind) You("spot a gem in the sparkling waters!"); @@ -163,10 +175,30 @@ dofindgem() exercise(A_WIS, TRUE); /* a discovery! */ } +staticfn 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!"); + } 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))); + } + return TRUE; + } + return FALSE; +} + void -dryup(x, y, isyou) -xchar x, y; -boolean isyou; +dryup(coordxy x, coordxy y, boolean isyou) { if (IS_FOUNTAIN(levl[x][y].typ) && (!rn2(3) || FOUNTAIN_IS_WARNED(x, y))) { @@ -175,55 +207,44 @@ boolean isyou; SET_FOUNTAIN_WARNED(x, y); /* Warn about future fountain use. */ - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) - continue; - 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!"); - } 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))); - } - break; - } - } + mtmp = get_iter_mons(watchman_warn_fountain); /* You can see or hear this effect */ if (!mtmp) pline_The("flow reduces to a trickle."); return; } if (isyou && wizard) { - if (yn("Dry up fountain?") == 'n') + 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!"); + } /* replace the fountain with ordinary floor */ - levl[x][y].typ = ROOM, levl[x][y].flags = 0; + set_levltyp(x, y, ROOM); /* updates level.flags.nfountains */ + levl[x][y].flags = 0; levl[x][y].blessedftn = 0; - if (cansee(x, y)) - pline_The("fountain dries up!"); /* The location is seen if the hero/monster is invisible or felt if the hero is blind. */ newsym(x, y); - level.flags.nfountains--; if (isyou && in_town(x, y)) (void) angry_guards(FALSE); } } +/* quaff from a fountain when standing on its location */ void -drinkfountain() +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); + boolean mgkftn = (levl[u.ux][u.uy].blessedftn == 1); + int fate = rnd(30); if (Levitation) { floating_above("fountain"); @@ -238,7 +259,7 @@ drinkfountain() for (ii = 0; ii < A_MAX; ii++) if (ABASE(ii) < AMAX(ii)) { ABASE(ii) = AMAX(ii); - context.botl = 1; + disp.botl = TRUE; } /* gain ability, blessed if "natural" luck is high */ i = rn2(A_MAX); /* start at a random attribute */ @@ -283,8 +304,8 @@ drinkfountain() losehp(rnd(4), "unrefrigerated sip of juice", KILLED_BY_AN); break; } - losestr(rn1(4, 3)); - losehp(rnd(10), "contaminated water", KILLED_BY); + poison_strdmg(rn1(4, 3), rnd(10), "contaminated water", + KILLED_BY); exercise(A_CON, FALSE); break; case 22: /* Fountain of snakes! */ @@ -293,15 +314,23 @@ drinkfountain() case 23: /* Water demon */ dowaterdemon(); break; - case 24: /* Curse an item */ { - register struct obj *obj; + case 24: { /* Maybe curse some items */ + struct obj *obj, *nextobj; + int buc_changed = 0; pline("This water's no good!"); morehungry(rn1(20, 11)); exercise(A_CON, FALSE); - for (obj = invent; obj; obj = obj->nobj) - if (!rn2(5)) + /* this is more severe than rndcurse() */ + for (obj = gi.invent; obj; obj = nextobj) { + nextobj = 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 */ @@ -321,7 +350,8 @@ drinkfountain() exercise(A_WIS, TRUE); break; case 26: /* See Monsters */ - (void) monster_detect((struct obj *) 0, 0); + 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. */ @@ -329,13 +359,14 @@ drinkfountain() dofindgem(); break; } + FALLTHROUGH; /*FALLTHRU*/ case 28: /* Water Nymph */ dowaternymph(); break; case 29: /* Scare */ { - register struct monst *mtmp; + struct monst *mtmp; pline("This %s gives you bad breath!", hliquid("water")); @@ -358,23 +389,29 @@ drinkfountain() dryup(u.ux, u.uy, TRUE); } +/* dip an object into a fountain when standing on its location */ void -dipfountain(obj) -register struct obj *obj; +dipfountain(struct obj *obj) { + int er = ER_NOTHING; + boolean is_hands = (obj == &hands_obj); + if (Levitation) { floating_above("fountain"); return; } - /* Don't grant Excalibur when there's more than one object. */ - /* (quantity could be > 1 if merged daggers got polymorphed) */ - if (obj->otyp == LONG_SWORD && obj->quan == 1L && u.ulevel >= 5 && !rn2(6) - && !obj->oartifact + if (obj->otyp == LONG_SWORD && u.ulevel >= 5 + && !rn2(Role_if(PM_KNIGHT) ? 6 : 30) + /* 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.", + pline("A freezing mist rises from the %s" + " and envelopes the sword.", hliquid("water")); pline_The("fountain disappears!"); curse(obj); @@ -382,47 +419,53 @@ register struct obj *obj; 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)); + 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(); - levl[u.ux][u.uy].typ = ROOM, levl[u.ux][u.uy].flags = 0; + set_levltyp(u.ux, u.uy, ROOM); /* updates level.flags.nfountains */ + levl[u.ux][u.uy].flags = 0; newsym(u.ux, u.uy); - level.flags.nfountains--; if (in_town(u.ux, u.uy)) (void) angry_guards(FALSE); return; + } else if (is_hands || obj == uarmg) { + er = wash_hands(); } else { - int er = water_damage(obj, NULL, TRUE); + er = water_damage(obj, NULL, TRUE); + } - if (obj->otyp == POT_ACID - && er != ER_DESTROYED) { /* Acid and water don't mix */ - useup(obj); - return; - } else if (er != ER_NOTHING && !rn2(2)) { /* no further effect */ - return; - } + if (er == ER_DESTROYED || (er != ER_NOTHING && !rn2(2))) { + return; /* no further effect */ } switch (rnd(30)) { case 16: /* Curse the item */ - curse(obj); + if (!is_hands && obj->oclass != COIN_CLASS && !obj->cursed) { + curse(obj); + } break; case 17: case 18: case 19: case 20: /* Uncurse the item */ - if (obj->cursed) { + if (!is_hands && obj->cursed) { if (!Blind) pline_The("%s glows for a moment.", hliquid("water")); uncurse(obj); @@ -444,6 +487,7 @@ register struct obj *obj; dofindgem(); break; } + FALLTHROUGH; /*FALLTHRU*/ case 25: /* Water gushes forth */ dogushforth(FALSE); @@ -457,13 +501,15 @@ register struct obj *obj; case 28: /* Strange feeling */ pline("An urge to take a bath overwhelms you."); { - long money = money_cnt(invent); - struct obj *otmp; + long money = money_cnt(gi.invent); + struct obj *otmp, *nextobj; + if (money > 10) { /* Amount to lose. Might get rounded up as fountains don't * pay change... */ money = somegold(money) / 10; - for (otmp = invent; otmp && money > 0; otmp = otmp->nobj) + for (otmp = gi.invent; otmp && money > 0; otmp = nextobj) { + nextobj = otmp->nobj; if (otmp->oclass == COIN_CLASS) { int denomination = objects[otmp->otyp].oc_cost; long coin_loss = @@ -474,7 +520,8 @@ register struct obj *obj; if (!otmp->quan) delobj(otmp); } - You("lost some of your money in the fountain!"); + } + You("lost some of your gold in the fountain!"); CLEAR_FOUNTAIN_LOOTED(u.ux, u.uy); exercise(A_WIS, FALSE); } @@ -497,27 +544,55 @@ register struct obj *obj; exercise(A_WIS, TRUE); newsym(u.ux, u.uy); break; + default: + if (er == ER_NOTHING) + pline1(nothing_seems_to_happen); + break; } update_inventory(); dryup(u.ux, u.uy, TRUE); } +/* dipping '-' in fountain, pool, or sink */ +int +wash_hands(void) +{ + const char *hands = makeplural(body_part(HAND)); + int res = ER_NOTHING; + boolean was_glib = !!Glib; + + You("wash your %s%s in the %s.", uarmg ? "gloved " : "", hands, + hliquid("water")); + if (Glib) { + make_glib(0); + Your("%s are no longer slippery.", fingers_or_gloves(TRUE)); + } + if (uarmg) + res = water_damage(uarmg, (const char *) 0, TRUE); + /* not what ER_GREASED is for, but the checks in dipfountain just + compare the result to ER_DESTROYED and ER_NOTHING, so it works */ + if (was_glib && res == ER_NOTHING) + res = ER_GREASED; + return res; +} + +/* convert a sink into a fountain */ void -breaksink(x, y) -int x, y; +breaksink(coordxy x, coordxy y) { - if (cansee(x, y) || (x == u.ux && y == u.uy)) + if (cansee(x, y) || u_at(x, y)) pline_The("pipes break! Water spurts out!"); - level.flags.nsinks--; - levl[x][y].typ = FOUNTAIN, levl[x][y].looted = 0; + /* 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); - level.flags.nfountains++; newsym(x, y); } +/* quaff from a sink while standing on its location */ void -drinksink() +drinksink(void) { struct obj *otmp; struct monst *mtmp; @@ -535,17 +610,20 @@ drinksink() break; case 2: You("take a sip of scalding hot %s.", hliquid("water")); - if (Fire_resistance) + if (Fire_resistance) { pline("It seems quite tasty."); - else + monstseesu(M_SEEN_FIRE); + } else { losehp(rnd(6), "sipping boiling water", KILLED_BY); + monstunseesu(M_SEEN_FIRE); + } /* boiling water burns considered fire damage */ break; case 3: - if (mvitals[PM_SEWER_RAT].mvflags & G_GONE) + if (svm.mvitals[PM_SEWER_RAT].mvflags & G_GONE) pline_The("sink seems quite dirty."); else { - mtmp = makemon(&mons[PM_SEWER_RAT], u.ux, u.uy, NO_MM_FLAGS); + 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" @@ -553,17 +631,18 @@ drinksink() } break; case 4: - do { + for (;;) { otmp = mkobj(POTION_CLASS, FALSE); - if (otmp->otyp == POT_WATER) { - obfree(otmp, (struct obj *) 0); - otmp = (struct obj *) 0; - } - } while (!otmp); + 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); + if(!(Blind || Hallucination)) + observe_object(otmp); otmp->quan++; /* Avoid panic upon useup() */ otmp->fromsink = 1; /* kludge for docall() */ (void) dopotion(otmp); @@ -584,8 +663,8 @@ drinksink() break; case 7: pline_The("%s moves as though of its own will!", hliquid("water")); - if ((mvitals[PM_WATER_ELEMENTAL].mvflags & G_GONE) - || !makemon(&mons[PM_WATER_ELEMENTAL], u.ux, u.uy, NO_MM_FLAGS)) + if ((svm.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: @@ -602,21 +681,28 @@ drinksink() pline("This %s contains toxic wastes!", hliquid("water")); if (!Unchanging) { You("undergo a freakish metamorphosis!"); - polyself(0); + polyself(POLY_NOFLAGS); } break; /* more odd messages --JJB */ 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: + pline("Ew, what a stench!"); + create_gas_cloud(u.ux, u.uy, 1, 4); + break; case 19: if (Hallucination) { pline("From the murky drain, a hand reaches up... --oops--"); break; } + FALLTHROUGH; /*FALLTHRU*/ default: You("take a sip of %s %s.", @@ -625,4 +711,118 @@ drinksink() } } +/* for #dip(potion.c) when standing on a sink */ +void +dipsink(struct obj *obj) +{ + boolean try_call = FALSE, + not_looted_yet = (levl[u.ux][u.uy].looted & S_LRING) == 0, + is_hands = (obj == &hands_obj || (uarmg && obj == uarmg)); + + if (!rn2(not_looted_yet ? 25 : 15)) { + /* can't rely on using sink for unlimited scroll blanking; however, + since sink will be converted into a fountain, hero can dip again */ + breaksink(u.ux, u.uy); /* "The pipes break! Water spurts out!" */ + if (Glib && is_hands) + Your("%s are still slippery.", fingers_or_gloves(TRUE)); + return; + } else if (is_hands) { + (void) wash_hands(); + return; + } else if (obj->oclass != POTION_CLASS) { + You("hold %s under the tap.", the(xname(obj))); + if (water_damage(obj, (const char *) 0, TRUE) == ER_NOTHING) + pline1(nothing_seems_to_happen); + return; + } + + /* at this point the object must be a potion */ + You("pour %s%s down the drain.", (obj->quan > 1L ? "one of " : ""), + the(xname(obj))); + switch (obj->otyp) { + case POT_POLYMORPH: + polymorph_sink(); + try_call = TRUE; + break; + case POT_OIL: + if (!Blind) { + pline("It leaves an oily film on the basin."); + try_call = TRUE; + } else { + pline1(nothing_seems_to_happen); + } + break; + case POT_ACID: + /* acts like a drain cleaner product */ + try_call = TRUE; + if (!Blind) { + pline_The("drain seems less clogged."); + } else if (!Deaf) { + You_hear("a sucking sound."); + } else { + pline1(nothing_seems_to_happen); + try_call = FALSE; + } + break; + case POT_LEVITATION: + sink_backs_up(u.ux, u.uy); + try_call = TRUE; + break; + case POT_OBJECT_DETECTION: + if (!(levl[u.ux][u.uy].looted & S_LRING)) { + You("sense a ring lost down the drain."); + try_call = TRUE; + break; + } + FALLTHROUGH; + /* FALLTHRU */ + case POT_GAIN_LEVEL: + case POT_GAIN_ENERGY: + case POT_MONSTER_DETECTION: + case POT_FRUIT_JUICE: + case POT_WATER: + /* potions with no potionbreathe() effects, plus water. if effects + are added to potionbreathe these should go to that instead (except + for water). */ + pline1(nothing_seems_to_happen); + break; + default: + /* hero can feel the vapor on her skin, so no need to check Blind or + breathless for this message */ + pline("A wisp of vapor rises up..."); + /* NB: potionbreathe calls trycall or makeknown as appropriate */ + if (!breathless(gy.youmonst.data) || haseyes(gy.youmonst.data)) + potionbreathe(obj); + break; + } + if (try_call && obj->dknown) + trycall(obj); + useup(obj); +} + +/* find a ring in a sink */ +void +sink_backs_up(coordxy x, coordxy y) +{ + char buf[BUFSZ]; + + if (!Blind) + Strcpy(buf, "Muddy waste pops up from the drain"); + else if (!Deaf) + Strcpy(buf, "You hear a sloshing sound"); /* Deaf-aware */ + else + Sprintf(buf, "Something splashes you in the %s", body_part(FACE)); + pline("%s%s.", !Deaf ? "Flupp! " : "", buf); + + if (!(levl[x][y].looted & S_LRING)) { /* once per sink */ + 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! */ + levl[x][y].looted |= S_LRING; + } +} + /*fountain.c*/ diff --git a/src/getpos.c b/src/getpos.c new file mode 100644 index 000000000..68be1f8a1 --- /dev/null +++ b/src/getpos.c @@ -0,0 +1,1169 @@ +/* NetHack 5.0 getpos.c $NHDT-Date: 1763708572 2025/11/20 23:02:52 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.6 $ */ +/*-Copyright (c) Pasi Kallinen, 2023. */ +/* NetHack may be freely redistributed. See license for details. */ + +#include "hack.h" + +extern const char what_is_a_location[]; /* from pager.c */ + +staticfn void getpos_toggle_hilite_state(void); +staticfn void getpos_getvalids_selection(struct selectionvar *, + boolean (*)(coordxy, coordxy)); +staticfn void getpos_help_keyxhelp(winid, const char *, const char *, int); +staticfn void getpos_help(boolean, const char *); +staticfn int QSORTCALLBACK cmp_coord_distu(const void *, const void *); +staticfn int gloc_filter_classify_glyph(int); +staticfn int gloc_filter_floodfill_matcharea(coordxy, coordxy); +staticfn void gloc_filter_floodfill(coordxy, coordxy); +staticfn void gloc_filter_init(void); +staticfn void gloc_filter_done(void); +staticfn void gather_locs(coord **, int *, int); +staticfn boolean known_vibrating_square_at(coordxy, coordxy); +staticfn void truncate_to_map(coordxy *, coordxy *, schar, schar); +staticfn void getpos_refresh(void); +/* Callback function for getpos() to highlight desired map locations. + * Parameter TRUE: initialize and highlight, FALSE: done (remove highlights). + */ +staticfn void (*getpos_hilitefunc)(boolean) = (void (*)(boolean)) 0; +staticfn boolean (*getpos_getvalid)(coordxy, coordxy) + = (boolean (*)(coordxy, coordxy)) 0; +enum getposHiliteState { + HiliteNormalMap = 0, + HiliteGoodposSymbol = 1, + HiliteBackground = 2, +}; + +static enum getposHiliteState + getpos_hilite_state = HiliteNormalMap, + defaultHiliteState = HiliteNormalMap; + +void +getpos_sethilite( + void (*gp_hilitef)(boolean), + boolean (*gp_getvalidf)(coordxy, coordxy)) +{ + boolean (*old_getvalid)(coordxy, coordxy) = getpos_getvalid; + uint32 old_map_frame_color = gw.wsettings.map_frame_color; + struct selectionvar *sel = selection_new(); + + defaultHiliteState = iflags.bgcolors ? HiliteBackground : HiliteNormalMap; + if (gp_getvalidf != old_getvalid) + getpos_hilite_state = defaultHiliteState; + + getpos_getvalids_selection(sel, getpos_getvalid); + getpos_hilitefunc = gp_hilitef; + getpos_getvalid = gp_getvalidf; + getpos_getvalids_selection(sel, getpos_getvalid); + gw.wsettings.map_frame_color = (getpos_hilite_state == HiliteBackground) + ? HI_ZAP : NO_COLOR; + + if (getpos_getvalid != old_getvalid + || gw.wsettings.map_frame_color != old_map_frame_color) + selection_force_newsyms(sel); + selection_free(sel, TRUE); +} + +/* cycle 'getpos_hilite_state' to its next state; + when 'bgcolors' is Off, it will alternate between not showing valid + positions and showing them via temporary S_goodpos symbol; + when 'bgcolors' is On, there are three states and showing them via + setting background color becomes the default */ +staticfn void +getpos_toggle_hilite_state(void) +{ + /* getpos_hilitefunc isn't Null */ + if (getpos_hilite_state == HiliteGoodposSymbol) { + /* currently on, finish */ + (*getpos_hilitefunc)(FALSE); /* tmp_at(DISP_END) */ + } + + getpos_hilite_state = (getpos_hilite_state + 1) + % (iflags.bgcolors ? 3 : 2); + /* resetting the callback functions to their current values will draw + valid-spots with background color if that is the new state and turn + off that color if it was the previous state */ + getpos_sethilite(getpos_hilitefunc, getpos_getvalid); + + if (getpos_hilite_state == HiliteGoodposSymbol) { + /* now on, begin */ + (*getpos_hilitefunc)(TRUE); + } +} + +boolean +mapxy_valid(coordxy x, coordxy y) +{ + if (getpos_getvalid) + return (*getpos_getvalid)(x, y); + return FALSE; +} + +staticfn void +getpos_getvalids_selection( + struct selectionvar *sel, + boolean (*validf)(coordxy, coordxy)) +{ + coordxy x, y; + + if (!sel || !validf) + return; + + for (x = 1; x < sel->wid; x++) + for (y = 0; y < sel->hei; y++) + if ((*validf)(x, y)) + selection_setpoint(x, y, sel, 1); +} + +static const char *const gloc_descr[NUM_GLOCS][4] = { + { "any monsters", "monster", "next/previous monster", "monsters" }, + { "any items", "item", "next/previous object", "objects" }, + { "any doors", "door", "next/previous door or doorway", + "doors or doorways" }, + { "any unexplored areas", "unexplored area", "unexplored location", + "locations next to unexplored locations" }, + { "anything interesting", "interesting thing", "anything interesting", + "anything interesting" }, + { "any valid locations", "valid location", "valid location", + "valid locations" } +}; + +static const char *const gloc_filtertxt[NUM_GFILTER] = { + "", + " in view", + " in this area" +}; + +staticfn void +getpos_help_keyxhelp( + winid tmpwin, + const char *k1, const char *k2, + int gloc) +{ + char sbuf[BUFSZ], fbuf[QBUFSZ]; + const char *move_cursor_to = "move the cursor to ", + *filtertxt = gloc_filtertxt[iflags.getloc_filter]; + + if (gloc == GLOC_EXPLORE) { + /* default of "move to unexplored location" is inaccurate + because the position will be one spot short of that */ + move_cursor_to = "move the cursor next to an "; + if (iflags.getloc_usemenu) + /* default is too wide for basic 80-column tty so shorten it + to avoid wrapping */ + filtertxt = strsubst(strcpy(fbuf, filtertxt), + "this area", "area"); + } + Sprintf(sbuf, "Use '%s'/'%s' to %s%s%s.", + k1, k2, + iflags.getloc_usemenu ? "get a menu of " : move_cursor_to, + gloc_descr[gloc][2 + iflags.getloc_usemenu], filtertxt); + putstr(tmpwin, 0, sbuf); +} + +DISABLE_WARNING_FORMAT_NONLITERAL + +/* the response for '?' help request in getpos() */ +staticfn void +getpos_help(boolean force, const char *goal) +{ + static const char *const fastmovemode[2] = { "8 units at a time", + "skipping same glyphs" }; + char sbuf[BUFSZ]; + boolean doing_what_is; + winid tmpwin = create_nhwindow(NHW_MENU); + + Sprintf(sbuf, + "Use '%s', '%s', '%s', '%s' to move the cursor to %s.", /* hjkl */ + visctrl(cmd_from_func(do_move_west)), + visctrl(cmd_from_func(do_move_south)), + visctrl(cmd_from_func(do_move_north)), + visctrl(cmd_from_func(do_move_east)), goal); + putstr(tmpwin, 0, sbuf); + Sprintf(sbuf, + "Use '%s', '%s', '%s', '%s' to fast-move the cursor, %s.", + visctrl(cmd_from_func(do_run_west)), + visctrl(cmd_from_func(do_run_south)), + visctrl(cmd_from_func(do_run_north)), + visctrl(cmd_from_func(do_run_east)), + fastmovemode[iflags.getloc_moveskip]); + putstr(tmpwin, 0, sbuf); + Sprintf(sbuf, "(or prefix normal move with '%s' or '%s' to fast-move)", + visctrl(cmd_from_func(do_run)), + visctrl(cmd_from_func(do_rush))); + putstr(tmpwin, 0, sbuf); + putstr(tmpwin, 0, "Or enter a background symbol (ex. '<')."); + Sprintf(sbuf, "Use '%s' to move the cursor on yourself.", + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_SELF])); + putstr(tmpwin, 0, sbuf); + if (!iflags.terrainmode || (iflags.terrainmode & TER_MON) != 0) { + getpos_help_keyxhelp(tmpwin, + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_MON_NEXT]), + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_MON_PREV]), + GLOC_MONS); + } + if (goal && !strcmp(goal, "a monster")) + goto skip_non_mons; + if (!iflags.terrainmode || (iflags.terrainmode & TER_OBJ) != 0) { + getpos_help_keyxhelp(tmpwin, + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_OBJ_NEXT]), + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_OBJ_PREV]), + GLOC_OBJS); + } + if (!iflags.terrainmode || (iflags.terrainmode & TER_MAP) != 0) { + /* these are primarily useful when choosing a travel + destination for the '_' command */ + getpos_help_keyxhelp(tmpwin, + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_DOOR_NEXT]), + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_DOOR_PREV]), + GLOC_DOOR); + getpos_help_keyxhelp(tmpwin, + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_UNEX_NEXT]), + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_UNEX_PREV]), + GLOC_EXPLORE); + getpos_help_keyxhelp(tmpwin, + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_INTERESTING_NEXT]), + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_INTERESTING_PREV]), + GLOC_INTERESTING); + } + Sprintf(sbuf, "Use '%s' to change fast-move mode to %s.", + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_MOVESKIP]), + fastmovemode[!iflags.getloc_moveskip]); + putstr(tmpwin, 0, sbuf); + if (!iflags.terrainmode || (iflags.terrainmode & TER_DETECT) == 0) { + Sprintf(sbuf, "Use '%s' to toggle menu listing for possible targets.", + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_MENU])); + putstr(tmpwin, 0, sbuf); + Sprintf(sbuf, + "Use '%s' to change the mode of limiting possible targets.", + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_LIMITVIEW])); + putstr(tmpwin, 0, sbuf); + } + if (!iflags.terrainmode) { + char kbuf[BUFSZ]; + + if (getpos_getvalid) { + Sprintf(sbuf, "Use '%s' or '%s' to move to valid locations.", + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_VALID_NEXT]), + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_VALID_PREV])); + putstr(tmpwin, 0, sbuf); + } + if (getpos_hilitefunc) { + Sprintf(sbuf, "Use '%s' to toggle marking of valid locations.", + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_SHOWVALID])); + putstr(tmpwin, 0, sbuf); + } + Sprintf(sbuf, "Use '%s' to toggle automatic description.", + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_AUTODESC])); + putstr(tmpwin, 0, sbuf); + if (iflags.cmdassist) { /* assisting the '/' command, I suppose... */ + Sprintf(sbuf, + (iflags.getpos_coords == GPCOORDS_NONE) + ? "(Set 'whatis_coord' option to include coordinates with '%s' text.)" + : "(Reset 'whatis_coord' option to omit coordinates from '%s' text.)", + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_AUTODESC])); + } + skip_non_mons: + /* disgusting hack; the alternate selection characters work for any + getpos call, but only matter for dowhatis (and doquickwhatis, + also for dotherecmdmenu's simulated mouse) */ + doing_what_is = (goal == what_is_a_location); + if (doing_what_is) { + Sprintf(kbuf, "'%s' or '%s' or '%s' or '%s'", + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_PICK]), + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_PICK_Q]), + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_PICK_O]), + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_PICK_V])); + } else { + Sprintf(kbuf, "'%s'", visctrl(gc.Cmd.spkeys[NHKF_GETPOS_PICK])); + } + Snprintf(sbuf, sizeof(sbuf), + "Type a %s when you are at the right place.", kbuf); + putstr(tmpwin, 0, sbuf); + if (doing_what_is) { + Sprintf(sbuf, + " '%s' describe current spot, show 'more info', move to another spot.", + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_PICK_V])); + putstr(tmpwin, 0, sbuf); + Sprintf(sbuf, + " '%s' describe current spot,%s move to another spot;", + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_PICK]), + flags.help && !force ? " prompt if 'more info'," : ""); + putstr(tmpwin, 0, sbuf); + Sprintf(sbuf, + " '%s' describe current spot, move to another spot;", + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_PICK_Q])); + putstr(tmpwin, 0, sbuf); + Sprintf(sbuf, + " '%s' describe current spot, stop looking at things;", + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_PICK_O])); + putstr(tmpwin, 0, sbuf); + } + } + if (!force) + putstr(tmpwin, 0, "Type Space or Escape when you're done."); + putstr(tmpwin, 0, ""); + display_nhwindow(tmpwin, TRUE); + destroy_nhwindow(tmpwin); +} + +RESTORE_WARNING_FORMAT_NONLITERAL + +staticfn int QSORTCALLBACK +cmp_coord_distu(const void *a, const void *b) +{ + const coord *c1 = a; + const coord *c2 = b; + int dx, dy, dist_1, dist_2; + + dx = u.ux - c1->x; + dy = u.uy - c1->y; + dist_1 = max(abs(dx), abs(dy)); + dx = u.ux - c2->x; + dy = u.uy - c2->y; + dist_2 = max(abs(dx), abs(dy)); + + if (dist_1 == dist_2) + return (c1->y != c2->y) ? (c1->y - c2->y) : (c1->x - c2->x); + + return dist_1 - dist_2; +} + +#define IS_UNEXPLORED_LOC(x,y) \ + (isok((x), (y)) \ + && glyph_is_unexplored(levl[(x)][(y)].glyph) \ + && !levl[(x)][(y)].seenv) + +#define GLOC_SAME_AREA(x,y) \ + (isok((x), (y)) \ + && (selection_getpoint((x),(y), gg.gloc_filter_map))) + +staticfn int +gloc_filter_classify_glyph(int glyph) +{ + int c; + + if (!glyph_is_cmap(glyph)) + return 0; + + c = glyph_to_cmap(glyph); + + if (is_cmap_room(c) || is_cmap_furniture(c)) + return 1; + else if (is_cmap_wall(c) || c == S_tree) + return 2; + else if (is_cmap_corr(c)) + return 3; + else if (is_cmap_water(c)) + return 4; + else if (is_cmap_lava(c)) + return 5; + return 0; +} + +staticfn int +gloc_filter_floodfill_matcharea(coordxy x, coordxy y) +{ + int glyph = back_to_glyph(x, y); + + if (!levl[x][y].seenv) + return FALSE; + + if (glyph == gg.gloc_filter_floodfill_match_glyph) + return TRUE; + + if (gloc_filter_classify_glyph(glyph) + == gloc_filter_classify_glyph(gg.gloc_filter_floodfill_match_glyph)) + return TRUE; + + return FALSE; +} + +staticfn void +gloc_filter_floodfill(coordxy x, coordxy y) +{ + gg.gloc_filter_floodfill_match_glyph = back_to_glyph(x, y); + + set_selection_floodfillchk(gloc_filter_floodfill_matcharea); + selection_floodfill(gg.gloc_filter_map, x, y, FALSE); +} + +staticfn void +gloc_filter_init(void) +{ + if (iflags.getloc_filter == GFILTER_AREA) { + if (!gg.gloc_filter_map) { + gg.gloc_filter_map = selection_new(); + } + /* special case: if we're in a doorway, try to figure out which + direction we're moving, and use that side of the doorway */ + if (IS_DOOR(levl[u.ux][u.uy].typ)) { + if ((u.dx || u.dy) && isok(u.ux + u.dx, u.uy + u.dy)) { + gloc_filter_floodfill(u.ux + u.dx, u.uy + u.dy); + } else { + /* TODO: maybe add both sides of the doorway? */ + } + } else { + gloc_filter_floodfill(u.ux, u.uy); + } + } +} + +staticfn void +gloc_filter_done(void) +{ + if (gg.gloc_filter_map) { + selection_free(gg.gloc_filter_map, TRUE); + gg.gloc_filter_map = (struct selectionvar *) 0; + + } +} + +staticfn boolean +known_vibrating_square_at(coordxy x, coordxy y) +{ + /* note: this only acknowledges the genuine vibrating square, not + fake ones produced by wizard mode wishing for traps which could + possibly be transfered to normal play via bones file */ + if (invocation_pos(x, y)) { + struct trap *ttmp = t_at(x, y); + + return ttmp && ttmp->ttyp == VIBRATING_SQUARE && ttmp->tseen; + } + return FALSE; +} + +DISABLE_WARNING_UNREACHABLE_CODE + +boolean +gather_locs_interesting( + coordxy x, coordxy y, + int gloc) +{ + int glyph, sym; + + if (iflags.getloc_filter == GFILTER_VIEW && !cansee(x, y)) + return FALSE; + if (iflags.getloc_filter == GFILTER_AREA && !GLOC_SAME_AREA(x, y) + && !GLOC_SAME_AREA(x - 1, y) && !GLOC_SAME_AREA(x, y - 1) + && !GLOC_SAME_AREA(x + 1, y) && !GLOC_SAME_AREA(x, y + 1)) + return FALSE; + + glyph = glyph_at(x, y); + sym = glyph_is_cmap(glyph) ? glyph_to_cmap(glyph) : -1; + switch (gloc) { + default: + case GLOC_MONS: + /* unlike '/M', this skips monsters revealed by + warning glyphs and remembered unseen ones */ + return (glyph_is_monster(glyph) + && glyph != monnum_to_glyph(PM_LONG_WORM_TAIL, MALE) + && glyph != monnum_to_glyph(PM_LONG_WORM_TAIL, FEMALE)); + case GLOC_OBJS: + return (glyph_is_object(glyph) + && glyph != objnum_to_glyph(BOULDER) + && glyph != objnum_to_glyph(ROCK)); + case GLOC_DOOR: + return (glyph_is_cmap(glyph) + && (is_cmap_door(sym) + || is_cmap_drawbridge(sym) + || sym == S_ndoor)); + case GLOC_EXPLORE: + return (glyph_is_cmap(glyph) + && !glyph_is_nothing(glyph_to_cmap(glyph)) + && (is_cmap_door(sym) + || is_cmap_drawbridge(sym) + || sym == S_ndoor + || is_cmap_room(sym) + || is_cmap_corr(sym)) + && (IS_UNEXPLORED_LOC(x + 1, y) + || IS_UNEXPLORED_LOC(x - 1, y) + || IS_UNEXPLORED_LOC(x, y + 1) + || IS_UNEXPLORED_LOC(x, y - 1))); + case GLOC_VALID: + if (getpos_getvalid) + return (*getpos_getvalid)(x, y); + FALLTHROUGH; + /*FALLTHRU*/ + case GLOC_INTERESTING: + return (gather_locs_interesting(x, y, GLOC_DOOR) + || !((glyph_is_cmap(glyph) + && (is_cmap_wall(sym) + || sym == S_tree + || sym == S_bars + || sym == S_ice + || sym == S_air + || sym == S_cloud + || is_cmap_lava(sym) + || is_cmap_water(sym) + || sym == S_ndoor + || is_cmap_room(sym) + || is_cmap_corr(sym))) + || glyph_is_nothing(glyph) + || glyph_is_unexplored(glyph)) + || known_vibrating_square_at(x, y)); + } + /*NOTREACHED*/ + return FALSE; +} + +RESTORE_WARNINGS + +/* gather locations for monsters or objects shown on the map */ +staticfn void +gather_locs(coord **arr_p, int *cnt_p, int gloc) +{ + int pass, idx; + coordxy x, y; + + /* + * We always include the hero's location even if there is no monster + * (invisible hero without see invisible) or object (usual case) + * displayed there. That way, the count will always be at least 1, + * and player has a visual indicator (cursor returns to hero's spot) + * highlighting when successive 'm's or 'o's have cycled all the way + * through all monsters or objects. + * + * Hero's spot will always sort to array[0] because it will always + * be the shortest distance (namely, 0 units) away from . + */ + + gloc_filter_init(); + + *cnt_p = idx = 0; + for (pass = 0; pass < 2; pass++) { + for (x = 1; x < COLNO; x++) + for (y = 0; y < ROWNO; y++) { + if (u_at(x, y) || gather_locs_interesting(x, y, gloc)) { + if (!pass) { + ++*cnt_p; + } else { + (*arr_p)[idx].x = x; + (*arr_p)[idx].y = y; + ++idx; + } + } + } + + if (!pass) /* end of first pass */ + *arr_p = (coord *) alloc(*cnt_p * sizeof (coord)); + else /* end of second pass */ + qsort(*arr_p, *cnt_p, sizeof (coord), cmp_coord_distu); + } /* pass */ + + gloc_filter_done(); +} + +char * +dxdy_to_dist_descr(coordxy dx, coordxy dy, boolean fulldir) +{ + static char buf[30]; + int dst; + + if (!dx && !dy) { + Sprintf(buf, "here"); + } else if ((dst = xytodir(dx, dy)) != -1) { + /* explicit direction; 'one step' is implicit */ + Sprintf(buf, "%s", directionname(dst)); + } else { + static const char *const dirnames[4][2] = { + { "n", "north" }, + { "s", "south" }, + { "w", "west" }, + { "e", "east" } }; + buf[0] = '\0'; + /* 9999: protect buf[] against overflow caused by invalid values */ + if (dy) { + if (abs(dy) > 9999) + dy = sgn(dy) * 9999; + Sprintf(eos(buf), "%d%s%s", abs(dy), dirnames[(dy > 0)][fulldir], + dx ? "," : ""); + } + if (dx) { + if (abs(dx) > 9999) + dx = sgn(dx) * 9999; + Sprintf(eos(buf), "%d%s", abs(dx), + dirnames[2 + (dx > 0)][fulldir]); + } + } + return buf; +} + +DISABLE_WARNING_FORMAT_NONLITERAL + +/* coordinate formatting for 'whatis_coord' option */ +char * +coord_desc(coordxy x, coordxy y, char *outbuf, char cmode) +{ + static char screen_fmt[16]; /* [12] suffices: "[%02d,%02d]" */ + int dx, dy; + + outbuf[0] = '\0'; + switch (cmode) { + default: + break; + case GPCOORDS_COMFULL: + case GPCOORDS_COMPASS: + /* "east", "3s", "2n,4w" */ + dx = x - u.ux; + dy = y - u.uy; + Sprintf(outbuf, "(%s)", + dxdy_to_dist_descr(dx, dy, cmode == GPCOORDS_COMFULL)); + break; + case GPCOORDS_MAP: /* x,y */ + /* upper left corner of map is <1,0>; + with default COLNO,ROWNO lower right corner is <79,20> */ + Sprintf(outbuf, "<%d,%d>", x, y); + break; + case GPCOORDS_SCREEN: /* y+2,x */ + /* for normal map sizes, force a fixed-width formatting so that + /m, /M, /o, and /O output lines up cleanly; map sizes bigger + than Nx999 or 999xM will still work, but not line up like normal + when displayed in a column setting. + + The (100) is placed in brackets below to mark the [: "03"] as + explicit compile-time dead code for clang */ + if (!*screen_fmt) + Sprintf(screen_fmt, "[%%%sd,%%%sd]", + (ROWNO - 1 + 2 < (100)) ? "02" : "03", + (COLNO - 1 < (100)) ? "02" : "03"); + /* map line 0 is screen row 2; + map column 0 isn't used, map column 1 is screen column 1 */ + Sprintf(outbuf, screen_fmt, y + 2, x); + break; + } + return outbuf; +} + +RESTORE_WARNING_FORMAT_NONLITERAL + +void +auto_describe(coordxy cx, coordxy cy) +{ + coord cc; + int sym = 0; + char tmpbuf[BUFSZ]; + const char *firstmatch = "unknown"; + + cc.x = cx; + cc.y = cy; + if (do_screen_description(cc, TRUE, sym, tmpbuf, &firstmatch, + (struct permonst **) 0)) { + (void) coord_desc(cx, cy, tmpbuf, iflags.getpos_coords); + custompline((SUPPRESS_HISTORY | OVERRIDE_MSGTYPE | NO_CURS_ON_U), + "%s%s%s%s%s", firstmatch, *tmpbuf ? " " : "", tmpbuf, + (iflags.autodescribe + && getpos_getvalid && !(*getpos_getvalid)(cx, cy)) + ? " (invalid target)" : "", + (iflags.getloc_travelmode && !is_valid_travelpt(cx, cy)) + ? " (no travel path)" : ""); + curs(WIN_MAP, cx, cy); + flush_screen(0); + } +} + +boolean +getpos_menu(coord *ccp, int gloc) +{ + coord *garr = DUMMY; + int gcount = 0; + winid tmpwin; + anything any; + int i, pick_cnt; + menu_item *picks = (menu_item *) 0; + char tmpbuf[BUFSZ]; + int clr = NO_COLOR; + + gather_locs(&garr, &gcount, gloc); + + if (gcount < 2) { /* gcount always includes the hero */ + free((genericptr_t) garr); + You("cannot %s %s.", + (iflags.getloc_filter == GFILTER_VIEW) ? "see" : "detect", + gloc_descr[gloc][0]); + return FALSE; + } + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + + /* gather_locs returns array[0] == you. skip it. */ + for (i = 1; i < gcount; i++) { + char fullbuf[BUFSZ]; + coord tmpcc; + const char *firstmatch = "unknown"; + int sym = 0; + + any.a_int = i + 1; + tmpcc.x = garr[i].x; + tmpcc.y = garr[i].y; + if (do_screen_description(tmpcc, TRUE, sym, tmpbuf, + &firstmatch, (struct permonst **)0)) { + (void) coord_desc(garr[i].x, garr[i].y, tmpbuf, + iflags.getpos_coords); + Snprintf(fullbuf, sizeof fullbuf, "%s%s%s", firstmatch, + (*tmpbuf ? " " : ""), tmpbuf); + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, + ATR_NONE, clr, fullbuf, MENU_ITEMFLAGS_NONE); + } + } + + Sprintf(tmpbuf, "Pick %s%s%s", + an(gloc_descr[gloc][1]), + gloc_filtertxt[iflags.getloc_filter], + iflags.getloc_travelmode ? " for travel destination" : ""); + end_menu(tmpwin, tmpbuf); + pick_cnt = select_menu(tmpwin, PICK_ONE, &picks); + destroy_nhwindow(tmpwin); + if (pick_cnt > 0) { + ccp->x = garr[picks->item.a_int - 1].x; + ccp->y = garr[picks->item.a_int - 1].y; + free((genericptr_t) picks); + } + free((genericptr_t) garr); + return (pick_cnt > 0); +} + +/* add dx,dy to cx,cy, truncating at map edges */ +staticfn void +truncate_to_map(coordxy *cx, coordxy *cy, schar dx, schar dy) +{ + /* diagonal moves complicate this... */ + if (*cx + dx < 1) { + dy -= sgn(dy) * (1 - (*cx + dx)); + dx = 1 - *cx; /* so that (cx+dx == 1) */ + } else if (*cx + dx > COLNO - 1) { + dy += sgn(dy) * ((COLNO - 1) - (*cx + dx)); + dx = (COLNO - 1) - *cx; + } + if (*cy + dy < 0) { + dx -= sgn(dx) * (0 - (*cy + dy)); + dy = 0 - *cy; /* so that (cy+dy == 0) */ + } else if (*cy + dy > ROWNO - 1) { + dx += sgn(dx) * ((ROWNO - 1) - (*cy + dy)); + dy = (ROWNO - 1) - *cy; + } + *cx += dx; + *cy += dy; +} + +/* called when ^R typed; if '$' is being shown for valid spots, remove that; + if alternate background color is being shown for that, redraw it */ +staticfn void +getpos_refresh(void) +{ + if (getpos_hilitefunc && getpos_hilite_state == HiliteGoodposSymbol) { + (*getpos_hilitefunc)(FALSE); /* tmp_at(DISP_END) */ + getpos_hilite_state = defaultHiliteState; + } + + docrt_flags(docrtRefresh); + + if (getpos_hilitefunc && getpos_hilite_state == HiliteBackground) { + /* resetting to current values will draw valid-spots highlighting */ + getpos_sethilite(getpos_hilitefunc, getpos_getvalid); + } +} + +/* have the player use movement keystrokes to position the cursor at a + particular map location, then use one of [.,:;] to pick the spot */ +int +getpos(coord *ccp, boolean force, const char *goal) +{ + static struct { + int nhkf, ret; + } const pick_chars_def[] = { + { NHKF_GETPOS_PICK, LOOK_TRADITIONAL }, + { NHKF_GETPOS_PICK_Q, LOOK_QUICK }, + { NHKF_GETPOS_PICK_O, LOOK_ONCE }, + { NHKF_GETPOS_PICK_V, LOOK_VERBOSE } + }; + static const int mMoOdDxX_def[] = { + NHKF_GETPOS_MON_NEXT, + NHKF_GETPOS_MON_PREV, + NHKF_GETPOS_OBJ_NEXT, + NHKF_GETPOS_OBJ_PREV, + NHKF_GETPOS_DOOR_NEXT, + NHKF_GETPOS_DOOR_PREV, + NHKF_GETPOS_UNEX_NEXT, + NHKF_GETPOS_UNEX_PREV, + NHKF_GETPOS_INTERESTING_NEXT, + NHKF_GETPOS_INTERESTING_PREV, + NHKF_GETPOS_VALID_NEXT, + NHKF_GETPOS_VALID_PREV + }; + struct _cmd_queue cq, *cmdq; + const char *cp; + char pick_chars[6]; + char mMoOdDxX[13]; + int result = 0; + int i, c; + int sidx; + coordxy cx, cy; + coordxy tx = u.ux, ty = u.uy; + boolean msg_given = TRUE; /* clear message window by default */ + boolean show_goal_msg = FALSE; + coord *garr[NUM_GLOCS] = DUMMY; + int gcount[NUM_GLOCS] = DUMMY; + int gidx[NUM_GLOCS] = DUMMY; + schar udx = u.dx, udy = u.dy, udz = u.dz; + int dx, dy; + boolean rushrun = FALSE; + + /* temporary? if we have a queued direction, return the adjacent spot + in that direction */ + if (!gi.in_doagain) { + if ((cmdq = cmdq_pop()) != 0) { + cq = *cmdq; + free((genericptr_t) cmdq); + if (cq.typ == CMDQ_DIR && !cq.dirz) { + ccp->x = u.ux + cq.dirx; + ccp->y = u.uy + cq.diry; + } else { + cmdq_clear(CQ_CANNED); + result = -1; + } + return result; + } + } + + for (i = 0; i < SIZE(pick_chars_def); i++) + pick_chars[i] = gc.Cmd.spkeys[pick_chars_def[i].nhkf]; + pick_chars[SIZE(pick_chars_def)] = '\0'; + + for (i = 0; i < SIZE(mMoOdDxX_def); i++) + mMoOdDxX[i] = gc.Cmd.spkeys[mMoOdDxX_def[i]]; + mMoOdDxX[SIZE(mMoOdDxX_def)] = '\0'; + + if (handle_tip(TIP_GETPOS)) + show_goal_msg = TRUE; /* tip has overwritten prompt in mesg window */ + + if (!goal) + goal = "desired location"; + if (flags.verbose) { + pline("(For instructions type a '%s')", + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_HELP])); + msg_given = TRUE; + } + cx = gg.getposx = ccp->x; + cy = gg.getposy = ccp->y; +#ifdef CLIPPING + cliparound(cx, cy); +#endif + curs(WIN_MAP, cx, cy); + flush_screen(0); +#ifdef MACOS9 + lock_mouse_cursor(TRUE); +#endif + lock_mouse_buttons(TRUE); + for (;;) { + if (show_goal_msg) { + pline("Move cursor to %s:", goal); + curs(WIN_MAP, cx, cy); + flush_screen(0); + show_goal_msg = FALSE; + } else if (iflags.autodescribe && !msg_given) { + auto_describe(cx, cy); + } + + rushrun = FALSE; + + if ((cmdq = cmdq_pop()) != 0) { + if (cmdq->typ == CMDQ_KEY) { + c = cmdq->key; + } else { + cmdq_clear(CQ_CANNED); + result = -1; + goto exitgetpos; + } + free(cmdq); + } else { + c = readchar_poskey(&tx, &ty, &sidx); + /* remember_getpos is normally False because reusing the + cursor positioning during ^A is almost never the right + thing to do, but caller could set it if that was needed */ + if (iflags.remember_getpos && !gi.in_doagain) + cmdq_add_key(CQ_REPEAT, c); + } + + if (iflags.autodescribe) + msg_given = FALSE; + + if (c == gc.Cmd.spkeys[NHKF_ESC]) { + cx = cy = -10; + msg_given = TRUE; /* force clear */ + result = -1; + break; + } + if (c == cmd_from_func(do_run) || c == cmd_from_func(do_rush)) { + c = readchar_poskey(&tx, &ty, &sidx); + rushrun = TRUE; + } + if (c == 0) { + if (!isok(tx, ty)) + continue; + /* a mouse click event, just assign and return */ + cx = tx; + cy = ty; + break; + } + if ((cp = strchr(pick_chars, c)) != 0) { + /* '.' => 0, ',' => 1, ';' => 2, ':' => 3 */ + result = pick_chars_def[(int) (cp - pick_chars)].ret; + break; + } else if (movecmd(c, MV_WALK)) { + if (rushrun) + goto do_rushrun; + dx = u.dx; + dy = u.dy; + truncate_to_map(&cx, &cy, dx, dy); + goto nxtc; + } else if (movecmd(c, MV_RUSH) || movecmd(c, MV_RUN)) { + do_rushrun: + if (iflags.getloc_moveskip) { + /* skip same glyphs */ + int glyph = glyph_at(cx, cy); + + dx = u.dx; + dy = u.dy; + while (isok(cx + dx, cy + dy) + && glyph == glyph_at(cx + dx, cy + dy) + && isok(cx + dx + u.dx, cy + dy + u.dy) + && glyph == glyph_at(cx + dx + u.dx, cy + dy + u.dy)) { + dx += u.dx; + dy += u.dy; + + } + } else { + dx = 8 * u.dx; + dy = 8 * u.dy; + } + truncate_to_map(&cx, &cy, dx, dy); + goto nxtc; + } + + if (c == gc.Cmd.spkeys[NHKF_GETPOS_HELP] || redraw_cmd(c)) { + /* '?' will redraw twice, first when removing popup text window + after showing the help text, then to reset highlighting */ + if (c == gc.Cmd.spkeys[NHKF_GETPOS_HELP]) + getpos_help(force, goal); + /* ^R: docrt(), hilite_state = default */ + getpos_refresh(); + curs(WIN_MAP, cx, cy); + /* update message window to reflect that we're still targeting */ + show_goal_msg = TRUE; + } else if (c == gc.Cmd.spkeys[NHKF_GETPOS_SHOWVALID]) { + if (getpos_hilitefunc) { + getpos_toggle_hilite_state(); + curs(WIN_MAP, cx, cy); + } + show_goal_msg = TRUE; /* we're still targeting */ + goto nxtc; + } else if (c == gc.Cmd.spkeys[NHKF_GETPOS_AUTODESC]) { + iflags.autodescribe = !iflags.autodescribe; + pline("Automatic description %sis %s.", + flags.verbose ? "of features under cursor " : "", + iflags.autodescribe ? "on" : "off"); + if (!iflags.autodescribe) + show_goal_msg = TRUE; + msg_given = TRUE; + goto nxtc; + } else if (c == gc.Cmd.spkeys[NHKF_GETPOS_LIMITVIEW]) { + static const char *const view_filters[NUM_GFILTER] = { + "Not limiting targets", + "Limiting targets to those in sight", + "Limiting targets to those in same area" + }; + + iflags.getloc_filter = (iflags.getloc_filter + 1) % NUM_GFILTER; + for (i = 0; i < NUM_GLOCS; i++) { + if (garr[i]) { + free((genericptr_t) garr[i]); + garr[i] = NULL; + } + gidx[i] = gcount[i] = 0; + } + pline("%s.", view_filters[iflags.getloc_filter]); + msg_given = TRUE; + goto nxtc; + } else if (c == gc.Cmd.spkeys[NHKF_GETPOS_MENU]) { + iflags.getloc_usemenu = !iflags.getloc_usemenu; + pline("%s a menu to show possible targets%s.", + iflags.getloc_usemenu ? "Using" : "Not using", + iflags.getloc_usemenu + ? " for 'm|M', 'o|O', 'd|D', and 'x|X'" : ""); + msg_given = TRUE; + goto nxtc; + } else if (c == gc.Cmd.spkeys[NHKF_GETPOS_SELF]) { + /* reset 'm&M', 'o&O', &c; otherwise, there's no way for player + to achieve that except by manually cycling through all spots */ + for (i = 0; i < NUM_GLOCS; i++) + gidx[i] = 0; + cx = u.ux; + cy = u.uy; + goto nxtc; + } else if (c == gc.Cmd.spkeys[NHKF_GETPOS_MOVESKIP]) { + iflags.getloc_moveskip = !iflags.getloc_moveskip; + pline("%skipping over similar terrain when fastmoving the cursor.", + iflags.getloc_moveskip ? "S" : "Not s"); + msg_given = TRUE; + goto nxtc; + } else if ((cp = strchr(mMoOdDxX, c)) != 0) { /* 'm|M', 'o|O', &c */ + /* nearest or farthest monster or object or door or unexplored */ + int gtmp = (int) (cp - mMoOdDxX), /* 0..7 */ + gloc = gtmp >> 1; /* 0..3 */ + + if (iflags.getloc_usemenu) { + coord tmpcrd; + + if (getpos_menu(&tmpcrd, gloc)) { + cx = tmpcrd.x; + cy = tmpcrd.y; + } + goto nxtc; + } + + if (!garr[gloc]) { + gather_locs(&garr[gloc], &gcount[gloc], gloc); + gidx[gloc] = 0; /* garr[][0] is hero's spot */ + } + if (!(gtmp & 1)) { /* c=='m' || c=='o' || c=='d' || c=='x') */ + gidx[gloc] = (gidx[gloc] + 1) % gcount[gloc]; + } else { /* c=='M' || c=='O' || c=='D' || c=='X') */ + if (--gidx[gloc] < 0) + gidx[gloc] = gcount[gloc] - 1; + } + cx = garr[gloc][gidx[gloc]].x; + cy = garr[gloc][gidx[gloc]].y; + goto nxtc; + } else { + if (!strchr(quitchars, c)) { + char matching[MAXPCHARS]; + int pass, k = 0; + coordxy lo_x, lo_y, hi_x, hi_y; + + (void) memset((genericptr_t) matching, 0, sizeof matching); + for (sidx = 0; sidx < MAXPCHARS; sidx++) { + /* don't even try to match some terrain: walls, room... */ + if (is_cmap_wall(sidx) || is_cmap_room(sidx) + || is_cmap_corr(sidx) || is_cmap_door(sidx) + || sidx == S_ndoor) + continue; + if (c == defsyms[sidx].sym + || c == (int) gs.showsyms[sidx] + /* have '^' match webs and vibrating square or any + other trap that uses something other than '^' */ + || (c == '^' && is_cmap_trap(sidx)) + /* have room engraving character (default '`') + match corridor engravings (default '#') too */ + || (c == gs.showsyms[S_engroom] + && is_cmap_engraving(sidx))) + matching[sidx] = (char) ++k; + } + if (k) { + for (pass = 0; pass <= 1; pass++) { + /* pass 0: just past current pos to lower right; + pass 1: upper left corner to current pos */ + lo_y = (pass == 0) ? cy : 0; + hi_y = (pass == 0) ? ROWNO - 1 : cy; + for (ty = lo_y; ty <= hi_y; ty++) { + lo_x = (pass == 0 && ty == lo_y) ? cx + 1 : 1; + hi_x = (pass == 1 && ty == hi_y) ? cx : COLNO - 1; + for (tx = lo_x; tx <= hi_x; tx++) { + /* first, look at what is currently visible + (might be monster) */ + k = glyph_at(tx, ty); + if (glyph_is_cmap(k) + && matching[glyph_to_cmap(k)]) + goto foundc; + /* next, try glyph that's remembered here + (might be trap or object) */ + if (svl.level.flags.hero_memory + /* !terrainmode: don't move to remembered + trap or object if not currently shown */ + && !iflags.terrainmode) { + k = levl[tx][ty].glyph; + if (glyph_is_cmap(k) + && matching[glyph_to_cmap(k)]) + goto foundc; + } + /* FIXME: check player-specified vib.sq trap + symbol rather than or in addition to '~' */ + if (c == '~' + && known_vibrating_square_at(tx, ty)) + goto foundc; + /* last, try actual terrain here (shouldn't + we be using svl.lastseentyp[][] instead?) */ + if (levl[tx][ty].seenv) { + k = back_to_glyph(tx, ty); + if (glyph_is_cmap(k) + && matching[glyph_to_cmap(k)]) + goto foundc; + } + continue; + foundc: + cx = tx, cy = ty; + if (msg_given) { + clear_nhwindow(WIN_MESSAGE); + msg_given = FALSE; + } + goto nxtc; + } /* column */ + } /* row */ + } /* pass */ + pline("Can't find dungeon feature '%c'.", c); + msg_given = TRUE; + goto nxtc; + } else { + char note[QBUFSZ]; + + if (!force) + Strcpy(note, "aborted"); + else /* hjkl */ + Sprintf(note, "use '%s', '%s', '%s', '%s' or '%s'", + visctrl(cmd_from_func(do_move_west)), + visctrl(cmd_from_func(do_move_south)), + visctrl(cmd_from_func(do_move_north)), + visctrl(cmd_from_func(do_move_east)), + visctrl(gc.Cmd.spkeys[NHKF_GETPOS_PICK])); + pline("Unknown direction: '%s' (%s).", visctrl((char) c), + note); + msg_given = TRUE; + } /* k => matching */ + } /* !quitchars */ + if (force) + goto nxtc; + pline("Done."); + msg_given = FALSE; /* suppress clear */ + cx = -1; + cy = 0; + result = 0; /* not -1 */ + break; + } + nxtc: + gg.getposx = cx, gg.getposy = cy; +#ifdef CLIPPING + cliparound(cx, cy); +#endif + curs(WIN_MAP, cx, cy); + flush_screen(0); + } + exitgetpos: +#ifdef MACOS9 + lock_mouse_cursor(FALSE); +#endif + lock_mouse_buttons(FALSE); + if (msg_given) + clear_nhwindow(WIN_MESSAGE); + ccp->x = cx; + ccp->y = cy; + gg.getposx = gg.getposy = 0; + for (i = 0; i < NUM_GLOCS; i++) + if (garr[i]) + free((genericptr_t) garr[i]); + getpos_sethilite(NULL, NULL); + u.dx = udx, u.dy = udy, u.dz = udz; + return result; +} + +/*getpos.c*/ diff --git a/src/glyphs.c b/src/glyphs.c new file mode 100644 index 000000000..53235659d --- /dev/null +++ b/src/glyphs.c @@ -0,0 +1,1324 @@ +/* NetHack 5.0 glyphs.c TODO: add NHDT branch/date/revision tags */ +/* Copyright (c) Michael Allison, 2021. */ +/* NetHack may be freely redistributed. See license for details. */ + +#include "hack.h" + +extern const struct symparse loadsyms[]; +extern glyph_map glyphmap[MAX_GLYPH]; +extern struct enum_dump monsdump[]; +extern struct enum_dump objdump[]; + +#define Fprintf (void) fprintf + +enum reserved_activities { res_nothing, res_dump_glyphids, res_fill_cache }; +enum things_to_find { find_nothing, find_pm, find_oc, find_cmap, find_glyph }; +struct find_struct { + enum things_to_find findtype; + int val; + int loadsyms_offset; + int loadsyms_count; + int *extraval; + uint32 color; + const char *unicode_val; /* U+NNNN format */ + void (*callback)(int glyph, struct find_struct *); + enum reserved_activities restype; + genericptr_t reserved; +}; +static const struct find_struct zero_find = { 0 }; +struct glyphid_cache_t { + int glyphnum; + char *id; +}; +static struct glyphid_cache_t *glyphid_cache; +static unsigned glyphid_cache_lsize; +static size_t glyphid_cache_size; +static struct find_struct glyphcache_find, to_custom_symbol_find; +static const long nonzero_black = CLR_BLACK | NH_BASIC_COLOR; + +staticfn void init_glyph_cache(void); +staticfn void add_glyph_to_cache(int glyphnum, const char *id); +staticfn int find_glyph_in_cache(const char *id); +staticfn char *find_glyphid_in_cache_by_glyphnum(int glyphnum); +staticfn uint32 glyph_hash(const char *id); +staticfn void to_custom_symset_entry_callback(int glyph, + struct find_struct *findwhat); +staticfn int parse_id(const char *id, struct find_struct *findwhat); +staticfn int glyph_find_core(const char *id, struct find_struct *findwhat); +staticfn char *fix_glyphname(char *str); +staticfn void shuffle_customizations(void); +/* staticfn void purge_custom_entries(enum graphics_sets which_set); */ + +staticfn void +to_custom_symset_entry_callback( + int glyph, + struct find_struct *findwhat) +{ + int idx = gs.symset_which_set; +#ifdef ENHANCED_SYMBOLS + uint8 utf8str[6] = { 0, 0, 0, 0, 0, 0 }; + int uval = 0; +#endif + + if (findwhat->extraval) + *findwhat->extraval = glyph; + + assert(idx >= 0 && idx < NUM_GRAPHICS); +#ifdef ENHANCED_SYMBOLS + if (findwhat->unicode_val) + uval = unicode_val(findwhat->unicode_val); + if (uval && unicodeval_to_utf8str(uval, utf8str, sizeof utf8str)) { + /* presently the customizations are affiliated with a particular + * symset but if we don't have any symset context, ignore it for now + * in order to avoid a segfault. + * FIXME: + * One future idea might be to store the U+ entries under "UTF8" + * and apply those customizations to any current symset if it has + * a UTF8 handler. Similar approach for unaffiliated glyph/symbols + * non-UTF color customizations + */ + if (gs.symset[idx].name) { + add_custom_urep_entry(gs.symset[idx].name, glyph, uval, + utf8str, gs.symset_which_set); + } else { + static int glyphnag = 0; + + if (!glyphnag++) + config_error_add("Unimplemented customization feature," + " ignoring for now"); + } + } +#endif + if (findwhat->color) { + if (gs.symset[idx].name) { + add_custom_nhcolor_entry(gs.symset[idx].name, glyph, + findwhat->color, gs.symset_which_set); + } else { + static int colornag = 0; + + if (!colornag++) + config_error_add("Unimplemented customization feature," + " ignoring for now"); + } + } +} + +/* + * Return value: + * 1 = success + * 0 = failure + */ +int +glyphrep_to_custom_map_entries( + const char *op, + int *glyphptr) +{ + to_custom_symbol_find = zero_find; + char buf[BUFSZ], *c_glyphid, *c_unicode, *c_colorval, *cp; + int reslt = 0; + long rgb = 0L; + boolean slash = FALSE, colon = FALSE; + + if (!glyphid_cache) + reslt = 1; /* for debugger use only; no cache available */ + nhUse(reslt); + + Snprintf(buf, sizeof buf, "%s", op); + c_unicode = c_colorval = (char *) 0; + c_glyphid = cp = buf; + while (*cp) { + if (*cp == ':' || *cp == '/') { + if (*cp == ':') { + colon = TRUE; + *cp = '\0'; + } + if (*cp == '/') { + slash = TRUE; + *cp = '\0'; + } + } + cp++; + if (colon) { + c_unicode = cp; + colon = FALSE; + } + if (slash) { + c_colorval = cp; + slash = FALSE; + } + } + /* some sanity checks */ + if (c_glyphid && *c_glyphid == ' ') + c_glyphid++; + if (c_colorval && *c_colorval == ' ') + c_colorval++; + if (c_unicode && *c_unicode == ' ') { + while (*c_unicode == ' ') { + c_unicode++; + } + } + if (c_unicode && !*c_unicode) + c_unicode = 0; + + if ((c_colorval && (rgb = rgbstr_to_int32(c_colorval)) != -1L) + || !c_colorval) { + /* if the color 0 is an actual color, as opposed to just "not set" + we set a marker bit outside the 24-bit range to indicate a + valid color value 0. That allows valid color 0, but allows a + simple checking for 0 to detect "not set". The window port that + implements the color switch, needs to either check that bit + or appropriately mask colors with 0xFFFFFF. */ + to_custom_symbol_find.color = (rgb == -1 || !c_colorval) ? 0L + : (rgb == 0L) ? nonzero_black + : rgb; + } + if (c_unicode) + to_custom_symbol_find.unicode_val = c_unicode; + to_custom_symbol_find.extraval = glyphptr; + to_custom_symbol_find.callback = to_custom_symset_entry_callback; + reslt = glyph_find_core(c_glyphid, &to_custom_symbol_find); + return reslt; +} + +staticfn char * +fix_glyphname(char *str) +{ + char *c; + + for (c = str; *c; c++) { + if (*c >= 'A' && *c <= 'Z') + *c += (char) ('a' - 'A'); + else if (*c >= '0' && *c <= '9') + ; + else if (*c < 'a' || *c > 'z') + *c = '_'; + } + return str; +} + +int +glyph_to_cmap(int glyph) +{ + if (glyph == GLYPH_CMAP_STONE_OFF) + return S_stone; + else if (glyph_is_cmap_main(glyph)) + return (glyph - GLYPH_CMAP_MAIN_OFF) + S_vwall; + else if (glyph_is_cmap_mines(glyph)) + return (glyph - GLYPH_CMAP_MINES_OFF) + S_vwall; + else if (glyph_is_cmap_gehennom(glyph)) + return (glyph - GLYPH_CMAP_GEH_OFF) + S_vwall; + else if (glyph_is_cmap_knox(glyph)) + return (glyph - GLYPH_CMAP_KNOX_OFF) + S_vwall; + else if (glyph_is_cmap_sokoban(glyph)) + return (glyph - GLYPH_CMAP_SOKO_OFF) + S_vwall; + else if (glyph_is_cmap_a(glyph)) + return (glyph - GLYPH_CMAP_A_OFF) + S_ndoor; + else if (glyph_is_cmap_altar(glyph)) + return S_altar; + else if (glyph_is_cmap_b(glyph)) + return (glyph - GLYPH_CMAP_B_OFF) + S_grave; + else if (glyph_is_cmap_c(glyph)) + return (glyph - GLYPH_CMAP_C_OFF) + S_digbeam; + else if (glyph_is_cmap_zap(glyph)) + return ((glyph - GLYPH_ZAP_OFF) % 4) + S_vbeam; + else if (glyph_is_swallow(glyph)) + return glyph_to_swallow(glyph) + S_sw_tl; + else if (glyph_is_explosion(glyph)) + return glyph_to_explosion(glyph) + S_expl_tl; + else + return MAXPCHARS; /* MAXPCHARS is legal array index because + * of trailing fencepost entry */ +} + +staticfn int +glyph_find_core( + const char *id, + struct find_struct *findwhat) +{ + int glyph; + boolean do_callback, end_find = FALSE; + + if (parse_id(id, findwhat)) { + if (findwhat->findtype == find_glyph) { + (*findwhat->callback)(findwhat->val, findwhat); + } else { + for (glyph = 0; glyph < MAX_GLYPH; ++glyph) { + do_callback = FALSE; + switch (findwhat->findtype) { + case find_cmap: + if (glyph_to_cmap(glyph) == findwhat->val) + do_callback = TRUE; + break; + case find_pm: + if (glyph_is_monster(glyph) + && mons[glyph_to_mon(glyph)].mlet + == findwhat->val) + do_callback = TRUE; + break; + case find_oc: + if (glyph_is_object(glyph) + && glyph_to_obj(glyph) == findwhat->val) + do_callback = TRUE; + break; + case find_glyph: + if (glyph == findwhat->val) { + do_callback = TRUE; + end_find = TRUE; + } + break; + case find_nothing: + default: + end_find = TRUE; + break; + } + if (do_callback) + (findwhat->callback)(glyph, findwhat); + if (end_find) + break; + } + } + return 1; + } + return 0; +} + +/* + When we start to process a config file or a symbol file, + that might have G_ entries, generating all 9000+ glyphid + for comparison repeatedly each time we encounter a G_ + entry to decipher, then comparing against them, is obviously + extremely performance-poor. + + Setting aside the "comparison" part for now (that has to be + done in some manner), we can likely do something about the + repeated "generation" of the names for parsing prior to the + actual comparison part by generating them once, ahead of the + bulk of the potential parsings. We can later free up + all the memory those names consumed once the bulk parsing is + over with. +*/ + + +void +fill_glyphid_cache(void) +{ + int reslt = 0; + + if (!glyphid_cache) { + init_glyph_cache(); + } + if (glyphid_cache) { + glyphcache_find = zero_find; + glyphcache_find.findtype = find_nothing; + glyphcache_find.reserved = (genericptr_t) glyphid_cache; + glyphcache_find.restype = res_fill_cache; + reslt = parse_id((char *) 0, &glyphcache_find); + if (!reslt) { + free_glyphid_cache(); + glyphid_cache = (struct glyphid_cache_t *) 0; + } + } +} + +/* + * The glyph ID cache is a simple double-hash table. + * The cache size is a power of two, and two hashes are derived from the + * cache ID. The first is a location in the table, and the second is an + * offset. On any collision, the second hash is added to the first until + * a match or an empty bucket is found. + * The second hash is an odd number, which is necessary and sufficient + * to traverse the entire table. + */ + +staticfn void +init_glyph_cache(void) +{ + size_t glyph; + + /* Cache size of power of 2 not less than 2*MAX_GLYPH */ + glyphid_cache_lsize = 0; + glyphid_cache_size = 1; + while (glyphid_cache_size < 2*MAX_GLYPH) { + ++glyphid_cache_lsize; + glyphid_cache_size <<= 1; + } + + glyphid_cache = (struct glyphid_cache_t *) alloc( + glyphid_cache_size * sizeof (struct glyphid_cache_t)); + for (glyph = 0; glyph < glyphid_cache_size; ++glyph) { + glyphid_cache[glyph].glyphnum = 0; + glyphid_cache[glyph].id = (char *) 0; + } +} + +void +free_glyphid_cache(void) +{ + size_t idx; + + if (!glyphid_cache) + return; + for (idx = 0; idx < glyphid_cache_size; ++idx) { + if (glyphid_cache[idx].id) { + free(glyphid_cache[idx].id); + glyphid_cache[idx].id = (char *) 0; + } + } + free(glyphid_cache); + glyphid_cache = (struct glyphid_cache_t *) 0; +} + +staticfn void +add_glyph_to_cache(int glyphnum, const char *id) +{ + uint32 hash = glyph_hash(id); + size_t hash1 = (size_t) (hash & (glyphid_cache_size - 1)); + size_t hash2 = (size_t) + (((hash >> glyphid_cache_lsize) & (glyphid_cache_size - 1)) | 1); + size_t i = hash1; + + do { + if (glyphid_cache[i].id == NULL) { + /* Empty bucket found */ + glyphid_cache[i].id = dupstr(id); + glyphid_cache[i].glyphnum = glyphnum; + return; + } + /* For speed, assume that no ID occurs twice */ + i = (i + hash2) & (glyphid_cache_size - 1); + } while (i != hash1); + /* This should never happen */ + panic("glyphid_cache full"); +} + +staticfn int +find_glyph_in_cache(const char *id) +{ + uint32 hash = glyph_hash(id); + size_t hash1 = (size_t) (hash & (glyphid_cache_size - 1)); + size_t hash2 = (size_t) + (((hash >> glyphid_cache_lsize) & (glyphid_cache_size - 1)) | 1); + size_t i = hash1; + + do { + if (glyphid_cache[i].id == NULL) { + /* Empty bucket found */ + return -1; + } + if (strcmpi(id, glyphid_cache[i].id) == 0) { + /* Match found */ + return glyphid_cache[i].glyphnum; + } + i = (i + hash2) & (glyphid_cache_size - 1); + } while (i != hash1); + return -1; +} + +staticfn char * +find_glyphid_in_cache_by_glyphnum(int glyphnum) +{ + size_t idx; + + if (!glyphid_cache) + return (char *) 0; + for (idx = 0; idx < glyphid_cache_size; ++idx) { + if (glyphid_cache[idx].glyphnum == glyphnum + && glyphid_cache[idx].id != 0) { + /* Match found */ + return glyphid_cache[idx].id; + } + } + return (char *) 0; +} + +staticfn uint32 +glyph_hash(const char *id) +{ + uint32 hash = 0; + size_t i; + + for (i = 0; id[i] != '\0'; ++i) { + char ch = id[i]; + if ('A' <= ch && ch <= 'Z') { + ch += 'a' - 'A'; + } + hash = (hash << 1) | (hash >> 31); + hash ^= ch; + } + return hash; +} + +boolean +glyphid_cache_status(void) +{ + return (glyphid_cache != 0); +} + +int +match_glyph(char *buf) +{ + char workbuf[BUFSZ]; + + /* buf contains a G_ glyph reference, not an S_ symbol. + There could be an R-G-B color attached too. + Let's get a copy to work with. */ + Snprintf(workbuf, sizeof workbuf, "%s", buf); /* get a copy */ + return glyphrep(workbuf); +} + +int +glyphrep(const char *op) +{ + int reslt = 0, glyph = NO_GLYPH; + + if (!glyphid_cache) + reslt = 1; /* for debugger use only; no cache available */ + nhUse(reslt); + reslt = glyphrep_to_custom_map_entries(op, &glyph); + if (reslt) + return 1; + return 0; +} + +int +add_custom_nhcolor_entry( + const char *customization_name, + int glyphidx, + uint32 nhcolor, + enum graphics_sets which_set) +{ + struct symset_customization *gdc + = &gs.sym_customizations[which_set][custom_nhcolor]; + struct customization_detail *details, *newdetails = 0; +#if 0 + static uint32 closecolor = 0; + static int clridx = 0; +#endif + + if (!gdc->details) { + gdc->customization_name = dupstr(customization_name); + gdc->custtype = custom_nhcolor; + gdc->details = 0; + gdc->details_end = 0; + } + details = find_matching_customization(customization_name, + custom_nhcolor, which_set); + if (details) { + while (details) { + if (details->content.ccolor.glyphidx == glyphidx) { + details->content.ccolor.nhcolor = nhcolor; + return 1; + } + details = details->next; + } + } + /* create new details entry */ + newdetails = (struct customization_detail *) alloc(sizeof *newdetails); + newdetails->content.urep.glyphidx = glyphidx; + newdetails->content.ccolor.nhcolor = nhcolor; + newdetails->next = (struct customization_detail *) 0; + if (gdc->details == NULL) { + gdc->details = newdetails; + } else { + gdc->details_end->next = newdetails; + } + gdc->details_end = newdetails; + gdc->count++; + return 1; +} + +void +apply_customizations( + enum graphics_sets which_set, + enum do_customizations docustomize) +{ + glyph_map *gmap; + struct customization_detail *details; + struct symset_customization *sc; + boolean at_least_one = FALSE, + do_colors = ((docustomize & do_custom_colors) != 0), + do_symbols = ((docustomize & do_custom_symbols) != 0); + int custs; + + for (custs = 0; custs < (int) custom_count; ++custs) { + sc = &gs.sym_customizations[which_set][custs]; + if (sc->count && sc->details) { + at_least_one = TRUE; + /* These glyph customizations get applied to the glyphmap array, + not to symset entries */ + details = sc->details; + while (details) { +#ifdef ENHANCED_SYMBOLS + if (iflags.customsymbols && do_symbols) { + if (sc->custtype == custom_ureps) { + gmap = &glyphmap[details->content.urep.glyphidx]; + if (gs.symset[which_set].handling == H_UTF8) + (void) set_map_u(gmap, + details->content.urep.u.utf32ch, + details->content.urep.u.utf8str); + } + } +#endif + if (iflags.customcolors && do_colors) { + if (sc->custtype == custom_nhcolor) { + gmap = &glyphmap[details->content.ccolor.glyphidx]; + (void) set_map_customcolor(gmap, + details->content.ccolor.nhcolor); + } + } + details = details->next; + } + } + } + iflags.pending_customizations = at_least_one; +} + +/* Shuffle the customizations to match shuffled object descriptions, + * so a red potion isn't displayed with a blue customization, and so on. + */ + +void +maybe_shuffle_customizations(void) +{ + if (iflags.pending_customizations) { + shuffle_customizations(); + iflags.pending_customizations = 0; + } +} + +#if 0 +staticfn void +shuffle_customizations(void) +{ + static const int offsets[2] = { GLYPH_OBJ_OFF, GLYPH_OBJ_PILETOP_OFF }; + int j; + + for (j = 0; j < SIZE(offsets); j++) { + glyph_map *obj_glyphs = glyphmap + offsets[j]; + int i; + struct unicode_representation *tmp_u[NUM_OBJECTS]; + int duplicate[NUM_OBJECTS]; + + for (i = 0; i < NUM_OBJECTS; i++) { + duplicate[i] = -1; + tmp_u[i] = (struct unicode_representation *) 0; + } + for (i = 0; i < NUM_OBJECTS; i++) { + int idx = objects[i].oc_descr_idx; + + /* + * Shuffling gem appearances can cause the same oc_descr_idx to + * appear more than once. Detect this condition and ensure that + * each pointer points to a unique allocation. + */ + if (duplicate[idx] >= 0) { + /* Current structure already appears in tmp_u */ + struct unicode_representation *other = tmp_u[duplicate[idx]]; + + tmp_u[i] = (struct unicode_representation *) + alloc(sizeof *tmp_u[i]); + *tmp_u[i] = *other; + if (other->utf8str != NULL) { + tmp_u[i]->utf8str = (uint8 *) + dupstr((const char *) other->utf8str); + } + } else { + tmp_u[i] = obj_glyphs[idx].u; + if (obj_glyphs[idx].u != NULL) { + duplicate[idx] = i; + obj_glyphs[idx].u = NULL; + } + } + } + for (i = 0; i < NUM_OBJECTS; i++) { + /* Some glyphmaps may not have been transferred */ + if (obj_glyphs[i].u != NULL) { + free(obj_glyphs[i].u->utf8str); + free(obj_glyphs[i].u); + } + obj_glyphs[i].u = tmp_u[i]; + } + } +} + +#else +staticfn void +shuffle_customizations(void) +{ + static const int offsets[2] = { GLYPH_OBJ_OFF, GLYPH_OBJ_PILETOP_OFF }; + int j; + + for (j = 0; j < SIZE(offsets); j++) { + glyph_map *obj_glyphs = glyphmap + offsets[j]; + int i; +#ifdef ENHANCED_SYMBOLS + struct unicode_representation *tmp_u[NUM_OBJECTS]; +#endif + uint32 tmp_customcolor[NUM_OBJECTS]; + uint16 tmp_color256idx[NUM_OBJECTS]; + + int duplicate[NUM_OBJECTS]; + + for (i = 0; i < NUM_OBJECTS; i++) { + duplicate[i] = -1; +#ifdef ENHANCED_SYMBOLS + tmp_u[i] = (struct unicode_representation *) 0; +#endif + tmp_customcolor[i] = 0; + tmp_color256idx[i] = 0; + } + for (i = 0; i < NUM_OBJECTS; i++) { + int idx = objects[i].oc_descr_idx; + + /* + * Shuffling gem appearances can cause the same oc_descr_idx to + * appear more than once. Detect this condition and ensure that + * each pointer points to a unique allocation. + */ + if (duplicate[idx] >= 0) { +#ifdef ENHANCED_SYMBOLS + /* Current structure already appears in tmp_u */ + struct unicode_representation *other = tmp_u[duplicate[idx]]; +#endif + uint32 other_customcolor = tmp_customcolor[duplicate[idx]]; + uint16 other_color256idx = tmp_color256idx[duplicate[idx]]; + + tmp_customcolor[i] = other_customcolor; + tmp_color256idx[i] = other_color256idx; +#ifdef ENHANCED_SYMBOLS + if (other) { + tmp_u[i] = (struct unicode_representation *) alloc( + sizeof *tmp_u[i]); + *tmp_u[i] = *other; + if (other->utf8str != NULL) { + tmp_u[i]->utf8str = + (uint8 *) dupstr((const char *) other->utf8str); + } + } +#endif + } else { + tmp_customcolor[i] = obj_glyphs[idx].customcolor; + tmp_color256idx[i] = obj_glyphs[idx].color256idx; +#ifdef ENHANCED_SYMBOLS + tmp_u[i] = obj_glyphs[idx].u; +#endif + if ( +#ifdef ENHANCED_SYMBOLS + obj_glyphs[idx].u != NULL || +#endif + obj_glyphs[idx].customcolor != 0) { + duplicate[idx] = i; +#ifdef ENHANCED_SYMBOLS + obj_glyphs[idx].u = NULL; +#endif + obj_glyphs[idx].customcolor = 0; + obj_glyphs[idx].color256idx = 0; + } + } + } + for (i = 0; i < NUM_OBJECTS; i++) { + /* Some glyphmaps may not have been transferred */ +#ifdef ENHANCED_SYMBOLS + if (obj_glyphs[i].u != NULL) { + free(obj_glyphs[i].u->utf8str); + free(obj_glyphs[i].u); + } + obj_glyphs[i].u = tmp_u[i]; +#endif + obj_glyphs[i].customcolor = tmp_customcolor[i]; + obj_glyphs[i].color256idx = tmp_color256idx[i]; + } + } +} +#endif + +struct customization_detail * +find_matching_customization( + const char *customization_name, + enum customization_types custtype, + enum graphics_sets which_set) +{ + struct symset_customization *gdc + = &gs.sym_customizations[which_set][custtype]; + + if ((gdc->custtype == custtype) && gdc->customization_name + && (strcmp(customization_name, gdc->customization_name) == 0)) + return gdc->details; + return (struct customization_detail *) 0; +} + +void +purge_all_custom_entries(void) +{ + int i; + + for (i = 0; i < NUM_GRAPHICS + 1; ++i) { + purge_custom_entries(i); + } +} + +void +purge_custom_entries(enum graphics_sets which_set) +{ + enum customization_types custtype; + struct symset_customization *gdc; + struct customization_detail *details, *next; + + for (custtype = custom_none; custtype < custom_count; ++custtype) { + gdc = &gs.sym_customizations[which_set][custtype]; + details = gdc->details; + while (details) { + next = details->next; + if (gdc->custtype == custom_ureps) { + if (details->content.urep.u.utf8str) + free(details->content.urep.u.utf8str); + details->content.urep.u.utf8str = (uint8 *) 0; + } else if (gdc->custtype == custom_symbols) { + details->content.sym.symparse = (struct symparse *) 0; + details->content.sym.val = 0; + } else if (gdc->custtype == custom_nhcolor) { + details->content.ccolor.nhcolor = 0; + details->content.ccolor.glyphidx = 0; + } + free(details); + details = next; + } + gdc->details = 0; + gdc->details_end = 0; + if (gdc->customization_name) { + free((genericptr_t) gdc->customization_name); + gdc->customization_name = 0; + } + gdc->count = 0; + } +} + +void +dump_all_glyphids(FILE *fp) +{ + struct find_struct dump_glyphid_find = zero_find; + + dump_glyphid_find.findtype = find_nothing; + dump_glyphid_find.reserved = (genericptr_t) fp; + dump_glyphid_find.restype = res_dump_glyphids; + (void) parse_id((char *) 0, &dump_glyphid_find); +} + +void +wizcustom_glyphids(winid win) +{ + int glyphnum; + char *id; + + if (!glyphid_cache) + return; + for (glyphnum = 0; glyphnum < MAX_GLYPH; ++glyphnum) { + id = find_glyphid_in_cache_by_glyphnum(glyphnum); + if (id) { + wizcustom_callback(win, glyphnum, id); + } + } +} + +staticfn int +parse_id( + const char *id, + struct find_struct *findwhat) +{ + FILE *fp = (FILE *) 0; + int i = 0, j, mnum, glyph, + pm_offset = 0, oc_offset = 0, cmap_offset = 0, + pm_count = 0, oc_count = 0, cmap_count = 0; + boolean skip_base = FALSE, skip_this_one = FALSE, dump_ids = FALSE, + filling_cache = FALSE, is_S = FALSE, is_G = FALSE; + char buf[4][QBUFSZ]; + + if (findwhat->findtype == find_nothing && findwhat->restype) { + if (findwhat->restype == res_dump_glyphids) { + if (findwhat->reserved) { + fp = (FILE *) findwhat->reserved; + dump_ids = TRUE; + } else { + return 0; + } + } + if (findwhat->restype == res_fill_cache) { + if (findwhat->reserved + && findwhat->reserved == (genericptr_t) glyphid_cache) { + filling_cache = TRUE; + } else { + return 0; + } + } + } + + is_G = (id && id[0] == 'G' && id[1] == '_'); + is_S = (id && id[0] == 'S' && id[1] == '_'); + + if ((is_G && !glyphid_cache) || filling_cache || dump_ids || is_S) { + while (loadsyms[i].range) { + if (!pm_offset && loadsyms[i].range == SYM_MON) + pm_offset = i; + if (!pm_count && pm_offset && loadsyms[i].range != SYM_MON) + pm_count = i - pm_offset; + if (!oc_offset && loadsyms[i].range == SYM_OC) + oc_offset = i; + if (!oc_count && oc_offset && loadsyms[i].range != SYM_OC) + oc_count = i - oc_offset; + if (!cmap_offset && loadsyms[i].range == SYM_PCHAR) + cmap_offset = i; + if (!cmap_count && cmap_offset && loadsyms[i].range != SYM_PCHAR) + cmap_count = i - cmap_offset; + i++; + } + } + if (is_G || filling_cache || dump_ids) { + if (!filling_cache && id && glyphid_cache) { + int val = find_glyph_in_cache(id); + if (val >= 0) { + findwhat->findtype = find_glyph; + findwhat->val = val; + findwhat->loadsyms_offset = 0; + return 1; + } else { + return 0; + } + } else { + const char *buf2, *buf3, *buf4; + + /* individual matching glyph entries */ + for (glyph = 0; glyph < MAX_GLYPH; ++glyph) { + skip_base = FALSE; + skip_this_one = FALSE; + buf[0][0] = buf[1][0] = buf[2][0] = buf[3][0] = '\0'; + if (glyph_is_monster(glyph)) { + /* buf2 will hold the distinguishing prefix */ + /* buf3 will hold the base name */ + buf2 = ""; + buf3 = monsdump[glyph_to_mon(glyph)].nm; + + if (glyph_is_normal_male_monster(glyph)) { + buf2 = "male_"; + } else if (glyph_is_normal_female_monster(glyph)) { + buf2 = "female_"; + } else if (glyph_is_ridden_male_monster(glyph)) { + buf2 = "ridden_male_"; + } else if (glyph_is_ridden_female_monster(glyph)) { + buf2 = "ridden_female_"; + } else if (glyph_is_detected_male_monster(glyph)) { + buf2 = "detected_male_"; + } else if (glyph_is_detected_female_monster(glyph)) { + buf2 = "detected_female_"; + } else if (glyph_is_male_pet(glyph)) { + buf2 = "pet_male_"; + } else if (glyph_is_female_pet(glyph)) { + buf2 = "pet_female_"; + } + Strcpy(buf[0], "G_"); + Strcat(buf[0], buf2); + Strcat(buf[0], buf3); + } else if (glyph_is_body(glyph)) { + /* buf2 will hold the distinguishing prefix */ + /* buf3 will hold the base name */ + buf2 = glyph_is_body_piletop(glyph) + ? "piletop_body_" + : "body_"; + buf3 = monsdump[glyph_to_body_corpsenm(glyph)].nm; + Strcpy(buf[0], "G_"); + Strcat(buf[0], buf2); + Strcat(buf[0], buf3); + } else if (glyph_is_statue(glyph)) { + /* buf2 will hold the distinguishing prefix */ + /* buf3 will hold the base name */ + buf2 = glyph_is_fem_statue_piletop(glyph) + ? "piletop_statue_of_female_" + : glyph_is_fem_statue(glyph) + ? "statue_of_female_" + : glyph_is_male_statue_piletop(glyph) + ? "piletop_statue_of_male_" + : glyph_is_male_statue(glyph) + ? "statue_of_male_" + : ""; /* shouldn't happen */ + buf3 = monsdump[glyph_to_statue_corpsenm(glyph)].nm; + Strcpy(buf[0], "G_"); + Strcat(buf[0], buf2); + Strcat(buf[0], buf3); + } else if (glyph_is_object(glyph)) { + i = glyph_to_obj(glyph); + /* buf2 will hold the distinguishing prefix */ + /* buf3 will hold the base name */ + if (((i > SCR_STINKING_CLOUD) && (i < SCR_MAIL)) + || ((i > WAN_LIGHTNING) && (i < GOLD_PIECE))) + skip_this_one = TRUE; + if (!skip_this_one) { + if ((i >= WAN_LIGHT) && (i <= WAN_LIGHTNING)) + buf2 = "wand of "; + else if ((i >= SPE_DIG) && (i < SPE_BLANK_PAPER)) + buf2 = "spellbook of "; + else if ((i >= SCR_ENCHANT_ARMOR) + && (i <= SCR_STINKING_CLOUD)) + buf2 = "scroll of "; + else if ((i >= POT_GAIN_ABILITY) && (i <= POT_WATER)) + buf2 = (i == POT_WATER) ? "flask of n" + : "potion of "; + else if ((i >= RIN_ADORNMENT) + && (i <= RIN_PROTECTION_FROM_SHAPE_CHAN)) + buf2 = "ring of "; + else if (i == LAND_MINE) + buf2 = "unset "; + else + buf2 = ""; + buf3 = (i == SCR_BLANK_PAPER) ? "blank scroll" + : (i == SPE_BLANK_PAPER) ? "blank spellbook" + : (i == SLIME_MOLD) ? "slime mold" + : obj_descr[i].oc_name + ? obj_descr[i].oc_name + : obj_descr[i].oc_descr; + Strcpy(buf[0], "G_"); + if (glyph_is_normal_piletop_obj(glyph)) + Strcat(buf[0], "piletop_"); + Strcat(buf[0], buf2); + Strcat(buf[0], buf3); + } + } else if (glyph_is_cmap(glyph) || glyph_is_cmap_zap(glyph) + || glyph_is_swallow(glyph) + || glyph_is_explosion(glyph)) { + int cmap = -1; + + /* buf2 will hold the distinguishing prefix */ + /* buf3 will hold the base name */ + /* buf4 will hold the distinguishing suffix */ + buf2 = ""; + buf3 = ""; + buf4 = ""; + if (glyph == GLYPH_CMAP_OFF) { + cmap = S_stone; + buf3 = "stone substrate"; + skip_base = TRUE; + } else if (glyph_is_cmap_gehennom(glyph)) { + cmap = (glyph - GLYPH_CMAP_GEH_OFF) + S_vwall; + buf4 = "_gehennom"; + } else if (glyph_is_cmap_knox(glyph)) { + cmap = (glyph - GLYPH_CMAP_KNOX_OFF) + S_vwall; + buf4 = "_knox"; + } else if (glyph_is_cmap_main(glyph)) { + cmap = (glyph - GLYPH_CMAP_MAIN_OFF) + S_vwall; + buf4 = "_main"; + } else if (glyph_is_cmap_mines(glyph)) { + cmap = (glyph - GLYPH_CMAP_MINES_OFF) + S_vwall; + buf4 = "_mines"; + } else if (glyph_is_cmap_sokoban(glyph)) { + cmap = (glyph - GLYPH_CMAP_SOKO_OFF) + S_vwall; + buf4 = "_sokoban"; + } else if (glyph_is_cmap_a(glyph)) { + cmap = (glyph - GLYPH_CMAP_A_OFF) + S_ndoor; + } else if (glyph_is_cmap_altar(glyph)) { + static const char *const altar_text[] = { + "unaligned", "chaotic", "neutral", + "lawful", "other", + }; + + j = (glyph - GLYPH_ALTAR_OFF); + cmap = S_altar; + if (j != altar_other) { + Snprintf(buf[2], sizeof buf[2], "%s_", + altar_text[j]); + buf2 = buf[2]; + } else { + buf3 = "altar other"; + skip_base = TRUE; + } + } else if (glyph_is_cmap_b(glyph)) { + cmap = (glyph - GLYPH_CMAP_B_OFF) + S_grave; + } else if (glyph_is_cmap_zap(glyph)) { + static const char *const zap_texts[] = { + "missile", "fire", "frost", "sleep", + "death", "lightning", "poison gas", "acid" + }; + + j = (glyph - GLYPH_ZAP_OFF); + cmap = (j % 4) + S_vbeam; + Snprintf(buf[2], sizeof buf[2], "%s", + loadsyms[cmap + cmap_offset].name + 2); + Snprintf(buf[3], sizeof buf[3], "%s zap %s", + zap_texts[j / 4], fix_glyphname(buf[2])); + buf3 = buf[3]; + buf2 = ""; + skip_base = TRUE; + } else if (glyph_is_cmap_c(glyph)) { + cmap = (glyph - GLYPH_CMAP_C_OFF) + S_digbeam; + } else if (glyph_is_swallow(glyph)) { + static const char *const swallow_texts[] = { + "top left", "top center", "top right", + "middle left", "middle right", "bottom left", + "bottom center", "bottom right", + }; + + j = glyph - GLYPH_SWALLOW_OFF; + cmap = glyph_to_swallow(glyph); + mnum = j / ((S_sw_br - S_sw_tl) + 1); + Strcpy(buf[3], "swallow "); + Strcat(buf[3], monsdump[mnum].nm); + Strcat(buf[3], " "); + Strcat(buf[3], swallow_texts[cmap]); + buf3 = buf[3]; + skip_base = TRUE; + } else if (glyph_is_explosion(glyph)) { + static const char *const expl_type_texts[] = { + "dark", "noxious", "muddy", "wet", + "magical", "fiery", "frosty", + }; + static const char *const expl_texts[] = { + "tl", "tc", "tr", "ml", "mc", + "mr", "bl", "bc", "br", + }; + int expl; + + j = glyph - GLYPH_EXPLODE_OFF; + expl = j / ((S_expl_br - S_expl_tl) + 1); + cmap = glyph_to_explosion(glyph) + S_expl_tl; + i = cmap - S_expl_tl; + Snprintf(buf[2], sizeof buf[2], "%s ", + expl_type_texts[expl]); + buf2 = buf[2]; + Snprintf(buf[3], sizeof buf[3], "%s%s", "expl_", + expl_texts[i]); + buf3 = buf[3]; + skip_base = TRUE; + } + if (!skip_base) { + if (cmap >= 0 && cmap < MAXPCHARS) { + buf3 = loadsyms[cmap + cmap_offset].name + 2; + } + } + Strcpy(buf[0], "G_"); + Strcat(buf[0], buf2); + Strcat(buf[0], buf3); + Strcat(buf[0], buf4); + } else if (glyph_is_invisible(glyph)) { + Strcpy(buf[0], "G_invisible"); + } else if (glyph_is_nothing(glyph)) { + Strcpy(buf[0], "G_nothing"); + } else if (glyph_is_unexplored(glyph)) { + Strcpy(buf[0], "G_unexplored"); + } else if (glyph_is_warning(glyph)) { + j = glyph - GLYPH_WARNING_OFF; + Snprintf(buf[0], sizeof buf[0], "G_%s%d", "warning", j); + } + if (memchr(buf[0], '\0', sizeof buf[0]) == NULL) + panic("parse_id: buf[0] overflowed"); + if (!skip_this_one) { + fix_glyphname(buf[0]+2); + if (dump_ids) { + Fprintf(fp, "(%04d) %s\n", glyph, buf[0]); + } else if (filling_cache) { + add_glyph_to_cache(glyph, buf[0]); + } else if (id) { + if (!strcmpi(id, buf[0])) { + findwhat->findtype = find_glyph; + findwhat->val = glyph; + findwhat->loadsyms_offset = 0; + return 1; + } + } + } + } + } /* not glyphid_cache */ + } else if (is_S) { + /* cmap entries */ + for (i = 0; i < cmap_count; ++i) { + if (!strcmpi(loadsyms[i + cmap_offset].name + 2, id + 2)) { + findwhat->findtype = find_cmap; + findwhat->val = i; + findwhat->loadsyms_offset = i + cmap_offset; + return 1; + } + } + /* objclass entries */ + for (i = 0; i < oc_count; ++i) { + if (!strcmpi(loadsyms[i + oc_offset].name + 2, id + 2)) { + findwhat->findtype = find_oc; + findwhat->val = i; + findwhat->loadsyms_offset = i + oc_offset; + return 1; + } + } + /* permonst entries */ + for (i = 0; i <= pm_count; ++i) { + if (!strcmpi(loadsyms[i + pm_offset].name + 2, id + 2)) { + findwhat->findtype = find_pm; + findwhat->val = i + 1; /* starts at 1 */ + findwhat->loadsyms_offset = i + pm_offset; + return 1; + } + } + } + if (dump_ids || filling_cache) + return 1; + findwhat->findtype = find_nothing; + findwhat->val = 0; + findwhat->loadsyms_offset = 0; + return 0; +} + +/* extern glyph_map glyphmap[MAX_GLYPH]; */ + +void +clear_all_glyphmap_colors(void) +{ + int glyph; + + for (glyph = 0; glyph < MAX_GLYPH; ++glyph) { + if (glyphmap[glyph].customcolor) + glyphmap[glyph].customcolor = 0; + glyphmap[glyph].color256idx = 0; + } +} + +void +reset_customcolors(void) +{ + clear_all_glyphmap_colors(); + apply_customizations(gc.currentgraphics, do_custom_colors); +} + +/* not used yet */ + +#if 0 +staticfn struct customization_detail *find_display_sym_customization( + const char *customization_name, const struct symparse *symparse, + enum graphics_sets which_set); +staticfn struct customization_detail *find_display_urep_customization( + const char *customization_name, int glyphidx, + enum graphics_sets which_set); + +struct customization_detail * +find_display_sym_customization( + const char *customization_name, + const struct symparse *symparse, + enum graphics_sets which_set) +{ + struct symset_customization *gdc; + struct customization_detail *symdetails; + + gdc = &gs.sym_customizations[which_set][custom_symbols]; + if ((gdc->custtype == custom_symbols) + && (strcmp(customization_name, gdc->customization_name) == 0)) { + symdetails = gdc->details; + while (symdetails) { + if (symdetails->content.sym.symparse == symparse) + return symdetails; + symdetails = symdetails->next; + } + } + return (struct customization_detail *) 0; +} + +struct customization_detail * +find_display_urep_customization( + const char *customization_name, + int glyphidx, + enum graphics_sets which_set) +{ + struct symset_customization *gdc = &gs.sym_customizations[which_set]; + struct customization_detail *urepdetails; + + if ((gdc->custtype == custom_reps) + || (strcmp(customization_name, gdc->customization_name) == 0)) { + urepdetails = gdc->details; + while (urepdetails) { + if (urepdetails->content.urep.glyphidx == glyphidx) + return urepdetails; + urepdetails = urepdetails->next; + } + } + return (struct customization_detail *) 0; +} +#endif /* 0 not used yet */ + +#ifdef TEST_GLYPHNAMES + +static struct { + int idx; + const char *nm1; + const char *nm2; +} cmapname[MAXPCHARS] = { +#define PCHAR_TILES +#include "defsym.h" +#undef PCHAR_TILES +}; + +void +test_glyphnames(void) +{ + int reslt; + + reslt = find_glyphs("G_potion_of_monster_detection"); + reslt = find_glyphs("G_piletop_body_chickatrice"); + reslt = find_glyphs("G_detected_male_homunculus"); + reslt = find_glyphs("S_pool"); + reslt = find_glyphs("S_dog"); + reslt = glyphs_to_unicode("S_dog", "U+130E6", 0L); +} + +staticfn void +just_find_callback(int glyph UNUSED, struct find_struct *findwhat UNUSED) +{ + return; +} + +staticfn int +find_glyphs(const char *id) +{ + struct find_struct find_only = zero_find; + + find_only.unicode_val = 0; + find_only.callback = just_find_callback; + return glyph_find_core(id, &find_only); +} + +staticfn void +to_unicode_callback(int glyph UNUSED, struct find_struct *findwhat) +{ + int uval; +#ifdef NO_PARSING_SYMSET + glyph_map *gm = &glyphmap[glyph]; +#endif + uint8 utf8str[6]; + + if (!findwhat->unicode_val) + return; + uval = unicode_val(findwhat->unicode_val); + if (unicodeval_to_utf8str(uval, utf8str, sizeof utf8str)) { +#ifdef NO_PARSING_SYMSET + set_map_u(gm, uval, utf8str, + (findwhat->color != 0L) ? findwhat->color : 0L); +#else + +#endif + } +} + +int +glyphs_to_unicode(const char *id, const char *unicode_val, long clr) +{ + struct find_struct to_unicode = zero_find; + + to_unicode.unicode_val = unicode_val; + to_unicode.callback = to_unicode_callback; + /* if the color 0 is an actual color, as opposed to just "not set" + we set a marker bit outside the 24-bit range to indicate a + valid color value 0. That allows valid color 0, but allows a + simple checking for 0 to detect "not set". The window port that + implements the color switch, needs to either check that bit + or appropriately mask colors with 0xFFFFFF. */ + to_unicode.color = (clr == -1) ? 0L : (clr == 0L) ? nonzero_black : clr; + return glyph_find_core(id, &to_unicode); +} + +#endif /* SOME TEST STUFF */ + +/* glyphs.c */ + + + diff --git a/src/hack.c b/src/hack.c index 79fe7408c..4dfca4006 100644 --- a/src/hack.c +++ b/src/hack.c @@ -1,80 +1,115 @@ -/* NetHack 3.6 hack.c $NHDT-Date: 1576638500 2019/12/18 03:08:20 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.220 $ */ +/* NetHack 5.0 hack.c $NHDT-Date: 1763708572 2025/11/20 23:02:52 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.494 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Derek S. Ray, 2015. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" +#include "extern.h" /* #define DEBUG */ /* uncomment for debugging */ -STATIC_DCL void NDECL(maybe_wail); -STATIC_DCL int NDECL(moverock); -STATIC_DCL int FDECL(still_chewing, (XCHAR_P, XCHAR_P)); -STATIC_DCL void NDECL(dosinkfall); -STATIC_DCL boolean FDECL(findtravelpath, (int)); -STATIC_DCL boolean FDECL(trapmove, (int, int, struct trap *)); -STATIC_DCL struct monst *FDECL(monstinroom, (struct permonst *, int)); -STATIC_DCL boolean FDECL(doorless_door, (int, int)); -STATIC_DCL void FDECL(move_update, (BOOLEAN_P)); -STATIC_DCL void FDECL(maybe_smudge_engr, (int, int, int, int)); -STATIC_DCL void NDECL(domove_core); - -#define IS_SHOP(x) (rooms[x].rtype >= SHOPBASE) +staticfn boolean could_move_onto_boulder(coordxy, coordxy); +staticfn void dopush(coordxy, coordxy, coordxy, coordxy, struct obj *, + boolean); +staticfn void cannot_push_msg(struct obj *, coordxy, coordxy); +staticfn int cannot_push(struct obj *, coordxy, coordxy); +staticfn void rock_disappear_msg(struct obj *); +staticfn void moverock_done(coordxy, coordxy); +staticfn int moverock(void); +staticfn int moverock_core(coordxy, coordxy); +staticfn void dosinkfall(void); +staticfn boolean findtravelpath(int); +staticfn boolean trapmove(coordxy, coordxy, struct trap *); +staticfn int QSORTCALLBACK notice_mons_cmp(const genericptr, + const genericptr) NONNULLPTRS; +staticfn schar u_simple_floortyp(coordxy, coordxy); +staticfn boolean swim_move_danger(coordxy, coordxy); +staticfn boolean domove_bump_mon(struct monst *, int) NONNULLARG1; +staticfn boolean domove_attackmon_at(struct monst *, coordxy, coordxy, + boolean *) NONNULLPTRS; +staticfn boolean domove_fight_ironbars(coordxy, coordxy); +staticfn boolean domove_fight_web(coordxy, coordxy); +staticfn boolean domove_swap_with_pet(struct monst *, + coordxy, coordxy) NONNULLARG1; +staticfn boolean domove_fight_empty(coordxy, coordxy); +staticfn boolean air_turbulence(void); +staticfn void slippery_ice_fumbling(void); +staticfn boolean impaired_movement(coordxy *, coordxy *) NONNULLPTRS; +staticfn boolean avoid_moving_on_trap(coordxy, coordxy, boolean); +staticfn boolean avoid_moving_on_liquid(coordxy, coordxy, boolean); +staticfn boolean avoid_running_into_trap_or_liquid(coordxy, coordxy); +staticfn boolean avoid_trap_andor_region(coordxy, coordxy); +staticfn boolean move_out_of_bounds(coordxy, coordxy); +staticfn boolean carrying_too_much(void); +staticfn boolean escape_from_sticky_mon(coordxy, coordxy); +staticfn void domove_core(void); +staticfn void maybe_smudge_engr(coordxy, coordxy, coordxy, coordxy); +staticfn struct monst *monstinroom(struct permonst *, int) NONNULLARG1; +staticfn boolean furniture_present(int, int); +staticfn void move_update(boolean); +staticfn int pickup_checks(void); +staticfn void maybe_wail(void); +staticfn boolean water_turbulence(coordxy *, coordxy *); +staticfn int QSORTCALLBACK cmp_weights(const void *, const void *); + +#define IS_SHOP(x) (svr.rooms[x].rtype >= SHOPBASE) + +/* XXX: if more sources of water walking than just boots are added, + cause_known(insight.c) should be externified and used for this */ +#define Known_wwalking \ + (uarmf && uarmf->otyp == WATER_WALKING_BOOTS \ + && objects[WATER_WALKING_BOOTS].oc_name_known \ + && !u.usteed) +#define Known_lwalking \ + (Known_wwalking && Fire_resistance \ + && uarmf->oerodeproof && uarmf->rknown) /* mode values for findtravelpath() */ #define TRAVP_TRAVEL 0 #define TRAVP_GUESS 1 #define TRAVP_VALID 2 -static anything tmp_anything; - anything * -uint_to_any(ui) -unsigned ui; +uint_to_any(unsigned ui) { - tmp_anything = zeroany; - tmp_anything.a_uint = ui; - return &tmp_anything; + gt.tmp_anything = cg.zeroany; + gt.tmp_anything.a_uint = ui; + return >.tmp_anything; } anything * -long_to_any(lng) -long lng; +long_to_any(long lng) { - tmp_anything = zeroany; - tmp_anything.a_long = lng; - return &tmp_anything; + gt.tmp_anything = cg.zeroany; + gt.tmp_anything.a_long = lng; + return >.tmp_anything; } anything * -monst_to_any(mtmp) -struct monst *mtmp; +monst_to_any(struct monst *mtmp) { - tmp_anything = zeroany; - tmp_anything.a_monst = mtmp; - return &tmp_anything; + gt.tmp_anything = cg.zeroany; + gt.tmp_anything.a_monst = mtmp; + return >.tmp_anything; } anything * -obj_to_any(obj) -struct obj *obj; +obj_to_any(struct obj *obj) { - tmp_anything = zeroany; - tmp_anything.a_obj = obj; - return &tmp_anything; + gt.tmp_anything = cg.zeroany; + gt.tmp_anything.a_obj = obj; + return >.tmp_anything; } boolean -revive_nasty(x, y, msg) -int x, y; -const char *msg; +revive_nasty(coordxy x, coordxy y, const char *msg) { - register struct obj *otmp, *otmp2; + struct obj *otmp = 0, *otmp2 = 0; struct monst *mtmp; coord cc; boolean revived = FALSE; - for (otmp = level.objects[x][y]; otmp; otmp = otmp2) { + for (otmp = svl.level.objects[x][y]; otmp; otmp = otmp2) { otmp2 = otmp->nexthere; if (otmp->otyp == CORPSE && (is_rider(&mons[otmp->corpsenm]) @@ -101,42 +136,307 @@ const char *msg; return revived; } -STATIC_OVL int -moverock() +#define squeezeablylightinvent() (!gi.invent \ + || inv_weight() <= (WT_SQUEEZABLE_INV * -1)) + +/* can hero move onto a spot containing one or more boulders? + used for m and travel and during boulder push failure */ +staticfn boolean +could_move_onto_boulder(coordxy sx, coordxy sy) +{ + /* can if able to phaze through rock (must be poly'd, so not riding) */ + if (Passes_walls) + return TRUE; + /* can't when riding */ + if (u.usteed) + return FALSE; + /* can if a giant, unless doing so allows hero to pass into a + diagonal squeeze at the same time */ + if (throws_rocks(gy.youmonst.data)) + return (!u.dx || !u.dy || !(IS_OBSTRUCTED(levl[u.ux][sy].typ) + && IS_OBSTRUCTED(levl[sx][u.uy].typ))); + /* can if tiny (implies carrying very little else couldn't move at all) */ + if (verysmall(gy.youmonst.data)) + return TRUE; + /* can squeeze to spot if carrying extremely little, otherwise can't */ + return squeezeablylightinvent(); +} + +staticfn void +dopush( + coordxy sx, + coordxy sy, + coordxy rx, + coordxy ry, + struct obj *otmp, + boolean costly) +{ + struct monst *shkp; + + { + const char *what; + boolean givemesg, easypush; + /* give boulder pushing feedback if this is a different + boulder than the last one pushed or if it's been at + least 2 turns since we last pushed this boulder; + unlike with Norep(), intervening messages don't cause + it to repeat, only doing something else in the meantime */ + if (otmp->o_id != gb.bldrpush_oid) { + gb.bldrpushtime = svm.moves + 1L; + gb.bldrpush_oid = otmp->o_id; + } + givemesg = (svm.moves > gb.bldrpushtime + 2L + || svm.moves < gb.bldrpushtime); + what = givemesg ? the(xname(otmp)) : 0; + if (!u.usteed) { + easypush = throws_rocks(gy.youmonst.data); + if (givemesg) + pline("With %s effort you move %s.", + easypush ? "little" : "great", what); + if (!easypush) + exercise(A_STR, TRUE); + } else { + if (givemesg) + pline("%s moves %s.", YMonnam(u.usteed), what); + } + gb.bldrpushtime = svm.moves; + } + + /* Move the boulder *after* the message. */ + if (glyph_is_invisible(levl[rx][ry].glyph)) + unmap_object(rx, ry); + otmp->next_boulder = 0; + movobj(otmp, rx, ry); /* does newsym(rx,ry) */ + if (Blind) { + feel_location(rx, ry); + feel_location(sx, sy); + } else { + newsym(sx, sy); + } + /* maybe adjust bill if boulder was pushed across shop boundary; + normally otmp->unpaid would not apply because otmp isn't in + hero's inventory, but addtobill() sets it and subfrombill() + clears it */ + if (costly && !costly_spot(rx, ry)) { + /* pushing from inside shop to its boundary (or free spot) */ + addtobill(otmp, FALSE, FALSE, FALSE); + } else if (!costly && costly_spot(rx, ry) && otmp->unpaid + && ((shkp = shop_keeper(*in_rooms(rx, ry, SHOPBASE))) + != 0) + && onshopbill(otmp, shkp, TRUE)) { + /* this can happen if hero pushes boulder from farther inside + shop into shop's free spot (which will add it to the bill), + then teleports or Passes_walls to doorway (without exiting + the shop), and then pushes the boulder from the free spot + back into the shop; it's contingent upon the shopkeeper not + "muttering an incantation" to fracture the boulder while it + is unpaid at the free spot */ + subfrombill(otmp, shkp); + } else if (otmp->unpaid + && (shkp = find_objowner(otmp, sx, sy)) != 0 + && !strchr(in_rooms(rx, ry, SHOPBASE), + ESHK(shkp)->shoproom)) { + /* once the boulder is fully out of the shop, so that it's + * impossible to change your mind and push it back in without + * leaving and triggering Kops, switch it to stolen_value */ + stolen_value(otmp, sx, sy, TRUE, FALSE); + } +} + +staticfn void +cannot_push_msg(struct obj *otmp, coordxy sx, coordxy sy) +{ + const char *what; + + what = the(xname(otmp)); + if (u.usteed) + pline("%s tries to move %s, but cannot.", + YMonnam(u.usteed), what); + else + You("try to move %s, but in vain.", what); + if (Blind) + feel_location(sx, sy); +} + +staticfn int +cannot_push(struct obj *otmp, coordxy sx, coordxy sy) +{ + if (throws_rocks(gy.youmonst.data)) { + boolean + canpickup = (!Sokoban + /* similar exception as in can_lift(): + when poly'd into a giant, you can + pick up a boulder if you have a free + slot or into the overflow ('#') slot + unless already carrying at least one */ + && (inv_cnt(FALSE) < invlet_basic + || !carrying(BOULDER))), + willpickup = (canpickup + && (flags.pickup && !svc.context.nopick) + && autopick_testobj(otmp, TRUE)); + + if (u.usteed && P_SKILL(P_RIDING) < P_BASIC) { + You("aren't skilled enough to %s %s from %s.", + willpickup ? "pick up" : "push aside", + the(xname(otmp)), y_monnam(u.usteed)); + } else { + /* + * will pick up: you easily pick it up + * can but won't: you maneuver over it and could pick it up + * can't pick up: you maneuver over it (possibly followed + * by feedback from failed auto-pickup attempt) + */ + pline("However, you %s%s.", + willpickup ? "easily pick it up" + : "maneuver over it", + (canpickup && !willpickup) + ? " and could pick it up" + : ""); + /* similar to dropping everything and squeezing onto + a Sokoban boulder's spot, moving to same breaks the + Sokoban rules because on next step you could go + past it without pushing it to plug a pit or hole */ + sokoban_guilt(); + } + return 0; + } + + if (could_move_onto_boulder(sx, sy)) { + pline( + "However, you can squeeze yourself into a small opening."); + sokoban_guilt(); + return 0; + } else { + return -1; + } +} + +staticfn void +rock_disappear_msg(struct obj *otmp) +{ + if (u.usteed) + pline("%s pushes %s and suddenly it disappears!", + YMonnam(u.usteed), the(xname(otmp))); + else + You("push %s and suddenly it disappears!", + the(xname(otmp))); + +} + +staticfn void +moverock_done(coordxy sx, coordxy sy) { - register xchar rx, ry, sx, sy; - register struct obj *otmp; - register struct trap *ttmp; - register struct monst *mtmp; + struct obj *otmp; + for (otmp = svl.level.objects[sx][sy]; otmp; otmp = otmp->nexthere) + if (otmp->otyp == BOULDER) + otmp->next_boulder = 0; /* resume normal xname() for this obj */ +} + +staticfn int +moverock(void) +{ + coordxy sx, sy; + int ret; sx = u.ux + u.dx, sy = u.uy + u.dy; /* boulder starting position */ + ret = moverock_core(sx, sy); + moverock_done(sx, sy); + return ret; +} + +staticfn int +moverock_core(coordxy sx, coordxy sy) +{ + coordxy rx, ry; + struct obj *otmp; + struct trap *ttmp; + struct monst *mtmp; + boolean costly, firstboulder = TRUE; + while ((otmp = sobj_at(BOULDER, sx, sy)) != 0) { + + if (Blind && glyph_to_obj(glyph_at(sx, sy)) != BOULDER) { + pline("That feels like a boulder."); + map_object(otmp, TRUE); + nomul(0); + return -1; + } + + /* when otmp->next_boulder is 1, xname() will format it as + "next boulder" instead of just "boulder"; affects + boulder_hits_pool()'s messages as well as messages below */ + otmp->next_boulder = firstboulder ? 0 : 1; + /* FIXME? 'firstboulder' should be reset to True if this boulder + isn't the first and the previous one is named differently from + this one. Probably not worth bothering with... */ + firstboulder = FALSE; + /* make sure that this boulder is visible as the top object */ - if (otmp != level.objects[sx][sy]) + if (otmp != svl.level.objects[sx][sy]) movobj(otmp, sx, sy); rx = u.ux + 2 * u.dx; /* boulder destination position */ ry = u.uy + 2 * u.dy; nomul(0); + + /* using m towards an adjacent boulder steps over/onto it if + poly'd into a giant or squeezes under/beside it if small/light + enough but is a no-op in other circumstances unless move attempt + reveals an unseen boulder or lack of remembered, unseen monster */ + if (svc.context.nopick) { + int oldglyph = glyph_at(sx, sy); /* before feel_location() */ + int res; + + feel_location(sx, sy); /* same for all 3 if/else-if/else cases */ + if (throws_rocks(gy.youmonst.data)) { + /* player has used 'm' to move, so step to boulder's + spot without pushing it; hero is poly'd into a giant, + so exotic forms of locomotion are out, but might be + levitating (ring, potion, spell) or flying (amulet) */ + You("%s over a boulder here.", u_locomotion("step")); + /* ["over" seems weird on air level but what else to say?] */ + sokoban_guilt(); + res = 0; /* move to */ + } else if (could_move_onto_boulder(sx, sy)) { + You("squeeze yourself %s the boulder.", + Flying ? "over" : "against"); + sokoban_guilt(); + res = 0; /* move to */ + } else { + There("is a boulder in your way."); + /* use a move if hero learns something; see test_move() for + how/why 'context.door_opened' is being dragged into this */ + if (glyph_at(sx, sy) != oldglyph) + svc.context.door_opened = svc.context.move = TRUE; + res = -1; /* don't move to , so no soko guilt */ + } + return res; + } if (Levitation || Is_airlevel(&u.uz)) { + /* FIXME? behavior in an air bubble on the water level should + be similar to being on the air level; both cases probably + ought to let push attempt proceed when flying (which implies + not levitating) */ if (Blind) feel_location(sx, sy); You("don't have enough leverage to push %s.", the(xname(otmp))); /* Give them a chance to climb over it? */ return -1; } - if (verysmall(youmonst.data) && !u.usteed) { + if (verysmall(gy.youmonst.data) && !u.usteed) { if (Blind) feel_location(sx, sy); pline("You're too small to push that %s.", xname(otmp)); - goto cannot_push; + return cannot_push(otmp, sx, sy); } - if (isok(rx, ry) && !IS_ROCK(levl[rx][ry].typ) + if (isok(rx, ry) && !IS_OBSTRUCTED(levl[rx][ry].typ) && levl[rx][ry].typ != IRONBARS && (!IS_DOOR(levl[rx][ry].typ) || !(u.dx && u.dy) || doorless_door(rx, ry)) && !sobj_at(BOULDER, rx, ry)) { ttmp = t_at(rx, ry); mtmp = m_at(rx, ry); + costly = (costly_spot(sx, sy) + && shop_keeper(*in_rooms(sx, sy, SHOPBASE))); /* KMH -- Sokoban doesn't let you push boulders diagonally */ if (Sokoban && u.dx && u.dy) { @@ -144,15 +444,16 @@ moverock() feel_location(sx, sy); pline("%s won't roll diagonally on this %s.", The(xname(otmp)), surface(sx, sy)); - goto cannot_push; + return cannot_push(otmp, sx, sy); } - if (revive_nasty(rx, ry, "You sense movement on the other side.")) + if (revive_nasty(rx, ry, + "You sense movement on the other side.")) { return -1; + } if (mtmp && !noncorporeal(mtmp->data) - && (!mtmp->mtrapped - || !(ttmp && is_pit(ttmp->ttyp)))) { + && (!mtmp->mtrapped || !(ttmp && is_pit(ttmp->ttyp)))) { boolean deliver_part1 = FALSE; if (Blind) @@ -161,6 +462,7 @@ moverock() pline("There's %s on the other side.", a_monnam(mtmp)); deliver_part1 = TRUE; } else { + Soundeffect(se_monster_behind_boulder, 50); You_hear("a monster behind %s.", the(xname(otmp))); if (!Deaf) deliver_part1 = TRUE; @@ -172,20 +474,29 @@ moverock() Strcpy(you_or_steed, u.usteed ? y_monnam(u.usteed) : "you"); pline("%s%s cannot move %s.", - deliver_part1 - ? "Perhaps that's why " - : "", - deliver_part1 - ? you_or_steed - : upstart(you_or_steed), - deliver_part1 - ? "it" - : the(xname(otmp))); + deliver_part1 ? "Perhaps that's why " : "", + deliver_part1 ? you_or_steed + : upstart(you_or_steed), + deliver_part1 ? "it" : the(xname(otmp))); } - goto cannot_push; + return cannot_push(otmp, sx, sy); + } + + if (closed_door(rx, ry)) { + cannot_push_msg(otmp, sx, sy); + return cannot_push(otmp, sx, sy); } + /* at this point the boulder should be able to move (though + potentially into something like a trap, pool, or lava) */ + + /* rumbling disturbs buried zombies */ + disturb_buried_zombies(sx, sy); + if (ttmp) { + int newlev = 0; /* lint suppression */ + d_level dest; + /* if a trap operates on the boulder, don't attempt to move any others at this location; return -1 if another boulder is in hero's way, or 0 if he @@ -196,7 +507,16 @@ moverock() obj_extract_self(otmp); place_object(otmp, rx, ry); newsym(sx, sy); - pline("KAABLAMM!!! %s %s land mine.", + pline("%s! %s %s land mine.", + /* "kablam" is a variation of "ka-boom" or + "kablooey", rather cartoonish descriptions + of the sound of an explosion, but give it + even when deaf if hero sees the explosion */ + (!Deaf || !Blind) ? "KAABLAMM!!" + /* use an alternate exclamation when feeling + the floor/ground/whatever shake (or maybe + a weak shockwave if levitating or flying) */ + : "Gadzooks", Tobjnam(otmp, "trigger"), ttmp->madeby_u ? "your" : "a"); blow_up_landmine(ttmp); @@ -214,7 +534,7 @@ moverock() the pit will temporarily be seen even if this is one among multiple boulders */ if (!Blind) - viz_array[ry][rx] |= IN_SIGHT; + gv.viz_array[ry][rx] |= IN_SIGHT; if (!flooreffects(otmp, rx, ry, "fall")) { place_object(otmp, rx, ry); } @@ -223,6 +543,7 @@ moverock() return sobj_at(BOULDER, sx, sy) ? -1 : 0; case HOLE: case TRAPDOOR: + Soundeffect(se_kerplunk_boulder_gone, 40); if (Blind) pline("Kerplunk! You no longer feel %s.", the(xname(otmp))); @@ -236,7 +557,7 @@ moverock() (ttmp->ttyp == TRAPDOOR) ? "trap door" : "hole", surface(rx, ry)); deltrap(ttmp); - delobj(otmp); + useupf(otmp, 1L); bury_objs(rx, ry); levl[rx][ry].wall_info &= ~W_NONDIGGABLE; levl[rx][ry].candig = 1; @@ -244,25 +565,24 @@ moverock() newsym(rx, ry); return sobj_at(BOULDER, sx, sy) ? -1 : 0; case LEVEL_TELEP: - case TELEP_TRAP: { - int newlev = 0; /* lint suppression */ - d_level dest; - - if (ttmp->ttyp == LEVEL_TELEP) { - newlev = random_teleport_level(); - if (newlev == depth(&u.uz) || In_endgame(&u.uz)) - /* trap didn't work; skip "disappears" message */ - goto dopush; + /* 20% chance of picking current level; 100% chance for + that if in single-level branch (Knox) or in endgame */ + newlev = random_teleport_level(); + /* if trap doesn't work, skip "disappears" message */ + if (newlev == depth(&u.uz)) { + dopush(sx, sy, rx, ry, otmp, costly); + continue; } - if (u.usteed) - pline("%s pushes %s and suddenly it disappears!", - upstart(y_monnam(u.usteed)), the(xname(otmp))); - else - You("push %s and suddenly it disappears!", - the(xname(otmp))); + FALLTHROUGH; + /*FALLTHRU*/ + case TELEP_TRAP: + rock_disappear_msg(otmp); + otmp->next_boulder = 0; /* reset before moving it */ if (ttmp->ttyp == TELEP_TRAP) { (void) rloco(otmp); } else { + if (costly) + stolen_value(otmp, rx, ry, !ttmp->tseen, FALSE); obj_extract_self(otmp); add_to_migration(otmp); get_level(&dest, newlev); @@ -272,16 +592,34 @@ moverock() } seetrap(ttmp); return sobj_at(BOULDER, sx, sy) ? -1 : 0; + case ROLLING_BOULDER_TRAP: + { + int tox = rx; + int toy = ry; + /* the boulder continues until it reaches one of + the trap's launch spots or hits a wall / out-of-bounds */ + while (isok(tox + u.dx, toy + u.dy)) { + tox += u.dx; + toy += u.dy; + if (tox == ttmp->launch.x && toy == ttmp->launch.y) + break; + if (tox == ttmp->launch2.x && toy == ttmp->launch2.y) + break; + } + pline("%s away from you!", + Tobjnam(otmp, "suddenly roll")); + feeltrap(ttmp); + launch_obj(BOULDER, sx, sy, tox, toy, ROLL | LAUNCH_KNOWN); + return sobj_at(BOULDER, sx, sy) ? -1 : 0; } default: break; /* boulder not affected by this trap */ } } - if (closed_door(rx, ry)) - goto nopushmsg; if (boulder_hits_pool(otmp, rx, ry, TRUE)) continue; + /* * Re-link at top of fobj chain so that pile order is preserved * when level is restored. @@ -290,91 +628,10 @@ moverock() remove_object(otmp); place_object(otmp, otmp->ox, otmp->oy); } - - { -#ifdef LINT /* static long lastmovetime; */ - long lastmovetime; - lastmovetime = 0; -#else - /* note: reset to zero after save/restore cycle */ - static NEARDATA long lastmovetime; -#endif - dopush: - if (!u.usteed) { - if (moves > lastmovetime + 2 || moves < lastmovetime) - pline("With %s effort you move %s.", - throws_rocks(youmonst.data) ? "little" - : "great", - the(xname(otmp))); - exercise(A_STR, TRUE); - } else - pline("%s moves %s.", upstart(y_monnam(u.usteed)), - the(xname(otmp))); - lastmovetime = moves; - } - - /* Move the boulder *after* the message. */ - if (glyph_is_invisible(levl[rx][ry].glyph)) - unmap_object(rx, ry); - movobj(otmp, rx, ry); /* does newsym(rx,ry) */ - if (Blind) { - feel_location(rx, ry); - feel_location(sx, sy); - } else { - newsym(sx, sy); - } + dopush(sx, sy, rx, ry, otmp, costly); } else { - nopushmsg: - if (u.usteed) - pline("%s tries to move %s, but cannot.", - upstart(y_monnam(u.usteed)), the(xname(otmp))); - else - You("try to move %s, but in vain.", the(xname(otmp))); - if (Blind) - feel_location(sx, sy); - cannot_push: - if (throws_rocks(youmonst.data)) { - boolean - canpickup = (!Sokoban - /* similar exception as in can_lift(): - when poly'd into a giant, you can - pick up a boulder if you have a free - slot or into the overflow ('#') slot - unless already carrying at least one */ - && (inv_cnt(FALSE) < 52 || !carrying(BOULDER))), - willpickup = (canpickup && autopick_testobj(otmp, TRUE)); - - if (u.usteed && P_SKILL(P_RIDING) < P_BASIC) { - You("aren't skilled enough to %s %s from %s.", - willpickup ? "pick up" : "push aside", - the(xname(otmp)), y_monnam(u.usteed)); - } else { - /* - * willpickup: you easily pick it up - * canpickup: you could easily pick it up - * otherwise: you easily push it aside - */ - pline("However, you %seasily %s.", - (willpickup || !canpickup) ? "" : "could ", - (willpickup || canpickup) ? "pick it up" - : "push it aside"); - sokoban_guilt(); - break; - } - break; - } - - if (!u.usteed - && (((!invent || inv_weight() <= -850) - && (!u.dx || !u.dy || (IS_ROCK(levl[u.ux][sy].typ) - && IS_ROCK(levl[sx][u.uy].typ)))) - || verysmall(youmonst.data))) { - pline( - "However, you can squeeze yourself into a small opening."); - sokoban_guilt(); - break; - } else - return -1; + cannot_push_msg(otmp, sx, sy); + return cannot_push(otmp, sx, sy); } } return 0; @@ -386,20 +643,19 @@ moverock() * Chew on a wall, door, or boulder. [What about statues?] * Returns TRUE if still eating, FALSE when done. */ -STATIC_OVL int -still_chewing(x, y) -xchar x, y; +int +still_chewing(coordxy x, coordxy y) { struct rm *lev = &levl[x][y]; struct obj *boulder = sobj_at(BOULDER, x, y); const char *digtxt = (char *) 0, *dmgtxt = (char *) 0; - if (context.digging.down) /* not continuing previous dig (w/ pick-axe) */ - (void) memset((genericptr_t) &context.digging, 0, + if (svc.context.digging.down) /* not continuing prev dig (w/ pick-axe) */ + (void) memset((genericptr_t) &svc.context.digging, 0, sizeof (struct dig_info)); if (!boulder - && ((IS_ROCK(lev->typ) && !may_dig(x, y)) + && ((IS_OBSTRUCTED(lev->typ) && !may_dig(x, y)) /* may_dig() checks W_NONDIGGABLE but doesn't handle iron bars */ || (lev->typ == IRONBARS && (lev->wall_info & W_NONDIGGABLE)))) { You("hurt your teeth on the %s.", @@ -410,17 +666,25 @@ xchar x, y; : "hard stone"); nomul(0); return 1; - } else if (context.digging.pos.x != x || context.digging.pos.y != y - || !on_level(&context.digging.level, &u.uz)) { - context.digging.down = FALSE; - context.digging.chew = TRUE; - context.digging.warned = FALSE; - context.digging.pos.x = x; - context.digging.pos.y = y; - assign_level(&context.digging.level, &u.uz); + } else if (lev->typ == IRONBARS + && metallivorous(gy.youmonst.data) && u.uhunger > 1500) { + /* finishing eating via 'morehungry()' doesn't handle choking */ + You("are too full to eat the bars."); + nomul(0); + return 1; + } else if (!svc.context.digging.chew + || svc.context.digging.pos.x != x + || svc.context.digging.pos.y != y + || !on_level(&svc.context.digging.level, &u.uz)) { + svc.context.digging.down = FALSE; + svc.context.digging.chew = TRUE; + svc.context.digging.warned = FALSE; + svc.context.digging.pos.x = x; + svc.context.digging.pos.y = y; + assign_level(&svc.context.digging.level, &u.uz); /* solid rock takes more work & time to dig through */ - context.digging.effort = - (IS_ROCK(lev->typ) && !IS_TREE(lev->typ) ? 30 : 60) + u.udaminc; + svc.context.digging.effort = + (IS_OBSTRUCTED(lev->typ) && !IS_TREE(lev->typ) ? 30 : 60) + u.udaminc; You("start chewing %s %s.", (boulder || IS_TREE(lev->typ) || lev->typ == IRONBARS) ? "on a" @@ -429,33 +693,40 @@ xchar x, y; ? "boulder" : IS_TREE(lev->typ) ? "tree" - : IS_ROCK(lev->typ) + : IS_OBSTRUCTED(lev->typ) ? "rock" : (lev->typ == IRONBARS) ? "bar" : "door"); watch_dig((struct monst *) 0, x, y, FALSE); return 1; - } else if ((context.digging.effort += (30 + u.udaminc)) <= 100) { + } else if ((svc.context.digging.effort += (30 + u.udaminc)) <= 100) { if (flags.verbose) You("%s chewing on the %s.", - context.digging.chew ? "continue" : "begin", + svc.context.digging.chew ? "continue" : "begin", boulder ? "boulder" : IS_TREE(lev->typ) ? "tree" - : IS_ROCK(lev->typ) + : IS_OBSTRUCTED(lev->typ) ? "rock" : (lev->typ == IRONBARS) ? "bars" : "door"); - context.digging.chew = TRUE; + svc.context.digging.chew = TRUE; watch_dig((struct monst *) 0, x, y, FALSE); return 1; } /* Okay, you've chewed through something */ - u.uconduct.food++; + if (!u.uconduct.food++) + livelog_printf(LL_CONDUCT, + "ate for the first time, by chewing through %s", + boulder ? "a boulder" + : IS_TREE(lev->typ) ? "a tree" + : IS_OBSTRUCTED(lev->typ) ? "rock" + : (lev->typ == IRONBARS) ? "iron bars" + : "a door"); u.uhunger += rnd(20); if (boulder) { @@ -469,11 +740,11 @@ xchar x, y; * * [perhaps use does_block() below (from vision.c)] */ - if (IS_ROCK(lev->typ) || closed_door(x, y) + if (IS_OBSTRUCTED(lev->typ) || closed_door(x, y) || sobj_at(BOULDER, x, y)) { block_point(x, y); /* delobj will unblock the point */ /* reset dig state */ - (void) memset((genericptr_t) &context.digging, 0, + (void) memset((genericptr_t) &svc.context.digging, 0, sizeof (struct dig_info)); return 1; } @@ -484,9 +755,9 @@ xchar x, y; dmgtxt = "damage"; } digtxt = "chew a hole in the wall."; - if (level.flags.is_maze_lev) { + if (svl.level.flags.is_maze_lev) { lev->typ = ROOM; - } else if (level.flags.is_cavernous_lev && !in_town(x, y)) { + } else if (svl.level.flags.is_cavernous_lev && !in_town(x, y)) { lev->typ = CORR; } else { lev->typ = DOOR; @@ -496,12 +767,25 @@ xchar x, y; digtxt = "chew through the tree."; lev->typ = ROOM; } else if (lev->typ == IRONBARS) { - digtxt = "eat through the bars."; + if (metallivorous(gy.youmonst.data)) { /* should always be True here */ + /* arbitrary amount; unlike proper eating, nutrition is + bestowed in a lump sum at the end */ + int nut = (int) objects[HEAVY_IRON_BALL].oc_weight; + + /* lesshungry() requires that victual be set up, so skip it; + morehungry() of a negative amount will increase nutrition + without any possibility of choking to death on the meal; + updates hunger state and requests status update if changed */ + morehungry(-nut); + } + digtxt = u_at(x, y) + ? "devour the iron bars." + : "eat through the bars."; dissolve_bars(x, y); } else if (lev->typ == SDOOR) { if (lev->doormask & D_TRAPPED) { lev->doormask = D_NODOOR; - b_trapped("secret door", 0); + b_trapped("secret door", NO_PART); } else { digtxt = "chew through the secret door."; lev->doormask = D_BROKEN; @@ -515,7 +799,7 @@ xchar x, y; } if (lev->doormask & D_TRAPPED) { lev->doormask = D_NODOOR; - b_trapped("door", 0); + b_trapped("door", NO_PART); } else { digtxt = "chew through the door."; lev->doormask = D_BROKEN; @@ -526,35 +810,33 @@ xchar x, y; lev->typ = CORR; } - unblock_point(x, y); /* vision */ + recalc_block_point(x, y); /* vision */ newsym(x, y); if (digtxt) You1(digtxt); /* after newsym */ if (dmgtxt) pay_for_damage(dmgtxt, FALSE); - (void) memset((genericptr_t) &context.digging, 0, + (void) memset((genericptr_t) &svc.context.digging, 0, sizeof (struct dig_info)); return 0; } void -movobj(obj, ox, oy) -register struct obj *obj; -register xchar ox, oy; +movobj(struct obj *obj, coordxy ox, coordxy oy) { /* optimize by leaving on the fobj chain? */ remove_object(obj); + maybe_unhide_at(obj->ox, obj->oy); newsym(obj->ox, obj->oy); place_object(obj, ox, oy); newsym(ox, oy); } -static NEARDATA const char fell_on_sink[] = "fell onto a sink"; - -STATIC_OVL void -dosinkfall() +staticfn void +dosinkfall(void) { - register struct obj *obj; + static const char fell_on_sink[] = "fell onto a sink"; + struct obj *obj; int dmg; boolean lev_boots = (uarmf && uarmf->otyp == LEVITATION_BOOTS), innate_lev = ((HLevitation & (FROMOUTSIDE | FROMFORM)) != 0L), @@ -582,7 +864,7 @@ dosinkfall() losehp(Maybe_Half_Phys(dmg), fell_on_sink, NO_KILLER_PREFIX); exercise(A_DEX, FALSE); selftouch("Falling, you"); - for (obj = level.objects[u.ux][u.uy]; obj; obj = obj->nexthere) + for (obj = svl.level.objects[u.ux][u.uy]; obj; obj = obj->nexthere) if (obj->oclass == WEAPON_CLASS || is_weptool(obj)) { You("fell on %s.", doname(obj)); losehp(Maybe_Half_Phys(rnd(3)), fell_on_sink, @@ -638,8 +920,7 @@ dosinkfall() /* intended to be called only on ROCKs or TREEs */ boolean -may_dig(x, y) -register xchar x, y; +may_dig(coordxy x, coordxy y) { struct rm *lev = &levl[x][y]; @@ -648,20 +929,17 @@ register xchar x, y; } boolean -may_passwall(x, y) -register xchar x, y; +may_passwall(coordxy x, coordxy y) { return (boolean) !(IS_STWALL(levl[x][y].typ) && (levl[x][y].wall_info & W_NONPASSWALL)); } boolean -bad_rock(mdat, x, y) -struct permonst *mdat; -register xchar x, y; +bad_rock(struct permonst *mdat, coordxy x, coordxy y) { return (boolean) ((Sokoban && sobj_at(BOULDER, x, y)) - || (IS_ROCK(levl[x][y].typ) + || (IS_OBSTRUCTED(levl[x][y].typ) && (!tunnels(mdat) || needspick(mdat) || !may_dig(x, y)) && !(passes_walls(mdat) && may_passwall(x, y)))); @@ -672,12 +950,14 @@ register xchar x, y; the reason why: 1: can't fit, 2: possessions won't fit, 3: sokoban returns 0 if we can squeeze through */ int -cant_squeeze_thru(mon) -struct monst *mon; +cant_squeeze_thru(struct monst *mon) { int amt; struct permonst *ptr = mon->data; + if ((mon == &gy.youmonst) ? Passes_walls : passes_walls(ptr)) + return 0; + /* too big? */ if (bigmonst(ptr) && !(amorphous(ptr) || is_whirly(ptr) || noncorporeal(ptr) @@ -685,13 +965,13 @@ struct monst *mon; return 1; /* lugging too much junk? */ - amt = (mon == &youmonst) ? inv_weight() + weight_cap() - : curr_mon_load(mon); - if (amt > 600) + amt = (mon == &gy.youmonst) ? inv_weight() + weight_cap() + : curr_mon_load(mon); + if (amt > WT_TOOMUCH_DIAGONAL) return 2; /* Sokoban restriction applies to hero only */ - if (mon == &youmonst && Sokoban) + if (mon == &gy.youmonst && Sokoban) return 3; /* can squeeze through */ @@ -699,31 +979,36 @@ struct monst *mon; } boolean -invocation_pos(x, y) -xchar x, y; +invocation_pos(coordxy x, coordxy y) { return (boolean) (Invocation_lev(&u.uz) - && x == inv_pos.x && y == inv_pos.y); + && x == svi.inv_pos.x && y == svi.inv_pos.y); } -/* return TRUE if (dx,dy) is an OK place to move - * mode is one of DO_MOVE, TEST_MOVE, TEST_TRAV, or TEST_TRAP - */ +/* return TRUE if (ux+dx,uy+dy) is an OK place to move; + mode is one of DO_MOVE, TEST_MOVE, TEST_TRAV, or TEST_TRAP */ boolean -test_move(ux, uy, dx, dy, mode) -int ux, uy, dx, dy; -int mode; +test_move( + coordxy ux, coordxy uy, + coordxy dx, coordxy dy, /* these are -1|0|+1, not coordinates */ + int mode) { - int x = ux + dx; - int y = uy + dy; - register struct rm *tmpr = &levl[x][y]; - register struct rm *ust; + coordxy x = ux + dx; + coordxy y = uy + dy; + struct rm *tmpr; + struct rm *ust; + + svc.context.door_opened = FALSE; + + if (!isok(x, y)) + return FALSE; + + tmpr = &levl[x][y]; - context.door_opened = FALSE; /* * Check for physical obstacles. First, the place we are going. */ - if (IS_ROCK(tmpr->typ) || tmpr->typ == IRONBARS) { + if (IS_OBSTRUCTED(tmpr->typ) || tmpr->typ == IRONBARS) { if (Blind && mode == DO_MOVE) feel_location(x, y); if (Passes_walls && may_passwall(x, y)) { @@ -734,42 +1019,55 @@ int mode; so we won't get here, hence don't need to worry about "there" being somewhere the player isn't sure of */ if (mode == DO_MOVE) - pline("There is an obstacle there."); + There("is an obstacle there."); return FALSE; } else if (tmpr->typ == IRONBARS) { - if ((dmgtype(youmonst.data, AD_RUST) - || dmgtype(youmonst.data, AD_CORR)) && mode == DO_MOVE + if (mode == DO_MOVE + && (dmgtype(gy.youmonst.data, AD_RUST) + || dmgtype(gy.youmonst.data, AD_CORR) + || metallivorous(gy.youmonst.data)) && still_chewing(x, y)) { return FALSE; } - if (!(Passes_walls || passes_bars(youmonst.data))) { - if (mode == DO_MOVE && iflags.mention_walls) + if (!(Passes_walls || passes_bars(gy.youmonst.data))) { + if (mode == DO_MOVE && flags.mention_walls) You("cannot pass through the bars."); return FALSE; } - } else if (tunnels(youmonst.data) && !needspick(youmonst.data)) { + } else if (tunnels(gy.youmonst.data) + && !needspick(gy.youmonst.data)) { /* Eat the rock. */ if (mode == DO_MOVE && still_chewing(x, y)) return FALSE; - } else if (flags.autodig && !context.run && !context.nopick && uwep - && is_pick(uwep)) { + } else if (flags.autodig && !svc.context.run && !svc.context.nopick + && uwep && is_pick(uwep)) { /* MRKR: Automatic digging when wielding the appropriate tool */ if (mode == DO_MOVE) (void) use_pick_axe2(uwep); return FALSE; } else { if (mode == DO_MOVE) { - if (is_db_wall(x, y)) + if (is_db_wall(x, y)) { pline("That drawbridge is up!"); - /* sokoban restriction stays even after puzzle is solved */ - else if (Passes_walls && !may_passwall(x, y) - && In_sokoban(&u.uz)) + } else if (Passes_walls && !may_passwall(x, y) + && In_sokoban(&u.uz)) { + /* soko restriction stays even after puzzle is solved */ pline_The("Sokoban walls resist your ability."); - else if (iflags.mention_walls) - pline("It's %s.", - (IS_WALL(tmpr->typ) || tmpr->typ == SDOOR) ? "a wall" - : IS_TREE(tmpr->typ) ? "a tree" - : "solid stone"); + } else if (flags.mention_walls) { + char buf[BUFSZ]; + int glyph = back_to_glyph(x, y), + sym = glyph_is_cmap(glyph) ? glyph_to_cmap(glyph) + : -1; + + if (sym == S_stone) + Strcpy(buf, "solid stone"); + else if (sym >= 0) + Strcpy(buf, an(defsyms[sym].explanation)); + else + Sprintf(buf, "impossible [background glyph=%d]", + glyph); + pline_dir(xytodir(dx, dy), "It's %s.", buf); + } } return FALSE; } @@ -779,26 +1077,38 @@ int mode; feel_location(x, y); if (Passes_walls) { ; /* do nothing */ - } else if (can_ooze(&youmonst)) { + } else if (can_ooze(&gy.youmonst)) { if (mode == DO_MOVE) You("ooze under the door."); } else if (Underwater) { if (mode == DO_MOVE) pline("There is an obstacle there."); return FALSE; - } else if (tunnels(youmonst.data) && !needspick(youmonst.data)) { + } else if (tunnels(gy.youmonst.data) + && !needspick(gy.youmonst.data)) { /* Eat the door. */ if (mode == DO_MOVE && still_chewing(x, y)) return FALSE; } else { if (mode == DO_MOVE) { - if (amorphous(youmonst.data)) - You( - "try to ooze under the door, but can't squeeze your possessions through."); - if (flags.autoopen && !context.run && !Confusion - && !Stunned && !Fumbling) { - context.door_opened = context.move = - doopen_indir(x, y); + if (amorphous(gy.youmonst.data)) + You("try to ooze under the door," + " but can't squeeze your possessions through."); + if (flags.autoopen && !svc.context.run + && !Confusion && !Stunned && !Fumbling) { + int tmp = doopen_indir(x, y); + /* if 'autounlock' includes Kick, we might have a + kick at the door queued up after doopen_indir() */ + struct _cmd_queue *cq = cmdq_peek(CQ_CANNED); + + if (tmp == ECMD_OK && cq && cq->typ == CMDQ_EXTCMD + && cq->ec_entry == ext_func_tab_from_func(dokick)) + /* door hasn't been opened, but fake it so that + canned kick will be executed as next command */ + svc.context.door_opened = TRUE; + else + svc.context.door_opened = !closed_door(x, y); + svc.context.move = (ux != u.ux || uy != u.uy); } else if (x == ux || y == uy) { if (Blind || Stunned || ACURR(A_DEX) < 10 || Fumbling) { @@ -809,6 +1119,15 @@ int mode; pline("Ouch! You bump into a door."); exercise(A_DEX, FALSE); } + /* use current move; needed for the "ouch" case + but done for steed case too for consistency; + we haven't opened a door but we're going to + return False and without having 'door_opened' + set, 'move' would get reset by caller */ + svc.context.door_opened = svc.context.move = TRUE; + /* since we've just lied about successfully + moving, we need to manually stop running */ + nomul(0); } else pline("That door is closed."); } @@ -824,17 +1143,17 @@ int mode; if (mode == DO_MOVE) { if (Blind) feel_location(x, y); - if (Underwater || iflags.mention_walls) + if (Underwater || flags.mention_walls) You_cant("move diagonally into an intact doorway."); } return FALSE; } } } - if (dx && dy && bad_rock(youmonst.data, ux, y) - && bad_rock(youmonst.data, x, uy)) { + if (dx && dy && bad_rock(gy.youmonst.data, ux, y) + && bad_rock(gy.youmonst.data, x, uy)) { /* Move at a diagonal. */ - switch (cant_squeeze_thru(&youmonst)) { + switch (cant_squeeze_thru(&gy.youmonst)) { case 3: if (mode == DO_MOVE) You("cannot pass that way."); @@ -853,19 +1172,30 @@ int mode; } else if (dx && dy && worm_cross(ux, uy, x, y)) { /* consecutive long worm segments are at and */ if (mode == DO_MOVE) - pline("%s is in your way.", Monnam(m_at(ux, y))); + pline("%s is in your way.", YMonnam(m_at(ux, y))); return FALSE; } /* Pick travel path that does not require crossing a trap. * Avoid water and lava using the usual running rules. * (but not u.ux/u.uy because findtravelpath walks toward u.ux/u.uy) */ - if (context.run == 8 && (mode != DO_MOVE) - && (x != u.ux || y != u.uy)) { + if (svc.context.run == 8 && (mode != DO_MOVE) && !u_at(x, y)) { struct trap *t = t_at(x, y); - if ((t && t->tseen) - || (!Levitation && !Flying && !is_clinger(youmonst.data) - && is_pool_or_lava(x, y) && levl[x][y].seenv)) + if (t && t->tseen && t->ttyp != VIBRATING_SQUARE) + return (mode == TEST_TRAP); + + /* FIXME: should be using lastseentyp[x][y] rather than seen vector + */ + if ((levl[x][y].seenv && is_pool_or_lava(x, y)) /* known pool/lava */ + && ((IS_WATERWALL(levl[x][y].typ) /* never enter wall of liquid */ + || levl[x][y].typ == LAVAWALL) + /* don't enter pool or lava (must be one of the two to + get here) unless flying or levitating or have known + water-walking for pool or known lava-walking and + already be on/over lava for lava */ + || !(Levitation || Flying + || (is_pool(x, y) ? Known_wwalking + : (Known_lwalking && is_lava(u.ux, u.uy)))))) return (mode == TEST_TRAP); } @@ -878,21 +1208,21 @@ int mode; if (dx && dy && !Passes_walls && IS_DOOR(ust->typ) && (!doorless_door(ux, uy) || block_entry(x, y))) { /* Can't move at a diagonal out of a doorway with door. */ - if (mode == DO_MOVE && iflags.mention_walls) + if (mode == DO_MOVE && flags.mention_walls) You_cant("move diagonally out of an intact doorway."); return FALSE; } if (sobj_at(BOULDER, x, y) && (Sokoban || !Passes_walls)) { - if (!(Blind || Hallucination) && (context.run >= 2) - && mode != TEST_TRAV) { - if (mode == DO_MOVE && iflags.mention_walls) - pline("A boulder blocks your path."); + if (mode != TEST_TRAV && svc.context.run >= 2 + && !(Blind || Hallucination) && !could_move_onto_boulder(x, y)) { + if (mode == DO_MOVE && flags.mention_walls) + pline_dir(xytodir(dx,dy), "A boulder blocks your path."); return FALSE; } if (mode == DO_MOVE) { /* tunneling monsters will chew before pushing */ - if (tunnels(youmonst.data) && !needspick(youmonst.data) + if (tunnels(gy.youmonst.data) && !needspick(gy.youmonst.data) && !Sokoban) { if (still_chewing(x, y)) return FALSE; @@ -908,7 +1238,9 @@ int mode; /* don't pick two boulders in a row, unless there's a way thru */ if (sobj_at(BOULDER, ux, uy) && !Sokoban) { if (!Passes_walls - && !(tunnels(youmonst.data) && !needspick(youmonst.data)) + && !could_move_onto_boulder(ux, uy) + && !(tunnels(gy.youmonst.data) + && !needspick(gy.youmonst.data)) && !carrying(PICK_AXE) && !carrying(DWARVISH_MATTOCK) && !((obj = carrying(WAN_DIGGING)) && !objects[obj->otyp].oc_name_known)) @@ -927,16 +1259,21 @@ int mode; * A shortest path is returned. If guess is TRUE, consider various * inaccessible locations as valid intermediate path points. * Returns TRUE if a path was found. + * gt.travelmap keeps track of map locations we've moved through + * this travel session. It will be cleared once the travel stops. */ -STATIC_OVL boolean -findtravelpath(mode) -int mode; +staticfn boolean +findtravelpath(int mode) { + if (!gt.travelmap) + gt.travelmap = selection_new(); /* if travel to adjacent, reachable location, use normal movement rules */ - if ((mode == TRAVP_TRAVEL || mode == TRAVP_VALID) && context.travel1 - && distmin(u.ux, u.uy, u.tx, u.ty) == 1 - && !(u.ux != u.tx && u.uy != u.ty && NODIAG(u.umonnum))) { - context.run = 0; + if ((mode == TRAVP_TRAVEL || mode == TRAVP_VALID) && svc.context.travel1 + /* was '&& distmin(u.ux, u.uy, u.tx, u.ty) == 1' */ + && next2u(u.tx, u.ty) /* one step away */ + /* handle restricted diagonals */ + && crawl_destination(u.tx, u.ty)) { + end_running(FALSE); if (test_move(u.ux, u.uy, u.tx - u.ux, u.ty - u.uy, TEST_MOVE)) { if (mode == TRAVP_TRAVEL) { u.dx = u.tx - u.ux; @@ -947,13 +1284,13 @@ int mode; return TRUE; } if (mode == TRAVP_TRAVEL) - context.run = 8; + svc.context.run = 8; } if (u.tx != u.ux || u.ty != u.uy) { - xchar travel[COLNO][ROWNO]; - xchar travelstepx[2][COLNO * ROWNO]; - xchar travelstepy[2][COLNO * ROWNO]; - xchar tx, ty, ux, uy; + coordxy travel[COLNO][ROWNO]; + coordxy travelstepx[2][COLNO * ROWNO]; + coordxy travelstepy[2][COLNO * ROWNO]; + coordxy tx, ty, ux, uy; int n = 1; /* max offset in travelsteps */ int set = 0; /* two sets current and previous */ int radius = 1; /* search radius */ @@ -985,16 +1322,15 @@ int mode; for (i = 0; i < n; i++) { int dir; - int x = travelstepx[set][i]; - int y = travelstepy[set][i]; - static int ordered[] = { 0, 2, 4, 6, 1, 3, 5, 7 }; + coordxy x = travelstepx[set][i]; + coordxy y = travelstepy[set][i]; /* no diagonal movement for grid bugs */ - int dirmax = NODIAG(u.umonnum) ? 4 : 8; + int dirmax = NODIAG(u.umonnum) ? 4 : N_DIRS; boolean alreadyrepeated = FALSE; for (dir = 0; dir < dirmax; ++dir) { - int nx = x + xdir[ordered[dir]]; - int ny = y + ydir[ordered[dir]]; + coordxy nx = x + xdir[dirs_ord[dir]]; + coordxy ny = y + ydir[dirs_ord[dir]]; /* * When guessing and trying to travel as close as possible @@ -1036,11 +1372,15 @@ int mode; if (!isok(nx, ny) || ((mode == TRAVP_GUESS) && !couldsee(nx, ny))) continue; - if ((!Passes_walls && !can_ooze(&youmonst) - && closed_door(x, y)) || sobj_at(BOULDER, x, y) - || test_move(x, y, nx-x, ny-y, TEST_TRAP)) { - /* closed doors and boulders usually - * cause a delay, so prefer another path */ + if ((!Passes_walls && !can_ooze(&gy.youmonst) + && closed_door(x, y)) + || (sobj_at(BOULDER, x, y) + && !could_move_onto_boulder(x, y)) + || test_move(x, y, nx - x, ny - y, TEST_TRAP)) { + /* closed doors and boulders usually cause a delay, + so prefer another path; however, giants and tiny + creatures can use m to move onto a boulder's + spot without pushing, so allow boulders for them */ if (travel[x][y] > radius - 3) { if (!alreadyrepeated) { travelstepx[1 - set][nn] = x; @@ -1057,15 +1397,24 @@ int mode; || (!Blind && couldsee(nx, ny)))) { if (nx == ux && ny == uy) { if (mode == TRAVP_TRAVEL || mode == TRAVP_VALID) { + boolean visited + = selection_getpoint(x, y, gt.travelmap); u.dx = x - ux; u.dy = y - uy; if (mode == TRAVP_TRAVEL - && x == u.tx && y == u.ty) { + && ((x == u.tx && y == u.ty) + || visited)) { nomul(0); /* reset run so domove run checks work */ - context.run = 8; - iflags.travelcc.x = iflags.travelcc.y = 0; + svc.context.run = 8; + if (visited) + You("stop, unsure which way to go."); + else + iflags.travelcc.x + = iflags.travelcc.y = 0; } + selection_setpoint(u.ux, u.uy, + gt.travelmap, 1); return TRUE; } } else if (!travel[nx][ny]) { @@ -1085,10 +1434,10 @@ int mode; for (i = 0; i < nn; ++i) { tmp_at(travelstepx[1 - set][i], travelstepy[1 - set][i]); } - delay_output(); + nh_delay_output(); if (flags.runmode == RUN_CRAWL) { - delay_output(); - delay_output(); + nh_delay_output(); + nh_delay_output(); } tmp_at(DISP_END, 0); } @@ -1103,35 +1452,40 @@ int mode; if (mode == TRAVP_GUESS) { int px = tx, py = ty; /* pick location */ int dist, nxtdist, d2, nd2; + int ctrav, ptrav = COLNO*ROWNO; dist = distmin(ux, uy, tx, ty); d2 = dist2(ux, uy, tx, ty); for (tx = 1; tx < COLNO; ++tx) for (ty = 0; ty < ROWNO; ++ty) - if (travel[tx][ty]) { + if (couldsee(tx, ty) && (ctrav = travel[tx][ty]) > 0) { nxtdist = distmin(ux, uy, tx, ty); - if (nxtdist == dist && couldsee(tx, ty)) { + if (nxtdist == dist && ctrav < ptrav) { nd2 = dist2(ux, uy, tx, ty); if (nd2 < d2) { /* prefer non-zigzag path */ px = tx; py = ty; d2 = nd2; + ptrav = ctrav; } - } else if (nxtdist < dist && couldsee(tx, ty)) { + } else if (nxtdist < dist) { px = tx; py = ty; dist = nxtdist; d2 = dist2(ux, uy, tx, ty); + ptrav = ctrav; } } - if (px == u.ux && py == u.uy) { + if (u_at(px, py)) { /* no guesses, just go in the general direction */ u.dx = sgn(u.tx - u.ux); u.dy = sgn(u.ty - u.uy); - if (test_move(u.ux, u.uy, u.dx, u.dy, TEST_MOVE)) + if (test_move(u.ux, u.uy, u.dx, u.dy, TEST_MOVE)) { + selection_setpoint(u.ux, u.uy, gt.travelmap, 1); return TRUE; + } goto found; } #ifdef DEBUG @@ -1139,12 +1493,12 @@ int mode; /* Use of warning glyph is arbitrary. It stands out. */ tmp_at(DISP_ALL, warning_to_glyph(2)); tmp_at(px, py); - delay_output(); + nh_delay_output(); if (flags.runmode == RUN_CRAWL) { - delay_output(); - delay_output(); - delay_output(); - delay_output(); + nh_delay_output(); + nh_delay_output(); + nh_delay_output(); + nh_delay_output(); } tmp_at(DISP_END, 0); } @@ -1169,17 +1523,16 @@ int mode; } boolean -is_valid_travelpt(x,y) -int x,y; +is_valid_travelpt(coordxy x, coordxy y) { int tx = u.tx; int ty = u.ty; boolean ret; - int g = glyph_at(x,y); + int glyph = glyph_at(x,y); - if (x == u.ux && y == u.uy) + if (u_at(x, y)) return TRUE; - if (isok(x,y) && glyph_is_cmap(g) && S_stone == glyph_to_cmap(g) + if (isok(x,y) && glyph_is_cmap(glyph) && S_stone == glyph_to_cmap(glyph) && !levl[x][y].seenv) return FALSE; u.tx = x; @@ -1193,10 +1546,10 @@ int x,y; /* try to escape being stuck in a trapped state by walking out of it; return true iff moving should continue to intended destination (all failures and most successful escapes leave hero at original spot) */ -STATIC_OVL boolean -trapmove(x, y, desttrap) -int x, y; /* targetted destination, */ -struct trap *desttrap; /* nonnull if another trap at */ +staticfn boolean +trapmove( + coordxy x, coordxy y, /* targeted destination, */ + struct trap *desttrap) /* nonnull if another trap at */ { boolean anchored = FALSE; const char *predicament, *culprit; @@ -1232,7 +1585,7 @@ struct trap *desttrap; /* nonnull if another trap at */ climb_pit(); break; case TT_WEB: - if (uwep && uwep->oartifact == ART_STING) { + if (u_wield_art(ART_STING)) { /* escape trap but don't move and don't destroy it */ u.utrap = 0; /* caller will call reset_utrap() */ pline("Sting cuts through the web!"); @@ -1326,6 +1679,9 @@ struct trap *desttrap; /* nonnull if another trap at */ buried_ball_to_punishment(); } break; + case TT_NONE: + impossible("trapmove: trapped in nothing?"); + break; default: impossible("trapmove: stuck in unknown trap? (%d)", (int) u.utraptype); @@ -1335,9 +1691,9 @@ struct trap *desttrap; /* nonnull if another trap at */ } boolean -u_rooted() +u_rooted(void) { - if (!youmonst.data->mmove) { + if (!gy.youmonst.data->mmove) { You("are rooted %s.", Levitation || Is_airlevel(&u.uz) || Is_waterlevel(&u.uz) ? "in place" @@ -1349,314 +1705,560 @@ u_rooted() } void -domove() +notice_mon(struct monst *mtmp) { - int ux1 = u.ux, uy1 = u.uy; - - domove_succeeded = 0L; - domove_core(); - /* domove_succeeded is available for making assessments now */ - if ((domove_succeeded & (DOMOVE_RUSH | DOMOVE_WALK)) != 0) - maybe_smudge_engr(ux1, uy1, u.ux, u.uy); - domove_attempting = 0L; + if (a11y.mon_notices && !a11y.mon_notices_blocked) { + boolean spot = canspotmon(mtmp) + && !(is_hider(mtmp->data) + && (mtmp->mundetected + || M_AP_TYPE(mtmp) == M_AP_FURNITURE + || M_AP_TYPE(mtmp) == M_AP_OBJECT)); + + if (spot && !mtmp->mspotted && !DEADMONSTER(mtmp)) { + mtmp->mspotted = TRUE; + set_msg_xy(mtmp->mx, mtmp->my); + You("%s %s.", canseemon(mtmp) ? "see" : "notice", + x_monnam(mtmp, + mtmp->mtame ? ARTICLE_YOUR + : (!has_mgivenname(mtmp) + && !type_is_pname(mtmp->data)) ? ARTICLE_A + : ARTICLE_NONE, + (mtmp->mpeaceful && !mtmp->mtame) ? "peaceful" : 0, + has_mgivenname(mtmp) ? SUPPRESS_SADDLE : 0, FALSE)); + } else if (!spot) { + mtmp->mspotted = FALSE; + } + } } -STATIC_OVL void -domove_core() +staticfn int QSORTCALLBACK +notice_mons_cmp(const genericptr ptr1, const genericptr ptr2) { - register struct monst *mtmp; - register struct rm *tmpr; - register xchar x, y; - struct trap *trap = NULL; - int wtcap; - boolean on_ice; - xchar chainx = 0, chainy = 0, - ballx = 0, bally = 0; /* ball&chain new positions */ - int bc_control = 0; /* control for ball&chain */ - boolean cause_delay = FALSE, /* dragging ball will skip a move */ - u_with_boulder = (sobj_at(BOULDER, u.ux, u.uy) != 0); + const struct monst *m1 = *(const struct monst **) ptr1, + *m2 = *(const struct monst **) ptr2; - if (context.travel) { - if (!findtravelpath(FALSE)) - (void) findtravelpath(TRUE); - context.travel1 = 0; - } + return (distu(m1->mx, m1->my) - distu(m2->mx, m2->my)); +} - if (((wtcap = near_capacity()) >= OVERLOADED - || (wtcap > SLT_ENCUMBER - && (Upolyd ? (u.mh < 5 && u.mh != u.mhmax) - : (u.uhp < 10 && u.uhp != u.uhpmax)))) - && !Is_airlevel(&u.uz)) { - if (wtcap < OVERLOADED) { - You("don't have enough stamina to move."); - exercise(A_CON, FALSE); - } else - You("collapse under your load."); - nomul(0); - return; - } - if (u.uswallow) { - u.dx = u.dy = 0; - u.ux = x = u.ustuck->mx; - u.uy = y = u.ustuck->my; - mtmp = u.ustuck; - } else { - if (Is_airlevel(&u.uz) && rn2(4) && !Levitation && !Flying) { - switch (rn2(3)) { - case 0: - You("tumble in place."); - exercise(A_DEX, FALSE); - break; - case 1: - You_cant("control your movements very well."); - break; - case 2: - pline("It's hard to walk in thin air."); - exercise(A_DEX, TRUE); - break; - } - return; - } +void +notice_all_mons(boolean reset) +{ + if (a11y.mon_notices && !a11y.mon_notices_blocked) { + struct monst *mtmp; + struct monst **arr = NULL; + int j, i = 0, cnt = 0; - /* check slippery ice */ - on_ice = !Levitation && is_ice(u.ux, u.uy); - if (on_ice) { - static int skates = 0; - - if (!skates) - skates = find_skates(); - if ((uarmf && uarmf->otyp == skates) || resists_cold(&youmonst) - || Flying || is_floater(youmonst.data) - || is_clinger(youmonst.data) || is_whirly(youmonst.data)) { - on_ice = FALSE; - } else if (!rn2(Cold_resistance ? 3 : 2)) { - HFumbling |= FROMOUTSIDE; - HFumbling &= ~TIMEOUT; - HFumbling += 1; /* slip on next move */ - } + for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { + if (DEADMONSTER(mtmp)) + continue; + if (canspotmon(mtmp)) + cnt++; + else if (reset) + mtmp->mspotted = FALSE; } - if (!on_ice && (HFumbling & FROMOUTSIDE)) - HFumbling &= ~FROMOUTSIDE; + if (!cnt) + return; - x = u.ux + u.dx; - y = u.uy + u.dy; - if (Stunned || (Confusion && !rn2(5))) { - register int tries = 0; + arr = (struct monst **) alloc(cnt * sizeof (struct monst *)); - do { - if (tries++ > 50) { - nomul(0); - return; - } - confdir(); - x = u.ux + u.dx; - y = u.uy + u.dy; - } while (!isok(x, y) || bad_rock(youmonst.data, x, y)); - } - /* turbulence might alter your actual destination */ - if (u.uinwater) { - water_friction(); - if (!u.dx && !u.dy) { - nomul(0); - return; - } - x = u.ux + u.dx; - y = u.uy + u.dy; - - /* are we trying to move out of water while carrying too much? */ - if (isok(x, y) && !is_pool(x, y) && !Is_waterlevel(&u.uz) - && wtcap > (Swimming ? MOD_ENCUMBER : SLT_ENCUMBER)) { - /* when escaping from drowning you need to be unencumbered - in order to crawl out of water, but when not drowning, - doing so while encumbered is feasible; if in an aquatic - form, stressed or less is allowed; otherwise (magical - breathing), only burdened is allowed */ - You("are carrying too much to climb out of the water."); - nomul(0); - return; - } - } - if (!isok(x, y)) { - if (iflags.mention_walls) { - int dx = u.dx, dy = u.dy; - - if (dx && dy) { /* diagonal */ - /* only as far as possible diagonally if in very - corner; otherwise just report whichever of the - cardinal directions has reached its limit */ - if (isok(x, u.uy)) - dx = 0; - else if (isok(u.ux, y)) - dy = 0; - } - You("have already gone as far %s as possible.", - directionname(xytod(dx, dy))); - } - nomul(0); - return; - } - if (((trap = t_at(x, y)) && trap->tseen) - || (Blind && !Levitation && !Flying && !is_clinger(youmonst.data) - && is_pool_or_lava(x, y) && levl[x][y].seenv)) { - if (context.run >= 2) { - if (iflags.mention_walls) { - if (trap && trap->tseen) { - int tt = what_trap(trap->ttyp, rn2_on_display_rng); - - You("stop in front of %s.", - an(defsyms[trap_to_defsym(tt)].explanation)); - } else if (is_pool_or_lava(x,y) && levl[x][y].seenv) { - You("stop at the edge of the %s.", - hliquid(is_pool(x,y) ? "water" : "lava")); - } - } - nomul(0); - context.move = 0; - return; - } else - nomul(0); + for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { + if (DEADMONSTER(mtmp)) + continue; + if (!canspotmon(mtmp)) + mtmp->mspotted = FALSE; + else if (i < cnt) + arr[i++] = mtmp; } - if (u.ustuck && (x != u.ustuck->mx || y != u.ustuck->my)) { - if (distu(u.ustuck->mx, u.ustuck->my) > 2) { - /* perhaps it fled (or was teleported or ... ) */ - u.ustuck = 0; - } else if (sticks(youmonst.data)) { - /* When polymorphed into a sticking monster, - * u.ustuck means it's stuck to you, not you to it. - */ - You("release %s.", mon_nam(u.ustuck)); - u.ustuck = 0; - } else { - /* If holder is asleep or paralyzed: - * 37.5% chance of getting away, - * 12.5% chance of waking/releasing it; - * otherwise: - * 7.5% chance of getting away. - * [strength ought to be a factor] - * If holder is tame and there is no conflict, - * guaranteed escape. - */ - switch (rn2(!u.ustuck->mcanmove ? 8 : 40)) { - case 0: - case 1: - case 2: - pull_free: - You("pull free from %s.", mon_nam(u.ustuck)); - u.ustuck = 0; - break; - case 3: - if (!u.ustuck->mcanmove) { - /* it's free to move on next turn */ - u.ustuck->mfrozen = 1; - u.ustuck->msleeping = 0; - } - /*FALLTHRU*/ - default: - if (u.ustuck->mtame && !Conflict && !u.ustuck->mconf) - goto pull_free; - You("cannot escape from %s!", mon_nam(u.ustuck)); - nomul(0); - return; - } - } - } + if (i) { + qsort((genericptr_t) arr, (size_t) i, sizeof *arr, + notice_mons_cmp); - mtmp = m_at(x, y); - if (mtmp && !is_safepet(mtmp)) { - /* Don't attack if you're running, and can see it */ - /* It's fine to displace pets, though */ - /* We should never get here if forcefight */ - if (context.run && ((!Blind && mon_visible(mtmp) - && ((M_AP_TYPE(mtmp) != M_AP_FURNITURE - && M_AP_TYPE(mtmp) != M_AP_OBJECT) - || Protection_from_shape_changers)) - || sensemon(mtmp))) { - nomul(0); - context.move = 0; - return; - } + for (j = 0; j < i; j++) + notice_mon(arr[j]); } + + free(arr); } +} - u.ux0 = u.ux; - u.uy0 = u.uy; - bhitpos.x = x; - bhitpos.y = y; - tmpr = &levl[x][y]; +/* maybe disturb buried zombies when an object is dropped or thrown nearby */ +void +impact_disturbs_zombies(struct obj *obj, boolean violent) +{ + /* if object won't make a noticeable impact, let buried zombies rest */ + if (obj->owt < (violent ? 10U : 100U) || is_flimsy(obj)) + return; - /* attack monster */ - if (mtmp) { - /* don't stop travel when displacing pets; if the - displace fails for some reason, attack() in uhitm.c - will stop travel rather than domove */ - if (!is_safepet(mtmp) || context.forcefight) - nomul(0); - /* only attack if we know it's there */ - /* or if we used the 'F' command to fight blindly */ - /* or if it hides_under, in which case we call attack() to print - * the Wait! message. - * This is different from ceiling hiders, who aren't handled in - * attack(). - */ + disturb_buried_zombies(obj->ox, obj->oy); +} - /* If they used a 'm' command, trying to move onto a monster - * prints the below message and wastes a turn. The exception is - * if the monster is unseen and the player doesn't remember an - * invisible monster--then, we fall through to attack() and - * attack_check(), which still wastes a turn, but prints a - * different message and makes the player remember the monster. - */ - if (context.nopick && !context.travel - && (canspotmon(mtmp) || glyph_is_invisible(levl[x][y].glyph))) { - if (M_AP_TYPE(mtmp) && !Protection_from_shape_changers - && !sensemon(mtmp)) - stumble_onto_mimic(mtmp); - else if (mtmp->mpeaceful && !Hallucination) - /* m_monnam(): "dog" or "Fido", no "invisible dog" or "it" */ - pline("Pardon me, %s.", m_monnam(mtmp)); - else - You("move right into %s.", mon_nam(mtmp)); - return; - } - if (context.forcefight || !mtmp->mundetected || sensemon(mtmp) - || ((hides_under(mtmp->data) || mtmp->data->mlet == S_EEL) - && !is_safepet(mtmp))) { - /* try to attack; note that it might evade */ - /* also, we don't attack tame when _safepet_ */ - if (attack(mtmp)) - return; +/* reduce zombification timeout of buried zombies around px, py */ +void +disturb_buried_zombies(coordxy x, coordxy y) +{ + struct obj *otmp; + long t; + + for (otmp = svl.level.buriedobjlist; otmp; otmp = otmp->nobj) { + if (otmp->otyp == CORPSE && otmp->timed + && otmp->ox >= x - 1 && otmp->ox <= x + 1 + && otmp->oy >= y - 1 && otmp->oy <= y + 1 + && (t = peek_timer(ZOMBIFY_MON, obj_to_any(otmp))) > 0) { + t = stop_timer(ZOMBIFY_MON, obj_to_any(otmp)); + (void) start_timer(max(1, (t*2/3)), TIMER_OBJECT, + ZOMBIFY_MON, obj_to_any(otmp)); } } +} - if (context.forcefight && levl[x][y].typ == IRONBARS && uwep) { - struct obj *obj = uwep; +/* return an appropriate locomotion word for hero */ +const char * +u_locomotion(const char *def) +{ + boolean capitalize = (*def == highc(*def)); + + /* regular locomotion() takes a monster type rather than a specific + monster, so can't tell whether it is operating on hero; + its is_flyer() and is_floater() tests wouldn't work on hero except + when hero is polymorphed and not wearing an amulet of flying + or boots/ring/spell of levitation */ + return Levitation ? (capitalize ? "Float" : "float") + : Flying ? (capitalize ? "Fly" : "fly") + : locomotion(gy.youmonst.data, def); +} - if (breaktest(obj)) { - if (obj->quan > 1L) - obj = splitobj(obj, 1L); - else - setuwep((struct obj *)0); - freeinv(obj); - } - hit_bars(&obj, u.ux, u.uy, x, y, TRUE, TRUE); - return; +/* Return a simplified floor solid/liquid state based on hero's state */ +staticfn schar +u_simple_floortyp(coordxy x, coordxy y) +{ + boolean u_in_air = (Levitation || Flying || !grounded(gy.youmonst.data)); + + if (is_waterwall(x, y)) + return WATER; /* wall of water, fly/lev does not matter */ + if (levl[x][y].typ == LAVAWALL) + return LAVAWALL; /* wall of lava, fly/lev does not matter */ + if (!u_in_air) { + if (is_pool(x, y)) + return POOL; + if (is_lava(x, y)) + return LAVAPOOL; } + return ROOM; +} - /* specifying 'F' with no monster wastes a turn */ - if (context.forcefight - /* remembered an 'I' && didn't use a move command */ - || (glyph_is_invisible(levl[x][y].glyph) && !context.nopick)) { - struct obj *boulder = 0; - boolean explo = (Upolyd && attacktype(youmonst.data, AT_EXPL)), - solid = !accessible(x, y); - int glyph = glyph_at(x, y); /* might be monster */ - char buf[BUFSZ]; +/* maybe show a helpful gameplay tip? returns True if tip gets shown */ +boolean +handle_tip(int tip) +{ + if (!flags.tips) + return FALSE; - if (!Underwater) { - boulder = sobj_at(BOULDER, x, y); - /* if a statue is displayed at the target location, - player is attempting to attack it [and boulder + if (tip >= 0 && tip < NUM_TIPS && !(svc.context.tips & (1 << tip))) { + svc.context.tips |= (1 << tip); + /* the "Tip:" prefix is a hint to use of OPTIONS=!tips to suppress */ + switch (tip) { + case TIP_ENHANCE: + pline("(Tip: use the #enhance command to advance them.)"); + break; + case TIP_SWIM: + pline("(Tip: use '%s' prefix to step in if you really want to.)", + visctrl(cmd_from_func(do_reqmenu))); + break; + case TIP_UNTRAP_MON: + pline("(Tip: perhaps #untrap would help?)"); + break; + case TIP_GETPOS: + l_nhcore_call(NHCORE_GETPOS_TIP); + break; + default: + impossible("Unknown tip in handle_tip(%i)", tip); + break; + } + return TRUE; + } + return FALSE; +} + +/* Is it dangerous for hero to move to x,y due to water or lava? */ +staticfn boolean +swim_move_danger(coordxy x, coordxy y) +{ + schar newtyp = u_simple_floortyp(x, y); + boolean liquid_wall = IS_WATERWALL(newtyp) || newtyp == LAVAWALL; + + if (Underwater && (is_pool(x,y) || IS_WATERWALL(newtyp))) + return FALSE; + + if ((newtyp != u_simple_floortyp(u.ux, u.uy)) + && !Stunned && !Confusion && levl[x][y].seenv + && (is_pool(x, y) || is_lava(x, y) || liquid_wall)) { + + /* FIXME: This can be exploited to identify ring of fire resistance + * if the player is wearing it unidentified and has identified + * fireproof boots of water walking and is walking over lava. However, + * this is such a marginal case that it may not be worth fixing. */ + if ((is_pool(x, y) && !Known_wwalking) + /* is_lava(ux,uy): don't move onto/over lava with known + lava-walking because it isn't completely safe, but do + continue to move over lava if already doing so */ + || (is_lava(x, y) && !Known_lwalking && !is_lava(u.ux, u.uy)) + || liquid_wall) { + if (svc.context.nopick) { + /* moving with m-prefix */ + svc.context.tips |= (1 << TIP_SWIM); + return FALSE; + } else if (ParanoidSwim || liquid_wall) { + You("avoid %s into the %s.", + ing_suffix(u_locomotion("step")), + waterbody_name(x, y)); + (void) handle_tip(TIP_SWIM); + return TRUE; + } + } + } + return FALSE; +} + +/* moving with 'm' prefix, bump into a monster? */ +staticfn boolean +domove_bump_mon(struct monst *mtmp, int glyph) +{ + /* If they used a 'm' command, trying to move onto a monster + * prints the below message and wastes a turn. The exception is + * if the monster is unseen and the player doesn't remember an + * invisible monster--then, we fall through to do_attack() and + * attack_check(), which still wastes a turn, but prints a + * different message and makes the player remember the monster. + */ + if (svc.context.nopick && !svc.context.travel + && (canspotmon(mtmp) || glyph_is_invisible(glyph) + || glyph_is_warning(glyph))) { + if (M_AP_TYPE(mtmp) && !Protection_from_shape_changers + && !sensemon(mtmp)) + stumble_onto_mimic(mtmp); + else if (mtmp->mpeaceful && !Hallucination) + /* m_monnam(): "dog" or "Fido", no "invisible dog" or "it" */ + pline("Pardon me, %s.", m_monnam(mtmp)); + else + You("move right into %s.", mon_nam(mtmp)); + return TRUE; + } + return FALSE; +} + +/* hero is moving, do we maybe attack a monster at (x,y)? + returns TRUE if hero movement is used up. + sets displaceu, if hero and monster could swap places instead. +*/ +staticfn boolean +domove_attackmon_at( + struct monst *mtmp, + coordxy x, coordxy y, + boolean *displaceu) +{ + /* assert(mtmp != NULL) */ + /* only attack if we know it's there + * or if we used the 'F' command to fight blindly + * or if it hides_under, in which case we call do_attack() to print + * the Wait! message. + * This is different from ceiling hiders, who aren't handled in + * do_attack(). + */ + if (svc.context.forcefight || !mtmp->mundetected || sensemon(mtmp) + || ((hides_under(mtmp->data) || mtmp->data->mlet == S_EEL) + && !is_safemon(mtmp))) { + /* target monster might decide to switch places with you... */ + *displaceu = (mtmp->data == &mons[PM_DISPLACER_BEAST] && !rn2(2) + && mtmp->mux == u.ux0 && mtmp->muy == u.uy0 + && !helpless(mtmp) + && !mtmp->meating && !mtmp->mtrapped + && !u.utrap && !u.ustuck && !u.usteed + && !(u.dx && u.dy + && (NODIAG(u.umonnum) + || (bad_rock(mtmp->data, x, u.uy0) + && bad_rock(mtmp->data, u.ux0, y)) + || (bad_rock(gy.youmonst.data, u.ux0, y) + && bad_rock(gy.youmonst.data, x, u.uy0)))) + && goodpos(u.ux0, u.uy0, mtmp, GP_ALLOW_U)); + /* if not displacing, try to attack; note that it might evade; + also, we don't attack tame or peaceful when safemon() */ + if (!*displaceu) { + if (do_attack(mtmp)) + return TRUE; + } + } + return FALSE; +} + +/* force-fight iron bars with your weapon? */ +staticfn boolean +domove_fight_ironbars(coordxy x, coordxy y) +{ + if (svc.context.forcefight && levl[x][y].typ == IRONBARS && uwep) { + struct obj *obj = uwep; + unsigned breakflags = (BRK_BY_HERO | BRK_FROM_INV | BRK_MELEE); + + if (breaktest(obj)) { + if (obj->quan > 1L) + obj = splitobj(obj, 1L); + else + setuwep((struct obj *) 0); + freeinv(obj); + breakflags |= BRK_KNOWN2BREAK; + } else { + breakflags |= BRK_KNOWN2NOTBREAK; + } + + hit_bars(&obj, u.ux, u.uy, x, y, breakflags); + return TRUE; + } + return FALSE; +} + +/* force-fight a spider web with your weapon */ +staticfn boolean +domove_fight_web(coordxy x, coordxy y) +{ + struct trap *trap = t_at(x, y); + + if (svc.context.forcefight && trap && trap->ttyp == WEB && trap->tseen) { + int wtype = uwep_skill_type(), + /* minus_2: restricted or unskilled: -1, basic: 0, skilled: 1, + expert: 2, master: 3, grandmaster: 4 */ + wskill_minus_2 = max(P_SKILL(wtype), P_UNSKILLED) - 2, + /* higher value is worse for player; for weaponless, adjust the + chance to succeed rather than maybe make two tries */ + roll = rn2(uwep ? 20 : (45 - 5 * wskill_minus_2)); + + if (uwep && (u_wield_art(ART_STING) + || (uwep->oartifact && attacks(AD_FIRE, uwep)))) { + /* guaranteed success */ + pline("%s %s through the web!", bare_artifactname(uwep), + u_wield_art(ART_STING) ? "cuts" : "burns"); + + /* is_blade() includes daggers (which are classified as PIERCE) + but doesn't include axes and slashing polearms */ + } else if (uwep && !is_blade(uwep) + && (!u.twoweap || !is_blade(uswapwep))) { + char *uwepstr = 0, *scndstr = 0, uwepbuf[BUFSZ], scndbuf[BUFSZ]; + boolean onewep; + + /* when dual wielding, second weapon will only be mentioned + if it has a different type description from primary */ + Strcpy(uwepbuf, weapon_descr(uwep)); + Strcpy(scndbuf, u.twoweap ? weapon_descr(uswapwep) : ""); + onewep = !*scndbuf || !strcmp(uwepbuf, scndbuf); + if (!strcmpi(uwepbuf, "armor") || !strcmpi(uwepbuf, "food") + || !strcmpi(uwepbuf, "venom")) { /* as-is */ + /* non-weapon item wielded, of a type where an() would + result in weird phrasing; dual wield not possible */ + uwepstr = uwepbuf; + } else if (uwep->quan == 1L /* singular */ + /* unless secondary is suppressed due to same type */ + && !(u.twoweap && onewep)) { + uwepstr = an(uwepbuf); + } else { /* plural */ + uwepstr = makeplural(uwepbuf); + } + if (!onewep) { + assert(uswapwep != NULL); + scndstr = (uswapwep->quan == 1L) ? an(scndbuf) + : makeplural(scndbuf); + } + You_cant("cut a web with %s%s%s!", uwepstr, + !onewep ? " or " : "", !onewep ? scndstr : ""); + return TRUE; + + /* weapon is ok; check whether hit is successful */ + } else if (roll > (acurrstr() - 2 /* 1..19 */ + /* for weaponless, 'roll' was adjusted above */ + + (uwep ? uwep->spe + wskill_minus_2 : 0))) { + /* TODO: add failures, maybe make an occupation? */ + You("%s ineffectually at some of the strands.", + uwep ? "hack" : "thrash"); + return TRUE; + + /* hit has succeeded */ + } else { + You("%s through the web.", uwep ? "cut" : "punch"); + /* doesn't break "never hit with a wielded weapon" conduct */ + use_skill(wtype, 1); + } + + deltrap(trap); + newsym(x, y); + return TRUE; + } + return FALSE; +} + +/* maybe swap places with a pet? returns TRUE if swapped places */ +staticfn boolean +domove_swap_with_pet( + struct monst *mtmp, + coordxy x, coordxy y) +{ + struct trap *trap; + /* if it turns out we can't actually move */ + boolean didnt_move = FALSE; + boolean u_with_boulder = (sobj_at(BOULDER, u.ux, u.uy) != 0); + + /* seemimic/newsym should be done before moving hero, otherwise + the display code will draw the hero here before we possibly + cancel the swap below (we can ignore steed mx,my here) */ + u.ux = u.ux0, u.uy = u.uy0; + mtmp->mundetected = 0; + if (M_AP_TYPE(mtmp)) + seemimic(mtmp); + u.ux = mtmp->mx, u.uy = mtmp->my; /* resume swapping positions */ + + trap = mtmp->mtrapped ? t_at(mtmp->mx, mtmp->my) : 0; + if (!trap) + mtmp->mtrapped = 0; + + if (mtmp->mtrapped && is_pit(trap->ttyp) + && sobj_at(BOULDER, trap->tx, trap->ty)) { + /* can't swap places with pet pinned in a pit by a boulder */ + didnt_move = TRUE; + } else if (u.ux0 != x && u.uy0 != y && NODIAG(mtmp->data - mons)) { + /* can't swap places when pet can't move to your spot */ + You("stop. %s can't move diagonally.", YMonnam(mtmp)); + didnt_move = TRUE; + } else if (u_with_boulder + && !(verysmall(mtmp->data) + && (!mtmp->minvent || curr_mon_load(mtmp) <= 600))) { + /* can't swap places when pet won't fit there with the boulder */ + You("stop. %s won't fit into the same spot that you're at.", + YMonnam(mtmp)); + didnt_move = TRUE; + } else if (u.ux0 != x && u.uy0 != y && bad_rock(mtmp->data, x, u.uy0) + && bad_rock(mtmp->data, u.ux0, y) + && (bigmonst(mtmp->data) || (curr_mon_load(mtmp) > 600))) { + /* can't swap places when pet won't fit thru the opening */ + You("stop. %s won't fit through.", YMonnam(mtmp)); + didnt_move = TRUE; + } else if (mtmp->mpeaceful && mtmp->mtrapped) { + /* all mtame are also mpeaceful, so this affects pets too */ + assert(trap != NULL); /* implied by mtrapped */ + const char *what = trapname(trap->ttyp, FALSE), *which = "that "; + char anbuf[10]; + + if (!trap->tseen) { + feeltrap(trap); /* show on map once mtmp is out of the way */ + which = just_an(anbuf, what); /* "a " or "an " */ + } + You("stop. %s can't move out of %s%s.", YMonnam(mtmp), which, what); + (void) handle_tip(TIP_UNTRAP_MON); + didnt_move = TRUE; + } else if (mtmp->mpeaceful + && (!goodpos(u.ux0, u.uy0, mtmp, 0) + || t_at(u.ux0, u.uy0) != NULL + || mundisplaceable(mtmp))) { + /* displacing peaceful into unsafe or trapped space, or trying to + displace quest leader, Oracle, shk, priest, or vault guard */ + You("stop. %s doesn't want to swap places.", YMonnam(mtmp)); + didnt_move = TRUE; + } else { + mtmp->mtrapped = 0; + remove_monster(x, y); + place_monster(mtmp, u.ux0, u.uy0); + newsym(x, y); + newsym(u.ux0, u.uy0); + + You("%s %s.", mtmp->mpeaceful ? "swap places with" : "frighten", + x_monnam(mtmp, + mtmp->mtame ? ARTICLE_YOUR + : (!has_mgivenname(mtmp) + && !type_is_pname(mtmp->data)) ? ARTICLE_THE + : ARTICLE_NONE, + (mtmp->mpeaceful && !mtmp->mtame) ? "peaceful" : 0, + has_mgivenname(mtmp) ? SUPPRESS_SADDLE : 0, FALSE)); + + /* check for displacing it into pools and traps */ + switch (minliquid(mtmp) ? Trap_Killed_Mon + : mintrap(mtmp, NO_TRAP_FLAGS)) { + case Trap_Effect_Finished: + break; + case Trap_Caught_Mon: /* trapped */ + case Trap_Moved_Mon: /* changed levels */ + /* there's already been a trap message, reinforce it */ + abuse_dog(mtmp); + adjalign(-3); + break; + case Trap_Killed_Mon: + /* drowned or died... + * you killed your pet by direct action, so get experience + * and possibly penalties; + * we want the level gain message, if it happens, to occur + * before the guilt message below + */ + { + /* minliquid() and mintrap() call mondead() rather than + killed() so we duplicate some of the latter here */ + int tmp, mndx; + + if (!u.uconduct.killer++) + livelog_printf(LL_CONDUCT, "killed for the first time"); + mndx = monsndx(mtmp->data); + tmp = experience(mtmp, (int) svm.mvitals[mndx].died); + more_experienced(tmp, 0); + newexplevel(); /* will decide if you go up */ + } + /* That's no way to treat a pet! Your god gets angry. + * + * [This has always been pretty iffy. Why does your + * patron deity care at all, let alone enough to get mad?] + */ + if (rn2(4)) { + You_feel("guilty about losing your pet like this."); + u.ugangr++; + adjalign(-15); + } + break; + default: + impossible("that's strange, unknown mintrap result!"); + break; + } + } + return !didnt_move; +} + +/* force-fight (x,y) which doesn't have anything to fight */ +staticfn boolean +domove_fight_empty(coordxy x, coordxy y) +{ + static const char unknown_obstacle[] = "an unknown obstacle"; + boolean off_edge = !isok(x, y); + int glyph = !off_edge ? glyph_at(x, y) : GLYPH_UNEXPLORED; + + if (off_edge) + x = 0, y = 1; /* for forcefight against the edge of the map; make + * sure 'bad' coordinates are within array bounds in + * case a bounds check gets overlooked; avoid <0,0> + * because m_at() might find a vault guard there */ + + /* specifying 'F' with no monster wastes a turn */ + if (svc.context.forcefight + /* remembered an 'I' && didn't use a move command */ + || (glyph_is_invisible(glyph) && !m_at(x, y) + && !svc.context.nopick)) { + struct obj *boulder = 0; + boolean explo = (Upolyd && attacktype(gy.youmonst.data, AT_EXPL)), + solid = (off_edge || (!accessible(x, y) + || IS_FURNITURE(levl[x][y].typ))); + char buf[BUFSZ]; + + if (off_edge) { + /* treat as if solid rock, even on planes' levels */ + Strcpy(buf, unknown_obstacle); + goto futile; + } + + if (!Underwater) { + boulder = sobj_at(BOULDER, x, y); + /* if a statue is displayed at the target location, + player is attempting to attack it [and boulder handling below is suitable for handling that] */ if (glyph_is_statue(glyph) || (Hallucination && glyph_is_monster(glyph))) @@ -1664,86 +2266,596 @@ domove_core() /* force fight at boulder/statue or wall/door while wielding pick: start digging to break the boulder or wall */ - if (context.forcefight + if (svc.context.forcefight /* can we dig? */ && uwep && dig_typ(uwep, x, y) /* should we dig? */ && !glyph_is_invisible(glyph) && !glyph_is_monster(glyph)) { (void) use_pick_axe2(uwep); + return TRUE; + } + } + + /* about to become known empty -- remove 'I' if present */ + unmap_object(x, y); + if (boulder) + map_object(boulder, TRUE); + newsym(x, y); + glyph = glyph_at(x, y); /* might have just changed */ + nhUse(glyph); + + if (boulder) { + Strcpy(buf, ansimpleoname(boulder)); + } else if (Underwater && !is_pool(x, y)) { + /* Underwater, targeting non-water; the map just shows blank + because you don't see remembered terrain while underwater; + although the hero can attack an adjacent monster this way, + assume he can't reach out far enough to distinguish terrain */ + Sprintf(buf, "%s", + (Is_waterlevel(&u.uz) && levl[x][y].typ == AIR) + ? "an air bubble" + : "nothing"); + } else if (solid) { + /* glyph might indicate unseen terrain if hero is blind; + unlike searching, this won't reveal what that terrain is; + 5.0: used to say "solid rock" for STONE, but that made it be + different from unmapped walls outside of rooms (and was wrong + on arboreal levels) */ + if (levl[x][y].seenv || IS_STWALL(levl[x][y].typ) + || levl[x][y].typ == SDOOR || levl[x][y].typ == SCORR) { + glyph = back_to_glyph(x, y); + Strcpy(buf, the(defsyms[glyph_to_cmap(glyph)].explanation)); + } else { + Strcpy(buf, unknown_obstacle); + } + /* note: 'solid' is misleadingly named and catches pools + of water and lava as well as rock and walls; + 5.0: furniture too */ + } else { + Strcpy(buf, "thin air"); + } + + futile: + You("%s%s %s.", + !(boulder || solid) ? "" : !explo ? "harmlessly " : "futilely ", + explo ? "explode at" : "attack", buf); + + nomul(0); + if (explo) { + struct attack *attk + = attacktype_fordmg(gy.youmonst.data, AT_EXPL, AD_ANY); + + /* no monster has been attacked so we have bypassed explum() */ + wake_nearto(u.ux, u.uy, 7 * 7); /* same radius as explum() */ + if (attk) + explum((struct monst *) 0, attk); + u.mh = -1; /* dead in the current form */ + rehumanize(); + } + return TRUE; + } + return FALSE; +} + +/* does the plane of air disturb movement? */ +staticfn boolean +air_turbulence(void) +{ + if (Is_airlevel(&u.uz) && rn2(4) && !Levitation && !Flying) { + switch (rn2(3)) { + case 0: + You("tumble in place."); + exercise(A_DEX, FALSE); + break; + case 1: + You_cant("control your movements very well."); + break; + case 2: + pline("It's hard to walk in thin air."); + exercise(A_DEX, TRUE); + break; + } + return TRUE; + } + return FALSE; +} + +/* does water disturb the movement? */ +staticfn boolean +water_turbulence(coordxy *x, coordxy *y) +{ + if (u.uinwater) { + int wtcap; + int wtmod = (Swimming ? MOD_ENCUMBER : SLT_ENCUMBER); + + water_friction(); + if (!u.dx && !u.dy) { + nomul(0); + return TRUE; + } + *x = u.ux + u.dx; + *y = u.uy + u.dy; + + /* are we trying to move out of water while carrying too much? */ + if (isok(*x, *y) && !is_pool(*x, *y) && !Is_waterlevel(&u.uz) + && (wtcap = near_capacity()) > wtmod) { + /* when escaping from drowning you need to be unencumbered + in order to crawl out of water, but when not drowning, + doing so while encumbered is feasible; if in an aquatic + form, stressed or less is allowed; otherwise (magical + breathing), only burdened is allowed */ + You("are carrying too much to climb out of the water."); + nomul(0); + return TRUE; + } + } + return FALSE; +} + +staticfn void +slippery_ice_fumbling(void) +{ + boolean on_ice = !Levitation && is_ice(u.ux, u.uy); + struct monst *iceskater = u.usteed ? u.usteed : &gy.youmonst; + + if (on_ice) { + if ((uarmf && objdescr_is(uarmf, "snow boots")) + || resists_cold(iceskater) || Flying + || is_floater(iceskater->data) || is_clinger(iceskater->data) + || is_whirly(iceskater->data)) { + on_ice = FALSE; + } else if (!rn2(Cold_resistance ? 3 : 2)) { + HFumbling |= FROMOUTSIDE; + HFumbling &= ~TIMEOUT; + HFumbling += 1; /* slip on next move */ + } + } + if (!on_ice && (HFumbling & FROMOUTSIDE)) + HFumbling &= ~FROMOUTSIDE; +} + +boolean +u_maybe_impaired(void) +{ + return (Stunned || (Confusion && !rn2(5))); +} + +/* change movement dir if impaired. return TRUE if can't move */ +staticfn boolean +impaired_movement(coordxy *x, coordxy *y) +{ + if (u_maybe_impaired()) { + int tries = 0; + + do { + if (tries++ > 50) { + nomul(0); + return TRUE; + } + confdir(TRUE); + *x = u.ux + u.dx; + *y = u.uy + u.dy; + } while (!isok(*x, *y) || bad_rock(gy.youmonst.data, *x, *y)); + } + return FALSE; +} + +staticfn boolean +avoid_moving_on_trap(coordxy x, coordxy y, boolean msg) +{ + struct trap *trap; + + if ((trap = t_at(x, y)) && trap->tseen + /* the vibrating square is implemented as a trap but treated as if + it were a type of terrain */ + && trap->ttyp != VIBRATING_SQUARE) { + if (msg && flags.mention_walls) { + set_msg_xy(x, y); + You("stop in front of %s.", + an(trapname(trap->ttyp, FALSE))); + } + return TRUE; + } + return FALSE; +} + +staticfn boolean +avoid_moving_on_liquid( + coordxy x, coordxy y, + boolean msg) +{ + boolean in_air = (Levitation || Flying); + + /* don't stop if you're not on a transition between terrain types... */ + if ((levl[x][y].typ == levl[u.ux][u.uy].typ + /* or you are using shift-dir running and the transition isn't + dangerous... */ + || (svc.context.run < 2 && (!is_lava(x, y) || in_air)) + || svc.context.travel) + /* and you know you won't fall in */ + && (in_air || Known_lwalking || (is_pool(x, y) && Known_wwalking)) + && !(IS_WATERWALL(levl[x][y].typ) || levl[x][y].typ == LAVAWALL)) { + /* XXX: should send 'is_clinger(gy.youmonst.data)' here once clinging + polyforms are allowed to move over water */ + return FALSE; /* liquid is safe to traverse */ + } else if (is_pool_or_lava(x, y) && levl[x][y].seenv) { + if (msg && flags.mention_walls) { + set_msg_xy(x, y); + You("stop at the edge of the %s.", + hliquid(is_pool(x,y) ? "water" : "lava")); + } + return TRUE; + } + return FALSE; +} + +/* when running/rushing, avoid stepping on a known trap or pool of liquid. + returns TRUE if avoided. */ +staticfn boolean +avoid_running_into_trap_or_liquid(coordxy x, coordxy y) +{ + boolean would_stop = (svc.context.run >= 2); + if (!svc.context.run) + return FALSE; + + if (avoid_moving_on_trap(x,y, would_stop) + || (Blind && avoid_moving_on_liquid(x,y, would_stop))) { + nomul(0); + if (would_stop) + svc.context.move = 0; + return would_stop; + } + return FALSE; +} + +/* if paranoid_confirm:Trap is enabled, check whether the next step forward + needs player confirmation due to visible region or discovered trap; + result: True => stop moving, False => proceed */ +staticfn boolean +avoid_trap_andor_region(coordxy x, coordxy y) +{ + char qbuf[QBUFSZ]; + NhRegion *newreg, *oldreg; + struct trap *trap = NULL; + + /* treat entering a visible gas cloud region like entering a trap; + there could be a known trap as well as a region at the target spot; + if so, ask about entring the region first; even though this could + lead to two consecutive confirmation prompts, the situation seems + to be too uncommon to warrant a separate case with combined + trap+region confirmation */ + if (ParanoidTrap && !Blind && !Stunned && !Confusion && !Hallucination + /* skip if player used 'm' prefix or is moving recklessly */ + && (!svc.context.nopick || svc.context.run) + /* check for region(s) */ + && (newreg = visible_region_at(x, y)) != 0 + && ((oldreg = visible_region_at(u.ux, u.uy)) == 0 + /* if moving from one region into another, only ask for + confirmation if the one potentially being entered inflicts + damage (poison gas) and the one being exited doesn't (vapor) */ + || (reg_damg(newreg) > 0 && reg_damg(oldreg) == 0)) + /* check whether attempted move will be viable */ + && test_move(u.ux, u.uy, u.dx, u.dy, TEST_MOVE) + /* we don't override confirmation for poison resistance since the + region also hinders hero's vision even if/when no damage is done */ + ) { + Snprintf(qbuf, sizeof qbuf, "%s into that %s cloud?", + u_locomotion("step"), + (reg_damg(newreg) > 0) ? "poison gas" : "vapor"); + if (!paranoid_query(ParanoidConfirm, upstart(qbuf))) { + nomul(0); + svc.context.move = 0; + return TRUE; + } + } + + /* maybe ask player for confirmation before walking into known trap */ + if (ParanoidTrap && !Stunned && !Confusion + /* skip if player used 'm' prefix or is moving recklessly */ + && (!svc.context.nopick || svc.context.run) + /* check for discovered trap */ + && (trap = t_at(x, y)) != 0 && trap->tseen + /* check whether attempted move will be viable */ + && test_move(u.ux, u.uy, u.dx, u.dy, TEST_MOVE) + /* override confirmation if the trap is harmless to the hero */ + && (immune_to_trap(&gy.youmonst, trap->ttyp) != TRAP_CLEARLY_IMMUNE + /* Hallucination: all traps still show as ^, but the + hero can't tell what they are, so treat as dangerous */ + || Hallucination)) { + int traptype = (Hallucination ? rnd(TRAPNUM - 1) : (int) trap->ttyp); + boolean into = into_vs_onto(traptype); + + Snprintf(qbuf, sizeof qbuf, "Really %s %s that %s?", + u_locomotion("step"), into ? "into" : "onto", + defsyms[trap_to_defsym(traptype)].explanation); + /* handled like paranoid_confirm:pray; when paranoid_confirm:trap + isn't set, don't ask at all but if it is set (checked above), + ask via y/n if parnoid_confirm:confirm isn't also set or via + yes/no if it is */ + if (!paranoid_query(ParanoidConfirm, qbuf)) { + nomul(0); + svc.context.move = 0; + return TRUE; + } + } + return FALSE; +} + +/* trying to move out-of-bounds? */ +staticfn boolean +move_out_of_bounds(coordxy x, coordxy y) +{ + if (!isok(x, y)) { + if (svc.context.forcefight) + return domove_fight_empty(x, y); + + if (flags.mention_walls) { + coordxy dx = u.dx, dy = u.dy; + + if (dx && dy) { /* diagonal */ + /* only as far as possible diagonally if in very + corner; otherwise just report whichever of the + cardinal directions has reached its limit */ + if (isok(x, u.uy)) + dx = 0; + else if (isok(u.ux, y)) + dy = 0; + } + You("have already gone as far %s as possible.", + directionname(xytodir(dx, dy))); + } + nomul(0); + svc.context.move = 0; + return TRUE; + } + return FALSE; +} + +/* carrying too much to be able to move? */ +staticfn boolean +carrying_too_much(void) +{ + int wtcap; + + if (((wtcap = near_capacity()) >= OVERLOADED + || (wtcap > SLT_ENCUMBER + && (Upolyd ? (u.mh < 5 && u.mh != u.mhmax) + : (u.uhp < 10 && u.uhp != u.uhpmax)))) + && !Is_airlevel(&u.uz)) { + if (wtcap < OVERLOADED) { + You("don't have enough stamina to move."); + exercise(A_CON, FALSE); + } else + You("collapse under your load."); + nomul(0); + return TRUE; + } + return FALSE; +} + +/* try to pull free from sticking monster, or you release a monster + you're sticking to. returns TRUE if you lose your movement. */ +staticfn boolean +escape_from_sticky_mon(coordxy x, coordxy y) +{ + if (u.ustuck && (x != u.ustuck->mx || y != u.ustuck->my)) { + struct monst *mtmp; + + if (!m_next2u(u.ustuck)) { + /* perhaps it fled (or was teleported or ... ) */ + set_ustuck((struct monst *) 0); + } else if (sticks(gy.youmonst.data)) { + /* When polymorphed into a sticking monster, + * u.ustuck means it's stuck to you, not you to it. + */ + mtmp = u.ustuck; + set_ustuck((struct monst *) 0); + You("release %s.", y_monnam(mtmp)); + } else { + /* If holder is asleep or paralyzed: + * 37.5% chance of getting away, + * 12.5% chance of waking/releasing it; + * otherwise: + * 7.5% chance of getting away. + * [strength ought to be a factor] + * If holder is tame and there is no conflict, + * guaranteed escape. + */ + switch (rn2(!u.ustuck->mcanmove ? 8 : 40)) { + case 3: + if (!u.ustuck->mcanmove) { + /* it's free to move on next turn */ + u.ustuck->mfrozen = 1; + u.ustuck->msleeping = 0; + } + FALLTHROUGH; + /*FALLTHRU*/ + default: + if (Conflict || u.ustuck->mconf || !u.ustuck->mtame) { + You("cannot escape from %s!", y_monnam(u.ustuck)); + nomul(0); + return TRUE; + } + FALLTHROUGH; + /*FALLTHRU*/ + case 0: + case 1: + case 2: + mtmp = u.ustuck; + set_ustuck((struct monst *) 0); + You("pull free from %s.", y_monnam(mtmp)); + break; + } + } + } + return FALSE; +} + +void +domove(void) +{ + coordxy ux1 = u.ux, uy1 = u.uy; + + gd.domove_succeeded = 0L; + domove_core(); + /* gd.domove_succeeded is available to make assessments now */ + if ((gd.domove_succeeded & (DOMOVE_RUSH | DOMOVE_WALK)) != 0) { + maybe_smudge_engr(ux1, uy1, u.ux, u.uy); + maybe_adjust_hero_bubble(); + } + gd.domove_attempting = 0L; + + gk.kickedloc.x = 0, gk.kickedloc.y = 0; +} + +staticfn void +domove_core(void) +{ + struct monst *mtmp; + struct rm *tmpr; + coordxy x, y; + int glyph; + coordxy chainx = 0, chainy = 0, + ballx = 0, bally = 0; /* ball&chain new positions */ + int bc_control = 0; /* control for ball&chain */ + boolean cause_delay = FALSE, /* dragging ball will skip a move */ + displaceu = FALSE; /* involuntary swap */ + + if (svc.context.travel) { + if (!findtravelpath(TRAVP_TRAVEL)) + (void) findtravelpath(TRAVP_GUESS); + svc.context.travel1 = 0; + } + + if (carrying_too_much()) + return; + + if (u.uswallow) { + u.dx = u.dy = 0; + x = u.ustuck->mx, y = u.ustuck->my; + u_on_newpos(x, y); /* set u.ux,uy and handle CLIPPING */ + mtmp = u.ustuck; + } else { + if (air_turbulence()) + return; + + /* check slippery ice */ + slippery_ice_fumbling(); + + x = u.ux + u.dx; + y = u.uy + u.dy; + if (impaired_movement(&x, &y)) + return; + + /* turbulence might alter your actual destination */ + if (water_turbulence(&x, &y)) + return; + + if (move_out_of_bounds(x, y)) + return; + + if (avoid_running_into_trap_or_liquid(x, y)) + return; + + if (escape_from_sticky_mon(x, y)) + return; + + mtmp = m_at(x, y); + if (mtmp && !is_safemon(mtmp)) { + /* Don't attack if you're running, and can see it */ + /* It's fine to displace pets, though */ + /* We should never get here if forcefight */ + if (svc.context.run && ((!Blind && mon_visible(mtmp) + && ((M_AP_TYPE(mtmp) != M_AP_FURNITURE + && M_AP_TYPE(mtmp) != M_AP_OBJECT) + || Protection_from_shape_changers)) + || sensemon(mtmp))) { + nomul(0); + svc.context.move = 0; return; } } + } - /* about to become known empty -- remove 'I' if present */ - unmap_object(x, y); - if (boulder) - map_object(boulder, TRUE); - newsym(x, y); - glyph = glyph_at(x, y); /* might have just changed */ + u.ux0 = u.ux; + u.uy0 = u.uy; + gb.bhitpos.x = x; + gb.bhitpos.y = y; + tmpr = &levl[x][y]; + glyph = glyph_at(x, y); - if (boulder) { - Strcpy(buf, ansimpleoname(boulder)); - } else if (Underwater && !is_pool(x, y)) { - /* Underwater, targetting non-water; the map just shows blank - because you don't see remembered terrain while underwater; - although the hero can attack an adjacent monster this way, - assume he can't reach out far enough to distinguish terrain */ - Sprintf(buf, (Is_waterlevel(&u.uz) && levl[x][y].typ == AIR) - ? "an air bubble" - : "nothing"); - } else if (solid) { - /* glyph might indicate unseen terrain if hero is blind; - unlike searching, this won't reveal what that terrain is - (except for solid rock, where the glyph would otherwise - yield ludicrous "dark part of a room") */ - Strcpy(buf, (levl[x][y].typ == STONE) ? "solid rock" - : glyph_is_cmap(glyph) - ? the(defsyms[glyph_to_cmap(glyph)].explanation) - : (const char *) "an unknown obstacle"); - /* note: 'solid' is misleadingly named and catches pools - of water and lava as well as rock and walls */ - } else { - Strcpy(buf, "thin air"); + if (mtmp) { + /* don't stop travel when displacing pets; if the + displace fails for some reason, do_attack() in uhitm.c + will stop travel rather than domove */ + if (!is_safemon(mtmp) || svc.context.forcefight) + nomul(0); + + if (domove_bump_mon(mtmp, glyph)) + return; + + /* attack monster */ + if (domove_attackmon_at(mtmp, x, y, &displaceu)) + return; + } + + if (!displaceu) { + + if (domove_fight_ironbars(x, y)) + return; + + if (domove_fight_web(x, y)) + return; + + if (domove_fight_empty(x, y)) + return; + + (void) unmap_invisible(x, y); + /* not attacking an animal, so we try to move */ + if ((u.dx || u.dy) && u.usteed && stucksteed(FALSE)) { + nomul(0); + return; } - You("%s%s %s.", - !(boulder || solid) ? "" : !explo ? "harmlessly " : "futilely ", - explo ? "explode at" : "attack", buf); - nomul(0); - if (explo) { - wake_nearby(); - u.mh = -1; /* dead in the current form */ - rehumanize(); + if (u_rooted()) + return; + + /* handling for paranoid_confirm:Trap which doubles as + paranoid_confirm:Region */ + if (ParanoidTrap) { + if (avoid_trap_andor_region(x, y)) + return; } - return; - } - (void) unmap_invisible(x, y); - /* not attacking an animal, so we try to move */ - if ((u.dx || u.dy) && u.usteed && stucksteed(FALSE)) { - nomul(0); - return; - } - if (u_rooted()) - return; + if (u.utrap) { /* when u.utrap is True, displaceu is False */ + boolean moved = trapmove(x, y, (struct trap *) NULL); - if (u.utrap) { - boolean moved = trapmove(x, y, trap); + if (!u.utrap) { + disp.botl = TRUE; + reset_utrap(TRUE); /* might resume levitation or flight */ + } + /* might not have escaped, or did escape but remain in the same + spot */ + if (!moved) + return; + } - if (!u.utrap) - reset_utrap(TRUE); /* might resume levitation or flight */ - /* might not have escaped, or did escape but remain in same spot */ - if (!moved) + if (!test_move(u.ux, u.uy, x - u.ux, y - u.uy, DO_MOVE)) { + if (!svc.context.door_opened) { + svc.context.move = 0; + nomul(0); + } return; - } + } - if (!test_move(u.ux, u.uy, x - u.ux, y - u.uy, DO_MOVE)) { - if (!context.door_opened) { - context.move = 0; + /* Is it dangerous to swim in water or lava? */ + if (swim_move_danger(x, y)) { + svc.context.move = 0; nomul(0); + return; } - return; - } + + } /* !dislacedu */ /* Move ball and chain. */ if (Punished) @@ -1755,155 +2867,88 @@ domove_core() if (!in_out_region(x, y)) return; - /* now move the hero */ mtmp = m_at(x, y); + /* mtmp can be null at this point */ + + /* tentatively move the hero plus steed; leave CLIPPING til later */ u.ux += u.dx; u.uy += u.dy; - /* Move your steed, too */ + + m_postmove_effect(&gy.youmonst); + if (u.usteed) { u.usteed->mx = u.ux; u.usteed->my = u.uy; - exercise_steed(); + /* [if move attempt ends up being blocked, should training count?] */ + exercise_steed(); /* train riding skill */ } - /* - * If safepet at destination then move the pet to the hero's - * previous location using the same conditions as in attack(). - * there are special extenuating circumstances: - * (1) if the pet dies then your god angers, - * (2) if the pet gets trapped then your god may disapprove, - * (3) if the pet was already trapped and you attempt to free it - * not only do you encounter the trap but you may frighten your - * pet causing it to go wild! moral: don't abuse this privilege. - * - * Ceiling-hiding pets are skipped by this section of code, to - * be caught by the normal falling-monster code. - */ - if (is_safepet(mtmp) && !(is_hider(mtmp->data) && mtmp->mundetected)) { - /* if trapped, there's a chance the pet goes wild */ - if (mtmp->mtrapped) { - if (!rn2(mtmp->mtame)) { - mtmp->mtame = mtmp->mpeaceful = mtmp->msleeping = 0; - if (mtmp->mleashed) - m_unleash(mtmp, TRUE); - growl(mtmp); - } else { - yelp(mtmp); - } - } - - /* seemimic/newsym should be done before moving hero, otherwise - the display code will draw the hero here before we possibly - cancel the swap below (we can ignore steed mx,my here) */ - u.ux = u.ux0, u.uy = u.uy0; - mtmp->mundetected = 0; - if (M_AP_TYPE(mtmp)) - seemimic(mtmp); - else if (!mtmp->mtame) - newsym(mtmp->mx, mtmp->my); - u.ux = mtmp->mx, u.uy = mtmp->my; /* resume swapping positions */ - - if (mtmp->mtrapped && (trap = t_at(mtmp->mx, mtmp->my)) != 0 - && is_pit(trap->ttyp) - && sobj_at(BOULDER, trap->tx, trap->ty)) { - /* can't swap places with pet pinned in a pit by a boulder */ - u.ux = u.ux0, u.uy = u.uy0; /* didn't move after all */ - if (u.usteed) - u.usteed->mx = u.ux, u.usteed->my = u.uy; - } else if (u.ux0 != x && u.uy0 != y && NODIAG(mtmp->data - mons)) { - /* can't swap places when pet can't move to your spot */ - u.ux = u.ux0, u.uy = u.uy0; - if (u.usteed) - u.usteed->mx = u.ux, u.usteed->my = u.uy; - You("stop. %s can't move diagonally.", upstart(y_monnam(mtmp))); - } else if (u_with_boulder - && !(verysmall(mtmp->data) - && (!mtmp->minvent || (curr_mon_load(mtmp) <= 600)))) { - /* can't swap places when pet won't fit there with the boulder */ - u.ux = u.ux0, u.uy = u.uy0; /* didn't move after all */ - if (u.usteed) - u.usteed->mx = u.ux, u.usteed->my = u.uy; - You("stop. %s won't fit into the same spot that you're at.", - upstart(y_monnam(mtmp))); - } else if (u.ux0 != x && u.uy0 != y && bad_rock(mtmp->data, x, u.uy0) - && bad_rock(mtmp->data, u.ux0, y) - && (bigmonst(mtmp->data) || (curr_mon_load(mtmp) > 600))) { - /* can't swap places when pet won't fit thru the opening */ - u.ux = u.ux0, u.uy = u.uy0; /* didn't move after all */ - if (u.usteed) - u.usteed->mx = u.ux, u.usteed->my = u.uy; - You("stop. %s won't fit through.", upstart(y_monnam(mtmp))); - } else { - char pnambuf[BUFSZ]; + if (mtmp) { + if (displaceu) { + boolean noticed_it = (canspotmon(mtmp) + || glyph_is_invisible(glyph) + || glyph_is_warning(glyph)); - /* save its current description in case of polymorph */ - Strcpy(pnambuf, y_monnam(mtmp)); - mtmp->mtrapped = 0; - remove_monster(x, y); + remove_monster(u.ux, u.uy); place_monster(mtmp, u.ux0, u.uy0); - newsym(x, y); + newsym(u.ux, u.uy); newsym(u.ux0, u.uy0); + /* monst still knows where hero is */ + mtmp->mux = u.ux, mtmp->muy = u.uy; + + pline("%s swaps places with you...", + !noticed_it ? Something : YMonnam(mtmp)); + if (!canspotmon(mtmp)) + map_invisible(u.ux0, u.uy0); + /* monster chose to swap places; hero doesn't get any credit + or blame if something bad happens to it */ + svc.context.mon_moving = 1; + if (!minliquid(mtmp)) + (void) mintrap(mtmp, NO_TRAP_FLAGS); + svc.context.mon_moving = 0; - You("%s %s.", mtmp->mtame ? "swap places with" : "frighten", - pnambuf); - - /* check for displacing it into pools and traps */ - switch (minliquid(mtmp) ? 2 : mintrap(mtmp)) { - case 0: - break; - case 1: /* trapped */ - case 3: /* changed levels */ - /* there's already been a trap message, reinforce it */ - abuse_dog(mtmp); - adjalign(-3); - break; - case 2: - /* drowned or died... - * you killed your pet by direct action, so get experience - * and possibly penalties; - * we want the level gain message, if it happens, to occur - * before the guilt message below - */ - { - /* minliquid() and mintrap() call mondead() rather than - killed() so we duplicate some of the latter here */ - int tmp, mndx; - - u.uconduct.killer++; - mndx = monsndx(mtmp->data); - tmp = experience(mtmp, (int) mvitals[mndx].died); - more_experienced(tmp, 0); - newexplevel(); /* will decide if you go up */ - } - /* That's no way to treat a pet! Your god gets angry. - * - * [This has always been pretty iffy. Why does your - * patron deity care at all, let alone enough to get mad?] - */ - if (rn2(4)) { - You_feel("guilty about losing your pet like this."); - u.ugangr++; - adjalign(-15); - } - break; - default: - pline("that's strange, unknown mintrap result!"); - break; + /* + * If safepet at destination then move the pet to the hero's + * previous location using the same conditions as in do_attack(). + * there are special extenuating circumstances: + * (1) if the pet dies then your god angers, + * (2) if the pet gets trapped then your god may disapprove. + * + * Ceiling-hiding pets are skipped by this section of code, to + * be caught by the normal falling-monster code. + */ + } else if (is_safemon(mtmp) + && !(is_hider(mtmp->data) && mtmp->mundetected)) { + if (!domove_swap_with_pet(mtmp, x, y)) { + u.ux = u.ux0, u.uy = u.uy0; /* didn't move after all */ + /* could skip this since we're about to call u_on_newpos() */ + if (u.usteed) + u.usteed->mx = u.ux, u.usteed->my = u.uy; } } - } + } /* mtmp != NULL */ + + /* tentative move above didn't handle CLIPPING, in case there was a + monster in the way and the move attempt ended up being blocked; + do a full re-position now, possibly back to where hero started */ + u_on_newpos(u.ux, u.uy); reset_occupations(); - if (context.run) { - if (context.run < 8) - if (IS_DOOR(tmpr->typ) || IS_ROCK(tmpr->typ) + if (svc.context.run) { + if (svc.context.run < 8) + if (IS_DOOR(tmpr->typ) || IS_OBSTRUCTED(tmpr->typ) || IS_FURNITURE(tmpr->typ)) nomul(0); } - if (hides_under(youmonst.data) || youmonst.data->mlet == S_EEL + /* your tread on the ground may disturb the slumber of nearby zombies */ + if (!Levitation && !Flying && !Stealth + && gy.youmonst.data->cwt >= (WT_ELF / 2)) + disturb_buried_zombies(u.ux, u.uy); + + if (hides_under(gy.youmonst.data) || gy.youmonst.data->mlet == S_EEL || u.dx || u.dy) - (void) hideunder(&youmonst); + (void) hideunder(&gy.youmonst); /* * Mimics (or whatever) become noticeable if they move and are @@ -1912,13 +2957,14 @@ domove_core() */ if ((u.dx || u.dy) && (U_AP_TYPE == M_AP_OBJECT || U_AP_TYPE == M_AP_FURNITURE)) - youmonst.m_ap_type = M_AP_NOTHING; + gy.youmonst.m_ap_type = M_AP_NOTHING; check_leash(u.ux0, u.uy0); if (u.ux0 != u.ux || u.uy0 != u.uy) { /* let caller know so that an evaluation may take place */ - domove_succeeded |= (domove_attempting & (DOMOVE_RUSH | DOMOVE_WALK)); + gd.domove_succeeded |= + (gd.domove_attempting & (DOMOVE_RUSH | DOMOVE_WALK)); u.umoved = TRUE; /* Clean old position -- vision_recalc() will print our new one. */ newsym(u.ux0, u.uy0); @@ -1937,30 +2983,41 @@ domove_core() /* must come after we finished picking up, in spoteffects() */ if (cause_delay) { nomul(-2); - multi_reason = "dragging an iron ball"; - nomovemsg = ""; + gm.multi_reason = "dragging an iron ball"; + gn.nomovemsg = ""; } - if (context.run && flags.runmode != RUN_TPORT) { - /* display every step or every 7th step depending upon mode */ - if (flags.runmode != RUN_LEAP || !(moves % 7L)) { - if (flags.time) - context.botl = 1; + runmode_delay_output(); +} + +/* delay output based on value of runmode, + if hero is running or doing a multi-turn action */ +void +runmode_delay_output(void) +{ + if ((svc.context.run || gm.multi) && flags.runmode != RUN_TPORT) { + /* for tport mode, don't display anything until we've stopped; + for normal (leap) mode, update display every 7th step + (relative to turn counter; ought to be to start of running); + for walk and crawl (visual debugging) modes, update the + display after every step */ + if (flags.runmode != RUN_LEAP || !(svm.moves % 7L)) { + /* moveloop() suppresses time_botl when running */ + disp.time_botl = flags.time; curs_on_u(); - delay_output(); + nh_delay_output(); if (flags.runmode == RUN_CRAWL) { - delay_output(); - delay_output(); - delay_output(); - delay_output(); + nh_delay_output(); + nh_delay_output(); + nh_delay_output(); + nh_delay_output(); } } } } -STATIC_OVL void -maybe_smudge_engr(x1,y1,x2,y2) -int x1, y1, x2, y2; +staticfn void +maybe_smudge_engr(coordxy x1, coordxy y1, coordxy x2, coordxy y2) { struct engr *ep; @@ -1973,30 +3030,38 @@ int x1, y1, x2, y2; } } +/* HP loss or passing out from overexerting yourself */ +void +overexert_hp(void) +{ + int *hp = (!Upolyd ? &u.uhp : &u.mh); + + if (*hp > 1) { + *hp -= 1; + disp.botl = TRUE; + } else { + You("pass out from exertion!"); + exercise(A_CON, FALSE); + fall_asleep(-10, FALSE); + } +} + /* combat increases metabolism */ boolean -overexertion() +overexertion(void) { /* this used to be part of domove() when moving to a monster's - position, but is now called by attack() so that it doesn't + position, but is now called by do_attack() so that it doesn't execute if you decline to attack a peaceful monster */ gethungry(); - if ((moves % 3L) != 0L && near_capacity() >= HVY_ENCUMBER) { - int *hp = (!Upolyd ? &u.uhp : &u.mh); - - if (*hp > 1) { - *hp -= 1; - } else { - You("pass out from exertion!"); - exercise(A_CON, FALSE); - fall_asleep(-10, FALSE); - } + if ((svm.moves % 3L) != 0L && near_capacity() >= HVY_ENCUMBER) { + overexert_hp(); } - return (boolean) (multi < 0); /* might have fainted (forced to sleep) */ + return (boolean) (gm.multi < 0); /* might have fainted (forced to sleep) */ } void -invocation_message() +invocation_message(void) { /* a special clue-msg when on the Invocation position */ if (invocation_pos(u.ux, u.uy) && !On_stairs(u.ux, u.uy)) { @@ -2019,15 +3084,103 @@ invocation_message() } } +/* for status: set up iflags.terrain_typ, an index into terrain_descrp[]; + some types need fixing up */ +void +classify_terrain(void) +{ + struct rm *lev = &levl[u.ux][u.uy]; + int typ = svl.lastseentyp[u.ux][u.uy]; /* lev->typ */ + + /* + * If the terrain under the hero is different now from what it + * was on the previous check, bring iflags.terrain_typ up to date + * and request a status update. Unless hero is running--then the + * update request will be suppressed. + */ + + if (Underwater) { + typ = xSUBMERGED; + } else { + switch (typ) { + case STONE: + if (svl.level.flags.arboreal) + typ = TREE; + break; + case CORR: + case ROOM: + /* this matches surface() but 'floor' is odd in many places */ + typ = !Is_earthlevel(&u.uz) ? xFLOOR : xGROUND; + break; + case DOOR: + /* defaults to "doorway" (door-less or broken) */ + if ((lev->doormask & D_ISOPEN) != 0) + typ = xOPENDOOR; + else if ((lev->doormask & (D_CLOSED | D_LOCKED | D_TRAPPED)) != 0) + typ = xSHUTDOOR; + break; + case DRAWBRIDGE_UP: + /* ICE, MOAT, LAVA, or 'STONE' (which ought to be 'room') */ + typ = db_under_typ(lev->drawbridgemask); + if (typ == STONE || typ == ROOM) + typ = xGROUND; + break; + case MOAT: + /* moat and swamp handling match waterbody_name()'s result */ + if (Is_medusa_level(&u.uz)) + typ = xSEA; + else if (Is_juiblex_level(&u.uz)) + typ = xSWAMP; + break; + case WATER: + if (!Is_waterlevel(&u.uz)) + typ = xWATERWALL; + break; +#if 0 /* don't bother -- Passes_walls for hero is rare, moving + * from one type of wall to another even rarer, and the + * cost of some extra once per move status updates is low */ + case VWALL: + case HWALL: + case TLCORNER: + case TRCORNER: + case BLCORNER: + case BRCORNER: + case CROSSWALL: + case TUWALL: + case TDWALL: + case TLWALL: + case TRWALL: + case SDOOR: /* (note: lastseentyp[][] never yields SDOOR) */ + /* any wall type would do, terrain_descr[] is "Wall" for all; + forcing just one avoids false 'changed' detection below if + hero with Passes_walls ability moves from one to another */ + typ = VWALL; + break; +#endif + default: + break; + } + } + + if (typ != iflags.terrain_typ) { + /* terrain at hero's spot is different */ + iflags.terrain_typ = typ; + /* request a status update unless hero is running */ + if (flags.terrainstatus && !svc.context.run) + disp.botl = TRUE; + } +} + /* moving onto different terrain; might be going into solid rock, inhibiting levitation or flight, or coming back out of such, reinstating levitation/flying */ void -switch_terrain() +switch_terrain(void) { struct rm *lev = &levl[u.ux][u.uy]; - boolean blocklev = (IS_ROCK(lev->typ) || closed_door(u.ux, u.uy) - || (Is_waterlevel(&u.uz) && lev->typ == WATER)), + boolean blocklev = (IS_OBSTRUCTED(lev->typ) || closed_door(u.ux, u.uy) + || IS_WATERWALL(lev->typ) + || lev->typ == LAVAWALL), was_levitating = !!Levitation, was_flying = !!Flying; if (blocklev) { @@ -2056,29 +3209,43 @@ switch_terrain() if (Flying) You("start flying."); } - if ((!Levitation ^ was_levitating) || (!Flying ^ was_flying)) - context.botl = TRUE; /* update Lev/Fly status condition */ + if ((!!Levitation ^ was_levitating) || (!!Flying ^ was_flying)) + disp.botl = TRUE; /* update Lev/Fly status condition */ + + if (flags.terrainstatus) + classify_terrain(); +} + +/* set or clear u.uinwater */ +void +set_uinwater(int in_out) +{ + if (in_out != (int) u.uinwater) { + u.uinwater = in_out ? 1 : 0; + switch_terrain(); + } } /* extracted from spoteffects; called by spoteffects to check for entering or leaving a pool of water/lava, and by moveloop to check for staying on one; returns true to skip rest of spoteffects */ boolean -pooleffects(newspot) -boolean newspot; /* true if called by spoteffects */ +pooleffects( + boolean newspot) /* true if called by spoteffects */ { /* check for leaving water */ if (u.uinwater) { boolean still_inwater = FALSE; /* assume we're getting out */ if (!is_pool(u.ux, u.uy)) { - if (Is_waterlevel(&u.uz)) + if (Is_waterlevel(&u.uz)) { You("pop into an air bubble."); - else if (is_lava(u.ux, u.uy)) + iflags.last_msg = PLNMSG_BACK_ON_GROUND; + } else if (is_lava(u.ux, u.uy)) { You("leave the %s...", hliquid("water")); /* oops! */ - else - You("are on solid %s again.", - is_ice(u.ux, u.uy) ? "ice" : "land"); + } else { + back_on_ground(FALSE); + } } else if (Is_waterlevel(&u.uz)) { still_inwater = TRUE; } else if (Levitation) { @@ -2093,19 +3260,17 @@ boolean newspot; /* true if called by spoteffects */ if (!still_inwater) { boolean was_underwater = (Underwater && !Is_waterlevel(&u.uz)); - u.uinwater = 0; /* leave the water */ + set_uinwater(0); /* u.uinwater = 0; leave the water */ if (was_underwater) { /* restore vision */ docrt(); - vision_full_recalc = 1; + gv.vision_full_recalc = 1; } } } /* check for entering water or lava */ if (!u.ustuck && !Levitation && !Flying && is_pool_or_lava(u.ux, u.uy)) { - if (u.usteed - && (is_flyer(u.usteed->data) || is_floater(u.usteed->data) - || is_clinger(u.usteed->data))) { + if (u.usteed && !grounded(u.usteed->data)) { /* floating or clinging steed keeps hero safe (is_flyer() test is redundant; it can't be true since Flying yielded false) */ return FALSE; @@ -2117,7 +3282,7 @@ boolean newspot; /* true if called by spoteffects */ if (Is_airlevel(&u.uz) || Is_waterlevel(&u.uz)) return FALSE; /* even if we actually end up at same location, float_down() - has already done spoteffect()'s trap and pickup actions */ + has already done trap and pickup actions of spoteffects() */ if (newspot) check_special_room(FALSE); /* spoteffects */ return TRUE; @@ -2133,8 +3298,9 @@ boolean newspot; /* true if called by spoteffects */ if (is_lava(u.ux, u.uy)) { if (lava_effects()) return TRUE; - } else if (!Wwalking - && (newspot || !u.uinwater || !(Swimming || Amphibious))) { + } else if ((!Wwalking || is_waterwall(u.ux,u.uy)) + && (newspot || !u.uinwater + || !(Swimming || Amphibious || Breathless))) { if (drown()) return TRUE; } @@ -2143,8 +3309,7 @@ boolean newspot; /* true if called by spoteffects */ } void -spoteffects(pick) -boolean pick; +spoteffects(boolean pick) { static int inspoteffects = 0; static coord spotloc; @@ -2159,19 +3324,26 @@ boolean pick; /* prevent recursion from affecting the hero all over again [hero poly'd to iron golem enters water here, drown() inflicts damage that triggers rehumanize() which calls spoteffects()...] */ - if (inspoteffects && u.ux == spotloc.x && u.uy == spotloc.y + if (inspoteffects && u_at(spotloc.x, spotloc.y) /* except when reason is transformed terrain (ice -> water) */ && spotterrain == levl[u.ux][u.uy].typ /* or transformed trap (land mine -> pit) */ && (!spottrap || !trap || trap->ttyp == spottraptyp)) return; + /* when float_down() puts hero into lava and she teleports out, + defer spoteffects() until after "you are back on solid " */ + if (iflags.in_lava_effects) + return; ++inspoteffects; spotterrain = levl[u.ux][u.uy].typ; spotloc.x = u.ux, spotloc.y = u.uy; - /* moving onto different terrain might cause Lev or Fly to toggle */ - if (spotterrain != levl[u.ux0][u.uy0].typ || !on_level(&u.uz, &u.uz0)) + /* moving onto different terrain might cause Lev or Fly to toggle; + level change sets to , so this spotterrain + check always fails then, but it also sets iflags.terrain_typ */ + if (spotterrain != levl[u.ux0][u.uy0].typ + || iflags.terrain_typ == MAX_TYPE) switch_terrain(); if (pooleffects(TRUE)) @@ -2180,7 +3352,7 @@ boolean pick; check_special_room(FALSE); if (IS_SINK(levl[u.ux][u.uy].typ) && Levitation) dosinkfall(); - if (!in_steed_dismounting) { /* if dismounting, we'll check again later */ + if (!gi.in_steed_dismounting) { /* if dismounting, check again later */ boolean pit; /* if levitation is due to time out at the end of this @@ -2212,10 +3384,9 @@ boolean pick; /* * dotrap on a fire trap calls melt_ice() which triggers * spoteffects() (again) which can trigger the same fire - * trap (again). Use static spottrap to prevent that. - * We track spottraptyp because some traps morph - * (landmine to pit) and any new trap type - * should get triggered. + * trap (again). Use static spottrap to prevent that. + * We track spottraptyp because some traps morph (landmine + * to pit) and any new trap type should get triggered. */ if (!spottrap || spottraptyp != trap->ttyp) { spottrap = trap; @@ -2242,7 +3413,8 @@ boolean pick; : (time_left < 10L) ? 1 : 0]); } - if ((mtmp = m_at(u.ux, u.uy)) && !u.uswallow) { + + if ((mtmp = m_at(u.ux, u.uy)) != 0 && !u.uswallow) { mtmp->mundetected = mtmp->msleeping = 0; switch (mtmp->data->mlet) { case S_PIERCER: @@ -2250,7 +3422,7 @@ boolean pick; ceiling(u.ux, u.uy)); if (mtmp->mtame) { /* jumps to greet you, not attack */ ; - } else if (uarmh && is_metallic(uarmh)) { + } else if (hard_helmet(uarmh)) { pline("Its blow glances off your %s.", helm_simple_name(uarmh)); } else if (u.uac + 3 <= rnd(20)) { @@ -2279,7 +3451,7 @@ boolean pick; pline("%s attacks you by surprise!", Amonnam(mtmp)); break; } - mnexto(mtmp); /* have to move the monster */ + mnexto(mtmp, RLOC_NOMSG); /* have to move the monster */ } spotdone: if (!--inspoteffects) { @@ -2290,36 +3462,49 @@ boolean pick; } /* returns first matching monster */ -STATIC_OVL struct monst * -monstinroom(mdat, roomno) -struct permonst *mdat; -int roomno; +staticfn struct monst * +monstinroom(struct permonst *mdat, int roomno) { - register struct monst *mtmp; + struct monst *mtmp; for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { if (DEADMONSTER(mtmp)) continue; if (mtmp->data == mdat - && index(in_rooms(mtmp->mx, mtmp->my, 0), roomno + ROOMOFFSET)) + && strchr(in_rooms(mtmp->mx, mtmp->my, 0), roomno + ROOMOFFSET)) return mtmp; } return (struct monst *) 0; } +/* check whether room contains a particular type of furniture */ +staticfn boolean +furniture_present(int furniture, int roomno) +{ + int x, y, lx, ly, hx, hy; + struct mkroom *sroom = &svr.rooms[roomno]; + + ly = sroom->ly, hy = sroom->hy; + lx = sroom->lx; hx = sroom->hx; + /* the inside_room() check handles irregularly shaped rooms */ + for (y = ly; y <= hy; ++y) + for (x = lx; x <= hx; ++x) + if (levl[x][y].typ == furniture && inside_room(sroom, x, y)) + return TRUE; + return FALSE; +} + char * -in_rooms(x, y, typewanted) -register xchar x, y; -register int typewanted; +in_rooms(coordxy x, coordxy y, int typewanted) { static char buf[5]; - char rno, *ptr = &buf[4]; + char rno = 0, *ptr = &buf[4]; int typefound, min_x, min_y, max_x, max_y_offset, step; - register struct rm *lev; + struct rm *lev; #define goodtype(rno) \ (!typewanted \ - || (typefound = rooms[rno - ROOMOFFSET].rtype) == typewanted \ + || (typefound = svr.rooms[rno - ROOMOFFSET].rtype) == typewanted \ || (typewanted == SHOPBASE && typefound > SHOPBASE)) switch (rno = levl[x][y].roomno) { @@ -2355,19 +3540,19 @@ register int typewanted; for (x = min_x; x <= max_x; x += step) { lev = &levl[x][min_y]; y = 0; - if ((rno = lev[y].roomno) >= ROOMOFFSET && !index(ptr, rno) + if ((rno = lev[y].roomno) >= ROOMOFFSET && !strchr(ptr, rno) && goodtype(rno)) *(--ptr) = rno; y += step; if (y > max_y_offset) continue; - if ((rno = lev[y].roomno) >= ROOMOFFSET && !index(ptr, rno) + if ((rno = lev[y].roomno) >= ROOMOFFSET && !strchr(ptr, rno) && goodtype(rno)) *(--ptr) = rno; y += step; if (y > max_y_offset) continue; - if ((rno = lev[y].roomno) >= ROOMOFFSET && !index(ptr, rno) + if ((rno = lev[y].roomno) >= ROOMOFFSET && !strchr(ptr, rno) && goodtype(rno)) *(--ptr) = rno; } @@ -2376,21 +3561,19 @@ register int typewanted; /* is (x,y) in a town? */ boolean -in_town(x, y) -register int x, y; +in_town(coordxy x, coordxy y) { - s_level *slev = Is_special(&u.uz); - register struct mkroom *sroom; + struct mkroom *sroom; boolean has_subrooms = FALSE; - if (!slev || !slev->flags.town) + if (!svl.level.flags.has_town) return FALSE; /* * See if (x,y) is in a room with subrooms, if so, assume it's the * town. If there are no subrooms, the whole level is in town. */ - for (sroom = &rooms[0]; sroom->hx > 0; sroom++) { + for (sroom = &svr.rooms[0]; sroom->hx > 0; sroom++) { if (sroom->nsubrooms > 0) { has_subrooms = TRUE; if (inside_room(sroom, x, y)) @@ -2401,52 +3584,48 @@ register int x, y; return !has_subrooms; } -STATIC_OVL void -move_update(newlev) -register boolean newlev; +staticfn void +move_update(boolean newlev) { - char *ptr1, *ptr2, *ptr3, *ptr4; + char c, *ptr1, *ptr2, *ptr3, *ptr4; Strcpy(u.urooms0, u.urooms); Strcpy(u.ushops0, u.ushops); if (newlev) { - u.urooms[0] = '\0'; - u.uentered[0] = '\0'; - u.ushops[0] = '\0'; - u.ushops_entered[0] = '\0'; + (void) memset(u.urooms, '\0', sizeof u.urooms); + (void) memset(u.uentered, '\0', sizeof u.uentered); + (void) memset(u.ushops, '\0', sizeof u.ushops); + (void) memset(u.ushops_entered, '\0', sizeof u.ushops_entered); Strcpy(u.ushops_left, u.ushops0); return; } Strcpy(u.urooms, in_rooms(u.ux, u.uy, 0)); - for (ptr1 = &u.urooms[0], ptr2 = &u.uentered[0], ptr3 = &u.ushops[0], - ptr4 = &u.ushops_entered[0]; - *ptr1; ptr1++) { - if (!index(u.urooms0, *ptr1)) - *(ptr2++) = *ptr1; - if (IS_SHOP(*ptr1 - ROOMOFFSET)) { - *(ptr3++) = *ptr1; - if (!index(u.ushops0, *ptr1)) - *(ptr4++) = *ptr1; + for (ptr1 = u.urooms, ptr2 = u.uentered, + ptr3 = u.ushops, ptr4 = u.ushops_entered; *ptr1; ptr1++) { + c = *ptr1; + if (!strchr(u.urooms0, c)) + *ptr2++ = c; + if (IS_SHOP(c - ROOMOFFSET)) { + *ptr3++ = c; + if (!strchr(u.ushops0, c)) + *ptr4++ = c; } } - *ptr2 = '\0'; - *ptr3 = '\0'; - *ptr4 = '\0'; + *ptr2 = '\0', *ptr3 = '\0', *ptr4 = '\0'; /* filter u.ushops0 -> u.ushops_left */ - for (ptr1 = &u.ushops0[0], ptr2 = &u.ushops_left[0]; *ptr1; ptr1++) - if (!index(u.ushops, *ptr1)) - *(ptr2++) = *ptr1; + for (ptr1 = u.ushops0, ptr2 = u.ushops_left; *ptr1; ptr1++) + if (!strchr(u.ushops, *ptr1)) + *ptr2++ = *ptr1; *ptr2 = '\0'; } /* possibly deliver a one-time room entry message */ void -check_special_room(newlev) -register boolean newlev; +check_special_room(boolean newlev) { - register struct monst *mtmp; + struct monst *mtmp; char *ptr; move_update(newlev); @@ -2454,6 +3633,24 @@ register boolean newlev; if (*u.ushops0) u_left_shop(u.ushops_left, newlev); + /* + * Check for attaining 'entered Mine Town' achievement. + * Most of the Mine Town variations have the town in one large room + * containing a bunch of subrooms; we check for entering that large + * room. However, two of the variations cover the whole level rather + * than include a room with subrooms. We need to check for town entry + * before the possible early return for not having entered a room in + * case we have arrived in the town but have not entered any room. + * + * TODO: change the minetn variants which don't include any town + * boundary to have such. + */ + if (svl.level.flags.has_town && !svc.context.achieveo.minetn_reached + && In_mines(&u.uz) && in_town(u.ux, u.uy)) { + record_achievement(ACH_TOWN); + svc.context.achieveo.minetn_reached = TRUE; + } + if (!*u.uentered && !*u.ushops_entered) /* implied by newlev */ return; /* no entrance messages necessary */ @@ -2462,7 +3659,7 @@ register boolean newlev; u_entered_shop(u.ushops_entered); for (ptr = &u.uentered[0]; *ptr; ptr++) { - int roomno = *ptr - ROOMOFFSET, rt = rooms[roomno].rtype; + int roomno = *ptr - ROOMOFFSET, rt = svr.rooms[roomno].rtype; boolean msg_given = TRUE; /* Did we just enter some other special room? */ @@ -2478,14 +3675,17 @@ register boolean newlev; Blind ? "humid" : "muddy"); break; case COURT: - You("enter an opulent throne room!"); + You("enter an opulent%s room!", + /* the throne room in Sam quest home level lacks a throne */ + !furniture_present(THRONE, roomno) ? "" : " throne"); break; case LEPREHALL: You("enter a leprechaun hall!"); break; case MORGUE: if (midnight()) { - const char *run = locomotion(youmonst.data, "Run"); + const char *run = u_locomotion("Run"); + pline("%s away! %s away!", run, run); } else You("have an uncanny feeling..."); @@ -2510,21 +3710,24 @@ register boolean newlev; break; case DELPHI: { struct monst *oracle = monstinroom(&mons[PM_ORACLE], roomno); + if (oracle) { + SetVoice(oracle, 0, 80, 0); if (!oracle->mpeaceful) - verbalize("You're in Delphi, %s.", plname); + verbalize("You're in Delphi, %s.", svp.plname); else verbalize("%s, %s, welcome to Delphi!", - Hello((struct monst *) 0), plname); + Hello((struct monst *) 0), svp.plname); } else msg_given = FALSE; break; } case TEMPLE: intemple(roomno + ROOMOFFSET); + FALLTHROUGH; /*FALLTHRU*/ default: - msg_given = (rt == TEMPLE); + msg_given = (rt == TEMPLE || rt >= SHOPBASE); rt = 0; break; } @@ -2532,30 +3735,30 @@ register boolean newlev; room_discovered(roomno); if (rt != 0) { - rooms[roomno].rtype = OROOM; + svr.rooms[roomno].rtype = OROOM; if (!search_special(rt)) { /* No more room of that type */ switch (rt) { case COURT: - level.flags.has_court = 0; + svl.level.flags.has_court = 0; break; case SWAMP: - level.flags.has_swamp = 0; + svl.level.flags.has_swamp = 0; break; case MORGUE: - level.flags.has_morgue = 0; + svl.level.flags.has_morgue = 0; break; case ZOO: - level.flags.has_zoo = 0; + svl.level.flags.has_zoo = 0; break; case BARRACKS: - level.flags.has_barracks = 0; + svl.level.flags.has_barracks = 0; break; case TEMPLE: - level.flags.has_temple = 0; + svl.level.flags.has_temple = 0; break; case BEEHIVE: - level.flags.has_beehive = 0; + svl.level.flags.has_beehive = 0; break; } } @@ -2563,12 +3766,16 @@ register boolean newlev; for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { if (DEADMONSTER(mtmp)) continue; - if (!Stealth && !rn2(3)) + if (!isok(mtmp->mx,mtmp->my) + || roomno != (int) levl[mtmp->mx][mtmp->my].roomno) + continue; + if (!Stealth && !rn2(3)) { + wake_msg(mtmp, FALSE); mtmp->msleeping = 0; + } } } } - return; } @@ -2577,13 +3784,15 @@ register boolean newlev; 0 = cannot pickup, no time taken -1 = do normal pickup -2 = loot the monster */ -int -pickup_checks() +staticfn int +pickup_checks(void) { + struct trap *traphere; + /* uswallow case added by GAN 01/29/87 */ if (u.uswallow) { if (!u.ustuck->minvent) { - if (is_animal(u.ustuck->data)) { + if (digests(u.ustuck->data)) { You("pick up %s tongue.", s_suffix(mon_nam(u.ustuck))); pline("But it's kind of slimy, so you drop it."); } else @@ -2595,8 +3804,8 @@ pickup_checks() } } if (is_pool(u.ux, u.uy)) { - if (Wwalking || is_floater(youmonst.data) || is_clinger(youmonst.data) - || (Flying && !Breathless)) { + if (Wwalking || is_floater(gy.youmonst.data) + || is_clinger(gy.youmonst.data) || (Flying && !Breathless)) { You("cannot dive into the %s to pick things up.", hliquid("water")); return 0; @@ -2606,17 +3815,17 @@ pickup_checks() } } if (is_lava(u.ux, u.uy)) { - if (Wwalking || is_floater(youmonst.data) || is_clinger(youmonst.data) - || (Flying && !Breathless)) { + if (Wwalking || is_floater(gy.youmonst.data) + || is_clinger(gy.youmonst.data) || (Flying && !Breathless)) { You_cant("reach the bottom to pick things up."); return 0; - } else if (!likes_lava(youmonst.data)) { + } else if (!likes_lava(gy.youmonst.data)) { You("would burn to a crisp trying to pick things up."); return 0; } } if (!OBJ_AT(u.ux, u.uy)) { - register struct rm *lev = &levl[u.ux][u.uy]; + struct rm *lev = &levl[u.ux][u.uy]; if (IS_THRONE(lev->typ)) pline("It must weigh%s a ton!", lev->looted ? " almost" : ""); @@ -2631,61 +3840,67 @@ pickup_checks() else if (IS_ALTAR(lev->typ)) pline("Moving the altar would be a very bad idea."); else if (lev->typ == STAIRS) - pline_The("stairs are solidly fixed to the %s.", - surface(u.ux, u.uy)); + pline_The("stairs are solidly affixed."); else There("is nothing here to pick up."); return 0; } - if (!can_reach_floor(TRUE)) { - struct trap *traphere = t_at(u.ux, u.uy); - if (traphere - && (uteetering_at_seen_pit(traphere) || uescaped_shaft(traphere))) - You("cannot reach the bottom of the %s.", - is_pit(traphere->ttyp) ? "pit" : "abyss"); - else if (u.usteed && P_SKILL(P_RIDING) < P_BASIC) + traphere = t_at(u.ux, u.uy); + if (!can_reach_floor(traphere && is_pit(traphere->ttyp))) { + /* if there's a hole here, any objects here clearly aren't at + the bottom so only check for pits */ + if (traphere && uteetering_at_seen_pit(traphere)) { + You("cannot reach the bottom of the pit."); + } else if (u.usteed && P_SKILL(P_RIDING) < P_BASIC) { rider_cant_reach(); - else if (Blind && !can_reach_floor(TRUE)) + } else if (Blind) { You("cannot reach anything here."); - else - You("cannot reach the %s.", surface(u.ux, u.uy)); + } else { + const char *surf = surface(u.ux, u.uy); + + if (traphere) { + if (traphere->ttyp == HOLE) + surf = "edge of the hole"; + else if (traphere->ttyp == TRAPDOOR) + surf = "trap door"; + } + You("cannot reach the %s.", surf); + } return 0; } return -1; /* can do normal pickup */ } -/* the ',' command */ +/* the #pickup command */ int -dopickup(VOID_ARGS) +dopickup(void) { int count, tmpcount, ret; - /* awful kludge to work around parse()'s pre-decrement */ - count = (multi || (save_cm && *save_cm == cmd_from_func(dopickup))) - ? multi + 1 : 0; - multi = 0; /* always reset */ + count = (int) gc.command_count; + gm.multi = 0; /* always reset */ if ((ret = pickup_checks()) >= 0) { - return ret; + return ret ? ECMD_TIME : ECMD_OK; } else if (ret == -2) { tmpcount = -count; - return loot_mon(u.ustuck, &tmpcount, (boolean *) 0); + return loot_mon(u.ustuck, &tmpcount, (boolean *) 0) ? ECMD_TIME + : ECMD_OK; } /* else ret == -1 */ - return pickup(-count); + return pickup(-count) ? ECMD_TIME : ECMD_OK; } -/* stop running if we see something interesting */ +/* stop running if we see something interesting next to us */ /* turn around a corner if that is the only way we can proceed */ /* do not turn left or right twice */ void -lookaround() +lookaround(void) { - register int x, y; - int i, x0 = 0, y0 = 0, m0 = 1, i0 = 9; + coordxy x, y; + coordxy i, x0 = 0, y0 = 0, m0 = 1, i0 = 9; int corrct = 0, noturn = 0; struct monst *mtmp; - struct trap *trap; /* Grid bugs stop if trying to move diagonal, even if blind. Maybe */ /* they polymorphed while in the middle of a long move. */ @@ -2695,55 +3910,88 @@ lookaround() return; } - if (Blind || context.run == 0) + if (Blind || svc.context.run == 0) return; for (x = u.ux - 1; x <= u.ux + 1; x++) for (y = u.uy - 1; y <= u.uy + 1; y++) { - if (!isok(x, y) || (x == u.ux && y == u.uy)) + boolean infront = (x == u.ux + u.dx && y == u.uy + u.dy); + + /* ignore out of bounds, and our own location */ + if (!isok(x, y) || u_at(x, y)) continue; + /* (grid bugs) ignore diagonals */ if (NODIAG(u.umonnum) && x != u.ux && y != u.uy) continue; + /* can we see a monster there? */ if ((mtmp = m_at(x, y)) != 0 && M_AP_TYPE(mtmp) != M_AP_FURNITURE && M_AP_TYPE(mtmp) != M_AP_OBJECT - && (!mtmp->minvis || See_invisible) && !mtmp->mundetected) { - if ((context.run != 1 && !mtmp->mtame) - || (x == u.ux + u.dx && y == u.uy + u.dy - && !context.travel)) { - if (iflags.mention_walls) - pline("%s blocks your path.", upstart(a_monnam(mtmp))); + && mon_visible(mtmp)) { + /* running movement and not a hostile monster */ + /* OR it blocks our move direction and we're not traveling */ + if ((svc.context.run != 1 && !is_safemon(mtmp)) + || (infront && !svc.context.travel)) { + if (flags.mention_walls) + pline_xy(x, y, "%s blocks your path.", + upstart(a_monnam(mtmp))); goto stop; } } + /* stone is never interesting */ if (levl[x][y].typ == STONE) continue; + /* ignore the square we're moving away from */ if (x == u.ux - u.dx && y == u.uy - u.dy) continue; - if (IS_ROCK(levl[x][y].typ) || levl[x][y].typ == ROOM - || IS_AIR(levl[x][y].typ)) { + /* stop for traps, sometimes */ + if (avoid_moving_on_trap(x, y, + (infront && svc.context.run > 1))) { + if (svc.context.run == 1) + goto bcorr; /* if you must */ + if (infront) + goto stop; + } + + /* more uninteresting terrain */ + if (IS_OBSTRUCTED(levl[x][y].typ) || levl[x][y].typ == ROOM + || IS_AIR(levl[x][y].typ) || levl[x][y].typ == ICE) { continue; } else if (closed_door(x, y) || (mtmp && is_door_mappear(mtmp))) { + /* a closed door? */ + /* ignore if diagonal */ if (x != u.ux && y != u.uy) continue; - if (context.run != 1) { - if (iflags.mention_walls) + if (svc.context.run != 1 && !svc.context.travel) { + if (flags.mention_walls) { + set_msg_xy(x, y); You("stop in front of the door."); + } goto stop; } + /* orthogonal to a closed door, consider it a corridor */ goto bcorr; } else if (levl[x][y].typ == CORR) { + /* corridor */ bcorr: if (levl[u.ux][u.uy].typ != ROOM) { - if (context.run == 1 || context.run == 3 - || context.run == 8) { + /* running or traveling */ + if (svc.context.run == 1 || svc.context.run == 3 + || svc.context.run == 8) { + /* distance from x,y to location we're moving to */ i = dist2(x, y, u.ux + u.dx, u.uy + u.dy); + /* ignore if not on or directly adjacent to it */ if (i > 2) continue; + /* x,y is (adjacent to) the location we're moving to; + if we've seen one corridor, and x,y is not directly + orthogonally next to it, mark noturn */ if (corrct == 1 && dist2(x, y, x0, y0) != 1) noturn = 1; + /* if previous x,y was diagonal, now x,y is + orthogonal (or this is first time we're here) */ if (i < i0) { i0 = i; x0 = x; @@ -2754,39 +4002,14 @@ lookaround() corrct++; } continue; - } else if ((trap = t_at(x, y)) && trap->tseen) { - if (context.run == 1) - goto bcorr; /* if you must */ - if (x == u.ux + u.dx && y == u.uy + u.dy) { - if (iflags.mention_walls) { - int tt = what_trap(trap->ttyp, rn2_on_display_rng); - - You("stop in front of %s.", - an(defsyms[trap_to_defsym(tt)].explanation)); - } - goto stop; - } - continue; } else if (is_pool_or_lava(x, y)) { - /* water and lava only stop you if directly in front, and stop - * you even if you are running - */ - if (!Levitation && !Flying && !is_clinger(youmonst.data) - && x == u.ux + u.dx && y == u.uy + u.dy) { - /* No Wwalking check; otherwise they'd be able - * to test boots by trying to SHIFT-direction - * into a pool and seeing if the game allowed it - */ - if (iflags.mention_walls) - You("stop at the edge of the %s.", - hliquid(is_pool(x,y) ? "water" : "lava")); + if (infront && avoid_moving_on_liquid(x, y, TRUE)) goto stop; - } continue; } else { /* e.g. objects or trap or stairs */ - if (context.run == 1) + if (svc.context.run == 1) goto bcorr; - if (context.run == 8) + if (svc.context.run == 8) continue; if (mtmp) continue; /* d */ @@ -2799,13 +4022,14 @@ lookaround() return; } /* end for loops */ - if (corrct > 1 && context.run == 2) { - if (iflags.mention_walls) + if (corrct > 1 && svc.context.run == 2) { + if (flags.mention_walls) pline_The("corridor widens here."); goto stop; } - if ((context.run == 1 || context.run == 3 || context.run == 8) && !noturn - && !m0 && i0 && (corrct == 1 || (corrct == 2 && i0 == 1))) { + if ((svc.context.run == 1 || svc.context.run == 3 || svc.context.run == 8) + && !noturn && !m0 && i0 + && (corrct == 1 || (corrct == 2 && i0 == 1))) { /* make sure that we do not turn too far */ if (i0 == 2) { if (u.dx == y0 - u.uy && u.dy == u.ux - x0) @@ -2835,9 +4059,8 @@ lookaround() } /* check for a doorway which lacks its door (NODOOR or BROKEN) */ -STATIC_OVL boolean -doorless_door(x, y) -int x, y; +boolean +doorless_door(coordxy x, coordxy y) { struct rm *lev_p = &levl[x][y]; @@ -2850,13 +4073,13 @@ int x, y; return !(lev_p->doormask & ~(D_NODOOR | D_BROKEN)); } -/* used by drown() to check whether hero can crawl from water to */ +/* used by drown() to check whether hero can crawl from water to ; + also used by findtravelpath() when destination is one step away */ boolean -crawl_destination(x, y) -int x, y; +crawl_destination(coordxy x, coordxy y) { /* is location ok in general? */ - if (!goodpos(x, y, &youmonst, 0)) + if (!goodpos(x, y, &gy.youmonst, 0)) return FALSE; /* orthogonal movement is unrestricted when destination is ok */ @@ -2872,29 +4095,31 @@ int x, y; if (IS_DOOR(levl[x][y].typ) && (!doorless_door(x, y) || block_door(x, y))) return FALSE; /* finally, are we trying to squeeze through a too-narrow gap? */ - return !(bad_rock(youmonst.data, u.ux, y) - && bad_rock(youmonst.data, x, u.uy)); + return !(bad_rock(gy.youmonst.data, u.ux, y) + && bad_rock(gy.youmonst.data, x, u.uy) + && cant_squeeze_thru(&gy.youmonst)); } /* something like lookaround, but we are not running */ /* react only to monsters that might hit us */ int -monster_nearby() +monster_nearby(void) { - register int x, y; - register struct monst *mtmp; + coordxy x, y; + struct monst *mtmp; /* Also see the similar check in dochugw() in monmove.c */ for (x = u.ux - 1; x <= u.ux + 1; x++) for (y = u.uy - 1; y <= u.uy + 1; y++) { - if (!isok(x, y) || (x == u.ux && y == u.uy)) + if (!isok(x, y) || u_at(x, y)) continue; - if ((mtmp = m_at(x, y)) && M_AP_TYPE(mtmp) != M_AP_FURNITURE + if ((mtmp = m_at(x, y)) != 0 + && M_AP_TYPE(mtmp) != M_AP_FURNITURE && M_AP_TYPE(mtmp) != M_AP_OBJECT - && (!mtmp->mpeaceful || Hallucination) + && (Hallucination + || (!mtmp->mpeaceful && !noattacks(mtmp->data))) && (!is_hider(mtmp->data) || !mtmp->mundetected) - && !noattacks(mtmp->data) && mtmp->mcanmove - && !mtmp->msleeping /* aplvax!jcn */ + && !helpless(mtmp) && !onscary(u.ux, u.uy, mtmp) && canspotmon(mtmp)) return 1; } @@ -2902,70 +4127,102 @@ monster_nearby() } void -nomul(nval) -register int nval; +end_running(boolean and_travel) +{ + /* moveloop() suppresses time_botl when context.run is non-zero; when + running stops, update 'time' even if other botl status is unchanged */ + if (svc.context.run) { + svc.context.run = 0; + if (flags.time) + disp.time_botl = TRUE; + /* classify_terrain() suppresses setting disp.botl when + running; after that, it can no longer compare current terrain + against iflaga.terrain_typ to detect a change, so recompute */ + if (flags.terrainstatus) { + iflags.terrain_typ = MAX_TYPE; /* "none of the above" value */ + classify_terrain(); + } + } + + /* 'context.mv' isn't travel but callers who want to end travel + all clear it too */ + if (and_travel) + svc.context.travel = svc.context.travel1 = svc.context.mv = 0; + if (gt.travelmap) { + selection_free(gt.travelmap, TRUE); + gt.travelmap = NULL; + } + /* cancel multi */ + if (gm.multi > 0) + gm.multi = 0; +} + +void +nomul(int nval) { - if (multi < nval) + if (gm.multi < nval) return; /* This is a bug fix by ab@unido */ + disp.botl |= (gm.multi >= 0); u.uinvulnerable = FALSE; /* Kludge to avoid ctrl-C bug -dlc */ u.usleep = 0; - multi = nval; + gm.multi = nval; if (nval == 0) - multi_reason = NULL; - context.travel = context.travel1 = context.mv = context.run = 0; + gm.multi_reason = NULL, gm.multireasonbuf[0] = '\0'; + end_running(TRUE); + cmdq_clear(CQ_CANNED); } /* called when a non-movement, multi-turn action has completed */ void -unmul(msg_override) -const char *msg_override; +unmul(const char *msg_override) { - multi = 0; /* caller will usually have done this already */ + disp.botl = TRUE; + gm.multi = 0; /* caller will usually have done this already */ if (msg_override) - nomovemsg = msg_override; - else if (!nomovemsg) - nomovemsg = You_can_move_again; - if (*nomovemsg) { - pline("%s", nomovemsg); + gn.nomovemsg = msg_override; + else if (!gn.nomovemsg) + gn.nomovemsg = You_can_move_again; + if (*gn.nomovemsg) { + pline("%s", gn.nomovemsg); /* follow "you survived that attempt on your life" with a message about current form if it's not the default; primarily for life-saving while turning into green slime but is also a reminder if life-saved while poly'd and Unchanging (explore or wizard mode declining to die since can't be both Unchanging and Lifesaved) */ - if (Upolyd && !strncmpi(nomovemsg, "You survived that ", 18)) - You("are %s.", an(mons[u.umonnum].mname)); /* (ignore Hallu) */ + if (Upolyd && !strncmpi(gn.nomovemsg, "You survived that ", 18)) + You("are %s.", + an(pmname(&mons[u.umonnum], Ugender))); /* (ignore Hallu) */ } - nomovemsg = 0; + gn.nomovemsg = 0; u.usleep = 0; - multi_reason = NULL; - if (afternmv) { - int NDECL((*f)) = afternmv; + gm.multi_reason = NULL, gm.multireasonbuf[0] = '\0'; + + if (ga.afternmv) { + int (*f)(void) = ga.afternmv; /* clear afternmv before calling it (to override the encumbrance hack for levitation--see weight_cap()) */ - afternmv = (int NDECL((*))) 0; + ga.afternmv = (int (*)(void)) 0; (void) (*f)(); - /* for finishing Armor/Boots/&c_on() */ - update_inventory(); } } -STATIC_OVL void -maybe_wail() +staticfn void +maybe_wail(void) { static short powers[] = { TELEPORT, SEE_INVIS, POISON_RES, COLD_RES, SHOCK_RES, FIRE_RES, SLEEP_RES, DISINT_RES, TELEPORT_CONTROL, STEALTH, FAST, INVIS }; - if (moves <= wailmsg + 50) + if (svm.moves <= gw.wailmsg + 50) return; - wailmsg = moves; + gw.wailmsg = svm.moves; if (Role_if(PM_WIZARD) || Race_if(PM_ELF) || Role_if(PM_VALKYRIE)) { const char *who; int i, powercnt; - who = (Role_if(PM_WIZARD) || Role_if(PM_VALKYRIE)) ? urole.name.m + who = (Role_if(PM_WIZARD) || Role_if(PM_VALKYRIE)) ? gu.urole.name.m : "Elf"; if (u.uhp == 1) { pline("%s is about to die.", who); @@ -2979,22 +4236,39 @@ maybe_wail() who); } } else { + Soundeffect(se_wailing_of_the_banshee, 75); You_hear(u.uhp == 1 ? "the wailing of the Banshee..." : "the howling of the CwnAnnwn..."); } } +/* show a message how much damage you received */ +void +showdamage(int dmg) +{ + if (!iflags.showdamage || !dmg) + return; + + pline("[HP %i, %i left]", -dmg, Upolyd ? u.mh : u.uhp); +} + void -losehp(n, knam, k_format) -register int n; -register const char *knam; -boolean k_format; +losehp(int n, const char *knam, schar k_format) { +#if 0 /* code below is prepared to handle negative 'loss' so don't add this + * until we've verified that no callers intentionally rely on that */ + if (n <= 0) { + impossible("hero losing %d hit points due to \"%s\"?", n, knam); + return; + } +#endif + disp.botl = TRUE; /* u.uhp or u.mh is changing */ + end_running(TRUE); if (Upolyd) { u.mh -= n; + showdamage(n); if (u.mhmax < u.mh) u.mhmax = u.mh; - context.botl = 1; if (u.mh < 1) rehumanize(); else if (n > 0 && u.mh * 10 < u.mhmax && Unchanging) @@ -3003,16 +4277,14 @@ boolean k_format; } u.uhp -= n; + showdamage(n); if (u.uhp > u.uhpmax) u.uhpmax = u.uhp; /* perhaps n was negative */ - else - context.travel = context.travel1 = context.mv = context.run = 0; - context.botl = 1; if (u.uhp < 1) { - killer.format = k_format; - if (killer.name != knam) /* the thing that killed you */ - Strcpy(killer.name, knam ? knam : ""); - You("die..."); + svk.killer.format = k_format; + if (svk.killer.name != knam) /* the thing that killed you */ + Strcpy(svk.killer.name, knam ? knam : ""); + urgent_pline("You die..."); done(DIED); } else if (n > 0 && u.uhp * 10 < u.uhpmax) { maybe_wail(); @@ -3020,7 +4292,7 @@ boolean k_format; } int -weight_cap() +weight_cap(void) { long carrcap, save_ELev = ELevitation, save_BLev = BLevitation; @@ -3028,7 +4300,7 @@ weight_cap() confer are enabled at the start rather than the end; that causes message sequencing issues for boots of levitation so defer their encumbrance benefit until they're fully worn */ - if (afternmv == Boots_on && (ELevitation & W_ARMF) != 0L) { + if (ga.afternmv == Boots_on && (ELevitation & W_ARMF) != 0L) { ELevitation &= ~W_ARMF; float_vs_flight(); /* in case Levitation is blocking Flying */ } @@ -3036,17 +4308,18 @@ weight_cap() functions enough in that situation to enhance carrying capacity */ BLevitation &= ~I_SPECIAL; - carrcap = 25 * (ACURRSTR + ACURR(A_CON)) + 50; + carrcap = (WT_WEIGHTCAP_STRCON * (ACURRSTR + ACURR(A_CON))) + + WT_WEIGHTCAP_SPARE; if (Upolyd) { /* consistent with can_carry() in mon.c */ - if (youmonst.data->mlet == S_NYMPH) + if (gy.youmonst.data->mlet == S_NYMPH) carrcap = MAX_CARR_CAP; - else if (!youmonst.data->cwt) - carrcap = (carrcap * (long) youmonst.data->msize) / MZ_HUMAN; - else if (!strongmonst(youmonst.data) - || (strongmonst(youmonst.data) - && (youmonst.data->cwt > WT_HUMAN))) - carrcap = (carrcap * (long) youmonst.data->cwt / WT_HUMAN); + else if (!gy.youmonst.data->cwt) + carrcap = (carrcap * (long) gy.youmonst.data->msize) / MZ_HUMAN; + else if (!strongmonst(gy.youmonst.data) + || (strongmonst(gy.youmonst.data) + && (gy.youmonst.data->cwt > WT_HUMAN))) + carrcap = (carrcap * (long) gy.youmonst.data->cwt / WT_HUMAN); } if (Levitation || Is_airlevel(&u.uz) /* pugh@cornell */ @@ -3057,12 +4330,10 @@ weight_cap() carrcap = MAX_CARR_CAP; if (!Flying) { if (EWounded_legs & LEFT_SIDE) - carrcap -= 100; + carrcap -= WT_WOUNDEDLEG_REDUCT; if (EWounded_legs & RIGHT_SIDE) - carrcap -= 100; + carrcap -= WT_WOUNDEDLEG_REDUCT; } - if (carrcap < 0) - carrcap = 0; } if (ELevitation != save_ELev || BLevitation != save_BLev) { @@ -3071,28 +4342,26 @@ weight_cap() float_vs_flight(); } - return (int) carrcap; + return (int) max(carrcap, 1L); /* never return 0 */ } -static int wc; /* current weight_cap(); valid after call to inv_weight() */ - /* returns how far beyond the normal capacity the player is currently. */ /* inv_weight() is negative if the player is below normal capacity. */ int -inv_weight() +inv_weight(void) { - register struct obj *otmp = invent; - register int wt = 0; + struct obj *otmp = gi.invent; + int wt = 0; while (otmp) { if (otmp->oclass == COIN_CLASS) wt += (int) (((long) otmp->quan + 50L) / 100L); - else if (otmp->otyp != BOULDER || !throws_rocks(youmonst.data)) + else if (otmp->otyp != BOULDER || !throws_rocks(gy.youmonst.data)) wt += otmp->owt; otmp = otmp->nobj; } - wc = weight_cap(); - return (wt - wc); + gw.wc = weight_cap(); + return (wt - gw.wc); } /* @@ -3100,36 +4369,34 @@ inv_weight() * over the normal capacity the player is loaded. Max is 5. */ int -calc_capacity(xtra_wt) -int xtra_wt; +calc_capacity(int xtra_wt) { int cap, wt = inv_weight() + xtra_wt; if (wt <= 0) return UNENCUMBERED; - if (wc <= 1) + if (gw.wc <= 1) return OVERLOADED; - cap = (wt * 2 / wc) + 1; + cap = (wt * 2 / gw.wc) + 1; return min(cap, OVERLOADED); } int -near_capacity() +near_capacity(void) { return calc_capacity(0); } int -max_capacity() +max_capacity(void) { int wt = inv_weight(); - return (wt - (2 * wc)); + return (wt - (2 * gw.wc)); } boolean -check_capacity(str) -const char *str; +check_capacity(const char *str) { if (near_capacity() >= EXT_ENCUMBER) { if (str) @@ -3141,12 +4408,95 @@ const char *str; return 0; } +struct weight_table_entry { + unsigned wtyp; /* 1 = monst, 2 = obj */ + const char *nm; /* 0-6 = weight in ascii; 7 - end = name */ + int wt, idx; + boolean unique; +}; + +static struct weight_table_entry *weightlist; + +void +dump_weights(void) +{ + int i, cnt = 0, nmwidth = 49, mcount = NUMMONS, ocount = NUM_OBJECTS; + char nmbuf[BUFSZ], nmbufbase[BUFSZ]; + size_t num_entries = (size_t) (mcount + ocount); + + weightlist = (struct weight_table_entry *) + alloc(sizeof (struct weight_table_entry) * num_entries); + decl_globals_init(); + init_objects(); + for (i = 0; i < mcount; ++i) { + if (i != PM_LONG_WORM_TAIL) { + boolean cm; + + weightlist[cnt].wt = (int) mons[i].cwt; + weightlist[cnt].idx = i; + weightlist[cnt].wtyp = 1; + weightlist[cnt].unique = ((mons[i].geno & G_UNIQ) != 0); + Snprintf(nmbuf, sizeof nmbuf, "%07u", weightlist[cnt].wt); + cm = CapitalMon(mons[i].pmnames[NEUTRAL]); + Snprintf(&nmbuf[7], sizeof nmbuf - 7, "%s%s", "the body of ", + (cm) ? the(mons[i].pmnames[NEUTRAL]) + : weightlist[cnt].unique ? mons[i].pmnames[NEUTRAL] + : an(mons[i].pmnames[NEUTRAL])); + weightlist[cnt].nm = dupstr(nmbuf); + cnt++; + } + } + for (i = 0; i < ocount; ++i) { + const char *oc_name = (i == SLIME_MOLD) ? "slime mold" + : obj_descr[i].oc_name; + int wt = (int) objects[i].oc_weight; + + if (wt && oc_name) { + weightlist[cnt].idx = i; + weightlist[cnt].wt = wt; + weightlist[cnt].wtyp = 2; + weightlist[cnt].unique = (objects[i].oc_unique != 0); + objects[i].oc_name_known = 1; + Strcpy(nmbufbase, simple_typename(i)); + Snprintf(nmbuf, sizeof nmbuf, "%07u%s", wt, + (weightlist[cnt].unique) ? the(nmbufbase) + : an(nmbufbase)); + weightlist[cnt].nm = dupstr(nmbuf); + cnt++; + } + } + qsort((genericptr_t) weightlist, cnt, + sizeof (struct weight_table_entry), cmp_weights); + raw_printf("int all_weights[] = {"); + for (i = 0; i < cnt; ++i) { + if (weightlist[i].nm) { + raw_printf(" %7u%s /* %*s */", weightlist[i].wt, + (i == cnt - 1) ? " " : ",", -nmwidth, + &weightlist[i].nm[7]); + free((genericptr_t) weightlist[i].nm), weightlist[i].nm = 0; + } + } + raw_print("};"); + raw_print(""); + free((genericptr_t) weightlist); + freedynamicdata(); +} + +staticfn int QSORTCALLBACK +cmp_weights(const void *p1, const void *p2) +{ + struct weight_table_entry *i1 = (struct weight_table_entry *) p1, + *i2 = (struct weight_table_entry *) p2; + + /* return (i1->wt - i2->wt); */ + return strcmp(i1->nm, i2->nm); +} + int -inv_cnt(incl_gold) -boolean incl_gold; +inv_cnt(boolean incl_gold) { - register struct obj *otmp = invent; - register int ct = 0; + struct obj *otmp = gi.invent; + int ct = 0; while (otmp) { if (incl_gold || otmp->invlet != GOLD_SYM) @@ -3161,11 +4511,9 @@ boolean incl_gold; /* now that u.gold/m.gold is gone.*/ /* Counting money in a container might be possible too. */ long -money_cnt(otmp) -struct obj *otmp; +money_cnt(struct obj *otmp) { while (otmp) { - /* Must change when silver & copper is implemented: */ if (otmp->oclass == COIN_CLASS) return otmp->quan; otmp = otmp->nobj; @@ -3173,4 +4521,55 @@ struct obj *otmp; return 0L; } +void +spot_checks(coordxy x, coordxy y, schar old_typ) +{ + schar new_typ = levl[x][y].typ; + boolean db_ice_now = FALSE; + + switch (old_typ) { + case DRAWBRIDGE_UP: + db_ice_now = ((levl[x][y].drawbridgemask & DB_UNDER) == DB_ICE); + FALLTHROUGH; + /*FALLTHRU*/ + case ICE: + if ((new_typ != old_typ) + || (old_typ == DRAWBRIDGE_UP && !db_ice_now)) { + /* make sure there's no MELT_ICE_AWAY timer */ + if (spot_time_left(x, y, MELT_ICE_AWAY)) { + spot_stop_timers(x, y, MELT_ICE_AWAY); + } + /* adjust things affected by the ice */ + obj_ice_effects(x, y, FALSE); + } + break; + } +} + +/* calculate x/y, rounding as appropriate */ +int +rounddiv(long x, int y) +{ + int r, m; + int divsgn = 1; + + if (y == 0) + panic("division by zero in rounddiv"); + else if (y < 0) { + divsgn = -divsgn; + y = -y; + } + if (x < 0) { + divsgn = -divsgn; + x = -x; + } + r = (int) (x / y); + m = x % y; + if (2 * m >= y) + r++; + + return divsgn * r; +} + + /*hack.c*/ diff --git a/src/hacklib.c b/src/hacklib.c index c0c877fc4..4279410c4 100644 --- a/src/hacklib.c +++ b/src/hacklib.c @@ -1,8 +1,7 @@ -/* NetHack 3.6 hacklib.c $NHDT-Date: 1552639487 2019/03/15 08:44:47 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.67 $ */ +/* NetHack 5.0 hacklib.c $NHDT-Date: 1706213796 2024/01/25 20:16:36 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.116 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Michael Allison, 2007. */ /* Copyright (c) Robert Patrick Rankin, 1991 */ -/* Copyright (c) Facebook, Inc. and its affiliates. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" /* for config.h+extern.h */ @@ -12,8 +11,7 @@ extern nle_settings settings; /*= Assorted 'small' utility routines. They're virtually independent of - NetHack, except that rounddiv may call panic(). setrandom calls one - of srandom(), srand48(), or srand() depending upon configuration. + NetHack. return type routine name argument type(s) boolean digit (char) @@ -23,12 +21,17 @@ extern nle_settings settings; char * lcase (char *) char * ucase (char *) char * upstart (char *) + char * upwords (char *) char * mungspaces (char *) char * trimspaces (char *) char * strip_newline (char *) char * stripchars (char *, const char *, const char *) + char * stripdigits (char *) char * eos (char *) + const char * c_eos (const char *) + boolean str_start_is (const char *, const char *, boolean) boolean str_end_is (const char *, const char *) + int str_lines_maxlen (const char *) char * strkitten (char *,char) void copynchars (char *,const char *,int) char chrcasecpy (int,int) @@ -41,86 +44,55 @@ extern nle_settings settings; char * visctrl (char) char * strsubst (char *, const char *, const char *) int strNsubst (char *,const char *,const char *,int) + const char * findword (const char *,const char *,int,boolean) const char * ordin (int) char * sitoa (int) int sgn (int) - int rounddiv (long, int) - int distmin (int, int, int, int) - int dist2 (int, int, int, int) - boolean online2 (int, int) - boolean pmatch (const char *, const char *) - boolean pmatchi (const char *, const char *) - boolean pmatchz (const char *, const char *) + int distmin (coordxy, coordxy, coordxy, coordxy) + int dist2 (coordxy, coordxy, coordxy, coordxy) + boolean online2 (coordxy, coordxy) int strncmpi (const char *, const char *, int) char * strstri (const char *, const char *) boolean fuzzymatch (const char *, const char *, const char *, boolean) - void setrandom (void) - void reseed_random (fn) - time_t getnow (void) - int getyear (void) - char * yymmdd (time_t) - long yyyymmdd (time_t) - long hhmmss (time_t) - char * yyyymmddhhmmss (time_t) - time_t time_from_yyyymmddhhmmss (char *) - int phase_of_the_moon (void) - boolean friday_13th (void) - int night (void) - int midnight (void) - void strbuf_init (strbuf *, const char *) - void strbuf_append (strbuf *, const char *) - void strbuf_reserve (strbuf *, int) - void strbuf_empty (strbuf *) - void strbuf_nl_to_crlf (strbuf_t *) + int swapbits (int, int, int) + void nh_snprintf (const char *, int, char *, size_t, + const char *, ...) =*/ -#ifdef LINT -#define Static /* pacify lint */ -#else -#define Static static -#endif - -static boolean FDECL(pmatch_internal, (const char *, const char *, - BOOLEAN_P, const char *)); /* is 'c' a digit? */ boolean -digit(c) -char c; +digit(char c) { return (boolean) ('0' <= c && c <= '9'); } /* is 'c' a letter? note: '@' classed as letter */ boolean -letter(c) -char c; +letter(char c) { return (boolean) ('@' <= c && c <= 'Z') || ('a' <= c && c <= 'z'); } /* force 'c' into uppercase */ char -highc(c) -char c; +highc(char c) { return (char) (('a' <= c && c <= 'z') ? (c & ~040) : c); } /* force 'c' into lowercase */ char -lowc(c) -char c; +lowc(char c) { return (char) (('A' <= c && c <= 'Z') ? (c | 040) : c); } /* convert a string into all lowercase */ char * -lcase(s) -char *s; +lcase(char *s) { - register char *p; + char *p; for (p = s; *p; p++) if ('A' <= *p && *p <= 'Z') @@ -130,10 +102,9 @@ char *s; /* convert a string into all uppercase */ char * -ucase(s) -char *s; +ucase(char *s) { - register char *p; + char *p; for (p = s; *p; p++) if ('a' <= *p && *p <= 'z') @@ -143,20 +114,37 @@ char *s; /* convert first character of a string to uppercase */ char * -upstart(s) -char *s; +upstart(char *s) { if (s) *s = highc(*s); return s; } +/* capitalize first letter of every word in a string (in place) */ +char * +upwords(char *s) +{ + char *p; + boolean space = TRUE; + + for (p = s; *p; p++) + if (*p == ' ') { + space = TRUE; + } else if (space && letter(*p)) { + *p = highc(*p); + space = FALSE; + } else { + space = FALSE; + } + return s; +} + /* remove excess whitespace from a string buffer (in place) */ char * -mungspaces(bp) -char *bp; +mungspaces(char *bp) { - register char c, *p, *p2; + char c, *p, *p2; boolean was_space = TRUE; for (p = p2 = bp; (c = *p) != '\0'; p++) { @@ -176,8 +164,7 @@ char *bp; /* skip leading whitespace; remove trailing whitespace, in place */ char * -trimspaces(txt) -char *txt; +trimspaces(char *txt) { char *end; @@ -193,10 +180,9 @@ char *txt; /* remove \n from end of line; remove \r too if one is there */ char * -strip_newline(str) -char *str; +strip_newline(char *str) { - char *p = rindex(str, '\n'); + char *p = strrchr(str, '\n'); if (p) { if (p > str && *(p - 1) == '\r') @@ -208,18 +194,54 @@ char *str; /* return the end of a string (pointing at '\0') */ char * -eos(s) -register char *s; +eos(char *s) +{ + while (*s) + s++; /* s += strlen(s); */ + return s; +} + +/* version of eos() which takes a const* arg and returns that result */ +const char * +c_eos(const char *s) { while (*s) s++; /* s += strlen(s); */ return s; } +/* determine whether 'str' starts with 'chkstr', possibly ignoring case; + * panics on huge strings */ +boolean +str_start_is( + const char *str, + const char *chkstr, + boolean caseblind) +{ + char t1, t2; + int n = LARGEST_INT; + + while (--n) { + if (!*str) + return (*chkstr == 0); /* chkstr >= str */ + else if (!*chkstr) + return TRUE; /* chkstr < str */ + t1 = caseblind ? lowc(*str) : *str; + t2 = caseblind ? lowc(*chkstr) : *chkstr; + str++, chkstr++; + if (t1 != t2) + return FALSE; + } +#if 0 + if (n == 0) + panic("string too long"); +#endif + return TRUE; +} + /* determine whether 'str' ends in 'chkstr' */ boolean -str_end_is(str, chkstr) -const char *str, *chkstr; +str_end_is(const char *str, const char *chkstr) { int clen = (int) strlen(chkstr); @@ -228,11 +250,33 @@ const char *str, *chkstr; return FALSE; } +/* return max line length from buffer comprising newline-separated strings */ +int +str_lines_maxlen(const char *str) +{ + const char *s1, *s2; + int len, max_len = 0; + + s1 = str; + while (s1 && *s1) { + s2 = strchr(s1, '\n'); + if (s2) { + len = (int) (s2 - s1); + s1 = s2 + 1; + } else { + len = (int) strlen(s1); + s1 = (char *) 0; + } + if (len > max_len) + max_len = len; + } + + return max_len; +} + /* append a character to a string (in place): strcat(s, {c,'\0'}); */ char * -strkitten(s, c) -char *s; -char c; +strkitten(char *s, char c) { char *p = eos(s); @@ -243,10 +287,7 @@ char c; /* truncating string copy */ void -copynchars(dst, src, n) -char *dst; -const char *src; -int n; +copynchars(char *dst, const char *src, int n) { /* copies at most n characters, stopping sooner if terminator reached; treats newline as input terminator; unlike strncpy, always supplies @@ -260,8 +301,7 @@ int n; /* convert char nc into oc's case; mostly used by strcasecpy */ char -chrcasecpy(oc, nc) -int oc, nc; +chrcasecpy(int oc, int nc) { #if 0 /* this will be necessary if we switch to */ oc = (int) (unsigned char) oc; @@ -283,9 +323,7 @@ int oc, nc; for case-insensitive editions of makeplural() and makesingular(); src might be shorter, same length, or longer than dst */ char * -strcasecpy(dst, src) -char *dst; -const char *src; +strcasecpy(char *dst, const char *src) { char *result = dst; int ic, oc, dst_exhausted = 0; @@ -307,10 +345,9 @@ const char *src; /* return a name converted to possessive */ char * -s_suffix(s) -const char *s; +s_suffix(const char *s) { - Static char buf[BUFSZ]; + static char buf[BUFSZ]; Strcpy(buf, s); if (!strcmpi(buf, "it")) /* it -> its */ @@ -326,8 +363,7 @@ const char *s; /* construct a gerund (a verb formed by appending "ing" to a noun) */ char * -ing_suffix(s) -const char *s; +ing_suffix(const char *s) { static const char vowel[] = "aeiouwy"; static char buf[BUFSZ]; @@ -340,12 +376,14 @@ const char *s; if ((p >= &buf[3] && !strcmpi(p - 3, " on")) || (p >= &buf[4] && !strcmpi(p - 4, " off")) || (p >= &buf[5] && !strcmpi(p - 5, " with"))) { - p = rindex(buf, ' '); + p = strrchr(buf, ' '); Strcpy(onoff, p); *p = '\0'; } - if (p >= &buf[3] && !index(vowel, *(p - 1)) - && index(vowel, *(p - 2)) && !index(vowel, *(p - 3))) { + if (p >= &buf[2] && !strcmpi(p - 2, "er")) { /* slither + ing */ + /* nothing here */ + } else if (p >= &buf[3] && !strchr(vowel, *(p - 1)) + && strchr(vowel, *(p - 2)) && !strchr(vowel, *(p - 3))) { /* tip -> tipp + ing */ *p = *(p - 1); *(p + 1) = '\0'; @@ -362,13 +400,11 @@ const char *s; /* trivial text encryption routine (see makedefs) */ char * -xcrypt(str, buf) -const char *str; -char *buf; +xcrypt(const char *str, char *buf) { - register const char *p; - register char *q; - register int bitmask; + const char *p; + char *q; + int bitmask; for (bitmask = 1, p = str, q = buf; *p; q++) { *q = *p++; @@ -383,8 +419,7 @@ char *buf; /* is a string entirely whitespace? */ boolean -onlyspace(s) -const char *s; +onlyspace(const char *s) { for (; *s; s++) if (*s != ' ' && *s != '\t') @@ -392,27 +427,41 @@ const char *s; return TRUE; } -/* expand tabs into proper number of spaces */ +/* expand tabs into proper number of spaces (in place) */ char * -tabexpand(sbuf) -char *sbuf; +tabexpand( + char *sbuf) /* assumed to be [BUFSZ] but can be smaller provided that + * expanded string fits; expansion bigger than BUFSZ-1 + * will be truncated */ { - char buf[BUFSZ]; - register char *bp, *s = sbuf; - register int idx; + char buf[BUFSZ + 10]; + char *bp, *s = sbuf; + int idx; if (!*s) return sbuf; - /* warning: no bounds checking performed */ - for (bp = buf, idx = 0; *s; s++) + for (bp = buf, idx = 0; *s; s++) { if (*s == '\t') { + /* + * clang-8's optimizer at -Os has been observed to mis-compile + * this code. Symptom is nethack getting stuck in an apparent + * infinite loop (or perhaps just an extremely long one) when + * examining data.base entries. + * clang-9 doesn't exhibit this problem. [Was the incorrect + * optimization fixed or just disabled?] + */ do *bp++ = ' '; while (++idx % 8); } else { *bp++ = *s; - idx++; + ++idx; + } + if (idx >= BUFSZ) { + bp = &buf[BUFSZ - 1]; + break; } + } *bp = 0; return strcpy(sbuf, buf); } @@ -420,12 +469,11 @@ char *sbuf; #define VISCTRL_NBUF 5 /* make a displayable string from a character */ char * -visctrl(c) -char c; +visctrl(char c) { - Static char visctrl_bufs[VISCTRL_NBUF][5]; + static char visctrl_bufs[VISCTRL_NBUF][5]; static int nbuf = 0; - register int i = 0; + int i = 0; char *ccc = visctrl_bufs[nbuf]; nbuf = (nbuf + 1) % VISCTRL_NBUF; @@ -451,63 +499,75 @@ char c; /* caller is responsible for ensuring that bp is a valid pointer to a BUFSZ buffer */ char * -stripchars(bp, stuff_to_strip, orig) -char *bp; -const char *stuff_to_strip, *orig; +stripchars( + char *bp, + const char *stuff_to_strip, + const char *orig) { int i = 0; char *s = bp; - if (s) { - while (*orig && i < (BUFSZ - 1)) { - if (!index(stuff_to_strip, *orig)) { - *s++ = *orig; - i++; - } - orig++; + while (*orig && i < (BUFSZ - 1)) { + if (!strchr(stuff_to_strip, *orig)) { + *s++ = *orig; + i++; } - *s = '\0'; - } else - impossible("no output buf in stripchars"); + orig++; + } + *s = '\0'; + return bp; } -/* substitute a word or phrase in a string (in place) */ -/* caller is responsible for ensuring that bp points to big enough buffer */ +/* remove digits from string */ +char * +stripdigits(char *s) +{ + char *s1, *s2; + + for (s1 = s2 = s; *s1; s1++) + if (*s1 < '0' || *s1 > '9') + *s2++ = *s1; + *s2 = '\0'; + + return s; +} + +/* substitute a word or phrase in a string (in place); + caller is responsible for ensuring that bp points to big enough buffer */ char * -strsubst(bp, orig, replacement) -char *bp; -const char *orig, *replacement; +strsubst( + char *bp, + const char *orig, + const char *replacement) { char *found, buf[BUFSZ]; + /* [this could be replaced by strNsubst(bp, orig, replacement, 1)] */ - if (bp) { - /* [this could be replaced by strNsubst(bp, orig, replacement, 1)] */ - found = strstr(bp, orig); - if (found) { - Strcpy(buf, found + strlen(orig)); - Strcpy(found, replacement); - Strcat(bp, buf); - } + found = strstr(bp, orig); + if (found) { + Strcpy(buf, found + strlen(orig)); + Strcpy(found, replacement); + Strcat(bp, buf); } return bp; } /* substitute the Nth occurrence of a substring within a string (in place); - if N is 0, substitute all occurrences; returns the number of subsitutions; + if N is 0, substitute all occurrences; returns the number of substitutions; maximum output length is BUFSZ (BUFSZ-1 chars + terminating '\0') */ int -strNsubst(inoutbuf, orig, replacement, n) -char *inoutbuf; /* current string, and result buffer */ -const char *orig, /* old substring; if "" then insert in front of Nth char */ - *replacement; /* new substring; if "" then delete old substring */ -int n; /* which occurrence to replace; 0 => all */ +strNsubst( + char *inoutbuf, /* current string, and result buffer */ + const char *orig, /* old substring; if "", insert in front of Nth char */ + const char *replacement, /* new substring; if "", delete old substring */ + int n) /* which occurrence to replace; 0 => all */ { char *bp, *op, workbuf[BUFSZ]; const char *rp; unsigned len = (unsigned) strlen(orig); int ocount = 0, /* number of times 'orig' has been matched */ - rcount = 0; /* number of subsitutions made */ + rcount = 0; /* number of substitutions made */ for (bp = inoutbuf, op = workbuf; *bp && op < &workbuf[BUFSZ - 1]; ) { if ((!len || !strncmp(bp, orig, len)) && (++ocount == n || n == 0)) { @@ -539,69 +599,67 @@ int n; /* which occurrence to replace; 0 => all */ return rcount; } +/* search for a word in a space-separated list; returns non-Null if found */ +const char * +findword( + const char *list, /* string of space-separated words */ + const char *word, /* word to try to find */ + int wordlen, /* so that it isn't required to be \0 terminated */ + boolean ignorecase) /* T: case-blind, F: case-sensitive */ +{ + const char *p = list; + + while (p) { + while (*p == ' ') + ++p; + if (!*p) + break; + if ((ignorecase ? !strncmpi(p, word, wordlen) + : !strncmp(p, word, wordlen)) + && (p[wordlen] == '\0' || p[wordlen] == ' ')) + return p; + p = strchr(p + 1, ' '); + } + return (const char *) 0; +} + /* return the ordinal suffix of a number */ const char * -ordin(n) -int n; /* note: should be non-negative */ +ordin(int n) /* note: should be non-negative */ { - register int dd = n % 10; + int dd = n % 10; return (dd == 0 || dd > 3 || (n % 100) / 10 == 1) ? "th" : (dd == 1) ? "st" : (dd == 2) ? "nd" : "rd"; } +DISABLE_WARNING_FORMAT_NONLITERAL /* one compiler complains about + result of ?: for format string */ + /* make a signed digit string from a number */ char * -sitoa(n) -int n; +sitoa(int n) { - Static char buf[13]; + static char buf[13]; Sprintf(buf, (n < 0) ? "%d" : "+%d", n); return buf; } +RESTORE_WARNING_FORMAT_NONLITERAL + /* return the sign of a number: -1, 0, or 1 */ int -sgn(n) -int n; +sgn(int n) { return (n < 0) ? -1 : (n != 0); } -/* calculate x/y, rounding as appropriate */ -int -rounddiv(x, y) -long x; -int y; -{ - int r, m; - int divsgn = 1; - - if (y == 0) - panic("division by zero in rounddiv"); - else if (y < 0) { - divsgn = -divsgn; - y = -y; - } - if (x < 0) { - divsgn = -divsgn; - x = -x; - } - r = x / y; - m = x % y; - if (2 * m >= y) - r++; - - return divsgn * r; -} - /* distance between two points, in moves */ int -distmin(x0, y0, x1, y1) -int x0, y0, x1, y1; +distmin(coordxy x0, coordxy y0, coordxy x1, coordxy y1) { - register int dx = x0 - x1, dy = y0 - y1; + coordxy dx = x0 - x1, dy = y0 - y1; if (dx < 0) dx = -dx; @@ -613,20 +671,18 @@ int x0, y0, x1, y1; return (dx < dy) ? dy : dx; } -/* square of euclidean distance between pair of pts */ +/* square of Euclidean distance between pair of pts */ int -dist2(x0, y0, x1, y1) -int x0, y0, x1, y1; +dist2(coordxy x0, coordxy y0, coordxy x1, coordxy y1) { - register int dx = x0 - x1, dy = y0 - y1; + coordxy dx = x0 - x1, dy = y0 - y1; return dx * dx + dy * dy; } /* integer square root function without using floating point */ int -isqrt(val) -int val; +isqrt(int val) { int rt = 0; int odd = 1; @@ -648,8 +704,7 @@ int val; /* are two points lined up (on a straight line)? */ boolean -online2(x0, y0, x1, y1) -int x0, y0, x1, y1; +online2(coordxy x0, coordxy y0, coordxy x1, coordxy y1) { int dx = x0 - x1, dy = y0 - y1; /* If either delta is zero then they're on an orthogonal line, @@ -658,82 +713,15 @@ int x0, y0, x1, y1; return (boolean) (!dy || !dx || dy == dx || dy == -dx); } -/* guts of pmatch(), pmatchi(), and pmatchz(); - match a string against a pattern */ -static boolean -pmatch_internal(patrn, strng, ci, sk) -const char *patrn, *strng; -boolean ci; /* True => case-insensitive, False => case-sensitive */ -const char *sk; /* set of characters to skip */ -{ - char s, p; - /* - * Simple pattern matcher: '*' matches 0 or more characters, '?' matches - * any single character. Returns TRUE if 'strng' matches 'patrn'. - */ -pmatch_top: - if (!sk) { - s = *strng++; - p = *patrn++; /* get next chars and pre-advance */ - } else { - /* fuzzy match variant of pmatch; particular characters are ignored */ - do { - s = *strng++; - } while (index(sk, s)); - do { - p = *patrn++; - } while (index(sk, p)); - } - if (!p) /* end of pattern */ - return (boolean) (s == '\0'); /* matches iff end of string too */ - else if (p == '*') /* wildcard reached */ - return (boolean) ((!*patrn - || pmatch_internal(patrn, strng - 1, ci, sk)) - ? TRUE - : s ? pmatch_internal(patrn - 1, strng, ci, sk) - : FALSE); - else if ((ci ? lowc(p) != lowc(s) : p != s) /* check single character */ - && (p != '?' || !s)) /* & single-char wildcard */ - return FALSE; /* doesn't match */ - else /* return pmatch_internal(patrn, strng, ci, sk); */ - goto pmatch_top; /* optimize tail recursion */ -} - -/* case-sensitive wildcard match */ -boolean -pmatch(patrn, strng) -const char *patrn, *strng; -{ - return pmatch_internal(patrn, strng, FALSE, (const char *) 0); -} - -/* case-insensitive wildcard match */ -boolean -pmatchi(patrn, strng) -const char *patrn, *strng; -{ - return pmatch_internal(patrn, strng, TRUE, (const char *) 0); -} - -/* case-insensitive wildcard fuzzymatch */ -boolean -pmatchz(patrn, strng) -const char *patrn, *strng; -{ - /* ignore spaces, tabs (just in case), dashes, and underscores */ - static const char fuzzychars[] = " \t-_"; - - return pmatch_internal(patrn, strng, TRUE, fuzzychars); -} - #ifndef STRNCMPI -/* case insensitive counted string comparison */ +/* case-insensitive counted string comparison */ +/*{ aka strncasecmp }*/ int -strncmpi(s1, s2, n) /*{ aka strncasecmp }*/ -register const char *s1, *s2; -register int n; /*(should probably be size_t, which is unsigned)*/ +strncmpi( + const char *s1, const char *s2, + int n) /*(should probably be size_t, which is unsigned)*/ { - register char t1, t2; + char t1, t2; while (n--) { if (!*s2) @@ -750,14 +738,12 @@ register int n; /*(should probably be size_t, which is unsigned)*/ #endif /* STRNCMPI */ #ifndef STRSTRI -/* case insensitive substring search */ +/* case-insensitive substring search */ char * -strstri(str, sub) -const char *str; -const char *sub; +strstri(const char *str, const char *sub) { - register const char *s1, *s2; - register int i, k; + const char *s1, *s2; + int i, k; #define TABSIZ 0x20 /* 0x40 would be case-sensitive */ char tstr[TABSIZ], tsub[TABSIZ]; /* nibble count tables */ #if 0 @@ -799,17 +785,17 @@ const char *sub; /* compare two strings for equality, ignoring the presence of specified characters (typically whitespace) and possibly ignoring case */ boolean -fuzzymatch(s1, s2, ignore_chars, caseblind) -const char *s1, *s2; -const char *ignore_chars; -boolean caseblind; +fuzzymatch( + const char *s1, const char *s2, + const char *ignore_chars, + boolean caseblind) { - register char c1, c2; + char c1, c2; do { - while ((c1 = *s1++) != '\0' && index(ignore_chars, c1) != 0) + while ((c1 = *s1++) != '\0' && strchr(ignore_chars, c1) != 0) continue; - while ((c2 = *s2++) != '\0' && index(ignore_chars, c2) != 0) + while ((c2 = *s2++) != '\0' && strchr(ignore_chars, c2) != 0) continue; if (!c1 || !c2) break; /* stop when end of either string is reached */ @@ -835,402 +821,203 @@ boolean caseblind; * - determination of what files are "very old" */ -/* TIME_type: type of the argument to time(); we actually use &(time_t) */ -#if defined(BSD) && !defined(POSIX_TYPES) -#define TIME_type long * -#else +/* TIME_type: type of the argument to time(); we actually use &(time_t); + you might need to define either or both of these to 'long *' in *conf.h */ +#ifndef TIME_type #define TIME_type time_t * #endif -/* LOCALTIME_type: type of the argument to localtime() */ -#if (defined(ULTRIX) && !(defined(ULTRIX_PROTO) || defined(NHSTDC))) \ - || (defined(BSD) && !defined(POSIX_TYPES)) -#define LOCALTIME_type long * -#else +#ifndef LOCALTIME_type #define LOCALTIME_type time_t * #endif -#if defined(AMIGA) && !defined(AZTEC_C) && !defined(__SASC_60) \ - && !defined(_DCC) && !defined(__GNUC__) -extern struct tm *FDECL(localtime, (time_t *)); -#endif -STATIC_DCL struct tm *NDECL(getlt); - /* NLE hack for seeds. Should stay in sync with rnglist in src/rnd.c. plus one other RNG seed for level generation. See nlernd.c */ extern unsigned long nle_seeds[]; -extern int FDECL(whichrng, (int FDECL((*fn), (int)))); - -/* Sets the seed for the random number generator */ -#ifdef USE_ISAAC64 +extern int whichrng(int (*fn)(int)); -void -set_random(seed, fn) -unsigned long seed; -int FDECL((*fn), (int)); -{ - nle_seeds[whichrng(fn)] = seed; - init_isaac64(seed, fn); -} - -#else /* USE_ISAAC64 */ - -/*ARGSUSED*/ -void -set_random(seed, fn) -unsigned long seed; -int FDECL((*fn), (int)) UNUSED; -{ - nle_seeds[whichrng(fn)] = seed; - /* the types are different enough here that sweeping the different - * routine names into one via #defines is even more confusing - */ -# ifdef RANDOM /* srandom() from sys/share/random.c */ - srandom((unsigned int) seed); -# else -# if defined(__APPLE__) || defined(BSD) || defined(LINUX) || defined(ULTRIX) \ - || defined(CYGWIN32) /* system srandom() */ -# if defined(BSD) && !defined(POSIX_TYPES) && defined(SUNOS4) - (void) -# endif - srandom((int) seed); -# else -# ifdef UNIX /* system srand48() */ - srand48((long) seed); -# else /* poor quality system routine */ - srand((int) seed); -# endif -# endif -# endif -} - -#endif /* USE_ISAAC64 */ - -/* init_random moved to nle.c. */ - -/* Reshuffles the random number generator. */ -void -reseed_random(fn) -int FDECL((*fn), (int)); -{ - /* only reseed if we are certain that the seed generation is unguessable - * by the players. */ - if (has_strong_rngseed) - init_random(fn); -} - -time_t -getnow() +/* swapbits(val, bita, bitb) swaps bit a with bit b in val */ +int +swapbits(int val, int bita, int bitb) { - time_t datetime = 0; + int tmp = ((val >> bita) & 1) ^ ((val >> bitb) & 1); - (void) time((TIME_type) &datetime); - return datetime; + return (val ^ ((tmp << bita) | (tmp << bitb))); } -STATIC_OVL struct tm * -getlt() -{ - time_t date = getnow(); - - return localtime((LOCALTIME_type) &date); -} +DISABLE_WARNING_FORMAT_NONLITERAL /* - * NLE: Return a deterministic struct tm when fix_moon_phase is enabled - * and seeds have been set. Otherwise fall back to real system time. - * The actual RNG work is done by nle_fill_fixed_tm() in nlernd.c. + * Wrap snprintf for use in the main code. + * + * Wrap reasons: + * 1. If there are any platform issues, we have one spot to fix them - + * snprintf is a routine with a troubling history of bad implementations. + * 2. Add cumbersome error checking in one spot. Problems with text + * wrangling do not have to be fatal. + * 3. Gcc 9+ will issue a warning unless the return value is used. + * Annoyingly, explicitly casting to void does not remove the error. + * So, use the result - see reason #2. */ -STATIC_OVL struct tm * -nle_getlt_maybe_fixed() -{ - static struct tm fixed_tm; - - if (!settings.fix_moon_phase || !settings.time_seed_is_set) - return getlt(); - - nle_fill_fixed_tm(&fixed_tm, settings.time_seed); - return &fixed_tm; -} - -int -getyear() -{ - return (1900 + getlt()->tm_year); -} - +void +nh_snprintf( + const char *func UNUSED, int line UNUSED, + char *str, size_t size, + const char *fmt, ...) +{ + va_list ap; + int n; + + va_start(ap, fmt); + n = vsnprintf(str, size, fmt, ap); + va_end(ap); + if (n < 0 || (size_t) n >= size) { /* is there a problem? */ #if 0 -/* This routine is no longer used since in 20YY it yields "1YYmmdd". */ -char * -yymmdd(date) -time_t date; -{ - Static char datestr[10]; - struct tm *lt; - - if (date == 0) - lt = getlt(); - else - lt = localtime((LOCALTIME_type) &date); - - Sprintf(datestr, "%02d%02d%02d", - lt->tm_year, lt->tm_mon + 1, lt->tm_mday); - return datestr; -} -#endif - -long -yyyymmdd(date) -time_t date; -{ - long datenum; - struct tm *lt; - - if (date == 0) - lt = getlt(); - else - lt = localtime((LOCALTIME_type) &date); - - /* just in case somebody's localtime supplies (year % 100) - rather than the expected (year - 1900) */ - if (lt->tm_year < 70) - datenum = (long) lt->tm_year + 2000L; - else - datenum = (long) lt->tm_year + 1900L; - /* yyyy --> yyyymm */ - datenum = datenum * 100L + (long) (lt->tm_mon + 1); - /* yyyymm --> yyyymmdd */ - datenum = datenum * 100L + (long) lt->tm_mday; - return datenum; -} - -long -hhmmss(date) -time_t date; -{ - long timenum; - struct tm *lt; - - if (date == 0) - lt = getlt(); - else - lt = localtime((LOCALTIME_type) &date); - - timenum = lt->tm_hour * 10000L + lt->tm_min * 100L + lt->tm_sec; - return timenum; -} - -char * -yyyymmddhhmmss(date) -time_t date; -{ - long datenum; - static char datestr[15]; - struct tm *lt; - - if (date == 0) - lt = getlt(); - else -#if (defined(ULTRIX) && !(defined(ULTRIX_PROTO) || defined(NHSTDC))) \ - || defined(BSD) - lt = localtime((long *) (&date)); -#else - lt = localtime(&date); +TODO: add set_impossible(), impossible -> func pointer, + test funcpointer before call + impossible("snprintf %s: func %s, file line %d", + (n < 0) ? "format error" : "overflow", + func, line); #endif - /* just in case somebody's localtime supplies (year % 100) - rather than the expected (year - 1900) */ - if (lt->tm_year < 70) - datenum = (long) lt->tm_year + 2000L; - else - datenum = (long) lt->tm_year + 1900L; - Sprintf(datestr, "%04ld%02d%02d%02d%02d%02d", datenum, lt->tm_mon + 1, - lt->tm_mday, lt->tm_hour, lt->tm_min, lt->tm_sec); - debugpline1("yyyymmddhhmmss() produced date string %s", datestr); - return datestr; -} - -time_t -time_from_yyyymmddhhmmss(buf) -char *buf; -{ - int k; - time_t timeresult = (time_t) 0; - struct tm t, *lt; - char *g, *p, y[5], mo[3], md[3], h[3], mi[3], s[3]; - - if (buf && strlen(buf) == 14) { - g = buf; - p = y; /* year */ - for (k = 0; k < 4; ++k) - *p++ = *g++; - *p = '\0'; - p = mo; /* month */ - for (k = 0; k < 2; ++k) - *p++ = *g++; - *p = '\0'; - p = md; /* day */ - for (k = 0; k < 2; ++k) - *p++ = *g++; - *p = '\0'; - p = h; /* hour */ - for (k = 0; k < 2; ++k) - *p++ = *g++; - *p = '\0'; - p = mi; /* minutes */ - for (k = 0; k < 2; ++k) - *p++ = *g++; - *p = '\0'; - p = s; /* seconds */ - for (k = 0; k < 2; ++k) - *p++ = *g++; - *p = '\0'; - lt = getlt(); - if (lt) { - t = *lt; - t.tm_year = atoi(y) - 1900; - t.tm_mon = atoi(mo) - 1; - t.tm_mday = atoi(md); - t.tm_hour = atoi(h); - t.tm_min = atoi(mi); - t.tm_sec = atoi(s); - timeresult = mktime(&t); - } - if ((int) timeresult == -1) - debugpline1("time_from_yyyymmddhhmmss(%s) would have returned -1", - buf ? buf : ""); - else - return timeresult; + str[size - 1] = '\0'; /* make sure it is nul terminated */ } - return (time_t) 0; -} - -/* - * moon period = 29.53058 days ~= 30, year = 365.2422 days - * days moon phase advances on first day of year compared to preceding year - * = 365.2422 - 12*29.53058 ~= 11 - * years in Metonic cycle (time until same phases fall on the same days of - * the month) = 18.6 ~= 19 - * moon phase on first day of year (epact) ~= (11*(year%19) + 29) % 30 - * (29 as initial condition) - * current phase in days = first day phase + days elapsed in year - * 6 moons ~= 177 days - * 177 ~= 8 reported phases * 22 - * + 11/22 for rounding - */ -int -phase_of_the_moon() /* 0-7, with 0: new, 4: full */ -{ - register struct tm *lt = nle_getlt_maybe_fixed(); - register int epact, diy, goldn; - - diy = lt->tm_yday; - goldn = (lt->tm_year % 19) + 1; - epact = (11 * goldn + 18) % 30; - if ((epact == 25 && goldn > 11) || epact == 24) - epact++; - - return ((((((diy + epact) * 6) + 11) % 177) / 22) & 7); } -boolean -friday_13th() -{ - register struct tm *lt = nle_getlt_maybe_fixed(); +RESTORE_WARNING_FORMAT_NONLITERAL - /* tm_wday (day of week; 0==Sunday) == 5 => Friday */ - return (boolean) (lt->tm_wday == 5 && lt->tm_mday == 13); -} +/* Unicode routines */ int -night() +unicodeval_to_utf8str(int uval, uint8 *buffer, size_t bufsz) { - register int hour = nle_getlt_maybe_fixed()->tm_hour; + // static uint8 buffer[7]; + uint8 *b = buffer; - return (hour < 6 || hour > 21); + if (bufsz < 5) + return 0; + /* + * Binary Hex Comments + * 0xxxxxxx 0x00..0x7F Only byte of a 1-byte character encoding + * 10xxxxxx 0x80..0xBF Continuation byte : one of 1-3 bytes following + * first 110xxxxx 0xC0..0xDF First byte of a 2-byte character encoding + * 1110xxxx 0xE0..0xEF First byte of a 3-byte character encoding + * 11110xxx 0xF0..0xF7 First byte of a 4-byte character encoding + */ + *b = '\0'; + if (uval < 0x80) { + *b++ = uval; + } else if (uval < 0x800) { + *b++ = 192 + uval / 64; + *b++ = 128 + uval % 64; + } else if (uval - 0xd800u < 0x800) { + return 0; + } else if (uval < 0x10000) { + *b++ = 224 + uval / 4096; + *b++ = 128 + uval / 64 % 64; + *b++ = 128 + uval % 64; + } else if (uval < 0x110000) { + *b++ = 240 + uval / 262144; + *b++ = 128 + uval / 4096 % 64; + *b++ = 128 + uval / 64 % 64; + *b++ = 128 + uval % 64; + } else { + return 0; + } + *b = '\0'; /* NUL terminate */ + return 1; } int -midnight() -{ - return (nle_getlt_maybe_fixed()->tm_hour == 0); +case_insensitive_comp(const char *s1, const char *s2) +{ + uchar u1, u2; + + for (;; s1++, s2++) { + u1 = (uchar) *s1; + if (isupper(u1)) + u1 = (uchar) tolower(u1); + u2 = (uchar) *s2; + if (isupper(u2)) + u2 = (uchar) tolower(u2); + if (u1 == '\0' || u1 != u2) + break; + } + return u1 - u2; } -/* strbuf_init() initializes strbuf state for use */ -void -strbuf_init(strbuf) -strbuf_t *strbuf; -{ - strbuf->str = NULL; - strbuf->len = 0; -} +#if defined(MACOS) +#define RETTYPE ssize_t +#else +#define RETTYPE int +#endif -/* strbuf_append() appends given str to strbuf->str */ -void -strbuf_append(strbuf, str) -strbuf_t *strbuf; -const char *str; +boolean +copy_bytes(int ifd, int ofd) { - int len = (int) strlen(str) + 1; + char buf[BUFSIZ]; + RETTYPE nfrom, nto; - strbuf_reserve(strbuf, - len + (strbuf->str ? (int) strlen(strbuf->str) : 0)); - Strcat(strbuf->str, str); + do { + nto = 0; + nfrom = read(ifd, buf, BUFSIZ); + /* read can return -1 */ + if (nfrom >= 0 && nfrom <= BUFSIZ) + nto = write(ofd, buf, nfrom); + if (nto != nfrom || nfrom < 0) + return FALSE; + } while (nfrom == (RETTYPE) BUFSIZ); + return TRUE; } +#undef RETTYPE + +#define MAX_D 5 +struct datamodel_information { + int sz[MAX_D]; + const char *datamodel; + const char *dmplatform; +}; + +static struct datamodel_information dm[] = { + { { (int) sizeof(short), (int) sizeof(int), (int) sizeof(long), + (int) sizeof(long long), (int) sizeof(genericptr_t) }, + "", "" }, + { { 2, 4, 4, 8, 4 }, "ILP32LL64", "x86 32-bit" }, /* Windows or Unix */ + { { 2, 4, 4, 8, 8 }, "IL32LLP64", "Windows x64 64-bit" }, + { { 2, 4, 8, 8, 8 }, "I32LP64", "Unix 64-bit"}, + { { 2, 8, 8, 8, 8 }, "ILP64", "Unix ILP64"}, /* HAL, SPARC64 */ +}; -/* strbuf_reserve() ensure strbuf->str has storage for len characters */ -void -strbuf_reserve(strbuf, len) -strbuf_t *strbuf; -int len; +const char * +datamodel(int retidx) { - if (strbuf->str == NULL) { - strbuf->str = strbuf->buf; - strbuf->str[0] = '\0'; - strbuf->len = (int) sizeof strbuf->buf; - } - - if (len > strbuf->len) { - char *oldbuf = strbuf->str; + int i, j, matchcount; + static const char *unknown = "Unknown"; - strbuf->len = len + (int) sizeof strbuf->buf; - strbuf->str = (char *) alloc(strbuf->len); - Strcpy(strbuf->str, oldbuf); - if (oldbuf != strbuf->buf) - free((genericptr_t) oldbuf); + for (i = 1; i < SIZE(dm); ++i) { + matchcount = 0; + for (j = 0; j < MAX_D; ++j) { + if (dm[0].sz[j] == dm[i].sz[j]) + ++matchcount; + } + if (matchcount == MAX_D) + return (retidx == 0) ? dm[i].datamodel : dm[i].dmplatform; } + return unknown; } -/* strbuf_empty() frees allocated memory and set strbuf to initial state */ -void -strbuf_empty(strbuf) -strbuf_t *strbuf; +const char * +what_datamodel_is_this(int retidx, int szshort, int szint, int szlong, int szll, + int szptr) { - if (strbuf->str != NULL && strbuf->str != strbuf->buf) - free((genericptr_t) strbuf->str); - strbuf_init(strbuf); -} + int i; + static const char *unknown = "Unknown"; -/* strbuf_nl_to_crlf() converts all occurences of \n to \r\n */ -void -strbuf_nl_to_crlf(strbuf) -strbuf_t *strbuf; -{ - if (strbuf->str) { - int len = (int) strlen(strbuf->str); - int count = 0; - char *cp = strbuf->str; - - while (*cp) - if (*cp++ == '\n') - count++; - if (count) { - strbuf_reserve(strbuf, len + count + 1); - for (cp = strbuf->str + len + count; count; --cp) - if ((*cp = cp[-count]) == '\n') { - *--cp = '\r'; - --count; - } - } + for (i = 1; i < SIZE(dm); ++i) { + if (szshort == dm[i].sz[0] && szint == dm[i].sz[1] + && szlong == dm[i].sz[2] && szll == dm[i].sz[3] + && szptr == dm[i].sz[4]) + return (retidx == 0) ? dm[i].datamodel : dm[i].dmplatform; } + return unknown; } - +#undef MAX_D /*hacklib.c*/ diff --git a/src/iactions.c b/src/iactions.c new file mode 100644 index 000000000..164bf2f5b --- /dev/null +++ b/src/iactions.c @@ -0,0 +1,716 @@ +/* NetHack 5.0 iactions.c $NHDT-Date: 1762680996 2025/11/09 01:36:36 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.543 $ */ +/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ +/*-Copyright (c) Pasi Kallinen, 2026. */ +/* NetHack may be freely redistributed. See license for details. */ + +#include "hack.h" + +staticfn boolean item_naming_classification(struct obj *, char *, char *); +staticfn int item_reading_classification(struct obj *, char *); +staticfn void ia_addmenu(winid, int, char, const char *); +staticfn void itemactions_pushkeys(struct obj *, int); + +enum item_action_actions { + IA_NONE = 0, + IA_UNWIELD, /* hack for 'w-' */ + IA_APPLY_OBJ, /* 'a' */ + IA_DIP_OBJ, /* 'a' on a potion == dip */ + IA_NAME_OBJ, /* 'c' name individual item */ + IA_NAME_OTYP, /* 'C' name item's type */ + IA_DROP_OBJ, /* 'd' */ + IA_EAT_OBJ, /* 'e' */ + IA_ENGRAVE_OBJ, /* 'E' */ + IA_FIRE_OBJ, /* 'f' */ + IA_ADJUST_OBJ, /* 'i' #adjust inventory letter */ + IA_ADJUST_STACK, /* 'I' #adjust with count to split stack */ + IA_SACRIFICE, /* 'O' offer sacrifice */ + IA_BUY_OBJ, /* 'p' pay shopkeeper */ + IA_QUAFF_OBJ, + IA_QUIVER_OBJ, + IA_READ_OBJ, + IA_RUB_OBJ, + IA_THROW_OBJ, + IA_TAKEOFF_OBJ, + IA_TIP_CONTAINER, + IA_INVOKE_OBJ, + IA_WIELD_OBJ, + IA_WEAR_OBJ, + IA_SWAPWEAPON, + IA_TWOWEAPON, + IA_ZAP_OBJ, + IA_WHATIS_OBJ, /* '/' specify inventory object */ +}; + +/* construct text for the menu entries for IA_NAME_OBJ and IA_NAME_OTYP */ +staticfn boolean +item_naming_classification( + struct obj *obj, + char *onamebuf, + char *ocallbuf) +{ + static const char + Name[] = "Name", + Rename[] = "Rename or un-name", + Call[] = "Call", + /* "re-call" seems a bit weird, but "recall" and + "rename" don't fit for changing a type name */ + Recall[] = "Re-call or un-call"; + + onamebuf[0] = ocallbuf[0] = '\0'; + if (name_ok(obj) == GETOBJ_SUGGEST) { + Sprintf(onamebuf, "%s %s %s", + (!has_oname(obj) || !*ONAME(obj)) ? Name : Rename, + the_unique_obj(obj) ? "the" + : !is_plural(obj) ? "this specific" + : "this stack of", /*"these",*/ + simpleonames(obj)); + } + if (call_ok(obj) == GETOBJ_SUGGEST) { + char *callname = simpleonames(obj); + + /* prefix known unique item with "the", make all other types plural */ + if (the_unique_obj(obj)) /* treats unID'd fake amulets as if real */ + callname = the(callname); + else if (!is_plural(obj)) + callname = makeplural(callname); + Sprintf(ocallbuf, "%s the type for %s", + (!objects[obj->otyp].oc_uname + || !*objects[obj->otyp].oc_uname) ? Call : Recall, + callname); + } + return (*onamebuf || *ocallbuf) ? TRUE : FALSE; +} + +/* construct text for the menu entries for IA_READ_OBJ */ +staticfn int +item_reading_classification(struct obj *obj, char *outbuf) +{ + int otyp = obj->otyp, res = IA_READ_OBJ; + + *outbuf = '\0'; + if (otyp == FORTUNE_COOKIE) { + Strcpy(outbuf, "Read the message inside this cookie"); + } else if (otyp == T_SHIRT) { + Strcpy(outbuf, "Read the slogan on the shirt"); + } else if (otyp == ALCHEMY_SMOCK) { + Strcpy(outbuf, "Read the slogan on the apron"); + } else if (otyp == HAWAIIAN_SHIRT) { + Strcpy(outbuf, "Look at the pattern on the shirt"); + } else if (obj->oclass == SCROLL_CLASS) { + const char *magic = ((obj->dknown +#ifdef MAIL_STRUCTURES + && otyp != SCR_MAIL +#endif + && (otyp != SCR_BLANK_PAPER + || !objects[otyp].oc_name_known)) + ? " to activate its magic" : ""); + + Sprintf(outbuf, "Read this scroll%s", magic); + } else if (obj->oclass == SPBOOK_CLASS) { + boolean novel = (otyp == SPE_NOVEL), + blank = (otyp == SPE_BLANK_PAPER + && objects[otyp].oc_name_known), + tome = (otyp == SPE_BOOK_OF_THE_DEAD + && objects[otyp].oc_name_known); + + Sprintf(outbuf, "%s this %s", + (novel || blank) ? "Read" : tome ? "Examine" : "Study", + novel ? simpleonames(obj) /* "novel" or "paperback book" */ + : tome ? "tome" : "spellbook"); + } else { + res = IA_NONE; + } + return res; +} + +staticfn void +ia_addmenu(winid win, int act, char let, const char *txt) +{ + anything any; + int clr = NO_COLOR; + + any = cg.zeroany; + any.a_int = act; + add_menu(win, &nul_glyphinfo, &any, let, 0, + ATR_NONE, clr, txt, MENU_ITEMFLAGS_NONE); +} + +/* set up a command to execute on a specific item next */ +staticfn void +itemactions_pushkeys(struct obj *otmp, int act) +{ + switch (act) { + default: + impossible("Unknown item action %d", act); + break; + case IA_NONE: + break; + case IA_UNWIELD: + cmdq_add_ec(CQ_CANNED, (otmp == uwep) ? dowield + : (otmp == uswapwep) ? remarm_swapwep + : (otmp == uquiver) ? dowieldquiver + : donull); /* can't happen */ + cmdq_add_key(CQ_CANNED, HANDS_SYM); + break; + case IA_APPLY_OBJ: + cmdq_add_ec(CQ_CANNED, doapply); + cmdq_add_key(CQ_CANNED, otmp->invlet); + break; + case IA_DIP_OBJ: + /* #altdip instead of normal #dip - takes potion to dip into + first (the inventory item instigating this) and item to + be dipped second, also ignores floor features such as + fountain/sink so we don't need to force m-prefix here */ + cmdq_add_ec(CQ_CANNED, dip_into); + cmdq_add_key(CQ_CANNED, otmp->invlet); + break; + case IA_NAME_OBJ: + case IA_NAME_OTYP: + cmdq_add_ec(CQ_CANNED, docallcmd); + cmdq_add_key(CQ_CANNED, (act == IA_NAME_OBJ) ? 'i' : 'o'); + cmdq_add_key(CQ_CANNED, otmp->invlet); + break; + case IA_DROP_OBJ: + cmdq_add_ec(CQ_CANNED, dodrop); + cmdq_add_key(CQ_CANNED, otmp->invlet); + break; + case IA_EAT_OBJ: + /* start with m-prefix; for #eat, it means ignore floor food + if present and eat food from invent */ + cmdq_add_ec(CQ_CANNED, do_reqmenu); + cmdq_add_ec(CQ_CANNED, doeat); + cmdq_add_key(CQ_CANNED, otmp->invlet); + break; + case IA_ENGRAVE_OBJ: + cmdq_add_ec(CQ_CANNED, doengrave); + cmdq_add_key(CQ_CANNED, otmp->invlet); + break; + case IA_FIRE_OBJ: + cmdq_add_ec(CQ_CANNED, dofire); + break; + case IA_ADJUST_OBJ: + cmdq_add_ec(CQ_CANNED, doorganize); /* #adjust */ + cmdq_add_key(CQ_CANNED, otmp->invlet); + break; + case IA_ADJUST_STACK: + cmdq_add_ec(CQ_CANNED, adjust_split); /* #altadjust */ + cmdq_add_key(CQ_CANNED, otmp->invlet); + break; + case IA_SACRIFICE: + cmdq_add_ec(CQ_CANNED, dosacrifice); + cmdq_add_key(CQ_CANNED, otmp->invlet); + break; + case IA_BUY_OBJ: + cmdq_add_ec(CQ_CANNED, dopay); + cmdq_add_key(CQ_CANNED, otmp->invlet); + break; + case IA_QUAFF_OBJ: + /* start with m-prefix; for #quaff, it means ignore fountain + or sink if present and drink a potion from invent */ + cmdq_add_ec(CQ_CANNED, do_reqmenu); + cmdq_add_ec(CQ_CANNED, dodrink); + cmdq_add_key(CQ_CANNED, otmp->invlet); + break; + case IA_QUIVER_OBJ: + cmdq_add_ec(CQ_CANNED, dowieldquiver); + cmdq_add_key(CQ_CANNED, otmp->invlet); + break; + case IA_READ_OBJ: + cmdq_add_ec(CQ_CANNED, doread); + cmdq_add_key(CQ_CANNED, otmp->invlet); + break; + case IA_RUB_OBJ: + cmdq_add_ec(CQ_CANNED, dorub); + cmdq_add_key(CQ_CANNED, otmp->invlet); + break; + case IA_THROW_OBJ: + cmdq_add_ec(CQ_CANNED, dothrow); + cmdq_add_key(CQ_CANNED, otmp->invlet); + break; + case IA_TAKEOFF_OBJ: + cmdq_add_ec(CQ_CANNED, ia_dotakeoff); /* #altdotakeoff */ + cmdq_add_key(CQ_CANNED, otmp->invlet); + break; + case IA_TIP_CONTAINER: + /* start with m-prefix to skip floor containers; + for menustyle:Traditional when more than one floor container + is present, player will get a #tip menu and have to pick + the "tip something being carried" choice, then this item + will be already chosen from inventory; suboptimal but + possibly an acceptable tradeoff since combining item actions + with use of traditional ggetobj() is an unlikely scenario */ + cmdq_add_ec(CQ_CANNED, do_reqmenu); + cmdq_add_ec(CQ_CANNED, dotip); + cmdq_add_key(CQ_CANNED, otmp->invlet); + break; + case IA_INVOKE_OBJ: + cmdq_add_ec(CQ_CANNED, doinvoke); + cmdq_add_key(CQ_CANNED, otmp->invlet); + break; + case IA_WIELD_OBJ: + cmdq_add_ec(CQ_CANNED, dowield); + cmdq_add_key(CQ_CANNED, otmp->invlet); + break; + case IA_WEAR_OBJ: + cmdq_add_ec(CQ_CANNED, dowear); + cmdq_add_key(CQ_CANNED, otmp->invlet); + break; + case IA_SWAPWEAPON: + cmdq_add_ec(CQ_CANNED, doswapweapon); + break; + case IA_TWOWEAPON: + cmdq_add_ec(CQ_CANNED, dotwoweapon); + break; + case IA_ZAP_OBJ: + cmdq_add_ec(CQ_CANNED, dozap); + cmdq_add_key(CQ_CANNED, otmp->invlet); + break; + case IA_WHATIS_OBJ: + cmdq_add_ec(CQ_CANNED, dowhatis); /* "/" command */ + cmdq_add_key(CQ_CANNED, 'i'); /* "i" == item from inventory */ + cmdq_add_key(CQ_CANNED, otmp->invlet); + break; + } +} + +/* Show menu of possible actions hero could do with item otmp */ +int +itemactions(struct obj *otmp) +{ + int n, act = IA_NONE; + winid win; + char buf[BUFSZ], buf2[BUFSZ]; + menu_item *selected; + struct monst *mtmp; + const char *light = otmp->lamplit ? "Extinguish" : "Light"; + boolean already_worn = (otmp->owornmask & (W_ARMOR | W_ACCESSORY)) != 0; + + win = create_nhwindow(NHW_MENU); + start_menu(win, MENU_BEHAVE_STANDARD); + + /* -: unwield; picking current weapon offers an opportunity for 'w-' + to wield bare/gloved hands; likewise for 'Q-' with quivered item(s) */ + if (otmp == uwep || otmp == uswapwep || otmp == uquiver) { + const char *verb = (otmp == uquiver) ? "Quiver" : "Wield", + *action = (otmp == uquiver) ? "un-ready" : "un-wield", + *which = is_plural(otmp) ? "these" : "this", + *what = ((otmp->oclass == WEAPON_CLASS || is_weptool(otmp)) + ? "weapon" : "item"); + /* + * TODO: if uwep is ammo, tell player that to shoot instead of toss, + * the corresponding launcher must be wielded; + */ + Sprintf(buf, "%s '%c' to %s %s %s", + verb, HANDS_SYM, action, which, + is_plural(otmp) ? makeplural(what) : what); + ia_addmenu(win, IA_UNWIELD, '-', buf); + } + + /* a: apply */ + if (otmp->oclass == COIN_CLASS) + ia_addmenu(win, IA_APPLY_OBJ, 'a', "Flip a coin"); + else if (otmp->otyp == CREAM_PIE) + ia_addmenu(win, IA_APPLY_OBJ, 'a', + "Hit yourself with this cream pie"); + else if (otmp->otyp == BULLWHIP) + ia_addmenu(win, IA_APPLY_OBJ, 'a', "Lash out with this whip"); + else if (otmp->otyp == GRAPPLING_HOOK) + ia_addmenu(win, IA_APPLY_OBJ, 'a', + "Grapple something with this hook"); + else if (otmp->otyp == BAG_OF_TRICKS && objects[otmp->otyp].oc_name_known) + /* bag of tricks skips this unless discovered */ + ia_addmenu(win, IA_APPLY_OBJ, 'a', "Reach into this bag"); + else if (Is_container(otmp)) + /* bag of tricks gets here only if not yet discovered */ + ia_addmenu(win, IA_APPLY_OBJ, 'a', "Open this container"); + else if (otmp->otyp == CAN_OF_GREASE) + ia_addmenu(win, IA_APPLY_OBJ, 'a', "Use the can to grease an item"); + else if (otmp->otyp == LOCK_PICK + || otmp->otyp == CREDIT_CARD + || otmp->otyp == SKELETON_KEY) + ia_addmenu(win, IA_APPLY_OBJ, 'a', "Use this tool to pick a lock"); + else if (otmp->otyp == TINNING_KIT) + ia_addmenu(win, IA_APPLY_OBJ, 'a', "Use this kit to tin a corpse"); + else if (otmp->otyp == LEASH) + ia_addmenu(win, IA_APPLY_OBJ, 'a', "Tie a pet to this leash"); + else if (otmp->otyp == SADDLE) + ia_addmenu(win, IA_APPLY_OBJ, 'a', "Place this saddle on a pet"); + else if (otmp->otyp == MAGIC_WHISTLE + || otmp->otyp == TIN_WHISTLE) + ia_addmenu(win, IA_APPLY_OBJ, 'a', "Blow this whistle"); + else if (otmp->otyp == EUCALYPTUS_LEAF) + ia_addmenu(win, IA_APPLY_OBJ, 'a', "Use this leaf as a whistle"); + else if (otmp->otyp == STETHOSCOPE) + ia_addmenu(win, IA_APPLY_OBJ, 'a', "Listen through the stethoscope"); + else if (otmp->otyp == MIRROR) + ia_addmenu(win, IA_APPLY_OBJ, 'a', "Show something its reflection"); + else if (otmp->otyp == BELL || otmp->otyp == BELL_OF_OPENING) + ia_addmenu(win, IA_APPLY_OBJ, 'a', "Ring the bell"); + else if (otmp->otyp == CANDELABRUM_OF_INVOCATION) { + Sprintf(buf, "%s the candelabrum", light); + ia_addmenu(win, IA_APPLY_OBJ, 'a', buf); + } else if (otmp->otyp == WAX_CANDLE || otmp->otyp == TALLOW_CANDLE) { + boolean multiple = (otmp->quan == 1L) ? FALSE : TRUE; + const char *s = multiple ? "these" : "this"; + struct obj *o = carrying(CANDELABRUM_OF_INVOCATION); + + if (o && o->spe < 7) + Sprintf(buf, "Attach %s to your candelabrum, or %s %s", s, + !otmp->lamplit ? "light" : "extinguish", /* [lowercase] */ + multiple ? "them" : "it"); + else + Sprintf(buf, "%s %s %s", light, s, simpleonames(otmp)); + ia_addmenu(win, IA_APPLY_OBJ, 'a', buf); + } else if (otmp->otyp == OIL_LAMP || otmp->otyp == MAGIC_LAMP + || otmp->otyp == BRASS_LANTERN) { + Sprintf(buf, "%s this light source", light); + ia_addmenu(win, IA_APPLY_OBJ, 'a', buf); + } else if (otmp->otyp == POT_OIL && objects[otmp->otyp].oc_name_known) { + Sprintf(buf, "%s this oil", light); + ia_addmenu(win, IA_APPLY_OBJ, 'a', buf); + } else if (otmp->oclass == POTION_CLASS) { + /* FIXME? this should probably be moved to 'D' rather than be 'a' */ + Sprintf(buf, "Dip something into %s potion%s", + is_plural(otmp) ? "one of these" : "this", plur(otmp->quan)); + ia_addmenu(win, IA_DIP_OBJ, 'a', buf); + } else if (otmp->otyp == EXPENSIVE_CAMERA) + ia_addmenu(win, IA_APPLY_OBJ, 'a', "Take a photograph"); + else if (otmp->otyp == TOWEL) + ia_addmenu(win, IA_APPLY_OBJ, 'a', + "Clean yourself off with this towel"); + else if (otmp->otyp == CRYSTAL_BALL) + ia_addmenu(win, IA_APPLY_OBJ, 'a', "Peer into this crystal ball"); + else if (otmp->otyp == MAGIC_MARKER) + ia_addmenu(win, IA_APPLY_OBJ, 'a', + "Write on something with this marker"); + else if (otmp->otyp == FIGURINE) + ia_addmenu(win, IA_APPLY_OBJ, 'a', "Make this figurine transform"); + else if (otmp->otyp == UNICORN_HORN) + ia_addmenu(win, IA_APPLY_OBJ, 'a', "Use this unicorn horn"); + else if (otmp->otyp == HORN_OF_PLENTY + && objects[otmp->otyp].oc_name_known) + ia_addmenu(win, IA_APPLY_OBJ, 'a', "Blow into the horn of plenty"); + else if (otmp->otyp >= WOODEN_FLUTE && otmp->otyp <= DRUM_OF_EARTHQUAKE) + ia_addmenu(win, IA_APPLY_OBJ, 'a', "Play this musical instrument"); + else if (otmp->otyp == LAND_MINE || otmp->otyp == BEARTRAP) + ia_addmenu(win, IA_APPLY_OBJ, 'a', "Arm this trap"); + else if (otmp->otyp == PICK_AXE || otmp->otyp == DWARVISH_MATTOCK) + ia_addmenu(win, IA_APPLY_OBJ, 'a', "Dig with this digging tool"); + else if (otmp->oclass == WAND_CLASS) + ia_addmenu(win, IA_APPLY_OBJ, 'a', "Break this wand"); + + /* 'c', 'C' - call an item or its type something */ + if (item_naming_classification(otmp, buf, buf2)) { + if (*buf) + ia_addmenu(win, IA_NAME_OBJ, 'c', buf); + if (*buf2) + ia_addmenu(win, IA_NAME_OTYP, 'C', buf2); + } + + /* d: drop item, works on everything except worn items; those will + always have a takeoff/remove choice so we don't have to worry + about the menu maybe being empty when 'd' is suppressed */ + if (!already_worn) { + Sprintf(buf, "Drop this %s", (otmp->quan > 1L) ? "stack" : "item"); + ia_addmenu(win, IA_DROP_OBJ, 'd', buf); + } + + /* e: eat item */ + if (otmp->otyp == TIN) { + Sprintf(buf, "Open %s%s and eat the contents", + (otmp->quan > 1L) ? "one of these tins" : "this tin", + (otmp->otyp == TIN && uwep && uwep->otyp == TIN_OPENER) + ? " with your tin opener" : ""); + ia_addmenu(win, IA_EAT_OBJ, 'e', buf); + } else if (is_edible(otmp)) { + Sprintf(buf, "Eat %s", (otmp->quan > 1L) ? "one of these" : "this"); + ia_addmenu(win, IA_EAT_OBJ, 'e', buf); + } + + /* E: engrave with item */ + if (otmp->otyp == TOWEL) { + ia_addmenu(win, IA_ENGRAVE_OBJ, 'E', + "Wipe the floor with this towel"); + } else if (otmp->otyp == MAGIC_MARKER) { + ia_addmenu(win, IA_ENGRAVE_OBJ, 'E', + "Scribble graffiti on the floor"); + } else if (otmp->oclass == WEAPON_CLASS || otmp->oclass == WAND_CLASS + || otmp->oclass == GEM_CLASS || otmp->oclass == RING_CLASS) { + Sprintf(buf, "%s on the %s with %s", + (is_blade(otmp) || otmp->oclass == WAND_CLASS + || ((otmp->oclass == GEM_CLASS || otmp->oclass == RING_CLASS) + && objects[otmp->otyp].oc_tough)) ? "Engrave" : "Write", + surface(u.ux, u.uy), + (otmp->quan > 1L) ? "one of these items" : "this item"); + ia_addmenu(win, IA_ENGRAVE_OBJ, 'E', buf); + } + + /* f: fire quivered ammo */ + if (otmp == uquiver) { + boolean shoot = ammo_and_launcher(otmp, uwep); + + /* FIXME: see the multi-shot FIXME about "one of" for 't: throw' */ + Sprintf(buf, "%s %s", shoot ? "Shoot" : "Throw", + (otmp->quan > 1L) ? "one of these" : "this"); + if (shoot) { + assert(uwep != NULL); + Sprintf(eos(buf), " with your wielded %s", simpleonames(uwep)); + } + ia_addmenu(win, IA_FIRE_OBJ, 'f', buf); + } + + /* i: #adjust inventory letter; gold can't be adjusted unless there + is some in a slot other than '$' (which shouldn't be possible) */ + if (otmp->oclass != COIN_CLASS || check_invent_gold("item-action")) + ia_addmenu(win, IA_ADJUST_OBJ, 'i', + "Adjust inventory by assigning new letter"); + /* I: #adjust inventory item by splitting its stack */ + if (otmp->quan > 1L && otmp->oclass != COIN_CLASS) + ia_addmenu(win, IA_ADJUST_STACK, 'I', + "Adjust inventory by splitting this stack"); + + /* O: offer sacrifice */ + if (IS_ALTAR(levl[u.ux][u.uy].typ) && !u.uswallow) { + /* FIXME: this doesn't match #offer's likely candidates, which don't + include corpses on Astral and don't include amulets off Astral */ + if (otmp->otyp == CORPSE) + ia_addmenu(win, IA_SACRIFICE, 'O', + "Offer this corpse as a sacrifice at this altar"); + else if (otmp->otyp == AMULET_OF_YENDOR + || otmp->otyp == FAKE_AMULET_OF_YENDOR) + ia_addmenu(win, IA_SACRIFICE, 'O', + "Offer this amulet as a sacrifice at this altar"); + } + + /* p: pay for unpaid utems */ + if (otmp->unpaid + /* FIXME: should also handle player owned container (so not + flagged 'unpaid') holding shop owned items */ + && (mtmp = shop_keeper(*in_rooms(u.ux, u.uy, SHOPBASE))) != 0 + && inhishop(mtmp)) { + Sprintf(buf, "Buy this unpaid %s", + (otmp->quan > 1L) ? "stack" : "item"); + ia_addmenu(win, IA_BUY_OBJ, 'p', buf); + } + + /* P: put on accessory */ + if (!already_worn) { + /* if 'otmp' is worn, we'll skip 'P' and show 'R' below; + if not worn, we show 'P - Put on this ' if + the slot is available, or 'P - '; for the latter, + 'P' will fail but we don't want to omit the choice because + item actions can be used to learn commands */ + *buf = '\0'; + if (otmp->oclass == AMULET_CLASS) { + Strcpy(buf, !uamul ? "Put this amulet on" + : "[already wearing an amulet]"); + } else if (otmp->oclass == RING_CLASS || otmp->otyp == MEAT_RING) { + if (!uleft || !uright) + Strcpy(buf, "Put this ring on"); + else + Sprintf(buf, "[both ring %s in use]", + makeplural(body_part(FINGER))); + } else if (otmp->otyp == BLINDFOLD || otmp->otyp == TOWEL + || otmp->otyp == LENSES) { + if (ublindf) + Strcpy(buf, "[already wearing eyewear]"); + else if (otmp->otyp == LENSES) + Strcpy(buf, "Put these lenses on"); + else + Sprintf(buf, "Put this on%s", + (otmp->otyp == TOWEL) ? " to blindfold yourself" : ""); + } + if (*buf) + ia_addmenu(win, IA_WEAR_OBJ, 'P', buf); + } + + /* q: drink item */ + if (otmp->oclass == POTION_CLASS) { + Sprintf(buf, "Quaff (drink) %s", + (otmp->quan > 1L) ? "one of these potions" : "this potion"); + ia_addmenu(win, IA_QUAFF_OBJ, 'q', buf); + } + + /* Q: quiver throwable item */ + if ((otmp->oclass == GEM_CLASS || otmp->oclass == WEAPON_CLASS) + && otmp != uquiver) { + Sprintf(buf, "Quiver this %s for easy %s with \'f\'ire", + (otmp->quan > 1L) ? "stack" : "item", + ammo_and_launcher(otmp, uwep) ? "shooting" : "throwing"); + ia_addmenu(win, IA_QUIVER_OBJ, 'Q', buf); + } + + /* r: read item */ + if (item_reading_classification(otmp, buf) == IA_READ_OBJ) + ia_addmenu(win, IA_READ_OBJ, 'r', buf); + + /* R: remove accessory or rub item */ + if (otmp->owornmask & W_ACCESSORY) { + Sprintf(buf, "Remove this %s", + (otmp->owornmask & W_AMUL) ? "amulet" + : (otmp->owornmask & W_RING) ? "ring" + : (otmp->owornmask & W_TOOL) ? "eyewear" + : "accessory"); /* catchall -- can't happen */ + ia_addmenu(win, IA_TAKEOFF_OBJ, 'R', buf); + } + if (otmp->otyp == OIL_LAMP || otmp->otyp == MAGIC_LAMP + || otmp->otyp == BRASS_LANTERN) { + Sprintf(buf, "Rub this %s", simpleonames(otmp)); + ia_addmenu(win, IA_RUB_OBJ, 'R', buf); + } else if (otmp->oclass == GEM_CLASS && is_graystone(otmp)) + ia_addmenu(win, IA_RUB_OBJ, 'R', "Rub something on this stone"); + + /* t: throw item */ + if (!already_worn) { + boolean shoot = ammo_and_launcher(otmp, uwep); + + /* + * FIXME: + * 'one of these' should be changed to 'some of these' when there + * is the possibility of a multi-shot volley but we don't have + * any way to determine that except by actually calculating the + * volley count and that could randomly yield 1 here and 2..N + * while throwing or vice versa. + */ + Sprintf(buf, "%s %s%s", shoot ? "Shoot" : "Throw", + (otmp->quan == 1L) ? "this item" + : (otmp->otyp == GOLD_PIECE) ? "them" + : "one of these", + /* if otmp is quivered, we've already listed + 'f - shoot|throw this item' as a choice; + if 't' is duplicating that, say so ('t' and 'f' + behavior differs for throwing a stack of gold) */ + (otmp == uquiver && (otmp->otyp != GOLD_PIECE + || otmp->quan == 1L)) + ? " (same as 'f')" : ""); + ia_addmenu(win, IA_THROW_OBJ, 't', buf); + } + + /* T: take off armor, tip carried container */ + if (otmp->owornmask & W_ARMOR) + ia_addmenu(win, IA_TAKEOFF_OBJ, 'T', "Take off this armor"); + if ((Is_container(otmp) && (Has_contents(otmp) || !otmp->cknown)) + || (otmp->otyp == HORN_OF_PLENTY && (otmp->spe > 0 || !otmp->known))) + ia_addmenu(win, IA_TIP_CONTAINER, 'T', + "Tip all the contents out of this container"); + + /* V: invoke */ + if ((otmp->otyp == FAKE_AMULET_OF_YENDOR && !otmp->known) + || otmp->oartifact || objects[otmp->otyp].oc_unique + /* non-artifact crystal balls don't have any unique power but + the #invoke command lists them as likely candidates */ + || otmp->otyp == CRYSTAL_BALL) + ia_addmenu(win, IA_INVOKE_OBJ, 'V', + "Try to invoke a unique power of this object"); + + /* w: wield, hold in hands, works on everything but with different + advice text; not mentioned for things that are already wielded */ + if (otmp == uwep || cantwield(gy.youmonst.data)) { + ; /* either already wielded or can't wield anything; skip 'w' */ + } else if (otmp->oclass == WEAPON_CLASS || is_weptool(otmp) + || is_wet_towel(otmp) || otmp->otyp == HEAVY_IRON_BALL) { + Sprintf(buf, "Wield this %s as your weapon", + (otmp->quan > 1L) ? "stack" : "item"); + ia_addmenu(win, IA_WIELD_OBJ, 'w', buf); + } else if (otmp->otyp == TIN_OPENER) { + ia_addmenu(win, IA_WIELD_OBJ, 'w', + "Wield the tin opener to easily open tins"); + } else if (!already_worn) { + /* originally this was using "hold this item in your hands" but + there's no concept of "holding an item", plus it unwields + whatever item you already have wielded so use "wield this item" */ + Sprintf(buf, "Wield this %s in your %s", + (otmp->quan > 1L) ? "stack" : "item", + /* only two-handed weapons and unicorn horns care about + pluralizing "hand" and they won't reach here, but plural + sounds better when poly'd into something with "claw" */ + makeplural(body_part(HAND))); + ia_addmenu(win, IA_WIELD_OBJ, 'w', buf); + } + + /* W: wear armor */ + if (!already_worn) { + if (otmp->oclass == ARMOR_CLASS) { + /* if 'otmp' is worn we skip 'W' (and show 'T' above instead); + if it isn't, we either show "W - wear this" if otmp's slot + isn't populated, or "W - [already wearing ]"; + for the latter, picking 'W' will fail but we don't want to + omit 'W' in this situation */ + long Wmask = armcat_to_wornmask(objects[otmp->otyp].oc_armcat); + struct obj *o = wearmask_to_obj(Wmask); + + if (!o) + Strcpy(buf, "Wear this armor"); + else + Sprintf(buf, "[already wearing %s]", an(armor_simple_name(o))); + + ia_addmenu(win, IA_WEAR_OBJ, 'W', buf); + } + } + + /* x: Swap main and readied weapon */ + if (otmp == uwep && uswapwep) + ia_addmenu(win, IA_SWAPWEAPON, 'x', + "Swap this with your alternate weapon"); + else if (otmp == uwep) + ia_addmenu(win, IA_SWAPWEAPON, 'x', + "Ready this as an alternate weapon"); + else if (otmp == uswapwep) + ia_addmenu(win, IA_SWAPWEAPON, 'x', + "Swap this with your main weapon"); + + /* this is based on TWOWEAPOK() in wield.c; we don't call can_two_weapon() + because it is very verbose; attempting to two-weapon might be rejected + but we screen out most reasons for rejection before offering it as a + choice */ +#define MAYBETWOWEAPON(obj) \ + ((((obj)->oclass == WEAPON_CLASS) \ + ? !(is_launcher(obj) || is_ammo(obj) || is_missile(obj)) \ + : is_weptool(obj)) \ + && !bimanual(obj)) + + /* X: Toggle two-weapon mode on or off */ + if ((otmp == uwep || otmp == uswapwep) + /* if already two-weaponing, no special checks needed to toggle off */ + && (u.twoweap + /* but if not, try to filter most "you can't do that" here */ + || (could_twoweap(gy.youmonst.data) && !uarms + && uwep && MAYBETWOWEAPON(uwep) + && uswapwep && MAYBETWOWEAPON(uswapwep)))) { + Sprintf(buf, "Toggle two-weapon combat %s", u.twoweap ? "off" : "on"); + ia_addmenu(win, IA_TWOWEAPON, 'X', buf); + } + +#undef MAYBETWOWEAPON + + /* z: Zap wand */ + if (otmp->oclass == WAND_CLASS) + ia_addmenu(win, IA_ZAP_OBJ, 'z', + "Zap this wand to release its magic"); + + /* ?: Look up an item in the game's database */ + if (ia_checkfile(otmp)) { + Sprintf(buf, "Look up information about %s", + (otmp->quan > 1L) ? "these" : "this"); + ia_addmenu(win, IA_WHATIS_OBJ, '/', buf); + } + + Sprintf(buf, "Do what with %s?", the(cxname(otmp))); + end_menu(win, buf); + + n = select_menu(win, PICK_ONE, &selected); + + if (n > 0) { + act = selected[0].item.a_int; + free((genericptr_t) selected); + + itemactions_pushkeys(otmp, act); + } + destroy_nhwindow(win); + + /* finish the 'i' command: no time elapses and cancelling without + selecting an action doesn't matter */ + return ECMD_OK; +} + +/*iactions.c*/ diff --git a/src/insight.c b/src/insight.c new file mode 100644 index 000000000..b6e56cd19 --- /dev/null +++ b/src/insight.c @@ -0,0 +1,3506 @@ +/* NetHack 5.0 insight.c $NHDT-Date: 1777004419 2026/04/23 20:20:19 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.134 $ */ +/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ +/* NetHack may be freely redistributed. See license for details. */ + +/* + * Enlightenment and Conduct+Achievements and Vanquished+Extinct+Geno'd + * and stethoscope/probing feedback. + * + * Most code used to reside in cmd.c, presumably because ^X was originally + * a wizard mode command and the majority of those are in that file. + * Some came from end.c where it is used during end of game disclosure. + * And some came from priest.c that had once been in pline.c. + */ + +#include "hack.h" + +staticfn void enlght_out(const char *); +staticfn void enlght_line(const char *, const char *, const char *, + const char *); +staticfn char *enlght_combatinc(const char *, int, int, char *); +staticfn void enlght_halfdmg(int, int); +staticfn boolean walking_on_water(void); +staticfn boolean cause_known(int); +staticfn char *attrval(int, int, char *); +staticfn char *fmt_elapsed_time(char *, int); +staticfn char *N_times(long, char *) NONNULL NONNULLARG2; +staticfn void background_enlightenment(int, int); +staticfn void basics_enlightenment(int, int); +staticfn void characteristics_enlightenment(int, int); +staticfn void one_characteristic(int, int, int); +staticfn void status_enlightenment(int, int); +staticfn void weapon_insight(int); +staticfn void attributes_enlightenment(int, int); +staticfn void show_achievements(int); +staticfn int QSORTCALLBACK vanqsort_cmp(const genericptr, const genericptr); +staticfn int num_extinct(void); +staticfn int num_gone(int, int *); +staticfn char *size_str(int); +staticfn void item_resistance_message(int, const char *, int); + +extern const char *const hu_stat[]; /* hunger status from eat.c */ +extern const char *const enc_stat[]; /* encumbrance status from botl.c */ + +static const char You_[] = "You ", are[] = "are ", were[] = "were ", + have[] = "have ", had[] = "had ", can[] = "can ", + could[] = "could "; +static const char have_been[] = "have been ", have_never[] = "have never ", + never[] = "never "; + +/* for livelogging: */ +struct ll_achieve_msg { + long llflag; + const char *msg; +}; +/* ordered per 'enum achievements' in you.h */ +/* take care to keep them in sync! */ +static struct ll_achieve_msg achieve_msg [] = { + { 0, "" }, /* actual achievements are numbered from 1 */ + { LL_ACHIEVE, "acquired the Bell of Opening" }, + { LL_ACHIEVE, "entered Gehennom" }, + { LL_ACHIEVE, "acquired the Candelabrum of Invocation" }, + { LL_ACHIEVE, "acquired the Book of the Dead" }, + { LL_ACHIEVE, "performed the invocation" }, + { LL_ACHIEVE, "acquired The Amulet of Yendor" }, + { LL_ACHIEVE, "entered the Elemental Planes" }, + { LL_ACHIEVE, "entered the Astral Plane" }, + { LL_ACHIEVE, "ascended" }, + /* if the type of item isn't discovered yet, disclosing the event + via #chronicle would be a spoiler (particularly for gray stone); + the ID'd name for the type of item will be appended to the next + two messages, for display via livelog and/or dumplog */ + { LL_ACHIEVE | LL_SPOILER, "acquired the Mines' End" }, /* " luckstone" */ + { LL_ACHIEVE | LL_SPOILER, "acquired the Sokoban" }, /* " " */ + { LL_ACHIEVE | LL_UMONST, "killed Medusa" }, + /* these two are not logged */ + { 0, "hero was always blond, no, blind" }, + { 0, "hero never wore armor" }, + /* */ + { LL_MINORAC | LL_DUMP, "entered the Gnomish Mines" }, + { LL_ACHIEVE, "reached Mine Town" }, /* probably minor, but dnh logs it */ + { LL_MINORAC, "entered a shop" }, + { LL_MINORAC, "entered a temple" }, + { LL_ACHIEVE, "consulted the Oracle" }, /* minor, but rare enough */ + { LL_MINORAC | LL_DUMP, "read a Discworld novel" }, /* even more so */ + { LL_ACHIEVE, "entered Sokoban" }, /* keep as major for turn comparison + * with completed sokoban */ + { LL_ACHIEVE, "entered the Bigroom" }, + /* The following 8 are for advancing through the ranks + and messages differ by role so are created on the fly; + rank 0 (Xp 1 and 2) isn't an achievement */ + { LL_MINORAC | LL_DUMP, "" }, /* Xp 3 */ + { LL_MINORAC | LL_DUMP, "" }, /* Xp 6 */ + { LL_MINORAC | LL_DUMP, "" }, /* Xp 10 */ + { LL_ACHIEVE, "" }, /* Xp 14, so able to attempt the quest */ + { LL_ACHIEVE, "" }, /* Xp 18 */ + { LL_ACHIEVE, "" }, /* Xp 22 */ + { LL_ACHIEVE, "" }, /* Xp 26 */ + { LL_ACHIEVE, "" }, /* Xp 30 */ + { LL_MINORAC, "learned castle drawbridge's tune" }, /* achievement #31 */ + { 0, "" } /* keep this one at the end */ +}; + +/* macros to simplify output of enlightenment messages; also used by + conduct and achievements */ +#define enl_msg(prefix, present, past, suffix, ps) \ + enlght_line((prefix), final ? (past) : (present), (suffix), (ps)) +#define you_are(attr, ps) enl_msg(You_, are, were, (attr), (ps)) +#define you_have(attr, ps) enl_msg(You_, have, had, (attr), (ps)) +#define you_can(attr, ps) enl_msg(You_, can, could, (attr), (ps)) +#define you_have_been(goodthing) \ + enl_msg(You_, have_been, were, (goodthing), "") +#define you_have_never(badthing) \ + enl_msg(You_, have_never, never, (badthing), "") +#define you_have_X(something) \ + enl_msg(You_, have, (const char *) "", (something), "") + +staticfn void +enlght_out(const char *buf) +{ + if (ge.en_via_menu) { + add_menu_str(ge.en_win, buf); + } else + putstr(ge.en_win, 0, buf); +} + +staticfn void +enlght_line( + const char *start, + const char *middle, + const char *end, + const char *ps) +{ +#ifndef NO_ENLGHT_CONTRACTIONS + static const struct contrctn { + const char *twowords, *contrctn; + } contra[] = { + { " are not ", " aren't " }, + { " were not ", " weren't " }, + { " have not ", " haven't " }, + { " had not ", " hadn't " }, + { " can not ", " can't " }, + { " could not ", " couldn't " }, + }; + int i; +#endif + char buf[BUFSZ]; + + Sprintf(buf, " %s%s%s%s.", start, middle, end, ps); +#ifndef NO_ENLGHT_CONTRACTIONS + if (strstri(buf, " not ")) { /* TODO: switch to libc strstr() */ + for (i = 0; i < SIZE(contra); ++i) + (void) strsubst(buf, contra[i].twowords, contra[i].contrctn); + } +#endif + enlght_out(buf); +} + +/* format increased chance to hit or damage or defense (Protection) */ +staticfn char * +enlght_combatinc( + const char *inctyp, /* "to hit" or "damage" or "defense" */ + int incamt, /* amount of increment (negative if decrement) */ + int final, /* ENL_{GAMEINPROGRESS,GAMEOVERALIVE,GAMEOVERDEAD} */ + char *outbuf) +{ + const char *modif, *bonus; + boolean invrt; + int absamt; + + absamt = abs(incamt); + /* Protection amount is typically larger than damage or to-hit; + reduce magnitude by a third in order to stretch modifier ranges + (small:1..5, moderate:6..10, large:11..19, huge:20+) */ + if (!strcmp(inctyp, "defense")) + absamt = (absamt * 2) / 3; + + if (absamt <= 3) + modif = "small"; + else if (absamt <= 6) + modif = "moderate"; + else if (absamt <= 12) + modif = "large"; + else + modif = "huge"; + + modif = !incamt ? "no" : an(modif); /* ("no" case shouldn't happen) */ + bonus = (incamt >= 0) ? "bonus" : "penalty"; + /* "bonus " (to hit) vs " bonus" (damage, defense) */ + invrt = strcmp(inctyp, "to hit") ? TRUE : FALSE; + + Sprintf(outbuf, "%s %s %s", modif, invrt ? inctyp : bonus, + invrt ? bonus : inctyp); + if (final || wizard) + Sprintf(eos(outbuf), " (%s%d)", (incamt > 0) ? "+" : "", incamt); + + return outbuf; +} + +/* report half physical or half spell damage */ +staticfn void +enlght_halfdmg(int category, int final) +{ + const char *category_name; + char buf[BUFSZ]; + + switch (category) { + case HALF_PHDAM: + category_name = "physical"; + break; + case HALF_SPDAM: + category_name = "spell"; + break; + default: + category_name = "unknown"; + break; + } + Sprintf(buf, " %s %s damage", (final || wizard) ? "half" : "reduced", + category_name); + enl_msg(You_, "take", "took", buf, from_what(category)); +} + +/* is hero actively using water walking capability on water (or lava)? */ +staticfn boolean +walking_on_water(void) +{ + if (u.uinwater || Levitation || Flying) + return FALSE; + return (boolean) (Wwalking && is_pool_or_lava(u.ux, u.uy)); +} + +/* describe u.utraptype; used by status_enlightenment() and self_lookat() */ +char * +trap_predicament(char *outbuf, int final, boolean wizxtra) +{ + struct trap *t; + + /* caller has verified u.utrap */ + *outbuf = '\0'; + switch (u.utraptype) { + case TT_BURIEDBALL: + Strcpy(outbuf, "tethered to something buried"); + break; + case TT_LAVA: + Sprintf(outbuf, "sinking into %s", final ? "lava" : hliquid("lava")); + break; + case TT_INFLOOR: + Sprintf(outbuf, "stuck in %s", the(surface(u.ux, u.uy))); + break; + default: /* TT_BEARTRAP, TT_PIT, or TT_WEB */ + Strcpy(outbuf, "trapped"); + if ((t = t_at(u.ux, u.uy)) != 0) /* should never be null */ + Sprintf(eos(outbuf), " in %s", an(trapname(t->ttyp, FALSE))); + break; + } + if (wizxtra) { /* give extra information for wizard mode enlightenment */ + /* curly braces: u.utrap is an escape attempt counter rather than a + turn timer so use different ornamentation than usual parentheses */ + Sprintf(eos(outbuf), " {%u}", u.utrap); + } + return outbuf; +} + +/* check whether hero is wearing something that player definitely knows + confers the target property; item must have been seen and its type + discovered but it doesn't necessarily have to be fully identified */ +staticfn boolean +cause_known( + int propindx) /* index of a property which can be conveyed by worn item */ +{ + struct obj *o; + long mask = W_ARMOR | W_AMUL | W_RING | W_TOOL; + + /* simpler than from_what()/what_gives(); we don't attempt to + handle artifacts and we deliberately ignore wielded items */ + for (o = gi.invent; o; o = o->nobj) { + if (!(o->owornmask & mask)) + continue; + if ((int) objects[o->otyp].oc_oprop == propindx + && objects[o->otyp].oc_name_known && o->dknown) + return TRUE; + } + return FALSE; +} + +/* format a characteristic value, accommodating Strength's strangeness */ +staticfn char * +attrval( + int attrindx, + int attrvalue, + char resultbuf[]) /* should be at least [7] to hold "18/100\0" */ +{ + if (attrindx != A_STR || attrvalue <= 18) + Sprintf(resultbuf, "%d", attrvalue); + else if (attrvalue > STR18(100)) /* 19 to 25 */ + Sprintf(resultbuf, "%d", attrvalue - 100); + else /* simplify "18/\**" to be "18/100" */ + Sprintf(resultbuf, "18/%02d", attrvalue - 18); + return resultbuf; +} + +/* format urealtime.realtime as + " D days, H hours, M minutes and S seconds" + with any fields having a value of 0 omitted: + 0-00:00:20 => " 20 seconds" + 0-00:15:05 => " 15 minutes and 5 seconds" + 0-00:16:00 => " 16 minutes" + 0-01:15:10 => " 1 hour, 15 minutes and 10 seconds" + 0-02:00:01 => " 2 hours and 1 second" + 3-00:25:40 => " 3 days, 25 minutes and 40 seconds" + (note: for a list of more than two entries, nethack usually includes the + [style-wise] optional comma before "and" but in this instance it does not) + */ +staticfn char * +fmt_elapsed_time(char *outbuf, int final) +{ + int fieldcnt; + long edays, ehours, eminutes, eseconds; + /* for a game that's over, reallydone() has updated urealtime.realtime + to its final value before calling us during end of game disclosure; + for a game that's still in progress, it holds the amount of elapsed + game time from previous sessions up through most recent save/restore + (or up through latest level change when 'checkpoint' is On); + '.start_timing' has a non-zero value even if '.realtime' is 0 */ + long etim = urealtime.realtime; + + if (!final) + etim += timet_delta(getnow(), urealtime.start_timing); + /* we could use localtime() to convert the value into a 'struct tm' + to get date and time fields but this is simple and straightforward */ + eseconds = etim % 60L, etim /= 60L; + eminutes = etim % 60L, etim /= 60L; + ehours = etim % 24L; + edays = etim / 24L; + fieldcnt = !!edays + !!ehours + !!eminutes + !!eseconds; + + Strcpy(outbuf, fieldcnt ? "" : " none"); /* 'none' should never happen */ + if (edays) { + Sprintf(eos(outbuf), " %ld day%s", edays, plur(edays)); + if (fieldcnt > 1) /* hours and/or minutes and/or seconds to follow */ + Strcat(outbuf, (fieldcnt == 2) ? " and" : ","); + --fieldcnt; /* edays has been processed */ + } + if (ehours) { + Sprintf(eos(outbuf), " %ld hour%s", ehours, plur(ehours)); + if (fieldcnt > 1) /* minutes and/or seconds to follow */ + Strcat(outbuf, (fieldcnt == 2) ? " and" : ","); + --fieldcnt; /* ehours has been processed */ + } + if (eminutes) { + Sprintf(eos(outbuf), " %ld minute%s", eminutes, plur(eminutes)); + if (fieldcnt > 1) /* seconds to follow */ + Strcat(outbuf, " and"); + /* eminutes has been processed but no need to decrement fieldcnt */ + } + if (eseconds) + Sprintf(eos(outbuf), " %ld second%s", eseconds, plur(eseconds)); + return outbuf; +} + +/* "once" vs "twice" vs "17 times", used in several places */ +staticfn char * +N_times(long n, char *outbuf) +{ + switch (n) { + case 0: + default: + Sprintf(outbuf, "%ld times", n); + break; + case 1: + Strcpy(outbuf, "once"); + break; + case 2: + Strcpy(outbuf, "twice"); + break; + case 3: + Strcpy(outbuf, "thrice"); + break; + } + return outbuf; +} + +void +enlightenment( + int mode, /* BASICENLIGHTENMENT | MAGICENLIGHTENMENT (| both) */ + int final) /* ENL_GAMEINPROGRESS:0, ENL_GAMEOVERALIVE, ENL_GAMEOVERDEAD */ +{ + char buf[BUFSZ], tmpbuf[BUFSZ]; + + ge.en_win = create_nhwindow(NHW_MENU); + ge.en_via_menu = !final; + if (ge.en_via_menu) + start_menu(ge.en_win, MENU_BEHAVE_STANDARD); + + Strcpy(tmpbuf, svp.plname); + *tmpbuf = highc(*tmpbuf); /* same adjustment as bottom line */ + /* as in background_enlightenment, when poly'd we need to use the saved + gender in u.mfemale rather than the current you-as-monster gender */ + Snprintf(buf, sizeof(buf), "%s the %s's attributes:", tmpbuf, + ((Upolyd ? u.mfemale : flags.female) && gu.urole.name.f) + ? gu.urole.name.f + : gu.urole.name.m); + + /* title */ + enlght_out(buf); /* "Conan the Archeologist's attributes:" */ + /* background and characteristics; ^X or end-of-game disclosure */ + if (mode & BASICENLIGHTENMENT) { + /* role, race, alignment, deities, dungeon level, time, experience */ + background_enlightenment(mode, final); + /* hit points, energy points, armor class, gold */ + basics_enlightenment(mode, final); + /* strength, dexterity, &c */ + characteristics_enlightenment(mode, final); + } + /* expanded status line information, including things which aren't + included there due to space considerations; + shown for both basic and magic enlightenment */ + status_enlightenment(mode, final); + /* remaining attributes; shown for potion,&c or wizard mode and + explore mode ^X or end of game disclosure */ + if (mode & MAGICENLIGHTENMENT) { + /* intrinsics and other traditional enlightenment feedback */ + attributes_enlightenment(mode, final); + } + + enlght_out(""); /* separator */ + enlght_out("Miscellaneous:"); + /* reminder to player and/or information for dumplog */ + if ((mode & BASICENLIGHTENMENT) != 0 && (wizard || discover || final)) { + if (wizard || discover) { + Sprintf(buf, "running in %s mode", wizard ? "debug" : "explore"); + you_are(buf, ""); + } + + if (!flags.bones) { + /* mention not saving bones iff hero just died */ + Sprintf(buf, "disabled loading%s of bones levels", + (final == ENL_GAMEOVERDEAD) ? " and storing" : ""); + you_have_X(buf); + } else if (!u.uroleplay.numbones) { + enl_msg(You_, "haven't encountered", "didn't encounter", + " any bones levels", ""); + } else { + Sprintf(buf, "encountered %ld bones level%s", + u.uroleplay.numbones, plur(u.uroleplay.numbones)); + you_have_X(buf); + } + } + (void) fmt_elapsed_time(buf, final); + enl_msg("Total elapsed playing time ", "is", "was", buf, ""); + + if (!ge.en_via_menu) { + display_nhwindow(ge.en_win, TRUE); + } else { + menu_item *selected = 0; + + end_menu(ge.en_win, (char *) 0); + if (select_menu(ge.en_win, PICK_NONE, &selected) > 0) + free((genericptr_t) selected); + ge.en_via_menu = FALSE; + } + destroy_nhwindow(ge.en_win); + ge.en_win = WIN_ERR; +} + +/*ARGSUSED*/ +/* display role, race, alignment and such to en_win */ +staticfn void +background_enlightenment(int unused_mode UNUSED, int final) +{ + const char *role_titl, *rank_titl; + int innategend, difgend, difalgn; + char buf[BUFSZ], tmpbuf[BUFSZ]; + + /* note that if poly'd, we need to use u.mfemale instead of flags.female + to access hero's saved gender-as-human/elf/&c rather than current */ + innategend = (Upolyd ? u.mfemale : flags.female) ? 1 : 0; + role_titl = (innategend && gu.urole.name.f) ? gu.urole.name.f + : gu.urole.name.m; + rank_titl = rank_of(u.ulevel, Role_switch, innategend); + + enlght_out(""); /* separator after title */ + enlght_out("Background:"); + + /* if polymorphed, report current shape before underlying role; + will be repeated as first status: "you are transformed" and also + among various attributes: "you are in beast form" (after being + told about lycanthropy) or "you are polymorphed into " + (with countdown timer appended for wizard mode); we really want + the player to know he's not a samurai at the moment... */ + if (Upolyd) { + char anbuf[20]; /* includes trailing space; [4] suffices */ + struct permonst *uasmon = gy.youmonst.data; + boolean altphrasing = vampshifted(&gy.youmonst); + + tmpbuf[0] = '\0'; + /* here we always use current gender, not saved role gender */ + if (!is_male(uasmon) && !is_female(uasmon) && !is_neuter(uasmon)) + Sprintf(tmpbuf, "%s ", genders[flags.female ? 1 : 0].adj); + if (altphrasing) + Sprintf(eos(tmpbuf), "%s in ", + pmname(&mons[gy.youmonst.cham], + flags.female ? FEMALE : MALE)); + Snprintf(buf, sizeof(buf), "%s%s%s%s form", + !final ? "currently " : "", + altphrasing ? just_an(anbuf, tmpbuf) : "in ", + tmpbuf, pmname(uasmon, flags.female ? FEMALE : MALE)); + you_are(buf, ""); + } + + /* report role; omit gender if it's redundant (eg, "female priestess") */ + tmpbuf[0] = '\0'; + if (!gu.urole.name.f + && ((gu.urole.allow & ROLE_GENDMASK) == (ROLE_MALE | ROLE_FEMALE) + || innategend != flags.initgend)) + Sprintf(tmpbuf, "%s ", genders[innategend].adj); + buf[0] = '\0'; + if (Upolyd) + Strcpy(buf, "actually "); /* "You are actually a ..." */ + if (!strcmpi(rank_titl, role_titl)) { + /* omit role when rank title matches it */ + Sprintf(eos(buf), "%s, level %d %s%s", an(rank_titl), u.ulevel, + tmpbuf, gu.urace.noun); + } else { + Sprintf(eos(buf), "%s, a level %d %s%s %s", an(rank_titl), u.ulevel, + tmpbuf, gu.urace.adj, role_titl); + } + you_are(buf, ""); + + /* report alignment (bypass you_are() in order to omit ending period); + adverb is used to distinguish between temporary change (helm of opp. + alignment), permanent change (one-time conversion), and original */ + Sprintf(buf, " %s%s%s, %son a mission for %s", + You_, !final ? are : were, + align_str(u.ualign.type), + /* helm of opposite alignment (might hide conversion) */ + (u.ualign.type != u.ualignbase[A_CURRENT]) + /* what's the past tense of "currently"? if we used "formerly" + it would sound like a reference to the original alignment */ + ? (!final ? "currently " : "temporarily ") + /* permanent conversion */ + : (u.ualign.type != u.ualignbase[A_ORIGINAL]) + /* and what's the past tense of "now"? certainly not "then" + in a context like this...; "belatedly" == weren't that + way sooner (in other words, didn't start that way) */ + ? (!final ? "now " : "belatedly ") + /* atheist (ignored in very early game) */ + : (!u.uconduct.gnostic && svm.moves > 1000L) + ? "nominally " + /* lastly, normal case */ + : "", + u_gname()); + enlght_out(buf); + /* show the rest of this game's pantheon (finishes previous sentence) + [appending "also Moloch" at the end would allow for straightforward + trailing "and" on all three aligned entries but looks too verbose] */ + Sprintf(buf, " who %s opposed by", !final ? "is" : "was"); + if (u.ualign.type != A_LAWFUL) + Sprintf(eos(buf), " %s (%s) and", align_gname(A_LAWFUL), + align_str(A_LAWFUL)); + if (u.ualign.type != A_NEUTRAL) + Sprintf(eos(buf), " %s (%s)%s", align_gname(A_NEUTRAL), + align_str(A_NEUTRAL), + (u.ualign.type != A_CHAOTIC) ? " and" : ""); + if (u.ualign.type != A_CHAOTIC) + Sprintf(eos(buf), " %s (%s)", align_gname(A_CHAOTIC), + align_str(A_CHAOTIC)); + Strcat(buf, "."); /* terminate sentence */ + enlght_out(buf); + + /* show original alignment,gender,race,role if any have been changed; + giving separate message for temporary alignment change bypasses need + for tricky phrasing otherwise necessitated by possibility of having + helm of opposite alignment mask a permanent alignment conversion */ + difgend = (innategend != flags.initgend); + difalgn = (((u.ualign.type != u.ualignbase[A_CURRENT]) ? 1 : 0) + + ((u.ualignbase[A_CURRENT] != u.ualignbase[A_ORIGINAL]) + ? 2 : 0)); + if (difalgn & 1) { /* have temporary alignment so report permanent one */ + Sprintf(buf, "actually %s", align_str(u.ualignbase[A_CURRENT])); + you_are(buf, ""); + difalgn &= ~1; /* suppress helm from "started out " message */ + } + if (difgend || difalgn) { /* sex change or perm align change or both */ + Sprintf(buf, " You started out %s%s%s.", + difgend ? genders[flags.initgend].adj : "", + (difgend && difalgn) ? " and " : "", + difalgn ? align_str(u.ualignbase[A_ORIGINAL]) : ""); + enlght_out(buf); + } + + /* "You are left-handed." won't work well if polymorphed into something + without hands; use "You are normally left-handed." in that situation */ + Sprintf(buf, "%s%s-handed", + !strcmp(body_part(HANDED), "handed") ? "" : "normally ", + URIGHTY ? "right" : "left"); + you_are(buf, ""); + + /* As of 3.6.2: dungeon level, so that ^X really has all status info as + claimed by the comment below; this reveals more information than + the basic status display, but that's one of the purposes of ^X; + similar information is revealed by #overview; the "You died in + " given by really_done() is more rudimentary than this */ + *buf = *tmpbuf = '\0'; + if (In_endgame(&u.uz)) { + int egdepth = observable_depth(&u.uz); + + (void) endgamelevelname(tmpbuf, egdepth); + Snprintf(buf, sizeof(buf), "in the endgame, on the %s%s", + !strncmp(tmpbuf, "Plane", 5) ? "Elemental " : "", tmpbuf); + } else if (Is_knox(&u.uz)) { + /* this gives away the fact that the knox branch is only 1 level */ + Sprintf(buf, "on the %s level", svd.dungeons[u.uz.dnum].dname); + /* TODO? maybe phrase it differently when actually inside the fort, + if we're able to determine that (not trivial) */ + } else { + char dgnbuf[QBUFSZ]; + + Strcpy(dgnbuf, svd.dungeons[u.uz.dnum].dname); + if (!strncmpi(dgnbuf, "The ", 4)) + *dgnbuf = lowc(*dgnbuf); + Sprintf(tmpbuf, "level %d", + In_quest(&u.uz) ? dunlev(&u.uz) : depth(&u.uz)); + /* TODO? maybe extend this bit to include various other automatic + annotations from the dungeon overview code */ + if (Is_rogue_level(&u.uz)) + Strcat(tmpbuf, ", a primitive area"); + else if (Is_bigroom(&u.uz) && !Blind) + Strcat(tmpbuf, ", a very big room"); + Snprintf(buf, sizeof(buf), "in %s, on %s", dgnbuf, tmpbuf); + } + you_are(buf, ""); + + /* this is shown even if the 'time' option is off */ + if (svm.moves == 1L) { + you_have("just started your adventure", ""); + } else { + /* 'turns' grates on the nerves in this context... */ + Sprintf(buf, "the dungeon %ld turn%s ago", + svm.moves, plur(svm.moves)); + /* same phrasing for current and final: "entered" is unconditional */ + enlght_line(You_, "entered ", buf, ""); + } + + /* for gameover, these have been obtained in really_done() so that they + won't vary if user leaves a disclosure prompt or --More-- unanswered + long enough for the dynamic value to change between then and now */ + if (final ? iflags.at_midnight : midnight()) { + enl_msg("It ", "is ", "was ", "the midnight hour", ""); + } else if (final ? iflags.at_night : night()) { + enl_msg("It ", "is ", "was ", "nighttime", ""); + } + /* other environmental factors */ + if (flags.moonphase == FULL_MOON || flags.moonphase == NEW_MOON) { + /* [This had "tonight" but has been changed to "in effect". + There is a similar issue to Friday the 13th--it's the value + at the start of the current session but that session might + have dragged on for an arbitrary amount of time. We want to + report the values that currently affect play--or affected + play when game ended--rather than actual outside situation.] */ + Sprintf(buf, "a %s moon in effect%s", + (flags.moonphase == FULL_MOON) ? "full" + : (flags.moonphase == NEW_MOON) ? "new" + /* showing these would probably just lead to confusion + since they have no effect on game play... */ + : (flags.moonphase < FULL_MOON) ? "first quarter" + : "last quarter", + /* we don't have access to 'how' here--aside from survived + vs died--so settle for general platitude */ + final ? " when your adventure ended" : ""); + enl_msg("There ", "is ", "was ", buf, ""); + } + if (flags.friday13) { + /* let player know that friday13 penalty is/was in effect; + we don't say "it is/was Friday the 13th" because that was at + the start of the session and it might be past midnight (or + days later if the game has been paused without save/restore), + so phrase this similar to the start up message */ + Sprintf(buf, " Bad things %s on Friday the 13th.", + !final ? "can happen" + : (final == ENL_GAMEOVERALIVE) ? "could have happened" + /* there's no may to tell whether -1 Luck made a + difference but hero has died... */ + : "happened"); + enlght_out(buf); + } + + if (!Upolyd) { + int ulvl = (int) u.ulevel; + /* [flags.showexp currently does not matter; should it?] */ + + /* experience level is already shown above */ + Sprintf(buf, "%-1ld experience point%s", u.uexp, plur(u.uexp)); + /* TODO? + * Remove wizard-mode restriction since patient players can + * determine the numbers needed without resorting to spoilers + * (even before this started being disclosed for 'final'; + * just enable 'showexp' and look at normal status lines + * after drinking gain level potions or eating wraith corpses + * or being level-drained by vampires). + */ + if (ulvl < 30 && (final || wizard)) { + long nxtlvl = newuexp(ulvl), delta = nxtlvl - u.uexp; + + Sprintf(eos(buf), ", %ld %s%sneeded %s level %d", + delta, (u.uexp > 0) ? "more " : "", + /* present tense=="needed", past tense=="were needed" */ + !final ? "" : (delta == 1L) ? "was " : "were ", + /* "for": grammatically iffy but less likely to wrap */ + (ulvl < 18) ? "to attain" : "for", (ulvl + 1)); + } + you_have(buf, ""); + } +#ifdef SCORE_ON_BOTL + if (flags.showscore) { + /* describes what's shown on status line, which is an approximation; + only show it here if player has the 'showscore' option enabled */ + Sprintf(buf, "%ld%s", botl_score(), + !final ? "" : " before end-of-game adjustments"); + enl_msg("Your score ", "is ", "was ", buf, ""); + } +#endif +} + +/* hit points, energy points, armor class -- essential information which + doesn't fit very well in other categories */ +/*ARGSUSED*/ +staticfn void +basics_enlightenment(int mode UNUSED, int final) +{ + static char Power[] = "energy points (spell power)"; + char buf[BUFSZ]; + int pw = u.uen, hp = (Upolyd ? u.mh : u.uhp), + pwmax = u.uenmax, hpmax = (Upolyd ? u.mhmax : u.uhpmax); + + enlght_out(""); /* separator after background */ + enlght_out("Basics:"); + + if (hp < 0) + hp = 0; + /* "1 out of 1" rather than "all" if max is only 1; should never happen */ + if (hp == hpmax && hpmax > 1) + Sprintf(buf, "all %d hit points", hpmax); + else + Sprintf(buf, "%d out of %d hit point%s", hp, hpmax, plur(hpmax)); + you_have(buf, ""); + + /* low max energy is feasible, so handle couple of extra special cases */ + if (pwmax == 0 || (pw == pwmax && pwmax == 2)) /* both: not "all 2" */ + Sprintf(buf, "%s %s", !pwmax ? "no" : "both", Power); + else if (pw == pwmax && pwmax > 2) + Sprintf(buf, "all %d %s", pwmax, Power); + else + Sprintf(buf, "%d out of %d %s", pw, pwmax, Power); + you_have(buf, ""); + + if (Upolyd) { + switch (mons[u.umonnum].mlevel) { + case 0: + /* status line currently being explained shows "HD:0" */ + Strcpy(buf, "0 hit dice (actually 1/2)"); + break; + case 1: + Strcpy(buf, "1 hit die"); + break; + default: + Sprintf(buf, "%d hit dice", mons[u.umonnum].mlevel); + break; + } + you_have(buf, ""); + } + + find_ac(); /* enforces AC_MAX cap */ + Sprintf(buf, "%d", u.uac); + if (abs(u.uac) == AC_MAX) + Sprintf(eos(buf), ", the %s possible", + (u.uac < 0) ? "best" : "worst"); + enl_msg("Your armor class ", "is ", "was ", buf, ""); + + /* gold; similar to doprgold (#showgold) but without shop billing info; + includes container contents, unlike status line but like doprgold */ + { + long umoney = money_cnt(gi.invent), hmoney = hidden_gold(final); + + if (!umoney) { + Sprintf(buf, " Your wallet %s empty", !final ? "is" : "was"); + } else { + Sprintf(buf, " Your wallet contain%s %ld %s", !final ? "s" : "ed", + umoney, currency(umoney)); + } + /* terminate the wallet line if appropriate, otherwise add an + introduction to subsequent continuation; output now either way */ + Strcat(buf, !hmoney ? "." : !umoney ? ", but" : ", and"); + enlght_out(buf); + + /* put contained gold on its own line to avoid excessive width; it's + phrased as a continuation of the wallet line so not capitalized */ + if (hmoney) { + Sprintf(buf, "%ld %s stashed away in your pack", + hmoney, umoney ? "more" : currency(hmoney)); + enl_msg("you ", "have ", "had ", buf, ""); + } + } + + if (flags.pickup) { + char ocl[MAXOCLASSES + 1]; + + Strcpy(buf, "on"); + if (costly_spot(u.ux, u.uy)) { + /* being in a shop inhibits autopickup, even 'pickup_thrown' */ + Strcat(buf, ", but temporarily disabled while inside the shop"); + } else { + oc_to_str(flags.pickup_types, ocl); + Sprintf(eos(buf), " for %s%s%s", *ocl ? "'" : "", + *ocl ? ocl : "all types", *ocl ? "'" : ""); + if (flags.pickup_thrown && *ocl) + Strcat(buf, " plus thrown"); /* show when not 'all types' */ + if (ga.apelist) + Strcat(buf, ", with exceptions"); + } + } else + Strcpy(buf, "off"); + enl_msg("Autopickup ", "is ", "was ", buf, ""); +} + +/* characteristics: expanded version of bottom line strength, dexterity, &c */ +staticfn void +characteristics_enlightenment(int mode, int final) +{ + char buf[BUFSZ]; + + enlght_out(""); + Sprintf(buf, "%sCharacteristics:", !final ? "" : "Final "); + enlght_out(buf); + + /* bottom line order */ + one_characteristic(mode, final, A_STR); /* strength */ + one_characteristic(mode, final, A_DEX); /* dexterity */ + one_characteristic(mode, final, A_CON); /* constitution */ + one_characteristic(mode, final, A_INT); /* intelligence */ + one_characteristic(mode, final, A_WIS); /* wisdom */ + one_characteristic(mode, final, A_CHA); /* charisma */ +} + +/* display one attribute value for characteristics_enlightenment() */ +staticfn void +one_characteristic(int mode, int final, int attrindx) +{ + extern const char *const attrname[]; /* attrib.c */ + boolean hide_innate_value = FALSE, interesting_alimit; + int acurrent, abase, apeak, alimit; + const char *paren_pfx; + char subjbuf[BUFSZ], valubuf[BUFSZ], valstring[32]; + + /* being polymorphed or wearing certain cursed items prevents + hero from reliably tracking changes to characteristics so + we don't show base & peak values then; when the items aren't + cursed, hero could take them off to check underlying values + and we show those in such case so that player doesn't need + to actually resort to doing that */ + if (Upolyd) { + hide_innate_value = TRUE; + } else if (Fixed_abil) { + if (stuck_ring(uleft, RIN_SUSTAIN_ABILITY) + || stuck_ring(uright, RIN_SUSTAIN_ABILITY)) + hide_innate_value = TRUE; + } + switch (attrindx) { + case A_STR: + if (uarmg && uarmg->otyp == GAUNTLETS_OF_POWER && uarmg->cursed) + hide_innate_value = TRUE; + break; + case A_DEX: + break; + case A_CON: + if (u_wield_art(ART_OGRESMASHER) && uwep->cursed) + hide_innate_value = TRUE; + break; + case A_INT: + if (uarmh && uarmh->otyp == DUNCE_CAP && uarmh->cursed) + hide_innate_value = TRUE; + break; + case A_WIS: + if (uarmh && uarmh->otyp == DUNCE_CAP && uarmh->cursed) + hide_innate_value = TRUE; + break; + case A_CHA: + break; + default: + return; /* impossible */ + }; + /* note: final disclosure includes MAGICENLIGHTENTMENT */ + if ((mode & MAGICENLIGHTENMENT) && !Upolyd) + hide_innate_value = FALSE; + + acurrent = ACURR(attrindx); + (void) attrval(attrindx, acurrent, valubuf); /* Sprintf(valubuf,"%d",) */ + Sprintf(subjbuf, "Your %s ", attrname[attrindx]); + + if (!hide_innate_value) { + /* show abase, amax, and/or attrmax if acurr doesn't match abase + (a magic bonus or penalty is in effect) or abase doesn't match + amax (some points have been lost to poison or exercise abuse + and are restorable) or attrmax is different from normal human + (while game is in progress; trying to reduce dependency on + spoilers to keep track of such stuff) or attrmax was different + from abase (at end of game; this attribute wasn't maxed out) */ + abase = ABASE(attrindx); + apeak = AMAX(attrindx); + alimit = ATTRMAX(attrindx); + /* criterium for whether the limit is interesting varies */ + interesting_alimit = + final ? TRUE /* was originally `(abase != alimit)' */ + : (alimit != (attrindx != A_STR ? 18 : STR18(100))); + paren_pfx = final ? " (" : " (current; "; + if (acurrent != abase) { + Sprintf(eos(valubuf), "%sbase:%s", paren_pfx, + attrval(attrindx, abase, valstring)); + paren_pfx = ", "; + } + if (abase != apeak) { + Sprintf(eos(valubuf), "%speak:%s", paren_pfx, + attrval(attrindx, apeak, valstring)); + paren_pfx = ", "; + } + if (interesting_alimit) { + Sprintf(eos(valubuf), "%s%slimit:%s", paren_pfx, + /* more verbose if exceeding 'limit' due to magic bonus */ + (acurrent > alimit) ? "innate " : "", + attrval(attrindx, alimit, valstring)); + /* paren_pfx = ", "; */ + } + if (acurrent != abase || abase != apeak || interesting_alimit) + Strcat(valubuf, ")"); + } + enl_msg(subjbuf, "is ", "was ", valubuf, ""); +} + +/* status: selected obvious capabilities, assorted troubles */ +staticfn void +status_enlightenment(int mode, int final) +{ + boolean magic = (mode & MAGICENLIGHTENMENT) ? TRUE : FALSE; + int cap; + char buf[BUFSZ], youtoo[BUFSZ], heldmon[BUFSZ]; + boolean Riding = (u.usteed + /* if hero dies while dismounting, u.usteed will still + be set; we want to ignore steed in that situation */ + && !(final == ENL_GAMEOVERDEAD + && !strcmp(svk.killer.name, "riding accident"))); + const char *steedname = (!Riding ? (char *) 0 + : x_monnam(u.usteed, + u.usteed->mtame ? ARTICLE_YOUR : ARTICLE_THE, + (char *) 0, + (SUPPRESS_SADDLE | SUPPRESS_HALLUCINATION), + FALSE)); + + /*\ + * Status (many are abbreviated on bottom line; others are or + * should be discernible to the hero hence to the player) + \*/ + enlght_out(""); /* separator after title or characteristics */ + enlght_out(final ? "Final Status:" : "Status:"); + + Strcpy(youtoo, You_); + /* not a traditional status but inherently obvious to player; more + detail given below (attributes section) for magic enlightenment */ + if (Upolyd) { + Strcpy(buf, "transformed"); + if (ugenocided()) + Sprintf(eos(buf), " and %s %s inside", + final ? "felt" : "feel", udeadinside()); + you_are(buf, ""); + } + /* not a trouble, but we want to display riding status before maybe + reporting steed as trapped or hero stuck to cursed saddle */ + if (Riding) { + Sprintf(buf, "riding %s", steedname); + you_are(buf, ""); + Sprintf(eos(youtoo), "and %s ", steedname); + } + /* other movement situations that hero should always know */ + if (Levitation) { + if (Lev_at_will && magic) + you_are("levitating, at will", ""); + else + enl_msg(youtoo, are, were, "levitating", from_what(LEVITATION)); + } else if (Flying) { /* can only fly when not levitating */ + enl_msg(youtoo, are, were, "flying", from_what(FLYING)); + } + if (Underwater) { + you_are("underwater", ""); + } else if (u.uinwater) { + you_are(Swimming ? "swimming" : "in water", from_what(SWIMMING)); + } else if (walking_on_water()) { + /* show active Wwalking here, potential Wwalking elsewhere */ + Sprintf(buf, "walking on %s", + is_pool(u.ux, u.uy) ? "water" + : is_lava(u.ux, u.uy) ? "lava" + : surface(u.ux, u.uy)); /* catchall; shouldn't happen */ + you_are(buf, from_what(WWALKING)); + } + if (Upolyd && (u.uundetected || U_AP_TYPE != M_AP_NOTHING)) + youhiding(TRUE, final); + + /* internal troubles, mostly in the order that prayer ranks them */ + if (Stoned) { + if (final && (Stoned & I_SPECIAL)) + enlght_out(" You turned into stone."); + else + you_are("turning to stone", ""); + } + if (Slimed) { + if (final && (Slimed & I_SPECIAL)) + enlght_out(" You turned into slime."); + else + you_are("turning into slime", ""); + } + if (Strangled) { + if (u.uburied) { + you_are("buried", ""); + } else { + if (final && (Strangled & I_SPECIAL)) { + enlght_out(" You died from strangulation."); + } else { + Strcpy(buf, "being strangled"); + if (wizard) + Sprintf(eos(buf), " (%ld)", (Strangled & TIMEOUT)); + you_are(buf, from_what(STRANGLED)); + } + } + } + if (Sick) { + /* the two types of sickness are lumped together; hero can be + afflicted by both but there is only one timeout; botl status + puts TermIll before FoodPois and death due to timeout reports + terminal illness if both are in effect, so do the same here */ + if (final && (Sick & I_SPECIAL)) { + Sprintf(buf, " %sdied from %s.", You_, /* has trailing space */ + (u.usick_type & SICK_NONVOMITABLE) + ? "terminal illness" : "food poisoning"); + enlght_out(buf); + } else { + /* unlike death due to sickness, report the two cases separately + because it is possible to cure one without curing the other */ + if (u.usick_type & SICK_NONVOMITABLE) + you_are("terminally sick from illness", ""); + if (u.usick_type & SICK_VOMITABLE) + you_are("terminally sick from food poisoning", ""); + } + } + if (Vomiting) + you_are("nauseated", ""); + if (Stunned) + you_are("stunned", ""); + if (Confusion) + you_are("confused", ""); + if (Hallucination) + you_are("hallucinating", ""); + if (Blind) { + /* check the reasons in same order as from_what() */ + Sprintf(buf, "%s blind", + (HBlinded & FROMOUTSIDE) != 0L ? "permanently" + : (HBlinded & FROMFORM) ? "innately" + /* better phrasing desperately wanted... */ + : Blindfolded_only ? "deliberately" + /* timed, possibly combined with blindfold */ + : "temporarily"); + if (wizard && (HBlinded == BlindedTimeout && !Blindfolded)) + Sprintf(eos(buf), " (%ld)", BlindedTimeout); + /* !haseyes: avoid "you are innately blind innately" */ + you_are(buf, !haseyes(gy.youmonst.data) ? "" : from_what(BLINDED)); + } + if (Deaf) + you_are("deaf", from_what(DEAF)); + + /* external troubles, more or less */ + if (Punished) { + if (uball) { + Sprintf(buf, "chained to %s", ansimpleoname(uball)); + } else { + impossible("Punished without uball?"); + Strcpy(buf, "punished"); + } + you_are(buf, ""); + } + if (u.utrap) { + char predicament[BUFSZ]; + boolean anchored = (u.utraptype == TT_BURIEDBALL); + + (void) trap_predicament(predicament, final, wizard); + if (u.usteed) { /* not `Riding' here */ + Sprintf(buf, "%s%s ", anchored ? "you and " : "", steedname); + *buf = highc(*buf); + enl_msg(buf, (anchored ? "are " : "is "), + (anchored ? "were " : "was "), predicament, ""); + } else + you_are(predicament, ""); + } /* (u.utrap) */ + heldmon[0] = '\0'; /* lint suppression */ + if (u.ustuck) { /* includes u.uswallow */ + Strcpy(heldmon, a_monnam(u.ustuck)); + if (!strcmp(heldmon, "it") + && (!has_mgivenname(u.ustuck) + || strcmp(MGIVENNAME(u.ustuck), "it") != 0)) + Strcpy(heldmon, "an unseen creature"); + } + if (u.uswallow) { + assert(u.ustuck != NULL); /* implied by u.uswallow */ + Snprintf(buf, sizeof buf, "%s by %s", + digests(u.ustuck->data) ? "swallowed" : "engulfed", + heldmon); + if (dmgtype(u.ustuck->data, AD_DGST)) { + /* if final, death via digestion can be deduced by u.uswallow + still being True and u.uswldtim having been decremented to 0 */ + if (final && !u.uswldtim) + Strcat(buf, " and got totally digested"); + else + Sprintf(eos(buf), " and %s being digested", + final ? "were" : "are"); + } + if (wizard) + Sprintf(eos(buf), " (%u)", u.uswldtim); + you_are(buf, ""); + } else if (u.ustuck) { + boolean ustick = (Upolyd && sticks(gy.youmonst.data)); + int dx = u.ustuck->mx - u.ux, dy = u.ustuck->my - u.uy; + + Snprintf(buf, sizeof buf, "%s %s (%s)", + ustick ? "holding" : "held by", + heldmon, dxdy_to_dist_descr(dx, dy, TRUE)); + you_are(buf, ""); + } + if (Riding) { + struct obj *saddle = which_armor(u.usteed, W_SADDLE); + + if (saddle && saddle->cursed) { + Sprintf(buf, "stuck to %s %s", s_suffix(steedname), + simpleonames(saddle)); + you_are(buf, ""); + } + } + if (Wounded_legs) { + /* EWounded_legs is used to track left/right/both rather than some + form of extrinsic impairment; HWounded_legs is used for timeout; + both apply to steed instead of hero when mounted */ + long whichleg = (EWounded_legs & BOTH_SIDES); + const char *bp = u.usteed ? mbodypart(u.usteed, LEG) : body_part(LEG), + *article = "a ", /* precedes "wounded", so never "an " */ + *leftright = ""; + + if (whichleg == BOTH_SIDES) + bp = makeplural(bp), article = ""; + else + leftright = (whichleg == LEFT_SIDE) ? "left " : "right "; + Sprintf(buf, "%swounded %s%s", article, leftright, bp); + + /* when mounted, Wounded_legs applies to steed rather than to + hero; we only report steed's wounded legs in wizard mode */ + if (u.usteed) { /* not `Riding' here */ + if (wizard && steedname) { + char steednambuf[BUFSZ]; + + Strcpy(steednambuf, steedname); + *steednambuf = highc(*steednambuf); + enl_msg(steednambuf, " has ", " had ", buf, ""); + } + } else { + you_have(buf, ""); + } + } + if (Glib) { + Sprintf(buf, "slippery %s", fingers_or_gloves(TRUE)); + if (wizard) + Sprintf(eos(buf), " (%ld)", (Glib & TIMEOUT)); + you_have(buf, ""); + } + if (Fumbling) { + if (magic || cause_known(FUMBLING)) + enl_msg(You_, "fumble", "fumbled", "", from_what(FUMBLING)); + } + if (Sleepy) { + if (magic || cause_known(SLEEPY)) { + Strcpy(buf, from_what(SLEEPY)); + if (wizard) + Sprintf(eos(buf), " (%ld)", (HSleepy & TIMEOUT)); + enl_msg("You ", "fall", "fell", " asleep uncontrollably", buf); + } + } + /* hunger/nutrition */ + if (Hunger) { + if (magic || cause_known(HUNGER)) + enl_msg(You_, "hunger", "hungered", " rapidly", + from_what(HUNGER)); + } + Strcpy(buf, hu_stat[u.uhs]); /* hunger status; omitted if "normal" */ + mungspaces(buf); /* strip trailing spaces */ + /* status line doesn't show hunger when state is "not hungry", we do; + needed for wizard mode's reveal of u.uhunger but add it for everyone */ + if (!*buf) + Strcpy(buf, "not hungry"); + if (*buf) { /* (since "not hungry" was added, this will always be True) */ + *buf = lowc(*buf); /* override capitalization */ + if (!strcmp(buf, "weak")) + Strcat(buf, " from severe hunger"); + else if (!strncmp(buf, "faint", 5)) /* fainting, fainted */ + Strcat(buf, " due to starvation"); + if (wizard) + Sprintf(eos(buf), " <%d>", u.uhunger); + you_are(buf, ""); + } + /* encumbrance */ + if ((cap = near_capacity()) > UNENCUMBERED) { + const char *adj = "?_?"; /* (should always get overridden) */ + + Strcpy(buf, enc_stat[cap]); + *buf = lowc(*buf); + switch (cap) { + case SLT_ENCUMBER: + adj = "slightly"; + break; /* burdened */ + case MOD_ENCUMBER: + adj = "moderately"; + break; /* stressed */ + case HVY_ENCUMBER: + adj = "very"; + break; /* strained */ + case EXT_ENCUMBER: + adj = "extremely"; + break; /* overtaxed */ + case OVERLOADED: + adj = "not possible"; + break; + } + if (wizard) + Sprintf(eos(buf), " <%d>", inv_weight()); + Sprintf(eos(buf), "; movement %s %s%s", !final ? "is" : "was", adj, + (cap < OVERLOADED) ? " slowed" : ""); + you_are(buf, ""); + } else { + /* last resort entry, guarantees Status section is non-empty + (no longer needed for that purpose since weapon status added; + still useful though) */ + Strcpy(buf, "unencumbered"); + if (wizard) + Sprintf(eos(buf), " <%d>", inv_weight()); + you_are(buf, ""); + } + /* current weapon(s) and corresponding skill level(s) */ + weapon_insight(final); + /* unlike ring of increase accuracy's effect, the monk's suit penalty + is too blatant to be restricted to magical enlightenment */ + if (iflags.tux_penalty && !Upolyd) { + (void) enlght_combatinc("to hit", -gu.urole.spelarmr, final, buf); + /* if from_what() ever gets extended from wizard mode to normal + play, it could be adapted to handle this */ + Sprintf(eos(buf), " due to your %s", suit_simple_name(uarm)); + you_have(buf, ""); + } + /* report 'nudity' */ + if (!uarm && !uarmu && !uarmc && !uarms && !uarmg && !uarmf && !uarmh) { + if (u.uroleplay.nudist) + enl_msg(You_, "do", "did", " not wear any armor", ""); + else + you_are("not wearing any armor", ""); + } +} + +/* extracted from status_enlightenment() to reduce clutter there */ +staticfn void +weapon_insight(int final) +{ + char buf[BUFSZ]; + int wtype; + + /* report being weaponless; distinguish whether gloves are worn + [perhaps mention silver ring(s) when not wearing gloves?] */ + if (!uwep) { + you_are(empty_handed(), ""); + + /* two-weaponing implies hands and + a weapon or wep-tool (not other odd stuff) in each hand */ + } else if (u.twoweap) { + you_are("wielding two weapons at once", ""); + + /* report most weapons by their skill class (so a katana will be + described as a long sword, for instance; mattock, hook, and aklys + are exceptions), or wielded non-weapon item by its object class */ + } else { + const char *what = weapon_descr(uwep); + + /* [what about other silver items?] */ + if (uwep->otyp == SHIELD_OF_REFLECTION) + what = shield_simple_name(uwep); /* silver|smooth shield */ + else if (is_wet_towel(uwep)) + what = /* (uwep->spe < 3) ? "moist towel" : */ "wet towel"; + + if (!strcmpi(what, "armor") || !strcmpi(what, "food") + || !strcmpi(what, "venom")) + Sprintf(buf, "wielding some %s", what); + else + /* [maybe include known blessed?] */ + Sprintf(buf, "wielding %s", + (uwep->quan == 1L) ? an(what) : makeplural(what)); + you_are(buf, ""); + } + + /* + * Skill with current weapon. Might help players who've never + * noticed #enhance or decided that it was pointless. + */ + if ((wtype = weapon_type(uwep)) != P_NONE && (!uwep || !is_ammo(uwep))) { + char sklvlbuf[20]; + int sklvl = P_SKILL(wtype); + boolean hav = (sklvl != P_UNSKILLED && sklvl != P_SKILLED); + + if (sklvl == P_ISRESTRICTED) + Strcpy(sklvlbuf, "no"); + else + (void) lcase(skill_level_name(wtype, sklvlbuf)); + /* "you have no/basic/expert/master/grand-master skill with " + or "you are unskilled/skilled in " */ + Sprintf(buf, "%s %s %s", sklvlbuf, + hav ? "skill with" : "in", skill_name(wtype)); + + if (!u.twoweap) { + if (can_advance(wtype, FALSE)) + Sprintf(eos(buf), " and %s that", + !final ? "can enhance" : "could have enhanced"); + if (hav) + you_have(buf, ""); + else + you_are(buf, ""); + + } else { /* two-weapon */ + static const char also_[] = "also "; + char pfx[QBUFSZ], sfx[QBUFSZ], + sknambuf2[20], sklvlbuf2[20], twobuf[20]; + const char *also = "", *also2 = "", *also3 = (char *) 0, + *verb_present, *verb_past; + int wtype2 = weapon_type(uswapwep), + sklvl2 = P_SKILL(wtype2), + twoskl = P_SKILL(P_TWO_WEAPON_COMBAT); + boolean a1, a2, ab, + hav2 = (sklvl2 != P_UNSKILLED && sklvl2 != P_SKILLED); + + /* normally hero must have access to two-weapon skill in + order to initiate u.twoweap, but not if polymorphed into + a form which has multiple weapon attacks, so we need to + avoid getting bitten by unexpected skill value */ + if (twoskl == P_ISRESTRICTED) { + twoskl = P_UNSKILLED; + /* restricted is the same as unskilled as far as bonus + or penalty goes, and it isn't ordinarily seen so + skill_level_name() returns "Unknown" for it */ + Strcpy(twobuf, "restricted"); + } else { + (void) lcase(skill_level_name(P_TWO_WEAPON_COMBAT, twobuf)); + } + + /* keep buf[] from above in case skill levels match */ + pfx[0] = sfx[0] = '\0'; + if (twoskl < sklvl) { + /* twoskil won't be restricted so sklvl is at least basic */ + Sprintf(pfx, "Your skill in %s ", skill_name(wtype)); + Sprintf(sfx, " limited by being %s with two weapons", twobuf); + also = also_; + } else if (twoskl > sklvl) { + /* sklvl might be restricted */ + Strcpy(pfx, "Your two weapon skill "); + Strcpy(sfx, " limited by "); + if (sklvl > P_ISRESTRICTED) + Sprintf(eos(sfx), "being %s", sklvlbuf); + else + Sprintf(eos(sfx), "having no skill"); + Sprintf(eos(sfx), " with %s", skill_name(wtype)); + also2 = also_; + } else { + Strcat(buf, " and two weapons"); + also3 = also_; + } + if (*pfx) + enl_msg(pfx, "is", "was", sfx, ""); + else if (hav) + you_have(buf, ""); + else + you_are(buf, ""); + + /* skip comparison between secondary and two-weapons if it is + identical to the comparison between primary and twoweap */ + if (wtype2 != wtype) { + Strcpy(sknambuf2, skill_name(wtype2)); + (void) lcase(skill_level_name(wtype2, sklvlbuf2)); + verb_present = "is", verb_past = "was"; + pfx[0] = sfx[0] = buf[0] = '\0'; + if (twoskl < sklvl2) { + /* twoskil is at least unskilled, sklvl2 at least basic */ + Sprintf(pfx, "Your skill in %s ", sknambuf2); + Sprintf(sfx, " %slimited by being %s with two weapons", + also, twobuf); + } else if (twoskl > sklvl2) { + /* sklvl2 might be restricted */ + Strcpy(pfx, "Your two weapon skill "); + Sprintf(sfx, " %slimited by ", also2); + if (sklvl2 > P_ISRESTRICTED) + Sprintf(eos(sfx), "being %s", sklvlbuf2); + else + Strcat(eos(sfx), "having no skill"); + Sprintf(eos(sfx), " with %s", sknambuf2); + } else { + /* equal; two-weapon is at least unskilled, so sklvl2 is + too; "you [also] have basic/expert/master/grand-master + skill with " or "you [also] are unskilled/ + skilled in */ + Sprintf(buf, "%s %s %s", sklvlbuf2, + hav2 ? "skill with" : "in", sknambuf2); + Strcat(buf, " and two weapons"); + if (also3) { + Strcpy(pfx, "You also "); + Snprintf(sfx, sizeof(sfx), " %s", buf), buf[0] = '\0'; + verb_present = hav2 ? "have" : "are"; + verb_past = hav2 ? "had" : "were"; + } + } + if (*pfx) + enl_msg(pfx, verb_present, verb_past, sfx, ""); + else if (hav2) + you_have(buf, ""); + else + you_are(buf, ""); + } /* wtype2 != wtype */ + + /* if training and available skill credits already allow + #enhance for any of primary, secondary, or two-weapon, + tell the player; avoid attempting figure out whether + spending skill credits enhancing one might make either + or both of the others become ineligible for enhancement */ + a1 = can_advance(wtype, FALSE); + a2 = (wtype2 != wtype) ? can_advance(wtype2, FALSE) : FALSE; + ab = can_advance(P_TWO_WEAPON_COMBAT, FALSE); + if (a1 || a2 || ab) { + static const char also_wik_[] = " and also with "; + + /* for just one, the conditionals yield + 1) "skill with "; for more than one: + 2) "skills with and also with " or + 3) "skills with and also with two-weapons" or + 4) "skills with and also with two-weapons" or + 5) "skills with , , and two-weapons" + (no 'also's or extra 'with's for case 5); when primary + and secondary use the same skill, only cases 1 and 3 are + possible because 'a2' gets forced to False above */ + Sprintf(sfx, " skill%s with %s%s%s%s%s", + ((int) a1 + (int) a2 + (int) ab > 1) ? "s" : "", + a1 ? skill_name(wtype) : "", + ((a1 && a2 && ab) ? ", " + : (a1 && (a2 || ab)) ? also_wik_ : ""), + a2 ? skill_name(wtype2) : "", + ((a1 && a2 && ab) ? ", and " + : (a2 && ab) ? also_wik_ : ""), + ab ? "two weapons" : ""); + enl_msg(You_, "can enhance", "could have enhanced", sfx, ""); + } + } /* two-weapon */ + } /* skill applies */ +} + +staticfn void +item_resistance_message( + int adtyp, + const char *prot_message, + int final) +{ + int protection = u_adtyp_resistance_obj(adtyp); + + if (protection) { + boolean somewhat = protection < 99; + + enl_msg("Your items ", + somewhat ? "are somewhat" : "are", + somewhat ? "were somewhat" : "were", + prot_message, item_what(adtyp)); + } +} + +/* attributes: intrinsics and the like, other non-obvious capabilities */ +staticfn void +attributes_enlightenment( + int unused_mode UNUSED, + int final) +{ + static NEARDATA const char + if_surroundings_permitted[] = " if surroundings permitted"; + int ltmp, armpro, warnspecies; + char buf[BUFSZ]; + + /*\ + * Attributes + \*/ + enlght_out(""); + enlght_out(final ? "Final Attributes:" : "Attributes:"); + + if (u.uevent.uhand_of_elbereth) { + static const char *const hofe_titles[3] = { "the Hand of Elbereth", + "the Envoy of Balance", + "the Glory of Arioch" }; + you_are(hofe_titles[u.uevent.uhand_of_elbereth - 1], ""); + } + + Sprintf(buf, "%s", piousness(TRUE, "aligned")); + if (u.ualign.record >= 0) + you_are(buf, ""); + else + you_have(buf, ""); + + if (wizard) { + Sprintf(buf, " %d", u.ualign.record); + enl_msg("Your alignment ", "is", "was", buf, ""); + } + + /*** Resistances to troubles ***/ + if (Invulnerable) + you_are("invulnerable", from_what(INVULNERABLE)); + if (Antimagic) + you_are("magic-protected", from_what(ANTIMAGIC)); + if (Fire_resistance) + you_are("fire resistant", from_what(FIRE_RES)); + item_resistance_message(AD_FIRE, " protected from fire", final); + if (Cold_resistance) + you_are("cold resistant", from_what(COLD_RES)); + item_resistance_message(AD_COLD, " protected from cold", final); + if (Sleep_resistance) + you_are("sleep resistant", from_what(SLEEP_RES)); + if (Disint_resistance) + you_are("disintegration resistant", from_what(DISINT_RES)); + item_resistance_message(AD_DISN, " protected from disintegration", final); + if (Shock_resistance) + you_are("shock resistant", from_what(SHOCK_RES)); + item_resistance_message(AD_ELEC, " protected from electric shocks", + final); + if (Poison_resistance) + you_are("poison resistant", from_what(POISON_RES)); + if (Acid_resistance) { + Sprintf(buf, "%.20s%.30s", + temp_resist(ACID_RES) ? "temporarily " : "", + "acid resistant"); + you_are(buf, from_what(ACID_RES)); + } + item_resistance_message(AD_ACID, " protected from acid", final); + if (Drain_resistance) + you_are("level-drain resistant", from_what(DRAIN_RES)); + if (Sick_resistance) + you_are("immune to sickness", from_what(SICK_RES)); + if (Stone_resistance) { + Sprintf(buf, "%.20s%.30s", + temp_resist(STONE_RES) ? "temporarily " : "", + "petrification resistant"); + you_are(buf, from_what(STONE_RES)); + } + if (Halluc_resistance) + enl_msg(You_, "resist", "resisted", " hallucinations", + from_what(HALLUC_RES)); + if (u.uedibility) + you_can("recognize detrimental food", ""); + + /*** Vision and senses ***/ + if ((HBlinded || EBlinded) && BBlinded) /* blind w/ blindness blocked */ + you_can("see", from_what(-BLINDED)); /* Eyes of the Overworld */ + if (Blnd_resist && !Blind) /* skip if no eyes or blindfolded */ + you_are("not subject to light-induced blindness", + from_what(BLND_RES)); + if (See_invisible) { + if (!Blind) + enl_msg(You_, "see", "saw", " invisible", from_what(SEE_INVIS)); + else if (!PermaBlind) + enl_msg(You_, "will see", "would have seen", + " invisible when not blind", ""); + else + enl_msg(You_, "would see", "would have seen", + " invisible if not blind", ""); + } + if (Blind_telepat) + you_are("telepathic", from_what(TELEPAT)); + if (Warning) + you_are("warned", from_what(WARNING)); + if (Warn_of_mon && svc.context.warntype.obj) { + Sprintf(buf, "aware of the presence of %s", + (svc.context.warntype.obj & M2_ORC) ? "orcs" + : (svc.context.warntype.obj & M2_ELF) ? "elves" + : (svc.context.warntype.obj & M2_DEMON) ? "demons" + : something); + you_are(buf, from_what(WARN_OF_MON)); + } + if (Warn_of_mon && svc.context.warntype.polyd) { + Sprintf(buf, "aware of the presence of %s", + ((svc.context.warntype.polyd & (M2_HUMAN | M2_ELF)) + == (M2_HUMAN | M2_ELF)) ? "humans and elves" + : (svc.context.warntype.polyd & M2_HUMAN) ? "humans" + : (svc.context.warntype.polyd & M2_ELF) ? "elves" + : (svc.context.warntype.polyd & M2_ORC) ? "orcs" + : (svc.context.warntype.polyd & M2_DEMON) ? "demons" + : "certain monsters"); + you_are(buf, ""); + } + warnspecies = svc.context.warntype.speciesidx; + if (Warn_of_mon && ismnum(warnspecies)) { + Sprintf(buf, "aware of the presence of %s", + makeplural(mons[warnspecies].pmnames[NEUTRAL])); + you_are(buf, from_what(WARN_OF_MON)); + } + if (Undead_warning) + you_are("warned of undead", from_what(WARN_UNDEAD)); + if (Searching) + you_have("automatic searching", from_what(SEARCHING)); + if (Clairvoyant) { + you_are("clairvoyant", from_what(CLAIRVOYANT)); + } else if ((HClairvoyant || EClairvoyant) && BClairvoyant) { + Strcpy(buf, from_what(-CLAIRVOYANT)); + (void) strsubst(buf, " because of ", " if not for "); + enl_msg(You_, "could be", "could have been", " clairvoyant", buf); + } + if (Infravision) + you_have("infravision", from_what(INFRAVISION)); + if (Detect_monsters) { + Strcpy(buf, "sensing the presence of monsters"); + if (wizard) { + long detectmon_timeout = (HDetect_monsters & TIMEOUT); + + if (detectmon_timeout) + Sprintf(eos(buf), " (%ld)", detectmon_timeout); + } + you_are(buf, ""); + } + if (u.umconf) { /* 'u.umconf' is a counter rather than a timeout */ + Strcpy(buf, " monsters when hitting them"); + if (wizard && !final) { + if (u.umconf == 1) + Strcat(buf, " (next hit only)"); + else /* u.umconf > 1 */ + Sprintf(eos(buf), " (next %u hits)", u.umconf); + } + enl_msg(You_, "will confuse", "would have confused", buf, ""); + } + + /*** Appearance and behavior ***/ + if (Adornment) { + int adorn = 0; + + if (uleft && uleft->otyp == RIN_ADORNMENT) + adorn += uleft->spe; + if (uright && uright->otyp == RIN_ADORNMENT) + adorn += uright->spe; + /* the sum might be 0 (+0 ring or two which negate each other); + that yields "you are charismatic" (which isn't pointless + because it potentially impacts seduction attacks) */ + Sprintf(buf, "%scharismatic", + (adorn > 0) ? "more " : (adorn < 0) ? "less " : ""); + you_are(buf, from_what(ADORNED)); + } + if (Invisible) + you_are("invisible", from_what(INVIS)); + else if (Invis) + you_are("invisible to others", from_what(INVIS)); + /* ordinarily "visible" is redundant; this is a special case for + the situation when invisibility would be an expected attribute */ + else if ((HInvis || EInvis) && BInvis) + you_are("visible", from_what(-INVIS)); + if (Displaced) + you_are("displaced", from_what(DISPLACED)); + if (Stealth) { + you_are("stealthy", from_what(STEALTH)); + } else if (BStealth && (HStealth || EStealth)) { + Sprintf(buf, " stealthy%s", + (BStealth == FROMOUTSIDE) ? " if not mounted" : ""); + enl_msg(You_, "would be", "would have been", buf, ""); + } + if (Aggravate_monster) + enl_msg("You aggravate", "", "d", " monsters", + from_what(AGGRAVATE_MONSTER)); + if (Conflict) + enl_msg("You cause", "", "d", " conflict", from_what(CONFLICT)); + + /*** Transportation ***/ + if (Jumping) + you_can("jump", from_what(JUMPING)); + if (Teleportation) + you_can("teleport", from_what(TELEPORT)); + if (Teleport_control) + you_have("teleport control", from_what(TELEPORT_CONTROL)); + /* actively levitating handled earlier as a status condition */ + if (BLevitation) { /* levitation is blocked */ + long save_BLev = BLevitation; + + BLevitation = 0L; + if (Levitation) { + /* either trapped in the floor or inside solid rock + (or both if chained to buried iron ball and have + moved one step into solid rock somehow) */ + boolean trapped = (save_BLev & I_SPECIAL) != 0L, + terrain = (save_BLev & FROMOUTSIDE) != 0L; + + Sprintf(buf, "%s%s%s", + trapped ? " if not trapped" : "", + (trapped && terrain) ? " and" : "", + terrain ? if_surroundings_permitted : ""); + enl_msg(You_, "would levitate", "would have levitated", buf, ""); + } + BLevitation = save_BLev; + } + /* actively flying handled earlier as a status condition */ + if (BFlying) { /* flight is blocked */ + long save_BFly = BFlying; + + BFlying = 0L; + if (Flying) { + enl_msg(You_, "would fly", "would have flown", + /* wording quibble: for past tense, "hadn't been" + would sound better than "weren't" (and + "had permitted" better than "permitted"), but + "weren't" and "permitted" are adequate so the + extra complexity to handle that isn't worth it */ + Levitation + ? " if you weren't levitating" + : (save_BFly == I_SPECIAL) + /* this is an oversimplification; being trapped + might also be blocking levitation so flight + would still be blocked after escaping trap */ + ? " if you weren't trapped" + : (save_BFly == FROMOUTSIDE) + ? if_surroundings_permitted + /* two or more of levitation, surroundings, + and being trapped in the floor */ + : " if circumstances permitted", + ""); + } + BFlying = save_BFly; + } + /* including this might bring attention to the fact that ceiling + clinging has inconsistencies... */ + if (is_clinger(gy.youmonst.data)) { + boolean has_lid = has_ceiling(&u.uz); + + if (has_lid && !u.uinwater) { + you_can("cling to the ceiling", ""); + } else { + Sprintf(buf, " to the ceiling if %s%s%s", + !has_lid ? "there was one" : "", + (!has_lid && u.uinwater) ? " and " : "", + u.uinwater ? (Underwater ? "you weren't underwater" + : "you weren't in the water") : ""); + /* past tense is applicable for death while Unchanging */ + enl_msg(You_, "could cling", "could have clung", buf, ""); + } + } + /* actively walking on water handled earlier as a status condition */ + if (Wwalking && !walking_on_water()) + you_can("walk on water", from_what(WWALKING)); + /* actively swimming (in water but not under it) handled earlier */ + if (Swimming && (Underwater || !u.uinwater)) + you_can("swim", from_what(SWIMMING)); + if (Breathless) + you_can("survive without air", from_what(MAGICAL_BREATHING)); + else if (Amphibious) + you_can("breathe water", from_what(MAGICAL_BREATHING)); + if (Passes_walls) + you_can("walk through walls", from_what(PASSES_WALLS)); + + /*** Physical attributes ***/ + if (Regeneration) + enl_msg("You regenerate", "", "d", "", from_what(REGENERATION)); + if (Slow_digestion) + you_have("slower digestion", from_what(SLOW_DIGESTION)); + if (u.uhitinc) { + (void) enlght_combatinc("to hit", u.uhitinc, final, buf); + if (iflags.tux_penalty && !Upolyd) + Sprintf(eos(buf), " %s your suit's penalty", + (u.uhitinc < 0) ? "increasing" + : (u.uhitinc < 4 * gu.urole.spelarmr / 5) + ? "partly offsetting" + : (u.uhitinc < gu.urole.spelarmr) ? "nearly offsetting" + : "overcoming"); + you_have(buf, ""); + } + if (u.udaminc) + you_have(enlght_combatinc("damage", u.udaminc, final, buf), ""); + if (u.uspellprot || Protection) { + int prot = 0; + + if (uleft && uleft->otyp == RIN_PROTECTION) + prot += uleft->spe; + if (uright && uright->otyp == RIN_PROTECTION) + prot += uright->spe; + if (uamul && uamul->otyp == AMULET_OF_GUARDING) + prot += 2; + if (HProtection & INTRINSIC) + prot += u.ublessed; + prot += u.uspellprot; + if (prot) + you_have(enlght_combatinc("defense", prot, final, buf), ""); + } + if ((armpro = magic_negation(&gy.youmonst)) > 0) { + /* magic cancellation factor, conferred by worn armor */ + static const char *const mc_types[] = { + "" /*ordinary*/, "warded", "guarded", "protected", + }; + /* sanity check */ + if (armpro >= SIZE(mc_types)) + armpro = SIZE(mc_types) - 1; + you_are(mc_types[armpro], ""); + } + if (Half_physical_damage) + enlght_halfdmg(HALF_PHDAM, final); + if (Half_spell_damage) + enlght_halfdmg(HALF_SPDAM, final); + if (Half_gas_damage) + enl_msg(You_, "take", "took", " reduced poison gas damage", ""); + if (spellid(0) > NO_SPELL) { /* skip if no spells are known yet */ + /* greatly simplified edition of percent_success(spell.c)--may need + to be suppressed if oversimplification leads to player confusion */ + char cast_adj[QBUFSZ]; + boolean suit = uarm && is_metallic(uarm), + robe = uarmc && uarmc->otyp == ROBE; + + *cast_adj = '\0'; + if (suit) /* omit "wearing" to shorten the text */ + Sprintf(cast_adj, " impaired by metallic armor%s", + robe ? ", mitigated by your robe" : ""); + else if (robe) + Strcpy(cast_adj, " enhanced by wearing a robe"); + + if (*cast_adj) + enl_msg("Your spell casting ", "is", "was", cast_adj, ""); + } + /* polymorph and other shape change */ + if (Protection_from_shape_changers) + you_are("protected from shape changers", + from_what(PROT_FROM_SHAPE_CHANGERS)); + if (Unchanging) { + const char *what = 0; + + if (!Upolyd) /* Upolyd handled below after current form */ + you_can("not change from your current form", + from_what(UNCHANGING)); + /* blocked shape changes */ + if (Polymorph) + what = !final ? "polymorph" : "have polymorphed"; + else if (ismnum(u.ulycn)) + what = !final ? "change shape" : "have changed shape"; + if (what) { + Sprintf(buf, "would %s periodically", what); + /* omit from_what(UNCHANGING); too verbose */ + enl_msg(You_, buf, buf, " if not locked into your current form", + ""); + } + } else if (Polymorph) { + you_are("polymorphing periodically", from_what(POLYMORPH)); + } + if (Polymorph_control) + you_have("polymorph control", from_what(POLYMORPH_CONTROL)); + if (Upolyd && u.umonnum != u.ulycn + /* if we've died from turning into slime, we're polymorphed + right now but don't want to list it as a temporary attribute + [we need a more reliable way to detect this situation] */ + && !(final == ENL_GAMEOVERDEAD + && u.umonnum == PM_GREEN_SLIME && !Unchanging)) { + /* foreign shape (except were-form which is handled below) */ + if (!vampshifted(&gy.youmonst)) + Sprintf(buf, "polymorphed into %s", + an(pmname(gy.youmonst.data, + flags.female ? FEMALE : MALE))); + else + Sprintf(buf, "polymorphed into %s in %s form", + an(pmname(&mons[gy.youmonst.cham], + flags.female ? FEMALE : MALE)), + pmname(gy.youmonst.data, flags.female ? FEMALE : MALE)); + if (wizard) + Sprintf(eos(buf), " (%d)", u.mtimedone); + you_are(buf, ""); + } + if (lays_eggs(gy.youmonst.data) && flags.female) /* Upolyd */ + you_can("lay eggs", ""); + if (ismnum(u.ulycn)) { + /* "you are a werecreature [in beast form]" */ + Strcpy(buf, an(pmname(&mons[u.ulycn], + flags.female ? FEMALE : MALE))); + if (u.umonnum == u.ulycn) { + Strcat(buf, " in beast form"); + if (wizard) + Sprintf(eos(buf), " (%d)", u.mtimedone); + } + you_are(buf, ""); + } + if (Unchanging && Upolyd) /* !Upolyd handled above */ + you_can("not change from your current form", from_what(UNCHANGING)); + if (Hate_silver) + you_are("harmed by silver", ""); + /* movement and non-armor-based protection */ + if (Fast) + you_are(Very_fast ? "very fast" : "fast", from_what(FAST)); + if (Reflecting) + you_have("reflection", from_what(REFLECTING)); + if (Free_action) + you_have("free action", from_what(FREE_ACTION)); + if (Fixed_abil) + you_have("fixed abilities", from_what(FIXED_ABIL)); + if (Lifesaved) + enl_msg("Your life ", "will be", "would have been", " saved", ""); + + /*** Miscellany ***/ + if (Luck) { + ltmp = abs((int) Luck); + Sprintf(buf, "%s%slucky", + ltmp >= 10 ? "extremely " : ltmp >= 5 ? "very " : "", + Luck < 0 ? "un" : ""); + if (wizard) + Sprintf(eos(buf), " (%d)", Luck); + you_are(buf, ""); + } else if (wizard) + enl_msg("Your luck ", "is", "was", " zero", ""); + if (u.moreluck > 0) + you_have("extra luck", ""); + else if (u.moreluck < 0) + you_have("reduced luck", ""); + if (carrying(LUCKSTONE) || stone_luck(TRUE)) { + ltmp = stone_luck(FALSE); + if (ltmp <= 0) + enl_msg("Bad luck ", "does", "did", " not time out for you", ""); + if (ltmp >= 0) + enl_msg("Good luck ", "does", "did", " not time out for you", ""); + } + + if (u.ugangr) { + Sprintf(buf, " %sangry with you", + u.ugangr > 6 ? "extremely " : u.ugangr > 3 ? "very " : ""); + if (wizard) + Sprintf(eos(buf), " (%d)", u.ugangr); + enl_msg(u_gname(), " is", " was", buf, ""); + } else { + /* + * We need to suppress this when the game is over, because death + * can change the value calculated by can_pray(), potentially + * resulting in a false claim that you could have prayed safely. + */ + if (!final) { +#if 0 + /* "can [not] safely pray" vs "could [not] have safely prayed" */ + Sprintf(buf, "%s%ssafely pray%s", can_pray(FALSE) ? "" : "not ", + final ? "have " : "", final ? "ed" : ""); +#else + Sprintf(buf, "%ssafely pray", can_pray(FALSE) ? "" : "not "); +#endif + if (wizard) + Sprintf(eos(buf), " (%d)", u.ublesscnt); + you_can(buf, ""); + } + } + +#ifdef DEBUG + /* named fruit debugging (doesn't really belong here...); to enable, + include 'fruit' in DEBUGFILES list (even though it isn't a file...) */ + if (wizard && explicitdebug("fruit")) { + struct fruit *f; + + reorder_fruit(TRUE); /* sort by fruit index, from low to high; + * this modifies the gf.ffruit chain, so could + * possibly mask or even introduce a problem, + * but it does useful sanity checking */ + for (f = gf.ffruit; f; f = f->nextf) { + Sprintf(buf, "Fruit #%d ", f->fid); + enl_msg(buf, "is ", "was ", f->fname, ""); + } + enl_msg("The current fruit ", "is ", "was ", svp.pl_fruit, ""); + Sprintf(buf, "%d", flags.made_fruit); + enl_msg("The made fruit flag ", "is ", "was ", buf, ""); + } +#endif + + { + const char *p; + + buf[0] = '\0'; + if (final < 2) { /* still in progress, or quit/escaped/ascended */ + p = "survived after being killed "; + if (!u.umortality) + p = !final ? (char *) 0 : "survived"; + else + (void) N_times((long) u.umortality, buf); + } else { /* game ended in character's death */ + p = "are dead"; + switch (u.umortality) { + case 0: + impossible("dead without dying?"); + FALLTHROUGH; + /* FALLTHRU */ + case 1: + break; /* just "are dead" */ + default: + Sprintf(buf, " (%d%s time!)", u.umortality, + ordin(u.umortality)); + break; + } + } + if (p) + enl_msg(You_, "have been killed ", p, buf, ""); + } +} + +/* ^X command */ +int +doattributes(void) +{ + int mode = BASICENLIGHTENMENT; + + /* show more--as if final disclosure--for wizard and explore modes */ + if (wizard || discover) + mode |= MAGICENLIGHTENMENT; + + enlightenment(mode, ENL_GAMEINPROGRESS); + return ECMD_OK; +} + +void +youhiding(boolean via_enlghtmt, /* enlightenment line vs topl message */ + int msgflag) /* for variant message phrasing */ +{ + char *bp, buf[BUFSZ]; + + Strcpy(buf, "hiding"); + if (U_AP_TYPE != M_AP_NOTHING) { + /* mimic; hero is only able to mimic a strange object or gold + or hallucinatory alternative to gold, so we skip the details + for the hypothetical furniture and monster cases */ + bp = eos(strcpy(buf, "mimicking")); + if (U_AP_TYPE == M_AP_OBJECT) { + Sprintf(bp, " %s", an(simple_typename(gy.youmonst.mappearance))); + } else if (U_AP_TYPE == M_AP_FURNITURE) { + Strcpy(bp, " something"); + } else if (U_AP_TYPE == M_AP_MONSTER) { + Strcpy(bp, " someone"); + } else { + ; /* something unexpected; leave 'buf' as-is */ + } + } else if (u.uundetected) { + bp = eos(buf); /* points past "hiding" */ + if (gy.youmonst.data->mlet == S_EEL) { + if (is_pool(u.ux, u.uy)) + Sprintf(bp, " in the %s", waterbody_name(u.ux, u.uy)); + } else if (hides_under(gy.youmonst.data)) { + struct obj *o = svl.level.objects[u.ux][u.uy]; + + if (o) + Sprintf(bp, " underneath %s", ansimpleoname(o)); + } else if (is_clinger(gy.youmonst.data) || Flying) { + /* Flying: 'lurker above' hides on ceiling but doesn't cling */ + Sprintf(bp, " on the %s", ceiling(u.ux, u.uy)); + } else { + /* on floor; is_hider() but otherwise not special: 'trapper' */ + if (u.utrap && u.utraptype == TT_PIT) { + struct trap *t = t_at(u.ux, u.uy); + + Sprintf(bp, " in a %spit", + (t && t->ttyp == SPIKED_PIT) ? "spiked " : ""); + } else + Sprintf(bp, " on the %s", surface(u.ux, u.uy)); + } + } else { + ; /* shouldn't happen; will result in generic "you are hiding" */ + } + + if (via_enlghtmt) { + int final = msgflag; /* 'final' is used by you_are() macro */ + + you_are(buf, ""); + } else { + /* for dohide(), when player uses '#monster' command */ + You("are %s %s.", msgflag ? "already" : "now", buf); + } +} + +/* #conduct command [KMH]; shares enlightenment's tense handling */ +int +doconduct(void) +{ + show_conduct(ENL_GAMEINPROGRESS); + return ECMD_OK; +} + +/* display conducts; for doconduct(), also disclose() and dump_everything() */ +void +show_conduct(int final) +{ + char buf[BUFSZ], bufN[40]; + int ngenocided; + + /* Create the conduct window */ + ge.en_win = create_nhwindow(NHW_MENU); + putstr(ge.en_win, 0, "Voluntary challenges:"); + + /* rerolling; "You " is about the character, rerolling + is about the player so phrase it differently; + also, always use past tense since the chance to do something with it + is gone by time player can issue #conduct command or see disclosure */ + if (!u.uroleplay.reroll) + Strcpy(buf, " Character rerolling was not enabled."); + else if (!u.uroleplay.numrerolls) + Strcpy(buf, " Your character was not rerolled."); + else + Sprintf(buf, " Your character was rerolled %s.", + N_times(u.uroleplay.numrerolls, bufN)); + enlght_out(buf); + + if (u.uroleplay.blind) + you_have_been("blind from birth"); + if (u.uroleplay.deaf) + you_have_been("deaf from birth"); + /* note: we don't report "you are without possessions" unless the + game started with the pauper option set */ + if (u.uroleplay.pauper) + enl_msg(You_, gi.invent ? "started" : "are", "started out", + " without possessions", ""); + /* nudist is far more than a subset of possessionless, and a much + more impressive accomplishment, but showing "started out without + possessions" before "faithfully nudist" looks more logical */ + if (u.uroleplay.nudist) + you_have_been("faithfully nudist"); + + if (!u.uconduct.food) + enl_msg(You_, "have gone", "went", " without food", ""); + /* but beverages are okay */ + else if (!u.uconduct.unvegan) + you_have_X("followed a strict vegan diet"); + else if (!u.uconduct.unvegetarian) + you_have_been("vegetarian"); + + if (!u.uconduct.gnostic) + you_have_been("an atheist"); + + if (!u.uconduct.weaphit) { + you_have_never("hit with a wielded weapon"); + } else if (wizard) { + Sprintf(buf, "hit with a wielded weapon %ld time%s", + u.uconduct.weaphit, plur(u.uconduct.weaphit)); + you_have_X(buf); + } + if (!u.uconduct.killer) + you_have_been("a pacifist"); + + if (!u.uconduct.literate) { + you_have_been("illiterate"); + } else if (wizard) { + Sprintf(buf, "read items or engraved %ld time%s", u.uconduct.literate, + plur(u.uconduct.literate)); + you_have_X(buf); + } + + if (!u.uconduct.pets) + you_have_never("had a pet"); + + ngenocided = num_genocides(); + if (ngenocided == 0) { + you_have_never("genocided any monsters"); + } else { + Sprintf(buf, "genocided %d type%s of monster%s", ngenocided, + plur(ngenocided), plur(ngenocided)); + you_have_X(buf); + } + + if (!u.uconduct.polypiles) { + you_have_never("polymorphed an object"); + } else if (wizard) { + Sprintf(buf, "polymorphed %ld item%s", u.uconduct.polypiles, + plur(u.uconduct.polypiles)); + you_have_X(buf); + } + + if (!u.uconduct.polyselfs) { + you_have_never("changed form"); + } else if (wizard) { + Sprintf(buf, "changed form %ld time%s", u.uconduct.polyselfs, + plur(u.uconduct.polyselfs)); + you_have_X(buf); + } + + if (!u.uconduct.wishes) { + you_have_X("used no wishes"); + } else { + Sprintf(buf, "used %ld wish%s", u.uconduct.wishes, + (u.uconduct.wishes > 1L) ? "es" : ""); + if (u.uconduct.wisharti) { + /* if wisharti == wishes + * 1 wish (for an artifact) + * 2 wishes (both for artifacts) + * N wishes (all for artifacts) + * else (N is at least 2 in order to get here; M < N) + * N wishes (1 for an artifact) + * N wishes (M for artifacts) + */ + if (u.uconduct.wisharti == u.uconduct.wishes) + Sprintf(eos(buf), " (%s", + (u.uconduct.wisharti > 2L) ? "all " + : (u.uconduct.wisharti == 2L) ? "both " : ""); + else + Sprintf(eos(buf), " (%ld ", u.uconduct.wisharti); + + Sprintf(eos(buf), "for %s)", + (u.uconduct.wisharti == 1L) ? "an artifact" + : "artifacts"); + } + you_have_X(buf); + + if (!u.uconduct.wisharti) + enl_msg(You_, "have not wished", "did not wish", + " for any artifacts", ""); + } + + /* only report Sokoban conduct if the Sokoban branch has been entered */ + if (sokoban_in_play()) { + const char *presentverb = "have violated", *pastverb = "violated"; + + if (!u.uconduct.sokocheat) { + presentverb = "have not violated"; + pastverb = "did not violate"; + Strcpy(buf, " any of the special Sokoban rules"); + } else { + Strcpy(buf, " the special Sokoban rules "); + Strcat(buf, N_times(u.uconduct.sokocheat, bufN)); + } + enl_msg(You_, presentverb, pastverb, buf, ""); + } + + show_achievements(final); + + /* Pop up the window and wait for a key */ + display_nhwindow(ge.en_win, TRUE); + destroy_nhwindow(ge.en_win); + ge.en_win = WIN_ERR; +} + +/* + * Achievements (see 'enum achievements' in you.h). + */ + +staticfn void +show_achievements( + int final) /* 'final' is used "behind the curtain" by enl_foo() macros */ +{ + int i, achidx, absidx, acnt; + char title[QBUFSZ], buf[QBUFSZ]; + winid awin = WIN_ERR; + + /* unfortunately we can't show the achievements (at least not all of + them) while the game is in progress because it would give away the + ID of luckstone (at Mine's End) and of real Amulet of Yendor */ + if (!final && !wizard) + return; + + /* first, figure whether any achievements have been accomplished + so that we don't show the header for them if the resulting list + below it would be empty */ + if ((acnt = count_achievements()) == 0) + return; + + if (ge.en_win != WIN_ERR) { + awin = ge.en_win; /* end of game disclosure window */ + putstr(awin, 0, ""); + } else { + awin = create_nhwindow(NHW_MENU); + } + Sprintf(title, "Achievement%s:", plur(acnt)); + putstr(awin, 0, title); + + /* display achievements in the order in which they were recorded; + lone exception is to defer the Amulet if we just ascended; + it warrants alternate wording when given away during ascension, + but the Amulet achievement is always attained before entering + endgame and the alternate wording looks strange if shown before + "reached endgame" and "reached Astral" */ + if (remove_achievement(ACH_UWIN)) { /* UWIN == Ascended! */ + /* for ascension, force it to be last and Amulet next to last + by taking them out and then adding them back */ + if (remove_achievement(ACH_AMUL)) /* should always be True here */ + record_achievement(ACH_AMUL); + record_achievement(ACH_UWIN); + } + for (i = 0; i < acnt; ++i) { + achidx = u.uachieved[i]; + absidx = abs(achidx); + + switch (absidx) { + case ACH_BLND: + enl_msg(You_, "are exploring", "explored", + " without being able to see", ""); + break; + case ACH_NUDE: + enl_msg(You_, "have gone", "went", " without any armor", ""); + break; + case ACH_MINE: + you_have_X("entered the Gnomish Mines"); + break; + case ACH_TOWN: + you_have_X("entered Minetown"); + break; + case ACH_SHOP: + you_have_X("entered a shop"); + break; + case ACH_TMPL: + you_have_X("entered a temple"); + break; + case ACH_ORCL: + you_have_X("consulted the Oracle of Delphi"); + break; + case ACH_NOVL: + you_have_X("read from a Discworld novel"); + break; + case ACH_SOKO: + you_have_X("entered Sokoban"); + break; + case ACH_SOKO_PRIZE: /* hard to reach guaranteed bag or amulet */ + you_have_X("completed Sokoban"); + break; + case ACH_MINE_PRIZE: /* hidden guaranteed luckstone */ + you_have_X("completed the Gnomish Mines"); + break; + case ACH_BGRM: + you_have_X("entered the Big Room"); + break; + case ACH_MEDU: + you_have_X("defeated Medusa"); + break; + case ACH_TUNE: + you_have_X( + "learned the tune to open and close the Castle's drawbridge"); + break; + case ACH_BELL: + /* alternate phrasing for present vs past and also for + possessing the item vs once held it */ + enl_msg(You_, + u.uhave.bell ? "have" : "have handled", + u.uhave.bell ? "had" : "handled", + " the Bell of Opening", ""); + break; + case ACH_HELL: + enl_msg(You_, "have ", "", "entered Gehennom", ""); + break; + case ACH_CNDL: + enl_msg(You_, + u.uhave.menorah ? "have" : "have handled", + u.uhave.menorah ? "had" : "handled", + " the Candelabrum of Invocation", ""); + break; + case ACH_BOOK: + enl_msg(You_, + u.uhave.book ? "have" : "have handled", + u.uhave.book ? "had" : "handled", + " the Book of the Dead", ""); + break; + case ACH_INVK: + you_have_X("gained access to Moloch's Sanctum"); + break; + case ACH_AMUL: + /* alternate wording for ascended (always past tense) since + hero had it until #offer forced it to be relinquished */ + enl_msg(You_, + u.uhave.amulet ? "have" : "have obtained", + u.uevent.ascended ? "delivered" + : u.uhave.amulet ? "had" : "had obtained", + " the Amulet of Yendor", ""); + break; + + /* reaching Astral makes feedback about reaching the Planes + be redundant and ascending makes both be redundant, but + we display all that apply */ + case ACH_ENDG: + you_have_X("reached the Elemental Planes"); + break; + case ACH_ASTR: + you_have_X("reached the Astral Plane"); + break; + case ACH_UWIN: + /* the ultimate achievement... */ + enlght_out(" You ascended!"); + break; + + /* rank 0 is the starting condition, not an achievement; 8 is Xp 30 */ + case ACH_RNK1: case ACH_RNK2: case ACH_RNK3: case ACH_RNK4: + case ACH_RNK5: case ACH_RNK6: case ACH_RNK7: case ACH_RNK8: + Sprintf(buf, "attained the rank of %s", + rank_of(rank_to_xlev(absidx - (ACH_RNK1 - 1)), + Role_switch, (achidx < 0) ? TRUE : FALSE)); + you_have_X(buf); + break; + + default: + Sprintf(buf, " [Unexpected achievement #%d.]", achidx); + enlght_out(buf); + break; + } /* switch */ + } /* for */ + + if (awin != ge.en_win) { + display_nhwindow(awin, TRUE); + destroy_nhwindow(awin); + } +} + +/* record an achievement (add at end of list unless already present) */ +void +record_achievement(schar achidx) +{ + int i, absidx; + int repeat_achievement = 0; + + absidx = abs(achidx); + /* valid achievements range from 1 to N_ACH-1; however, ranks can be + stored as the complement (ie, negative) to track gender */ + if ((achidx < 1 && (absidx < ACH_RNK1 || absidx > ACH_RNK8)) + || achidx >= N_ACH) { + impossible("Achievement #%d is out of range.", achidx); + return; + } + + /* the list has an extra slot so there is always at least one 0 at + its end (more than one unless all N_ACH-1 possible achievements + have been recorded); find first empty slot or achievement #achidx; + an attempt to duplicate an achievement can happen if any of Bell, + Candelabrum, Book, or Amulet is dropped then picked up again */ + for (i = 0; u.uachieved[i]; ++i) + if (abs(u.uachieved[i]) == absidx) { + repeat_achievement = 1; + break; + } + + /* + * We do the sound for an achievement, even if it has already been + * achieved before. Some players might have set up level-based + * theme music or something. We do let the sound interface know + * that it's not the original achievement though. + */ + SoundAchievement(achidx, 0, repeat_achievement); + + if (repeat_achievement) + return; /* already recorded, don't duplicate it */ + u.uachieved[i] = achidx; + + /* avoid livelog for achievements recorded during final disclosure: + nudist and blind-from-birth; also ascension which is suppressed + by this gets logged separately in really_done() */ + if (program_state.gameover) + return; + + if (absidx >= ACH_RNK1 && absidx <= ACH_RNK8) { + livelog_printf(achieve_msg[absidx].llflag, + "attained the rank of %s (level %d)", + rank_of(rank_to_xlev(absidx - (ACH_RNK1 - 1)), + Role_switch, (achidx < 0) ? TRUE : FALSE), + u.ulevel); + } else if (achidx == ACH_SOKO_PRIZE + || achidx == ACH_MINE_PRIZE) { + /* need to supply extra information for these two */ + short otyp = ((achidx == ACH_SOKO_PRIZE) + ? svc.context.achieveo.soko_prize_otyp + : svc.context.achieveo.mines_prize_otyp); + + /* note: OBJ_NAME() works here because both "bag of holding" and + "amulet of reflection" are fully named in their objects[] entry + but that's not true in the general case */ + livelog_printf(achieve_msg[achidx].llflag, "%s %s", + achieve_msg[achidx].msg, OBJ_NAME(objects[otyp])); + } else { + livelog_printf(achieve_msg[absidx].llflag, "%s", + achieve_msg[absidx].msg); + } +} + +/* discard a recorded achievement; return True if removed, False otherwise */ +boolean +remove_achievement(schar achidx) +{ + int i; + + for (i = 0; u.uachieved[i]; ++i) + if (abs(u.uachieved[i]) == abs(achidx)) + break; /* stop when found */ + if (!u.uachieved[i]) /* not found */ + return FALSE; + /* list is 0 terminated so any beyond the removed one move up a slot */ + do { + u.uachieved[i] = u.uachieved[i + 1]; + } while (u.uachieved[++i]); + return TRUE; +} + +/* used to decide whether there are any achievements to display */ +int +count_achievements(void) +{ + int i, acnt = 0; + + for (i = 0; u.uachieved[i]; ++i) + ++acnt; + return acnt; +} + +/* convert a rank index to an achievement number; encode it when female + in order to subsequently report gender-specific ranks accurately */ +schar +achieve_rank(int rank) /* 1..8 */ +{ + schar achidx = (schar) ((rank - 1) + ACH_RNK1); + + if (flags.female) + achidx = -achidx; + return achidx; +} + +/* return True if sokoban branch has been entered, False otherwise */ +boolean +sokoban_in_play(void) +{ + int achidx; + + /* TODO? move this to dungeon.c and test furthest level reached of the + sokoban branch instead of relying on the entered-sokoban achievement */ + + for (achidx = 0; u.uachieved[achidx]; ++achidx) + if (u.uachieved[achidx] == ACH_SOKO) + return TRUE; + return FALSE; +} + +/* #chronicle command */ +int +do_gamelog(void) +{ +#ifdef CHRONICLE + if (gg.gamelog) { + show_gamelog(ENL_GAMEINPROGRESS); + } else { + pline("No chronicled events."); + } +#else + pline("Chronicle was turned off during compile-time."); +#endif /* !CHRONICLE */ + return ECMD_OK; +} + +/* 'major' events for dumplog; inclusion or exclusion here may need tuning */ +#define LL_majors (0L \ + | LL_WISH \ + | LL_ACHIEVE \ + | LL_UMONST \ + | LL_DIVINEGIFT \ + | LL_LIFESAVE \ + | LL_ARTIFACT \ + | LL_GENOCIDE \ + | LL_DUMP) /* explicitly for dumplog */ +#define majorevent(llmsg) (((llmsg)->flags & LL_majors) != 0) +#define spoilerevent(llmsg) (((llmsg)->flags & LL_SPOILER) != 0) + +/* #chronicle details */ +void +show_gamelog(int final) +{ +#ifdef CHRONICLE + struct gamelog_line *llmsg; + winid win; + char buf[BUFSZ]; + int eventcnt = 0; + + win = create_nhwindow(NHW_TEXT); + Sprintf(buf, "%s events:", final ? "Major" : "Logged"); + putstr(win, 0, buf); + for (llmsg = gg.gamelog; llmsg; llmsg = llmsg->next) { + if (final && !majorevent(llmsg)) + continue; + if (!final && !wizard && spoilerevent(llmsg)) + continue; + if (!eventcnt++) + putstr(win, 0, " Turn"); + Snprintf(buf, sizeof buf, "%5ld: %s", llmsg->turn, llmsg->text); + putstr(win, 0, buf); + } + /* since start of game is logged as a major event, 'eventcnt' should + never end up as 0; for 'final', end of game is a major event too */ + if (!eventcnt) + putstr(win, 0, " none"); + + display_nhwindow(win, TRUE); + destroy_nhwindow(win); +#else + nhUse(final); +#endif /* !CHRONICLE */ + return; +} + +/* + * Vanquished monsters. + */ + +/* the two uppercase choices are implemented but suppressed from menu. + also used in options.c */ +const char *const vanqorders[NUM_VANQ_ORDER_MODES][3] = { + { "t", "traditional: by monster level", + "traditional: by monster level, by internal monster index" }, + { "d", "by monster difficulty rating", + "by monster difficulty rating, by internal monster index" }, + { "a", "alphabetically, unique monsters separate", + "alphabetically, first unique monsters, then others" }, + { "A", "alphabetically, unique monsters intermixed", + "alphabetically, unique monsters and others intermixed" }, + { "C", "by monster class, high to low level in class", + "by monster class, high to low level within class" }, + { "c", "by monster class, low to high level in class", + "by monster class, low to high level within class" }, + { "n", "by count, high to low", + "by count, high to low, by internal index within tied count" }, + { "z", "by count, low to high", + "by count, low to high, by internal index within tied count" }, +}; + +staticfn int QSORTCALLBACK +vanqsort_cmp( + const genericptr vptr1, + const genericptr vptr2) +{ + int indx1 = *(short *) vptr1, indx2 = *(short *) vptr2, + mlev1, mlev2, mstr1, mstr2, uniq1, uniq2, died1, died2, res; + const char *name1, *name2, *punct; + schar mcls1, mcls2; + + switch (flags.vanq_sortmode) { + default: + case VANQ_MLVL_MNDX: + /* sort by monster level */ + mlev1 = mons[indx1].mlevel; + mlev2 = mons[indx2].mlevel; + res = mlev2 - mlev1; /* mlevel high to low */ + break; + case VANQ_MSTR_MNDX: + /* sort by monster toughness */ + mstr1 = mons[indx1].difficulty; + mstr2 = mons[indx2].difficulty; + res = mstr2 - mstr1; /* monstr high to low */ + break; + case VANQ_ALPHA_SEP: + uniq1 = ((mons[indx1].geno & G_UNIQ) && indx1 != PM_HIGH_CLERIC); + uniq2 = ((mons[indx2].geno & G_UNIQ) && indx2 != PM_HIGH_CLERIC); + if (uniq1 ^ uniq2) { /* one or other uniq, but not both */ + res = uniq2 - uniq1; + break; + } /* else both unique or neither unique */ + FALLTHROUGH; + /*FALLTHRU*/ + case VANQ_ALPHA_MIX: + name1 = mons[indx1].pmnames[NEUTRAL]; + name2 = mons[indx2].pmnames[NEUTRAL]; + res = strcmpi(name1, name2); /* caseblind alpha, low to high */ + break; + case VANQ_MCLS_HTOL: + case VANQ_MCLS_LTOH: + /* mons[].mlet is a small integer, 1..N, of type plain char; + if 'char' happens to be unsigned, (mlet1 - mlet2) would yield + an inappropriate result when mlet2 is greater than mlet1, + so force our copies (mcls1, mcls2) to be signed */ + mcls1 = (schar) mons[indx1].mlet; + mcls2 = (schar) mons[indx2].mlet; + /* S_ANT through S_ZRUTY correspond to lowercase monster classes, + S_ANGEL through S_ZOMBIE correspond to uppercase, and various + punctuation characters are used for classes beyond those */ + if (mcls1 > S_ZOMBIE && mcls2 > S_ZOMBIE) { + /* force a specific order to the punctuation classes that's + different from the internal order; + internal order is ok if neither or just one is punctuation + since letters have lower values so come out before punct */ + static const char punctclasses[] = { + S_LIZARD, S_EEL, S_GOLEM, S_GHOST, S_DEMON, S_HUMAN, '\0' + }; + + if ((punct = strchr(punctclasses, mcls1)) != 0) + mcls1 = (schar) (S_ZOMBIE + 1 + (int) (punct - punctclasses)); + if ((punct = strchr(punctclasses, mcls2)) != 0) + mcls2 = (schar) (S_ZOMBIE + 1 + (int) (punct - punctclasses)); + } + res = mcls1 - mcls2; /* class */ + if (res == 0) { + /* Riders are in the same class as major demons, yielding res==0 + above when both mcls1 and mcls2 are either Riders or demons or + one of each; force Riders to be sorted before demons */ + res = is_rider(&mons[indx2]) - is_rider(&mons[indx1]); + /* res -1 => #1 is a Rider, #2 isn't; + 0 => both Riders or neither; + +1 => #2 is a Rider, #1 isn't */ + if (res) + break; + mlev1 = mons[indx1].mlevel; + mlev2 = mons[indx2].mlevel; + res = mlev1 - mlev2; /* mlevel low to high */ + if (flags.vanq_sortmode == VANQ_MCLS_HTOL) + res = -res; /* mlevel high to low */ + } + break; + case VANQ_COUNT_H_L: + case VANQ_COUNT_L_H: + died1 = svm.mvitals[indx1].died; + died2 = svm.mvitals[indx2].died; + res = died2 - died1; /* dead count high to low */ + if (flags.vanq_sortmode == VANQ_COUNT_L_H) + res = -res; /* dead count low to high */ + break; + } + /* tiebreaker: internal mons[] index */ + if (res == 0) + res = indx1 - indx2; /* mndx low to high */ + return res; +} + +/* returns -1 if cancelled via ESC */ +int +set_vanq_order(boolean for_vanq) +{ + winid tmpwin; + menu_item *selected; + anything any; + char buf[BUFSZ]; + const char *desc; + int i, n, choice, + clr = NO_COLOR; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; /* zero out all bits */ + for (i = 0; i < SIZE(vanqorders); i++) { + if (i == VANQ_ALPHA_MIX || i == VANQ_MCLS_HTOL) /* skip these */ + continue; + /* suppress some orderings if this menu if for 'm #genocided' */ + if (!for_vanq && (i == VANQ_COUNT_H_L || i == VANQ_COUNT_L_H)) + continue; + desc = vanqorders[i][2]; + /* unique monsters can't be genocided so "alpha, unique separate" + and "alpha, unique intermixed" are confusing descriptions when + this menu is for #genocided rather than for #vanquished */ + if (!for_vanq && i == VANQ_ALPHA_SEP) + desc = "alphabetically"; + any.a_int = i + 1; + add_menu(tmpwin, &nul_glyphinfo, &any, *vanqorders[i][0], 0, + ATR_NONE, clr, desc, + (i == flags.vanq_sortmode) ? MENU_ITEMFLAGS_SELECTED + : MENU_ITEMFLAGS_NONE); + } + Sprintf(buf, "Sort order for %s", + for_vanq ? "vanquished monster counts (also genocided types)" + : "genocided monster types (also vanquished counts)"); + end_menu(tmpwin, buf); + + n = select_menu(tmpwin, PICK_ONE, &selected); + destroy_nhwindow(tmpwin); + if (n > 0) { + choice = selected[0].item.a_int - 1; + /* skip preselected entry if we have more than one item chosen */ + if (n > 1 && choice == flags.vanq_sortmode) + choice = selected[1].item.a_int - 1; + free((genericptr_t) selected); + flags.vanq_sortmode = choice; + } + return (n < 0) ? -1 : flags.vanq_sortmode; +} + +/* #vanquished command */ +int +dovanquished(void) +{ + list_vanquished(iflags.menu_requested ? 'A' : 'y', FALSE); + iflags.menu_requested = FALSE; + return ECMD_OK; +} + +/* high priests aren't unique but are flagged as such to simplify something */ +#define UniqCritterIndx(mndx) \ + ((mons[mndx].geno & G_UNIQ) != 0 && mndx != PM_HIGH_CLERIC) + +#define done_stopprint program_state.stopprint + +/* used for #vanquished and end of game disclosure and end of game dumplog */ +void +list_vanquished(char defquery, boolean ask) +{ + int i; + int pfx, nkilled; + unsigned ntypes, ni; + long total_killed = 0L; + winid klwin; + short mindx[NUMMONS]; + char c, buf[BUFSZ], buftoo[BUFSZ]; + /* 'A' is only supplied by 'm #vanquished'; 'd' is only supplied by + dump_everything() when writing dumplog, so won't happen if built + without '#define DUMPLOG' but there's no need for conditionals here */ + boolean force_sort = (defquery == 'A'), + dumping = (defquery == 'd'); + + /* normally we don't ask about sort order for the vanquished list unless + it contains at least two entries; however, if player has used explicit + 'm #vanquished', choose order no matter what it contains so far */ + if (force_sort) { /* iflags.menu_requested via dovanquished() */ + /* choose value for vanq_sortmode via menu; ESC cancels choosing + sort order but continues with vanquishd monsters display */ + (void) set_vanq_order(TRUE); + } + if (dumping || force_sort) { + /* switch from 'A' or 'd' to 'y'; 'ask' is already False for the + cases that might supply 'A' or 'd' */ + defquery = 'y'; + ask = FALSE; /* redundant */ + } + + /* get totals first */ + ntypes = 0; + for (i = LOW_PM; i < NUMMONS; i++) { + if ((nkilled = (int) svm.mvitals[i].died) == 0) + continue; + mindx[ntypes++] = i; + total_killed += (long) nkilled; + } + + /* vanquished creatures list; + * includes all dead monsters, not just those killed by the player + */ + if (ntypes != 0) { + char mlet, prev_mlet = 0; /* used as small integer, not character */ + boolean class_header, uniq_header, Rider, + was_uniq = FALSE, special_hdr = FALSE; + + if (ask) { + char allow_yn[10]; + + if (ntypes > 1) { + Strcpy(allow_yn, ynaqchars); + } else { + Strcpy(allow_yn, ynqchars); /* don't include 'a', but */ + Strcat(allow_yn, "\033a"); /* allow user to answer 'a' */ + if (defquery == 'a') /* potential default from 'disclose' */ + defquery = 'y'; + } + c = yn_function("Do you want an account of creatures vanquished?", + allow_yn, defquery, TRUE); + } else { + c = defquery; + } + + if (c == 'q') + done_stopprint++; + if (c == 'y' || c == 'a') { + if (c == 'a' && ntypes > 1) { /* ask user to choose sort order */ + /* choose value for vanq_sortmode via menu; ESC cancels list + of vanquished monsters but does not set 'done_stopprint' */ + if (set_vanq_order(TRUE) < 0) + return; + } + uniq_header = (flags.vanq_sortmode == VANQ_ALPHA_SEP); + class_header = ((flags.vanq_sortmode == VANQ_MCLS_LTOH + || flags.vanq_sortmode == VANQ_MCLS_HTOL) + && ntypes > 1); + + klwin = create_nhwindow(NHW_MENU); + putstr(klwin, 0, "Vanquished creatures:"); + if (!dumping) + putstr(klwin, 0, ""); + + qsort((genericptr_t) mindx, ntypes, sizeof *mindx, vanqsort_cmp); + for (ni = 0; ni < ntypes; ni++) { + i = mindx[ni]; + nkilled = svm.mvitals[i].died; + Rider = is_rider(&mons[i]); + mlet = mons[i].mlet; + if (class_header + && (mlet != prev_mlet || (special_hdr && !Rider))) { + if (!Rider) { + Strcpy(buf, def_monsyms[(int) mlet].explain); + special_hdr = FALSE; + } else { + Strcpy(buf, "Rider"); + special_hdr = TRUE; + } + /* 'ask' implies final disclosure, where highlighting + of various header lines is suppressed */ + putstr(klwin, ask ? ATR_NONE : iflags.menu_headings.attr, + upstart(buf)); + prev_mlet = mlet; + } + if (UniqCritterIndx(i)) { + Sprintf(buf, "%s%s", + !type_is_pname(&mons[i]) ? "the " : "", + mons[i].pmnames[NEUTRAL]); + if (nkilled > 1) + Sprintf(eos(buf), " (%s)", + N_times((long) nkilled, buftoo)); + was_uniq = TRUE; + } else { + if (uniq_header && was_uniq) { + putstr(klwin, 0, ""); + was_uniq = FALSE; + } + /* trolls or undead might have come back, + but we don't keep track of that */ + if (nkilled == 1) + Strcpy(buf, an(mons[i].pmnames[NEUTRAL])); + else + Sprintf(buf, "%3d %s", nkilled, + makeplural(mons[i].pmnames[NEUTRAL])); + } + /* number of leading spaces to match 3 digit prefix */ + pfx = !strncmpi(buf, "the ", 4) ? 0 + : !strncmpi(buf, "an ", 3) ? 1 + : !strncmpi(buf, "a ", 2) ? 2 + : !digit(buf[2]) ? 4 : 0; + if (class_header) + ++pfx; + Snprintf(buftoo, sizeof buftoo, "%*s%s", pfx, "", buf); + putstr(klwin, 0, buftoo); + } + /* + * if (Hallucination) + * putstr(klwin, 0, "and a partridge in a pear tree"); + */ + if (ntypes > 1) { + if (!dumping) + putstr(klwin, 0, ""); + Sprintf(buf, "%ld creatures vanquished.", total_killed); + putstr(klwin, 0, buf); + } + display_nhwindow(klwin, TRUE); + destroy_nhwindow(klwin); + } + + /* + * For end-of-game disclosure, we're only called when some monsters + * were vanquished and won't reach these 'else-if's. + * + * If no monsters have been vanquished, we're either called for game + * still in progress, so use present tense via pline(), or for dumplog + * which needs putstr() and past tense. + */ + } else if (!program_state.gameover) { + /* #vanquished rather than final disclosure, so pline() is ok */ + pline("No creatures have been vanquished."); +#ifdef DUMPLOG + } else if (dumping) { + putstr(0, 0, "No creatures were vanquished."); /* not pline() */ +#endif + } +} + +/* number of monster species which have been genocided */ +int +num_genocides(void) +{ + int i, n = 0; + + for (i = LOW_PM; i < NUMMONS; ++i) { + if (svm.mvitals[i].mvflags & G_GENOD) { + ++n; + if (UniqCritterIndx(i)) + impossible("unique creature '%d: %s' genocided?", + i, mons[i].pmnames[NEUTRAL]); + } + } + return n; +} + +/* return a count of the number of extinct species */ +staticfn int +num_extinct(void) +{ + int i, n = 0; + + for (i = LOW_PM; i < NUMMONS; ++i) { + if (UniqCritterIndx(i)) + continue; + if ((svm.mvitals[i].mvflags & G_GONE) == G_EXTINCT) + ++n; + } + return n; +} + +/* collect both genocides and extinctions, skipping uniques */ +staticfn int +num_gone(int mvflags, int *mindx) +{ + uchar mflg = (uchar) mvflags; + int i, n = 0; + + (void) memset((genericptr_t) mindx, 0, NUMMONS * sizeof *mindx); + + for (i = LOW_PM; i < NUMMONS; ++i) { + /* uniques can't be genocided but can become extinct; + however, they're never reported as extinct, so skip them */ + if (UniqCritterIndx(i)) + continue; + + if ((svm.mvitals[i].mvflags & mflg) != 0) + mindx[n++] = i; + } + return n; +} + +/* show genocided and extinct monster types for final disclosure/dumplog + or for the #genocided command */ +void +list_genocided(char defquery, boolean ask) +{ + int i, mndx; + int ngenocided, nextinct, ngone, mvflags, mindx[NUMMONS]; + char c; + winid klwin; + char buf[BUFSZ]; + boolean genoing, /* prompting for genocide or class genocide */ + dumping; /* for DUMPLOG; doesn't need to be conditional */ + boolean both = (program_state.gameover || wizard || discover); + + dumping = (defquery == 'd'); + genoing = (defquery == 'g'); + if (dumping || genoing) + defquery = 'y'; + if (genoing) + both = FALSE; /* genocides only, not extinctions */ + + /* this goes through the whole monster list up to three times but will + happen rarely and is simpler than a more general single pass check; + extinctions are only revealed during end of game disclosure or when + running in wizard or explore mode */ + ngenocided = num_genocides(); + nextinct = both ? num_extinct() : 0; + mvflags = G_GENOD | (both ? G_EXTINCT : 0); + ngone = num_gone(mvflags, mindx); + + /* genocided or extinct species list */ + if (ngone > 0) { + Sprintf(buf, "Do you want a list of %sspecies%s%s?", + (nextinct && !ngenocided) ? "extinct " : "", + (ngenocided) ? " genocided" : "", + (nextinct && ngenocided) ? " and extinct" : ""); + c = ask ? yn_function(buf, (ngone > 1) ? "ynaq" : "ynq\033a", + defquery, TRUE) + : defquery; + if (c == 'q') + done_stopprint++; + if (c == 'y' || c == 'a') { + int save_sortmode; + char mlet, prev_mlet = 0; + boolean class_header = FALSE; + + if (ngone > 1) { + if (c == 'a') { /* ask player to choose sort order */ + /* #genocided shares #vanquished's sort order */ + if (set_vanq_order(FALSE) < 0) + return; + } + /* sort orderings count-high-to-low or count-low-to-high + don't make sense for genocides; if the preferred order + to set to either of those, use alphabetical instead; + note: the tie breaker for by-class is level-high-to-low + or level-low-to-high rather than count so is ok as-is */ + save_sortmode = flags.vanq_sortmode; + if (flags.vanq_sortmode == VANQ_COUNT_H_L + || flags.vanq_sortmode == VANQ_COUNT_L_H) + flags.vanq_sortmode = VANQ_ALPHA_MIX; + qsort((genericptr_t) mindx, ngone, + sizeof *mindx, vanqsort_cmp); + class_header = (flags.vanq_sortmode == VANQ_MCLS_LTOH + || flags.vanq_sortmode == VANQ_MCLS_HTOL); + flags.vanq_sortmode = save_sortmode; + } + + klwin = create_nhwindow(NHW_MENU); + Sprintf(buf, "%s%s species:", + (ngenocided) ? "Genocided" : "Extinct", + (nextinct && ngenocided) ? " or extinct" : ""); + putstr(klwin, 0, buf); + if (!dumping) + putstr(klwin, 0, ""); + + for (i = 0; i < ngone; ++i) { + mndx = mindx[i]; + mlet = mons[mndx].mlet; + if (class_header && mlet != prev_mlet) { + Strcpy(buf, def_monsyms[(int) mlet].explain); + /* 'ask' implies final disclosure, where highlighting + of various header lines is suppressed */ + putstr(klwin, ask ? ATR_NONE : iflags.menu_headings.attr, + upstart(buf)); + prev_mlet = mlet; + } + Sprintf(buf, " %s", makeplural(mons[mndx].pmnames[NEUTRAL])); + /* + * "Extinct" is unfortunate terminology. A species + * is marked extinct when its birth limit is reached, + * but there might be members of the species still + * alive, contradicting the meaning of the word. + * + * We only append "(extinct)" if the G_GENOD bit is + * clear. During normal play, 'mndx' won't be in the + * collected list unless that bit is set. + */ + if ((svm.mvitals[mndx].mvflags & G_GONE) == G_EXTINCT) + Strcat(buf, " (extinct)"); + putstr(klwin, 0, buf); + } + if (!dumping) + putstr(klwin, 0, ""); + if (ngenocided > 0) { + Sprintf(buf, "%d species genocided.", ngenocided); + putstr(klwin, 0, buf); + } + if (nextinct > 0) { + Sprintf(buf, "%d species extinct.", nextinct); + putstr(klwin, 0, buf); + } + + display_nhwindow(klwin, TRUE); + destroy_nhwindow(klwin); + } + + /* See the comment for similar code near the end of list_vanquished(). */ + } else if (!program_state.gameover) { + /* #genocided rather than final disclosure, so pline() is ok and + extinction has been ignored */ + pline("No creatures have been genocided%s.", genoing ? " yet" : ""); +#ifdef DUMPLOG + } else if (dumping) { /* 'gameover' is True if we make it here */ + putstr(0, 0, "No species were genocided or became extinct."); +#endif + } +} + +/* M-g - #genocided command */ +int +dogenocided(void) +{ + list_genocided(iflags.menu_requested ? 'a' : 'y', FALSE); + return ECMD_OK; +} + +DISABLE_WARNING_FORMAT_NONLITERAL + +/* #wizborn extended command */ +int +doborn(void) +{ + static const char fmt[] = "%4i %4i %c %-30s"; + int i; + winid datawin = create_nhwindow(NHW_TEXT); + char buf[BUFSZ]; + int nborn = 0, ndied = 0; + + putstr(datawin, 0, "died born"); + for (i = LOW_PM; i < NUMMONS; i++) + if (svm.mvitals[i].born || svm.mvitals[i].died + || (svm.mvitals[i].mvflags & G_GONE) != 0) { + Sprintf(buf, fmt, + svm.mvitals[i].died, svm.mvitals[i].born, + ((svm.mvitals[i].mvflags & G_GONE) == G_EXTINCT) ? 'E' + : ((svm.mvitals[i].mvflags & G_GONE) == G_GENOD) ? 'G' + : ((svm.mvitals[i].mvflags & G_GONE) != 0) ? 'X' + : ' ', + mons[i].pmnames[NEUTRAL]); + putstr(datawin, 0, buf); + nborn += svm.mvitals[i].born; + ndied += svm.mvitals[i].died; + } + + putstr(datawin, 0, ""); + Sprintf(buf, fmt, ndied, nborn, ' ', ""); + + display_nhwindow(datawin, FALSE); + destroy_nhwindow(datawin); + + return ECMD_OK; +} + +RESTORE_WARNING_FORMAT_NONLITERAL + +/* + * align_str(), piousness(), mstatusline() and ustatusline() once resided + * in pline.c, then got moved to priest.c just to be out of there. They + * fit better here. + */ + +const char * +align_str(aligntyp alignment) +{ + switch ((int) alignment) { + case A_CHAOTIC: + return "chaotic"; + case A_NEUTRAL: + return "neutral"; + case A_LAWFUL: + return "lawful"; + case A_NONE: + return "unaligned"; + } + return "unknown"; +} + +staticfn char * +size_str(int msize) +{ + static char outbuf[40]; + + switch (msize) { + case MZ_TINY: + Strcpy(outbuf, "tiny"); + break; + case MZ_SMALL: + Strcpy(outbuf, "small"); + break; + case MZ_MEDIUM: + Strcpy(outbuf, "medium"); + break; + case MZ_LARGE: + Strcpy(outbuf, "large"); + break; + case MZ_HUGE: + Strcpy(outbuf, "huge"); + break; + case MZ_GIGANTIC: + Strcpy(outbuf, "gigantic"); + break; + default: + Sprintf(outbuf, "unknown size (%d)", msize); + break; + } + return outbuf; +} + +/* used for self-probing */ +char * +piousness(boolean showneg, const char *suffix) +{ + static char buf[32]; /* bigger than "insufficiently neutral" */ + const char *pio; + + /* note: piousness 20 matches MIN_QUEST_ALIGN (quest.h) */ + if (u.ualign.record >= 20) + pio = "piously"; + else if (u.ualign.record > 13) + pio = "devoutly"; + else if (u.ualign.record > 8) + pio = "fervently"; + else if (u.ualign.record > 3) + pio = "stridently"; + else if (u.ualign.record == 3) + pio = ""; + else if (u.ualign.record > 0) + pio = "haltingly"; + else if (u.ualign.record == 0) + pio = "nominally"; + else if (!showneg) + pio = "insufficiently"; + else if (u.ualign.record >= -3) + pio = "strayed"; + else if (u.ualign.record >= -8) + pio = "sinned"; + else + pio = "transgressed"; + + Sprintf(buf, "%s", pio); + if (suffix && (!showneg || u.ualign.record >= 0)) { + if (u.ualign.record != 3) + Strcat(buf, " "); + Strcat(buf, suffix); + } + return buf; +} + +/* stethoscope or probing applied to monster -- one-line feedback */ +void +mstatusline(struct monst *mtmp) +{ + aligntyp alignment = mon_aligntyp(mtmp); + char info[BUFSZ], monnambuf[BUFSZ]; + + info[0] = 0; + if (mtmp->mtame) { + Strcat(info, ", tame"); + if (wizard) { + Sprintf(eos(info), " (%d", mtmp->mtame); + if (!mtmp->isminion) + Sprintf(eos(info), "; hungry %ld; apport %d", + EDOG(mtmp)->hungrytime, EDOG(mtmp)->apport); + Strcat(info, ")"); + } + } else if (mtmp->mpeaceful) + Strcat(info, ", peaceful"); + + if (mtmp->data == &mons[PM_LONG_WORM]) { + int segndx, nsegs = count_wsegs(mtmp); + + /* the worm code internals don't consider the head to be one of + the worm's segments, but we count it as such when presenting + worm feedback to the player */ + if (!nsegs) { + Strcat(info, ", single segment"); + } else { + ++nsegs; /* include head in the segment count */ + segndx = wseg_at(mtmp, gb.bhitpos.x, gb.bhitpos.y); + Sprintf(eos(info), ", %d%s of %d segments", + segndx, ordin(segndx), nsegs); + } + } + if (ismnum(mtmp->cham) && mtmp->data != &mons[mtmp->cham]) + /* don't reveal the innate form (chameleon, vampire, &c), + just expose the fact that this current form isn't it */ + Strcat(info, ", shapechanger"); + /* pets eating mimic corpses mimic while eating, so this comes first */ + if (mtmp->meating) + Strcat(info, ", eating"); + /* a stethoscope exposes mimic before getting here so this + won't be relevant for it, but wand of probing doesn't */ + if (mtmp->mundetected || mtmp->m_ap_type + || visible_region_at(gb.bhitpos.x, gb.bhitpos.y)) + mhidden_description(mtmp, + MHID_PREFIX | MHID_ARTICLE | MHID_ALTMON | MHID_REGION, + eos(info)); + if (mtmp->mcan) + Strcat(info, ", cancelled"); + if (mtmp->mconf) + Strcat(info, ", confused"); + if (mtmp->mblinded || !mtmp->mcansee) + Strcat(info, ", blind"); + if (mtmp->mstun) + Strcat(info, ", stunned"); + if (mtmp->msleeping) + Strcat(info, ", asleep"); +#if 0 /* unfortunately mfrozen covers temporary sleep and being busy + * (donning armor, for instance) as well as paralysis */ + else if (mtmp->mfrozen) + Strcat(info, ", paralyzed"); +#else + else if (mtmp->mfrozen || !mtmp->mcanmove) + Strcat(info, ", can't move"); +#endif + /* [arbitrary reason why it isn't moving] */ + else if ((mtmp->mstrategy & STRAT_WAITMASK) != 0) + Strcat(info, ", meditating"); + if (mtmp->mflee) + Strcat(info, ", scared"); + if (mtmp->mtrapped) + Strcat(info, ", trapped"); + if (mtmp->mspeed) + Strcat(info, (mtmp->mspeed == MFAST) ? ", fast" + : (mtmp->mspeed == MSLOW) ? ", slow" + : ", [? speed]"); + if (mtmp->minvis) + Strcat(info, ", invisible"); + if (mtmp == u.ustuck) { + struct permonst *pm = u.ustuck->data; + + /* being swallowed/engulfed takes priority over sticks(youmonst); + this used to have that backwards and checked sticks() first */ + Strcat(info, u.uswallow ? (digests(pm) + ? ", digesting you" + /* note: the "swallowing you" case won't + happen because all animal engulfers + either digest their victims (purple + worm) or enfold them (trappers and + lurkers above) */ + : (is_animal(pm) && !enfolds(pm)) + ? ", swallowing you" + : ", engulfing you") + /* !u.uswallow; if both youmonst and ustuck are holders, + youmonst wins */ + : (!sticks(gy.youmonst.data) ? ", holding you" + : ", held by you")); + } + if (mtmp == u.usteed) { + Strcat(info, ", carrying you"); + if (Wounded_legs) { + /* EWounded_legs is used to track left/right/both rather than + some form of extrinsic impairment; HWounded_legs is used for + timeout; both apply to steed instead of hero when mounted */ + long legs = (EWounded_legs & BOTH_SIDES); + const char *what = mbodypart(mtmp, LEG); + + if (legs == BOTH_SIDES) + what = makeplural(what); + Sprintf(eos(info), ", injured %s", what); + } + } + if (mtmp->mleashed) + Strcat(info, ", leashed"); + + /* avoid "Status of the invisible newt ..., invisible" */ + /* and unlike a normal mon_nam, use "saddled" even if it has a name */ + Strcpy(monnambuf, x_monnam(mtmp, ARTICLE_YOUR, (char *) 0, + (SUPPRESS_IT | SUPPRESS_INVISIBLE), FALSE)); + + pline("Status of %s (%s, %s): Level %d HP %d(%d) AC %d%s.", + monnambuf, align_str(alignment), size_str(mtmp->data->msize), + mtmp->m_lev, mtmp->mhp, mtmp->mhpmax, find_mac(mtmp), info); +} + +/* stethoscope or probing applied to hero -- one-line feedback */ +void +ustatusline(void) +{ + NhRegion *reg; + char info[BUFSZ]; + size_t ln; + + info[0] = '\0'; + if (Sick) { + Strcat(info, ", dying from"); + if (u.usick_type & SICK_VOMITABLE) + Strcat(info, " food poisoning"); + if (u.usick_type & SICK_NONVOMITABLE) { + if (u.usick_type & SICK_VOMITABLE) + Strcat(info, " and"); + Strcat(info, " illness"); + } + } + if (Stoned) + Strcat(info, ", solidifying"); + if (Slimed) + Strcat(info, ", becoming slimy"); + if (Strangled) + Strcat(info, ", being strangled"); + if (Vomiting) + Strcat(info, ", nauseated"); /* !"nauseous" */ + if (Confusion) + Strcat(info, ", confused"); + if (Blind) { + Strcat(info, ", blind"); + if (u.ucreamed) { + if ((long) u.ucreamed < BlindedTimeout || Blindfolded + || !haseyes(gy.youmonst.data)) + Strcat(info, ", cover"); + Strcat(info, "ed by sticky goop"); + } /* note: "goop" == "glop"; variation is intentional */ + } + if (Stunned) + Strcat(info, ", stunned"); + if (Wounded_legs && !u.usteed) { + /* EWounded_legs is used to track left/right/both rather than some + form of extrinsic impairment; HWounded_legs is used for timeout; + both apply to steed instead of hero when mounted */ + long legs = (EWounded_legs & BOTH_SIDES); + const char *what = body_part(LEG); + + if (legs == BOTH_SIDES) + what = makeplural(what); + /* when it's just one leg, ^X reports which, left or right; + ustatusline() doesn't, in order to keep the output a bit shorter */ + Sprintf(eos(info), ", injured %s", what); + } + if (Glib) + Sprintf(eos(info), ", slippery %s", fingers_or_gloves(TRUE)); + if (u.utrap) + Strcat(info, ", trapped"); + if (Fast) + Strcat(info, Very_fast ? ", very fast" : ", fast"); + if (u.uundetected) + Strcat(info, ", concealed"); + else if (U_AP_TYPE != M_AP_NOTHING) + Strcat(info, ", disguised"); + if (Invis) + Strcat(info, ", invisible"); + if (u.ustuck) { + if (u.uswallow) + Strcat(info, digests(u.ustuck->data) ? ", being digested by " + : ", engulfed by "); + else if (!sticks(gy.youmonst.data)) + Strcat(info, ", held by "); + else + Strcat(info, ", holding "); + /* FIXME? a_monnam() uses x_monnam() which has a special case that + forces "the" instead of "a" when formatting u.ustuck while hero + is swallowed; we don't really want that here but it isn't worth + fiddling with just for self-probing while engulfed */ + Strcat(info, a_monnam(u.ustuck)); + } + if (!u.uswallow + && (reg = visible_region_at(u.ux, u.uy)) != 0 + && (ln = strlen(info)) < sizeof info) + Snprintf(eos(info), sizeof info - ln, ", in a cloud of %s", + reg_damg(reg) ? "poison gas" : "vapor"); + + pline("Status of %s (%s): Level %d HP %d(%d) AC %d%s.", svp.plname, + piousness(FALSE, align_str(u.ualign.type)), + Upolyd ? mons[u.umonnum].mlevel : u.ulevel, Upolyd ? u.mh : u.uhp, + Upolyd ? u.mhmax : u.uhpmax, u.uac, info); +} + +/* for 'onefile' processing where end of this file isn't necessarily the + end of the source code seen by the compiler */ +#undef enl_msg +#undef you_are +#undef you_have +#undef you_can +#undef you_have_been +#undef you_have_never +#undef you_have_X +#undef LL_majors +#undef majorevent +#undef spoilerevent +#undef UniqCritterIndx +#undef done_stopprint + +/*insight.c*/ diff --git a/src/invent.c b/src/invent.c index 151c5317c..446859540 100644 --- a/src/invent.c +++ b/src/invent.c @@ -1,45 +1,49 @@ -/* NetHack 3.6 invent.c $NHDT-Date: 1674864733 2023/01/28 00:12:13 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.268 $ */ +/* NetHack 5.0 invent.c $NHDT-Date: 1762680996 2025/11/09 01:36:36 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.543 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Derek S. Ray, 2015. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -#ifndef C /* same as cmd.c */ -#define C(c) (0x1f & (c)) -#endif - -#define NOINVSYM '#' -#define CONTAINED_SYM '>' /* designator for inside a container */ -#define HANDS_SYM '-' - -STATIC_DCL void FDECL(loot_classify, (Loot *, struct obj *)); -STATIC_DCL char *FDECL(loot_xname, (struct obj *)); -STATIC_DCL int FDECL(CFDECLSPEC sortloot_cmp, (const genericptr, - const genericptr)); -STATIC_DCL void NDECL(reorder_invent); -STATIC_DCL void FDECL(noarmor, (BOOLEAN_P)); -STATIC_DCL void FDECL(invdisp_nothing, (const char *, const char *)); -STATIC_DCL boolean FDECL(worn_wield_only, (struct obj *)); -STATIC_DCL boolean FDECL(only_here, (struct obj *)); -STATIC_DCL void FDECL(compactify, (char *)); -STATIC_DCL boolean FDECL(taking_off, (const char *)); -STATIC_DCL boolean FDECL(putting_on, (const char *)); -STATIC_PTR int FDECL(ckvalidcat, (struct obj *)); -STATIC_PTR int FDECL(ckunpaid, (struct obj *)); -STATIC_PTR char *FDECL(safeq_xprname, (struct obj *)); -STATIC_PTR char *FDECL(safeq_shortxprname, (struct obj *)); -STATIC_DCL char FDECL(display_pickinv, (const char *, const char *, - const char *, BOOLEAN_P, long *)); -STATIC_DCL char FDECL(display_used_invlets, (CHAR_P)); -STATIC_DCL boolean FDECL(this_type_only, (struct obj *)); -STATIC_DCL void NDECL(dounpaid); -STATIC_DCL struct obj *FDECL(find_unpaid, (struct obj *, struct obj **)); -STATIC_DCL void FDECL(menu_identify, (int)); -STATIC_DCL boolean FDECL(tool_in_use, (struct obj *)); -STATIC_DCL char FDECL(obj_to_let, (struct obj *)); - -static int lastinvnr = 51; /* 0 ... 51 (never saved&restored) */ +staticfn void inuse_classify(Loot *, struct obj *); +staticfn char *loot_xname(struct obj *); +staticfn int invletter_value(char); +staticfn int QSORTCALLBACK sortloot_cmp(const genericptr, const genericptr); +staticfn void reorder_invent(void); +staticfn struct obj *addinv_core0(struct obj *, struct obj *, + boolean) NONNULLARG1; +staticfn void noarmor(boolean); +staticfn void invdisp_nothing(const char *, const char *); +staticfn boolean worn_wield_only(struct obj *); +staticfn char *cinv_doname(struct obj *); +staticfn char *cinv_ansimpleoname(struct obj *); +staticfn boolean only_here(struct obj *); +staticfn void compactify(char *); +staticfn boolean taking_off(const char *); +staticfn void mime_action(const char *); +staticfn char *getobj_hands_txt(const char *, char *); +staticfn int ckvalidcat(struct obj *); +staticfn int ckunpaid(struct obj *); +staticfn char *safeq_xprname(struct obj *); +staticfn char *safeq_shortxprname(struct obj *); +staticfn char display_pickinv(const char *, const char *, const char *, + boolean, boolean, long *); +staticfn char display_used_invlets(char); +staticfn boolean this_type_only(struct obj *); +staticfn void dounpaid(int, int, int); +staticfn struct obj *find_unpaid(struct obj *, struct obj **); +staticfn void menu_identify(int); +staticfn boolean tool_being_used(struct obj *); +staticfn int adjust_ok(struct obj *); +staticfn int adjust_gold_ok(struct obj *); +staticfn int doorganize_core(struct obj *); +staticfn char obj_to_let(struct obj *); +staticfn int dispinv_with_action(char *, boolean, const char *); + +/* enum and structs are defined in wintype.h */ +static win_request_info wri_info; +static int perminv_flags = InvOptNone; +static boolean in_perm_invent_toggled; /* wizards can wish for venom, which will become an invisible inventory * item without this. putting it in inv_order would mean venom would @@ -50,13 +54,99 @@ static int lastinvnr = 51; /* 0 ... 51 (never saved&restored) */ * confused: 'WIZARD' used to be a compile-time conditional so this was * guarded by #ifdef WIZARD/.../#endif.] */ -static char venom_inv[] = { VENOM_CLASS, 0 }; /* (constant) */ +static const char venom_inv[] = { VENOM_CLASS, 0 }; /* (constant) */ + +/* menu heading lines used instead of object classes when sorting by in-use; + pointers aren't const because dispinv_with_action() might temporarily + change "Accessories" to "Rings" or "Amulet", then back again */ +static const char *inuse_headers[] = { /* [4] shown first, [1] last */ + "", "Miscellaneous", "Worn Armor", + "Wielded/Readied Weapons", "Accessories", +}; + +/* sortloot() classification for in-use sort; + called at most once [per sort] for each object */ +staticfn void +inuse_classify(Loot *sort_item, struct obj *obj) +{ + long w_mask = (obj->owornmask & (W_ACCESSORY | W_WEAPONS | W_ARMOR)); + int rating = 0, altclass = 0; + +#define USE_RATING(test) \ + do { \ + /* 'rating' advances for each USE_RATING() call */ \ + ++rating; \ + if ((test) != 0) \ + goto assign_rating; \ + } while (0) + + /* + * In order of importance, least to most, somewhat arbitrarily. + * + * For instance, all accessories are grouped together even + * though they're usually less important than other stuff, so + * that they appear earlier within displayed list of used items. + * Amulet is rated as most important primarily because the + * default 'packorder' puts amulets first (possibly because one + * might be The Amulet). Non-wielded alternate weapon and + * quiver are grouped with primary weapon. Weapons are rated + * above armor because of default 'packorder'. + * + * These ratings don't match either subclasses or 'packorder'. + * + * USE_RATING() sets up 'rating' then jumps to 'assign_rating' + * if 'obj' warrants that. + */ + /* "Miscellaneous" */ + ++altclass; /* 1 */ + /* lamp and leash might be used doubly, as a tool and also wielded + or readied-in-quiver; these tests for used-as-tool only pass + when owornmask is 0 so that used-as-weapon takes precedence */ + USE_RATING(!w_mask && obj->otyp == LEASH && obj->leashmon); + USE_RATING(!w_mask && obj->oclass == TOOL_CLASS && obj->lamplit); + /* "Armor" */ + ++altclass; /* 2 */ + USE_RATING(w_mask & WORN_SHIRT); + USE_RATING(w_mask & WORN_BOOTS); + USE_RATING(w_mask & WORN_GLOVES); + USE_RATING(w_mask & WORN_HELMET); + USE_RATING(w_mask & WORN_SHIELD); + USE_RATING(w_mask & WORN_CLOAK); + USE_RATING(w_mask & WORN_ARMOR); + /* "Weapons" */ + ++altclass; /* 3 */ + /* could get more complicated: if uswapwep is just alternate weapon + rather than wielded secondary, swap order with quiver (unless + quiver is ammo for uswapwep without also being ammo for uwep) */ + USE_RATING(w_mask & W_QUIVER); + USE_RATING(w_mask & W_SWAPWEP); + USE_RATING(w_mask & W_WEP); + /* "Accessories" */ + ++altclass; /* 4 */ + USE_RATING(w_mask & WORN_BLINDF); + USE_RATING(w_mask & (ULEFTY ? RIGHT_RING : LEFT_RING)); /* off hand */ + USE_RATING(w_mask & (URIGHTY ? RIGHT_RING : LEFT_RING)); /* main hand */ + USE_RATING(w_mask & WORN_AMUL); + + /* if we get here, the USE_RATING() checks failed to find a match */ + rating = 0; + altclass = -1; /* 'orderclass' must end up non-zero */ + + assign_rating: + sort_item->inuse = rating; + sort_item->orderclass = altclass; /* used for alternate headings */ + + /* not applicable for in-use */ + sort_item->subclass = 0; + sort_item->disco = 0; + +#undef USE_RATING +} -/* sortloot() classification; called at most once [per sort] for each object */ -STATIC_OVL void -loot_classify(sort_item, obj) -Loot *sort_item; -struct obj *obj; +/* sortloot() classification; called at most once [per sort] for each object; + also called by '\' command if discoveries use sortloot order */ +void +loot_classify(Loot *sort_item, struct obj *obj) { /* we may eventually make this a settable option to always use with sortloot instead of only when the 'sortpack' option isn't @@ -69,7 +159,7 @@ struct obj *obj; }; static char armcat[8]; const char *classorder; - char *p; + const char *p; int k, otyp = obj->otyp, oclass = obj->oclass; boolean seen, discovered = objects[otyp].oc_name_known ? TRUE : FALSE; @@ -78,16 +168,16 @@ struct obj *obj; * will put lower valued ones before higher valued ones. */ if (!Blind) - obj->dknown = 1; /* xname(obj) does this; we want it sooner */ + observe_object(obj); /* xname(obj) does this; we want it sooner */ seen = obj->dknown ? TRUE : FALSE, /* class order */ classorder = flags.sortpack ? flags.inv_order : def_srt_order; - p = index(classorder, oclass); + p = strchr(classorder, oclass); if (p) k = 1 + (int) (p - classorder); else k = 1 + (int) strlen(classorder) + (oclass != VENOM_CLASS); - sort_item->orderclass = (xchar) k; + sort_item->orderclass = k; /* subclass designation; only a few classes have subclasses and the non-armor ones we use are fairly arbitrary */ switch (oclass) { @@ -203,19 +293,20 @@ struct obj *obj; k = 1; /* any non-zero would do */ break; } - sort_item->subclass = (xchar) k; + sort_item->subclass = k; /* discovery status */ k = !seen ? 1 /* unseen */ : (discovered || !OBJ_DESCR(objects[otyp])) ? 4 : (objects[otyp].oc_uname) ? 3 /* named (partially discovered) */ : 2; /* undiscovered */ - sort_item->disco = (xchar) k; + sort_item->disco = k; + /* not applicable */ + sort_item->inuse = 0; } /* sortloot() formatting routine; for alphabetizing, not shown to user */ -STATIC_OVL char * -loot_xname(obj) -struct obj *obj; +staticfn char * +loot_xname(struct obj *obj) { struct obj saveo; boolean save_debug; @@ -243,11 +334,10 @@ struct obj *obj; three group together */ if (obj->otyp == TOWEL) obj->spe = 0; - /* group " glob of " by rather than by */ + /* group globs by monster type rather than by size: force all to + have the same size adjective hence same "small glob of " prefix */ if (obj->globby) - obj->owt = 200; /* 200: weight of combined glob from ten creatures - (five or fewer is "small", more than fifteen is - "large", in between has no prefix) */ + obj->owt = 20; /* weight of a fresh glob (one pudding's worth) */ /* suppress user-assigned name */ if (save_oname && !obj->oartifact) ONAME(obj) = 0; @@ -296,24 +386,49 @@ struct obj *obj; return res; } -/* set by sortloot() for use by sortloot_cmp(); reset by sortloot when done */ -static unsigned sortlootmode = 0; +/* '$'==1, 'a'-'z'==2..27, 'A'-'Z'==28..53, '#'==54, catchall 55 */ +staticfn int +invletter_value(char c) +{ + return ('a' <= c && c <= 'z') ? (c - 'a' + 2) + : ('A' <= c && c <= 'Z') ? (c - 'A' + 2 + 26) + : (c == '$') ? 1 + : (c == '#') ? 1 + invlet_basic + 1 + : 1 + invlet_basic + 1 + 1; /* none of the above + * (shouldn't happen) */ +} /* qsort comparison routine for sortloot() */ -STATIC_OVL int CFDECLSPEC -sortloot_cmp(vptr1, vptr2) -const genericptr vptr1; -const genericptr vptr2; +staticfn int QSORTCALLBACK +sortloot_cmp(const genericptr vptr1, const genericptr vptr2) { struct sortloot_item *sli1 = (struct sortloot_item *) vptr1, *sli2 = (struct sortloot_item *) vptr2; struct obj *obj1 = sli1->obj, *obj2 = sli2->obj; - char *nam1, *nam2; - int val1, val2, c, namcmp; + char *nam1, *nam2, *tmpstr; + int val1, val2, namcmp; + + /* in-use takes precedence over all others */ + if ((gs.sortlootmode & SORTLOOT_INUSE) != 0) { + /* Classify each object at most once no matter how many + comparisons it is involved in. */ + if (!sli1->orderclass) + inuse_classify(sli1, obj1); + if (!sli2->orderclass) + inuse_classify(sli2, obj2); + + val1 = sli1->inuse; + val2 = sli2->inuse; + if (val1 != val2) + return val2 - val1; /* bigger value comes before smaller */ + /* neither item in use (or both are lit lamps/candles or both are + attached leashes; items using owornmask don't produce ties) */ + goto tiebreak; + } /* order by object class unless we're doing by-invlet without sortpack */ - if ((sortlootmode & (SORTLOOT_PACK | SORTLOOT_INVLET)) + if ((gs.sortlootmode & (SORTLOOT_PACK | SORTLOOT_INVLET)) != SORTLOOT_INVLET) { /* Classify each object at most once no matter how many comparisons it is involved in. */ @@ -326,10 +441,10 @@ const genericptr vptr2; val1 = sli1->orderclass; val2 = sli2->orderclass; if (val1 != val2) - return (int) (val1 - val2); + return val1 - val2; /* skip sub-classes when ordering by sortpack+invlet */ - if ((sortlootmode & SORTLOOT_INVLET) == 0) { + if ((gs.sortlootmode & SORTLOOT_INVLET) == 0) { /* Class matches; sort by subclass. */ val1 = sli1->subclass; val2 = sli2->subclass; @@ -354,24 +469,14 @@ const genericptr vptr2; } /* order by assigned inventory letter */ - if ((sortlootmode & SORTLOOT_INVLET) != 0) { - c = obj1->invlet; - val1 = ('a' <= c && c <= 'z') ? (c - 'a' + 2) - : ('A' <= c && c <= 'Z') ? (c - 'A' + 2 + 26) - : (c == '$') ? 1 - : (c == '#') ? 1 + 52 + 1 - : 1 + 52 + 1 + 1; /* none of the above */ - c = obj2->invlet; - val2 = ('a' <= c && c <= 'z') ? (c - 'a' + 2) - : ('A' <= c && c <= 'Z') ? (c - 'A' + 2 + 26) - : (c == '$') ? 1 - : (c == '#') ? 1 + 52 + 1 - : 1 + 52 + 1 + 1; /* none of the above */ + if ((gs.sortlootmode & SORTLOOT_INVLET) != 0) { + val1 = invletter_value(obj1->invlet); + val2 = invletter_value(obj2->invlet); if (val1 != val2) return val1 - val2; } - if ((sortlootmode & SORTLOOT_LOOT) == 0) + if ((gs.sortlootmode & SORTLOOT_LOOT) == 0) goto tiebreak; /* @@ -381,11 +486,17 @@ const genericptr vptr2; * comparisons it gets subjected to. */ nam1 = sli1->str; - if (!nam1) - nam1 = sli1->str = dupstr(loot_xname(obj1)); + if (!nam1) { + tmpstr = loot_xname(obj1); + nam1 = sli1->str = dupstr(tmpstr); + maybereleaseobuf(tmpstr); + } nam2 = sli2->str; - if (!nam2) - nam2 = sli2->str = dupstr(loot_xname(obj2)); + if (!nam2) { + tmpstr = loot_xname(obj2); + nam2 = sli2->str = dupstr(tmpstr); + maybereleaseobuf(tmpstr); + } if ((namcmp = strcmpi(nam1, nam2)) != 0) return namcmp; @@ -479,12 +590,13 @@ const genericptr vptr2; * instead of simple 'struct obj *' entries. */ Loot * -sortloot(olist, mode, by_nexthere, filterfunc) -struct obj **olist; /* previous version might have changed *olist, we don't */ -unsigned mode; /* flags for sortloot_cmp() */ -boolean by_nexthere; /* T: traverse via obj->nexthere, F: via obj->nobj */ -boolean FDECL((*filterfunc), (OBJ_P)); +sortloot( + struct obj **olist, /* old version might have changed *olist, we don't */ + unsigned mode, /* flags for sortloot_cmp() */ + boolean by_nexthere, /* T: traverse via obj->nexthere, F: via obj->nobj */ + boolean (*filterfunc)(struct obj *)) /* optional filter */ { + static Loot zerosli; Loot *sliarray; struct obj *o; unsigned n, i; @@ -507,23 +619,21 @@ boolean FDECL((*filterfunc), (OBJ_P)); && (!augment_filter || o->otyp != CORPSE || !touch_petrifies(&mons[o->corpsenm]))) continue; + sliarray[i] = zerosli; sliarray[i].obj = o, sliarray[i].indx = (int) i; - sliarray[i].str = (char *) 0; - sliarray[i].orderclass = sliarray[i].subclass = sliarray[i].disco = 0; ++i; } n = i; /* add a terminator so that we don't have to pass 'n' back to caller */ - sliarray[n].obj = (struct obj *) 0, sliarray[n].indx = -1; - sliarray[n].str = (char *) 0; - sliarray[n].orderclass = sliarray[n].subclass = sliarray[n].disco = 0; + sliarray[n] = zerosli; + sliarray[n].indx = -1; /* do the sort; if no sorting is requested, we'll just return a sortloot_item array reflecting the current ordering */ if (mode && n > 1) { - sortlootmode = mode; /* extra input for sortloot_cmp() */ + gs.sortlootmode = mode; /* extra input for sortloot_cmp() */ qsort((genericptr_t) sliarray, n, sizeof *sliarray, sortloot_cmp); - sortlootmode = 0; /* reset static mode flags */ + gs.sortlootmode = 0; /* reset static mode flags */ /* if sortloot_cmp formatted any objects, discard their strings now */ for (i = 0; i < n; ++i) if (sliarray[i].str) @@ -534,26 +644,25 @@ boolean FDECL((*filterfunc), (OBJ_P)); /* sortloot() callers should use this to free up memory it allocates */ void -unsortloot(loot_array_p) -Loot **loot_array_p; +unsortloot(Loot **loot_array_p) { if (*loot_array_p) free((genericptr_t) *loot_array_p), *loot_array_p = (Loot *) 0; } -#if 0 /* 3.6.0 'revamp' */ +#if 0 /* 3.6.0 'revamp' -- simpler than current, but ultimately too simple */ void -sortloot(olist, mode, by_nexthere) -struct obj **olist; -unsigned mode; /* flags for sortloot_cmp() */ -boolean by_nexthere; /* T: traverse via obj->nexthere, F: via obj->nobj */ +sortloot( + struct obj **olist, + unsigned mode, /* flags for sortloot_cmp() */ + boolean by_nexthere) /* T: traverse via obj->nexthere, F: via obj->nobj */ { struct sortloot_item *sliarray, osli, nsli; struct obj *o, **nxt_p; unsigned n, i; boolean already_sorted = TRUE; - sortlootmode = mode; /* extra input for sortloot_cmp() */ + gs.sortlootmode = mode; /* extra input for sortloot_cmp() */ for (n = osli.indx = 0, osli.obj = *olist; (o = osli.obj) != 0; osli = nsli) { nsli.obj = by_nexthere ? o->nexthere : o->nobj; @@ -577,17 +686,16 @@ boolean by_nexthere; /* T: traverse via obj->nexthere, F: via obj->nobj */ *olist = sliarray[0].obj; free((genericptr_t) sliarray); } - sortlootmode = 0; + gs.sortlootmode = 0; } #endif /*0*/ void -assigninvlet(otmp) -register struct obj *otmp; +assigninvlet(struct obj *otmp) { - boolean inuse[52]; - register int i; - register struct obj *obj; + boolean inuse[invlet_basic]; + int i; + struct obj *obj; /* there should be at most one of these in inventory... */ if (otmp->oclass == COIN_CLASS) { @@ -595,9 +703,9 @@ register struct obj *otmp; return; } - for (i = 0; i < 52; i++) + for (i = 0; i < invlet_basic; i++) inuse[i] = FALSE; - for (obj = invent; obj; obj = obj->nobj) + for (obj = gi.invent; obj; obj = obj->nobj) if (obj != otmp) { i = obj->invlet; if ('a' <= i && i <= 'z') @@ -610,8 +718,8 @@ register struct obj *otmp; if ((i = otmp->invlet) && (('a' <= i && i <= 'z') || ('A' <= i && i <= 'Z'))) return; - for (i = lastinvnr + 1; i != lastinvnr; i++) { - if (i == 52) { + for (i = gl.lastinvnr + 1; i != gl.lastinvnr; i++) { + if (i == invlet_basic) { i = -1; continue; } @@ -620,15 +728,15 @@ register struct obj *otmp; } otmp->invlet = (inuse[i] ? NOINVSYM : (i < 26) ? ('a' + i) : ('A' + i - 26)); - lastinvnr = i; + gl.lastinvnr = i; } /* note: assumes ASCII; toggling a bit puts lowercase in front of uppercase */ #define inv_rank(o) ((o)->invlet ^ 040) /* sort the inventory; used by addinv() and doorganize() */ -STATIC_OVL void -reorder_invent() +staticfn void +reorder_invent(void) { struct obj *otmp, *prev, *next; boolean need_more_sorting; @@ -639,14 +747,14 @@ reorder_invent() * isn't nearly as inefficient as it may first appear. */ need_more_sorting = FALSE; - for (otmp = invent, prev = 0; otmp;) { + for (otmp = gi.invent, prev = 0; otmp; ) { next = otmp->nobj; if (next && inv_rank(next) < inv_rank(otmp)) { need_more_sorting = TRUE; if (prev) prev->nobj = next; else - invent = next; + gi.invent = next; otmp->nobj = next->nobj; next->nobj = otmp; prev = next; @@ -664,19 +772,20 @@ reorder_invent() one of them; used in pickup.c when all 52 inventory slots are in use, to figure out whether another object could still be picked up */ struct obj * -merge_choice(objlist, obj) -struct obj *objlist, *obj; +merge_choice(struct obj *objlist, struct obj *obj) { struct monst *shkp; - int save_nocharge; + unsigned save_nocharge; + if (!objlist) /* might be checking 'obj' against empty inventory */ + return (struct obj *) 0; if (obj->otyp == SCR_SCARE_MONSTER) /* punt on these */ return (struct obj *) 0; /* if this is an item on the shop floor, the attributes it will have when carried are different from what they are now; prevent that from eliciting an incorrect result from mergable() */ save_nocharge = obj->no_charge; - if (objlist == invent && obj->where == OBJ_FLOOR + if (objlist == gi.invent && obj->where == OBJ_FLOOR && (shkp = shop_keeper(inside_shop(obj->ox, obj->oy))) != 0) { if (obj->no_charge) obj->no_charge = 0; @@ -690,21 +799,22 @@ struct obj *objlist, *obj; else if (inhishop(shkp)) return (struct obj *) 0; } - while (objlist) { + do { + /*assert(objlist != NULL);*/ if (mergable(objlist, obj)) break; objlist = objlist->nobj; - } + } while (objlist); obj->no_charge = save_nocharge; return objlist; } /* merge obj with otmp and delete obj if types agree */ int -merged(potmp, pobj) -struct obj **potmp, **pobj; +merged(struct obj **potmp, struct obj **pobj) { - register struct obj *otmp = *potmp, *obj = *pobj; + struct obj *otmp = *potmp, *obj = *pobj; + boolean discovered = FALSE; if (mergable(otmp, obj)) { /* Approximate age: we do it this way because if we were to @@ -729,17 +839,41 @@ struct obj **potmp, **pobj; otmp->owt = weight(otmp), otmp->bknown = 0; /* and puddings!!!1!!one! */ else if (!Is_pudding(otmp)) - otmp->owt += obj->owt; + otmp->owt = weight(otmp); if (!has_oname(otmp) && has_oname(obj)) - otmp = *potmp = oname(otmp, ONAME(obj)); + otmp = *potmp = oname(otmp, ONAME(obj), ONAME_SKIP_INVUPD); obj_extract_self(obj); + if (obj->pickup_prev && otmp->where == OBJ_INVENT) + otmp->pickup_prev = 1; + /* really should merge the timeouts */ if (obj->lamplit) obj_merge_light_sources(obj, otmp); if (obj->timed) obj_stop_timers(obj); /* follows lights */ + /* objects can be identified by comparing them (unless Blind, + but that is handled in mergable()); the object becomes + identified in a particular dimension if either object was + previously identified in that dimension, and if the + identification states don't match, one of them must have + previously been identified */ + if (obj->known != otmp->known) { + otmp->known = 1; + discovered = TRUE; + } + if (obj->rknown != otmp->rknown) { + otmp->rknown = 1; + if (otmp->oerodeproof) + discovered = TRUE; + } + if (obj->bknown != otmp->bknown) { + otmp->bknown = 1; + if (!Role_if(PM_CLERIC)) + discovered = TRUE; + } + /* fixup for `#adjust' merging wielded darts, daggers, &c */ if (obj->owornmask && carried(otmp)) { long wmask = otmp->owornmask | obj->owornmask; @@ -752,13 +886,13 @@ struct obj **potmp, **pobj; (Prior to 3.3.0, it was not possible for the two stacks to be worn in different slots and `obj' didn't need to be unworn when merging.) */ - if (wmask & W_WEP) + if ((wmask & W_WEP) != 0L) { wmask = W_WEP; - else if (wmask & W_SWAPWEP) + } else if ((wmask & W_SWAPWEP) != 0L) { wmask = W_SWAPWEP; - else if (wmask & W_QUIVER) + } else if ((wmask & W_QUIVER) != 0L) { wmask = W_QUIVER; - else { + } else { impossible("merging strangely worn items (%lx)", wmask); wmask = otmp->owornmask; } @@ -778,6 +912,17 @@ struct obj **potmp, **pobj; #endif /*0*/ } + /* mergable() no longer requires 'bypass' to match; if 'obj' has + the bypass bit set, force the combined stack to have that too; + primarily in case this merge is occurring because stackobj() + is operating on an object just dropped by a monster that was + zapped with polymorph, we want bypass set in order to inhibit + the same zap from affecting the new combined stack when it hits + objects at the monster's spot (but also in case we're called by + code that's using obj->bypass to track 'already processed') */ + if (obj->bypass) + otmp->bypass = 1; + /* handle puddings a bit differently; absorption will free the other object automatically so we can just return out from here */ if (obj->globby) { @@ -786,6 +931,16 @@ struct obj **potmp, **pobj; return 1; } + /* Print a message if item comparison discovers more + information about the items (with the exception of thrown + items, where this would be too spammy as such items get + unidentified by monsters very frequently). */ + if (discovered && otmp->where == OBJ_INVENT + && obj->how_lost != LOST_THROWN + && otmp->how_lost != LOST_THROWN) { + pline("You learn more about your items by comparing them."); + } + obfree(obj, otmp); /* free(obj), bill->otmp */ return 1; } @@ -800,35 +955,32 @@ struct obj **potmp, **pobj; * This is called when adding objects to the hero's inventory normally (via * addinv) or when an object in the hero's inventory has been polymorphed * in-place. - * - * It may be valid to merge this code with with addinv_core2(). */ void -addinv_core1(obj) -struct obj *obj; +addinv_core1(struct obj *obj) { if (obj->oclass == COIN_CLASS) { - context.botl = 1; + disp.botl = TRUE; } else if (obj->otyp == AMULET_OF_YENDOR) { if (u.uhave.amulet) impossible("already have amulet?"); u.uhave.amulet = 1; - u.uachieve.amulet = 1; + record_achievement(ACH_AMUL); } else if (obj->otyp == CANDELABRUM_OF_INVOCATION) { if (u.uhave.menorah) impossible("already have candelabrum?"); u.uhave.menorah = 1; - u.uachieve.menorah = 1; + record_achievement(ACH_CNDL); } else if (obj->otyp == BELL_OF_OPENING) { if (u.uhave.bell) impossible("already have silver bell?"); u.uhave.bell = 1; - u.uachieve.bell = 1; + record_achievement(ACH_BELL); } else if (obj->otyp == SPE_BOOK_OF_THE_DEAD) { if (u.uhave.book) impossible("already have the book?"); u.uhave.book = 1; - u.uachieve.book = 1; + record_achievement(ACH_BOOK); } else if (obj->oartifact) { if (is_quest_artifact(obj)) { if (u.uhave.questart) @@ -839,52 +991,71 @@ struct obj *obj; set_artifact_intrinsic(obj, 1, W_ART); } - /* "special achievements" aren't discoverable during play, they - end up being recorded in XLOGFILE at end of game, nowhere else; - record_achieve_special overloads corpsenm which is ordinarily - initialized to NON_PM (-1) rather than to 0; any special prize - must never be a corpse, egg, tin, figurine, or statue because - their use of obj->corpsenm for monster type would conflict, - nor be a leash (corpsenm overloaded for m_id of leashed - monster) or a novel (corpsenm overloaded for novel index) */ + /* "special achievements"; revealed in end of game disclosure and + dumplog, originally just recorded in XLOGFILE */ if (is_mines_prize(obj)) { - u.uachieve.mines_luckstone = 1; - obj->record_achieve_special = NON_PM; - obj->nomerge = 0; + record_achievement(ACH_MINE_PRIZE); + svc.context.achieveo.mines_prize_oid = 0; /* done w/ luckstone o_id */ + obj->nomerge = 0; /* was set in create_object(sp_lev.c) */ } else if (is_soko_prize(obj)) { - u.uachieve.finish_sokoban = 1; - obj->record_achieve_special = NON_PM; - obj->nomerge = 0; + record_achievement(ACH_SOKO_PRIZE); + svc.context.achieveo.soko_prize_oid = 0; /* done w/ bag/amulet o_id */ + obj->nomerge = 0; /* (got set in sp_lev.c) */ } } /* - * Adjust hero intrinsics as if this object was being added to the hero's - * inventory. Called _after_ the object has been added to the hero's - * inventory. + * Adjust hero intrinsics (and perform other side effects) as if this + * object was being added to the hero's inventory. Called _after_ the + * object has been added to the hero's inventory. + * + * This can be used either for updating intrinsics, or to allow the hero to + * react to objects that are now in inventory. * * This is called when adding objects to the hero's inventory normally (via - * addinv) or when an object in the hero's inventory has been polymorphed - * in-place. + * addinv), when an object in the hero's inventory has been polymorphed + * in-place, or when the hero re-examines objects that they picked up while + * blind. + * + * This may occasionally be called on an item that was already in inventory, + * so it should be written to work even if called multiple times in a row + * (e.g. do not assume that the object was not in inventory already). */ void -addinv_core2(obj) -struct obj *obj; +addinv_core2(struct obj *obj) { if (confers_luck(obj)) { /* new luckstone must be in inventory by this point - * for correct calculation */ + for correct calculation */ set_moreluck(); } + + /* Archeologists can decipher the writing on a scroll label to work out + what they are (exception: unlabeled scrolls don't have a label to + decipher) */ + if (Role_if(PM_ARCHEOLOGIST) && obj->oclass == SCROLL_CLASS && + obj->otyp != SCR_BLANK_PAPER && !Blind && + !objects[obj->otyp].oc_name_known) { + observe_object(obj); + pline("You decipher the label on %s.", yname(obj)); + makeknown(obj->otyp); + + /* conduct: this is avoidable via not picking up / wishing for + scrolls */ + if (!u.uconduct.literate++) + livelog_printf(LL_CONDUCT, + "became literate by deciphering a scroll label"); + } } /* * Add obj to the hero's inventory. Make sure the object is "free". * Adjust hero attributes as necessary. */ -struct obj * -addinv(obj) -struct obj *obj; +staticfn struct obj * +addinv_core0( + struct obj *obj, struct obj *other_obj, + boolean update_perm_invent) { struct obj *otmp, *prev; int saved_otyp = (int) obj->otyp; /* for panic */ @@ -892,15 +1063,37 @@ struct obj *obj; if (obj->where != OBJ_FREE) panic("addinv: obj not free"); + if (obj->how_lost == LOST_EXPLODING) + return (struct obj *) NULL; + /* normally addtobill() clears no_charge when items in a shop are picked up, but won't do so if the shop has become untended */ obj->no_charge = 0; /* should not be set in hero's invent */ if (Has_contents(obj)) picked_container(obj); /* clear no_charge */ - obj_was_thrown = obj->was_thrown; - obj->was_thrown = 0; /* not meaningful for invent */ + obj_was_thrown = (obj->how_lost == LOST_THROWN); + obj->how_lost = LOST_NONE; + + if (gl.loot_reset_justpicked) { + gl.loot_reset_justpicked = FALSE; + reset_justpicked(gi.invent); + } - addinv_core1(obj); + addinv_core1(obj); /* handle most side effects of carrying obj */ + + /* for addinv_before(); if something has been removed and is now being + reinserted, try to put it in the same place instead of merging or + placing at end; for thrown-and-return weapon with !fixinv setting */ + if (other_obj) { + for (otmp = gi.invent; otmp; otmp = otmp->nobj) { + if (otmp->nobj == other_obj) { + obj->nobj = other_obj; + otmp->nobj = obj; + obj->where = OBJ_INVENT; + goto added; + } + } + } /* merge with quiver in preference to any other inventory slot in case quiver and wielded weapon are both eligible; adding @@ -912,7 +1105,7 @@ struct obj *obj; goto added; } /* merge if possible; find end of chain in the process */ - for (prev = 0, otmp = invent; otmp; prev = otmp, otmp = otmp->nobj) + for (prev = 0, otmp = gi.invent; otmp; prev = otmp, otmp = otmp->nobj) if (merged(&otmp, &obj)) { obj = otmp; if (!obj) @@ -922,8 +1115,8 @@ struct obj *obj; /* didn't merge, so insert into chain */ assigninvlet(obj); if (flags.invlet_constant || !prev) { - obj->nobj = invent; /* insert at beginning */ - invent = obj; + obj->nobj = gi.invent; /* insert at beginning */ + gi.invent = obj; if (flags.invlet_constant) reorder_invent(); } else { @@ -933,21 +1126,57 @@ struct obj *obj; obj->where = OBJ_INVENT; /* fill empty quiver if obj was thrown */ - if (flags.pickup_thrown && !uquiver && obj_was_thrown + if (obj_was_thrown && flags.pickup_thrown && !uquiver /* if Mjollnir is thrown and fails to return, we want to - auto-pick it when we move to its spot, but not into quiver; - aklyses behave like Mjollnir when thrown while wielded, but - we lack sufficient information here make them exceptions */ - && obj->oartifact != ART_MJOLLNIR + auto-pick it when we move to its spot, but not into quiver + because it needs to be wielded to be re-thrown; + aklys likewise because player using 'f' to throw it might + not notice that it isn't wielded until it fails to return + several times; we never auto-wield, just omit from quiver + so that player will be prompted for what to throw and + possibly realize that re-wielding is necessary */ + && obj->oartifact != ART_MJOLLNIR && obj->otyp != AKLYS && (throwing_weapon(obj) || is_ammo(obj))) setuqwep(obj); added: - addinv_core2(obj); + obj->pickup_prev = 1; + addinv_core2(obj); /* handle extrinsics conferred by carrying obj */ carry_obj_effects(obj); /* carrying affects the obj */ - update_inventory(); + if (update_perm_invent) + update_inventory(); return obj; } +/* add obj to the hero's inventory in the default fashion */ +struct obj * +addinv(struct obj *obj) +{ + return addinv_core0(obj, (struct obj *) 0, TRUE); +} + +/* add obj to the hero's inventory by inserting in front of a specific item; + used for throw-and-return in case '!fixinv' is in effect */ +struct obj * +addinv_before( + struct obj *obj, struct obj *other_obj) +{ + /* if 'other_obj' is present this will implicitly be 'nomerge' */ + return addinv_core0(obj, other_obj, TRUE); +} + +/* return value will always be 'obj' */ +struct obj * +addinv_nomerge(struct obj *obj) +{ + struct obj *result; + unsigned save_nomerge = obj->nomerge; + + obj->nomerge = 1; + result = addinv(obj); + obj->nomerge = save_nomerge; + return result; +} + /* * Some objects are affected by being carried. * Make those adjustments here. Called _after_ the object @@ -955,8 +1184,7 @@ struct obj *obj; * and after hero's intrinsics have been updated. */ void -carry_obj_effects(obj) -struct obj *obj; +carry_obj_effects(struct obj *obj) { /* Cursed figurines can spontaneously transform when carried. */ if (obj->otyp == FIGURINE) { @@ -967,6 +1195,8 @@ struct obj *obj; } } +DISABLE_WARNING_FORMAT_NONLITERAL + /* Add an item to the inventory unless we're fumbling or it refuses to be * held (via touch_artifact), and give a message. * If there aren't any free inventory slots, we'll drop it instead. @@ -975,14 +1205,16 @@ struct obj *obj; * touch_artifact will print its own messages if they are warranted. */ struct obj * -hold_another_object(obj, drop_fmt, drop_arg, hold_msg) -struct obj *obj; -const char *drop_fmt, *drop_arg, *hold_msg; +hold_another_object( + struct obj *obj, /* object to be held */ + const char *drop_fmt, /* format string for message if it can't be held */ + const char *drop_arg, /* argument to use when formatting message */ + const char *hold_msg) /* message to display if successfully held */ { char buf[BUFSZ]; if (!Blind) - obj->dknown = 1; /* maximize mergibility */ + observe_object(obj); /* maximize mergeability */ if (obj->oartifact) { /* place_object may change these */ boolean crysknife = (obj->otyp == CRYSKNIFE); @@ -992,12 +1224,12 @@ const char *drop_fmt, *drop_arg, *hold_msg; /* in case touching this object turns out to be fatal */ place_object(obj, u.ux, u.uy); - if (!touch_artifact(obj, &youmonst)) { + if (!touch_artifact(obj, &gy.youmonst)) { obj_extract_self(obj); /* remove it from the floor */ dropy(obj); /* now put it back again :-) */ return obj; } else if (wasUpolyd && !Upolyd) { - /* loose your grip if you revert your form */ + /* lose your grip if you revert your form */ if (drop_fmt) pline(drop_fmt, drop_arg); obj_extract_self(obj); @@ -1012,24 +1244,36 @@ const char *drop_fmt, *drop_arg, *hold_msg; } if (Fumbling) { obj->nomerge = 1; - obj = addinv(obj); /* dropping expects obj to be in invent */ + /* dropping expects obj to be in invent; since it's going to be + dropped, avoid perminv update when temporarily adding it */ + obj = addinv_core0(obj, (struct obj *) 0, FALSE); + goto drop_it; + } else if (obj->otyp == CORPSE + && !u_safe_from_fatal_corpse(obj, st_all) + && obj->wishedfor) { + obj->wishedfor = 0; + obj = addinv_core0(obj, (struct obj *) 0, FALSE); goto drop_it; } else { long oquan = obj->quan; int prev_encumbr = near_capacity(); /* before addinv() */ - /* encumbrance only matters if it would now become worse - than max( current_value, stressed ) */ - if (prev_encumbr < MOD_ENCUMBER) - prev_encumbr = MOD_ENCUMBER; + /* encumbrance limit is max( current_state, pickup_burden ); + this used to use hardcoded MOD_ENCUMBER (stressed) instead + of the 'pickup_burden' option (which defaults to stressed) */ + if (prev_encumbr < flags.pickup_burden) + prev_encumbr = flags.pickup_burden; /* addinv() may redraw the entire inventory, overwriting - drop_arg when it comes from something like doname() */ + drop_arg when it is kept in an 'obuf' from doname(); + [should no longer be necessary now that perm_invent update is + suppressed, but it's cheap to keep as a paranoid precaution] */ if (drop_arg) drop_arg = strcpy(buf, drop_arg); - obj = addinv(obj); - if (inv_cnt(FALSE) > 52 || ((obj->otyp != LOADSTONE || !obj->cursed) - && near_capacity() > prev_encumbr)) { + obj = addinv_core0(obj, (struct obj *) 0, FALSE); + if (inv_cnt(FALSE) > invlet_basic + || ((obj->otyp != LOADSTONE || !obj->cursed) + && near_capacity() > prev_encumbr)) { /* undo any merge which took place */ if (obj->quan > oquan) obj = splitobj(obj, oquan); @@ -1041,6 +1285,9 @@ const char *drop_fmt, *drop_arg, *hold_msg; setuqwep(obj); if (hold_msg || drop_fmt) prinv(hold_msg, obj, oquan); + /* obj made it into inventory and is staying there */ + update_inventory(); + encumber_msg(); } } return obj; @@ -1049,7 +1296,7 @@ const char *drop_fmt, *drop_arg, *hold_msg; if (drop_fmt) pline(drop_fmt, drop_arg); obj->nomerge = 0; - if (can_reach_floor(TRUE)) { + if (can_reach_floor(TRUE) || u.uswallow) { dropx(obj); } else { freeinv(obj); @@ -1058,19 +1305,20 @@ const char *drop_fmt, *drop_arg, *hold_msg; return (struct obj *) 0; /* might be gone */ } +RESTORE_WARNING_FORMAT_NONLITERAL + /* useup() all of an item regardless of its quantity */ void -useupall(obj) -struct obj *obj; +useupall(struct obj *obj) { setnotworn(obj); freeinv(obj); obfree(obj, (struct obj *) 0); /* deletes contents also */ } +/* an item in inventory is going away after being used */ void -useup(obj) -register struct obj *obj; +useup(struct obj *obj) { /* Note: This works correctly for containers because they (containers) don't merge. */ @@ -1086,9 +1334,9 @@ register struct obj *obj; /* use one charge from an item and possibly incur shop debt for it */ void -consume_obj_charge(obj, maybe_unpaid) -struct obj *obj; -boolean maybe_unpaid; /* false if caller handles shop billing */ +consume_obj_charge( + struct obj *obj, + boolean maybe_unpaid) /* false if caller handles shop billing */ { if (maybe_unpaid) check_unpaid(obj); @@ -1105,11 +1353,10 @@ boolean maybe_unpaid; /* false if caller handles shop billing */ * Should think of a better name... */ void -freeinv_core(obj) -struct obj *obj; +freeinv_core(struct obj *obj) { if (obj->oclass == COIN_CLASS) { - context.botl = 1; + disp.botl = TRUE; return; } else if (obj->otyp == AMULET_OF_YENDOR) { if (!u.uhave.amulet) @@ -1140,29 +1387,34 @@ struct obj *obj; curse(obj); } else if (confers_luck(obj)) { set_moreluck(); - context.botl = 1; + disp.botl = TRUE; } else if (obj->otyp == FIGURINE && obj->timed) { (void) stop_timer(FIG_TRANSFORM, obj_to_any(obj)); } + + if (obj == svc.context.tin.tin) { + svc.context.tin.tin = (struct obj *) 0; + svc.context.tin.o_id = 0; + } } /* remove an object from the hero's inventory */ void -freeinv(obj) -register struct obj *obj; +freeinv(struct obj *obj) { - extract_nobj(obj, &invent); + extract_nobj(obj, &gi.invent); + obj->pickup_prev = 0; freeinv_core(obj); update_inventory(); } +/* drawbridge is destroying all objects at */ void -delallobj(x, y) -int x, y; +delallobj(coordxy x, coordxy y) { struct obj *otmp, *otmp2; - for (otmp = level.objects[x][y]; otmp; otmp = otmp2) { + for (otmp = svl.level.objects[x][y]; otmp; otmp = otmp2) { if (otmp == uball) unpunish(); /* after unpunish(), or might get deallocated chain */ @@ -1173,40 +1425,49 @@ int x, y; } } -/* destroy object in fobj chain (if unpaid, it remains on the bill) */ +/* normal object deletion (if unpaid, it remains on the bill) */ +void +delobj(struct obj *obj) +{ + delobj_core(obj, FALSE); +} + +/* destroy object; caller has control over whether to destroy something + that ordinarily shouldn't be destroyed */ void -delobj(obj) -register struct obj *obj; +delobj_core( + struct obj *obj, + boolean force) /* 'force==TRUE' used when reviving Rider corpses */ { boolean update_map; - if (obj->otyp == AMULET_OF_YENDOR - || obj->otyp == CANDELABRUM_OF_INVOCATION - || obj->otyp == BELL_OF_OPENING - || obj->otyp == SPE_BOOK_OF_THE_DEAD) { + /* obj_resists(obj,0,0) protects the Amulet, the invocation tools, + and Rider corpses */ + if (!force && obj_resists(obj, 0, 0)) { /* player might be doing something stupid, but we * can't guarantee that. assume special artifacts * are indestructible via drawbridges, and exploding * chests, and golem creation, and ... */ + obj->in_use = 0; /* in case caller has set this to 1 */ return; } update_map = (obj->where == OBJ_FLOOR); obj_extract_self(obj); - if (update_map) + if (update_map) { /* floor object's coordinates are always up to date */ + maybe_unhide_at(obj->ox, obj->oy); newsym(obj->ox, obj->oy); + } obfree(obj, (struct obj *) 0); /* frees contents also */ } /* try to find a particular type of object at designated map location */ struct obj * -sobj_at(otyp, x, y) -int otyp; -int x, y; +sobj_at(int otyp, coordxy x, coordxy y) { - register struct obj *otmp; + struct obj *otmp; - for (otmp = level.objects[x][y]; otmp; otmp = otmp->nexthere) + for (otmp = svl.level.objects[x][y]; otmp; otmp = otmp->nexthere) if (otmp->otyp == otyp) break; @@ -1215,12 +1476,9 @@ int x, y; /* sobj_at(&c) traversal -- find next object of specified type */ struct obj * -nxtobj(obj, type, by_nexthere) -struct obj *obj; -int type; -boolean by_nexthere; +nxtobj(struct obj *obj, int type, boolean by_nexthere) { - register struct obj *otmp; + struct obj *otmp; otmp = obj; /* start with the object after this one */ do { @@ -1232,16 +1490,29 @@ boolean by_nexthere; return otmp; } +/* return inventory object of type 'type' if hero has one, otherwise Null */ struct obj * -carrying(type) -register int type; +carrying(int type) { - register struct obj *otmp; + struct obj *otmp; - for (otmp = invent; otmp; otmp = otmp->nobj) + /* this could be replaced by 'return m_carrying(&gy.youmonst, type);' */ + for (otmp = gi.invent; otmp; otmp = otmp->nobj) if (otmp->otyp == type) - return otmp; - return (struct obj *) 0; + break; + return otmp; +} + +/* return inventory object of type that will petrify on touch */ +struct obj * +carrying_stoning_corpse(void) +{ + struct obj *otmp; + + for (otmp = gi.invent; otmp; otmp = otmp->nobj) + if (otmp->otyp == CORPSE && touch_petrifies(&mons[otmp->corpsenm])) + break; + return otmp; } /* Fictional and not-so-fictional currencies. @@ -1272,44 +1543,48 @@ static const char *const currencies[] = { }; const char * -currency(amount) -long amount; +currency(long amount) { const char *res; - res = Hallucination ? currencies[rn2(SIZE(currencies))] : "zorkmid"; + res = Hallucination ? ROLL_FROM(currencies) : "zorkmid"; if (amount != 1L) res = makeplural(res); return res; } -boolean -have_lizard() +struct obj * +u_carried_gloves(void) { - register struct obj *otmp; + struct obj *otmp, *gloves = (struct obj *) 0; - for (otmp = invent; otmp; otmp = otmp->nobj) - if (otmp->otyp == CORPSE && otmp->corpsenm == PM_LIZARD) - return TRUE; - return FALSE; + if (uarmg) { + gloves = uarmg; + } else { + for (otmp = gi.invent; otmp; otmp = otmp->nobj) + if (is_gloves(otmp)) { + gloves = otmp; + break; + } + } + return gloves; } + /* 3.6 tribute */ struct obj * -u_have_novel() +u_have_novel(void) { - register struct obj *otmp; + struct obj *otmp; - for (otmp = invent; otmp; otmp = otmp->nobj) + for (otmp = gi.invent; otmp; otmp = otmp->nobj) if (otmp->otyp == SPE_NOVEL) return otmp; return (struct obj *) 0; } struct obj * -o_on(id, objchn) -unsigned int id; -register struct obj *objchn; +o_on(unsigned int id, struct obj *objchn) { struct obj *temp; @@ -1324,23 +1599,20 @@ register struct obj *objchn; } boolean -obj_here(obj, x, y) -register struct obj *obj; -int x, y; +obj_here(struct obj *obj, coordxy x, coordxy y) { - register struct obj *otmp; + struct obj *otmp; - for (otmp = level.objects[x][y]; otmp; otmp = otmp->nexthere) + for (otmp = svl.level.objects[x][y]; otmp; otmp = otmp->nexthere) if (obj == otmp) return TRUE; return FALSE; } struct obj * -g_at(x, y) -register int x, y; +g_at(coordxy x, coordxy y) { - register struct obj *obj = level.objects[x][y]; + struct obj *obj = svl.level.objects[x][y]; while (obj) { if (obj->oclass == COIN_CLASS) @@ -1351,12 +1623,11 @@ register int x, y; } /* compact a string of inventory letters by dashing runs of letters */ -STATIC_OVL void -compactify(buf) -register char *buf; +staticfn void +compactify(char *buf) { - register int i1 = 1, i2 = 1; - register char ilet, ilet1, ilet2; + int i1 = 1, i2 = 1; + char ilet, ilet1, ilet2; ilet2 = buf[0]; ilet1 = buf[1]; @@ -1390,274 +1661,268 @@ register char *buf; /* some objects shouldn't be split when count given to getobj or askchain */ boolean -splittable(obj) -struct obj *obj; +splittable(struct obj *obj) { return !((obj->otyp == LOADSTONE && obj->cursed) || (obj == uwep && welded(uwep))); } /* match the prompt for either 'T' or 'R' command */ -STATIC_OVL boolean -taking_off(action) -const char *action; +staticfn boolean +taking_off(const char *action) { return !strcmp(action, "take off") || !strcmp(action, "remove"); } -/* match the prompt for either 'W' or 'P' command */ -STATIC_OVL boolean -putting_on(action) -const char *action; +staticfn void +mime_action(const char *word) +{ + char buf[BUFSZ]; + char *bp, *pfx, *sfx; + + Strcpy(buf, word); + bp = pfx = sfx = (char *) 0; + + if ((bp = strstr(buf, " on the ")) != 0) { + /* rub on the stone[s] */ + *bp = '\0'; + sfx = (bp + 1); /* "something " */ + } + if ((!strncmp(buf, "rub the ", 8) && strstr(buf + 8, " on")) + || (!strncmp(buf, "dip ", 4) && strstr(buf + 4, " into"))) { + /* "rub the royal jelly on" -> "rubbing the royal jelly on", or + "dip into" => "dipping into" */ + buf[3] = '\0'; + pfx = &buf[3 + 1]; /* " something" */ + } + if ((bp = strstr(buf, " or ")) != 0) { + *bp = '\0'; + bp = (rn2(2) ? buf : (bp + 4)); + } else + bp = buf; + + You("mime %s%s%s something%s%s.", ing_suffix(bp), + pfx ? " " : "", pfx ? pfx : "", sfx ? " " : "", sfx ? sfx : ""); +} + +/* getobj callback that allows any object - but not hands. */ +int +any_obj_ok(struct obj *obj) +{ + if (obj) + return GETOBJ_SUGGEST; + return GETOBJ_EXCLUDE; +} + +/* return string describing your hands based on action. */ +staticfn char * +getobj_hands_txt(const char *action, char *qbuf) { - return !strcmp(action, "wear") || !strcmp(action, "put on"); + if (!strcmp(action, "grease")) { + Sprintf(qbuf, "your %s", fingers_or_gloves(FALSE)); + } else if (!strcmp(action, "write with")) { + Sprintf(qbuf, "your %s", body_part(FINGERTIP)); + } else if (!strcmp(action, "wield")) { + Sprintf(qbuf, "your %s %s%s", uarmg ? "gloved" : "bare", + makeplural(body_part(HAND)), + !uwep ? " (wielded)" : ""); + } else if (!strcmp(action, "ready")) { + Sprintf(qbuf, "empty quiver%s", + !uquiver ? " (nothing readied)" : ""); + } else { + Sprintf(qbuf, "your %s", makeplural(body_part(HAND))); + } + return qbuf; } /* * getobj returns: * struct obj *xxx: object to do something with. * (struct obj *) 0 error return: no object. - * &zeroobj explicitly no object (as in w-). -!!!! test if gold can be used in unusual ways (eaten etc.) -!!!! may be able to remove "usegold" + * &hands_obj explicitly no object (as in w-). + * The obj_ok callback should not have side effects (apart from + * abnormal-behavior things like impossible calls); it can be called multiple + * times on the same object during the execution of this function. + * Callbacks' argument is either a valid object pointer or a null pointer, + * which represents the validity of doing that action on HANDS_SYM. getobj + * won't call it with &hands_obj, so its behavior can be undefined in that + * case. */ struct obj * -getobj(let, word) -register const char *let, *word; +getobj( + const char *word, /* usually a direct verb such as "drop" */ + int (*obj_ok)(OBJ_P), /* callback to classify an object's suitability */ + unsigned int ctrlflags) /* some control to fine-tune the behavior */ { - register struct obj *otmp; - register char ilet = 0; + struct obj *otmp; + char ilet = 0; char buf[BUFSZ], qbuf[QBUFSZ]; - char lets[BUFSZ], altlets[BUFSZ], *ap; - register int foo = 0; - register char *bp = buf; - xchar allowcnt = 0; /* 0, 1 or 2 */ - boolean usegold = FALSE; /* can't use gold because its illegal */ - boolean allowall = FALSE; - boolean allownone = FALSE; - boolean useboulder = FALSE; - xchar foox = 0; - long cnt; + char lets[BUFSZ], altlets[BUFSZ]; + int suggested = 0; + char *bp = buf, *ap = altlets; + boolean allowcnt = (ctrlflags & GETOBJ_ALLOWCNT), + forceprompt = (ctrlflags & GETOBJ_PROMPT), + allownone = FALSE; + int inaccess = 0; /* counts GETOBJ_EXCLUDE_INACCESS items to decide + * between "you don't have anything to " + * versus "you don't have anything _else_ to " + * (also used for GETOBJ_EXCLUDE_NONINVENT) */ + long cnt = 0L; boolean cntgiven = FALSE; boolean msggiven = FALSE; boolean oneloop = FALSE; - long dummymask; Loot *sortedinvent, *srtinv; + struct _cmd_queue cq, *cmdq; + boolean need_more_cq = FALSE; + + need_more_cq: + if ((cmdq = cmdq_pop()) != 0) { + cq = *cmdq; + free(cmdq); + /* user-input means pick something interactively now, with more + in the command queue for after that; if not user-input, it + has to be a key here */ + if (cq.typ != CMDQ_USER_INPUT) { + otmp = 0; /* in case of non-key or lookup failure */ + if (cq.typ == CMDQ_KEY) { + int v; + + if (cq.key == HANDS_SYM) { + /* check whether the hands/self choice is suitable */ + v = (*obj_ok)((struct obj *) 0); + if (v == GETOBJ_SUGGEST || v == GETOBJ_DOWNPLAY) + otmp = &hands_obj; + } else { + /* there could be more than one match if key is '#'; + take first one which passes the obj_ok callback */ + for (otmp = gi.invent; otmp; otmp = otmp->nobj) + if (otmp->invlet == cq.key) { + v = (*obj_ok)(otmp); + if (v == GETOBJ_SUGGEST || v == GETOBJ_DOWNPLAY) + break; + } + } + } else if (cq.typ == CMDQ_INT) { + /* getting a partial stack */ + if (!cntgiven && allowcnt) { + cnt = (long) cq.intval; + cntgiven = TRUE; + goto need_more_cq; /* now, get CMDQ_KEY */ + } else { + cmdq_clear(CQ_CANNED); + /* should maybe clear the CQ_REPEAT too? */ + return NULL; + } + } + if (!otmp) { /* didn't find what we were looking for, */ + cmdq_clear(CQ_CANNED); /* so discard any other queued cmnds */ + } else if (cntgiven) { + /* if stack is smaller than count, drop the whole stack */ + if (cnt < 1L || otmp->quan <= cnt) + cntgiven = FALSE; + goto split_otmp; + } + return otmp; + } /* !CMDQ_USER_INPUT */ + } else if (need_more_cq) { + return NULL; + } - if (*let == ALLOW_COUNT) - let++, allowcnt = 1; - if (*let == COIN_CLASS) - let++, usegold = TRUE; - - /* Equivalent of an "ugly check" for gold */ - if (usegold && !strcmp(word, "eat") - && (!metallivorous(youmonst.data) - || youmonst.data == &mons[PM_RUST_MONSTER])) - usegold = FALSE; - - if (*let == ALL_CLASSES) - let++, allowall = TRUE; - if (*let == ALLOW_NONE) - let++, allownone = TRUE; - /* "ugly check" for reading fortune cookies, part 1. - * The normal 'ugly check' keeps the object on the inventory list. - * We don't want to do that for shirts/cookies, so the check for - * them is handled a bit differently (and also requires that we set - * allowall in the caller). - */ - if (allowall && !strcmp(word, "read")) - allowall = FALSE; - - /* another ugly check: show boulders (not statues) */ - if (*let == WEAPON_CLASS && !strcmp(word, "throw") - && throws_rocks(youmonst.data)) - useboulder = TRUE; - - if (allownone) - *bp++ = HANDS_SYM, *bp++ = ' '; /* '-' */ - ap = altlets; + /* is "hands"/"self" a valid thing to do this action on? */ + switch ((*obj_ok)((struct obj *) 0)) { + case GETOBJ_SUGGEST: /* treat as likely candidate */ + allownone = TRUE; + *bp++ = HANDS_SYM; + *bp++ = ' '; /* put a space after the '-' in the prompt */ + break; + case GETOBJ_DOWNPLAY: /* acceptable but not shown as likely choice */ + case GETOBJ_EXCLUDE_INACCESS: /* nothing currently gives this for '-' + * but theoretically could if wearing + * gloves */ + case GETOBJ_EXCLUDE_SELECTABLE: /* ditto, I think... */ + allownone = TRUE; + *ap++ = HANDS_SYM; + break; + case GETOBJ_EXCLUDE_NONINVENT: /* player skipped some alternative that's + * not in inventory, now the hands/self + * possibility is telling us so */ + forceprompt = FALSE; + inaccess++; + break; + default: + break; + } if (!flags.invlet_constant) reassign(); /* force invent to be in invlet order before collecting candidate inventory letters */ - sortedinvent = sortloot(&invent, SORTLOOT_INVLET, FALSE, - (boolean FDECL((*), (OBJ_P))) 0); + sortedinvent = sortloot(&gi.invent, SORTLOOT_INVLET, FALSE, + (boolean (*)(OBJ_P)) 0); for (srtinv = sortedinvent; (otmp = srtinv->obj) != 0; ++srtinv) { - if (&bp[foo] == &buf[sizeof buf - 1] + if (&bp[suggested] == &buf[sizeof buf - 1] || ap == &altlets[sizeof altlets - 1]) { - /* we must have a huge number of NOINVSYM items somehow */ + /* we must have a huge number of noinvsym items somehow */ impossible("getobj: inventory overflow"); break; } - if (!*let || index(let, otmp->oclass) - || (usegold && otmp->invlet == GOLD_SYM) - || (useboulder && otmp->otyp == BOULDER)) { - register int otyp = otmp->otyp; - - bp[foo++] = otmp->invlet; -/* clang-format off */ -/* *INDENT-OFF* */ - /* ugly check: remove inappropriate things */ - if ( - (taking_off(word) /* exclude if not worn */ - && !(otmp->owornmask & (W_ARMOR | W_ACCESSORY))) - || (putting_on(word) /* exclude if already worn */ - && (otmp->owornmask & (W_ARMOR | W_ACCESSORY))) -#if 0 /* 3.4.1 -- include currently wielded weapon among 'wield' choices */ - || (!strcmp(word, "wield") - && (otmp->owornmask & W_WEP)) -#endif - || (!strcmp(word, "ready") /* exclude when wielded... */ - && ((otmp == uwep || (otmp == uswapwep && u.twoweap)) - && otmp->quan == 1L)) /* ...unless more than one */ - || ((!strcmp(word, "dip") || !strcmp(word, "grease")) - && inaccessible_equipment(otmp, (const char *) 0, FALSE)) - ) { - foo--; - foox++; - } - /* Second ugly check; unlike the first it won't trigger an - * "else" in "you don't have anything else to ___". - */ - else if ( - (putting_on(word) - && ((otmp->oclass == FOOD_CLASS && otmp->otyp != MEAT_RING) - || (otmp->oclass == TOOL_CLASS && otyp != BLINDFOLD - && otyp != TOWEL && otyp != LENSES))) - || (!strcmp(word, "wield") - && (otmp->oclass == TOOL_CLASS && !is_weptool(otmp))) - || (!strcmp(word, "eat") && !is_edible(otmp)) - || (!strcmp(word, "sacrifice") - && (otyp != CORPSE && otyp != AMULET_OF_YENDOR - && otyp != FAKE_AMULET_OF_YENDOR)) - || (!strcmp(word, "write with") - && (otmp->oclass == TOOL_CLASS - && otyp != MAGIC_MARKER && otyp != TOWEL)) - || (!strcmp(word, "tin") - && (otyp != CORPSE || !tinnable(otmp))) - || (!strcmp(word, "rub") - && ((otmp->oclass == TOOL_CLASS && otyp != OIL_LAMP - && otyp != MAGIC_LAMP && otyp != BRASS_LANTERN) - || (otmp->oclass == GEM_CLASS && !is_graystone(otmp)))) - || (!strcmp(word, "use or apply") - /* Picks, axes, pole-weapons, bullwhips */ - && ((otmp->oclass == WEAPON_CLASS - && !is_pick(otmp) && !is_axe(otmp) - && !is_pole(otmp) && otyp != BULLWHIP) - || (otmp->oclass == POTION_CLASS - /* only applicable potion is oil, and it will only - be offered as a choice when already discovered */ - && (otyp != POT_OIL || !otmp->dknown - || !objects[POT_OIL].oc_name_known)) - || (otmp->oclass == FOOD_CLASS - && otyp != CREAM_PIE && otyp != EUCALYPTUS_LEAF) - || (otmp->oclass == GEM_CLASS && !is_graystone(otmp)))) - || (!strcmp(word, "invoke") - && !otmp->oartifact - && !objects[otyp].oc_unique - && (otyp != FAKE_AMULET_OF_YENDOR || otmp->known) - && otyp != CRYSTAL_BALL /* synonym for apply */ - /* note: presenting the possibility of invoking non-artifact - mirrors and/or lamps is simply a cruel deception... */ - && otyp != MIRROR - && otyp != MAGIC_LAMP - && (otyp != OIL_LAMP /* don't list known oil lamp */ - || (otmp->dknown && objects[OIL_LAMP].oc_name_known))) - || (!strcmp(word, "untrap with") - && ((otmp->oclass == TOOL_CLASS && otyp != CAN_OF_GREASE) - || (otmp->oclass == POTION_CLASS - /* only applicable potion is oil, and it will only - be offered as a choice when already discovered */ - && (otyp != POT_OIL || !otmp->dknown - || !objects[POT_OIL].oc_name_known)))) - || (!strcmp(word, "tip") && !Is_container(otmp) - /* include horn of plenty if sufficiently discovered */ - && (otmp->otyp != HORN_OF_PLENTY || !otmp->dknown - || !objects[HORN_OF_PLENTY].oc_name_known)) - || (!strcmp(word, "charge") && !is_chargeable(otmp)) - || (!strcmp(word, "open") && otyp != TIN) - || (!strcmp(word, "call") && !objtyp_is_callable(otyp)) - || (!strcmp(word, "adjust") && otmp->oclass == COIN_CLASS - && !usegold) - ) { - foo--; - } - /* Third ugly check: acceptable but not listed as likely - * candidates in the prompt or in the inventory subset if - * player responds with '?'. - */ - else if ( - /* ugly check for unworn armor that can't be worn */ - (putting_on(word) && *let == ARMOR_CLASS - && !canwearobj(otmp, &dummymask, FALSE)) - /* or armor with 'P' or 'R' or accessory with 'W' or 'T' */ - || ((putting_on(word) || taking_off(word)) - && ((*let == ARMOR_CLASS) ^ (otmp->oclass == ARMOR_CLASS))) - /* or unsuitable items rubbed on known touchstone */ - || (!strncmp(word, "rub on the stone", 16) - && *let == GEM_CLASS && otmp->dknown - && objects[otyp].oc_name_known) - /* suppress corpses on astral, amulets elsewhere */ - || (!strcmp(word, "sacrifice") - /* (!astral && amulet) || (astral && !amulet) */ - && (!Is_astralevel(&u.uz) ^ (otmp->oclass != AMULET_CLASS))) - /* suppress container being stashed into */ - || (!strcmp(word, "stash") && !ck_bag(otmp)) - /* worn armor (shirt, suit) covered by worn armor (suit, cloak) - or accessory (ring) covered by cursed worn armor (gloves) */ - || (taking_off(word) - && inaccessible_equipment(otmp, (const char *) 0, - (boolean) (otmp->oclass == RING_CLASS))) - || (!strcmp(word, "write on") - && (!(otyp == SCR_BLANK_PAPER || otyp == SPE_BLANK_PAPER) - || !otmp->dknown || !objects[otyp].oc_name_known)) - ) { - /* acceptable but not listed as likely candidate */ - foo--; - allowall = TRUE; - *ap++ = otmp->invlet; - } -/* *INDENT-ON* */ -/* clang-format on */ - } else { - /* "ugly check" for reading fortune cookies, part 2 */ - if ((!strcmp(word, "read") && is_readable(otmp))) - allowall = usegold = TRUE; + bp[suggested++] = otmp->invlet; + switch ((*obj_ok)(otmp)) { + case GETOBJ_EXCLUDE_INACCESS: + /* remove inaccessible things */ + suggested--; + inaccess++; + break; + case GETOBJ_EXCLUDE: + case GETOBJ_EXCLUDE_SELECTABLE: + /* remove more inappropriate things, but unlike the first it won't + trigger an "else" in "you don't have anything else to ___" */ + suggested--; + break; + case GETOBJ_DOWNPLAY: + /* acceptable but not listed as likely candidates in the prompt + or in the inventory subset if player responds with '?' - thus, + don't add it to lets with bp, but add it to altlets with ap */ + suggested--; + forceprompt = TRUE; + *ap++ = otmp->invlet; + break; + case GETOBJ_SUGGEST: + break; /* adding otmp->invlet is all that's needed */ + case GETOBJ_EXCLUDE_NONINVENT: /* not applicable for invent items */ + default: + impossible("bad return from getobj callback"); } } unsortloot(&sortedinvent); - bp[foo] = 0; - if (foo == 0 && bp > buf && bp[-1] == ' ') - *--bp = 0; + bp[suggested] = '\0'; + /* If no objects were suggested but we added '- ' at the beginning for + * hands, destroy the trailing space */ + if (suggested == 0 && bp > buf && bp[-1] == ' ') + *--bp = '\0'; Strcpy(lets, bp); /* necessary since we destroy buf */ - if (foo > 5) /* compactify string */ + if (suggested > 5) /* compactify string */ compactify(bp); *ap = '\0'; - if (!foo && !allowall && !allownone) { - You("don't have anything %sto %s.", foox ? "else " : "", word); + if (suggested == 0 && !forceprompt && !allownone) { + You("don't have anything %sto %s.", inaccess ? "else " : "", word); return (struct obj *) 0; - } else if (!strcmp(word, "write on")) { /* ugly check for magic marker */ - /* we wanted all scrolls and books in altlets[], but that came with - 'allowall' which we don't want since it prevents "silly thing" - result if anything other than scroll or spellbook is chosen */ - allowall = FALSE; } for (;;) { - cnt = 0; + cnt = 0L; cntgiven = FALSE; Sprintf(qbuf, "What do you want to %s?", word); - if (in_doagain) + if (gi.in_doagain) { ilet = readchar(); - else if (iflags.force_invmenu) { + } else if (iflags.force_invmenu) { /* don't overwrite a possible quitchars */ if (!oneloop) - ilet = *let ? '?' : '*'; + ilet = (*lets || *altlets) ? '?' : '*'; if (!msggiven) putmsghistory(qbuf, FALSE); msggiven = TRUE; @@ -1667,154 +1932,149 @@ register const char *let, *word; Strcat(qbuf, " [*]"); else Sprintf(eos(qbuf), " [%s or ?*]", buf); - ilet = yn_function(qbuf, (char *) 0, '\0'); + ilet = yn_function(qbuf, (char *) 0, '\0', FALSE); } if (digit(ilet)) { - long tmpcnt = 0; + long tmpcnt = 0L; if (!allowcnt) { pline("No count allowed with this command."); continue; } - ilet = get_count(NULL, ilet, LARGEST_INT, &tmpcnt, TRUE); + ilet = get_count(NULL, ilet, LARGEST_INT, &tmpcnt, GC_SAVEHIST); if (tmpcnt) { cnt = tmpcnt; cntgiven = TRUE; } } - if (index(quitchars, ilet)) { + if (strchr(quitchars, ilet)) { if (flags.verbose) pline1(Never_mind); return (struct obj *) 0; } if (ilet == HANDS_SYM) { /* '-' */ - if (!allownone) { - char *suf = (char *) 0; - - strcpy(buf, word); - if ((bp = strstr(buf, " on the ")) != 0) { - /* rub on the stone[s] */ - *bp = '\0'; - suf = (bp + 1); - } - if ((bp = strstr(buf, " or ")) != 0) { - *bp = '\0'; - bp = (rn2(2) ? buf : (bp + 4)); - } else - bp = buf; - You("mime %s something%s%s.", ing_suffix(bp), suf ? " " : "", - suf ? suf : ""); - } - return (allownone ? (struct obj *) &zeroobj : (struct obj *) 0); + if (!allownone) + mime_action(word); + return (allownone ? &hands_obj : (struct obj *) 0); } redo_menu: /* since gold is now kept in inventory, we need to do processing for select-from-invent before checking whether gold has been picked */ if (ilet == '?' || ilet == '*') { char *allowed_choices = (ilet == '?') ? lets : (char *) 0; - long ctmp = 0; + long ctmp = 0L; char menuquery[QBUFSZ]; - - menuquery[0] = qbuf[0] = '\0'; - if (iflags.force_invmenu) - Sprintf(menuquery, "What do you want to %s?", word); - if (!strcmp(word, "grease")) - Sprintf(qbuf, "your %s", fingers_or_gloves(FALSE)); - else if (!strcmp(word, "write with")) - Sprintf(qbuf, "your %s", body_part(FINGERTIP)); - else if (!strcmp(word, "wield")) - Sprintf(qbuf, "your %s %s%s", uarmg ? "gloved" : "bare", - makeplural(body_part(HAND)), - !uwep ? " (wielded)" : ""); - else if (!strcmp(word, "ready")) - Sprintf(qbuf, "empty quiver%s", - !uquiver ? " (nothing readied)" : ""); + char *handsbuf = (char *) 0; if (ilet == '?' && !*lets && *altlets) allowed_choices = altlets; - ilet = display_pickinv(allowed_choices, *qbuf ? qbuf : (char *) 0, - menuquery, - TRUE, allowcnt ? &ctmp : (long *) 0); - if (!ilet) + + menuquery[0] = qbuf[0] = '\0'; + if (iflags.force_invmenu) + Snprintf(menuquery, sizeof menuquery, + "What do you want to %s?", word); + if (!allowed_choices || *allowed_choices == HANDS_SYM + || *buf == HANDS_SYM) + handsbuf = getobj_hands_txt(word, qbuf); + ilet = display_pickinv(allowed_choices, handsbuf, + menuquery, allownone, TRUE, + allowcnt ? &ctmp : (long *) 0); + if (!ilet) { + if (oneloop) + return (struct obj *) 0; continue; + } if (ilet == HANDS_SYM) - return (struct obj *) &zeroobj; /* cast away 'const' */ + return &hands_obj; if (ilet == '\033') { if (flags.verbose) pline1(Never_mind); return (struct obj *) 0; } - if (ilet == '*') + if (ilet == '*' || ilet == '?') goto redo_menu; - if (allowcnt && ctmp >= 0) { + if (allowcnt && ctmp >= 0L) { cnt = ctmp; cntgiven = TRUE; } /* they typed a letter (not a space) at the prompt */ } /* find the item which was picked */ - for (otmp = invent; otmp; otmp = otmp->nobj) + for (otmp = gi.invent; otmp; otmp = otmp->nobj) if (otmp->invlet == ilet) break; /* some items have restrictions */ - if (ilet == def_oc_syms[COIN_CLASS].sym - /* guard against the [hypothetical] chace of having more + if (ilet == GOLD_SYM + /* guard against the [hypothetical] chance of having more than one invent slot of gold and picking the non-'$' one */ || (otmp && otmp->oclass == COIN_CLASS)) { - if (!usegold) { + if (otmp && obj_ok(otmp) <= GETOBJ_EXCLUDE) { You("cannot %s gold.", word); return (struct obj *) 0; } - /* Historic note: early Nethack had a bug which was + /* + * Historical note: early Nethack had a bug which was * first reported for Larn, where trying to drop 2^32-n - * gold pieces was allowed, and did interesting things - * to your money supply. The LRS is the tax bureau - * from Larn. + * gold pieces was allowed, and did interesting things to + * your money supply. The LRS is the tax bureau from Larn. */ - if (cntgiven && cnt <= 0) { - if (cnt < 0) - pline_The( - "LRS would be very interested to know you have that much."); + if (cntgiven && cnt <= 0L) { + if (cnt < 0L) + pline_The("LRS would be very interested to know" + " you have that much."); return (struct obj *) 0; } } if (cntgiven && !strcmp(word, "throw")) { - /* permit counts for throwing gold, but don't accept - * counts for other things since the throw code will - * split off a single item anyway */ - if (cnt == 0) + static const char only_one[] = "can only throw one at a time"; + boolean coins; + + /* permit counts for throwing gold, but don't accept counts + for other things since the throw code will split off a + single item anyway; if populating quiver, 'word' will be + "ready" or "fire" and this restriction doesn't apply */ + if (cnt == 0L || !otmp) return (struct obj *) 0; - if (cnt > 1 && (ilet != def_oc_syms[COIN_CLASS].sym - && !(otmp && otmp->oclass == COIN_CLASS))) { - You("can only throw one item at a time."); + coins = (otmp->oclass == COIN_CLASS); + if (cnt > 1L && (!coins || cnt > otmp->quan)) { + if (cnt > otmp->quan) + You("only have %ld%s%s.", otmp->quan, + (!coins && otmp->quan > 1L) ? " and " : "", + (!coins && otmp->quan > 1L) ? only_one : ""); + else + You("%s.", only_one); continue; } } - context.botl = 1; /* May have changed the amount of money */ - savech(ilet); + disp.botl = TRUE; /* May have changed the amount of money */ + if (otmp && !gi.in_doagain) { + if (cntgiven && cnt > 0L) + cmdq_add_int(CQ_REPEAT, cnt); + cmdq_add_key(CQ_REPEAT, ilet); + } /* [we used to set otmp (by finding ilet in invent) here, but that's been moved above so that otmp can be checked earlier] */ /* verify the chosen object */ if (!otmp) { You("don't have that object."); - if (in_doagain) + if (gi.in_doagain) return (struct obj *) 0; continue; - } else if (cnt < 0 || otmp->quan < cnt) { + } else if (cnt < 0L || otmp->quan < cnt) { You("don't have that many! You have only %ld.", otmp->quan); - if (in_doagain) + if (gi.in_doagain) return (struct obj *) 0; continue; } break; } - if (!allowall && let && !index(let, otmp->oclass) - && !(usegold && otmp->oclass == COIN_CLASS)) { + if (obj_ok(otmp) == GETOBJ_EXCLUDE) { silly_thing(word, otmp); return (struct obj *) 0; } + split_otmp: if (cntgiven) { - if (cnt == 0) + if (cnt == 0L) return (struct obj *) 0; if (cnt != otmp->quan) { /* don't split a stack of cursed loadstones */ @@ -1828,14 +2088,14 @@ register const char *let, *word; return otmp; } +DISABLE_WARNING_FORMAT_NONLITERAL + void -silly_thing(word, otmp) -const char *word; -struct obj *otmp; +silly_thing(const char *word, + struct obj *otmp) { -#if 1 /* 'P','R' vs 'W','T' handling is obsolete */ - nhUse(otmp); -#else +#ifdef OBSOLETE_HANDLING + /* 'P','R' vs 'W','T' handling is obsolete */ const char *s1, *s2, *s3; int ocls = otmp->oclass, otyp = otmp->otyp; @@ -1860,59 +2120,72 @@ struct obj *otmp; !(is_plural(otmp) || pair_of(otmp)) ? "that" : "those", s3); else #endif + /* see comment about Amulet of Yendor in objtyp_is_callable(do_name.c); + known fakes yield the silly thing feedback */ + if (!strcmp(word, "call") + && (otmp->otyp == AMULET_OF_YENDOR + || (otmp->otyp == FAKE_AMULET_OF_YENDOR && !otmp->known))) + pline_The("Amulet doesn't like being called names."); + else pline(silly_thing_to, word); } -STATIC_PTR int -ckvalidcat(otmp) -struct obj *otmp; +RESTORE_WARNING_FORMAT_NONLITERAL + +staticfn int +ckvalidcat(struct obj *otmp) { /* use allow_category() from pickup.c */ return (int) allow_category(otmp); } -STATIC_PTR int -ckunpaid(otmp) -struct obj *otmp; +staticfn int +ckunpaid(struct obj *otmp) { return (otmp->unpaid || (Has_contents(otmp) && count_unpaid(otmp->cobj))); } boolean -wearing_armor() +wearing_armor(void) { return (boolean) (uarm || uarmc || uarmf || uarmg || uarmh || uarms || uarmu); } boolean -is_worn(otmp) -struct obj *otmp; +is_worn(struct obj *otmp) { return (otmp->owornmask & (W_ARMOR | W_ACCESSORY | W_SADDLE | W_WEAPONS)) ? TRUE : FALSE; } +/* is 'obj' being used by the hero? worn, wielded, active lamp or leash; + not to be confused with obj->in_use, which finishes using up an item + (destroys it) if restoring a save file finds that bit set */ +boolean +is_inuse(struct obj *obj) +{ + return (carried(obj) && (is_worn(obj) || tool_being_used(obj))); +} + /* extra xprname() input that askchain() can't pass through safe_qbuf() */ -STATIC_VAR struct xprnctx { +static struct xprnctx { char let; boolean dot; } safeq_xprn_ctx; /* safe_qbuf() -> short_oname() callback */ -STATIC_PTR char * -safeq_xprname(obj) -struct obj *obj; +staticfn char * +safeq_xprname(struct obj *obj) { return xprname(obj, (char *) 0, safeq_xprn_ctx.let, safeq_xprn_ctx.dot, 0L, 0L); } /* alternate safe_qbuf() -> short_oname() callback */ -STATIC_PTR char * -safeq_shortxprname(obj) -struct obj *obj; +staticfn char * +safeq_shortxprname(struct obj *obj) { return xprname(obj, ansimpleoname(obj), safeq_xprn_ctx.let, safeq_xprn_ctx.dot, 0L, 0L); @@ -1926,22 +2199,20 @@ static NEARDATA const char removeables[] = { ARMOR_CLASS, WEAPON_CLASS, Return the number of times fn was called successfully. If combo is TRUE, we just use this to get a category list. */ int -ggetobj(word, fn, mx, combo, resultflags) -const char *word; -int FDECL((*fn), (OBJ_P)), mx; -boolean combo; /* combination menu flag */ -unsigned *resultflags; +ggetobj(const char *word, int (*fn)(OBJ_P), int mx, + boolean combo, /* combination menu flag */ + unsigned *resultflags) { - int FDECL((*ckfn), (OBJ_P)) = (int FDECL((*), (OBJ_P))) 0; - boolean FDECL((*ofilter), (OBJ_P)) = (boolean FDECL((*), (OBJ_P))) 0; + int (*ckfn)(OBJ_P) = (int (*)(OBJ_P)) 0; + boolean (*ofilter)(OBJ_P) = (boolean (*)(OBJ_P)) 0; boolean takeoff, ident, allflag, m_seen; int itemcount; int oletct, iletct, unpaid, oc_of_sym; - char sym, *ip, olets[MAXOCLASSES + 5], ilets[MAXOCLASSES + 10]; + char sym, *ip, olets[MAXOCLASSES + 6], ilets[MAXOCLASSES + 11]; char extra_removeables[3 + 1]; /* uwep,uswapwep,uquiver */ char buf[BUFSZ] = DUMMY, qbuf[QBUFSZ]; - if (!invent) { + if (!gi.invent) { You("have nothing to %s.", word); if (resultflags) *resultflags = ALL_FINISHED; @@ -1959,23 +2230,26 @@ unsigned *resultflags; ofilter = not_fully_identified; } - iletct = collect_obj_classes(ilets, invent, FALSE, ofilter, &itemcount); - unpaid = count_unpaid(invent); + iletct = collect_obj_classes(ilets, gi.invent, FALSE, ofilter, + &itemcount); + unpaid = count_unpaid(gi.invent); if (ident && !iletct) { return -1; /* no further identifications */ - } else if (invent) { + } else if (gi.invent) { ilets[iletct++] = ' '; if (unpaid) ilets[iletct++] = 'u'; - if (count_buc(invent, BUC_BLESSED, ofilter)) + if (count_buc(gi.invent, BUC_BLESSED, ofilter)) ilets[iletct++] = 'B'; - if (count_buc(invent, BUC_UNCURSED, ofilter)) + if (count_buc(gi.invent, BUC_UNCURSED, ofilter)) ilets[iletct++] = 'U'; - if (count_buc(invent, BUC_CURSED, ofilter)) + if (count_buc(gi.invent, BUC_CURSED, ofilter)) ilets[iletct++] = 'C'; - if (count_buc(invent, BUC_UNKNOWN, ofilter)) + if (count_buc(gi.invent, BUC_UNKNOWN, ofilter)) ilets[iletct++] = 'X'; + if (count_justpicked(gi.invent)) + ilets[iletct++] = 'P'; ilets[iletct++] = 'a'; } ilets[iletct++] = 'i'; @@ -1989,17 +2263,19 @@ unsigned *resultflags; getlin(qbuf, buf); if (buf[0] == '\033') return 0; - if (index(buf, 'i')) { + if (strchr(buf, 'i')) { char ailets[1+26+26+1+5+1]; /* $ + a-z + A-Z + # + slop + \0 */ - struct obj *otmp; + struct obj *otmp, *nextobj; /* applicable inventory letters; if empty, show entire invent */ ailets[0] = '\0'; if (ofilter) - for (otmp = invent; otmp; otmp = otmp->nobj) - /* index() check: limit overflow items to one '#' */ - if ((*ofilter)(otmp) && !index(ailets, otmp->invlet)) + for (otmp = gi.invent; otmp; otmp = nextobj) { + nextobj = otmp->nobj; + /* strchr() check: limit overflow items to one '#' */ + if ((*ofilter)(otmp) && !strchr(ailets, otmp->invlet)) (void) strkitten(ailets, otmp->invlet); + } if (display_inventory(ailets, TRUE) == '\033') return 0; } else @@ -2025,9 +2301,9 @@ unsigned *resultflags; continue; oc_of_sym = def_char_to_objclass(sym); if (takeoff && oc_of_sym != MAXOCLASSES) { - if (index(extra_removeables, oc_of_sym)) { + if (strchr(extra_removeables, oc_of_sym)) { ; /* skip rest of takeoff checks */ - } else if (!index(removeables, oc_of_sym)) { + } else if (!strchr(removeables, oc_of_sym)) { pline("Not applicable."); return 0; } else if (oc_of_sym == ARMOR_CLASS && !wearing_armor()) { @@ -2049,27 +2325,25 @@ unsigned *resultflags; } } - if (oc_of_sym == COIN_CLASS && !combo) { - context.botl = 1; - } else if (sym == 'a') { + if (sym == 'a') { allflag = TRUE; } else if (sym == 'A') { ; /* same as the default */ } else if (sym == 'u') { add_valid_menu_class('u'); ckfn = ckunpaid; - } else if (index("BUCX", sym)) { - add_valid_menu_class(sym); /* 'B','U','C',or 'X' */ + } else if (strchr("BUCXP", sym)) { + add_valid_menu_class(sym); /* 'B','U','C','X', or 'P' */ ckfn = ckvalidcat; } else if (sym == 'm') { m_seen = TRUE; } else if (oc_of_sym == MAXOCLASSES) { You("don't have any %c's.", sym); - } else if (oc_of_sym != VENOM_CLASS) { /* suppress venom */ - if (!index(olets, oc_of_sym)) { + } else { + if (!strchr(olets, oc_of_sym)) { add_valid_menu_class(oc_of_sym); olets[oletct++] = oc_of_sym; - olets[oletct] = 0; + olets[oletct] = '\0'; } } } @@ -2080,13 +2354,8 @@ unsigned *resultflags; ? -2 : -3; } else if (flags.menu_style != MENU_TRADITIONAL && combo && !allflag) { return 0; -#if 0 - /* !!!! test gold dropping */ - } else if (allowgold == 2 && !oletct) { - return 1; /* you dropped gold (or at least tried to) */ -#endif } else { - int cnt = askchain(&invent, olets, allflag, fn, ckfn, mx, word); + int cnt = askchain(&gi.invent, olets, allflag, fn, ckfn, mx, word); /* * askchain() has already finished the job in this case * so set a special flag to convey that back to the caller @@ -2103,18 +2372,19 @@ unsigned *resultflags; * Walk through the chain starting at objchn and ask for all objects * with olet in olets (if nonNULL) and satisfying ckfn (if nonnull) * whether the action in question (i.e., fn) has to be performed. - * If allflag then no questions are asked. Mx gives the max number - * of objects to be treated. Return the number of objects treated. */ int -askchain(objchn, olets, allflag, fn, ckfn, mx, word) -struct obj **objchn; /* *objchn might change */ -int allflag, mx; -const char *olets, *word; /* olets is an Obj Class char array */ -int FDECL((*fn), (OBJ_P)), FDECL((*ckfn), (OBJ_P)); +askchain( + struct obj **objchn, /* *objchn might change */ + const char *olets, /* olets is an Obj Class char array */ + int allflag, /* bypass prompting about individual items */ + int (*fn)(OBJ_P), /* action to perform on selected items */ + int (*ckfn)(OBJ_P), /* callback to decided if an item is selectable */ + int mx, /* if non-0, maximum number of objects to process */ + const char *word) /* name of the action */ { struct obj *otmp, *otmpo; - register char sym, ilet; + char sym, ilet; int cnt = 0, dud = 0, tmp; boolean takeoff, nodot, ident, take_out, put_in, first, ininv, bycat; char qbuf[QBUFSZ], qpfx[QBUFSZ]; @@ -2126,15 +2396,16 @@ int FDECL((*fn), (OBJ_P)), FDECL((*ckfn), (OBJ_P)); put_in = !strcmp(word, "put in"); nodot = (!strcmp(word, "nodot") || !strcmp(word, "drop") || ident || takeoff || take_out || put_in); - ininv = (*objchn == invent); + ininv = (*objchn == gi.invent); bycat = (menu_class_present('u') || menu_class_present('B') || menu_class_present('U') - || menu_class_present('C') || menu_class_present('X')); + || menu_class_present('C') || menu_class_present('X') + || menu_class_present('P')); /* someday maybe we'll sort by 'olets' too (temporarily replace flags.packorder and pass SORTLOOT_PACK), but not yet... */ sortedchn = sortloot(objchn, SORTLOOT_INVLET, FALSE, - (boolean FDECL((*), (OBJ_P))) 0); + (boolean (*)(OBJ_P)) 0); first = TRUE; /* @@ -2147,7 +2418,7 @@ int FDECL((*fn), (OBJ_P)), FDECL((*ckfn), (OBJ_P)); if (*objchn && (*objchn)->oclass == COIN_CLASS) ilet--; /* extra iteration */ /* - * Multiple Drop can change the invent chain while it operates + * Multiple Drop can change the gi.invent chain while it operates * (dropping a burning potion of oil while levitating creates * an explosion which can destroy inventory items), so simple * list traversal @@ -2192,8 +2463,11 @@ int FDECL((*fn), (OBJ_P)), FDECL((*ckfn), (OBJ_P)); ininv ? safeq_xprname : doname, ininv ? safeq_shortxprname : ansimpleoname, "item"); - sym = (takeoff || ident || otmp->quan < 2L) ? nyaq(qbuf) - : nyNaq(qbuf); + /* nyaq(qbuf) or nyNaq(qbuf), bypassing canned input for ^A */ + sym = yn_function(qbuf, + (takeoff || ident || otmp->quan < 2L) + ? ynaqchars : ynNaqchars, + 'n', FALSE); } else sym = 'y'; @@ -2215,27 +2489,32 @@ int FDECL((*fn), (OBJ_P)), FDECL((*ckfn), (OBJ_P)); switch (sym) { case 'a': allflag = 1; + FALLTHROUGH; /*FALLTHRU*/ case 'y': tmp = (*fn)(otmp); - if (tmp < 0) { + if (tmp <= 0) { if (container_gone(fn)) { /* otmp caused magic bag to explode; both are now gone */ otmp = 0; /* and return */ } else if (otmp && otmp != otmpo) { /* split occurred, merge again */ - (void) merged(&otmpo, &otmp); + (void) unsplitobj(otmp); } - goto ret; + if (tmp < 0) + goto ret; } cnt += tmp; if (--mx == 0) goto ret; + FALLTHROUGH; /*FALLTHRU*/ case 'n': if (nodot) dud++; + FALLTHROUGH; + /*FALLTHRU*/ default: break; case 'q': @@ -2254,33 +2533,122 @@ int FDECL((*fn), (OBJ_P)), FDECL((*ckfn), (OBJ_P)); pline("No applicable objects."); ret: unsortloot(&sortedchn); - bypass_objlist(*objchn, FALSE); + /* can't just clear bypass bit of items in objchn because the action + applied to selected ones might move them to a different chain */ + /*bypass_objlist(*objchn, FALSE);*/ + clear_bypasses(); return cnt; } + +/* The menu for rerolling attributes and inventory. + + This is similar to the other inventory menus, but simpler to help it fit on + the screen (there's more text around it and rerolling is difficult if you + can't see the whole list at once). + + Returns TRUE (and increases numrerolls) if a reroll was requested. */ +boolean +reroll_menu(void) +{ + winid win; + anything any; + menu_item *pick_list = NULL; + struct obj *otmp; + int tmpglyph; + glyph_info tmpglyphinfo; + char option; + char buf[BUFSZ]; + + win = create_nhwindow(NHW_MENU); + start_menu(win, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + + any.a_char = 'n'; + add_menu(win, &nul_glyphinfo, &any, flags.lootabc ? 0 : 'p', 0, + ATR_NONE, NO_COLOR, "start the game with this character", + MENU_ITEMFLAGS_NONE); + any.a_char = 'y'; + add_menu(win, &nul_glyphinfo, &any, flags.lootabc ? 0 : 'r', 0, + ATR_NONE, NO_COLOR, "reroll another character", + MENU_ITEMFLAGS_NONE); + any.a_char = 0; + add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, NO_COLOR, "", + MENU_ITEMFLAGS_NONE); + + ++gd.distantname; /* avoid adding items to discoveries */ + ++iflags.override_ID; /* identify them */ + for (otmp = gi.invent; otmp; otmp = otmp->nobj) { + tmpglyph = obj_to_glyph(otmp, rn2_on_display_rng); + map_glyphinfo(0, 0, tmpglyph, 0U, &tmpglyphinfo); + add_menu(win, &tmpglyphinfo, &any, 0, 0, + ATR_NONE, NO_COLOR, doname(otmp), MENU_ITEMFLAGS_NONE); + } + --iflags.override_ID; + --gd.distantname; + + add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, NO_COLOR, "", + MENU_ITEMFLAGS_NONE); + Sprintf(buf, "St:%s Dx:%-1d Co:%-1d In:%-1d Wi:%-1d Ch:%-1d", + get_strength_str(), + ACURR(A_DEX), ACURR(A_CON), ACURR(A_INT), ACURR(A_WIS), + ACURR(A_CHA)); + add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, NO_COLOR, + buf, MENU_ITEMFLAGS_NONE); + + end_menu(win, "Reroll this character?"); + if (select_menu(win, PICK_ONE, &pick_list) > 0) { + option = pick_list[0].item.a_char; + free((genericptr_t) pick_list); + } else { + /* user closed the menu without selecting; unclear what their choice + is here so ask again; but (e.g. for hangup handling) stop asking if + the user cancels out again */ + option = y_n("Reroll this character?"); + } + destroy_nhwindow(win); + + if (option == 'y') { + ++u.uroleplay.numrerolls; + return TRUE; + } + return FALSE; +} + /* * Object identification routines: */ +/* set the cknown and lknown flags on an object if they're applicable */ +void +set_cknown_lknown(struct obj *obj) +{ + if (Is_container(obj) || obj->otyp == STATUE) + obj->cknown = obj->lknown = 1; + else if (obj->otyp == TIN) + obj->cknown = 1; + /* TODO? cknown might be extended to candy bar, where it would mean that + wrapper's text was known which in turn indicates candy bar's content */ + return; +} + /* make an object actually be identified; no display updating */ void -fully_identify_obj(otmp) -struct obj *otmp; +fully_identify_obj(struct obj *otmp) { makeknown(otmp->otyp); if (otmp->oartifact) - discover_artifact((xchar) otmp->oartifact); - otmp->known = otmp->dknown = otmp->bknown = otmp->rknown = 1; - if (Is_container(otmp) || otmp->otyp == STATUE) - otmp->cknown = otmp->lknown = 1; + discover_artifact((xint16) otmp->oartifact); + observe_object(otmp); + otmp->known = otmp->bknown = otmp->rknown = 1; + set_cknown_lknown(otmp); /* set otmp->{cknown,lknown} if applicable */ if (otmp->otyp == EGG && otmp->corpsenm != NON_PM) learn_egg_type(otmp->corpsenm); } /* ggetobj callback routine; identify an object and give immediate feedback */ int -identify(otmp) -struct obj *otmp; +identify(struct obj *otmp) { fully_identify_obj(otmp); prinv((char *) 0, otmp, 0L); @@ -2288,9 +2656,8 @@ struct obj *otmp; } /* menu of unidentified objects; select and identify up to id_limit of them */ -STATIC_OVL void -menu_identify(id_limit) -int id_limit; +staticfn void +menu_identify(int id_limit) { menu_item *pick_list; int n, i, first = 1, tryct = 5; @@ -2300,8 +2667,8 @@ int id_limit; while (id_limit) { Sprintf(buf, "What would you like to identify %s?", first ? "first" : "next"); - n = query_objlist(buf, &invent, (SIGNAL_NOMENU | SIGNAL_ESCAPE - | USE_INVLET | INVORDER_SORT), + n = query_objlist(buf, &gi.invent, (SIGNAL_NOMENU | SIGNAL_ESCAPE + | USE_INVLET | INVORDER_SORT), &pick_list, PICK_ANY, not_fully_identified); if (n > 0) { @@ -2310,7 +2677,8 @@ int id_limit; for (i = 0; i < n; i++, id_limit--) (void) identify(pick_list[i].item.a_obj); free((genericptr_t) pick_list); - mark_synch(); /* Before we loop to pop open another menu */ + if (id_limit) + wait_synch(); /* Before we loop to pop open another menu */ first = 0; } else if (n == -2) { /* player used ESC to quit menu */ break; @@ -2327,8 +2695,7 @@ int id_limit; } /* count the unidentified items */ int -count_unidentified(objchn) -struct obj *objchn; +count_unidentified(struct obj *objchn) { int unid_cnt = 0; struct obj *obj; @@ -2341,23 +2708,23 @@ struct obj *objchn; /* dialog with user to identify a given number of items; 0 means all */ void -identify_pack(id_limit, learning_id) -int id_limit; -boolean learning_id; /* true if we just read unknown identify scroll */ +identify_pack( + int id_limit, + boolean learning_id) /* T: just read unknown identify scroll */ { struct obj *obj; - int n, unid_cnt = count_unidentified(invent); + int n, unid_cnt = count_unidentified(gi.invent); if (!unid_cnt) { - You("have already identified all %sof your possessions.", - learning_id ? "the rest " : ""); + You("have already identified %s of your possessions.", + !learning_id ? "all" : "the rest"); } else if (!id_limit || id_limit >= unid_cnt) { /* identify everything */ /* TODO: use fully_identify_obj and cornline/menu/whatever here */ - for (obj = invent; obj; obj = obj->nobj) { + for (obj = gi.invent; obj; obj = obj->nobj) { if (not_fully_identified(obj)) { (void) identify(obj); - if (unid_cnt == 1) + if (--unid_cnt < 1) break; } } @@ -2380,50 +2747,118 @@ boolean learning_id; /* true if we just read unknown identify scroll */ /* called when regaining sight; mark inventory objects which were picked up while blind as now having been seen */ void -learn_unseen_invent() +learn_unseen_invent(void) { struct obj *otmp; + boolean invupdated = FALSE; if (Blind) return; /* sanity check */ - for (otmp = invent; otmp; otmp = otmp->nobj) { - if (otmp->dknown) + for (otmp = gi.invent; otmp; otmp = otmp->nobj) { + if (otmp->dknown && (otmp->bknown || !Role_if(PM_CLERIC)) && + (otmp->oclass != SCROLL_CLASS || !Role_if(PM_ARCHEOLOGIST))) continue; /* already seen */ - /* set dknown, perhaps bknown (for priest[ess]) */ - (void) xname(otmp); + invupdated = TRUE; + /* xname() will set dknown, perhaps bknown (for priest[ess]); + result from xname() is immediately released for re-use */ + maybereleaseobuf(xname(otmp)); + addinv_core2(otmp); /* you react to seeing the object */ + /* * If object->eknown gets implemented (see learnwand(zap.c)), * handle deferred discovery here. */ } - update_inventory(); + if (invupdated) + update_inventory(); } /* persistent inventory window is maintained by interface code; 'update_inventory' used to be a macro for - (*windowprocs.win_update_inventory) but the restore hackery - was getting out of hand; this is now a central call point */ + (*windowprocs.win_update_inventory) but the restore hackery to suppress + screen updates was getting out of hand; this is now a central call point */ void -update_inventory() +update_inventory(void) { - if (restoring) + int save_suppress_price; + + if (!program_state.in_moveloop) /* not covered by suppress_map_output */ + return; + if (suppress_map_output()) /* despite name, used for perm_invent too */ return; /* - * Ought to check (windowprocs.wincap2 & WC2_PERM_INVENT) here.... + * Ought to check (windowprocs.wincap & WC_PERM_INVENT) here.... * * We currently don't skip this call when iflags.perm_invent is False * because curses uses that to disable a previous perm_invent window * (after toggle via 'O'; perhaps the options code should handle that). + * + * perm_invent might get updated while some code is avoiding price + * feedback during obj name formatting for messages. Temporarily + * force 'normal' formatting during the perm_invent update. (Cited + * example was an update triggered by change in invent gold when + * transferring some to shk during itemized billing. A previous fix + * attempt in the shop code handled it for unpaid items but not for + * paying for used-up shop items; that follows a different code path.) + */ + save_suppress_price = iflags.suppress_price; + iflags.suppress_price = 0; + (*windowprocs.win_update_inventory)(0); + iflags.suppress_price = save_suppress_price; +} + +/* the #perminv command - call interface's persistent inventory routine */ +int +doperminv(void) +{ + /* + * If persistent inventory window is enabled, interact with it. + * + * Depending on interface, might accept and execute one scrolling + * request (MENU_{FIRST,NEXT,PREVIOUS,LAST}_PAGE) then return, + * or might stay and handle multiple requests until user finishes + * (typically by typing or but that's up to interface). */ - (*windowprocs.win_update_inventory)(); + +#if 0 + /* [currently this would redraw the persistent inventory window + whether that's needed or not, so also reset any previous + scrolling; we don't want that if the interface only accepts + one scroll command at a time] */ + update_inventory(); /* make sure that it's up to date */ +#endif + + if ((windowprocs.wincap & WC_PERM_INVENT) == 0) { + /* [TODO? perhaps omit "by " if all the window ports + compiled into this binary lack support for perm_invent...] */ + pline("Persistent inventory display is not supported by '%s'.", + windowprocs.name); + + } else if (!iflags.perm_invent) { + pline( + "Persistent inventory ('perm_invent' option) is not presently enabled."); + + } else if (!gi.invent) { + /* [should this be left for the interface to decide?] */ + pline("Persistent inventory display is empty."); + + } else { + /* note: we used to request a scrolling key here and pass that to + (*win_update_inventory)(key), but that limited the functionality + and also cluttered message history with prompt and response so + just send non-zero and have the interface be responsible for it */ + (*windowprocs.win_update_inventory)(1); + + } /* iflags.perm_invent */ + + return ECMD_OK; } /* should of course only be called for things in invent */ -STATIC_OVL char -obj_to_let(obj) -struct obj *obj; +staticfn char +obj_to_let(struct obj *obj) { if (!flags.invlet_constant) { obj->invlet = NOINVSYM; @@ -2437,35 +2872,39 @@ struct obj *obj; * the current quantity. */ void -prinv(prefix, obj, quan) -const char *prefix; -struct obj *obj; -long quan; +prinv(const char *prefix, struct obj *obj, long quan) { + boolean total_of = (quan && (quan < obj->quan)); + char totalbuf[QBUFSZ]; + if (!prefix) prefix = ""; - pline("%s%s%s", prefix, *prefix ? " " : "", - xprname(obj, (char *) 0, obj_to_let(obj), TRUE, 0L, quan)); + + totalbuf[0] = '\0'; + if (total_of) + Snprintf(totalbuf, sizeof totalbuf, + " (%ld in total).", obj->quan); + pline("%s%s%s%s", prefix, *prefix ? " " : "", + xprname(obj, (char *) 0, obj_to_let(obj), !total_of, 0L, quan), + flags.verbose ? totalbuf : ""); } +DISABLE_WARNING_FORMAT_NONLITERAL + char * -xprname(obj, txt, let, dot, cost, quan) -struct obj *obj; -const char *txt; /* text to print instead of obj */ -char let; /* inventory letter */ -boolean dot; /* append period; (dot && cost => Iu) */ -long cost; /* cost (for inventory of unpaid or expended items) */ -long quan; /* if non-0, print this quantity, not obj->quan */ +xprname( + struct obj *obj, + const char *txt, /* text to print instead of obj */ + char let, /* inventory letter */ + boolean dot, /* append period; (dot && cost => Iu) */ + long cost, /* cost (for inventory of unpaid or expended items) */ + long quan) /* if non-0, print this quantity, not obj->quan */ { -#ifdef LINT /* handle static char li[BUFSZ]; */ - char li[BUFSZ]; -#else static char li[BUFSZ]; -#endif char suffix[80]; /* plenty of room for count and hallucinatory currency */ int sfxlen, txtlen; /* signed int for %*s formatting */ const char *fmt; - boolean use_invlet = (flags.invlet_constant + boolean use_invlet = (flags.invlet_constant && obj != NULL && let != CONTAINED_SYM && let != HANDS_SYM); long savequan = 0L; @@ -2480,8 +2919,10 @@ long quan; /* if non-0, print this quantity, not obj->quan */ * > Then the object is contained and doesn't have an inventory letter. */ fmt = "%c - %.*s%s"; - if (!txt) + if (!txt) { + assert(obj != NULL); txt = doname(obj); + } txtlen = (int) strlen(txt); if (cost != 0L || let == '*') { @@ -2512,12 +2953,59 @@ long quan; /* if non-0, print this quantity, not obj->quan */ return li; } -/* the 'i' command */ +RESTORE_WARNING_FORMAT_NONLITERAL + + +/* show some or all of inventory while allowing the picking of an item in + order to preform context-sensitive item action on it; always returns 'ok'; + invent subsets specified by the ')', '[', '(', '=', '"', or '*' commands + when they're invoked with the 'm' prefix (or without it for '*') */ +staticfn int +dispinv_with_action( + char *lets, /* list of invlet values to include */ + boolean use_inuse_ordering, /* affects sortloot() and header labels */ + const char *alt_label) /* alternate value for in-use "Accessories" */ +{ + struct obj *otmp, *nextobj; + const char *save_accessories = 0; + char c, save_sortloot = 0; + unsigned len = lets ? (unsigned) strlen(lets) : 0U; + boolean menumode = (len != 1 || iflags.menu_requested) ? TRUE : FALSE, + save_force_invmenu = iflags.force_invmenu; + + if (use_inuse_ordering) { + save_accessories = inuse_headers[4]; + save_sortloot = flags.sortloot; + + flags.sortloot = 'i'; /* checked by display_pickinv() */ + if (alt_label) + inuse_headers[4] = alt_label; + } + iflags.force_invmenu = FALSE; + + c = display_inventory(lets, menumode); + + if (use_inuse_ordering) { + flags.sortloot = save_sortloot; + inuse_headers[4] = save_accessories; + } + iflags.force_invmenu = save_force_invmenu; + + if (c && c != '\033') { + for (otmp = gi.invent; otmp; otmp = nextobj) { + nextobj = otmp->nobj; + if (otmp->invlet == c) + return itemactions(otmp); + } + } + return ECMD_OK; +} + +/* the #inventory command (not much left...) */ int -ddoinv() +ddoinv(void) { - (void) display_inventory((char *) 0, FALSE); - return 0; + return dispinv_with_action((char *) 0, FALSE, NULL); } /* @@ -2529,9 +3017,8 @@ ddoinv() * next unpaid object is returned. This routine recursively follows * containers. */ -STATIC_OVL struct obj * -find_unpaid(list, last_found) -struct obj *list, **last_found; +staticfn struct obj * +find_unpaid(struct obj *list, struct obj **last_found) { struct obj *obj; @@ -2553,58 +3040,78 @@ struct obj *list, **last_found; return (struct obj *) 0; } -/* for perm_invent when operating on a partial inventory display, so that - the persistent one doesn't get shrunk during filtering for item selection - then regrown to full inventory, possibly being resized in the process */ -static winid cached_pickinv_win = WIN_ERR; - void -free_pickinv_cache() +free_pickinv_cache(void) { - if (cached_pickinv_win != WIN_ERR) { - destroy_nhwindow(cached_pickinv_win); - cached_pickinv_win = WIN_ERR; + if (gc.cached_pickinv_win != WIN_ERR) { + destroy_nhwindow(gc.cached_pickinv_win); + gc.cached_pickinv_win = WIN_ERR; } } /* * Internal function used by display_inventory and getobj that can display - * inventory and return a count as well as a letter. If out_cnt is not null, - * any count returned from the menu selection is placed here. + * inventory and return a count as well as a letter. */ -STATIC_OVL char -display_pickinv(lets, xtra_choice, query, want_reply, out_cnt) -register const char *lets; -const char *xtra_choice; /* "fingers", pick hands rather than an object */ -const char *query; -boolean want_reply; -long *out_cnt; +staticfn char +display_pickinv( + const char *lets, /* non-compacted list of invlet values */ + const char *xtra_choice, /* non-object "bare hands" or "fingers" */ + const char *query, /* optional; prompt string for menu */ + boolean allowxtra, /* hands are allowed (maybe alternate) choice */ + boolean want_reply, /* True: select an item, False: just display */ + long *out_cnt) /* optional; count player entered when selecting an item */ { - static const char not_carrying_anything[] = "Not carrying anything"; - struct obj *otmp, wizid_fakeobj; - char ilet, ret; - char *invlet = flags.inv_order; - int n, classcount; - winid win; /* windows being used */ + static const char /* potential entries for perm_invent window */ + not_carrying_anything[] = "Not carrying anything", + not_using_anything[] = "Not using any items", + only_carrying_gold[] = "Only carrying gold"; + struct obj *otmp, wizid_fakeobj, inuse_fakeobj; + char ilet, ret, *formattedobj; + const char *invlet = flags.inv_order; + int n, classcount, inusecount = 0; + winid win; /* windows being used */ anything any; menu_item *selected; unsigned sortflags; Loot *sortedinvent, *srtinv; + int8_t prevorderclass; + boolean (*filter)(struct obj *) = (boolean (*)(OBJ_P)) 0; boolean wizid = (wizard && iflags.override_ID), gotsomething = FALSE; + int clr = NO_COLOR, menu_behavior = MENU_BEHAVE_STANDARD; + boolean show_gold = TRUE, inuse_only = FALSE, skipped_gold = FALSE, + doing_perm_invent = FALSE, save_flags_sortpack, + usextra = (xtra_choice && allowxtra); if (lets && !*lets) lets = 0; /* simplify tests: (lets) instead of (lets && *lets) */ - if (iflags.perm_invent && (lets || xtra_choice || wizid)) { +#ifdef DUMPLOG + if (iflags.in_dumplog) { + win = 0; /* passed to dump_putstr() which ignores it... */ + } else +#endif + if (lets || usextra || wizid || want_reply +#ifdef TTY_PERM_INVENT + /*|| !gi.in_sync_perminvent*/ +#endif + || WIN_INVEN == WIN_ERR) { /* partial inventory in perm_invent setting; don't operate on full inventory window, use an alternate one instead; create the first time needed and keep it for re-use as needed later */ - if (cached_pickinv_win == WIN_ERR) - cached_pickinv_win = create_nhwindow(NHW_MENU); - win = cached_pickinv_win; - } else + if (gc.cached_pickinv_win == WIN_ERR) + gc.cached_pickinv_win = create_nhwindow(NHW_MENU); + win = gc.cached_pickinv_win; + if (flags.sortloot == 'i') + inuse_only = TRUE; + } else { win = WIN_INVEN; - + menu_behavior = MENU_BEHAVE_PERMINV; + prepare_perminvent(win); + show_gold = ((wri_info.fromcore.invmode & (enum inv_modes) InvShowGold) != 0); + inuse_only = ((wri_info.fromcore.invmode & (enum inv_modes) InvInUse) != 0); + doing_perm_invent = TRUE; + } /* * Exit early if no inventory -- but keep going if we are doing * a permanent inventory update. We need to keep going so the @@ -2616,17 +3123,18 @@ long *out_cnt; * an issue if empty checks are done before hand and the call * to here is short circuited away. * - * 2: our count here is only to distinguish between 0 and 1 and - * more than 1; for the last one, we don't need a precise number. + * 2: our count here is only to distinguish between 0 or 1 or + * more than 1; for the last case, we don't need a precise number. * For perm_invent update we force 'more than 1'. */ - n = (iflags.perm_invent && !lets && !want_reply) ? 2 + n = (doing_perm_invent && !lets && !want_reply) ? 2 : lets ? (int) strlen(lets) - : !invent ? 0 : !invent->nobj ? 1 : 2; + : !gi.invent ? 0 : !gi.invent->nobj ? 1 : 2; /* for xtra_choice, there's another 'item' not included in initial 'n'; - for !lets (full invent) and for override_ID (wizard mode identify), - skip message_menu handling of single item even if item count was 1 */ - if (xtra_choice || (n == 1 && (!lets || iflags.override_ID))) + for !lets (full invent or inuse_only) and for override_ID (wizard + mode identify), skip message_menu handling of single item even if + item count was 1 */ + if (usextra || (n == 1 && (!lets || wizid))) ++n; if (n == 0) { @@ -2638,19 +3146,20 @@ long *out_cnt; if (!flags.invlet_constant) reassign(); - if (n == 1 && !iflags.force_invmenu) { + if (n == 1 && !iflags.force_invmenu && !iflags.menu_requested) { /* when only one item of interest, use pline instead of menus; we actually use a fake message-line menu in order to allow the user to perform selection at the --More-- prompt for tty */ ret = '\0'; - if (xtra_choice) { + if (usextra) { /* xtra_choice is "bare hands" (wield), "fingertip" (Engrave), - "nothing" (ready Quiver), or "fingers" (apply grease) */ + "nothing" (prepare Quiver), "fingers" (apply grease), or + "hands" (default) */ ret = message_menu(HANDS_SYM, PICK_ONE, xprname((struct obj *) 0, xtra_choice, HANDS_SYM, TRUE, 0L, 0L)); /* '-' */ } else { - for (otmp = invent; otmp; otmp = otmp->nobj) + for (otmp = gi.invent; otmp; otmp = otmp->nobj) if (!lets || otmp->invlet == lets[0]) break; if (otmp) @@ -2667,26 +3176,63 @@ long *out_cnt; sortflags = (flags.sortloot == 'f') ? SORTLOOT_LOOT : SORTLOOT_INVLET; if (flags.sortpack) sortflags |= SORTLOOT_PACK; - sortedinvent = sortloot(&invent, sortflags, FALSE, - (boolean FDECL((*), (OBJ_P))) 0); + save_flags_sortpack = flags.sortpack; +#ifdef TTY_PERM_INVENT + if (doing_perm_invent && WINDOWPORT(tty)) { + flags.sortpack = FALSE; + sortflags = SORTLOOT_INVLET; + } +#endif + if (inuse_only) { + flags.sortpack = FALSE; + sortflags = SORTLOOT_INUSE; /* override */ + filter = is_inuse; + if (!uwep) { + /* + * inuse_only and not wielding anything: insert "bare hands" + * into primary weapon slot. Unlike adding an extra menu + * entry for 'xtra_choice' at top of menu, we need an object + * in invent for it to be sorted into desired position. + * It will need custom formatting below. + */ + inuse_fakeobj = cg.zeroobj; /* STRANGE_OBJECT, ILLOBJ_CLASS */ + inuse_fakeobj.invlet = HANDS_SYM; /* '-' */ + inuse_fakeobj.owornmask = W_WEP; /* inuse_classify needs this */ + inuse_fakeobj.where = OBJ_INVENT; /* is_inuse filter needs this */ + inuse_fakeobj.nobj = gi.invent; + gi.invent = &inuse_fakeobj; + } + } + + sortedinvent = sortloot(&gi.invent, sortflags, FALSE, filter); + /* inuse_only: if we inserted bare hands as a fake weapon, remove them; + although the fake object will no longer be in invent, sortedinvent + will still contain a pointer to it */ + if (gi.invent == &inuse_fakeobj) { + gi.invent = inuse_fakeobj.nobj; + inuse_fakeobj.nobj = (struct obj *) 0; + /* if inuse_fakeobj is the only thing present in sortedinvent, get + rid of it in order to produce "not using any items" */ + if (sortedinvent[0].obj == &inuse_fakeobj && !sortedinvent[1].obj) + sortedinvent[0].obj = (struct obj *) 0; + } - start_menu(win); - any = zeroany; - if (wizard && iflags.override_ID) { + start_menu(win, menu_behavior); + any = cg.zeroany; + if (wizid) { int unid_cnt; char prompt[QBUFSZ]; - unid_cnt = count_unidentified(invent); + unid_cnt = count_unidentified(gi.invent); Sprintf(prompt, "Debug Identify"); /* 'title' rather than 'prompt' */ if (unid_cnt) Sprintf(eos(prompt), " -- unidentified or partially identified item%s", plur(unid_cnt)); - add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, prompt, MENU_UNSELECTED); + add_menu_str(win, prompt); if (!unid_cnt) { - add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, - "(all items are permanently identified already)", - MENU_UNSELECTED); + add_menu_str(win, + "(all items are permanently identified already)"); gotsomething = TRUE; } else { any.a_obj = &wizid_fakeobj; @@ -2695,49 +3241,93 @@ long *out_cnt; /* wiz_identify stuffed the wiz_identify command character (^I) into iflags.override_ID for our use as an accelerator; it could be ambiguous if player has assigned a letter to - the #wizidentify command, so include it as a group accelator + the #wizidentify command, so include it as a group accelerator but use '_' as the primary selector */ if (unid_cnt > 1) Sprintf(eos(prompt), " (%s for all)", visctrl(iflags.override_ID)); - add_menu(win, NO_GLYPH, &any, '_', iflags.override_ID, ATR_NONE, - prompt, MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, '_', iflags.override_ID, + ATR_NONE, clr, prompt, MENU_ITEMFLAGS_SKIPINVERT); gotsomething = TRUE; } - } else if (xtra_choice) { + } else if (usextra) { /* wizard override ID and xtra_choice are mutually exclusive */ if (flags.sortpack) - add_menu(win, NO_GLYPH, &any, 0, 0, iflags.menu_headings, - "Miscellaneous", MENU_UNSELECTED); + add_menu_heading(win, "Miscellaneous"); any.a_char = HANDS_SYM; /* '-' */ - add_menu(win, NO_GLYPH, &any, HANDS_SYM, 0, ATR_NONE, - xtra_choice, MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, HANDS_SYM, 0, ATR_NONE, + clr, xtra_choice, MENU_ITEMFLAGS_NONE); gotsomething = TRUE; } + nextclass: classcount = 0; + prevorderclass = 0; for (srtinv = sortedinvent; (otmp = srtinv->obj) != 0; ++srtinv) { - if (lets && !index(lets, otmp->invlet)) + int tmpglyph; + glyph_info tmpglyphinfo = nul_glyphinfo; + + /* for showing a set of specific letters, skip ones not in the set */ + if (lets && !strchr(lets, otmp->invlet)) continue; if (!flags.sortpack || otmp->oclass == *invlet) { if (wizid && !not_fully_identified(otmp)) continue; - any = zeroany; /* all bits zero */ - ilet = otmp->invlet; - if (flags.sortpack && !classcount) { - add_menu(win, NO_GLYPH, &any, 0, 0, iflags.menu_headings, - let_to_name(*invlet, FALSE, - (want_reply && iflags.menu_head_objsym)), - MENU_UNSELECTED); + if (inuse_only) { + /* for inuse-only, start with an extra header */ + if (!inusecount++) + add_menu_heading(win, doing_perm_invent ? "In use" + : "Inventory in use"); + } else if (doing_perm_invent && !show_gold) { + /* don't skip gold if it is quivered, even for !show_gold */ + if (otmp->invlet == GOLD_SYM && !otmp->owornmask) { + skipped_gold = TRUE; + continue; + } + } + /* maybe insert a class header */ + if (inuse_only ? (srtinv->orderclass != prevorderclass) + : (flags.sortpack && !classcount)) { + boolean withsym = (want_reply && iflags.menu_head_objsym); + const char *class_header = inuse_only + ? inuse_headers[(int) srtinv->orderclass] + : (const char *) let_to_name(*invlet, FALSE, withsym); + + add_menu_heading(win, class_header); classcount++; + prevorderclass = srtinv->orderclass; } + + ilet = otmp->invlet; + any = cg.zeroany; /* all bits zero */ if (wizid) any.a_obj = otmp; else any.a_char = ilet; - add_menu(win, obj_to_glyph(otmp, rn2_on_display_rng), &any, ilet, - wizid ? def_oc_syms[(int) otmp->oclass].sym : 0, - ATR_NONE, doname(otmp), MENU_UNSELECTED); + + if (otmp == &inuse_fakeobj) { + /* fake item to format as "bare|gloved hands" */ + char barehands[QBUFSZ]; + + /* like doname() below, makeplural() returns an obuf[] */ + formattedobj = makeplural(body_part(HAND)); + Sprintf(barehands, "%s %s (no weapon)", + uarmg ? "gloved" : "bare", formattedobj); + add_menu(win, &nul_glyphinfo, &any, ilet, 0, + ATR_NONE, clr, barehands, MENU_ITEMFLAGS_NONE); + } else { + /* normal inventory item */ + tmpglyph = obj_to_glyph(otmp, rn2_on_display_rng); + map_glyphinfo(0, 0, tmpglyph, 0U, &tmpglyphinfo); + formattedobj = doname(otmp); + add_menu(win, &tmpglyphinfo, &any, ilet, + wizid ? def_oc_syms[(int) otmp->oclass].sym : 0, + ATR_NONE, clr, formattedobj, MENU_ITEMFLAGS_NONE); + } + /* doname() uses a static pool of obuf[] output buffers and + we don't want inventory display to overwrite all of them, + so when we've used one we release it for re-use */ + maybereleaseobuf(formattedobj); gotsomething = TRUE; } } @@ -2749,33 +3339,50 @@ long *out_cnt; goto nextclass; } } - if (iflags.force_invmenu && lets && want_reply) { - any = zeroany; - add_menu(win, NO_GLYPH, &any, 0, 0, iflags.menu_headings, - "Special", MENU_UNSELECTED); - any.a_char = '*'; - add_menu(win, NO_GLYPH, &any, '*', 0, ATR_NONE, - "(list everything)", MENU_UNSELECTED); - gotsomething = TRUE; + if (save_flags_sortpack != flags.sortpack) + flags.sortpack = save_flags_sortpack; + + /* default for force_invmenu is a menu listing likely candidates; + add '*' for 'list all' as an extra choice unless the menu already + includes everything; when reissuing the menu after player has + picked '*', add '?' for 'list likely candidates' to reverse that */ + if (iflags.force_invmenu && want_reply) { + const char *menutext = NULL; + + any = cg.zeroany; + if ((allowxtra && !usextra) + || (lets && (int) strlen(lets) < inv_cnt(TRUE))) { + any.a_char = '*'; + menutext = "(list everything)"; + } else if (!lets) { + any.a_char = '?'; + menutext = "(list likely candidates)"; + } + if (menutext) { + add_menu_heading(win, "Special"); + add_menu(win, &nul_glyphinfo, &any, any.a_char, 0, ATR_NONE, clr, + menutext, MENU_ITEMFLAGS_NONE); + gotsomething = TRUE; /* menu isn't empty */ + } } unsortloot(&sortedinvent); - /* for permanent inventory where we intend to show everything but - nothing has been listed (because there isn't anyhing to list; - the n==0 case above gets skipped for perm_invent), put something - into the menu */ - if (iflags.perm_invent && !lets && !gotsomething) { - any = zeroany; - add_menu(win, NO_GLYPH, &any, 0, 0, 0, - not_carrying_anything, MENU_UNSELECTED); + /* for permanent inventory where nothing has been listed (because + there isn't anything applicable to list; the n==0 case above + gets skipped for perm_invent), put something into the menu */ + if (doing_perm_invent && !lets && !gotsomething) { + add_menu_str(win, inuse_only ? not_using_anything + : (!show_gold && skipped_gold) ? only_carrying_gold + : not_carrying_anything); want_reply = FALSE; } - end_menu(win, query && *query ? query : (char *) 0); + end_menu(win, (query && *query) ? query : (char *) 0); n = select_menu(win, wizid ? PICK_ANY : want_reply ? PICK_ONE : PICK_NONE, &selected); if (n > 0) { if (wizid) { + boolean all_id = FALSE; int i; /* identifying items will update perm_invent, calling this @@ -2787,11 +3394,17 @@ long *out_cnt; otmp = selected[i].item.a_obj; if (otmp == &wizid_fakeobj) { identify_pack(0, FALSE); + /* identify_pack() performs update_inventory() */ + all_id = TRUE; + break; } else { if (not_fully_identified(otmp)) (void) identify(otmp); + /* identify() does not perform update_inventory() */ } } + if (!all_id) + update_inventory(); } else { ret = selected[0].item.a_char; if (out_cnt) @@ -2812,53 +3425,80 @@ long *out_cnt; * was selected. */ char -display_inventory(lets, want_reply) -const char *lets; -boolean want_reply; +display_inventory(const char *lets, boolean want_reply) { + struct _cmd_queue *cmdq = cmdq_pop(); + + if (cmdq) { + if (cmdq->typ == CMDQ_KEY) { + struct obj *otmp; + + for (otmp = gi.invent; otmp; otmp = otmp->nobj) + if (otmp->invlet == cmdq->key + && (!lets || !*lets + || strchr(lets, + def_oc_syms[(int) otmp->oclass].sym))) { + free(cmdq); + return otmp->invlet; + } + } + + /* cmdq not a key, or did not find the object, abort */ + free(cmdq); + cmdq_clear(CQ_CANNED); + return '\0'; + } return display_pickinv(lets, (char *) 0, (char *) 0, - want_reply, (long *) 0); + FALSE, want_reply, (long *) 0); +} + +void +repopulate_perminvent(void) +{ + (void) display_pickinv(NULL, (char *) 0, (char *) 0, + FALSE, FALSE, (long *) 0); } /* * Show what is current using inventory letters. * */ -STATIC_OVL char -display_used_invlets(avoidlet) -char avoidlet; +staticfn char +display_used_invlets(char avoidlet) { struct obj *otmp; char ilet, ret = 0; char *invlet = flags.inv_order; - int n, classcount, invdone = 0; + int n, classcount, invdone = 0, tmpglyph; + glyph_info tmpglyphinfo = nul_glyphinfo; winid win; anything any; menu_item *selected; + int clr = NO_COLOR; - if (invent) { + if (gi.invent) { win = create_nhwindow(NHW_MENU); - start_menu(win); + start_menu(win, MENU_BEHAVE_STANDARD); while (!invdone) { - any = zeroany; /* set all bits to zero */ + any = cg.zeroany; /* set all bits to zero */ classcount = 0; - for (otmp = invent; otmp; otmp = otmp->nobj) { + for (otmp = gi.invent; otmp; otmp = otmp->nobj) { ilet = otmp->invlet; if (ilet == avoidlet) continue; if (!flags.sortpack || otmp->oclass == *invlet) { if (flags.sortpack && !classcount) { - any = zeroany; /* zero */ - add_menu(win, NO_GLYPH, &any, 0, 0, - iflags.menu_headings, - let_to_name(*invlet, FALSE, FALSE), - MENU_UNSELECTED); + any = cg.zeroany; /* zero */ + add_menu_heading(win, + let_to_name(*invlet, FALSE, FALSE)); classcount++; } any.a_char = ilet; - add_menu(win, obj_to_glyph(otmp, rn2_on_display_rng), - &any, ilet, 0, ATR_NONE, - doname(otmp), MENU_UNSELECTED); + tmpglyph = obj_to_glyph(otmp, rn2_on_display_rng); + map_glyphinfo(0, 0, tmpglyph, 0U, &tmpglyphinfo); + add_menu(win, &tmpglyphinfo, &any, ilet, 0, + ATR_NONE, clr, doname(otmp), + MENU_ITEMFLAGS_NONE); } } if (flags.sortpack && *++invlet) @@ -2883,8 +3523,7 @@ char avoidlet; * contained objects. */ int -count_unpaid(list) -struct obj *list; +count_unpaid(struct obj *list) { int count = 0; @@ -2906,16 +3545,13 @@ struct obj *list; * at some point: bknown is forced for priest[ess], like in xname(). */ int -count_buc(list, type, filterfunc) -struct obj *list; -int type; -boolean FDECL((*filterfunc), (OBJ_P)); +count_buc(struct obj *list, int type, boolean (*filterfunc)(OBJ_P)) { int count = 0; for (; list; list = list->nobj) { /* priests always know bless/curse state */ - if (Role_if(PM_PRIEST)) + if (Role_if(PM_CLERIC)) list->bknown = (list->oclass != COIN_CLASS); /* some actions exclude some or most items */ if (filterfunc && !(*filterfunc)(list)) @@ -2923,7 +3559,7 @@ boolean FDECL((*filterfunc), (OBJ_P)); /* coins are either uncursed or unknown based upon option setting */ if (list->oclass == COIN_CLASS) { - if (type == (iflags.goldX ? BUC_UNKNOWN : BUC_UNCURSED)) + if (type == (flags.goldX ? BUC_UNKNOWN : BUC_UNCURSED)) ++count; continue; } @@ -2941,10 +3577,10 @@ boolean FDECL((*filterfunc), (OBJ_P)); /* similar to count_buc(), but tallies all states at once rather than looking for a specific type */ void -tally_BUCX(list, by_nexthere, bcp, ucp, ccp, xcp, ocp) -struct obj *list; -boolean by_nexthere; -int *bcp, *ucp, *ccp, *xcp, *ocp; +tally_BUCX( + struct obj *list, + boolean by_nexthere, + int *bcp, int *ucp, int *ccp, int *xcp, int *ocp, int *jcp) { /* Future extensions: * Skip current_container when list is invent, uchain when @@ -2952,14 +3588,16 @@ int *bcp, *ucp, *ccp, *xcp, *ocp; * have a function again (it was a counter for having skipped gold, * but that's not skipped anymore). */ - *bcp = *ucp = *ccp = *xcp = *ocp = 0; + *bcp = *ucp = *ccp = *xcp = *ocp = *jcp = 0; for ( ; list; list = (by_nexthere ? list->nexthere : list->nobj)) { /* priests always know bless/curse state */ - if (Role_if(PM_PRIEST)) + if (Role_if(PM_CLERIC)) list->bknown = (list->oclass != COIN_CLASS); + if (list->pickup_prev) + ++(*jcp); /* coins are either uncursed or unknown based upon option setting */ if (list->oclass == COIN_CLASS) { - if (iflags.goldX) + if (flags.goldX) ++(*xcp); else ++(*ucp); @@ -2979,21 +3617,22 @@ int *bcp, *ucp, *ccp, *xcp, *ocp; /* count everything inside a container, or just shop-owned items inside */ long -count_contents(container, nested, quantity, everything, newdrop) -struct obj *container; -boolean nested, /* include contents of any nested containers */ - quantity, /* count all vs count separate stacks */ - everything, /* all objects vs only unpaid objects */ - newdrop; /* on floor, but hero-owned items haven't been marked - * no_charge yet and shop-owned items are still marked - * unpaid -- used when asking the player whether to sell */ +count_contents( + struct obj *container, + boolean nested, /* include contents of any nested containers */ + boolean quantity, /* count all vs count separate stacks */ + boolean everything, /* all objects vs only unpaid objects */ + boolean newdrop) /* on floor, but hero-owned items haven't + * been marked no_charge yet and shop-owned + * items are still marked unpaid -- used + * when asking the player whether to sell */ { struct obj *otmp, *topc; boolean shoppy = FALSE; long count = 0L; if (!everything && !newdrop) { - xchar x, y; + coordxy x, y; for (topc = container; topc->where == OBJ_CONTAINED; topc = topc->ocontainer) @@ -3011,26 +3650,29 @@ boolean nested, /* include contents of any nested containers */ return count; } -STATIC_OVL void -dounpaid() +staticfn void +dounpaid( + int count, /* unpaid items in inventory */ + int floorcount, /* unpaid items on floor (rare) */ + int buriedcount) /* unpaid items under the floor (extremely rare) */ { winid win; struct obj *otmp, *marker, *contnr; - register char ilet; + char ilet; char *invlet = flags.inv_order; - int classcount, count, num_so_far; + int classcount, num_so_far, xtracount; long cost, totcost; - count = count_unpaid(invent); otmp = marker = contnr = (struct obj *) 0; + xtracount = floorcount + buriedcount; - if (count == 1) { - otmp = find_unpaid(invent, &marker); + if (count == 1 && !xtracount) { + otmp = find_unpaid(gi.invent, &marker); contnr = unknwn_contnr_contents(otmp); } if (otmp && !contnr) { /* 1 item; use pline instead of popup menu */ - cost = unpaid_cost(otmp, FALSE); + cost = unpaid_cost(otmp, COST_NOCONTENTS); iflags.suppress_price++; /* suppress "(unpaid)" suffix */ pline1(xprname(otmp, distant_name(otmp, doname), carried(otmp) ? otmp->invlet : CONTAINED_SYM, @@ -3040,14 +3682,14 @@ dounpaid() } win = create_nhwindow(NHW_MENU); - cost = totcost = 0; + totcost = 0L; num_so_far = 0; /* count of # printed so far */ if (!flags.invlet_constant) reassign(); do { classcount = 0; - for (otmp = invent; otmp; otmp = otmp->nobj) { + for (otmp = gi.invent; otmp; otmp = otmp->nobj) { ilet = otmp->invlet; if (otmp->unpaid) { if (!flags.sortpack || otmp->oclass == *invlet) { @@ -3056,7 +3698,7 @@ dounpaid() classcount++; } - totcost += cost = unpaid_cost(otmp, FALSE); + totcost += cost = unpaid_cost(otmp, COST_NOCONTENTS); iflags.suppress_price++; /* suppress "(unpaid)" suffix */ putstr(win, 0, xprname(otmp, distant_name(otmp, doname), ilet, TRUE, cost, 0L)); @@ -3076,13 +3718,13 @@ dounpaid() * unpaid items. The top level inventory items have already * been listed. */ - for (otmp = invent; otmp; otmp = otmp->nobj) { + for (otmp = gi.invent; otmp; otmp = otmp->nobj) { if (Has_contents(otmp)) { long contcost = 0L; marker = (struct obj *) 0; /* haven't found any */ while (find_unpaid(otmp->cobj, &marker)) { - totcost += cost = unpaid_cost(marker, FALSE); + totcost += cost = unpaid_cost(marker, COST_NOCONTENTS); contcost += cost; if (otmp->cknown) { iflags.suppress_price++; /* suppress "(unpaid)" sfx */ @@ -3105,29 +3747,62 @@ dounpaid() } } - putstr(win, 0, ""); - putstr(win, 0, - xprname((struct obj *) 0, "Total:", '*', FALSE, totcost, 0L)); - display_nhwindow(win, FALSE); + if (count > 0) { + putstr(win, 0, ""); + putstr(win, 0, + xprname((struct obj *) 0, "Total:", '*', FALSE, totcost, 0L)); + } + + /* an unpaid item can be on the floor if dropped on the shop boundary + (then possibly moved all the way into the shop during wall repair); + one can be buried if it started that way and a pit was dug at its + spot then filled by a boulder (or perhaps a theme room with a pool + and an unpaid item moved into that by wall repair, then freezing) */ + if (xtracount > 0) { /* floorcount + buriedcount > 0 */ + char buf[BUFSZ]; + const char + *floorverb = (xtracount > 1) ? "are" : "is", + /* "under the floor" might actually be "under the floor + beneath a wall" when shop repair is involved but that seems + too nit-picky to bother trying to handle here (even more + extreme description-wise: "under the floor beneath the + door/doorway") */ + *where = (buriedcount == 0) ? "on the floor" + : (floorcount == 0) ? "under the floor" + : "on or under the floor"; + + if (!count) { + You("aren't carrying any unpaid items but there %s %d %s.", + floorverb, xtracount, where); + } else { + putstr(win, 0, ""); + Sprintf(buf, "(There %s %d more unpaid object%s %s.)", + floorverb, xtracount, plur(xtracount), where); + putstr(win, 0, buf); + } + } + + if (count > 0) + display_nhwindow(win, FALSE); destroy_nhwindow(win); + return; } -/* query objlist callback: return TRUE if obj type matches "this_type" */ -static int this_type; -STATIC_OVL boolean -this_type_only(obj) -struct obj *obj; +staticfn boolean +this_type_only(struct obj *obj) { - boolean res = (obj->oclass == this_type); + boolean res = (obj->oclass == gt.this_type); - if (obj->oclass == COIN_CLASS) { + if (gt.this_type == 'P') { + res = obj->pickup_prev; + } else if (obj->oclass == COIN_CLASS) { /* if filtering by bless/curse state, gold is classified as either unknown or uncursed based on user option setting */ - if (this_type && index("BUCX", this_type)) - res = (this_type == (iflags.goldX ? 'X' : 'U')); + if (gt.this_type && strchr("BUCX", gt.this_type)) + res = (gt.this_type == (flags.goldX ? 'X' : 'U')); } else { - switch (this_type) { + switch (gt.this_type) { case 'B': res = (obj->bknown && obj->blessed); break; @@ -3147,26 +3822,35 @@ struct obj *obj; return res; } -/* the 'I' command */ +/* the #inventtype command */ int -dotypeinv() +dotypeinv(void) { + static const char + prompt[] = "What type of object do you want an inventory of?"; char c = '\0'; int n, i = 0; - char *extra_types, types[BUFSZ]; - int class_count, oclass, unpaid_count, itemcount; - int bcnt, ccnt, ucnt, xcnt, ocnt; + char *extra_types, types[BUFSZ], title[QBUFSZ]; + const char *before = "", *after = ""; + int class_count, oclass, itemcount, + any_unpaid, u_carried, u_floor, u_buried; + int bcnt, ccnt, ucnt, xcnt, ocnt, jcnt; boolean billx = *u.ushops && doinvbill(0); menu_item *pick_list; boolean traditional = TRUE; - const char *prompt = "What type of object do you want an inventory of?"; - if (!invent && !billx) { + gt.this_type = 0; + gt.this_title = NULL; + if (!gi.invent && !billx) { You("aren't carrying anything."); - return 0; + goto doI_done; } - unpaid_count = count_unpaid(invent); - tally_BUCX(invent, FALSE, &bcnt, &ucnt, &ccnt, &xcnt, &ocnt); + title[0] = '\0'; + u_carried = count_unpaid(gi.invent); + u_floor = count_unpaid(fobj); + u_buried = count_unpaid(svl.level.buriedobjlist); + any_unpaid = u_carried + u_floor + u_buried; + tally_BUCX(gi.invent, FALSE, &bcnt, &ucnt, &ccnt, &xcnt, &ocnt, &jcnt); if (flags.menu_style != MENU_TRADITIONAL) { if (flags.menu_style == MENU_FULL @@ -3183,23 +3867,25 @@ dotypeinv() i |= BUC_CURSED; if (xcnt) i |= BUC_UNKNOWN; - n = query_category(prompt, invent, i, &pick_list, PICK_ONE); + if (jcnt) + i |= JUSTPICKED; + i |= INCLUDE_VENOM; + n = query_category(prompt, gi.invent, i, &pick_list, PICK_ONE); if (!n) - return 0; - this_type = c = pick_list[0].item.a_int; + goto doI_done; + gt.this_type = c = pick_list[0].item.a_int; free((genericptr_t) pick_list); } } if (traditional) { - /* collect a list of classes of objects carried, for use as a prompt - */ + /* collect list of classes of objects carried, for use as a prompt */ types[0] = 0; - class_count = collect_obj_classes(types, invent, FALSE, - (boolean FDECL((*), (OBJ_P))) 0, + class_count = collect_obj_classes(types, gi.invent, FALSE, + (boolean (*)(OBJ_P)) 0, &itemcount); - if (unpaid_count || billx || (bcnt + ccnt + ucnt + xcnt) != 0) + if (any_unpaid || billx || (bcnt + ccnt + ucnt + xcnt) != 0 || jcnt) types[class_count++] = ' '; - if (unpaid_count) + if (any_unpaid) types[class_count++] = 'u'; if (billx) types[class_count++] = 'x'; @@ -3211,11 +3897,13 @@ dotypeinv() types[class_count++] = 'C'; if (xcnt) types[class_count++] = 'X'; + if (jcnt) + types[class_count++] = 'P'; types[class_count] = '\0'; /* add everything not already included; user won't see these */ extra_types = eos(types); *extra_types++ = '\033'; - if (!unpaid_count) + if (!any_unpaid) *extra_types++ = 'u'; if (!billx) *extra_types++ = 'x'; @@ -3227,23 +3915,24 @@ dotypeinv() *extra_types++ = 'C'; if (!xcnt) *extra_types++ = 'X'; - *extra_types = '\0'; /* for index() */ + if (!jcnt) + *extra_types++ = 'P'; + *extra_types = '\0'; /* for strchr() */ for (i = 0; i < MAXOCLASSES; i++) - if (!index(types, def_oc_syms[i].sym)) { + if (!strchr(types, def_oc_syms[i].sym)) { *extra_types++ = def_oc_syms[i].sym; *extra_types = '\0'; } if (class_count > 1) { - c = yn_function(prompt, types, '\0'); - savech(c); + c = yn_function(prompt, types, '\0', TRUE); if (c == '\0') { clear_nhwindow(WIN_MESSAGE); - return 0; + goto doI_done; } } else { /* only one thing to itemize */ - if (unpaid_count) + if (any_unpaid) c = 'u'; else if (billx) c = 'x'; @@ -3256,75 +3945,102 @@ dotypeinv() (void) doinvbill(1); else pline("No used-up objects%s.", - unpaid_count ? " on your shopping bill" : ""); - return 0; + any_unpaid ? " on your shopping bill" : ""); + goto doI_done; } - if (c == 'u' || (c == 'U' && unpaid_count && !ucnt)) { - if (unpaid_count) - dounpaid(); + if (c == 'u' || (c == 'U' && any_unpaid && !ucnt)) { + if (any_unpaid) + dounpaid(u_carried, u_floor, u_buried); else You("are not carrying any unpaid objects."); - return 0; + goto doI_done; } - if (traditional) { - if (index("BUCX", c)) - oclass = c; /* not a class but understood by this_type_only() */ - else - oclass = def_char_to_objclass(c); /* change to object class */ - if (oclass == COIN_CLASS) - return doprgold(); - if (index(types, c) > index(types, '\033')) { - /* '> ESC' => hidden choice, something known not to be carried */ - const char *before = "", *after = ""; + if (strchr("BUCXP", c)) + oclass = c; /* not a class but understood by this_type_only() */ + else + oclass = def_char_to_objclass(c); /* change to object class */ +#if 0 + /* this used to be done for the 'if traditional' case but not for the + menu case; also unlike '$', 'I$' explicitly asks about inventory, + so we no longer handle coin class differently from other classes */ + if (oclass == COIN_CLASS) { + return doprgold(); + } +#endif + /* these are used for traditional when not applicable and also for + constructing a title to be used by query_objlist() */ + switch (c) { + case 'B': + before = "known to be blessed "; + break; + case 'U': + before = "known to be uncursed "; + break; + case 'C': + before = "known to be cursed "; + break; + case 'X': + after = " whose blessed/uncursed/cursed status is unknown"; + break; /* better phrasing is desirable */ + case 'P': + after = " that were just picked up"; + break; + default: + /* 'c' is an object class, because we've already handled + all the non-class letters which were put into 'types[]'; + could/should move object class names[] array from below + to somewhere above so that we can access it here (via + lcase(strcpy(classnamebuf, names[(int) c]))), but the + game-play value of doing so is low... */ + before = "such "; + break; + } - switch (c) { - case 'B': - before = "known to be blessed "; - break; - case 'U': - before = "known to be uncursed "; - break; - case 'C': - before = "known to be cursed "; - break; - case 'X': - after = " whose blessed/uncursed/cursed status is unknown"; - break; /* better phrasing is desirable */ - default: - /* 'c' is an object class, because we've already handled - all the non-class letters which were put into 'types[]'; - could/should move object class names[] array from below - to somewhere above so that we can access it here (via - lcase(strcpy(classnamebuf, names[(int) c]))), but the - game-play value of doing so is low... */ - before = "such "; - break; - } + if (traditional) { + if (strchr(types, c) > strchr(types, '\033')) { You("have no %sobjects%s.", before, after); - return 0; + goto doI_done; } - this_type = oclass; + gt.this_type = oclass; /* extra input for this_type_only() */ } - if (query_objlist((char *) 0, &invent, + if (strchr("BUCXP", c)) { + /* the before and after phrases for "you have no..." can both be + treated as mutually-exclusive suffices when creating a title */ + Sprintf(title, "Items %s", (before && *before) ? before : after); + /* get rid of trailing space from 'before' and double-space from + 'after's leading space */ + (void) mungspaces(title); + Strcat(title, ":"); /* after removing unwanted trailing space */ + gt.this_title = title; + } + + if (query_objlist((char *) 0, &gi.invent, ((flags.invlet_constant ? USE_INVLET : 0) - | INVORDER_SORT), - &pick_list, PICK_NONE, this_type_only) > 0) + | INVORDER_SORT | INCLUDE_VENOM), + &pick_list, PICK_ONE, this_type_only) > 0) { + struct obj *otmp = pick_list[0].item.a_obj; + free((genericptr_t) pick_list); - return 0; + (void) itemactions(otmp); /* always returns ECMD_OK */ + } + + doI_done: + gt.this_type = 0; + gt.this_title = NULL; + return ECMD_OK; } /* return a string describing the dungeon feature at if there is one worth mentioning at that location; otherwise null */ const char * -dfeature_at(x, y, buf) -int x, y; -char *buf; +dfeature_at(coordxy x, coordxy y, char *buf) { struct rm *lev = &levl[x][y]; int ltyp = lev->typ, cmap = -1; const char *dfeature = 0; static char altbuf[BUFSZ]; + stairway *stway = stairway_at(x, y); if (IS_DOOR(ltyp)) { switch (lev->doormask) { @@ -3351,31 +4067,20 @@ char *buf; else if (is_lava(x, y)) cmap = S_lava; /* "molten lava" */ else if (is_ice(x, y)) - cmap = S_ice; /* "ice" */ + dfeature = ice_descr(x, y, altbuf), cmap = -1; /* "ice" */ else if (is_pool(x, y)) dfeature = "pool of water"; else if (IS_SINK(ltyp)) cmap = S_sink; /* "sink" */ else if (IS_ALTAR(ltyp)) { Sprintf(altbuf, "%saltar to %s (%s)", - ((lev->altarmask & AM_SHRINE) - && (Is_astralevel(&u.uz) || Is_sanctum(&u.uz))) - ? "high " - : "", + (lev->altarmask & AM_SANCTUM) ? "high " : "", a_gname(), align_str(Amask2align(lev->altarmask & ~AM_SHRINE))); dfeature = altbuf; - } else if ((x == xupstair && y == yupstair) - || (x == sstairs.sx && y == sstairs.sy && sstairs.up)) - cmap = S_upstair; /* "staircase up" */ - else if ((x == xdnstair && y == ydnstair) - || (x == sstairs.sx && y == sstairs.sy && !sstairs.up)) - cmap = S_dnstair; /* "staircase down" */ - else if (x == xupladder && y == yupladder) - cmap = S_upladder; /* "ladder up" */ - else if (x == xdnladder && y == ydnladder) - cmap = S_dnladder; /* "ladder down" */ - else if (ltyp == DRAWBRIDGE_DOWN) + } else if (stway) { + dfeature = stairs_description(stway, altbuf, TRUE); + } else if (ltyp == DRAWBRIDGE_DOWN) cmap = S_vodbridge; /* "lowered drawbridge" */ else if (ltyp == DBWALL) cmap = S_vcdbridge; /* "raised drawbridge" */ @@ -3396,9 +4101,9 @@ char *buf; /* look at what is here; if there are many objects (pile_limit or more), don't show them unless obj_cnt is 0 */ int -look_here(obj_cnt, picked_some) -int obj_cnt; /* obj_cnt > 0 implies that autopickup is in progress */ -boolean picked_some; +look_here( + int obj_cnt, /* obj_cnt > 0 implies that autopickup is in progress */ + unsigned lookhere_flags) { struct obj *otmp; struct trap *trap; @@ -3406,12 +4111,15 @@ boolean picked_some; const char *dfeature = (char *) 0; char fbuf[BUFSZ], fbuf2[BUFSZ]; winid tmpwin; - boolean skip_objects, felt_cockatrice = FALSE; + boolean skip_objects, felt_cockatrice = FALSE, + picked_some = (lookhere_flags & LOOKHERE_PICKED_SOME) != 0, + /* skip 'dfeature' if caller used describe_decor() to show it */ + skip_dfeature = (lookhere_flags & LOOKHERE_SKIP_DFEATURE) != 0; /* default pile_limit is 5; a value of 0 means "never skip" (and 1 effectively forces "always skip") */ skip_objects = (flags.pile_limit > 0 && obj_cnt >= flags.pile_limit); - if (u.uswallow && u.ustuck) { + if (u.uswallow) { struct monst *mtmp = u.ustuck; /* @@ -3449,13 +4157,27 @@ boolean picked_some; } else { You("%s no objects here.", verb); } - return !!Blind; + return (!!Blind ? ECMD_TIME : ECMD_OK); + } + if (!skip_objects) { + NhRegion *reg; + char regbuf[QBUFSZ]; + + regbuf[0] = '\0'; + if ((reg = visible_region_at(u.ux, u.uy)) != 0) + Sprintf(regbuf, "a %s cloud", + reg_damg(reg) ? "poison gas" : "vapor"); + if ((trap = t_at(u.ux, u.uy)) != 0 && !trap->tseen) + trap = (struct trap *) NULL; + + if (reg || trap) + There("is %s%s%s here.", + reg ? regbuf : "", + (reg && trap) ? " and " : "", + trap ? an(trapname(trap->ttyp, FALSE)) : ""); } - if (!skip_objects && (trap = t_at(u.ux, u.uy)) && trap->tseen) - There("is %s here.", - an(defsyms[trap_to_defsym(trap->ttyp)].explanation)); - otmp = level.objects[u.ux][u.uy]; + otmp = svl.level.objects[u.ux][u.uy]; dfeature = dfeature_at(u.ux, u.uy, fbuf2); if (dfeature && !strcmp(dfeature, "pool of water") && Underwater) dfeature = 0; @@ -3466,71 +4188,95 @@ boolean picked_some; if (dfeature && !strncmp(dfeature, "altar ", 6)) { /* don't say "altar" twice, dfeature has more info */ You("try to feel what is here."); + } else if (SURFACE_AT(u.ux, u.uy) == ICE) { + /* using describe_decor() to handle ice is simpler than + replicating it in the conditional message construction */ + if (!flags.mention_decor || iflags.prev_decor == ICE) + force_decor(FALSE); + /* plain "ice" if blind and levitating, otherwise "solid ice" &c; + "There is [thin ]ice here. You try to feel what is on it." */ + You("try to feel what is on it."); + skip_dfeature = TRUE; /* ice already described */ } else { - const char *where = (Blind && !can_reach_floor(TRUE)) - ? "lying beneath you" - : "lying here on the ", - *onwhat = (Blind && !can_reach_floor(TRUE)) - ? "" - : surface(u.ux, u.uy); + boolean cant_reach = !can_reach_floor(TRUE); + const char *surf = surface(u.ux, u.uy), + *where = cant_reach ? "lying beneath you" + : "lying here on the ", + *onwhat = cant_reach ? "" : surf; You("try to feel what is %s%s.", drift ? "floating here" : where, drift ? "" : onwhat); + + if (dfeature && !drift && !strcmp(dfeature, surf)) + skip_dfeature = TRUE; /* terrain feature already identified */ } - if (dfeature && !drift && !strcmp(dfeature, surface(u.ux, u.uy))) - dfeature = 0; /* ice already identified */ - if (!can_reach_floor(TRUE)) { + trap = t_at(u.ux, u.uy); + if (!can_reach_floor(trap && is_pit(trap->ttyp))) { pline("But you can't reach it!"); - return 0; + return ECMD_OK; } } - if (dfeature) - Sprintf(fbuf, "There is %s here.", an(dfeature)); + if (dfeature && !skip_dfeature) { + const char *p; + int article = 1; /* 0 => none, 1 => a/an, 2 => the (not used here) */ + + /* "molten lava", "iron bars", and plain "ice" are handled as special + cases in an() but probably shouldn't be; don't rely on that */ + if (!strcmp(dfeature, "molten lava") + || !strcmp(dfeature, "iron bars") + || !strcmp(dfeature, "ice") + || !strncmp(dfeature, "frozen ", 7) /* ice while hallucinating */ + /* thawing ice ("solid ice", "thin ice", &c) */ + || ((p = strchr(dfeature, ' ')) != 0 && !strcmpi(p, " ice"))) + article = 0; + if (article == 1) + dfeature = an(dfeature); + + /* hardcoded "is" worked here because "iron bars" is actually + "set of iron bars"; use vtense() instead of relying on that */ + Sprintf(fbuf, "There %s %s here.", vtense(dfeature, "are"), dfeature); + } if (!otmp || is_lava(u.ux, u.uy) || (is_pool(u.ux, u.uy) && !Underwater)) { - if (dfeature) + if (dfeature && !skip_dfeature) pline1(fbuf); read_engr_at(u.ux, u.uy); /* Eric Backus */ if (!skip_objects && (Blind || !dfeature)) You("%s no objects here.", verb); - return !!Blind; + return (!!Blind ? ECMD_TIME : ECMD_OK); } /* we know there is something here */ if (skip_objects) { - if (dfeature) + if (dfeature && !skip_dfeature) pline1(fbuf); read_engr_at(u.ux, u.uy); /* Eric Backus */ if (obj_cnt == 1 && otmp->quan == 1L) There("is %s object here.", picked_some ? "another" : "an"); else There("are %s%s objects here.", - (obj_cnt < 5) - ? "a few" - : (obj_cnt < 10) - ? "several" - : "many", + (obj_cnt == 2) ? "two" + : (obj_cnt < 5) ? "a few" + : (obj_cnt < 10) ? "several" + : "many", picked_some ? " more" : ""); for (; otmp; otmp = otmp->nexthere) if (otmp->otyp == CORPSE && will_feel_cockatrice(otmp, FALSE)) { pline("%s %s%s.", - (obj_cnt > 1) - ? "Including" - : (otmp->quan > 1L) - ? "They're" - : "It's", + (obj_cnt > 1) ? "Including" + : (otmp->quan > 1L) ? "They're" + : "It's", corpse_xname(otmp, (const char *) 0, CXN_ARTICLE), - poly_when_stoned(youmonst.data) - ? "" - : ", unfortunately"); + poly_when_stoned(gy.youmonst.data) ? "" + : ", unfortunately"); feel_cockatrice(otmp, FALSE); break; } } else if (!otmp->nexthere) { /* only one object */ - if (dfeature) + if (dfeature && !skip_dfeature) pline1(fbuf); read_engr_at(u.ux, u.uy); /* Eric Backus */ You("%s here %s.", verb, doname_with_price(otmp)); @@ -3542,7 +4288,7 @@ boolean picked_some; display_nhwindow(WIN_MESSAGE, FALSE); tmpwin = create_nhwindow(NHW_MENU); - if (dfeature) { + if (dfeature && !skip_dfeature) { putstr(tmpwin, 0, fbuf); putstr(tmpwin, 0, ""); } @@ -3565,12 +4311,12 @@ boolean picked_some; feel_cockatrice(otmp, FALSE); read_engr_at(u.ux, u.uy); /* Eric Backus */ } - return !!Blind; + return (!!Blind ? ECMD_TIME : ECMD_OK); } -/* the ':' command - explicitly look at what is here, including all objects */ +/* #look command - explicitly look at what is here, including all objects */ int -dolook() +dolook(void) { int res; @@ -3578,16 +4324,14 @@ dolook() MSGTYPE={norep,noshow} "You see here" interfere with feedback from the look-here command */ hide_unhide_msgtypes(TRUE, MSGTYP_MASK_REP_SHOW); - res = look_here(0, FALSE); + res = look_here(0, LOOKHERE_NOFLAGS); /* restore normal msgtype handling */ hide_unhide_msgtypes(FALSE, MSGTYP_MASK_REP_SHOW); return res; } boolean -will_feel_cockatrice(otmp, force_touch) -struct obj *otmp; -boolean force_touch; +will_feel_cockatrice(struct obj *otmp, boolean force_touch) { if ((Blind || force_touch) && !uarmg && !Stone_resistance && (otmp->otyp == CORPSE && touch_petrifies(&mons[otmp->corpsenm]))) @@ -3596,9 +4340,7 @@ boolean force_touch; } void -feel_cockatrice(otmp, force_touch) -struct obj *otmp; -boolean force_touch; +feel_cockatrice(struct obj *otmp, boolean force_touch) { char kbuf[BUFSZ]; @@ -3606,7 +4348,7 @@ boolean force_touch; /* "the corpse" */ Strcpy(kbuf, corpse_xname(otmp, (const char *) 0, CXN_PFX_THE)); - if (poly_when_stoned(youmonst.data)) + if (poly_when_stoned(gy.youmonst.data)) You("touched %s with your bare %s.", kbuf, makeplural(body_part(HAND))); else @@ -3618,13 +4360,15 @@ boolean force_touch; } } +/* 'obj' is being placed on the floor; if it can merge with something that + is already there, combine them and discard obj as a separate object */ void -stackobj(obj) -struct obj *obj; +stackobj(struct obj *obj) { struct obj *otmp; - for (otmp = level.objects[obj->ox][obj->oy]; otmp; otmp = otmp->nexthere) + for (otmp = svl.level.objects[obj->ox][obj->oy]; otmp; + otmp = otmp->nexthere) if (otmp != obj && merged(&obj, &otmp)) break; return; @@ -3632,10 +4376,11 @@ struct obj *obj; /* returns TRUE if obj & otmp can be merged; used in invent.c and mkobj.c */ boolean -mergable(otmp, obj) -register struct obj *otmp, *obj; +mergable( + struct obj *otmp, /* potential 'into' stack */ + struct obj *obj) /* 'combine' stack */ { - int objnamelth = 0, otmpnamelth = 0; + size_t objnamelth = 0, otmpnamelth = 0; /* fail if already the same object, if different types, if either is explicitly marked to prevent merge, or if not mergable in general */ @@ -3647,10 +4392,21 @@ register struct obj *otmp, *obj; if (obj->oclass == COIN_CLASS) return TRUE; - if (obj->bypass != otmp->bypass - || obj->cursed != otmp->cursed || obj->blessed != otmp->blessed) + if (obj->cursed != otmp->cursed || obj->blessed != otmp->blessed) return FALSE; + if (obj->how_lost == LOST_EXPLODING + || otmp->how_lost == LOST_EXPLODING) + return FALSE; + if (otmp->how_lost != LOST_NONE && (obj->how_lost != otmp->how_lost)) + return FALSE; +#if 0 /* don't require 'bypass' to match; that results in items dropped + * via 'D' not stacking with compatible items already on the floor; + * caller who wants that behavior should use 'nomerge' instead */ + if (obj->bypass != otmp->bypass) + return FALSE; +#endif + if (obj->globby) return TRUE; /* Checks beyond this point either aren't applicable to globs @@ -3667,14 +4423,15 @@ register struct obj *otmp, *obj; return FALSE; if (obj->dknown != otmp->dknown - || (obj->bknown != otmp->bknown && !Role_if(PM_PRIEST)) + || (obj->bknown != otmp->bknown && !Role_if(PM_CLERIC) && + (Blind || Hallucination)) || obj->oeroded != otmp->oeroded || obj->oeroded2 != otmp->oeroded2 || obj->greased != otmp->greased) return FALSE; - if ((obj->oclass == WEAPON_CLASS || obj->oclass == ARMOR_CLASS) + if ((erosion_matters(obj)) && (obj->oerodeproof != otmp->oerodeproof - || obj->rknown != otmp->rknown)) + || (obj->rknown != otmp->rknown && (Blind || Hallucination)))) return FALSE; if (obj->otyp == CORPSE || obj->otyp == EGG || obj->otyp == TIN) { @@ -3701,63 +4458,124 @@ register struct obj *otmp, *obj; if (obj->unpaid && !same_price(obj, otmp)) return FALSE; + /* some additional information is always incompatible */ + if (has_omonst(obj) || has_omid(obj) + || has_omonst(otmp) || has_omid(otmp)) + return FALSE; + /* if they have names, make sure they're the same */ objnamelth = strlen(safe_oname(obj)); otmpnamelth = strlen(safe_oname(otmp)); if ((objnamelth != otmpnamelth && ((objnamelth && otmpnamelth) || obj->otyp == CORPSE)) || (objnamelth && otmpnamelth - && strncmp(ONAME(obj), ONAME(otmp), objnamelth))) + /* verify pointers before deref for static analyzer */ + && has_oname(obj) && has_oname(otmp) + && strncmp(ONAME(obj), ONAME(otmp), objnamelth))) return FALSE; - /* for the moment, any additional information is incompatible */ - if (has_omonst(obj) || has_omid(obj) || has_olong(obj) || has_omonst(otmp) - || has_omid(otmp) || has_olong(otmp)) + /* if one has an attached mail command, other must have same command */ + if (!has_omailcmd(obj) ? has_omailcmd(otmp) + : (!has_omailcmd(otmp) || strcmp(OMAILCMD(obj), OMAILCMD(otmp)) != 0)) return FALSE; +#ifdef MAIL_STRUCTURES + if (obj->otyp == SCR_MAIL + /* wished or bones mail and hand written stamped scrolls + each have two flavors; spe keeps them separate from each + other but we want to keep their flavors separate too */ + && obj->spe > 0 && (obj->o_id % 2) != (otmp->o_id % 2)) + return FALSE; +#endif + + /* should be moot since matching artifacts wouldn't be unique */ if (obj->oartifact != otmp->oartifact) return FALSE; - if (obj->known == otmp->known || !objects[otmp->otyp].oc_uses_known) { - return (boolean) objects[obj->otyp].oc_merge; - } else + if (obj->known != otmp->known && (Blind || Hallucination)) return FALSE; + + return TRUE; } -/* the '$' command */ +/* the #showgold command */ int -doprgold() +doprgold(void) { - /* the messages used to refer to "carrying gold", but that didn't - take containers into account */ - long umoney = money_cnt(invent); + /* Command takes containers into account. */ + long umoney = money_cnt(gi.invent); - if (!umoney) - Your("wallet is empty."); - else - Your("wallet contains %ld %s.", umoney, currency(umoney)); + /* Only list the money you know about. Guards and shopkeepers + can somehow tell if there is any gold anywhere on your + person, but you have no such preternatural gold-sense. */ + long hmoney = hidden_gold(FALSE); + + if (flags.verbose) { + char buf[BUFSZ]; + + if (!umoney) { + Strcpy(buf, "Your wallet is empty"); + } else { + Sprintf(buf, "Your wallet contains %ld %s", + umoney, currency(umoney)); + } + if (hmoney) { + Sprintf(eos(buf), + ", %s you have %ld %s stashed away in your pack", + umoney ? "and" : "but", hmoney, + umoney ? "more" : currency(hmoney)); + } + pline("%s.", buf); + } else { + long total = umoney + hmoney; + if (total) + You("are carrying a total of %ld %s.", total, currency(total)); + else + You("have no money."); + } shopper_financial_report(); - return 0; + + if (umoney && iflags.menu_requested) { + char dollarsign[] = "$"; + + /* mustn't use TRUE or gold wouldn't show up unless it was quivered */ + (void) dispinv_with_action(dollarsign, FALSE, NULL); + } + + return ECMD_OK; } -/* the ')' command */ +/* the #seeweapon command */ int -doprwep() +doprwep(void) { if (!uwep) { - You("are empty %s.", body_part(HANDED)); - } else { + You("are %s.", empty_handed()); + } else if (!iflags.menu_requested) { prinv((char *) 0, uwep, 0L); if (u.twoweap) prinv((char *) 0, uswapwep, 0L); + } else { + char lets[4]; /* 4: uwep, uswapwep, uquiver, terminator */ + int ct = 0; + + /* obj_to_let() will assign letters to all of invent if necessary + (for '!fixinv') so doesn't need to be repeated once called here */ + lets[ct++] = obj_to_let(uwep); + if (uswapwep) + lets[ct++] = uswapwep->invlet; + if (uquiver) + lets[ct++] = uquiver->invlet; + lets[ct] = '\0'; + + (void) dispinv_with_action(lets, TRUE, NULL); } - return 0; + return ECMD_OK; } /* caller is responsible for checking !wearing_armor() */ -STATIC_OVL void -noarmor(report_uskin) -boolean report_uskin; +staticfn void +noarmor(boolean report_uskin) { if (!uskin || !report_uskin) { You("are not wearing any armor."); @@ -3778,12 +4596,10 @@ boolean report_uskin; } } -/* the '[' command */ +/* the #seearmor command */ int -doprarm() +doprarm(void) { - char lets[8]; - register int ct = 0; /* * Note: players sometimes get here by pressing a function key which * transmits ''ESC [ '' rather than by pressing '['; @@ -3793,118 +4609,161 @@ doprarm() if (!wearing_armor()) { noarmor(TRUE); } else { - if (uarmu) - lets[ct++] = obj_to_let(uarmu); + char lets[8]; /* 8: up to 7 pieces of armor plus terminator */ + int ct = 0; + + /* obj_to_let() will assign letters to all of invent if necessary + (for '!fixinv') so doesn't need to be repeated once called, but + each armor slot doesn't know whether any that precede have made + that call so just do it for each one; use SORTPACK_INUSE order */ if (uarm) lets[ct++] = obj_to_let(uarm); if (uarmc) lets[ct++] = obj_to_let(uarmc); - if (uarmh) - lets[ct++] = obj_to_let(uarmh); if (uarms) lets[ct++] = obj_to_let(uarms); + if (uarmh) + lets[ct++] = obj_to_let(uarmh); if (uarmg) lets[ct++] = obj_to_let(uarmg); if (uarmf) lets[ct++] = obj_to_let(uarmf); + if (uarmu) + lets[ct++] = obj_to_let(uarmu); lets[ct] = 0; - (void) display_inventory(lets, FALSE); + + (void) dispinv_with_action(lets, TRUE, NULL); } - return 0; + return ECMD_OK; } -/* the '=' command */ +/* the #seerings command */ int -doprring() +doprring(void) { - if (!uleft && !uright) + if (!uleft && !uright) { You("are not wearing any rings."); - else { - char lets[3]; - register int ct = 0; + } else { + char lets[3]; /* 3: uright, uleft, terminator */ + boolean use_inuse_mode = FALSE; + int ct = 0; - if (uleft) - lets[ct++] = obj_to_let(uleft); - if (uright) + /* if either ring is a meat ring, switch to use_inuse_mode in order + to label it/them as "Rings" rather than "Comestibles" */ + if (uright) { lets[ct++] = obj_to_let(uright); - lets[ct] = 0; - (void) display_inventory(lets, FALSE); + if (uright->oclass != RING_CLASS) + use_inuse_mode = TRUE; + } + if (uleft) { + lets[ct++] = obj_to_let(uleft); + if (uleft->oclass != RING_CLASS) + use_inuse_mode = TRUE; + } + lets[ct] = '\0'; + /* also switch to use_inuse_mode if there are two rings or player + used the 'm' prefix */ + if (ct > 1 || iflags.menu_requested) + use_inuse_mode = TRUE; + + (void) dispinv_with_action(lets, use_inuse_mode, + /* note; alternate label will be ignored + if 'use_inuse_mode' is False */ + (ct == 1) ? "Ring" : "Rings"); } - return 0; + return ECMD_OK; } -/* the '"' command */ +/* the #seeamulet command */ int -dopramulet() +dopramulet(void) { - if (!uamul) + if (!uamul) { You("are not wearing an amulet."); - else - prinv((char *) 0, uamul, 0L); - return 0; + } else { + char lets[2]; + + /* using display_inventory() instead of prinv() allows player + to use 'm "' to force and menu and be able to choose amulet + in order to perform a context-sensitive item action */ + lets[0] = obj_to_let(uamul), lets[1] = '\0'; + + (void) dispinv_with_action(lets, TRUE, "Amulet"); + } + return ECMD_OK; } -STATIC_OVL boolean -tool_in_use(obj) -struct obj *obj; +/* is 'obj' a tool that's in use? can't simply check obj->owornmask */ +staticfn boolean +tool_being_used(struct obj *obj) { + /* + * [Should this also include lit potions of oil? They're not tools + * but they are "in use" without being noticeable via obj->owornmask.] + */ if ((obj->owornmask & (W_TOOL | W_SADDLE)) != 0L) return TRUE; if (obj->oclass != TOOL_CLASS) return FALSE; + /* [don't actually need to check uwep here; caller catches it] */ return (boolean) (obj == uwep || obj->lamplit || (obj->otyp == LEASH && obj->leashmon)); } -/* the '(' command */ +/* the #seetools command */ int -doprtool() +doprtool(void) { struct obj *otmp; int ct = 0; - char lets[52 + 1]; + char lets[invlet_basic + 1]; - for (otmp = invent; otmp; otmp = otmp->nobj) - if (tool_in_use(otmp)) + for (otmp = gi.invent; otmp; otmp = otmp->nobj) + if (tool_being_used(otmp)) { + /* we could be carrying more than 52 items; theoretically they + might all be lit candles so avoid potential lets[] overflow */ + if (ct >= (int) sizeof lets - 1) + break; lets[ct++] = obj_to_let(otmp); + } lets[ct] = '\0'; if (!ct) You("are not using any tools."); else - (void) display_inventory(lets, FALSE); - return 0; + (void) dispinv_with_action(lets, TRUE, NULL); + return ECMD_OK; } -/* '*' command; combines the ')' + '[' + '=' + '"' + '(' commands; +/* the #seeall command; combines the ')' + '[' + '=' + '"' + '(' commands; show inventory of all currently wielded, worn, or used objects */ int -doprinuse() +doprinuse(void) { struct obj *otmp; int ct = 0; - char lets[52 + 1]; - for (otmp = invent; otmp; otmp = otmp->nobj) - if (is_worn(otmp) || tool_in_use(otmp)) - lets[ct++] = obj_to_let(otmp); - lets[ct] = '\0'; + /* no longer need to collect letters; sortloot() takes care of it, but + still want to count far enough to know whether anything is in use */ + for (otmp = gi.invent; otmp; otmp = otmp->nobj) + if (is_inuse(otmp)) { + ++ct; + break; + } if (!ct) You("are not wearing or wielding anything."); else - (void) display_inventory(lets, FALSE); - return 0; + (void) dispinv_with_action((char *) 0, TRUE, NULL); + return ECMD_OK; } /* * uses up an object that's on the floor, charging for it as necessary */ void -useupf(obj, numused) -register struct obj *obj; -long numused; +useupf(struct obj *obj, long numused) { - register struct obj *otmp; - boolean at_u = (obj->ox == u.ux && obj->oy == u.uy); + struct obj *otmp; + boolean at_u = u_at(obj->ox, obj->oy); /* burn_floor_objects() keeps an object pointer that it tries to * useupf() multiple times, so obj must survive if plural */ @@ -3912,36 +4771,33 @@ long numused; otmp = splitobj(obj, numused); else otmp = obj; - if (costly_spot(otmp->ox, otmp->oy)) { - if (index(u.urooms, *in_rooms(otmp->ox, otmp->oy, 0))) + if (!svc.context.mon_moving && costly_spot(otmp->ox, otmp->oy)) { + if (strchr(u.urooms, *in_rooms(otmp->ox, otmp->oy, 0))) addtobill(otmp, FALSE, FALSE, FALSE); else (void) stolen_value(otmp, otmp->ox, otmp->oy, FALSE, FALSE); } delobj(otmp); - if (at_u && u.uundetected && hides_under(youmonst.data)) - (void) hideunder(&youmonst); + if (at_u && u.uundetected && hides_under(gy.youmonst.data)) + (void) hideunder(&gy.youmonst); } /* * Conversion from a class to a string for printing. * This must match the object class order. */ -STATIC_VAR NEARDATA const char *names[] = { +static NEARDATA const char *names[] = { 0, "Illegal objects", "Weapons", "Armor", "Rings", "Amulets", "Tools", "Comestibles", "Potions", "Scrolls", "Spellbooks", "Wands", "Coins", "Gems/Stones", "Boulders/Statues", "Iron balls", "Chains", "Venoms" }; -STATIC_VAR NEARDATA const char oth_symbols[] = { CONTAINED_SYM, '\0' }; -STATIC_VAR NEARDATA const char *oth_names[] = { "Bagged/Boxed items" }; +static NEARDATA const char oth_symbols[] = { CONTAINED_SYM, '\0' }; +static NEARDATA const char *oth_names[] = { "Bagged/Boxed items" }; -STATIC_VAR NEARDATA char *invbuf = (char *) 0; -STATIC_VAR NEARDATA unsigned invbufsiz = 0; +DISABLE_WARNING_FORMAT_NONLITERAL char * -let_to_name(let, unpaid, showsym) -char let; -boolean unpaid, showsym; +let_to_name(char let, boolean unpaid, boolean showsym) { const char *ocsymfmt = " ('%c')"; const int invbuf_sympadding = 8; /* arbitrary */ @@ -3952,77 +4808,128 @@ boolean unpaid, showsym; if (oclass) class_name = names[oclass]; - else if ((pos = index(oth_symbols, let)) != 0) + else if ((pos = strchr(oth_symbols, let)) != 0) class_name = oth_names[pos - oth_symbols]; else - class_name = names[0]; - - len = strlen(class_name) + (unpaid ? sizeof "unpaid_" : sizeof "") - + (oclass ? (strlen(ocsymfmt) + invbuf_sympadding) : 0); - if (len > invbufsiz) { - if (invbuf) - free((genericptr_t) invbuf); - invbufsiz = len + 10; /* add slop to reduce incremental realloc */ - invbuf = (char *) alloc(invbufsiz); + class_name = names[ILLOBJ_CLASS]; + + len = Strlen(class_name) + (unpaid ? sizeof "unpaid_" : sizeof "") + + (oclass ? (Strlen(ocsymfmt) + invbuf_sympadding) : 0); + if (len > gi.invbufsiz) { + if (gi.invbuf) + free((genericptr_t) gi.invbuf); + gi.invbufsiz = len + 10; /* add slop to reduce incremental realloc */ + gi.invbuf = (char *) alloc(gi.invbufsiz); } if (unpaid) - Strcat(strcpy(invbuf, "Unpaid "), class_name); + Strcat(strcpy(gi.invbuf, "Unpaid "), class_name); else - Strcpy(invbuf, class_name); + Strcpy(gi.invbuf, class_name); if ((oclass != 0) && showsym) { - char *bp = eos(invbuf); - int mlen = invbuf_sympadding - strlen(class_name); + char *bp = eos(gi.invbuf); + int mlen = invbuf_sympadding - Strlen(class_name); while (--mlen > 0) { *bp = ' '; bp++; } *bp = '\0'; - Sprintf(eos(invbuf), ocsymfmt, def_oc_syms[oclass].sym); + Sprintf(eos(gi.invbuf), ocsymfmt, def_oc_syms[oclass].sym); } - return invbuf; + return gi.invbuf; } +RESTORE_WARNING_FORMAT_NONLITERAL + /* release the static buffer used by let_to_name() */ void -free_invbuf() +free_invbuf(void) { - if (invbuf) - free((genericptr_t) invbuf), invbuf = (char *) 0; - invbufsiz = 0; + if (gi.invbuf) + free((genericptr_t) gi.invbuf), gi.invbuf = (char *) 0; + gi.invbufsiz = 0; } /* give consecutive letters to every item in inventory (for !fixinv mode); gold is always forced to '$' slot at head of list */ void -reassign() +reassign(void) { int i; struct obj *obj, *prevobj, *goldobj; /* first, remove [first instance of] gold from invent, if present */ prevobj = goldobj = 0; - for (obj = invent; obj; prevobj = obj, obj = obj->nobj) + for (obj = gi.invent; obj; prevobj = obj, obj = obj->nobj) if (obj->oclass == COIN_CLASS) { goldobj = obj; if (prevobj) prevobj->nobj = goldobj->nobj; else - invent = goldobj->nobj; + gi.invent = goldobj->nobj; break; } /* second, re-letter the rest of the list */ - for (obj = invent, i = 0; obj; obj = obj->nobj, i++) + for (obj = gi.invent, i = 0; obj; obj = obj->nobj, i++) obj->invlet = (i < 26) ? ('a' + i) : (i < 52) ? ('A' + i - 26) : NOINVSYM; /* third, assign gold the "letter" '$' and re-insert it at head */ if (goldobj) { goldobj->invlet = GOLD_SYM; - goldobj->nobj = invent; - invent = goldobj; + goldobj->nobj = gi.invent; + gi.invent = goldobj; } if (i >= 52) i = 52 - 1; - lastinvnr = i; + gl.lastinvnr = i; +} + +/* invent gold sanity check; used by doorganize() to control how getobj() + deals with gold and also by wizard mode sanity_check() */ +boolean +check_invent_gold(const char *why) /* 'why' == caller in case of warning */ +{ + struct obj *otmp; + int goldstacks = 0, wrongslot = 0; + + /* there should be at most one stack of gold in invent, in slot '$' */ + for (otmp = gi.invent; otmp; otmp = otmp->nobj) + if (otmp->oclass == COIN_CLASS) { + ++goldstacks; + if (otmp->invlet != GOLD_SYM) + ++wrongslot; + } + + if (goldstacks > 1 || wrongslot > 0) { + impossible("%s: %s%s%s", why, + (wrongslot > 1) ? "gold in wrong slots" + : (wrongslot > 0) ? "gold in wrong slot" + : "", + (wrongslot > 0 && goldstacks > 1) ? " and " : "", + (goldstacks > 1) ? "multiple gold stacks" : ""); + return TRUE; /* gold can be #adjusted */ + } + + return FALSE; /* gold can't be #adjusted */ +} + +/* normal getobj callback for item to #adjust; excludes gold */ +staticfn int +adjust_ok(struct obj *obj) +{ + if (!obj || obj->oclass == COIN_CLASS) + return GETOBJ_EXCLUDE; + + return GETOBJ_SUGGEST; +} + +/* getobj callback for item to #adjust if gold is wonky; allows gold */ +staticfn int +adjust_gold_ok(struct obj *obj) +{ + if (!obj) + return GETOBJ_EXCLUDE; + + return GETOBJ_SUGGEST; } /* #adjust command @@ -4065,57 +4972,125 @@ reassign() * However, when splitting results in a merger, the name of the * destination overrides that of the source, even if destination * is unnamed and source is named. + * + * Gold is only a candidate to adjust if we've somehow managed + * to get multiple stacks and/or it is in a slot other than '$'. + * Specifying a count to split it into two stacks is not allowed. */ int -doorganize() /* inventory organizer by Del Lamb */ +doorganize(void) /* inventory organizer by Del Lamb */ { - struct obj *obj, *otmp, *splitting, *bumped; - int ix, cur, trycnt, goldstacks; - char let; -#define GOLD_INDX 0 -#define GOLD_OFFSET 1 -#define OVRFLW_INDX (GOLD_OFFSET + 52) /* past gold and 2*26 letters */ - char lets[1 + 52 + 1 + 1]; /* room for '$a-zA-Z#\0' */ - char qbuf[QBUFSZ]; - char allowall[4]; /* { ALLOW_COUNT, ALL_CLASSES, 0, 0 } */ - char *objname, *otmpname; - const char *adj_type; - boolean ever_mind = FALSE, collect; + int (*adjust_filter)(struct obj *); + struct obj *obj; /* when no invent, or just gold in '$' slot, there's nothing to adjust */ - if (!invent || (invent->oclass == COIN_CLASS - && invent->invlet == GOLD_SYM && !invent->nobj)) { + if (!gi.invent || (gi.invent->oclass == COIN_CLASS + && gi.invent->invlet == GOLD_SYM && !gi.invent->nobj)) { You("aren't carrying anything %s.", - !invent ? "to adjust" : "adjustable"); - return 0; + !gi.invent ? "to adjust" : "adjustable"); + return ECMD_OK; } if (!flags.invlet_constant) reassign(); + + /* filter passed to getobj() depends upon gold sanity */ + adjust_filter = check_invent_gold("adjust") ? adjust_gold_ok : adjust_ok; + /* get object the user wants to organize (the 'from' slot) */ - allowall[0] = ALLOW_COUNT; - allowall[1] = ALL_CLASSES; - allowall[2] = '\0'; - for (goldstacks = 0, otmp = invent; otmp; otmp = otmp->nobj) { - /* gold should never end up in a letter slot, nor should two '$' - slots occur, but if they ever do, allow #adjust to handle them - (in the past, things like this have happened, usually due to - bknown being erroneously set on one stack, clear on another; - object merger isn't fooled by that anymore) */ - if (otmp->oclass == COIN_CLASS - && (otmp->invlet != GOLD_SYM || ++goldstacks > 1)) { - allowall[1] = COIN_CLASS; - allowall[2] = ALL_CLASSES; - allowall[3] = '\0'; - break; + obj = getobj("adjust", adjust_filter, GETOBJ_PROMPT | GETOBJ_ALLOWCNT); + + return doorganize_core(obj); +} + +/* alternate version of #adjust used by itemactions() for splitting */ +int +adjust_split(void) +{ + struct obj *obj; + cmdcount_nht splitamount = 0L; + char let, dig = '\0'; + + /* invlet should be queued so no getobj prompting is expected */ + obj = getobj("split", adjust_ok, GETOBJ_NOFLAGS); + if (!obj || obj->quan < 2L || obj->otyp == GOLD_PIECE) + return ECMD_FAIL; /* caller has set things up to avoid this */ + + if (obj->quan == 2L) { + splitamount = 1L; + } else { + /* get first digit; doesn't wait for */ + dig = yn_function("Split off how many?", (char *) 0, '\0', TRUE); + if (!digit(dig)) { + pline1(Never_mind); + return ECMD_CANCEL; + } + /* got first digit, get more until next non-digit (except for + backspace/delete which will take away most recent digit and + keep going; we expect one of ' ', '\n', or '\r') */ + let = get_count(NULL, dig, 0L, &splitamount, + /* yn_function() added the first digit to the + prompt when recording message history; have + get_count() display "Count: N" when waiting + for additional digits (ordinarily that won't be + shown until a second digit is entered) and also + add "Count: N" to message history if more than + one digit gets entered or the original N is + deleted and replaced with different digit */ + GC_ECHOFIRST | GC_CONDHIST); + /* \033 is in quitchars[] so we need to check for it separately + in order to treat it as cancel rather than as accept */ + if (!let || let == '\033' || !strchr(quitchars, let)) { + pline1(Never_mind); + return ECMD_CANCEL; } } - if (!(obj = getobj(allowall, "adjust"))) - return 0; + if (splitamount < 1L || splitamount >= obj->quan) { + static const char + Amount[] = "Amount to split from current stack must be"; + + if (splitamount < 1L) + pline("%s at least 1.", Amount); + else + pline("%s less than %ld.", Amount, obj->quan); + return ECMD_CANCEL; + } + + /* normally a split would take place in getobj() if player supplies + a count there, so doorganize_core() figures out 'splitamount' + from the object; it will undo the split if player cancels while + selecting the destination slot */ + obj = splitobj(obj, (long) splitamount); + return doorganize_core(obj); +} + +staticfn int +doorganize_core(struct obj *obj) +{ + struct obj *otmp, *splitting, *bumped; + int ix, cur, trycnt; + char let; +#define GOLD_INDX 0 +#define GOLD_OFFSET 1 +#define OVRFLW_INDX (GOLD_OFFSET + invlet_basic) /* past gold+2*26 letters */ + char lets[1 + invlet_basic + 1 + 1]; /* room for '$a-zA-Z#\0' */ + char qbuf[QBUFSZ]; + char *objname, *otmpname; + const char *adj_type; + boolean ever_mind = FALSE, collect, isgold; + + if (!obj) + return ECMD_CANCEL; + + /* can only be gold if check_invent_gold() found a problem: multiple '$' + stacks and/or gold in some other slot, otherwise (*adjust_filter)() + won't allow gold to be picked; if player has picked any stack of gold + as #adjust 'from' slot, we'll force the 'to' slot to be '$' below */ + isgold = (obj->oclass == COIN_CLASS); /* figure out whether user gave a split count to getobj() */ splitting = bumped = 0; - for (otmp = invent; otmp; otmp = otmp->nobj) + for (otmp = gi.invent; otmp; otmp = otmp->nobj) if (otmp->nobj == obj) { /* knowledge of splitobj() operation */ if (otmp->invlet == obj->invlet) splitting = otmp; @@ -4131,12 +5106,12 @@ doorganize() /* inventory organizer by Del Lamb */ lets[OVRFLW_INDX] = ' '; lets[sizeof lets - 1] = '\0'; /* for floating inv letters, truncate list after the first open slot */ - if (!flags.invlet_constant && (ix = inv_cnt(FALSE)) < 52) - lets[ix + (splitting ? 0 : 1)] = '\0'; + if (!flags.invlet_constant && (ix = inv_cnt(FALSE)) < invlet_basic) + lets[ix + (splitting ? 1 : 2)] = '\0'; /* blank out all the letters currently in use in the inventory except those that will be merged with the selected object */ - for (otmp = invent; otmp; otmp = otmp->nobj) + for (otmp = gi.invent; otmp; otmp = otmp->nobj) if (otmp != obj && !mergable(otmp, obj)) { let = otmp->invlet; if (let >= 'a' && let <= 'z') @@ -4159,10 +5134,14 @@ doorganize() /* inventory organizer by Del Lamb */ compactify(lets); /* get 'to' slot to use as destination */ - Sprintf(qbuf, "Adjust letter to what [%s]%s?", lets, - invent ? " (? see used letters)" : ""); + if (!splitting) + Strcpy(qbuf, "Adjust letter"); + else /* note: splitting->quan is the amount being left in original slot */ + Sprintf(qbuf, "Split %ld", obj->quan); + Sprintf(eos(qbuf), " to what [%s]%s?", lets, + gi.invent ? " (? see used letters)" : ""); for (trycnt = 1; ; ++trycnt) { - let = yn_function(qbuf, (char *) 0, '\0'); + let = !isgold ? yn_function(qbuf, (char *) 0, '\0', TRUE) : GOLD_SYM; if (let == '?' || let == '*') { let = display_used_invlets(splitting ? obj->invlet : 0); if (!let) @@ -4170,7 +5149,7 @@ doorganize() /* inventory organizer by Del Lamb */ if (let == '\033') goto noadjust; } - if (index(quitchars, let) + if (strchr(quitchars, let) /* adjusting to same slot is meaningful since all compatible stacks get collected along the way, but splitting to same slot is not */ @@ -4180,7 +5159,7 @@ doorganize() /* inventory organizer by Del Lamb */ (void) merged(&splitting, &obj); if (!ever_mind) pline1(Never_mind); - return 0; + return ECMD_OK; } else if (let == GOLD_SYM && obj->oclass != COIN_CLASS) { pline("Only gold coins may be moved into the '%c' slot.", GOLD_SYM); @@ -4188,9 +5167,9 @@ doorganize() /* inventory organizer by Del Lamb */ goto noadjust; } /* letter() classifies '@' as one; compactify() can put '-' in lets; - the only thing of interest that index() might find is '$' or '#' + the only thing of interest that strchr() might find is '$' or '#' since letter() catches everything else that we put into lets[] */ - if ((letter(let) && let != '@') || (index(lets, let) && let != '-')) + if ((letter(let) && let != '@') || (strchr(lets, let) && let != '-')) break; /* got one */ if (trycnt == 5) goto noadjust; @@ -4199,15 +5178,18 @@ doorganize() /* inventory organizer by Del Lamb */ collect = (let == obj->invlet); /* change the inventory and print the resulting item */ - adj_type = collect ? "Collecting" : !splitting ? "Moving:" : "Splitting:"; + adj_type = collect ? "Collecting:" + : !splitting ? "Moving:" + : "Splitting:"; /* * don't use freeinv/addinv to avoid double-touching artifacts, * dousing lamps, losing luck, cursing loadstone, etc. */ - extract_nobj(obj, &invent); + extract_nobj(obj, &gi.invent); - for (otmp = invent; otmp;) { + for (otmp = gi.invent; otmp; ) { + otmpname = has_oname(otmp) ? ONAME(otmp) : (char *) 0; /* it's tempting to pull this outside the loop, but merged() could free ONAME(obj) [via obfree()] and replace it with ONAME(otmp) */ objname = has_oname(obj) ? ONAME(obj) : (char *) 0; @@ -4219,16 +5201,24 @@ doorganize() /* inventory organizer by Del Lamb */ with compatible named ones; we only want that if it is the 'from' stack (obj) with a name and candidate (otmp) without one, not unnamed 'from' with named candidate. */ - otmpname = has_oname(otmp) ? ONAME(otmp) : (char *) 0; if ((!otmpname || (objname && !strcmp(objname, otmpname))) && merged(&otmp, &obj)) { - adj_type = "Merging:"; + /*adj_type = "Collecting:"; //already set to this*/ obj = otmp; otmp = otmp->nobj; - extract_nobj(obj, &invent); + extract_nobj(obj, &gi.invent); continue; /* otmp has already been updated */ } } else if (otmp->invlet == let) { + /* Merging: when from and to are compatible */ + if ((!otmpname || (objname && !strcmp(objname, otmpname))) + && merged(&otmp, &obj)) { + adj_type = "Merging:"; + obj = otmp; + otmp = otmp->nobj; + extract_nobj(obj, &gi.invent); + break; /* otmp has been updated and we're done merging */ + } /* Moving or splitting: don't merge extra compatible stacks. Found 'otmp' in destination slot; merge if compatible, otherwise bump whatever is there to an open slot. */ @@ -4252,15 +5242,15 @@ doorganize() /* inventory organizer by Del Lamb */ if (merged(&otmp, &obj)) { adj_type = "Splitting and merging:"; obj = otmp; - extract_nobj(obj, &invent); - } else if (inv_cnt(FALSE) >= 52) { + extract_nobj(obj, &gi.invent); + } else if (inv_cnt(FALSE) >= invlet_basic) { (void) merged(&splitting, &obj); /* undo split */ /* "knapsack cannot accommodate any more items" */ Your("pack is too full."); - return 0; + return ECMD_OK; } else { bumped = otmp; - extract_nobj(bumped, &invent); + extract_nobj(bumped, &gi.invent); } } /* moving vs splitting */ break; /* not collecting and found 'to' slot */ @@ -4270,18 +5260,18 @@ doorganize() /* inventory organizer by Del Lamb */ /* inline addinv; insert loose object at beginning of inventory */ obj->invlet = let; - obj->nobj = invent; + obj->nobj = gi.invent; obj->where = OBJ_INVENT; - invent = obj; + gi.invent = obj; reorder_invent(); if (bumped) { /* splitting the 'from' stack is causing an incompatible stack in the 'to' slot to be moved into an open one; we need to do another inline insertion to inventory */ assigninvlet(bumped); - bumped->nobj = invent; + bumped->nobj = gi.invent; bumped->where = OBJ_INVENT; - invent = bumped; + gi.invent = bumped; reorder_invent(); } @@ -4292,25 +5282,21 @@ doorganize() /* inventory organizer by Del Lamb */ if (splitting) clear_splitobjs(); /* reset splitobj context */ update_inventory(); - return 0; + return ECMD_OK; } /* common to display_minventory and display_cinventory */ -STATIC_OVL void -invdisp_nothing(hdr, txt) -const char *hdr, *txt; +staticfn void +invdisp_nothing(const char *hdr, const char *txt) { winid win; - anything any; menu_item *selected; - any = zeroany; win = create_nhwindow(NHW_MENU); - start_menu(win); - add_menu(win, NO_GLYPH, &any, 0, 0, iflags.menu_headings, hdr, - MENU_UNSELECTED); - add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED); - add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, txt, MENU_UNSELECTED); + start_menu(win, MENU_BEHAVE_STANDARD); + add_menu_heading(win, hdr); + add_menu_str(win, ""); + add_menu_str(win, txt); end_menu(win, (char *) 0); if (select_menu(win, PICK_NONE, &selected) > 0) free((genericptr_t) selected); @@ -4319,9 +5305,8 @@ const char *hdr, *txt; } /* query_objlist callback: return things that are worn or wielded */ -STATIC_OVL boolean -worn_wield_only(obj) -struct obj *obj; +staticfn boolean +worn_wield_only(struct obj *obj) { #if 1 /* check for things that *are* worn or wielded (only used for monsters, @@ -4353,18 +5338,19 @@ struct obj *obj; * MINV_ALL - display all inventory */ struct obj * -display_minventory(mon, dflags, title) -register struct monst *mon; -int dflags; -char *title; +display_minventory( + struct monst *mon, /* monster whose minvent we're showing */ + int dflags, /* control over what to display */ + char *title) /* menu title */ { struct obj *ret; char tmp[QBUFSZ]; int n; menu_item *selected = 0; int do_all = (dflags & MINV_ALL) != 0, - incl_hero = (do_all && u.uswallow && mon == u.ustuck), - have_inv = (mon->minvent != 0), have_any = (have_inv || incl_hero), + incl_hero = (do_all && engulfing_u(mon)), + have_inv = (mon->minvent != 0), + have_any = (have_inv || incl_hero), pickings = (dflags & MINV_PICKMASK); Sprintf(tmp, "%s %s:", s_suffix(noit_Monnam(mon)), @@ -4374,7 +5360,7 @@ char *title; /* Fool the 'weapon in hand' routine into * displaying 'weapon in claw', etc. properly. */ - youmonst.data = mon->data; + gy.youmonst.data = mon->data; /* in case inside a shop, don't append "for sale" prices */ iflags.suppress_price++; @@ -4385,7 +5371,7 @@ char *title; iflags.suppress_price--; /* was 'set_uasmon();' but that potentially has side-effects */ - youmonst.data = &mons[u.umonnum]; /* most basic part of set_uasmon */ + gy.youmonst.data = &mons[u.umonnum]; /* basic part of set_uasmon() */ } else { invdisp_nothing(title ? title : tmp, "(none)"); n = 0; @@ -4399,21 +5385,76 @@ char *title; return ret; } -/* - * Display the contents of a container in inventory style. - * Currently, this is only used for statues, via wand of probing. - */ +/* format a container name for cinventory_display(), inserting "trapped" + if that's appropriate */ +staticfn char * +cinv_doname(struct obj *obj) +{ + char *result = doname(obj); + + /* + * If obj->tknown ever gets implemented, doname() will handle this. + * Assumes that probing reveals the trap prior to calling us. Since + * we lack that flag, hero forgets about it as soon as we're done.... + */ + + /* 'result' is an obuf[] but might point into the middle (&buf[PREFIX]) + rather than the beginning and we don't have access to that; + assume that there is at least QBUFSZ available when reusing it */ + if (obj->otrapped && strlen(result) + sizeof "trapped " <= QBUFSZ) { + /* obj->lknown has been set before calling us so either "locked" or + "unlocked" should always be present (for a trapped container) */ + char *p = strstri(result, " locked"), + *q = strstri(result, " unlocked"); + + if (p && (!q || p < q)) + (void) strsubst(p, " locked ", " trapped locked "); + else if (q) + (void) strsubst(q, " unlocked ", " trapped unlocked "); + /* might need to change "an" to "a"; when no BUC is present, + "an unlocked" yielded "an trapped unlocked" above */ + (void) strsubst(result, "an trapped ", "a trapped "); + } + return result; +} + +/* used by safe_qbuf() if the full doname() result is too long */ +staticfn char * +cinv_ansimpleoname(struct obj *obj) +{ + char *result = ansimpleoname(obj); + + /* result is an obuf[] so we know this will always fit */ + if (obj->otrapped) { + if (strncmp(result, "a ", 2)) + (void) strsubst(result, "a ", "a trapped "); + else if (strncmp(result, "an ", 3)) + (void) strsubst(result, "an ", "an trapped "); + /* unique container? nethack doesn't have any */ + else if (strncmp(result, "the ", 4)) + (void) strsubst(result, "the ", "the trapped "); + /* no leading article at all? shouldn't happen with ansimpleoname() */ + else + (void) strsubst(result, "", "trapped "); /* insert at beginning */ + } + return result; +} + +/* Display the contents of a container in inventory style. + Used for wand of probing of non-empty containers and statues. */ struct obj * -display_cinventory(obj) -register struct obj *obj; +display_cinventory(struct obj *obj) { struct obj *ret; char qbuf[QBUFSZ]; int n; menu_item *selected = 0; - (void) safe_qbuf(qbuf, "Contents of ", ":", obj, doname, ansimpleoname, - "that"); + (void) safe_qbuf(qbuf, "Contents of ", ":", obj, + /* custom formatting routines to insert "trapped" + into the object's name when appropriate; + last resort "that" won't ever get used */ + cinv_doname, cinv_ansimpleoname, "that"); if (obj->cobj) { n = query_objlist(qbuf, &(obj->cobj), INVORDER_SORT, @@ -4431,49 +5472,208 @@ register struct obj *obj; return ret; } -/* query objlist callback: return TRUE if obj is at given location */ -static coord only; -STATIC_OVL boolean -only_here(obj) -struct obj *obj; +staticfn boolean +only_here(struct obj *obj) { - return (obj->ox == only.x && obj->oy == only.y); + return (obj->ox == go.only.x && obj->oy == go.only.y); } /* - * Display a list of buried items in inventory style. Return a non-zero - * value if there were items at that spot. + * Display a list of buried or underwater items in inventory style. + * Return a non-zero value if there were items at that spot. * * Currently, this is only used with a wand of probing zapped downwards. */ int -display_binventory(x, y, as_if_seen) -int x, y; -boolean as_if_seen; +display_binventory(coordxy x, coordxy y, boolean as_if_seen) { struct obj *obj; + char qbuf[QBUFSZ]; + const char *underwhat = "here"; menu_item *selected = 0; - int n; + int n, n2 = 0; + + /* if hero is levitating or flying over water or lava, list any items + below (the map won't be showing them); if hero is underwater, player + should use the normal look_here command instead of probing (caller + has already used bhitpile() which will have set dknown on all items) */ + if (is_pool_or_lava(x, y) && !Underwater + && (obj = svl.level.objects[x][y]) != 0) { + const char *real_liquid = is_pool(x, y) ? "water" : "lava", + *seen_liquid = hliquid(real_liquid); + + if (!obj->nexthere) { + boolean more_than_1 = is_plural(obj); + + There("%s %s under the %s here.", more_than_1 ? "are" : "is", + doname(obj), seen_liquid); + n2 = 1; + /* "pair of boots" is singular but "beneath it" sounds strange */ + if (pair_of(obj)) + more_than_1 = TRUE; + underwhat = more_than_1 ? "under them" : "beneath it"; + } else { + Sprintf(qbuf, "Things that are under the %s here:", seen_liquid); + if (query_objlist(qbuf, &svl.level.objects[x][y], BY_NEXTHERE, + &selected, PICK_NONE, allow_all) > 0) + free((genericptr_t) selected), selected = 0; + for (n2 = 0; obj; obj = obj->nexthere) + ++n2; + underwhat = "beneath them"; + } + } - /* count # of objects here */ - for (n = 0, obj = level.buriedobjlist; obj; obj = obj->nobj) + /* count # of buried objects here */ + for (n = 0, obj = svl.level.buriedobjlist; obj; obj = obj->nobj) if (obj->ox == x && obj->oy == y) { if (as_if_seen) - obj->dknown = 1; + observe_object(obj); n++; } if (n) { - only.x = x; - only.y = y; - if (query_objlist("Things that are buried here:", - &level.buriedobjlist, INVORDER_SORT, + go.only.x = x; + go.only.y = y; + /* "buried here", but vary if we've already shown underwater items */ + Sprintf(qbuf, "Things that are buried %s:", underwhat); + if (query_objlist(qbuf, &svl.level.buriedobjlist, INVORDER_SORT, &selected, PICK_NONE, only_here) > 0) free((genericptr_t) selected); - only.x = only.y = 0; + go.only.x = go.only.y = 0; + } + return n + n2; +} + +void +prepare_perminvent(winid window) +{ + win_request_info *wri; + int invmode = (int) iflags.perminv_mode; + + if (perminv_flags != invmode) { + wri_info = zerowri; + wri_info.fromcore.invmode = invmode; + /* relay the mode settings to the window port */ + wri = ctrl_nhwindow(window, set_mode, &wri_info); + perminv_flags = invmode; + nhUse(wri); + } +} + +void +sync_perminvent(void) +{ + static win_request_info *wri = 0; + const char *wport_id; + + if (WIN_INVEN == WIN_ERR) { + if ((gc.core_invent_state + || (wri_info.tocore.tocore_flags & prohibited)) + && !(in_perm_invent_toggled + && gp.perm_invent_toggling_direction == toggling_on)) + return; + } + prepare_perminvent(WIN_INVEN); + + if ((!iflags.perm_invent && gc.core_invent_state)) { + /* Odd - but this could be end-of-game disclosure + * which just sets boolean iflags.perm_invent to + * FALSE without actually doing anything else. + */ +#ifdef TTY_PERM_INVENT + if (WINDOWPORT(tty)) + perm_invent_toggled(TRUE); /* TRUE means negated */ +#endif + docrt(); + return; + } + + /* + * The following conditions can bring us to here: + * 1. iflags.perm_invent is on + * AND + * gc.core_invent_state is still zero. + * OR + * 2. iflags.perm_invent is off, but we're in the + * midst of toggling it on. + * OR + * 3. iflags.perminv_mode has been changed via 'm O'. + */ + + if ((iflags.perm_invent && !gc.core_invent_state) + || (!iflags.perm_invent + && (in_perm_invent_toggled + && gp.perm_invent_toggling_direction == toggling_on))) { + /* Send windowport a request to return the related settings to us */ + if ((iflags.perm_invent && !gc.core_invent_state) + || in_perm_invent_toggled) { + wri = ctrl_nhwindow(WIN_INVEN, request_settings, &wri_info); + if (wri != 0) { + if ((wri->tocore.tocore_flags & (too_early)) != 0) { + /* don't be too noisy about this as it's really + * a startup timing issue. Just set a marker. */ + iflags.perm_invent_pending = TRUE; + return; + } + if ((wri->tocore.tocore_flags & (too_small | prohibited)) + != 0) { + /* sizes aren't good enough */ + if ((wri->tocore.tocore_flags & prohibited) != 0) { + set_option_mod_status("perm_invent", set_gameview); + set_option_mod_status("perminv_mode", set_gameview); + } + iflags.perm_invent = FALSE; + if (WIN_INVEN != WIN_ERR) + destroy_nhwindow(WIN_INVEN), WIN_INVEN = WIN_ERR; + wport_id = WINDOWPORT(tty) ? "tty perm_invent" + : "perm_invent"; + pline("%s could not be enabled.", wport_id); + pline("%s needs a terminal that is at least %dx%d, yours " + "is %dx%d.", + wport_id, wri->tocore.needrows, + wri->tocore.needcols, wri->tocore.haverows, + wri->tocore.havecols); + wait_synch(); + return; + } + } + gc.core_invent_state++; + } + } + + if (!wri || wri->tocore.maxslot == 0) + return; + + if (in_perm_invent_toggled + && gp.perm_invent_toggling_direction == toggling_on) { + WIN_INVEN = create_nhwindow(NHW_MENU); + } + + if (WIN_INVEN != WIN_ERR && program_state.beyond_savefile_load) { + gi.in_sync_perminvent = 1; + (void) display_inventory((char *) 0, FALSE); + gi.in_sync_perminvent = 0; + } +} + +void +perm_invent_toggled(boolean negated) +{ + in_perm_invent_toggled = TRUE; + if (negated) { + gp.perm_invent_toggling_direction = toggling_off; + if (WIN_INVEN != WIN_ERR) + destroy_nhwindow(WIN_INVEN), WIN_INVEN = WIN_ERR; + gc.core_invent_state = 0; + } else { + gp.perm_invent_toggling_direction = toggling_on; + if (iflags.perminv_mode == InvOptNone) + iflags.perminv_mode = InvOptOn; /* all inventory except gold */ + sync_perminvent(); } - return n; + gp.perm_invent_toggling_direction = toggling_not; + in_perm_invent_toggled = FALSE; } /*invent.c*/ diff --git a/src/isaac64.c b/src/isaac64.c index 8d0909250..0fa0e7a8d 100644 --- a/src/isaac64.c +++ b/src/isaac64.c @@ -30,19 +30,24 @@ #define inline /*empty*/ #endif +static inline uint32_t lower_bits(uint64_t); +static inline uint32_t upper_bits(uint64_t); +staticfn void isaac64_update(isaac64_ctx *); +staticfn void isaac64_mix(uint64_t[8]); + /* Extract ISAAC64_SZ_LOG bits (starting at bit 3). */ static inline uint32_t lower_bits(uint64_t x) { - return (x & ((ISAAC64_SZ-1) << 3)) >>3; + return (x & ((ISAAC64_SZ-1) << 3)) >>3; } /* Extract next ISAAC64_SZ_LOG bits (starting at bit ISAAC64_SZ_LOG+2). */ static inline uint32_t upper_bits(uint64_t y) { - return (y >> (ISAAC64_SZ_LOG+3)) & (ISAAC64_SZ-1); + return (y >> (ISAAC64_SZ_LOG+3)) & (ISAAC64_SZ-1); } -static void isaac64_update(isaac64_ctx *_ctx){ +staticfn void isaac64_update(isaac64_ctx *_ctx){ uint64_t *m; uint64_t *r; uint64_t a; @@ -95,7 +100,7 @@ static void isaac64_update(isaac64_ctx *_ctx){ _ctx->n=ISAAC64_SZ; } -static void isaac64_mix(uint64_t _x[8]){ +staticfn void isaac64_mix(uint64_t _x[8]){ static const unsigned char SHIFT[8]={9,9,23,15,14,20,17,14}; int i; for(i=0;i<8;i++){ diff --git a/src/light.c b/src/light.c index 7c2dc1f2d..fd41acd73 100644 --- a/src/light.c +++ b/src/light.c @@ -1,9 +1,8 @@ -/* NetHack 3.6 light.c $NHDT-Date: 1559994625 2019/06/08 11:50:25 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.30 $ */ +/* NetHack 5.0 light.c $NHDT-Date: 1773375430 2026/03/12 20:17:10 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.82 $ */ /* Copyright (c) Dean Luick, 1994 */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -#include "lev.h" /* for checking save modes */ /* * Mobile light sources. @@ -14,8 +13,8 @@ * Light sources are "things" that have a physical position and range. * They have a type, which gives us information about them. Currently * they are only attached to objects and monsters. Note well: the - * polymorphed-player handling assumes that both youmonst.m_id and - * youmonst.mx will always remain 0. + * polymorphed-player handling assumes that gy.youmonst.m_id will + * always remain 1 and gy.youmonst.mx will always remain 0. * * Light sources, like timers, either follow game play (RANGE_GLOBAL) or * stay on a level (RANGE_LEVEL). Light sources are unique by their @@ -39,65 +38,80 @@ */ /* flags */ -#define LSF_SHOW 0x1 /* display the light source */ -#define LSF_NEEDS_FIXUP 0x2 /* need oid fixup */ - -static light_source *light_base = 0; - -STATIC_DCL void FDECL(write_ls, (int, light_source *)); -STATIC_DCL int FDECL(maybe_write_ls, (int, int, BOOLEAN_P)); +#define LSF_SHOW 0x1 /* display the light source */ +#define LSF_NEEDS_FIXUP 0x2 /* need oid fixup */ +#define LSF_IS_PROBLEMATIC 0x4 /* impossible situation encountered */ + +#ifndef SFCTOOL +staticfn light_source *new_light_core(coordxy, coordxy, + int, int, anything *) NONNULLPTRS; +staticfn void delete_ls(light_source *); +staticfn void discard_flashes(void); +staticfn void write_ls(NHFILE *, light_source *); +staticfn int maybe_write_ls(NHFILE *, int, boolean); +staticfn unsigned whereis_mon(struct monst *, unsigned); /* imported from vision.c, for small circles */ -extern char circle_data[]; -extern char circle_start[]; +extern const coordxy circle_data[]; +extern const coordxy circle_start[]; + -/* Create a new light source. */ +/* Create a new light source. Caller (and extern.h) doesn't need to know + anything about type 'light_source'. */ void -new_light_source(x, y, range, type, id) -xchar x, y; -int range, type; -anything *id; +new_light_source(coordxy x, coordxy y, int range, int type, anything *id) +{ + (void) new_light_core(x, y, range, type, id); +} + +/* Create a new light source and return it. Only used within this file. */ +staticfn light_source * +new_light_core(coordxy x, coordxy y, int range, int type, anything *id) { light_source *ls; - if (range > MAX_RADIUS || range < 1) { + if (range > MAX_RADIUS || range < 0 + /* camera flash uses radius 0 and passes Null object */ + || (range == 0 && (type != LS_OBJECT || id->a_obj != 0))) { impossible("new_light_source: illegal range %d", range); - return; + return (light_source *) 0; } ls = (light_source *) alloc(sizeof *ls); - ls->next = light_base; + (void) memset((genericptr_t) ls, 0, sizeof (light_source)); + ls->next = gl.light_base; ls->x = x; ls->y = y; ls->range = range; ls->type = type; ls->id = *id; ls->flags = 0; - light_base = ls; + gl.light_base = ls; - vision_full_recalc = 1; /* make the source show up */ + gv.vision_full_recalc = 1; /* make the source show up */ + return ls; } -/* - * Delete a light source. This assumes only one light source is attached - * to an object at a time. - */ +/* Find and delete a light source. + Assumes at most one light source is attached to an object at a time. */ void -del_light_source(type, id) -int type; -anything *id; +del_light_source(int type, anything *id) { - light_source *curr, *prev; + light_source *curr; anything tmp_id; - tmp_id = zeroany; + tmp_id = cg.zeroany; /* need to be prepared for dealing a with light source which has only been partially restored during a level change (in particular: chameleon vs prot. from shape changers) */ switch (type) { + case LS_NONE: + impossible("del_light_source:type=none"); + tmp_id.a_uint = 0; + break; case LS_OBJECT: - tmp_id.a_uint = id->a_obj->o_id; + tmp_id.a_uint = id->a_obj ? id->a_obj->o_id : 0; break; case LS_MONSTER: tmp_id.a_uint = id->a_monst->m_id; @@ -107,37 +121,61 @@ anything *id; break; } - for (prev = 0, curr = light_base; curr; prev = curr, curr = curr->next) { + /* find the light source from its id */ + for (curr = gl.light_base; curr; curr = curr->next) { if (curr->type != type) continue; if (curr->id.a_obj - == ((curr->flags & LSF_NEEDS_FIXUP) ? tmp_id.a_obj : id->a_obj)) { + == ((curr->flags & LSF_NEEDS_FIXUP) ? tmp_id.a_obj : id->a_obj)) + break; + } + if (curr) { + delete_ls(curr); + } else { + impossible("del_light_source: not found type=%d, id=%s", type, + fmt_ptr((genericptr_t) id->a_obj)); + } +} + +/* remove a light source from the light_base list and free it */ +staticfn void +delete_ls(light_source *ls) +{ + light_source *curr, *prev; + + for (prev = 0, curr = gl.light_base; curr; + prev = curr, curr = curr->next) { + if (curr == ls) { if (prev) prev->next = curr->next; else - light_base = curr->next; - - free((genericptr_t) curr); - vision_full_recalc = 1; - return; + gl.light_base = curr->next; + break; } } - impossible("del_light_source: not found type=%d, id=%s", type, - fmt_ptr((genericptr_t) id->a_obj)); + if (curr) { + assert(curr == ls); + (void) memset((genericptr_t) ls, 0, sizeof(light_source)); + free((genericptr_t) ls); + gv.vision_full_recalc = 1; + } else { + impossible("delete_ls not found, ls=%s", fmt_ptr((genericptr_t) ls)); + } + return; } /* Mark locations that are temporarily lit via mobile light sources. */ void -do_light_sources(cs_rows) -char **cs_rows; +do_light_sources(seenV **cs_rows) { - int x, y, min_x, max_x, max_y, offset; - char *limits; + coordxy x, y, min_x, max_x, max_y; + int offset; + const coordxy *limits; short at_hero_range = 0; light_source *ls; - char *row; + seenV *row; - for (ls = light_base; ls; ls = ls->next) { + for (ls = gl.light_base; ls; ls = ls->next) { ls->flags &= ~LSF_SHOW; /* @@ -147,7 +185,8 @@ char **cs_rows; * vision recalc. */ if (ls->type == LS_OBJECT) { - if (get_obj_location(ls->id.a_obj, &ls->x, &ls->y, 0)) + if (ls->range == 0 /* camera flash; caller has set ls->{x,y} */ + || get_obj_location(ls->id.a_obj, &ls->x, &ls->y, 0)) ls->flags |= LSF_SHOW; } else if (ls->type == LS_MONSTER) { if (get_mon_location(ls->id.a_monst, &ls->x, &ls->y, 0)) @@ -156,7 +195,7 @@ char **cs_rows; /* minor optimization: don't bother with duplicate light sources at hero */ - if (ls->x == u.ux && ls->y == u.uy) { + if (u_at(ls->x, ls->y)) { if (at_hero_range >= ls->range) ls->flags &= ~LSF_SHOW; else @@ -179,12 +218,12 @@ char **cs_rows; for (; y <= max_y; y++) { row = cs_rows[y]; offset = limits[abs(y - ls->y)]; - if ((min_x = (ls->x - offset)) < 0) - min_x = 0; + if ((min_x = (ls->x - offset)) < 1) + min_x = 1; if ((max_x = (ls->x + offset)) >= COLNO) max_x = COLNO - 1; - if (ls->x == u.ux && ls->y == u.uy) { + if (u_at(ls->x, ls->y)) { /* * If the light source is located at the hero, then * we can use the COULD_SEE bits already calculated @@ -212,75 +251,121 @@ char **cs_rows; /* lit 'obj' has been thrown or kicked and is passing through x,y on the way to its destination; show its light so that hero has a chance to - remember terrain, objects, and monsters being revealed */ + remember terrain, objects, and monsters being revealed; + if 'obj' is Null, is being hit by a camera's light flash */ void -show_transient_light(obj, x, y) -struct obj *obj; -int x, y; +show_transient_light(struct obj *obj, coordxy x, coordxy y) { - light_source *ls; + light_source *ls = 0; + anything cameraflash; struct monst *mon; int radius_squared; - /* caller has verified obj->lamplit and that hero is not Blind; - validate light source and obtain its radius (for monster sightings) */ - for (ls = light_base; ls; ls = ls->next) { - if (ls->type != LS_OBJECT) - continue; - if (ls->id.a_obj == obj) - break; - } - if (!ls || obj->where != OBJ_FREE) { - impossible("transient light %s %s is not %s?", - obj->lamplit ? "lit" : "unlit", xname(obj), - !ls ? "a light source" : "free"); - } else { - /* "expensive" but rare */ - place_object(obj, bhitpos.x, bhitpos.y); /* temporarily put on map */ - vision_recalc(0); - flush_screen(0); - delay_output(); - remove_object(obj); /* take back off of map */ + /* Null object indicates camera flash */ + if (!obj) { + /* no need to temporarily light an already lit spot */ + if (levl[x][y].lit) + return; - radius_squared = ls->range * ls->range; - for (mon = fmon; mon; mon = mon->nmon) { - if (DEADMONSTER(mon)) + cameraflash = cg.zeroany; + /* radius 0 will just light ; cameraflash.a_obj is Null */ + ls = new_light_core(x, y, 0, LS_OBJECT, &cameraflash); + /* pacify static analysis; 'ls' is never Null for + new_light_core(,,0,LS_OBJECT,&zeroany) */ + assert(ls != NULL); + } else { + /* thrown or kicked object which is emitting light; validate its + light source to obtain its radius (for monster sightings) */ + for (ls = gl.light_base; ls; ls = ls->next) { + if (ls->type != LS_OBJECT) continue; - /* light range is the radius of a circle and we're limiting - canseemon() to a square exclosing that circle, but setting - mtemplit 'erroneously' for a seen monster is not a problem; - it just flags monsters for another canseemon() check when - 'obj' has reached its destination after missile traversal */ - if (dist2(mon->mx, mon->my, x, y) <= radius_squared - && canseemon(mon)) + if (ls->id.a_obj == obj) + break; + } + assert(obj != NULL); /* necessary condition to get into this 'else' */ + if (!ls || obj->where != OBJ_FREE) { + impossible("transient light %s %s %s not %s?", + obj->lamplit ? "lit" : "unlit", + simpleonames(obj), otense(obj, "are"), + !ls ? "a light source" : "free"); + return; + } + } + + if (obj) /* put lit candle or lamp temporarily on the map */ + place_object(obj, gb.bhitpos.x, gb.bhitpos.y); + else /* camera flash: no object; directly set light source's location */ + ls->x = x, ls->y = y; + + /* full recalc; runs do_light_sources() */ + vision_recalc(0); + flush_screen(0); + + radius_squared = ls->range * ls->range; + for (mon = fmon; mon; mon = mon->nmon) { + if (DEADMONSTER(mon) || (mon->isgd && !mon->mx)) + continue; + /* light range is the radius of a circle and we're limiting + canseemon() to a square enclosing that circle, but setting + mtemplit 'erroneously' for a seen monster is not a problem; + it just flags monsters for another canseemon() check when + 'obj' has reached its destination after missile traversal */ + if (dist2(mon->mx, mon->my, x, y) <= radius_squared) { + if (canseemon(mon)) mon->mtemplit = 1; - /* [what about worm tails?] */ } + /* [what about worm tails?] */ + } + + if (obj) { /* take thrown/kicked candle or lamp off the map */ + nh_delay_output(); + remove_object(obj); } } -/* draw "remembered, unseen monster" glyph at locations where a monster - was flagged for being visible during transient light movement but can't - be seen now */ +/* delete any camera flash light sources and draw "remembered, unseen + monster" glyph at locations where a monster was flagged for being + visible during transient light movement but can't be seen now */ void -transient_light_cleanup() +transient_light_cleanup(void) { struct monst *mon; - int mtempcount = 0; + int mtempcount; + /* in case we're cleaning up a camera flash, remove all object light + sources which aren't associated with a specific object */ + discard_flashes(); + if (gv.vision_full_recalc) /* set by del_light_source() */ + vision_recalc(0); + + /* for thrown/kicked candle or lamp or for camera flash, some + monsters may have been mapped in light which has now gone away + so need to be replaced by "remembered, unseen monster" glyph */ + mtempcount = 0; for (mon = fmon; mon; mon = mon->nmon) { if (DEADMONSTER(mon)) continue; if (mon->mtemplit) { mon->mtemplit = 0; ++mtempcount; - if (!canseemon(mon)) + if (!canspotmon(mon)) map_invisible(mon->mx, mon->my); } } - if (mtempcount) { - vision_recalc(0); + if (mtempcount) flush_screen(0); +} + +/* camera flashes have Null object; caller wants to get rid of them now */ +staticfn void +discard_flashes(void) +{ + light_source *ls, *nxt_ls; + + for (ls = gl.light_base; ls; ls = nxt_ls) { + nxt_ls = ls->next; + if (ls->type == LS_OBJECT && !ls->id.a_obj) + delete_ls(ls); } } @@ -288,48 +373,74 @@ transient_light_cleanup() #define mon_is_local(mon) ((mon)->mx > 0) struct monst * -find_mid(nid, fmflags) -unsigned nid; -unsigned fmflags; +find_mid(unsigned nid, unsigned fmflags) { struct monst *mtmp; - if (!nid) - return &youmonst; + if ((fmflags & FM_YOU) && nid == 1) + return &gy.youmonst; if (fmflags & FM_FMON) for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) if (!DEADMONSTER(mtmp) && mtmp->m_id == nid) return mtmp; if (fmflags & FM_MIGRATE) - for (mtmp = migrating_mons; mtmp; mtmp = mtmp->nmon) + for (mtmp = gm.migrating_mons; mtmp; mtmp = mtmp->nmon) if (mtmp->m_id == nid) return mtmp; if (fmflags & FM_MYDOGS) - for (mtmp = mydogs; mtmp; mtmp = mtmp->nmon) + for (mtmp = gm.mydogs; mtmp; mtmp = mtmp->nmon) if (mtmp->m_id == nid) return mtmp; return (struct monst *) 0; } +staticfn unsigned +whereis_mon(struct monst *mon, unsigned fmflags) +{ + struct monst *mtmp; + + if ((fmflags & FM_YOU) && mon == &gy.youmonst) + return FM_YOU; + if (fmflags & FM_FMON) + for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) + if (mtmp == mon) + return FM_FMON; + if (fmflags & FM_MIGRATE) + for (mtmp = gm.migrating_mons; mtmp; mtmp = mtmp->nmon) + if (mtmp == mon) + return FM_MIGRATE; + if (fmflags & FM_MYDOGS) + for (mtmp = gm.mydogs; mtmp; mtmp = mtmp->nmon) + if (mtmp == mon) + return FM_MYDOGS; + return 0; +} + /* Save all light sources of the given range. */ void -save_light_sources(fd, mode, range) -int fd, mode, range; +save_light_sources(NHFILE *nhfp, int range) { int count, actual, is_global; light_source **prev, *curr; - if (perform_bwrite(mode)) { - count = maybe_write_ls(fd, range, FALSE); - bwrite(fd, (genericptr_t) &count, sizeof count); - actual = maybe_write_ls(fd, range, TRUE); + /* camera flash light sources have Null object and would trigger + impossible("no id!") below; they can only happen here if we're + in the midst of a panic save and they wouldn't be useful after + restore so just throw any that are present away */ + discard_flashes(); + gv.vision_full_recalc = 0; + + if (update_file(nhfp)) { + count = maybe_write_ls(nhfp, range, FALSE); + Sfo_int(nhfp, &count, "lightsource-count"); + actual = maybe_write_ls(nhfp, range, TRUE); if (actual != count) panic("counted %d light sources, wrote %d! [range=%d]", count, actual, range); } - if (release_data(mode)) { - for (prev = &light_base; (curr = *prev) != 0;) { + if (release_data(nhfp)) { + for (prev = &gl.light_base; (curr = *prev) != 0; ) { if (!curr->id.a_monst) { impossible("save_light_sources: no id! [range=%d]", range); is_global = 0; @@ -350,6 +461,7 @@ int fd, mode, range; /* if global and not doing local, or vice versa, remove it */ if (is_global ^ (range == RANGE_LEVEL)) { *prev = curr->next; + (void) memset((genericptr_t) curr, 0, sizeof(light_source)); free((genericptr_t) curr); } else { prev = &(*prev)->next; @@ -357,76 +469,94 @@ int fd, mode, range; } } } +#endif /* !SFCTOOL */ /* * Pull in the structures from disk, but don't recalculate the object * pointers. */ void -restore_light_sources(fd) -int fd; +restore_light_sources(NHFILE *nhfp) { - int count; + int count = 0; light_source *ls; /* restore elements */ - mread(fd, (genericptr_t) &count, sizeof count); + Sfi_int(nhfp, &count, "lightsource-count"); while (count-- > 0) { ls = (light_source *) alloc(sizeof(light_source)); - mread(fd, (genericptr_t) ls, sizeof(light_source)); - ls->next = light_base; - light_base = ls; + Sfi_ls_t(nhfp, ls, "lightsource"); + ls->next = gl.light_base; + gl.light_base = ls; } } +#ifndef SFCTOOL + +DISABLE_WARNING_FORMAT_NONLITERAL + /* to support '#stats' wizard-mode command */ void -light_stats(hdrfmt, hdrbuf, count, size) -const char *hdrfmt; -char *hdrbuf; -long *count, *size; +light_stats(const char *hdrfmt, char *hdrbuf, long *count, long *size) { light_source *ls; Sprintf(hdrbuf, hdrfmt, (long) sizeof (light_source)); *count = *size = 0L; - for (ls = light_base; ls; ls = ls->next) { + for (ls = gl.light_base; ls; ls = ls->next) { ++*count; *size += (long) sizeof *ls; } } +RESTORE_WARNING_FORMAT_NONLITERAL + /* Relink all lights that are so marked. */ void -relink_light_sources(ghostly) -boolean ghostly; +relink_light_sources(boolean ghostly) { char which; unsigned nid; light_source *ls; - for (ls = light_base; ls; ls = ls->next) { + /* + * Caveat: + * There has been at least one instance during to-be-5.0 development + * where the light_base linked list ended up with a circular link. + * If that happens, then once all the traversed elements have their + * LSF_NEEDS_FIXUP flag cleared, the traversal attempt will run wild. + * + * The circular list instance was blamed on attempting to restore + * a save file which should have been invalidated by version/patch/ + * editlevel verification, but wasn't rejected because EDITLEVEL + * didn't get incremented when it should have been. Valid data should + * never produce the problem and it isn't possible in general to guard + * against code updates that neglect to set the verification info up + * to date. + */ + + for (ls = gl.light_base; ls; ls = ls->next) { if (ls->flags & LSF_NEEDS_FIXUP) { if (ls->type == LS_OBJECT || ls->type == LS_MONSTER) { - if (ghostly) { - if (!lookup_id_mapping(ls->id.a_uint, &nid)) - impossible("relink_light_sources: no id mapping"); - } else - nid = ls->id.a_uint; + nid = ls->id.a_uint; + if (ghostly && !lookup_id_mapping(nid, &nid)) + panic("relink_light_sources: no id mapping"); + + which = '\0'; if (ls->type == LS_OBJECT) { - which = 'o'; - ls->id.a_obj = find_oid(nid); + if ((ls->id.a_obj = find_oid(nid)) == 0) + which = 'o'; } else { - which = 'm'; - ls->id.a_monst = find_mid(nid, FM_EVERYWHERE); + if ((ls->id.a_monst = find_mid(nid, FM_EVERYWHERE)) == 0) + which = 'm'; } - if (!ls->id.a_monst) - impossible("relink_light_sources: cant find %c_id %d", - which, nid); - } else - impossible("relink_light_sources: bad type (%d)", ls->type); - + if (which != '\0') + panic("relink_light_sources: can't find %c_id %u", + which, nid); + } else { + panic("relink_light_sources: bad type (%d)", ls->type); + } ls->flags &= ~LSF_NEEDS_FIXUP; } } @@ -437,15 +567,13 @@ boolean ghostly; * sources that would be written. If write_it is true, actually write * the light source out. */ -STATIC_OVL int -maybe_write_ls(fd, range, write_it) -int fd, range; -boolean write_it; +staticfn int +maybe_write_ls(NHFILE *nhfp, int range, boolean write_it) { int count = 0, is_global; light_source *ls; - for (ls = light_base; ls; ls = ls->next) { + for (ls = gl.light_base; ls; ls = ls->next) { if (!ls->id.a_monst) { impossible("maybe_write_ls: no id! [range=%d]", range); continue; @@ -467,7 +595,7 @@ boolean write_it; if (is_global ^ (range == RANGE_LEVEL)) { count++; if (write_it) - write_ls(fd, ls); + write_ls(nhfp, ls); } } @@ -475,18 +603,18 @@ boolean write_it; } void -light_sources_sanity_check() +light_sources_sanity_check(void) { light_source *ls; struct monst *mtmp; struct obj *otmp; unsigned int auint; - for (ls = light_base; ls; ls = ls->next) { + for (ls = gl.light_base; ls; ls = ls->next) { if (!ls->id.a_monst) panic("insane light source: no id!"); if (ls->type == LS_OBJECT) { - otmp = (struct obj *) ls->id.a_obj; + otmp = ls->id.a_obj; auint = otmp->o_id; if (find_oid(auint) != otmp) panic("insane light source: can't find obj #%u!", auint); @@ -502,10 +630,8 @@ light_sources_sanity_check() } /* Write a light source structure to disk. */ -STATIC_OVL void -write_ls(fd, ls) -int fd; -light_source *ls; +staticfn void +write_ls(NHFILE *nhfp, light_source *ls) { anything arg_save; struct obj *otmp; @@ -513,29 +639,62 @@ light_source *ls; if (ls->type == LS_OBJECT || ls->type == LS_MONSTER) { if (ls->flags & LSF_NEEDS_FIXUP) { - bwrite(fd, (genericptr_t) ls, sizeof(light_source)); + Sfo_ls_t(nhfp, ls, "lightsource"); } else { /* replace object pointer with id for write, then put back */ arg_save = ls->id; if (ls->type == LS_OBJECT) { otmp = ls->id.a_obj; - ls->id = zeroany; + ls->id = cg.zeroany; ls->id.a_uint = otmp->o_id; - if (find_oid((unsigned) ls->id.a_uint) != otmp) + if (find_oid((unsigned) ls->id.a_uint) != otmp) { impossible("write_ls: can't find obj #%u!", ls->id.a_uint); + ls->flags |= LSF_IS_PROBLEMATIC; + } } else { /* ls->type == LS_MONSTER */ + unsigned monloc = 0; + mtmp = (struct monst *) ls->id.a_monst; - ls->id = zeroany; - ls->id.a_uint = mtmp->m_id; - if (find_mid((unsigned) ls->id.a_uint, FM_EVERYWHERE) != mtmp) - impossible("write_ls: can't find mon #%u!", - ls->id.a_uint); + + /* The monster pointer has been stashed in the light source + * for a while and while there is code meant to clean-up the + * light source aspects if a monster goes away, there have + * been some reports of light source issues, such as when + * going to the planes. + * + * Verify that the stashed monst pointer is still present + * in one of the monster chains before pulling subfield + * values such as m_id from it, to avoid any attempt to + * pull random m_id value from (now) freed memory. + * + * find_mid() disregards a DEADMONSTER, but whereis_mon() + * does not. */ + + if ((monloc = whereis_mon(mtmp, FM_EVERYWHERE)) != 0) { + ls->id = cg.zeroany; + ls->id.a_uint = mtmp->m_id; + if (find_mid((unsigned) ls->id.a_uint, monloc) != mtmp) { + impossible("write_ls: can't find mon%s #%u!", + DEADMONSTER(mtmp) ? " because it's dead" + : "", + ls->id.a_uint); + ls->flags |= LSF_IS_PROBLEMATIC; + } + } else { + impossible( + "write_ls: stashed monst ptr not in any chain"); + ls->flags |= LSF_IS_PROBLEMATIC; + } + } + if (ls->flags & LSF_IS_PROBLEMATIC) { + /* TODO: cleanup this ls, or skip writing it */ } ls->flags |= LSF_NEEDS_FIXUP; - bwrite(fd, (genericptr_t) ls, sizeof(light_source)); + Sfo_ls_t(nhfp, ls, "lightsource"); ls->id = arg_save; ls->flags &= ~LSF_NEEDS_FIXUP; + ls->flags &= ~LSF_IS_PROBLEMATIC; } } else { impossible("write_ls: bad type (%d)", ls->type); @@ -544,12 +703,11 @@ light_source *ls; /* Change light source's ID from src to dest. */ void -obj_move_light_source(src, dest) -struct obj *src, *dest; +obj_move_light_source(struct obj *src, struct obj *dest) { light_source *ls; - for (ls = light_base; ls; ls = ls->next) + for (ls = gl.light_base; ls; ls = ls->next) if (ls->type == LS_OBJECT && ls->id.a_obj == src) ls->id.a_obj = dest; src->lamplit = 0; @@ -558,9 +716,9 @@ struct obj *src, *dest; /* return true if there exist any light sources */ boolean -any_light_source() +any_light_source(void) { - return (boolean) (light_base != (light_source *) 0); + return (boolean) (gl.light_base != (light_source *) 0); } /* @@ -568,13 +726,12 @@ any_light_source() * only for burning light sources. */ void -snuff_light_source(x, y) -int x, y; +snuff_light_source(coordxy x, coordxy y) { light_source *ls; struct obj *obj; - for (ls = light_base; ls; ls = ls->next) + for (ls = gl.light_base; ls; ls = ls->next) /* * Is this position check valid??? Can I assume that the positions * will always be correct because the objects would have been @@ -603,8 +760,7 @@ int x, y; /* Return TRUE if object sheds any light at all. */ boolean -obj_sheds_light(obj) -struct obj *obj; +obj_sheds_light(struct obj *obj) { /* so far, only burning objects shed light */ return obj_is_burning(obj); @@ -612,22 +768,19 @@ struct obj *obj; /* Return TRUE if sheds light AND will be snuffed by end_burn(). */ boolean -obj_is_burning(obj) -struct obj *obj; +obj_is_burning(struct obj *obj) { - return (boolean) (obj->lamplit && (obj->otyp == MAGIC_LAMP - || ignitable(obj) + return (boolean) (obj->lamplit && (ignitable(obj) || artifact_light(obj))); } /* copy the light source(s) attached to src, and attach it/them to dest */ void -obj_split_light_source(src, dest) -struct obj *src, *dest; +obj_split_light_source(struct obj *src, struct obj *dest) { light_source *ls, *new_ls; - for (ls = light_base; ls; ls = ls->next) + for (ls = gl.light_base; ls; ls = ls->next) if (ls->type == LS_OBJECT && ls->id.a_obj == src) { /* * Insert the new source at beginning of list. This will @@ -640,11 +793,11 @@ struct obj *src, *dest; /* split candles may emit less light than original group */ ls->range = candle_light_range(src); new_ls->range = candle_light_range(dest); - vision_full_recalc = 1; /* in case range changed */ + gv.vision_full_recalc = 1; /* in case range changed */ } new_ls->id.a_obj = dest; - new_ls->next = light_base; - light_base = new_ls; + new_ls->next = gl.light_base; + gl.light_base = new_ls; dest->lamplit = 1; /* now an active light source */ } } @@ -652,8 +805,7 @@ struct obj *src, *dest; /* light source `src' has been folded into light source `dest'; used for merging lit candles and adding candle(s) to lit candelabrum */ void -obj_merge_light_sources(src, dest) -struct obj *src, *dest; +obj_merge_light_sources(struct obj *src, struct obj *dest) { light_source *ls; @@ -661,26 +813,24 @@ struct obj *src, *dest; if (src != dest) end_burn(src, TRUE); /* extinguish candles */ - for (ls = light_base; ls; ls = ls->next) + for (ls = gl.light_base; ls; ls = ls->next) if (ls->type == LS_OBJECT && ls->id.a_obj == dest) { ls->range = candle_light_range(dest); - vision_full_recalc = 1; /* in case range changed */ + gv.vision_full_recalc = 1; /* in case range changed */ break; } } /* light source `obj' is being made brighter or dimmer */ void -obj_adjust_light_radius(obj, new_radius) -struct obj *obj; -int new_radius; +obj_adjust_light_radius(struct obj *obj, int new_radius) { light_source *ls; - for (ls = light_base; ls; ls = ls->next) + for (ls = gl.light_base; ls; ls = ls->next) if (ls->type == LS_OBJECT && ls->id.a_obj == obj) { if (new_radius != ls->range) - vision_full_recalc = 1; + gv.vision_full_recalc = 1; ls->range = new_radius; return; } @@ -690,8 +840,7 @@ int new_radius; /* Candlelight is proportional to the number of candles; minimum range is 2 rather than 1 for playability. */ int -candle_light_range(obj) -struct obj *obj; +candle_light_range(struct obj *obj) { int radius; @@ -706,20 +855,19 @@ struct obj *obj; radius = (obj->spe < 4) ? 2 : (obj->spe < 7) ? 3 : 4; } else if (Is_candle(obj)) { /* - * Range is incremented by powers of 7 so that it will take - * wizard mode quantities of candles to get more light than - * from a lamp, without imposing an arbitrary limit. - * 1..6 candles, range 2; - * 7..48 candles, range 3; - * 49..342 candles, range 4; &c. + * Range is incremented quadratically. You can get the same + * amount of light as from a lamp with 4 candles, and + * even better light with 9 candles, and so on. + * 1..3 candles, range 2; + * 4..8 candles, range 3; + * 9..15 candles, range 4; &c. */ long n = obj->quan; radius = 1; /* always incremented at least once */ - do { + while(radius*radius <= n && radius < MAX_RADIUS) { radius++; - n /= 7L; - } while (n > 0L); + } } else { /* we're only called for lit candelabrum or candles */ /* impossible("candlelight for %d?", obj->otyp); */ @@ -730,9 +878,10 @@ struct obj *obj; /* light emitting artifact's range depends upon its curse/bless state */ int -arti_light_radius(obj) -struct obj *obj; +arti_light_radius(struct obj *obj) { + int res; + /* * Used by begin_burn() when setting up a new light source * (obj->lamplit will already be set by this point) and @@ -747,29 +896,43 @@ struct obj *obj; /* cursed radius of 1 is not noticeable for an item that's carried by the hero but is if it's carried by a monster or left lit on the floor (not applicable for Sunsword) */ - return (obj->blessed ? 3 : !obj->cursed ? 2 : 1); + res = (obj->blessed ? 3 : !obj->cursed ? 2 : 1); + + /* if poly'd into gold dragon with embedded scales, make the scales + have minimum radiance (hero as light source will use light radius + based on monster form); otherwise, worn gold DSM gives off more + light than other light sources */ + if (obj == uskin) + res = 1; + else if (obj->otyp == GOLD_DRAGON_SCALE_MAIL) /* DSM but not scales */ + ++res; + + return res; } -/* adverb describing lit artifact's light; depends on curse/bless state */ +/* adverb describing lit artifact's light; radius varies depending upon + curse/bless state; also used for gold dragon scales/scale mail */ const char * -arti_light_description(obj) -struct obj *obj; +arti_light_description(struct obj *obj) { switch (arti_light_radius(obj)) { + case 4: + return "radiantly"; /* blessed gold dragon scale mail */ case 3: - return "brilliantly"; /* blessed */ + return "brilliantly"; /* blessed artifact, uncursed gold DSM */ case 2: - return "brightly"; /* uncursed */ + return "brightly"; /* uncursed artifact, cursed gold DSM */ case 1: - return "dimly"; /* cursed */ + return "dimly"; /* cursed artifact, embedded scales */ default: break; } return "strangely"; } +/* the #lightsources command */ int -wiz_light_sources() +wiz_light_sources(void) { winid win; char buf[BUFSZ]; @@ -777,16 +940,16 @@ wiz_light_sources() win = create_nhwindow(NHW_MENU); /* corner text window */ if (win == WIN_ERR) - return 0; + return ECMD_OK; Sprintf(buf, "Mobile light sources: hero @ (%2d,%2d)", u.ux, u.uy); putstr(win, 0, buf); putstr(win, 0, ""); - if (light_base) { + if (gl.light_base) { putstr(win, 0, "location range flags type id"); putstr(win, 0, "-------- ----- ------ ---- -------"); - for (ls = light_base; ls; ls = ls->next) { + for (ls = gl.light_base; ls; ls = ls->next) { Sprintf(buf, " %2d,%2d %2d 0x%04x %s %s", ls->x, ls->y, ls->range, ls->flags, (ls->type == LS_OBJECT @@ -794,7 +957,7 @@ wiz_light_sources() : ls->type == LS_MONSTER ? (mon_is_local(ls->id.a_monst) ? "mon" - : (ls->id.a_monst == &youmonst) + : (ls->id.a_monst == &gy.youmonst) ? "you" /* migrating monster */ : "") @@ -808,7 +971,15 @@ wiz_light_sources() display_nhwindow(win, FALSE); destroy_nhwindow(win); - return 0; + return ECMD_OK; } +#endif /* !SFCTOOL */ + +/* for 'onefile' processing where end of this file isn't necessarily the + end of the source code seen by the compiler */ +#undef LSF_SHOW +#undef LSF_NEEDS_FIXUP +#undef LSF_IS_PROBLEMATIC +#undef mon_is_local /*light.c*/ diff --git a/src/lock.c b/src/lock.c index 270ab1866..7f62eb890 100644 --- a/src/lock.c +++ b/src/lock.c @@ -1,32 +1,22 @@ -/* NetHack 3.6 lock.c $NHDT-Date: 1548978605 2019/01/31 23:50:05 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.84 $ */ +/* NetHack 5.0 lock.c $NHDT-Date: 1741793439 2025/03/12 07:30:39 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.145 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2011. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -/* at most one of `door' and `box' should be non-null at any given time */ -STATIC_VAR NEARDATA struct xlock_s { - struct rm *door; - struct obj *box; - int picktyp, /* key|pick|card for unlock, sharp vs blunt for #force */ - chance, usedtime; - boolean magic_key; -} xlock; - /* occupation callbacks */ -STATIC_PTR int NDECL(picklock); -STATIC_PTR int NDECL(forcelock); +staticfn int picklock(void); +staticfn int forcelock(void); -STATIC_DCL const char *NDECL(lock_action); -STATIC_DCL boolean FDECL(obstructed, (int, int, BOOLEAN_P)); -STATIC_DCL void FDECL(chest_shatter_msg, (struct obj *)); +staticfn const char *lock_action(void); +staticfn boolean obstructed(coordxy, coordxy, boolean); +staticfn void chest_shatter_msg(struct obj *); boolean -picking_lock(x, y) -int *x, *y; +picking_lock(coordxy *x, coordxy *y) { - if (occupation == picklock) { + if (go.occupation == picklock) { *x = u.ux + u.dx; *y = u.uy + u.dy; return TRUE; @@ -37,18 +27,18 @@ int *x, *y; } boolean -picking_at(x, y) -int x, y; +picking_at(coordxy x, coordxy y) { - return (boolean) (occupation == picklock && xlock.door == &levl[x][y]); + return (boolean) (go.occupation == picklock + && gx.xlock.door == &levl[x][y]); } /* produce an occupation string appropriate for the current activity */ -STATIC_OVL const char * -lock_action() +staticfn const char * +lock_action(void) { /* "unlocking"+2 == "locking" */ - static const char *actions[] = { + static const char *const actions[] = { "unlocking the door", /* [0] */ "unlocking the chest", /* [1] */ "unlocking the box", /* [2] */ @@ -56,81 +46,84 @@ lock_action() }; /* if the target is currently unlocked, we're trying to lock it now */ - if (xlock.door && !(xlock.door->doormask & D_LOCKED)) + if (gx.xlock.door && !(gx.xlock.door->doormask & D_LOCKED)) return actions[0] + 2; /* "locking the door" */ - else if (xlock.box && !xlock.box->olocked) - return xlock.box->otyp == CHEST ? actions[1] + 2 : actions[2] + 2; + else if (gx.xlock.box && !gx.xlock.box->olocked) + return gx.xlock.box->otyp == CHEST ? actions[1] + 2 : actions[2] + 2; /* otherwise we're trying to unlock it */ - else if (xlock.picktyp == LOCK_PICK) + else if (gx.xlock.picktyp == LOCK_PICK) return actions[3]; /* "picking the lock" */ - else if (xlock.picktyp == CREDIT_CARD) + else if (gx.xlock.picktyp == CREDIT_CARD) return actions[3]; /* same as lock_pick */ - else if (xlock.door) + else if (gx.xlock.door) return actions[0]; /* "unlocking the door" */ - else if (xlock.box) - return xlock.box->otyp == CHEST ? actions[1] : actions[2]; + else if (gx.xlock.box) + return gx.xlock.box->otyp == CHEST ? actions[1] : actions[2]; else return actions[3]; } /* try to open/close a lock */ -STATIC_PTR int -picklock(VOID_ARGS) +staticfn int +picklock(void) { - if (xlock.box) { - if (xlock.box->where != OBJ_FLOOR - || xlock.box->ox != u.ux || xlock.box->oy != u.uy) { - return ((xlock.usedtime = 0)); /* you or it moved */ + if (gx.xlock.box) { + if (gx.xlock.box->where != OBJ_FLOOR + || gx.xlock.box->ox != u.ux || gx.xlock.box->oy != u.uy) { + return ((gx.xlock.usedtime = 0)); /* you or it moved */ } } else { /* door */ - if (xlock.door != &(levl[u.ux + u.dx][u.uy + u.dy])) { - return ((xlock.usedtime = 0)); /* you moved */ + if (gx.xlock.door != &(levl[u.ux + u.dx][u.uy + u.dy])) { + return ((gx.xlock.usedtime = 0)); /* you moved */ } - switch (xlock.door->doormask) { + switch (gx.xlock.door->doormask) { case D_NODOOR: pline("This doorway has no door."); - return ((xlock.usedtime = 0)); + return ((gx.xlock.usedtime = 0)); case D_ISOPEN: You("cannot lock an open door."); - return ((xlock.usedtime = 0)); + return ((gx.xlock.usedtime = 0)); case D_BROKEN: pline("This door is broken."); - return ((xlock.usedtime = 0)); + return ((gx.xlock.usedtime = 0)); } } - if (xlock.usedtime++ >= 50 || nohands(youmonst.data)) { + if (gx.xlock.usedtime++ >= 50 || nohands(gy.youmonst.data)) { You("give up your attempt at %s.", lock_action()); exercise(A_DEX, TRUE); /* even if you don't succeed */ - return ((xlock.usedtime = 0)); + return ((gx.xlock.usedtime = 0)); } - if (rn2(100) >= xlock.chance) + if (rn2(100) >= gx.xlock.chance) return 1; /* still busy */ /* using the Master Key of Thievery finds traps if its bless/curse state is adequate (non-cursed for rogues, blessed for others; checked when setting up 'xlock') */ - if ((!xlock.door ? (int) xlock.box->otrapped - : (xlock.door->doormask & D_TRAPPED) != 0) - && xlock.magic_key) { - xlock.chance += 20; /* less effort needed next time */ - /* unfortunately we don't have a 'tknown' flag to record - "known to be trapped" so declining to disarm and then - retrying lock manipulation will find it all over again */ - if (yn("You find a trap! Do you want to try to disarm it?") == 'y') { + if ((!gx.xlock.door ? (int) gx.xlock.box->otrapped + : (gx.xlock.door->doormask & D_TRAPPED) != 0) + && gx.xlock.magic_key) { + gx.xlock.chance += 20; /* less effort needed next time */ + if (!gx.xlock.door) { + if (!gx.xlock.box->tknown) + You("find a trap!"); + gx.xlock.box->tknown = 1; + } + if (y_n("Do you want to try to disarm it?") == 'y') { const char *what; boolean alreadyunlocked; /* disarming while using magic key always succeeds */ - if (xlock.door) { - xlock.door->doormask &= ~D_TRAPPED; + if (gx.xlock.door) { + gx.xlock.door->doormask &= ~D_TRAPPED; what = "door"; - alreadyunlocked = !(xlock.door->doormask & D_LOCKED); + alreadyunlocked = !(gx.xlock.door->doormask & D_LOCKED); } else { - xlock.box->otrapped = 0; - what = (xlock.box->otyp == CHEST) ? "chest" : "box"; - alreadyunlocked = !xlock.box->olocked; + gx.xlock.box->otrapped = 0; + gx.xlock.box->tknown = 0; + what = (gx.xlock.box->otyp == CHEST) ? "chest" : "box"; + alreadyunlocked = !gx.xlock.box->olocked; } You("succeed in disarming the trap. The %s is still %slocked.", what, alreadyunlocked ? "un" : ""); @@ -139,36 +132,34 @@ picklock(VOID_ARGS) You("stop %s.", lock_action()); exercise(A_WIS, FALSE); } - return ((xlock.usedtime = 0)); + return ((gx.xlock.usedtime = 0)); } You("succeed in %s.", lock_action()); - if (xlock.door) { - if (xlock.door->doormask & D_TRAPPED) { + if (gx.xlock.door) { + if (gx.xlock.door->doormask & D_TRAPPED) { b_trapped("door", FINGER); - xlock.door->doormask = D_NODOOR; + gx.xlock.door->doormask = D_NODOOR; unblock_point(u.ux + u.dx, u.uy + u.dy); if (*in_rooms(u.ux + u.dx, u.uy + u.dy, SHOPBASE)) add_damage(u.ux + u.dx, u.uy + u.dy, SHOP_DOOR_COST); newsym(u.ux + u.dx, u.uy + u.dy); - } else if (xlock.door->doormask & D_LOCKED) - xlock.door->doormask = D_CLOSED; + } else if (gx.xlock.door->doormask & D_LOCKED) + gx.xlock.door->doormask = D_CLOSED; else - xlock.door->doormask = D_LOCKED; + gx.xlock.door->doormask = D_LOCKED; } else { - xlock.box->olocked = !xlock.box->olocked; - xlock.box->lknown = 1; - if (xlock.box->otrapped) - (void) chest_trap(xlock.box, FINGER, FALSE); + gx.xlock.box->olocked = !gx.xlock.box->olocked; + gx.xlock.box->lknown = 1; + if (gx.xlock.box->otrapped) + (void) chest_trap(gx.xlock.box, FINGER, FALSE); } exercise(A_DEX, TRUE); - return ((xlock.usedtime = 0)); + return ((gx.xlock.usedtime = 0)); } void -breakchestlock(box, destroyit) -struct obj *box; -boolean destroyit; +breakchestlock(struct obj *box, boolean destroyit) { if (!destroyit) { /* bill for the box but not for its contents */ struct obj *hide_contents = box->cobj; @@ -195,7 +186,8 @@ boolean destroyit; if (!rn2(3) || otmp->oclass == POTION_CLASS) { chest_shatter_msg(otmp); if (costly) - loss += stolen_value(otmp, u.ux, u.uy, peaceful_shk, TRUE); + loss += stolen_value(otmp, u.ux, u.uy, peaceful_shk, + TRUE); if (otmp->quan == 1L) { obfree(otmp, (struct obj *) 0); continue; @@ -205,7 +197,7 @@ boolean destroyit; useup(otmp); } if (box->otyp == ICE_BOX && otmp->otyp == CORPSE) { - otmp->age = monstermoves - otmp->age; /* actual age */ + otmp->age = svm.moves - otmp->age; /* actual age */ start_corpse_timeout(otmp); } place_object(otmp, u.ux, u.uy); @@ -220,20 +212,20 @@ boolean destroyit; } /* try to force a locked chest */ -STATIC_PTR int -forcelock(VOID_ARGS) +staticfn int +forcelock(void) { - if ((xlock.box->ox != u.ux) || (xlock.box->oy != u.uy)) - return ((xlock.usedtime = 0)); /* you or it moved */ + if ((gx.xlock.box->ox != u.ux) || (gx.xlock.box->oy != u.uy)) + return ((gx.xlock.usedtime = 0)); /* you or it moved */ - if (xlock.usedtime++ >= 50 || !uwep || nohands(youmonst.data)) { + if (gx.xlock.usedtime++ >= 50 || !uwep || nohands(gy.youmonst.data)) { You("give up your attempt to force the lock."); - if (xlock.usedtime >= 50) /* you made the effort */ - exercise((xlock.picktyp) ? A_DEX : A_STR, TRUE); - return ((xlock.usedtime = 0)); + if (gx.xlock.usedtime >= 50) /* you made the effort */ + exercise((gx.xlock.picktyp) ? A_DEX : A_STR, TRUE); + return ((gx.xlock.usedtime = 0)); } - if (xlock.picktyp) { /* blade */ + if (gx.xlock.picktyp) { /* blade */ if (rn2(1000 - (int) uwep->spe) > (992 - greatest_erosion(uwep) * 10) && !uwep->cursed && !obj_resists(uwep, 0, 99)) { /* for a +0 weapon, probability that it survives an unsuccessful @@ -244,81 +236,151 @@ forcelock(VOID_ARGS) useup(uwep); You("give up your attempt to force the lock."); exercise(A_DEX, TRUE); - return ((xlock.usedtime = 0)); + return ((gx.xlock.usedtime = 0)); } } else /* blunt */ - wake_nearby(); /* due to hammering on the container */ + wake_nearby(FALSE); /* due to hammering on the container */ - if (rn2(100) >= xlock.chance) + if (rn2(100) >= gx.xlock.chance) return 1; /* still busy */ You("succeed in forcing the lock."); - exercise(xlock.picktyp ? A_DEX : A_STR, TRUE); + exercise(gx.xlock.picktyp ? A_DEX : A_STR, TRUE); /* breakchestlock() might destroy xlock.box; if so, xlock context will be cleared (delobj -> obfree -> maybe_reset_pick); but it might not, so explicitly clear that manually */ - breakchestlock(xlock.box, (boolean) (!xlock.picktyp && !rn2(3))); + breakchestlock(gx.xlock.box, (boolean) (!gx.xlock.picktyp && !rn2(3))); reset_pick(); /* lock-picking context is no longer valid */ return 0; } void -reset_pick() +reset_pick(void) { - xlock.usedtime = xlock.chance = xlock.picktyp = 0; - xlock.magic_key = FALSE; - xlock.door = (struct rm *) 0; - xlock.box = (struct obj *) 0; + gx.xlock.usedtime = gx.xlock.chance = gx.xlock.picktyp = 0; + gx.xlock.magic_key = FALSE; + gx.xlock.door = (struct rm *) 0; + gx.xlock.box = (struct obj *) 0; } /* level change or object deletion; context may no longer be valid */ void -maybe_reset_pick(container) -struct obj *container; /* passed from obfree() */ +maybe_reset_pick(struct obj *container) /* passed from obfree() */ { /* * If a specific container, only clear context if it is for that * particular container (which is being deleted). Other stuff on * the current dungeon level remains valid. * However if 'container' is Null, clear context if not carrying - * xlock.box (which might be Null if context is for a door). + * gx.xlock.box (which might be Null if context is for a door). * Used for changing levels, where a floor container or a door is * being left behind and won't be valid on the new level but a * carried container will still be. There might not be any context, * in which case redundantly clearing it is harmless. */ - if (container ? (container == xlock.box) - : (!xlock.box || !carried(xlock.box))) + if (container ? (container == gx.xlock.box) + : (!gx.xlock.box || !carried(gx.xlock.box))) reset_pick(); } +/* pick a tool for autounlock */ +struct obj * +autokey(boolean opening) /* True: key, pick, or card; False: key or pick */ +{ + struct obj *o, *key, *pick, *card, *akey, *apick, *acard; + + /* mundane item or regular artifact or own role's quest artifact */ + key = pick = card = (struct obj *) 0; + /* other role's quest artifact (Rogue's Key or Tourist's Credit Card) */ + akey = apick = acard = (struct obj *) 0; + for (o = gi.invent; o; o = o->nobj) { + if (any_quest_artifact(o) && !is_quest_artifact(o)) { + switch (o->otyp) { + case SKELETON_KEY: + if (!akey) + akey = o; + break; + case LOCK_PICK: + if (!apick) + apick = o; + break; + case CREDIT_CARD: + if (!acard) + acard = o; + break; + default: + break; + } + } else { + switch (o->otyp) { + case SKELETON_KEY: + if (!key || is_magic_key(&gy.youmonst, o)) + key = o; + break; + case LOCK_PICK: + if (!pick) + pick = o; + break; + case CREDIT_CARD: + if (!card) + card = o; + break; + default: + break; + } + } + } + if (!opening) + card = acard = 0; + /* only resort to other role's quest artifact if no other choice */ + if (!key && !pick && !card) + key = akey; + if (!pick && !card) + pick = apick; + if (!card) + card = acard; + return key ? key : pick ? pick : card ? card : 0; +} + +DISABLE_WARNING_FORMAT_NONLITERAL + /* for doapply(); if player gives a direction or resumes an interrupted - previous attempt then it costs hero a move even if nothing ultimately - happens; when told "can't do that" before being asked for direction - or player cancels with ESC while giving direction, it doesn't */ + previous attempt then it usually costs hero a move even if nothing + ultimately happens; when told "can't do that" before being asked for + direction or player cancels with ESC while giving direction, it doesn't */ #define PICKLOCK_LEARNED_SOMETHING (-1) /* time passes */ #define PICKLOCK_DID_NOTHING 0 /* no time passes */ #define PICKLOCK_DID_SOMETHING 1 /* player is applying a key, lock pick, or credit card */ int -pick_lock(pick) -struct obj *pick; +pick_lock( + struct obj *pick, + coordxy rx, coordxy ry, /* coordinates of door/container, for autounlock: + * doesn't prompt for direction if these are set */ + struct obj *container) /* container, for autounlock */ { + struct obj dummypick; int picktyp, c, ch; coord cc; struct rm *door; struct obj *otmp; char qbuf[QBUFSZ]; + boolean autounlock = (rx != 0 || container != NULL); + /* 'pick' might be Null [called by do_loot_cont() for AUTOUNLOCK_UNTRAP] */ + if (!pick) { + dummypick = cg.zeroobj; + pick = &dummypick; /* pick->otyp will be STRANGE_OBJECT */ + } picktyp = pick->otyp; /* check whether we're resuming an interrupted previous attempt */ - if (xlock.usedtime && picktyp == xlock.picktyp) { + if (gx.xlock.usedtime && picktyp == gx.xlock.picktyp) { static char no_longer[] = "Unfortunately, you can no longer %s %s."; - if (nohands(youmonst.data)) { + if (nohands(gy.youmonst.data)) { const char *what = (picktyp == LOCK_PICK) ? "pick" : "key"; if (picktyp == CREDIT_CARD) @@ -326,7 +388,7 @@ struct obj *pick; pline(no_longer, "hold the", what); reset_pick(); return PICKLOCK_LEARNED_SOMETHING; - } else if (u.uswallow || (xlock.box && !can_reach_floor(TRUE))) { + } else if (u.uswallow || (gx.xlock.box && !can_reach_floor(TRUE))) { pline(no_longer, "reach the", "lock"); reset_pick(); return PICKLOCK_LEARNED_SOMETHING; @@ -334,13 +396,13 @@ struct obj *pick; const char *action = lock_action(); You("resume your attempt at %s.", action); - xlock.magic_key = is_magic_key(&youmonst, pick); + gx.xlock.magic_key = is_magic_key(&gy.youmonst, pick); set_occupation(picklock, action, 0); return PICKLOCK_DID_SOMETHING; } } - if (nohands(youmonst.data)) { + if (nohands(gy.youmonst.data)) { You_cant("hold %s -- you have no hands!", doname(pick)); return PICKLOCK_DID_NOTHING; } else if (u.uswallow) { @@ -349,24 +411,28 @@ struct obj *pick; return PICKLOCK_DID_NOTHING; } - if (picktyp != LOCK_PICK - && picktyp != CREDIT_CARD - && picktyp != SKELETON_KEY) { + if (pick != &dummypick && picktyp != SKELETON_KEY + && picktyp != LOCK_PICK && picktyp != CREDIT_CARD) { impossible("picking lock with object %d?", picktyp); return PICKLOCK_DID_NOTHING; } ch = 0; /* lint suppression */ - if (!get_adjacent_loc((char *) 0, "Invalid location!", u.ux, u.uy, &cc)) + if (rx != 0) { /* autounlock; caller has provided coordinates */ + cc.x = rx; + cc.y = ry; + } else if (!get_adjacent_loc((char *) 0, "Invalid location!", + u.ux, u.uy, &cc)) { return PICKLOCK_DID_NOTHING; + } - if (cc.x == u.ux && cc.y == u.uy) { /* pick lock on a container */ + if (u_at(cc.x, cc.y)) { /* pick lock on a container */ const char *verb; char qsfx[QBUFSZ]; boolean it; int count; - if (u.dz < 0) { + if (u.dz < 0 && !autounlock) { /* beware stale u.dz value */ There("isn't any sort of lock up %s.", Levitation ? "here" : "there"); return PICKLOCK_LEARNED_SOMETHING; @@ -380,7 +446,12 @@ struct obj *pick; count = 0; c = 'n'; /* in case there are no boxes here */ - for (otmp = level.objects[cc.x][cc.y]; otmp; otmp = otmp->nexthere) + for (otmp = svl.level.objects[cc.x][cc.y]; otmp; + otmp = otmp->nexthere) { + /* autounlock on boxes: only the one that was just discovered to + be locked; don't include any other boxes which might be here */ + if (autounlock && otmp != container) + continue; if (Is_box(otmp)) { ++count; if (!can_reach_floor(TRUE)) { @@ -397,26 +468,54 @@ struct obj *pick; else verb = "pick"; - /* "There is here; ?" */ - Sprintf(qsfx, " here; %s %s?", verb, it ? "it" : "its lock"); - (void) safe_qbuf(qbuf, "There is ", qsfx, otmp, doname, - ansimpleoname, "a box"); - otmp->lknown = 1; - - c = ynq(qbuf); - if (c == 'q') - return 0; - if (c == 'n') - continue; + if (autounlock && (flags.autounlock & AUTOUNLOCK_UNTRAP) != 0 + && could_untrap(FALSE, TRUE) + && (c = otmp->tknown ? (otmp->otrapped ? 'y' : 'n') + : ynq(safe_qbuf(qbuf, "Check ", " for a trap?", + otmp, yname, ysimple_name, "this"))) + != 'n') { + if (c == 'q') + return PICKLOCK_DID_NOTHING; /* c == 'q' */ + /* c == 'y' */ + untrap(FALSE, 0, 0, otmp); + return PICKLOCK_DID_SOMETHING; /* even if no trap found */ + } else if (autounlock + && (flags.autounlock & AUTOUNLOCK_APPLY_KEY) != 0) { + c = 'q'; + if (pick != &dummypick) { + Sprintf(qbuf, "Unlock it with %s?", yname(pick)); + c = ynq(qbuf); + } + if (c != 'y') + return PICKLOCK_DID_NOTHING; + } else { + /* "There is here; ?" */ + Sprintf(qsfx, " here; %s %s?", + verb, it ? "it" : "its lock"); + (void) safe_qbuf(qbuf, "There is ", qsfx, otmp, doname, + ansimpleoname, "a box"); + otmp->lknown = 1; + + c = ynq(qbuf); + if (c == 'q') + return PICKLOCK_DID_NOTHING; + if (c == 'n') + continue; /* try next box */ + } if (otmp->obroken) { - You_cant("fix its broken lock with %s.", doname(pick)); + You_cant("fix its broken lock with %s.", + ansimpleoname(pick)); return PICKLOCK_LEARNED_SOMETHING; } else if (picktyp == CREDIT_CARD && !otmp->olocked) { /* credit cards are only good for unlocking */ You_cant("do that with %s.", an(simple_typename(picktyp))); return PICKLOCK_LEARNED_SOMETHING; + } else if (autounlock + && !touch_artifact(pick, &gy.youmonst)) { + /* note: for !autounlock, apply already did touch check */ + return PICKLOCK_DID_SOMETHING; } switch (picktyp) { case CREDIT_CARD: @@ -434,21 +533,26 @@ struct obj *pick; if (otmp->cursed) ch /= 2; - xlock.box = otmp; - xlock.door = 0; + gx.xlock.box = otmp; + gx.xlock.door = 0; break; } + } if (c != 'y') { if (!count) There("doesn't seem to be any sort of lock here."); return PICKLOCK_LEARNED_SOMETHING; /* decided against all boxes */ } - } else { /* pick the lock in a door */ + + /* not the hero's location; pick the lock in an adjacent door */ + } else { struct monst *mtmp; if (u.utrap && u.utraptype == TT_PIT) { You_cant("reach over the edge of the pit."); - return PICKLOCK_LEARNED_SOMETHING; + /* this used to return PICKLOCK_LEARNED_SOMETHING but the + #open command doesn't use a turn for similar situation */ + return PICKLOCK_DID_NOTHING; } door = &levl[cc.x][cc.y]; @@ -456,11 +560,13 @@ struct obj *pick; if (mtmp && canseemon(mtmp) && M_AP_TYPE(mtmp) != M_AP_FURNITURE && M_AP_TYPE(mtmp) != M_AP_OBJECT) { if (picktyp == CREDIT_CARD - && (mtmp->isshk || mtmp->data == &mons[PM_ORACLE])) + && (mtmp->isshk || mtmp->data == &mons[PM_ORACLE])) { + SetVoice(mtmp, 0, 80, 0); verbalize("No checks, no credit, no problem."); - else + } else { pline("I don't think %s would appreciate that.", mon_nam(mtmp)); + } return PICKLOCK_LEARNED_SOMETHING; } else if (mtmp && is_door_mappear(mtmp)) { /* "The door actually was a !" */ @@ -470,11 +576,20 @@ struct obj *pick; return PICKLOCK_LEARNED_SOMETHING; } if (!IS_DOOR(door->typ)) { + int res = PICKLOCK_DID_NOTHING, oldglyph = door->glyph; + schar oldlastseentyp = update_mapseen_for(cc.x, cc.y); + + /* this is probably only relevant when blind */ + feel_location(cc.x, cc.y); + if (door->glyph != oldglyph + || svl.lastseentyp[cc.x][cc.y] != oldlastseentyp) + res = PICKLOCK_LEARNED_SOMETHING; + if (is_drawbridge_wall(cc.x, cc.y) >= 0) You("%s no lock on the drawbridge.", Blind ? "feel" : "see"); else You("%s no door there.", Blind ? "feel" : "see"); - return PICKLOCK_LEARNED_SOMETHING; + return res; } switch (door->doormask) { case D_NODOOR: @@ -487,18 +602,32 @@ struct obj *pick; pline("This door is broken."); return PICKLOCK_LEARNED_SOMETHING; default: + if ((flags.autounlock & AUTOUNLOCK_UNTRAP) != 0 + && could_untrap(FALSE, FALSE) + && (c = ynq("Check this door for a trap?")) != 'n') { + if (c == 'q') + return PICKLOCK_DID_NOTHING; + /* c == 'y' */ + untrap(FALSE, cc.x, cc.y, (struct obj *) 0); + return PICKLOCK_DID_SOMETHING; /* even if no trap found */ + } /* credit cards are only good for unlocking */ if (picktyp == CREDIT_CARD && !(door->doormask & D_LOCKED)) { You_cant("lock a door with a credit card."); return PICKLOCK_LEARNED_SOMETHING; } - Sprintf(qbuf, "%s it?", - (door->doormask & D_LOCKED) ? "Unlock" : "Lock"); + Sprintf(qbuf, "%s it%s%s?", + (door->doormask & D_LOCKED) ? "Unlock" : "Lock", + autounlock ? " with " : "", + autounlock ? yname(pick) : ""); + c = ynq(qbuf); + if (c != 'y') + return PICKLOCK_DID_NOTHING; - c = yn(qbuf); - if (c == 'n') - return 0; + /* note: for !autounlock, 'apply' already did touch check */ + if (autounlock && !touch_artifact(pick, &gy.youmonst)) + return PICKLOCK_DID_SOMETHING; switch (picktyp) { case CREDIT_CARD: @@ -513,59 +642,78 @@ struct obj *pick; default: ch = 0; } - xlock.door = door; - xlock.box = 0; + gx.xlock.door = door; + gx.xlock.box = 0; } } - context.move = 0; - xlock.chance = ch; - xlock.picktyp = picktyp; - xlock.magic_key = is_magic_key(&youmonst, pick); - xlock.usedtime = 0; + svc.context.move = 0; + gx.xlock.chance = ch; + gx.xlock.picktyp = picktyp; + gx.xlock.magic_key = is_magic_key(&gy.youmonst, pick); + gx.xlock.usedtime = 0; set_occupation(picklock, lock_action(), 0); return PICKLOCK_DID_SOMETHING; } -/* try to force a chest with your weapon */ +/* is hero wielding a weapon that can #force? */ +boolean +u_have_forceable_weapon(void) +{ + if (!uwep /* proper type test */ + || ((uwep->oclass == WEAPON_CLASS || is_weptool(uwep)) + ? (objects[uwep->otyp].oc_skill < P_DAGGER + || objects[uwep->otyp].oc_skill == P_FLAIL + || objects[uwep->otyp].oc_skill > P_LANCE) + : uwep->oclass != ROCK_CLASS)) + return FALSE; + return TRUE; +} + +RESTORE_WARNING_FORMAT_NONLITERAL + +/* the #force command - try to force a chest with your weapon */ int -doforce() +doforce(void) { - register struct obj *otmp; - register int c, picktyp; + struct obj *otmp; + int c, picktyp; char qbuf[QBUFSZ]; + /* + * TODO? + * allow force with edged weapon to be performed on doors. + */ + if (u.uswallow) { You_cant("force anything from inside here."); - return 0; + return ECMD_OK; } - if (!uwep /* proper type test */ - || ((uwep->oclass == WEAPON_CLASS || is_weptool(uwep)) - ? (objects[uwep->otyp].oc_skill < P_DAGGER - || objects[uwep->otyp].oc_skill == P_FLAIL - || objects[uwep->otyp].oc_skill > P_LANCE) - : uwep->oclass != ROCK_CLASS)) { - You_cant("force anything %s weapon.", + if (!u_have_forceable_weapon()) { + boolean use_plural = uwep && uwep->quan > 1; + + You_cant("force anything %s weapon%s.", !uwep ? "when not wielding a" - : (uwep->oclass != WEAPON_CLASS && !is_weptool(uwep)) - ? "without a proper" - : "with that"); - return 0; + : (uwep->oclass != WEAPON_CLASS && !is_weptool(uwep)) + ? (use_plural ? "without proper" : "without a proper") + : (use_plural ? "with those" : "with that"), + use_plural ? "s" : ""); + return ECMD_OK; } if (!can_reach_floor(TRUE)) { - cant_reach_floor(u.ux, u.uy, FALSE, TRUE); - return 0; + cant_reach_floor(u.ux, u.uy, FALSE, TRUE, FALSE); + return ECMD_OK; } picktyp = is_blade(uwep) && !is_pick(uwep); - if (xlock.usedtime && xlock.box && picktyp == xlock.picktyp) { + if (gx.xlock.usedtime && gx.xlock.box && picktyp == gx.xlock.picktyp) { You("resume your attempt to force the lock."); set_occupation(forcelock, "forcing the lock", 0); - return 1; + return ECMD_TIME; } /* A lock is made only for the honest man, the thief will break it. */ - xlock.box = (struct obj *) 0; - for (otmp = level.objects[u.ux][u.uy]; otmp; otmp = otmp->nexthere) + gx.xlock.box = (struct obj *) 0; + for (otmp = svl.level.objects[u.ux][u.uy]; otmp; otmp = otmp->nexthere) if (Is_box(otmp)) { if (otmp->obroken || !otmp->olocked) { /* force doname() to omit known "broken" or "unlocked" @@ -584,7 +732,7 @@ doforce() c = ynq(qbuf); if (c == 'q') - return 0; + return ECMD_OK; if (c == 'n') continue; @@ -592,24 +740,23 @@ doforce() You("force %s into a crack and pry.", yname(uwep)); else You("start bashing it with %s.", yname(uwep)); - xlock.box = otmp; - xlock.chance = objects[uwep->otyp].oc_wldam * 2; - xlock.picktyp = picktyp; - xlock.magic_key = FALSE; - xlock.usedtime = 0; + gx.xlock.box = otmp; + gx.xlock.chance = objects[uwep->otyp].oc_wldam * 2; + gx.xlock.picktyp = picktyp; + gx.xlock.magic_key = FALSE; + gx.xlock.usedtime = 0; break; } - if (xlock.box) + if (gx.xlock.box) set_occupation(forcelock, "forcing the lock", 0); else You("decide not to force the issue."); - return 1; + return ECMD_TIME; } boolean -stumble_on_door_mimic(x, y) -int x, y; +stumble_on_door_mimic(coordxy x, coordxy y) { struct monst *mtmp; @@ -621,61 +768,74 @@ int x, y; return FALSE; } -/* the 'O' command - try to open a door */ +/* the #open command - try to open a door */ int -doopen() +doopen(void) { return doopen_indir(0, 0); } /* try to open a door in direction u.dx/u.dy */ int -doopen_indir(x, y) -int x, y; +doopen_indir(coordxy x, coordxy y) { coord cc; - register struct rm *door; + struct rm *door; boolean portcullis; - int res = 0; + const char *dirprompt; + int res = ECMD_OK; - if (nohands(youmonst.data)) { + if (nohands(gy.youmonst.data)) { You_cant("open anything -- you have no hands!"); - return 0; + return ECMD_OK; } - if (u.utrap && u.utraptype == TT_PIT) { - You_cant("reach over the edge of the pit."); - return 0; - } + dirprompt = NULL; /* have get_adjacent_loc() -> getdir() use default */ + if (u.utrap && u.utraptype == TT_PIT && container_at(u.ux, u.uy, FALSE)) + dirprompt = "Open where? [.>]"; - if (x > 0 && y > 0) { + if (x > 0 && y >= 0) { + /* nonzero is used when hero in amorphous form tries to + flow under a closed door at ; the test here was using + 'y > 0' but that would give incorrect results if doors are + ever allowed to be placed on the top row of the map */ cc.x = x; cc.y = y; - } else if (!get_adjacent_loc((char *) 0, (char *) 0, u.ux, u.uy, &cc)) - return 0; + } else if (!get_adjacent_loc(dirprompt, (char *) 0, u.ux, u.uy, &cc)) { + return ECMD_OK; + } - /* open at yourself/up/down */ - if ((cc.x == u.ux) && (cc.y == u.uy)) + /* open at yourself/up/down: switch to loot unless there is a closed + door here (possible with Passes_walls) and direction isn't 'down' */ + if (u_at(cc.x, cc.y) && (u.dz > 0 || !closed_door(u.ux, u.uy))) return doloot(); + /* this used to be done prior to get_adjacent_loc() but doing so was + incorrect once open at hero's spot became an alternate way to loot */ + if (u.utrap && u.utraptype == TT_PIT) { + You_cant("reach over the edge of the pit."); + return ECMD_OK; + } + if (stumble_on_door_mimic(cc.x, cc.y)) - return 1; + return ECMD_TIME; /* when choosing a direction is impaired, use a turn - regardless of whether a door is successfully targetted */ + regardless of whether a door is successfully targeted */ if (Confusion || Stunned) - res = 1; + res = ECMD_TIME; door = &levl[cc.x][cc.y]; portcullis = (is_drawbridge_wall(cc.x, cc.y) >= 0); - if (Blind) { + /* this used to be 'if (Blind)' but using a key skips that so we do too */ + { int oldglyph = door->glyph; - schar oldlastseentyp = lastseentyp[cc.x][cc.y]; + schar oldlastseentyp = update_mapseen_for(cc.x, cc.y); - feel_location(cc.x, cc.y); + newsym(cc.x, cc.y); if (door->glyph != oldglyph - || lastseentyp[cc.x][cc.y] != oldlastseentyp) - res = 1; /* learned something */ + || svl.lastseentyp[cc.x][cc.y] != oldlastseentyp) + res = ECMD_TIME; /* learned something */ } if (portcullis || !IS_DOOR(door->typ)) { @@ -694,6 +854,7 @@ int x, y; if (!(door->doormask & D_CLOSED)) { const char *mesg; + boolean locked = FALSE; switch (door->doormask) { case D_BROKEN: @@ -707,19 +868,41 @@ int x, y; break; default: mesg = " is locked"; + locked = TRUE; break; } + set_msg_xy(cc.x, cc.y); pline("This door%s.", mesg); + if (locked && flags.autounlock) { + struct obj *unlocktool; + + u.dz = 0; /* should already be 0 since hero moved toward door */ + if ((flags.autounlock & AUTOUNLOCK_APPLY_KEY) != 0 + && (unlocktool = autokey(TRUE)) != 0) { + res = pick_lock(unlocktool, cc.x, cc.y, + (struct obj *) 0) ? ECMD_TIME : ECMD_OK; + } else if ((flags.autounlock & AUTOUNLOCK_KICK) != 0 + && !u.usteed /* kicking is different when mounted */ + && ynq("Kick it?") == 'y') { + cmdq_add_ec(CQ_CANNED, dokick); + cmdq_add_dir(CQ_CANNED, + sgn(cc.x - u.ux), sgn(cc.y - u.uy), 0); + /* this was 'ECMD_TIME', but time shouldn't elapse until + the canned kick takes place */ + res = ECMD_OK; + } + } return res; } - if (verysmall(youmonst.data)) { + if (verysmall(gy.youmonst.data)) { pline("You're too small to pull the door open."); return res; } /* door is known to be CLOSED */ if (rnl(20) < (ACURRSTR + ACURR(A_DEX) + ACURR(A_CON)) / 3) { + set_msg_xy(cc.x, cc.y); pline_The("door opens."); if (door->doormask & D_TRAPPED) { b_trapped("door", FINGER); @@ -729,35 +912,32 @@ int x, y; } else door->doormask = D_ISOPEN; feel_newsym(cc.x, cc.y); /* the hero knows she opened it */ - unblock_point(cc.x, cc.y); /* vision: new see through there */ + recalc_block_point(cc.x, cc.y); /* vision: new see through there */ } else { exercise(A_STR, TRUE); + set_msg_xy(cc.x, cc.y); pline_The("door resists!"); } - return 1; + return ECMD_TIME; } -STATIC_OVL boolean -obstructed(x, y, quietly) -register int x, y; -boolean quietly; +staticfn boolean +obstructed(coordxy x, coordxy y, boolean quietly) { - register struct monst *mtmp = m_at(x, y); + struct monst *mtmp = m_at(x, y); if (mtmp && M_AP_TYPE(mtmp) != M_AP_FURNITURE) { if (M_AP_TYPE(mtmp) == M_AP_OBJECT) goto objhere; if (!quietly) { - if ((mtmp->mx != x) || (mtmp->my != y)) { - /* worm tail */ - pline("%s%s blocks the way!", - !canspotmon(mtmp) ? Something : s_suffix(Monnam(mtmp)), - !canspotmon(mtmp) ? "" : " tail"); - } else { - pline("%s blocks the way!", - !canspotmon(mtmp) ? "Some creature" : Monnam(mtmp)); - } + char *Mn = Some_Monnam(mtmp); /* Monnam, Someone or Something */ + + if ((mtmp->mx != x || mtmp->my != y) && canspotmon(mtmp)) + /* s_suffix() returns a modifiable buffer */ + Mn = strcat(s_suffix(Mn), " tail"); + + pline("%s blocks the way!", Mn); } if (!canspotmon(mtmp)) map_invisible(x, y); @@ -772,55 +952,56 @@ boolean quietly; return FALSE; } -/* the 'C' command - try to close a door */ +/* the #close command - try to close a door */ int -doclose() +doclose(void) { - register int x, y; - register struct rm *door; + coordxy x, y; + struct rm *door; boolean portcullis; - int res = 0; + int res = ECMD_OK; - if (nohands(youmonst.data)) { + if (nohands(gy.youmonst.data)) { You_cant("close anything -- you have no hands!"); - return 0; + return ECMD_OK; } if (u.utrap && u.utraptype == TT_PIT) { You_cant("reach over the edge of the pit."); - return 0; + return ECMD_OK; } if (!getdir((char *) 0)) - return 0; + return ECMD_CANCEL; x = u.ux + u.dx; y = u.uy + u.dy; - if ((x == u.ux) && (y == u.uy)) { + if (u_at(x, y) && !Passes_walls) { You("are in the way!"); - return 1; + return ECMD_TIME; } if (!isok(x, y)) goto nodoor; if (stumble_on_door_mimic(x, y)) - return 1; + return ECMD_TIME; /* when choosing a direction is impaired, use a turn - regardless of whether a door is successfully targetted */ + regardless of whether a door is successfully targeted */ if (Confusion || Stunned) - res = 1; + res = ECMD_TIME; door = &levl[x][y]; portcullis = (is_drawbridge_wall(x, y) >= 0); if (Blind) { int oldglyph = door->glyph; - schar oldlastseentyp = lastseentyp[x][y]; + schar oldlastseentyp = update_mapseen_for(x, y); feel_location(x, y); - if (door->glyph != oldglyph || lastseentyp[x][y] != oldlastseentyp) - res = 1; /* learned something */ + if (door->glyph != oldglyph + || svl.lastseentyp[x][y] != oldlastseentyp) + res = ECMD_TIME; /* learned something */ } if (portcullis || !IS_DOOR(door->typ)) { @@ -850,7 +1031,7 @@ doclose() } if (door->doormask == D_ISOPEN) { - if (verysmall(youmonst.data) && !u.usteed) { + if (verysmall(gy.youmonst.data) && !u.usteed) { pline("You're too small to push the door closed."); return res; } @@ -866,14 +1047,13 @@ doclose() } } - return 1; + return ECMD_TIME; } /* box obj was hit with spell or wand effect otmp; returns true if something happened */ boolean -boxlock(obj, otmp) -struct obj *obj, *otmp; /* obj *is* a box */ +boxlock(struct obj *obj, struct obj *otmp) /* obj *is* a box */ { boolean res = 0; @@ -881,6 +1061,7 @@ struct obj *obj, *otmp; /* obj *is* a box */ case WAN_LOCKING: case SPE_WIZARD_LOCK: if (!obj->olocked) { /* lock it; fix if broken */ + Soundeffect(se_klunk, 50); pline("Klunk!"); obj->olocked = 1; obj->obroken = 0; @@ -893,7 +1074,8 @@ struct obj *obj, *otmp; /* obj *is* a box */ break; case WAN_OPENING: case SPE_KNOCK: - if (obj->olocked) { /* unlock; couldn't be broken */ + if (obj->olocked) { /* unlock; isn't broken so doesn't need fixing */ + Soundeffect(se_klick, 50); pline("Klick!"); obj->olocked = 0; res = 1; @@ -908,7 +1090,7 @@ struct obj *obj, *otmp; /* obj *is* a box */ case SPE_POLYMORPH: /* maybe start unlocking chest, get interrupted, then zap it; we must avoid any attempt to resume unlocking it */ - if (xlock.box == obj) + if (gx.xlock.box == obj) reset_pick(); break; } @@ -918,11 +1100,9 @@ struct obj *obj, *otmp; /* obj *is* a box */ /* Door/secret door was hit with spell or wand effect otmp; returns true if something happened */ boolean -doorlock(otmp, x, y) -struct obj *otmp; -int x, y; +doorlock(struct obj *otmp, coordxy x, coordxy y) { - register struct rm *door = &levl[x][y]; + struct rm *door = &levl[x][y]; boolean res = TRUE; int loudness = 0; const char *msg = (const char *) 0; @@ -956,12 +1136,15 @@ int x, y; case SPE_WIZARD_LOCK: if (Is_rogue_level(&u.uz)) { boolean vis = cansee(x, y); + /* Can't have real locking in Rogue, so just hide doorway */ - if (vis) + if (vis) { pline("%s springs up in the older, more primitive doorway.", dustcloud); - else + } else { + Soundeffect(se_swoosh, 25); You_hear("a swoosh."); + } if (obstructed(x, y, mysterywand)) { if (vis) pline_The("cloud %s.", quickly_dissipates); @@ -1018,32 +1201,51 @@ int x, y; case WAN_STRIKING: case SPE_FORCE_BOLT: if (door->doormask & (D_LOCKED | D_CLOSED)) { + /* sawit: closed door location is more visible than open */ + boolean sawit, seeit; + if (door->doormask & D_TRAPPED) { - if (MON_AT(x, y)) - (void) mb_trapped(m_at(x, y)); - else if (flags.verbose) { - if (cansee(x, y)) - pline("KABOOM!! You see a door explode."); - else - You_hear("a distant explosion."); - } + struct monst *mtmp = m_at(x, y); + + sawit = mtmp ? canseemon(mtmp) : cansee(x, y); door->doormask = D_NODOOR; unblock_point(x, y); newsym(x, y); - loudness = 40; + seeit = mtmp ? canseemon(mtmp) : cansee(x, y); + if (mtmp) { + (void) mb_trapped(mtmp, sawit || seeit); + } else { + /* for mtmp, mb_trapped() does is own wake_nearto() */ + loudness = 40; + if (flags.verbose) { + Soundeffect(se_kaboom_door_explodes, 75); + if ((sawit || seeit) && !Unaware) { + pline("KABOOM!! You see a door explode."); + } else if (!Deaf) { + Soundeffect(se_explosion, 75); + You_hear("a %s explosion.", + (distu(x, y) > 7 * 7) ? "distant" + : "nearby"); + } + } + } break; } + sawit = cansee(x, y); door->doormask = D_BROKEN; + recalc_block_point(x, y); + seeit = cansee(x, y); + newsym(x, y); if (flags.verbose) { - if (cansee(x, y)) + if ((sawit || seeit) && !Unaware) { pline_The("door crashes open!"); - else + } else if (!Deaf) { + Soundeffect(se_crashing_sound, 100); You_hear("a crashing sound."); + } } - unblock_point(x, y); - newsym(x, y); /* force vision recalc before printing more messages */ - if (vision_full_recalc) + if (gv.vision_full_recalc) vision_recalc(0); loudness = 20; } else @@ -1070,26 +1272,25 @@ int x, y; return res; } -STATIC_OVL void -chest_shatter_msg(otmp) -struct obj *otmp; +staticfn void +chest_shatter_msg(struct obj *otmp) { const char *disposition; const char *thing; - long save_Blinded; + long save_HBlinded, save_BBlinded; if (otmp->oclass == POTION_CLASS) { You("%s %s shatter!", Blind ? "hear" : "see", an(bottlename())); - if (!breathless(youmonst.data) || haseyes(youmonst.data)) + if (!breathless(gy.youmonst.data) || haseyes(gy.youmonst.data)) potionbreathe(otmp); return; } /* We have functions for distant and singular names, but not one */ /* which does _both_... */ - save_Blinded = Blinded; - Blinded = 1; + save_HBlinded = HBlinded, save_BBlinded = BBlinded; + HBlinded = 1L, BBlinded = 0L; thing = singular(otmp, xname); - Blinded = save_Blinded; + HBlinded = save_HBlinded, BBlinded = save_BBlinded; switch (objects[otmp->otyp].oc_material) { case PAPER: disposition = "is torn to shreds"; diff --git a/src/mail.c b/src/mail.c index 064b69bc5..2f9528188 100644 --- a/src/mail.c +++ b/src/mail.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 mail.c $NHDT-Date: 1568508711 2019/09/15 00:51:51 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.40 $ */ +/* NetHack 5.0 mail.c $NHDT-Date: 1762750699 2025/11/09 20:58:19 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.77 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Pasi Kallinen, 2018. */ /* NetHack may be freely redistributed. See license for details. */ @@ -10,8 +10,12 @@ # include # include #endif /* SIMPLE_MAIL */ +#endif /* MAIL */ +#ifdef MAIL_STRUCTURES #include "mail.h" +#endif +#ifdef MAIL /* * Notify user when new mail has arrived. Idea by Merlyn Leroy. * @@ -36,12 +40,13 @@ * random intervals. */ -STATIC_DCL boolean FDECL(md_start, (coord *)); -STATIC_DCL boolean FDECL(md_stop, (coord *, coord *)); -STATIC_DCL boolean FDECL(md_rush, (struct monst *, int, int)); -STATIC_DCL void FDECL(newmail, (struct mail_info *)); - -extern char *viz_rmin, *viz_rmax; /* line-of-sight limits (vision.c) */ +staticfn boolean md_start(coord *); +staticfn boolean md_stop(coord *, coord *); +staticfn boolean md_rush(struct monst *, int, int); +staticfn void newmail(struct mail_info *); +#if defined(SIMPLE_MAIL) || defined(SERVER_ADMIN_MSG) +staticfn void read_simplemail(const char *mbox, boolean adminmsg); +#endif #if !defined(UNIX) && !defined(VMS) int mustgetmail = -1; @@ -55,9 +60,9 @@ int mustgetmail = -1; #if !defined(SUNOS4) && !(defined(ULTRIX) && defined(__GNUC__)) /* DO trust all SVR4 to typedef uid_t in (probably to a long) */ #if defined(POSIX_TYPES) || defined(SVR4) || defined(HPUX) -extern struct passwd *FDECL(getpwuid, (uid_t)); +extern struct passwd *getpwuid(uid_t); #else -extern struct passwd *FDECL(getpwuid, (int)); +extern struct passwd *getpwuid(int); #endif #endif #endif @@ -82,14 +87,14 @@ static long laststattime; #endif void -free_maildata() +free_maildata(void) { if (mailbox) free((genericptr_t) mailbox), mailbox = (char *) 0; } void -getmailstatus() +getmailstatus(void) { if (mailbox) { ; /* no need to repeat the setup */ @@ -140,15 +145,15 @@ getmailstatus() * Pick coordinates for a starting position for the mail daemon. Called * from newmail() and newphone(). */ -STATIC_OVL boolean -md_start(startp) -coord *startp; +staticfn boolean +md_start(coord *startp) { coord testcc; /* scratch coordinates */ int row; /* current row we are checking */ int lax; /* if TRUE, pick a position in sight. */ int dd; /* distance to current point */ int max_distance; /* max distance found so far */ + stairway *stway = gs.stairs; /* * If blind and not telepathic, then it doesn't matter what we pick --- @@ -164,15 +169,14 @@ coord *startp; * Arrive at an up or down stairwell if it is in line of sight from the * hero. */ - if (couldsee(upstair.sx, upstair.sy)) { - startp->x = upstair.sx; - startp->y = upstair.sy; - return TRUE; - } - if (couldsee(dnstair.sx, dnstair.sy)) { - startp->x = dnstair.sx; - startp->y = dnstair.sy; - return TRUE; + while (stway) { + if (stway->tolev.dnum == u.uz.dnum + && couldsee(stway->sx, stway->sy)) { + startp->x = stway->sx; + startp->y = stway->sy; + return TRUE; + } + stway = stway->next; } /* @@ -181,23 +185,23 @@ coord *startp; * position that could be seen. What we really ought to be doing is * finding a path from a stairwell... * - * The arrays viz_rmin[] and viz_rmax[] are set even when blind. These - * are the LOS limits for each row. + * The arrays gv.viz_rmin[] and gv.viz_rmax[] are set even when blind. + * These are the LOS limits for each row. */ lax = 0; /* be picky */ max_distance = -1; retry: for (row = 0; row < ROWNO; row++) { - if (viz_rmin[row] < viz_rmax[row]) { + if (gv.viz_rmin[row] < gv.viz_rmax[row]) { /* There are valid positions on this row. */ - dd = distu(viz_rmin[row], row); + dd = distu(gv.viz_rmin[row], row); if (dd > max_distance) { if (lax) { max_distance = dd; startp->y = row; - startp->x = viz_rmin[row]; + startp->x = gv.viz_rmin[row]; - } else if (enexto(&testcc, (xchar) viz_rmin[row], row, + } else if (enexto(&testcc, gv.viz_rmin[row], row, (struct permonst *) 0) && !cansee(testcc.x, testcc.y) && couldsee(testcc.x, testcc.y)) { @@ -205,14 +209,14 @@ coord *startp; *startp = testcc; } } - dd = distu(viz_rmax[row], row); + dd = distu(gv.viz_rmax[row], row); if (dd > max_distance) { if (lax) { max_distance = dd; startp->y = row; - startp->x = viz_rmax[row]; + startp->x = gv.viz_rmax[row]; - } else if (enexto(&testcc, (xchar) viz_rmax[row], row, + } else if (enexto(&testcc, gv.viz_rmax[row], row, (struct permonst *) 0) && !cansee(testcc.x, testcc.y) && couldsee(testcc.x, testcc.y)) { @@ -240,16 +244,15 @@ coord *startp; * enexto(). Use enexto() as a last resort because enexto() chooses * its point randomly, which is not what we want. */ -STATIC_OVL boolean -md_stop(stopp, startp) -coord *stopp; /* stopping position (we fill it in) */ -coord *startp; /* starting position (read only) */ +staticfn boolean +md_stop(coord *stopp, /* stopping position (we fill it in) */ + coord *startp) /* starting position (read only) */ { - int x, y, distance, min_distance = -1; + coordxy x, y, distance, min_distance = -1; for (x = u.ux - 1; x <= u.ux + 1; x++) for (y = u.uy - 1; y <= u.uy + 1; y++) { - if (!isok(x, y) || (x == u.ux && y == u.uy)) + if (!isok(x, y) || u_at(x, y)) continue; if (accessible(x, y) && !MON_AT(x, y)) { @@ -271,7 +274,7 @@ coord *startp; /* starting position (read only) */ } /* Let the mail daemon have a larger vocabulary. */ -static NEARDATA const char *mail_text[] = { "Gangway!", "Look out!", +staticfn NEARDATA const char *mail_text[] = { "Gangway!", "Look out!", "Pardon me!" }; #define md_exclamations() (mail_text[rn2(3)]) @@ -281,13 +284,12 @@ static NEARDATA const char *mail_text[] = { "Gangway!", "Look out!", * FALSE if the md gets stuck in a position where there is a monster. Return * TRUE otherwise. */ -STATIC_OVL boolean -md_rush(md, tx, ty) -struct monst *md; -register int tx, ty; /* destination of mail daemon */ +staticfn boolean +md_rush(struct monst *md, + int tx, int ty) /* destination of mail daemon */ { struct monst *mon; /* displaced monster */ - register int dx, dy; /* direction counters */ + int dx, dy; /* direction counters */ int fx = md->mx, fy = md->my; /* current location */ int nfx = fx, nfy = fy, /* new location */ d1, d2; /* shortest distances */ @@ -331,17 +333,21 @@ register int tx, ty; /* destination of mail daemon */ if (fx == tx && fy == ty) break; - if ((mon = m_at(fx, fy)) != 0) /* save monster at this position */ - verbalize1(md_exclamations()); - else if (fx == u.ux && fy == u.uy) - verbalize("Excuse me."); + mon = m_at(fx, fy); /* save monster at this position */ + if (!Deaf) { + SetVoice(md, 0, 80, 0); + if (mon) + verbalize1(md_exclamations()); + else if (u_at(fx, fy)) + verbalize("Excuse me."); + } if (mon) remove_monster(fx, fy); place_monster(md, fx, fy); /* put md down */ newsym(fx, fy); /* see it */ flush_screen(0); /* make sure md shows up */ - delay_output(); /* wait a little bit */ + nh_delay_output(); /* wait a little bit */ /* Remove md from the dungeon. Restore original mon, if necessary. */ remove_monster(fx, fy); @@ -362,7 +368,12 @@ register int tx, ty; /* destination of mail daemon */ remove_monster(fx, fy); place_monster(md, fx, fy); /* display md with text below */ newsym(fx, fy); - verbalize("This place's too crowded. I'm outta here."); + if (!Deaf) { + SetVoice(md, 0, 80, 0); + verbalize("This place's too crowded. I'm outta here."); + } else { + pline("%s.", Never_mind); + } remove_monster(fx, fy); if ((mon->mx != fx) || (mon->my != fy)) /* put mon back */ @@ -377,16 +388,15 @@ register int tx, ty; /* destination of mail daemon */ place_monster(md, fx, fy); /* place at final spot */ newsym(fx, fy); flush_screen(0); - delay_output(); /* wait a little bit */ + nh_delay_output(); /* wait a little bit */ return TRUE; } /* Deliver a scroll of mail. */ /*ARGSUSED*/ -STATIC_OVL void -newmail(info) -struct mail_info *info; +staticfn void +newmail(struct mail_info *info) { struct monst *md; coord start, stop; @@ -403,18 +413,30 @@ struct mail_info *info; goto go_back; message_seen = TRUE; - verbalize("%s, %s! %s.", Hello(md), plname, info->display_txt); + if (!Deaf) { + SetVoice(md, 0, 80, 0); + verbalize("%s, %s! %s.", Hello(md), svp.plname, info->display_txt); + } else { + pline("Message: %s.", info->display_txt); + } if (info->message_typ) { struct obj *obj = mksobj(SCR_MAIL, FALSE, FALSE); if (info->object_nam) - obj = oname(obj, info->object_nam); + obj = oname(obj, info->object_nam, ONAME_NO_FLAGS); if (info->response_cmd) new_omailcmd(obj, info->response_cmd); - if (distu(md->mx, md->my) > 2) - verbalize("Catch!"); + if (!m_next2u(md)) { + if (!Deaf) { + SetVoice(md, 0, 80, 0); + verbalize("Catch!"); + } else { + /* don't bother with nonverbal alternative ... */ + ; + } + } display_nhwindow(WIN_MESSAGE, FALSE); obj = hold_another_object(obj, "Oops!", (const char *) 0, (const char *) 0); @@ -436,13 +458,14 @@ struct mail_info *info; #if !defined(UNIX) && !defined(VMS) void -ckmailstatus() +ckmailstatus(void) { if (u.uswallow || !flags.biff) return; if (mustgetmail < 0) { #if defined(AMIGA) || defined(MSDOS) || defined(TOS) - mustgetmail = (moves < 2000) ? (100 + rn2(2000)) : (2000 + rn2(3000)); + mustgetmail = (svm.moves < 2000) ? (100 + rn2(2000)) + : (2000 + rn2(3000)); #endif return; } @@ -455,13 +478,19 @@ ckmailstatus() } } +DISABLE_WARNING_FORMAT_NONLITERAL + +enum delivery_types { faulty_delivery, normal_delivery, subst_delivery }; + /*ARGSUSED*/ void -readmail(otmp) -struct obj *otmp UNUSED; +readmail(struct obj *otmp UNUSED) { - static const char *junk[] = { - "Report bugs to <%s>.", /*** must be first entry ***/ + int i; + enum delivery_types delivery = normal_delivery; + const char *recipient = 0; + static const char *const junk_templates[] = { + "%sReport bugs to <%s>.%s", /*** must be first entry ***/ "Please disregard previous letter.", "Welcome to NetHack.", #ifdef AMIGA @@ -477,72 +506,59 @@ struct obj *otmp UNUSED; (suboptimal but works correctly); dollar sign and fractional zorkmids are inappropriate within nethack but are suitable for typical dysfunctional spam mail */ - "Buy a potion of gain level for only $19.99! Guaranteed to be blessed!", - /* DEVTEAM_URL will be substituted for "%s"; terminating punctuation - (formerly "!") has deliberately been omitted so that it can't be - mistaken for part of the URL (unfortunately that is still followed - by a closing quote--in the pline below, not the data here) */ - "Invitation: Visit the NetHack web site at %s" + ("Buy a potion of gain level for only $19.99! " + " Guaranteed to be blessed!"), + /* DEVTEAM_URL will be substituted for 2nd "%s"; + terminating punctuation (formerly "!") has deliberately been + omitted so that it can't be mistaken for part of the URL + (unfortunately that is still followed by a closing quote--in + the pline below, not the data here) */ + "%sInvitation: Visit the NetHack web site at %s%s" }; - - /* XXX replace with more general substitution code and add local - * contact message. - * - * FIXME: this allocated memory is never freed. However, if the - * game is restarted, the junk[] update will be a no-op for second - * and subsequent runs and this updated text will still be appropriate. - */ - if (index(junk[0], '%')) { - char *tmp; - int i; - - for (i = 0; i < SIZE(junk); ++i) { - if (index(junk[i], '%')) { - if (i == 0) { - /* +2 from '%s' in junk[0] suffices as substitute - for usual +1 for terminator */ - tmp = (char *) alloc(strlen(junk[0]) - + strlen(DEVTEAM_EMAIL)); - Sprintf(tmp, junk[0], DEVTEAM_EMAIL); - junk[0] = tmp; - } else if (strstri(junk[i], "web site")) { - /* as with junk[0], room for terminator is present */ - tmp = (char *) alloc(strlen(junk[i]) - + strlen(DEVTEAM_URL)); - Sprintf(tmp, junk[i], DEVTEAM_URL); - junk[i] = tmp; - } else { - /* could check for "%%" but unless that becomes needed, - handling it is more complicated than necessary */ - impossible("fake mail #%d has undefined substitution", i); - junk[i] = "Bad fake mail..."; - } - } + const char *const it_reads = "It reads: \""; + + i = rn2(SIZE(junk_templates)); + if (strchr(junk_templates[i], '%')) { + if (i == 0) { + recipient = DEVTEAM_EMAIL; + delivery = subst_delivery; + } else if (strstri(junk_templates[i], "web site")) { + recipient = DEVTEAM_URL; + delivery = subst_delivery; + } else { + impossible("fake mail #%d has undefined substitution", i); + delivery = faulty_delivery; } } if (Blind) { pline("Unfortunately you cannot see what it says."); - } else - pline("It reads: \"%s\"", junk[rn2(SIZE(junk))]); + } else { + if (delivery == subst_delivery) + pline(junk_templates[i], it_reads, recipient, "\""); + else if (delivery == normal_delivery) + pline("%s%s\"", it_reads, junk_templates[i]); + } } +RESTORE_WARNING_FORMAT_NONLITERAL + #endif /* !UNIX && !VMS */ #ifdef UNIX void -ckmailstatus() +ckmailstatus(void) { ck_server_admin_msg(); if (!mailbox || u.uswallow || !flags.biff #ifdef MAILCKFREQ - || moves < laststattime + MAILCKFREQ + || svm.moves < laststattime + MAILCKFREQ #endif ) return; - laststattime = moves; + laststattime = svm.moves; if (stat(mailbox, &nmstat)) { #ifdef PERMANENT_MAILBOX pline("Cannot get status of MAIL=\"%s\" anymore.", mailbox); @@ -568,20 +584,16 @@ ckmailstatus() } #if defined(SIMPLE_MAIL) || defined(SERVER_ADMIN_MSG) + void -read_simplemail(mbox, adminmsg) -char *mbox; -boolean adminmsg; +read_simplemail(const char *mbox, boolean adminmsg) { - FILE* mb = fopen(mbox, "r"); + FILE *mb = fopen(mbox, "r"); char curline[128], *msg; boolean seen_one_already = FALSE; #ifdef SIMPLE_MAIL struct flock fl = { 0 }; #endif - const char *msgfrom = adminmsg - ? "The voice of %s booms through the caverns:" - : "This message is from '%s'."; if (!mb) goto bail; @@ -597,34 +609,47 @@ boolean adminmsg; /* Allow this call to block. */ if (!adminmsg #ifdef SIMPLE_MAIL - && fcntl (fileno (mb), F_SETLKW, &fl) == -1 + && fcntl(fileno(mb), F_SETLKW, &fl) == -1 #endif ) goto bail; while (fgets(curline, 128, mb) != NULL) { + const char *endpunct; + int msglen; + if (!adminmsg) { #ifdef SIMPLE_MAIL fl.l_type = F_UNLCK; - fcntl (fileno(mb), F_UNLCK, &fl); + fcntl(fileno(mb), F_UNLCK, &fl); #endif - pline("There is a%s message on this scroll.", + There("is a%s message on this scroll.", seen_one_already ? "nother" : ""); } msg = strchr(curline, ':'); - if (!msg) + /* if incorrectly formatted, or message is empty (':' and '\n' take + up 2 chars, so must have at least 3 to be nonempty), give up */ + if (!msg || (msglen = (int) strlen(msg)) < 3) goto bail; *msg = '\0'; - msg++; - msg[strlen(msg) - 1] = '\0'; /* kill newline */ - - pline(msgfrom, curline); - if (adminmsg) - verbalize(msg); - else - pline("It reads: \"%s\".", msg); + msg++, msglen--; + msg[msglen - 1] = '\0'; /* kill newline */ + + /* supply ending punctuation only if the message doesn't have any */ + endpunct = ""; + if (!strchr(".!?", msg[msglen - 2])) + endpunct = "."; + + if (adminmsg) { + urgent_pline("The voice of %s booms through the caverns:", + curline); + } else { + pline("This message is from '%s'.", curline); + pline("It reads:"); + } + pline("\"%s\"%s", msg, endpunct); seen_one_already = TRUE; #ifdef SIMPLE_MAIL @@ -653,17 +678,18 @@ boolean adminmsg; if (!adminmsg) pline("It appears to be all gibberish."); } + #endif /* SIMPLE_MAIL */ void -ck_server_admin_msg() +ck_server_admin_msg(void) { #ifdef SERVER_ADMIN_MSG static struct stat ost,nst; static long lastchk = 0; - if (moves < lastchk + SERVER_ADMIN_MSG_CKFREQ) return; - lastchk = moves; + if (svm.moves < lastchk + SERVER_ADMIN_MSG_CKFREQ) return; + lastchk = svm.moves; if (!stat(SERVER_ADMIN_MSG, &nst)) { if (nst.st_mtime > ost.st_mtime) @@ -675,11 +701,10 @@ ck_server_admin_msg() /*ARGSUSED*/ void -readmail(otmp) -struct obj *otmp UNUSED; +readmail(struct obj *otmp UNUSED) { #ifdef DEF_MAILREADER /* This implies that UNIX is defined */ - register const char *mr = 0; + const char *mr = 0; #endif /* DEF_MAILREADER */ #ifdef SIMPLE_MAIL read_simplemail(mailbox, FALSE); @@ -711,12 +736,12 @@ struct obj *otmp UNUSED; #ifdef VMS -extern NDECL(struct mail_info *parse_next_broadcast); +extern struct mail_info *parse_next_broadcast(void); volatile int broadcasts = 0; void -ckmailstatus() +ckmailstatus(void) { struct mail_info *brdcst; @@ -735,8 +760,7 @@ ckmailstatus() } void -readmail(otmp) -struct obj *otmp; +readmail(struct obj *otmp) { #ifdef SHELL /* can't access mail reader without spawning subprocess */ const char *txt, *cmd; diff --git a/src/makemon.c b/src/makemon.c index 3145ad6de..9af36be2f 100644 --- a/src/makemon.c +++ b/src/makemon.c @@ -1,12 +1,10 @@ -/* NetHack 3.6 makemon.c $NHDT-Date: 1574722863 2019/11/25 23:01:03 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.142 $ */ +/* NetHack 5.0 makemon.c $NHDT-Date: 1770949988 2026/02/12 18:33:08 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.271 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2012. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -#include - /* this assumes that a human quest leader or nemesis is an archetype of the corresponding role; that isn't so for some roles (tourist for instance) but is for the priests and monks we use it for... */ @@ -14,27 +12,27 @@ (mptr->mlet == S_HUMAN && Role_if(role_pm) \ && (mptr->msound == MS_LEADER || mptr->msound == MS_NEMESIS)) -STATIC_DCL boolean FDECL(uncommon, (int)); -STATIC_DCL int FDECL(align_shift, (struct permonst *)); -STATIC_DCL boolean FDECL(mk_gen_ok, (int, int, int)); -STATIC_DCL boolean FDECL(wrong_elem_type, (struct permonst *)); -STATIC_DCL void FDECL(m_initgrp, (struct monst *, int, int, int, int)); -STATIC_DCL void FDECL(m_initthrow, (struct monst *, int, int)); -STATIC_DCL void FDECL(m_initweap, (struct monst *)); -STATIC_DCL void FDECL(m_initinv, (struct monst *)); -STATIC_DCL boolean FDECL(makemon_rnd_goodpos, (struct monst *, - unsigned, coord *)); +staticfn boolean uncommon(int); +staticfn int align_shift(struct permonst *); +staticfn int temperature_shift(struct permonst *); +staticfn boolean mk_gen_ok(int, unsigned, unsigned); +staticfn int QSORTCALLBACK cmp_init_mongen_order(const void *, const void *); +staticfn void init_mongen_order(void); +staticfn boolean wrong_elem_type(struct permonst *); +staticfn void m_initgrp(struct monst *, coordxy, coordxy, int, mmflags_nht); +staticfn void m_initthrow(struct monst *, int, int); +staticfn void m_initweap(struct monst *); +staticfn void m_initinv(struct monst *); +staticfn boolean makemon_rnd_goodpos(struct monst *, mmflags_nht, coord *); +staticfn void init_mextra(struct mextra *); #define m_initsgrp(mtmp, x, y, mmf) m_initgrp(mtmp, x, y, 3, mmf) #define m_initlgrp(mtmp, x, y, mmf) m_initgrp(mtmp, x, y, 10, mmf) -#define toostrong(monindx, lev) (mons[monindx].difficulty > lev) -#define tooweak(monindx, lev) (mons[monindx].difficulty < lev) boolean -is_home_elemental(ptr) -struct permonst *ptr; +is_home_elemental(struct permonst *ptr) { - if (ptr->mlet == S_ELEMENTAL) + if (ptr->mlet == S_ELEMENTAL) { switch (monsndx(ptr)) { case PM_AIR_ELEMENTAL: return Is_airlevel(&u.uz); @@ -44,16 +42,18 @@ struct permonst *ptr; return Is_earthlevel(&u.uz); case PM_WATER_ELEMENTAL: return Is_waterlevel(&u.uz); + default: + break; } + } return FALSE; } /* * Return true if the given monster cannot exist on this elemental level. */ -STATIC_OVL boolean -wrong_elem_type(ptr) -struct permonst *ptr; +staticfn boolean +wrong_elem_type(struct permonst *ptr) { if (ptr->mlet == S_ELEMENTAL) { return (boolean) !is_home_elemental(ptr); @@ -75,13 +75,14 @@ struct permonst *ptr; } /* make a group just like mtmp */ -STATIC_OVL void -m_initgrp(mtmp, x, y, n, mmflags) -struct monst *mtmp; -int x, y, n, mmflags; +staticfn void +m_initgrp( + struct monst *mtmp, + coordxy x, coordxy y, int n, + mmflags_nht mmflags) { coord mm; - register int cnt = rnd(n); + int cnt = rnd(n); struct monst *mon; #if defined(__GNUC__) && (defined(HPUX) || defined(DGUX)) /* There is an unresolved problem with several people finding that @@ -128,7 +129,7 @@ int x, y, n, mmflags; * are peaceful and some are not, the result will just be a * smaller group. */ - if (enexto(&mm, mm.x, mm.y, mtmp->data)) { + if (enexto_gpflags(&mm, mm.x, mm.y, mtmp->data, mmflags)) { mon = makemon(mtmp->data, mm.x, mm.y, (mmflags | MM_NOGRP)); if (mon) { mon->mpeaceful = FALSE; @@ -143,13 +144,10 @@ int x, y, n, mmflags; } } -STATIC_OVL -void -m_initthrow(mtmp, otyp, oquan) -struct monst *mtmp; -int otyp, oquan; +staticfn void +m_initthrow(struct monst *mtmp, int otyp, int oquan) { - register struct obj *otmp; + struct obj *otmp; otmp = mksobj(otyp, TRUE, FALSE); otmp->quan = (long) rn1(oquan, 3); @@ -159,12 +157,11 @@ int otyp, oquan; (void) mpickobj(mtmp, otmp); } -STATIC_OVL void -m_initweap(mtmp) -register struct monst *mtmp; +staticfn void +m_initweap(struct monst *mtmp) { - register struct permonst *ptr = mtmp->data; - register int mm = monsndx(ptr); + struct permonst *ptr = mtmp->data; + int mm = monsndx(ptr); struct obj *otmp; int bias, w1, w2; @@ -183,6 +180,8 @@ register struct monst *mtmp; case S_GIANT: if (rn2(2)) (void) mongets(mtmp, (mm != PM_ETTIN) ? BOULDER : CLUB); + if ((mm != PM_ETTIN) && !rn2(5)) + (void) mongets(mtmp, rn2(2) ? TWO_HANDED_SWORD : BATTLE_AXE); break; case S_HUMAN: if (is_mercenary(ptr)) { @@ -191,7 +190,12 @@ register struct monst *mtmp; case PM_WATCHMAN: case PM_SOLDIER: if (!rn2(3)) { - w1 = rn1(BEC_DE_CORBIN - PARTISAN + 1, PARTISAN); + /* lance and dwarvish mattock used to be in midst of + the polearms but use different skills from polearms + and aren't appropriates choices for human soldiers */ + do { + w1 = rn1(BEC_DE_CORBIN - PARTISAN + 1, PARTISAN); + } while (objects[w1].oc_skill != P_POLEARMS); w2 = rn2(2) ? DAGGER : KNIFE; } else w1 = rn2(2) ? SPEAR : SHORT_SWORD; @@ -250,14 +254,14 @@ register struct monst *mtmp; } break; } - if (mm == PM_ELVENKING) { - if (rn2(3) || (in_mklev && Is_earthlevel(&u.uz))) + if (mm == PM_ELVEN_MONARCH) { + if (rn2(3) || (gi.in_mklev && Is_earthlevel(&u.uz))) (void) mongets(mtmp, PICK_AXE); if (!rn2(50)) (void) mongets(mtmp, CRYSTAL_BALL); } } else if (ptr->msound == MS_PRIEST - || quest_mon_represents_role(ptr, PM_PRIEST)) { + || quest_mon_represents_role(ptr, PM_CLERIC)) { otmp = mksobj(MACE, FALSE, FALSE); otmp->spe = rnd(3); if (!rn2(2)) @@ -325,16 +329,24 @@ register struct monst *mtmp; case S_ANGEL: if (humanoid(ptr)) { - /* create minion stuff; can't use mongets */ - otmp = mksobj(LONG_SWORD, FALSE, FALSE); - - /* maybe make it special */ - if (!rn2(20) || is_lord(ptr)) - otmp = oname(otmp, - artiname(rn2(2) ? ART_DEMONBANE : ART_SUNSWORD)); + /* create minion stuff; bypass mongets */ + int typ = rn2(3) ? LONG_SWORD : SILVER_MACE; + const char *nam = (typ == LONG_SWORD) ? "Sunsword" : "Demonbane"; + + otmp = mksobj(typ, FALSE, FALSE); + /* maybe promote weapon to an artifact */ + if ((!rn2(20) || is_lord(ptr)) + && sgn(mtmp->isminion ? EMIN(mtmp)->min_align + : ptr->maligntyp) == A_LAWFUL) + otmp = oname(otmp, nam, ONAME_RANDOM); /* randomly created */ + /* enhance the weapon */ bless(otmp); otmp->oerodeproof = TRUE; + /* make long sword be +0 to +3, mace be +3 to +6 to compensate + for being significantly weaker against large opponents */ otmp->spe = rn2(4); + if (typ == SILVER_MACE) + otmp->spe += 3; (void) mpickobj(mtmp, otmp); otmp = mksobj(!rn2(4) || is_lord(ptr) ? SHIELD_OF_REFLECTION @@ -358,6 +370,7 @@ register struct monst *mtmp; break; case 2: (void) mongets(mtmp, SLING); + m_initthrow(mtmp, !rn2(4) ? FLINT : ROCK, 6); break; } if (!rn2(10)) @@ -431,7 +444,7 @@ register struct monst *mtmp; } break; case S_OGRE: - if (!rn2(mm == PM_OGRE_KING ? 3 : mm == PM_OGRE_LORD ? 6 : 12)) + if (!rn2(mm == PM_OGRE_TYRANT ? 3 : mm == PM_OGRE_LEADER ? 6 : 12)) (void) mongets(mtmp, BATTLE_AXE); else (void) mongets(mtmp, CLUB); @@ -508,6 +521,7 @@ register struct monst *mtmp; */ if (!is_demon(ptr)) break; + FALLTHROUGH; /*FALLTHRU*/ default: /* @@ -557,28 +571,26 @@ register struct monst *mtmp; (void) mongets(mtmp, rnd_offensive_item(mtmp)); } -/* - * Makes up money for monster's inventory. - * This will change with silver & copper coins - */ +/* create a new stack of gold in monster's inventory */ void -mkmonmoney(mtmp, amount) -struct monst *mtmp; -long amount; +mkmonmoney(struct monst *mtmp, long amount) { - struct obj *gold = mksobj(GOLD_PIECE, FALSE, FALSE); + /* mk_mplayer() passes rn2(1000) so the amount might be 0 */ + if (amount > 0L) { + struct obj *gold = mksobj(GOLD_PIECE, FALSE, FALSE); - gold->quan = amount; - add_to_minv(mtmp, gold); + gold->quan = amount; + gold->owt = weight(gold); + add_to_minv(mtmp, gold); + } } -STATIC_OVL void -m_initinv(mtmp) -register struct monst *mtmp; +staticfn void +m_initinv(struct monst *mtmp) { - register int cnt; - register struct obj *otmp; - register struct permonst *ptr = mtmp->data; + int cnt; + struct obj *otmp; + struct permonst *ptr = mtmp->data; if (Is_rogue_level(&u.uz)) return; @@ -589,7 +601,7 @@ register struct monst *mtmp; switch (ptr->mlet) { case S_HUMAN: if (is_mercenary(ptr)) { - register int mac; + int mac; switch (monsndx(ptr)) { case PM_GUARD: @@ -619,35 +631,52 @@ register struct monst *mtmp; break; } +#define add_ac(otmp) \ + if (otmp) { mac += ARM_BONUS(otmp); } \ + otmp = (struct obj *) 0; + + /* round 1: give them body armor */ if (mac < -1 && rn2(5)) - mac += 7 + mongets(mtmp, (rn2(5)) ? PLATE_MAIL - : CRYSTAL_PLATE_MAIL); + otmp = mongets(mtmp, (rn2(5)) ? PLATE_MAIL + : CRYSTAL_PLATE_MAIL); else if (mac < 3 && rn2(5)) - mac += - 6 + mongets(mtmp, (rn2(3)) ? SPLINT_MAIL : BANDED_MAIL); + otmp = mongets(mtmp, (rn2(3)) ? SPLINT_MAIL : BANDED_MAIL); else if (rn2(5)) - mac += 3 + mongets(mtmp, (rn2(3)) ? RING_MAIL - : STUDDED_LEATHER_ARMOR); + otmp = mongets(mtmp, (rn2(3)) ? RING_MAIL + : STUDDED_LEATHER_ARMOR); else - mac += 2 + mongets(mtmp, LEATHER_ARMOR); + otmp = mongets(mtmp, LEATHER_ARMOR); + add_ac(otmp); + /* round 2: helmets */ if (mac < 10 && rn2(3)) - mac += 1 + mongets(mtmp, HELMET); + otmp = mongets(mtmp, HELMET); else if (mac < 10 && rn2(2)) - mac += 1 + mongets(mtmp, DENTED_POT); + otmp = mongets(mtmp, DENTED_POT); + add_ac(otmp); + + /* round 3: shields */ if (mac < 10 && rn2(3)) - mac += 1 + mongets(mtmp, SMALL_SHIELD); + otmp = mongets(mtmp, SMALL_SHIELD); else if (mac < 10 && rn2(2)) - mac += 2 + mongets(mtmp, LARGE_SHIELD); + otmp = mongets(mtmp, LARGE_SHIELD); + add_ac(otmp); + + /* round 4: boots */ if (mac < 10 && rn2(3)) - mac += 1 + mongets(mtmp, LOW_BOOTS); + otmp = mongets(mtmp, LOW_BOOTS); else if (mac < 10 && rn2(2)) - mac += 2 + mongets(mtmp, HIGH_BOOTS); + otmp = mongets(mtmp, HIGH_BOOTS); + add_ac(otmp); + + /* round 5: gloves + cloak */ if (mac < 10 && rn2(3)) - mac += 1 + mongets(mtmp, LEATHER_GLOVES); + otmp = mongets(mtmp, LEATHER_GLOVES); else if (mac < 10 && rn2(2)) - mac += 1 + mongets(mtmp, LEATHER_CLOAK); + otmp = mongets(mtmp, LEATHER_CLOAK); + add_ac(otmp); /* not technically needed */ +#undef add_ac nhUse(mac); /* suppress 'dead increment' from static analyzer */ if (ptr == &mons[PM_WATCH_CAPTAIN]) { @@ -676,18 +705,21 @@ register struct monst *mtmp; /* MAJOR fall through ... */ case 0: (void) mongets(mtmp, WAN_MAGIC_MISSILE); + FALLTHROUGH; /*FALLTHRU*/ case 1: (void) mongets(mtmp, POT_EXTRA_HEALING); + FALLTHROUGH; /*FALLTHRU*/ case 2: (void) mongets(mtmp, POT_HEALING); + FALLTHROUGH; /*FALLTHRU*/ case 3: (void) mongets(mtmp, WAN_STRIKING); } } else if (ptr->msound == MS_PRIEST - || quest_mon_represents_role(ptr, PM_PRIEST)) { + || quest_mon_represents_role(ptr, PM_CLERIC)) { (void) mongets(mtmp, rn2(7) ? ROBE : rn2(3) ? CLOAK_OF_PROTECTION : CLOAK_OF_MAGIC_RESISTANCE); @@ -705,7 +737,7 @@ register struct monst *mtmp; break; case S_GIANT: if (ptr == &mons[PM_MINOTAUR]) { - if (!rn2(3) || (in_mklev && Is_earthlevel(&u.uz))) + if (!rn2(8) || (gi.in_mklev && Is_earthlevel(&u.uz))) (void) mongets(mtmp, WAN_DIGGING); } else if (is_giant(ptr)) { for (cnt = rn2((int) (mtmp->m_lev / 2)); cnt; cnt--) { @@ -742,7 +774,7 @@ register struct monst *mtmp; (void) mongets(mtmp, MUMMY_WRAPPING); break; case S_QUANTMECH: - if (!rn2(20)) { + if (!rn2(20) && ptr == &mons[PM_QUANTUM_MECHANIC]) { struct obj *catcorpse; otmp = mksobj(LARGE_BOX, FALSE, FALSE); @@ -775,7 +807,7 @@ register struct monst *mtmp; } break; case S_GNOME: - if (!rn2((In_mines(&u.uz) && in_mklev) ? 20 : 60)) { + if (!rn2((In_mines(&u.uz) && gi.in_mklev) ? 20 : 60)) { otmp = mksobj(rn2(4) ? TALLOW_CANDLE : WAX_CANDLE, TRUE, FALSE); otmp->quan = 1; otmp->owt = weight(otmp); @@ -802,15 +834,16 @@ register struct monst *mtmp; /* Note: for long worms, always call cutworm (cutworm calls clone_mon) */ struct monst * -clone_mon(mon, x, y) -struct monst *mon; -xchar x, y; /* clone's preferred location or 0 (near mon) */ +clone_mon( + struct monst *mon, + coordxy x, coordxy y) /* clone's preferred location or 0 (near mon) */ { coord mm; struct monst *m2; /* may be too weak or have been extinguished for population control */ - if (mon->mhp <= 1 || (mvitals[monsndx(mon->data)].mvflags & G_EXTINCT)) + if (mon->mhp <= 1 + || (svm.mvitals[monsndx(mon->data)].mvflags & G_EXTINCT) != 0) return (struct monst *) 0; if (x == 0) { @@ -835,9 +868,7 @@ xchar x, y; /* clone's preferred location or 0 (near mon) */ m2->mextra = (struct mextra *) 0; m2->nmon = fmon; fmon = m2; - m2->m_id = context.ident++; - if (!m2->m_id) - m2->m_id = context.ident++; /* ident overflowed */ + m2->m_id = next_ident(); m2->mx = mm.x; m2->my = mm.y; @@ -862,21 +893,21 @@ xchar x, y; /* clone's preferred location or 0 (near mon) */ /* ms->isminion handled below */ /* clone shouldn't be reluctant to move on spots 'parent' just moved on */ - (void) memset((genericptr_t) m2->mtrack, 0, sizeof m2->mtrack); + mon_track_clear(m2); place_monster(m2, m2->mx, m2->my); if (emits_light(m2->data)) new_light_source(m2->mx, m2->my, emits_light(m2->data), LS_MONSTER, monst_to_any(m2)); /* if 'parent' is named, give the clone the same name */ - if (has_mname(mon)) { - m2 = christen_monst(m2, MNAME(mon)); + if (has_mgivenname(mon)) { + m2 = christen_monst(m2, MGIVENNAME(mon)); } else if (mon->isshk) { m2 = christen_monst(m2, shkname(mon)); } /* not all clones caused by player are tame or peaceful */ - if (!context.mon_moving && mon->mpeaceful) { + if (!svc.context.mon_moving && mon->mpeaceful) { if (mon->mtame) m2->mtame = rn2(max(2 + u.uluck, 2)) ? mon->mtame : 0; else if (mon->mpeaceful) @@ -888,6 +919,7 @@ xchar x, y; /* clone's preferred location or 0 (near mon) */ int atyp; newemin(m2); + assert(has_emin(m2) && has_emin(mon)); *EMIN(m2) = *EMIN(mon); /* renegade when same alignment as hero but not peaceful or when peaceful while being different alignment from hero */ @@ -898,8 +930,10 @@ xchar x, y; /* clone's preferred location or 0 (near mon) */ However, tamedog() will not re-tame a tame dog, so m2 must be made non-tame to get initialized properly. */ m2->mtame = 0; - if (tamedog(m2, (struct obj *) 0)) + if (tamedog(m2, (struct obj *) 0, FALSE)) { + assert(has_edog(m2) && has_edog(mon)); *EDOG(m2) = *EDOG(mon); + } /* [TODO? some (most? all?) edog fields probably should be reinitialized rather that retain the 'parent's values] */ } @@ -921,40 +955,35 @@ xchar x, y; /* clone's preferred location or 0 (near mon) */ * TRUE propagation successful */ boolean -propagate(mndx, tally, ghostly) -int mndx; -boolean tally; -boolean ghostly; +propagate(int mndx, boolean tally, boolean ghostly) { - boolean result; - uchar lim = mbirth_limit(mndx); - boolean gone = (mvitals[mndx].mvflags & G_GONE) != 0; /* geno'd|extinct */ + boolean gone, result; + int lim = mbirth_limit(mndx); - result = (((int) mvitals[mndx].born < lim) && !gone) ? TRUE : FALSE; + gone = (svm.mvitals[mndx].mvflags & G_GONE) != 0; /* geno'd|extinct */ + result = ((int) svm.mvitals[mndx].born < lim && !gone) ? TRUE : FALSE; /* if it's unique, don't ever make it again */ - if ((mons[mndx].geno & G_UNIQ) && mndx != PM_HIGH_PRIEST) - mvitals[mndx].mvflags |= G_EXTINCT; - - if (mvitals[mndx].born < 255 && tally - && (!ghostly || (ghostly && result))) - mvitals[mndx].born++; - if ((int) mvitals[mndx].born >= lim && !(mons[mndx].geno & G_NOGEN) - && !(mvitals[mndx].mvflags & G_EXTINCT)) { + if ((mons[mndx].geno & G_UNIQ) != 0 && mndx != PM_HIGH_CLERIC) + svm.mvitals[mndx].mvflags |= G_EXTINCT; + + if (svm.mvitals[mndx].born < 255 && tally && (!ghostly || result)) + svm.mvitals[mndx].born++; + if ((int) svm.mvitals[mndx].born >= lim + && !(mons[mndx].geno & G_NOGEN) + && !(svm.mvitals[mndx].mvflags & G_EXTINCT)) { if (wizard) { debugpline1("Automatically extinguished %s.", - makeplural(mons[mndx].mname)); + makeplural(mons[mndx].pmnames[NEUTRAL])); } - mvitals[mndx].mvflags |= G_EXTINCT; - reset_rndmonst(mndx); + svm.mvitals[mndx].mvflags |= G_EXTINCT; } return result; } /* amount of HP to lose from level drain (or gain from Stormbringer) */ int -monhp_per_lvl(mon) -struct monst *mon; +monhp_per_lvl(struct monst *mon) { struct permonst *ptr = mon->data; int hp = rnd(8); /* default is d8 */ @@ -980,18 +1009,19 @@ struct monst *mon; /* set up a new monster's initial level and hit points; used by newcham() as well as by makemon() */ void -newmonhp(mon, mndx) -struct monst *mon; -int mndx; +newmonhp(struct monst *mon, int mndx) { struct permonst *ptr = &mons[mndx]; + int basehp = 0; mon->m_lev = adj_lev(ptr); if (is_golem(ptr)) { + /* golems have a fixed amount of HP, varying by golem type */ mon->mhpmax = mon->mhp = golemhp(mndx); } else if (is_rider(ptr)) { /* we want low HP, but a high mlevel so they can attack well */ - mon->mhpmax = mon->mhp = d(10, 8); + basehp = 10; /* minimum is 1 per false (weaker) level */ + mon->mhpmax = mon->mhp = d(basehp, 8); } else if (ptr->mlevel > 49) { /* "special" fixed hp monster * the hit points are encoded in the mlevel in a somewhat strange @@ -1000,63 +1030,78 @@ int mndx; mon->mhpmax = mon->mhp = 2 * (ptr->mlevel - 6); mon->m_lev = mon->mhp / 4; /* approximation */ } else if (ptr->mlet == S_DRAGON && mndx >= PM_GRAY_DRAGON) { - /* adult dragons */ - mon->mhpmax = mon->mhp = - (int) (In_endgame(&u.uz) - ? (8 * mon->m_lev) - : (4 * mon->m_lev + d((int) mon->m_lev, 4))); + /* adult dragons; N*(4+rnd(4)) before endgame, N*8 once there */ + basehp = (int) mon->m_lev; /* not really applicable; isolates cast */ + mon->mhpmax = mon->mhp = In_endgame(&u.uz) ? (8 * basehp) + : (4 * basehp + d(basehp, 4)); } else if (!mon->m_lev) { + basehp = 1; /* minimum is 1, increased to 2 below */ mon->mhpmax = mon->mhp = rnd(4); } else { - mon->mhpmax = mon->mhp = d((int) mon->m_lev, 8); + basehp = (int) mon->m_lev; /* minimum possible is one per level */ + mon->mhpmax = mon->mhp = d(basehp, 8); if (is_home_elemental(ptr)) - mon->mhpmax = (mon->mhp *= 3); + mon->mhpmax = (mon->mhp *= 3); /* leave 'basehp' as-is */ + } + + /* if d(X,8) rolled a 1 all X times, give a boost; + most beneficial for level 0 and level 1 monsters, making mhpmax + and starting mhp always be at least 2 */ + if (mon->mhpmax == basehp) { + mon->mhpmax += 1; + mon->mhp = mon->mhpmax; } } +static const struct mextra zeromextra = DUMMY; + +staticfn void +init_mextra(struct mextra *mex) +{ + *mex = zeromextra; + mex->mcorpsenm = NON_PM; +} + struct mextra * -newmextra() +newmextra(void) { struct mextra *mextra; - mextra = (struct mextra *) alloc(sizeof(struct mextra)); - mextra->mname = 0; - mextra->egd = 0; - mextra->epri = 0; - mextra->eshk = 0; - mextra->emin = 0; - mextra->edog = 0; - mextra->mcorpsenm = NON_PM; + mextra = (struct mextra *) alloc(sizeof (struct mextra)); + init_mextra(mextra); return mextra; } -STATIC_OVL boolean -makemon_rnd_goodpos(mon, gpflags, cc) -struct monst *mon; -unsigned gpflags; -coord *cc; +staticfn boolean +makemon_rnd_goodpos( + struct monst *mon, + mmflags_nht gpflags, + coord *cc) /* output */ { int tryct = 0; - int nx,ny; + coordxy nx, ny; boolean good; + gpflags |= GP_AVOID_MONPOS; do { nx = rn1(COLNO - 3, 2); ny = rn2(ROWNO); - good = (!in_mklev && cansee(nx,ny)) ? FALSE - : goodpos(nx, ny, mon, gpflags); + good = (!gi.in_mklev && cansee(nx,ny)) ? FALSE + : goodpos(nx, ny, mon, gpflags); } while ((++tryct < 50) && !good); if (!good) { /* else go through all map positions, twice, first round ignoring positions in sight, and pick first good one. skip first round if we're in special level loader or blind */ - int xofs = nx; - int yofs = ny; - int dx,dy; - int bl = (in_mklev || Blind) ? 1 : 0; + coordxy xofs = nx; + coordxy yofs = ny; + coordxy dx,dy; + int bl = (gi.in_mklev || Blind) ? 1 : 0; for ( ; bl < 2; bl++) { + if (!bl) + gpflags &= ~GP_CHECKSCARY; /* perhaps should be a 3rd pass */ for (dx = 0; dx < COLNO; dx++) for (dy = 0; dy < ROWNO; dy++) { nx = ((dx + xofs) % (COLNO - 1)) + 1; @@ -1067,20 +1112,16 @@ coord *cc; goto gotgood; } if (bl == 0 && (!mon || mon->data->mmove)) { + stairway *stway = gs.stairs; /* all map positions are visible (or not good), try to pick something logical */ - if (dnstair.sx && !rn2(2)) { - nx = dnstair.sx; - ny = dnstair.sy; - } else if (upstair.sx && !rn2(2)) { - nx = upstair.sx; - ny = upstair.sy; - } else if (dnladder.sx && !rn2(2)) { - nx = dnladder.sx; - ny = dnladder.sy; - } else if (upladder.sx && !rn2(2)) { - nx = upladder.sx; - ny = upladder.sy; + while (stway) { + if (stway->tolev.dnum == u.uz.dnum && !rn2(2)) { + nx = stway->sx; + ny = stway->sy; + break; + } + stway = stway->next; } if (goodpos(nx, ny, mon, gpflags)) goto gotgood; @@ -1098,29 +1139,35 @@ coord *cc; /* * called with [x,y] = coordinates; * [0,0] means anyplace - * [u.ux,u.uy] means: near player (if !in_mklev) + * [u.ux,u.uy] means: near player (if !gi.in_mklev) * * In case we make a monster group, only return the one at [x,y]. */ struct monst * -makemon(ptr, x, y, mmflags) -register struct permonst *ptr; -register int x, y; -int mmflags; +makemon( + struct permonst *ptr, + coordxy x, coordxy y, + mmflags_nht mmflags) { - register struct monst *mtmp; + struct monst *mtmp; struct monst fakemon; coord cc; int mndx, mcham, ct, mitem; - boolean anymon = (!ptr); - boolean byyou = (x == u.ux && y == u.uy); - boolean allow_minvent = ((mmflags & NO_MINVENT) == 0); - boolean countbirth = ((mmflags & MM_NOCOUNTBIRTH) == 0); - unsigned gpflags = (mmflags & MM_IGNOREWATER) ? MM_IGNOREWATER : 0; - - fakemon = zeromonst; + boolean femaleok, maleok, + anymon = !ptr, + byyou = u_at(x, y), + allow_minvent = ((mmflags & NO_MINVENT) == 0), + countbirth = ((mmflags & MM_NOCOUNTBIRTH) == 0), + allowtail = ((mmflags & MM_NOTAIL) == 0); + mmflags_nht gpflags = (((mmflags & MM_IGNOREWATER) ? MM_IGNOREWATER : 0) + | GP_CHECKSCARY | GP_AVOID_MONPOS); + + fakemon = cg.zeromonst; cc.x = cc.y = 0; + if (iflags.debug_mongen || (!svl.level.flags.rndmongen && !ptr)) + return (struct monst *) 0; + /* if caller wants random location, do it here */ if (x == 0 && y == 0) { fakemon.data = ptr; /* set up for goodpos */ @@ -1129,8 +1176,9 @@ int mmflags; return (struct monst *) 0; x = cc.x; y = cc.y; - } else if (byyou && !in_mklev) { - if (!enexto_core(&cc, u.ux, u.uy, ptr, gpflags)) + } else if (byyou && !gi.in_mklev) { + if (!enexto_core(&cc, u.ux, u.uy, ptr, gpflags) + && !enexto_core(&cc, u.ux, u.uy, ptr, gpflags & ~GP_CHECKSCARY)) return (struct monst *) 0; x = cc.x; y = cc.y; @@ -1155,11 +1203,11 @@ int mmflags; mndx = monsndx(ptr); /* if you are to make a specific monster and it has already been genocided, return */ - if (mvitals[mndx].mvflags & G_GENOD) + if (svm.mvitals[mndx].mvflags & G_GENOD) return (struct monst *) 0; - if (wizard && (mvitals[mndx].mvflags & G_EXTINCT)) { + if (wizard && (svm.mvitals[mndx].mvflags & G_EXTINCT)) { debugpline1("Explicitly creating extinct monster %s.", - mons[mndx].mname); + mons[mndx].pmnames[NEUTRAL]); } } else { /* make a random (common) monster that can survive here. @@ -1184,7 +1232,7 @@ int mmflags; } (void) propagate(mndx, countbirth, FALSE); mtmp = newmonst(); - *mtmp = zeromonst; /* clear all entries in structure */ + *mtmp = cg.zeromonst; /* clear all entries in structure */ if (mmflags & MM_EGD) newegd(mtmp); @@ -1200,40 +1248,57 @@ int mmflags; mtmp->msleeping = 1; mtmp->nmon = fmon; fmon = mtmp; - mtmp->m_id = context.ident++; - if (!mtmp->m_id) - mtmp->m_id = context.ident++; /* ident overflowed */ + mtmp->m_id = next_ident(); set_mon_data(mtmp, ptr); /* mtmp->data = ptr; */ if (ptr->msound == MS_LEADER && quest_info(MS_LEADER) == mndx) - quest_status.leader_m_id = mtmp->m_id; + svq.quest_status.leader_m_id = mtmp->m_id; mtmp->mnum = mndx; /* set up level and hit points */ newmonhp(mtmp, mndx); - if (is_female(ptr)) - mtmp->female = TRUE; - else if (is_male(ptr)) - mtmp->female = FALSE; + femaleok = (!is_male(ptr) && !is_neuter(ptr)); + maleok = (!is_female(ptr) && !is_neuter(ptr)); + if (is_female(ptr) || ((mmflags & MM_FEMALE) != 0 && femaleok)) + mtmp->female = 1; + else if (is_male(ptr) || ((mmflags & MM_MALE) != 0 && maleok)) + mtmp->female = 0; + /* leader and nemesis gender is usually hardcoded in mons[], but for ones which can be random, it has already been chosen (in role_init(), for possible use by the quest pager code) */ else if (ptr->msound == MS_LEADER && quest_info(MS_LEADER) == mndx) - mtmp->female = quest_status.ldrgend; + mtmp->female = svq.quest_status.ldrgend; else if (ptr->msound == MS_NEMESIS && quest_info(MS_NEMESIS) == mndx) - mtmp->female = quest_status.nemgend; + mtmp->female = svq.quest_status.nemgend; + + /* female used to be set randomly here even for neuters on the + grounds that it was ignored, but after corpses were changed to + retain gender it matters because it affects stacking of corpses */ else - mtmp->female = rn2(2); /* ignored for neuters */ + mtmp->female = femaleok ? rn2(2) : 0; - if (In_sokoban(&u.uz) && !mindless(ptr)) /* know about traps here */ - mtmp->mtrapseen = (1L << (PIT - 1)) | (1L << (HOLE - 1)); + if (In_sokoban(&u.uz) && !mindless(ptr)) { /* know about traps here */ + mon_learns_traps(mtmp, PIT); + mon_learns_traps(mtmp, HOLE); + } + if (Is_stronghold(&u.uz) && !mindless(ptr)) /* know about trap doors */ + mon_learns_traps(mtmp, TRAPDOOR); /* quest leader and nemesis both know about all trap types */ if (ptr->msound == MS_LEADER || ptr->msound == MS_NEMESIS) - mtmp->mtrapseen = ~0; + mon_learns_traps(mtmp, ALL_TRAPS); + /* locations where monsters are already experienced with wands */ + if (Is_stronghold(&u.uz) || Is_knox(&u.uz) || In_endgame(&u.uz) || + In_hell(&u.uz) || In_V_tower(&u.uz) || In_quest(&u.uz)) + mtmp->mwandexp = TRUE; place_monster(mtmp, x, y); mtmp->mcansee = mtmp->mcanmove = TRUE; + mtmp->mgenmklev = gi.in_mklev; + mtmp->seen_resistance = M_SEEN_NOTHING; mtmp->mpeaceful = (mmflags & MM_ANGRY) ? FALSE : peace_minded(ptr); + if ((mmflags & MM_MINVIS) != 0) /* for ^G */ + mon_set_minvis(mtmp, FALSE); /* call after place_monster() */ switch (ptr->mlet) { case S_MIMIC: @@ -1241,10 +1306,11 @@ int mmflags; break; case S_SPIDER: case S_SNAKE: - if (in_mklev) + if (gi.in_mklev) { if (x && y) - (void) mkobj_at(0, x, y, TRUE); - (void) hideunder(mtmp); + (void) mkobj_at(RANDOM_CLASS, x, y, TRUE); + (void) hideunder(mtmp); + } break; case S_LIGHT: case S_ELEMENTAL: @@ -1254,7 +1320,9 @@ int mmflags; } break; case S_EEL: - (void) hideunder(mtmp); + if (gi.in_mklev) { + (void) hideunder(mtmp); + } break; case S_LEPRECHAUN: mtmp->msleeping = 1; @@ -1280,7 +1348,7 @@ int mmflags; if ((ct = emits_light(mtmp->data)) > 0) new_light_source(mtmp->mx, mtmp->my, ct, LS_MONSTER, monst_to_any(mtmp)); - mitem = 0; /* extra inventory item for this monster */ + mitem = STRANGE_OBJECT; /* extra inventory item for this monster */ if (mndx == PM_VLAD_THE_IMPALER) mitem = CANDELABRUM_OF_INVOCATION; @@ -1296,12 +1364,12 @@ int mmflags; to the level's difficulty but ignoring the changer's usual type selection, so was inappropriate for vampshifters. Let newcham() pick the shape. */ - && newcham(mtmp, (struct permonst *) 0, FALSE, FALSE)) + && newcham(mtmp, (struct permonst *) 0, NO_NC_FLAGS)) allow_minvent = FALSE; } else if (mndx == PM_WIZARD_OF_YENDOR) { mtmp->iswiz = TRUE; - context.no_of_wizards++; - if (context.no_of_wizards == 1 && Is_earthlevel(&u.uz)) + svc.context.no_of_wizards++; + if (svc.context.no_of_wizards == 1 && Is_earthlevel(&u.uz)) mitem = SPE_DIG; } else if (mndx == PM_GHOST && !(mmflags & MM_NONAME)) { mtmp = christen_monst(mtmp, rndghostname()); @@ -1312,10 +1380,10 @@ int mmflags; } else if (mndx == PM_PESTILENCE) { mitem = POT_SICKNESS; } - if (mitem && allow_minvent) + if (mitem != STRANGE_OBJECT && allow_minvent) (void) mongets(mtmp, mitem); - if (in_mklev) { + if (gi.in_mklev) { if ((is_ndemon(ptr) || mndx == PM_WUMPUS || mndx == PM_LONG_WORM || mndx == PM_GIANT_EEL) && !u.uhave.amulet && rn2(5)) @@ -1329,20 +1397,13 @@ int mmflags; if (is_dprince(ptr) && ptr->msound == MS_BRIBE) { mtmp->mpeaceful = mtmp->minvis = mtmp->perminvis = 1; mtmp->mavenge = 0; - if (uwep && uwep->oartifact == ART_EXCALIBUR) + if (u_wield_art(ART_EXCALIBUR) || u_wield_art(ART_DEMONBANE)) mtmp->mpeaceful = mtmp->mtame = FALSE; } -#ifndef DCC30_BUG - if (mndx == PM_LONG_WORM && (mtmp->wormno = get_wormno()) != 0) -#else - /* DICE 3.0 doesn't like assigning and comparing mtmp->wormno in the - same expression. */ - if (mndx == PM_LONG_WORM - && (mtmp->wormno = get_wormno(), mtmp->wormno != 0)) -#endif - { - /* we can now create worms with tails - 11/91 */ - initworm(mtmp, rn2(5)); + if (mndx == PM_RAVEN && uwep && uwep->otyp == BEC_DE_CORBIN) + mtmp->mpeaceful = TRUE; + if (mndx == PM_LONG_WORM && (mtmp->wormno = get_wormno()) != 0) { + initworm(mtmp, allowtail ? rn2(5) : 0); if (count_wsegs(mtmp)) place_worm_tail_randomly(mtmp, x, y); } @@ -1350,7 +1411,7 @@ int mmflags; types; make sure their extended data is initialized to something sensible if caller hasn't specified MM_EPRI|MM_EMIN (when they're specified, caller intends to handle this itself) */ - if ((mndx == PM_ALIGNED_PRIEST || mndx == PM_HIGH_PRIEST) + if ((mndx == PM_ALIGNED_CLERIC || mndx == PM_HIGH_CLERIC) ? !(mmflags & (MM_EPRI | MM_EMIN)) : (mndx == PM_ANGEL && !(mmflags & MM_EMIN) && !rn2(3))) { struct emin *eminp; @@ -1385,15 +1446,15 @@ int mmflags; if (!rn2(100) && is_domestic(ptr) && can_saddle(mtmp) && !which_armor(mtmp, W_SADDLE)) { - struct obj *otmp = mksobj(SADDLE, TRUE, FALSE); - - put_saddle_on_mon(otmp, mtmp); + /* NULL obj arg means put_saddle_on_mon() + * will create the saddle itself */ + put_saddle_on_mon((struct obj *) 0, mtmp); } } else { /* no initial inventory is allowed */ if (mtmp->minvent) - discard_minvent(mtmp); + discard_minvent(mtmp, TRUE); mtmp->minvent = (struct obj *) 0; /* caller expects this */ } if (ptr->mflags3 && !(mmflags & MM_NOWAIT)) { @@ -1405,18 +1466,80 @@ int mmflags; mtmp->mstrategy |= STRAT_APPEARMSG; } - if (allow_minvent && migrating_objs) + if (allow_minvent && gm.migrating_objs) deliver_obj_to_mon(mtmp, 1, DF_NONE); /* in case of waiting items */ - if (!in_mklev) + if (!gi.in_mklev) { newsym(mtmp->mx, mtmp->my); /* make sure the mon shows up */ + if (!(mmflags & MM_NOMSG)) { + char mbuf[BUFSZ], *what = 0; + /* MM_NOEXCLAM is used for #wizgenesis (^G) */ + boolean exclaim = !(mmflags & MM_NOEXCLAM); + + if ((canseemon(mtmp) && (M_AP_TYPE(mtmp) == M_AP_NOTHING + || M_AP_TYPE(mtmp) == M_AP_MONSTER)) + || sensemon(mtmp)) { + what = Amonnam(mtmp); + if (M_AP_TYPE(mtmp) == M_AP_MONSTER) + exclaim = TRUE; + } else if (canseemon(mtmp)) { + /* mimic masquerading as furniture or object and not sensed */ + mhidden_description(mtmp, MHID_ARTICLE | MHID_ALTMON, mbuf); + what = upstart(mbuf); + } + if (what) { + set_msg_xy(mtmp->mx, mtmp->my); + Norep("%s%s %s%s%c", what, + exclaim ? " suddenly" : "", + /* 'what' might be "gold pieces" so need plural verb */ + vtense(what, "appear"), + next2u(x, y) ? " next to you" + : (distu(x, y) <= (BOLT_LIM * BOLT_LIM)) ? " close by" + : "", + exclaim ? '!' : '.'); + } + } + /* if discernable and a threat, stop fiddling while Rome burns */ + if (go.occupation) + (void) dochugw(mtmp, FALSE); + + /* TODO: unify with teleport appears msg */ + } return mtmp; } +/* caller rejects makemon()'s result; always returns Null */ +struct monst * +unmakemon( + struct monst *mon, + mmflags_nht mmflags) +{ + boolean countbirth = ((mmflags & MM_NOCOUNTBIRTH) == 0); + int mndx = monsndx(mon->data); + + /* if count has reached the limit of 255, we don't know whether + that just happened when creating this monster or the threshold + had already been reached and further increments were suppressed; + assume the latter */ + if (countbirth && svm.mvitals[mndx].born > 0 + && svm.mvitals[mndx].born < 255) + svm.mvitals[mndx].born -= 1; + if ((mon->data->geno & G_UNIQ) != 0) + svm.mvitals[mndx].mvflags &= ~G_EXTINCT; + + mon->mhp = 0; /* let discard_minvent() know that mon isn't being kept */ + /* uncreate any artifact that the monster was provided with; unlike + mongone(), this doesn't protect special items like the Amulet + by dropping them so caller should handle them when applicable */ + discard_minvent(mon, TRUE); + + mongone(mon); + return (struct monst *) 0; +} + int -mbirth_limit(mndx) -int mndx; +mbirth_limit(int mndx) { /* There is an implicit limit of 4 for "high priest of ", * but aligned priests can grow into high priests, thus they aren't @@ -1430,13 +1553,13 @@ int mndx; /* used for wand/scroll/spell of create monster */ /* returns TRUE iff you know monsters have been created */ boolean -create_critters(cnt, mptr, neverask) -int cnt; -struct permonst *mptr; /* usually null; used for confused reading */ -boolean neverask; +create_critters( + int cnt, + struct permonst *mptr, /* usually null; used for confused reading */ + boolean neverask) { coord c; - int x, y; + coordxy x, y; struct monst *mon; boolean known = FALSE; boolean ask = (wizard && !neverask); @@ -1455,20 +1578,23 @@ boolean neverask; if (!mptr && u.uinwater && enexto(&c, x, y, &mons[PM_GIANT_EEL])) x = c.x, y = c.y; - mon = makemon(mptr, x, y, NO_MM_FLAGS); - if (mon && canspotmon(mon)) + if ((mon = makemon(mptr, x, y, NO_MM_FLAGS)) == 0) + continue; /* try again [should probably stop instead] */ + + if ((canseemon(mon) && (M_AP_TYPE(mon) == M_AP_NOTHING + || M_AP_TYPE(mon) == M_AP_MONSTER)) + || sensemon(mon)) known = TRUE; } return known; } -STATIC_OVL boolean -uncommon(mndx) -int mndx; +staticfn boolean +uncommon(int mndx) { if (mons[mndx].geno & (G_NOGEN | G_UNIQ)) return TRUE; - if (mvitals[mndx].mvflags & G_GONE) + if (svm.mvitals[mndx].mvflags & G_GONE) return TRUE; if (Inhell) return (boolean) (mons[mndx].maligntyp > A_NEUTRAL); @@ -1481,19 +1607,18 @@ int mndx; * comparing the dungeon alignment and monster alignment. * return an integer in the range of 0-5. */ -STATIC_OVL int -align_shift(ptr) -register struct permonst *ptr; +staticfn int +align_shift(struct permonst *ptr) { static NEARDATA long oldmoves = 0L; /* != 1, starting value of moves */ static NEARDATA s_level *lev; - register int alshift; + int alshift; - if (oldmoves != moves) { + if (oldmoves != svm.moves) { lev = Is_special(&u.uz); - oldmoves = moves; + oldmoves = svm.moves; } - switch ((lev) ? lev->flags.align : dungeons[u.uz.dnum].flags.align) { + switch ((lev) ? lev->flags.align : svd.dungeons[u.uz.dnum].flags.align) { default: /* just in case */ case AM_NONE: alshift = 0; @@ -1511,124 +1636,114 @@ register struct permonst *ptr; return alshift; } -static NEARDATA struct { - int choice_count; - char mchoices[SPECIAL_PM]; /* value range is 0..127 */ -} rndmonst_state = { -1, { 0 } }; +/* return larger value if monster prefers the level temperature */ +staticfn int +temperature_shift(struct permonst *ptr) +{ + if (svl.level.flags.temperature + && pm_resistance(ptr, (svl.level.flags.temperature > 0) + ? MR_FIRE : MR_COLD)) + return 3; + return 0; +} /* select a random monster type */ struct permonst * -rndmonst() +rndmonst(void) +{ + return rndmonst_adj(0, 0); +} + +/* select a random monster type, with adjusted difficulty */ +struct permonst * +rndmonst_adj(int minadj, int maxadj) { - register struct permonst *ptr; - register int mndx, ct; + struct permonst *ptr; + int mndx; + int weight, totalweight, selected_mndx, zlevel, minmlev, maxmlev; + boolean elemlevel, upper; if (u.uz.dnum == quest_dnum && rn2(7) && (ptr = qt_montype()) != 0) return ptr; - if (rndmonst_state.choice_count < 0) { /* need to recalculate */ - int zlevel, minmlev, maxmlev; - boolean elemlevel; - boolean upper; + zlevel = level_difficulty(); + minmlev = monmin_difficulty(zlevel) + minadj; + maxmlev = monmax_difficulty(zlevel) + maxadj; + upper = Is_rogue_level(&u.uz); /* prefer uppercase only on rogue level */ + elemlevel = In_endgame(&u.uz) && !Is_astralevel(&u.uz); /* elmntl plane */ - rndmonst_state.choice_count = 0; - /* look for first common monster */ - for (mndx = LOW_PM; mndx < SPECIAL_PM; mndx++) { - if (!uncommon(mndx)) - break; - rndmonst_state.mchoices[mndx] = 0; - } - if (mndx == SPECIAL_PM) { - /* evidently they've all been exterminated */ - debugpline0("rndmonst: no common mons!"); - return (struct permonst *) 0; - } /* else `mndx' now ready for use below */ - zlevel = level_difficulty(); - /* determine the level of the weakest monster to make. */ - minmlev = zlevel / 6; - /* determine the level of the strongest monster to make. */ - maxmlev = (zlevel + u.ulevel) / 2; - upper = Is_rogue_level(&u.uz); - elemlevel = In_endgame(&u.uz) && !Is_astralevel(&u.uz); + /* amount processed so far */ + totalweight = 0; + selected_mndx = NON_PM; + + for (mndx = LOW_PM; mndx < SPECIAL_PM; ++mndx) { + ptr = &mons[mndx]; + + if (montooweak(mndx, minmlev) || montoostrong(mndx, maxmlev)) + continue; + if (upper && !isupper(monsym(ptr))) + continue; + if (elemlevel && wrong_elem_type(ptr)) + continue; + if (uncommon(mndx)) + continue; + if (Inhell && (ptr->geno & G_NOHELL)) + continue; /* - * Find out how many monsters exist in the range we have selected. + * Weighted reservoir sampling: select ptr with a + * (ptr weight)/(total of all weights so far including ptr's) + * probability. For example, if the previous total is 10, and + * this is now looking at acid blobs with a frequency of 2, it + * has a 2/12 chance of abandoning ptr's previous value in favor + * of acid blobs, and 10/12 chance of keeping whatever it was. + * + * This does not bias results towards either the earlier or the + * later monsters: the smaller pool and better odds from being + * earlier are exactly canceled out by having more monsters to + * potentially steal its spot. */ - for ( ; mndx < SPECIAL_PM; mndx++) { /* (`mndx' initialized above) */ - ptr = &mons[mndx]; - rndmonst_state.mchoices[mndx] = 0; - if (tooweak(mndx, minmlev) || toostrong(mndx, maxmlev)) - continue; - if (upper && !isupper((uchar) def_monsyms[(int) ptr->mlet].sym)) - continue; - if (elemlevel && wrong_elem_type(ptr)) - continue; - if (uncommon(mndx)) - continue; - if (Inhell && (ptr->geno & G_NOHELL)) - continue; - ct = (int) (ptr->geno & G_FREQ) + align_shift(ptr); - if (ct < 0 || ct > 127) - panic("rndmonst: bad count [#%d: %d]", mndx, ct); - rndmonst_state.choice_count += ct; - rndmonst_state.mchoices[mndx] = (char) ct; + weight = (int) (ptr->geno & G_FREQ) + align_shift(ptr); + weight += temperature_shift(ptr); + if (weight < 0 || weight > 127) { + impossible("bad weight in rndmonst for mndx %d", mndx); + weight = 0; + } + /* was unconditional, but if weight==0, rn2() < 0 will always fail; + also need to avoid rn2(0) if totalweight is still 0 so far */ + if (weight > 0) { + totalweight += weight; /* totalweight now guaranteed to be > 0 */ + if (rn2(totalweight) < weight) + selected_mndx = mndx; } - /* - * Possible modification: if choice_count is "too low", - * expand minmlev..maxmlev range and try again. - */ - } /* choice_count+mchoices[] recalc */ - - if (rndmonst_state.choice_count <= 0) { - /* maybe no common mons left, or all are too weak or too strong */ - debugpline1("rndmonst: choice_count=%d", rndmonst_state.choice_count); - return (struct permonst *) 0; } - /* - * Now, select a monster at random. + * Possible modification: if totalweight is "too low" or nothing + * viable was picked, expand minmlev..maxmlev range and try again. */ - ct = rnd(rndmonst_state.choice_count); - for (mndx = LOW_PM; mndx < SPECIAL_PM; mndx++) - if ((ct -= (int) rndmonst_state.mchoices[mndx]) <= 0) - break; - - if (mndx == SPECIAL_PM || uncommon(mndx)) { /* shouldn't happen */ - impossible("rndmonst: bad `mndx' [#%d]", mndx); + if (selected_mndx == NON_PM || uncommon(selected_mndx)) { + /* maybe no common monsters left, or all are too weak or too strong */ + if (selected_mndx != NON_PM) + debugpline1("rndmonst returning Null [uncommon 'mndx'=#%d]", + selected_mndx); return (struct permonst *) 0; } - return &mons[mndx]; -} - -/* called when you change level (experience or dungeon depth) or when - monster species can no longer be created (genocide or extinction) */ -void -reset_rndmonst(mndx) -int mndx; /* particular species that can no longer be created */ -{ - /* cached selection info is out of date */ - if (mndx == NON_PM) { - rndmonst_state.choice_count = -1; /* full recalc needed */ - } else if (mndx < SPECIAL_PM) { - rndmonst_state.choice_count -= rndmonst_state.mchoices[mndx]; - rndmonst_state.mchoices[mndx] = 0; - } /* note: safe to ignore extinction of unique monsters */ + return &mons[selected_mndx]; } /* decide whether it's ok to generate a candidate monster by mkclass() */ -STATIC_OVL boolean -mk_gen_ok(mndx, mvflagsmask, genomask) -int mndx, mvflagsmask, genomask; +staticfn boolean +mk_gen_ok(int mndx, unsigned mvflagsmask, unsigned genomask) { struct permonst *ptr = &mons[mndx]; - if (mvitals[mndx].mvflags & mvflagsmask) + if (svm.mvitals[mndx].mvflags & mvflagsmask) return FALSE; if (ptr->geno & genomask) return FALSE; if (is_placeholder(ptr)) return FALSE; -#ifdef MAIL +#ifdef MAIL_STRUCTURES /* special levels might ask for random demon type; reject this one */ if (ptr == &mons[PM_MAIL_DAEMON]) return FALSE; @@ -1636,28 +1751,140 @@ int mndx, mvflagsmask, genomask; return TRUE; } +/* monsters in order by mlet & difficulty for mkclass() */ +static int mongen_order[NUMMONS]; +static xint8 mclass_maxf[MAXMCLASSES]; +static boolean mongen_order_init = FALSE; + +staticfn int QSORTCALLBACK +cmp_init_mongen_order(const void *p1, const void *p2) +{ + int i1 = *((int *) p1), i2 = *((int *) p2); + #if 0 + /* This will cause these to be moved last in the mlet sort order */ + int offset1 = ((mons[i1].geno & (G_NOGEN | G_UNIQ)) != 0) ? 99 : 0, + offset2 = ((mons[i2].geno & (G_NOGEN | G_UNIQ)) != 0) ? 99 : 0; +#else + int offset1 = 0, offset2 = 0; +#endif + + /* incorporate the mlet into the sort values for comparison */ + int difficulty1 = + ((mons[i1].difficulty + offset1) | ((int) mons[i1].mlet << 8)), + difficulty2 = + ((mons[i2].difficulty + offset2) | ((int) mons[i2].mlet << 8)); + return difficulty1 - difficulty2; +} + +#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) +staticfn void check_mongen_order(void); +/* check that monsters are in correct difficulty order for mkclass() */ +staticfn void +check_mongen_order(void) +{ + int i, diff = 0; + char mlet = '\0'; + for (i = LOW_PM; i < SPECIAL_PM; i++) { + if (i != mongen_order[i]) { + debugpline2("changed:%s=>%s", mons[i].pmnames[NEUTRAL], mons[mongen_order[i]].pmnames[NEUTRAL]); + } + + if (mlet == mons[mongen_order[i]].mlet) { + if (mons[mongen_order[i]].difficulty < diff) + debugpline1("%s", mons[mongen_order[i]].pmnames[NEUTRAL]); + diff = mons[mongen_order[i]].difficulty; + } + if (!mlet || mlet != mons[mongen_order[i]].mlet) { + mlet = mons[mongen_order[i]].mlet; + diff = 0; + } + } +} +#endif + +/* initialize monster order for mkclass */ +staticfn void +init_mongen_order(void) +{ + int i, mlet; + + if (mongen_order_init) + return; + + mongen_order_init = TRUE; + for (i = LOW_PM; i < NUMMONS; i++) { + mongen_order[i] = i; + mlet = mons[i].mlet; + if ((xint8) (mons[i].geno & G_FREQ) > mclass_maxf[mlet]) + mclass_maxf[mlet] = (xint8) (mons[i].geno & G_FREQ); + } +#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) + check_mongen_order(); +#endif + qsort((genericptr_t) mongen_order, SPECIAL_PM, sizeof(int), cmp_init_mongen_order); +#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) + check_mongen_order(); +#endif +} + +#define MONSi(i) (mongen_order[i]) + +extern struct enum_dump monsdump[]; /* allmain.c */ + +void +dump_mongen(void) +{ + char mlet, prev_mlet = 0; + int i, nmwidth = 27, special; + char nmbuf[80]; + + monst_globals_init(); + init_mongen_order(); + raw_printf("int mongen_order[] = {"); + for (i = LOW_PM; i < SPECIAL_PM; ++i) { + special = (mons[MONSi(i)].geno & (G_NOGEN | G_UNIQ)); + mlet = def_monsyms[(int) mons[MONSi(i)].mlet].sym; + if (prev_mlet && prev_mlet != mlet) + raw_print(""); + Snprintf(nmbuf, sizeof nmbuf, "PM_%s%s", + monsdump[MONSi(i)].nm, + (i == SPECIAL_PM - 1) ? "" : ","); + raw_printf(" %*s /* %c seq=%3d, idx=%3d, sym='%c', diff=%2d, freq=%2d[%d] %s */", + -nmwidth, nmbuf, (i == MONSi(i)) ? ' ' : '.', i, MONSi(i), + mlet, (int) mons[MONSi(i)].difficulty, + (int) (mons[MONSi(i)].geno & G_FREQ), + (int) mclass_maxf[(int) mons[MONSi(i)].mlet], + (special == (G_NOGEN | G_UNIQ)) ? "(G_NOGEN | G_UNIQ)" + : (special == G_NOGEN) ? "(G_NOGEN)" + : (special == G_UNIQ) ? "(G_UNIQ)" + : ""); + prev_mlet = mlet; + } + raw_print("};"); + raw_print(""); + freedynamicdata(); +} + /* Make one of the multiple types of a given monster class. The second parameter specifies a special casing bit mask to allow the normal genesis masks to be deactivated. Returns Null if no monsters in that class can be made. */ struct permonst * -mkclass(class, spc) -char class; -int spc; +mkclass(char class, int spc) { return mkclass_aligned(class, spc, A_NONE); } /* mkclass() with alignment restrictions; used by ndemon() */ struct permonst * -mkclass_aligned(class, spc, atyp) -char class; -int spc; -aligntyp atyp; +mkclass_aligned(char class, int spc, /* special mons[].geno handling */ + aligntyp atyp) { - register int first, last, num = 0; + int first, last, num = 0; int k, nums[SPECIAL_PM + 1]; /* +1: insurance for final return value */ - int maxmlev, mask = (G_NOGEN | G_UNIQ) & ~spc; + int maxmlev, gehennom = Inhell != 0; + unsigned mv_mask, gn_mask; + boolean zero_freq_for_entire_class; (void) memset((genericptr_t) nums, 0, sizeof nums); maxmlev = level_difficulty() >> 1; @@ -1665,6 +1892,11 @@ aligntyp atyp; impossible("mkclass called with bad class!"); return (struct permonst *) 0; } + + init_mongen_order(); + /* the following must come after init_mongen_order() */ + zero_freq_for_entire_class = (mclass_maxf[(int) class] == 0); + /* Assumption #1: monsters of a given class are contiguous in the * mons[] array. Player monsters and quest denizens * are an exception; mkclass() won't pick them. @@ -1672,41 +1904,60 @@ aligntyp atyp; * regular monsters from the exceptions. */ for (first = LOW_PM; first < SPECIAL_PM; first++) - if (mons[first].mlet == class) + if (mons[MONSi(first)].mlet == class) break; if (first == SPECIAL_PM) { impossible("mkclass found no class %d monsters", class); return (struct permonst *) 0; } + mv_mask = G_GONE; /* G_GENOD | G_EXTINCT */ + if ((spc & G_IGNORE) != 0) { + mv_mask = 0; /* mv_mask &= ~G_GONE; */ + /* G_IGNORE is not a mons[].geno mask so get rid of it now */ + spc &= ~G_IGNORE; + } + /* Assumption #2: monsters of a given class are presented in ascending * order of strength. */ - for (last = first; last < SPECIAL_PM && mons[last].mlet == class; last++) { - if (atyp != A_NONE && sgn(mons[last].maligntyp) != sgn(atyp)) + for (last = first; last < SPECIAL_PM && mons[MONSi(last)].mlet == class; + last++) { + if (atyp != A_NONE && sgn(mons[MONSi(last)].maligntyp) != sgn(atyp)) continue; - if (mk_gen_ok(last, G_GONE, mask)) { + /* traditionally mkclass() ignored hell-only and never-in-hell; + now we usually honor those but not all the time, mostly so that + the majority of major demons aren't constrained to Gehennom; + arch- and master liches are always so constrained (for creation; + lesser liches might grow up into them elsewhere) */ + gn_mask = (G_NOGEN | G_UNIQ); + if (rn2(9) || class == S_LICH) + gn_mask |= (gehennom ? G_NOHELL : G_HELL); + gn_mask &= ~spc; + + if (mk_gen_ok(MONSi(last), mv_mask, gn_mask)) { /* consider it; don't reject a toostrong() monster if we don't have anything yet (num==0) or if it is the same (or lower) difficulty as preceding candidate (non-zero 'num' implies last > first so mons[last-1] is safe); sometimes accept it even if high difficulty */ - if (num && toostrong(last, maxmlev) - && mons[last].difficulty > mons[last - 1].difficulty + if (num && montoostrong(MONSi(last), maxmlev) + && mons[MONSi(last)].difficulty > mons[MONSi(last - 1)].difficulty && rn2(2)) break; - if ((k = (mons[last].geno & G_FREQ)) > 0) { + if ((k = (mons[MONSi(last)].geno & G_FREQ)) > 0 + || (k = (zero_freq_for_entire_class ? 1 : 0)) > 0) { /* skew towards lower value monsters at lower exp. levels (this used to be done in the next loop, but that didn't work well when multiple species had the same level and were followed by one that was past the bias threshold; - cited example was sucubus and incubus, where the bias + cited example was succubus and incubus, where the bias against picking the next demon resulted in incubus - being picked nearly twice as often as sucubus); + being picked nearly twice as often as succubus); we need the '+1' in case the entire set is too high - level (really low level hero) */ - nums[last] = k + 1 - (adj_lev(&mons[last]) > (u.ulevel * 2)); - num += nums[last]; + level (really low svl.level hero) */ + nums[MONSi(last)] = k + 1 - (adj_lev(&mons[MONSi(last)]) > (u.ulevel * 2)); + num += nums[MONSi(last)]; } } } @@ -1716,21 +1967,23 @@ aligntyp atyp; /* the hard work has already been done; 'num' should hit 0 before first reaches last (which is actually one past our last candidate) */ for (num = rnd(num); first < last; first++) - if ((num -= nums[first]) <= 0) + if ((num -= nums[MONSi(first)]) <= 0) break; - return nums[first] ? &mons[first] : (struct permonst *) 0; + return nums[MONSi(first)] ? &mons[MONSi(first)] : (struct permonst *) 0; } +#undef MONSi + /* like mkclass(), but excludes difficulty considerations; used when player with polycontrol picks a class instead of a specific type; genocided types are avoided but extinct ones are acceptable; we don't check polyok() here--caller accepts some choices !polyok() would reject */ int -mkclass_poly(class) -int class; +mkclass_poly(int class) { - register int first, last, num = 0; + int first, last, num = 0; + unsigned gmask; for (first = LOW_PM; first < SPECIAL_PM; first++) if (mons[first].mlet == class) @@ -1738,14 +1991,20 @@ int class; if (first == SPECIAL_PM) return NON_PM; + gmask = (G_NOGEN | G_UNIQ); + /* mkclass() does this on a per monster type basis, but doing that here + would make the two loops inconsistent with each other for non L */ + if (rn2(9) || class == S_LICH) + gmask |= (Inhell ? G_NOHELL : G_HELL); + for (last = first; last < SPECIAL_PM && mons[last].mlet == class; last++) - if (mk_gen_ok(last, G_GENOD, (G_NOGEN | G_UNIQ))) + if (mk_gen_ok(last, G_GENOD, gmask)) num += mons[last].geno & G_FREQ; if (!num) return NON_PM; for (num = rnd(num); num > 0; first++) - if (mk_gen_ok(first, G_GENOD, (G_NOGEN | G_UNIQ))) + if (mk_gen_ok(first, G_GENOD, gmask)) num -= mons[first].geno & G_FREQ; first--; /* correct an off-by-one error */ @@ -1754,8 +2013,7 @@ int class; /* adjust strength of monsters based on u.uz and u.ulevel */ int -adj_lev(ptr) -register struct permonst *ptr; +adj_lev(struct permonst *ptr) { int tmp, tmp2; @@ -1763,7 +2021,7 @@ register struct permonst *ptr; /* does not depend on other strengths, but does get stronger * every time he is killed */ - tmp = ptr->mlevel + mvitals[PM_WIZARD_OF_YENDOR].died; + tmp = ptr->mlevel + svm.mvitals[PM_WIZARD_OF_YENDOR].died; if (tmp > 49) tmp = 49; return tmp; @@ -1790,8 +2048,7 @@ register struct permonst *ptr; /* monster earned experience and will gain some hit points; it might also grow into a bigger monster (baby to adult, soldier to officer, etc) */ struct permonst * -grow_up(mtmp, victim) -struct monst *mtmp, *victim; +grow_up(struct monst *mtmp, struct monst *victim) { int oldtype, newtype, max_increase, cur_increase, lev_limit, hp_threshold; unsigned fem; @@ -1803,11 +2060,16 @@ struct monst *mtmp, *victim; return (struct permonst *) 0; /* note: none of the monsters with special hit point calculations - have both little and big forms */ + have both little and big forms (killer bee can't grow into queen + bee by just killing things, so isn't in the little_to_big list) */ oldtype = monsndx(ptr); - newtype = little_to_big(oldtype); + newtype = (oldtype == PM_KILLER_BEE && !victim) ? PM_QUEEN_BEE + : little_to_big(oldtype); +#if 0 + /* gender-neutral PM_CLERIC now */ if (newtype == PM_PRIEST && mtmp->female) newtype = PM_PRIESTESS; +#endif /* growth limits differ depending on method of advancement */ if (victim) { /* killed a monster */ @@ -1860,12 +2122,12 @@ struct monst *mtmp, *victim; /* new form might force gender change */ fem = is_male(ptr) ? 0 : is_female(ptr) ? 1 : mtmp->female; - if (mvitals[newtype].mvflags & G_GENOD) { /* allow G_EXTINCT */ + if (svm.mvitals[newtype].mvflags & G_GENOD) { /* allow G_EXTINCT */ if (canspotmon(mtmp)) pline("As %s grows up into %s, %s %s!", mon_nam(mtmp), - an(ptr->mname), mhe(mtmp), + an(pmname(ptr, Mgender(mtmp))), mhe(mtmp), nonliving(ptr) ? "expires" : "dies"); - set_mon_data(mtmp, ptr); /* keep mvitals[] accurate */ + set_mon_data(mtmp, ptr); /* keep svm.mvitals[] accurate */ mondied(mtmp); return (struct permonst *) 0; } else if (canspotmon(mtmp)) { @@ -1881,18 +2143,23 @@ struct monst *mtmp, *victim; (can't happen with 3.6.0 mons[], but perhaps slightly less sexist if prepared for it...) */ : (fem && !mtmp->female) ? "female " : "", - ptr->mname); - pline("%s %s %s.", upstart(y_monnam(mtmp)), - (fem != mtmp->female) ? "changes into" - : humanoid(ptr) ? "becomes" - : "grows up into", - an(buf)); + pmname(ptr, fem)); + pline_mon(mtmp, "%s %s %s.", YMonnam(mtmp), + (fem != mtmp->female) ? "changes into" + : humanoid(ptr) ? "becomes" + : "grows up into", + an(buf)); } set_mon_data(mtmp, ptr); + if (mtmp->cham == oldtype && is_shapeshifter(ptr)) + mtmp->cham = newtype; /* vampire growing into vampire lord */ newsym(mtmp->mx, mtmp->my); /* color may change */ lev_limit = (int) mtmp->m_lev; /* never undo increment */ mtmp->female = fem; /* gender might be changing */ + /* if 'mtmp' is leashed, persistent inventory window needs updating */ + if (mtmp->mleashed) + update_inventory(); /* x - leash (attached to a ) */ } /* sanity checks */ @@ -1910,16 +2177,13 @@ struct monst *mtmp, *victim; return ptr; } -int -mongets(mtmp, otyp) -register struct monst *mtmp; -int otyp; +struct obj * +mongets(struct monst *mtmp, int otyp) { - register struct obj *otmp; - int spe; + struct obj *otmp; if (!otyp) - return 0; + return (struct obj *) 0; otmp = mksobj(otyp, TRUE, FALSE); if (otmp) { if (mtmp->data->mlet == S_DEMON) { @@ -1931,7 +2195,8 @@ int otyp; otmp->cursed = FALSE; if (otmp->spe < 0) otmp->spe = 0; - otmp->oerodeproof = TRUE; + otmp->oerodeproof = 1; + otmp->oeroded = otmp->oeroded2 = 0; } else if (is_mplayer(mtmp->data) && is_sword(otmp)) { otmp->spe = (3 + rn2(4)); } @@ -1956,16 +2221,16 @@ int otyp; otmp->spe = 0; } - spe = otmp->spe; - (void) mpickobj(mtmp, otmp); /* might free otmp */ - return spe; + if (mpickobj(mtmp, otmp)) { + /* otmp was freed via merging with something else */ + otmp = (struct obj *) 0; + } } - return 0; + return otmp; } int -golemhp(type) -int type; +golemhp(int type) { switch (type) { case PM_STRAW_GOLEM: @@ -1977,19 +2242,19 @@ int type; case PM_LEATHER_GOLEM: return 40; case PM_GOLD_GOLEM: - return 40; + return 60; case PM_WOOD_GOLEM: return 50; case PM_FLESH_GOLEM: return 40; case PM_CLAY_GOLEM: - return 50; + return 70; case PM_STONE_GOLEM: - return 60; + return 100; case PM_GLASS_GOLEM: - return 60; - case PM_IRON_GOLEM: return 80; + case PM_IRON_GOLEM: + return 120; default: return 0; } @@ -2000,8 +2265,7 @@ int type; * (Some "animal" types are co-aligned, but also hungry.) */ boolean -peace_minded(ptr) -register struct permonst *ptr; +peace_minded(struct permonst *ptr) { aligntyp mal = ptr->maligntyp, ual = u.ualign.type; @@ -2013,6 +2277,8 @@ register struct permonst *ptr; return TRUE; if (ptr->msound == MS_NEMESIS) return FALSE; + if (ptr == &mons[PM_ERINYS]) + return !u.ualign.abuse; if (race_peaceful(ptr)) return TRUE; @@ -2052,8 +2318,7 @@ register struct permonst *ptr; * it's never bad to kill a hostile monster, although it may not be good. */ void -set_malign(mtmp) -struct monst *mtmp; +set_malign(struct monst *mtmp) { schar mal = mtmp->data->maligntyp; boolean coaligned; @@ -2102,8 +2367,7 @@ struct monst *mtmp; /* allocate a new mcorpsenm field for a monster; only need mextra itself */ void -newmcorpsenm(mtmp) -struct monst *mtmp; +newmcorpsenm(struct monst *mtmp) { if (!mtmp->mextra) mtmp->mextra = newmextra(); @@ -2112,23 +2376,21 @@ struct monst *mtmp; /* release monster's mcorpsenm field; basically a no-op */ void -freemcorpsenm(mtmp) -struct monst *mtmp; +freemcorpsenm(struct monst *mtmp) { if (has_mcorpsenm(mtmp)) MCORPSENM(mtmp) = NON_PM; } -static NEARDATA char syms[] = { - MAXOCLASSES, MAXOCLASSES + 1, RING_CLASS, WAND_CLASS, WEAPON_CLASS, +static const NEARDATA char syms[] = { + MAXOCLASSES, MAXOCLASSES, RING_CLASS, WAND_CLASS, WEAPON_CLASS, FOOD_CLASS, COIN_CLASS, SCROLL_CLASS, POTION_CLASS, ARMOR_CLASS, AMULET_CLASS, TOOL_CLASS, ROCK_CLASS, GEM_CLASS, SPBOOK_CLASS, S_MIMIC_DEF, S_MIMIC_DEF, }; void -set_mimic_sym(mtmp) -register struct monst *mtmp; +set_mimic_sym(struct monst *mtmp) { int typ, roomno, rt; unsigned appear, ap_type; @@ -2144,7 +2406,7 @@ register struct monst *mtmp; /* only valid for INSIDE of room */ roomno = levl[mx][my].roomno - ROOMOFFSET; if (roomno >= 0) - rt = rooms[roomno].rtype; + rt = svr.rooms[roomno].rtype; #ifdef SPECIALIZATION else if (IS_ROOM(typ)) rt = OROOM, roomno = 0; @@ -2154,7 +2416,7 @@ register struct monst *mtmp; if (OBJ_AT(mx, my)) { ap_type = M_AP_OBJECT; - appear = level.objects[mx][my]->otyp; + appear = svl.level.objects[mx][my]->otyp; } else if (IS_DOOR(typ) || IS_WALL(typ) || typ == SDOOR || typ == SCORR) { ap_type = M_AP_FURNITURE; /* @@ -2174,7 +2436,9 @@ register struct monst *mtmp; appear = Is_rogue_level(&u.uz) ? S_hwall : S_hcdoor; else appear = Is_rogue_level(&u.uz) ? S_vwall : S_vcdoor; - } else if (level.flags.is_maze_lev && !In_sokoban(&u.uz) && rn2(2)) { + } else if (svl.level.flags.is_maze_lev + && !(In_mines(&u.uz) && in_town(u.ux, u.uy)) + && !In_sokoban(&u.uz) && rn2(2)) { ap_type = M_AP_OBJECT; appear = STATUE; } else if (roomno < 0 && !t_at(mx, my)) { @@ -2201,21 +2465,36 @@ register struct monst *mtmp; */ } else if (rt >= SHOPBASE) { + if (rn2(10) >= depth(&u.uz)) { + s_sym = S_MIMIC_DEF; /* -> STRANGE_OBJECT */ + goto assign_sym; + } s_sym = get_shop_item(rt - SHOPBASE); if (s_sym < 0) { ap_type = M_AP_OBJECT; appear = -s_sym; + } else if (rt == FODDERSHOP && s_sym > MAXOCLASSES) { + /* health food store usually generates pseudo-class + VEGETARIAN_CLASS which is MAXOCLASSES+1; we don't bother + trying to select among all possible vegetarian food items */ + ap_type = M_AP_OBJECT; + appear = rn2(2) ? LUMP_OF_ROYAL_JELLY : SLIME_MOLD; } else { - if (s_sym == RANDOM_CLASS) - s_sym = syms[rn2((int) sizeof(syms) - 2) + 2]; + if (s_sym == RANDOM_CLASS || s_sym >= MAXOCLASSES) + s_sym = syms[rn2(SIZE(syms) - 2) + 2]; goto assign_sym; } } else { - s_sym = syms[rn2((int) sizeof(syms))]; + s_sym = ROLL_FROM(syms); assign_sym: - if (s_sym == MAXOCLASSES || s_sym == MAXOCLASSES + 1) { + if (s_sym == MAXOCLASSES) { + static const int furnsyms[] = { + S_upstair, S_upstair, S_dnstair, S_dnstair, + S_altar, S_grave, S_throne, S_sink + }; + ap_type = M_AP_FURNITURE; - appear = (s_sym == MAXOCLASSES) ? S_upstair : S_dnstair; + appear = ROLL_FROM(furnsyms); } else { ap_type = M_AP_OBJECT; if (s_sym == S_MIMIC_DEF) { @@ -2237,7 +2516,7 @@ register struct monst *mtmp; && (appear == STATUE || appear == FIGURINE || appear == CORPSE || appear == EGG || appear == TIN)) { int mndx = rndmonnum(), - nocorpse_ndx = (mvitals[mndx].mvflags & G_NOCORPSE) != 0; + nocorpse_ndx = (svm.mvitals[mndx].mvflags & G_NOCORPSE) != 0; if (appear == CORPSE && nocorpse_ndx) mndx = rn1(PM_WIZARD - PM_ARCHEOLOGIST + 1, PM_ARCHEOLOGIST); @@ -2249,7 +2528,7 @@ register struct monst *mtmp; MCORPSENM(mtmp) = mndx; } else if (ap_type == M_AP_OBJECT && appear == SLIME_MOLD) { newmcorpsenm(mtmp); - MCORPSENM(mtmp) = context.current_fruit; + MCORPSENM(mtmp) = svc.context.current_fruit; /* if no objects of this fruit type have been created yet, context.current_fruit is available for re-use when the player assigns a new fruit name; override that--having a mimic as the @@ -2272,10 +2551,10 @@ register struct monst *mtmp; /* release monster from bag of tricks; return number of monsters created */ int -bagotricks(bag, tipping, seencount) -struct obj *bag; -boolean tipping; /* caller emptying entire contents; affects shop handling */ -int *seencount; /* secondary output */ +bagotricks( + struct obj *bag, + boolean tipping, /* caller emptying entirely; affects shop handling */ + int *seencount) /* secondary output */ { int moncount = 0; @@ -2285,8 +2564,10 @@ int *seencount; /* secondary output */ /* if tipping known empty bag, give normal empty container message */ pline1((tipping && bag->cknown) ? "It's empty." : nothing_happens); /* now known to be empty if sufficiently discovered */ - if (bag->dknown && objects[bag->otyp].oc_name_known) + if (bag->dknown && objects[bag->otyp].oc_name_known) { bag->cknown = 1; + update_inventory(); /* for perm_invent */ + } } else { struct monst *mtmp; int creatcnt = 1, seecount = 0; @@ -2299,20 +2580,35 @@ int *seencount; /* secondary output */ mtmp = makemon((struct permonst *) 0, u.ux, u.uy, NO_MM_FLAGS); if (mtmp) { ++moncount; - if (canspotmon(mtmp)) + if ((canseemon(mtmp) && (M_AP_TYPE(mtmp) == M_AP_NOTHING + || M_AP_TYPE(mtmp) == M_AP_MONSTER)) + || sensemon(mtmp)) ++seecount; } } while (--creatcnt > 0); if (seecount) { if (seencount) *seencount += seecount; - if (bag->dknown) + if (bag->dknown) { makeknown(BAG_OF_TRICKS); + update_inventory(); /* for perm_invent */ + } } else if (!tipping) { - pline1(!moncount ? nothing_happens : "Nothing seems to happen."); + pline1(!moncount ? nothing_happens : nothing_seems_to_happen); } } return moncount; } +/* create some or all remaining erinyes around the player */ +void +summon_furies(int limit) /* number to create, or 0 to create until extinct */ +{ + int i = 0; + while (mk_gen_ok(PM_ERINYS, G_GONE, 0U) && (i < limit || !limit)) { + makemon(&mons[PM_ERINYS], u.ux, u.uy, MM_ADJACENTOK | MM_NOWAIT); + i++; + } +} + /*makemon.c*/ diff --git a/src/mapglyph.c b/src/mapglyph.c deleted file mode 100644 index cec7e2e80..000000000 --- a/src/mapglyph.c +++ /dev/null @@ -1,371 +0,0 @@ -/* NetHack 3.6 mapglyph.c $NHDT-Date: 1573943501 2019/11/16 22:31:41 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.51 $ */ -/* Copyright (c) David Cohrs, 1991 */ -/* NetHack may be freely redistributed. See license for details. */ - -#include "hack.h" -#if defined(TTY_GRAPHICS) -#include "wintty.h" /* for prototype of has_color() only */ -#endif -#include "color.h" -#define HI_DOMESTIC CLR_WHITE /* monst.c */ - -#if !defined(TTY_GRAPHICS) -#define has_color(n) TRUE -#endif - -#ifdef TEXTCOLOR -static const int explcolors[] = { - CLR_BLACK, /* dark */ - CLR_GREEN, /* noxious */ - CLR_BROWN, /* muddy */ - CLR_BLUE, /* wet */ - CLR_MAGENTA, /* magical */ - CLR_ORANGE, /* fiery */ - CLR_WHITE, /* frosty */ -}; - -#define zap_color(n) color = iflags.use_color ? zapcolors[n] : NO_COLOR -#define cmap_color(n) color = iflags.use_color ? defsyms[n].color : NO_COLOR -#define obj_color(n) color = iflags.use_color ? objects[n].oc_color : NO_COLOR -#define mon_color(n) color = iflags.use_color ? mons[n].mcolor : NO_COLOR -#define invis_color(n) color = NO_COLOR -#define pet_color(n) color = iflags.use_color ? mons[n].mcolor : NO_COLOR -#define warn_color(n) \ - color = iflags.use_color ? def_warnsyms[n].color : NO_COLOR -#define explode_color(n) color = iflags.use_color ? explcolors[n] : NO_COLOR - -#else /* no text color */ - -#define zap_color(n) -#define cmap_color(n) -#define obj_color(n) -#define mon_color(n) -#define invis_color(n) -#define pet_color(c) -#define warn_color(n) -#define explode_color(n) -#endif - -#if defined(USE_TILES) && defined(MSDOS) -#define HAS_ROGUE_IBM_GRAPHICS \ - (currentgraphics == ROGUESET && SYMHANDLING(H_IBM) && !iflags.grmode) -#else -#define HAS_ROGUE_IBM_GRAPHICS \ - (currentgraphics == ROGUESET && SYMHANDLING(H_IBM)) -#endif - -#define is_objpile(x,y) (!Hallucination && level.objects[(x)][(y)] \ - && level.objects[(x)][(y)]->nexthere) - -/*ARGSUSED*/ -int -mapglyph(glyph, ochar, ocolor, ospecial, x, y, mgflags) -int glyph, *ocolor, x, y; -int *ochar; -unsigned *ospecial; -unsigned mgflags; -{ - register int offset, idx; - int color = NO_COLOR; - nhsym ch; - unsigned special = 0; - /* condense multiple tests in macro version down to single */ - boolean has_rogue_ibm_graphics = HAS_ROGUE_IBM_GRAPHICS, - is_you = (x == u.ux && y == u.uy), - has_rogue_color = (has_rogue_ibm_graphics - && symset[currentgraphics].nocolor == 0); - - /* - * Map the glyph back to a character and color. - * - * Warning: For speed, this makes an assumption on the order of - * offsets. The order is set in display.h. - */ - if ((offset = (glyph - GLYPH_STATUE_OFF)) >= 0) { /* a statue */ - idx = mons[offset].mlet + SYM_OFF_M; - if (has_rogue_color) - color = CLR_RED; - else - obj_color(STATUE); - special |= MG_STATUE; - if (is_objpile(x,y)) - special |= MG_OBJPILE; - } else if ((offset = (glyph - GLYPH_WARNING_OFF)) >= 0) { /* warn flash */ - idx = offset + SYM_OFF_W; - if (has_rogue_color) - color = NO_COLOR; - else - warn_color(offset); - } else if ((offset = (glyph - GLYPH_SWALLOW_OFF)) >= 0) { /* swallow */ - /* see swallow_to_glyph() in display.c */ - idx = (S_sw_tl + (offset & 0x7)) + SYM_OFF_P; - if (has_rogue_color && iflags.use_color) - color = NO_COLOR; - else - mon_color(offset >> 3); - } else if ((offset = (glyph - GLYPH_ZAP_OFF)) >= 0) { /* zap beam */ - /* see zapdir_to_glyph() in display.c */ - idx = (S_vbeam + (offset & 0x3)) + SYM_OFF_P; - if (has_rogue_color && iflags.use_color) - color = NO_COLOR; - else - zap_color((offset >> 2)); - } else if ((offset = (glyph - GLYPH_EXPLODE_OFF)) >= 0) { /* explosion */ - idx = ((offset % MAXEXPCHARS) + S_explode1) + SYM_OFF_P; - explode_color(offset / MAXEXPCHARS); - } else if ((offset = (glyph - GLYPH_CMAP_OFF)) >= 0) { /* cmap */ - idx = offset + SYM_OFF_P; - if (has_rogue_color && iflags.use_color) { - if (offset >= S_vwall && offset <= S_hcdoor) - color = CLR_BROWN; - else if (offset >= S_arrow_trap && offset <= S_polymorph_trap) - color = CLR_MAGENTA; - else if (offset == S_corr || offset == S_litcorr) - color = CLR_GRAY; - else if (offset >= S_room && offset <= S_water - && offset != S_darkroom) - color = CLR_GREEN; - else - color = NO_COLOR; -#ifdef TEXTCOLOR - /* provide a visible difference if normal and lit corridor - use the same symbol */ - } else if (iflags.use_color && offset == S_litcorr - && showsyms[idx] == showsyms[S_corr + SYM_OFF_P]) { - color = CLR_WHITE; -#endif - /* try to provide a visible difference between water and lava - if they use the same symbol and color is disabled */ - } else if (!iflags.use_color && offset == S_lava - && (showsyms[idx] == showsyms[S_pool + SYM_OFF_P] - || showsyms[idx] == showsyms[S_water + SYM_OFF_P])) { - special |= MG_BW_LAVA; - } else { - cmap_color(offset); - } - } else if ((offset = (glyph - GLYPH_OBJ_OFF)) >= 0) { /* object */ - idx = objects[offset].oc_class + SYM_OFF_O; - if (offset == BOULDER) - idx = SYM_BOULDER + SYM_OFF_X; - if (has_rogue_color && iflags.use_color) { - switch (objects[offset].oc_class) { - case COIN_CLASS: - color = CLR_YELLOW; - break; - case FOOD_CLASS: - color = CLR_RED; - break; - default: - color = CLR_BRIGHT_BLUE; - break; - } - } else - obj_color(offset); - if (offset != BOULDER && is_objpile(x,y)) - special |= MG_OBJPILE; - } else if ((offset = (glyph - GLYPH_RIDDEN_OFF)) >= 0) { /* mon ridden */ - idx = mons[offset].mlet + SYM_OFF_M; - if (has_rogue_color) - /* This currently implies that the hero is here -- monsters */ - /* don't ride (yet...). Should we set it to yellow like in */ - /* the monster case below? There is no equivalent in rogue. */ - color = NO_COLOR; /* no need to check iflags.use_color */ - else - mon_color(offset); - special |= MG_RIDDEN; - } else if ((offset = (glyph - GLYPH_BODY_OFF)) >= 0) { /* a corpse */ - idx = objects[CORPSE].oc_class + SYM_OFF_O; - if (has_rogue_color && iflags.use_color) - color = CLR_RED; - else - mon_color(offset); - special |= MG_CORPSE; - if (is_objpile(x,y)) - special |= MG_OBJPILE; - } else if ((offset = (glyph - GLYPH_DETECT_OFF)) >= 0) { /* mon detect */ - idx = mons[offset].mlet + SYM_OFF_M; - if (has_rogue_color) - color = NO_COLOR; /* no need to check iflags.use_color */ - else - mon_color(offset); - /* Disabled for now; anyone want to get reverse video to work? */ - /* is_reverse = TRUE; */ - special |= MG_DETECT; - } else if ((offset = (glyph - GLYPH_INVIS_OFF)) >= 0) { /* invisible */ - idx = SYM_INVISIBLE + SYM_OFF_X; - if (has_rogue_color) - color = NO_COLOR; /* no need to check iflags.use_color */ - else - invis_color(offset); - special |= MG_INVIS; - } else if ((offset = (glyph - GLYPH_PET_OFF)) >= 0) { /* a pet */ - idx = mons[offset].mlet + SYM_OFF_M; - if (has_rogue_color) - color = NO_COLOR; /* no need to check iflags.use_color */ - else - pet_color(offset); - special |= MG_PET; - } else { /* a monster */ - idx = mons[glyph].mlet + SYM_OFF_M; - if (has_rogue_color && iflags.use_color) { - if (is_you) - /* actually player should be yellow-on-gray if in corridor */ - color = CLR_YELLOW; - else - color = NO_COLOR; - } else { - mon_color(glyph); -#ifdef TEXTCOLOR - /* special case the hero for `showrace' option */ - if (iflags.use_color && is_you && flags.showrace && !Upolyd) - color = HI_DOMESTIC; -#endif - } - } - - /* These were requested by a blind player to enhance screen reader use */ - if (sysopt.accessibility == 1 && !(mgflags & MG_FLAG_NOOVERRIDE)) { - int ovidx; - - if ((special & MG_PET) != 0) { - ovidx = SYM_PET_OVERRIDE + SYM_OFF_X; - if (Is_rogue_level(&u.uz) ? ov_rogue_syms[ovidx] - : ov_primary_syms[ovidx]) - idx = ovidx; - } - if (is_you) { - ovidx = SYM_HERO_OVERRIDE + SYM_OFF_X; - if (Is_rogue_level(&u.uz) ? ov_rogue_syms[ovidx] - : ov_primary_syms[ovidx]) - idx = ovidx; - } - } - - ch = showsyms[idx]; -#ifdef TEXTCOLOR - /* Turn off color if no color defined, or rogue level w/o PC graphics. */ - if (!has_color(color) || (Is_rogue_level(&u.uz) && !has_rogue_color)) -#endif - color = NO_COLOR; - *ochar = (int) ch; - *ospecial = special; - *ocolor = color; - return idx; -} - -char * -encglyph(glyph) -int glyph; -{ - static char encbuf[20]; /* 10+1 would suffice */ - - Sprintf(encbuf, "\\G%04X%04X", context.rndencode, glyph); - return encbuf; -} - -char * -decode_mixed(buf, str) -char *buf; -const char *str; -{ - static const char hex[] = "00112233445566778899aAbBcCdDeEfF"; - char *put = buf; - - if (!str) - return strcpy(buf, ""); - - while (*str) { - if (*str == '\\') { - int rndchk, dcount, so, gv, ch = 0, oc = 0; - unsigned os = 0; - const char *dp, *save_str; - - save_str = str++; - switch (*str) { - case 'G': /* glyph value \GXXXXNNNN*/ - rndchk = dcount = 0; - for (++str; *str && ++dcount <= 4; ++str) - if ((dp = index(hex, *str)) != 0) - rndchk = (rndchk * 16) + ((int) (dp - hex) / 2); - else - break; - if (rndchk == context.rndencode) { - gv = dcount = 0; - for (; *str && ++dcount <= 4; ++str) - if ((dp = index(hex, *str)) != 0) - gv = (gv * 16) + ((int) (dp - hex) / 2); - else - break; - so = mapglyph(gv, &ch, &oc, &os, 0, 0, 0); - *put++ = showsyms[so]; - /* 'str' is ready for the next loop iteration and '*str' - should not be copied at the end of this iteration */ - continue; - } else { - /* possible forgery - leave it the way it is */ - str = save_str; - } - break; -#if 0 - case 'S': /* symbol offset */ - so = rndchk = dcount = 0; - for (++str; *str && ++dcount <= 4; ++str) - if ((dp = index(hex, *str)) != 0) - rndchk = (rndchk * 16) + ((int) (dp - hex) / 2); - else - break; - if (rndchk == context.rndencode) { - dcount = 0; - for (; *str && ++dcount <= 2; ++str) - if ((dp = index(hex, *str)) != 0) - so = (so * 16) + ((int) (dp - hex) / 2); - else - break; - } - *put++ = showsyms[so]; - break; -#endif - case '\\': - break; - case '\0': - /* String ended with '\\'. This can happen when someone - names an object with a name ending with '\\', drops the - named object on the floor nearby and does a look at all - nearby objects. */ - /* brh - should we perhaps not allow things to have names - that contain '\\' */ - str = save_str; - break; - } - } - *put++ = *str++; - } - *put = '\0'; - return buf; -} - -/* - * This differs from putstr() because the str parameter can - * contain a sequence of characters representing: - * \GXXXXNNNN a glyph value, encoded by encglyph(). - * - * For window ports that haven't yet written their own - * XXX_putmixed() routine, this general one can be used. - * It replaces the encoded glyph sequence with a single - * showsyms[] char, then just passes that string onto - * putstr(). - */ - -void -genl_putmixed(window, attr, str) -winid window; -int attr; -const char *str; -{ - char buf[BUFSZ]; - - /* now send it to the normal putstr */ - putstr(window, attr, decode_mixed(buf, str)); -} - -/*mapglyph.c*/ diff --git a/src/mcastu.c b/src/mcastu.c index 1b2fb632b..655a3826e 100644 --- a/src/mcastu.c +++ b/src/mcastu.c @@ -1,58 +1,66 @@ -/* NetHack 3.6 mcastu.c $NHDT-Date: 1567418129 2019/09/02 09:55:29 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.55 $ */ +/* NetHack 5.0 mcastu.c $NHDT-Date: 1770949988 2026/02/12 18:33:08 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.111 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2011. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -/* monster mage spells */ -enum mcast_mage_spells { - MGC_PSI_BOLT = 0, - MGC_CURE_SELF, - MGC_HASTE_SELF, - MGC_STUN_YOU, - MGC_DISAPPEAR, - MGC_WEAKEN_YOU, - MGC_DESTRY_ARMR, - MGC_CURSE_ITEMS, - MGC_AGGRAVATION, - MGC_SUMMON_MONS, - MGC_CLONE_WIZ, - MGC_DEATH_TOUCH +#define MCASTU_ENUM +enum mcast_spells { + #include "mcastu.h" }; +#undef MCASTU_ENUM -/* monster cleric spells */ -enum mcast_cleric_spells { - CLC_OPEN_WOUNDS = 0, - CLC_CURE_SELF, - CLC_CONFUSE_YOU, - CLC_PARALYZE, - CLC_BLIND_YOU, - CLC_INSECTS, - CLC_CURSE_ITEMS, - CLC_LIGHTNING, - CLC_FIRE_PILLAR, - CLC_GEYSER +struct _mcast_data { + int level; + int flags; }; -STATIC_DCL void FDECL(cursetxt, (struct monst *, BOOLEAN_P)); -STATIC_DCL int FDECL(choose_magic_spell, (int)); -STATIC_DCL int FDECL(choose_clerical_spell, (int)); -STATIC_DCL int FDECL(m_cure_self, (struct monst *, int)); -STATIC_DCL void FDECL(cast_wizard_spell, (struct monst *, int, int)); -STATIC_DCL void FDECL(cast_cleric_spell, (struct monst *, int, int)); -STATIC_DCL boolean FDECL(is_undirected_spell, (unsigned int, int)); -STATIC_DCL boolean -FDECL(spell_would_be_useless, (struct monst *, unsigned int, int)); +#define MCASTU_INIT +static struct _mcast_data mcast_data[] = { + #include "mcastu.h" +}; +#undef MCASTU_INIT + +/* spell lists for specific monster casters */ +/* the spells in the list should be in ascending level order */ +static int mon_cleric_spells[] = { + MCAST_OPEN_WOUNDS, MCAST_CURE_SELF, MCAST_CONFUSE_YOU, MCAST_PARALYZE, + MCAST_BLIND_YOU, MCAST_INSECTS, MCAST_CURSE_ITEMS, MCAST_LIGHTNING, + MCAST_FIRE_PILLAR, MCAST_GEYSER +}; +static int mon_wizard_spells[] = { + MCAST_PSI_BOLT, MCAST_CURE_SELF, MCAST_HASTE_SELF, MCAST_STUN_YOU, + MCAST_DISAPPEAR, MCAST_WEAKEN_YOU, MCAST_DESTRY_ARMR, MCAST_CURSE_ITEMS, + MCAST_AGGRAVATION, MCAST_SUMMON_MONS, MCAST_CLONE_WIZ, MCAST_DEATH_TOUCH +}; -extern const char *const flash_types[]; /* from zap.c */ +staticfn void cursetxt(struct monst *, boolean); +staticfn int choose_monster_spell(struct monst *, int); +staticfn int m_cure_self(struct monst *, int); +staticfn void mcast_death_touch(struct monst *); +staticfn void mcast_clone_wiz(struct monst *); +staticfn void mcast_summon_mons(struct monst *); +staticfn void mcast_destroy_armor(void); +staticfn void mcast_weaken_you(struct monst *, int); +staticfn void mcast_disappear(struct monst *); +staticfn void mcast_stun_you(int); +staticfn int mcast_geyser(int); +staticfn int mcast_fire_pillar(struct monst *, int); +staticfn int mcast_lightning(struct monst *, int); +staticfn int mcast_psi_bolt(int); +staticfn int mcast_open_wounds(int); +staticfn void mcast_insects(struct monst *); +staticfn void mcast_blind_you(void); +staticfn int mcast_paralyze(struct monst *); +staticfn void mcast_confuse_you(struct monst *); +staticfn void mcast_spell(struct monst *, int, int); +staticfn boolean is_undirected_spell(int); +staticfn boolean spell_would_be_useless(struct monst *, int); /* feedback when frustrated monster couldn't cast a spell */ -STATIC_OVL -void -cursetxt(mtmp, undirected) -struct monst *mtmp; -boolean undirected; +staticfn void +cursetxt(struct monst *mtmp, boolean undirected) { if (canseemon(mtmp) && couldsee(mtmp->mx, mtmp->my)) { const char *point_msg; /* spellcasting monsters are impolite */ @@ -61,7 +69,7 @@ boolean undirected; point_msg = "all around, then curses"; else if ((Invis && !perceives(mtmp->data) && (mtmp->mux != u.ux || mtmp->muy != u.uy)) - || is_obj_mappear(&youmonst, STRANGE_OBJECT) + || is_obj_mappear(&gy.youmonst, STRANGE_OBJECT) || u.uundetected) point_msg = "and curses in your general direction"; else if (Displaced && (mtmp->mux != u.ux || mtmp->muy != u.uy)) @@ -69,111 +77,49 @@ boolean undirected; else point_msg = "at you, then curses"; - pline("%s points %s.", Monnam(mtmp), point_msg); - } else if ((!(moves % 4) || !rn2(4))) { + pline_mon(mtmp, "%s points %s.", Monnam(mtmp), point_msg); + } else if ((!(svm.moves % 4) || !rn2(4))) { if (!Deaf) Norep("You hear a mumbled curse."); /* Deaf-aware */ } } -/* convert a level based random selection into a specific mage spell; - inappropriate choices will be screened out by spell_would_be_useless() */ -STATIC_OVL int -choose_magic_spell(spellval) -int spellval; +/* choose a spell for monster to cast */ +staticfn int +choose_monster_spell(struct monst *mtmp, int adtyp) { - /* for 3.4.3 and earlier, val greater than 22 selected the default spell - */ - while (spellval > 24 && rn2(25)) - spellval = rn2(spellval); - - switch (spellval) { - case 24: - case 23: - if (Antimagic || Hallucination) - return MGC_PSI_BOLT; - /*FALLTHRU*/ - case 22: - case 21: - case 20: - return MGC_DEATH_TOUCH; - case 19: - case 18: - return MGC_CLONE_WIZ; - case 17: - case 16: - case 15: - return MGC_SUMMON_MONS; - case 14: - case 13: - return MGC_AGGRAVATION; - case 12: - case 11: - case 10: - return MGC_CURSE_ITEMS; - case 9: - case 8: - return MGC_DESTRY_ARMR; - case 7: - case 6: - return MGC_WEAKEN_YOU; - case 5: - case 4: - return MGC_DISAPPEAR; - case 3: - return MGC_STUN_YOU; - case 2: - return MGC_HASTE_SELF; - case 1: - return MGC_CURE_SELF; - case 0: - default: - return MGC_PSI_BOLT; + int *list = NULL; + int i, spellval, len = 0; + int maxlev; + + /* which spell list to use? */ + if (adtyp == AD_SPEL) { + list = mon_wizard_spells; + len = SIZE(mon_wizard_spells); + } else if (adtyp == AD_CLRC) { + list = mon_cleric_spells; + len = SIZE(mon_cleric_spells); } -} -/* convert a level based random selection into a specific cleric spell */ -STATIC_OVL int -choose_clerical_spell(spellnum) -int spellnum; -{ - /* for 3.4.3 and earlier, num greater than 13 selected the default spell - */ - while (spellnum > 15 && rn2(16)) - spellnum = rn2(spellnum); + if (!list || len < 1) + return MCAST_PSI_BOLT; - switch (spellnum) { - case 15: - case 14: - if (rn2(3)) - return CLC_OPEN_WOUNDS; - /*FALLTHRU*/ - case 13: - return CLC_GEYSER; - case 12: - return CLC_FIRE_PILLAR; - case 11: - return CLC_LIGHTNING; - case 10: - case 9: - return CLC_CURSE_ITEMS; - case 8: - return CLC_INSECTS; - case 7: - case 6: - return CLC_BLIND_YOU; - case 5: - case 4: - return CLC_PARALYZE; - case 3: - case 2: - return CLC_CONFUSE_YOU; - case 1: - return CLC_CURE_SELF; - case 0: - default: - return CLC_OPEN_WOUNDS; - } + /* max spell level in this monster spell list */ + maxlev = mcast_data[list[len - 1]].level; + + /* which level spell to cast? */ + spellval = rn2(mtmp->m_lev); + if (spellval > maxlev && rn2(maxlev)) + spellval = rn2(maxlev); + + /* find the highest spell in the list we could cast */ + for (i = len-1; i >= 0; i--) + if (mcast_data[list[i]].level <= spellval + && !spell_would_be_useless(mtmp, list[i])) + return list[i]; + + /* or return the first spell in the list */ + return list[0]; } /* return values: @@ -181,11 +127,11 @@ int spellnum; * 0: unsuccessful spell */ int -castmu(mtmp, mattk, thinks_it_foundyou, foundyou) -register struct monst *mtmp; -register struct attack *mattk; -boolean thinks_it_foundyou; -boolean foundyou; +castmu( + struct monst *mtmp, /* caster */ + struct attack *mattk, /* caster's current attack */ + boolean thinks_it_foundyou, /* might be mistaken if displaced */ + boolean foundyou) /* knows hero's precise location */ { int dmg, ml = mtmp->m_lev; int ret; @@ -207,70 +153,76 @@ boolean foundyou; int cnt = 40; do { - spellnum = rn2(ml); - if (mattk->adtyp == AD_SPEL) - spellnum = choose_magic_spell(spellnum); - else - spellnum = choose_clerical_spell(spellnum); + spellnum = choose_monster_spell(mtmp, mattk->adtyp); /* not trying to attack? don't allow directed spells */ if (!thinks_it_foundyou) { - if (!is_undirected_spell(mattk->adtyp, spellnum) - || spell_would_be_useless(mtmp, mattk->adtyp, spellnum)) { + if (!is_undirected_spell(spellnum) + || spell_would_be_useless(mtmp, spellnum)) { if (foundyou) impossible( "spellcasting monster found you and doesn't know it?"); - return 0; + return M_ATTK_MISS; } break; } } while (--cnt > 0 - && spell_would_be_useless(mtmp, mattk->adtyp, spellnum)); + && spell_would_be_useless(mtmp, spellnum)); if (cnt == 0) - return 0; + return M_ATTK_MISS; } /* monster unable to cast spells? */ - if (mtmp->mcan || mtmp->mspec_used || !ml) { - cursetxt(mtmp, is_undirected_spell(mattk->adtyp, spellnum)); - return (0); + if (mtmp->mcan || mtmp->mspec_used || !ml + || m_seenres(mtmp, cvt_adtyp_to_mseenres(mattk->adtyp))) { + cursetxt(mtmp, is_undirected_spell(spellnum)); + return M_ATTK_MISS; } + debugpline3("castmu:%s,lvl:%i,spell:%i", noit_Monnam(mtmp), ml, spellnum); + if (mattk->adtyp == AD_SPEL || mattk->adtyp == AD_CLRC) { - mtmp->mspec_used = 10 - mtmp->m_lev; - if (mtmp->mspec_used < 2) - mtmp->mspec_used = 2; + /* monst->m_lev is unsigned (uchar), monst->mspec_used is int */ + mtmp->mspec_used = (int) ((mtmp->m_lev < 8) ? (10 - mtmp->m_lev) : 2); } - /* monster can cast spells, but is casting a directed spell at the - wrong place? If so, give a message, and return. Do this *after* - penalizing mspec_used. */ + /* Monster can cast spells, but is casting a directed spell at the + * wrong place? If so, give a message, and return. + * Do this *after* penalizing mspec_used. + * + * FIXME? + * Shouldn't wall of lava have a case similar to wall of water? + * And should cold damage hit water or lava instead of missing + * even when the caster has targeted the wrong spot? Likewise + * for fire mis-aimed at ice. + */ if (!foundyou && thinks_it_foundyou - && !is_undirected_spell(mattk->adtyp, spellnum)) { - pline("%s casts a spell at %s!", - canseemon(mtmp) ? Monnam(mtmp) : "Something", - levl[mtmp->mux][mtmp->muy].typ == WATER ? "empty water" - : "thin air"); - return (0); + && !is_undirected_spell(spellnum)) { + pline_mon(mtmp, "%s casts a spell at %s!", + canseemon(mtmp) ? Monnam(mtmp) : "Something", + is_waterwall(mtmp->mux, mtmp->muy) ? "empty water" + : "thin air"); + return M_ATTK_MISS; } nomul(0); if (rn2(ml * 10) < (mtmp->mconf ? 100 : 20)) { /* fumbled attack */ - if (canseemon(mtmp) && !Deaf) + Soundeffect(se_air_crackles, 60); + if (canseemon(mtmp) && !Deaf) { + set_msg_xy(mtmp->mx, mtmp->my); pline_The("air crackles around %s.", mon_nam(mtmp)); - return (0); - } - if (canspotmon(mtmp) || !is_undirected_spell(mattk->adtyp, spellnum)) { - pline("%s casts a spell%s!", - canspotmon(mtmp) ? Monnam(mtmp) : "Something", - is_undirected_spell(mattk->adtyp, spellnum) - ? "" - : (Invis && !perceives(mtmp->data) - && (mtmp->mux != u.ux || mtmp->muy != u.uy)) - ? " at a spot near you" - : (Displaced - && (mtmp->mux != u.ux || mtmp->muy != u.uy)) - ? " at your displaced image" - : " at you"); + } + return M_ATTK_MISS; + } + if (canspotmon(mtmp) || !is_undirected_spell(spellnum)) { + pline_mon(mtmp, "%s casts a spell%s!", + canspotmon(mtmp) ? Monnam(mtmp) : "Something", + is_undirected_spell(spellnum) ? "" + : (Invis && !perceives(mtmp->data) + && !u_at(mtmp->mux, mtmp->muy)) + ? " at a spot near you" + : (Displaced && !u_at(mtmp->mux, mtmp->muy)) + ? " at your displaced image" + : " at you"); } /* @@ -283,7 +235,7 @@ boolean foundyou; impossible( "%s casting non-hand-to-hand version of hand-to-hand spell %d?", Monnam(mtmp), mattk->adtyp); - return (0); + return M_ATTK_MISS; } } else if (mattk->damd) dmg = d((int) ((ml / 2) + mattk->damn), (int) mattk->damd); @@ -292,68 +244,551 @@ boolean foundyou; if (Half_spell_damage) dmg = (dmg + 1) / 2; - ret = 1; - + ret = M_ATTK_HIT; + /* + * FIXME: none of these hit the steed when hero is riding, nor do + * they inflict damage on carried items. + */ switch (mattk->adtyp) { case AD_FIRE: pline("You're enveloped in flames."); if (Fire_resistance) { shieldeff(u.ux, u.uy); pline("But you resist the effects."); + monstseesu(M_SEEN_FIRE); dmg = 0; + } else { + monstunseesu(M_SEEN_FIRE); } burn_away_slime(); + /* burn up flammable items on the floor, melt ice terrain */ + mon_spell_hits_spot(mtmp, AD_FIRE, u.ux, u.uy); break; case AD_COLD: pline("You're covered in frost."); if (Cold_resistance) { shieldeff(u.ux, u.uy); pline("But you resist the effects."); + monstseesu(M_SEEN_COLD); dmg = 0; + } else { + monstunseesu(M_SEEN_COLD); } + /* freeze water or lava terrain */ + /* FIXME: mon_spell_hits_spot() uses zap_over_floor(); unlike with + * fire, it does not target susceptible floor items with cold */ + mon_spell_hits_spot(mtmp, AD_COLD, u.ux, u.uy); break; case AD_MAGM: You("are hit by a shower of missiles!"); if (Antimagic) { shieldeff(u.ux, u.uy); pline_The("missiles bounce off!"); + monstseesu(M_SEEN_MAGR); dmg = 0; - } else + } else { dmg = d((int) mtmp->m_lev / 2 + 1, 6); + monstunseesu(M_SEEN_MAGR); + } + /* shower of magic missiles scuffs an engraving */ + mon_spell_hits_spot(mtmp, AD_MAGM, u.ux, u.uy); break; case AD_SPEL: /* wizard spell */ case AD_CLRC: /* clerical spell */ - { - if (mattk->adtyp == AD_SPEL) - cast_wizard_spell(mtmp, dmg, spellnum); - else - cast_cleric_spell(mtmp, dmg, spellnum); + mcast_spell(mtmp, dmg, spellnum); dmg = 0; /* done by the spell casting functions */ break; - } - } + } /* switch */ if (dmg) mdamageu(mtmp, dmg); - return (ret); + return ret; } -STATIC_OVL int -m_cure_self(mtmp, dmg) -struct monst *mtmp; -int dmg; +staticfn int +m_cure_self(struct monst *mtmp, int dmg) { if (mtmp->mhp < mtmp->mhpmax) { if (canseemon(mtmp)) - pline("%s looks better.", Monnam(mtmp)); + pline_mon(mtmp, "%s looks better.", Monnam(mtmp)); /* note: player healing does 6d4; this used to do 1d8 */ - if ((mtmp->mhp += d(3, 6)) > mtmp->mhpmax) - mtmp->mhp = mtmp->mhpmax; + healmon(mtmp, d(3, 6), 0); dmg = 0; } return dmg; } -/* monster wizard and cleric spellcasting functions */ +/* unlike the finger of death spell which behaves like a wand of death, + this monster spell only attacks the hero */ +void +touch_of_death(struct monst *mtmp) +{ + char kbuf[BUFSZ]; + int dmg = 50 + d(8, 6); + int drain = dmg / 2; + + /* if we get here, we know that hero isn't magic resistant and isn't + poly'd into an undead or demon */ + You_feel("drained..."); + (void) death_inflicted_by(kbuf, "the touch of death", mtmp); + + if (Upolyd) { + u.mh = 0; + rehumanize(); /* fatal iff Unchanging */ + } else if (drain >= u.uhpmax) { + svk.killer.format = KILLED_BY; + Strcpy(svk.killer.name, kbuf); + done(DIED); + } else { + /* HP manipulation similar to poisoned(attrib.c) */ + int olduhp = u.uhp, + uhpmin = minuhpmax(3), + newuhpmax = u.uhpmax - drain; + + setuhpmax(max(newuhpmax, uhpmin), FALSE); + dmg = adjuhploss(dmg, olduhp); /* reduce pending damage if uhp has + * already been reduced due to drop + * in uhpmax */ + losehp(dmg, kbuf, KILLED_BY); + } + svk.killer.name[0] = '\0'; /* not killed if we get here... */ +} + +/* give a reason for death by some monster spells */ +char * +death_inflicted_by( + char *outbuf, /* assumed big enough; pm_names are short */ + const char *deathreason, /* cause of death */ + struct monst *mtmp) /* monster who caused it */ +{ + Strcpy(outbuf, deathreason); + if (mtmp) { + struct permonst *mptr = mtmp->data, + *champtr = (ismnum(mtmp->cham)) ? &mons[mtmp->cham] : mptr; + const char *realnm = pmname(champtr, Mgender(mtmp)), + *fakenm = pmname(mptr, Mgender(mtmp)); + + /* greatly simplified extract from done_in_by(), primarily for + reason for death due to 'touch of death' spell; if mtmp is + shape changed, it won't be a vampshifter or mimic since they + can't cast spells */ + if (!type_is_pname(champtr) && !the_unique_pm(mptr)) + realnm = an(realnm); + Sprintf(eos(outbuf), " inflicted by %s%s", + the_unique_pm(mptr) ? "the " : "", realnm); + if (champtr != mptr) + Sprintf(eos(outbuf), " imitating %s", an(fakenm)); + } + return outbuf; +} + +/* + * Monster wizard and cleric spellcasting functions. + */ + +staticfn void +mcast_death_touch(struct monst *mtmp) +{ + pline("Oh no, %s's using the touch of death!", mhe(mtmp)); + if (nonliving(gy.youmonst.data) || is_demon(gy.youmonst.data)) { + You("seem no deader than before."); + } else if (!Antimagic && rn2(mtmp->m_lev) > 12) { + if (Hallucination) { + You("have an out of body experience."); + } else { + touch_of_death(mtmp); + } + monstunseesu(M_SEEN_MAGR); + } else { + if (Antimagic) { + shieldeff(u.ux, u.uy); + monstseesu(M_SEEN_MAGR); + } + pline("Lucky for you, it didn't work!"); + } +} + +staticfn void +mcast_clone_wiz(struct monst *mtmp) +{ + if (mtmp->iswiz && svc.context.no_of_wizards == 1) { + pline("Double Trouble..."); + clonewiz(); + } else + impossible("bad wizard cloning?"); +} + +staticfn void +mcast_summon_mons(struct monst *mtmp) +{ + int count = nasty(mtmp); + + if (!count) { + ; /* nothing was created? */ + } else if (mtmp->iswiz) { + SetVoice(mtmp, 0, 80, 0); + verbalize("Destroy the thief, my pet%s!", plur(count)); + } else { + boolean one = (count == 1); + const char *mappear = one ? "A monster appears" + : "Monsters appear"; + + /* messages not quite right if plural monsters created but + only a single monster is seen */ + if (Invis && !perceives(mtmp->data) + && (mtmp->mux != u.ux || mtmp->muy != u.uy)) + pline("%s %s a spot near you!", mappear, + one ? "at" : "around"); + else if (Displaced && (mtmp->mux != u.ux || mtmp->muy != u.uy)) + pline("%s %s your displaced image!", mappear, + one ? "by" : "around"); + else + pline("%s from nowhere!", mappear); + } +} + +staticfn void +mcast_destroy_armor(void) +{ + if (Antimagic) { + shieldeff(u.ux, u.uy); + monstseesu(M_SEEN_MAGR); + pline("A field of force surrounds you!"); + } else if (!destroy_arm()) { + Your("skin itches."); + } else { + /* monsters only realize you aren't magic-protected if armor is + actually destroyed */ + monstunseesu(M_SEEN_MAGR); + } +} + +staticfn void +mcast_weaken_you(struct monst *mtmp, int dmg) +{ + if (Antimagic) { + shieldeff(u.ux, u.uy); + monstseesu(M_SEEN_MAGR); + You_feel("momentarily weakened."); + } else { + char kbuf[BUFSZ]; + + You("suddenly feel weaker!"); + dmg = mtmp->m_lev - 6; + if (dmg < 1) /* paranoia since only chosen when m_lev is high */ + dmg = 1; + if (Half_spell_damage) + dmg = (dmg + 1) / 2; + losestr(rnd(dmg), + death_inflicted_by(kbuf, "strength loss", mtmp), + KILLED_BY); + svk.killer.name[0] = '\0'; /* not killed if we get here... */ + monstunseesu(M_SEEN_MAGR); + } +} + +staticfn void +mcast_disappear(struct monst *mtmp) +{ + if (!mtmp->minvis && !mtmp->invis_blkd) { + if (canseemon(mtmp)) + pline_mon(mtmp, "%s suddenly %s!", Monnam(mtmp), + !See_invisible ? "disappears" : "becomes transparent"); + mon_set_minvis(mtmp, FALSE); + if (cansee(mtmp->mx, mtmp->my) && !canspotmon(mtmp)) + map_invisible(mtmp->mx, mtmp->my); + } else + impossible("no reason for monster to cast disappear spell?"); +} + +staticfn void +mcast_stun_you(int dmg) +{ + if (Antimagic || Free_action) { + shieldeff(u.ux, u.uy); + monstseesu(M_SEEN_MAGR); + if (!Stunned) + You_feel("momentarily disoriented."); + make_stunned(1L, FALSE); + } else { + You(Stunned ? "struggle to keep your balance." : "reel..."); + dmg = d(ACURR(A_DEX) < 12 ? 6 : 4, 4); + if (Half_spell_damage) + dmg = (dmg + 1) / 2; + make_stunned((HStun & TIMEOUT) + (long) dmg, FALSE); + monstunseesu(M_SEEN_MAGR); + } +} + +staticfn int +mcast_geyser(int dmg) +{ + /* this is physical damage (force not heat), + * not magical damage or fire damage + */ + pline("A sudden geyser slams into you from nowhere!"); + dmg = d(8, 6); + if (Half_physical_damage) + dmg = (dmg + 1) / 2; +#if 0 /* since inventory items aren't affected, don't include this */ + /* make floor items wet */ + water_damage_chain(level.objects[u.ux][u.uy], TRUE); +#endif + return dmg; +} + +staticfn int +mcast_fire_pillar(struct monst *mtmp, int dmg) +{ + int orig_dmg; + + pline("A pillar of fire strikes all around you!"); + orig_dmg = dmg = d(8, 6); + if (Fire_resistance) { + shieldeff(u.ux, u.uy); + monstseesu(M_SEEN_FIRE); + dmg = 0; + } else { + monstunseesu(M_SEEN_FIRE); + } + if (Half_spell_damage) + dmg = (dmg + 1) / 2; + burn_away_slime(); + (void) burnarmor(&gy.youmonst); + /* item destruction dmg */ + (void) destroy_items(&gy.youmonst, AD_FIRE, orig_dmg); + ignite_items(gi.invent); + /* burn up flammable items on the floor, melt ice terrain */ + mon_spell_hits_spot(mtmp, AD_FIRE, u.ux, u.uy); + return dmg; +} + +staticfn int +mcast_lightning(struct monst *mtmp, int dmg) +{ + int orig_dmg; + boolean reflects; + + Soundeffect(se_bolt_of_lightning, 80); + pline("A bolt of lightning strikes down at you from above!"); + reflects = ureflects("It bounces off your %s%s.", ""); + orig_dmg = dmg = d(8, 6); + if (reflects || Shock_resistance) { + shieldeff(u.ux, u.uy); + dmg = 0; + if (reflects) { + monstseesu(M_SEEN_REFL); + return dmg; + } + monstunseesu(M_SEEN_REFL); + monstseesu(M_SEEN_ELEC); + } else { + monstunseesu(M_SEEN_ELEC | M_SEEN_REFL); + } + if (Half_spell_damage) + dmg = (dmg + 1) / 2; + (void) destroy_items(&gy.youmonst, AD_ELEC, orig_dmg); + /* lightning might destroy iron bars if hero is on such a spot; + reflection protects terrain here [execution won't get here due + to 'if (reflects) break' above] but hero resistance doesn't; + do this before maybe blinding the hero via flashburn() */ + mon_spell_hits_spot(mtmp, AD_ELEC, u.ux, u.uy); + /* blind hero; no effect if already blind */ + (void) flashburn((long) rnd(100), TRUE); + return dmg; +} + +staticfn int +mcast_psi_bolt(int dmg) +{ + /* prior to 3.4.0 Antimagic was setting the damage to 1--this + made the spell virtually harmless to players with magic res. */ + if (Antimagic) { + shieldeff(u.ux, u.uy); + monstseesu(M_SEEN_MAGR); + dmg = (dmg + 1) / 2; + } else { + monstunseesu(M_SEEN_MAGR); + } + if (dmg <= 5) + You("get a slight %sache.", body_part(HEAD)); + else if (dmg <= 10) + Your("brain is on fire!"); + else if (dmg <= 20) + Your("%s suddenly aches painfully!", body_part(HEAD)); + else + Your("%s suddenly aches very painfully!", body_part(HEAD)); + return dmg; +} + +staticfn int +mcast_open_wounds(int dmg) +{ + if (Antimagic) { + shieldeff(u.ux, u.uy); + monstseesu(M_SEEN_MAGR); + dmg = (dmg + 1) / 2; + } else { + monstunseesu(M_SEEN_MAGR); + } + if (dmg <= 5) + Your("skin itches badly for a moment."); + else if (dmg <= 10) + pline("Wounds appear on your body!"); + else if (dmg <= 20) + pline("Severe wounds appear on your body!"); + else + Your("body is covered with painful wounds!"); + return dmg; +} + +staticfn void +mcast_insects(struct monst *mtmp) +{ + /* Try for insects, and if there are none + left, go for (sticks to) snakes. -3. */ + struct permonst *pm = mkclass(S_ANT, 0); + struct monst *mtmp2 = (struct monst *) 0; + char whatbuf[QBUFSZ], let = (pm ? S_ANT : S_SNAKE); + boolean success = FALSE, seecaster; + int i, quan, oldseen, newseen; + coord bypos; + const char *fmt, *what; + + oldseen = monster_census(TRUE); + quan = (mtmp->m_lev < 2) ? 1 : rnd((int) mtmp->m_lev / 2); + if (quan < 3) + quan = 3; + for (i = 0; i <= quan; i++) { + if (!enexto(&bypos, mtmp->mux, mtmp->muy, mtmp->data)) + return; + if ((pm = mkclass(let, 0)) != 0 + && (mtmp2 = makemon(pm, bypos.x, bypos.y, MM_ANGRY | MM_NOMSG)) + != 0) { + success = TRUE; + mtmp2->msleeping = mtmp2->mpeaceful = mtmp2->mtame = 0; + set_malign(mtmp2); + } + } + newseen = monster_census(TRUE); + + /* not canspotmon() which includes unseen things sensed via warning */ + seecaster = canseemon(mtmp) || tp_sensemon(mtmp) || Detect_monsters; + what = (let == S_SNAKE) ? "snakes" : "insects"; + if (Hallucination) + what = makeplural(bogusmon(whatbuf, (char *) 0)); + + fmt = 0; + if (!seecaster) { + if (newseen <= oldseen || Unaware) { + /* unseen caster fails or summons unseen critters, + or unconscious hero ("You dream that you hear...") */ + You_hear("someone summoning %s.", what); + } else { + char *arg; + + if (what != whatbuf) + what = strcpy(whatbuf, what); + /* unseen caster summoned seen critter(s) */ + arg = (newseen == oldseen + 1) ? an(makesingular(what)) + : whatbuf; + if (!Deaf) { + Soundeffect(se_someone_summoning, 100); + You_hear("someone summoning something, and %s %s.", arg, + vtense(arg, "appear")); + } else { + pline("%s %s.", upstart(arg), vtense(arg, "appear")); + } + } + + /* seen caster, possibly producing unseen--or just one--critters; + hero is told what the caster is doing and doesn't necessarily + observe complete accuracy of that caster's results (in other + words, no need to fuss with visibility or singularization; + player is told what's happening even if hero is unconscious) */ + } else if (!success) { + fmt = "%s casts at a clump of sticks, but nothing happens.%s"; + what = ""; + } else if (let == S_SNAKE) { + fmt = "%s transforms a clump of sticks into %s!"; + } else if (Invis && !perceives(mtmp->data) + && (mtmp->mux != u.ux || mtmp->muy != u.uy)) { + fmt = "%s summons %s around a spot near you!"; + } else if (Displaced && (mtmp->mux != u.ux || mtmp->muy != u.uy)) { + fmt = "%s summons %s around your displaced image!"; + } else { + fmt = "%s summons %s!"; + } + if (fmt) { + DISABLE_WARNING_FORMAT_NONLITERAL; + pline_mon(mtmp, fmt, Monnam(mtmp), what); + RESTORE_WARNING_FORMAT_NONLITERAL; + } +} + +staticfn void +mcast_blind_you(void) +{ + /* note: resists_blnd() doesn't apply here */ + if (!Blinded) { + int num_eyes = eyecount(gy.youmonst.data); + + pline("Scales cover your %s!", (num_eyes == 1) + ? body_part(EYE) + : makeplural(body_part(EYE))); + make_blinded(Half_spell_damage ? 100L : 200L, FALSE); + if (!Blind) + Your1(vision_clears); + } else + impossible("no reason for monster to cast blindness spell?"); +} + +staticfn int +mcast_paralyze(struct monst *mtmp) +{ + int dmg = 0; + + if (Antimagic || Free_action) { + shieldeff(u.ux, u.uy); + monstseesu(M_SEEN_MAGR); + if (gm.multi >= 0) + You("stiffen briefly."); + dmg = 1; /* to produce nomul(-1), not actual damage */ + } else { + if (gm.multi >= 0) + You("are frozen in place!"); + dmg = 4 + (int) mtmp->m_lev; + if (Half_spell_damage) + dmg = (dmg + 1) / 2; + monstunseesu(M_SEEN_MAGR); + } + nomul(-dmg); + gm.multi_reason = "paralyzed by a monster"; + gn.nomovemsg = 0; + return dmg; +} + +staticfn void +mcast_confuse_you(struct monst *mtmp) +{ + if (Antimagic) { + shieldeff(u.ux, u.uy); + monstseesu(M_SEEN_MAGR); + You_feel("momentarily dizzy."); + } else { + boolean oldprop = !!Confusion; + int dmg = (int) mtmp->m_lev; + + if (Half_spell_damage) + dmg = (dmg + 1) / 2; + make_confused(HConfusion + dmg, TRUE); + if (Hallucination) + You_feel("%s!", oldprop ? "trippier" : "trippy"); + else + You_feel("%sconfused!", oldprop ? "more " : ""); + monstunseesu(M_SEEN_MAGR); + } +} + /* If dmg is zero, then the monster is not casting at you. If the monster is intentionally not casting at you, we have previously @@ -362,367 +797,97 @@ int dmg; If you modify either of these, be sure to change is_undirected_spell() and spell_would_be_useless(). */ -STATIC_OVL -void -cast_wizard_spell(mtmp, dmg, spellnum) -struct monst *mtmp; -int dmg; -int spellnum; +staticfn void +mcast_spell(struct monst *mtmp, int dmg, int spellnum) { - if (dmg == 0 && !is_undirected_spell(AD_SPEL, spellnum)) { + if (dmg < 0) { + impossible("monster cast spell (%d) with negative dmg (%d)?", + spellnum, dmg); + return; + } + if (dmg == 0 && !is_undirected_spell(spellnum)) { impossible("cast directed wizard spell (%d) with dmg=0?", spellnum); return; } switch (spellnum) { - case MGC_DEATH_TOUCH: - pline("Oh no, %s's using the touch of death!", mhe(mtmp)); - if (nonliving(youmonst.data) || is_demon(youmonst.data)) { - You("seem no deader than before."); - } else if (!Antimagic && rn2(mtmp->m_lev) > 12) { - if (Hallucination) { - You("have an out of body experience."); - } else { - killer.format = KILLED_BY_AN; - Strcpy(killer.name, "touch of death"); - done(DIED); - } - } else { - if (Antimagic) - shieldeff(u.ux, u.uy); - pline("Lucky for you, it didn't work!"); - } + case MCAST_DEATH_TOUCH: + mcast_death_touch(mtmp); dmg = 0; break; - case MGC_CLONE_WIZ: - if (mtmp->iswiz && context.no_of_wizards == 1) { - pline("Double Trouble..."); - clonewiz(); - dmg = 0; - } else - impossible("bad wizard cloning?"); + case MCAST_CLONE_WIZ: + mcast_clone_wiz(mtmp); + dmg = 0; break; - case MGC_SUMMON_MONS: { - int count; - - count = nasty(mtmp); /* summon something nasty */ - if (mtmp->iswiz) { - verbalize("Destroy the thief, my pet%s!", plur(count)); - } else { - const char *mappear = (count == 1) ? "A monster appears" - : "Monsters appear"; - - /* messages not quite right if plural monsters created but - only a single monster is seen */ - if (Invis && !perceives(mtmp->data) - && (mtmp->mux != u.ux || mtmp->muy != u.uy)) - pline("%s around a spot near you!", mappear); - else if (Displaced && (mtmp->mux != u.ux || mtmp->muy != u.uy)) - pline("%s around your displaced image!", mappear); - else - pline("%s from nowhere!", mappear); - } + case MCAST_SUMMON_MONS: + mcast_summon_mons(mtmp); dmg = 0; break; - } - case MGC_AGGRAVATION: + case MCAST_AGGRAVATION: You_feel("that monsters are aware of your presence."); aggravate(); dmg = 0; break; - case MGC_CURSE_ITEMS: + case MCAST_CURSE_ITEMS: You_feel("as if you need some help."); rndcurse(); dmg = 0; break; - case MGC_DESTRY_ARMR: - if (Antimagic) { - shieldeff(u.ux, u.uy); - pline("A field of force surrounds you!"); - } else if (!destroy_arm(some_armor(&youmonst))) { - Your("skin itches."); - } + case MCAST_DESTRY_ARMR: + mcast_destroy_armor(); dmg = 0; break; - case MGC_WEAKEN_YOU: /* drain strength */ - if (Antimagic) { - shieldeff(u.ux, u.uy); - You_feel("momentarily weakened."); - } else { - You("suddenly feel weaker!"); - dmg = mtmp->m_lev - 6; - if (Half_spell_damage) - dmg = (dmg + 1) / 2; - losestr(rnd(dmg)); - if (u.uhp < 1) - done_in_by(mtmp, DIED); - } + case MCAST_WEAKEN_YOU: /* drain strength */ + mcast_weaken_you(mtmp, dmg); dmg = 0; break; - case MGC_DISAPPEAR: /* makes self invisible */ - if (!mtmp->minvis && !mtmp->invis_blkd) { - if (canseemon(mtmp)) - pline("%s suddenly %s!", Monnam(mtmp), - !See_invisible ? "disappears" : "becomes transparent"); - mon_set_minvis(mtmp); - if (cansee(mtmp->mx, mtmp->my) && !canspotmon(mtmp)) - map_invisible(mtmp->mx, mtmp->my); - dmg = 0; - } else - impossible("no reason for monster to cast disappear spell?"); + case MCAST_DISAPPEAR: /* makes self invisible */ + mcast_disappear(mtmp); + dmg = 0; break; - case MGC_STUN_YOU: - if (Antimagic || Free_action) { - shieldeff(u.ux, u.uy); - if (!Stunned) - You_feel("momentarily disoriented."); - make_stunned(1L, FALSE); - } else { - You(Stunned ? "struggle to keep your balance." : "reel..."); - dmg = d(ACURR(A_DEX) < 12 ? 6 : 4, 4); - if (Half_spell_damage) - dmg = (dmg + 1) / 2; - make_stunned((HStun & TIMEOUT) + (long) dmg, FALSE); - } + case MCAST_STUN_YOU: + mcast_stun_you(dmg); dmg = 0; break; - case MGC_HASTE_SELF: + case MCAST_HASTE_SELF: mon_adjust_speed(mtmp, 1, (struct obj *) 0); dmg = 0; break; - case MGC_CURE_SELF: + case MCAST_CURE_SELF: dmg = m_cure_self(mtmp, dmg); break; - case MGC_PSI_BOLT: - /* prior to 3.4.0 Antimagic was setting the damage to 1--this - made the spell virtually harmless to players with magic res. */ - if (Antimagic) { - shieldeff(u.ux, u.uy); - dmg = (dmg + 1) / 2; - } - if (dmg <= 5) - You("get a slight %sache.", body_part(HEAD)); - else if (dmg <= 10) - Your("brain is on fire!"); - else if (dmg <= 20) - Your("%s suddenly aches painfully!", body_part(HEAD)); - else - Your("%s suddenly aches very painfully!", body_part(HEAD)); + case MCAST_PSI_BOLT: + dmg = mcast_psi_bolt(dmg); break; - default: - impossible("mcastu: invalid magic spell (%d)", spellnum); - dmg = 0; + case MCAST_GEYSER: + dmg = mcast_geyser(dmg); break; - } - - if (dmg) - mdamageu(mtmp, dmg); -} - -STATIC_OVL -void -cast_cleric_spell(mtmp, dmg, spellnum) -struct monst *mtmp; -int dmg; -int spellnum; -{ - if (dmg == 0 && !is_undirected_spell(AD_CLRC, spellnum)) { - impossible("cast directed cleric spell (%d) with dmg=0?", spellnum); - return; - } - - switch (spellnum) { - case CLC_GEYSER: - /* this is physical damage (force not heat), - * not magical damage or fire damage - */ - pline("A sudden geyser slams into you from nowhere!"); - dmg = d(8, 6); - if (Half_physical_damage) - dmg = (dmg + 1) / 2; + case MCAST_FIRE_PILLAR: + dmg = mcast_fire_pillar(mtmp, dmg); break; - case CLC_FIRE_PILLAR: - pline("A pillar of fire strikes all around you!"); - if (Fire_resistance) { - shieldeff(u.ux, u.uy); - dmg = 0; - } else - dmg = d(8, 6); - if (Half_spell_damage) - dmg = (dmg + 1) / 2; - burn_away_slime(); - (void) burnarmor(&youmonst); - destroy_item(SCROLL_CLASS, AD_FIRE); - destroy_item(POTION_CLASS, AD_FIRE); - destroy_item(SPBOOK_CLASS, AD_FIRE); - (void) burn_floor_objects(u.ux, u.uy, TRUE, FALSE); - break; - case CLC_LIGHTNING: { - boolean reflects; - - pline("A bolt of lightning strikes down at you from above!"); - reflects = ureflects("It bounces off your %s%s.", ""); - if (reflects || Shock_resistance) { - shieldeff(u.ux, u.uy); - dmg = 0; - if (reflects) - break; - } else - dmg = d(8, 6); - if (Half_spell_damage) - dmg = (dmg + 1) / 2; - destroy_item(WAND_CLASS, AD_ELEC); - destroy_item(RING_CLASS, AD_ELEC); - (void) flashburn((long) rnd(100)); + case MCAST_LIGHTNING: + dmg = mcast_lightning(mtmp, dmg); break; - } - case CLC_CURSE_ITEMS: - You_feel("as if you need some help."); - rndcurse(); + case MCAST_INSECTS: + mcast_insects(mtmp); dmg = 0; break; - case CLC_INSECTS: { - /* Try for insects, and if there are none - left, go for (sticks to) snakes. -3. */ - struct permonst *pm = mkclass(S_ANT, 0); - struct monst *mtmp2 = (struct monst *) 0; - char let = (pm ? S_ANT : S_SNAKE); - boolean success = FALSE, seecaster; - int i, quan, oldseen, newseen; - coord bypos; - const char *fmt; - - oldseen = monster_census(TRUE); - quan = (mtmp->m_lev < 2) ? 1 : rnd((int) mtmp->m_lev / 2); - if (quan < 3) - quan = 3; - for (i = 0; i <= quan; i++) { - if (!enexto(&bypos, mtmp->mux, mtmp->muy, mtmp->data)) - break; - if ((pm = mkclass(let, 0)) != 0 - && (mtmp2 = makemon(pm, bypos.x, bypos.y, MM_ANGRY)) != 0) { - success = TRUE; - mtmp2->msleeping = mtmp2->mpeaceful = mtmp2->mtame = 0; - set_malign(mtmp2); - } - } - newseen = monster_census(TRUE); - - /* not canspotmon(), which includes unseen things sensed via warning - */ - seecaster = canseemon(mtmp) || tp_sensemon(mtmp) || Detect_monsters; - - fmt = 0; - if (!seecaster) { - char *arg; /* [not const: upstart(N==1 ? an() : makeplural())] */ - const char *what = (let == S_SNAKE) ? "snake" : "insect"; - - if (newseen <= oldseen || Unaware) { - /* unseen caster fails or summons unseen critters, - or unconscious hero ("You dream that you hear...") */ - You_hear("someone summoning %s.", makeplural(what)); - } else { - /* unseen caster summoned seen critter(s) */ - arg = (newseen == oldseen + 1) ? an(what) : makeplural(what); - if (!Deaf) - You_hear("someone summoning something, and %s %s.", arg, - vtense(arg, "appear")); - else - pline("%s %s.", upstart(arg), vtense(arg, "appear")); - } - - /* seen caster, possibly producing unseen--or just one--critters; - hero is told what the caster is doing and doesn't necessarily - observe complete accuracy of that caster's results (in other - words, no need to fuss with visibility or singularization; - player is told what's happening even if hero is unconscious) */ - } else if (!success) - fmt = "%s casts at a clump of sticks, but nothing happens."; - else if (let == S_SNAKE) - fmt = "%s transforms a clump of sticks into snakes!"; - else if (Invis && !perceives(mtmp->data) - && (mtmp->mux != u.ux || mtmp->muy != u.uy)) - fmt = "%s summons insects around a spot near you!"; - else if (Displaced && (mtmp->mux != u.ux || mtmp->muy != u.uy)) - fmt = "%s summons insects around your displaced image!"; - else - fmt = "%s summons insects!"; - if (fmt) - pline(fmt, Monnam(mtmp)); - + case MCAST_BLIND_YOU: + mcast_blind_you(); dmg = 0; break; - } - case CLC_BLIND_YOU: - /* note: resists_blnd() doesn't apply here */ - if (!Blinded) { - int num_eyes = eyecount(youmonst.data); - pline("Scales cover your %s!", (num_eyes == 1) - ? body_part(EYE) - : makeplural(body_part(EYE))); - make_blinded(Half_spell_damage ? 100L : 200L, FALSE); - if (!Blind) - Your1(vision_clears); - dmg = 0; - } else - impossible("no reason for monster to cast blindness spell?"); + case MCAST_PARALYZE: + dmg = mcast_paralyze(mtmp); break; - case CLC_PARALYZE: - if (Antimagic || Free_action) { - shieldeff(u.ux, u.uy); - if (multi >= 0) - You("stiffen briefly."); - nomul(-1); - multi_reason = "paralyzed by a monster"; - } else { - if (multi >= 0) - You("are frozen in place!"); - dmg = 4 + (int) mtmp->m_lev; - if (Half_spell_damage) - dmg = (dmg + 1) / 2; - nomul(-dmg); - multi_reason = "paralyzed by a monster"; - } - nomovemsg = 0; + case MCAST_CONFUSE_YOU: + mcast_confuse_you(mtmp); dmg = 0; break; - case CLC_CONFUSE_YOU: - if (Antimagic) { - shieldeff(u.ux, u.uy); - You_feel("momentarily dizzy."); - } else { - boolean oldprop = !!Confusion; - - dmg = (int) mtmp->m_lev; - if (Half_spell_damage) - dmg = (dmg + 1) / 2; - make_confused(HConfusion + dmg, TRUE); - if (Hallucination) - You_feel("%s!", oldprop ? "trippier" : "trippy"); - else - You_feel("%sconfused!", oldprop ? "more " : ""); - } - dmg = 0; - break; - case CLC_CURE_SELF: - dmg = m_cure_self(mtmp, dmg); - break; - case CLC_OPEN_WOUNDS: - if (Antimagic) { - shieldeff(u.ux, u.uy); - dmg = (dmg + 1) / 2; - } - if (dmg <= 5) - Your("skin itches badly for a moment."); - else if (dmg <= 10) - pline("Wounds appear on your body!"); - else if (dmg <= 20) - pline("Severe wounds appear on your body!"); - else - Your("body is covered with painful wounds!"); + case MCAST_OPEN_WOUNDS: + dmg = mcast_open_wounds(dmg); break; default: - impossible("mcastu: invalid clerical spell (%d)", spellnum); + impossible("mcastu: invalid magic spell (%d)", spellnum); dmg = 0; break; } @@ -731,43 +896,17 @@ int spellnum; mdamageu(mtmp, dmg); } -STATIC_DCL -boolean -is_undirected_spell(adtyp, spellnum) -unsigned int adtyp; -int spellnum; +staticfn boolean +is_undirected_spell(int spellnum) { - if (adtyp == AD_SPEL) { - switch (spellnum) { - case MGC_CLONE_WIZ: - case MGC_SUMMON_MONS: - case MGC_AGGRAVATION: - case MGC_DISAPPEAR: - case MGC_HASTE_SELF: - case MGC_CURE_SELF: - return TRUE; - default: - break; - } - } else if (adtyp == AD_CLRC) { - switch (spellnum) { - case CLC_INSECTS: - case CLC_CURE_SELF: - return TRUE; - default: - break; - } - } + if ((mcast_data[spellnum].flags & MCF_INDIRECT) != 0) + return TRUE; return FALSE; } /* Some spells are useless under some circumstances. */ -STATIC_DCL -boolean -spell_would_be_useless(mtmp, adtyp, spellnum) -struct monst *mtmp; -unsigned int adtyp; -int spellnum; +staticfn boolean +spell_would_be_useless(struct monst *mtmp, int spellnum) { /* Some spells don't require the player to really be there and can be cast * by the monster when you're invisible, yet still shouldn't be cast when @@ -775,94 +914,101 @@ int spellnum; * This check isn't quite right because it always uses your real position. * We really want something like "if the monster could see mux, muy". */ - boolean mcouldseeu = couldsee(mtmp->mx, mtmp->my); - if (adtyp == AD_SPEL) { - /* aggravate monsters, etc. won't be cast by peaceful monsters */ - if (mtmp->mpeaceful - && (spellnum == MGC_AGGRAVATION || spellnum == MGC_SUMMON_MONS - || spellnum == MGC_CLONE_WIZ)) + /* spell is only cast by hostile monsters */ + if ((mcast_data[spellnum].flags & MCF_HOSTILE) != 0) { + if (mtmp->mpeaceful) + return TRUE; + } + + /* spell needs the monster to see hero */ + if ((mcast_data[spellnum].flags & MCF_SIGHT) != 0) { + boolean mcouldseeu = couldsee(mtmp->mx, mtmp->my); + + if (!mcouldseeu) + return TRUE; + } + + switch (spellnum) { + case MCAST_DEATH_TOUCH: + if ((Antimagic || Hallucination) && !rn2(2)) return TRUE; + break; + case MCAST_GEYSER: + if (!rn2(5)) + return TRUE; + break; + case MCAST_CLONE_WIZ: + /* only the Wizard is allowed to clone himself */ + if (!mtmp->iswiz || svc.context.no_of_wizards > 1) + return TRUE; + break; + case MCAST_AGGRAVATION: + /* aggravation (global wakeup) when everyone is already active */ + /* if nothing needs to be awakened then this spell is useless + but caster might not realize that [chance to pick it then + must be very small otherwise caller's many retry attempts + will eventually end up picking it too often] */ + if (!has_aggravatables(mtmp)) + return rn2(100) ? TRUE : FALSE; + break; + case MCAST_HASTE_SELF: /* haste self when already fast */ - if (mtmp->permspeed == MFAST && spellnum == MGC_HASTE_SELF) + if (mtmp->permspeed == MFAST) return TRUE; + break; + case MCAST_DISAPPEAR: /* invisibility when already invisible */ - if ((mtmp->minvis || mtmp->invis_blkd) && spellnum == MGC_DISAPPEAR) + if (mtmp->minvis || mtmp->invis_blkd) return TRUE; /* peaceful monster won't cast invisibility if you can't see invisible, same as when monsters drink potions of invisibility. This doesn't really make a lot of sense, but lets the player avoid hitting peaceful monsters by mistake */ - if (mtmp->mpeaceful && !See_invisible && spellnum == MGC_DISAPPEAR) - return TRUE; - /* healing when already healed */ - if (mtmp->mhp == mtmp->mhpmax && spellnum == MGC_CURE_SELF) - return TRUE; - /* don't summon monsters if it doesn't think you're around */ - if (!mcouldseeu && (spellnum == MGC_SUMMON_MONS - || (!mtmp->iswiz && spellnum == MGC_CLONE_WIZ))) - return TRUE; - if ((!mtmp->iswiz || context.no_of_wizards > 1) - && spellnum == MGC_CLONE_WIZ) - return TRUE; - /* aggravation (global wakeup) when everyone is already active */ - if (spellnum == MGC_AGGRAVATION) { - /* if nothing needs to be awakened then this spell is useless - but caster might not realize that [chance to pick it then - must be very small otherwise caller's many retry attempts - will eventually end up picking it too often] */ - if (!has_aggravatables(mtmp)) - return rn2(100) ? TRUE : FALSE; - } - } else if (adtyp == AD_CLRC) { - /* summon insects/sticks to snakes won't be cast by peaceful monsters - */ - if (mtmp->mpeaceful && spellnum == CLC_INSECTS) + if (mtmp->mpeaceful && !See_invisible) return TRUE; + break; + case MCAST_CURE_SELF: /* healing when already healed */ - if (mtmp->mhp == mtmp->mhpmax && spellnum == CLC_CURE_SELF) - return TRUE; - /* don't summon insects if it doesn't think you're around */ - if (!mcouldseeu && spellnum == CLC_INSECTS) + if (mtmp->mhp == mtmp->mhpmax) return TRUE; - /* blindness spell on blinded player */ - if (Blinded && spellnum == CLC_BLIND_YOU) + break; + case MCAST_BLIND_YOU: + if (Blinded) return TRUE; + break; + default: + break; } return FALSE; } -/* convert 1..10 to 0..9; add 10 for second group (spell casting) */ -#define ad_to_typ(k) (10 + (int) k - 1) - /* monster uses spell (ranged) */ int -buzzmu(mtmp, mattk) -register struct monst *mtmp; -register struct attack *mattk; +buzzmu(struct monst *mtmp, struct attack *mattk) { /* don't print constant stream of curse messages for 'normal' spellcasting monsters at range */ - if (mattk->adtyp > AD_SPC2) - return (0); + if (!BZ_VALID_ADTYP(mattk->adtyp)) + return M_ATTK_MISS; - if (mtmp->mcan) { + if (mtmp->mcan || m_seenres(mtmp, cvt_adtyp_to_mseenres(mattk->adtyp))) { cursetxt(mtmp, FALSE); - return (0); + return M_ATTK_MISS; } if (lined_up(mtmp) && rn2(3)) { nomul(0); - if (mattk->adtyp && (mattk->adtyp < 11)) { /* no cf unsigned >0 */ - if (canseemon(mtmp)) - pline("%s zaps you with a %s!", Monnam(mtmp), - flash_types[ad_to_typ(mattk->adtyp)]); - buzz(-ad_to_typ(mattk->adtyp), (int) mattk->damn, mtmp->mx, - mtmp->my, sgn(tbx), sgn(tby)); - } else - impossible("Monster spell %d cast", mattk->adtyp - 1); - } - return (1); + if (canseemon(mtmp)) + pline_mon(mtmp, "%s zaps you with a %s!", Monnam(mtmp), + flash_str(BZ_OFS_AD(mattk->adtyp), FALSE)); + gb.buzzer = mtmp; + buzz(BZ_M_SPELL(BZ_OFS_AD(mattk->adtyp)), (int) mattk->damn, + mtmp->mx, mtmp->my, sgn(gt.tbx), sgn(gt.tby)); + gb.buzzer = 0; + return M_ATTK_HIT; + } + return M_ATTK_MISS; } /*mcastu.c*/ diff --git a/src/mdlib.c b/src/mdlib.c new file mode 100644 index 000000000..b7e0810c6 --- /dev/null +++ b/src/mdlib.c @@ -0,0 +1,877 @@ +/* NetHack 5.0 mdlib.c $NHDT-Date: 1701499945 2023/12/02 06:52:25 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.51 $ */ +/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ +/*-Copyright (c) Kenneth Lorber, Kensington, Maryland, 2015. */ +/* Copyright (c) M. Stephenson, 1990, 1991. */ +/* Copyright (c) Dean Luick, 1990. */ +/* NetHack may be freely redistributed. See license for details. */ + +/* + * This can be linked into a binary to provide the functionality + * via the contained functions, or it can be #included directly + * into util/makedefs.c to provide it there. + */ + +#ifndef MAKEDEFS_C +#define MDLIB_C +#include "config.h" +#include "permonst.h" +#include "objclass.h" +#include "wintype.h" +#include "sym.h" +#include "artilist.h" +#include "dungeon.h" +#include "sndprocs.h" +#include "obj.h" +#include "monst.h" +#include "you.h" +#include "context.h" +#include "flag.h" +#include "dlb.h" +#include "hacklib.h" + +/* version information */ +#ifdef SHORT_FILENAMES +#include "patchlev.h" +#else +#include "patchlevel.h" +#endif +#define Fprintf (void) fprintf +#define Fclose (void) fclose +#define Unlink (void) unlink +#if !defined(AMIGA) || defined(AZTEC_C) +#define rewind(fp) fseek((fp), 0L, SEEK_SET) /* guarantee a return value */ +#endif /* AMIGA || AZTEC_C */ +#else +#ifndef GLOBAL_H +#include "global.h" +#endif +#endif /* !MAKEDEFS_C */ + +/* shorten up some lines */ +#define FOR_RUNTIME + +#if defined(MAKEDEFS_C) || defined(FOR_RUNTIME) +#include +/* REPRODUCIBLE_BUILD will change this to TRUE */ +static boolean date_via_env = FALSE; + +extern unsigned long md_ignored_features(void); +extern const char *datamodel(int); +char *version_id_string(char *, size_t, const char *) NONNULL NONNULLPTRS; +char *bannerc_string(char *, size_t, const char *) NONNULL NONNULLPTRS; +int case_insensitive_comp(const char *, const char *) NONNULLPTRS; + +#ifndef SFCTOOL +staticfn +#endif /* SFCTOOL */ +void make_version(void); + +#ifndef HAS_NO_MKSTEMP +#ifdef _MSC_VER +static int mkstemp(char *); +#endif +#endif + +#endif /* MAKEDEFS_C || FOR_RUNTIME */ + +#if !defined(MAKEDEFS_C) && defined(WIN32) +extern int GUILaunched; +#endif + +/* these are in extern.h but we don't include hack.h */ +/* XXX move to new file mdlib.h? */ +extern void populate_nomakedefs(struct version_info *) NONNULLARG1; /*date.c*/ +extern void free_nomakedefs(void); /* date.c */ +void runtime_info_init(void); +const char *do_runtime_info(int *) NO_NNARGS; +void release_runtime_info(void); +char *mdlib_version_string(char *, const char *) NONNULL NONNULLPTRS; + +staticfn void build_options(void); +staticfn int count_and_validate_winopts(void); +staticfn void opt_out_words(char *, int *) NONNULLPTRS; +staticfn void build_savebones_compat_string(void); + +static int idxopttext, done_runtime_opt_init_once = 0; +#define MAXOPT 60 /* 5.0: currently 40 lines get inserted into opttext[] */ +static char *opttext[MAXOPT] = { 0 }; +#define STOREOPTTEXT(line) \ + ((void) ((idxopttext < MAXOPT) \ + ? (opttext[idxopttext++] = dupstr(line)) \ + : 0)) +static char optbuf[COLBUFSZ]; +static struct version_info version; +static const char opt_indent[] = " "; + +struct win_information { + const char *id, /* windowtype value */ + *name; /* description, often same as id */ + boolean valid; +}; + +static struct win_information window_opts[] = { +#ifdef TTY_GRAPHICS + { "tty", + /* testing TILES_IN_GLYPHMAP here would bring confusion because it could + apply to another interface such as X11, so check MSDOS explicitly + instead; even checking TTY_TILES_ESCCODES would probably be + confusing to most users (and it will already be listed separately + in the compiled options section so users aware of it can find it) */ +#ifdef MSDOS + "traditional text with optional 'tiles' graphics", +#else + /* assume that one or more of IBMgraphics, DECgraphics + can be enabled; we can't tell from here whether that is accurate */ + "traditional text with optional line-drawing", +#endif + TRUE + }, +#endif /*TTY_GRAPHICS */ +#ifdef CURSES_GRAPHICS + { "curses", "terminal-based graphics", TRUE }, +#endif +#ifdef X11_GRAPHICS + { "X11", "X11", TRUE }, +#endif +#ifdef QT_GRAPHICS /* too vague; there are multiple incompatible versions */ + { "Qt", "Qt", TRUE }, +#endif +#ifdef MSWIN_GRAPHICS /* win32 */ + { "mswin", "Windows GUI", TRUE }, +#endif +#ifdef SHIM_GRAPHICS + { "shim", "NetHack Library Windowing Shim", TRUE }, +#endif +#ifdef AMIGA_INTUITION + { "amii", "Amiga Intuition (text)", TRUE }, + { "amiv", "Amiga Intuition (tiles)", TRUE }, +#endif + +#if 0 /* remainder have been retired */ +#ifdef GNOME_GRAPHICS /* unmaintained/defunct */ + { "Gnome", "Gnome", TRUE }, +#endif +#ifdef MACOS9 /* defunct OS 9 interface */ + { "mac", "Mac", TRUE }, +#endif +#ifdef GEM_GRAPHICS /* defunct Atari interface */ + { "Gem", "Gem", TRUE }, +#endif +#ifdef BEOS_GRAPHICS /* unmaintained/defunct */ + { "BeOS", "BeOS InterfaceKit", TRUE }, +#endif +#endif /* 0 => retired */ + { 0, 0, FALSE } +}; + +#if !defined(MAKEDEFS_C) +staticfn int count_and_validate_soundlibopts(void); + +struct soundlib_information { + enum soundlib_ids id; + const char *const text_id; + const char *const Url; + boolean valid; +}; + +/* + * soundlibs + * + * None of these are endorsements or recommendations of one library + * or another, in any way. They are just preprocessor conditionals + * in the event that glue code for such a library is ever added into + * NetHack. + */ +static struct soundlib_information soundlib_opts[] = { + { soundlib_nosound, "soundlib_nosound", "", FALSE }, +#ifdef SND_LIB_PORTAUDIO + { soundlib_portaudio, "soundlib_portaudio", + "http://www.portaudio.com/", FALSE }, +#endif +#ifdef SND_LIB_OPENAL + { soundlib_openal, "soundlib_openal", + "https://www.openal.org/", FALSE }, +#endif +#ifdef SND_LIB_SDL_MIXER + { soundlib_sdl_mixer, "soundlib_sdl_mixer", + "https://github.com/libsdl-org/SDL_mixer/", FALSE }, +#endif +#ifdef SND_LIB_MINIAUDIO + { soundlib_miniaudio, "soundlib_miniaudio", + "https://miniaud.io/", FALSE }, +#endif +#ifdef SND_LIB_FMOD + /* proprietary, though a Free Indie License exists. + * https://www.fmod.com/licensing#indie-note + */ + { soundlib_fmod, "soundlib_fmod", + "https://www.fmod.com/", FALSE }, +#endif +#ifdef SND_LIB_SOUND_ESCCODES + { soundlib_sound_esccodes, "sound_esccodes", "", FALSE }, +#endif +#ifdef SND_LIB_VISSOUND + { soundlib_vissound, "soundlib_vissound", "", FALSE }, +#endif +#ifdef SND_LIB_WINDSOUND + /* Uses Windows WIN32 API */ + { soundlib_windsound, "soundlib_windsound", + "https://learn.microsoft.com/en-us/windows/win32/multimedia/waveform-audio", + FALSE }, +#endif +#ifdef SND_LIB_MACSOUND + /* Uses AppKit NSSound */ + { soundlib_macsound, "soundlib_macsound", + "https://developer.apple.com/documentation/appkit/nssound", + FALSE }, +#endif +#ifdef SND_LIB_QTSOUND + { soundlib_qtsound, "soundlib_qtsound", + "https://doc.qt.io/qt-5/qsoundeffect.html", FALSE }, +#endif + { 0, 0, 0, FALSE }, +}; +#endif /* !MAKEDEFS_C */ + +unsigned long +md_ignored_features(void) +{ + return (0UL + | (1UL << 19) /* SCORE_ON_BOTL */ + | SFCTOOL_BIT /* stored by SFCTOOL, not NetHack itself */ + ); +} + +#ifndef SFCTOOL +staticfn +#endif +void +make_version(void) +{ + int i; + + /* + * integer version number + */ + version.incarnation = ((unsigned long) VERSION_MAJOR << 24) + | ((unsigned long) VERSION_MINOR << 16) + | ((unsigned long) PATCHLEVEL << 8) + | ((unsigned long) EDITLEVEL); + /* + * encoded feature list + * Note: if any of these magic numbers are changed or reassigned, + * EDITLEVEL in patchlevel.h should be incremented at the same time. + * The actual values have no special meaning, and the category + * groupings are just for convenience. + */ + version.feature_set = (unsigned long) (0L +/* levels and/or topology (0..4) */ +/* monsters (5..9) */ +#ifdef MAIL_STRUCTURES + | (1L << 6) +#endif +/* objects (10..14) */ +/* flag bits and/or other global variables (15..26) */ + /* color support always*/ | (1L << 17) +#ifdef INSURANCE + | (1L << 18) +#endif +#ifdef SCORE_ON_BOTL + | (1L << 19) +#endif + ); + /* + * Value used for object & monster sanity check. + * (NROFARTIFACTS<<24) | (NUM_OBJECTS<<12) | (NUMMONS<<0) + */ + for (i = 1; artifact_names[i]; i++) + continue; + version.entity_count = (unsigned long) (i - 1); + i = NUM_OBJECTS; + version.entity_count = (version.entity_count << 12) | (unsigned long) i; + i = NUMMONS; + version.entity_count = (version.entity_count << 12) | (unsigned long) i; +/* free bits in here */ + return; +} + +#if defined(MAKEDEFS_C) || defined(FOR_RUNTIME) + +char * +mdlib_version_string(char *outbuf, const char *delim) +{ + Sprintf(outbuf, "%d%s%d%s%d", VERSION_MAJOR, delim, VERSION_MINOR, delim, + PATCHLEVEL); +#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) + Sprintf(eos(outbuf), "-%d", EDITLEVEL); +#endif + return outbuf; +} + +#define Snprintf(str, size, ...) \ + nh_snprintf(__func__, __LINE__, str, size, __VA_ARGS__) +extern void nh_snprintf(const char *func, int line, char *str, size_t size, + const char *fmt, ...); + +char * +version_id_string(char *outbuf, size_t bufsz, const char *build_date) +{ + char subbuf[64], versbuf[64]; + char statusbuf[64]; + +#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) +#if (NH_DEVEL_STATUS == NH_STATUS_BETA) + Strcpy(statusbuf, " Beta"); +#else +#if (NH_DEVEL_STATUS == NH_STATUS_WIP) + Strcpy(statusbuf, " Work-in-progress"); +#else + Strcpy(statusbuf, " post-release"); +#endif +#endif +#else + statusbuf[0] = '\0'; +#endif + subbuf[0] = '\0'; +#ifdef PORT_SUB_ID + subbuf[0] = ' '; + Strcpy(&subbuf[1], PORT_SUB_ID); +#endif + + Snprintf(outbuf, bufsz, "%s NetHack%s Version %s%s - last %s %s.", + PORT_ID, subbuf, mdlib_version_string(versbuf, "."), statusbuf, + date_via_env ? "revision" : "build", build_date); + return outbuf; +} + +/* still within #if MAKDEFS_C || FOR_RUNTIME */ + +char * +bannerc_string(char *outbuf, size_t bufsz, const char *build_date) +{ + char subbuf[64], versbuf[64]; + + subbuf[0] = '\0'; +#ifdef PORT_SUB_ID + subbuf[0] = ' '; + Strcpy(&subbuf[1], PORT_SUB_ID); +#endif + +#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) +#if (NH_DEVEL_STATUS == NH_STATUS_BETA) + Strcpy(subbuf, " Beta"); +#elif (NH_DEVEL_STATUS == NH_STATUS_WIP) + Strcpy(subbuf, " Work-in-progress"); +#elif (NH_DEVEL_STATUS == NH_STATUS_POSTRELEASE) + Strcpy(subbuf, " post-release"); +#endif +#endif /* !NH_STATUS_RELEASED */ + Snprintf(outbuf, bufsz, " Version %s %s%s, %s %s.", + mdlib_version_string(versbuf, "."), PORT_ID, subbuf, + date_via_env ? "revised" : "built", build_date); + return outbuf; +} + +#ifndef HAS_NO_MKSTEMP +#ifdef _MSC_VER +int +mkstemp(char *template) +{ + int err; + + err = _mktemp_s(template, strlen(template) + 1); + if( err != 0 ) + return -1; + return _open(template, + _O_RDWR | _O_BINARY | _O_TEMPORARY | _O_CREAT, + _S_IREAD | _S_IWRITE); +} +#endif /* _MSC_VER */ +#endif /* HAS_NO_MKSTEMP */ +#endif /* MAKEDEFS_C || FOR_RUNTIME */ + +static char save_bones_compat_buf[BUFSZ]; + +staticfn void +build_savebones_compat_string(void) +{ +#ifdef VERSION_COMPATIBILITY + unsigned long uver = VERSION_COMPATIBILITY, + cver = (((unsigned long) VERSION_MAJOR << 24) + | ((unsigned long) VERSION_MINOR << 16) + | ((unsigned long) PATCHLEVEL << 8)); +#endif + + Strcpy(save_bones_compat_buf, + "save and bones files accepted from version"); +#ifdef VERSION_COMPATIBILITY + if (uver != cver) + Sprintf(eos(save_bones_compat_buf), "s %lu.%lu.%lu through %d.%d.%d", + ((uver >> 24) & 0x0ffUL), + ((uver >> 16) & 0x0ffUL), + ((uver >> 8) & 0x0ffUL), + VERSION_MAJOR, VERSION_MINOR, PATCHLEVEL); + else +#endif + Sprintf(eos(save_bones_compat_buf), " %d.%d.%d only", + VERSION_MAJOR, VERSION_MINOR, PATCHLEVEL); +} + +static const char *const build_opts[] = { +#ifdef AMIGA_WBENCH + "Amiga WorkBench support", +#endif +#ifdef ANSI_DEFAULT + "ANSI default terminal", +#endif + "color", +#ifdef TTY_GRAPHICS +#ifdef TTY_TILES_ESCCODES + "console escape codes for tile hinting", +#endif +#endif +#ifdef LIFE + "Conway's Game of Life", +#endif +#ifdef COMPRESS + "data file compression", +#endif +#ifdef ZLIB_COMP + "ZLIB data file compression", +#endif +#ifdef DLB +#ifndef VERSION_IN_DLB_FILENAME + "data librarian", +#else + "data librarian with a version-dependent name", +#endif +#endif +#ifdef EDIT_GETLIN + "edit getlin - some prompts remember previous response", +#endif +#ifdef DUMPLOG + "end-of-game dumplogs", +#endif +#ifdef HOLD_LOCKFILE_OPEN + "exclusive lock on level 0 file", +#endif +#if defined(HANGUPHANDLING) && !defined(NO_SIGNAL) +#ifdef SAFERHANGUP + "deferred handling of hangup signal", +#else + "immediate handling of hangup signal", +#endif +#endif +#ifdef INSURANCE + "insurance files for recovering from crashes", +#endif +#ifdef LIVELOG + "live logging support", +#endif +#ifdef LOGFILE + "log file", +#endif +#ifdef XLOGFILE + "extended log file", +#endif +#ifdef PANICLOG + "errors and warnings log file", +#endif +#ifdef MAIL + "mail daemon", +#endif +#ifdef MONITOR_HEAP + "monitor heap - record memory usage for later analysis", +#endif +#if defined(GNUDOS) || defined(__DJGPP__) + "MSDOS protected mode", +#endif +#ifdef NEWS + "news file", +#endif +#ifdef OVERLAY +#ifdef MOVERLAY + "MOVE overlays", +#else +#ifdef VROOMM + "VROOMM overlays", +#else + "overlays", +#endif +#endif +#endif +#ifdef UNIX +#if defined(DEF_PAGER) && !defined(DLB) + "external pager used for viewing help files", +#else + "internal pager used for viewing help files", +#endif +#endif /* UNIX */ + /* pattern matching method will be substituted by nethack at run time */ + "pattern matching via :PATMATCH:", +#ifdef USE_ISAAC64 + "pseudo random numbers generated by ISAAC64", +#ifdef DEV_RANDOM + /* include which specific one */ + "strong PRNG seed from " DEV_RANDOM, +#else +#ifdef WIN32 + "strong PRNG seed from CNG BCryptGenRandom()", +#endif +#endif /* DEV_RANDOM */ +#else /* ISAAC64 */ +#ifdef RANDOM + "pseudo random numbers generated by random()", +#else + "pseudo random numbers generated by C rand()", +#endif +#endif /* ISAAC64 */ +#ifdef SELECTSAVED + "restore saved games via menu", +#endif +#ifdef SCORE_ON_BOTL + "score on status line", +#endif +#ifdef CLIPPING + "screen clipping", +#endif +#ifdef NO_TERMS +#ifdef MACOS9 + "screen control via mactty", +#endif +#ifdef SCREEN_BIOS + "screen control via BIOS", +#endif +#ifdef SCREEN_DJGPPFAST + "screen control via DJGPP fast", +#endif +#ifdef SCREEN_VGA + "screen control via VGA graphics", +#endif +#ifdef WIN32CON + "screen control via WIN32 console I/O", +#endif +#endif /* NO_TERMS */ +#ifdef SHELL + "shell command", +#endif + "traditional status display", +#ifdef STATUS_HILITES + "status via windowport with highlighting", +#else + "status via windowport without highlighting", +#endif +#ifdef SUSPEND + "suspend command", +#endif +#ifdef TTY_GRAPHICS +#ifdef TERMINFO + "terminal info library", +#else +#if defined(TERMLIB) || (!defined(MICRO) && !defined(WIN32)) + "terminal capability library", +#endif +#endif +#endif /*TTY_GRAPHICS*/ +#ifdef USE_XPM + "tiles file in XPM format", +#endif +#ifdef GRAPHIC_TOMBSTONE + "graphical RIP screen", +#endif +#ifdef TIMED_DELAY + "timed wait for display effects", +#endif +#ifdef PREFIXES_IN_USE + "variable playground", +#endif +#ifdef VISION_TABLES + "vision tables", +#endif +#ifdef SYSCF + "system configuration at run-time", +#endif +#ifdef PANICTRACE + "show stack trace on error", +#endif +#ifdef CRASHREPORT + "launch browser to report issues", +#endif + save_bones_compat_buf, + "and basic NetHack features" +}; + +staticfn int +count_and_validate_winopts(void) +{ + int i, cnt = 0; + + /* window_opts has a fencepost entry at the end */ + for (i = 0; i < SIZE(window_opts) - 1; i++) { +#if !defined(MAKEDEFS_C) && defined(FOR_RUNTIME) +#ifdef WIN32 + window_opts[i].valid = FALSE; + if ((GUILaunched + && case_insensitive_comp(window_opts[i].id, "curses") != 0 + && case_insensitive_comp(window_opts[i].id, "mswin") != 0) + || (!GUILaunched + && case_insensitive_comp(window_opts[i].id, "mswin") == 0)) + continue; +#endif +#endif /* !MAKEDEFS_C && FOR_RUNTIME */ + ++cnt; + window_opts[i].valid = TRUE; + } + return cnt; +} + +#if !defined(MAKEDEFS_C) +staticfn int +count_and_validate_soundlibopts(void) +{ + int i, cnt = 0; + + /* soundlib_opts has a fencepost entry at the end */ + for (i = 0; i < SIZE(soundlib_opts) - 1; i++) { + ++cnt; + soundlib_opts[i].valid = TRUE; + } + return cnt; +} +#endif + +staticfn void +opt_out_words( + char *str, /* input, but modified during processing */ + int *length_p) /* in/out */ +{ + char *word; + + while (*str) { + word = strchr(str, ' '); +#if 0 + /* treat " (" as unbreakable space */ + if (word && *(word + 1) == '(') + word = strchr(word + 1, ' '); +#endif + if (word) + *word = '\0'; + if (*length_p + (int) strlen(str) > COLNO - 5) { + STOREOPTTEXT(optbuf); + Sprintf(optbuf, "%s", opt_indent), + *length_p = (int) strlen(opt_indent); + } else { + Sprintf(eos(optbuf), " "), (*length_p)++; + } + Sprintf(eos(optbuf), "%s", str), *length_p += (int) strlen(str); + str += strlen(str) + (word ? 1 : 0); + } +} + +staticfn void +build_options(void) +{ + char buf[COLBUFSZ]; + int i, length, winsyscnt, cnt = 0; + const char *defwinsys = DEFAULT_WINDOW_SYS; +#if !defined(MAKEDEFS_C) && defined(FOR_RUNTIME) + int soundlibcnt; + +#ifdef WIN32 + defwinsys = GUILaunched ? "mswin" : "tty"; +#endif +#endif + build_savebones_compat_string(); + STOREOPTTEXT(optbuf); +#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) +#if (NH_DEVEL_STATUS == NH_STATUS_BETA) +#define STATUS_ARG " [beta]" +#else +#define STATUS_ARG " [work-in-progress]" +#endif +#else +#define STATUS_ARG "" +#endif /* NH_DEVEL_STATUS == NH_STATUS_RELEASED */ + Sprintf(optbuf, "%sNetHack version %d.%d.%d%s\n", + opt_indent, VERSION_MAJOR, VERSION_MINOR, PATCHLEVEL, STATUS_ARG); + STOREOPTTEXT(optbuf); + Sprintf(optbuf, "Options compiled into this edition:"); + STOREOPTTEXT(optbuf); + optbuf[0] = '\0'; + length = COLNO + 1; /* force 1st item onto new line */ + Strcat(strcpy(buf, datamodel(0)), " data model,"); + opt_out_words(buf, &length); + for (i = 0; i < SIZE(build_opts); i++) { +#if !defined(MAKEDEFS_C) && defined(FOR_RUNTIME) +#ifdef WIN32 + /* ignore the console entry if GUI version */ + if (GUILaunched + && !strcmp("screen control via WIN32 console I/O", build_opts[i])) + continue; +#endif +#endif /* !MAKEDEFS_C && FOR_RUNTIME */ + Strcat(strcpy(buf, build_opts[i]), + (i < SIZE(build_opts) - 1) ? "," : "."); + opt_out_words(buf, &length); + } + STOREOPTTEXT(optbuf); + optbuf[0] = '\0'; + winsyscnt = count_and_validate_winopts(); + STOREOPTTEXT(optbuf); + Sprintf(optbuf, "Supported windowing system%s:", + (winsyscnt > 1) ? "s" : ""); + STOREOPTTEXT(optbuf); + optbuf[0] = '\0'; + length = COLNO + 1; /* force 1st item onto new line */ + + for (i = 0; i < SIZE(window_opts) - 1; i++) { + if (!window_opts[i].valid) + continue; + Sprintf(buf, "\"%s\"", window_opts[i].id); + if (strcmp(window_opts[i].name, window_opts[i].id)) + Sprintf(eos(buf), " (%s)", window_opts[i].name); + /* + * 1 : foo. + * 2 : foo and bar, + * 3+: for, bar, and quux, + * + * 2+ will be followed by " with a default of..." + */ + Strcat(buf, (winsyscnt == 1) ? "." /* no 'default' */ + : (winsyscnt == 2 && cnt == 0) ? " and" + : (cnt == winsyscnt - 2) ? ", and" + : ","); + opt_out_words(buf, &length); + cnt++; + } + if (cnt > 1) { + /* loop ended with a comma; opt_out_words() will insert a space */ + Sprintf(buf, "with a default of \"%s\".", defwinsys); + opt_out_words(buf, &length); + } + +#if !defined(MAKEDEFS_C) + cnt = 0; + STOREOPTTEXT(optbuf); + optbuf[0] = '\0'; + soundlibcnt = count_and_validate_soundlibopts(); + STOREOPTTEXT(optbuf); + Sprintf(optbuf, "Supported soundlib%s:", (soundlibcnt > 1) ? "s" : ""); + STOREOPTTEXT(optbuf); + optbuf[0] = '\0'; + length = COLNO + 1; /* force 1st item onto new line */ + +#ifdef USER_SOUNDS + soundlibcnt += 1; +#endif + for (i = 0; i < SIZE(soundlib_opts) - 1; i++) { + const char *soundlib; + + if (!soundlib_opts[i].valid) + continue; + soundlib = soundlib_opts[i].text_id; + if (!strncmp(soundlib, "soundlib_", 9)) + soundlib += 9; + Sprintf(buf, "\"%s\"", soundlib); + /* + * 1 : foo. + * 2 : foo and bar. + * 3+: for, bar, and quux. + */ + Strcat(buf, (soundlibcnt == 1 || cnt == soundlibcnt - 1) + ? "." /* no 'with default' */ + : (soundlibcnt == 2 && cnt == 0) ? " and" + : (cnt == soundlibcnt - 2) ? ", and" + : ","); + opt_out_words(buf, &length); + cnt++; + } +#ifdef USER_SOUNDS + if (cnt > 1) { + /* loop ended with a comma; opt_out_words() will insert a space */ + Sprintf(buf, "user sounds."); + opt_out_words(buf, &length); + } +#endif +#endif /* !MAKEDEFS_C */ + + STOREOPTTEXT(optbuf); + optbuf[0] = '\0'; + +#if defined(MAKEDEFS_C) || defined(FOR_RUNTIME) + { + static const char *const lua_info[] = { + "", "NetHack 5.0.* uses the 'Lua' interpreter to process some data:", "", + " :LUACOPYRIGHT:", "", + /* 1 2 3 4 5 6 7 + 1234567890123456789012345678901234567890123456789012345678901234567890123456 + */ + (" \"Permission is hereby granted, free of charge," + " to any person obtaining"), + " a copy of this software and associated documentation files (the ", + " \"Software\"), to deal in the Software without restriction including", + " without limitation the rights to use, copy, modify, merge, publish,", + " distribute, sublicense, and/or sell copies of the Software, and to ", + " permit persons to whom the Software is furnished to do so, subject to", + " the following conditions:", + " The above copyright notice and this permission notice shall be", + " included in all copies or substantial portions of the Software.\"", + (const char *) 0 + }; + + /* add lua copyright notice; + ":TAG:" substitutions are deferred to caller */ + for (i = 0; lua_info[i]; ++i) { + STOREOPTTEXT(lua_info[i]); + } + } +#endif /* MAKEDEFS_C || FOR_RUNTIME */ + + /* end with a blank line */ + STOREOPTTEXT(""); + return; +} + +#undef STOREOPTTEXT + +void +runtime_info_init(void) +{ + if (!done_runtime_opt_init_once) { + done_runtime_opt_init_once = 1; + build_savebones_compat_string(); + /* construct the current version number */ + make_version(); + populate_nomakedefs(&version); /* date.c */ + idxopttext = 0; + build_options(); + } +} + +const char * +do_runtime_info(int *rtcontext) +{ + const char *retval = (const char *) 0; + + if (!done_runtime_opt_init_once) + runtime_info_init(); + if (idxopttext && rtcontext) + if (*rtcontext >= 0 && *rtcontext < MAXOPT) { + retval = opttext[*rtcontext]; + *rtcontext += 1; + } + return retval; +} + +void +release_runtime_info(void) +{ + while (idxopttext > 0) { + --idxopttext; + free((genericptr_t) opttext[idxopttext]), opttext[idxopttext] = 0; + } + done_runtime_opt_init_once = 0; + free_nomakedefs(); +} + +/*mdlib.c*/ diff --git a/src/mhitm.c b/src/mhitm.c index 5ebbd8429..ff59ef470 100644 --- a/src/mhitm.c +++ b/src/mhitm.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 mhitm.c $NHDT-Date: 1583606861 2020/03/07 18:47:41 $ $NHDT-Branch: NetHack-3.6-Mar2020 $:$NHDT-Revision: 1.119 $ */ +/* NetHack 5.0 mhitm.c $NHDT-Date: 1732979463 2024/11/30 07:11:03 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.253 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2011. */ /* NetHack may be freely redistributed. See license for details. */ @@ -6,83 +6,60 @@ #include "hack.h" #include "artifact.h" -extern boolean notonhead; - -static NEARDATA boolean vis, far_noise; -static NEARDATA long noisetime; -static NEARDATA struct obj *otmp; - static const char brief_feeling[] = "have a %s feeling for a moment, then it passes."; -STATIC_DCL int FDECL(hitmm, (struct monst *, struct monst *, - struct attack *)); -STATIC_DCL int FDECL(gazemm, (struct monst *, struct monst *, - struct attack *)); -STATIC_DCL int FDECL(gulpmm, (struct monst *, struct monst *, - struct attack *)); -STATIC_DCL int FDECL(explmm, (struct monst *, struct monst *, - struct attack *)); -STATIC_DCL int FDECL(mdamagem, (struct monst *, struct monst *, - struct attack *)); -STATIC_DCL void FDECL(mswingsm, (struct monst *, struct monst *, - struct obj *)); -STATIC_DCL void FDECL(noises, (struct monst *, struct attack *)); -STATIC_DCL void FDECL(missmm, (struct monst *, struct monst *, - struct attack *)); -STATIC_DCL int FDECL(passivemm, (struct monst *, struct monst *, - BOOLEAN_P, int)); - -/* Needed for the special case of monsters wielding vorpal blades (rare). - * If we use this a lot it should probably be a parameter to mdamagem() - * instead of a global variable. - */ -static int dieroll; - -STATIC_OVL void -noises(magr, mattk) -register struct monst *magr; -register struct attack *mattk; +staticfn void noises(struct monst *, struct attack *); +staticfn void pre_mm_attack(struct monst *, struct monst *); +staticfn void missmm(struct monst *, struct monst *, struct attack *); +staticfn int hitmm(struct monst *, struct monst *, struct attack *, + struct obj *, int); +staticfn int gazemm(struct monst *, struct monst *, struct attack *); +staticfn int gulpmm(struct monst *, struct monst *, struct attack *); +staticfn int explmm(struct monst *, struct monst *, struct attack *); +staticfn int mdamagem(struct monst *, struct monst *, struct attack *, + struct obj *, int); +staticfn void mswingsm(struct monst *, struct monst *, struct obj *); +staticfn int passivemm(struct monst *, struct monst *, boolean, int, + struct obj *); + +staticfn void +noises(struct monst *magr, struct attack *mattk) { - boolean farq = (distu(magr->mx, magr->my) > 15); + boolean farq = (mdistu(magr) > 15); - if (!Deaf && (farq != far_noise || moves - noisetime > 10)) { - far_noise = farq; - noisetime = moves; + if (!Deaf && (farq != gf.far_noise || svm.moves - gn.noisetime > 10)) { + gf.far_noise = farq; + gn.noisetime = svm.moves; You_hear("%s%s.", (mattk->aatyp == AT_EXPL) ? "an explosion" : "some noises", farq ? " in the distance" : ""); } } -STATIC_OVL -void -missmm(magr, mdef, mattk) -register struct monst *magr, *mdef; -struct attack *mattk; +staticfn void +pre_mm_attack(struct monst *magr, struct monst *mdef) { - const char *fmt; - char buf[BUFSZ]; boolean showit = FALSE; /* unhiding or unmimicking happens even if hero can't see it because the formerly concealed monster is now in action */ if (M_AP_TYPE(mdef)) { seemimic(mdef); - showit |= vis; + showit |= gv.vis; } else if (mdef->mundetected) { mdef->mundetected = 0; - showit |= vis; + showit |= gv.vis; } if (M_AP_TYPE(magr)) { seemimic(magr); - showit |= vis; + showit |= gv.vis; } else if (magr->mundetected) { magr->mundetected = 0; - showit |= vis; + showit |= gv.vis; } - if (vis) { + if (gv.vis) { if (!canspotmon(magr)) map_invisible(magr->mx, magr->my); else if (showit) @@ -91,14 +68,26 @@ struct attack *mattk; map_invisible(mdef->mx, mdef->my); else if (showit) newsym(mdef->mx, mdef->my); + } +} - fmt = (could_seduce(magr, mdef, mattk) && !magr->mcan) - ? "%s pretends to be friendly to" - : "%s misses"; - Sprintf(buf, fmt, Monnam(magr)); - pline("%s %s.", buf, mon_nam_too(mdef, magr)); - } else +/* feedback for when a monster-vs-monster attack misses */ +staticfn void +missmm( + struct monst *magr, /* attacker */ + struct monst *mdef, /* defender */ + struct attack *mattk) /* attack and damage types */ +{ + pre_mm_attack(magr, mdef); + + if (gv.vis) { + pline("%s %s %s.", Monnam(magr), + (magr->mcan || !could_seduce(magr, mdef, mattk)) ? "misses" + : "pretends to be friendly to", + mon_nam_too(mdef, magr)); + } else { noises(magr, mattk); + } } /* @@ -114,16 +103,12 @@ struct attack *mattk; */ /* have monsters fight each other */ int -fightm(mtmp) -register struct monst *mtmp; +fightm(struct monst *mtmp) { - register struct monst *mon, *nmon; + struct monst *mon, *nmon; int result, has_u_swallowed; -#ifdef LINT - nmon = 0; -#endif /* perhaps the monster will resist Conflict */ - if (resist(mtmp, RING_CLASS, 0, 0)) + if (resist_conflict(mtmp)) return 0; if (u.ustuck == mtmp) { @@ -131,7 +116,7 @@ register struct monst *mtmp; if (itsstuck(mtmp)) return 0; } - has_u_swallowed = (u.uswallow && (mtmp == u.ustuck)); + has_u_swallowed = engulfing_u(mtmp); for (mon = fmon; mon; mon = nmon) { nmon = mon->nmon; @@ -146,19 +131,18 @@ register struct monst *mtmp; if (monnear(mtmp, mon->mx, mon->my)) { if (!u.uswallow && (mtmp == u.ustuck)) { if (!rn2(4)) { + set_ustuck((struct monst *) 0); pline("%s releases you!", Monnam(mtmp)); - u.ustuck = 0; } else break; } /* mtmp can be killed */ - bhitpos.x = mon->mx; - bhitpos.y = mon->my; - notonhead = 0; + gb.bhitpos.x = mon->mx, gb.bhitpos.y = mon->my; + gn.notonhead = FALSE; result = mattackm(mtmp, mon); - if (result & MM_AGR_DIED) + if (result & M_ATTK_AGR_DIED) return 1; /* mtmp died */ /* * If mtmp has the hero swallowed, lie and say there @@ -167,17 +151,20 @@ register struct monst *mtmp; if (has_u_swallowed) return 0; - /* Allow attacked monsters a chance to hit back. Primarily - * to allow monsters that resist conflict to respond. - */ - if ((result & MM_HIT) && !(result & MM_DEF_DIED) && rn2(4) - && mon->movement >= NORMAL_SPEED) { - mon->movement -= NORMAL_SPEED; - notonhead = 0; + /* allow attacked monsters a chance to hit back, primarily + to allow monsters that resist conflict to respond */ + if ((result & (M_ATTK_HIT | M_ATTK_DEF_DIED)) == M_ATTK_HIT + && rn2(4) && mon->movement > rn2(NORMAL_SPEED)) { + if (mon->movement > NORMAL_SPEED) + mon->movement -= NORMAL_SPEED; + else + mon->movement = 0; + gb.bhitpos.x = mtmp->mx, gb.bhitpos.y = mtmp->my; + gn.notonhead = FALSE; (void) mattackm(mon, mtmp); /* return attack */ } - return (result & MM_HIT) ? 1 : 0; + return (result & M_ATTK_HIT) ? 1 : 0; } } } @@ -189,32 +176,33 @@ register struct monst *mtmp; * returns same results as mattackm(). */ int -mdisplacem(magr, mdef, quietly) -register struct monst *magr, *mdef; -boolean quietly; +mdisplacem( + struct monst *magr, + struct monst *mdef, + boolean quietly) { struct permonst *pa, *pd; int tx, ty, fx, fy; /* sanity checks; could matter if we unexpectedly get a long worm */ if (!magr || !mdef || magr == mdef) - return MM_MISS; + return M_ATTK_MISS; pa = magr->data, pd = mdef->data; tx = mdef->mx, ty = mdef->my; /* destination */ fx = magr->mx, fy = magr->my; /* current location */ if (m_at(fx, fy) != magr || m_at(tx, ty) != mdef) - return MM_MISS; + return M_ATTK_MISS; - /* The 1 in 7 failure below matches the chance in attack() + /* The 1 in 7 failure below matches the chance in do_attack() * for pet displacement. */ if (!rn2(7)) - return MM_MISS; + return M_ATTK_MISS; /* Grid bugs cannot displace at an angle. */ if (pa == &mons[PM_GRID_BUG] && magr->mx != mdef->mx && magr->my != mdef->my) - return MM_MISS; + return M_ATTK_MISS; /* undetected monster becomes un-hidden if it is displaced */ if (mdef->mundetected) @@ -231,37 +219,51 @@ boolean quietly; * You can observe monster displacement if you can see both of * the monsters involved. */ - vis = (canspotmon(magr) && canspotmon(mdef)); + gv.vis = (canspotmon(magr) && canspotmon(mdef)); if (touch_petrifies(pd) && !resists_ston(magr)) { - if (which_armor(magr, W_ARMG) != 0) { + if (!which_armor(magr, W_ARMG)) { if (poly_when_stoned(pa)) { mon_to_stone(magr); - return MM_HIT; /* no damage during the polymorph */ + return M_ATTK_HIT; /* no damage during the polymorph */ + } + if (!quietly && canspotmon(magr)) { + if (gv.vis) { + pline("%s tries to move %s out of %s way.", Monnam(magr), + mon_nam(mdef), is_rider(pa) ? "the" : mhis(magr)); + } + pline_mon(magr, "%s turns to stone!", Monnam(magr)); } - if (!quietly && canspotmon(magr)) - pline("%s turns to stone!", Monnam(magr)); monstone(magr); if (!DEADMONSTER(magr)) - return MM_HIT; /* lifesaved */ - else if (magr->mtame && !vis) + return M_ATTK_HIT; /* lifesaved */ + else if (magr->mtame && !gv.vis) You(brief_feeling, "peculiarly sad"); - return MM_AGR_DIED; + return M_ATTK_AGR_DIED; } } remove_monster(fx, fy); /* pick up from orig position */ - remove_monster(tx, ty); + if (mdef->wormno) + remove_worm(mdef); + else + remove_monster(tx, ty); place_monster(magr, tx, ty); /* put down at target spot */ place_monster(mdef, fx, fy); - if (vis && !quietly) + if (mdef->wormno) /* now put down tail */ + place_worm_tail_randomly(mdef, fx, fy); + /* either creature might move into or out of a poison gas cloud */ + update_monster_region(magr); + update_monster_region(mdef); + + if (gv.vis && !quietly) pline("%s moves %s out of %s way!", Monnam(magr), mon_nam(mdef), is_rider(pa) ? "the" : mhis(magr)); - newsym(fx, fy); /* see it */ + newsym(fx, fy); /* see it */ newsym(tx, ty); /* all happen */ flush_screen(0); /* make sure it shows up */ - return MM_HIT; + return M_ATTK_HIT; } /* @@ -273,49 +275,56 @@ boolean quietly; * / / / * x x x * - * 0x4 MM_AGR_DIED - * 0x2 MM_DEF_DIED - * 0x1 MM_HIT - * 0x0 MM_MISS + * 0x8 M_ATTK_AGR_DONE + * 0x4 M_ATTK_AGR_DIED + * 0x2 M_ATTK_DEF_DIED + * 0x1 M_ATTK_HIT + * 0x0 M_ATTK_MISS * * Each successive attack has a lower probability of hitting. Some rely on - * success of previous attacks. ** this doen't seem to be implemented -dl ** + * success of previous attacks. ** this doesn't seem to be implemented -dl ** + * + * Attacker has targeted rather than + * mx,mdef->my>; matters for long worms. * * In the case of exploding monsters, the monster dies as well. */ int -mattackm(magr, mdef) -register struct monst *magr, *mdef; +mattackm( + struct monst *magr, + struct monst *mdef) { int i, /* loop counter */ - tmp, /* amour class difference */ + tmp, /* armor class difference */ strike = 0, /* hit this attack */ attk, /* attack attempted this time */ struck = 0, /* hit at least once */ - res[NATTK]; /* results of all attacks */ + res[NATTK], /* results of all attacks */ + dieroll = 0; struct attack *mattk, alt_attk; + struct obj *mwep; struct permonst *pa, *pd; if (!magr || !mdef) - return MM_MISS; /* mike@genat */ - if (!magr->mcanmove || magr->msleeping) - return MM_MISS; + return M_ATTK_MISS; /* mike@genat */ + if (helpless(magr)) + return M_ATTK_MISS; pa = magr->data; pd = mdef->data; /* Grid bugs cannot attack at an angle. */ if (pa == &mons[PM_GRID_BUG] && magr->mx != mdef->mx && magr->my != mdef->my) - return MM_MISS; + return M_ATTK_MISS; /* Calculate the armour class differential. */ tmp = find_mac(mdef) + magr->m_lev; - if (mdef->mconf || !mdef->mcanmove || mdef->msleeping) { + if (mdef->mconf || helpless(mdef)) { tmp += 4; mdef->msleeping = 0; } - /* undetect monsters become un-hidden if they are attacked */ + /* mundetected monsters become un-hidden if they are attacked */ if (mdef->mundetected) { mdef->mundetected = 0; newsym(mdef->mx, mdef->my); @@ -329,8 +338,15 @@ register struct monst *magr, *mdef; if (!justone) montype = makeplural(montype); You("dream of %s.", montype); - } else - pline("Suddenly, you notice %s.", a_monnam(mdef)); + } else { + if (iflags.last_msg == PLNMSG_HIDE_UNDER + && mdef->m_id == gl.last_hider) + pline_mon(mdef, "%s emerges from hiding.", Monnam(mdef)); + else if (mdef->m_id == gl.last_hider) + You("notice %s.", mon_nam(mdef)); + else + pline("Suddenly, you notice %s.", a_monnam(mdef)); + } } } @@ -339,46 +355,66 @@ register struct monst *magr, *mdef; tmp++; /* Set up the visibility of action */ - vis = (cansee(magr->mx, magr->my) && cansee(mdef->mx, mdef->my) - && (canspotmon(magr) || canspotmon(mdef))); + gv.vis = ((cansee(magr->mx, magr->my) && canspotmon(magr)) + || (cansee(mdef->mx, mdef->my) && canspotmon(mdef))); /* Set flag indicating monster has moved this turn. Necessary since a * monster might get an attack out of sequence (i.e. before its move) in * some cases, in which case this still counts as its move for the round * and it shouldn't move again. */ - magr->mlstmv = monstermoves; + magr->mlstmv = svm.moves; + + /* controls whether a mind flayer uses all of its tentacle-for-DRIN + attacks; when fighting a headless monster, stop after the first + one because repeating the same failing hit (or even an ordinary + tentacle miss) is very verbose and makes the flayer look stupid */ + gs.skipdrin = FALSE; /* Now perform all attacks for the monster. */ for (i = 0; i < NATTK; i++) { - res[i] = MM_MISS; + res[i] = M_ATTK_MISS; + + /* target might no longer be there */ + if (i > 0 && (m_at(gb.bhitpos.x, gb.bhitpos.y) != mdef + || DEADMONSTER(magr) || DEADMONSTER(mdef))) + continue; + mattk = getmattk(magr, mdef, i, res, &alt_attk); - otmp = (struct obj *) 0; + /* reduce verbosity for mind flayer attacking creature without a + head (or worm's tail); this is similar to monster with multiple + attacks after a wildmiss against displaced or invisible hero */ + if (gs.skipdrin && mattk->aatyp == AT_TENT && mattk->adtyp == AD_DRIN) + continue; + mwep = (struct obj *) 0; attk = 1; + switch (mattk->aatyp) { case AT_WEAP: /* "hand to hand" attacks */ if (distmin(magr->mx, magr->my, mdef->mx, mdef->my) > 1) { /* D: Do a ranged attack here! */ - strike = thrwmm(magr, mdef); + strike = (thrwmm(magr, mdef) == M_ATTK_MISS) ? 0 : 1; + if (strike) + /* don't really know if we hit or not; pretend we did */ + res[i] |= M_ATTK_HIT; if (DEADMONSTER(mdef)) - res[i] = MM_DEF_DIED; + res[i] = M_ATTK_DEF_DIED; if (DEADMONSTER(magr)) - res[i] |= MM_AGR_DIED; + res[i] |= M_ATTK_AGR_DIED; break; } if (magr->weapon_check == NEED_WEAPON || !MON_WEP(magr)) { magr->weapon_check = NEED_HTH_WEAPON; if (mon_wield_item(magr) != 0) - return 0; + return M_ATTK_MISS; } possibly_unwield(magr, FALSE); - otmp = MON_WEP(magr); - - if (otmp) { - if (vis) - mswingsm(magr, mdef, otmp); - tmp += hitval(otmp, mdef); + if ((mwep = MON_WEP(magr)) != 0) { + if (gv.vis) + mswingsm(magr, mdef, mwep); + tmp += hitval(mwep, mdef); } + FALLTHROUGH; /*FALLTHRU*/ case AT_CLAW: case AT_KICK: @@ -387,6 +423,8 @@ register struct monst *magr, *mdef; case AT_TUCH: case AT_BUTT: case AT_TENT: + if (mattk->aatyp == AT_KICK && mtrapped_in_pit(magr)) + continue; /* Nymph that teleported away on first attack? */ if (distmin(magr->mx, magr->my, mdef->mx, mdef->my) > 1) /* Continue because the monster may have a ranged attack. */ @@ -395,7 +433,7 @@ register struct monst *magr, *mdef; * have a weapon instead. This instinct doesn't work for * players, or under conflict or confusion. */ - if (!magr->mconf && !Conflict && otmp && mattk->aatyp != AT_WEAP + if (!magr->mconf && !Conflict && mwep && mattk->aatyp != AT_WEAP && touch_petrifies(mdef->data)) { strike = 0; break; @@ -403,26 +441,32 @@ register struct monst *magr, *mdef; dieroll = rnd(20 + i); strike = (tmp > dieroll); /* KMH -- don't accumulate to-hit bonuses */ - if (otmp) - tmp -= hitval(otmp, mdef); + if (mwep) + tmp -= hitval(mwep, mdef); if (strike) { - res[i] = hitmm(magr, mdef, mattk); + /* for eel AT_TUCH+AD_WRAP attack: can't grab an unsolid + target; the unsolid test is redundant since failed_grab + checks it too, but is cheap and avoids calling failed_grab + for ordinary targets */ + if (unsolid(mdef->data) && failed_grab(magr, mdef, mattk)) { + strike = 0; + break; + } + res[i] = hitmm(magr, mdef, mattk, mwep, dieroll); if ((mdef->data == &mons[PM_BLACK_PUDDING] || mdef->data == &mons[PM_BROWN_PUDDING]) - && (otmp && (objects[otmp->otyp].oc_material == IRON - || objects[otmp->otyp].oc_material == METAL)) + && (mwep && (objects[mwep->otyp].oc_material == IRON + || objects[mwep->otyp].oc_material == METAL)) && mdef->mhp > 1 && !mdef->mcan) { struct monst *mclone; if ((mclone = clone_mon(mdef, 0, 0)) != 0) { - if (vis && canspotmon(mdef)) { - char buf[BUFSZ]; - - Strcpy(buf, Monnam(mdef)); - pline("%s divides as %s hits it!", buf, - mon_nam(magr)); - } - mintrap(mclone); + if (gv.vis && canspotmon(mdef)) + pline("%s divides as %s hits it!", + Monnam(mdef), mon_nam(magr)); + (void) mintrap(mclone, NO_TRAP_FLAGS); + if (DEADMONSTER(magr)) + res[i] |= M_ATTK_AGR_DIED; } } } else @@ -430,10 +474,19 @@ register struct monst *magr, *mdef; break; case AT_HUGS: /* automatic if prev two attacks succeed */ - strike = (i >= 2 && res[i - 1] == MM_HIT && res[i - 2] == MM_HIT); - if (strike) - res[i] = hitmm(magr, mdef, mattk); - + strike = (i >= 2 && res[i - 1] == M_ATTK_HIT + && res[i - 2] == M_ATTK_HIT); + if (strike) { + /* note: monsters with hug attacks don't wear cloaks or gloves + so this doesn't need a special case for hugging a shade + while covered by blessed armor (which does damage but does + not achieve a successful hold); likewise, rope golems can't + wield weapons so ability to choke isn't affected by such */ + if (failed_grab(magr, mdef, mattk)) + strike = 0; + else + res[i] = hitmm(magr, mdef, mattk, (struct obj *) 0, 0); + } break; case AT_GAZE: @@ -443,11 +496,11 @@ register struct monst *magr, *mdef; case AT_EXPL: /* D: Prevent explosions from a distance */ - if (distmin(magr->mx,magr->my,mdef->mx,mdef->my) > 1) + if (distmin(magr->mx, magr->my, mdef->mx, mdef->my) > 1) continue; res[i] = explmm(magr, mdef, mattk); - if (res[i] == MM_MISS) { /* cancelled--no attack */ + if (res[i] == M_ATTK_MISS) { /* cancelled--no attack */ strike = 0; attk = 0; } else @@ -456,7 +509,7 @@ register struct monst *magr, *mdef; case AT_ENGL: if (mdef->data == &mons[PM_SHADE]) { /* no silver teeth... */ - if (vis) + if (gv.vis) pline("%s attempt to engulf %s is futile.", s_suffix(Monnam(magr)), mon_nam(mdef)); strike = 0; @@ -470,41 +523,43 @@ register struct monst *magr, *mdef; if (distmin(magr->mx, magr->my, mdef->mx, mdef->my) > 1) continue; /* Engulfing attacks are directed at the hero if possible. -dlc */ - if (u.uswallow && magr == u.ustuck) + if (engulfing_u(magr)) { strike = 0; - else if ((strike = (tmp > rnd(20 + i))) != 0) - res[i] = gulpmm(magr, mdef, mattk); - else + } else if ((strike = (tmp > rnd(20 + i))) != 0) { + if (failed_grab(magr, mdef, mattk)) + strike = 0; /* purple worm can't swallow unsolid mons */ + else + res[i] = gulpmm(magr, mdef, mattk); + } else { missmm(magr, mdef, mattk); - break; - - case AT_BREA: - if (!monnear(magr, mdef->mx, mdef->my)) { - strike = breamm(magr, mattk, mdef); - - /* We don't really know if we hit or not; pretend we did. */ - if (strike) - res[i] |= MM_HIT; - if (DEADMONSTER(mdef)) - res[i] = MM_DEF_DIED; - if (DEADMONSTER(magr)) - res[i] |= MM_AGR_DIED; } - else - strike = 0; break; + case AT_BREA: case AT_SPIT: + /* + * Ranged attacks aren't allowed at point blank range. + * + * That impacts pet use of ranged attacks. It's rather arbitrary + * but various parts of the code assume it to be the case, not to + * mention a part of player tactics when fighting dragons. + */ if (!monnear(magr, mdef->mx, mdef->my)) { - strike = spitmm(magr, mattk, mdef); + int mmtmp = ((mattk->aatyp == AT_BREA) + ? breamm(magr, mattk, mdef) + : spitmm(magr, mattk, mdef)); + strike = (mmtmp == M_ATTK_MISS) ? 0 : 1; /* We don't really know if we hit or not; pretend we did. */ if (strike) - res[i] |= MM_HIT; + res[i] |= M_ATTK_HIT; if (DEADMONSTER(mdef)) - res[i] = MM_DEF_DIED; + res[i] = M_ATTK_DEF_DIED; if (DEADMONSTER(magr)) - res[i] |= MM_AGR_DIED; + res[i] |= M_ATTK_AGR_DIED; + } else { + strike = 0; + attk = 0; } break; @@ -514,103 +569,139 @@ register struct monst *magr, *mdef; break; } - if (attk && !(res[i] & MM_AGR_DIED) + if (attk && !(res[i] & M_ATTK_AGR_DIED) && distmin(magr->mx, magr->my, mdef->mx, mdef->my) <= 1) - res[i] = passivemm(magr, mdef, strike, res[i] & MM_DEF_DIED); + res[i] = passivemm(magr, mdef, strike, + (res[i] & M_ATTK_DEF_DIED), mwep); - if (res[i] & MM_DEF_DIED) + if (res[i] & M_ATTK_DEF_DIED) return res[i]; - if (res[i] & MM_AGR_DIED) + if (res[i] & M_ATTK_AGR_DIED) return res[i]; /* return if aggressor can no longer attack */ - if (!magr->mcanmove || magr->msleeping) + if ((res[i] & M_ATTK_AGR_DONE) || helpless(magr)) + return res[i]; + /* eg. defender was knocked into a level teleport trap */ + if (mon_offmap(mdef)) return res[i]; - if (res[i] & MM_HIT) + if (res[i] & M_ATTK_HIT) struck = 1; /* at least one hit */ - } + } /* for (;i < NATTK;) loop */ + + return (struck ? M_ATTK_HIT : M_ATTK_MISS); +} - return (struck ? MM_HIT : MM_MISS); +/* can't hold an unsolid target (ghosts, lights, vortices, most elementals) + or a long worm tail */ +boolean +failed_grab( + struct monst *magr, + struct monst *mdef, + struct attack *mattk) +{ + if ((unsolid(mdef->data) || gn.notonhead) + /* hug attack: most holders (owlbear, python, pit fiend, &c); + wrap damage: eel grabbing, trapper/lurker-above engulfing; + stick-to damage: mimic, lichen; + digestion damage: purple worm swallowing */ + && (mattk->aatyp == AT_HUGS || mattk->adtyp == AD_WRAP + || mattk->adtyp == AD_STCK || mattk->adtyp == AD_DGST)) { + if ((gv.vis && canspotmon(mdef)) /* mon-vs-mon */ + || magr == &gy.youmonst || mdef == &gy.youmonst) { + char magrnam[BUFSZ], mdefnam[BUFSZ]; + boolean tailmiss = gn.notonhead; + const char *verb = (mattk->adtyp == AD_DGST) ? "gulp" + : (mattk->adtyp == AD_STCK) ? "adhere" + : "grab"; + + /* beware of "Foo's grab passes through Bar's ghost"; + mon_nam(x_monnam) calls s_suffix() for named ghosts and + s_suffix() uses a single static buffer; make copies of both + names to overcome that [note: comment predates 'tailmiss'] */ + Strcpy(magrnam, (magr == &gy.youmonst) ? "Your" + : s_suffix(Monnam(magr))); + if (!tailmiss) { + Strcpy(mdefnam, (mdef == &gy.youmonst) ? "you" + : mon_nam(mdef)); + } else { + /* hero poly'd into long worm can't grow tail + so no 'youmonst' handling is needed here */ + Sprintf(mdefnam, "%s tail", s_suffix(some_mon_nam(mdef))); + } + /* unsolid grab misses are actually somewhat iffy--how come + ordinary attacks don't also pass right through? */ + pline("%.99s %s attempt %s %.99s!", magrnam, verb, + !tailmiss ? "passes right through" : "fails to hold", + mdefnam); + } + return TRUE; + } + return FALSE; } /* Returns the result of mdamagem(). */ -STATIC_OVL int -hitmm(magr, mdef, mattk) -register struct monst *magr, *mdef; -struct attack *mattk; +staticfn int +hitmm( + struct monst *magr, + struct monst *mdef, + struct attack *mattk, + struct obj *mwep, + int dieroll) { - boolean weaponhit = ((mattk->aatyp == AT_WEAP - || (mattk->aatyp == AT_CLAW && otmp))), - silverhit = (weaponhit && otmp - && objects[otmp->otyp].oc_material == SILVER), - showit = FALSE; + int compat; + boolean weaponhit = (mattk->aatyp == AT_WEAP + || (mattk->aatyp == AT_CLAW && mwep)), + silverhit = (weaponhit && mwep + && objects[mwep->otyp].oc_material == SILVER); - /* unhiding or unmimicking happens even if hero can't see it - because the formerly concealed monster is now in action */ - if (M_AP_TYPE(mdef)) { - seemimic(mdef); - showit |= vis; - } else if (mdef->mundetected) { - mdef->mundetected = 0; - showit |= vis; - } - if (M_AP_TYPE(magr)) { - seemimic(magr); - showit |= vis; - } else if (magr->mundetected) { - magr->mundetected = 0; - showit |= vis; - } + pre_mm_attack(magr, mdef); - if (vis) { - int compat; - char buf[BUFSZ]; + compat = !magr->mcan ? could_seduce(magr, mdef, mattk) : 0; + if (!compat && shade_miss(magr, mdef, mwep, FALSE, gv.vis)) + return M_ATTK_MISS; /* bypass mdamagem() */ - if (!canspotmon(magr)) - map_invisible(magr->mx, magr->my); - else if (showit) - newsym(magr->mx, magr->my); - if (!canspotmon(mdef)) - map_invisible(mdef->mx, mdef->my); - else if (showit) - newsym(mdef->mx, mdef->my); + if (gv.vis) { + char buf[BUFSZ], magr_name[BUFSZ]; - if ((compat = could_seduce(magr, mdef, mattk)) && !magr->mcan) { - Sprintf(buf, "%s %s", Monnam(magr), + Strcpy(magr_name, Monnam(magr)); + if (compat) { + Snprintf(buf, sizeof buf, "%s %s", magr_name, mdef->mcansee ? "smiles at" : "talks to"); pline("%s %s %s.", buf, mon_nam(mdef), - compat == 2 ? "engagingly" : "seductively"); - } else if (shade_miss(magr, mdef, otmp, FALSE, TRUE)) { - return MM_MISS; /* bypass mdamagem() */ + (compat == 2) ? "engagingly" : "seductively"); } else { - char magr_name[BUFSZ]; - - Strcpy(magr_name, Monnam(magr)); + buf[0] = '\0'; switch (mattk->aatyp) { case AT_BITE: - Sprintf(buf, "%s bites", magr_name); + Snprintf(buf, sizeof buf, "%s bites", magr_name); break; case AT_STNG: - Sprintf(buf, "%s stings", magr_name); + Snprintf(buf, sizeof buf, "%s stings", magr_name); break; case AT_BUTT: - Sprintf(buf, "%s butts", magr_name); + Snprintf(buf, sizeof buf, "%s butts", magr_name); break; case AT_TUCH: - Sprintf(buf, "%s touches", magr_name); + Snprintf(buf, sizeof buf, "%s touches", magr_name); break; case AT_TENT: - Sprintf(buf, "%s tentacles suck", s_suffix(magr_name)); + Snprintf(buf, sizeof buf, "%s tentacles suck", + s_suffix(magr_name)); break; case AT_HUGS: if (magr != u.ustuck) { - Sprintf(buf, "%s squeezes", magr_name); + Snprintf(buf, sizeof buf, "%s squeezes", magr_name); break; } + FALLTHROUGH; /*FALLTHRU*/ default: - Sprintf(buf, "%s hits", magr_name); + if (!weaponhit || !mwep || !mwep->oartifact) + Snprintf(buf, sizeof buf, "%s hits", magr_name); + break; } - pline("%s %s.", buf, mon_nam_too(mdef, magr)); + if (*buf) + pline("%s %s.", buf, mon_nam_too(mdef, magr)); if (mon_hates_silver(mdef) && silverhit) { char *mdef_name = mon_nam_too(mdef, magr); @@ -630,38 +721,46 @@ struct attack *mattk; Strcat(mdef_name, " flesh"); } - pline("%s %s sears %s!", magr_name, /*s_suffix(magr_name), */ - simpleonames(otmp), mdef_name); + pline("%s %s sears %s!", magr_name, /* s_suffix(magr_name), */ + simpleonames(mwep), mdef_name); } } } else noises(magr, mattk); - return mdamagem(magr, mdef, mattk); + return mdamagem(magr, mdef, mattk, mwep, dieroll); } /* Returns the same values as mdamagem(). */ -STATIC_OVL int -gazemm(magr, mdef, mattk) -register struct monst *magr, *mdef; -struct attack *mattk; +staticfn int +gazemm(struct monst *magr, struct monst *mdef, struct attack *mattk) { char buf[BUFSZ]; + /* an Archon's gaze affects target even if Archon itself is blinded */ + boolean archon = (magr->data == &mons[PM_ARCHON] + && mattk->adtyp == AD_BLND), + altmesg = (archon && !magr->mcansee); + + /* bring target out of hiding even if hero doesn't see it happen (this + is already done in pre_mm_attack() and shouldn't be needed here) */ + if (mdef->data->mlet == S_MIMIC && M_AP_TYPE(mdef) != M_AP_NOTHING) + seemimic(mdef); + mdef->mundetected = 0; - if (vis) { - if (mdef->data->mlet == S_MIMIC - && M_AP_TYPE(mdef) != M_AP_NOTHING) - seemimic(mdef); - Sprintf(buf, "%s gazes at", Monnam(magr)); + if (gv.vis) { + Sprintf(buf, "%s gazes %s", + altmesg ? Adjmonnam(magr, "blinded") : Monnam(magr), + altmesg ? "toward" : "at"); pline("%s %s...", buf, canspotmon(mdef) ? mon_nam(mdef) : "something"); } - if (magr->mcan || !magr->mcansee || !mdef->mcansee + if (magr->mcan || !mdef->mcansee + || (archon ? resists_blnd(mdef) : !magr->mcansee) || (magr->minvis && !perceives(mdef->data)) || mdef->msleeping) { - if (vis && canspotmon(mdef)) + if (gv.vis && canspotmon(mdef)) pline("but nothing happens."); - return MM_MISS; + return M_ATTK_MISS; } /* call mon_reflects 2x, first test, then, if visible, print message */ if (magr->data == &mons[PM_MEDUSA] && mon_reflects(mdef, (char *) 0)) { @@ -672,7 +771,7 @@ struct attack *mattk; if (canseemon(magr)) (void) mon_reflects(magr, "The gaze is reflected away by %s %s."); - return MM_MISS; + return M_ATTK_MISS; } if (mdef->minvis && !perceives(magr->data)) { if (canseemon(magr)) { @@ -680,87 +779,112 @@ struct attack *mattk; "%s doesn't seem to notice that %s gaze was reflected.", Monnam(magr), mhis(magr)); } - return MM_MISS; + return M_ATTK_MISS; } if (canseemon(magr)) - pline("%s is turned to stone!", Monnam(magr)); + pline_mon(magr, "%s is turned to stone!", Monnam(magr)); monstone(magr); if (!DEADMONSTER(magr)) - return MM_MISS; - return MM_AGR_DIED; + return M_ATTK_MISS; + return M_ATTK_AGR_DIED; } + } else if (archon) { + mhitm_ad_blnd(magr, mattk, mdef, (struct mhitm_data *) 0); + /* an Archon's blinding radiance also stuns; + this is different from the way the hero gets stunned because + a stunned monster recovers randomly instead of via countdown; + both cases make an effort to prevent the target from being + continuously stunned due to repeated gaze attacks */ + if (rn2(2)) + mdef->mstun = 1; } - return mdamagem(magr, mdef, mattk); + return mdamagem(magr, mdef, mattk, (struct obj *) 0, 0); } /* return True if magr is allowed to swallow mdef, False otherwise */ boolean -engulf_target(magr, mdef) -struct monst *magr, *mdef; +engulf_target(struct monst *magr, struct monst *mdef) { struct rm *lev; - int dx, dy; + int ax, ay, dx, dy; + boolean uatk = (magr == &gy.youmonst), + udef = (mdef == &gy.youmonst); /* can't swallow something that's too big */ - if (mdef->data->msize >= MZ_HUGE) + if (mdef->data->msize >= MZ_HUGE + || (magr->data->msize < mdef->data->msize && !is_whirly(magr->data))) return FALSE; - /* (hypothetical) engulfers who can pass through walls aren't - limited by rock|trees|bars */ - if ((magr == &youmonst) ? Passes_walls : passes_walls(magr->data)) - return TRUE; + /* can't (move to) swallow if trapped. TODO: could do some? */ + if (mdef->mtrapped || magr->mtrapped) + return FALSE; - /* don't swallow something in a spot where attacker wouldn't - otherwise be able to move onto; we don't want to engulf - a wall-phaser and end up with a non-phaser inside a wall */ - dx = mdef->mx, dy = mdef->my; - if (mdef == &youmonst) - dx = u.ux, dy = u.uy; + /* if attacker is phasing in solid rock and defender can't move there, + or vice versa, don't allow engulf to succeed; otherwise expelling + might not be able to place attacker and defender both back on map; + when defender is the hero, a sanity_check complaint about placing + the hero on top of a monster can occur */ + dx = (mdef == &gy.youmonst) ? u.ux : mdef->mx; + dy = (mdef == &gy.youmonst) ? u.uy : mdef->my; lev = &levl[dx][dy]; - if (IS_ROCK(lev->typ) || closed_door(dx, dy) || IS_TREE(lev->typ) - /* not passes_bars(); engulfer isn't squeezing through */ - || (lev->typ == IRONBARS && !is_whirly(magr->data))) + if (!(udef ? Passes_walls : passes_walls(mdef->data)) + && (IS_OBSTRUCTED(lev->typ) || closed_door(dx, dy) || IS_TREE(lev->typ) + /* not passes_bars(); engulfer isn't squeezing through */ + || (lev->typ == IRONBARS && !is_whirly(magr->data)))) + return FALSE; + ax = (magr == &gy.youmonst) ? u.ux : magr->mx; + ay = (magr == &gy.youmonst) ? u.uy : magr->my; + lev = &levl[ax][ay]; + if (!(uatk ? Passes_walls : passes_walls(magr->data)) + && (IS_OBSTRUCTED(lev->typ) || closed_door(ax, ay) || IS_TREE(lev->typ) + || (lev->typ == IRONBARS && !is_whirly(mdef->data)))) return FALSE; return TRUE; } /* Returns the same values as mattackm(). */ -STATIC_OVL int -gulpmm(magr, mdef, mattk) -register struct monst *magr, *mdef; -register struct attack *mattk; +staticfn int +gulpmm( + struct monst *magr, + struct monst *mdef, + struct attack *mattk) { - xchar ax, ay, dx, dy; + coordxy ax, ay, dx, dy; int status; - char buf[BUFSZ]; struct obj *obj; if (!engulf_target(magr, mdef)) - return MM_MISS; - - if (vis) { - /* [this two-part formatting dates back to when only one x_monnam - result could be included in an expression because the next one - would overwrite first's result -- that's no longer the case] */ - Sprintf(buf, "%s swallows", Monnam(magr)); - pline("%s %s.", buf, mon_nam(mdef)); + return M_ATTK_MISS; + + if (gv.vis) { + pline("%s %s %s.", Monnam(magr), + digests(magr->data) ? "swallows" + : enfolds(magr->data) ? "encloses" + : "engulfs", + mon_nam(mdef)); + } + if (!flaming(magr->data)) { + for (obj = mdef->minvent; obj; obj = obj->nobj) + (void) snuff_lit(obj); } - for (obj = mdef->minvent; obj; obj = obj->nobj) - (void) snuff_lit(obj); if (is_vampshifter(mdef) - && newcham(mdef, &mons[mdef->cham], FALSE, FALSE)) { - if (vis) { + && newcham(mdef, &mons[mdef->cham], NO_NC_FLAGS)) { + if (gv.vis) { /* 'it' -- previous form is no longer available and using that would be excessively verbose */ pline("%s expels %s.", Monnam(magr), canspotmon(mdef) ? "it" : something); - if (canspotmon(mdef)) - pline("It turns into %s.", a_monnam(mdef)); + if (canspotmon(mdef)) { + pline("It turns into %s.", + x_monnam(mdef, ARTICLE_A, (char *) 0, + (SUPPRESS_NAME | SUPPRESS_IT + | SUPPRESS_INVISIBLE), FALSE)); + } } - return MM_HIT; /* bypass mdamagem() */ + return M_ATTK_HIT; /* bypass mdamagem() */ } /* @@ -772,7 +896,7 @@ register struct attack *mattk; dx = mdef->mx; dy = mdef->my; /* - * Leave the defender in the monster chain at it's current position, + * Leave the defender in the monster chain at its current position, * but don't leave it on the screen. Move the aggressor to the * defender's position. */ @@ -782,28 +906,55 @@ register struct attack *mattk; newsym(ax, ay); /* erase old position */ newsym(dx, dy); /* update new position */ - status = mdamagem(magr, mdef, mattk); + gm.mswallower = magr; /* corpse_chance() wants this */ + status = mdamagem(magr, mdef, mattk, (struct obj *) 0, 0); + gm.mswallower = (struct monst *) 0; /* reset */ - if ((status & (MM_AGR_DIED | MM_DEF_DIED)) - == (MM_AGR_DIED | MM_DEF_DIED)) { + if ((status & (M_ATTK_AGR_DIED | M_ATTK_DEF_DIED)) + == (M_ATTK_AGR_DIED | M_ATTK_DEF_DIED)) { ; /* both died -- do nothing */ - } else if (status & MM_DEF_DIED) { /* defender died */ + } else if (status & M_ATTK_DEF_DIED) { /* defender died */ /* - * Note: remove_monster() was called in relmon(), wiping out - * magr from level.monsters[mdef->mx][mdef->my]. We need to - * put it back and display it. -kd + * Note: mdamagem() -> monkilled() -> mondead() -> m_detach() + * -> relmon() used to call remove_monster() for the dead + * monster even when it wasn't the one on the map, so we + * needed to put magr back after mdef was killed and removed + * from their shared spot. But now [5.0] relmon() calls + * mon_leaving_level() and that checks whether the monster at + * dying monster's coordinates is that dying monster and only + * removes it when they match. So magr is still at mdef's + * former spot these days. + * + * We still potentially do one fixup: if the gulp targeted + * an inhospitable location, magr will return to its previous + * spot instead of staying. */ - place_monster(magr, dx, dy); - newsym(dx, dy); + if (!goodpos(dx, dy, magr, MM_IGNOREWATER)) { + if (m_at(dx, dy) == magr) { + remove_monster(dx, dy); + newsym(dx, dy); + } + dx = ax, dy = ay; /* magr's spot at start of the attack */ + } + if (m_at(dx, dy) != magr) { + place_monster(magr, dx, dy); + newsym(dx, dy); + } /* aggressor moves to and might encounter trouble there */ - if (minliquid(magr) || (t_at(dx, dy) && mintrap(magr) == 2)) - status |= MM_AGR_DIED; - } else if (status & MM_AGR_DIED) { /* aggressor died */ + if (minliquid(magr) + || (t_at(dx, dy) + && mintrap(magr, NO_TRAP_FLAGS) == Trap_Killed_Mon)) + status |= M_ATTK_AGR_DIED; + } else if (status & M_ATTK_AGR_DIED) { /* aggressor died */ place_monster(mdef, dx, dy); newsym(dx, dy); } else { /* both alive, put them back */ - if (cansee(dx, dy)) - pline("%s is regurgitated!", Monnam(mdef)); + if (cansee(dx, dy)) { + pline("%s is %s!", Monnam(mdef), + digests(magr->data) ? "regurgitated" + : enfolds(magr->data) ? "released" + : "expelled"); + } remove_monster(dx,dy); place_monster(magr, ax, ay); @@ -815,31 +966,37 @@ register struct attack *mattk; return status; } -STATIC_OVL int -explmm(magr, mdef, mattk) -struct monst *magr, *mdef; -struct attack *mattk; +staticfn int +explmm(struct monst *magr, struct monst *mdef, struct attack *mattk) { int result; if (magr->mcan) - return MM_MISS; + return M_ATTK_MISS; if (cansee(magr->mx, magr->my)) - pline("%s explodes!", Monnam(magr)); + pline_mon(magr, "%s explodes!", Monnam(magr)); else noises(magr, mattk); - result = mdamagem(magr, mdef, mattk); + /* monster explosion types which actually create an explosion */ + if (mattk->adtyp == AD_FIRE || mattk->adtyp == AD_COLD + || mattk->adtyp == AD_ELEC) { + mon_explodes(magr, mattk); + /* unconditionally set AGR_DIED here; lifesaving is accounted below */ + result = M_ATTK_AGR_DIED | (DEADMONSTER(mdef) ? M_ATTK_DEF_DIED : 0); + } else { + result = mdamagem(magr, mdef, mattk, (struct obj *) 0, 0); + } /* Kill off aggressor if it didn't die. */ - if (!(result & MM_AGR_DIED)) { + if (!(result & M_ATTK_AGR_DIED)) { boolean was_leashed = (magr->mleashed != 0); mondead(magr); if (!DEADMONSTER(magr)) return result; /* life saved */ - result |= MM_AGR_DIED; + result |= M_ATTK_AGR_DIED; /* mondead() -> m_detach() -> m_unleash() always suppresses the m_unleash() slack message, so deliver it here instead */ @@ -855,18 +1012,22 @@ struct attack *mattk; /* * See comment at top of mattackm(), for return values. */ -STATIC_OVL int -mdamagem(magr, mdef, mattk) -register struct monst *magr, *mdef; -register struct attack *mattk; +staticfn int +mdamagem( + struct monst *magr, + struct monst *mdef, + struct attack *mattk, + struct obj *mwep, + int dieroll) { - struct obj *obj; - char buf[BUFSZ]; struct permonst *pa = magr->data, *pd = mdef->data; - int armpro, num, - tmp = d((int) mattk->damn, (int) mattk->damd), - res = MM_MISS; - boolean cancelled; + struct mhitm_data mhm; + mhm.damage = d((int) mattk->damn, (int) mattk->damd); + mhm.hitflags = M_ATTK_MISS; + mhm.permdmg = 0; + mhm.specialdmg = 0; + mhm.dieroll = dieroll; + mhm.done = FALSE; if ((touch_petrifies(pd) /* or flesh_petrifies() */ || (mattk->adtyp == AD_DGST && pd == &mons[PM_MEDUSA])) @@ -875,623 +1036,178 @@ register struct attack *mattk; wornitems = magr->misc_worn_check; /* wielded weapon gives same protection as gloves here */ - if (otmp != 0) + if (mwep) wornitems |= W_ARMG; if (protector == 0L || (protector != ~0L && (wornitems & protector) != protector)) { if (poly_when_stoned(pa)) { mon_to_stone(magr); - return MM_HIT; /* no damage during the polymorph */ + return M_ATTK_HIT; /* no damage during the polymorph */ } - if (vis && canspotmon(magr)) - pline("%s turns to stone!", Monnam(magr)); + if (gv.vis && canspotmon(magr)) + pline_mon(magr, "%s turns to stone!", Monnam(magr)); monstone(magr); if (!DEADMONSTER(magr)) - return MM_HIT; /* lifesaved */ - else if (magr->mtame && !vis) + return M_ATTK_HIT; /* lifesaved */ + else if (magr->mtame && !gv.vis) You(brief_feeling, "peculiarly sad"); - return MM_AGR_DIED; + return M_ATTK_AGR_DIED; } } - /* cancellation factor is the same as when attacking the hero */ - armpro = magic_negation(mdef); - cancelled = magr->mcan || !(rn2(10) >= 3 * armpro); - - switch (mattk->adtyp) { - case AD_DGST: - /* eating a Rider or its corpse is fatal */ - if (is_rider(pd)) { - if (vis && canseemon(magr)) - pline("%s %s!", Monnam(magr), - (pd == &mons[PM_FAMINE]) - ? "belches feebly, shrivels up and dies" - : (pd == &mons[PM_PESTILENCE]) - ? "coughs spasmodically and collapses" - : "vomits violently and drops dead"); - mondied(magr); - if (!DEADMONSTER(magr)) - return 0; /* lifesaved */ - else if (magr->mtame && !vis) - You(brief_feeling, "queasy"); - return MM_AGR_DIED; - } - if (flags.verbose && !Deaf) - verbalize("Burrrrp!"); - tmp = mdef->mhp; - /* Use up amulet of life saving */ - if (!!(obj = mlifesaver(mdef))) - m_useup(mdef, obj); - - /* Is a corpse for nutrition possible? It may kill magr */ - if (!corpse_chance(mdef, magr, TRUE) || DEADMONSTER(magr)) - break; + mhitm_adtyping(magr, mattk, mdef, &mhm); - /* Pets get nutrition from swallowing monster whole. - * No nutrition from G_NOCORPSE monster, eg, undead. - * DGST monsters don't die from undead corpses - */ - num = monsndx(pd); - if (magr->mtame && !magr->isminion - && !(mvitals[num].mvflags & G_NOCORPSE)) { - struct obj *virtualcorpse = mksobj(CORPSE, FALSE, FALSE); - int nutrit; - - set_corpsenm(virtualcorpse, num); - nutrit = dog_nutrition(magr, virtualcorpse); - dealloc_obj(virtualcorpse); - - /* only 50% nutrition, 25% of normal eating time */ - if (magr->meating > 1) - magr->meating = (magr->meating + 3) / 4; - if (nutrit > 1) - nutrit /= 2; - EDOG(magr)->hungrytime += nutrit; - } - break; - case AD_STUN: - if (magr->mcan) - break; - if (canseemon(mdef)) - pline("%s %s for a moment.", Monnam(mdef), - makeplural(stagger(pd, "stagger"))); - mdef->mstun = 1; - goto physical; - case AD_LEGS: - if (magr->mcan) { - tmp = 0; - break; - } - goto physical; - case AD_WERE: - case AD_HEAL: - case AD_PHYS: - physical: - obj = (mattk->aatyp == AT_WEAP || mattk->aatyp == AT_CLAW) ? otmp : 0; - if (shade_miss(magr, mdef, obj, FALSE, TRUE)) { - tmp = 0; - } else if (mattk->aatyp == AT_KICK && thick_skinned(pd)) { - tmp = 0; - } else if (mattk->aatyp == AT_WEAP - || (mattk->aatyp == AT_CLAW && otmp)) { - if (otmp) { - struct obj *marmg; - - if (otmp->otyp == CORPSE - && touch_petrifies(&mons[otmp->corpsenm])) - goto do_stone; - - tmp += dmgval(otmp, mdef); - if ((marmg = which_armor(magr, W_ARMG)) != 0 - && marmg->otyp == GAUNTLETS_OF_POWER) - tmp += rn1(4, 3); /* 3..6 */ - if (tmp < 1) /* is this necessary? mhitu.c has it... */ - tmp = 1; - if (otmp->oartifact) { - (void) artifact_hit(magr, mdef, otmp, &tmp, dieroll); - if (DEADMONSTER(mdef)) - return (MM_DEF_DIED - | (grow_up(magr, mdef) ? 0 : MM_AGR_DIED)); - } - if (tmp) - rustm(mdef, otmp); - } - } else if (pa == &mons[PM_PURPLE_WORM] && pd == &mons[PM_SHRIEKER]) { - /* hack to enhance mm_aggression(); we don't want purple - worm's bite attack to kill a shrieker because then it - won't swallow the corpse; but if the target survives, - the subsequent engulf attack should accomplish that */ - if (tmp >= mdef->mhp && mdef->mhp > 1) - tmp = mdef->mhp - 1; - } - break; - case AD_FIRE: - if (cancelled) { - tmp = 0; - break; - } - if (vis && canseemon(mdef)) - pline("%s is %s!", Monnam(mdef), on_fire(pd, mattk)); - if (completelyburns(pd)) { /* paper golem or straw golem */ - if (vis && canseemon(mdef)) - pline("%s burns completely!", Monnam(mdef)); - mondead(mdef); /* was mondied() but that dropped paper scrolls */ - if (!DEADMONSTER(mdef)) - return 0; - else if (mdef->mtame && !vis) - pline("May %s roast in peace.", mon_nam(mdef)); - return (MM_DEF_DIED | (grow_up(magr, mdef) ? 0 : MM_AGR_DIED)); - } - tmp += destroy_mitem(mdef, SCROLL_CLASS, AD_FIRE); - tmp += destroy_mitem(mdef, SPBOOK_CLASS, AD_FIRE); - if (resists_fire(mdef)) { - if (vis && canseemon(mdef)) - pline_The("fire doesn't seem to burn %s!", mon_nam(mdef)); - shieldeff(mdef->mx, mdef->my); - golemeffects(mdef, AD_FIRE, tmp); - tmp = 0; - } - /* only potions damage resistant players in destroy_item */ - tmp += destroy_mitem(mdef, POTION_CLASS, AD_FIRE); - break; - case AD_COLD: - if (cancelled) { - tmp = 0; - break; - } - if (vis && canseemon(mdef)) - pline("%s is covered in frost!", Monnam(mdef)); - if (resists_cold(mdef)) { - if (vis && canseemon(mdef)) - pline_The("frost doesn't seem to chill %s!", mon_nam(mdef)); - shieldeff(mdef->mx, mdef->my); - golemeffects(mdef, AD_COLD, tmp); - tmp = 0; - } - tmp += destroy_mitem(mdef, POTION_CLASS, AD_COLD); - break; - case AD_ELEC: - if (cancelled) { - tmp = 0; - break; - } - if (vis && canseemon(mdef)) - pline("%s gets zapped!", Monnam(mdef)); - tmp += destroy_mitem(mdef, WAND_CLASS, AD_ELEC); - if (resists_elec(mdef)) { - if (vis && canseemon(mdef)) - pline_The("zap doesn't shock %s!", mon_nam(mdef)); - shieldeff(mdef->mx, mdef->my); - golemeffects(mdef, AD_ELEC, tmp); - tmp = 0; - } - /* only rings damage resistant players in destroy_item */ - tmp += destroy_mitem(mdef, RING_CLASS, AD_ELEC); - break; - case AD_ACID: - if (magr->mcan) { - tmp = 0; - break; - } - if (resists_acid(mdef)) { - if (vis && canseemon(mdef)) - pline("%s is covered in %s, but it seems harmless.", - Monnam(mdef), hliquid("acid")); - tmp = 0; - } else if (vis && canseemon(mdef)) { - pline("%s is covered in %s!", Monnam(mdef), hliquid("acid")); - pline("It burns %s!", mon_nam(mdef)); - } - if (!rn2(30)) - erode_armor(mdef, ERODE_CORRODE); - if (!rn2(6)) - acid_damage(MON_WEP(mdef)); - break; - case AD_RUST: - if (magr->mcan) - break; - if (pd == &mons[PM_IRON_GOLEM]) { - if (vis && canseemon(mdef)) - pline("%s falls to pieces!", Monnam(mdef)); - mondied(mdef); - if (!DEADMONSTER(mdef)) - return 0; - else if (mdef->mtame && !vis) - pline("May %s rust in peace.", mon_nam(mdef)); - return (MM_DEF_DIED | (grow_up(magr, mdef) ? 0 : MM_AGR_DIED)); - } - erode_armor(mdef, ERODE_RUST); - mdef->mstrategy &= ~STRAT_WAITFORU; - tmp = 0; - break; - case AD_CORR: - if (magr->mcan) - break; - erode_armor(mdef, ERODE_CORRODE); - mdef->mstrategy &= ~STRAT_WAITFORU; - tmp = 0; - break; - case AD_DCAY: - if (magr->mcan) - break; - if (pd == &mons[PM_WOOD_GOLEM] || pd == &mons[PM_LEATHER_GOLEM]) { - if (vis && canseemon(mdef)) - pline("%s falls to pieces!", Monnam(mdef)); - mondied(mdef); - if (!DEADMONSTER(mdef)) - return 0; - else if (mdef->mtame && !vis) - pline("May %s rot in peace.", mon_nam(mdef)); - return (MM_DEF_DIED | (grow_up(magr, mdef) ? 0 : MM_AGR_DIED)); - } - erode_armor(mdef, ERODE_CORRODE); - mdef->mstrategy &= ~STRAT_WAITFORU; - tmp = 0; - break; - case AD_STON: - if (magr->mcan) - break; - do_stone: - /* may die from the acid if it eats a stone-curing corpse */ - if (munstone(mdef, FALSE)) - goto post_stone; - if (poly_when_stoned(pd)) { - mon_to_stone(mdef); - tmp = 0; - break; - } - if (!resists_ston(mdef)) { - if (vis && canseemon(mdef)) - pline("%s turns to stone!", Monnam(mdef)); - monstone(mdef); - post_stone: - if (!DEADMONSTER(mdef)) - return 0; - else if (mdef->mtame && !vis) - You(brief_feeling, "peculiarly sad"); - return (MM_DEF_DIED | (grow_up(magr, mdef) ? 0 : MM_AGR_DIED)); - } - tmp = (mattk->adtyp == AD_STON ? 0 : 1); - break; - case AD_TLPT: - if (!cancelled && tmp < mdef->mhp && !tele_restrict(mdef)) { - char mdef_Monnam[BUFSZ]; - boolean wasseen = canspotmon(mdef); - - /* save the name before monster teleports, otherwise - we'll get "it" in the suddenly disappears message */ - if (vis && wasseen) - Strcpy(mdef_Monnam, Monnam(mdef)); - mdef->mstrategy &= ~STRAT_WAITFORU; - (void) rloc(mdef, TRUE); - if (vis && wasseen && !canspotmon(mdef) && mdef != u.usteed) - pline("%s suddenly disappears!", mdef_Monnam); - if (tmp >= mdef->mhp) { /* see hitmu(mhitu.c) */ - if (mdef->mhp == 1) - ++mdef->mhp; - tmp = mdef->mhp - 1; - } - } - break; - case AD_SLEE: - if (!cancelled && !mdef->msleeping - && sleep_monst(mdef, rnd(10), -1)) { - if (vis && canspotmon(mdef)) { - Strcpy(buf, Monnam(mdef)); - pline("%s is put to sleep by %s.", buf, mon_nam(magr)); - } - mdef->mstrategy &= ~STRAT_WAITFORU; - slept_monst(mdef); - } - break; - case AD_PLYS: - if (!cancelled && mdef->mcanmove) { - if (vis && canspotmon(mdef)) { - Strcpy(buf, Monnam(mdef)); - pline("%s is frozen by %s.", buf, mon_nam(magr)); - } - paralyze_monst(mdef, rnd(10)); - } - break; - case AD_SLOW: - if (!cancelled && mdef->mspeed != MSLOW) { - unsigned int oldspeed = mdef->mspeed; - - mon_adjust_speed(mdef, -1, (struct obj *) 0); - mdef->mstrategy &= ~STRAT_WAITFORU; - if (mdef->mspeed != oldspeed && vis && canspotmon(mdef)) - pline("%s slows down.", Monnam(mdef)); - } - break; - case AD_CONF: - /* Since confusing another monster doesn't have a real time - * limit, setting spec_used would not really be right (though - * we still should check for it). - */ - if (!magr->mcan && !mdef->mconf && !magr->mspec_used) { - if (vis && canseemon(mdef)) - pline("%s looks confused.", Monnam(mdef)); - mdef->mconf = 1; - mdef->mstrategy &= ~STRAT_WAITFORU; - } - break; - case AD_BLND: - if (can_blnd(magr, mdef, mattk->aatyp, (struct obj *) 0)) { - register unsigned rnd_tmp; - - if (vis && mdef->mcansee && canspotmon(mdef)) - pline("%s is blinded.", Monnam(mdef)); - rnd_tmp = d((int) mattk->damn, (int) mattk->damd); - if ((rnd_tmp += mdef->mblinded) > 127) - rnd_tmp = 127; - mdef->mblinded = rnd_tmp; - mdef->mcansee = 0; - mdef->mstrategy &= ~STRAT_WAITFORU; - } - tmp = 0; - break; - case AD_HALU: - if (!magr->mcan && haseyes(pd) && mdef->mcansee) { - if (vis && canseemon(mdef)) - pline("%s looks %sconfused.", Monnam(mdef), - mdef->mconf ? "more " : ""); - mdef->mconf = 1; - mdef->mstrategy &= ~STRAT_WAITFORU; - } - tmp = 0; - break; - case AD_CURS: - if (!night() && (pa == &mons[PM_GREMLIN])) - break; - if (!magr->mcan && !rn2(10)) { - mdef->mcan = 1; /* cancelled regardless of lifesave */ - mdef->mstrategy &= ~STRAT_WAITFORU; - if (is_were(pd) && pd->mlet != S_HUMAN) - were_change(mdef); - if (pd == &mons[PM_CLAY_GOLEM]) { - if (vis && canseemon(mdef)) { - pline("Some writing vanishes from %s head!", - s_suffix(mon_nam(mdef))); - pline("%s is destroyed!", Monnam(mdef)); - } - mondied(mdef); - if (!DEADMONSTER(mdef)) - return 0; - else if (mdef->mtame && !vis) - You(brief_feeling, "strangely sad"); - return (MM_DEF_DIED - | (grow_up(magr, mdef) ? 0 : MM_AGR_DIED)); - } - if (!Deaf) { - if (!vis) - You_hear("laughter."); - else if (canseemon(magr)) - pline("%s chuckles.", Monnam(magr)); - } - } - break; - case AD_SGLD: - tmp = 0; - if (magr->mcan) - break; - /* technically incorrect; no check for stealing gold from - * between mdef's feet... - */ - { - struct obj *gold = findgold(mdef->minvent); + if (mhitm_knockback(magr, mdef, mattk, &mhm.hitflags, + (MON_WEP(magr) != 0)) + && ((mhm.hitflags & (M_ATTK_DEF_DIED | M_ATTK_HIT)) != 0 + || mon_offmap(mdef))) + return mhm.hitflags; - if (!gold) - break; - obj_extract_self(gold); - add_to_minv(magr, gold); - } - mdef->mstrategy &= ~STRAT_WAITFORU; - if (vis && canseemon(mdef)) { - Strcpy(buf, Monnam(magr)); - pline("%s steals some gold from %s.", buf, mon_nam(mdef)); - } - if (!tele_restrict(magr)) { - boolean couldspot = canspotmon(magr); - (void) rloc(magr, TRUE); - if (vis && couldspot && !canspotmon(magr)) - pline("%s suddenly disappears!", buf); - } - break; - case AD_DRLI: - if (!cancelled && !rn2(3) && !resists_drli(mdef)) { - tmp = d(2, 6); - if (vis && canspotmon(mdef)) - pline("%s suddenly seems weaker!", Monnam(mdef)); - mdef->mhpmax -= tmp; - if (mdef->m_lev == 0) - tmp = mdef->mhp; - else - mdef->m_lev--; - /* Automatic kill if drained past level 0 */ - } - break; - case AD_SSEX: - case AD_SITM: /* for now these are the same */ - case AD_SEDU: - if (magr->mcan) - break; - /* find an object to steal, non-cursed if magr is tame */ - for (obj = mdef->minvent; obj; obj = obj->nobj) - if (!magr->mtame || !obj->cursed) - break; + if (mhm.done) + return mhm.hitflags; - if (obj) { - char onambuf[BUFSZ], mdefnambuf[BUFSZ]; - - /* make a special x_monnam() call that never omits - the saddle, and save it for later messages */ - Strcpy(mdefnambuf, - x_monnam(mdef, ARTICLE_THE, (char *) 0, 0, FALSE)); - - otmp = obj; - if (u.usteed == mdef && otmp == which_armor(mdef, W_SADDLE)) - /* "You can no longer ride ." */ - dismount_steed(DISMOUNT_POLY); - obj_extract_self(otmp); - if (otmp->owornmask) { - mdef->misc_worn_check &= ~otmp->owornmask; - if (otmp->owornmask & W_WEP) - mwepgone(mdef); - otmp->owornmask = 0L; - update_mon_intrinsics(mdef, otmp, FALSE, FALSE); - /* give monster a chance to wear other equipment on its next - move instead of waiting until it picks something up */ - mdef->misc_worn_check |= I_SPECIAL; - } - /* add_to_minv() might free otmp [if it merges] */ - if (vis) - Strcpy(onambuf, doname(otmp)); - (void) add_to_minv(magr, otmp); - if (vis && canseemon(mdef)) { - Strcpy(buf, Monnam(magr)); - pline("%s steals %s from %s!", buf, onambuf, mdefnambuf); - } - possibly_unwield(mdef, FALSE); - mdef->mstrategy &= ~STRAT_WAITFORU; - mselftouch(mdef, (const char *) 0, FALSE); - if (DEADMONSTER(mdef)) - return (MM_DEF_DIED - | (grow_up(magr, mdef) ? 0 : MM_AGR_DIED)); - if (pa->mlet == S_NYMPH && !tele_restrict(magr)) { - boolean couldspot = canspotmon(magr); - - (void) rloc(magr, TRUE); - if (vis && couldspot && !canspotmon(magr)) - pline("%s suddenly disappears!", buf); - } - } - tmp = 0; - break; - case AD_DREN: - if (!cancelled && !rn2(4)) - xdrainenergym(mdef, (boolean) (vis && canspotmon(mdef) - && mattk->aatyp != AT_ENGL)); - tmp = 0; - break; - case AD_DRST: - case AD_DRDX: - case AD_DRCO: - if (!cancelled && !rn2(8)) { - if (vis && canspotmon(magr)) - pline("%s %s was poisoned!", s_suffix(Monnam(magr)), - mpoisons_subj(magr, mattk)); - if (resists_poison(mdef)) { - if (vis && canspotmon(mdef) && canspotmon(magr)) - pline_The("poison doesn't seem to affect %s.", - mon_nam(mdef)); - } else { - if (rn2(10)) - tmp += rn1(10, 6); - else { - if (vis && canspotmon(mdef)) - pline_The("poison was deadly..."); - tmp = mdef->mhp; - } - } - } - break; - case AD_DRIN: - if (notonhead || !has_head(pd)) { - if (vis && canspotmon(mdef)) - pline("%s doesn't seem harmed.", Monnam(mdef)); - /* Not clear what to do for green slimes */ - tmp = 0; - break; - } - if ((mdef->misc_worn_check & W_ARMH) && rn2(8)) { - if (vis && canspotmon(magr) && canseemon(mdef)) { - Strcpy(buf, s_suffix(Monnam(mdef))); - pline("%s helmet blocks %s attack to %s head.", buf, - s_suffix(mon_nam(magr)), mhis(mdef)); - } - break; - } - res = eat_brains(magr, mdef, vis, &tmp); - break; - case AD_SLIM: - if (cancelled) - break; /* physical damage only */ - if (!rn2(4) && !slimeproof(pd)) { - if (!munslime(mdef, FALSE) && !DEADMONSTER(mdef)) { - if (newcham(mdef, &mons[PM_GREEN_SLIME], FALSE, - (boolean) (vis && canseemon(mdef)))) - pd = mdef->data; - mdef->mstrategy &= ~STRAT_WAITFORU; - res = MM_HIT; - } - /* munslime attempt could have been fatal, - potentially to multiple monsters (SCR_FIRE) */ - if (DEADMONSTER(magr)) - res |= MM_AGR_DIED; - if (DEADMONSTER(mdef)) - res |= MM_DEF_DIED; - tmp = 0; - } - break; - case AD_STCK: - if (cancelled) - tmp = 0; - break; - case AD_WRAP: /* monsters cannot grab one another, it's too hard */ - if (magr->mcan) - tmp = 0; - break; - case AD_ENCH: - /* there's no msomearmor() function, so just do damage */ - /* if (cancelled) break; */ - break; - default: - tmp = 0; - break; - } - if (!tmp) - return res; + if (!mhm.damage) + return mhm.hitflags; - if ((mdef->mhp -= tmp) < 1) { + mdef->mhp -= mhm.damage; + if (mdef->mhp < 1) { if (m_at(mdef->mx, mdef->my) == magr) { /* see gulpmm() */ remove_monster(mdef->mx, mdef->my); mdef->mhp = 1; /* otherwise place_monster will complain */ place_monster(mdef, mdef->mx, mdef->my); mdef->mhp = 0; } + if (mattk->aatyp == AT_WEAP || mattk->aatyp == AT_CLAW) + gm.mkcorpstat_norevive = troll_baned(mdef, mwep) ? TRUE : FALSE; + gz.zombify = (!mwep && zombie_maker(magr) + && (mattk->aatyp == AT_TUCH + || mattk->aatyp == AT_CLAW + || mattk->aatyp == AT_BITE) + && zombie_form(mdef->data) != NON_PM); monkilled(mdef, "", (int) mattk->adtyp); + gz.zombify = FALSE; /* reset */ + gm.mkcorpstat_norevive = FALSE; if (!DEADMONSTER(mdef)) - return res; /* mdef lifesaved */ - else if (res == MM_AGR_DIED) - return (MM_DEF_DIED | MM_AGR_DIED); + return mhm.hitflags; /* mdef lifesaved */ + else if (mhm.hitflags == M_ATTK_AGR_DIED) + return (M_ATTK_DEF_DIED | M_ATTK_AGR_DIED); if (mattk->adtyp == AD_DGST) { /* various checks similar to dog_eat and meatobj. * after monkilled() to provide better message ordering */ - if (mdef->cham >= LOW_PM) { - (void) newcham(magr, (struct permonst *) 0, FALSE, TRUE); + if (ismnum(mdef->cham)) { + (void) newcham(magr, (struct permonst *) 0, NC_SHOW_MSG); } else if (pd == &mons[PM_GREEN_SLIME] && !slimeproof(pa)) { - (void) newcham(magr, &mons[PM_GREEN_SLIME], FALSE, TRUE); + (void) newcham(magr, &mons[PM_GREEN_SLIME], NC_SHOW_MSG); } else if (pd == &mons[PM_WRAITH]) { (void) grow_up(magr, (struct monst *) 0); /* don't grow up twice */ - return (MM_DEF_DIED | (!DEADMONSTER(magr) ? 0 : MM_AGR_DIED)); + return (M_ATTK_DEF_DIED + | (!DEADMONSTER(magr) ? 0 : M_ATTK_AGR_DIED)); } else if (pd == &mons[PM_NURSE]) { - magr->mhp = magr->mhpmax; + healmon(magr, magr->mhpmax, 0); } + mon_givit(magr, pd); } /* caveat: above digestion handling doesn't keep `pa' up to date */ - return (MM_DEF_DIED | (grow_up(magr, mdef) ? 0 : MM_AGR_DIED)); + return (M_ATTK_DEF_DIED + | (grow_up(magr, mdef) ? 0 : M_ATTK_AGR_DIED)); } - return (res == MM_AGR_DIED) ? MM_AGR_DIED : MM_HIT; + return (mhm.hitflags == M_ATTK_AGR_DIED) ? M_ATTK_AGR_DIED : M_ATTK_HIT; +} + +int +mon_poly(struct monst *magr, struct monst *mdef, int dmg) +{ + static const char freaky[] = " undergoes a freakish metamorphosis"; + struct permonst *oldform = mdef->data; + + if (mdef == &gy.youmonst) { + if (Antimagic) { + shieldeff(u.ux, u.uy); + } else if (Unchanging) { + ; /* just take a little damage */ + } else { + /* system shock might take place in polyself() */ + if (u.ulycn == NON_PM) { + You("are subjected to a freakish metamorphosis."); + polyself(POLY_NOFLAGS); + } else if (u.umonnum != u.ulycn) { + You_feel("an unnatural urge coming on."); + you_were(); + } else { + You_feel("a natural urge coming on."); + you_unwere(FALSE); + } + dmg = 0; + } + } else { + char Before[BUFSZ]; + + Strcpy(Before, Monnam(mdef)); + if (resists_magm(mdef)) { + /* Magic resistance */ + if (gv.vis) + shieldeff_mon(mdef); + } else if (resist(mdef, WAND_CLASS, 0, TELL)) { + /* general resistance to magic... */ + ; + } else if (!rn2(25) && mdef->cham == NON_PM + && (mdef->mcan + || pm_to_cham(monsndx(mdef->data)) != NON_PM)) { + /* system shock; this variation takes away half of mon's HP + rather than kill outright */ + if (gv.vis) + pline("%s shudders!", Before); + + dmg += (mdef->mhpmax + 1) / 2; + mdef->mhp -= dmg; + dmg = 0; + if (DEADMONSTER(mdef)) { + if (magr == &gy.youmonst) + xkilled(mdef, XKILL_GIVEMSG | XKILL_NOCORPSE); + else + monkilled(mdef, "", AD_RBRE); + } + } else if (newcham(mdef, (struct permonst *) 0, NO_NC_FLAGS)) { + if (gv.vis) { /* either seen or adjacent */ + boolean was_seen = !!strcmpi("It", Before), + verbosely = flags.verbose || !was_seen; + + if (canspotmon(mdef)) + pline("%s%s%s turns into %s.", Before, + verbosely ? freaky : "", verbosely ? " and" : "", + x_monnam(mdef, ARTICLE_A, (char *) 0, + (SUPPRESS_NAME | SUPPRESS_IT + | SUPPRESS_INVISIBLE), FALSE)); + else if (was_seen || magr == &gy.youmonst) + pline("%s%s%s.", Before, freaky, + !was_seen ? "" : " and disappears"); + } + dmg = 0; + if (can_teleport(magr->data)) { + if (magr == &gy.youmonst) + tele(); + else if (!tele_restrict(magr)) + (void) rloc(magr, RLOC_MSG); + } + } else { + if (gv.vis && flags.verbose) + pline1(nothing_happens); + } + } + /* when a transformation has happened, can't attack again for poly + effect during next turn or two; not enforced for poly'd hero */ + if (mdef->data != oldform && magr != &gy.youmonst) + magr->mspec_used += rnd(2); + + return dmg; } void -paralyze_monst(mon, amt) -struct monst *mon; -int amt; +paralyze_monst(struct monst *mon, int amt) { if (amt > 127) amt = 127; @@ -1504,11 +1220,15 @@ int amt; /* `mon' is hit by a sleep attack; return 1 if it's affected, 0 otherwise */ int -sleep_monst(mon, amt, how) -struct monst *mon; -int amt, how; +sleep_monst(struct monst *mon, int amt, int how) { - if (resists_sleep(mon) + /* reveal mimic unless already asleep or paralyzed (won't be 'busy') */ + if (how >= 0 && !mon->msleeping && !mon->mfrozen + && mon->data->mlet == S_MIMIC && (M_AP_TYPE(mon) == M_AP_FURNITURE + || M_AP_TYPE(mon) == M_AP_OBJECT)) + seemimic(mon); + + if (resists_sleep(mon) || defended(mon, AD_SLEE) || (how >= 0 && resist(mon, (char) how, 0, NOTELL))) { shieldeff(mon->mx, mon->my); } else if (mon->mcanmove) { @@ -1527,22 +1247,19 @@ int amt, how; /* sleeping grabber releases, engulfer doesn't; don't use for paralysis! */ void -slept_monst(mon) -struct monst *mon; +slept_monst(struct monst *mon) { - if ((mon->msleeping || !mon->mcanmove) && mon == u.ustuck - && !sticks(youmonst.data) && !u.uswallow) { - pline("%s grip relaxes.", s_suffix(Monnam(mon))); + if (helpless(mon) && mon == u.ustuck + && !sticks(gy.youmonst.data) && !u.uswallow) { + pline_mon(mon, "%s grip relaxes.", s_suffix(Monnam(mon))); unstuck(mon); } } void -rustm(mdef, obj) -struct monst *mdef; -struct obj *obj; +rustm(struct monst *mdef, struct obj *obj) { - int dmgtyp = -1, chance = 1; + int dmgtyp = ERODE_NONE, chance = 1; if (!mdef || !obj) return; /* just in case */ @@ -1558,18 +1275,22 @@ struct obj *obj; chance = 6; } - if (dmgtyp >= 0 && !rn2(chance)) + if (dmgtyp != ERODE_NONE && !rn2(chance)) (void) erode_obj(obj, (char *) 0, dmgtyp, EF_GREASE | EF_VERBOSE); } -STATIC_OVL void -mswingsm(magr, mdef, otemp) -struct monst *magr, *mdef; -struct obj *otemp; +staticfn void +mswingsm( + struct monst *magr, /* attacker */ + struct monst *mdef, /* defender */ + struct obj *otemp) /* attacker's weapon */ { if (flags.verbose && !Blind && mon_visible(magr)) { - pline("%s %s %s%s %s at %s.", Monnam(magr), - (objects[otemp->otyp].oc_dir & PIERCE) ? "thrusts" : "swings", + boolean bash = (is_pole(otemp) && !is_art(otemp, ART_SNICKERSNEE) + && (dist2(magr->mx, magr->my, mdef->mx, mdef->my) + <= 2)); + + pline("%s %s %s%s %s at %s.", Monnam(magr), mswings_verb(otemp, bash), (otemp->quan > 1L) ? "one of " : "", mhis(magr), xname(otemp), mon_nam(mdef)); } @@ -1579,16 +1300,19 @@ struct obj *otemp; * Passive responses by defenders. Does not replicate responses already * handled above. Returns same values as mattackm. */ -STATIC_OVL int -passivemm(magr, mdef, mhit, mdead) -register struct monst *magr, *mdef; -boolean mhit; -int mdead; +staticfn int +passivemm( + struct monst *magr, + struct monst *mdef, + boolean mhitb, + int mdead, + struct obj *mwep) { - register struct permonst *mddat = mdef->data; - register struct permonst *madat = magr->data; + struct permonst *mddat = mdef->data; + struct permonst *madat = magr->data; char buf[BUFSZ]; int i, tmp; + int mhit = mhitb ? M_ATTK_HIT : M_ATTK_MISS; for (i = 0;; i++) { if (i >= NATTK) @@ -1606,7 +1330,7 @@ int mdead; /* These affect the enemy even if defender killed */ switch (mddat->mattk[i].adtyp) { case AD_ACID: - if (mhit && !rn2(2)) { + if (mhitb && !rn2(2)) { Strcpy(buf, Monnam(magr)); if (canseemon(magr)) pline("%s is splashed by %s %s!", buf, @@ -1624,8 +1348,8 @@ int mdead; acid_damage(MON_WEP(magr)); goto assess_dmg; case AD_ENCH: /* KMH -- remove enchantment (disenchanter) */ - if (mhit && !mdef->mcan && otmp) { - (void) drain_item(otmp, FALSE); + if (mhitb && !mdef->mcan && mwep) { + (void) drain_item(mwep, FALSE); /* No message */ } break; @@ -1671,17 +1395,15 @@ int mdead; case AD_COLD: if (resists_cold(magr)) { if (canseemon(magr)) { - pline("%s is mildly chilly.", Monnam(magr)); + pline_mon(magr, "%s is mildly chilly.", Monnam(magr)); golemeffects(magr, AD_COLD, tmp); } tmp = 0; break; } if (canseemon(magr)) - pline("%s is suddenly very cold!", Monnam(magr)); - mdef->mhp += tmp / 2; - if (mdef->mhpmax < mdef->mhp) - mdef->mhpmax = mdef->mhp; + pline_mon(magr, "%s is suddenly very cold!", Monnam(magr)); + healmon(mdef, tmp/2, tmp/2); if (mdef->mhpmax > ((int) (mdef->m_lev + 1) * 8)) (void) split_mon(mdef, magr); break; @@ -1689,7 +1411,7 @@ int mdead; if (!magr->mstun) { magr->mstun = 1; if (canseemon(magr)) - pline("%s %s...", Monnam(magr), + pline_mon(magr, "%s %s...", Monnam(magr), makeplural(stagger(magr->data, "stagger"))); } tmp = 0; @@ -1697,26 +1419,27 @@ int mdead; case AD_FIRE: if (resists_fire(magr)) { if (canseemon(magr)) { - pline("%s is mildly warmed.", Monnam(magr)); + pline_mon(magr, "%s is mildly warmed.", Monnam(magr)); golemeffects(magr, AD_FIRE, tmp); } tmp = 0; break; } if (canseemon(magr)) - pline("%s is suddenly very hot!", Monnam(magr)); + pline_mon(magr, "%s is suddenly very hot!", Monnam(magr)); break; case AD_ELEC: if (resists_elec(magr)) { if (canseemon(magr)) { - pline("%s is mildly tingled.", Monnam(magr)); + pline_mon(magr, "%s is mildly tingled.", Monnam(magr)); golemeffects(magr, AD_ELEC, tmp); } tmp = 0; break; } if (canseemon(magr)) - pline("%s is jolted with electricity!", Monnam(magr)); + pline_mon(magr, "%s is jolted with electricity!", + Monnam(magr)); break; default: tmp = 0; @@ -1728,31 +1451,28 @@ int mdead; assess_dmg: if ((magr->mhp -= tmp) <= 0) { monkilled(magr, "", (int) mddat->mattk[i].adtyp); - return (mdead | mhit | MM_AGR_DIED); + return (mdead | mhit | M_ATTK_AGR_DIED); } return (mdead | mhit); } /* hero or monster has successfully hit target mon with drain energy attack */ void -xdrainenergym(mon, givemsg) -struct monst *mon; -boolean givemsg; +xdrainenergym(struct monst *mon, boolean givemsg) { if (mon->mspec_used < 20 /* limit draining */ && (attacktype(mon->data, AT_MAGC) || attacktype(mon->data, AT_BREA))) { mon->mspec_used += d(2, 2); if (givemsg) - pline("%s seems lethargic.", Monnam(mon)); + pline_mon(mon, "%s seems lethargic.", Monnam(mon)); } } /* "aggressive defense"; what type of armor prevents specified attack from touching its target? */ long -attk_protection(aatyp) -int aatyp; +attk_protection(int aatyp) { long w_mask = 0L; diff --git a/src/mhitu.c b/src/mhitu.c index 849ddb148..178f43eb3 100644 --- a/src/mhitu.c +++ b/src/mhitu.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 mhitu.c $NHDT-Date: 1575245065 2019/12/02 00:04:25 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.168 $ */ +/* NetHack 5.0 mhitu.c $NHDT-Date: 1775259433 2026/04/03 15:37:13 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.341 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2012. */ /* NetHack may be freely redistributed. See license for details. */ @@ -6,116 +6,148 @@ #include "hack.h" #include "artifact.h" -STATIC_VAR NEARDATA struct obj *mon_currwep = (struct obj *) 0; - -STATIC_DCL boolean FDECL(u_slip_free, (struct monst *, struct attack *)); -STATIC_DCL int FDECL(passiveum, (struct permonst *, struct monst *, - struct attack *)); -STATIC_DCL void FDECL(mayberem, (struct monst *, const char *, - struct obj *, const char *)); -STATIC_DCL boolean FDECL(diseasemu, (struct permonst *)); -STATIC_DCL int FDECL(hitmu, (struct monst *, struct attack *)); -STATIC_DCL int FDECL(gulpmu, (struct monst *, struct attack *)); -STATIC_DCL int FDECL(explmu, (struct monst *, struct attack *, BOOLEAN_P)); -STATIC_DCL void FDECL(missmu, (struct monst *, BOOLEAN_P, struct attack *)); -STATIC_DCL void FDECL(mswings, (struct monst *, struct obj *)); -STATIC_DCL void FDECL(wildmiss, (struct monst *, struct attack *)); -STATIC_DCL void FDECL(hitmsg, (struct monst *, struct attack *)); - -/* See comment in mhitm.c. If we use this a lot it probably should be */ -/* changed to a parameter to mhitu. */ -static int dieroll; - -STATIC_OVL void -hitmsg(mtmp, mattk) -struct monst *mtmp; -struct attack *mattk; +static NEARDATA struct obj *mon_currwep = (struct obj *) 0; + +staticfn void missmu(struct monst *, boolean, struct attack *); +staticfn void mswings(struct monst *, struct obj *, boolean); +staticfn void wildmiss(struct monst *, struct attack *); +staticfn void calc_mattacku_vars(struct monst *, boolean *, boolean *, + boolean *, boolean *); +staticfn void summonmu(struct monst *, boolean); +staticfn int hitmu(struct monst *, struct attack *); +staticfn int gulpmu(struct monst *, struct attack *); +staticfn int explmu(struct monst *, struct attack *, boolean); +staticfn void mayberem(struct monst *, const char *, struct obj *, + const char *); +staticfn int assess_dmg(struct monst *, int); +staticfn int passiveum(struct permonst *, struct monst *, struct attack *); + +#define ld() ((yyyymmdd((time_t) 0) - (getyear() * 10000L)) == 0xe5) + +/* monster hits hero (most callers have been moved to uthim.c) */ +void +hitmsg(struct monst *mtmp, struct attack *mattk) { int compat; - const char *pfmt = 0; + const char *verb = 0, *again, *punct = "!"; char *Monst_name = Monnam(mtmp); - /* Note: if opposite gender, "seductively" */ - /* If same gender, "engagingly" for nymph, normal msg for others */ - if ((compat = could_seduce(mtmp, &youmonst, mattk)) != 0 + /* Note: if opposite gender, "seductively"; + if same gender, "engagingly" for nymph, normal msg for others. */ + if ((compat = could_seduce(mtmp, &gy.youmonst, mattk)) != 0 && !mtmp->mcan && !mtmp->mspec_used) { - pline("%s %s you %s.", Monst_name, + pline_mon(mtmp, "%s %s you %s.", Monst_name, !Blind ? "smiles at" : !Deaf ? "talks to" : "touches", (compat == 2) ? "engagingly" : "seductively"); } else { switch (mattk->aatyp) { case AT_BITE: - pfmt = "%s bites!"; + verb = "bites"; break; case AT_KICK: - pline("%s kicks%c", Monst_name, - thick_skinned(youmonst.data) ? '.' : '!'); + if (thick_skinned(gy.youmonst.data)) + punct = "."; + verb = "kicks"; break; case AT_STNG: - pfmt = "%s stings!"; + verb = "stings"; break; case AT_BUTT: - pfmt = "%s butts!"; + verb = "butts"; break; case AT_TUCH: - pfmt = "%s touches you!"; + verb = "touches you"; break; case AT_TENT: - pfmt = "%s tentacles suck you!"; + verb = "tentacles suck your brain"; Monst_name = s_suffix(Monst_name); break; case AT_EXPL: case AT_BOOM: - pfmt = "%s explodes!"; + verb = "explodes"; break; default: - pfmt = "%s hits!"; - } - if (pfmt) - pline(pfmt, Monst_name); + verb = "hits"; + } + /* if a monster hits more than once with similar attack, say so */ + again = (mtmp->m_id == gh.hitmsg_mid + && gh.hitmsg_prev != NULL + && mattk == gh.hitmsg_prev + 1 + && mattk->aatyp == gh.hitmsg_prev->aatyp) ? " again" : ""; + pline_mon(mtmp, "%s %s%s%s", Monst_name, verb, again, punct); } + gh.hitmsg_mid = mtmp->m_id; + gh.hitmsg_prev = mattk; } /* monster missed you */ -STATIC_OVL void -missmu(mtmp, nearmiss, mattk) -struct monst *mtmp; -boolean nearmiss; -struct attack *mattk; +staticfn void +missmu(struct monst *mtmp, boolean nearmiss, struct attack *mattk) { + gh.hitmsg_mid = 0; + gh.hitmsg_prev = NULL; + if (!canspotmon(mtmp)) map_invisible(mtmp->mx, mtmp->my); - if (could_seduce(mtmp, &youmonst, mattk) && !mtmp->mcan) - pline("%s pretends to be friendly.", Monnam(mtmp)); + if (could_seduce(mtmp, &gy.youmonst, mattk) && !mtmp->mcan) + pline_mon(mtmp, "%s pretends to be friendly.", Monnam(mtmp)); else - pline("%s %smisses!", Monnam(mtmp), - (nearmiss && flags.verbose) ? "just " : ""); + pline_mon(mtmp, "%s %smisses!", Monnam(mtmp), + (nearmiss && flags.verbose) ? "just " : ""); stop_occupation(); } +/* strike types P|S|B: Pierce (pointed: stab) => "thrusts", + Slash (edged: slice) or whack (blunt: Bash) => "swings" */ +const char * +mswings_verb( + struct obj *mwep, /* attacker's weapon */ + boolean bash) /* True: using polearm while too close */ +{ + const char *verb; + int otyp = mwep->otyp, + /* (monsters don't actually wield towels, wet or otherwise) */ + lash = (objects[otyp].oc_skill == P_WHIP || is_wet_towel(mwep)), + /* some weapons can have more than one strike type; for those, + give a mix of thrust and swing (caller doesn't care either way) */ + thrust = ((objects[otyp].oc_dir & PIERCE) != 0 + && ((objects[otyp].oc_dir & ~PIERCE) == 0 || !rn2(2))); + + verb = bash ? "bashes with" /*sigh*/ + : lash ? "lashes" + : thrust ? "thrusts" + : "swings"; + /* (might have caller also pass attacker's formatted name so that + if hallucination makes that be plural, we could use vtense() to + adjust the result to match) */ + return verb; +} + /* monster swings obj */ -STATIC_OVL void -mswings(mtmp, otemp) -struct monst *mtmp; -struct obj *otemp; +staticfn void +mswings( + struct monst *mtmp, /* attacker */ + struct obj *otemp, /* attacker's weapon */ + boolean bash) /* True: polearm used at too close range */ { if (flags.verbose && !Blind && mon_visible(mtmp)) { - pline("%s %s %s%s %s.", Monnam(mtmp), - (objects[otemp->otyp].oc_dir & PIERCE) ? "thrusts" : "swings", - (otemp->quan > 1L) ? "one of " : "", mhis(mtmp), xname(otemp)); + pline_mon(mtmp, "%s %s %s%s %s.", Monnam(mtmp), + mswings_verb(otemp, bash), + (otemp->quan > 1L) ? "one of " : "", + mhis(mtmp), xname(otemp)); } } /* return how a poison attack was delivered */ const char * -mpoisons_subj(mtmp, mattk) -struct monst *mtmp; -struct attack *mattk; +mpoisons_subj( + struct monst *mtmp, + struct attack *mattk) { if (mattk->aatyp == AT_WEAP) { - struct obj *mwep = (mtmp == &youmonst) ? uwep : MON_WEP(mtmp); + struct obj *mwep = (mtmp == &gy.youmonst) ? uwep : MON_WEP(mtmp); /* "Foo's attack was poisoned." is pretty lame, but at least it's better than "sting" when not a stinging attack... */ return (!mwep || !mwep->opoisoned) ? "attack" : "weapon"; @@ -128,7 +160,7 @@ struct attack *mattk; /* called when your intrinsic speed is taken away */ void -u_slow_down() +u_slow_down(void) { HFast = 0L; if (!Fast) @@ -138,28 +170,41 @@ u_slow_down() exercise(A_DEX, FALSE); } -/* monster attacked your displaced image */ -STATIC_OVL void -wildmiss(mtmp, mattk) -struct monst *mtmp; -struct attack *mattk; +/* monster attacked wrong location due to monster blindness, hero + invisibility, hero displacement, or hero being underwater */ +staticfn void +wildmiss(struct monst *mtmp, struct attack *mattk) { int compat; - const char *Monst_name; /* Monnam(mtmp) */ + const char *Monst_name; /* Monnam(), deferred until after early returns */ + /* expected reasons for wildmiss() */ + boolean unotseen = (!mtmp->mcansee || (Invis && !perceives(mtmp->data))), + unotthere = (Displaced != 0), usubmerged = (Underwater != 0); + + /* the reasons for wildmiss end up getting checked twice so that the + impossible can be given, if warranted, before the early returns */ + if (!unotseen && !unotthere && !usubmerged) { + /* this used to be the 'else' case below */ + impossible("%s attacks you without knowing your location?", + Some_Monnam(mtmp)); + return; + } /* no map_invisible() -- no way to tell where _this_ is coming from */ if (!flags.verbose) return; + /* no feedback if hero doesn't see the monster's spot */ if (!cansee(mtmp->mx, mtmp->my)) return; /* maybe it's attacking an image around the corner? */ compat = ((mattk->adtyp == AD_SEDU || mattk->adtyp == AD_SSEX) - ? could_seduce(mtmp, &youmonst, mattk) : 0); + ? could_seduce(mtmp, &gy.youmonst, mattk) : 0); Monst_name = Monnam(mtmp); - if (!mtmp->mcansee || (Invis && !perceives(mtmp->data))) { + set_msg_xy(mtmp->mx, mtmp->my); + if (unotseen) { /* !mtmp->cansee || (Invis && !perceives(mtmp->data)) */ const char *swings = (mattk->aatyp == AT_BITE) ? "snaps" : (mattk->aatyp == AT_KICK) ? "kicks" : (mattk->aatyp == AT_STNG @@ -167,9 +212,9 @@ struct attack *mattk; || nolimbs(mtmp->data)) ? "lunges" : "swings"; - if (compat) + if (compat) { pline("%s tries to touch you and misses!", Monst_name); - else + } else { switch (rn2(3)) { case 0: pline("%s %s wildly and misses!", Monst_name, swings); @@ -179,7 +224,7 @@ struct attack *mattk; break; case 2: pline("%s strikes at %s!", Monst_name, - (levl[mtmp->mux][mtmp->muy].typ == WATER) + is_waterwall(mtmp->mux,mtmp->muy) ? "empty water" : "thin air"); break; @@ -187,8 +232,8 @@ struct attack *mattk; pline("%s %s wildly!", Monst_name, swings); break; } - - } else if (Displaced) { + } + } else if (unotthere) { /* Displaced */ /* give 'displaced' message even if hero is Blind */ if (compat) pline("%s smiles %s at your %sdisplaced image...", Monst_name, @@ -201,7 +246,7 @@ struct attack *mattk; * image, since the displaced image is also invisible. */ Monst_name, Invis ? "invisible " : ""); - } else if (Underwater) { + } else if (usubmerged) { /* Underwater */ /* monsters may miss especially on water level where bubbles shake the player here and there */ if (compat) @@ -210,20 +255,23 @@ struct attack *mattk; pline("%s is fooled by water reflections and misses!", Monst_name); - } else - impossible("%s attacks you without knowing your location?", - Monst_name); + } else { + ; /*NOTREACHED*/ + } } void -expels(mtmp, mdat, message) -struct monst *mtmp; -struct permonst *mdat; /* if mtmp is polymorphed, mdat != mtmp->data */ -boolean message; +expels( + struct monst *mtmp, + struct permonst *mdat, /* if mtmp is polymorphed, mdat != mtmp->data */ + boolean message) { + disp.botl = TRUE; if (message) { - if (is_animal(mdat)) { + if (digests(mdat)) { You("get regurgitated!"); + } else if (enfolds(mdat)) { + pline_mon(mtmp, "%s unfolds and you are released!", Monnam(mtmp)); } else { char blast[40]; struct attack *attk = attacktype_fordmg(mdat, AT_ENGL, AD_ANY); @@ -249,34 +297,35 @@ boolean message; } } unstuck(mtmp); /* ball&chain returned in unstuck() */ - mnexto(mtmp); + mnexto(mtmp, RLOC_NOMSG); newsym(u.ux, u.uy); - spoteffects(TRUE); /* to cover for a case where mtmp is not in a next square */ if (um_dist(mtmp->mx, mtmp->my, 1)) pline("Brrooaa... You land hard at some distance."); + spoteffects(TRUE); } /* select a monster's next attack, possibly substituting for its usual one */ struct attack * -getmattk(magr, mdef, indx, prev_result, alt_attk_buf) -struct monst *magr, *mdef; -int indx, prev_result[]; -struct attack *alt_attk_buf; +getmattk( + struct monst *magr, struct monst *mdef, + int indx, int prev_result[], + struct attack *alt_attk_buf) { struct permonst *mptr = magr->data; struct attack *attk = &mptr->mattk[indx]; - struct obj *weap = (magr == &youmonst) ? uwep : MON_WEP(magr); + struct obj *weap = (magr == &gy.youmonst) ? uwep : MON_WEP(magr); + boolean udefend = mdef == &gy.youmonst; /* honor SEDUCE=0 */ if (!SYSOPT_SEDUCE) { - extern const struct attack sa_no[NATTK]; + extern const struct attack c_sa_no[NATTK]; /* if the first attack is for SSEX damage, all six attacks will be substituted (expected succubus/incubus handling); if it isn't but another one is, only that other one will be substituted */ if (mptr->mattk[0].adtyp == AD_SSEX) { - *alt_attk_buf = sa_no[indx]; + *alt_attk_buf = c_sa_no[indx]; attk = alt_attk_buf; } else if (attk->adtyp == AD_SSEX) { *alt_attk_buf = *attk; @@ -288,7 +337,7 @@ struct attack *alt_attk_buf; /* prevent a monster with two consecutive disease or hunger attacks from hitting with both of them on the same turn; if the first has already hit, switch to a stun attack for the second */ - if (indx > 0 && prev_result[indx - 1] > 0 + if (indx > 0 && prev_result[indx - 1] > M_ATTK_MISS && (attk->adtyp == AD_DISE || attk->adtyp == AD_PEST || attk->adtyp == AD_FAMN) && attk->adtyp == mptr->mattk[indx - 1].adtyp) { @@ -297,14 +346,14 @@ struct attack *alt_attk_buf; attk->adtyp = AD_STUN; /* make drain-energy damage be somewhat in proportion to energy */ - } else if (attk->adtyp == AD_DREN && mdef == &youmonst) { + } else if (attk->adtyp == AD_DREN && udefend) { int ulev = max(u.ulevel, 6); *alt_attk_buf = *attk; attk = alt_attk_buf; /* 3.6.0 used 4d6 but since energy drain came out of max energy once current energy was gone, that tended to have a severe - effect on low energy characters; it's now 2d6 with ajustments */ + effect on low energy characters; it's now 2d6 with adjustments */ if (u.uen <= 5 * ulev && attk->damn > 1) { attk->damn -= 1; /* low energy: 2d6 -> 1d6 */ if (u.uenmax <= 2 * ulev && attk->damd > 3) @@ -316,8 +365,16 @@ struct attack *alt_attk_buf; /* note: 3d9 is slightly higher than previous 4d6 */ } - } else if (attk->aatyp == AT_ENGL && magr->mspec_used) { - /* can't re-engulf yet; switch to simpler attack */ + /* holders/engulfers who release the hero have mspec_used set to rnd(2) + and can't re-hold/re-engulf until it has been decremented to zero; + likewise for transformation by genetic engineer */ + } else if (magr->mspec_used && (attk->aatyp == AT_ENGL + || attk->aatyp == AT_HUGS + || attk->adtyp == AD_STCK + || attk->adtyp == AD_POLY)) { + boolean wimpy = (attk->damd == 0); /* lichen, violet fungus */ + + /* can't re-engulf or re-grab yet; switch to simpler attack */ *alt_attk_buf = *attk; attk = alt_attk_buf; if (attk->adtyp == AD_ACID || attk->adtyp == AD_ELEC @@ -329,28 +386,98 @@ struct attack *alt_attk_buf; } attk->damn = 1; /* relatively weak: 1d6 */ attk->damd = 6; + if (wimpy && attk->aatyp == AT_CLAW) { + attk->aatyp = AT_TUCH; + attk->damn = attk->damd = 0; + } /* barrow wight, Nazgul, erinys have weapon attack for non-physical damage; force physical damage if attacker has been cancelled or if weapon is sufficiently interesting; a few unique creatures have two weapon attacks where one does physical damage and other doesn't--avoid forcing physical damage for those */ - } else if (indx == 0 && magr != &youmonst + } else if (indx == 0 && magr != &gy.youmonst && attk->aatyp == AT_WEAP && attk->adtyp != AD_PHYS && !(mptr->mattk[1].aatyp == AT_WEAP && mptr->mattk[1].adtyp == AD_PHYS) && (magr->mcan || (weap && ((weap->otyp == CORPSE && touch_petrifies(&mons[weap->corpsenm])) - || weap->oartifact == ART_STORMBRINGER - || weap->oartifact == ART_VORPAL_BLADE)))) { + || is_art(weap, ART_STORMBRINGER) + || is_art(weap, ART_VORPAL_BLADE))))) { *alt_attk_buf = *attk; attk = alt_attk_buf; attk->adtyp = AD_PHYS; + + /* liches have a touch attack for cold damage and also a spell attack; + they won't use the spell for monster vs monster so become impotent + against cold resistant foes; change the touch damage from cold to + physical if target will resist */ + } else if (indx == 0 && attk->aatyp == AT_TUCH && attk->adtyp == AD_COLD + && (udefend ? Cold_resistance : resists_cold(mdef)) + /* don't substitute if target is immune to normal damage */ + && mdef->data != &mons[PM_SHADE]) { + *alt_attk_buf = *attk; + attk = alt_attk_buf; + attk->adtyp = AD_PHYS; + /* lessen new physical damage compared to old cold damage: + * before after + * lich: 1d10 1d6 + * demi: 3d4 2d4 + * master: 3d6 2d6 + * arch-: 5d6 3d6 + */ + attk->damn = (attk->damn + 1) / 2; + if (attk->damd == 10) + attk->damd = 6; + } + + /* elementals on their home plane do double damage */ + if (attk != alt_attk_buf && is_home_elemental(mptr)) { + *alt_attk_buf = *attk; + attk = alt_attk_buf; + attk->damn *= 2; + } + return attk; } +/* calc some variables needed for mattacku() */ +staticfn void +calc_mattacku_vars( + struct monst *mtmp, + boolean *ranged, boolean *range2, + boolean *foundyou, boolean *youseeit) +{ + *ranged = (mdistu(mtmp) > 3); + *range2 = !monnear(mtmp, mtmp->mux, mtmp->muy); + *foundyou = u_at(mtmp->mux, mtmp->muy); + *youseeit = canseemon(mtmp); + + /* do_attack() uses bhitpos to set/clear notonhead; do likewise here */ + gb.bhitpos.x = u.ux, gb.bhitpos.y = u.uy; + /* hero poly'd into a long worm isn't allowed to grow a tail, so + hitting tail instead of head can't happen */ + gn.notonhead = FALSE; +} + +/* return TRUE iff monster or hero is trapped in a (spiked) pit */ +boolean +mtrapped_in_pit(struct monst *mtmp) +{ + struct trap *ttmp = 0; + + if (mtmp == &gy.youmonst) + ttmp = (u.utrap && u.utraptype == TT_PIT) ? t_at(u.ux, u.uy) : 0; + else + ttmp = mtmp->mtrapped ? t_at(mtmp->mx, mtmp->my) : 0; + + if (ttmp && is_pit(ttmp->ttyp)) + return TRUE; + return FALSE; +} + /* * mattacku: monster attacks you * returns 1 if monster dies (e.g. "yellow light"), 0 otherwise @@ -361,28 +488,32 @@ struct attack *alt_attk_buf; * take care of it... */ int -mattacku(mtmp) -register struct monst *mtmp; +mattacku(struct monst *mtmp) { struct attack *mattk, alt_attk; int i, j = 0, tmp, sum[NATTK]; struct permonst *mdat = mtmp->data; - boolean ranged = (distu(mtmp->mx, mtmp->my) > 3); - /* Is it near you? Affects your actions */ - boolean range2 = !monnear(mtmp, mtmp->mux, mtmp->muy); - /* Does it think it's near you? Affects its actions */ - boolean foundyou = (mtmp->mux == u.ux && mtmp->muy == u.uy); - /* Is it attacking you or your image? */ - boolean youseeit = canseemon(mtmp); - /* Might be attacking your image around the corner, or - * invisible, or you might be blind.... + /* + * ranged: Is it near you? Affects your actions. + * range2: Does it think it's near you? Affects its actions. + * foundyou: Is it attacking you or your image? + * youseeit: Can you observe the attack? It might be attacking your + * image around the corner, or invisible, or you might be blind. + * skipnonmagc: Are further physical attack attempts useless? (After + * a wild miss--usually due to attacking displaced image. Avoids + * excessively verbose miss feedback when monster can do multiple + * attacks and would miss the same wrong spot each time.) */ - boolean skipnonmagc = FALSE; - /* Are further physical attack attempts useless? */ + boolean ranged, range2, foundyou, firstfoundyou, youseeit, + skipnonmagc = FALSE; + + calc_mattacku_vars(mtmp, &ranged, &range2, &foundyou, &youseeit); if (!ranged) nomul(0); - if (DEADMONSTER(mtmp) || (Underwater && !is_swimmer(mtmp->data))) + if (DEADMONSTER(mtmp)) + return 1; + if (Underwater && !is_swimmer(mtmp->data)) return 0; /* If swallowed, can only be affected by u.ustuck */ @@ -391,28 +522,29 @@ register struct monst *mtmp; return 0; u.ustuck->mux = u.ux; u.ustuck->muy = u.uy; - range2 = 0; - foundyou = 1; if (u.uinvulnerable) return 0; /* stomachs can't hurt you! */ - + range2 = 0; + foundyou = 1; } else if (u.usteed) { if (mtmp == u.usteed) /* Your steed won't attack you */ return 0; /* Orcs like to steal and eat horses and the like */ - if (!rn2(is_orc(mtmp->data) ? 2 : 4) - && distu(mtmp->mx, mtmp->my) <= 2) { - /* Attack your steed instead */ + if (!rn2(is_orc(mtmp->data) ? 2 : 4) && m_next2u(mtmp)) { + /* attack your steed instead; 'bhitpos' and 'notonhead' are + already set from targeting hero */ i = mattackm(mtmp, u.usteed); - if ((i & MM_AGR_DIED)) + if ((i & M_ATTK_AGR_DIED) != 0) return 1; /* make sure steed is still alive and within range */ - if ((i & MM_DEF_DIED) || !u.usteed - || distu(mtmp->mx, mtmp->my) > 2) + if ((i & M_ATTK_DEF_DIED) != 0 || !u.usteed + || !m_next2u(mtmp)) return 0; /* Let your steed retaliate */ - return !!(mattackm(u.usteed, mtmp) & MM_DEF_DIED); + gb.bhitpos.x = mtmp->mx, gb.bhitpos.y = mtmp->my; + gn.notonhead = FALSE; + return !!(mattackm(u.usteed, mtmp) & M_ATTK_DEF_DIED); } } @@ -420,7 +552,7 @@ register struct monst *mtmp; if (!canspotmon(mtmp)) map_invisible(mtmp->mx, mtmp->my); u.uundetected = 0; - if (is_hider(youmonst.data) && u.umonnum != PM_TRAPPER) { + if (is_hider(gy.youmonst.data) && u.umonnum != PM_TRAPPER) { /* ceiling hider */ coord cc; /* maybe we need a unexto() function? */ struct obj *obj; @@ -430,7 +562,7 @@ register struct monst *mtmp; is eligible for placing hero; we assume that a removed monster remembers its old spot */ remove_monster(mtmp->mx, mtmp->my); - if (!enexto(&cc, u.ux, u.uy, youmonst.data) + if (!enexto(&cc, u.ux, u.uy, gy.youmonst.data) /* a fish won't voluntarily swap positions when it's in water and hero is over land */ || (mtmp->data->mlet == S_EEL @@ -443,7 +575,7 @@ register struct monst *mtmp; so mtmp's next move will be a regular attack */ place_monster(mtmp, mtmp->mx, mtmp->my); /* put back */ newsym(u.ux, u.uy); /* u.uundetected was toggled */ - pline("%s draws back as you drop!", Monnam(mtmp)); + pline_mon(mtmp, "%s draws back as you drop!", Monnam(mtmp)); return 0; } @@ -455,17 +587,17 @@ register struct monst *mtmp; /* tail hasn't grown, so if it now occupies then one of its original spots must be free */ if (m_at(cc.x, cc.y)) - (void) enexto(&cc, u.ux, u.uy, youmonst.data); + (void) enexto(&cc, u.ux, u.uy, gy.youmonst.data); } - teleds(cc.x, cc.y, TRUE); /* move hero */ + teleds(cc.x, cc.y, TELEDS_ALLOW_DRAG); /* move hero */ set_apparxy(mtmp); newsym(u.ux, u.uy); - if (youmonst.data->mlet != S_PIERCER) + if (gy.youmonst.data->mlet != S_PIERCER) return 0; /* lurkers don't attack */ obj = which_armor(mtmp, WORN_HELMET); - if (obj && is_metallic(obj)) { + if (hard_helmet(obj)) { Your("blow glances off %s %s.", s_suffix(mon_nam(mtmp)), helm_simple_name(obj)); } else { @@ -490,10 +622,10 @@ register struct monst *mtmp; * parallelism to work, we can't rephrase it, so we * zap the "laid by you" momentarily instead. */ - struct obj *obj = level.objects[u.ux][u.uy]; + struct obj *obj = svl.level.objects[u.ux][u.uy]; if (obj || u.umonnum == PM_TRAPPER - || (youmonst.data->mlet == S_EEL + || (gy.youmonst.data->mlet == S_EEL && is_pool(u.ux, u.uy))) { int save_spe = 0; /* suppress warning */ @@ -504,16 +636,19 @@ register struct monst *mtmp; } /* note that m_monnam() overrides hallucination, which is what we want when message is from mtmp's perspective */ - if (youmonst.data->mlet == S_EEL + if (gy.youmonst.data->mlet == S_EEL || u.umonnum == PM_TRAPPER) pline( "Wait, %s! There's a hidden %s named %s there!", - m_monnam(mtmp), youmonst.data->mname, plname); + m_monnam(mtmp), + pmname(gy.youmonst.data, Ugender), svp.plname); else pline( "Wait, %s! There's a %s named %s hiding under %s!", - m_monnam(mtmp), youmonst.data->mname, plname, - doname(level.objects[u.ux][u.uy])); + m_monnam(mtmp), + pmname(gy.youmonst.data, Ugender), + svp.plname, + doname(svl.level.objects[u.ux][u.uy])); if (obj) obj->spe = save_spe; } else @@ -525,9 +660,9 @@ register struct monst *mtmp; } /* hero might be a mimic, concealed via #monster */ - if (youmonst.data->mlet == S_MIMIC && U_AP_TYPE && !range2 + if (gy.youmonst.data->mlet == S_MIMIC && U_AP_TYPE && !range2 && foundyou && !u.uswallow) { - boolean sticky = sticks(youmonst.data); + boolean sticky = sticks(gy.youmonst.data); if (!canspotmon(mtmp)) map_invisible(mtmp->mx, mtmp->my); @@ -535,11 +670,11 @@ register struct monst *mtmp; pline("It gets stuck on you."); else /* see note about m_monnam() above */ pline("Wait, %s! That's a %s named %s!", m_monnam(mtmp), - youmonst.data->mname, plname); + pmname(gy.youmonst.data, Ugender), svp.plname); if (sticky) - u.ustuck = mtmp; - youmonst.m_ap_type = M_AP_NOTHING; - youmonst.mappearance = 0; + set_ustuck(mtmp); + gy.youmonst.m_ap_type = M_AP_NOTHING; + gy.youmonst.mappearance = 0; newsym(u.ux, u.uy); return 0; } @@ -549,19 +684,21 @@ register struct monst *mtmp; if (!canspotmon(mtmp)) map_invisible(mtmp->mx, mtmp->my); if (!youseeit) - pline("%s %s!", Something, (likes_gold(mtmp->data) - && youmonst.mappearance == GOLD_PIECE) - ? "tries to pick you up" - : "disturbs you"); + pline("%s %s!", Something, + (likes_gold(mtmp->data) + && gy.youmonst.mappearance == GOLD_PIECE) + ? "tries to pick you up" + : "disturbs you"); else /* see note about m_monnam() above */ pline("Wait, %s! That %s is really %s named %s!", m_monnam(mtmp), - mimic_obj_name(&youmonst), an(mons[u.umonnum].mname), - plname); - if (multi < 0) { /* this should always be the case */ + mimic_obj_name(&gy.youmonst), + an(pmname(&mons[u.umonnum], Ugender)), svp.plname); + if (gm.multi < 0) { /* this should always be the case */ char buf[BUFSZ]; Sprintf(buf, "You appear to be %s again.", - Upolyd ? (const char *) an(youmonst.data->mname) + Upolyd ? (const char *) an(pmname(gy.youmonst.data, + flags.female)) : (const char *) "yourself"); unmul(buf); /* immediately stop mimicking */ } @@ -571,7 +708,7 @@ register struct monst *mtmp; /* Work out the armor class differential */ tmp = AC_VALUE(u.uac) + 10; /* tmp ~= 0 - 20 */ tmp += mtmp->m_lev; - if (multi < 0) + if (gm.multi < 0) tmp += 4; if ((Invis && !perceives(mdat)) || !mtmp->mcansee) tmp -= 2; @@ -586,63 +723,27 @@ register struct monst *mtmp; newsym(mtmp->mx, mtmp->my); } - /* Special demon handling code */ - if ((mtmp->cham == NON_PM) && is_demon(mdat) && !range2 - && mtmp->data != &mons[PM_BALROG] && mtmp->data != &mons[PM_SUCCUBUS] - && mtmp->data != &mons[PM_INCUBUS]) - if (!mtmp->mcan && !rn2(13)) - (void) msummon(mtmp); - - /* Special lycanthrope handling code */ - if ((mtmp->cham == NON_PM) && is_were(mdat) && !range2) { - if (is_human(mdat)) { - if (!rn2(5 - (night() * 2)) && !mtmp->mcan) - new_were(mtmp); - } else if (!rn2(30) && !mtmp->mcan) - new_were(mtmp); - mdat = mtmp->data; - - if (!rn2(10) && !mtmp->mcan) { - int numseen, numhelp; - char buf[BUFSZ], genericwere[BUFSZ]; - - Strcpy(genericwere, "creature"); - numhelp = were_summon(mdat, FALSE, &numseen, genericwere); - if (youseeit) { - pline("%s summons help!", Monnam(mtmp)); - if (numhelp > 0) { - if (numseen == 0) - You_feel("hemmed in."); - } else - pline("But none comes."); - } else { - const char *from_nowhere; - - if (!Deaf) { - pline("%s %s!", Something, makeplural(growl_sound(mtmp))); - from_nowhere = ""; - } else - from_nowhere = " from nowhere"; - if (numhelp > 0) { - if (numseen < 1) - You_feel("hemmed in."); - else { - if (numseen == 1) - Sprintf(buf, "%s appears", an(genericwere)); - else - Sprintf(buf, "%s appear", - makeplural(genericwere)); - pline("%s%s!", upstart(buf), from_nowhere); - } - } /* else no help came; but you didn't know it tried */ - } - } + /* when not cancelled and not in current form due to shapechange, many + demons can summon more demons and were creatures can summon critters; + also, were creature might change from human to animal or vice versa */ + if (mtmp->cham == NON_PM && !mtmp->mcan && !range2 + && (is_demon(mdat) || is_were(mdat))) { + boolean already_fleeing = mtmp->mflee != 0; + + summonmu(mtmp, youseeit); + /* were-creature might have changed to beast form; if that has + caused it to become afraid (due to non-human reacting to scroll + of scare monster or engraved "Elbereth" which was being ignored + while in human form), don't continue this attack */ + if (mtmp->mflee && !already_fleeing) + return 0; + mdat = mtmp->data; /* update cached value in case of were change */ } - if (u.uinvulnerable) { + if (u.uinvulnerable) { /* in the midst of successful prayer */ /* monsters won't attack you */ if (mtmp == u.ustuck) { - pline("%s loosens its grip slightly.", Monnam(mtmp)); + pline_mon(mtmp, "%s loosens its grip slightly.", Monnam(mtmp)); } else if (!range2) { if (youseeit || sensemon(mtmp)) pline("%s starts to attack you, but pulls back.", @@ -655,18 +756,38 @@ register struct monst *mtmp; /* Unlike defensive stuff, don't let them use item _and_ attack. */ if (find_offensive(mtmp)) { - int foo = use_offensive(mtmp); + int offended = use_offensive(mtmp); - if (foo != 0) - return (foo == 1); + if (offended != 0) + return (offended == 1); } + gs.skipdrin = FALSE; /* [see mattackm(mhitm.c)] */ + firstfoundyou = foundyou; + for (i = 0; i < NATTK; i++) { - sum[i] = 0; - mon_currwep = (struct obj *)0; - mattk = getmattk(mtmp, &youmonst, i, sum, &alt_attk); + sum[i] = M_ATTK_MISS; + /* counterattack against attack [i-1] might have been fatal */ + if (DEADMONSTER(mtmp)) + return 1; + if (i > 0) { + /* recalc in case prior attack moved hero; mtmp doesn't make + another attempt to guess your location but might have + accidentally knocked you to where it thought you were + [not sure whether that's actually possible] */ + calc_mattacku_vars(mtmp, &ranged, &range2, &foundyou, &youseeit); + /* if hero was found but isn't anymore, avoid wildmiss now */ + if (firstfoundyou && !foundyou) + continue; /* set sum[i] to 'miss' but skip other actions */ + if (!u_at(gb.bhitpos.x, gb.bhitpos.y)) + continue; + } + mon_currwep = (struct obj *) 0; + mattk = getmattk(mtmp, &gy.youmonst, i, sum, &alt_attk); if ((u.uswallow && mattk->aatyp != AT_ENGL) - || (skipnonmagc && mattk->aatyp != AT_MAGC)) + || (skipnonmagc && mattk->aatyp != AT_MAGC) + || (gs.skipdrin && mattk->aatyp == AT_TENT + && mattk->adtyp == AD_DRIN)) continue; switch (mattk->aatyp) { @@ -677,12 +798,17 @@ register struct monst *mtmp; case AT_TUCH: case AT_BUTT: case AT_TENT: + if (mattk->aatyp == AT_KICK && mtrapped_in_pit(mtmp)) + continue; if (!range2 && (!MON_WEP(mtmp) || mtmp->mconf || Conflict - || !touch_petrifies(youmonst.data))) { + || !touch_petrifies(gy.youmonst.data))) { if (foundyou) { if (tmp > (j = rnd(20 + i))) { + if (unsolid(gy.youmonst.data) + && failed_grab(mtmp, &gy.youmonst, mattk)) + continue; if (mattk->aatyp != AT_KICK - || !thick_skinned(youmonst.data)) + || !thick_skinned(gy.youmonst.data)) sum[i] = hitmu(mtmp, mattk); } else missmu(mtmp, (tmp == j), mattk); @@ -697,8 +823,10 @@ register struct monst *mtmp; case AT_HUGS: /* automatic if prev two attacks succeed */ /* Note: if displaced, prev attacks never succeeded */ if ((!range2 && i >= 2 && sum[i - 1] && sum[i - 2]) - || mtmp == u.ustuck) - sum[i] = hitmu(mtmp, mattk); + || mtmp == u.ustuck) { + if (!failed_grab(mtmp, &gy.youmonst, mattk)) + sum[i] = hitmu(mtmp, mattk); + } break; case AT_GAZE: /* can affect you either ranged or not */ @@ -725,15 +853,20 @@ register struct monst *mtmp; } else { missmu(mtmp, (tmp == j), mattk); } - } else if (is_animal(mtmp->data)) { - pline("%s gulps some air!", Monnam(mtmp)); + } else if (digests(mtmp->data)) { + pline_mon(mtmp, "%s gulps some air!", Monnam(mtmp)); } else { - if (youseeit) - pline("%s lunges forward and recoils!", Monnam(mtmp)); - else + if (youseeit) { + pline_mon(mtmp, "%s lunges forward and recoils!", + Monnam(mtmp)); + } else { + if (is_whirly(mtmp->data)) { + Soundeffect(se_rushing_wind_noise, 60); + } You_hear("a %s nearby.", is_whirly(mtmp->data) ? "rushing noise" : "splat"); + } } } break; @@ -767,11 +900,16 @@ register struct monst *mtmp; if (foundyou) { mon_currwep = MON_WEP(mtmp); if (mon_currwep) { - hittmp = hitval(mon_currwep, &youmonst); + boolean bash = (is_pole(mon_currwep) + && !is_art(mon_currwep, + ART_SNICKERSNEE) + && m_next2u(mtmp)); + + hittmp = hitval(mon_currwep, &gy.youmonst); tmp += hittmp; - mswings(mtmp, mon_currwep); + mswings(mtmp, mon_currwep, bash); } - if (tmp > (j = dieroll = rnd(20 + i))) + if (tmp > (j = gm.mhitu_dieroll = rnd(20 + i))) sum[i] = hitmu(mtmp, mattk); else missmu(mtmp, (tmp == j), mattk); @@ -795,46 +933,128 @@ register struct monst *mtmp; default: /* no attack */ break; } - if (context.botl) + if (disp.botl) bot(); /* give player a chance of waking up before dying -kaa */ - if (sum[i] == 1) { /* successful attack */ - if (u.usleep && u.usleep < monstermoves && !rn2(10)) { - multi = -1; - nomovemsg = "The combat suddenly awakens you."; + if (sum[i] == M_ATTK_HIT) { /* successful attack */ + if (u.usleep && u.usleep < svm.moves && !rn2(10)) { + gm.multi = -1; + gn.nomovemsg = "The combat suddenly awakens you."; } } - if (sum[i] == 2) + if ((sum[i] & M_ATTK_AGR_DIED)) return 1; /* attacker dead */ - if (sum[i] == 3) + if ((sum[i] & M_ATTK_AGR_DONE)) break; /* attacker teleported, no more attacks */ /* sum[i] == 0: unsuccessful attack */ } return 0; } -STATIC_OVL boolean -diseasemu(mdat) -struct permonst *mdat; +/* monster summons help for its fight against hero */ +staticfn void +summonmu(struct monst *mtmp, boolean youseeit) +{ + struct permonst *mdat = mtmp->data; + + /* + * Extracted from mattacku() to reduce clutter there. + * Caller has verified that 'mtmp' hasn't been cancelled + * and isn't a shapechanger. + */ + + if (is_demon(mdat)) { + if (mdat != &mons[PM_BALROG] && mdat != &mons[PM_AMOROUS_DEMON]) { + if (!rn2(Inhell ? 10 : 16)) + (void) msummon(mtmp); + } + return; /* no such thing as a demon were creature, so we're done */ + } + + if (is_were(mdat)) { + /* if hero has Protection_from_shape_changers, new_were() will work + in the critter-to-human direction but be a no-op the other way; + we repeat the criteria here for clarity */ + if (is_human(mdat)) { /* maybe switch to animal form */ + if (!Protection_from_shape_changers && !rn2(5 - (night() * 2))) + new_were(mtmp); + } else { /* maybe switch to back human form */ + if (Protection_from_shape_changers || !rn2(30)) + new_were(mtmp); + } + mdat = mtmp->data; /* form change invalidates cached value */ + + /* maybe summon compatible critters; + not blocked by Protection_from_shape_changers */ + if (!rn2(10)) { + int numseen, numhelp; + char buf[BUFSZ], genericwere[BUFSZ]; + + Strcpy(genericwere, "creature"); + if (youseeit) + pline_mon(mtmp, "%s summons help!", Monnam(mtmp)); + numhelp = were_summon(mdat, FALSE, &numseen, genericwere); + if (youseeit) { + if (numhelp > 0) { + if (numseen == 0) + You_feel("hemmed in."); + } else { + pline("But none comes."); + } + } else { + const char *from_nowhere; + + if (!Deaf) { + pline("%s %s!", Something, + makeplural(growl_sound(mtmp))); + from_nowhere = ""; + } else { + from_nowhere = " from nowhere"; + } + if (numhelp > 0) { + if (numseen < 1) { + You_feel("hemmed in."); + } else { + if (numseen == 1) + Sprintf(buf, "%s appears", an(genericwere)); + else + Sprintf(buf, "%s appear", + makeplural(genericwere)); + pline("%s%s!", upstart(buf), from_nowhere); + } + } /* else no help came; but you didn't know it tried */ + } + } /* summon critters */ + return; + } /* were creature */ +} + +boolean +diseasemu(struct permonst *mdat) { if (Sick_resistance) { You_feel("a slight illness."); return FALSE; } else { make_sick(Sick ? Sick / 3L + 1L : (long) rn1(ACURR(A_CON), 20), - mdat->mname, TRUE, SICK_NONVOMITABLE); + mdat->pmnames[NEUTRAL], TRUE, SICK_NONVOMITABLE); return TRUE; } } /* check whether slippery clothing protects from hug or wrap attack */ -STATIC_OVL boolean -u_slip_free(mtmp, mattk) -struct monst *mtmp; -struct attack *mattk; +boolean +u_slip_free( + struct monst *mtmp, + struct attack *mattk) { - struct obj *obj = (uarmc ? uarmc : uarm); + struct obj *obj; + + /* greased armor does not protect against AT_ENGL+AD_WRAP */ + if (mattk->aatyp == AT_ENGL) + return FALSE; + obj = (uarmc ? uarmc : uarm); if (!obj) obj = uarmu; if (mattk->adtyp == AD_DRIN) @@ -844,7 +1064,7 @@ struct attack *mattk; protection might fail (33% chance) when the armor is cursed */ if (obj && (obj->greased || obj->otyp == OILSKIN_CLOAK) && (!obj->cursed || rn2(3))) { - pline("%s %s your %s %s!", Monnam(mtmp), + pline_mon(mtmp, "%s %s your %s %s!", Monnam(mtmp), (mattk->adtyp == AD_WRAP) ? "slips off of" : "grabs you, but cannot hold onto", obj->greased ? "greased" : "slippery", @@ -866,23 +1086,25 @@ struct attack *mattk; /* armor that sufficiently covers the body might be able to block magic */ int -magic_negation(mon) -struct monst *mon; +magic_negation(struct monst *mon) { struct obj *o; long wearmask; int armpro, mc = 0; - boolean is_you = (mon == &youmonst), + boolean is_you = (mon == &gy.youmonst), + via_amul = FALSE, gotprot = is_you ? (EProtection != 0L) /* high priests have innate protection */ - : (mon->data == &mons[PM_HIGH_PRIEST]); + : (mon->data == &mons[PM_HIGH_CLERIC]); - for (o = is_you ? invent : mon->minvent; o; o = o->nobj) { + for (o = is_you ? gi.invent : mon->minvent; o; o = o->nobj) { /* a_can field is only applicable for armor (which must be worn) */ if ((o->owornmask & W_ARMOR) != 0L) { armpro = objects[o->otyp].a_can; if (armpro > mc) mc = armpro; + } else if ((o->owornmask & W_AMUL) != 0L) { + via_amul = (o->otyp == AMULET_OF_GUARDING); } /* if we've already confirmed Protection, skip additional checks */ if (is_you || gotprot) @@ -897,15 +1119,18 @@ struct monst *mon; } if (gotprot) { - /* extrinsic Protection increases mc by 1 */ - if (mc < 3) - mc += 1; + /* extrinsic Protection increases mc by 1 (2 for amulet of guarding); + multiple sources don't provide multiple increments */ + mc += via_amul ? 2 : 1; + if (mc > 3) + mc = 3; } else if (mc < 1) { /* intrinsic Protection is weaker (play balance; obtaining divine protection is too easy); it confers minimum mc 1 instead of 0 */ if ((is_you && ((HProtection && u.ublessed > 0) || u.uspellprot)) /* aligned priests and angels have innate intrinsic Protection */ - || (mon->data == &mons[PM_ALIGNED_PRIEST] || is_minion(mon->data))) + || (mon->data == &mons[PM_ALIGNED_CLERIC] + || is_minion(mon->data))) mc = 1; } return mc; @@ -913,21 +1138,19 @@ struct monst *mon; /* * hitmu: monster hits you - * returns 2 if monster dies (e.g. "yellow light"), 1 otherwise - * 3 if the monster lives but teleported/paralyzed, so it can't keep - * attacking you - */ -STATIC_OVL int -hitmu(mtmp, mattk) -register struct monst *mtmp; -register struct attack *mattk; + * returns MM_ flags +*/ +staticfn int +hitmu(struct monst *mtmp, struct attack *mattk) { struct permonst *mdat = mtmp->data; - int uncancelled, ptmp; - int dmg, armpro, permdmg, tmphp; - char buf[BUFSZ]; - struct permonst *olduasmon = youmonst.data; + struct permonst *olduasmon = gy.youmonst.data; int res; + struct mhitm_data mhm; + mhm.hitflags = M_ATTK_MISS; + mhm.permdmg = 0; + mhm.specialdmg = 0; + mhm.done = FALSE; if (!canspotmon(mtmp)) map_invisible(mtmp->mx, mtmp->my); @@ -937,11 +1160,12 @@ register struct attack *mattk; */ if (mtmp->mundetected && (hides_under(mdat) || mdat->mlet == S_EEL)) { mtmp->mundetected = 0; - if (!(Blind ? Blind_telepat : Unblind_telepat)) { + if (!tp_sensemon(mtmp) && !Detect_monsters) { struct obj *obj; const char *what; + char Amonbuf[BUFSZ]; - if ((obj = level.objects[mtmp->mx][mtmp->my]) != 0) { + if ((obj = svl.level.objects[mtmp->mx][mtmp->my]) != 0) { if (Blind && !obj->dknown) what = something; else if (is_pool(mtmp->mx, mtmp->my) && !Underwater) @@ -949,768 +1173,53 @@ register struct attack *mattk; else what = doname(obj); - pline("%s was hidden under %s!", Amonnam(mtmp), what); + Strcpy(Amonbuf, Amonnam(mtmp)); + /* mtmp might be invisible with hero unable to see same */ + if (!strcmp(Amonbuf, "It")) /* note: not strcmpi() */ + Strcpy(Amonbuf, Something); + pline("%s was hidden under %s!", Amonbuf, what); } newsym(mtmp->mx, mtmp->my); } } /* First determine the base damage done */ - dmg = d((int) mattk->damn, (int) mattk->damd); + mhm.damage = d((int) mattk->damn, (int) mattk->damd); if ((is_undead(mdat) || is_vampshifter(mtmp)) && midnight()) - dmg += d((int) mattk->damn, (int) mattk->damd); /* extra damage */ - - /* Next a cancellation factor. - * Use uncancelled when cancellation factor takes into account certain - * armor's special magic protection. Otherwise just use !mtmp->mcan. - */ - armpro = magic_negation(&youmonst); - uncancelled = !mtmp->mcan && (rn2(10) >= 3 * armpro); - - permdmg = 0; - /* Now, adjust damages via resistances or specific attacks */ - switch (mattk->adtyp) { - case AD_PHYS: - if (mattk->aatyp == AT_HUGS && !sticks(youmonst.data)) { - if (!u.ustuck && rn2(2)) { - if (u_slip_free(mtmp, mattk)) { - dmg = 0; - } else { - u.ustuck = mtmp; - pline("%s grabs you!", Monnam(mtmp)); - } - } else if (u.ustuck == mtmp) { - exercise(A_STR, FALSE); - You("are being %s.", (mtmp->data == &mons[PM_ROPE_GOLEM]) - ? "choked" - : "crushed"); - } - } else { /* hand to hand weapon */ - struct obj *otmp = mon_currwep; - - if (mattk->aatyp == AT_WEAP && otmp) { - struct obj *marmg; - int tmp; - - if (otmp->otyp == CORPSE - && touch_petrifies(&mons[otmp->corpsenm])) { - dmg = 1; - pline("%s hits you with the %s corpse.", Monnam(mtmp), - mons[otmp->corpsenm].mname); - if (!Stoned) - goto do_stone; - } - dmg += dmgval(otmp, &youmonst); - if ((marmg = which_armor(mtmp, W_ARMG)) != 0 - && marmg->otyp == GAUNTLETS_OF_POWER) - dmg += rn1(4, 3); /* 3..6 */ - if (dmg <= 0) - dmg = 1; - if (!(otmp->oartifact - && artifact_hit(mtmp, &youmonst, otmp, &dmg, dieroll))) - hitmsg(mtmp, mattk); - if (!dmg) - break; - if (objects[otmp->otyp].oc_material == SILVER - && Hate_silver) { - pline_The("silver sears your flesh!"); - exercise(A_CON, FALSE); - } - /* this redundancy necessary because you have - to take the damage _before_ being cloned; - need to have at least 2 hp left to split */ - tmp = dmg; - if (u.uac < 0) - tmp -= rnd(-u.uac); - if (tmp < 1) - tmp = 1; - if (u.mh - tmp > 1 - && (objects[otmp->otyp].oc_material == IRON - /* relevant 'metal' objects are scalpel and tsurugi */ - || objects[otmp->otyp].oc_material == METAL) - && (u.umonnum == PM_BLACK_PUDDING - || u.umonnum == PM_BROWN_PUDDING)) { - if (tmp > 1) - exercise(A_STR, FALSE); - /* inflict damage now; we know it can't be fatal */ - u.mh -= tmp; - context.botl = 1; - dmg = 0; /* don't inflict more damage below */ - if (cloneu()) - You("divide as %s hits you!", mon_nam(mtmp)); - } - rustm(&youmonst, otmp); - } else if (mattk->aatyp != AT_TUCH || dmg != 0 - || mtmp != u.ustuck) - hitmsg(mtmp, mattk); - } - break; - case AD_DISE: - hitmsg(mtmp, mattk); - if (!diseasemu(mdat)) - dmg = 0; - break; - case AD_FIRE: - hitmsg(mtmp, mattk); - if (uncancelled) { - pline("You're %s!", on_fire(youmonst.data, mattk)); - if (completelyburns(youmonst.data)) { /* paper or straw golem */ - You("go up in flames!"); - /* KMH -- this is okay with unchanging */ - rehumanize(); - break; - } else if (Fire_resistance) { - pline_The("fire doesn't feel hot!"); - dmg = 0; - } - if ((int) mtmp->m_lev > rn2(20)) - destroy_item(SCROLL_CLASS, AD_FIRE); - if ((int) mtmp->m_lev > rn2(20)) - destroy_item(POTION_CLASS, AD_FIRE); - if ((int) mtmp->m_lev > rn2(25)) - destroy_item(SPBOOK_CLASS, AD_FIRE); - burn_away_slime(); - } else - dmg = 0; - break; - case AD_COLD: - hitmsg(mtmp, mattk); - if (uncancelled) { - pline("You're covered in frost!"); - if (Cold_resistance) { - pline_The("frost doesn't seem cold!"); - dmg = 0; - } - if ((int) mtmp->m_lev > rn2(20)) - destroy_item(POTION_CLASS, AD_COLD); - } else - dmg = 0; - break; - case AD_ELEC: - hitmsg(mtmp, mattk); - if (uncancelled) { - You("get zapped!"); - if (Shock_resistance) { - pline_The("zap doesn't shock you!"); - dmg = 0; - } - if ((int) mtmp->m_lev > rn2(20)) - destroy_item(WAND_CLASS, AD_ELEC); - if ((int) mtmp->m_lev > rn2(20)) - destroy_item(RING_CLASS, AD_ELEC); - } else - dmg = 0; - break; - case AD_SLEE: - hitmsg(mtmp, mattk); - if (uncancelled && multi >= 0 && !rn2(5)) { - if (Sleep_resistance) - break; - fall_asleep(-rnd(10), TRUE); - if (Blind) - You("are put to sleep!"); - else - You("are put to sleep by %s!", mon_nam(mtmp)); - } - break; - case AD_BLND: - if (can_blnd(mtmp, &youmonst, mattk->aatyp, (struct obj *) 0)) { - if (!Blind) - pline("%s blinds you!", Monnam(mtmp)); - make_blinded(Blinded + (long) dmg, FALSE); - if (!Blind) - Your1(vision_clears); - } - dmg = 0; - break; - case AD_DRST: - ptmp = A_STR; - goto dopois; - case AD_DRDX: - ptmp = A_DEX; - goto dopois; - case AD_DRCO: - ptmp = A_CON; - dopois: - hitmsg(mtmp, mattk); - if (uncancelled && !rn2(8)) { - Sprintf(buf, "%s %s", s_suffix(Monnam(mtmp)), - mpoisons_subj(mtmp, mattk)); - poisoned(buf, ptmp, mdat->mname, 30, FALSE); - } - break; - case AD_DRIN: - hitmsg(mtmp, mattk); - if (defends(AD_DRIN, uwep) || !has_head(youmonst.data)) { - You("don't seem harmed."); - /* Not clear what to do for green slimes */ - break; - } - if (u_slip_free(mtmp, mattk)) - break; - - if (uarmh && rn2(8)) { - /* not body_part(HEAD) */ - Your("%s blocks the attack to your head.", - helm_simple_name(uarmh)); - break; - } - /* negative armor class doesn't reduce this damage */ - if (Half_physical_damage) - dmg = (dmg + 1) / 2; - mdamageu(mtmp, dmg); - dmg = 0; /* don't inflict a second dose below */ - - if (!uarmh || uarmh->otyp != DUNCE_CAP) { - /* eat_brains() will miss if target is mindless (won't - happen here; hero is considered to retain his mind - regardless of current shape) or is noncorporeal - (can't happen here; no one can poly into a ghost - or shade) so this check for missing is academic */ - if (eat_brains(mtmp, &youmonst, TRUE, (int *) 0) == MM_MISS) - break; - } - /* adjattrib gives dunce cap message when appropriate */ - (void) adjattrib(A_INT, -rnd(2), FALSE); - forget_levels(25); /* lose memory of 25% of levels */ - forget_objects(25); /* lose memory of 25% of objects */ - break; - case AD_PLYS: - hitmsg(mtmp, mattk); - if (uncancelled && multi >= 0 && !rn2(3)) { - if (Free_action) { - You("momentarily stiffen."); - } else { - if (Blind) - You("are frozen!"); - else - You("are frozen by %s!", mon_nam(mtmp)); - nomovemsg = You_can_move_again; - nomul(-rnd(10)); - multi_reason = "paralyzed by a monster"; - exercise(A_DEX, FALSE); - } - } - break; - case AD_DRLI: - hitmsg(mtmp, mattk); - if (uncancelled && !rn2(3) && !Drain_resistance) { - losexp("life drainage"); - } - break; - case AD_LEGS: { - long side = rn2(2) ? RIGHT_SIDE : LEFT_SIDE; - const char *sidestr = (side == RIGHT_SIDE) ? "right" : "left", - *Monst_name = Monnam(mtmp), *leg = body_part(LEG); - - /* This case is too obvious to ignore, but Nethack is not in - * general very good at considering height--most short monsters - * still _can_ attack you when you're flying or mounted. - */ - if ((u.usteed || Levitation || Flying) && !is_flyer(mtmp->data)) { - pline("%s tries to reach your %s %s!", Monst_name, sidestr, leg); - dmg = 0; - } else if (mtmp->mcan) { - pline("%s nuzzles against your %s %s!", Monnam(mtmp), - sidestr, leg); - dmg = 0; - } else { - if (uarmf) { - if (rn2(2) && (uarmf->otyp == LOW_BOOTS - || uarmf->otyp == IRON_SHOES)) { - pline("%s pricks the exposed part of your %s %s!", - Monst_name, sidestr, leg); - } else if (!rn2(5)) { - pline("%s pricks through your %s boot!", Monst_name, - sidestr); - } else { - pline("%s scratches your %s boot!", Monst_name, - sidestr); - dmg = 0; - break; - } - } else - pline("%s pricks your %s %s!", Monst_name, sidestr, leg); - - set_wounded_legs(side, rnd(60 - ACURR(A_DEX))); - exercise(A_STR, FALSE); - exercise(A_DEX, FALSE); - } - break; - } - case AD_STON: /* cockatrice */ - hitmsg(mtmp, mattk); - if (!rn2(3)) { - if (mtmp->mcan) { - if (!Deaf) - You_hear("a cough from %s!", mon_nam(mtmp)); - } else { - if (!Deaf) - You_hear("%s hissing!", s_suffix(mon_nam(mtmp))); - if (!rn2(10) - || (flags.moonphase == NEW_MOON && !have_lizard())) { - do_stone: - if (!Stoned && !Stone_resistance - && !(poly_when_stoned(youmonst.data) - && polymon(PM_STONE_GOLEM))) { - int kformat = KILLED_BY_AN; - const char *kname = mtmp->data->mname; - - if (mtmp->data->geno & G_UNIQ) { - if (!type_is_pname(mtmp->data)) - kname = the(kname); - kformat = KILLED_BY; - } - make_stoned(5L, (char *) 0, kformat, kname); - return 1; - /* done_in_by(mtmp, STONING); */ - } - } - } - } - break; - case AD_STCK: - hitmsg(mtmp, mattk); - if (uncancelled && !u.ustuck && !sticks(youmonst.data)) - u.ustuck = mtmp; - break; - case AD_WRAP: - if ((!mtmp->mcan || u.ustuck == mtmp) && !sticks(youmonst.data)) { - if (!u.ustuck && !rn2(10)) { - if (u_slip_free(mtmp, mattk)) { - dmg = 0; - } else { - pline("%s swings itself around you!", Monnam(mtmp)); - u.ustuck = mtmp; - } - } else if (u.ustuck == mtmp) { - if (is_pool(mtmp->mx, mtmp->my) && !Swimming && !Amphibious) { - boolean moat = (levl[mtmp->mx][mtmp->my].typ != POOL) - && (levl[mtmp->mx][mtmp->my].typ != WATER) - && !Is_medusa_level(&u.uz) - && !Is_waterlevel(&u.uz); - - pline("%s drowns you...", Monnam(mtmp)); - killer.format = KILLED_BY_AN; - Sprintf(killer.name, "%s by %s", - moat ? "moat" : "pool of water", - an(mtmp->data->mname)); - done(DROWNING); - } else if (mattk->aatyp == AT_HUGS) - You("are being crushed."); - } else { - dmg = 0; - if (flags.verbose) - pline("%s brushes against your %s.", Monnam(mtmp), - body_part(LEG)); - } - } else - dmg = 0; - break; - case AD_WERE: - hitmsg(mtmp, mattk); - if (uncancelled && !rn2(4) && u.ulycn == NON_PM - && !Protection_from_shape_changers && !defends(AD_WERE, uwep)) { - You_feel("feverish."); - exercise(A_CON, FALSE); - set_ulycn(monsndx(mdat)); - retouch_equipment(2); - } - break; - case AD_SGLD: - hitmsg(mtmp, mattk); - if (youmonst.data->mlet == mdat->mlet) - break; - if (!mtmp->mcan) - stealgold(mtmp); - break; + mhm.damage += d((int) mattk->damn, (int) mattk->damd); /* extra dmg */ - case AD_SSEX: - if (SYSOPT_SEDUCE) { - if (could_seduce(mtmp, &youmonst, mattk) == 1 && !mtmp->mcan) - if (doseduce(mtmp)) - return 3; - break; - } - /*FALLTHRU*/ - case AD_SITM: /* for now these are the same */ - case AD_SEDU: - if (is_animal(mtmp->data)) { - hitmsg(mtmp, mattk); - if (mtmp->mcan) - break; - /* Continue below */ - } else if (dmgtype(youmonst.data, AD_SEDU) - /* !SYSOPT_SEDUCE: when hero is attacking and AD_SSEX - is disabled, it would be changed to another damage - type, but when defending, it remains as-is */ - || dmgtype(youmonst.data, AD_SSEX)) { - pline("%s %s.", Monnam(mtmp), - Deaf ? "says something but you can't hear it" - : mtmp->minvent - ? "brags about the goods some dungeon explorer provided" - : "makes some remarks about how difficult theft is lately"); - 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; - } - break; - } - buf[0] = '\0'; - switch (steal(mtmp, buf)) { - case -1: - return 2; - case 0: - break; - default: - if (!is_animal(mtmp->data) && !tele_restrict(mtmp)) - (void) rloc(mtmp, TRUE); - if (is_animal(mtmp->data) && *buf) { - if (canseemon(mtmp)) - pline("%s tries to %s away with %s.", Monnam(mtmp), - locomotion(mtmp->data, "run"), buf); - } - monflee(mtmp, 0, FALSE, FALSE); - return 3; - } - break; + mhitm_adtyping(mtmp, mattk, &gy.youmonst, &mhm); - case AD_SAMU: - hitmsg(mtmp, mattk); - /* when the Wizard or quest nemesis hits, there's a 1/20 chance - to steal a quest artifact (any, not just the one for the hero's - own role) or the Amulet or one of the invocation tools */ - if (!rn2(20)) - stealamulet(mtmp); - break; + (void) mhitm_knockback(mtmp, &gy.youmonst, mattk, &mhm.hitflags, + (MON_WEP(mtmp) != 0)); - case AD_TLPT: - hitmsg(mtmp, mattk); - if (uncancelled) { - if (flags.verbose) - Your("position suddenly seems %suncertain!", - (Teleport_control && !Stunned && !unconscious()) ? "" - : "very "); - tele(); - /* As of 3.6.2: make sure damage isn't fatal; previously, it - was possible to be teleported and then drop dead at - the destination when QM's 1d4 damage gets applied below; - even though that wasn't "wrong", it seemed strange, - particularly if the teleportation had been controlled - [applying the damage first and not teleporting if fatal - is another alternative but it has its own complications] */ - if ((Half_physical_damage ? (dmg - 1) / 2 : dmg) - >= (tmphp = (Upolyd ? u.mh : u.uhp))) { - dmg = tmphp - 1; - if (Half_physical_damage) - dmg *= 2; /* doesn't actually increase damage; we only - * get here if half the original damage would - * would have been fatal, so double reduced - * damage will be less than original damage */ - if (dmg < 1) { /* implies (tmphp <= 1) */ - dmg = 1; - /* this might increase current HP beyond maximum HP but - it will be immediately reduced below, so that should - be indistinguishable from zero damage; we don't drop - damage all the way to zero because that inhibits any - passive counterattack if poly'd hero has one */ - if (Upolyd && u.mh == 1) - ++u.mh; - else if (!Upolyd && u.uhp == 1) - ++u.uhp; - /* [don't set context.botl here] */ - } - } - } - break; - case AD_RUST: - hitmsg(mtmp, mattk); - if (mtmp->mcan) - break; - if (u.umonnum == PM_IRON_GOLEM) { - You("rust!"); - /* KMH -- this is okay with unchanging */ - rehumanize(); - break; - } - erode_armor(&youmonst, ERODE_RUST); - break; - case AD_CORR: - hitmsg(mtmp, mattk); - if (mtmp->mcan) - break; - erode_armor(&youmonst, ERODE_CORRODE); - break; - case AD_DCAY: - hitmsg(mtmp, mattk); - if (mtmp->mcan) - break; - if (u.umonnum == PM_WOOD_GOLEM || u.umonnum == PM_LEATHER_GOLEM) { - You("rot!"); - /* KMH -- this is okay with unchanging */ - rehumanize(); - break; - } - erode_armor(&youmonst, ERODE_ROT); - break; - case AD_HEAL: - /* a cancelled nurse is just an ordinary monster, - * nurses don't heal those that cause petrification */ - if (mtmp->mcan || (Upolyd && touch_petrifies(youmonst.data))) { - hitmsg(mtmp, mattk); - break; - } - if (!uwep && !uarmu && !uarm && !uarmc - && !uarms && !uarmg && !uarmf && !uarmh) { - boolean goaway = FALSE; + if (mhm.done) + return mhm.hitflags; - pline("%s hits! (I hope you don't mind.)", Monnam(mtmp)); - if (Upolyd) { - u.mh += rnd(7); - if (!rn2(7)) { - /* no upper limit necessary; effect is temporary */ - u.mhmax++; - if (!rn2(13)) - goaway = TRUE; - } - if (u.mh > u.mhmax) - u.mh = u.mhmax; - } else { - u.uhp += rnd(7); - if (!rn2(7)) { - /* hard upper limit via nurse care: 25 * ulevel */ - if (u.uhpmax < 5 * u.ulevel + d(2 * u.ulevel, 10)) - u.uhpmax++; - if (!rn2(13)) - goaway = TRUE; - } - if (u.uhp > u.uhpmax) - u.uhp = u.uhpmax; - } - if (!rn2(3)) - exercise(A_STR, TRUE); - if (!rn2(3)) - exercise(A_CON, TRUE); - if (Sick) - make_sick(0L, (char *) 0, FALSE, SICK_ALL); - context.botl = 1; - if (goaway) { - mongone(mtmp); - return 2; - } else if (!rn2(33)) { - if (!tele_restrict(mtmp)) - (void) rloc(mtmp, TRUE); - monflee(mtmp, d(3, 6), TRUE, FALSE); - return 3; - } - dmg = 0; - } else { - if (Role_if(PM_HEALER)) { - if (!Deaf && !(moves % 5)) - verbalize("Doc, I can't help you unless you cooperate."); - dmg = 0; - } else - hitmsg(mtmp, mattk); - } - break; - case AD_CURS: - hitmsg(mtmp, mattk); - if (!night() && mdat == &mons[PM_GREMLIN]) - break; - if (!mtmp->mcan && !rn2(10)) { - if (!Deaf) { - if (Blind) - You_hear("laughter."); - else - pline("%s chuckles.", Monnam(mtmp)); - } - if (u.umonnum == PM_CLAY_GOLEM) { - pline("Some writing vanishes from your head!"); - /* KMH -- this is okay with unchanging */ - rehumanize(); - break; - } - attrcurse(); - } - break; - case AD_STUN: - hitmsg(mtmp, mattk); - if (!mtmp->mcan && !rn2(4)) { - make_stunned((HStun & TIMEOUT) + (long) dmg, TRUE); - dmg /= 2; - } - break; - case AD_ACID: - hitmsg(mtmp, mattk); - if (!mtmp->mcan && !rn2(3)) - if (Acid_resistance) { - pline("You're covered in %s, but it seems harmless.", - hliquid("acid")); - dmg = 0; - } else { - pline("You're covered in %s! It burns!", hliquid("acid")); - exercise(A_STR, FALSE); - } - else - dmg = 0; - break; - case AD_SLOW: - hitmsg(mtmp, mattk); - if (uncancelled && HFast && !defends(AD_SLOW, uwep) && !rn2(4)) - u_slow_down(); - break; - case AD_DREN: - hitmsg(mtmp, mattk); - if (uncancelled && !rn2(4)) /* 25% chance */ - drain_en(dmg); - dmg = 0; - break; - case AD_CONF: - hitmsg(mtmp, mattk); - if (!mtmp->mcan && !rn2(4) && !mtmp->mspec_used) { - mtmp->mspec_used = mtmp->mspec_used + (dmg + rn2(6)); - if (Confusion) - You("are getting even more confused."); - else - You("are getting confused."); - make_confused(HConfusion + dmg, FALSE); - } - dmg = 0; - break; - case AD_DETH: - pline("%s reaches out with its deadly touch.", Monnam(mtmp)); - if (is_undead(youmonst.data)) { - /* Still does normal damage */ - pline("Was that the touch of death?"); - break; - } - switch (rn2(20)) { - case 19: - case 18: - case 17: - if (!Antimagic) { - killer.format = KILLED_BY_AN; - Strcpy(killer.name, "touch of death"); - done(DIED); - dmg = 0; - break; - } - /*FALLTHRU*/ - default: /* case 16: ... case 5: */ - You_feel("your life force draining away..."); - permdmg = 1; /* actual damage done below */ - break; - case 4: - case 3: - case 2: - case 1: - case 0: - if (Antimagic) - shieldeff(u.ux, u.uy); - pline("Lucky for you, it didn't work!"); - dmg = 0; - break; - } - break; - case AD_PEST: - pline("%s reaches out, and you feel fever and chills.", Monnam(mtmp)); - (void) diseasemu(mdat); /* plus the normal damage */ - break; - case AD_FAMN: - pline("%s reaches out, and your body shrivels.", Monnam(mtmp)); - exercise(A_CON, FALSE); - if (!is_fainted()) - morehungry(rn1(40, 40)); - /* plus the normal damage */ - break; - case AD_SLIM: - hitmsg(mtmp, mattk); - if (!uncancelled) - break; - if (flaming(youmonst.data)) { - pline_The("slime burns away!"); - dmg = 0; - } else if (Unchanging || noncorporeal(youmonst.data) - || youmonst.data == &mons[PM_GREEN_SLIME]) { - You("are unaffected."); - dmg = 0; - } 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; - case AD_ENCH: /* KMH -- remove enchantment (disenchanter) */ - hitmsg(mtmp, mattk); - /* uncancelled is sufficient enough; please - don't make this attack less frequent */ - if (uncancelled) { - struct obj *obj = some_armor(&youmonst); - - if (!obj) { - /* some rings are susceptible; - amulets and blindfolds aren't (at present) */ - switch (rn2(5)) { - case 0: - break; - case 1: - obj = uright; - break; - case 2: - obj = uleft; - break; - case 3: - obj = uamul; - break; - case 4: - obj = ublindf; - break; - } - } - if (drain_item(obj, FALSE)) { - pline("%s less effective.", Yobjnam2(obj, "seem")); - } - } - break; - default: - dmg = 0; - break; - } if ((Upolyd ? u.mh : u.uhp) < 1) { /* already dead? call rehumanize() or done_in_by() as appropriate */ mdamageu(mtmp, 1); - dmg = 0; + mhm.damage = 0; } /* Negative armor class reduces damage done instead of fully protecting * against hits. */ - if (dmg && u.uac < 0) { - dmg -= rnd(-u.uac); - if (dmg < 1) - dmg = 1; + if (mhm.damage && u.uac < 0) { + mhm.damage -= rnd(-u.uac); + if (mhm.damage < 1) + mhm.damage = 1; } - if (dmg) { + if (mhm.damage > 0) { + /* [Half_physical_damage isn't applied to mhm.permdmg] */ if (Half_physical_damage - /* Mitre of Holiness */ - || (Role_if(PM_PRIEST) && uarmh && is_quest_artifact(uarmh) - && (is_undead(mtmp->data) || is_demon(mtmp->data) - || is_vampshifter(mtmp)))) - dmg = (dmg + 1) / 2; + /* Mitre of Holiness, even if not currently blessed */ + || (Role_if(PM_CLERIC) && uarmh && is_quest_artifact(uarmh) + && mon_hates_blessings(mtmp))) + mhm.damage = (mhm.damage + 1) / 2; - if (permdmg) { /* Death's life force drain */ + if (mhm.permdmg) { /* Death's life force drain */ int lowerlimit, *hpmax_p; /* * Apply some of the damage to permanent hit points: @@ -1721,38 +1230,38 @@ register struct attack *mattk; * otherwise 0..50% * Never reduces hpmax below 1 hit point per level. */ - permdmg = rn2(dmg / 2 + 1); + mhm.permdmg = rn2(mhm.damage / 2 + 1); if (Upolyd || u.uhpmax > 25 * u.ulevel) - permdmg = dmg; + mhm.permdmg = mhm.damage; else if (u.uhpmax > 10 * u.ulevel) - permdmg += dmg / 2; + mhm.permdmg += mhm.damage / 2; else if (u.uhpmax > 5 * u.ulevel) - permdmg += dmg / 4; + mhm.permdmg += mhm.damage / 4; if (Upolyd) { hpmax_p = &u.mhmax; - /* [can't use youmonst.m_lev] */ - lowerlimit = min((int) youmonst.data->mlevel, u.ulevel); + /* [can't use gy.youmonst.m_lev] */ + lowerlimit = min((int) gy.youmonst.data->mlevel, u.ulevel); } else { hpmax_p = &u.uhpmax; - lowerlimit = u.ulevel; + lowerlimit = minuhpmax(1); } - if (*hpmax_p - permdmg > lowerlimit) - *hpmax_p -= permdmg; + if (*hpmax_p - mhm.permdmg > lowerlimit) + *hpmax_p -= mhm.permdmg; else if (*hpmax_p > lowerlimit) *hpmax_p = lowerlimit; /* else unlikely... - * already at or below minimum threshold; do nothing */ - context.botl = 1; + * already at or below minimum threshold, do nothing to hpmax */ + disp.botl = TRUE; } - mdamageu(mtmp, dmg); + mdamageu(mtmp, mhm.damage); } - if (dmg) + if (mhm.damage) res = passiveum(olduasmon, mtmp, mattk); else - res = 1; + res = M_ATTK_HIT; stop_occupation(); return res; } @@ -1761,13 +1270,13 @@ register struct attack *mattk; * to see if an engulfing attack should immediately take affect, like * a passive attack. TRUE if engulfing blindness occurred */ boolean -gulp_blnd_check() +gulp_blnd_check(void) { struct attack *mattk; if (!Blinded && u.uswallow && (mattk = attacktype_fordmg(u.ustuck->data, AT_ENGL, AD_BLND)) - && can_blnd(u.ustuck, &youmonst, mattk->aatyp, (struct obj *) 0)) { + && can_blnd(u.ustuck, &gy.youmonst, mattk->aatyp, (struct obj *) 0)) { ++u.uswldtim; /* compensate for gulpmu change */ (void) gulpmu(u.ustuck, mattk); return TRUE; @@ -1775,53 +1284,65 @@ gulp_blnd_check() return FALSE; } -/* monster swallows you, or damage if u.uswallow */ -STATIC_OVL int -gulpmu(mtmp, mattk) -struct monst *mtmp; -struct attack *mattk; +/* monster swallows you, or damage if already swallowed (u.uswallow != 0) */ +staticfn int +gulpmu(struct monst *mtmp, struct attack *mattk) { struct trap *t = t_at(u.ux, u.uy); int tmp = d((int) mattk->damn, (int) mattk->damd); int tim_tmp; - struct obj *otmp2; + struct obj *otmp2, *nextobj; int i; boolean physical_damage = FALSE; if (!u.uswallow) { /* swallows you */ int omx = mtmp->mx, omy = mtmp->my; - if (!engulf_target(mtmp, &youmonst)) - return 0; + if (!engulf_target(mtmp, &gy.youmonst)) + return M_ATTK_MISS; if ((t && is_pit(t->ttyp)) && sobj_at(BOULDER, u.ux, u.uy)) - return 0; + return M_ATTK_MISS; + if (failed_grab(mtmp, &gy.youmonst, mattk)) + return M_ATTK_MISS; if (Punished) unplacebc(); /* ball&chain go away */ remove_monster(omx, omy); mtmp->mtrapped = 0; /* no longer on old trap */ place_monster(mtmp, u.ux, u.uy); - u.ustuck = mtmp; + set_ustuck(mtmp); newsym(mtmp->mx, mtmp->my); - if (is_animal(mtmp->data) && u.usteed) { + /* 5.0: dismount for all engulfers, not just for purple worms */ + if (u.usteed) { char buf[BUFSZ]; - /* Too many quirks presently if hero and steed - * are swallowed. Pretend purple worms don't - * like horses for now :-) - */ Strcpy(buf, mon_nam(u.usteed)); - pline("%s lunges forward and plucks you off %s!", Monnam(mtmp), - buf); + urgent_pline("%s %s forward and plucks you off %s!", + Some_Monnam(mtmp), + /* 't', purple 'w' */ + is_animal(mtmp->data) ? "lunges" + /* 'v', air 'E' */ + : is_whirly(mtmp->data) ? "whirls" + /* none (some 'v', already whirling) */ + : unsolid(mtmp->data) ? "flows" + /* ochre 'j', Juiblex */ + : amorphous(mtmp->data) ? "oozes" + /* none (all AT_ENGL are already covered) */ + : "surges", + buf); dismount_steed(DISMOUNT_ENGULFED); - } else - pline("%s engulfs you!", Monnam(mtmp)); + } else { + urgent_pline("%s %s!", Monnam(mtmp), + digests(mtmp->data) ? "swallows you whole" + : enfolds(mtmp->data) ? "folds itself around you" + : "engulfs you"); + } stop_occupation(); reset_occupations(); /* behave as if you had moved */ if (u.utrap) { You("are released from the %s!", - u.utraptype == TT_WEB ? "web" : "trap"); + (u.utraptype == TT_WEB) ? "web" : "trap"); reset_utrap(FALSE); } @@ -1833,9 +1354,13 @@ struct attack *mattk; unleash_all(); } - if (touch_petrifies(youmonst.data) && !resists_ston(mtmp)) { + if (touch_petrifies(gy.youmonst.data) && !resists_ston(mtmp)) { /* put the attacker back where it started; - the resulting statue will end up there */ + the resulting statue will end up there + [note: if poly'd hero could ride or non-poly'd hero could + acquire touch_petrifies() capability somehow, this code + would need to deal with possibility of steed having taken + engulfer's previous spot when hero was forcibly dismounted] */ remove_monster(mtmp->mx, mtmp->my); /* u.ux,u.uy */ place_monster(mtmp, omx, omy); minstapetrify(mtmp, TRUE); @@ -1843,8 +1368,8 @@ struct attack *mattk; fully swallowed yet so that won't work here */ if (Punished) placebc(); - u.ustuck = 0; - return (!DEADMONSTER(mtmp)) ? 0 : 2; + set_ustuck((struct monst *) 0); + return (!DEADMONSTER(mtmp)) ? M_ATTK_MISS : M_ATTK_AGR_DIED; } display_nhwindow(WIN_MESSAGE, FALSE); @@ -1854,28 +1379,31 @@ struct attack *mattk; for other swallowings, longer time means more chances for the swallower to attack */ if (mattk->adtyp == AD_DGST) { - tim_tmp = 25 - (int) mtmp->m_lev; - if (tim_tmp > 0) - tim_tmp = rnd(tim_tmp) / 2; - else if (tim_tmp < 0) - tim_tmp = -(rnd(-tim_tmp) / 2); /* having good armor & high constitution makes it take longer for you to be digested, but you'll end up trapped inside for longer too */ - tim_tmp += -u.uac + 10 + (ACURR(A_CON) / 3 - 1); + tim_tmp = (int)ACURR(A_CON) + 10 - (int)u.uac + rn2(20); + if (tim_tmp < 0) + tim_tmp = 0; + tim_tmp /= (int) mtmp->m_lev; + tim_tmp += 3; } else { /* higher level attacker takes longer to eject hero */ tim_tmp = rnd((int) mtmp->m_lev + 10 / 2); } /* u.uswldtim always set > 1 */ u.uswldtim = (unsigned) ((tim_tmp < 2) ? 2 : tim_tmp); - swallowed(1); - for (otmp2 = invent; otmp2; otmp2 = otmp2->nobj) - (void) snuff_lit(otmp2); + swallowed(1); /* update the map display, shows hero swallowed */ + if (!flaming(mtmp->data)) { + for (otmp2 = gi.invent; otmp2; otmp2 = nextobj) { + nextobj = otmp2->nobj; + (void) snuff_lit(otmp2); + } + } } if (mtmp != u.ustuck) - return 0; + return M_ATTK_MISS; if (Punished) { /* ball&chain are in limbo while swallowed; update their internal location to be at swallower's spot */ @@ -1910,23 +1438,26 @@ struct attack *mattk; physical_damage = TRUE; if (mtmp->data == &mons[PM_FOG_CLOUD]) { You("are laden with moisture and %s", - flaming(youmonst.data) + flaming(gy.youmonst.data) ? "are smoldering out!" : Breathless ? "find it mildly uncomfortable." - : amphibious(youmonst.data) + : amphibious(gy.youmonst.data) ? "feel comforted." : "can barely breathe!"); - /* NB: Amphibious includes Breathless */ - if (Amphibious && !flaming(youmonst.data)) + if ((Amphibious || Breathless) && !flaming(gy.youmonst.data)) tmp = 0; } else { - You("are pummeled with debris!"); + You("are %s!", enfolds(mtmp->data) ? "being squashed" + : "pummeled with debris"); exercise(A_STR, FALSE); } break; case AD_ACID: if (Acid_resistance) { You("are covered with a seemingly harmless goo."); + /* NB: the monst[un]seesu calls in gulpmu are no-ops since the + hero must be currently swallowed for the attack to hit... */ + monstseesu(M_SEEN_ACID); tmp = 0; } else { if (Hallucination) @@ -1934,10 +1465,11 @@ struct attack *mattk; else You("are covered in slime! It burns!"); exercise(A_STR, FALSE); + monstunseesu(M_SEEN_ACID); } break; case AD_BLND: - if (can_blnd(mtmp, &youmonst, mattk->aatyp, (struct obj *) 0)) { + if (can_blnd(mtmp, &gy.youmonst, mattk->aatyp, (struct obj *) 0)) { if (!Blind) { long was_blinded = Blinded; @@ -1948,7 +1480,7 @@ struct attack *mattk; Your1(vision_clears); } else /* keep him blind until disgorged */ - make_blinded(Blinded + 1, FALSE); + incr_itimeout(&HBlinded, 1L); } tmp = 0; break; @@ -1958,8 +1490,11 @@ struct attack *mattk; if (Shock_resistance) { shieldeff(u.ux, u.uy); You("seem unhurt."); + monstseesu(M_SEEN_ELEC); ugolemeffects(AD_ELEC, tmp); tmp = 0; + } else { + monstunseesu(M_SEEN_ELEC); } } else tmp = 0; @@ -1969,10 +1504,13 @@ struct attack *mattk; if (Cold_resistance) { shieldeff(u.ux, u.uy); You_feel("mildly chilly."); + monstseesu(M_SEEN_COLD); ugolemeffects(AD_COLD, tmp); tmp = 0; - } else + } else { You("are freezing to death!"); + monstunseesu(M_SEEN_COLD); + } } else tmp = 0; break; @@ -1981,10 +1519,13 @@ struct attack *mattk; if (Fire_resistance) { shieldeff(u.ux, u.uy); You_feel("mildly hot."); + monstseesu(M_SEEN_FIRE); ugolemeffects(AD_FIRE, tmp); tmp = 0; - } else + } else { You("are burning to a crisp!"); + monstunseesu(M_SEEN_FIRE); + } burn_away_slime(); } else tmp = 0; @@ -1996,7 +1537,7 @@ struct attack *mattk; case AD_DREN: /* AC magic cancellation doesn't help when engulfed */ if (!mtmp->mcan && rn2(4)) /* 75% chance */ - drain_en(tmp); + drain_en(tmp, FALSE); tmp = 0; break; default: @@ -2005,144 +1546,126 @@ struct attack *mattk; break; } - if (physical_damage) + if (physical_damage) { + /* same damage reduction for AC as in hitmu */ + if (u.uac < 0) + tmp -= rnd(-u.uac); + if (tmp < 0) + tmp = 1; + tmp = Maybe_Half_Phys(tmp); + } + gm.mswallower = mtmp; /* match gulpmm() */ mdamageu(mtmp, tmp); + gm.mswallower = 0; if (tmp) stop_occupation(); if (!u.uswallow) { ; /* life-saving has already expelled swallowed hero */ - } else if (touch_petrifies(youmonst.data) && !resists_ston(mtmp)) { + } else if (touch_petrifies(gy.youmonst.data) && !resists_ston(mtmp)) { pline("%s very hurriedly %s you!", Monnam(mtmp), - is_animal(mtmp->data) ? "regurgitates" : "expels"); + digests(mtmp->data) ? "regurgitates" + : enfolds(mtmp->data) ? "releases" + : "expels"); expels(mtmp, mtmp->data, FALSE); - } else if (!u.uswldtim || youmonst.data->msize >= MZ_HUGE) { + } else if (!u.uswldtim || gy.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 swallowed is still possible */ - You("get %s!", is_animal(mtmp->data) ? "regurgitated" : "expelled"); + You("get %s!", digests(mtmp->data) ? "regurgitated" + : enfolds(mtmp->data) ? "released" + : "expelled"); if (flags.verbose - && (is_animal(mtmp->data) - || (dmgtype(mtmp->data, AD_DGST) && Slow_digestion))) + && (digests(mtmp->data) && Slow_digestion)) pline("Obviously %s doesn't like your taste.", mon_nam(mtmp)); expels(mtmp, mtmp->data, FALSE); } - return 1; + return M_ATTK_HIT; } /* monster explodes in your face */ -STATIC_OVL int -explmu(mtmp, mattk, ufound) -struct monst *mtmp; -struct attack *mattk; -boolean ufound; +staticfn int +explmu( + struct monst *mtmp, + struct attack *mattk, + boolean ufound) { - boolean physical_damage = TRUE, kill_agr = TRUE; + boolean kill_agr = TRUE; + boolean not_affected; + int tmp; if (mtmp->mcan) - return 0; + return M_ATTK_MISS; + + tmp = d((int) mattk->damn, (int) mattk->damd); + not_affected = defended(mtmp, (int) mattk->adtyp); if (!ufound) { pline("%s explodes at a spot in %s!", canseemon(mtmp) ? Monnam(mtmp) : "It", - levl[mtmp->mux][mtmp->muy].typ == WATER ? "empty water" - : "thin air"); + is_waterwall(mtmp->mux,mtmp->muy) ? "empty water" + : "thin air"); } else { - int tmp = d((int) mattk->damn, (int) mattk->damd); - boolean not_affected = defends((int) mattk->adtyp, uwep); - hitmsg(mtmp, mattk); + } - switch (mattk->adtyp) { - case AD_COLD: - physical_damage = FALSE; - not_affected |= Cold_resistance; - goto common; - case AD_FIRE: - physical_damage = FALSE; - not_affected |= Fire_resistance; - goto common; - case AD_ELEC: - physical_damage = FALSE; - not_affected |= Shock_resistance; - goto common; - case AD_PHYS: - /* there aren't any exploding creatures with AT_EXPL attack - for AD_PHYS damage but there might be someday; without this, - static analysis complains that 'physical_damage' is always - False when tested below; it's right, but having that in - place means one less thing to update if AD_PHYS gets added */ - common: - - if (!not_affected) { - if (ACURR(A_DEX) > rnd(20)) { - You("duck some of the blast."); - tmp = (tmp + 1) / 2; - } else { - if (flags.verbose) - You("get blasted!"); - } - if (mattk->adtyp == AD_FIRE) - burn_away_slime(); - if (physical_damage) - tmp = Maybe_Half_Phys(tmp); - mdamageu(mtmp, tmp); - } - break; - - case AD_BLND: - not_affected = resists_blnd(&youmonst); - if (!not_affected) { - /* sometimes you're affected even if it's invisible */ - if (mon_visible(mtmp) || (rnd(tmp /= 2) > u.ulevel)) { - You("are blinded by a blast of light!"); - make_blinded((long) tmp, FALSE); - if (!Blind) - Your1(vision_clears); - } else if (flags.verbose) - You("get the impression it was not terribly bright."); - } - break; - - case AD_HALU: - not_affected |= Blind || (u.umonnum == PM_BLACK_LIGHT - || u.umonnum == PM_VIOLET_FUNGUS - || dmgtype(youmonst.data, AD_STUN)); - if (!not_affected) { - boolean chg; - if (!Hallucination) - You("are caught in a blast of kaleidoscopic light!"); - /* avoid hallucinating the black light as it dies */ - mondead(mtmp); /* remove it from map now */ - kill_agr = FALSE; /* already killed (maybe lifesaved) */ - chg = - make_hallucinated(HHallucination + (long) tmp, FALSE, 0L); - You("%s.", chg ? "are freaked out" : "seem unaffected"); - } - break; - - default: - break; + switch (mattk->adtyp) { + case AD_COLD: + case AD_FIRE: + case AD_ELEC: + mon_explodes(mtmp, mattk); + if (!DEADMONSTER(mtmp)) + kill_agr = FALSE; /* lifesaving? */ + break; + case AD_BLND: + not_affected = resists_blnd(&gy.youmonst); + if (ufound && !not_affected) { + /* sometimes you're affected even if it's invisible */ + if (mon_visible(mtmp) || (rnd(tmp /= 2) > u.ulevel)) { + You("are blinded by a blast of light!"); + make_blinded((long) tmp, FALSE); + if (!Blind) + Your1(vision_clears); + } else if (flags.verbose) + You("get the impression it was not terribly bright."); } - if (not_affected) { - You("seem unaffected by it."); - ugolemeffects((int) mattk->adtyp, tmp); + break; + case AD_HALU: + not_affected |= Blind || (u.umonnum == PM_BLACK_LIGHT + || u.umonnum == PM_VIOLET_FUNGUS + || dmgtype(gy.youmonst.data, AD_STUN)); + if (ufound && !not_affected) { + boolean chg; + if (!Hallucination) + You("are caught in a blast of kaleidoscopic light!"); + /* avoid hallucinating the black light as it dies */ + mondead(mtmp); /* remove it from map now */ + kill_agr = FALSE; /* already killed (maybe lifesaved) */ + chg = + make_hallucinated(HHallucination + (long) tmp, FALSE, 0L); + You("%s.", chg ? "are freaked out" : "seem unaffected"); } + break; + default: + impossible("unknown exploder damage type %d", mattk->adtyp); + break; } - if (kill_agr) + if (not_affected) { + You("seem unaffected by it."); + ugolemeffects((int) mattk->adtyp, tmp); + } + if (kill_agr && !DEADMONSTER(mtmp)) mondead(mtmp); wake_nearto(mtmp->mx, mtmp->my, 7 * 7); - return (!DEADMONSTER(mtmp)) ? 0 : 2; + return (!DEADMONSTER(mtmp)) ? M_ATTK_MISS : M_ATTK_AGR_DIED; } /* monster gazes at you */ int -gazemu(mtmp, mattk) -struct monst *mtmp; -struct attack *mattk; +gazemu(struct monst *mtmp, struct attack *mattk) { static const char *const reactions[] = { "confused", /* [0] */ @@ -2153,40 +1676,56 @@ struct attack *mattk; "dulled", /* [7] */ }; int react = -1; - boolean cancelled = (mtmp->mcan != 0), already = FALSE; + boolean is_medusa, reflectable, + cancelled = (mtmp->mcan != 0), already = FALSE, + mcanseeu = (canseemon(mtmp) && couldsee(mtmp->mx, mtmp->my) + && mtmp->mcansee); + + if (m_seenres(mtmp, cvt_adtyp_to_mseenres(mattk->adtyp))) + return M_ATTK_MISS; + is_medusa = (mtmp->data == &mons[PM_MEDUSA]); + reflectable = (Reflecting && couldsee(mtmp->mx, mtmp->my) && is_medusa); /* assumes that hero has to see monster's gaze in order to be affected, rather than monster just having to look at hero; + Unaware: asleep or unconscious => not blind but won't see; when hallucinating, hero's brain doesn't register what it's seeing correctly so the gaze is usually ineffective [this could be taken a lot farther and select a gaze effect appropriate to what's currently being displayed, giving ordinary monsters a gaze attack when hero thinks he or she is facing a gazing creature, but let's not go that far...] */ - if (Hallucination && rn2(4)) + if ((Hallucination && rn2(4)) || (Unaware && !reflectable)) cancelled = TRUE; switch (mattk->adtyp) { case AD_STON: + /* note: Medusa is the only monster with stoning gaze, so + 'is_medusa' will always be True here */ if (cancelled || !mtmp->mcansee) { if (!canseemon(mtmp)) break; /* silently */ - pline("%s %s.", Monnam(mtmp), - (mtmp->data == &mons[PM_MEDUSA] && mtmp->mcan) - ? "doesn't look all that ugly" - : "gazes ineffectually"); + if (Unaware) { /* can't see attacker even though not blind */ + react = is_medusa ? 4 : 2; /* irritated or puzzled */ + break; + } + if (is_medusa && Hallucination && !rn2(3)) + pline("Someone seems overdue for a serpent cut."); + else + pline_mon(mtmp, "%s %s.", Monnam(mtmp), + (is_medusa && mtmp->mcan && !react) + ? "doesn't look all that ugly" + : "gazes ineffectually"); break; - } - if (Reflecting && couldsee(mtmp->mx, mtmp->my) - && mtmp->data == &mons[PM_MEDUSA]) { + } + if (reflectable) { /* hero has line of sight to Medusa and she's not blind */ boolean useeit = canseemon(mtmp); if (useeit) (void) ureflects("%s gaze is reflected by your %s.", s_suffix(Monnam(mtmp))); - if (mon_reflects( - mtmp, !useeit ? (char *) 0 + if (mon_reflects(mtmp, !useeit ? (char *) 0 : "The gaze is reflected away by %s %s!")) break; if (!m_canseeu(mtmp)) { /* probably you're invisible */ @@ -2197,29 +1736,28 @@ struct attack *mattk; break; } if (useeit) - pline("%s is turned to stone!", Monnam(mtmp)); - stoned = TRUE; + pline_mon(mtmp, "%s is turned to stone!", Monnam(mtmp)); + gs.stoned = TRUE; killed(mtmp); if (!DEADMONSTER(mtmp)) break; - return 2; + return M_ATTK_AGR_DIED; } if (canseemon(mtmp) && couldsee(mtmp->mx, mtmp->my) - && !Stone_resistance) { + && !Stone_resistance && !Unaware) { You("meet %s gaze.", s_suffix(mon_nam(mtmp))); stop_occupation(); - if (poly_when_stoned(youmonst.data) && polymon(PM_STONE_GOLEM)) + if (poly_when_stoned(gy.youmonst.data) && polymon(PM_STONE_GOLEM)) break; - You("turn to stone..."); - killer.format = KILLED_BY; - Strcpy(killer.name, mtmp->data->mname); + urgent_pline("You turn to stone..."); + svk.killer.format = KILLED_BY; + Strcpy(svk.killer.name, pmname(mtmp->data, Mgender(mtmp))); done(STONING); } break; case AD_CONF: - if (canseemon(mtmp) && couldsee(mtmp->mx, mtmp->my) && mtmp->mcansee - && !mtmp->mspec_used && rn2(5)) { + if (mcanseeu && !mtmp->mspec_used && rn2(5)) { if (cancelled) { react = 0; /* "confused" */ already = (mtmp->mconf != 0); @@ -2228,7 +1766,8 @@ struct attack *mattk; mtmp->mspec_used = mtmp->mspec_used + (conf + rn2(6)); if (!Confusion) - pline("%s gaze confuses you!", s_suffix(Monnam(mtmp))); + pline_mon(mtmp, "%s gaze confuses you!", + s_suffix(Monnam(mtmp))); else You("are getting more and more confused."); make_confused(HConfusion + conf, FALSE); @@ -2237,8 +1776,7 @@ struct attack *mattk; } break; case AD_STUN: - if (canseemon(mtmp) && couldsee(mtmp->mx, mtmp->my) && mtmp->mcansee - && !mtmp->mspec_used && rn2(5)) { + if (mcanseeu && !mtmp->mspec_used && rn2(5)) { if (cancelled) { react = 1; /* "stunned" */ already = (mtmp->mstun != 0); @@ -2246,15 +1784,15 @@ struct attack *mattk; int stun = d(2, 6); mtmp->mspec_used = mtmp->mspec_used + (stun + rn2(6)); - pline("%s stares piercingly at you!", Monnam(mtmp)); + pline_mon(mtmp, "%s stares piercingly at you!", Monnam(mtmp)); make_stunned((HStun & TIMEOUT) + (long) stun, TRUE); stop_occupation(); } } break; case AD_BLND: - if (canseemon(mtmp) && !resists_blnd(&youmonst) - && distu(mtmp->mx, mtmp->my) <= BOLT_LIM * BOLT_LIM) { + if (canseemon(mtmp) && !resists_blnd(&gy.youmonst) + && mdistu(mtmp) <= BOLT_LIM * BOLT_LIM) { if (cancelled) { react = rn1(2, 2); /* "puzzled" || "dazzled" */ already = (mtmp->mcansee == 0); @@ -2284,26 +1822,31 @@ struct attack *mattk; } break; case AD_FIRE: - if (canseemon(mtmp) && couldsee(mtmp->mx, mtmp->my) && mtmp->mcansee - && !mtmp->mspec_used && rn2(5)) { + if (mcanseeu && !mtmp->mspec_used && rn2(5)) { if (cancelled) { react = rn1(2, 4); /* "irritated" || "inflamed" */ } else { - int dmg = d(2, 6), lev = (int) mtmp->m_lev; + int dmg = d(2, 6), orig_dmg = dmg, lev = (int) mtmp->m_lev; - pline("%s attacks you with a fiery gaze!", Monnam(mtmp)); + pline_mon(mtmp, "%s attacks you with a fiery gaze!", + Monnam(mtmp)); stop_occupation(); if (Fire_resistance) { + shieldeff(u.ux, u.uy); pline_The("fire doesn't feel hot!"); + monstseesu(M_SEEN_FIRE); + ugolemeffects(AD_FIRE, d(12, 6)); dmg = 0; + } else { + monstunseesu(M_SEEN_FIRE); } burn_away_slime(); if (lev > rn2(20)) - destroy_item(SCROLL_CLASS, AD_FIRE); - if (lev > rn2(20)) - destroy_item(POTION_CLASS, AD_FIRE); - if (lev > rn2(25)) - destroy_item(SPBOOK_CLASS, AD_FIRE); + (void) burnarmor(&gy.youmonst); + if (lev > rn2(20)) { + (void) destroy_items(&gy.youmonst, AD_FIRE, orig_dmg); + ignite_items(gi.invent); + } if (dmg) mdamageu(mtmp, dmg); } @@ -2311,8 +1854,7 @@ struct attack *mattk; break; #ifdef PM_BEHOLDER /* work in progress */ case AD_SLEE: - if (canseemon(mtmp) && couldsee(mtmp->mx, mtmp->my) && mtmp->mcansee - && multi >= 0 && !rn2(5) && !Sleep_resistance) { + if (mcanseeu && gm.multi >= 0 && !rn2(5) && !Sleep_resistance) { if (cancelled) { react = 6; /* "tired" */ already = (mtmp->mfrozen != 0); /* can't happen... */ @@ -2320,12 +1862,13 @@ struct attack *mattk; fall_asleep(-rnd(10), TRUE); pline("%s gaze makes you very sleepy...", s_suffix(Monnam(mtmp))); + monstunseesu(M_SEEN_SLEEP); } } break; case AD_SLOW: - if (canseemon(mtmp) && couldsee(mtmp->mx, mtmp->my) && mtmp->mcansee - && (HFast & (INTRINSIC | TIMEOUT)) && !defends(AD_SLOW, uwep) + if (mcanseeu + && (HFast & (INTRINSIC | TIMEOUT)) && !defended(mtmp, AD_SLOW) && !rn2(4)) { if (cancelled) { react = 7; /* "dulled" */ @@ -2346,27 +1889,38 @@ struct attack *mattk; react = rn2(SIZE(reactions)); /* cancelled/hallucinatory feedback; monster might look "confused", "stunned",&c but we don't actually set corresponding attribute */ - pline("%s looks %s%s.", Monnam(mtmp), + pline_mon(mtmp, "%s looks %s%s.", Monnam(mtmp), !rn2(3) ? "" : already ? "quite " : (!rn2(2) ? "a bit " : "somewhat "), reactions[react]); } - return 0; + return M_ATTK_MISS; } /* mtmp hits you for n points damage */ void -mdamageu(mtmp, n) -struct monst *mtmp; -int n; +mdamageu(struct monst *mtmp, int n) { - context.botl = 1; + if (n < 0) { + impossible("mdamageu for negative damage? (%d)", n); + n = 0; + } + + disp.botl = TRUE; if (Upolyd) { u.mh -= n; + showdamage(n); + /* caller might have reduced mhmax before calling mdamageu() */ + if (u.mh > u.mhmax) + u.mh = u.mhmax; if (u.mh < 1) rehumanize(); } else { u.uhp -= n; + showdamage(n); + /* caller might have reduced uhpmax before calling mdamageu() */ + if (u.uhp > u.uhpmax) + u.uhp = u.uhpmax; if (u.uhp < 1) done_in_by(mtmp, DIED); } @@ -2377,19 +1931,19 @@ int n; * 2 if wrong gender for nymph */ int -could_seduce(magr, mdef, mattk) -struct monst *magr, *mdef; -struct attack *mattk; /* non-Null: current attack; Null: general capability */ +could_seduce( + struct monst *magr, struct monst *mdef, + struct attack *mattk) /* non-Null: current atk; Null: genrl capability */ { struct permonst *pagr; boolean agrinvis, defperc; - xchar genagr, gendef; + xint16 genagr, gendef; int adtyp; if (is_animal(magr->data)) return 0; - if (magr == &youmonst) { - pagr = youmonst.data; + if (magr == &gy.youmonst) { + pagr = gy.youmonst.data; agrinvis = (Invis != 0); genagr = poly_gender(); } else { @@ -2397,7 +1951,7 @@ struct attack *mattk; /* non-Null: current attack; Null: general capability */ agrinvis = magr->minvis; genagr = gender(magr); } - if (mdef == &youmonst) { + if (mdef == &gy.youmonst) { defperc = (See_invisible != 0); gendef = poly_gender(); } else { @@ -2419,8 +1973,7 @@ struct attack *mattk; /* non-Null: current attack; Null: general capability */ for seduction, both pass the could_seduce() test; incubi/succubi have three attacks, their claw attacks for damage don't pass the test */ - if ((pagr->mlet != S_NYMPH - && pagr != &mons[PM_INCUBUS] && pagr != &mons[PM_SUCCUBUS]) + if ((pagr->mlet != S_NYMPH && pagr != &mons[PM_AMOROUS_DEMON]) || (adtyp != AD_SEDU && adtyp != AD_SSEX && adtyp != AD_SITM)) return 0; @@ -2429,22 +1982,23 @@ struct attack *mattk; /* non-Null: current attack; Null: general capability */ /* returns 1 if monster teleported (or hero leaves monster's vicinity) */ int -doseduce(mon) -struct monst *mon; +doseduce(struct monst *mon) { struct obj *ring, *nring; - boolean fem = (mon->data == &mons[PM_SUCCUBUS]); /* otherwise incubus */ + boolean fem = (mon->data == &mons[PM_AMOROUS_DEMON] + && Mgender(mon) == FEMALE); /* otherwise incubus */ boolean seewho, naked; /* True iff no armor */ int attr_tot, tried_gloves = 0; char qbuf[QBUFSZ], Who[QBUFSZ]; if (mon->mcan || mon->mspec_used) { - pline("%s acts as though %s has got a %sheadache.", Monnam(mon), - mhe(mon), mon->mcan ? "severe " : ""); + pline_mon(mon, "%s acts as though %s has got a %sheadache.", + Monnam(mon), mhe(mon), mon->mcan ? "severe " : ""); return 0; } - if (unconscious()) { - pline("%s seems dismayed at your lack of response.", Monnam(mon)); + if (unresponsive()) { + pline_mon(mon, "%s seems dismayed at your lack of response.", + Monnam(mon)); return 0; } seewho = canseemon(mon); @@ -2462,7 +2016,7 @@ struct monst *mon; if (welded(uwep)) tried_gloves = 1; - for (ring = invent; ring; ring = nring) { + for (ring = gi.invent; ring; ring = nring) { nring = ring->nobj; if (ring->otyp != RIN_ADORNMENT) continue; @@ -2480,7 +2034,8 @@ struct monst *mon; " looks pretty. May I have it?\"", ring, xname, simpleonames, "ring"); makeknown(RIN_ADORNMENT); - if (yn(qbuf) == 'n') + SetVoice(mon, 0, 80, 0); + if (y_n(qbuf) == 'n') continue; } else pline("%s decides she'd like %s, and takes it.", @@ -2507,10 +2062,11 @@ struct monst *mon; /* confirmation prompt when charisma is high bypassed if deaf */ if (!Deaf && rn2(20) < ACURR(A_CHA)) { (void) safe_qbuf(qbuf, "\"That ", - " looks pretty. Would you wear it for me?\"", + " looks pretty. Would you wear it for me?\"", ring, xname, simpleonames, "ring"); makeknown(RIN_ADORNMENT); - if (yn(qbuf) == 'n') + SetVoice(mon, 0, 80, 0); + if (y_n(qbuf) == 'n') continue; } else { pline("%s decides you'd look prettier wearing %s,", @@ -2536,7 +2092,7 @@ struct monst *mon; Ring_gone(uright); /* ring removal might cause loss of levitation which could drop hero onto trap that transports hero somewhere else */ - if (u.utotype || distu(mon->mx, mon->my) > 2) + if (u.utotype || !m_next2u(mon)) return 1; setworn(ring, RIGHT_RING); } else if (uleft && uleft->otyp != RIN_ADORNMENT) { @@ -2544,7 +2100,7 @@ struct monst *mon; pline("%s replaces %s with %s.", Who, yname(uleft), yname(ring)); Ring_gone(uleft); - if (u.utotype || distu(mon->mx, mon->my) > 2) + if (u.utotype || !m_next2u(mon)) return 1; setworn(ring, LEFT_RING); } else @@ -2555,11 +2111,11 @@ struct monst *mon; } naked = (!uarmc && !uarmf && !uarmg && !uarms && !uarmh && !uarmu); - pline("%s %s%s.", Who, - Deaf ? "seems to murmur into your ear" - : naked ? "murmurs sweet nothings into your ear" - : "murmurs in your ear", - naked ? "" : ", while helping you undress"); + urgent_pline("%s %s%s.", Who, + Deaf ? "seems to murmur into your ear" + : naked ? "murmurs sweet nothings into your ear" + : "murmurs in your ear", + naked ? "" : ", while helping you undress"); mayberem(mon, Who, uarmc, cloak_simple_name(uarmc)); if (!uarmc) mayberem(mon, Who, uarm, suit_simple_name(uarm)); @@ -2577,27 +2133,43 @@ struct monst *mon; and changing location, so hero might not be adjacent to seducer any more (mayberem() has its own adjacency test so we don't need to check after each potential removal) */ - if (u.utotype || distu(mon->mx, mon->my) > 2) + if (u.utotype || !m_next2u(mon)) return 1; if (uarm || uarmc) { - if (!Deaf) - verbalize("You're such a %s; I wish...", - flags.female ? "sweet lady" : "nice guy"); - else if (seewho) - pline("%s appears to sigh.", Monnam(mon)); + if (!Deaf) { + if (!(ld() && mon->female)) { + SetVoice(mon, 0, 80, 0); + verbalize("You're such a %s; I wish...", + flags.female ? "sweet lady" : "nice guy"); + } else { + struct obj *yourgloves = u_carried_gloves(); + + /* have her call your gloves by their correct + name, possibly revealing them to you */ + if (yourgloves) + observe_object(yourgloves); + verbalize("Well, then you owe me %s%s!", + yourgloves ? yname(yourgloves) + : "twelve pairs of gloves", + yourgloves ? " and eleven more pairs of gloves" + : ""); + } + } else if (seewho) + pline_mon(mon, "%s appears to sigh.", Monnam(mon)); /* else no regret message if can't see or hear seducer */ if (!tele_restrict(mon)) - (void) rloc(mon, TRUE); + (void) rloc(mon, RLOC_MSG); return 1; } if (u.ualign.type == A_CHAOTIC) adjalign(1); /* by this point you have discovered mon's identity, blind or not... */ - pline("Time stands still while you and %s lie in each other's arms...", - noit_mon_nam(mon)); + urgent_pline( + "Time stands still while you and %s lie in each other's arms...", + noit_mon_nam(mon)); /* 3.6.1: a combined total for charisma plus intelligence of 35-1 used to guarantee successful outcome; now total maxes out at 32 as far as deciding what will happen; chance for bad outcome when @@ -2620,16 +2192,16 @@ struct monst *mon; You("are down in the dumps."); (void) adjattrib(A_CON, -1, TRUE); exercise(A_CON, FALSE); - context.botl = 1; + disp.botl = TRUE; break; case 2: Your("senses are dulled."); (void) adjattrib(A_WIS, -1, TRUE); exercise(A_WIS, FALSE); - context.botl = 1; + disp.botl = TRUE; break; case 3: - if (!resists_drli(&youmonst)) { + if (!resists_drli(&gy.youmonst)) { You_feel("out of shape."); losexp("overexertion"); } else { @@ -2657,18 +2229,20 @@ struct monst *mon; You_feel("raised to your full potential."); exercise(A_CON, TRUE); u.uen = (u.uenmax += rnd(5)); + if (u.uenmax > u.uenpeak) + u.uenpeak = u.uenmax; break; case 1: You_feel("good enough to do it again."); (void) adjattrib(A_CON, 1, TRUE); exercise(A_CON, TRUE); - context.botl = 1; + disp.botl = TRUE; break; case 2: You("will always remember %s...", noit_mon_nam(mon)); (void) adjattrib(A_WIS, 1, TRUE); exercise(A_WIS, TRUE); - context.botl = 1; + disp.botl = TRUE; break; case 3: pline("That was a very educational experience."); @@ -2681,7 +2255,7 @@ struct monst *mon; if (Upolyd) u.mh = u.mhmax; exercise(A_STR, TRUE); - context.botl = 1; + disp.botl = TRUE; break; } } @@ -2692,10 +2266,11 @@ struct monst *mon; pline("%s demands that you pay %s, but you refuse...", noit_Monnam(mon), noit_mhim(mon)); } else if (u.umonnum == PM_LEPRECHAUN) { - pline("%s tries to take your money, but fails...", noit_Monnam(mon)); + pline_mon(mon, "%s tries to take your gold, but fails...", + noit_Monnam(mon)); } else { long cost; - long umoney = money_cnt(invent); + long umoney = money_cnt(gi.invent); if (umoney > (long) LARGEST_INT - 10L) cost = (long) rnd(LARGEST_INT) + 500L; @@ -2709,27 +2284,31 @@ struct monst *mon; if (cost > umoney) cost = umoney; if (!cost) { - verbalize("It's on the house!"); + if (!Deaf) { + SetVoice(mon, 0, 80, 0); + verbalize("It's on the house!"); + } else { + pline("No charge."); + } } else { - pline("%s takes %ld %s for services rendered!", noit_Monnam(mon), - cost, currency(cost)); + pline_mon(mon, "%s takes %ld %s for services rendered!", + noit_Monnam(mon), cost, currency(cost)); money2mon(mon, cost); - context.botl = 1; + disp.botl = TRUE; } } if (!rn2(25)) mon->mcan = 1; /* monster is worn out */ if (!tele_restrict(mon)) - (void) rloc(mon, TRUE); + (void) rloc(mon, RLOC_MSG); return 1; } -STATIC_OVL void -mayberem(mon, seducer, obj, str) -struct monst *mon; -const char *seducer; /* only used for alternate message */ -struct obj *obj; -const char *str; +/* 'mon' tries to remove a piece of hero's armor */ +staticfn void +mayberem(struct monst *mon, + const char *seducer, /* only used for alternate message */ + struct obj *obj, const char *str) { char qbuf[QBUFSZ]; @@ -2737,22 +2316,24 @@ const char *str; return; /* removal of a previous item might have sent the hero elsewhere (loss of levitation that leads to landing on a transport trap) */ - if (u.utotype || distu(mon->mx, mon->my) > 2) + if (u.utotype || !m_next2u(mon)) return; /* being deaf overrides confirmation prompt for high charisma */ if (Deaf) { pline("%s takes off your %s.", seducer, str); } else if (rn2(20) < ACURR(A_CHA)) { + SetVoice(mon, 0, 80, 0); /* y_n aka yn_function is set up for this */ Sprintf(qbuf, "\"Shall I remove your %s, %s?\"", str, (!rn2(2) ? "lover" : !rn2(2) ? "dear" : "sweetheart")); - if (yn(qbuf) == 'n') + if (y_n(qbuf) == 'n') return; } else { char hairbuf[BUFSZ]; Sprintf(hairbuf, "let me run my fingers through your %s", body_part(HAIR)); + SetVoice(mon, 0, 80, 0); verbalize("Take off your %s; %s.", str, (obj == uarm) ? "let's get a little closer" @@ -2770,6 +2351,79 @@ const char *str; remove_worn_item(obj, TRUE); } +staticfn int +assess_dmg(struct monst *mtmp, int tmp) +{ + if ((mtmp->mhp -= tmp) <= 0) { + pline_mon(mtmp, "%s dies!", Monnam(mtmp)); + xkilled(mtmp, XKILL_NOMSG); + if (!DEADMONSTER(mtmp)) + return M_ATTK_HIT; + return M_ATTK_AGR_DIED; + } + return M_ATTK_HIT; +} + +#if 0 +/* returns True if monster has a range attack in its repertoire + that it will actually utilize. Caller provides the assessment + callback (optional). Callback returns 0 if the attack is + active */ + +boolean +ranged_attk_assessed( + struct monst *mtmp, + boolean (*assessfunc)(struct monst *, int)) +{ + int i; + struct permonst *ptr = mtmp->data; + + for (i = 0; i < NATTK; i++) + if (DISTANCE_ATTK_TYPE(ptr->mattk[i].aatyp)) { + if (!assessfunc || (*assessfunc)(mtmp, i) == 0) + return TRUE; + } + return FALSE; +} +#endif + +/* can be used as ranged_attk_assessed() callback. + Returns TRUE if monster is avoiding use of this attack */ +boolean +mon_avoiding_this_attack( + struct monst *mtmp, + int attkidx) +{ + struct permonst *ptr = mtmp->data; + int typ = -1; + + if (attkidx >= 0 + && (typ = get_atkdam_type(ptr->mattk[attkidx].adtyp)) >= 0 + && m_seenres(mtmp, cvt_adtyp_to_mseenres(typ))) + return TRUE; + return FALSE; +} + +/* + * This would be equivalent to: + * ranged_attk_assessed(mtmp, mon_avoiding_this_attack) + * but without the added assessment function call overhead. + */ +boolean +ranged_attk_available(struct monst *mtmp) +{ + int i, typ = -1; + struct permonst *ptr = mtmp->data; + + for (i = 0; i < NATTK; i++) { + if (DISTANCE_ATTK_TYPE(ptr->mattk[i].aatyp) + && (typ = get_atkdam_type(ptr->mattk[i].adtyp)) >= 0 + && m_seenres(mtmp, cvt_adtyp_to_mseenres(typ)) == 0) + return TRUE; + } + return FALSE; +} + /* FIXME: * sequencing issue: a monster's attack might cause poly'd hero * to revert to normal form. The messages for passive counterattack @@ -2777,11 +2431,11 @@ const char *str; * to know whether hero reverted in order to decide whether passive * damage applies. */ -STATIC_OVL int -passiveum(olduasmon, mtmp, mattk) -struct permonst *olduasmon; -struct monst *mtmp; -struct attack *mattk; +staticfn int +passiveum( + struct permonst *olduasmon, + struct monst *mtmp, + struct attack *mattk) { int i, tmp; struct attack *oldu_mattk = 0; @@ -2793,7 +2447,7 @@ struct attack *mattk; */ for (i = 0; !oldu_mattk; i++) { if (i >= NATTK) - return 1; + return M_ATTK_HIT; if (olduasmon->mattk[i].aatyp == AT_NONE || olduasmon->mattk[i].aatyp == AT_BOOM) oldu_mattk = &olduasmon->mattk[i]; @@ -2809,13 +2463,13 @@ struct attack *mattk; switch (oldu_mattk->adtyp) { case AD_ACID: if (!rn2(2)) { - pline("%s is splashed by %s%s!", Monnam(mtmp), + pline_mon(mtmp, "%s is splashed by %s%s!", Monnam(mtmp), /* temporary? hack for sequencing issue: "your acid" looks strange coming immediately after player has been told that hero has reverted to normal form */ !Upolyd ? "" : "your ", hliquid("acid")); if (resists_acid(mtmp)) { - pline("%s is not affected.", Monnam(mtmp)); + pline_mon(mtmp, "%s is not affected.", Monnam(mtmp)); tmp = 0; } } else @@ -2824,7 +2478,7 @@ struct attack *mattk; erode_armor(mtmp, ERODE_CORRODE); if (!rn2(6)) acid_damage(MON_WEP(mtmp)); - goto assess_dmg; + return assess_dmg(mtmp, tmp); case AD_STON: /* cockatrice */ { long protector = attk_protection((int) mattk->aatyp), @@ -2842,14 +2496,14 @@ struct attack *mattk; mon_to_stone(mtmp); return 1; } - pline("%s turns to stone!", Monnam(mtmp)); - stoned = 1; + pline_mon(mtmp, "%s turns to stone!", Monnam(mtmp)); + gs.stoned = 1; xkilled(mtmp, XKILL_NOMSG); if (!DEADMONSTER(mtmp)) - return 1; - return 2; + return M_ATTK_HIT; + return M_ATTK_AGR_DIED; } - return 1; + return M_ATTK_HIT; } case AD_ENCH: /* KMH -- remove enchantment (disenchanter) */ if (mon_currwep) { @@ -2858,12 +2512,12 @@ struct attack *mattk; (void) drain_item(mon_currwep, TRUE); /* No message */ } - return 1; + return M_ATTK_HIT; default: break; } if (!Upolyd) - return 1; + return M_ATTK_HIT; /* These affect the enemy only if you are still a monster */ if (rn2(3)) @@ -2873,7 +2527,7 @@ struct attack *mattk; You("explode!"); /* KMH, balance patch -- this is okay with unchanging */ rehumanize(); - goto assess_dmg; + return assess_dmg(mtmp, tmp); } break; case AD_PLYS: /* Floating eye */ @@ -2884,43 +2538,45 @@ struct attack *mattk; tmp = 127; if (mtmp->mcansee && haseyes(mtmp->data) && rn2(3) && (perceives(mtmp->data) || !Invis)) { - if (Blind) + if (Blind) { pline("As a blind %s, you cannot defend yourself.", - youmonst.data->mname); - else { + pmname(gy.youmonst.data, + flags.female ? FEMALE : MALE)); + } else { if (mon_reflects(mtmp, "Your gaze is reflected by %s %s.")) return 1; - pline("%s is frozen by your gaze!", Monnam(mtmp)); + pline_mon(mtmp, "%s is frozen by your gaze!", + Monnam(mtmp)); paralyze_monst(mtmp, tmp); - return 3; + return M_ATTK_AGR_DONE; } } } else { /* gelatinous cube */ - pline("%s is frozen by you.", Monnam(mtmp)); + pline_mon(mtmp, "%s is frozen by you.", Monnam(mtmp)); paralyze_monst(mtmp, tmp); - return 3; + return M_ATTK_AGR_DONE; } - return 1; + return M_ATTK_HIT; case AD_COLD: /* Brown mold or blue jelly */ if (resists_cold(mtmp)) { shieldeff(mtmp->mx, mtmp->my); - pline("%s is mildly chilly.", Monnam(mtmp)); + pline_mon(mtmp, "%s is mildly chilly.", Monnam(mtmp)); golemeffects(mtmp, AD_COLD, tmp); tmp = 0; break; } - pline("%s is suddenly very cold!", Monnam(mtmp)); - u.mh += tmp / 2; + pline_mon(mtmp, "%s is suddenly very cold!", Monnam(mtmp)); + u.mh += (tmp + rn2(2)) / 2; if (u.mhmax < u.mh) u.mhmax = u.mh; - if (u.mhmax > ((youmonst.data->mlevel + 1) * 8)) - (void) split_mon(&youmonst, mtmp); + if (u.mhmax > (((int) gy.youmonst.data->mlevel + 1) * 8)) + (void) split_mon(&gy.youmonst, mtmp); break; case AD_STUN: /* Yellow mold */ if (!mtmp->mstun) { mtmp->mstun = 1; - pline("%s %s.", Monnam(mtmp), + pline_mon(mtmp, "%s %s.", Monnam(mtmp), makeplural(stagger(mtmp->data, "stagger"))); } tmp = 0; @@ -2928,22 +2584,23 @@ struct attack *mattk; case AD_FIRE: /* Red mold */ if (resists_fire(mtmp)) { shieldeff(mtmp->mx, mtmp->my); - pline("%s is mildly warm.", Monnam(mtmp)); + pline_mon(mtmp, "%s is mildly warm.", Monnam(mtmp)); golemeffects(mtmp, AD_FIRE, tmp); tmp = 0; break; } - pline("%s is suddenly very hot!", Monnam(mtmp)); + pline_mon(mtmp, "%s is suddenly very hot!", Monnam(mtmp)); break; case AD_ELEC: if (resists_elec(mtmp)) { shieldeff(mtmp->mx, mtmp->my); - pline("%s is slightly tingled.", Monnam(mtmp)); + pline_mon(mtmp, "%s is slightly tingled.", Monnam(mtmp)); golemeffects(mtmp, AD_ELEC, tmp); tmp = 0; break; } - pline("%s is jolted with your electricity!", Monnam(mtmp)); + pline_mon(mtmp, "%s is jolted with your electricity!", + Monnam(mtmp)); break; default: tmp = 0; @@ -2952,39 +2609,34 @@ struct attack *mattk; else tmp = 0; - assess_dmg: - if ((mtmp->mhp -= tmp) <= 0) { - pline("%s dies!", Monnam(mtmp)); - xkilled(mtmp, XKILL_NOMSG); - if (!DEADMONSTER(mtmp)) - return 1; - return 2; - } - return 1; + return assess_dmg(mtmp, tmp); } struct monst * -cloneu() +cloneu(void) { struct monst *mon; - int mndx = monsndx(youmonst.data); + int mndx = monsndx(gy.youmonst.data); if (u.mh <= 1) return (struct monst *) 0; - if (mvitals[mndx].mvflags & G_EXTINCT) + if (svm.mvitals[mndx].mvflags & G_EXTINCT) return (struct monst *) 0; - mon = makemon(youmonst.data, u.ux, u.uy, NO_MINVENT | MM_EDOG); + mon = makemon(gy.youmonst.data, u.ux, u.uy, + NO_MINVENT | MM_EDOG | MM_NOMSG); if (!mon) return NULL; mon->mcloned = 1; - mon = christen_monst(mon, plname); - initedog(mon); - mon->m_lev = youmonst.data->mlevel; + mon = christen_monst(mon, svp.plname); + initedog(mon, TRUE); + mon->m_lev = gy.youmonst.data->mlevel; mon->mhpmax = u.mhmax; mon->mhp = u.mh / 2; u.mh -= mon->mhp; - context.botl = 1; + disp.botl = TRUE; return mon; } +#undef ld + /*mhitu.c*/ diff --git a/src/minion.c b/src/minion.c index 4277b4587..b161f928d 100644 --- a/src/minion.c +++ b/src/minion.c @@ -1,25 +1,32 @@ -/* NetHack 3.6 minion.c $NHDT-Date: 1575245071 2019/12/02 00:04:31 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.44 $ */ +/* NetHack 5.0 minion.c $NHDT-Date: 1762727599 2025/11/09 14:33:19 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.81 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2008. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" +/* used to pick among the four basic elementals without worrying whether + they've been reordered (difficulty reassessment?) or any new ones have + been introduced (hybrid types added to 'E'-class?) */ +static const int elementals[4] = { + PM_AIR_ELEMENTAL, PM_FIRE_ELEMENTAL, + PM_EARTH_ELEMENTAL, PM_WATER_ELEMENTAL +}; + void -newemin(mtmp) -struct monst *mtmp; +newemin(struct monst *mtmp) { if (!mtmp->mextra) mtmp->mextra = newmextra(); if (!EMIN(mtmp)) { EMIN(mtmp) = (struct emin *) alloc(sizeof(struct emin)); (void) memset((genericptr_t) EMIN(mtmp), 0, sizeof(struct emin)); + EMIN(mtmp)->parentmid = mtmp->m_id; } } void -free_emin(mtmp) -struct monst *mtmp; +free_emin(struct monst *mtmp) { if (mtmp->mextra && EMIN(mtmp)) { free((genericptr_t) EMIN(mtmp)); @@ -30,8 +37,7 @@ struct monst *mtmp; /* count the number of monsters on the level */ int -monster_census(spotted) -boolean spotted; /* seen|sensed vs all */ +monster_census(boolean spotted) /* seen|sensed vs all */ { struct monst *mtmp; int count = 0; @@ -39,6 +45,8 @@ boolean spotted; /* seen|sensed vs all */ for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { if (DEADMONSTER(mtmp)) continue; + if (mtmp->isgd && mtmp->mx == 0) + continue; if (spotted && !canspotmon(mtmp)) continue; ++count; @@ -48,28 +56,27 @@ boolean spotted; /* seen|sensed vs all */ /* mon summons a monster */ int -msummon(mon) -struct monst *mon; +msummon(struct monst *mon) { struct permonst *ptr; int dtype = NON_PM, cnt = 0, result = 0, census; + boolean xlight; aligntyp atyp; struct monst *mtmp; if (mon) { ptr = mon->data; - if (uwep && uwep->oartifact == ART_DEMONBANE && is_demon(ptr)) { + if (u_wield_art(ART_DEMONBANE) && is_demon(ptr)) { if (canseemon(mon)) pline("%s looks puzzled for a moment.", Monnam(mon)); return 0; } atyp = mon->ispriest ? EPRI(mon)->shralign - : mon->isminion ? EMIN(mon)->min_align - : (ptr->maligntyp == A_NONE) - ? A_NONE - : sgn(ptr->maligntyp); + : mon->isminion ? EMIN(mon)->min_align + : (ptr->maligntyp == A_NONE) ? A_NONE + : sgn(ptr->maligntyp); } else { ptr = &mons[PM_WIZARD_OF_YENDOR]; atyp = (ptr->maligntyp == A_NONE) ? A_NONE : sgn(ptr->maligntyp); @@ -85,6 +92,9 @@ struct monst *mon; : ndemon(atyp); cnt = ((dtype != NON_PM) && !rn2(4) && is_ndemon(&mons[dtype])) ? 2 : 1; + } else if (ptr == &mons[PM_BONE_DEVIL]) { + dtype = PM_SKELETON; + cnt = 1; } else if (is_ndemon(ptr)) { dtype = (!rn2(20)) ? dlord(atyp) : (!rn2(6)) ? ndemon(atyp) : monsndx(ptr); @@ -100,7 +110,7 @@ struct monst *mon; if (!rn2(6)) { switch (atyp) { /* see summon_minion */ case A_NEUTRAL: - dtype = PM_AIR_ELEMENTAL + rn2(4); + dtype = ROLL_FROM(elementals); break; case A_CHAOTIC: case A_NONE: @@ -118,13 +128,13 @@ struct monst *mon; return 0; /* sanity checks */ - if (cnt > 1 && (mons[dtype].geno & G_UNIQ)) + if (cnt > 1 && (mons[dtype].geno & G_UNIQ) != 0) cnt = 1; /* * If this daemon is unique and being re-summoned (the only way we * could get this far with an extinct dtype), try another. */ - if (mvitals[dtype].mvflags & G_GONE) { + if ((svm.mvitals[dtype].mvflags & G_GONE) != 0) { dtype = ndemon(atyp); if (dtype == NON_PM) return 0; @@ -133,9 +143,10 @@ struct monst *mon; /* some candidates can generate a group of monsters, so simple count of non-null makemon() result is not sufficient */ census = monster_census(FALSE); + xlight = FALSE; while (cnt > 0) { - mtmp = makemon(&mons[dtype], u.ux, u.uy, MM_EMIN); + mtmp = makemon(&mons[dtype], u.ux, u.uy, MM_EMIN|MM_NOMSG); if (mtmp) { result++; /* an angel's alignment should match the summoner */ @@ -147,12 +158,35 @@ struct monst *mon; EMIN(mtmp)->renegade = (atyp != u.ualign.type) ^ !mtmp->mpeaceful; } - if (is_demon(ptr) && canseemon(mtmp)) - pline("%s appears in a cloud of smoke!", Amonnam(mtmp)); + + if (mtmp->data->mlet == S_ANGEL && !Blind) { + /* for any 'A', 'cloud of smoke' will be 'flash of light'; + if more than one monster is being created, that message + might be skipped for this monster but show 'mtmp' anyway */ + show_transient_light((struct obj *) 0, mtmp->mx, mtmp->my); + xlight = TRUE; + /* we don't do this for 'burst of flame' (fire elemental) + because those monsters become their own light source */ + } + + if (cnt == 1 && canseemon(mtmp)) { + const char *cloud = 0, + *what = msummon_environ(mtmp->data, &cloud); + + pline("%s appears in a %s of %s!", Amonnam(mtmp), + cloud, what); + } } cnt--; } + if (xlight) { + /* Note: if we forced --More-- here, the 'A's would be visible for + long enough to be seen, but like with clairvoyance, some players + would be annoyed at the disruption of having to acknowledge it */ + transient_light_cleanup(); + } + /* how many monsters exist now compared to before? */ if (result) result = monster_census(FALSE) - census; @@ -161,11 +195,9 @@ struct monst *mon; } void -summon_minion(alignment, talk) -aligntyp alignment; -boolean talk; +summon_minion(aligntyp alignment, boolean talk) { - register struct monst *mon; + struct monst *mon; int mnum; switch ((int) alignment) { @@ -173,7 +205,7 @@ boolean talk; mnum = lminion(); break; case A_NEUTRAL: - mnum = PM_AIR_ELEMENTAL + rn2(4); + mnum = ROLL_FROM(elementals); break; case A_CHAOTIC: case A_NONE: @@ -187,24 +219,24 @@ boolean talk; if (mnum == NON_PM) { mon = 0; } else if (mnum == PM_ANGEL) { - mon = makemon(&mons[mnum], u.ux, u.uy, MM_EMIN); + mon = makemon(&mons[mnum], u.ux, u.uy, MM_EMIN|MM_NOMSG); if (mon) { mon->isminion = 1; EMIN(mon)->min_align = alignment; EMIN(mon)->renegade = FALSE; } } else if (mnum != PM_SHOPKEEPER && mnum != PM_GUARD - && mnum != PM_ALIGNED_PRIEST && mnum != PM_HIGH_PRIEST) { + && mnum != PM_ALIGNED_CLERIC && mnum != PM_HIGH_CLERIC) { /* This was mons[mnum].pxlth == 0 but is this restriction appropriate or necessary now that the structures are separate? */ - mon = makemon(&mons[mnum], u.ux, u.uy, MM_EMIN); + mon = makemon(&mons[mnum], u.ux, u.uy, MM_EMIN|MM_NOMSG); if (mon) { mon->isminion = 1; EMIN(mon)->min_align = alignment; EMIN(mon)->renegade = FALSE; } } else { - mon = makemon(&mons[mnum], u.ux, u.uy, NO_MM_FLAGS); + mon = makemon(&mons[mnum], u.ux, u.uy, MM_NOMSG); } if (mon) { if (talk) { @@ -213,6 +245,7 @@ boolean talk; else You_feel("%s booming voice:", s_suffix(align_gname(alignment))); + SetVoice(mon, 0, 80, 0); verbalize("Thou shalt pay for thine indiscretion!"); if (canspotmon(mon)) pline("%s appears before you.", Amonnam(mon)); @@ -227,13 +260,15 @@ boolean talk; /* returns 1 if it won't attack. */ int -demon_talk(mtmp) -register struct monst *mtmp; +demon_talk(struct monst *mtmp) { long cash, demand, offer; - if (uwep && uwep->oartifact == ART_EXCALIBUR) { - pline("%s looks very angry.", Amonnam(mtmp)); + if (u_wield_art(ART_EXCALIBUR) || u_wield_art(ART_DEMONBANE)) { + if (canspotmon(mtmp)) + pline("%s looks very angry.", Amonnam(mtmp)); + else + You_feel("tension building."); mtmp->mpeaceful = mtmp->mtame = 0; set_malign(mtmp); newsym(mtmp->mx, mtmp->my); @@ -244,7 +279,7 @@ register struct monst *mtmp; reset_faint(); /* if fainted - wake up */ } else { stop_occupation(); - if (multi > 0) { + if (gm.multi > 0) { nomul(0); unmul((char *) 0); } @@ -261,21 +296,21 @@ register struct monst *mtmp; } newsym(mtmp->mx, mtmp->my); } - if (youmonst.data->mlet == S_DEMON) { /* Won't blackmail their own. */ + if (gy.youmonst.data->mlet == S_DEMON) { /* Won't blackmail their own. */ if (!Deaf) pline("%s says, \"Good hunting, %s.\"", Amonnam(mtmp), flags.female ? "Sister" : "Brother"); else if (canseemon(mtmp)) pline("%s says something.", Amonnam(mtmp)); if (!tele_restrict(mtmp)) - (void) rloc(mtmp, TRUE); + (void) rloc(mtmp, RLOC_MSG); return 1; } - cash = money_cnt(invent); + cash = money_cnt(gi.invent); demand = (cash * (rnd(80) + 20 * Athome)) / (100 * (1 + (sgn(u.ualign.type) == sgn(mtmp->data->maligntyp)))); - if (!demand || multi < 0) { /* you have no gold or can't move */ + if (!demand || gm.multi < 0) { /* you have no gold or can't move */ mtmp->mpeaceful = 0; set_malign(mtmp); return 0; @@ -296,9 +331,9 @@ register struct monst *mtmp; Amonnam(mtmp), demand, currency(demand)); else if (canseemon(mtmp)) pline("%s seems to be demanding something.", Amonnam(mtmp)); - offer = 0L; - if (!Deaf && ((offer = bribe(mtmp)) >= demand)) { + if (!Deaf && + ((offer = bribe(mtmp, "How much will you offer?")) >= demand)) { pline("%s vanishes, laughing about cowardly mortals.", Amonnam(mtmp)); } else if (offer > 0L @@ -312,19 +347,24 @@ register struct monst *mtmp; return 0; } } + /* if 'mtmp' is unrecognizable due to hero's hallucination, + #chronicle will reveal its true identity -- just live with that; + also, avoid random hallucinatory currency() units */ + livelog_printf(LL_UMONST, "bribed %s with %ld %s for safe passage", + x_monnam(mtmp, ARTICLE_A, (char *) 0, EXACT_NAME, FALSE), + offer, (offer == 1L) ? "zorkmid" : "zorkmids"); mongone(mtmp); return 1; } long -bribe(mtmp) -struct monst *mtmp; +bribe(struct monst *mtmp, const char *prompt) { char buf[BUFSZ] = DUMMY; long offer; - long umoney = money_cnt(invent); + long umoney = money_cnt(gi.invent); - getlin("How much will you offer?", buf); + getlin(prompt, buf); if (sscanf(buf, "%ld", &offer) != 1) offer = 0L; @@ -343,19 +383,18 @@ struct monst *mtmp; You("give %s %ld %s.", mon_nam(mtmp), offer, currency(offer)); } (void) money2mon(mtmp, offer); - context.botl = 1; + disp.botl = TRUE; return offer; } int -dprince(atyp) -aligntyp atyp; +dprince(aligntyp atyp) { int tryct, pm; for (tryct = !In_endgame(&u.uz) ? 20 : 0; tryct > 0; --tryct) { pm = rn1(PM_DEMOGORGON + 1 - PM_ORCUS, PM_ORCUS); - if (!(mvitals[pm].mvflags & G_GONE) + if (!(svm.mvitals[pm].mvflags & G_GONE) && (atyp == A_NONE || sgn(mons[pm].maligntyp) == sgn(atyp))) return pm; } @@ -363,14 +402,13 @@ aligntyp atyp; } int -dlord(atyp) -aligntyp atyp; +dlord(aligntyp atyp) { int tryct, pm; for (tryct = !In_endgame(&u.uz) ? 20 : 0; tryct > 0; --tryct) { pm = rn1(PM_YEENOGHU + 1 - PM_JUIBLEX, PM_JUIBLEX); - if (!(mvitals[pm].mvflags & G_GONE) + if (!(svm.mvitals[pm].mvflags & G_GONE) && (atyp == A_NONE || sgn(mons[pm].maligntyp) == sgn(atyp))) return pm; } @@ -379,16 +417,16 @@ aligntyp atyp; /* create lawful (good) lord */ int -llord() +llord(void) { - if (!(mvitals[PM_ARCHON].mvflags & G_GONE)) + if (!(svm.mvitals[PM_ARCHON].mvflags & G_GONE)) return PM_ARCHON; return lminion(); /* approximate */ } int -lminion() +lminion(void) { int tryct; struct permonst *ptr; @@ -403,8 +441,7 @@ lminion() } int -ndemon(atyp) -aligntyp atyp; /* A_NONE is used for 'any alignment' */ +ndemon(aligntyp atyp) /* A_NONE is used for 'any alignment' */ { struct permonst *ptr; @@ -428,8 +465,8 @@ aligntyp atyp; /* A_NONE is used for 'any alignment' */ /* guardian angel has been affected by conflict so is abandoning hero */ void -lose_guardian_angel(mon) -struct monst *mon; /* if null, angel hasn't been created yet */ +lose_guardian_angel( + struct monst *mon) /* if Null, angel hasn't been created yet */ { coord mm; int i; @@ -438,6 +475,7 @@ struct monst *mon; /* if null, angel hasn't been created yet */ if (canspotmon(mon)) { if (!Deaf) { pline("%s rebukes you, saying:", Monnam(mon)); + SetVoice(mon, 0, 80, 0); verbalize("Since you desire conflict, have some more!"); } else { pline("%s vanishes!", Monnam(mon)); @@ -457,7 +495,7 @@ struct monst *mon; /* if null, angel hasn't been created yet */ /* just entered the Astral Plane; receive tame guardian angel if worthy */ void -gain_guardian_angel() +gain_guardian_angel(void) { struct monst *mtmp; struct obj *otmp; @@ -466,10 +504,11 @@ gain_guardian_angel() Hear_again(); /* attempt to cure any deafness now (divine message will be heard even if that fails) */ if (Conflict) { - if (!Deaf) + if (!Deaf) pline("A voice booms:"); else You_feel("a booming voice:"); + SetVoice((struct monst *) 0, 0, 80, voice_deity); verbalize("Thy desire for conflict shall be fulfilled!"); /* send in some hostile angels instead */ lose_guardian_angel((struct monst *) 0); @@ -478,6 +517,7 @@ gain_guardian_angel() pline("A voice whispers:"); else You_feel("a soft voice:"); + SetVoice((struct monst *) 0, 0, 80, voice_deity); verbalize("Thou hast been worthy of me!"); mm.x = u.ux; mm.y = u.uy; @@ -490,7 +530,13 @@ gain_guardian_angel() * [Note: this predates mon->mextra which allows a monster * to have both emin and edog at the same time.] */ - mtmp->mtame = 10; + /* Too nasty for the game to unexpectedly break petless conduct on + * the final level of the game. The angel will still appear, but + * won't be tamed. */ + if (u.uconduct.pets) { + mtmp->mtame = 10; + u.uconduct.pets++; + } /* for 'hilite_pet'; after making tame, before next message */ newsym(mtmp->mx, mtmp->my); if (!Blind) diff --git a/src/mklev.c b/src/mklev.c index 743355f67..ab13d8f6b 100644 --- a/src/mklev.c +++ b/src/mklev.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 mklev.c $NHDT-Date: 1562455089 2019/07/06 23:18:09 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.63 $ */ +/* NetHack 5.0 mklev.c $NHDT-Date: 1737387068 2025/01/20 07:31:08 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.194 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Alex Smith, 2017. */ /* NetHack may be freely redistributed. See license for details. */ @@ -12,115 +12,230 @@ /* croom->lx etc are schar (width <= int), so % arith ensures that */ /* conversion of result to int is reasonable */ -STATIC_DCL void FDECL(mkfount, (int, struct mkroom *)); -STATIC_DCL void FDECL(mksink, (struct mkroom *)); -STATIC_DCL void FDECL(mkaltar, (struct mkroom *)); -STATIC_DCL void FDECL(mkgrave, (struct mkroom *)); -STATIC_DCL void NDECL(makevtele); -STATIC_DCL void NDECL(clear_level_structures); -STATIC_DCL void NDECL(makelevel); -STATIC_DCL boolean FDECL(bydoor, (XCHAR_P, XCHAR_P)); -STATIC_DCL struct mkroom *FDECL(find_branch_room, (coord *)); -STATIC_DCL struct mkroom *FDECL(pos_to_room, (XCHAR_P, XCHAR_P)); -STATIC_DCL boolean FDECL(place_niche, (struct mkroom *, int *, int *, int *)); -STATIC_DCL void FDECL(makeniche, (int)); -STATIC_DCL void NDECL(make_niches); -STATIC_PTR int FDECL(CFDECLSPEC do_comp, (const genericptr, - const genericptr)); -STATIC_DCL void FDECL(dosdoor, (XCHAR_P, XCHAR_P, struct mkroom *, int)); -STATIC_DCL void FDECL(join, (int, int, BOOLEAN_P)); -STATIC_DCL void FDECL(do_room_or_subroom, (struct mkroom *, int, int, - int, int, BOOLEAN_P, - SCHAR_P, BOOLEAN_P, BOOLEAN_P)); -STATIC_DCL void NDECL(makerooms); -STATIC_DCL void FDECL(finddpos, (coord *, XCHAR_P, XCHAR_P, - XCHAR_P, XCHAR_P)); -STATIC_DCL void FDECL(mkinvpos, (XCHAR_P, XCHAR_P, int)); -STATIC_DCL void FDECL(mk_knox_portal, (XCHAR_P, XCHAR_P)); +staticfn boolean generate_stairs_room_good(struct mkroom *, int); +staticfn struct mkroom *generate_stairs_find_room(void); +staticfn void generate_stairs(void); +staticfn void mkfount(struct mkroom *); +staticfn boolean find_okay_roompos(struct mkroom *, coord *) NONNULLARG12; +staticfn void mksink(struct mkroom *); +staticfn void mkaltar(struct mkroom *); +staticfn void mkgrave(struct mkroom *); +staticfn void mkinvpos(coordxy, coordxy, int); +staticfn int mkinvk_check_wall(coordxy x, coordxy y); +staticfn void mk_knox_portal(coordxy, coordxy); +staticfn void makevtele(void); +staticfn void fill_ordinary_room(struct mkroom *, boolean) NONNULLARG1; +staticfn void themerooms_post_level_generate(void); +staticfn boolean chk_okdoor(coordxy, coordxy); +staticfn void mklev_sanity_check(void); +staticfn void makelevel(void); +staticfn boolean water_has_kelp(coordxy, coordxy, int, int); +staticfn boolean bydoor(coordxy, coordxy); +staticfn void mktrap_victim(struct trap *); +staticfn int traptype_rnd(unsigned); +staticfn int traptype_roguelvl(void); +staticfn struct mkroom *find_branch_room(coord *) NONNULLARG1; +staticfn struct mkroom *pos_to_room(coordxy, coordxy); +staticfn boolean cardinal_nextto_room(struct mkroom *, coordxy, coordxy); +staticfn boolean place_niche(struct mkroom *, int *, coordxy *, coordxy *); +staticfn void makeniche(int); +staticfn void make_niches(void); +staticfn int QSORTCALLBACK mkroom_cmp(const genericptr, const genericptr); +staticfn void dosdoor(coordxy, coordxy, struct mkroom *, int); +staticfn void join(int, int, boolean); +staticfn void alloc_doors(void); +staticfn void do_room_or_subroom(struct mkroom *, + coordxy, coordxy, coordxy, coordxy, + boolean, schar, boolean, boolean); +staticfn void makerooms(void); +staticfn boolean good_rm_wall_doorpos(coordxy, coordxy, int, struct mkroom *); +staticfn boolean finddpos_shift(coordxy *, coordxy *, int, struct mkroom *); + +staticfn boolean finddpos(coord *, int, struct mkroom *); #define create_vault() create_room(-1, -1, 2, 2, -1, -1, VAULT, TRUE) -#define init_vault() vault_x = -1 -#define do_vault() (vault_x != -1) -static xchar vault_x, vault_y; -static boolean made_branch; /* used only during level creation */ +#define init_vault() gv.vault_x = -1 +#define do_vault() (gv.vault_x != -1) /* Args must be (const genericptr) so that qsort will always be happy. */ -STATIC_PTR int CFDECLSPEC -do_comp(vx, vy) -const genericptr vx; -const genericptr vy; +staticfn int QSORTCALLBACK +mkroom_cmp(const genericptr vx, const genericptr vy) { -#ifdef LINT - /* lint complains about possible pointer alignment problems, but we know - that vx and vy are always properly aligned. Hence, the following - bogus definition: - */ - return (vx == vy) ? 0 : -1; -#else - register const struct mkroom *x, *y; + const struct mkroom *x, *y; x = (const struct mkroom *) vx; y = (const struct mkroom *) vy; if (x->lx < y->lx) return -1; return (x->lx > y->lx); -#endif /* LINT */ } -STATIC_OVL void -finddpos(cc, xl, yl, xh, yh) -coord *cc; -xchar xl, yl, xh, yh; +/* is x,y a good location for a door into room? */ +staticfn boolean +good_rm_wall_doorpos(coordxy x, coordxy y, int dir, struct mkroom *room) { - register xchar x, y; + coordxy tx, ty; + int rmno; - x = rn1(xh - xl + 1, xl); - y = rn1(yh - yl + 1, yl); - if (okdoor(x, y)) - goto gotit; + if (!isok(x, y) || !room->needjoining) + return FALSE; - for (x = xl; x <= xh; x++) - for (y = yl; y <= yh; y++) - if (okdoor(x, y)) - goto gotit; + if (!(levl[x][y].typ == HWALL + || levl[x][y].typ == VWALL + || IS_DOOR(levl[x][y].typ) + || levl[x][y].typ == SDOOR)) + return FALSE; + + if (bydoor(x, y)) + return FALSE; + + tx = x + xdir[dir]; + ty = y + ydir[dir]; + + if (!isok(tx,ty) || IS_OBSTRUCTED(levl[tx][ty].typ)) + return FALSE; + + rmno = (room - svr.rooms) + ROOMOFFSET; + + if (rmno != (int) levl[tx][ty].roomno) + return FALSE; + + return TRUE; +} + +/* starting from x,y going towards dir, find a good location for a door */ +staticfn boolean +finddpos_shift(coordxy *x, coordxy *y, int dir, struct mkroom *aroom) +{ + coordxy dx, dy; + + dir = DIR_180(dir); + + dx = xdir[dir]; + dy = ydir[dir]; + + if (good_rm_wall_doorpos(*x, *y, dir, aroom)) + return TRUE; + + /* irregular rooms may have the room wall away from the room rectangular + area; go into the area until we encounter something */ + if (aroom->irregular) { + coordxy rx = *x, ry = *y; + boolean fail = FALSE; + + while (!fail && isok(rx, ry) + && (levl[rx][ry].typ == STONE || levl[rx][ry].typ == CORR)) { + rx += dx; + ry += dy; + if (good_rm_wall_doorpos(rx, ry, dir, aroom)) { + *x = rx; + *y = ry; + return TRUE; + } + if (!(levl[rx][ry].typ == STONE || levl[rx][ry].typ == CORR)) + fail = TRUE; + if (rx < aroom->lx || rx > aroom->hx + || ry < aroom->ly || ry > aroom->hy) + fail = TRUE; + } + } + return FALSE; +} - for (x = xl; x <= xh; x++) - for (y = yl; y <= yh; y++) - if (IS_DOOR(levl[x][y].typ) || levl[x][y].typ == SDOOR) +/* find a valid door position at room edge. + dir is the preferred edge of the room. + if found, returns TRUE and the coordinate in cc */ +staticfn boolean +finddpos(coord *cc, int dir, struct mkroom *aroom) +{ + coordxy x, y; + coordxy x1, y1, x2, y2; + int tryct = 0; + + switch (dir) { + case DIR_N: + x1 = aroom->lx; + x2 = aroom->hx; + y1 = aroom->ly - 1; + y2 = aroom->ly - 1; + break; + case DIR_S: + x1 = aroom->lx; + x2 = aroom->hx; + y1 = aroom->hy + 1; + y2 = aroom->hy + 1; + break; + case DIR_W: + x1 = aroom->lx - 1; + x2 = aroom->lx - 1; + y1 = aroom->ly; + y2 = aroom->hy; + break; + case DIR_E: + x1 = aroom->hx + 1; + x2 = aroom->hx + 1; + y1 = aroom->ly; + y2 = aroom->hy; + break; + default: + impossible("finddpos: illegal dir"); + return FALSE; + } + + /* try random points */ + do { + x = (x2 - x1) ? rn1(x2 - x1 + 1, x1) : x1; + y = (y2 - y1) ? rn1(y2 - y1 + 1, y1) : y1; + if (finddpos_shift(&x, &y, dir, aroom)) + goto gotit; + } while (++tryct < 20); + + /* try all the points */ + for (x = x1; x <= x2; x++) + for (y = y1; y <= y2; y++) + if (finddpos_shift(&x, &y, dir, aroom)) goto gotit; + /* cannot find something reasonable -- strange */ - x = xl; - y = yh; + cc->x = x1; + cc->y = y1; + return FALSE; gotit: cc->x = x; cc->y = y; - return; + return TRUE; } +/* Sort rooms on the level so they're ordered from left to right on the map. + makecorridors() by default links rooms N and N+1 */ void -sort_rooms() +sort_rooms(void) { -#if defined(SYSV) || defined(DGUX) -#define CAST_nroom (unsigned) nroom -#else -#define CAST_nroom nroom /*as-is*/ -#endif - qsort((genericptr_t) rooms, CAST_nroom, sizeof (struct mkroom), do_comp); -#undef CAST_nroom + coordxy x, y; + unsigned i, ri[MAXNROFROOMS + 1] = { 0U }, n = (unsigned) svn.nroom; + + qsort((genericptr_t) svr.rooms, n, sizeof (struct mkroom), mkroom_cmp); + + /* Update the roomnos on the map */ + for (i = 0; i < n; i++) + ri[svr.rooms[i].roomnoidx] = i; + + for (x = 1; x < COLNO; x++) + for (y = 0; y < ROWNO; y++) { + unsigned rno = levl[x][y].roomno; + + if (rno >= ROOMOFFSET && rno < MAXNROFROOMS + 1) + levl[x][y].roomno = ri[rno - ROOMOFFSET] + ROOMOFFSET; + } } -STATIC_OVL void -do_room_or_subroom(croom, lowx, lowy, hix, hiy, lit, rtype, special, is_room) -register struct mkroom *croom; -int lowx, lowy; -register int hix, hiy; -boolean lit; -schar rtype; -boolean special; -boolean is_room; +staticfn void +do_room_or_subroom(struct mkroom *croom, + coordxy lowx, coordxy lowy, coordxy hix, coordxy hiy, + boolean lit, schar rtype, boolean special, boolean is_room) { - register int x, y; + coordxy x, y; struct rm *lev; /* locations might bump level edges in wall-less rooms */ @@ -144,22 +259,24 @@ boolean is_room; } else croom->rlit = 0; + croom->roomnoidx = (croom - svr.rooms); croom->lx = lowx; croom->hx = hix; croom->ly = lowy; croom->hy = hiy; croom->rtype = rtype; croom->doorct = 0; - /* if we're not making a vault, doorindex will still be 0 + /* if we're not making a vault, gd.doorindex will still be 0 * if we are, we'll have problems adding niches to the previous room - * unless fdoor is at least doorindex + * unless fdoor is at least gd.doorindex */ - croom->fdoor = doorindex; + croom->fdoor = gd.doorindex; croom->irregular = FALSE; croom->nsubrooms = 0; croom->sbrooms[0] = (struct mkroom *) 0; if (!special) { + croom->needjoining = TRUE; for (x = lowx - 1; x <= hix + 1; x++) for (y = lowy - 1; y <= hiy + 1; y += (hiy - lowy + 2)) { levl[x][y].typ = HWALL; @@ -187,211 +304,322 @@ boolean is_room; } void -add_room(lowx, lowy, hix, hiy, lit, rtype, special) -int lowx, lowy, hix, hiy; -boolean lit; -schar rtype; -boolean special; +add_room(coordxy lowx, coordxy lowy, coordxy hix, coordxy hiy, + boolean lit, schar rtype, boolean special) { - register struct mkroom *croom; + struct mkroom *croom; - croom = &rooms[nroom]; +#ifdef DEBUG + if (svn.nroom >= MAXNROFROOMS) + panic("level has too many rooms"); +#endif /*DEBUG*/ + croom = &svr.rooms[svn.nroom]; do_room_or_subroom(croom, lowx, lowy, hix, hiy, lit, rtype, special, (boolean) TRUE); croom++; croom->hx = -1; - nroom++; + svn.nroom++; } void -add_subroom(proom, lowx, lowy, hix, hiy, lit, rtype, special) -struct mkroom *proom; -int lowx, lowy, hix, hiy; -boolean lit; -schar rtype; -boolean special; +add_subroom(struct mkroom *proom, + coordxy lowx, coordxy lowy, + coordxy hix, coordxy hiy, + boolean lit, schar rtype, boolean special) { - register struct mkroom *croom; + struct mkroom *croom; - croom = &subrooms[nsubroom]; +#ifdef DEBUG + if (gn.nsubroom >= MAXNROFROOMS) + panic("level has too many subrooms"); + if (proom->nsubrooms >= MAX_SUBROOMS) + panic("room has too many subrooms"); +#endif /*DEBUG*/ + croom = &gs.subrooms[gn.nsubroom]; do_room_or_subroom(croom, lowx, lowy, hix, hiy, lit, rtype, special, (boolean) FALSE); proom->sbrooms[proom->nsubrooms++] = croom; croom++; croom->hx = -1; - nsubroom++; + gn.nsubroom++; } -STATIC_OVL void -makerooms() +void +free_luathemes(enum lua_theme_group theme_group) +{ + int i; + + /* + * Release which group(s)? + * tut_themes => leaving tutorial, free tutorial themes only; + * most_themes => entering endgame, free non-endgame themes; + * all_themes => end of game, free all themes. + */ + for (i = 0; i < svn.n_dgns; ++i) { + if ((theme_group == tut_themes && i != tutorial_dnum) + || (theme_group == most_themes && i == astral_level.dnum)) + continue; + if (gl.luathemes[i]) { + nhl_done((lua_State *) gl.luathemes[i]); + gl.luathemes[i] = (lua_State *) 0; + } + } +} + +staticfn void +makerooms(void) { boolean tried_vault = FALSE; + int themeroom_tries = 0; + char *fname; + nhl_sandbox_info sbi = {NHL_SB_SAFE, 1*1024*1024, 0, 1*1024*1024}; + lua_State *themes = (lua_State *) gl.luathemes[u.uz.dnum]; + + if (!themes && *(fname = svd.dungeons[u.uz.dnum].themerms)) { + if ((themes = nhl_init(&sbi)) != 0) { + if (!nhl_loadlua(themes, fname)) { + /* loading lua failed, don't use themed rooms */ + nhl_done(themes); + themes = (lua_State *) 0; + } else { + /* success; save state for this dungeon branch */ + gl.luathemes[u.uz.dnum] = (genericptr_t) themes; + /* keep themes context, so not 'nhl_done(themes);' */ + iflags.in_lua = FALSE; /* can affect error messages */ + } + } + if (!themes) /* don't try again when making next level */ + *fname = '\0'; /* svd.dungeons[u.uz.dnum].themerms */ + } + + if (themes) { + create_des_coder(); + iflags.in_lua = gi.in_mk_themerooms = TRUE; + gt.themeroom_failed = FALSE; + lua_getglobal(themes, "pre_themerooms_generate"); + nhl_pcall_handle(themes, 0, 0, "makerooms-1", NHLpa_impossible); + iflags.in_lua = gi.in_mk_themerooms = FALSE; + } /* make rooms until satisfied */ /* rnd_rect() will returns 0 if no more rects are available... */ - while (nroom < MAXNROFROOMS && rnd_rect()) { - if (nroom >= (MAXNROFROOMS / 6) && rn2(2) && !tried_vault) { + while (svn.nroom < (MAXNROFROOMS - 1) && rnd_rect()) { + if (svn.nroom >= (MAXNROFROOMS / 6) && rn2(2) && !tried_vault) { tried_vault = TRUE; if (create_vault()) { - vault_x = rooms[nroom].lx; - vault_y = rooms[nroom].ly; - rooms[nroom].hx = -1; + gv.vault_x = svr.rooms[svn.nroom].lx; + gv.vault_y = svr.rooms[svn.nroom].ly; + svr.rooms[svn.nroom].hx = -1; } - } else if (!create_room(-1, -1, -1, -1, -1, -1, OROOM, -1)) - return; + } else { + if (themes) { + iflags.in_lua = gi.in_mk_themerooms = TRUE; + gt.themeroom_failed = FALSE; + lua_getglobal(themes, "themerooms_generate"); + nhl_pcall_handle(themes, 0, 0, "makerooms-2", NHLpa_panic); + iflags.in_lua = gi.in_mk_themerooms = FALSE; + if (gt.themeroom_failed + && ((themeroom_tries++ > 10) + || (svn.nroom >= (MAXNROFROOMS / 6)))) + break; + } else { + if (!create_room(-1, -1, -1, -1, -1, -1, OROOM, -1)) + break;; + } + } + } + if (themes) { + reset_xystart_size(); + iflags.in_lua = gi.in_mk_themerooms = TRUE; + gt.themeroom_failed = FALSE; + lua_getglobal(themes, "post_themerooms_generate"); + nhl_pcall_handle(themes, 0, 0, "makerooms-3", NHLpa_panic); + iflags.in_lua = gi.in_mk_themerooms = FALSE; } - return; } -STATIC_OVL void -join(a, b, nxcor) -register int a, b; -boolean nxcor; +staticfn void +join(int a, int b, boolean nxcor) { coord cc, tt, org, dest; - register xchar tx, ty, xx, yy; - register struct mkroom *croom, *troom; - register int dx, dy; + coordxy tx, ty, xx, yy; + struct mkroom *croom, *troom; + int dx, dy; + int npoints; + boolean dig_result; - croom = &rooms[a]; - troom = &rooms[b]; + croom = &svr.rooms[a]; + troom = &svr.rooms[b]; + + if (!croom->needjoining || !troom->needjoining) + return; /* find positions cc and tt for doors in croom and troom and direction for a corridor between them */ - if (troom->hx < 0 || croom->hx < 0 || doorindex >= DOORMAX) + if (troom->hx < 0 || croom->hx < 0) return; if (troom->lx > croom->hx) { dx = 1; dy = 0; - xx = croom->hx + 1; - tx = troom->lx - 1; - finddpos(&cc, xx, croom->ly, xx, croom->hy); - finddpos(&tt, tx, troom->ly, tx, troom->hy); + if (!finddpos(&cc, DIR_E, croom)) + return; + if (!finddpos(&tt, DIR_W, troom)) + return; } else if (troom->hy < croom->ly) { dy = -1; dx = 0; - yy = croom->ly - 1; - finddpos(&cc, croom->lx, yy, croom->hx, yy); - ty = troom->hy + 1; - finddpos(&tt, troom->lx, ty, troom->hx, ty); + if (!finddpos(&cc, DIR_N, croom)) + return; + if (!finddpos(&tt, DIR_S, troom)) + return; } else if (troom->hx < croom->lx) { dx = -1; dy = 0; - xx = croom->lx - 1; - tx = troom->hx + 1; - finddpos(&cc, xx, croom->ly, xx, croom->hy); - finddpos(&tt, tx, troom->ly, tx, troom->hy); + if (!finddpos(&cc, DIR_W, croom)) + return; + if (!finddpos(&tt, DIR_E, troom)) + return; } else { dy = 1; dx = 0; - yy = croom->hy + 1; - ty = troom->ly - 1; - finddpos(&cc, croom->lx, yy, croom->hx, yy); - finddpos(&tt, troom->lx, ty, troom->hx, ty); + if (!finddpos(&cc, DIR_S, croom)) + return; + if (!finddpos(&tt, DIR_N, troom)) + return; } xx = cc.x; yy = cc.y; tx = tt.x - dx; ty = tt.y - dy; - if (nxcor && levl[xx + dx][yy + dy].typ) + if (nxcor && levl[xx + dx][yy + dy].typ != STONE) return; - if (okdoor(xx, yy) || !nxcor) - dodoor(xx, yy, croom); org.x = xx + dx; org.y = yy + dy; dest.x = tx; dest.y = ty; - if (!dig_corridor(&org, &dest, nxcor, level.flags.arboreal ? ROOM : CORR, - STONE)) + dig_result = dig_corridor(&org, &dest, &npoints, nxcor, + svl.level.flags.arboreal ? ROOM : CORR, STONE); + + /* we created at least 1 tile of corridor, even if it failed */ + if ((npoints > 0) && (okdoor(xx, yy) || !nxcor)) + dodoor(xx, yy, croom); + + if (!dig_result) return; /* we succeeded in digging the corridor */ if (okdoor(tt.x, tt.y) || !nxcor) dodoor(tt.x, tt.y, troom); - if (smeq[a] < smeq[b]) - smeq[b] = smeq[a]; + if (gs.smeq[a] < gs.smeq[b]) + gs.smeq[b] = gs.smeq[a]; else - smeq[a] = smeq[b]; + gs.smeq[a] = gs.smeq[b]; } +/* create random corridors between rooms */ void -makecorridors() +makecorridors(void) { int a, b, i; boolean any = TRUE; - for (a = 0; a < nroom - 1; a++) { + for (a = 0; a < svn.nroom - 1; a++) { join(a, a + 1, FALSE); if (!rn2(50)) break; /* allow some randomness */ } - for (a = 0; a < nroom - 2; a++) - if (smeq[a] != smeq[a + 2]) + for (a = 0; a < svn.nroom - 2; a++) + if (gs.smeq[a] != gs.smeq[a + 2]) join(a, a + 2, FALSE); - for (a = 0; any && a < nroom; a++) { + for (a = 0; any && a < svn.nroom; a++) { any = FALSE; - for (b = 0; b < nroom; b++) - if (smeq[a] != smeq[b]) { + for (b = 0; b < svn.nroom; b++) + if (gs.smeq[a] != gs.smeq[b]) { join(a, b, FALSE); any = TRUE; } } - if (nroom > 2) - for (i = rn2(nroom) + 4; i; i--) { - a = rn2(nroom); - b = rn2(nroom - 2); + /* add some extra corridors which may be blocked off */ + if (svn.nroom > 2) + for (i = rn2(svn.nroom) + 4; i; i--) { + a = rn2(svn.nroom); + b = rn2(svn.nroom - 2); if (b >= a) b += 2; join(a, b, TRUE); } } +/* (re)allocate space for svd.doors array */ +staticfn void +alloc_doors(void) +{ + if (!svd.doors || gd.doorindex >= svd.doors_alloc) { + int c = svd.doors_alloc + DOORINC; + coord *doortmp = (coord *) alloc(c * sizeof (coord)); + + (void) memset((genericptr_t) doortmp, 0, c * sizeof (coord)); + if (svd.doors) { + (void) memcpy(doortmp, svd.doors, + svd.doors_alloc * sizeof (coord)); + free(svd.doors); + } + svd.doors = doortmp; + svd.doors_alloc = c; + } +} + void -add_door(x, y, aroom) -register int x, y; -register struct mkroom *aroom; +add_door(coordxy x, coordxy y, struct mkroom *aroom) { - register struct mkroom *broom; - register int tmp; + struct mkroom *broom; + int tmp; int i; + alloc_doors(); + + if (aroom->doorct) { + for (i = 0; i < aroom->doorct; i++) { + tmp = aroom->fdoor + i; + if (svd.doors[tmp].x == x && svd.doors[tmp].y == y) + return; + } + } + if (aroom->doorct == 0) - aroom->fdoor = doorindex; + aroom->fdoor = gd.doorindex; aroom->doorct++; - for (tmp = doorindex; tmp > aroom->fdoor; tmp--) - doors[tmp] = doors[tmp - 1]; + for (tmp = gd.doorindex; tmp > aroom->fdoor; tmp--) + svd.doors[tmp] = svd.doors[tmp - 1]; - for (i = 0; i < nroom; i++) { - broom = &rooms[i]; + for (i = 0; i < svn.nroom; i++) { + broom = &svr.rooms[i]; if (broom != aroom && broom->doorct && broom->fdoor >= aroom->fdoor) broom->fdoor++; } - for (i = 0; i < nsubroom; i++) { - broom = &subrooms[i]; + for (i = 0; i < gn.nsubroom; i++) { + broom = &gs.subrooms[i]; if (broom != aroom && broom->doorct && broom->fdoor >= aroom->fdoor) broom->fdoor++; } - doorindex++; - doors[aroom->fdoor].x = x; - doors[aroom->fdoor].y = y; + gd.doorindex++; + svd.doors[aroom->fdoor].x = x; + svd.doors[aroom->fdoor].y = y; } -STATIC_OVL void -dosdoor(x, y, aroom, type) -register xchar x, y; -struct mkroom *aroom; -int type; +staticfn void +dosdoor(coordxy x, coordxy y, struct mkroom *aroom, int type) { boolean shdoor = *in_rooms(x, y, SHOPBASE) ? TRUE : FALSE; - if (!IS_WALL(levl[x][y].typ)) /* avoid SDOORs on already made doors */ + if (!IS_WALL(levl[x][y].typ)) /* avoid S.doors on already made doors */ type = DOOR; levl[x][y].typ = type; if (type == DOOR) { @@ -418,7 +646,7 @@ int type; } /* also done in roguecorr(); doing it here first prevents - making mimics in place of trapped doors on rogue level */ + making mimics in place of trapped doors on rogue svl.level */ if (Is_rogue_level(&u.uz)) levl[x][y].doormask = D_NODOOR; @@ -426,9 +654,9 @@ int type; struct monst *mtmp; if (level_difficulty() >= 9 && !rn2(5) - && !((mvitals[PM_SMALL_MIMIC].mvflags & G_GONE) - && (mvitals[PM_LARGE_MIMIC].mvflags & G_GONE) - && (mvitals[PM_GIANT_MIMIC].mvflags & G_GONE))) { + && !((svm.mvitals[PM_SMALL_MIMIC].mvflags & G_GONE) + && (svm.mvitals[PM_LARGE_MIMIC].mvflags & G_GONE) + && (svm.mvitals[PM_GIANT_MIMIC].mvflags & G_GONE))) { /* make a mimic instead */ levl[x][y].doormask = D_NODOOR; mtmp = makemon(mkclass(S_MIMIC, 0), x, y, NO_MM_FLAGS); @@ -450,19 +678,44 @@ int type; add_door(x, y, aroom); } -STATIC_OVL boolean -place_niche(aroom, dy, xx, yy) -register struct mkroom *aroom; -int *dy, *xx, *yy; +/* is x,y location such that NEWS direction from it is inside aroom, + excluding subrooms */ +staticfn boolean +cardinal_nextto_room(struct mkroom *aroom, coordxy x, coordxy y) +{ + int rmno = (int) ((aroom - svr.rooms) + ROOMOFFSET); + + if (isok(x - 1, y) && !levl[x - 1][y].edge + && (int) levl[x - 1][y].roomno == rmno) + return TRUE; + if (isok(x + 1, y) && !levl[x + 1][y].edge + && (int) levl[x + 1][y].roomno == rmno) + return TRUE; + if (isok(x, y - 1) && !levl[x][y - 1].edge + && (int) levl[x][y - 1].roomno == rmno) + return TRUE; + if (isok(x, y + 1) && !levl[x][y + 1].edge + && (int) levl[x][y + 1].roomno == rmno) + return TRUE; + return FALSE; +} + +staticfn boolean +place_niche( + struct mkroom *aroom, + int *dy, + coordxy *xx, coordxy *yy) { coord dd; if (rn2(2)) { *dy = 1; - finddpos(&dd, aroom->lx, aroom->hy + 1, aroom->hx, aroom->hy + 1); + if (!finddpos(&dd, DIR_S, aroom)) + return FALSE; } else { *dy = -1; - finddpos(&dd, aroom->lx, aroom->ly - 1, aroom->hx, aroom->ly - 1); + if (!finddpos(&dd, DIR_N, aroom)) + return FALSE; } *xx = dd.x; *yy = dd.y; @@ -470,7 +723,8 @@ int *dy, *xx, *yy; && levl[*xx][*yy + *dy].typ == STONE) && (isok(*xx, *yy - *dy) && !IS_POOL(levl[*xx][*yy - *dy].typ) - && !IS_FURNITURE(levl[*xx][*yy - *dy].typ))); + && !IS_FURNITURE(levl[*xx][*yy - *dy].typ)) + && cardinal_nextto_room(aroom, *xx, *yy)); } /* there should be one of these per trap, in the same order as trap.h */ @@ -481,78 +735,77 @@ static NEARDATA const char *trap_engravings[TRAPNUM] = { /* 14..16: trap door, teleport, level-teleport */ "Vlad was here", "ad aerarium", "ad aerarium", (char *) 0, (char *) 0, (char *) 0, (char *) 0, (char *) 0, (char *) 0, (char *) 0, + /* 24..25 */ + (char *) 0, (char *) 0, }; -STATIC_OVL void -makeniche(trap_type) -int trap_type; +staticfn void +makeniche(int trap_type) { - register struct mkroom *aroom; + struct mkroom *aroom; struct rm *rm; - int vct = 8; - int dy, xx, yy; + int dy, vct = 8; + coordxy xx, yy; struct trap *ttmp; - if (doorindex < DOORMAX) { - while (vct--) { - aroom = &rooms[rn2(nroom)]; - if (aroom->rtype != OROOM) - continue; /* not an ordinary room */ - if (aroom->doorct == 1 && rn2(5)) - continue; - if (!place_niche(aroom, &dy, &xx, &yy)) - continue; - - rm = &levl[xx][yy + dy]; - if (trap_type || !rn2(4)) { - rm->typ = SCORR; - if (trap_type) { - if (is_hole(trap_type) && !Can_fall_thru(&u.uz)) - trap_type = ROCKTRAP; - ttmp = maketrap(xx, yy + dy, trap_type); - if (ttmp) { - if (trap_type != ROCKTRAP) - ttmp->once = 1; - if (trap_engravings[trap_type]) { - make_engr_at(xx, yy - dy, - trap_engravings[trap_type], 0L, - DUST); - wipe_engr_at(xx, yy - dy, 5, - FALSE); /* age it a little */ - } + while (vct--) { + aroom = &svr.rooms[rn2(svn.nroom)]; + if (aroom->rtype != OROOM) + continue; /* not an ordinary room */ + if (aroom->doorct == 1 && rn2(5)) + continue; + if (!place_niche(aroom, &dy, &xx, &yy)) + continue; + + rm = &levl[xx][yy + dy]; + if (trap_type || !rn2(4)) { + rm->typ = SCORR; + if (trap_type) { + if (is_hole(trap_type) && !Can_fall_thru(&u.uz)) + trap_type = ROCKTRAP; + ttmp = maketrap(xx, yy + dy, trap_type); + if (ttmp) { + if (trap_type != ROCKTRAP) + ttmp->once = 1; + if (trap_engravings[trap_type]) { + make_engr_at(xx, yy - dy, + trap_engravings[trap_type], NULL, 0L, + DUST); + wipe_engr_at(xx, yy - dy, 5, + FALSE); /* age it a little */ } } - dosdoor(xx, yy, aroom, SDOOR); + } + dosdoor(xx, yy, aroom, SDOOR); + } else { + rm->typ = CORR; + if (rn2(7)) { + dosdoor(xx, yy, aroom, rn2(5) ? SDOOR : DOOR); } else { - rm->typ = CORR; - if (rn2(7)) - dosdoor(xx, yy, aroom, rn2(5) ? SDOOR : DOOR); - else { - /* inaccessible niches occasionally have iron bars */ - if (!rn2(5) && IS_WALL(levl[xx][yy].typ)) { - levl[xx][yy].typ = IRONBARS; - if (rn2(3)) - (void) mkcorpstat(CORPSE, (struct monst *) 0, - mkclass(S_HUMAN, 0), xx, - yy + dy, TRUE); - } - if (!level.flags.noteleport) - (void) mksobj_at(SCR_TELEPORTATION, xx, yy + dy, TRUE, - FALSE); - if (!rn2(3)) - (void) mkobj_at(0, xx, yy + dy, TRUE); + /* inaccessible niches occasionally have iron bars */ + if (!rn2(5) && IS_WALL(levl[xx][yy].typ)) { + (void) set_levltyp(xx, yy, IRONBARS); + if (rn2(3)) + (void) mkcorpstat(CORPSE, (struct monst *) 0, + mkclass(S_HUMAN, 0), xx, + yy + dy, TRUE); } + if (!svl.level.flags.noteleport) + (void) mksobj_at(SCR_TELEPORTATION, xx, yy + dy, TRUE, + FALSE); + if (!rn2(3)) + (void) mkobj_at(RANDOM_CLASS, xx, yy + dy, TRUE); } - return; } + return; } } -STATIC_OVL void -make_niches() +staticfn void +make_niches(void) { - int ct = rnd((nroom >> 1) + 1), dep = depth(&u.uz); - boolean ltptr = (!level.flags.noteleport && dep > 15), + int ct = rnd((svn.nroom >> 1) + 1), dep = depth(&u.uz); + boolean ltptr = (!svl.level.flags.noteleport && dep > 15), vamp = (dep > 5 && dep < 25); while (ct--) { @@ -567,24 +820,42 @@ make_niches() } } -STATIC_OVL void -makevtele() +staticfn void +makevtele(void) { makeniche(TELEP_TRAP); } +/* count the tracked features (sinks, fountains) present on the level */ +void +count_level_features(void) +{ + coordxy x, y; + + svl.level.flags.nfountains = svl.level.flags.nsinks = 0; + for (y = 0; y < ROWNO; y++) + for (x = 1; x < COLNO; x++) { + int typ = levl[x][y].typ; + + if (typ == FOUNTAIN) + svl.level.flags.nfountains++; + else if (typ == SINK) + svl.level.flags.nsinks++; + } +} + /* clear out various globals that keep information on the current level. * some of this is only necessary for some types of levels (maze, normal, * special) but it's easier to put it all in one place than make sure * each type initializes what it needs to separately. */ -STATIC_OVL void -clear_level_structures() +void +clear_level_structures(void) { - static struct rm zerorm = { cmap_to_glyph(S_stone), + static struct rm zerorm = { GLYPH_UNEXPLORED, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - register int x, y; - register struct rm *lev; + coordxy x, y; + struct rm *lev; /* note: normally we'd start at x=1 because map column #0 isn't used (except for placing vault guard at <0,0> when removed from the map @@ -594,314 +865,599 @@ clear_level_structures() lev = &levl[x][0]; for (y = 0; y < ROWNO; y++) { *lev++ = zerorm; - /* - * These used to be '#if MICROPORT_BUG', - * with use of memset(0) for '#if !MICROPORT_BUG' below, - * but memset is not appropriate for initializing pointers, - * so do these level.objects[][] and level.monsters[][] - * initializations unconditionally. - */ - level.objects[x][y] = (struct obj *) 0; - level.monsters[x][y] = (struct monst *) 0; + svl.level.objects[x][y] = (struct obj *) 0; + svl.level.monsters[x][y] = (struct monst *) 0; } } - level.objlist = (struct obj *) 0; - level.buriedobjlist = (struct obj *) 0; - level.monlist = (struct monst *) 0; - level.damagelist = (struct damage *) 0; - level.bonesinfo = (struct cemetery *) 0; - - level.flags.nfountains = 0; - level.flags.nsinks = 0; - level.flags.has_shop = 0; - level.flags.has_vault = 0; - level.flags.has_zoo = 0; - level.flags.has_court = 0; - level.flags.has_morgue = level.flags.graveyard = 0; - level.flags.has_beehive = 0; - level.flags.has_barracks = 0; - level.flags.has_temple = 0; - level.flags.has_swamp = 0; - level.flags.noteleport = 0; - level.flags.hardfloor = 0; - level.flags.nommap = 0; - level.flags.hero_memory = 1; - level.flags.shortsighted = 0; - level.flags.sokoban_rules = 0; - level.flags.is_maze_lev = 0; - level.flags.is_cavernous_lev = 0; - level.flags.arboreal = 0; - level.flags.wizard_bones = 0; - level.flags.corrmaze = 0; - - nroom = 0; - rooms[0].hx = -1; - nsubroom = 0; - subrooms[0].hx = -1; - doorindex = 0; + svl.level.objlist = (struct obj *) 0; + svl.level.buriedobjlist = (struct obj *) 0; + svl.level.monlist = (struct monst *) 0; + svl.level.damagelist = (struct damage *) 0; + svl.level.bonesinfo = (struct cemetery *) 0; + + svl.level.flags.nfountains = 0; + svl.level.flags.nsinks = 0; + svl.level.flags.has_shop = 0; + svl.level.flags.has_vault = 0; + svl.level.flags.has_zoo = 0; + svl.level.flags.has_court = 0; + svl.level.flags.has_morgue = svl.level.flags.graveyard = 0; + svl.level.flags.has_beehive = 0; + svl.level.flags.has_barracks = 0; + svl.level.flags.has_temple = 0; + svl.level.flags.has_swamp = 0; + svl.level.flags.noteleport = 0; + svl.level.flags.hardfloor = 0; + svl.level.flags.nommap = 0; + svl.level.flags.hero_memory = 1; + svl.level.flags.shortsighted = 0; + svl.level.flags.sokoban_rules = 0; + svl.level.flags.is_maze_lev = 0; + svl.level.flags.is_cavernous_lev = 0; + svl.level.flags.arboreal = 0; + svl.level.flags.has_town = 0; + svl.level.flags.wizard_bones = 0; + svl.level.flags.corrmaze = 0; + svl.level.flags.temperature = In_hell(&u.uz) ? 1 : 0; + svl.level.flags.rndmongen = 1; + svl.level.flags.deathdrops = 1; + svl.level.flags.noautosearch = 0; + svl.level.flags.fumaroles = 0; + svl.level.flags.stormy = 0; + svl.level.flags.stasis_until = 0L; + + svn.nroom = 0; + svr.rooms[0].hx = -1; + gn.nsubroom = 0; + gs.subrooms[0].hx = -1; + gd.doorindex = 0; + if (svd.doors_alloc) { + free((genericptr_t) svd.doors); + svd.doors = (coord *) 0; + svd.doors_alloc = 0; + } init_rect(); init_vault(); - xdnstair = ydnstair = xupstair = yupstair = 0; - sstairs.sx = sstairs.sy = 0; - xdnladder = ydnladder = xupladder = yupladder = 0; - dnstairs_room = upstairs_room = sstairs_room = (struct mkroom *) 0; - made_branch = FALSE; + stairway_free_all(); + gm.made_branch = FALSE; clear_regions(); + free_exclusions(); + reset_xystart_size(); + if (gl.lev_message) { + free(gl.lev_message); + gl.lev_message = (char *) 0; + } } -/* Added for NLE. */ -extern char* nle_getenv(const char *); +#define ROOM_IS_FILLABLE(croom) \ + ((croom->rtype == OROOM || croom->rtype == THEMEROOM) \ + && croom->needfill == FILL_NORMAL) + +/* Fill a "random" room (i.e. a typical non-special room in the Dungeons of + Doom) with random monsters, objects, and dungeon features. -STATIC_OVL void -makelevel() + If bonus_items is TRUE, there may be an additional special item + generated, depending on depth. */ +staticfn void +fill_ordinary_room( + struct mkroom *croom, + boolean bonus_items) { - register struct mkroom *croom, *troom; - register int tryct; - register int x, y; + int trycnt = 0; + coord pos; struct monst *tmonst; /* always put a web with a spider */ - branch *branchp; - int room_threshold; + coordxy x, y; + boolean skip_chests = FALSE; - if (wiz1_level.dlevel == 0) - init_dungeons(); - oinit(); /* assign level dependent obj probabilities */ - clear_level_structures(); + if (croom->rtype != OROOM && croom->rtype != THEMEROOM) + return; - { - register s_level *slev = Is_special(&u.uz); + /* If there are subrooms, fill them now - we don't want an outer room + * that's specified to be unfilled to block an inner subroom that's + * specified to be filled. */ + for (x = 0; x < croom->nsubrooms; ++x) { + struct mkroom *subroom = croom->sbrooms[x]; - /* check for special levels */ - if (slev && !Is_rogue_level(&u.uz)) { - makemaz(slev->proto); + if (!subroom) { + impossible("fill_ordinary_room: Null subroom"); return; - } else if (dungeons[u.uz.dnum].proto[0]) { - makemaz(""); - return; - } else if (In_mines(&u.uz)) { - makemaz("minefill"); - return; - } else if (In_quest(&u.uz)) { - char fillname[9]; - s_level *loc_lev; + } + fill_ordinary_room(subroom, FALSE); + } - Sprintf(fillname, "%s-loca", urole.filecode); - loc_lev = find_level(fillname); + if (croom->needfill != FILL_NORMAL) + return; - Sprintf(fillname, "%s-fil", urole.filecode); - Strcat(fillname, - (u.uz.dlevel < loc_lev->dlevel.dlevel) ? "a" : "b"); - makemaz(fillname); - return; - } else if (In_hell(&u.uz) - || (rn2(5) && u.uz.dnum == medusa_level.dnum - && depth(&u.uz) > depth(&medusa_level))) { - makemaz(""); - return; + /* put a sleeping monster inside */ + /* Note: monster may be on the stairs. This cannot be + avoided: maybe the player fell through a trap door + while a monster was on the stairs. Conclusion: + we have to check for monsters on the stairs anyway. */ + + if ((u.uhave.amulet || !rn2(3)) && somexyspace(croom, &pos)) { + tmonst = makemon((struct permonst *) 0, pos.x, pos.y, MM_NOGRP); + if (tmonst && tmonst->data == &mons[PM_GIANT_SPIDER] + && !occupied(pos.x, pos.y)) + (void) maketrap(pos.x, pos.y, WEB); + } + /* put traps and mimics inside */ + x = 8 - (level_difficulty() / 6); + if (x <= 1) + x = 2; + while (!rn2(x) && (++trycnt < 1000)) + mktrap(0, MKTRAP_NOFLAGS, croom, (coord *) 0); + if (!rn2(3) && somexyspace(croom, &pos)) + (void) mkgold(0L, pos.x, pos.y); + if (Is_rogue_level(&u.uz)) + goto skip_nonrogue; + if (!rn2(10)) + mkfount(croom); + if (!rn2(60)) + mksink(croom); + if (!rn2(60)) + mkaltar(croom); + x = 80 - (depth(&u.uz) * 2); + if (x < 2) + x = 2; + if (!rn2(x)) + mkgrave(croom); + + /* put statues inside */ + if (!rn2(20) && somexyspace(croom, &pos)) + (void) mkcorpstat(STATUE, (struct monst *) 0, + (struct permonst *) 0, pos.x, + pos.y, CORPSTAT_INIT); + + /* + * bonus_items means that this is the room where the bonus item + * should be placed, if there is one; but there might not be a + * bonus item on any given level. + * + * Bonus items are currently as follows: + * a) on the Mines branch level, 100% chance of a fairly filling + * comestible; + * b) on other levels above the Oracle, 2/3 chance of a "supply + * chest" that contains an early-game survivability item + * (there are therefore more of these when Sokoban is deep, + * which is intentional as those games are harder). + * This mechanism could be expanded in the future to place + * near-guaranteed items on particular levels (but, it is possible + * that no room will be given a bonus item if there is no suitable + * room to place it in, so it should not be used for plot-critical + * items). + */ + if (bonus_items && somexyspace(croom, &pos)) { + branch *uz_branch = Is_branchlev(&u.uz); + + if (uz_branch && u.uz.dnum != mines_dnum + && (uz_branch->end1.dnum == mines_dnum + || uz_branch->end2.dnum == mines_dnum)) { + (void) mksobj_at((rn2(5) < 3) ? FOOD_RATION + : rn2(2) ? CRAM_RATION + : LEMBAS_WAFER, + pos.x, pos.y, TRUE, FALSE); + } else if (u.uz.dnum == oracle_level.dnum + && u.uz.dlevel < oracle_level.dlevel && rn2(3)) { + struct obj *otmp; + int otyp, tryct = 0; + boolean cursed; + /* reverse probabilities compared to non-supply chests; + these are twice as likely to be chests than large + boxes, rather than vice versa */ + struct obj *supply_chest = mksobj_at(rn2(3) ? CHEST : LARGE_BOX, + pos.x, pos.y, FALSE, FALSE); + + supply_chest->olocked = !!(rn2(6)); + + do { + static const int supply_items[] = { + POT_EXTRA_HEALING, + POT_SPEED, + POT_GAIN_ENERGY, + SCR_ENCHANT_WEAPON, + SCR_ENCHANT_ARMOR, + SCR_CONFUSE_MONSTER, + SCR_SCARE_MONSTER, + WAN_DIGGING, + SPE_HEALING, + }; + + /* 50% this is a potion of healing */ + otyp = rn2(2) ? POT_HEALING : ROLL_FROM(supply_items); + otmp = mksobj(otyp, TRUE, FALSE); + if (otyp == POT_HEALING && rn2(2)) { + otmp->quan = 2; + otmp->owt = weight(otmp); + } + cursed = otmp->cursed; + add_to_container(supply_chest, otmp); /* owt updated below */ + + ++tryct; + if (tryct == 50) { + impossible("couldn't generate supply chest item"); + break; + } + /* guarantee at least one noncursed item, with a small + probability of more; if we generate a cursed item, it's + added to the supply chest but we reroll for a noncursed + item and add that too */ + } while (cursed || !rn2(5)); + + /* maybe put a random item into the supply chest, biased + slightly towards low-level spellbooks; avoid tools + because chests don't fit into other chests */ + if (rn2(3)) { + static const int extra_classes[] = { + FOOD_CLASS, + WEAPON_CLASS, + ARMOR_CLASS, + GEM_CLASS, + SCROLL_CLASS, + POTION_CLASS, + RING_CLASS, + SPBOOK_no_NOVEL, + SPBOOK_no_NOVEL, + SPBOOK_no_NOVEL + }; + int oclass = ROLL_FROM(extra_classes); + + otmp = mkobj(oclass, FALSE); + if (oclass == SPBOOK_no_NOVEL) { + int pass, maxpass = (depth(&u.uz) > 2) ? 2 : 3; + + /* bias towards lower level by generating again + and taking the lower-level book; do that three + times if on level 1 or 2, twice when deeper */ + for (pass = 1; pass <= maxpass; ++pass) { + struct obj *otmp2 = mkobj(oclass, FALSE); + + if (objects[otmp->otyp].oc_level + <= objects[otmp2->otyp].oc_level) { + dealloc_obj(otmp2); + } else { + dealloc_obj(otmp); + otmp = otmp2; + } + } + } + add_to_container(supply_chest, otmp); /* owt updated below */ + } + + /* add_to_container() doesn't update the container's weight */ + supply_chest->owt = weight(supply_chest); + + skip_chests = TRUE; /* don't want a second chest in this room */ } } - /* otherwise, fall through - it's a "regular" level. */ - if (Is_rogue_level(&u.uz)) { - makeroguerooms(); - makerogueghost(); - } else - makerooms(); - sort_rooms(); + /* put box/chest inside; + * 40% chance for at least 1 box, regardless of number + * of rooms; about 5 - 7.5% for 2 boxes, least likely + * when few rooms; chance for 3 or more is negligible. + */ + /*assert(svn.nroom > 0); // must be true because we're filling a room*/ + if (!skip_chests && !rn2(svn.nroom * 5 / 2) && somexyspace(croom, &pos)) + (void) mksobj_at(rn2(3) ? LARGE_BOX : CHEST, + pos.x, pos.y, TRUE, FALSE); + + /* maybe make some graffiti */ + if (!rn2(27 + 3 * abs(depth(&u.uz)))) { + char buf[BUFSZ], pristinebuf[BUFSZ]; + const char *mesg = random_engraving(buf, pristinebuf); + + if (mesg) { + do { + (void) somexyspace(croom, &pos); + x = pos.x; + y = pos.y; + } while (levl[x][y].typ != ROOM && !rn2(40)); + if (levl[x][y].typ == ROOM) + make_engr_at(x, y, mesg, pristinebuf, 0L, MARK); + } + } - /* construct stairs (up and down in different rooms if possible) */ - croom = &rooms[rn2(nroom)]; - if (!Is_botlevel(&u.uz)) - mkstairs(somex(croom), somey(croom), 0, croom); /* down */ - if (nroom > 1) { - troom = croom; - croom = &rooms[rn2(nroom - 1)]; - if (croom == troom) - croom++; + skip_nonrogue: + if (!rn2(3) && somexyspace(croom, &pos)) { + (void) mkobj_at(RANDOM_CLASS, pos.x, pos.y, TRUE); + trycnt = 0; + while (!rn2(5)) { + if (++trycnt > 100) { + impossible("trycnt overflow4"); + break; + } + if (somexyspace(croom, &pos)) { + (void) mkobj_at(RANDOM_CLASS, pos.x, pos.y, TRUE); + } + } } +} - if (u.uz.dlevel != 1) { - xchar sx, sy; - do { - sx = somex(croom); - sy = somey(croom); - } while (occupied(sx, sy)); - mkstairs(sx, sy, 1, croom); /* up */ +staticfn void +themerooms_post_level_generate(void) +{ + lua_State *themes = (lua_State *) gl.luathemes[u.uz.dnum]; + + /* themes should already be loaded by makerooms(); + * if not, we don't run this either */ + if (!themes) + return; + + reset_xystart_size(); + iflags.in_lua = gi.in_mk_themerooms = TRUE; + gt.themeroom_failed = FALSE; + lua_getglobal(themes, "post_level_generate"); + nhl_pcall_handle(themes, 0, 0, "post_level_generate", NHLpa_panic); + iflags.in_lua = gi.in_mk_themerooms = FALSE; + + wallification(1, 0, COLNO - 1, ROWNO - 1); + if (gc.coder) + free(gc.coder), gc.coder = NULL; + lua_gc(themes, LUA_GCCOLLECT); +} + +/* if x,y is door, does it open into solid terrain */ +staticfn boolean +chk_okdoor(coordxy x, coordxy y) +{ + if (IS_DOOR(levl[x][y].typ)) { + if (levl[x][y].horizontal) { + if ((isok(x, y-1) && (levl[x][y-1].typ > TREE)) + && (isok(x, y+1) && (levl[x][y+1].typ <= TREE))) + return FALSE; + if ((isok(x, y-1) && (levl[x][y-1].typ <= TREE)) + && (isok(x, y+1) && (levl[x][y+1].typ > TREE))) + return FALSE; + } else { + if ((isok(x-1, y) && (levl[x-1][y].typ > TREE)) + && (isok(x+1, y) && (levl[x+1][y].typ <= TREE))) + return FALSE; + if ((isok(x-1, y) && (levl[x-1][y].typ <= TREE)) + && (isok(x+1, y) && (levl[x+1][y].typ > TREE))) + return FALSE; + } + return TRUE; } + return TRUE; +} - branchp = Is_branchlev(&u.uz); /* possible dungeon branch */ - room_threshold = branchp ? 4 : 3; /* minimum number of rooms needed - to allow a random special room */ - if (Is_rogue_level(&u.uz)) - goto skip0; - makecorridors(); - make_niches(); - - /* make a secret treasure vault, not connected to the rest */ - if (do_vault()) { - xchar w, h; - - debugpline0("trying to make a vault..."); - w = 1; - h = 1; - if (check_room(&vault_x, &w, &vault_y, &h, TRUE)) { - fill_vault: - add_room(vault_x, vault_y, vault_x + w, vault_y + h, - TRUE, VAULT, FALSE); - level.flags.has_vault = 1; - ++room_threshold; - fill_room(&rooms[nroom - 1], FALSE); - mk_knox_portal(vault_x + w, vault_y + h); - if (!level.flags.noteleport && !rn2(3)) - makevtele(); - } else if (rnd_rect() && create_vault()) { - vault_x = rooms[nroom].lx; - vault_y = rooms[nroom].ly; - if (check_room(&vault_x, &w, &vault_y, &h, TRUE)) - goto fill_vault; - else - rooms[nroom].hx = -1; +/* check mklev created level sanity */ +staticfn void +mklev_sanity_check(void) +{ + coordxy x, y; + int i; + int rmno = -1; + + if (!(iflags.sanity_check || iflags.debug_fuzzer)) + return; + + for (y = 0; y < ROWNO; y++) { + for (x = 1; x < COLNO; x++) { + if (!chk_okdoor(x,y)) + impossible("levl[%i][%i] door not ok", x, y); } } - { - register int u_depth = depth(&u.uz); + for (i = 0; i < svn.nroom; i++) { + if (!svr.rooms[i].needjoining) + continue; + if (rmno == -1) + rmno = gs.smeq[i]; + if (rmno != -1 && gs.smeq[i] != rmno) + impossible("room %i not connected?", i); + } +} + +/* Added for NLE. */ +extern char* nle_getenv(const char *); + +staticfn void +makelevel(void) +{ + struct mkroom *croom; + branch *branchp; + stairway *prevstairs; + int room_threshold; + s_level *slev; + int i; + + if (wiz1_level.dlevel == 0) { + impossible("makelevel() called when dungeon not yet initialized."); + init_dungeons(); + } + oinit(); /* assign level dependent obj probabilities */ + clear_level_structures(); + + slev = Is_special(&u.uz); + /* check for special levels */ + if (slev && !Is_rogue_level(&u.uz)) { + makemaz(slev->proto); + } else if (svd.dungeons[u.uz.dnum].proto[0]) { + makemaz(""); + } else if (svd.dungeons[u.uz.dnum].fill_lvl[0]) { + makemaz(svd.dungeons[u.uz.dnum].fill_lvl); + } else if (In_quest(&u.uz)) { + char fillname[9]; + s_level *loc_lev; + + Sprintf(fillname, "%s-loca", gu.urole.filecode); + loc_lev = find_level(fillname); + + Sprintf(fillname, "%s-fil", gu.urole.filecode); + Strcat(fillname, + (u.uz.dlevel < loc_lev->dlevel.dlevel) ? "a" : "b"); + makemaz(fillname); + } else if (In_hell(&u.uz) + || (rn2(5) && u.uz.dnum == medusa_level.dnum + && depth(&u.uz) > depth(&medusa_level))) { + makemaz(""); + } else { + /* otherwise, fall through - it's a "regular" level. */ + int u_depth = depth(&u.uz); + + if (Is_rogue_level(&u.uz)) { + makeroguerooms(); + makerogueghost(); + } else { + makerooms(); + } + assert(svn.nroom > 0); + sort_rooms(); + + generate_stairs(); /* up and down stairs */ + + branchp = Is_branchlev(&u.uz); /* possible dungeon branch */ + room_threshold = branchp ? 4 : 3; /* minimum number of rooms needed + to allow a random special room */ + if (Is_rogue_level(&u.uz)) + goto skip0; + makecorridors(); + make_niches(); + + mklev_sanity_check(); + + /* make a secret treasure vault, not connected to the rest */ + if (do_vault()) { + coordxy w, h; + + debugpline0("trying to make a vault..."); + w = 1; + h = 1; + if (check_room(&gv.vault_x, &w, &gv.vault_y, &h, TRUE)) { + fill_vault: + add_room(gv.vault_x, gv.vault_y, + gv.vault_x + w, gv.vault_y + h, + TRUE, VAULT, FALSE); + svl.level.flags.has_vault = 1; + ++room_threshold; + svr.rooms[svn.nroom - 1].needfill = FILL_NORMAL; + fill_special_room(&svr.rooms[svn.nroom - 1]); + mk_knox_portal(gv.vault_x + w, gv.vault_y + h); + if (!svl.level.flags.noteleport && !rn2(3)) + makevtele(); + } else if (rnd_rect() && create_vault()) { + gv.vault_x = svr.rooms[svn.nroom].lx; + gv.vault_y = svr.rooms[svn.nroom].ly; + if (check_room(&gv.vault_x, &w, &gv.vault_y, &h, TRUE)) + goto fill_vault; + else + svr.rooms[svn.nroom].hx = -1; + } + } + /* make up to 1 special room, with type dependent on depth; + note that mkroom doesn't guarantee a room gets created, and that + this step only sets the room's rtype - it doesn't fill it yet. */ if (wizard && nle_getenv("SHOPTYPE")) - mkroom(SHOPBASE); + do_mkroom(SHOPBASE); else if (u_depth > 1 && u_depth < depth(&medusa_level) - && nroom >= room_threshold && rn2(u_depth) < 3) - mkroom(SHOPBASE); + && svn.nroom >= room_threshold && rn2(u_depth) < 3) + do_mkroom(SHOPBASE); else if (u_depth > 4 && !rn2(6)) - mkroom(COURT); + do_mkroom(COURT); else if (u_depth > 5 && !rn2(8) - && !(mvitals[PM_LEPRECHAUN].mvflags & G_GONE)) - mkroom(LEPREHALL); + && !(svm.mvitals[PM_LEPRECHAUN].mvflags & G_GONE)) + do_mkroom(LEPREHALL); else if (u_depth > 6 && !rn2(7)) - mkroom(ZOO); + do_mkroom(ZOO); else if (u_depth > 8 && !rn2(5)) - mkroom(TEMPLE); + do_mkroom(TEMPLE); else if (u_depth > 9 && !rn2(5) - && !(mvitals[PM_KILLER_BEE].mvflags & G_GONE)) - mkroom(BEEHIVE); + && !(svm.mvitals[PM_KILLER_BEE].mvflags & G_GONE)) + do_mkroom(BEEHIVE); else if (u_depth > 11 && !rn2(6)) - mkroom(MORGUE); + do_mkroom(MORGUE); else if (u_depth > 12 && !rn2(8) && antholemon()) - mkroom(ANTHOLE); + do_mkroom(ANTHOLE); else if (u_depth > 14 && !rn2(4) - && !(mvitals[PM_SOLDIER].mvflags & G_GONE)) - mkroom(BARRACKS); + && !(svm.mvitals[PM_SOLDIER].mvflags & G_GONE)) + do_mkroom(BARRACKS); else if (u_depth > 15 && !rn2(6)) - mkroom(SWAMP); + do_mkroom(SWAMP); else if (u_depth > 16 && !rn2(8) - && !(mvitals[PM_COCKATRICE].mvflags & G_GONE)) - mkroom(COCKNEST); - } + && !(svm.mvitals[PM_COCKATRICE].mvflags & G_GONE)) + do_mkroom(COCKNEST); skip0: - /* Place multi-dungeon branch. */ - place_branch(branchp, 0, 0); - - /* for each room: put things inside */ - for (croom = rooms; croom->hx > 0; croom++) { - if (croom->rtype != OROOM) - continue; - - /* put a sleeping monster inside */ - /* Note: monster may be on the stairs. This cannot be - avoided: maybe the player fell through a trap door - while a monster was on the stairs. Conclusion: - we have to check for monsters on the stairs anyway. */ - - if (u.uhave.amulet || !rn2(3)) { - x = somex(croom); - y = somey(croom); - tmonst = makemon((struct permonst *) 0, x, y, MM_NOGRP); - if (tmonst && tmonst->data == &mons[PM_GIANT_SPIDER] - && !occupied(x, y)) - (void) maketrap(x, y, WEB); + prevstairs = gs.stairs; /* used to test for place_branch() success */ + /* Place multi-dungeon branch. */ + place_branch(branchp, 0, 0); + + /* for main dungeon level 1, the stairs up where the hero starts + are branch stairs; treat them as if hero had just come down + them by marking them as having been traversed; most recently + created stairway is held in 'gs.stairs' */ + if (u.uz.dnum == 0 && u.uz.dlevel == 1 && gs.stairs != prevstairs) + gs.stairs->u_traversed = TRUE; + + /* some levels have specially generated items in ordinary + rooms (intended to be indistinguishable from the normally + generated items); work out which room these will be placed in */ + int fillable_room_count = 0; + for (croom = svr.rooms; croom->hx > 0; croom++) { + if (ROOM_IS_FILLABLE(croom)) + fillable_room_count++; } - /* put traps and mimics inside */ - x = 8 - (level_difficulty() / 6); - if (x <= 1) - x = 2; - while (!rn2(x)) - mktrap(0, 0, croom, (coord *) 0); - if (!rn2(3)) - (void) mkgold(0L, somex(croom), somey(croom)); - if (Is_rogue_level(&u.uz)) - goto skip_nonrogue; - if (!rn2(10)) - mkfount(0, croom); - if (!rn2(60)) - mksink(croom); - if (!rn2(60)) - mkaltar(croom); - x = 80 - (depth(&u.uz) * 2); - if (x < 2) - x = 2; - if (!rn2(x)) - mkgrave(croom); - - /* put statues inside */ - if (!rn2(20)) - (void) mkcorpstat(STATUE, (struct monst *) 0, - (struct permonst *) 0, somex(croom), - somey(croom), CORPSTAT_INIT); - /* put box/chest inside; - * 40% chance for at least 1 box, regardless of number - * of rooms; about 5 - 7.5% for 2 boxes, least likely - * when few rooms; chance for 3 or more is negligible. - */ - if (!rn2(nroom * 5 / 2)) - (void) mksobj_at((rn2(3)) ? LARGE_BOX : CHEST, somex(croom), - somey(croom), TRUE, FALSE); - - /* maybe make some graffiti */ - if (!rn2(27 + 3 * abs(depth(&u.uz)))) { - char buf[BUFSZ]; - const char *mesg = random_engraving(buf); - - if (mesg) { - do { - x = somex(croom); - y = somey(croom); - } while (levl[x][y].typ != ROOM && !rn2(40)); - if (!(IS_POOL(levl[x][y].typ) - || IS_FURNITURE(levl[x][y].typ))) - make_engr_at(x, y, mesg, 0L, MARK); - } + /* choose a random fillable room to be the one that gets the + bonus items, if there are any; if there aren't any we don't + generate the bonus items (but levels with no fillable rooms + typically don't have any bonus items to generate anyway) */ + int bonus_item_room_countdown = fillable_room_count + ? rn2(fillable_room_count) : -1; + + /* for each room: put things inside */ + for (croom = svr.rooms; croom->hx > 0; croom++) { + boolean fillable = ROOM_IS_FILLABLE(croom); + + fill_ordinary_room(croom, + fillable && bonus_item_room_countdown == 0); + if (fillable) + --bonus_item_room_countdown; } + } + /* Fill all special rooms now, regardless of whether this is a special + * level, proto level, or ordinary level. */ + for (i = 0; i < svn.nroom; ++i) { + fill_special_room(&svr.rooms[i]); + } - skip_nonrogue: - if (!rn2(3)) { - (void) mkobj_at(0, somex(croom), somey(croom), TRUE); - tryct = 0; - while (!rn2(5)) { - if (++tryct > 100) { - impossible("tryct overflow4"); - break; - } - (void) mkobj_at(0, somex(croom), somey(croom), TRUE); - } - } + themerooms_post_level_generate(); + + if (gl.luacore && nhcb_counts[NHCB_LVL_ENTER]) { + lua_getglobal(gl.luacore, "nh_callback_run"); + lua_pushstring(gl.luacore, nhcb_name[NHCB_LVL_ENTER]); + nhl_pcall_handle(gl.luacore, 1, 0, "makelevel", NHLpa_panic); + lua_settop(gl.luacore, 0); } } +/* return TRUE if water location at (x,y) should have kelp. */ +staticfn boolean +water_has_kelp(coordxy x, coordxy y, int kelp_pool, int kelp_moat) +{ + if ((kelp_pool && (levl[x][y].typ == POOL + || (levl[x][y].typ == WATER && !Is_waterlevel(&u.uz))) + && !rn2(kelp_pool)) + || (kelp_moat && levl[x][y].typ == MOAT && !rn2(kelp_moat))) + return TRUE; + return FALSE; +} + /* * Place deposits of minerals (gold and misc gems) in the stone * surrounding the rooms on the map. * Also place kelp in water. - * mineralize(-1, -1, -1, -1, FALSE); => "default" behaviour + * mineralize(-1, -1, -1, -1, FALSE); => "default" behavior */ void -mineralize(kelp_pool, kelp_moat, goldprob, gemprob, skip_lvl_checks) -int kelp_pool, kelp_moat, goldprob, gemprob; -boolean skip_lvl_checks; +mineralize(int kelp_pool, int kelp_moat, int goldprob, int gemprob, + boolean skip_lvl_checks) { s_level *sp; struct obj *otmp; - int x, y, cnt; + coordxy x, y; + int cnt; if (kelp_pool < 0) kelp_pool = 10; @@ -913,15 +1469,14 @@ boolean skip_lvl_checks; return; for (x = 2; x < (COLNO - 2); x++) for (y = 1; y < (ROWNO - 1); y++) - if ((kelp_pool && levl[x][y].typ == POOL && !rn2(kelp_pool)) - || (kelp_moat && levl[x][y].typ == MOAT && !rn2(kelp_moat))) + if (water_has_kelp(x, y, kelp_pool, kelp_moat)) (void) mksobj_at(KELP_FROND, x, y, TRUE, FALSE); /* determine if it is even allowed; almost all special levels are excluded */ if (!skip_lvl_checks && (In_hell(&u.uz) || In_V_tower(&u.uz) || Is_rogue_level(&u.uz) - || level.flags.arboreal + || svl.level.flags.arboreal || ((sp = Is_special(&u.uz)) != 0 && !Is_oracle_level(&u.uz) && (!In_mines(&u.uz) || sp->flags.town)))) return; @@ -991,33 +1546,25 @@ boolean skip_lvl_checks; } void -mklev() +level_finalize_topology(void) { struct mkroom *croom; int ridx; - reseed_random(rn2); - reseed_random(rn2_on_display_rng); - - /* NLE: Use the level generation RNG if required */ - nle_swap_to_lgen(u.uz.dnum); - - init_mapseen(&u.uz); - if (getbones()) - return; - - in_mklev = TRUE; - makelevel(); bound_digging(); mineralize(-1, -1, -1, -1, FALSE); - in_mklev = FALSE; + gi.in_mklev = FALSE; + /* avoid coordinates in future lua-loads for this level being thrown off + * because xstart and ystart aren't saved with the level and will be 0 + * after leaving and returning */ + gx.xstart = gy.ystart = 0; /* has_morgue gets cleared once morgue is entered; graveyard stays set (graveyard might already be set even when has_morgue is clear [see fixup_special()], so don't update it unconditionally) */ - if (level.flags.has_morgue) - level.flags.graveyard = 1; - if (!level.flags.is_maze_lev) { - for (croom = &rooms[0]; croom != &rooms[nroom]; croom++) + if (svl.level.flags.has_morgue) + svl.level.flags.graveyard = 1; + if (!svl.level.flags.is_maze_lev) { + for (croom = &svr.rooms[0]; croom != &svr.rooms[svn.nroom]; croom++) #ifdef SPECIALIZATION topologize(croom, FALSE); #else @@ -1025,38 +1572,48 @@ mklev() #endif } set_wall_state(); - /* for many room types, rooms[].rtype is zeroed once the room has been - entered; rooms[].orig_rtype always retains original rtype value */ - for (ridx = 0; ridx < SIZE(rooms); ridx++) - rooms[ridx].orig_rtype = rooms[ridx].rtype; - - /* something like this usually belongs in clear_level_structures() - but these aren't saved and restored so might not retain their - values for the life of the current level; reset them to default - now so that they never do and no one will be tempted to introduce - a new use of them for anything on this level */ - dnstairs_room = upstairs_room = sstairs_room = (struct mkroom *) 0; + /* for many room types, svr.rooms[].rtype is zeroed once the room has been + entered; svr.rooms[].orig_rtype always retains original rtype value */ + for (ridx = 0; ridx < SIZE(svr.rooms); ridx++) + svr.rooms[ridx].orig_rtype = svr.rooms[ridx].rtype; +} + +void +mklev(void) +{ + reseed_random(rn2); + reseed_random(rn2_on_display_rng); + + /* NLE: Use the level generation RNG if required */ + nle_swap_to_lgen(u.uz.dnum); + + init_mapseen(&u.uz); + if (getbones()) + return; + + gi.in_mklev = TRUE; + makelevel(); + + level_finalize_topology(); /* NLE: Restore CORE RNG state if required */ nle_swap_to_core(u.uz.dnum); - + reseed_random(rn2); reseed_random(rn2_on_display_rng); } void #ifdef SPECIALIZATION -topologize(croom, do_ordinary) -struct mkroom *croom; -boolean do_ordinary; +topologize(struct mkroom *croom, boolean do_ordinary) #else -topologize(croom) -struct mkroom *croom; +topologize(struct mkroom *croom) #endif { - register int x, y, roomno = (int) ((croom - rooms) + ROOMOFFSET); - int lowx = croom->lx, lowy = croom->ly; - int hix = croom->hx, hiy = croom->hy; + coordxy x, y; + int roomno = (int) ((croom - svr.rooms) + ROOMOFFSET); + coordxy lowx = croom->lx, lowy = croom->ly; + coordxy hix = croom->hx, hiy = croom->hy; #ifdef SPECIALIZATION schar rtype = croom->rtype; #endif @@ -1100,7 +1657,7 @@ struct mkroom *croom; levl[x][y].roomno = roomno; } } - /* subrooms */ + /* gs.subrooms */ for (subindex = 0; subindex < nsubrooms; subindex++) #ifdef SPECIALIZATION topologize(croom->sbrooms[subindex], (boolean) (rtype != OROOM)); @@ -1110,45 +1667,30 @@ struct mkroom *croom; } /* Find an unused room for a branch location. */ -STATIC_OVL struct mkroom * -find_branch_room(mp) -coord *mp; +staticfn struct mkroom * +find_branch_room(coord *mp) { struct mkroom *croom = 0; - if (nroom == 0) { + if (svn.nroom == 0) { mazexy(mp); /* already verifies location */ } else { - /* not perfect - there may be only one stairway */ - if (nroom > 2) { - int tryct = 0; - - do - croom = &rooms[rn2(nroom)]; - while ((croom == dnstairs_room || croom == upstairs_room - || croom->rtype != OROOM) && (++tryct < 100)); - } else - croom = &rooms[rn2(nroom)]; - - do { - if (!somexy(croom, mp)) - impossible("Can't place branch!"); - } while (occupied(mp->x, mp->y) - || (levl[mp->x][mp->y].typ != CORR - && levl[mp->x][mp->y].typ != ROOM)); + croom = generate_stairs_find_room(); + assert(croom != NULL); /* Null iff nroom==0 which won't get here */ + if (!somexyspace(croom, mp)) + impossible("Can't place branch!"); } return croom; } /* Find the room for (x,y). Return null if not in a room. */ -STATIC_OVL struct mkroom * -pos_to_room(x, y) -xchar x, y; +staticfn struct mkroom * +pos_to_room(coordxy x, coordxy y) { int i; struct mkroom *curr; - for (curr = rooms, i = 0; i < nroom; curr++, i++) + for (curr = svr.rooms, i = 0; i < svn.nroom; curr++, i++) if (inside_room(curr, x, y)) return curr; ; @@ -1157,14 +1699,13 @@ xchar x, y; /* If given a branch, randomly place a special stair or portal. */ void -place_branch(br, x, y) -branch *br; /* branch to place */ -xchar x, y; /* location */ +place_branch( + branch *br, /* branch to place */ + coordxy x, coordxy y) /* location */ { - coord m; + coord m = {0}; d_level *dest; boolean make_stairs; - struct mkroom *br_room; /* * Return immediately if there is no branch to make or we have @@ -1172,15 +1713,16 @@ xchar x, y; /* location */ * a special level is loaded that specifies an SSTAIR location * as a favored spot for a branch. */ - if (!br || made_branch) + if (!br || gm.made_branch) return; if (!x) { /* find random coordinates for branch */ - br_room = find_branch_room(&m); + /* br_room = find_branch_room(&m); */ + (void) find_branch_room(&m); /* sets m via mazexy() or somexy() */ x = m.x; y = m.y; } else { - br_room = pos_to_room(x, y); + (void) pos_to_room(x, y); } if (on_level(&br->end1, &u.uz)) { @@ -1194,17 +1736,17 @@ xchar x, y; /* location */ } if (br->type == BR_PORTAL) { - mkportal(x, y, dest->dnum, dest->dlevel); + if (iflags.debug_fuzzer && (u.ucamefrom.dnum || u.ucamefrom.dlevel)) + mkportal(x, y, u.ucamefrom.dnum, u.ucamefrom.dlevel); + else + mkportal(x, y, dest->dnum, dest->dlevel); } else if (make_stairs) { - sstairs.sx = x; - sstairs.sy = y; - sstairs.up = - (char) on_level(&br->end1, &u.uz) ? br->end1_up : !br->end1_up; - assign_level(&sstairs.tolev, dest); - sstairs_room = br_room; + boolean goes_up = on_level(&br->end1, &u.uz) ? br->end1_up + : !br->end1_up; - levl[x][y].ladder = sstairs.up ? LA_UP : LA_DOWN; - levl[x][y].typ = STAIRS; + stairway_add(x, y, goes_up, FALSE, dest); + (void) set_levltyp(x, y, STAIRS); + levl[x][y].ladder = goes_up ? LA_UP : LA_DOWN; } /* * Set made_branch to TRUE even if we didn't make a stairwell (i.e. @@ -1212,14 +1754,13 @@ xchar x, y; /* location */ * per level, if we failed once, we're going to fail again on the * next call. */ - made_branch = TRUE; + gm.made_branch = TRUE; } -STATIC_OVL boolean -bydoor(x, y) -register xchar x, y; +staticfn boolean +bydoor(coordxy x, coordxy y) { - register int typ; + int typ; if (isok(x + 1, y)) { typ = levl[x + 1][y].typ; @@ -1246,132 +1787,303 @@ register xchar x, y; /* see whether it is allowable to create a door at [x,y] */ int -okdoor(x, y) -xchar x, y; +okdoor(coordxy x, coordxy y) { boolean near_door = bydoor(x, y); return ((levl[x][y].typ == HWALL || levl[x][y].typ == VWALL) - && doorindex < DOORMAX && !near_door); + && ((isok(x - 1, y) && !IS_OBSTRUCTED(levl[x - 1][y].typ)) + || (isok(x + 1, y) && !IS_OBSTRUCTED(levl[x + 1][y].typ)) + || (isok(x, y - 1) && !IS_OBSTRUCTED(levl[x][y - 1].typ)) + || (isok(x, y + 1) && !IS_OBSTRUCTED(levl[x][y + 1].typ))) + && !near_door); } -void -dodoor(x, y, aroom) -int x, y; -struct mkroom *aroom; +/* do we want a secret door/corridor? */ +boolean +maybe_sdoor(int chance) { - if (doorindex >= DOORMAX) { - impossible("DOORMAX exceeded?"); - return; - } + return (depth(&u.uz) > 2) && !rn2(max(2, chance)); +} - dosdoor(x, y, aroom, rn2(8) ? DOOR : SDOOR); +/* create a door at x,y in room aroom */ +void +dodoor(coordxy x, coordxy y, struct mkroom *aroom) +{ + dosdoor(x, y, aroom, maybe_sdoor(8) ? SDOOR : DOOR); } boolean -occupied(x, y) -register xchar x, y; +occupied(coordxy x, coordxy y) { return (boolean) (t_at(x, y) || IS_FURNITURE(levl[x][y].typ) || is_lava(x, y) || is_pool(x, y) || invocation_pos(x, y)); } -/* make a trap somewhere (in croom if mazeflag = 0 && !tm) */ -/* if tm != null, make trap at that location */ -void -mktrap(num, mazeflag, croom, tm) -int num, mazeflag; -struct mkroom *croom; -coord *tm; +/* generate a corpse and some items on top of a trap */ +staticfn void +mktrap_victim(struct trap *ttmp) { - register int kind; - struct trap *t; + /* Object generated by the trap; initially NULL, stays NULL if + the trap doesn't generate objects. */ + struct obj *otmp = NULL; + int victim_mnum; /* race of the victim */ unsigned lvl = level_difficulty(); - coord m; + int kind = ttmp->ttyp; + coordxy x = ttmp->tx, y = ttmp->ty; + + assert(x > 0 && x < COLNO && y >= 0 && y < ROWNO); + /* Not all trap types have special handling here; only the ones + that kill in a specific way that's obvious after the fact. */ + switch (kind) { + case ARROW_TRAP: + otmp = mksobj(ARROW, TRUE, FALSE); + otmp->opoisoned = 0; + /* don't adjust the quantity; maybe the trap shot multiple + times, there was an untrapping attempt, etc... */ + break; + case DART_TRAP: + otmp = mksobj(DART, TRUE, FALSE); + break; + case ROCKTRAP: + otmp = mksobj(ROCK, TRUE, FALSE); + break; + default: + /* no item dropped by the trap */ + break; + } + if (otmp) { + place_object(otmp, x, y); + } - /* no traps in pools */ - if (tm && is_pool(tm->x, tm->y)) - return; + /* now otmp is reused for other items we're placing */ - if (num > 0 && num < TRAPNUM) { - kind = num; - } else if (Is_rogue_level(&u.uz)) { - switch (rn2(7)) { - default: - kind = BEAR_TRAP; - break; /* 0 */ + /* Place a random possession. This could be a weapon, tool, + food, or gem, i.e. the item classes that are typically + nonmagical and not worthless. */ + do { + int poss_class = RANDOM_CLASS; /* init => lint suppression */ + + switch (rn2(4)) { + case 0: + poss_class = WEAPON_CLASS; + break; case 1: - kind = ARROW_TRAP; + poss_class = TOOL_CLASS; break; case 2: - kind = DART_TRAP; + poss_class = FOOD_CLASS; break; case 3: - kind = TRAPDOOR; - break; - case 4: - kind = PIT; - break; - case 5: - kind = SLP_GAS_TRAP; - break; - case 6: - kind = RUST_TRAP; + poss_class = GEM_CLASS; break; } + + /* these items are always cursed, both for flavour (owned + by a dead adventurer, bones-pile-style) and for balance + (less useful to use, and encourage pets to avoid the trap) */ + otmp = mkobj(poss_class, FALSE); + curse(otmp); + /* for mktrap_victim(), PIT is actually an exploded LANDMINE */ + if (ttmp->ttyp == PIT && breaktest(otmp)) { + /* landmine: if fragile object has been created, destroy it; + don't worry about non-empty containers--they aren't + breakable--nor about breakable contents of such */ + dealloc_obj(otmp); + } else { + place_object(otmp, x, y); + } + + /* 20% chance of placing an additional item, recursively */ + } while (!rn2(5)); + + /* Place a corpse. */ + switch (rn2(15)) { + case 0: + /* elf corpses are the rarest as they're the most useful */ + victim_mnum = PM_ELF; + /* elven adventurers get sleep resistance early; so don't + generate elf corpses on sleeping gas traps unless a) + we're on dlvl 2 (1 is impossible) and b) we pass a coin + flip */ + if (kind == SLP_GAS_TRAP && !(lvl <= 2 && rn2(2))) + victim_mnum = PM_HUMAN; + break; + case 1: case 2: + victim_mnum = PM_DWARF; + break; + case 3: case 4: case 5: + victim_mnum = PM_ORC; + break; + case 6: case 7: case 8: case 9: + /* more common as they could have come from the Mines */ + victim_mnum = PM_GNOME; + /* 10% chance of a candle too */ + if (!rn2(10)) { + otmp = mksobj(rn2(4) ? TALLOW_CANDLE : WAX_CANDLE, TRUE, FALSE); + otmp->quan = 1; + otmp->owt = weight(otmp); + curse(otmp); + place_object(otmp, x, y); + if (!levl[x][y].lit) + begin_burn(otmp, FALSE); + } + break; + default: + /* human is the most common result */ + victim_mnum = PM_HUMAN; + break; + } + /* PM_HUMAN is a placeholder monster primarily used for zombie, mummy, + and vampire corpses; usually change it into a fake player monster + instead (always human); no role-specific equipment is provided */ + if (victim_mnum == PM_HUMAN && rn2(25)) + victim_mnum = rn1(PM_WIZARD - PM_ARCHEOLOGIST, PM_ARCHEOLOGIST); + otmp = mkcorpstat(CORPSE, NULL, &mons[victim_mnum], x, y, CORPSTAT_INIT); + otmp->age -= (TAINT_AGE + 1); /* died too long ago to safely eat */ +} + +/* pick a random trap type, return NO_TRAP if "too hard" */ +staticfn int +traptype_rnd(unsigned mktrapflags) +{ + unsigned lvl = level_difficulty(); + int kind = rnd(TRAPNUM - 1); + + switch (kind) { + /* these are controlled by the feature or object they guard, + not by the map so mustn't be created on it */ + case TRAPPED_DOOR: + case TRAPPED_CHEST: + kind = NO_TRAP; + break; + /* these can have a random location but can't be generated + randomly */ + case MAGIC_PORTAL: + case VIBRATING_SQUARE: + kind = NO_TRAP; + break; + case ROLLING_BOULDER_TRAP: + case SLP_GAS_TRAP: + if (lvl < 2) + kind = NO_TRAP; + break; + case LEVEL_TELEP: + if (lvl < 5 || svl.level.flags.noteleport + || single_level_branch(&u.uz)) + kind = NO_TRAP; + break; + case SPIKED_PIT: + if (lvl < 5) + kind = NO_TRAP; + break; + case LANDMINE: + if (lvl < 6) + kind = NO_TRAP; + break; + case WEB: + if (lvl < 7 && !(mktrapflags & MKTRAP_NOSPIDERONWEB)) + kind = NO_TRAP; + break; + case STATUE_TRAP: + case POLY_TRAP: + if (lvl < 8) + kind = NO_TRAP; + break; + case FIRE_TRAP: + if (!Inhell) + kind = NO_TRAP; + break; + case TELEP_TRAP: + if (svl.level.flags.noteleport) + kind = NO_TRAP; + break; + case HOLE: + /* make these much less often than other traps */ + if (rn2(7)) + kind = NO_TRAP; + break; + } + return kind; +} + +/* random trap type for the Rogue level */ +staticfn int +traptype_roguelvl(void) +{ + int kind; + + switch (rn2(7)) { + default: + kind = BEAR_TRAP; + break; /* 0 */ + case 1: + kind = ARROW_TRAP; + break; + case 2: + kind = DART_TRAP; + break; + case 3: + kind = TRAPDOOR; + break; + case 4: + kind = PIT; + break; + case 5: + kind = SLP_GAS_TRAP; + break; + case 6: + kind = RUST_TRAP; + break; + } + return kind; +} + +/* mktrap(): select trap type and location, then use maketrap() to create it; + make it at location 'tm' when that isn't Null, otherwise in 'croom' + if mktrapflags doesn't have MKTRAP_MAZEFLAG set, else in maze corridor */ +void +mktrap( + int num, /* if non-zero, specific type of trap to make */ + unsigned mktrapflags, /* MKTRAP_{SEEN,MAZEFLAG,NOSPIDERONWEB,NOVICTIM} */ + struct mkroom *croom, /* room to hold trap */ + coord *tm) /* specific location for trap */ +{ + static int mktrap_err = 0; /* move to struct g? */ + struct trap *t; + coord m; + int kind; + unsigned lvl = level_difficulty(); + + if (!tm && !croom && !(mktrapflags & MKTRAP_MAZEFLAG)) { + /* complain when the combination of arguments will never set 'm' */ + if (!mktrap_err++) { + char errbuf[BUFSZ]; + + Snprintf(errbuf, sizeof errbuf, + "args (%d,%d,%s,%s) are invalid", + num, mktrapflags, "null room", "null location"); + paniclog("mktrap", errbuf); + } + return; + } + m.x = m.y = 0; + + /* no traps in pools */ + if (tm && is_pool_or_lava(tm->x, tm->y)) + return; + + if (num > NO_TRAP && num < TRAPNUM) { + kind = num; + } else if (Is_rogue_level(&u.uz)) { + kind = traptype_roguelvl(); } else if (Inhell && !rn2(5)) { /* bias the frequency of fire traps in Gehennom */ kind = FIRE_TRAP; } else { do { - kind = rnd(TRAPNUM - 1); - /* reject "too hard" traps */ - switch (kind) { - case MAGIC_PORTAL: - case VIBRATING_SQUARE: - kind = NO_TRAP; - break; - case ROLLING_BOULDER_TRAP: - case SLP_GAS_TRAP: - if (lvl < 2) - kind = NO_TRAP; - break; - case LEVEL_TELEP: - if (lvl < 5 || level.flags.noteleport) - kind = NO_TRAP; - break; - case SPIKED_PIT: - if (lvl < 5) - kind = NO_TRAP; - break; - case LANDMINE: - if (lvl < 6) - kind = NO_TRAP; - break; - case WEB: - if (lvl < 7) - kind = NO_TRAP; - break; - case STATUE_TRAP: - case POLY_TRAP: - if (lvl < 8) - kind = NO_TRAP; - break; - case FIRE_TRAP: - if (!Inhell) - kind = NO_TRAP; - break; - case TELEP_TRAP: - if (level.flags.noteleport) - kind = NO_TRAP; - break; - case HOLE: - /* make these much less often than other traps */ - if (rn2(7)) - kind = NO_TRAP; - break; - } + kind = traptype_rnd(mktrapflags); } while (kind == NO_TRAP); } @@ -1381,15 +2093,15 @@ coord *tm; if (tm) { m = *tm; } else { - register int tryct = 0; + int tryct = 0; boolean avoid_boulder = (is_pit(kind) || is_hole(kind)); do { if (++tryct > 200) return; - if (mazeflag) + if ((mktrapflags & MKTRAP_MAZEFLAG) != 0) mazexy(&m); - else if (!somexy(croom, &m)) + else if (croom && !somexyspace(croom, &m)) return; } while (occupied(m.x, m.y) || (avoid_boulder && sobj_at(BOULDER, m.x, m.y))); @@ -1400,8 +2112,13 @@ coord *tm; should prevent cases where that might not happen) but be paranoid */ kind = t ? t->ttyp : NO_TRAP; - if (kind == WEB) + if (kind == WEB && !(mktrapflags & MKTRAP_NOSPIDERONWEB)) (void) makemon(&mons[PM_GIANT_SPIDER], m.x, m.y, NO_MM_FLAGS); + if (t && (mktrapflags & MKTRAP_SEEN)) + t->tseen = TRUE; + if (kind == MAGIC_PORTAL && (u.ucamefrom.dnum || u.ucamefrom.dlevel)) { + assign_level(&t->dst, &u.ucamefrom); + } /* The hero isn't the only person who's entered the dungeon in search of treasure. On the very shallowest levels, there's a @@ -1421,251 +2138,241 @@ coord *tm; nonlethal, even indirectly. We also exclude all of the later/fancier traps because they tend to have special considerations (e.g. webs, portals), often are indirectly - lethal, and tend not to generate on shallower levels anyway. - Finally, pits are excluded because it's weird to see an item - in a pit and yet not be able to identify that the pit is there. */ - if (kind != NO_TRAP && lvl <= (unsigned) rnd(4) + lethal, and tend not to generate on shallower levels anyway + (exception: magic traps can generate on dlvl 1 and be + immediately lethal). Finally, pits are excluded because it's + weird to see an item in a pit and yet not be able to identify + that the pit is there. */ + if (gi.in_mklev + && kind != NO_TRAP && !(mktrapflags & MKTRAP_NOVICTIM) + && lvl <= (unsigned) rnd(4) && kind != SQKY_BOARD && kind != RUST_TRAP /* rolling boulder trap might not have a boulder if there was no viable path (such as when placed in the corner of a room), in which case tx,ty==launch.x,y; no boulder => no dead predecessor */ && !(kind == ROLLING_BOULDER_TRAP && t->launch.x == t->tx && t->launch.y == t->ty) - && !is_pit(kind) && kind < HOLE) { - /* Object generated by the trap; initially NULL, stays NULL if - we fail to generate an object or if the trap doesn't - generate objects. */ - struct obj *otmp = NULL; - int victim_mnum; /* race of the victim */ - - /* Not all trap types have special handling here; only the ones - that kill in a specific way that's obvious after the fact. */ - switch (kind) { - case ARROW_TRAP: - otmp = mksobj(ARROW, TRUE, FALSE); - otmp->opoisoned = 0; - /* don't adjust the quantity; maybe the trap shot multiple - times, there was an untrapping attempt, etc... */ - break; - case DART_TRAP: - otmp = mksobj(DART, TRUE, FALSE); - break; - case ROCKTRAP: - otmp = mksobj(ROCK, TRUE, FALSE); - break; - default: - /* no item dropped by the trap */ - break; - } - if (otmp) { - place_object(otmp, m.x, m.y); - } - - /* now otmp is reused for other items we're placing */ - - /* Place a random possession. This could be a weapon, tool, - food, or gem, i.e. the item classes that are typically - nonmagical and not worthless. */ - do { - int poss_class = RANDOM_CLASS; /* init => lint suppression */ - - switch (rn2(4)) { - case 0: - poss_class = WEAPON_CLASS; - break; - case 1: - poss_class = TOOL_CLASS; - break; - case 2: - poss_class = FOOD_CLASS; - break; - case 3: - poss_class = GEM_CLASS; - break; - } - - otmp = mkobj(poss_class, FALSE); - /* these items are always cursed, both for flavour (owned - by a dead adventurer, bones-pile-style) and for balance - (less useful to use, and encourage pets to avoid the trap) */ - if (otmp) { - otmp->blessed = 0; - otmp->cursed = 1; - otmp->owt = weight(otmp); - place_object(otmp, m.x, m.y); - } - - /* 20% chance of placing an additional item, recursively */ - } while (!rn2(5)); - - /* Place a corpse. */ - switch (rn2(15)) { - case 0: - /* elf corpses are the rarest as they're the most useful */ - victim_mnum = PM_ELF; - /* elven adventurers get sleep resistance early; so don't - generate elf corpses on sleeping gas traps unless a) - we're on dlvl 2 (1 is impossible) and b) we pass a coin - flip */ - if (kind == SLP_GAS_TRAP && !(lvl <= 2 && rn2(2))) - victim_mnum = PM_HUMAN; - break; - case 1: case 2: - victim_mnum = PM_DWARF; - break; - case 3: case 4: case 5: - victim_mnum = PM_ORC; - break; - case 6: case 7: case 8: case 9: - /* more common as they could have come from the Mines */ - victim_mnum = PM_GNOME; - /* 10% chance of a candle too */ - if (!rn2(10)) { - otmp = mksobj(rn2(4) ? TALLOW_CANDLE : WAX_CANDLE, - TRUE, FALSE); - otmp->quan = 1; - otmp->blessed = 0; - otmp->cursed = 1; - otmp->owt = weight(otmp); - place_object(otmp, m.x, m.y); - } - break; - default: - /* the most common race */ - victim_mnum = PM_HUMAN; - break; + && !is_pit(kind) && (kind < HOLE || kind == MAGIC_TRAP)) { + if (kind == LANDMINE) { + /* if victim was killed by a land mine, we won't scatter objects; + treat it as exploded, converting it into an unconcealed pit */ + t->ttyp = PIT; + t->tseen = 1; } - otmp = mkcorpstat(CORPSE, NULL, &mons[victim_mnum], m.x, m.y, - CORPSTAT_INIT); - if (otmp) - otmp->age -= 51; /* died too long ago to eat */ + mktrap_victim(t); } + return; } +/* Create stairs up or down at x,y. + If force is TRUE, change the terrain to ROOM first */ void -mkstairs(x, y, up, croom) -xchar x, y; -char up; -struct mkroom *croom; +mkstairs( + coordxy x, coordxy y, + char up, /* [why 'char' when usage is boolean?] */ + struct mkroom *croom UNUSED, + boolean force) { - if (!x) { + int ltyp; + d_level dest; + + if (!x || !isok(x, y)) { impossible("mkstairs: bogus stair attempt at <%d,%d>", x, y); return; } + if (force) + levl[x][y].typ = ROOM; + ltyp = levl[x][y].typ; /* somexyspace() allows ice */ + if (ltyp != ROOM && ltyp != CORR && ltyp != ICE) { + int glyph = back_to_glyph(x, y), + sidx = glyph_to_cmap(glyph); + + impossible("mkstairs: placing stairs %s on %s at <%d,%d>", + up ? "up" : "down", defsyms[sidx].explanation, x, y); + } /* * We can't make a regular stair off an end of the dungeon. This * attempt can happen when a special level is placed at an end and * has an up or down stair specified in its description file. */ - if ((dunlev(&u.uz) == 1 && up) - || (dunlev(&u.uz) == dunlevs_in_dungeon(&u.uz) && !up)) + if (dunlev(&u.uz) == (up ? 1 : dunlevs_in_dungeon(&u.uz))) return; - if (up) { - xupstair = x; - yupstair = y; - upstairs_room = croom; - } else { - xdnstair = x; - ydnstair = y; - dnstairs_room = croom; - } + dest.dnum = u.uz.dnum; + dest.dlevel = u.uz.dlevel + (up ? -1 : 1); + stairway_add(x, y, up ? TRUE : FALSE, FALSE, &dest); - levl[x][y].typ = STAIRS; + (void) set_levltyp(x, y, STAIRS); levl[x][y].ladder = up ? LA_UP : LA_DOWN; } -STATIC_OVL void -mkfount(mazeflag, croom) -int mazeflag; -struct mkroom *croom; +/* is room a good one to generate up or down stairs in? */ +staticfn boolean +generate_stairs_room_good(struct mkroom *croom, int phase) +{ + /* + * phase values, smaller allows for more relaxed criteria: + * 2 == no relaxed criteria; + * 1 == allow a themed room; + * 0 == allow same room as existing up/downstairs; + * -1 == allow an unjoined room. + */ + return (croom && (croom->needjoining || (phase < 0)) + && ((!has_dnstairs(croom) && !has_upstairs(croom)) + || phase < 1) + && (croom->rtype == OROOM + || ((phase < 2) && croom->rtype == THEMEROOM))); +} + +/* find a good room to generate an up or down stairs in */ +staticfn struct mkroom * +generate_stairs_find_room(void) +{ + struct mkroom *croom; + int i, phase, ai; + int *rmarr; + + if (!svn.nroom) + return (struct mkroom *) 0; + + rmarr = (int *) alloc(sizeof(int) * svn.nroom); + + for (phase = 2; phase > -1; phase--) { + ai = 0; + for (i = 0; i < svn.nroom; i++) + if (generate_stairs_room_good(&svr.rooms[i], phase)) + rmarr[ai++] = i; + if (ai > 0) { + i = rmarr[rn2(ai)]; + free(rmarr); + return &svr.rooms[i]; + } + } + + free(rmarr); + croom = &svr.rooms[rn2(svn.nroom)]; + return croom; +} + +/* construct stairs up and down within the same branch, + up and down in different rooms if possible */ +staticfn void +generate_stairs(void) +{ + /* generate_stairs_find_room() returns Null if nroom == 0, but that + should never happen for a rooms+corridors style level */ + static const char + gen_stairs_panic[] = "generate_stairs: failed to find a room! (%d)"; + struct mkroom *croom; + coord pos; + + if (!Is_botlevel(&u.uz)) { + if ((croom = generate_stairs_find_room()) == NULL) + panic(gen_stairs_panic, svn.nroom); + + if (!somexyspace(croom, &pos)) { + pos.x = somex(croom); + pos.y = somey(croom); + } + mkstairs(pos.x, pos.y, 0, croom, FALSE); /* down */ + } + + if (u.uz.dlevel != 1) { + /* if there is only 1 room and we found it above, this will find + it again */ + if ((croom = generate_stairs_find_room()) == NULL) + panic(gen_stairs_panic, svn.nroom); + + if (!somexyspace(croom, &pos)) { + pos.x = somex(croom); + pos.y = somey(croom); + } + mkstairs(pos.x, pos.y, 1, croom, FALSE); /* up */ + } +} + +staticfn void +mkfount(struct mkroom *croom) { coord m; - register int tryct = 0; - do { - if (++tryct > 200) - return; - if (mazeflag) - mazexy(&m); - else if (!somexy(croom, &m)) - return; - } while (occupied(m.x, m.y) || bydoor(m.x, m.y)); + if (!find_okay_roompos(croom, &m)) + return; /* Put a fountain at m.x, m.y */ - levl[m.x][m.y].typ = FOUNTAIN; + if (!set_levltyp(m.x, m.y, FOUNTAIN)) + return; /* Is it a "blessed" fountain? (affects drinking from fountain) */ if (!rn2(7)) levl[m.x][m.y].blessedftn = 1; - level.flags.nfountains++; + svl.level.flags.nfountains++; } -STATIC_OVL void -mksink(croom) -struct mkroom *croom; +staticfn boolean +find_okay_roompos(struct mkroom *croom, coord *crd) { - coord m; - register int tryct = 0; + int tryct = 0; do { if (++tryct > 200) - return; - if (!somexy(croom, &m)) - return; - } while (occupied(m.x, m.y) || bydoor(m.x, m.y)); + return FALSE; + if (!somexyspace(croom, crd)) + return FALSE; + } while (occupied(crd->x, crd->y) || bydoor(crd->x, crd->y)); + return TRUE; +} + +staticfn void +mksink(struct mkroom *croom) +{ + coord m; + + if (!find_okay_roompos(croom, &m)) + return; /* Put a sink at m.x, m.y */ - levl[m.x][m.y].typ = SINK; + if (!set_levltyp(m.x, m.y, SINK)) + return; - level.flags.nsinks++; + svl.level.flags.nsinks++; } -STATIC_OVL void -mkaltar(croom) -struct mkroom *croom; +staticfn void +mkaltar(struct mkroom *croom) { coord m; - register int tryct = 0; aligntyp al; if (croom->rtype != OROOM) return; - do { - if (++tryct > 200) - return; - if (!somexy(croom, &m)) - return; - } while (occupied(m.x, m.y) || bydoor(m.x, m.y)); + if (!find_okay_roompos(croom, &m)) + return; /* Put an altar at m.x, m.y */ - levl[m.x][m.y].typ = ALTAR; + if (!set_levltyp(m.x, m.y, ALTAR)) + return; /* -1 - A_CHAOTIC, 0 - A_NEUTRAL, 1 - A_LAWFUL */ al = rn2((int) A_LAWFUL + 2) - 1; levl[m.x][m.y].altarmask = Align2amask(al); } -static void -mkgrave(croom) -struct mkroom *croom; +staticfn void +mkgrave(struct mkroom *croom) { coord m; - register int tryct = 0; - register struct obj *otmp; + int tryct = 0; + struct obj *otmp; boolean dobell = !rn2(10); if (croom->rtype != OROOM) return; - do { - if (++tryct > 200) - return; - if (!somexy(croom, &m)) - return; - } while (occupied(m.x, m.y) || bydoor(m.x, m.y)); + if (!find_okay_roompos(croom, &m)) + return; /* Put a grave at */ make_grave(m.x, m.y, dobell ? "Saved by the bell!" : (char *) 0); @@ -1700,14 +2407,10 @@ struct mkroom *croom; return; } -/* maze levels have slightly different constraints from normal levels */ -#define x_maze_min 2 -#define y_maze_min 2 - /* * Major level transmutation: add a set of stairs (to the Sanctum) after * an earthquake that leaves behind a new topology, centered at inv_pos. - * Assumes there are no rooms within the invocation area and that inv_pos + * Assumes there are no rooms within the invocation area and that svi.inv_pos * is not too close to the edge of the map. Also assume the hero can see, * which is guaranteed for normal play due to the fact that sight is needed * to read the Book of the Dead. [That assumption is not valid; it is @@ -1715,21 +2418,52 @@ struct mkroom *croom; * attempted while blind (in order to make blind-from-birth conduct viable).] */ void -mkinvokearea() +mkinvokearea(void) { - int dist; - xchar xmin = inv_pos.x, xmax = inv_pos.x, - ymin = inv_pos.y, ymax = inv_pos.y; - register xchar i; + int dist, wallct; + coordxy xmin, xmax, ymin, ymax; + coordxy i; /* slightly odd if levitating, but not wrong */ pline_The("floor shakes violently under you!"); - /* - * TODO: - * Suppress this message if player has dug out all the walls - * that would otherwise be affected. - */ - pline_The("walls around you begin to bend and crumble!"); + /* decide whether to issue the crumbling walls message */ + { + xmin = xmax = svi.inv_pos.x; + ymin = ymax = svi.inv_pos.y; + wallct = mkinvk_check_wall(xmin, ymin); + /* this replicates the somewhat convoluted loop below, working + out from the stair position, except for stopping early when + walls are found */ + for (dist = 1; !wallct && dist < 7; ++dist) { + xmin--, xmax++; + /* top and bottom */ + if (dist != 3) { /* the area is wider that it is high */ + ymin--, ymax++; + for (i = xmin + 1; i < xmax; i++) { + if (mkinvk_check_wall(i, ymin)) + ++wallct; /* we could break after finding first wall + * but it isn't a significant optimization + * for code which only executes once */ + if (mkinvk_check_wall(i, ymax)) + ++wallct; + } + } + /* left and right */ + if (!wallct) { /* skip y loop if x loop found any walls */ + for (i = ymin; i <= ymax; i++) { + if (mkinvk_check_wall(xmin, i)) + ++wallct; + if (mkinvk_check_wall(xmax, i)) + ++wallct; + } + } + } + /* message won't appear if the maze 'walls' on this level are lava + or if all the walls within range have been dug away; when it does + appear, it will describe iron bars as "walls" (which is ok) */ + if (wallct) + pline_The("walls around you begin to bend and crumble!"); + } display_nhwindow(WIN_MESSAGE, TRUE); /* any trap hero is stuck in will be going away now */ @@ -1738,6 +2472,9 @@ mkinvokearea() buried_ball_to_punishment(); reset_utrap(FALSE); } + + xmin = xmax = svi.inv_pos.x; /* reset after the check for walls */ + ymin = ymax = svi.inv_pos.y; mkinvpos(xmin, ymin, 0); /* middle, before placing stairs */ for (dist = 1; dist < 7; dist++) { @@ -1761,35 +2498,42 @@ mkinvokearea() } flush_screen(1); /* make sure the new glyphs shows up */ - delay_output(); + nh_delay_output(); } You("are standing at the top of a stairwell leading down!"); - mkstairs(u.ux, u.uy, 0, (struct mkroom *) 0); /* down */ + mkstairs(u.ux, u.uy, 0, (struct mkroom *) 0, FALSE); /* down */ newsym(u.ux, u.uy); - vision_full_recalc = 1; /* everything changed */ + gv.vision_full_recalc = 1; /* everything changed */ } /* Change level topology. Boulders in the vicinity are eliminated. * Temporarily overrides vision in the name of a nice effect. */ -STATIC_OVL void -mkinvpos(x, y, dist) -xchar x, y; -int dist; +staticfn void +mkinvpos(coordxy x, coordxy y, int dist) { struct trap *ttmp; struct obj *otmp; boolean make_rocks; - register struct rm *lev = &levl[x][y]; + struct rm *lev = &levl[x][y]; struct monst *mon; + /* maze levels have slightly different constraints from normal levels; + these are also defined in mkmaze.c and may not be appropriate for + mazes with corridors wider than 1 or for cavernous levels */ +#define x_maze_min 2 +#define y_maze_min 2 /* clip at existing map borders if necessary */ - if (!within_bounded_area(x, y, x_maze_min + 1, y_maze_min + 1, - x_maze_max - 1, y_maze_max - 1)) { + if (!within_bounded_area(x, y, x_maze_min, y_maze_min, + gx.x_maze_max, gy.y_maze_max)) { /* outermost 2 columns and/or rows may be truncated due to edge */ - if (dist < (7 - 2)) - panic("mkinvpos: <%d,%d> (%d) off map edge!", x, y, dist); + if (dist < (7 - 2)) { /* panic() or impossible() */ + void (*errfunc)(const char *, ...) PRINTF_F(1, 2); + + errfunc = !isok(x, y) ? panic : impossible; + (*errfunc)("mkinvpos: <%d,%d> (%d) off map edge!", x, y, dist); + } return; } @@ -1817,7 +2561,7 @@ int dist; lev->waslit = TRUE; lev->horizontal = FALSE; /* short-circuit vision recalc */ - viz_array[y][x] = (dist < 6) ? (IN_SIGHT | COULD_SEE) : COULD_SEE; + gv.viz_array[y][x] = (dist < 6) ? (IN_SIGHT | COULD_SEE) : COULD_SEE; switch (dist) { case 1: /* fire traps */ @@ -1850,16 +2594,33 @@ int dist; seemimic(mon); if ((ttmp = t_at(x, y)) != 0) - (void) mintrap(mon); + (void) mintrap(mon, NO_TRAP_FLAGS); else (void) minliquid(mon); } if (!does_block(x, y, lev)) - unblock_point(x, y); /* make sure vision knows this location is open */ + unblock_point(x, y); /* make sure vision knows location is open */ /* display new value of position; could have a monster/object on it */ newsym(x, y); +#undef x_maze_min +#undef y_maze_min +} + +/* reduces clutter in mkinvokearea() while avoiding potential static analyzer + confusion about using isok(x,y) to control access to levl[x][y] */ +staticfn int +mkinvk_check_wall(coordxy x, coordxy y) +{ + unsigned ltyp; + + if (!isok(x, y)) + return 0; + assert(x > 0 && x < COLNO); + assert(y >= 0 && y < ROWNO); + ltyp = levl[x][y].typ; + return (IS_STWALL(ltyp) || ltyp == IRONBARS) ? 1 : 0; } /* @@ -1870,16 +2631,17 @@ int dist; * * Ludios will remain isolated until the branch is corrected by this function. */ -STATIC_OVL void -mk_knox_portal(x, y) -xchar x, y; +staticfn void +mk_knox_portal(coordxy x, coordxy y) { - extern int n_dgns; /* from dungeon.c */ d_level *source; branch *br; schar u_depth; br = dungeon_branch("Fort Ludios"); + /* dungeon_branch() panics (so never returns) if result would be Null */ + assert(br != NULL); + if (on_level(&knox_level, &br->end1)) { source = &br->end2; } else { @@ -1890,7 +2652,7 @@ xchar x, y; } /* Already set or 2/3 chance of deferring until a later level. */ - if (source->dnum < n_dgns || (rn2(3) && !wizard)) + if (source->dnum < svn.n_dgns || (rn2(3) && !wizard)) return; if (!(u.uz.dnum == oracle_level.dnum /* in main dungeon */ diff --git a/src/mkmap.c b/src/mkmap.c index df2e37122..3ed81f14f 100644 --- a/src/mkmap.c +++ b/src/mkmap.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 mkmap.c $NHDT-Date: 1432512767 2015/05/25 00:12:47 $ $NHDT-Branch: master $:$NHDT-Revision: 1.16 $ */ +/* NetHack 5.0 mkmap.c $NHDT-Date: 1717432093 2024/06/03 16:28:13 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.40 $ */ /* Copyright (c) J. C. Collet, M. Stephenson and D. Cohrs, 1992 */ /* NetHack may be freely redistributed. See license for details. */ @@ -8,77 +8,72 @@ #define HEIGHT (ROWNO - 1) #define WIDTH (COLNO - 2) -STATIC_DCL void FDECL(init_map, (SCHAR_P)); -STATIC_DCL void FDECL(init_fill, (SCHAR_P, SCHAR_P)); -STATIC_DCL schar FDECL(get_map, (int, int, SCHAR_P)); -STATIC_DCL void FDECL(pass_one, (SCHAR_P, SCHAR_P)); -STATIC_DCL void FDECL(pass_two, (SCHAR_P, SCHAR_P)); -STATIC_DCL void FDECL(pass_three, (SCHAR_P, SCHAR_P)); -STATIC_DCL void NDECL(wallify_map); -STATIC_DCL void FDECL(join_map, (SCHAR_P, SCHAR_P)); -STATIC_DCL void FDECL(finish_map, - (SCHAR_P, SCHAR_P, BOOLEAN_P, BOOLEAN_P, BOOLEAN_P)); -STATIC_DCL void FDECL(remove_room, (unsigned)); -void FDECL(mkmap, (lev_init *)); - -static char *new_locations; -int min_rx, max_rx, min_ry, max_ry; /* rectangle bounds for regions */ -static int n_loc_filled; - -STATIC_OVL void -init_map(bg_typ) -schar bg_typ; +staticfn void init_map(schar); +staticfn void init_fill(schar, schar); +staticfn schar get_map(coordxy, coordxy, schar); +staticfn void pass_one(schar, schar); +staticfn void pass_two(schar, schar); +staticfn void pass_three(schar, schar); +staticfn void join_map_cleanup(void); +staticfn void join_map(schar, schar); +staticfn void finish_map(schar, schar, boolean, boolean, boolean); +staticfn void remove_room(unsigned); +void mkmap(lev_init *); + +staticfn void +init_map(schar bg_typ) { - register int i, j; + coordxy x, y; - for (i = 1; i < COLNO; i++) - for (j = 0; j < ROWNO; j++) - levl[i][j].typ = bg_typ; + for (x = 1; x < COLNO; x++) + for (y = 0; y < ROWNO; y++) { + levl[x][y].roomno = NO_ROOM; + levl[x][y].typ = bg_typ; + levl[x][y].lit = FALSE; + } } -STATIC_OVL void -init_fill(bg_typ, fg_typ) -schar bg_typ, fg_typ; +staticfn void +init_fill(schar bg_typ, schar fg_typ) { - register int i, j; + coordxy x, y; long limit, count; limit = (WIDTH * HEIGHT * 2) / 5; count = 0; while (count < limit) { - i = rn1(WIDTH - 1, 2); - j = rnd(HEIGHT - 1); - if (levl[i][j].typ == bg_typ) { - levl[i][j].typ = fg_typ; + x = (coordxy) rn1(WIDTH - 1, 2); + y = (coordxy) rnd(HEIGHT - 1); + if (levl[x][y].typ == bg_typ) { + levl[x][y].typ = fg_typ; count++; } } } -STATIC_OVL schar -get_map(col, row, bg_typ) -int col, row; -schar bg_typ; +staticfn schar +get_map(coordxy col, coordxy row, schar bg_typ) { if (col <= 0 || row < 0 || col > WIDTH || row >= HEIGHT) return bg_typ; return levl[col][row].typ; } -static int dirs[16] = { -1, -1 /**/, -1, 0 /**/, -1, 1 /**/, 0, -1 /**/, - 0, 1 /**/, 1, -1 /**/, 1, 0 /**/, 1, 1 }; +staticfn const int dirs[16] = { + -1, -1 /**/, -1, 0 /**/, -1, 1 /**/, 0, -1 /**/, + 0, 1 /**/, 1, -1 /**/, 1, 0 /**/, 1, 1 +}; -STATIC_OVL void -pass_one(bg_typ, fg_typ) -schar bg_typ, fg_typ; +staticfn void +pass_one(schar bg_typ, schar fg_typ) { - register int i, j; + coordxy x, y; short count, dr; - for (i = 2; i <= WIDTH; i++) - for (j = 1; j < HEIGHT; j++) { + for (x = 2; x <= WIDTH; x++) + for (y = 1; y < HEIGHT; y++) { for (count = 0, dr = 0; dr < 8; dr++) - if (get_map(i + dirs[dr * 2], j + dirs[(dr * 2) + 1], bg_typ) + if (get_map(x + dirs[dr * 2], y + dirs[(dr * 2) + 1], bg_typ) == fg_typ) count++; @@ -86,13 +81,13 @@ schar bg_typ, fg_typ; case 0: /* death */ case 1: case 2: - levl[i][j].typ = bg_typ; + levl[x][y].typ = bg_typ; break; case 5: case 6: case 7: case 8: - levl[i][j].typ = fg_typ; + levl[x][y].typ = fg_typ; break; default: break; @@ -100,54 +95,52 @@ schar bg_typ, fg_typ; } } -#define new_loc(i, j) *(new_locations + ((j) * (WIDTH + 1)) + (i)) +#define new_loc(i, j) *(gn.new_locations + ((j) * (WIDTH + 1)) + (i)) -STATIC_OVL void -pass_two(bg_typ, fg_typ) -schar bg_typ, fg_typ; +staticfn void +pass_two(schar bg_typ, schar fg_typ) { - register int i, j; + coordxy x, y; short count, dr; - for (i = 2; i <= WIDTH; i++) - for (j = 1; j < HEIGHT; j++) { + for (x = 2; x <= WIDTH; x++) + for (y = 1; y < HEIGHT; y++) { for (count = 0, dr = 0; dr < 8; dr++) - if (get_map(i + dirs[dr * 2], j + dirs[(dr * 2) + 1], bg_typ) + if (get_map(x + dirs[dr * 2], y + dirs[(dr * 2) + 1], bg_typ) == fg_typ) count++; if (count == 5) - new_loc(i, j) = bg_typ; + new_loc(x, y) = bg_typ; else - new_loc(i, j) = get_map(i, j, bg_typ); + new_loc(x, y) = get_map(x, y, bg_typ); } - for (i = 2; i <= WIDTH; i++) - for (j = 1; j < HEIGHT; j++) - levl[i][j].typ = new_loc(i, j); + for (x = 2; x <= WIDTH; x++) + for (y = 1; y < HEIGHT; y++) + levl[x][y].typ = new_loc(x, y); } -STATIC_OVL void -pass_three(bg_typ, fg_typ) -schar bg_typ, fg_typ; +staticfn void +pass_three(schar bg_typ, schar fg_typ) { - register int i, j; + coordxy x, y; short count, dr; - for (i = 2; i <= WIDTH; i++) - for (j = 1; j < HEIGHT; j++) { + for (x = 2; x <= WIDTH; x++) + for (y = 1; y < HEIGHT; y++) { for (count = 0, dr = 0; dr < 8; dr++) - if (get_map(i + dirs[dr * 2], j + dirs[(dr * 2) + 1], bg_typ) + if (get_map(x + dirs[dr * 2], y + dirs[(dr * 2) + 1], bg_typ) == fg_typ) count++; if (count < 3) - new_loc(i, j) = bg_typ; + new_loc(x, y) = bg_typ; else - new_loc(i, j) = get_map(i, j, bg_typ); + new_loc(x, y) = get_map(x, y, bg_typ); } - for (i = 2; i <= WIDTH; i++) - for (j = 1; j < HEIGHT; j++) - levl[i][j].typ = new_loc(i, j); + for (x = 2; x <= WIDTH; x++) + for (y = 1; y < HEIGHT; y++) + levl[x][y].typ = new_loc(x, y); } /* @@ -157,15 +150,14 @@ schar bg_typ, fg_typ; * exactly matching levl[sx][sy].typ and walls are included as well. */ void -flood_fill_rm(sx, sy, rmno, lit, anyroom) -int sx; -register int sy; -register int rmno; -boolean lit; -boolean anyroom; +flood_fill_rm( + coordxy sx, + coordxy sy, + int rmno, + boolean lit, + boolean anyroom) { - register int i; - int nx; + coordxy i, nx; schar fg_typ = levl[sx][sy].typ; /* back up to find leftmost uninitialized location */ @@ -176,17 +168,17 @@ boolean anyroom; sx++; /* compensate for extra decrement */ /* assume sx,sy is valid */ - if (sx < min_rx) - min_rx = sx; - if (sy < min_ry) - min_ry = sy; + if (sx < gm.min_rx) + gm.min_rx = sx; + if (sy < gm.min_ry) + gm.min_ry = sy; for (i = sx; i <= WIDTH && levl[i][sy].typ == fg_typ; i++) { levl[i][sy].roomno = rmno; levl[i][sy].lit = lit; if (anyroom) { /* add walls to room as well */ - register int ii, jj; + coordxy ii, jj; for (ii = (i == sx ? i - 1 : i); ii <= i + 1; ii++) for (jj = sy - 1; jj <= sy + 1; jj++) if (isok(ii, jj) && (IS_WALL(levl[ii][jj].typ) @@ -195,11 +187,14 @@ boolean anyroom; levl[ii][jj].edge = 1; if (lit) levl[ii][jj].lit = lit; - if ((int) levl[ii][jj].roomno != rmno) + + if (levl[ii][jj].roomno == NO_ROOM) + levl[ii][jj].roomno = rmno; + else if ((int) levl[ii][jj].roomno != rmno) levl[ii][jj].roomno = SHARED; } } - n_loc_filled++; + gn.n_loc_filled++; } nx = i; @@ -240,69 +235,57 @@ boolean anyroom; } } - if (nx > max_rx) - max_rx = nx - 1; /* nx is just past valid region */ - if (sy > max_ry) - max_ry = sy; + if (nx > gm.max_rx) + gm.max_rx = nx - 1; /* nx is just past valid region */ + if (sy > gm.max_ry) + gm.max_ry = sy; } -/* - * If we have drawn a map without walls, this allows us to - * auto-magically wallify it. Taken from lev_main.c. - */ -STATIC_OVL void -wallify_map() +/* join_map uses temporary rooms; clean up after it */ +staticfn void +join_map_cleanup(void) { - int x, y, xx, yy; + coordxy x, y; for (x = 1; x < COLNO; x++) for (y = 0; y < ROWNO; y++) - if (levl[x][y].typ == STONE) { - for (yy = y - 1; yy <= y + 1; yy++) - for (xx = x - 1; xx <= x + 1; xx++) - if (isok(xx, yy) && levl[xx][yy].typ == ROOM) { - if (yy != y) - levl[x][y].typ = HWALL; - else - levl[x][y].typ = VWALL; - } - } + levl[x][y].roomno = NO_ROOM; + svn.nroom = gn.nsubroom = 0; + svr.rooms[svn.nroom].hx = gs.subrooms[gn.nsubroom].hx = -1; } -STATIC_OVL void -join_map(bg_typ, fg_typ) -schar bg_typ, fg_typ; +staticfn void +join_map(schar bg_typ, schar fg_typ) { - register struct mkroom *croom, *croom2; + struct mkroom *croom, *croom2; - register int i, j; - int sx, sy; + coordxy x, y, sx, sy; coord sm, em; /* first, use flood filling to find all of the regions that need joining */ - for (i = 2; i <= WIDTH; i++) - for (j = 1; j < HEIGHT; j++) { - if (levl[i][j].typ == fg_typ && levl[i][j].roomno == NO_ROOM) { - min_rx = max_rx = i; - min_ry = max_ry = j; - n_loc_filled = 0; - flood_fill_rm(i, j, nroom + ROOMOFFSET, FALSE, FALSE); - if (n_loc_filled > 3) { - add_room(min_rx, min_ry, max_rx, max_ry, FALSE, OROOM, - TRUE); - rooms[nroom - 1].irregular = TRUE; - if (nroom >= (MAXNROFROOMS * 2)) + for (x = 2; x <= WIDTH; x++) + for (y = 1; y < HEIGHT; y++) { + if (levl[x][y].typ == fg_typ && levl[x][y].roomno == NO_ROOM) { + gm.min_rx = gm.max_rx = x; + gm.min_ry = gm.max_ry = y; + gn.n_loc_filled = 0; + flood_fill_rm(x, y, svn.nroom + ROOMOFFSET, FALSE, FALSE); + if (gn.n_loc_filled > 3) { + add_room(gm.min_rx, gm.min_ry, gm.max_rx, gm.max_ry, + FALSE, OROOM, TRUE); + svr.rooms[svn.nroom - 1].irregular = TRUE; + if (svn.nroom >= (MAXNROFROOMS * 2)) goto joinm; } else { /* * it's a tiny hole; erase it from the map to avoid * having the player end up here with no way out. */ - for (sx = min_rx; sx <= max_rx; sx++) - for (sy = min_ry; sy <= max_ry; sy++) + for (sx = gm.min_rx; sx <= gm.max_rx; sx++) + for (sy = gm.min_ry; sy <= gm.max_ry; sy++) if ((int) levl[sx][sy].roomno - == nroom + ROOMOFFSET) { + == svn.nroom + ROOMOFFSET) { levl[sx][sy].typ = bg_typ; levl[sx][sy].roomno = NO_ROOM; } @@ -310,14 +293,15 @@ schar bg_typ, fg_typ; } } -joinm: + joinm: /* * Ok, now we can actually join the regions with fg_typ's. * The rooms are already sorted due to the previous loop, * so don't call sort_rooms(), which can screw up the roomno's * validity in the levl structure. */ - for (croom = &rooms[0], croom2 = croom + 1; croom2 < &rooms[nroom];) { + for (croom = &svr.rooms[0], croom2 = croom + 1; + croom2 < &svr.rooms[svn.nroom]; ) { /* pick random starting and end locations for "corridor" */ if (!somexy(croom, &sm) || !somexy(croom2, &em)) { /* ack! -- the level is going to be busted */ @@ -329,7 +313,7 @@ schar bg_typ, fg_typ; em.y = croom2->ly + ((croom2->hy - croom2->ly) / 2); } - (void) dig_corridor(&sm, &em, FALSE, fg_typ, bg_typ); + (void) dig_corridor(&sm, &em, NULL, FALSE, fg_typ, bg_typ); /* choose next region to join */ /* only increment croom if croom and croom2 are non-overlapping */ @@ -340,41 +324,49 @@ schar bg_typ, fg_typ; } croom2++; /* always increment the next room */ } + join_map_cleanup(); } -STATIC_OVL void -finish_map(fg_typ, bg_typ, lit, walled, icedpools) -schar fg_typ, bg_typ; -boolean lit, walled, icedpools; +staticfn void +finish_map( + schar fg_typ, + schar bg_typ, + boolean lit, + boolean walled, + boolean icedpools) { - int i, j; + coordxy x, y; if (walled) - wallify_map(); + wallify_map(1, 0, COLNO-1, ROWNO-1); if (lit) { - for (i = 1; i < COLNO; i++) - for (j = 0; j < ROWNO; j++) - if ((!IS_ROCK(fg_typ) && levl[i][j].typ == fg_typ) - || (!IS_ROCK(bg_typ) && levl[i][j].typ == bg_typ) - || (bg_typ == TREE && levl[i][j].typ == bg_typ) - || (walled && IS_WALL(levl[i][j].typ))) - levl[i][j].lit = TRUE; - for (i = 0; i < nroom; i++) - rooms[i].rlit = 1; + for (x = 1; x < COLNO; x++) + for (y = 0; y < ROWNO; y++) + if ((!IS_OBSTRUCTED(fg_typ) && levl[x][y].typ == fg_typ) + || (!IS_OBSTRUCTED(bg_typ) && levl[x][y].typ == bg_typ) + || (bg_typ == TREE && levl[x][y].typ == bg_typ) + || (walled && IS_WALL(levl[x][y].typ))) + levl[x][y].lit = TRUE; + for (x = 0; x < svn.nroom; x++) + svr.rooms[x].rlit = 1; } /* light lava even if everything's otherwise unlit; ice might be frozen pool rather than frozen moat */ - for (i = 1; i < COLNO; i++) - for (j = 0; j < ROWNO; j++) { - if (levl[i][j].typ == LAVAPOOL) - levl[i][j].lit = TRUE; - else if (levl[i][j].typ == ICE) - levl[i][j].icedpool = icedpools ? ICED_POOL : ICED_MOAT; + for (x = 1; x < COLNO; x++) + for (y = 0; y < ROWNO; y++) { + if (levl[x][y].typ == LAVAPOOL) + levl[x][y].lit = TRUE; + else if (levl[x][y].typ == ICE) + levl[x][y].icedpool = icedpools ? ICED_POOL : ICED_MOAT; } } /* + * TODO: If we really want to remove rooms after a map is plopped down + * in a special level, this needs to be rewritten - the maps may have + * holes in them ("x" mapchar), leaving parts of rooms still on the map. + * * When level processed by join_map is overlaid by a MAP, some rooms may no * longer be valid. All rooms in the region lx <= x < hx, ly <= y < hy are * removed. Rooms partially in the region are truncated. This function @@ -384,14 +376,13 @@ boolean lit, walled, icedpools; * region are all set. */ void -remove_rooms(lx, ly, hx, hy) -int lx, ly, hx, hy; +remove_rooms(coordxy lx, coordxy ly, coordxy hx, coordxy hy) { int i; struct mkroom *croom; - for (i = nroom - 1; i >= 0; --i) { - croom = &rooms[i]; + for (i = svn.nroom - 1; i >= 0; --i) { + croom = &svr.rooms[i]; if (croom->hx < lx || croom->lx >= hx || croom->hy < ly || croom->ly >= hy) continue; /* no overlap */ @@ -410,34 +401,34 @@ int lx, ly, hx, hy; } /* - * Remove roomno from the rooms array, decrementing nroom. Also updates - * all level roomno values of affected higher numbered rooms. Assumes - * level structure contents corresponding to roomno have already been reset. + * Remove roomno from the rooms array, decrementing nroom. + * The last room is swapped with the being-removed room and locations + * within it have their roomno field updated. Other rooms are unaffected. + * Assumes level structure contents corresponding to roomno have already + * been reset. * Currently handles only the removal of rooms that have no subrooms. */ -STATIC_OVL void -remove_room(roomno) -unsigned roomno; +staticfn void +remove_room(unsigned int roomno) { - struct mkroom *croom = &rooms[roomno]; - struct mkroom *maxroom = &rooms[--nroom]; - int i, j; + struct mkroom *croom = &svr.rooms[roomno]; + struct mkroom *maxroom = &svr.rooms[--svn.nroom]; + coordxy x, y; unsigned oroomno; if (croom != maxroom) { /* since the order in the array only matters for making corridors, * copy the last room over the one being removed on the assumption * that corridors have already been dug. */ - (void) memcpy((genericptr_t) croom, (genericptr_t) maxroom, - sizeof(struct mkroom)); + *croom = *maxroom; /* since maxroom moved, update affected level roomno values */ - oroomno = nroom + ROOMOFFSET; + oroomno = svn.nroom + ROOMOFFSET; roomno += ROOMOFFSET; - for (i = croom->lx; i <= croom->hx; ++i) - for (j = croom->ly; j <= croom->hy; ++j) { - if (levl[i][j].roomno == oroomno) - levl[i][j].roomno = roomno; + for (x = croom->lx; x <= croom->hx; ++x) + for (y = croom->ly; y <= croom->hy; ++y) { + if (levl[x][y].roomno == oroomno) + levl[x][y].roomno = roomno; } } @@ -448,19 +439,25 @@ unsigned roomno; #define N_P2_ITER 1 /* tune map generation via this value */ #define N_P3_ITER 2 /* tune map smoothing via this value */ +boolean +litstate_rnd(int litstate) +{ + if (litstate < 0) + return (rnd(1 + abs(depth(&u.uz))) < 11 && rn2(77)) ? TRUE : FALSE; + return (boolean) litstate; +} + void -mkmap(init_lev) -lev_init *init_lev; +mkmap(lev_init *init_lev) { schar bg_typ = init_lev->bg, fg_typ = init_lev->fg; boolean smooth = init_lev->smoothed, join = init_lev->joined; - xchar lit = init_lev->lit, walled = init_lev->walled; + xint16 lit = init_lev->lit, walled = init_lev->walled; int i; - if (lit < 0) - lit = (rnd(1 + abs(depth(&u.uz))) < 11 && rn2(77)) ? 1 : 0; + lit = litstate_rnd(lit); - new_locations = (char *) alloc((WIDTH + 1) * HEIGHT); + gn.new_locations = (char *) alloc((WIDTH + 1) * HEIGHT); init_map(bg_typ); init_fill(bg_typ, fg_typ); @@ -482,10 +479,10 @@ lev_init *init_lev; init_lev->icedpools); /* a walled, joined level is cavernous, not mazelike -dlc */ if (walled && join) { - level.flags.is_maze_lev = FALSE; - level.flags.is_cavernous_lev = TRUE; + svl.level.flags.is_maze_lev = FALSE; + svl.level.flags.is_cavernous_lev = TRUE; } - free(new_locations); + free(gn.new_locations); } /*mkmap.c*/ diff --git a/src/mkmaze.c b/src/mkmaze.c index e18a91e4a..39642b2db 100644 --- a/src/mkmaze.c +++ b/src/mkmaze.c @@ -1,34 +1,32 @@ -/* NetHack 3.6 mkmaze.c $NHDT-Date: 1559422240 2019/06/01 20:50:40 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.74 $ */ +/* NetHack 5.0 mkmaze.c $NHDT-Date: 1745114235 2025/04/19 17:57:15 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.179 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Pasi Kallinen, 2018. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" #include "sp_lev.h" -#include "lev.h" /* save & restore info */ - -/* from sp_lev.c, for fixup_special() */ -extern lev_region *lregions; -extern int num_lregions; -/* for preserving the insect legs when wallifying baalz level */ -static lev_region bughack = { {COLNO, ROWNO, 0, 0}, {COLNO, ROWNO, 0, 0} }; - -STATIC_DCL int FDECL(iswall, (int, int)); -STATIC_DCL int FDECL(iswall_or_stone, (int, int)); -STATIC_DCL boolean FDECL(is_solid, (int, int)); -STATIC_DCL int FDECL(extend_spine, (int[3][3], int, int, int)); -STATIC_DCL boolean FDECL(okay, (int, int, int)); -STATIC_DCL void FDECL(maze0xy, (coord *)); -STATIC_DCL boolean FDECL(put_lregion_here, (XCHAR_P, XCHAR_P, XCHAR_P, - XCHAR_P, XCHAR_P, XCHAR_P, - XCHAR_P, BOOLEAN_P, d_level *)); -STATIC_DCL void NDECL(baalz_fixup); -STATIC_DCL void NDECL(setup_waterlevel); -STATIC_DCL void NDECL(unsetup_waterlevel); -STATIC_DCL void FDECL(check_ransacked, (char *)); -STATIC_DCL void FDECL(migr_booty_item, (int, const char *)); -STATIC_DCL void FDECL(migrate_orc, (struct monst *, unsigned long)); -STATIC_DCL void NDECL(stolen_booty); + +#ifndef SFCTOOL +staticfn int iswall(coordxy, coordxy); +staticfn int iswall_or_stone(coordxy, coordxy); +staticfn boolean is_solid(coordxy, coordxy); +staticfn int extend_spine(int[3][3], int, int, int); +staticfn void wall_cleanup(coordxy, coordxy, coordxy, coordxy); +staticfn boolean okay(coordxy, coordxy, coordxy); +staticfn void maze0xy(coord *); +staticfn boolean put_lregion_here(coordxy, coordxy, coordxy, coordxy, coordxy, + coordxy, xint16, boolean, d_level *); +staticfn void baalz_fixup(void); +staticfn void setup_waterlevel(void); +staticfn void unsetup_waterlevel(void); +staticfn void check_ransacked(const char *); +staticfn void migr_booty_item(int, const char *); +staticfn void migrate_orc(struct monst *, unsigned long); +staticfn void shiny_orc_stuff(struct monst *); +staticfn void stolen_booty(void); +staticfn boolean maze_inbounds(coordxy, coordxy); +staticfn void maze_remove_deadends(xint16); +staticfn void populate_maze(void); /* adjust a coordinate one step in the specified direction */ #define mz_move(X, Y, dir) \ @@ -42,9 +40,9 @@ STATIC_DCL void NDECL(stolen_booty); } \ } while (0) -STATIC_OVL int -iswall(x, y) -int x, y; +/* used to determine if wall spines can join this location */ +staticfn int +iswall(coordxy x, coordxy y) { int type; @@ -52,12 +50,13 @@ int x, y; return 0; type = levl[x][y].typ; return (IS_WALL(type) || IS_DOOR(type) + || type == LAVAWALL || type == WATER || type == SDOOR || type == IRONBARS); } -STATIC_OVL int -iswall_or_stone(x, y) -int x, y; +/* used to determine if wall spines can join this location */ +staticfn int +iswall_or_stone(coordxy x, coordxy y) { /* out of bounds = stone */ if (!isok(x, y)) @@ -67,13 +66,84 @@ int x, y; } /* return TRUE if out of bounds, wall or rock */ -STATIC_OVL boolean -is_solid(x, y) -int x, y; +staticfn boolean +is_solid(coordxy x, coordxy y) { return (boolean) (!isok(x, y) || IS_STWALL(levl[x][y].typ)); } +/* set map terrain type, handling lava lit, ice melt timers, etc */ +boolean +set_levltyp(coordxy x, coordxy y, schar newtyp) +{ + if (isok(x, y) && newtyp >= STONE && newtyp < MAX_TYPE) { + schar oldtyp = levl[x][y].typ; + + /* hack for secret doors in garden theme rooms */ + if (oldtyp == SDOOR && newtyp == AIR) { + /* levl[][].typ stays SDOOR rather than change to AIR */ + levl[x][y].arboreal_sdoor = 1; + return TRUE; + } + + if (CAN_OVERWRITE_TERRAIN(oldtyp)) { + /* typ==ICE || (typ==DRAWBRIDGE_UP && drawbridgemask==DB_ICE) */ + boolean was_ice = is_ice(x, y); + + levl[x][y].typ = newtyp; + /* TODO? + * if oldtyp used flags or horizontal differently from + * the way newtyp will use them, clear them. + */ + + if (IS_LAVA(newtyp)) /* [what about IS_LAVA(oldtyp)=>.lit = 0?] */ + levl[x][y].lit = 1; + if (was_ice && newtyp != ICE) { + /* frozen corpses resume rotting, no more ice to melt away */ + obj_ice_effects(x, y, TRUE); + spot_stop_timers(x, y, MELT_ICE_AWAY); + } + if ((IS_FOUNTAIN(oldtyp) != IS_FOUNTAIN(newtyp)) + || (IS_SINK(oldtyp) != IS_SINK(newtyp))) + count_level_features(); /* level.flags.nfountains,nsinks */ + + return TRUE; + } +#ifdef EXTRA_SANITY_CHECKS + } else { + impossible("set_levltyp(%d,%d,%d)%s%s", + (int) x, (int) y, (int) newtyp, + !isok(x, y) ? " not isok()" : "", + (newtyp < STONE || newtyp >= MAX_TYPE) ? " bad type" : ""); +#endif /*EXTRA_SANITY_CHECKS*/ + } + return FALSE; +} + +/* set map terrain type and light state */ +boolean +set_levltyp_lit(coordxy x, coordxy y, schar typ, schar lit) +{ + boolean ret = set_levltyp(x, y, typ); + + if (ret && isok(x, y)) { + if (lit != SET_LIT_NOCHANGE) { +#ifdef EXTRA_SANITY_CHECKS + if (lit < SET_LIT_NOCHANGE || lit > 1) + impossible("set_levltyp_lit(%d,%d,%d,%d)", + (int) x, (int) y, (int) typ, (int) lit); +#endif /*EXTRA_SANITY_CHECKS*/ + if (IS_LAVA(typ)) + lit = 1; + else if (lit == SET_LIT_RANDOM) + lit = rn2(2); + + levl[x][y].lit = lit; + } + } + return ret; +} + /* * Return 1 (not TRUE - we're doing bit vectors here) if we want to extend * a wall spine in the (dx,dy) direction. Return 0 otherwise. @@ -92,10 +162,8 @@ int x, y; * W x W This would extend a spine from x down. * . W W */ -STATIC_OVL int -extend_spine(locale, wall_there, dx, dy) -int locale[3][3]; -int wall_there, dx, dy; +staticfn int +extend_spine(int locale[3][3], int wall_there, int dx, int dy) { int spine, nx, ny; @@ -126,12 +194,11 @@ int wall_there, dx, dy; } /* Remove walls totally surrounded by stone */ -void -wall_cleanup(x1, y1, x2, y2) -int x1, y1, x2, y2; +staticfn void +wall_cleanup(coordxy x1, coordxy y1, coordxy x2, coordxy y2) { uchar type; - int x, y; + coordxy x, y; struct rm *lev; /* sanity check on incoming variables */ @@ -142,8 +209,8 @@ int x1, y1, x2, y2; for (x = x1; x <= x2; x++) for (y = y1; y <= y2; y++) { if (within_bounded_area(x, y, - bughack.inarea.x1, bughack.inarea.y1, - bughack.inarea.x2, bughack.inarea.y2)) + gb.bughack.inarea.x1, gb.bughack.inarea.y1, + gb.bughack.inarea.x2, gb.bughack.inarea.y2)) continue; lev = &levl[x][y]; type = lev->typ; @@ -159,13 +226,12 @@ int x1, y1, x2, y2; /* Correct wall types so they extend and connect to each other */ void -fix_wall_spines(x1, y1, x2, y2) -int x1, y1, x2, y2; +fix_wall_spines(coordxy x1, coordxy y1, coordxy x2, coordxy y2) { uchar type; - int x, y; + coordxy x, y; struct rm *lev; - int FDECL((*loc_f), (int, int)); + int (*loc_f)(coordxy, coordxy); int bits; int locale[3][3]; /* rock or wall status surrounding positions */ @@ -174,7 +240,7 @@ int x1, y1, x2, y2; * so even though this table says VWALL, we actually leave whatever * typ was there alone. */ - static xchar spine_array[16] = { VWALL, HWALL, HWALL, HWALL, + static xint16 spine_array[16] = { VWALL, HWALL, HWALL, HWALL, VWALL, TRCORNER, TLCORNER, TDWALL, VWALL, BRCORNER, BLCORNER, TUWALL, VWALL, TLWALL, TRWALL, CROSSWALL }; @@ -193,8 +259,8 @@ int x1, y1, x2, y2; /* set the locations TRUE if rock or wall or out of bounds */ loc_f = within_bounded_area(x, y, /* for baalz insect */ - bughack.inarea.x1, bughack.inarea.y1, - bughack.inarea.x2, bughack.inarea.y2) + gb.bughack.inarea.x1, gb.bughack.inarea.y1, + gb.bughack.inarea.x2, gb.bughack.inarea.y2) ? iswall : iswall_or_stone; locale[0][0] = (*loc_f)(x - 1, y - 1); @@ -221,50 +287,65 @@ int x1, y1, x2, y2; } void -wallification(x1, y1, x2, y2) -int x1, y1, x2, y2; +wallification(coordxy x1, coordxy y1, coordxy x2, coordxy y2) { wall_cleanup(x1, y1, x2, y2); fix_wall_spines(x1, y1, x2, y2); } -STATIC_OVL boolean -okay(x, y, dir) -int x, y; -int dir; +staticfn boolean +okay(coordxy x, coordxy y, coordxy dir) { mz_move(x, y, dir); mz_move(x, y, dir); - if (x < 3 || y < 3 || x > x_maze_max || y > y_maze_max + if (x < 3 || y < 3 || x > gx.x_maze_max || y > gy.y_maze_max || levl[x][y].typ != STONE) return FALSE; return TRUE; } /* find random starting point for maze generation */ -STATIC_OVL void -maze0xy(cc) -coord *cc; +staticfn void +maze0xy(coord *cc) { - cc->x = 3 + 2 * rn2((x_maze_max >> 1) - 1); - cc->y = 3 + 2 * rn2((y_maze_max >> 1) - 1); + cc->x = 3 + 2 * rn2((gx.x_maze_max >> 1) - 1); + cc->y = 3 + 2 * rn2((gy.y_maze_max >> 1) - 1); return; } +boolean +is_exclusion_zone(xint16 type, coordxy x, coordxy y) +{ + struct exclusion_zone *ez = sve.exclusion_zones; + + while (ez) { + if (((type == LR_DOWNTELE + && (ez->zonetype == LR_DOWNTELE || ez->zonetype == LR_TELE)) + || (type == LR_UPTELE + && (ez->zonetype == LR_UPTELE || ez->zonetype == LR_TELE)) + || type == ez->zonetype) + && within_bounded_area(x, y, ez->lx, ez->ly, ez->hx, ez->hy)) + return TRUE; + ez = ez->next; + } + return FALSE; +} + /* * Bad if: * pos is occupied OR - * pos is inside restricted region (lx,ly,hx,hy) OR + * pos is inside restricted region (nlx,nly,nhx,nhy) OR * NOT (pos is corridor and a maze level OR pos is a room OR pos is air) */ boolean -bad_location(x, y, lx, ly, hx, hy) -xchar x, y; -xchar lx, ly, hx, hy; +bad_location( + coordxy x, coordxy y, + coordxy nlx, coordxy nly, coordxy nhx, coordxy nhy) { return (boolean) (occupied(x, y) - || within_bounded_area(x, y, lx, ly, hx, hy) - || !((levl[x][y].typ == CORR && level.flags.is_maze_lev) + || within_bounded_area(x, y, nlx, nly, nhx, nhy) + || !((levl[x][y].typ == CORR + && svl.level.flags.is_maze_lev) || levl[x][y].typ == ROOM || levl[x][y].typ == AIR)); } @@ -272,22 +353,22 @@ xchar lx, ly, hx, hy; /* pick a location in area (lx, ly, hx, hy) but not in (nlx, nly, nhx, nhy) and place something (based on rtype) in that region */ void -place_lregion(lx, ly, hx, hy, nlx, nly, nhx, nhy, rtype, lev) -xchar lx, ly, hx, hy; -xchar nlx, nly, nhx, nhy; -xchar rtype; -d_level *lev; +place_lregion( + coordxy lx, coordxy ly, coordxy hx, coordxy hy, + coordxy nlx, coordxy nly, coordxy nhx, coordxy nhy, + xint16 rtype, + d_level *lev) { int trycnt; boolean oneshot; - xchar x, y; + coordxy x, y; if (!lx) { /* default to whole level */ /* * if there are rooms and this a branch, let place_branch choose * the branch location (to avoid putting branches in corridors). */ - if (rtype == LR_BRANCH && nroom) { + if (rtype == LR_BRANCH && svn.nroom) { place_branch(Is_branchlev(&u.uz), 0, 0); return; } @@ -298,6 +379,16 @@ d_level *lev; hy = ROWNO - 1; } + /* clamp the area to the map */ + if (lx < 1) + lx = 1; + if (hx > COLNO - 1) + hx = COLNO - 1; + if (ly < 0) + ly = 0; + if (hy > ROWNO - 1) + hy = ROWNO - 1; + /* first a probabilistic approach */ oneshot = (lx == hx && ly == hy); @@ -318,17 +409,18 @@ d_level *lev; impossible("Couldn't place lregion type %d!", rtype); } -STATIC_OVL boolean -put_lregion_here(x, y, nlx, nly, nhx, nhy, rtype, oneshot, lev) -xchar x, y; -xchar nlx, nly, nhx, nhy; -xchar rtype; -boolean oneshot; -d_level *lev; +staticfn boolean +put_lregion_here( + coordxy x, coordxy y, + coordxy nlx, coordxy nly, coordxy nhx, coordxy nhy, + xint16 rtype, + boolean oneshot, + d_level *lev) { struct monst *mtmp; - if (bad_location(x, y, nlx, nly, nhx, nhy)) { + if (bad_location(x, y, nlx, nly, nhx, nhy) + || is_exclusion_zone(rtype, x, y)) { if (!oneshot) { return FALSE; /* caller should try again */ } else { @@ -337,9 +429,13 @@ d_level *lev; It might still fail if there's a dungeon feature here. */ struct trap *t = t_at(x, y); - if (t && t->ttyp != MAGIC_PORTAL && t->ttyp != VIBRATING_SQUARE) + if (t && !undestroyable_trap(t->ttyp)) { + if (((mtmp = m_at(x, y)) != 0) && mtmp->mtrapped) + mtmp->mtrapped = 0; deltrap(t); - if (bad_location(x, y, nlx, nly, nhx, nhy)) + } + if (bad_location(x, y, nlx, nly, nhx, nhy) + || is_exclusion_zone(rtype, x, y)) return FALSE; } } @@ -351,7 +447,7 @@ d_level *lev; if ((mtmp = m_at(x, y)) != 0) { /* move the monster if no choice, or just try again */ if (oneshot) { - if (!rloc(mtmp, TRUE)) + if (!rloc(mtmp, RLOC_NOMSG)) m_into_limbo(mtmp); } else return FALSE; @@ -363,7 +459,7 @@ d_level *lev; break; case LR_DOWNSTAIR: case LR_UPSTAIR: - mkstairs(x, y, (char) rtype, (struct mkroom *) 0); + mkstairs(x, y, (char) rtype, (struct mkroom *) 0, FALSE); break; case LR_BRANCH: place_branch(Is_branchlev(&u.uz), x, y); @@ -375,8 +471,8 @@ d_level *lev; /* fix up Baalzebub's lair, which depicts a level-sized beetle; its legs are walls within solid rock--regular wallification classifies them as superfluous and gets rid of them */ -STATIC_OVL void -baalz_fixup() +staticfn void +baalz_fixup(void) { struct monst *mtmp; int x, y, lastx, lasty; @@ -397,86 +493,96 @@ baalz_fixup() for (lastx = x = 0; x < COLNO; ++x) if ((levl[x][y].wall_info & W_NONDIGGABLE) != 0) { if (!lastx) - bughack.inarea.x1 = x + 1; + gb.bughack.inarea.x1 = x + 1; lastx = x; } - bughack.inarea.x2 = ((lastx > bughack.inarea.x1) ? lastx : x) - 1; + gb.bughack.inarea.x2 = ((lastx > gb.bughack.inarea.x1) ? lastx : x) - 1; /* find low and high y for to-be-wallified portion of level */ - x = bughack.inarea.x1; + x = gb.bughack.inarea.x1; for (lasty = y = 0; y < ROWNO; ++y) if ((levl[x][y].wall_info & W_NONDIGGABLE) != 0) { if (!lasty) - bughack.inarea.y1 = y + 1; + gb.bughack.inarea.y1 = y + 1; lasty = y; } - bughack.inarea.y2 = ((lasty > bughack.inarea.y1) ? lasty : y) - 1; + gb.bughack.inarea.y2 = ((lasty > gb.bughack.inarea.y1) ? lasty : y) - 1; /* two pools mark where special post-wallify fix-ups are needed */ - for (x = bughack.inarea.x1; x <= bughack.inarea.x2; ++x) - for (y = bughack.inarea.y1; y <= bughack.inarea.y2; ++y) + for (x = gb.bughack.inarea.x1; x <= gb.bughack.inarea.x2; ++x) + for (y = gb.bughack.inarea.y1; y <= gb.bughack.inarea.y2; ++y) if (levl[x][y].typ == POOL) { levl[x][y].typ = HWALL; - if (bughack.delarea.x1 == COLNO) - bughack.delarea.x1 = x, bughack.delarea.y1 = y; + if (gb.bughack.delarea.x1 == COLNO) + gb.bughack.delarea.x1 = x, gb.bughack.delarea.y1 = y; else - bughack.delarea.x2 = x, bughack.delarea.y2 = y; + gb.bughack.delarea.x2 = x, gb.bughack.delarea.y2 = y; } else if (levl[x][y].typ == IRONBARS) { /* novelty effect; allowing digging in front of 'eyes' */ - levl[x - 1][y].wall_info &= ~W_NONDIGGABLE; - if (isok(x - 2, y)) - levl[x - 2][y].wall_info &= ~W_NONDIGGABLE; + if (isok(x - 1, y) + && (levl[x - 1][y].wall_info & W_NONDIGGABLE) != 0) { + levl[x - 1][y].wall_info &= ~W_NONDIGGABLE; + if (isok(x - 2, y)) + levl[x - 2][y].wall_info &= ~W_NONDIGGABLE; + } else if (isok(x + 1, y) + && (levl[x + 1][y].wall_info & W_NONDIGGABLE) != 0) { + levl[x + 1][y].wall_info &= ~W_NONDIGGABLE; + if (isok(x + 2, y)) + levl[x + 2][y].wall_info &= ~W_NONDIGGABLE; + } } - wallification(max(bughack.inarea.x1 - 2, 1), - max(bughack.inarea.y1 - 2, 0), - min(bughack.inarea.x2 + 2, COLNO - 1), - min(bughack.inarea.y2 + 2, ROWNO - 1)); + wallification(max(gb.bughack.inarea.x1 - 2, 1), + max(gb.bughack.inarea.y1 - 2, 0), + min(gb.bughack.inarea.x2 + 2, COLNO - 1), + min(gb.bughack.inarea.y2 + 2, ROWNO - 1)); /* bughack hack for rear-most legs on baalz level; first joint on both top and bottom gets a bogus extra connection to room area, producing unwanted rectangles; change back to separated legs */ - x = bughack.delarea.x1, y = bughack.delarea.y1; - if (isok(x, y) && levl[x][y].typ == TLWALL + x = gb.bughack.delarea.x1, y = gb.bughack.delarea.y1; + if (isok(x, y) && (levl[x][y].typ == TLWALL || levl[x][y].typ == TRWALL) && isok(x, y + 1) && levl[x][y + 1].typ == TUWALL) { - levl[x][y].typ = BRCORNER; + levl[x][y].typ = (levl[x][y].typ == TLWALL) ? BRCORNER : BLCORNER; levl[x][y + 1].typ = HWALL; if ((mtmp = m_at(x, y)) != 0) /* something at temporary pool... */ - (void) rloc(mtmp, FALSE); + (void) rloc(mtmp, RLOC_ERR|RLOC_NOMSG); } - x = bughack.delarea.x2, y = bughack.delarea.y2; - if (isok(x, y) && levl[x][y].typ == TLWALL + + x = gb.bughack.delarea.x2, y = gb.bughack.delarea.y2; + if (isok(x, y) && (levl[x][y].typ == TLWALL || levl[x][y].typ == TRWALL) && isok(x, y - 1) && levl[x][y - 1].typ == TDWALL) { - levl[x][y].typ = TRCORNER; + levl[x][y].typ = (levl[x][y].typ == TLWALL) ? TRCORNER : TLCORNER; levl[x][y - 1].typ = HWALL; if ((mtmp = m_at(x, y)) != 0) /* something at temporary pool... */ - (void) rloc(mtmp, FALSE); + (void) rloc(mtmp, RLOC_ERR|RLOC_NOMSG); } /* reset bughack region; set low end to so that within_bounded_region() in fix_wall_spines() will fail most quickly--on its first test--when loading other levels */ - bughack.inarea.x1 = bughack.delarea.x1 = COLNO; - bughack.inarea.y1 = bughack.delarea.y1 = ROWNO; - bughack.inarea.x2 = bughack.delarea.x2 = 0; - bughack.inarea.y2 = bughack.delarea.y2 = 0; + gb.bughack.inarea.x1 = gb.bughack.delarea.x1 = COLNO; + gb.bughack.inarea.y1 = gb.bughack.delarea.y1 = ROWNO; + gb.bughack.inarea.x2 = gb.bughack.delarea.x2 = 0; + gb.bughack.inarea.y2 = gb.bughack.delarea.y2 = 0; } /* this is special stuff that the level compiler cannot (yet) handle */ void -fixup_special() +fixup_special(void) { - lev_region *r = lregions; + lev_region *r = gl.lregions; + s_level *sp; struct d_level lev; int x, y; struct mkroom *croom; boolean added_branch = FALSE; if (Is_waterlevel(&u.uz) || Is_airlevel(&u.uz)) { - level.flags.hero_memory = 0; + svl.level.flags.hero_memory = 0; /* water level is an odd beast - it has to be set up before calling place_lregions etc. */ setup_waterlevel(); } - for (x = 0; x < num_lregions; x++, r++) { + for (x = 0; x < gn.num_lregions; x++, r++) { switch (r->rtype) { case LR_BRANCH: added_branch = TRUE; @@ -488,10 +594,10 @@ fixup_special() lev = u.uz; lev.dlevel = atoi(r->rname.str); } else { - s_level *sp = find_level(r->rname.str); - + sp = find_level(r->rname.str); lev = sp->dlevel; } + FALLTHROUGH; /*FALLTHRU*/ case LR_UPSTAIR: @@ -507,24 +613,24 @@ fixup_special() case LR_DOWNTELE: /* save the region outlines for goto_level() */ if (r->rtype == LR_TELE || r->rtype == LR_UPTELE) { - updest.lx = r->inarea.x1; - updest.ly = r->inarea.y1; - updest.hx = r->inarea.x2; - updest.hy = r->inarea.y2; - updest.nlx = r->delarea.x1; - updest.nly = r->delarea.y1; - updest.nhx = r->delarea.x2; - updest.nhy = r->delarea.y2; + svu.updest.lx = r->inarea.x1; + svu.updest.ly = r->inarea.y1; + svu.updest.hx = r->inarea.x2; + svu.updest.hy = r->inarea.y2; + svu.updest.nlx = r->delarea.x1; + svu.updest.nly = r->delarea.y1; + svu.updest.nhx = r->delarea.x2; + svu.updest.nhy = r->delarea.y2; } if (r->rtype == LR_TELE || r->rtype == LR_DOWNTELE) { - dndest.lx = r->inarea.x1; - dndest.ly = r->inarea.y1; - dndest.hx = r->inarea.x2; - dndest.hy = r->inarea.y2; - dndest.nlx = r->delarea.x1; - dndest.nly = r->delarea.y1; - dndest.nhx = r->delarea.x2; - dndest.nhy = r->delarea.y2; + svd.dndest.lx = r->inarea.x1; + svd.dndest.ly = r->inarea.y1; + svd.dndest.hx = r->inarea.x2; + svd.dndest.hy = r->inarea.y2; + svd.dndest.nlx = r->delarea.x1; + svd.dndest.nly = r->delarea.y1; + svd.dndest.nhx = r->delarea.x2; + svd.dndest.nhy = r->delarea.y2; } /* place_lregion gets called from goto_level() */ break; @@ -544,13 +650,16 @@ fixup_special() struct obj *otmp; int tryct; - croom = &rooms[0]; /* only one room on the medusa level */ + croom = &svr.rooms[0]; /* the first room defined on the medusa level */ for (tryct = rnd(4); tryct; tryct--) { x = somex(croom); y = somey(croom); if (goodpos(x, y, (struct monst *) 0, 0)) { + int tryct2 = 0; + otmp = mk_tt_object(STATUE, x, y); - while (otmp && (poly_when_stoned(&mons[otmp->corpsenm]) + while (++tryct2 < 100 && otmp + && (poly_when_stoned(&mons[otmp->corpsenm]) || pm_resistance(&mons[otmp->corpsenm], MR_STONE))) { /* set_corpsenm() handles weight too */ @@ -566,81 +675,53 @@ fixup_special() mkcorpstat(STATUE, (struct monst *) 0, (struct permonst *) 0, somex(croom), somey(croom), CORPSTAT_NONE); if (otmp) { - while (pm_resistance(&mons[otmp->corpsenm], MR_STONE) - || poly_when_stoned(&mons[otmp->corpsenm])) { + tryct = 0; + while (++tryct < 100 + && (pm_resistance(&mons[otmp->corpsenm], MR_STONE) + || poly_when_stoned(&mons[otmp->corpsenm]))) { /* set_corpsenm() handles weight too */ set_corpsenm(otmp, rndmonnum()); } } - } else if (Is_wiz1_level(&u.uz)) { - croom = search_special(MORGUE); - - create_secret_door(croom, W_SOUTH | W_EAST | W_WEST); - } else if (Is_knox(&u.uz)) { - /* using an unfilled morgue for rm id */ - croom = search_special(MORGUE); - /* avoid inappropriate morgue-related messages */ - level.flags.graveyard = level.flags.has_morgue = 0; - croom->rtype = OROOM; /* perhaps it should be set to VAULT? */ - /* stock the main vault */ - for (x = croom->lx; x <= croom->hx; x++) - for (y = croom->ly; y <= croom->hy; y++) { - (void) mkgold((long) rn1(300, 600), x, y); - if (!rn2(3) && !is_pool(x, y)) - (void) maketrap(x, y, rn2(3) ? LANDMINE : SPIKED_PIT); - } - } else if (Role_if(PM_PRIEST) && In_quest(&u.uz)) { + } else if (Role_if(PM_CLERIC) && In_quest(&u.uz)) { /* less chance for undead corpses (lured from lower morgues) */ - level.flags.graveyard = 1; + svl.level.flags.graveyard = 1; } else if (Is_stronghold(&u.uz)) { - level.flags.graveyard = 1; - } else if (Is_sanctum(&u.uz)) { - croom = search_special(TEMPLE); - - create_secret_door(croom, W_ANY); - } else if (on_level(&u.uz, &orcus_level)) { - struct monst *mtmp, *mtmp2; - - /* it's a ghost town, get rid of shopkeepers */ - for (mtmp = fmon; mtmp; mtmp = mtmp2) { - mtmp2 = mtmp->nmon; - if (mtmp->isshk) - mongone(mtmp); - } + svl.level.flags.graveyard = 1; } else if (on_level(&u.uz, &baalzebub_level)) { /* custom wallify the "beetle" potion of the level */ baalz_fixup(); - } else if (u.uz.dnum == mines_dnum && ransacked) { + } else if (u.uz.dnum == mines_dnum && gr.ransacked) { stolen_booty(); } - if (lregions) - free((genericptr_t) lregions), lregions = 0; - num_lregions = 0; + if ((sp = Is_special(&u.uz)) != 0 && sp->flags.town) /* Mine Town */ + svl.level.flags.has_town = 1; + + if (gl.lregions) + free((genericptr_t) gl.lregions), gl.lregions = 0; + gn.num_lregions = 0; } -STATIC_OVL void -check_ransacked(s) -char *s; +staticfn void +check_ransacked(const char *s) { /* this kludge only works as long as orctown is minetn-1 */ - ransacked = (u.uz.dnum == mines_dnum && !strcmp(s, "minetn-1")); + gr.ransacked = (u.uz.dnum == mines_dnum && !strcmp(s, "minetn-1")); } #define ORC_LEADER 1 -static const char *orcfruit[] = { "paddle cactus", "dwarven root" }; +static const char *const orcfruit[] = { "paddle cactus", "dwarven root" }; -STATIC_OVL void -migrate_orc(mtmp, mflags) -struct monst *mtmp; -unsigned long mflags; +staticfn void +migrate_orc(struct monst *mtmp, unsigned long mflags) { int nlev, max_depth, cur_depth; d_level dest; cur_depth = (int) depth(&u.uz); max_depth = dunlevs_in_dungeon(&u.uz) - + (dungeons[u.uz.dnum].depth_start - 1); + + (svd.dungeons[u.uz.dnum].depth_start - 1); if (mflags == ORC_LEADER) { /* Note that the orc leader will take possession of any * remaining stuff not already delivered to other @@ -650,22 +731,21 @@ unsigned long mflags; /* once in a blue moon, he won't be at the very bottom */ if (!rn2(40)) nlev--; - mtmp->mspare1 |= MIGR_LEFTOVERS; + mtmp->migflags |= MIGR_LEFTOVERS; } else { nlev = rn2((max_depth - cur_depth) + 1) + cur_depth; if (nlev == cur_depth) nlev++; if (nlev > max_depth) nlev = max_depth; - mtmp->mspare1 = (mtmp->mspare1 & ~MIGR_LEFTOVERS); + mtmp->migflags = (mtmp->migflags & ~MIGR_LEFTOVERS); } get_level(&dest, nlev); migrate_to_level(mtmp, ledger_no(&dest), MIGR_RANDOM, (coord *) 0); } -void -shiny_orc_stuff(mtmp) -struct monst *mtmp; +staticfn void +shiny_orc_stuff(struct monst *mtmp) { int gemprob, goldprob, otyp; struct obj *otmp; @@ -695,20 +775,19 @@ struct monst *mtmp; add_to_minv(mtmp, otmp); } } -STATIC_OVL void -migr_booty_item(otyp, gang) -int otyp; -const char *gang; + +staticfn void +migr_booty_item(int otyp, const char *gang) { struct obj *otmp; otmp = mksobj_migr_to_species(otyp, (unsigned long) M2_ORC, TRUE, FALSE); if (otmp && gang) { - new_oname(otmp, strlen(gang) + 1); /* removes old name if present */ + new_oname(otmp, Strlen(gang) + 1); /* removes old name if present */ Strcpy(ONAME(otmp), gang); - if (otyp >= TRIPE_RATION && otyp <= TIN) { + if (objects[otyp].oc_class == FOOD_CLASS) { if (otyp == SLIME_MOLD) - otmp->spe = fruitadd((char *) orcfruit[rn2(SIZE(orcfruit))], + otmp->spe = fruitadd((char *) ROLL_FROM(orcfruit), (struct fruit *) 0); otmp->quan += (long) rn2(3); otmp->owt = weight(otmp); @@ -716,8 +795,8 @@ const char *gang; } } -STATIC_OVL void -stolen_booty(VOID_ARGS) +staticfn void +stolen_booty(void) { char *gang, gang_name[BUFSZ]; struct monst *mtmp; @@ -744,17 +823,21 @@ stolen_booty(VOID_ARGS) cnt = rnd(3); for (i = 0; i < cnt; ++i) migr_booty_item(SKELETON_KEY, gang); - otyp = rn2((GAUNTLETS_OF_DEXTERITY - LEATHER_GLOVES) + 1) + LEATHER_GLOVES; + otyp = rn1((GAUNTLETS_OF_DEXTERITY - LEATHER_GLOVES) + 1, LEATHER_GLOVES); migr_booty_item(otyp, gang); cnt = rnd(10); for (i = 0; i < cnt; ++i) { /* Food items - but no lembas! (or some other weird things) */ - otyp = rn2((TIN - TRIPE_RATION) + 1) + TRIPE_RATION; - if (otyp != LEMBAS_WAFER && otyp != GLOB_OF_GRAY_OOZE - && otyp != GLOB_OF_BROWN_PUDDING && otyp != GLOB_OF_GREEN_SLIME - && otyp != GLOB_OF_BLACK_PUDDING && otyp != MEAT_STICK - && otyp != MEATBALL && otyp != MEAT_STICK && otyp != MEAT_RING - && otyp != HUGE_CHUNK_OF_MEAT && otyp != CORPSE) + otyp = rn1(TIN - TRIPE_RATION + 1, TRIPE_RATION); + if (otyp != LEMBAS_WAFER + /* exclude meat , globs of , kelp + which all have random generation probability of 0 + (K-/C-rations do too, but we want to include those) */ + && (objects[otyp].oc_prob != 0 + || otyp == C_RATION || otyp == K_RATION) + /* exclude food items which utilize obj->corpsenm because + that field is going to be overloaded for delivery purposes */ + && otyp != CORPSE && otyp != EGG && otyp != TIN) migr_booty_item(otyp, gang); } migr_booty_item(rn2(2) ? LONG_SWORD : SILVER_SABER, gang); @@ -763,6 +846,7 @@ stolen_booty(VOID_ARGS) if (mtmp) { mtmp = christen_monst(mtmp, upstart(gang)); mtmp->mpeaceful = 0; + set_malign(mtmp); shiny_orc_stuff(mtmp); migrate_orc(mtmp, ORC_LEADER); } @@ -771,10 +855,10 @@ stolen_booty(VOID_ARGS) if (DEADMONSTER(mtmp)) continue; - if (is_orc(mtmp->data) && !has_mname(mtmp) && rn2(10)) { + if (is_orc(mtmp->data) && !has_mgivenname(mtmp) && rn2(10)) { /* * We'll consider the orc captain from the level - * .des file to be the captain of a rival orc horde + * description to be the captain of a rival orc horde * who is there to see what has transpired, and to * contemplate future action. * @@ -801,29 +885,30 @@ stolen_booty(VOID_ARGS) migrate_orc(mtmp, 0UL); } } - ransacked = 0; + gr.ransacked = 0; } #undef ORC_LEADER -boolean -maze_inbounds(x, y) -int x, y; +staticfn boolean +maze_inbounds(coordxy x, coordxy y) { return (x >= 2 && y >= 2 - && x < x_maze_max && y < y_maze_max && isok(x, y)); + && x < gx.x_maze_max && y < gy.y_maze_max + /* isok() test is superfluous here (unless something has + clobbered the static *_maze_max variables) */ + && isok(x, y)); } -void -maze_remove_deadends(typ) -xchar typ; +staticfn void +maze_remove_deadends(xint16 typ) { char dirok[4]; - int x, y, dir, idx, idx2, dx, dy, dx2, dy2; + coordxy x, y, dir, idx, idx2, dx, dy, dx2, dy2; dirok[0] = 0; /* lint suppression */ - for (x = 2; x < x_maze_max; x++) - for (y = 2; y < y_maze_max; y++) + for (x = 2; x < gx.x_maze_max; x++) + for (y = 2; y < gy.y_maze_max; y++) if (ACCESSIBLE(levl[x][y].typ) && (x % 2) && (y % 2)) { idx = idx2 = 0; for (dir = 0; dir < 4; dir++) { @@ -862,18 +947,22 @@ xchar typ; * TODO: rewrite walkfrom so it works on temp space, not levl */ void -create_maze(corrwid, wallthick) -int corrwid; -int wallthick; +create_maze(int corrwid, int wallthick, boolean rmdeadends) { - int x,y; + coordxy x,y; coord mm; - int tmp_xmax = x_maze_max; - int tmp_ymax = y_maze_max; + int tmp_xmax = gx.x_maze_max; + int tmp_ymax = gy.y_maze_max; int rdx = 0; int rdy = 0; int scale; + if (corrwid == -1) + corrwid = rnd(4); + + if (wallthick == -1) + wallthick = rnd(4) - corrwid; + if (wallthick < 1) wallthick = 1; else if (wallthick > 5) @@ -885,10 +974,10 @@ int wallthick; corrwid = 5; scale = corrwid + wallthick; - rdx = (x_maze_max / scale); - rdy = (y_maze_max / scale); + rdx = (gx.x_maze_max / scale); + rdy = (gy.y_maze_max / scale); - if (level.flags.corrmaze) + if (svl.level.flags.corrmaze) for (x = 2; x < (rdx * 2); x++) for (y = 2; y < (rdy * 2); y++) levl[x][y].typ = STONE; @@ -898,47 +987,44 @@ int wallthick; levl[x][y].typ = ((x % 2) && (y % 2)) ? STONE : HWALL; /* set upper bounds for maze0xy and walkfrom */ - x_maze_max = (rdx * 2); - y_maze_max = (rdy * 2); + gx.x_maze_max = (rdx * 2); + gy.y_maze_max = (rdy * 2); /* create maze */ maze0xy(&mm); walkfrom((int) mm.x, (int) mm.y, 0); - if (!rn2(5)) - maze_remove_deadends((level.flags.corrmaze) ? CORR : ROOM); + if (rmdeadends) + maze_remove_deadends((svl.level.flags.corrmaze) ? CORR : ROOM); /* restore bounds */ - x_maze_max = tmp_xmax; - y_maze_max = tmp_ymax; + gx.x_maze_max = tmp_xmax; + gy.y_maze_max = tmp_ymax; /* scale maze up if needed */ if (scale > 2) { char tmpmap[COLNO][ROWNO]; - int rx = 1, ry = 1; + int mx, my, dx, dy, rx = 1, ry = 1; /* back up the existing smaller maze */ - for (x = 1; x < x_maze_max; x++) - for (y = 1; y < y_maze_max; y++) { + for (x = 1; x < gx.x_maze_max; x++) + for (y = 1; y < gy.y_maze_max; y++) { tmpmap[x][y] = levl[x][y].typ; } /* do the scaling */ rx = x = 2; - while (rx < x_maze_max) { - int mx = (x % 2) ? corrwid - : ((x == 2 || x == (rdx * 2)) ? 1 - : wallthick); + while (rx < gx.x_maze_max) { + mx = (x % 2) ? corrwid : (x == 2 || x == rdx * 2) ? 1 : wallthick; ry = y = 2; - while (ry < y_maze_max) { - int dx = 0, dy = 0; - int my = (y % 2) ? corrwid - : ((y == 2 || y == (rdy * 2)) ? 1 - : wallthick); + while (ry < gy.y_maze_max) { + my = (y % 2) ? corrwid + : (y == 2 || y == rdy * 2) ? 1 + : wallthick; for (dx = 0; dx < mx; dx++) for (dy = 0; dy < my; dy++) { - if (rx+dx >= x_maze_max - || ry+dy >= y_maze_max) + if (rx + dx >= gx.x_maze_max + || ry + dy >= gy.y_maze_max) break; levl[rx + dx][ry + dy].typ = tmpmap[x][y]; } @@ -952,34 +1038,120 @@ int wallthick; } } +void +pick_vibrasquare_location(void) +{ + coordxy x, y; + stairway *stway; + int trycnt = 0; + /* these are also defined in mklev.c and they may not be appropriate + for mazes with corridors wider than 1 or for cavernous levels */ +#define x_maze_min 2 +#define y_maze_min 2 + + /* + * Pick a position where the stairs down to Moloch's Sanctum + * level will ultimately be created. At that time, an area + * will be altered: walls removed, moat and traps generated, + * boulders destroyed. The position picked here must ensure + * that that invocation area won't extend off the map. + * + * We actually allow up to 2 squares around the usual edge of + * the area to get truncated; see mkinvokearea(mklev.c). + */ +#define INVPOS_X_MARGIN (6 - 2) +#define INVPOS_Y_MARGIN (5 - 2) +#define INVPOS_DISTANCE 11 + int x_range = gx.x_maze_max - x_maze_min - 2 * INVPOS_X_MARGIN - 1, + y_range = gy.y_maze_max - y_maze_min - 2 * INVPOS_Y_MARGIN - 1; + + if (x_range <= INVPOS_X_MARGIN || y_range <= INVPOS_Y_MARGIN + || (x_range * y_range) <= (INVPOS_DISTANCE * INVPOS_DISTANCE)) { + debugpline2("svi.inv_pos: maze is too small! (%d x %d)", + gx.x_maze_max, gy.y_maze_max); + } + svi.inv_pos.x = svi.inv_pos.y = 0; /*{occupied() => invocation_pos()}*/ + do { + x = rn1(x_range, x_maze_min + INVPOS_X_MARGIN + 1); + y = rn1(y_range, y_maze_min + INVPOS_Y_MARGIN + 1); + /* we don't want it to be too near the stairs, nor + to be on a spot that's already in use (wall|trap) */ + if (++trycnt > 1000) + break; + } while (((stway = stairway_find_dir(TRUE)) != 0) + && (x == stway->sx || y == stway->sy /*(direct line)*/ + || abs(x - stway->sx) == abs(y - stway->sy) + || distmin(x, y, stway->sx, stway->sy) <= INVPOS_DISTANCE + || !SPACE_POS(levl[x][y].typ) || occupied(x, y))); + svi.inv_pos.x = x; + svi.inv_pos.y = y; +#undef INVPOS_X_MARGIN +#undef INVPOS_Y_MARGIN +#undef INVPOS_DISTANCE +#undef x_maze_min +#undef y_maze_min +} + +/* add objects and monsters to random maze */ +staticfn void +populate_maze(void) +{ + int i; + coord mm; + + for (i = rn1(8, 11); i; i--) { + mazexy(&mm); + (void) mkobj_at(rn2(2) ? GEM_CLASS : RANDOM_CLASS, mm.x, mm.y, TRUE); + } + for (i = rn1(10, 2); i; i--) { + mazexy(&mm); + (void) mksobj_at(BOULDER, mm.x, mm.y, TRUE, FALSE); + } + for (i = rn2(3); i; i--) { + mazexy(&mm); + (void) makemon(&mons[PM_MINOTAUR], mm.x, mm.y, NO_MM_FLAGS); + } + for (i = rn1(5, 7); i; i--) { + mazexy(&mm); + (void) makemon((struct permonst *) 0, mm.x, mm.y, NO_MM_FLAGS); + } + for (i = rn1(6, 7); i; i--) { + mazexy(&mm); + (void) mkgold(0L, mm.x, mm.y); + } + for (i = rn1(6, 7); i; i--) + mktrap(0, MKTRAP_MAZEFLAG, (struct mkroom *) 0, (coord *) 0); +} void -makemaz(s) -const char *s; +makemaz(const char *s) { - int x, y; char protofile[20]; s_level *sp = Is_special(&u.uz); coord mm; if (*s) { if (sp && sp->rndlevs) - Sprintf(protofile, "%s-%d", s, rnd((int) sp->rndlevs)); + Snprintf(protofile, sizeof protofile, + "%s-%d", s, rnd((int) sp->rndlevs)); else Strcpy(protofile, s); - } else if (*(dungeons[u.uz.dnum].proto)) { + } else if (*(svd.dungeons[u.uz.dnum].proto)) { if (dunlevs_in_dungeon(&u.uz) > 1) { if (sp && sp->rndlevs) - Sprintf(protofile, "%s%d-%d", dungeons[u.uz.dnum].proto, - dunlev(&u.uz), rnd((int) sp->rndlevs)); + Snprintf(protofile, sizeof protofile, + "%s%d-%d", svd.dungeons[u.uz.dnum].proto, + dunlev(&u.uz), rnd((int) sp->rndlevs)); else - Sprintf(protofile, "%s%d", dungeons[u.uz.dnum].proto, - dunlev(&u.uz)); + Snprintf(protofile, sizeof protofile, + "%s%d", svd.dungeons[u.uz.dnum].proto, + dunlev(&u.uz)); } else if (sp && sp->rndlevs) { - Sprintf(protofile, "%s-%d", dungeons[u.uz.dnum].proto, - rnd((int) sp->rndlevs)); + Snprintf(protofile, sizeof protofile, + "%s-%d", svd.dungeons[u.uz.dnum].proto, + rnd((int) sp->rndlevs)); } else - Strcpy(protofile, dungeons[u.uz.dnum].proto); + Strcpy(protofile, svd.dungeons[u.uz.dnum].proto); } else Strcpy(protofile, ""); @@ -989,18 +1161,19 @@ const char *s; /* char *ep = getenv("SPLEVTYPE"); */ /* not nh_getenv */ char *ep = 0; /* For NLE. */ if (ep) { - /* rindex always succeeds due to code in prior block */ - int len = (int) ((rindex(protofile, '-') - protofile) + 1); + /* strrchr always succeeds due to code in prior block */ + int len = (int) ((strrchr(protofile, '-') - protofile) + 1); while (ep && *ep) { if (!strncmp(ep, protofile, len)) { int pick = atoi(ep + len); + /* use choice only if valid */ if (pick > 0 && pick <= (int) sp->rndlevs) Sprintf(protofile + len, "%d", pick); break; } else { - ep = index(ep, ','); + ep = strchr(ep, ','); if (ep) ++ep; } @@ -1011,6 +1184,7 @@ const char *s; if (*protofile) { check_ransacked(protofile); Strcat(protofile, LEV_EXT); + gi.in_mk_themerooms = FALSE; if (load_special(protofile)) { /* some levels can end up with monsters on dead mon list, including light source monsters */ @@ -1020,93 +1194,32 @@ const char *s; impossible("Couldn't load \"%s\" - making a maze.", protofile); } - level.flags.is_maze_lev = TRUE; - level.flags.corrmaze = !rn2(3); + svl.level.flags.is_maze_lev = 1; + svl.level.flags.corrmaze = !rn2(3); if (!Invocation_lev(&u.uz) && rn2(2)) { - int corrscale = rnd(4); - create_maze(corrscale,rnd(4)-corrscale); + create_maze(-1, -1, !rn2(5)); } else { - create_maze(1,1); + create_maze(1, 1, FALSE); } - if (!level.flags.corrmaze) - wallification(2, 2, x_maze_max, y_maze_max); + if (!svl.level.flags.corrmaze) + wallification(2, 2, gx.x_maze_max, gy.y_maze_max); mazexy(&mm); - mkstairs(mm.x, mm.y, 1, (struct mkroom *) 0); /* up */ + mkstairs(mm.x, mm.y, 1, (struct mkroom *) 0, FALSE); /* up */ if (!Invocation_lev(&u.uz)) { mazexy(&mm); - mkstairs(mm.x, mm.y, 0, (struct mkroom *) 0); /* down */ + mkstairs(mm.x, mm.y, 0, (struct mkroom *) 0, FALSE); /* down */ } else { /* choose "vibrating square" location */ -#define x_maze_min 2 -#define y_maze_min 2 -/* - * Pick a position where the stairs down to Moloch's Sanctum - * level will ultimately be created. At that time, an area - * will be altered: walls removed, moat and traps generated, - * boulders destroyed. The position picked here must ensure - * that that invocation area won't extend off the map. - * - * We actually allow up to 2 squares around the usual edge of - * the area to get truncated; see mkinvokearea(mklev.c). - */ -#define INVPOS_X_MARGIN (6 - 2) -#define INVPOS_Y_MARGIN (5 - 2) -#define INVPOS_DISTANCE 11 - int x_range = x_maze_max - x_maze_min - 2 * INVPOS_X_MARGIN - 1, - y_range = y_maze_max - y_maze_min - 2 * INVPOS_Y_MARGIN - 1; - - if (x_range <= INVPOS_X_MARGIN || y_range <= INVPOS_Y_MARGIN - || (x_range * y_range) <= (INVPOS_DISTANCE * INVPOS_DISTANCE)) { - debugpline2("inv_pos: maze is too small! (%d x %d)", - x_maze_max, y_maze_max); - } - inv_pos.x = inv_pos.y = 0; /*{occupied() => invocation_pos()}*/ - do { - x = rn1(x_range, x_maze_min + INVPOS_X_MARGIN + 1); - y = rn1(y_range, y_maze_min + INVPOS_Y_MARGIN + 1); - /* we don't want it to be too near the stairs, nor - to be on a spot that's already in use (wall|trap) */ - } while (x == xupstair || y == yupstair /*(direct line)*/ - || abs(x - xupstair) == abs(y - yupstair) - || distmin(x, y, xupstair, yupstair) <= INVPOS_DISTANCE - || !SPACE_POS(levl[x][y].typ) || occupied(x, y)); - inv_pos.x = x; - inv_pos.y = y; - maketrap(inv_pos.x, inv_pos.y, VIBRATING_SQUARE); -#undef INVPOS_X_MARGIN -#undef INVPOS_Y_MARGIN -#undef INVPOS_DISTANCE -#undef x_maze_min -#undef y_maze_min + pick_vibrasquare_location(); + maketrap(svi.inv_pos.x, svi.inv_pos.y, VIBRATING_SQUARE); } /* place branch stair or portal */ place_branch(Is_branchlev(&u.uz), 0, 0); - for (x = rn1(8, 11); x; x--) { - mazexy(&mm); - (void) mkobj_at(rn2(2) ? GEM_CLASS : 0, mm.x, mm.y, TRUE); - } - for (x = rn1(10, 2); x; x--) { - mazexy(&mm); - (void) mksobj_at(BOULDER, mm.x, mm.y, TRUE, FALSE); - } - for (x = rn2(3); x; x--) { - mazexy(&mm); - (void) makemon(&mons[PM_MINOTAUR], mm.x, mm.y, NO_MM_FLAGS); - } - for (x = rn1(5, 7); x; x--) { - mazexy(&mm); - (void) makemon((struct permonst *) 0, mm.x, mm.y, NO_MM_FLAGS); - } - for (x = rn1(6, 7); x; x--) { - mazexy(&mm); - (void) mkgold(0L, mm.x, mm.y); - } - for (x = rn1(6, 7); x; x--) - mktrap(0, 1, (struct mkroom *) 0, (coord *) 0); + populate_maze(); } #ifdef MICRO @@ -1116,9 +1229,7 @@ const char *s; * that is totally safe. */ void -walkfrom(x, y, typ) -int x, y; -schar typ; +walkfrom(coordxy x, coordxy y, schar typ) { #define CELLS (ROWNO * COLNO) / 4 /* a maze cell is 4 squares */ char mazex[CELLS + 1], mazey[CELLS + 1]; /* char's are OK */ @@ -1126,7 +1237,7 @@ schar typ; int dirs[4]; if (!typ) { - if (level.flags.corrmaze) + if (svl.level.flags.corrmaze) typ = CORR; else typ = ROOM; @@ -1165,15 +1276,13 @@ schar typ; #else /* !MICRO */ void -walkfrom(x, y, typ) -int x, y; -schar typ; +walkfrom(coordxy x, coordxy y, schar typ) { int q, a, dir; int dirs[4]; if (!typ) { - if (level.flags.corrmaze) + if (svl.level.flags.corrmaze) typ = CORR; else typ = ROOM; @@ -1204,56 +1313,52 @@ schar typ; /* find random point in generated corridors, so we don't create items in moats, bunkers, or walls */ void -mazexy(cc) -coord *cc; +mazexy(coord *cc) { + coordxy x, y; + int allowedtyp = (svl.level.flags.corrmaze ? CORR : ROOM); int cpt = 0; do { - cc->x = 1 + rn2(x_maze_max); - cc->y = 1 + rn2(y_maze_max); - cpt++; - } while (cpt < 100 - && levl[cc->x][cc->y].typ - != (level.flags.corrmaze ? CORR : ROOM)); - if (cpt >= 100) { - int x, y; - - /* last try */ - for (x = 1; x < x_maze_max; x++) - for (y = 1; y < y_maze_max; y++) { + /* once upon a time this only considered odd values greater than 2 + and less than N (for N=={x,y}_maze_max) because even values were + where maze walls always got placed; when wider maze corridors + were introduced it was changed to 1+rn2(N) which is just an + obscure way to get rnd(N); probably ought to be using 2+rn2(N-1) + to exclude the maze's outer boundary walls; trying and rejecting + those walls will waste some of the 100 random attempts... */ + x = rnd(gx.x_maze_max); + y = rnd(gy.y_maze_max); + if (levl[x][y].typ == allowedtyp) { + cc->x = x; + cc->y = y; + return; + } + } while (++cpt < 100); + /* 100 random attempts failed; systematically try every possibility */ + for (x = 1; x <= gx.x_maze_max; x++) + for (y = 1; y <= gy.y_maze_max; y++) + if (levl[x][y].typ == allowedtyp) { cc->x = x; cc->y = y; - if (levl[cc->x][cc->y].typ - == (level.flags.corrmaze ? CORR : ROOM)) - return; + return; } - panic("mazexy: can't find a place!"); - } + /* every spot on the area of map allowed for mazes has been rejected */ + panic("mazexy: can't find a place!"); + /*NOTREACHED*/ return; } -/* put a non-diggable boundary around the initial portion of a level map. - * assumes that no level will initially put things beyond the isok() range. - * - * we can't bound unconditionally on the last line with something in it, - * because that something might be a niche which was already reachable, - * so the boundary would be breached - * - * we can't bound unconditionally on one beyond the last line, because - * that provides a window of abuse for wallified special levels - */ void -bound_digging() +get_level_extends( + coordxy *left, coordxy *top, + coordxy *right, coordxy *bottom) { - int x, y; + coordxy x, y; unsigned typ; struct rm *lev; boolean found, nonwall; - int xmin, xmax, ymin, ymax; - - if (Is_earthlevel(&u.uz)) - return; /* everything diggable here */ + coordxy xmin, xmax, ymin, ymax; found = nonwall = FALSE; for (xmin = 0; !found && xmin <= COLNO; xmin++) { @@ -1267,7 +1372,7 @@ bound_digging() } } } - xmin -= (nonwall || !level.flags.is_maze_lev) ? 2 : 1; + xmin -= (nonwall || !svl.level.flags.is_maze_lev) ? 2 : 1; if (xmin < 0) xmin = 0; @@ -1283,7 +1388,7 @@ bound_digging() } } } - xmax += (nonwall || !level.flags.is_maze_lev) ? 2 : 1; + xmax += (nonwall || !svl.level.flags.is_maze_lev) ? 2 : 1; if (xmax >= COLNO) xmax = COLNO - 1; @@ -1299,7 +1404,7 @@ bound_digging() } } } - ymin -= (nonwall || !level.flags.is_maze_lev) ? 2 : 1; + ymin -= (nonwall || !svl.level.flags.is_maze_lev) ? 2 : 1; found = nonwall = FALSE; for (ymax = ROWNO - 1; !found && ymax >= 0; ymax--) { @@ -1313,51 +1418,90 @@ bound_digging() } } } - ymax += (nonwall || !level.flags.is_maze_lev) ? 2 : 1; + ymax += (nonwall || !svl.level.flags.is_maze_lev) ? 2 : 1; + + *left = xmin; + *right = xmax; + *top = ymin; + *bottom = ymax; +} + +/* put a non-diggable/non-phaseable boundary around the initial portion + * of a level map. assumes that no level will initially put things + * beyond the isok() range. + * + * we can't bound unconditionally on the last line with something in it, + * because that something might be a niche which was already reachable, + * so the boundary would be breached + * + * we can't bound unconditionally on one beyond the last line, because + * that provides a window of abuse for wallified special levels + */ +void +bound_digging(void) +{ + coordxy x, y; + coordxy xmin, xmax, ymin, ymax; + + if (Is_earthlevel(&u.uz)) + return; /* everything diggable here */ + + get_level_extends(&xmin, &ymin, &xmax, &ymax); for (x = 0; x < COLNO; x++) for (y = 0; y < ROWNO; y++) - if (y <= ymin || y >= ymax || x <= xmin || x >= xmax) { -#ifdef DCC30_BUG - lev = &levl[x][y]; - lev->wall_info |= W_NONDIGGABLE; -#else - levl[x][y].wall_info |= W_NONDIGGABLE; -#endif + if (IS_STWALL(levl[x][y].typ)) { + /* undiggable walls at edges, ... */ + if (y <= ymin || y >= ymax || x <= xmin || x >= xmax) + levl[x][y].wall_info |= W_NONDIGGABLE; + /* one tile past that, everything is also unphaseable */ + if (y < ymin || y > ymax || x < xmin || x > xmax) + levl[x][y].wall_info |= W_NONPASSWALL; } } void -mkportal(x, y, todnum, todlevel) -xchar x, y, todnum, todlevel; +mkportal(coordxy x, coordxy y, xint16 todnum, xint16 todlevel) { /* a portal "trap" must be matched by a portal in the destination dungeon/dlevel */ struct trap *ttmp = maketrap(x, y, MAGIC_PORTAL); if (!ttmp) { - impossible("portal on top of portal??"); + impossible("portal on top of portal?"); return; } debugpline4("mkportal: at <%d,%d>, to %s, level %d", x, y, - dungeons[todnum].dname, todlevel); + svd.dungeons[todnum].dname, todlevel); ttmp->dst.dnum = todnum; ttmp->dst.dlevel = todlevel; return; } +/* augment the Plane of Fire; called from goto_level() when arriving and + moveloop_core() when on the level */ void -fumaroles() +fumaroles(void) { - xchar n; + xint16 n, nmax = rn2(3); + int sizemin = 5; boolean snd = FALSE, loud = FALSE; - for (n = rn2(3) + 2; n; n--) { - xchar x = rn1(COLNO - 4, 3); - xchar y = rn1(ROWNO - 4, 3); + if (Is_firelevel(&u.uz)) { + nmax++; + sizemin += 5; + } + if (svl.level.flags.temperature > 0) { + nmax++; + sizemin += 5; + } + + for (n = nmax; n; n--) { + coordxy x = rn1(COLNO - 4, 3); + coordxy y = rn1(ROWNO - 4, 3); if (levl[x][y].typ == LAVAPOOL) { - NhRegion *r = create_gas_cloud(x, y, 4 + rn2(5), rn1(10, 5)); + NhRegion *r = create_gas_cloud(x, y, rn1(10, sizemin), rn1(10, 5)); clear_heros_fault(r); snd = TRUE; @@ -1376,39 +1520,44 @@ fumaroles() * other source files, but they are all so nicely encapsulated here. */ -static struct bubble *bbubbles, *ebubbles; - -static struct trap *wportal; -static int xmin, ymin, xmax, ymax; /* level boundaries */ /* bubble movement boundaries */ -#define bxmin (xmin + 1) -#define bymin (ymin + 1) -#define bxmax (xmax - 1) -#define bymax (ymax - 1) +#define gbxmin (svx.xmin + 1) +#define gbymin (svy.ymin + 1) +#define gbxmax (svx.xmax - 1) +#define gbymax (svy.ymax - 1) -STATIC_DCL void NDECL(set_wportal); -STATIC_DCL void FDECL(mk_bubble, (int, int, int)); -STATIC_DCL void FDECL(mv_bubble, (struct bubble *, int, int, BOOLEAN_P)); +/* the bubble hero is in */ +static struct bubble *hero_bubble = NULL; +staticfn void set_wportal(void); +staticfn void mk_bubble(coordxy, coordxy, int); +staticfn void mv_bubble(struct bubble *, coordxy, coordxy, boolean); + +/* augment the Planes of Water (for bubbles) and Air (for clouds); called + from goto_level() when arriving and moveloop_core() when on the level */ void -movebubbles() +movebubbles(void) { - static const struct rm water_pos = { cmap_to_glyph(S_water), WATER, 0, 0, - 0, 0, 0, 0, 0, 0 }; - static const struct rm air_pos = { cmap_to_glyph(S_cloud), AIR, 0, 0, 0, - 1, 0, 0, 0, 0 }; + static const struct rm water_pos = { + cmap_b_to_glyph(S_water), WATER, 0, 0, 0, 0, 0, 0, 0, 0 + }, air_pos = { + cmap_b_to_glyph(S_cloud), AIR, 0, 0, 0, 1, 0, 0, 0, 0 + }; static boolean up = FALSE; struct bubble *b; struct container *cons; struct trap *btrap; - int x, y, i, j, bcpin = 0; + coordxy x, y; + int i, j, bcpin = 0; /* set up the portal the first time bubbles are moved */ - if (!wportal) + if (!gw.wportal) set_wportal(); vision_recalc(2); + hero_bubble = NULL; + if (Is_waterlevel(&u.uz)) { /* keep attached ball&chain separate from bubble objects */ if (Punished) @@ -1418,7 +1567,8 @@ movebubbles() * Pick up everything inside of a bubble then fill all bubble * locations. */ - for (b = up ? bbubbles : ebubbles; b; b = up ? b->next : b->prev) { + for (b = up ? svb.bbubbles : ge.ebubbles; b; + b = up ? b->next : b->prev) { if (b->cons) panic("movebubbles: cons != null"); for (i = 0, x = b->x; i < (int) b->bm[0]; i++, x++) @@ -1433,7 +1583,7 @@ movebubbles() if (OBJ_AT(x, y)) { struct obj *olist = (struct obj *) 0, *otmp; - while ((otmp = level.objects[x][y]) != 0) { + while ((otmp = svl.level.objects[x][y]) != 0) { remove_object(otmp); otmp->ox = otmp->oy = 0; otmp->nexthere = olist; @@ -1469,7 +1619,7 @@ movebubbles() mon->mx = mon->my = 0; mon->mstate |= MON_BUBBLEMOVE; } - if (!u.uswallow && x == u.ux && y == u.uy) { + if (!u.uswallow && u_at(x, y)) { cons = (struct container *) alloc(sizeof *cons); cons->x = x; cons->y = y; @@ -1478,6 +1628,7 @@ movebubbles() cons->next = b->cons; b->cons = cons; + hero_bubble = b; } if ((btrap = t_at(x, y)) != 0) { cons = (struct container *) alloc(sizeof *cons); @@ -1500,11 +1651,11 @@ movebubbles() for (x = 1; x <= (COLNO - 1); x++) for (y = 0; y <= (ROWNO - 1); y++) { levl[x][y] = air_pos; - unblock_point(x, y); + recalc_block_point(x, y); /* all air or all cloud around the perimeter of the Air level tends to look strange; break up the pattern */ - xedge = (boolean) (x < bxmin || x > bxmax); - yedge = (boolean) (y < bymin || y > bymax); + xedge = (boolean) (x < gbxmin || x > gbxmax); + yedge = (boolean) (y < gbymin || y > gbymax); if (xedge || yedge) { if (!rn2(xedge ? 3 : 5)) { levl[x][y].typ = CLOUD; @@ -1520,7 +1671,7 @@ movebubbles() * would eventually end up in the last bubble in the chain. */ up = !up; - for (b = up ? bbubbles : ebubbles; b; b = up ? b->next : b->prev) { + for (b = up ? svb.bbubbles : ge.ebubbles; b; b = up ? b->next : b->prev) { int rx = rn2(3), ry = rn2(3); mv_bubble(b, b->dx + 1 - (!b->dx ? rx : (rx ? 1 : 0)), @@ -1530,14 +1681,14 @@ movebubbles() /* put attached ball&chain back */ if (Is_waterlevel(&u.uz) && Punished) lift_covet_and_placebc(bcpin); - vision_full_recalc = 1; + gv.vision_full_recalc = 1; } /* when moving in water, possibly (1 in 3) alter the intended destination */ void -water_friction() +water_friction(void) { - int x, y, dx, dy; + coordxy x, y, dx, dy; boolean eff = FALSE; if (Swimming && rn2(4)) @@ -1569,130 +1720,114 @@ water_friction() } void -save_waterlevel(fd, mode) -int fd, mode; +save_waterlevel(NHFILE *nhfp) { struct bubble *b; - if (!Is_waterlevel(&u.uz) && !Is_airlevel(&u.uz)) + if (!svb.bbubbles) return; - if (perform_bwrite(mode)) { + if (update_file(nhfp)) { int n = 0; - for (b = bbubbles; b; b = b->next) + for (b = svb.bbubbles; b; b = b->next) ++n; - bwrite(fd, (genericptr_t) &n, sizeof n); - bwrite(fd, (genericptr_t) &xmin, sizeof xmin); - bwrite(fd, (genericptr_t) &ymin, sizeof ymin); - bwrite(fd, (genericptr_t) &xmax, sizeof xmax); - bwrite(fd, (genericptr_t) &ymax, sizeof ymax); - for (b = bbubbles; b; b = b->next) - bwrite(fd, (genericptr_t) b, sizeof *b); + Sfo_int(nhfp, &n, "waterlevel-bubble_count"); + Sfo_int(nhfp, &svx.xmin, "waterlevel-xmin"); + Sfo_int(nhfp, &svy.ymin, "waterlevel-ymin"); + Sfo_int(nhfp, &svx.xmax, "waterlevel-xmax"); + Sfo_int(nhfp, &svy.ymax, "waterlevel-ymax"); + for (b = svb.bbubbles; b; b = b->next) { + Sfo_bubble(nhfp, b, "waterlevel-bubble"); + } } - if (release_data(mode)) + if (release_data(nhfp)) unsetup_waterlevel(); } +#endif /* !SFCTOOL */ +/* restoring air bubbles on Plane of Water or clouds on Plane of Air */ void -restore_waterlevel(fd) -int fd; +restore_waterlevel(NHFILE *nhfp) { struct bubble *b = (struct bubble *) 0, *btmp; - int i, n; + int i, n = 0; - if (!Is_waterlevel(&u.uz) && !Is_airlevel(&u.uz)) - return; - - if (fd == -1) { /* special handling for restore in goto_level() */ - if (!wizard) - impossible("restore_waterlevel: returning to %s?", - Is_waterlevel(&u.uz) ? "Water" : "Air"); - setup_waterlevel(); - return; - } +#ifdef SFCTOOL + svb.bbubbles = (struct bubble *) 0; + /* set_wportal(); */ +#endif - set_wportal(); - mread(fd, (genericptr_t) &n, sizeof n); - mread(fd, (genericptr_t) &xmin, sizeof xmin); - mread(fd, (genericptr_t) &ymin, sizeof ymin); - mread(fd, (genericptr_t) &xmax, sizeof xmax); - mread(fd, (genericptr_t) &ymax, sizeof ymax); + Sfi_int(nhfp, &n, "waterlevel-bubble_count"); + Sfi_int(nhfp, &svx.xmin, "waterlevel-xmin"); + Sfi_int(nhfp, &svy.ymin, "waterlevel-ymin"); + Sfi_int(nhfp, &svx.xmax, "waterlevel-xmax"); + Sfi_int(nhfp, &svy.ymax, "waterlevel-ymax"); for (i = 0; i < n; i++) { btmp = b; - b = (struct bubble *) alloc(sizeof *b); - mread(fd, (genericptr_t) b, sizeof *b); - if (bbubbles) { + b = (struct bubble *) alloc((unsigned) sizeof *b); + Sfi_bubble(nhfp, b, "waterlevel-bubble"); + if (btmp) { btmp->next = b; b->prev = btmp; } else { - bbubbles = b; + svb.bbubbles = b; b->prev = (struct bubble *) 0; } +#ifndef SFCTOOL mv_bubble(b, 0, 0, TRUE); +#endif } - ebubbles = b; - b->next = (struct bubble *) 0; -} - -const char * -waterbody_name(x, y) -xchar x, y; -{ - struct rm *lev; - schar ltyp; - - if (!isok(x, y)) - return "drink"; /* should never happen */ - lev = &levl[x][y]; - ltyp = lev->typ; - if (ltyp == DRAWBRIDGE_UP) - ltyp = db_under_typ(lev->drawbridgemask); - - if (ltyp == LAVAPOOL) - return hliquid("lava"); - else if (ltyp == ICE) - return "ice"; - else if (ltyp == POOL) - return "pool of water"; - else if (ltyp == WATER || Is_waterlevel(&u.uz)) - ; /* fall through to default return value */ - else if (Is_juiblex_level(&u.uz)) - return "swamp"; - else if (ltyp == MOAT && !Is_medusa_level(&u.uz)) - return "moat"; - - return hliquid("water"); +#ifndef SFCTOOL + ge.ebubbles = b; + if (b) { + b->next = (struct bubble *) 0; + } else { + /* avoid "saving and reloading may fix this" */ + program_state.something_worth_saving = 0; + /* during restore, information about what level this is might not + be available so we're wishy-washy about what we describe */ + impossible("No %s to restore?", + (Is_waterlevel(&u.uz) || Is_waterlevel(&gu.uz_save)) + ? "air bubbles" + : (Is_airlevel(&u.uz) || Is_airlevel(&gu.uz_save)) + ? "clouds" + : "air bubbles or clouds"); + program_state.something_worth_saving = 1; + } +#endif } -STATIC_OVL void -set_wportal() +#ifndef SFCTOOL +staticfn void +set_wportal(void) { /* there better be only one magic portal on water level... */ - for (wportal = ftrap; wportal; wportal = wportal->ntrap) - if (wportal->ttyp == MAGIC_PORTAL) + for (gw.wportal = gf.ftrap; gw.wportal; gw.wportal = gw.wportal->ntrap) + if (gw.wportal->ttyp == MAGIC_PORTAL) return; impossible("set_wportal(): no portal!"); } -STATIC_OVL void -setup_waterlevel() +staticfn void +setup_waterlevel(void) { - int x, y, xskip, yskip, typ, glyph; + int typ, glyph; + coordxy x, y, xskip, yskip; if (!Is_waterlevel(&u.uz) && !Is_airlevel(&u.uz)) panic("setup_waterlevel(): [%d:%d] neither 'Water' nor 'Air'", (int) u.uz.dnum, (int) u.uz.dlevel); /* ouch, hardcoded... (file scope statics and used in bxmin,bymax,&c) */ - xmin = 3; - ymin = 1; + svx.xmin = 3; + svy.ymin = 1; /* use separate statements so that compiler won't complain about min() comparing two constants; the alternative is to do this in the preprocessor: #if (20 > ROWNO-1) ymax=ROWNO-1 #else ymax=20 #endif */ - xmax = 78; - xmax = min(xmax, (COLNO - 1) - 1); - ymax = 20; - ymax = min(ymax, (ROWNO - 1)); + svx.xmax = 78; + svx.xmax = min(svx.xmax, (COLNO - 1) - 1); + svy.ymax = 20; + svy.ymax = min(svy.ymax, (ROWNO - 1)); /* entire level is remembered as one glyph and any unspecified portion should default to level's base element rather than to usual stone */ @@ -1716,27 +1851,26 @@ setup_waterlevel() yskip = 3 + rn2(3); } - for (x = bxmin; x <= bxmax; x += xskip) - for (y = bymin; y <= bymax; y += yskip) + for (x = gbxmin; x <= gbxmax; x += xskip) + for (y = gbymin; y <= gbymax; y += yskip) mk_bubble(x, y, rn2(7)); } -STATIC_OVL void -unsetup_waterlevel() +staticfn void +unsetup_waterlevel(void) { struct bubble *b, *bb; /* free bubbles */ - for (b = bbubbles; b; b = bb) { + for (b = svb.bbubbles; b; b = bb) { bb = b->next; free((genericptr_t) b); } - bbubbles = ebubbles = (struct bubble *) 0; + svb.bbubbles = ge.ebubbles = (struct bubble *) 0; } -STATIC_OVL void -mk_bubble(x, y, n) -int x, y, n; +staticfn void +mk_bubble(coordxy x, coordxy y, int n) { /* * These bit masks make visually pleasing bubbles on a normal aspect @@ -1756,7 +1890,7 @@ int x, y, n; *const bmask[] = { bm2, bm3, bm4, bm5, bm6, bm7, bm8 }; struct bubble *b; - if (x >= bxmax || y >= bymax) + if (x >= gbxmax || y >= gbymax) return; if (n >= SIZE(bmask)) { impossible("n too large (mk_bubble)"); @@ -1766,10 +1900,10 @@ int x, y, n; panic("bmask size is larger than MAX_BMASK"); } b = (struct bubble *) alloc(sizeof *b); - if ((x + (int) bmask[n][0] - 1) > bxmax) - x = bxmax - bmask[n][0] + 1; - if ((y + (int) bmask[n][1] - 1) > bymax) - y = bymax - bmask[n][1] + 1; + if ((x + (int) bmask[n][0] - 1) > gbxmax) + x = gbxmax - bmask[n][0] + 1; + if ((y + (int) bmask[n][1] - 1) > gbymax) + y = gbymax - bmask[n][1] + 1; b->x = x; b->y = y; b->dx = 1 - rn2(3); @@ -1778,18 +1912,34 @@ int x, y, n; (void) memcpy((genericptr_t) b->bm, (genericptr_t) bmask[n], (bmask[n][1] + 2) * sizeof (b->bm[0])); b->cons = 0; - if (!bbubbles) - bbubbles = b; - if (ebubbles) { - ebubbles->next = b; - b->prev = ebubbles; + if (!svb.bbubbles) + svb.bbubbles = b; + if (ge.ebubbles) { + ge.ebubbles->next = b; + b->prev = ge.ebubbles; } else b->prev = (struct bubble *) 0; b->next = (struct bubble *) 0; - ebubbles = b; + ge.ebubbles = b; mv_bubble(b, 0, 0, TRUE); } +/* maybe change the movement direction of the bubble hero is in */ +void +maybe_adjust_hero_bubble(void) +{ + if (!Is_waterlevel(&u.uz)) + return; + + if (!u.dx && !u.dy) + return; + + if (hero_bubble && !rn2(2)) { + hero_bubble->dx = u.dx; + hero_bubble->dy = u.dy; + } +} + /* * The player, the portal and all other objects and monsters * float along with their associated bubbles. Bubbles may overlap @@ -1798,13 +1948,11 @@ int x, y, n; * in the immediate neighborhood of one, he/she may get sucked inside. * This property also makes leaving a bubble slightly difficult. */ -STATIC_OVL void -mv_bubble(b, dx, dy, ini) -struct bubble *b; -int dx, dy; -boolean ini; +staticfn void +mv_bubble(struct bubble *b, coordxy dx, coordxy dy, boolean ini) { - int x, y, i, j, colli = 0; + int i, j, colli = 0; + coordxy x, y; struct container *cons, *ctemp; /* clouds move slowly */ @@ -1820,42 +1968,42 @@ boolean ini; * collision with level borders? * 1 = horizontal border, 2 = vertical, 3 = corner */ - if (b->x <= bxmin) + if (b->x <= gbxmin) colli |= 2; - if (b->y <= bymin) + if (b->y <= gbymin) colli |= 1; - if ((int) (b->x + b->bm[0] - 1) >= bxmax) + if ((int) (b->x + b->bm[0] - 1) >= gbxmax) colli |= 2; - if ((int) (b->y + b->bm[1] - 1) >= bymax) + if ((int) (b->y + b->bm[1] - 1) >= gbymax) colli |= 1; - if (b->x < bxmin) { - pline("bubble xmin: x = %d, xmin = %d", b->x, bxmin); - b->x = bxmin; + if (b->x < gbxmin) { + pline("bubble xmin: x = %d, xmin = %d", b->x, gbxmin); + b->x = gbxmin; } - if (b->y < bymin) { - pline("bubble ymin: y = %d, ymin = %d", b->y, bymin); - b->y = bymin; + if (b->y < gbymin) { + pline("bubble ymin: y = %d, ymin = %d", b->y, gbymin); + b->y = gbymin; } - if ((int) (b->x + b->bm[0] - 1) > bxmax) { + if ((int) (b->x + b->bm[0] - 1) > gbxmax) { pline("bubble xmax: x = %d, xmax = %d", b->x + b->bm[0] - 1, - bxmax); - b->x = bxmax - b->bm[0] + 1; + gbxmax); + b->x = gbxmax - b->bm[0] + 1; } - if ((int) (b->y + b->bm[1] - 1) > bymax) { + if ((int) (b->y + b->bm[1] - 1) > gbymax) { pline("bubble ymax: y = %d, ymax = %d", b->y + b->bm[1] - 1, - bymax); - b->y = bymax - b->bm[1] + 1; + gbymax); + b->y = gbymax - b->bm[1] + 1; } /* bounce if we're trying to move off the border */ - if (b->x == bxmin && dx < 0) + if (b->x == gbxmin && dx < 0) dx = -dx; - if (b->x + b->bm[0] - 1 == bxmax && dx > 0) + if (b->x + b->bm[0] - 1 == gbxmax && dx > 0) dx = -dx; - if (b->y == bymin && dy < 0) + if (b->y == gbymin && dy < 0) dy = -dy; - if (b->y + b->bm[1] - 1 == bymax && dy > 0) + if (b->y + b->bm[1] - 1 == gbymax && dy > 0) dy = -dy; b->x += dx; @@ -1901,7 +2049,7 @@ boolean ini; /* mnearto() might fail. We can jump right to elemental_clog from here rather than deal_with_overcrowding() */ - if (!mnearto(mon, cons->x, cons->y, TRUE)) + if (!mnearto(mon, cons->x, cons->y, TRUE, RLOC_NOMSG)) elemental_clog(mon); break; } @@ -1914,7 +2062,7 @@ boolean ini; newsym(ux0, uy0); /* clean up old position */ if (mtmp) { - mnexto(mtmp); + mnexto(mtmp, RLOC_NOMSG); } break; } @@ -1943,6 +2091,7 @@ boolean ini; break; case 3: b->dy = -b->dy; + FALLTHROUGH; /*FALLTHRU*/ case 2: b->dx = -b->dx; @@ -1956,5 +2105,6 @@ boolean ini; } } } +#endif /* !SFCTOOL */ /*mkmaze.c*/ diff --git a/src/mkobj.c b/src/mkobj.c index 17265ff89..93f047d14 100644 --- a/src/mkobj.c +++ b/src/mkobj.c @@ -1,25 +1,32 @@ -/* NetHack 3.6 mkobj.c $NHDT-Date: 1571531889 2019/10/20 00:38:09 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.157 $ */ +/* NetHack 5.0 mkobj.c $NHDT-Date: 1764044196 2025/11/24 20:16:36 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.326 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Derek S. Ray, 2015. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -STATIC_DCL void FDECL(mkbox_cnts, (struct obj *)); -STATIC_DCL unsigned FDECL(nextoid, (struct obj *, struct obj *)); -STATIC_DCL void FDECL(maybe_adjust_light, (struct obj *, int)); -STATIC_DCL void FDECL(obj_timer_checks, (struct obj *, - XCHAR_P, XCHAR_P, int)); -STATIC_DCL void FDECL(container_weight, (struct obj *)); -STATIC_DCL struct obj *FDECL(save_mtraits, (struct obj *, struct monst *)); -STATIC_DCL void FDECL(objlist_sanity, (struct obj *, int, const char *)); -STATIC_DCL void FDECL(mon_obj_sanity, (struct monst *, const char *)); -STATIC_DCL const char *FDECL(where_name, (struct obj *)); -STATIC_DCL void FDECL(insane_object, (struct obj *, const char *, - const char *, struct monst *)); -STATIC_DCL void FDECL(check_contained, (struct obj *, const char *)); -STATIC_DCL void FDECL(check_glob, (struct obj *, const char *)); -STATIC_DCL void FDECL(sanity_check_worn, (struct obj *)); +staticfn boolean may_generate_eroded(struct obj *); +staticfn void mkobj_erosions(struct obj *); +staticfn void mkbox_cnts(struct obj *); +staticfn unsigned nextoid(struct obj *, struct obj *); +staticfn void mksobj_init(struct obj **, boolean); +staticfn int item_on_ice(struct obj *); +staticfn void shrinking_glob_gone(struct obj *); +staticfn void obj_timer_checks(struct obj *, coordxy, coordxy, int); +staticfn void dealloc_obj_real(struct obj *); +staticfn struct obj *save_mtraits(struct obj *, struct monst *); +staticfn void objlist_sanity(struct obj *, int, const char *); +staticfn void shop_obj_sanity(struct obj *, const char *); +staticfn void mon_obj_sanity(struct monst *, const char *); +staticfn void insane_obj_bits(struct obj *, struct monst *); +staticfn boolean nomerge_exception(struct obj *); +staticfn const char *where_name(struct obj *); +staticfn void insane_object(struct obj *, const char *, const char *, + struct monst *); +staticfn void check_contained(struct obj *, const char *); +staticfn void check_glob(struct obj *, const char *); +staticfn void sanity_check_worn(struct obj *); +staticfn void init_oextra(struct oextra *); struct icp { int iprob; /* probability of an item type */ @@ -27,10 +34,10 @@ struct icp { }; static const struct icp mkobjprobs[] = { { 10, WEAPON_CLASS }, - { 10, ARMOR_CLASS }, + { 11, ARMOR_CLASS }, { 20, FOOD_CLASS }, { 8, TOOL_CLASS }, - { 8, GEM_CLASS }, + { 7, GEM_CLASS }, { 16, POTION_CLASS }, { 16, SCROLL_CLASS }, { 4, SPBOOK_CLASS }, @@ -67,37 +74,36 @@ static const struct icp hellprobs[] = { { 20, WEAPON_CLASS }, { 8, RING_CLASS }, { 4, AMULET_CLASS } }; +static const struct oextra zerooextra = DUMMY; + +staticfn void +init_oextra(struct oextra *oex) +{ + *oex = zerooextra; +} + struct oextra * -newoextra() +newoextra(void) { struct oextra *oextra; oextra = (struct oextra *) alloc(sizeof (struct oextra)); - oextra->oname = 0; - oextra->omonst = 0; - oextra->omid = 0; - oextra->olong = 0; - oextra->omailcmd = 0; + init_oextra(oextra); return oextra; } void -dealloc_oextra(o) -struct obj *o; +dealloc_oextra(struct obj *o) { struct oextra *x = o->oextra; if (x) { if (x->oname) - free((genericptr_t) x->oname); + free((genericptr_t) x->oname), x->oname = 0; if (x->omonst) - free_omonst(o); /* 'o' rather than 'x' */ - if (x->omid) - free((genericptr_t) x->omid); - if (x->olong) - free((genericptr_t) x->olong); + free_omonst(o); /* note: pass 'o' rather than 'x' */ if (x->omailcmd) - free((genericptr_t) x->omailcmd); + free((genericptr_t) x->omailcmd), x->omailcmd = 0; free((genericptr_t) x); o->oextra = (struct oextra *) 0; @@ -105,8 +111,7 @@ struct obj *o; } void -newomonst(otmp) -struct obj *otmp; +newomonst(struct obj *otmp) { if (!otmp->oextra) otmp->oextra = newoextra(); @@ -114,14 +119,13 @@ struct obj *otmp; if (!OMONST(otmp)) { struct monst *m = newmonst(); - *m = zeromonst; + *m = cg.zeromonst; OMONST(otmp) = m; } } void -free_omonst(otmp) -struct obj *otmp; +free_omonst(struct obj *otmp) { if (otmp->oextra) { struct monst *m = OMONST(otmp); @@ -136,76 +140,91 @@ struct obj *otmp; } void -newomid(otmp) -struct obj *otmp; +newomid(struct obj *otmp) { if (!otmp->oextra) otmp->oextra = newoextra(); - if (!OMID(otmp)) { - OMID(otmp) = (unsigned *) alloc(sizeof (unsigned)); - (void) memset((genericptr_t) OMID(otmp), 0, sizeof (unsigned)); - } + OMID(otmp) = 0; } void -free_omid(otmp) -struct obj *otmp; +free_omid(struct obj *otmp) { - if (otmp->oextra && OMID(otmp)) { - free((genericptr_t) OMID(otmp)); - OMID(otmp) = (unsigned *) 0; - } + OMID(otmp) = 0; } void -newolong(otmp) -struct obj *otmp; +new_omailcmd(struct obj *otmp, const char *response_cmd) { if (!otmp->oextra) otmp->oextra = newoextra(); - if (!OLONG(otmp)) { - OLONG(otmp) = (long *) alloc(sizeof (long)); - (void) memset((genericptr_t) OLONG(otmp), 0, sizeof (long)); - } + if (OMAILCMD(otmp)) + free_omailcmd(otmp); + OMAILCMD(otmp) = dupstr(response_cmd); } void -free_olong(otmp) -struct obj *otmp; +free_omailcmd(struct obj *otmp) { - if (otmp->oextra && OLONG(otmp)) { - free((genericptr_t) OLONG(otmp)); - OLONG(otmp) = (long *) 0; + if (otmp->oextra && OMAILCMD(otmp)) { + free((genericptr_t) OMAILCMD(otmp)); + OMAILCMD(otmp) = (char *) 0; } } -void -new_omailcmd(otmp, response_cmd) -struct obj *otmp; -const char *response_cmd; +/* can object be generated eroded? */ +staticfn boolean +may_generate_eroded(struct obj *otmp) { - if (!otmp->oextra) - otmp->oextra = newoextra(); - if (OMAILCMD(otmp)) - free_omailcmd(otmp); - OMAILCMD(otmp) = dupstr(response_cmd); + /* initial hero inventory */ + if (svm.moves <= 1 && !gi.in_mklev) + return FALSE; + /* already erodeproof or cannot be eroded */ + if (otmp->oerodeproof || !erosion_matters(otmp) || !is_damageable(otmp)) + return FALSE; + /* part of a monster's body and produced when it dies */ + if (otmp->otyp == WORM_TOOTH || otmp->otyp == UNICORN_HORN) + return FALSE; + /* artifacts cannot be generated eroded */ + if (otmp->oartifact) + return FALSE; + return TRUE; } -void -free_omailcmd(otmp) -struct obj *otmp; +/* random chance of applying erosions/grease to object */ +staticfn void +mkobj_erosions(struct obj *otmp) { - if (otmp->oextra && OMAILCMD(otmp)) { - free((genericptr_t) OMAILCMD(otmp)); - OMAILCMD(otmp) = (char *) 0; + if (may_generate_eroded(otmp)) { + /* A small fraction of non-artifact items will generate eroded or + * possibly erodeproof. An item that generates eroded will never be + * erodeproof, and vice versa. */ + if (!rn2(100)) { + otmp->oerodeproof = 1; + } else { + if (!rn2(80) && (is_flammable(otmp) || is_rustprone(otmp) + || is_crackable(otmp))) { + do { + otmp->oeroded++; + } while (otmp->oeroded < 3 && !rn2(9)); + } + if (!rn2(80) && (is_rottable(otmp) || is_corrodeable(otmp))) { + do { + otmp->oeroded2++; + } while (otmp->oeroded2 < 3 && !rn2(9)); + } + } + /* and an extremely small fraction of the time, erodable items + * will generate greased */ + if (!rn2(1000)) + otmp->greased = 1; } } +/* make a random object of class 'let' at a specific location; + 'let' might be random class; place_object() will validate */ struct obj * -mkobj_at(let, x, y, artif) -char let; -int x, y; -boolean artif; +mkobj_at(char let, coordxy x, coordxy y, boolean artif) { struct obj *otmp; @@ -214,10 +233,12 @@ boolean artif; return otmp; } +/* make a specific object at a specific location */ struct obj * -mksobj_at(otyp, x, y, init, artif) -int otyp, x, y; -boolean init, artif; +mksobj_at( + int otyp, + coordxy x, coordxy y, + boolean init, boolean artif) { struct obj *otmp; @@ -226,29 +247,29 @@ boolean init, artif; return otmp; } + +/* used for extra orctown loot */ struct obj * -mksobj_migr_to_species(otyp, mflags2, init, artif) -int otyp; -unsigned mflags2; -boolean init, artif; +mksobj_migr_to_species( + int otyp, + unsigned mflags2, + boolean init, boolean artif) { struct obj *otmp; otmp = mksobj(otyp, init, artif); add_to_migration(otmp); otmp->owornmask = (long) MIGR_TO_SPECIES; - otmp->corpsenm = mflags2; + otmp->migr_species = mflags2; return otmp; } /* mkobj(): select a type of item from a class, use mksobj() to create it; result is always non-Null */ struct obj * -mkobj(oclass, artif) -char oclass; -boolean artif; +mkobj(int oclass, boolean artif) { - int tprob, i, prob = rnd(1000); + int tprob, i, prob; if (oclass == RANDOM_CLASS) { const struct icp *iprobs = Is_rogue_level(&u.uz) @@ -257,26 +278,33 @@ boolean artif; : (const struct icp *) mkobjprobs; for (tprob = rnd(100); (tprob -= iprobs->iprob) > 0; iprobs++) - ; + continue; oclass = iprobs->iclass; } - i = bases[(int) oclass]; - while ((prob -= objects[i].oc_prob) > 0) - i++; + if (oclass == SPBOOK_no_NOVEL) { + i = rnd_class(svb.bases[SPBOOK_CLASS], SPE_BLANK_PAPER); + oclass = SPBOOK_CLASS; /* for sanity check below */ + } else { + prob = rnd(go.oclass_prob_totals[oclass]); + i = svb.bases[oclass]; + while ((prob -= objects[i].oc_prob) > 0) + ++i; + } - if (objects[i].oc_class != oclass || !OBJ_NAME(objects[i])) - panic("probtype error, oclass=%d i=%d", (int) oclass, i); + if (objects[i].oc_class != oclass || !OBJ_NAME(objects[i])) { + impossible("probtype error, oclass=%d i=%d", (int) oclass, i); + i = svb.bases[oclass]; + } return mksobj(i, TRUE, artif); } -STATIC_OVL void -mkbox_cnts(box) -struct obj *box; +staticfn void +mkbox_cnts(struct obj *box) { - register int n; - register struct obj *otmp; + int n; + struct obj *otmp; box->cobj = (struct obj *) 0; @@ -293,10 +321,11 @@ struct obj *box; case SACK: case OILSKIN_SACK: /* initial inventory: sack starts out empty */ - if (moves <= 1 && !in_mklev) { + if (svm.moves <= 1 && !gi.in_mklev) { n = 0; break; } + FALLTHROUGH; /*FALLTHRU*/ case BAG_OF_HOLDING: n = 1; @@ -308,7 +337,7 @@ struct obj *box; for (n = rn2(n + 1); n > 0; n--) { if (box->otyp == ICE_BOX) { - otmp = mksobj(CORPSE, TRUE, TRUE); + otmp = mksobj(CORPSE, TRUE, FALSE); /* Note: setting age to 0 is correct. Age has a different * from usual meaning for objects stored in ice boxes. -KAA */ @@ -316,14 +345,15 @@ struct obj *box; if (otmp->timed) { (void) stop_timer(ROT_CORPSE, obj_to_any(otmp)); (void) stop_timer(REVIVE_MON, obj_to_any(otmp)); + (void) stop_timer(SHRINK_GLOB, obj_to_any(otmp)); } } else { - register int tprob; + int tprob; const struct icp *iprobs = boxiprobs; for (tprob = rnd(100); (tprob -= iprobs->iprob) > 0; iprobs++) ; - if (!(otmp = mkobj(iprobs->iclass, TRUE))) + if (!(otmp = mkobj(iprobs->iclass, FALSE))) continue; /* handle a couple of special cases */ @@ -350,18 +380,26 @@ struct obj *box; } (void) add_to_container(box, otmp); } + /* caller will update box->owt */ } /* select a random, common monster type */ int -rndmonnum() +rndmonnum(void) +{ + return rndmonnum_adj(0, 0); +} + +/* select a random, common monster type, with adjusted difficulty */ +int +rndmonnum_adj(int minadj, int maxadj) { - register struct permonst *ptr; - register int i; + struct permonst *ptr; + int i; unsigned short excludeflags; /* Plan A: get a level-appropriate common monster */ - ptr = rndmonst(); + ptr = rndmonst_adj(minadj, maxadj); if (ptr) return monsndx(ptr); @@ -376,8 +414,7 @@ rndmonnum() } void -copy_oextra(obj2, obj1) -struct obj *obj2, *obj1; +copy_oextra(struct obj *obj2, struct obj *obj1) { if (!obj2 || !obj1 || !obj1->oextra) return; @@ -385,54 +422,47 @@ struct obj *obj2, *obj1; if (!obj2->oextra) obj2->oextra = newoextra(); if (has_oname(obj1)) - oname(obj2, ONAME(obj1)); + oname(obj2, ONAME(obj1), ONAME_SKIP_INVUPD); if (has_omonst(obj1)) { if (!OMONST(obj2)) newomonst(obj2); + assert(has_omonst(obj2)); (void) memcpy((genericptr_t) OMONST(obj2), (genericptr_t) OMONST(obj1), sizeof (struct monst)); OMONST(obj2)->mextra = (struct mextra *) 0; OMONST(obj2)->nmon = (struct monst *) 0; #if 0 - OMONST(obj2)->m_id = context.ident++; - if (OMONST(obj2)->m_id) /* ident overflowed */ - OMONST(obj2)->m_id = context.ident++; + OMONST(obj2)->m_id = next_ident(); #endif if (OMONST(obj1)->mextra) copy_mextra(OMONST(obj2), OMONST(obj1)); } + if (has_omailcmd(obj1)) { + new_omailcmd(obj2, OMAILCMD(obj1)); + } if (has_omid(obj1)) { if (!OMID(obj2)) newomid(obj2); - (void) memcpy((genericptr_t) OMID(obj2), (genericptr_t) OMID(obj1), - sizeof (unsigned)); - } - if (has_olong(obj1)) { - if (!OLONG(obj2)) - newolong(obj2); - (void) memcpy((genericptr_t) OLONG(obj2), (genericptr_t) OLONG(obj1), - sizeof (long)); - } - if (has_omailcmd(obj1)) { - new_omailcmd(obj2, OMAILCMD(obj1)); + OMID(obj2) = OMID(obj1); } } /* - * Split obj so that it gets size gets reduced by num. The quantity num is + * Split stack so that its size gets reduced by num. The quantity num is * put in the object structure delivered by this call. The returned object * has its wornmask cleared and is positioned just following the original * in the nobj chain (and nexthere chain when on the floor). */ struct obj * -splitobj(obj, num) -struct obj *obj; -long num; +splitobj(struct obj *obj, long num) { struct obj *otmp; + /* can't split containers */ if (obj->cobj || num <= 0L || obj->quan <= num) - panic("splitobj"); /* can't split containers */ + panic("splitobj [cobj=%s num=%ld quan=%ld]", + obj->cobj ? "non-empty container" : "(null)", num, obj->quan); + otmp = newobj(); *otmp = *obj; /* copies whole structure */ otmp->oextra = (struct oextra *) 0; @@ -444,19 +474,27 @@ long num; obj->owt = weight(obj); otmp->quan = num; otmp->owt = weight(otmp); /* -= obj->owt ? */ + otmp->lua_ref_cnt = 0; + otmp->pickup_prev = 0; - context.objsplit.parent_oid = obj->o_id; - context.objsplit.child_oid = otmp->o_id; + svc.context.objsplit.parent_oid = obj->o_id; + svc.context.objsplit.child_oid = otmp->o_id; obj->nobj = otmp; - /* Only set nexthere when on the floor, nexthere is also used */ - /* as a back pointer to the container object when contained. */ + /* Only set nexthere when on the floor; nexthere is also used + as a back pointer to the container object when contained. + For either case, otmp's nexthere pointer is already pointing + at the right thing. */ if (obj->where == OBJ_FLOOR) - obj->nexthere = otmp; - copy_oextra(otmp, obj); - if (has_omid(otmp)) - free_omid(otmp); /* only one association with m_id*/ + obj->nexthere = otmp; /* insert into chain: obj -> otmp -> next */ + /* lua isn't tracking the split off portion even if it happens to + be tracking the original */ + if (otmp->where == OBJ_LUAFREE) + otmp->where = OBJ_FREE; if (obj->unpaid) splitbill(obj, otmp); + copy_oextra(otmp, obj); + if (has_omid(otmp)) + free_omid(otmp); /* only one association with m_id */ if (obj->timed) obj_split_timers(obj, otmp); if (obj_sheds_light(obj)) @@ -464,14 +502,41 @@ long num; return otmp; } +/* return the value of context.ident and then increment it to be ready for + its next use; used to be simple += 1 so that every value from 1 to N got + used but now has a random increase that skips half of potential values */ +unsigned +next_ident(void) +{ + unsigned res = svc.context.ident; + + /* +rnd(2): originally just +1; changed to rnd() to avoid potential + exploit of player using #adjust to split an object stack in a manner + that makes most recent ident%2 known; since #adjust takes no time, + no intervening activity like random creation of a new monster will + take place before next user command; with former +1, o_id%2 of the + next object to be created was knowable and player could make a wish + under controlled circumstances for an item that is affected by the + low bits of its obj->o_id [particularly helm of opposite alignment] */ + svc.context.ident += rnd(2); /* ready for next new object or monster */ + + /* if ident has wrapped to 0, force it to be non-zero; if/when it + ever wraps past 0 (unlikely, but possible on a configuration which + uses 16-bit 'int'), just live with that and hope no o_id conflicts + between objects or m_id conflicts between monsters arise */ + if (!svc.context.ident) + svc.context.ident = rnd(2) + 1; /* id 1 is reserved */ + + return res; +} + /* when splitting a stack that has o_id-based shop prices, pick an o_id value for the new stack that will maintain the same price */ -STATIC_OVL unsigned -nextoid(oldobj, newobj) -struct obj *oldobj, *newobj; +staticfn unsigned +nextoid(struct obj *oldobj, struct obj *newobj) { int olddif, newdif, trylimit = 256; /* limit of 4 suffices at present */ - unsigned oid = context.ident - 1; /* loop increment will reverse -1 */ + unsigned oid = svc.context.ident - 1; /* loop increment will reverse -1 */ olddif = oid_price_adjustment(oldobj, oldobj->o_id); do { @@ -480,15 +545,15 @@ struct obj *oldobj, *newobj; ++oid; newdif = oid_price_adjustment(newobj, oid); } while (newdif != olddif && --trylimit >= 0); - context.ident = oid + 1; /* ready for next new object */ - return oid; + svc.context.ident = oid; /* update 'last ident used' */ + (void) next_ident(); /* increment context.ident for next use */ + return oid; /* caller will use this ident */ } /* try to find the stack obj was split from, then merge them back together; returns the combined object if unsplit is successful, null otherwise */ struct obj * -unsplitobj(obj) -struct obj *obj; +unsplitobj(struct obj *obj) { unsigned target_oid = 0; struct obj *oparent = 0, *ochild = 0, *list = 0; @@ -510,7 +575,7 @@ struct obj *obj; default: return (struct obj *) 0; case OBJ_INVENT: - list = invent; + list = gi.invent; break; case OBJ_MINVENT: list = obj->ocarry->minvent; @@ -521,17 +586,17 @@ struct obj *obj; } /* first try the expected case; obj is split from another stack */ - if (obj->o_id == context.objsplit.child_oid) { + if (obj->o_id == svc.context.objsplit.child_oid) { /* parent probably precedes child and will require list traversal */ ochild = obj; - target_oid = context.objsplit.parent_oid; + target_oid = svc.context.objsplit.parent_oid; if (obj->nobj && obj->nobj->o_id == target_oid) oparent = obj->nobj; - } else if (obj->o_id == context.objsplit.parent_oid) { + } else if (obj->o_id == svc.context.objsplit.parent_oid) { /* alternate scenario: another stack was split from obj; child probably follows parent and will be found here */ oparent = obj; - target_oid = context.objsplit.child_oid; + target_oid = svc.context.objsplit.child_oid; if (obj->nobj && obj->nobj->o_id == target_oid) ochild = obj->nobj; } @@ -558,9 +623,9 @@ struct obj *obj; /* reset splitobj()/unsplitobj() context */ void -clear_splitobjs() +clear_splitobjs(void) { - context.objsplit.parent_oid = context.objsplit.child_oid = 0; + svc.context.objsplit.parent_oid = svc.context.objsplit.child_oid = 0; } /* @@ -569,13 +634,11 @@ clear_splitobjs() * the caller to provide a valid context for the swap. When done, obj will * still exist, but not on any chain. * - * Note: Don't use use obj_extract_self() -- we are doing an in-place swap, + * Note: Don't use obj_extract_self() -- we are doing an in-place swap, * not actually moving something. */ void -replace_object(obj, otmp) -struct obj *obj; -struct obj *otmp; +replace_object(struct obj *obj, struct obj *otmp) { otmp->where = obj->where; switch (obj->where) { @@ -585,7 +648,7 @@ struct obj *otmp; case OBJ_INVENT: otmp->nobj = obj->nobj; obj->nobj = otmp; - extract_nobj(obj, &invent); + extract_nobj(obj, &gi.invent); break; case OBJ_CONTAINED: otmp->nobj = obj->nobj; @@ -607,7 +670,7 @@ struct obj *otmp; obj->nobj = otmp; obj->nexthere = otmp; extract_nobj(obj, &fobj); - extract_nexthere(obj, &level.objects[obj->ox][obj->oy]); + extract_nexthere(obj, &svl.level.objects[obj->ox][obj->oy]); break; default: panic("replace_object: obj position"); @@ -618,8 +681,7 @@ struct obj *otmp; /* is 'obj' inside a container whose contents aren't known? if so, return the outermost container meeting that criterium */ struct obj * -unknwn_contnr_contents(obj) -struct obj *obj; +unknwn_contnr_contents(struct obj *obj) { struct obj *result = 0, *parent; @@ -647,36 +709,33 @@ struct obj *obj; * usage of an object. */ void -bill_dummy_object(otmp) -register struct obj *otmp; +bill_dummy_object(struct obj *otmp) { - register struct obj *dummy; + struct obj *dummy; long cost = 0L; if (otmp->unpaid) { - cost = unpaid_cost(otmp, FALSE); + cost = unpaid_cost(otmp, COST_SINGLEOBJ); subfrombill(otmp, shop_keeper(*u.ushops)); } dummy = newobj(); *dummy = *otmp; dummy->oextra = (struct oextra *) 0; dummy->where = OBJ_FREE; - dummy->o_id = context.ident++; - if (!dummy->o_id) - dummy->o_id = context.ident++; /* ident overflowed */ + dummy->o_id = nextoid(otmp, dummy); dummy->timed = 0; copy_oextra(dummy, otmp); if (has_omid(dummy)) - free_omid(dummy); /* only one association with m_id*/ + free_omid(dummy); /* only one association with m_id */ if (Is_candle(dummy)) dummy->lamplit = 0; dummy->owornmask = 0L; /* dummy object is not worn */ addtobill(dummy, FALSE, TRUE, TRUE); - if (cost) + if (cost && dummy->where != OBJ_DELETED) alter_cost(dummy, -cost); /* no_charge is only valid for some locations */ - otmp->no_charge = - (otmp->where == OBJ_FLOOR || otmp->where == OBJ_CONTAINED) ? 1 : 0; + otmp->no_charge = (otmp->where == OBJ_FLOOR + || otmp->where == OBJ_CONTAINED) ? 1 : 0; otmp->unpaid = 0; return; } @@ -685,16 +744,14 @@ register struct obj *otmp; static const char *const alteration_verbs[] = { "cancel", "drain", "uncharge", "unbless", "uncurse", "disenchant", "degrade", "dilute", "erase", "burn", "neutralize", "destroy", "splatter", - "bite", "open", "break the lock on", "rust", "rot", "tarnish" + "bite", "open", "break the lock on", "rust", "rot", "tarnish", "crack", }; /* possibly bill for an object which the player has just modified */ void -costly_alteration(obj, alter_type) -struct obj *obj; -int alter_type; +costly_alteration(struct obj *obj, int alter_type) { - xchar ox, oy; + coordxy ox, oy; char objroom; boolean learn_bknown; const char *those, *them; @@ -743,6 +800,9 @@ int alter_type; case OBJ_INVENT: if (learn_bknown) set_bknown(obj, 1); + if (shkp) { + SetVoice(shkp, 0, 80, 0); + } verbalize("You %s %s %s, you pay for %s!", alteration_verbs[alter_type], those, simpleonames(obj), them); @@ -752,6 +812,9 @@ int alter_type; if (learn_bknown) obj->bknown = 1; /* ok to bypass set_bknown() here */ if (costly_spot(u.ux, u.uy) && objroom == *u.ushops) { + if (shkp) { + SetVoice(shkp, 0, 80, 0); + } verbalize("You %s %s, you pay for %s!", alteration_verbs[alter_type], those, them); bill_dummy_object(obj); @@ -764,311 +827,376 @@ int alter_type; static const char dknowns[] = { WAND_CLASS, RING_CLASS, POTION_CLASS, SCROLL_CLASS, GEM_CLASS, SPBOOK_CLASS, - WEAPON_CLASS, TOOL_CLASS, 0 }; + WEAPON_CLASS, TOOL_CLASS, VENOM_CLASS, 0 }; -/* mksobj(): create a specific type of object; result it always non-Null */ -struct obj * -mksobj(otyp, init, artif) -int otyp; -boolean init; -boolean artif; +/* set obj->dknown to 0 for most types of objects, to 1 otherwise; + split off from unknow_object() */ +void +clear_dknown(struct obj *obj) +{ + /* note: this is an unobserving not an observing, so don't call + observe_object even if dknown is being set to 1 */ + obj->dknown = strchr(dknowns, obj->oclass) ? 0 : 1; + if ((obj->otyp >= ELVEN_SHIELD && obj->otyp <= ORCISH_SHIELD) + || obj->otyp == SHIELD_OF_REFLECTION + || objects[obj->otyp].oc_merge) + obj->dknown = 0; + /* globs always have dknown flag set (to maximize merging) but for new + object, globby flag won't be set yet so isn't available to check */ + if (Is_pudding(obj)) + obj->dknown = 1; +} + +/* some init for a brand new object, or partial re-init when hero loses + potentially known info about an object (called when an unseen monster + picks up or uses it); moved from invent.c to here for access to dknowns */ +void +unknow_object(struct obj *obj) { - int mndx, tryct; - struct obj *otmp; - char let = objects[otyp].oc_class; - - otmp = newobj(); - *otmp = zeroobj; - otmp->age = monstermoves; - otmp->o_id = context.ident++; - if (!otmp->o_id) - otmp->o_id = context.ident++; /* ident overflowed */ - otmp->quan = 1L; - otmp->oclass = let; - otmp->otyp = otyp; - otmp->where = OBJ_FREE; - otmp->dknown = index(dknowns, let) ? 0 : 1; - if ((otmp->otyp >= ELVEN_SHIELD && otmp->otyp <= ORCISH_SHIELD) - || otmp->otyp == SHIELD_OF_REFLECTION - || objects[otmp->otyp].oc_merge) - otmp->dknown = 0; - if (!objects[otmp->otyp].oc_uses_known) - otmp->known = 1; - otmp->lknown = 0; - otmp->cknown = 0; - otmp->corpsenm = NON_PM; + clear_dknown(obj); /* obj->dknown = 0; */ - if (init) { - switch (let) { - case WEAPON_CLASS: - otmp->quan = is_multigen(otmp) ? (long) rn1(6, 6) : 1L; - if (!rn2(11)) { - otmp->spe = rne(3); - otmp->blessed = rn2(2); - } else if (!rn2(10)) { - curse(otmp); - otmp->spe = -rne(3); - } else - blessorcurse(otmp, 10); - if (is_poisonable(otmp) && !rn2(100)) - otmp->opoisoned = 1; + obj->bknown = obj->rknown = 0; + obj->cknown = obj->lknown = 0; + obj->tknown = 0; + /* for an existing object, awareness of charges or enchantment has + gone poof... [object types which don't use the known flag have + it set True for some reason] */ + obj->known = objects[obj->otyp].oc_uses_known ? 0 : 1; +} - if (artif && !rn2(20)) - otmp = mk_artifact(otmp, (aligntyp) A_NONE); +/* do some initialization to newly created object; otyp must already be set */ +staticfn void +mksobj_init(struct obj **obj, boolean artif) +{ + int mndx, tryct; + struct obj *otmp = *obj; + char let = objects[otmp->otyp].oc_class; + + switch (let) { + case WEAPON_CLASS: + otmp->quan = is_multigen(otmp) ? (long) rn1(6, 6) : 1L; + if (!rn2(11)) { + otmp->spe = rne(3); + otmp->blessed = rn2(2); + } else if (!rn2(10)) { + curse(otmp); + otmp->spe = -rne(3); + } else + blessorcurse(otmp, 10); + if (is_poisonable(otmp) && !rn2(100)) + otmp->opoisoned = 1; + + if (artif && !rn2(20 + (10 * nartifact_exist()))) { + /* mk_artifact() with otmp and A_NONE will never return NULL */ + otmp = mk_artifact(otmp, (aligntyp) A_NONE, 99, TRUE); + *obj = otmp; + } + break; + case FOOD_CLASS: + otmp->oeaten = 0; + switch (otmp->otyp) { + case CORPSE: + /* possibly overridden by mkcorpstat() */ + tryct = 50; + do + otmp->corpsenm = undead_to_corpse(rndmonnum()); + while ((svm.mvitals[otmp->corpsenm].mvflags & G_NOCORPSE) + && (--tryct > 0)); + if (tryct == 0) { + /* perhaps rndmonnum() only wants to make G_NOCORPSE + monsters on this svl.level; create an adventurer's + corpse instead, then */ + otmp->corpsenm = PM_HUMAN; + } + /* timer set below */ break; - case FOOD_CLASS: - otmp->oeaten = 0; - switch (otmp->otyp) { - case CORPSE: - /* possibly overridden by mkcorpstat() */ - tryct = 50; - do - otmp->corpsenm = undead_to_corpse(rndmonnum()); - while ((mvitals[otmp->corpsenm].mvflags & G_NOCORPSE) - && (--tryct > 0)); - if (tryct == 0) { - /* perhaps rndmonnum() only wants to make G_NOCORPSE - monsters on this level; create an adventurer's - corpse instead, then */ - otmp->corpsenm = PM_HUMAN; - } - /* timer set below */ - break; - case EGG: - otmp->corpsenm = NON_PM; /* generic egg */ - if (!rn2(3)) - for (tryct = 200; tryct > 0; --tryct) { - mndx = can_be_hatched(rndmonnum()); - if (mndx != NON_PM && !dead_species(mndx, TRUE)) { - otmp->corpsenm = mndx; /* typed egg */ - break; - } - } - /* timer set below */ - break; - case TIN: - otmp->corpsenm = NON_PM; /* empty (so far) */ - if (!rn2(6)) - set_tin_variety(otmp, SPINACH_TIN); - else - for (tryct = 200; tryct > 0; --tryct) { - mndx = undead_to_corpse(rndmonnum()); - if (mons[mndx].cnutrit - && !(mvitals[mndx].mvflags & G_NOCORPSE)) { - otmp->corpsenm = mndx; - set_tin_variety(otmp, RANDOM_TIN); - break; - } + case EGG: + otmp->corpsenm = NON_PM; /* generic egg */ + if (!rn2(3)) + for (tryct = 200; tryct > 0; --tryct) { + mndx = can_be_hatched(rndmonnum()); + if (mndx != NON_PM && !dead_species(mndx, TRUE)) { + otmp->corpsenm = mndx; /* typed egg */ + break; } - blessorcurse(otmp, 10); - break; - case SLIME_MOLD: - otmp->spe = context.current_fruit; - flags.made_fruit = TRUE; - break; - case KELP_FROND: - otmp->quan = (long) rnd(2); - break; - } - if (Is_pudding(otmp)) { - otmp->quan = 1L; /* for emphasis; glob quantity is always 1 */ - otmp->globby = 1; - otmp->known = otmp->dknown = 1; - otmp->corpsenm = PM_GRAY_OOZE - + (otmp->otyp - GLOB_OF_GRAY_OOZE); - } else { - if (otmp->otyp != CORPSE && otmp->otyp != MEAT_RING - && otmp->otyp != KELP_FROND && !rn2(6)) { - otmp->quan = 2L; } - } + /* timer set below */ break; - case GEM_CLASS: - otmp->corpsenm = 0; /* LOADSTONE hack */ - if (otmp->otyp == LOADSTONE) - curse(otmp); - else if (otmp->otyp == ROCK) - otmp->quan = (long) rn1(6, 6); - else if (otmp->otyp != LUCKSTONE && !rn2(6)) - otmp->quan = 2L; + case TIN: + otmp->corpsenm = NON_PM; /* empty (so far) */ + if (!rn2(6)) + set_tin_variety(otmp, SPINACH_TIN); else - otmp->quan = 1L; + for (tryct = 200; tryct > 0; --tryct) { + mndx = undead_to_corpse(rndmonnum()); + if (mons[mndx].cnutrit + && !(svm.mvitals[mndx].mvflags & G_NOCORPSE)) { + otmp->corpsenm = mndx; + set_tin_variety(otmp, RANDOM_TIN); + break; + } + } + blessorcurse(otmp, 10); break; - case TOOL_CLASS: - switch (otmp->otyp) { - case TALLOW_CANDLE: - case WAX_CANDLE: - otmp->spe = 1; - otmp->age = 20L * /* 400 or 200 */ - (long) objects[otmp->otyp].oc_cost; - otmp->lamplit = 0; - otmp->quan = 1L + (long) (rn2(2) ? rn2(7) : 0); - blessorcurse(otmp, 5); - break; - case BRASS_LANTERN: - case OIL_LAMP: - otmp->spe = 1; - otmp->age = (long) rn1(500, 1000); - otmp->lamplit = 0; - blessorcurse(otmp, 5); - break; - case MAGIC_LAMP: - otmp->spe = 1; - otmp->lamplit = 0; - blessorcurse(otmp, 2); - break; - case CHEST: - case LARGE_BOX: - otmp->olocked = !!(rn2(5)); - otmp->otrapped = !(rn2(10)); - /*FALLTHRU*/ - case ICE_BOX: - case SACK: - case OILSKIN_SACK: - case BAG_OF_HOLDING: - mkbox_cnts(otmp); - break; - case EXPENSIVE_CAMERA: - case TINNING_KIT: - case MAGIC_MARKER: - otmp->spe = rn1(70, 30); - break; - case CAN_OF_GREASE: - otmp->spe = rnd(25); - blessorcurse(otmp, 10); - break; - case CRYSTAL_BALL: - otmp->spe = rnd(5); - blessorcurse(otmp, 2); - break; - case HORN_OF_PLENTY: - case BAG_OF_TRICKS: - otmp->spe = rnd(20); - break; - case FIGURINE: - tryct = 0; - do - otmp->corpsenm = rndmonnum(); - while (is_human(&mons[otmp->corpsenm]) && tryct++ < 30); - blessorcurse(otmp, 4); - break; - case BELL_OF_OPENING: - otmp->spe = 3; - break; - case MAGIC_FLUTE: - case MAGIC_HARP: - case FROST_HORN: - case FIRE_HORN: - case DRUM_OF_EARTHQUAKE: - otmp->spe = rn1(5, 4); - break; + case SLIME_MOLD: + otmp->spe = svc.context.current_fruit; + flags.made_fruit = TRUE; + break; + case KELP_FROND: + otmp->quan = (long) rnd(2); + break; + case CANDY_BAR: + /* set otmp->spe */ + assign_candy_wrapper(otmp); + break; + default: + break; + } + if (Is_pudding(otmp)) { + otmp->globby = 1; + /* for emphasis; glob quantity is always 1 and weight varies + when other globs coalesce with it or this one shrinks */ + otmp->quan = 1L; + /* 5.0: globs in 3.6.x left owt as 0 and let weight() fix + that up during 'obj->owt = weight(obj)' below, but now + we initialize glob->owt explicitly so weight() doesn't + need to perform any fix up and returns glob->owt as-is */ + otmp->owt = objects[otmp->otyp].oc_weight; + /* dknown, but not observed */ + otmp->known = otmp->dknown = 1; + otmp->corpsenm = PM_GRAY_OOZE + (otmp->otyp - GLOB_OF_GRAY_OOZE); + start_glob_timeout(otmp, 0L); + } else { + if (otmp->otyp != CORPSE && otmp->otyp != MEAT_RING + && otmp->otyp != KELP_FROND && !rn2(6)) { + otmp->quan = 2L; } + } + break; + case GEM_CLASS: + otmp->corpsenm = 0; /* LOADSTONE hack */ + if (otmp->otyp == LOADSTONE) + curse(otmp); + else if (otmp->otyp == ROCK) + otmp->quan = (long) rn1(6, 6); + else if (otmp->otyp != LUCKSTONE && !rn2(6)) + otmp->quan = 2L; + else + otmp->quan = 1L; + break; + case TOOL_CLASS: + switch (otmp->otyp) { + case TALLOW_CANDLE: + case WAX_CANDLE: + otmp->spe = 1; + otmp->age = 20L * /* 400 or 200 */ + (long) objects[otmp->otyp].oc_cost; + otmp->lamplit = 0; + otmp->quan = 1L + (long) (rn2(2) ? rn2(7) : 0); + blessorcurse(otmp, 5); break; - case AMULET_CLASS: - if (otmp->otyp == AMULET_OF_YENDOR) - context.made_amulet = TRUE; - if (rn2(10) && (otmp->otyp == AMULET_OF_STRANGULATION - || otmp->otyp == AMULET_OF_CHANGE - || otmp->otyp == AMULET_OF_RESTFUL_SLEEP)) { - curse(otmp); - } else - blessorcurse(otmp, 10); + case BRASS_LANTERN: + case OIL_LAMP: + otmp->spe = 1; + otmp->age = (long) rn1(500, 1000); + otmp->lamplit = 0; + blessorcurse(otmp, 5); break; - case VENOM_CLASS: - case CHAIN_CLASS: - case BALL_CLASS: + case MAGIC_LAMP: + otmp->spe = 1; + otmp->lamplit = 0; + blessorcurse(otmp, 2); break; - case POTION_CLASS: /* note: potions get some additional init below */ - case SCROLL_CLASS: -#ifdef MAIL - if (otmp->otyp != SCR_MAIL) -#endif - blessorcurse(otmp, 4); + case CHEST: + case LARGE_BOX: + otmp->olocked = !!(rn2(5)); + otmp->otrapped = !(rn2(10)); + otmp->tknown = otmp->otrapped && !rn2(100); /* obvious trap */ + FALLTHROUGH; + /*FALLTHRU*/ + case ICE_BOX: + case SACK: + case OILSKIN_SACK: + case BAG_OF_HOLDING: + mkbox_cnts(otmp); break; - case SPBOOK_CLASS: - otmp->spestudied = 0; - blessorcurse(otmp, 17); + case EXPENSIVE_CAMERA: + case TINNING_KIT: + case MAGIC_MARKER: + otmp->spe = rn1(70, 30); break; - case ARMOR_CLASS: - if (rn2(10) - && (otmp->otyp == FUMBLE_BOOTS - || otmp->otyp == LEVITATION_BOOTS - || otmp->otyp == HELM_OF_OPPOSITE_ALIGNMENT - || otmp->otyp == GAUNTLETS_OF_FUMBLING || !rn2(11))) { - curse(otmp); - otmp->spe = -rne(3); - } else if (!rn2(10)) { - otmp->blessed = rn2(2); - otmp->spe = rne(3); - } else - blessorcurse(otmp, 10); - if (artif && !rn2(40)) - otmp = mk_artifact(otmp, (aligntyp) A_NONE); - /* simulate lacquered armor for samurai */ - if (Role_if(PM_SAMURAI) && otmp->otyp == SPLINT_MAIL - && (moves <= 1 || In_quest(&u.uz))) { + case CAN_OF_GREASE: + otmp->spe = rn1(21, 5); /* 0..20 + 5 => 5..25 */ + blessorcurse(otmp, 10); + break; + case CRYSTAL_BALL: + otmp->spe = rn1(5, 3); /* 0..4 + 3 => 3..7 */ + blessorcurse(otmp, 2); + break; + case HORN_OF_PLENTY: + case BAG_OF_TRICKS: + otmp->spe = rn1(18, 3); /* 0..17 + 3 => 3..20 */ + break; + case FIGURINE: + tryct = 0; + /* figurines are slightly harder monsters */ + do + otmp->corpsenm = rndmonnum_adj(5, 10); + while (is_human(&mons[otmp->corpsenm]) && tryct++ < 30); + blessorcurse(otmp, 4); + break; + case BELL_OF_OPENING: + otmp->spe = 3; + break; + case MAGIC_FLUTE: + case MAGIC_HARP: + case FROST_HORN: + case FIRE_HORN: + case DRUM_OF_EARTHQUAKE: + otmp->spe = rn1(5, 4); + break; + } + break; + case AMULET_CLASS: + if (otmp->otyp == AMULET_OF_YENDOR) + svc.context.made_amulet = TRUE; + if (rn2(10) && (otmp->otyp == AMULET_OF_STRANGULATION + || otmp->otyp == AMULET_OF_CHANGE + || otmp->otyp == AMULET_OF_RESTFUL_SLEEP)) { + curse(otmp); + } else + blessorcurse(otmp, 10); + break; + case VENOM_CLASS: + case CHAIN_CLASS: + case BALL_CLASS: + break; + case POTION_CLASS: /* note: potions get some additional init below */ + case SCROLL_CLASS: +#ifdef MAIL_STRUCTURES + if (otmp->otyp != SCR_MAIL) +#endif + blessorcurse(otmp, 4); + break; + case SPBOOK_CLASS: + otmp->spestudied = 0; + blessorcurse(otmp, 17); + break; + case ARMOR_CLASS: + if (rn2(10) + && (otmp->otyp == FUMBLE_BOOTS + || otmp->otyp == LEVITATION_BOOTS + || otmp->otyp == HELM_OF_OPPOSITE_ALIGNMENT + || otmp->otyp == GAUNTLETS_OF_FUMBLING || !rn2(11))) { + curse(otmp); + otmp->spe = -rne(3); + } else if (!rn2(10)) { + otmp->blessed = rn2(2); + otmp->spe = rne(3); + } else + blessorcurse(otmp, 10); + if (artif && !rn2(40 + (10 * nartifact_exist()))) { + /* mk_artifact() with otmp and A_NONE will never return NULL */ + otmp = mk_artifact(otmp, (aligntyp) A_NONE, 99, TRUE); + *obj = otmp; + } + /* simulate lacquered armor for samurai */ + if (Role_if(PM_SAMURAI) && otmp->otyp == SPLINT_MAIL + && (svm.moves <= 1 || In_quest(&u.uz))) { #ifdef UNIXPC - /* optimizer bitfield bug */ - otmp->oerodeproof = 1; - otmp->rknown = 1; + /* optimizer bitfield bug */ + otmp->oerodeproof = 1; + otmp->rknown = 1; #else - otmp->oerodeproof = otmp->rknown = 1; + otmp->oerodeproof = otmp->rknown = 1; #endif + } + break; + case WAND_CLASS: + if (otmp->otyp == WAN_WISHING) + otmp->spe = 1; + else if (otmp->otyp == WAN_STASIS) + /* just as easy to recharge as other NODIR wands, but starts with + fewer charges */ + otmp->spe = rn1(4, 3); + else + otmp->spe = rn1(5, + (objects[otmp->otyp].oc_dir == NODIR) ? 11 : 4); + blessorcurse(otmp, 17); + otmp->recharged = 0; /* used to control recharging */ + break; + case RING_CLASS: + if (objects[otmp->otyp].oc_charged) { + blessorcurse(otmp, 3); + if (rn2(10)) { + if (rn2(10) && bcsign(otmp)) + otmp->spe = bcsign(otmp) * rne(3); + else + otmp->spe = rn2(2) ? rne(3) : -rne(3); } - break; - case WAND_CLASS: - if (otmp->otyp == WAN_WISHING) - otmp->spe = rnd(3); - else - otmp->spe = - rn1(5, (objects[otmp->otyp].oc_dir == NODIR) ? 11 : 4); - blessorcurse(otmp, 17); - otmp->recharged = 0; /* used to control recharging */ - break; - case RING_CLASS: - if (objects[otmp->otyp].oc_charged) { - blessorcurse(otmp, 3); - if (rn2(10)) { - if (rn2(10) && bcsign(otmp)) - otmp->spe = bcsign(otmp) * rne(3); - else - otmp->spe = rn2(2) ? rne(3) : -rne(3); - } - /* make useless +0 rings much less common */ - if (otmp->spe == 0) - otmp->spe = rn2(4) - rn2(3); - /* negative rings are usually cursed */ - if (otmp->spe < 0 && rn2(5)) - curse(otmp); - } else if (rn2(10) && (otmp->otyp == RIN_TELEPORTATION - || otmp->otyp == RIN_POLYMORPH - || otmp->otyp == RIN_AGGRAVATE_MONSTER - || otmp->otyp == RIN_HUNGER || !rn2(9))) { + /* make useless +0 rings much less common */ + if (otmp->spe == 0) + otmp->spe = rn2(4) - rn2(3); + /* negative rings are usually cursed */ + if (otmp->spe < 0 && rn2(5)) curse(otmp); - } - break; - case ROCK_CLASS: - switch (otmp->otyp) { - case STATUE: - /* possibly overridden by mkcorpstat() */ - otmp->corpsenm = rndmonnum(); - if (!verysmall(&mons[otmp->corpsenm]) - && rn2(level_difficulty() / 2 + 10) > 10) - (void) add_to_container(otmp, mkobj(SPBOOK_CLASS, FALSE)); - } - break; - case COIN_CLASS: - break; /* do nothing */ - default: - /* 3.6.3: this used to be impossible() followed by return 0 - but most callers aren't prepared to deal with Null result - and cluttering them up to do so is pointless */ - panic("mksobj tried to make type %d, class %d.", - (int) otmp->otyp, (int) objects[otmp->otyp].oc_class); - /*NOTREACHED*/ + } else if (rn2(10) && (otmp->otyp == RIN_TELEPORTATION + || otmp->otyp == RIN_POLYMORPH + || otmp->otyp == RIN_AGGRAVATE_MONSTER + || otmp->otyp == RIN_HUNGER || !rn2(9))) { + curse(otmp); + } + break; + case ROCK_CLASS: + if (otmp->otyp == STATUE) { + /* possibly overridden by mkcorpstat() */ + otmp->corpsenm = rndmonnum(); + if (!verysmall(&mons[otmp->corpsenm]) + && rn2(level_difficulty() / 2 + 10) > 10) + (void) add_to_container(otmp, /* caller will update owt */ + mkobj(SPBOOK_no_NOVEL, FALSE)); } + /* boulder init'd below in the 'regardless of !init' code */ + break; + case COIN_CLASS: + break; /* do nothing */ + default: + /* 3.6.3: this used to be impossible() followed by return 0 + but most callers aren't prepared to deal with Null result + and cluttering them up to do so is pointless */ + panic("mksobj tried to make type %d, class %d.", + (int) otmp->otyp, (int) objects[otmp->otyp].oc_class); + /*NOTREACHED*/ } + mkobj_erosions(otmp); + if (permapoisoned(otmp)) + otmp->opoisoned = 1; +} + +/* mksobj(): create a specific type of object; result is always non-Null */ +struct obj * +mksobj(int otyp, boolean init, boolean artif) +{ + struct obj *otmp; + char let = objects[otyp].oc_class; + + otmp = newobj(); + *otmp = cg.zeroobj; + otmp->age = max(svm.moves, 1L); + otmp->o_id = next_ident(); + otmp->quan = 1L; + otmp->oclass = let; + otmp->otyp = otyp; + otmp->where = OBJ_FREE; + unknow_object(otmp); /* set up dknown and known: non-0 for some things */ + otmp->corpsenm = NON_PM; + otmp->lua_ref_cnt = 0; + otmp->pickup_prev = 0; + + if (init) + mksobj_init(&otmp, artif); + /* some things must get done (corpsenm, timers) even if init = 0 */ switch ((otmp->oclass == POTION_CLASS && otmp->otyp != POT_OIL) ? POT_WATER @@ -1076,21 +1204,38 @@ boolean artif; case CORPSE: if (otmp->corpsenm == NON_PM) { otmp->corpsenm = undead_to_corpse(rndmonnum()); - if (mvitals[otmp->corpsenm].mvflags & (G_NOCORPSE | G_GONE)) - otmp->corpsenm = urole.malenum; + if (svm.mvitals[otmp->corpsenm].mvflags & (G_NOCORPSE | G_GONE)) + otmp->corpsenm = gu.urole.mnum; } + FALLTHROUGH; /*FALLTHRU*/ case STATUE: case FIGURINE: if (otmp->corpsenm == NON_PM) otmp->corpsenm = rndmonnum(); + if (otmp->corpsenm != NON_PM) { + struct permonst *ptr = &mons[otmp->corpsenm]; + + otmp->spe = (is_neuter(ptr) ? CORPSTAT_NEUTER + : is_female(ptr) ? CORPSTAT_FEMALE + : is_male(ptr) ? CORPSTAT_MALE + : rn2(2) ? CORPSTAT_FEMALE : CORPSTAT_MALE); + } + FALLTHROUGH; /*FALLTHRU*/ case EGG: /* case TIN: */ set_corpsenm(otmp, otmp->corpsenm); break; + case BOULDER: + /* next_boulder overloads corpsenm so the default value is NON_PM; + since that is non-zero, the "next boulder" case in xname() would + happen when it shouldn't; explicitly set it to 0 */ + otmp->next_boulder = 0; + break; case POT_OIL: otmp->age = MAX_OIL_IN_FLASK; /* amount of oil */ + FALLTHROUGH; /*FALLTHRU*/ case POT_WATER: /* POTION_CLASS */ otmp->fromsink = 0; /* overloads corpsenm, which was set to NON_PM */ @@ -1100,20 +1245,58 @@ boolean artif; break; case SPE_NOVEL: otmp->novelidx = -1; /* "none of the above"; will be changed */ - otmp = oname(otmp, noveltitle(&otmp->novelidx)); + otmp = oname(otmp, noveltitle(&otmp->novelidx), ONAME_NO_FLAGS); break; } /* unique objects may have an associated artifact entry */ - if (objects[otyp].oc_unique && !otmp->oartifact) - otmp = mk_artifact(otmp, (aligntyp) A_NONE); + if (objects[otyp].oc_unique && !otmp->oartifact) { + /* mk_artifact() with otmp and A_NONE will never return NULL */ + otmp = mk_artifact(otmp, (aligntyp) A_NONE, 99, FALSE); + } otmp->owt = weight(otmp); return otmp; } +/* potential mimic shapes that should be undone by stone-to-flesh; + not used for objects that will be transformed when hit by stone-to-flesh */ +boolean +stone_object_type(unsigned mappearance) +{ + int otyp = (int) mappearance; + + /* we exclude wands, rings, and gems even though some qualify as stone; + there aren't any weapons or armor classified as made out of stone */ + return (otyp == BOULDER || otyp == STATUE || otyp == FIGURINE); +} + +/* possible mimic shapes that are affected by stone-to-flesh; + mappearance for furniture is a display symbol rather than a terrain type */ +boolean +stone_furniture_type(unsigned mappearance) +{ + int sym = (int) mappearance; + + switch (sym) { + case S_upstair: + case S_dnstair: + case S_brupstair: + case S_brdnstair: + case S_altar: + case S_throne: + case S_sink: /* stone sink is iffy; metal might be more appropriate */ + return TRUE; + default: + if (sym >= S_vwall && sym <= S_trwall) + return TRUE; + break; + } + return FALSE; +} + /* * Several areas of the code made direct reassignments - * to obj->corpsenm. Because some special handling is + * to obj->corpsenm. Because some special handling is * required in certain cases, place that handling here * and call this routine in place of the direct assignment. * @@ -1132,20 +1315,35 @@ boolean artif; * */ void -set_corpsenm(obj, id) -struct obj *obj; -int id; +set_corpsenm(struct obj *obj, int id) { + int old_id = obj->corpsenm; long when = 0L; if (obj->timed) { - if (obj->otyp == EGG) + if (obj->otyp == EGG) { when = stop_timer(HATCH_EGG, obj_to_any(obj)); - else { + } else { when = 0L; obj_stop_timers(obj); /* corpse or figurine */ } } + /* oeaten is used to determine how much nutrition is left in + multiple-bite food and also used to derive how many hit points + a creature resurrected from a partly eaten corpse gets; latter + is of interest when a corpse revives as a zombie + in case they are defined with different mons[].cnutrit values */ + if (obj->otyp == CORPSE && obj->oeaten != 0 + /* when oeaten is non-zero, index old_id can't be NON_PM + and divisor mons[old_id].cnutrit can't be zero */ + && mons[old_id].cnutrit != mons[id].cnutrit) { + /* oeaten and cnutrit are unsigned; theoretically that could + be 16 bits and the calculation might overflow, so force long */ + obj->oeaten = (unsigned) ((long) obj->oeaten + * (long) mons[id].cnutrit + / (long) mons[old_id].cnutrit); + } + obj->corpsenm = id; switch (obj->otyp) { case CORPSE: @@ -1168,77 +1366,354 @@ int id; } } +/* Return the number of turns after which a Rider corpse revives */ +long +rider_revival_time(struct obj *body, boolean retry) +{ + long when; + long minturn = retry ? 3L : (body->corpsenm == PM_DEATH) ? 6L : 12L; + + /* Riders have a 1/3 chance per turn of reviving after 12, 6, or 3 turns. + Always revive by 67. */ + for (when = minturn; when < 67L; when++) + if (!rn2(3)) + break; + return when; +} + /* * Start a corpse decay or revive timer. * This takes the age of the corpse into consideration as of 3.4.0. */ void -start_corpse_timeout(body) -struct obj *body; +start_corpse_timeout(struct obj *body) { long when; /* rot away when this old */ - long corpse_age; /* age of corpse */ + long age; /* age of corpse */ int rot_adjust; short action; -#define TAINT_AGE (50L) /* age when corpses go bad */ -#define TROLL_REVIVE_CHANCE 37 /* 1/37 chance for 50 turns ~ 75% chance */ -#define ROT_AGE (250L) /* age when corpses rot away */ + /* + * Note: + * if body->norevive is set, the corpse will rot away instead + * of revive when its REVIVE_MON timer finishes. + */ /* lizards and lichen don't rot or revive */ if (body->corpsenm == PM_LIZARD || body->corpsenm == PM_LICHEN) return; - action = ROT_CORPSE; /* default action: rot away */ - rot_adjust = in_mklev ? 25 : 10; /* give some variation */ - corpse_age = monstermoves - body->age; - if (corpse_age > ROT_AGE) + action = ROT_CORPSE; /* default action: rot away */ + rot_adjust = gi.in_mklev ? 25 : 10; /* give some variation */ + age = max(svm.moves, 1) - body->age; + if (age > ROT_AGE) when = rot_adjust; else - when = ROT_AGE - corpse_age; + when = ROT_AGE - age; when += (long) (rnz(rot_adjust) - rot_adjust); if (is_rider(&mons[body->corpsenm])) { - /* - * Riders always revive. They have a 1/3 chance per turn - * of reviving after 12 turns. Always revive by 500. - */ action = REVIVE_MON; - for (when = 12L; when < 500L; when++) - if (!rn2(3)) - break; - - } else if (mons[body->corpsenm].mlet == S_TROLL && !body->norevive) { - long age; + when = rider_revival_time(body, FALSE); + } else if (mons[body->corpsenm].mlet == S_TROLL) { for (age = 2; age <= TAINT_AGE; age++) if (!rn2(TROLL_REVIVE_CHANCE)) { /* troll revives */ action = REVIVE_MON; when = age; break; } + } else if (gz.zombify && zombie_form(&mons[body->corpsenm]) != NON_PM + && !body->norevive) { + action = ZOMBIFY_MON; + when = rn1(15, 5); /* 5..19 */ } - if (body->norevive) - body->norevive = 0; (void) start_timer(when, TIMER_OBJECT, action, obj_to_any(body)); } -STATIC_OVL void -maybe_adjust_light(obj, old_range) -struct obj *obj; -int old_range; +/* used by item_on_ice() and shrink_glob() */ +enum obj_on_ice { + NOT_ON_ICE = 0, + SET_ON_ICE = 1, + BURIED_UNDER_ICE = 2 +}; + +/* used by shrink_glob(); is 'item' or enclosing container on or under ice? */ +staticfn int +item_on_ice(struct obj *item) +{ + struct obj *otmp; + coordxy ox, oy; + + otmp = item; + /* if in a container, it might be nested so find outermost one since + that's the item whose location needs to be checked */ + while (otmp->where == OBJ_CONTAINED) + otmp = otmp->ocontainer; + + if (get_obj_location(otmp, &ox, &oy, BURIED_TOO)) { + switch (otmp->where) { + case OBJ_FLOOR: + if (is_ice(ox, oy)) + return SET_ON_ICE; + break; + case OBJ_BURIED: + if (is_ice(ox, oy)) + return BURIED_UNDER_ICE; + break; + default: + break; + } + } + return NOT_ON_ICE; +} + +/* schedule a timer that will shrink the target glob by 1 unit of weight */ +void +start_glob_timeout( + struct obj *obj, /* glob */ + long when) /* when to shrink; if 0L, use random value close to 25 */ +{ + if (!obj->globby) { + impossible("start_glob_timeout for non-glob [%d: %s]?", + obj->otyp, simpleonames(obj)); + return; /* skip timer creation */ + } + /* sanity precaution */ + if (obj->timed) + (void) stop_timer(SHRINK_GLOB, obj_to_any(obj)); + + if (when < 1L) /* caller usually passes 0L; should never be negative */ + when = 25L + (long) rn2(5) - 2L; /* 25+[0..4]-2 => 23..27, avg 25 */ + /* 1 new glob weighs 20 units and loses 1 unit every 25 turns, + so lasts for 500 turns, twice as long as the average corpse */ + (void) start_timer(when, TIMER_OBJECT, SHRINK_GLOB, obj_to_any(obj)); +} + +/* globs have quantity 1 and size which varies by multiples of 20 in owt; + they don't become tainted with age, but every 25 turns this timer runs + and reduces owt by 1; when it hits 0, destroy the glob (if some other + part of the program destroys it, the timer will be cancelled); + note: timer keeps going if an object gets buried or scheduled to + migrate to another level and can delete the glob in those states */ +void +shrink_glob( + anything *arg, /* glob (in arg->a_obj) */ + long expire_time) /* turn the timer should have gone off; if less than + * current 'moves', we're making up for lost time + * after leaving and then returning to this level */ +{ + char globnambuf[BUFSZ]; + struct obj *obj = arg->a_obj; + int globloc = item_on_ice(obj); + boolean ininv = (obj->where == OBJ_INVENT), + shrink = FALSE, gone = FALSE, updinv = FALSE; + struct obj *contnr = (obj->where == OBJ_CONTAINED) ? obj->ocontainer : 0, + *topcontnr = 0; + unsigned old_top_owt = 0; + + if (!obj->globby) { + impossible("shrink_glob for non-glob [%d: %s]?", + obj->otyp, simpleonames(obj)); + return; /* old timer is gone, don't start a new one */ + } + /* note: if check_glob() complains about a problem, the " obj " here + will be replaced in the feedback with info about this glob */ + check_glob(obj, "shrink obj "); + + /* + * If shrinkage occurred while we were on another level, catch up now. + */ + if (expire_time < svm.moves && globloc != BURIED_UNDER_ICE) { + /* number of units of weight to remove */ + long delta = (svm.moves - expire_time + 24L) / 25L, + /* leftover amount to use for new timer */ + moddelta = 25L - (delta % 25L); + + if (globloc == SET_ON_ICE) + delta = (delta + 2L) / 3L; + + if (delta >= (long) obj->owt) { + /* gone; no newsym() or message here--forthcoming map update for + level arrival is all that's needed */ + obj->owt = 0; /* not required; accurately reflects obj's state */ + shrinking_glob_gone(obj); + } else { + /* shrank but not gone; reduce remaining weight */ + obj->owt -= (unsigned) delta; + /* when contained, update container's weight (recursively if + nested); won't be in a container carried by hero (since + catching up for lost time never applies in that situation) + but might be in one on floor or one carried by a monster */ + if (contnr) + container_weight(contnr); + /* resume regular shrinking */ + start_glob_timeout(obj, moddelta); + } + return; + } + + /* + * When on ice, only shrink every third try. If buried under ice, + * don't shrink at all, similar to being contained in an ice box + * except that the timer remains active. [FIXME: stop the timer + * for obj in pool that becomes frozen, restart it if/when unburied.] + * + * If the glob is actively being eaten by hero, skip weight reduction + * to avoid messing up the context.victual data (if/when eaten by a + * monster, timer won't have a chance to run before meal is finished). + */ + if (eating_glob(obj) + || globloc == BURIED_UNDER_ICE + || (globloc == SET_ON_ICE && (svm.moves % 3L) == 1L)) { + /* schedule next shrink attempt; for the being eaten case, the + glob and its timer might be deleted before this kicks in */ + start_glob_timeout(obj, 0L); + return; + } + + /* format "Your/Shk's/The [partly eaten] glob of " into + globnambuf[] before shrinking the glob; Yname2() calls yname() + which calls xname() which ordinarily leaves "partly eaten" to + doname() rather than inserting that itself; ask xname() to add + that when appropriate */ + iflags.partly_eaten_hack = TRUE; + Strcpy(globnambuf, Yname2(obj)); + iflags.partly_eaten_hack = FALSE; + + if (obj->owt > 0) { /* sanity precaution */ + /* globs start out weighing 20 units; give two messages per glob, + when going from 20 to 19 and from 10 to 9; a different message + is given for going from 1 to 0 (gone) */ + unsigned basewt = objects[obj->otyp].oc_weight, /* 20 */ + msgwt = (max(basewt, 1U) + 1U) / 2U; /* 10 */ + + shrink = (obj->owt % msgwt) == 0; + obj->owt -= 1; + /* if glob is partly eaten, reduce the amount still available (but + not all the way to 0 which would change it back to untouched) */ + if (obj->oeaten > 1) + obj->oeaten -= 1; + } + gone = !obj->owt; + + /* timer might go off when the glob is migrating to another level and + possibly delete it; messages are only given for in-open-inventory, + inside-container-in-invent, and going away when can-see-on-floor */ + if (ininv) { + if (shrink || gone) + pline("%s %s.", globnambuf, + /* globs always have quantity 1 so we don't need otense() + because the verb always references a singular item */ + gone ? "dissolves completely" : "shrinks"); + updinv = TRUE; + } else if (contnr) { + /* when in a container, it might be nested so find outermost one */ + topcontnr = contnr; + while (topcontnr->where == OBJ_CONTAINED) + topcontnr = topcontnr->ocontainer; + /* obj's weight has been reduced, but weight(s) of enclosing + container(s) haven't been adjusted for that yet */ + old_top_owt = topcontnr->owt; + /* update those weights now; recursively updates nested containers */ + container_weight(contnr); + + if (topcontnr->where == OBJ_INVENT) { + /* for regular containers, the weight will always be reduced + when glob's weight has been reduced but we only say so + when shrinking beneath a particular threshold (N*20 to + (N-1)*20 + 19 or (N-1)*20 + 10 to (N-1)*20 + 9), or + if we're going to report a change in carrying capacity; + for a non-cursed bag of holding the total weight might not + change because only a fraction of glob's weight is counted; + however, always say the bag is lighter for the 'gone' case */ + if (gone || (shrink && topcontnr->owt != old_top_owt) + || near_capacity() != go.oldcap) + pline("%s %s%s lighter.", Yname2(topcontnr), + /* containers also always have quantity 1 */ + (topcontnr->owt != old_top_owt) ? "becomes" : "seems", + /* TODO? maybe also skip "slightly" if description + is changing (from "very large" to "large", + "large" to "medium", or "medium to "small") */ + !gone ? " slightly" : ""); + updinv = TRUE; + } + } + + if (gone) { + coordxy ox = 0, oy = 0; + /* check location for visibility before destroying obj */ + boolean seeit = (obj->where == OBJ_FLOOR + && get_obj_location(obj, &ox, &oy, 0) + && cansee(ox, oy)); + + /* weight has been reduced to 0 so destroy the glob */ + shrinking_glob_gone(obj); + + if (seeit) { + newsym(ox, oy); + if ((ox != u.ux || oy != u.uy) && !strncmp(globnambuf, "The ", 4)) + /* fortunately none of the glob adjectives warrant "An " */ + (void) strsubst(globnambuf, "The ", "A "); + /* again, quantity is always 1 so no need for otense()/vtense() */ + pline("%s fades away.", globnambuf); + } + } else { + /* schedule next shrink ~25 turns from now */ + start_glob_timeout(obj, 0L); + } + if (updinv) { + update_inventory(); + encumber_msg(); + } +} + +/* a glob has shrunk away to nothing; handle owornmask, then delete glob */ +staticfn void +shrinking_glob_gone(struct obj *obj) +{ + xint16 owhere = obj->where; + + if (owhere == OBJ_INVENT) { + if (obj->owornmask) { + remove_worn_item(obj, FALSE); + stop_occupation(); + } + useupall(obj); /* freeinv()+obfree() */ + } else { + if (owhere == OBJ_MIGRATING) { + /* destination flag overloads owornmask; clear it so obfree()'s + check for freeing a worn object doesn't get a false hit */ + obj->owornmask = 0L; + } else if (owhere == OBJ_MINVENT) { + /* monsters don't wield globs so this isn't strictly needed */ + if (obj->owornmask && obj == MON_WEP(obj->ocarry)) + setmnotwielded(obj->ocarry, obj); /* clears owornmask */ + } + /* remove the glob from whatever list it's on and then delete it; + if it's contained, obj_extract_self() will update the container's + weight and if nested, the enclosing containers' weights too */ + obj_extract_self(obj); + if (owhere == OBJ_FLOOR) + maybe_unhide_at(obj->ox, obj->oy); + obfree(obj, (struct obj *) 0); + } +} + +void +maybe_adjust_light(struct obj *obj, int old_range) { char buf[BUFSZ]; - xchar ox, oy; + coordxy ox, oy; int new_range = arti_light_radius(obj), delta = new_range - old_range; /* radius of light emitting artifact varies by curse/bless state so will change after blessing or cursing */ if (delta) { obj_adjust_light_radius(obj, new_range); - /* simplifying assumptions: hero is wielding this object; - artifacts have to be in use to emit light and monsters' - gear won't change bless or curse state */ + /* simplifying assumptions: hero is wielding or wearing this object; + artifacts have to be in use to emit light and monsters' gear won't + change bless or curse state */ if (!Blind && get_obj_location(obj, &ox, &oy, 0)) { *buf = '\0'; if (iflags.last_msg == PLNMSG_OBJ_GLOWS) @@ -1267,8 +1742,7 @@ int old_range; */ void -bless(otmp) -register struct obj *otmp; +bless(struct obj *otmp) { int old_light = 0; @@ -1290,8 +1764,7 @@ register struct obj *otmp; } void -unbless(otmp) -register struct obj *otmp; +unbless(struct obj *otmp) { int old_light = 0; @@ -1307,8 +1780,7 @@ register struct obj *otmp; } void -curse(otmp) -register struct obj *otmp; +curse(struct obj *otmp) { unsigned already_cursed; int old_light = 0; @@ -1347,8 +1819,7 @@ register struct obj *otmp; } void -uncurse(otmp) -register struct obj *otmp; +uncurse(struct obj *otmp) { int old_light = 0; @@ -1367,9 +1838,7 @@ register struct obj *otmp; } void -blessorcurse(otmp, chance) -register struct obj *otmp; -register int chance; +blessorcurse(struct obj *otmp, int chance) { if (otmp->blessed || otmp->cursed) return; @@ -1385,21 +1854,20 @@ register int chance; } int -bcsign(otmp) -register struct obj *otmp; +bcsign(struct obj *otmp) { return (!!otmp->blessed - !!otmp->cursed); } /* set the object's bless/curse-state known flag */ void -set_bknown(obj, onoff) -struct obj *obj; -unsigned onoff; /* 1 or 0 */ +set_bknown( + struct obj *obj, + unsigned int onoff) /* 1 or 0 */ { if (obj->bknown != onoff) { obj->bknown = onoff; - if (obj->where == OBJ_INVENT && moves > 1L) + if (obj->where == OBJ_INVENT && svm.moves > 1L) update_inventory(); } } @@ -1411,25 +1879,58 @@ unsigned onoff; /* 1 or 0 */ * Note: It is possible to end up with an incorrect weight if some part * of the code messes with a contained object and doesn't update the * container's weight. + * + * Note too: obj->owt is an unsigned int and objects[].oc_weight an + * unsigned short int, so weight() should probably be changed to + * use and return unsigned int instead of signed int. */ int -weight(obj) -register struct obj *obj; -{ - int wt = (int) objects[obj->otyp].oc_weight; - - /* glob absorpsion means that merging globs accumulates weight while - quantity stays 1, so update 'wt' to reflect that, unless owt is 0, - when we assume this is a brand new glob so use objects[].oc_weight */ - if (obj->globby && obj->owt > 0) - wt = obj->owt; +weight(struct obj *obj) +{ + int wt = (int) objects[obj->otyp].oc_weight; /* weight of 1 'otyp' */ + + if (obj->quan < 1L) { + impossible("Calculating weight of %ld %s?", + obj->quan, simpleonames(obj)); + return 0; + } + /* glob absorption means that merging globs combines their weight + while quantity stays 1; mksobj(), obj_absorb(), and shrink_glob() + manage glob->owt and there is nothing for weight() to do except + return the current value as-is */ + if (obj->globby) { + /* 5.0: in 3.6.x this checked for owt==0 and then used + owt as-is when non-zero or objects[].oc_weight if zero; + we don't do that anymore because it confused calculating + the weight of a container when a glob inside shrank down + to 0 and was about to be deleted [mksobj() now initializes + owt for globs sooner and the subsequent o->owt = weight(o) + general initialization is benignly redundant for globs] */ + return (int) obj->owt; + } if (Is_container(obj) || obj->otyp == STATUE) { struct obj *contents; - register int cwt = 0; - - if (obj->otyp == STATUE && obj->corpsenm >= LOW_PM) - wt = (int) obj->quan * ((int) mons[obj->corpsenm].cwt * 3 / 2); + int cwt; + + if (obj->otyp == STATUE && ismnum(obj->corpsenm)) { + int msize = (int) mons[obj->corpsenm].msize, /* 0..7 */ + minwt = (msize + msize + 1) * 100; + + /* default statue weight is 1.5 times corpse weight */ + wt = 3 * (int) mons[obj->corpsenm].cwt / 2; + /* some monsters that never leave a corpse when they die have + corpse weight defined as 0; statues resembling them need to + have non-zero weight; others are so tiny (killer bee) that + they weigh barely more than nothing or so insubstantial + (wraith) that they actually weigh nothing; statues of such + need more heft */ + if (wt < minwt) + wt = minwt; + /* this has no effect because statues don't stack */ + wt *= (int) obj->quan; + } + cwt = 0; /* contents weight */ for (contents = obj->cobj; contents; contents = contents->nobj) cwt += weight(contents); /* @@ -1447,12 +1948,13 @@ register struct obj *obj; * weight equations. */ if (obj->otyp == BAG_OF_HOLDING) - cwt = obj->cursed ? (cwt * 2) : obj->blessed ? ((cwt + 3) / 4) - : ((cwt + 1) / 2); + cwt = obj->cursed ? (cwt * 2) + : obj->blessed ? ((cwt + 3) / 4) + : ((cwt + 1) / 2); /* uncursed */ return wt + cwt; } - if (obj->otyp == CORPSE && obj->corpsenm >= LOW_PM) { + if (obj->otyp == CORPSE && ismnum(obj->corpsenm)) { long long_wt = obj->quan * (long) mons[obj->corpsenm].cwt; wt = (long_wt > LARGEST_INT) ? LARGEST_INT : (int) long_wt; @@ -1462,7 +1964,9 @@ register struct obj *obj; } else if (obj->oclass == FOOD_CLASS && obj->oeaten) { return eaten_stat((int) obj->quan * wt, obj); } else if (obj->oclass == COIN_CLASS) { - return (int) ((obj->quan + 50L) / 100L); + /* 5.0: always weigh at least 1 unit; used to yield 0 for 1..49 */ + wt = (int) ((obj->quan + 50L) / 100L); + return max(wt, 1); } else if (obj->otyp == HEAVY_IRON_BALL && obj->owt != 0) { return (int) obj->owt; /* kludge for "very" heavy iron ball */ } else if (obj->otyp == CANDELABRUM_OF_INVOCATION && obj->spe) { @@ -1471,21 +1975,32 @@ register struct obj *obj; return (wt ? wt * (int) obj->quan : ((int) obj->quan + 1) >> 1); } -static int treefruits[] = { APPLE, ORANGE, PEAR, BANANA, EUCALYPTUS_LEAF }; +static const int treefruits[] = { + APPLE, ORANGE, PEAR, BANANA, EUCALYPTUS_LEAF +}; /* called when a tree is kicked; never returns Null */ struct obj * -rnd_treefruit_at(x, y) -int x, y; +rnd_treefruit_at(coordxy x, coordxy y) +{ + return mksobj_at(ROLL_FROM(treefruits), x, y, TRUE, FALSE); +} + +/* for describing objects embedded in trees */ +boolean +is_treefruit(struct obj *otmp) { - return mksobj_at(treefruits[rn2(SIZE(treefruits))], x, y, TRUE, FALSE); + int fruitidx; + + for (fruitidx = 0; fruitidx < SIZE(treefruits); ++fruitidx) + if (treefruits[fruitidx] == otmp->otyp) + return TRUE; + return FALSE; } /* create a stack of N gold pieces; never returns Null */ struct obj * -mkgold(amount, x, y) -long amount; -int x, y; +mkgold(long amount, coordxy x, coordxy y) { struct obj *gold = g_at(x, y); @@ -1504,6 +2019,34 @@ int x, y; return gold; } +/* potions of oil use their obj->age field differently from other potions + so changing potion type to or from oil needs to have that fixed up */ +void +fixup_oil( + struct obj *potion, /* potion that just had its otyp changed */ + struct obj *source) /* item used to create potion; might be Null */ +{ + if (potion->otyp == POT_OIL) { + if (source && source->otyp == POT_OIL) { + /* potion of oil being used to set potion's otyp to oil; + source might be partly used */ + potion->age = source->age; + } else { + /* non-oil is being turned into oil; change absolute age + (turn created) into relative age (amount remaining / + burn time available) */ + potion->age = MAX_OIL_IN_FLASK; + } + } else if (source && source->otyp == POT_OIL) { + /* potion is no longer oil, being turned into non-oil */ + if (potion->age == source->age) + potion->age = svm.moves; + /* when source is a partly used oil, mark potion as diluted */ + if (source->age < MAX_OIL_IN_FLASK) + potion->odiluted = 1; + } +} + /* return TRUE if the corpse has special timing; lizards and lichen don't rot, trolls and Riders auto-revive */ #define special_corpse(num) \ @@ -1521,12 +2064,12 @@ int x, y; * resurrection. */ struct obj * -mkcorpstat(objtype, mtmp, ptr, x, y, corpstatflags) -int objtype; /* CORPSE or STATUE */ -struct monst *mtmp; -struct permonst *ptr; -int x, y; -unsigned corpstatflags; +mkcorpstat( + int objtype, /* CORPSE or STATUE */ + struct monst *mtmp, /* dead monster, might be Null */ + struct permonst *ptr, /* if non-Null, overrides mtmp->mndx */ + coordxy x, coordxy y, /* where to place corpse; <0,0> => random */ + unsigned corpstatflags) { struct obj *otmp; boolean init = ((corpstatflags & CORPSTAT_INIT) != 0); @@ -1539,6 +2082,9 @@ unsigned corpstatflags; } else { otmp = mksobj_at(objtype, x, y, init, FALSE); } + /* record gender and 'historic statue' in overloaded enchantment field */ + otmp->spe = (corpstatflags & CORPSTAT_SPE_VAL); + otmp->norevive = gm.mkcorpstat_norevive; /* via envrmt rather than flags */ /* when 'mtmp' is non-null save the monster's details with the corpse or statue; it will also force the 'ptr' override below */ @@ -1548,6 +2094,10 @@ unsigned corpstatflags; if (!ptr) ptr = mtmp->data; + + /* don't give a revive timer to a cancelled troll's corpse */ + if (mtmp->mcan && !is_rider(ptr)) + otmp->norevive = 1; } /* when 'ptr' is non-null it comes from our caller or from 'mtmp'; @@ -1557,8 +2107,9 @@ unsigned corpstatflags; otmp->corpsenm = monsndx(ptr); otmp->owt = weight(otmp); - if (otmp->otyp == CORPSE && (special_corpse(old_corpsenm) - || special_corpse(otmp->corpsenm))) { + if (otmp->otyp == CORPSE + && (gz.zombify || special_corpse(old_corpsenm) + || special_corpse(otmp->corpsenm))) { obj_stop_timers(otmp); start_corpse_timeout(otmp); } @@ -1575,8 +2126,7 @@ unsigned corpstatflags; * The return value is an index into mons[]. */ int -corpse_revive_type(obj) -struct obj *obj; +corpse_revive_type(struct obj *obj) { int revivetype = obj->corpsenm; struct monst *mtmp; @@ -1594,41 +2144,52 @@ struct obj *obj; * a lasting association between the two. */ struct obj * -obj_attach_mid(obj, mid) -struct obj *obj; -unsigned mid; +obj_attach_mid(struct obj *obj, unsigned int mid) { if (!mid || !obj) return (struct obj *) 0; newomid(obj); - *OMID(obj) = mid; + OMID(obj) = mid; return obj; } -static struct obj * -save_mtraits(obj, mtmp) -struct obj *obj; -struct monst *mtmp; +staticfn struct obj * +save_mtraits(struct obj *obj, struct monst *mtmp) { if (mtmp->ispriest) forget_temple_entry(mtmp); /* EPRI() */ if (!has_omonst(obj)) newomonst(obj); if (has_omonst(obj)) { + int baselevel = mtmp->data->mlevel; /* mtmp->data is valid ptr */ struct monst *mtmp2 = OMONST(obj); *mtmp2 = *mtmp; mtmp2->mextra = (struct mextra *) 0; - if (mtmp->data) - mtmp2->mnum = monsndx(mtmp->data); + mtmp2->mnum = monsndx(mtmp->data); /* invalidate pointers */ /* m_id is needed to know if this is a revived quest leader */ /* but m_id must be cleared when loading bones */ mtmp2->nmon = (struct monst *) 0; mtmp2->data = (struct permonst *) 0; mtmp2->minvent = (struct obj *) 0; + MON_NOWEP(mtmp2); /* mtmp2->mw = (struct obj *) 0; */ if (mtmp->mextra) copy_mextra(mtmp2, mtmp); + /* if mtmp is a long worm with segments, its saved traits will + be one without any segments */ + mtmp2->wormno = 0; + /* mtmp might have been killed by repeated life draining; make sure + mtmp2 can survive if revived ('baselevel' will be 0 for 1d4 mon) */ + if (mtmp2->mhpmax <= baselevel) + mtmp2->mhpmax = baselevel + 1; + /* mtmp is assumed to be dead but we don't kill it or its saved + traits, just force those to have a sane value for current HP */ + if (mtmp2->mhp > mtmp2->mhpmax) + mtmp2->mhp = mtmp2->mhpmax; + if (mtmp2->mhp < 1) + mtmp2->mhp = 0; + mtmp2->mstate &= ~MON_DETACH; } return obj; } @@ -1637,9 +2198,7 @@ struct monst *mtmp; * the one contained within the obj. */ struct monst * -get_mtraits(obj, copyof) -struct obj *obj; -boolean copyof; +get_mtraits(struct obj *obj, boolean copyof) { struct monst *mtmp = (struct monst *) 0; struct monst *mnew = (struct monst *) 0; @@ -1657,6 +2216,7 @@ boolean copyof; /* Never insert this returned pointer into mon chains! */ mnew = mtmp; } + mnew->data = &mons[mnew->mnum]; } return mnew; } @@ -1664,9 +2224,9 @@ boolean copyof; /* make an object named after someone listed in the scoreboard file; never returns Null */ struct obj * -mk_tt_object(objtype, x, y) -int objtype; /* CORPSE or STATUE */ -int x, y; +mk_tt_object( + int objtype, /* CORPSE or STATUE */ + coordxy x, coordxy y) { struct obj *otmp; boolean initialize_it; @@ -1674,10 +2234,15 @@ int x, y; /* player statues never contain books */ initialize_it = (objtype != STATUE); otmp = mksobj_at(objtype, x, y, initialize_it, FALSE); - /* tt_oname() will return null if the scoreboard is empty; - assigning an object name used to allocate a new obj but - doesn't any more so we can safely ignore the return value */ - (void) tt_oname(otmp); + + /* tt_oname() will return null if the scoreboard is empty, which in + turn leaves the random corpsenm value; force it to match a player */ + if (!tt_oname(otmp)) { + int pm = rn1(PM_WIZARD - PM_ARCHEOLOGIST + 1, PM_ARCHEOLOGIST); + + /* update weight for either, force timer sanity for corpses */ + set_corpsenm(otmp, pm); + } return otmp; } @@ -1685,11 +2250,11 @@ int x, y; /* make a new corpse or statue, uninitialized if a statue (i.e. no books); never returns Null */ struct obj * -mk_named_object(objtype, ptr, x, y, nm) -int objtype; /* CORPSE or STATUE */ -struct permonst *ptr; -int x, y; -const char *nm; +mk_named_object( + int objtype, /* CORPSE or STATUE */ + struct permonst *ptr, + coordxy x, coordxy y, + const char *nm) { struct obj *otmp; unsigned corpstatflags = (objtype != STATUE) ? CORPSTAT_INIT @@ -1697,13 +2262,12 @@ const char *nm; otmp = mkcorpstat(objtype, (struct monst *) 0, ptr, x, y, corpstatflags); if (nm) - otmp = oname(otmp, nm); + otmp = oname(otmp, nm, ONAME_NO_FLAGS); return otmp; } boolean -is_flammable(otmp) -register struct obj *otmp; +is_flammable(struct obj *otmp) { int otyp = otmp->otyp; int omat = objects[otyp].oc_material; @@ -1722,13 +2286,13 @@ register struct obj *otmp; } boolean -is_rottable(otmp) -register struct obj *otmp; +is_rottable(struct obj *otmp) { int otyp = otmp->otyp; - return (boolean) (objects[otyp].oc_material <= WOOD - && objects[otyp].oc_material != LIQUID); + return (boolean) ((objects[otyp].oc_material <= WOOD + && objects[otyp].oc_material != LIQUID) + || objects[otyp].oc_material == DRAGON_HIDE); } /* @@ -1738,24 +2302,31 @@ register struct obj *otmp; /* put the object at the given location */ void -place_object(otmp, x, y) -register struct obj *otmp; -int x, y; +place_object(struct obj *otmp, coordxy x, coordxy y) { - register struct obj *otmp2 = level.objects[x][y]; + struct obj *otmp2; if (!isok(x, y)) { /* validate location */ - void VDECL((*func), (const char *, ...)) PRINTF_F(1, 2); + void (*func)(const char *, ...) PRINTF_F_PTR(1, 2); func = (x < 0 || y < 0 || x > COLNO - 1 || y > ROWNO - 1) ? panic : impossible; (*func)("place_object: \"%s\" [%d] off map <%d,%d>", safe_typename(otmp->otyp), otmp->where, x, y); + + /* we'll only get to here if we've issued a warning (and fuzzer + is not running since it escalates impossible to panic), so + x,y has failed isok() but is within array bounds for the map; + in other words, x specifies column 0 which should not happen + but we let the game keep going */ } if (otmp->where != OBJ_FREE) panic("place_object: obj \"%s\" [%d] not free", safe_typename(otmp->otyp), otmp->where); + assert(x >= 0 && x < COLNO && y >= 0 && y < ROWNO); + otmp2 = svl.level.objects[x][y]; + obj_no_longer_held(otmp); if (otmp->otyp == BOULDER) { if (!otmp2 || otmp2->otyp != BOULDER) @@ -1766,10 +2337,7 @@ int x, y; here without display code needing to traverse pile chain to find one */ if (otmp2 && otmp2->otyp == BOULDER && otmp->otyp != BOULDER) { /* 3.6.3: put otmp under last consecutive boulder rather than under - just the first one; multiple boulders at same spot in new games - will be consecutive due to this, ones in old games saved before - this change might not be; can affect the map display if the top - boulder is moved/removed by some means other than pushing */ + just the first one */ while (otmp2->nexthere && otmp2->nexthere->otyp == BOULDER) otmp2 = otmp2->nexthere; otmp->nexthere = otmp2->nexthere; @@ -1777,14 +2345,19 @@ int x, y; } else { /* put on top of current pile */ otmp->nexthere = otmp2; - level.objects[x][y] = otmp; + svl.level.objects[x][y] = otmp; } - /* set the new object's location */ + /* set the object's new location */ otmp->ox = x; otmp->oy = y; otmp->where = OBJ_FLOOR; + /* if placed outside of shop, no_charge is no longer applicable */ + if (otmp->no_charge && !costly_spot(x, y) + && !costly_adjacent(find_objowner(otmp, x, y), x, y)) + otmp->no_charge = 0; + /* add to floor chain */ otmp->nobj = fobj; fobj = otmp; @@ -1792,24 +2365,45 @@ int x, y; obj_timer_checks(otmp, x, y, 0); } +/* tear down the object pile at and create it again, so that any + boulders which are present get forced to the top */ +void +recreate_pile_at(coordxy x, coordxy y) +{ + struct obj *otmp, *next_obj, *reversed = 0; + + /* remove all objects at , saving a reversed temporary list */ + for (otmp = svl.level.objects[x][y]; otmp; otmp = next_obj) { + next_obj = otmp->nexthere; + remove_object(otmp); /* obj_extract_self() for floor */ + otmp->nobj = reversed; + reversed = otmp; + } + /* pile at is now empty; create new one, re-reversing to restore + original order; place_object() handles making boulders be on top */ + for (otmp = reversed; otmp; otmp = next_obj) { + next_obj = otmp->nobj; + otmp->nobj = 0; /* obj->where is OBJ_FREE */ + place_object(otmp, x, y); + } +} + #define ROT_ICE_ADJUSTMENT 2 /* rotting on ice takes 2 times as long */ /* If ice was affecting any objects correct that now * Also used for starting ice effects too. [zap.c] */ void -obj_ice_effects(x, y, do_buried) -int x, y; -boolean do_buried; +obj_ice_effects(coordxy x, coordxy y, boolean do_buried) { struct obj *otmp; - for (otmp = level.objects[x][y]; otmp; otmp = otmp->nexthere) { + for (otmp = svl.level.objects[x][y]; otmp; otmp = otmp->nexthere) { if (otmp->timed) obj_timer_checks(otmp, x, y, 0); } if (do_buried) { - for (otmp = level.buriedobjlist; otmp; otmp = otmp->nobj) { + for (otmp = svl.level.buriedobjlist; otmp; otmp = otmp->nobj) { if (otmp->ox == x && otmp->oy == y) { if (otmp->timed) obj_timer_checks(otmp, x, y, 0); @@ -1826,28 +2420,27 @@ boolean do_buried; * restarted etc. */ long -peek_at_iced_corpse_age(otmp) -struct obj *otmp; +peek_at_iced_corpse_age(struct obj *otmp) { long age, retval = otmp->age; if (otmp->otyp == CORPSE && otmp->on_ice) { /* Adjust the age; must be same as obj_timer_checks() for off ice*/ - age = monstermoves - otmp->age; + age = svm.moves - otmp->age; retval += age * (ROT_ICE_ADJUSTMENT - 1) / ROT_ICE_ADJUSTMENT; debugpline3( "The %s age has ice modifications: otmp->age = %ld, returning %ld.", s_suffix(doname(otmp)), otmp->age, retval); - debugpline1("Effective age of corpse: %ld.", monstermoves - retval); + debugpline1("Effective age of corpse: %ld.", svm.moves - retval); } return retval; } -STATIC_OVL void -obj_timer_checks(otmp, x, y, force) -struct obj *otmp; -xchar x, y; -int force; /* 0 = no force so do checks, <0 = force off, >0 force on */ +staticfn void +obj_timer_checks( + struct obj *otmp, + coordxy x, coordxy y, + int force) /* 0 = no force so do checks, <0 = force off, >0 force on */ { long tleft = 0L; short action = ROT_CORPSE; @@ -1867,8 +2460,8 @@ int force; /* 0 = no force so do checks, <0 = force off, >0 force on */ /* mark the corpse as being on ice */ otmp->on_ice = 1; - debugpline3("%s is now on ice at <%d,%d>.", The(xname(otmp)), x, - y); + debugpline3("%s is now on ice at <%d,%d>.", + The(xname(otmp)), x, y); /* Adjust the time remaining */ tleft *= ROT_ICE_ADJUSTMENT; restart_timer = TRUE; @@ -1877,8 +2470,8 @@ int force; /* 0 = no force so do checks, <0 = force off, >0 force on */ later calculations behave as if it had been on ice during that time (longwinded way of saying this is the inverse of removing it from the ice and of peeking at its age). */ - age = monstermoves - otmp->age; - otmp->age = monstermoves - (age * ROT_ICE_ADJUSTMENT); + age = svm.moves - otmp->age; + otmp->age = svm.moves - (age * ROT_ICE_ADJUSTMENT); } /* Check for corpses coming off ice */ @@ -1899,7 +2492,7 @@ int force; /* 0 = no force so do checks, <0 = force off, >0 force on */ tleft /= ROT_ICE_ADJUSTMENT; restart_timer = TRUE; /* Adjust the age */ - age = monstermoves - otmp->age; + age = svm.moves - otmp->age; otmp->age += age * (ROT_ICE_ADJUSTMENT - 1) / ROT_ICE_ADJUSTMENT; } } @@ -1912,50 +2505,40 @@ int force; /* 0 = no force so do checks, <0 = force off, >0 force on */ #undef ROT_ICE_ADJUSTMENT void -remove_object(otmp) -register struct obj *otmp; +remove_object(struct obj *otmp) { - xchar x = otmp->ox; - xchar y = otmp->oy; + coordxy x = otmp->ox; + coordxy y = otmp->oy; if (otmp->where != OBJ_FLOOR) - panic("remove_object: obj not on floor"); - extract_nexthere(otmp, &level.objects[x][y]); + panic("remove_object: obj where=%d, not on floor", otmp->where); + extract_nexthere(otmp, &svl.level.objects[x][y]); extract_nobj(otmp, &fobj); - /* update vision iff this was the only boulder at its spot */ - if (otmp->otyp == BOULDER && !sobj_at(BOULDER, x, y)) - unblock_point(x, y); /* vision */ + if (otmp->otyp == BOULDER) + recalc_block_point(x, y); /* vision */ if (otmp->timed) obj_timer_checks(otmp, x, y, 0); } /* throw away all of a monster's inventory */ void -discard_minvent(mtmp) -struct monst *mtmp; +discard_minvent(struct monst *mtmp, boolean uncreate_artifacts) { - struct obj *otmp, *mwep = MON_WEP(mtmp); - boolean keeping_mon = (!DEADMONSTER(mtmp)); + struct obj *otmp; while ((otmp = mtmp->minvent) != 0) { /* this has now become very similar to m_useupall()... */ - obj_extract_self(otmp); - if (otmp->owornmask) { - if (keeping_mon) { - if (otmp == mwep) - mwepgone(mtmp), mwep = 0; - mtmp->misc_worn_check &= ~otmp->owornmask; - update_mon_intrinsics(mtmp, otmp, FALSE, TRUE); - } - otmp->owornmask = 0L; /* obfree() expects this */ - } + extract_from_minvent(mtmp, otmp, TRUE, TRUE); + if (uncreate_artifacts && otmp->oartifact) + artifact_exists(otmp, safe_oname(otmp), FALSE, ONAME_NO_FLAGS); obfree(otmp, (struct obj *) 0); /* dealloc_obj() isn't sufficient */ } } /* * Free obj from whatever list it is on in preparation for deleting it - * or moving it elsewhere; obj->where will end up set to OBJ_FREE. + * or moving it elsewhere; obj->where will end up set to OBJ_FREE unless + * it is already OBJ_LUAFREE or OBJ_DELETED. * Doesn't handle unwearing of objects in hero's or monsters' inventories. * * Object positions: @@ -1966,14 +2549,17 @@ struct monst *mtmp; * OBJ_MINVENT monster's invent chain * OBJ_MIGRATING migrating chain * OBJ_BURIED level.buriedobjs chain - * OBJ_ONBILL on billobjs chain + * OBJ_ONBILL on gb.billobjs chain + * OBJ_LUAFREE obj is dealloc'd from core, but still used by lua + * OBJ_DELETED obj has been deleted from play but not yet deallocated */ void -obj_extract_self(obj) -struct obj *obj; +obj_extract_self(struct obj *obj) { switch (obj->where) { case OBJ_FREE: + case OBJ_LUAFREE: + case OBJ_DELETED: break; case OBJ_FLOOR: remove_object(obj); @@ -1991,24 +2577,23 @@ struct obj *obj; obj->ocarry = (struct monst *) 0; /* clear stale back-link */ break; case OBJ_MIGRATING: - extract_nobj(obj, &migrating_objs); + extract_nobj(obj, &gm.migrating_objs); break; case OBJ_BURIED: - extract_nobj(obj, &level.buriedobjlist); + extract_nobj(obj, &svl.level.buriedobjlist); break; case OBJ_ONBILL: - extract_nobj(obj, &billobjs); + extract_nobj(obj, &gb.billobjs); break; default: - panic("obj_extract_self"); + panic("obj_extract_self, where=%d", obj->where); break; } } /* Extract the given object from the chain, following nobj chain. */ void -extract_nobj(obj, head_ptr) -struct obj *obj, **head_ptr; +extract_nobj(struct obj *obj, struct obj **head_ptr) { struct obj *curr, *prev; @@ -2035,8 +2620,7 @@ struct obj *obj, **head_ptr; * in tandem with extract_nobj, which does set it. */ void -extract_nexthere(obj, head_ptr) -struct obj *obj, **head_ptr; +extract_nexthere(struct obj *obj, struct obj **head_ptr) { struct obj *curr, *prev; @@ -2061,14 +2645,12 @@ struct obj *obj, **head_ptr; * Otherwise 0 is returned. */ int -add_to_minv(mon, obj) -struct monst *mon; -struct obj *obj; +add_to_minv(struct monst *mon, struct obj *obj) { struct obj *otmp; if (obj->where != OBJ_FREE) - panic("add_to_minv: obj not free"); + panic("add_to_minv: obj where=%d, not free", obj->where); /* merge if possible */ for (otmp = mon->minvent; otmp; otmp = otmp->nobj) @@ -2085,15 +2667,18 @@ struct obj *obj; /* * Add obj to container, make sure obj is "free". Returns (merged) obj. * The input obj may be deleted in the process. + * + * Caveat: this does not update the container's weight [possibly to + * prevent that from being recalculated repeatedly when adding multiple + * items]. */ struct obj * -add_to_container(container, obj) -struct obj *container, *obj; +add_to_container(struct obj *container, struct obj *obj) { struct obj *otmp; if (obj->where != OBJ_FREE) - panic("add_to_container: obj not free"); + panic("add_to_container: obj where=%d, not free", obj->where); if (container->where != OBJ_INVENT && container->where != OBJ_MINVENT) obj_no_longer_held(obj); @@ -2110,61 +2695,72 @@ struct obj *container, *obj; } void -add_to_migration(obj) -struct obj *obj; +add_to_migration(struct obj *obj) { if (obj->where != OBJ_FREE) - panic("add_to_migration: obj not free"); + panic("add_to_migration: obj where=%d, not free", obj->where); + + if (obj->unpaid) /* caller should have changed unpaid item to stolen */ + impossible("unpaid object migrating to another level? [%s]", + simpleonames(obj)); + obj->no_charge = 0; /* was only relevant while inside a shop */ /* lock picking context becomes stale if it's for this object */ if (Is_container(obj)) maybe_reset_pick(obj); obj->where = OBJ_MIGRATING; - obj->nobj = migrating_objs; - migrating_objs = obj; + obj->nobj = gm.migrating_objs; + obj->omigr_from_dnum = u.uz.dnum; + obj->omigr_from_dlevel = u.uz.dlevel; + gm.migrating_objs = obj; } void -add_to_buried(obj) -struct obj *obj; +add_to_buried(struct obj *obj) { if (obj->where != OBJ_FREE) - panic("add_to_buried: obj not free"); + panic("add_to_buried: obj where=%d, not free", obj->where); obj->where = OBJ_BURIED; - obj->nobj = level.buriedobjlist; - level.buriedobjlist = obj; + obj->nobj = svl.level.buriedobjlist; + svl.level.buriedobjlist = obj; } -/* Recalculate the weight of this container and all of _its_ containers. */ -STATIC_OVL void -container_weight(container) -struct obj *container; +/* recalculate weight of object, which doesn't have to be a container + itself; if it is contained, recursively handle _its_ container(s) */ +void +container_weight(struct obj *object) { - container->owt = weight(container); - if (container->where == OBJ_CONTAINED) - container_weight(container->ocontainer); - /* - else if (container->where == OBJ_INVENT) - recalculate load delay here ??? - */ + object->owt = weight(object); + if (object->where == OBJ_CONTAINED) + container_weight(object->ocontainer); } /* - * Deallocate the object. _All_ objects should be run through here for - * them to be deallocated. + * Mark object to be deallocated. _All_ objects should be run through here + * for them to be deallocated. */ void -dealloc_obj(obj) -struct obj *obj; +dealloc_obj(struct obj *obj) { - if (obj->where != OBJ_FREE) - panic("dealloc_obj: obj not free"); + if (obj->otyp == BOULDER) + obj->next_boulder = 0; + if (obj->where == OBJ_DELETED) { + impossible("dealloc_obj: obj already deleted (type=%d)", obj->otyp); + return; + } else if (obj->where != OBJ_FREE && obj->where != OBJ_LUAFREE) { + panic("dealloc_obj: obj not free (type=%d, where=%d)", + obj->otyp, obj->where); + } if (obj->nobj) panic("dealloc_obj with nobj"); if (obj->cobj) panic("dealloc_obj with cobj"); + if (obj == &hands_obj) { + impossible("dealloc_obj with hands_obj"); + return; + } /* free up any timers attached to the object */ if (obj->timed) @@ -2178,24 +2774,80 @@ struct obj *obj; * list must track all objects that can have a light source * attached to it (and also requires lamplit to be set). */ - if (obj_sheds_light(obj)) + if (obj_sheds_light(obj)) { del_light_source(LS_OBJECT, obj_to_any(obj)); + obj->lamplit = 0; + } + + if (obj == gt.thrownobj) + gt.thrownobj = 0; + if (obj == gk.kickedobj) + gk.kickedobj = 0; + if (obj == svc.context.tin.tin) { + svc.context.tin.tin = (struct obj *) 0; + svc.context.tin.o_id = 0; + } - if (obj == thrownobj) - thrownobj = 0; - if (obj == kickedobj) - kickedobj = 0; + /* if obj came from the most recent splitobj(), it's no longer eligible + for unsplitobj(); perform inline clear_splitobjs() */ + if (obj->o_id == svc.context.objsplit.parent_oid + || obj->o_id == svc.context.objsplit.child_oid) + svc.context.objsplit.parent_oid = svc.context.objsplit.child_oid = 0; + + if (obj->lua_ref_cnt) { + /* obj is referenced from a lua script, let lua gc free it */ + obj->where = OBJ_LUAFREE; + return; + } + if (!program_state.freeingdata) { + /* mark object as deleted, put it into queue to be freed */ + obj->where = OBJ_DELETED; + obj->nobj = go.objs_deleted; + go.objs_deleted = obj; + } else { + /* when saving, there's no need to stage deletions on objs_deleted */ + dealloc_obj_real(obj); + } +} +/* actually deallocate the object */ +staticfn void +dealloc_obj_real(struct obj *obj) +{ if (obj->oextra) dealloc_oextra(obj); + + /* clear out of date information contained in the about-to-become + stale memory so that potential used-after-freed bugs (should never + happen) might trigger an object lost panic instead of continuing; + linking with a debugging malloc library is likely to do something + similar so this is mainly useful for ordinary malloc/free */ + *obj = cg.zeroobj; free((genericptr_t) obj); } +/* free all the objects marked for deletion */ +void +dobjsfree(void) +{ + struct obj *otmp; + + while (go.objs_deleted) { + otmp = go.objs_deleted; + go.objs_deleted = otmp->nobj; + if (otmp->where != OBJ_DELETED) + panic("dobjsfree: obj where=%d, not OBJ_DELETED", otmp->where); + obj_extract_self(otmp); + dealloc_obj_real(otmp); + } +} + /* create an object from a horn of plenty; mirrors bagotricks(makemon.c) */ int -hornoplenty(horn, tipping) -struct obj *horn; -boolean tipping; /* caller emptying entire contents; affects shop handling */ +hornoplenty( + struct obj *horn, + boolean tipping, /* caller emptying entire contents; affects shop mesgs */ + struct obj *targetbox) /* if non-Null, container to tip into */ { int objcount = 0; @@ -2203,6 +2855,10 @@ boolean tipping; /* caller emptying entire contents; affects shop handling */ impossible("bad horn o' plenty"); } else if (horn->spe < 1) { pline1(nothing_happens); + if (!horn->cknown) { + horn->cknown = 1; + update_inventory(); + } } else { struct obj *obj; const char *what; @@ -2210,10 +2866,14 @@ boolean tipping; /* caller emptying entire contents; affects shop handling */ consume_obj_charge(horn, !tipping); if (!rn2(13)) { obj = mkobj(POTION_CLASS, FALSE); - if (objects[obj->otyp].oc_magic) + if (objects[obj->otyp].oc_magic) { do { obj->otyp = rnd_class(POT_BOOZE, POT_WATER); } while (obj->otyp == POT_SICKNESS); + /* oil uses obj->age field differently from other potions */ + if (obj->otyp == POT_OIL) + fixup_oil(obj, (struct obj *) NULL); + } what = (obj->quan > 1L) ? "Some potions" : "A potion"; } else { obj = mkobj(FOOD_CLASS, FALSE); @@ -2230,7 +2890,7 @@ boolean tipping; /* caller emptying entire contents; affects shop handling */ confers ownership of the created item to the shopkeeper */ if (horn->unpaid) addtobill(obj, FALSE, FALSE, tipping); - /* if it ended up on bill, we don't want "(unpaid, N zorkids)" + /* if it ended up on bill, we don't want "(unpaid, N zorkmids)" being included in its formatted name during next message */ iflags.suppress_price++; if (!tipping) { @@ -2245,6 +2905,16 @@ boolean tipping; /* caller emptying entire contents; affects shop handling */ : "Oops! %s to the floor!", The(aobjnam(obj, "slip")), (char *) 0); nhUse(obj); + } else if (targetbox) { + add_to_container(targetbox, obj); + /* add to container doesn't update the weight */ + targetbox->owt = weight(targetbox); + /* item still in magic horn was weightless; when it's now in + a carried container, hero's encumbrance could change */ + if (carried(targetbox)) { + encumber_msg(); + update_inventory(); /* for contents count or wizweight */ + } } else { /* assumes this is taking place at hero's location */ if (!can_reach_floor(TRUE)) { @@ -2276,17 +2946,10 @@ static const char NEARDATA /* pline formats for insane_object() */ /* Check all object lists for consistency. */ void -obj_sanity_check() +obj_sanity_check(void) { - int x, y; - struct obj *obj; - - /* - * TODO: - * Should check whether the obj->bypass and/or obj->nomerge bits - * are set. Those are both used for temporary purposes and should - * be clear between moves. - */ + coordxy x, y; + struct obj *obj, *otop, *prevo; objlist_sanity(fobj, OBJ_FLOOR, "floor sanity"); @@ -2294,61 +2957,92 @@ obj_sanity_check() those objects should have already been sanity checked via the floor list so container contents are skipped here */ for (x = 0; x < COLNO; x++) - for (y = 0; y < ROWNO; y++) - for (obj = level.objects[x][y]; obj; obj = obj->nexthere) { + for (y = 0; y < ROWNO; y++) { + char at_fmt[BUFSZ]; + + otop = svl.level.objects[x][y]; + prevo = 0; + for (obj = otop; obj; prevo = obj, obj = prevo->nexthere) { /* should match ; <0,*> should always be empty */ if (obj->where != OBJ_FLOOR || x == 0 || obj->ox != x || obj->oy != y) { - char at_fmt[BUFSZ]; - Sprintf(at_fmt, "%%s obj@<%d,%d> %%s %%s: %%s@<%d,%d>", x, y, obj->ox, obj->oy); insane_object(obj, at_fmt, "location sanity", (struct monst *) 0); + + /* when one or more boulders are present, they should always + be at the top of their pile; also never in water or lava */ + } else if (obj->otyp == BOULDER) { + if (prevo && prevo->otyp != BOULDER) { + Sprintf(at_fmt, + "%%s boulder@<%d,%d> %%s %%s: not on top", + x, y); + insane_object(obj, at_fmt, "boulder sanity", + (struct monst *) 0); + } + if (is_pool_or_lava(x, y)) { + Sprintf(at_fmt, + "%%s boulder@<%d,%d> %%s %%s: on/in %s", + x, y, is_pool(x, y) ? "water" : "lava"); + insane_object(obj, at_fmt, "boulder sanity", + (struct monst *) 0); + } } } + } - objlist_sanity(invent, OBJ_INVENT, "invent sanity"); - objlist_sanity(migrating_objs, OBJ_MIGRATING, "migrating sanity"); - objlist_sanity(level.buriedobjlist, OBJ_BURIED, "buried sanity"); - objlist_sanity(billobjs, OBJ_ONBILL, "bill sanity"); + objlist_sanity(gi.invent, OBJ_INVENT, "invent sanity"); + objlist_sanity(gm.migrating_objs, OBJ_MIGRATING, "migrating sanity"); + objlist_sanity(svl.level.buriedobjlist, OBJ_BURIED, "buried sanity"); + objlist_sanity(gb.billobjs, OBJ_ONBILL, "bill sanity"); + objlist_sanity(go.objs_deleted, OBJ_DELETED, "deleted object sanity"); mon_obj_sanity(fmon, "minvent sanity"); - mon_obj_sanity(migrating_mons, "migrating minvent sanity"); + mon_obj_sanity(gm.migrating_mons, "migrating minvent sanity"); /* monsters temporarily in transit; they should have arrived with hero by the time we get called */ - if (mydogs) { - impossible("mydogs sanity [not empty]"); - mon_obj_sanity(mydogs, "mydogs minvent sanity"); + if (gm.mydogs) { + impossible("gm.mydogs sanity [not empty]"); + mon_obj_sanity(gm.mydogs, "mydogs minvent sanity"); } /* objects temporarily freed from invent/floor lists; they should have arrived somewhere by the time we get called */ - if (thrownobj) - insane_object(thrownobj, ofmt3, "thrownobj sanity", + if (gt.thrownobj) + insane_object(gt.thrownobj, ofmt3, "thrownobj sanity", (struct monst *) 0); - if (kickedobj) - insane_object(kickedobj, ofmt3, "kickedobj sanity", + if (gk.kickedobj) + insane_object(gk.kickedobj, ofmt3, "kickedobj sanity", (struct monst *) 0); - /* current_wand isn't removed from invent while in use, but should + /* returning_missile temporarily remembers thrownobj and should be + Null in between moves */ + if (iflags.returning_missile) + insane_object(gk.kickedobj, ofmt3, "returning_missile sanity", + (struct monst *) 0); + /* gc.current_wand isn't removed from invent while in use, but should be Null between moves when we're called */ - if (current_wand) - insane_object(current_wand, ofmt3, "current_wand sanity", + if (gc.current_wand) + insane_object(gc.current_wand, ofmt3, "current_wand sanity", (struct monst *) 0); } /* sanity check for objects on specified list (fobj, &c) */ -STATIC_OVL void -objlist_sanity(objlist, wheretype, mesg) -struct obj *objlist; -int wheretype; -const char *mesg; +staticfn void +objlist_sanity(struct obj *objlist, int wheretype, const char *mesg) { struct obj *obj; for (obj = objlist; obj; obj = obj->nobj) { if (obj->where != wheretype) insane_object(obj, ofmt0, mesg, (struct monst *) 0); + if (obj->where == OBJ_INVENT && obj->how_lost != LOST_NONE) { + char lostbuf[40]; + + /* %d: bitfield is unsigned but narrow, so promotes to int */ + Sprintf(lostbuf, "how_lost=%d obj in inventory!", obj->how_lost); + insane_object(obj, ofmt0, lostbuf, (struct monst *) 0); + } if (Has_contents(obj)) { if (wheretype == OBJ_ONBILL) /* containers on shop bill should always be empty */ @@ -2356,6 +3050,9 @@ const char *mesg; mesg, (struct monst *) 0); check_contained(obj, mesg); } + if (obj->unpaid || obj->no_charge) { + shop_obj_sanity(obj, mesg); + } if (obj->owornmask) { char maskbuf[40]; boolean bc_ok = FALSE; @@ -2373,6 +3070,7 @@ const char *mesg; /* note: ball and chain can also be OBJ_FREE, but not across turns so this sanity check shouldn't encounter that */ bc_ok = TRUE; + FALLTHROUGH; /*FALLTHRU*/ default: if ((obj != uchain && obj != uball) || !bc_ok) { @@ -2383,17 +3081,127 @@ const char *mesg; } break; } - if (obj->globby) - check_glob(obj, mesg); } - } + if (obj->otyp == LEASH && obj->leashmon) { + char buf[BUFSZ]; + struct monst *mtmp = find_mid(obj->leashmon, FM_FMON); + + if (obj->where == OBJ_INVENT) { + if (!mtmp) { /* found leash with phantom mon */ + Sprintf(buf, "leashmon=%u no monst,", + (unsigned) obj->leashmon); + insane_object(obj, ofmt0, buf, (struct monst *) 0); + } else if (!mtmp->mleashed) { /* found leashed mon + * not flagged as leashed */ + Sprintf(buf, "leashmon=%u %s not leashed,", + (unsigned) obj->leashmon, mon_pmname(mtmp)); + insane_object(obj, ofmt0, buf, (struct monst *) 0); + } + + /* have to explicitly exclude migrating_objs because the + obj->migr_species field overlays obj->corpsenm just like + obj->leashmon does, so obj->leashmon and consequently 'mtmp' + might be inaccurate for any leash found on migrating_objs */ + } else if (obj->where != OBJ_MIGRATING) { + struct monst *mtmp2 = (obj->where == OBJ_MINVENT) + ? obj->ocarry : (struct monst *) 0; + + if (mtmp) { /* found monst leashed by non-invent leash */ + Sprintf(buf, "leashmon:%u %s leashed by %s leash,", + (unsigned) obj->leashmon, + mon_pmname(mtmp), where_name(obj)); + insane_object(obj, ofmt0, buf, mtmp2); + } else { /* found non-invent leash with m_id of phantom mon */ + Sprintf(buf, "leashmon:%u no monst for %s leash,", + (unsigned) obj->leashmon, where_name(obj)); + insane_object(obj, ofmt0, buf, mtmp2); + } + } + } + if (obj->globby) + check_glob(obj, mesg); + /* temporary flags that might have been set but which should + be clear by the time this sanity check is taking place */ + if (obj->in_use || obj->bypass || obj->nomerge + || (obj->otyp == BOULDER && obj->next_boulder)) + insane_obj_bits(obj, (struct monst *) 0); + } +} + +/* check obj->unpaid and obj->no_charge for shop sanity; caller has + verified that at least one of them is set */ +staticfn void +shop_obj_sanity(struct obj *obj, const char *mesg) +{ + struct obj *otop; + struct monst *shkp; + const char *why; + boolean costly, costlytoo; + coordxy x = 0, y = 0; + + /* if contained, get top-most container; we needs its location */ + otop = obj; + while (otop->where == OBJ_CONTAINED) + otop = otop->ocontainer; + /* get obj's or its container's location; do not update obj->ox,oy + or otop->ox,oy because that would cause sanity checking to + produce side-effects that won't occur when not sanity checking; + no need for CONTAINED_TOO because we have a top level container */ + (void) get_obj_location(otop, &x, &y, BURIED_TOO); + + /* these will always be needed for the normal case, so don't bother + waiting until we find an insanity to fetch them */ + shkp = find_objowner(obj, x, y); + if (shkp && obj->where == OBJ_ONBILL) + x = shkp->mx, y = shkp->my; + costly = costly_spot(x, y); + costlytoo = costly_adjacent(shkp, x, y); + + why = (const char *) 0; + if (obj->no_charge && obj->unpaid) { + why = "%s obj both unpaid and no_charge! %s %s: %s"; + } else if (obj->unpaid) { + /* unpaid is only applicable for directly carried objects, for + objects inside carried containers, for used up items on the + billobjs list, and for floor items outside the shop proper + but within the shop boundary (walls, door, "free spot") and + for objects moved from such spots into the shop proper by + repair of shop walls or items buried while on boundary */ + if (otop->where != OBJ_INVENT + && obj->where != OBJ_ONBILL /* when on bill, obj==otop */ + && ((otop->where != OBJ_FLOOR && obj->where != OBJ_BURIED) + || !(costly || costlytoo))) + why = "%s unpaid obj not carried! %s %s: %s"; + else if (!costly && !costlytoo) + why = "%s unpaid obj not inside tended shop! %s %s: %s"; + else if (!shkp) + why = "%s unpaid obj inside untended shop! %s %s: %s"; + else if (!onshopbill(obj, shkp, TRUE)) + why = "%s unpaid obj not on shop bill! %s %s: %s"; + } else if (obj->no_charge) { + /* no_charge is only applicable for floor objects in shops, for + objects inside floor containers in shops, and for objects buried + beneath the shop floor or carried by a monster (usually pet) */ + if (otop->where != OBJ_FLOOR + && otop->where != OBJ_BURIED + && otop->where != OBJ_MINVENT) + why = "%s no_charge obj not on floor! %s %s: %s"; + else if (!costly && !costlytoo) + why = "%s no_charge obj not inside tended shop! %s %s: %s"; + else if (!shkp) + why = "%s no_charge obj inside untended shop! %s %s: %s"; + else if (onshopbill(obj, shkp, TRUE)) + why = "%s no_charge obj on shop bill! %s %s: %s"; + } + if (why) + insane_object(obj, why, mesg, + mcarried(otop) ? otop->ocarry : (struct monst *) 0); + return; } /* sanity check for objects carried by all monsters in specified list */ -STATIC_OVL void -mon_obj_sanity(monlist, mesg) -struct monst *monlist; -const char *mesg; +staticfn void +mon_obj_sanity(struct monst *monlist, const char *mesg) { struct monst *mon; struct obj *obj, *mwep; @@ -2401,7 +3209,7 @@ const char *mesg; for (mon = monlist; mon; mon = mon->nmon) { if (DEADMONSTER(mon)) continue; - mwep = MON_WEP(mon); + mwep = MON_WEP(mon); /* mon->mw */ if (mwep) { if (!mcarried(mwep)) insane_object(mwep, mfmt1, mesg, mon); @@ -2416,19 +3224,76 @@ const char *mesg; if (obj->globby) check_glob(obj, mesg); check_contained(obj, mesg); + if (obj->unpaid || obj->no_charge) + shop_obj_sanity(obj, mesg); + if (obj->in_use || obj->bypass || obj->nomerge + || (obj->otyp == BOULDER && obj->next_boulder)) + insane_obj_bits(obj, mon); + if (obj == mwep) + mwep = (struct obj *) 0; } + if (mwep) { + /* this is a monster check rather than an object check, but doing + it here avoids making an extra pass through mon's minvent; + if the full pass through that list hasn't reset mwep to Null, + then mwep isn't in that list where it should be */ + impossible("monst (%s: %u) wielding %s (%u) not in %s inventory", + pmname(mon->data, Mgender(mon)), mon->m_id, + safe_typename(mwep->otyp), mwep->o_id, mhis(mon)); + + } + } +} + +staticfn void +insane_obj_bits(struct obj *obj, struct monst *mon) +{ + unsigned o_in_use, o_bypass, o_nomerge, o_boulder; + + if (obj->where == OBJ_DELETED) + return; /* skip bit checking for deleted objects */ + + o_in_use = obj->in_use; + o_bypass = obj->bypass; + /* having obj->nomerge be set might be intentional */ + o_nomerge = (obj->nomerge && !nomerge_exception(obj)); + /* next_boulder is only for object name formatting when pushing + boulders and should be reset by time of next sanity check */ + o_boulder = (obj->otyp == BOULDER && obj->next_boulder); + + if (o_in_use || o_bypass || o_nomerge || o_boulder) { + char infobuf[QBUFSZ]; + + Sprintf(infobuf, "flagged%s%s%s%s", + o_in_use ? " in_use" : "", + o_bypass ? " bypass" : "", + o_nomerge ? " nomerge" : "", + o_boulder ? " nxtbldr" : ""); + insane_object(obj, ofmt0, infobuf, mon); } } +/* does 'obj' use the 'nomerge' flag persistently? */ +staticfn boolean +nomerge_exception(struct obj *obj) +{ + /* special prize objects for achievement tracking are set 'nomerge' + until they get picked up by the hero */ + if (is_mines_prize(obj) || is_soko_prize(obj)) + return TRUE; + + return FALSE; +} + /* This must stay consistent with the defines in obj.h. */ -static const char *obj_state_names[NOBJ_STATES] = { "free", "floor", - "contained", "invent", - "minvent", "migrating", - "buried", "onbill" }; +static const char *const obj_state_names[NOBJ_STATES] = { + "free", "floor", "contained", "invent", + "minvent", "migrating", "buried", "onbill", + "luafree", "deleted", +}; -STATIC_OVL const char * -where_name(obj) -struct obj *obj; +staticfn const char * +where_name(struct obj *obj) { static char unknown[32]; /* big enough to handle rogue 64-bit int */ int where; @@ -2443,11 +3308,14 @@ struct obj *obj; return obj_state_names[where]; } -STATIC_OVL void -insane_object(obj, fmt, mesg, mon) -struct obj *obj; -const char *fmt, *mesg; -struct monst *mon; +DISABLE_WARNING_FORMAT_NONLITERAL + +staticfn void +insane_object( + struct obj *obj, + const char *fmt, + const char *mesg, + struct monst *mon) { const char *objnm, *monnm; char altfmt[BUFSZ]; @@ -2463,48 +3331,47 @@ struct monst *mon; if (mon) monnm = x_monnam(mon, ARTICLE_A, (char *) 0, EXACT_NAME, TRUE); impossible(altfmt, mesg, fmt_ptr((genericptr_t) obj), where_name(obj), - objnm, fmt_ptr((genericptr_t) mon), monnm); + objnm, fmt_ptr((genericptr_t) mon), monnm); } else { impossible(fmt, mesg, fmt_ptr((genericptr_t) obj), where_name(obj), objnm); } } -/* - * Initialize a dummy obj with just enough info - * to allow some of the tests in obj.h that - * take an obj pointer to work. - */ +RESTORE_WARNING_FORMAT_NONLITERAL + +/* initialize a dummy obj with just enough info to allow some of the tests in + obj.h that take an obj pointer to work; used when applying a stethoscope + toward a mimic mimicking an object */ struct obj * -init_dummyobj(obj, otyp, oquan) -struct obj *obj; -short otyp; -long oquan; +init_dummyobj(struct obj *obj, short otyp, long oquan) { if (obj) { - *obj = zeroobj; + *obj = cg.zeroobj; obj->otyp = otyp; obj->oclass = objects[otyp].oc_class; /* obj->dknown = 0; */ /* suppress known except for amulets (needed for fakes & real AoY) */ obj->known = (obj->oclass == AMULET_CLASS) - ? obj->known + ? obj->known /* default is "on" for types which don't use it */ : !objects[otyp].oc_uses_known; obj->quan = oquan ? oquan : 1L; obj->corpsenm = NON_PM; /* suppress statue and figurine details */ + if (obj->otyp == LEASH) + obj->leashmon = 0; /* overloads corpsenm, avoid NON_PM */ + if (obj->otyp == BOULDER) + obj->next_boulder = 0; /* overloads corpsenm, avoid NON_PM */ /* but suppressing fruit details leads to "bad fruit #0" */ if (obj->otyp == SLIME_MOLD) - obj->spe = context.current_fruit; + obj->spe = svc.context.current_fruit; } return obj; } /* obj sanity check: check objects inside container */ -STATIC_OVL void -check_contained(container, mesg) -struct obj *container; -const char *mesg; +staticfn void +check_contained(struct obj *container, const char *mesg) { struct obj *obj; /* big enough to work with, not too big to blow out stack in recursion */ @@ -2549,18 +3416,22 @@ const char *mesg; } /* called when 'obj->globby' is set so we don't recheck it here */ -STATIC_OVL void -check_glob(obj, mesg) -struct obj *obj; -const char *mesg; +staticfn void +check_glob(struct obj *obj, const char *mesg) { #define LOWEST_GLOB GLOB_OF_GRAY_OOZE #define HIGHEST_GLOB GLOB_OF_BLACK_PUDDING if (obj->quan != 1L || obj->owt == 0 || obj->otyp < LOWEST_GLOB || obj->otyp > HIGHEST_GLOB +#if 0 /* + * This was relevant before the shrink_glob timer was adopted but + * now any glob could have a weight that isn't a multiple of 20. + */ /* a partially eaten glob could have any non-zero weight but an intact one should weigh an exact multiple of base weight (20) */ - || ((obj->owt % objects[obj->otyp].oc_weight) != 0 && !obj->oeaten)) { + || ((obj->owt % objects[obj->otyp].oc_weight) != 0 && !obj->oeaten) +#endif + ) { char mesgbuf[BUFSZ], globbuf[QBUFSZ]; Sprintf(globbuf, " glob %d,quan=%ld,owt=%u ", @@ -2572,9 +3443,8 @@ const char *mesg; } /* check an object in hero's or monster's inventory which has worn mask set */ -STATIC_OVL void -sanity_check_worn(obj) -struct obj *obj; +staticfn void +sanity_check_worn(struct obj *obj) { #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) || defined(DEBUG) static unsigned long wearbits[] = { @@ -2770,8 +3640,7 @@ struct obj *obj; * wrapper to make "near this object" convenient */ struct obj * -obj_nexto(otmp) -struct obj *otmp; +obj_nexto(struct obj *otmp) { if (!otmp) { impossible("obj_nexto: wasn't given an object to check"); @@ -2789,10 +3658,7 @@ struct obj *otmp; * reliably predict which one we want to 'find' first */ struct obj * -obj_nexto_xy(obj, x, y, recurs) -struct obj *obj; -int x, y; -boolean recurs; +obj_nexto_xy(struct obj *obj, coordxy x, coordxy y, boolean recurs) { struct obj *otmp; int fx, fy, ex, ey, otyp = obj->otyp; @@ -2829,13 +3695,11 @@ boolean recurs; } /* - * Causes one object to absorb another, increasing - * weight accordingly. Frees obj2; obj1 remains and - * is returned. + * Causes one object to absorb another, increasing weight + * accordingly. Frees obj2; obj1 remains and is returned. */ struct obj * -obj_absorb(obj1, obj2) -struct obj **obj1, **obj2; +obj_absorb(struct obj **obj1, struct obj **obj2) { struct obj *otmp1, *otmp2; int o1wt, o2wt; @@ -2859,14 +3723,25 @@ struct obj **obj1, **obj2; o2wt = otmp2->oeaten ? otmp2->oeaten : otmp2->owt; /* averaging the relative ages is less likely to overflow than averaging the absolute ages directly */ - agetmp = (((moves - otmp1->age) * o1wt - + (moves - otmp2->age) * o2wt) + agetmp = (((svm.moves - otmp1->age) * o1wt + + (svm.moves - otmp2->age) * o2wt) / (o1wt + o2wt)); - otmp1->age = moves - agetmp; /* conv. relative back to absolute */ + /* convert relative age back to absolute age */ + otmp1->age = svm.moves - agetmp; otmp1->owt += o2wt; if (otmp1->oeaten || otmp2->oeaten) otmp1->oeaten = o1wt + o2wt; otmp1->quan = 1L; + if (otmp1->globby && otmp2->globby) { + /* average (not weighted, no pun intended) the two globs' + shrink timers and use that to give otmp1 a new timer */ + long tm1 = stop_timer(SHRINK_GLOB, obj_to_any(otmp1)), + tm2 = stop_timer(SHRINK_GLOB, obj_to_any(otmp2)); + + tm1 = ((tm1 ? tm1 : 25L) + (tm2 ? tm2 : 25L) + 1L) / 2L; + start_glob_timeout(otmp1, tm1); + } + /* get rid of second glob, return augmented first one */ obj_extract_self(otmp2); dealloc_obj(otmp2); *obj2 = (struct obj *) 0; @@ -2881,17 +3756,19 @@ struct obj **obj1, **obj2; /* * Causes the heavier object to absorb the lighter object in * most cases, but if one object is OBJ_FREE and the other is - * on the floor, the floor object goes first. + * on the floor, the floor object goes first. Note that when + * a globby monster dies, its corpse (new glob) will be created + * on the floor; when a glob is dropped, thrown, or kicked it + * will be free at the time obj_meld() gets called. * - * wrapper for obj_absorb so that floor_effects works more - * cleanly (since we don't know which we want to stay around) + * Wrapper for obj_absorb() so that floor_effects works more + * cleanly (since we don't know which we want to stay around). */ struct obj * -obj_meld(obj1, obj2) -struct obj **obj1, **obj2; +obj_meld(struct obj **obj1, struct obj **obj2) { struct obj *otmp1, *otmp2, *result = 0; - int ox, oy; + int ox, oy; /* coordinates for the glob that goes away */ if (obj1 && obj2) { otmp1 = *obj1; @@ -2921,8 +3798,14 @@ struct obj **obj1, **obj2; } /* callers really ought to take care of this; glob melding is a bookkeeping issue rather than a display one */ - if (ox && cansee(ox, oy)) - newsym(ox, oy); + if (ox) { + if (cansee(ox, oy)) + newsym(ox, oy); + /* and this; a hides-under monster might be hiding under + the glob that went away; if there's nothing else there + to hide under, force it out of hiding */ + maybe_unhide_at(ox, oy); + } } } else { impossible("obj_meld: not called with two actual objects"); @@ -2932,9 +3815,7 @@ struct obj **obj1, **obj2; /* give a message if hero notices two globs merging [used to be in pline.c] */ void -pudding_merge_message(otmp, otmp2) -struct obj *otmp; -struct obj *otmp2; +pudding_merge_message(struct obj *otmp, struct obj *otmp2) { boolean visible = (cansee(otmp->ox, otmp->oy) || cansee(otmp2->ox, otmp2->oy)), @@ -2962,6 +3843,7 @@ struct obj *otmp2; inpack ? " inside your pack" : ""); } } else { + Soundeffect(se_faint_sloshing, 25); You_hear("a faint sloshing sound."); } } diff --git a/src/mkroom.c b/src/mkroom.c index 9945ec810..6fc71f703 100644 --- a/src/mkroom.c +++ b/src/mkroom.c @@ -1,11 +1,11 @@ -/* NetHack 3.6 mkroom.c $NHDT-Date: 1446887530 2015/11/07 09:12:10 $ $NHDT-Branch: master $:$NHDT-Revision: 1.24 $ */ +/* NetHack 5.0 mkroom.c $NHDT-Date: 1613086701 2021/02/11 23:38:21 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.52 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2011. */ /* NetHack may be freely redistributed. See license for details. */ /* * Entry points: - * mkroom() -- make and stock a room of a given type + * do_mkroom() -- make and stock a room of a given type * nexttodoor() -- return TRUE if adjacent to a door * has_dnstairs() -- return TRUE if given room has a down staircase * has_upstairs() -- return TRUE if given room has an up staircase @@ -17,25 +17,31 @@ #include "hack.h" -STATIC_DCL boolean FDECL(isbig, (struct mkroom *)); -STATIC_DCL struct mkroom *FDECL(pick_room, (BOOLEAN_P)); -STATIC_DCL void NDECL(mkshop), FDECL(mkzoo, (int)), NDECL(mkswamp); -STATIC_DCL void NDECL(mktemple); -STATIC_DCL coord *FDECL(shrine_pos, (int)); -STATIC_DCL struct permonst *NDECL(morguemon); -STATIC_DCL struct permonst *NDECL(squadmon); -STATIC_DCL void FDECL(save_room, (int, struct mkroom *)); -STATIC_DCL void FDECL(rest_room, (int, struct mkroom *)); +#ifndef SFCTOOL +staticfn boolean isbig(struct mkroom *); +staticfn struct mkroom *pick_room(boolean); +staticfn void mkshop(void), mkzoo(int), mkswamp(void); +staticfn void mk_zoo_thronemon(coordxy, coordxy); +staticfn void mktemple(void); +staticfn coord *shrine_pos(int); +staticfn struct permonst *morguemon(void); +staticfn struct permonst *squadmon(void); +#endif /* SFCTOOL */ + +staticfn void save_room(NHFILE *, struct mkroom *); +staticfn void rest_room(NHFILE *, struct mkroom *); + +#ifndef SFCTOOL +staticfn boolean invalid_shop_shape(struct mkroom *sroom); #define sq(x) ((x) * (x)) extern const struct shclass shtypes[]; /* defined in shknam.c */ -STATIC_OVL boolean -isbig(sroom) -register struct mkroom *sroom; +staticfn boolean +isbig(struct mkroom *sroom) { - register int area = (sroom->hx - sroom->lx + 1) + int area = (sroom->hx - sroom->lx + 1) * (sroom->hy - sroom->ly + 1); return (boolean) (area > 20); @@ -43,12 +49,11 @@ register struct mkroom *sroom; /* make and stock a room of a given type */ void -mkroom(roomtype) -int roomtype; +do_mkroom(int roomtype) { - if (roomtype >= SHOPBASE) + if (roomtype >= SHOPBASE) { mkshop(); /* someday, we should be able to specify shop type */ - else + } else { switch (roomtype) { case COURT: mkzoo(COURT); @@ -83,21 +88,22 @@ int roomtype; default: impossible("Tried to make a room of type %d.", roomtype); } + } } /* Added for NLE. */ extern char* nle_getenv(const char *); -STATIC_OVL void -mkshop() +staticfn void +mkshop(void) { - register struct mkroom *sroom; + struct mkroom *sroom; int i = -1; char *ep = (char *) 0; /* (init == lint suppression) */ /* first determine shoptype */ if (wizard) { -#ifndef MAC + /* NLE: use NLE-specific function*/ ep = nle_getenv("SHOPTYPE"); if (ep) { if (*ep == 'z' || *ep == 'Z') { @@ -150,27 +156,33 @@ mkshop() else i = -1; } -#endif } -#ifndef MAC -gottype: -#endif - for (sroom = &rooms[0];; sroom++) { + + gottype: + for (sroom = &svr.rooms[0];; sroom++) { + /* return from this loop: cannot find any eligible room to be a shop + * continue: sroom is ineligible + * break: sroom is eligible + */ if (sroom->hx < 0) return; - if (sroom - rooms >= nroom) { - pline("rooms not closed by -1?"); + if (sroom - svr.rooms >= svn.nroom) { + impossible("rooms[] not closed by -1?"); return; } if (sroom->rtype != OROOM) continue; if (has_dnstairs(sroom) || has_upstairs(sroom)) continue; - if ((wizard && ep && sroom->doorct != 0) || sroom->doorct == 1) - break; + if (sroom->doorct == 1 || (wizard && ep && sroom->doorct != 0)) { + if (invalid_shop_shape(sroom)) + continue; + else + break; + } } if (!sroom->rlit) { - int x, y; + coordxy x, y; for (x = sroom->lx - 1; x <= sroom->hx + 1; x++) for (y = sroom->ly - 1; y <= sroom->hy + 1; y++) @@ -179,7 +191,7 @@ mkshop() } if (i < 0) { /* shoptype not yet determined */ - register int j; + int j; /* pick a shop type at random */ for (j = rnd(100), i = 0; (j -= shtypes[i].prob) > 0; i++) @@ -201,21 +213,22 @@ mkshop() topologize(sroom); #endif - /* stock the room with a shopkeeper and artifacts */ - stock_room(i, sroom); + /* The shop used to be stocked here, but this no longer happens--all we do + is set its rtype, and it gets stocked at the end of makelevel() along + with other special rooms. */ + sroom->needfill = FILL_NORMAL; } /* pick an unused room, preferably with only one door */ -STATIC_OVL struct mkroom * -pick_room(strict) -register boolean strict; +staticfn struct mkroom * +pick_room(boolean strict) { - register struct mkroom *sroom; - register int i = nroom; + struct mkroom *sroom; + int i = svn.nroom; - for (sroom = &rooms[rn2(nroom)]; i--; sroom++) { - if (sroom == &rooms[nroom]) - sroom = &rooms[0]; + for (sroom = &svr.rooms[rn2(svn.nroom)]; i--; sroom++) { + if (sroom == &svr.rooms[svn.nroom]) + sroom = &svr.rooms[0]; if (sroom->hx < 0) return (struct mkroom *) 0; if (sroom->rtype != OROOM) @@ -231,27 +244,27 @@ register boolean strict; return (struct mkroom *) 0; } -STATIC_OVL void -mkzoo(type) -int type; +staticfn void +mkzoo(int type) { - register struct mkroom *sroom; + struct mkroom *sroom; if ((sroom = pick_room(FALSE)) != 0) { sroom->rtype = type; - fill_zoo(sroom); + /* room does not get stocked at this time - it will get stocked at the + * end of makelevel() */ + sroom->needfill = FILL_NORMAL; } } -void -mk_zoo_thronemon(x,y) -int x,y; +staticfn void +mk_zoo_thronemon(coordxy x, coordxy y) { int i = rnd(level_difficulty()); - int pm = (i > 9) ? PM_OGRE_KING - : (i > 5) ? PM_ELVENKING - : (i > 2) ? PM_DWARF_KING - : PM_GNOME_KING; + int pm = (i > 9) ? PM_OGRE_TYRANT + : (i > 5) ? PM_ELVEN_MONARCH + : (i > 2) ? PM_DWARF_RULER + : PM_GNOME_RULER; struct monst *mon = makemon(&mons[pm], x, y, NO_MM_FLAGS); if (mon) { @@ -264,19 +277,21 @@ int x,y; } void -fill_zoo(sroom) -struct mkroom *sroom; +fill_zoo(struct mkroom *sroom) { struct monst *mon; - register int sx, sy, i; - int sh, tx = 0, ty = 0, goldlim = 0, type = sroom->rtype; - int rmno = (int) ((sroom - rooms) + ROOMOFFSET); + int sx, sy, i; + int sh, goldlim = 0, type = sroom->rtype; + coordxy tx = 0, ty = 0; + int rmno = (int) ((sroom - svr.rooms) + ROOMOFFSET); coord mm; + /* Note: This doesn't check needfill; it assumes the caller has already + done that. */ sh = sroom->fdoor; switch (type) { case COURT: - if (level.flags.is_maze_lev) { + if (svl.level.flags.is_maze_lev) { for (tx = sroom->lx; tx <= sroom->hx; tx++) for (ty = sroom->ly; ty <= sroom->hy; ty++) if (IS_THRONE(levl[tx][ty].typ)) @@ -284,11 +299,11 @@ struct mkroom *sroom; } i = 100; do { /* don't place throne on top of stairs */ - (void) somexy(sroom, &mm); + (void) somexyspace(sroom, &mm); tx = mm.x; ty = mm.y; - } while (occupied((xchar) tx, (xchar) ty) && --i > 0); - throne_placed: + } while (occupied(tx, ty) && --i > 0); + throne_placed: mk_zoo_thronemon(tx, ty); break; case BEEHIVE: @@ -297,7 +312,7 @@ struct mkroom *sroom; if (sroom->irregular) { /* center might not be valid, so put queen elsewhere */ if ((int) levl[tx][ty].roomno != rmno || levl[tx][ty].edge) { - (void) somexy(sroom, &mm); + (void) somexyspace(sroom, &mm); tx = mm.x; ty = mm.y; } @@ -314,15 +329,18 @@ struct mkroom *sroom; if (sroom->irregular) { if ((int) levl[sx][sy].roomno != rmno || levl[sx][sy].edge || (sroom->doorct - && distmin(sx, sy, doors[sh].x, doors[sh].y) <= 1)) + && (distmin(sx, sy, svd.doors[sh].x, svd.doors[sh].y) + <= 1))) continue; } else if (!SPACE_POS(levl[sx][sy].typ) || (sroom->doorct - && ((sx == sroom->lx && doors[sh].x == sx - 1) - || (sx == sroom->hx && doors[sh].x == sx + 1) - || (sy == sroom->ly && doors[sh].y == sy - 1) + && ((sx == sroom->lx && svd.doors[sh].x == sx - 1) + || (sx == sroom->hx && svd.doors[sh].x + == sx + 1) + || (sy == sroom->ly && svd.doors[sh].y + == sy - 1) || (sy == sroom->hy - && doors[sh].y == sy + 1)))) + && svd.doors[sh].y == sy + 1)))) continue; /* don't place monster on explicitly placed throne */ if (type == COURT && IS_THRONE(levl[sx][sy].typ)) @@ -344,7 +362,7 @@ struct mkroom *sroom; : (type == ANTHOLE) ? antholemon() : (struct permonst *) 0, - sx, sy, MM_ASLEEP); + sx, sy, MM_ASLEEP | MM_NOGRP); if (mon) { mon->msleeping = 1; if (type == COURT && mon->mpeaceful) { @@ -356,7 +374,8 @@ struct mkroom *sroom; case ZOO: case LEPREHALL: if (sroom->doorct) { - int distval = dist2(sx, sy, doors[sh].x, doors[sh].y); + int distval = dist2(sx, sy, + svd.doors[sh].x, svd.doors[sh].y); i = sq(distval); } else i = goldlim; @@ -406,7 +425,7 @@ struct mkroom *sroom; case COURT: { struct obj *chest, *gold; levl[tx][ty].typ = THRONE; - (void) somexy(sroom, &mm); + (void) somexyspace(sroom, &mm); gold = mksobj(GOLD_PIECE, TRUE, FALSE); gold->quan = (long) rn1(50 * level_difficulty(), 10); gold->owt = weight(gold); @@ -415,33 +434,33 @@ struct mkroom *sroom; add_to_container(chest, gold); chest->owt = weight(chest); chest->spe = 2; /* so it can be found later */ - level.flags.has_court = 1; + svl.level.flags.has_court = 1; break; } case BARRACKS: - level.flags.has_barracks = 1; + svl.level.flags.has_barracks = 1; break; case ZOO: - level.flags.has_zoo = 1; + svl.level.flags.has_zoo = 1; break; case MORGUE: - level.flags.has_morgue = 1; + svl.level.flags.has_morgue = 1; break; case SWAMP: - level.flags.has_swamp = 1; + svl.level.flags.has_swamp = 1; break; case BEEHIVE: - level.flags.has_beehive = 1; + svl.level.flags.has_beehive = 1; break; } } /* make a swarm of undead around mm */ void -mkundead(mm, revive_corpses, mm_flags) -coord *mm; -boolean revive_corpses; -int mm_flags; +mkundead( + coord *mm, + boolean revive_corpses, + int mm_flags) { int cnt = (level_difficulty() + 1) / 10 + rnd(5); struct permonst *mdat; @@ -456,13 +475,13 @@ int mm_flags; || !revive(otmp, FALSE))) (void) makemon(mdat, cc.x, cc.y, mm_flags); } - level.flags.graveyard = TRUE; /* reduced chance for undead corpse */ + svl.level.flags.graveyard = TRUE; /* reduced chance for undead corpse */ } -STATIC_OVL struct permonst * -morguemon() +staticfn struct permonst * +morguemon(void) { - register int i = rn2(100), hd = rn2(level_difficulty()); + int i = rn2(100), hd = rn2(level_difficulty()); if (hd > 10 && i < 10) { if (Inhell || In_endgame(&u.uz)) { @@ -484,7 +503,7 @@ morguemon() } struct permonst * -antholemon() +antholemon(void) { int mtyp, indx, trycnt = 0; @@ -505,31 +524,39 @@ antholemon() break; } /* try again if chosen type has been genocided or used up */ - } while (++trycnt < 3 && (mvitals[mtyp].mvflags & G_GONE)); + } while (++trycnt < 3 && (svm.mvitals[mtyp].mvflags & G_GONE)); - return ((mvitals[mtyp].mvflags & G_GONE) ? (struct permonst *) 0 + return ((svm.mvitals[mtyp].mvflags & G_GONE) ? (struct permonst *) 0 : &mons[mtyp]); } -STATIC_OVL void -mkswamp() /* Michiel Huisjes & Fred de Wilde */ +staticfn void +mkswamp(void) /* Michiel Huisjes & Fred de Wilde */ { - register struct mkroom *sroom; - register int sx, sy, i, eelct = 0; + struct mkroom *sroom; + int i, eelct = 0; + coordxy sx, sy; + int rmno; for (i = 0; i < 5; i++) { /* turn up to 5 rooms swampy */ - sroom = &rooms[rn2(nroom)]; + sroom = &svr.rooms[rn2(svn.nroom)]; if (sroom->hx < 0 || sroom->rtype != OROOM || has_upstairs(sroom) || has_dnstairs(sroom)) continue; + rmno = (int)(sroom - svr.rooms) + ROOMOFFSET; + /* satisfied; make a swamp */ sroom->rtype = SWAMP; for (sx = sroom->lx; sx <= sroom->hx; sx++) - for (sy = sroom->ly; sy <= sroom->hy; sy++) + for (sy = sroom->ly; sy <= sroom->hy; sy++) { + if (!IS_ROOM(levl[sx][sy].typ) + || (int) levl[sx][sy].roomno != rmno) + continue; if (!OBJ_AT(sx, sy) && !MON_AT(sx, sy) && !t_at(sx, sy) && !nexttodoor(sx, sy)) { if ((sx + sy) % 2) { + del_engr_at(sx, sy); levl[sx][sy].typ = POOL; if (!eelct || !rn2(4)) { /* mkclass() won't do, as we might get kraken */ @@ -545,17 +572,17 @@ mkswamp() /* Michiel Huisjes & Fred de Wilde */ (void) makemon(mkclass(S_FUNGUS, 0), sx, sy, NO_MM_FLAGS); } - level.flags.has_swamp = 1; + } + svl.level.flags.has_swamp = 1; } } -STATIC_OVL coord * -shrine_pos(roomno) -int roomno; +staticfn coord * +shrine_pos(int roomno) { static coord buf; int delta; - struct mkroom *troom = &rooms[roomno - ROOMOFFSET]; + struct mkroom *troom = &svr.rooms[roomno - ROOMOFFSET]; /* if width and height are odd, placement will be the exact center; if either or both are even, center point is a hypothetical spot @@ -571,12 +598,12 @@ int roomno; return &buf; } -STATIC_OVL void -mktemple() +staticfn void +mktemple(void) { - register struct mkroom *sroom; + struct mkroom *sroom; coord *shrine_spot; - register struct rm *lev; + struct rm *lev; if (!(sroom = pick_room(TRUE))) return; @@ -587,21 +614,20 @@ mktemple() * In temples, shrines are blessed altars * located in the center of the room */ - shrine_spot = shrine_pos((int) ((sroom - rooms) + ROOMOFFSET)); + shrine_spot = shrine_pos((int) ((sroom - svr.rooms) + ROOMOFFSET)); lev = &levl[shrine_spot->x][shrine_spot->y]; lev->typ = ALTAR; lev->altarmask = induced_align(80); priestini(&u.uz, sroom, shrine_spot->x, shrine_spot->y, FALSE); lev->altarmask |= AM_SHRINE; - level.flags.has_temple = 1; + svl.level.flags.has_temple = 1; } boolean -nexttodoor(sx, sy) -register int sx, sy; +nexttodoor(int sx, int sy) { - register int dx, dy; - register struct rm *lev; + int dx, dy; + struct rm *lev; for (dx = -1; dx <= 1; dx++) for (dy = -1; dy <= 1; dy++) { @@ -615,60 +641,67 @@ register int sx, sy; } boolean -has_dnstairs(sroom) -register struct mkroom *sroom; +has_dnstairs(struct mkroom *sroom) { - if (sroom == dnstairs_room) - return TRUE; - if (sstairs.sx && !sstairs.up) - return (boolean) (sroom == sstairs_room); + stairway *stway = gs.stairs; + + while (stway) { + if (!stway->up && inside_room(sroom, stway->sx, stway->sy)) + return TRUE; + stway = stway->next; + } return FALSE; } boolean -has_upstairs(sroom) -register struct mkroom *sroom; +has_upstairs(struct mkroom *sroom) { - if (sroom == upstairs_room) - return TRUE; - if (sstairs.sx && sstairs.up) - return (boolean) (sroom == sstairs_room); + stairway *stway = gs.stairs; + + while (stway) { + if (stway->up && inside_room(sroom, stway->sx, stway->sy)) + return TRUE; + stway = stway->next; + } return FALSE; } int -somex(croom) -register struct mkroom *croom; +somex(struct mkroom *croom) { return rn1(croom->hx - croom->lx + 1, croom->lx); } int -somey(croom) -register struct mkroom *croom; +somey(struct mkroom *croom) { return rn1(croom->hy - croom->ly + 1, croom->ly); } boolean -inside_room(croom, x, y) -struct mkroom *croom; -xchar x, y; +inside_room(struct mkroom *croom, coordxy x, coordxy y) { + if (croom->irregular) { + int i = (int) ((croom - svr.rooms) + ROOMOFFSET); + return (!levl[x][y].edge && (int) levl[x][y].roomno == i); + } + return (boolean) (x >= croom->lx - 1 && x <= croom->hx + 1 && y >= croom->ly - 1 && y <= croom->hy + 1); } +/* return a coord c inside mkroom croom, but not in a subroom. + returns TRUE if any such space found. + can return a non-accessible location, eg. inside a wall + if a themed room is not irregular, but has some non-room terrain */ boolean -somexy(croom, c) -struct mkroom *croom; -coord *c; +somexy(struct mkroom *croom, coord *c) { int try_cnt = 0; int i; if (croom->irregular) { - i = (int) ((croom - rooms) + ROOMOFFSET); + i = (int) ((croom - svr.rooms) + ROOMOFFSET); while (try_cnt++ < 100) { c->x = somex(croom); @@ -702,7 +735,7 @@ coord *c; if (inside_room(croom->sbrooms[i], c->x, c->y)) goto you_lose; break; - you_lose: + you_lose: ; } if (try_cnt >= 100) @@ -710,6 +743,22 @@ coord *c; return TRUE; } +/* like somexy(), but returns an accessible location */ +boolean +somexyspace(struct mkroom* croom, coord *c) +{ + int trycnt = 0; + boolean okay; + + do { + okay = somexy(croom, c) && isok(c->x, c->y) && !occupied(c->x, c->y) + && (levl[c->x][c->y].typ == ROOM + || levl[c->x][c->y].typ == CORR + || levl[c->x][c->y].typ == ICE); + } while (trycnt++ < 100 && !okay); + return okay; +} + /* * Search for a special room given its type (zoo, court, etc...) * Special values : @@ -717,17 +766,16 @@ coord *c; * - ANY_TYPE */ struct mkroom * -search_special(type) -schar type; +search_special(schar type) { - register struct mkroom *croom; + struct mkroom *croom; - for (croom = &rooms[0]; croom->hx >= 0; croom++) + for (croom = &svr.rooms[0]; croom->hx >= 0; croom++) if ((type == ANY_TYPE && croom->rtype != OROOM) || (type == ANY_SHOP && croom->rtype >= SHOPBASE) || croom->rtype == type) return croom; - for (croom = &subrooms[0]; croom->hx >= 0; croom++) + for (croom = &gs.subrooms[0]; croom->hx >= 0; croom++) if ((type == ANY_TYPE && croom->rtype != OROOM) || (type == ANY_SHOP && croom->rtype >= SHOPBASE) || croom->rtype == type) @@ -736,7 +784,7 @@ schar type; } struct permonst * -courtmon() +courtmon(void) { int i = rn2(60) + rn2(3 * level_difficulty()); @@ -760,35 +808,33 @@ courtmon() return mkclass(S_KOBOLD, 0); } -#define NSTYPES (PM_CAPTAIN - PM_SOLDIER + 1) - -static struct { +static const struct { unsigned pm; unsigned prob; -} squadprob[NSTYPES] = { { PM_SOLDIER, 80 }, - { PM_SERGEANT, 15 }, - { PM_LIEUTENANT, 4 }, - { PM_CAPTAIN, 1 } }; +} squadprob[] = { { PM_SOLDIER, 80 }, + { PM_SERGEANT, 15 }, + { PM_LIEUTENANT, 4 }, + { PM_CAPTAIN, 1 } }; /* return soldier types. */ -STATIC_OVL struct permonst * -squadmon() +staticfn struct permonst * +squadmon(void) { int sel_prob, i, cpro, mndx; sel_prob = rnd(80 + level_difficulty()); cpro = 0; - for (i = 0; i < NSTYPES; i++) { + for (i = 0; i < SIZE(squadprob); i++) { cpro += squadprob[i].prob; if (cpro > sel_prob) { mndx = squadprob[i].pm; goto gotone; } } - mndx = squadprob[rn2(NSTYPES)].pm; -gotone: - if (!(mvitals[mndx].mvflags & G_GONE)) + mndx = ROLL_FROM(squadprob).pm; + gotone: + if (!(svm.mvitals[mndx].mvflags & G_GONE)) return &mons[mndx]; else return (struct permonst *) 0; @@ -798,50 +844,48 @@ squadmon() * save_room : A recursive function that saves a room and its subrooms * (if any). */ -STATIC_OVL void -save_room(fd, r) -int fd; -struct mkroom *r; +staticfn void +save_room(NHFILE *nhfp, struct mkroom *r) { short i; /* * Well, I really should write only useful information instead * of writing the whole structure. That is I should not write - * the subrooms pointers, but who cares ? + * the gs.subrooms pointers, but who cares ? */ - bwrite(fd, (genericptr_t) r, sizeof (struct mkroom)); - for (i = 0; i < r->nsubrooms; i++) - save_room(fd, r->sbrooms[i]); + Sfo_mkroom(nhfp, r, "room-mkroom"); + for (i = 0; i < r->nsubrooms; i++) { + save_room(nhfp, r->sbrooms[i]); + } } /* * save_rooms : Save all the rooms on disk! */ void -save_rooms(fd) -int fd; +save_rooms(NHFILE *nhfp) { short i; /* First, write the number of rooms */ - bwrite(fd, (genericptr_t) &nroom, sizeof(nroom)); - for (i = 0; i < nroom; i++) - save_room(fd, &rooms[i]); + Sfo_int(nhfp, &svn.nroom, "room-nroom"); + for (i = 0; i < svn.nroom; i++) + save_room(nhfp, &svr.rooms[i]); } +#endif /* !SFCTOOL */ -STATIC_OVL void -rest_room(fd, r) -int fd; -struct mkroom *r; +staticfn void +rest_room(NHFILE *nhfp, struct mkroom *r) { short i; - mread(fd, (genericptr_t) r, sizeof(struct mkroom)); + Sfi_mkroom(nhfp, r, "room-mkroom"); + for (i = 0; i < r->nsubrooms; i++) { - r->sbrooms[i] = &subrooms[nsubroom]; - rest_room(fd, &subrooms[nsubroom]); - subrooms[nsubroom++].resident = (struct monst *) 0; + r->sbrooms[i] = &gs.subrooms[gn.nsubroom]; + rest_room(nhfp, &gs.subrooms[gn.nsubroom]); + gs.subrooms[gn.nsubroom++].resident = (struct monst *) 0; } } @@ -850,26 +894,26 @@ struct mkroom *r; * the disk. */ void -rest_rooms(fd) -int fd; +rest_rooms(NHFILE *nhfp) { short i; - mread(fd, (genericptr_t) &nroom, sizeof(nroom)); - nsubroom = 0; - for (i = 0; i < nroom; i++) { - rest_room(fd, &rooms[i]); - rooms[i].resident = (struct monst *) 0; + Sfi_int(nhfp, &svn.nroom, "room-nroom"); + + gn.nsubroom = 0; + for (i = 0; i < svn.nroom; i++) { + rest_room(nhfp, &svr.rooms[i]); + svr.rooms[i].resident = (struct monst *) 0; } - rooms[nroom].hx = -1; /* restore ending flags */ - subrooms[nsubroom].hx = -1; + svr.rooms[svn.nroom].hx = -1; /* restore ending flags */ + gs.subrooms[gn.nsubroom].hx = -1; } +#ifndef SFCTOOL /* convert a display symbol for terrain into topology type; used for remembered terrain when mimics pose as furniture */ int -cmap_to_type(sym) -int sym; +cmap_to_type(int sym) { int typ = STONE; /* catchall */ @@ -924,6 +968,7 @@ int sym; typ = TREE; break; case S_room: + case S_darkroom: typ = ROOM; break; case S_corr: @@ -979,10 +1024,80 @@ int sym; case S_water: typ = WATER; break; + case S_lavawall: + typ = LAVAWALL; + break; default: break; /* not a cmap symbol? */ } return typ; } +/* With the introduction of themed rooms, there are certain room shapes that + * may generate a door, the square just inside the door, and only one other + * ROOM square touching that one. E.g. + * --- + * ---.. + * +.... + * ---.. + * --- + * This means that if the room becomes a shop, the shopkeeper will move + * between those two squares nearest the door without ever allowing the + * player to get past them. + * Before approving sroom as a shop, check for this circumstance, and if it + * exists, don't consider it as valid for a shop. + * + * Note that the invalidity of the shape derives from the position of its door + * already being chosen. It's quite possible that if the door were somewhere + * else on the perimeter of this room, it would work fine as a shop.*/ +staticfn boolean +invalid_shop_shape(struct mkroom *sroom) +{ + coordxy x, y; + coordxy doorx = svd.doors[sroom->fdoor].x; + coordxy doory = svd.doors[sroom->fdoor].y; + coordxy insidex = 0, insidey = 0, insidect = 0; + + /* First, identify squares inside the room and next to the door. */ + for (x = max(doorx - 1, sroom->lx); + x <= min(doorx + 1, sroom->hx); x++) { + for (y = max(doory - 1, sroom->ly); + y <= min(doory + 1, sroom->hy); y++) { + if (levl[x][y].typ == ROOM) { + insidex = x; + insidey = y; + insidect++; + } + } + } + if (insidect < 1) { + impossible("invalid_shop_shape: no squares inside door?"); + return TRUE; + } + /* if insidect > 1, then the shopkeeper already has alternate + * squares to move to so we don't need to check further. */ + if (insidect == 1) { + /* But if it is 1, scan all adjacent squares for other squares + * that are part of this room. */ + insidect = 0; + for (x = max(insidex - 1, sroom->lx); + x <= min(insidex + 1, sroom->hx); x++) { + for (y = max(insidey - 1, sroom->ly); + y <= min(insidey + 1, sroom->hy); y++) { + if (x == insidex && y == insidey) + continue; + if (levl[x][y].typ == ROOM) + insidect++; + } + } + if (insidect == 1) { + /* shopkeeper standing just inside the door can only move + * to one other square; this cannot be a shop. */ + return TRUE; + } + } + return FALSE; +} +#endif /* !SFCTOOL */ + /*mkroom.c*/ diff --git a/src/mon.c b/src/mon.c index 4f0a13d4a..c67785325 100644 --- a/src/mon.c +++ b/src/mon.c @@ -1,78 +1,126 @@ -/* NetHack 3.6 mon.c $NHDT-Date: 1569276991 2019/09/23 22:16:31 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.297 $ */ +/* NetHack 5.0 mon.c $NHDT-Date: 1770949988 2026/02/12 18:33:08 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.621 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Derek S. Ray, 2015. */ /* NetHack may be freely redistributed. See license for details. */ -/* If you're using precompiled headers, you don't want this either */ -#ifdef MICROPORT_BUG -#define MKROOM_H -#endif - #include "hack.h" #include "mfndpos.h" -#include - -STATIC_VAR boolean vamp_rise_msg, disintegested; - -STATIC_DCL void FDECL(sanity_check_single_mon, (struct monst *, BOOLEAN_P, - const char *)); -STATIC_DCL boolean FDECL(restrap, (struct monst *)); -STATIC_DCL long FDECL(mm_aggression, (struct monst *, struct monst *)); -STATIC_DCL long FDECL(mm_displacement, (struct monst *, struct monst *)); -STATIC_DCL int NDECL(pick_animal); -STATIC_DCL void FDECL(kill_eggs, (struct obj *)); -STATIC_DCL int FDECL(pickvampshape, (struct monst *)); -STATIC_DCL boolean FDECL(isspecmon, (struct monst *)); -STATIC_DCL boolean FDECL(validspecmon, (struct monst *, int)); -STATIC_DCL struct permonst *FDECL(accept_newcham_form, (struct monst *, int)); -STATIC_DCL struct obj *FDECL(make_corpse, (struct monst *, unsigned)); -STATIC_DCL void FDECL(m_detach, (struct monst *, struct permonst *)); -STATIC_DCL void FDECL(lifesaved_monster, (struct monst *)); -STATIC_DCL void FDECL(migrate_mon, (struct monst *, XCHAR_P, XCHAR_P)); -STATIC_DCL boolean FDECL(ok_to_obliterate, (struct monst *)); -STATIC_DCL void FDECL(deal_with_overcrowding, (struct monst *)); - -/* note: duplicated in dog.c */ + +staticfn void pet_sanity_check(struct monst *, const char *); +staticfn void sanity_check_single_mon(struct monst *, boolean, const char *); +staticfn struct obj *make_corpse(struct monst *, unsigned); +staticfn int minliquid_core(struct monst *); +staticfn void m_calcdistress(struct monst *); +staticfn boolean monlineu(struct monst *, int, int); +staticfn long mm_2way_aggression(struct monst *, struct monst *); +staticfn long mm_aggression(struct monst *, struct monst *); +staticfn long mm_displacement(struct monst *, struct monst *); +staticfn void mon_leaving_level(struct monst *); +staticfn void m_detach(struct monst *, struct permonst *, boolean); +staticfn void set_mon_min_mhpmax(struct monst *, int); +staticfn void lifesaved_monster(struct monst *); +staticfn boolean vamprises(struct monst *); +staticfn void logdeadmon(struct monst *, int); +staticfn void anger_quest_guardians(struct monst *); +staticfn boolean ok_to_obliterate(struct monst *); +staticfn void m_respond_shrieker(struct monst *); +staticfn void m_respond_medusa(struct monst *); +staticfn void qst_guardians_respond(void); +staticfn void peacefuls_respond(struct monst *); +staticfn void wake_nearto_core(coordxy, coordxy, int, boolean); +staticfn void m_restartcham(struct monst *); +staticfn boolean restrap(struct monst *); +staticfn int pick_animal(void); +staticfn int pickvampshape(struct monst *); +staticfn boolean isspecmon(struct monst *); +staticfn boolean validspecmon(struct monst *, int); +staticfn int wiz_force_cham_form(struct monst *); +staticfn struct permonst *accept_newcham_form(struct monst *, int); +staticfn void kill_eggs(struct obj *) NO_NNARGS; +staticfn void pacify_guard(struct monst *); + +extern const struct shclass shtypes[]; /* defined in shknam.c */ + #define LEVEL_SPECIFIC_NOCORPSE(mdat) \ (Is_rogue_level(&u.uz) \ - || (level.flags.graveyard && is_undead(mdat) && rn2(3))) + || !svl.level.flags.deathdrops \ + || (svl.level.flags.graveyard && is_undead(mdat) && rn2(3))) -#if 0 +#if 0 /* potentially of historical interest */ /* part of the original warning code which was replaced in 3.3.1 */ const char *warnings[] = { "white", "pink", "red", "ruby", "purple", "black" }; #endif /* 0 */ +staticfn void +pet_sanity_check( + struct monst *mtmp, + const char *msgarg) +{ + if (has_edog(mtmp)) { + struct edog *edog = EDOG(mtmp); + + if (edog->droptime > svm.moves) + impossible("insane pet #%u has droptime (%ld)" + " in the future (%ld) (%s)", + mtmp->m_id, edog->droptime, svm.moves, msgarg); + /* TODO: verify some of the other edog fields */ + } +} -STATIC_OVL void -sanity_check_single_mon(mtmp, chk_geno, msg) -struct monst *mtmp; -boolean chk_geno; -const char *msg; +staticfn void +sanity_check_single_mon( + struct monst *mtmp, + boolean chk_geno, + const char *msg) { - if (mtmp->data < &mons[LOW_PM] || mtmp->data >= &mons[NUMMONS]) { - impossible("illegal mon data %s; mnum=%d (%s)", - fmt_ptr((genericptr_t) mtmp->data), mtmp->mnum, msg); + struct permonst *mptr = mtmp->data; + coordxy mx = mtmp->mx, my = mtmp->my; + + if (!mptr || mptr < &mons[LOW_PM] || mptr > &mons[HIGH_PM]) { + /* most sanity checks issue warnings if they detect a problem, + but this would be too extreme to keep going */ + panic("illegal mon data %s; mnum=%d (%s)", + fmt_ptr((genericptr_t) mptr), mtmp->mnum, msg); + /*NOTREACHED*/ } else { - int mndx = monsndx(mtmp->data); + int mndx = monsndx(mptr); if (mtmp->mnum != mndx) { impossible("monster mnum=%d, monsndx=%d (%s)", mtmp->mnum, mndx, msg); mtmp->mnum = mndx; } + /* check before DEADMONSTER() because dead monsters should still + have sane mhpmax */ + if (mtmp->mhpmax < 1 + /* Gremlins don't obey the (mhpmax >= m_lev) rule so disable + * this check, at least for the time being. We could skip it + * when the cloned flag is set, but the original gremlin would + * still be an issue. + || mtmp->mhpmax < (int) mtmp->m_lev + */ + || mtmp->mhp > mtmp->mhpmax) + impossible("%s: level %d %s #%u [%s] has %d cur HP, %d max HP", + msg, (int) mtmp->m_lev, mptr->pmnames[NEUTRAL], + mtmp->m_id, fmt_ptr((genericptr_t) mtmp), + mtmp->mhp, mtmp->mhpmax); if (DEADMONSTER(mtmp)) { #if 0 - /* bad if not fmons list or if not vault guard */ + /* bad if not fmon list or if not vault guard */ if (strcmp(msg, "fmon") || !mtmp->isgd) impossible("dead monster on %s; %s at <%d,%d>", - msg, mons[mndx].mname, mtmp->mx, mtmp->my); + msg, mptr->pmnames[NEUTRAL], mx, my); #endif return; } - if (chk_geno && (mvitals[mndx].mvflags & G_GENOD) != 0) - impossible("genocided %s in play (%s)", mons[mndx].mname, msg); + if (chk_geno && (svm.mvitals[mndx].mvflags & G_GENOD) != 0) + impossible("genocided %s in play (%s)", + pmname(mptr, Mgender(mtmp)), msg); + if (mtmp->mtame && !mtmp->mpeaceful) + impossible("tame %s is not peaceful (%s)", + pmname(mptr, Mgender(mtmp)), msg); } if (mtmp->isshk && !has_eshk(mtmp)) impossible("shk without eshk (%s)", msg); @@ -83,14 +131,133 @@ const char *msg; if (mtmp->isminion && !has_emin(mtmp)) impossible("minion without emin (%s)", msg); /* guardian angel on astral level is tame but has emin rather than edog */ - if (mtmp->mtame && !has_edog(mtmp) && !mtmp->isminion) - impossible("pet without edog (%s)", msg); + if (mtmp->mtame) { + if (!has_edog(mtmp) && !mtmp->isminion) + impossible("pet without edog (%s)", msg); + else + pet_sanity_check(mtmp, msg); + } + /* steed should be tame and saddled */ + if (mtmp == u.usteed) { + const char *ns, *nt = !mtmp->mtame ? "not tame" : 0; + + ns = !m_carrying(mtmp, SADDLE) ? "no saddle" + : !which_armor(mtmp, W_SADDLE) ? "saddle not worn" + : 0; + if (ns || nt) + impossible("steed: %s%s%s (%s)", + ns ? ns : "", (ns && nt) ? ", " : "", nt ? nt : "", + msg); + } + + if (mtmp->mtrapped) { + if (mtmp->wormno) { + ; /* TODO: how to check worm in trap? */ + } else if (!t_at(mx, my)) + impossible("trapped without a trap (%s)", msg); + } + /* monst->mfrozen is difficult to deal with--it's used for paralysis, + for temporary sleep, and for being busy (usually donning armor); + code that sets mfrozen needs to also clear mcanmove, otherwise the + helpless() test will be unreliable */ + if (mtmp->mfrozen && mtmp->mcanmove) + impossible("frozen monster [%s%s] is able to move (%s)", + mtmp->mtame ? "tame " : mtmp->mpeaceful ? "peaceful " : "", + pmname(mptr, Mgender(mtmp)), msg); + + /* monster is hiding? */ + if (mtmp->mundetected) { + struct trap *t; + + if (!isok(mx, my)) /* caller will have checked this but not fixed it */ + mx = my = 0; + if (mtmp == u.ustuck) + impossible("hiding monster stuck to you (%s)", msg); + if (m_at(mx, my) == mtmp && hides_under(mptr) && !OBJ_AT(mx, my)) + impossible("mon hiding under nonexistent obj (%s)", msg); + if (mptr->mlet == S_EEL + && !(is_pool(mx, my) && !Is_waterlevel(&u.uz))) + impossible("eel hiding %s (%s)", + !Is_waterlevel(&u.uz) ? "out of water" + : "on Plane of Water", msg); + if (ceiling_hider(mptr) + /* normally !accessible would be overridable with passes_walls, + but not for hiding on the ceiling */ + && (!has_ceiling(&u.uz) + || !(levl[mx][my].typ == POOL + || levl[mx][my].typ == MOAT + || levl[mx][my].typ == WATER + || levl[mx][my].typ == LAVAPOOL + || levl[mx][my].typ == LAVAWALL + || accessible(mx, my)))) + impossible("ceiling hider hiding %s (%s)", + !has_ceiling(&u.uz) ? "without ceiling" + : "in solid stone", + msg); + if (mtmp->mtrapped && (t = t_at(mx, my)) != 0 && !is_pit(t->ttyp)) + impossible("hiding while trapped in a non-pit (%s)", msg); + } else if (M_AP_TYPE(mtmp) != M_AP_NOTHING) { + boolean is_mimic = (mptr->mlet == S_MIMIC); + const char *what = (M_AP_TYPE(mtmp) == M_AP_FURNITURE) ? "furniture" + : (M_AP_TYPE(mtmp) == M_AP_MONSTER) ? "a monster" + : (M_AP_TYPE(mtmp) == M_AP_OBJECT) ? "an object" + : "something strange"; + + if (!strcmp(msg, "migr")) { + if (M_AP_TYPE(mtmp) != M_AP_MONSTER) + impossible("migrating %s mimicking %s %s", + is_mimic ? "mimic" : "monster", what, msg); + } else if (Protection_from_shape_changers) { + impossible( + "mimic%s concealed as %s despite Prot-from-shape-changers %s", + is_mimic ? "" : "ker", what, msg); + } + /* the Wizard's clone after "double trouble" starts out mimicking + some other monster; pet's quickmimic effect can temporarily take + on furniture, object, or monster shape, but only until the pet + finishes eating a mimic corpse */ + if (!(is_mimic || mtmp->meating + || (mtmp->iswiz && M_AP_TYPE(mtmp) == M_AP_MONSTER))) + impossible("non-mimic (%s) posing as %s (%s)", + mptr->pmnames[NEUTRAL], what, msg); +#if 0 /* mimics who end up in strange locations do still hide while there */ + if (!(accessible(mx, my) || passes_walls(mptr))) { + char buf[BUFSZ]; + const char *typnam = levltyp_to_name(levl[mx][my].typ); + + if (!typnam) { + Sprintf(buf, "[%d]", levl[mx][my].typ); + typnam = buf; + } + impossible("mimic%s concealed in inaccessible location: %s (%s)", + is_mimic ? "" : "ker", typnam, msg); + } +#endif + } + if (mtmp->mleashed) { + if (!get_mleash(mtmp)) + impossible("monst %u: leashed but no leash for %s", + mtmp->m_id, mon_pmname(mtmp)); + else if (!mtmp->mtame) + impossible("monst %u: leashed but not tame %s", + mtmp->m_id, mon_pmname(mtmp)); +#if 0 + /* after hero moves, leashed mon won't necessarily pass 'm_next2u()' + test; 90 is farthest observed distance with expert jumping spell + when very slow mon is already several steps away and hero jumps in + opposite direction (if hero teleports, leashed mon moves adjacent + immediately; knockback has shorter range than magical jumping) */ + else if (distu(mtmp->mx, mtmp->my) > 90) /*if (!m_next2u(mtmp))*/ + impossible("monst %u: leashed but not next to you (%d)", + mtmp->m_id, distu(mtmp->mx, mtmp->my)); +#endif + } } void -mon_sanity_check() +mon_sanity_check(void) { - int x, y; + coordxy x, y; struct monst *mtmp, *m; for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { @@ -109,17 +276,23 @@ mon_sanity_check() if (x != u.ux || y != u.uy) impossible("steed (%s) claims to be at <%d,%d>?", fmt_ptr((genericptr_t) mtmp), x, y); - } else if (level.monsters[x][y] != mtmp) { + } else if (svl.level.monsters[x][y] != mtmp) { impossible("mon (%s) at <%d,%d> is not there!", fmt_ptr((genericptr_t) mtmp), x, y); } else if (mtmp->wormno) { sanity_check_worm(mtmp); + + /* some temp mstate bits can be expected for a mon on fmon, as part of + removing it, but DEADMONSTER check above should skip those. */ + } else if (mon_offmap(mtmp)) { + impossible("floor mon (%s) with mstate set to 0x%08lx", + fmt_ptr((genericptr_t) mtmp), mtmp->mstate); } } for (x = 1; x < COLNO; x++) for (y = 0; y < ROWNO; y++) - if ((mtmp = level.monsters[x][y]) != 0) { + if ((mtmp = svl.level.monsters[x][y]) != 0) { for (m = fmon; m; m = m->nmon) if (m == mtmp) break; @@ -136,16 +309,112 @@ mon_sanity_check() mtmp->mx, mtmp->my, x, y); } - for (mtmp = migrating_mons; mtmp; mtmp = mtmp->nmon) { + for (mtmp = gm.migrating_mons; mtmp; mtmp = mtmp->nmon) { sanity_check_single_mon(mtmp, FALSE, "migr"); + + if ((mtmp->mstate + & ~(MON_MIGRATING | MON_LIMBO | MON_ENDGAME_MIGR | MON_OFFMAP)) + != 0L + || !(mtmp->mstate & MON_MIGRATING)) + impossible("migrating mon (%s) with mstate set to 0x%08lx", + fmt_ptr((genericptr_t) mtmp), mtmp->mstate); } + + wormno_sanity_check(); /* test for bogus worm tail */ } +/* Would monster be OK with poison gas? */ +/* Does not check for actual poison gas at the location. */ +/* Returns one of M_POISONGAS_foo */ +int +m_poisongas_ok(struct monst *mtmp) +{ + int px, py; + boolean is_you = (mtmp == &gy.youmonst); + + /* Non living, non breathing, immune monsters are not concerned */ + if (nonliving(mtmp->data) || is_vampshifter(mtmp) + || breathless(mtmp->data) || immune_poisongas(mtmp->data)) + return M_POISONGAS_OK; + /* not is_swimmer(); assume that non-fish are swimming on + the surface and breathing the air above it periodically + unless located at water spot on plane of water */ + px = is_you ? u.ux : mtmp->mx; + py = is_you ? u.uy : mtmp->my; + if ((mtmp->data->mlet == S_EEL || Is_waterlevel(&u.uz)) + && is_pool(px, py)) + return M_POISONGAS_OK; + /* exclude monsters with poison gas breath attack: + adult green dragon and Chromatic Dragon (and iron golem, + but nonliving() and breathless() tests also catch that) */ + if (attacktype_fordmg(mtmp->data, AT_BREA, AD_DRST) + || attacktype_fordmg(mtmp->data, AT_BREA, AD_RBRE)) + return M_POISONGAS_OK; + if (is_you && (u.uinvulnerable || Breathless || Underwater)) + return M_POISONGAS_OK; + if (is_you ? Poison_resistance : resists_poison(mtmp)) + return M_POISONGAS_MINOR; + return M_POISONGAS_BAD; +} + +/* return True if mon is capable of converting other monsters into zombies */ +boolean +zombie_maker(struct monst *mon) +{ + struct permonst *pm = mon->data; + + if (mon->mcan) + return FALSE; + + switch (pm->mlet) { + case S_ZOMBIE: + /* Z-class monsters that aren't actually zombies go here */ + if (pm == &mons[PM_GHOUL] || pm == &mons[PM_SKELETON]) + return FALSE; + return TRUE; + case S_LICH: + /* all liches will create zombies as well */ + return TRUE; + } + return FALSE; +} + +/* Return monster index of zombie monster which this monster could + be turned into, or NON_PM if it doesn't have a direct counterpart. + Sort of the zombie-specific inverse of undead_to_corpse. */ +int +zombie_form(struct permonst *pm) +{ + switch (pm->mlet) { + case S_ZOMBIE: /* when already a zombie/ghoul/skeleton, will stay as is */ + return NON_PM; + case S_KOBOLD: + return PM_KOBOLD_ZOMBIE; + case S_ORC: + return PM_ORC_ZOMBIE; + case S_GIANT: + if (pm == &mons[PM_ETTIN]) + return PM_ETTIN_ZOMBIE; + return PM_GIANT_ZOMBIE; + case S_HUMAN: + case S_KOP: + if (is_elf(pm)) + return PM_ELF_ZOMBIE; + return PM_HUMAN_ZOMBIE; + case S_HUMANOID: + if (is_dwarf(pm)) + return PM_DWARF_ZOMBIE; + else + break; + case S_GNOME: + return PM_GNOME_ZOMBIE; + } + return NON_PM; +} /* convert the monster index of an undead to its living counterpart */ int -undead_to_corpse(mndx) -int mndx; +undead_to_corpse(int mndx) { switch (mndx) { case PM_KOBOLD_ZOMBIE: @@ -169,7 +438,7 @@ int mndx; mndx = PM_ELF; break; case PM_VAMPIRE: - case PM_VAMPIRE_LORD: + case PM_VAMPIRE_LEADER: #if 0 /* DEFERRED */ case PM_VAMPIRE_MAGE: #endif @@ -198,8 +467,7 @@ int mndx; * if mode is 1. */ int -genus(mndx, mode) -int mndx, mode; +genus(int mndx, int mode) { switch (mndx) { /* Quest guardians */ @@ -210,7 +478,7 @@ int mndx, mode; mndx = mode ? PM_BARBARIAN : PM_HUMAN; break; case PM_NEANDERTHAL: - mndx = mode ? PM_CAVEMAN : PM_HUMAN; + mndx = mode ? PM_CAVE_DWELLER : PM_HUMAN; break; case PM_ATTENDANT: mndx = mode ? PM_HEALER : PM_HUMAN; @@ -222,7 +490,7 @@ int mndx, mode; mndx = mode ? PM_MONK : PM_HUMAN; break; case PM_ACOLYTE: - mndx = mode ? PM_PRIEST : PM_HUMAN; + mndx = mode ? PM_CLERIC : PM_HUMAN; break; case PM_HUNTER: mndx = mode ? PM_RANGER : PM_HUMAN; @@ -243,7 +511,7 @@ int mndx, mode; mndx = mode ? PM_VALKYRIE : PM_HUMAN; break; default: - if (mndx >= LOW_PM && mndx < NUMMONS) { + if (ismnum(mndx)) { struct permonst *ptr = &mons[mndx]; if (is_human(ptr)) @@ -264,8 +532,7 @@ int mndx, mode; /* return monster index if chameleon, or NON_PM if not */ int -pm_to_cham(mndx) -int mndx; +pm_to_cham(int mndx) { int mcham = NON_PM; @@ -273,19 +540,19 @@ int mndx; * As of 3.6.0 we just check M2_SHAPESHIFTER instead of having a * big switch statement with hardcoded shapeshifter types here. */ - if (mndx >= LOW_PM && is_shapeshifter(&mons[mndx])) + if (ismnum(mndx) && is_shapeshifter(&mons[mndx])) mcham = mndx; return mcham; } /* for deciding whether corpse will carry along full monster data */ -#define KEEPTRAITS(mon) \ - ((mon)->isshk || (mon)->mtame || unique_corpstat((mon)->data) \ - || is_reviver((mon)->data) \ - /* normally quest leader will be unique, */ \ - /* but he or she might have been polymorphed */ \ - || (mon)->m_id == quest_status.leader_m_id \ - /* special cancellation handling for these */ \ +#define KEEPTRAITS(mon) \ + ((mon)->isshk || (mon)->mtame || unique_corpstat((mon)->data) \ + || is_reviver((mon)->data) \ + /* normally quest leader will be unique, */ \ + /* but he or she might have been polymorphed */ \ + || (mon)->m_id == svq.quest_status.leader_m_id \ + /* special cancellation handling for these */ \ || (dmgtype((mon)->data, AD_SEDU) || dmgtype((mon)->data, AD_SSEX))) /* Creates a monster corpse, a "special" corpse, or nothing if it doesn't @@ -293,22 +560,27 @@ int mndx; * G_NOCORPSE set in order to prevent wishing for one, finding tins of one, * etc.... */ -STATIC_OVL struct obj * -make_corpse(mtmp, corpseflags) -register struct monst *mtmp; -unsigned corpseflags; +staticfn struct obj * +make_corpse(struct monst *mtmp, unsigned int corpseflags) { - register struct permonst *mdat = mtmp->data; + struct permonst *mdat = mtmp->data; int num; struct obj *obj = (struct obj *) 0; struct obj *otmp = (struct obj *) 0; - int x = mtmp->mx, y = mtmp->my; - int mndx = monsndx(mdat); + coordxy x = mtmp->mx, y = mtmp->my; +/* int mndx = monsndx(mdat); */ + enum monnums mndx = monsndx(mdat); unsigned corpstatflags = corpseflags; boolean burythem = ((corpstatflags & CORPSTAT_BURIED) != 0); + if (mtmp->female) + corpstatflags |= CORPSTAT_FEMALE; + else if (!is_neuter(mtmp->data)) + corpstatflags |= CORPSTAT_MALE; + switch (mndx) { case PM_GRAY_DRAGON: + case PM_GOLD_DRAGON: case PM_SILVER_DRAGON: #if 0 /* DEFERRED */ case PM_SHIMMERING_DRAGON: @@ -334,7 +606,8 @@ unsigned corpseflags; case PM_BLACK_UNICORN: if (mtmp->mrevived && rn2(2)) { if (canseemon(mtmp)) - pline("%s recently regrown horn crumbles to dust.", + pline_mon(mtmp, + "%s recently regrown horn crumbles to dust.", s_suffix(Monnam(mtmp))); } else { obj = mksobj_at(UNICORN_HORN, x, y, TRUE, FALSE); @@ -346,12 +619,12 @@ unsigned corpseflags; (void) mksobj_at(WORM_TOOTH, x, y, TRUE, FALSE); goto default_1; case PM_VAMPIRE: - case PM_VAMPIRE_LORD: + case PM_VAMPIRE_LEADER: /* include mtmp in the mkcorpstat() call */ num = undead_to_corpse(mndx); corpstatflags |= CORPSTAT_INIT; obj = mkcorpstat(CORPSE, mtmp, &mons[num], x, y, corpstatflags); - obj->age -= 100; /* this is an *OLD* corpse */ + obj->age -= (TAINT_AGE + 1); /* this is an *OLD* corpse */ break; case PM_KOBOLD_MUMMY: case PM_DWARF_MUMMY: @@ -372,54 +645,71 @@ unsigned corpseflags; num = undead_to_corpse(mndx); corpstatflags |= CORPSTAT_INIT; obj = mkcorpstat(CORPSE, mtmp, &mons[num], x, y, corpstatflags); - obj->age -= 100; /* this is an *OLD* corpse */ + obj->age -= (TAINT_AGE + 1); /* this is an *OLD* corpse */ break; case PM_IRON_GOLEM: num = d(2, 6); while (num--) obj = mksobj_at(IRON_CHAIN, x, y, TRUE, FALSE); - free_mname(mtmp); /* don't christen obj */ + free_mgivenname(mtmp); /* don't christen obj */ break; case PM_GLASS_GOLEM: num = d(2, 4); /* very low chance of creating all glass gems */ while (num--) - obj = mksobj_at((LAST_GEM + rnd(9)), x, y, TRUE, FALSE); - free_mname(mtmp); + obj = mksobj_at(FIRST_GLASS_GEM + rn2(NUM_GLASS_GEMS), + x, y, TRUE, FALSE); + free_mgivenname(mtmp); break; case PM_CLAY_GOLEM: obj = mksobj_at(ROCK, x, y, FALSE, FALSE); obj->quan = (long) (rn2(20) + 50); obj->owt = weight(obj); - free_mname(mtmp); + free_mgivenname(mtmp); break; case PM_STONE_GOLEM: corpstatflags &= ~CORPSTAT_INIT; - obj = - mkcorpstat(STATUE, (struct monst *) 0, mdat, x, y, corpstatflags); + obj = mkcorpstat(STATUE, (struct monst *) 0, mdat, x, y, + corpstatflags); break; case PM_WOOD_GOLEM: num = d(2, 4); while (num--) { - obj = mksobj_at(QUARTERSTAFF, x, y, TRUE, FALSE); + obj = mksobj_at( + rn2(2) ? QUARTERSTAFF + : rn2(3) ? SMALL_SHIELD + : rn2(3) ? CLUB + : rn2(3) ? ELVEN_SPEAR : BOOMERANG, + x, y, TRUE, FALSE); } - free_mname(mtmp); + free_mgivenname(mtmp); + break; + case PM_ROPE_GOLEM: + num = rn2(3); + while (num-- > 0) { + obj = mksobj_at(rn2(2) ? LEASH + : rn2(3) ? BULLWHIP : GRAPPLING_HOOK, + x, y, TRUE, FALSE); + } + free_mgivenname(mtmp); break; case PM_LEATHER_GOLEM: num = d(2, 4); while (num--) - obj = mksobj_at(LEATHER_ARMOR, x, y, TRUE, FALSE); - free_mname(mtmp); + obj = mksobj_at(rn2(4) ? LEATHER_ARMOR + : rn2(3) ? LEATHER_CLOAK : SADDLE, + x, y, TRUE, FALSE); + free_mgivenname(mtmp); break; case PM_GOLD_GOLEM: /* Good luck gives more coins */ obj = mkgold((long) (200 - rnl(101)), x, y); - free_mname(mtmp); + free_mgivenname(mtmp); break; case PM_PAPER_GOLEM: num = rnd(4); while (num--) obj = mksobj_at(SCR_BLANK_PAPER, x, y, TRUE, FALSE); - free_mname(mtmp); + free_mgivenname(mtmp); break; /* expired puddings will congeal into a large blob; like dragons, relies on the order remaining consistent */ @@ -436,11 +726,171 @@ unsigned corpseflags; pudding_merge_message(obj, otmp); obj = obj_meld(&obj, &otmp); } - free_mname(mtmp); + free_mgivenname(mtmp); + newsym(x, y); return obj; - default_1: + case NON_PM: case LEAVESTATUE: case NUMMONS: /* never use as index */ + break; + +#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) + case PM_GIANT_ANT: case PM_KILLER_BEE: case PM_SOLDIER_ANT: + case PM_FIRE_ANT: case PM_GIANT_BEETLE: case PM_QUEEN_BEE: + + case PM_QUIVERING_BLOB: case PM_ACID_BLOB: case PM_GELATINOUS_CUBE: + case PM_CHICKATRICE: case PM_COCKATRICE: case PM_PYROLISK: + + case PM_JACKAL: case PM_FOX: case PM_COYOTE: case PM_WEREJACKAL: + case PM_LITTLE_DOG: case PM_DINGO: case PM_DOG: case PM_LARGE_DOG: + case PM_WOLF: case PM_WEREWOLF: case PM_WINTER_WOLF_CUB: + case PM_WARG: case PM_WINTER_WOLF: case PM_HELL_HOUND_PUP: + case PM_HELL_HOUND: + + case PM_GAS_SPORE: case PM_FLOATING_EYE: case PM_FREEZING_SPHERE: + case PM_FLAMING_SPHERE: case PM_SHOCKING_SPHERE: + + case PM_KITTEN: case PM_HOUSECAT: case PM_JAGUAR: case PM_LYNX: + case PM_PANTHER: case PM_LARGE_CAT: case PM_TIGER: + + case PM_DISPLACER_BEAST: case PM_GREMLIN: + case PM_GARGOYLE: case PM_WINGED_GARGOYLE: + + case PM_HOBBIT: case PM_DWARF: case PM_BUGBEAR: case PM_DWARF_LEADER: + case PM_DWARF_RULER: + case PM_MIND_FLAYER: case PM_MASTER_MIND_FLAYER: case PM_MANES: + case PM_HOMUNCULUS: case PM_IMP: case PM_LEMURE: case PM_QUASIT: + case PM_TENGU: case PM_BLUE_JELLY: case PM_SPOTTED_JELLY: + case PM_OCHRE_JELLY: case PM_KOBOLD: case PM_LARGE_KOBOLD: + case PM_KOBOLD_LEADER: case PM_KOBOLD_SHAMAN: case PM_LEPRECHAUN: + case PM_SMALL_MIMIC: case PM_LARGE_MIMIC: case PM_GIANT_MIMIC: + case PM_WOOD_NYMPH: case PM_WATER_NYMPH: case PM_MOUNTAIN_NYMPH: + case PM_GOBLIN: case PM_HOBGOBLIN: case PM_ORC: case PM_HILL_ORC: + case PM_MORDOR_ORC: case PM_URUK_HAI: case PM_ORC_SHAMAN: + case PM_ORC_CAPTAIN: + case PM_ROCK_PIERCER: case PM_IRON_PIERCER: case PM_GLASS_PIERCER: + case PM_ROTHE: case PM_MUMAK: case PM_LEOCROTTA: case PM_WUMPUS: + case PM_TITANOTHERE: case PM_BALUCHITHERIUM: case PM_MASTODON: + case PM_SEWER_RAT: case PM_GIANT_RAT: case PM_RABID_RAT: + case PM_WERERAT: + + case PM_ROCK_MOLE: case PM_WOODCHUCK: + case PM_CAVE_SPIDER: case PM_CENTIPEDE: case PM_GIANT_SPIDER: + case PM_SCORPION: + case PM_LURKER_ABOVE: case PM_TRAPPER: + case PM_PONY: case PM_HORSE: case PM_WARHORSE: + case PM_FOG_CLOUD: case PM_DUST_VORTEX: case PM_ICE_VORTEX: + case PM_ENERGY_VORTEX: case PM_STEAM_VORTEX: case PM_FIRE_VORTEX: + + case PM_BABY_LONG_WORM: case PM_BABY_PURPLE_WORM: + case PM_PURPLE_WORM: + + case PM_GRID_BUG: case PM_XAN: case PM_YELLOW_LIGHT: case PM_BLACK_LIGHT: + case PM_ZRUTY: case PM_COUATL: case PM_ALEAX: case PM_ANGEL: + case PM_KI_RIN: case PM_ARCHON: + + case PM_BAT: case PM_GIANT_BAT: case PM_RAVEN: case PM_VAMPIRE_BAT: + case PM_PLAINS_CENTAUR: case PM_FOREST_CENTAUR: case PM_MOUNTAIN_CENTAUR: + + case PM_BABY_GRAY_DRAGON: case PM_BABY_GOLD_DRAGON: + case PM_BABY_SILVER_DRAGON: case PM_BABY_RED_DRAGON: + case PM_BABY_WHITE_DRAGON: case PM_BABY_ORANGE_DRAGON: + case PM_BABY_BLACK_DRAGON: case PM_BABY_BLUE_DRAGON: + case PM_BABY_GREEN_DRAGON: case PM_BABY_YELLOW_DRAGON: + + case PM_STALKER: case PM_AIR_ELEMENTAL: case PM_FIRE_ELEMENTAL: + case PM_EARTH_ELEMENTAL: case PM_WATER_ELEMENTAL: + + case PM_LICHEN: case PM_BROWN_MOLD: case PM_YELLOW_MOLD: + case PM_GREEN_MOLD: case PM_RED_MOLD: case PM_SHRIEKER: + case PM_VIOLET_FUNGUS: + + case PM_GNOME: case PM_GNOME_LEADER: case PM_GNOMISH_WIZARD: + case PM_GNOME_RULER: + case PM_GIANT: case PM_STONE_GIANT: case PM_HILL_GIANT: + case PM_FIRE_GIANT: case PM_FROST_GIANT: case PM_ETTIN: + case PM_STORM_GIANT: case PM_TITAN: + + case PM_MINOTAUR: case PM_JABBERWOCK: case PM_KEYSTONE_KOP: + case PM_KOP_SERGEANT: case PM_KOP_LIEUTENANT: case PM_KOP_KAPTAIN: + case PM_LICH: case PM_DEMILICH: + case PM_MASTER_LICH: case PM_ARCH_LICH: + + case PM_RED_NAGA_HATCHLING: case PM_BLACK_NAGA_HATCHLING: + case PM_GOLDEN_NAGA_HATCHLING: case PM_GUARDIAN_NAGA_HATCHLING: + case PM_RED_NAGA: case PM_BLACK_NAGA: case PM_GOLDEN_NAGA: + case PM_GUARDIAN_NAGA: + + case PM_OGRE: case PM_OGRE_LEADER: case PM_OGRE_TYRANT: + + case PM_QUANTUM_MECHANIC: case PM_GENETIC_ENGINEER: + case PM_RUST_MONSTER: case PM_DISENCHANTER: + + case PM_GARTER_SNAKE: case PM_SNAKE: case PM_WATER_MOCCASIN: + case PM_PYTHON: case PM_PIT_VIPER: case PM_COBRA: + + case PM_TROLL: case PM_ICE_TROLL: case PM_ROCK_TROLL: case PM_WATER_TROLL: + case PM_OLOG_HAI: case PM_UMBER_HULK: + + case PM_VLAD_THE_IMPALER: + + case PM_BARROW_WIGHT: case PM_WRAITH: case PM_NAZGUL: + case PM_XORN: case PM_MONKEY: case PM_APE: case PM_OWLBEAR: + case PM_YETI: case PM_CARNIVOROUS_APE: case PM_SASQUATCH: + + case PM_GHOUL: case PM_SKELETON: + + case PM_STRAW_GOLEM: case PM_FLESH_GOLEM: + + case PM_HUMAN: case PM_HUMAN_WERERAT: case PM_HUMAN_WEREJACKAL: + case PM_HUMAN_WEREWOLF: case PM_ELF: case PM_WOODLAND_ELF: + case PM_GREEN_ELF: case PM_GREY_ELF: case PM_ELF_NOBLE: + case PM_ELVEN_MONARCH: + case PM_DOPPELGANGER: case PM_SHOPKEEPER: + case PM_GUARD: case PM_PRISONER: case PM_ORACLE: + case PM_ALIGNED_CLERIC: case PM_HIGH_CLERIC: + case PM_SOLDIER: case PM_SERGEANT: case PM_NURSE: + case PM_LIEUTENANT: case PM_CAPTAIN: case PM_WATCHMAN: + case PM_WATCH_CAPTAIN: + + case PM_MEDUSA: case PM_WIZARD_OF_YENDOR: case PM_CROESUS: + case PM_GHOST: case PM_SHADE: case PM_WATER_DEMON: + case PM_AMOROUS_DEMON: case PM_HORNED_DEVIL: + case PM_ERINYS: case PM_BARBED_DEVIL: case PM_MARILITH: case PM_VROCK: + case PM_HEZROU: case PM_BONE_DEVIL: case PM_ICE_DEVIL: case PM_NALFESHNEE: + case PM_PIT_FIEND: case PM_SANDESTIN: case PM_BALROG: case PM_JUIBLEX: + case PM_YEENOGHU: case PM_ORCUS: case PM_GERYON: case PM_DISPATER: + case PM_BAALZEBUB: case PM_ASMODEUS: case PM_DEMOGORGON: + case PM_DEATH: case PM_PESTILENCE: case PM_FAMINE: + case PM_MAIL_DAEMON: case PM_DJINNI: + + case PM_JELLYFISH: case PM_PIRANHA: case PM_SHARK: case PM_GIANT_EEL: + case PM_ELECTRIC_EEL: case PM_KRAKEN: + case PM_NEWT: case PM_GECKO: case PM_IGUANA: case PM_BABY_CROCODILE: + case PM_LIZARD: case PM_CHAMELEON: case PM_CROCODILE: + case PM_SALAMANDER: case PM_LONG_WORM_TAIL: + + case PM_ARCHEOLOGIST: case PM_BARBARIAN: case PM_CAVE_DWELLER: + case PM_HEALER: case PM_KNIGHT: case PM_MONK: case PM_CLERIC: + case PM_RANGER: case PM_ROGUE: case PM_SAMURAI: case PM_TOURIST: + case PM_VALKYRIE: case PM_WIZARD: + + case PM_LORD_CARNARVON: case PM_PELIAS: case PM_SHAMAN_KARNOV: + case PM_HIPPOCRATES: case PM_KING_ARTHUR: case PM_GRAND_MASTER: + case PM_ARCH_PRIEST: case PM_ORION: case PM_MASTER_OF_THIEVES: + case PM_LORD_SATO: case PM_TWOFLOWER: case PM_NORN: + case PM_NEFERET_THE_GREEN: case PM_MINION_OF_HUHETOTL: + case PM_THOTH_AMON: case PM_CHROMATIC_DRAGON: case PM_CYCLOPS: + case PM_IXOTH: case PM_MASTER_KAEN: case PM_NALZOK: + case PM_SCORPIUS: case PM_MASTER_ASSASSIN: case PM_ASHIKAGA_TAKAUJI: + case PM_LORD_SURTUR: case PM_DARK_ONE: case PM_STUDENT: + case PM_CHIEFTAIN: case PM_NEANDERTHAL: case PM_ATTENDANT: + case PM_PAGE: case PM_ABBOT: case PM_ACOLYTE: case PM_HUNTER: + case PM_THUG: case PM_NINJA: case PM_ROSHI: case PM_GUIDE: + case PM_WARRIOR: case PM_APPRENTICE: +#else default: - if (mvitals[mndx].mvflags & G_NOCORPSE) { +#endif + default_1: + if (svm.mvitals[mndx].mvflags & G_NOCORPSE) { return (struct obj *) 0; } else { corpstatflags |= CORPSTAT_INIT; @@ -464,11 +914,11 @@ unsigned corpseflags; /* if polymorph or undead turning has killed this monster, prevent the same attack beam from hitting its corpse */ - if (context.bypasses) + if (svc.context.bypasses) bypass_obj(obj); - if (has_mname(mtmp)) - obj = oname(obj, MNAME(mtmp)); + if (has_mgivenname(mtmp)) + obj = oname(obj, MGIVENNAME(mtmp), ONAME_NO_FLAGS); /* Avoid "It was hidden under a green mold corpse!" * during Blind combat. An unseen monster referred to as "it" @@ -479,19 +929,39 @@ unsigned corpseflags; * if the corpse's obj->dknown is 0. */ if (Blind && !sensemon(mtmp)) - obj->dknown = 0; + clear_dknown(obj); /* obj->dknown = 0; */ - stackobj(obj); + stackobj(obj); /* 'obj' remains valid if stacking happens */ newsym(x, y); + /* in case the corpse was placed at a different spot from where + the monster was (not expected to happen) */ + if (obj->ox != x || obj->oy != y) + newsym(obj->ox, obj->oy); return obj; } +#undef KEEPTRAITS + /* check mtmp and water/lava for compatibility, 0 (survived), 1 (died) */ int -minliquid(mtmp) -register struct monst *mtmp; +minliquid(struct monst *mtmp) +{ + int res; + + /* set up flag for mondead() and xkilled() */ + iflags.sad_feeling = (mtmp->mtame && !canseemon(mtmp)); + res = minliquid_core(mtmp); + /* always clear the flag */ + iflags.sad_feeling = FALSE; + return res; +} + +/* guts of minliquid() */ +staticfn int +minliquid_core(struct monst *mtmp) { boolean inpool, inlava, infountain; + boolean waterwall = is_waterwall(mtmp->mx,mtmp->my); /* [ceiling clingers are handled below] */ inpool = (is_pool(mtmp->mx, mtmp->my) @@ -507,7 +977,7 @@ register struct monst *mtmp; water location on the Plane of Water, flight and levitating are blocked so this (Flying || Levitation) test fails there and steed will be subject to water effects, as intended) */ - if (mtmp == u.usteed && (Flying || Levitation)) + if (mtmp == u.usteed && (Flying || Levitation) && !waterwall) return 0; /* Gremlin multiplying won't go on forever since the hit points @@ -524,12 +994,12 @@ register struct monst *mtmp; int dam = d(2, 6); if (cansee(mtmp->mx, mtmp->my)) - pline("%s rusts.", Monnam(mtmp)); + pline_mon(mtmp, "%s rusts.", Monnam(mtmp)); mtmp->mhp -= dam; if (mtmp->mhpmax > dam) mtmp->mhpmax -= dam; if (DEADMONSTER(mtmp)) { - mondead(mtmp); + mondied(mtmp); if (DEADMONSTER(mtmp)) return 1; } @@ -547,7 +1017,7 @@ register struct monst *mtmp; /* not fair...? hero doesn't automatically teleport away from lava, just from water */ if (can_teleport(mtmp->data) && !tele_restrict(mtmp)) { - if (rloc(mtmp, TRUE)) + if (rloc(mtmp, RLOC_MSG)) return 0; } if (!resists_fire(mtmp)) { @@ -555,7 +1025,7 @@ register struct monst *mtmp; struct attack *dummy = &mtmp->data->mattk[0]; const char *how = on_fire(mtmp->data, dummy); - pline("%s %s.", Monnam(mtmp), + pline_mon(mtmp, "%s %s.", Monnam(mtmp), !strcmp(how, "boiling") ? "boils away" : !strcmp(how, "melting") ? "melts away" : "burns to a crisp"); @@ -564,68 +1034,84 @@ register struct monst *mtmp; hero to create lava beneath a monster, so the !mon_moving case is not expected to happen (and we haven't made a player-against-monster variation of the message above) */ - if (context.mon_moving) - mondead(mtmp); + if (svc.context.mon_moving) + mondead(mtmp); /* no corpse */ else xkilled(mtmp, XKILL_NOMSG); } else { mtmp->mhp -= 1; if (DEADMONSTER(mtmp)) { if (cansee(mtmp->mx, mtmp->my)) - pline("%s surrenders to the fire.", Monnam(mtmp)); - mondead(mtmp); - } else if (cansee(mtmp->mx, mtmp->my)) - pline("%s burns slightly.", Monnam(mtmp)); + pline_mon(mtmp, "%s surrenders to the fire.", + Monnam(mtmp)); + mondead(mtmp); /* no corpse */ + } else if (cansee(mtmp->mx, mtmp->my)) { + pline_mon(mtmp, "%s burns slightly.", Monnam(mtmp)); + } } if (!DEADMONSTER(mtmp)) { - (void) fire_damage_chain(mtmp->minvent, FALSE, FALSE, - mtmp->mx, mtmp->my); - (void) rloc(mtmp, FALSE); + if (m_in_air(mtmp)) { + ; /* vampshifter in wolf form can revert to vampire lord + * and become a flyer so not need to teleport */ + } else if (likes_lava(mtmp->data)) { + ; /* likes_lava case is hypothetical */ + } else { + (void) fire_damage_chain(mtmp->minvent, FALSE, FALSE, + mtmp->mx, mtmp->my); + if (!rloc(mtmp, RLOC_MSG)) + deal_with_overcrowding(mtmp); + } return 0; } return 1; } - } else if (inpool) { + } else if (inpool || waterwall) { /* Most monsters drown in pools. flooreffects() will take care of * water damage to dead monsters' inventory, but survivors need to * be handled here. Swimmers are able to protect their stuff... */ - if (!is_clinger(mtmp->data) && !is_swimmer(mtmp->data) - && !amphibious(mtmp->data)) { + if ((waterwall || !is_clinger(mtmp->data)) + && !cant_drown(mtmp->data)) { /* like hero with teleport intrinsic or spell, teleport away if possible */ if (can_teleport(mtmp->data) && !tele_restrict(mtmp)) { - if (rloc(mtmp, TRUE)) + if (rloc(mtmp, RLOC_MSG)) return 0; } if (cansee(mtmp->mx, mtmp->my)) { - if (context.mon_moving) - pline("%s drowns.", Monnam(mtmp)); + if (svc.context.mon_moving) + pline_mon(mtmp, "%s drowns.", Monnam(mtmp)); else /* hero used fire to melt ice that monster was on */ You("drown %s.", mon_nam(mtmp)); } - if (u.ustuck && u.uswallow && u.ustuck == mtmp) { + if (engulfing_u(mtmp)) { /* This can happen after a purple worm plucks you off a flying steed while you are over water. */ pline("%s sinks as %s rushes in and flushes you out.", Monnam(mtmp), hliquid("water")); } - if (context.mon_moving) - mondead(mtmp); + if (svc.context.mon_moving) + mondied(mtmp); /* ok to leave corpse despite water */ else xkilled(mtmp, XKILL_NOMSG); if (!DEADMONSTER(mtmp)) { - water_damage_chain(mtmp->minvent, FALSE); - if (!rloc(mtmp, TRUE)) - deal_with_overcrowding(mtmp); + if (m_in_air(mtmp)) { + ; /* vampshifter in wolf form can revert to vampire lord + * and become a flyer so not need to teleport */ + } else { + water_damage_chain(mtmp->minvent, FALSE); + if (!rloc(mtmp, RLOC_NOMSG)) + deal_with_overcrowding(mtmp); + } return 0; } return 1; } } else { /* but eels have a difficult time outside */ - if (mtmp->data->mlet == S_EEL && !Is_waterlevel(&u.uz)) { + if (mtmp->data->mlet == S_EEL && !Is_waterlevel(&u.uz) + && !breathless(mtmp->data)) { /* as mhp gets lower, the rate of further loss slows down */ if (mtmp->mhp > 1 && rn2(mtmp->mhp) > rn2(8)) mtmp->mhp--; @@ -635,9 +1121,12 @@ register struct monst *mtmp; return 0; } +/* calculate 'mon's movement for current turn; called from moveloop() */ int -mcalcmove(mon) -struct monst *mon; +mcalcmove( + struct monst *mon, + boolean m_moving) /* True: adjust for moving; + * False: just adjust for speed */ { int mmove = mon->data->mmove; int mmove_adj; @@ -646,29 +1135,35 @@ struct monst *mon; * MFAST's `+ 2' prevents hasted speed 1 from becoming a no-op; * both adjustments have negligible effect on higher speeds. */ - if (mon->mspeed == MSLOW) - mmove = (2 * mmove + 1) / 3; - else if (mon->mspeed == MFAST) + if (mon->mspeed == MSLOW) { + /* slow-monster effects work better against faster monsters: they + lose 1/3 of their speed below 12 but 2/3 of their speed above */ + if (mmove < NORMAL_SPEED) + mmove = (2 * mmove + 1) / 3; + else + mmove = 4 + (mmove / 3); + } else if (mon->mspeed == MFAST) mmove = (4 * mmove + 2) / 3; - if (mon == u.usteed && u.ugallop && context.mv) { + if (mon == u.usteed && u.ugallop && svc.context.mv) { /* increase movement by a factor of 1.5; also increase variance of movement speed (if it's naturally 24, we don't want it to always become 36) */ mmove = ((rn2(2) ? 4 : 5) * mmove) / 3; } - /* Randomly round the monster's speed to a multiple of NORMAL_SPEED. - This makes it impossible for the player to predict when they'll get - a free turn (thus preventing exploits like "melee kiting"), while - retaining guarantees about shopkeepers not being outsped by a - normal-speed player, normal-speed players being unable to open up - a gap when fleeing a normal-speed monster, etc. */ - mmove_adj = mmove % NORMAL_SPEED; - mmove -= mmove_adj; - if (rn2(NORMAL_SPEED) < mmove_adj) - mmove += NORMAL_SPEED; - + if (m_moving) { + /* Randomly round the monster's speed to a multiple of NORMAL_SPEED. + This makes it impossible for the player to predict when they'll + get a free turn (thus preventing exploits like "melee kiting"), + while retaining guarantees about shopkeepers not being outsped + by a normal-speed player, normal-speed players being unable + to open up a gap when fleeing a normal-speed monster, etc. */ + mmove_adj = mmove % NORMAL_SPEED; + mmove -= mmove_adj; + if (rn2(NORMAL_SPEED) < mmove_adj) + mmove += NORMAL_SPEED; + } return mmove; } @@ -676,171 +1171,168 @@ struct monst *mon; individual monster's metabolism; some of these might need to be reclassified to occur more in proportion with movement rate */ void -mcalcdistress() +mcalcdistress(void) { - struct monst *mtmp; - - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) - continue; + iter_mons(m_calcdistress); +} - /* must check non-moving monsters once/turn in case they managed - to end up in water or lava; note: when not in liquid they regen, - shape-shift, timeout temporary maladies just like other monsters */ - if (mtmp->data->mmove == 0) { - if (vision_full_recalc) - vision_recalc(0); - if (minliquid(mtmp)) - continue; - } +staticfn void +m_calcdistress(struct monst *mtmp) +{ + /* must check non-moving monsters once/turn in case they managed + to end up in water or lava; note: when not in liquid they regen, + shape-shift, timeout temporary maladies just like other monsters */ + if (mtmp->data->mmove == 0) { + if (gv.vision_full_recalc) + vision_recalc(0); + if (minliquid(mtmp)) + return; + } - /* regenerate hit points */ - mon_regen(mtmp, FALSE); + /* regenerate hit points */ + mon_regen(mtmp, FALSE); - /* possibly polymorph shapechangers and lycanthropes */ - if (mtmp->cham >= LOW_PM) - decide_to_shapeshift(mtmp, (canspotmon(mtmp) - || (u.uswallow && mtmp == u.ustuck)) - ? SHIFT_MSG : 0); - were_change(mtmp); + /* possibly polymorph shapechangers and lycanthropes */ + if (ismnum(mtmp->cham)) + decide_to_shapeshift(mtmp); + were_change(mtmp); - /* gradually time out temporary problems */ - if (mtmp->mblinded && !--mtmp->mblinded) - mtmp->mcansee = 1; - if (mtmp->mfrozen && !--mtmp->mfrozen) - mtmp->mcanmove = 1; - if (mtmp->mfleetim && !--mtmp->mfleetim) - mtmp->mflee = 0; + /* gradually time out temporary problems */ + if (mtmp->mblinded && !--mtmp->mblinded) + mtmp->mcansee = 1; + if (mtmp->mfrozen && !--mtmp->mfrozen) + mtmp->mcanmove = 1; + if (mtmp->mfleetim && !--mtmp->mfleetim) + mtmp->mflee = 0; - /* FIXME: mtmp->mlstmv ought to be updated here */ - } + /* FIXME: mtmp->mlstmv ought to be updated here */ } -int -movemon() +/* perform movement for a single monster. + meant to be used with iter_mons_safe. */ +boolean +movemon_singlemon(struct monst *mtmp) { - register struct monst *mtmp, *nmtmp; - register boolean somebody_can_move = FALSE; - - /* - * Some of you may remember the former assertion here that - * because of deaths and other actions, a simple one-pass - * algorithm wasn't possible for movemon. Deaths are no longer - * removed to the separate list fdmon; they are simply left in - * the chain with hit points <= 0, to be cleaned up at the end - * of the pass. - * - * The only other actions which cause monsters to be removed from - * the chain are level migrations and losedogs(). I believe losedogs() - * is a cleanup routine not associated with monster movements, and - * monsters can only affect level migrations on themselves, not others - * (hence the fetching of nmon before moving the monster). Currently, - * monsters can jump into traps, read cursed scrolls of teleportation, - * and drink cursed potions of raise level to change levels. These are - * all reflexive at this point. Should one monster be able to level - * teleport another, this scheme would have problems. - */ - - for (mtmp = fmon; mtmp; mtmp = nmtmp) { - /* end monster movement early if hero is flagged to leave the level */ - if (u.utotype + /* end monster movement early if hero is flagged to leave the level */ + if (u.utotype #ifdef SAFERHANGUP - /* or if the program has lost contact with the user */ - || program_state.done_hup + /* or if the program has lost contact with the user */ + || program_state.done_hup #endif - ) { - somebody_can_move = FALSE; - break; - } - nmtmp = mtmp->nmon; - /* one dead monster needs to perform a move after death: vault - guard whose temporary corridor is still on the map; live - guards who have led the hero back to civilization get moved - off the map too; gd_move() decides whether the temporary - corridor can be removed and guard discarded (via clearing - mon->isgd flag so that dmonsfree() will get rid of mon) */ - if (mtmp->isgd && !mtmp->mx) { - /* parked at <0,0>; eventually isgd should get set to false */ - if (monstermoves > mtmp->mlstmv) { - (void) gd_move(mtmp); - mtmp->mlstmv = monstermoves; - } - continue; + ) { + gs.somebody_can_move = FALSE; + return TRUE; + } + + /* one dead monster needs to perform a move after death: vault + guard whose temporary corridor is still on the map; live + guards who have led the hero back to civilization get moved + off the map too; gd_move() decides whether the temporary + corridor can be removed and guard discarded (via clearing + mon->isgd flag so that dmonsfree() will get rid of mon) */ + if (mtmp->isgd && !mtmp->mx && !(mtmp->mstate & MON_MIGRATING)) { + /* parked at <0,0>; eventually isgd should get set to false */ + if (svm.moves > mtmp->mlstmv) { + (void) gd_move(mtmp); + mtmp->mlstmv = svm.moves; } - if (DEADMONSTER(mtmp)) - continue; + return FALSE; + } + if (DEADMONSTER(mtmp)) + return FALSE; - /* Find a monster that we have not treated yet. */ - if (mtmp->movement < NORMAL_SPEED) - continue; + /* monster isn't on this map anymore */ + if (mon_offmap(mtmp)) + return FALSE; - mtmp->movement -= NORMAL_SPEED; - if (mtmp->movement >= NORMAL_SPEED) - somebody_can_move = TRUE; + m_everyturn_effect(mtmp); - if (vision_full_recalc) - vision_recalc(0); /* vision! */ + /* Find a monster that we have not treated yet. */ + if (mtmp->movement < NORMAL_SPEED) + return FALSE; - /* reset obj bypasses before next monster moves */ - if (context.bypasses) - clear_bypasses(); - clear_splitobjs(); - if (minliquid(mtmp)) - continue; + mtmp->movement -= NORMAL_SPEED; + if (mtmp->movement >= NORMAL_SPEED) + gs.somebody_can_move = TRUE; + + if (gv.vision_full_recalc) + vision_recalc(0); /* vision! */ + + /* reset obj bypasses before next monster moves */ + if (svc.context.bypasses) + clear_bypasses(); + clear_splitobjs(); + if (minliquid(mtmp)) + return FALSE; - /* after losing equipment, try to put on replacement */ - if (mtmp->misc_worn_check & I_SPECIAL) { - long oldworn; + /* after losing equipment, try to put on replacement */ + if (mtmp->misc_worn_check & I_SPECIAL) { + long oldworn; + /* hostiles only try to equip things if they think hero isn't + * nearby; if they think hero is nearby, leave the flag intact so + * that it can be checked again on subsequent moves until the hero + * is perceived to be farther away. */ + if (mtmp->mpeaceful || mtmp->mtame + || dist2(mtmp->mx, mtmp->my, mtmp->mux, mtmp->muy) > (3 * 3)) { mtmp->misc_worn_check &= ~I_SPECIAL; oldworn = mtmp->misc_worn_check; m_dowear(mtmp, FALSE); if (mtmp->misc_worn_check != oldworn || !mtmp->mcanmove) - continue; + return FALSE; /* is spending this turn equipping */ } + } - if (is_hider(mtmp->data)) { - /* unwatched mimics and piercers may hide again [MRS] */ - if (restrap(mtmp)) - continue; - if (M_AP_TYPE(mtmp) == M_AP_FURNITURE - || M_AP_TYPE(mtmp) == M_AP_OBJECT) - continue; - if (mtmp->mundetected) - continue; - } else if (mtmp->data->mlet == S_EEL && !mtmp->mundetected - && (mtmp->mflee || distu(mtmp->mx, mtmp->my) > 2) - && !canseemon(mtmp) && !rn2(4)) { - /* some eels end up stuck in isolated pools, where they - can't--or at least won't--move, so they never reach - their post-move chance to re-hide */ - if (hideunder(mtmp)) - continue; - } + if (is_hider(mtmp->data)) { + /* unwatched mimics and piercers may hide again [MRS] */ + if (restrap(mtmp)) + return FALSE; + if (M_AP_TYPE(mtmp) == M_AP_FURNITURE + || M_AP_TYPE(mtmp) == M_AP_OBJECT) + return FALSE; + if (mtmp->mundetected) + return FALSE; + } else if (mtmp->data->mlet == S_EEL && !mtmp->mundetected + && (mtmp->mflee || !m_next2u(mtmp)) + && !canseemon(mtmp) && !rn2(4)) { + /* some eels end up stuck in isolated pools, where they + can't--or at least won't--move, so they never reach + their post-move chance to re-hide */ + if (hideunder(mtmp)) + return FALSE; + } - /* continue if the monster died fighting */ - if (Conflict && !mtmp->iswiz && mtmp->mcansee) { - /* Note: - * Conflict does not take effect in the first round. - * Therefore, A monster when stepping into the area will - * get to swing at you. - * - * The call to fightm() must be _last_. The monster might - * have died if it returns 1. - */ - if (couldsee(mtmp->mx, mtmp->my) - && (distu(mtmp->mx, mtmp->my) <= BOLT_LIM * BOLT_LIM) - && fightm(mtmp)) - continue; /* mon might have died */ - } - if (dochugw(mtmp)) /* otherwise just move the monster */ - continue; + /* continue if the monster died fighting */ + if (Conflict && !mtmp->iswiz && m_canseeu(mtmp)) { + /* Note: + * Conflict does not take effect in the first round. + * Therefore, A monster when stepping into the area will + * get to swing at you. + * + * The call to fightm() must be _last_. The monster might + * have died if it returns 1. + */ + if (cansee(mtmp->mx, mtmp->my) + && (mdistu(mtmp) <= BOLT_LIM * BOLT_LIM) + && fightm(mtmp)) + return FALSE; /* mon might have died */ } + (void) dochugw(mtmp, TRUE); /* otherwise just move the monster */ + return FALSE; +} + +/* perform movement for all monsters */ +int +movemon(void) +{ + gs.somebody_can_move = FALSE; + + iter_mons_safe(movemon_singlemon); if (any_light_source()) - vision_full_recalc = 1; /* in case a mon moved with a light source */ + gv.vision_full_recalc = 1; /* in case a mon moved w/ a light source */ /* reset obj bypasses after last monster has moved */ - if (context.bypasses) + if (svc.context.bypasses) clear_bypasses(); clear_splitobjs(); /* remove dead monsters; dead vault guard will be left at <0,0> @@ -851,15 +1343,114 @@ movemon() if (u.utotype) { deferred_goto(); /* changed levels, so these monsters are dormant */ - somebody_can_move = FALSE; + gs.somebody_can_move = FALSE; } - return somebody_can_move; + return gs.somebody_can_move; +} + +/* dispose of contents of an eaten container; used for pets and other mons */ +void +meatbox(struct monst *mon, struct obj *otmp) +{ + boolean engulf_contents = (mon->data == &mons[PM_GELATINOUS_CUBE]); + int x = mon->mx, y = mon->my; + struct obj *cobj; + + if (!Has_contents(otmp) || !isok(x, y)) + return; + + /* contents of eaten containers become engulfed or dropped onto + the floor; this is arbitrary, but otherwise g-cubes are too + powerful */ + if (!engulf_contents && cansee(x, y)) { + pline("%s contents spill out onto the %s.", + s_suffix(The(distant_name(otmp, xname))), + surface(x, y)); + } + while ((cobj = otmp->cobj) != 0) { + obj_extract_self(cobj); + if (otmp->otyp == ICE_BOX) + removed_from_icebox(cobj); + if (engulf_contents) { + (void) mpickobj(mon, cobj); + } else { + if (!flooreffects(cobj, x, y, "")) + place_object(cobj, x, y); + } + } } -#define mstoning(obj) \ - (ofood(obj) && (touch_petrifies(&mons[(obj)->corpsenm]) \ - || (obj)->corpsenm == PM_MEDUSA)) +#define mstoning(obj) \ + (ofood(obj) && ismnum(obj->corpsenm) \ + && flesh_petrifies(&mons[obj->corpsenm])) + +/* Monster mtmp consumes an object. + Monster may die, polymorph, grow up, heal, etc; meating is not changed. + Object is extracted from any linked list and freed. */ +void +m_consume_obj(struct monst *mtmp, struct obj *otmp) +{ + boolean ispet = mtmp->mtame; + + /* non-pet: Heal up to the object's weight in hp */ + if (!ispet && mtmp->mhp < mtmp->mhpmax) { + healmon(mtmp, objects[otmp->otyp].oc_weight, 0); + } + if (Has_contents(otmp)) + meatbox(mtmp, otmp); + if (otmp == uball) { + unpunish(); + delobj(otmp); + } else if (otmp == uchain) { + unpunish(); /* frees uchain */ + } else { + boolean deadmimic, slimer; + int poly, grow, heal, eyes, mstone, vis = canseemon(mtmp); + int corpsenm = (otmp->otyp == CORPSE ? otmp->corpsenm : NON_PM); + + deadmimic = (otmp->otyp == CORPSE && (corpsenm == PM_SMALL_MIMIC + || corpsenm == PM_LARGE_MIMIC + || corpsenm == PM_GIANT_MIMIC)); + slimer = (otmp->otyp == GLOB_OF_GREEN_SLIME); + poly = polyfood(otmp); + grow = mlevelgain(otmp); + heal = mhealup(otmp); + eyes = (otmp->otyp == CARROT); + mstone = mstoning(otmp); + delobj(otmp); /* munch */ + if (poly || slimer) { + struct permonst *ptr = slimer ? &mons[PM_GREEN_SLIME] : 0; + + (void) newcham(mtmp, ptr, vis ? NC_SHOW_MSG : NO_NC_FLAGS); + } + if (grow) { + if ((ispet && (int) mtmp->m_lev < (int) mtmp->data->mlevel + 15) + || !ispet) + (void) grow_up(mtmp, (struct monst *) 0); + } + if (mstone) { + if (poly_when_stoned(mtmp->data)) { + mon_to_stone(mtmp); + } else if (!resists_ston(mtmp)) { + if (vis) + pline_mon(mtmp, "%s turns to stone!", + Monnam(mtmp)); + monstone(mtmp); + } + } + if (heal) + healmon(mtmp, mtmp->mhpmax, 0); + if ((eyes || heal) && !mtmp->mcansee) + mcureblindness(mtmp, canseemon(mtmp)); + if (ispet && deadmimic) + quickmimic(mtmp); + if (otmp->otyp == EGG && corpsenm == PM_PYROLISK) + explode(mtmp->mx, mtmp->my, -11, d(3, 6), 0, EXPL_FIERY); + if (corpsenm != NON_PM) + mon_givit(mtmp, &mons[corpsenm]); + } +} /* * Maybe eat a metallic object (not just gold). @@ -869,85 +1460,63 @@ movemon() * has young and old forms). */ int -meatmetal(mtmp) -register struct monst *mtmp; +meatmetal(struct monst *mtmp) { - register struct obj *otmp; - struct permonst *ptr; - int poly, grow, heal, mstone; + struct obj *otmp; + char *otmpname; + int vis = canseemon(mtmp); /* If a pet, eating is handled separately, in dog.c */ if (mtmp->mtame) return 0; /* Eats topmost metal object if it is there */ - for (otmp = level.objects[mtmp->mx][mtmp->my]; otmp; + for (otmp = svl.level.objects[mtmp->mx][mtmp->my]; otmp; otmp = otmp->nexthere) { /* Don't eat indigestible/choking/inappropriate objects */ if ((mtmp->data == &mons[PM_RUST_MONSTER] && !is_rustprone(otmp)) - || (otmp->otyp == AMULET_OF_STRANGULATION) - || (otmp->otyp == RIN_SLOW_DIGESTION)) + || (otmp->otyp == AMULET_OF_STRANGULATION + || otmp->otyp == RIN_SLOW_DIGESTION) + || (otmp->opoisoned && !resists_poison(mtmp))) continue; if (is_metallic(otmp) && !obj_resists(otmp, 5, 95) && touch_artifact(otmp, mtmp)) { if (mtmp->data == &mons[PM_RUST_MONSTER] && otmp->oerodeproof) { - if (canseemon(mtmp) && flags.verbose) { - pline("%s eats %s!", Monnam(mtmp), - distant_name(otmp, doname)); + if (vis) { + /* call distant_name() for its side-effects even when + !verbose so won't be printed */ + otmpname = distant_name(otmp, doname); + if (flags.verbose) + pline_mon(mtmp, "%s eats %s!", + Monnam(mtmp), otmpname); } /* The object's rustproofing is gone now */ otmp->oerodeproof = 0; mtmp->mstun = 1; - if (canseemon(mtmp) && flags.verbose) { - pline("%s spits %s out in disgust!", Monnam(mtmp), - distant_name(otmp, doname)); + if (vis) { + /* (see above; format even if it won't be printed) */ + otmpname = distant_name(otmp, doname); + if (flags.verbose) + pline_mon(mtmp, "%s spits %s out in disgust!", + Monnam(mtmp), otmpname); } } else { - if (cansee(mtmp->mx, mtmp->my) && flags.verbose) - pline("%s eats %s!", Monnam(mtmp), - distant_name(otmp, doname)); - else if (flags.verbose) - You_hear("a crunching sound."); - mtmp->meating = otmp->owt / 2 + 1; - /* Heal up to the object's weight in hp */ - if (mtmp->mhp < mtmp->mhpmax) { - mtmp->mhp += objects[otmp->otyp].oc_weight; - if (mtmp->mhp > mtmp->mhpmax) - mtmp->mhp = mtmp->mhpmax; - } - if (otmp == uball) { - unpunish(); - delobj(otmp); - } else if (otmp == uchain) { - unpunish(); /* frees uchain */ + if (cansee(mtmp->mx, mtmp->my)) { + /* (see above; format even if it won't be printed) */ + otmpname = distant_name(otmp, doname); + if (flags.verbose) + pline_mon(mtmp, "%s eats %s!", + Monnam(mtmp), otmpname); } else { - poly = polyfodder(otmp); - grow = mlevelgain(otmp); - heal = mhealup(otmp); - mstone = mstoning(otmp); - delobj(otmp); - ptr = mtmp->data; - if (poly) { - if (newcham(mtmp, (struct permonst *) 0, FALSE, FALSE)) - ptr = mtmp->data; - } else if (grow) { - ptr = grow_up(mtmp, (struct monst *) 0); - } else if (mstone) { - if (poly_when_stoned(ptr)) { - mon_to_stone(mtmp); - ptr = mtmp->data; - } else if (!resists_ston(mtmp)) { - if (canseemon(mtmp)) - pline("%s turns to stone!", Monnam(mtmp)); - monstone(mtmp); - ptr = (struct permonst *) 0; - } - } else if (heal) { - mtmp->mhp = mtmp->mhpmax; + if (flags.verbose) { + Soundeffect(se_crunching_sound, 50); + You_hear("a crunching sound."); } - if (!ptr) - return 2; /* it died */ } + mtmp->meating = otmp->owt / 2 + 1; + m_consume_obj(mtmp, otmp); + if (DEADMONSTER(mtmp)) + return 2; /* Left behind a pile? */ if (rnd(25) < 3) (void) mksobj_at(ROCK, mtmp->mx, mtmp->my, TRUE, FALSE); @@ -961,13 +1530,12 @@ register struct monst *mtmp; /* monster eats a pile of objects */ int -meatobj(mtmp) /* for gelatinous cubes */ -struct monst *mtmp; +meatobj(struct monst *mtmp) /* for gelatinous cubes */ { struct obj *otmp, *otmp2; struct permonst *ptr, *original_ptr = mtmp->data; - int poly, grow, heal, eyes, count = 0, ecount = 0; - char buf[BUFSZ]; + int count = 0, ecount = 0; + char buf[BUFSZ], *otmpname; buf[0] = '\0'; /* If a pet, eating is handled separately, in dog.c */ @@ -976,14 +1544,28 @@ struct monst *mtmp; /* eat organic objects, including cloth and wood, if present; engulf others, except huge rocks and metal attached to player - [despite comment at top, doesn't assume that eater is a g.cube] */ - for (otmp = level.objects[mtmp->mx][mtmp->my]; otmp; otmp = otmp2) { + [despite comment at top, doesn't assume that eater is a g-cube] */ + for (otmp = svl.level.objects[mtmp->mx][mtmp->my]; otmp; otmp = otmp2) { otmp2 = otmp->nexthere; + /* avoid special items; once hero picks them up, they'll cease + being special, becoming eligible for engulf and devore if + dropped again */ + if (is_mines_prize(otmp) || is_soko_prize(otmp)) + continue; + /* touch sensitive items */ if (otmp->otyp == CORPSE && is_rider(&mons[otmp->corpsenm])) { + int ox = otmp->ox, oy = otmp->oy; + boolean revived_it = revive_corpse(otmp); + + newsym(ox, oy); /* Rider corpse isn't just inedible; can't engulf it either */ - (void) revive_corpse(otmp); + if (!revived_it) + continue; + /* [should check whether revival forced 'mtmp' off the level + and return 3 in that situation (if possible...)] */ + break; /* untouchable (or inaccessible) items */ } else if ((otmp->otyp == CORPSE @@ -1007,19 +1589,19 @@ struct monst *mtmp; included for emphasis */ || (otmp->otyp == AMULET_OF_STRANGULATION || otmp->otyp == RIN_SLOW_DIGESTION) + || (otmp->opoisoned && !resists_poison(mtmp)) /* cockatrice corpses handled above; this touch_petrifies() check catches eggs */ - || ((otmp->otyp == CORPSE || otmp->otyp == EGG - || otmp->globby) - && ((touch_petrifies(&mons[otmp->corpsenm]) - && !resists_ston(mtmp)) - || (otmp->corpsenm == PM_GREEN_SLIME - && !slimeproof(mtmp->data))))) { + || (mstoning(otmp) && !resists_ston(mtmp)) + || (otmp->otyp == GLOB_OF_GREEN_SLIME + && !slimeproof(mtmp->data))) { /* engulf */ ++ecount; + /* call distant_name() for its possible side-effects even if + the result won't be printed */ + otmpname = distant_name(otmp, doname); if (ecount == 1) - Sprintf(buf, "%s engulfs %s.", Monnam(mtmp), - distant_name(otmp, doname)); + Sprintf(buf, "%s engulfs %s.", Monnam(mtmp), otmpname); else if (ecount == 2) Sprintf(buf, "%s engulfs several objects.", Monnam(mtmp)); obj_extract_self(otmp); @@ -1030,54 +1612,23 @@ struct monst *mtmp; /* devour */ ++count; if (cansee(mtmp->mx, mtmp->my)) { + /* (see above; distant_name() sometimes has side-effects */ + otmpname = distant_name(otmp, doname); if (flags.verbose) - pline("%s eats %s!", Monnam(mtmp), - distant_name(otmp, doname)); + pline_mon(mtmp, "%s eats %s!", + Monnam(mtmp), otmpname); /* give this one even if !verbose */ if (otmp->oclass == SCROLL_CLASS - && !strcmpi(OBJ_DESCR(objects[otmp->otyp]), "YUM YUM")) + && objdescr_is(otmp, "YUM YUM")) pline("Yum%c", otmp->blessed ? '!' : '.'); } else { + Soundeffect(se_slurping_sound, 30); if (flags.verbose) You_hear("a slurping sound."); } - /* Heal up to the object's weight in hp */ - if (mtmp->mhp < mtmp->mhpmax) { - mtmp->mhp += objects[otmp->otyp].oc_weight; - if (mtmp->mhp > mtmp->mhpmax) - mtmp->mhp = mtmp->mhpmax; - } - if (Has_contents(otmp)) { - register struct obj *otmp3; - - /* contents of eaten containers become engulfed; this - is arbitrary, but otherwise g.cubes are too powerful */ - while ((otmp3 = otmp->cobj) != 0) { - obj_extract_self(otmp3); - if (otmp->otyp == ICE_BOX && otmp3->otyp == CORPSE) { - otmp3->age = monstermoves - otmp3->age; - start_corpse_timeout(otmp3); - } - (void) mpickobj(mtmp, otmp3); - } - } - poly = polyfodder(otmp); - grow = mlevelgain(otmp); - heal = mhealup(otmp); - eyes = (otmp->otyp == CARROT); - delobj(otmp); /* munch */ - ptr = mtmp->data; - if (poly) { - if (newcham(mtmp, (struct permonst *) 0, FALSE, FALSE)) - ptr = mtmp->data; - } else if (grow) { - ptr = grow_up(mtmp, (struct monst *) 0); - } else if (heal) { - mtmp->mhp = mtmp->mhpmax; - } - if ((eyes || heal) && !mtmp->mcansee) - mcureblindness(mtmp, canseemon(mtmp)); + m_consume_obj(mtmp, otmp); /* in case it polymorphed or died */ + ptr = mtmp->data; if (ptr != original_ptr) return !ptr ? 2 : 1; } @@ -1097,11 +1648,185 @@ struct monst *mtmp; return (count > 0 || ecount > 0) ? 1 : 0; } +#undef mstoning + +/* Monster eats a corpse off the ground. + Return value is 0 = nothing eaten, 1 = ate a corpse, 2 = died. */ +int +meatcorpse( + struct monst *mtmp) /* for purple worms and other voracious monsters */ +{ + struct obj *otmp; + struct permonst *ptr, *original_ptr = mtmp->data, *corpsepm; + coordxy x = mtmp->mx, y = mtmp->my; + + /* if a pet, eating is handled separately, in dog.c */ + if (mtmp->mtame) + return 0; + + /* skips past any globs */ + for (otmp = sobj_at(CORPSE, x, y); otmp; + /* won't get back here if otmp is split or gets used up */ + otmp = nxtobj(otmp, CORPSE, TRUE)) { + + corpsepm = &mons[otmp->corpsenm]; + /* skip some corpses */ + if (vegan(corpsepm) /* ignore veggy corpse even if omnivorous */ + /* don't eat harmful corpses */ + || (flesh_petrifies(corpsepm) && !resists_ston(mtmp))) + continue; + if (is_rider(corpsepm)) { + boolean revived_it = revive_corpse(otmp); + + newsym(x, y); /* corpse is gone; mtmp might be too so do this now + since we're bypassing the bottom of the loop */ + if (!revived_it) + continue; /* revival failed? if so, corpse is gone */ + /* Successful Rider revival; unlike skipped corpses, don't + just move on to next corpse as if nothing has happened. + [Can Rider revival bump 'mtmp' off level when it's full? + We ought to return 3 if that happens.] */ + break; + } + + if (otmp->quan > 1) + otmp = splitobj(otmp, 1L); + + if (cansee(x, y) && canseemon(mtmp)) { + /* call distant_name() for its possible side-effects even if + the result won't be printed */ + char *otmpname = distant_name(otmp, doname); + + if (flags.verbose) + pline_mon(mtmp, "%s eats %s!", + Monnam(mtmp), otmpname); + } else { + Soundeffect(se_masticating_sound, 50); + if (flags.verbose) + You_hear("a masticating sound."); + } + + m_consume_obj(mtmp, otmp); + /* in case it polymorphed or died */ + ptr = mtmp->data; + if (ptr != original_ptr) + return !ptr ? 2 : 1; + + /* Engulf & devour is instant, so don't set meating */ + if (mtmp->minvis) + newsym(x, y); + + return 1; + } + return 0; +} + +/* give monster property prop */ +void +mon_give_prop(struct monst *mtmp, int prop) +{ + const char *msg = NULL; + unsigned short intrinsic = 0; /* MR_* constant */ + + /* Pets don't have all the fields that the hero does, so they can't get + all the same intrinsics. If it happens to choose strength gain or + teleport control or whatever, ignore it. */ + switch (prop) { + case FIRE_RES: + msg = "%s shivers slightly."; + break; + case COLD_RES: + msg = "%s looks quite warm."; + break; + case SLEEP_RES: + msg = "%s looks wide awake."; + break; + case DISINT_RES: + msg = "%s looks very firm."; + break; + case SHOCK_RES: + msg = "%s crackles with static electricity."; + break; + case POISON_RES: + msg = "%s looks healthy."; + break; + default: + return; /* can't give it */ + break; + } + intrinsic = res_to_mr(prop); + + /* Don't give message if it already had this property intrinsically, but + still do grant the intrinsic if it only had it from mresists. + Do print the message if it only had this property extrinsically, which + is why mon_resistancebits isn't used here. */ + if ((mtmp->data->mresists | mtmp->mintrinsics) & intrinsic) + msg = (const char *) 0; + + if (intrinsic) + mtmp->mintrinsics |= intrinsic; + + if (canseemon(mtmp) && msg) { + DISABLE_WARNING_FORMAT_NONLITERAL + pline_mon(mtmp, msg, Monnam(mtmp)); + RESTORE_WARNING_FORMAT_NONLITERAL + } +} + +/* Maybe give an intrinsic to monster from eating corpse that confers it. */ +void +mon_givit(struct monst *mtmp, struct permonst *ptr) +{ + int prop = corpse_intrinsic(ptr); + boolean vis = canseemon(mtmp); + + if (DEADMONSTER(mtmp)) + return; + + if (ptr == &mons[PM_STALKER]) { + /* + * Invisible stalker isn't flagged as conferring invisibility + * so prop is 0. For hero, eating a stalker corpse confers + * temporary invisibility if hero is visible. When already + * invisible, if confers permanent invisibility and also + * permanent see invisible. For monsters, only permanent + * invisibility is possible; temporary invisibility and see + * invisible aren't implemented for them. + * + * A monster being invisible gains no benefit against other + * monsters, and an invisible pet when hero can't see invisible + * is a nuisance at best, so this is probably detrimental. + * Players will just have to live with it if they want to be + * able to have pets gain intrinsics from eating corpses. + */ + if (!mtmp->perminvis || mtmp->invis_blkd) { + char mtmpbuf[BUFSZ]; + + Strcpy(mtmpbuf, Monnam(mtmp)); + mon_set_minvis(mtmp, FALSE); + if (vis) + pline_mon(mtmp, "%s %s.", mtmpbuf, + !canspotmon(mtmp) ? "vanishes" + : mtmp->invis_blkd ? "seems to flicker" + : "becomes invisible"); + } + mtmp->mstun = 1; /* no timeout but will eventually wear off */ + return; + } + + if (prop == 0) + return; /* no intrinsic from this corpse */ + + if (!should_givit(prop, ptr)) + return; /* failed die roll */ + + mon_give_prop(mtmp, prop); +} + void -mpickgold(mtmp) -register struct monst *mtmp; +mpickgold(struct monst *mtmp) { - register struct obj *gold; + struct obj *gold; int mat_idx; if ((gold = g_at(mtmp->mx, mtmp->my)) != 0) { @@ -1110,56 +1835,73 @@ register struct monst *mtmp; add_to_minv(mtmp, gold); if (cansee(mtmp->mx, mtmp->my)) { if (flags.verbose && !mtmp->isgd) - pline("%s picks up some %s.", Monnam(mtmp), - mat_idx == GOLD ? "gold" : "money"); + pline_mon(mtmp, "%s picks up some %s.", Monnam(mtmp), + mat_idx == GOLD ? "gold" : "money"); newsym(mtmp->mx, mtmp->my); } } } +/* monster picks up one item stack from the map location they are at */ boolean -mpickstuff(mtmp, str) -register struct monst *mtmp; -register const char *str; +mpickstuff(struct monst *mtmp) { - register struct obj *otmp, *otmp2, *otmp3; + struct obj *otmp, *otmp2, *otmp3; int carryamt = 0; /* prevent shopkeepers from leaving the door of their shop */ if (mtmp->isshk && inhishop(mtmp)) return FALSE; - for (otmp = level.objects[mtmp->mx][mtmp->my]; otmp; otmp = otmp2) { + /* non-tame monsters normally don't go shopping */ + if (!mtmp->mtame && *in_rooms(mtmp->mx, mtmp->my, SHOPBASE) && rn2(25)) + return FALSE; + + /* item in a pool, but monster can't swim */ + if (!could_reach_item(mtmp, mtmp->mx, mtmp->my)) + return FALSE; + + for (otmp = svl.level.objects[mtmp->mx][mtmp->my]; otmp; otmp = otmp2) { otmp2 = otmp->nexthere; + + /* avoid special items; once hero picks them up, they'll cease + being special, becoming eligible for normal pickup */ + if (is_mines_prize(otmp) || is_soko_prize(otmp)) + continue; + /* Nymphs take everything. Most monsters don't pick up corpses. */ - if (!str ? searches_for_item(mtmp, otmp) - : !!(index(str, otmp->oclass))) { + if (mon_would_take_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]) && otmp->corpsenm != PM_LIZARD && !acidic(&mons[otmp->corpsenm])) continue; - if (!touch_artifact(otmp, mtmp)) + if (!can_touch_safely(mtmp, otmp)) continue; carryamt = can_carry(mtmp, otmp); if (carryamt == 0) continue; - if (is_pool(mtmp->mx, mtmp->my)) - continue; /* handle cases where the critter can only get some */ otmp3 = otmp; if (carryamt != otmp->quan) { otmp3 = splitobj(otmp, carryamt); } - if (cansee(mtmp->mx, mtmp->my) && flags.verbose) - pline("%s picks up %s.", Monnam(mtmp), - (distu(mtmp->mx, mtmp->my) <= 5) - ? doname(otmp3) - : distant_name(otmp3, doname)); + if (cansee(mtmp->mx, mtmp->my)) { + /* call distant_name() for its possible side-effects even + if the result won't be printed; do it before the extract + from floor and subsequent pickup by mtmp */ + char *otmpname = distant_name(otmp, doname); + + if (flags.verbose) + pline_mon(mtmp, "%s picks up %s.", + Monnam(mtmp), otmpname); + } obj_extract_self(otmp3); /* remove from floor */ (void) mpickobj(mtmp, otmp3); /* may merge and free otmp3 */ - m_dowear(mtmp, FALSE); + /* let them try to equip it on the next turn */ + check_gear_next_turn(mtmp); newsym(mtmp->mx, mtmp->my); return TRUE; /* pick only one object */ } @@ -1168,8 +1910,7 @@ register const char *str; } int -curr_mon_load(mtmp) -struct monst *mtmp; +curr_mon_load(struct monst *mtmp) { int curload = 0; struct obj *obj; @@ -1183,8 +1924,7 @@ struct monst *mtmp; } int -max_mon_load(mtmp) -struct monst *mtmp; +max_mon_load(struct monst *mtmp) { long maxload; @@ -1213,23 +1953,41 @@ struct monst *mtmp; return (int) maxload; } +/* can monster touch object safely? */ +boolean +can_touch_safely(struct monst *mtmp, struct obj *otmp) +{ + int otyp = otmp->otyp; + struct permonst *mdat = mtmp->data; + + if (otyp == CORPSE && touch_petrifies(&mons[otmp->corpsenm]) + && !(mtmp->misc_worn_check & W_ARMG) && !resists_ston(mtmp)) + return FALSE; + if (otyp == CORPSE && is_rider(&mons[otmp->corpsenm])) + return FALSE; + if (objects[otyp].oc_material == SILVER && mon_hates_silver(mtmp) + && (otyp != BELL_OF_OPENING || !is_covetous(mdat))) + return FALSE; + if (!touch_artifact(otmp, mtmp)) + return FALSE; + return TRUE; +} + /* for restricting monsters' object-pickup. * * to support the new pet behavior, this now returns the max # of objects * that a given monster could pick up from a pile. frequently this will be - * otmp->quan, but special cases for 'only one' now exist so. + * otmp->quan, but special cases for 'only one' now exist. * * this will probably cause very amusing behavior with pets and gold coins. * * TODO: allow picking up 2-N objects from a pile of N based on weight. - * Change from 'int' to 'long' to accomate big stacks of gold. + * Change from 'int' to 'long' to accommodate big stacks of gold. * Right now we fake it by reporting a partial quantity, but the * likesgold handling m_move results in picking up the whole stack. */ int -can_carry(mtmp, otmp) -struct monst *mtmp; -struct obj *otmp; +can_carry(struct monst *mtmp, struct obj *otmp) { int iquan, otyp = otmp->otyp, newload = otmp->owt; struct permonst *mdat = mtmp->data; @@ -1238,17 +1996,11 @@ struct obj *otmp; if (notake(mdat)) return 0; /* can't carry anything */ - if (otyp == CORPSE && touch_petrifies(&mons[otmp->corpsenm]) - && !(mtmp->misc_worn_check & W_ARMG) && !resists_ston(mtmp)) - return 0; - if (otyp == CORPSE && is_rider(&mons[otmp->corpsenm])) - return 0; - if (objects[otyp].oc_material == SILVER && mon_hates_silver(mtmp) - && (otyp != BELL_OF_OPENING || !is_covetous(mdat))) + if (!can_touch_safely(mtmp, otmp)) return 0; /* hostile monsters who like gold will pick up the whole stack; - tame mosnters with hands will pick up the partial stack */ + tame monsters with hands will pick up the partial stack */ iquan = (otmp->quan > (long) LARGEST_INT) ? 20000 + rn2(LARGEST_INT - 20000 + 1) : (int) otmp->quan; @@ -1300,17 +2052,99 @@ struct obj *otmp; return iquan; } +/* is in direct line with where 'mon' thinks hero is? */ +staticfn boolean +monlineu(struct monst *mon, int nx, int ny) +{ + return online2(nx, ny, mon->mux, mon->muy); +} + +/* return flags based on monster data, for mfndpos() */ +long +mon_allowflags(struct monst *mtmp) +{ + long allowflags = 0L; + boolean can_open = !(nohands(mtmp->data) || verysmall(mtmp->data)); + boolean can_unlock = ((can_open && monhaskey(mtmp, TRUE)) + || mtmp->iswiz || is_rider(mtmp->data)); + boolean doorbuster = is_giant(mtmp->data); + /* don't tunnel if on rogue level or if hostile and close enough + to prefer a weapon; same criteria as in m_move() */ + boolean can_tunnel = (tunnels(mtmp->data) && !Is_rogue_level(&u.uz)); + + if (can_tunnel && needspick(mtmp->data) + && ((!mtmp->mpeaceful || Conflict) + && dist2(mtmp->mx, mtmp->my, mtmp->mux, mtmp->muy) <= 8)) + can_tunnel = FALSE; + + if (mtmp->mtame) + allowflags |= ALLOW_M | ALLOW_TRAPS | ALLOW_SANCT | ALLOW_SSM; + else if (mtmp->mpeaceful) + allowflags |= ALLOW_SANCT | ALLOW_SSM; + else + allowflags |= ALLOW_U; + if (Conflict && !resist_conflict(mtmp)) + allowflags |= ALLOW_U; + if (mtmp->isshk) + allowflags |= ALLOW_SSM; + if (mtmp->ispriest) + allowflags |= ALLOW_SSM | ALLOW_SANCT; + if (passes_walls(mtmp->data)) + allowflags |= (ALLOW_ROCK | ALLOW_WALL); + if (throws_rocks(mtmp->data) || m_can_break_boulder(mtmp)) + allowflags |= ALLOW_ROCK; + if (can_tunnel) + allowflags |= ALLOW_DIG; + if (doorbuster) + allowflags |= BUSTDOOR; + if (can_open) + allowflags |= OPENDOOR; + if (can_unlock) + allowflags |= UNLOCKDOOR; + if (passes_bars(mtmp->data) + /* restrict engulfer or holder who might try to pass iron bars while + carrying hero; accept small subset for poly'd hero passes_bars() */ + && (mtmp != u.ustuck || (unsolid(gy.youmonst.data) + || verysmall(gy.youmonst.data)))) + allowflags |= ALLOW_BARS; +#if 0 /* can't do this here; leave it for mfndpos() */ + if (is_displacer(mtmp->data)) + allowflags |= ALLOW_MDISP; +#endif + if (is_minion(mtmp->data) || is_rider(mtmp->data)) + allowflags |= ALLOW_SANCT; + /* unicorn may not be able to avoid hero on a noteleport level */ + if (is_unicorn(mtmp->data) && !noteleport_level(mtmp)) + allowflags |= NOTONL; + if (is_human(mtmp->data) || mtmp->data == &mons[PM_MINOTAUR]) + allowflags |= ALLOW_SSM; + if ((is_undead(mtmp->data) && mtmp->data->mlet != S_GHOST) + || is_vampshifter(mtmp)) + allowflags |= NOGARLIC; + + return allowflags; +} + +/* return TRUE if monster is up in the air/on the ceiling */ +boolean +m_in_air(struct monst *mtmp) +{ + return (is_flyer(mtmp->data) + || is_floater(mtmp->data) + || (is_clinger(mtmp->data) + && has_ceiling(&u.uz) && mtmp->mundetected)); +} + /* return number of acceptable neighbour positions */ int -mfndpos(mon, poss, info, flag) -struct monst *mon; -coord *poss; /* coord poss[9] */ -long *info; /* long info[9] */ -long flag; +mfndpos( + struct monst *mon, + struct mfndposdata *data, + long flag) { struct permonst *mdat = mon->data; - register struct trap *ttmp; - xchar x, y, nx, ny; + struct trap *ttmp; + coordxy x, y, nx, ny; int cnt = 0; uchar ntyp; uchar nowtyp; @@ -1325,20 +2159,17 @@ long flag; y = mon->my; nowtyp = levl[x][y].typ; + (void)memset((genericptr_t) data, 0, sizeof(struct mfndposdata)); + nodiag = NODIAG(mdat - mons); wantpool = (mdat->mlet == S_EEL); - poolok = ((!Is_waterlevel(&u.uz) - && (is_flyer(mdat) || is_floater(mdat) || is_clinger(mdat))) + poolok = ((!Is_waterlevel(&u.uz) && m_in_air(mon)) || (is_swimmer(mdat) && !wantpool)); - /* note: floating eye is the only is_floater() so this could be - simplified, but then adding another floater would be error prone */ - lavaok = (is_flyer(mdat) || is_floater(mdat) || is_clinger(mdat) - || likes_lava(mdat)); + lavaok = (m_in_air(mon) || likes_lava(mdat)); if (mdat == &mons[PM_FLOATING_EYE]) /* prefers to avoid heat */ lavaok = FALSE; thrudoor = ((flag & (ALLOW_WALL | BUSTDOOR)) != 0L); - poisongas_ok = ((nonliving(mdat) || is_vampshifter(mon) - || breathless(mdat)) || resists_poison(mon)); + poisongas_ok = (m_poisongas_ok(mon) == M_POISONGAS_OK); in_poisongas = ((gas_reg = visible_region_at(x,y)) != 0 && gas_reg->glyph == gas_glyph); @@ -1378,10 +2209,18 @@ long flag; if (nx == x && ny == y) continue; ntyp = levl[nx][ny].typ; - if (IS_ROCK(ntyp) + if (IS_OBSTRUCTED(ntyp) && !((flag & ALLOW_WALL) && may_passwall(nx, ny)) && !((IS_TREE(ntyp) ? treeok : rockok) && may_dig(nx, ny))) continue; + /* intelligent peacefuls avoid digging shop/temple walls */ + if (IS_OBSTRUCTED(ntyp) && rockok + && !mindless(mon->data) && (mon->mpeaceful || mon->mtame) + && (*in_rooms(nx, ny, TEMPLE) || *in_rooms(nx, ny, SHOPBASE)) + && !(*in_rooms(x, y, TEMPLE) || *in_rooms(x, y, SHOPBASE))) + continue; + if (IS_WATERWALL(ntyp) && !is_swimmer(mdat)) + continue; /* KMH -- Added iron bars */ if (ntyp == IRONBARS && (!(flag & ALLOW_BARS) @@ -1389,7 +2228,10 @@ long flag; && (dmgtype(mdat, AD_RUST) || dmgtype(mdat, AD_CORR))))) continue; - if (IS_DOOR(ntyp) && !(amorphous(mdat) || can_fog(mon)) + if (IS_DOOR(ntyp) + /* an amorphous creature can only move under/through a + closed door if it doesn't currently have hero engulfed */ + && !((amorphous(mdat) || can_fog(mon)) && !engulfing_u(mon)) && (((levl[nx][ny].doormask & D_CLOSED) && !(flag & OPENDOOR)) || ((levl[nx][ny].doormask & D_LOCKED) && !(flag & UNLOCKDOOR))) && !thrudoor) @@ -1411,7 +2253,9 @@ long flag; || (m_at(x, ny) && m_at(nx, y) && worm_cross(x, y, nx, ny) && !m_at(nx, ny) && (nx != u.ux || ny != u.uy)))) continue; - if ((is_pool(nx, ny) == wantpool || poolok) + if ((!lavaok || !(flag & ALLOW_WALL)) && ntyp == LAVAWALL) + continue; + if ((poolok || is_pool(nx, ny) == wantpool) && (lavaok || !is_lava(nx, ny))) { int dispx, dispy; boolean monseeu = (mon->mcansee @@ -1421,7 +2265,8 @@ long flag; /* Displacement also displaces the Elbereth/scare monster, * as long as you are visible. */ - if (Displaced && monseeu && mon->mux == nx && mon->muy == ny) { + if (Displaced && monseeu + && mon->mux == nx && mon->muy == ny) { dispx = u.ux; dispy = u.uy; } else { @@ -1429,15 +2274,15 @@ long flag; dispy = ny; } - info[cnt] = 0; + data->info[cnt] = 0; if (onscary(dispx, dispy, mon)) { if (!(flag & ALLOW_SSM)) continue; - info[cnt] |= ALLOW_SSM; + data->info[cnt] |= ALLOW_SSM; } - if ((nx == u.ux && ny == u.uy) + if (u_at(nx, ny) || (nx == mon->mux && ny == mon->muy)) { - if (nx == u.ux && ny == u.uy) { + if (u_at(nx, ny)) { /* If it's right next to you, it found you, * displaced or no. We must set mux and muy * right now, so when we return we can tell @@ -1449,50 +2294,52 @@ long flag; } if (!(flag & ALLOW_U)) continue; - info[cnt] |= ALLOW_U; + data->info[cnt] |= ALLOW_U; } else { if (MON_AT(nx, ny)) { struct monst *mtmp2 = m_at(nx, ny); long mmflag = flag | mm_aggression(mon, mtmp2); if (mmflag & ALLOW_M) { - info[cnt] |= ALLOW_M; + data->info[cnt] |= ALLOW_M; if (mtmp2->mtame) { if (!(mmflag & ALLOW_TM)) continue; - info[cnt] |= ALLOW_TM; + data->info[cnt] |= ALLOW_TM; } } else { + flag &= ~ALLOW_MDISP; /* depends upon defender */ mmflag = flag | mm_displacement(mon, mtmp2); if (!(mmflag & ALLOW_MDISP)) continue; - info[cnt] |= ALLOW_MDISP; + data->info[cnt] |= ALLOW_MDISP; } } /* Note: ALLOW_SANCT only prevents movement, not attack, into a temple. */ - if (level.flags.has_temple && *in_rooms(nx, ny, TEMPLE) + if (svl.level.flags.has_temple + && *in_rooms(nx, ny, TEMPLE) && !*in_rooms(x, y, TEMPLE) && in_your_sanctuary((struct monst *) 0, nx, ny)) { if (!(flag & ALLOW_SANCT)) continue; - info[cnt] |= ALLOW_SANCT; + data->info[cnt] |= ALLOW_SANCT; } } if (checkobj && sobj_at(CLOVE_OF_GARLIC, nx, ny)) { if (flag & NOGARLIC) continue; - info[cnt] |= NOGARLIC; + data->info[cnt] |= NOGARLIC; } if (checkobj && sobj_at(BOULDER, nx, ny)) { if (!(flag & ALLOW_ROCK)) continue; - info[cnt] |= ALLOW_ROCK; + data->info[cnt] |= ALLOW_ROCK; } - if (monseeu && onlineu(nx, ny)) { + if (monseeu && monlineu(mon, nx, ny)) { if (flag & NOTONL) continue; - info[cnt] |= NOTONL; + data->info[cnt] |= NOTONL; } /* check for diagonal tight squeeze */ if (nx != x && ny != y && bad_rock(mdat, x, ny) @@ -1505,38 +2352,24 @@ long flag; */ if ((ttmp = t_at(nx, ny)) != 0) { if (ttmp->ttyp >= TRAPNUM || ttmp->ttyp == 0) { - impossible( - "A monster looked at a very strange trap of type %d.", + impossible("A monster looked at a very strange trap" + " of type %d.", ttmp->ttyp); continue; } - if ((ttmp->ttyp != RUST_TRAP - || mdat == &mons[PM_IRON_GOLEM]) - && ttmp->ttyp != STATUE_TRAP - && ttmp->ttyp != VIBRATING_SQUARE - && ((!is_pit(ttmp->ttyp) && !is_hole(ttmp->ttyp)) - || (!is_flyer(mdat) && !is_floater(mdat) - && !is_clinger(mdat)) || Sokoban) - && (ttmp->ttyp != SLP_GAS_TRAP || !resists_sleep(mon)) - && (ttmp->ttyp != BEAR_TRAP - || (mdat->msize > MZ_SMALL && !amorphous(mdat) - && !is_flyer(mdat) && !is_floater(mdat) - && !is_whirly(mdat) && !unsolid(mdat))) - && (ttmp->ttyp != FIRE_TRAP || !resists_fire(mon)) - && (ttmp->ttyp != SQKY_BOARD || !is_flyer(mdat)) - && (ttmp->ttyp != WEB - || (!amorphous(mdat) && !webmaker(mdat) - && !is_whirly(mdat) && !unsolid(mdat))) - && (ttmp->ttyp != ANTI_MAGIC || !resists_magm(mon))) { + /* fixed-destination teleport trap, was used by hero */ + if (fixed_tele_trap(ttmp) && hastrack(nx, ny)) + data->info[cnt] |= ALLOW_TRAPS; + else if (!m_harmless_trap(mon, ttmp)) { if (!(flag & ALLOW_TRAPS)) { - if (mon->mtrapseen & (1L << (ttmp->ttyp - 1))) + if (mon_knows_traps(mon, ttmp->ttyp)) continue; } - info[cnt] |= ALLOW_TRAPS; + data->info[cnt] |= ALLOW_TRAPS; } } - poss[cnt].x = nx; - poss[cnt].y = ny; + data->poss[cnt].x = nx; + data->poss[cnt].y = ny; cnt++; } } @@ -1544,43 +2377,89 @@ long flag; wantpool = FALSE; goto nexttry; } + data->cnt = cnt; return cnt; } +/* Part of mm_aggression that represents two-way aggression. To avoid + having to code each case twice, this function contains those cases that + ought to happen twice, and mm_aggression will call it twice. */ +staticfn long +mm_2way_aggression(struct monst *magr, struct monst *mdef) +{ + if (On_W_tower_level(&u.uz)) { + /* treat inside the Wizard's tower as if it were a separate level + from outside so when hero is inside Wizard's tower, both monsters + need to be too; when outside, the monsters need to be too */ + if (In_W_tower(u.ux, u.uy, &u.uz) + ? (!In_W_tower(magr->mx, magr->my, &u.uz) + || !In_W_tower(mdef->mx, mdef->my, &u.uz)) + : (In_W_tower(magr->mx, magr->my, &u.uz) + || In_W_tower(mdef->mx, mdef->my, &u.uz))) + return 0L; + } + /* liches/zombies vs things that can be zombified + + Note: avoid this on the Castle level, partly for balance reasons + (the monster-versus-monster fights clear out significant portions of + the Castle and make it easier than it should be), partly for flavor + reasons (monsters who attacked other monsters to zombify them would + have been counterattacked to death long before the hero arrived). + + Also don't include unique monsters in this, otherwise it leads to + them waking up early (e.g. because a zombie decided to attack the + Wizard of Yendor). */ + if (zombie_maker(magr) && zombie_form(mdef->data) != NON_PM) { + if (magr->mgenmklev && mdef->mgenmklev) + return 0L; + if (!Is_stronghold(&u.uz) + && !unique_corpstat(magr->data) && !unique_corpstat(mdef->data)) + return (ALLOW_M | ALLOW_TM); + } + return 0L; +} + /* Monster against monster special attacks; for the specified monster combinations, this allows one monster to attack another adjacent one - in the absence of Conflict. There is no provision for targetting + in the absence of Conflict. There is no provision for targeting other monsters; just hand to hand fighting when they happen to be next to each other. */ -STATIC_OVL long -mm_aggression(magr, mdef) -struct monst *magr, /* monster that is currently deciding where to move */ - *mdef; /* another monster which is next to it */ +staticfn long +mm_aggression( + struct monst *magr, /* monster that is currently deciding where to move */ + struct monst *mdef) /* another monster which is next to it */ { + int mndx = monsndx(magr->data); + + /* don't allow pets to fight each other */ + if (magr->mtame && mdef->mtame) + return 0L; + /* supposedly purple worms are attracted to shrieking because they like to eat shriekers, so attack the latter when feasible */ - if (magr->data == &mons[PM_PURPLE_WORM] + if ((mndx == PM_PURPLE_WORM || mndx == PM_BABY_PURPLE_WORM) && mdef->data == &mons[PM_SHRIEKER]) return ALLOW_M | ALLOW_TM; /* Various other combinations such as dog vs cat, cat vs rat, and elf vs orc have been suggested. For the time being we don't support those. */ - return 0L; + return (mm_2way_aggression(magr, mdef) | mm_2way_aggression(mdef, magr)); } /* Monster displacing another monster out of the way */ -STATIC_OVL long -mm_displacement(magr, mdef) -struct monst *magr, /* monster that is currently deciding where to move */ - *mdef; /* another monster which is next to it */ +staticfn long +mm_displacement( + struct monst *magr, /* monster that is currently deciding where to move */ + struct monst *mdef) /* another monster which is next to it */ { struct permonst *pa = magr->data, *pd = mdef->data; /* if attacker can't barge through, there's nothing to do; - or if defender can barge through too, don't let attacker - do so, otherwise they might just end up swapping places - again when defender gets its chance to move */ - if ((pa->mflags3 & M3_DISPLACES) != 0 && (pd->mflags3 & M3_DISPLACES) == 0 + or if defender can barge through too and has a level at least + as high as the attacker, don't let attacker do so, otherwise + they might just end up swapping places again when defender + gets its chance to move */ + if (is_displacer(pa) && (!is_displacer(pd) || magr->m_lev > mdef->m_lev) /* no displacing grid bugs diagonally */ && !(magr->mx != mdef->mx && magr->my != mdef->my && NODIAG(monsndx(pd))) @@ -1594,9 +2473,7 @@ struct monst *magr, /* monster that is currently deciding where to move */ /* Is the square close enough for the monster to move or attack into? */ boolean -monnear(mon, x, y) -struct monst *mon; -int x, y; +monnear(struct monst *mon, coordxy x, coordxy y) { int distance = dist2(mon->mx, mon->my, x, y); @@ -1607,14 +2484,14 @@ int x, y; /* really free dead monsters */ void -dmonsfree() +dmonsfree(void) { struct monst **mtmp, *freetmp; int count = 0; char buf[QBUFSZ]; buf[0] = '\0'; - for (mtmp = &fmon; *mtmp;) { + for (mtmp = &fmon; *mtmp; ) { freetmp = *mtmp; if (DEADMONSTER(freetmp) && !freetmp->isgd) { *mtmp = freetmp->nmon; @@ -1626,7 +2503,7 @@ dmonsfree() } if (count != iflags.purge_monsters) { - describe_level(buf); + describe_level(buf, 2); impossible("dmonsfree: %d removed doesn't match %d pending on %s", count, iflags.purge_monsters, buf); } @@ -1635,8 +2512,7 @@ dmonsfree() /* called when monster is moved to larger structure */ void -replmon(mtmp, mtmp2) -struct monst *mtmp, *mtmp2; +replmon(struct monst *mtmp, struct monst *mtmp2) { struct obj *otmp; @@ -1647,6 +2523,9 @@ struct monst *mtmp, *mtmp2; otmp->ocarry = mtmp2; } mtmp->minvent = 0; + /* before relmon(mtmp), because it could clear polearm.hitmon */ + if (svc.context.polearm.hitmon == mtmp) + svc.context.polearm.hitmon = mtmp2; /* remove the old monster from the map and from `fmon' list */ relmon(mtmp, (struct monst **) 0); @@ -1655,7 +2534,7 @@ struct monst *mtmp, *mtmp2; if (mtmp != u.usteed) /* don't place steed onto the map */ place_monster(mtmp2, mtmp2->mx, mtmp2->my); if (mtmp2->wormno) /* update level.monsters[wseg->wx][wseg->wy] */ - place_wsegs(mtmp2, NULL); /* locations to mtmp2 not mtmp. */ + place_wsegs(mtmp2, mtmp); /* locations to mtmp2 not mtmp. */ if (emits_light(mtmp2->data)) { /* since this is so rare, we don't have any `mon_move_light_source' */ new_light_source(mtmp2->mx, mtmp2->my, emits_light(mtmp2->data), @@ -1666,7 +2545,7 @@ struct monst *mtmp, *mtmp2; mtmp2->nmon = fmon; fmon = mtmp2; if (u.ustuck == mtmp) - u.ustuck = mtmp2; + set_ustuck(mtmp2); if (u.usteed == mtmp) u.usteed = mtmp2; if (mtmp2->isshk) @@ -1679,52 +2558,33 @@ struct monst *mtmp, *mtmp2; /* release mon from the display and the map's monster list, maybe transfer it to one of the other monster lists */ void -relmon(mon, monst_list) -struct monst *mon; -struct monst **monst_list; /* &migrating_mons or &mydogs or null */ +relmon( + struct monst *mon, + struct monst **monst_list) /* &gm.migrating_mons or &gm.mydogs or null */ { - struct monst *mtmp; - int mx = mon->mx, my = mon->my; - boolean on_map = (m_at(mx, my) == mon), - unhide = (monst_list != 0); - if (!fmon) panic("relmon: no fmon available."); - if (unhide) { - /* can't remain hidden across level changes (exception: wizard - clone can continue imitating some other monster form); also, - might be imitating a boulder so need line-of-sight unblocking */ - mon->mundetected = 0; - if (M_AP_TYPE(mon) && M_AP_TYPE(mon) != M_AP_MONSTER) - seemimic(mon); - } - - if (on_map) { - mon->mtrapped = 0; - if (mon->wormno) - remove_worm(mon); - else - remove_monster(mx, my); - } + /* take 'mon' off the map */ + mon_leaving_level(mon); + /* remove 'mon' from the 'fmon' list */ if (mon == fmon) { fmon = fmon->nmon; } else { + struct monst *mtmp; + for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) - if (mtmp->nmon == mon) + if (mtmp->nmon == mon) { + mtmp->nmon = mon->nmon; break; - - if (mtmp) - mtmp->nmon = mon->nmon; - else + } + if (!mtmp) panic("relmon: mon not in list."); } - if (unhide) { - if (on_map) - newsym(mx, my); - /* insert into mydogs or migrating_mons */ + if (monst_list) { + /* insert into gm.mydogs or gm.migrating_mons */ mon->nmon = *monst_list; *monst_list = mon; } else { @@ -1734,67 +2594,78 @@ struct monst **monst_list; /* &migrating_mons or &mydogs or null */ } void -copy_mextra(mtmp2, mtmp1) -struct monst *mtmp2, *mtmp1; +copy_mextra(struct monst *mtmp2, struct monst *mtmp1) { if (!mtmp2 || !mtmp1 || !mtmp1->mextra) return; if (!mtmp2->mextra) mtmp2->mextra = newmextra(); - if (MNAME(mtmp1)) { - new_mname(mtmp2, (int) strlen(MNAME(mtmp1)) + 1); - Strcpy(MNAME(mtmp2), MNAME(mtmp1)); + if (MGIVENNAME(mtmp1)) { + new_mgivenname(mtmp2, (int) strlen(MGIVENNAME(mtmp1)) + 1); + Strcpy(MGIVENNAME(mtmp2), MGIVENNAME(mtmp1)); } if (EGD(mtmp1)) { if (!EGD(mtmp2)) newegd(mtmp2); + assert(has_egd(mtmp2)); *EGD(mtmp2) = *EGD(mtmp1); } if (EPRI(mtmp1)) { if (!EPRI(mtmp2)) newepri(mtmp2); + assert(has_epri(mtmp2)); *EPRI(mtmp2) = *EPRI(mtmp1); } if (ESHK(mtmp1)) { if (!ESHK(mtmp2)) neweshk(mtmp2); + assert(has_eshk(mtmp2)); *ESHK(mtmp2) = *ESHK(mtmp1); } if (EMIN(mtmp1)) { if (!EMIN(mtmp2)) newemin(mtmp2); + assert(has_emin(mtmp2)); *EMIN(mtmp2) = *EMIN(mtmp1); } if (EDOG(mtmp1)) { if (!EDOG(mtmp2)) newedog(mtmp2); + assert(has_edog(mtmp2)); *EDOG(mtmp2) = *EDOG(mtmp1); } + if (EBONES(mtmp1)) { + if (!EBONES(mtmp2)) + newebones(mtmp2); + assert(has_ebones(mtmp2)); + *EBONES(mtmp2) = *EBONES(mtmp1); + } if (has_mcorpsenm(mtmp1)) MCORPSENM(mtmp2) = MCORPSENM(mtmp1); } void -dealloc_mextra(m) -struct monst *m; +dealloc_mextra(struct monst *m) { struct mextra *x = m->mextra; if (x) { - if (x->mname) - free((genericptr_t) x->mname); + if (x->mgivenname) + free((genericptr_t) x->mgivenname), x->mgivenname = 0; if (x->egd) - free((genericptr_t) x->egd); + free((genericptr_t) x->egd), x->egd = 0; if (x->epri) - free((genericptr_t) x->epri); + free((genericptr_t) x->epri), x->epri = 0; if (x->eshk) - free((genericptr_t) x->eshk); + free((genericptr_t) x->eshk), x->eshk = 0; if (x->emin) - free((genericptr_t) x->emin); + free((genericptr_t) x->emin), x->emin = 0; if (x->edog) - free((genericptr_t) x->edog); - /* [no action needed for x->mcorpsenm] */ + free((genericptr_t) x->edog), x->edog = 0; + if (x->ebones) + free((genericptr_t) x->ebones), x->ebones = 0; + x->mcorpsenm = NON_PM; /* no allocation to release */ free((genericptr_t) x); m->mextra = (struct mextra *) 0; @@ -1802,53 +2673,114 @@ struct monst *m; } void -dealloc_monst(mon) -struct monst *mon; +dealloc_monst(struct monst *mon) { char buf[QBUFSZ]; - buf[0] = '\0'; - if (mon->nmon) { - describe_level(buf); - panic("dealloc_monst with nmon on %s", buf); + buf[0] = '\0'; + if (mon->nmon) { + describe_level(buf, 2); + panic("dealloc_monst with nmon on %s", buf); + } + if (mon->mextra) + dealloc_mextra(mon); + /* clear out of date information contained in the about-to-become + stale memory; see dealloc_obj() */ + *mon = cg.zeromonst; + free((genericptr_t) mon); +} + +/* 'mon' is being removed from level due to migration [relmon from keepdogs + or migrate_to_level] or due to death [m_detach from mondead or mongone] */ +staticfn void +mon_leaving_level(struct monst *mon) +{ + coordxy mx = mon->mx, my = mon->my; + boolean onmap = (isok(mx, my) && svl.level.monsters[mx][my] == mon); + + /* to prevent an infinite relobj-flooreffects-hmon-killed loop */ + mon->mtrapped = 0; + unstuck(mon); /* mon is not swallowing or holding you nor held by you */ + + /* vault guard might be at <0,0> */ + if (onmap || mon == svl.level.monsters[0][0]) { + if (mon->wormno) + remove_worm(mon); + else + remove_monster(mx, my); + +#if 0 /* mustn't do this; too many places assume that the stale + * monst->mx,my values are still valid */ + mon->mx = mon->my = 0; /* off normal map */ +#endif } - if (mon->mextra) - dealloc_mextra(mon); - free((genericptr_t) mon); + if (onmap) { + mon->mundetected = 0; /* for migration; doesn't matter for death */ + /* unhide mimic in case its shape has been blocking line of sight + or it is accompanying the hero to another level */ + if (M_AP_TYPE(mon) != M_AP_NOTHING && M_AP_TYPE(mon) != M_AP_MONSTER) + seemimic(mon); + /* if mon is pinned by a boulder, removing mon lets boulder drop */ + fill_pit(mx, my); + newsym(mx, my); + } + /* if mon is a remembered target, forget it since it isn't here anymore */ + if (mon == svc.context.polearm.hitmon) + svc.context.polearm.hitmon = (struct monst *) 0; } -/* remove effects of mtmp from other data structures */ -STATIC_OVL void -m_detach(mtmp, mptr) -struct monst *mtmp; -struct permonst *mptr; /* reflects mtmp->data _prior_ to mtmp's death */ +/* 'mtmp' is going away; remove effects of mtmp from other data structures */ +staticfn void +m_detach( + struct monst *mtmp, + struct permonst *mptr, /* reflects mtmp->data _prior_ to mtmp's death */ + boolean due_to_death) { - boolean onmap = (mtmp->mx > 0); + coordxy mx = mtmp->mx, my = mtmp->my; - if (mtmp == context.polearm.hitmon) - context.polearm.hitmon = 0; if (mtmp->mleashed) m_unleash(mtmp, FALSE); - /* to prevent an infinite relobj-flooreffects-hmon-killed loop */ - mtmp->mtrapped = 0; + + if (mx > 0 && emits_light(mptr)) + del_light_source(LS_MONSTER, monst_to_any(mtmp)); + + /* + * Take mtmp off map but not out of fmon list yet (dmonsfree does that). + * + * Sequencing issue: mtmp's inventory should be dropped before taking + * it off the map but if that includes a boulder and mtmp is at a pit + * location, dropping minvent ought to be deferred until its corpse + * gets placed. We compromise and just make sure mtmp is off the map + * before dropping its former belongings. + */ + mon_leaving_level(mtmp); + mtmp->mhp = 0; /* simplify some tests: force mhp to 0 */ - relobj(mtmp, 0, FALSE); - if (onmap || mtmp == level.monsters[0][0]) { - if (mtmp->wormno) - remove_worm(mtmp); - else - remove_monster(mtmp->mx, mtmp->my); + /* death handling for the Wizard needs to take place even if he is + leaving the dungeon alive rather than dying */ + if (mtmp->iswiz) + wizdeadorgone(); + /* foodead() might give quest feedback for foo having died; skip that + if we're called for mongone() rather than mondead(); saving bones + or wizard mode genocide of "*" can result in special monsters going + away without having been killed */ + if (due_to_death) { + if (mtmp->data->msound == MS_NEMESIS) { + nemdead(); + /* The Archeologist, Caveman, and Priest quest texts describe + the nemesis's body creating noxious fumes/gas when killed. */ + if (stinky_nemesis(mtmp)) + nemesis_stinks(mx, my); + } + if (mtmp->data->msound == MS_LEADER) + leaddead(); + /* release (drop onto map) all objects carried by mtmp; assumes that + mtmp->mx,my contains the appropriate location */ + relobj(mtmp, 1, FALSE); /* drop mtmp->minvent, issue newsym(mx,my) */ } - if (emits_light(mptr)) - del_light_source(LS_MONSTER, monst_to_any(mtmp)); - if (M_AP_TYPE(mtmp)) - seemimic(mtmp); - if (onmap) - newsym(mtmp->mx, mtmp->my); - unstuck(mtmp); - if (onmap) - fill_pit(mtmp->mx, mtmp->my); + if (mtmp->m_id == gs.stealmid) + thiefdead(); /* reset theft-in-progress data */ if (mtmp->isshk) shkgone(mtmp); if (mtmp->wormno) @@ -1856,14 +2788,43 @@ struct permonst *mptr; /* reflects mtmp->data _prior_ to mtmp's death */ if (In_endgame(&u.uz)) mtmp->mstate |= MON_ENDGAME_FREE; - mtmp->mstate |= MON_DETACH; - iflags.purge_monsters++; + if ((mtmp->mstate & MON_DETACH) != 0) { + impossible("m_detach: %s is already detached?", + minimal_monnam(mtmp, FALSE)); + } else { + mtmp->mstate |= MON_DETACH; + iflags.purge_monsters++; + } + + /* hero is thrown from his steed when it dies or gets genocided */ + if (mtmp == u.usteed) + dismount_steed(DISMOUNT_GENERIC); + return; +} + +/* give a life-saved monster a reasonable mhpmax value in case it has + been the victim of excessive life draining */ +staticfn void +set_mon_min_mhpmax( + struct monst *mon, + int minimum_mhpmax) /* monster life-saving has traditionally used 10 */ +{ + /* can't be less than m_lev+1 (if we just used m_lev itself, level 0 + monsters would end up allowing a minimum of 0); since life draining + reduces m_lev, this usually won't give the monster much of a boost */ + if (mon->mhpmax < (int) mon->m_lev + 1) + mon->mhpmax = (int) mon->m_lev + 1; + /* caller can specify an alternate minimum; we'll honor it iff it is + greater than m_lev+1; the traditional arbitrary value of 10 always + gives level 0 and level 1 monsters a boost and has a moderate + chance of doing so for level 2, a tiny chance for levels 3..9 */ + if (mon->mhpmax < minimum_mhpmax) + mon->mhpmax = minimum_mhpmax; } /* find the worn amulet of life saving which will save a monster */ struct obj * -mlifesaver(mon) -struct monst *mon; +mlifesaver(struct monst *mon) { if (!nonliving(mon->data) || is_vampshifter(mon)) { struct obj *otmp = which_armor(mon, W_AMUL); @@ -1874,9 +2835,8 @@ struct monst *mon; return (struct obj *) 0; } -STATIC_OVL void -lifesaved_monster(mtmp) -struct monst *mtmp; +staticfn void +lifesaved_monster(struct monst *mtmp) { boolean surviver; struct obj *lifesave = mlifesaver(mtmp); @@ -1902,16 +2862,15 @@ struct monst *mtmp; } m_useup(mtmp, lifesave); /* equip replacement amulet, if any, on next move */ - mtmp->misc_worn_check |= I_SPECIAL; + check_gear_next_turn(mtmp); - surviver = !(mvitals[monsndx(mtmp->data)].mvflags & G_GENOD); + surviver = !(svm.mvitals[monsndx(mtmp->data)].mvflags & G_GENOD); mtmp->mcanmove = 1; mtmp->mfrozen = 0; if (mtmp->mtame && !mtmp->isminion) { wary_dog(mtmp, !surviver); } - if (mtmp->mhpmax <= 0) - mtmp->mhpmax = 10; + set_mon_min_mhpmax(mtmp, 10); /* mtmp->mhpmax=max(mtmp->m_lev+1,10) */ mtmp->mhp = mtmp->mhpmax; if (!surviver) { @@ -1924,82 +2883,225 @@ struct monst *mtmp; } } -void -mondead(mtmp) -register struct monst *mtmp; +/* when a shape-shifted vampire is killed, it reverts to base form instead + of dying; returns True if mtmp successfully revives, False otherwise; + "successfully revived" vampire might be killed by a booby trapped door */ +staticfn boolean +vamprises(struct monst *mtmp) { - struct permonst *mptr; - int tmp; - - mtmp->mhp = 0; /* in case caller hasn't done this */ - lifesaved_monster(mtmp); - if (!DEADMONSTER(mtmp)) - return; - - if (is_vampshifter(mtmp)) { - int mndx = mtmp->cham; - int x = mtmp->mx, y = mtmp->my; + int mndx = mtmp->cham; - /* this only happens if shapeshifted */ - if (mndx >= LOW_PM && mndx != monsndx(mtmp->data) - && !(mvitals[mndx].mvflags & G_GENOD)) { - char buf[BUFSZ]; - boolean in_door = (amorphous(mtmp->data) - && closed_door(mtmp->mx, mtmp->my)), - /* alternate message phrasing for some monster types */ - spec_mon = (nonliving(mtmp->data) + /* + * Protection from shape changers protects against this because + * the vampire will always be in normal form instead of shifted. + * So there's no need to check for that attribute being active. + */ + if (ismnum(mndx) && mndx != monsndx(mtmp->data) + && !(svm.mvitals[mndx].mvflags & G_GENOD)) { + char action[BUFSZ]; + /* alternate message phrasing for some monster types */ + boolean spec_mon = (nonliving(mtmp->data) || noncorporeal(mtmp->data) || amorphous(mtmp->data)), - spec_death = (disintegested /* disintegrated or digested */ + spec_death = (gd.disintegested /* disintegrated/digested */ || noncorporeal(mtmp->data) || amorphous(mtmp->data)); + coordxy x = mtmp->mx, y = mtmp->my; + + /* construct a 'before' argument to pass to pline(); this used + to construct a dynamic format string but that's overkill */ + Snprintf(action, sizeof action, "%s%s %s%s and rises as", + Unaware ? "you dream that " : "", + x_monnam(mtmp, ARTICLE_THE, + spec_mon ? (char *) 0 : "seemingly dead", + (SUPPRESS_INVISIBLE | AUGMENT_IT), FALSE), + Unaware ? "" : "suddenly ", + spec_death ? "reconstitutes" : "transforms"); + mtmp->mcanmove = 1; + mtmp->mfrozen = 0; + set_mon_min_mhpmax(mtmp, 10); /* mtmp->mhpmax=max(m_lev+1,10) */ + mtmp->mhp = mtmp->mhpmax; + /* mtmp==u.ustuck can happen if previously a fog cloud or if + poly'd hero is hugging a vampire bat */ + if (mtmp == u.ustuck) { + if (u.uswallow) + expels(mtmp, mtmp->data, FALSE); + else + uunstick(); + } - /* construct a format string before transformation; - will be capitalized when used, expects one %s arg */ - Sprintf(buf, "%s suddenly %s and rises as %%s!", - x_monnam(mtmp, ARTICLE_THE, - spec_mon ? (char *) 0 : "seemingly dead", - (SUPPRESS_INVISIBLE | SUPPRESS_IT), FALSE), - spec_death ? "reconstitutes" : "transforms"); - mtmp->mcanmove = 1; - mtmp->mfrozen = 0; - if (mtmp->mhpmax <= 0) - mtmp->mhpmax = 10; - mtmp->mhp = mtmp->mhpmax; - /* mtmp==u.ustuck can happen if previously a fog cloud - or poly'd hero is hugging a vampire bat */ - if (mtmp == u.ustuck) { - if (u.uswallow) - expels(mtmp, mtmp->data, FALSE); - else - uunstick(); - } - if (in_door) { - coord new_xy; + if (!newcham(mtmp, &mons[mndx], NO_NC_FLAGS)) + return !DEADMONSTER(mtmp); + mtmp->cham = (mtmp->data == &mons[mndx]) ? NON_PM : mndx; - if (enexto(&new_xy, mtmp->mx, mtmp->my, &mons[mndx])) { - rloc_to(mtmp, new_xy.x, new_xy.y); - } - } - newcham(mtmp, &mons[mndx], FALSE, FALSE); - if (mtmp->data == &mons[mndx]) - mtmp->cham = NON_PM; - else - mtmp->cham = mndx; - if (canspotmon(mtmp)) { - /* 3.6.0 used a_monnam(mtmp); that was weird if mtmp was - named: "Dracula suddenly transforms and rises as Dracula"; - 3.6.1 used mtmp->data->mname; that ignored hallucination */ - pline(upstart(buf), + if (canspotmon(mtmp)) { + /* 3.6.0 used a_monnam(mtmp); that was weird if mtmp was + named: "Dracula suddenly transforms and rises as Dracula"; + 3.6.1 used mtmp->data->mname; that ignored hallucination */ + pline_mon(mtmp, "%s %s!", upstart(action), x_monnam(mtmp, ARTICLE_A, (char *) 0, - (SUPPRESS_NAME | SUPPRESS_IT - | SUPPRESS_INVISIBLE), FALSE)); - vamp_rise_msg = TRUE; + (SUPPRESS_NAME | SUPPRESS_IT | SUPPRESS_INVISIBLE), + FALSE)); + gv.vamp_rise_msg = TRUE; + } + /* revived vampire is in normal shape, so can't be amorphous; if on + a closed door spot, destroy the door and if trapped, blow it up */ + if (closed_door(x, y)) { + static const char + door_smashed[] = "a door being smashed", + door_go_boom[] = "a door exploding"; + struct rm *door = &levl[x][y]; + boolean trapped = (door->doormask & D_TRAPPED) != 0, + seeit = cansee(x, y); + + set_msg_xy(x, y); /* You()/pline() will reset this */ + if (!seeit) + You_hear("%s.", trapped ? "an explosion" : door_smashed); + else if (!canspotmon(mtmp)) + You_see("%s.", trapped ? door_go_boom : door_smashed); + else if (!Unaware) + pline_The("door is smashed%s", + trapped ? " and it explodes!" : "."); + set_msg_xy(0, 0); /* in case none of the messages was delivered */ + + door->doormask = D_NODOOR; + recalc_block_point(x, y); + if (trapped) { + boolean trap_killed, save_verbose = flags.verbose; + + flags.verbose = FALSE; /* suppress mb_trapped() messages + * (that makes the 'seeit' arg moot) */ + trap_killed = mb_trapped(mtmp, seeit); + flags.verbose = save_verbose; + /* if the booby trap has killed the monster, mondied() will + have been called but no message about its death given yet; + mtmp was a vampire so use unconditional "destroyed" */ + if (trap_killed && canspotmon(mtmp) && !Unaware) + pline_mon(mtmp, "%s is destroyed!", Monnam(mtmp)); } - newsym(x, y); - return; + } + newsym(x, y); + return TRUE; + } + return FALSE; +} + +/* specific combination of x_monnam flags for livelogging; show what was + actually killed even when unseen or hallucinated to be something else */ +#define livelog_mon_nam(mtmp) \ + x_monnam(mtmp, ARTICLE_THE, (char *) 0, EXACT_NAME, FALSE) + +/* when a mon has died, maybe record an achievement or issue livelog message; + moved into separate routine to unclutter mondead() */ +staticfn void +logdeadmon(struct monst *mtmp, int mndx) +{ + int howmany = svm.mvitals[mndx].died; + + if (mndx == PM_MEDUSA && howmany == 1) { + record_achievement(ACH_MEDU); /* also generates a livelog event */ + } else if ((unique_corpstat(mtmp->data) + && (mndx != PM_HIGH_CLERIC || !mtmp->mrevived)) + || (mtmp->isshk && !mtmp->mrevived)) { + char shkdetail[QBUFSZ]; + const char *mkilled; + boolean herodidit = !svc.context.mon_moving; + + /* + * livelog event; unique_corpstat() includes the Wizard and + * any High Priest even though they aren't actually unique. + * + * Shopkeeper kills are logged, but only the first time per + * shopkeeper, since their shared kill counter wouldn't work + * for this purpose (and it wouldn't account for polymorphed + * shopkeepers either). + */ + shkdetail[0] = '\0'; + if (mtmp->isshk) { + howmany = 1; + /* ", the proprietor" needs a trailing comma for + the alternate phrasing ", shkdetails, has been killed" + when hero isn't directly responsible */ + Snprintf(shkdetail, sizeof shkdetail, ", the %s %s%s", + shtypes[ESHK(mtmp)->shoptype - SHOPBASE].name, + /* in case shk name doesn't include Mr or Ms honorific */ + mtmp->female ? "proprietrix" : "proprietor", + herodidit ? "" : ","); + } else if (mndx == PM_HIGH_CLERIC) { + /* the high priest[ess] monster is not unique; we know that + this is the first death for this particular high priest + (because of the !mtmp->mrevived test above) */ + howmany = 1; + } + + /* killing a unique more than once doesn't get logged every time; + the Wizard and the Riders can be killed more than once + "naturally", others require deliberate player action such as + use of undead turning to revive a corpse or petrification plus + stone-to-flesh to create and revive a statue */ + if (howmany <= 3 || howmany == 5 || howmany == 10 || howmany == 25 + || (howmany % 50) == 0) { /* 50, 100, 150, 200, 250 */ + char xtra[40]; /* space for " (Nth time)" when N > 1 */ + long llevent_type = LL_UMONST; + + /* the first kill of any unique monster is a major event; + all kills of the Wizard and the Riders are major when + they're logged but they still don't get logged every time */ + if (howmany == 1 || mtmp->iswiz || is_rider(mtmp->data)) + llevent_type |= LL_ACHIEVE; + xtra[0] = '\0'; + if (howmany > 1) /* "(2nd time)" or "(50th time)" */ + Sprintf(xtra, " (%d%s time)", howmany, ordin(howmany)); + + mkilled = nonliving(mtmp->data) ? "destroyed" : "killed"; + /* hero is responsible: "killed " */ + if (herodidit) + livelog_printf(llevent_type, "%s %s%s%s", + mkilled, + livelog_mon_nam(mtmp), shkdetail, xtra); + else /* trap, pet, conflict: " has been killed" */ + livelog_printf(llevent_type, "%s%s has been %s%s", + livelog_mon_nam(mtmp), shkdetail, + mkilled, xtra); } } +} + +/* anger all the quest guards on the level */ +staticfn void +anger_quest_guardians(struct monst *mtmp) +{ + if (mtmp->data == &mons[gu.urole.guardnum]) + setmangry(mtmp, TRUE); +} + +/* monster 'mtmp' has died; maybe life-save, otherwise unshapeshift and + update vanquished stats and update map */ +void +mondead(struct monst *mtmp) +{ + struct permonst *mptr; + boolean be_sad; + int mndx; + + /* potential pet message; always clear global flag */ + be_sad = iflags.sad_feeling; + iflags.sad_feeling = FALSE; + + mtmp->mhp = 0; /* in case caller hasn't done this */ + lifesaved_monster(mtmp); + if (!DEADMONSTER(mtmp)) + return; + + /* vampire in bat/fog/wolf form reverts to vampire instead of dying */ + if (is_vampshifter(mtmp) && vamprises(mtmp)) + return; + + if (be_sad) + You("have a sad feeling for a moment, then it passes."); + + if (mtmp->data == &mons[PM_STEAM_VORTEX]) + create_gas_cloud(mtmp->mx, mtmp->my, rn2(10) + 5, 0); /* harmless */ /* dead vault guard is actually kept at coordinate <0,0> until his temporary corridor to/from the vault has been removed; @@ -2007,13 +3109,9 @@ register struct monst *mtmp; if (mtmp->isgd && !grddead(mtmp)) return; - /* Player is thrown from his steed when it dies */ - if (mtmp == u.usteed) - dismount_steed(DISMOUNT_GENERIC); - mptr = mtmp->data; /* save this for m_detach() */ /* restore chameleon, lycanthropes to true form at death */ - if (mtmp->cham >= LOW_PM) { + if (ismnum(mtmp->cham)) { set_mon_data(mtmp, &mons[mtmp->cham]); mtmp->cham = NON_PM; } else if (mtmp->data == &mons[PM_WEREJACKAL]) @@ -2024,7 +3122,7 @@ register struct monst *mtmp; set_mon_data(mtmp, &mons[PM_HUMAN_WERERAT]); /* - * mvitals[].died does double duty as total number of dead monsters + * svm.mvitals[].died does double duty as total number of dead monsters * and as experience factor for the player killing more monsters. * this means that a dragon dying by other means reduces the * experience the player gets for killing a dragon directly; this @@ -2033,25 +3131,31 @@ register struct monst *mtmp; * based on only player kills probably opens more avenues of abuse * for rings of conflict and such. */ - tmp = monsndx(mtmp->data); - if (mvitals[tmp].died < 255) - mvitals[tmp].died++; + mndx = monsndx(mtmp->data); + if (svm.mvitals[mndx].died < 255) + svm.mvitals[mndx].died++; /* if it's a (possibly polymorphed) quest leader, mark him as dead */ - if (mtmp->m_id == quest_status.leader_m_id) - quest_status.leader_is_dead = TRUE; -#ifdef MAIL + if (mtmp->m_id == svq.quest_status.leader_m_id) + svq.quest_status.leader_is_dead = TRUE; +#ifdef MAIL_STRUCTURES /* if the mail daemon dies, no more mail delivery. -3. */ - if (tmp == PM_MAIL_DAEMON) - mvitals[tmp].mvflags |= G_GENOD; + if (mndx == PM_MAIL_DAEMON) + svm.mvitals[mndx].mvflags |= G_GENOD; #endif if (mtmp->data->mlet == S_KOP) { + stairway *stway = stairway_find_type_dir(FALSE, FALSE); + /* Dead Kops may come back. */ switch (rnd(5)) { case 1: /* returns near the stairs */ - (void) makemon(mtmp->data, xdnstair, ydnstair, NO_MM_FLAGS); - break; + if (stway) { + (void) makemon(mtmp->data, stway->sx, stway->sy, NO_MM_FLAGS); + break; + } + FALLTHROUGH; + /* FALLTHRU */ case 2: /* randomly */ (void) makemon(mtmp->data, 0, 0, NO_MM_FLAGS); break; @@ -2059,30 +3163,36 @@ register struct monst *mtmp; break; } } - if (mtmp->iswiz) - wizdead(); - if (mtmp->data->msound == MS_NEMESIS) - nemdead(); - if (mtmp->data == &mons[PM_MEDUSA]) - u.uachieve.killed_medusa = 1; + + /* achievement and/or livelog */ + logdeadmon(mtmp, mndx); + if (glyph_is_invisible(levl[mtmp->mx][mtmp->my].glyph)) unmap_object(mtmp->mx, mtmp->my); - m_detach(mtmp, mptr); + + /* remove 'mtmp' from play; it will stay on the fmon list until end of + current move, then dmonsfree() will get rid of it */ + m_detach(mtmp, mptr, TRUE); + return; } /* TRUE if corpse might be dropped, magr may die if mon was swallowed */ boolean -corpse_chance(mon, magr, was_swallowed) -struct monst *mon; -struct monst *magr; /* killer, if swallowed */ -boolean was_swallowed; /* digestion */ +corpse_chance( + struct monst *mon, + struct monst *magr, /* killer, if swallowed */ + boolean was_swallowed) /* digestion */ { struct permonst *mdat = mon->data; int i, tmp; + if (!magr && gm.mswallower && attacktype(gm.mswallower->data, AT_ENGL)) + magr = gm.mswallower, was_swallowed = TRUE; /* for gas spore boom */ + if (mdat == &mons[PM_VLAD_THE_IMPALER] || mdat->mlet == S_LICH) { if (cansee(mon->mx, mon->my) && !was_swallowed) - pline("%s body crumbles into dust.", s_suffix(Monnam(mon))); + pline_mon(mon, "%s body crumbles into dust.", + s_suffix(Monnam(mon))); return FALSE; } @@ -2095,12 +3205,16 @@ boolean was_swallowed; /* digestion */ tmp = d((int) mdat->mlevel + 1, (int) mdat->mattk[i].damd); else tmp = 0; + if (was_swallowed && magr) { - if (magr == &youmonst) { + /* mdef is a gas spore (AT_BOOM) that is exploding inside an + engulfer; suppress usual explosion since it's contained */ + if (magr == &gy.youmonst) { There("is an explosion in your %s!", body_part(STOMACH)); - Sprintf(killer.name, "%s explosion", - s_suffix(mdat->mname)); - losehp(Maybe_Half_Phys(tmp), killer.name, KILLED_BY_AN); + Sprintf(svk.killer.name, "%s explosion", + s_suffix(pmname(mdat, Mgender(mon)))); + losehp(Maybe_Half_Phys(tmp), svk.killer.name, + KILLED_BY_AN); } else { You_hear("an explosion."); magr->mhp -= tmp; @@ -2108,19 +3222,15 @@ boolean was_swallowed; /* digestion */ mondied(magr); if (DEADMONSTER(magr)) { /* maybe lifesaved */ if (canspotmon(magr)) - pline("%s rips open!", Monnam(magr)); + pline_mon(magr, "%s rips open!", Monnam(magr)); } else if (canseemon(magr)) - pline("%s seems to have indigestion.", Monnam(magr)); + pline_mon(magr, "%s seems to have indigestion.", + Monnam(magr)); } - return FALSE; } - Sprintf(killer.name, "%s explosion", s_suffix(mdat->mname)); - killer.format = KILLED_BY_AN; - explode(mon->mx, mon->my, -1, tmp, MON_EXPLODE, EXPL_NOXIOUS); - killer.name[0] = '\0'; - killer.format = 0; + mon_explodes(mon, &mdat->mattk[i]); return FALSE; } } @@ -2140,13 +3250,13 @@ boolean was_swallowed; /* digestion */ /* drop (perhaps) a cadaver and remove monster */ void -mondied(mdef) -register struct monst *mdef; +mondied(struct monst *mdef) { mondead(mdef); if (!DEADMONSTER(mdef)) return; /* lifesaved */ + /* this assumes that the dead monster's map coordinates remain accurate */ if (corpse_chance(mdef, (struct monst *) 0, FALSE) && (accessible(mdef->mx, mdef->my) || is_pool(mdef->mx, mdef->my))) (void) make_corpse(mdef, CORPSTAT_NONE); @@ -2154,8 +3264,7 @@ register struct monst *mdef; /* monster disappears, not dies */ void -mongone(mdef) -struct monst *mdef; +mongone(struct monst *mdef) { mdef->mhp = 0; /* can skip some inventory bookkeeping */ @@ -2163,26 +3272,22 @@ struct monst *mdef; his temporary corridor to/from the vault has been removed */ if (mdef->isgd && !grddead(mdef)) return; - /* hero is thrown from his steed when it disappears */ - if (mdef == u.usteed) - dismount_steed(DISMOUNT_GENERIC); /* stuck to you? release */ unstuck(mdef); /* drop special items like the Amulet so that a dismissed Kop or nurse can't remove them from the game */ mdrop_special_objs(mdef); /* release rest of monster's inventory--it is removed from game */ - discard_minvent(mdef); - m_detach(mdef, mdef->data); + discard_minvent(mdef, FALSE); + m_detach(mdef, mdef->data, FALSE); } /* drop a statue or rock and remove monster */ void -monstone(mdef) -struct monst *mdef; +monstone(struct monst *mdef) { struct obj *otmp, *obj, *oldminvent; - xchar x = mdef->mx, y = mdef->my; + coordxy x = mdef->mx, y = mdef->my; boolean wasinside = FALSE; /* vampshifter reverts to vampire; @@ -2203,16 +3308,12 @@ struct monst *mdef; if ((int) mdef->data->msize > MZ_TINY || !rn2(2 + ((int) (mdef->data->geno & G_FREQ) > 2))) { + unsigned corpstatflags = CORPSTAT_NONE; + oldminvent = 0; /* some objects may end up outside the statue */ while ((obj = mdef->minvent) != 0) { - obj_extract_self(obj); - if (obj->owornmask) - update_mon_intrinsics(mdef, obj, FALSE, TRUE); - obj_no_longer_held(obj); - if (obj->owornmask & W_WEP) - setmnotwielded(mdef, obj); - obj->owornmask = 0L; + extract_from_minvent(mdef, obj, TRUE, TRUE); if (obj->otyp == BOULDER #if 0 /* monsters don't carry statues */ || (obj->otyp == STATUE @@ -2233,17 +3334,21 @@ struct monst *mdef; /* defer statue creation until after inventory removal so that saved monster traits won't retain any stale item-conferred attributes */ - otmp = mkcorpstat(STATUE, mdef, mdef->data, x, y, CORPSTAT_NONE); - if (has_mname(mdef)) - otmp = oname(otmp, MNAME(mdef)); + if (mdef->female) + corpstatflags |= CORPSTAT_FEMALE; + else if (!is_neuter(mdef->data)) + corpstatflags |= CORPSTAT_MALE; + /* Archeologists should not break unique statues */ + if (mdef->data->geno & G_UNIQ) + corpstatflags |= CORPSTAT_HISTORIC; + otmp = mkcorpstat(STATUE, mdef, mdef->data, x, y, corpstatflags); + if (has_mgivenname(mdef)) + otmp = oname(otmp, MGIVENNAME(mdef), ONAME_NO_FLAGS); while ((obj = oldminvent) != 0) { oldminvent = obj->nobj; obj->nobj = 0; /* avoid merged-> obfree-> dealloc_obj-> panic */ (void) add_to_container(otmp, obj); } - /* Archeologists should not break unique statues */ - if (mdef->data->geno & G_UNIQ) - otmp->spe = 1; otmp->owt = weight(otmp); } else otmp = mksobj_at(ROCK, x, y, TRUE, FALSE); @@ -2254,98 +3359,149 @@ struct monst *mdef; unmap_object(x, y); if (cansee(x, y)) newsym(x, y); - /* We don't currently trap the hero in the statue in this case but we - * could */ - if (u.uswallow && u.ustuck == mdef) + /* we don't currently trap the hero in the statue in this case but we + could */ + if (engulfing_u(mdef)) wasinside = TRUE; mondead(mdef); if (wasinside) { - if (is_animal(mdef->data)) + if (digests(mdef->data)) You("%s through an opening in the new %s.", - locomotion(youmonst.data, "jump"), xname(otmp)); + u_locomotion("jump"), xname(otmp)); } + return; } /* another monster has killed the monster mdef */ void -monkilled(mdef, fltxt, how) -struct monst *mdef; -const char *fltxt; -int how; +monkilled( + struct monst *mdef, + const char *fltxt, + int how) { - boolean be_sad = FALSE; /* true if unseen pet is killed */ + struct permonst *mptr = mdef->data; - if ((mdef->wormno ? worm_known(mdef) : cansee(mdef->mx, mdef->my)) - && fltxt) - pline("%s is %s%s%s!", Monnam(mdef), - nonliving(mdef->data) ? "destroyed" : "killed", + if (fltxt && (mdef->wormno ? worm_known(mdef) + : cansee(mdef->mx, mdef->my))) + pline_mon(mdef, "%s is %s%s%s!", Monnam(mdef), + nonliving(mptr) ? "destroyed" : "killed", *fltxt ? " by the " : "", fltxt); else - be_sad = (mdef->mtame != 0); - - /* no corpses if digested or disintegrated */ - disintegested = (how == AD_DGST || how == -AD_RBRE); - if (disintegested) - mondead(mdef); + /* sad feeling is deferred until after potential life-saving */ + iflags.sad_feeling = mdef->mtame ? TRUE : FALSE; + + /* no corpse if digested or disintegrated or flammable golem burnt up; + no corpse for a paper golem means no scrolls; golems that rust or + rot completely are described as "falling to pieces" so they do + leave a corpse (which means staves for wood golem, leather armor for + leather golem, iron chains for iron golem, not a regular corpse) */ + gd.disintegested = (how == AD_DGST || how == -AD_RBRE + || (how == AD_FIRE && completelyburns(mptr))); + if (gd.disintegested) + mondead(mdef); /* never leaves a corpse */ else - mondied(mdef); + mondied(mdef); /* calls mondead() and maybe leaves a corpse */ - if (be_sad && DEADMONSTER(mdef)) - You("have a sad feeling for a moment, then it passes."); + if (!DEADMONSTER(mdef)) + return; /* life-saved */ + /* extra message if pet golem is completely destroyed; + if not visible, this will follow "you have a sad feeling" */ + if (mdef->mtame) { + const char *rxt = (how == AD_FIRE && completelyburns(mptr)) ? "roast" + : (how == AD_RUST && completelyrusts(mptr)) ? "rust" + : (how == AD_DCAY && completelyrots(mptr)) ? "rot" + : 0; + if (rxt) + pline("May %s %s in peace.", noit_mon_nam(mdef), rxt); + } + return; +} + +void +set_ustuck(struct monst *mtmp) +{ + if (iflags.sanity_check || iflags.debug_fuzzer) { + if (mtmp && !m_next2u(mtmp)) + impossible("Sticking to %s at distu %d?", + mon_nam(mtmp), mdistu(mtmp)); + } + + disp.botl = TRUE; + u.ustuck = mtmp; + if (!u.ustuck) { + u.uswallow = 0; + u.uswldtim = 0; + } } void -unstuck(mtmp) -struct monst *mtmp; +unstuck(struct monst *mtmp) { if (u.ustuck == mtmp) { - if (u.uswallow) { + struct permonst *ptr = mtmp->data; + unsigned swallowed = u.uswallow; + + /* do this first so that docrt()'s botl update is accurate; + clears u.uswallow as well as setting u.ustuck to Null */ + set_ustuck((struct monst *) 0); + + if (swallowed) { + gm.mswallower = (struct monst *) 0; u.ux = mtmp->mx; u.uy = mtmp->my; - u.uswallow = 0; - u.uswldtim = 0; if (Punished && uchain->where != OBJ_FLOOR) placebc(); - vision_full_recalc = 1; + gv.vision_full_recalc = 1; docrt(); - /* prevent swallower (mtmp might have just poly'd into something - without an engulf attack) from immediately re-engulfing */ - if (attacktype(mtmp->data, AT_ENGL) && !mtmp->mspec_used) - mtmp->mspec_used = rnd(2); } - u.ustuck = 0; + + /* prevent holder/engulfer from immediately re-holding/re-engulfing + [note: this call to unstuck() might be because u.ustuck has just + changed shape and doesn't have a holding attack any more, hence + don't set mspec_used unconditionally] */ + if (!mtmp->mspec_used && (dmgtype(ptr, AD_STCK) + || attacktype(ptr, AT_ENGL) + || attacktype(ptr, AT_HUGS))) + mtmp->mspec_used = rnd(2); } } void -killed(mtmp) -struct monst *mtmp; +killed(struct monst *mtmp) { xkilled(mtmp, XKILL_GIVEMSG); } /* the player has killed the monster mtmp */ void -xkilled(mtmp, xkill_flags) -struct monst *mtmp; -int xkill_flags; /* 1: suppress message, 2: suppress corpse, 4: pacifist */ +xkilled( + struct monst *mtmp, + int xkill_flags) /* 1: suppress mesg, 2: suppress corpse, 4: pacifist */ { - int tmp, mndx, x = mtmp->mx, y = mtmp->my; + int tmp, mndx; + coordxy x = mtmp->mx, y = mtmp->my; + struct monst museum = cg.zeromonst; struct permonst *mdat; struct obj *otmp; struct trap *t; - boolean wasinside = u.uswallow && (u.ustuck == mtmp), + boolean be_sad; + boolean wasinside = engulfing_u(mtmp), burycorpse = FALSE, nomsg = (xkill_flags & XKILL_NOMSG) != 0, nocorpse = (xkill_flags & XKILL_NOCORPSE) != 0, noconduct = (xkill_flags & XKILL_NOCONDUCT) != 0; - mtmp->mhp = 0; /* caller will usually have already done this */ - if (!noconduct) /* KMH, conduct */ - u.uconduct.killer++; + /* potential pet message; always clear global flag */ + be_sad = iflags.sad_feeling; + iflags.sad_feeling = FALSE; + mtmp->mhp = 0; /* caller will usually have already done this */ + if (!noconduct) { /* KMH, conduct */ + if (!u.uconduct.killer++) + livelog_printf(LL_CONDUCT, "killed for the first time"); + } if (!nomsg) { - boolean namedpet = has_mname(mtmp) && !Hallucination; + boolean namedpet = has_mgivenname(mtmp) && !Hallucination; You("%s %s!", nonliving(mtmp->data) ? "destroy" : "kill", @@ -2355,13 +3511,12 @@ int xkill_flags; /* 1: suppress message, 2: suppress corpse, 4: pacifist */ "poor", namedpet ? SUPPRESS_SADDLE : 0, FALSE)); } - if (mtmp->mtrapped && (t = t_at(x, y)) != 0 - && is_pit(t->ttyp)) { + if (mtmp->mtrapped && (t = t_at(x, y)) != 0 && is_pit(t->ttyp)) { if (sobj_at(BOULDER, x, y)) nocorpse = TRUE; /* Prevent corpses/treasure being created - "on top" of boulder that is about to fall in. - This is out of order, but cannot be helped - unless this whole routine is rearranged. */ + * "on top" of boulder that is about to fall in. + * This is out of order, but cannot be helped + * unless this whole routine is rearranged. */ if (m_carrying(mtmp, BOULDER)) burycorpse = TRUE; } @@ -2370,51 +3525,56 @@ int xkill_flags; /* 1: suppress message, 2: suppress corpse, 4: pacifist */ if (mtmp->mtame && !mtmp->isminion) EDOG(mtmp)->killed_by_u = 1; - if (wasinside && thrownobj && thrownobj != uball + if (wasinside && gt.thrownobj && gt.thrownobj != uball + /* don't give to mon if missile is going to be destroyed */ + && gt.thrownobj->oclass != POTION_CLASS /* don't give to mon if missile is going to return to hero */ - && thrownobj != (struct obj *) iflags.returning_missile) { + && gt.thrownobj != (struct obj *) iflags.returning_missile) { /* thrown object has killed hero's engulfer; add it to mon's inventory now so that it will be placed with mon's other stuff prior to lookhere/autopickup when hero is expelled below (as a side-effect, this missile has immunity from being consumed [for this shot/throw only]) */ - mpickobj(mtmp, thrownobj); + mpickobj(mtmp, gt.thrownobj); /* let throwing code know that missile has been disposed of */ - thrownobj = 0; + gt.thrownobj = 0; } - vamp_rise_msg = FALSE; /* might get set in mondead(); only checked below */ - disintegested = nocorpse; /* alternate vamp_rise message needed if true */ + gv.vamp_rise_msg = FALSE; /* might get set in mondead(); checked below */ + gd.disintegested = nocorpse; /* alternate vamp_rise mesg needed if true */ /* dispose of monster and make cadaver */ - if (stoned) + if (gs.stoned) monstone(mtmp); else mondead(mtmp); - disintegested = FALSE; /* reset */ + gd.disintegested = FALSE; /* reset */ if (!DEADMONSTER(mtmp)) { /* monster lifesaved */ /* Cannot put the non-visible lifesaving message in * lifesaved_monster() since the message appears only when _you_ * kill it (as opposed to visible lifesaving which always appears). */ - stoned = FALSE; - if (!cansee(x, y) && !vamp_rise_msg) + gs.stoned = FALSE; + if (!cansee(x, y) && !gv.vamp_rise_msg) pline("Maybe not..."); return; } + if (be_sad) + You("have a sad feeling for a moment, then it passes."); + mdat = mtmp->data; /* note: mondead can change mtmp->data */ mndx = monsndx(mdat); - if (stoned) { - stoned = FALSE; + if (gs.stoned) { + gs.stoned = FALSE; goto cleanup; } if (nocorpse || LEVEL_SPECIFIC_NOCORPSE(mdat)) goto cleanup; -#ifdef MAIL +#ifdef MAIL_STRUCTURES if (mdat == &mons[PM_MAIL_DAEMON]) { stackobj(mksobj_at(SCR_MAIL, x, y, FALSE, FALSE)); } @@ -2424,7 +3584,7 @@ int xkill_flags; /* 1: suppress message, 2: suppress corpse, 4: pacifist */ int otyp; /* illogical but traditional "treasure drop" */ - if (!rn2(6) && !(mvitals[mndx].mvflags & G_NOCORPSE) + if (!rn2(6) && !(svm.mvitals[mndx].mvflags & G_NOCORPSE) /* no extra item from swallower or steed */ && (x != u.ux || y != u.uy) /* no extra item from kops--too easy to abuse */ @@ -2434,9 +3594,20 @@ int xkill_flags; /* 1: suppress message, 2: suppress corpse, 4: pacifist */ otmp = mkobj(RANDOM_CLASS, TRUE); /* don't create large objects from small monsters */ otyp = otmp->otyp; - if (mdat->msize < MZ_HUMAN && otyp != FIGURINE + if (otmp->oclass == FOOD_CLASS && !(mdat->mflags2 & M2_COLLECT) + && !otmp->oartifact) { + /* don't drop newly created permafood from kills, unless + the monster collects food; it creates too much nutrition + in the late game and encourages grinding in the early + game; oartifact check is paranoia and will be redundant + until an artifact comestible is added */ + delobj(otmp); + } else if (mdat->msize < MZ_HUMAN && otyp != FIGURINE /* oc_big is also oc_bimanual and oc_bulky */ && (otmp->owt > 30 || objects[otyp].oc_big)) { + if (otmp->oartifact) /* un-create */ + artifact_exists(otmp, safe_oname(otmp), FALSE, + ONAME_NO_FLAGS); delobj(otmp); } else if (!flooreffects(otmp, x, y, nomsg ? "" : "fall")) { place_object(otmp, x, y); @@ -2445,24 +3616,44 @@ int xkill_flags; /* 1: suppress message, 2: suppress corpse, 4: pacifist */ } /* corpse--none if hero was inside the monster */ if (!wasinside && corpse_chance(mtmp, (struct monst *) 0, FALSE)) { + gz.zombify = (!gt.thrownobj && !gs.stoned && !uwep + && zombie_maker(&gy.youmonst) + && zombie_form(mtmp->data) != NON_PM); cadaver = make_corpse(mtmp, burycorpse ? CORPSTAT_BURIED : CORPSTAT_NONE); + gz.zombify = FALSE; /* reset */ if (burycorpse && cadaver && cansee(x, y) && !mtmp->minvis && cadaver->where == OBJ_BURIED && !nomsg) { pline("%s corpse ends up buried.", s_suffix(Monnam(mtmp))); } } } - if (wasinside) + + if (wasinside) { + /* spoteffects() can end up clearing level of monsters; grab a copy */ + museum = *mtmp; + museum.nmon = 0; + museum.minvent = 0; + museum.mextra = 0; spoteffects(TRUE); /* poor man's expels() */ + mtmp = &museum; /* use the reference copy now */ + } /* monster is gone, corpse or other object might now be visible */ newsym(x, y); cleanup: - /* punish bad behaviour */ + /* + * Punish bad behavior. + */ if (is_human(mdat) && (!always_hostile(mdat) && mtmp->malign <= 0) + /* exclude role monsters */ && (mndx < PM_ARCHEOLOGIST || mndx > PM_WIZARD) + /* exclude plain "human", which isn't flagged as always hostile; + it is rare and most likely to occur as the result of resurrecting + a corpse or animating a statue and usually will be hostile */ + && mndx != PM_HUMAN + /* only applicable if hero is lawful or neutral */ && u.ualign.type != A_CHAOTIC) { HTelepat &= ~INTRINSIC; change_luck(-2); @@ -2478,19 +3669,26 @@ int xkill_flags; /* 1: suppress message, 2: suppress corpse, 4: pacifist */ } /* give experience points */ - tmp = experience(mtmp, (int) mvitals[mndx].died); + tmp = experience(mtmp, (int) svm.mvitals[mndx].died); more_experienced(tmp, 0); newexplevel(); /* will decide if you go up */ /* adjust alignment points */ - if (mtmp->m_id == quest_status.leader_m_id) { /* REAL BAD! */ + if (mtmp->m_id == svq.quest_status.leader_m_id) { /* REAL BAD! */ adjalign(-(u.ualign.record + (int) ALIGNLIM / 2)); + u.ugangr += 7; /* instantly become "extremely" angry */ + change_luck(-20); pline("That was %sa bad idea...", u.uevent.qcompleted ? "probably " : ""); + if (!svc.context.mon_moving) + iter_mons(anger_quest_guardians); } else if (mdat->msound == MS_NEMESIS) { /* Real good! */ - adjalign((int) (ALIGNLIM / 4)); + if (!svq.quest_status.killed_leader) + adjalign((int) (ALIGNLIM / 4)); } else if (mdat->msound == MS_GUARDIAN) { /* Bad */ adjalign(-(int) (ALIGNLIM / 8)); + u.ugangr++; + change_luck(-4); if (!Hallucination) pline("That was probably a bad idea..."); else @@ -2505,30 +3703,57 @@ int xkill_flags; /* 1: suppress message, 2: suppress corpse, 4: pacifist */ } else if (mtmp->mtame) { adjalign(-15); /* bad!! */ /* your god is mighty displeased... */ - if (!Hallucination) + if (!Hallucination) { + Soundeffect(se_distant_thunder, 40); You_hear("the rumble of distant thunder..."); - else + } else { + Soundeffect(se_applause, 40); You_hear("the studio audience applaud!"); + } + if (!unique_corpstat(mdat)) { + boolean mname = has_mgivenname(mtmp); + + livelog_printf(LL_KILLEDPET, "murdered %s%s%s faithful %s", + mname ? MGIVENNAME(mtmp) : "", + mname ? ", " : "", + uhis(), pmname(mdat, Mgender(mtmp))); + } } else if (mtmp->mpeaceful) adjalign(-5); /* malign was already adjusted for u.ualign.type and randomization */ adjalign(mtmp->malign); + +#if 0 /* HARDFOUGHT-only at present */ +#ifdef LIVELOG + if (has_ebones(mtmp)) { + livelog_printf(LL_UMONST, "destroyed %s, %s former %s", + livelog_mon_nam(mtmp), + (mtmp->data == &mons[PM_GHOST]) ? "the" : "and", + rank_of(EBONES(mtmp)->deathlevel, + EBONES(mtmp)->mnum, + EBONES(mtmp)->female)); + } +#endif /* LIVELOG */ +#endif + return; } +#undef LEVEL_SPECIFIC_NOCORPSE +#undef livelog_mon_nam + /* changes the monster into a stone monster of the same type this should only be called when poly_when_stoned() is true */ void -mon_to_stone(mtmp) -struct monst *mtmp; +mon_to_stone(struct monst *mtmp) { if (mtmp->data->mlet == S_GOLEM) { /* it's a golem, and not a stone golem */ if (canseemon(mtmp)) - pline("%s solidifies...", Monnam(mtmp)); - if (newcham(mtmp, &mons[PM_STONE_GOLEM], FALSE, FALSE)) { + pline_mon(mtmp, "%s solidifies...", Monnam(mtmp)); + if (newcham(mtmp, &mons[PM_STONE_GOLEM], NO_NC_FLAGS)) { if (canseemon(mtmp)) - pline("Now it's %s.", an(mtmp->data->mname)); + pline("Now it's %s.", an(pmname(mtmp->data, Mgender(mtmp)))); } else { if (canseemon(mtmp)) pline("... and returns to normal."); @@ -2538,19 +3763,16 @@ struct monst *mtmp; } boolean -vamp_stone(mtmp) -struct monst *mtmp; +vamp_stone(struct monst *mtmp) { if (is_vampshifter(mtmp)) { int mndx = mtmp->cham; - int x = mtmp->mx, y = mtmp->my; + coordxy x = mtmp->mx, y = mtmp->my; /* this only happens if shapeshifted */ if (mndx >= LOW_PM && mndx != monsndx(mtmp->data) - && !(mvitals[mndx].mvflags & G_GENOD)) { + && !(svm.mvitals[mndx].mvflags & G_GENOD)) { char buf[BUFSZ]; - boolean in_door = (amorphous(mtmp->data) - && closed_door(mtmp->mx, mtmp->my)); /* construct a format string before transformation */ Sprintf(buf, "The lapidifying %s %s %s", @@ -2560,16 +3782,15 @@ struct monst *mtmp; amorphous(mtmp->data) ? "coalesces on the" : is_flyer(mtmp->data) ? "drops to the" : "writhes on the", - surface(x,y)); + surface(x, y)); mtmp->mcanmove = 1; mtmp->mfrozen = 0; - if (mtmp->mhpmax <= 0) - mtmp->mhpmax = 10; + set_mon_min_mhpmax(mtmp, 10); /* mtmp->mhpmax=max(m_lev+1,10) */ mtmp->mhp = mtmp->mhpmax; /* this can happen if previously a fog cloud */ - if (u.uswallow && (mtmp == u.ustuck)) + if (engulfing_u(mtmp)) expels(mtmp, mtmp->data, FALSE); - if (in_door) { + if (amorphous(mtmp->data) && closed_door(mtmp->mx, mtmp->my)) { coord new_xy; if (enexto(&new_xy, mtmp->mx, mtmp->my, &mons[mndx])) { @@ -2577,31 +3798,31 @@ struct monst *mtmp; } } if (canspotmon(mtmp)) { - pline("%s!", buf); + pline_mon(mtmp, "%s!", buf); display_nhwindow(WIN_MESSAGE, FALSE); } - newcham(mtmp, &mons[mndx], FALSE, FALSE); + (void) newcham(mtmp, &mons[mndx], NO_NC_FLAGS); if (mtmp->data == &mons[mndx]) mtmp->cham = NON_PM; else mtmp->cham = mndx; if (canspotmon(mtmp)) { - pline("%s rises from the %s with renewed agility!", + pline_mon(mtmp, + "%s rises from the %s with renewed agility!", Amonnam(mtmp), surface(mtmp->mx, mtmp->my)); } newsym(mtmp->mx, mtmp->my); return FALSE; /* didn't petrify */ } - } else if (mtmp->cham >= LOW_PM + } else if (ismnum(mtmp->cham) && (mons[mtmp->cham].mresists & MR_STONE)) { /* sandestins are stoning-immune so if hit by stoning damage they revert to innate shape rather than become a statue */ mtmp->mcanmove = 1; mtmp->mfrozen = 0; - if (mtmp->mhpmax <= 0) - mtmp->mhpmax = 10; + set_mon_min_mhpmax(mtmp, 10); /* mtmp->mhpmax=max(mtmp->m_lev+1,10) */ mtmp->mhp = mtmp->mhpmax; - (void) newcham(mtmp, &mons[mtmp->cham], FALSE, TRUE); + (void) newcham(mtmp, &mons[mtmp->cham], NC_SHOW_MSG); newsym(mtmp->mx, mtmp->my); return FALSE; /* didn't petrify */ } @@ -2610,29 +3831,37 @@ struct monst *mtmp; /* drop monster into "limbo" - that is, migrate to the current level */ void -m_into_limbo(mtmp) -struct monst *mtmp; +m_into_limbo(struct monst *mtmp) { - xchar target_lev = ledger_no(&u.uz), xyloc = MIGR_APPROX_XY; + xint16 target_lev = ledger_no(&u.uz), xyloc = MIGR_APPROX_XY; mtmp->mstate |= MON_LIMBO; migrate_mon(mtmp, target_lev, xyloc); } -STATIC_OVL void -migrate_mon(mtmp, target_lev, xyloc) -struct monst *mtmp; -xchar target_lev, xyloc; +void +migrate_mon( + struct monst *mtmp, + xint16 target_lev, /* destination level */ + xint16 xyloc) /* MIGR_xxx flag for location within destination */ { - unstuck(mtmp); - mdrop_special_objs(mtmp); + /* + * If mtmp->mx is zero, this was a failed arrival attempt from a + * prior migration and mtmp isn't on the map. In that situation + * it can't be engulfing or holding the hero or held by same and + * should have dropped any special objects during that earlier + * migration back when it had a valid map location. So only + * perform some actions when mx is non-zero. + */ + if (mtmp->mx) { + unstuck(mtmp); + mdrop_special_objs(mtmp); + } migrate_to_level(mtmp, target_lev, xyloc, (coord *) 0); - mtmp->mstate |= MON_MIGRATING; } -STATIC_OVL boolean -ok_to_obliterate(mtmp) -struct monst *mtmp; +staticfn boolean +ok_to_obliterate(struct monst *mtmp) { /* * Add checks for monsters that should not be obliterated @@ -2640,25 +3869,24 @@ struct monst *mtmp; */ if (mtmp->data == &mons[PM_WIZARD_OF_YENDOR] || is_rider(mtmp->data) || has_emin(mtmp) || has_epri(mtmp) || has_eshk(mtmp) - || (u.ustuck == mtmp) || (u.usteed == mtmp)) + || mtmp == u.ustuck || mtmp == u.usteed) return FALSE; return TRUE; } void -elemental_clog(mon) -struct monst *mon; +elemental_clog(struct monst *mon) { - int m_lev = 0; static long msgmv = 0L; + int m_lev = 0; struct monst *mtmp, *m1, *m2, *m3, *m4, *m5, *zm; if (In_endgame(&u.uz)) { m1 = m2 = m3 = m4 = m5 = zm = (struct monst *) 0; - if (!msgmv || (moves - msgmv) > 200L) { + if (!msgmv || (svm.moves - msgmv) > 200L) { if (!msgmv || rn2(2)) You_feel("besieged."); - msgmv = moves; + msgmv = svm.moves; } /* * m1 an elemental from another plane. @@ -2710,7 +3938,7 @@ struct monst *mon; /* last resort - migrate mon to the next plane */ } else if (!Is_astralevel(&u.uz)) { d_level dest; - xchar target_lev; + coordxy target_lev; dest = u.uz; dest.dlevel--; @@ -2724,11 +3952,9 @@ struct monst *mon; /* make monster mtmp next to you (if possible); might place monst on far side of a wall or boulder */ void -mnexto(mtmp) -struct monst *mtmp; +mnexto(struct monst *mtmp, unsigned int rlocflags) { coord mm; - boolean couldspot = canspotmon(mtmp); if (mtmp == u.usteed) { /* Keep your steed in sync with you instead */ @@ -2741,19 +3967,23 @@ struct monst *mtmp; deal_with_overcrowding(mtmp); return; } - rloc_to(mtmp, mm.x, mm.y); - if (!in_mklev && (mtmp->mstrategy & STRAT_APPEARMSG)) { - mtmp->mstrategy &= ~STRAT_APPEARMSG; /* one chance only */ - if (!couldspot && canspotmon(mtmp)) - pline("%s suddenly %s!", Amonnam(mtmp), - !Blind ? "appears" : "arrives"); + /* wizard-mode player can choose destination by setting 'montelecontrol' + option; enexto()'s value for 'mm' will be the default; 'savemm' is + used to make sure player doesn't choose hero's location and then + answer 'y' to the 'override invalid spot' prompt */ + if (iflags.mon_telecontrol) { + coord savemm = mm; + + if (!control_mon_tele(mtmp, &mm, rlocflags, FALSE)) + mm = savemm; } + + rloc_to_flag(mtmp, mm.x, mm.y, rlocflags); return; } -STATIC_OVL void -deal_with_overcrowding(mtmp) -struct monst *mtmp; +void +deal_with_overcrowding(struct monst *mtmp) { if (In_endgame(&u.uz)) { debugpline1("overcrowding: elemental_clog on %s", m_monnam(mtmp)); @@ -2766,8 +3996,7 @@ struct monst *mtmp; /* like mnexto() but requires destination to be directly accessible */ void -maybe_mnexto(mtmp) -struct monst *mtmp; +maybe_mnexto(struct monst *mtmp) { coord mm; struct permonst *ptr = mtmp->data; @@ -2780,6 +4009,7 @@ struct monst *mtmp; if (couldsee(mm.x, mm.y) /* don't move grid bugs diagonally */ && (diagok || mm.x == mtmp->mx || mm.y == mtmp->my)) { + /* [this doesn't honor the 'montelecontrol' option] */ rloc_to(mtmp, mm.x, mm.y); return; } @@ -2798,13 +4028,15 @@ struct monst *mtmp; * will be False on the nested call so there won't be any further recursion. */ int -mnearto(mtmp, x, y, move_other) -register struct monst *mtmp; -xchar x, y; -boolean move_other; /* make sure mtmp gets to x, y! so move m_at(x, y) */ +mnearto( + struct monst *mtmp, + coordxy x, + coordxy y, + boolean move_other, /* make sure mtmp gets to x, y! so move m_at(x, y) */ + unsigned int rlocflags) { struct monst *othermon = (struct monst *) 0; - xchar newx, newy; + coordxy newx, newy; coord mm; int res = 1; @@ -2812,11 +4044,9 @@ boolean move_other; /* make sure mtmp gets to x, y! so move m_at(x, y) */ return res; if (move_other && (othermon = m_at(x, y)) != 0) { - if (othermon->wormno) - remove_worm(othermon); - else - remove_monster(x, y); - + /* take othermon off the map; it might end up immediately returning + but for the moment it is leaving */ + mon_leaving_level(othermon); othermon->mx = othermon->my = 0; /* 'othermon' is not on the map */ othermon->mstate |= MON_OFFMAP; } @@ -2841,44 +4071,188 @@ boolean move_other; /* make sure mtmp gets to x, y! so move m_at(x, y) */ newx = mm.x; newy = mm.y; } - rloc_to(mtmp, newx, newy); + /* [this doesn't honor the 'montelecontrol' option] */ + rloc_to_flag(mtmp, newx, newy, rlocflags); if (move_other && othermon) { res = 2; /* moving another monster out of the way */ - if (!mnearto(othermon, x, y, FALSE)) /* no 'move_other' this time */ + /* 'move_other'==FALSE this time; fail rather than recurse */ + if (!mnearto(othermon, x, y, FALSE, rlocflags)) deal_with_overcrowding(othermon); } return res; } -/* monster responds to player action; not the same as a passive attack; - assumes reason for response has been tested, and response _must_ be made */ -void -m_respond(mtmp) -struct monst *mtmp; +/* shrieker special action: shriek, maybe summon monster, aggravate */ +staticfn void +m_respond_shrieker(struct monst *mtmp) { - if (mtmp->data->msound == MS_SHRIEK) { - if (!Deaf) { - pline("%s shrieks.", Monnam(mtmp)); - stop_occupation(); - } - if (!rn2(10)) { - if (!rn2(13)) - (void) makemon(&mons[PM_PURPLE_WORM], 0, 0, NO_MM_FLAGS); - else - (void) makemon((struct permonst *) 0, 0, 0, NO_MM_FLAGS); + if (!Deaf) { + pline("%s shrieks.", Monnam(mtmp)); + stop_occupation(); + } + if (!rn2(10)) { /* 1/10 chance per shriek to create a monster */ + /* new monster has a 1/13 chance to be a purple worm, random + otherwise; baby purple worm if adult is too difficult */ + (void) makemon(rn2(13) ? (struct permonst *) 0 + : &mons[montoostrong(PM_PURPLE_WORM, + monmax_difficulty_lev()) + ? PM_BABY_PURPLE_WORM : PM_PURPLE_WORM], + 0, 0, NO_MM_FLAGS); + } + aggravate(); +} + +/* medusa special action: gaze at hero */ +staticfn void +m_respond_medusa(struct monst *mtmp) +{ + int i; + + for (i = 0; i < NATTK; i++) + if (mtmp->data->mattk[i].aatyp == AT_GAZE) { + (void) gazemu(mtmp, &mtmp->data->mattk[i]); + break; } +} + +/* monster responds to player action; not the same as a passive attack */ +void +m_respond(struct monst *mtmp) +{ + if (mtmp->data->msound == MS_SHRIEK && !um_dist(mtmp->mx, mtmp->my, 1)) + m_respond_shrieker(mtmp); + if (mtmp->data == &mons[PM_MEDUSA] && couldsee(mtmp->mx, mtmp->my)) + m_respond_medusa(mtmp); + /* Erinyes will inform surrounding monsters of your crimes */ + if (mtmp->data == &mons[PM_ERINYS] && !mtmp->mpeaceful && m_canseeu(mtmp)) aggravate(); +} + +/* how quest guardians respond when you attack the quest leader */ +staticfn void +qst_guardians_respond(void) +{ + struct monst *mon; + struct permonst *q_guardian = &mons[quest_info(MS_GUARDIAN)]; + int got_mad = 0; + + /* guardians will sense this attack even if they can't see it */ + for (mon = fmon; mon; mon = mon->nmon) { + if (DEADMONSTER(mon)) + continue; + if (mon->data == q_guardian && mon->mpeaceful) { + mon->mpeaceful = 0; + if (canseemon(mon)) + ++got_mad; + } } - if (mtmp->data == &mons[PM_MEDUSA]) { - register int i; + if (got_mad && !Hallucination) { + const char *who = q_guardian->pmnames[NEUTRAL]; - for (i = 0; i < NATTK; i++) - if (mtmp->data->mattk[i].aatyp == AT_GAZE) { - (void) gazemu(mtmp, &mtmp->data->mattk[i]); - break; + if (got_mad > 1) + who = makeplural(who); + pline_The("%s %s to be angry too...", + who, vtense(who, "appear")); + } +} + +/* how other peacefuls react when you attack monster */ +staticfn void +peacefuls_respond(struct monst *mtmp) +{ + struct monst *mon; + int mndx = monsndx(mtmp->data); + + for (mon = fmon; mon; mon = mon->nmon) { + if (DEADMONSTER(mon)) + continue; + if (mon == mtmp) /* the mpeaceful test catches this since mtmp */ + continue; /* is no longer peaceful, but be explicit... */ + + if (!mindless(mon->data) && mon->mpeaceful + && couldsee(mon->mx, mon->my) && !mon->msleeping + && mon->mcansee && m_canseeu(mon)) { + char buf[BUFSZ]; + boolean exclaimed = FALSE, needpunct = FALSE, alreadyfleeing; + + buf[0] = '\0'; + if (humanoid(mon->data) || mon->isshk || mon->ispriest) { + if (is_watch(mon->data)) { + SetVoice(mon, 0, 80, 0); + verbalize("Halt! You're under arrest!"); + (void) angry_guards(!!Deaf); + } else { + if (!Deaf && !rn2(5)) { + const char *gasp = maybe_gasp(mon); + + if (gasp) { + if (!strncmpi(gasp, "gasp", 4)) { + Sprintf(buf, "%s gasps", Monnam(mon)); + needpunct = TRUE; + } else { + Sprintf(buf, "%s exclaims \"%s\"", + Monnam(mon), gasp); + } + exclaimed = TRUE; + } + } + /* shopkeepers and temple priests might gasp in + surprise, but they won't become angry here; + quest leader will only get angry if hero attacks + own quest guardians */ + if (mon->isshk || mon->ispriest + || (mon->data == &mons[quest_info(MS_LEADER)] + && mtmp->data != &mons[gu.urole.guardnum])) { + if (exclaimed) + pline_mon(mon, "%s%s", buf, " then shrugs."); + continue; + } + + if (mon->data->mlevel < rn2(10) + /* don't have quest guardians turn to flee */ + && (mon->data != &mons[gu.urole.guardnum])) { + alreadyfleeing = (mon->mflee || mon->mfleetim); + monflee(mon, rn2(50) + 25, TRUE, !exclaimed); + if (exclaimed) { + if (flags.verbose && !alreadyfleeing) { + Strcat(buf, " and then turns to flee."); + needpunct = FALSE; + } + } else + exclaimed = TRUE; /* got msg from monflee() */ + } + if (*buf) + pline_mon(mon, "%s%s", buf, needpunct ? "." : ""); + if (mon->mtame) { + ; /* mustn't set mpeaceful to 0 as below; + * perhaps reduce tameness? */ + } else { + mon->mpeaceful = 0; + mon->mstrategy &= ~STRAT_WAITMASK; + adjalign(-1); + if (!exclaimed) + pline_mon(mon, "%s gets angry!", Monnam(mon)); + } + } + } else if (mon->data->mlet == mtmp->data->mlet + && big_little_match(mndx, monsndx(mon->data)) + && !rn2(3)) { + if (!rn2(4)) { + growl(mon); + exclaimed = (iflags.last_msg == PLNMSG_GROWL); + } + if (rn2(6)) { + alreadyfleeing = (mon->mflee || mon->mfleetim); + monflee(mon, rn2(25) + 15, TRUE, !exclaimed); + if (exclaimed && !alreadyfleeing) + /* word like a separate sentence so that we + don't have to poke around inside growl() */ + pline("And then starts to flee."); + } } + } } } @@ -2888,9 +4262,7 @@ struct monst *mtmp; where mtmp was already hostile; it checks for situations where the player shouldn't be attacking and any ramifications /that/ might have. */ void -setmangry(mtmp, via_attack) -struct monst *mtmp; -boolean via_attack; +setmangry(struct monst *mtmp, boolean via_attack) { if (via_attack && sengr_at("Elbereth", u.ux, u.uy, TRUE) /* only hypocritical if monster is vulnerable to Elbereth (or @@ -2916,6 +4288,9 @@ boolean via_attack; mtmp->mstrategy &= ~STRAT_WAITMASK; if (!mtmp->mpeaceful) return; + /* [FIXME: this logic seems wrong; peaceful humanoids gasp or exclaim + when they see you attack a peaceful monster but they just casually + look the other way when you attack a pet?] */ if (mtmp->mtame) return; mtmp->mpeaceful = 0; @@ -2926,132 +4301,77 @@ boolean via_attack; adjalign(2); } else adjalign(-1); /* attacking peaceful monsters is bad */ - if (couldsee(mtmp->mx, mtmp->my)) { - if (humanoid(mtmp->data) || mtmp->isshk || mtmp->isgd) - pline("%s gets angry!", Monnam(mtmp)); - else if (flags.verbose && !Deaf) - growl(mtmp); + if (humanoid(mtmp->data) || mtmp->isshk || mtmp->isgd) { + if (couldsee(mtmp->mx, mtmp->my)) + pline_mon(mtmp, "%s gets angry!", Monnam(mtmp)); + } else { + growl(mtmp); } /* attacking your own quest leader will anger his or her guardians */ - if (!context.mon_moving /* should always be the case here */ - && mtmp->data == &mons[quest_info(MS_LEADER)]) { - struct monst *mon; - struct permonst *q_guardian = &mons[quest_info(MS_GUARDIAN)]; - int got_mad = 0; - - /* guardians will sense this attack even if they can't see it */ - for (mon = fmon; mon; mon = mon->nmon) { - if (DEADMONSTER(mon)) - continue; - if (mon->data == q_guardian && mon->mpeaceful) { - mon->mpeaceful = 0; - if (canseemon(mon)) - ++got_mad; - } - } - if (got_mad && !Hallucination) { - const char *who = q_guardian->mname; - - if (got_mad > 1) - who = makeplural(who); - pline_The("%s %s to be angry too...", - who, vtense(who, "appear")); - } - } + if (mtmp->data == &mons[quest_info(MS_LEADER)]) + qst_guardians_respond(); /* make other peaceful monsters react */ - if (!context.mon_moving) { - static const char *const Exclam[] = { - "Gasp!", "Uh-oh.", "Oh my!", "What?", "Why?", - }; - struct monst *mon; - int mndx = monsndx(mtmp->data); - - for (mon = fmon; mon; mon = mon->nmon) { - if (DEADMONSTER(mon)) - continue; - if (mon == mtmp) /* the mpeaceful test catches this since mtmp */ - continue; /* is no longer peaceful, but be explicit... */ - - if (!mindless(mon->data) && mon->mpeaceful - && couldsee(mon->mx, mon->my) && !mon->msleeping - && mon->mcansee && m_canseeu(mon)) { - boolean exclaimed = FALSE; - - if (humanoid(mon->data) || mon->isshk || mon->ispriest) { - if (is_watch(mon->data)) { - verbalize("Halt! You're under arrest!"); - (void) angry_guards(!!Deaf); - } else { - if (!rn2(5)) { - verbalize("%s", Exclam[mon->m_id % SIZE(Exclam)]); - exclaimed = TRUE; - } - /* shopkeepers and temple priests might gasp in - surprise, but they won't become angry here */ - if (mon->isshk || mon->ispriest) - continue; + if (!svc.context.mon_moving) + peacefuls_respond(mtmp); +} - if (mon->data->mlevel < rn2(10)) { - monflee(mon, rn2(50) + 25, TRUE, !exclaimed); - exclaimed = TRUE; - } - if (mon->mtame) { - /* mustn't set mpeaceful to 0 as below; - perhaps reduce tameness? */ - } else { - mon->mpeaceful = 0; - adjalign(-1); - if (!exclaimed) - pline("%s gets angry!", Monnam(mon)); - } - } - } else if (mon->data->mlet == mtmp->data->mlet - && big_little_match(mndx, monsndx(mon->data)) - && !rn2(3)) { - if (!rn2(4)) { - growl(mon); - exclaimed = TRUE; - } - if (rn2(6)) - monflee(mon, rn2(25) + 15, TRUE, !exclaimed); - } - } - } +/* Indicate via message that a monster has awoken. */ +void +wake_msg(struct monst *mtmp, boolean interesting) +{ + if (mtmp->msleeping && canseemon(mtmp)) { + pline_mon(mtmp, "%s wakes up%s%s", + Monnam(mtmp), interesting ? "!" : ".", + mtmp->data == &mons[PM_FLESH_GOLEM] ? " It's alive!" : ""); } } /* wake up a monster, possibly making it angry in the process */ void -wakeup(mtmp, via_attack) -register struct monst *mtmp; -boolean via_attack; +wakeup(struct monst *mtmp, boolean via_attack) { + boolean was_sleeping = mtmp->msleeping; + + wake_msg(mtmp, via_attack); mtmp->msleeping = 0; - if (M_AP_TYPE(mtmp)) { - seemimic(mtmp); - } else if (context.forcefight && !context.mon_moving + if (M_AP_TYPE(mtmp) != M_AP_NOTHING) { + /* mimics come out of hiding, but disguised Wizard doesn't + have to lose his disguise */ + if (M_AP_TYPE(mtmp) != M_AP_MONSTER) + seemimic(mtmp); + } else if (svc.context.forcefight && !svc.context.mon_moving && mtmp->mundetected) { mtmp->mundetected = 0; newsym(mtmp->mx, mtmp->my); } finish_meating(mtmp); - if (via_attack) + if (via_attack) { + boolean was_peaceful = mtmp->mpeaceful; + + if (was_sleeping) + growl(mtmp); setmangry(mtmp, TRUE); + if (was_peaceful) { + if (mtmp->ispriest && *in_rooms(mtmp->mx, mtmp->my, TEMPLE)) + ghod_hitsu(mtmp); + if (mtmp->isshk && !*u.ushops) + hot_pursuit(mtmp); + } + } } /* Wake up nearby monsters without angering them. */ void -wake_nearby() +wake_nearby(boolean petcall) { - wake_nearto(u.ux, u.uy, u.ulevel * 20); + wake_nearto_core(u.ux, u.uy, u.ulevel * 20, petcall); } /* Wake up monsters near some particular location. */ -void -wake_nearto(x, y, distance) -int x, y, distance; +staticfn void +wake_nearto_core(coordxy x, coordxy y, int distance, boolean petcall) { struct monst *mtmp; @@ -3061,26 +4381,32 @@ int x, y, distance; if (distance == 0 || dist2(mtmp->mx, mtmp->my, x, y) < distance) { /* sleep for N turns uses mtmp->mfrozen, but so does paralysis so we leave mfrozen monsters alone */ + wake_msg(mtmp, FALSE); mtmp->msleeping = 0; /* wake indeterminate sleep */ if (!(mtmp->data->geno & G_UNIQ)) mtmp->mstrategy &= ~STRAT_WAITMASK; /* wake 'meditation' */ - if (context.mon_moving) + if (svc.context.mon_moving || !petcall) continue; if (mtmp->mtame) { if (!mtmp->isminion) - EDOG(mtmp)->whistletime = moves; - /* Clear mtrack. This is to fix up a pet who is - stuck "fleeing" its master. */ - memset(mtmp->mtrack, 0, sizeof mtmp->mtrack); + EDOG(mtmp)->whistletime = svm.moves; + /* Fix up a pet who is stuck "fleeing" its master */ + mon_track_clear(mtmp); } } } + disturb_buried_zombies(x, y); +} + +void +wake_nearto(coordxy x, coordxy y, int distance) +{ + wake_nearto_core(x, y, distance, FALSE); } /* NOTE: we must check for mimicry before calling this routine */ void -seemimic(mtmp) -register struct monst *mtmp; +seemimic(struct monst *mtmp) { boolean is_blocker_appear = (is_lightblocker_mappear(mtmp)); @@ -3100,77 +4426,240 @@ register struct monst *mtmp; newsym(mtmp->mx, mtmp->my); } -/* force all chameleons to become normal */ +/* [taken out of rescham() in order to be shared by restore_cham()] */ void -rescham() +normal_shape(struct monst *mon) { - register struct monst *mtmp; - int mcham; + int mcham = (int) mon->cham; - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) - continue; - mcham = (int) mtmp->cham; - if (mcham >= LOW_PM) { - (void) newcham(mtmp, &mons[mcham], FALSE, FALSE); - mtmp->cham = NON_PM; + if (ismnum(mcham)) { + unsigned mcan = mon->mcan; + + (void) newcham(mon, &mons[mcham], NC_SHOW_MSG); + mon->cham = NON_PM; + /* newcham() may uncancel a polymorphing monster; override that */ + if (mcan) + mon->mcan = 1; + newsym(mon->mx, mon->my); + } + if (is_were(mon->data) && mon->data->mlet != S_HUMAN) { + new_were(mon); + } + if (M_AP_TYPE(mon) != M_AP_NOTHING) { + /* this used to include a cansee() check but Protection_from_ + _shape_changers shouldn't be trumped by being unseen */ + if (!mon->meating) { + /* make revealed mimic fall asleep in lieu of shape change */ + if (M_AP_TYPE(mon) != M_AP_MONSTER) + mon->msleeping = 1; + seemimic(mon); + } else { + /* quickmimic: pet is midst of eating a mimic corpse; + this terminates the meal early */ + finish_meating(mon); } - if (is_were(mtmp->data) && mtmp->data->mlet != S_HUMAN) - new_were(mtmp); - if (M_AP_TYPE(mtmp) && cansee(mtmp->mx, mtmp->my)) { - seemimic(mtmp); - /* we pretend that the mimic doesn't - know that it has been unmasked */ - mtmp->msleeping = 1; + } +} + +/* freed by freedynamicdata() when game ends; doesn't need to be struct g */ +static struct monst **itermonarr = NULL; +static unsigned itermonsiz = 0; /* size in 'monst *' pointers */ + +/* manage itermonarr; it used to be allocated and freed every time the + monster movement loop ran; now, keep it around most of the time */ +void +alloc_itermonarr(unsigned count) +{ + /* if count is 0 or bigger than itermonsiz or much smaller than + itermonsiz, release itermonarr (and reset itermonsiz to 0) */ + if (!count || count > itermonsiz || count + 40 < itermonsiz) { + if (itermonarr) + free((genericptr_t) itermonarr), itermonarr = NULL; + itermonsiz = 0; + } + /* when count is more than itermonsiz (including when that just + got reset to 0), allocate a new instance of itermonarr; + implies that count is greater than 0 */ + if (count > itermonsiz) { + /* overallocate to reduce free/alloc-again thrashing when the + number of monsters varies from turn to turn */ + itermonsiz = count + 20; + itermonarr = (struct monst **) alloc( + itermonsiz * sizeof (struct monst *)); + } +} + +/* Iterate all monsters on the level, even dead or off-map ones, calling + bfunc() for each monster. If bfunc() returns TRUE, stop iterating. + If the game ends during the call to bfunc(), then freedynamicdata() + will free 'itermonarr'. + + Safe for list deletions and insertions, and guarantees calling bfunc() + once per monster in fmon unless it returns TRUE (or game ends). */ +void +iter_mons_safe(boolean (*bfunc)(struct monst *)) +{ + struct monst *mtmp; + unsigned i, nmons; + + for (nmons = 0, mtmp = fmon; mtmp; mtmp = mtmp->nmon) + nmons++; + + /* make sure itermonarr[] is big enough to hold nmons entries */ + alloc_itermonarr(nmons); + + if (nmons) { + for (i = 0, mtmp = fmon; mtmp; mtmp = mtmp->nmon) + itermonarr[i++] = mtmp; + + for (i = 0; i < nmons; i++) { + mtmp = itermonarr[i]; + if ((*bfunc)(mtmp)) + break; } } + return; } -/* Let the chameleons change again -dgk */ + +/* iterate all living monsters on current level, calling vfunc for each. */ void -restartcham() +iter_mons(void (*vfunc)(struct monst *)) { - register struct monst *mtmp; + struct monst *mtmp, *mtmp2; - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) + for (mtmp = fmon; mtmp; mtmp = mtmp2) { + mtmp2 = mtmp->nmon; + if (DEADMONSTER(mtmp) || mon_offmap(mtmp)) continue; - if (!mtmp->mcan) - mtmp->cham = pm_to_cham(monsndx(mtmp->data)); - if (mtmp->data->mlet == S_MIMIC && mtmp->msleeping - && cansee(mtmp->mx, mtmp->my)) { - set_mimic_sym(mtmp); - newsym(mtmp->mx, mtmp->my); + (*vfunc)(mtmp); + } + return; +} + + +/* iterate all living monsters on current level, calling bfunc for each. + if bfunc returns TRUE, stop and return that monster. */ +struct monst * +get_iter_mons(boolean (*bfunc)(struct monst *)) +{ + struct monst *mtmp, *mtmp2; + + for (mtmp = fmon; mtmp; mtmp = mtmp2) { + mtmp2 = mtmp->nmon; + if (DEADMONSTER(mtmp) || mon_offmap(mtmp)) + continue; + if ((*bfunc)(mtmp)) + break; + } + return mtmp; +} + +/* iterate all living monsters on current level, calling bfunc for each, + passing x,y to the function. + if bfunc returns TRUE, stop and return that monster. */ +struct monst * +get_iter_mons_xy( + boolean (*bfunc)(struct monst *, coordxy, coordxy), + coordxy x, coordxy y) +{ + struct monst *mtmp, *mtmp2; + + for (mtmp = fmon; mtmp; mtmp = mtmp2) { + mtmp2 = mtmp->nmon; + if (DEADMONSTER(mtmp) || mon_offmap(mtmp)) + continue; + if ((*bfunc)(mtmp, x, y)) + break; + } + return mtmp; +} + + +/* Heal the given monster by amt hitpoints, unless it is somehow prevented + from healing. "overheal" is the maximum amount by which the max HP will + increase to allow for the healing (the resulting HP caps at max HP + + overheal, and the max HP stays the some unless it needs to increase to + accommodate the new HP). Overhealing the player is not currently + implemented by this method. + + This function should only be used for situations which are conceptually + heals, rather than other situations where a monster's HP is set, so that + "prevent healing" effects work correctly. In particular, it should not + be used for cases where a monster's HP is restored upon revival, or when + a monster is created. It also shouldn't be used for lifesaving, which + overrides "cannot heal" effects. + + amt and overheal must not be negative (0 is allowed, and a very common + amount for overheal). Returns the number of hitpoints healed. */ +int +healmon(struct monst *mtmp, int amt, int overheal) +{ + if (mtmp == &gy.youmonst) { + int oldhp = Upolyd ? u.mh : u.uhp; + healup(amt, 0, 0, 0); + return (Upolyd ? u.mh : u.uhp) - oldhp; + } else { + int oldhp = mtmp->mhp; + if (mtmp->mhp + amt > mtmp->mhpmax + overheal) { + mtmp->mhpmax += overheal; + mtmp->mhp = mtmp->mhpmax; + } else { + mtmp->mhp += amt; + if (mtmp->mhp > mtmp->mhpmax) + mtmp->mhpmax = mtmp->mhp; } + return mtmp->mhp - oldhp; + } +} + + +/* force all chameleons and mimics to become themselves and werecreatures + to revert to human form; called when Protection_from_shape_changers gets + activated via wearing or eating ring or via #wizintrinsic */ +void +rescham(void) +{ + iter_mons(normal_shape); +} + +staticfn void +m_restartcham(struct monst *mtmp) +{ + if (!mtmp->mcan) + mtmp->cham = pm_to_cham(monsndx(mtmp->data)); + if (mtmp->data->mlet == S_MIMIC && mtmp->msleeping) { + set_mimic_sym(mtmp); + newsym(mtmp->mx, mtmp->my); } } +/* let chameleons change and mimics hide again; called when taking off + ring of protection from shape changers */ +void +restartcham(void) +{ + iter_mons(m_restartcham); +} + /* called when restoring a monster from a saved level; protection against shape-changing might be different now than it was at the time the level was saved. */ void -restore_cham(mon) -struct monst *mon; -{ - int mcham; - - if (Protection_from_shape_changers) { - mcham = (int) mon->cham; - if (mcham >= LOW_PM) { - mon->cham = NON_PM; - (void) newcham(mon, &mons[mcham], FALSE, FALSE); - } else if (is_were(mon->data) && !is_human(mon->data)) { - new_were(mon); - } +restore_cham(struct monst *mon) +{ + if (Protection_from_shape_changers || mon->mcan) { + /* force chameleon or mimic to revert to its natural shape */ + normal_shape(mon); } else if (mon->cham == NON_PM) { + /* chameleon doesn't change shape here, just gets allowed to do so */ mon->cham = pm_to_cham(monsndx(mon->data)); } } /* unwatched hiders may hide again; if so, returns True */ -STATIC_OVL boolean -restrap(mtmp) -register struct monst *mtmp; +staticfn boolean +restrap(struct monst *mtmp) { struct trap *t; @@ -3179,10 +4668,20 @@ register struct monst *mtmp; /* can't hide while trapped except in pits */ || (mtmp->mtrapped && (t = t_at(mtmp->mx, mtmp->my)) != 0 && !is_pit(t->ttyp)) - || (sensemon(mtmp) && distu(mtmp->mx, mtmp->my) <= 2)) + /* can't hide on ceiling if there isn't one */ + || (ceiling_hider(mtmp->data) && !has_ceiling(&u.uz)) + /* won't hide when adjacent to hero */ + || (sensemon(mtmp) && m_next2u(mtmp))) return FALSE; if (mtmp->data->mlet == S_MIMIC) { + if (mtmp->msleeping || mtmp->mfrozen) { + /* + * The mimic needs to be awake to disguise itself + * as something else. + */ + return FALSE; + } set_mimic_sym(mtmp); return TRUE; } else if (levl[mtmp->mx][mtmp->my].typ == ROOM) { @@ -3193,71 +4692,141 @@ register struct monst *mtmp; return FALSE; } -/* monster/hero tries to hide under something at the current location */ +/* reveal a hiding monster at x,y, either under nonexistent object, + or an eel out of water. */ +void +maybe_unhide_at(coordxy x, coordxy y) +{ + struct monst *mtmp; + boolean undetected = FALSE, trapped = FALSE; + + if ((mtmp = m_at(x, y)) != (struct monst *) 0) { + undetected = mtmp->mundetected; + trapped = mtmp->mtrapped; + } else if (u_at(x, y)) { + mtmp = &gy.youmonst; + undetected = u.uundetected; + trapped = u.utrap; + } else { + return; + } + + if (undetected + && ((hides_under(mtmp->data) + && (!OBJ_AT(x, y) || trapped + || !can_hide_under_obj(svl.level.objects[x][y]))) + || (mtmp->data->mlet == S_EEL && !is_pool(x, y)))) + (void) hideunder(mtmp); +} + +/* monster/hero tries to hide under something at the current location; + if used by monster creation, should only happen during level + creation, otherwise there will be message sequencing issues */ boolean -hideunder(mtmp) -struct monst *mtmp; +hideunder(struct monst *mtmp) { struct trap *t; - boolean undetected = FALSE, is_u = (mtmp == &youmonst); - xchar x = is_u ? u.ux : mtmp->mx, y = is_u ? u.uy : mtmp->my; + struct obj *otmp; + const char *seenmon = (char *) 0, *seenobj = (char *) 0, + *locomo = (char *) 0; + int seeit = gi.in_mklev ? 0 : canseemon(mtmp); + boolean oldundetctd, undetected = FALSE, is_u = (mtmp == &gy.youmonst); + coordxy x = is_u ? u.ux : mtmp->mx, y = is_u ? u.uy : mtmp->my; if (mtmp == u.ustuck) { - ; /* can't hide if holding you or held by you */ - } else if (is_u ? (u.utrap && u.utraptype != TT_PIT) - : (mtmp->mtrapped && (t = t_at(x, y)) != 0 - && !is_pit(t->ttyp))) { - ; /* can't hide while stuck in a non-pit trap */ + ; /* undetected==FALSE; can't hide if holding you or held by you */ + } else if ((is_u ? u.utrap : mtmp->mtrapped) + || ((t = t_at(x, y)) != 0 && !is_pit(t->ttyp))) { + ; /* undetected==FALSE; can't hide while trapped or on/in/under + any non-pit trap when not trapped */ } 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 = level.objects[x][y]; - - /* most monsters won't hide under cockatrice corpse */ - if (otmp->nexthere || otmp->otyp != CORPSE - || (mtmp == &youmonst ? Stone_resistance : resists_ston(mtmp)) - || !touch_petrifies(&mons[otmp->corpsenm])) + /* aquatic creatures only hide under water, not under objects; + they don't do so on the Plane of Water or when hero is also + under water unless some obstacle blocks line-of-sight */ + undetected = (is_pool(x, y) && !Is_waterlevel(&u.uz) + && (!Underwater || !couldsee(x, y))); + if (seeit) { + seenobj = "the water"; + locomo = "dive"; + } + } else if (hides_under(mtmp->data) + /* hider-underers only hide under objects */ + && (otmp = svl.level.objects[x][y]) != 0 + /* most things can be hidden under, but not all */ + && can_hide_under_obj(otmp) + /* pets won't hide under a cursed item or an item of any BUC + state that shares a pile with one or more cursed items */ + && (!mtmp->mtame || !cursed_object_at(x, y)) + /* aquatic creatures don't reach here; other swimmers + shouldn't hide beneath underwater objects */ + && !is_pool_or_lava(x, y)) { + if (seeit) /*&& (!is_pool(x, y) || (Underwater && distu(x, y) <= 2))*/ + seenobj = ansimpleoname(otmp); + /* most monsters won't hide under a cockatrice corpse but they + can hide under a pile containing more than just such corpses */ + if (is_u ? !Stone_resistance : !resists_ston(mtmp)) + while (otmp && otmp->otyp == CORPSE + && touch_petrifies(&mons[otmp->corpsenm])) + otmp = otmp->nexthere; + if (otmp) undetected = TRUE; } - if (is_u) - u.uundetected = undetected; - else - mtmp->mundetected = undetected; + if (is_u) { + oldundetctd = u.uundetected != 0; + u.uundetected = undetected ? 1 : 0; +#if 0 /* feedback handled via #monster */ + if (undetected && !oldundeteced && seenobj) + You("hide under %s.", seenobj); +#endif + } else { + if (seeit) + seenmon = y_monnam(mtmp); + oldundetctd = mtmp->mundetected != 0; + mtmp->mundetected = undetected ? 1 : 0; + /* the "you see" message won't be shown for monster hiding during + level creation because 'seeit' will be 0 so 'seenmon' and 'seenobj' + will be Null */ + if (undetected && seenmon && seenobj) { + if (!locomo) + locomo = locomotion(mtmp->data, "hide"); + set_msg_xy(mtmp->mx, mtmp->my); /* pline() will reset this */ + You_see("%s %s under %s.", seenmon, locomo, seenobj); + iflags.last_msg = PLNMSG_HIDE_UNDER; + gl.last_hider = mtmp->m_id; + } + } + if (undetected != oldundetctd) + newsym(x, y); return undetected; } /* called when returning to a previously visited level */ void -hide_monst(mon) -struct monst *mon; +hide_monst(struct monst *mon) { boolean hider_under = hides_under(mon->data) || mon->data->mlet == S_EEL; if ((is_hider(mon->data) || hider_under) && !(mon->mundetected || M_AP_TYPE(mon))) { - xchar x = mon->mx, y = mon->my; - char save_viz = viz_array[y][x]; + coordxy x = mon->mx, y = mon->my; + char save_viz = gv.viz_array[y][x]; /* override vision, forcing hero to be unable to see monster's spot */ - viz_array[y][x] &= ~(IN_SIGHT | COULD_SEE); + gv.viz_array[y][x] &= ~(IN_SIGHT | COULD_SEE); if (is_hider(mon->data)) (void) restrap(mon); /* try again if mimic missed its 1/3 chance to hide */ if (mon->data->mlet == S_MIMIC && !M_AP_TYPE(mon)) (void) restrap(mon); + gv.viz_array[y][x] = save_viz; if (hider_under) (void) hideunder(mon); - viz_array[y][x] = save_viz; } } -static short *animal_list = 0; /* list of PM values for animal monsters */ -static int animal_list_count; - void -mon_animal_list(construct) -boolean construct; +mon_animal_list(boolean construct) { if (construct) { short animal_temp[SPECIAL_PM]; @@ -3270,53 +4839,50 @@ boolean construct; animal_temp[n++] = i; /* if (n == 0) animal_temp[n++] = NON_PM; */ - animal_list = (short *) alloc(n * sizeof *animal_list); - (void) memcpy((genericptr_t) animal_list, (genericptr_t) animal_temp, - n * sizeof *animal_list); - animal_list_count = n; + ga.animal_list = (short *) alloc(n * sizeof *ga.animal_list); + (void) memcpy((genericptr_t) ga.animal_list, + (genericptr_t) animal_temp, + n * sizeof *ga.animal_list); + ga.animal_list_count = n; } else { /* release */ - if (animal_list) - free((genericptr_t) animal_list), animal_list = 0; - animal_list_count = 0; + if (ga.animal_list) + free((genericptr_t) ga.animal_list), ga.animal_list = 0; + ga.animal_list_count = 0; } } -STATIC_OVL int -pick_animal() +staticfn int +pick_animal(void) { int res; - if (!animal_list) + if (!ga.animal_list) mon_animal_list(TRUE); - - res = animal_list[rn2(animal_list_count)]; + assert(ga.animal_list != 0); + res = ga.animal_list[rn2(ga.animal_list_count)]; /* rogue level should use monsters represented by uppercase letters only, but since chameleons aren't generated there (not uppercase!) we don't perform a lot of retries */ - if (Is_rogue_level(&u.uz) && !isupper((uchar) mons[res].mlet)) - res = animal_list[rn2(animal_list_count)]; + if (Is_rogue_level(&u.uz) && !isupper(monsym(&mons[res]))) + res = ga.animal_list[rn2(ga.animal_list_count)]; return res; } void -decide_to_shapeshift(mon, shiftflags) -struct monst *mon; -int shiftflags; +decide_to_shapeshift(struct monst *mon) { struct permonst *ptr = 0; int mndx; unsigned was_female = mon->female; - boolean msg = FALSE, dochng = FALSE; - - if ((shiftflags & SHIFT_MSG) - || ((shiftflags & SHIFT_SEENMSG) && sensemon(mon))) - msg = TRUE; + boolean dochng = FALSE; if (!is_vampshifter(mon)) { - /* regular shapeshifter */ - if (!rn2(6)) + /* regular shapeshifter; 'ptr' is Null */ + if (!mon->mspec_used && !rn2(6)) { dochng = TRUE; - } else { + mon->mspec_used = 3 + rn2(10); + } + } else if (!(mon->mstrategy & STRAT_WAITFORU)) { /* The vampire has to be in good health (mhp) to maintain * its shifted form. * @@ -3326,43 +4892,53 @@ int shiftflags; */ if (mon->data->mlet != S_VAMPIRE) { if ((mon->mhp <= (mon->mhpmax + 5) / 6) && rn2(4) - && mon->cham >= LOW_PM) { + && ismnum(mon->cham)) { ptr = &mons[mon->cham]; dochng = TRUE; } else if (mon->data == &mons[PM_FOG_CLOUD] - && mon->mhp == mon->mhpmax && !rn2(4) - && (!canseemon(mon) - || distu(mon->mx, mon->my) > BOLT_LIM * BOLT_LIM)) { + && mon->mhp == mon->mhpmax && !rn2(4) + && (!canseemon(mon) + || mdistu(mon) > BOLT_LIM * BOLT_LIM)) { /* if a fog cloud, maybe change to wolf or vampire bat; those are more likely to take damage--at least when tame--and then switch back to vampire; they'll also switch to fog cloud if they encounter a closed door */ mndx = pickvampshape(mon); - if (mndx >= LOW_PM) { + if (ismnum(mndx)) { ptr = &mons[mndx]; dochng = (ptr != mon->data); } } + if (dochng && amorphous(mon->data) + && closed_door(mon->mx, mon->my)) { + coord new_xy; + + if (enexto(&new_xy, mon->mx, mon->my, ptr)) { + rloc_to(mon, new_xy.x, new_xy.y); + } + } } else { if (mon->mhp >= 9 * mon->mhpmax / 10 && !rn2(6) && (!canseemon(mon) - || distu(mon->mx, mon->my) > BOLT_LIM * BOLT_LIM)) + || mdistu(mon) > BOLT_LIM * BOLT_LIM)) dochng = TRUE; /* 'ptr' stays Null */ } } if (dochng) { - if (newcham(mon, ptr, FALSE, msg) && is_vampshifter(mon)) { - /* for vampshift, override the 10% chance for sex change */ - ptr = mon->data; - if (!is_male(ptr) && !is_female(ptr) && !is_neuter(ptr)) - mon->female = was_female; + if (newcham(mon, ptr, NC_SHOW_MSG)) { + /* for vampshift, override the 10% chance for sex change + (by forcing original gender in case that occurred) */ + if (is_vampshifter(mon)) { + ptr = mon->data; + if (!is_male(ptr) && !is_female(ptr) && !is_neuter(ptr)) + mon->female = was_female; + } } } } -STATIC_OVL int -pickvampshape(mon) -struct monst *mon; +staticfn int +pickvampshape(struct monst *mon) { int mndx = mon->cham, wolfchance = 10; /* avoid picking monsters with lowercase display symbols ('d' for wolf @@ -3375,36 +4951,46 @@ struct monst *mon; if (mon_has_special(mon)) break; /* leave mndx as is */ wolfchance = 3; + FALLTHROUGH; /*FALLTHRU*/ - case PM_VAMPIRE_LORD: /* vampire lord or Vlad can become wolf */ - if (!rn2(wolfchance) && !uppercase_only) { + case PM_VAMPIRE_LEADER: /* vampire lord or Vlad can become wolf */ + if (!rn2(wolfchance) && !uppercase_only + /* don't pick a walking form if that would lead to immediate + drowning or immolation and reversion to vampire form */ + && !is_pool_or_lava(mon->mx, mon->my)) { mndx = PM_WOLF; break; } + FALLTHROUGH; /*FALLTHRU*/ case PM_VAMPIRE: /* any vampire can become fog or bat */ mndx = (!rn2(4) && !uppercase_only) ? PM_FOG_CLOUD : PM_VAMPIRE_BAT; break; } + + /* return to base form if chosen poly target has been genocided + or randomly if already in an alternate form (to prevent always + switching back and forth between bat and fog) */ + if ((svm.mvitals[mndx].mvflags & G_GENOD) != 0 + || (mon->data != &mons[mon->cham] && !rn2(4))) + return mon->cham; + return mndx; } /* nonshapechangers who warrant special polymorph handling */ -STATIC_OVL boolean -isspecmon(mon) -struct monst *mon; +staticfn boolean +isspecmon(struct monst *mon) { return (mon->isshk || mon->ispriest || mon->isgd - || mon->m_id == quest_status.leader_m_id); + || mon->m_id == svq.quest_status.leader_m_id); } /* restrict certain special monsters (shopkeepers, aligned priests, vault guards) to forms that allow them to behave sensibly (catching gold, speaking?) so that they don't need too much extra code */ -STATIC_OVL boolean -validspecmon(mon, mndx) -struct monst *mon; -int mndx; +staticfn boolean +validspecmon(struct monst *mon, int mndx) { if (mndx == NON_PM) return TRUE; /* caller wants random */ @@ -3424,11 +5010,22 @@ int mndx; return TRUE; /* potential new form is ok */ } -/* prevent wizard mode user from specifying invalid vampshifter shape */ +/* used for hero polyself handling */ boolean -validvamp(mon, mndx_p, monclass) -struct monst *mon; -int *mndx_p, monclass; +valid_vampshiftform(int base, int form) +{ + if (base >= LOW_PM && is_vampire(&mons[base])) { + if (form == PM_VAMPIRE_BAT || form == PM_FOG_CLOUD + || (form == PM_WOLF && base != PM_VAMPIRE)) + return TRUE; + } + return FALSE; +} + +/* prevent wizard mode user from specifying invalid vampshifter shape + when using monpolycontrol to assign a new form to a vampshifter */ +boolean +validvamp(struct monst *mon, int *mndx_p, int monclass) { /* simplify caller's usage */ if (!is_vampshifter(mon)) @@ -3439,7 +5036,7 @@ int *mndx_p, monclass; *mndx_p = PM_VLAD_THE_IMPALER; return TRUE; } - if (*mndx_p >= LOW_PM && is_shapeshifter(&mons[*mndx_p])) { + if (ismnum(*mndx_p) && is_shapeshifter(&mons[*mndx_p])) { /* player picked some type of shapeshifter; use mon's self (vampire or chameleon) */ *mndx_p = mon->cham; @@ -3468,6 +5065,7 @@ int *mndx_p, monclass; *mndx_p = PM_WOLF; break; } + FALLTHROUGH; /*FALLTHRU*/ default: *mndx_p = NON_PM; @@ -3476,31 +5074,110 @@ int *mndx_p, monclass; return (boolean) (*mndx_p != NON_PM); } +staticfn int +wiz_force_cham_form(struct monst *mon) +{ + char pprompt[BUFSZ], parttwo[QBUFSZ], buf[BUFSZ], prevbuf[BUFSZ]; + int monclass, len, tryct, mndx = NON_PM; + + /* construct prompt in pieces */ + Sprintf(pprompt, "Change %s", noit_mon_nam(mon)); + Sprintf(parttwo, " @ %s into what?", + coord_desc((int) mon->mx, (int) mon->my, buf, + (iflags.getpos_coords != GPCOORDS_NONE) + ? iflags.getpos_coords : GPCOORDS_MAP)); + /* combine the two parts, not exceeding QBUFSZ-1 in overall length; + if combined length is too long it has to be due to monster's + name so we'll chop enough of that off to fit the second part */ + if ((len = (int) strlen(pprompt) + (int) strlen(parttwo)) >= QBUFSZ) + /* strlen(parttwo) is less than QBUFSZ/2 so strlen(pprompt) is + more than QBUFSZ/2 and excess amount being truncated can't + exceed pprompt's length and back up to before &pprompt[0]) */ + *(eos(pprompt) - (len - (QBUFSZ - 1))) = '\0'; + Strcat(pprompt, parttwo); + + buf[0] = prevbuf[0] = '\0'; /* clear buffer for EDIT_GETLIN */ +#define TRYLIMIT 5 + tryct = TRYLIMIT; + do { + if (tryct == TRYLIMIT - 1) { /* first retry */ + /* change "into what?" to "into what kind of monster?" */ + if (strlen(pprompt) + sizeof " kind of monster" - 1 < QBUFSZ) + Strcpy(eos(pprompt) - 1, " kind of monster?"); + } +#undef TRYLIMIT + monclass = 0; + getlin(pprompt, buf); + mungspaces(buf); + /* for ESC, take form selected above (might be NON_PM) */ + if (*buf == '\033') + break; + /* for "*", use NON_PM to pick an arbitrary shape below */ + if (!strcmp(buf, "*") || !strcmpi(buf, "random")) { + mndx = NON_PM; + break; + } + mndx = name_to_mon(buf, (int *) 0); + if (mndx == NON_PM) { + /* didn't get a type, so check whether it's a class + (single letter or text match with def_monsyms[]) */ + monclass = name_to_monclass(buf, &mndx); + if (monclass && mndx == NON_PM) + mndx = mkclass_poly(monclass); + } + if (ismnum(mndx)) { + /* got a specific type of monster; use it if we can */ + if (validvamp(mon, &mndx, monclass)) + break; + /* can't; revert to random in case we exhaust tryct */ + mndx = NON_PM; + } + + pline("It can't become that."); +#ifdef EDIT_GETLIN + /* EDIT_GETLIN preloads the input buffer with the previous + response but we shouldn't just keep repeating that if player + leaves it unchanged; affects retry for empty input too */ + if (!strcmp(buf, prevbuf)) + Strcpy(buf, "random"); + Strcpy(prevbuf, buf); +#else + nhUse(prevbuf); +#endif + } while (--tryct > 0); + + if (!tryct) + pline1(thats_enough_tries); + if (is_vampshifter(mon) && !validvamp(mon, &mndx, monclass)) + mndx = pickvampshape(mon); /* don't resort to arbitrary */ + return mndx; +} + int -select_newcham_form(mon) -struct monst *mon; +select_newcham_form(struct monst *mon) { int mndx = NON_PM, tryct; switch (mon->cham) { case PM_SANDESTIN: if (rn2(7)) - mndx = pick_nasty(); + mndx = pick_nasty(mons[PM_ARCHON].difficulty - 1); break; case PM_DOPPELGANGER: if (!rn2(7)) { - mndx = pick_nasty(); + mndx = pick_nasty(mons[PM_JABBERWOCK].difficulty - 1); } else if (rn2(3)) { /* role monsters */ - mndx = rn1(PM_WIZARD - PM_ARCHEOLOGIST + 1, PM_ARCHEOLOGIST); + mndx = tt_doppel(mon); } else if (!rn2(3)) { /* quest guardians */ mndx = rn1(PM_APPRENTICE - PM_STUDENT + 1, PM_STUDENT); /* avoid own role's guardian */ - if (mndx == urole.guardnum) + if (mndx == gu.urole.guardnum) mndx = NON_PM; } else { /* general humanoids */ tryct = 5; do { mndx = rn1(SPECIAL_PM - LOW_PM, LOW_PM); + /* assert(ismnum(mndx)); */ if (humanoid(&mons[mndx]) && polyok(&mons[mndx])) break; } while (--tryct > 0); @@ -3513,7 +5190,7 @@ struct monst *mon; mndx = pick_animal(); break; case PM_VLAD_THE_IMPALER: - case PM_VAMPIRE_LORD: + case PM_VAMPIRE_LEADER: case PM_VAMPIRE: mndx = pickvampshape(mon); break; @@ -3530,97 +5207,33 @@ struct monst *mon; } /* for debugging: allow control of polymorphed monster */ - if (wizard && iflags.mon_polycontrol) { - char pprompt[BUFSZ], parttwo[QBUFSZ], buf[BUFSZ]; - int monclass, len; - - /* construct prompt in pieces */ - Sprintf(pprompt, "Change %s", noit_mon_nam(mon)); - Sprintf(parttwo, " @ %s into what?", - coord_desc((int) mon->mx, (int) mon->my, buf, - (iflags.getpos_coords != GPCOORDS_NONE) - ? iflags.getpos_coords : GPCOORDS_MAP)); - /* combine the two parts, not exceeding QBUFSZ-1 in overall length; - if combined length is too long it has to be due to monster's - name so we'll chop enough of that off to fit the second part */ - if ((len = (int) strlen(pprompt) + (int) strlen(parttwo)) >= QBUFSZ) - /* strlen(parttwo) is less than QBUFSZ/2 so strlen(pprompt) is - more than QBUFSZ/2 and excess amount being truncated can't - exceed pprompt's length and back up to before &pprompt[0]) */ - *(eos(pprompt) - (len - (QBUFSZ - 1))) = '\0'; - Strcat(pprompt, parttwo); - - buf[0] = '\0'; /* clear buffer for EDIT_GETLIN */ -#define TRYLIMIT 5 - tryct = TRYLIMIT; - do { - if (tryct == TRYLIMIT - 1) { /* first retry */ - /* change "into what?" to "into what kind of monster?" */ - if (strlen(pprompt) + sizeof " kind of monster" - 1 < QBUFSZ) - Strcpy(eos(pprompt) - 1, " kind of monster?"); - } -#undef TRYLIMIT - monclass = 0; - getlin(pprompt, buf); - mungspaces(buf); - /* for ESC, take form selected above (might be NON_PM) */ - if (*buf == '\033') - break; - /* for "*", use NON_PM to pick an arbitrary shape below */ - if (!strcmp(buf, "*") || !strcmpi(buf, "random")) { - mndx = NON_PM; - break; - } - mndx = name_to_mon(buf); - if (mndx == NON_PM) { - /* didn't get a type, so check whether it's a class - (single letter or text match with def_monsyms[]) */ - monclass = name_to_monclass(buf, &mndx); - if (monclass && mndx == NON_PM) - mndx = mkclass_poly(monclass); - } - if (mndx >= LOW_PM) { - /* got a specific type of monster; use it if we can */ - if (validvamp(mon, &mndx, monclass)) - break; - /* can't; revert to random in case we exhaust tryct */ - mndx = NON_PM; - } - - pline("It can't become that."); - } while (--tryct > 0); - - if (!tryct) - pline1(thats_enough_tries); - if (is_vampshifter(mon) && !validvamp(mon, &mndx, monclass)) - mndx = pickvampshape(mon); /* don't resort to arbitrary */ - } + if (wizard && iflags.mon_polycontrol) + mndx = wiz_force_cham_form(mon); /* if no form was specified above, pick one at random now */ if (mndx == NON_PM) { tryct = 50; do { mndx = rn1(SPECIAL_PM - LOW_PM, LOW_PM); + /* assert(ismnum(mndx); */ } while (--tryct > 0 && !validspecmon(mon, mndx) /* try harder to select uppercase monster on rogue level */ && (tryct > 40 && Is_rogue_level(&u.uz) - && !isupper((uchar) mons[mndx].mlet))); + && !isupper(monsym(&mons[mndx])))); } return mndx; } /* this used to be inline within newcham() but monpolycontrol needs it too */ -STATIC_OVL struct permonst * -accept_newcham_form(mon, mndx) -struct monst *mon; -int mndx; +staticfn struct permonst * +accept_newcham_form(struct monst *mon, int mndx) { struct permonst *mdat; if (mndx == NON_PM) return 0; mdat = &mons[mndx]; - if ((mvitals[mndx].mvflags & G_GENOD) != 0) + if ((svm.mvitals[mndx].mvflags & G_GENOD) != 0) return 0; if (is_placeholder(mdat)) return 0; @@ -3632,25 +5245,28 @@ int mndx; /* shapeshifters are rejected by polyok() but allow a shapeshifter to take on its 'natural' form */ if (is_shapeshifter(mdat) - && mon->cham >= LOW_PM && mdat == &mons[mon->cham]) + && ismnum(mon->cham) && mdat == &mons[mon->cham]) return mdat; /* polyok() rules out M2_PNAME, M2_WERE, and all humans except Kops */ return polyok(mdat) ? mdat : 0; } +/* shapechanger might take on a shape that forces gender change */ void -mgender_from_permonst(mtmp, mdat) -struct monst *mtmp; -struct permonst *mdat; +mgender_from_permonst( + struct monst *mtmp, + struct permonst *mdat) { if (is_male(mdat)) { - if (mtmp->female) - mtmp->female = FALSE; + mtmp->female = FALSE; } else if (is_female(mdat)) { - if (!mtmp->female) - mtmp->female = TRUE; + mtmp->female = TRUE; } else if (!is_neuter(mdat)) { - if (!rn2(10)) + /* usually leave as-is; same chance to change as polymorphing hero; + vampires use controlled shapechange (from their perspective, even + if it is random from the player's perspective) and don't undergo + gender change */ + if (!rn2(10) && !(is_vampire(mdat) || is_vampshifter(mtmp))) mtmp->female = !mtmp->female; } } @@ -3659,16 +5275,18 @@ struct permonst *mdat; (possibly self-inflicted) become a different monster; returns 1 if it actually changes form */ int -newcham(mtmp, mdat, polyspot, msg) -struct monst *mtmp; -struct permonst *mdat; -boolean polyspot; /* change is the result of wand or spell of polymorph */ -boolean msg; /* "The oldmon turns into a newmon!" */ -{ - int hpn, hpd; - int mndx, tryct; +newcham( + struct monst *mtmp, + struct permonst *mdat, + unsigned ncflags) +{ + boolean polyspot = ((ncflags & NC_VIA_WAND_OR_SPELL) !=0), + /* "The oldmon turns into a newmon!" */ + msg = ((ncflags & NC_SHOW_MSG) != 0), + seenorsensed = canspotmon(mtmp); + int hpn, hpd, mndx, tryct; struct permonst *olddata = mtmp->data; - char *p, oldname[BUFSZ], l_oldname[BUFSZ], newname[BUFSZ]; + char *p, oldname[BUFSZ], l_oldname[BUFSZ]; /* Riders are immune to polymorph and green slime (but apparent Rider might actually be a doppelganger) */ @@ -3676,7 +5294,7 @@ boolean msg; /* "The oldmon turns into a newmon!" */ if (is_rider(olddata)) return 0; /* make Nazgul and erinyes immune too, to reduce chance of - anomalous extinction feedback during final disclsoure */ + anomalous extinction feedback during final disclosure */ if (mbirth_limit(monsndx(olddata)) < MAXMONNO) return 0; /* cancelled shapechangers become uncancelled prior @@ -3689,14 +5307,16 @@ boolean msg; /* "The oldmon turns into a newmon!" */ } if (msg) { - /* like Monnam() but never mention saddle */ - Strcpy(oldname, x_monnam(mtmp, ARTICLE_THE, (char *) 0, - SUPPRESS_SADDLE, FALSE)); + Strcpy(oldname, + /* like YMonnam() but never mention saddle */ + x_monnam(mtmp, mtmp->mtame ? ARTICLE_YOUR : ARTICLE_THE, + (char *) 0, SUPPRESS_SADDLE, FALSE)); oldname[0] = highc(oldname[0]); } /* we need this one whether msg is true or not */ Strcpy(l_oldname, x_monnam(mtmp, ARTICLE_THE, (char *) 0, - has_mname(mtmp) ? SUPPRESS_SADDLE : 0, FALSE)); + has_mgivenname(mtmp) ? SUPPRESS_SADDLE : 0, + FALSE)); /* mdat = 0 -> caller wants a random monster shape */ if (mdat == 0) { @@ -3709,14 +5329,14 @@ boolean msg; /* "The oldmon turns into a newmon!" */ /* for the first several tries we require upper-case on the rogue level (after that, we take whatever we get) */ if (tryct > 15 && Is_rogue_level(&u.uz) - && mdat && !isupper((uchar) mdat->mlet)) + && mdat && !isupper(monsym(mdat))) mdat = 0; if (mdat) break; } while (--tryct > 0); if (!tryct) return 0; - } else if (mvitals[monsndx(mdat)].mvflags & G_GENOD) + } else if (svm.mvitals[monsndx(mdat)].mvflags & G_GENOD) return 0; /* passed in mdat is genocided */ if (mdat == olddata) @@ -3729,28 +5349,30 @@ boolean msg; /* "The oldmon turns into a newmon!" */ * polymorphed, so dropping rank for mplayers seems reasonable. */ if (In_endgame(&u.uz) && is_mplayer(olddata) - && has_mname(mtmp) && (p = strstr(MNAME(mtmp), " the ")) != 0) + && has_mgivenname(mtmp) + && (p = strstr(MGIVENNAME(mtmp), " the ")) != 0) *p = '\0'; if (mtmp->wormno) { /* throw tail away */ - wormgone(mtmp); - place_monster(mtmp, mtmp->mx, mtmp->my); + coordxy mx = mtmp->mx, my = mtmp->my; + + wormgone(mtmp); /* discards tail segments, takes head off the map */ + /* put the head back; it will morph into mtmp's new form */ + place_monster(mtmp, mx, my); } if (M_AP_TYPE(mtmp) && mdat->mlet != S_MIMIC) seemimic(mtmp); /* revert to normal monster */ /* (this code used to try to adjust the monster's health based on a normal one of its type but there are too many special cases - which need to handled in order to do that correctly, so just + which need to be handled in order to do that correctly, so just give the new form the same proportion of HP as its old one had) */ hpn = mtmp->mhp; hpd = mtmp->mhpmax; /* set level and hit points */ newmonhp(mtmp, monsndx(mdat)); /* new hp: same fraction of max as before */ -#ifndef LINT mtmp->mhp = (int) (((long) hpn * (long) mtmp->mhp) / (long) hpd); -#endif /* sanity check (potential overflow) */ if (mtmp->mhp < 0 || mtmp->mhp > mtmp->mhpmax) mtmp->mhp = mtmp->mhpmax; @@ -3762,8 +5384,15 @@ boolean msg; /* "The oldmon turns into a newmon!" */ /* take on the new form... */ set_mon_data(mtmp, mdat); - if (mtmp->mleashed && !leashable(mtmp)) - m_unleash(mtmp, TRUE); + if (mtmp->mleashed) { + if (!leashable(mtmp)) + m_unleash(mtmp, TRUE); + else + /* if leashed, persistent inventory window needs updating + (really only when mon_nam() is going to yield "a frog" + rather than "Kermit" but no need to micromanage here) */ + update_inventory(); /* x - leash (attached to a ) */ + } if (emits_light(olddata) != emits_light(mtmp->data)) { /* used to give light, now doesn't, or vice versa, @@ -3783,19 +5412,18 @@ boolean msg; /* "The oldmon turns into a newmon!" */ if (u.uswallow) { if (!attacktype(mdat, AT_ENGL)) { /* Does mdat care? */ - if (!noncorporeal(mdat) && !amorphous(mdat) - && !is_whirly(mdat) && (mdat != &mons[PM_YELLOW_LIGHT])) { + if (!noncorporeal(mdat) && !is_whirly(mdat) + && !(amorphous(mdat) || mdat->mlet == S_LIGHT)) { char msgtrail[BUFSZ]; if (is_vampshifter(mtmp)) { Sprintf(msgtrail, " which was a shapeshifted %s", noname_monnam(mtmp, ARTICLE_NONE)); - } else if (is_animal(mdat)) { + } else if (digests(mdat)) { Strcpy(msgtrail, "'s stomach"); } else { msgtrail[0] = '\0'; } - /* Do this even if msg is FALSE */ You("%s %s%s!", (amorphous(olddata) || is_whirly(olddata)) @@ -3809,38 +5437,41 @@ boolean msg; /* "The oldmon turns into a newmon!" */ /* update swallow glyphs for new monster */ swallowed(0); } - } else if (!sticks(mdat) && !sticks(youmonst.data)) + } else if ((!sticks(mdat) && !sticks(gy.youmonst.data)) + /* sticky hero can't continue to hold mtmp if it has + turned into a non-solid creature; we don't use + uunstick() for that because its message would be + shown out of sequence [before 'if (msg)' below]; + unstuck() doesn't issue any messages */ + || unsolid(mdat)) { unstuck(mtmp); + } } -#ifndef DCC30_BUG if (mdat == &mons[PM_LONG_WORM] && (mtmp->wormno = get_wormno()) != 0) { -#else - /* DICE 3.0 doesn't like assigning and comparing mtmp->wormno in the - * same expression. - */ - if (mdat == &mons[PM_LONG_WORM] - && (mtmp->wormno = get_wormno(), mtmp->wormno != 0)) { -#endif - /* we can now create worms with tails - 11/91 */ initworm(mtmp, rn2(5)); place_worm_tail_randomly(mtmp, mtmp->mx, mtmp->my); } + mtmp->meverseen = 0; /* never seen mon in present shape; newsym() -> + * display_monster() may change it right back */ newsym(mtmp->mx, mtmp->my); if (msg) { - Strcpy(newname, noname_monnam(mtmp, ARTICLE_A)); - /* oldname was capitalized above; newname will be lower case */ - if (!strcmpi(newname, "it")) { /* can't see or sense it now */ - if (!!strcmpi(oldname, "it")) /* could see or sense it before */ - pline("%s disappears!", oldname); + /* oldname is capitalized and might be an assigned name */ + if (!canspotmon(mtmp)) { /* can't see or sense it now */ + if (seenorsensed) /* could see or sense it before */ + pline_mon(mtmp, "%s disappears!", oldname); (void) usmellmon(mdat); - } else { /* can see or sense it now */ - if (!strcmpi(oldname, "it")) /* couldn't see or sense it before */ - pline("%s appears!", upstart(newname)); - else - pline("%s turns into %s!", oldname, newname); + } else if (!seenorsensed) { /* couldn't see/sense before, can now */ + char *mnm = x_monnam(mtmp, mtmp->mtame ? ARTICLE_YOUR : ARTICLE_A, + (char *) 0, 0, FALSE); + + pline_mon(mtmp, "%s appears!", upstart(mnm)); + } else { /* saw/sensed it before, still see/sense it now */ + pline_mon(mtmp, "%s turns into %s!", oldname, + /* "a " even if it has a name assigned */ + noname_monnam(mtmp, ARTICLE_A)); } } @@ -3854,8 +5485,8 @@ boolean msg; /* "The oldmon turns into a newmon!" */ mon_break_armor(mtmp, polyspot); if (!(mtmp->misc_worn_check & W_ARMG)) mselftouch(mtmp, "No longer petrify-resistant, ", - !context.mon_moving); - m_dowear(mtmp, FALSE); + !svc.context.mon_moving); + check_gear_next_turn(mtmp); /* This ought to re-test can_carry() on each item in the inventory * rather than just checking ex-giants & boulders, but that'd be @@ -3864,9 +5495,11 @@ boolean msg; /* "The oldmon turns into a newmon!" */ */ /* former giants can't continue carrying boulders */ if (mtmp->minvent && !throws_rocks(mdat)) { - register struct obj *otmp, *otmp2; + struct obj *otmp, *otmp2; - for (otmp = mtmp->minvent; otmp; otmp = otmp2) { + /* DEADMONSTER(): it is possible for flooreffects() to kill mtmp; + the rest of its inventory would be dropped making otmp2 stale */ + for (otmp = mtmp->minvent; otmp && !DEADMONSTER(mtmp); otmp = otmp2) { otmp2 = otmp->nobj; if (otmp->otyp == BOULDER) { /* this keeps otmp from being polymorphed in the @@ -3881,6 +5514,22 @@ boolean msg; /* "The oldmon turns into a newmon!" */ } } } + if (mtmp == u.usteed) + poly_steed(mtmp, olddata); + + /* old form might not have been affected by Elbereth but perhaps the + new form is */ + if (svc.context.mon_moving) { + /* give 'mtmp' a new chance to pinpoint hero's location */ + if (!u_at(mtmp->mux, mtmp->muy)) + set_apparxy(mtmp); + /* if hero is on Elbereth or scare monster, mtmp in new form might + become scared */ + if (!mtmp->mpeaceful + && onscary(mtmp->mux, mtmp->muy, mtmp) + && monnear(mtmp, mtmp->mux, mtmp->muy)) + monflee(mtmp, rn1(9, 2), TRUE, TRUE); /* 2..10 turns */ + } return 1; } @@ -3894,8 +5543,7 @@ boolean msg; /* "The oldmon turns into a newmon!" */ * NON_PM if the given monster can't be hatched. */ int -can_be_hatched(mnum) -int mnum; +can_be_hatched(int mnum) { /* ranger quest nemesis has the oviparous bit set, making it be possible to wish for eggs of that unique monster; turn @@ -3918,9 +5566,9 @@ int mnum; /* type of egg laid by #sit; usually matches parent */ int -egg_type_from_parent(mnum, force_ordinary) -int mnum; /* parent monster; caller must handle lays_eggs() check */ -boolean force_ordinary; +egg_type_from_parent( + int mnum, /* parent monster; caller must handle lays_eggs() check */ + boolean force_ordinary) { if (force_ordinary || !BREEDER_EGG) { if (mnum == PM_QUEEN_BEE) @@ -3931,12 +5579,12 @@ boolean force_ordinary; return mnum; } +#undef BREEDER_EGG + /* decide whether an egg of the indicated monster type is viable; also used to determine whether an egg or tin can be created... */ boolean -dead_species(m_idx, egg) -int m_idx; -boolean egg; +dead_species(int m_idx, boolean egg) { int alt_idx; @@ -3950,15 +5598,15 @@ boolean egg; * fortunately, none of them have eggs. Species extinction due to * overpopulation does not kill eggs. */ + /* assert(ismnum(m_idx)); */ alt_idx = egg ? big_to_little(m_idx) : m_idx; - return (boolean) ((mvitals[m_idx].mvflags & G_GENOD) != 0 - || (mvitals[alt_idx].mvflags & G_GENOD) != 0); + return (boolean) ((svm.mvitals[m_idx].mvflags & G_GENOD) != 0 + || (svm.mvitals[alt_idx].mvflags & G_GENOD) != 0); } /* kill off any eggs of genocided monsters */ -STATIC_OVL void -kill_eggs(obj_list) -struct obj *obj_list; +staticfn void +kill_eggs(struct obj *obj_list) { struct obj *otmp; @@ -3988,7 +5636,7 @@ struct obj *obj_list; /* kill all members of genocided species */ void -kill_genocided_monsters() +kill_genocided_monsters(void) { struct monst *mtmp, *mtmp2; boolean kill_cham; @@ -4010,11 +5658,11 @@ kill_genocided_monsters() if (DEADMONSTER(mtmp)) continue; mndx = monsndx(mtmp->data); - kill_cham = (mtmp->cham >= LOW_PM - && (mvitals[mtmp->cham].mvflags & G_GENOD)); - if ((mvitals[mndx].mvflags & G_GENOD) || kill_cham) { - if (mtmp->cham >= LOW_PM && !kill_cham) - (void) newcham(mtmp, (struct permonst *) 0, FALSE, FALSE); + kill_cham = (ismnum(mtmp->cham) + && (svm.mvitals[mtmp->cham].mvflags & G_GENOD)); + if ((svm.mvitals[mndx].mvflags & G_GENOD) || kill_cham) { + if (ismnum(mtmp->cham) && !kill_cham) + (void) newcham(mtmp, (struct permonst *) 0, NC_SHOW_MSG); else mondead(mtmp); } @@ -4022,16 +5670,14 @@ kill_genocided_monsters() kill_eggs(mtmp->minvent); } - kill_eggs(invent); + kill_eggs(gi.invent); kill_eggs(fobj); - kill_eggs(migrating_objs); - kill_eggs(level.buriedobjlist); + kill_eggs(gm.migrating_objs); + kill_eggs(svl.level.buriedobjlist); } void -golemeffects(mon, damtype, dam) -register struct monst *mon; -int damtype, dam; +golemeffects(struct monst *mon, int damtype, int dam) { int heal = 0, slow = 0; @@ -4053,19 +5699,16 @@ int damtype, dam; mon_adjust_speed(mon, -1, (struct obj *) 0); } if (heal) { - if (mon->mhp < mon->mhpmax) { - mon->mhp += heal; - if (mon->mhp > mon->mhpmax) - mon->mhp = mon->mhpmax; + if (healmon(mon, heal, 0)) { if (cansee(mon->mx, mon->my)) - pline("%s seems healthier.", Monnam(mon)); + pline_mon(mon, "%s seems healthier.", Monnam(mon)); } } } +/* anger the Minetown watch */ boolean -angry_guards(silent) -boolean silent; +angry_guards(boolean silent) { struct monst *mtmp; int ct = 0, nct = 0, sct = 0, slct = 0; @@ -4075,8 +5718,8 @@ boolean silent; continue; if (is_watch(mtmp->data) && mtmp->mpeaceful) { ct++; - if (cansee(mtmp->mx, mtmp->my) && mtmp->mcanmove) { - if (distu(mtmp->mx, mtmp->my) == 2) + if (canspotmon(mtmp) && mtmp->mcanmove) { + if (m_next2u(mtmp)) nct++; else sct++; @@ -4090,41 +5733,47 @@ boolean silent; } if (ct) { if (!silent) { /* do we want pline msgs? */ - if (slct) - pline_The("guard%s wake%s up!", slct > 1 ? "s" : "", - slct == 1 ? "s" : ""); - if (nct || sct) { - if (nct) - pline_The("guard%s get%s angry!", nct == 1 ? "" : "s", - nct == 1 ? "s" : ""); - else if (!Blind) - You_see("%sangry guard%s approaching!", - sct == 1 ? "an " : "", sct > 1 ? "s" : ""); - } else - You_hear("the shrill sound of a guard's whistle."); + char buf[BUFSZ]; + + if (slct) { /* sleeping guard(s) */ + Sprintf(buf, "guard%s", plur(slct)); + pline_The("%s %s up.", buf, vtense(buf, "wake")); + } + + if (nct) { /* seen/sensed adjacent guard(s) */ + Sprintf(buf, "guard%s", plur(nct)); + pline_The("%s %s angry!", buf, vtense(buf, "get")); + } else if (sct) { /* seen/sensed non-adjacent guard(s) */ + Sprintf(buf, "guard%s", plur(sct)); + pline("%s %s %s approaching!", + (sct == 1) ? "An angry" : "Angry", + buf, vtense(buf, "are")); + } else { + Strcpy(buf, (ct == 1) ? "a guard's" : "guards'"); + Soundeffect(se_shrill_whistle, 100); + You_hear("the shrill sound of %s whistle%s.", buf, plur(ct)); + } } return TRUE; } return FALSE; } -void -pacify_guards() +staticfn void +pacify_guard(struct monst *mtmp) { - struct monst *mtmp; + if (is_watch(mtmp->data)) + mtmp->mpeaceful = 1; +} - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) - continue; - if (is_watch(mtmp->data)) - mtmp->mpeaceful = 1; - } +void +pacify_guards(void) +{ + iter_mons(pacify_guard); } void -mimic_hit_msg(mtmp, otyp) -struct monst *mtmp; -short otyp; +mimic_hit_msg(struct monst *mtmp, short otyp) { short ap = mtmp->mappearance; @@ -4135,7 +5784,7 @@ short otyp; break; case M_AP_OBJECT: if (otyp == SPE_HEALING || otyp == SPE_EXTRA_HEALING) { - pline("%s seems a more vivid %s than before.", + pline_mon(mtmp, "%s seems a more vivid %s than before.", The(simple_typename(ap)), c_obj_colors[objects[ap].oc_color]); } @@ -4144,15 +5793,14 @@ short otyp; } boolean -usmellmon(mdat) -struct permonst *mdat; +usmellmon(struct permonst *mdat) { int mndx; boolean nonspecific = FALSE; boolean msg_given = FALSE; if (mdat) { - if (!olfaction(youmonst.data)) + if (!olfaction(gy.youmonst.data)) return FALSE; mndx = monsndx(mdat); switch (mndx) { @@ -4161,8 +5809,7 @@ struct permonst *mdat; You("notice a bovine smell."); msg_given = TRUE; break; - case PM_CAVEMAN: - case PM_CAVEWOMAN: + case PM_CAVE_DWELLER: case PM_BARBARIAN: case PM_NEANDERTHAL: You("smell body odor."); @@ -4248,7 +5895,7 @@ struct permonst *mdat; msg_given = TRUE; break; case S_ORC: - if (maybe_polyd(is_orc(youmonst.data), Race_if(PM_ORC))) + if (maybe_polyd(is_orc(gy.youmonst.data), Race_if(PM_ORC))) You("notice an attractive smell."); else pline("A foul stench makes you feel a little nauseated."); @@ -4261,4 +5908,181 @@ struct permonst *mdat; return msg_given ? TRUE : FALSE; } +/* setting misc_worn_check's I_SPECIAL bit flags a monster to reassess + and potentially re-equip gear at the start of its next move; + this hides the details of that */ +void +check_gear_next_turn(struct monst *mon) +{ + mon->misc_worn_check |= I_SPECIAL; +} + +/* make erinyes more dangerous based on your alignment abuse */ +void +adj_erinys(unsigned abuse) +{ + struct permonst *pm = &mons[PM_ERINYS]; + + if (abuse > 5L) { + pm->mflags1 |= M1_SEE_INVIS; + } + if (abuse > 10L) { + pm->mflags1 |= M1_AMPHIBIOUS; + } + if (abuse > 15L) { + pm->mflags1 |= M1_FLY; + } + if (abuse > 20L) { + /* more powerful attack */ + pm->mattk[0].damn = 3; + } + if (abuse > 25L) { + pm->mflags1 |= M1_REGEN; + } + if (abuse > 30L) { + pm->mflags1 |= M1_TPORT_CNTRL; + } + if (abuse > 35L) { + /* second attack */ + pm->mattk[1].aatyp = AT_WEAP; + pm->mattk[1].adtyp = AD_DRST; + pm->mattk[1].damn = 3; + pm->mattk[1].damd = 4; + } + if (abuse > 40L) { + pm->mflags1 |= M1_TPORT; + } + if (abuse > 50L) { + /* third (spellcasting) attack */ + pm->mattk[2].aatyp = AT_MAGC; + pm->mattk[2].adtyp = AD_SPEL; + pm->mattk[2].damn = 3; + pm->mattk[2].damd = 4; + } + + /* also adjust level and difficulty */ + pm->mlevel = min(7 + u.ualign.abuse, 50); + pm->difficulty = min(10 + (u.ualign.abuse / 3), 25); +} + +/* mark individual monster type as seen from close-up, + if we haven't seen it nearby before */ +void +see_monster_closeup(struct monst *mtmp, boolean photo) +{ + int mndx; + + if (Hallucination || (Blind && !Blind_telepat)) + return; + + mndx = monsndx(mtmp->data); + if (M_AP_TYPE(mtmp) == M_AP_MONSTER && !sensemon(mtmp)) + mndx = mtmp->mappearance; + if (mndx == PM_LONG_WORM && gn.notonhead) + mndx = PM_LONG_WORM_TAIL; + + if (!svm.mvitals[mndx].seen_close) { + svm.mvitals[mndx].seen_close = 1; + svc.context.lifelist.total_seen_upclose++; + } + + /* hallucinatory monsters don't reach here--they're not recorded; + being able to see invisible doesn't make invisible monsters show up + on photos; likewise, telepathy allows hero to see hidden monsters + but doesn't cause them to appear on photos */ + if (photo && !mtmp->minvis && !mtmp->mundetected + && (M_AP_TYPE(mtmp) == M_AP_NOTHING + || M_AP_TYPE(mtmp) == M_AP_MONSTER)) { + if (M_AP_TYPE(mtmp) == M_AP_MONSTER) /* cloned Wizard of Yendor */ + mndx = mtmp->mappearance; + + if (!svm.mvitals[mndx].photographed) { + svm.mvitals[mndx].photographed = 1; + svc.context.lifelist.total_photographed++; + + /* tourist earns points (toward EXP but not final score) for + the first instance of each type of monster photographed; + worm tail can be photographed but yields no EXP bonus */ + if (Role_if(PM_TOURIST) + /* suppress extra points for photographing the pet that hero + started with (unless it has changed shape due to growing + up or being polymorphed) */ + && (mtmp->m_id != svc.context.startingpet_mid + || mndx != svc.context.startingpet_typ) + /* monsndx() check covers worm tail and also disguised + Wizard of Yendor; experienced() won't yield a reasonable + value for those */ + && mndx == monsndx(mtmp->data)) { + more_experienced(experience(mtmp, 0), 0); + newexplevel(); + } + } + } +} + +/* mark a monster type as seen close-up when we see it next to us */ +void +see_nearby_monsters(void) +{ + struct monst *mtmp; + int mndx; + coordxy x, y; + + if (Hallucination || (Blind && !Blind_telepat)) + return; + + for (x = u.ux - 1; x <= u.ux + 1; x++) + for (y = u.uy - 1; y <= u.uy + 1; y++) { + if (!isok(x, y)) + continue; + if (!(mtmp = m_at(x, y))) + continue; + mndx = monsndx(mtmp->data); + if (M_AP_TYPE(mtmp) == M_AP_MONSTER) + mndx = mtmp->mappearance; + /* skip closeup handling if this mon type has already been done */ + if (svm.mvitals[mndx].seen_close) + continue; + /* disguised mimics pass canseemon(); undetected hiders don't */ + if (canseemon(mtmp) || (mtmp->mundetected && sensemon(mtmp))) { + gb.bhitpos.x = x, gb.bhitpos.y = y; + gn.notonhead = (x != mtmp->mx || y != mtmp->my); + see_monster_closeup(mtmp, FALSE); + } + } +} + +/* monster resists something. + make a shield effect at monster's location and give a message */ +void +shieldeff_mon(struct monst *mtmp) +{ + shieldeff(mtmp->mx, mtmp->my); + /* does not depend on seeing the monster; the shield effect is visible */ + if (cansee(mtmp->mx, mtmp->my)) + pline_mon(mtmp, "%s resists!", Monnam(mtmp)); +} + +void +flash_mon(struct monst *mtmp) +{ + coordxy mx = mtmp->mx, my = mtmp->my; + int count = couldsee(mx, my) ? 8 : 4; + char saveviz = gv.viz_array[my][mx]; + + if (!flags.sparkle) + count /= 2; + gv.viz_array[my][mx] |= (IN_SIGHT | COULD_SEE); + flash_glyph_at(mx, my, mon_to_glyph(mtmp, newsym_rn2), count); + gv.viz_array[my][mx] = saveviz; + newsym(mx, my); +} + +/* cleanup for 'onefile' processing */ +#undef LEVEL_SPECIFIC_NOCORPSE +#undef KEEPTRAITS +#undef mstoning +#undef livelog_mon_nam +#undef BREEDER_EGG + /*mon.c*/ diff --git a/src/mondata.c b/src/mondata.c index fae34e8b7..d7be05a18 100644 --- a/src/mondata.c +++ b/src/mondata.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 mondata.c $NHDT-Date: 1550525093 2019/02/18 21:24:53 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.72 $ */ +/* NetHack 5.0 mondata.c $NHDT-Date: 1738638877 2025/02/03 19:14:37 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.140 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2011. */ /* NetHack may be freely redistributed. See license for details. */ @@ -10,16 +10,15 @@ /* set up an individual monster's base type (initial creation, shapechange) */ void -set_mon_data(mon, ptr) -struct monst *mon; -struct permonst *ptr; +set_mon_data(struct monst *mon, struct permonst *ptr) { int new_speed, old_speed = mon->data ? mon->data->mmove : 0; + short *movement_p = (mon == &gy.youmonst) ? &u.umovement : &mon->movement; mon->data = ptr; mon->mnum = (short) monsndx(ptr); - if (mon->movement) { /* used to adjust poly'd hero as well as monsters */ + if (*movement_p) { /* used to adjust poly'd hero as well as monsters */ new_speed = ptr->mmove; /* prorate unused movement if new form is slower so that it doesn't get extra moves leftover from previous form; @@ -30,9 +29,9 @@ struct permonst *ptr; mon->movement = new_speed * mon->movement / old_speed; * so add a redundant test to suppress that. */ - mon->movement *= new_speed; + *movement_p *= new_speed; if (old_speed > 0) /* old > new and new >= 0, so always True */ - mon->movement /= old_speed; + *movement_p /= old_speed; } } return; @@ -40,9 +39,7 @@ struct permonst *ptr; /* does monster-type have any attack for a specific type of damage? */ struct attack * -attacktype_fordmg(ptr, atyp, dtyp) -struct permonst *ptr; -int atyp, dtyp; +attacktype_fordmg(struct permonst *ptr, int atyp, int dtyp) { struct attack *a; @@ -54,17 +51,14 @@ int atyp, dtyp; /* does monster-type have a particular type of attack */ boolean -attacktype(ptr, atyp) -struct permonst *ptr; -int atyp; +attacktype(struct permonst *ptr, int atyp) { return attacktype_fordmg(ptr, atyp, AD_ANY) ? TRUE : FALSE; } /* returns True if monster doesn't attack, False if it does */ boolean -noattacks(ptr) -struct permonst *ptr; +noattacks(struct permonst *ptr) { int i; struct attack *mattk = ptr->mattk; @@ -83,39 +77,145 @@ struct permonst *ptr; /* does monster-type transform into something else when petrified? */ boolean -poly_when_stoned(ptr) -struct permonst *ptr; +poly_when_stoned(struct permonst *ptr) { /* non-stone golems turn into stone golems unless latter is genocided */ return (boolean) (is_golem(ptr) && ptr != &mons[PM_STONE_GOLEM] - && !(mvitals[PM_STONE_GOLEM].mvflags & G_GENOD)); + && !(svm.mvitals[PM_STONE_GOLEM].mvflags & G_GENOD)); /* allow G_EXTINCT */ } +/* is 'mon' (possibly youmonst) protected against damage type 'adtype' via + wielded weapon or worn dragon scales? [or by virtue of being a dragon?] */ +boolean +defended(struct monst *mon, int adtyp) +{ + struct obj *o, otemp; + int mndx; + boolean is_you = (mon == &gy.youmonst); + + /* is 'mon' wielding an artifact that protects against 'adtyp'? */ + o = is_you ? uwep : MON_WEP(mon); + if (o && o->oartifact && defends(adtyp, o)) + return TRUE; + + /* if 'mon' is an adult dragon, treat it as if it was wearing scales + so that it has the same benefit as a hero wearing dragon scales */ + mndx = monsndx(mon->data); + if (mndx >= PM_GRAY_DRAGON && mndx <= PM_YELLOW_DRAGON) { + /* a dragon is its own suit... if mon is poly'd hero, we don't + care about embedded scales (uskin) because being a dragon with + embedded scales is no better than just being a dragon */ + otemp = cg.zeroobj; + otemp.oclass = ARMOR_CLASS; + otemp.otyp = GRAY_DRAGON_SCALES + (mndx - PM_GRAY_DRAGON); + /* defends() and Is_dragon_armor() only care about otyp so ignore + the rest of otemp's fields */ + o = &otemp; + } else { + /* ordinary case: not an adult dragon */ + o = is_you ? uarm : which_armor(mon, W_ARM); + } + /* is 'mon' wearing dragon scales that protect against 'adtyp'? */ + if (o && Is_dragon_armor(o) && defends(adtyp, o)) + return TRUE; + + return FALSE; +} + +/* returns True if monster resists particular elemental damage; + handles 'carry' effects of artifacts as well as worn/wielded items */ +boolean +Resists_Elem(struct monst *mon, int propindx) +{ + struct obj *o; + long slotmask; + boolean is_you = (mon == &gy.youmonst); + int u_resist = 0, damgtype = 0, rsstmask = 0; + + /* + * Main damage/resistance types, mostly matching dragon breath values. + * propindx = property index, fire (1), cold, (2) through stone (8); + * damgtype = damage type, 2 through 9 (0 and 1 aren't used here); + * rsstmask = resistance mask, 1, 2, 4, ..., 64, 128. + */ + + switch (propindx) { + case FIRE_RES: /* 1 */ + case COLD_RES: /* 2 */ + case SLEEP_RES: /* 3 */ + case DISINT_RES: /* 4 */ + case SHOCK_RES: /* 5 */ + case POISON_RES: /* 6 */ + case ACID_RES: /* 7 */ + case STONE_RES: /* 8 */ + damgtype = propindx + 1; /* valid for propindx 1..8, damgtype 2..9 */ + rsstmask = 1 << (propindx - 1); /* valid for propindx 1..8 */ + u_resist = u.uprops[propindx].intrinsic + || u.uprops[propindx].extrinsic; + break; + + /* accept these, but we expect callers to use their routines directly */ + case ANTIMAGIC: + return resists_magm(mon); + case DRAIN_RES: + return resists_drli(mon); + case BLND_RES: + return resists_blnd(mon); + + default: + impossible("Resists_Elem(%d), unexpected property type", propindx); + return FALSE; + } + + if (is_you ? u_resist : ((mon_resistancebits(mon) & rsstmask) != 0)) + return TRUE; + /* check for resistance granted by wielded weapon */ + o = is_you ? uwep : MON_WEP(mon); + if (o && o->oartifact && defends(damgtype, o)) + return TRUE; + /* check for resistance granted by worn or carried items */ + o = is_you ? gi.invent : mon->minvent; + slotmask = W_ARMOR | W_ACCESSORY; + if (!is_you /* assumes monsters don't wield non-weapons */ + || (uwep && (uwep->oclass == WEAPON_CLASS || is_weptool(uwep)))) + slotmask |= W_WEP; + if (is_you && u.twoweap) + slotmask |= W_SWAPWEP; + for (; o; o = o->nobj) + if (((o->owornmask & slotmask) != 0L + && objects[o->otyp].oc_oprop == propindx) + || ((o->owornmask & W_ARMC) == W_ARMC + /* worn apron confers a pair of resistances but + objects[ALCHEMY_SMOCK].oc_oprop can only represent one; + we check both so won't need to know which one that is */ + && o->otyp == ALCHEMY_SMOCK + && (propindx == POISON_RES || propindx == ACID_RES)) + || (o->oartifact && defends_when_carried(damgtype, o))) + return TRUE; + return FALSE; +} + /* returns True if monster is drain-life resistant */ boolean -resists_drli(mon) -struct monst *mon; +resists_drli(struct monst *mon) { struct permonst *ptr = mon->data; - struct obj *wep; if (is_undead(ptr) || is_demon(ptr) || is_were(ptr) /* is_were() doesn't handle hero in human form */ - || (mon == &youmonst && u.ulycn >= LOW_PM) + || (mon == &gy.youmonst && u.ulycn >= LOW_PM) || ptr == &mons[PM_DEATH] || is_vampshifter(mon)) return TRUE; - wep = (mon == &youmonst) ? uwep : MON_WEP(mon); - return (boolean) (wep && wep->oartifact && defends(AD_DRLI, wep)); + return defended(mon, AD_DRLI); } /* True if monster is magic-missile (actually, general magic) resistant */ boolean -resists_magm(mon) -struct monst *mon; +resists_magm(struct monst *mon) { struct permonst *ptr = mon->data; - boolean is_you = (mon == &youmonst); + boolean is_you = (mon == &gy.youmonst); long slotmask; struct obj *o; @@ -128,7 +228,7 @@ struct monst *mon; if (o && o->oartifact && defends(AD_MAGM, o)) return TRUE; /* check for magic resistance granted by worn or carried items */ - o = is_you ? invent : mon->minvent; + o = is_you ? gi.invent : mon->minvent; slotmask = W_ARMOR | W_ACCESSORY; if (!is_you /* assumes monsters don't wield non-weapons */ || (uwep && (uwep->oclass == WEAPON_CLASS || is_weptool(uwep)))) @@ -143,15 +243,12 @@ struct monst *mon; return FALSE; } -/* True iff monster is resistant to light-induced blindness */ +/* True if monster is resistant to light-induced blindness */ boolean -resists_blnd(mon) -struct monst *mon; +resists_blnd(struct monst *mon) { struct permonst *ptr = mon->data; - boolean is_you = (mon == &youmonst); - long slotmask; - struct obj *o; + boolean is_you = (mon == &gy.youmonst); if (is_you ? (Blind || Unaware) : (mon->mblinded || !mon->mcansee || !haseyes(ptr) @@ -163,42 +260,73 @@ struct monst *mon; if (dmgtype_fromattack(ptr, AD_BLND, AT_EXPL) || dmgtype_fromattack(ptr, AD_BLND, AT_GAZE)) return TRUE; + /* Sunsword */ + if (resists_blnd_by_arti(mon)) + return TRUE; + /* catchall */ + if (is_you && Blnd_resist) { + impossible("'Blnd_resist' but not resists_blnd()?"); + return TRUE; + } + return FALSE; +} + +/* True iff monster is resistant to light-induced blindness due to worn + or wielded magical equipment (used to decide whether to show sparkle + animation when resisting) */ +boolean +resists_blnd_by_arti(struct monst *mon) +{ + struct obj *o; + boolean is_you = (mon == &gy.youmonst); + o = is_you ? uwep : MON_WEP(mon); if (o && o->oartifact && defends(AD_BLND, o)) return TRUE; - o = is_you ? invent : mon->minvent; - slotmask = W_ARMOR | W_ACCESSORY; - if (!is_you /* assumes monsters don't wield non-weapons */ - || (uwep && (uwep->oclass == WEAPON_CLASS || is_weptool(uwep)))) - slotmask |= W_WEP; - if (is_you && u.twoweap) - slotmask |= W_SWAPWEP; + o = is_you ? gi.invent : mon->minvent; for (; o; o = o->nobj) - if (((o->owornmask & slotmask) != 0L - && objects[o->otyp].oc_oprop == BLINDED) - || (o->oartifact && defends_when_carried(AD_BLND, o))) + if (defends_when_carried(AD_BLND, o)) return TRUE; +#if 0 /* omit this; the Eyes of the Overworld have no carry property and + * their worn property is magic resistance rather than blindness + * resistance; wearing them blocks blindness without actually + * preventing it, so don't classify them as providing resistance */ + if (is_you && is_art(uamul, ART_EYES_OF_THE_OVERWORLD)) + return TRUE; +#endif /* 0 */ return FALSE; } /* True iff monster can be blinded by the given attack; - note: may return True when mdef is blind (e.g. new cream-pie attack) */ + note: may return True when mdef is blind (e.g. new cream-pie attack) + magr can be NULL. +*/ boolean -can_blnd(magr, mdef, aatyp, obj) -struct monst *magr; /* NULL == no specific aggressor */ -struct monst *mdef; -uchar aatyp; -struct obj *obj; /* aatyp == AT_WEAP, AT_SPIT */ +can_blnd( + struct monst *magr, /* NULL == no specific aggressor */ + struct monst *mdef, + uchar aatyp, + struct obj *obj) /* aatyp == AT_WEAP, AT_SPIT */ { - boolean is_you = (mdef == &youmonst); + boolean is_you = (mdef == &gy.youmonst); boolean check_visor = FALSE; struct obj *o; - const char *s; /* no eyes protect against all attacks for now */ if (!haseyes(mdef->data)) return FALSE; + /* if monster has been permanently blinded, the deed is already done */ + if (!is_you && mon_perma_blind(mdef)) + return FALSE; + + /* /corvus oculum corvi non eruit/ + a saying expressed in Latin rather than a zoological observation: + "a crow will not pluck out the eye of another crow" + so prevent ravens from blinding each other */ + if (magr && magr->data == &mons[PM_RAVEN] && mdef->data == &mons[PM_RAVEN]) + return FALSE; + switch (aatyp) { case AT_EXPL: case AT_BOOM: @@ -226,7 +354,7 @@ struct obj *obj; /* aatyp == AT_WEAP, AT_SPIT */ return TRUE; /* no defense */ } else return FALSE; /* other objects cannot cause blindness yet */ - if ((magr == &youmonst) && u.uswallow) + if ((magr == &gy.youmonst) && u.uswallow) return FALSE; /* can't affect eyes while inside monster */ break; @@ -241,7 +369,7 @@ struct obj *obj; /* aatyp == AT_WEAP, AT_SPIT */ /* e.g. raven: all ublindf, including LENSES, protect */ if (is_you && ublindf) return FALSE; - if ((magr == &youmonst) && u.uswallow) + if ((magr == &gy.youmonst) && u.uswallow) return FALSE; /* can't affect eyes while inside monster */ check_visor = TRUE; break; @@ -259,11 +387,10 @@ struct obj *obj; /* aatyp == AT_WEAP, AT_SPIT */ /* check if wearing a visor (only checked if visor might help) */ if (check_visor) { - o = (mdef == &youmonst) ? invent : mdef->minvent; + o = (mdef == &gy.youmonst) ? gi.invent : mdef->minvent; for (; o; o = o->nobj) if ((o->owornmask & W_ARMH) - && (s = OBJ_DESCR(objects[o->otyp])) != (char *) 0 - && !strcmp(s, "visored helmet")) + && objdescr_is(o, "visored helmet")) return FALSE; } @@ -272,85 +399,187 @@ struct obj *obj; /* aatyp == AT_WEAP, AT_SPIT */ /* returns True if monster can attack at range */ boolean -ranged_attk(ptr) -struct permonst *ptr; +ranged_attk(struct permonst *ptr) { - register int i, atyp; - long atk_mask = (1L << AT_BREA) | (1L << AT_SPIT) | (1L << AT_GAZE); + int i; - /* was: (attacktype(ptr, AT_BREA) || attacktype(ptr, AT_WEAP) - * || attacktype(ptr, AT_SPIT) || attacktype(ptr, AT_GAZE) - * || attacktype(ptr, AT_MAGC)); - * but that's too slow -dlc - */ - for (i = 0; i < NATTK; i++) { - atyp = ptr->mattk[i].aatyp; - if (atyp >= AT_WEAP) + for (i = 0; i < NATTK; i++) + if (DISTANCE_ATTK_TYPE(ptr->mattk[i].aatyp)) return TRUE; - /* assert(atyp < 32); */ - if ((atk_mask & (1L << atyp)) != 0L) + return FALSE; +} + +#if defined(MAKEDEFS_C) \ + || (NH_DEVEL_STATUS != NH_STATUS_RELEASED) || defined(DEBUG) +/* + * If adding a new monster, include a guestimate for difficulty, + * build the program, then run it in wizard mode and use the + * #mondifficulty command. If it reports a discrepancy, update + * the monsters array with the more accurate value (or possibly + * modify the 'mstrength()' algorithm to generate the guessed one). + */ +static boolean mstrength_ranged_attk(struct permonst *); + + +/* This routine is designed to return an integer value which represents + an approximation of monster strength. It uses a similar method of + determination as "experience()" to arrive at the strength. */ +int +mstrength(struct permonst *ptr) +{ + int i, tmp2, n, tmp = ptr->mlevel; + + if (tmp > 49) /* special fixed hp monster */ + tmp = 2 * (tmp - 6) / 4; + + /* for creation in groups */ + n = (!!(ptr->geno & G_SGROUP)); + n += (!!(ptr->geno & G_LGROUP)) << 1; + + /* for ranged attacks */ + if (mstrength_ranged_attk(ptr)) + n++; + + /* for higher ac values */ + n += (ptr->ac < 4); + n += (ptr->ac < 0); + + /* for very fast monsters */ + n += (ptr->mmove >= 18); + + /* for each attack and "special" attack */ + for (i = 0; i < NATTK; i++) { + tmp2 = ptr->mattk[i].aatyp; + n += (tmp2 > 0); + n += (tmp2 == AT_MAGC); + n += (tmp2 == AT_WEAP && (ptr->mflags2 & M2_STRONG)); + if (tmp2 == AT_EXPL) { + int tmp3 = ptr->mattk[i].adtyp; + /* {freezing,flaming,shocking} spheres are fairly weak but + can destroy equipment; {yellow,black} lights can't */ + n += ((tmp3 == AD_COLD || tmp3 == AD_FIRE) ? 3 + : (tmp3 == AD_ELEC) ? 5 + : 0); + } + } + + /* for each "special" damage type */ + for (i = 0; i < NATTK; i++) { + tmp2 = ptr->mattk[i].adtyp; + if ((tmp2 == AD_DRLI) || (tmp2 == AD_STON) || (tmp2 == AD_DRST) + || (tmp2 == AD_DRDX) || (tmp2 == AD_DRCO) || (tmp2 == AD_WERE)) + n += 2; + else if (strcmp(ptr->pmnames[NEUTRAL], "grid bug")) + n += (tmp2 != AD_PHYS); + n += ((int) (ptr->mattk[i].damd * ptr->mattk[i].damn) > 23); + } + + /* Leprechauns are a special case. They have many hit dice so they can + hit and are hard to kill, but they don't really do much damage. */ + if (!strcmp(ptr->pmnames[NEUTRAL], "leprechaun")) + n -= 2; + + /* despite group and poison increments, soldier ants and killer bees are + underestimated by the formula, so have an artificial +1 difficulty */ + if (!strcmp(ptr->pmnames[NEUTRAL], "killer bee") || + !strcmp(ptr->pmnames[NEUTRAL], "soldier ant")) + n += 2; /* +1 after 'tmp += n/2' below */ + + /* finally, adjust the monster level 0 <= n <= 24 (approx.) */ + if (n == 0) + tmp -= 1; + else if (n < 6) + tmp += (n / 3 + 1); + else + tmp += (n / 2); + + return (tmp >= 0) ? tmp : 0; +} + +/* returns True if monster can attack at range */ +staticfn boolean +mstrength_ranged_attk(struct permonst *ptr) +{ + int i, j; + int atk_mask = (1 << AT_BREA) | (1 << AT_SPIT) | (1 << AT_GAZE); + + for (i = 0; i < NATTK; i++) { + if ((j = ptr->mattk[i].aatyp) >= AT_WEAP + || (j < 32 && (atk_mask & (1 << j)) != 0)) return TRUE; } return FALSE; } +#endif /* (NH_DEVEL_STATUS != NH_STATUS_RELEASED) || DEBUG || MAKEDEFS_C */ /* True if specific monster is especially affected by silver weapons */ boolean -mon_hates_silver(mon) -struct monst *mon; +mon_hates_silver(struct monst *mon) { return (boolean) (is_vampshifter(mon) || hates_silver(mon->data)); } /* True if monster-type is especially affected by silver weapons */ boolean -hates_silver(ptr) -register struct permonst *ptr; +hates_silver(struct permonst *ptr) { return (boolean) (is_were(ptr) || ptr->mlet == S_VAMPIRE || is_demon(ptr) || ptr == &mons[PM_SHADE] || (ptr->mlet == S_IMP && ptr != &mons[PM_TENGU])); } +/* True if specific monster is especially affected by blessed objects */ +boolean +mon_hates_blessings(struct monst *mon) +{ + return (boolean) (is_vampshifter(mon) || hates_blessings(mon->data)); +} + +/* True if monster-type is especially affected by blessed objects */ +boolean +hates_blessings(struct permonst *ptr) +{ + return (boolean) (is_undead(ptr) || is_demon(ptr)); +} + /* True if specific monster is especially affected by light-emitting weapons */ boolean -mon_hates_light(mon) -struct monst *mon; +mon_hates_light(struct monst *mon) { - return (boolean) (hates_light(mon->data)); + return (boolean) hates_light(mon->data); } /* True iff the type of monster pass through iron bars */ boolean -passes_bars(mptr) -struct permonst *mptr; +passes_bars(struct permonst *mptr) { return (boolean) (passes_walls(mptr) || amorphous(mptr) || unsolid(mptr) || is_whirly(mptr) || verysmall(mptr) - || dmgtype(mptr, AD_CORR) || dmgtype(mptr, AD_RUST) + /* rust monsters and some puddings can destroy bars */ + || dmgtype(mptr, AD_RUST) || dmgtype(mptr, AD_CORR) + /* rock moles can eat bars */ + || metallivorous(mptr) || (slithy(mptr) && !bigmonst(mptr))); } /* returns True if monster can blow (whistle, etc) */ boolean -can_blow(mtmp) -struct monst *mtmp; +can_blow(struct monst *mtmp) { if ((is_silent(mtmp->data) || mtmp->data->msound == MS_BUZZ) && (breathless(mtmp->data) || verysmall(mtmp->data) || !has_head(mtmp->data) || mtmp->data->mlet == S_EEL)) return FALSE; - if ((mtmp == &youmonst) && Strangled) + if ((mtmp == &gy.youmonst) && Strangled) return FALSE; return TRUE; } /* for casting spells and reading scrolls while blind */ boolean -can_chant(mtmp) -struct monst *mtmp; +can_chant(struct monst *mtmp) { - if ((mtmp == &youmonst && Strangled) + if ((mtmp == &gy.youmonst && Strangled) || is_silent(mtmp->data) || !has_head(mtmp->data) || mtmp->data->msound == MS_BUZZ || mtmp->data->msound == MS_BURBLE) return FALSE; @@ -359,8 +588,7 @@ struct monst *mtmp; /* True if mon is vulnerable to strangulation */ boolean -can_be_strangled(mon) -struct monst *mon; +can_be_strangled(struct monst *mon) { struct obj *mamul; boolean nonbreathing, nobrainer; @@ -374,10 +602,10 @@ struct monst *mon; are non-breathing creatures which have higher brain function. */ if (!has_head(mon->data)) return FALSE; - if (mon == &youmonst) { + if (mon == &gy.youmonst) { /* hero can't be mindless but poly'ing into mindless form can confer strangulation protection */ - nobrainer = mindless(youmonst.data); + nobrainer = mindless(gy.youmonst.data); nonbreathing = Breathless; } else { nobrainer = mindless(mon->data); @@ -392,19 +620,16 @@ struct monst *mon; /* returns True if monster can track well */ boolean -can_track(ptr) -register struct permonst *ptr; +can_track(struct permonst *ptr) { - if (uwep && uwep->oartifact == ART_EXCALIBUR) + if (u_wield_art(ART_EXCALIBUR)) return TRUE; - else - return (boolean) haseyes(ptr); + return (boolean) haseyes(ptr); } /* creature will slide out of armor */ boolean -sliparm(ptr) -register struct permonst *ptr; +sliparm(struct permonst *ptr) { return (boolean) (is_whirly(ptr) || ptr->msize <= MZ_SMALL || noncorporeal(ptr)); @@ -412,8 +637,7 @@ register struct permonst *ptr; /* creature will break out of armor */ boolean -breakarm(ptr) -register struct permonst *ptr; +breakarm(struct permonst *ptr) { if (sliparm(ptr)) return FALSE; @@ -427,30 +651,31 @@ register struct permonst *ptr; /* creature sticks other creatures it hits */ boolean -sticks(ptr) -register struct permonst *ptr; +sticks(struct permonst *ptr) { - return (boolean) (dmgtype(ptr, AD_STCK) || dmgtype(ptr, AD_WRAP) + return (boolean) (dmgtype(ptr, AD_STCK) + || (dmgtype(ptr, AD_WRAP) && !attacktype(ptr, AT_ENGL)) || attacktype(ptr, AT_HUGS)); } /* some monster-types can't vomit */ boolean -cantvomit(ptr) -struct permonst *ptr; +cantvomit(struct permonst *ptr) { - /* rats and mice are incapable of vomiting; + /* rats and mice are incapable of vomiting; likewise with horses; which other creatures have the same limitation? */ if (ptr->mlet == S_RODENT && ptr != &mons[PM_ROCK_MOLE] && ptr != &mons[PM_WOODCHUCK]) return TRUE; + if (ptr == &mons[PM_WARHORSE] || ptr == &mons[PM_HORSE] + || ptr == &mons[PM_PONY]) + return TRUE; return FALSE; } /* number of horns this type of monster has on its head */ int -num_horns(ptr) -struct permonst *ptr; +num_horns(struct permonst *ptr) { switch (monsndx(ptr)) { case PM_HORNED_DEVIL: /* ? "more than one" */ @@ -472,9 +697,7 @@ struct permonst *ptr; /* does monster-type deal out a particular type of damage from a particular type of attack? */ struct attack * -dmgtype_fromattack(ptr, dtyp, atyp) -struct permonst *ptr; -int dtyp, atyp; +dmgtype_fromattack(struct permonst *ptr, int dtyp, int atyp) { struct attack *a; @@ -486,9 +709,7 @@ int dtyp, atyp; /* does monster-type deal out a particular type of damage from any attack */ boolean -dmgtype(ptr, dtyp) -struct permonst *ptr; -int dtyp; +dmgtype(struct permonst *ptr, int dtyp) { return dmgtype_fromattack(ptr, dtyp, AT_ANY) ? TRUE : FALSE; } @@ -496,10 +717,9 @@ int dtyp; /* returns the maximum damage a defender can do to the attacker via a passive defense */ int -max_passive_dmg(mdef, magr) -register struct monst *mdef, *magr; +max_passive_dmg(struct monst *mdef, struct monst *magr) { - int i, dmg = 0, multi2 = 0; + int i, dmg, multi2 = 0; uchar adtyp; /* each attack by magr can result in passive damage */ @@ -521,31 +741,34 @@ register struct monst *mdef, *magr; break; } + dmg = 0; for (i = 0; i < NATTK; i++) if (mdef->data->mattk[i].aatyp == AT_NONE || mdef->data->mattk[i].aatyp == AT_BOOM) { adtyp = mdef->data->mattk[i].adtyp; - if ((adtyp == AD_ACID && !resists_acid(magr)) - || (adtyp == AD_COLD && !resists_cold(magr)) - || (adtyp == AD_FIRE && !resists_fire(magr)) - || (adtyp == AD_ELEC && !resists_elec(magr)) - || adtyp == AD_PHYS) { + if ((adtyp == AD_FIRE && completelyburns(magr->data)) + || (adtyp == AD_DCAY && completelyrots(magr->data)) + || (adtyp == AD_RUST && completelyrusts(magr->data))) { + dmg = magr->mhp; + } else if ((adtyp == AD_ACID && !resists_acid(magr)) + || (adtyp == AD_COLD && !resists_cold(magr)) + || (adtyp == AD_FIRE && !resists_fire(magr)) + || (adtyp == AD_ELEC && !resists_elec(magr)) + || adtyp == AD_PHYS) { dmg = mdef->data->mattk[i].damn; if (!dmg) dmg = mdef->data->mlevel + 1; dmg *= mdef->data->mattk[i].damd; - } else - dmg = 0; - - return dmg * multi2; + } + dmg *= multi2; + break; } - return 0; + return dmg; } /* determine whether two monster types are from the same species */ boolean -same_race(pm1, pm2) -struct permonst *pm1, *pm2; +same_race(struct permonst *pm1, struct permonst *pm2) { char let1 = pm1->mlet, let2 = pm2->mlet; @@ -647,32 +870,30 @@ struct permonst *pm1, *pm2; return FALSE; } -/* return an index into the mons array */ -int -monsndx(ptr) -struct permonst *ptr; -{ - register int i; - - i = (int) (ptr - &mons[0]); - if (i < LOW_PM || i >= NUMMONS) { - panic("monsndx - could not index monster (%s)", - fmt_ptr((genericptr_t) ptr)); - return NON_PM; /* will not get here */ - } - return i; -} - /* for handling alternate spellings */ struct alt_spl { const char *name; short pm_val; + int genderhint; }; -/* figure out what type of monster a user-supplied string is specifying */ +/* figure out what type of monster a user-supplied string is specifying; + ignore anything past the monster name */ int -name_to_mon(in_str) -const char *in_str; +name_to_mon(const char *in_str, int *gender_name_var) +{ + return name_to_monplus(in_str, (const char **) 0, gender_name_var); +} + +/* figure out what type of monster a user-supplied string is specifying; + return a pointer to whatever is past the monster name--necessary if + caller wants to strip off the name and it matches one of the alternate + names rather the canonical mons[].mname */ +int +name_to_monplus( + const char *in_str, + const char **remainder_p, + int *gender_name_var) { /* Be careful. We must check the entire string in case it was * something such as "ettin zombie corpse". The calling routine @@ -686,11 +907,16 @@ const char *in_str; * This also permits plurals created by adding suffixes such as 's' * or 'es'. Other plurals must still be handled explicitly. */ - register int i; - register int mntmp = NON_PM; - register char *s, *str, *term; + int i; + int mntmp = NON_PM; + char *s, *str, *term; char buf[BUFSZ]; - int len, slen; + int len, mgend, matchgend = -1; + size_t slen; + boolean exact_match = FALSE; + + if (remainder_p) + *remainder_p = (const char *) 0; str = strcpy(buf, in_str); @@ -719,73 +945,111 @@ const char *in_str; { static const struct alt_spl names[] = { /* Alternate spellings */ - { "grey dragon", PM_GRAY_DRAGON }, - { "baby grey dragon", PM_BABY_GRAY_DRAGON }, - { "grey unicorn", PM_GRAY_UNICORN }, - { "grey ooze", PM_GRAY_OOZE }, - { "gray-elf", PM_GREY_ELF }, - { "mindflayer", PM_MIND_FLAYER }, - { "master mindflayer", PM_MASTER_MIND_FLAYER }, + { "grey dragon", PM_GRAY_DRAGON, NEUTRAL }, + { "baby grey dragon", PM_BABY_GRAY_DRAGON, NEUTRAL }, + { "grey unicorn", PM_GRAY_UNICORN, NEUTRAL }, + { "grey ooze", PM_GRAY_OOZE, NEUTRAL }, + { "gray-elf", PM_GREY_ELF, NEUTRAL }, + { "mindflayer", PM_MIND_FLAYER, NEUTRAL }, + { "master mindflayer", PM_MASTER_MIND_FLAYER, NEUTRAL }, /* More alternates; priest and priestess are separate monster types but that isn't the case for {aligned,high} priests */ - { "aligned priestess", PM_ALIGNED_PRIEST }, - { "high priestess", PM_HIGH_PRIEST }, + { "aligned priest", PM_ALIGNED_CLERIC, MALE }, + { "aligned priestess", PM_ALIGNED_CLERIC, FEMALE }, + { "high priest", PM_HIGH_CLERIC, MALE }, + { "high priestess", PM_HIGH_CLERIC, FEMALE }, /* Inappropriate singularization by -ves check above */ - { "master of thief", PM_MASTER_OF_THIEVES }, + { "master of thief", PM_MASTER_OF_THIEVES, NEUTRAL }, /* Potential misspellings where we want to avoid falling back to the rank title prefix (input has been singularized) */ - { "master thief", PM_MASTER_OF_THIEVES }, - { "master of assassin", PM_MASTER_ASSASSIN }, + { "master thief", PM_MASTER_OF_THIEVES, NEUTRAL }, + { "master of assassin", PM_MASTER_ASSASSIN, NEUTRAL }, + { "master-lich", PM_MASTER_LICH, NEUTRAL }, /* cf arch-lich */ + { "masterlich", PM_MASTER_LICH, NEUTRAL }, /* cf demilich */ /* Outdated names */ - { "invisible stalker", PM_STALKER }, - { "high-elf", PM_ELVENKING }, /* PM_HIGH_ELF is obsolete */ + { "invisible stalker", PM_STALKER, NEUTRAL }, + { "high-elf", PM_ELVEN_MONARCH, NEUTRAL }, /* PM_HIGH_ELF is + * obsolete */ /* other misspellings or incorrect words */ - { "wood-elf", PM_WOODLAND_ELF }, - { "wood elf", PM_WOODLAND_ELF }, - { "woodland nymph", PM_WOOD_NYMPH }, - { "halfling", PM_HOBBIT }, /* potential guess for polyself */ - { "genie", PM_DJINNI }, /* potential guess for ^G/#wizgenesis */ + { "wood-elf", PM_WOODLAND_ELF, NEUTRAL }, + { "wood elf", PM_WOODLAND_ELF, NEUTRAL }, + { "woodland nymph", PM_WOOD_NYMPH, NEUTRAL }, + { "halfling", PM_HOBBIT, NEUTRAL }, /* potential guess for + * polyself */ + { "genie", PM_DJINNI, NEUTRAL }, /* potential guess for + * ^G/#wizgenesis */ + /* prefix used to workaround duplicate monster names for + monsters with alternate forms */ + { "human wererat", PM_HUMAN_WERERAT, NEUTRAL }, + { "human werejackal", PM_HUMAN_WEREJACKAL, NEUTRAL }, + { "human werewolf", PM_HUMAN_WEREWOLF, NEUTRAL }, + /* for completeness */ + { "rat wererat", PM_WERERAT, NEUTRAL }, + { "jackal werejackal", PM_WEREJACKAL, NEUTRAL }, + { "wolf werewolf", PM_WEREWOLF, NEUTRAL }, /* Hyphenated names -- it would be nice to handle these via fuzzymatch() but it isn't able to ignore trailing stuff */ - { "ki rin", PM_KI_RIN }, - { "uruk hai", PM_URUK_HAI }, - { "orc captain", PM_ORC_CAPTAIN }, - { "woodland elf", PM_WOODLAND_ELF }, - { "green elf", PM_GREEN_ELF }, - { "grey elf", PM_GREY_ELF }, - { "gray elf", PM_GREY_ELF }, - { "elf lord", PM_ELF_LORD }, - { "olog hai", PM_OLOG_HAI }, - { "arch lich", PM_ARCH_LICH }, + { "ki rin", PM_KI_RIN, NEUTRAL }, + { "kirin", PM_KI_RIN, NEUTRAL }, + { "uruk hai", PM_URUK_HAI, NEUTRAL }, + { "orc captain", PM_ORC_CAPTAIN, NEUTRAL }, + { "woodland elf", PM_WOODLAND_ELF, NEUTRAL }, + { "green elf", PM_GREEN_ELF, NEUTRAL }, + { "grey elf", PM_GREY_ELF, NEUTRAL }, + { "gray elf", PM_GREY_ELF, NEUTRAL }, + { "elf lady", PM_ELF_NOBLE, FEMALE }, + { "elf lord", PM_ELF_NOBLE, MALE }, + { "elf noble", PM_ELF_NOBLE, NEUTRAL }, + { "olog hai", PM_OLOG_HAI, NEUTRAL }, + { "arch lich", PM_ARCH_LICH, NEUTRAL }, + { "archlich", PM_ARCH_LICH, NEUTRAL }, /* Some irregular plurals */ - { "incubi", PM_INCUBUS }, - { "succubi", PM_SUCCUBUS }, - { "violet fungi", PM_VIOLET_FUNGUS }, - { "homunculi", PM_HOMUNCULUS }, - { "baluchitheria", PM_BALUCHITHERIUM }, - { "lurkers above", PM_LURKER_ABOVE }, - { "cavemen", PM_CAVEMAN }, - { "cavewomen", PM_CAVEWOMAN }, - { "watchmen", PM_WATCHMAN }, - { "djinn", PM_DJINNI }, - { "mumakil", PM_MUMAK }, - { "erinyes", PM_ERINYS }, + { "incubi", PM_AMOROUS_DEMON, MALE }, + { "succubi", PM_AMOROUS_DEMON, FEMALE }, + { "violet fungi", PM_VIOLET_FUNGUS, NEUTRAL }, + { "homunculi", PM_HOMUNCULUS, NEUTRAL }, + { "baluchitheria", PM_BALUCHITHERIUM, NEUTRAL }, + { "lurkers above", PM_LURKER_ABOVE, NEUTRAL }, + { "cavemen", PM_CAVE_DWELLER, MALE }, + { "cavewomen", PM_CAVE_DWELLER, FEMALE }, + { "watchmen", PM_WATCHMAN, NEUTRAL }, + { "djinn", PM_DJINNI, NEUTRAL }, + { "mumakil", PM_MUMAK, NEUTRAL }, + { "erinyes", PM_ERINYS, NEUTRAL }, /* end of list */ - { 0, NON_PM } + { 0, NON_PM, NEUTRAL } }; - register const struct alt_spl *namep; - - for (namep = names; namep->name; namep++) - if (!strncmpi(str, namep->name, (int) strlen(namep->name))) + const struct alt_spl *namep; + + for (namep = names; namep->name; namep++) { + len = (int) strlen(namep->name); + if (!strncmpi(str, namep->name, len) + /* force full word (which could conceivably be possessive) */ + && (!str[len] || str[len] == ' ' || str[len] == '\'')) { + if (remainder_p) + *remainder_p = in_str + (&str[len] - buf); + if (gender_name_var) + *gender_name_var = namep->genderhint; return namep->pm_val; + } + } } for (len = 0, i = LOW_PM; i < NUMMONS; i++) { - register int m_i_len = (int) strlen(mons[i].mname); + for (mgend = MALE; mgend < NUM_MGENDERS; mgend++) { + size_t m_i_len; - if (m_i_len > len && !strncmpi(mons[i].mname, str, m_i_len)) { + if (!mons[i].pmnames[mgend]) + continue; + + m_i_len = strlen(mons[i].pmnames[mgend]); + if (m_i_len > (size_t) len + && !strncmpi(mons[i].pmnames[mgend], str, (int) m_i_len)) { if (m_i_len == slen) { mntmp = i; + len = (int) m_i_len; + matchgend = mgend; + exact_match = TRUE; break; /* exact match */ } else if (slen > m_i_len && (str[m_i_len] == ' ' @@ -798,27 +1062,38 @@ const char *in_str; || !strcmpi(&str[m_i_len], "es") || !strncmpi(&str[m_i_len], "es ", 3))) { mntmp = i; - len = m_i_len; + len = (int) m_i_len; + matchgend = mgend; } } + } + if (exact_match) + break; } + /* FIXME: some titles have gender; title_to_mon() doesn't propagate it */ if (mntmp == NON_PM) - mntmp = title_to_mon(str, (int *) 0, (int *) 0); + mntmp = title_to_mon(str, (int *) 0, &len); + if (len && remainder_p) + *remainder_p = in_str + (&str[len] - buf); + if (gender_name_var && matchgend != -1) { + /* don't override with neuter if caller has already specified male + or female and we've matched the neuter name */ + if (*gender_name_var == -1 || matchgend != NEUTRAL) + *gender_name_var = matchgend; + } return mntmp; } /* monster class from user input; used for genocide and controlled polymorph; returns 0 rather than MAXMCLASSES if no match is found */ int -name_to_monclass(in_str, mndx_p) -const char *in_str; -int *mndx_p; +name_to_monclass(const char *in_str, int * mndx_p) { /* Single letters are matched against def_monsyms[].sym; words or phrases are first matched against def_monsyms[].explain to check class description; if not found there, then against - mons[].mname to test individual monster types. Input can be a - substring of the full description or mname, but to be accepted, + mons[].pmnames[] to test individual monster types. Input can be a + substring of the full description or pmname, but to be accepted, such partial matches must start at beginning of a word. Some class descriptions include "foo or bar" and "foo or other foo" so we don't want to accept "or", "other", "or other" there. */ @@ -830,16 +1105,16 @@ int *mndx_p; static NEARDATA const struct alt_spl truematch[] = { /* "long worm" won't match "worm" class but would accidentally match "long worm tail" class before the comparison with monster types */ - { "long worm", PM_LONG_WORM }, + { "long worm", PM_LONG_WORM, NEUTRAL }, /* matches wrong--or at least suboptimal--class */ - { "demon", -S_DEMON }, /* hits "imp or minor demon" */ + { "demon", -S_DEMON, NEUTRAL }, /* hits "imp or minor demon" */ /* matches specific monster (overly restrictive) */ - { "devil", -S_DEMON }, /* always "horned devil" */ + { "devil", -S_DEMON, NEUTRAL }, /* always "horned devil" */ /* some plausible guesses which need help */ - { "bug", -S_XAN }, /* would match bugbear... */ - { "fish", -S_EEL }, /* wouldn't match anything */ + { "bug", -S_XAN, NEUTRAL }, /* would match bugbear... */ + { "fish", -S_EEL, NEUTRAL }, /* wouldn't match anything */ /* end of list */ - { 0, NON_PM } + { 0, NON_PM, NEUTRAL} }; const char *p, *x; int i, len; @@ -890,7 +1165,7 @@ int *mndx_p; return i; } /* check individual species names */ - i = name_to_mon(in_str); + i = name_to_mon(in_str, (int *) 0); if (i != NON_PM) { if (mndx_p) *mndx_p = i; @@ -902,21 +1177,27 @@ int *mndx_p; /* returns 3 values (0=male, 1=female, 2=none) */ int -gender(mtmp) -register struct monst *mtmp; +gender(struct monst *mtmp) { if (is_neuter(mtmp->data)) return 2; return mtmp->female; } -/* Like gender(), but lower animals and such are still "it". - This is the one we want to use when printing messages. */ +/* Like gender(), but unseen humanoids are "it" rather than "he" or "she" + and lower animals and such are "it" even when seen; hallucination might + yield "they". This is the one we want to use when printing messages. */ int -pronoun_gender(mtmp, override_vis) -register struct monst *mtmp; -boolean override_vis; /* if True then 'no it' unless neuter */ +pronoun_gender( + struct monst *mtmp, + unsigned pg_flags) /* flags&1: 'no it' unless neuter, + * flags&2: random if hallucinating */ { + boolean override_vis = (pg_flags & PRONOUN_NO_IT) ? TRUE : FALSE, + hallu_rand = (pg_flags & PRONOUN_HALLU) ? TRUE : FALSE; + + if (hallu_rand && Hallucination) + return rn2(4); /* 0..3 */ if (!override_vis && !canspotmon(mtmp)) return 2; if (is_neuter(mtmp->data)) @@ -927,8 +1208,7 @@ boolean override_vis; /* if True then 'no it' unless neuter */ /* used for nearby monsters when you go to another level */ boolean -levl_follower(mtmp) -struct monst *mtmp; +levl_follower(struct monst *mtmp) { if (mtmp == u.usteed) return TRUE; @@ -956,11 +1236,11 @@ static const short grownups[][2] = { { PM_PONY, PM_HORSE }, { PM_HORSE, PM_WARHORSE }, { PM_KOBOLD, PM_LARGE_KOBOLD }, - { PM_LARGE_KOBOLD, PM_KOBOLD_LORD }, - { PM_GNOME, PM_GNOME_LORD }, - { PM_GNOME_LORD, PM_GNOME_KING }, - { PM_DWARF, PM_DWARF_LORD }, - { PM_DWARF_LORD, PM_DWARF_KING }, + { PM_LARGE_KOBOLD, PM_KOBOLD_LEADER }, + { PM_GNOME, PM_GNOME_LEADER }, + { PM_GNOME_LEADER, PM_GNOME_RULER }, + { PM_DWARF, PM_DWARF_LEADER }, + { PM_DWARF_LEADER, PM_DWARF_RULER }, { PM_MIND_FLAYER, PM_MASTER_MIND_FLAYER }, { PM_ORC, PM_ORC_CAPTAIN }, { PM_HILL_ORC, PM_ORC_CAPTAIN }, @@ -968,19 +1248,20 @@ static const short grownups[][2] = { { PM_URUK_HAI, PM_ORC_CAPTAIN }, { PM_SEWER_RAT, PM_GIANT_RAT }, { PM_CAVE_SPIDER, PM_GIANT_SPIDER }, - { PM_OGRE, PM_OGRE_LORD }, - { PM_OGRE_LORD, PM_OGRE_KING }, - { PM_ELF, PM_ELF_LORD }, - { PM_WOODLAND_ELF, PM_ELF_LORD }, - { PM_GREEN_ELF, PM_ELF_LORD }, - { PM_GREY_ELF, PM_ELF_LORD }, - { PM_ELF_LORD, PM_ELVENKING }, + { PM_OGRE, PM_OGRE_LEADER }, + { PM_OGRE_LEADER, PM_OGRE_TYRANT }, + { PM_ELF, PM_ELF_NOBLE }, + { PM_WOODLAND_ELF, PM_ELF_NOBLE }, + { PM_GREEN_ELF, PM_ELF_NOBLE }, + { PM_GREY_ELF, PM_ELF_NOBLE }, + { PM_ELF_NOBLE, PM_ELVEN_MONARCH }, { PM_LICH, PM_DEMILICH }, { PM_DEMILICH, PM_MASTER_LICH }, { PM_MASTER_LICH, PM_ARCH_LICH }, - { PM_VAMPIRE, PM_VAMPIRE_LORD }, + { PM_VAMPIRE, PM_VAMPIRE_LEADER }, { PM_BAT, PM_GIANT_BAT }, { PM_BABY_GRAY_DRAGON, PM_GRAY_DRAGON }, + { PM_BABY_GOLD_DRAGON, PM_GOLD_DRAGON }, { PM_BABY_SILVER_DRAGON, PM_SILVER_DRAGON }, #if 0 /* DEFERRED */ {PM_BABY_SHIMMERING_DRAGON, PM_SHIMMERING_DRAGON}, @@ -1005,11 +1286,11 @@ static const short grownups[][2] = { { PM_SERGEANT, PM_LIEUTENANT }, { PM_LIEUTENANT, PM_CAPTAIN }, { PM_WATCHMAN, PM_WATCH_CAPTAIN }, - { PM_ALIGNED_PRIEST, PM_HIGH_PRIEST }, + { PM_ALIGNED_CLERIC, PM_HIGH_CLERIC }, { PM_STUDENT, PM_ARCHEOLOGIST }, { PM_ATTENDANT, PM_HEALER }, { PM_PAGE, PM_KNIGHT }, - { PM_ACOLYTE, PM_PRIEST }, + { PM_ACOLYTE, PM_CLERIC }, { PM_APPRENTICE, PM_WIZARD }, { PM_MANES, PM_LEMURE }, { PM_KEYSTONE_KOP, PM_KOP_SERGEANT }, @@ -1019,10 +1300,9 @@ static const short grownups[][2] = { }; int -little_to_big(montype) -int montype; +little_to_big(int montype) { - register int i; + int i; for (i = 0; grownups[i][0] >= LOW_PM; i++) if (montype == grownups[i][0]) { @@ -1033,10 +1313,9 @@ int montype; } int -big_to_little(montype) -int montype; +big_to_little(int montype) { - register int i; + int i; for (i = 0; grownups[i][0] >= LOW_PM; i++) if (montype == grownups[i][1]) { @@ -1049,8 +1328,7 @@ int montype; /* determine whether two permonst indices are part of the same progression; existence of progressions with more than one step makes it a bit tricky */ boolean -big_little_match(montyp1, montyp2) -int montyp1, montyp2; +big_little_match(int montyp1, int montyp2) { int l, b; @@ -1078,62 +1356,59 @@ int montyp1, montyp2; * player. It does not return a pointer to player role character. */ const struct permonst * -raceptr(mtmp) -struct monst *mtmp; +raceptr(struct monst *mtmp) { - if (mtmp == &youmonst && !Upolyd) - return &mons[urace.malenum]; - else - return mtmp->data; + if (mtmp == &gy.youmonst && !Upolyd) + return &mons[gu.urace.mnum]; + return mtmp->data; } -static const char *levitate[4] = { "float", "Float", "wobble", "Wobble" }; -static const char *flys[4] = { "fly", "Fly", "flutter", "Flutter" }; -static const char *flyl[4] = { "fly", "Fly", "stagger", "Stagger" }; -static const char *slither[4] = { "slither", "Slither", "falter", "Falter" }; -static const char *ooze[4] = { "ooze", "Ooze", "tremble", "Tremble" }; -static const char *immobile[4] = { "wiggle", "Wiggle", "pulsate", "Pulsate" }; -static const char *crawl[4] = { "crawl", "Crawl", "falter", "Falter" }; +typedef const char *const locoverbs[4]; +static locoverbs levitate = { "float", "Float", "wobble", "Wobble" }, + flys = { "fly", "Fly", "flutter", "Flutter" }, + flyl = { "fly", "Fly", "stagger", "Stagger" }, + slither = { "slither", "Slither", "falter", "Falter" }, + /* it would be useful to incorporate "swim" but we lack + * sufficient information to know whether water is involved + swim = { "swim", "Swim", "flop", "Flop" }, + */ + ooze = { "ooze", "Ooze", "tremble", "Tremble" }, + immobile = { "wiggle", "Wiggle", "pulsate", "Pulsate" }, + crawl = { "crawl", "Crawl", "falter", "Falter" }; const char * -locomotion(ptr, def) -const struct permonst *ptr; -const char *def; +locomotion(const struct permonst *ptr, const char *def) { - int capitalize = (*def == highc(*def)); - - return (is_floater(ptr) ? levitate[capitalize] - : (is_flyer(ptr) && ptr->msize <= MZ_SMALL) ? flys[capitalize] - : (is_flyer(ptr) && ptr->msize > MZ_SMALL) ? flyl[capitalize] - : slithy(ptr) ? slither[capitalize] - : amorphous(ptr) ? ooze[capitalize] - : !ptr->mmove ? immobile[capitalize] - : nolimbs(ptr) ? crawl[capitalize] + int locoindx = (*def != highc(*def)) ? 0 : 1; + + return (is_floater(ptr) ? levitate[locoindx] + : (is_flyer(ptr) && ptr->msize <= MZ_SMALL) ? flys[locoindx] + : (is_flyer(ptr) && ptr->msize > MZ_SMALL) ? flyl[locoindx] + : slithy(ptr) ? slither[locoindx] + : amorphous(ptr) ? ooze[locoindx] + : !ptr->mmove ? immobile[locoindx] + : nolimbs(ptr) ? crawl[locoindx] : def); } const char * -stagger(ptr, def) -const struct permonst *ptr; -const char *def; +stagger(const struct permonst *ptr, const char *def) { - int capitalize = 2 + (*def == highc(*def)); - - return (is_floater(ptr) ? levitate[capitalize] - : (is_flyer(ptr) && ptr->msize <= MZ_SMALL) ? flys[capitalize] - : (is_flyer(ptr) && ptr->msize > MZ_SMALL) ? flyl[capitalize] - : slithy(ptr) ? slither[capitalize] - : amorphous(ptr) ? ooze[capitalize] - : !ptr->mmove ? immobile[capitalize] - : nolimbs(ptr) ? crawl[capitalize] + int locoindx = (*def != highc(*def)) ? 2 : 3; + + return (is_floater(ptr) ? levitate[locoindx] + : (is_flyer(ptr) && ptr->msize <= MZ_SMALL) ? flys[locoindx] + : (is_flyer(ptr) && ptr->msize > MZ_SMALL) ? flyl[locoindx] + : slithy(ptr) ? slither[locoindx] + : amorphous(ptr) ? ooze[locoindx] + : !ptr->mmove ? immobile[locoindx] + : nolimbs(ptr) ? crawl[locoindx] : def); } /* return phrase describing the effect of fire attack on a type of monster */ const char * -on_fire(mptr, mattk) -struct permonst *mptr; -struct attack *mattk; +on_fire(struct permonst *mptr, struct attack *mattk) { const char *what; @@ -1169,18 +1444,67 @@ struct attack *mattk; return what; } +/* similar to on_fire(); creature is summoned in a cloud of */ +const char * +msummon_environ(struct permonst *mptr, const char **cloud) +{ + const char *what; + int mndx = ((mptr->mlet == S_ANGEL) ? PM_ANGEL + : (mptr->mlet == S_LIGHT) ? PM_YELLOW_LIGHT + : monsndx(mptr)); + + *cloud = "cloud"; /* default is "cloud of " */ + switch (mndx) { + case PM_WATER_DEMON: + case PM_AIR_ELEMENTAL: + case PM_WATER_ELEMENTAL: + case PM_FOG_CLOUD: + case PM_ICE_VORTEX: + case PM_FREEZING_SPHERE: + what = "vapor"; + break; + case PM_STEAM_VORTEX: + what = "steam"; + break; + case PM_ENERGY_VORTEX: + case PM_SHOCKING_SPHERE: + *cloud = "shower"; /* "shower of sparks" instead of "cloud of..." */ + what = "sparks"; + break; + case PM_EARTH_ELEMENTAL: + case PM_DUST_VORTEX: + what = "dust"; + break; + case PM_FIRE_ELEMENTAL: + case PM_FIRE_VORTEX: + case PM_FLAMING_SPHERE: + /*case PM_SALAMANDER:*/ + *cloud = "ball"; /* "ball of flame" instead of "cloud of..." */ + what = "flame"; + break; + case PM_ANGEL: /* actually any 'A'-class */ + case PM_YELLOW_LIGHT: /* any 'y'-class */ + *cloud = "flash"; /* "flash of light" instead of "cloud of..." */ + what = "light"; + break; + default: + what = "smoke"; + break; + } + return what; +} + /* * Returns: * True if monster is presumed to have a sense of smell. * False if monster definitely does not have a sense of smell. * * Do not base this on presence of a head or nose, since many - * creatures sense smells other ways (feelers, forked-tongues, etc.) + * creatures sense smells other ways (feelers, forked-tongues, etc). * We're assuming all insects can smell at a distance too. */ boolean -olfaction(mdat) -struct permonst *mdat; +olfaction(struct permonst *mdat) { if (is_golem(mdat) || mdat->mlet == S_EYE /* spheres */ @@ -1193,4 +1517,155 @@ struct permonst *mdat; return TRUE; } +/* Convert attack damage type AD_foo to M_SEEN_bar */ +unsigned long +cvt_adtyp_to_mseenres(uchar adtyp) +{ + switch (adtyp) { + case AD_MAGM: return M_SEEN_MAGR; + case AD_FIRE: return M_SEEN_FIRE; + case AD_COLD: return M_SEEN_COLD; + case AD_SLEE: return M_SEEN_SLEEP; + case AD_DISN: return M_SEEN_DISINT; + case AD_ELEC: return M_SEEN_ELEC; + case AD_DRST: return M_SEEN_POISON; + case AD_ACID: return M_SEEN_ACID; + /* M_SEEN_REFL has no corresponding AD_foo type */ + default: return M_SEEN_NOTHING; + } +} + +/* Convert property resistance to M_SEEN_bar */ +unsigned long +cvt_prop_to_mseenres(uchar prop) +{ + switch (prop) { + case ANTIMAGIC: return M_SEEN_MAGR; + case FIRE_RES: return M_SEEN_FIRE; + case COLD_RES: return M_SEEN_COLD; + case SLEEP_RES: return M_SEEN_SLEEP; + case DISINT_RES: return M_SEEN_DISINT; + case POISON_RES: return M_SEEN_POISON; + case SHOCK_RES: return M_SEEN_ELEC; + case ACID_RES: return M_SEEN_ACID; + case REFLECTING: return M_SEEN_REFL; + default: return M_SEEN_NOTHING; + } +} + +/* Monsters in line of sight remember hero resisting effect M_SEEN_foo */ +void +monstseesu(unsigned long seenres) +{ + struct monst *mtmp; + + if (seenres == M_SEEN_NOTHING || u.uswallow) + return; + + for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) + if (!DEADMONSTER(mtmp) && m_canseeu(mtmp)) + m_setseenres(mtmp, seenres); +} + +/* Monsters in line of sight forget hero resistance to M_SEEN_foo */ +void +monstunseesu(unsigned long seenres) +{ + struct monst *mtmp; + + if (seenres == M_SEEN_NOTHING || u.uswallow) + return; + + for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) + if (!DEADMONSTER(mtmp) && m_canseeu(mtmp)) + m_clearseenres(mtmp, seenres); +} + +/* give monster mtmp the same intrinsics hero has */ +void +give_u_to_m_resistances(struct monst *mtmp) +{ + int intr; + + /* convert the hero's current set of intrinsics to their monster + equivalents -- FIRE_RES to MR_FIRE, COLD_RES to MR_COLD, etc -- and + add each to the mintrinsics field for the given monster */ + for (intr = FIRE_RES; intr <= STONE_RES; intr++) { + if ((u.uprops[intr].intrinsic & INTRINSIC) != 0L) { + mtmp->mintrinsics |= (unsigned short) res_to_mr(intr); + } + } +} + +/* Can monster resist conflict caused by hero? + + High-CHA heroes will be able to 'convince' monsters + (through the magic of the ring, of course) to fight + for them much more easily than low-CHA ones. +*/ +boolean +resist_conflict(struct monst *mtmp) +{ + /* always a small chance at 19 */ + int resist_chance = min(19, (ACURR(A_CHA) - mtmp->m_lev + u.ulevel)); + + return (rnd(20) > resist_chance); +} + +/* does monster mtmp know traps of type ttyp */ +boolean +mon_knows_traps(struct monst *mtmp, int ttyp) +{ + if (ttyp == ALL_TRAPS) + return (boolean)(mtmp->mtrapseen); + else if (ttyp == NO_TRAP) + return !(boolean)(mtmp->mtrapseen); + else + return ((mtmp->mtrapseen & (1L << (ttyp - 1))) != 0); +} + +/* monster mtmp learns all traps of type ttyp */ +void +mon_learns_traps(struct monst *mtmp, int ttyp) +{ + if (ttyp == ALL_TRAPS) + mtmp->mtrapseen = ~0L; + else if (ttyp == NO_TRAP) + mtmp->mtrapseen = 0L; + else + mtmp->mtrapseen |= (1L << (ttyp - 1)); +} + +/* monsters see a trap trigger, and remember it */ +void +mons_see_trap(struct trap *ttmp) +{ + struct monst *mtmp; + coordxy tx = ttmp->tx, ty = ttmp->ty; + int maxdist = levl[tx][ty].lit ? 7*7 : 2; + + for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { + if (is_animal(mtmp->data) || mindless(mtmp->data) + || !haseyes(mtmp->data) || !mtmp->mcansee) + continue; + if (dist2(mtmp->mx, mtmp->my, tx, ty) > maxdist) + continue; + if (!m_cansee(mtmp, tx, ty)) + continue; + mon_learns_traps(mtmp, ttmp->ttyp); + } +} + +int +get_atkdam_type(int adtyp) +{ + if (adtyp == AD_RBRE) { + static const int rnd_breath_typ[] = { + AD_MAGM, AD_FIRE, AD_COLD, AD_SLEE, + AD_DISN, AD_ELEC, AD_DRST, AD_ACID }; + return ROLL_FROM(rnd_breath_typ); + } + return adtyp; +} + /*mondata.c*/ diff --git a/src/monmove.c b/src/monmove.c index cd6ca98bb..f1a294d55 100644 --- a/src/monmove.c +++ b/src/monmove.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 monmove.c $NHDT-Date: 1575245074 2019/12/02 00:04:34 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.116 $ */ +/* NetHack 5.0 monmove.c $NHDT-Date: 1737392015 2025/01/20 08:53:35 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.266 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Michael Allison, 2006. */ /* NetHack may be freely redistributed. See license for details. */ @@ -7,46 +7,95 @@ #include "mfndpos.h" #include "artifact.h" -extern boolean notonhead; - -STATIC_DCL void FDECL(watch_on_duty, (struct monst *)); -STATIC_DCL int FDECL(disturb, (struct monst *)); -STATIC_DCL void FDECL(release_hero, (struct monst *)); -STATIC_DCL void FDECL(distfleeck, (struct monst *, int *, int *, int *)); -STATIC_DCL int FDECL(m_arrival, (struct monst *)); -STATIC_DCL boolean FDECL(stuff_prevents_passage, (struct monst *)); -STATIC_DCL int FDECL(vamp_shift, (struct monst *, struct permonst *, - BOOLEAN_P)); +staticfn void msg_mon_movement(struct monst *, coordxy, coordxy) NONNULLARG1; +staticfn void watch_on_duty(struct monst *); +staticfn int disturb(struct monst *); +staticfn void release_hero(struct monst *); +staticfn void distfleeck(struct monst *, int *, int *, int *); +staticfn int m_arrival(struct monst *); +staticfn void mind_blast(struct monst *); +staticfn boolean holds_up_web(coordxy, coordxy); +staticfn int count_webbing_walls(coordxy, coordxy); +staticfn boolean soko_allow_web(struct monst *); +staticfn boolean m_search_items(struct monst *, coordxy *, coordxy *, int *, + int *) NONNULLPTRS; +staticfn int postmov(struct monst *, struct permonst *, coordxy, coordxy, int, + unsigned, boolean, boolean, boolean) NONNULLPTRS; +staticfn boolean leppie_avoidance(struct monst *); +staticfn void leppie_stash(struct monst *); +staticfn int m_balks_at_approaching(int, struct monst *, int *, int *); +staticfn boolean stuff_prevents_passage(struct monst *); +staticfn int vamp_shift(struct monst *, struct permonst *, boolean); +staticfn void maybe_spin_web(struct monst *); + +/* a11y: give a message when monster moved */ +staticfn void +msg_mon_movement(struct monst *mtmp, coordxy omx, coordxy omy) +{ + if (a11y.mon_movement && canspotmon(mtmp) && mtmp->mspotted) { + coordxy nix = mtmp->mx, niy = mtmp->my; + boolean n2u = next2u(nix, niy), + close = !n2u && (distu(nix, niy) <= (BOLT_LIM * BOLT_LIM)), + closer = !n2u && (distu(nix, niy) <= distu(omx, omy)); + + pline_xy(nix, niy, "%s %s%s.", Monnam(mtmp), + vtense((char *) 0, locomotion(mtmp->data, "move")), + n2u ? " next to you" + : (close && closer) ? " closer" + : (close && !closer) ? " further away" + : " in the distance"); + } +} -/* True if mtmp died */ +/* monster has triggered trapped door lock or was present when it got + triggered remotely (at door spot, door hit by zap); + returns True if mtmp dies */ boolean -mb_trapped(mtmp) -struct monst *mtmp; +mb_trapped(struct monst *mtmp, boolean canseeit) { if (flags.verbose) { - if (cansee(mtmp->mx, mtmp->my) && !Unaware) - pline("KABOOM!! You see a door explode."); + if (canseeit && !Unaware) + pline_mon(mtmp, "KABOOM!! You see a door explode."); else if (!Deaf) - You_hear("a distant explosion."); + You_hear("a %s explosion.", + (mdistu(mtmp) > 7 * 7) ? "distant" : "nearby"); } wake_nearto(mtmp->mx, mtmp->my, 7 * 7); mtmp->mstun = 1; mtmp->mhp -= rnd(15); if (DEADMONSTER(mtmp)) { mondied(mtmp); - if (!DEADMONSTER(mtmp)) /* lifesaved */ - return FALSE; - else + if (DEADMONSTER(mtmp)) return TRUE; + /* will get here if lifesaved */ } + mon_learns_traps(mtmp, TRAPPED_DOOR); return FALSE; } +/* push coordinate x,y to mtrack, making monster remember where it was */ +void +mon_track_add(struct monst *mtmp, coordxy x, coordxy y) +{ + int j; + + for (j = MTSZ - 1; j > 0; j--) + mtmp->mtrack[j] = mtmp->mtrack[j - 1]; + mtmp->mtrack[0].x = x; + mtmp->mtrack[0].y = y; +} + +void +mon_track_clear(struct monst *mtmp) +{ + memset(mtmp->mtrack, 0, sizeof(mtmp->mtrack)); +} + /* check whether a monster is carrying a locking/unlocking tool */ boolean -monhaskey(mon, for_unlocking) -struct monst *mon; -boolean for_unlocking; /* true => credit card ok, false => not ok */ +monhaskey( + struct monst *mon, + boolean for_unlocking) /* true => credit card ok, false => not ok */ { if (for_unlocking && m_carrying(mon, CREDIT_CARD)) return TRUE; @@ -54,35 +103,79 @@ boolean for_unlocking; /* true => credit card ok, false => not ok */ } void -mon_yells(mon, shout) -struct monst *mon; -const char *shout; +mon_yells(struct monst *mon, const char *shout) { if (Deaf) { if (canspotmon(mon)) /* Sidenote on "A watchman angrily waves her arms!" * Female being called watchman is correct (career name). */ - pline("%s angrily %s %s %s!", + pline_mon(mon, "%s angrily %s %s %s!", Amonnam(mon), nolimbs(mon->data) ? "shakes" : "waves", mhis(mon), nolimbs(mon->data) ? mbodypart(mon, HEAD) : makeplural(mbodypart(mon, ARM))); } else { - if (canspotmon(mon)) - pline("%s yells:", Amonnam(mon)); - else + if (canspotmon(mon)) { + pline_mon(mon, "%s yells:", Amonnam(mon)); + } else { + /* Soundeffect(se_someone_yells, 75); */ You_hear("someone yell:"); + } + SetVoice(mon, 0, 80, 0); verbalize1(shout); } } -STATIC_OVL void -watch_on_duty(mtmp) -register struct monst *mtmp; +/* can monster mtmp break boulders? */ +boolean +m_can_break_boulder(struct monst *mtmp) +{ + return (is_rider(mtmp->data) + || (!mtmp->mspec_used + && (mtmp->isshk || mtmp->ispriest + || (mtmp->data->msound == MS_LEADER)))); +} + +/* monster mtmp breaks boulder at x,y */ +void +m_break_boulder(struct monst *mtmp, coordxy x, coordxy y) +{ + struct obj *otmp; + + if (m_can_break_boulder(mtmp) && ((otmp = sobj_at(BOULDER, x, y)) != 0)) { + if (!is_rider(mtmp->data)) { + if (!Deaf && (mdistu(mtmp) < 4*4)) { + if (canspotmon(mtmp)) + set_msg_xy(mtmp->mx, mtmp->my); + pline("%s mutters %s.", + Monnam(mtmp), + mtmp->ispriest ? "a prayer" : "an incantation"); + } + mtmp->mspec_used += rn1(20, 10); + } + if (cansee(x, y)) { + set_msg_xy(x, y); + pline_The("boulder falls apart."); + } + + /* boulders pushed onto shop's boundary or free spot are cases where + an item not in hero's inventory can have its unpaid flag set; + if the boulder isn't already on the bill, don't charge for it */ + if (otmp->unpaid) { + /* remove original from bill + add cloned copy to used-up bill */ + bill_dummy_object(otmp); + } + /* fracturing keeps otmp, changing its otyp from BOULDER to ROCK */ + fracture_rock(otmp); + } +} + +staticfn void +watch_on_duty(struct monst *mtmp) { - int x, y; + coordxy x, y; if (mtmp->mpeaceful && in_town(u.ux + u.dx, u.uy + u.dy) && mtmp->mcansee && m_canseeu(mtmp) && !rn2(3)) { @@ -100,54 +193,81 @@ register struct monst *mtmp; } } else if (is_digging()) { /* chewing, wand/spell of digging are checked elsewhere */ - watch_dig(mtmp, context.digging.pos.x, context.digging.pos.y, - FALSE); + watch_dig(mtmp, svc.context.digging.pos.x, + svc.context.digging.pos.y, FALSE); } } } +/* move a monster; if a threat to busy hero, stop doing whatever it is */ int -dochugw(mtmp) -register struct monst *mtmp; +dochugw( + struct monst *mtmp, + boolean chug) /* True: monster is moving; + * False: monster was just created or has teleported + * so perform stop-what-you're-doing-if-close-enough- + * to-be-a-threat check but don't move mtmp */ { - int x = mtmp->mx, y = mtmp->my; - boolean already_saw_mon = !occupation ? 0 : canspotmon(mtmp); - int rd = dochug(mtmp); + coordxy x = mtmp->mx, y = mtmp->my; /* 'mtmp's location before dochug() */ + /* skip canspotmon() if occupation is Null */ + boolean already_saw_mon = (chug && go.occupation) ? canspotmon(mtmp) : 0; + int rd = chug ? dochug(mtmp) : 0; + + /* + * A similar check is in monster_nearby() in hack.c. + * [The two checks have a lot of differences and chances are high + * that some of those are unintentional.] + */ - /* a similar check is in monster_nearby() in hack.c */ /* check whether hero notices monster and stops current activity */ - if (occupation && !rd && !Confusion && (!mtmp->mpeaceful || Hallucination) + if (go.occupation && !rd + /* monster is hostile and can attack (or hallu distorts knowledge) */ + && (Hallucination || (!mtmp->mpeaceful && !noattacks(mtmp->data))) /* it's close enough to be a threat */ - && distu(x, y) <= (BOLT_LIM + 1) * (BOLT_LIM + 1) + && mdistu(mtmp) <= (BOLT_LIM + 1) * (BOLT_LIM + 1) /* and either couldn't see it before, or it was too far away */ && (!already_saw_mon || !couldsee(x, y) || distu(x, y) > (BOLT_LIM + 1) * (BOLT_LIM + 1)) /* can see it now, or sense it and would normally see it */ - && (canseemon(mtmp) || (sensemon(mtmp) && couldsee(x, y))) - && mtmp->mcanmove && !noattacks(mtmp->data) - && !onscary(u.ux, u.uy, mtmp)) + && canspotmon(mtmp) && couldsee(mtmp->mx, mtmp->my) + /* monster isn't paralyzed or afraid (scare monster/Elbereth) */ + && mtmp->mcanmove && !onscary(u.ux, u.uy, mtmp)) stop_occupation(); return rd; } boolean -onscary(x, y, mtmp) -int x, y; -struct monst *mtmp; +onscary(coordxy x, coordxy y, struct monst *mtmp) { - /* creatures who are directly resistant to magical scaring: - * Rodney, lawful minions, Angels, the Riders, shopkeepers - * inside their own shop, priests inside their own temple */ + struct engr *ep; + /* <0,0> is used by musical scaring; + * it doesn't care about scrolls or engravings or dungeon branch */ + boolean auditory_scare = (x == 0 && y == 0), + magical_scare = !auditory_scare; + + /* creatures who are directly resistant to any type of scaring: + * Rodney, lawful minions, Angels, the Riders */ if (mtmp->iswiz || is_lminion(mtmp) || mtmp->data == &mons[PM_ANGEL] - || is_rider(mtmp->data) - || (mtmp->isshk && inhishop(mtmp)) + || is_rider(mtmp->data)) + return FALSE; + + /* creatures who are directly resistant to magical scaring + * based on the mere presence of something at a location: + * humans etc. + * uniques have ascended their base monster instincts */ + if (magical_scare + && (mtmp->data->mlet == S_HUMAN || unique_corpstat(mtmp->data))) + return FALSE; + + /* creatues who resist scaring under particular circumstances: + * shopkeepers inside their own shop + * priests inside their own temple */ + if ((mtmp->isshk && inhishop(mtmp)) || (mtmp->ispriest && inhistemple(mtmp))) return FALSE; - /* <0,0> is used by musical scaring to check for the above; - * it doesn't care about scrolls or engravings or dungeon branch */ - if (x == 0 && y == 0) + if (auditory_scare) return TRUE; /* should this still be true for defiled/molochian altars? */ @@ -172,24 +292,22 @@ struct monst *mtmp; * Elbereth doesn't work in Gehennom, the Elemental Planes, or the * Astral Plane; the influence of the Valar only reaches so far. */ - return (sengr_at("Elbereth", x, y, TRUE) - && ((u.ux == x && u.uy == y) - || (Displaced && mtmp->mux == x && mtmp->muy == y)) + return ((ep = sengr_at("Elbereth", x, y, TRUE)) != 0 + && (u_at(x, y) + || (Displaced && mtmp->mux == x && mtmp->muy == y) + || (ep->guardobjects && vobj_at(x, y))) && !(mtmp->isshk || mtmp->isgd || !mtmp->mcansee - || mtmp->mpeaceful || mtmp->data->mlet == S_HUMAN + || mtmp->mpeaceful || mtmp->data == &mons[PM_MINOTAUR] || Inhell || In_endgame(&u.uz))); } - /* regenerate lost hit points */ void -mon_regen(mon, digest_meal) -struct monst *mon; -boolean digest_meal; +mon_regen(struct monst *mon, boolean digest_meal) { - if (mon->mhp < mon->mhpmax && (moves % 20 == 0 || regenerates(mon->data))) - mon->mhp++; + if (svm.moves % 20 == 0 || regenerates(mon->data)) + healmon(mon, 1, 0); if (mon->mspec_used) mon->mspec_used--; if (digest_meal) { @@ -205,9 +323,8 @@ boolean digest_meal; * Possibly awaken the given monster. Return a 1 if the monster has been * jolted awake. */ -STATIC_OVL int -disturb(mtmp) -register struct monst *mtmp; +staticfn int +disturb(struct monst *mtmp) { /* * + Ettins are hard to surprise. @@ -219,9 +336,9 @@ register struct monst *mtmp; * not stealthy or (mon is an ettin and 9/10) AND * (mon is not a nymph, jabberwock, or leprechaun) or 1/50 AND * Aggravate or mon is (dog or human) or - * (1/7 and mon is not mimicing furniture or object) + * (1/7 and mon is not mimicking furniture or object) */ - if (couldsee(mtmp->mx, mtmp->my) && distu(mtmp->mx, mtmp->my) <= 100 + if (couldsee(mtmp->mx, mtmp->my) && mdistu(mtmp) <= 100 && (!Stealth || (mtmp->data == &mons[PM_ETTIN] && rn2(10))) && (!(mtmp->data->mlet == S_NYMPH || mtmp->data == &mons[PM_JABBERWOCK] @@ -233,6 +350,7 @@ register struct monst *mtmp; || (mtmp->data->mlet == S_DOG || mtmp->data->mlet == S_HUMAN) || (!rn2(7) && M_AP_TYPE(mtmp) != M_AP_FURNITURE && M_AP_TYPE(mtmp) != M_AP_OBJECT))) { + wake_msg(mtmp, !mtmp->mpeaceful); mtmp->msleeping = 0; return 1; } @@ -240,35 +358,112 @@ register struct monst *mtmp; } /* ungrab/expel held/swallowed hero */ -STATIC_OVL void -release_hero(mon) -struct monst *mon; +staticfn void +release_hero(struct monst *mon) { if (mon == u.ustuck) { if (u.uswallow) { expels(mon, mon->data, TRUE); - } else if (!sticks(youmonst.data)) { + } else if (!sticks(gy.youmonst.data)) { unstuck(mon); /* let go */ You("get released!"); } } } -#define flees_light(mon) ((mon)->data == &mons[PM_GREMLIN] \ - && (uwep && artifact_light(uwep) && uwep->lamplit)) -/* we could include this in the above macro, but probably overkill/overhead */ -/* && (!(which_armor((mon), W_ARMC) != 0 */ -/* && which_armor((mon), W_ARMH) != 0)) */ +struct monst * +find_pmmonst(int pm) +{ + struct monst *mtmp = 0; + + if ((svm.mvitals[pm].mvflags & G_GENOD) == 0) + for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { + if (DEADMONSTER(mtmp)) + continue; + if (mtmp->data == &mons[pm]) + break; + } + + return mtmp; +} + +/* killer bee 'mon' is on a spot containing lump of royal jelly 'obj' and + will eat it if there is no queen bee on the level; return 1: mon died, + 0: mon ate jelly and lived; -1: mon didn't eat jelly to use its move */ +int +bee_eat_jelly(struct monst *mon, struct obj *obj) +{ + int m_delay; + struct monst *mtmp = find_pmmonst(PM_QUEEN_BEE); + + /* if there's no queen on the level, eat the royal jelly and become one */ + if (!mtmp) { + m_delay = obj->blessed ? 3 : !obj->cursed ? 5 : 7; + if (obj->quan > 1L) + obj = splitobj(obj, 1L); + if (canseemon(mon)) + pline_mon(mon, "%s eats %s.", Monnam(mon), an(xname(obj))); + delobj(obj); + + if ((int) mon->m_lev < mons[PM_QUEEN_BEE].mlevel - 1) + mon->m_lev = (uchar) (mons[PM_QUEEN_BEE].mlevel - 1); + /* there should be delay after eating, but that's too much + hassle; transform immediately, then have a short delay */ + (void) grow_up(mon, (struct monst *) 0); + + if (DEADMONSTER(mon)) + return 1; /* dead; apparently queen bees have been genocided */ + mon->mfrozen = m_delay, mon->mcanmove = 0; + return 0; /* bee used its move */ + } + return -1; /* a queen is already present; ordinary bee hasn't moved yet */ +} + +/* gelatinous cube eats something from its inventory */ +static int +gelcube_digests(struct monst *mtmp) +{ + struct obj *otmp = mtmp->minvent; + + if (mtmp->meating || !mtmp->minvent) + return -1; + + while (otmp) { + if (is_organic(otmp) && !otmp->oartifact + && !is_mines_prize(otmp) && !is_soko_prize(otmp)) + break; + otmp = otmp->nobj; + } + + if (!otmp) + return -1; + + mtmp->meating = eaten_stat(mtmp->meating, otmp); + extract_from_minvent(mtmp, otmp, TRUE, TRUE); + m_consume_obj(mtmp, otmp); + return 0; /* used a move */ +} + + +/* FIXME: gremlins don't flee from monsters wielding Sunsword or wearing + gold dragon scales/mail, nor from gold dragons, only from the hero */ +#define flees_light(mon) \ + ((mon)->data == &mons[PM_GREMLIN] \ + && ((uwep && uwep->lamplit && artifact_light(uwep)) \ + || (uarm && uarm->lamplit && artifact_light(uarm))) \ + /* not applicable if mon can't see or hero isn't in line of sight */ \ + && mon->mcansee && couldsee(mon->mx, mon->my)) \ + /* doesn't matter if hero is invisible--light being emitted isn't */ /* monster begins fleeing for the specified time, 0 means untimed flee * if first, only adds fleetime if monster isn't already fleeing * if fleemsg, prints a message about new flight, otherwise, caller should */ void -monflee(mtmp, fleetime, first, fleemsg) -struct monst *mtmp; -int fleetime; -boolean first; -boolean fleemsg; +monflee( + struct monst *mtmp, + int fleetime, + boolean first, + boolean fleemsg) { /* shouldn't happen; maybe warrants impossible()? */ if (DEADMONSTER(mtmp)) @@ -295,26 +490,49 @@ boolean fleemsg; sleep and temporary paralysis, so both conditions receive the same alternate message */ if (!mtmp->mcanmove || !mtmp->data->mmove) { - pline("%s seems to flinch.", Adjmonnam(mtmp, "immobile")); + pline_mon(mtmp, "%s seems to flinch.", + Adjmonnam(mtmp, "immobile")); } else if (flees_light(mtmp)) { - if (rn2(10) || Deaf) - pline("%s flees from the painful light of %s.", - Monnam(mtmp), bare_artifactname(uwep)); - else + if (Unaware) { + /* tell the player even if the hero is unconscious */ + pline_mon(mtmp, "%s is frightened.", Monnam(mtmp)); + } else if (rn2(10) || Deaf) { + /* via flees_light(), will always be either via uwep + (Sunsword) or uarm (gold dragon scales/mail) or both; + TODO? check for both and describe the one which is + emitting light with a bigger radius */ + const char *lsrc = (uwep && artifact_light(uwep)) + ? bare_artifactname(uwep) + : (uarm && artifact_light(uarm)) + ? yname(uarm) + : "[its imagination?]"; + + pline_mon(mtmp, "%s flees from the painful light of %s.", + Monnam(mtmp), lsrc); + } else { + SetVoice(mtmp, 0, 80, 0); verbalize("Bright light!"); - } else - pline("%s turns to flee.", Monnam(mtmp)); + } + } else { + pline_mon(mtmp, "%s turns to flee.", Monnam(mtmp)); + } + } + + if (mtmp->data == &mons[PM_VROCK] && !mtmp->mspec_used) { + mtmp->mspec_used = 75 + rn2(25); + (void) create_gas_cloud(mtmp->mx, mtmp->my, 5, 8); } + mtmp->mflee = 1; } /* ignore recently-stepped spaces when made to flee */ - memset(mtmp->mtrack, 0, sizeof(mtmp->mtrack)); + mon_track_clear(mtmp); } -STATIC_OVL void -distfleeck(mtmp, inrange, nearby, scared) -register struct monst *mtmp; -int *inrange, *nearby, *scared; +staticfn void +distfleeck( + struct monst *mtmp, + int *inrange, int *nearby, int *scared) /* output */ { int seescaryx, seescaryy; boolean sawscary = FALSE, bravegremlin = (rn2(5) == 0); @@ -352,38 +570,142 @@ int *inrange, *nearby, *scared; /* perform a special one-time action for a monster; returns -1 if nothing special happened, 0 if monster uses up its turn, 1 if monster is killed */ -STATIC_OVL int -m_arrival(mon) -struct monst *mon; +staticfn int +m_arrival(struct monst *mon) { mon->mstrategy &= ~STRAT_ARRIVE; /* always reset */ return -1; } +/* a mind flayer unleashes a mind blast */ +staticfn void +mind_blast(struct monst *mtmp) +{ + struct monst *m2, *nmon = (struct monst *) 0; + + if (canseemon(mtmp)) + pline_mon(mtmp, "%s concentrates.", Monnam(mtmp)); + if (mdistu(mtmp) > BOLT_LIM * BOLT_LIM) { + You("sense a faint wave of psychic energy."); + return; + } + pline("A wave of psychic energy pours over you!"); + if (mtmp->mpeaceful + && (!Conflict || resist_conflict(mtmp))) { + pline("It feels quite soothing."); + } else if (!u.uinvulnerable) { + int dmg; + boolean m_sen = sensemon(mtmp); + + if (m_sen || (Blind_telepat && rn2(2)) || !rn2(10)) { + /* hiding monsters are brought out of hiding when hit by + a psychic blast, so do the same for hiding poly'd hero */ + if (u.uundetected) { + u.uundetected = 0; + newsym(u.ux, u.uy); + } else if (U_AP_TYPE != M_AP_NOTHING + /* hero has no way to hide as monster but + check for that theoretical case anyway */ + && U_AP_TYPE != M_AP_MONSTER) { + gy.youmonst.m_ap_type = M_AP_NOTHING; + gy.youmonst.mappearance = 0; + newsym(u.ux, u.uy); + } + pline("It locks on to your %s!", + m_sen ? "telepathy" + : Blind_telepat ? "latent telepathy" + : "mind"); /* note: hero is never mindless */ + dmg = rnd(15); + if (Half_spell_damage) + dmg = (dmg + 1) / 2; + losehp(dmg, "psychic blast", KILLED_BY_AN); + } + } + for (m2 = fmon; m2; m2 = nmon) { + nmon = m2->nmon; + if (DEADMONSTER(m2)) + continue; + if (m2->mpeaceful == mtmp->mpeaceful) + continue; + if (mindless(m2->data)) + continue; + if (m2 == mtmp) + continue; + if ((telepathic(m2->data) && (rn2(2) || m2->mblinded)) || !rn2(10)) { + /* wake it up first, to bring hidden monster out of hiding */ + wakeup(m2, FALSE); + if (cansee(m2->mx, m2->my)) + pline("It locks on to %s.", mon_nam(m2)); + m2->mhp -= rnd(15); + if (DEADMONSTER(m2)) + monkilled(m2, "", AD_DRIN); + } + } +} + +/* called every turn for each living monster on the map, and the hero; + caller makes sure that we're not called for DEADMONSTER() */ +void +m_everyturn_effect(struct monst *mtmp) +{ + boolean is_u = (mtmp == &gy.youmonst) ? TRUE : FALSE; + coordxy x = is_u ? u.ux : mtmp->mx, + y = is_u ? u.uy : mtmp->my; + + if (mtmp->data == &mons[PM_FOG_CLOUD]) { + /* don't leave a vapor cloud if some other gas cloud is already + present, or when flowing under closed doors so that visibility + changes aren't mixed with messages about doing such */ + if (!closed_door(x, y) && !visible_region_at(x, y)) + create_gas_cloud(x, y, 1, 0); /* harmless vapor */ + } +} + +/* do whatever effects monster has after moving. + called for both monsters and polyed hero. + for hero, called after location changes, + to prevent spam messages for hero getting enveloped in a cloud. + for monsters, called before location changes, + because monsters don't have "previous location" field */ +void +m_postmove_effect(struct monst *mtmp) +{ + boolean is_u = (mtmp == &gy.youmonst) ? TRUE : FALSE; + coordxy x = is_u ? u.ux0 : mtmp->mx, + y = is_u ? u.uy0 : mtmp->my; + + /* Hezrous create clouds of stench. This does not cost a move. */ + if (mtmp->data == &mons[PM_HEZROU]) /* stench */ + create_gas_cloud(x, y, 1, 8); + else if (mtmp->data == &mons[PM_STEAM_VORTEX] && !mtmp->mcan) + create_gas_cloud(x, y, 1, 0); /* harmless vapor */ +} + /* returns 1 if monster died moving, 0 otherwise */ /* The whole dochugw/m_move/distfleeck/mfndpos section is serious spaghetti * code. --KAA */ int -dochug(mtmp) -register struct monst *mtmp; +dochug(struct monst *mtmp) { - register struct permonst *mdat; - register int tmp = 0; - int inrange, nearby, scared; + struct permonst *mdat; + int status = MMOVE_NOTHING; + int inrange, nearby, scared, res; + struct obj *otmp; + boolean panicattk = FALSE; - /* Pre-movement adjustments + /* + * PHASE ONE: Pre-movement adjustments */ mdat = mtmp->data; if (mtmp->mstrategy & STRAT_ARRIVE) { - int res = m_arrival(mtmp); + res = m_arrival(mtmp); if (res >= 0) return res; } - /* check for waitmask status change */ if ((mtmp->mstrategy & STRAT_WAITFORU) && (m_canseeu(mtmp) || mtmp->mhp < mtmp->mhpmax)) @@ -419,16 +741,16 @@ register struct monst *mtmp; if (mtmp->mstun && !rn2(10)) mtmp->mstun = 0; - /* some monsters teleport */ + /* Some monsters teleport. Teleportation costs a turn. */ if (mtmp->mflee && !rn2(40) && can_teleport(mdat) && !mtmp->iswiz - && !level.flags.noteleport) { - (void) rloc(mtmp, TRUE); + && !noteleport_level(mtmp)) { + if (rloc(mtmp, RLOC_MSG)) + leppie_stash(mtmp); return 0; } - if (mdat->msound == MS_SHRIEK && !um_dist(mtmp->mx, mtmp->my, 1)) - m_respond(mtmp); - if (mdat == &mons[PM_MEDUSA] && couldsee(mtmp->mx, mtmp->my)) - m_respond(mtmp); + + /* some monsters have special abilities */ + m_respond(mtmp); if (DEADMONSTER(mtmp)) return 1; /* m_respond gaze can kill medusa */ @@ -437,27 +759,39 @@ register struct monst *mtmp; && !rn2(25)) mtmp->mflee = 0; - /* cease conflict-induced swallow/grab if conflict has ended */ + /* Cease conflict-induced swallow/grab if conflict has ended. Releasing + the hero in this way uses up the monster's turn. */ if (mtmp == u.ustuck && mtmp->mpeaceful && !mtmp->mconf && !Conflict) { release_hero(mtmp); - return 0; /* uses up monster's turn */ + return 0; } - set_apparxy(mtmp); - /* Must be done after you move and before the monster does. The - * set_apparxy() call in m_move() doesn't suffice since the variables - * inrange, etc. all depend on stuff set by set_apparxy(). + /* + * PHASE TWO: Special Movements and Actions + */ + + /* The monster decides where it thinks you are. This call to set_apparxy() + must be done after you move and before the monster does. The + set_apparxy() call in m_move() doesn't suffice since the variables + inrange, etc. all depend on stuff set by set_apparxy(). */ + set_apparxy(mtmp); - /* Monsters that want to acquire things */ - /* may teleport, so do it before inrange is set */ - if (is_covetous(mdat)) + /* Monsters that want to acquire things may teleport, so do it before + inrange is set. This costs a turn only if mstate is set. */ + if (is_covetous(mdat)) { (void) tactics(mtmp); + /* tactics -> mnexto -> deal_with_overcrowding */ + if (mtmp->mstate) + return 0; + set_apparxy(mtmp); + } /* check distance and scariness of attacks */ distfleeck(mtmp, &inrange, &nearby, &scared); - if (find_defensive(mtmp)) { + /* search for and potentially use defensive or miscellaneous items. */ + if (find_defensive(mtmp, FALSE)) { if (use_defensive(mtmp) != 0) return 1; } else if (find_misc(mtmp)) { @@ -472,13 +806,15 @@ register struct monst *mtmp; pline("%s whispers at thin air.", cansee(mtmp->mux, mtmp->muy) ? Monnam(mtmp) : "It"); - if (is_demon(youmonst.data)) { + if (is_demon(gy.youmonst.data)) { /* "Good hunting, brother" */ if (!tele_restrict(mtmp)) - (void) rloc(mtmp, TRUE); + (void) rloc(mtmp, RLOC_MSG); } else { mtmp->minvis = mtmp->perminvis = 0; /* Why? For the same reason in real demon talk */ + if (canseemon(mtmp)) + set_msg_xy(mtmp->mx, mtmp->my); pline("%s gets angry!", Amonnam(mtmp)); mtmp->mpeaceful = 0; set_malign(mtmp); @@ -491,59 +827,14 @@ register struct monst *mtmp; /* the watch will look around and see if you are up to no good :-) */ if (is_watch(mdat)) { watch_on_duty(mtmp); - + /* mind flayers can make psychic attacks! */ } else if (is_mind_flayer(mdat) && !rn2(20)) { - struct monst *m2, *nmon = (struct monst *) 0; - - if (canseemon(mtmp)) - pline("%s concentrates.", Monnam(mtmp)); - if (distu(mtmp->mx, mtmp->my) > BOLT_LIM * BOLT_LIM) { - You("sense a faint wave of psychic energy."); - goto toofar; - } - pline("A wave of psychic energy pours over you!"); - if (mtmp->mpeaceful - && (!Conflict || resist(mtmp, RING_CLASS, 0, 0))) { - pline("It feels quite soothing."); - } else if (!u.uinvulnerable) { - register boolean m_sen = sensemon(mtmp); - - if (m_sen || (Blind_telepat && rn2(2)) || !rn2(10)) { - int dmg; - pline("It locks on to your %s!", - m_sen ? "telepathy" : Blind_telepat ? "latent telepathy" - : "mind"); - dmg = rnd(15); - if (Half_spell_damage) - dmg = (dmg + 1) / 2; - losehp(dmg, "psychic blast", KILLED_BY_AN); - } - } - for (m2 = fmon; m2; m2 = nmon) { - nmon = m2->nmon; - if (DEADMONSTER(m2)) - continue; - if (m2->mpeaceful == mtmp->mpeaceful) - continue; - if (mindless(m2->data)) - continue; - if (m2 == mtmp) - continue; - if ((telepathic(m2->data) && (rn2(2) || m2->mblinded)) - || !rn2(10)) { - if (cansee(m2->mx, m2->my)) - pline("It locks on to %s.", mon_nam(m2)); - m2->mhp -= rnd(15); - if (DEADMONSTER(m2)) - monkilled(m2, "", AD_DRIN); - else - m2->msleeping = 0; - } - } + mind_blast(mtmp); + set_apparxy(mtmp); + distfleeck(mtmp, &inrange, &nearby, &scared); } - toofar: - /* If monster is nearby you, and has to wield a weapon, do so. This + /* If monster is nearby you, and has to wield a weapon, do so. This * costs the monster a move, of course. */ if ((!mtmp->mpeaceful || Conflict) && inrange @@ -568,43 +859,69 @@ register struct monst *mtmp; } } - /* Now the actual movement phase + /* + * PHASE THREE: Now the actual movement phase */ + /* A killer bee may eat honey in order to turn into a queen bee, + costing it a move. */ + if (mdat == &mons[PM_KILLER_BEE] + /* could be smarter and deliberately move to royal jelly, but + then we'd need to scan the level for queen bee in advance; + avoid that overhead and rely on serendipity... */ + && (otmp = sobj_at(LUMP_OF_ROYAL_JELLY, mtmp->mx, mtmp->my)) != 0 + && (res = bee_eat_jelly(mtmp, otmp)) >= 0) + return res; + + if (mdat == &mons[PM_GELATINOUS_CUBE] + && (res = gelcube_digests(mtmp)) >= 0) + return res; + + /* A monster that passes the following checks has the opportunity + to move. Movement itself is handled by the m_move() function. */ if (!nearby || mtmp->mflee || scared || mtmp->mconf || mtmp->mstun || (mtmp->minvis && !rn2(3)) - || (mdat->mlet == S_LEPRECHAUN && !findgold(invent) + || (mdat->mlet == S_LEPRECHAUN && !findgold(gi.invent) && (findgold(mtmp->minvent) || rn2(2))) || (is_wanderer(mdat) && !rn2(4)) || (Conflict && !mtmp->iswiz) || (!mtmp->mcansee && !rn2(4)) || mtmp->mpeaceful) { + /* Possibly cast an undirected spell if not attacking you */ /* note that most of the time castmu() will pick a directed spell and do nothing, so the monster moves normally */ /* arbitrary distance restriction to keep monster far away from you from having cast dozens of sticks-to-snakes or similar spells by the time you reach it */ - if (dist2(mtmp->mx, mtmp->my, u.ux, u.uy) <= 49 - && !mtmp->mspec_used) { + if (!mtmp->mspec_used + && dist2(mtmp->mx, mtmp->my, u.ux, u.uy) <= 49) { struct attack *a; for (a = &mdat->mattk[0]; a < &mdat->mattk[NATTK]; a++) { if (a->aatyp == AT_MAGC && (a->adtyp == AD_SPEL || a->adtyp == AD_CLRC)) { - if (castmu(mtmp, a, FALSE, FALSE)) { - tmp = 3; + if ((castmu(mtmp, a, FALSE, FALSE) & M_ATTK_HIT)) { + status = MMOVE_DONE; /* bypass m_move() */ break; } } } } - tmp = m_move(mtmp, 0); - if (tmp != 2) + if (!status) + status = m_move(mtmp, 0); + if (mon_offmap(mtmp)) + return 1; + if (status != MMOVE_DIED) distfleeck(mtmp, &inrange, &nearby, &scared); /* recalc */ - switch (tmp) { /* for pets, cases 0 and 3 are equivalent */ - case 0: /* no movement, but it can still attack you */ - case 3: /* absolutely no movement */ + switch (status) { /* for pets, cases 0 and 3 are equivalent */ + case MMOVE_NOMOVES: + if (scared) + panicattk = TRUE; + FALLTHROUGH; + /*FALLTHRU*/ + case MMOVE_NOTHING: /* no movement, but it can still attack you */ + case MMOVE_DONE: /* absolutely no movement */ /* vault guard might have vanished */ if (mtmp->isgd && (DEADMONSTER(mtmp) || mtmp->mx == 0)) return 1; /* behave as if it died */ @@ -614,52 +931,61 @@ register struct monst *mtmp; if (Hallucination) newsym(mtmp->mx, mtmp->my); break; - case 1: /* monster moved */ + case MMOVE_MOVED: /* monster moved */ + /* if confused grabber has wandered off, let go */ + if (mtmp == u.ustuck && !m_next2u(mtmp)) + unstuck(mtmp); + if (grounded(mdat)) + disturb_buried_zombies(mtmp->mx, mtmp->my); /* Maybe it stepped on a trap and fell asleep... */ - if (mtmp->msleeping || !mtmp->mcanmove) + if (helpless(mtmp)) return 0; /* Monsters can move and then shoot on same turn; our hero can't. Is that fair? */ - if (!nearby && (ranged_attk(mdat) || find_offensive(mtmp))) + if (!nearby + && (ranged_attk_available(mtmp) + || attacktype(mdat, AT_WEAP) + || find_offensive(mtmp))) break; - /* engulfer/grabber checks */ - if (mtmp == u.ustuck) { - /* a monster that's digesting you can move at the - * same time -dlc - */ - if (u.uswallow) - return mattacku(mtmp); - /* if confused grabber has wandered off, let go */ - if (distu(mtmp->mx, mtmp->my) > 2) - unstuck(mtmp); - } + /* a monster that's digesting you can move at the + * same time -dlc + */ + if (engulfing_u(mtmp)) + return mattacku(mtmp); return 0; - case 2: /* monster died */ + case MMOVE_DIED: /* monster died */ return 1; } } - /* Now, attack the player if possible - one attack set per monst + /* + * PHASE FOUR: Standard Attacks */ - if (!mtmp->mpeaceful || (Conflict && !resist(mtmp, RING_CLASS, 0, 0))) { - if (inrange && !noattacks(mdat) - && (Upolyd ? u.mh : u.uhp) > 0 && !scared && tmp != 3) + /* Now, attack the player if possible - one attack set per monst */ + if (status != MMOVE_DONE && (!mtmp->mpeaceful + || (Conflict && !resist_conflict(mtmp)))) { + if (((inrange && !scared) || panicattk) && !noattacks(mdat) + /* [is this hp check really needed?] */ + && (Upolyd ? u.mh : u.uhp) > 0) { if (mattacku(mtmp)) return 1; /* monster died (e.g. exploded) */ - - if (mtmp->wormno) - wormhitu(mtmp); + } + if (mtmp->wormno) { + if (wormhitu(mtmp)) + return 1; /* worm died (poly'd hero passive counter-attack) */ + } } /* special speeches for quest monsters */ - if (!mtmp->msleeping && mtmp->mcanmove && nearby) + if (!helpless(mtmp) && nearby) quest_talk(mtmp); /* extra emotional attack for vile monsters */ if (inrange && mtmp->data->msound == MS_CUSS && !mtmp->mpeaceful && couldsee(mtmp->mx, mtmp->my) && !mtmp->minvis && !rn2(5)) cuss(mtmp); - return (tmp == 2); + /* note: can't get here when monster is dead, so this always returns 0 */ + return (status == MMOVE_DIED); } static NEARDATA const char practical[] = { WEAPON_CLASS, ARMOR_CLASS, @@ -667,16 +993,67 @@ static NEARDATA const char practical[] = { WEAPON_CLASS, ARMOR_CLASS, 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 }; + +/* monster mtmp would love to take object otmp? */ +boolean +mon_would_take_item(struct monst *mtmp, struct obj *otmp) +{ + int pctload = (curr_mon_load(mtmp) * 100) / max_mon_load(mtmp); + + if (otmp == uball || otmp == uchain) + return FALSE; + if (mtmp->mtame && otmp->cursed) + return FALSE; /* note: will get overridden if mtmp will eat otmp */ + if (is_unicorn(mtmp->data) && objects[otmp->otyp].oc_material != GEMSTONE) + return FALSE; + if (!mindless(mtmp->data) && !is_animal(mtmp->data) && pctload < 75 + && searches_for_item(mtmp, otmp)) + return TRUE; + if (likes_gold(mtmp->data) && otmp->otyp == GOLD_PIECE && pctload < 95) + return TRUE; + if (likes_gems(mtmp->data) && otmp->oclass == GEM_CLASS + && objects[otmp->otyp].oc_material != MINERAL && pctload < 85) + return TRUE; + if (likes_objs(mtmp->data) && strchr(practical, otmp->oclass) + && pctload < 75) + return TRUE; + if (likes_magic(mtmp->data) && strchr(magical, otmp->oclass) + && pctload < 85) + return TRUE; + if (throws_rocks(mtmp->data) && otmp->otyp == BOULDER + && pctload < 50 && !Sokoban) + return TRUE; + if (mtmp->data == &mons[PM_GELATINOUS_CUBE] + && otmp->oclass != ROCK_CLASS && otmp->oclass != BALL_CLASS + && !(otmp->otyp == CORPSE && touch_petrifies(&mons[otmp->corpsenm]))) + return TRUE; + + return FALSE; +} + +/* monster mtmp would love to consume object otmp, without picking it up */ +boolean +mon_would_consume_item(struct monst *mtmp, struct obj *otmp) +{ + int ftyp; + + if (otmp->otyp == CORPSE && !touch_petrifies(&mons[otmp->corpsenm]) + && corpse_eater(mtmp->data)) + return TRUE; + + if (mtmp->mtame && has_edog(mtmp) /* has_edog(): not guardian angel */ + && (ftyp = dogfood(mtmp, otmp)) < MANFOOD + && (ftyp < ACCFOOD || EDOG(mtmp)->hungrytime <= svm.moves)) + return TRUE; + + return FALSE; +} boolean -itsstuck(mtmp) -register struct monst *mtmp; +itsstuck(struct monst *mtmp) { - if (sticks(youmonst.data) && mtmp == u.ustuck && !u.uswallow) { - pline("%s cannot escape from you!", Monnam(mtmp)); + if (sticks(gy.youmonst.data) && mtmp == u.ustuck && !u.uswallow) { + pline_mon(mtmp, "%s cannot escape from you!", Monnam(mtmp)); return TRUE; } return FALSE; @@ -690,24 +1067,23 @@ register struct monst *mtmp; * those should be used instead. This function does that evaluation. */ boolean -should_displace(mtmp, poss, info, cnt, gx, gy) -struct monst *mtmp; -coord *poss; /* coord poss[9] */ -long *info; /* long info[9] */ -int cnt; -xchar gx, gy; +should_displace( + struct monst *mtmp, + const struct mfndposdata *data, + coordxy ggx, + coordxy ggy) { int shortest_with_displacing = -1; int shortest_without_displacing = -1; int count_without_displacing = 0; - register int i, nx, ny; + int i, nx, ny; int ndist; - for (i = 0; i < cnt; i++) { - nx = poss[i].x; - ny = poss[i].y; - ndist = dist2(nx, ny, gx, gy); - if (MON_AT(nx, ny) && (info[i] & ALLOW_MDISP) && !(info[i] & ALLOW_M) + for (i = 0; i < data->cnt; i++) { + nx = data->poss[i].x; + ny = data->poss[i].y; + ndist = dist2(nx, ny, ggx, ggy); + if (MON_AT(nx, ny) && (data->info[i] & ALLOW_MDISP) && !(data->info[i] & ALLOW_M) && !undesirable_disp(mtmp, nx, ny)) { if (shortest_with_displacing == -1 || (ndist < shortest_with_displacing)) @@ -726,12 +1102,14 @@ xchar gx, gy; return FALSE; } +/* have monster wield a pick-axe if it wants to dig and it has one; + return True if it spends this move wielding one, False otherwise */ boolean -m_digweapon_check(mtmp, nix, niy) -struct monst *mtmp; -xchar nix,niy; +m_digweapon_check( + struct monst *mtmp, + coordxy nix, coordxy niy) { - boolean can_tunnel = 0; + boolean can_tunnel = FALSE; struct obj *mw_tmp = MON_WEP(mtmp); if (!Is_rogue_level(&u.uz)) @@ -741,15 +1119,13 @@ xchar nix,niy; && (may_dig(nix, niy) || closed_door(nix, niy))) { /* may_dig() is either IS_STWALL or IS_TREE */ if (closed_door(nix, niy)) { - if (!mw_tmp - || !is_pick(mw_tmp) - || !is_axe(mw_tmp)) + if (!mw_tmp || !is_pick(mw_tmp) || !is_axe(mw_tmp)) mtmp->weapon_check = NEED_PICK_OR_AXE; } else if (IS_TREE(levl[nix][niy].typ)) { - if (!(mw_tmp = MON_WEP(mtmp)) || !is_axe(mw_tmp)) + if (!mw_tmp || !is_axe(mw_tmp)) mtmp->weapon_check = NEED_AXE; } else if (IS_STWALL(levl[nix][niy].typ)) { - if (!(mw_tmp = MON_WEP(mtmp)) || !is_pick(mw_tmp)) + if (!mw_tmp || !is_pick(mw_tmp)) mtmp->weapon_check = NEED_PICK_AXE; } if (mtmp->weapon_check >= NEED_PICK_AXE && mon_wield_item(mtmp)) @@ -758,44 +1134,611 @@ xchar nix,niy; return FALSE; } -/* Return values: - * 0: did not move, but can still attack and do other stuff. - * 1: moved, possibly can attack. - * 2: monster died. +/* does leprechaun want to avoid the hero? */ +staticfn boolean +leppie_avoidance(struct monst *mtmp) +{ + struct obj *lepgold, *ygold; + + if (mtmp->data == &mons[PM_LEPRECHAUN] + && ((lepgold = findgold(mtmp->minvent)) + && (lepgold->quan + > ((ygold = findgold(gi.invent)) ? ygold->quan : 0L)))) + return TRUE; + + return FALSE; +} + +/* unseen leprechaun with gold might stash it */ +staticfn void +leppie_stash(struct monst *mtmp) +{ + struct obj *gold; + + if (mtmp->data == &mons[PM_LEPRECHAUN] + && !DEADMONSTER(mtmp) + && !m_canseeu(mtmp) + && !*in_rooms(mtmp->mx, mtmp->my, SHOPBASE) + && levl[mtmp->mx][mtmp->my].typ == ROOM + && !t_at(mtmp->mx, mtmp->my) + && rn2(4) + && (gold = findgold(mtmp->minvent)) != 0) { + mdrop_obj(mtmp, gold, FALSE); + gold = g_at(mtmp->mx, mtmp->my); + if (gold) + (void) bury_an_obj(gold, (boolean *) 0); + } +} + +/* does monster want to avoid you? + * returns the original value of appr if not. + * returns -1 if so. + * returns -2 if monster wants to adhere to a particular range, + * which may actually be further away, + * and sets *pdistmin and *pdistmax to describe that range + */ +staticfn int +m_balks_at_approaching(int oldappr, struct monst *mtmp, int *pdistmin, + int *pdistmax) +{ + struct obj *mwep = MON_WEP(mtmp); + coordxy x = mtmp->mx, y = mtmp->my, ux = mtmp->mux, uy = mtmp->muy; + int edist = dist2(x, y, ux, uy); + const struct throw_and_return_weapon *arw; + + if (pdistmin) + *pdistmin = 0; + if (pdistmax) + *pdistmax = 0; + + /* peaceful, far away, or can't see you */ + if (mtmp->mpeaceful || (edist >= 5 * 5) || !m_canseeu(mtmp)) + return oldappr; + + /* has ammo+launcher */ + if (m_has_launcher_and_ammo(mtmp)) + return -1; + + /* is using a polearm and in range */ + if (MON_WEP(mtmp) && is_pole(MON_WEP(mtmp)) + && edist <= MON_POLE_DIST) + return -1; + + /* is using a throw-and-return weapon; provide min and max preferred range + */ + if (mwep && (arw = autoreturn_weapon(mwep)) != 0) { + if (pdistmin) + *pdistmin = 2 * 2; + if (pdistmax) + *pdistmax = arw->range; + return -2; + } + + /* can attack from distance, and hp loss or attack not used */ + if (ranged_attk_available(mtmp) + && ((mtmp->mhp < (mtmp->mhpmax+1) / 3) + || !mtmp->mspec_used)) + return -1; + + return oldappr; /* leaves appr unchanged */ +} + +staticfn boolean +holds_up_web(coordxy x, coordxy y) +{ + stairway *sway; + + if (!isok(x, y) + || IS_OBSTRUCTED(levl[x][y].typ) + || ((levl[x][y].typ == STAIRS || levl[x][y].typ == LADDER) + && (sway = stairway_at(x, y)) != 0 && sway->up) + || levl[x][y].typ == IRONBARS) + return TRUE; + + return FALSE; +} + +/* returns the number of walls in the four cardinal directions that could + hold up a web */ +staticfn int +count_webbing_walls(coordxy x, coordxy y) +{ + return (holds_up_web(x, y - 1) + holds_up_web(x + 1, y) + + holds_up_web(x, y + 1) + holds_up_web(x - 1, y)); +} + +/* reject webs which interfere with solving Sokoban */ +staticfn boolean +soko_allow_web(struct monst *mon) +{ + stairway *stway; + + /* for a non-Sokoban level or a solved Sokoban level, no restriction */ + if (!Sokoban) + return TRUE; + /* not-yet-solved Sokoban level: allow web only when spinner can see + the stairs up [we really want 'is in same chamber as stairs up'] */ + stway = stairway_find_dir(TRUE); /* stairs up */ + if (stway && m_cansee(mon, stway->sx, stway->sy)) + return TRUE; + return FALSE; +} + +/* monster might spin a web */ +staticfn void +maybe_spin_web(struct monst *mtmp) +{ + if (webmaker(mtmp->data) + && !helpless(mtmp) && !mtmp->mspec_used + && !t_at(mtmp->mx, mtmp->my) && soko_allow_web(mtmp)) { + struct trap *trap; + int prob = ((((mtmp->data == &mons[PM_GIANT_SPIDER]) ? 15 : 5) + * (count_webbing_walls(mtmp->mx, mtmp->my) + 1)) + - (3 * count_traps(WEB))); + + if (rn2(1000) < prob + && (trap = maketrap(mtmp->mx, mtmp->my, WEB)) != 0) { + mtmp->mspec_used = d(4, 4); /* 4..16 */ + if (cansee(mtmp->mx, mtmp->my)) { + char mbuf[BUFSZ]; + + Strcpy(mbuf, canspotmon(mtmp) ? y_monnam(mtmp) : something); + pline_mon(mtmp, "%s spins a web.", upstart(mbuf)); + trap->tseen = 1; + } + if (*in_rooms(mtmp->mx, mtmp->my, SHOPBASE)) + add_damage(mtmp->mx, mtmp->my, 0L); + } + } +} + +/* monster avoids a location nx, ny, if hero kicked that location */ +boolean +m_avoid_kicked_loc(struct monst *mtmp, coordxy nx, coordxy ny) +{ + if ((mtmp->mpeaceful || mtmp->mtame) + && mtmp->mcansee + && !mtmp->mconf && !mtmp->mstun + && !Conflict + && isok(gk.kickedloc.x, gk.kickedloc.y) + && nx == gk.kickedloc.x && ny == gk.kickedloc.y + && next2u(nx, ny)) + return TRUE; + return FALSE; +} + +/* monster avoids a location nx, ny, if we're in sokoban, and + there's a boulder between the location and hero */ +boolean +m_avoid_soko_push_loc(struct monst *mtmp, coordxy nx, coordxy ny) +{ + if (Sokoban + && (mtmp->mpeaceful || mtmp->mtame) + && !mtmp->mconf && !mtmp->mstun + && !Conflict + && (dist2(nx, ny, u.ux, u.uy) == 4) + && sobj_at(BOULDER, nx + sgn(u.ux - nx), ny + sgn(u.uy - ny))) + return TRUE; + return FALSE; +} + +/* max distmin() distance for monster to look for items */ +#define SQSRCHRADIUS 5 + +/* monster looks for items it wants nearby */ +staticfn boolean +m_search_items( + struct monst *mtmp, + coordxy *ggx, coordxy *ggy, + int *mmoved, + int *appr) +{ + int minr = SQSRCHRADIUS; /* not too far away */ + struct obj *otmp; + coordxy xx, yy; + coordxy hmx, hmy, lmx, lmy; + struct trap *ttmp; + coordxy omx = mtmp->mx, omy = mtmp->my; + struct permonst *ptr = mtmp->data; + struct monst *mtoo; + boolean costly; + + /* cut down the search radius if it thinks character is closer. */ + if (distmin(mtmp->mux, mtmp->muy, omx, omy) < SQSRCHRADIUS + && !mtmp->mpeaceful) + minr--; + /* guards shouldn't get too distracted */ + if (!mtmp->mpeaceful && is_mercenary(ptr)) + minr = 1; + + /* in shop, usually skip */ + if (*in_rooms(omx, omy, SHOPBASE) && (rn2(25) || mtmp->isshk)) + goto finish_search; + + /* distmin() gives a rectangular area */ + hmx = min(COLNO - 1, omx + minr); + hmy = min(ROWNO - 1, omy + minr); + lmx = max(1, omx - minr); + lmy = max(0, omy - minr); + + for (xx = lmx; xx <= hmx; xx++) { + for (yy = lmy; yy <= hmy; yy++) { + /* no object here */ + if (!OBJ_AT(xx, yy)) + continue; + /* found an object closer already */ + if (minr < distmin(omx, omy, xx, yy)) + continue; + /* the mfndpos() test for whether to allow a move to a + water location accepts flyers, but they can't reach + underwater objects, so being able to move to a spot + is insufficient for deciding whether to do so */ + if (!could_reach_item(mtmp, xx, yy)) + continue; + /* hiders avoid hero's line of sight */ + if (hides_under(ptr) && cansee(xx, yy)) + continue; + /* don't get stuck circling around object that's + underneath an immobile or hidden monster; + paralysis victims excluded */ + if ((mtoo = m_at(xx, yy)) != 0 + && (helpless(mtoo) || mtoo->mundetected + || (mtoo->mappearance && !mtoo->iswiz) + || !mtoo->data->mmove)) + continue; + /* Don't get stuck circling an Elbereth */ + if (onscary(xx, yy, mtmp)) + continue; + /* ignore obj if there's a trap and monster knows it */ + if ((ttmp = t_at(xx, yy)) != 0 + && mon_knows_traps(mtmp, ttmp->ttyp)) { + if (*ggx == xx && *ggy == yy) { + *ggx = mtmp->mux; + *ggy = mtmp->muy; + } + continue; + } + /* avoid getting stuck on eg. items in niches */ + if (!m_cansee(mtmp, xx, yy)) + continue; + + costly = costly_spot(xx, yy); + + /* look through the items on this location */ + for (otmp = svl.level.objects[xx][yy]; + otmp; otmp = otmp->nexthere) { + /* monsters may pick rocks up, but won't go out of their way + to grab them; this might hamper sling wielders, but it cuts + down on move overhead by filtering out most common item */ + if (otmp->otyp == ROCK) + continue; + /* avoid special items; once hero picks them up, they'll + cease being special */ + if (is_mines_prize(otmp) || is_soko_prize(otmp)) + continue; + /* skip shop merchandise */ + if (costly && !otmp->no_charge) + continue; + + if (((mon_would_take_item(mtmp, otmp) + && (can_carry(mtmp, otmp) > 0)) + || mon_would_consume_item(mtmp, otmp)) + && can_touch_safely(mtmp, otmp)) { + minr = distmin(omx, omy, xx, yy); + *ggx = otmp->ox; + *ggy = otmp->oy; + if (*ggx == omx && *ggy == omy) { + *mmoved = MMOVE_DONE; /* actually unnecessary */ + return TRUE; + } + /* found an item of interest; skip the rest of the pile */ + break; + } + } + } + } + + finish_search: + if (minr < SQSRCHRADIUS && *appr == -1) { + if (distmin(omx, omy, mtmp->mux, mtmp->muy) <= 3) { + *ggx = mtmp->mux; + *ggy = mtmp->muy; + } else + *appr = 1; + } + return FALSE; +} + +#undef SQSRCHRADIUS + +staticfn int +postmov( + struct monst *mtmp, + struct permonst *ptr, + coordxy omx, coordxy omy, + int mmoved, + unsigned seenflgs, + boolean can_tunnel, + boolean can_unlock, + boolean can_open) +{ + coordxy nix, niy; + int etmp, trapret; + boolean canseeit = cansee(mtmp->mx, mtmp->my), + didseeit = canseeit; + + notice_mon(mtmp); + + if (mmoved == MMOVE_MOVED) { + nix = mtmp->mx, niy = mtmp->my; + /* sequencing issue: when monster movement decides that a + monster can move to a door location, it moves the monster + there before dealing with the door rather than after; + so a vampire/bat that is going to shift to fog cloud and + pass under the door is already there but transformation + into fog form--and its message, when in sight--has not + happened yet; we have to move monster back to previous + location before performing the vamp_shift() to make the + message happen at right time, then back to the door again + [if we did the shift sooner, before moving the monster, + we would need to duplicate it in dog_move()...] */ + if (is_vampshifter(mtmp) && !amorphous(mtmp->data) + && IS_DOOR(levl[nix][niy].typ) + && ((levl[nix][niy].doormask & (D_LOCKED | D_CLOSED)) != 0) + && can_fog(mtmp)) { + /* note: remove_monster()+place_monster is not right for + long worms but they won't reach here */ + if (seenflgs) { + remove_monster(nix, niy); + place_monster(mtmp, omx, omy); + newsym(nix, niy), newsym(omx, omy); + } + if (vamp_shift(mtmp, &mons[PM_FOG_CLOUD], + ((seenflgs & 1) != 0) ? TRUE : FALSE)) { + ptr = mtmp->data; /* update cached value */ + nhUse(ptr); + } + if (seenflgs) { + remove_monster(omx, omy); + place_monster(mtmp, nix, niy); + newsym(omx, omy), newsym(nix, niy); + } + } + + newsym(omx, omy); /* update the old position */ + trapret = mintrap(mtmp, NO_TRAP_FLAGS); + if (trapret == Trap_Killed_Mon || trapret == Trap_Moved_Mon) { + if (mtmp->mx) + newsym(mtmp->mx, mtmp->my); + return MMOVE_DIED; /* it died */ + } else if (mon_offmap(mtmp)) { + return MMOVE_DONE; + } + ptr = mtmp->data; /* in case mintrap() caused polymorph */ + + /* open a door, or crash through it, if 'mtmp' can */ + if (IS_DOOR(levl[mtmp->mx][mtmp->my].typ) + && !passes_walls(ptr) /* doesn't need to open doors */ + && !can_tunnel) { /* taken care of below */ + struct rm *here = &levl[mtmp->mx][mtmp->my]; + boolean btrapped = (here->doormask & D_TRAPPED) != 0; + + /* used after monster 'who' has been moved to closed door spot 'where' + which will now be changed to door state 'what' with map update */ +#define UnblockDoor(where,who,what) \ + do { \ + (where)->doormask = (what); \ + newsym((who)->mx, (who)->my); \ + recalc_block_point((who)->mx, (who)->my); \ + vision_recalc(0); \ + /* update cached value since it might change */ \ + canseeit = didseeit || cansee((who)->mx, (who)->my); \ + } while (0) + + /* if mon has MKoT, disarm door trap; no message given */ + if (btrapped && has_magic_key(mtmp)) { + /* BUG: this lets a vampire or blob or a doorbuster + holding the Key disarm the trap even though it isn't + using that Key when squeezing under or smashing the + door. Not significant enough to worry about; perhaps + the Key's magic is more powerful for monsters? */ + here->doormask &= ~D_TRAPPED; + btrapped = FALSE; + } + if ((here->doormask & (D_LOCKED | D_CLOSED)) != 0 + && amorphous(ptr)) { + if (flags.verbose && canseemon(mtmp)) + pline_mon(mtmp, "%s %s under the door.", YMonnam(mtmp), + (ptr == &mons[PM_FOG_CLOUD] + || ptr->mlet == S_LIGHT) ? "flows" : "oozes"); + } else if ((here->doormask & D_LOCKED) != 0 && can_unlock) { + /* like the vampshift hack, there are sequencing + issues when the monster is moved to the door's spot + first then door handling plus feedback comes after */ + + UnblockDoor(here, mtmp, !btrapped ? D_ISOPEN : D_NODOOR); + if (btrapped) { + if (mb_trapped(mtmp, canseeit)) + return MMOVE_DIED; + } else { + Soundeffect(se_door_unlock_and_open, 50); + if (flags.verbose) { + if (canseeit && canspotmon(mtmp)) { + pline_mon(mtmp, "%s unlocks and opens a door.", + Monnam(mtmp)); + } else if (canseeit) { + You_see("a door unlock and open."); + } else if (!Deaf) { + You_hear("a door unlock and open."); + } + } + } + } else if (here->doormask == D_CLOSED && can_open) { + UnblockDoor(here, mtmp, !btrapped ? D_ISOPEN : D_NODOOR); + if (btrapped) { + if (mb_trapped(mtmp, canseeit)) + return MMOVE_DIED; + } else { + Soundeffect(se_door_open, 100); + if (flags.verbose) { + if (canseeit && canspotmon(mtmp)) { + pline_mon(mtmp, "%s opens a door.", Monnam(mtmp)); + } else if (canseeit) { + You_see("a door open."); + } else if (!Deaf) { + You_hear("a door open."); + } + } + } + } else if ((here->doormask & (D_LOCKED | D_CLOSED)) != 0) { + /* mfndpos guarantees this must be a doorbuster */ + unsigned mask; + + mask = ((btrapped + || ((here->doormask & D_LOCKED) != 0 && !rn2(2))) + ? D_NODOOR + : D_BROKEN); + UnblockDoor(here, mtmp, mask); + if (btrapped) { + if (mb_trapped(mtmp, canseeit)) + return MMOVE_DIED; + } else { + Soundeffect(se_door_crash_open, 50); + if (flags.verbose) { + if (canseeit && canspotmon(mtmp)) { + pline_mon(mtmp, "%s smashes down a door.", + Monnam(mtmp)); + } else if (canseeit) { + You_see("a door crash open."); + } else if (!Deaf) { + You_hear("a door crash open."); + } + } + } + /* if it's a shop door, schedule repair */ + if (*in_rooms(mtmp->mx, mtmp->my, SHOPBASE)) + add_damage(mtmp->mx, mtmp->my, 0L); + } +#undef UnblockDoor + + } else if (levl[mtmp->mx][mtmp->my].typ == IRONBARS) { + /* 3.6.2: was using may_dig() but that doesn't handle bars; + AD_RUST catches rust monsters but metallivorous() is + needed for xorns and rock moles */ + if (!(levl[mtmp->mx][mtmp->my].wall_info & W_NONDIGGABLE) + && (dmgtype(ptr, AD_RUST) || dmgtype(ptr, AD_CORR) + || metallivorous(ptr))) { + if (canseemon(mtmp)) + pline_mon(mtmp, "%s eats through the iron bars.", + Monnam(mtmp)); + dissolve_bars(mtmp->mx, mtmp->my); + return MMOVE_DONE; + } else if (flags.verbose && canseemon(mtmp)) + Norep("%s %s %s the iron bars.", Monnam(mtmp), + /* pluralization fakes verb conjugation */ + makeplural(locomotion(ptr, "pass")), + passes_walls(ptr) ? "through" : "between"); + } /* doors and bars */ + + /* possibly dig */ + if (can_tunnel && may_dig(mtmp->mx, mtmp->my) + && mdig_tunnel(mtmp)) + return MMOVE_DIED; /* mon died (position already updated) */ + + /* set also in domove(), hack.c */ + if (engulfing_u(mtmp) && (mtmp->mx != omx || mtmp->my != omy)) { + /* If the monster moved, then update */ + u.ux0 = u.ux; + u.uy0 = u.uy; + u_on_newpos(mtmp->mx, mtmp->my); + swallowed(0); + } else { + newsym(mtmp->mx, mtmp->my); + } + } /* mmoved==MMOVE_MOVED */ + + if (mmoved == MMOVE_MOVED || mmoved == MMOVE_DONE) { + if (OBJ_AT(mtmp->mx, mtmp->my) && mtmp->mcanmove) { + + /* Maybe a rock mole just ate some metal object */ + if (metallivorous(ptr)) { + if (meatmetal(mtmp) == 2) + return MMOVE_DIED; /* it died */ + } + + /* Maybe a cube ate just about anything */ + if (ptr == &mons[PM_GELATINOUS_CUBE]) { + if ((etmp = meatobj(mtmp)) >= 2) + return etmp; /* it died or got forced off the level */ + } + /* Maybe a purple worm ate a corpse */ + if (corpse_eater(ptr)) { + if ((etmp = meatcorpse(mtmp)) >= 2) + return etmp; /* it died or got forced off the level */ + } + + if (mpickstuff(mtmp)) + mmoved = MMOVE_DONE; + + if (mtmp->minvis) { + newsym(mtmp->mx, mtmp->my); + if (mtmp->wormno) + see_wsegs(mtmp); + } + } + + maybe_spin_web(mtmp); + + if (hides_under(ptr) || ptr->mlet == S_EEL) { + /* Always set--or reset--mundetected if it's already hidden + (just in case the object it was hiding under went away); + usually set mundetected unless monster can't move. */ + if (mtmp->mundetected || (!helpless(mtmp) && rn2(5))) + (void) hideunder(mtmp); + newsym(mtmp->mx, mtmp->my); + } + if (mtmp->isshk) { + after_shk_move(mtmp); + } + } + return mmoved; +} + +/* Handles the movement of a standard monster. + * Return values: + * 0: did not move, but can still attack and do other stuff; + * 1: moved, possibly can attack; + * 2: monster died; * 3: did not move, and can't do anything else either. */ int -m_move(mtmp, after) -register struct monst *mtmp; -register int after; +m_move(struct monst *mtmp, int after) { - register int appr; - xchar gx, gy, nix, niy, chcnt; - int chi; /* could be schar except for stupid Sun-2 compiler */ - boolean likegold = 0, likegems = 0, likeobjs = 0, likemagic = 0, - conceals = 0; - boolean likerock = 0, can_tunnel = 0; - boolean can_open = 0, can_unlock = 0, doorbuster = 0; - boolean uses_items = 0, setlikes = 0; + int appr; + coordxy ggx, ggy, nix, niy; + xint16 chcnt; + boolean can_tunnel = 0; + boolean can_open = 0, can_unlock = 0 /*, doorbuster = 0 */; + boolean getitems = FALSE; boolean avoid = FALSE; boolean better_with_displacing = FALSE; - boolean sawmon = canspotmon(mtmp); /* before it moved */ + unsigned seenflgs; struct permonst *ptr; - struct monst *mtoo; - schar mmoved = 0; /* not strictly nec.: chi >= 0 will do */ - long info[9]; + int chi, mmoved = MMOVE_NOTHING, /* not strictly nec.: chi >= 0 will do */ + preferredrange_min = 0, preferredrange_max = 0; + struct mfndposdata mfp; long flag; - int omx = mtmp->mx, omy = mtmp->my; + coordxy omx = mtmp->mx, omy = mtmp->my; if (mtmp->mtrapped) { - int i = mintrap(mtmp); + int i = mintrap(mtmp, NO_TRAP_FLAGS); - if (i >= 2) { + if (i == Trap_Killed_Mon) { newsym(mtmp->mx, mtmp->my); - return 2; + return MMOVE_DIED; } /* it died */ - if (i == 1) - return 0; /* still in trap, so didn't move */ + if (i == Trap_Caught_Mon) + return MMOVE_NOTHING; /* still in trap, so didn't move */ } ptr = mtmp->data; /* mintrap() can change mtmp->data -dlc */ @@ -803,86 +1746,94 @@ register int after; mtmp->meating--; if (mtmp->meating <= 0) finish_meating(mtmp); - return 3; /* still eating */ + return MMOVE_DONE; /* still eating */ } - if (hides_under(ptr) && OBJ_AT(mtmp->mx, mtmp->my) && rn2(10)) - return 0; /* do not leave hiding place */ + if (hides_under(ptr) && OBJ_AT(mtmp->mx, mtmp->my) + && can_hide_under_obj(svl.level.objects[mtmp->mx][mtmp->my]) + && rn2(10)) + return MMOVE_NOTHING; /* do not leave hiding place */ + + /* set up pre-move visibility flags */ + seenflgs = (canseemon(mtmp) ? 1 : 0) | (canspotmon(mtmp) ? 2 : 0); + + /* Where does 'mtmp' think you are? Not necessary if m_move() called + from this file, but needed for other calls of m_move(). */ + set_apparxy(mtmp); /* set mtmp->mux, mtmp->muy */ - set_apparxy(mtmp); - /* where does mtmp think you are? */ - /* Not necessary if m_move called from this file, but necessary in - * other calls of m_move (ex. leprechauns dodging) - */ if (!Is_rogue_level(&u.uz)) can_tunnel = tunnels(ptr); can_open = !(nohands(ptr) || verysmall(ptr)); - can_unlock = - ((can_open && monhaskey(mtmp, TRUE)) || mtmp->iswiz || is_rider(ptr)); - doorbuster = is_giant(ptr); + can_unlock = ((can_open && monhaskey(mtmp, TRUE)) + || mtmp->iswiz || is_rider(ptr)); + /* doorbuster = is_giant(ptr); */ if (mtmp->wormno) goto not_special; /* my dog gets special treatment */ if (mtmp->mtame) { - mmoved = dog_move(mtmp, after); - goto postmov; - } - - /* likewise for shopkeeper */ - if (mtmp->isshk) { - mmoved = shk_move(mtmp); - if (mmoved == -2) - return 2; - if (mmoved >= 0) - goto postmov; - mmoved = 0; /* follow player outside shop */ - } - - /* and for the guard */ - if (mtmp->isgd) { - mmoved = gd_move(mtmp); - if (mmoved == -2) - return 2; - if (mmoved >= 0) - goto postmov; - mmoved = 0; + return postmov(mtmp, ptr, omx, omy, dog_move(mtmp, after), + seenflgs, can_tunnel, can_unlock, can_open); } /* and the acquisitive monsters get special treatment */ - if (is_covetous(ptr)) { - xchar tx = STRAT_GOALX(mtmp->mstrategy), - ty = STRAT_GOALY(mtmp->mstrategy); - struct monst *intruder = m_at(tx, ty); + if (is_covetous(ptr)) { /* [should this include + * '&& mtmp->mstrategy != STRAT_NONE'?] */ + int covetousattack; + coordxy tx = mtmp->mgoal.x, + ty = mtmp->mgoal.y; + struct monst *intruder = isok(tx, ty) ? m_at(tx, ty) : NULL; /* * if there's a monster on the object or in possession of it, * attack it. */ - if ((dist2(mtmp->mx, mtmp->my, tx, ty) < 2) && intruder - && (intruder != mtmp)) { - notonhead = (intruder->mx != tx || intruder->my != ty); - if (mattackm(mtmp, intruder) == 2) - return 2; - mmoved = 1; - } else - mmoved = 0; - goto postmov; + if (intruder && intruder != mtmp + /* 5.0: this used to use 'dist2() < 2' which meant that intended + attack was disallowed if they were adjacent diagonally */ + && dist2(mtmp->mx, mtmp->my, tx, ty) <= 2) { + gb.bhitpos.x = tx, gb.bhitpos.y = ty; + gn.notonhead = (intruder->mx != tx || intruder->my != ty); + covetousattack = mattackm(mtmp, intruder); + /* 5.0: this used to erroneously use '== 2' (M_ATTK_DEF_DIED) */ + if (covetousattack & M_ATTK_AGR_DIED) + return MMOVE_DIED; + mmoved = MMOVE_MOVED; + return postmov(mtmp, ptr, omx, omy, mmoved, + seenflgs, can_tunnel, can_unlock, can_open); + } + /* otherwise continue with normal AI routine */ } - /* and for the priest */ - if (mtmp->ispriest) { - mmoved = pri_move(mtmp); - if (mmoved == -2) - return 2; - if (mmoved >= 0) - goto postmov; - mmoved = 0; + /* likewise for shopkeeper, guard, or priest */ + if (mtmp->isshk || mtmp->isgd || mtmp->ispriest) { + int xm = mtmp->isshk ? shk_move(mtmp) + : mtmp->isgd ? gd_move(mtmp) + : pri_move(mtmp); + + switch (xm) { + case -2: + return MMOVE_DIED; + case -1: + mmoved = MMOVE_NOTHING; /* shk follow hero outside shop */ + break; + default: + impossible("unknown shk/gd/pri_move return value (%d)", xm); + FALLTHROUGH; + /*FALLTHRU*/ + case 0: + case 1: + return postmov(mtmp, ptr, omx, omy, + (xm != 1) ? MMOVE_NOTHING : MMOVE_MOVED, + seenflgs, can_tunnel, can_unlock, can_open); + } } -#ifdef MAIL +#ifdef MAIL_STRUCTURES if (ptr == &mons[PM_MAIL_DAEMON]) { - if (!Deaf && canseemon(mtmp)) + if (!Deaf && canseemon(mtmp)) { + SetVoice(mtmp, 0, 80, 0); verbalize("I'm late!"); + } mongone(mtmp); - return 2; + return MMOVE_DIED; } #endif @@ -890,185 +1841,71 @@ register int after; if (ptr == &mons[PM_TENGU] && !rn2(5) && !mtmp->mcan && !tele_restrict(mtmp)) { if (mtmp->mhp < 7 || mtmp->mpeaceful || rn2(2)) - (void) rloc(mtmp, TRUE); + (void) rloc(mtmp, RLOC_MSG); else - mnexto(mtmp); - mmoved = 1; - goto postmov; + mnexto(mtmp, RLOC_MSG); + return postmov(mtmp, ptr, omx, omy, MMOVE_MOVED, + seenflgs, can_tunnel, can_unlock, can_open); } not_special: if (u.uswallow && !mtmp->mflee && u.ustuck != mtmp) - return 1; + return MMOVE_MOVED; omx = mtmp->mx; omy = mtmp->my; - gx = mtmp->mux; - gy = mtmp->muy; + ggx = mtmp->mux; + ggy = mtmp->muy; appr = mtmp->mflee ? -1 : 1; - if (mtmp->mconf || (u.uswallow && mtmp == u.ustuck)) { + if (mtmp->mconf || engulfing_u(mtmp)) { appr = 0; } else { - struct obj *lepgold, *ygold; boolean should_see = (couldsee(omx, omy) - && (levl[gx][gy].lit || !levl[omx][omy].lit) - && (dist2(omx, omy, gx, gy) <= 36)); + && (levl[ggx][ggy].lit || !levl[omx][omy].lit) + && (dist2(omx, omy, ggx, ggy) <= 36)); if (!mtmp->mcansee || (should_see && Invis && !perceives(ptr) && rn2(11)) - || is_obj_mappear(&youmonst,STRANGE_OBJECT) || u.uundetected - || (is_obj_mappear(&youmonst,GOLD_PIECE) && !likes_gold(ptr)) + || is_obj_mappear(&gy.youmonst, STRANGE_OBJECT) || u.uundetected + || (is_obj_mappear(&gy.youmonst, GOLD_PIECE) && !likes_gold(ptr)) || (mtmp->mpeaceful && !mtmp->isshk) /* allow shks to follow */ || ((monsndx(ptr) == PM_STALKER || ptr->mlet == S_BAT || ptr->mlet == S_LIGHT) && !rn2(3))) appr = 0; - if (monsndx(ptr) == PM_LEPRECHAUN && (appr == 1) - && ((lepgold = findgold(mtmp->minvent)) - && (lepgold->quan - > ((ygold = findgold(invent)) ? ygold->quan : 0L)))) + if (appr == 1 && leppie_avoidance(mtmp)) appr = -1; + /* hostiles with ranged weapon or attack try to stay away */ + appr = m_balks_at_approaching(appr, mtmp, &preferredrange_min, &preferredrange_max); + if (!should_see && can_track(ptr)) { - register coord *cp; + coord *cp; cp = gettrack(omx, omy); if (cp) { - gx = cp->x; - gy = cp->y; + ggx = cp->x; + ggy = cp->y; } } } if ((!mtmp->mpeaceful || !rn2(10)) && (!Is_rogue_level(&u.uz))) { boolean in_line = (lined_up(mtmp) - && (distmin(mtmp->mx, mtmp->my, mtmp->mux, mtmp->muy) - <= (throws_rocks(youmonst.data) ? 20 : ACURRSTR / 2 + 1))); + && (distmin(mtmp->mx, mtmp->my, mtmp->mux, mtmp->muy) + <= (throws_rocks(gy.youmonst.data) ? 20 + : (ACURRSTR / 2 + 1)))); if (appr != 1 || !in_line) { /* Monsters in combat won't pick stuff up, avoiding the * situation where you toss arrows at it and it has nothing * better to do than pick the arrows up. */ - register int pctload = - (curr_mon_load(mtmp) * 100) / max_mon_load(mtmp); - - /* look for gold or jewels nearby */ - likegold = (likes_gold(ptr) && pctload < 95); - likegems = (likes_gems(ptr) && pctload < 85); - uses_items = (!mindless(ptr) && !is_animal(ptr) && pctload < 75); - likeobjs = (likes_objs(ptr) && pctload < 75); - likemagic = (likes_magic(ptr) && pctload < 85); - likerock = (throws_rocks(ptr) && pctload < 50 && !Sokoban); - conceals = hides_under(ptr); - setlikes = TRUE; + getitems = TRUE; } } -#define SQSRCHRADIUS 5 - - { - register int minr = SQSRCHRADIUS; /* not too far away */ - register struct obj *otmp; - register int xx, yy; - int oomx, oomy, lmx, lmy; - - /* cut down the search radius if it thinks character is closer. */ - if (distmin(mtmp->mux, mtmp->muy, omx, omy) < SQSRCHRADIUS - && !mtmp->mpeaceful) - minr--; - /* guards shouldn't get too distracted */ - if (!mtmp->mpeaceful && is_mercenary(ptr)) - minr = 1; - - if ((likegold || likegems || likeobjs || likemagic || likerock - || conceals) && (!*in_rooms(omx, omy, SHOPBASE) - || (!rn2(25) && !mtmp->isshk))) { - look_for_obj: - oomx = min(COLNO - 1, omx + minr); - oomy = min(ROWNO - 1, omy + minr); - lmx = max(1, omx - minr); - lmy = max(0, omy - minr); - for (otmp = fobj; otmp; otmp = otmp->nobj) { - /* monsters may pick rocks up, but won't go out of their way - to grab them; this might hamper sling wielders, but it cuts - down on move overhead by filtering out most common item */ - if (otmp->otyp == ROCK) - continue; - xx = otmp->ox; - yy = otmp->oy; - /* Nymphs take everything. Most other creatures should not - * pick up corpses except as a special case like in - * searches_for_item(). We need to do this check in - * mpickstuff() as well. - */ - if (xx >= lmx && xx <= oomx && yy >= lmy && yy <= oomy) { - /* don't get stuck circling around object that's - underneath an immobile or hidden monster; - paralysis victims excluded */ - if ((mtoo = m_at(xx, yy)) != 0 - && (mtoo->msleeping || mtoo->mundetected - || (mtoo->mappearance && !mtoo->iswiz) - || !mtoo->data->mmove)) - continue; - /* the mfndpos() test for whether to allow a move to a - water location accepts flyers, but they can't reach - underwater objects, so being able to move to a spot - is insufficient for deciding whether to do so */ - if ((is_pool(xx, yy) && !is_swimmer(ptr)) - || (is_lava(xx, yy) && !likes_lava(ptr))) - continue; - - if (((likegold && otmp->oclass == COIN_CLASS) - || (likeobjs && index(practical, otmp->oclass) - && (otmp->otyp != CORPSE - || (ptr->mlet == S_NYMPH - && !is_rider(&mons[otmp->corpsenm])))) - || (likemagic && index(magical, otmp->oclass)) - || (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) - && !(otmp->otyp == CORPSE - && touch_petrifies(&mons[otmp->corpsenm])))) - && touch_artifact(otmp, mtmp)) { - if (can_carry(mtmp, otmp) > 0 - && (throws_rocks(ptr) || !sobj_at(BOULDER, xx, yy)) - && (!is_unicorn(ptr) - || objects[otmp->otyp].oc_material == GEMSTONE) - /* Don't get stuck circling an Elbereth */ - && !onscary(xx, yy, mtmp)) { - minr = distmin(omx, omy, xx, yy); - oomx = min(COLNO - 1, omx + minr); - oomy = min(ROWNO - 1, omy + minr); - lmx = max(1, omx - minr); - lmy = max(0, omy - minr); - gx = otmp->ox; - gy = otmp->oy; - if (gx == omx && gy == omy) { - mmoved = 3; /* actually unnecessary */ - goto postmov; - } - } - } - } - } - } else if (likegold) { - /* don't try to pick up anything else, but use the same loop */ - uses_items = 0; - likegems = likeobjs = likemagic = likerock = conceals = 0; - goto look_for_obj; - } - - if (minr < SQSRCHRADIUS && appr == -1) { - if (distmin(omx, omy, mtmp->mux, mtmp->muy) <= 3) { - gx = mtmp->mux; - gy = mtmp->muy; - } else - appr = 1; - } - } + if (getitems && m_search_items(mtmp, &ggx, &ggy, &mmoved, &appr)) + return postmov(mtmp, ptr, omx, omy, mmoved, + seenflgs, can_tunnel, can_unlock, can_open); /* don't tunnel if hostile and close enough to prefer a weapon */ if (can_tunnel && needspick(ptr) @@ -1078,66 +1915,46 @@ register int after; nix = omx; niy = omy; - flag = 0L; - if (mtmp->mpeaceful && (!Conflict || resist(mtmp, RING_CLASS, 0, 0))) - flag |= (ALLOW_SANCT | ALLOW_SSM); - else - flag |= ALLOW_U; - if (is_minion(ptr) || is_rider(ptr)) - flag |= ALLOW_SANCT; - /* unicorn may not be able to avoid hero on a noteleport level */ - if (is_unicorn(ptr) && !level.flags.noteleport) - flag |= NOTONL; - if (passes_walls(ptr)) - flag |= (ALLOW_WALL | ALLOW_ROCK); - if (passes_bars(ptr)) - flag |= ALLOW_BARS; - if (can_tunnel) - flag |= ALLOW_DIG; - if (is_human(ptr) || ptr == &mons[PM_MINOTAUR]) - flag |= ALLOW_SSM; - if ((is_undead(ptr) && ptr->mlet != S_GHOST) || is_vampshifter(mtmp)) - flag |= NOGARLIC; - if (throws_rocks(ptr)) - flag |= ALLOW_ROCK; - if (can_open) - flag |= OPENDOOR; - if (can_unlock) - flag |= UNLOCKDOOR; - if (doorbuster) - flag |= BUSTDOOR; + flag = mon_allowflags(mtmp); { - register int i, j, nx, ny, nearer; + int i, j, nx, ny, nearer; int jcnt, cnt; int ndist, nidist; - register coord *mtrk; - coord poss[9]; + coord *mtrk; - cnt = mfndpos(mtmp, poss, info, flag); + cnt = mfndpos(mtmp, &mfp, flag); + if (cnt == 0 && !is_unicorn(mtmp->data)) { + if (find_defensive(mtmp, TRUE) && use_defensive(mtmp)) + return MMOVE_DONE; + return MMOVE_NOMOVES; + } chcnt = 0; jcnt = min(MTSZ, cnt - 1); chi = -1; - nidist = dist2(nix, niy, gx, gy); + nidist = dist2(nix, niy, ggx, ggy); /* allow monsters be shortsighted on some levels for balance */ - if (!mtmp->mpeaceful && level.flags.shortsighted + if (!mtmp->mpeaceful && svl.level.flags.shortsighted && nidist > (couldsee(nix, niy) ? 144 : 36) && appr == 1) appr = 0; - if (is_unicorn(ptr) && level.flags.noteleport) { + if (is_unicorn(ptr) && noteleport_level(mtmp)) { /* on noteleport levels, perhaps we cannot avoid hero */ for (i = 0; i < cnt; i++) - if (!(info[i] & NOTONL)) + if (!(mfp.info[i] & NOTONL)) avoid = TRUE; } better_with_displacing = - should_displace(mtmp, poss, info, cnt, gx, gy); + should_displace(mtmp, &mfp, ggx, ggy); for (i = 0; i < cnt; i++) { - if (avoid && (info[i] & NOTONL)) + if (avoid && (mfp.info[i] & NOTONL)) + continue; + nx = mfp.poss[i].x; + ny = mfp.poss[i].y; + + if (m_avoid_kicked_loc(mtmp, nx, ny)) continue; - nx = poss[i].x; - ny = poss[i].y; - if (MON_AT(nx, ny) && (info[i] & ALLOW_MDISP) - && !(info[i] & ALLOW_M) && !better_with_displacing) + if (MON_AT(nx, ny) && (mfp.info[i] & ALLOW_MDISP) + && !(mfp.info[i] & ALLOW_M) && !better_with_displacing) continue; if (appr != 0) { mtrk = &mtmp->mtrack[0]; @@ -1147,29 +1964,31 @@ register int after; goto nxti; } - nearer = ((ndist = dist2(nx, ny, gx, gy)) < nidist); + nearer = ((ndist = dist2(nx, ny, ggx, ggy)) < nidist); if ((appr == 1 && nearer) || (appr == -1 && !nearer) - || (!appr && !rn2(++chcnt)) || !mmoved) { + || (!appr && !rn2(++chcnt)) + || (appr == -2 + && ((ndist <= preferredrange_min && !nearer) + || (ndist >= preferredrange_max && nearer))) + || (mmoved == MMOVE_NOTHING)) { nix = nx; niy = ny; nidist = ndist; chi = i; - mmoved = 1; + mmoved = MMOVE_MOVED; } nxti: ; } } - if (mmoved) { - register int j; + if (mmoved != MMOVE_NOTHING) { + if (mmoved == MMOVE_MOVED && !u_at(nix, niy) && itsstuck(mtmp)) + return MMOVE_DONE; - if (mmoved == 1 && (u.ux != nix || u.uy != niy) && itsstuck(mtmp)) - return 3; - - if (mmoved == 1 && m_digweapon_check(mtmp, nix,niy)) - return 3; + if (mmoved == MMOVE_MOVED && m_digweapon_check(mtmp, nix, niy)) + return MMOVE_DONE; /* If ALLOW_U is set, either it's trying to attack you, or it * thinks it is. In either case, attack this spot in preference to @@ -1183,14 +2002,15 @@ register int after; * mfndpos) has no effect for normal attacks, though it lets a * confused monster attack you by accident. */ - if (info[chi] & ALLOW_U) { + assert(IndexOk(chi, mfp.info)); + if (mfp.info[chi] & ALLOW_U) { nix = mtmp->mux; niy = mtmp->muy; } - if (nix == u.ux && niy == u.uy) { + if (u_at(nix, niy)) { mtmp->mux = u.ux; mtmp->muy = u.uy; - return 0; + return MMOVE_NOTHING; } /* The monster may attack another based on 1 of 2 conditions: * 1 - It may be confused. @@ -1198,395 +2018,245 @@ register int after; * Pets get taken care of above and shouldn't reach this code. * Conflict gets handled even farther away (movemon()). */ - if ((info[chi] & ALLOW_M) || (nix == mtmp->mux && niy == mtmp->muy)) { - struct monst *mtmp2; - int mstatus; - - mtmp2 = m_at(nix, niy); - - notonhead = mtmp2 && (nix != mtmp2->mx || niy != mtmp2->my); - /* note: mstatus returns 0 if mtmp2 is nonexistent */ - mstatus = mattackm(mtmp, mtmp2); - - if (mstatus & MM_AGR_DIED) /* aggressor died */ - return 2; - - if ((mstatus & MM_HIT) && !(mstatus & MM_DEF_DIED) && rn2(4) - && mtmp2->movement >= NORMAL_SPEED) { - mtmp2->movement -= NORMAL_SPEED; - notonhead = 0; - mstatus = mattackm(mtmp2, mtmp); /* return attack */ - if (mstatus & MM_DEF_DIED) - return 2; - } - return 3; - } + if ((mfp.info[chi] & ALLOW_M) != 0 + || (nix == mtmp->mux && niy == mtmp->muy)) + return m_move_aggress(mtmp, nix, niy); - if ((info[chi] & ALLOW_MDISP)) { + if ((mfp.info[chi] & ALLOW_MDISP) != 0) { struct monst *mtmp2; int mstatus; - mtmp2 = m_at(nix, niy); + mtmp2 = m_at(nix, niy); /* ALLOW_MDISP implies m_at() is !Null */ mstatus = mdisplacem(mtmp, mtmp2, FALSE); - if ((mstatus & MM_AGR_DIED) || (mstatus & MM_DEF_DIED)) - return 2; - if (mstatus & MM_HIT) - return 1; - return 3; + /*[if either dies, this reports mtmp has died; is that correct?]*/ + if (mstatus & (M_ATTK_AGR_DIED | M_ATTK_DEF_DIED)) + return MMOVE_DIED; + if (mstatus & M_ATTK_HIT) + return MMOVE_MOVED; + return MMOVE_DONE; } if (!m_in_out_region(mtmp, nix, niy)) - return 3; + return MMOVE_DONE; + if ((mfp.info[chi] & ALLOW_ROCK) && m_can_break_boulder(mtmp)) { + (void) m_break_boulder(mtmp, nix, niy); + return MMOVE_DONE; + } + + m_postmove_effect(mtmp); + + /* move a normal monster; for a long worm, remove_monster() and + place_monster() only manipulate the head; they leave tail as-is */ remove_monster(omx, omy); place_monster(mtmp, nix, niy); - for (j = MTSZ - 1; j > 0; j--) - mtmp->mtrack[j] = mtmp->mtrack[j - 1]; - mtmp->mtrack[0].x = omx; - mtmp->mtrack[0].y = omy; - /* Place a segment at the old position. */ + msg_mon_movement(mtmp, omx, omy); + /* for a long worm, insert a new segment to reconnect the head + with the tail; worm_move() keeps the end of the tail if worm + is scheduled to grow, removes that for move-without-growing */ if (mtmp->wormno) worm_move(mtmp); + + maybe_unhide_at(mtmp->mx, mtmp->my); + + mon_track_add(mtmp, omx, omy); } else { if (is_unicorn(ptr) && rn2(2) && !tele_restrict(mtmp)) { - (void) rloc(mtmp, TRUE); - return 1; + (void) rloc(mtmp, RLOC_MSG); + return MMOVE_MOVED; } + /* for a long worm, shrink it (by discarding end of tail) when + it has failed to move */ if (mtmp->wormno) worm_nomove(mtmp); } - postmov: - if (mmoved == 1 || mmoved == 3) { - boolean canseeit = cansee(mtmp->mx, mtmp->my); - - if (mmoved == 1) { - /* normal monster move will already have , - but pet dog_move() with 'goto postmov' won't */ - nix = mtmp->mx, niy = mtmp->my; - /* sequencing issue: when monster movement decides that a - monster can move to a door location, it moves the monster - there before dealing with the door rather than after; - so a vampire/bat that is going to shift to fog cloud and - pass under the door is already there but transformation - into fog form--and its message, when in sight--has not - happened yet; we have to move monster back to previous - location before performing the vamp_shift() to make the - message happen at right time, then back to the door again - [if we did the shift above, before moving the monster, - we would need to duplicate it in dog_move()...] */ - if (is_vampshifter(mtmp) && !amorphous(mtmp->data) - && IS_DOOR(levl[nix][niy].typ) - && ((levl[nix][niy].doormask & (D_LOCKED | D_CLOSED)) != 0) - && can_fog(mtmp)) { - if (sawmon) { - remove_monster(nix, niy); - place_monster(mtmp, omx, omy); - newsym(nix, niy), newsym(omx, omy); - } - if (vamp_shift(mtmp, &mons[PM_FOG_CLOUD], sawmon)) { - ptr = mtmp->data; /* update cached value */ - } - if (sawmon) { - remove_monster(omx, omy); - place_monster(mtmp, nix, niy); - newsym(omx, omy), newsym(nix, niy); - } - } - - newsym(omx, omy); /* update the old position */ - if (mintrap(mtmp) >= 2) { - if (mtmp->mx) - newsym(mtmp->mx, mtmp->my); - return 2; /* it died */ - } - ptr = mtmp->data; /* in case mintrap() caused polymorph */ - - /* open a door, or crash through it, if 'mtmp' can */ - if (IS_DOOR(levl[mtmp->mx][mtmp->my].typ) - && !passes_walls(ptr) /* doesn't need to open doors */ - && !can_tunnel) { /* taken care of below */ - struct rm *here = &levl[mtmp->mx][mtmp->my]; - boolean btrapped = (here->doormask & D_TRAPPED) != 0, - observeit = canseeit && canspotmon(mtmp); - - /* if mon has MKoT, disarm door trap; no message given */ - if (btrapped && has_magic_key(mtmp)) { - /* BUG: this lets a vampire or blob or a doorbuster - holding the Key disarm the trap even though it isn't - using that Key when squeezing under or smashing the - door. Not significant enough to worry about; perhaps - the Key's magic is more powerful for monsters? */ - here->doormask &= ~D_TRAPPED; - btrapped = FALSE; - } - if ((here->doormask & (D_LOCKED | D_CLOSED)) != 0 - && amorphous(ptr)) { - if (flags.verbose && canseemon(mtmp)) - pline("%s %s under the door.", Monnam(mtmp), - (ptr == &mons[PM_FOG_CLOUD] - || ptr->mlet == S_LIGHT) ? "flows" : "oozes"); - } else if (here->doormask & D_LOCKED && can_unlock) { - if (btrapped) { - here->doormask = D_NODOOR; - newsym(mtmp->mx, mtmp->my); - unblock_point(mtmp->mx, mtmp->my); /* vision */ - if (mb_trapped(mtmp)) - return 2; - } else { - if (flags.verbose) { - if (observeit) - pline("%s unlocks and opens a door.", - Monnam(mtmp)); - else if (canseeit) - You_see("a door unlock and open."); - else if (!Deaf) - You_hear("a door unlock and open."); - } - here->doormask = D_ISOPEN; - /* newsym(mtmp->mx, mtmp->my); */ - unblock_point(mtmp->mx, mtmp->my); /* vision */ - } - } else if (here->doormask == D_CLOSED && can_open) { - if (btrapped) { - here->doormask = D_NODOOR; - newsym(mtmp->mx, mtmp->my); - unblock_point(mtmp->mx, mtmp->my); /* vision */ - if (mb_trapped(mtmp)) - return 2; - } else { - if (flags.verbose) { - if (observeit) - pline("%s opens a door.", Monnam(mtmp)); - else if (canseeit) - You_see("a door open."); - else if (!Deaf) - You_hear("a door open."); - } - here->doormask = D_ISOPEN; - /* newsym(mtmp->mx, mtmp->my); */ /* done below */ - unblock_point(mtmp->mx, mtmp->my); /* vision */ - } - } else if (here->doormask & (D_LOCKED | D_CLOSED)) { - /* mfndpos guarantees this must be a doorbuster */ - if (btrapped) { - here->doormask = D_NODOOR; - newsym(mtmp->mx, mtmp->my); - unblock_point(mtmp->mx, mtmp->my); /* vision */ - if (mb_trapped(mtmp)) - return 2; - } else { - if (flags.verbose) { - if (observeit) - pline("%s smashes down a door.", - Monnam(mtmp)); - else if (canseeit) - You_see("a door crash open."); - else if (!Deaf) - You_hear("a door crash open."); - } - if ((here->doormask & D_LOCKED) != 0 && !rn2(2)) - here->doormask = D_NODOOR; - else - here->doormask = D_BROKEN; - /* newsym(mtmp->mx, mtmp->my); */ /* done below */ - unblock_point(mtmp->mx, mtmp->my); /* vision */ - } - /* if it's a shop door, schedule repair */ - if (*in_rooms(mtmp->mx, mtmp->my, SHOPBASE)) - add_damage(mtmp->mx, mtmp->my, 0L); - } - } else if (levl[mtmp->mx][mtmp->my].typ == IRONBARS) { - /* As of 3.6.2: was using may_dig() but it doesn't handle bars */ - if (!(levl[mtmp->mx][mtmp->my].wall_info & W_NONDIGGABLE) - && (dmgtype(ptr, AD_RUST) || dmgtype(ptr, AD_CORR))) { - if (canseemon(mtmp)) - pline("%s eats through the iron bars.", Monnam(mtmp)); - dissolve_bars(mtmp->mx, mtmp->my); - return 3; - } else if (flags.verbose && canseemon(mtmp)) - Norep("%s %s %s the iron bars.", Monnam(mtmp), - /* pluralization fakes verb conjugation */ - makeplural(locomotion(ptr, "pass")), - passes_walls(ptr) ? "through" : "between"); - } - - /* possibly dig */ - if (can_tunnel && mdig_tunnel(mtmp)) - return 2; /* mon died (position already updated) */ - - /* set also in domove(), hack.c */ - if (u.uswallow && mtmp == u.ustuck - && (mtmp->mx != omx || mtmp->my != omy)) { - /* If the monster moved, then update */ - u.ux0 = u.ux; - u.uy0 = u.uy; - u.ux = mtmp->mx; - u.uy = mtmp->my; - swallowed(0); - } else - newsym(mtmp->mx, mtmp->my); - } - if (OBJ_AT(mtmp->mx, mtmp->my) && mtmp->mcanmove) { - /* recompute the likes tests, in case we polymorphed - * or if the "likegold" case got taken above */ - if (setlikes) { - int pctload = (curr_mon_load(mtmp) * 100) / max_mon_load(mtmp); - - /* look for gold or jewels nearby */ - likegold = (likes_gold(ptr) && pctload < 95); - likegems = (likes_gems(ptr) && pctload < 85); - uses_items = - (!mindless(ptr) && !is_animal(ptr) && pctload < 75); - likeobjs = (likes_objs(ptr) && pctload < 75); - likemagic = (likes_magic(ptr) && pctload < 85); - likerock = (throws_rocks(ptr) && pctload < 50 && !Sokoban); - conceals = hides_under(ptr); - } + return postmov(mtmp, ptr, omx, omy, mmoved, + seenflgs, can_tunnel, can_unlock, can_open); +} - /* Maybe a rock mole just ate some metal object */ - if (metallivorous(ptr)) { - if (meatmetal(mtmp) == 2) - return 2; /* it died */ - } +/* The part of m_move that deals with a monster attacking another monster (and + * that monster possibly retaliating). + * Extracted into its own function so that it can be called with monsters that + * have special move patterns (shopkeepers, priests, etc) that want to attack + * other monsters but aren't just roaming freely around the level (so allowing + * m_move to run fully for them could select an invalid move). + * x and y are the coordinates mtmp wants to attack. + * Return values are the same as for m_move, but this function only return 2 + * (mtmp died) or 3 (mtmp made its move). + */ +int +m_move_aggress(struct monst *mtmp, coordxy x, coordxy y) +{ + struct monst *mtmp2; + int mstatus = 0; /* M_ATTK_MISS */ + + mtmp2 = m_at(x, y); + if (mtmp2) { + gb.bhitpos.x = x, gb.bhitpos.y = y; + gn.notonhead = (x != mtmp2->mx || y != mtmp2->my); + mstatus = mattackm(mtmp, mtmp2); + } - if (g_at(mtmp->mx, mtmp->my) && likegold) - mpickgold(mtmp); + if ((mstatus & M_ATTK_AGR_DIED) || DEADMONSTER(mtmp)) /* aggressor died */ + return MMOVE_DIED; - /* Maybe a cube ate just about anything */ - if (ptr == &mons[PM_GELATINOUS_CUBE]) { - if (meatobj(mtmp) == 2) - return 2; /* it died */ - } + if ((mstatus & (M_ATTK_HIT | M_ATTK_DEF_DIED)) == M_ATTK_HIT + && rn2(4) && mtmp2->movement > rn2(NORMAL_SPEED)) { + if (mtmp2->movement > NORMAL_SPEED) + mtmp2->movement -= NORMAL_SPEED; + else + mtmp2->movement = 0; + gb.bhitpos.x = mtmp->mx, gb.bhitpos.y = mtmp->my; + gn.notonhead = FALSE; + mstatus = mattackm(mtmp2, mtmp); /* return attack */ + /* note: at this point, defender is the original (moving) aggressor */ + if (mstatus & M_ATTK_DEF_DIED) + return MMOVE_DIED; + } + return MMOVE_DONE; +} - if (!*in_rooms(mtmp->mx, mtmp->my, SHOPBASE) || !rn2(25)) { - boolean picked = FALSE; - - if (likeobjs) - picked |= mpickstuff(mtmp, practical); - if (likemagic) - picked |= mpickstuff(mtmp, magical); - if (likerock) - picked |= mpickstuff(mtmp, boulder_class); - if (likegems) - picked |= mpickstuff(mtmp, gem_class); - if (uses_items) - picked |= mpickstuff(mtmp, (char *) 0); - if (picked) - mmoved = 3; - } +/* returns TRUE if a mon can hide under the obj */ +boolean +can_hide_under_obj(struct obj *obj) +{ +/* uncomment '#define NO_HIDING_UNDER_STATUES' to prevent hiding under + * statues; that was introduced to avoid nullifying statue traps but + * isn't needed now that hiding at any non-pit trap site is disallowed */ +/* #define NO_HIDING_UNDER_STATUES */ + struct trap *t; - if (mtmp->minvis) { - newsym(mtmp->mx, mtmp->my); - if (mtmp->wormno) - see_wsegs(mtmp); - } - } + if (!obj || obj->where != OBJ_FLOOR) + return FALSE; + /* can't hide in/on/under traps (except pits) even when there is an + object here; since obj is on floor, its are up to date */ + if ((t = t_at(obj->ox, obj->oy)) != 0 && !is_pit(t->ttyp)) + return FALSE; + /* can't hide under small amount of coins unless non-coins are also + present; we expect coins to be a single stack but don't assume that */ + if (obj->oclass == COIN_CLASS) { + long coinquan = 0L; - if (hides_under(ptr) || ptr->mlet == S_EEL) { - /* Always set--or reset--mundetected if it's already hidden - (just in case the object it was hiding under went away); - usually set mundetected unless monster can't move. */ - if (mtmp->mundetected - || (mtmp->mcanmove && !mtmp->msleeping && rn2(5))) - (void) hideunder(mtmp); - newsym(mtmp->mx, mtmp->my); - } - if (mtmp->isshk) { - after_shk_move(mtmp); - } + do { + /* 10 coins is arbitrary amount considered enough to hide under */ + if ((coinquan += obj->quan) >= 10L) + break; /* fall through to other checks */ + obj = obj->nexthere; + if (!obj) + return FALSE; /* whole pile was less than 10 coins */ + } while (obj->oclass == COIN_CLASS); } - return mmoved; +#ifdef NO_HIDING_UNDER_STATUES + /* + * 'obj' might have been changed, but only if we've skipped coins that + * are on the top of a pile. However, the statue loop will clobber it. + */ + /* can't hide under statues regardless of pile stacking order */ + while (obj) { + if (obj->otyp == STATUE) + return FALSE; + obj = obj->nexthere; + } + /* + * If we reach here, 'obj' is now Null but wasn't earlier so the original + * 'obj' can be hidden beneath. + */ +#undef NO_HIDING_UNDER_STATUES +#endif + return TRUE; /* can hide under the object */ } void -dissolve_bars(x, y) -register int x, y; +dissolve_bars(coordxy x, coordxy y) { - levl[x][y].typ = (Is_special(&u.uz) || *in_rooms(x, y, 0)) ? ROOM : CORR; - levl[x][y].flags = 0; + levl[x][y].typ = (levl[x][y].edge == 1) ? DOOR + : (Is_special(&u.uz) || *in_rooms(x, y, 0)) ? ROOM : CORR; + levl[x][y].flags = 0; /* doormask = D_NODOOR */ newsym(x, y); + if (u_at(x, y)) + switch_terrain(); } boolean -closed_door(x, y) -register int x, y; +closed_door(coordxy x, coordxy y) { return (boolean) (IS_DOOR(levl[x][y].typ) && (levl[x][y].doormask & (D_LOCKED | D_CLOSED))); } boolean -accessible(x, y) -register int x, y; +accessible(coordxy x, coordxy y) { - int levtyp = levl[x][y].typ; - /* use underlying terrain in front of closed drawbridge */ - if (levtyp == DRAWBRIDGE_UP) - levtyp = db_under_typ(levl[x][y].drawbridgemask); + int levtyp = SURFACE_AT(x, y); return (boolean) (ACCESSIBLE(levtyp) && !closed_door(x, y)); } /* decide where the monster thinks you are standing */ void -set_apparxy(mtmp) -register struct monst *mtmp; +set_apparxy(struct monst *mtmp) { - boolean notseen, gotu; - register int disp, mx = mtmp->mux, my = mtmp->muy; - long umoney = money_cnt(invent); + boolean notseen, notthere, gotu; + int displ; + coordxy mx = mtmp->mux, my = mtmp->muy; + long umoney = money_cnt(gi.invent); /* * do cheapest and/or most likely tests first */ - /* pet knows your smell; grabber still has hold of you */ - if (mtmp->mtame || mtmp == u.ustuck) - goto found_you; - - /* monsters which know where you are don't suddenly forget, - if you haven't moved away */ - if (mx == u.ux && my == u.uy) - goto found_you; + /* pet knows your smell; grabber still has hold of you; monsters which + know where you are don't suddenly forget, if you haven't moved away */ + if (mtmp->mtame || mtmp == u.ustuck || u_at(mx, my)) { + mtmp->mux = u.ux; + mtmp->muy = u.uy; + return; + } notseen = (!mtmp->mcansee || (Invis && !perceives(mtmp->data))); + notthere = (Displaced && mtmp->data != &mons[PM_DISPLACER_BEAST]); /* add cases as required. eg. Displacement ... */ - if (notseen || Underwater) { + if (Underwater) { + displ = 1; + } else if (notseen) { /* Xorns can smell quantities of valuable metal - like that in solid gold coins, treat as seen */ - if ((mtmp->data == &mons[PM_XORN]) && umoney && !Underwater) - disp = 0; - else - disp = 1; - } else if (Displaced) { - disp = couldsee(mx, my) ? 2 : 1; - } else - disp = 0; - if (!disp) - goto found_you; + like that in solid gold coins, treat as seen */ + displ = (mtmp->data == &mons[PM_XORN] && umoney) ? 0 : 1; + } else if (notthere) { + displ = couldsee(mx, my) ? 2 : 1; + } else { + displ = 0; + } + if (!displ) { + mtmp->mux = u.ux; + mtmp->muy = u.uy; + return; + } /* without something like the following, invisibility and displacement are too powerful */ - gotu = notseen ? !rn2(3) : Displaced ? !rn2(4) : FALSE; + gotu = notseen ? !rn2(3) : notthere ? !rn2(4) : FALSE; if (!gotu) { - register int try_cnt = 0; + int try_cnt = 0; do { - if (++try_cnt > 200) - goto found_you; /* punt */ - mx = u.ux - disp + rn2(2 * disp + 1); - my = u.uy - disp + rn2(2 * disp + 1); + if (++try_cnt > 200) { + mx = u.ux; + my = u.uy; + break; /* punt */ + } + mx = u.ux - displ + rn2(2 * displ + 1); + my = u.uy - displ + rn2(2 * displ + 1); } while (!isok(mx, my) - || (disp != 2 && mx == mtmp->mx && my == mtmp->my) + || (displ != 2 && mx == mtmp->mx && my == mtmp->my) || ((mx != u.ux || my != u.uy) && !passes_walls(mtmp->data) && !(accessible(mx, my) || (closed_door(mx, my) && (can_ooze(mtmp) || can_fog(mtmp))))) || !couldsee(mx, my)); } else { - found_you: mx = u.ux; my = u.uy; } @@ -1604,11 +2274,12 @@ register struct monst *mtmp; * location however. */ boolean -undesirable_disp(mtmp, x, y) -struct monst *mtmp; /* barging creature */ -xchar x, y; /* spot 'mtmp' is considering moving to */ +undesirable_disp( + struct monst *mtmp, /* barging creature */ + coordxy x, + coordxy y) /* spot 'mtmp' is considering moving to */ { - boolean is_pet = (mtmp && mtmp->mtame && !mtmp->isminion); + boolean is_pet = (mtmp->mtame && !mtmp->isminion); struct trap *trap = t_at(x, y); if (is_pet) { @@ -1621,7 +2292,7 @@ xchar x, y; /* spot 'mtmp' is considering moving to */ /* Monsters avoid a trap if they've seen that type before */ } else if (trap && rn2(40) - && (mtmp->mtrapseen & (1 << (trap->ttyp - 1))) != 0) { + && mon_knows_traps(mtmp, trap->ttyp)) { return TRUE; } @@ -1644,14 +2315,13 @@ xchar x, y; /* spot 'mtmp' is considering moving to */ * Inventory prevents passage under door. * Used by can_ooze() and can_fog(). */ -STATIC_OVL boolean -stuff_prevents_passage(mtmp) -struct monst *mtmp; +staticfn boolean +stuff_prevents_passage(struct monst *mtmp) { struct obj *chain, *obj; - if (mtmp == &youmonst) { - chain = invent; + if (mtmp == &gy.youmonst) { + chain = gi.invent; } else { chain = mtmp->minvent; } @@ -1683,8 +2353,7 @@ struct monst *mtmp; } boolean -can_ooze(mtmp) -struct monst *mtmp; +can_ooze(struct monst *mtmp) { if (!amorphous(mtmp->data) || stuff_prevents_passage(mtmp)) return FALSE; @@ -1693,44 +2362,34 @@ struct monst *mtmp; /* monster can change form into a fog if necessary */ boolean -can_fog(mtmp) -struct monst *mtmp; +can_fog(struct monst *mtmp) { - if (!(mvitals[PM_FOG_CLOUD].mvflags & G_GENOD) && is_vampshifter(mtmp) + if (!(svm.mvitals[PM_FOG_CLOUD].mvflags & G_GENOD) && is_vampshifter(mtmp) && !Protection_from_shape_changers && !stuff_prevents_passage(mtmp)) return TRUE; return FALSE; } -STATIC_OVL int -vamp_shift(mon, ptr, domsg) -struct monst *mon; -struct permonst *ptr; -boolean domsg; +/* this is called when a vampire turns into a fog cloud in order to move + under a closed door; if it was sensed via telepathy or seen via + infravision, its new fog cloud shape will disappear */ +staticfn int +vamp_shift( + struct monst *mon, + struct permonst *ptr, + boolean domsg) { int reslt = 0; - char oldmtype[BUFSZ]; - - /* remember current monster type before shapechange */ - Strcpy(oldmtype, domsg ? noname_monnam(mon, ARTICLE_THE) : ""); if (mon->data == ptr) { /* already right shape */ reslt = 1; - domsg = FALSE; } else if (is_vampshifter(mon)) { - reslt = newcham(mon, ptr, FALSE, FALSE); - } - - if (reslt && domsg) { - pline("You %s %s where %s was.", - !canseemon(mon) ? "now detect" : "observe", - noname_monnam(mon, ARTICLE_A), oldmtype); - /* this message is given when it turns into a fog cloud - in order to move under a closed door */ + reslt = newcham(mon, ptr, domsg ? NC_SHOW_MSG : NO_NC_FLAGS); + /* shape-change message is given when vampshifter turns into a + fog cloud in order to move under a closed door */ display_nhwindow(WIN_MESSAGE, FALSE); } - return reslt; } diff --git a/src/monst.c b/src/monst.c index 6b8a5f76e..ef794c4c3 100644 --- a/src/monst.c +++ b/src/monst.c @@ -1,3242 +1,89 @@ -/* NetHack 3.6 monst.c $NHDT-Date: 1547118631 2019/01/10 11:10:31 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.62 $ */ +/* NetHack 5.0 monst.c $NHDT-Date: 1705092160 2024/01/12 20:42:40 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.100 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Michael Allison, 2006. */ /* NetHack may be freely redistributed. See license for details. */ #include "config.h" +#include "weight.h" #include "permonst.h" -#include "monsym.h" +#include "wintype.h" +#include "sym.h" -#define NO_ATTK \ - { \ - 0, 0, 0, 0 \ - } +#include "color.h" -#define WT_ELF 800 -#define WT_DRAGON 4500 +extern const struct attack c_sa_yes[NATTK]; +extern const struct attack c_sa_no[NATTK]; -#ifdef C -#undef C -#endif -#ifdef TEXTCOLOR -#include "color.h" -#define C(color) color -#define HI_DOMESTIC CLR_WHITE /* use for player + friendlies */ -#define HI_LORD CLR_MAGENTA -#else -#define C(color) -#endif +#define NO_ATTK { 0, 0, 0, 0 } -void NDECL(monst_init); -/* - * Entry Format: (from permonst.h) - * - * name, symbol (S_* defines), - * base monster level, move rate, armor class, magic resistance, - * alignment, creation/geno flags (G_* defines), - * 6 * attack structs ( type , damage-type, # dice, # sides ), - * weight (WT_* defines), nutritional value, extension length, - * sounds made (MS_* defines), physical size (MZ_* defines), - * resistances, resistances conferred (both MR_* defines), - * 3 * flag bitmaps (M1_*, M2_*, and M3_* defines respectively), - * difficulty, symbol color (C(x) macro) - * - * For AT_BREA attacks, '# sides' is ignored; 6 is used for most - * damage types, 25 for sleep, not applicable for death or poison. - */ -#define MON(nam, sym, lvl, gen, atk, siz, mr1, mr2, flg1, flg2, flg3, d, col) \ - { \ - nam, sym, lvl, gen, atk, siz, mr1, mr2, flg1, flg2, flg3, d, C(col) \ +/* monster type with single name */ +#define MON(nam, sym, lvl, gen, atk, siz, mr1, mr2, \ + flg1, flg2, flg3, d, col, bn) \ + { \ + nam, PM_##bn, \ + sym, lvl, gen, atk, siz, mr1, mr2, flg1, flg2, flg3, d, col \ } -/* LVL() and SIZ() collect several fields to cut down on # of args for MON() + +/* LVL() and SIZ() collect several fields to cut down on number of args + * for MON(). Using more than 15 would fail to conform to the C Standard. + * ATTK() and A() are to avoid braces and commas within args to MON(). + * NAM() and NAMS() are used for both reasons. */ +#define NAM(name) { (const char *) 0, (const char *) 0, name } +#define NAMS(namm, namf, namn) { namm, namf, namn } #define LVL(lvl, mov, ac, mr, aln) lvl, mov, ac, mr, aln #define SIZ(wt, nut, snd, siz) wt, nut, snd, siz -/* ATTK() and A() are to avoid braces and commas within args to MON() */ -#define ATTK(at, ad, n, d) \ - { \ - at, ad, n, d \ - } -#define A(a1, a2, a3, a4, a5, a6) \ - { \ - a1, a2, a3, a4, a5, a6 \ - } +#define ATTK(at, ad, n, d) { at, ad, n, d } +#define A(a1, a2, a3, a4, a5, a6) { a1, a2, a3, a4, a5, a6 } -/* - * Rule #1: monsters of a given class are contiguous in the - * mons[] array. - * - * Rule #2: monsters of a given class are presented in ascending - * order of strength. - * - * Rule #3: monster frequency is included in the geno mask; - * the frequency can be from 0 to 7. 0's will also - * be skipped during generation. - * - * Rule #4: monster subclasses (e.g. giants) should be kept - * together, unless it violates Rule 2. NOGEN monsters - * won't violate Rule 2. - * - * Guidelines for color assignment: - * - * * Use the same color for all `growth stages' of a monster (ex. - * little dog/big dog, baby naga/full-grown naga. - * - * * Use colors given in names wherever possible. If the class has `real' - * members with strong color associations, use those. - * - * * Favor `cool' colors for cold-resistant monsters, `warm' ones for - * fire-resistant ones. - * - * * Try to reserve purple (magenta) for powerful `ruler' monsters (queen - * bee, kobold lord, &c.). - * - * * Subject to all these constraints, try to use color to make as many - * distinctions as the / command (that is, within a monster letter - * distinct names should map to distinct colors). - * - * The aim in assigning colors is to be consistent enough so a player can - * become `intuitive' about them, deducing some or all of these rules - * unconsciously. Use your common sense. - */ - -#ifndef SPLITMON_2 -NEARDATA struct permonst mons[] = { - /* - * ants - */ - MON("giant ant", S_ANT, LVL(2, 18, 3, 0, 0), (G_GENO | G_SGROUP | 3), - A(ATTK(AT_BITE, AD_PHYS, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(10, 10, MS_SILENT, MZ_TINY), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_OVIPAROUS | M1_CARNIVORE, M2_HOSTILE, 0, - 4, CLR_BROWN), - MON("killer bee", S_ANT, LVL(1, 18, -1, 0, 0), (G_GENO | G_LGROUP | 2), - A(ATTK(AT_STNG, AD_DRST, 1, 3), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(1, 5, MS_BUZZ, MZ_TINY), MR_POISON, MR_POISON, - M1_ANIMAL | M1_FLY | M1_NOHANDS | M1_POIS, M2_HOSTILE | M2_FEMALE, 0, - 5, CLR_YELLOW), - MON("soldier ant", S_ANT, LVL(3, 18, 3, 0, 0), (G_GENO | G_SGROUP | 2), - A(ATTK(AT_BITE, AD_PHYS, 2, 4), ATTK(AT_STNG, AD_DRST, 3, 4), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(20, 5, MS_SILENT, MZ_TINY), MR_POISON, MR_POISON, - M1_ANIMAL | M1_NOHANDS | M1_OVIPAROUS | M1_POIS | M1_CARNIVORE, - M2_HOSTILE, 0, 6, CLR_BLUE), - MON("fire ant", S_ANT, LVL(3, 18, 3, 10, 0), (G_GENO | G_SGROUP | 1), - A(ATTK(AT_BITE, AD_PHYS, 2, 4), ATTK(AT_BITE, AD_FIRE, 2, 4), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(30, 10, MS_SILENT, MZ_TINY), MR_FIRE, MR_FIRE, - M1_ANIMAL | M1_NOHANDS | M1_OVIPAROUS | M1_CARNIVORE, M2_HOSTILE, - M3_INFRAVISIBLE, 6, CLR_RED), - MON("giant beetle", S_ANT, LVL(5, 6, 4, 0, 0), (G_GENO | 3), - A(ATTK(AT_BITE, AD_PHYS, 3, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(10, 10, MS_SILENT, MZ_LARGE), MR_POISON, MR_POISON, - M1_ANIMAL | M1_NOHANDS | M1_POIS | M1_CARNIVORE, M2_HOSTILE, 0, - 6, CLR_BLACK), - MON("queen bee", S_ANT, LVL(9, 24, -4, 0, 0), (G_GENO | G_NOGEN), - A(ATTK(AT_STNG, AD_DRST, 1, 8), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(1, 5, MS_BUZZ, MZ_TINY), MR_POISON, MR_POISON, - M1_ANIMAL | M1_FLY | M1_NOHANDS | M1_OVIPAROUS | M1_POIS, - M2_HOSTILE | M2_FEMALE | M2_PRINCE, 0, 12, HI_LORD), - /* - * blobs - */ - MON("acid blob", S_BLOB, LVL(1, 3, 8, 0, 0), (G_GENO | 2), - A(ATTK(AT_NONE, AD_ACID, 1, 8), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(30, 10, MS_SILENT, MZ_TINY), - MR_SLEEP | MR_POISON | MR_ACID | MR_STONE, MR_STONE, - M1_BREATHLESS | M1_AMORPHOUS | M1_NOEYES | M1_NOLIMBS | M1_NOHEAD - | M1_MINDLESS | M1_ACID, - M2_WANDER | M2_NEUTER, 0, 2, CLR_GREEN), - MON("quivering blob", S_BLOB, LVL(5, 1, 8, 0, 0), (G_GENO | 2), - A(ATTK(AT_TUCH, AD_PHYS, 1, 8), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(200, 100, MS_SILENT, MZ_SMALL), MR_SLEEP | MR_POISON, MR_POISON, - M1_NOEYES | M1_NOLIMBS | M1_NOHEAD | M1_MINDLESS, - M2_WANDER | M2_HOSTILE | M2_NEUTER, 0, 6, CLR_WHITE), - MON("gelatinous cube", S_BLOB, LVL(6, 6, 8, 0, 0), (G_GENO | 2), - A(ATTK(AT_TUCH, AD_PLYS, 2, 4), ATTK(AT_NONE, AD_PLYS, 1, 4), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(600, 150, MS_SILENT, MZ_LARGE), - MR_FIRE | MR_COLD | MR_ELEC | MR_SLEEP | MR_POISON | MR_ACID - | MR_STONE, - MR_FIRE | MR_COLD | MR_ELEC | MR_SLEEP, - M1_NOEYES | M1_NOLIMBS | M1_NOHEAD | M1_MINDLESS | M1_OMNIVORE - | M1_ACID, - M2_WANDER | M2_HOSTILE | M2_NEUTER, 0, 8, CLR_CYAN), - /* - * cockatrice - */ - MON("chickatrice", S_COCKATRICE, LVL(4, 4, 8, 30, 0), - (G_GENO | G_SGROUP | 1), - A(ATTK(AT_BITE, AD_PHYS, 1, 2), ATTK(AT_TUCH, AD_STON, 0, 0), - ATTK(AT_NONE, AD_STON, 0, 0), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(10, 10, MS_HISS, MZ_TINY), MR_POISON | MR_STONE, - MR_POISON | MR_STONE, M1_ANIMAL | M1_NOHANDS | M1_OMNIVORE, - M2_HOSTILE, M3_INFRAVISIBLE, 7, CLR_BROWN), - MON("cockatrice", S_COCKATRICE, LVL(5, 6, 6, 30, 0), (G_GENO | 5), - A(ATTK(AT_BITE, AD_PHYS, 1, 3), ATTK(AT_TUCH, AD_STON, 0, 0), - ATTK(AT_NONE, AD_STON, 0, 0), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(30, 30, MS_HISS, MZ_SMALL), MR_POISON | MR_STONE, - MR_POISON | MR_STONE, - M1_ANIMAL | M1_NOHANDS | M1_OMNIVORE | M1_OVIPAROUS, M2_HOSTILE, - M3_INFRAVISIBLE, 8, CLR_YELLOW), - MON("pyrolisk", S_COCKATRICE, LVL(6, 6, 6, 30, 0), (G_GENO | 1), - A(ATTK(AT_GAZE, AD_FIRE, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(30, 30, MS_HISS, MZ_SMALL), MR_POISON | MR_FIRE, - MR_POISON | MR_FIRE, - M1_ANIMAL | M1_NOHANDS | M1_OMNIVORE | M1_OVIPAROUS, M2_HOSTILE, - M3_INFRAVISIBLE, 8, CLR_RED), - /* - * dogs & other canines - */ - MON("jackal", S_DOG, LVL(0, 12, 7, 0, 0), (G_GENO | G_SGROUP | 3), - A(ATTK(AT_BITE, AD_PHYS, 1, 2), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(300, 250, MS_BARK, MZ_SMALL), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_HOSTILE, M3_INFRAVISIBLE, - 1, CLR_BROWN), - MON("fox", S_DOG, LVL(0, 15, 7, 0, 0), (G_GENO | 1), - A(ATTK(AT_BITE, AD_PHYS, 1, 3), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(300, 250, MS_BARK, MZ_SMALL), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_HOSTILE, M3_INFRAVISIBLE, - 1, CLR_RED), - MON("coyote", S_DOG, LVL(1, 12, 7, 0, 0), (G_GENO | G_SGROUP | 1), - A(ATTK(AT_BITE, AD_PHYS, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(300, 250, MS_BARK, MZ_SMALL), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_HOSTILE, M3_INFRAVISIBLE, - 2, CLR_BROWN), - MON("werejackal", S_DOG, LVL(2, 12, 7, 10, -7), (G_NOGEN | G_NOCORPSE), - A(ATTK(AT_BITE, AD_WERE, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(300, 250, MS_BARK, MZ_SMALL), MR_POISON, 0, - M1_NOHANDS | M1_POIS | M1_REGEN | M1_CARNIVORE, - M2_NOPOLY | M2_WERE | M2_HOSTILE, M3_INFRAVISIBLE, 4, CLR_BROWN), - MON("little dog", S_DOG, LVL(2, 18, 6, 0, 0), (G_GENO | 1), - A(ATTK(AT_BITE, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(150, 150, MS_BARK, MZ_SMALL), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_DOMESTIC, M3_INFRAVISIBLE, - 3, HI_DOMESTIC), - MON("dingo", S_DOG, LVL(4, 16, 5, 0, 0), (G_GENO | 1), - A(ATTK(AT_BITE, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(400, 200, MS_BARK, MZ_MEDIUM), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_HOSTILE, M3_INFRAVISIBLE, - 5, CLR_YELLOW), - MON("dog", S_DOG, LVL(4, 16, 5, 0, 0), (G_GENO | 1), - A(ATTK(AT_BITE, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(400, 200, MS_BARK, MZ_MEDIUM), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_DOMESTIC, M3_INFRAVISIBLE, - 5, HI_DOMESTIC), - MON("large dog", S_DOG, LVL(6, 15, 4, 0, 0), (G_GENO | 1), - A(ATTK(AT_BITE, AD_PHYS, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(800, 250, MS_BARK, MZ_MEDIUM), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_STRONG | M2_DOMESTIC, - M3_INFRAVISIBLE, 7, HI_DOMESTIC), - MON("wolf", S_DOG, LVL(5, 12, 4, 0, 0), (G_GENO | G_SGROUP | 2), - A(ATTK(AT_BITE, AD_PHYS, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(500, 250, MS_BARK, MZ_MEDIUM), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_HOSTILE, M3_INFRAVISIBLE, - 6, CLR_BROWN), - MON("werewolf", S_DOG, LVL(5, 12, 4, 20, -7), (G_NOGEN | G_NOCORPSE), - A(ATTK(AT_BITE, AD_WERE, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(500, 250, MS_BARK, MZ_MEDIUM), MR_POISON, 0, - M1_NOHANDS | M1_POIS | M1_REGEN | M1_CARNIVORE, - M2_NOPOLY | M2_WERE | M2_HOSTILE, M3_INFRAVISIBLE, 7, CLR_BROWN), - MON("winter wolf cub", S_DOG, LVL(5, 12, 4, 0, -5), - (G_NOHELL | G_GENO | G_SGROUP | 2), - A(ATTK(AT_BITE, AD_PHYS, 1, 8), ATTK(AT_BREA, AD_COLD, 1, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(250, 200, MS_BARK, MZ_SMALL), MR_COLD, MR_COLD, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_HOSTILE, 0, 7, CLR_CYAN), - MON("warg", S_DOG, LVL(7, 12, 4, 0, -5), (G_GENO | G_SGROUP | 2), - A(ATTK(AT_BITE, AD_PHYS, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(850, 350, MS_BARK, MZ_MEDIUM), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_HOSTILE, M3_INFRAVISIBLE, - 8, CLR_BROWN), - MON("winter wolf", S_DOG, LVL(7, 12, 4, 20, 0), (G_NOHELL | G_GENO | 1), - A(ATTK(AT_BITE, AD_PHYS, 2, 6), ATTK(AT_BREA, AD_COLD, 2, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(700, 300, MS_BARK, MZ_LARGE), MR_COLD, MR_COLD, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_HOSTILE | M2_STRONG, 0, - 9, CLR_CYAN), - MON("hell hound pup", S_DOG, LVL(7, 12, 4, 20, -5), - (G_HELL | G_GENO | G_SGROUP | 1), - A(ATTK(AT_BITE, AD_PHYS, 2, 6), ATTK(AT_BREA, AD_FIRE, 2, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(200, 200, MS_BARK, MZ_SMALL), MR_FIRE, MR_FIRE, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_HOSTILE, M3_INFRAVISIBLE, - 9, CLR_RED), - MON("hell hound", S_DOG, LVL(12, 14, 2, 20, 0), (G_HELL | G_GENO | 1), - A(ATTK(AT_BITE, AD_PHYS, 3, 6), ATTK(AT_BREA, AD_FIRE, 3, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(600, 300, MS_BARK, MZ_MEDIUM), MR_FIRE, MR_FIRE, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_HOSTILE | M2_STRONG, - M3_INFRAVISIBLE, 14, CLR_RED), -#ifdef CHARON - MON("Cerberus", S_DOG, LVL(12, 10, 2, 20, -7), - (G_NOGEN | G_UNIQ | G_HELL), - A(ATTK(AT_BITE, AD_PHYS, 3, 6), ATTK(AT_BITE, AD_PHYS, 3, 6), - ATTK(AT_BITE, AD_PHYS, 3, 6), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1000, 350, MS_BARK, MZ_LARGE), MR_FIRE, MR_FIRE, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, - M2_NOPOLY | M2_HOSTILE | M2_STRONG | M2_PNAME | M2_MALE, - M3_INFRAVISIBLE, 14, CLR_RED), -#endif - /* - * eyes - */ - MON("gas spore", S_EYE, LVL(1, 3, 10, 0, 0), (G_NOCORPSE | G_GENO | 1), - A(ATTK(AT_BOOM, AD_PHYS, 4, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(10, 10, MS_SILENT, MZ_SMALL), 0, 0, - M1_FLY | M1_BREATHLESS | M1_NOLIMBS | M1_NOHEAD | M1_MINDLESS, - M2_HOSTILE | M2_NEUTER, 0, 2, CLR_GRAY), - MON("floating eye", S_EYE, LVL(2, 1, 9, 10, 0), (G_GENO | 5), - A(ATTK(AT_NONE, AD_PLYS, 0, 70), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - 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), - 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, - NO_ATTK), - SIZ(10, 10, MS_SILENT, MZ_SMALL), MR_COLD, MR_COLD, - M1_FLY | M1_BREATHLESS | M1_NOLIMBS | M1_NOHEAD | M1_MINDLESS - | M1_NOTAKE, - M2_HOSTILE | M2_NEUTER, M3_INFRAVISIBLE, 8, CLR_WHITE), - MON("flaming sphere", S_EYE, LVL(6, 13, 4, 0, 0), - (G_NOCORPSE | G_GENO | 2), A(ATTK(AT_EXPL, AD_FIRE, 4, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(10, 10, MS_SILENT, MZ_SMALL), MR_FIRE, MR_FIRE, - M1_FLY | M1_BREATHLESS | M1_NOLIMBS | M1_NOHEAD | M1_MINDLESS - | M1_NOTAKE, - M2_HOSTILE | M2_NEUTER, M3_INFRAVISIBLE, 8, CLR_RED), - MON("shocking sphere", S_EYE, LVL(6, 13, 4, 0, 0), - (G_NOCORPSE | G_GENO | 2), A(ATTK(AT_EXPL, AD_ELEC, 4, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(10, 10, MS_SILENT, MZ_SMALL), MR_ELEC, MR_ELEC, - M1_FLY | M1_BREATHLESS | M1_NOLIMBS | M1_NOHEAD | M1_MINDLESS - | M1_NOTAKE, - M2_HOSTILE | M2_NEUTER, M3_INFRAVISIBLE, 8, HI_ZAP), -#if 0 /* not yet implemented */ - MON("beholder", S_EYE, - LVL(6, 3, 4, 0, -10), (G_GENO | 2), - A(ATTK(AT_GAZE, AD_SLOW, 0, 0), ATTK(AT_GAZE, AD_SLEE, 2,25), - ATTK(AT_GAZE, AD_DISN, 0, 0), ATTK(AT_GAZE, AD_STON, 0, 0), - ATTK(AT_GAZE, AD_CNCL, 2, 4), ATTK(AT_BITE, AD_PHYS, 2, 4)), - SIZ(10, 10, MS_SILENT, MZ_SMALL), MR_COLD, 0, - M1_FLY | M1_BREATHLESS | M1_NOLIMBS | M1_NOHEAD | M1_MINDLESS, - M2_NOPOLY | M2_HOSTILE | M2_NEUTER, M3_INFRAVISIBLE, 13, CLR_BROWN), -#endif - /* - * felines - */ - MON("kitten", S_FELINE, LVL(2, 18, 6, 0, 0), (G_GENO | 1), - A(ATTK(AT_BITE, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(150, 150, MS_MEW, MZ_SMALL), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_WANDER | M2_DOMESTIC, - M3_INFRAVISIBLE, 3, HI_DOMESTIC), - MON("housecat", S_FELINE, LVL(4, 16, 5, 0, 0), (G_GENO | 1), - A(ATTK(AT_BITE, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(200, 200, MS_MEW, MZ_SMALL), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_DOMESTIC, M3_INFRAVISIBLE, - 5, HI_DOMESTIC), - MON("jaguar", S_FELINE, LVL(4, 15, 6, 0, 0), (G_GENO | 2), - A(ATTK(AT_CLAW, AD_PHYS, 1, 4), ATTK(AT_CLAW, AD_PHYS, 1, 4), - ATTK(AT_BITE, AD_PHYS, 1, 8), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(600, 300, MS_GROWL, MZ_LARGE), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_HOSTILE, M3_INFRAVISIBLE, - 6, CLR_BROWN), - MON("lynx", S_FELINE, LVL(5, 15, 6, 0, 0), (G_GENO | 1), - A(ATTK(AT_CLAW, AD_PHYS, 1, 4), ATTK(AT_CLAW, AD_PHYS, 1, 4), - ATTK(AT_BITE, AD_PHYS, 1, 10), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(600, 300, MS_GROWL, MZ_SMALL), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_HOSTILE, M3_INFRAVISIBLE, - 7, CLR_CYAN), - MON("panther", S_FELINE, LVL(5, 15, 6, 0, 0), (G_GENO | 1), - A(ATTK(AT_CLAW, AD_PHYS, 1, 6), ATTK(AT_CLAW, AD_PHYS, 1, 6), - ATTK(AT_BITE, AD_PHYS, 1, 10), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(600, 300, MS_GROWL, MZ_LARGE), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_HOSTILE, M3_INFRAVISIBLE, - 7, CLR_BLACK), - MON("large cat", S_FELINE, LVL(6, 15, 4, 0, 0), (G_GENO | 1), - A(ATTK(AT_BITE, AD_PHYS, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(250, 250, MS_MEW, MZ_SMALL), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_STRONG | M2_DOMESTIC, - M3_INFRAVISIBLE, 7, HI_DOMESTIC), - MON("tiger", S_FELINE, LVL(6, 12, 6, 0, 0), (G_GENO | 2), - A(ATTK(AT_CLAW, AD_PHYS, 2, 4), ATTK(AT_CLAW, AD_PHYS, 2, 4), - ATTK(AT_BITE, AD_PHYS, 1, 10), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(600, 300, MS_GROWL, MZ_LARGE), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_HOSTILE, M3_INFRAVISIBLE, - 8, CLR_YELLOW), - /* - * gremlins and gargoyles - */ - MON("gremlin", S_GREMLIN, LVL(5, 12, 2, 25, -9), (G_GENO | 2), - A(ATTK(AT_CLAW, AD_PHYS, 1, 6), ATTK(AT_CLAW, AD_PHYS, 1, 6), - ATTK(AT_BITE, AD_PHYS, 1, 4), ATTK(AT_CLAW, AD_CURS, 0, 0), NO_ATTK, - NO_ATTK), - SIZ(100, 20, MS_LAUGH, MZ_SMALL), MR_POISON, MR_POISON, - M1_SWIM | M1_HUMANOID | M1_POIS, M2_STALK, M3_INFRAVISIBLE, - 8, CLR_GREEN), - MON("gargoyle", S_GREMLIN, LVL(6, 10, -4, 0, -9), (G_GENO | 2), - A(ATTK(AT_CLAW, AD_PHYS, 2, 6), ATTK(AT_CLAW, AD_PHYS, 2, 6), - ATTK(AT_BITE, AD_PHYS, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1000, 200, MS_GRUNT, MZ_HUMAN), MR_STONE, MR_STONE, - M1_HUMANOID | M1_THICK_HIDE | M1_BREATHLESS, M2_HOSTILE | M2_STRONG, - 0, 8, CLR_BROWN), - MON("winged gargoyle", S_GREMLIN, LVL(9, 15, -2, 0, -12), (G_GENO | 1), - A(ATTK(AT_CLAW, AD_PHYS, 3, 6), ATTK(AT_CLAW, AD_PHYS, 3, 6), - ATTK(AT_BITE, AD_PHYS, 3, 4), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1200, 300, MS_GRUNT, MZ_HUMAN), MR_STONE, MR_STONE, - M1_FLY | M1_HUMANOID | M1_THICK_HIDE | M1_BREATHLESS | M1_OVIPAROUS, - M2_LORD | M2_HOSTILE | M2_STRONG | M2_MAGIC, 0, 11, HI_LORD), - /* - * humanoids - */ - MON("hobbit", S_HUMANOID, LVL(1, 9, 10, 0, 6), (G_GENO | 2), - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(500, 200, MS_HUMANOID, MZ_SMALL), 0, 0, M1_HUMANOID | M1_OMNIVORE, - M2_COLLECT, M3_INFRAVISIBLE | M3_INFRAVISION, 2, CLR_GREEN), - MON("dwarf", S_HUMANOID, LVL(2, 6, 10, 10, 4), (G_GENO | 3), - A(ATTK(AT_WEAP, AD_PHYS, 1, 8), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(900, 300, MS_HUMANOID, MZ_HUMAN), 0, 0, - M1_TUNNEL | M1_NEEDPICK | M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_DWARF | M2_STRONG | M2_GREEDY | M2_JEWELS | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 4, CLR_RED), - 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), - 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), - SIZ(900, 300, MS_HUMANOID, MZ_HUMAN), 0, 0, - M1_TUNNEL | M1_NEEDPICK | M1_HUMANOID | M1_OMNIVORE, - M2_DWARF | M2_STRONG | M2_LORD | M2_MALE | M2_GREEDY | M2_JEWELS - | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 6, CLR_BLUE), - MON("dwarf king", S_HUMANOID, LVL(6, 6, 10, 20, 6), (G_GENO | 1), - A(ATTK(AT_WEAP, AD_PHYS, 2, 6), ATTK(AT_WEAP, AD_PHYS, 2, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(900, 300, MS_HUMANOID, MZ_HUMAN), 0, 0, - M1_TUNNEL | M1_NEEDPICK | M1_HUMANOID | M1_OMNIVORE, - M2_DWARF | M2_STRONG | M2_PRINCE | M2_MALE | M2_GREEDY | M2_JEWELS - | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 8, HI_LORD), - MON("mind flayer", S_HUMANOID, LVL(9, 12, 5, 90, -8), (G_GENO | 1), - A(ATTK(AT_WEAP, AD_PHYS, 1, 4), ATTK(AT_TENT, AD_DRIN, 2, 1), - ATTK(AT_TENT, AD_DRIN, 2, 1), ATTK(AT_TENT, AD_DRIN, 2, 1), NO_ATTK, - NO_ATTK), - SIZ(1450, 400, MS_HISS, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_FLY | M1_SEE_INVIS | M1_OMNIVORE, - M2_HOSTILE | M2_NASTY | M2_GREEDY | M2_JEWELS | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 13, CLR_MAGENTA), - MON("master mind flayer", S_HUMANOID, LVL(13, 12, 0, 90, -8), - (G_GENO | 1), - A(ATTK(AT_WEAP, AD_PHYS, 1, 8), ATTK(AT_TENT, AD_DRIN, 2, 1), - ATTK(AT_TENT, AD_DRIN, 2, 1), ATTK(AT_TENT, AD_DRIN, 2, 1), - ATTK(AT_TENT, AD_DRIN, 2, 1), ATTK(AT_TENT, AD_DRIN, 2, 1)), - SIZ(1450, 400, MS_HISS, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_FLY | M1_SEE_INVIS | M1_OMNIVORE, - M2_HOSTILE | M2_NASTY | M2_GREEDY | M2_JEWELS | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 19, CLR_MAGENTA), - /* - * imps & other minor demons/devils - */ - MON("manes", S_IMP, LVL(1, 3, 7, 0, -7), - (G_GENO | G_LGROUP | G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 1, 3), ATTK(AT_CLAW, AD_PHYS, 1, 3), - ATTK(AT_BITE, AD_PHYS, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(100, 100, MS_SILENT, MZ_SMALL), MR_SLEEP | MR_POISON, 0, M1_POIS, - M2_HOSTILE | M2_STALK, M3_INFRAVISIBLE | M3_INFRAVISION, 3, CLR_RED), - MON("homunculus", S_IMP, LVL(2, 12, 6, 10, -7), (G_GENO | 2), - A(ATTK(AT_BITE, AD_SLEE, 1, 3), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(60, 100, MS_SILENT, MZ_TINY), MR_SLEEP | MR_POISON, - MR_SLEEP | MR_POISON, M1_FLY | M1_POIS, M2_STALK, - M3_INFRAVISIBLE | M3_INFRAVISION, 3, CLR_GREEN), - MON("imp", S_IMP, LVL(3, 12, 2, 20, -7), (G_GENO | 1), - A(ATTK(AT_CLAW, AD_PHYS, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(20, 10, MS_CUSS, MZ_TINY), 0, 0, M1_REGEN, M2_WANDER | M2_STALK, - M3_INFRAVISIBLE | M3_INFRAVISION, 4, CLR_RED), - MON("lemure", S_IMP, LVL(3, 3, 7, 0, -7), - (G_HELL | G_GENO | G_LGROUP | G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 1, 3), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(150, 100, MS_SILENT, MZ_MEDIUM), MR_SLEEP | MR_POISON, MR_SLEEP, - M1_POIS | M1_REGEN, M2_HOSTILE | M2_WANDER | M2_STALK | M2_NEUTER, - M3_INFRAVISIBLE | M3_INFRAVISION, 5, CLR_BROWN), - MON("quasit", S_IMP, LVL(3, 15, 2, 20, -7), (G_GENO | 2), - A(ATTK(AT_CLAW, AD_DRDX, 1, 2), ATTK(AT_CLAW, AD_DRDX, 1, 2), - ATTK(AT_BITE, AD_PHYS, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(200, 200, MS_SILENT, MZ_SMALL), MR_POISON, MR_POISON, M1_REGEN, - M2_STALK, M3_INFRAVISIBLE | M3_INFRAVISION, 7, CLR_BLUE), - MON("tengu", S_IMP, LVL(6, 13, 5, 30, 7), (G_GENO | 3), - A(ATTK(AT_BITE, AD_PHYS, 1, 7), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(300, 200, MS_SQAWK, MZ_SMALL), MR_POISON, MR_POISON, - M1_TPORT | M1_TPORT_CNTRL, M2_STALK, M3_INFRAVISIBLE | M3_INFRAVISION, - 7, CLR_CYAN), - /* - * jellies - */ - MON("blue jelly", S_JELLY, LVL(4, 0, 8, 10, 0), (G_GENO | 2), - A(ATTK(AT_NONE, AD_COLD, 0, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(50, 20, MS_SILENT, MZ_MEDIUM), MR_COLD | MR_POISON, - MR_COLD | MR_POISON, - M1_BREATHLESS | M1_AMORPHOUS | M1_NOEYES | M1_NOLIMBS | M1_NOHEAD - | M1_MINDLESS | M1_NOTAKE, - M2_HOSTILE | M2_NEUTER, 0, 5, CLR_BLUE), - MON("spotted jelly", S_JELLY, LVL(5, 0, 8, 10, 0), (G_GENO | 1), - A(ATTK(AT_NONE, AD_ACID, 0, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(50, 20, MS_SILENT, MZ_MEDIUM), MR_ACID | MR_STONE, 0, - M1_BREATHLESS | M1_AMORPHOUS | M1_NOEYES | M1_NOLIMBS | M1_NOHEAD - | M1_MINDLESS | M1_ACID | M1_NOTAKE, - M2_HOSTILE | M2_NEUTER, 0, 6, CLR_GREEN), - MON("ochre jelly", S_JELLY, LVL(6, 3, 8, 20, 0), (G_GENO | 2), - A(ATTK(AT_ENGL, AD_ACID, 3, 6), ATTK(AT_NONE, AD_ACID, 3, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(50, 20, MS_SILENT, MZ_MEDIUM), MR_ACID | MR_STONE, 0, - M1_BREATHLESS | M1_AMORPHOUS | M1_NOEYES | M1_NOLIMBS | M1_NOHEAD - | M1_MINDLESS | M1_ACID | M1_NOTAKE, - M2_HOSTILE | M2_NEUTER, 0, 8, CLR_BROWN), - /* - * kobolds - */ - MON("kobold", S_KOBOLD, LVL(0, 6, 10, 0, -2), (G_GENO | 1), - 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), MR_POISON, 0, - M1_HUMANOID | M1_POIS | M1_OMNIVORE, M2_HOSTILE | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 1, CLR_BROWN), - MON("large kobold", S_KOBOLD, LVL(1, 6, 10, 0, -3), (G_GENO | 1), - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(450, 150, MS_ORC, MZ_SMALL), MR_POISON, 0, - M1_HUMANOID | M1_POIS | M1_OMNIVORE, M2_HOSTILE | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 2, CLR_RED), - MON("kobold lord", S_KOBOLD, LVL(2, 6, 10, 0, -4), (G_GENO | 1), - A(ATTK(AT_WEAP, AD_PHYS, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(500, 200, MS_ORC, MZ_SMALL), MR_POISON, 0, - M1_HUMANOID | M1_POIS | M1_OMNIVORE, - M2_HOSTILE | M2_LORD | M2_MALE | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 3, HI_LORD), - MON("kobold shaman", S_KOBOLD, LVL(2, 6, 6, 10, -4), (G_GENO | 1), - A(ATTK(AT_MAGC, AD_SPEL, 0, 0), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(450, 150, MS_ORC, MZ_SMALL), MR_POISON, 0, - M1_HUMANOID | M1_POIS | M1_OMNIVORE, M2_HOSTILE | M2_MAGIC, - M3_INFRAVISIBLE | M3_INFRAVISION, 4, HI_ZAP), - /* - * leprechauns - */ - MON("leprechaun", S_LEPRECHAUN, LVL(5, 15, 8, 20, 0), (G_GENO | 4), - A(ATTK(AT_CLAW, AD_SGLD, 1, 2), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(60, 30, MS_LAUGH, MZ_TINY), 0, 0, M1_HUMANOID | M1_TPORT, - M2_HOSTILE | M2_GREEDY, M3_INFRAVISIBLE, 4, CLR_GREEN), - /* - * mimics - */ - MON("small mimic", S_MIMIC, LVL(7, 3, 7, 0, 0), (G_GENO | 2), - A(ATTK(AT_CLAW, AD_PHYS, 3, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(300, 200, MS_SILENT, MZ_MEDIUM), MR_ACID, 0, - M1_BREATHLESS | M1_AMORPHOUS | M1_HIDE | M1_ANIMAL | M1_NOEYES - | M1_NOHEAD | M1_NOLIMBS | M1_THICK_HIDE | M1_CARNIVORE, - M2_HOSTILE, 0, 8, CLR_BROWN), - MON("large mimic", S_MIMIC, LVL(8, 3, 7, 10, 0), (G_GENO | 1), - A(ATTK(AT_CLAW, AD_STCK, 3, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(600, 400, MS_SILENT, MZ_LARGE), MR_ACID, 0, - M1_CLING | M1_BREATHLESS | M1_AMORPHOUS | M1_HIDE | M1_ANIMAL - | M1_NOEYES | M1_NOHEAD | M1_NOLIMBS | M1_THICK_HIDE - | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG, 0, 9, CLR_RED), - MON("giant mimic", S_MIMIC, LVL(9, 3, 7, 20, 0), (G_GENO | 1), - A(ATTK(AT_CLAW, AD_STCK, 3, 6), ATTK(AT_CLAW, AD_STCK, 3, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(800, 500, MS_SILENT, MZ_LARGE), MR_ACID, 0, - M1_CLING | M1_BREATHLESS | M1_AMORPHOUS | M1_HIDE | M1_ANIMAL - | M1_NOEYES | M1_NOHEAD | M1_NOLIMBS | M1_THICK_HIDE - | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG, 0, 11, HI_LORD), - /* - * nymphs - */ - MON("wood nymph", S_NYMPH, LVL(3, 12, 9, 20, 0), (G_GENO | 2), - A(ATTK(AT_CLAW, AD_SITM, 0, 0), ATTK(AT_CLAW, AD_SEDU, 0, 0), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(600, 300, MS_SEDUCE, MZ_HUMAN), 0, 0, M1_HUMANOID | M1_TPORT, - M2_HOSTILE | M2_FEMALE | M2_COLLECT, M3_INFRAVISIBLE, 5, CLR_GREEN), - MON("water nymph", S_NYMPH, LVL(3, 12, 9, 20, 0), (G_GENO | 2), - A(ATTK(AT_CLAW, AD_SITM, 0, 0), ATTK(AT_CLAW, AD_SEDU, 0, 0), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(600, 300, MS_SEDUCE, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_TPORT | M1_SWIM, M2_HOSTILE | M2_FEMALE | M2_COLLECT, - M3_INFRAVISIBLE, 5, CLR_BLUE), - MON("mountain nymph", S_NYMPH, LVL(3, 12, 9, 20, 0), (G_GENO | 2), - A(ATTK(AT_CLAW, AD_SITM, 0, 0), ATTK(AT_CLAW, AD_SEDU, 0, 0), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(600, 300, MS_SEDUCE, MZ_HUMAN), 0, 0, M1_HUMANOID | M1_TPORT, - M2_HOSTILE | M2_FEMALE | M2_COLLECT, M3_INFRAVISIBLE, 5, CLR_BROWN), - /* - * orcs - */ - 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), - 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), - /* 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, - 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, - 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, - 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, - 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, - 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, - M2_ORC | M2_STRONG | M2_GREEDY | M2_JEWELS | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 7, HI_LORD), +static struct permonst mons_init[NUMMONS + 1] = { +#include "monsters.h" /* - * piercers - */ - MON("rock piercer", S_PIERCER, LVL(3, 1, 3, 0, 0), (G_GENO | 4), - A(ATTK(AT_BITE, AD_PHYS, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(200, 200, MS_SILENT, MZ_SMALL), 0, 0, - M1_CLING | M1_HIDE | M1_ANIMAL | M1_NOEYES | M1_NOLIMBS | M1_CARNIVORE - | M1_NOTAKE, - M2_HOSTILE, 0, 4, CLR_GRAY), - MON("iron piercer", S_PIERCER, LVL(5, 1, 0, 0, 0), (G_GENO | 2), - A(ATTK(AT_BITE, AD_PHYS, 3, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(400, 300, MS_SILENT, MZ_MEDIUM), 0, 0, - M1_CLING | M1_HIDE | M1_ANIMAL | M1_NOEYES | M1_NOLIMBS | M1_CARNIVORE - | M1_NOTAKE, - M2_HOSTILE, 0, 6, CLR_CYAN), - MON("glass piercer", S_PIERCER, LVL(7, 1, 0, 0, 0), (G_GENO | 1), - A(ATTK(AT_BITE, AD_PHYS, 4, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(400, 300, MS_SILENT, MZ_MEDIUM), MR_ACID, 0, - M1_CLING | M1_HIDE | M1_ANIMAL | M1_NOEYES | M1_NOLIMBS | M1_CARNIVORE - | M1_NOTAKE, - M2_HOSTILE, 0, 9, CLR_WHITE), - /* - * quadrupeds - */ - MON("rothe", S_QUADRUPED, LVL(2, 9, 7, 0, 0), (G_GENO | G_SGROUP | 4), - A(ATTK(AT_CLAW, AD_PHYS, 1, 3), ATTK(AT_BITE, AD_PHYS, 1, 3), - ATTK(AT_BITE, AD_PHYS, 1, 8), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(400, 100, MS_SILENT, MZ_LARGE), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_OMNIVORE, M2_HOSTILE, M3_INFRAVISIBLE, - 4, CLR_BROWN), - MON("mumak", S_QUADRUPED, LVL(5, 9, 0, 0, -2), (G_GENO | 1), - A(ATTK(AT_BUTT, AD_PHYS, 4, 12), ATTK(AT_BITE, AD_PHYS, 2, 6), - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(2500, 500, MS_ROAR, MZ_LARGE), 0, 0, - M1_ANIMAL | M1_THICK_HIDE | M1_NOHANDS | M1_HERBIVORE, - M2_HOSTILE | M2_STRONG, M3_INFRAVISIBLE, 7, CLR_GRAY), - MON("leocrotta", S_QUADRUPED, LVL(6, 18, 4, 10, 0), (G_GENO | 2), - A(ATTK(AT_CLAW, AD_PHYS, 2, 6), ATTK(AT_BITE, AD_PHYS, 2, 6), - ATTK(AT_CLAW, AD_PHYS, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1200, 500, MS_IMITATE, MZ_LARGE), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_OMNIVORE, M2_HOSTILE | M2_STRONG, - M3_INFRAVISIBLE, 8, CLR_RED), - MON("wumpus", S_QUADRUPED, LVL(8, 3, 2, 10, 0), (G_GENO | 1), - A(ATTK(AT_BITE, AD_PHYS, 3, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(2500, 500, MS_BURBLE, MZ_LARGE), 0, 0, - M1_CLING | M1_ANIMAL | M1_NOHANDS | M1_OMNIVORE, - M2_HOSTILE | M2_STRONG, M3_INFRAVISIBLE, 9, CLR_CYAN), - MON("titanothere", S_QUADRUPED, LVL(12, 12, 6, 0, 0), (G_GENO | 2), - A(ATTK(AT_CLAW, AD_PHYS, 2, 8), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(2650, 650, MS_SILENT, MZ_LARGE), 0, 0, - M1_ANIMAL | M1_THICK_HIDE | M1_NOHANDS | M1_HERBIVORE, - M2_HOSTILE | M2_STRONG, M3_INFRAVISIBLE, 13, CLR_GRAY), - MON("baluchitherium", S_QUADRUPED, LVL(14, 12, 5, 0, 0), (G_GENO | 2), - A(ATTK(AT_CLAW, AD_PHYS, 5, 4), ATTK(AT_CLAW, AD_PHYS, 5, 4), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(3800, 800, MS_SILENT, MZ_LARGE), 0, 0, - M1_ANIMAL | M1_THICK_HIDE | M1_NOHANDS | M1_HERBIVORE, - M2_HOSTILE | M2_STRONG, M3_INFRAVISIBLE, 15, CLR_GRAY), - MON("mastodon", S_QUADRUPED, LVL(20, 12, 5, 0, 0), (G_GENO | 1), - A(ATTK(AT_BUTT, AD_PHYS, 4, 8), ATTK(AT_BUTT, AD_PHYS, 4, 8), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(3800, 800, MS_SILENT, MZ_LARGE), 0, 0, - M1_ANIMAL | M1_THICK_HIDE | M1_NOHANDS | M1_HERBIVORE, - M2_HOSTILE | M2_STRONG, M3_INFRAVISIBLE, 22, CLR_BLACK), - /* - * rodents - */ - MON("sewer rat", S_RODENT, LVL(0, 12, 7, 0, 0), (G_GENO | G_SGROUP | 1), - 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), - 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), - SIZ(30, 30, MS_SQEEK, MZ_TINY), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_HOSTILE, M3_INFRAVISIBLE, - 2, CLR_BROWN), - MON("rabid rat", S_RODENT, LVL(2, 12, 6, 0, 0), (G_GENO | 1), - 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), - 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), - SIZ(40, 30, MS_SQEEK, MZ_TINY), MR_POISON, 0, - M1_NOHANDS | M1_POIS | M1_REGEN | M1_CARNIVORE, - M2_NOPOLY | M2_WERE | M2_HOSTILE, M3_INFRAVISIBLE, 4, CLR_BROWN), - MON("rock mole", S_RODENT, LVL(3, 3, 0, 20, 0), (G_GENO | 2), - A(ATTK(AT_BITE, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(30, 30, MS_SILENT, MZ_SMALL), 0, 0, - M1_TUNNEL | M1_ANIMAL | M1_NOHANDS | M1_METALLIVORE, - M2_HOSTILE | M2_GREEDY | M2_JEWELS | M2_COLLECT, M3_INFRAVISIBLE, - 4, CLR_GRAY), - MON("woodchuck", S_RODENT, LVL(3, 3, 0, 20, 0), (G_NOGEN | G_GENO), - A(ATTK(AT_BITE, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(30, 30, MS_SILENT, MZ_SMALL), 0, 0, - M1_TUNNEL /*LOGGING*/ | M1_ANIMAL | M1_NOHANDS | M1_SWIM - | M1_HERBIVORE, - /* In reality, they tunnel instead of cutting lumber. Oh, well. */ - M2_WANDER | M2_HOSTILE, M3_INFRAVISIBLE, 4, CLR_BROWN), - /* - * spiders & scorpions (keep webmaker() in sync if new critters are added) - */ - MON("cave spider", S_SPIDER, LVL(1, 12, 3, 0, 0), (G_GENO | G_SGROUP | 2), - A(ATTK(AT_BITE, AD_PHYS, 1, 2), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(50, 50, MS_SILENT, MZ_TINY), MR_POISON, MR_POISON, - M1_CONCEAL | M1_ANIMAL | M1_NOHANDS | M1_OVIPAROUS | M1_CARNIVORE, - M2_HOSTILE, 0, 3, CLR_GRAY), - MON("centipede", S_SPIDER, LVL(2, 4, 3, 0, 0), (G_GENO | 1), - A(ATTK(AT_BITE, AD_DRST, 1, 3), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(50, 50, MS_SILENT, MZ_TINY), MR_POISON, MR_POISON, - M1_CONCEAL | M1_ANIMAL | M1_NOHANDS | M1_OVIPAROUS | M1_CARNIVORE, - M2_HOSTILE, 0, 4, CLR_YELLOW), - MON("giant spider", S_SPIDER, LVL(5, 15, 4, 0, 0), (G_GENO | 1), - A(ATTK(AT_BITE, AD_DRST, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(100, 100, MS_SILENT, MZ_LARGE), MR_POISON, MR_POISON, - M1_ANIMAL | M1_NOHANDS | M1_OVIPAROUS | M1_POIS | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG, 0, 7, CLR_MAGENTA), - MON("scorpion", S_SPIDER, LVL(5, 15, 3, 0, 0), (G_GENO | 2), - A(ATTK(AT_CLAW, AD_PHYS, 1, 2), ATTK(AT_CLAW, AD_PHYS, 1, 2), - ATTK(AT_STNG, AD_DRST, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(50, 100, MS_SILENT, MZ_SMALL), MR_POISON, MR_POISON, - M1_CONCEAL | M1_ANIMAL | M1_NOHANDS | M1_OVIPAROUS | M1_POIS - | M1_CARNIVORE, - M2_HOSTILE, 0, 8, CLR_RED), - /* - * trappers, lurkers, &c - */ - MON("lurker above", S_TRAPPER, LVL(10, 3, 3, 0, 0), (G_GENO | 2), - A(ATTK(AT_ENGL, AD_DGST, 1, 8), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(800, 350, MS_SILENT, MZ_HUGE), 0, 0, - M1_HIDE | M1_FLY | M1_ANIMAL | M1_NOEYES | M1_NOLIMBS | M1_NOHEAD - | M1_CARNIVORE, - M2_HOSTILE | M2_STALK | M2_STRONG, 0, 12, CLR_GRAY), - MON("trapper", S_TRAPPER, LVL(12, 3, 3, 0, 0), (G_GENO | 2), - A(ATTK(AT_ENGL, AD_DGST, 1, 10), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(800, 350, MS_SILENT, MZ_HUGE), 0, 0, - M1_HIDE | M1_ANIMAL | M1_NOEYES | M1_NOLIMBS | M1_NOHEAD - | M1_CARNIVORE, - M2_HOSTILE | M2_STALK | M2_STRONG, 0, 14, CLR_GREEN), - /* - * unicorns and horses - */ - MON("pony", S_UNICORN, LVL(3, 16, 6, 0, 0), (G_GENO | 2), - A(ATTK(AT_KICK, AD_PHYS, 1, 6), ATTK(AT_BITE, AD_PHYS, 1, 2), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1300, 250, MS_NEIGH, MZ_MEDIUM), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_HERBIVORE, - M2_WANDER | M2_STRONG | M2_DOMESTIC, M3_INFRAVISIBLE, 4, CLR_BROWN), - MON("white unicorn", S_UNICORN, LVL(4, 24, 2, 70, 7), (G_GENO | 2), - A(ATTK(AT_BUTT, AD_PHYS, 1, 12), ATTK(AT_KICK, AD_PHYS, 1, 6), - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1300, 300, MS_NEIGH, MZ_LARGE), MR_POISON, MR_POISON, - M1_NOHANDS | M1_HERBIVORE, M2_WANDER | M2_STRONG | M2_JEWELS, - M3_INFRAVISIBLE, 6, CLR_WHITE), - MON("gray unicorn", S_UNICORN, LVL(4, 24, 2, 70, 0), (G_GENO | 1), - A(ATTK(AT_BUTT, AD_PHYS, 1, 12), ATTK(AT_KICK, AD_PHYS, 1, 6), - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1300, 300, MS_NEIGH, MZ_LARGE), MR_POISON, MR_POISON, - M1_NOHANDS | M1_HERBIVORE, M2_WANDER | M2_STRONG | M2_JEWELS, - M3_INFRAVISIBLE, 6, CLR_GRAY), - MON("black unicorn", S_UNICORN, LVL(4, 24, 2, 70, -7), (G_GENO | 1), - A(ATTK(AT_BUTT, AD_PHYS, 1, 12), ATTK(AT_KICK, AD_PHYS, 1, 6), - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1300, 300, MS_NEIGH, MZ_LARGE), MR_POISON, MR_POISON, - M1_NOHANDS | M1_HERBIVORE, M2_WANDER | M2_STRONG | M2_JEWELS, - M3_INFRAVISIBLE, 6, CLR_BLACK), - MON("horse", S_UNICORN, LVL(5, 20, 5, 0, 0), (G_GENO | 2), - A(ATTK(AT_KICK, AD_PHYS, 1, 8), ATTK(AT_BITE, AD_PHYS, 1, 3), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1500, 300, MS_NEIGH, MZ_LARGE), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_HERBIVORE, - M2_WANDER | M2_STRONG | M2_DOMESTIC, M3_INFRAVISIBLE, 7, CLR_BROWN), - MON("warhorse", S_UNICORN, LVL(7, 24, 4, 0, 0), (G_GENO | 2), - A(ATTK(AT_KICK, AD_PHYS, 1, 10), ATTK(AT_BITE, AD_PHYS, 1, 4), - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1800, 350, MS_NEIGH, MZ_LARGE), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_HERBIVORE, - M2_WANDER | M2_STRONG | M2_DOMESTIC, M3_INFRAVISIBLE, 9, CLR_BROWN), - /* - * vortices - */ - MON("fog cloud", S_VORTEX, LVL(3, 1, 0, 0, 0), (G_GENO | G_NOCORPSE | 2), - A(ATTK(AT_ENGL, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(0, 0, MS_SILENT, MZ_HUGE), MR_SLEEP | MR_POISON | MR_STONE, 0, - M1_FLY | M1_BREATHLESS | M1_NOEYES | M1_NOLIMBS | M1_NOHEAD - | M1_MINDLESS | M1_AMORPHOUS | M1_UNSOLID, - M2_HOSTILE | M2_NEUTER, 0, 4, CLR_GRAY), - MON("dust vortex", S_VORTEX, LVL(4, 20, 2, 30, 0), - (G_GENO | G_NOCORPSE | 2), A(ATTK(AT_ENGL, AD_BLND, 2, 8), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(0, 0, MS_SILENT, MZ_HUGE), MR_SLEEP | MR_POISON | MR_STONE, 0, - M1_FLY | M1_BREATHLESS | M1_NOEYES | M1_NOLIMBS | M1_NOHEAD - | M1_MINDLESS, - M2_HOSTILE | M2_NEUTER, 0, 6, CLR_BROWN), - MON("ice vortex", S_VORTEX, LVL(5, 20, 2, 30, 0), - (G_NOHELL | G_GENO | G_NOCORPSE | 1), - A(ATTK(AT_ENGL, AD_COLD, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(0, 0, MS_SILENT, MZ_HUGE), - MR_COLD | MR_SLEEP | MR_POISON | MR_STONE, 0, - M1_FLY | M1_BREATHLESS | M1_NOEYES | M1_NOLIMBS | M1_NOHEAD - | M1_MINDLESS, - M2_HOSTILE | M2_NEUTER, M3_INFRAVISIBLE, 7, CLR_CYAN), - MON("energy vortex", S_VORTEX, LVL(6, 20, 2, 30, 0), - (G_GENO | G_NOCORPSE | 1), - A(ATTK(AT_ENGL, AD_ELEC, 1, 6), ATTK(AT_ENGL, AD_DREN, 2, 6), - ATTK(AT_NONE, AD_ELEC, 0, 4), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(0, 0, MS_SILENT, MZ_HUGE), - MR_ELEC | MR_SLEEP | MR_DISINT | MR_POISON | MR_STONE, 0, - M1_FLY | M1_BREATHLESS | M1_NOEYES | M1_NOLIMBS | M1_NOHEAD - | M1_MINDLESS | M1_UNSOLID, - M2_HOSTILE | M2_NEUTER, 0, 9, HI_ZAP), - MON("steam vortex", S_VORTEX, LVL(7, 22, 2, 30, 0), - (G_HELL | G_GENO | G_NOCORPSE | 2), - A(ATTK(AT_ENGL, AD_FIRE, 1, 8), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(0, 0, MS_SILENT, MZ_HUGE), - MR_FIRE | MR_SLEEP | MR_POISON | MR_STONE, 0, - M1_FLY | M1_BREATHLESS | M1_NOEYES | M1_NOLIMBS | M1_NOHEAD - | M1_MINDLESS | M1_UNSOLID, - M2_HOSTILE | M2_NEUTER, M3_INFRAVISIBLE, 9, CLR_BLUE), - MON("fire vortex", S_VORTEX, LVL(8, 22, 2, 30, 0), - (G_HELL | G_GENO | G_NOCORPSE | 1), - A(ATTK(AT_ENGL, AD_FIRE, 1, 10), ATTK(AT_NONE, AD_FIRE, 0, 4), - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(0, 0, MS_SILENT, MZ_HUGE), - MR_FIRE | MR_SLEEP | MR_POISON | MR_STONE, 0, - M1_FLY | M1_BREATHLESS | M1_NOEYES | M1_NOLIMBS | M1_NOHEAD - | M1_MINDLESS | M1_UNSOLID, - M2_HOSTILE | M2_NEUTER, M3_INFRAVISIBLE, 10, CLR_YELLOW), - /* - * worms - */ - MON("baby long worm", S_WORM, LVL(5, 3, 5, 0, 0), G_GENO, - A(ATTK(AT_BITE, AD_PHYS, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(600, 250, MS_SILENT, MZ_LARGE), 0, 0, - M1_ANIMAL | M1_SLITHY | M1_NOLIMBS | M1_CARNIVORE | M1_NOTAKE, - M2_HOSTILE, 0, 6, CLR_BROWN), - MON("baby purple worm", S_WORM, LVL(8, 3, 5, 0, 0), G_GENO, - A(ATTK(AT_BITE, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(600, 250, MS_SILENT, MZ_LARGE), 0, 0, - M1_ANIMAL | M1_SLITHY | M1_NOLIMBS | M1_CARNIVORE, M2_HOSTILE, 0, - 9, CLR_MAGENTA), - MON("long worm", S_WORM, LVL(9, 3, 5, 10, 0), (G_GENO | 2), - A(ATTK(AT_BITE, AD_PHYS, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(1500, 500, MS_SILENT, MZ_GIGANTIC), 0, 0, - M1_ANIMAL | M1_SLITHY | M1_NOLIMBS | M1_OVIPAROUS | M1_CARNIVORE - | M1_NOTAKE, - M2_HOSTILE | M2_STRONG | M2_NASTY, 0, 10, CLR_BROWN), - MON("purple worm", S_WORM, LVL(15, 9, 6, 20, 0), (G_GENO | 2), - A(ATTK(AT_BITE, AD_PHYS, 2, 8), ATTK(AT_ENGL, AD_DGST, 1, 10), - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(2700, 700, MS_SILENT, MZ_GIGANTIC), 0, 0, - M1_ANIMAL | M1_SLITHY | M1_NOLIMBS | M1_OVIPAROUS | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG | M2_NASTY, 0, 17, CLR_MAGENTA), - /* - * xan, &c - */ - MON("grid bug", S_XAN, LVL(0, 12, 9, 0, 0), - (G_GENO | G_SGROUP | G_NOCORPSE | 3), - A(ATTK(AT_BITE, AD_ELEC, 1, 1), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(15, 10, MS_BUZZ, MZ_TINY), MR_ELEC | MR_POISON, 0, M1_ANIMAL, - M2_HOSTILE, M3_INFRAVISIBLE, 1, CLR_MAGENTA), - MON("xan", S_XAN, LVL(7, 18, -4, 0, 0), (G_GENO | 3), - A(ATTK(AT_STNG, AD_LEGS, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(300, 300, MS_BUZZ, MZ_TINY), MR_POISON, MR_POISON, - M1_FLY | M1_ANIMAL | M1_NOHANDS | M1_POIS, M2_HOSTILE, - M3_INFRAVISIBLE, 9, CLR_RED), - /* - * lights - */ - MON("yellow light", S_LIGHT, LVL(3, 15, 0, 0, 0), - (G_NOCORPSE | G_GENO | 4), A(ATTK(AT_EXPL, AD_BLND, 10, 20), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(0, 0, MS_SILENT, MZ_SMALL), - MR_FIRE | MR_COLD | MR_ELEC | MR_DISINT | MR_SLEEP | MR_POISON - | MR_ACID | MR_STONE, - 0, M1_FLY | M1_BREATHLESS | M1_AMORPHOUS | M1_NOEYES | M1_NOLIMBS - | M1_NOHEAD | M1_MINDLESS | M1_UNSOLID | M1_NOTAKE, - M2_HOSTILE | M2_NEUTER, M3_INFRAVISIBLE, 5, CLR_YELLOW), - MON("black light", S_LIGHT, LVL(5, 15, 0, 0, 0), - (G_NOCORPSE | G_GENO | 2), A(ATTK(AT_EXPL, AD_HALU, 10, 12), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(0, 0, MS_SILENT, MZ_SMALL), - MR_FIRE | MR_COLD | MR_ELEC | MR_DISINT | MR_SLEEP | MR_POISON - | MR_ACID | MR_STONE, - 0, - M1_FLY | M1_BREATHLESS | M1_AMORPHOUS | M1_NOEYES | M1_NOLIMBS - | M1_NOHEAD | M1_MINDLESS | M1_UNSOLID | M1_SEE_INVIS | M1_NOTAKE, - M2_HOSTILE | M2_NEUTER, 0, 7, CLR_BLACK), - /* - * zruty - */ - MON("zruty", S_ZRUTY, LVL(9, 8, 3, 0, 0), (G_GENO | 2), - A(ATTK(AT_CLAW, AD_PHYS, 3, 4), ATTK(AT_CLAW, AD_PHYS, 3, 4), - ATTK(AT_BITE, AD_PHYS, 3, 6), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1200, 600, MS_SILENT, MZ_LARGE), 0, 0, - M1_ANIMAL | M1_HUMANOID | M1_CARNIVORE, M2_HOSTILE | M2_STRONG, - M3_INFRAVISIBLE, 11, CLR_BROWN), - /* - * Angels and other lawful minions - */ - MON("couatl", S_ANGEL, LVL(8, 10, 5, 30, 7), - (G_NOHELL | G_SGROUP | G_NOCORPSE | 1), - A(ATTK(AT_BITE, AD_DRST, 2, 4), ATTK(AT_BITE, AD_PHYS, 1, 3), - ATTK(AT_HUGS, AD_WRAP, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(900, 400, MS_HISS, MZ_LARGE), MR_POISON, 0, - M1_FLY | M1_NOHANDS | M1_SLITHY | M1_POIS, - M2_MINION | M2_STALK | M2_STRONG | M2_NASTY, - M3_INFRAVISIBLE | M3_INFRAVISION, 11, CLR_GREEN), - MON("Aleax", S_ANGEL, LVL(10, 8, 0, 30, 7), (G_NOHELL | G_NOCORPSE | 1), - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), ATTK(AT_WEAP, AD_PHYS, 1, 6), - ATTK(AT_KICK, AD_PHYS, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_IMITATE, MZ_HUMAN), - MR_COLD | MR_ELEC | MR_SLEEP | MR_POISON, 0, - M1_HUMANOID | M1_SEE_INVIS, - M2_MINION | M2_STALK | M2_NASTY | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 12, CLR_YELLOW), - /* Angels start with the emin extension attached, and usually have - the isminion flag set; however, non-minion Angels can be tamed - and will switch to edog (guardian Angel is handled specially and - always sticks with emin) */ - MON("Angel", S_ANGEL, LVL(14, 10, -4, 55, 12), - (G_NOHELL | G_NOCORPSE | 1), - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), ATTK(AT_WEAP, AD_PHYS, 1, 6), - ATTK(AT_CLAW, AD_PHYS, 1, 4), ATTK(AT_MAGC, AD_MAGM, 2, 6), NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_CUSS, MZ_HUMAN), - MR_COLD | MR_ELEC | MR_SLEEP | MR_POISON, 0, - M1_FLY | M1_HUMANOID | M1_SEE_INVIS, - M2_NOPOLY | M2_MINION | M2_STALK | M2_STRONG | M2_NASTY | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 19, CLR_WHITE), - MON("ki-rin", S_ANGEL, LVL(16, 18, -5, 90, 15), - (G_NOHELL | G_NOCORPSE | 1), - A(ATTK(AT_KICK, AD_PHYS, 2, 4), ATTK(AT_KICK, AD_PHYS, 2, 4), - ATTK(AT_BUTT, AD_PHYS, 3, 6), ATTK(AT_MAGC, AD_SPEL, 2, 6), NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_NEIGH, MZ_LARGE), 0, 0, - M1_FLY | M1_ANIMAL | M1_NOHANDS | M1_SEE_INVIS, - M2_NOPOLY | M2_MINION | M2_STALK | M2_STRONG | M2_NASTY | M2_LORD, - M3_INFRAVISIBLE | M3_INFRAVISION, 21, HI_GOLD), - MON("Archon", S_ANGEL, LVL(19, 16, -6, 80, 15), - (G_NOHELL | G_NOCORPSE | 1), - A(ATTK(AT_WEAP, AD_PHYS, 2, 4), ATTK(AT_WEAP, AD_PHYS, 2, 4), - ATTK(AT_GAZE, AD_BLND, 2, 6), ATTK(AT_CLAW, AD_PHYS, 1, 8), - ATTK(AT_MAGC, AD_SPEL, 4, 6), NO_ATTK), - SIZ(WT_HUMAN, 400, MS_CUSS, MZ_LARGE), - MR_FIRE | MR_COLD | MR_ELEC | MR_SLEEP | MR_POISON, 0, - M1_FLY | M1_HUMANOID | M1_SEE_INVIS | M1_REGEN, - M2_NOPOLY | M2_MINION | M2_STALK | M2_STRONG | M2_NASTY | M2_LORD - | M2_COLLECT | M2_MAGIC, - M3_INFRAVISIBLE | M3_INFRAVISION, 26, HI_LORD), - /* - * Bats - */ - MON("bat", S_BAT, LVL(0, 22, 8, 0, 0), (G_GENO | G_SGROUP | 1), - A(ATTK(AT_BITE, AD_PHYS, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(20, 20, MS_SQEEK, MZ_TINY), 0, 0, - M1_FLY | M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_WANDER, - M3_INFRAVISIBLE, 2, CLR_BROWN), - MON("giant bat", S_BAT, LVL(2, 22, 7, 0, 0), (G_GENO | 2), - A(ATTK(AT_BITE, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(30, 30, MS_SQEEK, MZ_SMALL), 0, 0, - M1_FLY | M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, - M2_WANDER | M2_HOSTILE, M3_INFRAVISIBLE, 3, CLR_RED), - MON("raven", S_BAT, LVL(4, 20, 6, 0, 0), (G_GENO | 2), - A(ATTK(AT_BITE, AD_PHYS, 1, 6), ATTK(AT_CLAW, AD_BLND, 1, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(40, 20, MS_SQAWK, MZ_SMALL), 0, 0, - M1_FLY | M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, - M2_WANDER | M2_HOSTILE, M3_INFRAVISIBLE, 6, CLR_BLACK), - MON("vampire bat", S_BAT, LVL(5, 20, 6, 0, 0), (G_GENO | 2), - A(ATTK(AT_BITE, AD_PHYS, 1, 6), ATTK(AT_BITE, AD_DRST, 0, 0), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(30, 20, MS_SQEEK, MZ_SMALL), MR_SLEEP | MR_POISON, 0, - M1_FLY | M1_ANIMAL | M1_NOHANDS | M1_POIS | M1_REGEN | M1_OMNIVORE, - M2_HOSTILE, M3_INFRAVISIBLE, 7, CLR_BLACK), - /* - * Centaurs - */ - MON("plains centaur", S_CENTAUR, LVL(4, 18, 4, 0, 0), (G_GENO | 1), - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), ATTK(AT_KICK, AD_PHYS, 1, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(2500, 500, MS_HUMANOID, MZ_LARGE), 0, 0, - M1_HUMANOID | M1_OMNIVORE, M2_STRONG | M2_GREEDY | M2_COLLECT, - M3_INFRAVISIBLE, 6, CLR_BROWN), - MON("forest centaur", S_CENTAUR, LVL(5, 18, 3, 10, -1), (G_GENO | 1), - A(ATTK(AT_WEAP, AD_PHYS, 1, 8), ATTK(AT_KICK, AD_PHYS, 1, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(2550, 600, MS_HUMANOID, MZ_LARGE), 0, 0, - M1_HUMANOID | M1_OMNIVORE, M2_STRONG | M2_GREEDY | M2_COLLECT, - M3_INFRAVISIBLE, 8, CLR_GREEN), - MON("mountain centaur", S_CENTAUR, LVL(6, 20, 2, 10, -3), (G_GENO | 1), - A(ATTK(AT_WEAP, AD_PHYS, 1, 10), ATTK(AT_KICK, AD_PHYS, 1, 6), - ATTK(AT_KICK, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(2550, 500, MS_HUMANOID, MZ_LARGE), 0, 0, - M1_HUMANOID | M1_OMNIVORE, M2_STRONG | M2_GREEDY | M2_COLLECT, - M3_INFRAVISIBLE, 9, CLR_CYAN), - /* - * Dragons - */ - /* The order of the dragons is VERY IMPORTANT. Quite a few - * pieces of code depend on gray being first and yellow being last. - * The code also depends on the *order* being the same as that for - * dragon scale mail and dragon scales in objects.c. Baby dragons - * cannot confer intrinsics, to avoid polyself/egg abuse. + * Array terminator, added to the end of the entries in monsters.h. * - * As reptiles, dragons are cold-blooded and thus aren't seen - * with infravision. Red dragons are the exception. - */ - MON("baby gray dragon", S_DRAGON, LVL(12, 9, 2, 10, 0), G_GENO, - A(ATTK(AT_BITE, AD_PHYS, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(1500, 500, MS_ROAR, MZ_HUGE), 0, 0, - M1_FLY | M1_THICK_HIDE | M1_NOHANDS | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG | M2_GREEDY | M2_JEWELS, 0, 13, CLR_GRAY), - MON("baby silver dragon", S_DRAGON, LVL(12, 9, 2, 10, 0), G_GENO, - A(ATTK(AT_BITE, AD_PHYS, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(1500, 500, MS_ROAR, MZ_HUGE), 0, 0, - M1_FLY | M1_THICK_HIDE | M1_NOHANDS | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG | M2_GREEDY | M2_JEWELS, 0, 13, DRAGON_SILVER), -#if 0 /* DEFERRED */ - MON("baby shimmering dragon", S_DRAGON, - LVL(12, 9, 2, 10, 0), G_GENO, - A(ATTK(AT_BITE, AD_PHYS, 2, 6), - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1500, 500, MS_ROAR, MZ_HUGE), 0, 0, - M1_FLY | M1_THICK_HIDE | M1_NOHANDS | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG | M2_GREEDY | M2_JEWELS, 0, 13, CLR_CYAN), -#endif - MON("baby red dragon", S_DRAGON, LVL(12, 9, 2, 10, 0), G_GENO, - A(ATTK(AT_BITE, AD_PHYS, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(1500, 500, MS_ROAR, MZ_HUGE), MR_FIRE, 0, - M1_FLY | M1_THICK_HIDE | M1_NOHANDS | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG | M2_GREEDY | M2_JEWELS, M3_INFRAVISIBLE, - 13, CLR_RED), - MON("baby white dragon", S_DRAGON, LVL(12, 9, 2, 10, 0), G_GENO, - A(ATTK(AT_BITE, AD_PHYS, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(1500, 500, MS_ROAR, MZ_HUGE), MR_COLD, 0, - M1_FLY | M1_THICK_HIDE | M1_NOHANDS | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG | M2_GREEDY | M2_JEWELS, 0, 13, CLR_WHITE), - MON("baby orange dragon", S_DRAGON, LVL(12, 9, 2, 10, 0), G_GENO, - A(ATTK(AT_BITE, AD_PHYS, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(1500, 500, MS_ROAR, MZ_HUGE), MR_SLEEP, 0, - M1_FLY | M1_THICK_HIDE | M1_NOHANDS | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG | M2_GREEDY | M2_JEWELS, 0, 13, CLR_ORANGE), - MON("baby black dragon", S_DRAGON, LVL(12, 9, 2, 10, 0), G_GENO, - A(ATTK(AT_BITE, AD_PHYS, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(1500, 500, MS_ROAR, MZ_HUGE), MR_DISINT, 0, - M1_FLY | M1_THICK_HIDE | M1_NOHANDS | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG | M2_GREEDY | M2_JEWELS, 0, 13, CLR_BLACK), - MON("baby blue dragon", S_DRAGON, LVL(12, 9, 2, 10, 0), G_GENO, - A(ATTK(AT_BITE, AD_PHYS, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(1500, 500, MS_ROAR, MZ_HUGE), MR_ELEC, 0, - M1_FLY | M1_THICK_HIDE | M1_NOHANDS | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG | M2_GREEDY | M2_JEWELS, 0, 13, CLR_BLUE), - MON("baby green dragon", S_DRAGON, LVL(12, 9, 2, 10, 0), G_GENO, - A(ATTK(AT_BITE, AD_PHYS, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(1500, 500, MS_ROAR, MZ_HUGE), MR_POISON, 0, - M1_FLY | M1_THICK_HIDE | M1_NOHANDS | M1_CARNIVORE | M1_POIS, - M2_HOSTILE | M2_STRONG | M2_GREEDY | M2_JEWELS, 0, 13, CLR_GREEN), - MON("baby yellow dragon", S_DRAGON, LVL(12, 9, 2, 10, 0), G_GENO, - A(ATTK(AT_BITE, AD_PHYS, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(1500, 500, MS_ROAR, MZ_HUGE), MR_ACID | MR_STONE, 0, - M1_FLY | M1_THICK_HIDE | M1_NOHANDS | M1_CARNIVORE | M1_ACID, - M2_HOSTILE | M2_STRONG | M2_GREEDY | M2_JEWELS, 0, 13, CLR_YELLOW), - MON("gray dragon", S_DRAGON, LVL(15, 9, -1, 20, 4), (G_GENO | 1), - A(ATTK(AT_BREA, AD_MAGM, 4, 6), ATTK(AT_BITE, AD_PHYS, 3, 8), - ATTK(AT_CLAW, AD_PHYS, 1, 4), ATTK(AT_CLAW, AD_PHYS, 1, 4), NO_ATTK, - NO_ATTK), - SIZ(WT_DRAGON, 1500, MS_ROAR, MZ_GIGANTIC), 0, 0, - M1_FLY | M1_THICK_HIDE | M1_NOHANDS | M1_SEE_INVIS | M1_OVIPAROUS - | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG | M2_NASTY | M2_GREEDY | M2_JEWELS | M2_MAGIC, - 0, 20, CLR_GRAY), - MON("silver dragon", S_DRAGON, LVL(15, 9, -1, 20, 4), (G_GENO | 1), - A(ATTK(AT_BREA, AD_COLD, 4, 6), ATTK(AT_BITE, AD_PHYS, 3, 8), - ATTK(AT_CLAW, AD_PHYS, 1, 4), ATTK(AT_CLAW, AD_PHYS, 1, 4), NO_ATTK, - NO_ATTK), - SIZ(WT_DRAGON, 1500, MS_ROAR, MZ_GIGANTIC), MR_COLD, 0, - M1_FLY | M1_THICK_HIDE | M1_NOHANDS | M1_SEE_INVIS | M1_OVIPAROUS - | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG | M2_NASTY | M2_GREEDY | M2_JEWELS | M2_MAGIC, - 0, 20, DRAGON_SILVER), -#if 0 /* DEFERRED */ - MON("shimmering dragon", S_DRAGON, - LVL(15, 9, -1, 20, 4), (G_GENO | 1), - A(ATTK(AT_BREA, AD_MAGM, 4, 6), ATTK(AT_BITE, AD_PHYS, 3, 8), - ATTK(AT_CLAW, AD_PHYS, 1, 4), ATTK(AT_CLAW, AD_PHYS, 1, 4), - NO_ATTK, NO_ATTK), - SIZ(WT_DRAGON, 1500, MS_ROAR, MZ_GIGANTIC), 0, 0, - M1_FLY | M1_THICK_HIDE | M1_NOHANDS | M1_SEE_INVIS | M1_OVIPAROUS - | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG | M2_NASTY | M2_GREEDY | M2_JEWELS | M2_MAGIC, - 0, 20, CLR_CYAN), -#endif - MON("red dragon", S_DRAGON, LVL(15, 9, -1, 20, -4), (G_GENO | 1), - A(ATTK(AT_BREA, AD_FIRE, 6, 6), ATTK(AT_BITE, AD_PHYS, 3, 8), - ATTK(AT_CLAW, AD_PHYS, 1, 4), ATTK(AT_CLAW, AD_PHYS, 1, 4), NO_ATTK, - NO_ATTK), - SIZ(WT_DRAGON, 1500, MS_ROAR, MZ_GIGANTIC), MR_FIRE, MR_FIRE, - M1_FLY | M1_THICK_HIDE | M1_NOHANDS | M1_SEE_INVIS | M1_OVIPAROUS - | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG | M2_NASTY | M2_GREEDY | M2_JEWELS | M2_MAGIC, - M3_INFRAVISIBLE, 20, CLR_RED), - MON("white dragon", S_DRAGON, LVL(15, 9, -1, 20, -5), (G_GENO | 1), - A(ATTK(AT_BREA, AD_COLD, 4, 6), ATTK(AT_BITE, AD_PHYS, 3, 8), - ATTK(AT_CLAW, AD_PHYS, 1, 4), ATTK(AT_CLAW, AD_PHYS, 1, 4), NO_ATTK, - NO_ATTK), - SIZ(WT_DRAGON, 1500, MS_ROAR, MZ_GIGANTIC), MR_COLD, MR_COLD, - M1_FLY | M1_THICK_HIDE | M1_NOHANDS | M1_SEE_INVIS | M1_OVIPAROUS - | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG | M2_NASTY | M2_GREEDY | M2_JEWELS | M2_MAGIC, - 0, 20, CLR_WHITE), - MON("orange dragon", S_DRAGON, LVL(15, 9, -1, 20, 5), (G_GENO | 1), - A(ATTK(AT_BREA, AD_SLEE, 4, 25), ATTK(AT_BITE, AD_PHYS, 3, 8), - ATTK(AT_CLAW, AD_PHYS, 1, 4), ATTK(AT_CLAW, AD_PHYS, 1, 4), NO_ATTK, - NO_ATTK), - SIZ(WT_DRAGON, 1500, MS_ROAR, MZ_GIGANTIC), MR_SLEEP, MR_SLEEP, - M1_FLY | M1_THICK_HIDE | M1_NOHANDS | M1_SEE_INVIS | M1_OVIPAROUS - | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG | M2_NASTY | M2_GREEDY | M2_JEWELS | M2_MAGIC, - 0, 20, CLR_ORANGE), - /* disintegration breath is actually all or nothing, not 1d255 */ - MON("black dragon", S_DRAGON, LVL(15, 9, -1, 20, -6), (G_GENO | 1), - A(ATTK(AT_BREA, AD_DISN, 1, 255), ATTK(AT_BITE, AD_PHYS, 3, 8), - ATTK(AT_CLAW, AD_PHYS, 1, 4), ATTK(AT_CLAW, AD_PHYS, 1, 4), NO_ATTK, - NO_ATTK), - SIZ(WT_DRAGON, 1500, MS_ROAR, MZ_GIGANTIC), MR_DISINT, MR_DISINT, - M1_FLY | M1_THICK_HIDE | M1_NOHANDS | M1_SEE_INVIS | M1_OVIPAROUS - | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG | M2_NASTY | M2_GREEDY | M2_JEWELS | M2_MAGIC, - 0, 20, CLR_BLACK), - MON("blue dragon", S_DRAGON, LVL(15, 9, -1, 20, -7), (G_GENO | 1), - A(ATTK(AT_BREA, AD_ELEC, 4, 6), ATTK(AT_BITE, AD_PHYS, 3, 8), - ATTK(AT_CLAW, AD_PHYS, 1, 4), ATTK(AT_CLAW, AD_PHYS, 1, 4), NO_ATTK, - NO_ATTK), - SIZ(WT_DRAGON, 1500, MS_ROAR, MZ_GIGANTIC), MR_ELEC, MR_ELEC, - M1_FLY | M1_THICK_HIDE | M1_NOHANDS | M1_SEE_INVIS | M1_OVIPAROUS - | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG | M2_NASTY | M2_GREEDY | M2_JEWELS | M2_MAGIC, - 0, 20, CLR_BLUE), - MON("green dragon", S_DRAGON, LVL(15, 9, -1, 20, 6), (G_GENO | 1), - A(ATTK(AT_BREA, AD_DRST, 4, 6), ATTK(AT_BITE, AD_PHYS, 3, 8), - ATTK(AT_CLAW, AD_PHYS, 1, 4), ATTK(AT_CLAW, AD_PHYS, 1, 4), NO_ATTK, - NO_ATTK), - SIZ(WT_DRAGON, 1500, MS_ROAR, MZ_GIGANTIC), MR_POISON, MR_POISON, - M1_FLY | M1_THICK_HIDE | M1_NOHANDS | M1_SEE_INVIS | M1_OVIPAROUS - | M1_CARNIVORE | M1_POIS, - M2_HOSTILE | M2_STRONG | M2_NASTY | M2_GREEDY | M2_JEWELS | M2_MAGIC, - 0, 20, CLR_GREEN), - MON("yellow dragon", S_DRAGON, LVL(15, 9, -1, 20, 7), (G_GENO | 1), - A(ATTK(AT_BREA, AD_ACID, 4, 6), ATTK(AT_BITE, AD_PHYS, 3, 8), - ATTK(AT_CLAW, AD_PHYS, 1, 4), ATTK(AT_CLAW, AD_PHYS, 1, 4), NO_ATTK, - NO_ATTK), - SIZ(WT_DRAGON, 1500, MS_ROAR, MZ_GIGANTIC), MR_ACID | MR_STONE, - MR_STONE, M1_FLY | M1_THICK_HIDE | M1_NOHANDS | M1_SEE_INVIS - | M1_OVIPAROUS | M1_CARNIVORE | M1_ACID, - M2_HOSTILE | M2_STRONG | M2_NASTY | M2_GREEDY | M2_JEWELS | M2_MAGIC, - 0, 20, CLR_YELLOW), - /* - * Elementals - */ - MON("stalker", S_ELEMENTAL, LVL(8, 12, 3, 0, 0), (G_GENO | 3), - A(ATTK(AT_CLAW, AD_PHYS, 4, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(900, 400, MS_SILENT, MZ_LARGE), 0, 0, - M1_ANIMAL | M1_FLY | M1_SEE_INVIS, - M2_WANDER | M2_STALK | M2_HOSTILE | M2_STRONG, M3_INFRAVISION, - 9, CLR_WHITE), - MON("air elemental", S_ELEMENTAL, LVL(8, 36, 2, 30, 0), (G_NOCORPSE | 1), - A(ATTK(AT_ENGL, AD_PHYS, 1, 10), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(0, 0, MS_SILENT, MZ_HUGE), MR_POISON | MR_STONE, 0, - M1_NOEYES | M1_NOLIMBS | M1_NOHEAD | M1_MINDLESS | M1_BREATHLESS - | M1_UNSOLID | M1_FLY, - M2_STRONG | M2_NEUTER, 0, 10, CLR_CYAN), - MON("fire elemental", S_ELEMENTAL, LVL(8, 12, 2, 30, 0), (G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_FIRE, 3, 6), ATTK(AT_NONE, AD_FIRE, 0, 4), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(0, 0, MS_SILENT, MZ_HUGE), MR_FIRE | MR_POISON | MR_STONE, 0, - M1_NOEYES | M1_NOLIMBS | M1_NOHEAD | M1_MINDLESS | M1_BREATHLESS - | M1_UNSOLID | M1_FLY | M1_NOTAKE, - M2_STRONG | M2_NEUTER, M3_INFRAVISIBLE, 10, CLR_YELLOW), - MON("earth elemental", S_ELEMENTAL, LVL(8, 6, 2, 30, 0), (G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 4, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(2500, 0, MS_SILENT, MZ_HUGE), - MR_FIRE | MR_COLD | MR_POISON | MR_STONE, 0, - M1_NOEYES | M1_NOLIMBS | M1_NOHEAD | M1_MINDLESS | M1_BREATHLESS - | M1_WALLWALK | M1_THICK_HIDE, - M2_STRONG | M2_NEUTER, 0, 10, CLR_BROWN), - MON("water elemental", S_ELEMENTAL, LVL(8, 6, 2, 30, 0), (G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 5, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(2500, 0, MS_SILENT, MZ_HUGE), MR_POISON | MR_STONE, 0, - M1_NOEYES | M1_NOLIMBS | M1_NOHEAD | M1_MINDLESS | M1_BREATHLESS - | M1_UNSOLID | M1_AMPHIBIOUS | M1_SWIM, - M2_STRONG | M2_NEUTER, 0, 10, CLR_BLUE), - /* - * Fungi - */ - MON("lichen", S_FUNGUS, LVL(0, 1, 9, 0, 0), (G_GENO | 4), - A(ATTK(AT_TUCH, AD_STCK, 0, 0), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(20, 200, MS_SILENT, MZ_SMALL), 0, 0, - M1_BREATHLESS | M1_NOEYES | M1_NOLIMBS | M1_NOHEAD | M1_MINDLESS - | M1_NOTAKE, - M2_HOSTILE | M2_NEUTER, 0, 1, CLR_BRIGHT_GREEN), - MON("brown mold", S_FUNGUS, LVL(1, 0, 9, 0, 0), (G_GENO | 1), - A(ATTK(AT_NONE, AD_COLD, 0, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(50, 30, MS_SILENT, MZ_SMALL), MR_COLD | MR_POISON, - MR_COLD | MR_POISON, M1_BREATHLESS | M1_NOEYES | M1_NOLIMBS - | M1_NOHEAD | M1_MINDLESS | M1_NOTAKE, - M2_HOSTILE | M2_NEUTER, 0, 2, CLR_BROWN), - MON("yellow mold", S_FUNGUS, LVL(1, 0, 9, 0, 0), (G_GENO | 2), - A(ATTK(AT_NONE, AD_STUN, 0, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(50, 30, MS_SILENT, MZ_SMALL), MR_POISON, MR_POISON, - M1_BREATHLESS | M1_NOEYES | M1_NOLIMBS | M1_NOHEAD | M1_MINDLESS - | M1_POIS | M1_NOTAKE, - M2_HOSTILE | M2_NEUTER, 0, 2, CLR_YELLOW), - MON("green mold", S_FUNGUS, LVL(1, 0, 9, 0, 0), (G_GENO | 1), - A(ATTK(AT_NONE, AD_ACID, 0, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(50, 30, MS_SILENT, MZ_SMALL), MR_ACID | MR_STONE, MR_STONE, - M1_BREATHLESS | M1_NOEYES | M1_NOLIMBS | M1_NOHEAD | M1_MINDLESS - | M1_ACID | M1_NOTAKE, - M2_HOSTILE | M2_NEUTER, 0, 2, CLR_GREEN), - MON("red mold", S_FUNGUS, LVL(1, 0, 9, 0, 0), (G_GENO | 1), - A(ATTK(AT_NONE, AD_FIRE, 0, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(50, 30, MS_SILENT, MZ_SMALL), MR_FIRE | MR_POISON, - MR_FIRE | MR_POISON, M1_BREATHLESS | M1_NOEYES | M1_NOLIMBS - | M1_NOHEAD | M1_MINDLESS | M1_NOTAKE, - M2_HOSTILE | M2_NEUTER, M3_INFRAVISIBLE, 2, CLR_RED), - MON("shrieker", S_FUNGUS, LVL(3, 1, 7, 0, 0), (G_GENO | 1), + * mons[NUMMONS] used to be all zero except "" instead of Null for + * the name field. Then the index field was added and the terminator + * uses NON_PM for that. Now, a few monster flags also get set. + */ +#undef MON +#define MON(nam, sym, lvl, gen, atk, siz, mr1, mr2, \ + flg1, flg2, flg3, d, col, bn) \ + { \ + nam, NON_PM, \ + sym, lvl, gen, atk, siz, mr1, mr2, flg1, flg2, flg3, d, col \ + } + MON(NAM(""), 0, + LVL(0, 0, 0, 0, 0), G_NOGEN | G_NOCORPSE, A(NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(100, 100, MS_SHRIEK, MZ_SMALL), MR_POISON, MR_POISON, - M1_BREATHLESS | M1_NOEYES | M1_NOLIMBS | M1_NOHEAD | M1_MINDLESS - | M1_NOTAKE, - M2_HOSTILE | M2_NEUTER, 0, 2, CLR_MAGENTA), - MON("violet fungus", S_FUNGUS, LVL(3, 1, 7, 0, 0), (G_GENO | 2), - A(ATTK(AT_TUCH, AD_PHYS, 1, 4), ATTK(AT_TUCH, AD_STCK, 0, 0), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(100, 100, MS_SILENT, MZ_SMALL), MR_POISON, MR_POISON, - M1_BREATHLESS | M1_NOEYES | M1_NOLIMBS | M1_NOHEAD | M1_MINDLESS - | M1_NOTAKE, - M2_HOSTILE | M2_NEUTER, 0, 5, CLR_MAGENTA), - /* - * Gnomes - */ - MON("gnome", S_GNOME, LVL(1, 6, 10, 4, 0), (G_GENO | G_SGROUP | 1), - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(650, 100, MS_ORC, MZ_SMALL), 0, 0, M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_GNOME | M2_COLLECT, M3_INFRAVISIBLE | M3_INFRAVISION, - 3, CLR_BROWN), - MON("gnome lord", S_GNOME, LVL(3, 8, 10, 4, 0), (G_GENO | 2), - A(ATTK(AT_WEAP, AD_PHYS, 1, 8), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(700, 120, MS_ORC, MZ_SMALL), 0, 0, M1_HUMANOID | M1_OMNIVORE, - M2_GNOME | M2_LORD | M2_MALE | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 4, CLR_BLUE), - MON("gnomish wizard", S_GNOME, LVL(3, 10, 4, 10, 0), (G_GENO | 1), - A(ATTK(AT_MAGC, AD_SPEL, 0, 0), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(700, 120, MS_ORC, MZ_SMALL), 0, 0, M1_HUMANOID | M1_OMNIVORE, - M2_GNOME | M2_MAGIC, M3_INFRAVISIBLE | M3_INFRAVISION, 5, HI_ZAP), - MON("gnome king", S_GNOME, LVL(5, 10, 10, 20, 0), (G_GENO | 1), - A(ATTK(AT_WEAP, AD_PHYS, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(750, 150, MS_ORC, MZ_SMALL), 0, 0, M1_HUMANOID | M1_OMNIVORE, - M2_GNOME | M2_PRINCE | M2_MALE | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 6, HI_LORD), -#ifdef SPLITMON_1 + SIZ(0, 0, 0, 0), 0, 0, + 0L, M2_NOPOLY, 0, + 0, 0, 0), }; -#endif -#endif /* !SPLITMON_2 */ - -/* horrible kludge alert: - * This is a compiler-specific kludge to allow the compilation of monst.o in - * two pieces, by defining first SPLITMON_1 and then SPLITMON_2. The - * resulting assembler files (monst1.s and monst2.s) are then run through - * sed to change local symbols, concatenated together, and assembled to - * produce monst.o. THIS ONLY WORKS WITH THE ATARI GCC, and should only - * be done if you don't have enough memory to compile monst.o the "normal" - * way. --ERS - */ -#ifndef SPLITMON_1 -#ifdef SPLITMON_2 -struct permonst _mons2[] = { -#endif - /* - * giant Humanoids - */ - MON("giant", S_GIANT, LVL(6, 6, 0, 0, 2), (G_GENO | G_NOGEN | 1), - A(ATTK(AT_WEAP, AD_PHYS, 2, 10), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(2250, 750, MS_BOAST, MZ_HUGE), 0, 0, M1_HUMANOID | M1_CARNIVORE, - M2_GIANT | M2_STRONG | M2_ROCKTHROW | M2_NASTY | M2_COLLECT - | M2_JEWELS, - M3_INFRAVISIBLE | M3_INFRAVISION, 8, CLR_RED), - MON("stone giant", S_GIANT, LVL(6, 6, 0, 0, 2), (G_GENO | G_SGROUP | 1), - A(ATTK(AT_WEAP, AD_PHYS, 2, 10), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(2250, 750, MS_BOAST, MZ_HUGE), 0, 0, M1_HUMANOID | M1_CARNIVORE, - M2_GIANT | M2_STRONG | M2_ROCKTHROW | M2_NASTY | M2_COLLECT - | M2_JEWELS, - M3_INFRAVISIBLE | M3_INFRAVISION, 8, CLR_GRAY), - MON("hill giant", S_GIANT, LVL(8, 10, 6, 0, -2), (G_GENO | G_SGROUP | 1), - A(ATTK(AT_WEAP, AD_PHYS, 2, 8), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(2200, 700, MS_BOAST, MZ_HUGE), 0, 0, M1_HUMANOID | M1_CARNIVORE, - M2_GIANT | M2_STRONG | M2_ROCKTHROW | M2_NASTY | M2_COLLECT - | M2_JEWELS, - M3_INFRAVISIBLE | M3_INFRAVISION, 10, CLR_CYAN), - MON("fire giant", S_GIANT, LVL(9, 12, 4, 5, 2), (G_GENO | G_SGROUP | 1), - A(ATTK(AT_WEAP, AD_PHYS, 2, 10), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(2250, 750, MS_BOAST, MZ_HUGE), MR_FIRE, MR_FIRE, - M1_HUMANOID | M1_CARNIVORE, M2_GIANT | M2_STRONG | M2_ROCKTHROW - | M2_NASTY | M2_COLLECT | M2_JEWELS, - M3_INFRAVISIBLE | M3_INFRAVISION, 11, CLR_YELLOW), - MON("frost giant", S_GIANT, LVL(10, 12, 3, 10, -3), - (G_NOHELL | G_GENO | G_SGROUP | 1), - A(ATTK(AT_WEAP, AD_PHYS, 2, 12), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(2250, 750, MS_BOAST, MZ_HUGE), MR_COLD, MR_COLD, - M1_HUMANOID | M1_CARNIVORE, M2_GIANT | M2_STRONG | M2_ROCKTHROW - | M2_NASTY | M2_COLLECT | M2_JEWELS, - M3_INFRAVISIBLE | M3_INFRAVISION, 13, CLR_WHITE), - MON("ettin", S_GIANT, LVL(10, 12, 3, 0, 0), (G_GENO | 1), - A(ATTK(AT_WEAP, AD_PHYS, 2, 8), ATTK(AT_WEAP, AD_PHYS, 3, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1700, 500, MS_GRUNT, MZ_HUGE), 0, 0, - M1_ANIMAL | M1_HUMANOID | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG | M2_NASTY | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 13, CLR_BROWN), - MON("storm giant", S_GIANT, LVL(16, 12, 3, 10, -3), - (G_GENO | G_SGROUP | 1), A(ATTK(AT_WEAP, AD_PHYS, 2, 12), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(2250, 750, MS_BOAST, MZ_HUGE), MR_ELEC, MR_ELEC, - M1_HUMANOID | M1_CARNIVORE, M2_GIANT | M2_STRONG | M2_ROCKTHROW - | M2_NASTY | M2_COLLECT | M2_JEWELS, - M3_INFRAVISIBLE | M3_INFRAVISION, 19, CLR_BLUE), - MON("titan", S_GIANT, LVL(16, 18, -3, 70, 9), (1), - A(ATTK(AT_WEAP, AD_PHYS, 2, 8), ATTK(AT_MAGC, AD_SPEL, 0, 0), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(2300, 900, MS_SPELL, MZ_HUGE), 0, 0, - M1_FLY | M1_HUMANOID | M1_OMNIVORE, - M2_STRONG | M2_ROCKTHROW | M2_NASTY | M2_COLLECT | M2_MAGIC, - M3_INFRAVISIBLE | M3_INFRAVISION, 20, CLR_MAGENTA), - MON("minotaur", S_GIANT, LVL(15, 15, 6, 0, 0), (G_GENO | G_NOGEN), - A(ATTK(AT_CLAW, AD_PHYS, 3, 10), ATTK(AT_CLAW, AD_PHYS, 3, 10), - ATTK(AT_BUTT, AD_PHYS, 2, 8), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1500, 700, MS_SILENT, MZ_LARGE), 0, 0, - M1_ANIMAL | M1_HUMANOID | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG | M2_NASTY, M3_INFRAVISIBLE | M3_INFRAVISION, - 17, CLR_BROWN), - /* 'I' is a visual marker for all invisible monsters and must be unused */ - /* - * Jabberwock - */ - /* the illustration from _Through_the_Looking_Glass_ - depicts hands as well as wings */ - MON("jabberwock", S_JABBERWOCK, LVL(15, 12, -2, 50, 0), (G_GENO | 1), - A(ATTK(AT_BITE, AD_PHYS, 2, 10), ATTK(AT_BITE, AD_PHYS, 2, 10), - ATTK(AT_CLAW, AD_PHYS, 2, 10), ATTK(AT_CLAW, AD_PHYS, 2, 10), - NO_ATTK, NO_ATTK), - SIZ(1300, 600, MS_BURBLE, MZ_LARGE), 0, 0, - M1_ANIMAL | M1_FLY | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG | M2_NASTY | M2_COLLECT, M3_INFRAVISIBLE, - 18, CLR_ORANGE), -#if 0 /* DEFERRED */ - MON("vorpal jabberwock", S_JABBERWOCK, - LVL(20, 12, -2, 50, 0), (G_GENO | 1), - A(ATTK(AT_BITE, AD_PHYS, 3, 10), ATTK(AT_BITE, AD_PHYS, 3, 10), - ATTK(AT_CLAW, AD_PHYS, 3, 10), ATTK(AT_CLAW, AD_PHYS, 3, 10), - NO_ATTK, NO_ATTK), - SIZ(1300, 600, MS_BURBLE, MZ_LARGE), 0, 0, - M1_ANIMAL | M1_FLY | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG | M2_NASTY | M2_COLLECT, M3_INFRAVISIBLE, - 25, HI_LORD), -#endif - /* - * Kops - */ - MON("Keystone Kop", S_KOP, LVL(1, 6, 10, 10, 9), - (G_GENO | G_LGROUP | G_NOGEN), - A(ATTK(AT_WEAP, AD_PHYS, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 200, MS_ARREST, MZ_HUMAN), 0, 0, M1_HUMANOID, - M2_HUMAN | M2_WANDER | M2_HOSTILE | M2_MALE | M2_COLLECT, - M3_INFRAVISIBLE, 3, CLR_BLUE), - MON("Kop Sergeant", S_KOP, LVL(2, 8, 10, 10, 10), - (G_GENO | G_SGROUP | G_NOGEN), - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 200, MS_ARREST, MZ_HUMAN), 0, 0, M1_HUMANOID, - M2_HUMAN | M2_WANDER | M2_HOSTILE | M2_STRONG | M2_MALE | M2_COLLECT, - M3_INFRAVISIBLE, 4, CLR_BLUE), - MON("Kop Lieutenant", S_KOP, LVL(3, 10, 10, 20, 11), (G_GENO | G_NOGEN), - A(ATTK(AT_WEAP, AD_PHYS, 1, 8), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 200, MS_ARREST, MZ_HUMAN), 0, 0, M1_HUMANOID, - M2_HUMAN | M2_WANDER | M2_HOSTILE | M2_STRONG | M2_MALE | M2_COLLECT, - M3_INFRAVISIBLE, 5, CLR_CYAN), - MON("Kop Kaptain", S_KOP, LVL(4, 12, 10, 20, 12), (G_GENO | G_NOGEN), - A(ATTK(AT_WEAP, AD_PHYS, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 200, MS_ARREST, MZ_HUMAN), 0, 0, M1_HUMANOID, - M2_HUMAN | M2_WANDER | M2_HOSTILE | M2_STRONG | M2_MALE | M2_COLLECT, - M3_INFRAVISIBLE, 6, HI_LORD), - /* - * Liches - */ - MON("lich", S_LICH, LVL(11, 6, 0, 30, -9), (G_GENO | G_NOCORPSE | 1), - A(ATTK(AT_TUCH, AD_COLD, 1, 10), ATTK(AT_MAGC, AD_SPEL, 0, 0), - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1200, 100, MS_MUMBLE, MZ_HUMAN), MR_COLD | MR_SLEEP | MR_POISON, - MR_COLD, M1_BREATHLESS | M1_HUMANOID | M1_POIS | M1_REGEN, - M2_UNDEAD | M2_HOSTILE | M2_MAGIC, M3_INFRAVISION, 14, CLR_BROWN), - MON("demilich", S_LICH, LVL(14, 9, -2, 60, -12), - (G_GENO | G_NOCORPSE | 1), - A(ATTK(AT_TUCH, AD_COLD, 3, 4), ATTK(AT_MAGC, AD_SPEL, 0, 0), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1200, 100, MS_MUMBLE, MZ_HUMAN), MR_COLD | MR_SLEEP | MR_POISON, - MR_COLD, M1_BREATHLESS | M1_HUMANOID | M1_POIS | M1_REGEN, - M2_UNDEAD | M2_HOSTILE | M2_MAGIC, M3_INFRAVISION, 18, CLR_RED), - MON("master lich", S_LICH, LVL(17, 9, -4, 90, -15), - (G_HELL | G_GENO | G_NOCORPSE | 1), - A(ATTK(AT_TUCH, AD_COLD, 3, 6), ATTK(AT_MAGC, AD_SPEL, 0, 0), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1200, 100, MS_MUMBLE, MZ_HUMAN), - MR_FIRE | MR_COLD | MR_SLEEP | MR_POISON, MR_FIRE | MR_COLD, - M1_BREATHLESS | M1_HUMANOID | M1_POIS | M1_REGEN, - M2_UNDEAD | M2_HOSTILE | M2_MAGIC, M3_WANTSBOOK | M3_INFRAVISION, - 21, HI_LORD), - MON("arch-lich", S_LICH, LVL(25, 9, -6, 90, -15), - (G_HELL | G_GENO | G_NOCORPSE | 1), - A(ATTK(AT_TUCH, AD_COLD, 5, 6), ATTK(AT_MAGC, AD_SPEL, 0, 0), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1200, 100, MS_MUMBLE, MZ_HUMAN), - MR_FIRE | MR_COLD | MR_SLEEP | MR_ELEC | MR_POISON, MR_FIRE | MR_COLD, - M1_BREATHLESS | M1_HUMANOID | M1_POIS | M1_REGEN, - M2_UNDEAD | M2_HOSTILE | M2_MAGIC, M3_WANTSBOOK | M3_INFRAVISION, - 29, HI_LORD), - /* - * Mummies - */ - MON("kobold mummy", S_MUMMY, LVL(3, 8, 6, 20, -2), - (G_GENO | G_NOCORPSE | 1), A(ATTK(AT_CLAW, AD_PHYS, 1, 4), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(400, 50, MS_SILENT, MZ_SMALL), MR_COLD | MR_SLEEP | MR_POISON, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID | M1_POIS, - M2_UNDEAD | M2_HOSTILE, M3_INFRAVISION, 4, CLR_BROWN), - MON("gnome mummy", S_MUMMY, LVL(4, 10, 6, 20, -3), - (G_GENO | G_NOCORPSE | 1), A(ATTK(AT_CLAW, AD_PHYS, 1, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(650, 50, MS_SILENT, MZ_SMALL), MR_COLD | MR_SLEEP | MR_POISON, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID | M1_POIS, - M2_UNDEAD | M2_HOSTILE | M2_GNOME, M3_INFRAVISION, 5, CLR_RED), - MON("orc mummy", S_MUMMY, LVL(5, 10, 5, 20, -4), - (G_GENO | G_NOCORPSE | 1), A(ATTK(AT_CLAW, AD_PHYS, 1, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(850, 75, MS_SILENT, MZ_HUMAN), MR_COLD | MR_SLEEP | MR_POISON, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID | M1_POIS, - M2_UNDEAD | M2_HOSTILE | M2_ORC | M2_GREEDY | M2_JEWELS, - M3_INFRAVISION, 6, CLR_GRAY), - MON("dwarf mummy", S_MUMMY, LVL(5, 10, 5, 20, -4), - (G_GENO | G_NOCORPSE | 1), A(ATTK(AT_CLAW, AD_PHYS, 1, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(900, 150, MS_SILENT, MZ_HUMAN), MR_COLD | MR_SLEEP | MR_POISON, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID | M1_POIS, - M2_UNDEAD | M2_HOSTILE | M2_DWARF | M2_GREEDY | M2_JEWELS, - M3_INFRAVISION, 6, CLR_RED), - MON("elf mummy", S_MUMMY, LVL(6, 12, 4, 30, -5), - (G_GENO | G_NOCORPSE | 1), A(ATTK(AT_CLAW, AD_PHYS, 2, 4), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_ELF, 175, MS_SILENT, MZ_HUMAN), MR_COLD | MR_SLEEP | MR_POISON, - 0, M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID | M1_POIS, - M2_UNDEAD | M2_HOSTILE | M2_ELF, M3_INFRAVISION, 7, CLR_GREEN), - MON("human mummy", S_MUMMY, LVL(6, 12, 4, 30, -5), - (G_GENO | G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 2, 4), ATTK(AT_CLAW, AD_PHYS, 2, 4), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 200, MS_SILENT, MZ_HUMAN), - MR_COLD | MR_SLEEP | MR_POISON, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID | M1_POIS, - M2_UNDEAD | M2_HOSTILE, M3_INFRAVISION, 7, CLR_GRAY), - MON("ettin mummy", S_MUMMY, LVL(7, 12, 4, 30, -6), - (G_GENO | G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 2, 6), ATTK(AT_CLAW, AD_PHYS, 2, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1700, 250, MS_SILENT, MZ_HUGE), MR_COLD | MR_SLEEP | MR_POISON, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID | M1_POIS, - M2_UNDEAD | M2_HOSTILE | M2_STRONG, M3_INFRAVISION, 8, CLR_BLUE), - MON("giant mummy", S_MUMMY, LVL(8, 14, 3, 30, -7), - (G_GENO | G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 3, 4), ATTK(AT_CLAW, AD_PHYS, 3, 4), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(2050, 375, MS_SILENT, MZ_HUGE), MR_COLD | MR_SLEEP | MR_POISON, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID | M1_POIS, - M2_UNDEAD | M2_HOSTILE | M2_GIANT | M2_STRONG | M2_JEWELS, - M3_INFRAVISION, 10, CLR_CYAN), - /* - * Nagas - */ - MON("red naga hatchling", S_NAGA, LVL(3, 10, 6, 0, 0), G_GENO, - A(ATTK(AT_BITE, AD_PHYS, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(500, 100, MS_MUMBLE, MZ_LARGE), MR_FIRE | MR_POISON, - MR_FIRE | MR_POISON, - M1_NOLIMBS | M1_SLITHY | M1_THICK_HIDE | M1_NOTAKE | M1_OMNIVORE, - M2_STRONG, M3_INFRAVISIBLE, 4, CLR_RED), - MON("black naga hatchling", S_NAGA, LVL(3, 10, 6, 0, 0), G_GENO, - A(ATTK(AT_BITE, AD_PHYS, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(500, 100, MS_MUMBLE, MZ_LARGE), MR_POISON | MR_ACID | MR_STONE, - MR_POISON | MR_STONE, M1_NOLIMBS | M1_SLITHY | M1_THICK_HIDE | M1_ACID - | M1_NOTAKE | M1_CARNIVORE, - M2_STRONG, 0, 4, CLR_BLACK), - MON("golden naga hatchling", S_NAGA, LVL(3, 10, 6, 0, 0), G_GENO, - A(ATTK(AT_BITE, AD_PHYS, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(500, 100, MS_MUMBLE, MZ_LARGE), MR_POISON, MR_POISON, - M1_NOLIMBS | M1_SLITHY | M1_THICK_HIDE | M1_NOTAKE | M1_OMNIVORE, - M2_STRONG, 0, 4, HI_GOLD), - MON("guardian naga hatchling", S_NAGA, LVL(3, 10, 6, 0, 0), G_GENO, - A(ATTK(AT_BITE, AD_PHYS, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(500, 100, MS_MUMBLE, MZ_LARGE), MR_POISON, MR_POISON, - M1_NOLIMBS | M1_SLITHY | M1_THICK_HIDE | M1_NOTAKE | M1_OMNIVORE, - M2_STRONG, 0, 4, CLR_GREEN), - MON("red naga", S_NAGA, LVL(6, 12, 4, 0, -4), (G_GENO | 1), - A(ATTK(AT_BITE, AD_PHYS, 2, 4), ATTK(AT_BREA, AD_FIRE, 2, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(2600, 400, MS_MUMBLE, MZ_HUGE), MR_FIRE | MR_POISON, - MR_FIRE | MR_POISON, M1_NOLIMBS | M1_SLITHY | M1_THICK_HIDE - | M1_OVIPAROUS | M1_NOTAKE | M1_OMNIVORE, - M2_STRONG, M3_INFRAVISIBLE, 8, CLR_RED), - MON("black naga", S_NAGA, LVL(8, 14, 2, 10, 4), (G_GENO | 1), - A(ATTK(AT_BITE, AD_PHYS, 2, 6), ATTK(AT_SPIT, AD_ACID, 0, 0), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(2600, 400, MS_MUMBLE, MZ_HUGE), MR_POISON | MR_ACID | MR_STONE, - MR_POISON | MR_STONE, - M1_NOLIMBS | M1_SLITHY | M1_THICK_HIDE | M1_OVIPAROUS | M1_ACID - | M1_NOTAKE | M1_CARNIVORE, - M2_STRONG, 0, 10, CLR_BLACK), - MON("golden naga", S_NAGA, LVL(10, 14, 2, 70, 5), (G_GENO | 1), - A(ATTK(AT_BITE, AD_PHYS, 2, 6), ATTK(AT_MAGC, AD_SPEL, 4, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(2600, 400, MS_MUMBLE, MZ_HUGE), MR_POISON, MR_POISON, - M1_NOLIMBS | M1_SLITHY | M1_THICK_HIDE | M1_OVIPAROUS | M1_NOTAKE - | M1_OMNIVORE, - M2_STRONG, 0, 13, HI_GOLD), - MON("guardian naga", S_NAGA, LVL(12, 16, 0, 50, 7), (G_GENO | 1), - A(ATTK(AT_BITE, AD_PLYS, 1, 6), ATTK(AT_SPIT, AD_DRST, 1, 6), - ATTK(AT_HUGS, AD_PHYS, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(2600, 400, MS_MUMBLE, MZ_HUGE), MR_POISON, MR_POISON, - M1_NOLIMBS | M1_SLITHY | M1_THICK_HIDE | M1_OVIPAROUS | M1_POIS - | M1_NOTAKE | M1_OMNIVORE, - M2_STRONG, 0, 16, CLR_GREEN), - /* - * Ogres - */ - MON("ogre", S_OGRE, LVL(5, 10, 5, 0, -3), (G_SGROUP | G_GENO | 1), - A(ATTK(AT_WEAP, AD_PHYS, 2, 5), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(1600, 500, MS_GRUNT, MZ_LARGE), 0, 0, M1_HUMANOID | M1_CARNIVORE, - M2_STRONG | M2_GREEDY | M2_JEWELS | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 7, CLR_BROWN), - MON("ogre lord", S_OGRE, LVL(7, 12, 3, 30, -5), (G_GENO | 2), - A(ATTK(AT_WEAP, AD_PHYS, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(1700, 700, MS_GRUNT, MZ_LARGE), 0, 0, M1_HUMANOID | M1_CARNIVORE, - M2_STRONG | M2_LORD | M2_MALE | M2_GREEDY | M2_JEWELS | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 9, CLR_RED), - MON("ogre king", S_OGRE, LVL(9, 14, 4, 60, -7), (G_GENO | 2), - A(ATTK(AT_WEAP, AD_PHYS, 3, 5), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(1700, 750, MS_GRUNT, MZ_LARGE), 0, 0, M1_HUMANOID | M1_CARNIVORE, - M2_STRONG | M2_PRINCE | M2_MALE | M2_GREEDY | M2_JEWELS | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 11, HI_LORD), - /* - * Puddings - * - * must be in the same order as the pudding globs in objects.c - */ - MON("gray ooze", S_PUDDING, LVL(3, 1, 8, 0, 0), (G_GENO | G_NOCORPSE | 2), - A(ATTK(AT_BITE, AD_RUST, 2, 8), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(500, 250, MS_SILENT, MZ_MEDIUM), - MR_FIRE | MR_COLD | MR_POISON | MR_ACID | MR_STONE, - MR_FIRE | MR_COLD | MR_POISON, - M1_BREATHLESS | M1_AMORPHOUS | M1_NOEYES | M1_NOLIMBS | M1_NOHEAD - | M1_MINDLESS | M1_OMNIVORE | M1_ACID, - M2_HOSTILE | M2_NEUTER, 0, 4, CLR_GRAY), - MON("brown pudding", S_PUDDING, LVL(5, 3, 8, 0, 0), - (G_GENO | G_NOCORPSE | 1), A(ATTK(AT_BITE, AD_DCAY, 0, 0), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(500, 250, MS_SILENT, MZ_MEDIUM), - MR_COLD | MR_ELEC | MR_POISON | MR_ACID | MR_STONE, - MR_COLD | MR_ELEC | MR_POISON, - M1_BREATHLESS | M1_AMORPHOUS | M1_NOEYES | M1_NOLIMBS | M1_NOHEAD - | M1_MINDLESS | M1_OMNIVORE | M1_ACID, - 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), - 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 - | M1_MINDLESS | M1_OMNIVORE | M1_ACID | M1_POIS, - M2_HOSTILE | M2_NEUTER, 0, 8, CLR_GREEN), - MON("black pudding", S_PUDDING, LVL(10, 6, 6, 0, 0), - (G_GENO | G_NOCORPSE | 1), - A(ATTK(AT_BITE, AD_CORR, 3, 8), ATTK(AT_NONE, AD_CORR, 0, 0), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(900, 250, MS_SILENT, MZ_LARGE), - MR_COLD | MR_ELEC | MR_POISON | MR_ACID | MR_STONE, - MR_COLD | MR_ELEC | MR_POISON, - M1_BREATHLESS | M1_AMORPHOUS | M1_NOEYES | M1_NOLIMBS | M1_NOHEAD - | M1_MINDLESS | M1_OMNIVORE | M1_ACID, - M2_HOSTILE | M2_NEUTER, 0, 12, CLR_BLACK), - /* - * Quantum mechanics - */ - MON("quantum mechanic", S_QUANTMECH, LVL(7, 12, 3, 10, 0), (G_GENO | 3), - A(ATTK(AT_CLAW, AD_TLPT, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 20, MS_HUMANOID, MZ_HUMAN), MR_POISON, 0, - M1_HUMANOID | M1_OMNIVORE | M1_POIS | M1_TPORT, M2_HOSTILE, - M3_INFRAVISIBLE, 9, CLR_CYAN), - /* - * Rust monster or disenchanter - */ - MON("rust monster", S_RUSTMONST, LVL(5, 18, 2, 0, 0), (G_GENO | 2), - A(ATTK(AT_TUCH, AD_RUST, 0, 0), ATTK(AT_TUCH, AD_RUST, 0, 0), - ATTK(AT_NONE, AD_RUST, 0, 0), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1000, 250, MS_SILENT, MZ_MEDIUM), 0, 0, - M1_SWIM | M1_ANIMAL | M1_NOHANDS | M1_METALLIVORE, M2_HOSTILE, - M3_INFRAVISIBLE, 8, CLR_BROWN), - MON("disenchanter", S_RUSTMONST, LVL(12, 12, -10, 0, -3), - (G_HELL | G_GENO | 2), - A(ATTK(AT_CLAW, AD_ENCH, 4, 4), ATTK(AT_NONE, AD_ENCH, 0, 0), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(750, 200, MS_GROWL, MZ_LARGE), 0, 0, M1_ANIMAL | M1_CARNIVORE, - M2_HOSTILE, M3_INFRAVISIBLE, 14, CLR_BLUE), - /* - * Snakes - */ - MON("garter snake", S_SNAKE, LVL(1, 8, 8, 0, 0), (G_LGROUP | G_GENO | 1), - A(ATTK(AT_BITE, AD_PHYS, 1, 2), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(50, 60, MS_HISS, MZ_TINY), 0, 0, - M1_SWIM | M1_CONCEAL | M1_NOLIMBS | M1_ANIMAL | M1_SLITHY - | M1_OVIPAROUS | M1_CARNIVORE | M1_NOTAKE, - 0, 0, 3, CLR_GREEN), - MON("snake", S_SNAKE, LVL(4, 15, 3, 0, 0), (G_GENO | 2), - A(ATTK(AT_BITE, AD_DRST, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(100, 80, MS_HISS, MZ_SMALL), MR_POISON, MR_POISON, - M1_SWIM | M1_CONCEAL | M1_NOLIMBS | M1_ANIMAL | M1_SLITHY | M1_POIS - | M1_OVIPAROUS | M1_CARNIVORE | M1_NOTAKE, - M2_HOSTILE, 0, 6, CLR_BROWN), - MON("water moccasin", S_SNAKE, LVL(4, 15, 3, 0, 0), - (G_GENO | G_NOGEN | G_LGROUP), - A(ATTK(AT_BITE, AD_DRST, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(150, 80, MS_HISS, MZ_SMALL), MR_POISON, MR_POISON, - M1_SWIM | M1_CONCEAL | M1_NOLIMBS | M1_ANIMAL | M1_SLITHY | M1_POIS - | M1_CARNIVORE | M1_OVIPAROUS | M1_NOTAKE, - M2_HOSTILE, 0, 7, CLR_RED), - MON("python", S_SNAKE, LVL(6, 3, 5, 0, 0), (G_GENO | 1), - A(ATTK(AT_BITE, AD_PHYS, 1, 4), ATTK(AT_TUCH, AD_PHYS, 0, 0), - ATTK(AT_HUGS, AD_WRAP, 1, 4), ATTK(AT_HUGS, AD_PHYS, 2, 4), NO_ATTK, - NO_ATTK), - SIZ(250, 100, MS_HISS, MZ_LARGE), 0, 0, - M1_SWIM | M1_NOLIMBS | M1_ANIMAL | M1_SLITHY | M1_CARNIVORE - | M1_OVIPAROUS | M1_NOTAKE, - M2_HOSTILE | M2_STRONG, M3_INFRAVISION, 8, CLR_MAGENTA), - MON("pit viper", S_SNAKE, LVL(6, 15, 2, 0, 0), (G_GENO | 1), - A(ATTK(AT_BITE, AD_DRST, 1, 4), ATTK(AT_BITE, AD_DRST, 1, 4), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(100, 60, MS_HISS, MZ_MEDIUM), MR_POISON, MR_POISON, - M1_SWIM | M1_CONCEAL | M1_NOLIMBS | M1_ANIMAL | M1_SLITHY | M1_POIS - | M1_CARNIVORE | M1_OVIPAROUS | M1_NOTAKE, - M2_HOSTILE, M3_INFRAVISION, 9, CLR_BLUE), - MON("cobra", S_SNAKE, LVL(6, 18, 2, 0, 0), (G_GENO | 1), - A(ATTK(AT_BITE, AD_DRST, 2, 4), ATTK(AT_SPIT, AD_BLND, 0, 0), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(250, 100, MS_HISS, MZ_MEDIUM), MR_POISON, MR_POISON, - M1_SWIM | M1_CONCEAL | M1_NOLIMBS | M1_ANIMAL | M1_SLITHY | M1_POIS - | M1_CARNIVORE | M1_OVIPAROUS | M1_NOTAKE, - M2_HOSTILE, 0, 10, CLR_BLUE), - /* - * Trolls - */ - MON("troll", S_TROLL, LVL(7, 12, 4, 0, -3), (G_GENO | 2), - A(ATTK(AT_WEAP, AD_PHYS, 4, 2), ATTK(AT_CLAW, AD_PHYS, 4, 2), - ATTK(AT_BITE, AD_PHYS, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(800, 350, MS_GRUNT, MZ_LARGE), 0, 0, - M1_HUMANOID | M1_REGEN | M1_CARNIVORE, - M2_STRONG | M2_STALK | M2_HOSTILE, M3_INFRAVISIBLE | M3_INFRAVISION, - 9, CLR_BROWN), - MON("ice troll", S_TROLL, LVL(9, 10, 2, 20, -3), (G_NOHELL | G_GENO | 1), - A(ATTK(AT_WEAP, AD_PHYS, 2, 6), ATTK(AT_CLAW, AD_COLD, 2, 6), - ATTK(AT_BITE, AD_PHYS, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1000, 300, MS_GRUNT, MZ_LARGE), MR_COLD, MR_COLD, - M1_HUMANOID | M1_REGEN | M1_CARNIVORE, - M2_STRONG | M2_STALK | M2_HOSTILE, M3_INFRAVISIBLE | M3_INFRAVISION, - 12, CLR_WHITE), - MON("rock troll", S_TROLL, LVL(9, 12, 0, 0, -3), (G_GENO | 1), - A(ATTK(AT_WEAP, AD_PHYS, 3, 6), ATTK(AT_CLAW, AD_PHYS, 2, 8), - ATTK(AT_BITE, AD_PHYS, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1200, 300, MS_GRUNT, MZ_LARGE), 0, 0, - M1_HUMANOID | M1_REGEN | M1_CARNIVORE, - M2_STRONG | M2_STALK | M2_HOSTILE | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 12, CLR_CYAN), - MON("water troll", S_TROLL, LVL(11, 14, 4, 40, -3), (G_NOGEN | G_GENO), - A(ATTK(AT_WEAP, AD_PHYS, 2, 8), ATTK(AT_CLAW, AD_PHYS, 2, 8), - ATTK(AT_BITE, AD_PHYS, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1200, 350, MS_GRUNT, MZ_LARGE), 0, 0, - M1_HUMANOID | M1_REGEN | M1_CARNIVORE | M1_SWIM, - M2_STRONG | M2_STALK | M2_HOSTILE, M3_INFRAVISIBLE | M3_INFRAVISION, - 13, CLR_BLUE), - MON("Olog-hai", S_TROLL, LVL(13, 12, -4, 0, -7), (G_GENO | 1), - A(ATTK(AT_WEAP, AD_PHYS, 3, 6), ATTK(AT_CLAW, AD_PHYS, 2, 8), - ATTK(AT_BITE, AD_PHYS, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1500, 400, MS_GRUNT, MZ_LARGE), 0, 0, - M1_HUMANOID | M1_REGEN | M1_CARNIVORE, - M2_STRONG | M2_STALK | M2_HOSTILE | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 16, HI_LORD), - /* - * Umber hulk - */ - MON("umber hulk", S_UMBER, LVL(9, 6, 2, 25, 0), (G_GENO | 2), - A(ATTK(AT_CLAW, AD_PHYS, 3, 4), ATTK(AT_CLAW, AD_PHYS, 3, 4), - ATTK(AT_BITE, AD_PHYS, 2, 5), ATTK(AT_GAZE, AD_CONF, 0, 0), NO_ATTK, - NO_ATTK), - SIZ(1200, 500, MS_SILENT, MZ_LARGE), 0, 0, M1_TUNNEL | M1_CARNIVORE, - M2_STRONG, M3_INFRAVISIBLE, 12, CLR_BROWN), - /* - * Vampires - */ - MON("vampire", S_VAMPIRE, LVL(10, 12, 1, 25, -8), - (G_GENO | G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 1, 6), ATTK(AT_BITE, AD_DRLI, 1, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_VAMPIRE, MZ_HUMAN), MR_SLEEP | MR_POISON, 0, - M1_FLY | M1_BREATHLESS | M1_HUMANOID | M1_POIS | M1_REGEN, - M2_UNDEAD | M2_STALK | M2_HOSTILE | M2_STRONG | M2_NASTY - | M2_SHAPESHIFTER, - M3_INFRAVISIBLE, 12, CLR_RED), - MON("vampire lord", S_VAMPIRE, LVL(12, 14, 0, 50, -9), - (G_GENO | G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 1, 8), ATTK(AT_BITE, AD_DRLI, 1, 8), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_VAMPIRE, MZ_HUMAN), MR_SLEEP | MR_POISON, 0, - M1_FLY | M1_BREATHLESS | M1_HUMANOID | M1_POIS | M1_REGEN, - M2_UNDEAD | M2_STALK | M2_HOSTILE | M2_STRONG | M2_NASTY | M2_LORD - | M2_MALE | M2_SHAPESHIFTER, - M3_INFRAVISIBLE, 14, CLR_BLUE), -#if 0 /* DEFERRED */ - MON("vampire mage", S_VAMPIRE, - LVL(20, 14, -4, 50, -9), (G_GENO | G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_DRLI, 2, 8), ATTK(AT_BITE, AD_DRLI, 1, 8), - ATTK(AT_MAGC, AD_SPEL, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_VAMPIRE, MZ_HUMAN), MR_SLEEP | MR_POISON, 0, - M1_FLY | M1_BREATHLESS | M1_HUMANOID | M1_POIS | M1_REGEN, - M2_UNDEAD | M2_STALK | M2_HOSTILE | M2_STRONG | M2_NASTY | M2_LORD - | M2_MALE | M2_MAGIC | M2_SHAPESHIFTER, - M3_INFRAVISIBLE, 26, HI_ZAP), -#endif - MON("Vlad the Impaler", S_VAMPIRE, LVL(28, 26, -6, 80, -10), - (G_NOGEN | G_NOCORPSE | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 2, 10), ATTK(AT_BITE, AD_DRLI, 1, 12), - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_VAMPIRE, MZ_HUMAN), MR_SLEEP | MR_POISON, 0, - M1_FLY | M1_BREATHLESS | M1_HUMANOID | M1_POIS | M1_REGEN, - M2_NOPOLY | M2_UNDEAD | M2_STALK | M2_HOSTILE | M2_PNAME | M2_STRONG - | M2_NASTY | M2_PRINCE | M2_MALE | M2_SHAPESHIFTER, - M3_WAITFORU | M3_WANTSCAND | M3_INFRAVISIBLE, 32, HI_LORD), - /* - * Wraiths - */ - MON("barrow wight", S_WRAITH, LVL(3, 12, 5, 5, -3), - (G_GENO | G_NOCORPSE | 1), - A(ATTK(AT_WEAP, AD_DRLI, 0, 0), ATTK(AT_MAGC, AD_SPEL, 0, 0), - ATTK(AT_CLAW, AD_PHYS, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1200, 0, MS_SPELL, MZ_HUMAN), MR_COLD | MR_SLEEP | MR_POISON, 0, - M1_BREATHLESS | M1_HUMANOID, - M2_UNDEAD | M2_STALK | M2_HOSTILE | M2_COLLECT, 0, 7, CLR_GRAY), - MON("wraith", S_WRAITH, LVL(6, 12, 4, 15, -6), (G_GENO | 2), - A(ATTK(AT_TUCH, AD_DRLI, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(0, 0, MS_SILENT, MZ_HUMAN), - MR_COLD | MR_SLEEP | MR_POISON | MR_STONE, 0, - M1_BREATHLESS | M1_FLY | M1_HUMANOID | M1_UNSOLID, - M2_UNDEAD | M2_STALK | M2_HOSTILE, 0, 8, CLR_BLACK), - MON("Nazgul", S_WRAITH, LVL(13, 12, 0, 25, -17), - (G_GENO | G_NOCORPSE | 1), - A(ATTK(AT_WEAP, AD_DRLI, 1, 4), ATTK(AT_BREA, AD_SLEE, 2, 25), - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 0, MS_SPELL, MZ_HUMAN), MR_COLD | MR_SLEEP | MR_POISON, - 0, M1_BREATHLESS | M1_HUMANOID, - M2_NOPOLY | M2_UNDEAD | M2_STALK | M2_STRONG | M2_HOSTILE | M2_MALE - | M2_COLLECT, - 0, 17, HI_LORD), - /* - * Xorn - */ - MON("xorn", S_XORN, LVL(8, 9, -2, 20, 0), (G_GENO | 1), - A(ATTK(AT_CLAW, AD_PHYS, 1, 3), ATTK(AT_CLAW, AD_PHYS, 1, 3), - ATTK(AT_CLAW, AD_PHYS, 1, 3), ATTK(AT_BITE, AD_PHYS, 4, 6), NO_ATTK, - NO_ATTK), - SIZ(1200, 700, MS_ROAR, MZ_MEDIUM), MR_FIRE | MR_COLD | MR_STONE, - MR_STONE, - M1_BREATHLESS | M1_WALLWALK | M1_THICK_HIDE | M1_METALLIVORE, - M2_HOSTILE | M2_STRONG, 0, 11, CLR_BROWN), - /* - * Apelike beasts - */ - /* tameable via banana; does not grow up into ape... - not flagged as domestic, so no guilt penalty for eating non-pet one */ - MON("monkey", S_YETI, LVL(2, 12, 6, 0, 0), (G_GENO | 1), - A(ATTK(AT_CLAW, AD_SITM, 0, 0), ATTK(AT_BITE, AD_PHYS, 1, 3), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(100, 50, MS_GROWL, MZ_SMALL), 0, 0, - M1_ANIMAL | M1_HUMANOID | M1_OMNIVORE, 0, M3_INFRAVISIBLE, 4, CLR_GRAY), - MON("ape", S_YETI, LVL(4, 12, 6, 0, 0), (G_GENO | G_SGROUP | 2), - A(ATTK(AT_CLAW, AD_PHYS, 1, 3), ATTK(AT_CLAW, AD_PHYS, 1, 3), - ATTK(AT_BITE, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1100, 500, MS_GROWL, MZ_LARGE), 0, 0, - M1_ANIMAL | M1_HUMANOID | M1_OMNIVORE, M2_STRONG, M3_INFRAVISIBLE, - 6, CLR_BROWN), - MON("owlbear", S_YETI, LVL(5, 12, 5, 0, 0), (G_GENO | 3), - A(ATTK(AT_CLAW, AD_PHYS, 1, 6), ATTK(AT_CLAW, AD_PHYS, 1, 6), - ATTK(AT_HUGS, AD_PHYS, 2, 8), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1700, 700, MS_ROAR, MZ_LARGE), 0, 0, - M1_ANIMAL | M1_HUMANOID | M1_CARNIVORE, - M2_HOSTILE | M2_STRONG | M2_NASTY, M3_INFRAVISIBLE, 7, CLR_BROWN), - MON("yeti", S_YETI, LVL(5, 15, 6, 0, 0), (G_GENO | 2), - A(ATTK(AT_CLAW, AD_PHYS, 1, 6), ATTK(AT_CLAW, AD_PHYS, 1, 6), - ATTK(AT_BITE, AD_PHYS, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1600, 700, MS_GROWL, MZ_LARGE), MR_COLD, MR_COLD, - M1_ANIMAL | M1_HUMANOID | M1_CARNIVORE, M2_HOSTILE | M2_STRONG, - M3_INFRAVISIBLE, 7, CLR_WHITE), - MON("carnivorous ape", S_YETI, LVL(6, 12, 6, 0, 0), (G_GENO | 1), - A(ATTK(AT_CLAW, AD_PHYS, 1, 4), ATTK(AT_CLAW, AD_PHYS, 1, 4), - ATTK(AT_HUGS, AD_PHYS, 1, 8), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1250, 550, MS_GROWL, MZ_LARGE), 0, 0, - M1_ANIMAL | M1_HUMANOID | M1_CARNIVORE, M2_HOSTILE | M2_STRONG, - M3_INFRAVISIBLE, 8, CLR_BLACK), - MON("sasquatch", S_YETI, LVL(7, 15, 6, 0, 2), (G_GENO | 1), - A(ATTK(AT_CLAW, AD_PHYS, 1, 6), ATTK(AT_CLAW, AD_PHYS, 1, 6), - ATTK(AT_KICK, AD_PHYS, 1, 8), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1550, 750, MS_GROWL, MZ_LARGE), 0, 0, - M1_ANIMAL | M1_HUMANOID | M1_SEE_INVIS | M1_OMNIVORE, M2_STRONG, - M3_INFRAVISIBLE, 9, CLR_GRAY), - /* - * Zombies - */ - MON("kobold zombie", S_ZOMBIE, LVL(0, 6, 10, 0, -2), - (G_GENO | G_NOCORPSE | 1), A(ATTK(AT_CLAW, AD_PHYS, 1, 4), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(400, 50, MS_SILENT, MZ_SMALL), MR_COLD | MR_SLEEP | MR_POISON, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID | M1_POIS, - M2_UNDEAD | M2_STALK | M2_HOSTILE, M3_INFRAVISION, 1, CLR_BROWN), - MON("gnome zombie", S_ZOMBIE, LVL(1, 6, 10, 0, -2), - (G_GENO | G_NOCORPSE | 1), A(ATTK(AT_CLAW, AD_PHYS, 1, 5), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(650, 50, MS_SILENT, MZ_SMALL), MR_COLD | MR_SLEEP | MR_POISON, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID | M1_POIS, - M2_UNDEAD | M2_STALK | M2_HOSTILE | M2_GNOME, M3_INFRAVISION, - 2, CLR_BROWN), - MON("orc zombie", S_ZOMBIE, LVL(2, 6, 9, 0, -3), - (G_GENO | G_SGROUP | G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(850, 75, MS_SILENT, MZ_HUMAN), MR_COLD | MR_SLEEP | MR_POISON, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID | M1_POIS, - M2_UNDEAD | M2_STALK | M2_HOSTILE | M2_ORC, M3_INFRAVISION, 3, - CLR_GRAY), - MON("dwarf zombie", S_ZOMBIE, LVL(2, 6, 9, 0, -3), - (G_GENO | G_SGROUP | G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(900, 150, MS_SILENT, MZ_HUMAN), MR_COLD | MR_SLEEP | MR_POISON, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID | M1_POIS, - M2_UNDEAD | M2_STALK | M2_HOSTILE | M2_DWARF, M3_INFRAVISION, - 3, CLR_RED), - MON("elf zombie", S_ZOMBIE, LVL(3, 6, 9, 0, -3), - (G_GENO | G_SGROUP | G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 1, 7), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_ELF, 175, MS_SILENT, MZ_HUMAN), MR_COLD | MR_SLEEP | MR_POISON, - 0, M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID, - M2_UNDEAD | M2_STALK | M2_HOSTILE | M2_ELF, M3_INFRAVISION, - 4, CLR_GREEN), - MON("human zombie", S_ZOMBIE, LVL(4, 6, 8, 0, -3), - (G_GENO | G_SGROUP | G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 1, 8), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 200, MS_SILENT, MZ_HUMAN), - MR_COLD | MR_SLEEP | MR_POISON, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID, - M2_UNDEAD | M2_STALK | M2_HOSTILE, M3_INFRAVISION, 5, HI_DOMESTIC), - MON("ettin zombie", S_ZOMBIE, LVL(6, 8, 6, 0, -4), - (G_GENO | G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 1, 10), ATTK(AT_CLAW, AD_PHYS, 1, 10), - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1700, 250, MS_SILENT, MZ_HUGE), MR_COLD | MR_SLEEP | MR_POISON, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID, - M2_UNDEAD | M2_STALK | M2_HOSTILE | M2_STRONG, M3_INFRAVISION, - 7, CLR_BLUE), - MON("ghoul", S_ZOMBIE, LVL(3, 6, 10, 0, -2), (G_GENO | G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PLYS, 1, 2), ATTK(AT_CLAW, AD_PHYS, 1, 3), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(400, 50, MS_SILENT, MZ_SMALL), MR_COLD | MR_SLEEP | MR_POISON, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID | M1_POIS | M1_OMNIVORE, - M2_UNDEAD | M2_WANDER | M2_HOSTILE, M3_INFRAVISION, 5, CLR_BLACK), - MON("giant zombie", S_ZOMBIE, LVL(8, 8, 6, 0, -4), - (G_GENO | G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 2, 8), ATTK(AT_CLAW, AD_PHYS, 2, 8), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(2050, 375, MS_SILENT, MZ_HUGE), MR_COLD | MR_SLEEP | MR_POISON, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID, - M2_UNDEAD | M2_STALK | M2_HOSTILE | M2_GIANT | M2_STRONG, - M3_INFRAVISION, 9, CLR_CYAN), - MON("skeleton", S_ZOMBIE, LVL(12, 8, 4, 0, 0), (G_NOCORPSE | G_NOGEN), - A(ATTK(AT_WEAP, AD_PHYS, 2, 6), ATTK(AT_TUCH, AD_SLOW, 1, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(300, 5, MS_BONES, MZ_HUMAN), - MR_COLD | MR_SLEEP | MR_POISON | MR_STONE, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID | M1_THICK_HIDE, - M2_UNDEAD | M2_WANDER | M2_HOSTILE | M2_STRONG | M2_COLLECT - | M2_NASTY, - M3_INFRAVISION, 14, CLR_WHITE), - /* - * golems - */ - MON("straw golem", S_GOLEM, LVL(3, 12, 10, 0, 0), (G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 1, 2), ATTK(AT_CLAW, AD_PHYS, 1, 2), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(400, 0, MS_SILENT, MZ_LARGE), MR_COLD | MR_SLEEP | MR_POISON, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID, M2_HOSTILE | M2_NEUTER, 0, - 4, CLR_YELLOW), - MON("paper golem", S_GOLEM, LVL(3, 12, 10, 0, 0), (G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 1, 3), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(400, 0, MS_SILENT, MZ_LARGE), MR_COLD | MR_SLEEP | MR_POISON, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID, M2_HOSTILE | M2_NEUTER, 0, - 4, HI_PAPER), - MON("rope golem", S_GOLEM, LVL(4, 9, 8, 0, 0), (G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 1, 4), ATTK(AT_CLAW, AD_PHYS, 1, 4), - ATTK(AT_HUGS, AD_PHYS, 6, 1), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(450, 0, MS_SILENT, MZ_LARGE), MR_SLEEP | MR_POISON, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID, M2_HOSTILE | M2_NEUTER, 0, - 6, CLR_BROWN), - MON("gold golem", S_GOLEM, LVL(5, 9, 6, 0, 0), (G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 2, 3), ATTK(AT_CLAW, AD_PHYS, 2, 3), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(450, 0, MS_SILENT, MZ_LARGE), MR_SLEEP | MR_POISON | MR_ACID, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID | M1_THICK_HIDE, - M2_HOSTILE | M2_NEUTER, 0, 6, HI_GOLD), - MON("leather golem", S_GOLEM, LVL(6, 6, 6, 0, 0), (G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 1, 6), ATTK(AT_CLAW, AD_PHYS, 1, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(800, 0, MS_SILENT, MZ_LARGE), MR_SLEEP | MR_POISON, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID, M2_HOSTILE | M2_NEUTER, 0, - 7, HI_LEATHER), - MON("wood golem", S_GOLEM, LVL(7, 3, 4, 0, 0), (G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 3, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(900, 0, MS_SILENT, MZ_LARGE), MR_COLD | MR_SLEEP | MR_POISON, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID | M1_THICK_HIDE, - M2_HOSTILE | M2_NEUTER, 0, 8, HI_WOOD), - MON("flesh golem", S_GOLEM, LVL(9, 8, 9, 30, 0), (1), - A(ATTK(AT_CLAW, AD_PHYS, 2, 8), ATTK(AT_CLAW, AD_PHYS, 2, 8), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1400, 600, MS_SILENT, MZ_LARGE), - MR_FIRE | MR_COLD | MR_ELEC | MR_SLEEP | MR_POISON, - MR_FIRE | MR_COLD | MR_ELEC | MR_SLEEP | MR_POISON, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID, M2_HOSTILE | M2_STRONG, 0, - 10, CLR_RED), - MON("clay golem", S_GOLEM, LVL(11, 7, 7, 40, 0), (G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 3, 10), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(1550, 0, MS_SILENT, MZ_LARGE), MR_SLEEP | MR_POISON, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID | M1_THICK_HIDE, - M2_HOSTILE | M2_STRONG, 0, 12, CLR_BROWN), - MON("stone golem", S_GOLEM, LVL(14, 6, 5, 50, 0), (G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 3, 8), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(1900, 0, MS_SILENT, MZ_LARGE), MR_SLEEP | MR_POISON | MR_STONE, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID | M1_THICK_HIDE, - M2_HOSTILE | M2_STRONG, 0, 15, CLR_GRAY), - MON("glass golem", S_GOLEM, LVL(16, 6, 1, 50, 0), (G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 2, 8), ATTK(AT_CLAW, AD_PHYS, 2, 8), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1800, 0, MS_SILENT, MZ_LARGE), MR_SLEEP | MR_POISON | MR_ACID, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID | M1_THICK_HIDE, - M2_HOSTILE | M2_STRONG, 0, 18, CLR_CYAN), - MON("iron golem", S_GOLEM, LVL(18, 6, 3, 60, 0), (G_NOCORPSE | 1), - A(ATTK(AT_WEAP, AD_PHYS, 4, 10), ATTK(AT_BREA, AD_DRST, 4, 6), - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(2000, 0, MS_SILENT, MZ_LARGE), - MR_FIRE | MR_COLD | MR_ELEC | MR_SLEEP | MR_POISON, 0, - M1_BREATHLESS | M1_MINDLESS | M1_HUMANOID | M1_THICK_HIDE | M1_POIS, - M2_HOSTILE | M2_STRONG | M2_COLLECT, 0, 22, HI_METAL), - /* - * humans, including elves and were-critters - */ - MON("human", S_HUMAN, LVL(0, 12, 10, 0, 0), G_NOGEN, /* for corpses */ - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_HUMANOID, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_STRONG | M2_COLLECT, M3_INFRAVISIBLE, - 2, HI_DOMESTIC), - MON("wererat", S_HUMAN, LVL(2, 12, 10, 10, -7), (1), - A(ATTK(AT_WEAP, AD_PHYS, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_WERE, MZ_HUMAN), MR_POISON, 0, - M1_HUMANOID | M1_POIS | M1_REGEN | M1_OMNIVORE, - M2_NOPOLY | M2_WERE | M2_HOSTILE | M2_HUMAN | M2_COLLECT, - M3_INFRAVISIBLE, 3, CLR_BROWN), - MON("werejackal", S_HUMAN, LVL(2, 12, 10, 10, -7), (1), - A(ATTK(AT_WEAP, AD_PHYS, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_WERE, MZ_HUMAN), MR_POISON, 0, - M1_HUMANOID | M1_POIS | M1_REGEN | M1_OMNIVORE, - M2_NOPOLY | M2_WERE | M2_HOSTILE | M2_HUMAN | M2_COLLECT, - M3_INFRAVISIBLE, 3, CLR_RED), - MON("werewolf", S_HUMAN, LVL(5, 12, 10, 20, -7), (1), - A(ATTK(AT_WEAP, AD_PHYS, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_WERE, MZ_HUMAN), MR_POISON, 0, - M1_HUMANOID | M1_POIS | M1_REGEN | M1_OMNIVORE, - M2_NOPOLY | M2_WERE | M2_HOSTILE | M2_HUMAN | M2_COLLECT, - M3_INFRAVISIBLE, 6, CLR_ORANGE), - MON("elf", S_HUMAN, LVL(10, 12, 10, 2, -3), G_NOGEN, /* for corpses */ - A(ATTK(AT_WEAP, AD_PHYS, 1, 8), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_ELF, 350, MS_HUMANOID, MZ_HUMAN), MR_SLEEP, MR_SLEEP, - M1_HUMANOID | M1_OMNIVORE | M1_SEE_INVIS, - M2_NOPOLY | M2_ELF | M2_STRONG | M2_COLLECT, - M3_INFRAVISION | M3_INFRAVISIBLE, 12, HI_DOMESTIC), - MON("Woodland-elf", S_HUMAN, LVL(4, 12, 10, 10, -5), - (G_GENO | G_SGROUP | 2), A(ATTK(AT_WEAP, AD_PHYS, 2, 4), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_ELF, 350, MS_HUMANOID, MZ_HUMAN), MR_SLEEP, MR_SLEEP, - M1_HUMANOID | M1_OMNIVORE | M1_SEE_INVIS, M2_ELF | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 6, CLR_GREEN), - MON("Green-elf", S_HUMAN, LVL(5, 12, 10, 10, -6), (G_GENO | G_SGROUP | 2), - A(ATTK(AT_WEAP, AD_PHYS, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_ELF, 350, MS_HUMANOID, MZ_HUMAN), MR_SLEEP, MR_SLEEP, - M1_HUMANOID | M1_OMNIVORE | M1_SEE_INVIS, M2_ELF | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 7, CLR_BRIGHT_GREEN), - MON("Grey-elf", S_HUMAN, LVL(6, 12, 10, 10, -7), (G_GENO | G_SGROUP | 2), - A(ATTK(AT_WEAP, AD_PHYS, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_ELF, 350, MS_HUMANOID, MZ_HUMAN), MR_SLEEP, MR_SLEEP, - M1_HUMANOID | M1_OMNIVORE | M1_SEE_INVIS, M2_ELF | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 8, CLR_GRAY), - MON("elf-lord", S_HUMAN, LVL(8, 12, 10, 20, -9), (G_GENO | G_SGROUP | 2), - A(ATTK(AT_WEAP, AD_PHYS, 2, 4), ATTK(AT_WEAP, AD_PHYS, 2, 4), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_ELF, 350, MS_HUMANOID, MZ_HUMAN), MR_SLEEP, MR_SLEEP, - M1_HUMANOID | M1_OMNIVORE | M1_SEE_INVIS, - M2_ELF | M2_STRONG | M2_LORD | M2_MALE | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 11, CLR_BRIGHT_BLUE), - MON("Elvenking", S_HUMAN, LVL(9, 12, 10, 25, -10), (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(WT_ELF, 350, MS_HUMANOID, MZ_HUMAN), MR_SLEEP, MR_SLEEP, - M1_HUMANOID | M1_OMNIVORE | M1_SEE_INVIS, - M2_ELF | M2_STRONG | M2_PRINCE | M2_MALE | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 11, HI_LORD), - MON("doppelganger", S_HUMAN, LVL(9, 12, 5, 20, 0), (G_GENO | 1), - A(ATTK(AT_WEAP, AD_PHYS, 1, 12), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_IMITATE, MZ_HUMAN), MR_SLEEP, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_HOSTILE | M2_STRONG | M2_COLLECT - | M2_SHAPESHIFTER, - M3_INFRAVISIBLE, 11, HI_DOMESTIC), - MON("shopkeeper", S_HUMAN, LVL(12, 18, 0, 50, 0), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 4, 4), ATTK(AT_WEAP, AD_PHYS, 4, 4), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_SELL, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, M2_NOPOLY | M2_HUMAN | M2_PEACEFUL - | M2_STRONG | M2_COLLECT | M2_MAGIC, - M3_INFRAVISIBLE, 15, HI_DOMESTIC), - MON("guard", S_HUMAN, LVL(12, 12, 10, 40, 10), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 4, 10), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_GUARD, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_MERC | M2_PEACEFUL | M2_STRONG | M2_COLLECT, - M3_INFRAVISIBLE, 14, CLR_BLUE), - MON("prisoner", S_HUMAN, LVL(12, 12, 10, 0, 0), - G_NOGEN, /* for special levels */ - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_DJINNI, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_PEACEFUL | M2_STRONG | M2_COLLECT, - M3_INFRAVISIBLE | M3_CLOSE, 14, HI_DOMESTIC), - MON("Oracle", S_HUMAN, LVL(12, 0, 0, 50, 0), (G_NOGEN | G_UNIQ), - A(ATTK(AT_NONE, AD_MAGM, 0, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_ORACLE, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_PEACEFUL | M2_FEMALE, M3_INFRAVISIBLE, - 13, HI_ZAP), - /* aligned priests always have the epri extension attached; - individual instantiations should always have either ispriest - or isminion set */ - MON("aligned priest", S_HUMAN, LVL(12, 12, 10, 50, 0), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 4, 10), ATTK(AT_KICK, AD_PHYS, 1, 4), - ATTK(AT_MAGC, AD_CLRC, 0, 0), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_PRIEST, MZ_HUMAN), MR_ELEC, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_LORD | M2_PEACEFUL | M2_COLLECT, - M3_INFRAVISIBLE, 15, CLR_WHITE), - /* high priests always have epri and always have ispriest set */ - MON("high priest", S_HUMAN, LVL(25, 15, 7, 70, 0), (G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 4, 10), ATTK(AT_KICK, AD_PHYS, 2, 8), - ATTK(AT_MAGC, AD_CLRC, 2, 8), ATTK(AT_MAGC, AD_CLRC, 2, 8), NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_PRIEST, MZ_HUMAN), - MR_FIRE | MR_ELEC | MR_SLEEP | MR_POISON, 0, - M1_HUMANOID | M1_SEE_INVIS | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_MINION | M2_PRINCE | M2_NASTY | M2_COLLECT - | M2_MAGIC, - M3_INFRAVISIBLE, 30, CLR_WHITE), - MON("soldier", S_HUMAN, LVL(6, 10, 10, 0, -2), (G_SGROUP | G_GENO | 1), - A(ATTK(AT_WEAP, AD_PHYS, 1, 8), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_SOLDIER, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, M2_NOPOLY | M2_HUMAN | M2_MERC | M2_STALK - | M2_HOSTILE | M2_STRONG | M2_COLLECT, - M3_INFRAVISIBLE, 8, CLR_GRAY), - MON("sergeant", S_HUMAN, LVL(8, 10, 10, 5, -3), (G_SGROUP | G_GENO | 1), - A(ATTK(AT_WEAP, AD_PHYS, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_SOLDIER, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, M2_NOPOLY | M2_HUMAN | M2_MERC | M2_STALK - | M2_HOSTILE | M2_STRONG | M2_COLLECT, - M3_INFRAVISIBLE, 10, CLR_RED), - MON("nurse", S_HUMAN, LVL(11, 6, 0, 0, 0), (G_GENO | 3), - A(ATTK(AT_CLAW, AD_HEAL, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_NURSE, MZ_HUMAN), MR_POISON, MR_POISON, - M1_HUMANOID | M1_OMNIVORE, M2_NOPOLY | M2_HUMAN | M2_HOSTILE, - M3_INFRAVISIBLE, 13, HI_DOMESTIC), - MON("lieutenant", S_HUMAN, LVL(10, 10, 10, 15, -4), (G_GENO | 1), - A(ATTK(AT_WEAP, AD_PHYS, 3, 4), ATTK(AT_WEAP, AD_PHYS, 3, 4), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_SOLDIER, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, M2_NOPOLY | M2_HUMAN | M2_MERC | M2_STALK - | M2_HOSTILE | M2_STRONG | M2_COLLECT, - M3_INFRAVISIBLE, 12, CLR_GREEN), - MON("captain", S_HUMAN, LVL(12, 10, 10, 15, -5), (G_GENO | 1), - A(ATTK(AT_WEAP, AD_PHYS, 4, 4), ATTK(AT_WEAP, AD_PHYS, 4, 4), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_SOLDIER, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, M2_NOPOLY | M2_HUMAN | M2_MERC | M2_STALK - | M2_HOSTILE | M2_STRONG | M2_COLLECT, - M3_INFRAVISIBLE, 14, CLR_BLUE), - /* Keep these separate - some of the mkroom code assumes that - * all the soldiers are contiguous. - */ - MON("watchman", S_HUMAN, LVL(6, 10, 10, 0, -2), - (G_SGROUP | G_NOGEN | G_GENO | 1), - A(ATTK(AT_WEAP, AD_PHYS, 1, 8), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_SOLDIER, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, M2_NOPOLY | M2_HUMAN | M2_MERC | M2_STALK - | M2_PEACEFUL | M2_STRONG | M2_COLLECT, - M3_INFRAVISIBLE, 8, CLR_GRAY), - MON("watch captain", S_HUMAN, LVL(10, 10, 10, 15, -4), - (G_NOGEN | G_GENO | 1), - A(ATTK(AT_WEAP, AD_PHYS, 3, 4), ATTK(AT_WEAP, AD_PHYS, 3, 4), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_SOLDIER, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, M2_NOPOLY | M2_HUMAN | M2_MERC | M2_STALK - | M2_PEACEFUL | M2_STRONG | M2_COLLECT, - M3_INFRAVISIBLE, 12, CLR_GREEN), - /* Unique humans not tied to quests. - */ - MON("Medusa", S_HUMAN, LVL(20, 12, 2, 50, -15), (G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 2, 4), ATTK(AT_CLAW, AD_PHYS, 1, 8), - ATTK(AT_GAZE, AD_STON, 0, 0), ATTK(AT_BITE, AD_DRST, 1, 6), NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_HISS, MZ_LARGE), MR_POISON | MR_STONE, - MR_POISON | MR_STONE, M1_FLY | M1_SWIM | M1_AMPHIBIOUS | M1_HUMANOID - | M1_POIS | M1_OMNIVORE, - M2_NOPOLY | M2_HOSTILE | M2_STRONG | M2_PNAME | M2_FEMALE, - M3_WAITFORU | M3_INFRAVISIBLE, 25, CLR_BRIGHT_GREEN), - MON("Wizard of Yendor", S_HUMAN, LVL(30, 12, -8, 100, A_NONE), - (G_NOGEN | G_UNIQ), - A(ATTK(AT_CLAW, AD_SAMU, 2, 12), ATTK(AT_MAGC, AD_SPEL, 0, 0), - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_CUSS, MZ_HUMAN), MR_FIRE | MR_POISON, - MR_FIRE | MR_POISON, - M1_FLY | M1_BREATHLESS | M1_HUMANOID | M1_REGEN | M1_SEE_INVIS - | M1_TPORT | M1_TPORT_CNTRL | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_HOSTILE | M2_STRONG | M2_NASTY | M2_PRINCE - | M2_MALE | M2_MAGIC, - M3_COVETOUS | M3_WAITFORU | M3_INFRAVISIBLE, 34, HI_LORD), - MON("Croesus", S_HUMAN, LVL(20, 15, 0, 40, 15), (G_UNIQ | G_NOGEN), - A(ATTK(AT_WEAP, AD_PHYS, 4, 10), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_GUARD, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_SEE_INVIS | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_STALK | M2_HOSTILE | M2_STRONG | M2_NASTY - | M2_PNAME | M2_PRINCE | M2_MALE | M2_GREEDY | M2_JEWELS - | M2_COLLECT | M2_MAGIC, - M3_INFRAVISIBLE, 22, HI_LORD), -#ifdef CHARON - MON("Charon", S_HUMAN, LVL(76, 18, -5, 120, 0), - (G_HELL | G_NOCORPSE | G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 1, 8), ATTK(AT_TUCH, AD_PLYS, 1, 8), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_FERRY, MZ_HUMAN), - MR_FIRE | MR_COLD | MR_POISON | MR_STONE, 0, - M1_BREATHLESS | M1_SEE_INVIS | M1_HUMANOID, - M2_NOPOLY | M2_HUMAN | M2_PEACEFUL | M2_PNAME | M2_MALE | M2_GREEDY - | M2_COLLECT, - M3_INFRAVISIBLE, 38, CLR_WHITE), -#endif - /* - * ghosts - */ - MON("ghost", S_GHOST, LVL(10, 3, -5, 50, -5), (G_NOCORPSE | G_NOGEN), - A(ATTK(AT_TUCH, AD_PHYS, 1, 1), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 0, MS_SILENT, MZ_HUMAN), - MR_COLD | MR_DISINT | MR_SLEEP | MR_POISON | MR_STONE, 0, - M1_FLY | M1_BREATHLESS | M1_WALLWALK | M1_HUMANOID | M1_UNSOLID, - M2_NOPOLY | M2_UNDEAD | M2_STALK | M2_HOSTILE, M3_INFRAVISION, - 12, CLR_GRAY), - MON("shade", S_GHOST, LVL(12, 10, 10, 0, 0), (G_NOCORPSE | G_NOGEN), - A(ATTK(AT_TUCH, AD_PLYS, 2, 6), ATTK(AT_TUCH, AD_SLOW, 1, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 0, MS_WAIL, MZ_HUMAN), - MR_COLD | MR_DISINT | MR_SLEEP | MR_POISON | MR_STONE, 0, - M1_FLY | M1_BREATHLESS | M1_WALLWALK | M1_HUMANOID | M1_UNSOLID - | M1_SEE_INVIS, - M2_NOPOLY | M2_UNDEAD | M2_WANDER | M2_STALK | M2_HOSTILE | M2_NASTY, - M3_INFRAVISION, 14, CLR_BLACK), - /* - * (major) demons - */ - MON("water demon", S_DEMON, LVL(8, 12, -4, 30, -7), - (G_NOCORPSE | G_NOGEN), - A(ATTK(AT_WEAP, AD_PHYS, 1, 3), ATTK(AT_CLAW, AD_PHYS, 1, 3), - ATTK(AT_BITE, AD_PHYS, 1, 3), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_DJINNI, MZ_HUMAN), MR_FIRE | MR_POISON, 0, - M1_HUMANOID | M1_POIS | M1_SWIM, - M2_NOPOLY | M2_DEMON | M2_STALK | M2_HOSTILE | M2_NASTY | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 11, CLR_BLUE), - /* standard demons & devils - */ -#define SEDUCTION_ATTACKS_YES \ - A(ATTK(AT_BITE, AD_SSEX, 0, 0), ATTK(AT_CLAW, AD_PHYS, 1, 3), \ - ATTK(AT_CLAW, AD_PHYS, 1, 3), NO_ATTK, NO_ATTK, NO_ATTK) -#define SEDUCTION_ATTACKS_NO \ - A(ATTK(AT_CLAW, AD_PHYS, 1, 3), ATTK(AT_CLAW, AD_PHYS, 1, 3), \ - ATTK(AT_BITE, AD_DRLI, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK) - MON("succubus", S_DEMON, LVL(6, 12, 0, 70, -9), (G_NOCORPSE | 1), - SEDUCTION_ATTACKS_YES, SIZ(WT_HUMAN, 400, MS_SEDUCE, MZ_HUMAN), - MR_FIRE | MR_POISON, 0, M1_HUMANOID | M1_FLY | M1_POIS, - M2_DEMON | M2_STALK | M2_HOSTILE | M2_NASTY | M2_FEMALE, - M3_INFRAVISIBLE | M3_INFRAVISION, 8, CLR_GRAY), - MON("horned devil", S_DEMON, LVL(6, 9, -5, 50, 11), - (G_HELL | G_NOCORPSE | 2), - A(ATTK(AT_WEAP, AD_PHYS, 1, 4), ATTK(AT_CLAW, AD_PHYS, 1, 4), - ATTK(AT_BITE, AD_PHYS, 2, 3), ATTK(AT_STNG, AD_PHYS, 1, 3), NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_SILENT, MZ_HUMAN), MR_FIRE | MR_POISON, 0, - M1_POIS | M1_THICK_HIDE, M2_DEMON | M2_STALK | M2_HOSTILE | M2_NASTY, - M3_INFRAVISIBLE | M3_INFRAVISION, 9, CLR_BROWN), - MON("incubus", S_DEMON, LVL(6, 12, 0, 70, -9), (G_NOCORPSE | 1), - SEDUCTION_ATTACKS_YES, SIZ(WT_HUMAN, 400, MS_SEDUCE, MZ_HUMAN), - MR_FIRE | MR_POISON, 0, M1_HUMANOID | M1_FLY | M1_POIS, - M2_DEMON | M2_STALK | M2_HOSTILE | M2_NASTY | M2_MALE, - M3_INFRAVISIBLE | M3_INFRAVISION, 8, CLR_GRAY), - /* Used by AD&D for a type of demon, originally one of the Furies - and spelled this way */ - MON("erinys", S_DEMON, LVL(7, 12, 2, 30, 10), - (G_HELL | G_NOCORPSE | G_SGROUP | 2), - A(ATTK(AT_WEAP, AD_DRST, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_SILENT, MZ_HUMAN), MR_FIRE | MR_POISON, 0, - M1_HUMANOID | M1_POIS, - M2_NOPOLY | M2_DEMON | M2_STALK | M2_HOSTILE | M2_STRONG | M2_NASTY - | M2_FEMALE | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 10, CLR_RED), - MON("barbed devil", S_DEMON, LVL(8, 12, 0, 35, 8), - (G_HELL | G_NOCORPSE | G_SGROUP | 2), - A(ATTK(AT_CLAW, AD_PHYS, 2, 4), ATTK(AT_CLAW, AD_PHYS, 2, 4), - ATTK(AT_STNG, AD_PHYS, 3, 4), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_SILENT, MZ_HUMAN), MR_FIRE | MR_POISON, 0, - M1_POIS | M1_THICK_HIDE, M2_DEMON | M2_STALK | M2_HOSTILE | M2_NASTY, - M3_INFRAVISIBLE | M3_INFRAVISION, 10, CLR_RED), - MON("marilith", S_DEMON, LVL(7, 12, -6, 80, -12), - (G_HELL | G_NOCORPSE | 1), - A(ATTK(AT_WEAP, AD_PHYS, 2, 4), ATTK(AT_WEAP, AD_PHYS, 2, 4), - ATTK(AT_CLAW, AD_PHYS, 2, 4), ATTK(AT_CLAW, AD_PHYS, 2, 4), - ATTK(AT_CLAW, AD_PHYS, 2, 4), ATTK(AT_CLAW, AD_PHYS, 2, 4)), - SIZ(WT_HUMAN, 400, MS_CUSS, MZ_LARGE), MR_FIRE | MR_POISON, 0, - M1_HUMANOID | M1_SLITHY | M1_SEE_INVIS | M1_POIS, - M2_DEMON | M2_STALK | M2_HOSTILE | M2_NASTY | M2_FEMALE | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 11, CLR_RED), - MON("vrock", S_DEMON, LVL(8, 12, 0, 50, -9), - (G_HELL | G_NOCORPSE | G_SGROUP | 2), - A(ATTK(AT_CLAW, AD_PHYS, 1, 4), ATTK(AT_CLAW, AD_PHYS, 1, 4), - ATTK(AT_CLAW, AD_PHYS, 1, 8), ATTK(AT_CLAW, AD_PHYS, 1, 8), - ATTK(AT_BITE, AD_PHYS, 1, 6), NO_ATTK), - SIZ(WT_HUMAN, 400, MS_SILENT, MZ_LARGE), MR_FIRE | MR_POISON, 0, - M1_POIS, M2_DEMON | M2_STALK | M2_HOSTILE | M2_NASTY, - M3_INFRAVISIBLE | M3_INFRAVISION, 11, CLR_RED), - MON("hezrou", S_DEMON, LVL(9, 6, -2, 55, -10), - (G_HELL | G_NOCORPSE | G_SGROUP | 2), - A(ATTK(AT_CLAW, AD_PHYS, 1, 3), ATTK(AT_CLAW, AD_PHYS, 1, 3), - ATTK(AT_BITE, AD_PHYS, 4, 4), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_SILENT, MZ_LARGE), MR_FIRE | MR_POISON, 0, - M1_HUMANOID | M1_POIS, M2_DEMON | M2_STALK | M2_HOSTILE | M2_NASTY, - M3_INFRAVISIBLE | M3_INFRAVISION, 12, CLR_RED), - MON("bone devil", S_DEMON, LVL(9, 15, -1, 40, -9), - (G_HELL | G_NOCORPSE | G_SGROUP | 2), - A(ATTK(AT_WEAP, AD_PHYS, 3, 4), ATTK(AT_STNG, AD_DRST, 2, 4), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_SILENT, MZ_LARGE), MR_FIRE | MR_POISON, 0, - M1_POIS, M2_DEMON | M2_STALK | M2_HOSTILE | M2_NASTY | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 13, CLR_GRAY), - MON("ice devil", S_DEMON, LVL(11, 6, -4, 55, -12), - (G_HELL | G_NOCORPSE | 2), - A(ATTK(AT_CLAW, AD_PHYS, 1, 4), ATTK(AT_CLAW, AD_PHYS, 1, 4), - ATTK(AT_BITE, AD_PHYS, 2, 4), ATTK(AT_STNG, AD_COLD, 3, 4), NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_SILENT, MZ_LARGE), - MR_FIRE | MR_COLD | MR_POISON, 0, M1_SEE_INVIS | M1_POIS, - M2_DEMON | M2_STALK | M2_HOSTILE | M2_NASTY, - M3_INFRAVISIBLE | M3_INFRAVISION, 14, CLR_WHITE), - MON("nalfeshnee", S_DEMON, LVL(11, 9, -1, 65, -11), - (G_HELL | G_NOCORPSE | 1), - A(ATTK(AT_CLAW, AD_PHYS, 1, 4), ATTK(AT_CLAW, AD_PHYS, 1, 4), - ATTK(AT_BITE, AD_PHYS, 2, 4), ATTK(AT_MAGC, AD_SPEL, 0, 0), NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_SPELL, MZ_LARGE), MR_FIRE | MR_POISON, 0, - M1_HUMANOID | M1_POIS, M2_DEMON | M2_STALK | M2_HOSTILE | M2_NASTY, - M3_INFRAVISIBLE | M3_INFRAVISION, 15, CLR_RED), - MON("pit fiend", S_DEMON, LVL(13, 6, -3, 65, -13), - (G_HELL | G_NOCORPSE | 2), - A(ATTK(AT_WEAP, AD_PHYS, 4, 2), ATTK(AT_WEAP, AD_PHYS, 4, 2), - ATTK(AT_HUGS, AD_PHYS, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_GROWL, MZ_LARGE), MR_FIRE | MR_POISON, 0, - M1_SEE_INVIS | M1_POIS, - M2_DEMON | M2_STALK | M2_HOSTILE | M2_NASTY | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 16, CLR_RED), - MON("sandestin", S_DEMON, LVL(13, 12, 4, 60, -5), - (G_HELL | G_NOCORPSE | 1), - A(ATTK(AT_WEAP, AD_PHYS, 2, 6), ATTK(AT_WEAP, AD_PHYS, 2, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1500, 400, MS_CUSS, MZ_HUMAN), MR_STONE, 0, M1_HUMANOID, - M2_NOPOLY | M2_STALK | M2_STRONG | M2_COLLECT | M2_SHAPESHIFTER, - M3_INFRAVISIBLE | M3_INFRAVISION, 15, CLR_GRAY), - MON("balrog", S_DEMON, LVL(16, 5, -2, 75, -14), (G_HELL | G_NOCORPSE | 1), - A(ATTK(AT_WEAP, AD_PHYS, 8, 4), ATTK(AT_WEAP, AD_PHYS, 4, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_SILENT, MZ_LARGE), MR_FIRE | MR_POISON, 0, - M1_FLY | M1_SEE_INVIS | M1_POIS, - M2_DEMON | M2_STALK | M2_HOSTILE | M2_STRONG | M2_NASTY | M2_COLLECT, - M3_INFRAVISIBLE | M3_INFRAVISION, 20, CLR_RED), - /* Named demon lords & princes plus Arch-Devils. - * (their order matters; see minion.c) - */ - MON("Juiblex", S_DEMON, LVL(50, 3, -7, 65, -15), - (G_HELL | G_NOCORPSE | G_NOGEN | G_UNIQ), - A(ATTK(AT_ENGL, AD_DISE, 4, 10), ATTK(AT_SPIT, AD_ACID, 3, 6), - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1500, 0, MS_GURGLE, MZ_LARGE), - MR_FIRE | MR_POISON | MR_ACID | MR_STONE, 0, - M1_AMPHIBIOUS | M1_AMORPHOUS | M1_NOHEAD | M1_FLY | M1_SEE_INVIS - | M1_ACID | M1_POIS, - M2_NOPOLY | M2_DEMON | M2_STALK | M2_HOSTILE | M2_PNAME | M2_NASTY - | M2_LORD | M2_MALE, - M3_WAITFORU | M3_WANTSAMUL | M3_INFRAVISION, 26, CLR_BRIGHT_GREEN), - MON("Yeenoghu", S_DEMON, LVL(56, 18, -5, 80, -15), - (G_HELL | G_NOCORPSE | G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 3, 6), ATTK(AT_WEAP, AD_CONF, 2, 8), - ATTK(AT_CLAW, AD_PLYS, 1, 6), ATTK(AT_MAGC, AD_MAGM, 2, 6), NO_ATTK, - NO_ATTK), - SIZ(900, 500, MS_ORC, MZ_LARGE), MR_FIRE | MR_POISON, 0, - M1_FLY | M1_SEE_INVIS | M1_POIS, - M2_NOPOLY | M2_DEMON | M2_STALK | M2_HOSTILE | M2_PNAME | M2_NASTY - | M2_LORD | M2_MALE | M2_COLLECT, - M3_WANTSAMUL | M3_INFRAVISIBLE | M3_INFRAVISION, 31, HI_LORD), - MON("Orcus", S_DEMON, LVL(66, 9, -6, 85, -20), - (G_HELL | G_NOCORPSE | G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 3, 6), ATTK(AT_CLAW, AD_PHYS, 3, 4), - ATTK(AT_CLAW, AD_PHYS, 3, 4), ATTK(AT_MAGC, AD_SPEL, 8, 6), - ATTK(AT_STNG, AD_DRST, 2, 4), NO_ATTK), - SIZ(1500, 500, MS_ORC, MZ_HUGE), MR_FIRE | MR_POISON, 0, - M1_FLY | M1_SEE_INVIS | M1_POIS, - M2_NOPOLY | M2_DEMON | M2_STALK | M2_HOSTILE | M2_PNAME | M2_NASTY - | M2_PRINCE | M2_MALE | M2_COLLECT, - M3_WAITFORU | M3_WANTSBOOK | M3_WANTSAMUL | M3_INFRAVISIBLE - | M3_INFRAVISION, - 36, HI_LORD), - MON("Geryon", S_DEMON, LVL(72, 3, -3, 75, 15), - (G_HELL | G_NOCORPSE | G_NOGEN | G_UNIQ), - A(ATTK(AT_CLAW, AD_PHYS, 3, 6), ATTK(AT_CLAW, AD_PHYS, 3, 6), - ATTK(AT_STNG, AD_DRST, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1500, 500, MS_BRIBE, MZ_HUGE), MR_FIRE | MR_POISON, 0, - M1_FLY | M1_SEE_INVIS | M1_POIS | M1_SLITHY, - M2_NOPOLY | M2_DEMON | M2_STALK | M2_HOSTILE | M2_PNAME | M2_NASTY - | M2_PRINCE | M2_MALE, - M3_WANTSAMUL | M3_INFRAVISIBLE | M3_INFRAVISION, 36, HI_LORD), - MON("Dispater", S_DEMON, LVL(78, 15, -2, 80, 15), - (G_HELL | G_NOCORPSE | G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 4, 6), ATTK(AT_MAGC, AD_SPEL, 6, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1500, 500, MS_BRIBE, MZ_HUMAN), MR_FIRE | MR_POISON, 0, - M1_FLY | M1_SEE_INVIS | M1_POIS | M1_HUMANOID, - M2_NOPOLY | M2_DEMON | M2_STALK | M2_HOSTILE | M2_PNAME | M2_NASTY - | M2_PRINCE | M2_MALE | M2_COLLECT, - M3_WANTSAMUL | M3_INFRAVISIBLE | M3_INFRAVISION, 40, HI_LORD), - MON("Baalzebub", S_DEMON, LVL(89, 9, -5, 85, 20), - (G_HELL | G_NOCORPSE | G_NOGEN | G_UNIQ), - A(ATTK(AT_BITE, AD_DRST, 2, 6), ATTK(AT_GAZE, AD_STUN, 2, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1500, 500, MS_BRIBE, MZ_LARGE), MR_FIRE | MR_POISON, 0, - M1_FLY | M1_SEE_INVIS | M1_POIS, - M2_NOPOLY | M2_DEMON | M2_STALK | M2_HOSTILE | M2_PNAME | M2_NASTY - | M2_PRINCE | M2_MALE, - M3_WANTSAMUL | M3_WAITFORU | M3_INFRAVISIBLE | M3_INFRAVISION, - 45, HI_LORD), - MON("Asmodeus", S_DEMON, LVL(105, 12, -7, 90, 20), - (G_HELL | G_NOCORPSE | G_NOGEN | G_UNIQ), - A(ATTK(AT_CLAW, AD_PHYS, 4, 4), ATTK(AT_MAGC, AD_COLD, 6, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1500, 500, MS_BRIBE, MZ_HUGE), MR_FIRE | MR_COLD | MR_POISON, 0, - M1_FLY | M1_SEE_INVIS | M1_HUMANOID | M1_POIS, - M2_NOPOLY | M2_DEMON | M2_STALK | M2_HOSTILE | M2_PNAME | M2_STRONG - | M2_NASTY | M2_PRINCE | M2_MALE, - M3_WANTSAMUL | M3_WAITFORU | M3_INFRAVISIBLE | M3_INFRAVISION, - 53, HI_LORD), - MON("Demogorgon", S_DEMON, LVL(106, 15, -8, 95, -20), - (G_HELL | G_NOCORPSE | G_NOGEN | G_UNIQ), - A(ATTK(AT_MAGC, AD_SPEL, 8, 6), ATTK(AT_STNG, AD_DRLI, 1, 4), - ATTK(AT_CLAW, AD_DISE, 1, 6), ATTK(AT_CLAW, AD_DISE, 1, 6), NO_ATTK, - NO_ATTK), - SIZ(1500, 500, MS_GROWL, MZ_HUGE), MR_FIRE | MR_POISON, 0, - M1_FLY | M1_SEE_INVIS | M1_NOHANDS | M1_POIS, - M2_NOPOLY | M2_DEMON | M2_STALK | M2_HOSTILE | M2_PNAME | M2_NASTY - | M2_PRINCE | M2_MALE, - M3_WANTSAMUL | M3_INFRAVISIBLE | M3_INFRAVISION, 57, HI_LORD), - /* Riders -- the Four Horsemen of the Apocalypse ("War" == player); - * depicted with '&' but do not have M2_DEMON set. - */ - MON("Death", S_DEMON, LVL(30, 12, -5, 100, 0), (G_UNIQ | G_NOGEN), - A(ATTK(AT_TUCH, AD_DETH, 8, 8), ATTK(AT_TUCH, AD_DETH, 8, 8), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 1, MS_RIDER, MZ_HUMAN), - MR_FIRE | MR_COLD | MR_ELEC | MR_SLEEP | MR_POISON | MR_STONE, 0, - M1_FLY | M1_HUMANOID | M1_REGEN | M1_SEE_INVIS | M1_TPORT_CNTRL, - M2_NOPOLY | M2_STALK | M2_HOSTILE | M2_PNAME | M2_STRONG | M2_NASTY, - M3_INFRAVISIBLE | M3_INFRAVISION | M3_DISPLACES, 34, HI_LORD), - MON("Pestilence", S_DEMON, LVL(30, 12, -5, 100, 0), (G_UNIQ | G_NOGEN), - A(ATTK(AT_TUCH, AD_PEST, 8, 8), ATTK(AT_TUCH, AD_PEST, 8, 8), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 1, MS_RIDER, MZ_HUMAN), - MR_FIRE | MR_COLD | MR_ELEC | MR_SLEEP | MR_POISON | MR_STONE, 0, - M1_FLY | M1_HUMANOID | M1_REGEN | M1_SEE_INVIS | M1_TPORT_CNTRL, - M2_NOPOLY | M2_STALK | M2_HOSTILE | M2_PNAME | M2_STRONG | M2_NASTY, - M3_INFRAVISIBLE | M3_INFRAVISION | M3_DISPLACES, 34, HI_LORD), - MON("Famine", S_DEMON, LVL(30, 12, -5, 100, 0), (G_UNIQ | G_NOGEN), - A(ATTK(AT_TUCH, AD_FAMN, 8, 8), ATTK(AT_TUCH, AD_FAMN, 8, 8), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 1, MS_RIDER, MZ_HUMAN), - MR_FIRE | MR_COLD | MR_ELEC | MR_SLEEP | MR_POISON | MR_STONE, 0, - M1_FLY | M1_HUMANOID | M1_REGEN | M1_SEE_INVIS | M1_TPORT_CNTRL, - M2_NOPOLY | M2_STALK | M2_HOSTILE | M2_PNAME | M2_STRONG | M2_NASTY, - M3_INFRAVISIBLE | M3_INFRAVISION | M3_DISPLACES, 34, HI_LORD), - /* other demons - */ -#ifdef MAIL - MON("mail daemon", S_DEMON, LVL(56, 24, 10, 127, 0), - (G_NOGEN | G_NOCORPSE), - A(NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(600, 300, MS_SILENT, MZ_HUMAN), - MR_FIRE | MR_COLD | MR_ELEC | MR_SLEEP | MR_POISON | MR_STONE, 0, - M1_FLY | M1_SWIM | M1_BREATHLESS | M1_SEE_INVIS | M1_HUMANOID - | M1_POIS, - M2_NOPOLY | M2_STALK | M2_PEACEFUL, M3_INFRAVISIBLE | M3_INFRAVISION, - 26, CLR_BRIGHT_BLUE), -#endif - MON("djinni", S_DEMON, LVL(7, 12, 4, 30, 0), (G_NOGEN | G_NOCORPSE), - A(ATTK(AT_WEAP, AD_PHYS, 2, 8), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(1500, 400, MS_DJINNI, MZ_HUMAN), MR_POISON | MR_STONE, 0, - M1_HUMANOID | M1_FLY | M1_POIS, M2_NOPOLY | M2_STALK | M2_COLLECT, - M3_INFRAVISIBLE, 8, CLR_YELLOW), - /* - * sea monsters - */ - MON("jellyfish", S_EEL, LVL(3, 3, 6, 0, 0), (G_GENO | G_NOGEN), - A(ATTK(AT_STNG, AD_DRST, 3, 3), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(80, 20, MS_SILENT, MZ_SMALL), MR_POISON, MR_POISON, - M1_SWIM | M1_AMPHIBIOUS | M1_SLITHY | M1_NOLIMBS | M1_NOHEAD - | M1_NOTAKE | M1_POIS, - M2_HOSTILE, 0, 5, CLR_BLUE), - MON("piranha", S_EEL, LVL(5, 12, 4, 0, 0), (G_GENO | G_NOGEN | G_SGROUP), - A(ATTK(AT_BITE, AD_PHYS, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(60, 30, MS_SILENT, MZ_SMALL), 0, 0, - M1_SWIM | M1_AMPHIBIOUS | M1_ANIMAL | M1_SLITHY | M1_NOLIMBS - | M1_CARNIVORE | M1_OVIPAROUS | M1_NOTAKE, - M2_HOSTILE, 0, 6, CLR_RED), - MON("shark", S_EEL, LVL(7, 12, 2, 0, 0), (G_GENO | G_NOGEN), - A(ATTK(AT_BITE, AD_PHYS, 5, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(500, 350, MS_SILENT, MZ_LARGE), 0, 0, - M1_SWIM | M1_AMPHIBIOUS | M1_ANIMAL | M1_SLITHY | M1_NOLIMBS - | M1_CARNIVORE | M1_OVIPAROUS | M1_THICK_HIDE | M1_NOTAKE, - M2_HOSTILE, 0, 9, CLR_GRAY), - MON("giant eel", S_EEL, LVL(5, 9, -1, 0, 0), (G_GENO | G_NOGEN), - A(ATTK(AT_BITE, AD_PHYS, 3, 6), ATTK(AT_TUCH, AD_WRAP, 0, 0), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(200, 250, MS_SILENT, MZ_HUGE), 0, 0, - M1_SWIM | M1_AMPHIBIOUS | M1_ANIMAL | M1_SLITHY | M1_NOLIMBS - | M1_CARNIVORE | M1_OVIPAROUS | M1_NOTAKE, - M2_HOSTILE, M3_INFRAVISIBLE, 7, CLR_CYAN), - MON("electric eel", S_EEL, LVL(7, 10, -3, 0, 0), (G_GENO | G_NOGEN), - A(ATTK(AT_BITE, AD_ELEC, 4, 6), ATTK(AT_TUCH, AD_WRAP, 0, 0), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(200, 250, MS_SILENT, MZ_HUGE), MR_ELEC, MR_ELEC, - M1_SWIM | M1_AMPHIBIOUS | M1_ANIMAL | M1_SLITHY | M1_NOLIMBS - | M1_CARNIVORE | M1_OVIPAROUS | M1_NOTAKE, - M2_HOSTILE, M3_INFRAVISIBLE, 10, CLR_BRIGHT_BLUE), - MON("kraken", S_EEL, LVL(20, 3, 6, 0, -3), (G_GENO | G_NOGEN), - A(ATTK(AT_CLAW, AD_PHYS, 2, 4), ATTK(AT_CLAW, AD_PHYS, 2, 4), - ATTK(AT_HUGS, AD_WRAP, 2, 6), ATTK(AT_BITE, AD_PHYS, 5, 4), NO_ATTK, - NO_ATTK), - SIZ(1800, 1000, MS_SILENT, MZ_HUGE), 0, 0, - M1_SWIM | M1_AMPHIBIOUS | M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, - M2_NOPOLY | M2_HOSTILE | M2_STRONG, M3_INFRAVISIBLE, 22, CLR_RED), - /* - * lizards, &c - */ - MON("newt", S_LIZARD, LVL(0, 6, 8, 0, 0), (G_GENO | 5), - A(ATTK(AT_BITE, AD_PHYS, 1, 2), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(10, 20, MS_SILENT, MZ_TINY), 0, 0, - M1_SWIM | M1_AMPHIBIOUS | M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, - M2_HOSTILE, 0, 1, CLR_YELLOW), - MON("gecko", S_LIZARD, LVL(1, 6, 8, 0, 0), (G_GENO | 5), - A(ATTK(AT_BITE, AD_PHYS, 1, 3), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(10, 20, MS_SQEEK, MZ_TINY), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_HOSTILE, 0, 2, CLR_GREEN), - MON("iguana", S_LIZARD, LVL(2, 6, 7, 0, 0), (G_GENO | 5), - A(ATTK(AT_BITE, AD_PHYS, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(30, 30, MS_SILENT, MZ_TINY), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_HOSTILE, 0, 3, CLR_BROWN), - MON("baby crocodile", S_LIZARD, LVL(3, 6, 7, 0, 0), G_GENO, - A(ATTK(AT_BITE, AD_PHYS, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(200, 200, MS_SILENT, MZ_MEDIUM), 0, 0, - M1_SWIM | M1_AMPHIBIOUS | M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, - M2_HOSTILE, 0, 4, CLR_BROWN), - MON("lizard", S_LIZARD, LVL(5, 6, 6, 10, 0), (G_GENO | 5), - A(ATTK(AT_BITE, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(10, 40, MS_SILENT, MZ_TINY), MR_STONE, MR_STONE, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_HOSTILE, 0, 6, CLR_GREEN), - MON("chameleon", S_LIZARD, LVL(6, 5, 6, 10, 0), (G_GENO | 2), - A(ATTK(AT_BITE, AD_PHYS, 4, 2), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(100, 100, MS_SILENT, MZ_TINY), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, - M2_NOPOLY | M2_HOSTILE | M2_SHAPESHIFTER, 0, 7, CLR_BROWN), - MON("crocodile", S_LIZARD, LVL(6, 9, 5, 0, 0), (G_GENO | 1), - A(ATTK(AT_BITE, AD_PHYS, 4, 2), ATTK(AT_CLAW, AD_PHYS, 1, 12), - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_SILENT, MZ_LARGE), 0, 0, - M1_SWIM | M1_AMPHIBIOUS | M1_ANIMAL | M1_THICK_HIDE | M1_NOHANDS - | M1_OVIPAROUS | M1_CARNIVORE, - M2_STRONG | M2_HOSTILE, 0, 7, CLR_BROWN), - MON("salamander", S_LIZARD, LVL(8, 12, -1, 0, -9), (G_HELL | 1), - A(ATTK(AT_WEAP, AD_PHYS, 2, 8), ATTK(AT_TUCH, AD_FIRE, 1, 6), - ATTK(AT_HUGS, AD_PHYS, 2, 6), ATTK(AT_HUGS, AD_FIRE, 3, 6), NO_ATTK, - NO_ATTK), - SIZ(1500, 400, MS_MUMBLE, MZ_HUMAN), MR_SLEEP | MR_FIRE, MR_FIRE, - M1_HUMANOID | M1_SLITHY | M1_THICK_HIDE | M1_POIS, - M2_STALK | M2_HOSTILE | M2_COLLECT | M2_MAGIC, M3_INFRAVISIBLE, - 12, CLR_ORANGE), +#undef MON +#undef NAM +#undef NAMS - /* - * dummy monster needed for visual interface - * (marking it unique prevents figurines) - */ - MON("long worm tail", S_WORM_TAIL, LVL(0, 0, 0, 0, 0), - (G_NOGEN | G_NOCORPSE | G_UNIQ), - A(NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(0, 0, 0, 0), 0, 0, 0L, M2_NOPOLY, 0, 1, CLR_BROWN), - /* Note: - * Worm tail must be between the normal monsters and the special - * quest & pseudo-character ones because an optimization in the - * random monster selection code assumes everything beyond here - * has the G_NOGEN and M2_NOPOLY attributes. - */ +void monst_globals_init(void); /* in hack.h but we're using config.h */ - /* - * character classes - */ - MON("archeologist", S_HUMAN, LVL(10, 12, 10, 1, 3), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_HUMANOID, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_TUNNEL | M1_NEEDPICK | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_STRONG | M2_COLLECT, M3_INFRAVISIBLE, - 12, HI_DOMESTIC), - MON("barbarian", S_HUMAN, LVL(10, 12, 10, 1, 0), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_HUMANOID, MZ_HUMAN), MR_POISON, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_STRONG | M2_COLLECT, M3_INFRAVISIBLE, - 12, HI_DOMESTIC), - MON("caveman", S_HUMAN, LVL(10, 12, 10, 0, 1), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_HUMANOID, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_STRONG | M2_MALE | M2_COLLECT, - M3_INFRAVISIBLE, 12, HI_DOMESTIC), - MON("cavewoman", S_HUMAN, LVL(10, 12, 10, 0, 1), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_HUMANOID, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_STRONG | M2_FEMALE | M2_COLLECT, - M3_INFRAVISIBLE, 12, HI_DOMESTIC), - MON("healer", S_HUMAN, LVL(10, 12, 10, 1, 0), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_HUMANOID, MZ_HUMAN), MR_POISON, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_STRONG | M2_COLLECT, M3_INFRAVISIBLE, - 12, HI_DOMESTIC), - MON("knight", S_HUMAN, LVL(10, 12, 10, 1, 3), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_HUMANOID, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_STRONG | M2_COLLECT, M3_INFRAVISIBLE, - 12, HI_DOMESTIC), - MON("monk", S_HUMAN, LVL(10, 12, 10, 2, 0), G_NOGEN, - A(ATTK(AT_CLAW, AD_PHYS, 1, 8), ATTK(AT_KICK, AD_PHYS, 1, 8), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_HUMANOID, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_HERBIVORE, - M2_NOPOLY | M2_HUMAN | M2_STRONG | M2_COLLECT | M2_MALE, - M3_INFRAVISIBLE, 11, HI_DOMESTIC), - MON("priest", S_HUMAN, LVL(10, 12, 10, 2, 0), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_HUMANOID, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_STRONG | M2_MALE | M2_COLLECT, - M3_INFRAVISIBLE, 12, HI_DOMESTIC), - MON("priestess", S_HUMAN, LVL(10, 12, 10, 2, 0), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_HUMANOID, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_STRONG | M2_FEMALE | M2_COLLECT, - M3_INFRAVISIBLE, 12, HI_DOMESTIC), - MON("ranger", S_HUMAN, LVL(10, 12, 10, 2, -3), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_HUMANOID, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_STRONG | M2_COLLECT, M3_INFRAVISIBLE, - 12, HI_DOMESTIC), - MON("rogue", S_HUMAN, LVL(10, 12, 10, 1, -3), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_HUMANOID, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_STRONG | M2_GREEDY | M2_JEWELS | M2_COLLECT, - M3_INFRAVISIBLE, 12, HI_DOMESTIC), - MON("samurai", S_HUMAN, LVL(10, 12, 10, 1, 3), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 1, 8), ATTK(AT_WEAP, AD_PHYS, 1, 8), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_HUMANOID, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_STRONG | M2_COLLECT, M3_INFRAVISIBLE, - 12, HI_DOMESTIC), - MON("tourist", S_HUMAN, LVL(10, 12, 10, 1, 0), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_HUMANOID, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_STRONG | M2_COLLECT, M3_INFRAVISIBLE, - 12, HI_DOMESTIC), - MON("valkyrie", S_HUMAN, LVL(10, 12, 10, 1, -1), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 1, 8), ATTK(AT_WEAP, AD_PHYS, 1, 8), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_HUMANOID, MZ_HUMAN), MR_COLD, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_STRONG | M2_FEMALE | M2_COLLECT, - M3_INFRAVISIBLE, 12, HI_DOMESTIC), - MON("wizard", S_HUMAN, LVL(10, 12, 10, 3, 0), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_HUMANOID, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_STRONG | M2_COLLECT | M2_MAGIC, - M3_INFRAVISIBLE, 12, HI_DOMESTIC), - /* - * quest leaders - */ - MON("Lord Carnarvon", S_HUMAN, LVL(20, 12, 0, 30, 20), (G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_LEADER, MZ_HUMAN), 0, 0, - M1_TUNNEL | M1_NEEDPICK | M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_PNAME | M2_PEACEFUL | M2_STRONG | M2_MALE - | M2_COLLECT | M2_MAGIC, - M3_CLOSE | M3_INFRAVISIBLE, 22, HI_LORD), - MON("Pelias", S_HUMAN, LVL(20, 12, 0, 30, 0), (G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_LEADER, MZ_HUMAN), MR_POISON, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_PNAME | M2_PEACEFUL | M2_STRONG | M2_MALE - | M2_COLLECT | M2_MAGIC, - M3_CLOSE | M3_INFRAVISIBLE, 22, HI_LORD), - MON("Shaman Karnov", S_HUMAN, LVL(20, 12, 0, 30, 20), (G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_LEADER, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_PNAME | M2_PEACEFUL | M2_STRONG | M2_MALE - | M2_COLLECT | M2_MAGIC, - M3_CLOSE | M3_INFRAVISIBLE, 22, HI_LORD), -#if 0 /* OBSOLETE */ - /* Two for elves - one of each sex. - */ - MON("Earendil", S_HUMAN, - LVL(20, 12, 0, 50, -20), (G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 1, 8), - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_ELF, 350, MS_LEADER, MZ_HUMAN), MR_SLEEP, MR_SLEEP, - M1_HUMANOID | M1_SEE_INVIS | M1_OMNIVORE, - M2_NOPOLY | M2_ELF | M2_HUMAN | M2_PNAME | M2_PEACEFUL | M2_STRONG - | M2_MALE | M2_COLLECT | M2_MAGIC, - M3_CLOSE | M3_INFRAVISION | M3_INFRAVISIBLE, 22, HI_LORD), - MON("Elwing", S_HUMAN, - LVL(20, 12, 0, 50, -20), (G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 1, 8), - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_ELF, 350, MS_LEADER, MZ_HUMAN), MR_SLEEP, MR_SLEEP, - M1_HUMANOID | M1_SEE_INVIS | M1_OMNIVORE, - M2_NOPOLY | M2_ELF | M2_HUMAN | M2_PNAME | M2_PEACEFUL | M2_STRONG - | M2_FEMALE | M2_COLLECT | M2_MAGIC, - M3_CLOSE | M3_INFRAVISION | M3_INFRAVISIBLE, 22, HI_LORD), -#endif - MON("Hippocrates", S_HUMAN, LVL(20, 12, 0, 40, 0), (G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_LEADER, MZ_HUMAN), MR_POISON, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_PNAME | M2_PEACEFUL | M2_STRONG | M2_MALE - | M2_COLLECT | M2_MAGIC, - M3_CLOSE | M3_INFRAVISIBLE, 22, HI_LORD), - MON("King Arthur", S_HUMAN, LVL(20, 12, 0, 40, 20), (G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_LEADER, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_PNAME | M2_PEACEFUL | M2_STRONG | M2_MALE - | M2_COLLECT | M2_MAGIC, - M3_CLOSE | M3_INFRAVISIBLE, 23, HI_LORD), - MON("Grand Master", S_HUMAN, LVL(25, 12, 0, 70, 0), (G_NOGEN | G_UNIQ), - A(ATTK(AT_CLAW, AD_PHYS, 4, 10), ATTK(AT_KICK, AD_PHYS, 2, 8), - ATTK(AT_MAGC, AD_CLRC, 2, 8), ATTK(AT_MAGC, AD_CLRC, 2, 8), NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_LEADER, MZ_HUMAN), - MR_FIRE | MR_ELEC | MR_SLEEP | MR_POISON, 0, - M1_HUMANOID | M1_SEE_INVIS | M1_HERBIVORE, - M2_NOPOLY | M2_HUMAN | M2_PEACEFUL | M2_STRONG | M2_MALE | M2_NASTY - | M2_MAGIC, - M3_CLOSE | M3_INFRAVISIBLE, 30, CLR_BLACK), - MON("Arch Priest", S_HUMAN, LVL(25, 12, 7, 70, 0), (G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 4, 10), ATTK(AT_KICK, AD_PHYS, 2, 8), - ATTK(AT_MAGC, AD_CLRC, 2, 8), ATTK(AT_MAGC, AD_CLRC, 2, 8), NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_LEADER, MZ_HUMAN), - MR_FIRE | MR_ELEC | MR_SLEEP | MR_POISON, 0, - M1_HUMANOID | M1_SEE_INVIS | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_PEACEFUL | M2_STRONG | M2_MALE | M2_COLLECT - | M2_MAGIC, - M3_CLOSE | M3_INFRAVISIBLE, 30, CLR_WHITE), - MON("Orion", S_HUMAN, LVL(20, 12, 0, 30, 0), (G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(2200, 700, MS_LEADER, MZ_HUGE), 0, 0, - M1_HUMANOID | M1_OMNIVORE | M1_SEE_INVIS | M1_SWIM | M1_AMPHIBIOUS, - M2_NOPOLY | M2_HUMAN | M2_PNAME | M2_PEACEFUL | M2_STRONG | M2_MALE - | M2_COLLECT | M2_MAGIC, - M3_CLOSE | M3_INFRAVISION | M3_INFRAVISIBLE, 22, HI_LORD), - /* Note: Master of Thieves is also the Tourist's nemesis. - */ - MON("Master of Thieves", S_HUMAN, LVL(20, 12, 0, 30, -20), - (G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 2, 6), ATTK(AT_WEAP, AD_PHYS, 2, 6), - ATTK(AT_CLAW, AD_SAMU, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_LEADER, MZ_HUMAN), MR_STONE, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_PEACEFUL | M2_STRONG | M2_MALE | M2_GREEDY - | M2_JEWELS | M2_COLLECT | M2_MAGIC, - M3_CLOSE | M3_INFRAVISIBLE, 24, HI_LORD), - MON("Lord Sato", S_HUMAN, LVL(20, 12, 0, 30, 20), (G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 1, 8), ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_LEADER, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_PNAME | M2_PEACEFUL | M2_STRONG | M2_MALE - | M2_COLLECT | M2_MAGIC, - M3_CLOSE | M3_INFRAVISIBLE, 23, HI_LORD), - MON("Twoflower", S_HUMAN, LVL(20, 12, 10, 20, 0), (G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_LEADER, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_PNAME | M2_PEACEFUL | M2_STRONG | M2_MALE - | M2_COLLECT | M2_MAGIC, - M3_CLOSE | M3_INFRAVISIBLE, 22, HI_DOMESTIC), - MON("Norn", S_HUMAN, LVL(20, 12, 0, 80, 0), (G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 1, 8), ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1800, 550, MS_LEADER, MZ_HUGE), MR_COLD, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_PEACEFUL | M2_STRONG | M2_FEMALE - | M2_COLLECT | M2_MAGIC, - M3_CLOSE | M3_INFRAVISIBLE, 23, HI_LORD), - MON("Neferet the Green", S_HUMAN, LVL(20, 12, 0, 60, 0), - (G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), ATTK(AT_MAGC, AD_SPEL, 2, 8), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_LEADER, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_FEMALE | M2_PNAME | M2_PEACEFUL | M2_STRONG - | M2_COLLECT | M2_MAGIC, - M3_CLOSE | M3_INFRAVISIBLE, 23, CLR_GREEN), - /* - * quest nemeses - */ - MON("Minion of Huhetotl", S_DEMON, LVL(16, 12, -2, 75, -14), - (G_NOCORPSE | G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 8, 4), ATTK(AT_WEAP, AD_PHYS, 4, 6), - ATTK(AT_MAGC, AD_SPEL, 0, 0), ATTK(AT_CLAW, AD_SAMU, 2, 6), NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_NEMESIS, MZ_LARGE), - MR_FIRE | MR_POISON | MR_STONE, 0, M1_FLY | M1_SEE_INVIS | M1_POIS, - M2_NOPOLY | M2_DEMON | M2_STALK | M2_HOSTILE | M2_STRONG | M2_NASTY - | M2_COLLECT, - M3_WANTSARTI | M3_WAITFORU | M3_INFRAVISION | M3_INFRAVISIBLE, - 23, CLR_RED), - MON("Thoth Amon", S_HUMAN, LVL(16, 12, 0, 10, -14), - (G_NOGEN | G_UNIQ | G_NOCORPSE), - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), ATTK(AT_MAGC, AD_SPEL, 0, 0), - ATTK(AT_MAGC, AD_SPEL, 0, 0), ATTK(AT_CLAW, AD_SAMU, 1, 4), NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_NEMESIS, MZ_HUMAN), MR_POISON | MR_STONE, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_PNAME | M2_STRONG | M2_MALE | M2_STALK - | M2_HOSTILE | M2_NASTY | M2_COLLECT | M2_MAGIC, - M3_WANTSARTI | M3_WAITFORU | M3_INFRAVISIBLE, 22, HI_LORD), - /* Multi-headed, possessing the breath attacks of all the other dragons - * (selected at random when attacking). - */ - MON("Chromatic Dragon", S_DRAGON, LVL(16, 12, 0, 30, -14), - (G_NOGEN | G_UNIQ), - A(ATTK(AT_BREA, AD_RBRE, 6, 6), ATTK(AT_MAGC, AD_SPEL, 0, 0), - ATTK(AT_CLAW, AD_SAMU, 2, 8), ATTK(AT_BITE, AD_PHYS, 4, 8), - ATTK(AT_BITE, AD_PHYS, 4, 8), ATTK(AT_STNG, AD_PHYS, 1, 6)), - SIZ(WT_DRAGON, 1700, MS_NEMESIS, MZ_GIGANTIC), - MR_FIRE | MR_COLD | MR_SLEEP | MR_DISINT | MR_ELEC | MR_POISON - | MR_ACID | MR_STONE, - MR_FIRE | MR_COLD | MR_SLEEP | MR_DISINT | MR_ELEC | MR_POISON - | MR_STONE, - M1_THICK_HIDE | M1_NOHANDS | M1_CARNIVORE | M1_SEE_INVIS | M1_POIS, - M2_NOPOLY | M2_HOSTILE | M2_FEMALE | M2_STALK | M2_STRONG | M2_NASTY - | M2_GREEDY | M2_JEWELS | M2_MAGIC, - M3_WANTSARTI | M3_WAITFORU | M3_INFRAVISIBLE, 23, HI_LORD), -#if 0 /* OBSOLETE */ - MON("Goblin King", S_ORC, - LVL(15, 12, 10, 0, -15), (G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 2, 6), ATTK(AT_WEAP, AD_PHYS, 2, 6), - ATTK(AT_CLAW, AD_SAMU, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(750, 350, MS_NEMESIS, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_ORC | M2_HOSTILE | M2_STRONG | M2_STALK | M2_NASTY - | M2_MALE | M2_GREEDY | M2_JEWELS | M2_COLLECT | M2_MAGIC, - M3_WANTSARTI | M3_WAITFORU | M3_INFRAVISION | M3_INFRAVISIBLE, - 18, HI_LORD), -#endif - MON("Cyclops", S_GIANT, LVL(18, 12, 0, 0, -15), (G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 4, 8), ATTK(AT_WEAP, AD_PHYS, 4, 8), - ATTK(AT_CLAW, AD_SAMU, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1900, 700, MS_NEMESIS, MZ_HUGE), MR_STONE, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_GIANT | M2_STRONG | M2_ROCKTHROW | M2_STALK - | M2_HOSTILE | M2_NASTY | M2_MALE | M2_JEWELS | M2_COLLECT, - M3_WANTSARTI | M3_WAITFORU | M3_INFRAVISION | M3_INFRAVISIBLE, - 23, CLR_GRAY), - MON("Ixoth", S_DRAGON, LVL(15, 12, -1, 20, -14), (G_NOGEN | G_UNIQ), - A(ATTK(AT_BREA, AD_FIRE, 8, 6), ATTK(AT_BITE, AD_PHYS, 4, 8), - ATTK(AT_MAGC, AD_SPEL, 0, 0), ATTK(AT_CLAW, AD_PHYS, 2, 4), - ATTK(AT_CLAW, AD_SAMU, 2, 4), NO_ATTK), - SIZ(WT_DRAGON, 1600, MS_NEMESIS, MZ_GIGANTIC), MR_FIRE | MR_STONE, - MR_FIRE, - M1_FLY | M1_THICK_HIDE | M1_NOHANDS | M1_CARNIVORE | M1_SEE_INVIS, - M2_NOPOLY | M2_MALE | M2_PNAME | M2_HOSTILE | M2_STRONG | M2_NASTY - | M2_STALK | M2_GREEDY | M2_JEWELS | M2_MAGIC, - M3_WANTSARTI | M3_WAITFORU | M3_INFRAVISIBLE, 22, CLR_RED), - MON("Master Kaen", S_HUMAN, LVL(25, 12, -10, 10, -20), (G_NOGEN | G_UNIQ), - A(ATTK(AT_CLAW, AD_PHYS, 16, 2), ATTK(AT_CLAW, AD_PHYS, 16, 2), - ATTK(AT_MAGC, AD_CLRC, 0, 0), ATTK(AT_CLAW, AD_SAMU, 1, 4), NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_NEMESIS, MZ_HUMAN), MR_POISON | MR_STONE, - MR_POISON, M1_HUMANOID | M1_HERBIVORE | M1_SEE_INVIS, - M2_NOPOLY | M2_HUMAN | M2_MALE | M2_PNAME | M2_HOSTILE | M2_STRONG - | M2_NASTY | M2_STALK | M2_COLLECT | M2_MAGIC, - M3_WANTSARTI | M3_WAITFORU | M3_INFRAVISIBLE, 31, HI_LORD), - MON("Nalzok", S_DEMON, LVL(16, 12, -2, 85, -127), - (G_NOGEN | G_UNIQ | G_NOCORPSE), - A(ATTK(AT_WEAP, AD_PHYS, 8, 4), ATTK(AT_WEAP, AD_PHYS, 4, 6), - ATTK(AT_MAGC, AD_SPEL, 0, 0), ATTK(AT_CLAW, AD_SAMU, 2, 6), NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_NEMESIS, MZ_LARGE), - MR_FIRE | MR_POISON | MR_STONE, 0, M1_FLY | M1_SEE_INVIS | M1_POIS, - M2_NOPOLY | M2_DEMON | M2_MALE | M2_PNAME | M2_HOSTILE | M2_STRONG - | M2_STALK | M2_NASTY | M2_COLLECT, - M3_WANTSARTI | M3_WAITFORU | M3_INFRAVISION | M3_INFRAVISIBLE, - 23, CLR_RED), - MON("Scorpius", S_SPIDER, LVL(15, 12, 10, 0, -15), (G_NOGEN | G_UNIQ), - A(ATTK(AT_CLAW, AD_PHYS, 2, 6), ATTK(AT_CLAW, AD_SAMU, 2, 6), - ATTK(AT_STNG, AD_DISE, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(750, 350, MS_NEMESIS, MZ_HUMAN), MR_POISON | MR_STONE, MR_POISON, - M1_ANIMAL | M1_NOHANDS | M1_OVIPAROUS | M1_POIS | M1_CARNIVORE, - M2_NOPOLY | M2_MALE | M2_PNAME | M2_HOSTILE | M2_STRONG | M2_STALK - | M2_NASTY | M2_COLLECT | M2_MAGIC, - M3_WANTSARTI | M3_WAITFORU, 17, HI_LORD), - MON("Master Assassin", S_HUMAN, LVL(15, 12, 0, 30, 18), - (G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_DRST, 2, 6), ATTK(AT_WEAP, AD_PHYS, 2, 8), - ATTK(AT_CLAW, AD_SAMU, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_NEMESIS, MZ_HUMAN), MR_STONE, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_STRONG | M2_MALE | M2_HOSTILE | M2_STALK - | M2_NASTY | M2_COLLECT | M2_MAGIC, - M3_WANTSARTI | M3_WAITFORU | M3_INFRAVISIBLE, 20, HI_LORD), - /* A renegade daimyo who led a 13 year civil war against the shogun - * of his time. - */ - MON("Ashikaga Takauji", S_HUMAN, LVL(15, 12, 0, 40, -13), - (G_NOGEN | G_UNIQ | G_NOCORPSE), - A(ATTK(AT_WEAP, AD_PHYS, 2, 6), ATTK(AT_WEAP, AD_PHYS, 2, 6), - ATTK(AT_CLAW, AD_SAMU, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_NEMESIS, MZ_HUMAN), MR_STONE, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_PNAME | M2_HOSTILE | M2_STRONG | M2_STALK - | M2_NASTY | M2_MALE | M2_COLLECT | M2_MAGIC, - M3_WANTSARTI | M3_WAITFORU | M3_INFRAVISIBLE, 19, HI_LORD), - /* - * Note: the Master of Thieves was defined above. - */ - MON("Lord Surtur", S_GIANT, LVL(15, 12, 2, 50, 12), (G_NOGEN | G_UNIQ), - A(ATTK(AT_WEAP, AD_PHYS, 2, 10), ATTK(AT_WEAP, AD_PHYS, 2, 10), - ATTK(AT_CLAW, AD_SAMU, 2, 6), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(2250, 850, MS_NEMESIS, MZ_HUGE), MR_FIRE | MR_STONE, MR_FIRE, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_GIANT | M2_MALE | M2_PNAME | M2_HOSTILE | M2_STALK - | M2_STRONG | M2_NASTY | M2_ROCKTHROW | M2_JEWELS | M2_COLLECT, - M3_WANTSARTI | M3_WAITFORU | M3_INFRAVISION | M3_INFRAVISIBLE, - 19, HI_LORD), - MON("Dark One", S_HUMAN, LVL(15, 12, 0, 80, -10), - (G_NOGEN | G_UNIQ | G_NOCORPSE), - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), ATTK(AT_WEAP, AD_PHYS, 1, 6), - ATTK(AT_CLAW, AD_SAMU, 1, 4), ATTK(AT_MAGC, AD_SPEL, 0, 0), NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_NEMESIS, MZ_HUMAN), MR_STONE, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_STRONG | M2_HOSTILE | M2_STALK | M2_NASTY - | M2_COLLECT | M2_MAGIC, - M3_WANTSARTI | M3_WAITFORU | M3_INFRAVISIBLE, 20, CLR_BLACK), - /* - * quest "guardians" - */ - MON("student", S_HUMAN, LVL(5, 12, 10, 10, 3), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_GUARDIAN, MZ_HUMAN), 0, 0, - M1_TUNNEL | M1_NEEDPICK | M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_PEACEFUL | M2_STRONG | M2_COLLECT, - M3_INFRAVISIBLE, 7, HI_DOMESTIC), - MON("chieftain", S_HUMAN, LVL(5, 12, 10, 10, 0), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_GUARDIAN, MZ_HUMAN), MR_POISON, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_PEACEFUL | M2_STRONG | M2_COLLECT, - M3_INFRAVISIBLE, 7, HI_DOMESTIC), - MON("neanderthal", S_HUMAN, LVL(5, 12, 10, 10, 1), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_GUARDIAN, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_PEACEFUL | M2_STRONG | M2_COLLECT, - M3_INFRAVISIBLE, 7, HI_DOMESTIC), -#if 0 /* OBSOLETE */ - MON("High-elf", S_HUMAN, - LVL(5, 12, 10, 10, -7), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 2, 4), ATTK(AT_MAGC, AD_CLRC, 0, 0), - NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_ELF, 350, MS_GUARDIAN, MZ_HUMAN), MR_SLEEP, MR_SLEEP, - M1_HUMANOID | M1_SEE_INVIS | M1_OMNIVORE, - M2_NOPOLY | M2_ELF | M2_PEACEFUL | M2_COLLECT, - M3_INFRAVISION | M3_INFRAVISIBLE, 7, HI_DOMESTIC), -#endif - MON("attendant", S_HUMAN, LVL(5, 12, 10, 10, 3), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_GUARDIAN, MZ_HUMAN), MR_POISON, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_PEACEFUL | M2_STRONG | M2_COLLECT, - M3_INFRAVISIBLE, 7, HI_DOMESTIC), - MON("page", S_HUMAN, LVL(5, 12, 10, 10, 3), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_GUARDIAN, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_PEACEFUL | M2_STRONG | M2_COLLECT, - M3_INFRAVISIBLE, 7, HI_DOMESTIC), - MON("abbot", S_HUMAN, LVL(5, 12, 10, 20, 0), G_NOGEN, - A(ATTK(AT_CLAW, AD_PHYS, 8, 2), ATTK(AT_KICK, AD_STUN, 3, 2), - ATTK(AT_MAGC, AD_CLRC, 0, 0), NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_GUARDIAN, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_HERBIVORE, - M2_NOPOLY | M2_HUMAN | M2_PEACEFUL | M2_STRONG | M2_COLLECT, - M3_INFRAVISIBLE, 8, HI_DOMESTIC), - MON("acolyte", S_HUMAN, LVL(5, 12, 10, 20, 0), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), ATTK(AT_MAGC, AD_CLRC, 0, 0), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_GUARDIAN, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_PEACEFUL | M2_STRONG | M2_COLLECT, - M3_INFRAVISIBLE, 8, HI_DOMESTIC), - MON("hunter", S_HUMAN, LVL(5, 12, 10, 10, -7), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, - NO_ATTK), - SIZ(WT_HUMAN, 400, MS_GUARDIAN, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_SEE_INVIS | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_PEACEFUL | M2_STRONG | M2_COLLECT, - M3_INFRAVISION | M3_INFRAVISIBLE, 7, HI_DOMESTIC), - MON("thug", S_HUMAN, LVL(5, 12, 10, 10, -3), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_GUARDIAN, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, M2_NOPOLY | M2_HUMAN | M2_PEACEFUL - | M2_STRONG | M2_GREEDY | M2_COLLECT, - M3_INFRAVISIBLE, 7, HI_DOMESTIC), - MON("ninja", S_HUMAN, LVL(5, 12, 10, 10, 3), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 1, 8), ATTK(AT_WEAP, AD_PHYS, 1, 8), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_HUMANOID, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_HOSTILE | M2_STRONG | M2_COLLECT, - M3_INFRAVISIBLE, 7, HI_DOMESTIC), - MON("roshi", S_HUMAN, LVL(5, 12, 10, 10, 3), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 1, 8), ATTK(AT_WEAP, AD_PHYS, 1, 8), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_GUARDIAN, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, - M2_NOPOLY | M2_HUMAN | M2_PEACEFUL | M2_STRONG | M2_COLLECT, - M3_INFRAVISIBLE, 7, HI_DOMESTIC), - MON("guide", S_HUMAN, LVL(5, 12, 10, 20, 0), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), ATTK(AT_MAGC, AD_SPEL, 0, 0), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_GUARDIAN, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, M2_NOPOLY | M2_HUMAN | M2_PEACEFUL - | M2_STRONG | M2_COLLECT | M2_MAGIC, - M3_INFRAVISIBLE, 8, HI_DOMESTIC), - MON("warrior", S_HUMAN, LVL(5, 12, 10, 10, -1), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 1, 8), ATTK(AT_WEAP, AD_PHYS, 1, 8), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_GUARDIAN, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, M2_NOPOLY | M2_HUMAN | M2_PEACEFUL - | M2_STRONG | M2_COLLECT | M2_FEMALE, - M3_INFRAVISIBLE, 7, HI_DOMESTIC), - MON("apprentice", S_HUMAN, LVL(5, 12, 10, 30, 0), G_NOGEN, - A(ATTK(AT_WEAP, AD_PHYS, 1, 6), ATTK(AT_MAGC, AD_SPEL, 0, 0), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(WT_HUMAN, 400, MS_GUARDIAN, MZ_HUMAN), 0, 0, - M1_HUMANOID | M1_OMNIVORE, M2_NOPOLY | M2_HUMAN | M2_PEACEFUL - | M2_STRONG | M2_COLLECT | M2_MAGIC, - M3_INFRAVISIBLE, 8, HI_DOMESTIC), - /* - * array terminator - */ - MON("", 0, LVL(0, 0, 0, 0, 0), (0), - A(NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(0, 0, 0, 0), 0, 0, 0L, 0L, 0, 0, 0) -}; -#endif /* !SPLITMON_1 */ +struct permonst mons[SIZE(mons_init)]; -#ifndef SPLITMON_1 -/* dummy routine used to force linkage */ void -monst_init() +monst_globals_init(void) { + memcpy(mons, mons_init, sizeof mons); return; } -const struct attack sa_yes[NATTK] = SEDUCTION_ATTACKS_YES; -const struct attack sa_no[NATTK] = SEDUCTION_ATTACKS_NO; -#endif +const struct attack c_sa_yes[NATTK] = SEDUCTION_ATTACKS_YES; +const struct attack c_sa_no[NATTK] = SEDUCTION_ATTACKS_NO; + +/* for 'onefile' processing where end of this file isn't necessarily the + end of the source code seen by the compiler */ +#undef NO_ATTK +#undef LVL +#undef SIZ +#undef ATTK +#undef A /*monst.c*/ diff --git a/src/mplayer.c b/src/mplayer.c index 2dba08e1b..267ab0d39 100644 --- a/src/mplayer.c +++ b/src/mplayer.c @@ -1,12 +1,12 @@ -/* NetHack 3.6 mplayer.c $NHDT-Date: 1550524564 2019/02/18 21:16:04 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.26 $ */ +/* NetHack 5.0 mplayer.c $NHDT-Date: 1596498188 2020/08/03 23:43:08 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.30 $ */ /* Copyright (c) Izchak Miller, 1992. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -STATIC_DCL const char *NDECL(dev_name); -STATIC_DCL void FDECL(get_mplname, (struct monst *, char *)); -STATIC_DCL void FDECL(mk_mplayer_armor, (struct monst *, SHORT_P)); +staticfn const char *dev_name(void); +staticfn void get_mplname(struct monst *, char *); +staticfn void mk_mplayer_armor(struct monst *, short); /* These are the names of those who * contributed to the development of NetHack 3.2/3.3/3.4/3.6. @@ -14,7 +14,7 @@ STATIC_DCL void FDECL(mk_mplayer_armor, (struct monst *, SHORT_P)); * Keep in alphabetical order within teams. * Same first name is entered once within each team. */ -static const char *developers[] = { +static const char *const developers[] = { /* devteam */ "Alex", "Dave", "Dean", "Derek", "Eric", "Izchak", "Janet", "Jessie", "Ken", "Kevin", "Michael", "Mike", @@ -40,12 +40,12 @@ static const char *developers[] = { }; /* return a randomly chosen developer name */ -STATIC_OVL const char * -dev_name() +staticfn const char * +dev_name(void) { - register int i, m = 0, n = SIZE(developers); - register struct monst *mtmp; - register boolean match; + int i, m = 0, n = SIZE(developers); + struct monst *mtmp; + boolean match; do { match = FALSE; @@ -53,7 +53,8 @@ dev_name() for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { if (!is_mplayer(mtmp->data)) continue; - if (!strncmp(developers[i], (has_mname(mtmp)) ? MNAME(mtmp) : "", + if (!strncmp(developers[i], + (has_mgivenname(mtmp)) ? MGIVENNAME(mtmp) : "", strlen(developers[i]))) { match = TRUE; break; @@ -67,10 +68,8 @@ dev_name() return (developers[i]); } -STATIC_OVL void -get_mplname(mtmp, nam) -register struct monst *mtmp; -char *nam; +staticfn void +get_mplname(struct monst *mtmp, char *nam) { boolean fmlkind = is_female(mtmp->data); const char *devnam; @@ -92,16 +91,15 @@ char *nam; (boolean) mtmp->female)); } -STATIC_OVL void -mk_mplayer_armor(mon, typ) -struct monst *mon; -short typ; +staticfn void +mk_mplayer_armor(struct monst *mon, short typ) { struct obj *obj; if (typ == STRANGE_OBJECT) return; obj = mksobj(typ, FALSE, FALSE); + obj->oeroded = obj->oeroded2 = 0; if (!rn2(3)) obj->oerodeproof = 1; if (!rn2(3)) @@ -117,24 +115,21 @@ short typ; } struct monst * -mk_mplayer(ptr, x, y, special) -register struct permonst *ptr; -xchar x, y; -register boolean special; +mk_mplayer(struct permonst *ptr, coordxy x, coordxy y, boolean special) { - register struct monst *mtmp; + struct monst *mtmp; char nam[PL_NSIZ]; if (!is_mplayer(ptr)) return ((struct monst *) 0); if (MON_AT(x, y)) - (void) rloc(m_at(x, y), FALSE); /* insurance */ + (void) rloc(m_at(x, y), RLOC_ERR|RLOC_NOMSG); /* insurance */ if (!In_endgame(&u.uz)) special = FALSE; - if ((mtmp = makemon(ptr, x, y, NO_MM_FLAGS)) != 0) { + if ((mtmp = makemon(ptr, x, y, special ? MM_NOMSG : NO_MM_FLAGS)) != 0) { short weapon, armor, cloak, helm, shield; int quan; struct obj *otmp; @@ -176,8 +171,7 @@ register boolean special; if (helm == HELM_OF_BRILLIANCE) helm = STRANGE_OBJECT; break; - case PM_CAVEMAN: - case PM_CAVEWOMAN: + case PM_CAVE_DWELLER: if (rn2(4)) weapon = MACE; else if (rn2(2)) @@ -208,8 +202,7 @@ register boolean special; if (rn2(2)) shield = STRANGE_OBJECT; break; - case PM_PRIEST: - case PM_PRIESTESS: + case PM_CLERIC: if (rn2(2)) weapon = MACE; if (rn2(2)) @@ -262,19 +255,22 @@ register boolean special; if (weapon != STRANGE_OBJECT) { otmp = mksobj(weapon, TRUE, FALSE); + otmp->oeroded = otmp->oeroded2 = 0; otmp->spe = (special ? rn1(5, 4) : rn2(4)); if (!rn2(3)) otmp->oerodeproof = 1; else if (!rn2(2)) otmp->greased = 1; + /* mk_artifact() with otmp and A_NONE will never return NULL */ if (special && rn2(2)) - otmp = mk_artifact(otmp, A_NONE); + otmp = mk_artifact(otmp, A_NONE, 99, FALSE); /* usually increase stack size if stackable weapon */ if (objects[otmp->otyp].oc_merge && !otmp->oartifact && monmightthrowwep(otmp)) otmp->quan += (long) rn2(is_spear(otmp) ? 4 : 8); + otmp->owt = weight(otmp); /* mplayers knew better than to overenchant Magicbane */ - if (otmp->oartifact == ART_MAGICBANE) + if (is_art(otmp, ART_MAGICBANE)) otmp->spe = rnd(4); (void) mpickobj(mtmp, otmp); } @@ -328,14 +324,12 @@ register boolean special; * fill up the overflow. */ void -create_mplayers(num, special) -register int num; -boolean special; +create_mplayers(int num, boolean special) { int pm, x, y; struct monst fakemon; - fakemon = zeromonst; + fakemon = cg.zeromonst; while (num) { int tryct = 0; @@ -353,14 +347,13 @@ boolean special; if (tryct > 50) return; - (void) mk_mplayer(&mons[pm], (xchar) x, (xchar) y, special); + (void) mk_mplayer(&mons[pm], (coordxy) x, (coordxy) y, special); num--; } } void -mplayer_talk(mtmp) -register struct monst *mtmp; +mplayer_talk(struct monst *mtmp) { static const char *same_class_msg[3] = { @@ -377,10 +370,10 @@ register struct monst *mtmp; if (mtmp->mpeaceful) return; /* will drop to humanoid talk */ - pline("Talk? -- %s", (mtmp->data == &mons[urole.malenum] - || mtmp->data == &mons[urole.femalenum]) - ? same_class_msg[rn2(3)] - : other_class_msg[rn2(3)]); + SetVoice(mtmp, 0, 80, 0); + verbalize("Talk? -- %s", mtmp->data == &mons[gu.urole.mnum] + ? same_class_msg[rn2(3)] + : other_class_msg[rn2(3)]); } /*mplayer.c*/ diff --git a/src/mthrowu.c b/src/mthrowu.c index 7ecbbb2b2..c292fd7e6 100644 --- a/src/mthrowu.c +++ b/src/mthrowu.c @@ -1,44 +1,86 @@ -/* NetHack 3.6 mthrowu.c $NHDT-Date: 1573688695 2019/11/13 23:44:55 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.86 $ */ +/* NetHack 5.0 mthrowu.c $NHDT-Date: 1737392015 2025/01/20 08:53:35 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.173 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Pasi Kallinen, 2016. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -STATIC_DCL int FDECL(monmulti, (struct monst *, struct obj *, struct obj *)); -STATIC_DCL void FDECL(monshoot, (struct monst *, struct obj *, struct obj *)); -STATIC_DCL int FDECL(drop_throw, (struct obj *, BOOLEAN_P, int, int)); -STATIC_DCL boolean FDECL(m_lined_up, (struct monst *, struct monst *)); +staticfn int monmulti(struct monst *, struct obj *, struct obj *); +staticfn void monshoot(struct monst *, struct obj *, struct obj *); +staticfn boolean ucatchgem(struct obj *, struct monst *); +staticfn boolean u_catch_thrown_obj(struct obj *); +staticfn const char *breathwep_name(int); +staticfn boolean drop_throw(struct obj *, boolean, coordxy, coordxy); +staticfn boolean blocking_terrain(coordxy, coordxy); +staticfn int m_lined_up(struct monst *, struct monst *) NONNULLARG12; +staticfn void return_from_mtoss(struct monst *, struct obj *, boolean); #define URETREATING(x, y) \ (distmin(u.ux, u.uy, x, y) > distmin(u.ux0, u.uy0, x, y)) -#define POLE_LIM 5 /* How far monsters can use pole-weapons */ - -#define PET_MISSILE_RANGE2 36 /* Square of distance within which pets shoot */ - /* * Keep consistent with breath weapons in zap.c, and AD_* in monattk.h. */ -STATIC_OVL NEARDATA const char *breathwep[] = { +static NEARDATA const char *breathwep[] = { "fragments", "fire", "frost", "sleep gas", "a disintegration blast", "lightning", "poison gas", "acid", "strange breath #8", "strange breath #9" }; -extern boolean notonhead; /* for long worms */ -STATIC_VAR int mesg_given; /* for m_throw()/thitu() 'miss' message */ +/* hallucinatory ray types */ +static const char *const hallublasts[] = { + "asteroids", "beads", "bubbles", "butterflies", "champagne", "chaos", + "coins", "cotton candy", "crumbs", "dark matter", "darkness", "data", + "dust specks", "emoticons", "emotions", "entropy", "flowers", "foam", + "fog", "gamma rays", "gelatin", "gemstones", "ghosts", "glass shards", + "glitter", "good vibes", "gravel", "gravity", "gravy", "grawlixes", + "holy light", "hornets", "hot air", "hyphens", "hypnosis", "infrared", + "insects", "jargon", "laser beams", "leaves", "lightening", "logic gates", + "magma", "marbles", "mathematics", "megabytes", "metal shavings", + "metapatterns", "meteors", "mist", "mud", "music", "nanites", "needles", + "noise", "nostalgia", "oil", "paint", "photons", "pixels", "plasma", + "polarity", "powder", "powerups", "prismatic light", "pure logic", + "purple", "radio waves", "rainbows", "rock music", "rocket fuel", "rope", + "sadness", "salt", "sand", "scrolls", "sludge", "smileys", "snowflakes", + "sparkles", "specularity", "spores", "stars", "steam", "tetrahedrons", + "text", "the past", "tornadoes", "toxic waste", "ultraviolet light", + "viruses", "water", "waveforms", "wind", "X-rays", "zorkmids" +}; + +/* Return a random hallucinatory blast. */ +const char * +rnd_hallublast(void) +{ + return ROLL_FROM(hallublasts); +} + +boolean +m_has_launcher_and_ammo(struct monst *mtmp) +{ + struct obj *mwep = MON_WEP(mtmp); + + if (mwep && is_launcher(mwep)) { + struct obj *otmp; + + for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj) + if (ammo_and_launcher(otmp, mwep)) + return TRUE; + } + return FALSE; +} -/* hero is hit by something other than a monster */ +/* hero is hit by something other than a monster (though it could be a + missile thrown or shot by a monster) */ int -thitu(tlev, dam, objp, name) -int tlev, dam; -struct obj **objp; -const char *name; /* if null, then format `*objp' */ +thitu( + int tlev, /* pseudo-level used when deciding whether to hit hero's AC */ + int dam, + struct obj **objp, + const char *name) /* if null, then format `*objp' */ { struct obj *obj = objp ? *objp : 0; const char *onm, *knm; - boolean is_acid; + boolean is_acid, named = (name != 0); int kprefix = KILLED_BY_AN, dieroll; char onmbuf[BUFSZ], knmbuf[BUFSZ]; @@ -51,7 +93,7 @@ const char *name; /* if null, then format `*objp' */ kprefix = KILLED_BY; /* killer_name supplies "an" if warranted */ } else { knm = name; - /* [perhaps ought to check for plural here to] */ + /* [perhaps ought to check for plural here too] */ if (!strncmpi(name, "the ", 4) || !strncmpi(name, "an ", 3) || !strncmpi(name, "a ", 2)) kprefix = KILLED_BY; @@ -62,7 +104,7 @@ const char *name; /* if null, then format `*objp' */ is_acid = (obj && obj->otyp == ACID_VENOM); if (u.uac + tlev <= (dieroll = rnd(20))) { - ++mesg_given; + ++gm.mesg_given; if (Blind || !flags.verbose) { pline("It misses."); } else if (u.uac + tlev <= dieroll - 2) { @@ -80,10 +122,19 @@ const char *name; /* if null, then format `*objp' */ if (is_acid && Acid_resistance) { pline("It doesn't seem to hurt you."); + monstseesu(M_SEEN_ACID); + } else if (obj && stone_missile(obj) + && passes_rocks(gy.youmonst.data)) { + /* use 'named' as an approximation for "hitting from above"; + we avoid "passes through you" for horizontal flight path + because missile stops and that wording would suggest that + it should keep going */ + pline("It %s you.", + named ? "passes harmlessly through" : "doesn't harm"); } else if (obj && obj->oclass == POTION_CLASS) { /* an explosion which scatters objects might hit hero with one (potions deliberately thrown at hero are handled by m_throw) */ - potionhit(&youmonst, obj, POTHIT_OTHER_THROW); + potionhit(&gy.youmonst, obj, POTHIT_OTHER_THROW); *objp = obj = 0; /* potionhit() uses up the potion */ } else { if (obj && objects[obj->otyp].oc_material == SILVER @@ -92,8 +143,10 @@ const char *name; /* if null, then format `*objp' */ pline_The("silver sears your flesh!"); exercise(A_CON, FALSE); } - if (is_acid) + if (is_acid) { pline("It burns!"); + monstunseesu(M_SEEN_ACID); + } losehp(dam, knm, kprefix); /* acid damage */ exercise(A_STR, FALSE); } @@ -103,63 +156,52 @@ const char *name; /* if null, then format `*objp' */ /* Be sure this corresponds with what happens to player-thrown objects in * dothrow.c (for consistency). --KAA - * Returns 0 if object still exists (not destroyed). + * Returns FALSE if object still exists (not destroyed). */ -STATIC_OVL int -drop_throw(obj, ohit, x, y) -register struct obj *obj; -boolean ohit; -int x, y; +staticfn boolean +drop_throw( + struct obj *obj, + boolean ohit, + coordxy x, + coordxy y) { - int retvalu = 1; - int create; - struct monst *mtmp; - struct trap *t; + boolean broken; if (obj->otyp == CREAM_PIE || obj->oclass == VENOM_CLASS - || (ohit && obj->otyp == EGG)) - create = 0; - else if (ohit && (is_multigen(obj) || obj->otyp == ROCK)) - create = !rn2(3); - else - create = 1; - - if (create && !((mtmp = m_at(x, y)) != 0 && mtmp->mtrapped - && (t = t_at(x, y)) != 0 - && is_pit(t->ttyp))) { - int objgone = 0; + || (ohit && obj->otyp == EGG)) { + broken = TRUE; + } else { + broken = (ohit && should_mulch_missile(obj)); + } + if (broken) { + delobj(obj); + } else { if (down_gate(x, y) != -1) - objgone = ship_object(obj, x, y, FALSE); - if (!objgone) { - if (!flooreffects(obj, x, y, "fall")) { + broken = ship_object(obj, x, y, FALSE); + if (!broken) { + struct monst *mtmp = m_at(x, y); + if (!(broken = flooreffects(obj, x, y, "fall"))) { place_object(obj, x, y); - if (!mtmp && x == u.ux && y == u.uy) - mtmp = &youmonst; + if (!mtmp && u_at(x, y)) + mtmp = &gy.youmonst; if (mtmp && ohit) passive_obj(mtmp, obj, (struct attack *) 0); stackobj(obj); - retvalu = 0; } } - } else - obfree(obj, (struct obj *) 0); - return retvalu; + } + gt.thrownobj = 0; + return broken; } -/* The monster that's being shot at when one monster shoots at another */ -STATIC_OVL struct monst *target = 0; -/* The monster that's doing the shooting/throwing */ -STATIC_OVL struct monst *archer = 0; - /* calculate multishot volley count for mtmp throwing otmp (if not ammo) or shooting otmp with mwep (if otmp is ammo and mwep appropriate launcher) */ -STATIC_OVL int -monmulti(mtmp, otmp, mwep) -struct monst *mtmp; -struct obj *otmp, *mwep; +staticfn int +monmulti( + struct monst *mtmp, + struct obj *otmp, struct obj *mwep) { - int skill = (int) objects[otmp->otyp].oc_skill; int multishot = 1; if (otmp->quan > 1L /* no point checking if there's only 1 */ @@ -193,44 +235,18 @@ struct obj *otmp, *mwep; if (ammo_and_launcher(otmp, mwep) && mwep->spe > 1) multishot += (long) rounddiv(mwep->spe, 3); /* Some randomness */ - multishot = (long) rnd((int) multishot); + multishot = rnd((int) multishot); /* class bonus */ - switch (monsndx(mtmp->data)) { - case PM_CAVEMAN: /* give bonus for low-tech gear */ - if (skill == -P_SLING || skill == P_SPEAR) - multishot++; - break; - case PM_MONK: /* allow higher volley count */ - if (skill == -P_SHURIKEN) - multishot++; - break; - case PM_RANGER: - if (skill != P_DAGGER) - multishot++; - break; - case PM_ROGUE: - if (skill == P_DAGGER) - multishot++; - break; - case PM_NINJA: - if (skill == -P_SHURIKEN || skill == -P_DART) - multishot++; - /*FALLTHRU*/ - case PM_SAMURAI: - if (otmp->otyp == YA && mwep->otyp == YUMI) - multishot++; - break; - default: - break; - } + multishot += multishot_class_bonus(monsndx(mtmp->data), otmp, mwep); + /* racial bonus */ if ((is_elf(mtmp->data) && otmp->otyp == ELVEN_ARROW - && mwep->otyp == ELVEN_BOW) + && mwep && mwep->otyp == ELVEN_BOW) || (is_orc(mtmp->data) && otmp->otyp == ORCISH_ARROW - && mwep->otyp == ORCISH_BOW) + && mwep && mwep->otyp == ORCISH_BOW) || (is_gnome(mtmp->data) && otmp->otyp == CROSSBOW_BOLT - && mwep->otyp == CROSSBOW)) + && mwep && mwep->otyp == CROSSBOW)) multishot++; } @@ -242,19 +258,18 @@ struct obj *otmp, *mwep; } /* mtmp throws otmp, or shoots otmp with mwep, at hero or at monster mtarg */ -STATIC_OVL void -monshoot(mtmp, otmp, mwep) -struct monst *mtmp; -struct obj *otmp, *mwep; +staticfn void +monshoot(struct monst *mtmp, struct obj *otmp, struct obj *mwep) { - struct monst *mtarg = target; + struct monst *mtarg = gm.mtarget; int dm = distmin(mtmp->mx, mtmp->my, mtarg ? mtarg->mx : mtmp->mux, mtarg ? mtarg->my : mtmp->muy), multishot = monmulti(mtmp, otmp, mwep); - /* - * Caller must have called linedup() to set up tbx, tby. - */ + + /* + * Caller must have called linedup() to set up . + */ if (canseemon(mtmp)) { const char *onm; @@ -270,62 +285,65 @@ struct obj *otmp, *mwep; onm = singular(otmp, xname); onm = obj_is_pname(otmp) ? the(onm) : an(onm); } - m_shot.s = ammo_and_launcher(otmp, mwep) ? TRUE : FALSE; - Strcpy(trgbuf, mtarg ? mon_nam(mtarg) : ""); - if (!strcmp(trgbuf, "it")) - Strcpy(trgbuf, humanoid(mtmp->data) ? "someone" : something); + gm.m_shot.s = ammo_and_launcher(otmp, mwep) ? TRUE : FALSE; + Strcpy(trgbuf, mtarg ? some_mon_nam(mtarg) : ""); + set_msg_xy(mtmp->mx, mtmp->my); pline("%s %s %s%s%s!", Monnam(mtmp), - m_shot.s ? "shoots" : "throws", onm, + gm.m_shot.s ? "shoots" : "throws", onm, mtarg ? " at " : "", trgbuf); - m_shot.o = otmp->otyp; + gm.m_shot.o = otmp->otyp; } else { - m_shot.o = STRANGE_OBJECT; /* don't give multishot feedback */ + gm.m_shot.o = STRANGE_OBJECT; /* don't give multishot feedback */ } - m_shot.n = multishot; - for (m_shot.i = 1; m_shot.i <= m_shot.n; m_shot.i++) { - m_throw(mtmp, mtmp->mx, mtmp->my, sgn(tbx), sgn(tby), dm, otmp); + gm.m_shot.n = multishot; + for (gm.m_shot.i = 1; gm.m_shot.i <= gm.m_shot.n; gm.m_shot.i++) { + m_throw(mtmp, mtmp->mx, mtmp->my, sgn(gt.tbx), sgn(gt.tby), dm, otmp); /* conceptually all N missiles are in flight at once, but if mtmp gets killed (shot kills adjacent gas spore and triggers explosion, perhaps), inventory will be dropped and otmp might go away via merging into another stack */ - if (DEADMONSTER(mtmp) && m_shot.i < m_shot.n) + if (DEADMONSTER(mtmp) && gm.m_shot.i < gm.m_shot.n) /* cancel pending shots (perhaps ought to give a message here since we gave one above about throwing/shooting N missiles) */ break; /* endmultishot(FALSE); */ } - /* reset 'm_shot' */ - m_shot.n = m_shot.i = 0; - m_shot.o = STRANGE_OBJECT; - m_shot.s = FALSE; + /* reset 'gm.m_shot' */ + gm.m_shot.n = gm.m_shot.i = 0; + gm.m_shot.o = STRANGE_OBJECT; + gm.m_shot.s = FALSE; } /* an object launched by someone/thing other than player attacks a monster; - return 1 if the object has stopped moving (hit or its range used up) */ -int -ohitmon(mtmp, otmp, range, verbose) -struct monst *mtmp; /* accidental target, located at */ -struct obj *otmp; /* missile; might be destroyed by drop_throw */ -int range; /* how much farther will object travel if it misses; - use -1 to signify to keep going even after hit, - unless it's gone (used for rolling_boulder_traps) */ -boolean verbose; /* give message(s) even when you can't see what happened */ + return 1 if the object has stopped moving (hit or its range used up); + can anger the monster, if this happened due to hero (eg. exploding + bag of holding throwing the items) */ +boolean +ohitmon( + struct monst *mtmp, /* accidental target, located at */ + struct obj *otmp, /* missile; might be destroyed by drop_throw */ + int range, /* how much farther will object travel if it misses; + * use -1 to signify to keep going even after hit, + * unless it's gone (for rolling_boulder_traps) */ + boolean verbose) /* give messages even when you can't see what happened */ { int damage, tmp; - boolean vis, ismimic; - int objgone = 1; - struct obj *mon_launcher = archer ? MON_WEP(archer) : NULL; + boolean vis, ismimic, objgone; + struct obj *mon_launcher = gm.marcher ? MON_WEP(gm.marcher) : NULL; - notonhead = (bhitpos.x != mtmp->mx || bhitpos.y != mtmp->my); + /* assert(otmp != NULL); */ + gn.notonhead = (gb.bhitpos.x != mtmp->mx || gb.bhitpos.y != mtmp->my); ismimic = M_AP_TYPE(mtmp) && M_AP_TYPE(mtmp) != M_AP_MONSTER; - vis = cansee(bhitpos.x, bhitpos.y); + vis = cansee(gb.bhitpos.x, gb.bhitpos.y); + if (vis) + observe_object(otmp); tmp = 5 + find_mac(mtmp) + omon_adj(mtmp, otmp, FALSE); /* High level monsters will be more likely to hit */ /* This check applies only if this monster is the target * the archer was aiming at. */ - if (archer && target == mtmp) { - if (archer->m_lev > 5) - tmp += archer->m_lev - 5; + if (gm.marcher && gm.mtarget == mtmp) { + if (gm.marcher->m_lev > 5) + tmp += gm.marcher->m_lev - 5; if (mon_launcher && mon_launcher->oartifact) tmp += spec_abon(mon_launcher, mtmp); } @@ -333,7 +351,7 @@ boolean verbose; /* give message(s) even when you can't see what happened */ if (!ismimic) { if (vis) miss(distant_name(otmp, mshot_xname), mtmp); - else if (verbose && !target) + else if (verbose && !gm.mtarget) pline("It is missed."); } if (!range) { /* Last position; object drops */ @@ -344,13 +362,14 @@ boolean verbose; /* give message(s) even when you can't see what happened */ if (ismimic) seemimic(mtmp); mtmp->msleeping = 0; - if (vis) - otmp->dknown = 1; /* probably thrown by a monster rather than 'other', but the distinction only matters when hitting the hero */ potionhit(mtmp, otmp, POTHIT_OTHER_THROW); return 1; } else { + int material = objects[otmp->otyp].oc_material; + boolean harmless = (stone_missile(otmp) && passes_rocks(mtmp->data)); + damage = dmgval(otmp, mtmp); if (otmp->otyp == ACID_VENOM && resists_acid(mtmp)) damage = 0; @@ -361,13 +380,23 @@ boolean verbose; /* give message(s) even when you can't see what happened */ if (ismimic) seemimic(mtmp); mtmp->msleeping = 0; + Soundeffect(se_splat_egg, 35); if (vis) { - if (otmp->otyp == EGG) + if (otmp->otyp == EGG) { pline("Splat! %s is hit with %s egg!", Monnam(mtmp), - otmp->known ? an(mons[otmp->corpsenm].mname) : "an"); - else - hit(distant_name(otmp, mshot_xname), mtmp, exclam(damage)); - } else if (verbose && !target) + otmp->known ? an(mons[otmp->corpsenm].pmnames[NEUTRAL]) + : "an"); + } else { + char how[BUFSZ]; + + if (!harmless) + Strcpy(how, exclam(damage)); /* "!" or "." */ + else + Sprintf(how, " but passes harmlessly through %.9s.", + mhim(mtmp)); + hit(distant_name(otmp, mshot_xname), mtmp, how); + } + } else if (verbose && !gm.mtarget) pline("%s%s is hit%s", (otmp->otyp == EGG) ? "Splat! " : "", Monnam(mtmp), exclam(damage)); @@ -386,8 +415,7 @@ boolean verbose; /* give message(s) even when you can't see what happened */ } } } - if (objects[otmp->otyp].oc_material == SILVER - && mon_hates_silver(mtmp)) { + if (material == SILVER && mon_hates_silver(mtmp)) { boolean flesh = (!noncorporeal(mtmp->data) && !amorphous(mtmp->data)); @@ -398,38 +426,39 @@ boolean verbose; /* give message(s) even when you can't see what happened */ if (flesh) /* s_suffix returns a modifiable buffer */ m_name = strcat(s_suffix(m_name), " flesh"); pline_The("silver sears %s!", m_name); - } else if (verbose && !target) { + } else if (verbose && !gm.mtarget) { pline("%s is seared!", flesh ? "Its flesh" : "It"); } } if (otmp->otyp == ACID_VENOM && cansee(mtmp->mx, mtmp->my)) { if (resists_acid(mtmp)) { - if (vis || (verbose && !target)) + if (vis || (verbose && !gm.mtarget)) pline("%s is unaffected.", Monnam(mtmp)); } else { if (vis) pline_The("%s burns %s!", hliquid("acid"), mon_nam(mtmp)); - else if (verbose && !target) + else if (verbose && !gm.mtarget) pline("It is burned!"); } } if (otmp->otyp == EGG && touch_petrifies(&mons[otmp->corpsenm])) { - if (!munstone(mtmp, TRUE)) - minstapetrify(mtmp, TRUE); + if (!munstone(mtmp, FALSE)) + minstapetrify(mtmp, FALSE); if (resists_ston(mtmp)) damage = 0; } - if (!DEADMONSTER(mtmp)) { /* might already be dead (if petrified) */ + /* might already be dead (if petrified) */ + if (!harmless && !DEADMONSTER(mtmp)) { mtmp->mhp -= damage; if (DEADMONSTER(mtmp)) { - if (vis || (verbose && !target)) + if (vis || (verbose && !gm.mtarget)) pline("%s is %s!", Monnam(mtmp), (nonliving(mtmp->data) || is_vampshifter(mtmp) || !canspotmon(mtmp)) ? "destroyed" : "killed"); /* don't blame hero for unknown rolling boulder trap */ - if (!context.mon_moving && (otmp->otyp != BOULDER - || range >= 0 || otmp->otrapped)) + if (!svc.context.mon_moving + && (otmp->otyp != BOULDER || range >= 0 || otmp->otrapped)) xkilled(mtmp, XKILL_NOMSG); else mondied(mtmp); @@ -444,7 +473,14 @@ boolean verbose; /* give message(s) even when you can't see what happened */ : AT_WEAP), otmp)) { if (vis && mtmp->mcansee) - pline("%s is blinded by %s.", Monnam(mtmp), the(xname(otmp))); + /* shorten object name to reduce redundancy in the + two message [first via hit() above] sequence: + "The {splash of venom,cream pie} hits ." + " is blinded by the {venom,pie}." */ + pline("%s is blinded by %s.", Monnam(mtmp), + the((otmp->oclass == VENOM_CLASS) ? "venom" + : (otmp->otyp == CREAM_PIE) ? "pie" + : xname(otmp))); /* catchall; not used */ mtmp->mcansee = 0; tmp = (int) mtmp->mblinded + rnd(25) + 20; if (tmp > 127) @@ -452,48 +488,107 @@ boolean verbose; /* give message(s) even when you can't see what happened */ mtmp->mblinded = tmp; } - objgone = drop_throw(otmp, 1, bhitpos.x, bhitpos.y); + if (!DEADMONSTER(mtmp) && !svc.context.mon_moving) + setmangry(mtmp, TRUE); + + objgone = drop_throw(otmp, 1, gb.bhitpos.x, gb.bhitpos.y); if (!objgone && range == -1) { /* special case */ obj_extract_self(otmp); /* free it for motion again */ - return 0; + return FALSE; } - return 1; + return TRUE; + } + return FALSE; +} + +/* hero catches gem thrown by mon iff poly'd into unicorn; might drop it */ +staticfn boolean +ucatchgem( + struct obj *gem, /* caller has verified gem->oclass */ + struct monst *mon) +{ + /* won't catch rock or gray stone; catch (then drop) worthless glass */ + if (gem->otyp <= LAST_GLASS_GEM && is_unicorn(gy.youmonst.data)) { + char *gem_xname = xname(gem), + *mon_s_name = s_suffix(mon_nam(mon)); + + if (gem->otyp >= FIRST_GLASS_GEM) { + You("catch the %s.", gem_xname); + You("are not interested in %s junk.", mon_s_name); + makeknown(gem->otyp); + dropy(gem); + } else { + You("accept %s gift in the spirit in which it was intended.", + mon_s_name); + (void) hold_another_object(gem, "You catch, but drop, %s.", + gem_xname, "You catch:"); + } + return TRUE; } - return 0; + return FALSE; +} + +/* hero may catch thrown obj. it is added to inventory, if possible */ +staticfn boolean +u_catch_thrown_obj(struct obj *otmp) +{ + int catch_chance = 100 - ACURR(A_DEX) + - ((Role_if(PM_MONK) || Role_if(PM_ROGUE)) ? 20 : 0); + + if (!Blind && !Confusion && !Stunned && !Fumbling + && otmp->oclass != VENOM_CLASS + && !nohands(gy.youmonst.data) && freehand() + && calc_capacity(otmp->owt) <= SLT_ENCUMBER && !rn2(catch_chance)) { + char buf[BUFSZ]; + + Snprintf(buf, BUFSZ, "You catch the %s!", simpleonames(otmp)); + (void) hold_another_object(otmp, "You catch, but drop, the %s.", + simpleonames(otmp), buf); + return TRUE; + } + return FALSE; } -#define MT_FLIGHTCHECK(pre) \ - (/* missile hits edge of screen */ \ - !isok(bhitpos.x + dx, bhitpos.y + dy) \ - /* missile hits the wall */ \ - || IS_ROCK(levl[bhitpos.x + dx][bhitpos.y + dy].typ) \ - /* missile hit closed door */ \ - || closed_door(bhitpos.x + dx, bhitpos.y + dy) \ - /* missile might hit iron bars */ \ - /* the random chance for small objects hitting bars is */ \ - /* skipped when reaching them at point blank range */ \ - || (levl[bhitpos.x + dx][bhitpos.y + dy].typ == IRONBARS \ - && hits_bars(&singleobj, \ - bhitpos.x, bhitpos.y, \ - bhitpos.x + dx, bhitpos.y + dy, \ - ((pre) ? 0 : !rn2(5)), 0)) \ - /* Thrown objects "sink" */ \ - || (!(pre) && IS_SINK(levl[bhitpos.x][bhitpos.y].typ))) +#define MT_FLIGHTCHECK(pre,forcehit) \ + (/* missile hits edge of screen */ \ + !isok(gb.bhitpos.x + dx, gb.bhitpos.y + dy) \ + /* missile hits the wall */ \ + || IS_OBSTRUCTED(levl[gb.bhitpos.x + dx][gb.bhitpos.y + dy].typ) \ + /* missile hit closed door */ \ + || closed_door(gb.bhitpos.x + dx, gb.bhitpos.y + dy) \ + /* missile might hit iron bars */ \ + /* the random chance for small objects hitting bars is */ \ + /* skipped when reaching them at point blank range */ \ + || (levl[gb.bhitpos.x + dx][gb.bhitpos.y + dy].typ == IRONBARS \ + && hits_bars(&singleobj, \ + gb.bhitpos.x, gb.bhitpos.y, \ + gb.bhitpos.x + dx, gb.bhitpos.y + dy, \ + ((pre) ? 0 : forcehit), 0)) \ + /* Thrown objects "sink" */ \ + || (!(pre) && IS_SINK(levl[gb.bhitpos.x][gb.bhitpos.y].typ)) \ + ) void -m_throw(mon, x, y, dx, dy, range, obj) -struct monst *mon; /* launching monster */ -int x, y, dx, dy, range; /* launch point, direction, and range */ -struct obj *obj; /* missile (or stack providing it) */ +m_throw( + struct monst *mon, /* launching monster */ + coordxy x, coordxy y, /* launch point */ + coordxy dx, coordxy dy, /* direction */ + int range, /* maximum distance */ + struct obj *obj) /* missile (or stack providing it) */ { struct monst *mtmp; struct obj *singleobj; + boolean forcehit; char sym = obj->oclass; int hitu = 0, oldumort, blindinc = 0; + const struct throw_and_return_weapon *arw = autoreturn_weapon(obj); + boolean tethered_weapon = + (obj == MON_WEP(mon) && arw && arw->tethered != 0), + return_flightpath = FALSE; - bhitpos.x = x; - bhitpos.y = y; - notonhead = FALSE; /* reset potentially stale value */ + gb.bhitpos.x = x; + gb.bhitpos.y = y; + gn.notonhead = FALSE; /* reset potentially stale value */ if (obj->quan == 1L) { /* @@ -502,12 +597,12 @@ struct obj *obj; /* missile (or stack providing it) */ * with 0 daggers? (This caused the infamous 2^32-1 orcish * dagger bug). * - * VENOM is not in minvent - it should already be OBJ_FREE. + * VENOM is not in minvent--it should already be OBJ_FREE. * The extract below does nothing. */ - /* not possibly_unwield, which checks the object's */ - /* location, not its existence */ + /* not possibly_unwield(), which checks the object's location, + not its existence */ if (MON_WEP(mon) == obj) setmnotwielded(mon, obj); obj_extract_self(obj); @@ -517,8 +612,12 @@ struct obj *obj; /* missile (or stack providing it) */ singleobj = splitobj(obj, 1L); obj_extract_self(singleobj); } + /* global pointer for missile object in OBJ_FREE state */ + gt.thrownobj = singleobj; - singleobj->owornmask = 0; /* threw one of multiple weapons in hand? */ + singleobj->owornmask = 0L; /* threw one of multiple weapons in hand? */ + if (!canseemon(mon)) + clear_dknown(singleobj); /* singleobj->dknown = 0; */ if ((singleobj->cursed || singleobj->greased) && (dx || dy) && !rn2(7)) { if (canseemon(mon) && flags.verbose) { @@ -532,27 +631,52 @@ struct obj *obj; /* missile (or stack providing it) */ dy = rn2(3) - 1; /* check validity of new direction */ if (!dx && !dy) { - (void) drop_throw(singleobj, 0, bhitpos.x, bhitpos.y); + (void) drop_throw(singleobj, 0, gb.bhitpos.x, gb.bhitpos.y); return; } } - if (MT_FLIGHTCHECK(TRUE)) { - (void) drop_throw(singleobj, 0, bhitpos.x, bhitpos.y); + if (MT_FLIGHTCHECK(TRUE, 0)) { + (void) drop_throw(singleobj, 0, gb.bhitpos.x, gb.bhitpos.y); return; } - mesg_given = 0; /* a 'missile misses' message has not yet been shown */ + gm.mesg_given = 0; /* a 'missile misses' message has not yet been shown */ /* Note: drop_throw may destroy singleobj. Since obj must be destroyed * early to avoid the dagger bug, anyone who modifies this code should * be careful not to use either one after it's been freed. */ - if (sym) - tmp_at(DISP_FLASH, obj_to_glyph(singleobj, rn2_on_display_rng)); + if (sym) { + if (!tethered_weapon) { + tmp_at(DISP_FLASH, obj_to_glyph(singleobj, rn2_on_display_rng)); + } else { + tmp_at(DISP_TETHER, obj_to_glyph(singleobj, rn2_on_display_rng)); + /* + * Considerations for a tethered object based on throwit()/bhit() : + * - wall of water/lava will stop items, and triggers return. + * - iron bars will stop items, and triggers return. + * - pass harmlessly through shades. + * X stops forward motion at hit monster/hero, triggers return. + * - closed door will stop item's forward motion, triggers return. + * - sinks stop forward motion, triggers fall, then return. + * - object can get tangled in a web, no return (tether snaps?). + * On return: + * X rn2(100) chance of returning to thrower's location. + * X if impaired and rn2(100) == 0, + * -50/50 chance of landing on the ground. + * -50/50 chance of hitting the thrower and causing + * rnd(3) damage. + * + */ + } + } while (range-- > 0) { /* Actually the loop is always exited by break */ - bhitpos.x += dx; - bhitpos.y += dy; - mtmp = m_at(bhitpos.x, bhitpos.y); + singleobj->ox = gb.bhitpos.x += dx; + singleobj->oy = gb.bhitpos.y += dy; + if (cansee(gb.bhitpos.x, gb.bhitpos.y)) + observe_object(singleobj); + + mtmp = m_at(gb.bhitpos.x, gb.bhitpos.y); if (mtmp && shade_miss(mon, mtmp, singleobj, TRUE, TRUE)) { /* if mtmp is a shade and missile passes harmlessly through it, give message and skip it in order to keep going */ @@ -560,39 +684,24 @@ struct obj *obj; /* missile (or stack providing it) */ } else if (mtmp) { if (ohitmon(mtmp, singleobj, range, TRUE)) break; - } else if (bhitpos.x == u.ux && bhitpos.y == u.uy) { - if (multi) + } else if (u_at(gb.bhitpos.x, gb.bhitpos.y)) { + if (gm.multi) nomul(0); - if (singleobj->oclass == GEM_CLASS - && singleobj->otyp <= LAST_GEM + 9 /* 9 glass colors */ - && is_unicorn(youmonst.data)) { - if (singleobj->otyp > LAST_GEM) { - You("catch the %s.", xname(singleobj)); - You("are not interested in %s junk.", - s_suffix(mon_nam(mon))); - makeknown(singleobj->otyp); - dropy(singleobj); - } else { - You( - "accept %s gift in the spirit in which it was intended.", - s_suffix(mon_nam(mon))); - (void) hold_another_object(singleobj, - "You catch, but drop, %s.", - xname(singleobj), - "You catch:"); - } + /* hero might be poly'd into a unicorn */ + if (singleobj->oclass == GEM_CLASS && ucatchgem(singleobj, mon)) break; - } + + if (!tethered_weapon && u_catch_thrown_obj(singleobj)) + break; + if (singleobj->oclass == POTION_CLASS) { - if (!Blind) - singleobj->dknown = 1; - potionhit(&youmonst, singleobj, POTHIT_MONST_THROW); + potionhit(&gy.youmonst, singleobj, POTHIT_MONST_THROW); break; } oldumort = u.umortality; + switch (singleobj->otyp) { - int dam, hitv; case EGG: if (!touch_petrifies(&mons[singleobj->corpsenm])) { impossible("monster throwing egg type %d", @@ -600,30 +709,38 @@ struct obj *obj; /* missile (or stack providing it) */ hitu = 0; break; } - /* fall through */ + FALLTHROUGH; + /*FALLTHRU*/ case CREAM_PIE: case BLINDING_VENOM: hitu = thitu(8, 0, &singleobj, (char *) 0); break; default: - dam = dmgval(singleobj, &youmonst); - hitv = 3 - distmin(u.ux, u.uy, mon->mx, mon->my); - if (hitv < -4) - hitv = -4; - if (is_elf(mon->data) - && objects[singleobj->otyp].oc_skill == P_BOW) { - hitv++; - if (MON_WEP(mon) && MON_WEP(mon)->otyp == ELVEN_BOW) + { + int dam, hitv; + + dam = dmgval(singleobj, &gy.youmonst); + hitv = 3 - distmin(u.ux, u.uy, mon->mx, mon->my); + if (hitv < -4) + hitv = -4; + /* [elves get a shooting bonus, orcs don't...] */ + if (is_elf(mon->data) + && objects[singleobj->otyp].oc_skill == -P_BOW) { hitv++; - if (singleobj->otyp == ELVEN_ARROW) - dam++; + if (MON_WEP(mon) && MON_WEP(mon)->otyp == ELVEN_BOW) + hitv++; + if (singleobj->otyp == ELVEN_ARROW) + dam++; + } + if (bigmonst(gy.youmonst.data)) + hitv++; + hitv += 8 + singleobj->spe; + if (dam < 1) + dam = 1; + if (singleobj->otyp != ACID_VENOM) + dam = Maybe_Half_Phys(dam); + hitu = thitu(hitv, dam, &singleobj, (char *) 0); } - if (bigmonst(youmonst.data)) - hitv++; - hitv += 8 + singleobj->spe; - if (dam < 1) - dam = 1; - hitu = thitu(hitv, dam, &singleobj, (char *) 0); } if (hitu && singleobj->opoisoned && is_poisonable(singleobj)) { char onmbuf[BUFSZ], knmbuf[BUFSZ]; @@ -635,7 +752,7 @@ struct obj *obj; /* missile (or stack providing it) */ poison is limited to attrib loss */ (u.umortality > oldumort) ? 0 : 10, TRUE); } - if (hitu && can_blnd((struct monst *) 0, &youmonst, + if (hitu && can_blnd((struct monst *) 0, &gy.youmonst, (uchar) ((singleobj->otyp == BLINDING_VENOM) ? AT_SPIT : AT_WEAP), @@ -650,7 +767,7 @@ struct obj *obj; /* missile (or stack providing it) */ } else if (singleobj->otyp == BLINDING_VENOM) { const char *eyes = body_part(EYE); - if (eyecount(youmonst.data) != 1) + if (eyecount(gy.youmonst.data) != 1) eyes = makeplural(eyes); /* venom in the eyes */ if (!Blind) @@ -661,54 +778,198 @@ struct obj *obj; /* missile (or stack providing it) */ } if (hitu && singleobj->otyp == EGG) { if (!Stoned && !Stone_resistance - && !(poly_when_stoned(youmonst.data) + && !(poly_when_stoned(gy.youmonst.data) && polymon(PM_STONE_GOLEM))) { make_stoned(5L, (char *) 0, KILLED_BY, ""); } } stop_occupation(); if (hitu) { - (void) drop_throw(singleobj, hitu, u.ux, u.uy); + if (!tethered_weapon) { + (void) drop_throw(singleobj, hitu, u.ux, u.uy); + } else { + /* ready for return journey */ + return_flightpath = TRUE; + } break; } } - if (!range /* reached end of path */ - || MT_FLIGHTCHECK(FALSE)) { + + forcehit = !rn2(5); + if (!range || MT_FLIGHTCHECK(FALSE, forcehit)) { + /* end of path or blocked */ if (singleobj) { /* hits_bars might have destroyed it */ - if (m_shot.n > 1 - && (!mesg_given || bhitpos.x != u.ux || bhitpos.y != u.uy) - && (cansee(bhitpos.x, bhitpos.y) - || (archer && canseemon(archer)))) + /* note: pline(The(missile)) rather than pline_The(missile) + in order to get "Grimtooth" rather than "The Grimtooth" */ + if (range && cansee(gb.bhitpos.x, gb.bhitpos.y) + && IS_SINK(levl[gb.bhitpos.x][gb.bhitpos.y].typ)) + pline("%s %s onto the sink.", The(mshot_xname(singleobj)), + otense(singleobj, Hallucination ? "plop" : "drop")); + else if (gm.m_shot.n > 1 + && (!gm.mesg_given + || gb.bhitpos.x != u.ux || gb.bhitpos.y != u.uy) + && (cansee(gb.bhitpos.x, gb.bhitpos.y) + || (gm.marcher && canseemon(gm.marcher)))) pline("%s misses.", The(mshot_xname(singleobj))); - (void) drop_throw(singleobj, 0, bhitpos.x, bhitpos.y); + if (!tethered_weapon) { + (void) drop_throw(singleobj, 0, + gb.bhitpos.x, gb.bhitpos.y); + } else { + /*ready for return journey */ + return_flightpath = TRUE; + } } break; } - tmp_at(bhitpos.x, bhitpos.y); - delay_output(); + tmp_at(gb.bhitpos.x, gb.bhitpos.y); + nh_delay_output(); } - tmp_at(bhitpos.x, bhitpos.y); - delay_output(); - tmp_at(DISP_END, 0); - mesg_given = 0; /* reset */ + tmp_at(gb.bhitpos.x, gb.bhitpos.y); + nh_delay_output(); + if (arw && return_flightpath) + return_from_mtoss(mon, singleobj, tethered_weapon); + /* mon could be DEADMONSTER now */ + else + tmp_at(DISP_END, 0); + gm.mesg_given = 0; /* reset */ if (blindinc) { u.ucreamed += blindinc; - make_blinded(Blinded + (long) blindinc, FALSE); + make_blinded(BlindedTimeout + (long) blindinc, FALSE); if (!Blind) Your1(vision_clears); } + /* note: all early returns follow drop_throw() which clears thrownobj */ + gt.thrownobj = 0; + return; } #undef MT_FLIGHTCHECK +staticfn void +return_from_mtoss( + struct monst *magr, + struct obj *otmp, + boolean tethered_weapon) +{ + boolean impaired = (magr->mconf || magr->mstun || magr->mblinded), + notcaught = FALSE, hits_thrower = FALSE; + coordxy x = gb.bhitpos.x, y = gb.bhitpos.y; + int made_it_back = rn2(100), dmg = 0; + + if (otmp && made_it_back) { + /* it made it back to thrower's location */ + if (tethered_weapon) { + tmp_at(DISP_END, BACKTRACK); + } else { + int dx = sgn(x - magr->mx), + dy = sgn(y - magr->my); + + if (x != magr->mx || y != magr->my) { + tmp_at(DISP_FLASH, obj_to_glyph(otmp, rn2_on_display_rng)); + while (isok(x, y) && (x != magr->mx || y != magr->my)) { + tmp_at(x, y); + nh_delay_output(); + x -= dx; + y -= dy; + } + tmp_at(DISP_END, 0); + } + } + x = magr->mx; + y = magr->my; + if (!impaired && rn2(100)) { + /* FIXME: this should be moved to struct g (gd these days) */ + static long do_not_annoy = 0; + + if (!do_not_annoy || (svm.moves - do_not_annoy) > 500L) { + pline("%s to %s %s!", Tobjnam(otmp, "return"), + s_suffix(mon_nam(magr)), mbodypart(magr, HAND)); + do_not_annoy = svm.moves; + } + if (otmp) { + add_to_minv(magr, otmp); + if (tethered_weapon) { + magr->mw = otmp; + otmp->owornmask |= W_WEP; + } + } + if (cansee(x, y)) + newsym(x, y); + } else { + boolean mlevitating = FALSE; /* msg future-proofing only */ + + dmg = rn2(2); + if (!dmg) { + if (canseemon(magr)) { + pline("%s back to %s, landing %s %s %s.", + Tobjnam(otmp, "return"), mon_nam(magr), + mlevitating ? "beneath" : "at", mhis(magr), + makeplural(mbodypart(magr, FOOT))); + } else if (!Deaf) { + You_hear("%s land near %s.", Something, mon_nam(magr)); + } + } else { + dmg += rnd(3); + if (canseemon(magr)) { + pline("%s back toward %s, hitting %s %s!", + Tobjnam(otmp, "fly"), mon_nam(magr), + mhis(magr), body_part(ARM)); + } else if (!Deaf) { + You_hear("%s hit %s with a thud!", something, + mon_nam(magr)); + } + hits_thrower = TRUE; + } + notcaught = TRUE; + } + } else { + /* it didn't make it back to thrower's location */ + if (tethered_weapon) + tmp_at(DISP_END, 0); + You_hear("a loud snap!"); + notcaught = TRUE; + } + if (otmp) { + if (hits_thrower) { + if (otmp->oartifact) + (void) artifact_hit((struct monst *) 0, magr, otmp, &dmg, 0); + magr->mhp -= dmg; + if (DEADMONSTER(magr)) + monkilled(magr, canspotmon(magr) ? "" : (char *) 0, AD_PHYS); + } + if (notcaught) { + (void) snuff_candle(otmp); + if (!ship_object(otmp, x, y, FALSE)) { + if (flooreffects(otmp, x, y, "drop")) { + if (cansee(x, y)) + newsym(x, y); + return; + } + place_object(otmp, x, y); + stackobj(otmp); + } + if (!Deaf && !Underwater) { + /* Some sound effects when item lands in water or lava */ + if (is_pool(x, y) || (is_lava(x, y) && !is_flammable(otmp))) { + Soundeffect(se_splash, 50); + pline((weight(otmp) > 9) ? "Splash!" : "Plop!"); + } + } + if (obj_sheds_light(otmp)) + gv.vision_full_recalc = 1; + } + } + if (cansee(x, y)) + newsym(x, y); +} + /* Monster throws item at another monster */ int -thrwmm(mtmp, mtarg) -struct monst *mtmp, *mtarg; +thrwmm(struct monst *mtmp, struct monst *mtarg) { struct obj *otmp, *mwep; - register xchar x, y; + coordxy x, y; boolean ispole; /* Polearms won't be applied by monsters against other monsters */ @@ -716,13 +977,13 @@ struct monst *mtmp, *mtarg; mtmp->weapon_check = NEED_RANGED_WEAPON; /* mon_wield_item resets weapon_check as appropriate */ if (mon_wield_item(mtmp) != 0) - return 0; + return M_ATTK_MISS; } /* Pick a weapon */ otmp = select_rwep(mtmp); if (!otmp) - return 0; + return M_ATTK_MISS; ispole = is_pole(otmp); x = mtmp->mx; @@ -737,53 +998,63 @@ struct monst *mtmp, *mtarg; if (ammo_and_launcher(otmp, mwep) && dist2(mtmp->mx, mtmp->my, mtarg->mx, mtarg->my) > PET_MISSILE_RANGE2) - return 0; /* Out of range */ + return M_ATTK_MISS; /* Out of range */ /* Set target monster */ - target = mtarg; - archer = mtmp; + gm.mtarget = mtarg; + gm.marcher = mtmp; monshoot(mtmp, otmp, mwep); /* multishot shooting or throwing */ - archer = target = (struct monst *) 0; + gm.marcher = gm.mtarget = (struct monst *) 0; nomul(0); - return 1; + return M_ATTK_HIT; } } - return 0; + return M_ATTK_MISS; } /* monster spits substance at monster */ int -spitmm(mtmp, mattk, mtarg) -struct monst *mtmp, *mtarg; -struct attack *mattk; +spitmm(struct monst *mtmp, struct attack *mattk, struct monst *mtarg) { struct obj *otmp; if (mtmp->mcan) { - if (!Deaf) - pline("A dry rattle comes from %s throat.", - s_suffix(mon_nam(mtmp))); - return 0; + if (!Deaf && mdistu(mtmp) < BOLT_LIM * BOLT_LIM) { + if (canspotmon(mtmp)) { + pline("A dry rattle comes from %s throat.", + s_suffix(mon_nam(mtmp))); + } else { + Soundeffect(se_dry_throat_rattle, 50); + You_hear("a dry rattle nearby."); + } + } + return M_ATTK_MISS; } if (m_lined_up(mtarg, mtmp)) { + boolean utarg = (mtarg == &gy.youmonst); + coordxy tx = utarg ? mtmp->mux : mtarg->mx; + coordxy ty = utarg ? mtmp->muy : mtarg->my; + switch (mattk->adtyp) { case AD_BLND: case AD_DRST: otmp = mksobj(BLINDING_VENOM, TRUE, FALSE); break; default: - impossible("bad attack type in spitmu"); + impossible("bad attack type in spitmm"); + FALLTHROUGH; /*FALLTHRU*/ case AD_ACID: otmp = mksobj(ACID_VENOM, TRUE, FALSE); break; } - if (!rn2(BOLT_LIM-distmin(mtmp->mx,mtmp->my,mtarg->mx,mtarg->my))) { + if (!rn2(BOLT_LIM-distmin(mtmp->mx,mtmp->my,tx,ty))) { if (canseemon(mtmp)) pline("%s spits venom!", Monnam(mtmp)); - target = mtarg; - m_throw(mtmp, mtmp->mx, mtmp->my, sgn(tbx), sgn(tby), - distmin(mtmp->mx,mtmp->my,mtarg->mx,mtarg->my), otmp); - target = (struct monst *)0; + if (!utarg) + gm.mtarget = mtarg; + m_throw(mtmp, mtmp->mx, mtmp->my, sgn(gt.tbx), sgn(gt.tby), + distmin(mtmp->mx,mtmp->my,tx,ty), otmp); + gm.mtarget = (struct monst *) 0; nomul(0); /* If this is a pet, it'll get hungry. Minions and @@ -796,42 +1067,71 @@ struct attack *mattk; dog->hungrytime -= 5; } - return 1; + return M_ATTK_HIT; + } else { + obj_extract_self(otmp); + obfree(otmp, (struct obj *) 0); } } - return 0; + return M_ATTK_MISS; +} + +/* Return the name of a breath weapon. If the player is hallucinating, return + * a silly name instead. + * typ is AD_MAGM, AD_FIRE, etc */ +staticfn const char * +breathwep_name(int typ) +{ + if (Hallucination) + return rnd_hallublast(); + + return breathwep[BZ_OFS_AD(typ)]; } /* monster breathes at monster (ranged) */ int -breamm(mtmp, mattk, mtarg) -struct monst *mtmp, *mtarg; -struct attack *mattk; +breamm(struct monst *mtmp, struct attack *mattk, struct monst *mtarg) { - /* if new breath types are added, change AD_ACID to max type */ - int typ = (mattk->adtyp == AD_RBRE) ? rnd(AD_ACID) : mattk->adtyp ; + int typ = get_atkdam_type(mattk->adtyp); + boolean utarget = (mtarg == &gy.youmonst); if (m_lined_up(mtarg, mtmp)) { if (mtmp->mcan) { if (!Deaf) { - if (canseemon(mtmp)) + if (canseemon(mtmp)) { pline("%s coughs.", Monnam(mtmp)); - else + } else { + Soundeffect(se_cough, 100); You_hear("a cough."); + } } - return 0; + return M_ATTK_MISS; } + + /* if we've seen the actual resistance, don't bother, or + if we're close by and they reflect, just jump the player */ + if (utarget && (m_seenres(mtmp, cvt_adtyp_to_mseenres(typ)) + || m_seenres(mtmp, M_SEEN_REFL))) + return M_ATTK_HIT; + if (!mtmp->mspec_used && rn2(3)) { - if ((typ >= AD_MAGM) && (typ <= AD_ACID)) { + if (BZ_VALID_ADTYP(typ)) { if (canseemon(mtmp)) - pline("%s breathes %s!", Monnam(mtmp), breathwep[typ - 1]); - dobuzz((int) (-20 - (typ - 1)), (int) mattk->damn, - mtmp->mx, mtmp->my, sgn(tbx), sgn(tby), FALSE); + pline("%s breathes %s!", + Monnam(mtmp), breathwep_name(typ)); + gb.buzzer = mtmp; + dobuzz(BZ_M_BREATH(BZ_OFS_AD(typ)), (int) mattk->damn, + mtmp->mx, mtmp->my, sgn(gt.tbx), sgn(gt.tby), + utarget, utarget, FALSE); + gb.buzzer = 0; nomul(0); /* breath runs out sometimes. Also, give monster some * cunning; don't breath if the target fell asleep. */ - mtmp->mspec_used = 6 + rn2(18); + if (!utarget || !rn2(3)) + mtmp->mspec_used = 8 + rn2(18); + if (utarget && typ == AD_SLEE && !Sleep_resistance) + mtmp->mspec_used += rnd(20); /* If this is a pet, it'll get hungry. Minions and * spell beings won't hunger */ @@ -844,35 +1144,22 @@ struct attack *mattk; } } else impossible("Breath weapon %d used", typ-1); } else - return 0; + return M_ATTK_MISS; } - return 1; + return M_ATTK_HIT; } - - /* remove an entire item from a monster's inventory; destroy that item */ void -m_useupall(mon, obj) -struct monst *mon; -struct obj *obj; +m_useupall(struct monst *mon, struct obj *obj) { - obj_extract_self(obj); - if (obj->owornmask) { - if (obj == MON_WEP(mon)) - mwepgone(mon); - mon->misc_worn_check &= ~obj->owornmask; - update_mon_intrinsics(mon, obj, FALSE, FALSE); - obj->owornmask = 0L; - } + extract_from_minvent(mon, obj, TRUE, FALSE); obfree(obj, (struct obj *) 0); } /* remove one instance of an item from a monster's inventory */ void -m_useup(mon, obj) -struct monst *mon; -struct obj *obj; +m_useup(struct monst *mon, struct obj *obj) { if (obj->quan > 1L) { obj->quan--; @@ -884,12 +1171,14 @@ struct obj *obj; /* monster attempts ranged weapon attack against player */ void -thrwmu(mtmp) -struct monst *mtmp; +thrwmu(struct monst *mtmp) { struct obj *otmp, *mwep; - xchar x, y; + coordxy x, y; const char *onm; + int rang; + const struct throw_and_return_weapon *arw; + boolean always_toss = FALSE; /* Rearranged beginning so monsters can use polearms not in a line */ if (mtmp->weapon_check == NEED_WEAPON || !MON_WEP(mtmp)) { @@ -908,30 +1197,52 @@ struct monst *mtmp; int dam, hitv; if (otmp != MON_WEP(mtmp)) - return; /* polearm must be wielded */ - if (dist2(mtmp->mx, mtmp->my, mtmp->mux, mtmp->muy) > POLE_LIM - || !couldsee(mtmp->mx, mtmp->my)) + return; /* polearm, aklys must be wielded */ + + /* + * MON_POLE_DIST encompasses knight's move range (5): two spots + * away provided it's not on a straight diagonal, same as skilled + * hero. Using polearm while adjacent is allowed but the verb + * is adjusted from "thrusts" to "bashes", where the hero would + * have to switch from applying a polearm to ordinary melee attack + * to accomplish that. + * + * .545. + * 52125 + * 41014 + * 52125 + * .545. + */ + rang = dist2(mtmp->mx, mtmp->my, mtmp->mux, mtmp->muy); + if (rang > MON_POLE_DIST || !couldsee(mtmp->mx, mtmp->my)) return; /* Out of range, or intervening wall */ if (canseemon(mtmp)) { onm = xname(otmp); - pline("%s thrusts %s.", Monnam(mtmp), + pline_mon(mtmp, "%s %s %s.", Monnam(mtmp), + /* "thrusts" or "swings", or "bashes with" if adjacent */ + mswings_verb(otmp, (rang <= 2) ? TRUE : FALSE), obj_is_pname(otmp) ? the(onm) : an(onm)); } - dam = dmgval(otmp, &youmonst); + dam = dmgval(otmp, &gy.youmonst); hitv = 3 - distmin(u.ux, u.uy, mtmp->mx, mtmp->my); if (hitv < -4) hitv = -4; - if (bigmonst(youmonst.data)) + if (bigmonst(gy.youmonst.data)) hitv++; hitv += 8 + otmp->spe; if (dam < 1) dam = 1; - (void) thitu(hitv, dam, &otmp, (char *) 0); + (void) thitu(hitv, Maybe_Half_Phys(dam), &otmp, (char *) 0); stop_occupation(); return; + } else if ((arw = autoreturn_weapon(otmp)) != 0 && !mwelded(otmp)) { + rang = dist2(mtmp->mx, mtmp->my, mtmp->mux, mtmp->muy); + if (rang > arw->range || !couldsee(mtmp->mx, mtmp->my)) + return; /* Out of range, or intervening wall */ + always_toss = TRUE; } x = mtmp->mx; @@ -943,7 +1254,8 @@ struct monst *mtmp; */ if (!lined_up(mtmp) || (URETREATING(x, y) - && rn2(BOLT_LIM - distmin(x, y, mtmp->mux, mtmp->muy)))) + && (!always_toss + && rn2(BOLT_LIM - distmin(x, y, mtmp->mux, mtmp->muy))))) return; mwep = MON_WEP(mtmp); /* wielded weapon */ @@ -953,108 +1265,91 @@ struct monst *mtmp; /* monster spits substance at you */ int -spitmu(mtmp, mattk) -struct monst *mtmp; -struct attack *mattk; +spitmu(struct monst *mtmp, struct attack *mattk) { - struct obj *otmp; - - if (mtmp->mcan) { - if (!Deaf) - pline("A dry rattle comes from %s throat.", - s_suffix(mon_nam(mtmp))); - return 0; - } - if (lined_up(mtmp)) { - switch (mattk->adtyp) { - case AD_BLND: - case AD_DRST: - otmp = mksobj(BLINDING_VENOM, TRUE, FALSE); - break; - default: - impossible("bad attack type in spitmu"); - /* fall through */ - case AD_ACID: - otmp = mksobj(ACID_VENOM, TRUE, FALSE); - break; - } - if (!rn2(BOLT_LIM - - distmin(mtmp->mx, mtmp->my, mtmp->mux, mtmp->muy))) { - if (canseemon(mtmp)) - pline("%s spits venom!", Monnam(mtmp)); - m_throw(mtmp, mtmp->mx, mtmp->my, sgn(tbx), sgn(tby), - distmin(mtmp->mx, mtmp->my, mtmp->mux, mtmp->muy), otmp); - nomul(0); - return 0; - } else { - obj_extract_self(otmp); - obfree(otmp, (struct obj *) 0); - } - } - return 0; + return spitmm(mtmp, mattk, &gy.youmonst); } /* monster breathes at you (ranged) */ int -breamu(mtmp, mattk) -struct monst *mtmp; -struct attack *mattk; +breamu(struct monst *mtmp, struct attack *mattk) { - /* if new breath types are added, change AD_ACID to max type */ - int typ = (mattk->adtyp == AD_RBRE) ? rnd(AD_ACID) : mattk->adtyp; + return breamm(mtmp, mattk, &gy.youmonst); +} - if (lined_up(mtmp)) { - if (mtmp->mcan) { - if (!Deaf) { - if (canseemon(mtmp)) - pline("%s coughs.", Monnam(mtmp)); - else - You_hear("a cough."); - } - return 0; - } - if (!mtmp->mspec_used && rn2(3)) { - if ((typ >= AD_MAGM) && (typ <= AD_ACID)) { - if (canseemon(mtmp)) - pline("%s breathes %s!", Monnam(mtmp), - breathwep[typ - 1]); - buzz((int) (-20 - (typ - 1)), (int) mattk->damn, mtmp->mx, - mtmp->my, sgn(tbx), sgn(tby)); - nomul(0); - /* breath runs out sometimes. Also, give monster some - * cunning; don't breath if the player fell asleep. - */ - if (!rn2(3)) - mtmp->mspec_used = 10 + rn2(20); - if (typ == AD_SLEE && !Sleep_resistance) - mtmp->mspec_used += rnd(20); - } else - impossible("Breath weapon %d used", typ - 1); - } +/* return TRUE if terrain at x,y blocks linedup checks */ +staticfn boolean +blocking_terrain(coordxy x, coordxy y) +{ + if (!isok(x, y) || IS_OBSTRUCTED(levl[x][y].typ) || closed_door(x, y) + || is_waterwall(x, y) || levl[x][y].typ == LAVAWALL) + return TRUE; + return FALSE; +} + +/* Move from (ax,ay) to (bx,by), but only if distance is up to BOLT_LIM + and only in straight line or diagonal, calling fnc for each step. + Stops if fnc return TRUE, or if step was blocked by wall or closed door. + Returns TRUE if fnc returned TRUE. */ +boolean +linedup_callback( + coordxy ax, + coordxy ay, + coordxy bx, + coordxy by, + boolean (*fnc)(coordxy, coordxy)) +{ + int dx, dy; + + /* These two values are set for use after successful return. */ + gt.tbx = ax - bx; + gt.tby = ay - by; + + /* sometimes displacement makes a monster think that you're at its + own location; prevent it from throwing and zapping in that case */ + if (!gt.tbx && !gt.tby) + return FALSE; + + /* straight line, orthogonal to the map or diagonal */ + if ((!gt.tbx || !gt.tby || abs(gt.tbx) == abs(gt.tby)) + && distmin(gt.tbx, gt.tby, 0, 0) < BOLT_LIM) { + dx = sgn(ax - bx), dy = sgn(ay - by); + do { + /* is guaranteed to eventually converge with */ + bx += dx, by += dy; + if (blocking_terrain(bx, by)) + return FALSE; + if ((*fnc)(bx, by)) + return TRUE; + } while (bx != ax || by != ay); } - return 1; + return FALSE; } boolean -linedup(ax, ay, bx, by, boulderhandling) -register xchar ax, ay, bx, by; -int boulderhandling; /* 0=block, 1=ignore, 2=conditionally block */ +linedup( + coordxy ax, + coordxy ay, + coordxy bx, + coordxy by, + int boulderhandling) /* 0=block, 1=ignore, 2=conditionally block */ { int dx, dy, boulderspots; /* These two values are set for use after successful return. */ - tbx = ax - bx; - tby = ay - by; + gt.tbx = ax - bx; + gt.tby = ay - by; /* sometimes displacement makes a monster think that you're at its own location; prevent it from throwing and zapping in that case */ - if (!tbx && !tby) + if (!gt.tbx && !gt.tby) return FALSE; - if ((!tbx || !tby || abs(tbx) == abs(tby)) /* straight line or diagonal */ - && distmin(tbx, tby, 0, 0) < BOLT_LIM) { - if ((ax == u.ux && ay == u.uy) ? (boolean) couldsee(bx, by) - : clear_path(ax, ay, bx, by)) + /* straight line, orthogonal to the map or diagonal */ + if ((!gt.tbx || !gt.tby || abs(gt.tbx) == abs(gt.tby)) + && distmin(gt.tbx, gt.tby, 0, 0) < BOLT_LIM) { + if (u_at(ax, ay) ? (boolean) couldsee(bx, by) + : clear_path(ax, ay, bx, by)) return TRUE; /* don't have line of sight, but might still be lined up if that lack of sight is due solely to boulders */ @@ -1065,7 +1360,7 @@ int boulderhandling; /* 0=block, 1=ignore, 2=conditionally block */ do { /* is guaranteed to eventually converge with */ bx += dx, by += dy; - if (IS_ROCK(levl[bx][by].typ) || closed_door(bx, by)) + if (blocking_terrain(bx, by)) return FALSE; if (sobj_at(BOULDER, bx, by)) ++boulderspots; @@ -1077,88 +1372,138 @@ int boulderhandling; /* 0=block, 1=ignore, 2=conditionally block */ return FALSE; } -STATIC_OVL boolean -m_lined_up(mtarg, mtmp) -struct monst *mtarg, *mtmp; +staticfn int +m_lined_up(struct monst *mtarg, struct monst *mtmp) { - return (linedup(mtarg->mx, mtarg->my, mtmp->mx, mtmp->my, 0)); -} - - -/* is mtmp in position to use ranged attack? */ -boolean -lined_up(mtmp) -register struct monst *mtmp; -{ - boolean ignore_boulders; + boolean utarget = (mtarg == &gy.youmonst); + coordxy tx = utarget ? mtmp->mux : mtarg->mx; + coordxy ty = utarget ? mtmp->muy : mtarg->my; + boolean ignore_boulders = utarget && (throws_rocks(mtmp->data) + || m_carrying(mtmp, WAN_STRIKING)); /* hero concealment usually trumps monst awareness of being lined up */ - if (Upolyd && rn2(25) + if (utarget && Upolyd && rn2(25) && (u.uundetected || (U_AP_TYPE != M_AP_NOTHING && U_AP_TYPE != M_AP_MONSTER))) return FALSE; - ignore_boulders = (throws_rocks(mtmp->data) - || m_carrying(mtmp, WAN_STRIKING)); - return linedup(mtmp->mux, mtmp->muy, mtmp->mx, mtmp->my, - ignore_boulders ? 1 : 2); + /* [no callers care about the 1 vs 2 situation any more] */ + return linedup(tx, ty, mtmp->mx, mtmp->my, + utarget ? (ignore_boulders ? 1 : 2) : 0); +} + + +/* is mtmp in position to use ranged attack on hero? */ +boolean +lined_up(struct monst *mtmp) +{ + return m_lined_up(&gy.youmonst, mtmp) ? TRUE : FALSE; } -/* check if a monster is carrying a particular item */ +/* check if a monster is carrying an item of a particular type */ struct obj * -m_carrying(mtmp, type) -struct monst *mtmp; -int type; +m_carrying(struct monst *mtmp, int type) { - register struct obj *otmp; + struct obj *otmp; - for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj) + for (otmp = (mtmp == &gy.youmonst) ? gi.invent : mtmp->minvent; otmp; + otmp = otmp->nobj) if (otmp->otyp == type) - return otmp; - return (struct obj *) 0; + break; + return otmp; } void -hit_bars(objp, objx, objy, barsx, barsy, your_fault, from_invent) -struct obj **objp; /* *objp will be set to NULL if object breaks */ -int objx, objy, barsx, barsy; -boolean your_fault, from_invent; +hit_bars( + struct obj **objp, /* *objp will be set to NULL if object breaks */ + coordxy objx, coordxy objy, /* hero's (when wielded) or missile's spot */ + coordxy barsx, coordxy barsy, /* adjacent spot where bars are located */ + unsigned breakflags) /* breakage control */ { struct obj *otmp = *objp; int obj_type = otmp->otyp; - boolean unbreakable = (levl[barsx][barsy].wall_info & W_NONDIGGABLE) != 0; + boolean nodissolve = (levl[barsx][barsy].wall_info & W_NONDIGGABLE) != 0, + your_fault = (breakflags & BRK_BY_HERO) != 0, + melee_attk = (breakflags & BRK_MELEE) != 0; + int noise = 0; if (your_fault - ? hero_breaks(otmp, objx, objy, from_invent) + ? hero_breaks(otmp, objx, objy, breakflags) : breaks(otmp, objx, objy)) { *objp = 0; /* object is now gone */ /* breakage makes its own noises */ if (obj_type == POT_ACID) { - if (cansee(barsx, barsy) && !unbreakable) + if (cansee(barsx, barsy) && !nodissolve) { pline_The("iron bars are dissolved!"); - else - You_hear(Hallucination ? "angry snakes!" : "a hissing noise."); - if (!unbreakable) + } else { + Soundeffect(se_angry_snakes, 100); + You_hear(Hallucination ? "angry snakes!" + : "a hissing noise."); + } + if (!nodissolve) dissolve_bars(barsx, barsy); } + } else { + if (!Deaf) { + static enum sound_effect_entries se[] = { + se_zero_invalid, + se_bars_whang, se_bars_whap, se_bars_flapp, + se_bars_clink, se_bars_clonk + }; + static const char *const barsounds[] = { + "", "Whang", "Whap", "Flapp", "Clink", "Clonk" + }; + int bsindx = (obj_type == BOULDER || obj_type == HEAVY_IRON_BALL) + ? 1 + : harmless_missile(otmp) ? 2 + : is_flimsy(otmp) ? 3 + : (otmp->oclass == COIN_CLASS + || objects[obj_type].oc_material == GOLD + || objects[obj_type].oc_material == SILVER) + ? 4 + : SIZE(barsounds) - 1; + + Soundeffect(se[bsindx], 100); + pline("%s!", barsounds[bsindx]); + nhUse(se[bsindx]); + } + if (!(harmless_missile(otmp) || is_flimsy(otmp))) + noise = 4 * 4; + + if (your_fault && (otmp->otyp == WAR_HAMMER + || otmp->otyp == HEAVY_IRON_BALL)) { + /* iron ball isn't a weapon or wep-tool so doesn't use obj->spe; + weight is normally 480 but can be increased by increments + of 160 (scrolls of punishment read while already punished) */ + int spe = ((otmp->otyp == HEAVY_IRON_BALL) /* 3+ for iron ball */ + ? ((int) otmp->owt / WT_IRON_BALL_INCR) + : otmp->spe); + /* chance: used in saving throw for the bars; more likely to + break those when 'chance' is _lower_; acurrstr(): 3..25 */ + int chance = (melee_attk ? 40 : 60) - acurrstr() - spe; + + if (!rn2(max(2, chance))) { + You("break the bars apart!"); + dissolve_bars(barsx, barsy); + noise = noise * 2; + } + } + + if (noise) + wake_nearto(barsx, barsy, noise); } - else if (obj_type == BOULDER || obj_type == HEAVY_IRON_BALL) - pline("Whang!"); - else if (otmp->oclass == COIN_CLASS - || objects[obj_type].oc_material == GOLD - || objects[obj_type].oc_material == SILVER) - pline("Clink!"); - else - pline("Clonk!"); } /* TRUE iff thrown/kicked/rolled object doesn't pass through iron bars */ boolean -hits_bars(obj_p, x, y, barsx, barsy, always_hit, whodidit) -struct obj **obj_p; /* *obj_p will be set to NULL if object breaks */ -int x, y, barsx, barsy; -int always_hit; /* caller can force a hit for items which would fit through */ -int whodidit; /* 1==hero, 0=other, -1==just check whether it'll pass thru */ +hits_bars( + struct obj **obj_p, /* *obj_p will be set to NULL if object breaks */ + coordxy x, coordxy y, + coordxy barsx, coordxy barsy, + int always_hit, /* caller can force a hit for items which would + * fit through */ + int whodidit) /* 1==hero, 0=other, -1==just check whether it + * will pass through */ { struct obj *otmp = *obj_p; int obj_type = otmp->otyp; @@ -1193,7 +1538,7 @@ int whodidit; /* 1==hero, 0=other, -1==just check whether it'll pass thru */ hits = TRUE; else hits = (obj_type == MEAT_STICK - || obj_type == HUGE_CHUNK_OF_MEAT); + || obj_type == ENORMOUS_MEATBALL); break; case SPBOOK_CLASS: case WAND_CLASS: @@ -1206,7 +1551,8 @@ int whodidit; /* 1==hero, 0=other, -1==just check whether it'll pass thru */ } if (hits && whodidit != -1) { - hit_bars(obj_p, x,y, barsx,barsy, whodidit, FALSE); + hit_bars(obj_p, x, y, barsx, barsy, + (whodidit == 1) ? BRK_BY_HERO : 0); } return hits; diff --git a/src/muse.c b/src/muse.c index 2dfb92f3f..1a8e6ea0c 100644 --- a/src/muse.c +++ b/src/muse.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 muse.c $NHDT-Date: 1561053256 2019/06/20 17:54:16 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.97 $ */ +/* NetHack 5.0 muse.c $NHDT-Date: 1770949988 2026/02/12 18:33:08 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.241 $ */ /* Copyright (C) 1990 by Ken Arromdee */ /* NetHack may be freely redistributed. See license for details. */ @@ -8,61 +8,55 @@ #include "hack.h" -boolean m_using = FALSE; - /* Let monsters use magic items. Arbitrary assumptions: Monsters only use * scrolls when they can see, monsters know when wands have 0 charges, * monsters cannot recognize if items are cursed are not, monsters which * are confused don't know not to read scrolls, etc.... */ -STATIC_DCL struct permonst *FDECL(muse_newcham_mon, (struct monst *)); -STATIC_DCL int FDECL(precheck, (struct monst *, struct obj *)); -STATIC_DCL void FDECL(mzapwand, (struct monst *, struct obj *, BOOLEAN_P)); -STATIC_DCL void FDECL(mplayhorn, (struct monst *, struct obj *, BOOLEAN_P)); -STATIC_DCL void FDECL(mreadmsg, (struct monst *, struct obj *)); -STATIC_DCL void FDECL(mquaffmsg, (struct monst *, struct obj *)); -STATIC_DCL boolean FDECL(m_use_healing, (struct monst *)); -STATIC_PTR int FDECL(mbhitm, (struct monst *, struct obj *)); -STATIC_DCL void FDECL(mbhit, (struct monst *, int, - int FDECL((*), (MONST_P, OBJ_P)), - int FDECL((*), (OBJ_P, OBJ_P)), struct obj *)); -STATIC_DCL void FDECL(you_aggravate, (struct monst *)); -STATIC_DCL void FDECL(mon_consume_unstone, (struct monst *, struct obj *, - BOOLEAN_P, BOOLEAN_P)); -STATIC_DCL boolean FDECL(cures_stoning, (struct monst *, struct obj *, - BOOLEAN_P)); -STATIC_DCL boolean FDECL(mcould_eat_tin, (struct monst *)); -STATIC_DCL boolean FDECL(muse_unslime, (struct monst *, struct obj *, - struct trap *, BOOLEAN_P)); -STATIC_DCL int FDECL(cures_sliming, (struct monst *, struct obj *)); -STATIC_DCL boolean FDECL(green_mon, (struct monst *)); - -static struct musable { - struct obj *offensive; - struct obj *defensive; - struct obj *misc; - int has_offense, has_defense, has_misc; - /* =0, no capability; otherwise, different numbers. - * If it's an object, the object is also set (it's 0 otherwise). - */ -} m; -static int trapx, trapy; -static boolean zap_oseen; /* for wands which use mbhitm and are zapped at - * players. We usually want an oseen local to - * the function, but this is impossible since the - * function mbhitm has to be compatible with the - * normal zap routines, and those routines don't - * remember who zapped the wand. */ +staticfn int precheck(struct monst *, struct obj *); +staticfn void mzapwand(struct monst *, struct obj *, boolean) NONNULLPTRS; +staticfn void mplayhorn(struct monst *, struct obj *, boolean) NONNULLPTRS; +staticfn void mreadmsg(struct monst *, struct obj *) NONNULLPTRS; +staticfn void mquaffmsg(struct monst *, struct obj *) NONNULLPTRS; +staticfn boolean m_use_healing(struct monst *); +staticfn boolean m_sees_sleepy_soldier(struct monst *); +staticfn void m_tele(struct monst *, boolean, boolean, int); +staticfn boolean m_next2m(struct monst *); +staticfn void reveal_trap(struct trap *, boolean); +staticfn int mon_escape(struct monst *, boolean); +staticfn boolean linedup_chk_corpse(coordxy, coordxy); +staticfn void m_use_undead_turning(struct monst *, struct obj *); +staticfn boolean hero_behind_chokepoint(struct monst *); +staticfn boolean mon_has_friends(struct monst *); +staticfn boolean mon_likes_objpile_at(struct monst *mtmp, coordxy x, coordxy y) NONNULLARG1; +staticfn int mbhitm(struct monst *, struct obj *); +staticfn void buzz_force_miss(int, int, coordxy, coordxy, int, int); +staticfn boolean fhito_loc(struct obj *obj, coordxy x, coordxy y, + int (*fhito)(OBJ_P, OBJ_P)); +staticfn void mbhit(struct monst *, int, int (*)(MONST_P, OBJ_P), + int (*)(OBJ_P, OBJ_P), struct obj *); +staticfn struct permonst *muse_newcham_mon(struct monst *); +staticfn int mloot_container(struct monst *mon, struct obj *, boolean); +staticfn void you_aggravate(struct monst *); +#if 0 +staticfn boolean necrophiliac(struct obj *, boolean); +#endif +staticfn void mon_consume_unstone(struct monst *, struct obj *, boolean, + boolean); +staticfn boolean cures_stoning(struct monst *, struct obj *, boolean); +staticfn boolean mcould_eat_tin(struct monst *); +staticfn boolean muse_unslime(struct monst *, struct obj *, struct trap *, + boolean); +staticfn int cures_sliming(struct monst *, struct obj *); +staticfn boolean green_mon(struct monst *); /* Any preliminary checks which may result in the monster being unable to use * the item. Returns 0 if nothing happened, 2 if the monster can't do * anything (i.e. it teleported) and 1 if it's dead. */ -STATIC_OVL int -precheck(mon, obj) -struct monst *mon; -struct obj *obj; +staticfn int +precheck(struct monst *mon, struct obj *obj) { boolean vis; @@ -72,19 +66,17 @@ struct obj *obj; if (obj->oclass == POTION_CLASS) { coord cc; - static const char *empty = "The potion turns out to be empty."; - const char *potion_descr; + static const char *const empty = "The potion turns out to be empty."; struct monst *mtmp; - potion_descr = OBJ_DESCR(objects[obj->otyp]); - if (potion_descr && !strcmp(potion_descr, "milky")) { - if (!(mvitals[PM_GHOST].mvflags & G_GONE) - && !rn2(POTION_OCCUPANT_CHANCE(mvitals[PM_GHOST].born))) { + if (objdescr_is(obj, "milky")) { + if (!(svm.mvitals[PM_GHOST].mvflags & G_GONE) + && !rn2(POTION_OCCUPANT_CHANCE(svm.mvitals[PM_GHOST].born))) { if (!enexto(&cc, mon->mx, mon->my, &mons[PM_GHOST])) return 0; mquaffmsg(mon, obj); m_useup(mon, obj); - mtmp = makemon(&mons[PM_GHOST], cc.x, cc.y, NO_MM_FLAGS); + mtmp = makemon(&mons[PM_GHOST], cc.x, cc.y, MM_NOMSG); if (!mtmp) { if (vis) pline1(empty); @@ -95,7 +87,8 @@ struct obj *obj; mon_nam(mon), Hallucination ? rndmonnam(NULL) : (const char *) "ghost"); - pline("%s is frightened to death, and unable to move.", + pline("%s is frightened to death," + " and unable to move.", Monnam(mon)); } paralyze_monst(mon, 3); @@ -103,23 +96,24 @@ struct obj *obj; return 2; } } - if (potion_descr && !strcmp(potion_descr, "smoky") - && !(mvitals[PM_DJINNI].mvflags & G_GONE) - && !rn2(POTION_OCCUPANT_CHANCE(mvitals[PM_DJINNI].born))) { + if (objdescr_is(obj, "smoky") + && !(svm.mvitals[PM_DJINNI].mvflags & G_GONE) + && !rn2(POTION_OCCUPANT_CHANCE(svm.mvitals[PM_DJINNI].born))) { if (!enexto(&cc, mon->mx, mon->my, &mons[PM_DJINNI])) return 0; mquaffmsg(mon, obj); m_useup(mon, obj); - mtmp = makemon(&mons[PM_DJINNI], cc.x, cc.y, NO_MM_FLAGS); + mtmp = makemon(&mons[PM_DJINNI], cc.x, cc.y, MM_NOMSG); if (!mtmp) { if (vis) pline1(empty); } else { if (vis) - pline("In a cloud of smoke, %s emerges!", a_monnam(mtmp)); + pline_mon(mtmp, "In a cloud of smoke, %s emerges!", a_monnam(mtmp)); pline("%s speaks.", vis ? Monnam(mtmp) : Something); /* I suspect few players will be upset that monsters */ /* can't wish for wands of death here.... */ + SetVoice(mtmp, 0, 80, 0); if (rn2(2)) { verbalize("You freed me!"); mtmp->mpeaceful = 1; @@ -141,15 +135,16 @@ struct obj *obj; /* 3.6.1: no Deaf filter; 'if' message doesn't warrant it, 'else' message doesn't need it since You_hear() has one of its own */ if (vis) { - pline("%s zaps %s, which suddenly explodes!", Monnam(mon), + pline_mon(mon, "%s zaps %s, which suddenly explodes!", Monnam(mon), an(xname(obj))); } else { /* same near/far threshold as mzapwand() */ int range = couldsee(mon->mx, mon->my) /* 9 or 5 */ ? (BOLT_LIM + 1) : (BOLT_LIM - 3); + Soundeffect(se_zap_then_explosion, 100); You_hear("a zap and an explosion %s.", - (distu(mon->mx, mon->my) <= range * range) + (mdistu(mon) <= range * range) ? "nearby" : "in the distance"); } m_useup(mon, obj); @@ -158,7 +153,7 @@ struct obj *obj; monkilled(mon, "", AD_RBRE); return 1; } - m.has_defense = m.has_offense = m.has_misc = 0; + gm.m.has_defense = gm.m.has_offense = gm.m.has_misc = 0; /* Only one needed to be set to 0 but the others are harmless */ } return 0; @@ -166,106 +161,144 @@ struct obj *obj; /* when a monster zaps a wand give a message, deduct a charge, and if it isn't directly seen, remove hero's memory of the number of charges */ -STATIC_OVL void -mzapwand(mtmp, otmp, self) -struct monst *mtmp; -struct obj *otmp; -boolean self; +staticfn void +mzapwand( + struct monst *mtmp, + struct obj *otmp, + boolean self) { + if (otmp->spe < 1) { + impossible("Mon zapping wand with %d charges?", otmp->spe); + return; + } if (!canseemon(mtmp)) { int range = couldsee(mtmp->mx, mtmp->my) /* 9 or 5 */ ? (BOLT_LIM + 1) : (BOLT_LIM - 3); - You_hear("a %s zap.", (distu(mtmp->mx, mtmp->my) <= range * range) + Soundeffect(se_zap, 100); + You_hear("a %s zap.", (mdistu(mtmp) <= range * range) ? "nearby" : "distant"); - otmp->known = 0; + unknow_object(otmp); /* hero loses info when unseen obj is used */ } else if (self) { - pline("%s zaps %sself with %s!", Monnam(mtmp), mhim(mtmp), + pline("%s with %s!", + monverbself(mtmp, Monnam(mtmp), "zap", (char *) 0), doname(otmp)); } else { - pline("%s zaps %s!", Monnam(mtmp), an(xname(otmp))); + pline_mon(mtmp, "%s zaps %s!", Monnam(mtmp), an(xname(otmp))); stop_occupation(); } otmp->spe -= 1; } /* similar to mzapwand() but for magical horns (only instrument mons play) */ -STATIC_OVL void -mplayhorn(mtmp, otmp, self) -struct monst *mtmp; -struct obj *otmp; -boolean self; +staticfn void +mplayhorn( + struct monst *mtmp, + struct obj *otmp, + boolean self) { + char *objnamp, objbuf[BUFSZ]; + if (!canseemon(mtmp)) { int range = couldsee(mtmp->mx, mtmp->my) /* 9 or 5 */ ? (BOLT_LIM + 1) : (BOLT_LIM - 3); + Soundeffect(se_horn_being_played, 50); You_hear("a horn being played %s.", - (distu(mtmp->mx, mtmp->my) <= range * range) - ? "nearby" : "in the distance"); - otmp->known = 0; /* hero doesn't know how many charges are left */ - } else { - otmp->dknown = 1; - pline("%s plays a %s directed at %s!", Monnam(mtmp), xname(otmp), - self ? mon_nam_too(mtmp, mtmp) : (char *) "you"); + (mdistu(mtmp) <= range * range) + ? "nearby" : "in the distance"); + unknow_object(otmp); /* hero loses info when unseen obj is used */ + } else if (self) { + observe_object(otmp); + objnamp = xname(otmp); + if (strlen(objnamp) >= QBUFSZ) + objnamp = simpleonames(otmp); + Sprintf(objbuf, "a %s directed at", objnamp); + /* " plays a directed at himself!" */ + pline("%s!", monverbself(mtmp, Monnam(mtmp), "play", objbuf)); makeknown(otmp->otyp); /* (wands handle this slightly differently) */ - if (!self) - stop_occupation(); + } else { + observe_object(otmp); + objnamp = xname(otmp); + if (strlen(objnamp) >= QBUFSZ) + objnamp = simpleonames(otmp); + pline("%s %s %s directed at you!", + /* monverbself() would adjust the verb if hallucination made + subject plural; stick with singular here, at least for now */ + Monnam(mtmp), "plays", an(objnamp)); + makeknown(otmp->otyp); + stop_occupation(); } otmp->spe -= 1; /* use a charge */ } -STATIC_OVL void -mreadmsg(mtmp, otmp) -struct monst *mtmp; -struct obj *otmp; +/* see or hear a monster reading a scroll; + when scroll hasn't been seen, its label is revealed unless hero is deaf */ +staticfn void +mreadmsg(struct monst *mtmp, struct obj *otmp) { - boolean vismon = canseemon(mtmp); char onambuf[BUFSZ]; - short saverole; - unsigned savebknown; + boolean vismon = canseemon(mtmp), + tpindicator = (!vismon && sensemon(mtmp)); if (!vismon && Deaf) return; /* no feedback */ - otmp->dknown = 1; /* seeing or hearing it read reveals its label */ - /* shouldn't be able to hear curse/bless status of unseen scrolls; - for priest characters, bknown will always be set during naming */ - savebknown = otmp->bknown; - saverole = Role_switch; - if (!vismon) { - otmp->bknown = 0; - if (Role_if(PM_PRIEST)) - Role_switch = 0; + observe_object(otmp); /* seeing/hearing scroll read reveals its label */ + Strcpy(onambuf, singular(otmp, vismon ? doname : ansimpleoname)); + + if (vismon) { + /* directly see the monster reading the scroll */ + pline_mon(mtmp, "%s reads %s!", Monnam(mtmp), onambuf); + } else { /* !Deaf, otherwise we wouldn't reach here */ + char blindbuf[BUFSZ]; + boolean similar = same_race(gy.youmonst.data, mtmp->data), + uniqmon = ((mtmp->data->geno & G_UNIQ) != 0 + /* shopkeepers aren't unique monsters but since + they have distinct names, treat them as such */ + || mtmp->isshk), + recognize = (!Hallucination + && (mtmp->meverseen || (similar && !uniqmon))); + /* describe unseen monster accurately when not hallucinating if it + has ever been seen or is the same race as the hero (not yet seen + unique monsters excepted) */ + int mflags = (SUPPRESS_INVISIBLE | SUPPRESS_SADDLE + | (recognize ? SUPPRESS_IT : AUGMENT_IT)); + + if (sensemon(mtmp)) { + tpindicator = TRUE; + } else if (couldsee(mtmp->mx, mtmp->my) && mdistu(mtmp) <= 10 * 10) { + /* monster can't be seen or sensed; hero might be blind or monster + might be at a spot that isn't in view or might be invisible; + remember it if the spot is within line of sight and relatively + close */ + map_invisible(mtmp->mx, mtmp->my); + } + + Snprintf(blindbuf, sizeof blindbuf, "reading %s", onambuf); + strsubst(blindbuf, "reading a scroll labeled", + mtmp->mconf ? "attempting to incant" : "incant"); + You_hear("%s %s.", + x_monnam(mtmp, ARTICLE_A, (char *) 0, mflags, FALSE), + blindbuf); + if (tpindicator) + flash_mon(mtmp); } - Strcpy(onambuf, singular(otmp, doname)); - Role_switch = saverole; - otmp->bknown = savebknown; - - if (vismon) - pline("%s reads %s!", Monnam(mtmp), onambuf); - else - You_hear("%s reading %s.", - x_monnam(mtmp, ARTICLE_A, (char *) 0, - (SUPPRESS_IT | SUPPRESS_INVISIBLE | SUPPRESS_SADDLE), - FALSE), - onambuf); - - if (mtmp->mconf) + if (mtmp->mconf) /* (note: won't get if not seen and hero can't hear) */ pline("Being confused, %s mispronounces the magic words...", vismon ? mon_nam(mtmp) : mhe(mtmp)); } -STATIC_OVL void -mquaffmsg(mtmp, otmp) -struct monst *mtmp; -struct obj *otmp; +staticfn void +mquaffmsg(struct monst *mtmp, struct obj *otmp) { if (canseemon(mtmp)) { - otmp->dknown = 1; - pline("%s drinks %s!", Monnam(mtmp), singular(otmp, doname)); - } else if (!Deaf) + observe_object(otmp); + pline_mon(mtmp, "%s drinks %s!", Monnam(mtmp), singular(otmp, doname)); + } else if (!Deaf) { + Soundeffect(se_mon_chugging_potion, 25); You_hear("a chugging sound."); + } } /* Defines for various types of stuff. The order in which monsters prefer @@ -291,6 +324,7 @@ struct obj *otmp; #define MUSE_UNICORN_HORN 17 #define MUSE_POT_FULL_HEALING 18 #define MUSE_LIZARD_CORPSE 19 +#define MUSE_WAN_UNDEAD_TURNING 20 /* also an offensive item */ /* #define MUSE_INNATE_TPT 9999 * We cannot use this. Since monsters get unlimited teleportation, if they @@ -299,64 +333,155 @@ struct obj *otmp; * that if you polymorph into one you teleport at will. */ -STATIC_OVL boolean -m_use_healing(mtmp) -struct monst *mtmp; +staticfn boolean +m_use_healing(struct monst *mtmp) { - struct obj *obj = 0; + struct obj *obj; + if ((obj = m_carrying(mtmp, POT_FULL_HEALING)) != 0) { - m.defensive = obj; - m.has_defense = MUSE_POT_FULL_HEALING; + gm.m.defensive = obj; + gm.m.has_defense = MUSE_POT_FULL_HEALING; return TRUE; } if ((obj = m_carrying(mtmp, POT_EXTRA_HEALING)) != 0) { - m.defensive = obj; - m.has_defense = MUSE_POT_EXTRA_HEALING; + gm.m.defensive = obj; + gm.m.has_defense = MUSE_POT_EXTRA_HEALING; return TRUE; } if ((obj = m_carrying(mtmp, POT_HEALING)) != 0) { - m.defensive = obj; - m.has_defense = MUSE_POT_HEALING; + gm.m.defensive = obj; + gm.m.has_defense = MUSE_POT_HEALING; return TRUE; } return FALSE; } +/* return TRUE if monster mtmp can see at least one sleeping soldier */ +staticfn boolean +m_sees_sleepy_soldier(struct monst *mtmp) +{ + coordxy x = mtmp->mx, y = mtmp->my; + coordxy xx, yy; + struct monst *mon; + + /* Distance is arbitrary. What we really want to do is + * have the soldier play the bugle when it sees or + * remembers soldiers nearby... + */ + for (xx = x - 3; xx <= x + 3; xx++) + for (yy = y - 3; yy <= y + 3; yy++) { + if (!isok(xx, yy) || (xx == x && yy == y)) + continue; + if ((mon = m_at(xx, yy)) != 0 && is_mercenary(mon->data) + && mon->data != &mons[PM_GUARD] + && helpless(mon)) + return TRUE; + } + return FALSE; +} + +staticfn void +m_tele( + struct monst *mtmp, /* monst that might be teleported */ + boolean vismon, /* can see it */ + boolean oseen, /* have seen the object that triggered this */ + int how) /* type of that object */ +{ + if (tele_restrict(mtmp)) { /* mysterious force... */ + if (vismon && how) /* mentions 'teleport' */ + makeknown(how); + /* monster learns that teleportation isn't useful here */ + if (noteleport_level(mtmp)) + mon_learns_traps(mtmp, TELEP_TRAP); + } else if ((mon_has_amulet(mtmp) || On_W_tower_level(&u.uz)) && !rn2(3)) { + if (vismon) + pline_mon(mtmp, "%s seems disoriented for a moment.", Monnam(mtmp)); + } else { + /* teleport monster 'mtmp' */ + if (how) { + /* teleporation has been triggered by an object */ + if (oseen) + makeknown(how); + (void) rloc(mtmp, RLOC_MSG); + } else { + /* monster is voluntarily entering a teleporation trap; use the + trap instead of rloc() in case it sends 'victim' to a vault */ + mtmp->mx = gt.trapx, mtmp->my = gt.trapy; + (void) mintrap(mtmp, FORCETRAP); + } + } +} + +/* return TRUE if monster mtmp has another monster next to it. + * Called from find_defensive() where it is limited to Is_knox() + * only, otherwise you could trap two monsters next to each other + * in a boulder fort, and they would be happy to stay in there. */ +staticfn boolean +m_next2m(struct monst *mtmp) +{ + coordxy x, y; + struct monst *m2; + + if (DEADMONSTER(mtmp) || mon_offmap(mtmp)) + return FALSE; + + for (x = mtmp->mx - 1; x <= mtmp->mx + 1; x++) + for (y = mtmp->my - 1; y <= mtmp->my + 1; y++) { + if (!isok(x,y)) + continue; + if ((m2 = m_at(x, y)) && m2 != mtmp) + return TRUE; + } + return FALSE; +} + /* Select a defensive item/action for a monster. Returns TRUE iff one is found. */ boolean -find_defensive(mtmp) -struct monst *mtmp; +find_defensive(struct monst *mtmp, boolean tryescape) { - register struct obj *obj = 0; + struct obj *obj; struct trap *t; - int x = mtmp->mx, y = mtmp->my; - boolean stuck = (mtmp == u.ustuck); - boolean immobile = (mtmp->data->mmove == 0); int fraction; + coordxy x = mtmp->mx, y = mtmp->my; + boolean stuck = (mtmp == u.ustuck), + immobile = (mtmp->data->mmove == 0); + stairway *stway; + + gm.m.defensive = (struct obj *) 0; + gm.m.has_defense = 0; if (is_animal(mtmp->data) || mindless(mtmp->data)) return FALSE; - if (dist2(x, y, mtmp->mux, mtmp->muy) > 25) + if (!tryescape && dist2(x, y, mtmp->mux, mtmp->muy) > 25) + return FALSE; + if (tryescape && Is_knox(&u.uz) + && !m_next2u(mtmp) && m_next2m(mtmp)) return FALSE; if (u.uswallow && stuck) return FALSE; - m.defensive = (struct obj *) 0; - m.has_defense = 0; - - /* since unicorn horns don't get used up, the monster would look - * silly trying to use the same cursed horn round after round + /* + * Since unicorn horns don't get used up, the monster would look + * silly trying to use the same cursed horn round after round, + * so skip cursed unicorn horns. + * + * Unicorns use their own horns; they're excluded from inventory + * scanning by nohands(). Ki-rin is depicted in the AD&D Monster + * Manual with same horn as a unicorn, so let it use its horn too. + * is_unicorn() doesn't include it; the class differs and it has + * no interest in gems. */ if (mtmp->mconf || mtmp->mstun || !mtmp->mcansee) { - if (!is_unicorn(mtmp->data) && !nohands(mtmp->data)) { + obj = 0; + if (!nohands(mtmp->data)) { for (obj = mtmp->minvent; obj; obj = obj->nobj) if (obj->otyp == UNICORN_HORN && !obj->cursed) break; } - if (obj || is_unicorn(mtmp->data)) { - m.defensive = obj; - m.has_defense = MUSE_UNICORN_HORN; + if (obj || is_unicorn(mtmp->data) || mtmp->data == &mons[PM_KI_RIN]) { + gm.m.defensive = obj; + gm.m.has_defense = MUSE_UNICORN_HORN; return TRUE; } } @@ -366,8 +491,8 @@ struct monst *mtmp; for (obj = mtmp->minvent; obj; obj = obj->nobj) { if (obj->otyp == CORPSE && obj->corpsenm == PM_LIZARD) { - m.defensive = obj; - m.has_defense = MUSE_LIZARD_CORPSE; + gm.m.defensive = obj; + gm.m.has_defense = MUSE_LIZARD_CORPSE; return TRUE; } else if (obj->otyp == TIN && obj->corpsenm == PM_LIZARD) { liztin = obj; @@ -375,9 +500,9 @@ struct monst *mtmp; } /* confused or stunned monster might not be able to open tin */ if (liztin && mcould_eat_tin(mtmp) && rn2(3)) { - m.defensive = liztin; + gm.m.defensive = liztin; /* tin and corpse ultimately end up being handled the same */ - m.has_defense = MUSE_LIZARD_CORPSE; + gm.m.has_defense = MUSE_LIZARD_CORPSE; return TRUE; } } @@ -396,44 +521,67 @@ struct monst *mtmp; return TRUE; } - fraction = u.ulevel < 10 ? 5 : u.ulevel < 14 ? 4 : 3; - if (mtmp->mhp >= mtmp->mhpmax - || (mtmp->mhp >= 10 && mtmp->mhp * fraction >= mtmp->mhpmax)) - return FALSE; - - if (mtmp->mpeaceful) { - if (!nohands(mtmp->data)) { - if (m_use_healing(mtmp)) + /* monsters aren't given wands of undead turning but if they + happen to have picked one up, use it against corpse wielder; + when applicable, use it now even if 'mtmp' isn't wounded */ + if (!mtmp->mpeaceful && !nohands(mtmp->data) + && uwep && uwep->otyp == CORPSE + && touch_petrifies(&mons[uwep->corpsenm]) + && !poly_when_stoned(mtmp->data) && !resists_ston(mtmp) + && lined_up(mtmp)) { /* only lines up if distu range is within 5*5 */ + /* could use m_carrying(), then nxtobj() when matching wand + is empty, but direct traversal is actually simpler here */ + for (obj = mtmp->minvent; obj; obj = obj->nobj) + if (obj->otyp == WAN_UNDEAD_TURNING && obj->spe > 0) { + gm.m.defensive = obj; + gm.m.has_defense = MUSE_WAN_UNDEAD_TURNING; return TRUE; + } + } + + if (!tryescape) { + /* do we try to heal? */ + fraction = u.ulevel < 10 ? 5 : u.ulevel < 14 ? 4 : 3; + if (mtmp->mhp >= mtmp->mhpmax + || (mtmp->mhp >= 10 && mtmp->mhp * fraction >= mtmp->mhpmax)) + return FALSE; + + if (mtmp->mpeaceful) { + if (!nohands(mtmp->data)) { + if (m_use_healing(mtmp)) + return TRUE; + } + return FALSE; } - return FALSE; } - if (stuck || immobile) { + if (stuck || immobile || mtmp->mtrapped) { ; /* fleeing by stairs or traps is not possible */ } else if (levl[x][y].typ == STAIRS) { - if (x == xdnstair && y == ydnstair) { + stway = stairway_at(x,y); + if (stway && !stway->up && stway->tolev.dnum == u.uz.dnum) { if (!is_floater(mtmp->data)) - m.has_defense = MUSE_DOWNSTAIRS; - } else if (x == xupstair && y == yupstair) { - m.has_defense = MUSE_UPSTAIRS; - } else if (sstairs.sx && x == sstairs.sx && y == sstairs.sy) { - if (sstairs.up || !is_floater(mtmp->data)) - m.has_defense = MUSE_SSTAIRS; + gm.m.has_defense = MUSE_DOWNSTAIRS; + } else if (stway && stway->up && stway->tolev.dnum == u.uz.dnum) { + gm.m.has_defense = MUSE_UPSTAIRS; + } else if (stway && stway->tolev.dnum != u.uz.dnum) { + if (stway->up || !is_floater(mtmp->data)) + gm.m.has_defense = MUSE_SSTAIRS; } } else if (levl[x][y].typ == LADDER) { - if (x == xupladder && y == yupladder) { - m.has_defense = MUSE_UP_LADDER; - } else if (x == xdnladder && y == ydnladder) { + stway = stairway_at(x,y); + if (stway && stway->up && stway->tolev.dnum == u.uz.dnum) { + gm.m.has_defense = MUSE_UP_LADDER; + } else if (stway && !stway->up && stway->tolev.dnum == u.uz.dnum) { if (!is_floater(mtmp->data)) - m.has_defense = MUSE_DN_LADDER; - } else if (sstairs.sx && x == sstairs.sx && y == sstairs.sy) { - if (sstairs.up || !is_floater(mtmp->data)) - m.has_defense = MUSE_SSTAIRS; + gm.m.has_defense = MUSE_DN_LADDER; + } else if (stway && stway->tolev.dnum != u.uz.dnum) { + if (stway->up || !is_floater(mtmp->data)) + gm.m.has_defense = MUSE_SSTAIRS; } } else { /* Note: trap doors take precedence over teleport traps. */ - int xx, yy, i, locs[10][2]; + coordxy xx, yy, i, locs[10][2]; boolean ignore_boulders = (verysmall(mtmp->data) || throws_rocks(mtmp->data) || passes_walls(mtmp->data)), @@ -458,9 +606,9 @@ struct monst *mtmp; /* skip if it's hero's location or a diagonal spot and monster can't move diagonally or some other monster is there */ - if ((xx == u.ux && yy == u.uy) + if (u_at(xx, yy) || (xx != x && yy != y && !diag_ok) - || (level.monsters[xx][yy] && !(xx == x && yy == y))) + || (svl.level.monsters[xx][yy] && !(xx == x && yy == y))) continue; /* skip if there's no trap or can't/won't move onto trap */ if ((t = t_at(xx, yy)) == 0 @@ -472,14 +620,14 @@ struct monst *mtmp; && !is_floater(mtmp->data) && !mtmp->isshk && !mtmp->isgd && !mtmp->ispriest && Can_fall_thru(&u.uz)) { - trapx = xx; - trapy = yy; - m.has_defense = MUSE_TRAPDOOR; + gt.trapx = xx; + gt.trapy = yy; + gm.m.has_defense = MUSE_TRAPDOOR; break; /* no need to look at any other spots */ } else if (t->ttyp == TELEP_TRAP) { - trapx = xx; - trapy = yy; - m.has_defense = MUSE_TELEPORT_TRAP; + gt.trapx = xx; + gt.trapy = yy; + gm.m.has_defense = MUSE_TELEPORT_TRAP; } } } @@ -487,33 +635,14 @@ struct monst *mtmp; if (nohands(mtmp->data)) /* can't use objects */ goto botm; - if (is_mercenary(mtmp->data) && (obj = m_carrying(mtmp, BUGLE)) != 0) { - int xx, yy; - struct monst *mon; - - /* Distance is arbitrary. What we really want to do is - * have the soldier play the bugle when it sees or - * remembers soldiers nearby... - */ - for (xx = x - 3; xx <= x + 3; xx++) { - for (yy = y - 3; yy <= y + 3; yy++) { - if (!isok(xx, yy) || (xx == x && yy == y)) - continue; - if ((mon = m_at(xx, yy)) != 0 && is_mercenary(mon->data) - && mon->data != &mons[PM_GUARD] - && (mon->msleeping || !mon->mcanmove)) { - m.defensive = obj; - m.has_defense = MUSE_BUGLE; - goto toot; /* double break */ - } - } - } - toot: - ; + if (is_mercenary(mtmp->data) && (obj = m_carrying(mtmp, BUGLE)) != 0 + && m_sees_sleepy_soldier(mtmp)) { + gm.m.defensive = obj; + gm.m.has_defense = MUSE_BUGLE; } /* use immediate physical escape prior to attempting magic */ - if (m.has_defense) /* stairs, trap door or tele-trap, bugle alert */ + if (gm.m.has_defense) /* stairs, trap door or tele-trap, bugle alert */ goto botm; /* kludge to cut down on trap destruction (particularly portals) */ @@ -522,16 +651,16 @@ struct monst *mtmp; || t->ttyp == BEAR_TRAP)) t = 0; /* ok for monster to dig here */ -#define nomore(x) if (m.has_defense == x) continue; +#define nomore(x) if (gm.m.has_defense == x) continue; /* selection could be improved by collecting all possibilities into an array and then picking one at random */ for (obj = mtmp->minvent; obj; obj = obj->nobj) { /* don't always use the same selection pattern */ - if (m.has_defense && !rn2(3)) + if (gm.m.has_defense && !rn2(3)) break; /* nomore(MUSE_WAN_DIGGING); */ - if (m.has_defense == MUSE_WAN_DIGGING) + if (gm.m.has_defense == MUSE_WAN_DIGGING) break; if (obj->otyp == WAN_DIGGING && obj->spe > 0 && !stuck && !t && !mtmp->isshk && !mtmp->isgd && !mtmp->ispriest @@ -542,10 +671,9 @@ struct monst *mtmp; && !(levl[x][y].wall_info & W_NONDIGGABLE) && !(Is_botlevel(&u.uz) || In_endgame(&u.uz)) && !(is_ice(x, y) || is_pool(x, y) || is_lava(x, y)) - && !(mtmp->data == &mons[PM_VLAD_THE_IMPALER] - && In_V_tower(&u.uz))) { - m.defensive = obj; - m.has_defense = MUSE_WAN_DIGGING; + && !(is_Vlad(mtmp) && In_V_tower(&u.uz))) { + gm.m.defensive = obj; + gm.m.has_defense = MUSE_WAN_DIGGING; } nomore(MUSE_WAN_TELEPORTATION_SELF); nomore(MUSE_WAN_TELEPORTATION); @@ -556,10 +684,10 @@ struct monst *mtmp; * mean if the monster leaves the level, they'll know * about teleport traps. */ - if (!level.flags.noteleport - || !(mtmp->mtrapseen & (1 << (TELEP_TRAP - 1)))) { - m.defensive = obj; - m.has_defense = (mon_has_amulet(mtmp)) + if (!noteleport_level(mtmp) + || !mon_knows_traps(mtmp, TELEP_TRAP)) { + gm.m.defensive = obj; + gm.m.has_defense = (mon_has_amulet(mtmp)) ? MUSE_WAN_TELEPORTATION : MUSE_WAN_TELEPORTATION_SELF; } @@ -570,69 +698,109 @@ struct monst *mtmp; && (!obj->cursed || (!(mtmp->isshk && inhishop(mtmp)) && !mtmp->isgd && !mtmp->ispriest))) { /* see WAN_TELEPORTATION case above */ - if (!level.flags.noteleport - || !(mtmp->mtrapseen & (1 << (TELEP_TRAP - 1)))) { - m.defensive = obj; - m.has_defense = MUSE_SCR_TELEPORTATION; + if (!noteleport_level(mtmp) + || !mon_knows_traps(mtmp, TELEP_TRAP)) { + gm.m.defensive = obj; + gm.m.has_defense = MUSE_SCR_TELEPORTATION; } } if (mtmp->data != &mons[PM_PESTILENCE]) { nomore(MUSE_POT_FULL_HEALING); if (obj->otyp == POT_FULL_HEALING) { - m.defensive = obj; - m.has_defense = MUSE_POT_FULL_HEALING; + gm.m.defensive = obj; + gm.m.has_defense = MUSE_POT_FULL_HEALING; } nomore(MUSE_POT_EXTRA_HEALING); if (obj->otyp == POT_EXTRA_HEALING) { - m.defensive = obj; - m.has_defense = MUSE_POT_EXTRA_HEALING; + gm.m.defensive = obj; + gm.m.has_defense = MUSE_POT_EXTRA_HEALING; } nomore(MUSE_WAN_CREATE_MONSTER); if (obj->otyp == WAN_CREATE_MONSTER && obj->spe > 0) { - m.defensive = obj; - m.has_defense = MUSE_WAN_CREATE_MONSTER; + gm.m.defensive = obj; + gm.m.has_defense = MUSE_WAN_CREATE_MONSTER; } nomore(MUSE_POT_HEALING); if (obj->otyp == POT_HEALING) { - m.defensive = obj; - m.has_defense = MUSE_POT_HEALING; + gm.m.defensive = obj; + gm.m.has_defense = MUSE_POT_HEALING; } } else { /* Pestilence */ nomore(MUSE_POT_FULL_HEALING); if (obj->otyp == POT_SICKNESS) { - m.defensive = obj; - m.has_defense = MUSE_POT_FULL_HEALING; + gm.m.defensive = obj; + gm.m.has_defense = MUSE_POT_FULL_HEALING; } nomore(MUSE_WAN_CREATE_MONSTER); if (obj->otyp == WAN_CREATE_MONSTER && obj->spe > 0) { - m.defensive = obj; - m.has_defense = MUSE_WAN_CREATE_MONSTER; + gm.m.defensive = obj; + gm.m.has_defense = MUSE_WAN_CREATE_MONSTER; } } nomore(MUSE_SCR_CREATE_MONSTER); if (obj->otyp == SCR_CREATE_MONSTER) { - m.defensive = obj; - m.has_defense = MUSE_SCR_CREATE_MONSTER; + gm.m.defensive = obj; + gm.m.has_defense = MUSE_SCR_CREATE_MONSTER; } } botm: - return (boolean) !!m.has_defense; + return (boolean) !!gm.m.has_defense; #undef nomore } +/* when a monster deliberately enters a trap, make sure the spot becomes + accessible (trap doors and teleporters inside niches are located at + secret corridor locations; convert such into normal corridor even if + hero doesn't see it happen) */ +staticfn void +reveal_trap(struct trap *t, boolean seeit) +{ + struct rm *lev = &levl[t->tx][t->ty]; + + if (lev->typ == SCORR) { + lev->typ = CORR, lev->flags = 0; /* set_levltyp(,,CORR) */ + unblock_point(t->tx, t->ty); + } + if (seeit) + seetrap(t); +} + +/* Monsters without the Amulet escape the dungeon and + * are gone for good when they leave up the up stairs. + * A monster with the Amulet would leave it behind + * (mongone -> mdrop_special_objs) but we force any + * monster who manages to acquire it or the invocation + * tools to stick around instead of letting it escape. + * Don't let the Wizard escape even when not carrying + * anything of interest unless there are more than 1 + * of him. + */ +staticfn int +mon_escape(struct monst *mtmp, boolean vismon) +{ + if (mon_has_special(mtmp) + || (mtmp->iswiz && svc.context.no_of_wizards < 2)) + return 0; + if (vismon) + pline_mon(mtmp, "%s escapes the dungeon!", Monnam(mtmp)); + mongone(mtmp); + return 2; +} + /* Perform a defensive action for a monster. Must be called immediately * after find_defensive(). Return values are 0: did something, 1: died, * 2: did something and can't attack again (i.e. teleported). */ int -use_defensive(mtmp) -struct monst *mtmp; +use_defensive(struct monst *mtmp) { - int i, fleetim, how = 0; - struct obj *otmp = m.defensive; + static const char MissingDefensiveItem[] = "use_defensive: no %s"; + int i, fleetim; + struct obj *otmp = gm.m.defensive; boolean vis, vismon, oseen; - const char *Mnam; + struct trap *t; + stairway *stway; if ((i = precheck(mtmp, otmp)) != 0) return i; @@ -648,11 +816,12 @@ struct monst *mtmp; monflee(m, fleetim, FALSE, FALSE); \ } - switch (m.has_defense) { + switch (gm.m.has_defense) { case MUSE_UNICORN_HORN: + /* unlike most defensive cases, unicorn horn object is optional */ if (vismon) { if (otmp) - pline("%s uses a unicorn horn!", Monnam(mtmp)); + pline_mon(mtmp, "%s uses a unicorn horn!", Monnam(mtmp)); else pline_The("tip of %s's horn glows!", mon_nam(mtmp)); } @@ -661,88 +830,93 @@ struct monst *mtmp; } else if (mtmp->mconf || mtmp->mstun) { mtmp->mconf = mtmp->mstun = 0; if (vismon) - pline("%s seems steadier now.", Monnam(mtmp)); - } else + pline_mon(mtmp, "%s seems steadier now.", Monnam(mtmp)); + } else { impossible("No need for unicorn horn?"); + } return 2; case MUSE_BUGLE: - if (vismon) - pline("%s plays %s!", Monnam(mtmp), doname(otmp)); - else if (!Deaf) + if (!otmp) + panic(MissingDefensiveItem, "bugle"); + if (vismon) { + pline_mon(mtmp, "%s plays %s!", Monnam(mtmp), doname(otmp)); + } else if (!Deaf) { + Soundeffect(se_bugle_playing_reveille, 100); You_hear("a bugle playing reveille!"); + } awaken_soldiers(mtmp); return 2; case MUSE_WAN_TELEPORTATION_SELF: + if (!otmp) + panic(MissingDefensiveItem, "wand of teleportation"); if ((mtmp->isshk && inhishop(mtmp)) || mtmp->isgd || mtmp->ispriest) return 2; m_flee(mtmp); mzapwand(mtmp, otmp, TRUE); - how = WAN_TELEPORTATION; - mon_tele: - if (tele_restrict(mtmp)) { /* mysterious force... */ - if (vismon && how) /* mentions 'teleport' */ - makeknown(how); - /* monster learns that teleportation isn't useful here */ - if (level.flags.noteleport) - mtmp->mtrapseen |= (1 << (TELEP_TRAP - 1)); - return 2; - } - if ((mon_has_amulet(mtmp) || On_W_tower_level(&u.uz)) && !rn2(3)) { - if (vismon) - pline("%s seems disoriented for a moment.", Monnam(mtmp)); - return 2; - } - if (oseen && how) - makeknown(how); - (void) rloc(mtmp, TRUE); + m_tele(mtmp, vismon, oseen, WAN_TELEPORTATION); return 2; case MUSE_WAN_TELEPORTATION: - zap_oseen = oseen; + if (!otmp) + panic(MissingDefensiveItem, "wand of teleportation"); + gz.zap_oseen = oseen; mzapwand(mtmp, otmp, FALSE); - m_using = TRUE; + gm.m_using = TRUE; mbhit(mtmp, rn1(8, 6), mbhitm, bhito, otmp); /* monster learns that teleportation isn't useful here */ - if (level.flags.noteleport) - mtmp->mtrapseen |= (1 << (TELEP_TRAP - 1)); - m_using = FALSE; + if (noteleport_level(mtmp)) + mon_learns_traps(mtmp, TELEP_TRAP); + gm.m_using = FALSE; return 2; case MUSE_SCR_TELEPORTATION: { - int obj_is_cursed = otmp->cursed; + int obj_is_cursed; + if (!otmp) + panic(MissingDefensiveItem, "scroll of teleportation"); + obj_is_cursed = otmp->cursed; if (mtmp->isshk || mtmp->isgd || mtmp->ispriest) return 2; m_flee(mtmp); - mreadmsg(mtmp, otmp); - m_useup(mtmp, otmp); /* otmp might be free'ed */ - how = SCR_TELEPORTATION; + /* we want to be able to access otmp after the teleport but it + might get destroyed if still in mtmp's inventory (maybe mtmp + lands in lava or on a fire trap) so take it out in advance */ + if (otmp->quan > 1L) + otmp = splitobj(otmp, 1L); + extract_from_minvent(mtmp, otmp, FALSE, FALSE); + /* 'last_msg' will be changed to PLNMSG_UNKNOWN if any messages + are issued by mreadmsg(), 'if (vismon) pline()', or m_tele() */ + iflags.last_msg = PLNMSG_enum; + mreadmsg(mtmp, otmp); /* sets otmp->dknown if !Blind or !Deaf */ if (obj_is_cursed || mtmp->mconf) { int nlev; d_level flev; + nlev = random_teleport_level(); if (mon_has_amulet(mtmp) || In_endgame(&u.uz)) { if (vismon) - pline("%s seems very disoriented for a moment.", + pline_mon(mtmp, "%s seems very disoriented for a moment.", Monnam(mtmp)); - return 2; - } - nlev = random_teleport_level(); - if (nlev == depth(&u.uz)) { + } else if (nlev == depth(&u.uz)) { if (vismon) - pline("%s shudders for a moment.", Monnam(mtmp)); - return 2; + pline_mon(mtmp, "%s shudders for a moment.", Monnam(mtmp)); + } else { + get_level(&flev, nlev); + migrate_to_level(mtmp, ledger_no(&flev), MIGR_RANDOM, + (coord *) 0); } - get_level(&flev, nlev); - migrate_to_level(mtmp, ledger_no(&flev), MIGR_RANDOM, - (coord *) 0); - if (oseen) - makeknown(SCR_TELEPORTATION); - } else - goto mon_tele; + } else { + m_tele(mtmp, vismon, oseen, SCR_TELEPORTATION); + } + /* m_tele() handles makeknown(); trycall() will be a no-op when + otmp->otyp is already discovered */ + if (otmp->dknown && iflags.last_msg != PLNMSG_enum) + trycall(otmp); + /* already removed from mtmp->minvent so not 'm_useup(mtmp, otmp)' */ + obfree(otmp, (struct obj *) 0); return 2; } - case MUSE_WAN_DIGGING: { - struct trap *ttmp; - + case MUSE_WAN_DIGGING: + if (!otmp) + panic(MissingDefensiveItem, "wand of digging"); m_flee(mtmp); mzapwand(mtmp, otmp, FALSE); if (oseen) @@ -750,43 +924,69 @@ struct monst *mtmp; if (IS_FURNITURE(levl[mtmp->mx][mtmp->my].typ) || IS_DRAWBRIDGE(levl[mtmp->mx][mtmp->my].typ) || (is_drawbridge_wall(mtmp->mx, mtmp->my) >= 0) - || (sstairs.sx && sstairs.sx == mtmp->mx - && sstairs.sy == mtmp->my)) { + || stairway_at(mtmp->mx, mtmp->my)) { pline_The("digging ray is ineffective."); return 2; } if (!Can_dig_down(&u.uz) && !levl[mtmp->mx][mtmp->my].candig) { - if (canseemon(mtmp)) - pline_The("%s here is too hard to dig in.", - surface(mtmp->mx, mtmp->my)); - return 2; + /* can't dig further if there's already a pit (or other trap) + here, or if pit creation fails for some reason */ + if (t_at(mtmp->mx, mtmp->my) + || !(t = maketrap(mtmp->mx, mtmp->my, PIT))) { + if (vismon) { + pline_The("%s here is too hard to dig in.", + surface(mtmp->mx, mtmp->my)); + } + return 2; + } + /* pit creation succeeded */ + if (vis) { + seetrap(t); + pline_mon(mtmp, "%s has made a pit in the %s.", Monnam(mtmp), + surface(mtmp->mx, mtmp->my)); + } + fill_pit(mtmp->mx, mtmp->my); + recalc_block_point(mtmp->mx, mtmp->my); + return (mintrap(mtmp, FORCEBUNGLE) == Trap_Killed_Mon) ? 1 : 2; } - ttmp = maketrap(mtmp->mx, mtmp->my, HOLE); - if (!ttmp) + t = maketrap(mtmp->mx, mtmp->my, HOLE); + if (!t) return 2; - seetrap(ttmp); + recalc_block_point(mtmp->mx, mtmp->my); + seetrap(t); if (vis) { - pline("%s has made a hole in the %s.", Monnam(mtmp), + pline_mon(mtmp, "%s has made a hole in the %s.", Monnam(mtmp), surface(mtmp->mx, mtmp->my)); - pline("%s %s through...", Monnam(mtmp), + pline_mon(mtmp, "%s %s through...", Monnam(mtmp), is_flyer(mtmp->data) ? "dives" : "falls"); - } else if (!Deaf) + } else if (!Deaf) { + Soundeffect(se_crash_through_floor, 100); You_hear("%s crash through the %s.", something, surface(mtmp->mx, mtmp->my)); + } + fill_pit(mtmp->mx, mtmp->my); /* we made sure that there is a level for mtmp to go to */ migrate_to_level(mtmp, ledger_no(&u.uz) + 1, MIGR_RANDOM, (coord *) 0); return 2; - } + case MUSE_WAN_UNDEAD_TURNING: + if (!otmp) + panic(MissingDefensiveItem, "wand of undead turning"); + gz.zap_oseen = oseen; + mzapwand(mtmp, otmp, FALSE); + gm.m_using = TRUE; + mbhit(mtmp, rn1(8, 6), mbhitm, bhito, otmp); + gm.m_using = FALSE; + return 2; case MUSE_WAN_CREATE_MONSTER: { coord cc; - /* pm: 0 => random, eel => aquatic, croc => amphibious */ - struct permonst *pm = - !is_pool(mtmp->mx, mtmp->my) - ? 0 - : &mons[u.uinwater ? PM_GIANT_EEL : PM_CROCODILE]; struct monst *mon; + /* pm: 0 => random, eel => aquatic, croc => amphibious */ + struct permonst *pm = !is_pool(mtmp->mx, mtmp->my) ? 0 + : &mons[u.uinwater ? PM_GIANT_EEL : PM_CROCODILE]; + if (!otmp) + panic(MissingDefensiveItem, "wand of create monster"); if (!enexto(&cc, mtmp->mx, mtmp->my, pm)) return 0; mzapwand(mtmp, otmp, FALSE); @@ -802,6 +1002,8 @@ struct monst *mtmp; struct monst *mon; boolean known = FALSE; + if (!otmp) + panic(MissingDefensiveItem, "scroll of create monster"); if (!rn2(73)) cnt += rnd(4); if (mtmp->mconf || otmp->cursed) @@ -827,9 +1029,8 @@ struct monst *mtmp; */ if (known) makeknown(SCR_CREATE_MONSTER); - else if (!objects[SCR_CREATE_MONSTER].oc_name_known - && !objects[SCR_CREATE_MONSTER].oc_uname) - docall(otmp); + else + trycall(otmp); m_useup(mtmp, otmp); return 2; } @@ -841,40 +1042,39 @@ struct monst *mtmp; if (Is_botlevel(&u.uz)) return 0; m_flee(mtmp); + t = t_at(gt.trapx, gt.trapy); if (vis) { - struct trap *t = t_at(trapx, trapy); - - Mnam = Monnam(mtmp); - pline("%s %s into a %s!", Mnam, + pline_mon(mtmp, "%s %s into a %s!", Monnam(mtmp), vtense(fakename[0], locomotion(mtmp->data, "jump")), - (t->ttyp == TRAPDOOR) ? "trap door" : "hole"); - if (levl[trapx][trapy].typ == SCORR) { - levl[trapx][trapy].typ = CORR; - unblock_point(trapx, trapy); - } - seetrap(t_at(trapx, trapy)); + trapname(t->ttyp, FALSE)); } + /* if trap was in a concealed niche, it's no longer concealed */ + reveal_trap(t, vis); /* don't use rloc_to() because worm tails must "move" */ remove_monster(mtmp->mx, mtmp->my); newsym(mtmp->mx, mtmp->my); /* update old location */ - place_monster(mtmp, trapx, trapy); + place_monster(mtmp, gt.trapx, gt.trapy); if (mtmp->wormno) worm_move(mtmp); - newsym(trapx, trapy); + newsym(gt.trapx, gt.trapy); migrate_to_level(mtmp, ledger_no(&u.uz) + 1, MIGR_RANDOM, (coord *) 0); return 2; case MUSE_UPSTAIRS: m_flee(mtmp); + stway = stairway_at(mtmp->mx, mtmp->my); + if (!stway) + return 0; if (ledger_no(&u.uz) == 1) - goto escape; /* impossible; level 1 upstairs are SSTAIRS */ + /* impossible; level 1 upstairs are SSTAIRS */ + return mon_escape(mtmp, vismon); if (Inhell && mon_has_amulet(mtmp) && !rn2(4) && (dunlev(&u.uz) < dunlevs_in_dungeon(&u.uz) - 3)) { if (vismon) - pline( - "As %s climbs the stairs, a mysterious force momentarily surrounds %s...", + pline("As %s climbs the stairs, a mysterious force" + " momentarily surrounds %s...", mon_nam(mtmp), mhim(mtmp)); /* simpler than for the player; this will usually be the Wizard and he'll immediately go right to the @@ -884,119 +1084,126 @@ struct monst *mtmp; (coord *) 0); } else { if (vismon) - pline("%s escapes upstairs!", Monnam(mtmp)); - migrate_to_level(mtmp, ledger_no(&u.uz) - 1, MIGR_STAIRS_DOWN, - (coord *) 0); + pline_mon(mtmp, "%s escapes upstairs!", Monnam(mtmp)); + migrate_to_level(mtmp, ledger_no(&(stway->tolev)), + MIGR_STAIRS_DOWN, (coord *) 0); } return 2; case MUSE_DOWNSTAIRS: m_flee(mtmp); + stway = stairway_at(mtmp->mx, mtmp->my); + if (!stway) + return 0; if (vismon) - pline("%s escapes downstairs!", Monnam(mtmp)); - migrate_to_level(mtmp, ledger_no(&u.uz) + 1, MIGR_STAIRS_UP, + pline_mon(mtmp, "%s escapes downstairs!", Monnam(mtmp)); + migrate_to_level(mtmp, ledger_no(&(stway->tolev)), MIGR_STAIRS_UP, (coord *) 0); return 2; case MUSE_UP_LADDER: m_flee(mtmp); + stway = stairway_at(mtmp->mx, mtmp->my); + if (!stway) + return 0; if (vismon) - pline("%s escapes up the ladder!", Monnam(mtmp)); - migrate_to_level(mtmp, ledger_no(&u.uz) - 1, MIGR_LADDER_DOWN, + pline_mon(mtmp, "%s escapes up the ladder!", Monnam(mtmp)); + migrate_to_level(mtmp, ledger_no(&(stway->tolev)), MIGR_LADDER_DOWN, (coord *) 0); return 2; case MUSE_DN_LADDER: m_flee(mtmp); + stway = stairway_at(mtmp->mx, mtmp->my); + if (!stway) + return 0; if (vismon) - pline("%s escapes down the ladder!", Monnam(mtmp)); - migrate_to_level(mtmp, ledger_no(&u.uz) + 1, MIGR_LADDER_UP, + pline_mon(mtmp, "%s escapes down the ladder!", Monnam(mtmp)); + migrate_to_level(mtmp, ledger_no(&(stway->tolev)), MIGR_LADDER_UP, (coord *) 0); return 2; case MUSE_SSTAIRS: m_flee(mtmp); + stway = stairway_at(mtmp->mx, mtmp->my); + if (!stway) + return 0; if (ledger_no(&u.uz) == 1) { - escape: - /* Monsters without the Amulet escape the dungeon and - * are gone for good when they leave up the up stairs. - * A monster with the Amulet would leave it behind - * (mongone -> mdrop_special_objs) but we force any - * monster who manages to acquire it or the invocation - * tools to stick around instead of letting it escape. - */ - if (mon_has_special(mtmp)) - return 0; - if (vismon) - pline("%s escapes the dungeon!", Monnam(mtmp)); - mongone(mtmp); - return 2; + return mon_escape(mtmp, vismon); } if (vismon) - pline("%s escapes %sstairs!", Monnam(mtmp), - sstairs.up ? "up" : "down"); + pline_mon(mtmp, "%s escapes %sstairs!", Monnam(mtmp), + stway->up ? "up" : "down"); /* going from the Valley to Castle (Stronghold) has no sstairs - to target, but having sstairs. == <0,0> will work the + to target, but having gs.sstairs. == <0,0> will work the same as specifying MIGR_RANDOM when mon_arrive() eventually places the monster, so we can use MIGR_SSTAIRS unconditionally */ - migrate_to_level(mtmp, ledger_no(&sstairs.tolev), MIGR_SSTAIRS, + migrate_to_level(mtmp, ledger_no(&(stway->tolev)), MIGR_SSTAIRS, (coord *) 0); return 2; case MUSE_TELEPORT_TRAP: m_flee(mtmp); + t = t_at(gt.trapx, gt.trapy); if (vis) { - Mnam = Monnam(mtmp); - pline("%s %s onto a teleport trap!", Mnam, - vtense(fakename[0], locomotion(mtmp->data, "jump"))); - seetrap(t_at(trapx, trapy)); + pline_mon(mtmp, "%s %s onto a %s!", Monnam(mtmp), + vtense(fakename[0], locomotion(mtmp->data, "jump")), + trapname(t->ttyp, FALSE)); } + /* if trap was in a concealed niche, it's no longer concealed */ + reveal_trap(t, vis); /* don't use rloc_to() because worm tails must "move" */ remove_monster(mtmp->mx, mtmp->my); newsym(mtmp->mx, mtmp->my); /* update old location */ - place_monster(mtmp, trapx, trapy); + place_monster(mtmp, gt.trapx, gt.trapy); if (mtmp->wormno) worm_move(mtmp); - newsym(trapx, trapy); - - goto mon_tele; + maybe_unhide_at(mtmp->mx, mtmp->my); + newsym(gt.trapx, gt.trapy); + /* 0: 'no object' rather than STRANGE_OBJECT; FALSE: obj not seen */ + m_tele(mtmp, vismon, FALSE, 0); + return 2; case MUSE_POT_HEALING: + if (!otmp) + panic(MissingDefensiveItem, "potioh of healing"); mquaffmsg(mtmp, otmp); i = d(6 + 2 * bcsign(otmp), 4); - mtmp->mhp += i; - if (mtmp->mhp > mtmp->mhpmax) - mtmp->mhp = ++mtmp->mhpmax; + healmon(mtmp, i, 1); if (!otmp->cursed && !mtmp->mcansee) mcureblindness(mtmp, vismon); if (vismon) - pline("%s looks better.", Monnam(mtmp)); + pline_mon(mtmp, "%s looks better.", Monnam(mtmp)); if (oseen) makeknown(POT_HEALING); m_useup(mtmp, otmp); return 2; case MUSE_POT_EXTRA_HEALING: + if (!otmp) + panic(MissingDefensiveItem, "potioh of extra healing"); mquaffmsg(mtmp, otmp); i = d(6 + 2 * bcsign(otmp), 8); - mtmp->mhp += i; - if (mtmp->mhp > mtmp->mhpmax) - mtmp->mhp = (mtmp->mhpmax += (otmp->blessed ? 5 : 2)); + healmon(mtmp, i, otmp->blessed ? 5 : 2); if (!mtmp->mcansee) mcureblindness(mtmp, vismon); if (vismon) - pline("%s looks much better.", Monnam(mtmp)); + pline_mon(mtmp, "%s looks much better.", Monnam(mtmp)); if (oseen) makeknown(POT_EXTRA_HEALING); m_useup(mtmp, otmp); return 2; case MUSE_POT_FULL_HEALING: + if (!otmp) + panic(MissingDefensiveItem, "potioh of full healing"); mquaffmsg(mtmp, otmp); if (otmp->otyp == POT_SICKNESS) unbless(otmp); /* Pestilence */ - mtmp->mhp = (mtmp->mhpmax += (otmp->blessed ? 8 : 4)); + healmon(mtmp, mtmp->mhpmax, otmp->blessed ? 8 : 4); if (!mtmp->mcansee && otmp->otyp != POT_SICKNESS) mcureblindness(mtmp, vismon); if (vismon) - pline("%s looks completely healed.", Monnam(mtmp)); + pline_mon(mtmp, "%s looks completely healed.", Monnam(mtmp)); if (oseen) makeknown(otmp->otyp); m_useup(mtmp, otmp); return 2; case MUSE_LIZARD_CORPSE: + if (!otmp) + panic(MissingDefensiveItem, "lizard corpse"); /* not actually called for its unstoning effect */ mon_consume_unstone(mtmp, otmp, FALSE, FALSE); return 2; @@ -1004,7 +1211,7 @@ struct monst *mtmp; return 0; /* i.e. an exploded wand */ default: impossible("%s wanted to perform action %d?", Monnam(mtmp), - m.has_defense); + gm.m.has_defense); break; } return 0; @@ -1012,8 +1219,7 @@ struct monst *mtmp; } int -rnd_defensive_item(mtmp) -struct monst *mtmp; +rnd_defensive_item(struct monst *mtmp) { struct permonst *pm = mtmp->data; int difficulty = mons[(monsndx(pm))].difficulty; @@ -1026,10 +1232,11 @@ struct monst *mtmp; switch (rn2(8 + (difficulty > 3) + (difficulty > 6) + (difficulty > 8))) { case 6: case 9: - if (level.flags.noteleport && ++trycnt < 2) + if (noteleport_level(mtmp) && ++trycnt < 2) goto try_again; if (!rn2(3)) return WAN_TELEPORTATION; + FALLTHROUGH; /*FALLTHRU*/ case 0: case 1: @@ -1038,6 +1245,7 @@ struct monst *mtmp; case 10: if (!rn2(3)) return WAN_CREATE_MONSTER; + FALLTHROUGH; /*FALLTHRU*/ case 2: return SCR_CREATE_MONSTER; @@ -1048,11 +1256,14 @@ struct monst *mtmp; case 5: return (mtmp->data != &mons[PM_PESTILENCE]) ? POT_FULL_HEALING : POT_SICKNESS; - case 7: + case 7: /* wand of digging */ + /* usually avoid digging in Sokoban */ + if (Sokoban && rn2(4)) + goto try_again; + /* some creatures shouldn't dig down to another level when hurt */ if (is_floater(pm) || mtmp->isshk || mtmp->isgd || mtmp->ispriest) return 0; - else - return WAN_DIGGING; + return WAN_DIGGING; } /*NOTREACHED*/ return 0; @@ -1072,23 +1283,148 @@ struct monst *mtmp; #define MUSE_FROST_HORN 12 #define MUSE_FIRE_HORN 13 #define MUSE_POT_ACID 14 -/*#define MUSE_WAN_TELEPORTATION 15*/ +#define MUSE_WAN_TELEPORTATION 15 #define MUSE_POT_SLEEPING 16 #define MUSE_SCR_EARTH 17 +#define MUSE_CAMERA 18 +/*#define MUSE_WAN_UNDEAD_TURNING 20*/ /* also a defensive item so don't + * redefine; nonconsecutive value is ok */ + +staticfn boolean +linedup_chk_corpse(coordxy x, coordxy y) +{ + return (sobj_at(CORPSE, x, y) != 0); +} + +staticfn void +m_use_undead_turning(struct monst *mtmp, struct obj *obj) +{ + coordxy ax = u.ux + sgn(mtmp->mux - mtmp->mx) * 3, + ay = u.uy + sgn(mtmp->muy - mtmp->my) * 3; + coordxy bx = mtmp->mx, by = mtmp->my; + + if (!(obj->otyp == WAN_UNDEAD_TURNING && obj->spe > 0)) + return; + + /* not necrophiliac(); unlike deciding whether to pick this + type of wand up, we aren't interested in corpses within + carried containers until they're moved into open inventory; + we don't check whether hero is poly'd into an undead--the + wand's turning effect is too weak to be a useful direct + attack--only whether hero is carrying at least one corpse */ + if (carrying(CORPSE) + /* + * If hero is carrying one or more corpses but isn't wielding + * a cockatrice corpse (unless being hit by one won't do + * the monster much harm); otherwise we'd be using this wand + * as a defensive item with higher priority. + * + * Might be cockatrice intended as a weapon (or being denied + * to glove-wearing monsters for use as a weapon) or lizard + * intended as a cure or lichen intended as veggy food or + * sacrifice fodder being lugged to an altar. Zapping with + * this will deprive hero of one from each stack although + * they might subsequently be recovered after killing again. + * In the sacrifice fodder case, it could even be to the + * player's advantage (fresher corpse if a new one gets + * dropped; player might not choose to spend a wand charge + * on that when/if hero acquires this wand). + */ + || linedup_callback(ax, ay, bx, by, linedup_chk_corpse) + /* or there's a corpse on the ground in a direct line from the + monster to the hero, and up to 3 steps beyond. */ + ) { + gm.m.offensive = obj; + gm.m.has_offense = MUSE_WAN_UNDEAD_TURNING; + } +} + +/* from monster's point of view, is hero behind a chokepoint? */ +staticfn boolean +hero_behind_chokepoint(struct monst *mtmp) +{ + coordxy dx = sgn(mtmp->mx - mtmp->mux); + coordxy dy = sgn(mtmp->my - mtmp->muy); + + coordxy x = mtmp->mux + dx; + coordxy y = mtmp->muy + dy; + + int dir = xytodir(dx, dy); + int dir_l = DIR_CLAMP(DIR_LEFT2(dir)); + int dir_r = DIR_CLAMP(DIR_RIGHT2(dir)); + + coord c1, c2; + + dirtocoord(&c1, dir_l); + dirtocoord(&c2, dir_r); + c1.x += x, c2.x += x; + c1.y += y, c2.y += y; + + if ((!isok(c1.x, c1.y) || !accessible(c1.x, c1.y)) + && (!isok(c2.x, c2.y) || !accessible(c2.x, c2.y))) + return TRUE; + return FALSE; +} + +/* hostile monster has another hostile next to it */ +staticfn boolean +mon_has_friends(struct monst *mtmp) +{ + coordxy dx, dy; + struct monst *mon2; + + if (mtmp->mtame || mtmp->mpeaceful) + return FALSE; + + for (dx = -1; dx <= 1; dx++) + for (dy = -1; dy <= 1; dy++) { + coordxy x = mtmp->mx + dx; + coordxy y = mtmp->my + dy; + + if (isok(x, y) && (mon2 = m_at(x, y)) != 0 + && mon2 != mtmp + && !mon2->mtame && !mon2->mpeaceful) + return TRUE; + } + + return FALSE; +} + +/* does monster like object pile at x,y? */ +staticfn boolean +mon_likes_objpile_at(struct monst *mtmp, coordxy x, coordxy y) +{ + int i; + struct obj *otmp; + + if (!isok(x,y) || !OBJ_AT(x,y)) + return FALSE; + + /* monster likes any of the top 3 items in the pile? */ + for (i = 0, otmp = svl.level.objects[x][y]; otmp && i < 3; i++) { + if (mon_would_take_item(mtmp, otmp)) + return TRUE; + otmp = otmp->nexthere; + } + + /* pile is larger than 3 stacks? */ + if (i >= 3) + return TRUE; + + return FALSE; +} /* Select an offensive item/action for a monster. Returns TRUE iff one is * found. */ boolean -find_offensive(mtmp) -struct monst *mtmp; +find_offensive(struct monst *mtmp) { - register struct obj *obj; - boolean reflection_skip = (Reflecting && rn2(2)); - struct obj *helmet = which_armor(mtmp, W_ARMH); + struct obj *obj, *mtmp_helmet; + boolean reflection_skip; - m.offensive = (struct obj *) 0; - m.has_offense = 0; + gm.m.offensive = (struct obj *) 0; + gm.m.has_offense = 0; if (mtmp->mpeaceful || is_animal(mtmp->data) || mindless(mtmp->data) || nohands(mtmp->data)) return FALSE; @@ -1100,102 +1436,115 @@ struct monst *mtmp; && !uwep && !uarmu && !uarm && !uarmh && !uarms && !uarmg && !uarmc && !uarmf) return FALSE; - /* all offensive items require orthogonal or diagonal targetting */ + /* all offensive items require orthogonal or diagonal targeting */ if (!lined_up(mtmp)) return FALSE; -#define nomore(x) if (m.has_offense == x) continue; +#define nomore(x) if (gm.m.has_offense == x) continue; + reflection_skip = (m_seenres(mtmp, M_SEEN_REFL) != 0 + || monnear(mtmp, mtmp->mux, mtmp->muy)); + mtmp_helmet = which_armor(mtmp, W_ARMH); /* this picks the last viable item rather than prioritizing choices */ for (obj = mtmp->minvent; obj; obj = obj->nobj) { if (!reflection_skip) { nomore(MUSE_WAN_DEATH); - if (obj->otyp == WAN_DEATH && obj->spe > 0) { - m.offensive = obj; - m.has_offense = MUSE_WAN_DEATH; + if (obj->otyp == WAN_DEATH && obj->spe > 0 + && !m_seenres(mtmp, M_SEEN_MAGR)) { + gm.m.offensive = obj; + gm.m.has_offense = MUSE_WAN_DEATH; } nomore(MUSE_WAN_SLEEP); - if (obj->otyp == WAN_SLEEP && obj->spe > 0 && multi >= 0) { - m.offensive = obj; - m.has_offense = MUSE_WAN_SLEEP; + if (obj->otyp == WAN_SLEEP && obj->spe > 0 && gm.multi >= 0 + && !m_seenres(mtmp, M_SEEN_SLEEP)) { + gm.m.offensive = obj; + gm.m.has_offense = MUSE_WAN_SLEEP; } nomore(MUSE_WAN_FIRE); - if (obj->otyp == WAN_FIRE && obj->spe > 0) { - m.offensive = obj; - m.has_offense = MUSE_WAN_FIRE; + if (obj->otyp == WAN_FIRE && obj->spe > 0 + && !m_seenres(mtmp, M_SEEN_FIRE)) { + gm.m.offensive = obj; + gm.m.has_offense = MUSE_WAN_FIRE; } nomore(MUSE_FIRE_HORN); - if (obj->otyp == FIRE_HORN && obj->spe > 0 && can_blow(mtmp)) { - m.offensive = obj; - m.has_offense = MUSE_FIRE_HORN; + if (obj->otyp == FIRE_HORN && obj->spe > 0 && can_blow(mtmp) + && !m_seenres(mtmp, M_SEEN_FIRE)) { + gm.m.offensive = obj; + gm.m.has_offense = MUSE_FIRE_HORN; } nomore(MUSE_WAN_COLD); - if (obj->otyp == WAN_COLD && obj->spe > 0) { - m.offensive = obj; - m.has_offense = MUSE_WAN_COLD; + if (obj->otyp == WAN_COLD && obj->spe > 0 + && !m_seenres(mtmp, M_SEEN_COLD)) { + gm.m.offensive = obj; + gm.m.has_offense = MUSE_WAN_COLD; } nomore(MUSE_FROST_HORN); - if (obj->otyp == FROST_HORN && obj->spe > 0 && can_blow(mtmp)) { - m.offensive = obj; - m.has_offense = MUSE_FROST_HORN; + if (obj->otyp == FROST_HORN && obj->spe > 0 && can_blow(mtmp) + && !m_seenres(mtmp, M_SEEN_COLD)) { + gm.m.offensive = obj; + gm.m.has_offense = MUSE_FROST_HORN; } nomore(MUSE_WAN_LIGHTNING); - if (obj->otyp == WAN_LIGHTNING && obj->spe > 0) { - m.offensive = obj; - m.has_offense = MUSE_WAN_LIGHTNING; + if (obj->otyp == WAN_LIGHTNING && obj->spe > 0 + && !m_seenres(mtmp, M_SEEN_ELEC)) { + gm.m.offensive = obj; + gm.m.has_offense = MUSE_WAN_LIGHTNING; } nomore(MUSE_WAN_MAGIC_MISSILE); - if (obj->otyp == WAN_MAGIC_MISSILE && obj->spe > 0) { - m.offensive = obj; - m.has_offense = MUSE_WAN_MAGIC_MISSILE; + if (obj->otyp == WAN_MAGIC_MISSILE && obj->spe > 0 + && !m_seenres(mtmp, M_SEEN_MAGR)) { + gm.m.offensive = obj; + gm.m.has_offense = MUSE_WAN_MAGIC_MISSILE; } } + nomore(MUSE_WAN_UNDEAD_TURNING); + m_use_undead_turning(mtmp, obj); nomore(MUSE_WAN_STRIKING); - if (obj->otyp == WAN_STRIKING && obj->spe > 0) { - m.offensive = obj; - m.has_offense = MUSE_WAN_STRIKING; + if (obj->otyp == WAN_STRIKING && obj->spe > 0 + && !m_seenres(mtmp, M_SEEN_MAGR)) { + gm.m.offensive = obj; + gm.m.has_offense = MUSE_WAN_STRIKING; } -#if 0 /* use_offensive() has had some code to support wand of teleportation - * for a long time, but find_offensive() never selected one; - * so for the time being, this is still disabled */ nomore(MUSE_WAN_TELEPORTATION); if (obj->otyp == WAN_TELEPORTATION && obj->spe > 0 /* don't give controlled hero a free teleport */ && !Teleport_control + /* same hack as MUSE_WAN_TELEPORTATION_SELF */ + && (!noteleport_level(mtmp) + || !mon_knows_traps(mtmp, TELEP_TRAP)) /* do try to move hero to a more vulnerable spot */ && (onscary(u.ux, u.uy, mtmp) - || (u.ux == xupstair && u.uy == yupstair) - || (u.ux == xdnstair && u.uy == ydnstair) - || (u.ux == sstairs.sx && u.uy == sstairs.sy) - || (u.ux == xupladder && u.uy == yupladder) - || (u.ux == xdnladder && u.uy == ydnladder))) { - m.offensive = obj; - m.has_offense = MUSE_WAN_TELEPORTATION; + || (hero_behind_chokepoint(mtmp) && mon_has_friends(mtmp)) + || mon_likes_objpile_at(mtmp, u.ux, u.uy) + || stairway_at(u.ux, u.uy))) { + gm.m.offensive = obj; + gm.m.has_offense = MUSE_WAN_TELEPORTATION; } -#endif nomore(MUSE_POT_PARALYSIS); - if (obj->otyp == POT_PARALYSIS && multi >= 0) { - m.offensive = obj; - m.has_offense = MUSE_POT_PARALYSIS; + if (obj->otyp == POT_PARALYSIS && gm.multi >= 0) { + gm.m.offensive = obj; + gm.m.has_offense = MUSE_POT_PARALYSIS; } nomore(MUSE_POT_BLINDNESS); if (obj->otyp == POT_BLINDNESS && !attacktype(mtmp->data, AT_GAZE)) { - m.offensive = obj; - m.has_offense = MUSE_POT_BLINDNESS; + gm.m.offensive = obj; + gm.m.has_offense = MUSE_POT_BLINDNESS; } nomore(MUSE_POT_CONFUSION); if (obj->otyp == POT_CONFUSION) { - m.offensive = obj; - m.has_offense = MUSE_POT_CONFUSION; + gm.m.offensive = obj; + gm.m.has_offense = MUSE_POT_CONFUSION; } nomore(MUSE_POT_SLEEPING); - if (obj->otyp == POT_SLEEPING) { - m.offensive = obj; - m.has_offense = MUSE_POT_SLEEPING; + if (obj->otyp == POT_SLEEPING + && !m_seenres(mtmp, M_SEEN_SLEEP)) { + gm.m.offensive = obj; + gm.m.has_offense = MUSE_POT_SLEEPING; } nomore(MUSE_POT_ACID); - if (obj->otyp == POT_ACID) { - m.offensive = obj; - m.has_offense = MUSE_POT_ACID; + if (obj->otyp == POT_ACID + && !m_seenres(mtmp, M_SEEN_ACID)) { + gm.m.offensive = obj; + gm.m.has_offense = MUSE_POT_ACID; } /* we can safely put this scroll here since the locations that * are in a 1 square radius are a subset of the locations that @@ -1203,7 +1552,7 @@ struct monst *mtmp; */ nomore(MUSE_SCR_EARTH); if (obj->otyp == SCR_EARTH - && ((helmet && is_metallic(helmet)) || mtmp->mconf + && (hard_helmet(mtmp_helmet) || mtmp->mconf || amorphous(mtmp->data) || passes_walls(mtmp->data) || noncorporeal(mtmp->data) || unsolid(mtmp->data) || !rn2(10)) @@ -1211,33 +1560,47 @@ struct monst *mtmp; && mtmp->mcansee && haseyes(mtmp->data) && !Is_rogue_level(&u.uz) && (!In_endgame(&u.uz) || Is_earthlevel(&u.uz))) { - m.offensive = obj; - m.has_offense = MUSE_SCR_EARTH; + gm.m.offensive = obj; + gm.m.has_offense = MUSE_SCR_EARTH; + } + nomore(MUSE_CAMERA); + if (obj->otyp == EXPENSIVE_CAMERA + && ((!Blind && !resists_blnd(&gy.youmonst)) + || hates_light(gy.youmonst.data)) + && dist2(mtmp->mx, mtmp->my, mtmp->mux, mtmp->muy) <= 2 + && obj->spe > 0 && !rn2(6)) { + gm.m.offensive = obj; + gm.m.has_offense = MUSE_CAMERA; } #if 0 nomore(MUSE_SCR_FIRE); if (obj->otyp == SCR_FIRE && resists_fire(mtmp) && dist2(mtmp->mx, mtmp->my, mtmp->mux, mtmp->muy) <= 2 - && mtmp->mcansee && haseyes(mtmp->data)) { - m.offensive = obj; - m.has_offense = MUSE_SCR_FIRE; + && mtmp->mcansee && haseyes(mtmp->data) + && !m_seenres(mtmp, M_SEEN_FIRE)) { + /* + * TODO? + * Could choose scroll if (where attacker thinks + * hero is located) is ice and attacker either isn't also + * on ice or is able to fly/float/swim. + */ + gm.m.offensive = obj; + gm.m.has_offense = MUSE_SCR_FIRE; } #endif /* 0 */ } - return (boolean) !!m.has_offense; + return (boolean) !!gm.m.has_offense; #undef nomore } -STATIC_PTR -int -mbhitm(mtmp, otmp) -register struct monst *mtmp; -register struct obj *otmp; +staticfn int +mbhitm(struct monst *mtmp, struct obj *otmp) { int tmp; - boolean reveal_invis = FALSE; + boolean reveal_invis = FALSE, learnit = FALSE, + hits_you = (mtmp == &gy.youmonst); - if (mtmp != &youmonst) { + if (!hits_you && otmp->otyp != WAN_UNDEAD_TURNING) { mtmp->msleeping = 0; if (mtmp->m_ap_type) seemimic(mtmp); @@ -1245,177 +1608,237 @@ register struct obj *otmp; switch (otmp->otyp) { case WAN_STRIKING: reveal_invis = TRUE; - if (mtmp == &youmonst) { - if (zap_oseen) - makeknown(WAN_STRIKING); + if (hits_you) { if (Antimagic) { + monstseesu(M_SEEN_MAGR); /* monsters notice hero resisting */ shieldeff(u.ux, u.uy); + Soundeffect(se_boing, 40); pline("Boing!"); - } else if (rnd(20) < 10 + u.uac) { + learnit = TRUE; + } else if (rnd(20) < 10 + u.uac && + !(gb.buzzer && !gb.buzzer->mwandexp)) { + monstunseesu(M_SEEN_MAGR); /* mons see hero not resisting */ pline_The("wand hits you!"); tmp = d(2, 12); if (Half_spell_damage) tmp = (tmp + 1) / 2; losehp(tmp, "wand", KILLED_BY_AN); - } else + learnit = TRUE; + } else { pline_The("wand misses you."); + } stop_occupation(); nomul(0); } else if (resists_magm(mtmp)) { shieldeff(mtmp->mx, mtmp->my); + Soundeffect(se_boing, 40); pline("Boing!"); + learnit = TRUE; } else if (rnd(20) < 10 + find_mac(mtmp)) { tmp = d(2, 12); hit("wand", mtmp, exclam(tmp)); (void) resist(mtmp, otmp->oclass, tmp, TELL); - if (cansee(mtmp->mx, mtmp->my) && zap_oseen) - makeknown(WAN_STRIKING); + learnit = TRUE; } else { miss("wand", mtmp); - if (cansee(mtmp->mx, mtmp->my) && zap_oseen) - makeknown(WAN_STRIKING); } + /* need to see the wand being zapped and also the spot where the + target is hit; don't have to see the target itself though */ + if (learnit && gz.zap_oseen && (hits_you + || cansee(mtmp->mx, mtmp->my))) + makeknown(WAN_STRIKING); break; -#if 0 /* disabled because find_offensive() never picks WAN_TELEPORTATION */ case WAN_TELEPORTATION: - if (mtmp == &youmonst) { - if (zap_oseen) - makeknown(WAN_TELEPORTATION); + if (hits_you) { tele(); + if (gz.zap_oseen) + makeknown(WAN_TELEPORTATION); } else { /* for consistency with zap.c, don't identify */ if (mtmp->ispriest && *in_rooms(mtmp->mx, mtmp->my, TEMPLE)) { if (cansee(mtmp->mx, mtmp->my)) - pline("%s resists the magic!", Monnam(mtmp)); + pline_mon(mtmp, "%s resists the magic!", Monnam(mtmp)); } else if (!tele_restrict(mtmp)) - (void) rloc(mtmp, TRUE); + (void) rloc(mtmp, RLOC_MSG); } break; -#endif case WAN_CANCELLATION: case SPE_CANCELLATION: (void) cancel_monst(mtmp, otmp, FALSE, TRUE, FALSE); break; + case WAN_UNDEAD_TURNING: + if (hits_you) { + unturn_you(); + learnit = gz.zap_oseen; + } else { + boolean wake = FALSE; + + if (unturn_dead(mtmp)) /* affects mtmp's invent, not mtmp */ + wake = TRUE; + if (is_undead(mtmp->data) || is_vampshifter(mtmp)) { + wake = reveal_invis = TRUE; + /* context.bypasses=True: if resist() happens to be fatal, + make_corpse() will set obj->bypass on the new corpse + so that mbhito() will skip it instead of reviving it */ + svc.context.bypasses = TRUE; /* for make_corpse() */ + (void) resist(mtmp, WAND_CLASS, rnd(8), NOTELL); + } + if (wake) { + if (!DEADMONSTER(mtmp)) + wakeup(mtmp, FALSE); + learnit = gz.zap_oseen; + } + } + if (learnit) + makeknown(WAN_UNDEAD_TURNING); + break; + default: + break; } - if (reveal_invis) { - if (!DEADMONSTER(mtmp) && cansee(bhitpos.x, bhitpos.y) - && !canspotmon(mtmp)) - map_invisible(bhitpos.x, bhitpos.y); - } + if (reveal_invis && !DEADMONSTER(mtmp) + && cansee(gb.bhitpos.x, gb.bhitpos.y) && !canspotmon(mtmp)) + map_invisible(gb.bhitpos.x, gb.bhitpos.y); + return 0; } +/* hit all objects at x,y with fhito function */ +staticfn boolean +fhito_loc(struct obj *obj, + coordxy tx, coordxy ty, + int (*fhito)(OBJ_P, OBJ_P)) +{ + struct obj *otmp, *next_obj; + int hitanything = 0; + + if (!fhito || !OBJ_AT(tx, ty)) + return FALSE; + + for (otmp = svl.level.objects[tx][ty]; otmp; otmp = next_obj) { + next_obj = otmp->nexthere; + + if (otmp->where != OBJ_FLOOR || otmp->ox != tx || otmp->oy != ty) + continue; + hitanything += (*fhito)(otmp, obj); + } + + return hitanything ? TRUE : FALSE; +} + /* A modified bhit() for monsters. Based on bhit() in zap.c. Unlike * buzz(), bhit() doesn't take into account the possibility of a monster * zapping you, so we need a special function for it. (Unless someone wants * to merge the two functions...) */ -STATIC_OVL void -mbhit(mon, range, fhitm, fhito, obj) -struct monst *mon; /* monster shooting the wand */ -register int range; /* direction and range */ -int FDECL((*fhitm), (MONST_P, OBJ_P)); -int FDECL((*fhito), (OBJ_P, OBJ_P)); /* fns called when mon/obj hit */ -struct obj *obj; /* 2nd arg to fhitm/fhito */ +staticfn void +mbhit( + struct monst *mon, /* monster shooting the wand */ + int range, /* direction and range */ + int (*fhitm)(MONST_P, OBJ_P), /* must be non-Null */ + int (*fhito)(OBJ_P, OBJ_P), /* fns called when mon/obj hit */ + struct obj *obj) /* 2nd arg to fhitm/fhito */ { - register struct monst *mtmp; - register struct obj *otmp; - register uchar typ; - int ddx, ddy; + struct monst *mtmp; + uchar ltyp; + int ddx, ddy, otyp = obj->otyp; - bhitpos.x = mon->mx; - bhitpos.y = mon->my; + gb.bhitpos.x = mon->mx; + gb.bhitpos.y = mon->my; ddx = sgn(mon->mux - mon->mx); ddy = sgn(mon->muy - mon->my); while (range-- > 0) { - int x, y; + coordxy x, y, dbx, dby; - bhitpos.x += ddx; - bhitpos.y += ddy; - x = bhitpos.x; - y = bhitpos.y; + gb.bhitpos.x += ddx; + gb.bhitpos.y += ddy; + x = gb.bhitpos.x; + y = gb.bhitpos.y; if (!isok(x, y)) { - bhitpos.x -= ddx; - bhitpos.y -= ddy; + gb.bhitpos.x -= ddx; + gb.bhitpos.y -= ddy; break; } - if (find_drawbridge(&x, &y)) - switch (obj->otyp) { - case WAN_STRIKING: - destroy_drawbridge(x, y); - } - if (bhitpos.x == u.ux && bhitpos.y == u.uy) { - (*fhitm)(&youmonst, obj); + if (u_at(gb.bhitpos.x, gb.bhitpos.y)) { + (*fhitm)(&gy.youmonst, obj); range -= 3; - } else if ((mtmp = m_at(bhitpos.x, bhitpos.y)) != 0) { - if (cansee(bhitpos.x, bhitpos.y) && !canspotmon(mtmp)) - map_invisible(bhitpos.x, bhitpos.y); + } else if ((mtmp = m_at(gb.bhitpos.x, gb.bhitpos.y)) != 0) { + if (cansee(gb.bhitpos.x, gb.bhitpos.y) && !canspotmon(mtmp)) + map_invisible(gb.bhitpos.x, gb.bhitpos.y); (*fhitm)(mtmp, obj); range -= 3; } - /* modified by GAN to hit all objects */ - if (fhito) { - int hitanything = 0; - register struct obj *next_obj; - - for (otmp = level.objects[bhitpos.x][bhitpos.y]; otmp; - otmp = next_obj) { - /* Fix for polymorph bug, Tim Wright */ - next_obj = otmp->nexthere; - hitanything += (*fhito)(otmp, obj); - } - if (hitanything) - range--; - } - typ = levl[bhitpos.x][bhitpos.y].typ; - if (IS_DOOR(typ) || typ == SDOOR) { - switch (obj->otyp) { + if (fhito_loc(obj, gb.bhitpos.x, gb.bhitpos.y, fhito)) + range--; + ltyp = levl[gb.bhitpos.x][gb.bhitpos.y].typ; + dbx = x, dby = y; + if (otyp == WAN_STRIKING + /* if levl[x][y].typ is DRAWBRIDGE_UP then the zap is passing + over the moat in front of a closed drawbridge and doesn't + hit any part of the bridge's mechanism (yet; it might be + about to hit the closed portcullis on the next iteration) */ + && ltyp != DRAWBRIDGE_UP && find_drawbridge(&dbx, &dby)) { + /* this might kill mon and destroy obj but they'll remain + accessible; (*fhitm)() and (*fhito)() use obj for zap type */ + destroy_drawbridge(dbx, dby); + } else if (IS_DOOR(ltyp) || ltyp == SDOOR) { + switch (otyp) { /* note: monsters don't use opening or locking magic at present, but keep these as placeholders */ case WAN_OPENING: case WAN_LOCKING: case WAN_STRIKING: - if (doorlock(obj, bhitpos.x, bhitpos.y)) { - if (zap_oseen) - makeknown(obj->otyp); + if (doorlock(obj, gb.bhitpos.x, gb.bhitpos.y)) { + if (gz.zap_oseen) + makeknown(otyp); /* if a shop door gets broken, add it to the shk's fix list (no cost to player) */ - if (levl[bhitpos.x][bhitpos.y].doormask == D_BROKEN - && *in_rooms(bhitpos.x, bhitpos.y, SHOPBASE)) - add_damage(bhitpos.x, bhitpos.y, 0L); + if (levl[gb.bhitpos.x][gb.bhitpos.y].doormask == D_BROKEN + && *in_rooms(gb.bhitpos.x, gb.bhitpos.y, SHOPBASE)) + add_damage(gb.bhitpos.x, gb.bhitpos.y, 0L); } break; } } - if (!ZAP_POS(typ) - || (IS_DOOR(typ) && (levl[bhitpos.x][bhitpos.y].doormask - & (D_LOCKED | D_CLOSED)))) { - bhitpos.x -= ddx; - bhitpos.y -= ddy; + if (!ZAP_POS(ltyp) + || (IS_DOOR(ltyp) && (levl[gb.bhitpos.x][gb.bhitpos.y].doormask + & (D_LOCKED | D_CLOSED)))) { + gb.bhitpos.x -= ddx; + gb.bhitpos.y -= ddy; break; } } } +staticfn void +buzz_force_miss(int type, int nd, coordxy sx, coordxy sy, int dx, int dy) +{ + dobuzz(type, nd, sx, sy, dx, dy, TRUE, FALSE, TRUE); +} + /* Perform an offensive action for a monster. Must be called immediately * after find_offensive(). Return values are same as use_defensive(). */ int -use_offensive(mtmp) -struct monst *mtmp; +use_offensive(struct monst *mtmp) { int i; - struct obj *otmp = m.offensive; + struct obj *otmp = gm.m.offensive; boolean oseen; + /* if a monster has never used an attack wand before, it takes them some + time to get used to holding that much power, so the first shot always + misses */ + void (*buzzfn)(int, int, coordxy, coordxy, int, int) = + mtmp->mwandexp ? buzz : buzz_force_miss; + /* offensive potions are not drunk, they're thrown */ if (otmp->oclass != POTION_CLASS && (i = precheck(mtmp, otmp)) != 0) return i; - oseen = otmp && canseemon(mtmp); + oseen = canseemon(mtmp); - switch (m.has_offense) { + switch (gm.m.has_offense) { case MUSE_WAN_DEATH: case MUSE_WAN_SLEEP: case MUSE_WAN_FIRE: @@ -1425,36 +1848,53 @@ struct monst *mtmp; mzapwand(mtmp, otmp, FALSE); if (oseen) makeknown(otmp->otyp); - m_using = TRUE; - buzz((int) (-30 - (otmp->otyp - WAN_MAGIC_MISSILE)), - (otmp->otyp == WAN_MAGIC_MISSILE) ? 2 : 6, mtmp->mx, mtmp->my, - sgn(mtmp->mux - mtmp->mx), sgn(mtmp->muy - mtmp->my)); - m_using = FALSE; + gm.m_using = TRUE; + gc.current_wand = otmp; + gb.buzzer = mtmp; + buzzfn(BZ_M_WAND(BZ_OFS_WAN(otmp->otyp)), + (otmp->otyp == WAN_MAGIC_MISSILE) ? 2 : 6, mtmp->mx, mtmp->my, + sgn(mtmp->mux - mtmp->mx), sgn(mtmp->muy - mtmp->my)); + gb.buzzer = 0; + gc.current_wand = 0; + gm.m_using = FALSE; + mtmp->mwandexp = TRUE; return (DEADMONSTER(mtmp)) ? 1 : 2; case MUSE_FIRE_HORN: case MUSE_FROST_HORN: mplayhorn(mtmp, otmp, FALSE); - m_using = TRUE; - buzz(-30 - ((otmp->otyp == FROST_HORN) ? AD_COLD - 1 : AD_FIRE - 1), + gm.m_using = TRUE; + gb.buzzer = mtmp; + gc.current_wand = otmp; /* needed by zhitu() */ + buzzfn(BZ_M_WAND(BZ_OFS_AD( + (otmp->otyp == FROST_HORN) ? AD_COLD : AD_FIRE)), rn1(6, 6), mtmp->mx, mtmp->my, sgn(mtmp->mux - mtmp->mx), sgn(mtmp->muy - mtmp->my)); - m_using = FALSE; + gb.buzzer = 0; + gc.current_wand = 0; + gm.m_using = FALSE; + mtmp->mwandexp = TRUE; return (DEADMONSTER(mtmp)) ? 1 : 2; case MUSE_WAN_TELEPORTATION: + case MUSE_WAN_UNDEAD_TURNING: case MUSE_WAN_STRIKING: - zap_oseen = oseen; + gz.zap_oseen = oseen; mzapwand(mtmp, otmp, FALSE); - m_using = TRUE; + gm.m_using = TRUE; + gb.buzzer = mtmp; mbhit(mtmp, rn1(8, 6), mbhitm, bhito, otmp); - m_using = FALSE; + gb.buzzer = 0; + /* note: 'otmp' might have been destroyed (drawbridge destruction) */ + gm.m_using = FALSE; + if (gm.m.has_offense == MUSE_WAN_STRIKING) + mtmp->mwandexp = TRUE; return 2; case MUSE_SCR_EARTH: { /* TODO: handle steeds */ - register int x, y; + coordxy x, y; /* don't use monster fields after killing it */ boolean confused = (mtmp->mconf ? TRUE : FALSE); int mmx = mtmp->mx, mmy = mtmp->my; - boolean is_cursed = otmp->cursed; + boolean is_cursed = otmp->cursed, is_blessed = otmp->blessed; mreadmsg(mtmp, otmp); /* Identify the scroll */ @@ -1472,27 +1912,47 @@ struct monst *mtmp; makeknown(otmp->otyp); } + /* could be fatal to monster, so use up the scroll before + there's a chance that monster's inventory will be dropped */ + m_useup(mtmp, otmp); + /* Loop through the surrounding squares */ for (x = mmx - 1; x <= mmx + 1; x++) { for (y = mmy - 1; y <= mmy + 1; y++) { /* Is this a suitable spot? */ if (isok(x, y) && !closed_door(x, y) - && !IS_ROCK(levl[x][y].typ) && !IS_AIR(levl[x][y].typ) - && (((x == mmx) && (y == mmy)) ? !otmp->blessed - : !otmp->cursed) + && !IS_OBSTRUCTED(levl[x][y].typ) && !IS_AIR(levl[x][y].typ) + && (((x == mmx) && (y == mmy)) ? !is_blessed : !is_cursed) && (x != u.ux || y != u.uy)) { (void) drop_boulder_on_monster(x, y, confused, FALSE); } } } - m_useup(mtmp, otmp); /* Attack the player */ if (distmin(mmx, mmy, u.ux, u.uy) == 1 && !is_cursed) { drop_boulder_on_player(confused, !is_cursed, FALSE, TRUE); } return (DEADMONSTER(mtmp)) ? 1 : 2; - } + } /* case MUSE_SCR_EARTH */ + case MUSE_CAMERA: { + if (Hallucination) { + SetVoice(mtmp, 0, 80, 0); + verbalize("Say cheese!"); + } else if (!Blind) { + pline("%s takes a picture of you with %s!", + Monnam(mtmp), an(xname(otmp))); + } + gm.m_using = TRUE; + if (!Blind && !resists_blnd(&gy.youmonst)) { + You("are blinded by the flash of light!"); + make_blinded(BlindedTimeout + (long) rnd(1 + 50), FALSE); + } + lightdamage(otmp, TRUE, 5); + gm.m_using = FALSE; + otmp->spe--; + return 1; + } /* case MUSE_CAMERA */ #if 0 case MUSE_SCR_FIRE: { boolean vis = cansee(mtmp->mx, mtmp->my); @@ -1508,10 +1968,11 @@ struct monst *mtmp; if (vis) pline_The("scroll erupts in a tower of flame!"); shieldeff(mtmp->mx, mtmp->my); - pline("%s is uninjured.", Monnam(mtmp)); + pline_mon(mtmp, "%s is uninjured.", Monnam(mtmp)); (void) destroy_mitem(mtmp, SCROLL_CLASS, AD_FIRE); (void) destroy_mitem(mtmp, SPBOOK_CLASS, AD_FIRE); (void) destroy_mitem(mtmp, POTION_CLASS, AD_FIRE); + ignite_items(mtmp->minvent); num = (2 * (rn1(3, 3) + 2 * bcsign(otmp)) + 1) / 3; if (Fire_resistance) You("are not harmed."); @@ -1552,8 +2013,9 @@ struct monst *mtmp; * are not objects. Also set dknown in mthrowu.c. */ if (cansee(mtmp->mx, mtmp->my)) { - otmp->dknown = 1; - pline("%s hurls %s!", Monnam(mtmp), singular(otmp, doname)); + observe_object(otmp); + pline_mon(mtmp, "%s hurls %s!", + Monnam(mtmp), singular(otmp, doname)); } m_throw(mtmp, mtmp->mx, mtmp->my, sgn(mtmp->mux - mtmp->mx), sgn(mtmp->muy - mtmp->my), @@ -1563,15 +2025,14 @@ struct monst *mtmp; return 0; /* i.e. an exploded wand */ default: impossible("%s wanted to perform action %d?", Monnam(mtmp), - m.has_offense); + gm.m.has_offense); break; } return 0; } int -rnd_offensive_item(mtmp) -struct monst *mtmp; +rnd_offensive_item(struct monst *mtmp) { struct permonst *pm = mtmp->data; int difficulty = mons[(monsndx(pm))].difficulty; @@ -1583,12 +2044,14 @@ struct monst *mtmp; return WAN_DEATH; switch (rn2(9 - (difficulty < 4) + 4 * (difficulty > 6))) { case 0: { - struct obj *helmet = which_armor(mtmp, W_ARMH); + struct obj *mtmp_helmet = which_armor(mtmp, W_ARMH); - if ((helmet && is_metallic(helmet)) || amorphous(pm) + if (hard_helmet(mtmp_helmet) || amorphous(pm) || passes_walls(pm) || noncorporeal(pm) || unsolid(pm)) return SCR_EARTH; - } /* fall through */ + } + FALLTHROUGH; + /* FALLTHRU */ case 1: return WAN_STRIKING; case 2: @@ -1626,21 +2089,22 @@ struct monst *mtmp; #define MUSE_WAN_SPEED_MONSTER 7 #define MUSE_BULLWHIP 8 #define MUSE_POT_POLYMORPH 9 +#define MUSE_BAG 10 boolean -find_misc(mtmp) -struct monst *mtmp; +find_misc(struct monst *mtmp) { - register struct obj *obj; + struct obj *obj; struct permonst *mdat = mtmp->data; - int x = mtmp->mx, y = mtmp->my; + coordxy x = mtmp->mx, y = mtmp->my; struct trap *t; - int xx, yy, pmidx = NON_PM; + coordxy xx, yy; + int pmidx = NON_PM; boolean immobile = (mdat->mmove == 0); boolean stuck = (mtmp == u.ustuck); - m.misc = (struct obj *) 0; - m.has_misc = 0; + gm.m.misc = (struct obj *) 0; + gm.m.has_misc = 0; if (is_animal(mdat) || mindless(mdat)) return 0; if (u.uswallow && stuck) @@ -1653,7 +2117,7 @@ struct monst *mtmp; if (dist2(x, y, mtmp->mux, mtmp->muy) > 36) return FALSE; - if (!stuck && !immobile && (mtmp->cham == NON_PM) + if (!stuck && !immobile && !mtmp->mtrapped && (mtmp->cham == NON_PM) && mons[(pmidx = monsndx(mdat))].difficulty < 6) { boolean ignore_boulders = (verysmall(mdat) || throws_rocks(mdat) || passes_walls(mdat)), @@ -1661,17 +2125,19 @@ struct monst *mtmp; for (xx = x - 1; xx <= x + 1; xx++) for (yy = y - 1; yy <= y + 1; yy++) - if (isok(xx, yy) && (xx != u.ux || yy != u.uy) + if (isok(xx, yy) && !u_at(xx, yy) && (diag_ok || xx == x || yy == y) - && ((xx == x && yy == y) || !level.monsters[xx][yy])) + && ((xx == x && yy == y) || !svl.level.monsters[xx][yy])) if ((t = t_at(xx, yy)) != 0 && (ignore_boulders || !sobj_at(BOULDER, xx, yy)) && !onscary(xx, yy, mtmp)) { - /* use trap if it's the correct type */ - if (t->ttyp == POLY_TRAP) { - trapx = xx; - trapy = yy; - m.has_misc = MUSE_POLY_TRAP; + /* use trap if it's the correct type and will + polymorph the monster */ + if (t->ttyp == POLY_TRAP && + !wearing_iron_shoes(mtmp)) { + gt.trapx = xx; + gt.trapy = yy; + gm.m.has_misc = MUSE_POLY_TRAP; return TRUE; } } @@ -1679,12 +2145,26 @@ struct monst *mtmp; if (nohands(mdat)) return 0; -#define nomore(x) if (m.has_misc == x) continue + /* normally we would want to bracket a macro expansion containing + 'if' without matching 'else' with 'do { ... } while (0)' but we + can't do that here because it would intercept 'continue' */ +#define nomore(x) if (gm.m.has_misc == (x)) continue /* * [bug?] Choice of item is not prioritized; the last viable one * in the monster's inventory will be chosen. * 'nomore()' is nearly worthless because it only screens checking * of duplicates when there is no alternate type in between them. + * + * MUSE_BAG issues: + * should allow looting floor container instead of needing the + * monster to have picked it up and now be carrying it which takes + * extra time and renders heavily filled containers immune; + * hero should have a chance to see the monster fail to open a + * locked container instead of monster always knowing lock state + * (may not be feasible to implement--requires too much per-object + * info for each monster); + * monster with key should be able to unlock a locked floor + * container and not know whether it is trapped. */ for (obj = mtmp->minvent; obj; obj = obj->nobj) { /* Monsters shouldn't recognize cursed items; this kludge is @@ -1692,8 +2172,8 @@ struct monst *mtmp; if (obj->otyp == POT_GAIN_LEVEL && (!obj->cursed || (!mtmp->isgd && !mtmp->isshk && !mtmp->ispriest))) { - m.misc = obj; - m.has_misc = MUSE_POT_GAIN_LEVEL; + gm.m.misc = obj; + gm.m.has_misc = MUSE_POT_GAIN_LEVEL; } nomore(MUSE_BULLWHIP); if (obj->otyp == BULLWHIP && !mtmp->mpeaceful @@ -1701,14 +2181,15 @@ struct monst *mtmp; monster from attempting disarm every turn */ && uwep && !rn2(5) && obj == MON_WEP(mtmp) /* hero's location must be known and adjacent */ - && mtmp->mux == u.ux && mtmp->muy == u.uy - && distu(mtmp->mx, mtmp->my) <= 2 + && u_at(mtmp->mux, mtmp->muy) + && m_next2u(mtmp) /* don't bother if it can't work (this doesn't - prevent cursed weapons from being targetted) */ + prevent cursed weapons from being targeted) */ + && !u.uswallow && (canletgo(uwep, "") || (u.twoweap && canletgo(uswapwep, "")))) { - m.misc = obj; - m.has_misc = MUSE_BULLWHIP; + gm.m.misc = obj; + gm.m.has_misc = MUSE_BULLWHIP; } /* Note: peaceful/tame monsters won't make themselves * invisible unless you can see them. Not really right, but... @@ -1717,49 +2198,56 @@ struct monst *mtmp; if (obj->otyp == WAN_MAKE_INVISIBLE && obj->spe > 0 && !mtmp->minvis && !mtmp->invis_blkd && (!mtmp->mpeaceful || See_invisible) && (!attacktype(mtmp->data, AT_GAZE) || mtmp->mcan)) { - m.misc = obj; - m.has_misc = MUSE_WAN_MAKE_INVISIBLE; + gm.m.misc = obj; + gm.m.has_misc = MUSE_WAN_MAKE_INVISIBLE; } nomore(MUSE_POT_INVISIBILITY); if (obj->otyp == POT_INVISIBILITY && !mtmp->minvis && !mtmp->invis_blkd && (!mtmp->mpeaceful || See_invisible) && (!attacktype(mtmp->data, AT_GAZE) || mtmp->mcan)) { - m.misc = obj; - m.has_misc = MUSE_POT_INVISIBILITY; + gm.m.misc = obj; + gm.m.has_misc = MUSE_POT_INVISIBILITY; } nomore(MUSE_WAN_SPEED_MONSTER); if (obj->otyp == WAN_SPEED_MONSTER && obj->spe > 0 && mtmp->mspeed != MFAST && !mtmp->isgd) { - m.misc = obj; - m.has_misc = MUSE_WAN_SPEED_MONSTER; + gm.m.misc = obj; + gm.m.has_misc = MUSE_WAN_SPEED_MONSTER; } nomore(MUSE_POT_SPEED); if (obj->otyp == POT_SPEED && mtmp->mspeed != MFAST && !mtmp->isgd) { - m.misc = obj; - m.has_misc = MUSE_POT_SPEED; + gm.m.misc = obj; + gm.m.has_misc = MUSE_POT_SPEED; } nomore(MUSE_WAN_POLYMORPH); if (obj->otyp == WAN_POLYMORPH && obj->spe > 0 && (mtmp->cham == NON_PM) && mons[monsndx(mdat)].difficulty < 6) { - m.misc = obj; - m.has_misc = MUSE_WAN_POLYMORPH; + gm.m.misc = obj; + gm.m.has_misc = MUSE_WAN_POLYMORPH; } nomore(MUSE_POT_POLYMORPH); if (obj->otyp == POT_POLYMORPH && (mtmp->cham == NON_PM) && mons[monsndx(mdat)].difficulty < 6) { - m.misc = obj; - m.has_misc = MUSE_POT_POLYMORPH; + gm.m.misc = obj; + gm.m.has_misc = MUSE_POT_POLYMORPH; + } + nomore(MUSE_BAG); + if (Is_container(obj) && obj->otyp != BAG_OF_TRICKS && !rn2(5) + && !SchroedingersBox(obj) + && !gm.m.has_misc && Has_contents(obj) + && !obj->olocked && !obj->otrapped) { + gm.m.misc = obj; + gm.m.has_misc = MUSE_BAG; } } - return (boolean) !!m.has_misc; + return (boolean) !!gm.m.has_misc; #undef nomore } /* type of monster to polymorph into; defaults to one suitable for the current level rather than the totally arbitrary choice of newcham() */ -static struct permonst * -muse_newcham_mon(mon) -struct monst *mon; +staticfn struct permonst * +muse_newcham_mon(struct monst *mon) { struct obj *m_armr; @@ -1772,14 +2260,134 @@ struct monst *mon; return rndmonst(); } +staticfn int +mloot_container( + struct monst *mon, + struct obj *container, + boolean vismon) +{ + char contnr_nam[BUFSZ], mpronounbuf[20]; + boolean nearby; + int takeout_indx, takeout_count, howfar, res = 0; + + if (!container || !Has_contents(container) || container->olocked) + return res; /* 0 */ + /* FIXME: handle cursed bag of holding */ + if (Is_mbag(container) && container->cursed) + return res; /* 0 */ + if (SchroedingersBox(container)) + return res; + + switch (rn2(10)) { + default: /* case 0, 1, 2, 3: */ + takeout_count = 1; + break; + case 4: case 5: case 6: + takeout_count = 2; + break; + case 7: case 8: + takeout_count = 3; + break; + case 9: + takeout_count = 4; + break; + } + howfar = mdistu(mon); + nearby = (howfar <= 7 * 7); + contnr_nam[0] = mpronounbuf[0] = '\0'; + if (vismon) { + /* do this once so that when hallucinating it won't change + from one item to the next */ + Strcpy(mpronounbuf, mhe(mon)); + } + + for (takeout_indx = 0; takeout_indx < takeout_count; ++takeout_indx) { + struct obj *xobj; + int nitems; + + if (!Has_contents(container)) /* might have removed all items */ + break; + /* TODO? + * Monster ought to prioritize on something it wants to use. + */ + nitems = 0; + for (xobj = container->cobj; xobj != 0; xobj = xobj->nobj) + ++nitems; + /* nitems is always greater than 0 due to Has_contents() check; + throttle item removal as the container becomes less filled */ + if (!rn2(nitems + 1)) + break; + nitems = rn2(nitems); + for (xobj = container->cobj; xobj != 0; xobj = xobj->nobj) + if (--nitems < 0) + break; + assert(xobj != NULL); + + container->cknown = 0; /* hero no longer knows container's contents + * even if [attempted] removal is observed */ + if (!*contnr_nam) { + /* xname sets dknown, distant_name might depending on its own + idea about nearness */ + Strcpy(contnr_nam, an(nearby ? xname(container) + : distant_name(container, xname))); + } + /* this was originally just 'can_carry(mon, xobj)' which + covers objects a monster shouldn't pick up but also + checks carrying capacity; for that, it ended up counting + xobj's weight twice when container is carried; so take + xobj out, check whether it can be carried, and then put + it back (below) if it can't be */ + obj_extract_self(xobj); /* this reduces container's weight */ + /* check whether mon can handle xobj and whether weight of xobj plus + minvent (including container, now without xobj) can be carried */ + if (can_carry(mon, xobj)) { + if (vismon) { + if (howfar > 2) /* not adjacent */ + Norep("%s rummages through %s.", Monnam(mon), contnr_nam); + else if (takeout_indx == 0) /* adjacent, first item */ + pline_mon(mon, "%s removes %s from %s.", Monnam(mon), + doname(xobj), contnr_nam); + else /* adjacent, additional items */ + pline("%s removes %s.", upstart(mpronounbuf), + doname(xobj)); + } + if (container->otyp == ICE_BOX) + removed_from_icebox(xobj); /* resume rotting for corpse */ + /* obj_extract_self(xobj); -- already done above */ + (void) mpickobj(mon, xobj); + res = 2; + } else { /* couldn't carry xobj separately so put back inside */ + /* an achievement prize (castle's wand?) might already be + marked nomerge (when it hasn't been in invent yet) */ + boolean already_nomerge = xobj->nomerge != 0, + just_xobj = !Has_contents(container); + + /* this doesn't restore the original contents ordering + [shouldn't be a problem; even though this item didn't + give the rummage message, that's what mon was doing] */ + xobj->nomerge = 1; + xobj = add_to_container(container, xobj); + if (!already_nomerge) + xobj->nomerge = 0; + container->owt = weight(container); + if (just_xobj) + break; /* out of takeout_count loop */ + } /* can_carry */ + } /* takeout_count */ + return res; +} + +DISABLE_WARNING_UNREACHABLE_CODE + int -use_misc(mtmp) -struct monst *mtmp; +use_misc(struct monst *mtmp) { - int i; - struct obj *otmp = m.misc; - boolean vis, vismon, oseen; + static const char MissingMiscellaneousItem[] = "use_misc: no %s"; char nambuf[BUFSZ]; + boolean vis, vismon, vistrapspot, oseen; + int i; + struct trap *t; + struct obj *otmp = gm.m.misc; if ((i = precheck(mtmp, otmp)) != 0) return i; @@ -1787,12 +2395,14 @@ struct monst *mtmp; vismon = canseemon(mtmp); oseen = otmp && vismon; - switch (m.has_misc) { + switch (gm.m.has_misc) { case MUSE_POT_GAIN_LEVEL: + if (!otmp) + panic(MissingMiscellaneousItem, "potion of gain level"); mquaffmsg(mtmp, otmp); if (otmp->cursed) { if (Can_rise_up(mtmp->mx, mtmp->my, &u.uz)) { - register int tolev = depth(&u.uz) - 1; + int tolev = depth(&u.uz) - 1; d_level tolevel; get_level(&tolevel, tolev); @@ -1800,11 +2410,10 @@ struct monst *mtmp; if (on_level(&tolevel, &u.uz)) goto skipmsg; if (vismon) { - pline("%s rises up, through the %s!", Monnam(mtmp), - ceiling(mtmp->mx, mtmp->my)); - if (!objects[POT_GAIN_LEVEL].oc_name_known - && !objects[POT_GAIN_LEVEL].oc_uname) - docall(otmp); + pline_mon(mtmp, "%s rises up, through the %s!", + Monnam(mtmp), + ceiling(mtmp->mx, mtmp->my)); + trycall(otmp); } m_useup(mtmp, otmp); migrate_to_level(mtmp, ledger_no(&tolevel), MIGR_RANDOM, @@ -1813,17 +2422,15 @@ struct monst *mtmp; } else { skipmsg: if (vismon) { - pline("%s looks uneasy.", Monnam(mtmp)); - if (!objects[POT_GAIN_LEVEL].oc_name_known - && !objects[POT_GAIN_LEVEL].oc_uname) - docall(otmp); + pline_mon(mtmp, "%s looks uneasy.", Monnam(mtmp)); + trycall(otmp); } m_useup(mtmp, otmp); return 2; } } if (vismon) - pline("%s seems more experienced.", Monnam(mtmp)); + pline_mon(mtmp, "%s seems more experienced.", Monnam(mtmp)); if (oseen) makeknown(POT_GAIN_LEVEL); m_useup(mtmp, otmp); @@ -1833,13 +2440,15 @@ struct monst *mtmp; return 2; case MUSE_WAN_MAKE_INVISIBLE: case MUSE_POT_INVISIBILITY: + if (!otmp) + panic(MissingMiscellaneousItem, "potion of invisibility"); if (otmp->otyp == WAN_MAKE_INVISIBLE) { mzapwand(mtmp, otmp, TRUE); } else mquaffmsg(mtmp, otmp); /* format monster's name before altering its visibility */ Strcpy(nambuf, mon_nam(mtmp)); - mon_set_minvis(mtmp); + mon_set_minvis(mtmp, !otmp->cursed ? FALSE : TRUE); if (vismon && mtmp->minvis) { /* was seen, now invisible */ if (canspotmon(mtmp)) { pline("%s body takes on a %s transparency.", @@ -1852,6 +2461,17 @@ struct monst *mtmp; } if (oseen) makeknown(otmp->otyp); + } else if (vismon && !mtmp->minvis) { + /* cursed potion; mon tried to make itself invisible but failed */ + pline("%s briefly seems to be transparent.", Monnam(mtmp)); + /* we could call map_invisible() before the pline(), then + newsym() after; unseen monster glyph would be visible during + the pline, but hero would forget any remembered object under + the monster */ + } else if (!vismon && canseemon(mtmp)) { + /* cursed potion; this won't happen because a monster will only + drink a potion of invisibility when not already invisible */ + pline("%s suddenly appears!", Monnam(mtmp)); } if (otmp->otyp == POT_INVISIBILITY) { if (otmp->cursed) @@ -1860,10 +2480,14 @@ struct monst *mtmp; } return 2; case MUSE_WAN_SPEED_MONSTER: + if (!otmp) + panic(MissingMiscellaneousItem, "wand of speed monster"); mzapwand(mtmp, otmp, TRUE); mon_adjust_speed(mtmp, 1, otmp); return 2; case MUSE_POT_SPEED: + if (!otmp) + panic(MissingMiscellaneousItem, "potion of speed"); mquaffmsg(mtmp, otmp); /* note difference in potion effect due to substantially different methods of maintaining speed ratings: @@ -1873,40 +2497,55 @@ struct monst *mtmp; m_useup(mtmp, otmp); return 2; case MUSE_WAN_POLYMORPH: + if (!otmp) + panic(MissingMiscellaneousItem, "wand of polymorph"); mzapwand(mtmp, otmp, TRUE); - (void) newcham(mtmp, muse_newcham_mon(mtmp), TRUE, FALSE); + (void) newcham(mtmp, muse_newcham_mon(mtmp), + NC_VIA_WAND_OR_SPELL | NC_SHOW_MSG); if (oseen) makeknown(WAN_POLYMORPH); return 2; case MUSE_POT_POLYMORPH: + if (!otmp) + panic(MissingMiscellaneousItem, "potion of polymorph"); mquaffmsg(mtmp, otmp); + m_useup(mtmp, otmp); if (vismon) - pline("%s suddenly mutates!", Monnam(mtmp)); - (void) newcham(mtmp, muse_newcham_mon(mtmp), FALSE, FALSE); + pline_mon(mtmp, "%s suddenly mutates!", Monnam(mtmp)); + (void) newcham(mtmp, muse_newcham_mon(mtmp), NC_SHOW_MSG); if (oseen) makeknown(POT_POLYMORPH); - m_useup(mtmp, otmp); return 2; case MUSE_POLY_TRAP: - if (vismon) { - const char *Mnam = Monnam(mtmp); - - pline("%s deliberately %s onto a polymorph trap!", Mnam, - vtense(fakename[0], locomotion(mtmp->data, "jump"))); + t = t_at(gt.trapx, gt.trapy); + vistrapspot = cansee(t->tx, t->ty); + if (vis || vistrapspot) + seetrap(t); + if (vismon || vistrapspot) { + pline_mon(mtmp, "%s deliberately %s onto a %s!", Some_Monnam(mtmp), + vtense(fakename[0], locomotion(mtmp->data, "jump")), + t->tseen ? trapname(t->ttyp, FALSE) : "hidden trap"); + /* note: if mtmp is unseen because it is invisible, its new + shape will also be invisible and could produce "Its armor + falls off" messages during the transformation; those make + more sense after we've given "Someone jumps onto a trap." */ } - if (vis) - seetrap(t_at(trapx, trapy)); /* don't use rloc() due to worms */ remove_monster(mtmp->mx, mtmp->my); newsym(mtmp->mx, mtmp->my); - place_monster(mtmp, trapx, trapy); + place_monster(mtmp, gt.trapx, gt.trapy); + maybe_unhide_at(gt.trapx, gt.trapy); if (mtmp->wormno) worm_move(mtmp); - newsym(trapx, trapy); + newsym(gt.trapx, gt.trapy); - (void) newcham(mtmp, (struct permonst *) 0, FALSE, FALSE); + (void) newcham(mtmp, (struct permonst *) 0, NC_SHOW_MSG); return 2; + case MUSE_BAG: + if (!otmp) + panic(MissingMiscellaneousItem, "container"); + return mloot_container(mtmp, otmp, vismon); case MUSE_BULLWHIP: /* attempt to disarm hero */ { @@ -1914,7 +2553,7 @@ struct monst *mtmp; int where_to = rn2(4); struct obj *obj = uwep; const char *hand; - char the_weapon[BUFSZ]; + char the_weapon[BUFSZ], hand_buf[BUFSZ]; if (!obj || !canletgo(obj, "") || (u.twoweap && canletgo(uswapwep, "") && rn2(2))) @@ -1926,19 +2565,21 @@ struct monst *mtmp; hand = body_part(HAND); if (bimanual(obj)) hand = makeplural(hand); + (void) strncpy(hand_buf, hand, sizeof hand_buf - 1); + hand_buf[sizeof hand_buf - 1] = '\0'; if (vismon) - pline("%s flicks a bullwhip towards your %s!", Monnam(mtmp), - hand); + pline_mon(mtmp, "%s flicks a bullwhip towards your %s!", + Monnam(mtmp), hand_buf); if (obj->otyp == HEAVY_IRON_BALL) { pline("%s fails to wrap around %s.", The_whip, the_weapon); return 1; } - pline("%s wraps around %s you're wielding!", The_whip, - the_weapon); + urgent_pline("%s wraps around %s you're wielding!", The_whip, + the_weapon); if (welded(obj)) { pline("%s welded to your %s%c", - !is_plural(obj) ? "It is" : "They are", hand, + !is_plural(obj) ? "It is" : "They are", hand_buf, !obj->bknown ? '!' : '.'); /* obj->bknown = 1; */ /* welded() takes care of this */ where_to = 0; @@ -1956,36 +2597,38 @@ struct monst *mtmp; freeinv(obj); switch (where_to) { case 1: /* onto floor beneath mon */ - pline("%s yanks %s from your %s!", Monnam(mtmp), the_weapon, - hand); + pline_mon(mtmp, "%s yanks %s from your %s!", Monnam(mtmp), + the_weapon, hand_buf); place_object(obj, mtmp->mx, mtmp->my); break; case 2: /* onto floor beneath you */ - pline("%s yanks %s to the %s!", Monnam(mtmp), the_weapon, - surface(u.ux, u.uy)); + pline_mon(mtmp, "%s yanks %s to the %s!", Monnam(mtmp), + the_weapon, surface(u.ux, u.uy)); dropy(obj); break; case 3: /* into mon's inventory */ - pline("%s snatches %s!", Monnam(mtmp), the_weapon); + pline_mon(mtmp, "%s snatches %s!", Monnam(mtmp), the_weapon); (void) mpickobj(mtmp, obj); break; } return 1; } + /*NOTREACHED*/ return 0; case 0: return 0; /* i.e. an exploded wand */ default: impossible("%s wanted to perform action %d?", Monnam(mtmp), - m.has_misc); + gm.m.has_misc); break; } return 0; } -STATIC_OVL void -you_aggravate(mtmp) -struct monst *mtmp; +RESTORE_WARNINGS + +staticfn void +you_aggravate(struct monst *mtmp) { pline("For some reason, %s presence is known to you.", s_suffix(noit_mon_nam(mtmp))); @@ -1999,8 +2642,8 @@ struct monst *mtmp; display_nhwindow(WIN_MAP, TRUE); docrt(); if (unconscious()) { - multi = -1; - nomovemsg = "Aggravated, you are jolted into full consciousness."; + gm.multi = -1; + gn.nomovemsg = "Aggravated, you are jolted into full consciousness."; } newsym(mtmp->mx, mtmp->my); if (!canspotmon(mtmp)) @@ -2008,8 +2651,7 @@ struct monst *mtmp; } int -rnd_misc_item(mtmp) -struct monst *mtmp; +rnd_misc_item(struct monst *mtmp) { struct permonst *pm = mtmp->data; int difficulty = mons[(monsndx(pm))].difficulty; @@ -2043,13 +2685,35 @@ struct monst *mtmp; return 0; } +#if 0 +/* check whether hero is carrying a corpse or contained petrifier corpse */ +staticfn boolean +necrophiliac(struct obj *objlist, boolean any_corpse) +{ + while (objlist) { + if (objlist->otyp == CORPSE + && (any_corpse || touch_petrifies(&mons[objlist->corpsenm]))) + return TRUE; + if (Has_contents(objlist) && necrophiliac(objlist->cobj, FALSE)) + return TRUE; + objlist = objlist->nobj; + } + return FALSE; +} +#endif + boolean -searches_for_item(mon, obj) -struct monst *mon; -struct obj *obj; +searches_for_item(struct monst *mon, struct obj *obj) { int typ = obj->otyp; + /* don't let monsters interact with protected items on the floor */ + if (obj->where == OBJ_FLOOR + && (obj->ox == mon->mx && obj->oy == mon->my) + && onscary(obj->ox, obj->oy, mon)) { + return FALSE; + } + if (is_animal(mon->data) || mindless(mon->data) || mon->data == &mons[PM_GHOST]) /* don't loot bones piles */ return FALSE; @@ -2069,6 +2733,7 @@ struct obj *obj; if (typ == WAN_POLYMORPH) return (boolean) (mons[monsndx(mon->data)].difficulty < 6); if (objects[typ].oc_dir == RAY || typ == WAN_STRIKING + || typ == WAN_UNDEAD_TURNING || typ == WAN_TELEPORTATION || typ == WAN_CREATE_MONSTER) return TRUE; break; @@ -2089,16 +2754,22 @@ struct obj *obj; case AMULET_CLASS: if (typ == AMULET_OF_LIFE_SAVING) return (boolean) !(nonliving(mon->data) || is_vampshifter(mon)); - if (typ == AMULET_OF_REFLECTION) + if (typ == AMULET_OF_REFLECTION || typ == AMULET_OF_GUARDING) return TRUE; break; case TOOL_CLASS: if (typ == PICK_AXE) return (boolean) needspick(mon->data); if (typ == UNICORN_HORN) - return (boolean) (!obj->cursed && !is_unicorn(mon->data)); + return (boolean) (!obj->cursed && !is_unicorn(mon->data) + && mon->data != &mons[PM_KI_RIN]); if (typ == FROST_HORN || typ == FIRE_HORN) return (obj->spe > 0 && can_blow(mon)); + if (Is_container(obj) && !(Is_mbag(obj) && obj->cursed) + && !obj->olocked) + return TRUE; + if (typ == EXPENSIVE_CAMERA) + return (obj->spe > 0); break; case FOOD_CLASS: if (typ == CORPSE) @@ -2110,7 +2781,7 @@ struct obj *obj; return (boolean) (mcould_eat_tin(mon) && (!resists_ston(mon) && cures_stoning(mon, obj, TRUE))); - if (typ == EGG) + if (typ == EGG && ismnum(obj->corpsenm)) return (boolean) touch_petrifies(&mons[obj->corpsenm]); break; default: @@ -2120,10 +2791,10 @@ struct obj *obj; return FALSE; } +DISABLE_WARNING_FORMAT_NONLITERAL + boolean -mon_reflects(mon, str) -struct monst *mon; -const char *str; +mon_reflects(struct monst *mon, const char *str) { struct obj *orefl = which_armor(mon, W_ARMS); @@ -2162,8 +2833,7 @@ const char *str; } boolean -ureflects(fmt, str) -const char *fmt, *str; +ureflects(const char *fmt, const char *str) { /* Check from outermost to innermost objects */ if (EReflecting & W_ARMS) { @@ -2187,7 +2857,7 @@ const char *fmt, *str; if (fmt && str) pline(fmt, str, uskin ? "luster" : "armor"); return TRUE; - } else if (youmonst.data == &mons[PM_SILVER_DRAGON]) { + } else if (gy.youmonst.data == &mons[PM_SILVER_DRAGON]) { if (fmt && str) pline(fmt, str, "scales"); return TRUE; @@ -2195,32 +2865,30 @@ const char *fmt, *str; return FALSE; } +RESTORE_WARNING_FORMAT_NONLITERAL + /* cure mon's blindness (use_defensive, dog_eat, meatobj) */ void -mcureblindness(mon, verbos) -struct monst *mon; -boolean verbos; +mcureblindness(struct monst *mon, boolean verbos) { if (!mon->mcansee) { mon->mcansee = 1; mon->mblinded = 0; if (verbos && haseyes(mon->data)) - pline("%s can see again.", Monnam(mon)); + pline_mon(mon, "%s can see again.", Monnam(mon)); } } /* TRUE if the monster ate something */ boolean -munstone(mon, by_you) -struct monst *mon; -boolean by_you; +munstone(struct monst *mon, boolean by_you) { struct obj *obj; boolean tinok; if (resists_ston(mon)) return FALSE; - if (mon->meating || !mon->mcanmove || mon->msleeping) + if (mon->meating || helpless(mon)) return FALSE; mon->mstrategy &= ~STRAT_WAITFORU; @@ -2234,12 +2902,12 @@ boolean by_you; return FALSE; } -STATIC_OVL void -mon_consume_unstone(mon, obj, by_you, stoning) -struct monst *mon; -struct obj *obj; -boolean by_you; -boolean stoning; /* True: stop petrification, False: cure stun && confusion */ +staticfn void +mon_consume_unstone( + struct monst *mon, + struct obj *obj, + boolean by_you, + boolean stoning) /* T: stop petrification, F: cure stun && confusion */ { boolean vis = canseemon(mon), tinned = obj->otyp == TIN, food = obj->otyp == CORPSE || tinned, @@ -2257,11 +2925,10 @@ boolean stoning; /* True: stop petrification, False: cure stun && confusion */ long save_quan = obj->quan; obj->quan = 1L; - pline("%s %s %s.", Monnam(mon), - (obj->oclass == POTION_CLASS) - ? "quaffs" - : (obj->otyp == TIN) ? "opens and eats the contents of" - : "eats", + pline_mon(mon, "%s %s %s.", Monnam(mon), + ((obj->oclass == POTION_CLASS) ? "quaffs" + : (obj->otyp == TIN) ? "opens and eats the contents of" + : "eats"), distant_name(obj, doname)); obj->quan = save_quan; } else if (!Deaf) @@ -2274,9 +2941,9 @@ boolean stoning; /* True: stop petrification, False: cure stun && confusion */ if (acid && !tinned && !resists_acid(mon)) { mon->mhp -= rnd(15); if (vis) - pline("%s has a very bad case of stomach acid.", Monnam(mon)); + pline_mon(mon, "%s has a very bad case of stomach acid.", Monnam(mon)); if (DEADMONSTER(mon)) { - pline("%s dies!", Monnam(mon)); + pline_mon(mon, "%s dies!", Monnam(mon)); if (by_you) /* hero gets credit (experience) and blame (possible loss of alignment and/or luck and/or telepathy depending on @@ -2292,50 +2959,46 @@ boolean stoning; /* True: stop petrification, False: cure stun && confusion */ pline("What a pity - %s just ruined a future piece of art!", mon_nam(mon)); else - pline("%s seems limber!", Monnam(mon)); + pline_mon(mon, "%s seems limber!", Monnam(mon)); } if (lizard && (mon->mconf || mon->mstun)) { mon->mconf = 0; mon->mstun = 0; if (vis && !is_bat(mon->data) && mon->data != &mons[PM_STALKER]) - pline("%s seems steadier now.", Monnam(mon)); + pline_mon(mon, "%s seems steadier now.", Monnam(mon)); } if (mon->mtame && !mon->isminion && nutrit > 0) { struct edog *edog = EDOG(mon); - if (edog->hungrytime < monstermoves) - edog->hungrytime = monstermoves; + if (edog->hungrytime < svm.moves) + edog->hungrytime = svm.moves; edog->hungrytime += nutrit; mon->mconf = 0; } /* use up monster's next move */ mon->movement -= NORMAL_SPEED; - mon->mlstmv = monstermoves; + mon->mlstmv = svm.moves; } /* decide whether obj can cure petrification; also used when picking up */ -STATIC_OVL boolean -cures_stoning(mon, obj, tinok) -struct monst *mon; -struct obj *obj; -boolean tinok; +staticfn boolean +cures_stoning(struct monst *mon, struct obj *obj, boolean tinok) { if (obj->otyp == POT_ACID) return TRUE; + if (obj->otyp == GLOB_OF_GREEN_SLIME) + return (boolean) slimeproof(mon->data); if (obj->otyp != CORPSE && (obj->otyp != TIN || !tinok)) return FALSE; /* corpse, or tin that mon can open */ if (obj->corpsenm == NON_PM) /* empty/special tin */ return FALSE; return (boolean) (obj->corpsenm == PM_LIZARD - || (acidic(&mons[obj->corpsenm]) - && (obj->corpsenm != PM_GREEN_SLIME - || slimeproof(mon->data)))); + || acidic(&mons[obj->corpsenm])); } -STATIC_OVL boolean -mcould_eat_tin(mon) -struct monst *mon; +staticfn boolean +mcould_eat_tin(struct monst *mon) { struct obj *obj, *mwep; boolean welded_wep; @@ -2365,9 +3028,7 @@ struct monst *mon; /* TRUE if monster does something to avoid turning into green slime */ boolean -munslime(mon, by_you) -struct monst *mon; -boolean by_you; +munslime(struct monst *mon, boolean by_you) { struct obj *obj, odummy; struct permonst *mptr = mon->data; @@ -2381,7 +3042,7 @@ boolean by_you; if (slimeproof(mptr)) return FALSE; - if (mon->meating || !mon->mcanmove || mon->msleeping) + if (mon->meating || helpless(mon)) return FALSE; mon->mstrategy &= ~STRAT_WAITFORU; @@ -2392,7 +3053,7 @@ boolean by_you; spells could toss pillar of fire at self--probably too suicidal] */ if (!mon->mcan && !mon->mspec_used && attacktype_fordmg(mptr, AT_BREA, AD_FIRE)) { - odummy = zeroobj; /* otyp == STRANGE_OBJECT */ + odummy = cg.zeroobj; /* otyp == STRANGE_OBJECT */ return muse_unslime(mon, &odummy, (struct trap *) 0, by_you); } @@ -2406,7 +3067,7 @@ boolean by_you; if (((t = t_at(mon->mx, mon->my)) == 0 || t->ttyp != FIRE_TRAP) && mptr->mmove && !mon->mtrapped) { - int xy[2][8], x, y, idx, ridx, nxy = 0; + coordxy xy[2][8], x, y, idx, ridx, nxy = 0; for (x = mon->mx - 1; x <= mon->mx + 1; ++x) for (y = mon->my - 1; y <= mon->my + 1; ++y) @@ -2431,7 +3092,7 @@ boolean by_you; } } if (t && t->ttyp == FIRE_TRAP) - return muse_unslime(mon, (struct obj *) &zeroobj, t, by_you); + return muse_unslime(mon, &hands_obj, t, by_you); } /* MUSE */ @@ -2439,19 +3100,19 @@ boolean by_you; } /* mon uses an item--selected by caller--to burn away incipient slime */ -STATIC_OVL boolean -muse_unslime(mon, obj, trap, by_you) -struct monst *mon; -struct obj *obj; -struct trap *trap; -boolean by_you; /* true: if mon kills itself, hero gets credit/blame */ -{ /* [by_you not honored if 'mon' triggers fire trap]. */ +staticfn boolean +muse_unslime( + struct monst *mon, + struct obj *obj, + struct trap *trap, + boolean by_you) /* true: if mon kills itself, hero gets credit/blame */ +{ /* [by_you not honored if 'mon' triggers fire trap]. */ struct obj *odummyp; int otyp = obj->otyp, dmg = 0; boolean vis = canseemon(mon), res = TRUE; if (vis) - pline("%s starts turning %s.", Monnam(mon), + pline_mon(mon, "%s starts turning %s.", Monnam(mon), green_mon(mon) ? "into ooze" : hcolor(NH_GREEN)); /* -4 => sliming, causes quiet loss of enhanced speed */ mon_adjust_speed(mon, -4, (struct obj *) 0); @@ -2476,13 +3137,12 @@ boolean by_you; /* true: if mon kills itself, hero gets credit/blame */ is_floater(mon->data) ? "over" : "onto", trap->tseen ? "the" : "a"); } - /* hack to avoid mintrap()'s chance of avoiding known trap */ - mon->mtrapseen &= ~(1 << (FIRE_TRAP - 1)); - mintrap(mon); + (void) mintrap(mon, FORCETRAP); } else if (otyp == STRANGE_OBJECT) { /* monster is using fire breath on self */ if (vis) - pline("%s breathes fire on %sself.", Monnam(mon), mhim(mon)); + pline_mon(mon, "%s.", + monverbself(mon, Monnam(mon), "breath", "fire on")); if (!rn2(3)) mon->mspec_used = rn1(10, 5); /* -21 => monster's fire breath; 1 => # of damage dice */ @@ -2492,10 +3152,9 @@ boolean by_you; /* true: if mon kills itself, hero gets credit/blame */ if (mon->mconf) { if (cansee(mon->mx, mon->my)) pline("Oh, what a pretty fire!"); - if (vis && !objects[otyp].oc_name_known - && !objects[otyp].oc_uname) - docall(obj); - m_useup(mon, obj); /* after docall() */ + if (vis) + trycall(obj); + m_useup(mon, obj); /* after trycall() */ vis = FALSE; /* skip makeknown() below */ res = FALSE; /* failed to cure sliming */ } else { @@ -2507,6 +3166,38 @@ boolean by_you; /* true: if mon kills itself, hero gets credit/blame */ by_you ? -EXPL_FIERY : EXPL_FIERY); dmg = 0; /* damage has been applied by explode() */ } + } else if (otyp == POT_OIL) { + char Pronoun[40]; + boolean was_lit = obj->lamplit ? TRUE : FALSE, saw_lit = FALSE; + /* + * If not already lit, requires two actions. We cheat and let + * monster do both rather than render the potion unusable. + * + * Monsters don't start with oil and don't actively pick up oil + * so this may never occur in a real game. (Possible though; + * nymph can steal potions of oil; shapechanger could take on + * nymph form or vacuum up stuff as a gel.cube and then eventually + * engage with a green slime.) + */ + + if (obj->quan > 1L) + obj = splitobj(obj, 1L); + if (vis && !was_lit) { + pline_mon(mon, "%s ignites %s.", Monnam(mon), ansimpleoname(obj)); + saw_lit = TRUE; + } + begin_burn(obj, was_lit); + vis |= canseemon(mon); /* burning potion may improve visibility */ + if (vis) { + if (!Unaware) + observe_object(obj); /* hero is watching mon drink obj */ + pline("%s quaffs a burning %s", + saw_lit ? upstart(strcpy(Pronoun, mhe(mon))) : Monnam(mon), + simpleonames(obj)); + makeknown(POT_OIL); + } + dmg = d(3, 4); /* [**TEMP** (different from hero)] */ + m_useup(mon, obj); } else { /* wand/horn of fire w/ positive charge count */ if (obj->otyp == FIRE_HORN) mplayhorn(mon, obj, TRUE); @@ -2527,7 +3218,7 @@ boolean by_you; /* true: if mon kills itself, hero gets credit/blame */ for pacifist conduct); xkilled()'s message would say "You killed/destroyed " so give our own message */ if (vis) - pline("%s is %s by the fire!", Monnam(mon), + pline_mon(mon, "%s is %s by the fire!", Monnam(mon), nonliving(mon->data) ? "destroyed" : "killed"); xkilled(mon, XKILL_NOMSG | XKILL_NOCONDUCT); } else @@ -2535,73 +3226,84 @@ boolean by_you; /* true: if mon kills itself, hero gets credit/blame */ } else { /* non-fatal damage occurred */ if (vis) - pline("%s is burned%s", Monnam(mon), exclam(dmg)); + pline_mon(mon, "%s is burned%s", Monnam(mon), exclam(dmg)); } } if (vis) { if (res && !DEADMONSTER(mon)) - pline("%s slime is burned away!", s_suffix(Monnam(mon))); + pline_mon(mon, "%s slime is burned away!", s_suffix(Monnam(mon))); if (otyp != STRANGE_OBJECT) makeknown(otyp); } /* use up monster's next move */ mon->movement -= NORMAL_SPEED; - mon->mlstmv = monstermoves; + mon->mlstmv = svm.moves; return res; } /* decide whether obj can be used to cure green slime */ -STATIC_OVL int -cures_sliming(mon, obj) -struct monst *mon; -struct obj *obj; +staticfn int +cures_sliming(struct monst *mon, struct obj *obj) { - /* scroll of fire, non-empty wand or horn of fire */ + /* scroll of fire */ if (obj->otyp == SCR_FIRE) - return (haseyes(mon->data) && mon->mcansee); - /* hero doesn't need hands or even limbs to zap, so mon doesn't either */ + return (haseyes(mon->data) && mon->mcansee && !nohands(mon->data)); + + /* potion of oil; will be set burning if not already */ + if (obj->otyp == POT_OIL) + return !nohands(mon->data); + + /* non-empty wand or horn of fire; + hero doesn't need hands or even limbs to zap, so mon doesn't either */ return ((obj->otyp == WAN_FIRE || (obj->otyp == FIRE_HORN && can_blow(mon))) && obj->spe > 0); } -/* TRUE if monster appears to be green; for active TEXTCOLOR, we go by - the display color, otherwise we just pick things that seem plausibly - green (which doesn't necessarily match the TEXTCOLOR categorization) */ -STATIC_OVL boolean -green_mon(mon) -struct monst *mon; +/* TRUE if monster appears to be green; we go by the display color. + The alternative was to just pick things that + seem plausibly green (which didn't necessarily match the categorization + by the color of the text). + iflags.use_color is not meant for game behavior decisions */ +staticfn boolean +green_mon(struct monst *mon) { struct permonst *ptr = mon->data; if (Hallucination) return FALSE; -#ifdef TEXTCOLOR + return (ptr->mcolor == CLR_GREEN || ptr->mcolor == CLR_BRIGHT_GREEN); +#if 0 if (iflags.use_color) return (ptr->mcolor == CLR_GREEN || ptr->mcolor == CLR_BRIGHT_GREEN); -#endif - /* approximation */ - if (strstri(ptr->mname, "green")) - return TRUE; - switch (monsndx(ptr)) { - case PM_FOREST_CENTAUR: - case PM_GARTER_SNAKE: - case PM_GECKO: - case PM_GREMLIN: - case PM_HOMUNCULUS: - case PM_JUIBLEX: - case PM_LEPRECHAUN: - case PM_LICHEN: - case PM_LIZARD: - case PM_WOOD_NYMPH: - return TRUE; - default: - if (is_elf(ptr) && !is_prince(ptr) && !is_lord(ptr) - && ptr != &mons[PM_GREY_ELF]) + else { + /* approximation */ + if (strstri(ptr->pmnames[NEUTRAL], "green") + || (ptr->pmnames[MALE] && strstri(ptr->pmnames[MALE], "green")) + || (ptr->pmnames[FEMALE] + && strstri(ptr->pmnames[FEMALE], "green"))) return TRUE; - break; + switch (monsndx(ptr)) { + case PM_FOREST_CENTAUR: + case PM_GARTER_SNAKE: + case PM_GECKO: + case PM_GREMLIN: + case PM_HOMUNCULUS: + case PM_JUIBLEX: + case PM_LEPRECHAUN: + case PM_LICHEN: + case PM_LIZARD: + case PM_WOOD_NYMPH: + return TRUE; + default: + if (is_elf(ptr) && !is_prince(ptr) && !is_lord(ptr) + && ptr != &mons[PM_GREY_ELF]) + return TRUE; + break; + } } return FALSE; +#endif } /*muse.c*/ diff --git a/src/music.c b/src/music.c index 4f125793b..ce06dc954 100644 --- a/src/music.c +++ b/src/music.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 music.c $NHDT-Date: 1573063606 2019/11/06 18:06:46 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.60 $ */ +/* NetHack 5.0 music.c $NHDT-Date: 1736530208 2025/01/10 09:30:08 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.120 $ */ /* Copyright (c) 1989 by Jean-Christophe Collet */ /* NetHack may be freely redistributed. See license for details. */ @@ -28,59 +28,52 @@ #include "hack.h" -STATIC_DCL void FDECL(awaken_monsters, (int)); -STATIC_DCL void FDECL(put_monsters_to_sleep, (int)); -STATIC_DCL void FDECL(charm_snakes, (int)); -STATIC_DCL void FDECL(calm_nymphs, (int)); -STATIC_DCL void FDECL(charm_monsters, (int)); -STATIC_DCL void FDECL(do_earthquake, (int)); -STATIC_DCL int FDECL(do_improvisation, (struct obj *)); - -#ifdef UNIX386MUSIC -STATIC_DCL int NDECL(atconsole); -STATIC_DCL void FDECL(speaker, (struct obj *, char *)); -#endif -#ifdef VPIX_MUSIC -extern int sco_flag_console; /* will need changing if not _M_UNIX */ -STATIC_DCL void NDECL(playinit); -STATIC_DCL void FDECL(playstring, (char *, size_t)); -STATIC_DCL void FDECL(speaker, (struct obj *, char *)); -#endif -#ifdef PCMUSIC -void FDECL(pc_speaker, (struct obj *, char *)); -#endif -#ifdef AMIGA -void FDECL(amii_speaker, (struct obj *, char *, int)); -#endif +staticfn void awaken_scare(struct monst *, boolean); +staticfn void awaken_monsters(int); +staticfn void put_monsters_to_sleep(int); +staticfn void charm_snakes(int); +staticfn void calm_nymphs(int); +staticfn void charm_monsters(int); +staticfn void do_pit(coordxy, coordxy, unsigned); +staticfn void do_earthquake(int); +staticfn const char *generic_lvl_desc(void); +staticfn int do_improvisation(struct obj *); +staticfn char *improvised_notes(boolean *); + +/* wake up monster, possibly scare it */ +staticfn void +awaken_scare(struct monst *mtmp, boolean scary) +{ + mtmp->msleeping = 0; + mtmp->mcanmove = 1; + mtmp->mfrozen = 0; + /* may scare some monsters -- waiting monsters excluded */ + if (!unique_corpstat(mtmp->data) + && (mtmp->mstrategy & STRAT_WAITMASK) != 0) + mtmp->mstrategy &= ~STRAT_WAITMASK; + else if (scary + && !mindless(mtmp->data) + && !resist(mtmp, TOOL_CLASS, 0, NOTELL) + /* some monsters are immune */ + && onscary(0, 0, mtmp)) + monflee(mtmp, 0, FALSE, TRUE); +} /* * Wake every monster in range... */ -STATIC_OVL void -awaken_monsters(distance) -int distance; +staticfn void +awaken_monsters(int distance) { - register struct monst *mtmp; - register int distm; + struct monst *mtmp; + int distm; for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { if (DEADMONSTER(mtmp)) continue; - if ((distm = distu(mtmp->mx, mtmp->my)) < distance) { - mtmp->msleeping = 0; - mtmp->mcanmove = 1; - mtmp->mfrozen = 0; - /* may scare some monsters -- waiting monsters excluded */ - if (!unique_corpstat(mtmp->data) - && (mtmp->mstrategy & STRAT_WAITMASK) != 0) - mtmp->mstrategy &= ~STRAT_WAITMASK; - else if (distm < distance / 3 - && !resist(mtmp, TOOL_CLASS, 0, NOTELL) - /* some monsters are immune */ - && onscary(0, 0, mtmp)) - monflee(mtmp, 0, FALSE, TRUE); - } + if ((distm = mdistu(mtmp)) < distance) + awaken_scare(mtmp, (distm < distance / 3)); } } @@ -88,16 +81,15 @@ int distance; * Make monsters fall asleep. Note that they may resist the spell. */ -STATIC_OVL void -put_monsters_to_sleep(distance) -int distance; +staticfn void +put_monsters_to_sleep(int distance) { - register struct monst *mtmp; + struct monst *mtmp; for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { if (DEADMONSTER(mtmp)) continue; - if (distu(mtmp->mx, mtmp->my) < distance + if (mdistu(mtmp) < distance && sleep_monst(mtmp, d(10, 10), TOOL_CLASS)) { mtmp->msleeping = 1; /* 10d10 turns + wake_nearby to rouse */ slept_monst(mtmp); @@ -109,18 +101,17 @@ int distance; * Charm snakes in range. Note that the snakes are NOT tamed. */ -STATIC_OVL void -charm_snakes(distance) -int distance; +staticfn void +charm_snakes(int distance) { - register struct monst *mtmp; + struct monst *mtmp; int could_see_mon, was_peaceful; for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { if (DEADMONSTER(mtmp)) continue; if (mtmp->data->mlet == S_SNAKE && mtmp->mcanmove - && distu(mtmp->mx, mtmp->my) < distance) { + && mdistu(mtmp) < distance) { was_peaceful = mtmp->mpeaceful; mtmp->mpeaceful = 1; mtmp->mavenge = 0; @@ -144,17 +135,16 @@ int distance; * Calm nymphs in range. */ -STATIC_OVL void -calm_nymphs(distance) -int distance; +staticfn void +calm_nymphs(int distance) { - register struct monst *mtmp; + struct monst *mtmp; for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { if (DEADMONSTER(mtmp)) continue; if (mtmp->data->mlet == S_NYMPH && mtmp->mcanmove - && distu(mtmp->mx, mtmp->my) < distance) { + && mdistu(mtmp) < distance) { mtmp->msleeping = 0; mtmp->mpeaceful = 1; mtmp->mavenge = 0; @@ -169,20 +159,22 @@ int distance; /* Awake soldiers anywhere the level (and any nearby monster). */ void -awaken_soldiers(bugler) -struct monst *bugler; /* monster that played instrument */ +awaken_soldiers(struct monst *bugler /* monster that played instrument */) { - register struct monst *mtmp; + struct monst *mtmp; int distance, distm; /* distance of affected non-soldier monsters to bugler */ - distance = ((bugler == &youmonst) ? u.ulevel : bugler->data->mlevel) * 30; + distance = ((bugler == &gy.youmonst) ? u.ulevel + : bugler->data->mlevel) * 30; for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { if (DEADMONSTER(mtmp)) continue; if (is_mercenary(mtmp->data) && mtmp->data != &mons[PM_GUARD]) { - mtmp->mpeaceful = mtmp->msleeping = mtmp->mfrozen = 0; + if (!mtmp->mtame) + mtmp->mpeaceful = 0; + mtmp->msleeping = mtmp->mfrozen = 0; mtmp->mcanmove = 1; mtmp->mstrategy &= ~STRAT_WAITMASK; if (canseemon(mtmp)) @@ -190,67 +182,179 @@ struct monst *bugler; /* monster that played instrument */ else if (!Deaf) Norep("%s the rattle of battle gear being readied.", "You hear"); /* Deaf-aware */ - } else if ((distm = ((bugler == &youmonst) - ? distu(mtmp->mx, mtmp->my) + } else if ((distm = ((bugler == &gy.youmonst) + ? mdistu(mtmp) : dist2(bugler->mx, bugler->my, mtmp->mx, mtmp->my))) < distance) { - mtmp->msleeping = 0; - mtmp->mcanmove = 1; - mtmp->mfrozen = 0; - /* may scare some monsters -- waiting monsters excluded */ - if (!unique_corpstat(mtmp->data) - && (mtmp->mstrategy & STRAT_WAITMASK) != 0) - mtmp->mstrategy &= ~STRAT_WAITMASK; - else if (distm < distance / 3 - && !resist(mtmp, TOOL_CLASS, 0, NOTELL)) - monflee(mtmp, 0, FALSE, TRUE); + awaken_scare(mtmp, (distm < distance / 3)); } } } -/* Charm monsters in range. Note that they may resist the spell. - * If swallowed, range is reduced to 0. - */ -STATIC_OVL void -charm_monsters(distance) -int distance; +/* Charm monsters in range. Note that they may resist the spell. */ +staticfn void +charm_monsters(int distance) { struct monst *mtmp, *mtmp2; - if (u.uswallow) { - if (!resist(u.ustuck, TOOL_CLASS, 0, NOTELL)) - (void) tamedog(u.ustuck, (struct obj *) 0); - } else { - for (mtmp = fmon; mtmp; mtmp = mtmp2) { - mtmp2 = mtmp->nmon; - if (DEADMONSTER(mtmp)) - continue; + if (u.uswallow) + distance = 0; /* only u.ustuck will be affected (u.usteed is Null + * since hero gets forcibly dismounted when engulfed) */ + + for (mtmp = fmon; mtmp; mtmp = mtmp2) { + mtmp2 = mtmp->nmon; + if (DEADMONSTER(mtmp)) + continue; + + if (mdistu(mtmp) <= distance) { + /* a shopkeeper can't be tamed but tamedog() pacifies an angry + one; do that even if mtmp resists in order to behave the same + as a non-cursed scroll of taming or spell of charm monster */ + if (!resist(mtmp, TOOL_CLASS, 0, NOTELL) || mtmp->isshk) + (void) tamedog(mtmp, (struct obj *) 0, TRUE); + } + } +} + +/* Try to make a pit. */ +staticfn void +do_pit(coordxy x, coordxy y, unsigned tu_pit) +{ + struct monst *mtmp; + struct obj *otmp; + struct trap *chasm; + schar filltype; - if (distu(mtmp->mx, mtmp->my) <= distance) { - if (!resist(mtmp, TOOL_CLASS, 0, NOTELL)) - (void) tamedog(mtmp, (struct obj *) 0); + chasm = maketrap(x, y, PIT); + if (!chasm) + return; /* no pit if portal at that location */ + chasm->tseen = 1; + + mtmp = m_at(x, y); /* (redundant?) */ + if ((otmp = sobj_at(BOULDER, x, y)) != 0) { + if (cansee(x, y)) + pline("KADOOM! The boulder falls into a chasm%s!", + u_at(x, y) ? " below you" : ""); + if (mtmp) + mtmp->mtrapped = 0; + obj_extract_self(otmp); + (void) flooreffects(otmp, x, y, ""); + return; + } + + /* Let liquid flow into the newly created chasm. + Adjust corresponding code in apply.c for exploding + wand of digging if you alter this sequence. */ + filltype = fillholetyp(x, y, FALSE); + if (filltype != ROOM) { + set_levltyp(x, y, filltype); /* levl[x][y] = filltype; */ + liquid_flow(x, y, filltype, chasm, (char *) 0); + /* liquid_flow() deletes trap, might kill mtmp */ + if ((chasm = t_at(x, y)) == NULL) + return; + } + + /* We have to check whether monsters or hero falls into a + new pit.... Note: if we get here, chasm is non-Null. */ + if (mtmp) { + if (!is_flyer(mtmp->data) && !is_clinger(mtmp->data)) { + boolean m_already_trapped = mtmp->mtrapped; + + mtmp->mtrapped = 1; + if (!m_already_trapped) { /* suppress messages */ + if (cansee(x, y)) { + pline("%s falls into a chasm!", Monnam(mtmp)); + } else if (humanoid(mtmp->data)) { + Soundeffect(se_scream, 50); + You_hear("a scream!"); + } + } + /* Falling is okay for falling down + within a pit from jostling too */ + mselftouch(mtmp, "Falling, ", TRUE); + if (!DEADMONSTER(mtmp)) { + mtmp->mhp -= rnd(m_already_trapped ? 4 : 6); + if (DEADMONSTER(mtmp)) { + if (!cansee(x, y)) { + pline("It is destroyed!"); + } else { + You("destroy %s!", + mtmp->mtame + ? x_monnam(mtmp, ARTICLE_THE, "poor", + has_mgivenname(mtmp) + ? SUPPRESS_SADDLE : 0, + FALSE) + : mon_nam(mtmp)); + } + xkilled(mtmp, XKILL_NOMSG); + } } } + } else if (u_at(x, y)) { + if (u.utrap && u.utraptype == TT_BURIEDBALL) { + /* Note: the chain should break if a pit gets + created at the buried ball's location, which + is not necessarily here. But if we don't do + things this way, entering the new pit below + will override current trap anyway, but too + late to get Lev and Fly handling. */ + Your("chain breaks!"); + reset_utrap(TRUE); + } + if (Levitation || Flying || is_clinger(gy.youmonst.data)) { + if (!tu_pit) { /* no pit here previously */ + pline("A chasm opens up under you!"); + You("don't fall in!"); + } + } else if (!tu_pit || !u.utrap || u.utraptype != TT_PIT) { + /* no pit here previously, or you were + not in it even if there was */ + You("fall into a chasm!"); + set_utrap(rn1(6, 2), TT_PIT); + losehp(Maybe_Half_Phys(rnd(6)), + "fell into a chasm", NO_KILLER_PREFIX); + selftouch("Falling, you"); + } else if (u.utrap && u.utraptype == TT_PIT) { + boolean keepfooting = + (!(Fumbling && rn2(5)) + && (!(rnl(Role_if(PM_ARCHEOLOGIST) ? 3 : 9)) + || ((ACURR(A_DEX) > 7) && rn2(5)))); + + You("are jostled around violently!"); + set_utrap(rn1(6, 2), TT_PIT); + losehp(Maybe_Half_Phys(rnd(keepfooting ? 2 : 4)), + "hurt in a chasm", NO_KILLER_PREFIX); + if (keepfooting) + exercise(A_DEX, TRUE); + else + selftouch((Upolyd && (slithy(gy.youmonst.data) + || nolimbs(gy.youmonst.data))) + ? "Shaken, you" + : "Falling down, you"); + } + } else { + newsym(x, y); } } /* Generate earthquake :-) of desired force. * That is: create random chasms (pits). */ -STATIC_OVL void -do_earthquake(force) -int force; +staticfn void +do_earthquake(int force) { - register int x, y; + static const char into_a_chasm[] = " into a chasm"; + coordxy x, y; struct monst *mtmp; - struct obj *otmp; - struct trap *chasm, *trap_at_u = t_at(u.ux, u.uy); - int start_x, start_y, end_x, end_y; - schar filltype; + struct trap *trap_at_u = t_at(u.ux, u.uy); + int start_x, start_y, end_x, end_y, amsk; + aligntyp algn; unsigned tu_pit = 0; if (trap_at_u) tu_pit = is_pit(trap_at_u->ttyp); + if (force > 13) /* sanity precaution; maximum used is actually 10 */ + force = 13; start_x = u.ux - (force * 2); start_y = u.uy - (force * 2); end_x = u.ux + (force * 2); @@ -263,185 +367,115 @@ int force; for (y = start_y; y <= end_y; y++) { if ((mtmp = m_at(x, y)) != 0) { wakeup(mtmp, TRUE); /* peaceful monster will become hostile */ - if (mtmp->mundetected && is_hider(mtmp->data)) { + if (mtmp->mundetected) { mtmp->mundetected = 0; - if (cansee(x, y)) - pline("%s is shaken loose from the ceiling!", - Amonnam(mtmp)); - else - You_hear("a thumping sound."); - if (x == u.ux && y == u.uy) - You("easily dodge the falling %s.", mon_nam(mtmp)); newsym(x, y); + if (ceiling_hider(mtmp->data)) { + if (cansee(x, y)) { + pline("%s is shaken loose from the ceiling!", + Amonnam(mtmp)); + } else if (!is_flyer(mtmp->data)) { + Soundeffect(se_thump, 50); + You_hear("a thump."); + } + } } + if (M_AP_TYPE(mtmp) != M_AP_NOTHING + && M_AP_TYPE(mtmp) != M_AP_MONSTER) + seemimic(mtmp); } - if (!rn2(14 - force)) - switch (levl[x][y].typ) { - case FOUNTAIN: /* Make the fountain disappear */ - if (cansee(x, y)) - pline_The("fountain falls into a chasm."); - goto do_pit; - case SINK: - if (cansee(x, y)) - pline_The("kitchen sink falls into a chasm."); - goto do_pit; - case ALTAR: - if (Is_astralevel(&u.uz) || Is_sanctum(&u.uz)) - break; - - if (cansee(x, y)) - pline_The("altar falls into a chasm."); - goto do_pit; - case GRAVE: - if (cansee(x, y)) - pline_The("headstone topples into a chasm."); - goto do_pit; - case THRONE: - if (cansee(x, y)) - pline_The("throne falls into a chasm."); - /*FALLTHRU*/ - case ROOM: - case CORR: /* Try to make a pit */ - do_pit: - chasm = maketrap(x, y, PIT); - if (!chasm) - break; /* no pit if portal at that location */ - chasm->tseen = 1; - - /* TODO: - * This ought to be split into a separate routine to - * reduce indentation and the consequent line-wraps. - */ - - levl[x][y].doormask = 0; - /* - * Let liquid flow into the newly created chasm. - * Adjust corresponding code in apply.c for - * exploding wand of digging if you alter this sequence. - */ - filltype = fillholetyp(x, y, FALSE); - if (filltype != ROOM) { - levl[x][y].typ = filltype; /* flags set via doormask */ - liquid_flow(x, y, filltype, chasm, (char *) 0); - } - - mtmp = m_at(x, y); - - if ((otmp = sobj_at(BOULDER, x, y)) != 0) { - if (cansee(x, y)) - pline("KADOOM! The boulder falls into a chasm%s!", - (x == u.ux && y == u.uy) ? " below you" - : ""); - if (mtmp) - mtmp->mtrapped = 0; - obj_extract_self(otmp); - (void) flooreffects(otmp, x, y, ""); - break; - } + if (rn2(14 - force)) + continue; - /* We have to check whether monsters or player - falls in a chasm... */ - if (mtmp) { - if (!is_flyer(mtmp->data) - && !is_clinger(mtmp->data)) { - boolean m_already_trapped = mtmp->mtrapped; - - mtmp->mtrapped = 1; - if (!m_already_trapped) { /* suppress messages */ - if (cansee(x, y)) - pline("%s falls into a chasm!", - Monnam(mtmp)); - else if (humanoid(mtmp->data)) - You_hear("a scream!"); - } - /* Falling is okay for falling down - within a pit from jostling too */ - mselftouch(mtmp, "Falling, ", TRUE); - if (!DEADMONSTER(mtmp)) { - mtmp->mhp -= rnd(m_already_trapped ? 4 : 6); - if (DEADMONSTER(mtmp)) { - if (!cansee(x, y)) { - pline("It is destroyed!"); - } else { - You("destroy %s!", - mtmp->mtame - ? x_monnam(mtmp, ARTICLE_THE, - "poor", - has_mname(mtmp) - ? SUPPRESS_SADDLE - : 0, - FALSE) - : mon_nam(mtmp)); - } - xkilled(mtmp, XKILL_NOMSG); - } - } - } - } else if (x == u.ux && y == u.uy) { - if (u.utrap && u.utraptype == TT_BURIEDBALL) { - /* Note: the chain should break if a pit gets - created at the buried ball's location, which - is not necessarily here. But if we don't do - things this way, entering the new pit below - will override current trap anyway, but too - late to get Lev and Fly handling. */ - Your("chain breaks!"); - reset_utrap(TRUE); - } - if (Levitation || Flying - || is_clinger(youmonst.data)) { - if (!tu_pit) { /* no pit here previously */ - pline("A chasm opens up under you!"); - You("don't fall in!"); - } - } else if (!tu_pit || !u.utrap - || (u.utrap && u.utraptype != TT_PIT)) { - /* no pit here previously, or you were - not in it even if there was */ - You("fall into a chasm!"); - set_utrap(rn1(6, 2), TT_PIT); - losehp(Maybe_Half_Phys(rnd(6)), - "fell into a chasm", NO_KILLER_PREFIX); - selftouch("Falling, you"); - } else if (u.utrap && u.utraptype == TT_PIT) { - boolean keepfooting = - ((Fumbling && !rn2(5)) - || (!rnl(Role_if(PM_ARCHEOLOGIST) ? 3 : 9)) - || ((ACURR(A_DEX) > 7) && rn2(5))); - - You("are jostled around violently!"); - set_utrap(rn1(6, 2), TT_PIT); - losehp(Maybe_Half_Phys(rnd(keepfooting ? 2 : 4)), - "hurt in a chasm", NO_KILLER_PREFIX); - if (keepfooting) - exercise(A_DEX, TRUE); - else - selftouch( - (Upolyd && (slithy(youmonst.data) - || nolimbs(youmonst.data))) - ? "Shaken, you" - : "Falling down, you"); - } - } else - newsym(x, y); + /* + * Possible extensions: + * When a door is trapped, explode it instead of silently + * turning it into an empty doorway. + * Trigger divine wrath when an altar is dumped into a chasm. + * Sometimes replace sink with fountain or fountain with pool + * instead of always producing a pit. + * Sometimes release monster and/or treasure from a grave or + * a throne instead of just dumping them into the chasm. + * Chance to destroy wall segments? Trees too? + * Honor non-diggable for locked doors, walls, and trees. + * Treat non-passwall as if it was non-diggable? + * Conjoin some of the umpteen pits when they're adjacent? + * + * Replace 'goto do_pit;' with 'do_pit = TRUE; break;' and + * move the pit code to after the switch. + */ + + switch (levl[x][y].typ) { + case FOUNTAIN: /* make the fountain disappear */ + if (cansee(x, y)) + pline_The("fountain falls%s.", into_a_chasm); + do_pit(x, y, tu_pit); + break; + case SINK: + if (cansee(x, y)) + pline_The("kitchen sink falls%s.", into_a_chasm); + do_pit(x, y, tu_pit); + break; + case ALTAR: + amsk = altarmask_at(x, y); + /* always preserve the high altars */ + if ((amsk & AM_SANCTUM) != 0) break; - case DOOR: /* Make the door collapse */ - if (levl[x][y].doormask == D_NODOOR) - goto do_pit; - if (cansee(x, y)) - pline_The("door collapses."); - if (*in_rooms(x, y, SHOPBASE)) - add_damage(x, y, 0L); - levl[x][y].doormask = D_NODOOR; - unblock_point(x, y); - newsym(x, y); + algn = Amask2align(amsk & AM_MASK); + if (cansee(x, y)) + pline_The("%s altar falls%s.", + align_str(algn), into_a_chasm); + desecrate_altar(FALSE, algn); + do_pit(x, y, tu_pit); + break; + case GRAVE: + if (cansee(x, y)) + pline_The("headstone topples%s.", into_a_chasm); + do_pit(x, y, tu_pit); + break; + case THRONE: + if (cansee(x, y)) + pline_The("throne falls%s.", into_a_chasm); + do_pit(x, y, tu_pit); + break; + case SCORR: + levl[x][y].typ = CORR; + unblock_point(x, y); + if (cansee(x, y)) + pline("A secret corridor is revealed."); + FALLTHROUGH; + /*FALLTHRU*/ + case CORR: + case ROOM: + do_pit(x, y, tu_pit); + break; + case SDOOR: + cvt_sdoor_to_door(&levl[x][y]); /* .typ = DOOR */ + if (cansee(x, y)) + pline("A secret door is revealed."); + FALLTHROUGH; + /*FALLTHRU*/ + case DOOR: /* make the door collapse */ + /* if already doorless, treat like room or corridor */ + if (levl[x][y].doormask == D_NODOOR) { + do_pit(x, y, tu_pit); break; } + /* wasn't doorless, now it will be */ + levl[x][y].doormask = D_NODOOR; + recalc_block_point(x, y); + newsym(x, y); /* before pline */ + if (cansee(x, y)) + pline_The("door collapses."); + if (*in_rooms(x, y, SHOPBASE)) + add_damage(x, y, 0L); + break; + } } } -const char * -generic_lvl_desc() +staticfn const char * +generic_lvl_desc(void) { if (Is_astralevel(&u.uz)) return "astral plane"; @@ -457,7 +491,7 @@ generic_lvl_desc() return "dungeon"; } -const char *beats[] = { +static const char *beats[] = { "stepper", "one drop", "slow two", "triple stroke roll", "double shuffle", "half-time shuffle", "second line", "train" }; @@ -465,18 +499,19 @@ const char *beats[] = { /* * The player is trying to extract something from his/her instrument. */ -STATIC_OVL int -do_improvisation(instr) -struct obj *instr; +staticfn int +do_improvisation(struct obj *instr) { int damage, mode, do_spec = !(Stunned || Confusion); struct obj itmp; - boolean mundane = FALSE; + boolean mundane = FALSE, same_old_song = FALSE; + static char my_goto_song[] = {'C', '\0'}, + *improvisation = my_goto_song; itmp = *instr; itmp.oextra = (struct oextra *) 0; /* ok on this copy as instr maintains - the ptr to free at some point if - there is one */ + * the ptr to free at some point if + * there is one */ /* if won't yield special effect, make sound of mundane counterpart */ if (!do_spec || instr->spe <= 0) @@ -485,20 +520,6 @@ struct obj *instr; mundane = TRUE; } -#ifdef MAC - mac_speaker(&itmp, "C"); -#endif -#ifdef AMIGA - amii_speaker(&itmp, "Cw", AMII_OKAY_VOLUME); -#endif -#ifdef VPIX_MUSIC - if (sco_flag_console) - speaker(&itmp, "C"); -#endif -#ifdef PCMUSIC - pc_speaker(&itmp, "C"); -#endif - #define PLAY_NORMAL 0x00 #define PLAY_STUNNED 0x01 #define PLAY_CONFUSED 0x02 @@ -562,21 +583,27 @@ struct obj *instr; #undef PLAY_CONFUSED #undef PLAY_HALLU + improvisation = improvised_notes(&same_old_song); + switch (itmp.otyp) { /* note: itmp.otyp might differ from instr->otyp */ case MAGIC_FLUTE: /* Make monster fall asleep */ consume_obj_charge(instr, TRUE); - You("%sproduce %s music.", !Deaf ? "" : "seem to ", - Hallucination ? "piped" : "soft"); + You("%sproduce %s%s music.", !Deaf ? "" : "seem to ", + Hallucination ? "piped" : "soft", + same_old_song ? ", familiar" : ""); + Hero_playnotes(obj_to_instr(&itmp), improvisation, 50); put_monsters_to_sleep(u.ulevel * 5); exercise(A_DEX, TRUE); break; case WOODEN_FLUTE: /* May charm snakes */ do_spec &= (rn2(ACURR(A_DEX)) + u.ulevel > 25); if (!Deaf) - pline("%s.", Tobjnam(instr, do_spec ? "trill" : "toot")); + pline("%s%s.", Tobjnam(instr, do_spec ? "trill" : "toot"), + same_old_song ? " a familiar tune" : ""); else You_feel("%s %s.", yname(instr), do_spec ? "trill" : "toot"); + Hero_playnotes(obj_to_instr(&itmp), improvisation, 50); if (do_spec) charm_snakes(u.ulevel * 3); exercise(A_DEX, TRUE); @@ -593,37 +620,52 @@ struct obj *instr; char buf[BUFSZ]; Sprintf(buf, "using a magical horn on %sself", uhim()); + Hero_playnotes(obj_to_instr(&itmp), improvisation, 50); losehp(damage, buf, KILLED_BY); /* fire or frost damage */ } } else { - buzz((instr->otyp == FROST_HORN) ? AD_COLD - 1 : AD_FIRE - 1, - rn1(6, 6), u.ux, u.uy, u.dx, u.dy); + int type = BZ_OFS_AD((instr->otyp == FROST_HORN) ? AD_COLD + : AD_FIRE); + + if (!Blind) + pline("A %s blasts out of the horn!", flash_str(type, FALSE)); + Hero_playnotes(obj_to_instr(&itmp), improvisation, 50); + gc.current_wand = instr; + ubuzz(BZ_U_WAND(type), rn1(6, 6)); + gc.current_wand = 0; } makeknown(instr->otyp); break; case TOOLED_HORN: /* Awaken or scare monsters */ if (!Deaf) - You("produce a frightful, grave sound."); + You("produce a frightful, grave%s sound.", + same_old_song ? ", yet familiar," : ""); else You("blow into the horn."); + Hero_playnotes(obj_to_instr(&itmp), improvisation, 80); awaken_monsters(u.ulevel * 30); exercise(A_WIS, FALSE); break; case BUGLE: /* Awaken & attract soldiers */ if (!Deaf) - You("extract a loud noise from %s.", yname(instr)); + You("extract a loud%s noise from %s.", + same_old_song ? ", familiar" : "", yname(instr)); else You("blow into the bugle."); - awaken_soldiers(&youmonst); + Hero_playnotes(obj_to_instr(&itmp), improvisation, 80); + awaken_soldiers(&gy.youmonst); exercise(A_WIS, FALSE); break; case MAGIC_HARP: /* Charm monsters */ consume_obj_charge(instr, TRUE); if (!Deaf) - pline("%s very attractive music.", Tobjnam(instr, "produce")); + pline("%s very attractive%s music.", + Tobjnam(instr, "produce"), + same_old_song ? " and familiar" : ""); else You_feel("very soothing vibrations."); + Hero_playnotes(obj_to_instr(&itmp), improvisation, 50); charm_monsters((u.ulevel - 1) / 3 + 1); exercise(A_DEX, TRUE); break; @@ -631,9 +673,14 @@ struct obj *instr; do_spec &= (rn2(ACURR(A_DEX)) + u.ulevel > 25); if (!Deaf) pline("%s %s.", Yname2(instr), - do_spec ? "produces a lilting melody" : "twangs"); + (do_spec && same_old_song) + ? "produces a familiar, lilting melody" + : (do_spec) ? "produces a lilting melody" + : (same_old_song) ? "twangs a familiar tune" + : "twangs"); else You_feel("soothing vibrations."); + Hero_playnotes(obj_to_instr(&itmp), improvisation, 50); if (do_spec) calm_nymphs(u.ulevel * 3); exercise(A_DEX, TRUE); @@ -646,6 +693,7 @@ struct obj *instr; consume_obj_charge(instr, TRUE); You("produce a heavy, thunderous rolling!"); + Hero_playnotes(obj_to_instr(&itmp), "C", 100); pline_The("entire %s is shaking around you!", generic_lvl_desc()); do_earthquake((u.ulevel - 1) / 3 + 1); /* shake up monsters in a much larger radius... */ @@ -655,47 +703,75 @@ struct obj *instr; case LEATHER_DRUM: /* Awaken monsters */ if (!mundane) { if (!Deaf) { - You("beat a deafening row!"); + You("beat a %sdeafening row!", + same_old_song ? "familiar " : ""); + Hero_playnotes(obj_to_instr(&itmp), "CCC", 100); incr_itimeout(&HDeaf, rn1(20, 30)); } else { You("pound on the drum."); } exercise(A_WIS, FALSE); - } else + } else { + /* TODO maybe: sound effects for these riffs */ You("%s %s.", rn2(2) ? "butcher" : rn2(2) ? "manage" : "pull off", - an(beats[rn2(SIZE(beats))])); + an(ROLL_FROM(beats))); + Hero_playnotes(obj_to_instr(&itmp), improvisation, 50); + } awaken_monsters(u.ulevel * (mundane ? 5 : 40)); - context.botl = TRUE; + disp.botl = TRUE; break; default: impossible("What a weird instrument (%d)!", instr->otyp); return 0; } + nhUse(improvisation); return 2; /* That takes time */ } +staticfn char * +improvised_notes(boolean *same_as_last_time) +{ + static const char notes[7] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' }; + /* target buffer has to be in svc.context, otherwise saving game + * between improvised recitals would not be able to maintain + * the same_as_last_time context. */ + + /* You can change your tune, usually */ + if (!(Unchanging && svc.context.jingle[0] != '\0')) { + int i, notecount = rnd(SIZE(svc.context.jingle) - 1); /* 1 - 5 */ + + for (i = 0; i < notecount; ++i) { + svc.context.jingle[i] = ROLL_FROM(notes); + } + svc.context.jingle[notecount] = '\0'; + *same_as_last_time = FALSE; + } else { + *same_as_last_time = TRUE; + } + return svc.context.jingle; +} + /* * So you want music... */ int -do_play_instrument(instr) -struct obj *instr; +do_play_instrument(struct obj *instr) { char buf[BUFSZ] = DUMMY, c = 'y'; char *s; - int x, y; + coordxy x, y; boolean ok; if (Underwater) { You_cant("play music underwater!"); - return 0; + return ECMD_OK; } else if ((instr->otyp == WOODEN_FLUTE || instr->otyp == MAGIC_FLUTE || instr->otyp == TOOLED_HORN || instr->otyp == FROST_HORN || instr->otyp == FIRE_HORN || instr->otyp == BUGLE) - && !can_blow(&youmonst)) { - You("are incapable of playing %s.", the(distant_name(instr, xname))); - return 0; + && !can_blow(&gy.youmonst)) { + You("are incapable of playing %s.", thesimpleoname(instr)); + return ECMD_OK; } if (instr->otyp != LEATHER_DRUM && instr->otyp != DRUM_OF_EARTHQUAKE && !(Stunned || Confusion || Hallucination)) { @@ -704,286 +780,166 @@ struct obj *instr; goto nevermind; } - if (c == 'n') { - if (u.uevent.uheard_tune == 2) - c = ynq("Play the passtune?"); - if (c == 'q') { + if (c != 'n') + return do_improvisation(instr) ? ECMD_TIME : ECMD_OK; + + if (u.uevent.uheard_tune == 2) + c = ynq("Play the passtune?"); + if (c == 'q') { + goto nevermind; + } else if (c == 'y') { + Strcpy(buf, svt.tune); + } else { + getlin("What tune are you playing? [5 notes, A-G]", buf); + (void) mungspaces(buf); + if (*buf == '\033') goto nevermind; - } else if (c == 'y') { - Strcpy(buf, tune); - } else { - getlin("What tune are you playing? [5 notes, A-G]", buf); - (void) mungspaces(buf); - if (*buf == '\033') - goto nevermind; - - /* convert to uppercase and change any "H" to the expected "B" */ - for (s = buf; *s; s++) { -#ifndef AMIGA - *s = highc(*s); -#else - /* The AMIGA supports two octaves of notes */ - if (*s == 'h') - *s = 'b'; -#endif - if (*s == 'H') - *s = 'B'; - } + + /* convert to uppercase and change any "H" to the expected "B" */ + for (s = buf; *s; s++) { + *s = highc(*s); + if (*s == 'H') + *s = 'B'; } + } - You(!Deaf ? "extract a strange sound from %s!" - : "can feel %s emitting vibrations.", the(xname(instr))); + You(!Deaf ? "extract a strange sound from %s!" + : "can feel %s emitting vibrations.", the(xname(instr))); + Hero_playnotes(obj_to_instr(instr), buf, 50); -#ifdef UNIX386MUSIC - /* if user is at the console, play through the console speaker */ - if (atconsole()) - speaker(instr, buf); -#endif -#ifdef VPIX_MUSIC - if (sco_flag_console) - speaker(instr, buf); -#endif -#ifdef MAC - mac_speaker(instr, buf); -#endif -#ifdef PCMUSIC - pc_speaker(instr, buf); -#endif -#ifdef AMIGA - { - char nbuf[20]; - int i; - - for (i = 0; buf[i] && i < 5; ++i) { - nbuf[i * 2] = buf[i]; - nbuf[(i * 2) + 1] = 'h'; - } - nbuf[i * 2] = 0; - amii_speaker(instr, nbuf, AMII_OKAY_VOLUME); - } -#endif - /* Check if there was the Stronghold drawbridge near - * and if the tune conforms to what we're waiting for. - */ - if (Is_stronghold(&u.uz)) { - exercise(A_WIS, TRUE); /* just for trying */ - if (!strcmp(buf, tune)) { - /* Search for the drawbridge */ - for (y = u.uy - 1; y <= u.uy + 1; y++) - for (x = u.ux - 1; x <= u.ux + 1; x++) - if (isok(x, y)) - if (find_drawbridge(&x, &y)) { - /* tune now fully known */ - u.uevent.uheard_tune = 2; - if (levl[x][y].typ == DRAWBRIDGE_DOWN) - close_drawbridge(x, y); - else - open_drawbridge(x, y); - return 1; - } - } else if (!Deaf) { - if (u.uevent.uheard_tune < 1) - u.uevent.uheard_tune = 1; - /* Okay, it wasn't the right tune, but perhaps - * we can give the player some hints like in the - * Mastermind game */ - ok = FALSE; - for (y = u.uy - 1; y <= u.uy + 1 && !ok; y++) - for (x = u.ux - 1; x <= u.ux + 1 && !ok; x++) - if (isok(x, y)) - if (IS_DRAWBRIDGE(levl[x][y].typ) - || is_drawbridge_wall(x, y) >= 0) - ok = TRUE; - if (ok) { /* There is a drawbridge near */ - int tumblers, gears; - boolean matched[5]; - - tumblers = gears = 0; - for (x = 0; x < 5; x++) - matched[x] = FALSE; - - for (x = 0; x < (int) strlen(buf); x++) - if (x < 5) { - if (buf[x] == tune[x]) { - gears++; - matched[x] = TRUE; - } else { - for (y = 0; y < 5; y++) - if (!matched[y] && buf[x] == tune[y] - && buf[y] != tune[y]) { - tumblers++; - matched[y] = TRUE; - break; - } - } - } - if (tumblers) { - if (gears) - You_hear("%d tumbler%s click and %d gear%s turn.", - tumblers, plur(tumblers), gears, - plur(gears)); + + /* Check if there was the Stronghold drawbridge near + * and if the tune conforms to what we're waiting for. + */ + if (Is_stronghold(&u.uz)) { + exercise(A_WIS, TRUE); /* just for trying */ + if (!strcmp(buf, svt.tune)) { + /* Search for the drawbridge */ + for (y = u.uy - 1; y <= u.uy + 1; y++) + for (x = u.ux - 1; x <= u.ux + 1; x++) { + if (!isok(x, y)) + continue; + if (find_drawbridge(&x, &y)) { + /* tune now fully known */ + u.uevent.uheard_tune = 2; + record_achievement(ACH_TUNE); + if (levl[x][y].typ == DRAWBRIDGE_DOWN) + close_drawbridge(x, y); else - You_hear("%d tumbler%s click.", tumblers, - plur(tumblers)); - } else if (gears) { - You_hear("%d gear%s turn.", gears, plur(gears)); - /* could only get `gears == 5' by playing five - correct notes followed by excess; otherwise, - tune would have matched above */ - if (gears == 5) - u.uevent.uheard_tune = 2; + open_drawbridge(x, y); + return ECMD_TIME; + } + } + } else if (!Deaf) { + if (u.uevent.uheard_tune < 1) + u.uevent.uheard_tune = 1; + /* Okay, it wasn't the right tune, but perhaps + * we can give the player some hints like in the + * Mastermind game */ + ok = FALSE; + for (y = u.uy - 1; y <= u.uy + 1 && !ok; y++) + for (x = u.ux - 1; x <= u.ux + 1 && !ok; x++) + if (isok(x, y)) + if (IS_DRAWBRIDGE(levl[x][y].typ) + || is_drawbridge_wall(x, y) >= 0) + ok = TRUE; + if (ok) { /* There is a drawbridge near */ + int tumblers, gears; + boolean matched[5]; + + tumblers = gears = 0; + for (x = 0; x < 5; x++) + matched[x] = FALSE; + + for (x = 0; x < (int) strlen(buf); x++) + if (x < 5) { + if (buf[x] == svt.tune[x]) { + gears++; + matched[x] = TRUE; + } else { + for (y = 0; y < 5; y++) + if (!matched[y] && buf[x] == svt.tune[y] + && buf[y] != svt.tune[y]) { + tumblers++; + matched[y] = TRUE; + break; + } + } + } + if (tumblers) { + if (gears) { + Soundeffect(se_tumbler_click, 50); + Soundeffect(se_gear_turn, 50); + You_hear("%d tumbler%s click and %d gear%s turn.", + tumblers, plur(tumblers), gears, + plur(gears)); + } else { + Soundeffect(se_tumbler_click, 50); + You_hear("%d tumbler%s click.", tumblers, + plur(tumblers)); + } + } else if (gears) { + You_hear("%d gear%s turn.", gears, plur(gears)); + /* could only get `gears == 5' by playing five + correct notes followed by excess; otherwise, + tune would have matched above */ + if (gears == 5) { + u.uevent.uheard_tune = 2; + record_achievement(ACH_TUNE); } } } } - return 1; - } else - return do_improvisation(instr); + } + return ECMD_TIME; nevermind: pline1(Never_mind); - return 0; -} - -#ifdef UNIX386MUSIC -/* - * Play audible music on the machine's speaker if appropriate. - */ - -STATIC_OVL int -atconsole() -{ - /* - * Kluge alert: This code assumes that your [34]86 has no X terminals - * attached and that the console tty type is AT386 (this is always true - * under AT&T UNIX for these boxen). The theory here is that your remote - * ttys will have terminal type `ansi' or something else other than - * `AT386' or `xterm'. We'd like to do better than this, but testing - * to see if we're running on the console physical terminal is quite - * difficult given the presence of virtual consoles and other modern - * UNIX impedimenta... - */ - char *termtype = nh_getenv("TERM"); - - return (!strcmp(termtype, "AT386") || !strcmp(termtype, "xterm")); + return ECMD_OK; } -STATIC_OVL void -speaker(instr, buf) -struct obj *instr; -char *buf; -{ - /* - * For this to work, you need to have installed the PD speaker-control - * driver for PC-compatible UNIX boxes that I (esr@snark.thyrsus.com) - * posted to comp.sources.unix in Feb 1990. A copy should be included - * with your nethack distribution. - */ - int fd; +enum instruments +obj_to_instr(struct obj *obj SOUNDLIBONLY) { + enum instruments ret_instr = ins_no_instrument; - if ((fd = open("/dev/speaker", 1)) != -1) { - /* send a prefix to modify instrumental `timbre' */ - switch (instr->otyp) { +#if defined(SND_LIB_INTEGRATED) + switch(obj->otyp) { case WOODEN_FLUTE: + ret_instr = ins_flute; + break; case MAGIC_FLUTE: - (void) write(fd, ">ol", 1); /* up one octave & lock */ + ret_instr = ins_pan_flute; break; case TOOLED_HORN: + ret_instr = ins_english_horn; + break; case FROST_HORN: + ret_instr = ins_french_horn; + break; case FIRE_HORN: - (void) write(fd, "< -#include -#include -#else -#define KIOC ('K' << 8) -#define KDMKTONE (KIOC | 8) -#endif - -#define noDEBUG - -/* emit tone of frequency hz for given number of ticks */ -STATIC_OVL void -tone(hz, ticks) -unsigned int hz, ticks; -{ - ioctl(0, KDMKTONE, hz | ((ticks * 10) << 16)); -#ifdef DEBUG - printf("TONE: %6d %6d\n", hz, ticks * 10); -#endif - nap(ticks * 10); -} - -/* rest for given number of ticks */ -STATIC_OVL void -rest(ticks) -int ticks; -{ - nap(ticks * 10); -#ifdef DEBUG - printf("REST: %6d\n", ticks * 10); #endif + return ret_instr; } - -#include "interp.c" /* from snd86unx.shr */ - -STATIC_OVL void -speaker(instr, buf) -struct obj *instr; -char *buf; -{ - /* emit a prefix to modify instrumental `timbre' */ - playinit(); - switch (instr->otyp) { - case WOODEN_FLUTE: - case MAGIC_FLUTE: - playstring(">ol", 1); /* up one octave & lock */ - break; - case TOOLED_HORN: - case FROST_HORN: - case FIRE_HORN: - playstring("<obj && (lo)->obj->where != OBJ_LUAFREE) + +staticfn struct _lua_obj * +l_obj_check(lua_State *L, int indx) +{ + struct _lua_obj *lo; + + luaL_checktype(L, indx, LUA_TUSERDATA); + lo = (struct _lua_obj *) luaL_checkudata(L, indx, "obj"); + if (!lo) + nhl_error(L, "Obj error"); + return lo; +} + +staticfn int +l_obj_gc(lua_State *L) +{ + struct obj *obj, *otmp; + struct _lua_obj *lo = l_obj_check(L, 1); + + if (lo && (obj = lo->obj) != 0) { + if (obj->lua_ref_cnt > 0) + obj->lua_ref_cnt--; + /* free-floating objects with no other refs are deallocated. */ + if (!obj->lua_ref_cnt + && (obj->where == OBJ_FREE || obj->where == OBJ_LUAFREE)) { + if (Has_contents(obj)) { + while ((otmp = obj->cobj) != 0) { + obj_extract_self(otmp); + dealloc_obj(otmp); + } + } + obj->where = OBJ_FREE; + dealloc_obj(obj), obj = 0; + } + lo->obj = NULL; + } + return 0; +} + +staticfn struct _lua_obj * +l_obj_push(lua_State *L, struct obj *otmp) +{ + struct _lua_obj *lo + = (struct _lua_obj *) lua_newuserdata(L, sizeof (struct _lua_obj)); + luaL_getmetatable(L, "obj"); + lua_setmetatable(L, -2); + + lo->state = 0; + lo->obj = otmp; + if (otmp) + otmp->lua_ref_cnt++; + + return lo; +} + +void +nhl_push_obj(lua_State *L, struct obj *otmp) +{ + (void) l_obj_push(L, otmp); +} + +/* local o = obj.new("large chest"); + local cobj = o:contents(); */ +staticfn int +l_obj_getcontents(lua_State *L) +{ + struct _lua_obj *lo = l_obj_check(L, 1); + struct obj *obj = lo->obj; + + if (!obj) + nhl_error(L, "l_obj_getcontents: no obj"); + + (void) l_obj_push(L, obj->cobj); + return 1; +} + +/* Puts object inside another object. */ +/* local box = obj.new("large chest"); + box:addcontent(obj.new("rock")); +*/ +staticfn int +l_obj_add_to_container(lua_State *L) +{ + struct _lua_obj *lobox = l_obj_check(L, 1); + struct _lua_obj *lo = l_obj_check(L, 2); + struct obj *otmp; + int refs; + + if (!lobj_is_ok(lo) || !lobj_is_ok(lobox)) + return 0; + + refs = lo->obj->lua_ref_cnt; + + obj_extract_self(lo->obj); + otmp = add_to_container(lobox->obj, lo->obj); + + /* was lo->obj merged? */ + if (otmp != lo->obj) { + lo->obj = otmp; + lo->obj->lua_ref_cnt += refs; + } + lobox->obj->owt = weight(lobox->obj); + + return 0; +} + +/* Put object into player's inventory */ +/* u.giveobj(obj.new("rock")); */ +int +nhl_obj_u_giveobj(lua_State *L) +{ + struct _lua_obj *lo = l_obj_check(L, 1); + struct obj *otmp; + int refs; + + if (!lobj_is_ok(lo) || lo->obj->where == OBJ_INVENT) + return 0; + + refs = lo->obj->lua_ref_cnt; + + obj_extract_self(lo->obj); + otmp = addinv(lo->obj); + + if (otmp != lo->obj) { + lo->obj->lua_ref_cnt += refs; + lo->obj = otmp; + } + + return 0; +} + +DISABLE_WARNING_UNREACHABLE_CODE + +/* Get a table of object class data. */ +/* local odata = obj.class(otbl.otyp); */ +/* local odata = obj.class(obj.new("rock")); */ +/* local odata = o:class(); */ +staticfn int +l_obj_objects_to_table(lua_State *L) +{ + int argc = lua_gettop(L); + int otyp = -1; + struct objclass *o; + + if (argc != 1) { + nhl_error(L, "l_obj_objects_to_table: Wrong args"); + /*NOTREACHED*/ + return 0; + } + + if (lua_type(L, 1) == LUA_TNUMBER) { + otyp = (int) luaL_checkinteger(L, 1); + } else if (lua_type(L, 1) == LUA_TUSERDATA) { + struct _lua_obj *lo = l_obj_check(L, 1); + if (lo && lo->obj) + otyp = lo->obj->otyp; + } + lua_pop(L, 1); + + if (otyp == -1) { + nhl_error(L, "l_obj_objects_to_table: Wrong args"); + /*NOTREACHED*/ + return 0; + } + + o = &objects[otyp]; + + lua_newtable(L); + + if (OBJ_NAME(objects[otyp])) + nhl_add_table_entry_str(L, "name", OBJ_NAME(objects[otyp])); + if (OBJ_DESCR(objects[otyp])) + nhl_add_table_entry_str(L, "descr", + OBJ_DESCR(objects[otyp])); + if (o->oc_uname) + nhl_add_table_entry_str(L, "uname", o->oc_uname); + + nhl_add_table_entry_int(L, "name_known", o->oc_name_known); + nhl_add_table_entry_int(L, "merge", o->oc_merge); + nhl_add_table_entry_int(L, "uses_known", o->oc_uses_known); + nhl_add_table_entry_int(L, "encountered", o->oc_encountered); + nhl_add_table_entry_int(L, "magic", o->oc_magic); + nhl_add_table_entry_int(L, "charged", o->oc_charged); + nhl_add_table_entry_int(L, "unique", o->oc_unique); + nhl_add_table_entry_int(L, "nowish", o->oc_nowish); + nhl_add_table_entry_int(L, "big", o->oc_big); + /* TODO: oc_bimanual, oc_bulky */ + nhl_add_table_entry_int(L, "tough", o->oc_tough); + nhl_add_table_entry_int(L, "dir", o->oc_dir); /* TODO: convert to text */ + nhl_add_table_entry_str(L, "material", materialnm[o->oc_material]); + /* TODO: oc_subtyp, oc_skill, oc_armcat */ + nhl_add_table_entry_int(L, "oprop", o->oc_oprop); + nhl_add_table_entry_char(L, "class", + def_oc_syms[(uchar) o->oc_class].sym); + nhl_add_table_entry_int(L, "delay", o->oc_delay); + nhl_add_table_entry_int(L, "color", o->oc_color); /* TODO: text? */ + nhl_add_table_entry_int(L, "prob", o->oc_prob); + nhl_add_table_entry_int(L, "weight", o->oc_weight); + nhl_add_table_entry_int(L, "cost", o->oc_cost); + nhl_add_table_entry_int(L, "damage_small", o->oc_wsdam); + nhl_add_table_entry_int(L, "damage_large", o->oc_wldam); + /* TODO: oc_oc1, oc_oc2, oc_hitbon, a_ac, a_can, oc_level */ + nhl_add_table_entry_int(L, "nutrition", o->oc_nutrition); + + return 1; +} + +RESTORE_WARNING_UNREACHABLE_CODE + +/* Create a lua table representation of the object, unpacking all the + object fields. + local o = obj.new("rock"); + local otbl = o:totable(); */ +staticfn int +l_obj_to_table(lua_State *L) +{ + struct _lua_obj *lo = l_obj_check(L, 1); + struct obj *obj = lo->obj; + + lua_newtable(L); + + if (!obj || obj->where == OBJ_LUAFREE) { + nhl_add_table_entry_int(L, "NO_OBJ", 1); + return 1; + } + + nhl_add_table_entry_int(L, "has_contents", Has_contents(obj)); + nhl_add_table_entry_int(L, "is_container", Is_container(obj)); + nhl_add_table_entry_int(L, "o_id", obj->o_id); + nhl_add_table_entry_int(L, "ox", obj->ox); + nhl_add_table_entry_int(L, "oy", obj->oy); + nhl_add_table_entry_int(L, "otyp", obj->otyp); + if (OBJ_NAME(objects[obj->otyp])) + nhl_add_table_entry_str(L, "otyp_name", OBJ_NAME(objects[obj->otyp])); + if (OBJ_DESCR(objects[obj->otyp])) + nhl_add_table_entry_str(L, "otyp_descr", + OBJ_DESCR(objects[obj->otyp])); + nhl_add_table_entry_int(L, "owt", obj->owt); + nhl_add_table_entry_int(L, "quan", obj->quan); + nhl_add_table_entry_int(L, "spe", obj->spe); + + if (obj->otyp == STATUE) + nhl_add_table_entry_int(L, "historic", + (obj->spe & CORPSTAT_HISTORIC) != 0); + if (obj->otyp == CORPSE || obj->otyp == STATUE) { + nhl_add_table_entry_int(L, "male", + (obj->spe & CORPSTAT_MALE) != 0); + nhl_add_table_entry_int(L, "female", + (obj->spe & CORPSTAT_FEMALE) != 0); + } + + nhl_add_table_entry_char(L, "oclass", + def_oc_syms[(uchar) obj->oclass].sym); + nhl_add_table_entry_char(L, "invlet", obj->invlet); + /* TODO: nhl_add_table_entry_char(L, "oartifact", obj->oartifact);*/ + nhl_add_table_entry_int(L, "where", obj->where); + /* TODO: nhl_add_table_entry_int(L, "timed", obj->timed); */ + nhl_add_table_entry_int(L, "cursed", obj->cursed); + nhl_add_table_entry_int(L, "blessed", obj->blessed); + nhl_add_table_entry_int(L, "unpaid", obj->unpaid); + nhl_add_table_entry_int(L, "no_charge", obj->no_charge); + nhl_add_table_entry_int(L, "known", obj->known); + nhl_add_table_entry_int(L, "dknown", obj->dknown); + nhl_add_table_entry_int(L, "bknown", obj->bknown); + nhl_add_table_entry_int(L, "rknown", obj->rknown); + nhl_add_table_entry_int(L, "tknown", obj->tknown); + if (obj->oclass == POTION_CLASS) + nhl_add_table_entry_int(L, "odiluted", obj->odiluted); + else + nhl_add_table_entry_int(L, "oeroded", obj->oeroded); + nhl_add_table_entry_int(L, "oeroded2", obj->oeroded2); + /* TODO: orotten, norevive */ + nhl_add_table_entry_int(L, "oerodeproof", obj->oerodeproof); + nhl_add_table_entry_int(L, "olocked", obj->olocked); + nhl_add_table_entry_int(L, "obroken", obj->obroken); + if (is_poisonable(obj)) + nhl_add_table_entry_int(L, "opoisoned", obj->opoisoned); + else + nhl_add_table_entry_int(L, "otrapped", obj->otrapped); + /* TODO: degraded_horn */ + nhl_add_table_entry_int(L, "recharged", obj->recharged); + /* TODO: on_ice */ + nhl_add_table_entry_int(L, "lamplit", obj->lamplit); + nhl_add_table_entry_int(L, "globby", obj->globby); + nhl_add_table_entry_int(L, "greased", obj->greased); + nhl_add_table_entry_int(L, "nomerge", obj->nomerge); + nhl_add_table_entry_int(L, "how_lost", obj->how_lost); + nhl_add_table_entry_int(L, "in_use", obj->in_use); + nhl_add_table_entry_int(L, "bypass", obj->bypass); + nhl_add_table_entry_int(L, "cknown", obj->cknown); + nhl_add_table_entry_int(L, "lknown", obj->lknown); + nhl_add_table_entry_int(L, "corpsenm", obj->corpsenm); + if (obj->corpsenm != NON_PM + && (obj->otyp == TIN || obj->otyp == CORPSE || obj->otyp == EGG + || obj->otyp == FIGURINE || obj->otyp == STATUE)) + nhl_add_table_entry_str(L, "corpsenm_name", + mons[obj->corpsenm].pmnames[NEUTRAL]); + /* TODO: leashmon, fromsink, novelidx, record_achieve_special */ + nhl_add_table_entry_int(L, "usecount", obj->usecount); + /* TODO: spestudied */ + nhl_add_table_entry_int(L, "oeaten", obj->oeaten); + nhl_add_table_entry_int(L, "age", obj->age); + nhl_add_table_entry_int(L, "owornmask", obj->owornmask); + /* TODO: more of oextra */ + nhl_add_table_entry_int(L, "has_oname", has_oname(obj)); + if (has_oname(obj)) + nhl_add_table_entry_str(L, "oname", ONAME(obj)); + + return 1; +} + +DISABLE_WARNING_UNREACHABLE_CODE + +/* create a new object via wishing routine */ +/* local o = obj.new("rock"); */ +/* local o = obj.new({ id = "food ration", class = "%" }); */ +staticfn int +l_obj_new_readobjnam(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 1 && lua_type(L, 1) == LUA_TSTRING) { + char buf[BUFSZ]; + struct obj *otmp; + + Sprintf(buf, "%s", luaL_checkstring(L, 1)); + lua_pop(L, 1); + if ((otmp = readobjnam(buf, NULL)) == &hands_obj) + otmp = NULL; + (void) l_obj_push(L, otmp); + return 1; + } else if (argc == 1 && lua_type(L, 1) == LUA_TTABLE) { + short id = get_table_objtype(L); + xint16 class = get_table_objclass(L); + struct obj *otmp; + + if (id >= FIRST_OBJECT) { + otmp = mksobj(id, TRUE, FALSE); + } else { + class = def_char_to_objclass(class); + if (class >= MAXOCLASSES) + class = RANDOM_CLASS; + otmp = mkobj(class, FALSE); + } + lua_pop(L, 1); + (void) l_obj_push(L, otmp); + return 1; + } else + nhl_error(L, "l_obj_new_readobjname: Wrong args"); + /*NOTREACHED*/ + return 0; +} + +/* Get the topmost object on the map at x,y */ +/* local o = obj.at(x, y); */ +staticfn int +l_obj_at(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 2) { + coordxy x, y; + + x = (coordxy) luaL_checkinteger(L, 1); + y = (coordxy) luaL_checkinteger(L, 2); + cvt_to_abscoord(&x, &y); + + lua_pop(L, 2); + (void) l_obj_push(L, svl.level.objects[x][y]); + return 1; + } else + nhl_error(L, "l_obj_at: Wrong args"); + /*NOTREACHED*/ + return 0; +} + +/* Place an object on the map at (x,y). + local o = obj.new("rock"); + o:placeobj(u.ux, u.uy); */ +staticfn int +l_obj_placeobj(lua_State *L) +{ + int argc = lua_gettop(L); + struct _lua_obj *lo = l_obj_check(L, 1); + coordxy x, y; + + if (argc != 3) + nhl_error(L, "l_obj_placeobj: Wrong args"); + + x = (coordxy) luaL_checkinteger(L, 2); + y = (coordxy) luaL_checkinteger(L, 3); + cvt_to_abscoord(&x, &y); + + lua_pop(L, 3); + + if (lobj_is_ok(lo)) { + obj_extract_self(lo->obj); + place_object(lo->obj, x, y); + newsym(x, y); + } + + return 0; +} + +RESTORE_WARNING_UNREACHABLE_CODE + +/* Get the next object in the object chain */ +/* local o = obj.at(x, y); + local o2 = o:next(true); + local firstobj = obj.next(); +*/ +staticfn int +l_obj_nextobj(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 0) { + (void) l_obj_push(L, fobj); + } else { + struct _lua_obj *lo = l_obj_check(L, 1); + boolean use_nexthere = FALSE; + + if (argc == 2) + use_nexthere = lua_toboolean(L, 2); + + if (lo && lo->obj) + (void) l_obj_push(L, (use_nexthere && lo->obj->where == OBJ_FLOOR) + ? lo->obj->nexthere + : lo->obj->nobj); + } + return 1; +} + +/* Get the container object is in */ +/* local box = o:container(); */ +staticfn int +l_obj_container(lua_State *L) +{ + struct _lua_obj *lo = l_obj_check(L, 1); + + if (lo && lo->obj && lo->obj->where == OBJ_CONTAINED) + (void) l_obj_push(L, lo->obj->ocontainer); + else + (void) l_obj_push(L, NULL); + return 1; +} + +/* Is the object a null? */ +/* local badobj = o:isnull(); */ +staticfn int +l_obj_isnull(lua_State *L) +{ + struct _lua_obj *lo = l_obj_check(L, 1); + + lua_pushboolean(L, !lobj_is_ok(lo)); + return 1; +} + +DISABLE_WARNING_UNREACHABLE_CODE + +/* does object have a timer of certain type? */ +/* local hastimer = o:has_timer("rot-organic"); */ +staticfn int +l_obj_timer_has(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 2) { + struct _lua_obj *lo = l_obj_check(L, 1); + short timertype = nhl_get_timertype(L, 2); + + if (timer_is_obj(timertype) && lo && lo->obj) { + lua_pushboolean(L, obj_has_timer(lo->obj, timertype)); + return 1; + } else { + lua_pushboolean(L, FALSE); + return 1; + } + } else + nhl_error(L, "l_obj_timer_has: Wrong args"); + return 0; +} + +/* peek at an object timer. return the turn when timer triggers. + returns 0 if no such timer attached to the object. */ +/* local timeout = o:peek_timer("hatch-egg"); */ +staticfn int +l_obj_timer_peek(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 2) { + struct _lua_obj *lo = l_obj_check(L, 1); + short timertype = nhl_get_timertype(L, 2); + + if (timer_is_obj(timertype) && lo && lo->obj) { + lua_pushinteger(L, peek_timer(timertype, obj_to_any(lo->obj))); + return 1; + } else { + lua_pushinteger(L, 0); + return 1; + } + } else + nhl_error(L, "l_obj_timer_peek: Wrong args"); + /*NOTREACHED*/ + return 0; +} + +/* stop object timer(s). return the turn when timer triggers. + returns 0 if no such timer attached to the object. + without a timer type parameter, stops all timers for the object. */ +/* local timeout = o:stop_timer("rot-organic"); */ +/* o:stop_timer(); */ +staticfn int +l_obj_timer_stop(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 1) { + struct _lua_obj *lo = l_obj_check(L, 1); + + if (lo && lo->obj) + obj_stop_timers(lo->obj); + return 0; + + } else if (argc == 2) { + struct _lua_obj *lo = l_obj_check(L, 1); + short timertype = nhl_get_timertype(L, 2); + + if (timer_is_obj(timertype) && lo && lo->obj) { + lua_pushinteger(L, stop_timer(timertype, obj_to_any(lo->obj))); + return 1; + } else { + lua_pushinteger(L, 0); + return 1; + } + } else + nhl_error(L, "l_obj_timer_stop: Wrong args"); + return 0; +} + +RESTORE_WARNING_UNREACHABLE_CODE + +/* start an object timer. */ +/* o:start_timer("hatch-egg", 10); */ +staticfn int +l_obj_timer_start(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 3) { + struct _lua_obj *lo = l_obj_check(L, 1); + short timertype = nhl_get_timertype(L, 2); + long when = luaL_checkinteger(L, 3); + + if (timer_is_obj(timertype) && lo && lo->obj && when > 0) { + if (obj_has_timer(lo->obj, timertype)) + stop_timer(timertype, obj_to_any(lo->obj)); + start_timer(when, TIMER_OBJECT, timertype, obj_to_any(lo->obj)); + } + } else + nhl_error(L, "l_obj_timer_start: Wrong args"); + return 0; +} + +/* bury an obj. returns true if object is gone (merged with ground), + false otherwise. */ +/* local ogone = o:bury(); */ +/* local ogone = o:bury(5,5); */ +staticfn int +l_obj_bury(lua_State *L) +{ + int argc = lua_gettop(L); + boolean dealloced = FALSE; + struct _lua_obj *lo = l_obj_check(L, 1); + coordxy x = 0, y = 0; + + if (argc == 1) { + x = lo->obj->ox; + y = lo->obj->oy; + } else if (argc == 3) { + x = (coordxy) lua_tointeger(L, 2); + y = (coordxy) lua_tointeger(L, 3); + cvt_to_abscoord(&x, &y); + } else + nhl_error(L, "l_obj_bury: Wrong args"); + + if (lobj_is_ok(lo) && isok(x, y)) { + lo->obj->ox = x; + lo->obj->oy = y; + (void) bury_an_obj(lo->obj, &dealloced); + } + lua_pushboolean(L, dealloced); + return 1; +} + +static const struct luaL_Reg l_obj_methods[] = { + { "new", l_obj_new_readobjnam }, + { "isnull", l_obj_isnull }, + { "at", l_obj_at }, + { "next", l_obj_nextobj }, + { "totable", l_obj_to_table }, + { "class", l_obj_objects_to_table }, + { "placeobj", l_obj_placeobj }, + { "container", l_obj_container }, + { "contents", l_obj_getcontents }, + { "addcontent", l_obj_add_to_container }, + { "has_timer", l_obj_timer_has }, + { "peek_timer", l_obj_timer_peek }, + { "stop_timer", l_obj_timer_stop }, + { "start_timer", l_obj_timer_start }, + { "bury", l_obj_bury }, + { NULL, NULL } +}; + +static const luaL_Reg l_obj_meta[] = { + { "__gc", l_obj_gc }, + { NULL, NULL } +}; + +int +l_obj_register(lua_State *L) +{ + /* Table of instance methods (e.g. an_object:isnull()) + and static methods (e.g. obj.new("dagger")). */ + luaL_newlib(L, l_obj_methods); + + /* metatable = { __name = "obj", __gc = l_obj_gc } */ + luaL_newmetatable(L, "obj"); + luaL_setfuncs(L, l_obj_meta, 0); + /* metatable.__index points at the object method table. */ + lua_pushvalue(L, -2); + lua_setfield(L, -2, "__index"); + + /* Don't let lua code mess with the real metatable. + Instead offer a fake one that only contains __gc. */ + luaL_newlib(L, l_obj_meta); + lua_setfield(L, -2, "__metatable"); + + /* We don't need the metatable anymore. It's safe in the + Lua registry for use by luaL_setmetatable. */ + lua_pop(L, 1); + + /* global obj = the method table we created at the start */ + lua_setglobal(L, "obj"); + return 0; +} + +/* for 'onefile' processing where end of this file isn't necessarily the + end of the source code seen by the compiler */ +#undef lobj_is_ok + +/*nhlobj.c*/ diff --git a/src/nhlsel.c b/src/nhlsel.c new file mode 100644 index 000000000..cbaf6f9b8 --- /dev/null +++ b/src/nhlsel.c @@ -0,0 +1,1051 @@ +/* NetHack 5.0 nhlua.c $NHDT-Date: 1769840272 2026/01/30 22:17:52 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.69 $ */ +/* Copyright (c) 2018 by Pasi Kallinen */ +/* NetHack may be freely redistributed. See license for details. */ + +#include "hack.h" +#include "sp_lev.h" + + +struct selectionvar *l_selection_check(lua_State *, int); +staticfn struct selectionvar *l_selection_push_new(lua_State *); + +/* lua_CFunction prototypes */ +staticfn int l_selection_new(lua_State *); +staticfn int l_selection_clone(lua_State *); +staticfn int l_selection_numpoints(lua_State *); +staticfn int l_selection_getpoint(lua_State *); +staticfn int l_selection_setpoint(lua_State *); +staticfn int l_selection_filter_percent(lua_State *); +staticfn int l_selection_rndcoord(lua_State *); +staticfn int l_selection_room(lua_State *); +staticfn int l_selection_getbounds(lua_State *); +staticfn boolean params_sel_2coords(lua_State *, struct selectionvar **, + coordxy *, coordxy *, coordxy *, coordxy *); +staticfn int l_selection_line(lua_State *); +staticfn int l_selection_randline(lua_State *); +staticfn int l_selection_rect(lua_State *); +staticfn int l_selection_fillrect(lua_State *); +staticfn int l_selection_grow(lua_State *); +staticfn int l_selection_filter_mapchar(lua_State *); +staticfn int l_selection_match(lua_State *); +staticfn int l_selection_flood(lua_State *); +staticfn int l_selection_circle(lua_State *); +staticfn int l_selection_ellipse(lua_State *); +staticfn int l_selection_gradient(lua_State *); +staticfn int l_selection_iterate(lua_State *); +staticfn int l_selection_size_description(lua_State *L); +staticfn int l_selection_gc(lua_State *); +staticfn int l_selection_not(lua_State *); +staticfn int l_selection_and(lua_State *); +staticfn int l_selection_or(lua_State *); +staticfn int l_selection_xor(lua_State *); +/* There doesn't seem to be a point in having a l_selection_add since it would + * do the same thing as l_selection_or. The addition operator is mapped to + * l_selection_or. */ +staticfn int l_selection_sub(lua_State *); +#if 0 +/* the following do not appear to currently be + used and because they are static, the OSX + compiler is complaining about them. I've + if ifdef'd out the prototype here and the + function body below. + */ +staticfn int l_selection_ipairs(lua_State *); +staticfn struct selectionvar *l_selection_to(lua_State *, int); +#endif + +struct selectionvar * +l_selection_check(lua_State *L, int index) +{ + struct selectionvar *sel; + + luaL_checktype(L, index, LUA_TUSERDATA); + sel = (struct selectionvar *) luaL_checkudata(L, index, "selection"); + if (!sel) + nhl_error(L, "Selection error"); + return sel; +} + +staticfn int +l_selection_gc(lua_State *L) +{ + struct selectionvar *sel = l_selection_check(L, 1); + + if (sel) + selection_free(sel, FALSE); + return 0; +} + +#if 0 +staticfn struct selectionvar * +l_selection_to(lua_State *L, int index) +{ + struct selectionvar *sel + = (struct selectionvar *) lua_touserdata(L, index); + + if (!sel) + nhl_error(L, "Selection error"); + return sel; +} +#endif + +/* push a new selection into lua stack, return the selectionvar */ +staticfn struct selectionvar * +l_selection_push_new(lua_State *L) +{ + struct selectionvar *tmp = selection_new(); + struct selectionvar *sel + = (struct selectionvar *) lua_newuserdata(L, sizeof (struct selectionvar)); + + luaL_getmetatable(L, "selection"); + lua_setmetatable(L, -2); + + *sel = *tmp; + sel->map = dupstr(tmp->map); + selection_free(tmp, TRUE); + + return sel; +} + +/* push a copy of selectionvar tmp to lua stack */ +void +l_selection_push_copy(lua_State *L, struct selectionvar *tmp) +{ + struct selectionvar *sel + = (struct selectionvar *) lua_newuserdata(L, sizeof (struct selectionvar)); + + luaL_getmetatable(L, "selection"); + lua_setmetatable(L, -2); + + *sel = *tmp; + sel->map = dupstr(tmp->map); +} + + +/* local sel = selection.new(); */ +staticfn int +l_selection_new(lua_State *L) +{ + (void) l_selection_push_new(L); + return 1; +} + +/* Replace the topmost selection in the stack with a clone of it. */ +/* local sel = selection.clone(sel); */ +staticfn int +l_selection_clone(lua_State *L) +{ + struct selectionvar *sel = l_selection_check(L, 1); + struct selectionvar *tmp; + + (void) l_selection_new(L); + tmp = l_selection_check(L, 2); + if (tmp->map) + free(tmp->map); + *tmp = *sel; + tmp->map = dupstr(sel->map); + return 1; +} + +DISABLE_WARNING_UNREACHABLE_CODE + +/* selection.set(sel, x, y); */ +/* selection.set(sel, x, y, value); */ +/* local sel = selection.set(); */ +/* local sel = sel:set(); */ +/* local sel = selection.set(sel); */ +/* TODO: allow setting multiple coords at once: set({x1,y1},{x2,y2},...); */ +staticfn int +l_selection_setpoint(lua_State *L) +{ + struct selectionvar *sel = (struct selectionvar *) 0; + coordxy x = -1, y = -1; + int val = 1; + int argc = lua_gettop(L); + long crd = 0L; + + if (argc == 0) { + (void) l_selection_new(L); + } else if (argc == 1) { + sel = l_selection_check(L, 1); + } else if (argc == 2) { + x = (coordxy) luaL_checkinteger(L, 1); + y = (coordxy) luaL_checkinteger(L, 2); + lua_pop(L, 2); + (void) l_selection_new(L); + sel = l_selection_check(L, 1); + } else { + sel = l_selection_check(L, 1); + x = (coordxy) luaL_checkinteger(L, 2); + y = (coordxy) luaL_checkinteger(L, 3); + val = (int) luaL_optinteger(L, 4, 1); + } + + if (!sel || !sel->map) { + nhl_error(L, "Selection setpoint error"); + /*NOTREACHED*/ + return 0; + } + + if (x == -1 && y == -1) + crd = SP_COORD_PACK_RANDOM(0); + else + crd = SP_COORD_PACK(x,y); + get_location_coord(&x, &y, ANY_LOC, + gc.coder ? gc.coder->croom : NULL, crd); + selection_setpoint(x, y, sel, val); + lua_settop(L, 1); + return 1; +} + +/* local numpoints = selection.numpoints(sel); */ +staticfn int +l_selection_numpoints(lua_State *L) +{ + struct selectionvar *sel = l_selection_check(L, 1); + coordxy x, y; + int ret = 0; + NhRect rect = cg.zeroNhRect; + + selection_getbounds(sel, &rect); + + for (x = rect.lx; x <= rect.hx; x++) + for (y = rect.ly; y <= rect.hy; y++) + if (selection_getpoint(x, y, sel)) + ret++; + + lua_settop(L, 0); + lua_pushinteger(L, ret); + return 1; +} + +/* local value = selection.get(sel, x, y); */ +staticfn int +l_selection_getpoint(lua_State *L) +{ + struct selectionvar *sel = l_selection_check(L, 1); + coordxy x, y; + lua_Integer ix, iy; + int val; + long crd; + + lua_remove(L, 1); /* sel */ + if (!nhl_get_xy_params(L, &ix, &iy)) { + nhl_error(L, "l_selection_getpoint: Incorrect params"); + /*NOTREACHED*/ + return 0; + } + x = (coordxy) ix; + y = (coordxy) iy; + + if (x == -1 && y == -1) + crd = SP_COORD_PACK_RANDOM(0); + else + crd = SP_COORD_PACK(x,y); + get_location_coord(&x, &y, ANY_LOC, + gc.coder ? gc.coder->croom : NULL, crd); + + val = selection_getpoint(x, y, sel); + lua_settop(L, 0); + lua_pushnumber(L, val); + return 1; +} + +RESTORE_WARNING_UNREACHABLE_CODE + +/* local s = selection.negate(sel); */ +/* local s = selection.negate(); */ +/* local s = sel:negate(); */ +staticfn int +l_selection_not(lua_State *L) +{ + int argc = lua_gettop(L); + struct selectionvar *sel, *sel2; + + if (argc == 0) { + (void) l_selection_new(L); + sel = l_selection_check(L, 1); + selection_clear(sel, 1); + } else { + (void) l_selection_check(L, 1); + (void) l_selection_clone(L); + sel2 = l_selection_check(L, 2); + selection_not(sel2); + lua_remove(L, 1); + } + return 1; +} + +/* local sel = selection.area(4,5, 40,10) & selection.rect(7,8, 60,14); */ +staticfn int +l_selection_and(lua_State *L) +{ + int x, y; + struct selectionvar *sela = l_selection_check(L, 1); + struct selectionvar *selb = l_selection_check(L, 2); + struct selectionvar *selr = l_selection_push_new(L); + NhRect rect = cg.zeroNhRect; + + rect_bounds(sela->bounds, selb->bounds, &rect); + + for (x = rect.lx; x <= rect.hx; x++) + for (y = rect.ly; y <= rect.hy; y++) { + int val = (selection_getpoint(x, y, sela) + & selection_getpoint(x, y, selb)); + + selection_setpoint(x, y, selr, val); + } + + lua_remove(L, 1); + lua_remove(L, 1); + return 1; +} + +/* local sel = selection.area(4,5, 40,10) | selection.rect(7,8, 60,14); */ +staticfn int +l_selection_or(lua_State *L) +{ + int x,y; + struct selectionvar *sela = l_selection_check(L, 1); + struct selectionvar *selb = l_selection_check(L, 2); + struct selectionvar *selr = l_selection_push_new(L); + NhRect rect = cg.zeroNhRect; + + rect_bounds(sela->bounds, selb->bounds, &rect); + + for (x = rect.lx; x <= rect.hx; x++) + for (y = rect.ly; y <= rect.hy; y++) { + int val = (selection_getpoint(x, y, sela) + | selection_getpoint(x, y, selb)); + + selection_setpoint(x, y, selr, val); + } + selr->bounds = rect; + + lua_remove(L, 1); + lua_remove(L, 1); + return 1; +} + +/* local sel = selection.area(4,5, 40,10) ~ selection.rect(7,8, 60,14); */ +staticfn int +l_selection_xor(lua_State *L) +{ + int x,y; + struct selectionvar *sela = l_selection_check(L, 1); + struct selectionvar *selb = l_selection_check(L, 2); + struct selectionvar *selr = l_selection_push_new(L); + NhRect rect = cg.zeroNhRect; + + rect_bounds(sela->bounds, selb->bounds, &rect); + + for (x = rect.lx; x <= rect.hx; x++) + for (y = rect.ly; y <= rect.hy; y++) { + int val = (selection_getpoint(x, y, sela) + ^ selection_getpoint(x, y, selb)); + + selection_setpoint(x, y, selr, val); + } + /* this may have created a smaller or irregular selection with + * bounds_dirty set to true - update its boundaries */ + selection_recalc_bounds(selr); + + lua_remove(L, 1); + lua_remove(L, 1); + return 1; +} + +/* local sel = selection.area(10,10, 20,20) - selection.area(14,14, 17,17) + * - i.e. points that are in A but not in B */ +staticfn int +l_selection_sub(lua_State *L) +{ + int x,y; + struct selectionvar *sela = l_selection_check(L, 1); + struct selectionvar *selb = l_selection_check(L, 2); + struct selectionvar *selr = l_selection_push_new(L); + NhRect rect = cg.zeroNhRect; + + rect_bounds(sela->bounds, selb->bounds, &rect); + + for (x = rect.lx; x <= rect.hx; x++) + for (y = rect.ly; y <= rect.hy; y++) { + coordxy a_pt = selection_getpoint(x, y, sela); + coordxy b_pt = selection_getpoint(x, y, selb); + int val = (a_pt ^ b_pt) & a_pt; + selection_setpoint(x, y, selr, val); + } + /* this may have created a smaller or irregular selection with + * bounds_dirty set to true - update its boundaries */ + selection_recalc_bounds(selr); + + lua_remove(L, 1); + lua_remove(L, 1); + return 1; +} + +/* local s = selection.percentage(sel, 50); */ +staticfn int +l_selection_filter_percent(lua_State *L) +{ + int argc = lua_gettop(L); + struct selectionvar *sel = l_selection_check(L, 1); + int p = (int) luaL_checkinteger(L, 2); + struct selectionvar *tmp; + + tmp = selection_filter_percent(sel, p); + lua_pop(L, argc); + l_selection_push_copy(L, tmp); + selection_free(tmp, TRUE); + + return 1; +} + +/* local pt = selection.rndcoord(sel); */ +/* local pt = selection.rndcoord(sel, 1); */ +staticfn int +l_selection_rndcoord(lua_State *L) +{ + struct selectionvar *sel = l_selection_check(L, 1); + int removeit = (int) luaL_optinteger(L, 2, 0); + coordxy x = -1, y = -1; + selection_rndcoord(sel, &x, &y, removeit); + if (!(x == -1 && y == -1)) { + update_croom(); + if (gc.coder && gc.coder->croom) { + x -= gc.coder->croom->lx; + y -= gc.coder->croom->ly; + } else { + x -= gx.xstart; + y -= gy.ystart; + } + } + lua_settop(L, 0); + lua_newtable(L); + nhl_add_table_entry_int(L, "x", x); + nhl_add_table_entry_int(L, "y", y); + return 1; +} + +/* local s = selection.room(); */ +staticfn int +l_selection_room(lua_State *L) +{ + struct selectionvar *sel; + int argc = lua_gettop(L); + struct mkroom *croom = NULL; + + if (argc == 1) { + int i = luaL_checkinteger(L, -1); + + croom = (i >= 0 && i < svn.nroom) ? &svr.rooms[i] : NULL; + } + + sel = selection_from_mkroom(croom); + + l_selection_push_copy(L, sel); + selection_free(sel, TRUE); + + return 1; +} + +/* local rect = sel:bounds(); */ +staticfn int +l_selection_getbounds(lua_State *L) +{ + struct selectionvar *sel = l_selection_check(L, 1); + NhRect rect = cg.zeroNhRect; + + selection_getbounds(sel, &rect); + lua_settop(L, 0); + lua_newtable(L); + nhl_add_table_entry_int(L, "lx", rect.lx); + nhl_add_table_entry_int(L, "ly", rect.ly); + nhl_add_table_entry_int(L, "hx", rect.hx); + nhl_add_table_entry_int(L, "hy", rect.hy); + return 1; +} + +/* internal function to get a selection and 4 integer values from lua stack. + removes the integers from the stack. + returns TRUE if params are good. +*/ +/* function(selection, x1,y1, x2,y2) */ +/* selection:function(x1,y1, x2,y2) */ +staticfn boolean +params_sel_2coords(lua_State *L, struct selectionvar **sel, + coordxy *x1, coordxy *y1, coordxy *x2, coordxy *y2) +{ + int argc = lua_gettop(L); + + if (argc == 4) { + (void) l_selection_new(L); + *x1 = (coordxy) luaL_checkinteger(L, 1); + *y1 = (coordxy) luaL_checkinteger(L, 2); + *x2 = (coordxy) luaL_checkinteger(L, 3); + *y2 = (coordxy) luaL_checkinteger(L, 4); + *sel = l_selection_check(L, 5); + lua_remove(L, 1); + lua_remove(L, 1); + lua_remove(L, 1); + lua_remove(L, 1); + return TRUE; + } else if (argc == 5) { + *sel = l_selection_check(L, 1); + *x1 = (coordxy) luaL_checkinteger(L, 2); + *y1 = (coordxy) luaL_checkinteger(L, 3); + *x2 = (coordxy) luaL_checkinteger(L, 4); + *y2 = (coordxy) luaL_checkinteger(L, 5); + lua_pop(L, 4); + return TRUE; + } + return FALSE; +} + +/* local s = selection.line(sel, x1,y1, x2,y2); */ +/* local s = selection.line(x1,y1, x2,y2); */ +/* s:line(x1,y1, x2,y2); */ +staticfn int +l_selection_line(lua_State *L) +{ + struct selectionvar *sel = NULL; + coordxy x1, y1, x2, y2; + + if (!params_sel_2coords(L, &sel, &x1, &y1, &x2, &y2)) { + nhl_error(L, "selection.line: illegal arguments"); + } + + get_location_coord(&x1, &y1, ANY_LOC, gc.coder ? gc.coder->croom : NULL, + SP_COORD_PACK(x1, y1)); + get_location_coord(&x2, &y2, ANY_LOC, gc.coder ? gc.coder->croom : NULL, + SP_COORD_PACK(x2, y2)); + + (void) l_selection_clone(L); + sel = l_selection_check(L, 2); + selection_do_line(x1,y1,x2,y2, sel); + return 1; +} + +/* local s = selection.rect(sel, x1,y1, x2,y2); */ +staticfn int +l_selection_rect(lua_State *L) +{ + struct selectionvar *sel = NULL; + coordxy x1, y1, x2, y2; + + if (!params_sel_2coords(L, &sel, &x1, &y1, &x2, &y2)) { + nhl_error(L, "selection.rect: illegal arguments"); + } + + get_location_coord(&x1, &y1, ANY_LOC, gc.coder ? gc.coder->croom : NULL, + SP_COORD_PACK(x1, y1)); + get_location_coord(&x2, &y2, ANY_LOC, gc.coder ? gc.coder->croom : NULL, + SP_COORD_PACK(x2, y2)); + + (void) l_selection_clone(L); + sel = l_selection_check(L, 2); + selection_do_line(x1, y1, x2, y1, sel); + selection_do_line(x1, y1, x1, y2, sel); + selection_do_line(x2, y1, x2, y2, sel); + selection_do_line(x1, y2, x2, y2, sel); + return 1; +} + +/* local s = selection.fillrect(sel, x1,y1, x2,y2); */ +/* local s = selection.fillrect(x1,y1, x2,y2); */ +/* s:fillrect(x1,y1, x2,y2); */ +/* selection.area(x1,y1, x2,y2); */ +staticfn int +l_selection_fillrect(lua_State *L) +{ + struct selectionvar *sel = NULL; + int y; + coordxy x1, y1, x2, y2; + + if (!params_sel_2coords(L, &sel, &x1, &y1, &x2, &y2)) { + nhl_error(L, "selection.fillrect: illegal arguments"); + } + + get_location_coord(&x1, &y1, ANY_LOC, gc.coder ? gc.coder->croom : NULL, + SP_COORD_PACK(x1, y1)); + get_location_coord(&x2, &y2, ANY_LOC, gc.coder ? gc.coder->croom : NULL, + SP_COORD_PACK(x2, y2)); + + (void) l_selection_clone(L); + sel = l_selection_check(L, 2); + if (x1 == x2) { + for (y = y1; y <= y2; y++) + selection_setpoint(x1, y, sel, 1); + } else { + for (y = y1; y <= y2; y++) + selection_do_line(x1, y, x2, y, sel); + } + return 1; +} + +/* local s = selection.randline(sel, x1,y1, x2,y2, roughness); */ +/* local s = selection.randline(x1,y1, x2,y2, roughness); */ +/* TODO: selection.randline(x1,y1, x2,y2, roughness); */ +/* TODO: selection.randline({x1,y1}, {x2,y2}, roughness); */ +staticfn int +l_selection_randline(lua_State *L) +{ + int argc = lua_gettop(L); + struct selectionvar *sel; + coordxy x1 = 0, y1 = 0, x2 = 0, y2 = 0; + int roughness = 7; + + if (argc == 6) { + (void) l_selection_check(L, 1); + x1 = (coordxy) luaL_checkinteger(L, 2); + y1 = (coordxy) luaL_checkinteger(L, 3); + x2 = (coordxy) luaL_checkinteger(L, 4); + y2 = (coordxy) luaL_checkinteger(L, 5); + roughness = (int) luaL_checkinteger(L, 6); + lua_pop(L, 5); + } else if (argc == 5 && lua_type(L, 1) == LUA_TNUMBER) { + x1 = (coordxy) luaL_checkinteger(L, 1); + y1 = (coordxy) luaL_checkinteger(L, 2); + x2 = (coordxy) luaL_checkinteger(L, 3); + y2 = (coordxy) luaL_checkinteger(L, 4); + roughness = (int) luaL_checkinteger(L, 5); + lua_pop(L, 5); + (void) l_selection_new(L); + (void) l_selection_check(L, 1); + } + + get_location_coord(&x1, &y1, ANY_LOC, gc.coder ? gc.coder->croom : NULL, + SP_COORD_PACK(x1, y1)); + get_location_coord(&x2, &y2, ANY_LOC, gc.coder ? gc.coder->croom : NULL, + SP_COORD_PACK(x2, y2)); + + (void) l_selection_clone(L); + sel = l_selection_check(L, 2); + selection_do_randline(x1, y1, x2, y2, roughness, 12, sel); + return 1; +} + +/* local s = selection.grow(sel); */ +/* local s = selection.grow(sel, "north"); */ +staticfn int +l_selection_grow(lua_State *L) +{ + static const char *const growdirs[] = { + "all", "random", "north", "west", "east", "south", NULL + }; + static const int growdirs2i[] = { + W_ANY, W_RANDOM, W_NORTH, W_WEST, W_EAST, W_SOUTH, 0 + }; + struct selectionvar *sel; + int dir, argc = lua_gettop(L); + + (void) l_selection_check(L, 1); + dir = growdirs2i[luaL_checkoption(L, 2, "all", growdirs)]; + + if (argc == 2) + lua_pop(L, 1); /* get rid of growdir */ + + (void) l_selection_clone(L); + sel = l_selection_check(L, 2); + selection_do_grow(sel, dir); + return 1; +} + + +/* local s = selection.filter_mapchar(sel, mapchar, lit); */ +staticfn int +l_selection_filter_mapchar(lua_State *L) +{ + int argc = lua_gettop(L); + struct selectionvar *sel = l_selection_check(L, 1); + char *mapchr = dupstr(luaL_checkstring(L, 2)); + coordxy typ = check_mapchr(mapchr); + int lit = (int) luaL_optinteger(L, 3, -2); /* TODO: special lit values */ + struct selectionvar *tmp; + + if (typ == INVALID_TYPE) + nhl_error(L, "Erroneous map char"); + + tmp = selection_filter_mapchar(sel, typ, lit); + lua_pop(L, argc); + l_selection_push_copy(L, tmp); + selection_free(tmp, TRUE); + + if (mapchr) + free(mapchr); + + return 1; +} + +/* local s = selection.match([[...]]); */ +staticfn int +l_selection_match(lua_State *L) +{ + int argc = lua_gettop(L); + struct selectionvar *sel = (struct selectionvar *) 0; + struct mapfragment *mf = (struct mapfragment *) 0; + int x, y; + + if (argc == 1) { + const char *err; + char *mapstr = dupstr(luaL_checkstring(L, 1)); + lua_pop(L, 1); + (void) l_selection_new(L); + sel = l_selection_check(L, 1); + + mf = mapfrag_fromstr(mapstr); + free(mapstr); + + if ((err = mapfrag_error(mf)) != NULL) { + nhl_error(L, err); + /*NOTREACHED*/ + } + + } else { + nhl_error(L, "wrong parameters"); + /*NOTREACHED*/ + } + + for (y = 0; y <= sel->hei; y++) + for (x = 1; x < sel->wid; x++) + selection_setpoint(x, y, sel, mapfrag_match(mf, x,y) ? 1 : 0); + + /* unless the (0, 1) coordinate is a match, this would wind up with a + selection with lx=COLNO, hx=0, etc, so fix the boundaries */ + selection_recalc_bounds(sel); + + mapfrag_free(&mf); + + return 1; +} + + +/* local s = selection.floodfill(x,y); */ +/* local s = selection.floodfill(x,y, diagonals); */ +staticfn int +l_selection_flood(lua_State *L) +{ + int argc = lua_gettop(L); + struct selectionvar *sel = (struct selectionvar *) 0; + coordxy x = 0, y = 0; + boolean diagonals = FALSE; + + if (argc == 2 || argc == 3) { + x = (coordxy) luaL_checkinteger(L, 1); + y = (coordxy) luaL_checkinteger(L, 2); + if (argc == 3) + diagonals = lua_toboolean(L, 3); + lua_pop(L, argc); + (void) l_selection_new(L); + sel = l_selection_check(L, 1); + } else { + nhl_error(L, "wrong parameters"); + /*NOTREACHED*/ + } + + get_location_coord(&x, &y, ANY_LOC, gc.coder ? gc.coder->croom : NULL, + SP_COORD_PACK(x, y)); + + if (isok(x, y)) { + set_floodfillchk_match_under(levl[x][y].typ); + selection_floodfill(sel, x, y, diagonals); + } + return 1; +} + + +/* local s = selection.circle(x,y, radius); */ +/* local s = selection.circle(x, y, radius, filled); */ +/* local s = selection.circle(sel, x, y, radius); */ +/* local s = selection.circle(sel, x, y, radius, filled); */ +staticfn int +l_selection_circle(lua_State *L) +{ + int argc = lua_gettop(L); + struct selectionvar *sel = (struct selectionvar *) 0; + coordxy x = 0, y = 0; + int r = 0, filled = 0; + + if (argc == 3) { + x = (coordxy) luaL_checkinteger(L, 1); + y = (coordxy) luaL_checkinteger(L, 2); + r = (int) luaL_checkinteger(L, 3); + lua_pop(L, 3); + (void) l_selection_new(L); + sel = l_selection_check(L, 1); + filled = 0; + } else if (argc == 4 && lua_type(L, 1) == LUA_TNUMBER) { + x = (coordxy) luaL_checkinteger(L, 1); + y = (coordxy) luaL_checkinteger(L, 2); + r = (int) luaL_checkinteger(L, 3); + filled = (int) luaL_checkinteger(L, 4); /* TODO: boolean*/ + lua_pop(L, 4); + (void) l_selection_new(L); + sel = l_selection_check(L, 1); + } else if (argc == 4 || argc == 5) { + sel = l_selection_check(L, 1); + x = (coordxy) luaL_checkinteger(L, 2); + y = (coordxy) luaL_checkinteger(L, 3); + r = (int) luaL_checkinteger(L, 4); + filled = (int) luaL_optinteger(L, 5, 0); /* TODO: boolean */ + } else { + nhl_error(L, "wrong parameters"); + /*NOTREACHED*/ + } + + get_location_coord(&x, &y, ANY_LOC, gc.coder ? gc.coder->croom : NULL, + SP_COORD_PACK(x, y)); + + selection_do_ellipse(sel, x, y, r, r, !filled); + + lua_settop(L, 1); + return 1; +} + +/* local s = selection.ellipse(x, y, radius1, radius2); */ +/* local s = selection.ellipse(x, y, radius1, radius2, filled); */ +/* local s = selection.ellipse(sel, x, y, radius1, radius2); */ +/* local s = selection.ellipse(sel, x, y, radius1, radius2, filled); */ +staticfn int +l_selection_ellipse(lua_State *L) +{ + int argc = lua_gettop(L); + struct selectionvar *sel = (struct selectionvar *) 0; + coordxy x = 0, y = 0; + int r1 = 0, r2 = 0, filled = 0; + + if (argc == 4) { + x = (coordxy) luaL_checkinteger(L, 1); + y = (coordxy) luaL_checkinteger(L, 2); + r1 = (int) luaL_checkinteger(L, 3); + r2 = (int) luaL_checkinteger(L, 4); + lua_pop(L, 4); + (void) l_selection_new(L); + sel = l_selection_check(L, 1); + filled = 0; + } else if (argc == 5 && lua_type(L, 1) == LUA_TNUMBER) { + x = (coordxy) luaL_checkinteger(L, 1); + y = (coordxy) luaL_checkinteger(L, 2); + r1 = (int) luaL_checkinteger(L, 3); + r2 = (int) luaL_checkinteger(L, 4); + filled = (int) luaL_optinteger(L, 5, 0); /* TODO: boolean */ + lua_pop(L, 5); + (void) l_selection_new(L); + sel = l_selection_check(L, 1); + } else if (argc == 5 || argc == 6) { + sel = l_selection_check(L, 1); + x = (coordxy) luaL_checkinteger(L, 2); + y = (coordxy) luaL_checkinteger(L, 3); + r1 = (int) luaL_checkinteger(L, 4); + r2 = (int) luaL_checkinteger(L, 5); + filled = (int) luaL_optinteger(L, 6, 0); /* TODO: boolean */ + } else { + nhl_error(L, "wrong parameters"); + /*NOTREACHED*/ + } + + get_location_coord(&x, &y, ANY_LOC, gc.coder ? gc.coder->croom : NULL, + SP_COORD_PACK(x, y)); + + selection_do_ellipse(sel, x, y, r1, r2, !filled); + + lua_settop(L, 1); + return 1; +} + +/* Gradients are versatile enough, with so many independently optional + * arguments, that it doesn't seem helpful to provide a non-table form with + * non-obvious argument order. */ +/* selection.gradient({ type = "radial", x = 3, y = 5, x2 = 10, y2 = 12, + * mindist = 4, maxdist = 10, limited = false }); */ +staticfn int +l_selection_gradient(lua_State *L) +{ + int argc = lua_gettop(L); + struct selectionvar *sel = (struct selectionvar *) 0; + /* if x2 and y2 aren't set, the gradient has a single center point of x,y; + * if they are set, the gradient is centered on a (x,y) to (x2,y2) line */ + coordxy x = 0, y = 0, x2 = -1, y2 = -1; + /* points are always added within mindist of the center; the chance for a + * point between mindist and maxdist to be added to the selection starts + * at 100% at mindist and decreases linearly to 0% at maxdist */ + coordxy mindist = 0, maxdist = 0; + long type = 0; + static const char *const gradtypes[] = { + "radial", "square", NULL + }; + static const int gradtypes2i[] = { + SEL_GRADIENT_RADIAL, SEL_GRADIENT_SQUARE, -1 + }; + + if (argc == 1 && lua_type(L, 1) == LUA_TTABLE) { + lcheck_param_table(L); + type = gradtypes2i[get_table_option(L, "type", "radial", gradtypes)]; + x = (coordxy) get_table_int(L, "x"); + y = (coordxy) get_table_int(L, "y"); + x2 = (coordxy) get_table_int_opt(L, "x2", -1); + y2 = (coordxy) get_table_int_opt(L, "y2", -1); + cvt_to_abscoord(&x, &y); + cvt_to_abscoord(&x2, &y2); + /* maxdist is required because there's no obvious default value for + * it, whereas mindist has an obvious default of 0 */ + maxdist = get_table_int(L, "maxdist"); + mindist = get_table_int_opt(L, "mindist", 0); + + lua_pop(L, 1); + (void) l_selection_new(L); + sel = l_selection_check(L, 1); + } else { + nhl_error(L, "selection.gradient requires table argument"); + /* NOTREACHED */ + } + + /* someone might conceivably want to draw a gradient somewhere off-map. So + * the only coordinate that's "illegal" for that is (-1,-1). + * If a level designer really needs to draw a gradient line using that + * coordinate, they can do so by setting regular x and y to -1. */ + if (x2 == -1 && y2 == -1) { + x2 = x; + y2 = y; + } + + selection_do_gradient(sel, x, y, x2, y2, type, mindist, maxdist); + lua_settop(L, 1); + return 1; +} + +DISABLE_WARNING_UNREACHABLE_CODE + +/* sel:iterate(function(x,y) ... end); + * The x, y coordinates passed to the function are map- or room-relative + * rather than absolute, unless there has been no previous map or room + * defined. + */ +staticfn int +l_selection_iterate(lua_State *L) +{ + int argc = lua_gettop(L); + struct selectionvar *sel = (struct selectionvar *) 0; + int x, y; + NhRect rect = cg.zeroNhRect; + + if (argc == 2 && lua_type(L, 2) == LUA_TFUNCTION) { + sel = l_selection_check(L, 1); + selection_getbounds(sel, &rect); + for (y = rect.ly; y <= rect.hy; y++) { + for (x = max(1,rect.lx); x <= rect.hx; x++) + if (selection_getpoint(x, y, sel)) { + coordxy tmpx = x, tmpy = y; + cvt_to_relcoord(&tmpx, &tmpy); + lua_pushvalue(L, 2); + lua_pushinteger(L, tmpx); + lua_pushinteger(L, tmpy); + if (nhl_pcall_handle(L, 2, 0, "l_selection_iterate", + NHLpa_impossible)) { + /* abort loops to prevent possible error cascade */ + goto out; + } + } + lua_gc(L, LUA_GCCOLLECT, 0); + } + } else { + nhl_error(L, "wrong parameters"); + /*NOTREACHED*/ + } + out: + return 0; +} + +/* local txt = sel:describe_size(); */ +/* gives a textual description of the selection size */ +staticfn int +l_selection_size_description(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 1) { + struct selectionvar *sel = l_selection_check(L, 1); + char buf[BUFSZ]; + + lua_pushstring(L, selection_size_description(sel, buf)); + return 1; + } else { + nhl_error(L, "wrong parameters"); + /*NOTREACHED*/ + } + return 0; +} + +RESTORE_WARNING_UNREACHABLE_CODE + +static const struct luaL_Reg l_selection_methods[] = { + { "new", l_selection_new }, + { "clone", l_selection_clone }, + { "get", l_selection_getpoint }, + { "set", l_selection_setpoint }, + { "numpoints", l_selection_numpoints }, + { "negate", l_selection_not }, + { "percentage", l_selection_filter_percent }, + { "rndcoord", l_selection_rndcoord }, + { "line", l_selection_line }, + { "randline", l_selection_randline }, + { "rect", l_selection_rect }, + { "fillrect", l_selection_fillrect }, + { "area", l_selection_fillrect }, + { "grow", l_selection_grow }, + { "filter_mapchar", l_selection_filter_mapchar }, + { "match", l_selection_match }, + { "floodfill", l_selection_flood }, + { "circle", l_selection_circle }, + { "ellipse", l_selection_ellipse }, + { "gradient", l_selection_gradient }, + { "iterate", l_selection_iterate }, + { "bounds", l_selection_getbounds }, + { "room", l_selection_room }, + { "describe_size", l_selection_size_description }, + { NULL, NULL } +}; + +static const luaL_Reg l_selection_meta[] = { + { "__gc", l_selection_gc }, + { "__unm", l_selection_not }, + { "__band", l_selection_and }, + { "__bor", l_selection_or }, + { "__bxor", l_selection_xor }, + { "__bnot", l_selection_not }, + { "__add", l_selection_or }, /* this aliases + to be the same as | */ + { "__sub", l_selection_sub }, + /* TODO: http://lua-users.org/wiki/MetatableEvents + { "__ipairs", l_selection_ipairs }, + */ + { NULL, NULL } +}; + +int +l_selection_register(lua_State *L) +{ + /* Table of instance methods and static methods. */ + luaL_newlib(L, l_selection_methods); + + /* metatable = { __name = "selection", __gc = l_selection_gc } */ + luaL_newmetatable(L, "selection"); + luaL_setfuncs(L, l_selection_meta, 0); + + /* metatable.__index points at the selection method table. */ + lua_pushvalue(L, -2); + lua_setfield(L, -2, "__index"); + + /* Don't let lua code mess with the real metatable. + Instead offer a fake one that only contains __gc. */ + luaL_newlib(L, l_selection_meta); + lua_setfield(L, -2, "__metatable"); + + /* We don't need the metatable anymore. It's safe in the + Lua registry for use by luaL_setmetatable. */ + lua_pop(L, 1); + + /* global selection = the method table we created at the start */ + lua_setglobal(L, "selection"); + + return 0; +} diff --git a/src/nhlua.c b/src/nhlua.c new file mode 100644 index 000000000..314e51f07 --- /dev/null +++ b/src/nhlua.c @@ -0,0 +1,3138 @@ +/* NetHack 5.0 nhlua.c $NHDT-Date: 1744963460 2025/04/18 00:04:20 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.153 $ */ +/* Copyright (c) 2018 by Pasi Kallinen */ +/* NetHack may be freely redistributed. See license for details. */ + +#include "hack.h" +#include "dlb.h" + +#ifndef LUA_VERSION_RELEASE_NUM +#ifdef NHL_SANDBOX +#undef NHL_SANDBOX +#endif +#endif + +#ifdef NHL_SANDBOX +#include +#endif + +/* +#- include +#- include +#- include +*/ + +/* */ + +struct e; + +#ifndef SFCTOOL +/* lua_CFunction prototypes */ +#ifdef DUMPLOG +staticfn int nhl_dump_fmtstr(lua_State *); +#endif /* DUMPLOG */ +staticfn int nhl_dnum_name(lua_State *); +staticfn int nhl_int_to_pm_name(lua_State *); +staticfn int nhl_int_to_obj_name(lua_State *); +staticfn int nhl_stairways(lua_State *); +staticfn int nhl_pushkey(lua_State *); +staticfn int nhl_doturn(lua_State *); +staticfn int nhl_debug_flags(lua_State *); +staticfn int nhl_flip_level(lua_State *); +staticfn int nhl_timer_has_at(lua_State *); +staticfn int nhl_timer_peek_at(lua_State *); +staticfn int nhl_timer_stop_at(lua_State *); +staticfn int nhl_timer_start_at(lua_State *); +staticfn int nhl_get_cmd_key(lua_State *); +staticfn int nhl_callback(lua_State *); +staticfn int nhl_gamestate(lua_State *); +staticfn int nhl_test(lua_State *); +staticfn int nhl_getmap(lua_State *); +staticfn char splev_typ2chr(schar); +staticfn int nhl_gettrap(lua_State *); +staticfn int nhl_deltrap(lua_State *); +#if 0 +staticfn int nhl_setmap(lua_State *); +#endif +staticfn int nhl_impossible(lua_State *); +staticfn int nhl_pline(lua_State *); +staticfn int nhl_verbalize(lua_State *); +staticfn int nhl_parse_config(lua_State *); +staticfn int nhl_menu(lua_State *); +staticfn int nhl_text(lua_State *); +staticfn int nhl_getlin(lua_State *); +staticfn int nhl_makeplural(lua_State *); +staticfn int nhl_makesingular(lua_State *); +staticfn int nhl_s_suffix(lua_State *); +staticfn int nhl_ing_suffix(lua_State *); +staticfn int nhl_an(lua_State *); +staticfn int nhl_rn2(lua_State *); +staticfn int nhl_random(lua_State *); +staticfn int nhl_level_difficulty(lua_State *); +staticfn int nhl_is_genocided(lua_State *); +staticfn int nhl_get_debug_themerm_name(lua_State *); +staticfn void init_nhc_data(lua_State *); +staticfn int nhl_push_anything(lua_State *, int, void *); +staticfn int nhl_meta_u_index(lua_State *); +staticfn int nhl_meta_u_newindex(lua_State *); +staticfn int nhl_u_clear_inventory(lua_State *); +staticfn int nhl_u_giveobj(lua_State *); +staticfn void init_u_data(lua_State *); +#ifdef notyet +staticfn int nhl_set_package_path(lua_State *, const char *); +#endif +staticfn int traceback_handler(lua_State *); +staticfn uint32_t nhl_getmeminuse(lua_State *); +#ifdef NHL_SANDBOX +staticfn void nhlL_openlibs(lua_State *, uint32_t); +#endif +staticfn void *nhl_alloc(void *, void *, size_t, size_t); +staticfn lua_State *nhlL_newstate (nhl_sandbox_info *, const char *); +staticfn void end_luapat(void); +staticfn int nhl_get_config(lua_State *); +staticfn int nhl_variable(lua_State *); +staticfn void nhl_clearfromtable(lua_State *, int, int, struct e *); +staticfn void nhl_warn(void *, const char *, int); +staticfn void nhl_clearfromtable(lua_State *, int, int, struct e *); +staticfn int nhl_panic(lua_State *); +staticfn void nhl_hookfn(lua_State *, lua_Debug *); +#endif /* !SFCTOOL */ + +#ifndef SFCTOOL +static const char *const nhcore_call_names[NUM_NHCORE_CALLS] = { + "start_new_game", + "restore_old_game", + "moveloop_turn", + "game_exit", + "getpos_tip", + "enter_tutorial", + "leave_tutorial", +}; +static boolean nhcore_call_available[NUM_NHCORE_CALLS]; +#endif + +/* internal structure that hangs off L->ud (but use lua_getallocf() ) + * Note that we use it for both memory use tracking and instruction counting. + */ +typedef struct nhl_user_data { + lua_State *L; /* because the allocator needs it */ + uint32_t flags; /* from nhl_sandbox_info */ + + uint32_t memlimit; + + uint32_t steps; /* current counter */ + uint32_t osteps; /* original steps value */ + uint32_t perpcall; /* per pcall steps value */ + + /* stats */ + uint32_t statctr; /* stats step reload count */ + int sid; /* id number (per state) */ + const char *name; /* for stats logging (per pcall) */ + +#ifdef NHL_SANDBOX + jmp_buf jb; +#endif +} nhl_user_data; + +#ifndef SFCTOOL +static lua_State *luapat; /* instance for file pattern matching */ + +void +l_nhcore_init(void) +{ + nhl_sandbox_info sbi = { NHL_SB_SAFE, 1 * 1024 * 1024, 0, + 1 * 1024 * 1024 }; + + if ((gl.luacore = nhl_init(&sbi)) != 0) { + if (!nhl_loadlua(gl.luacore, "nhcore.lua")) { + gl.luacore = (lua_State *) 0; + } else { + int i; + + for (i = 0; i < NUM_NHCORE_CALLS; i++) + nhcore_call_available[i] = TRUE; + } + } else + impossible("l_nhcore_init failed"); +} + +void +l_nhcore_done(void) +{ + if (gl.luacore) { + nhl_done(gl.luacore); + gl.luacore = 0; + } + end_luapat(); +} + +void +l_nhcore_call(int callidx) +{ + int ltyp; + + if (callidx < 0 || callidx >= NUM_NHCORE_CALLS || !gl.luacore + || !nhcore_call_available[callidx]) + return; + + lua_getglobal(gl.luacore, "nhcore"); + if (!lua_istable(gl.luacore, -1)) { + /*impossible("nhcore is not a lua table");*/ + nhl_done(gl.luacore); + gl.luacore = 0; + return; + } + + lua_getfield(gl.luacore, -1, nhcore_call_names[callidx]); + ltyp = lua_type(gl.luacore, -1); + if (ltyp == LUA_TFUNCTION) { + nhl_pcall_handle(gl.luacore, 0, 1, "l_nhcore_call", NHLpa_panic); + } else { + /*impossible("nhcore.%s is not a lua function", + nhcore_call_names[callidx]);*/ + nhcore_call_available[callidx] = FALSE; + } +} + +DISABLE_WARNING_UNREACHABLE_CODE + +ATTRNORETURN void +nhl_error(lua_State *L, const char *msg) +{ + lua_Debug ar; + char buf[BUFSZ * 2]; + + lua_getstack(L, 1, &ar); + lua_getinfo(L, "lS", &ar); + Sprintf(buf, "%s (line %d ", msg, ar.currentline); + Sprintf(eos(buf), "%.*s)", + /* (max length of ar.short_src is actually LUA_IDSIZE + so this is overkill for it, but crucial for ar.source) */ + (int) (sizeof buf - (strlen(buf) + sizeof ")")), + ar.short_src); /* (used to be 'ar.source' here) */ + lua_pushstring(L, buf); +#if 0 /* defined(PANICTRACE) && !defined(NO_SIGNALS) */ + panictrace_setsignals(FALSE); +#endif + (void) lua_error(L); + /*NOTREACHED*/ +} + +RESTORE_WARNING_UNREACHABLE_CODE + +/* Check that parameters are nothing but single table, + or if no parameters given, put empty table there */ +void +lcheck_param_table(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc < 1) + lua_createtable(L, 0, 0); + + /* discard any extra arguments passed in */ + lua_settop(L, 1); + + luaL_checktype(L, 1, LUA_TTABLE); +} + +DISABLE_WARNING_UNREACHABLE_CODE + +schar +get_table_mapchr(lua_State *L, const char *name) +{ + char *ter; + xint8 typ; + + ter = get_table_str(L, name); + typ = check_mapchr(ter); + if (typ == INVALID_TYPE) + nhl_error(L, "Erroneous map char"); + if (ter) + free(ter); + return typ; +} + +schar +get_table_mapchr_opt(lua_State *L, const char *name, schar defval) +{ + char *ter; + xint8 typ; + + ter = get_table_str_opt(L, name, emptystr); + if (ter && *ter) { + typ = (xint8) check_mapchr(ter); + if (typ == INVALID_TYPE) + nhl_error(L, "Erroneous map char"); + } else + typ = (xint8) defval; + if (ter) + free(ter); + return typ; +} + +short +nhl_get_timertype(lua_State *L, int idx) +{ + /* these are in the same order as enum timeout_types in timeout.h and + ttable timeout_funcs[] in timeout.c, although not spelled the same */ + static const char *const timerstr[NUM_TIME_FUNCS + 1] = { + "rot-organic", "rot-corpse", "revive-mon", "zombify-mon", + "burn-obj", "hatch-egg", "fig-transform", "shrink-glob", + "melt-ice", NULL + }; + short ret = luaL_checkoption(L, idx, NULL, timerstr); + + if (ret < 0 || ret >= NUM_TIME_FUNCS) + nhl_error(L, "Unknown timer type"); + return ret; +} + +RESTORE_WARNING_UNREACHABLE_CODE + +void +nhl_add_table_entry_int(lua_State *L, const char *name, lua_Integer value) +{ + lua_pushstring(L, name); + lua_pushinteger(L, value); + lua_rawset(L, -3); +} + +void +nhl_add_table_entry_char(lua_State *L, const char *name, char value) +{ + char buf[2]; + Sprintf(buf, "%c", value); + lua_pushstring(L, name); + lua_pushstring(L, buf); + lua_rawset(L, -3); +} + +void +nhl_add_table_entry_str(lua_State *L, const char *name, const char *value) +{ + lua_pushstring(L, name); + lua_pushstring(L, value); + lua_rawset(L, -3); +} +void +nhl_add_table_entry_bool(lua_State *L, const char *name, boolean value) +{ + lua_pushstring(L, name); + lua_pushboolean(L, value); + lua_rawset(L, -3); +} + +void +nhl_add_table_entry_region(lua_State *L, const char *name, coordxy x1, + coordxy y1, coordxy x2, coordxy y2) +{ + lua_pushstring(L, name); + lua_newtable(L); + nhl_add_table_entry_int(L, "x1", x1); + nhl_add_table_entry_int(L, "y1", y1); + nhl_add_table_entry_int(L, "x2", x2); + nhl_add_table_entry_int(L, "y2", y2); + lua_rawset(L, -3); +} + +/* converting from special level "map character" to levl location type + and back. order here is important. */ +static const struct { + char ch; + schar typ; +} char2typ[] = { + { ' ', STONE }, + { '#', CORR }, + { '.', ROOM }, + { '-', HWALL }, + { '-', TLCORNER }, + { '-', TRCORNER }, + { '-', BLCORNER }, + { '-', BRCORNER }, + { '-', CROSSWALL }, + { '-', TUWALL }, + { '-', TDWALL }, + { '-', TLWALL }, + { '-', TRWALL }, + { '-', DBWALL }, + { '|', VWALL }, + { '+', DOOR }, + { 'A', AIR }, + { 'C', CLOUD }, + { 'S', SDOOR }, + { 'H', SCORR }, + { '{', FOUNTAIN }, + { '\\', THRONE }, + { 'K', SINK }, + { '}', MOAT }, + { 'P', POOL }, + { 'L', LAVAPOOL }, + { 'Z', LAVAWALL }, + { 'I', ICE }, + { 'W', WATER }, + { 'T', TREE }, + { 'F', IRONBARS }, /* Fe = iron */ + { 'x', MAX_TYPE }, /* "see-through" */ + { 'B', CROSSWALL }, /* hack: boundary location */ + { 'w', MATCH_WALL }, /* IS_STWALL() */ + { '\0', STONE }, +}; + +schar +splev_chr2typ(char c) +{ + int i; + + for (i = 0; char2typ[i].ch; i++) + if (c == char2typ[i].ch) + return char2typ[i].typ; + return (INVALID_TYPE); +} + +schar +check_mapchr(const char *s) +{ + if (s && strlen(s) == 1) + return splev_chr2typ(s[0]); + return INVALID_TYPE; +} + +staticfn char +splev_typ2chr(schar typ) +{ + int i; + + for (i = 0; char2typ[i].typ < MAX_TYPE; i++) + if (typ == char2typ[i].typ) + return char2typ[i].ch; + return 'x'; +} + +DISABLE_WARNING_UNREACHABLE_CODE + +/* local t = nh.gettrap(x,y); */ +/* local t = nh.gettrap({ x = 10, y = 10 }); */ +staticfn int +nhl_gettrap(lua_State *L) +{ + lua_Integer lx, ly; + coordxy x, y; + + if (!nhl_get_xy_params(L, &lx, &ly)) { + nhl_error(L, "Incorrect arguments"); + /*NOTREACHED*/ + return 0; + } + x = (coordxy) lx; + y = (coordxy) ly; + cvt_to_abscoord(&x, &y); + + if (isok(x, y)) { + struct trap *ttmp = t_at(x, y); + + if (ttmp) { + lua_newtable(L); + + nhl_add_table_entry_int(L, "tx", ttmp->tx); + nhl_add_table_entry_int(L, "ty", ttmp->ty); + nhl_add_table_entry_int(L, "ttyp", ttmp->ttyp); + nhl_add_table_entry_str(L, "ttyp_name", + get_trapname_bytype(ttmp->ttyp)); + nhl_add_table_entry_bool(L, "tseen", ttmp->tseen); + nhl_add_table_entry_bool(L, "madeby_u", ttmp->madeby_u); + nhl_add_table_entry_bool(L, "once", ttmp->once); + switch (ttmp->ttyp) { + case SQKY_BOARD: + nhl_add_table_entry_int(L, "tnote", ttmp->tnote); + break; + case ROLLING_BOULDER_TRAP: + nhl_add_table_entry_int(L, "launchx", ttmp->launch.x); + nhl_add_table_entry_int(L, "launchy", ttmp->launch.y); + nhl_add_table_entry_int(L, "launch2x", ttmp->launch2.x); + nhl_add_table_entry_int(L, "launch2y", ttmp->launch2.y); + break; + case PIT: + case SPIKED_PIT: + nhl_add_table_entry_int(L, "conjoined", ttmp->conjoined); + break; + } + return 1; + } else { + nhl_error(L, "No trap at location"); + } + } else { + nhl_error(L, "Coordinates out of range"); + } + return 0; +} + +/* nh.deltrap(x,y); nh.deltrap({ x = 10, y = 15 }); */ +staticfn int +nhl_deltrap(lua_State *L) +{ + lua_Integer lx, ly; + coordxy x, y; + + if (!nhl_get_xy_params(L, &lx, &ly)) { + nhl_error(L, "Incorrect arguments"); + /*NOTREACHED*/ + return 0; + } + x = (coordxy) lx; + y = (coordxy) ly; + cvt_to_abscoord(&x, &y); + + if (isok(x, y)) { + struct trap *ttmp = t_at(x, y); + + if (ttmp) + deltrap(ttmp); + } + return 0; +} + +RESTORE_WARNING_UNREACHABLE_CODE + +/* get parameters (XX,YY) or ({ x = XX, y = YY }) or ({ XX, YY }), + and set the x and y values. + return TRUE if there are such params in the stack. + Note that this does not adjust the values of x and y at all from what is + specified in the level file; so, it returns absolute coordinates rather + than map-relative coordinates. Callers of this function must decide if + they want to interpret the values as absolute or as map-relative, and + adjust accordingly. + */ +boolean +nhl_get_xy_params(lua_State *L, lua_Integer *x, lua_Integer *y) +{ + int argc = lua_gettop(L); + boolean ret = FALSE; + + if (argc == 2) { + *x = lua_tointeger(L, 1); + *y = lua_tointeger(L, 2); + ret = TRUE; + } else if (argc == 1 && lua_type(L, 1) == LUA_TTABLE) { + lua_Integer ax, ay; + + ret = get_coord(L, 1, &ax, &ay); + *x = ax; + *y = ay; + } + return ret; +} + +DISABLE_WARNING_UNREACHABLE_CODE + +/* local loc = nh.getmap(x,y); */ +/* local loc = nh.getmap({ x = 10, y = 35 }); */ +staticfn int +nhl_getmap(lua_State *L) +{ + lua_Integer lx, ly; + coordxy x, y; + + if (!nhl_get_xy_params(L, &lx, &ly)) { + nhl_error(L, "Incorrect arguments"); + return 0; + } + x = (coordxy) lx; + y = (coordxy) ly; + cvt_to_abscoord(&x, &y); + + if (isok(x, y)) { + char buf[BUFSZ]; + lua_newtable(L); + + /* FIXME: some should be boolean values */ + nhl_add_table_entry_int(L, "glyph", levl[x][y].glyph); + nhl_add_table_entry_int(L, "typ", levl[x][y].typ); + nhl_add_table_entry_str(L, "typ_name", + levltyp_to_name(levl[x][y].typ)); + Sprintf(buf, "%c", splev_typ2chr(levl[x][y].typ)); + nhl_add_table_entry_str(L, "mapchr", buf); + nhl_add_table_entry_int(L, "seenv", levl[x][y].seenv); + nhl_add_table_entry_bool(L, "horizontal", levl[x][y].horizontal); + nhl_add_table_entry_bool(L, "lit", levl[x][y].lit); + nhl_add_table_entry_bool(L, "waslit", levl[x][y].waslit); + nhl_add_table_entry_int(L, "roomno", levl[x][y].roomno); + nhl_add_table_entry_bool(L, "edge", levl[x][y].edge); + nhl_add_table_entry_bool(L, "candig", levl[x][y].candig); + + nhl_add_table_entry_bool(L, "has_trap", t_at(x, y) ? 1 : 0); + + /* TODO: FIXME: levl[x][y].flags */ + + lua_pushliteral(L, "flags"); + lua_newtable(L); + + if (IS_DOOR(levl[x][y].typ)) { + nhl_add_table_entry_bool(L, "nodoor", + (levl[x][y].flags == D_NODOOR)); + nhl_add_table_entry_bool(L, "broken", + (levl[x][y].flags & D_BROKEN)); + nhl_add_table_entry_bool(L, "isopen", + (levl[x][y].flags & D_ISOPEN)); + nhl_add_table_entry_bool(L, "closed", + (levl[x][y].flags & D_CLOSED)); + nhl_add_table_entry_bool(L, "locked", + (levl[x][y].flags & D_LOCKED)); + nhl_add_table_entry_bool(L, "trapped", + (levl[x][y].flags & D_TRAPPED)); + } else if (IS_ALTAR(levl[x][y].typ)) { + /* TODO: bits 0, 1, 2 */ + nhl_add_table_entry_bool(L, "shrine", + (levl[x][y].flags & AM_SHRINE)); + } else if (IS_THRONE(levl[x][y].typ)) { + nhl_add_table_entry_bool(L, "looted", + (levl[x][y].flags & T_LOOTED)); + } else if (levl[x][y].typ == TREE) { + nhl_add_table_entry_bool(L, "looted", + (levl[x][y].flags & TREE_LOOTED)); + nhl_add_table_entry_bool(L, "swarm", + (levl[x][y].flags & TREE_SWARM)); + } else if (IS_FOUNTAIN(levl[x][y].typ)) { + nhl_add_table_entry_bool(L, "looted", + (levl[x][y].flags & F_LOOTED)); + nhl_add_table_entry_bool(L, "warned", + (levl[x][y].flags & F_WARNED)); + } else if (IS_SINK(levl[x][y].typ)) { + nhl_add_table_entry_bool(L, "pudding", + (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 */ + + lua_settable(L, -3); + + return 1; + } else { + /* TODO: return zerorm instead? */ + nhl_error(L, "Coordinates out of range"); + return 0; + } +} + +/* impossible("Error!") */ +staticfn int +nhl_impossible(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 1) + impossible("%s", luaL_checkstring(L, 1)); + else + nhl_error(L, "Wrong args"); + return 0; +} + +/* pline("It hits!") */ +/* pline("It hits!", true) */ +staticfn int +nhl_pline(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 1 || argc == 2) { + pline("%s", luaL_checkstring(L, 1)); + if (lua_toboolean(L, 2)) + display_nhwindow(WIN_MESSAGE, TRUE); /* --more-- */ + } else + nhl_error(L, "Wrong args"); + + return 0; +} + +/* verbalize("Fool!") */ +staticfn int +nhl_verbalize(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 1) + verbalize("%s", luaL_checkstring(L, 1)); + else + nhl_error(L, "Wrong args"); + + return 0; +} + +/* parse_config("OPTIONS=!color") */ +staticfn int +nhl_parse_config(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 1) + parse_conf_str(luaL_checkstring(L, 1), parse_config_line); + else + nhl_error(L, "Wrong args"); + + return 0; +} + +/* local windowtype = get_config("windowtype"); */ +staticfn int +nhl_get_config(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 1) { + lua_pushstring(L, get_option_value(luaL_checkstring(L, 1), TRUE)); + return 1; + } else + nhl_error(L, "Wrong args"); + + return 0; +} + +/* + str = getlin("What do you want to call this dungeon level?"); + */ +staticfn int +nhl_getlin(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 1) { + const char *prompt = luaL_checkstring(L, 1); + char buf[BUFSZ]; + + getlin(prompt, buf); + lua_pushstring(L, buf); + return 1; + } + + nhl_error(L, "Wrong args"); + /*NOTREACHED*/ + return 0; +} + +/* + selected = menu("prompt",default,pickX,{"a"="option a", "b"="option b",...}) + pickX = 0,1,2, or "none", "one", "any" (PICK_X in code) + + selected = menu("prompt", default, pickX, + {{key:"a", text:"option a"},{key:"b", text:"option b"},... }) + */ +staticfn int +nhl_menu(lua_State *L) +{ + static const char *const pickX[] = { "none", "one", "any" }; /* PICK_x */ + int argc = lua_gettop(L); + const char *prompt; + const char *defval = ""; + int pick = PICK_ONE, pick_cnt; + winid tmpwin; + anything any; + menu_item *picks = (menu_item *) 0; + int clr = NO_COLOR; + + if (argc < 2 || argc > 4) { + nhl_error(L, "Wrong args"); + /*NOTREACHED*/ + return 0; + } + + prompt = luaL_checkstring(L, 1); + if (lua_isstring(L, 2)) + defval = luaL_checkstring(L, 2); + if (lua_isstring(L, 3)) + pick = luaL_checkoption(L, 3, "one", pickX); + luaL_checktype(L, argc, LUA_TTABLE); + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + + lua_pushnil(L); /* first key */ + while (lua_next(L, argc) != 0) { + const char *str = ""; + const char *key = ""; + + /* key @ index -2, value @ index -1 */ + if (lua_istable(L, -1)) { + lua_pushliteral(L, "key"); + lua_gettable(L, -2); + key = lua_tostring(L, -1); + lua_pop(L, 1); + + lua_pushliteral(L, "text"); + lua_gettable(L, -2); + str = lua_tostring(L, -1); + lua_pop(L, 1); + + /* TODO: glyph, attr, accel, group accel (all optional) */ + } else if (lua_isstring(L, -1)) { + str = luaL_checkstring(L, -1); + key = luaL_checkstring(L, -2); + } + + any = cg.zeroany; + if (*key) + any.a_char = key[0]; + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr, str, + (*defval && *key && defval[0] == key[0]) + ? MENU_ITEMFLAGS_SELECTED + : MENU_ITEMFLAGS_NONE); + + lua_pop(L, 1); /* removes 'value'; keeps 'key' for next iteration */ + } + + end_menu(tmpwin, prompt); + pick_cnt = select_menu(tmpwin, pick, &picks); + destroy_nhwindow(tmpwin); + + if (pick_cnt > 0) { + char buf[2]; + buf[0] = picks[0].item.a_char; + + if (pick == PICK_ONE && pick_cnt > 1 && *defval + && defval[0] == picks[0].item.a_char) + buf[0] = picks[1].item.a_char; + + buf[1] = '\0'; + lua_pushstring(L, buf); + /* TODO: pick any */ + } else { + char buf[2]; + buf[0] = defval[0]; + buf[1] = '\0'; + lua_pushstring(L, buf); + } + + return 1; +} + +/* text("foo\nbar\nbaz") */ +staticfn int +nhl_text(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc > 0) { + menu_item *picks = (menu_item *) 0; + winid tmpwin; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + + while (lua_gettop(L) > 0) { + char *ostr = dupstr(luaL_checkstring(L, 1)); + char *ptr, *str = ostr; + char *lstr = str + strlen(str) - 1; + + do { + char *nlp = strchr(str, '\n'); + + if (nlp && (nlp - str) <= 76) { + ptr = nlp; + } else { + ptr = str + 76; + if (ptr > lstr) + ptr = lstr; + } + while ((ptr > str) && !(*ptr == ' ' || *ptr == '\n')) + ptr--; + *ptr = '\0'; + add_menu_str(tmpwin, str); + str = ptr + 1; + } while (*str && str <= lstr); + lua_pop(L, 1); + free(ostr); + } + + end_menu(tmpwin, (char *) 0); + (void) select_menu(tmpwin, PICK_NONE, &picks); + destroy_nhwindow(tmpwin); + } + return 0; +} + +/* makeplural("zorkmid") */ +staticfn int +nhl_makeplural(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 1) + lua_pushstring(L, makeplural(luaL_checkstring(L, 1))); + else + nhl_error(L, "Wrong args"); + + return 1; +} + +/* makesingular("zorkmids") */ +staticfn int +nhl_makesingular(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 1) + lua_pushstring(L, makesingular(luaL_checkstring(L, 1))); + else + nhl_error(L, "Wrong args"); + + return 1; +} + +/* s_suffix("foo") */ +staticfn int +nhl_s_suffix(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 1) + lua_pushstring(L, s_suffix(luaL_checkstring(L, 1))); + else + nhl_error(L, "Wrong args"); + + return 1; +} + +/* ing_suffix("foo") */ +staticfn int +nhl_ing_suffix(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 1) + lua_pushstring(L, ing_suffix(luaL_checkstring(L, 1))); + else + nhl_error(L, "Wrong args"); + + return 1; +} + +/* an("foo") */ +staticfn int +nhl_an(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 1) + lua_pushstring(L, an(luaL_checkstring(L, 1))); + else + nhl_error(L, "Wrong args"); + + return 1; +} + +/* rn2(10) */ +staticfn int +nhl_rn2(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 1) + lua_pushinteger(L, rn2((int) luaL_checkinteger(L, 1))); + else + nhl_error(L, "Wrong args"); + + return 1; +} + +/* random(10); -- is the same as rn2(10); */ +/* random(5,8); -- same as 5 + rn2(8); */ +staticfn int +nhl_random(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 1) + lua_pushinteger(L, rn2((int) luaL_checkinteger(L, 1))); + else if (argc == 2) + lua_pushinteger(L, luaL_checkinteger(L, 1) + + rn2((int) luaL_checkinteger(L, 2))); + else + nhl_error(L, "Wrong args"); + + return 1; +} + +/* level_difficulty() */ +staticfn int +nhl_level_difficulty(lua_State *L) +{ + int argc = lua_gettop(L); + if (argc == 0) { + lua_pushinteger(L, level_difficulty()); + } else { + nhl_error(L, "level_difficulty should not have any args"); + } + return 1; +} + +/* local x = nh.is_genocided("vampire") */ +staticfn int +nhl_is_genocided(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 1) { + const char *paramstr = luaL_checkstring(L, 1); + int mgend; + int i = name_to_mon(paramstr, &mgend); + + lua_pushboolean(L, (i != NON_PM) + && (svm.mvitals[i].mvflags & G_GENOD) + ? TRUE : FALSE); + } else { + nhl_error(L, "Wrong args"); + } + return 1; +} + +/* local debug_themerm = nh.debug_themerm(isfill) + * if isfill is false, returns value of env variable THEMERM + * if isfill is true, returns value of env variable THEMERMFILL + * return nil if not in wizard mode or the variable isn't set */ +staticfn int +nhl_get_debug_themerm_name(lua_State *L) +{ + int argc = lua_gettop(L); + if (argc == 1) { + char *dbg_themerm = (char *) 0; + boolean is_fill = lua_toboolean(L, 1); + lua_pop(L, 1); + if (wizard) + dbg_themerm = getenv(is_fill ? "THEMERMFILL" : "THEMERM"); + if (!dbg_themerm || !*dbg_themerm) { + lua_pushnil(L); + } else { + lua_pushstring(L, dbg_themerm); + } + } else { + nhl_error(L, "debug_themerm should have 1 boolean arg"); + } + return 1; +} + +RESTORE_WARNING_UNREACHABLE_CODE + +/* get mandatory integer value from table */ +int +get_table_int(lua_State *L, const char *name) +{ + int ret; + + lua_getfield(L, -1, name); + ret = (int) luaL_checkinteger(L, -1); + lua_pop(L, 1); + return ret; +} + +/* get optional integer value from table */ +int +get_table_int_opt(lua_State *L, const char *name, int defval) +{ + int ret = defval; + + lua_getfield(L, -1, name); + if (!lua_isnil(L, -1)) { + ret = (int) luaL_checkinteger(L, -1); + } + lua_pop(L, 1); + return ret; +} + +char * +get_table_str(lua_State *L, const char *name) +{ + char *ret; + + lua_getfield(L, -1, name); + ret = dupstr(luaL_checkstring(L, -1)); + lua_pop(L, 1); + return ret; +} + +/* get optional string value from table. + return value must be freed by caller. */ +char * +get_table_str_opt(lua_State *L, const char *name, char *defval) +{ + const char *ret; + int ltyp; + + lua_getfield(L, -1, name); + ltyp = lua_type(L, -1); + if (ltyp == LUA_TSTRING || ltyp == LUA_TNIL) { + ret = luaL_optstring(L, -1, defval); + } else if (ltyp == LUA_TFUNCTION) { + nhl_pcall_handle(L, 0, 1, "get_table_str_opt", NHLpa_panic); + ret = luaL_optstring(L, -1, defval); + } else { + nhl_error(L, "get_table_str_opt: no string"); + } + if (ret) { + lua_pop(L, 1); + return dupstr(ret); + } + lua_pop(L, 1); + return NULL; +} + +int +get_table_boolean(lua_State *L, const char *name) +{ + static const char *const boolstr[] = { + "true", "false", "yes", "no", NULL + }; + /* static const int boolstr2i[] = { TRUE, FALSE, TRUE, FALSE, -1 }; */ + int ltyp; + int ret = -1; + + lua_getfield(L, -1, name); + ltyp = lua_type(L, -1); + if (ltyp == LUA_TSTRING) { + ret = luaL_checkoption(L, -1, NULL, boolstr); + /* nhUse(boolstr2i[0]); */ + } else if (ltyp == LUA_TBOOLEAN) { + ret = lua_toboolean(L, -1); + } else if (ltyp == LUA_TNUMBER) { + ret = (int) luaL_checkinteger(L, -1); + if (ret < 0 || ret > 1) + ret = -1; + } + lua_pop(L, 1); + if (ret == -1) + nhl_error(L, "Expected a boolean"); + return ret; +} + +int +get_table_boolean_opt(lua_State *L, const char *name, int defval) +{ + int ret = defval; + + lua_getfield(L, -1, name); + if (lua_type(L, -1) != LUA_TNIL) { + lua_pop(L, 1); + return get_table_boolean(L, name); + } + lua_pop(L, 1); + return ret; +} + +/* opts[] is a null-terminated list */ +int +get_table_option(lua_State *L, + const char *name, + const char *defval, + const char *const opts[]) +{ + int ret; + + lua_getfield(L, -1, name); + ret = luaL_checkoption(L, -1, defval, opts); + lua_pop(L, 1); + return ret; +} + +#ifdef DUMPLOG +/* local fname = dump_fmtstr("/tmp/nethack.%n.%d.log"); */ +staticfn int +nhl_dump_fmtstr(lua_State *L) +{ + int argc = lua_gettop(L); + char buf[512]; + + if (argc == 1) + lua_pushstring(L, dump_fmtstr(luaL_checkstring(L, 1), buf, TRUE)); + else + nhl_error(L, "Expected a string parameter"); + return 1; +} +#endif /* DUMPLOG */ + +/* local dungeon_name = dnum_name(u.dnum); */ +staticfn int +nhl_dnum_name(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 1) { + lua_Integer dnum = luaL_checkinteger(L, 1); + + if (dnum >= 0 && dnum < svn.n_dgns) + lua_pushstring(L, svd.dungeons[dnum].dname); + else + lua_pushstring(L, ""); + } else + nhl_error(L, "Expected an integer parameter"); + return 1; +} + +/* return gender-neutral monster type name by integer value, + or empty string if outside LOW_PM - HIGH_PM range */ +/* local montypename = int_to_pmname(12); */ +staticfn int +nhl_int_to_pm_name(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 1) { + lua_Integer i = luaL_checkinteger(L, 1); + + if (i >= LOW_PM && i <= HIGH_PM) + lua_pushstring(L, mons[i].pmnames[NEUTRAL]); + else + lua_pushstring(L, ""); + } else + nhl_error(L, "Expected an integer parameter"); + return 1; +} + +/* convert integer to object type name and class */ +/* local oname,oclass = int_to_objname(25); */ +staticfn int +nhl_int_to_obj_name(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 1) { + char buf[8]; + lua_Integer i = luaL_checkinteger(L, 1); + + if (i >= 0 && i < NUM_OBJECTS && OBJ_NAME(objects[i])) { + lua_pushstring(L, OBJ_NAME(objects[i])); + buf[0] = def_oc_syms[(int)objects[i].oc_class].sym; + buf[1] = '\0'; + lua_pushstring(L, buf); + } else { + lua_pushstring(L, ""); + lua_pushstring(L, ""); + } + } else + nhl_error(L, "Expected an integer parameter"); + return 2; +} + +DISABLE_WARNING_UNREACHABLE_CODE +/* because nhl_error() does not return */ + +/* set or get variables which are saved and restored along with the game. + nh.variable("test", 10); + local ten = nh.variable("test"); */ +staticfn int +nhl_variable(lua_State *L) +{ + int argc = lua_gettop(L); + int typ; + const char *key; + + if (!gl.luacore) { + panic("nh luacore not inited"); + /*NOTREACHED*/ + return 0; + } + + lua_getglobal(gl.luacore, "nh_lua_variables"); + if (!lua_istable(gl.luacore, -1)) { + impossible("nh_lua_variables is not a lua table"); + return 0; + } + + if (argc == 1) { + key = luaL_checkstring(L, 1); + + lua_getfield(gl.luacore, -1, key); + typ = lua_type(gl.luacore, -1); + if (typ == LUA_TSTRING) + lua_pushstring(L, lua_tostring(gl.luacore, -1)); + else if (typ == LUA_TNIL) + lua_pushnil(L); + else if (typ == LUA_TBOOLEAN) + lua_pushboolean(L, lua_toboolean(gl.luacore, -1)); + else if (typ == LUA_TNUMBER) + lua_pushinteger(L, lua_tointeger(gl.luacore, -1)); + else if (typ == LUA_TTABLE) { + lua_getglobal(gl.luacore, "nh_get_variables_string"); + lua_pushvalue(gl.luacore, -2); + nhl_pcall_handle(gl.luacore, 1, 1, "nhl_variable", NHLpa_panic); + luaL_loadstring(L, lua_tostring(gl.luacore, -1)); + nhl_pcall_handle(L, 0, 1, "nhl_variable-1", NHLpa_panic); + } else + nhl_error(L, "Cannot get variable of that type"); + return 1; + } else if (argc == 2) { + /* set nh_lua_variables[key] = value; + nh.variable("key", value); */ + key = luaL_checkstring(L, 1); + //pline("SETVAR:%s", key); + typ = lua_type(L, -1); + + if (typ == LUA_TSTRING) { + lua_pushstring(gl.luacore, lua_tostring(L, -1)); + lua_setfield(gl.luacore, -2, key); + } else if (typ == LUA_TNIL) { + lua_pushnil(gl.luacore); + lua_setfield(gl.luacore, -2, key); + } else if (typ == LUA_TBOOLEAN) { + lua_pushboolean(gl.luacore, lua_toboolean(L, -1)); + lua_setfield(gl.luacore, -2, key); + } else if (typ == LUA_TNUMBER) { + lua_pushinteger(gl.luacore, lua_tointeger(L, -1)); + lua_setfield(gl.luacore, -2, key); + } else if (typ == LUA_TTABLE) { + lua_getglobal(L, "nh_set_variables_string"); + lua_pushstring(L, key); + lua_pushvalue(L, -3); /* copy value to top */ + nhl_pcall_handle(L, 2, 1, "nhl_variable-2", NHLpa_panic); + luaL_loadstring(gl.luacore, lua_tostring(L, -1)); + nhl_pcall_handle(gl.luacore, 0, 0, "nhl_variable-3", NHLpa_panic); + } else + nhl_error(L, "Cannot set variable of that type"); + return 0; + } else + nhl_error(L, "Wrong number of arguments"); + return 1; +} + +/* return nh_lua_variable lua table as a string */ +char * +get_nh_lua_variables(void) +{ + char *key = NULL; + + if (!gl.luacore) { + panic("nh luacore not inited"); + /*NOTREACHED*/ + return key; + } + lua_getglobal(gl.luacore, "get_variables_string"); + if (lua_type(gl.luacore, -1) == LUA_TFUNCTION) { + if (nhl_pcall_handle(gl.luacore, 0, 1, "get_nh_lua_variables", + NHLpa_impossible)) { + return key; + } + key = dupstr(lua_tostring(gl.luacore, -1)); + } + lua_pop(gl.luacore, 1); + return key; +} + +RESTORE_WARNING_UNREACHABLE_CODE + +#endif /* !SFCTOOL */ + +#ifdef SFCTOOL +char *lua_data; +#endif + +/* save nh_lua_variables table to file */ +void +save_luadata(NHFILE *nhfp) +{ + unsigned lua_data_len; +#ifndef SFCTOOL + char *lua_data = get_nh_lua_variables(); /* note: '\0' terminated */ +#endif + + if (!lua_data) + lua_data = dupstr(emptystr); + lua_data_len = Strlen(lua_data) + 1; /* +1: include the terminator */ + Sfo_unsigned(nhfp, &lua_data_len, "luadata-lua_data_len"); + Sfo_char(nhfp, lua_data, "luadata", lua_data_len); + free(lua_data); +} + +/* restore nh_lua_variables table from file */ +void +restore_luadata(NHFILE *nhfp) +{ + unsigned lua_data_len = 0; +#ifndef SFCTOOL + char *lua_data; +#endif /* !SFCTOOL */ + + Sfi_unsigned(nhfp, &lua_data_len, "luadata-lua_data_len"); + lua_data = (char *) alloc(lua_data_len); + Sfi_char(nhfp, lua_data, "luadata", lua_data_len); + +#ifndef SFCTOOL + if (!gl.luacore) + l_nhcore_init(); + luaL_loadstring(gl.luacore, lua_data); + free(lua_data); + nhl_pcall_handle(gl.luacore, 0, 0, "restore_luadata", NHLpa_panic); +#endif /* !SFCTOOL */ +} + +#ifndef SFCTOOL + +/* local stairs = stairways(); */ +staticfn int +nhl_stairways(lua_State *L) +{ + stairway *tmp = gs.stairs; + int i = 1; /* lua arrays should start at 1 */ + + lua_newtable(L); + + while (tmp) { + lua_pushinteger(L, i); + lua_newtable(L); + + nhl_add_table_entry_bool(L, "up", tmp->up); + nhl_add_table_entry_bool(L, "ladder", tmp->isladder); + nhl_add_table_entry_int(L, "x", tmp->sx); + nhl_add_table_entry_int(L, "y", tmp->sy); + nhl_add_table_entry_int(L, "dnum", tmp->tolev.dnum); + nhl_add_table_entry_int(L, "dlevel", tmp->tolev.dlevel); + + lua_settable(L, -3); + + tmp = tmp->next; + i++; + } + + return 1; +} + +/* + test( { x = 123, y = 456 } ); +*/ +staticfn int +nhl_test(lua_State *L) +{ + coordxy x, y; + char *name, Player[] = "Player"; + + /* discard any extra arguments passed in */ + lua_settop(L, 1); + + luaL_checktype(L, 1, LUA_TTABLE); + + x = (coordxy) get_table_int(L, "x"); + y = (coordxy) get_table_int(L, "y"); + name = get_table_str_opt(L, "name", Player); + + pline("TEST:{ x=%i, y=%i, name=\"%s\" }", (int) x, (int) y, name); + + free(name); + + return 1; +} + +/* push a key into command queue */ +/* nh.pushkey("i"); */ +staticfn int +nhl_pushkey(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 1) { + const char *key = luaL_checkstring(L, 1); + + while (*key) { + cmdq_add_key(CQ_CANNED, *key); + key++; + } + } + + return 0; +} + +/* do a turn of moveloop, or until gm.multi is done if param is true. */ +/* nh.doturn(); nh.doturn(true); */ +staticfn int +nhl_doturn(lua_State *L) +{ + int argc = lua_gettop(L); + boolean domulti = FALSE; + + if (argc == 1) + domulti = lua_toboolean(L, 1); + + do { + moveloop_core(); + } while (domulti && gm.multi); + + return 0; +} + +/* set debugging flags. debugging use only, of course. */ +/* nh.debug_flags({ mongen = false, + hunger = false, + overwrite_stairs = true }); */ +staticfn int +nhl_debug_flags(lua_State *L) +{ + int val; + + lcheck_param_table(L); + + /* disable monster generation */ + val = get_table_boolean_opt(L, "mongen", -1); + if (val != -1) { + iflags.debug_mongen = !(boolean) val; /* value in lua is negated */ + if (iflags.debug_mongen) { + struct monst *mtmp, *mtmp2; + + for (mtmp = fmon; mtmp; mtmp = mtmp2) { + mtmp2 = mtmp->nmon; + if (DEADMONSTER(mtmp)) + continue; + mongone(mtmp); + } + } + } + + /* prevent hunger */ + val = get_table_boolean_opt(L, "hunger", -1); + if (val != -1) { + iflags.debug_hunger = !(boolean) val; /* value in lua is negated */ + } + + /* allow overwriting stairs */ + val = get_table_boolean_opt(L, "overwrite_stairs", -1); + if (val != -1) { + iflags.debug_overwrite_stairs = (boolean) val; + } + + /* prevent pline going out to the UI */ + val = get_table_boolean_opt(L, "prevent_pline", -1); + if (val != -1) { + iflags.debug_prevent_pline = (boolean) val; + } + + return 0; +} + +/* flip level */ +/* nh.flip_level(n); */ +staticfn int +nhl_flip_level(lua_State *L) +{ + int argc = lua_gettop(L); + int flp = 0; + + if (argc == 1) + flp = lua_tointeger(L, 1); + + flip_level(flp, !gi.in_mklev); + + return 0; +} + +DISABLE_WARNING_UNREACHABLE_CODE + +/* does location at x,y have timer? */ +/* local has_melttimer = nh.has_timer_at(x,y, "melt-ice"); */ +/* local has_melttimer = nh.has_timer_at({x=4,y=7}, "melt-ice"); */ +staticfn int +nhl_timer_has_at(lua_State *L) +{ + boolean ret = FALSE; + short timertype = nhl_get_timertype(L, -1); + lua_Integer lx, ly; + coordxy x, y; + long when; + + lua_pop(L, 1); /* remove timertype */ + if (!nhl_get_xy_params(L, &lx, &ly)) { + nhl_error(L, "nhl_timer_has_at: Wrong args"); + /*NOTREACHED*/ + return 0; + } + + x = (coordxy) lx; + y = (coordxy) ly; + cvt_to_abscoord(&x, &y); + + if (isok(x, y)) { + when = spot_time_expires(x, y, timertype); + ret = (when > 0L); + } + lua_pushboolean(L, ret); + return 1; +} + +/* when does location at x,y timer trigger? */ +/* local melttime = nh.peek_timer_at(x,y, "melt-ice"); */ +/* local melttime = nh.peek_timer_at({x=5,y=6}, "melt-ice"); */ +staticfn int +nhl_timer_peek_at(lua_State *L) +{ + long when = 0L; + short timertype = nhl_get_timertype(L, -1); + lua_Integer lx, ly; + coordxy x, y; + + lua_pop(L, 1); /* remove timertype */ + if (!nhl_get_xy_params(L, &lx, &ly)) { + nhl_error(L, "nhl_timer_peek_at: Wrong args"); + /*NOTREACHED*/ + return 0; + } + + x = (coordxy) lx; + y = (coordxy) ly; + cvt_to_abscoord(&x, &y); + + if (timer_is_pos(timertype) && isok(x, y)) + when = spot_time_expires(x, y, timertype); + lua_pushinteger(L, when); + return 1; +} + +/* stop timer at location x,y */ +/* nh.stop_timer_at(x,y, "melt-ice"); */ +/* nh.stop_timer_at({x=6,y=8}, "melt-ice"); */ +staticfn int +nhl_timer_stop_at(lua_State *L) +{ + short timertype = nhl_get_timertype(L, -1); + lua_Integer lx, ly; + coordxy x, y; + + lua_pop(L, 1); /* remove timertype */ + if (!nhl_get_xy_params(L, &lx, &ly)) { + nhl_error(L, "nhl_timer_stop_at: Wrong args"); + /*NOTREACHED*/ + return 0; + } + + x = (coordxy) lx; + y = (coordxy) ly; + cvt_to_abscoord(&x, &y); + + if (timer_is_pos(timertype) && isok(x, y)) + spot_stop_timers(x, y, timertype); + return 0; +} + +/* start timer at location x,y */ +/* nh.start_timer_at(x,y, "melt-ice", 10); */ +staticfn int +nhl_timer_start_at(lua_State *L) +{ + short timertype = nhl_get_timertype(L, -2); + long when = lua_tointeger(L, -1); + lua_Integer lx, ly; + coordxy x, y; + + lua_pop(L, 2); /* remove when and timertype */ + if (!nhl_get_xy_params(L, &lx, &ly)) { + nhl_error(L, "nhl_timer_start_at: Wrong args"); + /*NOTREACHED*/ + return 0; + } + + x = (coordxy) lx; + y = (coordxy) ly; + cvt_to_abscoord(&x, &y); + + if (timer_is_pos(timertype) && isok(x, y)) { + long where = ((long) x << 16) | (long) y; + + spot_stop_timers(x, y, timertype); + (void) start_timer((long) when, TIMER_LEVEL, MELT_ICE_AWAY, + long_to_any(where)); + } + return 0; +} + +/* returns the visual interpretation of the key bound to an extended command, + or the ext cmd name if not bound to any key */ +/* local helpkey = eckey("help"); */ +staticfn int +nhl_get_cmd_key(lua_State *L) +{ + int argc = lua_gettop(L); + + if (argc == 1) { + const char *cmd = luaL_checkstring(L, 1); + char *key = cmd_from_ecname(cmd); + + lua_pushstring(L, key); + return 1; + } + + return 0; +} + +/* add or remove a lua function callback */ +/* callback("level_enter", "function_name"); */ +/* callback("level_enter", "function_name", true); */ +/* level_enter, level_leave, cmd_before */ +staticfn int +nhl_callback(lua_State *L) +{ + int argc = lua_gettop(L); + int i; + boolean rm; + const char *fn, *cb; + + if (!gl.luacore) { + panic("nh luacore not inited"); + /*NOTREACHED*/ + return 0; + } + if (argc == 2 || argc == 3) { + if (argc == 2) { + rm = FALSE; + fn = luaL_checkstring(L, -1); + cb = luaL_checkstring(L, -2); + } else { + rm = lua_toboolean(L, -1); + fn = luaL_checkstring(L, -2); + cb = luaL_checkstring(L, -3); + } + for (i = 0; i < NUM_NHCB; i++) + if (!strcmp(cb, nhcb_name[i])) + break; + if (i >= NUM_NHCB) + return 0; + + if (rm) { + nhcb_counts[i]--; + if (nhcb_counts[i] < 0) + impossible("nh.callback counts are wrong"); + } else { + nhcb_counts[i]++; + } + + lua_getglobal(gl.luacore, rm ? "nh_callback_rm" : "nh_callback_set"); + lua_pushstring(gl.luacore, cb); + lua_pushstring(gl.luacore, fn); + nhl_pcall_handle(gl.luacore, 2, 0, "nhl_callback", NHLpa_panic); + } + return 0; +} + +/* store or restore game state */ +/* + * Currently handles + * turn counter, + * hero inventory and hunger, + * hero attributes and skills and conducts (all via 'struct u'), + * object discoveries, + * monster generation and vanquished statistics. + * NOTE: wouldn't work after restore if game has been saved, so the + * #save command ('S') is disabled during the tutorial. + */ +/* gamestate(); -- save state */ +/* gamestate(true); -- restore state */ +staticfn int +nhl_gamestate(lua_State *L) +{ + long wornmask; + struct obj *otmp; + int argc = lua_gettop(L); + boolean reststate = (argc > 0) ? lua_toboolean(L, -1) : FALSE; + int otyp; + + debugpline4("gamestate: %d:%d (%c vs %c)", u.uz.dnum, u.uz.dlevel, + reststate ? 'T' : 'F', gg.gmst_stored ? 't' : 'f'); + + if (reststate && gg.gmst_stored) { + d_level cur_uz = u.uz, cur_uz0 = u.uz0; + + /* restore game state */ + svm.moves = gg.gmst_moves; + pline("Resetting time to move #%ld.", svm.moves); + gg.gmst_moves = 0L; + + gl.lastinvnr = 51; + while (gi.invent) + useupall(gi.invent); + while ((otmp = gg.gmst_invent) != NULL) { + wornmask = otmp->owornmask; + otmp->owornmask = 0L; + extract_nobj(otmp, &gg.gmst_invent); + addinv_nomerge(otmp); + if (wornmask) + setworn(otmp, wornmask); + } + assert(gg.gmst_ubak != NULL); + (void) memcpy((genericptr_t) &u, gg.gmst_ubak, sizeof u); + assert(gg.gmst_disco != NULL); + (void) memcpy((genericptr_t) &svd.disco, gg.gmst_disco, + sizeof svd.disco); + assert(gg.gmst_mvitals != NULL); + (void) memcpy((genericptr_t) &svm.mvitals, gg.gmst_mvitals, + sizeof svm.mvitals); + /* clear user-given object type names */ + for (otyp = 0; otyp < NUM_OBJECTS; otyp++) + if (objects[otyp].oc_uname) { + free(objects[otyp].oc_uname); + objects[otyp].oc_uname = (char *) 0; + } + /* some restored state would confuse the level change in progress */ + u.uz = cur_uz, u.uz0 = cur_uz0; + init_uhunger(); + free_tutorial(); /* release gg.gmst_XYZ */ + gg.gmst_stored = FALSE; + (void) memcpy(svs.spl_book, gg.gmst_spl_book, sizeof svs.spl_book); + } else if (!reststate && !gg.gmst_stored) { + /* store game state */ + gg.gmst_moves = svm.moves; + while ((otmp = gi.invent) != NULL) { + wornmask = otmp->owornmask; + setnotworn(otmp); + freeinv(otmp); + otmp->owornmask = wornmask; /* flag for later restore */ + otmp->nobj = gg.gmst_invent; + gg.gmst_invent = otmp; + } + gl.lastinvnr = 51; /* next inv letter to try to use will be 'a' */ + gg.gmst_ubak = (genericptr_t) alloc(sizeof u); + (void) memcpy(gg.gmst_ubak, (genericptr_t) &u, sizeof u); + gg.gmst_disco = (genericptr_t) alloc(sizeof svd.disco); + (void) memcpy(gg.gmst_disco, (genericptr_t) &svd.disco, + sizeof svd.disco); + gg.gmst_mvitals = (genericptr_t) alloc(sizeof svm.mvitals); + (void) memcpy(gg.gmst_mvitals, (genericptr_t) &svm.mvitals, + sizeof svm.mvitals); + (void) memcpy(gg.gmst_spl_book, svs.spl_book, sizeof svs.spl_book); + (void) memset(svs.spl_book, 0, sizeof(svs.spl_book)); + gg.gmst_stored = TRUE; + } else { + impossible("nhl_gamestate: inconsistent state (%s vs %s)", + reststate ? "restore" : "save", + gg.gmst_stored ? "already stored" : "not stored"); + } + update_inventory(); + return 0; +} + +RESTORE_WARNING_UNREACHABLE_CODE + +/* free dynamic date allocated when entering tutorial; + called when exiting tutorial normally or if player quits while in it */ +void +free_tutorial(void) +{ + struct obj *otmp; + + /* for normal tutorial exit, gmst_invent will already be Null */ + while ((otmp = gg.gmst_invent) != 0) { + /* set otmp->where = OBJ_FREE, otmp->nobj = NULL */ + extract_nobj(otmp, &gg.gmst_invent); + /* gmst_invent is a list of invent items sequestered when entering + the tutorial; for them, owornmask is used as a flag to re-wear + them when exiting tutorial, not that they are currently worn; + clear it to avoid a "deleting worn obj" complaint from obfree() */ + otmp->owornmask = 0L; + /* dealloc_obj() isn't enough (for containers, it assumes that + caller has already freed their contents) */ + obfree(otmp, (struct obj *) 0); + } + + if (gg.gmst_ubak) + free(gg.gmst_ubak), gg.gmst_ubak = NULL; + if (gg.gmst_disco) + free(gg.gmst_disco), gg.gmst_disco = NULL; + if (gg.gmst_mvitals) + free(gg.gmst_mvitals), gg.gmst_mvitals = NULL; +} + +/* called from gotolevel(do.c) */ +void +tutorial(boolean entering) +{ + l_nhcore_call(entering ? NHCORE_ENTER_TUTORIAL : NHCORE_LEAVE_TUTORIAL); + + if (!entering) { /* after leaving, can't go back */ + nhcore_call_available[NHCORE_ENTER_TUTORIAL] + = nhcore_call_available[NHCORE_LEAVE_TUTORIAL] + = FALSE; + } +} + +static const struct luaL_Reg nhl_functions[] = { + { "test", nhl_test }, + + { "getmap", nhl_getmap }, +#if 0 + { "setmap", nhl_setmap }, +#endif + { "gettrap", nhl_gettrap }, + { "deltrap", nhl_deltrap }, + + { "has_timer_at", nhl_timer_has_at }, + { "peek_timer_at", nhl_timer_peek_at }, + { "stop_timer_at", nhl_timer_stop_at }, + { "start_timer_at", nhl_timer_start_at }, + + { "abscoord", nhl_abs_coord }, + + { "impossible", nhl_impossible }, + { "pline", nhl_pline }, + { "verbalize", nhl_verbalize }, + { "menu", nhl_menu }, + { "text", nhl_text }, + { "getlin", nhl_getlin }, + { "eckey", nhl_get_cmd_key }, + { "callback", nhl_callback }, + { "gamestate", nhl_gamestate }, + + { "makeplural", nhl_makeplural }, + { "makesingular", nhl_makesingular }, + { "s_suffix", nhl_s_suffix }, + { "ing_suffix", nhl_ing_suffix }, + { "an", nhl_an }, + { "rn2", nhl_rn2 }, + { "random", nhl_random }, + { "level_difficulty", nhl_level_difficulty }, + { "is_genocided", nhl_is_genocided }, + { "debug_themerm", nhl_get_debug_themerm_name }, + { "parse_config", nhl_parse_config }, + { "get_config", nhl_get_config }, + { "get_config_errors", l_get_config_errors }, +#ifdef DUMPLOG + { "dump_fmtstr", nhl_dump_fmtstr }, +#endif /* DUMPLOG */ + { "dnum_name", nhl_dnum_name }, + { "int_to_pmname", nhl_int_to_pm_name }, + { "int_to_objname", nhl_int_to_obj_name }, + { "variable", nhl_variable }, + { "stairways", nhl_stairways }, + { "pushkey", nhl_pushkey }, + { "doturn", nhl_doturn }, + { "debug_flags", nhl_debug_flags }, + { "flip_level", nhl_flip_level }, + { NULL, NULL } +}; + +static const struct { + const char *name; + long value; +} nhl_consts[] = { + { "COLNO", COLNO }, + { "ROWNO", ROWNO }, + { "NUMMONS", NUMMONS }, + { "LOW_PM", LOW_PM }, + { "HIGH_PM", HIGH_PM }, + { "FIRST_OBJECT", FIRST_OBJECT }, + { "LAST_OBJECT", NUM_OBJECTS-1 }, +#ifdef DLB + { "DLB", 1 }, +#else + { "DLB", 0 }, +#endif /* DLB */ + { NULL, 0 }, +}; + +/* register and init the constants table */ +staticfn void +init_nhc_data(lua_State *L) +{ + int i; + + lua_newtable(L); + + for (i = 0; nhl_consts[i].name; i++) { + lua_pushstring(L, nhl_consts[i].name); + lua_pushinteger(L, nhl_consts[i].value); + lua_rawset(L, -3); + } + + lua_setglobal(L, "nhc"); +} + +staticfn int +nhl_push_anything(lua_State *L, int anytype, void *src) +{ + anything any = cg.zeroany; + + switch (anytype) { + case ANY_INT: + any.a_int = *(int *) src; + lua_pushinteger(L, any.a_int); + break; + case ANY_UCHAR: + any.a_uchar = *(uchar *) src; + lua_pushinteger(L, any.a_uchar); + break; + case ANY_SCHAR: + any.a_schar = *(schar *) src; + lua_pushinteger(L, any.a_schar); + break; + } + return 1; +} + +DISABLE_WARNING_UNREACHABLE_CODE + +staticfn int +nhl_meta_u_index(lua_State *L) +{ + static const struct { + const char *name; + void *ptr; + int type; + } ustruct[] = { + { "ux", &(u.ux), ANY_UCHAR }, + { "uy", &(u.uy), ANY_UCHAR }, + { "dx", &(u.dx), ANY_SCHAR }, + { "dy", &(u.dy), ANY_SCHAR }, + { "dz", &(u.dz), ANY_SCHAR }, + { "tx", &(u.tx), ANY_UCHAR }, + { "ty", &(u.ty), ANY_UCHAR }, + { "ulevel", &(u.ulevel), ANY_INT }, + { "ulevelmax", &(u.ulevelmax), ANY_INT }, + { "uhunger", &(u.uhunger), ANY_INT }, + { "nv_range", &(u.nv_range), ANY_INT }, + { "xray_range", &(u.xray_range), ANY_INT }, + { "umonster", &(u.umonster), ANY_INT }, + { "umonnum", &(u.umonnum), ANY_INT }, + { "mh", &(u.mh), ANY_INT }, + { "mhmax", &(u.mhmax), ANY_INT }, + { "mtimedone", &(u.mtimedone), ANY_INT }, + { "dlevel", &(u.uz.dlevel), ANY_SCHAR }, /* actually coordxy */ + { "dnum", &(u.uz.dnum), ANY_SCHAR }, /* actually coordxy */ + { "uluck", &(u.uluck), ANY_SCHAR }, + { "uhp", &(u.uhp), ANY_INT }, + { "uhpmax", &(u.uhpmax), ANY_INT }, + { "uen", &(u.uen), ANY_INT }, + { "uenmax", &(u.uenmax), ANY_INT }, + }; + const char *tkey = luaL_checkstring(L, 2); + int i; + + /* FIXME: doesn't really work, eg. negative values for u.dx */ + for (i = 0; i < SIZE(ustruct); i++) + if (!strcmp(tkey, ustruct[i].name)) { + return nhl_push_anything(L, ustruct[i].type, ustruct[i].ptr); + } + + if (!strcmp(tkey, "inventory")) { + nhl_push_obj(L, gi.invent); + return 1; + } else if (!strcmp(tkey, "role")) { + lua_pushstring(L, gu.urole.name.m); + return 1; + } else if (!strcmp(tkey, "moves")) { + lua_pushinteger(L, svm.moves); + return 1; + } else if (!strcmp(tkey, "uhave_amulet")) { + lua_pushinteger(L, u.uhave.amulet); + return 1; + } else if (!strcmp(tkey, "depth")) { + lua_pushinteger(L, depth(&u.uz)); + return 1; + } else if (!strcmp(tkey, "invocation_level")) { + lua_pushboolean(L, Invocation_lev(&u.uz)); + return 1; + } + + nhl_error(L, "Unknown u table index"); + /*NOTREACHED*/ + return 0; +} + +staticfn int +nhl_meta_u_newindex(lua_State *L) +{ + nhl_error(L, "Cannot set u table values"); + /*NOTREACHED*/ + return 0; +} + +RESTORE_WARNING_UNREACHABLE_CODE + +staticfn int +nhl_u_clear_inventory(lua_State *L UNUSED) +{ + while (gi.invent) + useupall(gi.invent); + return 0; +} + +/* Put object into player's inventory */ +/* u.giveobj(obj.new("rock")); */ +staticfn int +nhl_u_giveobj(lua_State *L) +{ + return nhl_obj_u_giveobj(L); +} + +static const struct luaL_Reg nhl_u_functions[] = { + { "clear_inventory", nhl_u_clear_inventory }, + { "giveobj", nhl_u_giveobj }, + { NULL, NULL } +}; + +staticfn void +init_u_data(lua_State *L) +{ + lua_newtable(L); + luaL_setfuncs(L, nhl_u_functions, 0); + lua_newtable(L); + lua_pushcfunction(L, nhl_meta_u_index); + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, nhl_meta_u_newindex); + lua_setfield(L, -2, "__newindex"); + lua_setmetatable(L, -2); + lua_setglobal(L, "u"); +} + +#ifdef notyet +staticfn int +nhl_set_package_path(lua_State *L, const char *path) +{ + if (LUA_TTABLE != lua_getglobal(L, "package")) { + impossible("package not a table in nhl_set_package_path"); + return 1; + }; + lua_pushstring(L, path); + lua_setfield(L, -2, "path"); + lua_pop(L, 1); + return 0; +} +#endif + +staticfn int +traceback_handler(lua_State *L) +{ + luaL_traceback(L, L, lua_tostring(L, 1), 0); + /* TODO: call impossible() if fuzzing? */ + return 1; +} + +staticfn uint32_t +nhl_getmeminuse(lua_State *L) +{ + return lua_gc(L, LUA_GCCOUNT) * 1024 + lua_gc(L, LUA_GCCOUNTB); +} + +/* lua_pcall with our traceback handler and memory and instruction step + * limiting. + * On error, traceback will be on top of stack */ +int +nhl_pcall(lua_State *L, int nargs, int nresults, const char *name) +{ + struct nhl_user_data *nud; + int rv; + + lua_pushcfunction(L, traceback_handler); + lua_insert(L, 1); + (void) lua_getallocf(L, (void **) &nud); +#ifdef NHL_SANDBOX + if (nud && name) { + nud->name = name; + } + /* NB: We don't need to deal with nud->memlimit - Lua handles that. */ + if (nud && (nud->steps || nud->perpcall)) { + if (nud->perpcall) { + nud->steps = nud->perpcall; + nud->statctr = 0; + } + if (setjmp(nud->jb)) { + /* panic, because we don't know if the game state is corrupt */ + /* XXX can we get a lua stack trace as well? */ + panic("Lua time exceeded %d:%s", nud->sid, + nud->name ? nud->name : "(unknown)"); + } + } +#endif + + rv = lua_pcall(L, nargs, nresults, 1); + + lua_remove(L, 1); /* remove handler */ + +#ifdef NHL_SANDBOX + if (nud && nud->perpcall && gl.loglua) { + long ic = nud->statctr * NHL_SB_STEPSIZE; // an approximation + livelog_printf(LL_DEBUG, "LUASTATS PCAL %d:%s %ld", nud->sid, + nud->name, ic); + } + if (nud && nud->memlimit && gl.loglua) { + livelog_printf(LL_DEBUG, "LUASTATS PMEM %d:%s %lu", nud->sid, + nud->name, (long unsigned) nhl_getmeminuse(L)); + } +#endif + return rv; +} + +int +nhl_pcall_handle(lua_State *L, int nargs, int nresults, const char *name, + NHL_pcall_action npa) +{ + int rv = nhl_pcall(L, nargs, nresults, name); + if (rv) { + nhl_user_data *nud; + (void) lua_getallocf(L, (void **) &nud); + /* XXX can we get a lua stack trace as well? */ + switch (npa) { + case NHLpa_panic: + panic("Lua error %d:%s %s", nud->sid, + nud->name ? nud->name : "(unknown)", lua_tostring(L, -1)); + /*NOTREACHED*/ + break; + case NHLpa_impossible: + impossible("Lua error: %d:%s %s", nud->sid, + nud->name ? nud->name : "(unknown)", + lua_tostring(L, -1)); + /* Drop the error. If the caller cares, use nhl_pcall(). */ + lua_pop(L, 1); + } + } + return rv; +} + +/* XXX impossible() should be swappable with pline/nothing/panic via flag */ +/* read lua code/data from a dlb module or an external file + into a string buffer and feed that to lua */ +boolean +nhl_loadlua(lua_State *L, const char *fname) +{ +#define LOADCHUNKSIZE (1L << 13) /* 8K */ + boolean ret = TRUE; + dlb *fh; + char *buf = (char *) 0, *bufin, *bufout, *p, *nl, *altfname; + long buflen, ct, cnt; + int llret; + + altfname = (char *) alloc(Strlen(fname) + 3); /* 3: '('...')\0' */ + /* don't know whether 'fname' is inside a dlb container; + if we did, we could choose between "nhdat()" and "" + but since we don't, compromise */ + Sprintf(altfname, "(%s)", fname); + fh = dlb_fopen(fname, RDBMODE); + if (!fh) { + impossible("nhl_loadlua: Error opening %s", altfname); + ret = FALSE; + goto give_up; + } + + dlb_fseek(fh, 0L, SEEK_END); + buflen = dlb_ftell(fh); + dlb_fseek(fh, 0L, SEEK_SET); + + /* extra +1: room to add final '\n' if missing */ + buf = bufout = (char *) alloc(FITSint(buflen + 1 + 1)); + buf[0] = '\0'; + bufin = bufout = buf; + + ct = 0L; + while (buflen > 0 || ct) { + /* + * Semi-arbitrarily limit reads to 8K at a time. That's big + * enough to cover the majority of our Lua files in one bite + * but small enough to fully exercise the partial record + * handling (when processing the castle's level description). + * + * [For an external file (non-DLB), VMS may only be able to + * read at most 32K-1 at a time depending on the file format + * in use, and fseek(SEEK_END) only yields an upper bound on + * the actual amount of data in that situation.] + */ + if ((cnt = dlb_fread(bufin, 1, min((int) buflen, LOADCHUNKSIZE), fh)) + < 0L) + break; + buflen -= cnt; /* set up for next iteration, if any */ + if (cnt == 0L) { + *bufin = '\n'; /* very last line is unterminated? */ + cnt = 1; + } + bufin[cnt] = '\0'; /* fread() doesn't do this */ + + /* in case partial line was leftover from previous fread */ + bufin -= ct, cnt += ct, ct = 0; + + while (cnt > 0) { + if ((nl = strchr(bufin, '\n')) != 0) { + /* normal case, newline is present */ + ct = (long) (nl - bufin + 1L); /* +1: keep the newline */ + for (p = bufin; p <= nl; ++p) + *bufout++ = *bufin++; + if (*bufin == '\r') + ++bufin, ++ct; + /* update for next loop iteration */ + cnt -= ct; + ct = 0; + } else if (strlen(bufin) < LOADCHUNKSIZE) { + /* no newline => partial record; move unprocessed chars + to front of input buffer (bufin portion of buf[]) */ + ct = cnt = (long) (eos(bufin) - bufin); + for (p = bufout; cnt > 0; --cnt) + *p++ = *bufin++; + *p = '\0'; + bufin = p; /* next fread() populates buf[] starting here */ + /* cnt==0 so inner loop will terminate */ + } else { + /* LOADCHUNKSIZE portion of buffer already completely full */ + impossible("(%s) line too long", altfname); + goto give_up; + } + } + } + *bufout = '\0'; + (void) dlb_fclose(fh); + + llret = luaL_loadbuffer(L, buf, strlen(buf), altfname); + if (llret != LUA_OK) { + impossible("luaL_loadbuffer: Error loading %s: %s", altfname, + lua_tostring(L, -1)); + ret = FALSE; + goto give_up; + } else { + if (nhl_pcall_handle(L, 0, LUA_MULTRET, fname, NHLpa_impossible)) { + ret = FALSE; + goto give_up; + } + } + + give_up: + if (altfname) + free((genericptr_t) altfname); + if (buf) + free((genericptr_t) buf); + return ret; +#undef LOADCHUNKSIZE +} + +DISABLE_WARNING_CONDEXPR_IS_CONSTANT + +lua_State * +nhl_init(nhl_sandbox_info *sbi) +{ + /* It would be nice to import EXPECTED from each build system. XXX */ + /* And it would be nice to do it only once, but it's cheap. */ +#ifndef NHL_VERSION_EXPECTED +#if LUA_VERSION_NUM >= 505 +#define NHL_VERSION_EXPECTED 50500 +#else +#define NHL_VERSION_EXPECTED 50408 +#endif +#endif + +#ifdef NHL_SANDBOX + if (NHL_VERSION_EXPECTED != LUA_VERSION_RELEASE_NUM) { + panic( + "sandbox doesn't know this Lua version: this=%d != expected=%d ", + LUA_VERSION_RELEASE_NUM, NHL_VERSION_EXPECTED); + } +#endif + + lua_State *L = nhlL_newstate(sbi, "nhl_init"); + if(!L) return 0; + + iflags.in_lua = TRUE; + /* Temporary for development XXX */ + /* Turn this off in config.h to disable the sandbox. */ +#ifdef NHL_SANDBOX + nhlL_openlibs(L, sbi->flags); +#else + luaL_openlibs(L); +#endif + +#ifdef notyet + if (sbi->flags & NHL_SB_PACKAGE) { + /* XXX Is this still needed? */ + if (nhl_set_package_path(L, "./?.lua")) + return 0; + } +#endif + + /* register nh -table, and functions for it */ + lua_newtable(L); + luaL_setfuncs(L, nhl_functions, 0); + lua_setglobal(L, "nh"); + + /* init nhc -table */ + init_nhc_data(L); + + /* init u -table */ + init_u_data(L); + + l_selection_register(L); + l_register_des(L); + + l_obj_register(L); + + /* nhlib.lua assumes the math table exists. */ + if (LUA_TTABLE != lua_getglobal(L, "math")) { + lua_newtable(L); + lua_setglobal(L, "math"); + } + + if (!nhl_loadlua(L, "nhlib.lua")) { + nhl_done(L); + return (lua_State *) 0; + } + + return L; +} + +RESTORE_WARNING_CONDEXPR_IS_CONSTANT + +void +nhl_done(lua_State *L) +{ + if (L) { + nhl_user_data *nud = 0; + (void) lua_getallocf(L, (void **) &nud); + if (gl.loglua) { + if (nud && nud->osteps) { + long ic = nud->statctr * NHL_SB_STEPSIZE; // an approximation + livelog_printf(LL_DEBUG, "LUASTATS DONE %d:%s %ld", nud->sid, + nud->name, ic); + } + if (nud && nud->memlimit && !nud->perpcall) { + livelog_printf(LL_DEBUG, "LUASTATS DMEM %d:%s %lu", nud->sid, + nud->name, (long unsigned) nhl_getmeminuse(L)); + } + } + lua_close(L); + if (nud) + nhl_alloc(NULL, nud, 0, 0); // free nud + } + iflags.in_lua = FALSE; +} + +boolean +load_lua(const char *name, nhl_sandbox_info *sbi) +{ + boolean ret = TRUE; + lua_State *L = nhl_init(sbi); + + if (!L) { + ret = FALSE; + goto give_up; + } + + if (!nhl_loadlua(L, name)) { + ret = FALSE; + goto give_up; + } + + give_up: + nhl_done(L); + + return ret; +} + +DISABLE_WARNING_CONDEXPR_IS_CONSTANT + +const char * +get_lua_version(void) +{ + nhl_sandbox_info sbi = { NHL_SB_VERSION, 1 * 1024 * 1024, 0, + 1 * 1024 * 1024 }; + + if (gl.lua_ver[0] == 0) { + lua_State *L = nhl_init(&sbi); + + if (L) { + size_t len = 0; + const char *vs = (const char *) 0; + + /* LUA_VERSION yields "." although we check to see + whether it is "Lua-." and strip prefix if so; + LUA_RELEASE is . but doesn't + get set up as a lua global */ + lua_getglobal(L, "_RELEASE"); + if (lua_isstring(L, -1)) + vs = lua_tolstring(L, -1, &len); +#ifdef LUA_RELEASE + else + vs = LUA_RELEASE, len = strlen(vs); +#endif + if (!vs) { + lua_getglobal(L, "_VERSION"); + if (lua_isstring(L, -1)) + vs = lua_tolstring(L, -1, &len); +#ifdef LUA_VERSION + else + vs = LUA_VERSION, len = strlen(vs); +#endif + } + if (vs && len < sizeof gl.lua_ver) { + if (!strncmpi(vs, "Lua", 3)) { + vs += 3; + if (*vs == '-' || *vs == ' ') + vs += 1; + } + Strcpy(gl.lua_ver, vs); + } + } + nhl_done(L); +#ifdef LUA_COPYRIGHT + if (sizeof LUA_COPYRIGHT <= sizeof gl.lua_copyright) + Strcpy(gl.lua_copyright, LUA_COPYRIGHT); +#endif + } + return (const char *) gl.lua_ver; +} + +RESTORE_WARNING_CONDEXPR_IS_CONSTANT + +/*** + *** SANDBOX / HARDENING CODE + ***/ +/* + * Tracing the sandbox: + * 1. Make sure CHRONICLE and LIVELOG are defined in config.h. + * 2. Rebuild the game. + * 3. Run the game and do whatever you want to check with lua. + * 4. Find the logged information in livelog. + * + * Logging format in livelog: + * message=LUASTATS : + * where: + * TYPE + * DONE rough number of step taken during the life of the VM + * DMEM memory in use at destruction of VM + * PCAL rough number of steps during lua_pcall + * PMEM memory in use after lua_pcall returns + * SID a small integer identifying the Lua VM instance + * TAG a string from the nhl_luapcall call + * DATA memory: rough number of bytes in use by the VM + * steps: rough number of steps by the VM(see NHL_SB_STEPSIZE) + */ +#ifdef NHL_SANDBOX + +enum ewhen {NEVER, IFFLAG, EOT}; +struct e { + enum ewhen when; + const char *fnname; +}; + +/* NHL_BASE_BASE - safe things */ +static struct e ct_base_base[] = { + { IFFLAG, "ipairs" }, + { IFFLAG, "next" }, + { IFFLAG, "pairs" }, + { IFFLAG, "pcall" }, + { IFFLAG, "select" }, + { IFFLAG, "tonumber" }, + { IFFLAG, "tostring" }, + { IFFLAG, "type" }, + { IFFLAG, "xpcall" }, + { EOT, NULL } +}; + +/* NHL_BASE_ERROR - not really safe--might not want Lua to kill the process */ +static struct e ct_base_error[] = { + { IFFLAG, "assert" }, /* ok, calls error */ + { IFFLAG, "error" }, /* ok, calls G->panic */ + { NEVER, "print" }, /* not ok - calls lua_writestring/lua_writeline + * which write to stdout */ + { NEVER, "warn" }, /* not ok - calls lua_writestringerror which writes + * to stderr */ + { EOT, NULL } +}; + +/* NHL_BASE_META - metatable access */ +static struct e ct_base_meta[] = { + { IFFLAG, "getmetatable" }, + { IFFLAG, "rawequal" }, + { IFFLAG, "rawget" }, + { IFFLAG, "rawlen" }, + { IFFLAG, "rawset" }, + { IFFLAG, "setmetatable" }, + { EOT, NULL } +}; + +/* NHL_BASE_GC - questionable safety */ +static struct e ct_base_iffy[] = { + { IFFLAG, "collectgarbage" }, + { EOT, NULL } +}; + +/* NHL_BASE_UNSAFE - include only if required */ +/* TODO: if NHL_BASE_UNSAFE is ever used, we need to wrap lua_load with + * something to forbid mode=="b" */ +static struct e ct_base_unsafe[] = { + { IFFLAG, "dofile" }, + { IFFLAG, "loadfile" }, + { IFFLAG, "load" }, + { EOT, NULL } +}; + +/* no ct_co_ tables; all fns at same level of concern */ +/* no ct_string_ tables; all fns at same level of concern */ +/* no ct_table_ tables; all fns at same level of concern (but + sort can take a lot of time and can't be caught by the step limit */ +/* no ct_utf8_ tables; all fns at same level of concern */ + + +/* possible ct_debug tables - likely to need changes */ +static struct e ct_debug_debug[] = { + { NEVER, "debug" }, /* uses normal I/O so needs re-write */ + { IFFLAG, "getuservalue" }, + { NEVER, "gethook" }, /* see sethook */ + { IFFLAG, "getinfo" }, + { IFFLAG, "getlocal" }, + { IFFLAG, "getregistry" }, + { IFFLAG, "getmetatable" }, + { IFFLAG, "getupvalue" }, + { IFFLAG, "upvaluejoin" }, + { IFFLAG, "upvalueid" }, + { IFFLAG, "setuservalue" }, + { NEVER, "sethook" }, /* used for memory and step limits */ + { IFFLAG, "setlocal" }, + { IFFLAG, "setmetatable" }, + { IFFLAG, "setupvalue" }, + { IFFLAG, "setcstacklimit" }, + { EOT, NULL } +}; +static struct e ct_debug_safe[] = { + { IFFLAG, "traceback" }, + { EOT, NULL } +}; + +/* possible ct_os_ tables */ +static struct e ct_os_time[] = { + { IFFLAG, "clock" }, /* is this portable? XXX */ + { IFFLAG, "date" }, + { IFFLAG, "difftime" }, + { IFFLAG, "time" }, + { EOT, NULL } +}; + +static struct e ct_os_files[] = { + { NEVER, "execute" }, /* not portable */ + { NEVER, "exit" }, + { NEVER, "getenv" }, + { IFFLAG, "remove" }, + { IFFLAG, "rename" }, + { NEVER, "setlocale" }, + { NEVER, "tmpname" }, + { EOT, NULL } +}; + + +#define DROPIF(flag, lib, ct) \ + nhl_clearfromtable(L, !!(lflags & flag), lib, ct) + +staticfn void +nhl_clearfromtable(lua_State *L, int flag, int tndx, struct e *todo) +{ + while (todo->when != EOT) { + lua_pushnil(L); + /* if we load the library at all, NEVER items must be erased + * and IFFLAG items should be erased if !flag */ + if (todo->when == NEVER || !flag) { + lua_setfield(L, tndx, todo->fnname); + } + todo++; + } +} +#endif + +/* +XXX +registry["org.nethack.nethack.sb.fs"][N]= + CODEOBJECT + { + modepat: PATTERN, + filepat: PATTERN + } +CODEOBJECT + if string then if pcall(string,mode, dir, file) + if table then if mode matches pattern and filepat ma.... +or do we use a real regex engine? (which we don't have and I just + argued against adding) + +return values from "call it": + accept - file access granted + reject - file access denied + continue - try next element + fail - error. deny and call impossible/panic +*/ + +/* stack indexes: + * -1 table to index with ename + * params file + * params+1 [mode] + */ +/* + * Problem: NetHack doesn't have a regex engine and Lua doesn't give + * C access to pattern matching. There are 3 poor solutions: + * 1. Import ~5K lines of code in a dozen files from FreeBSD. (Upside - we + * could use it in other places in NetHack.) + * 2. Hack up lstrlib.c to give C direct access to the pattern matching code. + * 3. Create a Lua state just to do pattern matching. + * We're going to do #3. + */ +#ifdef notyet +staticfn boolean +start_luapat(void) +{ + int rv; + /* XXX set memory and step limits */ + nhl_sandbox_info sbi = { NHL_SB_STRING, 0, 0, 0 }; + + if ((luapat = nhl_init(&sbi)) == NULL) + return FALSE; + + /* load a pattern matching function */ + rv = luaL_loadstring( + luapat, + "function matches(s,p) return not not stringm.match(s,p) end"); + if (rv != LUA_OK) { + panic("start_luapat: %d", rv); + } + return TRUE; +} +#endif + +staticfn void +end_luapat(void) +{ + if (luapat) { + lua_close(luapat); + luapat = NULL; + } +} + +#ifdef notyet +staticfn int +opencheckpat(lua_State *L, const char *ename, int param) +{ + /* careful - we're using 2 different and unrelated Lua states */ + const char *string; + int rv; + + lua_pushliteral(luapat, "matches"); /* function -0,+1 */ + + string = lua_tolstring(L, param, NULL); /* mode or filename -0,+0 */ + lua_pushstring(luapat, string); /* -0,+1 */ + + + (void) lua_getfield(L, -1, ename); /* pattern -0,+1 */ + lua_pop(L, 1); /* -1,+0 */ + string = lua_tolstring(L, -1, NULL); /* -0,+0 */ + lua_pushstring(luapat, string); /* -0,+1 */ + + if (nhl_pcall(luapat, 2, 1)) { /* -3,+1 */ + /* impossible("access check internal error"); */ + return NHL_SBRV_FAIL; + } + rv = lua_toboolean(luapat, -1); /* -0,+0 */ +#if 0 + if (lua_resetthread(luapat) != LUA_OK) + return NHL_SBRV_FAIL; +is pop sufficient? XXX or wrong - look at the balance +#else + lua_pop(luapat, 1); /* -1,+0 */ +#endif + return rv ? NHL_SBRV_ACCEPT : NHL_SBRV_DENY; +} +#endif + +/* put the table open uses to check its arguments on the top of the stack, + * creating it if needed + */ +#define HOOKTBLNAME "org.nethack.nethack.sb.fs" +#ifdef notyet +static int (*io_open)(lua_State *) = NULL; /* XXX this may have to be in + * struct g (gi now) TBD */ +#endif + +void +nhl_pushhooked_open_table(lua_State *L) +{ + int hot = lua_getfield(L, LUA_REGISTRYINDEX, HOOKTBLNAME); + + if (hot == LUA_TNONE) { + lua_newtable(L); + lua_pushvalue(L, -1); + lua_setfield(L, LUA_REGISTRYINDEX, HOOKTBLNAME); + } +} + +#ifdef notyet +staticfn int +hooked_open(lua_State *L) +{ + const char *mode; + static boolean never = TRUE; + const char *filename; + int params; + int hot; + + if (never) { + if (!start_luapat()) + return NHL_SBRV_FAIL; + never = FALSE; + } + filename = luaL_checkstring(L, 1); + + /* Unlike io.open, we want to treat mode as non-optional. */ + if (lua_gettop(L) < 2) { + lua_pushstring(L, "r"); + } + mode = luaL_optstring(L, 2, "r"); + + /* sandbox checks */ + /* Do we need some ud from the calling state to let this be different for + each call without redoing the HO table?? Maybe for version 2. XXX */ + + params = lua_gettop(L)-1; /* point at first param */ + nhl_pushhooked_open_table(L); + hot = lua_gettop(L); + + if (lua_type(L, hot) == LUA_TTABLE) { + int idx; + for (idx = 1; + lua_pushinteger(L, idx), + lua_geti(L, hot, idx), + !lua_isnoneornil(L, -1); + ++idx) { + /* top of stack is our configtbl[idx] */ + switch (lua_type(L, -1)) { + /* lots of options to expand this with other types XXX */ + case LUA_TTABLE: { + int moderv, filerv; + moderv = opencheckpat(L, "modepat", params+1); + if (moderv == NHL_SBRV_FAIL) + return moderv; + filerv = opencheckpat(L, "filepat", params); + if (filerv == NHL_SBRV_FAIL) + return moderv; + if (filerv == moderv) { + if (filerv == NHL_SBRV_DENY) + return NHL_SBRV_DENY; + if (filerv == NHL_SBRV_ACCEPT) + goto doopen; + } + break; /* try next entry */ + } + default: + return NHL_SBRV_FAIL; + } + } + } else + return NHL_SBRV_DENY; /* default to "no" */ + + doopen: + lua_settop(L, params + 1); + return (*io_open)(L); +} + +staticfn boolean +hook_open(lua_State *L) +{ + boolean rv = FALSE; + if (!io_open) { + int tos = lua_gettop(L); + lua_pushglobaltable(L); + if (lua_getfield(L, -1, "io") != LUA_TTABLE) + goto out; + lua_getfield(L, -1, "open"); + /* The only way this can happen is if someone is messing with us, + * and I'm not sure even that is possible. */ + if (!lua_iscfunction(L, -1)) + goto out; + /* XXX This is fragile: C11 says casting func* to void* + * doesn't have to work, but POSIX says it does. So it + * _should_ work everywhere but all we can do without messing + * around inside Lua is to try to keep the compiler quiet. */ + io_open = (int (*)(lua_State *)) lua_topointer(L, -1); + lua_pushcfunction(L, hooked_open); + lua_setfield(L, -1, "open"); + rv = TRUE; + out: + lua_settop(L, tos); + } + return rv; +} +#endif + +DISABLE_WARNING_CONDEXPR_IS_CONSTANT + +#ifdef NHL_SANDBOX +staticfn void +nhlL_openlibs(lua_State *L, uint32_t lflags) +{ + /* translate lflags from user-friendly to internal */ + if (NHL_SB_DEBUGGING & lflags) { + lflags |= NHL_SB_DB_SAFE; + } + /* only for debugging the sandbox integration */ + if (NHL_SB_ALL & lflags) { + lflags = -1; + } else if (NHL_SB_SAFE & lflags) { + lflags |= NHL_SB_BASE_BASE; + lflags |= NHL_SB_COROUTINE; + lflags |= NHL_SB_TABLE; + lflags |= NHL_SB_STRING; + lflags |= NHL_SB_MATH; + lflags |= NHL_SB_UTF8; + } else if (NHL_SB_VERSION) { + lflags |= NHL_SB_BASE_BASE; + } +#ifdef notyet +/* Handling I/O is complex, so it's not available yet. I'll +finish it if and when we need it. (keni) + - hooked open; array of tuples of (r/w/rw/a/etc, directory pat, file pat) + +{"close", io_close}, but with no args closes default output, so needs hook +{"flush", io_flush}, +{"lines", io_lines}, hook due to filename +{"open", io_open}, hooked version: + only safe if mode not present or == "r" + or WRITEIO + only safe if path has no slashes + XXX probably need to be: matches port-specific list of paths + WRITEIO needs a different list + dlb integration? + may need to #define l_getc (but that wouldn't hook core) + may need to #define fopen/fread/fwrite/feof/ftell (etc?) + ugh: lauxlib.c uses getc() below luaL_loadfilex + override in lua.h? + ugh: liolib.c uses getc() below g_read->test_eof + override in lua.h? +{"read", io_read}, +{"type", io_type}, +{"input", io_input}, safe with a complex hook, but may be needed for read? +WRITEIO: needs changes to hooked open? +{"output", io_output}, do we want to allow access to default output? + {"write", io_write}, +UNSAFEIO: + {"popen", io_popen}, + {"tmpfile", io_tmpfile}, +*/ +#endif + + if (lflags & NHL_SB_BASEMASK) { + int baselib; + /* load the entire library ... */ + luaL_requiref(L, LUA_GNAME, luaopen_base, 1); + + baselib = lua_gettop(L); + + /* ... and remove anything unsupported or not requested */ + DROPIF(NHL_SB_BASE_BASE, baselib, ct_base_base); + DROPIF(NHL_SB_BASE_ERROR, baselib, ct_base_error); + DROPIF(NHL_SB_BASE_META, baselib, ct_base_meta); + DROPIF(NHL_SB_BASE_GC, baselib, ct_base_iffy); + DROPIF(NHL_SB_BASE_UNSAFE, baselib, ct_base_unsafe); + + lua_pop(L, 1); + } + + if (lflags & NHL_SB_COROUTINE) { + luaL_requiref(L, LUA_COLIBNAME, luaopen_coroutine, 1); + lua_pop(L, 1); + } + if (lflags & NHL_SB_TABLE) { + luaL_requiref(L, LUA_TABLIBNAME, luaopen_table, 1); + lua_pop(L, 1); + } +#ifdef notyet + if (lflags & NHL_SB_IO) { + luaL_requiref(L, LUA_IOLIBNAME, luaopen_io, 1); + lua_pop(L, 1); + if (!hook_open(L)) + panic("can't hook io.open"); + } +#endif + if (lflags & NHL_SB_OSMASK) { + int oslib; + luaL_requiref(L, LUA_OSLIBNAME, luaopen_os, 1); + oslib = lua_gettop(L); + DROPIF(NHL_SB_OS_TIME, oslib, ct_os_time); + DROPIF(NHL_SB_OS_FILES, oslib, ct_os_files); + lua_pop(L, 1); + } + + if (lflags & NHL_SB_STRING) { + luaL_requiref(L, LUA_STRLIBNAME, luaopen_string, 1); + lua_pop(L, 1); + } + if (lflags & NHL_SB_MATH) { + luaL_requiref(L, LUA_MATHLIBNAME, luaopen_math, 1); + /* XXX Note that math.random uses Lua's built-in xoshiro256** + * algorithm regardless of what the rest of the game uses. + * Fixing this would require changing lmathlib.c. */ + lua_pop(L, 1); + } + if (lflags & NHL_SB_UTF8) { + luaL_requiref(L, LUA_UTF8LIBNAME, luaopen_utf8, 1); + lua_pop(L, 1); + } + if (lflags & NHL_SB_DBMASK) { + int dblib; + luaL_requiref(L, LUA_DBLIBNAME, luaopen_debug, 1); + dblib = lua_gettop(L); + DROPIF(NHL_SB_DB_DB, dblib, ct_debug_debug); + DROPIF(NHL_SB_DB_SAFE, dblib, ct_debug_safe); + lua_pop(L, 1); + } +} +#endif + +RESTORE_WARNING_CONDEXPR_IS_CONSTANT + +staticfn void * +nhl_alloc(void *ud, void *ptr, size_t osize UNUSED, size_t nsize) +{ + nhl_user_data *nud = ud; + + if (nsize == 0) { + if (ptr != NULL) { + free(ptr); + } + return NULL; + } + + /* Check nud->L because it will be NULL during state init. */ + if (nud && nud->L && nud->memlimit) { /* this state is size limited */ + if (nhl_getmeminuse(nud->L) > nud->memlimit) + return NULL; + } + + return re_alloc(ptr, (unsigned) nsize); +} + +DISABLE_WARNING_UNREACHABLE_CODE + +staticfn int +nhl_panic(lua_State *L) +{ + const char *msg = lua_tostring(L, -1); + + if (msg == NULL) + msg = "error object is not a string"; + panic("unprotected error in call to Lua API (%s)\n", msg); + /*NOTREACHED*/ + return 0; /* return to Lua to abort */ +} + +RESTORE_WARNING_UNREACHABLE_CODE + +/* called when lua issues a warning message; the text of the message + is passed to us in pieces across multiple function calls */ +staticfn void +nhl_warn( + void *userdata UNUSED, + const char *msg_fragment, + int to_be_continued) /* 0: last fragment; 1: more to come */ +{ + size_t fraglen, buflen = strlen(gl.lua_warnbuf); + + if (msg_fragment && buflen < sizeof gl.lua_warnbuf - 1) { + fraglen = strlen(msg_fragment); + if (buflen + fraglen > sizeof gl.lua_warnbuf - 1) + fraglen = sizeof gl.lua_warnbuf - 1 - buflen; + (void) strncat(gl.lua_warnbuf, msg_fragment, fraglen); + } + if (!to_be_continued) { + paniclog("[lua]", gl.lua_warnbuf); + gl.lua_warnbuf[0] = '\0'; + } +} + +#ifdef NHL_SANDBOX +staticfn void +nhl_hookfn(lua_State *L, lua_Debug *ar UNUSED) +{ + nhl_user_data *nud; + + (void) lua_getallocf(L, (void **) &nud); + + if (nud->steps <= NHL_SB_STEPSIZE) + longjmp(nud->jb, 1); + + nud->steps -= NHL_SB_STEPSIZE; + nud->statctr++; +} +#endif + +staticfn lua_State * +nhlL_newstate(nhl_sandbox_info *sbi, const char *name) +{ + nhl_user_data *nud = 0; + + if (sbi->memlimit || sbi->steps || sbi->perpcall) { + nud = nhl_alloc(NULL, NULL, 0, sizeof (struct nhl_user_data)); + if (!nud) + return 0; + nud->L = NULL; + nud->memlimit = sbi->memlimit; + nud->perpcall = 0; /* set up below, if needed */ + nud->steps = 0; + nud->osteps = 0; + nud->flags = sbi->flags; /* save reporting flags */ + nud->statctr = 0; + + if (name) { + nud->name = name; + } + nud->sid = ++gl.lua_sid; + } + + lua_State *L = lua_newstate(nhl_alloc, nud +#if LUA_VERSION_NUM >= 505 + , 0 +#endif + ); + if (!L) + panic("NULL lua_newstate"); + + if(nud) nud->L = L; + lua_atpanic(L, nhl_panic); +#if LUA_VERSION_NUM == 504 + lua_setwarnf(L, nhl_warn, L); +#endif + +#ifdef NHL_SANDBOX + if (nud && (sbi->steps || sbi->perpcall)) { + if (sbi->steps && sbi->perpcall) + impossible("steps and perpcall both non-zero"); + if (sbi->perpcall) { + nud->perpcall = sbi->perpcall; + } else { + nud->steps = sbi->steps; + nud->osteps = sbi->steps; + } + lua_sethook(L, nhl_hookfn, LUA_MASKCOUNT, NHL_SB_STEPSIZE); + } +#endif + + return L; +} + +#endif /* !SFCTOOL */ + +/* +(See end of comment for conclusion.) +to make packages safe, we need something like: + if setuid/setgid (but does NH drop privs before we can check? TBD) + unsetenv LUA_CPATH, LUA_CPATH_5_4 (and this needs to change with + version) maybe more + luaopen_package calls getenv + unsetenv(LUA_PATH_VAR) + unsetenv(LUA_CPATH_VAR) + unsetenv(LUA_PATH_VAR LUA_VERSUFFIX) + unsetenv(LUA_CPATH_VAR LUA_VERSUFFIX) + package.config + oackage[fieldname] = path + NB: LUA_PATH_DEFAULT and LUA_CPATH_DEFAULT must be safe + or we must setenv LUA_PATH_VAR and LUA_CPATH_VAR to something + safe + or we could just clean out the searchers table? + package.searchers[preload,Lua,C,Croot] +also, can setting package.path to something odd get Lua to load files + it shouldn't? (see docs package.searchers) +set (and disallow changing) package.cpath (etc?) +loadlib.c: + lsys_load -> dlopen Kill with undef LUA_USE_DLOPEN LUA_DL_DLL + searchpath -> readable -> fopen + <- ll_searchpath + <- findfile <- {searchers C, Croot, Lua} +Probably the best thing to do is replace G.require with our own function +that does whatever it is we need and completely ignore the package library. +*/ +/* +TODO: +docs +unfinished functionality & design +commit, cleanup, commit with SHA1 of full code version +BUT how do we compact the current history? + new branch, then compress there +XXX +*/ + +/*nhlua.c*/ diff --git a/src/nhmd4.c b/src/nhmd4.c new file mode 100644 index 000000000..580d8e49f --- /dev/null +++ b/src/nhmd4.c @@ -0,0 +1,298 @@ +/* NetHack 5.0 nhmd4.c $NHDT-Date: 1708811387 2024/02/24 21:49:47 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.0 $ */ +/*-Copyright (c) Kenneth Lorber, Kensington, Maryland, 2024 */ +/* NetHack may be freely redistributed. See license for details. */ + +/* + * Usage is to try to match traceback data with the instance of the + * program which produced that, not for security related purposes. + * + * Derived from: + */ +/* + * MD4 (RFC-1320) message digest. + * Modified from MD5 code by Andrey Panin + * + * Written by Solar Designer in 2001, and placed in + * the public domain. There's absolutely no warranty. + * + * This differs from Colin Plumb's older public domain implementation in + * that no 32-bit integer data type is required, there's no compile-time + * endianness configuration, and the function prototypes match OpenSSL's. + * The primary goals are portability and ease of use. + * + * This implementation is meant to be fast, but not as fast as possible. + * Some known optimizations are not included to reduce source code size + * and avoid compile-time configuration. + */ +#include "hack.h" +#ifdef CRASHREPORT + +#include "nhmd4.h" + +staticfn const unsigned char *nhmd4_body(struct nhmd4_context *, + const unsigned char *, size_t); + +/* Avoid a conflict from a Lua header */ +#ifdef G +#undef G +#endif + +/* + * The basic MD4 functions. + */ +#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) + +/* + * STEP: the MD4 transformation used for all four rounds. + * (Joining two expressions with the comma operator provides a sequence + * point. C89/C90 and later guarantee that the first will be fully complete + * before the second starts, making both assignments to 'a' be well defined.) + */ +#define STEP(f, a, b, c, d, x, s) \ + (((a) += f((b), (c), (d)) + (x)), \ + ((a) = ((a) << (s)) | ((a) >> (32 - (s))))) + +/* + * SET reads 4 input bytes in little-endian byte order and stores them + * in a properly aligned word in host byte order. + * + * The check for little-endian architectures which tolerate unaligned + * memory accesses is just an optimization. Nothing will break if it + * doesn't work. + */ +#if defined(__i386__) || defined(__x86_64__) +#define SET(n) (*(const quint32 *) &ptr[(n) * 4]) +#define GET(n) SET(n) +#else +#define SET(n) \ + (ctx->block[(n)] = \ + ((quint32) ptr[(n) * 4] \ + | ((quint32) ptr[(n) * 4 + 1] << 8) \ + | ((quint32) ptr[(n) * 4 + 2] << 16) \ + | ((quint32) ptr[(n) * 4 + 3] << 24))) +#define GET(n) (ctx->block[(n)]) +#endif + +/* + * This processes one or more 64-byte data blocks, but does NOT update + * the bit counters. There're no alignment requirements. + */ +staticfn const unsigned char * +nhmd4_body( + struct nhmd4_context *ctx, + const unsigned char *data, + size_t size) +{ + const unsigned char *ptr; + quint32 a, b, c, d; + quint32 saved_a, saved_b, saved_c, saved_d; + + ptr = data; + + a = ctx->a; + b = ctx->b; + c = ctx->c; + d = ctx->d; + + do { + saved_a = a; + saved_b = b; + saved_c = c; + saved_d = d; + +/* Round 1 */ + STEP(F, a, b, c, d, SET( 0), 3); + STEP(F, d, a, b, c, SET( 1), 7); + STEP(F, c, d, a, b, SET( 2), 11); + STEP(F, b, c, d, a, SET( 3), 19); + + STEP(F, a, b, c, d, SET( 4), 3); + STEP(F, d, a, b, c, SET( 5), 7); + STEP(F, c, d, a, b, SET( 6), 11); + STEP(F, b, c, d, a, SET( 7), 19); + + STEP(F, a, b, c, d, SET( 8), 3); + STEP(F, d, a, b, c, SET( 9), 7); + STEP(F, c, d, a, b, SET(10), 11); + STEP(F, b, c, d, a, SET(11), 19); + + STEP(F, a, b, c, d, SET(12), 3); + STEP(F, d, a, b, c, SET(13), 7); + STEP(F, c, d, a, b, SET(14), 11); + STEP(F, b, c, d, a, SET(15), 19); +/* Round 2 */ + STEP(G, a, b, c, d, GET( 0) + 0x5A827999, 3); + STEP(G, d, a, b, c, GET( 4) + 0x5A827999, 5); + STEP(G, c, d, a, b, GET( 8) + 0x5A827999, 9); + STEP(G, b, c, d, a, GET(12) + 0x5A827999, 13); + + STEP(G, a, b, c, d, GET( 1) + 0x5A827999, 3); + STEP(G, d, a, b, c, GET( 5) + 0x5A827999, 5); + STEP(G, c, d, a, b, GET( 9) + 0x5A827999, 9); + STEP(G, b, c, d, a, GET(13) + 0x5A827999, 13); + + STEP(G, a, b, c, d, GET( 2) + 0x5A827999, 3); + STEP(G, d, a, b, c, GET( 6) + 0x5A827999, 5); + STEP(G, c, d, a, b, GET(10) + 0x5A827999, 9); + STEP(G, b, c, d, a, GET(14) + 0x5A827999, 13); + + STEP(G, a, b, c, d, GET( 3) + 0x5A827999, 3); + STEP(G, d, a, b, c, GET( 7) + 0x5A827999, 5); + STEP(G, c, d, a, b, GET(11) + 0x5A827999, 9); + STEP(G, b, c, d, a, GET(15) + 0x5A827999, 13); +/* Round 3 */ + STEP(H, a, b, c, d, GET( 0) + 0x6ED9EBA1, 3); + STEP(H, d, a, b, c, GET( 8) + 0x6ED9EBA1, 9); + STEP(H, c, d, a, b, GET( 4) + 0x6ED9EBA1, 11); + STEP(H, b, c, d, a, GET(12) + 0x6ED9EBA1, 15); + + STEP(H, a, b, c, d, GET( 2) + 0x6ED9EBA1, 3); + STEP(H, d, a, b, c, GET(10) + 0x6ED9EBA1, 9); + STEP(H, c, d, a, b, GET( 6) + 0x6ED9EBA1, 11); + STEP(H, b, c, d, a, GET(14) + 0x6ED9EBA1, 15); + + STEP(H, a, b, c, d, GET( 1) + 0x6ED9EBA1, 3); + STEP(H, d, a, b, c, GET( 9) + 0x6ED9EBA1, 9); + STEP(H, c, d, a, b, GET( 5) + 0x6ED9EBA1, 11); + STEP(H, b, c, d, a, GET(13) + 0x6ED9EBA1, 15); + + STEP(H, a, b, c, d, GET( 3) + 0x6ED9EBA1, 3); + STEP(H, d, a, b, c, GET(11) + 0x6ED9EBA1, 9); + STEP(H, c, d, a, b, GET( 7) + 0x6ED9EBA1, 11); + STEP(H, b, c, d, a, GET(15) + 0x6ED9EBA1, 15); + + a += saved_a; + b += saved_b; + c += saved_c; + d += saved_d; + + ptr += 64; + } while (size -= 64); + + ctx->a = a; + ctx->b = b; + ctx->c = c; + ctx->d = d; + + return ptr; +} + +void +nhmd4_init( + struct nhmd4_context *ctx) +{ + ctx->a = 0x67452301; + ctx->b = 0xefcdab89; + ctx->c = 0x98badcfe; + ctx->d = 0x10325476; + + ctx->lo = 0; + ctx->hi = 0; +} + +void +nhmd4_update( + struct nhmd4_context *ctx, + const unsigned char *data, + size_t size) +{ + /* @UNSAFE */ + quint32 saved_lo; + unsigned long used, free; + + saved_lo = ctx->lo; + if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) + ctx->hi++; + ctx->hi += (quint32)(size >> 29); + + used = saved_lo & 0x3f; + + if (used) { + free = 64 - used; + + if (size < free) { + memcpy(&ctx->buffer[used], data, size); + return; + } + + memcpy(&ctx->buffer[used], data, free); + data = (const unsigned char *) data + free; + size -= free; + nhmd4_body(ctx, ctx->buffer, 64); + } + + if (size >= 64) { + data = nhmd4_body(ctx, data, size & ~0x3fUL); + size &= 0x3fUL; + } + + memcpy(ctx->buffer, data, size); +} + +void +nhmd4_final( + struct nhmd4_context *ctx, + unsigned char result[NHMD4_RESULTLEN]) +{ + /* @UNSAFE */ + unsigned long used, free; + + used = ctx->lo & 0x3fUL; + + ctx->buffer[used++] = 0x80; + + free = 64 - used; + + if (free < 8) { + memset(&ctx->buffer[used], 0, free); + nhmd4_body(ctx, ctx->buffer, 64); + used = 0; + free = 64; + } + + memset(&ctx->buffer[used], 0, free - 8); + + ctx->lo <<= 3; + ctx->buffer[56] = ctx->lo; + ctx->buffer[57] = ctx->lo >> 8; + ctx->buffer[58] = ctx->lo >> 16; + ctx->buffer[59] = ctx->lo >> 24; + ctx->buffer[60] = ctx->hi; + ctx->buffer[61] = ctx->hi >> 8; + ctx->buffer[62] = ctx->hi >> 16; + ctx->buffer[63] = ctx->hi >> 24; + + nhmd4_body(ctx, ctx->buffer, 64); + + result[0] = ctx->a; + result[1] = ctx->a >> 8; + result[2] = ctx->a >> 16; + result[3] = ctx->a >> 24; + result[4] = ctx->b; + result[5] = ctx->b >> 8; + result[6] = ctx->b >> 16; + result[7] = ctx->b >> 24; + result[8] = ctx->c; + result[9] = ctx->c >> 8; + result[10] = ctx->c >> 16; + result[11] = ctx->c >> 24; + result[12] = ctx->d; + result[13] = ctx->d >> 8; + result[14] = ctx->d >> 16; + result[15] = ctx->d >> 24; + + memset(ctx, 0, sizeof *ctx); +} + +#undef F +#undef G +#undef H +#undef STEP +#undef SET +#undef GET + +#endif /* CRASHREPORT */ + +/*nhmd4.c*/ diff --git a/src/nlernd.c b/src/nlernd.c index 73892c35f..e119f1bc8 100644 --- a/src/nlernd.c +++ b/src/nlernd.c @@ -4,32 +4,32 @@ #include #include -/* See rng.c. */ +/* Must match rnglist_t in rnd.c. */ struct rnglist_t { - int FDECL((*fn), (int) ); + int (*fn)(int); boolean init; isaac64_ctx rng_state; }; extern struct rnglist_t rnglist[]; -extern int FDECL(whichrng, (int FDECL((*fn), (int) ))); +extern int whichrng(int (*fn)(int)); -/* See hacklib.c. */ -extern int FDECL(set_random, (unsigned long, int FDECL((*fn), (int) ))); +/* See rnd.c. */ +extern int set_random(unsigned long, int (*fn)(int)); /* An appropriate version of this must always be provided in port-specific code somewhere. It returns a number suitable as seed for the random number generator */ -extern unsigned long NDECL(sys_random_seed); +extern unsigned long sys_random_seed(void); /* NLE settings contains the initial RNG seeds */ extern nle_settings settings; /* * Initializes the random number generator. - * Originally in hacklib.c. + * Originally in rnd.c. */ void -init_random(int FDECL((*fn), (int) )) +init_random(int (*fn)(int)) { if (settings.initial_seeds.use_init_seeds) { set_random(settings.initial_seeds.seeds[whichrng(fn)], fn); diff --git a/src/o_init.c b/src/o_init.c index 5db3c416b..f6ede4dab 100644 --- a/src/o_init.c +++ b/src/o_init.c @@ -1,23 +1,26 @@ -/* NetHack 3.6 o_init.c $NHDT-Date: 1674864731 2023/01/28 00:12:11 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.27 $ */ +/* NetHack 5.0 o_init.c $NHDT-Date: 1771216675 2026/02/15 20:37:55 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.101 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2011. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -#include "lev.h" /* save & restore info */ -STATIC_DCL void FDECL(setgemprobs, (d_level *)); -STATIC_DCL void FDECL(shuffle, (int, int, BOOLEAN_P)); -STATIC_DCL void NDECL(shuffle_all); -STATIC_DCL boolean FDECL(interesting_to_discover, (int)); -STATIC_DCL void FDECL(disco_append_typename, (char *, int)); -STATIC_DCL char *FDECL(oclass_to_name, (CHAR_P, char *)); - -static NEARDATA short disco[NUM_OBJECTS] = DUMMY; - -#ifdef USE_TILES -STATIC_DCL void NDECL(shuffle_tiles); -extern short glyph2tile[]; /* from tile.c */ +#ifndef SFCTOOL +staticfn void setgemprobs(d_level *); +staticfn void randomize_gem_colors(void); +staticfn void shuffle(int, int, boolean); +staticfn void shuffle_all(void); +staticfn int QSORTCALLBACK discovered_cmp(const genericptr, const genericptr); +staticfn char *sortloot_descr(int, char *); +staticfn char *disco_typename(int); +staticfn void disco_append_typename(char *, int); +staticfn void disco_fmt_uniq(int, char *outbuf) NONNULLARG2; +staticfn void disco_output_sorted(winid, char **, int, boolean); +staticfn char *oclass_to_name(char, char *); + +#ifdef TILES_IN_GLYPHMAP +extern glyph_map glyphmap[MAX_GLYPH]; +staticfn void shuffle_tiles(void); /* Shuffle tile assignments to match descriptions, so a red potion isn't * displayed with a blue tile and so on. @@ -28,51 +31,86 @@ extern short glyph2tile[]; /* from tile.c */ * is restored. So might as well do that the first time instead of writing * another routine. */ -STATIC_OVL void -shuffle_tiles() +staticfn void +shuffle_tiles(void) { int i; - short tmp_tilemap[NUM_OBJECTS]; - - for (i = 0; i < NUM_OBJECTS; i++) - tmp_tilemap[i] = glyph2tile[objects[i].oc_descr_idx + GLYPH_OBJ_OFF]; + short tmp_tilemap[2][NUM_OBJECTS]; - for (i = 0; i < NUM_OBJECTS; i++) - glyph2tile[i + GLYPH_OBJ_OFF] = tmp_tilemap[i]; + for (i = 0; i < NUM_OBJECTS; i++) { + tmp_tilemap[0][i] = glyphmap[objects[i].oc_descr_idx + + GLYPH_OBJ_OFF].tileidx; + tmp_tilemap[1][i] = glyphmap[objects[i].oc_descr_idx + + GLYPH_OBJ_PILETOP_OFF].tileidx; + } + for (i = 0; i < NUM_OBJECTS; i++) { + glyphmap[i + GLYPH_OBJ_OFF].tileidx = tmp_tilemap[0][i]; + glyphmap[i + GLYPH_OBJ_PILETOP_OFF].tileidx = tmp_tilemap[1][i]; + } } -#endif /* USE_TILES */ +#endif /* TILES_IN_GLYPHMAP */ -STATIC_OVL void -setgemprobs(dlev) -d_level *dlev; +staticfn void +setgemprobs(d_level *dlev) { - int j, first, lev; + int j, first, lev, sum = 0; if (dlev) lev = (ledger_no(dlev) > maxledgerno()) ? maxledgerno() : ledger_no(dlev); else lev = 0; - first = bases[GEM_CLASS]; + first = svb.bases[GEM_CLASS]; for (j = 0; j < 9 - lev / 3; j++) objects[first + j].oc_prob = 0; first += j; - if (first > LAST_GEM || objects[first].oc_class != GEM_CLASS + if (first > LAST_REAL_GEM || objects[first].oc_class != GEM_CLASS || OBJ_NAME(objects[first]) == (char *) 0) { raw_printf("Not enough gems? - first=%d j=%d LAST_GEM=%d", first, j, - LAST_GEM); + LAST_REAL_GEM); wait_synch(); } - for (j = first; j <= LAST_GEM; j++) - objects[j].oc_prob = (171 + j - first) / (LAST_GEM + 1 - first); + for (j = first; j <= LAST_REAL_GEM; j++) + objects[j].oc_prob = (171 + j - first) / (LAST_REAL_GEM + 1 - first); + + /* recompute GEM_CLASS total oc_prob - including rocks/stones */ + for (j = svb.bases[GEM_CLASS]; j < svb.bases[GEM_CLASS + 1]; j++) + sum += objects[j].oc_prob; + go.oclass_prob_totals[GEM_CLASS] = sum; +} + +/* some gems can have different colors */ +staticfn void +randomize_gem_colors(void) +{ +#define COPY_OBJ_DESCR(o_dst, o_src) \ + o_dst.oc_descr_idx = o_src.oc_descr_idx, o_dst.oc_color = o_src.oc_color + if (rn2(2)) { /* change turquoise from green to blue? */ + COPY_OBJ_DESCR(objects[TURQUOISE], objects[SAPPHIRE]); + } + if (rn2(2)) { /* change aquamarine from green to blue? */ + COPY_OBJ_DESCR(objects[AQUAMARINE], objects[SAPPHIRE]); + } + switch (rn2(4)) { /* change fluorite from violet? */ + case 0: + break; + case 1: /* blue */ + COPY_OBJ_DESCR(objects[FLUORITE], objects[SAPPHIRE]); + break; + case 2: /* white */ + COPY_OBJ_DESCR(objects[FLUORITE], objects[DIAMOND]); + break; + case 3: /* green */ + COPY_OBJ_DESCR(objects[FLUORITE], objects[EMERALD]); + break; + } +#undef COPY_OBJ_DESCR } /* shuffle descriptions on objects o_low to o_high */ -STATIC_OVL void -shuffle(o_low, o_high, domaterial) -int o_low, o_high; -boolean domaterial; +staticfn void +shuffle(int o_low, int o_high, boolean domaterial) { int i, j, num_to_shuffle; short sw; @@ -110,84 +148,127 @@ boolean domaterial; } void -init_objects() +init_objects(void) { - register int i, first, last, sum; - register char oclass; -#ifdef TEXTCOLOR -#define COPY_OBJ_DESCR(o_dst, o_src) \ - o_dst.oc_descr_idx = o_src.oc_descr_idx, o_dst.oc_color = o_src.oc_color -#else -#define COPY_OBJ_DESCR(o_dst, o_src) o_dst.oc_descr_idx = o_src.oc_descr_idx -#endif - - /* bug fix to prevent "initialization error" abort on Intel Xenix. - * reported by mikew@semike - */ - for (i = 0; i < MAXOCLASSES; i++) - bases[i] = 0; + int i, first, last, prevoclass; + char oclass; + + for (i = 0; i <= MAXOCLASSES; i++) { + svb.bases[i] = 0; + if (i > 0 && i < MAXOCLASSES && objects[i].oc_class != i) + panic( + "init_objects: class for generic object #%d doesn't match (%d)", + i, objects[i].oc_class); + } /* initialize object descriptions */ for (i = 0; i < NUM_OBJECTS; i++) objects[i].oc_name_idx = objects[i].oc_descr_idx = i; /* init base; if probs given check that they add up to 1000, otherwise compute probs */ - first = 0; + first = MAXOCLASSES; + prevoclass = -1; while (first < NUM_OBJECTS) { oclass = objects[first].oc_class; + /* + * objects[] sanity check: must be in ascending oc_class order to + * be able to use bases[class+1]-1 for the end of a class's range. + * Also catches a non-contiguous class because reverting to any + * earlier class would involve switching back to a lower class + * number after having moved on to one or more other classes. + */ + if ((int) oclass < prevoclass) + panic("objects[%d] class #%d not in order!", first, oclass); + last = first + 1; while (last < NUM_OBJECTS && objects[last].oc_class == oclass) last++; - bases[(int) oclass] = first; + svb.bases[(int) oclass] = first; if (oclass == GEM_CLASS) { setgemprobs((d_level *) 0); - - if (rn2(2)) { /* change turquoise from green to blue? */ - COPY_OBJ_DESCR(objects[TURQUOISE], objects[SAPPHIRE]); - } - if (rn2(2)) { /* change aquamarine from green to blue? */ - COPY_OBJ_DESCR(objects[AQUAMARINE], objects[SAPPHIRE]); - } - switch (rn2(4)) { /* change fluorite from violet? */ - case 0: - break; - case 1: /* blue */ - COPY_OBJ_DESCR(objects[FLUORITE], objects[SAPPHIRE]); - break; - case 2: /* white */ - COPY_OBJ_DESCR(objects[FLUORITE], objects[DIAMOND]); - break; - case 3: /* green */ - COPY_OBJ_DESCR(objects[FLUORITE], objects[EMERALD]); - break; - } - } - check: - sum = 0; - for (i = first; i < last; i++) - sum += objects[i].oc_prob; - if (sum == 0) { - for (i = first; i < last; i++) - objects[i].oc_prob = (1000 + i - first) / (last - first); - goto check; + randomize_gem_colors(); } - if (sum != 1000) - error("init-prob error for class %d (%d%%)", oclass, sum); first = last; + prevoclass = (int) oclass; + } + /* extra entry allows deriving the range of a class via + bases[class] through bases[class+1]-1 for all classes + (except for ILLOBJ_CLASS which is separated from WEAPON_CLASS + by generic objects); second extra entry is to prevent an + unexplained crash in doclassdisco(), where the code ended up + attempting to process non-existent class MAXOCLASSES; the + [MAXOCLASSES+1] element gives that non-class 0 objects + when traversing objects[] from bases[X] through bases[X+1]-1 */ + svb.bases[MAXOCLASSES] = svb.bases[MAXOCLASSES + 1] = NUM_OBJECTS; + /* hypothetically someone might remove all objects of some class, + or be adding a new class and not populated it yet, leaving gaps + in bases[]; guarantee that there are no such gaps */ + for (last = MAXOCLASSES - 1; last >= 0; --last) + if (!svb.bases[last]) + svb.bases[last] = svb.bases[last + 1]; + + /* check objects[].oc_name_known */ + for (i = MAXOCLASSES; i < NUM_OBJECTS; ++i) { + int nmkn = objects[i].oc_name_known != 0; + + if (!OBJ_DESCR(objects[i]) ^ nmkn) { + if (iflags.sanity_check) { + impossible( + "obj #%d (%s) name is %s despite%s alternate description", + i, OBJ_NAME(objects[i]), + nmkn ? "pre-known" : "not known", + nmkn ? "" : " no"); + } + /* repair the mistake and keep going */ + objects[i].oc_name_known = nmkn ? 0 : 1; + } } + /* compute oclass_prob_totals */ + init_oclass_probs(); + /* shuffle descriptions */ shuffle_all(); -#ifdef USE_TILES +#ifdef TILES_IN_GLYPHMAP shuffle_tiles(); #endif objects[WAN_NOTHING].oc_dir = rn2(2) ? NODIR : IMMEDIATE; } +/* Compute the total probability of each object class. + * Assumes svb.bases[] has already been set. */ +void +init_oclass_probs(void) +{ + int i; + short sum; + int oclass; + for (oclass = 0; oclass < MAXOCLASSES; ++oclass) { + sum = 0; + /* note: for ILLOBJ_CLASS, bases[oclass+1]-1 isn't the last item + in the class; but all the generic items have probability 0 so + adding them to 'sum' has no impact */ + for (i = svb.bases[oclass]; i < svb.bases[oclass + 1]; ++i) { + sum += objects[i].oc_prob; + } + if (sum <= 0 && oclass != ILLOBJ_CLASS + && svb.bases[oclass] != svb.bases[oclass + 1]) { + impossible("%s (%d) probability total for oclass %d", + !sum ? "zero" : "negative", sum, oclass); + /* gracefully fail by setting all members of this class to 1 */ + for (i = svb.bases[oclass]; i < svb.bases[oclass + 1]; ++i) { + objects[i].oc_prob = 1; + sum++; + } + } + go.oclass_prob_totals[oclass] = sum; + } +} + /* retrieve the range of objects that otyp shares descriptions with */ void -obj_shuffle_range(otyp, lo_p, hi_p) -int otyp; /* input: representative item */ -int *lo_p, *hi_p; /* output: range that item belongs among */ +obj_shuffle_range( + int otyp, /* input: representative item */ + int *lo_p, int *hi_p) /* output: range that item belongs among */ { int i, ocls = objects[otyp].oc_class; @@ -207,14 +288,14 @@ int *lo_p, *hi_p; /* output: range that item belongs among */ break; case POTION_CLASS: /* potion of water has the only fixed description */ - *lo_p = bases[POTION_CLASS]; + *lo_p = svb.bases[POTION_CLASS]; *hi_p = POT_WATER - 1; break; case AMULET_CLASS: case SCROLL_CLASS: case SPBOOK_CLASS: /* exclude non-magic types and also unique ones */ - *lo_p = bases[ocls]; + *lo_p = svb.bases[ocls]; for (i = *lo_p; objects[i].oc_class == ocls; i++) if (objects[i].oc_unique || !objects[i].oc_magic) break; @@ -224,10 +305,8 @@ int *lo_p, *hi_p; /* output: range that item belongs among */ case WAND_CLASS: case VENOM_CLASS: /* entire class */ - *lo_p = bases[ocls]; - for (i = *lo_p; objects[i].oc_class == ocls; i++) - continue; - *hi_p = i - 1; + *lo_p = svb.bases[ocls]; + *hi_p = svb.bases[ocls + 1] - 1; break; } @@ -239,8 +318,8 @@ int *lo_p, *hi_p; /* output: range that item belongs among */ } /* randomize object descriptions */ -STATIC_OVL void -shuffle_all() +staticfn void +shuffle_all(void) { /* entire classes; obj_shuffle_range() handles their exceptions */ static char shuffle_classes[] = { @@ -255,7 +334,8 @@ shuffle_all() /* do whole classes (amulets, &c) */ for (idx = 0; idx < SIZE(shuffle_classes); idx++) { - obj_shuffle_range(bases[(int) shuffle_classes[idx]], &first, &last); + obj_shuffle_range(svb.bases[(int) shuffle_classes[idx]], + &first, &last); shuffle(first, last, TRUE); } /* do type ranges (helms, &c) */ @@ -266,246 +346,535 @@ shuffle_all() return; } -/* find the object index for snow boots; used [once] by slippery ice code */ -int -find_skates() +/* Return TRUE if the provided string matches the unidentified description of + * the provided object. */ +boolean +objdescr_is(struct obj *obj, const char *descr) { - register int i; - register const char *s; + const char *objdescr; - for (i = SPEED_BOOTS; i <= LEVITATION_BOOTS; i++) - if ((s = OBJ_DESCR(objects[i])) != 0 && !strcmp(s, "snow boots")) - return i; + if (!obj) { + impossible("objdescr_is: null obj"); + return FALSE; + } - impossible("snow boots not found?"); - return -1; /* not 0, or caller would try again each move */ + objdescr = OBJ_DESCR(objects[obj->otyp]); + if (!objdescr) + return FALSE; /* no obj description, no match */ + return !strcmp(objdescr, descr); } /* level dependent initialization */ void -oinit() +oinit(void) { setgemprobs(&u.uz); } void -savenames(fd, mode) -int fd, mode; +savenames(NHFILE *nhfp) { - register int i; + int i; unsigned int len; - if (perform_bwrite(mode)) { - bwrite(fd, (genericptr_t) bases, sizeof bases); - bwrite(fd, (genericptr_t) disco, sizeof disco); - bwrite(fd, (genericptr_t) objects, - sizeof(struct objclass) * NUM_OBJECTS); + if (update_file(nhfp)) { + for (i = 0; i < (MAXOCLASSES + 2); ++i) { + Sfo_int(nhfp, &svb.bases[i], "names-bases"); + } + for (i = 0; i < NUM_OBJECTS; ++i) { + Sfo_short(nhfp, &svd.disco[i], "names-disco"); + } + for (i = 0; i < NUM_OBJECTS; ++i) { + Sfo_objclass(nhfp, &objects[i], "names-objclass"); + } } /* as long as we use only one version of Hack we need not save oc_name and oc_descr, but we must save oc_uname for all objects */ for (i = 0; i < NUM_OBJECTS; i++) if (objects[i].oc_uname) { - if (perform_bwrite(mode)) { - len = strlen(objects[i].oc_uname) + 1; - bwrite(fd, (genericptr_t) &len, sizeof len); - bwrite(fd, (genericptr_t) objects[i].oc_uname, len); + if (update_file(nhfp)) { + len = Strlen(objects[i].oc_uname) + 1; + Sfo_unsigned(nhfp, &len, "names-len"); + Sfo_char(nhfp, objects[i].oc_uname, "names-oc_uname", + (int) len); } - if (release_data(mode)) { + if (release_data(nhfp)) { free((genericptr_t) objects[i].oc_uname); objects[i].oc_uname = 0; } } } +#endif /* !SFCTOOL */ void -restnames(fd) -register int fd; +restnames(NHFILE *nhfp) { - register int i; - unsigned int len; + int i; + unsigned int len = 0; - mread(fd, (genericptr_t) bases, sizeof bases); - mread(fd, (genericptr_t) disco, sizeof disco); - mread(fd, (genericptr_t) objects, sizeof(struct objclass) * NUM_OBJECTS); - for (i = 0; i < NUM_OBJECTS; i++) + for (i = 0; i < (MAXOCLASSES + 2); ++i) { + Sfi_int(nhfp, &svb.bases[i], "names-bases"); + } + for (i = 0; i < NUM_OBJECTS; ++i) { + Sfi_short(nhfp, &svd.disco[i], "names-disco"); + } + for (i = 0; i < NUM_OBJECTS; ++i) { + Sfi_objclass(nhfp, &objects[i], "names-objclass"); + } + for (i = 0; i < NUM_OBJECTS; i++) { if (objects[i].oc_uname) { - mread(fd, (genericptr_t) &len, sizeof len); + Sfi_unsigned(nhfp, &len, "names-len"); objects[i].oc_uname = (char *) alloc(len); - mread(fd, (genericptr_t) objects[i].oc_uname, len); + Sfi_char(nhfp, objects[i].oc_uname, "names-oc_uname", (int) len); } -#ifdef USE_TILES + } +#ifndef SFCTOOL +#ifdef TILES_IN_GLYPHMAP shuffle_tiles(); #endif +#endif +} + +#ifndef SFCTOOL +/* make the object dknown and mark it as encountered */ +void +observe_object(struct obj *obj) +{ + int oindx = obj->otyp; + + /* skip for generic objects and for STRANGE_OBJECT */ + if (oindx >= FIRST_OBJECT && !Hallucination) { + obj->dknown = 1; + discover_object(oindx, FALSE, TRUE, FALSE); + } } void -discover_object(oindx, mark_as_known, credit_hero) -register int oindx; -boolean mark_as_known; -boolean credit_hero; +discover_object( + int oindx, /* type of object */ + boolean mark_as_known, /* discover the type */ + boolean mark_as_encountered, /* mark the type as having been seen/felt */ + boolean credit_hero) /* exercise wisdom */ { - if (!objects[oindx].oc_name_known) { - register int dindx, acls = objects[oindx].oc_class; + if (oindx < FIRST_OBJECT) /* don't discover generic objects */ + return; + + if ((!objects[oindx].oc_name_known && mark_as_known) + || (!objects[oindx].oc_encountered && mark_as_encountered) + || (Role_if(PM_SAMURAI) + && Japanese_item_name(oindx, (const char *) 0))) { + int dindx, acls = objects[oindx].oc_class; /* Loop thru disco[] 'til we find the target (which may have been uname'd) or the next open slot; one or the other will be found - before we reach the next class... - */ - for (dindx = bases[acls]; disco[dindx] != 0; dindx++) - if (disco[dindx] == oindx) + before we reach the next class... */ + for (dindx = svb.bases[acls]; svd.disco[dindx] != 0; dindx++) + if (svd.disco[dindx] == oindx) break; - disco[dindx] = oindx; + svd.disco[dindx] = oindx; + + if (mark_as_encountered) + objects[oindx].oc_encountered = 1; - if (mark_as_known) { + if (!objects[oindx].oc_name_known && mark_as_known) { objects[oindx].oc_name_known = 1; if (credit_hero) exercise(A_WIS, TRUE); - } - /* moves==1L => initial inventory, gameover => final disclosure */ - if (moves > 1L && !program_state.gameover) { - if (objects[oindx].oc_class == GEM_CLASS) - gem_learned(oindx); /* could affect price of unpaid gems */ - update_inventory(); + + /* !in_moveloop => initial inventory, + gameover => final disclosure */ + if (program_state.in_moveloop && !program_state.gameover) { + if (objects[oindx].oc_class == GEM_CLASS) + gem_learned(oindx); /* could affect price of unpaid gems */ + update_inventory(); + } } } } /* if a class name has been cleared, we may need to purge it from disco[] */ void -undiscover_object(oindx) -register int oindx; +undiscover_object(int oindx) { - if (!objects[oindx].oc_name_known) { - register int dindx, acls = objects[oindx].oc_class; - register boolean found = FALSE; + if (!objects[oindx].oc_name_known && !objects[oindx].oc_encountered) { + int dindx, acls = objects[oindx].oc_class; + boolean found = FALSE; /* find the object; shift those behind it forward one slot */ - for (dindx = bases[acls]; dindx < NUM_OBJECTS && disco[dindx] != 0 - && objects[dindx].oc_class == acls; + for (dindx = svb.bases[acls]; + dindx < NUM_OBJECTS && svd.disco[dindx] != 0 + && objects[dindx].oc_class == acls; dindx++) if (found) - disco[dindx - 1] = disco[dindx]; - else if (disco[dindx] == oindx) + svd.disco[dindx - 1] = svd.disco[dindx]; + else if (svd.disco[dindx] == oindx) found = TRUE; /* clear last slot */ if (found) - disco[dindx - 1] = 0; + svd.disco[dindx - 1] = 0; else impossible("named object not in disco"); if (objects[oindx].oc_class == GEM_CLASS) gem_learned(oindx); /* ok, it's actually been unlearned */ - update_inventory(); } } -STATIC_OVL boolean -interesting_to_discover(i) -register int i; +boolean +interesting_to_discover(int i) { - /* Pre-discovered objects are now printed with a '*' */ + /* most players who don't speak Japanese manage to figure out what + gunyoki, osaku, and so forth mean, but treat them as pre-discovered + to be disclosed by '\' */ + if (Role_if(PM_SAMURAI) && Japanese_item_name(i, (const char *) 0)) + return TRUE; + + /* Objects that were discovered without encountering them are now printed + with a '*' */ return (boolean) (objects[i].oc_uname != (char *) 0 - || (objects[i].oc_name_known + || ((objects[i].oc_name_known + || objects[i].oc_encountered) && OBJ_DESCR(objects[i]) != (char *) 0)); } /* items that should stand out once they're known */ -static short uniq_objs[] = { - AMULET_OF_YENDOR, SPE_BOOK_OF_THE_DEAD, CANDELABRUM_OF_INVOCATION, +static const short uniq_objs[] = { + AMULET_OF_YENDOR, + /* same order as major oracularity; alphabetical when fully IDed */ BELL_OF_OPENING, + SPE_BOOK_OF_THE_DEAD, + CANDELABRUM_OF_INVOCATION, }; -/* append typename(dis) to buf[], possibly truncating in the process */ -STATIC_OVL void -disco_append_typename(buf, dis) -char *buf; -int dis; +/* discoveries qsort comparison function */ +staticfn int QSORTCALLBACK +discovered_cmp(const genericptr v1, const genericptr v2) { - unsigned len = (unsigned) strlen(buf); - char *p, *typnm = obj_typename(dis); + const char *s1 = *(const char **) v1; + const char *s2 = *(const char **) v2; + /* each element starts with "* " or " " but we don't sort by those */ + int res = strcmpi(s1 + 2, s2 + 2); + + if (res == 0) { + ; /* no tie-breaker needed */ + } + return res; +} - if (len + (unsigned) strlen(typnm) < BUFSZ) { +staticfn char * +sortloot_descr(int otyp, char *outbuf) +{ + Loot sl_cookie; + struct obj o; + + o = cg.zeroobj; + o.otyp = otyp; + o.oclass = objects[otyp].oc_class; + o.dknown = 1; /* not observe_object, this isn't a real object */ + o.known = (objects[otyp].oc_name_known || !objects[otyp].oc_uses_known) + ? 1 : 0; + o.corpsenm = NON_PM; /* suppress statue and figurine details */ + /* but suppressing fruit details leads to "bad fruit #0" */ + if (otyp == SLIME_MOLD) + o.spe = svc.context.current_fruit; + + (void) memset((genericptr_t) &sl_cookie, 0, sizeof sl_cookie); + sl_cookie.obj = (struct obj *) 0; + sl_cookie.str = (char *) 0; + + loot_classify(&sl_cookie, &o); + Sprintf(outbuf, "%02d%02d%1d ", + sl_cookie.orderclass, sl_cookie.subclass, sl_cookie.disco); + return outbuf; +} +#endif /* !SFCTOOL */ + +#define DISCO_BYCLASS 0 /* by discovery order within each class */ +#define DISCO_SORTLOOT 1 /* by discovery order within each subclass */ +#define DISCO_ALPHABYCLASS 2 /* alphabetized within each class */ +#define DISCO_ALPHABETIZED 3 /* alphabetized across all classes */ +/* also used in options.c (optfn_sortdiscoveries) */ +static const char disco_order_let[] = "osca"; +static const char *const disco_orders_descr[] = { + "by order of discovery within each class", + "sortloot order (by class with some sub-class groupings)", + "alphabetical within each class", + "alphabetical across all classes", + (char *) 0 +}; + +#ifndef SFCTOOL + +int +choose_disco_sort( + int mode) /* 0 => 'O' cmd, 1 => full discoveries; 2 => class disco */ +{ + winid tmpwin; + menu_item *selected; + anything any; + int i, n, choice; + int clr = NO_COLOR; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; /* zero out all bits */ + for (i = 0; disco_orders_descr[i]; ++i) { + any.a_int = disco_order_let[i]; + add_menu(tmpwin, &nul_glyphinfo, &any, (char) any.a_int, + 0, ATR_NONE, clr, + disco_orders_descr[i], + (disco_order_let[i] == flags.discosort) + ? MENU_ITEMFLAGS_SELECTED + : MENU_ITEMFLAGS_NONE); + } + if (mode == 2) { + /* called via 'm `' where full alphabetize doesn't make sense + (only showing one class so can't span all classes) but the + chosen sort will stick and also apply to '\' usage */ + add_menu_str(tmpwin, ""); + add_menu_str(tmpwin, + "Note: full alphabetical and alphabetical within class"); + add_menu_str(tmpwin, + " are equivalent for single class discovery, but"); + add_menu_str(tmpwin, + " will matter for future use of total discoveries."); + } + end_menu(tmpwin, "Ordering of discoveries"); + + n = select_menu(tmpwin, PICK_ONE, &selected); + destroy_nhwindow(tmpwin); + if (n > 0) { + choice = selected[0].item.a_int; + /* skip preselected entry if we have more than one item chosen */ + if (n > 1 && choice == (int) flags.discosort) + choice = selected[1].item.a_int; + free((genericptr_t) selected); + flags.discosort = choice; + } + return n; +} + +/* augment obj_typename() with explanation of Japanese item names */ +staticfn char * +disco_typename(int otyp) +{ + char *result = obj_typename(otyp); + + if (Role_if(PM_SAMURAI) && Japanese_item_name(otyp, (const char *) 0)) { + char buf[BUFSZ]; + const char *actualn = (((otyp != MAGIC_HARP && otyp != WOODEN_HARP) + || objects[otyp].oc_name_known) + ? OBJ_NAME(objects[otyp]) + /* undiscovered harp (since wooden harp is + non-magic so pre-discovered, only applies + to magic harp and will only be seen if + magic harp has been 'called' something) */ + : "harp"); + + if (!actualn) { /* won't happen; used to pacify static analyzer */ + ; + } else if (strstri(result, " called")) { + Sprintf(buf, " [%s] called", actualn); + (void) strsubst(result, " called", buf); + } else if (strstri(result, " (")) { + Sprintf(buf, " [%s] (", actualn); + (void) strsubst(result, " (", buf); + } else { + Sprintf(eos(result), " [%s]", actualn); + } + } + return result; +} + +/* append typename(dis) to buf[], possibly truncating in the process; + also append price quote information if it fits */ +staticfn void +disco_append_typename(char *buf, int dis) +{ + size_t len = strlen(buf); + char *p, *typnm = disco_typename(dis); + size_t typnm_len = strlen(typnm); + char *eos; + + if (len + typnm_len < BUFSZ) { /* ordinary */ Strcat(buf, typnm); - } else if ((p = rindex(typnm, '(')) != 0 - && p > typnm && p[-1] == ' ' && index(p, ')') != 0) { + eos = buf + len + typnm_len; + } else if ((p = strrchr(typnm, '(')) != 0 + && p > typnm && p[-1] == ' ' && strchr(p, ')') != 0) { /* typename() returned "really long user-applied name (actual type)" and we want to truncate from "really long user-applied name" while keeping " (actual type)" intact */ --p; /* back up to space in front of open paren */ (void) strncat(buf, typnm, BUFSZ - 1 - (len + (unsigned) strlen(p))); Strcat(buf, p); + eos = buf + strlen(buf); } else { /* unexpected; just truncate from end of typename */ (void) strncat(buf, typnm, BUFSZ - 1 - len); + eos = buf + strlen(buf); + } + + append_price_quote(buf, &eos, dis); +} + +/* minor fixup for Book of the Dead needed in more than one place */ +staticfn void +disco_fmt_uniq(int uidx, char *outbuf) +{ + Sprintf(outbuf, " %s", objects[uidx].oc_name_known + ? OBJ_NAME(objects[uidx]) + : OBJ_DESCR(objects[uidx])); + /* in the spellbooks section of main discoveries list, encountered + but not fully discovered Book of the Dead is shown as + "spellbook (papyrus)" like other encountered but not discovered books; + in the unique/relics section we want "papyrus spellbook" instead */ + if (!objects[uidx].oc_name_known + && objects[uidx].oc_class == SPBOOK_CLASS) + Strcat(outbuf, " spellbook"); +} + +/* sort and output sorted_lines to window and free the lines */ +staticfn void +disco_output_sorted( + winid tmpwin, + char **sorted_lines, int sorted_ct, + boolean lootsort) +{ + char *p; + int j; + + qsort(sorted_lines, sorted_ct, sizeof (char *), discovered_cmp); + for (j = 0; j < sorted_ct; ++j) { + p = sorted_lines[j]; + assert(p != NULL); /* pacify static analyzer */ + if (lootsort) { + p[6] = p[0]; /* '*' or ' ' */ + p += 6; + } + putstr(tmpwin, 0, p); + free(sorted_lines[j]), sorted_lines[j] = 0; } } -/* the '\' command - show discovered object types */ +/* the #known command - show discovered object types */ int -dodiscovered() /* free after Robert Viduya */ +dodiscovered(void) /* free after Robert Viduya */ { - register int i, dis; - int ct = 0; - char *s, oclass, prev_class, classes[MAXOCLASSES], buf[BUFSZ]; winid tmpwin; - - tmpwin = create_nhwindow(NHW_MENU); - putstr(tmpwin, 0, "Discoveries"); + char *s, oclass, prev_class, + classes[MAXOCLASSES], buf[BUFSZ], + *sorted_lines[NUM_OBJECTS]; /* overkill */ + const char *p; + int i, dis, ct, uniq_ct, arti_ct, sorted_ct, uidx; + long sortindx; // should be ptrdiff_t, but we don't require that exists + boolean alphabetized, alphabyclass, lootsort; + + if (!flags.discosort || !(p = strchr(disco_order_let, flags.discosort))) + flags.discosort = 'o'; + + if (iflags.menu_requested) { + if (choose_disco_sort(1) < 0) + return ECMD_OK; + } + alphabyclass = (flags.discosort == 'c'); + alphabetized = (flags.discosort == 'a' || alphabyclass); + lootsort = (flags.discosort == 's'); + sortindx = strchr(disco_order_let, flags.discosort) - disco_order_let; + + tmpwin = create_nhwindow(NHW_TEXT); + Sprintf(buf, "Discoveries, %s", disco_orders_descr[sortindx]); + putstr(tmpwin, 0, buf); putstr(tmpwin, 0, ""); - /* gather "unique objects" into a pseudo-class; note that they'll - also be displayed individually within their regular class */ - for (i = dis = 0; i < SIZE(uniq_objs); i++) - if (objects[uniq_objs[i]].oc_name_known) { + /* + * FIXME? + * relics and artifacts don't obey player's sort order even though + * the header line states that they're shown in such-and-such order. + */ + + /* gather "unique objects", also called "relics", into a pseudo-class; + they'll also be displayed individually within their regular class */ + uniq_ct = 0; + for (i = dis = 0; i < SIZE(uniq_objs); i++) { + uidx = uniq_objs[i]; + if (objects[uidx].oc_name_known + || (objects[uidx].oc_encountered && uidx != AMULET_OF_YENDOR)) { if (!dis++) - putstr(tmpwin, iflags.menu_headings, "Unique items"); - Sprintf(buf, " %s", OBJ_NAME(objects[uniq_objs[i]])); + putstr(tmpwin, iflags.menu_headings.attr, + "Unique items or Relics"); + ++uniq_ct; + disco_fmt_uniq(uidx, buf); putstr(tmpwin, 0, buf); - ++ct; } + } /* display any known artifacts as another pseudo-class */ - ct += disp_artifact_discoveries(tmpwin); + arti_ct = disp_artifact_discoveries(tmpwin); /* several classes are omitted from packorder; one is of interest here */ Strcpy(classes, flags.inv_order); - if (!index(classes, VENOM_CLASS)) + if (!strchr(classes, VENOM_CLASS)) (void) strkitten(classes, VENOM_CLASS); /* append char to string */ + ct = uniq_ct + arti_ct; + sorted_ct = 0; for (s = classes; *s; s++) { oclass = *s; prev_class = oclass + 1; /* forced different from oclass */ - for (i = bases[(int) oclass]; + for (i = svb.bases[(int) oclass]; i < NUM_OBJECTS && objects[i].oc_class == oclass; i++) { - if ((dis = disco[i]) != 0 && interesting_to_discover(dis)) { + if ((dis = svd.disco[i]) != 0 && interesting_to_discover(dis)) { ct++; if (oclass != prev_class) { - putstr(tmpwin, iflags.menu_headings, - let_to_name(oclass, FALSE, FALSE)); - prev_class = oclass; + if ((alphabyclass || lootsort) && sorted_ct) { + /* output previous class */ + disco_output_sorted(tmpwin, sorted_lines, sorted_ct, + lootsort); + sorted_ct = 0; + } + if (!alphabetized || alphabyclass) { + /* header for new class */ + putstr(tmpwin, iflags.menu_headings.attr, + let_to_name(oclass, FALSE, FALSE)); + prev_class = oclass; + } } - Strcpy(buf, objects[dis].oc_pre_discovered ? "* " : " "); + Strcpy(buf, objects[dis].oc_encountered ? " " : "* "); + if (lootsort) + (void) sortloot_descr(dis, &buf[2]); disco_append_typename(buf, dis); - putstr(tmpwin, 0, buf); + + if (!alphabetized && !lootsort) + putstr(tmpwin, 0, buf); + else + sorted_lines[sorted_ct++] = dupstr(buf); } } } if (ct == 0) { You("haven't discovered anything yet..."); - } else + } else { + if (sorted_ct) { + /* if we're alphabetizing by class, we've already shown the + relevant header above; if we're alphabetizing across all + classes, we normally don't need a header; but it we showed + any unique items or any artifacts then we do need one */ + if ((uniq_ct || arti_ct) && alphabetized && !alphabyclass) + putstr(tmpwin, iflags.menu_headings.attr, "Discovered items"); + disco_output_sorted(tmpwin, sorted_lines, sorted_ct, lootsort); + } display_nhwindow(tmpwin, TRUE); + } destroy_nhwindow(tmpwin); - return 0; + return ECMD_OK; } /* lower case let_to_name() output, which differs from def_oc_syms[].name */ -STATIC_OVL char * -oclass_to_name(oclass, buf) -char oclass; -char *buf; +staticfn char * +oclass_to_name(char oclass, char *buf) { char *s; @@ -515,74 +884,105 @@ char *buf; return buf; } -/* the '`' command - show discovered object types for one class */ +/* the #knownclass command - show discovered object types for one class; + in addition to actual object classes, supports pseudo-class 'a' for + discovered artifacts and 'u' (or 'r', for "relics") for unique items */ int -doclassdisco() +doclassdisco(void) { static NEARDATA const char prompt[] = "View discoveries for which sort of objects?", havent_discovered_any[] = "haven't discovered any %s yet.", - unique_items[] = "unique items", + unique_items[] = "unique items or relics", artifact_items[] = "artifacts"; - char *s, c, oclass, menulet, allclasses[MAXOCLASSES], - discosyms[2 + MAXOCLASSES + 1], buf[BUFSZ]; - int i, ct, dis, xtras; - boolean traditional; winid tmpwin = WIN_ERR; - anything any; menu_item *pick_list = 0; + anything any; + char *s, c, oclass, menulet, allclasses[MAXOCLASSES], + discosyms[3 + MAXOCLASSES + 1], buf[BUFSZ], + *sorted_lines[NUM_OBJECTS]; /* overkill */ + const char *p; + int i, ct, dis, xtras, sorted_ct, uidx; + boolean traditional, alphabetized, lootsort; + int clr = NO_COLOR; + + if (!flags.discosort || !(p = strchr(disco_order_let, flags.discosort))) + flags.discosort = 'o'; + + if (iflags.menu_requested) { + if (choose_disco_sort(2) < 0) + return ECMD_OK; + } + alphabetized = (flags.discosort == 'a' || flags.discosort == 'c'); + lootsort = (flags.discosort == 's'); discosyms[0] = '\0'; traditional = (flags.menu_style == MENU_TRADITIONAL || flags.menu_style == MENU_COMBINATION); if (!traditional) { tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); } - any = zeroany; + any = cg.zeroany; menulet = 'a'; - /* check whether we've discovered any unique objects */ - for (i = 0; i < SIZE(uniq_objs); i++) - if (objects[uniq_objs[i]].oc_name_known) { + /* + * FIXME? + * relics and artifacts don't obey player's sort order even though + * the header line states that they're shown in such-and-such order. + */ + + /* check whether we've discovered any unique objects (primarily the + invocation items; the Guidebook calls unique items "relics" but the + Amulet of Yendor is unique too so we haven't made a blanket change + from 'u' to 'r') */ + for (i = 0; i < SIZE(uniq_objs); i++) { + uidx = uniq_objs[i]; + if (objects[uidx].oc_name_known + || (objects[uidx].oc_encountered && uidx != AMULET_OF_YENDOR)) { Strcat(discosyms, "u"); if (!traditional) { any.a_int = 'u'; - add_menu(tmpwin, NO_GLYPH, &any, menulet++, 0, ATR_NONE, - unique_items, MENU_UNSELECTED); + /* FIXME: having 'r' as an accelerator to provide an unseen + synonym works but doesn't make much sense since the main + selector is 'a' (implicit lootabc) rather than 'u' */ + add_menu(tmpwin, &nul_glyphinfo, &any, menulet++, 'r', + ATR_NONE, clr, unique_items, MENU_ITEMFLAGS_NONE); } break; } + } /* check whether we've discovered any artifacts */ if (disp_artifact_discoveries(WIN_ERR) > 0) { Strcat(discosyms, "a"); if (!traditional) { any.a_int = 'a'; - add_menu(tmpwin, NO_GLYPH, &any, menulet++, 0, ATR_NONE, - artifact_items, MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, menulet++, 0, + ATR_NONE, clr, artifact_items, MENU_ITEMFLAGS_NONE); } } /* collect classes with discoveries, in packorder ordering; several classes are omitted from packorder and one is of interest here */ Strcpy(allclasses, flags.inv_order); - if (!index(allclasses, VENOM_CLASS)) + if (!strchr(allclasses, VENOM_CLASS)) (void) strkitten(allclasses, VENOM_CLASS); /* append char to string */ /* construct discosyms[] */ for (s = allclasses; *s; ++s) { oclass = *s; c = def_oc_syms[(int) oclass].sym; - for (i = bases[(int) oclass]; + for (i = svb.bases[(int) oclass]; i < NUM_OBJECTS && objects[i].oc_class == oclass; ++i) - if ((dis = disco[i]) != 0 && interesting_to_discover(dis)) { - if (!index(discosyms, c)) { - Sprintf(eos(discosyms), "%c", c); + if ((dis = svd.disco[i]) != 0 && interesting_to_discover(dis)) { + if (!strchr(discosyms, c)) { + (void) strkitten(discosyms, c); if (!traditional) { any.a_int = c; - add_menu(tmpwin, NO_GLYPH, &any, menulet++, c, - ATR_NONE, oclass_to_name(oclass, buf), - MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, + menulet++, c, ATR_NONE, clr, + oclass_to_name(oclass, buf), + MENU_ITEMFLAGS_NONE); } } } @@ -593,26 +993,28 @@ doclassdisco() You(havent_discovered_any, "items"); if (tmpwin != WIN_ERR) destroy_nhwindow(tmpwin); - return 0; + return ECMD_OK; } /* have player choose a class */ c = '\0'; /* class not chosen yet */ if (traditional) { + char allclasses_plustwo[sizeof allclasses + 3]; + /* we'll prompt even if there's only one viable class; we add all nonviable classes as unseen acceptable choices so player can ask for discoveries of any class whether it has discoveries or not */ - for (s = allclasses, xtras = 0; *s; ++s) { - c = def_oc_syms[(int) *s].sym; - if (!index(discosyms, c)) { + Sprintf(allclasses_plustwo, "%s%c%c%c", allclasses, 'a', 'u', 'r'); + for (s = allclasses_plustwo, xtras = 0; *s; ++s) { + c = strchr("aur", *s) ? *s : def_oc_syms[(int) *s].sym; + if (!strchr(discosyms, c)) { if (!xtras++) (void) strkitten(discosyms, '\033'); (void) strkitten(discosyms, c); } } /* get the class (via its symbol character) */ - c = yn_function(prompt, discosyms, '\0'); - savech(c); + c = yn_function(prompt, discosyms, '\0', TRUE); if (!c) clear_nhwindow(WIN_MESSAGE); } else { @@ -634,27 +1036,41 @@ doclassdisco() destroy_nhwindow(tmpwin); } if (!c) - return 0; /* player declined to make a selection */ + return ECMD_OK; /* player declined to make a selection */ /* * show discoveries for object class c */ - tmpwin = create_nhwindow(NHW_MENU); + tmpwin = create_nhwindow(NHW_TEXT); ct = 0; switch (c) { case 'u': - putstr(tmpwin, iflags.menu_headings, + case 'r': + putstr(tmpwin, iflags.menu_headings.attr, upstart(strcpy(buf, unique_items))); - for (i = 0; i < SIZE(uniq_objs); i++) - if (objects[uniq_objs[i]].oc_name_known) { - Sprintf(buf, " %s", OBJ_NAME(objects[uniq_objs[i]])); - putstr(tmpwin, 0, buf); + for (i = 0; i < SIZE(uniq_objs); i++) { + uidx = uniq_objs[i]; + if (objects[uidx].oc_name_known + || (objects[uidx].oc_encountered + && uidx != AMULET_OF_YENDOR)) { ++ct; + disco_fmt_uniq(uidx, buf); + putstr(tmpwin, 0, buf); } + } if (!ct) You(havent_discovered_any, unique_items); break; case 'a': + /* note: this will work all the time for menustyle traditional + but requires at least one artifact discovery for other styles + [could fix that by forcing the 'a' choice into the pick-class + menu when running in wizard mode] */ + if (wizard && y_n("Dump information about all artifacts?") == 'y') { + dump_artifact_info(tmpwin); + ct = NROFARTIFACTS; /* non-zero vs zero is what matters below */ + break; + } /* disp_artifact_discoveries() includes a header */ ct = disp_artifact_discoveries(tmpwin); if (!ct) @@ -662,41 +1078,70 @@ doclassdisco() break; default: oclass = def_char_to_objclass(c); - Sprintf(buf, "Discovered %s", let_to_name(oclass, FALSE, FALSE)); - putstr(tmpwin, iflags.menu_headings, buf); - for (i = bases[(int) oclass]; - i < NUM_OBJECTS && objects[i].oc_class == oclass; ++i) { - if ((dis = disco[i]) != 0 && interesting_to_discover(dis)) { - Strcpy(buf, objects[dis].oc_pre_discovered ? "* " : " "); - disco_append_typename(buf, dis); - putstr(tmpwin, 0, buf); + /* this should never happen but has been observed via the fuzzer */ + if (oclass == MAXOCLASSES) + impossible("doclassdisco: invalid object class '%s'", visctrl(c)); + Sprintf(buf, "Discovered %s in %s", let_to_name(oclass, FALSE, FALSE), + (flags.discosort == 'o') ? "order of discovery" + : (flags.discosort == 's') ? "'sortloot' order" + : "alphabetical order"); + putstr(tmpwin, 0, buf); /* skip iflags.menu_headings */ + sorted_ct = 0; + for (i = svb.bases[(int) oclass]; i <= svb.bases[oclass + 1] - 1; + ++i) { + if ((dis = svd.disco[i]) != 0 && interesting_to_discover(dis)) { ++ct; + Strcpy(buf, objects[dis].oc_encountered ? " " : "* "); + if (lootsort) + (void) sortloot_descr(dis, &buf[2]); + disco_append_typename(buf, dis); + + if (!alphabetized && !lootsort) + putstr(tmpwin, 0, buf); + else + sorted_lines[sorted_ct++] = dupstr(buf); } } - if (!ct) + if (!ct) { You(havent_discovered_any, oclass_to_name(oclass, buf)); + } else if (sorted_ct) { + qsort(sorted_lines, sorted_ct, sizeof (char *), discovered_cmp); + for (i = 0; i < sorted_ct; ++i) { + char *sl; + + sl = sorted_lines[i]; + if (lootsort) { + sl[6] = sl[0]; /* '*' or ' ' */ + sl += 6; + } + putstr(tmpwin, 0, sl); + free(sorted_lines[i]), sorted_lines[i] = 0; + } + } break; } if (ct) display_nhwindow(tmpwin, TRUE); destroy_nhwindow(tmpwin); - return 0; + return ECMD_OK; } /* put up nameable subset of discoveries list as a menu */ void -rename_disco() +rename_disco(void) { - register int i, dis; + int i, dis; int ct = 0, mn = 0, sl; char *s, oclass, prev_class; winid tmpwin; anything any; menu_item *selected = 0; + int clr = NO_COLOR; + char buf[BUFSZ]; - any = zeroany; + any = cg.zeroany; tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); /* * Skip the "unique objects" section (each will appear within its @@ -709,9 +1154,9 @@ rename_disco() for (s = flags.inv_order; *s; s++) { oclass = *s; prev_class = oclass + 1; /* forced different from oclass */ - for (i = bases[(int) oclass]; + for (i = svb.bases[(int) oclass]; i < NUM_OBJECTS && objects[i].oc_class == oclass; i++) { - dis = disco[i]; + dis = svd.disco[i]; if (!dis || !interesting_to_discover(dis)) continue; ct++; @@ -721,14 +1166,15 @@ rename_disco() if (oclass != prev_class) { any.a_int = 0; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, iflags.menu_headings, - let_to_name(oclass, FALSE, FALSE), - MENU_UNSELECTED); + add_menu_heading(tmpwin, + let_to_name(oclass, FALSE, FALSE)); prev_class = oclass; } any.a_int = dis; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - obj_typename(dis), MENU_UNSELECTED); + *buf = '\0'; + disco_append_typename(buf, dis); + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, + ATR_NONE, clr, buf, MENU_ITEMFLAGS_NONE); } } if (ct == 0) { @@ -746,17 +1192,30 @@ rename_disco() if (dis != STRANGE_OBJECT) { struct obj odummy; - odummy = zeroobj; + odummy = cg.zeroobj; odummy.otyp = dis; odummy.oclass = objects[dis].oc_class; odummy.quan = 1L; odummy.known = !objects[dis].oc_uses_known; - odummy.dknown = 1; + odummy.dknown = 1; /* not observe_object: it isn't real */ docall(&odummy); } } destroy_nhwindow(tmpwin); return; } +#endif /* !SFCTOOL */ +void +get_sortdisco(char *opts, boolean cnf) +{ + const char *p = strchr(disco_order_let, flags.discosort); + + if (!p) + flags.discosort = 'o', p = disco_order_let; + if (cnf) + Sprintf(opts, "%c", flags.discosort); + else + Strcpy(opts, disco_orders_descr[p - disco_order_let]); +} /*o_init.c*/ diff --git a/src/objects.c b/src/objects.c index fcca3b0f7..b6bbaa3dd 100644 --- a/src/objects.c +++ b/src/objects.c @@ -1,1183 +1,38 @@ -/* NetHack 3.6 objects.c $NHDT-Date: 1535422421 2018/08/28 02:13:41 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.51 $ */ +/* NetHack 5.0 objects.c $NHDT-Date: 1596498192 2020/08/03 23:43:12 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.66 $ */ /* Copyright (c) Mike Threepoint, 1989. */ /* NetHack may be freely redistributed. See license for details. */ -/* - * The data in this file is processed twice, to construct two arrays. - * On the first pass, only object name and object description matter. - * On the second pass, all object-class fields except those two matter. - * 2nd pass is a recursive inclusion of this file, not a 2nd compilation. - * The name/description array is also used by makedefs and lev_comp. - * - * #ifndef OBJECTS_PASS_2_ - * # define OBJECT(name,desc,foo,bar,glorkum) name,desc - * struct objdescr obj_descr[] = - * #else - * # define OBJECT(name,desc,foo,bar,glorkum) foo,bar,glorkum - * struct objclass objects[] = - * #endif - * { - * { OBJECT("strange object",NULL, 1,2,3) }, - * { OBJECT("arrow","pointy stick", 4,5,6) }, - * ... - * { OBJECT(NULL,NULL, 0,0,0) } - * }; - * #define OBJECTS_PASS_2_ - * #include "objects.c" - */ - -/* *INDENT-OFF* */ -/* clang-format off */ - -#ifndef OBJECTS_PASS_2_ -/* first pass */ -struct monst { struct monst *dummy; }; /* lint: struct obj's union */ #include "config.h" +#include "weight.h" #include "obj.h" -#include "objclass.h" + #include "prop.h" #include "skills.h" - -#else /* !OBJECTS_PASS_2_ */ -/* second pass */ #include "color.h" -#define COLOR_FIELD(X) X, -#endif /* !OBJECTS_PASS_2_ */ - -/* objects have symbols: ) [ = " ( % ! ? + / $ * ` 0 _ . */ - -/* - * Note: OBJ() and BITS() macros are used to avoid exceeding argument - * limits imposed by some compilers. The ctnr field of BITS currently - * does not map into struct objclass, and is ignored in the expansion. - * The 0 in the expansion corresponds to oc_pre_discovered, which is - * set at run-time during role-specific character initialization. - */ - -#ifndef OBJECTS_PASS_2_ -/* first pass -- object descriptive text */ -#define OBJ(name,desc) name, desc -#define OBJECT(obj,bits,prp,sym,prob,dly,wt, \ - cost,sdam,ldam,oc1,oc2,nut,color) { obj } -#define None (char *) 0 /* less visual distraction for 'no description' */ - -NEARDATA struct objdescr obj_descr[] = -#else -/* second pass -- object definitions */ -#define BITS(nmkn,mrg,uskn,ctnr,mgc,chrg,uniq,nwsh,big,tuf,dir,sub,mtrl) \ - nmkn,mrg,uskn,0,mgc,chrg,uniq,nwsh,big,tuf,dir,mtrl,sub /*SCO cpp fodder*/ -#define OBJECT(obj,bits,prp,sym,prob,dly,wt,cost,sdam,ldam,oc1,oc2,nut,color) \ - { 0, 0, (char *) 0, bits, prp, sym, dly, COLOR_FIELD(color) prob, wt, \ - cost, sdam, ldam, oc1, oc2, nut } -#ifndef lint -#define HARDGEM(n) (n >= 8) -#else -#define HARDGEM(n) (0) -#endif - -NEARDATA struct objclass objects[] = -#endif -{ -/* dummy object[0] -- description [2nd arg] *must* be NULL */ -OBJECT(OBJ("strange object", None), - BITS(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, P_NONE, 0), - 0, ILLOBJ_CLASS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), - -/* weapons ... */ -#define WEAPON(name,desc,kn,mg,bi,prob,wt, \ - cost,sdam,ldam,hitbon,typ,sub,metal,color) \ - OBJECT(OBJ(name,desc), \ - BITS(kn, mg, 1, 0, 0, 1, 0, 0, bi, 0, typ, sub, metal), \ - 0, WEAPON_CLASS, prob, 0, wt, \ - cost, sdam, ldam, hitbon, 0, wt, color) -#define PROJECTILE(name,desc,kn,prob,wt, \ - cost,sdam,ldam,hitbon,metal,sub,color) \ - OBJECT(OBJ(name,desc), \ - BITS(kn, 1, 1, 0, 0, 1, 0, 0, 0, 0, PIERCE, sub, metal), \ - 0, WEAPON_CLASS, prob, 0, wt, \ - cost, sdam, ldam, hitbon, 0, wt, color) -#define BOW(name,desc,kn,prob,wt,cost,hitbon,metal,sub,color) \ - OBJECT(OBJ(name,desc), \ - BITS(kn, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, sub, metal), \ - 0, WEAPON_CLASS, prob, 0, wt, \ - cost, 2, 2, hitbon, 0, wt, color) - -/* Note: for weapons that don't do an even die of damage (ex. 2-7 or 3-18) - the extra damage is added on in weapon.c, not here! */ - -#define P PIERCE -#define S SLASH -#define B WHACK - -/* missiles; materiel reflects the arrowhead, not the shaft */ -PROJECTILE("arrow", None, - 1, 55, 1, 2, 6, 6, 0, IRON, -P_BOW, HI_METAL), -PROJECTILE("elven arrow", "runed arrow", - 0, 20, 1, 2, 7, 6, 0, WOOD, -P_BOW, HI_WOOD), -PROJECTILE("orcish arrow", "crude arrow", - 0, 20, 1, 2, 5, 6, 0, IRON, -P_BOW, CLR_BLACK), -PROJECTILE("silver arrow", None, - 1, 12, 1, 5, 6, 6, 0, SILVER, -P_BOW, HI_SILVER), -PROJECTILE("ya", "bamboo arrow", - 0, 15, 1, 4, 7, 7, 1, METAL, -P_BOW, HI_METAL), -PROJECTILE("crossbow bolt", None, - 1, 55, 1, 2, 4, 6, 0, IRON, -P_CROSSBOW, HI_METAL), - -/* missiles that don't use a launcher */ -WEAPON("dart", None, - 1, 1, 0, 60, 1, 2, 3, 2, 0, P, -P_DART, IRON, HI_METAL), -WEAPON("shuriken", "throwing star", - 0, 1, 0, 35, 1, 5, 8, 6, 2, P, -P_SHURIKEN, IRON, HI_METAL), -WEAPON("boomerang", None, - 1, 1, 0, 15, 5, 20, 9, 9, 0, 0, -P_BOOMERANG, WOOD, HI_WOOD), - -/* spears [note: javelin used to have a separate skill from spears, - because the latter are primarily stabbing weapons rather than - throwing ones; but for playability, they've been merged together - under spear skill and spears can now be thrown like javelins] */ -WEAPON("spear", None, - 1, 1, 0, 50, 30, 3, 6, 8, 0, P, P_SPEAR, IRON, HI_METAL), -WEAPON("elven spear", "runed spear", - 0, 1, 0, 10, 30, 3, 7, 8, 0, P, P_SPEAR, WOOD, HI_WOOD), -WEAPON("orcish spear", "crude spear", - 0, 1, 0, 13, 30, 3, 5, 8, 0, P, P_SPEAR, IRON, CLR_BLACK), -WEAPON("dwarvish spear", "stout spear", - 0, 1, 0, 12, 35, 3, 8, 8, 0, P, P_SPEAR, IRON, HI_METAL), -WEAPON("silver spear", None, - 1, 1, 0, 2, 36, 40, 6, 8, 0, P, P_SPEAR, SILVER, HI_SILVER), -WEAPON("javelin", "throwing spear", - 0, 1, 0, 10, 20, 3, 6, 6, 0, P, P_SPEAR, IRON, HI_METAL), - -/* spearish; doesn't stack, not intended to be thrown */ -WEAPON("trident", None, - 1, 0, 0, 8, 25, 5, 6, 4, 0, P, P_TRIDENT, IRON, HI_METAL), - /* +1 small, +2d4 large */ - -/* blades; all stack */ -WEAPON("dagger", None, - 1, 1, 0, 30, 10, 4, 4, 3, 2, P, P_DAGGER, IRON, HI_METAL), -WEAPON("elven dagger", "runed dagger", - 0, 1, 0, 10, 10, 4, 5, 3, 2, P, P_DAGGER, WOOD, HI_WOOD), -WEAPON("orcish dagger", "crude dagger", - 0, 1, 0, 12, 10, 4, 3, 3, 2, P, P_DAGGER, IRON, CLR_BLACK), -WEAPON("silver dagger", None, - 1, 1, 0, 3, 12, 40, 4, 3, 2, P, P_DAGGER, SILVER, HI_SILVER), -WEAPON("athame", None, - 1, 1, 0, 0, 10, 4, 4, 3, 2, S, P_DAGGER, IRON, HI_METAL), -WEAPON("scalpel", None, - 1, 1, 0, 0, 5, 6, 3, 3, 2, S, P_KNIFE, METAL, HI_METAL), -WEAPON("knife", None, - 1, 1, 0, 20, 5, 4, 3, 2, 0, P|S, P_KNIFE, IRON, HI_METAL), -WEAPON("stiletto", None, - 1, 1, 0, 5, 5, 4, 3, 2, 0, P|S, P_KNIFE, IRON, HI_METAL), -/* 3.6: worm teeth and crysknives now stack; - when a stack of teeth is enchanted at once, they fuse into one crysknife; - when a stack of crysknives drops, the whole stack reverts to teeth */ -WEAPON("worm tooth", None, - 1, 1, 0, 0, 20, 2, 2, 2, 0, 0, P_KNIFE, 0, CLR_WHITE), -WEAPON("crysknife", None, - 1, 1, 0, 0, 20, 100, 10, 10, 3, P, P_KNIFE, MINERAL, CLR_WHITE), - -/* axes */ -WEAPON("axe", None, - 1, 0, 0, 40, 60, 8, 6, 4, 0, S, P_AXE, IRON, HI_METAL), -WEAPON("battle-axe", "double-headed axe", /* "double-bitted"? */ - 0, 0, 1, 10, 120, 40, 8, 6, 0, S, P_AXE, IRON, HI_METAL), - -/* swords */ -WEAPON("short sword", None, - 1, 0, 0, 8, 30, 10, 6, 8, 0, P, P_SHORT_SWORD, IRON, HI_METAL), -WEAPON("elven short sword", "runed short sword", - 0, 0, 0, 2, 30, 10, 8, 8, 0, P, P_SHORT_SWORD, WOOD, HI_WOOD), -WEAPON("orcish short sword", "crude short sword", - 0, 0, 0, 3, 30, 10, 5, 8, 0, P, P_SHORT_SWORD, IRON, CLR_BLACK), -WEAPON("dwarvish short sword", "broad short sword", - 0, 0, 0, 2, 30, 10, 7, 8, 0, P, P_SHORT_SWORD, IRON, HI_METAL), -WEAPON("scimitar", "curved sword", - 0, 0, 0, 15, 40, 15, 8, 8, 0, S, P_SCIMITAR, IRON, HI_METAL), -WEAPON("silver saber", None, - 1, 0, 0, 6, 40, 75, 8, 8, 0, S, P_SABER, SILVER, HI_SILVER), -WEAPON("broadsword", None, - 1, 0, 0, 8, 70, 10, 4, 6, 0, S, P_BROAD_SWORD, IRON, HI_METAL), - /* +d4 small, +1 large */ -WEAPON("elven broadsword", "runed broadsword", - 0, 0, 0, 4, 70, 10, 6, 6, 0, S, P_BROAD_SWORD, WOOD, HI_WOOD), - /* +d4 small, +1 large */ -WEAPON("long sword", None, - 1, 0, 0, 50, 40, 15, 8, 12, 0, S, P_LONG_SWORD, IRON, HI_METAL), -WEAPON("two-handed sword", None, - 1, 0, 1, 22, 150, 50, 12, 6, 0, S, P_TWO_HANDED_SWORD, - IRON, HI_METAL), - /* +2d6 large */ -WEAPON("katana", "samurai sword", - 0, 0, 0, 4, 40, 80, 10, 12, 1, S, P_LONG_SWORD, IRON, HI_METAL), -/* special swords set up for artifacts */ -WEAPON("tsurugi", "long samurai sword", - 0, 0, 1, 0, 60, 500, 16, 8, 2, S, P_TWO_HANDED_SWORD, - METAL, HI_METAL), - /* +2d6 large */ -WEAPON("runesword", "runed broadsword", - 0, 0, 0, 0, 40, 300, 4, 6, 0, S, P_BROAD_SWORD, IRON, CLR_BLACK), - /* +d4 small, +1 large; Stormbringer: +5d2 +d8 from level drain */ - -/* polearms */ -/* spear-type */ -WEAPON("partisan", "vulgar polearm", - 0, 0, 1, 5, 80, 10, 6, 6, 0, P, P_POLEARMS, IRON, HI_METAL), - /* +1 large */ -WEAPON("ranseur", "hilted polearm", - 0, 0, 1, 5, 50, 6, 4, 4, 0, P, P_POLEARMS, IRON, HI_METAL), - /* +d4 both */ -WEAPON("spetum", "forked polearm", - 0, 0, 1, 5, 50, 5, 6, 6, 0, P, P_POLEARMS, IRON, HI_METAL), - /* +1 small, +d6 large */ -WEAPON("glaive", "single-edged polearm", - 0, 0, 1, 8, 75, 6, 6, 10, 0, S, P_POLEARMS, IRON, HI_METAL), -WEAPON("lance", None, - 1, 0, 0, 4, 180, 10, 6, 8, 0, P, P_LANCE, IRON, HI_METAL), - /* +2d10 when jousting with lance as primary weapon */ -/* axe-type */ -WEAPON("halberd", "angled poleaxe", - 0, 0, 1, 8, 150, 10, 10, 6, 0, P|S, P_POLEARMS, IRON, HI_METAL), - /* +1d6 large */ -WEAPON("bardiche", "long poleaxe", - 0, 0, 1, 4, 120, 7, 4, 4, 0, S, P_POLEARMS, IRON, HI_METAL), - /* +1d4 small, +2d4 large */ -WEAPON("voulge", "pole cleaver", - 0, 0, 1, 4, 125, 5, 4, 4, 0, S, P_POLEARMS, IRON, HI_METAL), - /* +d4 both */ -WEAPON("dwarvish mattock", "broad pick", - 0, 0, 1, 13, 120, 50, 12, 8, -1, B, P_PICK_AXE, IRON, HI_METAL), -/* curved/hooked */ -WEAPON("fauchard", "pole sickle", - 0, 0, 1, 6, 60, 5, 6, 8, 0, P|S, P_POLEARMS, IRON, HI_METAL), -WEAPON("guisarme", "pruning hook", - 0, 0, 1, 6, 80, 5, 4, 8, 0, S, P_POLEARMS, IRON, HI_METAL), - /* +1d4 small */ -WEAPON("bill-guisarme", "hooked polearm", - 0, 0, 1, 4, 120, 7, 4, 10, 0, P|S, P_POLEARMS, IRON, HI_METAL), - /* +1d4 small */ -/* other */ -WEAPON("lucern hammer", "pronged polearm", - 0, 0, 1, 5, 150, 7, 4, 6, 0, B|P, P_POLEARMS, IRON, HI_METAL), - /* +1d4 small */ -WEAPON("bec de corbin", "beaked polearm", - 0, 0, 1, 4, 100, 8, 8, 6, 0, B|P, P_POLEARMS, IRON, HI_METAL), - -/* bludgeons */ -WEAPON("mace", None, - 1, 0, 0, 40, 30, 5, 6, 6, 0, B, P_MACE, IRON, HI_METAL), - /* +1 small */ -WEAPON("morning star", None, - 1, 0, 0, 12, 120, 10, 4, 6, 0, B, P_MORNING_STAR, IRON, HI_METAL), - /* +d4 small, +1 large */ -WEAPON("war hammer", None, - 1, 0, 0, 15, 50, 5, 4, 4, 0, B, P_HAMMER, IRON, HI_METAL), - /* +1 small */ -WEAPON("club", None, - 1, 0, 0, 12, 30, 3, 6, 3, 0, B, P_CLUB, WOOD, HI_WOOD), -WEAPON("rubber hose", None, - 1, 0, 0, 0, 20, 3, 4, 3, 0, B, P_WHIP, PLASTIC, CLR_BROWN), -WEAPON("quarterstaff", "staff", - 0, 0, 1, 11, 40, 5, 6, 6, 0, B, P_QUARTERSTAFF, WOOD, HI_WOOD), -/* two-piece */ -WEAPON("aklys", "thonged club", - 0, 0, 0, 8, 15, 4, 6, 3, 0, B, P_CLUB, IRON, HI_METAL), -WEAPON("flail", None, - 1, 0, 0, 40, 15, 4, 6, 4, 0, B, P_FLAIL, IRON, HI_METAL), - /* +1 small, +1d4 large */ - -/* misc */ -WEAPON("bullwhip", None, - 1, 0, 0, 2, 20, 4, 2, 1, 0, 0, P_WHIP, LEATHER, CLR_BROWN), - -/* bows */ -BOW("bow", None, 1, 24, 30, 60, 0, WOOD, P_BOW, HI_WOOD), -BOW("elven bow", "runed bow", 0, 12, 30, 60, 0, WOOD, P_BOW, HI_WOOD), -BOW("orcish bow", "crude bow", 0, 12, 30, 60, 0, WOOD, P_BOW, CLR_BLACK), -BOW("yumi", "long bow", 0, 0, 30, 60, 0, WOOD, P_BOW, HI_WOOD), -BOW("sling", None, 1, 40, 3, 20, 0, LEATHER, P_SLING, HI_LEATHER), -BOW("crossbow", None, 1, 45, 50, 40, 0, WOOD, P_CROSSBOW, HI_WOOD), - -#undef P -#undef S -#undef B - -#undef WEAPON -#undef PROJECTILE -#undef BOW - -/* armor ... */ - /* IRON denotes ferrous metals, including steel. - * Only IRON weapons and armor can rust. - * Only COPPER (including brass) corrodes. - * Some creatures are vulnerable to SILVER. - */ -#define ARMOR(name,desc,kn,mgc,blk,power,prob,delay,wt, \ - cost,ac,can,sub,metal,c) \ - OBJECT(OBJ(name, desc), \ - BITS(kn, 0, 1, 0, mgc, 1, 0, 0, blk, 0, 0, sub, metal), \ - power, ARMOR_CLASS, prob, delay, wt, \ - cost, 0, 0, 10 - ac, can, wt, c) -#define HELM(name,desc,kn,mgc,power,prob,delay,wt,cost,ac,can,metal,c) \ - ARMOR(name, desc, kn, mgc, 0, power, prob, delay, wt, \ - cost, ac, can, ARM_HELM, metal, c) -#define CLOAK(name,desc,kn,mgc,power,prob,delay,wt,cost,ac,can,metal,c) \ - ARMOR(name, desc, kn, mgc, 0, power, prob, delay, wt, \ - cost, ac, can, ARM_CLOAK, metal, c) -#define SHIELD(name,desc,kn,mgc,blk,power,prob,delay,wt,cost,ac,can,metal,c) \ - ARMOR(name, desc, kn, mgc, blk, power, prob, delay, wt, \ - cost, ac, can, ARM_SHIELD, metal, c) -#define GLOVES(name,desc,kn,mgc,power,prob,delay,wt,cost,ac,can,metal,c) \ - ARMOR(name, desc, kn, mgc, 0, power, prob, delay, wt, \ - cost, ac, can, ARM_GLOVES, metal, c) -#define BOOTS(name,desc,kn,mgc,power,prob,delay,wt,cost,ac,can,metal,c) \ - ARMOR(name, desc, kn, mgc, 0, power, prob, delay, wt, \ - cost, ac, can, ARM_BOOTS, metal, c) - -/* helmets */ -HELM("elven leather helm", "leather hat", - 0, 0, 0, 6, 1, 3, 8, 9, 0, LEATHER, HI_LEATHER), -HELM("orcish helm", "iron skull cap", - 0, 0, 0, 6, 1, 30, 10, 9, 0, IRON, CLR_BLACK), -HELM("dwarvish iron helm", "hard hat", - 0, 0, 0, 6, 1, 40, 20, 8, 0, IRON, HI_METAL), -HELM("fedora", None, - 1, 0, 0, 0, 0, 3, 1, 10, 0, CLOTH, CLR_BROWN), -HELM("cornuthaum", "conical hat", - 0, 1, CLAIRVOYANT, 3, 1, 4, 80, 10, 1, CLOTH, CLR_BLUE), - /* name coined by devteam; confers clairvoyance for wizards, - blocks clairvoyance if worn by role other than wizard */ -HELM("dunce cap", "conical hat", - 0, 1, 0, 3, 1, 4, 1, 10, 0, CLOTH, CLR_BLUE), -HELM("dented pot", None, - 1, 0, 0, 2, 0, 10, 8, 9, 0, IRON, CLR_BLACK), -/* with shuffled appearances... */ -HELM("helmet", "plumed helmet", - 0, 0, 0, 10, 1, 30, 10, 9, 0, IRON, HI_METAL), -HELM("helm of brilliance", "etched helmet", - 0, 1, 0, 6, 1, 50, 50, 9, 0, IRON, CLR_GREEN), -HELM("helm of opposite alignment", "crested helmet", - 0, 1, 0, 6, 1, 50, 50, 9, 0, IRON, HI_METAL), -HELM("helm of telepathy", "visored helmet", - 0, 1, TELEPAT, 2, 1, 50, 50, 9, 0, IRON, HI_METAL), - -/* suits of armor */ -/* - * There is code in polyself.c that assumes (1) and (2). - * There is code in obj.h, objnam.c, mon.c, read.c that assumes (2). - * (1) The dragon scale mails and the dragon scales are together. - * (2) That the order of the dragon scale mail and dragon scales - * is the same as order of dragons defined in monst.c. - */ -#define DRGN_ARMR(name,mgc,power,cost,ac,color) \ - ARMOR(name, None, 1, mgc, 1, power, 0, 5, 40, \ - cost, ac, 0, ARM_SUIT, DRAGON_HIDE, color) -/* 3.4.1: dragon scale mail reclassified as "magic" since magic is - needed to create them */ -DRGN_ARMR("gray dragon scale mail", 1, ANTIMAGIC, 1200, 1, CLR_GRAY), -DRGN_ARMR("silver dragon scale mail", 1, REFLECTING, 1200, 1, DRAGON_SILVER), -#if 0 /* DEFERRED */ -DRGN_ARMR("shimmering dragon scale mail", 1, DISPLACED, 1200, 1, CLR_CYAN), -#endif -DRGN_ARMR("red dragon scale mail", 1, FIRE_RES, 900, 1, CLR_RED), -DRGN_ARMR("white dragon scale mail", 1, COLD_RES, 900, 1, CLR_WHITE), -DRGN_ARMR("orange dragon scale mail", 1, SLEEP_RES, 900, 1, CLR_ORANGE), -DRGN_ARMR("black dragon scale mail", 1, DISINT_RES, 1200, 1, CLR_BLACK), -DRGN_ARMR("blue dragon scale mail", 1, SHOCK_RES, 900, 1, CLR_BLUE), -DRGN_ARMR("green dragon scale mail", 1, POISON_RES, 900, 1, CLR_GREEN), -DRGN_ARMR("yellow dragon scale mail", 1, ACID_RES, 900, 1, CLR_YELLOW), -/* For now, only dragons leave these. */ -/* 3.4.1: dragon scales left classified as "non-magic"; they confer - magical properties but are produced "naturally" */ -DRGN_ARMR("gray dragon scales", 0, ANTIMAGIC, 700, 7, CLR_GRAY), -DRGN_ARMR("silver dragon scales", 0, REFLECTING, 700, 7, DRAGON_SILVER), -#if 0 /* DEFERRED */ -DRGN_ARMR("shimmering dragon scales", 0, DISPLACED, 700, 7, CLR_CYAN), -#endif -DRGN_ARMR("red dragon scales", 0, FIRE_RES, 500, 7, CLR_RED), -DRGN_ARMR("white dragon scales", 0, COLD_RES, 500, 7, CLR_WHITE), -DRGN_ARMR("orange dragon scales", 0, SLEEP_RES, 500, 7, CLR_ORANGE), -DRGN_ARMR("black dragon scales", 0, DISINT_RES, 700, 7, CLR_BLACK), -DRGN_ARMR("blue dragon scales", 0, SHOCK_RES, 500, 7, CLR_BLUE), -DRGN_ARMR("green dragon scales", 0, POISON_RES, 500, 7, CLR_GREEN), -DRGN_ARMR("yellow dragon scales", 0, ACID_RES, 500, 7, CLR_YELLOW), -#undef DRGN_ARMR -/* other suits */ -ARMOR("plate mail", None, - 1, 0, 1, 0, 44, 5, 450, 600, 3, 2, ARM_SUIT, IRON, HI_METAL), -ARMOR("crystal plate mail", None, - 1, 0, 1, 0, 10, 5, 450, 820, 3, 2, ARM_SUIT, GLASS, CLR_WHITE), -ARMOR("bronze plate mail", None, - 1, 0, 1, 0, 25, 5, 450, 400, 4, 1, ARM_SUIT, COPPER, HI_COPPER), -ARMOR("splint mail", None, - 1, 0, 1, 0, 62, 5, 400, 80, 4, 1, ARM_SUIT, IRON, HI_METAL), -ARMOR("banded mail", None, - 1, 0, 1, 0, 72, 5, 350, 90, 4, 1, ARM_SUIT, IRON, HI_METAL), -ARMOR("dwarvish mithril-coat", None, - 1, 0, 0, 0, 10, 1, 150, 240, 4, 2, ARM_SUIT, MITHRIL, HI_SILVER), -ARMOR("elven mithril-coat", None, - 1, 0, 0, 0, 15, 1, 150, 240, 5, 2, ARM_SUIT, MITHRIL, HI_SILVER), -ARMOR("chain mail", None, - 1, 0, 0, 0, 72, 5, 300, 75, 5, 1, ARM_SUIT, IRON, HI_METAL), -ARMOR("orcish chain mail", "crude chain mail", - 0, 0, 0, 0, 20, 5, 300, 75, 6, 1, ARM_SUIT, IRON, CLR_BLACK), -ARMOR("scale mail", None, - 1, 0, 0, 0, 72, 5, 250, 45, 6, 1, ARM_SUIT, IRON, HI_METAL), -ARMOR("studded leather armor", None, - 1, 0, 0, 0, 72, 3, 200, 15, 7, 1, ARM_SUIT, LEATHER, HI_LEATHER), -ARMOR("ring mail", None, - 1, 0, 0, 0, 72, 5, 250, 100, 7, 1, ARM_SUIT, IRON, HI_METAL), -ARMOR("orcish ring mail", "crude ring mail", - 0, 0, 0, 0, 20, 5, 250, 80, 8, 1, ARM_SUIT, IRON, CLR_BLACK), -ARMOR("leather armor", None, - 1, 0, 0, 0, 82, 3, 150, 5, 8, 1, ARM_SUIT, LEATHER, HI_LEATHER), -ARMOR("leather jacket", None, - 1, 0, 0, 0, 12, 0, 30, 10, 9, 0, ARM_SUIT, LEATHER, CLR_BLACK), - -/* shirts */ -ARMOR("Hawaiian shirt", None, - 1, 0, 0, 0, 8, 0, 5, 3, 10, 0, ARM_SHIRT, CLOTH, CLR_MAGENTA), -ARMOR("T-shirt", None, - 1, 0, 0, 0, 2, 0, 5, 2, 10, 0, ARM_SHIRT, CLOTH, CLR_WHITE), - -/* cloaks */ -CLOAK("mummy wrapping", None, - 1, 0, 0, 0, 0, 3, 2, 10, 1, CLOTH, CLR_GRAY), - /* worn mummy wrapping blocks invisibility */ -CLOAK("elven cloak", "faded pall", - 0, 1, STEALTH, 8, 0, 10, 60, 9, 1, CLOTH, CLR_BLACK), -CLOAK("orcish cloak", "coarse mantelet", - 0, 0, 0, 8, 0, 10, 40, 10, 1, CLOTH, CLR_BLACK), -CLOAK("dwarvish cloak", "hooded cloak", - 0, 0, 0, 8, 0, 10, 50, 10, 1, CLOTH, HI_CLOTH), -CLOAK("oilskin cloak", "slippery cloak", - 0, 0, 0, 8, 0, 10, 50, 9, 2, CLOTH, HI_CLOTH), -CLOAK("robe", None, - 1, 1, 0, 3, 0, 15, 50, 8, 2, CLOTH, CLR_RED), - /* robe was adopted from slash'em, where it's worn as a suit - rather than as a cloak and there are several variations */ -CLOAK("alchemy smock", "apron", - 0, 1, POISON_RES, 9, 0, 10, 50, 9, 1, CLOTH, CLR_WHITE), -CLOAK("leather cloak", None, - 1, 0, 0, 8, 0, 15, 40, 9, 1, LEATHER, CLR_BROWN), -/* with shuffled appearances... */ -CLOAK("cloak of protection", "tattered cape", - 0, 1, PROTECTION, 9, 0, 10, 50, 7, 3, CLOTH, HI_CLOTH), - /* cloak of protection is now the only item conferring MC 3 */ -CLOAK("cloak of invisibility", "opera cloak", - 0, 1, INVIS, 10, 0, 10, 60, 9, 1, CLOTH, CLR_BRIGHT_MAGENTA), -CLOAK("cloak of magic resistance", "ornamental cope", - 0, 1, ANTIMAGIC, 2, 0, 10, 60, 9, 1, CLOTH, CLR_WHITE), - /* 'cope' is not a spelling mistake... leave it be */ -CLOAK("cloak of displacement", "piece of cloth", - 0, 1, DISPLACED, 10, 0, 10, 50, 9, 1, CLOTH, HI_CLOTH), - -/* shields */ -SHIELD("small shield", None, - 1, 0, 0, 0, 6, 0, 30, 3, 9, 0, WOOD, HI_WOOD), -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), -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), -SHIELD("shield of reflection", "polished silver shield", - 0, 1, 0, REFLECTING, 3, 0, 50, 50, 8, 0, SILVER, HI_SILVER), - -/* gloves */ -/* These have their color but not material shuffled, so the IRON must - * stay CLR_BROWN (== HI_LEATHER) even though it's normally either - * HI_METAL or CLR_BLACK. All have shuffled descriptions. - */ -GLOVES("leather gloves", "old gloves", - 0, 0, 0, 16, 1, 10, 8, 9, 0, LEATHER, HI_LEATHER), -GLOVES("gauntlets of fumbling", "padded gloves", - 0, 1, FUMBLING, 8, 1, 10, 50, 9, 0, LEATHER, HI_LEATHER), -GLOVES("gauntlets of power", "riding gloves", - 0, 1, 0, 8, 1, 30, 50, 9, 0, IRON, CLR_BROWN), -GLOVES("gauntlets of dexterity", "fencing gloves", - 0, 1, 0, 8, 1, 10, 50, 9, 0, LEATHER, HI_LEATHER), - -/* boots */ -BOOTS("low boots", "walking shoes", - 0, 0, 0, 25, 2, 10, 8, 9, 0, LEATHER, HI_LEATHER), -BOOTS("iron shoes", "hard shoes", - 0, 0, 0, 7, 2, 50, 16, 8, 0, IRON, HI_METAL), -BOOTS("high boots", "jackboots", - 0, 0, 0, 15, 2, 20, 12, 8, 0, LEATHER, HI_LEATHER), -/* with shuffled appearances... */ -BOOTS("speed boots", "combat boots", - 0, 1, FAST, 12, 2, 20, 50, 9, 0, LEATHER, HI_LEATHER), -BOOTS("water walking boots", "jungle boots", - 0, 1, WWALKING, 12, 2, 15, 50, 9, 0, LEATHER, HI_LEATHER), -BOOTS("jumping boots", "hiking boots", - 0, 1, JUMPING, 12, 2, 20, 50, 9, 0, LEATHER, HI_LEATHER), -BOOTS("elven boots", "mud boots", - 0, 1, STEALTH, 12, 2, 15, 8, 9, 0, LEATHER, HI_LEATHER), -BOOTS("kicking boots", "buckled boots", - 0, 1, 0, 12, 2, 50, 8, 9, 0, IRON, CLR_BROWN), - /* CLR_BROWN for same reason as gauntlets of power */ -BOOTS("fumble boots", "riding boots", - 0, 1, FUMBLING, 12, 2, 20, 30, 9, 0, LEATHER, HI_LEATHER), -BOOTS("levitation boots", "snow boots", - 0, 1, LEVITATION, 12, 2, 15, 30, 9, 0, LEATHER, HI_LEATHER), -#undef HELM -#undef CLOAK -#undef SHIELD -#undef GLOVES -#undef BOOTS -#undef ARMOR - -/* rings ... */ -#define RING(name,stone,power,cost,mgc,spec,mohs,metal,color) \ - OBJECT(OBJ(name, stone), \ - BITS(0, 0, spec, 0, mgc, spec, 0, 0, 0, \ - HARDGEM(mohs), 0, P_NONE, metal), \ - power, RING_CLASS, 0, 0, 3, cost, 0, 0, 0, 0, 15, color) -RING("adornment", "wooden", - ADORNED, 100, 1, 1, 2, WOOD, HI_WOOD), -RING("gain strength", "granite", - 0, 150, 1, 1, 7, MINERAL, HI_MINERAL), -RING("gain constitution", "opal", - 0, 150, 1, 1, 7, MINERAL, HI_MINERAL), -RING("increase accuracy", "clay", - 0, 150, 1, 1, 4, MINERAL, CLR_RED), -RING("increase damage", "coral", - 0, 150, 1, 1, 4, MINERAL, CLR_ORANGE), -RING("protection", "black onyx", - PROTECTION, 100, 1, 1, 7, MINERAL, CLR_BLACK), - /* 'PROTECTION' intrinsic enhances MC from worn armor by +1, - regardless of ring's enchantment; wearing a second ring of - protection (or even one ring of protection combined with - cloak of protection) doesn't give a second MC boost */ -RING("regeneration", "moonstone", - REGENERATION, 200, 1, 0, 6, MINERAL, HI_MINERAL), -RING("searching", "tiger eye", - SEARCHING, 200, 1, 0, 6, GEMSTONE, CLR_BROWN), -RING("stealth", "jade", - STEALTH, 100, 1, 0, 6, GEMSTONE, CLR_GREEN), -RING("sustain ability", "bronze", - FIXED_ABIL, 100, 1, 0, 4, COPPER, HI_COPPER), -RING("levitation", "agate", - LEVITATION, 200, 1, 0, 7, GEMSTONE, CLR_RED), -RING("hunger", "topaz", - HUNGER, 100, 1, 0, 8, GEMSTONE, CLR_CYAN), -RING("aggravate monster", "sapphire", - AGGRAVATE_MONSTER, 150, 1, 0, 9, GEMSTONE, CLR_BLUE), -RING("conflict", "ruby", - CONFLICT, 300, 1, 0, 9, GEMSTONE, CLR_RED), -RING("warning", "diamond", - WARNING, 100, 1, 0, 10, GEMSTONE, CLR_WHITE), -RING("poison resistance", "pearl", - POISON_RES, 150, 1, 0, 4, BONE, CLR_WHITE), -RING("fire resistance", "iron", - FIRE_RES, 200, 1, 0, 5, IRON, HI_METAL), -RING("cold resistance", "brass", - COLD_RES, 150, 1, 0, 4, COPPER, HI_COPPER), -RING("shock resistance", "copper", - SHOCK_RES, 150, 1, 0, 3, COPPER, HI_COPPER), -RING("free action", "twisted", - FREE_ACTION, 200, 1, 0, 6, IRON, HI_METAL), -RING("slow digestion", "steel", - SLOW_DIGESTION, 200, 1, 0, 8, IRON, HI_METAL), -RING("teleportation", "silver", - TELEPORT, 200, 1, 0, 3, SILVER, HI_SILVER), -RING("teleport control", "gold", - TELEPORT_CONTROL, 300, 1, 0, 3, GOLD, HI_GOLD), -RING("polymorph", "ivory", - POLYMORPH, 300, 1, 0, 4, BONE, CLR_WHITE), -RING("polymorph control", "emerald", - POLYMORPH_CONTROL, 300, 1, 0, 8, GEMSTONE, CLR_BRIGHT_GREEN), -RING("invisibility", "wire", - INVIS, 150, 1, 0, 5, IRON, HI_METAL), -RING("see invisible", "engagement", - SEE_INVIS, 150, 1, 0, 5, IRON, HI_METAL), -RING("protection from shape changers", "shiny", - PROT_FROM_SHAPE_CHANGERS, 100, 1, 0, 5, IRON, CLR_BRIGHT_CYAN), -#undef RING - -/* amulets ... - THE Amulet comes last because it is special */ -#define AMULET(name,desc,power,prob) \ - OBJECT(OBJ(name, desc), \ - BITS(0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, P_NONE, IRON), \ - power, AMULET_CLASS, prob, 0, 20, 150, 0, 0, 0, 0, 20, HI_METAL) -AMULET("amulet of ESP", "circular", TELEPAT, 175), -AMULET("amulet of life saving", "spherical", LIFESAVED, 75), -AMULET("amulet of strangulation", "oval", STRANGLED, 135), -AMULET("amulet of restful sleep", "triangular", SLEEPY, 135), -AMULET("amulet versus poison", "pyramidal", POISON_RES, 165), -AMULET("amulet of change", "square", 0, 130), -AMULET("amulet of unchanging", "concave", UNCHANGING, 45), -AMULET("amulet of reflection", "hexagonal", REFLECTING, 75), -AMULET("amulet of magical breathing", "octagonal", MAGICAL_BREATHING, 65), -/* fixed descriptions; description duplication is deliberate; - * fake one must come before real one because selection for - * description shuffling stops when a non-magic amulet is encountered - */ -OBJECT(OBJ("cheap plastic imitation of the Amulet of Yendor", - "Amulet of Yendor"), - BITS(0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, PLASTIC), - 0, AMULET_CLASS, 0, 0, 20, 0, 0, 0, 0, 0, 1, HI_METAL), -OBJECT(OBJ("Amulet of Yendor", /* note: description == name */ - "Amulet of Yendor"), - BITS(0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, MITHRIL), - 0, AMULET_CLASS, 0, 0, 20, 30000, 0, 0, 0, 0, 20, HI_METAL), -#undef AMULET - -/* tools ... */ -/* tools with weapon characteristics come last */ -#define TOOL(name,desc,kn,mrg,mgc,chg,prob,wt,cost,mat,color) \ - OBJECT(OBJ(name, desc), \ - BITS(kn, mrg, chg, 0, mgc, chg, 0, 0, 0, 0, 0, P_NONE, mat), \ - 0, TOOL_CLASS, prob, 0, wt, cost, 0, 0, 0, 0, wt, color) -#define CONTAINER(name,desc,kn,mgc,chg,prob,wt,cost,mat,color) \ - OBJECT(OBJ(name, desc), \ - BITS(kn, 0, chg, 1, mgc, chg, 0, 0, 0, 0, 0, P_NONE, mat), \ - 0, TOOL_CLASS, prob, 0, wt, cost, 0, 0, 0, 0, wt, color) -#define WEPTOOL(name,desc,kn,mgc,bi,prob,wt,cost,sdam,ldam,hitbon,sub,mat,clr)\ - OBJECT(OBJ(name, desc), \ - BITS(kn, 0, 1, 0, mgc, 1, 0, 0, bi, 0, hitbon, sub, mat), \ - 0, TOOL_CLASS, prob, 0, wt, cost, sdam, ldam, hitbon, 0, wt, clr) -/* containers */ -CONTAINER("large box", None, 1, 0, 0, 40, 350, 8, WOOD, HI_WOOD), -CONTAINER("chest", None, 1, 0, 0, 35, 600, 16, WOOD, HI_WOOD), -CONTAINER("ice box", None, 1, 0, 0, 5, 900, 42, PLASTIC, CLR_WHITE), -CONTAINER("sack", "bag", 0, 0, 0, 35, 15, 2, CLOTH, HI_CLOTH), -CONTAINER("oilskin sack", "bag", 0, 0, 0, 5, 15, 100, CLOTH, HI_CLOTH), -CONTAINER("bag of holding", "bag", 0, 1, 0, 20, 15, 100, CLOTH, HI_CLOTH), -CONTAINER("bag of tricks", "bag", 0, 1, 1, 20, 15, 100, CLOTH, HI_CLOTH), -#undef CONTAINER - -/* lock opening tools */ -TOOL("skeleton key", "key", 0, 0, 0, 0, 80, 3, 10, IRON, HI_METAL), -TOOL("lock pick", None, 1, 0, 0, 0, 60, 4, 20, IRON, HI_METAL), -TOOL("credit card", None, 1, 0, 0, 0, 15, 1, 10, PLASTIC, CLR_WHITE), -/* light sources */ -TOOL("tallow candle", "candle", 0, 1, 0, 0, 20, 2, 10, WAX, CLR_WHITE), -TOOL("wax candle", "candle", 0, 1, 0, 0, 5, 2, 20, WAX, CLR_WHITE), -TOOL("brass lantern", None, 1, 0, 0, 0, 30, 30, 12, COPPER, CLR_YELLOW), -TOOL("oil lamp", "lamp", 0, 0, 0, 0, 45, 20, 10, COPPER, CLR_YELLOW), -TOOL("magic lamp", "lamp", 0, 0, 1, 0, 15, 20, 50, COPPER, CLR_YELLOW), -/* other tools */ -TOOL("expensive camera", None, 1, 0, 0, 1, 15, 12,200, PLASTIC, CLR_BLACK), -TOOL("mirror", "looking glass", 0, 0, 0, 0, 45, 13, 10, GLASS, HI_SILVER), -TOOL("crystal ball", "glass orb", 0, 0, 1, 1, 15,150, 60, GLASS, HI_GLASS), -TOOL("lenses", None, 1, 0, 0, 0, 5, 3, 80, GLASS, HI_GLASS), -TOOL("blindfold", None, 1, 0, 0, 0, 50, 2, 20, CLOTH, CLR_BLACK), -TOOL("towel", None, 1, 0, 0, 0, 50, 2, 50, CLOTH, CLR_MAGENTA), -TOOL("saddle", None, 1, 0, 0, 0, 5,200,150, LEATHER, HI_LEATHER), -TOOL("leash", None, 1, 0, 0, 0, 65, 12, 20, LEATHER, HI_LEATHER), -TOOL("stethoscope", None, 1, 0, 0, 0, 25, 4, 75, IRON, HI_METAL), -TOOL("tinning kit", None, 1, 0, 0, 1, 15,100, 30, IRON, HI_METAL), -TOOL("tin opener", None, 1, 0, 0, 0, 35, 4, 30, IRON, HI_METAL), -TOOL("can of grease", None, 1, 0, 0, 1, 15, 15, 20, IRON, HI_METAL), -TOOL("figurine", None, 1, 0, 1, 0, 25, 50, 80, MINERAL, HI_MINERAL), - /* monster type specified by obj->corpsenm */ -TOOL("magic marker", None, 1, 0, 1, 1, 15, 2, 50, PLASTIC, CLR_RED), -/* traps */ -TOOL("land mine", None, 1, 0, 0, 0, 0, 300,180, IRON, CLR_RED), -TOOL("beartrap", None, 1, 0, 0, 0, 0, 200, 60, IRON, HI_METAL), -/* instruments; - "If tin whistles are made out of tin, what do they make foghorns out of?" */ -TOOL("tin whistle", "whistle", 0, 0, 0, 0,100, 3, 10, METAL, HI_METAL), -TOOL("magic whistle", "whistle", 0, 0, 1, 0, 30, 3, 10, METAL, HI_METAL), -TOOL("wooden flute", "flute", 0, 0, 0, 0, 4, 5, 12, WOOD, HI_WOOD), -TOOL("magic flute", "flute", 0, 0, 1, 1, 2, 5, 36, WOOD, HI_WOOD), -TOOL("tooled horn", "horn", 0, 0, 0, 0, 5, 18, 15, BONE, CLR_WHITE), -TOOL("frost horn", "horn", 0, 0, 1, 1, 2, 18, 50, BONE, CLR_WHITE), -TOOL("fire horn", "horn", 0, 0, 1, 1, 2, 18, 50, BONE, CLR_WHITE), -TOOL("horn of plenty", "horn", 0, 0, 1, 1, 2, 18, 50, BONE, CLR_WHITE), - /* horn, but not an instrument */ -TOOL("wooden harp", "harp", 0, 0, 0, 0, 4, 30, 50, WOOD, HI_WOOD), -TOOL("magic harp", "harp", 0, 0, 1, 1, 2, 30, 50, WOOD, HI_WOOD), -TOOL("bell", None, 1, 0, 0, 0, 2, 30, 50, COPPER, HI_COPPER), -TOOL("bugle", None, 1, 0, 0, 0, 4, 10, 15, COPPER, HI_COPPER), -TOOL("leather drum", "drum", 0, 0, 0, 0, 4, 25, 25, LEATHER, HI_LEATHER), -TOOL("drum of earthquake","drum", 0, 0, 1, 1, 2, 25, 25, LEATHER, HI_LEATHER), -/* tools useful as weapons */ -WEPTOOL("pick-axe", None, - 1, 0, 0, 20, 100, 50, 6, 3, WHACK, P_PICK_AXE, IRON, HI_METAL), -WEPTOOL("grappling hook", "iron hook", - 0, 0, 0, 5, 30, 50, 2, 6, WHACK, P_FLAIL, IRON, HI_METAL), -WEPTOOL("unicorn horn", None, - 1, 1, 1, 0, 20, 100, 12, 12, PIERCE, P_UNICORN_HORN, - BONE, CLR_WHITE), - /* 3.4.1: unicorn horn left classified as "magic" */ -/* two unique tools; - * not artifacts, despite the comment which used to be here - */ -OBJECT(OBJ("Candelabrum of Invocation", "candelabrum"), - BITS(0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, P_NONE, GOLD), - 0, TOOL_CLASS, 0, 0, 10, 5000, 0, 0, 0, 0, 200, HI_GOLD), -OBJECT(OBJ("Bell of Opening", "silver bell"), - BITS(0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, P_NONE, SILVER), - 0, TOOL_CLASS, 0, 0, 10, 5000, 0, 0, 0, 0, 50, HI_SILVER), -#undef TOOL -#undef WEPTOOL - -/* Comestibles ... */ -#define FOOD(name, prob, delay, wt, unk, tin, nutrition, color) \ - OBJECT(OBJ(name, None), \ - BITS(1, 1, unk, 0, 0, 0, 0, 0, 0, 0, 0, P_NONE, tin), 0, \ - FOOD_CLASS, prob, delay, wt, nutrition / 20 + 5, 0, 0, 0, 0, \ - nutrition, color) -/* All types of food (except tins & corpses) must have a delay of at least 1. - * Delay on corpses is computed and is weight dependant. - * Domestic pets prefer tripe rations above all others. - * Fortune cookies can be read, using them up without ingesting them. - * Carrots improve your vision. - * +0 tins contain monster meat. - * +1 tins (of spinach) make you stronger (like Popeye). - * Meatballs/sticks/rings are only created from objects via stone to flesh. - */ -/* meat */ -FOOD("tripe ration", 140, 2, 10, 0, FLESH, 200, CLR_BROWN), -FOOD("corpse", 0, 1, 0, 0, FLESH, 0, CLR_BROWN), -FOOD("egg", 85, 1, 1, 1, FLESH, 80, CLR_WHITE), -FOOD("meatball", 0, 1, 1, 0, FLESH, 5, CLR_BROWN), -FOOD("meat stick", 0, 1, 1, 0, FLESH, 5, CLR_BROWN), -FOOD("huge chunk of meat", 0, 20,400, 0, FLESH,2000, CLR_BROWN), -/* special case because it's not mergable */ -OBJECT(OBJ("meat ring", None), - BITS(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, FLESH), - 0, FOOD_CLASS, 0, 1, 5, 1, 0, 0, 0, 0, 5, CLR_BROWN), -/* pudding 'corpses' will turn into these and combine; - must be in same order as the pudding monsters */ -FOOD("glob of gray ooze", 0, 2, 20, 0, FLESH, 20, CLR_GRAY), -FOOD("glob of brown pudding", 0, 2, 20, 0, FLESH, 20, CLR_BROWN), -FOOD("glob of green slime", 0, 2, 20, 0, FLESH, 20, CLR_GREEN), -FOOD("glob of black pudding", 0, 2, 20, 0, FLESH, 20, CLR_BLACK), - -/* fruits & veggies */ -FOOD("kelp frond", 0, 1, 1, 0, VEGGY, 30, CLR_GREEN), -FOOD("eucalyptus leaf", 3, 1, 1, 0, VEGGY, 30, CLR_GREEN), -FOOD("apple", 15, 1, 2, 0, VEGGY, 50, CLR_RED), -FOOD("orange", 10, 1, 2, 0, VEGGY, 80, CLR_ORANGE), -FOOD("pear", 10, 1, 2, 0, VEGGY, 50, CLR_BRIGHT_GREEN), -FOOD("melon", 10, 1, 5, 0, VEGGY, 100, CLR_BRIGHT_GREEN), -FOOD("banana", 10, 1, 2, 0, VEGGY, 80, CLR_YELLOW), -FOOD("carrot", 15, 1, 2, 0, VEGGY, 50, CLR_ORANGE), -FOOD("sprig of wolfsbane", 7, 1, 1, 0, VEGGY, 40, CLR_GREEN), -FOOD("clove of garlic", 7, 1, 1, 0, VEGGY, 40, CLR_WHITE), -/* name of slime mold is changed based on player's OPTION=fruit:something - and bones data might have differently named ones from prior games */ -FOOD("slime mold", 75, 1, 5, 0, VEGGY, 250, HI_ORGANIC), - -/* people food */ -FOOD("lump of royal jelly", 0, 1, 2, 0, VEGGY, 200, CLR_YELLOW), -FOOD("cream pie", 25, 1, 10, 0, VEGGY, 100, CLR_WHITE), -FOOD("candy bar", 13, 1, 2, 0, VEGGY, 100, CLR_BROWN), -FOOD("fortune cookie", 55, 1, 1, 0, VEGGY, 40, CLR_YELLOW), -FOOD("pancake", 25, 2, 2, 0, VEGGY, 200, CLR_YELLOW), -FOOD("lembas wafer", 20, 2, 5, 0, VEGGY, 800, CLR_WHITE), -FOOD("cram ration", 20, 3, 15, 0, VEGGY, 600, HI_ORGANIC), -FOOD("food ration", 380, 5, 20, 0, VEGGY, 800, HI_ORGANIC), -FOOD("K-ration", 0, 1, 10, 0, VEGGY, 400, HI_ORGANIC), -FOOD("C-ration", 0, 1, 10, 0, VEGGY, 300, HI_ORGANIC), -/* tins have type specified by obj->spe (+1 for spinach, other implies - flesh; negative specifies preparation method {homemade,boiled,&c}) - and by obj->corpsenm (type of monster flesh) */ -FOOD("tin", 75, 0, 10, 1, METAL, 0, HI_METAL), -#undef FOOD - -/* potions ... */ -#define POTION(name,desc,mgc,power,prob,cost,color) \ - OBJECT(OBJ(name, desc), \ - BITS(0, 1, 0, 0, mgc, 0, 0, 0, 0, 0, 0, P_NONE, GLASS), \ - power, POTION_CLASS, prob, 0, 20, cost, 0, 0, 0, 0, 10, color) -POTION("gain ability", "ruby", 1, 0, 42, 300, CLR_RED), -POTION("restore ability", "pink", 1, 0, 40, 100, CLR_BRIGHT_MAGENTA), -POTION("confusion", "orange", 1, CONFUSION, 42, 100, CLR_ORANGE), -POTION("blindness", "yellow", 1, BLINDED, 40, 150, CLR_YELLOW), -POTION("paralysis", "emerald", 1, 0, 42, 300, CLR_BRIGHT_GREEN), -POTION("speed", "dark green", 1, FAST, 42, 200, CLR_GREEN), -POTION("levitation", "cyan", 1, LEVITATION, 42, 200, CLR_CYAN), -POTION("hallucination", "sky blue", 1, HALLUC, 40, 100, CLR_CYAN), -POTION("invisibility", "brilliant blue", 1, INVIS, 40, 150, CLR_BRIGHT_BLUE), -POTION("see invisible", "magenta", 1, SEE_INVIS, 42, 50, CLR_MAGENTA), -POTION("healing", "purple-red", 1, 0, 57, 100, CLR_MAGENTA), -POTION("extra healing", "puce", 1, 0, 47, 100, CLR_RED), -POTION("gain level", "milky", 1, 0, 20, 300, CLR_WHITE), -POTION("enlightenment", "swirly", 1, 0, 20, 200, CLR_BROWN), -POTION("monster detection", "bubbly", 1, 0, 40, 150, CLR_WHITE), -POTION("object detection", "smoky", 1, 0, 42, 150, CLR_GRAY), -POTION("gain energy", "cloudy", 1, 0, 42, 150, CLR_WHITE), -POTION("sleeping", "effervescent", 1, 0, 42, 100, CLR_GRAY), -POTION("full healing", "black", 1, 0, 10, 200, CLR_BLACK), -POTION("polymorph", "golden", 1, 0, 10, 200, CLR_YELLOW), -POTION("booze", "brown", 0, 0, 42, 50, CLR_BROWN), -POTION("sickness", "fizzy", 0, 0, 42, 50, CLR_CYAN), -POTION("fruit juice", "dark", 0, 0, 42, 50, CLR_BLACK), -POTION("acid", "white", 0, 0, 10, 250, CLR_WHITE), -POTION("oil", "murky", 0, 0, 30, 250, CLR_BROWN), -/* fixed description - */ -POTION("water", "clear", 0, 0, 92, 100, CLR_CYAN), -#undef POTION - -/* scrolls ... */ -#define SCROLL(name,text,mgc,prob,cost) \ - OBJECT(OBJ(name, text), \ - BITS(0, 1, 0, 0, mgc, 0, 0, 0, 0, 0, 0, P_NONE, PAPER), \ - 0, SCROLL_CLASS, prob, 0, 5, cost, 0, 0, 0, 0, 6, HI_PAPER) -SCROLL("enchant armor", "ZELGO MER", 1, 63, 80), -SCROLL("destroy armor", "JUYED AWK YACC", 1, 45, 100), -SCROLL("confuse monster", "NR 9", 1, 53, 100), -SCROLL("scare monster", "XIXAXA XOXAXA XUXAXA", 1, 35, 100), -SCROLL("remove curse", "PRATYAVAYAH", 1, 65, 80), -SCROLL("enchant weapon", "DAIYEN FOOELS", 1, 80, 60), -SCROLL("create monster", "LEP GEX VEN ZEA", 1, 45, 200), -SCROLL("taming", "PRIRUTSENIE", 1, 15, 200), -SCROLL("genocide", "ELBIB YLOH", 1, 15, 300), -SCROLL("light", "VERR YED HORRE", 1, 90, 50), -SCROLL("teleportation", "VENZAR BORGAVVE", 1, 55, 100), -SCROLL("gold detection", "THARR", 1, 33, 100), -SCROLL("food detection", "YUM YUM", 1, 25, 100), -SCROLL("identify", "KERNOD WEL", 1, 180, 20), -SCROLL("magic mapping", "ELAM EBOW", 1, 45, 100), -SCROLL("amnesia", "DUAM XNAHT", 1, 35, 200), -SCROLL("fire", "ANDOVA BEGARIN", 1, 30, 100), -SCROLL("earth", "KIRJE", 1, 18, 200), -SCROLL("punishment", "VE FORBRYDERNE", 1, 15, 300), -SCROLL("charging", "HACKEM MUCHE", 1, 15, 300), -SCROLL("stinking cloud", "VELOX NEB", 1, 15, 300), - /* Extra descriptions, shuffled into use at start of new game. - * Code in win/share/tilemap.c depends on SCR_STINKING_CLOUD preceding - * these and on how many of them there are. If a real scroll gets added - * after stinking cloud or the number of extra descriptions changes, - * tilemap.c must be modified to match. - */ -SCROLL(None, "FOOBIE BLETCH", 1, 0, 100), -SCROLL(None, "TEMOV", 1, 0, 100), -SCROLL(None, "GARVEN DEH", 1, 0, 100), -SCROLL(None, "READ ME", 1, 0, 100), -SCROLL(None, "ETAOIN SHRDLU", 1, 0, 100), -SCROLL(None, "LOREM IPSUM", 1, 0, 100), -SCROLL(None, "FNORD", 1, 0, 100), /* Illuminati */ -SCROLL(None, "KO BATE", 1, 0, 100), /* Kurd Lasswitz */ -SCROLL(None, "ABRA KA DABRA", 1, 0, 100), /* traditional incantation */ -SCROLL(None, "ASHPD SODALG", 1, 0, 100), /* Portal */ -SCROLL(None, "ZLORFIK", 1, 0, 100), /* Zak McKracken */ -SCROLL(None, "GNIK SISI VLE", 1, 0, 100), /* Zak McKracken */ -SCROLL(None, "HAPAX LEGOMENON", 1, 0, 100), -SCROLL(None, "EIRIS SAZUN IDISI", 1, 0, 100), /* Merseburg Incantations */ -SCROLL(None, "PHOL ENDE WODAN", 1, 0, 100), /* Merseburg Incantations */ -SCROLL(None, "GHOTI", 1, 0, 100), /* pronounced as 'fish', - George Bernard Shaw */ -SCROLL(None, "MAPIRO MAHAMA DIROMAT", 1, 0, 100), /* Wizardry */ -SCROLL(None, "VAS CORP BET MANI", 1, 0, 100), /* Ultima */ -SCROLL(None, "XOR OTA", 1, 0, 100), /* Aarne Haapakoski */ -SCROLL(None, "STRC PRST SKRZ KRK", 1, 0, 100), /* Czech and Slovak - tongue-twister */ - /* These must come last because they have special fixed descriptions. - */ -#ifdef MAIL -SCROLL("mail", "stamped", 0, 0, 0), -#endif -SCROLL("blank paper", "unlabeled", 0, 28, 60), -#undef SCROLL - -/* spellbooks ... */ - /* Expanding beyond 52 spells would require changes in spellcasting - * or imposition of a limit on number of spells hero can know because - * they are currently assigned successive letters, a-zA-Z, when learned. - * [The existing spell sorting capability could conceivably be extended - * to enable moving spells from beyond Z to within it, bumping others - * out in the process, allowing more than 52 spells be known but keeping - * only 52 be castable at any given time.] - */ -#define SPELL(name,desc,sub,prob,delay,level,mgc,dir,color) \ - OBJECT(OBJ(name, desc), \ - BITS(0, 0, 0, 0, mgc, 0, 0, 0, 0, 0, dir, sub, PAPER), \ - 0, SPBOOK_CLASS, prob, delay, 50, level * 100, \ - 0, 0, 0, level, 20, color) -/* Spellbook description normally refers to book covers (primarily color). - Parchment and vellum would never be used for such, but rather than - eliminate those, finagle their definitions to refer to the pages - rather than the cover. They are made from animal skin (typically of - a goat or sheep) and books using them for pages generally need heavy - covers with straps or clamps to tightly close the book in order to - keep the pages flat. (However, a wooden cover might itself be covered - by a sheet of parchment, making this become less of an exception. Also, - changing the internal composition from paper to leather makes eating a - parchment or vellum spellbook break vegetarian conduct, as it should.) */ -#define PAPER LEATHER /* override enum for use in SPELL() expansion */ -SPELL("dig", "parchment", - P_MATTER_SPELL, 20, 6, 5, 1, RAY, HI_LEATHER), -SPELL("magic missile", "vellum", - P_ATTACK_SPELL, 45, 2, 2, 1, RAY, HI_LEATHER), -#undef PAPER /* revert to normal material */ -SPELL("fireball", "ragged", - P_ATTACK_SPELL, 20, 4, 4, 1, RAY, HI_PAPER), -SPELL("cone of cold", "dog eared", - P_ATTACK_SPELL, 10, 7, 4, 1, RAY, HI_PAPER), -SPELL("sleep", "mottled", - P_ENCHANTMENT_SPELL, 50, 1, 1, 1, RAY, HI_PAPER), -SPELL("finger of death", "stained", - P_ATTACK_SPELL, 5, 10, 7, 1, RAY, HI_PAPER), -SPELL("light", "cloth", - P_DIVINATION_SPELL, 45, 1, 1, 1, NODIR, HI_CLOTH), -SPELL("detect monsters", "leathery", - P_DIVINATION_SPELL, 43, 1, 1, 1, NODIR, HI_LEATHER), -SPELL("healing", "white", - P_HEALING_SPELL, 40, 2, 1, 1, IMMEDIATE, CLR_WHITE), -SPELL("knock", "pink", - P_MATTER_SPELL, 35, 1, 1, 1, IMMEDIATE, CLR_BRIGHT_MAGENTA), -SPELL("force bolt", "red", - P_ATTACK_SPELL, 35, 2, 1, 1, IMMEDIATE, CLR_RED), -SPELL("confuse monster", "orange", - P_ENCHANTMENT_SPELL, 30, 2, 2, 1, IMMEDIATE, CLR_ORANGE), -SPELL("cure blindness", "yellow", - P_HEALING_SPELL, 25, 2, 2, 1, IMMEDIATE, CLR_YELLOW), -SPELL("drain life", "velvet", - P_ATTACK_SPELL, 10, 2, 2, 1, IMMEDIATE, CLR_MAGENTA), -SPELL("slow monster", "light green", - P_ENCHANTMENT_SPELL, 30, 2, 2, 1, IMMEDIATE, CLR_BRIGHT_GREEN), -SPELL("wizard lock", "dark green", - P_MATTER_SPELL, 30, 3, 2, 1, IMMEDIATE, CLR_GREEN), -SPELL("create monster", "turquoise", - P_CLERIC_SPELL, 35, 3, 2, 1, NODIR, CLR_BRIGHT_CYAN), -SPELL("detect food", "cyan", - P_DIVINATION_SPELL, 30, 3, 2, 1, NODIR, CLR_CYAN), -SPELL("cause fear", "light blue", - P_ENCHANTMENT_SPELL, 25, 3, 3, 1, NODIR, CLR_BRIGHT_BLUE), -SPELL("clairvoyance", "dark blue", - P_DIVINATION_SPELL, 15, 3, 3, 1, NODIR, CLR_BLUE), -SPELL("cure sickness", "indigo", - P_HEALING_SPELL, 32, 3, 3, 1, NODIR, CLR_BLUE), -SPELL("charm monster", "magenta", - P_ENCHANTMENT_SPELL, 20, 3, 3, 1, IMMEDIATE, CLR_MAGENTA), -SPELL("haste self", "purple", - P_ESCAPE_SPELL, 33, 4, 3, 1, NODIR, CLR_MAGENTA), -SPELL("detect unseen", "violet", - P_DIVINATION_SPELL, 20, 4, 3, 1, NODIR, CLR_MAGENTA), -SPELL("levitation", "tan", - P_ESCAPE_SPELL, 20, 4, 4, 1, NODIR, CLR_BROWN), -SPELL("extra healing", "plaid", - P_HEALING_SPELL, 27, 5, 3, 1, IMMEDIATE, CLR_GREEN), -SPELL("restore ability", "light brown", - P_HEALING_SPELL, 25, 5, 4, 1, NODIR, CLR_BROWN), -SPELL("invisibility", "dark brown", - P_ESCAPE_SPELL, 25, 5, 4, 1, NODIR, CLR_BROWN), -SPELL("detect treasure", "gray", - P_DIVINATION_SPELL, 20, 5, 4, 1, NODIR, CLR_GRAY), -SPELL("remove curse", "wrinkled", - P_CLERIC_SPELL, 25, 5, 3, 1, NODIR, HI_PAPER), -SPELL("magic mapping", "dusty", - P_DIVINATION_SPELL, 18, 7, 5, 1, NODIR, HI_PAPER), -SPELL("identify", "bronze", - P_DIVINATION_SPELL, 20, 6, 3, 1, NODIR, HI_COPPER), -SPELL("turn undead", "copper", - P_CLERIC_SPELL, 16, 8, 6, 1, IMMEDIATE, HI_COPPER), -SPELL("polymorph", "silver", - P_MATTER_SPELL, 10, 8, 6, 1, IMMEDIATE, HI_SILVER), -SPELL("teleport away", "gold", - P_ESCAPE_SPELL, 15, 6, 6, 1, IMMEDIATE, HI_GOLD), -SPELL("create familiar", "glittering", - P_CLERIC_SPELL, 10, 7, 6, 1, NODIR, CLR_WHITE), -SPELL("cancellation", "shining", - P_MATTER_SPELL, 15, 8, 7, 1, IMMEDIATE, CLR_WHITE), -SPELL("protection", "dull", - P_CLERIC_SPELL, 18, 3, 1, 1, NODIR, HI_PAPER), -SPELL("jumping", "thin", - P_ESCAPE_SPELL, 20, 3, 1, 1, IMMEDIATE, HI_PAPER), -SPELL("stone to flesh", "thick", - P_HEALING_SPELL, 15, 1, 3, 1, IMMEDIATE, HI_PAPER), -#if 0 /* DEFERRED */ -/* from slash'em, create a tame critter which explodes when attacking, - damaging adjacent creatures--friend or foe--and dying in the process */ -SPELL("flame sphere", "canvas", - P_MATTER_SPELL, 20, 2, 1, 1, NODIR, CLR_BROWN), -SPELL("freeze sphere", "hardcover", - P_MATTER_SPELL, 20, 2, 1, 1, NODIR, CLR_BROWN), -#endif -/* books with fixed descriptions - */ -SPELL("blank paper", "plain", P_NONE, 18, 0, 0, 0, 0, HI_PAPER), -/* tribute book for 3.6 */ -OBJECT(OBJ("novel", "paperback"), - BITS(0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, P_NONE, PAPER), - 0, SPBOOK_CLASS, 0, 0, 0, 20, 0, 0, 0, 1, 20, CLR_BRIGHT_BLUE), -/* a special, one of a kind, spellbook */ -OBJECT(OBJ("Book of the Dead", "papyrus"), - BITS(0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, P_NONE, PAPER), - 0, SPBOOK_CLASS, 0, 0, 20, 10000, 0, 0, 0, 7, 20, HI_PAPER), -#undef SPELL - -/* wands ... */ -#define WAND(name,typ,prob,cost,mgc,dir,metal,color) \ - OBJECT(OBJ(name, typ), \ - BITS(0, 0, 1, 0, mgc, 1, 0, 0, 0, 0, dir, P_NONE, metal), \ - 0, WAND_CLASS, prob, 0, 7, cost, 0, 0, 0, 0, 30, color) -WAND("light", "glass", 95, 100, 1, NODIR, GLASS, HI_GLASS), -WAND("secret door detection", - "balsa", 50, 150, 1, NODIR, WOOD, HI_WOOD), -WAND("enlightenment", "crystal", 15, 150, 1, NODIR, GLASS, HI_GLASS), -WAND("create monster", "maple", 45, 200, 1, NODIR, WOOD, HI_WOOD), -WAND("wishing", "pine", 5, 500, 1, NODIR, WOOD, HI_WOOD), -WAND("nothing", "oak", 25, 100, 0, IMMEDIATE, WOOD, HI_WOOD), -WAND("striking", "ebony", 75, 150, 1, IMMEDIATE, WOOD, HI_WOOD), -WAND("make invisible", "marble", 45, 150, 1, IMMEDIATE, MINERAL, HI_MINERAL), -WAND("slow monster", "tin", 50, 150, 1, IMMEDIATE, METAL, HI_METAL), -WAND("speed monster", "brass", 50, 150, 1, IMMEDIATE, COPPER, HI_COPPER), -WAND("undead turning", "copper", 50, 150, 1, IMMEDIATE, COPPER, HI_COPPER), -WAND("polymorph", "silver", 45, 200, 1, IMMEDIATE, SILVER, HI_SILVER), -WAND("cancellation", "platinum", 45, 200, 1, IMMEDIATE, PLATINUM, CLR_WHITE), -WAND("teleportation", "iridium", 45, 200, 1, IMMEDIATE, METAL, - CLR_BRIGHT_CYAN), -WAND("opening", "zinc", 25, 150, 1, IMMEDIATE, METAL, HI_METAL), -WAND("locking", "aluminum", 25, 150, 1, IMMEDIATE, METAL, HI_METAL), -WAND("probing", "uranium", 30, 150, 1, IMMEDIATE, METAL, HI_METAL), -WAND("digging", "iron", 55, 150, 1, RAY, IRON, HI_METAL), -WAND("magic missile", "steel", 50, 150, 1, RAY, IRON, HI_METAL), -WAND("fire", "hexagonal", 40, 175, 1, RAY, IRON, HI_METAL), -WAND("cold", "short", 40, 175, 1, RAY, IRON, HI_METAL), -WAND("sleep", "runed", 50, 175, 1, RAY, IRON, HI_METAL), -WAND("death", "long", 5, 500, 1, RAY, IRON, HI_METAL), -WAND("lightning", "curved", 40, 175, 1, RAY, IRON, HI_METAL), -/* extra descriptions, shuffled into use at start of new game */ -WAND(None, "forked", 0, 150, 1, 0, WOOD, HI_WOOD), -WAND(None, "spiked", 0, 150, 1, 0, IRON, HI_METAL), -WAND(None, "jeweled", 0, 150, 1, 0, IRON, HI_MINERAL), -#undef WAND - -/* coins ... - so far, gold is all there is */ -#define COIN(name,prob,metal,worth) \ - OBJECT(OBJ(name, None), \ - BITS(0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, P_NONE, metal), \ - 0, COIN_CLASS, prob, 0, 1, worth, 0, 0, 0, 0, 0, HI_GOLD) -COIN("gold piece", 1000, GOLD, 1), -#undef COIN - -/* gems ... - includes stones and rocks but not boulders */ -#define GEM(name,desc,prob,wt,gval,nutr,mohs,glass,color) \ - OBJECT(OBJ(name, desc), \ - BITS(0, 1, 0, 0, 0, 0, 0, 0, 0, \ - HARDGEM(mohs), 0, -P_SLING, glass), \ - 0, GEM_CLASS, prob, 0, 1, gval, 3, 3, 0, 0, nutr, color) -#define ROCK(name,desc,kn,prob,wt,gval,sdam,ldam,mgc,nutr,mohs,glass,color) \ - OBJECT(OBJ(name, desc), \ - BITS(kn, 1, 0, 0, mgc, 0, 0, 0, 0, \ - HARDGEM(mohs), 0, -P_SLING, glass), \ - 0, GEM_CLASS, prob, 0, wt, gval, sdam, ldam, 0, 0, nutr, color) -GEM("dilithium crystal", "white", 2, 1, 4500, 15, 5, GEMSTONE, CLR_WHITE), -GEM("diamond", "white", 3, 1, 4000, 15, 10, GEMSTONE, CLR_WHITE), -GEM("ruby", "red", 4, 1, 3500, 15, 9, GEMSTONE, CLR_RED), -GEM("jacinth", "orange", 3, 1, 3250, 15, 9, GEMSTONE, CLR_ORANGE), -GEM("sapphire", "blue", 4, 1, 3000, 15, 9, GEMSTONE, CLR_BLUE), -GEM("black opal", "black", 3, 1, 2500, 15, 8, GEMSTONE, CLR_BLACK), -GEM("emerald", "green", 5, 1, 2500, 15, 8, GEMSTONE, CLR_GREEN), -GEM("turquoise", "green", 6, 1, 2000, 15, 6, GEMSTONE, CLR_GREEN), -GEM("citrine", "yellow", 4, 1, 1500, 15, 6, GEMSTONE, CLR_YELLOW), -GEM("aquamarine", "green", 6, 1, 1500, 15, 8, GEMSTONE, CLR_GREEN), -GEM("amber", "yellowish brown", 8, 1, 1000, 15, 2, GEMSTONE, CLR_BROWN), -GEM("topaz", "yellowish brown", 10, 1, 900, 15, 8, GEMSTONE, CLR_BROWN), -GEM("jet", "black", 6, 1, 850, 15, 7, GEMSTONE, CLR_BLACK), -GEM("opal", "white", 12, 1, 800, 15, 6, GEMSTONE, CLR_WHITE), -GEM("chrysoberyl", "yellow", 8, 1, 700, 15, 5, GEMSTONE, CLR_YELLOW), -GEM("garnet", "red", 12, 1, 700, 15, 7, GEMSTONE, CLR_RED), -GEM("amethyst", "violet", 14, 1, 600, 15, 7, GEMSTONE, CLR_MAGENTA), -GEM("jasper", "red", 15, 1, 500, 15, 7, GEMSTONE, CLR_RED), -GEM("fluorite", "violet", 15, 1, 400, 15, 4, GEMSTONE, CLR_MAGENTA), -GEM("obsidian", "black", 9, 1, 200, 15, 6, GEMSTONE, CLR_BLACK), -GEM("agate", "orange", 12, 1, 200, 15, 6, GEMSTONE, CLR_ORANGE), -GEM("jade", "green", 10, 1, 300, 15, 6, GEMSTONE, CLR_GREEN), -GEM("worthless piece of white glass", "white", - 77, 1, 0, 6, 5, GLASS, CLR_WHITE), -GEM("worthless piece of blue glass", "blue", - 77, 1, 0, 6, 5, GLASS, CLR_BLUE), -GEM("worthless piece of red glass", "red", - 77, 1, 0, 6, 5, GLASS, CLR_RED), -GEM("worthless piece of yellowish brown glass", "yellowish brown", - 77, 1, 0, 6, 5, GLASS, CLR_BROWN), -GEM("worthless piece of orange glass", "orange", - 76, 1, 0, 6, 5, GLASS, CLR_ORANGE), -GEM("worthless piece of yellow glass", "yellow", - 77, 1, 0, 6, 5, GLASS, CLR_YELLOW), -GEM("worthless piece of black glass", "black", - 76, 1, 0, 6, 5, GLASS, CLR_BLACK), -GEM("worthless piece of green glass", "green", - 77, 1, 0, 6, 5, GLASS, CLR_GREEN), -GEM("worthless piece of violet glass", "violet", - 77, 1, 0, 6, 5, GLASS, CLR_MAGENTA), - -/* Placement note: there is a wishable subrange for - * "gray stones" in the o_ranges[] array in objnam.c - * that is currently everything between luckstones and flint - * (inclusive). - */ -ROCK("luckstone", "gray", 0, 10, 10, 60, 3, 3, 1, 10, 7, MINERAL, CLR_GRAY), -ROCK("loadstone", "gray", 0, 10, 500, 1, 3, 3, 1, 10, 6, MINERAL, CLR_GRAY), -ROCK("touchstone", "gray", 0, 8, 10, 45, 3, 3, 1, 10, 6, MINERAL, CLR_GRAY), -ROCK("flint", "gray", 0, 10, 10, 1, 6, 6, 0, 10, 7, MINERAL, CLR_GRAY), -ROCK("rock", None, 1, 100, 10, 0, 3, 3, 0, 10, 7, MINERAL, CLR_GRAY), -#undef GEM -#undef ROCK - -/* miscellaneous ... */ -/* Note: boulders and rocks are not normally created at random; the - * probabilities only come into effect when you try to polymorph them. - * Boulders weigh more than MAX_CARR_CAP; statues use corpsenm to take - * on a specific type and may act as containers (both affect weight). - */ -OBJECT(OBJ("boulder", None), - BITS(1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, P_NONE, MINERAL), 0, - ROCK_CLASS, 100, 0, 6000, 0, 20, 20, 0, 0, 2000, HI_MINERAL), -OBJECT(OBJ("statue", None), - BITS(1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, P_NONE, MINERAL), 0, - ROCK_CLASS, 900, 0, 2500, 0, 20, 20, 0, 0, 2500, CLR_WHITE), - -OBJECT(OBJ("heavy iron ball", None), - BITS(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, WHACK, P_NONE, IRON), 0, - BALL_CLASS, 1000, 0, 480, 10, 25, 25, 0, 0, 200, HI_METAL), - /* +d4 when "very heavy" */ -OBJECT(OBJ("iron chain", None), - BITS(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, WHACK, P_NONE, IRON), 0, - CHAIN_CLASS, 1000, 0, 120, 0, 4, 4, 0, 0, 200, HI_METAL), - /* +1 both l & s */ - -/* Venom is normally a transitory missile (spit by various creatures) - * but can be wished for in wizard mode so could occur in bones data. - */ -OBJECT(OBJ("blinding venom", "splash of venom"), - BITS(0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, P_NONE, LIQUID), 0, - VENOM_CLASS, 500, 0, 1, 0, 0, 0, 0, 0, 0, HI_ORGANIC), -OBJECT(OBJ("acid venom", "splash of venom"), - BITS(0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, P_NONE, LIQUID), 0, - VENOM_CLASS, 500, 0, 1, 0, 6, 6, 0, 0, 0, HI_ORGANIC), - /* +d6 small or large */ - -/* fencepost, the deadly Array Terminator -- name [1st arg] *must* be NULL */ -OBJECT(OBJ(None, None), - BITS(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, P_NONE, 0), 0, - ILLOBJ_CLASS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) -}; /* objects[] */ +#include "objclass.h" -#ifndef OBJECTS_PASS_2_ +static struct objdescr obj_descr_init[NUM_OBJECTS + 1] = { +#define OBJECTS_DESCR_INIT +#include "objects.h" +#undef OBJECTS_DESCR_INIT +}; -/* perform recursive compilation for second structure */ -#undef OBJ -#undef OBJECT -#define OBJECTS_PASS_2_ -#include "objects.c" +static struct objclass obj_init[NUM_OBJECTS + 1] = { +#define OBJECTS_INIT +#include "objects.h" +#undef OBJECTS_INIT +}; -/* clang-format on */ -/* *INDENT-ON* */ +void objects_globals_init(void); /* in hack.h but we're using config.h */ -void NDECL(objects_init); +struct objdescr obj_descr[SIZE(obj_descr_init)]; +struct objclass objects[SIZE(obj_init)]; -/* dummy routine used to force linkage */ void -objects_init() +objects_globals_init(void) { - return; + memcpy(obj_descr, obj_descr_init, sizeof(obj_descr)); + memcpy(objects, obj_init, sizeof(objects)); } -#endif /* !OBJECTS_PASS_2_ */ - /*objects.c*/ diff --git a/src/objnam.c b/src/objnam.c index 00edf1fa6..f9bc1749f 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 objnam.c $NHDT-Date: 1674864732 2023/01/28 00:12:12 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.259 $ */ +/* NetHack 5.0 objnam.c $NHDT-Date: 1745114235 2025/04/19 17:57:15 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.453 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2011. */ /* NetHack may be freely redistributed. See license for details. */ @@ -10,23 +10,53 @@ #define SCHAR_LIM 127 #define NUMOBUF 12 -STATIC_DCL char *FDECL(strprepend, (char *, const char *)); -STATIC_DCL short FDECL(rnd_otyp_by_wpnskill, (SCHAR_P)); -STATIC_DCL short FDECL(rnd_otyp_by_namedesc, (const char *, CHAR_P, int)); -STATIC_DCL boolean FDECL(wishymatch, (const char *, const char *, BOOLEAN_P)); -STATIC_DCL char *NDECL(nextobuf); -STATIC_DCL void FDECL(releaseobuf, (char *)); -STATIC_DCL void FDECL(xcalled, (char *, int, const char *, const char *)); -STATIC_DCL char *FDECL(minimal_xname, (struct obj *)); -STATIC_DCL void FDECL(add_erosion_words, (struct obj *, char *)); -STATIC_DCL char *FDECL(doname_base, (struct obj *obj, unsigned)); -STATIC_DCL char *FDECL(just_an, (char *str, const char *)); -STATIC_DCL boolean FDECL(singplur_lookup, (char *, char *, BOOLEAN_P, - const char *const *)); -STATIC_DCL char *FDECL(singplur_compound, (char *)); -STATIC_DCL char *FDECL(xname_flags, (struct obj *, unsigned)); -STATIC_DCL boolean FDECL(badman, (const char *, BOOLEAN_P)); -STATIC_DCL char *FDECL(globwt, (struct obj *, char *, boolean *)); +struct _readobjnam_data { + struct obj *otmp; + char *bp; + char *origbp; + char oclass; + char *un, *dn, *actualn; + const char *name; + char *p; + int cnt, spe, spesgn, typ, very, rechrg; + int blessed, uncursed, iscursed, ispoisoned, isgreased; + int eroded, eroded2, erodeproof, locked, unlocked, broken, real, fake; + int halfeaten, mntmp, contents; + int islit, unlabeled, ishistoric, isdiluted, trapped; + int doorless, open, closed, looted; + int tmp, tinv, tvariety, mgend; + int wetness, gsize; + int ftype; + boolean zombify; + char globbuf[BUFSZ]; + char fruitbuf[BUFSZ]; +}; + +staticfn char *strprepend(char *, const char *) NONNULL NONNULLARG1; +staticfn char *nextobuf(void) NONNULL; +staticfn void releaseobuf(char *) NONNULLARG1; +staticfn void xcalled(char *, int, const char *, const char *); +staticfn char *xname_flags(struct obj *, unsigned); +staticfn char *minimal_xname(struct obj *); +staticfn void add_erosion_words(struct obj *, char *); +staticfn char *doname_base(struct obj *obj, unsigned); +staticfn boolean singplur_lookup(char *, char *, boolean, + const char *const *); +staticfn char *singplur_compound(char *); +staticfn boolean ch_ksound(const char *basestr); +staticfn boolean badman(const char *, boolean); +staticfn boolean wishymatch(const char *, const char *, boolean); +staticfn short rnd_otyp_by_wpnskill(schar); +staticfn short rnd_otyp_by_namedesc(const char *, char, int); +staticfn void set_wallprop_from_str(char *) NONNULLARG1; +staticfn struct obj *wizterrainwish(struct _readobjnam_data *); +staticfn void dbterrainmesg(const char *, coordxy, coordxy) NONNULLARG1; +staticfn void readobjnam_init(char *, struct _readobjnam_data *); +staticfn int readobjnam_preparse(struct _readobjnam_data *); +staticfn void readobjnam_parse_charges(struct _readobjnam_data *); +staticfn int readobjnam_postparse1(struct _readobjnam_data *); +staticfn int readobjnam_postparse2(struct _readobjnam_data *); +staticfn int readobjnam_postparse3(struct _readobjnam_data *); struct Jitem { int item; @@ -37,6 +67,32 @@ struct Jitem { #define BSTRNCMPI(base, ptr, str, num) \ ((ptr) < base || strncmpi((ptr), str, num)) #define Strcasecpy(dst, src) (void) strcasecpy(dst, src) +#define Strncat(dst, src, cnt) (void) strncat(dst, src, cnt) + +/* Concat(): append text to base, adjusted by delta, with bounds checking + via a pair of behind-the-scenes variables; delta is either 0 for normal + concatenation or 1 to replace the final character with something */ +#define Concat(base, delta, text) \ + do { \ + Strncat(base ## _eos - delta, text, base ## spaceleft + delta); \ + ConcUpdate(base); \ + } while (0) +#define ConcatF1(base, delta, fmt, arg1) \ + do { \ + Snprintf(base ## _eos - delta, base ## spaceleft + delta, \ + fmt, arg1); \ + ConcUpdate(base); \ + } while (0) +#define ConcatF2(base, delta, fmt, arg1, arg2) \ + do { \ + Snprintf(base ## _eos - delta, base ## spaceleft + delta, \ + fmt, arg1, arg2); \ + ConcUpdate(base); \ + } while (0) +#define ConcUpdate(base) \ + base ## _eos = eos(base), \ + /* convert signed ptrdiff_t to unsigned size_t */ \ + base ## spaceleft = (size_t) (base ## _end - base ## _eos) /* true for gems/rocks that should have " stone" appended to their names */ #define GemStone(typ) \ @@ -46,53 +102,52 @@ struct Jitem { && typ != SAPPHIRE && typ != BLACK_OPAL && typ != EMERALD \ && typ != OPAL))) -STATIC_OVL struct Jitem Japanese_items[] = { { SHORT_SWORD, "wakizashi" }, - { BROADSWORD, "ninja-to" }, - { FLAIL, "nunchaku" }, - { GLAIVE, "naginata" }, - { LOCK_PICK, "osaku" }, - { WOODEN_HARP, "koto" }, - { KNIFE, "shito" }, - { PLATE_MAIL, "tanko" }, - { HELMET, "kabuto" }, - { LEATHER_GLOVES, "yugake" }, - { FOOD_RATION, "gunyoki" }, - { POT_BOOZE, "sake" }, - { 0, "" } }; - -STATIC_DCL const char *FDECL(Japanese_item_name, (int i)); - -STATIC_OVL char * -strprepend(s, pref) -register char *s; -register const char *pref; +static const struct Jitem Japanese_items[] = { + { SHORT_SWORD, "wakizashi" }, + { BROADSWORD, "ninja-to" }, + { FLAIL, "nunchaku" }, + { GLAIVE, "naginata" }, + { LOCK_PICK, "osaku" }, + { WOODEN_HARP, "koto" }, + { MAGIC_HARP, "magic koto" }, + { KNIFE, "shito" }, + { PLATE_MAIL, "tanko" }, + { HELMET, "kabuto" }, + { LEATHER_GLOVES, "yugake" }, + { FOOD_RATION, "gunyoki" }, + { POT_BOOZE, "sake" }, + { 0, "" } +}; + +staticfn char * +strprepend(char *s, const char *pref) { - register int i = (int) strlen(pref); + char star_s = *s; + int i = (int) strlen(pref); if (i > PREFIX) { impossible("PREFIX too short (for %d).", i); return s; } - s -= i; - (void) strncpy(s, pref, i); /* do not copy trailing 0 */ - return s; + copynchars(s - i, pref, i + 1); + *s = star_s; + return s - i; } /* manage a pool of BUFSZ buffers, so callers don't have to */ static char NEARDATA obufs[NUMOBUF][BUFSZ]; static int obufidx = 0; -STATIC_OVL char * -nextobuf() +staticfn char * +nextobuf(void) { obufidx = (obufidx + 1) % NUMOBUF; return obufs[obufidx]; } /* put the most recently allocated buffer back if possible */ -STATIC_OVL void -releaseobuf(bufp) -char *bufp; +staticfn void +releaseobuf(char *bufp) { /* caller may not know whether bufp is the most recently allocated buffer; if it isn't, do nothing; note that because of the somewhat @@ -104,9 +159,46 @@ char *bufp; obufidx = (obufidx - 1 + NUMOBUF) % NUMOBUF; } +/* used by display_pickinv (invent.c, main whole-inventory routine) to + release each successive doname() result in order to try to avoid + clobbering all the obufs when 'perm_invent' is enabled and updated + while one or more obufs have been allocated but not released yet */ +void +maybereleaseobuf(char *obuffer) +{ + releaseobuf(obuffer); + + /* + * An example from 3.6.x where all obufs got clobbered was when a + * monster used a bullwhip to disarm the hero of a two-handed weapon: + * "The ogre lord yanks Cleaver from your corpses!" + | + | hand = body_part(HAND); + | if (use_plural) // switches 'hand' from static buffer to an obuf + | hand = makeplural(hand); + ... + | release_worn_item(); // triggers full inventory update for perm_invent + ... + | pline(..., hand); // the obuf[] for "hands" was clobbered with the + | //+ partial formatting of an item from invent + * + * Another example was from writing a scroll without room in invent to + * hold it after being split from a stack of blank scrolls: + * "Oops! food rations out of your grasp!" + * hold_another_object() was passed 'the(aobjnam(newscroll, "slip"))' + * as an argument and that should have yielded + * "Oops! The scroll of slips out of your grasp!" + * but attempting to add the item to inventory triggered update for + * perm_invent and the result from 'the(...)' was clobbered by partial + * formatting of some inventory item. [It happened in a shop and the + * shk claimed ownership of the new scroll, but that wasn't relevant.] + * That got fixed earlier, by delaying update_inventory() during + * hold_another_object() rather than by avoiding using all the obufs. + */ +} + char * -obj_typename(otyp) -register int otyp; +obj_typename(int otyp) { char *buf = nextobuf(); struct objclass *ocl = &objects[otyp]; @@ -115,13 +207,20 @@ register int otyp; const char *un = ocl->oc_uname; int nn = ocl->oc_name_known; - if (Role_if(PM_SAMURAI) && Japanese_item_name(otyp)) - actualn = Japanese_item_name(otyp); - buf[0] = '\0'; + if (Role_if(PM_SAMURAI)) { + actualn = Japanese_item_name(otyp, actualn); + if (otyp == WOODEN_HARP || otyp == MAGIC_HARP) + dn = "koto"; + } + /* generic items don't have an actual-name; we shouldn't ever be called + for those; pacify static analyzer without resorting to impossible() */ + if (!actualn) + actualn = (otyp > 0 && otyp < MAXOCLASSES) ? "generic" : "object?"; + + buf[0] = '\0'; /* redundant */ switch (ocl->oc_class) { case COIN_CLASS: - Strcpy(buf, "coin"); - break; + return strcpy(buf, actualn); /* "gold piece" */ case POTION_CLASS: Strcpy(buf, "potion"); break; @@ -152,9 +251,17 @@ register int otyp; if (dn) Sprintf(eos(buf), " (%s)", dn); return buf; + case ARMOR_CLASS: + if (objects[otyp].oc_armcat == ARM_GLOVES + || objects[otyp].oc_armcat == ARM_BOOTS) + Strcpy(buf, "pair of "); + else if (otyp >= GRAY_DRAGON_SCALES && otyp <= YELLOW_DRAGON_SCALES) + Strcpy(buf, "set of "); + FALLTHROUGH; + /*FALLTHRU*/ default: if (nn) { - Strcpy(buf, actualn); + Strcat(buf, actualn); if (GemStone(otyp)) Strcat(buf, " stone"); if (un) /* 3: length of " (" + ")" which will enclose 'dn' */ @@ -162,7 +269,7 @@ register int otyp; if (dn) Sprintf(eos(buf), " (%s)", dn); } else { - Strcpy(buf, dn ? dn : actualn); + Strcat(buf, dn ? dn : actualn); if (ocl->oc_class == GEM_CLASS) Strcat(buf, (ocl->oc_material == MINERAL) ? " stone" : " gem"); @@ -188,8 +295,7 @@ register int otyp; /* less verbose result than obj_typename(); either the actual name or the description (but not both); user-assigned name is ignored */ char * -simple_typename(otyp) -int otyp; +simple_typename(int otyp) { char *bufp, *pp, *save_uname = objects[otyp].oc_uname; @@ -203,8 +309,7 @@ int otyp; /* typename for debugging feedback where data involved might be suspect */ char * -safe_typename(otyp) -int otyp; +safe_typename(int otyp) { unsigned save_nameknown; char *res = 0; @@ -213,6 +318,7 @@ int otyp; || !OBJ_NAME(objects[otyp])) { res = nextobuf(); Sprintf(res, "glorkum[%d]", otyp); + impossible("safe_typename: %s", res); } else { /* force it to be treated as fully discovered */ save_nameknown = objects[otyp].oc_name_known; @@ -224,8 +330,7 @@ int otyp; } boolean -obj_is_pname(obj) -struct obj *obj; +obj_is_pname(struct obj *obj) { if (!obj->oartifact || !has_oname(obj)) return FALSE; @@ -236,48 +341,86 @@ struct obj *obj; return TRUE; } -/* used by distant_name() to pass extra information to xname_flags(); - it would be much cleaner if this were a parameter, but that would - require all of the xname() and doname() calls to be modified */ -static int distantname = 0; - /* Give the name of an object seen at a distance. Unlike xname/doname, - * we don't want to set dknown if it's not set already. - */ + we usually don't want to set dknown if it's not set already. */ char * -distant_name(obj, func) -struct obj *obj; -char *FDECL((*func), (OBJ_P)); +distant_name( + struct obj *obj, /* object to be formatted */ + char *(*func)(OBJ_P)) /* formatting routine (usually xname or doname) */ { char *str; + unsigned save_oid; + coordxy ox = 0, oy = 0; + /* + * (r * r): square of the x or y distance; + * (r * r) * 2: sum of squares of both x and y distances + * (r * r) * 2 - r: instead of a square extending from the hero, + * round the corners (so shorter distance imposed for diagonal). + * + * distu() matrix covering a range of 3+ for one quadrant: + * 16 17 - - - + * 9 10 13 18 - + * 4 5 8 13 - + * 1 2 5 10 17 + * @ 1 4 9 16 + * Theoretical r==1 would yield 1. + * r==2 yields 6, functionally equivalent to 5, a knight's jump, + * r==3, the xray range of the Eyes of the Overworld, yields 15. + */ + int r = (u.xray_range > 2) ? u.xray_range : 2, + neardist = (r * r) * 2 - r; /* same as r*r + r*(r-1) */ + + /* setting o_id to 0 prevents xname() from adding T-shirt or apron + slogan, Hawaiian shirt motif, or candy wrapper label when called + with 'program_state.gameover' set; we want this suppression for + html-dump (not implemented in nethack) to prevent object-on-map + tooltips from including that extra text; also guards against a + potential change to minimal_xname() [indirectly used by attribute + disclosure] that propagates o_id rather than leave it 0, and + against a potential extra chance to browse the map with getpos() + during final disclosure (not currently implemented, nor planned) */ + save_oid = obj->o_id; + if (program_state.gameover) + obj->o_id = 0; + + /* this maybe-nearby part used to be replicated in multiple callers */ + if (get_obj_location(obj, &ox, &oy, 0) && cansee(ox, oy) + && (obj->oartifact || distu(ox, oy) <= neardist)) { + /* side-effects: treat as having been seen up close; + cansee() is True hence hero isn't Blind so if 'func' is + the usual doname or xname, obj->dknown will become set + and then for an artifact, find_artifact() will be called */ + str = (*func)(obj); + } else { + /* prior to 3.6.1, this used to save current blindness state, + explicitly set state to hero-is-blind, make the call (which + won't set obj->dknown when blind), then restore the saved + value; but the Eyes of the Overworld override blindness and + would let characters wearing them get obj->dknown set for + distant items, so the external flag was added */ + ++gd.distantname; + str = (*func)(obj); + --gd.distantname; + } + + obj->o_id = save_oid; /* reset to normal */ - /* 3.6.1: this used to save Blind, set it, make the call, then restore - * the saved value; but the Eyes of the Overworld override blindness - * and let characters wearing them get dknown set for distant items. - * - * TODO? if the hero is wearing those Eyes, figure out whether the - * object is within X-ray radius and only treat it as distant when - * beyond that radius. Logic is iffy but result might be interesting. - */ - ++distantname; - str = (*func)(obj); - --distantname; return str; } /* convert player specified fruit name into corresponding fruit juice name ("slice of pizza" -> "pizza juice" rather than "slice of pizza juice") */ char * -fruitname(juice) -boolean juice; /* whether or not to append " juice" to the name */ +fruitname( + boolean juice) /* whether or not to append " juice" to the name */ { char *buf = nextobuf(); - const char *fruit_nam = strstri(pl_fruit, " of "); + const char *fruit_nam = strstri(svp.pl_fruit, " of "); if (fruit_nam) fruit_nam += 4; /* skip past " of " */ else - fruit_nam = pl_fruit; /* use it as is */ + fruit_nam = svp.pl_fruit; /* use it as is */ Sprintf(buf, "%s%s", makesingular(fruit_nam), juice ? " juice" : ""); return buf; @@ -285,12 +428,11 @@ boolean juice; /* whether or not to append " juice" to the name */ /* look up a named fruit by index (1..127) */ struct fruit * -fruit_from_indx(indx) -int indx; +fruit_from_indx(int indx) { struct fruit *f; - for (f = ffruit; f; f = f->nextf) + for (f = gf.ffruit; f; f = f->nextf) if (f->fid == indx) break; return f; @@ -298,22 +440,22 @@ int indx; /* look up a named fruit by name */ struct fruit * -fruit_from_name(fname, exact, highest_fid) -const char *fname; -boolean exact; /* False => prefix or exact match, True = exact match only */ -int *highest_fid; /* optional output; only valid if 'fname' isn't found */ +fruit_from_name( + const char *fname, + boolean exact, /* False: prefix or exact match, True: exact match only */ + int *highest_fid) /* optional output; only valid if 'fname' isn't found */ { struct fruit *f, *tentativef; char *altfname; unsigned k; /* - * note: named fruits are case-senstive... + * note: named fruits are case-sensitive... */ if (highest_fid) *highest_fid = 0; /* first try for an exact match */ - for (f = ffruit; f; f = f->nextf) + for (f = gf.ffruit; f; f = f->nextf) if (!strcmp(f->fname, fname)) return f; else if (highest_fid && f->fid > *highest_fid) @@ -324,8 +466,8 @@ int *highest_fid; /* optional output; only valid if 'fname' isn't found */ matches, not the first */ if (!exact) { tentativef = 0; - for (f = ffruit; f; f = f->nextf) { - k = strlen(f->fname); + for (f = gf.ffruit; f; f = f->nextf) { + k = Strlen(f->fname); if (!strncmp(f->fname, fname, k) && (!fname[k] || fname[k] == ' ') && (!tentativef || k > strlen(tentativef->fname))) @@ -337,7 +479,7 @@ int *highest_fid; /* optional output; only valid if 'fname' isn't found */ for exact match, that's trivial, but for prefix, it's hard */ if (!f) { altfname = makesingular(fname); - for (f = ffruit; f; f = f->nextf) { + for (f = gf.ffruit; f; f = f->nextf) { if (!strcmp(f->fname, altfname)) break; } @@ -345,11 +487,11 @@ int *highest_fid; /* optional output; only valid if 'fname' isn't found */ } if (!f && !exact) { char fnamebuf[BUFSZ], *p; - unsigned fname_k = strlen(fname); /* length of assumed plural fname */ + unsigned fname_k = Strlen(fname); /* length of assumed plural fname */ tentativef = 0; - for (f = ffruit; f; f = f->nextf) { - k = strlen(f->fname); + for (f = gf.ffruit; f; f = f->nextf) { + k = Strlen(f->fname); /* reload fnamebuf[] each iteration in case it gets modified; there's no need to recalculate fname_k */ Strcpy(fnamebuf, fname); @@ -361,10 +503,10 @@ int *highest_fid; /* optional output; only valid if 'fname' isn't found */ compromise and use 'fname_k >= k' instead of '>', accepting 1 char length discrepancy without risking false match (I hope...) */ - if (fname_k >= k && (p = index(&fnamebuf[k], ' ')) != 0) { + if (fname_k >= k && (p = strchr(&fnamebuf[k], ' ')) != 0) { *p = '\0'; /* truncate at 1st space past length of f->fname */ altfname = makesingular(fnamebuf); - k = strlen(altfname); /* actually revised 'fname_k' */ + k = Strlen(altfname); /* actually revised 'fname_k' */ if (!strcmp(f->fname, altfname) && (!tentativef || k > strlen(tentativef->fname))) tentativef = f; @@ -378,15 +520,14 @@ int *highest_fid; /* optional output; only valid if 'fname' isn't found */ /* sort the named-fruit linked list by fruit index number */ void -reorder_fruit(forward) -boolean forward; +reorder_fruit(boolean forward) { struct fruit *f, *allfr[1 + 127]; int i, j, k = SIZE(allfr); for (i = 0; i < k; ++i) allfr[i] = (struct fruit *) 0; - for (f = ffruit; f; f = f->nextf) { + for (f = gf.ffruit; f; f = f->nextf) { /* without sanity checking, this would reduce to 'allfr[f->fid]=f' */ j = f->fid; if (j < 1 || j >= k) { @@ -398,7 +539,7 @@ boolean forward; } allfr[j] = f; } - ffruit = 0; /* reset linked list; we're rebuilding it from scratch */ + gf.ffruit = 0; /* reset linked list; we're rebuilding it from scratch */ /* slot [0] will always be empty; must start 'i' at 1 to avoid [k - i] being out of bounds during first iteration */ for (i = 1; i < k; ++i) { @@ -406,19 +547,19 @@ boolean forward; for backward ordering, go from low to high */ j = forward ? (k - i) : i; if (allfr[j]) { - allfr[j]->nextf = ffruit; - ffruit = allfr[j]; + allfr[j]->nextf = gf.ffruit; + gf.ffruit = allfr[j]; } } } /* add " called " to end of buf, truncating if necessary */ -STATIC_OVL void -xcalled(buf, siz, pfx, sfx) -char *buf; /* eos(obuf) or eos(&obuf[PREFIX]) */ -int siz; /* BUFSZ or BUFSZ-PREFIX */ -const char *pfx; /* usually class string, sometimes more specific */ -const char *sfx; /* user assigned type name */ +staticfn void +xcalled( + char *buf, /* eos(obuf) or eos(&obuf[PREFIX]) */ + int siz, /* BUFSZ or BUFSZ-PREFIX */ + const char *pfx, /* usually class string, sometimes more specific */ + const char *sfx) /* user assigned type name */ { int bufsiz = siz - 1 - (int) strlen(buf), pfxlen = (int) (strlen(pfx) + sizeof " called " - sizeof ""); @@ -431,20 +572,21 @@ const char *sfx; /* user assigned type name */ } char * -xname(obj) -struct obj *obj; +xname(struct obj *obj) { return xname_flags(obj, CXN_NORMAL); } -STATIC_OVL char * -xname_flags(obj, cxn_flags) -register struct obj *obj; -unsigned cxn_flags; /* bitmask of CXN_xxx values */ +staticfn char * +xname_flags( + struct obj *obj, + unsigned cxn_flags) /* bitmask of CXN_xxx values */ { - register char *buf; - register int typ = obj->otyp; - register struct objclass *ocl = &objects[typ]; + char *buf; + char *obufp, *buf_end, *buf_eos; + size_t bufspaceleft; + int typ = obj->otyp; + struct objclass *ocl = &objects[typ]; int nn = ocl->oc_name_known, omndx = obj->corpsenm; const char *actualn = OBJ_NAME(*ocl); const char *dn = OBJ_DESCR(*ocl); @@ -452,15 +594,28 @@ unsigned cxn_flags; /* bitmask of CXN_xxx values */ boolean pluralize = (obj->quan != 1L) && !(cxn_flags & CXN_SINGULAR); boolean known, dknown, bknown; - buf = nextobuf() + PREFIX; /* leave room for "17 -3 " */ - if (Role_if(PM_SAMURAI) && Japanese_item_name(typ)) - actualn = Japanese_item_name(typ); + gx.xnamep = nextobuf(); + /* set up primary work buffer; the first 'PREFIX' bytes are set + aside for use by doname() */ + buf = gx.xnamep + PREFIX; /* leave room for "17 -3 " */ + buf_end = gx.xnamep + BUFSZ - 1; /* last byte within the obuf[] */ + buf[0] = '\0'; + ConcUpdate(buf); /* set buf_eos and bufspaceleft */ + + if (Role_if(PM_SAMURAI)) { + actualn = Japanese_item_name(typ, actualn); + if (typ == WOODEN_HARP || typ == MAGIC_HARP) + dn = "koto"; + } + /* generic items don't have an actual-name; we shouldn't ever be called + for those; pacify static analyzer without resorting to impossible() */ + if (!actualn) + actualn = (typ > 0 && typ < MAXOCLASSES) ? "generic" : "object?"; /* 3.6.2: this used to be part of 'dn's initialization, but it needs to come after possibly overriding 'actualn' */ if (!dn) dn = actualn; - buf[0] = '\0'; /* * clean up known when it's tied to oc_name_known, eg after AD_DRIN * This is only required for unique objects since the article @@ -469,13 +624,10 @@ unsigned cxn_flags; /* bitmask of CXN_xxx values */ */ if (!nn && ocl->oc_uses_known && ocl->oc_unique) obj->known = 0; - if (!Blind && !distantname) - obj->dknown = 1; - if (Role_if(PM_PRIEST)) - obj->bknown = 1; /* actively avoid set_bknown(); - * we mustn't call update_inventory() now because - * it would call xname() (via doname()) recursively - * and could end up clobbering all the obufs... */ + if (!Blind && !gd.distantname) + observe_object(obj); + if (Role_if(PM_CLERIC)) + obj->bknown = 1; /* avoid set_bknown() to bypass update_inventory() */ if (iflags.override_ID) { known = dknown = bknown = TRUE; @@ -486,8 +638,36 @@ unsigned cxn_flags; /* bitmask of CXN_xxx values */ bknown = obj->bknown; } + /* + * Maybe find a previously unseen artifact. + * + * Assumption 1: if an artifact object is being formatted, it is + * being shown to the hero (on floor, or looking into container, + * or probing a monster, or seeing a monster wield it). + * Assumption 2: if in a pile that has been stepped on, the + * artifact won't be noticed for cases where the pile to too deep + * to be auto-shown, unless the player explicitly looks at that + * spot (via ':'). Might need to make an exception somehow (at + * the point where the decision whether to auto-show gets made?) + * when an artifact is on the top of the pile. + * Assumption 3: since this is used for livelog events, not being + * 100% correct won't negatively affect the player's current game. + * + * We use the real obj->dknown rather than the override_ID variant + * so that wizard-mode ^I doesn't cause a not-yet-seen artifact in + * inventory (picked up while blind, still blind) to become found. + */ + if (obj->oartifact && obj->dknown) + find_artifact(obj); + if (obj_is_pname(obj)) goto nameit; + + /* Some classes use strcpy(buf, something)+strcat(buf, otherthing). + In those cases, ConcUpdate() is needed in between if Concat() + will be used for the strcat() part. Other classes just use + strcpy(buf, something) and the ConcUpdate() can be deferred + until after the switch. */ switch (obj->oclass) { case AMULET_CLASS: if (!dknown) @@ -505,9 +685,12 @@ unsigned cxn_flags; /* bitmask of CXN_xxx values */ case WEAPON_CLASS: if (is_poisonable(obj) && obj->opoisoned) Strcpy(buf, "poisoned "); + FALLTHROUGH; /*FALLTHRU*/ case VENOM_CLASS: case TOOL_CLASS: + /* note: lenses or towel prefix would overwrite poisoned weapon + prefix if both were simultaneously possible, but they aren't */ if (typ == LENSES) Strcpy(buf, "pair of "); else if (is_wet_towel(obj)) @@ -521,16 +704,16 @@ unsigned cxn_flags; /* bitmask of CXN_xxx values */ xcalled(buf, BUFSZ - PREFIX, dn, un); else Strcat(buf, dn); + ConcUpdate(buf); if (typ == FIGURINE && omndx != NON_PM) { char anbuf[10]; /* [4] would be enough: 'a','n',' ','\0' */ + const char *pm_name = obj_pmname(obj); - Sprintf(eos(buf), " of %s%s", - just_an(anbuf, mons[omndx].mname), - mons[omndx].mname); + ConcatF2(buf, 0, " of %s%s", just_an(anbuf, pm_name), pm_name); } else if (is_wet_towel(obj)) { if (wizard) - Sprintf(eos(buf), " (%d)", obj->spe); + ConcatF1(buf, 0, " (%d)", obj->spe); } break; case ARMOR_CLASS: @@ -538,41 +721,29 @@ unsigned cxn_flags; /* bitmask of CXN_xxx values */ if (typ >= GRAY_DRAGON_SCALES && typ <= YELLOW_DRAGON_SCALES) { Sprintf(buf, "set of %s", actualn); break; - } - if (is_boots(obj) || is_gloves(obj)) + } else if (is_boots(obj) || is_gloves(obj)) { Strcpy(buf, "pair of "); - - if (obj->otyp >= ELVEN_SHIELD && obj->otyp <= ORCISH_SHIELD - && !dknown) { - Strcpy(buf, "shield"); - break; - } - if (obj->otyp == SHIELD_OF_REFLECTION && !dknown) { - Strcpy(buf, "smooth shield"); - break; + /*FALLTHRU*/ + } else if (is_shield(obj) && !dknown) { + if (obj->otyp >= ELVEN_SHIELD && obj->otyp <= ORCISH_SHIELD) { + Strcpy(buf, "shield"); + break; + } else if (obj->otyp == SHIELD_OF_REFLECTION) { + Strcpy(buf, "smooth shield"); + break; + } } + ConcUpdate(buf); - if (nn) { - Strcat(buf, actualn); - } else if (un) { - if (is_boots(obj)) - Strcat(buf, "boots"); - else if (is_gloves(obj)) - Strcat(buf, "gloves"); - else if (is_cloak(obj)) - Strcpy(buf, "cloak"); - else if (is_helmet(obj)) - Strcpy(buf, "helmet"); - else if (is_shield(obj)) - Strcpy(buf, "shield"); - else - Strcpy(buf, "armor"); - Strcat(buf, " called "); - Strcat(buf, un); - } else - Strcat(buf, dn); + if (nn) + Concat(buf, 0, actualn); + else if (un) + xcalled(buf, BUFSZ - PREFIX, armor_simple_name(obj), un); + else + Concat(buf, 0, dn); break; case FOOD_CLASS: + /* we could include partly-eaten-hack on fruit but don't need to */ if (typ == SLIME_MOLD) { struct fruit *f = fruit_from_indx(obj->spe); @@ -580,31 +751,45 @@ unsigned cxn_flags; /* bitmask of CXN_xxx values */ impossible("Bad fruit #%d?", obj->spe); Strcpy(buf, "fruit"); } else { + /* fruit name is limited in length to PL_FSIZ; converting + to/from singular/plural might increase the length a + little but not enough to pose a risk of overflowing buf */ Strcpy(buf, f->fname); if (pluralize) { - /* ick; already pluralized fruit names - are allowed--we want to try to avoid - adding a redundant plural suffix */ - Strcpy(buf, makeplural(makesingular(buf))); + /* ick: already pluralized fruit names are allowed--we + want to try to avoid adding a redundant plural suffix; + double ick: makesingular() and makeplural() each use + and return an obuf but we don't want any particular + xname() call to consume more than one of those + [note: makeXXX() will be fully evaluated and done with + 'buf' before strcpy() touches its output buffer] */ + Strcpy(buf, obufp = makesingular(buf)); + releaseobuf(obufp); + Strcpy(buf, obufp = makeplural(buf)); + releaseobuf(obufp); + pluralize = FALSE; } } break; } - if (obj->globby) { - Sprintf(buf, "%s%s", - (obj->owt <= 100) - ? "small " - : (obj->owt > 500) - ? "very large " - : (obj->owt > 300) - ? "large " - : "", - actualn); + if (iflags.partly_eaten_hack && obj->oeaten) { + /* normally "partly eaten" is supplied by doname() when + appropriate and omitted by xname(); shrink_glob() wants + it but uses Yname2() -> yname() -> xname() rather than + doname() so we've added an external flag to request it */ + Concat(buf, 0, "partly eaten "); + } + if (obj->globby) { /* 5.0 added "medium" to replace no-prefix */ + ConcatF2(buf, 0, "%s %s", (obj->owt <= 100) ? "small" + : (obj->owt <= 300) ? "medium" + : (obj->owt <= 500) ? "large" + : "very large", + actualn); break; } - Strcpy(buf, actualn); + Concat(buf, 0, actualn); if (typ == TIN && known) tin_details(obj, omndx, buf); break; @@ -615,20 +800,30 @@ unsigned cxn_flags; /* bitmask of CXN_xxx values */ case ROCK_CLASS: if (typ == STATUE && omndx != NON_PM) { char anbuf[10]; + const char *statue_pmname = obj_pmname(obj); - Sprintf(buf, "%s%s of %s%s", - (Role_if(PM_ARCHEOLOGIST) && (obj->spe & STATUE_HISTORIC)) - ? "historic " + Snprintf(buf, bufspaceleft, "%s%s of %s%s", + (Role_if(PM_ARCHEOLOGIST) + && (obj->spe & CORPSTAT_HISTORIC) != 0) ? "historic " : "", - actualn, - type_is_pname(&mons[omndx]) - ? "" - : the_unique_pm(&mons[omndx]) - ? "the " - : just_an(anbuf, mons[omndx].mname), - mons[omndx].mname); - } else - Strcpy(buf, actualn); + actualn, + type_is_pname(&mons[omndx]) ? "" + : the_unique_pm(&mons[omndx]) ? "the " + : just_an(anbuf, statue_pmname), + statue_pmname); + } else if (typ == BOULDER && obj->next_boulder == 1) { + /* sometimes caller wants "next boulder" rather than just + "boulder" (when pushing against a pile of more than one); + originally we just tested for non-0 but checking for 1 is + more robust because the default value for that overloaded + field (obj->corpsenm) is NON_PM (-1) rather than 0 */ + Strcat(strcpy(buf, "next "), actualn); /* "next boulder" */ + /* once "next boulder" occurs, subsequent messages should just + use ordinary "boulder" */ + obj->next_boulder = 0; + } else { + Strcpy(buf, actualn); /* "boulder" or "statue" */ + } break; case BALL_CLASS: Sprintf(buf, "%sheavy iron ball", @@ -732,28 +927,104 @@ unsigned cxn_flags; /* bitmask of CXN_xxx values */ Strcat(buf, " stone"); } break; - } + } /* gem */ default: Sprintf(buf, "glorkum %d %d %d", obj->oclass, typ, obj->spe); - } - if (pluralize) - Strcpy(buf, makeplural(buf)); - - if (obj->otyp == T_SHIRT && program_state.gameover) { + impossible("xname_flags: %s", buf); + break; + } /* switch */ + + /* check whether we've already gone out of bounds of the obuf[], prior + to pluralization and end-of-game shirt and apron text */ + buf_eos = eos(buf); + if (buf_eos > buf_end) { + /* PREFIX is bigger than 6 so there will always be room within the + obuf[] in front of buf to insert "buf[]="; strncpy(,,N) doesn't + add '\0' terminator unless fewer than N chars are copied, which + is what we want, but gcc complains about that so use memcpy() */ + paniclog("xname", (char *) memcpy(buf - 6, "buf[]=", 6)); + panic("xname: buffer overflow before appending name."); + /*NOTREACHED*/ + } + bufspaceleft = (size_t) (buf_end - buf_eos); + + /* if the name should be plural, do that now, after overflow check; + it could make buf[] become shorter */ + if (pluralize) { + obufp = makeplural(buf); + buf[0] = '\0'; /* replace the whole string */ + ConcUpdate(buf); /* reset buf_eos and bufspaceleft */ + Concat(buf, 0, obufp); + releaseobuf(obufp); + } + + /* give some extra information when game is over; for end-of-game + attribute disclosure in wizard mode, ysimple_name() calls + minimal_xname() which passes us a dummy object with o_id==0; + tshirt_text(), apron_text(), and so forth base their result on + o_id and would give inconsistent information compared to what + just got shown for inventory disclosure; fortunately, we want to + avoid the 'with text' part of + "You were acid resistant because of your alchemy smock \ + with text \"Kiss the cook\"." + when disclosing attributes anyway */ + if (program_state.gameover && obj->o_id && bufspaceleft > 0) { + const char *lbl; char tmpbuf[BUFSZ]; - Sprintf(eos(buf), " with text \"%s\"", tshirt_text(obj, tmpbuf)); + /* disclose without breaking illiterate conduct, but mainly tip off + players who aren't aware that something readable is present */ + switch (obj->otyp) { + case T_SHIRT: + case ALCHEMY_SMOCK: + ConcatF1(buf, 0, " with text \"%s\"", + (obj->otyp == T_SHIRT) ? tshirt_text(obj, tmpbuf) + : apron_text(obj, tmpbuf)); + break; + case CANDY_BAR: + lbl = candy_wrapper_text(obj); + if (*lbl) + ConcatF1(buf, 0, " labeled \"%s\"", lbl); + break; + case HAWAIIAN_SHIRT: + ConcatF1(buf, 0, " with %s motif", + an(hawaiian_motif(obj, tmpbuf))); + break; + default: + break; + } } if (has_oname(obj) && dknown) { - Strcat(buf, " named "); + Concat(buf, 0, " named "); + + /* jump directly here if obj passes the has-personal-name test */ nameit: - (void) strncat(buf, ONAME(obj), - BUFSZ - 1 - PREFIX - (unsigned) strlen(buf)); + /*assert(has_oname(obj));*/ + obufp = eos(buf); /* remember where the name will start */ + Concat(buf, 0, ONAME(obj)); + /* downcase "The" in " named The ..." */ + if (obj->oartifact && !strncmp(obufp, "The ", 4)) + *obufp = lowc(*obufp); /* change 'T' in "The " to 't' */ } if (!strncmpi(buf, "the ", 4)) buf += 4; + + buf_eos = eos(buf); /* pointer to '\0' terminator somewhere in obuf[] */ + if (buf_eos >= buf_end) { /* ('>' shouldn't be possible) */ + static int xname_full = 0; + + /* we want a record of something needing more buffer space than + anticipated; since we aren't panicking here, this could happen + repeatedly and we don't want to spam the paniclog file */ + if (!xname_full++) { + paniclog("xname", (char *) memcpy(buf - 6, "buf[]=", 6)); + /* 'PREFIX' ought to be 'PREFIX+4' if we stripped leading "the" */ + paniclog("xname", "used up entire obuf[PREFIX..BUFSX-1]"); + } + } + return buf; } @@ -763,9 +1034,8 @@ unsigned cxn_flags; /* bitmask of CXN_xxx values */ brown potion -- if oc_name_known not set potion of object detection -- if discovered */ -STATIC_OVL char * -minimal_xname(obj) -struct obj *obj; +staticfn char * +minimal_xname(struct obj *obj) { char *bufp; struct obj bareobj; @@ -777,30 +1047,38 @@ struct obj *obj; objects[otyp].oc_uname = 0; /* suppress actual name if object's description is unknown */ saveobcls.oc_name_known = objects[otyp].oc_name_known; - if (!obj->dknown) + if (iflags.override_ID) + objects[otyp].oc_name_known = 1; + else if (!obj->dknown) objects[otyp].oc_name_known = 0; /* caveat: this makes a lot of assumptions about which fields are required in order for xname() to yield a sensible result */ - bareobj = zeroobj; + bareobj = cg.zeroobj; bareobj.otyp = otyp; bareobj.oclass = obj->oclass; - bareobj.dknown = obj->dknown; + /* not observe_object, either the hero observed the object already or this + is overriding ID and shouldn't discover the object */ + bareobj.dknown = (obj->dknown || iflags.override_ID) ? 1 : 0; /* suppress known except for amulets (needed for fakes and real A-of-Y) */ bareobj.known = (obj->oclass == AMULET_CLASS) ? obj->known /* default is "on" for types which don't use it */ : !objects[otyp].oc_uses_known; bareobj.quan = 1L; /* don't want plural */ - bareobj.corpsenm = NON_PM; /* suppress statue and figurine details */ + /* for a boulder, leave corpsenm as 0; non-zero produces "next boulder" */ + if (otyp != BOULDER) + bareobj.corpsenm = NON_PM; /* suppress statue and figurine details */ /* but suppressing fruit details leads to "bad fruit #0" [perhaps we should force "slime mold" rather than use xname?] */ if (obj->otyp == SLIME_MOLD) bareobj.spe = obj->spe; - bufp = distant_name(&bareobj, xname); /* xname(&bareobj) */ + bufp = distant_name(&bareobj, xname); + /* undo forced setting of bareobj.blessed for cleric (priest[ess]); + bufp is an obuf[] so a pointer into the middle of that is viable */ if (!strncmp(bufp, "uncursed ", 9)) - bufp += 9; /* Role_if(PM_PRIEST) */ + bufp += 9; objects[otyp].oc_uname = saveobcls.oc_uname; objects[otyp].oc_name_known = saveobcls.oc_name_known; @@ -809,16 +1087,15 @@ struct obj *obj; /* xname() output augmented for multishot missile feedback */ char * -mshot_xname(obj) -struct obj *obj; +mshot_xname(struct obj *obj) { char tmpbuf[BUFSZ]; char *onm = xname(obj); - if (m_shot.n > 1 && m_shot.o == obj->otyp) { + if (gm.m_shot.n > 1 && gm.m_shot.o == obj->otyp) { /* "the Nth arrow"; value will eventually be passed to an() or The(), both of which correctly handle this "the " prefix */ - Sprintf(tmpbuf, "the %d%s ", m_shot.i, ordin(m_shot.i)); + Sprintf(tmpbuf, "the %d%s ", gm.m_shot.i, ordin(gm.m_shot.i)); onm = strprepend(onm, tmpbuf); } return onm; @@ -826,8 +1103,7 @@ struct obj *obj; /* used for naming "the unique_item" instead of "a unique_item" */ boolean -the_unique_obj(obj) -struct obj *obj; +the_unique_obj(struct obj *obj) { boolean known = (obj->known || iflags.override_ID); @@ -842,8 +1118,7 @@ struct obj *obj; /* should monster type be prefixed with "the"? (mostly used for corpses) */ boolean -the_unique_pm(ptr) -struct permonst *ptr; +the_unique_pm(struct permonst *ptr) { boolean uniq; @@ -856,7 +1131,7 @@ struct permonst *ptr; /* high priest is unique if it includes "of ", otherwise not (caller needs to handle the 1st possibility; we assume the 2nd); worm tail should be irrelevant but is included for completeness */ - if (ptr == &mons[PM_HIGH_PRIEST] || ptr == &mons[PM_LONG_WORM_TAIL]) + if (ptr == &mons[PM_HIGH_CLERIC] || ptr == &mons[PM_LONG_WORM_TAIL]) uniq = FALSE; /* Wizard no longer needs this; he's flagged as unique these days */ if (ptr == &mons[PM_WIZARD_OF_YENDOR]) @@ -864,10 +1139,8 @@ struct permonst *ptr; return uniq; } -STATIC_OVL void -add_erosion_words(obj, prefix) -struct obj *obj; -char *prefix; +staticfn void +add_erosion_words(struct obj *obj, char *prefix) { boolean iscrys = (obj->otyp == CRYSKNIFE); boolean rknown; @@ -889,7 +1162,9 @@ char *prefix; Strcat(prefix, "thoroughly "); break; } - Strcat(prefix, is_rustprone(obj) ? "rusty " : "burnt "); + Strcat(prefix, is_rustprone(obj) ? "rusty " + : is_crackable(obj) ? "cracked " + : "burnt "); } if (obj->oeroded2 && !iscrys) { switch (obj->oeroded2) { @@ -902,22 +1177,22 @@ char *prefix; } Strcat(prefix, is_corrodeable(obj) ? "corroded " : "rotted "); } + /* note: it is possible for an item to be both eroded and erodeproof + (cursed scroll of destroy armor read while confused erodeproofs an + item of armor without repairing existing erosion) */ if (rknown && obj->oerodeproof) - Strcat(prefix, iscrys - ? "fixed " - : is_rustprone(obj) - ? "rustproof " - : is_corrodeable(obj) - ? "corrodeproof " /* "stainless"? */ - : is_flammable(obj) - ? "fireproof " - : ""); + Strcat(prefix, iscrys ? "fixed " + : is_rustprone(obj) ? "rustproof " + : is_corrodeable(obj) ? "corrodeproof " + : is_flammable(obj) ? "fireproof " + : is_crackable(obj) ? "tempered " /* hardened */ + : is_rottable(obj) ? "rotproof " + : ""); } /* used to prevent rust on items where rust makes no difference */ boolean -erosion_matters(obj) -struct obj *obj; +erosion_matters(struct obj *obj) { switch (obj->oclass) { case TOOL_CLASS: @@ -941,23 +1216,40 @@ struct obj *obj; #define DONAME_WITH_PRICE 1 #define DONAME_VAGUE_QUAN 2 +#define DONAME_FOR_MENU 4 /* [not used anywhere yet] */ -STATIC_OVL char * -doname_base(obj, doname_flags) -struct obj *obj; -unsigned doname_flags; +/* core of doname() */ +staticfn char * +doname_base( + struct obj *obj, /* object to format */ + unsigned doname_flags) /* special case requests */ { boolean ispoisoned = FALSE, with_price = (doname_flags & DONAME_WITH_PRICE) != 0, vague_quan = (doname_flags & DONAME_VAGUE_QUAN) != 0, - weightshown = FALSE; - boolean known, dknown, cknown, bknown, lknown; - int omndx = obj->corpsenm; - char prefix[PREFIX], globbuf[QBUFSZ]; + for_menu = (doname_flags & DONAME_FOR_MENU) != 0; + boolean known, dknown, cknown, bknown, lknown, + fake_arti, force_the; + char prefix[PREFIX]; char tmpbuf[PREFIX + 1]; /* for when we have to add something at - the start of prefix instead of the - end (Strcat is used on the end) */ - register char *bp = xname(obj); + * the start of prefix instead of the + * end (Strcat is used on the end) */ + const char *aname = 0; + int omndx = obj->corpsenm; + char *bp; + char *bp_eos, *bp_end; + size_t bpspaceleft; + + /* 'bp' will be within an obuf[] rather than at the start of one, + usually (but not always) pointing at &obuf[PREFIX]; + gx.xnamep always points to the start of that buffer; + 'bp_eos' and 'bpspaceleft' are used and updated by Concat*() macros */ + bp = xname(obj); + bp_end = gx.xnamep + BUFSZ - 1; + bp_eos = eos(bp); + assert(bp_end >= bp_eos); /* ok provided xname() bounds checking works */ + /* size_t cast: convert signed ptrdiff_t to unsigned size_t */ + bpspaceleft = (size_t) (bp_end - bp_eos); if (iflags.override_ID) { known = dknown = cknown = bknown = lknown = TRUE; @@ -976,10 +1268,18 @@ unsigned doname_flags; */ /* must check opoisoned--someone can have a weirdly-named fruit */ if (!strncmp(bp, "poisoned ", 9) && obj->opoisoned) { - bp += 9; + bp += 9; /* doesn't affect bp_eos or bpspaceleft */ ispoisoned = TRUE; } + /* fruits are allowed to be given artifact names; when that happens, + format the name like the corresponding artifact, which may or may not + want "the" prefix and when it doesn't, avoid "a"/"an" prefix too */ + fake_arti = (obj->otyp == SLIME_MOLD + && (aname = artifact_name(bp, (short *) 0, FALSE)) != 0); + force_the = (fake_arti && !strncmpi(aname, "the ", 4)); + + prefix[0] = '\0'; if (obj->quan != 1L) { if (dknown || !vague_quan) Sprintf(prefix, "%ld ", obj->quan); @@ -988,12 +1288,13 @@ unsigned doname_flags; } else if (obj->otyp == CORPSE) { /* skip article prefix for corpses [else corpse_xname() would have to be taught how to strip it off again] */ - *prefix = '\0'; - } else if (obj_is_pname(obj) || the_unique_obj(obj)) { + ; + } else if (force_the || obj_is_pname(obj) || the_unique_obj(obj)) { if (!strncmpi(bp, "the ", 4)) - bp += 4; + bp += 4; /* doesn't affect bp_eos or bpspaceleft */ Strcpy(prefix, "the "); - } else { + } else if (!fake_arti) { + /* default prefix */ Strcpy(prefix, "a "); } @@ -1004,10 +1305,12 @@ unsigned doname_flags; (when that is known, suffix of "(n:0)" will be appended, making the prefix be redundant; note that 'known' flag isn't set when emptiness gets discovered because then - charging magic would yield known number of new charges) */ - && ((obj->otyp == BAG_OF_TRICKS) - ? (obj->spe == 0 && !obj->known) - /* not bag of tricks: empty if container which has no contents */ + charging magic would yield known number of new charges); + horn of plenty isn't a container but is close enough */ + && ((obj->otyp == BAG_OF_TRICKS || obj->otyp == HORN_OF_PLENTY) + ? (obj->spe == 0 && !known) + /* not a bag of tricks or horn of plenty: it's empty if + it is a container that has no contents */ : ((Is_container(obj) || obj->otyp == STATUE) && !Has_contents(obj)))) Strcat(prefix, "empty "); @@ -1022,7 +1325,7 @@ unsigned doname_flags; Strcat(prefix, "cursed "); else if (obj->blessed) Strcat(prefix, "blessed "); - else if (!iflags.implicit_uncursed + else if (!flags.implicit_uncursed /* For most items with charges or +/-, if you know how many * charges are left or what the +/- is, then you must have * totally identified the item, so "uncursed" is unnecessary, @@ -1036,15 +1339,22 @@ unsigned doname_flags; || ((!known || !objects[obj->otyp].oc_charged || obj->oclass == ARMOR_CLASS || obj->oclass == RING_CLASS) -#ifdef MAIL +#ifdef MAIL_STRUCTURES && obj->otyp != SCR_MAIL #endif && obj->otyp != FAKE_AMULET_OF_YENDOR && obj->otyp != AMULET_OF_YENDOR - && !Role_if(PM_PRIEST))) + && !Role_if(PM_CLERIC))) Strcat(prefix, "uncursed "); } + /* "a large trapped box" would perhaps be more correct; [no!] + what about ``(obj->tknown && !obj->otrapped)''? shouldn't that + yield "a non-trapped large box"? (not "an untrapped large box"); + TODO: this should be ``(Is_box(obj) || obj->otyp == TIN) && ...'' + but at present there's no way to set obj->tknown for tins */ + if (Is_box(obj) && obj->otrapped && obj->tknown && obj->dknown) + Strcat(prefix,"trapped "); if (lknown && Is_box(obj)) { if (obj->obroken) /* 3.6.0 used "unlockable" here but that could be misunderstood @@ -1060,79 +1370,111 @@ unsigned doname_flags; if (obj->greased) Strcat(prefix, "greased "); - if (cknown && Has_contents(obj)) { + if (cknown && Has_contents(obj) && bpspaceleft > 0) { /* we count the number of separate stacks, which corresponds to the number of inventory slots needed to be able to take everything out if no merges occur */ long itemcount = count_contents(obj, FALSE, FALSE, TRUE, FALSE); - Sprintf(eos(bp), " containing %ld item%s", itemcount, - plur(itemcount)); + ConcatF2(bp, 0, " containing %ld item%s", itemcount, plur(itemcount)); } switch (is_weptool(obj) ? WEAPON_CLASS : obj->oclass) { case AMULET_CLASS: if (obj->owornmask & W_AMUL) - Strcat(bp, " (being worn)"); + Concat(bp, 0, " (being worn)"); break; case ARMOR_CLASS: if (obj->owornmask & W_ARMOR) { - Strcat(bp, (obj == uskin) ? " (embedded in your skin)" - /* in case of perm_invent update while Wear/Takeoff - is in progress; check doffing() before donning() - because donning() returns True for both cases */ - : doffing(obj) ? " (being doffed)" - : donning(obj) ? " (being donned)" - : " (being worn)"); - /* slippery fingers is an intrinsic condition of the hero - rather than extrinsic condition of objects, but gloves - are described as slippery when hero has slippery fingers */ - if (obj == uarmg && Glib) /* just appended "(something)", - * change to "(something; slippery)" */ - Strcpy(rindex(bp, ')'), "; slippery)"); + Concat(bp, 0, + (obj == uskin) ? " (embedded in your skin)" + /* in case of perm_invent update while Wear/Takeoff + is in progress; check doffing() before donning() + because donning() returns True for both cases */ + : doffing(obj) ? " (being doffed)" + : donning(obj) ? " (being donned)" + : " (being worn)"); + /* we just added a parenthesized phrase, but the right paren + might be absent if the appended string got truncated */ + if (bp_eos[-1] == ')') { + /* slippery fingers is an intrinsic condition of the hero + rather than extrinsic condition of objects, but gloves + are described as slippery when hero has slippery fingers */ + if (obj == uarmg && Glib) /* just appended "(something)", + * replace paren, changing that + * to be "(something; slippery)" */ + Concat(bp, 1, "; slippery)"); + } + if (bp_eos[-1] == ')') { + /* there could be light-emitting artifact gloves someday, + so add 'lit' separately from 'slippery' rather than via + 'else if' after uarmg+Glib */ + if (!Blind && obj->lamplit && artifact_light(obj)) + ConcatF1(bp, 1, ", %s lit)", arti_light_description(obj)); + } } + FALLTHROUGH; /*FALLTHRU*/ case WEAPON_CLASS: if (ispoisoned) Strcat(prefix, "poisoned "); add_erosion_words(obj, prefix); if (known) { - Strcat(prefix, sitoa(obj->spe)); - Strcat(prefix, " "); + Sprintf(eos(prefix), "%+d ", obj->spe); /* sitoa(obj->spe)+" " */ } break; case TOOL_CLASS: if (obj->owornmask & (W_TOOL | W_SADDLE)) { /* blindfold */ - Strcat(bp, " (being worn)"); + Concat(bp, 0, " (being worn)"); break; } if (obj->otyp == LEASH && obj->leashmon != 0) { struct monst *mlsh = find_mid(obj->leashmon, FM_FMON); - if (!mlsh) { - impossible("leashed monster not on this level"); - obj->leashmon = 0; + if (mlsh && !DEADMONSTER(mlsh)) { + ConcatF1(bp, 0, " (attached to %s)", noit_mon_nam(mlsh)); } else { - Sprintf(eos(bp), " (attached to %s)", - noit_mon_nam(mlsh)); + if (mlsh) /*&& DEADMONSTER(mlsh)*/ + impossible("leashed %s #%u is dead", + mon_pmname(mlsh), (unsigned) obj->leashmon); + else + impossible("leashed monster #%u not found", + (unsigned) obj->leashmon); + obj->leashmon = 0; } break; } if (obj->otyp == CANDELABRUM_OF_INVOCATION) { - if (!obj->spe) - Strcpy(tmpbuf, "no"); - else - Sprintf(tmpbuf, "%d", obj->spe); - Sprintf(eos(bp), " (%s candle%s%s)", tmpbuf, plur(obj->spe), + char suffix[20]; /* longest value is "s attached" */ + + /* separately formatted suffix avoids need for ConcatF3() */ + Sprintf(suffix, "%s%s", plur(obj->spe), !obj->lamplit ? " attached" : ", lit"); + ConcatF2(bp, 0, " (%d of 7 candle%s)", obj->spe, suffix); break; } else if (obj->otyp == OIL_LAMP || obj->otyp == MAGIC_LAMP || obj->otyp == BRASS_LANTERN || Is_candle(obj)) { - if (Is_candle(obj) - && obj->age < 20L * (long) objects[obj->otyp].oc_cost) - Strcat(prefix, "partly used "); + if (Is_candle(obj)) { + anything timer; + long full_burn_time = 20L * (long) objects[obj->otyp].oc_cost, + turns_left = obj->age; + + if (obj->lamplit) { + timer = cg.zeroany; + timer.a_obj = obj; + /* without this, wishing for "lit candle" yields + "partly used candle (lit)" because the time it can + burn gets adjusted when it becomes lit; matters for + the message as it gets added to invent and also if it + gets snuffed out immediately (where it will end up as + not partly used after all) */ + turns_left += peek_timer(BURN_OBJECT, &timer) - svm.moves; + } + if (turns_left < full_burn_time) + Strcat(prefix, "partly used "); + } if (obj->lamplit) - Strcat(bp, " (lit)"); + Concat(bp, 0, " (lit)"); break; } if (objects[obj->otyp].oc_charged) @@ -1141,25 +1483,22 @@ unsigned doname_flags; case WAND_CLASS: charges: if (known) - Sprintf(eos(bp), " (%d:%d)", (int) obj->recharged, obj->spe); + ConcatF2(bp, 0, " (%d:%d)", (int) obj->recharged, obj->spe); break; case POTION_CLASS: if (obj->otyp == POT_OIL && obj->lamplit) - Strcat(bp, " (lit)"); + Concat(bp, 0, " (lit)"); break; case RING_CLASS: - ring: + ring: /* normal rings reach here 'naturally'; meat ring jumps here */ if (obj->owornmask & W_RINGR) - Strcat(bp, " (on right "); + Concat(bp, 0, " (on right "); if (obj->owornmask & W_RINGL) - Strcat(bp, " (on left "); - if (obj->owornmask & W_RING) { - Strcat(bp, body_part(HAND)); - Strcat(bp, ")"); - } + Concat(bp, 0, " (on left "); + if (obj->owornmask & W_RING) /* either left or right */ + ConcatF1(bp, 0,"%s)", body_part(HAND)); if (known && objects[obj->otyp].oc_charged) { - Strcat(prefix, sitoa(obj->spe)); - Strcat(prefix, " "); + Sprintf(eos(prefix), "%+d ", obj->spe); /* sitoa(obj->spe)+" " */ } break; case FOOD_CLASS: @@ -1171,118 +1510,179 @@ unsigned doname_flags; "corpse" is already in the buffer returned by xname() */ unsigned cxarg = (((obj->quan != 1L) ? 0 : CXN_ARTICLE) | CXN_NOCORPSE); - char *cxstr = corpse_xname(obj, prefix, cxarg); + char *cxstr, *save_xnamep; + /* corpse_xname() sets xnamep; callers other than doname_base() + itself shouldn't care about xnamep (pointer to start of + current obuf[]) but keep it accurate anyway */ + save_xnamep = gx.xnamep; + cxstr = corpse_xname(obj, prefix, cxarg); Sprintf(prefix, "%s ", cxstr); /* avoid having doname(corpse) consume an extra obuf */ releaseobuf(cxstr); + gx.xnamep = save_xnamep; } else if (obj->otyp == EGG) { #if 0 /* corpses don't tell if they're stale either */ if (known && stale_egg(obj)) Strcat(prefix, "stale "); #endif - if (omndx >= LOW_PM - && (known || (mvitals[omndx].mvflags & MV_KNOWS_EGG))) { - Strcat(prefix, mons[omndx].mname); + if (ismnum(omndx) + && (known || (svm.mvitals[omndx].mvflags & MV_KNOWS_EGG))) { + Strcat(prefix, mons[omndx].pmnames[NEUTRAL]); Strcat(prefix, " "); - if (obj->spe) - Strcat(bp, " (laid by you)"); + if (obj->spe == 1) + Concat(bp, 0, " (laid by you)"); } - } - if (obj->otyp == MEAT_RING) + } else if (obj->otyp == MEAT_RING) { goto ring; + } break; case BALL_CLASS: case CHAIN_CLASS: add_erosion_words(obj, prefix); - if (obj->owornmask & W_BALL) - Strcat(bp, " (chained to you)"); + if (obj->owornmask & (W_BALL | W_CHAIN)) + ConcatF1(bp, 0, " (%s to you)", + (obj->owornmask & W_BALL) ? "chained" : "attached"); break; } - if ((obj->owornmask & W_WEP) && !mrg_to_wielded) { - if (obj->quan != 1L) { - Strcat(bp, " (wielded)"); + if ((obj->otyp == STATUE || obj->otyp == CORPSE || obj->otyp == FIGURINE) + && wizard && iflags.wizmgender) { + int cgend = (obj->spe & CORPSTAT_GENDER), + mgend = ((cgend == CORPSTAT_MALE) ? MALE + : (cgend == CORPSTAT_FEMALE) ? FEMALE + : NEUTRAL); + + ConcatF1(bp, 0, " (%s)", + (cgend != CORPSTAT_RANDOM) ? genders[mgend].adj + : "unspecified gender"); + } + + if ((obj->owornmask & W_WEP) && !gm.mrg_to_wielded) { + boolean twoweap_primary = (obj == uwep && u.twoweap), + tethered = (obj->otyp == AKLYS); + + + /* use alternate phrasing for non-weapons and for wielded ammo + (arrows, bolts), or missiles (darts, shuriken, boomerangs) + except when those are being actively dual-wielded where the + regular phrasing will list them as "in right hand" to + contrast with secondary weapon's "in left hand" */ + if ((obj->quan != 1L + || ((obj->oclass == WEAPON_CLASS) + ? (is_ammo(obj) || is_missile(obj)) + : !is_weptool(obj))) + && !twoweap_primary) { + Concat(bp, 0, " (wielded)"); } else { const char *hand_s = body_part(HAND); - - if (bimanual(obj)) - hand_s = makeplural(hand_s); + char *obufp, handsbuf[40]; + + if (bimanual(obj)) { /* "hands" */ + hand_s = strcpy(handsbuf, obufp = makeplural(hand_s)); + releaseobuf(obufp); + } else { /* "right hand" or "left hand" */ + Sprintf(handsbuf, "%s %s", + URIGHTY ? "right" : "left", hand_s); + hand_s = handsbuf; + } /* note: Sting's glow message, if added, will insert text in front of "(weapon in hand)"'s closing paren */ - Sprintf(eos(bp), " (%sweapon in %s)", - (obj->otyp == AKLYS) ? "tethered " : "", hand_s); - - if (warn_obj_cnt && obj == uwep && (EWarn_of_mon & W_WEP) != 0L) { - if (!Blind) /* we know bp[] ends with ')'; overwrite that */ - Sprintf(eos(bp) - 1, ", %s %s)", - glow_verb(warn_obj_cnt, TRUE), - glow_color(obj->oartifact)); + ConcatF2(bp, 0, " (%s %s)", + tethered ? "tethered to" + : twoweap_primary ? "wielded in" + : "weapon in", + hand_s); + + /* we just added a parenthesized phrase, but the right paren + might be absent if the appended string got truncated */ + if (!Blind && bpspaceleft && bp_eos[-1] == ')') { + if (gw.warn_obj_cnt && obj == uwep + && (EWarn_of_mon & W_WEP) != 0L) + /* we know bp[] ends with ')'; overwrite that */ + ConcatF2(bp, 1, ", %s %s)", + glow_verb(gw.warn_obj_cnt, TRUE), + glow_color(obj->oartifact)); + else if (obj->lamplit && artifact_light(obj)) + /* as above, overwrite known closing paren */ + ConcatF1(bp, 1, ", %s lit)", + arti_light_description(obj)); } } } if (obj->owornmask & W_SWAPWEP) { if (u.twoweap) - Sprintf(eos(bp), " (wielded in other %s)", body_part(HAND)); + ConcatF2(bp, 0, " (wielded in %s %s)", + URIGHTY ? "left" : "right", body_part(HAND)); else - Strcat(bp, " (alternate weapon; not wielded)"); + /* TODO: rephrase this when obj isn't a weapon or weptool */ + ConcatF1(bp, 0, " (alternate weapon%s; not wielded)", + plur(obj->quan)); } if (obj->owornmask & W_QUIVER) { + int Qtyp; + switch (obj->oclass) { case WEAPON_CLASS: - if (is_ammo(obj)) { - if (objects[obj->otyp].oc_skill == -P_BOW) { - /* Ammo for a bow */ - Strcat(bp, " (in quiver)"); - break; - } else { - /* Ammo not for a bow */ - Strcat(bp, " (in quiver pouch)"); - break; - } - } else { - /* Weapons not considered ammo */ - Strcat(bp, " (at the ready)"); - break; - } - /* Small things and ammo not for a bow */ + Qtyp = !is_ammo(obj) ? 3 /* not ammo: "at the ready" */ + : (objects[obj->otyp].oc_skill != -P_BOW) ? 2 /* non-bow */ + : 1; /* ammo for a bow: "in quiver" */ + break; case RING_CLASS: case AMULET_CLASS: case WAND_CLASS: case COIN_CLASS: case GEM_CLASS: - Strcat(bp, " (in quiver pouch)"); + Qtyp = 2; /* small, non-bow: "in quiver pouch" */ break; default: /* odd things */ - Strcat(bp, " (at the ready)"); + Qtyp = 3; /* "at the ready" */ + break; } + ConcatF1(bp, 0, " (%s)", + (Qtyp == 1) ? "in quiver" + : (Qtyp == 2) ? "in quiver pouch" + : "at the ready"); } + /* treat 'restoring' like suppress_price because shopkeeper and bill might not be available yet while restore is in progress (objects won't normally be formatted during that time, but if - 'perm_invent' is enabled then they might be) */ - if (iflags.suppress_price || restoring) { - ; /* don't attempt to obtain any stop pricing, even if 'with_price' */ + 'perm_invent' is enabled then they might be [not any more...]) */ + if (iflags.suppress_price || program_state.restoring) { + ; /* don't attempt to obtain any shop pricing, even if 'with_price' */ } else if (is_unpaid(obj)) { /* in inventory or in container in invent */ - long quotedprice = unpaid_cost(obj, TRUE); + char pricebuf[40]; + long quotedprice = unpaid_cost(obj, COST_CONTENTS); + + /* separately formatted suffix avoids need for ConcatF3() */ + Sprintf(pricebuf, "%ld %s", quotedprice, currency(quotedprice)); + ConcatF2(bp, 0, " (%s, %s)", + obj->unpaid ? "unpaid" : "contents", pricebuf); - Sprintf(eos(bp), " (%s, %s%ld %s)", - obj->unpaid ? "unpaid" : "contents", - globwt(obj, globbuf, &weightshown), - quotedprice, currency(quotedprice)); + record_price_quote(obj->otyp, quotedprice / obj->quan, TRUE); } else if (with_price) { /* on floor or in container on floor */ int nochrg = 0; long price = get_cost_of_shop_item(obj, &nochrg); + if (price > 0L) { + char pricebuf[40]; + + Sprintf(pricebuf, "%ld %s", price, currency(price)); + ConcatF2(bp, 0, " (%s, %s)", + nochrg ? "contents" : "for sale", pricebuf); + } else if (nochrg > 0) { + Concat(bp, 0, " (no charge)"); + } else if (iflags.pricequotes && !objects[obj->otyp].oc_name_known) { + append_price_quote(bp, &bp_eos, obj->otyp); + } + if (price > 0L) - Sprintf(eos(bp), " (%s, %s%ld %s)", - nochrg ? "contents" : "for sale", - globwt(obj, globbuf, &weightshown), - price, currency(price)); - else if (nochrg > 0) - Sprintf(eos(bp), " (%sno charge)", - globwt(obj, globbuf, &weightshown)); + record_price_quote(obj->otyp, price / obj->quan, TRUE); + } else if (iflags.pricequotes && !objects[obj->otyp].oc_name_known) { + append_price_quote(bp, &bp_eos, obj->otyp); } + if (!strncmp(prefix, "a ", 2)) { /* save current prefix, without "a "; might be empty */ Strcpy(tmpbuf, prefix + 2); @@ -1295,34 +1695,77 @@ unsigned doname_flags; /* show weight for items (debug tourist info); "aum" is stolen from Crawl's "Arbitrary Unit of Measure" */ if (wizard && iflags.wizweight) { - /* wizard mode user has asked to see object weights; - globs with shop pricing attached already include it */ - if (!weightshown) - Sprintf(eos(bp), " (%u aum)", obj->owt); + /* wizard mode user has asked to see object weights */ + if (with_price && bp_eos[-1] == ')') + ConcatF1(bp, 1, ", %u aum)", obj->owt); + else + ConcatF1(bp, 0, " (%u aum)", obj->owt); + + /* ConcatF1(bp) updates bp_eos and bpspaceleft but we're done + with them now; add a fake use so compiler won't complain + about a variable assignment that won't be subsequently used */ + nhUse(bp_eos); + nhUse(bpspaceleft); } + bp = strprepend(bp, prefix); + + /* + * Last gasp bounds check. + * + * If caller intends this to be for a menu entry, make sure that + * there is some room to combine with menu selector prefix without + * exceeding BUFSZ-1. + * + * offsetbp=4: width of menu entry selector text: "c - " for tty. + * For curses, that wastes a char since it only needs 3: "c) ". + * + * Reaching full BUFSZ-1 length can't happen unless both doname + * (BUFSZ-PREFIX) and strprepend (PREFIX) use up all available + * space or one of them overflows without being detected. + */ + if (strlen(bp) > BUFSZ - 1) { + paniclog("doname", bp); + /* ideally this will never happen; if xnamep is any obuf[] + other than the last, overflow here would be relatively + benign and we could probably keep going */ + panic("doname: long object description overflow."); + /*NOTREACHED*/ + } else { + static int doname_full = 0; + int offsetbp = for_menu ? 4 : 0; + + if (strlen(bp) + offsetbp >= BUFSZ - 1) { + /* for !offsetbp, we'll only get here if strlen(bp)==BUFSZ-1 */ + if (!doname_full++) { + paniclog("doname", bp); + Sprintf(tmpbuf, "long object description%s.", + offsetbp ? " truncated for menu use" : ""); + paniclog("doname", tmpbuf); + } + bp[BUFSZ - 1 - offsetbp] = '\0'; + } + } + return bp; } char * -doname(obj) -struct obj *obj; +doname(struct obj *obj) { return doname_base(obj, (unsigned) 0); } /* Name of object including price. */ char * -doname_with_price(obj) -struct obj *obj; +doname_with_price(struct obj *obj) { return doname_base(obj, DONAME_WITH_PRICE); } /* "some" instead of precise quantity if obj->dknown not set */ char * -doname_vague_quan(obj) -struct obj *obj; +doname_vague_quan(struct obj *obj) { /* Used by farlook. * If it hasn't been seen up close and quantity is more than one, @@ -1341,15 +1784,14 @@ struct obj *obj; /* used from invent.c */ boolean -not_fully_identified(otmp) -struct obj *otmp; +not_fully_identified(struct obj *otmp) { /* gold doesn't have any interesting attributes [yet?] */ if (otmp->oclass == COIN_CLASS) return FALSE; /* always fully ID'd */ /* check fundamental ID hallmarks first */ if (!otmp->known || !otmp->dknown -#ifdef MAIL +#ifdef MAIL_STRUCTURES || (!otmp->bknown && otmp->otyp != SCR_MAIL) #else || !otmp->bknown @@ -1373,20 +1815,18 @@ struct obj *otmp; && otmp->oclass != BALL_CLASS)) /* (useless) */ return FALSE; else /* lack of `rknown' only matters for vulnerable objects */ - return (boolean) (is_rustprone(otmp) || is_corrodeable(otmp) - || is_flammable(otmp)); + return (boolean) is_damageable(otmp); } /* format a corpse name (xname() omits monster type; doname() calls us); eatcorpse() also uses us for death reason when eating tainted glob */ char * -corpse_xname(otmp, adjective, cxn_flags) -struct obj *otmp; -const char *adjective; -unsigned cxn_flags; /* bitmask of CXN_xxx values */ +corpse_xname( + struct obj *otmp, + const char *adjective, + unsigned cxn_flags) /* bitmask of CXN_xxx values */ { - /* some callers [aobjnam()] rely on prefix area that xname() sets aside */ - char *nambuf = nextobuf() + PREFIX; + char *nambuf; int omndx = otmp->corpsenm; boolean ignore_quan = (cxn_flags & CXN_SINGULAR) != 0, /* suppress "the" from "the unique monster corpse" */ @@ -1399,21 +1839,20 @@ unsigned cxn_flags; /* bitmask of CXN_xxx values */ omit_corpse = (cxn_flags & CXN_NOCORPSE) != 0, possessive = FALSE, glob = (otmp->otyp != CORPSE && otmp->globby); - const char *mname; + const char *mnam; + + /* some callers [aobjnam()] rely on prefix area that xname() sets aside */ + gx.xnamep = nextobuf(); + nambuf = gx.xnamep + PREFIX; if (glob) { - mname = OBJ_NAME(objects[otmp->otyp]); /* "glob of " */ + mnam = OBJ_NAME(objects[otmp->otyp]); /* "glob of " */ } else if (omndx == NON_PM) { /* paranoia */ - mname = "thing"; - /* [Possible enhancement: check whether corpse has monster traits - attached in order to use priestname() for priests and minions.] */ - } else if (omndx == PM_ALIGNED_PRIEST) { - /* avoid "aligned priest"; it just exposes internal details */ - mname = "priest"; + mnam = "thing"; } else { - mname = mons[omndx].mname; + mnam = obj_pmname(otmp); if (the_unique_pm(&mons[omndx]) || type_is_pname(&mons[omndx])) { - mname = s_suffix(mname); + mnam = s_suffix(mnam); possessive = TRUE; /* don't precede personal name like "Medusa" with an article */ if (type_is_pname(&mons[omndx])) @@ -1436,16 +1875,19 @@ unsigned cxn_flags; /* bitmask of CXN_xxx values */ to precede capitalized unique monsters (pnames are handled above) */ if (the_prefix) Strcat(nambuf, "the "); + /* note: over time, various instances of the(mon_name()) have crept + into the code, so the() has been modified to deal with capitalized + monster names; we could switch to using it below like an() */ if (!adjective || !*adjective) { /* normal case: newt corpse */ - Strcat(nambuf, mname); + Strcat(nambuf, mnam); } else { /* adjective positioning depends upon format of monster name */ if (possessive) /* Medusa's cursed partly eaten corpse */ - Sprintf(eos(nambuf), "%s %s", mname, adjective); + Sprintf(eos(nambuf), "%s %s", mnam, adjective); else /* cursed partly eaten troll corpse */ - Sprintf(eos(nambuf), "%s %s", adjective, mname); + Sprintf(eos(nambuf), "%s %s", adjective, mnam); /* in case adjective has a trailing space, squeeze it out */ mungspaces(nambuf); /* doname() might include a count in the adjective argument; @@ -1465,18 +1907,21 @@ unsigned cxn_flags; /* bitmask of CXN_xxx values */ } } - /* it's safe to overwrite our nambuf after an() has copied - its old value into another buffer */ - if (any_prefix) - Strcpy(nambuf, an(nambuf)); + /* it's safe to overwrite our nambuf[] after an() has copied its + old value into another buffer; and once _that_ has been copied, + the obuf[] returned by an() can be made available for re-use */ + if (any_prefix) { + char *obufp; + Strcpy(nambuf, obufp = an(nambuf)); + releaseobuf(obufp); + } return nambuf; } /* xname doesn't include monster type for "corpse"; cxname does */ char * -cxname(obj) -struct obj *obj; +cxname(struct obj *obj) { if (obj->otyp == CORPSE) return corpse_xname(obj, (const char *) 0, CXN_NORMAL); @@ -1485,8 +1930,7 @@ struct obj *obj; /* like cxname, but ignores quantity */ char * -cxname_singular(obj) -struct obj *obj; +cxname_singular(struct obj *obj) { if (obj->otyp == CORPSE) return corpse_xname(obj, (const char *) 0, CXN_SINGULAR); @@ -1495,8 +1939,7 @@ struct obj *obj; /* treat an object as fully ID'd when it might be used as reason for death */ char * -killer_xname(obj) -struct obj *obj; +killer_xname(struct obj *obj) { struct obj save_obj; unsigned save_ocknown; @@ -1514,7 +1957,8 @@ struct obj *obj; save_oname = ONAME(obj); /* killer name should be more specific than general xname; however, exact - info like blessed/cursed and rustproof makes things be too verbose */ + info like blessed/cursed and rustproof makes things be too verbose; set + dknown (not observe_object) because dead characters don't observe */ obj->known = obj->dknown = 1; obj->bknown = obj->rknown = obj->greased = 0; /* if character is a priest[ess], bknown will get toggled back on */ @@ -1562,11 +2006,11 @@ struct obj *obj; /* xname,doname,&c with long results reformatted to omit some stuff */ char * -short_oname(obj, func, altfunc, lenlimit) -struct obj *obj; -char *FDECL((*func), (OBJ_P)), /* main formatting routine */ - *FDECL((*altfunc), (OBJ_P)); /* alternate for shortest result */ -unsigned lenlimit; +short_oname( + struct obj *obj, + char *(*func)(OBJ_P), /* main formatting routine */ + char *(*altfunc)(OBJ_P), /* alternate for shortest result */ + unsigned lenlimit) { struct obj save_obj; char unamebuf[12], onamebuf[12], *save_oname, *save_uname, *outbuf; @@ -1644,9 +2088,7 @@ unsigned lenlimit; * Used if only one of a collection of objects is named (e.g. in eat.c). */ const char * -singular(otmp, func) -register struct obj *otmp; -char *FDECL((*func), (OBJ_P)); +singular(struct obj *otmp, char *(*func)(OBJ_P)) { long savequan; char *nam; @@ -1663,26 +2105,35 @@ char *FDECL((*func), (OBJ_P)); } /* pick "", "a ", or "an " as article for 'str'; used by an() and doname() */ -STATIC_OVL char * -just_an(outbuf, str) -char *outbuf; -const char *str; +char * +just_an(char *outbuf, const char *str) { char c0; *outbuf = '\0'; c0 = lowc(*str); - if (!str[1]) { - /* single letter; might be used for named fruit */ - Strcpy(outbuf, index("aefhilmnosx", c0) ? "an " : "a "); - } else if (!strncmpi(str, "the ", 4) || !strcmpi(str, "molten lava") - || !strcmpi(str, "iron bars") || !strcmpi(str, "ice")) { + if (!str[1] || str[1] == ' ') { + /* single letter; might be used for named fruit or a musical note */ + Strcpy(outbuf, strchr("aefhilmnosx", c0) ? "an " : "a "); + } else if (!strncmpi(str, "the ", 4) + /* these probably shouldn't be handled here because doing so + impacts inventory when using them for named fruit */ + || !strcmpi(str, "molten lava") + || !strcmpi(str, "iron bars") + || !strcmpi(str, "ice") + ) { ; /* no article */ } else { - if ((index(vowels, c0) && strncmpi(str, "one-", 4) - && strncmpi(str, "eucalyptus", 10) && strncmpi(str, "unicorn", 7) - && strncmpi(str, "uranium", 7) && strncmpi(str, "useful", 6)) - || (index("x", c0) && !index(vowels, lowc(str[1])))) + /* normal case is "an " or "a " */ + if ((strchr(vowels, c0) /* some exceptions warranting "a " */ + /* 'wun' initial sound */ + && (strncmpi(str, "one", 3) || (str[3] && !strchr("-_ ", str[3]))) + /* long 'u' initial sound */ + && strncmpi(str, "eu", 2) /* "eucalyptus leaf" */ + && strncmpi(str, "uke", 3) && strncmpi(str, "ukulele", 7) + && strncmpi(str, "unicorn", 7) && strncmpi(str, "uranium", 7) + && strncmpi(str, "useful", 6)) /* "useful tool" */ + || (c0 == 'x' && !strchr(vowels, lowc(str[1])))) Strcpy(outbuf, "an "); else Strcpy(outbuf, "a "); @@ -1691,8 +2142,7 @@ const char *str; } char * -an(str) -const char *str; +an(const char *str) { char *buf = nextobuf(); @@ -1701,12 +2151,11 @@ const char *str; return strcpy(buf, "an []"); } (void) just_an(buf, str); - return strncat(buf, str, BUFSZ - 1 - (unsigned) strlen(buf)); + return strncat(buf, str, BUFSZ - 1 - Strlen(buf)); } char * -An(str) -const char *str; +An(const char *str) { char *tmp = an(str); @@ -1719,9 +2168,9 @@ const char *str; * Use type_is_pname() for monster names, not the(). the() is idempotent. */ char * -the(str) -const char *str; +the(const char *str) { + const char *aname; char *buf = nextobuf(); boolean insert_the = FALSE; @@ -1734,21 +2183,30 @@ const char *str; Strcpy(&buf[1], str + 1); return buf; } else if (*str < 'A' || *str > 'Z' + /* some capitalized monster names want "the", others don't */ + || CapitalMon(str) /* treat named fruit as not a proper name, even if player - has assigned a capitalized proper name as his/her fruit */ - || fruit_from_name(str, TRUE, (int *) 0)) { + has assigned a capitalized proper name as his/her fruit, + unless it matches an artifact name */ + || (fruit_from_name(str, TRUE, (int *) 0) + && ((aname = artifact_name(str, (short *) 0, FALSE)) == 0 + || strncmpi(aname, "the ", 4) == 0))) { /* not a proper name, needs an article */ insert_the = TRUE; } else { /* Probably a proper name, might not need an article */ - register char *tmp, *named, *called; + char *named, *called; + const char *tmp; int l; /* some objects have capitalized adjectives in their names */ - if (((tmp = rindex(str, ' ')) != 0 || (tmp = rindex(str, '-')) != 0) + if (((tmp = strrchr(str, ' ')) != 0 || (tmp = strrchr(str, '-')) != 0) && (tmp[1] < 'A' || tmp[1] > 'Z')) { - insert_the = TRUE; - } else if (tmp && index(str, ' ') < tmp) { /* has spaces */ + /* insert "the" unless we have an apostrophe (where we assume + we're dealing with "Unique's corpse" when "Unique" wasn't + caught by CapitalMon() above) */ + insert_the = !strchr(str, '\''); + } else if (tmp && strchr(str, ' ') < tmp) { /* has spaces */ /* it needs an article if the name contains "of" */ tmp = strstri(str, " of "); named = strstri(str, " named "); @@ -1759,7 +2217,7 @@ const char *str; if (tmp && (!named || tmp < named)) /* found an "of" */ insert_the = TRUE; /* stupid special case: lacks "of" but needs "the" */ - else if (!named && (l = strlen(str)) >= 31 + else if (!named && (l = Strlen(str)) >= 31 && !strcmp(&str[l - 31], "Platinum Yendorian Express Card")) insert_the = TRUE; @@ -1769,12 +2227,11 @@ const char *str; Strcpy(buf, "the "); else buf[0] = '\0'; - return strncat(buf, str, BUFSZ - 1 - (unsigned) strlen(buf)); + return strncat(buf, str, BUFSZ - 1 - Strlen(buf)); } char * -The(str) -const char *str; +The(const char *str) { char *tmp = the(str); @@ -1784,9 +2241,7 @@ const char *str; /* returns "count cxname(otmp)" or just cxname(otmp) if count == 1 */ char * -aobjnam(otmp, verb) -struct obj *otmp; -const char *verb; +aobjnam(struct obj *otmp, const char *verb) { char prefix[PREFIX]; char *bp = cxname(otmp); @@ -1804,9 +2259,7 @@ const char *verb; /* combine yname and aobjnam eg "your count cxname(otmp)" */ char * -yobjnam(obj, verb) -struct obj *obj; -const char *verb; +yobjnam(struct obj *obj, const char *verb) { char *s = aobjnam(obj, verb); @@ -1815,7 +2268,7 @@ const char *verb; if (!carried(obj) || !obj_is_pname(obj) || obj->oartifact >= ART_ORB_OF_DETECTION) { char *outbuf = shk_your(nextobuf(), obj); - int space_left = BUFSZ - 1 - strlen(outbuf); + int space_left = BUFSZ - 1 - Strlen(outbuf); s = strncat(outbuf, s, space_left); } @@ -1824,11 +2277,9 @@ const char *verb; /* combine Yname2 and aobjnam eg "Your count cxname(otmp)" */ char * -Yobjnam2(obj, verb) -struct obj *obj; -const char *verb; +Yobjnam2(struct obj *obj, const char *verb) { - register char *s = yobjnam(obj, verb); + char *s = yobjnam(obj, verb); *s = highc(*s); return s; @@ -1836,9 +2287,7 @@ const char *verb; /* like aobjnam, but prepend "The", not count, and use xname */ char * -Tobjnam(otmp, verb) -struct obj *otmp; -const char *verb; +Tobjnam(struct obj *otmp, const char *verb) { char *bp = The(xname(otmp)); @@ -1851,8 +2300,7 @@ const char *verb; /* capitalized variant of doname() */ char * -Doname2(obj) -struct obj *obj; +Doname2(struct obj *obj) { char *s = doname(obj); @@ -1860,34 +2308,55 @@ struct obj *obj; return s; } -#if 0 /* stalled-out work in progress */ -/* Doname2() for itemized buying of 'obj' from a shop */ +/* doname() for itemized buying of 'obj' from a shop */ char * -payDoname(obj) -struct obj *obj; +paydoname(struct obj *obj) { static const char and_contents[] = " and its contents"; - char *p = doname(obj); + char *p; + unsigned save_cknown = obj->cknown; + boolean save_wizweight = iflags.wizweight; + + if (Has_contents(obj)) + obj->cknown = 0; + /* avoid showing item weights to unclutter billing's pay-menu a bit */ + iflags.wizweight = FALSE; + /* suppress invent-style price; caller will add billing-style price */ + iflags.suppress_price++; + p = doname_base(obj, 0U); + iflags.suppress_price--; + iflags.wizweight = save_wizweight; + + if (Has_contents(obj)) { + /* buy_container() sets no_charge for a container that has just + been purchased so that when paydoname() is called by + shk_names_obj(), we'll provide "a/an " instead of + "your " */ + if (!obj->no_charge) { + if (!strncmp(p, "a ", 2)) + p += 2; + else if (!strncmp(p, "an ", 3)) + p += 3; + p = strprepend(p, obj->unpaid ? "an unpaid " : "your "); + } - if (Is_container(obj) && !obj->cknown) { - if (obj->unpaid) { - if ((int) strlen(p) + sizeof and_contents - 1 < BUFSZ - PREFIX) - Strcat(p, and_contents); - *p = highc(*p); - } else { - p = strprepend(p, "Contents of "); + if (!obj->cknown) { + if (obj->unpaid) { + if ((int) strlen(p) + sizeof and_contents - 1 + < BUFSZ - PREFIX) + Strcat(p, and_contents); + } else { + p = strprepend(p, "the contents of "); + } } - } else { - *p = highc(*p); } + obj->cknown = save_cknown; return p; } -#endif /*0*/ /* returns "[your ]xname(obj)" or "Foobar's xname(obj)" or "the xname(obj)" */ char * -yname(obj) -struct obj *obj; +yname(struct obj *obj) { char *s = cxname(obj); @@ -1896,7 +2365,7 @@ struct obj *obj; if (!carried(obj) || !obj_is_pname(obj) || obj->oartifact >= ART_ORB_OF_DETECTION) { char *outbuf = shk_your(nextobuf(), obj); - int space_left = BUFSZ - 1 - strlen(outbuf); + int space_left = BUFSZ - 1 - Strlen(outbuf); s = strncat(outbuf, s, space_left); } @@ -1906,8 +2375,7 @@ struct obj *obj; /* capitalized variant of yname() */ char * -Yname2(obj) -struct obj *obj; +Yname2(struct obj *obj) { char *s = yname(obj); @@ -1920,20 +2388,18 @@ struct obj *obj; * or "the minimal_xname(obj)" */ char * -ysimple_name(obj) -struct obj *obj; +ysimple_name(struct obj *obj) { char *outbuf = nextobuf(); char *s = shk_your(outbuf, obj); /* assert( s == outbuf ); */ - int space_left = BUFSZ - 1 - strlen(s); + int space_left = BUFSZ - 1 - Strlen(s); return strncat(s, minimal_xname(obj), space_left); } /* capitalized variant of ysimple_name() */ char * -Ysimple_name2(obj) -struct obj *obj; +Ysimple_name2(struct obj *obj) { char *s = ysimple_name(obj); @@ -1941,55 +2407,99 @@ struct obj *obj; return s; } + /* + * FIXME: + * simpleonames(), ansimpleoname(), and thesimpleoname() need to + * know the beginning of the obuf[] they use so that they can + * guard against buffer overflow when pluralizing (is that an + * actual word?) or inserting "an" or "the". + * + * minimal_xname() returns a call to xname() which writes into + * the middle of its obuf[] then backs up to accomodate a prefix, + * so BUFSZ is not a reliable limit for the length of the result. + * + * [Overflow likely moot. Since the formatted object name has + * user-supplied name suppressed, the length is sure to be short + * enough to added plural suffix or "an" or "the" prefix.] + */ + /* "scroll" or "scrolls" */ char * -simpleonames(obj) -struct obj *obj; +simpleonames(struct obj *obj) { - char *simpleoname = minimal_xname(obj); + char *obufp, *simpleoname = minimal_xname(obj); - if (obj->quan != 1L) - simpleoname = makeplural(simpleoname); + if (obj->quan != 1L) { + /* 'simpleoname' points to an obuf; makeplural() will allocate + another one and only that one can be explicitly released for + re-use, so this is slightly convoluted to cope with that; + makeplural() will be fully evaluated and done with its input + argument before strcpy() touches its output argument */ + Strcpy(simpleoname, obufp = makeplural(simpleoname)); + releaseobuf(obufp); + } return simpleoname; } /* "a scroll" or "scrolls"; "a silver bell" or "the Bell of Opening" */ char * -ansimpleoname(obj) -struct obj *obj; +ansimpleoname(struct obj *obj) { - char *simpleoname = simpleonames(obj); + char *obufp, *simpleoname = simpleonames(obj); int otyp = obj->otyp; /* prefix with "the" if a unique item, or a fake one imitating same, - has been formatted with its actual name (we let typename() handle + has been formatted with its actual name (we let minimal_xname() handle any `known' and `dknown' checking necessary) */ if (otyp == FAKE_AMULET_OF_YENDOR) otyp = AMULET_OF_YENDOR; - if (objects[otyp].oc_unique - && !strcmp(simpleoname, OBJ_NAME(objects[otyp]))) - return the(simpleoname); - - /* simpleoname is singular if quan==1, plural otherwise */ - if (obj->quan == 1L) - simpleoname = an(simpleoname); + if (objects[otyp].oc_unique && OBJ_NAME(objects[otyp]) + && !strcmp(simpleoname, OBJ_NAME(objects[otyp]))) { + /* the() will allocate another obuf[]; we want to avoid using two */ + obufp = the(simpleoname); + Strcpy(simpleoname, obufp); + releaseobuf(obufp); + } else if (obj->quan == 1L) { + /* simpleoname[] is singular if quan==1, plural otherwise; + an() will allocate another obuf[]; we want to avoid using two */ + obufp = an(simpleoname); + Strcpy(simpleoname, obufp); + releaseobuf(obufp); + } return simpleoname; } /* "the scroll" or "the scrolls" */ char * -thesimpleoname(obj) -struct obj *obj; +thesimpleoname(struct obj *obj) +{ + char *obufp, *simpleoname = simpleonames(obj); + + /* the() will allocate another obuf[]; we want to avoid using two */ + obufp = the(simpleoname); + Strcpy(simpleoname, obufp); + releaseobuf(obufp); + return simpleoname; +} + +/* basic name of obj, as if it has been discovered; for some types of + items, we can't just use OBJ_NAME() because it doesn't always include + the class (for instance "light" when we want "spellbook of light"); + minimal_xname() uses xname() to get that */ +char * +actualoname(struct obj *obj) { - char *simpleoname = simpleonames(obj); + char *res; - return the(simpleoname); + iflags.override_ID = TRUE; + res = minimal_xname(obj); + iflags.override_ID = FALSE; + return res; } /* artifact's name without any object type or known/dknown/&c feedback */ char * -bare_artifactname(obj) -struct obj *obj; +bare_artifactname(struct obj *obj) { char *outbuf; @@ -2004,7 +2514,7 @@ struct obj *obj; return outbuf; } -static const char *wrp[] = { +static const char *const wrp[] = { "wand", "ring", "potion", "scroll", "gem", "amulet", "spellbook", "spell book", /* for non-specific wishes */ @@ -2018,9 +2528,7 @@ static const char wrpsym[] = { WAND_CLASS, RING_CLASS, POTION_CLASS, /* return form of the verb (input plural) if xname(otmp) were the subject */ char * -otense(otmp, verb) -struct obj *otmp; -const char *verb; +otense(struct obj *otmp, const char *verb) { char *buf; @@ -2052,9 +2560,7 @@ static const char *const special_subjs[] = { /* return form of the verb (input plural) for present tense 3rd person subj */ char * -vtense(subj, verb) -register const char *subj; -register const char *verb; +vtense(const char *subj, const char *verb) { char *buf = nextobuf(), *bspot; int len, ltmp; @@ -2075,7 +2581,7 @@ register const char *verb; if (!strncmpi(subj, "a ", 2) || !strncmpi(subj, "an ", 3)) goto sing; spot = (const char *) 0; - for (sp = subj; (sp = index(sp, ' ')) != 0; ++sp) { + for (sp = subj; (sp = strchr(sp, ' ')) != 0; ++sp) { if (!strncmpi(sp, " of ", 4) || !strncmpi(sp, " from ", 6) || !strncmpi(sp, " called ", 8) || !strncmpi(sp, " named ", 7) || !strncmpi(sp, " labeled ", 9)) { @@ -2093,7 +2599,7 @@ register const char *verb; * Guess at a few other special cases that makeplural creates. */ if ((lowc(*spot) == 's' && spot != subj - && !index("us", lowc(*(spot - 1)))) + && !strchr("us", lowc(*(spot - 1)))) || !BSTRNCMPI(subj, spot - 3, "eeth", 4) || !BSTRNCMPI(subj, spot - 3, "feet", 4) || !BSTRNCMPI(subj, spot - 1, "ia", 2) @@ -2101,7 +2607,7 @@ register const char *verb; /* check for special cases to avoid false matches */ len = (int) (spot - subj) + 1; for (spec = special_subjs; *spec; spec++) { - ltmp = strlen(*spec); + ltmp = Strlen(*spec); if (len == ltmp && !strncmpi(*spec, subj, len)) goto sing; /* also check for @@ -2130,13 +2636,13 @@ register const char *verb; Strcasecpy(buf, "is"); } else if (!strcmpi(buf, "have")) { Strcasecpy(bspot - 1, "s"); - } else if (index("zxs", lowc(*bspot)) + } else if (strchr("zxs", lowc(*bspot)) || (len >= 2 && lowc(*bspot) == 'h' - && index("cs", lowc(*(bspot - 1)))) + && strchr("cs", lowc(*(bspot - 1)))) || (len == 2 && lowc(*bspot) == 'o')) { /* Ends in z, x, s, ch, sh; add an "es" */ Strcasecpy(bspot + 1, "es"); - } else if (lowc(*bspot) == 'y' && !index(vowels, lowc(*(bspot - 1)))) { + } else if (lowc(*bspot) == 'y' && !strchr(vowels, lowc(*(bspot - 1)))) { /* like "y" case in makeplural */ Strcasecpy(bspot, "ies"); } else { @@ -2153,11 +2659,12 @@ struct sing_plur { /* word pairs that don't fit into formula-based transformations; also some suffices which have very few--often one--matches or which aren't systematically reversible (knives, staves) */ -static struct sing_plur one_off[] = { +static const struct sing_plur one_off[] = { { "child", "children" }, /* (for wise guys who give their food funny names) */ { "cubus", "cubi" }, /* in-/suc-cubus */ { "culus", "culi" }, /* homunculus */ + { "Cyclops", "Cyclopes" }, { "djinni", "djinn" }, { "erinys", "erinyes" }, { "foot", "feet" }, @@ -2188,7 +2695,8 @@ static const char *const as_is[] = { "tuna", "yaki", "-hai", "krill", "manes", "moose", "ninja", "sheep", "ronin", "roshi", "shito", "tengu", "ki-rin", "Nazgul", "gunyoki", - "piranha", "samurai", "shuriken", 0, + "piranha", "samurai", "shuriken", "haggis", "Bordeaux", + 0, /* Note: "fish" and "piranha" are collective plurals, suitable for "wiped out all ". For "3 ", they should be "fishes" and "piranhas" instead. We settle for collective @@ -2196,16 +2704,16 @@ static const char *const as_is[] = { }; /* singularize/pluralize decisions common to both makesingular & makeplural */ -STATIC_OVL boolean -singplur_lookup(basestr, endstring, to_plural, alt_as_is) -char *basestr, *endstring; /* base string, pointer to eos(string) */ -boolean to_plural; /* true => makeplural, false => makesingular */ -const char *const *alt_as_is; /* another set like as_is[] */ +staticfn boolean +singplur_lookup( + char *basestr, char *endstring, /* base string, pointer to eos(string) */ + boolean to_plural, /* true => makeplural, false => makesingular */ + const char *const *alt_as_is) /* another set like as_is[] */ { const struct sing_plur *sp; const char *same, *other, *const *as; int al; - int baselen = strlen(basestr); + int baselen = Strlen(basestr); for (as = as_is; *as; ++as) { al = (int) strlen(*as); @@ -2271,9 +2779,8 @@ const char *const *alt_as_is; /* another set like as_is[] */ } /* searches for common compounds, ex. lump of royal jelly */ -STATIC_OVL char * -singplur_compound(str) -char *str; +staticfn char * +singplur_compound(char *str) { /* if new entries are added, be sure to keep compound_start[] in sync */ static const char *const compounds[] = @@ -2283,7 +2790,8 @@ char *str; " versus ", " from ", " in ", " on ", " a la ", " with", /* " with "? */ " de ", " d'", " du ", - "-in-", "-at-", 0 + " au ", "-in-", "-at-", + 0 }, /* list of first characters for all compounds[] entries */ compound_start[] = " -"; @@ -2293,7 +2801,7 @@ char *str; for (p = str; *p; ++p) { /* substring starting at p can only match if *p is found within compound_start[] */ - if (!index(compound_start, *p)) + if (!strchr(compound_start, *p)) continue; /* check current substring against all words in the compound[] list */ @@ -2325,13 +2833,12 @@ char *str; * 3.6.0: made case-insensitive. */ char * -makeplural(oldstr) -const char *oldstr; +makeplural(const char *oldstr) { - register char *spot; + char *spot; char lo_c, *str = nextobuf(); const char *excess = (char *) 0; - int len; + int len, i; if (oldstr) while (*oldstr == ' ') @@ -2341,6 +2848,26 @@ const char *oldstr; Strcpy(str, "s"); return str; } + /* makeplural() is sometimes used on monsters rather than objects + and sometimes pronouns are used for monsters, so check those; + unfortunately, "her" (which matches genders[1].him and [1].his) + and "it" (which matches genders[2].he and [2].him) are ambiguous; + we'll live with that; caller can fix things up if necessary */ + *str = '\0'; + for (i = 0; i <= 2; ++i) { + if (!strcmpi(genders[i].he, oldstr)) + Strcpy(str, genders[3].he); /* "they" */ + else if (!strcmpi(genders[i].him, oldstr)) + Strcpy(str, genders[3].him); /* "them" */ + else if (!strcmpi(genders[i].his, oldstr)) + Strcpy(str, genders[3].his); /* "their" */ + if (*str) { + if (oldstr[0] == highc(oldstr[0])) + str[0] = highc(str[0]); + return str; + } + } + Strcpy(str, oldstr); /* @@ -2365,7 +2892,7 @@ const char *oldstr; *(spot + 1) = '\0'; /* Now spot is the last character of the string */ - len = strlen(str); + len = Strlen(str); /* Single letters */ if (len == 1 || !letter(*spot)) { @@ -2377,6 +2904,7 @@ const char *oldstr; { static const char *const already_plural[] = { "ae", /* algae, larvae, &c */ + "eaux", /* chateaux, gateaux */ "matzot", 0, }; @@ -2402,7 +2930,7 @@ const char *oldstr; if (len >= 3 && !strcmpi(spot - 2, "erf")) { /* avoid "nerf" -> "nerves", "serf" -> "serves" */ ; /* fall through to default (append 's') */ - } else if (index("lr", lo_c) || index(vowels, lo_c)) { + } else if (strchr("lr", lo_c) || strchr(vowels, lo_c)) { /* [aeioulr]f to [aeioulr]ves */ Strcasecpy(spot, "ves"); goto bottom; @@ -2435,6 +2963,13 @@ const char *oldstr; Strcasecpy(spot - 1, "es"); goto bottom; } + /* -eau/-eaux (gateau, chapeau...) */ + if (len >= 3 && !strcmpi(spot - 2, "eau") + /* 'bureaus' is the more common plural of 'bureau' */ + && BSTRCMPI(str, spot - 5, "bureau")) { + Strcasecpy(spot + 1, "x"); + goto bottom; + } /* matzoh/matzot, possible food name */ if (len >= 6 && (!strcmpi(spot - 5, "matzoh") || !strcmpi(spot - 5, "matzah"))) { @@ -2447,7 +2982,6 @@ const char *oldstr; goto bottom; } - /* note: -eau/-eaux (gateau, bordeau...) */ /* note: ox/oxen, VAX/VAXen, goose/geese */ lo_c = lowc(*spot); @@ -2463,14 +2997,10 @@ const char *oldstr; goto bottom; } /* Ends in z, x, s, ch, sh; add an "es" */ - if (index("zxs", lo_c) - || (len >= 2 && lo_c == 'h' && index("cs", lowc(*(spot - 1))) + if (strchr("zxs", lo_c) + || (len >= 2 && lo_c == 'h' && strchr("cs", lowc(*(spot - 1))) /* 21st century k-sound */ - && !(len >= 4 && - ((lowc(*(spot - 2)) == 'e' - && index("mt", lowc(*(spot - 3)))) || - (lowc(*(spot - 2)) == 'o' - && index("lp", lowc(*(spot - 3))))))) + && !(len >= 4 && lowc(*(spot - 1)) == 'c' && ch_ksound(str))) /* Kludge to get "tomatoes" and "potatoes" right */ || (len >= 4 && !strcmpi(spot - 2, "ato")) || (len >= 5 && !strcmpi(spot - 4, "dingo"))) { @@ -2478,7 +3008,7 @@ const char *oldstr; goto bottom; } /* Ends in y preceded by consonant (note: also "qu") change to "ies" */ - if (lo_c == 'y' && !index(vowels, lowc(*(spot - 1)))) { + if (lo_c == 'y' && !strchr(vowels, lowc(*(spot - 1)))) { Strcasecpy(spot, "ies"); /* y -> ies */ goto bottom; } @@ -2504,10 +3034,9 @@ const char *oldstr; * 3.6.0: made case-insensitive. */ char * -makesingular(oldstr) -const char *oldstr; +makesingular(const char *oldstr) { - register char *p, *bp; + char *p, *bp; const char *excess = 0; char *str = nextobuf(); @@ -2519,6 +3048,20 @@ const char *oldstr; str[0] = '\0'; return str; } + /* makeplural() of pronouns isn't reversible but at least we can + force a singular value */ + *str = '\0'; + if (!strcmpi(genders[3].he, oldstr)) /* "they" */ + Strcpy(str, genders[2].he); /* "it" */ + else if (!strcmpi(genders[3].him, oldstr)) /* "them" */ + Strcpy(str, genders[2].him); /* also "it" */ + else if (!strcmpi(genders[3].his, oldstr)) /* "their" */ + Strcpy(str, genders[2].his); /* "its" */ + if (*str) { + if (oldstr[0] == highc(oldstr[0])) + str[0] = highc(str[0]); + return str; + } bp = strcpy(str, oldstr); @@ -2552,8 +3095,8 @@ const char *oldstr; goto bottom; } /* wolves, but f to ves isn't fully reversible */ - if (p - 4 >= bp && (index("lr", lowc(*(p - 4))) - || index(vowels, lowc(*(p - 4)))) + if (p - 4 >= bp && (strchr("lr", lowc(*(p - 4))) + || strchr(vowels, lowc(*(p - 4)))) && !BSTRCMPI(bp, p - 3, "ves")) { if (!BSTRCMPI(bp, p - 6, "cloves") || !BSTRCMPI(bp, p - 6, "nerves")) @@ -2567,6 +3110,7 @@ const char *oldstr; || !BSTRCMPI(bp, p - 4, "nxes") /* lynxes */ || !BSTRCMPI(bp, p - 4, "ches") || !BSTRCMPI(bp, p - 4, "uses") /* lotuses */ + || !BSTRCMPI(bp, p - 4, "shes") /* splashes [of venom] */ || !BSTRCMPI(bp, p - 4, "sses") /* priestesses */ || !BSTRCMPI(bp, p - 5, "atoes") /* tomatoes */ || !BSTRCMPI(bp, p - 7, "dingoes") @@ -2596,13 +3140,14 @@ const char *oldstr; goto bottom; } /* matzot -> matzo, algae -> alga */ - if (!BSTRCMPI(bp, p - 6, "matzot") || !BSTRCMPI(bp, p - 2, "ae")) { - *(p - 1) = '\0'; /* drop t/e */ + if (!BSTRCMPI(bp, p - 6, "matzot") || !BSTRCMPI(bp, p - 2, "ae") + || !BSTRCMPI(bp, p - 4, "eaux")) { + *(p - 1) = '\0'; /* drop t/e/x */ goto bottom; } /* balactheria -> balactherium */ if (p - 4 >= bp && !strcmpi(p - 2, "ia") - && index("lr", lowc(*(p - 3))) && lowc(*(p - 4)) == 'e') { + && strchr("lr", lowc(*(p - 3))) && lowc(*(p - 4)) == 'e') { Strcasecpy(p - 1, "um"); /* a -> um */ } @@ -2618,13 +3163,40 @@ const char *oldstr; return bp; } -STATIC_OVL boolean -badman(basestr, to_plural) -const char *basestr; -boolean to_plural; /* true => makeplural, false => makesingular */ + +staticfn boolean +ch_ksound(const char *basestr) +{ + /* these are some *ch words/suffixes that make a k-sound. They pluralize by + adding 's' rather than 'es' */ + static const char *const ch_k[] = { + "monarch", "poch", "tech", "mech", "stomach", "psych", + "amphibrach", "anarch", "atriarch", "azedarach", "broch", + "gastrotrich", "isopach", "loch", "oligarch", "peritrich", + "sandarach", "sumach", "symposiarch", + }; + int i, al; + const char *endstr; + + if (!basestr || strlen(basestr) < 4) + return FALSE; + + endstr = eos((char *) basestr); + for (i = 0; i < SIZE(ch_k); i++) { + al = (int) strlen(ch_k[i]); + if (!BSTRCMPI(basestr, endstr - al, ch_k[i])) + return TRUE; + } + return FALSE; +} + +staticfn boolean +badman( + const char *basestr, + boolean to_plural) /* True: makeplural, False: makesingular */ { /* these are all the prefixes for *man that don't have a *men plural */ - static const char *no_men[] = { + static const char *const no_men[] = { "albu", "antihu", "anti", "ata", "auto", "bildungsro", "cai", "cay", "ceru", "corner", "decu", "des", "dura", "fir", "hanu", "het", "infrahu", "inhu", "nonhu", "otto", "out", "prehu", "protohu", @@ -2632,7 +3204,7 @@ boolean to_plural; /* true => makeplural, false => makesingular */ "hu", "un", "le", "re", "so", "to", "at", "a", }; /* these are all the prefixes for *men that don't have a *man singular */ - static const char *no_man[] = { + static const char *const no_man[] = { "abdo", "acu", "agno", "ceru", "cogno", "cycla", "fleh", "grava", "hegu", "preno", "sonar", "speci", "dai", "exa", "fla", "sta", "teg", "tegu", "vela", "da", "hy", "lu", "no", "nu", "ra", "ru", "se", "vi", @@ -2667,11 +3239,11 @@ boolean to_plural; /* true => makeplural, false => makesingular */ } /* compare user string against object name string using fuzzy matching */ -STATIC_OVL boolean -wishymatch(u_str, o_str, retry_inverted) -const char *u_str; /* from user, so might be variant spelling */ -const char *o_str; /* from objects[], so is in canonical form */ -boolean retry_inverted; /* optional extra "of" handling */ +staticfn boolean +wishymatch( + const char *u_str, /* from user, so might be variant spelling */ + const char *o_str, /* from objects[], so is in canonical form */ + boolean retry_inverted) /* optional extra "of" handling */ { static NEARDATA const char detect_SP[] = "detect ", SP_detection[] = " detection"; @@ -2690,18 +3262,14 @@ boolean retry_inverted; /* optional extra "of" handling */ o_of = strstri(o_str, " of "); if (u_of && !o_of) { Strcpy(buf, u_of + 4); - p = eos(strcat(buf, " ")); - while (u_str < u_of) - *p++ = *u_str++; - *p = '\0'; - return fuzzymatch(buf, o_str, " -", TRUE); + copynchars(eos(strcat(buf, " ")), u_str, (int) (u_of - u_str)); + if (fuzzymatch(buf, o_str, " -", TRUE)) + return TRUE; } else if (o_of && !u_of) { Strcpy(buf, o_of + 4); - p = eos(strcat(buf, " ")); - while (o_str < o_of) - *p++ = *o_str++; - *p = '\0'; - return fuzzymatch(u_str, buf, " -", TRUE); + copynchars(eos(strcat(buf, " ")), o_str, (int) (o_of - o_str)); + if (fuzzymatch(u_str, buf, " -", TRUE)) + return TRUE; } } @@ -2716,6 +3284,15 @@ boolean retry_inverted; /* optional extra "of" handling */ return fuzzymatch(u_str + 7, o_str + 6, " -", TRUE); else if (!strncmpi(u_str, "elfin ", 6)) return fuzzymatch(u_str + 6, o_str + 6, " -", TRUE); + } else if (strstri(o_str, "helm") && strstri(u_str, "helmet")) { + copynchars(buf, u_str, (int) sizeof buf - 1); + (void) strsubst(buf, "helmet", "helm"); + return wishymatch(buf, o_str, TRUE); + } else if (strstri(o_str, "gauntlets") && strstri(u_str, "gloves")) { + /* -3: room to replace shorter "gloves" with longer "gauntlets" */ + copynchars(buf, u_str, (int) sizeof buf - 1 - 3); + (void) strsubst(buf, "gloves", "gauntlets"); + return wishymatch(buf, o_str, TRUE); } else if (!strncmp(o_str, detect_SP, sizeof detect_SP - 1)) { /* check for "detect " vs " detection" */ if ((p = strstri(u_str, SP_detection)) != 0 @@ -2766,7 +3343,7 @@ struct o_range { }; /* wishable subranges of objects */ -STATIC_OVL NEARDATA const struct o_range o_ranges[] = { +static NEARDATA const struct o_range o_ranges[] = { { "bag", TOOL_CLASS, SACK, BAG_OF_TRICKS }, { "lamp", TOOL_CLASS, OIL_LAMP, MAGIC_LAMP }, { "candle", TOOL_CLASS, TALLOW_CANDLE, WAX_CANDLE }, @@ -2809,7 +3386,16 @@ static const struct alt_spellings { { "lantern", BRASS_LANTERN }, { "mattock", DWARVISH_MATTOCK }, { "amulet of poison resistance", AMULET_VERSUS_POISON }, + { "amulet of protection", AMULET_OF_GUARDING }, + { "amulet of telepathy", AMULET_OF_ESP }, + { "helm of esp", HELM_OF_TELEPATHY }, + { "gauntlets of ogre power", GAUNTLETS_OF_POWER }, + { "gauntlets of giant strength", GAUNTLETS_OF_POWER }, + { "elven chain mail", ELVEN_MITHRIL_COAT }, + { "silver shield", SHIELD_OF_REFLECTION }, { "potion of sleep", POT_SLEEPING }, + { "scroll of recharging", SCR_CHARGING }, + { "recharging", SCR_CHARGING }, { "stone", ROCK }, { "camera", EXPENSIVE_CAMERA }, { "tee shirt", T_SHIRT }, @@ -2817,16 +3403,19 @@ static const struct alt_spellings { { "can opener", TIN_OPENER }, { "kelp", KELP_FROND }, { "eucalyptus", EUCALYPTUS_LEAF }, - { "royal jelly", LUMP_OF_ROYAL_JELLY }, { "lembas", LEMBAS_WAFER }, + { "tripe", TRIPE_RATION }, { "cookie", FORTUNE_COOKIE }, { "pie", CREAM_PIE }, + { "huge meatball", ENORMOUS_MEATBALL }, /* likely conflated name */ + { "huge chunk of meat", ENORMOUS_MEATBALL }, /* original name */ { "marker", MAGIC_MARKER }, { "hook", GRAPPLING_HOOK }, { "grappling iron", GRAPPLING_HOOK }, { "grapnel", GRAPPLING_HOOK }, { "grapple", GRAPPLING_HOOK }, { "protection from shape shifters", RIN_PROTECTION_FROM_SHAPE_CHAN }, + { "accuracy", RIN_INCREASE_ACCURACY }, /* if we ever add other sizes, move this to o_ranges[] with "bag" */ { "box", LARGE_BOX }, /* normally we wouldn't have to worry about unnecessary , but @@ -2839,14 +3428,13 @@ static const struct alt_spellings { { (const char *) 0, 0 }, }; -STATIC_OVL short -rnd_otyp_by_wpnskill(skill) -schar skill; +staticfn short +rnd_otyp_by_wpnskill(schar skill) { int i, n = 0; short otyp = STRANGE_OBJECT; - for (i = bases[WEAPON_CLASS]; + for (i = svb.bases[WEAPON_CLASS]; i < NUM_OBJECTS && objects[i].oc_class == WEAPON_CLASS; i++) if (objects[i].oc_skill == skill) { n++; @@ -2854,7 +3442,7 @@ schar skill; } if (n > 0) { n = rn2(n); - for (i = bases[WEAPON_CLASS]; + for (i = svb.bases[WEAPON_CLASS]; i < NUM_OBJECTS && objects[i].oc_class == WEAPON_CLASS; i++) if (objects[i].oc_skill == skill) if (--n < 0) @@ -2863,22 +3451,35 @@ schar skill; return otyp; } -STATIC_OVL short -rnd_otyp_by_namedesc(name, oclass, xtra_prob) -const char *name; -char oclass; -int xtra_prob; /* to force 0% random generation items to also be considered */ +staticfn short +rnd_otyp_by_namedesc( + const char *name, + char oclass, + int xtra_prob) /* add to item's chance of being chosen; non-zero causes + * 0% random generation items to also be considered */ { int i, n = 0; short validobjs[NUM_OBJECTS]; - register const char *zn; - int prob, maxprob = 0; + const char *zn, *of; + boolean check_of; + int lo, hi, minglob, maxglob, prob, maxprob = 0; if (!name || !*name) return STRANGE_OBJECT; - memset((genericptr_t) validobjs, 0, sizeof validobjs); + /* only skip "foo of" for "foo of bar" if target doesn't contain " of " */ + check_of = (strstri(name, " of ") == 0); + minglob = GLOB_OF_GRAY_OOZE; + maxglob = GLOB_OF_BLACK_PUDDING; + (void) memset((genericptr_t) validobjs, 0, sizeof validobjs); + if (oclass) { + lo = svb.bases[(uchar) oclass]; + hi = svb.bases[(uchar) oclass + 1] - 1; + } else { + lo = MAXOCLASSES; /* STRANGE_OBJECT + 1; */ + hi = NUM_OBJECTS - 1; + } /* FIXME: * When this spans classes (the !oclass case), the item * probabilities are not very useful because they don't take @@ -2887,17 +3488,31 @@ int xtra_prob; /* to force 0% random generation items to also be considered */ * "blank" would have 10/11 chance to yield a book even though * scrolls are supposed to be much more common than books.] */ - for (i = oclass ? bases[(int) oclass] : STRANGE_OBJECT + 1; - i < NUM_OBJECTS && (!oclass || objects[i].oc_class == oclass); - ++i) { + for (i = lo; i <= hi; ++i) { /* don't match extra descriptions (w/o real name) */ if ((zn = OBJ_NAME(objects[i])) == 0) continue; - if (wishymatch(name, zn, TRUE) + if (wishymatch(name, zn, TRUE) /* objects[] name */ + /* let "" match " of " (already does if foo is + an object class, but this is for lump of royal jelly, + clove of garlic, bag of tricks, &c) with a few exceptions: + for "opening", don't match "bell of opening"; for monster + type ooze/pudding/slime don't match glob of same since that + ought to match "corpse/egg/figurine of type" too but won't */ + || (check_of + && i != BELL_OF_OPENING + && (i < minglob || i > maxglob) + && (of = strstri(zn, " of ")) != 0 + && wishymatch(name, of + 4, FALSE)) /* partial name */ || ((zn = OBJ_DESCR(objects[i])) != 0 - && wishymatch(name, zn, FALSE)) + && wishymatch(name, zn, FALSE)) /* objects[] description */ + /* "cloth" should match "piece of cloth"; there's only one + description containing " of " so no special case handling */ + || (zn && check_of && (of = strstri(zn, " of ")) != 0 + && wishymatch(name, of + 4, FALSE)) /* partial description */ || ((zn = objects[i].oc_uname) != 0 - && wishymatch(name, zn, FALSE))) { + && wishymatch(name, zn, FALSE)) /* user-called name */ + ) { validobjs[n++] = (short) i; maxprob += (objects[i].oc_prob + xtra_prob); } @@ -2914,246 +3529,695 @@ int xtra_prob; /* to force 0% random generation items to also be considered */ } int -shiny_obj(oclass) -char oclass; +shiny_obj(char oclass) { return (int) rnd_otyp_by_namedesc("shiny", oclass, 0); } -/* - * Return something wished for. Specifying a null pointer for - * the user request string results in a random object. Otherwise, - * if asking explicitly for "nothing" (or "nil") return no_wish; - * if not an object return &zeroobj; if an error (no matching object), - * return null. - */ -struct obj * -readobjnam(bp, no_wish) -register char *bp; -struct obj *no_wish; +/* set wall under hero undiggable/unphaseable from string */ +staticfn void +set_wallprop_from_str(char *bp) { - register char *p; - register int i; - register struct obj *otmp; - int cnt, spe, spesgn, typ, very, rechrg; - int blessed, uncursed, iscursed, ispoisoned, isgreased; - int eroded, eroded2, erodeproof, locked, unlocked, broken; - int halfeaten, mntmp, contents; - int islit, unlabeled, ishistoric, isdiluted, trapped; - int tmp, tinv, tvariety; - int wetness, gsize = 0; - struct fruit *f; - int ftype = context.current_fruit; - char fruitbuf[BUFSZ], globbuf[BUFSZ]; - /* Fruits may not mess up the ability to wish for real objects (since - * you can leave a fruit in a bones file and it will be added to - * another person's game), so they must be checked for last, after - * stripping all the possible prefixes and seeing if there's a real - * name in there. So we have to save the full original name. However, - * it's still possible to do things like "uncursed burnt Alaska", - * or worse yet, "2 burned 5 course meals", so we need to loop to - * strip off the prefixes again, this time stripping only the ones - * possible on food. - * We could get even more detailed so as to allow food names with - * prefixes that _are_ possible on food, so you could wish for - * "2 3 alarm chilis". Currently this isn't allowed; options.c - * automatically sticks 'candied' in front of such names. - */ - char oclass; - char *un, *dn, *actualn, *origbp = bp; - const char *name = 0; - - cnt = spe = spesgn = typ = 0; - very = rechrg = blessed = uncursed = iscursed = ispoisoned = - isgreased = eroded = eroded2 = erodeproof = halfeaten = - islit = unlabeled = ishistoric = isdiluted = trapped = - locked = unlocked = broken = 0; - tvariety = RANDOM_TIN; - mntmp = NON_PM; -#define UNDEFINED 0 -#define EMPTY 1 -#define SPINACH 2 - contents = UNDEFINED; - oclass = 0; - actualn = dn = un = 0; - wetness = 0; + int wall_prop = 0; + + if (strstr(bp, "undiggable ") || strstr(bp, "nondiggable ")) + wall_prop |= W_NONDIGGABLE; + if (strstr(bp, "unphaseable ") || strstr(bp, "nonpasswall ")) + wall_prop |= W_NONPASSWALL; + /* |= because wall_info (aka flags) is overloaded with other stuff */ + if (wall_prop) + levl[u.ux][u.uy].wall_info |= wall_prop; +} - if (!bp) - goto any; - /* first, remove extra whitespace they may have typed */ - (void) mungspaces(bp); - /* allow wishing for "nothing" to preserve wishless conduct... - [now requires "wand of nothing" if that's what was really wanted] */ - if (!strcmpi(bp, "nothing") || !strcmpi(bp, "nil") - || !strcmpi(bp, "none")) - return no_wish; - /* save the [nearly] unmodified choice string */ - Strcpy(fruitbuf, bp); +/* in wizard mode, readobjnam() can accept wishes for traps and terrain */ +staticfn struct obj * +wizterrainwish(struct _readobjnam_data *d) +{ + struct rm *lev; + boolean madeterrain = FALSE, badterrain = FALSE, is_dbridge; + int trap; + unsigned oldtyp, ltyp; + coordxy x = u.ux, y = u.uy; + char *bp = d->bp, *p; + + for (trap = NO_TRAP + 1; trap < TRAPNUM; trap++) { + struct trap *t; + const char *tname; + + tname = trapname(trap, TRUE); + if (!str_start_is(bp, tname, TRUE)) + continue; + /* found it; avoid stupid mistakes */ + if (is_hole(trap) && !Can_fall_thru(&u.uz)) + trap = ROCKTRAP; + if ((t = maketrap(x, y, trap)) != 0) { + trap = t->ttyp; + tname = trapname(trap, TRUE); + pline("%s%s.", An(tname), + (trap != MAGIC_PORTAL) ? "" : " to nowhere"); + } else { + pline("Creation of %s failed.", an(tname)); + } + return &hands_obj; + } + + /* furniture and terrain (use at your own risk; can clobber stairs + or place furniture on existing traps which shouldn't be allowed) */ + lev = &levl[x][y]; + oldtyp = lev->typ; + is_dbridge = (oldtyp == DRAWBRIDGE_DOWN || oldtyp == DRAWBRIDGE_UP); + p = eos(bp); + if (!BSTRCMPI(bp, p - 8, "fountain")) { + lev->typ = FOUNTAIN; + if (oldtyp != FOUNTAIN) + svl.level.flags.nfountains++; + lev->looted = d->looted ? F_LOOTED : 0; /* overlays 'flags' */ + lev->blessedftn = d->blessed || !strncmpi(bp, "magic ", 6); + pline("A %sfountain.", lev->blessedftn ? "magic " : ""); + madeterrain = TRUE; + } else if (!BSTRCMPI(bp, p - 6, "throne")) { + lev->typ = THRONE; + lev->looted = d->looted ? T_LOOTED : 0; /* overlays 'flags' */ + pline("A throne."); + madeterrain = TRUE; + } else if (!BSTRCMPI(bp, p - 4, "sink")) { + lev->typ = SINK; + if (oldtyp != SINK) + svl.level.flags.nsinks++; + lev->looted = d->looted ? (S_LPUDDING | S_LDWASHER | S_LRING) : 0; + pline("A sink."); + madeterrain = TRUE; + + /* ("water" matches "potion of water" rather than terrain) */ + } else if (!BSTRCMPI(bp, p - 4, "pool") + || !BSTRCMPI(bp, p - 4, "moat") + || !BSTRCMPI(bp, p - 13, "wall of water")) { + long save_prop; + const char *new_water; + + ltyp = !BSTRCMPI(bp, p - 4, "pool") ? POOL + : !BSTRCMPI(bp, p - 4, "moat") ? MOAT + : WATER; + if (!is_dbridge) { + lev->typ = ltyp; + lev->flags = 0; + } else { + /* drawbridgemask overloads flags */ + lev->drawbridgemask &= ~DB_UNDER; + lev->drawbridgemask |= DB_MOAT; + } + del_engr_at(x, y); + if (!is_dbridge) { + save_prop = EHalluc_resistance; + EHalluc_resistance = 1; + new_water = waterbody_name(x, y); + EHalluc_resistance = save_prop; + pline("%s.", An(new_water)); + /* Must manually make kelp! */ + } else { + dbterrainmesg("Moat", x, y); + } + water_damage_chain(svl.level.objects[x][y], TRUE); + madeterrain = TRUE; + + /* also matches "molten lava" */ + } else if (!BSTRCMPI(bp, p - 4, "lava") + || !BSTRCMPI(bp, p - 12, "wall of lava")) { + ltyp = !BSTRCMPI(bp, p - 12, "wall of lava") ? LAVAWALL : LAVAPOOL; + if (!is_dbridge) { + lev->typ = ltyp; + lev->flags = 0; + } else { + /* drawbridgemask overloads flags */ + lev->drawbridgemask &= ~DB_UNDER; + lev->drawbridgemask |= DB_LAVA; + } + del_engr_at(x, y); + if (!is_dbridge) { + pline("A %s of molten lava.", + (lev->typ == LAVAPOOL) ? "pool" : "wall"); + if (!(Levitation || Flying) || lev->typ == LAVAWALL) + pooleffects(FALSE); + } else { + dbterrainmesg("Lava", x, y); + } + fire_damage_chain(svl.level.objects[x][y], TRUE, TRUE, x, y); + madeterrain = TRUE; + } else if (!BSTRCMPI(bp, p - 3, "ice")) { + if (!is_dbridge) { + lev->typ = ICE; + /* icedpool overloads flags; specifies what ice will melt into */ + lev->icedpool = (oldtyp == ROOM) ? ICED_POOL : ICED_MOAT; + } else { + /* drawbridgemask overloads flags */ + lev->drawbridgemask &= ~DB_UNDER; + lev->drawbridgemask |= DB_ICE; + } + del_engr_at(x, y); + + if (!strncmpi(bp, "melting ", 8)) + start_melt_ice_timeout(x, y, 0L); + + if (!is_dbridge) { + char icebuf[40]; + + pline("%s.", upstart(ice_descr(x, y, icebuf))); + } else { + dbterrainmesg("Ice", x, y); + } + madeterrain = TRUE; + } else if (!BSTRCMPI(bp, p - 5, "altar")) { + aligntyp al; + + lev->typ = ALTAR; + if (!strncmpi(bp, "chaotic ", 8)) + al = A_CHAOTIC; + else if (!strncmpi(bp, "neutral ", 8)) + al = A_NEUTRAL; + else if (!strncmpi(bp, "lawful ", 7)) + al = A_LAWFUL; + else if (!strncmpi(bp, "unaligned ", 10)) + al = A_NONE; + else /* -1 - A_CHAOTIC, 0 - A_NEUTRAL, 1 - A_LAWFUL */ + al = !rn2(6) ? A_NONE : (rn2((int) A_LAWFUL + 2) - 1); + lev->altarmask = Align2amask(al); /* overlays 'flags' */ + pline("%s altar.", An(align_str(al))); + madeterrain = TRUE; + } else if (!BSTRCMPI(bp, p - 5, "grave") + || !BSTRCMPI(bp, p - 9, "headstone")) { + make_grave(x, y, (char *) 0); + if (IS_GRAVE(lev->typ)) { + lev->looted = 0; /* overlays 'flags' */ + lev->disturbed = d->looted ? 1 : 0; + pline("A %sgrave.", lev->disturbed ? "disturbed " : ""); + madeterrain = TRUE; + } else { + pline("Can't place a grave here."); + badterrain = TRUE; + } + } else if (!BSTRCMPI(bp, p - 4, "tree")) { + lev->typ = TREE; + lev->looted = d->looted ? (TREE_LOOTED | TREE_SWARM) : 0; + set_wallprop_from_str(bp); + pline("A tree."); + madeterrain = TRUE; + } else if (!BSTRCMPI(bp, p - 4, "bars")) { + lev->typ = IRONBARS; + lev->flags = 0; + set_wallprop_from_str(bp); + /* [FIXME: if this isn't a wall or door location where 'horizontal' + is already set up, that should be calculated for this spot. + Unfortunately, it can be tricky; placing one in open space + and then another adjacent might need to recalculate first one.] */ + pline("Iron bars."); + madeterrain = TRUE; + } else if (!BSTRCMPI(bp, p - 5, "cloud")) { + lev->typ = CLOUD; + lev->flags = 0; + pline("A cloud."); + del_engr_at(x, y); + madeterrain = TRUE; + } else if (!BSTRCMPI(bp, p - 4, "door") + || (d->doorless && !BSTRCMPI(bp, p - 7, "doorway"))) { + char dbuf[40]; + unsigned old_wall_info; + boolean secret = !BSTRCMPI(bp, p - 11, "secret door"); + + /* require door or wall so that the 'horizontal' flag will + already have the correct value; player might choose to put + DOOR on top of existing DOOR or SDOOR on top of existing SDOOR + to control its trapped state; iron bars are surrogate walls; + a previously dug wall looks like corridor but is actually a + doorless doorway so will be acceptable here */ + if (lev->typ == DOOR || lev->typ == SDOOR + || (IS_WALL(lev->typ) && lev->typ != DBWALL) + || lev->typ == IRONBARS) { + /* remember previous wall info [is this right for iron bars?] */ + old_wall_info = (lev->typ != DOOR) ? lev->wall_info : 0; + /* set the new terrain type */ + lev->typ = secret ? SDOOR : DOOR; + lev->wall_info = 0; /* overlays 'flags' */ + /* lev->horizontal stays as-is */ + if (Is_rogue_level(&u.uz)) { + /* all doors on the rogue level are doorless; locking magic + there converts them into walls rather than closed doors */ + d->doorless = 1; + d->locked = d->closed = d->open = d->broken = 0; + } + /* if not locked, secret doors are implicitly closed but + mustn't be set that way explicitly because they use both + doormask and wall_info which both overload rm[x][y].flags + (CLOSED overlaps wall_info bits, LOCKED and TRAPPED don't); + conversion from SDOOR to DOOR changes NODOOR to CLOSED */ + lev->doormask = d->locked ? D_LOCKED + : (d->doorless || secret) ? D_NODOOR + : d->open ? D_ISOPEN + : d->broken ? D_BROKEN + : D_CLOSED; + /* SDOOR uses wall_info, restore relevant bits. + * FIXME? if we're changing a regular door into a secret door, + * old_wall_info bits will be 0 instead of being set properly. + * Probably only matters if player uses Passes_walls and a wish + * to turn a T- or cross-wall into a door, losing wall info, + * and then another wish to turn that door into a secret door. */ + if (secret) + lev->wall_info |= (old_wall_info & WM_MASK); + /* set up trapped flag; open door states aren't eligible */ + if (d->trapped == 2 /* 2: wish includes explicit "untrapped" */ + || ((lev->doormask & (D_LOCKED | D_CLOSED)) == 0 + /* D_CLOSED is implicit for secret doors */ + && !secret)) + d->trapped = 0; + if (d->trapped) + lev->doormask |= D_TRAPPED; + /* feedback */ + dbuf[0] = '\0'; + if (lev->doormask & D_TRAPPED) + Strcat(dbuf, "trapped "); + if (lev->doormask & D_LOCKED) + Strcat(dbuf, "locked "); + if (lev->typ == SDOOR) { + Strcat(dbuf, "secret door"); + } else { + /* these should be mutually exclusive but we describe them + as if they're independent to maybe catch future bugs... */ + if (lev->doormask & D_CLOSED) + Strcat(dbuf, "closed "); + if (lev->doormask & D_ISOPEN) + Strcat(dbuf, "open "); + if (lev->doormask & D_BROKEN) + Strcat(dbuf, "broken "); + if ((lev->doormask & ~D_TRAPPED) == D_NODOOR) + Strcat(dbuf, "doorless doorway"); + else + Strcat(dbuf, "door"); + } + pline("%s.", upstart(an(dbuf))); + madeterrain = TRUE; + } else { + Strcpy(dbuf, secret ? "secret door" : "door"); + pline("%s requires door or wall location.", upstart(dbuf)); + badterrain = TRUE; + } + } else if (!BSTRCMPI(bp, p - 4, "wall") + && (bp == p - 4 || p[-5] == ' ')) { + schar wall = HWALL; + + if ((isok(u.ux, u.uy-1) && IS_WALL(levl[u.ux][u.uy-1].typ)) + || (isok(u.ux, u.uy+1) && IS_WALL(levl[u.ux][u.uy+1].typ))) + wall = VWALL; + madeterrain = TRUE; + lev->typ = wall; + lev->flags = 0; + set_wallprop_from_str(bp); + fix_wall_spines(max(0,u.ux-1), max(0,u.uy-1), + min(COLNO,u.ux+1), min(ROWNO,u.uy+1)); + pline("A wall."); + } else if (!BSTRCMPI(bp, p - 15, "secret corridor")) { + if (lev->typ == CORR) { + lev->typ = SCORR; + /* neither CORR nor SCORR uses 'flags' or 'horizontal' */ + pline("Secret corridor."); + madeterrain = TRUE; + } else { + pline("Secret corridor requires corridor location."); + badterrain = TRUE; + } + } else if (!BSTRCMPI(bp, p - 4, "room") + || !BSTRCMPI(bp, p - 5, "floor") + || !BSTRCMPI(bp, p - 6, "ground")) { + if (oldtyp == ROOM + || (IS_FURNITURE(oldtyp) && CAN_OVERWRITE_TERRAIN(oldtyp)) + || oldtyp == ICE || is_pool_or_lava(x, y)) { + struct trap *t; + + lev->typ = ROOM; + pline("Room floor."); + if (IS_FURNITURE(oldtyp)) + count_level_features(); + if ((t = t_at(x, y)) != 0 && t->ttyp != MAGIC_PORTAL) + deltrap(t); + madeterrain = TRUE; + } else if (is_dbridge) { + lev->drawbridgemask &= ~DB_UNDER; + lev->drawbridgemask |= DB_FLOOR; + dbterrainmesg("Floor", x, y); + madeterrain = TRUE; + } else { + pline("Room|floor|ground not allowed here."); + badterrain = TRUE; + } + } + + if (madeterrain) { + feel_newsym(x, y); /* map the spot where the wish occurred */ + + /* hero started at but might not be there anymore (create + lava, decline to die, and get teleported away to safety) */ + if (u.uinwater && !is_pool(u.ux, u.uy)) { + set_uinwater(0); /* u.uinwater = 0; leave the water */ + docrt(); + /* [block/unblock_point handled by docrt -> vision_recalc] */ + } else { + if (u.utrap && u.utraptype == TT_LAVA && !is_lava(u.ux, u.uy)) + reset_utrap(FALSE); + recalc_block_point(x, y); + } + + /* fixups for replaced terrain that aren't handled above */ + if (IS_FOUNTAIN(oldtyp) || IS_SINK(oldtyp)) + count_level_features(); /* update level.flags.nfountains,nsinks */ + if (!is_ice(x, y)) + spot_stop_timers(x, y, MELT_ICE_AWAY); + /* horizontal is overlaid by fountain->blessedftn, grave->disturbed */ + if (IS_FOUNTAIN(oldtyp) || IS_GRAVE(oldtyp) + || IS_WALL(oldtyp) || oldtyp == IRONBARS + || IS_DOOR(oldtyp) || oldtyp == SDOOR) { + /* when new terrain is a fountain, 'blessedftn' was explicitly + set above; likewise for grave and 'disturbed'; when it's a + door, the old type was a wall or a door and we retain the + 'horizontal' value from those */ + if (!IS_FOUNTAIN(lev->typ) && !IS_GRAVE(lev->typ) + && !IS_DOOR(lev->typ) && lev->typ != SDOOR) + lev->horizontal = 0; /* also clears blessedftn, disturbed */ + } + /* note: lev->lit and lev->nondiggable retain their values even + though those might not make sense with the new terrain */ + + /* might have changed terrain from something that blocked + levitation and flying to something that doesn't (levitating + while in xorn form and replacing solid stone with furniture) */ + switch_terrain(); + } + if (madeterrain || badterrain) + return &hands_obj; + + return (struct obj *) 0; +} + +/* message common to several wizterrainwish() results */ +staticfn void +dbterrainmesg( + const char *newtype, + coordxy x, coordxy y) +{ + pline("%s %s the drawbridge.", newtype, + (levl[x][y].typ == DRAWBRIDGE_UP) ? "in front of" : "under"); +} + +#define TIN_UNDEFINED 0 +#define TIN_EMPTY 1 +#define TIN_SPINACH 2 + +staticfn void +readobjnam_init(char *bp, struct _readobjnam_data *d) +{ + d->otmp = (struct obj *) 0; + d->cnt = d->spe = d->spesgn = d->typ = 0; + d->very = d->rechrg = d->blessed = d->uncursed = d->iscursed + = d->ispoisoned = d->isgreased = d->eroded = d->eroded2 + = d->erodeproof = d->halfeaten = d->islit = d->unlabeled + = d->ishistoric = d->isdiluted /* statues, potions */ + /* box/chest and wizard mode door */ + = d->trapped = d->locked = d->unlocked = d->broken + = d->open = d->closed = d->doorless /* wizard mode door */ + = d->looted /* wizard mode fountain/sink/throne/tree and grave */ + = d->real = d->fake = 0; /* Amulet */ + d->tvariety = RANDOM_TIN; + d->mgend = -1; /* not specified, aka random */ + d->mntmp = NON_PM; + d->contents = TIN_UNDEFINED; + d->oclass = 0; + d->actualn = d->dn = d->un = 0; + d->wetness = 0; + d->gsize = 0; + d->zombify = FALSE; + d->bp = d->origbp = bp; + d->p = (char *) 0; + d->name = (const char *) 0; + d->ftype = svc.context.current_fruit; + (void) memset(d->globbuf, '\0', sizeof d->globbuf); + (void) memset(d->fruitbuf, '\0', sizeof d->fruitbuf); +} + +/* return 1 if d->bp is empty or contains only various qualifiers like + "blessed", "rustproof", and so on, or 0 if anything else is present */ +staticfn int +readobjnam_preparse(struct _readobjnam_data *d) +{ + char *save_bp = 0; + int more_l = 0, res = 1; for (;;) { - register int l; + int l; - if (!bp || !*bp) - goto any; - if (!strncmpi(bp, "an ", l = 3) || !strncmpi(bp, "a ", l = 2)) { - cnt = 1; - } else if (!strncmpi(bp, "the ", l = 4)) { + if (!d->bp || !*d->bp) + break; + res = 0; + + if (!strncmpi(d->bp, "an ", l = 3) || !strncmpi(d->bp, "a ", l = 2)) { + d->cnt = 1; + } else if (!strncmpi(d->bp, "the ", l = 4)) { ; /* just increment `bp' by `l' below */ - } else if (!cnt && digit(*bp) && strcmp(bp, "0")) { - cnt = atoi(bp); - while (digit(*bp)) - bp++; - while (*bp == ' ') - bp++; + } else if (!d->cnt && digit(*d->bp) && strcmp(d->bp, "0")) { + d->cnt = atoi(d->bp); + while (digit(*d->bp)) + d->bp++; + while (*d->bp == ' ') + d->bp++; l = 0; - } else if (*bp == '+' || *bp == '-') { - spesgn = (*bp++ == '+') ? 1 : -1; - spe = atoi(bp); - while (digit(*bp)) - bp++; - while (*bp == ' ') - bp++; + } else if (*d->bp == '+' || *d->bp == '-') { + d->spesgn = (*d->bp++ == '+') ? 1 : -1; + d->spe = atoi(d->bp); + while (digit(*d->bp)) + d->bp++; + while (*d->bp == ' ') + d->bp++; l = 0; - } else if (!strncmpi(bp, "blessed ", l = 8) - || !strncmpi(bp, "holy ", l = 5)) { - blessed = 1; - } else if (!strncmpi(bp, "moist ", l = 6) - || !strncmpi(bp, "wet ", l = 4)) { - if (!strncmpi(bp, "wet ", 4)) - wetness = rn2(3) + 3; + } else if (!strncmpi(d->bp, "blessed ", l = 8) + || !strncmpi(d->bp, "holy ", l = 5)) { + d->blessed = 1, d->uncursed = d->iscursed = 0; + } else if (!strncmpi(d->bp, "cursed ", l = 7) + || !strncmpi(d->bp, "unholy ", l = 7)) { + d->iscursed = 1, d->blessed = d->uncursed = 0; + } else if (!strncmpi(d->bp, "uncursed ", l = 9)) { + d->uncursed = 1, d->blessed = d->iscursed = 0; + } else if (!strncmpi(d->bp, "rustproof ", l = 10) + || !strncmpi(d->bp, "erodeproof ", l = 11) + || !strncmpi(d->bp, "corrodeproof ", l = 13) + || !strncmpi(d->bp, "fixed ", l = 6) + || !strncmpi(d->bp, "fireproof ", l = 10) + || !strncmpi(d->bp, "rotproof ", l = 9) + || !strncmpi(d->bp, "tempered ", l = 9) + || !strncmpi(d->bp, "crackproof ", l = 11)) { + d->erodeproof = 1; + } else if (!strncmpi(d->bp, "lit ", l = 4) + || !strncmpi(d->bp, "burning ", l = 8)) { + d->islit = 1; + } else if (!strncmpi(d->bp, "unlit ", l = 6) + || !strncmpi(d->bp, "extinguished ", l = 13)) { + d->islit = 0; + + /* "wet" and "moist" are only applicable for towels */ + } else if (!strncmpi(d->bp, "moist ", l = 6) + || !strncmpi(d->bp, "wet ", l = 4)) { + if (!strncmpi(d->bp, "wet ", 4)) + d->wetness = 3 + rn2(3); /* 3..5 */ else - wetness = rnd(2); - } else if (!strncmpi(bp, "cursed ", l = 7) - || !strncmpi(bp, "unholy ", l = 7)) { - iscursed = 1; - } else if (!strncmpi(bp, "uncursed ", l = 9)) { - uncursed = 1; - } else if (!strncmpi(bp, "rustproof ", l = 10) - || !strncmpi(bp, "erodeproof ", l = 11) - || !strncmpi(bp, "corrodeproof ", l = 13) - || !strncmpi(bp, "fixed ", l = 6) - || !strncmpi(bp, "fireproof ", l = 10) - || !strncmpi(bp, "rotproof ", l = 9)) { - erodeproof = 1; - } else if (!strncmpi(bp, "lit ", l = 4) - || !strncmpi(bp, "burning ", l = 8)) { - islit = 1; - } else if (!strncmpi(bp, "unlit ", l = 6) - || !strncmpi(bp, "extinguished ", l = 13)) { - islit = 0; - /* "unlabeled" and "blank" are synonymous */ - } else if (!strncmpi(bp, "unlabeled ", l = 10) - || !strncmpi(bp, "unlabelled ", l = 11) - || !strncmpi(bp, "blank ", l = 6)) { - unlabeled = 1; - } else if (!strncmpi(bp, "poisoned ", l = 9)) { - ispoisoned = 1; - /* "trapped" recognized but not honored outside wizard mode */ - } else if (!strncmpi(bp, "trapped ", l = 8)) { - trapped = 0; /* undo any previous "untrapped" */ + d->wetness = rnd(2); /* 1..2 */ + + /* "unlabeled" and "blank" are synonymous */ + } else if (!strncmpi(d->bp, "unlabeled ", l = 10) + || !strncmpi(d->bp, "unlabelled ", l = 11) + || !strncmpi(d->bp, "blank ", l = 6)) { + d->unlabeled = 1; + } else if (!strncmpi(d->bp, "poisoned ", l = 9)) { + d->ispoisoned = 1; + + /* "trapped" recognized but not honored outside wizard mode */ + } else if (!strncmpi(d->bp, "trapped ", l = 8)) { + d->trapped = 0; /* undo any previous "untrapped" */ if (wizard) - trapped = 1; - } else if (!strncmpi(bp, "untrapped ", l = 10)) { - trapped = 2; /* not trapped */ - /* locked, unlocked, broken: box/chest lock states */ - } else if (!strncmpi(bp, "locked ", l = 7)) { - locked = 1, unlocked = broken = 0; - } else if (!strncmpi(bp, "unlocked ", l = 9)) { - unlocked = 1, locked = broken = 0; - } else if (!strncmpi(bp, "broken ", l = 7)) { - broken = 1, locked = unlocked = 0; - } else if (!strncmpi(bp, "greased ", l = 8)) { - isgreased = 1; - } else if (!strncmpi(bp, "very ", l = 5)) { + d->trapped = 1; + } else if (!strncmpi(d->bp, "untrapped ", l = 10)) { + d->trapped = 2; /* not trapped */ + + /* locked, unlocked, broken: box/chest lock states, also door states; + open, closed, doorless: additional door states */ + } else if (!strncmpi(d->bp, "locked ", l = 7)) { + d->locked = d->closed = 1, + d->unlocked = d->broken = d->open = d->doorless = 0; + } else if (!strncmpi(d->bp, "unlocked ", l = 9)) { + d->unlocked = d->closed = 1, + d->locked = d->broken = d->open = d->doorless = 0; + } else if (!strncmpi(d->bp, "broken ", l = 7)) { + d->broken = 1, + d->locked = d->unlocked = d->open = d->closed + = d->doorless = 0; + } else if (!strncmpi(d->bp, "open ", l = 5)) { + d->open = 1, + d->closed = d->locked = d->broken = d->doorless = 0; + } else if (!strncmpi(d->bp, "closed ", l = 7)) { + d->closed = 1, + d->open = d->locked = d->broken = d->doorless = 0; + } else if (!strncmpi(d->bp, "doorless ", l = 9)) { + d->doorless = 1, + d->open = d->closed = d->locked = d->unlocked = d->broken = 0; + /* looted: fountain/sink/throne/tree; disturbed: grave */ + } else if (!strncmpi(d->bp, "looted ", l = 7) + /* overload disturbed grave with looted fountain here + even though they're separate in struct rm */ + || !strncmpi(d->bp, "disturbed ", l = 10)) { + d->looted = 1; + } else if (!strncmpi(d->bp, "greased ", l = 8)) { + d->isgreased = 1; + } else if (!strncmpi(d->bp, "zombifying ", l = 11)) { + d->zombify = TRUE; + } else if (!strncmpi(d->bp, "very ", l = 5)) { /* very rusted very heavy iron ball */ - very = 1; - } else if (!strncmpi(bp, "thoroughly ", l = 11)) { - very = 2; - } else if (!strncmpi(bp, "rusty ", l = 6) - || !strncmpi(bp, "rusted ", l = 7) - || !strncmpi(bp, "burnt ", l = 6) - || !strncmpi(bp, "burned ", l = 7)) { - eroded = 1 + very; - very = 0; - } else if (!strncmpi(bp, "corroded ", l = 9) - || !strncmpi(bp, "rotted ", l = 7)) { - eroded2 = 1 + very; - very = 0; - } else if (!strncmpi(bp, "partly eaten ", l = 13) - || !strncmpi(bp, "partially eaten ", l = 16)) { - halfeaten = 1; - } else if (!strncmpi(bp, "historic ", l = 9)) { - ishistoric = 1; - } else if (!strncmpi(bp, "diluted ", l = 8)) { - isdiluted = 1; - } else if (!strncmpi(bp, "empty ", l = 6)) { - contents = EMPTY; - } else if (!strncmpi(bp, "small ", l = 6)) { /* glob sizes */ + d->very = 1; + } else if (!strncmpi(d->bp, "thoroughly ", l = 11)) { + d->very = 2; + } else if (!strncmpi(d->bp, "rusty ", l = 6) + || !strncmpi(d->bp, "rusted ", l = 7) + || !strncmpi(d->bp, "burnt ", l = 6) + || !strncmpi(d->bp, "burned ", l = 7) + || !strncmpi(d->bp, "cracked ", l = 8)) { + d->eroded = 1 + d->very; + d->very = 0; + } else if (!strncmpi(d->bp, "corroded ", l = 9) + || !strncmpi(d->bp, "rotted ", l = 7)) { + d->eroded2 = 1 + d->very; + d->very = 0; + } else if (!strncmpi(d->bp, "partly eaten ", l = 13) + || !strncmpi(d->bp, "partially eaten ", l = 16)) { + d->halfeaten = 1; + } else if (!strncmpi(d->bp, "historic ", l = 9)) { + d->ishistoric = 1; + } else if (!strncmpi(d->bp, "diluted ", l = 8)) { + d->isdiluted = 1; + } else if (!strncmpi(d->bp, "empty ", l = 6)) { + d->contents = TIN_EMPTY; + } else if (!strncmpi(d->bp, "small ", l = 6)) { /* glob sizes */ /* "small" might be part of monster name (mimic, if wishing for its corpse) rather than prefix for glob size; when used for globs, it might be either "small glob of " or "small glob" and user might add 's' even though plural doesn't accomplish anything because globs don't stack */ - if (strncmpi(bp + l, "glob", 4) && !strstri(bp + l, " glob")) + if (strncmpi(d->bp + l, "glob", 4) && !strstri(d->bp + l, " glob")) break; - gsize = 1; - } else if (!strncmpi(bp, "medium ", l = 7)) { - /* xname() doesn't display "medium" but without this - there'd be no way to ask for the intermediate size - ("glob" without size prefix yields smallest one) */ - gsize = 2; - } else if (!strncmpi(bp, "large ", l = 6)) { - /* "large" might be part of monster name (dog, cat, koboold, + d->gsize = 1; + } else if (!strncmpi(d->bp, "medium ", l = 7)) { + /* 5.0: in 3.6, "medium" was only used during wishing and the + mid-size glob had no adjective when formatted, but as of + 5.0, "medium" has become an explicit part of the name for + combined globs of at least 5 individual ones (owt >= 100) + and less than 15 (owt < 300) */ + d->gsize = 2; + } else if (!strncmpi(d->bp, "large ", l = 6)) { + /* "large" might be part of monster name (dog, cat, kobold, mimic) or object name (box, round shield) rather than prefix for glob size */ - if (strncmpi(bp + l, "glob", 4) && !strstri(bp + l, " glob")) + if (strncmpi(d->bp + l, "glob", 4) && !strstri(d->bp + l, " glob")) break; /* "very large " had "very " peeled off on previous iteration */ - gsize = (very != 1) ? 3 : 4; - } else + d->gsize = (d->very != 1) ? 3 : 4; + } else if (!strncmpi(d->bp, "real ", l = 5)) { + /* accept "real Amulet of Yendor" with "blessed" or "cursed" + or useless "erodeproof" before or after "real" ... */ + d->real = 1; /* don't negate 'fake' here; "real fake amulet" and + * "fake real amulet" will both yield fake amulet + * (so will "real amulet" outside of wizard mode) */ + } else if (!strncmpi(d->bp, "fake ", l = 5)) { + /* ... and "fake Amulet of Yendor" likewise */ + d->fake = 1, d->real = 0; + /* ['real' isn't actually needed (unless we someday add + "real gem" for random non-glass, non-stone)] */ + } else if (!strncmpi(d->bp, "female ", l = 7)) { + d->mgend = FEMALE; + /* if after "corpse/statue/figurine of", remove from string */ + if (save_bp) + strsubst(d->bp, "female ", ""), l = 0; + } else if (!strncmpi(d->bp, "male ", l = 5)) { + d->mgend = MALE; + if (save_bp) + strsubst(d->bp, "male ", ""), l = 0; + } else if (!strncmpi(d->bp, "neuter ", l = 7)) { + d->mgend = NEUTRAL; + if (save_bp) + strsubst(d->bp, "neuter ", ""), l = 0; + + /* + * Corpse/statue/figurine gender hack: in order to accept + * "statue of a female gnome ruler" for gnome queen we need + * to recognize and skip over "statue of [a ]". Otherwise + * we would only accept "female gnome ruler statue" and the + * viable but silly "female statue of a gnome ruler". + */ + } else if ((!strncmpi(d->bp, "corpse ", l = 7) + || !strncmpi(d->bp, "statue ", l = 7) + || !strncmpi(d->bp, "figurine ", l = 9)) + && !strncmpi(d->bp + l, "of ", more_l = 3)) { + save_bp = d->bp; /* we'll backtrack to here later */ + l += more_l, more_l = 0; + if (!strncmpi(d->bp + l, "a ", more_l = 2) + || !strncmpi(d->bp + l, "an ", more_l = 3) + || !strncmpi(d->bp + l, "the ", more_l = 4)) + l += more_l; + } else { break; - bp += l; + } + d->bp += l; } - if (!cnt) - cnt = 1; /* will be changed to 2 if makesingular() changes string */ - if (strlen(bp) > 1 && (p = rindex(bp, '(')) != 0) { - boolean keeptrailingchars = TRUE; + if (save_bp) + d->bp = save_bp; + return res; +} - p[(p > bp && p[-1] == ' ') ? -1 : 0] = '\0'; /*terminate bp */ - ++p; /* advance past '(' */ - if (!strncmpi(p, "lit)", 4)) { - islit = 1; - p += 4 - 1; /* point at ')' */ +staticfn void +readobjnam_parse_charges(struct _readobjnam_data *d) +{ + if (strlen(d->bp) > 1 && (d->p = strrchr(d->bp, '(')) != 0) { + boolean keeptrailingchars = TRUE; + int idx = 0; + + if (d->p > d->bp && d->p[-1] == ' ') + idx = -1; + d->p[idx] = '\0'; /* terminate bp */ + ++d->p; /* advance past '(' */ + if (!strncmpi(d->p, "lit)", 4)) { + d->islit = 1; + d->p += 4 - 1; /* point at ')' */ } else { - spe = atoi(p); - while (digit(*p)) - p++; - if (*p == ':') { - p++; - rechrg = spe; - spe = atoi(p); - while (digit(*p)) - p++; + d->spe = atoi(d->p); + while (digit(*d->p)) + d->p++; + if (*d->p == ':') { + d->p++; + d->rechrg = d->spe; + d->spe = atoi(d->p); + while (digit(*d->p)) + d->p++; } - if (*p != ')') { - spe = rechrg = 0; + if (*d->p != ')') { + d->spe = d->rechrg = 0; /* mis-matched parentheses; rest of string will be ignored * [probably we should restore everything back to '(' * instead since it might be part of "named ..."] */ keeptrailingchars = FALSE; } else { - spesgn = 1; + d->spesgn = 1; } } if (keeptrailingchars) { - char *pp = eos(bp); + char *pp = eos(d->bp); /* 'pp' points at 'pb's terminating '\0', 'p' points at ')' and will be incremented past it */ do { - *pp++ = *++p; - } while (*p); + *pp++ = *++d->p; + } while (*d->p); } } /* @@ -3161,14 +4225,21 @@ struct obj *no_wish; * smaller. Also, spe should always be positive --some cheaters may * try to confuse atoi(). */ - if (spe < 0) { - spesgn = -1; /* cheaters get what they deserve */ - spe = abs(spe); - } - if (spe > SCHAR_LIM) - spe = SCHAR_LIM; - if (rechrg < 0 || rechrg > 7) - rechrg = 7; /* recharge_limit */ + if (d->spe < 0) { + d->spesgn = -1; /* cheaters get what they deserve */ + d->spe = abs(d->spe); + } + /* cap on obj->spe is independent of (and less than) SCHAR_LIM */ + if (d->spe > SPE_LIM) + d->spe = SPE_LIM; /* slime mold uses d.ftype, so not affected */ + if (d->rechrg < 0 || d->rechrg > 7) + d->rechrg = 7; /* recharge_limit */ +} + +staticfn int +readobjnam_postparse1(struct _readobjnam_data *d) +{ + int i; /* now we have the actual name, as delivered by xname, say * green potions called whisky @@ -3179,34 +4250,62 @@ struct obj *no_wish; * wand of wishing * elven cloak */ - if ((p = strstri(bp, " named ")) != 0) { - *p = 0; + if ((d->p = strstri(d->bp, " named ")) != 0) { + *d->p = 0; /* note: if 'name' is too long, oname() will truncate it */ - name = p + 7; + d->name = d->p + 7; } - if ((p = strstri(bp, " called ")) != 0) { - *p = 0; + if ((d->p = strstri(d->bp, " called ")) != 0) { + *d->p = 0; /* note: if 'un' is too long, obj lookup just won't match anything */ - un = p + 8; + d->un = d->p + 8; /* "helmet called telepathy" is not "helmet" (a specific type) * "shield called reflection" is not "shield" (a general type) */ for (i = 0; i < SIZE(o_ranges); i++) - if (!strcmpi(bp, o_ranges[i].name)) { - oclass = o_ranges[i].oclass; - goto srch; + if (!strcmpi(d->bp, o_ranges[i].name)) { + d->oclass = o_ranges[i].oclass; + return 1; /*goto srch;*/ } } - if ((p = strstri(bp, " labeled ")) != 0) { - *p = 0; - dn = p + 9; - } else if ((p = strstri(bp, " labelled ")) != 0) { - *p = 0; - dn = p + 10; - } - if ((p = strstri(bp, " of spinach")) != 0) { - *p = 0; - contents = SPINACH; + if ((d->p = strstri(d->bp, " labeled ")) != 0) { + *d->p = 0; + d->dn = d->p + 9; + } else if ((d->p = strstri(d->bp, " labelled ")) != 0) { + *d->p = 0; + d->dn = d->p + 10; + } + if ((d->p = strstri(d->bp, " of spinach")) != 0) { + *d->p = 0; + d->contents = TIN_SPINACH; + } + /* real vs fake is only useful for wizard mode but we'll accept its + parsing in normal play (result is never real Amulet for that case) */ + if ((d->p = strstri(d->bp, OBJ_DESCR(objects[AMULET_OF_YENDOR]))) != 0 + && (d->p == d->bp || d->p[-1] == ' ')) { + char *s = d->bp; + + /* "Amulet of Yendor" matches two items, name of real Amulet + and description of fake one; player can explicitly specify + "real" to disambiguate, but not specifying "fake" achieves + the same thing; "real" and "fake" are parsed above with other + prefixes so that combinations like "blessed real" and "real + blessed" work as expected; also accept partial specification + of the full name of the fake; unlike the prefix recognition + loop above, these have to be in the right order when more + than one is present (similar to worthless glass gems below) */ + if (!strncmpi(s, "cheap ", 6)) + d->fake = 1, s += 6; + if (!strncmpi(s, "plastic ", 8)) + d->fake = 1, s += 8; + if (!strncmpi(s, "imitation ", 10)) + d->fake = 1, s += 10; + nhUse(s); /* suppress potential assigned-but-not-used complaint */ + /* when 'fake' is True, it overrides 'real' if both were given; + when it is False, force 'real' whether that was specified or not */ + d->real = !d->fake; + d->typ = d->real ? AMULET_OF_YENDOR : FAKE_AMULET_OF_YENDOR; + return 2; /*goto typfnd;*/ } /* @@ -3219,109 +4318,139 @@ struct obj *no_wish; * referred to as a "pair of". E.g. We should double if the player * types "pair of spears", but not if the player types "pair of * lenses". Luckily (?) all objects that are referred to as pairs - * -- boots, gloves, and lenses -- are also not mergable, so cnt is + * -- boots, gloves, and lenses -- are also not mergeable, so cnt is * ignored anyway. */ - if (!strncmpi(bp, "pair of ", 8)) { - bp += 8; - cnt *= 2; - } else if (!strncmpi(bp, "pairs of ", 9)) { - bp += 9; - if (cnt > 1) - cnt *= 2; - } else if (!strncmpi(bp, "set of ", 7)) { - bp += 7; - } else if (!strncmpi(bp, "sets of ", 8)) { - bp += 8; - } - - /* intercept pudding globs here; they're a valid wish target, + if (!strncmpi(d->bp, "pair of ", 8)) { + d->bp += 8; + d->cnt *= 2; + } else if (!strncmpi(d->bp, "pairs of ", 9)) { + d->bp += 9; + if (d->cnt > 1) + d->cnt *= 2; + } else if (!strncmpi(d->bp, "set of ", 7)) { + d->bp += 7; + } else if (!strncmpi(d->bp, "sets of ", 8)) { + d->bp += 8; + } + + /* Intercept pudding globs here; they're a valid wish target, * but we need them to not get treated like a corpse. - * - * also don't let player wish for multiple globs. + * If a count is specified, it will be used to magnify weight + * rather than to specify quantity (which is always 1 for globs). */ - i = (int) strlen(bp); - p = (char *) 0; + i = (int) strlen(d->bp); + d->p = (char *) 0; /* check for "glob", " glob", and "glob of " */ - if (!strcmpi(bp, "glob") || !BSTRCMPI(bp, bp + i - 5, " glob") - || !strcmpi(bp, "globs") || !BSTRCMPI(bp, bp + i - 6, " globs") - || (p = strstri(bp, "glob of ")) != 0 - || (p = strstri(bp, "globs of ")) != 0) { - mntmp = name_to_mon(!p ? bp : (strstri(p, " of ") + 4)); + if (!strcmpi(d->bp, "glob") || !BSTRCMPI(d->bp, d->bp + i - 5, " glob") + || !strcmpi(d->bp, "globs") + || !BSTRCMPI(d->bp, d->bp + i - 6, " globs") + || (d->p = strstri(d->bp, "glob of ")) != 0 + || (d->p = strstri(d->bp, "globs of ")) != 0) { + d->mntmp = name_to_mon(!d->p ? d->bp + : (strstri(d->p, " of ") + 4), (int *) 0); /* if we didn't recognize monster type, pick a valid one at random */ - if (mntmp == NON_PM) - mntmp = rn1(PM_BLACK_PUDDING - PM_GRAY_OOZE, PM_GRAY_OOZE); + if (d->mntmp == NON_PM) + d->mntmp = rn1(PM_BLACK_PUDDING - PM_GRAY_OOZE, PM_GRAY_OOZE); + /* normally this would be done when makesingular() changes the value + but canonical form here is already singular so that won't happen */ + if (d->cnt < 2 && strstri(d->bp, "globs")) + d->cnt = 2; /* affects otmp->owt but not otmp->quan for globs */ /* construct canonical spelling in case name_to_mon() recognized a variant (grey ooze) or player used inverted syntax ( glob); if player has given a valid monster type but not valid glob type, object name lookup won't find it and wish attempt will fail */ - Sprintf(globbuf, "glob of %s", mons[mntmp].mname); - bp = globbuf; - mntmp = NON_PM; /* not useful for "glob of " object lookup */ - cnt = 0; /* globs don't stack */ - oclass = FOOD_CLASS; - actualn = bp, dn = 0; - goto srch; + Sprintf(d->globbuf, "glob of %s", mons[d->mntmp].pmnames[NEUTRAL]); + d->bp = d->globbuf; + d->mntmp = NON_PM; /* not useful for "glob of " object lookup */ + d->oclass = FOOD_CLASS; + d->actualn = d->bp, d->dn = 0; + return 1; /*goto srch;*/ } else { /* * Find corpse type using "of" (figurine of an orc, tin of orc meat) * Don't check if it's a wand or spellbook. * (avoid "wand/finger of death" confusion). + * Don't match "ogre" or "giant" monster name inside alternate item + * names "gauntlets of ogre power" and "gauntlets of giant strength" + * (or the alternate spelling of those, "gloves of ..."). */ - if (!strstri(bp, "wand ") && !strstri(bp, "spellbook ") - && !strstri(bp, "finger ")) { - if ((p = strstri(bp, "tin of ")) != 0) { - if (!strcmpi(p + 7, "spinach")) { - contents = SPINACH; - mntmp = NON_PM; + if (!strstri(d->bp, "wand ") && !strstri(d->bp, "spellbook ") + && !strstri(d->bp, "gauntlets ") && !strstri(d->bp, "gloves ") + && !strstri(d->bp, "finger ")) { + if ((d->p = strstri(d->bp, "tin of ")) != 0) { + if (!strcmpi(d->p + 7, "spinach")) { + d->contents = TIN_SPINACH; + d->mntmp = NON_PM; } else { - tmp = tin_variety_txt(p + 7, &tinv); - tvariety = tinv; - mntmp = name_to_mon(p + 7 + tmp); + d->tmp = tin_variety_txt(d->p + 7, &d->tinv); + d->tvariety = d->tinv; + d->mntmp = name_to_mon(d->p + 7 + d->tmp, &d->mgend); } - typ = TIN; - goto typfnd; - } else if ((p = strstri(bp, " of ")) != 0 - && (mntmp = name_to_mon(p + 4)) >= LOW_PM) - *p = 0; + d->typ = TIN; + return 2; /*goto typfnd;*/ + } else if ((d->p = strstri(d->bp, " of ")) != 0 + && ((d->mntmp = name_to_mon(d->p + 4, &d->mgend)) + >= LOW_PM)) + *d->p = 0; } } /* Find corpse type w/o "of" (red dragon scale mail, yeti corpse) */ - if (strncmpi(bp, "samurai sword", 13) /* not the "samurai" monster! */ - && strncmpi(bp, "wizard lock", 11) /* not the "wizard" monster! */ - && strncmpi(bp, "ninja-to", 8) /* not the "ninja" rank */ - && strncmpi(bp, "master key", 10) /* not the "master" rank */ - && strncmpi(bp, "magenta", 7)) { /* not the "mage" rank */ - if (mntmp < LOW_PM && strlen(bp) > 2 - && (mntmp = name_to_mon(bp)) >= LOW_PM) { - int mntmptoo, mntmplen; /* double check for rank title */ - char *obp = bp; - - mntmptoo = title_to_mon(bp, (int *) 0, &mntmplen); - bp += (mntmp != mntmptoo) ? (int) strlen(mons[mntmp].mname) - : mntmplen; - if (*bp == ' ') { - bp++; - } else if (!strncmpi(bp, "s ", 2)) { - bp += 2; - } else if (!strncmpi(bp, "es ", 3)) { - bp += 3; - } else if (!*bp && !actualn && !dn && !un && !oclass) { + if (strncmpi(d->bp, "samurai sword", 13) /* not the "samurai" monster! */ + && strncmpi(d->bp, "wizard lock", 11) /* not the "wizard" monster! */ + && strncmpi(d->bp, "death wand", 10) /* 'of inversion', not Rider */ + && strncmpi(d->bp, "master key", 10) /* not the "master" rank */ + && strncmpi(d->bp, "ninja-to", 8) /* not the "ninja" rank */ + && strncmpi(d->bp, "magenta", 7)) { /* not the "mage" rank */ + const char *rest = 0; + + if (d->mntmp < LOW_PM && strlen(d->bp) > 2 + && ((d->mntmp = name_to_monplus(d->bp, &rest, &d->mgend)) + >= LOW_PM)) { + char *obp = d->bp; + + /* 'rest' is a pointer past the matching portion; if that was + an alternate name or a rank title rather than the canonical + monster name we wouldn't otherwise know how much to skip */ + d->bp = (char *) rest; /* cast away const */ + + if (*d->bp == ' ') { + d->bp++; + } else if (!strncmpi(d->bp, "s ", 2) + || (d->bp > d->origbp + && !strncmpi(d->bp - 1, "s' ", 3))) { + d->bp += 2; + } else if (!strncmpi(d->bp, "es ", 3) + || !strncmpi(d->bp, "'s ", 3)) { + d->bp += 3; + } else if (!*d->bp && !d->actualn && !d->dn && !d->un + && !d->oclass) { /* no referent; they don't really mean a monster type */ - bp = obp; - mntmp = NON_PM; + d->bp = obp; + d->mntmp = NON_PM; } } } /* first change to singular if necessary */ - if (*bp) { - char *sng = makesingular(bp); - - if (strcmp(bp, sng)) { - if (cnt == 1) - cnt = 2; - Strcpy(bp, sng); + if (*d->bp + /* we want "tricks" to match "bag of tricks" [rnd_otyp_by_namedesc()] + but that wouldn't work if it gets singularized to "trick" + ["tricks bag" matches whether or not this exception is present + because singularize operates on "bag" and wishymatch()'s + 'of inversion' finds a match] */ + && strcmpi(d->bp, "tricks") + /* an odd potential wish; fail rather than get a false match with + "cloth" because it might yield a "cloth spellbook" rather than + a "piece of cloth" cloak [maybe we should give random armor?] */ + && strcmpi(d->bp, "clothes") + ) { + char *sng = makesingular(d->bp); + + if (strcmp(d->bp, sng)) { + if (d->cnt == 1) + d->cnt = 2; + Strcpy(d->bp, sng); } } @@ -3330,53 +4459,70 @@ struct obj *no_wish; const struct alt_spellings *as = spellings; while (as->sp) { - if (fuzzymatch(bp, as->sp, " -", TRUE)) { - typ = as->ob; - goto typfnd; + if (wishymatch(d->bp, as->sp, TRUE)) { + d->typ = as->ob; + return 2; /*goto typfnd;*/ } as++; } /* can't use spellings list for this one due to shuffling */ - if (!strncmpi(bp, "grey spell", 10)) - *(bp + 2) = 'a'; + if (!strncmpi(d->bp, "grey spell", 10)) + *(d->bp + 2) = 'a'; - if ((p = strstri(bp, "armour")) != 0) { + if ((d->p = strstri(d->bp, "armour")) != 0) { /* skip past "armo", then copy remainder beyond "u" */ - p += 4; - while ((*p = *(p + 1)) != '\0') - ++p; /* self terminating */ + d->p += 4; + while ((*d->p = *(d->p + 1)) != '\0') + ++d->p; /* self terminating */ } } /* dragon scales - assumes order of dragons */ - if (!strcmpi(bp, "scales") && mntmp >= PM_GRAY_DRAGON - && mntmp <= PM_YELLOW_DRAGON) { - typ = GRAY_DRAGON_SCALES + mntmp - PM_GRAY_DRAGON; - mntmp = NON_PM; /* no monster */ - goto typfnd; + if (!strcmpi(d->bp, "scales") && d->mntmp >= PM_GRAY_DRAGON + && d->mntmp <= PM_YELLOW_DRAGON) { + d->typ = GRAY_DRAGON_SCALES + d->mntmp - PM_GRAY_DRAGON; + d->mntmp = NON_PM; /* no monster */ + return 2; /*goto typfnd;*/ + } + + d->p = eos(d->bp); + if (!BSTRCMPI(d->bp, d->p - 10, "holy water")) { + /* this isn't needed for "[un]holy water" because adjective parsing + handles holy==blessed and unholy==cursed and leaves "water" for + the object type, but it is needed for "potion of [un]holy water" + since that parsing stops when it reaches "potion"; also, neither + "holy water" nor "unholy water" is an actual type of potion */ + if (!BSTRNCMPI(d->bp, d->p - 10 - 2, "un", 2)) + d->iscursed = 1, d->blessed = d->uncursed = 0; /* unholy water */ + else + d->blessed = 1, d->iscursed = d->uncursed = 0; /* holy water */ + d->typ = POT_WATER; + return 2; /*goto typfnd;*/ } + /* accept "paperback" or "paperback book", reject "paperback spellbook" */ + if (!strncmpi(d->bp, "paperback", 9)) { + char *dbp = d->bp + 9; /* just past "paperback" */ - p = eos(bp); - if (!BSTRCMPI(bp, p - 10, "holy water")) { - typ = POT_WATER; - if ((p - bp) >= 12 && *(p - 12) == 'u') - iscursed = 1; /* unholy water */ - else - blessed = 1; - goto typfnd; + if (!*dbp || !strncmpi(dbp, " book", 5)) { + d->typ = SPE_NOVEL; + return 2; /*goto typfnd;*/ + } else { + d->otmp = (struct obj *) 0; + return 3; + } } - if (unlabeled && !BSTRCMPI(bp, p - 6, "scroll")) { - typ = SCR_BLANK_PAPER; - goto typfnd; + if (d->unlabeled && !BSTRCMPI(d->bp, d->p - 6, "scroll")) { + d->typ = SCR_BLANK_PAPER; + return 2; /*goto typfnd;*/ } - if (unlabeled && !BSTRCMPI(bp, p - 9, "spellbook")) { - typ = SPE_BLANK_PAPER; - goto typfnd; + if (d->unlabeled && !BSTRCMPI(d->bp, d->p - 9, "spellbook")) { + d->typ = SPE_BLANK_PAPER; + return 2; /*goto typfnd;*/ } /* specific food rather than color of gem/potion/spellbook[/scales] */ - if (!BSTRCMPI(bp, p - 6, "orange") && mntmp == NON_PM) { - typ = ORANGE; - goto typfnd; + if (!BSTRCMPI(d->bp, d->p - 6, "orange") && d->mntmp == NON_PM) { + d->typ = ORANGE; + return 2; /*goto typfnd;*/ } /* * NOTE: Gold pieces are handled as objects nowadays, and therefore @@ -3384,78 +4530,92 @@ struct obj *no_wish; * gold/money concept. Maybe we want to add other monetary units as * well in the future. (TH) */ - if (!BSTRCMPI(bp, p - 10, "gold piece") - || !BSTRCMPI(bp, p - 7, "zorkmid") - || !strcmpi(bp, "gold") || !strcmpi(bp, "money") - || !strcmpi(bp, "coin") || *bp == GOLD_SYM) { - if (cnt > 5000 && !wizard) - cnt = 5000; - else if (cnt < 1) - cnt = 1; - otmp = mksobj(GOLD_PIECE, FALSE, FALSE); - otmp->quan = (long) cnt; - otmp->owt = weight(otmp); - context.botl = 1; - return otmp; + if (!BSTRCMPI(d->bp, d->p - 10, "gold piece") + || !BSTRCMPI(d->bp, d->p - 7, "zorkmid") + || !strcmpi(d->bp, "gold") || !strcmpi(d->bp, "money") + || !strcmpi(d->bp, "coin") || *d->bp == GOLD_SYM) { + if (d->cnt > 5000 && !wizard) + d->cnt = 5000; + else if (d->cnt < 1) + d->cnt = 1; + d->otmp = mksobj(GOLD_PIECE, FALSE, FALSE); + d->otmp->quan = (long) d->cnt; + d->otmp->owt = weight(d->otmp); + disp.botl = TRUE; + return 3; /*return otmp;*/ } /* check for single character object class code ("/" for wand, &c) */ - if (strlen(bp) == 1 && (i = def_char_to_objclass(*bp)) < MAXOCLASSES + if (strlen(d->bp) == 1 && (i = def_char_to_objclass(*d->bp)) < MAXOCLASSES && i > ILLOBJ_CLASS && (i != VENOM_CLASS || wizard)) { - oclass = i; - goto any; - } - - /* Search for class names: XXXXX potion, scroll of XXXXX. Avoid */ - /* false hits on, e.g., rings for "ring mail". */ - if (strncmpi(bp, "enchant ", 8) - && strncmpi(bp, "destroy ", 8) - && strncmpi(bp, "detect food", 11) - && strncmpi(bp, "food detection", 14) - && strncmpi(bp, "ring mail", 9) - && strncmpi(bp, "studded leather armor", 21) - && strncmpi(bp, "leather armor", 13) - && strncmpi(bp, "tooled horn", 11) - && strncmpi(bp, "food ration", 11) - && strncmpi(bp, "meat ring", 9)) + d->oclass = i; + return 4; /*goto any;*/ + } + + /* Search for class names: XXXXX potion, scroll of XXXXX. + Avoid false hits on, e.g., rings for "ring mail". */ + if (strncmpi(d->bp, "enchant ", 8) + && strncmpi(d->bp, "destroy ", 8) + && strncmpi(d->bp, "detect food", 11) + && strncmpi(d->bp, "food detection", 14) + && strncmpi(d->bp, "ring mail", 9) + && strncmpi(d->bp, "studded leather armor", 21) + && strncmpi(d->bp, "leather armor", 13) + && strncmpi(d->bp, "tooled horn", 11) + && strncmpi(d->bp, "food ration", 11) + && strncmpi(d->bp, "meat ring", 9)) for (i = 0; i < (int) (sizeof wrpsym); i++) { - register int j = strlen(wrp[i]); + int j = Strlen(wrp[i]); /* check for " [ of ] something" */ - if (!strncmpi(bp, wrp[i], j)) { - oclass = wrpsym[i]; - if (oclass != AMULET_CLASS) { - bp += j; - if (!strncmpi(bp, " of ", 4)) - actualn = bp + 4; + if (!strncmpi(d->bp, wrp[i], j)) { + d->oclass = wrpsym[i]; + if (d->oclass != AMULET_CLASS) { + d->bp += j; + if (!strncmpi(d->bp, " of ", 4)) + d->actualn = d->bp + 4; /* else if(*bp) ?? */ } else - actualn = bp; - goto srch; + d->actualn = d->bp; + return 1; /*goto srch;*/ } /* check for "something " */ - if (!BSTRCMPI(bp, p - j, wrp[i])) { - oclass = wrpsym[i]; + if (!BSTRCMPI(d->bp, d->p - j, wrp[i])) { + d->oclass = wrpsym[i]; /* for "foo amulet", leave the class name so that wishymatch() can do "of inversion" to try matching "amulet of foo"; other classes don't include their class name in their full object names (where "potion of healing" is just "healing", for instance) */ - if (oclass != AMULET_CLASS) { - p -= j; - *p = '\0'; - if (p > bp && p[-1] == ' ') - p[-1] = '\0'; + if (d->oclass != AMULET_CLASS) { + d->p -= j; + *d->p = '\0'; + if (d->p > d->bp && d->p[-1] == ' ') + d->p[-1] = '\0'; } else { + int k, l; + char amubuf[BUFSZ]; + /* amulet without "of"; convoluted wording but better a special case that's handled than one that's missing */ - if (!strncmpi(bp, "versus poison ", 14)) { - typ = AMULET_VERSUS_POISON; - goto typfnd; + if (!strncmpi(d->bp, "versus poison ", 14)) { + d->typ = AMULET_VERSUS_POISON; + return 2; /*goto typfnd;*/ + } + /* check for " amulet"; strip off trailing + " amulet" for that w/o changing contents of d->bp */ + l = (int) strlen(d->bp) - j; + if (l > 0 && d->bp[l - 1] == ' ') + l -= 1; + copynchars(amubuf, d->bp, min(l, (int) sizeof amubuf - 1)); + k = rnd_otyp_by_namedesc(amubuf, AMULET_CLASS, 0); + if (k != STRANGE_OBJECT) { + d->typ = k; + return 2; /*goto typfnd;*/ } } - actualn = dn = bp; - goto srch; + d->actualn = d->dn = d->bp; + return 1; /*goto srch;*/ } } @@ -3475,113 +4635,137 @@ struct obj *no_wish; * " object", but " trap" is suggested--to either the trap * name or the object name. */ - if (wizard && (!strncmpi(bp, "bear", 4) || !strncmpi(bp, "land", 4))) { - boolean beartrap = (lowc(*bp) == 'b'); - char *zp = bp + 4; /* skip "bear"/"land" */ + if (wizard && (!strncmpi(d->bp, "bear", 4) + || !strncmpi(d->bp, "land", 4))) { + boolean beartrap = (lowc(*d->bp) == 'b'); + char *zp = d->bp + 4; /* skip "bear"/"land" */ if (*zp == ' ') ++zp; /* embedded space is optional */ if (!strncmpi(zp, beartrap ? "trap" : "mine", 4)) { zp += 4; - if (trapped == 2 || !strcmpi(zp, " object")) { + if (d->trapped == 2 || !strcmpi(zp, " object")) { /* "untrapped " or " object" */ - typ = beartrap ? BEARTRAP : LAND_MINE; - goto typfnd; - } else if (trapped == 1 || *zp != '\0') { + d->typ = beartrap ? BEARTRAP : LAND_MINE; + return 2; /*goto typfnd;*/ + } else if (d->trapped == 1 || *zp != '\0') { /* "trapped " or " trap" (actually "*") */ - int idx = trap_to_defsym(beartrap ? BEAR_TRAP : LANDMINE); - /* use canonical trap spelling, skip object matching */ - Strcpy(bp, defsyms[idx].explanation); - goto wiztrap; + Strcpy(d->bp, trapname(beartrap ? BEAR_TRAP : LANDMINE, TRUE)); + return 5; /*goto wiztrap;*/ } /* [no prefix or suffix; we're going to end up matching the object name and getting a disarmed trap object] */ } } - retry: + return 0; +} + +staticfn int +readobjnam_postparse2(struct _readobjnam_data *d) +{ + int i; + /* "grey stone" check must be before general "stone" */ for (i = 0; i < SIZE(o_ranges); i++) - if (!strcmpi(bp, o_ranges[i].name)) { - typ = rnd_class(o_ranges[i].f_o_range, o_ranges[i].l_o_range); - goto typfnd; + if (!strcmpi(d->bp, o_ranges[i].name)) { + d->typ = rnd_class(o_ranges[i].f_o_range, o_ranges[i].l_o_range); + return 2; /*goto typfnd;*/ } - if (!BSTRCMPI(bp, p - 6, " stone") || !BSTRCMPI(bp, p - 4, " gem")) { - p[!strcmpi(p - 4, " gem") ? -4 : -6] = '\0'; - oclass = GEM_CLASS; - dn = actualn = bp; - goto srch; - } else if (!strcmpi(bp, "looking glass")) { + if (!BSTRCMPI(d->bp, d->p - 6, " stone") + || !BSTRCMPI(d->bp, d->p - 4, " gem")) { + d->p[!strcmpi(d->p - 4, " gem") ? -4 : -6] = '\0'; + d->oclass = GEM_CLASS; + d->dn = d->actualn = d->bp; + return 1; /*goto srch;*/ + } else if (!strcmpi(d->bp, "looking glass")) { ; /* avoid false hit on "* glass" */ - } else if (!BSTRCMPI(bp, p - 6, " glass") || !strcmpi(bp, "glass")) { - register char *g = bp; + } else if (!BSTRCMPI(d->bp, d->p - 6, " glass") + || !strcmpi(d->bp, "glass")) { + char *s = d->bp; /* treat "broken glass" as a non-existent item; since "broken" is also a chest/box prefix it might have been stripped off above */ - if (broken || strstri(g, "broken")) - return (struct obj *) 0; - if (!strncmpi(g, "worthless ", 10)) - g += 10; - if (!strncmpi(g, "piece of ", 9)) - g += 9; - if (!strncmpi(g, "colored ", 8)) - g += 8; - else if (!strncmpi(g, "coloured ", 9)) - g += 9; - if (!strcmpi(g, "glass")) { /* choose random color */ + if (d->broken || strstri(s, "broken")) { + d->otmp = (struct obj *) 0; + return 3; /* return otmp */ + } + if (!strncmpi(s, "worthless ", 10)) + s += 10; + if (!strncmpi(s, "piece of ", 9)) + s += 9; + if (!strncmpi(s, "colored ", 8)) + s += 8; + else if (!strncmpi(s, "coloured ", 9)) + s += 9; + if (!strcmpi(s, "glass")) { /* choose random color */ /* 9 different kinds */ - typ = LAST_GEM + rnd(9); - if (objects[typ].oc_class == GEM_CLASS) - goto typfnd; + d->typ = FIRST_GLASS_GEM + rn2(NUM_GLASS_GEMS); + if (objects[d->typ].oc_class == GEM_CLASS) + return 2; /*goto typfnd;*/ else - typ = 0; /* somebody changed objects[]? punt */ + d->typ = 0; /* somebody changed objects[]? punt */ } else { /* try to construct canonical form */ char tbuf[BUFSZ]; Strcpy(tbuf, "worthless piece of "); - Strcat(tbuf, g); /* assume it starts with the color */ - Strcpy(bp, tbuf); + Strcat(tbuf, s); /* assume it starts with the color */ + Strcpy(d->bp, tbuf); } } - actualn = bp; - if (!dn) - dn = actualn; /* ex. "skull cap" */ - srch: + d->actualn = d->bp; + if (!d->dn) + d->dn = d->actualn; /* ex. "skull cap" */ + + return 0; +} + +staticfn int +readobjnam_postparse3(struct _readobjnam_data *d) +{ + int i; + /* check real names of gems first */ - if (!oclass && actualn) { - for (i = bases[GEM_CLASS]; i <= LAST_GEM; i++) { - register const char *zn; + if (!d->oclass && d->actualn) { + for (i = svb.bases[GEM_CLASS]; i <= LAST_REAL_GEM; i++) { + const char *zn; - if ((zn = OBJ_NAME(objects[i])) != 0 && !strcmpi(actualn, zn)) { - typ = i; - goto typfnd; + if ((zn = OBJ_NAME(objects[i])) != 0 && !strcmpi(d->actualn, zn)) { + d->typ = i; + return 2; /*goto typfnd;*/ } } /* "tin of foo" would be caught above, but plain "tin" has a random chance of yielding "tin wand" unless we do this */ - if (!strcmpi(actualn, "tin")) { - typ = TIN; - goto typfnd; + if (!strcmpi(d->actualn, "tin")) { + d->typ = TIN; + return 2; /*goto typfnd;*/ } } - if (((typ = rnd_otyp_by_namedesc(actualn, oclass, 1)) != STRANGE_OBJECT) - || ((typ = rnd_otyp_by_namedesc(dn, oclass, 1)) != STRANGE_OBJECT) - || ((typ = rnd_otyp_by_namedesc(un, oclass, 1)) != STRANGE_OBJECT) - || ((typ = rnd_otyp_by_namedesc(origbp, oclass, 1)) != STRANGE_OBJECT)) - goto typfnd; - typ = 0; + if (((d->typ = rnd_otyp_by_namedesc(d->actualn, d->oclass, 1)) + != STRANGE_OBJECT) + || (d->dn != d->actualn + && ((d->typ = rnd_otyp_by_namedesc(d->dn, d->oclass, 1)) + != STRANGE_OBJECT)) + || ((d->typ = rnd_otyp_by_namedesc(d->un, d->oclass, 1)) + != STRANGE_OBJECT) + || (d->origbp != d->actualn + && ((d->typ = rnd_otyp_by_namedesc(d->origbp, d->oclass, 1)) + != STRANGE_OBJECT))) + return 2; /*goto typfnd;*/ + d->typ = 0; - if (actualn) { - struct Jitem *j = Japanese_items; + if (d->actualn) { + const struct Jitem *j = Japanese_items; while (j->item) { - if (actualn && !strcmpi(actualn, j->name)) { - typ = j->item; - goto typfnd; + if (!strcmpi(d->actualn, j->name)) { + d->typ = j->item; + return 2; /*goto typfnd;*/ } j++; } @@ -3589,17 +4773,31 @@ struct obj *no_wish; /* if we've stripped off "armor" and failed to match anything in objects[], append "mail" and try again to catch misnamed requests like "plate armor" and "yellow dragon scale armor" */ - if (oclass == ARMOR_CLASS && !strstri(bp, "mail")) { + if (d->oclass == ARMOR_CLASS && !strstri(d->bp, "mail")) { /* modifying bp's string is ok; we're about to resort to random armor if this also fails to match anything */ - Strcat(bp, " mail"); - goto retry; + Strcat(d->bp, " mail"); + return 6; /*goto retry;*/ } - if (!strcmpi(bp, "spinach")) { - contents = SPINACH; - typ = TIN; - goto typfnd; + if (!strcmpi(d->bp, "spinach")) { + d->contents = TIN_SPINACH; + d->typ = TIN; + return 2; /*goto typfnd;*/ } + /* Fruits must not mess up the ability to wish for real objects (since + * you can leave a fruit in a bones file and it will be added to + * another person's game), so they must be checked for last, after + * stripping all the possible prefixes and seeing if there's a real + * name in there. So we have to save the full original name. However, + * it's still possible to do things like "uncursed burnt Alaska", + * or worse yet, "2 burned 5 course meals", so we need to loop to + * strip off the prefixes again, this time stripping only the ones + * possible on food. + * We could get even more detailed so as to allow food names with + * prefixes that _are_ possible on food, so you could wish for + * "2 3 alarm chilis". Currently this isn't allowed; options.c + * automatically sticks 'candied' in front of such names. + */ /* Note: not strcmpi. 2 fruits, one capital, one not, are possible. Also not strncmp. We used to ignore trailing text with it, but that resulted in "grapefruit" matching "grape" if the latter came @@ -3608,11 +4806,12 @@ struct obj *no_wish; char *fp; int l, cntf; int blessedf, iscursedf, uncursedf, halfeatenf; + struct fruit *f; blessedf = iscursedf = uncursedf = halfeatenf = 0; cntf = 0; - fp = fruitbuf; + fp = d->fruitbuf; for (;;) { if (!fp || !*fp) break; @@ -3639,7 +4838,7 @@ struct obj *no_wish; fp += l; } - for (f = ffruit; f; f = f->nextf) { + for (f = gf.ffruit; f; f = f->nextf) { /* match type: 0=none, 1=exact, 2=singular, 3=plural */ int ftyp = 0; @@ -3650,11 +4849,11 @@ struct obj *no_wish; else if (!strcmp(fp, makeplural(f->fname))) ftyp = 3; if (ftyp) { - typ = SLIME_MOLD; - blessed = blessedf; - iscursed = iscursedf; - uncursed = uncursedf; - halfeaten = halfeatenf; + d->typ = SLIME_MOLD; + d->blessed = blessedf; + d->iscursed = iscursedf; + d->uncursed = uncursedf; + d->halfeaten = halfeatenf; /* adjust count if user explicitly asked for singular amount (can't happen unless fruit has been given an already pluralized name) @@ -3663,24 +4862,110 @@ struct obj *no_wish; cntf = 1; else if (ftyp == 3 && !cntf) cntf = 2; - cnt = cntf; - ftype = f->fid; - goto typfnd; + d->cnt = cntf; + d->ftype = f->fid; + return 2; /*goto typfnd;*/ } } } - if (!oclass && actualn) { + if (!d->oclass && d->actualn) { short objtyp; /* Perhaps it's an artifact specified by name, not type */ - name = artifact_name(actualn, &objtyp); - if (name) { - typ = objtyp; - goto typfnd; + d->name = artifact_name(d->actualn, &objtyp, TRUE); + if (d->name) { + d->typ = objtyp; + return 2; /*goto typfnd;*/ } } + /* got a class, but not specific type; + check alternate spellings of items with matching classes */ + if (d->oclass && !d->typ) { + const struct alt_spellings *as = spellings; + + while (as->sp) { + if (objects[as->ob].oc_class == d->oclass + && wishymatch(d->bp, as->sp, TRUE)) { + d->typ = as->ob; + return 2; /*goto typfnd;*/ + } + as++; + } + } + + return 0; +} + + +/* + * Return something wished for. Specifying a null pointer for + * the user request string results in a random object. Otherwise, + * if asking explicitly for "nothing" (or "nil") return no_wish; + * if not an object return &hands_obj; if an error (no matching object), + * return null. + */ +struct obj * +readobjnam(char *bp, struct obj *no_wish) +{ + struct _readobjnam_data d; + + readobjnam_init(bp, &d); + if (!bp) + goto any; + + /* first, remove extra whitespace they may have typed */ + (void) mungspaces(bp); + /* allow wishing for "nothing" to preserve wishless conduct... + [now requires "wand of nothing" if that's what was really wanted] */ + if (!strcmpi(bp, "nothing") || !strcmpi(bp, "nil") + || !strcmpi(bp, "none")) + return no_wish; + /* save the [nearly] unmodified choice string */ + Strcpy(d.fruitbuf, bp); + + if (readobjnam_preparse(&d)) + goto any; + + if (!d.cnt) + d.cnt = 1; /* will be changed to 2 if makesingular() changes string */ + + readobjnam_parse_charges(&d); + + switch (readobjnam_postparse1(&d)) { + default: + case 0: break; + case 1: goto srch; + case 2: goto typfnd; + case 3: return d.otmp; + case 4: goto any; + case 5: goto wiztrap; + } + + retry: + switch (readobjnam_postparse2(&d)) { + default: + case 0: break; + case 1: goto srch; + case 2: goto typfnd; + case 3: return d.otmp; + case 4: goto any; + case 5: goto wiztrap; + } + + srch: + switch (readobjnam_postparse3(&d)) { + default: + case 0: break; + case 1: goto srch; + case 2: goto typfnd; + case 3: return d.otmp; + case 4: goto any; + case 5: goto wiztrap; + case 6: goto retry; + } + /* * Let wizards wish for traps and furniture. * Must come after objects check so wizards can still wish for @@ -3688,305 +4973,281 @@ struct obj *no_wish; * Disallow such topology tweaks for WIZKIT startup wishes. */ wiztrap: - if (wizard && !program_state.wizkit_wishing) { - struct rm *lev; - boolean madeterrain = FALSE; - int trap, x = u.ux, y = u.uy; - - for (trap = NO_TRAP + 1; trap < TRAPNUM; trap++) { - struct trap *t; - const char *tname; - - tname = defsyms[trap_to_defsym(trap)].explanation; - if (strncmpi(tname, bp, strlen(tname))) - continue; - /* found it; avoid stupid mistakes */ - if (is_hole(trap) && !Can_fall_thru(&u.uz)) - trap = ROCKTRAP; - if ((t = maketrap(x, y, trap)) != 0) { - trap = t->ttyp; - tname = defsyms[trap_to_defsym(trap)].explanation; - pline("%s%s.", An(tname), - (trap != MAGIC_PORTAL) ? "" : " to nowhere"); - } else - pline("Creation of %s failed.", an(tname)); - return (struct obj *) &zeroobj; - } - - /* furniture and terrain (use at your own risk; can clobber stairs - or place furniture on existing traps which shouldn't be allowed) */ - lev = &levl[x][y]; - p = eos(bp); - if (!BSTRCMPI(bp, p - 8, "fountain")) { - lev->typ = FOUNTAIN; - level.flags.nfountains++; - if (!strncmpi(bp, "magic ", 6)) - lev->blessedftn = 1; - pline("A %sfountain.", lev->blessedftn ? "magic " : ""); - madeterrain = TRUE; - } else if (!BSTRCMPI(bp, p - 6, "throne")) { - lev->typ = THRONE; - pline("A throne."); - madeterrain = TRUE; - } else if (!BSTRCMPI(bp, p - 4, "sink")) { - lev->typ = SINK; - level.flags.nsinks++; - pline("A sink."); - madeterrain = TRUE; - - /* ("water" matches "potion of water" rather than terrain) */ - } else if (!BSTRCMPI(bp, p - 4, "pool") - || !BSTRCMPI(bp, p - 4, "moat")) { - lev->typ = !BSTRCMPI(bp, p - 4, "pool") ? POOL : MOAT; - del_engr_at(x, y); - pline("A %s.", (lev->typ == POOL) ? "pool" : "moat"); - /* Must manually make kelp! */ - water_damage_chain(level.objects[x][y], TRUE); - madeterrain = TRUE; - - /* also matches "molten lava" */ - } else if (!BSTRCMPI(bp, p - 4, "lava")) { - lev->typ = LAVAPOOL; - del_engr_at(x, y); - pline("A pool of molten lava."); - if (!(Levitation || Flying)) - pooleffects(FALSE); - madeterrain = TRUE; - } else if (!BSTRCMPI(bp, p - 5, "altar")) { - aligntyp al; - - lev->typ = ALTAR; - if (!strncmpi(bp, "chaotic ", 8)) - al = A_CHAOTIC; - else if (!strncmpi(bp, "neutral ", 8)) - al = A_NEUTRAL; - else if (!strncmpi(bp, "lawful ", 7)) - al = A_LAWFUL; - else if (!strncmpi(bp, "unaligned ", 10)) - al = A_NONE; - else /* -1 - A_CHAOTIC, 0 - A_NEUTRAL, 1 - A_LAWFUL */ - al = !rn2(6) ? A_NONE : (rn2((int) A_LAWFUL + 2) - 1); - lev->altarmask = Align2amask(al); - pline("%s altar.", An(align_str(al))); - madeterrain = TRUE; - } else if (!BSTRCMPI(bp, p - 5, "grave") - || !BSTRCMPI(bp, p - 9, "headstone")) { - make_grave(x, y, (char *) 0); - pline("%s.", IS_GRAVE(lev->typ) ? "A grave" - : "Can't place a grave here"); - madeterrain = TRUE; - } else if (!BSTRCMPI(bp, p - 4, "tree")) { - lev->typ = TREE; - pline("A tree."); - block_point(x, y); - madeterrain = TRUE; - } else if (!BSTRCMPI(bp, p - 4, "bars")) { - lev->typ = IRONBARS; - pline("Iron bars."); - madeterrain = TRUE; - } - - if (madeterrain) { - feel_newsym(x, y); /* map the spot where the wish occurred */ - /* hero started at but might not be there anymore (create - lava, decline to die, and get teleported away to safety) */ - if (u.uinwater && !is_pool(u.ux, u.uy)) { - u.uinwater = 0; /* leave the water */ - docrt(); - vision_full_recalc = 1; - } else if (u.utrap && u.utraptype == TT_LAVA - && !is_lava(u.ux, u.uy)) { - reset_utrap(FALSE); - } - /* cast 'const' away; caller won't modify this */ - return (struct obj *) &zeroobj; - } - } /* end of wizard mode traps and terrain */ + if (wizard && !program_state.wizkit_wishing && !d.oclass) { + /* [inline code moved to separate routine to unclutter readobjnam] */ + if ((d.otmp = wizterrainwish(&d)) != 0) + return d.otmp; + } - if (!oclass && !typ) { - if (!strncmpi(bp, "polearm", 7)) { - typ = rnd_otyp_by_wpnskill(P_POLEARMS); + if (!d.oclass && !d.typ) { + if (!strncmpi(d.bp, "polearm", 7)) { + d.typ = rnd_otyp_by_wpnskill(P_POLEARMS); goto typfnd; - } else if (!strncmpi(bp, "hammer", 6)) { - typ = rnd_otyp_by_wpnskill(P_HAMMER); + } else if (!strncmpi(d.bp, "hammer", 6)) { + d.typ = rnd_otyp_by_wpnskill(P_HAMMER); goto typfnd; } } - if (!oclass) + if (!d.oclass) return ((struct obj *) 0); any: - if (!oclass) - oclass = wrpsym[rn2((int) sizeof wrpsym)]; + if (!d.oclass) + d.oclass = wrpsym[rn2((int) sizeof wrpsym)]; typfnd: - if (typ) - oclass = objects[typ].oc_class; + if (d.typ) + d.oclass = objects[d.typ].oc_class; /* handle some objects that are only allowed in wizard mode */ - if (typ && !wizard) { - switch (typ) { + if (d.typ && !wizard) { + switch (d.typ) { case AMULET_OF_YENDOR: - typ = FAKE_AMULET_OF_YENDOR; + d.typ = FAKE_AMULET_OF_YENDOR; break; case CANDELABRUM_OF_INVOCATION: - typ = rnd_class(TALLOW_CANDLE, WAX_CANDLE); + d.typ = rnd_class(TALLOW_CANDLE, WAX_CANDLE); break; case BELL_OF_OPENING: - typ = BELL; + d.typ = BELL; break; case SPE_BOOK_OF_THE_DEAD: - typ = SPE_BLANK_PAPER; + d.typ = SPE_BLANK_PAPER; break; case MAGIC_LAMP: - typ = OIL_LAMP; + d.typ = OIL_LAMP; break; default: /* catch any other non-wishable objects (venom) */ - if (objects[typ].oc_nowish) + if (objects[d.typ].oc_nowish) return (struct obj *) 0; break; } } + /* if asking for corpse of a monster which leaves behind a glob, give + glob instead of rejecting the monster type to create random corpse */ + if (d.typ == CORPSE && d.mntmp >= LOW_PM + && mons[d.mntmp].mlet == S_PUDDING) { + d.typ = GLOB_OF_GRAY_OOZE + (d.mntmp - PM_GRAY_OOZE); + d.mntmp = NON_PM; /* not used for globs */ + } /* * Create the object, then fine-tune it. */ - otmp = typ ? mksobj(typ, TRUE, FALSE) : mkobj(oclass, FALSE); - typ = otmp->otyp, oclass = otmp->oclass; /* what we actually got */ - - if (islit && (typ == OIL_LAMP || typ == MAGIC_LAMP || typ == BRASS_LANTERN - || Is_candle(otmp) || typ == POT_OIL)) { - place_object(otmp, u.ux, u.uy); /* make it viable light source */ - begin_burn(otmp, FALSE); - obj_extract_self(otmp); /* now release it for caller's use */ - } - - /* if player specified a reasonable count, maybe honor it */ - if (cnt > 0 && objects[typ].oc_merge - && (wizard || cnt < rnd(6) || (cnt <= 7 && Is_candle(otmp)) - || (cnt <= 20 && ((oclass == WEAPON_CLASS && is_ammo(otmp)) - || typ == ROCK || is_missile(otmp))))) - otmp->quan = (long) cnt; - - if (oclass == VENOM_CLASS) - otmp->spe = 1; - - if (spesgn == 0) { - spe = otmp->spe; + d.otmp = d.typ ? mksobj(d.typ, TRUE, FALSE) : mkobj(d.oclass, FALSE); + d.typ = d.otmp->otyp, d.oclass = d.otmp->oclass; /* what we actually got */ + + /* if player specified a reasonable count, maybe honor it; + quantity for gold is handled elsewhere and d.cnt is 0 for it here */ + if (d.otmp->globby) { + /* for globs, calculate weight based on gsize, then multiply by cnt; + asking for 2 globs or for 2 small globs produces 1 small glob + weighing 40au instead of normal 20au; asking for 5 medium globs + might produce 1 very large glob weighing 600au */ + d.otmp->quan = 1L; /* always 1 for globs */ + d.otmp->owt = weight(d.otmp); + /* gsize 0: unspecified => small; + 1: small (1..5) => keep default owt for 1, yielding 20; + 2: medium (6..15) => use weight for 6, yielding 120; + 3: large (16..25) => 320; 4: very large (26+) => 520 */ + if (d.gsize > 1) + d.otmp->owt += ((unsigned) (5 + (d.gsize - 2) * 10) + * d.otmp->owt); /* 20 + {5|15|25} times 20 */ + /* limit overall weight which limits shrink-away time which in turn + affects how long some of it will remain available to be eaten */ + if (d.cnt > 1) { + int rn1cnt = rn1(5, 2); /* 2..6 */ + + if (rn1cnt > 6 - d.gsize) + rn1cnt = 6 - d.gsize; + if (d.cnt > rn1cnt + && (!wizard || program_state.wizkit_wishing + || y_n("Override glob weight limit?") != 'y')) + d.cnt = rn1cnt; + d.otmp->owt *= (unsigned) d.cnt; + } + /* note: the owt assignment below will not change glob's weight */ + d.cnt = 0; + } else if (d.cnt > 0) { + if (objects[d.typ].oc_merge + && (wizard /* quantity isn't restricted when debugging */ + /* note: in normal play, explicitly asking for 1 might + fail the 'cnt < rnd(6)' test and could produce more + than 1 if mksobj() creates the item that way */ + || d.cnt < rnd(6) + || (d.cnt <= 7 && Is_candle(d.otmp)) + || (d.cnt <= 20 + && (d.typ == ROCK || d.typ == FLINT || is_missile(d.otmp) + /* WEAPON_CLASS test excludes gems, gray stones */ + || (d.oclass == WEAPON_CLASS && is_ammo(d.otmp)))))) + d.otmp->quan = (long) d.cnt; + } + + if (d.islit && (d.typ == OIL_LAMP || d.typ == MAGIC_LAMP + || d.typ == BRASS_LANTERN + || Is_candle(d.otmp) || d.typ == POT_OIL)) { + place_object(d.otmp, u.ux, u.uy); /* make it viable light source */ + begin_burn(d.otmp, FALSE); + obj_extract_self(d.otmp); /* now release it for caller's use */ + } + + if (d.spesgn == 0) { + /* spe not specified; retain the randomly assigned value */ + d.spe = d.otmp->spe; } else if (wizard) { - ; /* no alteration to spe */ - } else if (oclass == ARMOR_CLASS || oclass == WEAPON_CLASS - || is_weptool(otmp) - || (oclass == RING_CLASS && objects[typ].oc_charged)) { - if (spe > rnd(5) && spe > otmp->spe) - spe = 0; - if (spe > 2 && Luck < 0) - spesgn = -1; + ; /* no restrictions except SPE_LIM */ + } else if (d.oclass == ARMOR_CLASS || d.oclass == WEAPON_CLASS + || is_weptool(d.otmp) + || (d.oclass == RING_CLASS && objects[d.typ].oc_charged)) { + if (d.spe > rnd(5) && d.spe > d.otmp->spe) + d.spe = 0; + if (d.spe > 2 && Luck < 0) + d.spesgn = -1; } else { - if (oclass == WAND_CLASS) { - if (spe > 1 && spesgn == -1) - spe = 1; + /* crystal ball cancels like a wand, to (n:-1) */ + if (d.oclass == WAND_CLASS || d.typ == CRYSTAL_BALL) { + if (d.spe > 1 && d.spesgn == -1) + d.spe = 1; } else { - if (spe > 0 && spesgn == -1) - spe = 0; + if (d.spe > 0 && d.spesgn == -1) + d.spe = 0; } - if (spe > otmp->spe) - spe = otmp->spe; + if (d.spe > d.otmp->spe) + d.spe = d.otmp->spe; } - if (spesgn == -1) - spe = -spe; + if (d.spesgn == -1) + d.spe = -d.spe; - /* set otmp->spe. This may, or may not, use spe... */ - switch (typ) { + /* set otmp->spe. This may, or may not, use d.spe... */ + switch (d.typ) { case TIN: - if (contents == EMPTY) { - otmp->corpsenm = NON_PM; - otmp->spe = 0; - } else if (contents == SPINACH) { - otmp->corpsenm = NON_PM; - otmp->spe = 1; + d.otmp->spe = 0; /* default: not spinach */ + if (d.contents == TIN_EMPTY) { + d.otmp->corpsenm = NON_PM; + } else if (d.contents == TIN_SPINACH) { + d.otmp->corpsenm = NON_PM; + d.otmp->spe = 1; /* spinach after all */ } break; case TOWEL: - if (wetness) - otmp->spe = wetness; + if (d.wetness) + d.otmp->spe = d.wetness; break; case SLIME_MOLD: - otmp->spe = ftype; - /* Fall through */ + d.otmp->spe = d.ftype; + FALLTHROUGH; + /* FALLTHRU */ case SKELETON_KEY: case CHEST: case LARGE_BOX: case HEAVY_IRON_BALL: case IRON_CHAIN: - case STATUE: - /* otmp->cobj already done in mksobj() */ break; -#ifdef MAIL + case STATUE: /* otmp->cobj already done in mksobj() */ + case FIGURINE: + case CORPSE: { + struct permonst *P = (ismnum(d.mntmp)) ? &mons[d.mntmp] : 0; + + d.otmp->spe = !P ? CORPSTAT_RANDOM + /* if neuter, force neuter regardless of wish request */ + : is_neuter(P) ? CORPSTAT_NEUTER + /* not neuter, honor wish unless it conflicts */ + : (d.mgend == FEMALE && !is_male(P)) ? CORPSTAT_FEMALE + : (d.mgend == MALE && !is_female(P)) ? CORPSTAT_MALE + /* unspecified or wish conflicts */ + : CORPSTAT_RANDOM; + if (P && d.otmp->spe == CORPSTAT_RANDOM) + d.otmp->spe = is_male(P) ? CORPSTAT_MALE + : is_female(P) ? CORPSTAT_FEMALE + : rn2(2) ? CORPSTAT_MALE : CORPSTAT_FEMALE; + if (d.ishistoric && d.typ == STATUE) + d.otmp->spe |= CORPSTAT_HISTORIC; + break; + }; +#ifdef MAIL_STRUCTURES + /* scroll of mail: 0: delivered in-game via external event (or randomly + for fake mail); 1: from bones or wishing; 2: written with marker */ case SCR_MAIL: - /* 0: delivered in-game via external event (or randomly for fake mail); - 1: from bones or wishing; 2: written with marker */ - otmp->spe = 1; + d.otmp->spe = 1; break; #endif + /* splash of venom: 0: normal, and transitory; 1: wishing */ + case ACID_VENOM: + case BLINDING_VENOM: + d.otmp->spe = 1; + break; case WAN_WISHING: if (!wizard) { - otmp->spe = (rn2(10) ? -1 : 0); + d.otmp->spe = (rn2(10) ? -1 : 0); break; } + FALLTHROUGH; /*FALLTHRU*/ default: - otmp->spe = spe; + d.otmp->spe = d.spe; } /* set otmp->corpsenm or dragon scale [mail] */ - if (mntmp >= LOW_PM) { - if (mntmp == PM_LONG_WORM_TAIL) - mntmp = PM_LONG_WORM; - - switch (typ) { + if (ismnum(d.mntmp)) { + int humanwere; + + if (d.mntmp == PM_LONG_WORM_TAIL) + d.mntmp = PM_LONG_WORM; + /* werecreatures in beast form are all flagged no-corpse so for + corpses and tins, switch to their corresponding human form; + for figurines, override the can't-be-human restriction instead */ + if (d.typ != FIGURINE && is_were(&mons[d.mntmp]) + && (svm.mvitals[d.mntmp].mvflags & G_NOCORPSE) != 0 + && (humanwere = counter_were(d.mntmp)) != NON_PM) + d.mntmp = humanwere; + + switch (d.typ) { case TIN: - otmp->spe = 0; /* No spinach */ - if (dead_species(mntmp, FALSE)) { - otmp->corpsenm = NON_PM; /* it's empty */ - } else if ((!(mons[mntmp].geno & G_UNIQ) || wizard) - && !(mvitals[mntmp].mvflags & G_NOCORPSE) - && mons[mntmp].cnutrit != 0) { - otmp->corpsenm = mntmp; + if (dead_species(d.mntmp, FALSE)) { + d.otmp->corpsenm = NON_PM; /* it's empty */ + } else if ((!(mons[d.mntmp].geno & G_UNIQ) || wizard) + && !(svm.mvitals[d.mntmp].mvflags & G_NOCORPSE) + && mons[d.mntmp].cnutrit != 0) { + d.otmp->corpsenm = d.mntmp; } break; case CORPSE: - if ((!(mons[mntmp].geno & G_UNIQ) || wizard) - && !(mvitals[mntmp].mvflags & G_NOCORPSE)) { - if (mons[mntmp].msound == MS_GUARDIAN) - mntmp = genus(mntmp, 1); - set_corpsenm(otmp, mntmp); + if ((!(mons[d.mntmp].geno & G_UNIQ) || wizard) + && !(svm.mvitals[d.mntmp].mvflags & G_NOCORPSE)) { + if (mons[d.mntmp].msound == MS_GUARDIAN) + d.mntmp = genus(d.mntmp, 1); + set_corpsenm(d.otmp, d.mntmp); + } + if (d.zombify && zombie_form(&mons[d.mntmp])) { + (void) start_timer(rn1(5, 10), TIMER_OBJECT, + ZOMBIFY_MON, obj_to_any(d.otmp)); } break; case EGG: - mntmp = can_be_hatched(mntmp); + d.mntmp = can_be_hatched(d.mntmp); /* this also sets hatch timer if appropriate */ - set_corpsenm(otmp, mntmp); + set_corpsenm(d.otmp, d.mntmp); break; case FIGURINE: - if (!(mons[mntmp].geno & G_UNIQ) && !is_human(&mons[mntmp]) -#ifdef MAIL - && mntmp != PM_MAIL_DAEMON + if (!(mons[d.mntmp].geno & G_UNIQ) + && (!is_human(&mons[d.mntmp]) || is_were(&mons[d.mntmp])) +#ifdef MAIL_STRUCTURES + && d.mntmp != PM_MAIL_DAEMON #endif ) - otmp->corpsenm = mntmp; + d.otmp->corpsenm = d.mntmp; break; case STATUE: - otmp->corpsenm = mntmp; - if (Has_contents(otmp) && verysmall(&mons[mntmp])) - delete_contents(otmp); /* no spellbook */ - otmp->spe = ishistoric ? STATUE_HISTORIC : 0; + d.otmp->corpsenm = d.mntmp; + if (Has_contents(d.otmp) && verysmall(&mons[d.mntmp])) + delete_contents(d.otmp); /* no spellbook */ break; case SCALE_MAIL: /* Dragon mail - depends on the order of objects & dragons. */ - if (mntmp >= PM_GRAY_DRAGON && mntmp <= PM_YELLOW_DRAGON) - otmp->otyp = GRAY_DRAGON_SCALE_MAIL + mntmp - PM_GRAY_DRAGON; + if (d.mntmp >= PM_GRAY_DRAGON && d.mntmp <= PM_YELLOW_DRAGON) + d.otmp->otyp = GRAY_DRAGON_SCALE_MAIL + + d.mntmp - PM_GRAY_DRAGON; break; } } @@ -3994,181 +5255,220 @@ struct obj *no_wish; /* set blessed/cursed -- setting the fields directly is safe * since weight() is called below and addinv() will take care * of luck */ - if (iscursed) { - curse(otmp); - } else if (uncursed) { - otmp->blessed = 0; - otmp->cursed = (Luck < 0 && !wizard); - } else if (blessed) { - otmp->blessed = (Luck >= 0 || wizard); - otmp->cursed = (Luck < 0 && !wizard); - } else if (spesgn < 0) { - curse(otmp); + if (d.iscursed) { + curse(d.otmp); + } else if (d.uncursed) { + d.otmp->blessed = 0; + d.otmp->cursed = (Luck < 0 && !wizard); + } else if (d.blessed) { + d.otmp->blessed = (Luck >= 0 || wizard); + d.otmp->cursed = (Luck < 0 && !wizard); + } else if (d.spesgn < 0) { + curse(d.otmp); } /* set eroded and erodeproof */ - if (erosion_matters(otmp)) { - if (eroded && (is_flammable(otmp) || is_rustprone(otmp))) - otmp->oeroded = eroded; - if (eroded2 && (is_corrodeable(otmp) || is_rottable(otmp))) - otmp->oeroded2 = eroded2; + if (erosion_matters(d.otmp)) { + /* wished-for item shouldn't be eroded unless specified */ + d.otmp->oeroded = d.otmp->oeroded2 = 0; + if (d.eroded && (is_flammable(d.otmp) || is_rustprone(d.otmp) + || is_crackable(d.otmp))) + d.otmp->oeroded = d.eroded; + if (d.eroded2 && (is_corrodeable(d.otmp) || is_rottable(d.otmp))) + d.otmp->oeroded2 = d.eroded2; /* * 3.6.1: earlier versions included `&& !eroded && !eroded2' here, * but damageproof combined with damaged is feasible (eroded * armor modified by confused reading of cursed destroy armor) * so don't prevent player from wishing for such a combination. */ - if (erodeproof && (is_damageable(otmp) || otmp->otyp == CRYSKNIFE)) - otmp->oerodeproof = (Luck >= 0 || wizard); + if (d.erodeproof + && (is_damageable(d.otmp) || d.otmp->otyp == CRYSKNIFE)) + d.otmp->oerodeproof = (Luck >= 0 || wizard); } /* set otmp->recharged */ - if (oclass == WAND_CLASS) { + if (d.oclass == WAND_CLASS) { /* prevent wishing abuse */ - if (otmp->otyp == WAN_WISHING && !wizard) - rechrg = 1; - otmp->recharged = (unsigned) rechrg; + if (d.otmp->otyp == WAN_WISHING && !wizard) + d.rechrg = 1; + d.otmp->recharged = (unsigned) d.rechrg; } /* set poisoned */ - if (ispoisoned) { - if (is_poisonable(otmp)) - otmp->opoisoned = (Luck >= 0); - else if (oclass == FOOD_CLASS) + if (d.ispoisoned) { + if (is_poisonable(d.otmp)) + d.otmp->opoisoned = (Luck >= 0); + else if (d.oclass == FOOD_CLASS) /* try to taint by making it as old as possible */ - otmp->age = 1L; + d.otmp->age = 1L; } /* and [un]trapped */ - if (trapped) { - if (Is_box(otmp) || typ == TIN) - otmp->otrapped = (trapped == 1); + if (d.trapped) { + if (Is_box(d.otmp) || d.typ == TIN) + d.otmp->otrapped = (d.trapped == 1); } /* empty for containers rather than for tins */ - if (contents == EMPTY) { - if (otmp->otyp == BAG_OF_TRICKS || otmp->otyp == HORN_OF_PLENTY) { - if (otmp->spe > 0) - otmp->spe = 0; - } else if (Has_contents(otmp)) { + if (d.contents == TIN_EMPTY) { + if (d.otmp->otyp == BAG_OF_TRICKS || d.otmp->otyp == HORN_OF_PLENTY) { + if (d.otmp->spe > 0) + d.otmp->spe = 0; + } else if (Has_contents(d.otmp)) { /* this assumes that artifacts can't be randomly generated inside containers */ - delete_contents(otmp); - otmp->owt = weight(otmp); + delete_contents(d.otmp); + d.otmp->owt = weight(d.otmp); } } /* set locked/unlocked/broken */ - if (Is_box(otmp)) { - if (locked) { - otmp->olocked = 1, otmp->obroken = 0; - } else if (unlocked) { - otmp->olocked = 0, otmp->obroken = 0; - } else if (broken) { - otmp->olocked = 0, otmp->obroken = 1; + if (Is_box(d.otmp)) { + if (d.locked) { + d.otmp->olocked = 1, d.otmp->obroken = 0; + } else if (d.unlocked) { + d.otmp->olocked = 0, d.otmp->obroken = 0; + } else if (d.broken) { + d.otmp->olocked = 0, d.otmp->obroken = 1; } + if (d.otmp->obroken) + d.otmp->otrapped = 0; } - if (isgreased) - otmp->greased = 1; + if (d.isgreased) + d.otmp->greased = 1; - if (isdiluted && otmp->oclass == POTION_CLASS && otmp->otyp != POT_WATER) - otmp->odiluted = 1; + if (d.isdiluted && d.otmp->oclass == POTION_CLASS) + d.otmp->odiluted = (d.otmp->otyp != POT_WATER); /* set tin variety */ - if (otmp->otyp == TIN && tvariety >= 0 && (rn2(4) || wizard)) - set_tin_variety(otmp, tvariety); + if (d.otmp->otyp == TIN && d.tvariety >= 0 && (rn2(4) || wizard)) + set_tin_variety(d.otmp, d.tvariety); - if (name) { - const char *aname; + if (d.name) { + const char *aname, *novelname; short objtyp; /* an artifact name might need capitalization fixing */ - aname = artifact_name(name, &objtyp); - if (aname && objtyp == otmp->otyp) - name = aname; + aname = artifact_name(d.name, &objtyp, TRUE); + if (aname && objtyp == d.otmp->otyp) + d.name = aname; /* 3.6 tribute - fix up novel */ - if (otmp->otyp == SPE_NOVEL) { - const char *novelname; - - novelname = lookup_novel(name, &otmp->novelidx); - if (novelname) - name = novelname; - } + if (d.otmp->otyp == SPE_NOVEL + && (novelname = lookup_novel(d.name, &d.otmp->novelidx)) != 0) + d.name = novelname; - otmp = oname(otmp, name); + d.otmp = oname(d.otmp, d.name, ONAME_WISH); /* name==aname => wished for artifact (otmp->oartifact => got it) */ - if (otmp->oartifact || name == aname) { - otmp->quan = 1L; + if (d.otmp->oartifact || d.name == aname) { + d.otmp->quan = 1L; u.uconduct.wisharti++; /* KMH, conduct */ } } + if (permapoisoned(d.otmp)) + d.otmp->opoisoned = 1; + /* more wishing abuse: don't allow wishing for certain artifacts */ /* and make them pay; charge them for the wish anyway! */ - if ((is_quest_artifact(otmp) - || (otmp->oartifact && rn2(nartifact_exist()) > 1)) && !wizard) { - artifact_exists(otmp, safe_oname(otmp), FALSE); - obfree(otmp, (struct obj *) 0); - otmp = (struct obj *) &zeroobj; + if ((is_quest_artifact(d.otmp) + || (d.otmp->oartifact && rn2(nartifact_exist()) > 1)) && !wizard) { + artifact_exists(d.otmp, safe_oname(d.otmp), FALSE, ONAME_NO_FLAGS); + obfree(d.otmp, (struct obj *) 0); + d.otmp = &hands_obj; pline("For a moment, you feel %s in your %s, but it disappears!", something, makeplural(body_part(HAND))); - return otmp; + return d.otmp; } - if (halfeaten && otmp->oclass == FOOD_CLASS) { - if (otmp->otyp == CORPSE) - otmp->oeaten = mons[otmp->corpsenm].cnutrit; - else - otmp->oeaten = objects[otmp->otyp].oc_nutrition; - /* (do this adjustment before setting up object's weight) */ - consume_oeaten(otmp, 1); - } - otmp->owt = weight(otmp); - if (very && otmp->otyp == HEAVY_IRON_BALL) - otmp->owt += IRON_BALL_W_INCR; - else if (gsize > 1 && otmp->globby) - /* 0: unspecified => small; 1: small => keep default owt of 20; - 2: medium => 120; 3: large => 320; 4: very large => 520 */ - otmp->owt += 100 + (gsize - 2) * 200; - - return otmp; + if (d.halfeaten && d.otmp->oclass == FOOD_CLASS) { + unsigned nut = obj_nutrition(d.otmp); + + /* do this adjustment before setting up object's weight; skip + "partly eaten" for food with 0 nutrition (wraith corpse) or for + anything that couldn't take more than one bite (1 nutrition; + ought to check for one-bite instead but that's complicated) */ + if (nut > 1) { + d.otmp->oeaten = nut; + consume_oeaten(d.otmp, 1); + } + } + d.otmp->owt = weight(d.otmp); + if (d.very && d.otmp->otyp == HEAVY_IRON_BALL) + d.otmp->owt += WT_IRON_BALL_INCR; + + return d.otmp; } int -rnd_class(first, last) -int first, last; +rnd_class(int first, int last) { int i, x, sum = 0; - if (first == last) - return first; - for (i = first; i <= last; i++) - sum += objects[i].oc_prob; - if (!sum) /* all zero */ - return first + rn2(last - first + 1); - x = rnd(sum); - for (i = first; i <= last; i++) - if (objects[i].oc_prob && (x -= objects[i].oc_prob) <= 0) - return i; - return 0; + if (last > first) { + for (i = first; i <= last; i++) + sum += objects[i].oc_prob; + if (!sum) /* all zero, so equal probability */ + return rn1(last - first + 1, first); + + x = rnd(sum); + for (i = first; i <= last; i++) + if ((x -= objects[i].oc_prob) <= 0) + return i; + } + return (first == last) ? first : STRANGE_OBJECT; } -STATIC_OVL const char * -Japanese_item_name(i) -int i; +const char * +Japanese_item_name(int i, const char *ordinaryname) { - struct Jitem *j = Japanese_items; + const struct Jitem *j = Japanese_items; while (j->item) { if (i == j->item) return j->name; j++; } - return (const char *) 0; + return ordinaryname; +} + +const char * +armor_simple_name(struct obj *armor) +{ + const char *result = 0; + unsigned armcat = objects[armor->otyp].oc_armcat; + + switch (armcat) { + case ARM_SUIT: + result = suit_simple_name(armor); + break; + case ARM_CLOAK: + result = cloak_simple_name(armor); + break; + case ARM_HELM: + result = helm_simple_name(armor); + break; + case ARM_GLOVES: + result = gloves_simple_name(armor); + break; + case ARM_BOOTS: + result = boots_simple_name(armor); + break; + case ARM_SHIELD: + result = shield_simple_name(armor); + break; + case ARM_SHIRT: + result = shirt_simple_name(armor); + break; + default: + result = simpleonames(armor); + impossible("unknown armor category (%s => %u)", result, armcat); + break; + } + return result; } const char * -suit_simple_name(suit) -struct obj *suit; +suit_simple_name(struct obj *suit) { const char *suitnm, *esuitp; @@ -4189,8 +5489,7 @@ struct obj *suit; } const char * -cloak_simple_name(cloak) -struct obj *cloak; +cloak_simple_name(struct obj *cloak) { if (cloak) { switch (cloak->otyp) { @@ -4211,8 +5510,7 @@ struct obj *cloak; /* helm vs hat for messages */ const char * -helm_simple_name(helmet) -struct obj *helmet; +helm_simple_name(struct obj *helmet) { /* * There is some wiggle room here; the result has been chosen @@ -4226,13 +5524,12 @@ struct obj *helmet; * fedora, cornuthaum, dunce cap -> hat * all other types of helmets -> helm */ - return (helmet && !is_metallic(helmet)) ? "hat" : "helm"; + return !hard_helmet(helmet) ? "hat" : "helm"; } /* gloves vs gauntlets; depends upon discovery state */ const char * -gloves_simple_name(gloves) -struct obj *gloves; +gloves_simple_name(struct obj *gloves) { static const char gauntlets[] = "gauntlets"; @@ -4249,9 +5546,64 @@ struct obj *gloves; return "gloves"; } +/* boots vs shoes; depends upon discovery state */ +const char * +boots_simple_name(struct obj *boots) +{ + static const char shoes[] = "shoes"; + + if (boots && boots->dknown) { + int otyp = boots->otyp; + struct objclass *ocl = &objects[otyp]; + const char *actualn = OBJ_NAME(*ocl), + *descrpn = OBJ_DESCR(*ocl); + + if (strstri(descrpn, shoes) + || (objects[otyp].oc_name_known && strstri(actualn, shoes))) + return shoes; + } + return "boots"; +} + +/* simplified shield for messages */ const char * -mimic_obj_name(mtmp) -struct monst *mtmp; +shield_simple_name(struct obj *shield) +{ + if (shield) { + /* xname() describes unknown (unseen) reflection as smooth */ + if (shield->otyp == SHIELD_OF_REFLECTION) + return shield->dknown ? "silver shield" : "smooth shield"; + /* + * We might distinguish between wooden vs metallic or + * light vs heavy to give small benefit to spell casters. + * Fighter types probably care more about the former for + * vulnerability to fire or rust. + * + * We could do that both ways: light wooden shield, light + * metallic shield (there aren't any), heavy wooden shield, + * and heavy metallic shield but that's getting away from + * "simple name" which is intended to be shorter as well + * as less detailed than xname(). + */ +#if 0 + /* spellcasting uses a division like this */ + return (weight(shield) > (int) objects[SMALL_SHIELD].oc_weight) + ? "heavy shield" + : "light shield"; +#endif + } + return "shield"; +} + +/* for completeness */ +const char * +shirt_simple_name(struct obj *shirt UNUSED) +{ + return "shirt"; +} + +const char * +mimic_obj_name(struct monst *mtmp) { if (M_AP_TYPE(mtmp) == M_AP_OBJECT) { if (mtmp->mappearance == GOLD_PIECE) @@ -4269,12 +5621,14 @@ struct monst *mtmp; * last resort literal which should be very short), and an optional suffix. */ char * -safe_qbuf(qbuf, qprefix, qsuffix, obj, func, altfunc, lastR) -char *qbuf; /* output buffer */ -const char *qprefix, *qsuffix; -struct obj *obj; -char *FDECL((*func), (OBJ_P)), *FDECL((*altfunc), (OBJ_P)); -const char *lastR; +safe_qbuf( + char *qbuf, /* output buffer */ + const char *qprefix, + const char *qsuffix, + struct obj *obj, + char *(*func)(OBJ_P), + char *(*altfunc)(OBJ_P), + const char *lastR) { char *bufp, *endp; /* convert size_t (or int for ancient systems) to ordinary unsigned */ @@ -4285,6 +5639,7 @@ const char *lastR; lenlimit = QBUFSZ - 1; endp = qbuf + lenlimit; + assert(endp != NULL); /* workaround for static analyzer issue */ /* sanity check, aimed mainly at paniclog (it's conceivable for the result of short_oname() to be shorter than the length of the last resort string, but we ignore that possibility here) */ @@ -4342,20 +5697,4 @@ const char *lastR; return qbuf; } -STATIC_OVL char * -globwt(otmp, buf, weightformatted_p) -struct obj *otmp; -char *buf; -boolean *weightformatted_p; -{ - *buf = '\0'; - if (otmp->globby) { - Sprintf(buf, "%u aum, ", otmp->owt); - *weightformatted_p = TRUE; - } else { - *weightformatted_p = FALSE; - } - return buf; -} - /*objnam.c*/ diff --git a/src/options.c b/src/options.c index 10291518c..5f5ad1b17 100644 --- a/src/options.c +++ b/src/options.c @@ -1,36 +1,76 @@ -/* NetHack 3.6 options.c $NHDT-Date: 1578996303 2020/01/14 10:05:03 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.396 $ */ +/* NetHack 5.0 options.c $NHDT-Date: 1737556914 2025/01/22 06:41:54 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.753 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Michael Allison, 2008. */ /* NetHack may be freely redistributed. See license for details. */ -#ifdef OPTION_LISTS_ONLY /* (AMIGA) external program for opt lists */ +#ifndef OPTION_LISTS_ONLY +#include "hack.h" +#include "tcap.h" +#else /* OPTION_LISTS_ONLY: (AMIGA) external program for opt lists */ #include "config.h" #include "objclass.h" #include "flag.h" NEARDATA struct flag flags; /* provide linkage */ -#ifdef SYSFLAGS -NEARDATA struct sysflag sysflags; /* provide linkage */ -#endif NEARDATA struct instance_flags iflags; /* provide linkage */ +NEARDATA struct accessibility_data a11y; #define static -#else -#include "hack.h" -#include "tcap.h" -#include #endif #define BACKWARD_COMPAT +#define COMPLAIN_ABOUT_PRAYCONFIRM -#ifdef DEFAULT_WC_TILED_MAP -#define PREFER_TILED TRUE +/* whether the 'msg_window' option is used to control ^P behavior */ +#if defined(TTY_GRAPHICS) || defined(CURSES_GRAPHICS) +#define PREV_MSGS 1 #else -#define PREFER_TILED FALSE +#define PREV_MSGS 0 #endif -#ifdef CURSES_GRAPHICS -extern int curses_read_attrs(const char *attrs); -extern char *curses_fmt_attrs(char *); -#endif +/* + * NOTE: If you add (or delete) an option, please review the following: + * doc/options.txt + * + * It contains how-to info and outlines some required/suggested + * updates that should accompany your change. + */ + +/* + * include/optlist.h is utilized 3 successive times, for 3 different + * objectives. + * + * The first time is with NHOPT_PROTO defined, to produce and include + * the prototypes for the individual option processing functions. + * + * The second time is with NHOPT_ENUM defined, to produce the enum values + * for the individual options that are used throughout options processing. + * They are generally opt_optname, where optname is the name of the option. + * + * The third time is with NHOPT_PARSE defined, to produce the initializers + * to fill out the allopt[] array of options (both boolean and compound). + * + */ + +#define OPTIONS_C + +#define NHOPT_PROTO +#include "optlist.h" +#undef NHOPT_PROTO + +#define NHOPT_PARSE +static struct allopt_t allopt_init[] = { +#include "optlist.h" + {(const char *) 0, OptS_Advanced, 0, 0, 0, set_in_sysconf, BoolOpt, + No, No, No, No, Term_False, 0, (boolean *) 0, + (int (*)(int, int, boolean, char *, char *)) 0, + (char *) 0, (const char *) 0, (const char *) 0, 0, 0, 0, TRUE } +}; +#undef NHOPT_PARSE + +#undef OPTIONS_C + +#define PILE_LIMIT_DFLT 5 +#define rolestring(val, array, field) \ + ((val >= 0) ? array[val].field : (val == ROLE_RANDOM) ? randomrole : none) enum window_option_types { MESSAGE_OPTION = 1, @@ -40,456 +80,212 @@ enum window_option_types { TEXT_OPTION }; -#define PILE_LIMIT_DFLT 5 - -static char empty_optstr[] = { '\0' }; - -/* - * NOTE: If you add (or delete) an option, please update the short - * options help (option_help()), the long options help (dat/opthelp), - * and the current options setting display function (doset()), - * and also the Guidebooks. - * - * The order matters. If an option is a an initial substring of another - * option (e.g. time and timed_delay) the shorter one must come first. - */ - -static struct Bool_Opt { - const char *name; - boolean *addr, initvalue; - int optflags; -} boolopt[] = { - { "acoustics", &flags.acoustics, TRUE, SET_IN_GAME }, -#if defined(SYSFLAGS) && defined(AMIGA) - /* Amiga altmeta causes Alt+key to be converted into Meta+key by - low level nethack code; on by default, can be toggled off if - Alt+key is needed for some ASCII chars on non-ASCII keyboard */ - { "altmeta", &sysflags.altmeta, TRUE, DISP_IN_GAME }, -#else -#ifdef ALTMETA - /* non-Amiga altmeta causes nethack's top level command loop to treat - two character sequence "ESC c" as M-c, for terminals or emulators - which send "ESC c" when Alt+c is pressed; off by default, enabling - this can potentially make trouble if user types ESC when nethack - is honoring this conversion request (primarily after starting a - count prefix prior to a command and then deciding to cancel it) */ - { "altmeta", &iflags.altmeta, FALSE, SET_IN_GAME }, -#else - { "altmeta", (boolean *) 0, TRUE, DISP_IN_GAME }, -#endif -#endif - { "ascii_map", &iflags.wc_ascii_map, !PREFER_TILED, SET_IN_GAME }, /*WC*/ -#if defined(SYSFLAGS) && defined(MFLOPPY) - { "asksavedisk", &sysflags.asksavedisk, FALSE, SET_IN_GAME }, -#else - { "asksavedisk", (boolean *) 0, FALSE, SET_IN_FILE }, -#endif - { "autodescribe", &iflags.autodescribe, TRUE, SET_IN_GAME }, - { "autodig", &flags.autodig, FALSE, SET_IN_GAME }, - { "autoopen", &flags.autoopen, TRUE, SET_IN_GAME }, - { "autopickup", &flags.pickup, TRUE, SET_IN_GAME }, - { "autoquiver", &flags.autoquiver, FALSE, SET_IN_GAME }, -#if defined(MICRO) && !defined(AMIGA) - { "BIOS", &iflags.BIOS, FALSE, SET_IN_FILE }, -#else - { "BIOS", (boolean *) 0, FALSE, SET_IN_FILE }, -#endif - { "blind", &u.uroleplay.blind, FALSE, DISP_IN_GAME }, - { "bones", &flags.bones, TRUE, SET_IN_FILE }, -#ifdef INSURANCE - { "checkpoint", &flags.ins_chkpt, TRUE, SET_IN_GAME }, -#else - { "checkpoint", (boolean *) 0, FALSE, SET_IN_FILE }, -#endif -#ifdef MFLOPPY - { "checkspace", &iflags.checkspace, TRUE, SET_IN_GAME }, -#else - { "checkspace", (boolean *) 0, FALSE, SET_IN_FILE }, -#endif - { "clicklook", &iflags.clicklook, FALSE, SET_IN_GAME }, - { "cmdassist", &iflags.cmdassist, TRUE, SET_IN_GAME }, -#if defined(MICRO) || defined(WIN32) || defined(CURSES_GRAPHICS) - { "color", &iflags.wc_color, TRUE, SET_IN_GAME }, /* on/off: use WC or not */ -#else /* systems that support multiple terminals, many monochrome */ - { "color", &iflags.wc_color, FALSE, SET_IN_GAME }, -#endif - { "confirm", &flags.confirm, TRUE, SET_IN_GAME }, - { "dark_room", &flags.dark_room, TRUE, SET_IN_GAME }, - { "eight_bit_tty", &iflags.wc_eight_bit_input, FALSE, SET_IN_GAME }, /*WC*/ -#if defined(TTY_GRAPHICS) || defined(CURSES_GRAPHICS) || defined(X11_GRAPHICS) - { "extmenu", &iflags.extmenu, FALSE, SET_IN_GAME }, -#else - { "extmenu", (boolean *) 0, FALSE, SET_IN_FILE }, -#endif -#ifdef OPT_DISPMAP - { "fast_map", &flags.fast_map, TRUE, SET_IN_GAME }, -#else - { "fast_map", (boolean *) 0, TRUE, SET_IN_FILE }, -#endif - { "female", &flags.female, FALSE, DISP_IN_GAME }, - { "fixinv", &flags.invlet_constant, TRUE, SET_IN_GAME }, -#if defined(SYSFLAGS) && defined(AMIFLUSH) - { "flush", &sysflags.amiflush, FALSE, SET_IN_GAME }, -#else - { "flush", (boolean *) 0, FALSE, SET_IN_FILE }, -#endif - { "force_invmenu", &iflags.force_invmenu, FALSE, SET_IN_GAME }, - { "fullscreen", &iflags.wc2_fullscreen, FALSE, SET_IN_FILE }, /*WC2*/ - { "goldX", &iflags.goldX, FALSE, SET_IN_GAME }, - { "guicolor", &iflags.wc2_guicolor, TRUE, SET_IN_GAME}, /*WC2*/ - { "help", &flags.help, TRUE, SET_IN_GAME }, - { "herecmd_menu", &iflags.herecmd_menu, FALSE, SET_IN_GAME }, - { "hilite_pet", &iflags.wc_hilite_pet, FALSE, SET_IN_GAME }, /*WC*/ - { "hilite_pile", &iflags.hilite_pile, FALSE, SET_IN_GAME }, - { "hitpointbar", &iflags.wc2_hitpointbar, FALSE, SET_IN_GAME }, /*WC2*/ -#ifndef MAC - { "ignintr", &flags.ignintr, FALSE, SET_IN_GAME }, -#else - { "ignintr", (boolean *) 0, FALSE, SET_IN_FILE }, -#endif - { "implicit_uncursed", &iflags.implicit_uncursed, TRUE, SET_IN_GAME }, - { "large_font", &iflags.obsolete, FALSE, SET_IN_FILE }, /* OBSOLETE */ - { "legacy", &flags.legacy, TRUE, DISP_IN_GAME }, - { "lit_corridor", &flags.lit_corridor, FALSE, SET_IN_GAME }, - { "lootabc", &flags.lootabc, FALSE, SET_IN_GAME }, -#ifdef MAIL - { "mail", &flags.biff, TRUE, SET_IN_GAME }, -#else - { "mail", (boolean *) 0, TRUE, SET_IN_FILE }, -#endif - { "mention_walls", &iflags.mention_walls, FALSE, SET_IN_GAME }, - { "menucolors", &iflags.use_menu_color, FALSE, SET_IN_GAME }, - /* for menu debugging only*/ - { "menu_tab_sep", &iflags.menu_tab_sep, FALSE, SET_IN_WIZGAME }, - { "menu_objsyms", &iflags.menu_head_objsym, FALSE, SET_IN_GAME }, -#ifdef TTY_GRAPHICS - { "menu_overlay", &iflags.menu_overlay, TRUE, SET_IN_GAME }, -#else - { "menu_overlay", (boolean *) 0, FALSE, SET_IN_FILE }, -#endif - { "monpolycontrol", &iflags.mon_polycontrol, FALSE, SET_IN_WIZGAME }, -#ifdef NEWS - { "news", &iflags.news, TRUE, DISP_IN_GAME }, -#else - { "news", (boolean *) 0, FALSE, SET_IN_FILE }, -#endif - { "nudist", &u.uroleplay.nudist, FALSE, DISP_IN_GAME }, - { "null", &flags.null, TRUE, SET_IN_GAME }, -#if defined(SYSFLAGS) && defined(MAC) - { "page_wait", &sysflags.page_wait, TRUE, SET_IN_GAME }, -#else - { "page_wait", (boolean *) 0, FALSE, SET_IN_FILE }, -#endif - /* moved perm_invent from flags to iflags and out of save file in 3.6.2 */ - { "perm_invent", &iflags.perm_invent, FALSE, SET_IN_GAME }, - { "pickup_thrown", &flags.pickup_thrown, TRUE, SET_IN_GAME }, - { "popup_dialog", &iflags.wc_popup_dialog, FALSE, SET_IN_GAME }, /*WC*/ - { "preload_tiles", &iflags.wc_preload_tiles, TRUE, DISP_IN_GAME }, /*WC*/ - { "pushweapon", &flags.pushweapon, FALSE, SET_IN_GAME }, -#if defined(MICRO) && !defined(AMIGA) - { "rawio", &iflags.rawio, FALSE, DISP_IN_GAME }, -#else - { "rawio", (boolean *) 0, FALSE, SET_IN_FILE }, -#endif - { "rest_on_space", &flags.rest_on_space, FALSE, SET_IN_GAME }, -#ifdef RLECOMP - { "rlecomp", &iflags.rlecomp, -#if defined(COMPRESS) || defined(ZLIB_COMP) - FALSE, -#else - TRUE, -#endif - DISP_IN_GAME }, -#endif - { "safe_pet", &flags.safe_dog, TRUE, SET_IN_GAME }, - { "sanity_check", &iflags.sanity_check, FALSE, SET_IN_WIZGAME }, - { "selectsaved", &iflags.wc2_selectsaved, TRUE, DISP_IN_GAME }, /*WC*/ - { "showexp", &flags.showexp, FALSE, SET_IN_GAME }, - { "showrace", &flags.showrace, FALSE, SET_IN_GAME }, -#ifdef SCORE_ON_BOTL - { "showscore", &flags.showscore, FALSE, SET_IN_GAME }, -#else - { "showscore", (boolean *) 0, FALSE, SET_IN_FILE }, -#endif - { "silent", &flags.silent, TRUE, SET_IN_GAME }, - { "softkeyboard", &iflags.wc2_softkeyboard, FALSE, SET_IN_FILE }, /*WC2*/ - { "sortpack", &flags.sortpack, TRUE, SET_IN_GAME }, - { "sparkle", &flags.sparkle, TRUE, SET_IN_GAME }, - { "splash_screen", &iflags.wc_splash_screen, TRUE, DISP_IN_GAME }, /*WC*/ - { "standout", &flags.standout, FALSE, SET_IN_GAME }, - { "status_updates", &iflags.status_updates, TRUE, DISP_IN_GAME }, - { "tiled_map", &iflags.wc_tiled_map, PREFER_TILED, DISP_IN_GAME }, /*WC*/ - { "time", &flags.time, FALSE, SET_IN_GAME }, -#ifdef TIMED_DELAY - { "timed_delay", &flags.nap, TRUE, SET_IN_GAME }, -#else - { "timed_delay", (boolean *) 0, FALSE, SET_IN_GAME }, -#endif - { "tombstone", &flags.tombstone, TRUE, SET_IN_GAME }, - { "toptenwin", &iflags.toptenwin, FALSE, SET_IN_GAME }, - { "travel", &flags.travelcmd, TRUE, SET_IN_GAME }, -#ifdef DEBUG - { "travel_debug", &iflags.trav_debug, FALSE, SET_IN_WIZGAME }, /*hack.c*/ -#endif - { "use_darkgray", &iflags.wc2_darkgray, TRUE, SET_IN_FILE }, /*WC2*/ -#ifdef WIN32 - { "use_inverse", &iflags.wc_inverse, TRUE, SET_IN_GAME }, /*WC*/ -#else - { "use_inverse", &iflags.wc_inverse, FALSE, SET_IN_GAME }, /*WC*/ -#endif - { "verbose", &flags.verbose, TRUE, SET_IN_GAME }, -#ifdef TTY_TILES_ESCCODES - { "vt_tiledata", &iflags.vt_tiledata, FALSE, SET_IN_FILE }, -#else - { "vt_tiledata", (boolean *) 0, FALSE, SET_IN_FILE }, -#endif - { "whatis_menu", &iflags.getloc_usemenu, FALSE, SET_IN_GAME }, - { "whatis_moveskip", &iflags.getloc_moveskip, FALSE, SET_IN_GAME }, - { "wizweight", &iflags.wizweight, FALSE, SET_IN_WIZGAME }, - { "wraptext", &iflags.wc2_wraptext, FALSE, SET_IN_GAME }, /*WC2*/ -#ifdef ZEROCOMP - { "zerocomp", &iflags.zerocomp, -#if defined(COMPRESS) || defined(ZLIB_COMP) - FALSE, -#else - TRUE, -#endif - DISP_IN_GAME }, -#endif - { (char *) 0, (boolean *) 0, FALSE, 0 } +enum optn_result { + optn_silenterr = -1, optn_err = 0, optn_ok }; - -/* compound options, for option_help() and external programs like Amiga - * frontend */ -static struct Comp_Opt { - const char *name, *descr; - int size; /* for frontends and such allocating space -- - * usually allowed size of data in game, but - * occasionally maximum reasonable size for - * typing when game maintains information in - * a different format */ - int optflags; -} compopt[] = { - { "align", "your starting alignment (lawful, neutral, or chaotic)", 8, - DISP_IN_GAME }, - { "align_message", "message window alignment", 20, DISP_IN_GAME }, /*WC*/ - { "align_status", "status window alignment", 20, DISP_IN_GAME }, /*WC*/ - { "altkeyhandler", "alternate key handler", 20, SET_IN_GAME }, -#ifdef BACKWARD_COMPAT - { "boulder", "deprecated (use S_boulder in sym file instead)", 1, - SET_IN_GAME }, -#endif - { "catname", "the name of your (first) cat (e.g., catname:Tabby)", - PL_PSIZ, DISP_IN_GAME }, - { "disclose", "the kinds of information to disclose at end of game", - sizeof flags.end_disclose * 2, SET_IN_GAME }, - { "dogname", "the name of your (first) dog (e.g., dogname:Fang)", PL_PSIZ, - DISP_IN_GAME }, - { "dungeon", "the symbols to use in drawing the dungeon map", - MAXDCHARS + 1, SET_IN_FILE }, - { "effects", "the symbols to use in drawing special effects", - MAXECHARS + 1, SET_IN_FILE }, - { "font_map", "the font to use in the map window", 40, - DISP_IN_GAME }, /*WC*/ - { "font_menu", "the font to use in menus", 40, DISP_IN_GAME }, /*WC*/ - { "font_message", "the font to use in the message window", 40, - DISP_IN_GAME }, /*WC*/ - { "font_size_map", "the size of the map font", 20, DISP_IN_GAME }, /*WC*/ - { "font_size_menu", "the size of the menu font", 20, - DISP_IN_GAME }, /*WC*/ - { "font_size_message", "the size of the message font", 20, - DISP_IN_GAME }, /*WC*/ - { "font_size_status", "the size of the status font", 20, - DISP_IN_GAME }, /*WC*/ - { "font_size_text", "the size of the text font", 20, - DISP_IN_GAME }, /*WC*/ - { "font_status", "the font to use in status window", 40, - DISP_IN_GAME }, /*WC*/ - { "font_text", "the font to use in text windows", 40, - DISP_IN_GAME }, /*WC*/ - { "fruit", "the name of a fruit you enjoy eating", PL_FSIZ, SET_IN_GAME }, - { "gender", "your starting gender (male or female)", 8, DISP_IN_GAME }, - { "horsename", "the name of your (first) horse (e.g., horsename:Silver)", - PL_PSIZ, DISP_IN_GAME }, - { "map_mode", "map display mode under Windows", 20, DISP_IN_GAME }, /*WC*/ - { "menustyle", "user interface for object selection", MENUTYPELEN, - SET_IN_GAME }, - { "menu_deselect_all", "deselect all items in a menu", 4, SET_IN_FILE }, - { "menu_deselect_page", "deselect all items on this page of a menu", 4, - SET_IN_FILE }, - { "menu_first_page", "jump to the first page in a menu", 4, SET_IN_FILE }, - { "menu_headings", "text attribute for menu headings", 9, SET_IN_GAME }, - { "menu_invert_all", "invert all items in a menu", 4, SET_IN_FILE }, - { "menu_invert_page", "invert all items on this page of a menu", 4, - SET_IN_FILE }, - { "menu_last_page", "jump to the last page in a menu", 4, SET_IN_FILE }, - { "menu_next_page", "goto the next menu page", 4, SET_IN_FILE }, - { "menu_previous_page", "goto the previous menu page", 4, SET_IN_FILE }, - { "menu_search", "search for a menu item", 4, SET_IN_FILE }, - { "menu_select_all", "select all items in a menu", 4, SET_IN_FILE }, - { "menu_select_page", "select all items on this page of a menu", 4, - SET_IN_FILE }, - { "monsters", "the symbols to use for monsters", MAXMCLASSES, - SET_IN_FILE }, - { "msghistory", "number of top line messages to save", 5, DISP_IN_GAME }, -#if defined(TTY_GRAPHICS) || defined(CURSES_GRAPHICS) - { "msg_window", "the type of message window required", 1, SET_IN_GAME }, -#else - { "msg_window", "the type of message window required", 1, SET_IN_FILE }, -#endif - { "name", "your character's name (e.g., name:Merlin-W)", PL_NSIZ, - DISP_IN_GAME }, - { "mouse_support", "game receives click info from mouse", 0, SET_IN_GAME }, - { "number_pad", "use the number pad for movement", 1, SET_IN_GAME }, - { "objects", "the symbols to use for objects", MAXOCLASSES, SET_IN_FILE }, - { "packorder", "the inventory order of the items in your pack", - MAXOCLASSES, SET_IN_GAME }, -#ifdef CHANGE_COLOR - { "palette", -#ifndef WIN32 - "palette (00c/880/-fff is blue/yellow/reverse white)", 15, SET_IN_GAME -#else - "palette (adjust an RGB color in palette (color-R-G-B)", 15, SET_IN_FILE -#endif - }, -#if defined(MAC) - { "hicolor", "same as palette, only order is reversed", 15, SET_IN_FILE }, -#endif -#endif - { "paranoid_confirmation", "extra prompting in certain situations", 28, - SET_IN_GAME }, - { "petattr", "attributes for highlighting pets", 88, SET_IN_GAME }, - { "pettype", "your preferred initial pet type", 4, DISP_IN_GAME }, - { "pickup_burden", "maximum burden picked up before prompt", 20, - SET_IN_GAME }, - { "pickup_types", "types of objects to pick up automatically", - MAXOCLASSES, SET_IN_GAME }, - { "pile_limit", "threshold for \"there are many objects here\"", 24, - SET_IN_GAME }, - { "playmode", "normal play, non-scoring explore mode, or debug mode", 8, - DISP_IN_GAME }, - { "player_selection", "choose character via dialog or prompts", 12, - DISP_IN_GAME }, - { "race", "your starting race (e.g., Human, Elf)", PL_CSIZ, - DISP_IN_GAME }, - { "role", "your starting role (e.g., Barbarian, Valkyrie)", PL_CSIZ, - DISP_IN_GAME }, - { "runmode", "display frequency when `running' or `travelling'", - sizeof "teleport", SET_IN_GAME }, - { "scores", "the parts of the score list you wish to see", 32, - SET_IN_GAME }, - { "scroll_amount", "amount to scroll map when scroll_margin is reached", - 20, DISP_IN_GAME }, /*WC*/ - { "scroll_margin", "scroll map when this far from the edge", 20, - DISP_IN_GAME }, /*WC*/ - { "sortloot", "sort object selection lists by description", 4, - SET_IN_GAME }, -#ifdef MSDOS - { "soundcard", "type of sound card to use", 20, SET_IN_FILE }, -#endif - { "statushilites", -#ifdef STATUS_HILITES - "0=no status highlighting, N=show highlights for N turns", - 20, SET_IN_GAME -#else - "highlight control", 20, SET_IN_FILE -#endif - }, - { "statuslines", -#ifdef CURSES_GRAPHICS - "2 or 3 lines for horizontal (bottom or top) status display", - 20, SET_IN_GAME -#else - "2 or 3 lines for status display", - 20, SET_IN_FILE -#endif - }, /*WC2*/ - { "symset", "load a set of display symbols from the symbols file", 70, - SET_IN_GAME }, - { "roguesymset", - "load a set of rogue display symbols from the symbols file", 70, - SET_IN_GAME }, -#ifdef WIN32 - { "subkeyvalue", "override keystroke value", 7, SET_IN_FILE }, -#endif - { "suppress_alert", "suppress alerts about version-specific features", 8, - SET_IN_GAME }, - /* term_cols,term_rows -> WC2_TERM_SIZE (6: room to format 1..32767) */ - { "term_cols", "number of columns", 6, SET_IN_FILE }, /*WC2*/ - { "term_rows", "number of rows", 6, SET_IN_FILE }, /*WC2*/ - { "tile_width", "width of tiles", 20, DISP_IN_GAME }, /*WC*/ - { "tile_height", "height of tiles", 20, DISP_IN_GAME }, /*WC*/ - { "tile_file", "name of tile file", 70, DISP_IN_GAME }, /*WC*/ - { "traps", "the symbols to use in drawing traps", MAXTCHARS + 1, - SET_IN_FILE }, - { "vary_msgcount", "show more old messages at a time", 20, - DISP_IN_GAME }, /*WC*/ -#ifdef MSDOS - { "video", "method of video updating", 20, SET_IN_FILE }, -#endif -#ifdef VIDEOSHADES - { "videocolors", "color mappings for internal screen routines", 40, - DISP_IN_GAME }, - { "videoshades", "gray shades to map to black/gray/white", 32, - DISP_IN_GAME }, -#endif - { "whatis_coord", "show coordinates when auto-describing cursor position", - 1, SET_IN_GAME }, - { "whatis_filter", - "filter coordinate locations when targeting next or previous", - 1, SET_IN_GAME }, - { "windowborders", "0 (off), 1 (on), 2 (auto)", 9, SET_IN_GAME }, /*WC2*/ - { "windowcolors", "the foreground/background colors of windows", /*WC*/ - 80, DISP_IN_GAME }, - { "windowtype", "windowing system to use", WINTYPELEN, DISP_IN_GAME }, -#ifdef WINCHAIN - { "windowchain", "window processor to use", WINTYPELEN, SET_IN_SYS }, -#endif -#ifdef BACKWARD_COMPAT - { "DECgraphics", "load DECGraphics display symbols", 70, SET_IN_FILE }, - { "IBMgraphics", "load IBMGraphics display symbols", 70, SET_IN_FILE }, -#ifdef CURSES_GRAPHICS - { "cursesgraphics", "load curses display symbols", 70, SET_IN_FILE }, -#endif -#ifdef MAC_GRAPHICS_ENV - { "Macgraphics", "load MACGraphics display symbols", 70, SET_IN_FILE }, -#endif -#endif - { (char *) 0, (char *) 0, 0, 0 } +enum requests { + do_nothing, do_init, do_set, do_handler, get_val, get_cnf_val }; -#ifdef OPTION_LISTS_ONLY -#undef static +static struct allopt_t allopt[SIZE(allopt_init)]; -#else /* use rest of file */ +#ifndef OPTION_LISTS_ONLY -extern char configfile[]; /* for messages */ +/* use rest of file */ -extern struct symparse loadsyms[]; -static boolean need_redraw; /* for doset() */ - -#if defined(TOS) && defined(TEXTCOLOR) +/* extern char configfile[]; */ /* for messages; files.c */ +extern const struct symparse loadsyms[]; +#if defined(TOS) extern boolean colors_changed; /* in tos.c */ #endif - #ifdef VIDEOSHADES extern char *shade[3]; /* in sys/msdos/video.c */ extern char ttycolors[CLR_MAX]; /* in sys/msdos/video.c */ #endif -static char def_inv_order[MAXOCLASSES] = { +static char empty_optstr[] = { '\0' }; +static boolean duplicate, using_alias; +static boolean give_opt_msg = TRUE; + +enum { MAX_ROLEOPT = 4 }; /* 4: role,race,gend,algn */ +static boolean opt_set_in_config[OPTCOUNT]; +static char *roleoptvals[MAX_ROLEOPT][num_opt_phases]; + +static NEARDATA const char *OptS_type[OptS_Advanced+1] = { + "General", "Behavior", "Map", "Status", "Advanced" +}; + +static const char def_inv_order[MAXOCLASSES] = { COIN_CLASS, AMULET_CLASS, WEAPON_CLASS, ARMOR_CLASS, FOOD_CLASS, SCROLL_CLASS, SPBOOK_CLASS, POTION_CLASS, RING_CLASS, WAND_CLASS, TOOL_CLASS, GEM_CLASS, ROCK_CLASS, BALL_CLASS, CHAIN_CLASS, 0, }; +static const char none[] = "(none)", randomrole[] = "random", + to_be_done[] = "(to be done)", + defopt[] = "default", defbrief[] = "def"; + +/* paranoia[] - used by parseoptions() and handler_paranoid_confirmation() */ +static const struct paranoia_opts { + int flagmask; /* which paranoid option */ + const char *argname; /* primary name */ + int argMinLen; /* minimum number of letters to match */ + const char *synonym; /* alternate name (optional) */ + int synMinLen; + const char *explain; /* for interactive menu */ +} paranoia[] = { + /* there are some initial-letter conflicts: "a"ttack vs "A"utoall vs + "a"ll, "attack" takes precedence and "all" isn't present in the + interactive menu with "Autoall" capitalized there, + and "d"ie vs "d"eath, synonyms for each other so doesn't matter; + (also "p"ray vs "P"aranoia, "pray" takes precedence since "Paranoia" + is just a synonym for "Confirm"); "b"ones vs "br"eak-wand, the + latter requires at least two letters; "e"at vs "ex"plore, + "cont"inue eating vs "C"onfirm; "wand"-break vs "Were"-change, + both require at least two letters during config processing but use + one letter with case-sensitivity for 'm O's interactive menu; + if any entry or alias beginning with 'n' gets added, aside from "none", + the parsing to accept "nofoo" to mean "!foo" will need fixing */ + { PARANOID_CONFIRM, "Confirm", 1, "Paranoia", 2, + "for \"yes\" confirmations, require \"no\" to reject" }, + { PARANOID_QUIT, "quit", 1, "explore", 2, + "yes vs y to quit or to enter explore mode" }, + { PARANOID_DIE, "die", 1, "death", 2, + "yes vs y to die (explore mode or debug mode)" }, + { PARANOID_BONES, "bones", 1, 0, 0, + "yes vs y to save bones data when dying in debug mode" }, + { PARANOID_HIT, "attack", 1, "hit", 1, + "yes vs y to attack a peaceful monster" }, + { PARANOID_BREAKWAND, "wand-break", 2, "break-wand", 2, + "yes vs y to break a wand via (a)pply" }, + { PARANOID_EATING, "eat", 1, "continue", 4, + "yes vs y to continue eating after first bite when satiated" }, + { PARANOID_WERECHANGE, "Were-change", 2, (const char *) 0, 0, + "yes vs y to change form when lycanthropy is controllable" }, + /* extra y/n questions rather than changing y/n to yes/n[o]; + they switch to yes/no if paranoid:confirm is also set */ + { PARANOID_PRAY, "pray", 1, 0, 0, + "y required to pray (supersedes old \"prayconfirm\" option)" }, + { PARANOID_TRAP, "trap", 1, "move-trap", 1, + "y required to enter known trap unless considered harmless" }, + { PARANOID_AUTOALL, "Autoall", 2, "autoselect-all", 2, + "y required to pick filter choice 'A' for menustyle:Full" }, + /* not a yes/n[o] vs y/n change nor a y/n addition */ + { PARANOID_SWIM, "swim", 1, 0, 0, + "'m' prefix necessary to deliberately walk into lava or water" }, + { PARANOID_REMOVE, "Remove", 1, "Takeoff", 1, + /* normally when there is only 1 candidate it's chosen automatically */ + "always pick from inventory for Remove and Takeoff" }, + /* for config file parsing; interactive menu skips these */ + { 0, "none", 4, 0, 0, 0 }, /* require full word match */ + { ~0, "all", 3, 0, 0, 0 }, /* ditto */ +}; + +static NEARDATA const char *menutype[][3] = { /* 'menustyle' settings */ + { "traditional", "[prompt for object class(es), then", + " ask y/n for each item in those classes]" }, + { "combination", "[prompt for object class(es), then", + " use menu for items in those classes]" }, + { "full", "[use menu to choose class(es), then", + " use another menu for items in those]" }, + { "partial", "[skip class filtering; always", + " use menu of all available items]" } +}; +#if PREV_MSGS /* tty supports all four settings, curses just final two */ +static NEARDATA const char *msgwind[][3] = { /* 'msg_window' settings */ + { "single", "[show one old message at a time,", + " most recent first]" }, + { "combination", "[for consecutive ^P requests, use", + " 'single' for first two, then 'full']" }, + { "full", "[show all available messages,", + " oldest first and most recent last]" }, + { "reversed", "[show all available messages,", + " most recent first]" } +}; +#endif +/* autounlock settings */ +static NEARDATA const char *unlocktypes[][2] = { + { "untrap", "(might fail)" }, + { "apply-key", "" }, + { "kick", "(doors only)" }, + { "force", "(chests/boxes only)" }, +}; +static NEARDATA const char *burdentype[] = { + "unencumbered", "burdened", "stressed", + "strained", "overtaxed", "overloaded" +}; +static NEARDATA const char *runmodes[] = { + "teleport", "run", "walk", "crawl" +}; +static NEARDATA const char *sortltype[] = { + "none", "loot", "full" +}; +/* second column is an alias for the first; third is brief explanation; + entries 5 and 6 are 1|4 and 2|4 (tty only) */ +static NEARDATA const char *perminv_modes[][3] = { + /*0*/ { "none", "off", "no permanent inventory window" }, + /*1*/ { "all" , "on", "all inventory except for gold" }, + /*2*/ { "full", "gold", "full inventory including gold" }, + /*3*/ { NULL, NULL, NULL }, + /*4*/ { NULL, NULL, NULL }, +#ifdef TTY_PERM_INVENT + /*5*/ { "on+grid", "all+grid", "all except gold, plus unused letters" }, + /*6*/ { "gold+grid", "full+grid", "full inventory, plus unused letters" }, +#else + /*5*/ { NULL, NULL, NULL }, + /*6*/ { NULL, NULL, NULL }, +#endif + /*7*/ { NULL, NULL, NULL }, + /*8*/ { "in-use", "inuse-only", "subset: items currently in use" }, +}; + +struct objsymopt { + int num; + const char *nam; + const char *descr; +}; + +/* + * menuobjsyms: + * Inventory display for the various values of menuobjsyms. + * 4' and 5' represent !sortpack which lacks headers; they + * produce the same result. + * + * 0: 1: + * Weapons Weapons (')') + * a - 15 darts a - 15 darts + * Armor Armor ('[') + * b - Hawaiian shirt b - Hawaiian shirt + * 2: 3: + * Weapons Weapons (')') + * a ) 15 darts a ) 15 darts + * Armor Armor ('[') + * b [ Hawaiian shirt b [ Hawaiian shirt + * 4: 5: + * Weapons Weapons (')') + * a - 15 darts a - 15 darts + * Armor Armor ('[') + * b - Hawaiian shirt b - Hawaiian shirt + * 4': 5': + * a ) 15 darts a ) 15 darts + * b [ Hawaiian shirt b [ Hawaiian shirt + */ +static const struct objsymopt objsymvals[] = { + { 0, "none", "don't show object symbols in menus" }, + { 1, "headers", "show object symbols in menu header lines" }, + { 2, "entries", "show object symbols in individual menu entries" }, + { 3, "both", "show object symbols in headers and menu entries" }, + { 4, "conditional", "show objsyms in entries if no headers are shown" }, + { 5, "one-or-other", "show objsyms in header, in entries if no header" }, +}; + /* * Default menu manipulation command accelerators. These may _not_ be: * - * + a number - reserved for counts + * + a number or '#' - reserved for counts * + an upper or lower case US ASCII letter - used for accelerators * + ESC - reserved for escaping the menu - * + NULL, CR or LF - reserved for commiting the selection(s). NULL + * + NULL, CR or LF - reserved for committing the selection(s). NULL * is kind of odd, but the tty's xwaitforspace() will return it if * someone hits a . * + a default object class symbol - used for object class accelerators @@ -516,2068 +312,2458 @@ typedef struct { } menu_cmd_t; static const menu_cmd_t default_menu_cmd_info[] = { - { "menu_first_page", MENU_FIRST_PAGE, "Go to first page" }, - { "menu_last_page", MENU_LAST_PAGE, "Go to last page" }, - { "menu_next_page", MENU_NEXT_PAGE, "Go to next page" }, - { "menu_previous_page", MENU_PREVIOUS_PAGE, "Go to previous page" }, - { "menu_select_all", MENU_SELECT_ALL, "Select all items" }, - { "menu_deselect_all", MENU_UNSELECT_ALL, "Unselect all items" }, - { "menu_invert_all", MENU_INVERT_ALL, "Invert selection" }, - { "menu_select_page", MENU_SELECT_PAGE, "Select items in current page" }, - { "menu_deselect_page", MENU_UNSELECT_PAGE, - "Unselect items in current page" }, - { "menu_invert_page", MENU_INVERT_PAGE, "Invert current page selection" }, - { "menu_search", MENU_SEARCH, "Search and toggle matching items" }, + { "menu_next_page", MENU_NEXT_PAGE, "Go to next page" }, + { "menu_previous_page", MENU_PREVIOUS_PAGE, "Go to previous page" }, + { "menu_first_page", MENU_FIRST_PAGE, "Go to first page" }, + { "menu_last_page", MENU_LAST_PAGE, "Go to last page" }, + { "menu_select_all", MENU_SELECT_ALL, + "Select all items in entire menu" }, + { "menu_invert_all", MENU_INVERT_ALL, + "Invert selection for all items" }, + { "menu_deselect_all", MENU_UNSELECT_ALL, + "Unselect all items in entire menu" }, + { "menu_select_page", MENU_SELECT_PAGE, + "Select all items on current page" }, + { "menu_invert_page", MENU_INVERT_PAGE, + "Invert current page's selections" }, + { "menu_deselect_page", MENU_UNSELECT_PAGE, + "Unselect all items on current page" }, + { "menu_search", MENU_SEARCH, + "Search and invert matching items" }, + { "menu_shift_right", MENU_SHIFT_RIGHT, + "Pan current page to right (perm_invent only)" }, + { "menu_shift_left", MENU_SHIFT_LEFT, + "Pan current page to left (perm_invent only)" }, + { (char *) 0, '\0', (char *) 0 } }; -/* - * Allow the user to map incoming characters to various menu commands. - * The accelerator list must be a valid C string. - */ -#define MAX_MENU_MAPPED_CMDS 32 /* some number */ -char mapped_menu_cmds[MAX_MENU_MAPPED_CMDS + 1]; /* exported */ -static char mapped_menu_op[MAX_MENU_MAPPED_CMDS + 1]; -static short n_menu_mapped = 0; - -static boolean initial, from_file; - -STATIC_DCL void FDECL(nmcpy, (char *, const char *, int)); -STATIC_DCL void FDECL(escapes, (const char *, char *)); -STATIC_DCL void FDECL(rejectoption, (const char *)); -STATIC_DCL char *FDECL(string_for_opt, (char *, BOOLEAN_P)); -STATIC_DCL char *FDECL(string_for_env_opt, (const char *, char *, BOOLEAN_P)); -STATIC_DCL void FDECL(bad_negation, (const char *, BOOLEAN_P)); -STATIC_DCL int FDECL(change_inv_order, (char *)); -STATIC_DCL boolean FDECL(warning_opts, (char *, const char *)); -STATIC_DCL int FDECL(feature_alert_opts, (char *, const char *)); -STATIC_DCL boolean FDECL(duplicate_opt_detection, (const char *, int)); -STATIC_DCL void FDECL(complain_about_duplicate, (const char *, int)); - -STATIC_DCL const char *FDECL(attr2attrname, (int)); -STATIC_DCL const char * FDECL(msgtype2name, (int)); -STATIC_DCL int NDECL(query_msgtype); -STATIC_DCL boolean FDECL(msgtype_add, (int, char *)); -STATIC_DCL void FDECL(free_one_msgtype, (int)); -STATIC_DCL int NDECL(msgtype_count); -STATIC_DCL boolean FDECL(test_regex_pattern, (const char *, const char *)); -STATIC_DCL boolean FDECL(add_menu_coloring_parsed, (char *, int, int)); -STATIC_DCL void FDECL(free_one_menu_coloring, (int)); -STATIC_DCL int NDECL(count_menucolors); -STATIC_DCL boolean FDECL(parse_role_opts, (BOOLEAN_P, const char *, - char *, char **)); - -STATIC_DCL void FDECL(doset_add_menu, (winid, const char *, int)); -STATIC_DCL void FDECL(opts_add_others, (winid, const char *, int, - char *, int)); -STATIC_DCL int FDECL(handle_add_list_remove, (const char *, int)); -STATIC_DCL boolean FDECL(special_handling, (const char *, - BOOLEAN_P, BOOLEAN_P)); -STATIC_DCL const char *FDECL(get_compopt_value, (const char *, char *)); -STATIC_DCL void FDECL(remove_autopickup_exception, - (struct autopickup_exception *)); - -STATIC_DCL boolean FDECL(is_wc_option, (const char *)); -STATIC_DCL boolean FDECL(wc_supported, (const char *)); -STATIC_DCL boolean FDECL(is_wc2_option, (const char *)); -STATIC_DCL boolean FDECL(wc2_supported, (const char *)); -STATIC_DCL void FDECL(wc_set_font_name, (int, char *)); -STATIC_DCL int FDECL(wc_set_window_colors, (char *)); - -void -reglyph_darkroom() +static const char n_currently_set[] = "(%d currently set)"; + +staticfn void nmcpy(char *, const char *, int); +staticfn void escapes(const char *, char *); +staticfn void rejectoption(const char *); +staticfn char *string_for_opt(char *, boolean); +staticfn char *string_for_env_opt(const char *, char *, boolean); +staticfn void bad_negation(const char *, boolean); +staticfn void set_menuobjsyms_flags(int); +staticfn int change_inv_order(char *); +staticfn boolean warning_opts(char *, const char *); +staticfn int feature_alert_opts(char *, const char *); +staticfn boolean duplicate_opt_detection(int); +staticfn void complain_about_duplicate(int); +staticfn int length_without_val(const char *, int len); +staticfn void determine_ambiguities(void); +staticfn int check_misc_menu_command(char *, char *); +staticfn int opt2roleopt(int); +staticfn char *getoptstr(int, int); +staticfn void saveoptstr(int, const char *); +staticfn void unsaveoptstr(int, int); +staticfn int petname_optfn(int, int, boolean, char *, char *); +staticfn int shared_menu_optfn(int, int, boolean, char *, char *); +staticfn int spcfn_misc_menu_cmd(int, int, boolean, char *, char *); + +staticfn const char * msgtype2name(int); +staticfn int query_msgtype(void); +staticfn boolean msgtype_add(int, char *); +staticfn void free_one_msgtype(int); +staticfn int msgtype_count(void); +staticfn boolean test_regex_pattern(const char *, const char *); +staticfn boolean parse_role_opt(int, boolean, const char *, char *, char **); +staticfn char *get_cnf_role_opt(int); +staticfn unsigned int longest_option_name(int, int); +staticfn int doset_simple_menu(void); +staticfn void reset_needed_visuals(void); +staticfn void doset_add_menu(winid, const char *, const char *, int, int); +staticfn int handle_add_list_remove(const char *, int); +staticfn void all_options_conds(strbuf_t *); +staticfn void all_options_menucolors(strbuf_t *); +staticfn void all_options_msgtypes(strbuf_t *); +staticfn void all_options_apes(strbuf_t *); +#ifdef CHANGE_COLOR +staticfn void all_options_palette(strbuf_t *); +#endif +staticfn void remove_autopickup_exception(struct autopickup_exception *); +staticfn int count_apes(void); +staticfn int count_cond(void); +staticfn void enhance_menu_text(char *, size_t, int, boolean *, + struct allopt_t *); +staticfn boolean can_set_perm_invent(void); +staticfn int handler_align_misc(int); +staticfn int handler_autounlock(int); +staticfn int handler_disclose(void); +staticfn int handler_menu_headings(void); +staticfn int handler_menu_objsyms(void); +staticfn int handler_menustyle(void); +staticfn int handler_msg_window(void); +staticfn int handler_number_pad(void); +staticfn int handler_paranoid_confirmation(void); +staticfn int handler_perminv_mode(void); +staticfn int handler_pickup_burden(void); +staticfn int handler_pickup_types(void); +staticfn int handler_runmode(void); +staticfn int handler_petattr(void); +staticfn int handler_sortloot(void); +staticfn int handler_symset(int); +staticfn int handler_versinfo(void); +staticfn int handler_whatis_coord(void); +staticfn int handler_whatis_filter(void); +/* next few are not allopt[] entries, so will only be called + directly from doset, not from individual optfn's */ +staticfn int handler_autopickup_exception(void); +staticfn int handler_menu_colors(void); +staticfn int handler_msgtype(void); +staticfn int handler_windowborders(void); + +staticfn boolean is_wc_option(const char *); +staticfn boolean wc_supported(const char *); +staticfn boolean is_wc2_option(const char *); +staticfn boolean wc2_supported(const char *); +staticfn void wc_set_font_name(int, char *); +staticfn int wc_set_window_colors(char *); +staticfn boolean illegal_menu_cmd_key(uchar); +staticfn const char *term_for_boolean(int, boolean *); + +/* ask user if they want a tutorial, except if tutorial boolean option has + been set in config - either on or off - in which case just obey that + setting without asking */ +boolean +ask_do_tutorial(void) { - xchar x, y; - - for (x = 0; x < COLNO; x++) - for (y = 0; y < ROWNO; y++) { - struct rm *lev = &levl[x][y]; - - if (!flags.dark_room || !iflags.use_color - || Is_rogue_level(&u.uz)) { - if (lev->glyph == cmap_to_glyph(S_darkroom)) - lev->glyph = lev->waslit ? cmap_to_glyph(S_room) - : cmap_to_glyph(S_stone); - } else { - if (lev->glyph == cmap_to_glyph(S_room) && lev->seenv - && lev->waslit && !cansee(x, y)) - lev->glyph = cmap_to_glyph(S_darkroom); - else if (lev->glyph == cmap_to_glyph(S_stone) - && lev->typ == ROOM && lev->seenv && !cansee(x, y)) - lev->glyph = cmap_to_glyph(S_darkroom); - } + boolean dotut = flags.tutorial; + + if (!opt_set_in_config[opt_tutorial]) { + winid win; + menu_item *sel; + anything any; + char buf[BUFSZ]; + const char *rc; + boolean norc; + int n, pass = 0; + + rc = nh_basename(get_configfile(), TRUE); + norc = !strcmp(get_configfile(), "/dev/null"); + Snprintf(buf, sizeof buf, + "Put \"OPTIONS=!tutorial\" in %s to skip this query.", + (rc && *rc && !norc) ? rc : "your configuration file"); + do { + win = create_nhwindow(NHW_MENU); + start_menu(win, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + any.a_char = 'y'; + add_menu(win, &nul_glyphinfo, &any, any.a_char, 0, + ATR_NONE, NO_COLOR, + "Yes, do a tutorial", MENU_ITEMFLAGS_NONE); + any.a_char = 'n'; + add_menu(win, &nul_glyphinfo, &any, any.a_char, 0, + ATR_NONE, NO_COLOR, + "No, just start play", MENU_ITEMFLAGS_NONE); + + add_menu_str(win, ""); + add_menu_str(win, buf); + if (pass++) /* we'll get here after or */ + add_menu_str(win, "(Please choose 'y' or 'n'.)"); + + end_menu(win, "Do you want a tutorial?"); + + n = select_menu(win, PICK_ONE, &sel); + destroy_nhwindow(win); + } while (!n); + if (n > 0) { + dotut = (sel[0].item.a_char == 'y'); + free((genericptr_t) sel); + } else { /* ESC */ + dotut = FALSE; } - if (flags.dark_room && iflags.use_color) - showsyms[S_darkroom] = showsyms[S_room]; - else - showsyms[S_darkroom] = showsyms[S_stone]; + } + return dotut; } -/* check whether a user-supplied option string is a proper leading - substring of a particular option name; option string might have - a colon or equals sign and arbitrary value appended to it */ +/* + ********************************** + * + * parseoptions + * + ********************************** + */ boolean -match_optname(user_string, opt_name, min_length, val_allowed) -const char *user_string, *opt_name; -int min_length; -boolean val_allowed; +parseoptions( + char *opts, + boolean tinitial, + boolean tfrom_file) { - int len = (int) strlen(user_string); - - if (val_allowed) { - const char *p = index(user_string, ':'), - *q = index(user_string, '='); + char *op; + boolean negated, got_match = FALSE, pfx_match = FALSE; +#if 0 + boolean has_val = FALSE; +#endif + int i, matchidx = -1, optresult = optn_err, optlen, optlen_wo_val; + boolean retval = TRUE; - if (!p || (q && q < p)) - p = q; - if (p) { - /* 'user_string' hasn't necessarily been through mungspaces() - so might have tabs or consecutive spaces */ - while (p > user_string && isspace((uchar) *(p - 1))) - p--; - len = (int) (p - user_string); - } + duplicate = FALSE; + using_alias = FALSE; + go.opt_initial = tinitial; + go.opt_from_file = tfrom_file; + /* + * Process elements of comma-separated list in right to left order. + * When some options are set interactively--notably various compound + * options that issue a prompt for a value--they use parseoptions() + * to handle setting the new value. For those, 'tinitial' is False + * and if user tries to supply a comma-separated list, it will be + * treated as part of the current option, probably failing to parse. + */ + if (tinitial && (op = strchr(opts, ',')) != 0) { + *op++ = 0; + /* current element remains pending while the rest of the line gets + handled recursively; if the rest of line contains any commas, + then the process will recurse deeper as it is processed */ + if (!parseoptions(op, go.opt_initial, go.opt_from_file)) + retval = FALSE; + } + if (strlen(opts) > BUFSZ / 2) { + config_error_add("Option too long, max length is %i characters", + (BUFSZ / 2)); + return FALSE; } - return (boolean) (len >= min_length - && !strncmpi(opt_name, user_string, len)); -} - -/* Added for NLE. */ -extern char * nle_getenv(const char *); + /* strip leading and trailing white space */ + while (isspace((uchar) *opts)) + opts++; + op = eos(opts); + while (--op >= opts && isspace((uchar) *op)) + *op = '\0'; -/* most environment variables will eventually be printed in an error - * message if they don't work, and most error message paths go through - * BUFSZ buffers, which could be overflowed by a maliciously long - * environment variable. If a variable can legitimately be long, or - * if it's put in a smaller buffer, the responsible code will have to - * bounds-check itself. - */ -char * -nh_getenv(ev) -const char *ev; -{ - fprintf(stderr, "Warning: NetHack asked for env variable %s\n", ev); + if (!*opts) { + config_error_add("Empty statement"); + return FALSE; + } + negated = FALSE; + while ((*opts == '!') || !strncmpi(opts, "no", 2)) { + opts += (*opts == '!') ? 1 : (opts[2] != '-') ? 2 : 3; + negated = !negated; + } + optlen = (int) strlen(opts); + optlen_wo_val = length_without_val(opts, optlen); + if (optlen_wo_val < optlen) { +#if 0 + has_val = TRUE; +#endif + optlen = optlen_wo_val; +#if 0 + } else { + has_val = FALSE; +#endif + } - char *getev = getenv(ev); + for (i = 0; i < OPTCOUNT; ++i) { + got_match = FALSE; - if (getev && strlen(getev) <= (BUFSZ / 2)) - return getev; - else - return (char *) 0; -} + if (allopt[i].pfx) { + if (str_start_is(opts, allopt[i].name, TRUE)) { + matchidx = i; + got_match = pfx_match = TRUE; + } + } +#if 0 /* this prevents "boolopt:True" &c */ + if (!got_match) { + if (has_val && !allopt[i].valok) + continue; + } +#endif + /* + * During option initialization, the function + * determine_ambiguities() + * figured out exactly how many characters are required to + * unambiguously differentiate one option from all others, and it + * placed that number into each option's allopt[n].minmatch. + * + */ + if (!got_match && allopt[i].name) + got_match = match_optname(opts, allopt[i].name, + allopt[i].minmatch, TRUE); + if (got_match) { + if (!allopt[i].pfx && optlen < allopt[i].minmatch) { + config_error_add( + "Ambiguous option %s, %d characters are needed to differentiate", + opts, allopt[i].minmatch); + break; + } + matchidx = i; + break; + } + } -/* process options, possibly including SYSCF */ -void -initoptions() -{ - initoptions_init(); -#ifdef SYSCF -/* someday there may be other SYSCF alternatives besides text file */ -#ifdef SYSCF_FILE - /* If SYSCF_FILE is specified, it _must_ exist... */ - assure_syscf_file(); - config_error_init(TRUE, SYSCF_FILE, FALSE); + if (!got_match) { + /* spin through the aliases to see if there's a match in those. + Note that if multiple delimited aliases for the same option + becomes desirable in the future, this is where you'll need + to split a delimited allopt[i].alias field into each + individual alias */ - /* ... and _must_ parse correctly. */ - if (!read_config_file(SYSCF_FILE, SET_IN_SYS)) { - if (config_error_done() && !iflags.initoptions_noterminate) - nh_terminate(EXIT_FAILURE); + for (i = 0; i < OPTCOUNT; ++i) { + if (!allopt[i].alias) + continue; + got_match = match_optname(opts, allopt[i].alias, + (int) strlen(allopt[i].alias), + TRUE); + if (got_match) { + matchidx = i; + using_alias = TRUE; + break; + } + } } - config_error_done(); - /* - * TODO [maybe]: parse the sysopt entries which are space-separated - * lists of usernames into arrays with one name per element. - */ -#endif -#endif /* SYSCF */ - initoptions_finish(); -} -void -initoptions_init() -{ -#if (defined(UNIX) || defined(VMS)) && defined(TTY_GRAPHICS) - char *opts; -#endif - int i; + /* allow optfn's to test whether they were called from parseoptions() */ + program_state.in_parseoptions++; - /* set up the command parsing */ - reset_commands(TRUE); /* init */ + if (got_match && (matchidx >= 0 && matchidx < OPTCOUNT) + && !allopt[matchidx].disregarded) { + duplicate = duplicate_opt_detection(matchidx); + if (duplicate && !allopt[matchidx].dupeok) + complain_about_duplicate(matchidx); - /* initialize the random number generator(s) */ - init_random(rn2); - init_random(rn2_on_display_rng); + /* check for bad negation, so option functions don't have to */ + if (negated && !allopt[matchidx].negateok) { + bad_negation(allopt[matchidx].name, TRUE); + return optn_err; + } - /* for detection of configfile options specified multiple times */ - iflags.opt_booldup = iflags.opt_compdup = (int *) 0; + /* + * Now call the option's associated function via the function + * pointer for it in the allopt[] array, specifying a 'do_set' req. + */ + if (allopt[matchidx].optfn) { + op = string_for_opt(opts, TRUE); + optresult = (*allopt[matchidx].optfn)(allopt[matchidx].idx, + do_set, negated, opts, op); + if (optresult == optn_ok) + opt_set_in_config[matchidx] = TRUE; + } + } + + if (program_state.in_parseoptions > 0) + program_state.in_parseoptions--; + +#if 0 + /* This specialization shouldn't be needed any longer because each of + the individual options is part of the allopts[] list, thus already + taken care of in the for-loop above */ + if (!got_match) { + int res = check_misc_menu_command(opts, op); - for (i = 0; boolopt[i].name; i++) { - if (boolopt[i].addr) - *(boolopt[i].addr) = boolopt[i].initvalue; + if (res >= 0) + optresult = spcfn_misc_menu_cmd(res, do_set, negated, opts, op); + if (optresult == optn_ok) + got_match = TRUE; } -#if defined(COMPRESS) || defined(ZLIB_COMP) - set_savepref("externalcomp"); - set_restpref("externalcomp"); -#ifdef RLECOMP - set_savepref("!rlecomp"); - set_restpref("!rlecomp"); #endif -#else -#ifdef ZEROCOMP - set_savepref("zerocomp"); - set_restpref("zerocomp"); -#endif -#ifdef RLECOMP - set_savepref("rlecomp"); - set_restpref("rlecomp"); -#endif -#endif -#ifdef SYSFLAGS - Strcpy(sysflags.sysflagsid, "sysflags"); - sysflags.sysflagsid[9] = (char) sizeof (struct sysflag); -#endif - flags.end_own = FALSE; - flags.end_top = 3; - flags.end_around = 2; - flags.paranoia_bits = PARANOID_PRAY; /* old prayconfirm=TRUE */ - flags.pile_limit = PILE_LIMIT_DFLT; /* 5 */ - flags.runmode = RUN_LEAP; - iflags.msg_history = 20; - /* msg_window has conflicting defaults for multi-interface binary */ -#ifdef TTY_GRAPHICS - iflags.prevmsg_window = 's'; -#else -#ifdef CURSES_GRAPHICS - iflags.prevmsg_window = 'r'; -#endif -#endif - iflags.menu_headings = ATR_INVERSE; - iflags.getpos_coords = GPCOORDS_NONE; - /* hero's role, race, &c haven't been chosen yet */ - flags.initrole = flags.initrace = flags.initgend = flags.initalign - = ROLE_NONE; + if (!got_match) { + /* Is it a symbol? */ + if (strstr(opts, "S_") == opts && parsesymbols(opts, PRIMARYSET)) { + switch_symbols(TRUE); + check_gold_symbol(); + optresult = optn_ok; + } + } - init_ov_primary_symbols(); - init_ov_rogue_symbols(); - /* Set the default monster and object class symbols. */ - init_symbols(); - for (i = 0; i < WARNCOUNT; i++) - warnsyms[i] = def_warnsyms[i].sym; + if (optresult == optn_silenterr + || (got_match && allopt[matchidx].disregarded) + || (!got_match && config_unmatched_ignored())) + return FALSE; + if (pfx_match && optresult == optn_err) { + char pfxbuf[BUFSZ], *pfxp; - /* for "special achievement" tracking (see obj.h, - create_object(sp_lev.c), addinv_core1(invent.c) */ - iflags.mines_prize_type = LUCKSTONE; - iflags.soko_prize_type1 = BAG_OF_HOLDING; - iflags.soko_prize_type2 = AMULET_OF_REFLECTION; + Snprintf(pfxbuf, sizeof pfxbuf, "%s", opts); + if ((pfxp = strchr(pfxbuf, ':')) != 0) + *pfxp = '\0'; + config_error_add("bad option suffix variation '%s'", pfxbuf); + return FALSE; + } + if (got_match && optresult == optn_err) + return FALSE; + if (optresult == optn_ok) + return retval; - /* assert( sizeof flags.inv_order == sizeof def_inv_order ); */ - (void) memcpy((genericptr_t) flags.inv_order, - (genericptr_t) def_inv_order, sizeof flags.inv_order); - flags.pickup_types[0] = '\0'; - flags.pickup_burden = MOD_ENCUMBER; - flags.sortloot = 'l'; /* sort only loot by default */ + /* out of valid options */ + config_error_add("Unknown option '%s'", opts); + return FALSE; +} - for (i = 0; i < NUM_DISCLOSURE_OPTIONS; i++) - flags.end_disclose[i] = DISCLOSE_PROMPT_DEFAULT_NO; - switch_symbols(FALSE); /* set default characters */ - init_rogue_symbols(); -#if defined(UNIX) && defined(TTY_GRAPHICS) - /* - * Set defaults for some options depending on what we can - * detect about the environment's capabilities. - * This has to be done after the global initialization above - * and before reading user-specific initialization via - * config file/environment variable below. - */ - /* this detects the IBM-compatible console on most 386 boxes */ - if ((opts = nle_getenv("TERM")) && !strncmp(opts, "AT", 2)) { - if (!symset[PRIMARY].explicitly) - load_symset("IBMGraphics", PRIMARY); - if (!symset[ROGUESET].explicitly) - load_symset("RogueIBM", ROGUESET); - switch_symbols(TRUE); -#ifdef TEXTCOLOR - iflags.use_color = TRUE; -#endif +staticfn int +check_misc_menu_command(char *opts, char *op UNUSED) +{ + int i; + const char *name_to_check; + + /* check for menu command mapping */ + for (i = 0; default_menu_cmd_info[i].name; i++) { + name_to_check = default_menu_cmd_info[i].name; + if (match_optname(opts, name_to_check, + (int) strlen(name_to_check), TRUE)) + return i; } -#endif /* UNIX && TTY_GRAPHICS */ -#if defined(UNIX) || defined(VMS) -#ifdef TTY_GRAPHICS - /* detect whether a "vt" terminal can handle alternate charsets */ - if ((opts = nle_getenv("TERM")) - /* [could also check "xterm" which emulates vtXXX by default] */ - && !strncmpi(opts, "vt", 2) - && AS && AE && index(AS, '\016') && index(AE, '\017')) { - if (!symset[PRIMARY].explicitly) - load_symset("DECGraphics", PRIMARY); - switch_symbols(TRUE); + return -1; +} + +static int roleopt2opt[4] = { + opt_role, opt_race, opt_gender, opt_alignment +}; + +/* role => 0, race => 1, gender => 2, alignment =>3 */ +staticfn int +opt2roleopt(int roleopt) +{ + switch (roleopt) { + case opt_role: + return 0; + case opt_race: + return 1; + case opt_gender: + return 2; + case opt_alignment: + return 3; + default: + break; } -#endif -#endif /* UNIX || VMS */ + return 0; +} -#if defined(MSDOS) || defined(WIN32) - /* Use IBM defaults. Can be overridden via config file */ - if (!symset[PRIMARY].explicitly) - load_symset("IBMGraphics_2", PRIMARY); - if (!symset[ROGUESET].explicitly) - load_symset("RogueEpyx", ROGUESET); -#endif -#ifdef MAC_GRAPHICS_ENV - if (!symset[PRIMARY].explicitly) - load_symset("MACGraphics", PRIMARY); - switch_symbols(TRUE); -#endif /* MAC_GRAPHICS_ENV */ - flags.menu_style = MENU_FULL; +/* fetch saved option string for a particular option phase */ +staticfn char * +getoptstr(int optidx, int ophase) +{ + int roleoptindx = opt2roleopt(optidx); - iflags.wc_align_message = ALIGN_TOP; - iflags.wc_align_status = ALIGN_BOTTOM; - /* used by tty and curses */ - iflags.wc2_statuslines = 2; - /* only used by curses */ - iflags.wc2_windowborders = 2; /* 'Auto' */ + if (ophase == num_opt_phases) { /* any source */ + int phase; - /* since this is done before init_objects(), do partial init here */ - objects[SLIME_MOLD].oc_name_idx = SLIME_MOLD; - nmcpy(pl_fruit, OBJ_NAME(objects[SLIME_MOLD]), PL_FSIZ); + /* find non-Null, in order optvals[][play_opt], [cmdline_opt], + [environ_opt], [rc_file_opt], [syscf_opt], [builtin_opt] */ + for (phase = num_opt_phases - 1; phase >= 0; --phase) + if (roleoptvals[roleoptindx][phase]) { + ophase = phase; + break; + } + } + if ((roleoptindx >= 0 && roleoptindx < MAX_ROLEOPT + && ophase >= 0 && ophase < num_opt_phases)) + return roleoptvals[roleoptindx][ophase]; + panic("bad index roleoptvals[%d][%d]", roleoptindx, ophase); + /*NOTREACHED*/ +} + +/* to track some unparsed option settings in case #saveoptions needs them */ +staticfn void +saveoptstr(int optidx, const char *optstr) +{ + int phase = go.opt_phase, roleoptindx = opt2roleopt(optidx); + const char *p = strchr(optstr, ':'), *q = strchr(optstr, '='); + + /* strip away "optname:" from optname:optstr */ + if (!p || (q && q < p)) + p = q; + if (p) + optstr = p + 1; + + if (roleoptvals[roleoptindx][phase]) + free((genericptr_t) roleoptvals[roleoptindx][phase]); + roleoptvals[roleoptindx][phase] = dupstr(optstr); +} + +/* discard specific saved option string */ +staticfn void +unsaveoptstr(int optidx, int ophase) +{ + int roleoptindx = opt2roleopt(optidx); + + if (roleoptvals[roleoptindx][ophase]) + free((genericptr_t) roleoptvals[roleoptindx][ophase]), + roleoptvals[roleoptindx][ophase] = 0; } +/* discard all saved option strings */ void -initoptions_finish() -{ - nhsym sym = 0; -#ifndef MAC - char *opts = nle_getenv("NETHACKOPTIONS"); - - if (!opts) - opts = getenv("HACKOPTIONS"); - if (opts) { - if (*opts == '/' || *opts == '\\' || *opts == '@') { - if (*opts == '@') - opts++; /* @filename */ - /* looks like a filename */ - if (strlen(opts) < BUFSZ / 2) { - config_error_init(TRUE, opts, CONFIG_ERROR_SECURE); - read_config_file(opts, SET_IN_FILE); - config_error_done(); - } - } else { - config_error_init(TRUE, (char *) 0, FALSE); - read_config_file((char *) 0, SET_IN_FILE); - config_error_done(); - /* let the total length of options be long; - * parseoptions() will check each individually - */ - config_error_init(FALSE, "NETHACKOPTIONS", FALSE); - (void) parseoptions(opts, TRUE, FALSE); - config_error_done(); - } - } else -#endif /* !MAC */ - /*else*/ { - config_error_init(TRUE, (char *) 0, FALSE); - read_config_file((char *) 0, SET_IN_FILE); - config_error_done(); - } +freeroleoptvals(void) +{ + int i, j; - (void) fruitadd(pl_fruit, (struct fruit *) 0); - /* - * Remove "slime mold" from list of object names. This will - * prevent it from being wished unless it's actually present - * as a named (or default) fruit. Wishing for "fruit" will - * result in the player's preferred fruit [better than "\033"]. - */ - obj_descr[SLIME_MOLD].oc_name = "fruit"; + for (i = 0; i < 4; ++i) + for (j = 0; j < num_opt_phases; ++j) + unsaveoptstr(roleopt2opt[i], j); +} - sym = get_othersym(SYM_BOULDER, - Is_rogue_level(&u.uz) ? ROGUESET : PRIMARY); - if (sym) - showsyms[SYM_BOULDER + SYM_OFF_X] = sym; - reglyph_darkroom(); +#if 0 /* not needed */ -#ifdef STATUS_HILITES - /* - * A multi-interface binary might only support status highlighting - * for some of the interfaces; check whether we asked for it but are - * using one which doesn't. - * - * Option processing can take place before a user-decided WindowPort - * is even initialized, so check for that too. - */ - if (!WINDOWPORT("safe-startup")) { - if (iflags.hilite_delta && !wc2_supported("statushilites")) { - raw_printf("Status highlighting not supported for %s interface.", - windowprocs.name); - iflags.hilite_delta = 0; - } +/* put roleoptvals[][] into save file; will be needed if #saveoptions + takes place after restore */ +void +saveoptvals(NHFILE *nhfp) +{ + if (update_file(nhfp)) { + char *val; + unsigned len; + int i, j; + + for (i = 0; i < 4; ++i) + for (j = 0; j < num_opt_phases; ++j) { + val = roleoptvals[i][j]; + len = val ? Strlen(val) + 1 : 0; + Sfo_unsigned(nhfp, &len, "optvals-len"); + if (val) + Sfo_char(nhfp, val, "optvals-val", len); + } } -#endif - return; + if (release_data(nhfp)) + freeroleoptvals(); } -/* copy up to maxlen-1 characters; 'dest' must be able to hold maxlen; - treat comma as alternate end of 'src' */ -STATIC_OVL void -nmcpy(dest, src, maxlen) -char *dest; -const char *src; -int maxlen; +/* get roleoptvals[][] from save file */ +void +restoptvals(NHFILE *nhfp) { - int count; - - for (count = 1; count < maxlen; count++) { - if (*src == ',' || *src == '\0') - break; /*exit on \0 terminator*/ - *dest++ = *src++; + char *val; + unsigned len; + int i, j; + + if (nhfp->structlevel) { + for (i = 0; i < 4; ++i) + for (j = 0; j < num_opt_phases; ++j) { + /* len includes terminating '\0' for non-Null values */ + Sfi_unsigned(nhfp, &len, "optvals-len"); + if (len) { + val = roleoptvals[i][j] = (char *) alloc(len); + Sfi_char(nhfp, val, "opvals-val", (int) len); + } else { + roleoptvals[i][j] = NULL; + } + } } - *dest = '\0'; +} + +#endif /* 0 */ + +/* common to optfn_catname(), optfn_dogname(), optfn_horsename() */ +staticfn int +petname_optfn( + int optidx, int req, + boolean negated, + char *opts, char *op) +{ + char failsafe[PL_PSIZ + 1]; + char *petname = (optidx == opt_catname) ? gc.catname + : (optidx == opt_dogname) ? gd.dogname + : (optidx == opt_horsename) ? gh.horsename + : failsafe; + + if (req == do_init) { + ; + } else if (req == do_set) { + if (op == empty_optstr && !negated) + return optn_err; + if (negated || !strcmp(op, "none") || !strcmp(op, none)) + op = empty_optstr; + nmcpy(petname, op, PL_PSIZ); + sanitize_name(petname); + } else if (req == get_val || req == get_cnf_val) { + failsafe[0] = '\0'; + Sprintf(opts, "%s", *petname ? petname + : (req == get_cnf_val) ? "none" : none); + } + return optn_ok; } /* - * escapes(): escape expansion for showsyms. C-style escapes understood - * include \n, \b, \t, \r, \xnnn (hex), \onnn (octal), \nnn (decimal). - * (Note: unlike in C, leading digit 0 is not used to indicate octal; - * the letter o (either upper or lower case) is used for that. - * The ^-prefix for control characters is also understood, and \[mM] - * has the effect of 'meta'-ing the value which follows (so that the - * alternate character set will be enabled). + ********************************** * - * X normal key X - * ^X control-X - * \mX meta-X + * Per-option Functions * - * For 3.4.3 and earlier, input ending with "\M", backslash, or caret - * prior to terminating '\0' would pull that '\0' into the output and then - * keep processing past it, potentially overflowing the output buffer. - * Now, trailing \ or ^ will act like \\ or \^ and add '\\' or '^' to the - * output and stop there; trailing \M will fall through to \ and - * yield 'M', then stop. Any \X or \O followed by something other than - * an appropriate digit will also fall through to \ and yield 'X' - * or 'O', plus stop if the non-digit is end-of-string. + ********************************** */ -STATIC_OVL void -escapes(cp, tp) -const char *cp; /* might be 'tp', updating in place */ -char *tp; /* result is never longer than 'cp' */ -{ - static NEARDATA const char oct[] = "01234567", dec[] = "0123456789", - hex[] = "00112233445566778899aAbBcCdDeEfF"; - const char *dp; - int cval, meta, dcount; - while (*cp) { - /* \M has to be followed by something to do meta conversion, - otherwise it will just be \M which ultimately yields 'M' */ - meta = (*cp == '\\' && (cp[1] == 'm' || cp[1] == 'M') && cp[2]); - if (meta) - cp += 2; - - cval = dcount = 0; /* for decimal, octal, hexadecimal cases */ - if ((*cp != '\\' && *cp != '^') || !cp[1]) { - /* simple character, or nothing left for \ or ^ to escape */ - cval = *cp++; - } else if (*cp == '^') { /* expand control-character syntax */ - cval = (*++cp & 0x1f); - ++cp; +staticfn int +optfn_alignment( + int optidx, + int req, + boolean negated, + char *opts, + char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* alignment:string */ + if (!parse_role_opt(optidx, negated, allopt[optidx].name, opts, &op)) + return optn_silenterr; - /* remaining cases are all for backslash; we know cp[1] is not \0 */ - } else if (index(dec, cp[1])) { - ++cp; /* move past backslash to first digit */ - do { - cval = (cval * 10) + (*cp - '0'); - } while (*++cp && index(dec, *cp) && ++dcount < 3); - } else if ((cp[1] == 'o' || cp[1] == 'O') && cp[2] - && index(oct, cp[2])) { - cp += 2; /* move past backslash and 'O' */ - do { - cval = (cval * 8) + (*cp - '0'); - } while (*++cp && index(oct, *cp) && ++dcount < 3); - } else if ((cp[1] == 'x' || cp[1] == 'X') && cp[2] - && (dp = index(hex, cp[2])) != 0) { - cp += 2; /* move past backslash and 'X' */ - do { - cval = (cval * 16) + ((int) (dp - hex) / 2); - } while (*++cp && (dp = index(hex, *cp)) != 0 && ++dcount < 2); - } else { /* C-style character escapes */ - switch (*++cp) { - case '\\': - cval = '\\'; - break; - case 'n': - cval = '\n'; - break; - case 't': - cval = '\t'; - break; - case 'b': - cval = '\b'; - break; - case 'r': - cval = '\r'; - break; - default: - cval = *cp; + if (*op != '!') { + if ((flags.initalign = str2align(op)) == ROLE_NONE) { + config_error_add("Unknown %s '%s'", allopt[optidx].name, op); + return optn_err; } - ++cp; + saveoptstr(optidx, rolestring(flags.initalign, aligns, adj)); } - - if (meta) - cval |= 0x80; - *tp++ = (char) cval; + return optn_ok; } - *tp = '\0'; -} - -STATIC_OVL void -rejectoption(optname) -const char *optname; -{ -#ifdef MICRO - pline("\"%s\" settable only from %s.", optname, configfile); -#else - pline("%s can be set only from NETHACKOPTIONS or %s.", optname, - configfile); -#endif + if (req == get_val) { + Sprintf(opts, "%s", rolestring(flags.initalign, aligns, adj)); + return optn_ok; + } + if (req == get_cnf_val) { + op = get_cnf_role_opt(optidx); + Strcpy(opts, op ? op : "none"); + return optn_ok; + } + return optn_ok; } -/* - -# errors: -OPTIONS=aaaaaaaaaa[ more than 247 (255 - 8 for 'OPTIONS=') total ]aaaaaaaaaa -OPTIONS -OPTIONS= -MSGTYPE=stop"You swap places with " -MSGTYPE=st.op "You swap places with " -MSGTYPE=stop "You swap places with \" -MENUCOLOR=" blessed "green&none -MENUCOLOR=" holy " = green&reverse -MENUCOLOR=" cursed " = red&uline -MENUCOLOR=" unholy " = reed -OPTIONS=!legacy:true,fooo -OPTIONS=align:!pin -OPTIONS=gender - -*/ -STATIC_OVL char * -string_for_opt(opts, val_optional) -char *opts; -boolean val_optional; +staticfn int +optfn_align_message( + int optidx, int req, boolean negated, + char *opts, char *op) { - char *colon, *equals; - - colon = index(opts, ':'); - equals = index(opts, '='); - if (!colon || (equals && equals < colon)) - colon = equals; - - if (!colon || !*++colon) { - if (!val_optional) - config_error_add("Missing parameter for '%s'", opts); - return empty_optstr; + if (req == do_init) { + return optn_ok; } - return colon; -} + if (req == do_set) { + /* WINCAP align_message:[left|top|right|bottom] */ -STATIC_OVL char * -string_for_env_opt(optname, opts, val_optional) -const char *optname; -char *opts; -boolean val_optional; -{ - if (!initial) { - rejectoption(optname); - return empty_optstr; + op = string_for_opt(opts, negated); + if ((op != empty_optstr) && !negated) { + if (!strncmpi(op, "left", sizeof "left" - 1)) + iflags.wc_align_message = ALIGN_LEFT; + else if (!strncmpi(op, "top", sizeof "top" - 1)) + iflags.wc_align_message = ALIGN_TOP; + else if (!strncmpi(op, "right", sizeof "right" - 1)) + iflags.wc_align_message = ALIGN_RIGHT; + else if (!strncmpi(op, "bottom", sizeof "bottom" - 1)) + iflags.wc_align_message = ALIGN_BOTTOM; + else { + config_error_add("Unknown %s parameter '%s'", + allopt[optidx].name, op); + return optn_err; + } + } else if (negated) { + bad_negation(allopt[optidx].name, TRUE); + return optn_err; + } + return optn_ok; } - return string_for_opt(opts, val_optional); -} + if (req == get_val || req == get_cnf_val) { + int which; -STATIC_OVL void -bad_negation(optname, with_parameter) -const char *optname; -boolean with_parameter; -{ - pline_The("%s option may not %sbe negated.", optname, - with_parameter ? "both have a value and " : ""); + which = iflags.wc_align_message; + Sprintf(opts, "%s", + (which == ALIGN_TOP) ? "top" + : (which == ALIGN_LEFT) ? "left" + : (which == ALIGN_BOTTOM) ? "bottom" + : (which == ALIGN_RIGHT) ? "right" + : defopt); + return optn_ok; + } + if (req == do_handler) { + return handler_align_misc(optidx); + } + return optn_ok; } -/* - * Change the inventory order, using the given string as the new order. - * Missing characters in the new order are filled in at the end from - * the current inv_order, except for gold, which is forced to be first - * if not explicitly present. - * - * This routine returns 1 unless there is a duplicate or bad char in - * the string. - */ -STATIC_OVL int -change_inv_order(op) -char *op; +staticfn int +optfn_align_status( + int optidx, int req, boolean negated, + char *opts, char *op) { - int oc_sym, num; - char *sp, buf[QBUFSZ]; - int retval = 1; - - num = 0; - if (!index(op, GOLD_SYM)) - buf[num++] = COIN_CLASS; - - for (sp = op; *sp; sp++) { - boolean fail = FALSE; - oc_sym = def_char_to_objclass(*sp); - /* reject bad or duplicate entries */ - if (oc_sym == MAXOCLASSES) { /* not an object class char */ - config_error_add("Not an object class '%c'", *sp); - retval = 0; - fail = TRUE; - } else if (!index(flags.inv_order, oc_sym)) { - /* VENOM_CLASS, RANDOM_CLASS, and ILLOBJ_CLASS are excluded - because they aren't in def_inv_order[] so don't make it - into flags.inv_order, hence always fail this index() test */ - config_error_add("Object class '%c' not allowed", *sp); - retval = 0; - fail = TRUE; - } else if (index(sp + 1, *sp)) { - config_error_add("Duplicate object class '%c'", *sp); - retval = 0; - fail = TRUE; + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* WINCAP align_status:[left|top|right|bottom] */ + op = string_for_opt(opts, negated); + if ((op != empty_optstr) && !negated) { + if (!strncmpi(op, "left", sizeof "left" - 1)) + iflags.wc_align_status = ALIGN_LEFT; + else if (!strncmpi(op, "top", sizeof "top" - 1)) + iflags.wc_align_status = ALIGN_TOP; + else if (!strncmpi(op, "right", sizeof "right" - 1)) + iflags.wc_align_status = ALIGN_RIGHT; + else if (!strncmpi(op, "bottom", sizeof "bottom" - 1)) + iflags.wc_align_status = ALIGN_BOTTOM; + else { + config_error_add("Unknown %s parameter '%s'", + allopt[optidx].name, op); + return optn_err; + } + } else if (negated) { + bad_negation(allopt[optidx].name, TRUE); + return optn_err; } - /* retain good ones */ - if (!fail) - buf[num++] = (char) oc_sym; + return optn_ok; } - buf[num] = '\0'; + if (req == get_val || req == get_cnf_val) { + int which; - /* fill in any omitted classes, using previous ordering */ - for (sp = flags.inv_order; *sp; sp++) - if (!index(buf, *sp)) - (void) strkitten(&buf[num++], *sp); - buf[MAXOCLASSES - 1] = '\0'; - - Strcpy(flags.inv_order, buf); - return retval; + which = iflags.wc_align_status; + Sprintf(opts, "%s", + (which == ALIGN_TOP) ? "top" + : (which == ALIGN_LEFT) ? "left" + : (which == ALIGN_BOTTOM) ? "bottom" + : (which == ALIGN_RIGHT) ? "right" + : defopt); + return optn_ok; + } + if (req == do_handler) { + return handler_align_misc(optidx); + } + return optn_ok; } -STATIC_OVL boolean -warning_opts(opts, optype) -register char *opts; -const char *optype; +staticfn int +optfn_altkeyhandling( + int optidx UNUSED, + int req, + boolean negated, + char *opts, + char *op) { - uchar translate[WARNCOUNT]; - int length, i; - - if ((opts = string_for_env_opt(optype, opts, FALSE)) == empty_optstr) - return FALSE; - escapes(opts, opts); + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* altkeyhandling:string */ - length = (int) strlen(opts); - /* match the form obtained from PC configuration files */ - for (i = 0; i < WARNCOUNT; i++) - translate[i] = (i >= length) ? 0 - : opts[i] ? (uchar) opts[i] - : def_warnsyms[i].sym; - assign_warnings(translate); - return TRUE; +#if defined(WIN32CON) && defined(TTY_GRAPHICS) + if (op == empty_optstr || negated) + return optn_err; + set_altkeyhandling(op); +#else + nhUse(negated); + nhUse(op); +#endif + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + opts[0] = '\0'; +#ifdef WIN32 + Sprintf(opts, "%s", + (iflags.key_handling == nh340_keyhandling) + ? "340" + : (iflags.key_handling == ray_keyhandling) + ? "ray" + : "default"); +#endif + return optn_ok; + } +#ifdef WIN32CON + if (req == do_handler) { + return set_keyhandling_via_option(); + } +#endif + return optn_ok; } -void -assign_warnings(graph_chars) -register uchar *graph_chars; +staticfn int +optfn_autounlock( + int optidx, + int req, + boolean negated, + char *opts, + char *op) { - int i; + if (req == do_init) { + flags.autounlock = AUTOUNLOCK_APPLY_KEY; + return optn_ok; + } + if (req == do_set) { + /* autounlock:none or autounlock:untrap+apply-key+kick+force; + autounlock without a value is same as autounlock:apply-key and + !autounlock is same as autounlock:none; multiple values can be + space separated or plus-sign separated but the same separation + must be used for each element, not mix&match */ + char sep, *nxt; + unsigned newflags; + int i; - for (i = 0; i < WARNCOUNT; i++) - if (graph_chars[i]) - warnsyms[i] = graph_chars[i]; + if ((op = string_for_opt(opts, TRUE)) == empty_optstr) { + flags.autounlock = negated ? 0 : AUTOUNLOCK_APPLY_KEY; + return optn_ok; + } + newflags = 0; + sep = strchr(op, '+') ? '+' : ' '; + while (op) { + boolean matched = FALSE; + op = trimspaces(op); /* might have leading space */ + if ((nxt = strchr(op, sep)) != 0) { + *nxt++ = '\0'; + op = trimspaces(op); /* might have trailing space after + * plus sign removal */ + } + if (str_start_is("none", op, TRUE)) + negated = TRUE, matched = TRUE; + for (i = 0; i < SIZE(unlocktypes) && !matched; ++i) { + if (str_start_is(unlocktypes[i][0], op, TRUE) + /* fuzzymatch() doesn't match leading substrings but + this allows "apply_key" and "applykey" to match + "apply-key"; "apply key" too if part of foo+bar */ + || fuzzymatch(op, unlocktypes[i][0], " -_", TRUE)) { + matched = TRUE; + switch (*op) { + case 'u': + newflags |= AUTOUNLOCK_UNTRAP; + break; + case 'a': + newflags |= AUTOUNLOCK_APPLY_KEY; + break; + case 'k': + newflags |= AUTOUNLOCK_KICK; + break; + case 'f': + newflags |= AUTOUNLOCK_FORCE; + break; + default: + matched = FALSE; + break; + } + } + } + if (!matched) { + config_error_add("Invalid value for \"%s\": \"%s\"", + allopt[optidx].name, op); + return optn_silenterr; + } + op = nxt; + } + if (negated && newflags != 0) { + config_error_add( + "Invalid value combination for \"%s\": 'none' with some", + allopt[optidx].name); + return optn_silenterr; + } + flags.autounlock = newflags; + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + if (!flags.autounlock) { + Strcpy(opts, "none"); + } else { + static const char plus[] = " + "; + const char *p = ""; + + *opts = '\0'; + if (flags.autounlock & AUTOUNLOCK_UNTRAP) + Sprintf(eos(opts), "%s%s", p, unlocktypes[0][0]), p = plus; + if (flags.autounlock & AUTOUNLOCK_APPLY_KEY) + Sprintf(eos(opts), "%s%s", p, unlocktypes[1][0]), p = plus; + if (flags.autounlock & AUTOUNLOCK_KICK) + Sprintf(eos(opts), "%s%s", p, unlocktypes[2][0]), p = plus; + if (flags.autounlock & AUTOUNLOCK_FORCE) + Sprintf(eos(opts), "%s%s", p, unlocktypes[3][0]); /*no more p*/ + } + return optn_ok; + } + if (req == do_handler) { + return handler_autounlock(optidx); + } + return optn_ok; } -STATIC_OVL int -feature_alert_opts(op, optn) -char *op; -const char *optn; +staticfn int +optfn_boulder( + int optidx UNUSED, int req, boolean negated UNUSED, + char *opts, char *op UNUSED) { - char buf[BUFSZ]; - unsigned long fnv = get_feature_notice_ver(op); /* version.c */ +#ifdef BACKWARD_COMPAT + int clash = 0; +#endif - if (fnv == 0L) - return 0; - if (fnv > get_current_feature_ver()) { - if (!initial) { - You_cant("disable new feature alerts for future versions."); - } else { - config_error_add( - "%s=%s Invalid reference to a future version ignored", - optn, op); - } - return 0; + if (req == do_init) { + return optn_ok; } + if (req == do_set) { + /* boulder:symbol */ - flags.suppress_alert = fnv; - if (!initial) { - Sprintf(buf, "%lu.%lu.%lu", FEATURE_NOTICE_VER_MAJ, - FEATURE_NOTICE_VER_MIN, FEATURE_NOTICE_VER_PATCH); - pline( - "Feature change alerts disabled for NetHack %s features and prior.", - buf); - } - return 1; -} +#ifdef BACKWARD_COMPAT -void -set_duplicate_opt_detection(on_or_off) -int on_or_off; -{ - int k, *optptr; - - if (on_or_off != 0) { - /*-- ON --*/ - if (iflags.opt_booldup) - impossible("iflags.opt_booldup already on (memory leak)"); - iflags.opt_booldup = (int *) alloc(SIZE(boolopt) * sizeof (int)); - optptr = iflags.opt_booldup; - for (k = 0; k < SIZE(boolopt); ++k) - *optptr++ = 0; - - if (iflags.opt_compdup) - impossible("iflags.opt_compdup already on (memory leak)"); - iflags.opt_compdup = (int *) alloc(SIZE(compopt) * sizeof (int)); - optptr = iflags.opt_compdup; - for (k = 0; k < SIZE(compopt); ++k) - *optptr++ = 0; - } else { - /*-- OFF --*/ - if (iflags.opt_booldup) - free((genericptr_t) iflags.opt_booldup); - iflags.opt_booldup = (int *) 0; - if (iflags.opt_compdup) - free((genericptr_t) iflags.opt_compdup); - iflags.opt_compdup = (int *) 0; - } -} - -STATIC_OVL boolean -duplicate_opt_detection(opts, iscompound) -const char *opts; -int iscompound; /* 0 == boolean option, 1 == compound */ -{ - int i, *optptr; - - if (!iscompound && iflags.opt_booldup && initial && from_file) { - for (i = 0; boolopt[i].name; i++) { - if (match_optname(opts, boolopt[i].name, 3, FALSE)) { - optptr = iflags.opt_booldup + i; - *optptr += 1; - if (*optptr > 1) - return TRUE; - else - return FALSE; - } - } - } else if (iscompound && iflags.opt_compdup && initial && from_file) { - for (i = 0; compopt[i].name; i++) { - if (match_optname(opts, compopt[i].name, strlen(compopt[i].name), - TRUE)) { - optptr = iflags.opt_compdup + i; - *optptr += 1; - if (*optptr > 1) - return TRUE; - else - return FALSE; + /* if ((opts = string_for_env_opt(allopt[optidx].name, opts, FALSE)) + == empty_optstr) + */ + if ((opts = string_for_opt(opts, FALSE)) == empty_optstr) + return FALSE; + escapes(opts, opts); + /* note: dummy monclass #0 has symbol value '\0'; we allow that-- + attempting to set bouldersym to '^@'/'\0' will reset to default */ + if (def_char_to_monclass(opts[0]) != MAXMCLASSES) + clash = opts[0] ? 1 : 0; + else if (opts[0] >= '1' && opts[0] < WARNCOUNT + '0') + clash = 2; + if (opts[0] < ' ') { + config_error_add("boulder symbol cannot be a control character"); + return optn_ok; + } else if (clash) { + /* symbol chosen matches a used monster or warning + symbol which is not good - reject it */ + config_error_add("Badoption - boulder symbol '%s' would conflict " + "with a %s symbol", + visctrl(opts[0]), + (clash == 1) ? "monster" : "warning"); + } else { + /* + * Override the default boulder symbol. + */ + go.ov_primary_syms[SYM_BOULDER + SYM_OFF_X] = (nhsym) opts[0]; + go.ov_rogue_syms[SYM_BOULDER + SYM_OFF_X] = (nhsym) opts[0]; + /* for 'initial', update of BOULDER symbol is done in + initoptions_finish(), after all symset options + have been processed */ + if (!go.opt_initial) { + nhsym sym = get_othersym(SYM_BOULDER, + Is_rogue_level(&u.uz) ? ROGUESET + : PRIMARYSET); + + if (sym) + gs.showsyms[SYM_BOULDER + SYM_OFF_X] = sym; + go.opt_need_redraw = TRUE; } } + return optn_ok; +#else + config_error_add("'%s' no longer supported; use S_boulder:c instead", + allopt[optidx].name); + return optn_err; +#endif } - return FALSE; + if (req == get_val || req == get_cnf_val) { + opts[0] = '\0'; +#ifdef BACKWARD_COMPAT + Sprintf(opts, "%c", + go.ov_primary_syms[SYM_BOULDER + SYM_OFF_X] + ? go.ov_primary_syms[SYM_BOULDER + SYM_OFF_X] + : gs.showsyms[(int) objects[BOULDER].oc_class + SYM_OFF_O]); +#endif + return optn_ok; + } + return optn_ok; } -STATIC_OVL void -complain_about_duplicate(opts, iscompound) -const char *opts; -int iscompound; /* 0 == boolean option, 1 == compound */ +staticfn int +optfn_catname( + int optidx, int req, + boolean negated, + char *opts, char *op) { -#ifdef MAC - /* the Mac has trouble dealing with the output of messages while - * processing the config file. That should get fixed one day. - * For now just return. - */ -#else /* !MAC */ - config_error_add("%s option specified multiple times: %s", - iscompound ? "compound" : "boolean", opts); -#endif /* ?MAC */ - return; + return petname_optfn(optidx, req, negated, opts, op); } -/* paranoia[] - used by parseoptions() and special_handling() */ -STATIC_VAR const struct paranoia_opts { - int flagmask; /* which paranoid option */ - const char *argname; /* primary name */ - int argMinLen; /* minimum number of letters to match */ - const char *synonym; /* alternate name (optional) */ - int synMinLen; - const char *explain; /* for interactive menu */ -} paranoia[] = { - /* there are some initial-letter conflicts: "a"ttack vs "a"ll, "attack" - takes precedence and "all" isn't present in the interactive menu, - and "d"ie vs "d"eath, synonyms for each other so doesn't matter; - (also "p"ray vs "P"aranoia, "pray" takes precedence since "Paranoia" - is just a synonym for "Confirm"); "b"ones vs "br"eak-wand, the - latter requires at least two letters; "e"at vs "ex"plore, - "cont"inue eating vs "C"onfirm; "wand"-break vs "Were"-change, - both require at least two letters during config processing and use - case-senstivity for 'O's interactive menu */ - { PARANOID_CONFIRM, "Confirm", 1, "Paranoia", 2, - "for \"yes\" confirmations, require \"no\" to reject" }, - { PARANOID_QUIT, "quit", 1, "explore", 2, - "yes vs y to quit or to enter explore mode" }, - { PARANOID_DIE, "die", 1, "death", 2, - "yes vs y to die (explore mode or debug mode)" }, - { PARANOID_BONES, "bones", 1, 0, 0, - "yes vs y to save bones data when dying in debug mode" }, - { PARANOID_HIT, "attack", 1, "hit", 1, - "yes vs y to attack a peaceful monster" }, - { PARANOID_BREAKWAND, "wand-break", 2, "break-wand", 2, - "yes vs y to break a wand via (a)pply" }, - { PARANOID_EATING, "eat", 1, "continue", 4, - "yes vs y to continue eating after first bite when satiated" }, - { PARANOID_WERECHANGE, "Were-change", 2, (const char *) 0, 0, - "yes vs y to change form when lycanthropy is controllable" }, - { PARANOID_PRAY, "pray", 1, 0, 0, - "y to pray (supersedes old \"prayconfirm\" option)" }, - { PARANOID_REMOVE, "Remove", 1, "Takeoff", 1, - "always pick from inventory for Remove and Takeoff" }, - /* for config file parsing; interactive menu skips these */ - { 0, "none", 4, 0, 0, 0 }, /* require full word match */ - { ~0, "all", 3, 0, 0, 0 }, /* ditto */ -}; - -extern struct menucoloring *menu_colorings; - -static const struct { - const char *name; - const int color; -} colornames[] = { - { "black", CLR_BLACK }, - { "red", CLR_RED }, - { "green", CLR_GREEN }, - { "brown", CLR_BROWN }, - { "blue", CLR_BLUE }, - { "magenta", CLR_MAGENTA }, - { "cyan", CLR_CYAN }, - { "gray", CLR_GRAY }, - { "orange", CLR_ORANGE }, - { "light green", CLR_BRIGHT_GREEN }, - { "yellow", CLR_YELLOW }, - { "light blue", CLR_BRIGHT_BLUE }, - { "light magenta", CLR_BRIGHT_MAGENTA }, - { "light cyan", CLR_BRIGHT_CYAN }, - { "white", CLR_WHITE }, - { "no color", NO_COLOR }, - { NULL, CLR_BLACK }, /* everything after this is an alias */ - { "transparent", NO_COLOR }, - { "purple", CLR_MAGENTA }, - { "light purple", CLR_BRIGHT_MAGENTA }, - { "bright purple", CLR_BRIGHT_MAGENTA }, - { "grey", CLR_GRAY }, - { "bright red", CLR_ORANGE }, - { "bright green", CLR_BRIGHT_GREEN }, - { "bright blue", CLR_BRIGHT_BLUE }, - { "bright magenta", CLR_BRIGHT_MAGENTA }, - { "bright cyan", CLR_BRIGHT_CYAN } -}; - -static const struct { - const char *name; - const int attr; -} attrnames[] = { - { "none", ATR_NONE }, - { "bold", ATR_BOLD }, - { "dim", ATR_DIM }, - { "underline", ATR_ULINE }, - { "blink", ATR_BLINK }, - { "inverse", ATR_INVERSE }, - { NULL, ATR_NONE }, /* everything after this is an alias */ - { "normal", ATR_NONE }, - { "uline", ATR_ULINE }, - { "reverse", ATR_INVERSE }, -}; - -const char * -clr2colorname(clr) -int clr; +#ifdef CRASHREPORT +staticfn int +optfn_crash_email( + int optidx UNUSED, int req, boolean negated UNUSED, + char *opts, char *op) { - int i; - - for (i = 0; i < SIZE(colornames); i++) - if (colornames[i].name && colornames[i].color == clr) - return colornames[i].name; - return (char *) 0; -} - -int -match_str2clr(str) -char *str; -{ - int i, c = CLR_MAX; - - /* allow "lightblue", "light blue", and "light-blue" to match "light blue" - (also junk like "_l i-gh_t---b l u e" but we won't worry about that); - also copes with trailing space; caller has removed any leading space */ - for (i = 0; i < SIZE(colornames); i++) - if (colornames[i].name - && fuzzymatch(str, colornames[i].name, " -_", TRUE)) { - c = colornames[i].color; - break; - } - if (i == SIZE(colornames) && digit(*str)) - c = atoi(str); - - if (c < 0 || c >= CLR_MAX) { - config_error_add("Unknown color '%.60s'", str); - c = CLR_MAX; /* "none of the above" */ + if (req == do_init) { + return optn_ok; } - - return c; -} - -STATIC_OVL const char * -attr2attrname(attr) -int attr; -{ - int i; - - for (i = 0; i < SIZE(attrnames); i++) - if (attrnames[i].attr == attr) - return attrnames[i].name; - return (char *) 0; + if (req == do_set) { + if ((op = string_for_opt(opts, FALSE)) == empty_optstr) + return optn_err; + if (gc.crash_email) + free((genericptr_t) gc.crash_email); + gc.crash_email = dupstr(op); + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + if (!opts) + return optn_err; + if (gc.crash_email) + Sprintf(opts, "%s", gc.crash_email); + return optn_ok; + } + return optn_ok; } -int -match_str2attr(str, complain) -const char *str; -boolean complain; +staticfn int +optfn_crash_name( + int optidx UNUSED, int req, boolean negated UNUSED, + char *opts, char *op) { - int i, a = -1; - - for (i = 0; i < SIZE(attrnames); i++) - if (attrnames[i].name - && fuzzymatch(str, attrnames[i].name, " -_", TRUE)) { - a = attrnames[i].attr; - break; - } - - if (a == -1 && complain) - config_error_add("Unknown text attribute '%.50s'", str); - - return a; + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + if ((op = string_for_opt(opts, FALSE)) == empty_optstr) + return optn_err; + if (gc.crash_name) + free((genericptr_t) gc.crash_name); + gc.crash_name = dupstr(op); + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + if (!opts) + return optn_err; + if (gc.crash_name) + Sprintf(opts, "%s", gc.crash_name); + return optn_ok; + } + return optn_ok; } -int -query_color(prompt) -const char *prompt; +staticfn int +optfn_crash_urlmax( + int optidx UNUSED, int req, boolean negated UNUSED, + char *opts, char *op) { - winid tmpwin; - anything any; - int i, pick_cnt; - menu_item *picks = (menu_item *) 0; + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + if ((op = string_for_opt(opts, FALSE)) != empty_optstr) { + int temp = atoi(op); - tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - any = zeroany; - for (i = 0; i < SIZE(colornames); i++) { - if (!colornames[i].name) - break; - any.a_int = i + 1; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, colornames[i].name, - (colornames[i].color == NO_COLOR) ? MENU_SELECTED - : MENU_UNSELECTED); + if (temp < 75){ + config_error_add("Invalid value %d for crash_urlmax. " + " Minimum value is 75.", temp); + return optn_err; + } + gc.crash_urlmax = temp; + } else + return optn_err; + return optn_ok; } - end_menu(tmpwin, (prompt && *prompt) ? prompt : "Pick a color"); - pick_cnt = select_menu(tmpwin, PICK_ONE, &picks); - destroy_nhwindow(tmpwin); - if (pick_cnt > 0) { - i = colornames[picks[0].item.a_int - 1].color; - /* pick_cnt==2: explicitly picked something other than the - preselected entry */ - if (pick_cnt == 2 && i == NO_COLOR) - i = colornames[picks[1].item.a_int - 1].color; - free((genericptr_t) picks); - return i; - } else if (pick_cnt == 0) { - /* pick_cnt==0: explicitly picking preselected entry toggled it off */ - return NO_COLOR; + if (req == get_val || req == get_cnf_val) { + if (!opts) + return optn_err; + Sprintf(opts, "%d", gc.crash_urlmax); + return optn_ok; } - return -1; + return optn_ok; } -/* ask about highlighting attribute; for menu headers and menu - coloring patterns, only one attribute at a time is allowed; - for status highlighting, multiple attributes are allowed [overkill; - life would be much simpler if that were restricted to one also...] */ -int -query_attr(prompt) -const char *prompt; +#endif /* CRASHREPORT */ + +#ifdef CURSES_GRAPHICS +staticfn int +optfn_cursesgraphics( + int optidx, int req, boolean negated, + char *opts, char *op UNUSED) { - winid tmpwin; - anything any; - int i, pick_cnt; - menu_item *picks = (menu_item *) 0; - boolean allow_many = (prompt && !strncmpi(prompt, "Choose", 6)); - int default_attr = ATR_NONE; +#ifdef BACKWARD_COMPAT + boolean badflag = FALSE; +#endif - if (prompt && strstri(prompt, "menu headings")) - default_attr = iflags.menu_headings; - tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - any = zeroany; - for (i = 0; i < SIZE(attrnames); i++) { - if (!attrnames[i].name) - break; - any.a_int = i + 1; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, attrnames[i].attr, - attrnames[i].name, - (attrnames[i].attr == default_attr) ? MENU_SELECTED - : MENU_UNSELECTED); + if (req == do_init) { + return optn_ok; } - end_menu(tmpwin, (prompt && *prompt) ? prompt : "Pick an attribute"); - pick_cnt = select_menu(tmpwin, allow_many ? PICK_ANY : PICK_ONE, &picks); - destroy_nhwindow(tmpwin); - if (pick_cnt > 0) { - int j, k = 0; - - if (allow_many) { - /* PICK_ANY, with one preselected entry (ATR_NONE) which - should be excluded if any other choices were picked */ - for (i = 0; i < pick_cnt; ++i) { - j = picks[i].item.a_int - 1; - if (attrnames[j].attr != ATR_NONE || pick_cnt == 1) { - switch (attrnames[j].attr) { - case ATR_DIM: - k |= HL_DIM; - break; - case ATR_BLINK: - k |= HL_BLINK; - break; - case ATR_ULINE: - k |= HL_ULINE; - break; - case ATR_INVERSE: - k |= HL_INVERSE; - break; - case ATR_BOLD: - k |= HL_BOLD; - break; - case ATR_NONE: - k = HL_NONE; - break; - } - } + if (req == do_set) { + /* "cursesgraphics" */ + +#ifdef BACKWARD_COMPAT + if (!negated) { + /* There is no rogue level cursesgraphics-specific set */ + if (gs.symset[PRIMARYSET].name) { + badflag = TRUE; + } else { + gs.symset[PRIMARYSET].name = dupstr(allopt[optidx].name); + if (!read_sym_file(PRIMARYSET)) { + badflag = TRUE; + clear_symsetentry(PRIMARYSET, TRUE); + } else + switch_symbols(TRUE); + } + if (badflag) { + config_error_add("Failure to load symbol set %s.", + allopt[optidx].name); + return optn_err; } - } else { - /* PICK_ONE, but might get 0 or 2 due to preselected entry */ - j = picks[0].item.a_int - 1; - /* pick_cnt==2: explicitly picked something other than the - preselected entry */ - if (pick_cnt == 2 && attrnames[j].attr == default_attr) - j = picks[1].item.a_int - 1; - k = attrnames[j].attr; } - free((genericptr_t) picks); - return k; - } else if (pick_cnt == 0 && !allow_many) { - /* PICK_ONE, preselected entry explicitly chosen */ - return default_attr; + return optn_ok; +#else + config_error_add("'%s' no longer supported; use 'symset:%s' instead", + allopt[optidx].name, allopt[optidx].name); + return optn_err; +#endif } - /* either ESC to explicitly cancel (pick_cnt==-1) or - PICK_ANY with preselected entry toggled off and nothing chosen */ - return -1; + if (req == get_val || req == get_cnf_val) { + opts[0] = '\0'; + return optn_ok; + } + return optn_ok; } +#endif -static const struct { - const char *name; - xchar msgtyp; - const char *descr; -} msgtype_names[] = { - { "show", MSGTYP_NORMAL, "Show message normally" }, - { "hide", MSGTYP_NOSHOW, "Hide message" }, - { "noshow", MSGTYP_NOSHOW, NULL }, - { "stop", MSGTYP_STOP, "Prompt for more after the message" }, - { "more", MSGTYP_STOP, NULL }, - { "norep", MSGTYP_NOREP, "Do not repeat the message" } -}; - -STATIC_OVL const char * -msgtype2name(typ) -int typ; +staticfn int +optfn_DECgraphics( + int optidx, int req, boolean negated, + char *opts, char *op UNUSED) { - int i; - - for (i = 0; i < SIZE(msgtype_names); i++) - if (msgtype_names[i].descr && msgtype_names[i].msgtyp == typ) - return msgtype_names[i].name; - return (char *) 0; -} +#ifdef BACKWARD_COMPAT + boolean badflag = FALSE; +#endif -STATIC_OVL int -query_msgtype() -{ - winid tmpwin; - anything any; - int i, pick_cnt; - menu_item *picks = (menu_item *) 0; + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* "DECgraphics" */ - tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - any = zeroany; - for (i = 0; i < SIZE(msgtype_names); i++) - if (msgtype_names[i].descr) { - any.a_int = msgtype_names[i].msgtyp + 1; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - msgtype_names[i].descr, MENU_UNSELECTED); +#ifdef BACKWARD_COMPAT + if (!negated) { + /* There is no rogue level DECgraphics-specific set */ + if (gs.symset[PRIMARYSET].name) { + badflag = TRUE; + } else { + gs.symset[PRIMARYSET].name = dupstr(allopt[optidx].name); + if (!read_sym_file(PRIMARYSET)) { + badflag = TRUE; + clear_symsetentry(PRIMARYSET, TRUE); + } else + switch_symbols(TRUE); + } + if (badflag) { + config_error_add("Failure to load symbol set %s.", + allopt[optidx].name); + return optn_err; + } } - end_menu(tmpwin, "How to show the message"); - pick_cnt = select_menu(tmpwin, PICK_ONE, &picks); - destroy_nhwindow(tmpwin); - if (pick_cnt > 0) { - i = picks->item.a_int - 1; - free((genericptr_t) picks); - return i; + return optn_ok; +#else + config_error_add("'%s' no longer supported; use 'symset:%s' instead", + allopt[optidx].name, allopt[optidx].name); + return optn_err; +#endif } - return -1; -} - -STATIC_OVL boolean -msgtype_add(typ, pattern) -int typ; -char *pattern; -{ - struct plinemsg_type *tmp = (struct plinemsg_type *) alloc(sizeof *tmp); - - tmp->msgtype = typ; - tmp->regex = regex_init(); - if (!regex_compile(pattern, tmp->regex)) { - static const char *re_error = "MSGTYPE regex error"; - - config_error_add("%s: %s", re_error, regex_error_desc(tmp->regex)); - regex_free(tmp->regex); - free((genericptr_t) tmp); - return FALSE; + if (req == get_val || req == get_cnf_val) { + opts[0] = '\0'; + return optn_ok; } - tmp->pattern = dupstr(pattern); - tmp->next = plinemsg_types; - plinemsg_types = tmp; - return TRUE; + return optn_ok; } -void -msgtype_free() +staticfn int +optfn_disclose( + int optidx, int req, boolean negated, + char *opts, char *op) { - struct plinemsg_type *tmp, *tmp2 = 0; + int i, idx, prefix_val; + unsigned num; - for (tmp = plinemsg_types; tmp; tmp = tmp2) { - tmp2 = tmp->next; - free((genericptr_t) tmp->pattern); - regex_free(tmp->regex); - free((genericptr_t) tmp); + if (req == do_init) { + return optn_ok; } - plinemsg_types = (struct plinemsg_type *) 0; -} + if (req == do_set) { + /* things to disclose at end of game */ -STATIC_OVL void -free_one_msgtype(idx) -int idx; /* 0 .. */ -{ - struct plinemsg_type *tmp = plinemsg_types; - struct plinemsg_type *prev = NULL; - - while (tmp) { - if (idx == 0) { - struct plinemsg_type *next = tmp->next; - - regex_free(tmp->regex); - free((genericptr_t) tmp->pattern); - free((genericptr_t) tmp); - if (prev) - prev->next = next; - else - plinemsg_types = next; - return; + /* + * The order that the end_disclose options are stored: + * inventory, attribs, vanquished, genocided, + * conduct, overview. + * There is an array in flags: + * end_disclose[NUM_DISCLOSURE_OPT]; + * with option settings for the each of the following: + * iagvc [see disclosure_options in decl.c]: + * Allowed setting values in that array are: + * DISCLOSE_PROMPT_DEFAULT_YES ask with default answer yes + * DISCLOSE_PROMPT_DEFAULT_NO ask with default answer no + * DISCLOSE_YES_WITHOUT_PROMPT always disclose and don't ask + * DISCLOSE_NO_WITHOUT_PROMPT never disclose and don't ask + * DISCLOSE_PROMPT_DEFAULT_SPECIAL for 'vanq'/'genod' only... + * DISCLOSE_SPECIAL_WITHOUT_PROMPT ...to set up sort order. + * + * Those setting values can be used in the option + * string as a prefix to get the desired behavior. + * + * For backward compatibility, no prefix is required, + * and the presence of a i,a,g,v, or c without a prefix + * sets the corresponding value to DISCLOSE_YES_WITHOUT_PROMPT. + */ + + op = string_for_opt(opts, TRUE); + if (op != empty_optstr && negated) { + bad_negation(allopt[optidx].name, TRUE); + return optn_err; + } + /* "disclose" without a value means "all with prompting" + and negated means "none without prompting" */ + if (op == empty_optstr || !strcmpi(op, "all") + || !strcmpi(op, "none")) { + if (op != empty_optstr && !strcmpi(op, "none")) + negated = TRUE; + for (num = 0; num < NUM_DISCLOSURE_OPTIONS; num++) + flags.end_disclose[num] = negated + ? DISCLOSE_NO_WITHOUT_PROMPT + : DISCLOSE_PROMPT_DEFAULT_YES; + return optn_ok; } - idx--; - prev = tmp; - tmp = tmp->next; - } -} -int -msgtype_type(msg, norepeat) -const char *msg; -boolean norepeat; /* called from Norep(via pline) */ -{ - struct plinemsg_type *tmp = plinemsg_types; + num = 0; + prefix_val = -1; + while (*op && num < sizeof flags.end_disclose - 1) { + static char valid_settings[] = { DISCLOSE_PROMPT_DEFAULT_YES, + DISCLOSE_PROMPT_DEFAULT_NO, + DISCLOSE_PROMPT_DEFAULT_SPECIAL, + DISCLOSE_YES_WITHOUT_PROMPT, + DISCLOSE_NO_WITHOUT_PROMPT, + DISCLOSE_SPECIAL_WITHOUT_PROMPT, + '\0' }; + char c; + const char *dop; - while (tmp) { - /* we don't exclude entries with negative msgtype values - because then the msg might end up matching a later pattern */ - if (regex_match(msg, tmp->regex)) - return tmp->msgtype; - tmp = tmp->next; + c = lowc(*op); + if (c == 'k') + c = 'v'; /* killed -> vanquished */ + if (c == 'd') + c = 'o'; /* dungeon -> overview */ + dop = strchr(disclosure_options, c); + if (dop) { + idx = (int) (dop - disclosure_options); + if (idx < 0 || idx > NUM_DISCLOSURE_OPTIONS - 1) { + impossible("bad disclosure index %d %c", idx, c); + continue; + } + if (prefix_val != -1) { + if (*dop != 'v' && *dop != 'g') { + if (prefix_val == DISCLOSE_PROMPT_DEFAULT_SPECIAL) + prefix_val = DISCLOSE_PROMPT_DEFAULT_YES; + if (prefix_val == DISCLOSE_SPECIAL_WITHOUT_PROMPT) + prefix_val = DISCLOSE_YES_WITHOUT_PROMPT; + } + flags.end_disclose[idx] = prefix_val; + prefix_val = -1; + } else + flags.end_disclose[idx] = DISCLOSE_YES_WITHOUT_PROMPT; + } else if (strchr(valid_settings, c)) { + prefix_val = c; + } else if (c == ' ') { + ; /* do nothing */ + } else { + config_error_add("Unknown %s parameter '%c'", + allopt[optidx].name, *op); + return optn_err; + } + op++; + } + return optn_ok; } - return norepeat ? MSGTYP_NOREP : MSGTYP_NORMAL; + if (req == get_val || req == get_cnf_val) { + opts[0] = '\0'; + for (i = 0; i < NUM_DISCLOSURE_OPTIONS; i++) { + if (i) + (void) strkitten(opts, ' '); + (void) strkitten(opts, flags.end_disclose[i]); + (void) strkitten(opts, disclosure_options[i]); + } + return optn_ok; + } + if (req == do_handler) { + return handler_disclose(); + } + return optn_ok; } -/* negate one or more types of messages so that their type handling will - be disabled or re-enabled; MSGTYPE_NORMAL (value 0) is not affected */ -void -hide_unhide_msgtypes(hide, hide_mask) -boolean hide; -int hide_mask; +staticfn int +optfn_dogname( + int optidx, int req, + boolean negated, + char *opts, char *op) { - struct plinemsg_type *tmp; - int mt; + return petname_optfn(optidx, req, negated, opts, op); +} - /* negative msgtype value won't be recognized by pline, so does nothing */ - for (tmp = plinemsg_types; tmp; tmp = tmp->next) { - mt = tmp->msgtype; - if (!hide) - mt = -mt; /* unhide: negate negative, yielding positive */ - if (mt > 0 && ((1 << mt) & hide_mask)) - tmp->msgtype = -tmp->msgtype; +staticfn int +optfn_dungeon( + int optidx UNUSED, int req, boolean negated UNUSED, + char *opts, char *op UNUSED) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + return optn_ok; + } + if (req == get_val) { + Sprintf(opts, "%s", to_be_done); + return optn_ok; } + if (req == get_cnf_val) { + opts[0] = '\0'; + return optn_ok; + } + return optn_ok; } -STATIC_OVL int -msgtype_count(VOID_ARGS) +staticfn int +optfn_effects( + int optidx UNUSED, int req, boolean negated UNUSED, + char *opts, char *op UNUSED) { - int c = 0; - struct plinemsg_type *tmp = plinemsg_types; - - while (tmp) { - c++; - tmp = tmp->next; + if (req == do_init) { + return optn_ok; } - return c; + if (req == do_set) { + return optn_ok; + } + if (req == get_val) { + Sprintf(opts, "%s", to_be_done); + return optn_ok; + } + if (req == get_cnf_val) { + opts[0] = '\0'; + return optn_ok; + } + return optn_ok; } -boolean -msgtype_parse_add(str) -char *str; +staticfn int +optfn_font_map( + int optidx, int req, boolean negated, + char *opts, char *op) { - char pattern[256]; - char msgtype[11]; + /* send them over to the prefix handling for font_ */ + return pfxfn_font(optidx, req, negated, opts, op); +} - if (sscanf(str, "%10s \"%255[^\"]\"", msgtype, pattern) == 2) { - int typ = -1; - int i; +staticfn int +optfn_font_menu( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + /* send them over to the prefix handling for font_ */ + return pfxfn_font(optidx, req, negated, opts, op); +} - for (i = 0; i < SIZE(msgtype_names); i++) - if (!strncmpi(msgtype_names[i].name, msgtype, strlen(msgtype))) { - typ = msgtype_names[i].msgtyp; - break; - } - if (typ != -1) - return msgtype_add(typ, pattern); - else - config_error_add("Unknown message type '%s'", msgtype); - } else { - config_error_add("Malformed MSGTYPE"); - } - return FALSE; +staticfn int +optfn_font_message( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + /* send them over to the prefix handling for font_ */ + return pfxfn_font(optidx, req, negated, opts, op); } -STATIC_OVL boolean -test_regex_pattern(str, errmsg) -const char *str; -const char *errmsg; +staticfn int +optfn_font_size_map( + int optidx, int req, boolean negated, + char *opts, char *op) { - static const char re_error[] = "Regex error"; - struct nhregex *match; - boolean retval = TRUE; + /* send them over to the prefix handling for font_ */ + return pfxfn_font(optidx, req, negated, opts, op); +} - if (!str) - return FALSE; +staticfn int +optfn_font_size_menu( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + /* send them over to the prefix handling for font_ */ + return pfxfn_font(optidx, req, negated, opts, op); +} - match = regex_init(); - if (!match) { - config_error_add("NHregex error"); - return FALSE; - } +staticfn int +optfn_font_size_message( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + /* send them over to the prefix handling for font_ */ + return pfxfn_font(optidx, req, negated, opts, op); +} - if (!regex_compile(str, match)) { - config_error_add("%s: %s", errmsg ? errmsg : re_error, - regex_error_desc(match)); - retval = FALSE; - } - regex_free(match); - return retval; +staticfn int +optfn_font_size_status( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + /* send them over to the prefix handling for font_ */ + return pfxfn_font(optidx, req, negated, opts, op); } -STATIC_OVL boolean -add_menu_coloring_parsed(str, c, a) -char *str; -int c, a; +staticfn int +optfn_font_size_text( + int optidx, int req, boolean negated, + char *opts, char *op) { - static const char re_error[] = "Menucolor regex error"; - struct menucoloring *tmp; + /* send them over to the prefix handling for font_ */ + return pfxfn_font(optidx, req, negated, opts, op); +} - if (!str) - return FALSE; - tmp = (struct menucoloring *) alloc(sizeof *tmp); - tmp->match = regex_init(); - if (!regex_compile(str, tmp->match)) { - config_error_add("%s: %s", re_error, regex_error_desc(tmp->match)); - regex_free(tmp->match); - free(tmp); - return FALSE; - } else { - tmp->next = menu_colorings; - tmp->origstr = dupstr(str); - tmp->color = c; - tmp->attr = a; - menu_colorings = tmp; - return TRUE; - } +staticfn int +optfn_font_status( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + /* send them over to the prefix handling for font_ */ + return pfxfn_font(optidx, req, negated, opts, op); } -/* parse '"regex_string"=color&attr' and add it to menucoloring */ -boolean -add_menu_coloring(tmpstr) -char *tmpstr; /* never Null but could be empty */ +staticfn int +optfn_font_text( + int optidx, int req, boolean negated, + char *opts, char *op) { - int c = NO_COLOR, a = ATR_NONE; - char *tmps, *cs, *amp; - char str[BUFSZ]; + /* send them over to the prefix handling for font_ */ + return pfxfn_font(optidx, req, negated, opts, op); +} - (void) strncpy(str, tmpstr, sizeof str - 1); - str[sizeof str - 1] = '\0'; +staticfn int +optfn_fruit( + int optidx UNUSED, int req, boolean negated, + char *opts, char *op) +{ + struct fruit *forig = 0; - if ((cs = index(str, '=')) == 0) { - config_error_add("Malformed MENUCOLOR"); - return FALSE; + if (req == do_init) { + return optn_ok; } + if (req == do_set) { + op = string_for_opt(opts, negated || !go.opt_initial); + if (negated) { + if (op != empty_optstr) { + bad_negation("fruit", TRUE); + return optn_err; + } + op = empty_optstr; + goto goodfruit; + } + if (op == empty_optstr) + return optn_err; + /* strip leading/trailing spaces, condense internal ones (3.6.2) */ + mungspaces(op); + if (!go.opt_initial) { + struct fruit *f; + int fnum = 0; - tmps = cs + 1; /* advance past '=' */ - mungspaces(tmps); - if ((amp = index(tmps, '&')) != 0) - *amp = '\0'; - - c = match_str2clr(tmps); - if (c >= CLR_MAX) - return FALSE; - - if (amp) { - tmps = amp + 1; /* advance past '&' */ - a = match_str2attr(tmps, TRUE); - if (a == -1) - return FALSE; - } + /* count number of named fruits; if 'op' is found among them, + then the count doesn't matter because we won't be adding it */ + f = fruit_from_name(op, FALSE, &fnum); + if (!f) { + if (!flags.made_fruit) + forig = fruit_from_name(svp.pl_fruit, FALSE, (int *) 0); - /* the regexp portion here has not been condensed by mungspaces() */ - *cs = '\0'; - tmps = str; - if (*tmps == '"' || *tmps == '\'') { - cs--; - while (isspace((uchar) *cs)) - cs--; - if (*cs == *tmps) { - *cs = '\0'; - tmps++; + if (!forig && fnum >= 100) { + config_error_add( + "Doing that so many times isn't very fruitful."); + return optn_ok; + } + } + } + goodfruit: + nmcpy(svp.pl_fruit, op, PL_FSIZ); + sanitize_name(svp.pl_fruit); + /* OBJ_NAME(objects[SLIME_MOLD]) won't work for this after + initialization; it gets changed to generic "fruit" */ + if (!*svp.pl_fruit) + nmcpy(svp.pl_fruit, "slime mold", PL_FSIZ); + if (!go.opt_initial) { + /* if 'forig' is nonNull, we replace it rather than add + a new fruit; it can only be nonNull if no fruits have + been created since the previous name was put in place */ + (void) fruitadd(svp.pl_fruit, forig); + if (give_opt_msg) + pline("Fruit is now \"%s\".", svp.pl_fruit); } + /* If initial, then initoptions is allowed to do it instead + * of here (initoptions always has to do it even if there's + * no fruit option at all. Also, we don't want people + * setting multiple fruits in their options.) + */ + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + Sprintf(opts, "%s", svp.pl_fruit); + return optn_ok; } - return add_menu_coloring_parsed(tmps, c, a); + return optn_ok; } -boolean -get_menu_coloring(str, color, attr) -const char *str; -int *color, *attr; +staticfn int +optfn_gender( + int optidx, + int req, + boolean negated, + char *opts, + char *op) { - struct menucoloring *tmpmc; + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* gender:string */ + if (!parse_role_opt(optidx, negated, allopt[optidx].name, opts, &op)) + return optn_silenterr; - if (iflags.use_menu_color) - for (tmpmc = menu_colorings; tmpmc; tmpmc = tmpmc->next) - if (regex_match(str, tmpmc->match)) { - *color = tmpmc->color; - *attr = tmpmc->attr; - return TRUE; + if (*op != '!') { + if ((flags.initgend = str2gend(op)) == ROLE_NONE) { + config_error_add("Unknown %s '%s'", allopt[optidx].name, op); + return optn_err; } - return FALSE; + flags.female = flags.initgend; + saveoptstr(optidx, rolestring(flags.initgend, genders, adj)); + } + return optn_ok; + } + if (req == get_val) { + Sprintf(opts, "%s", rolestring(flags.initgend, genders, adj)); + return optn_ok; + } + if (req == get_cnf_val) { + op = get_cnf_role_opt(optidx); + Strcpy(opts, op ? op : "none"); + return optn_ok; + } + return optn_ok; } -void -free_menu_coloring() +staticfn int +optfn_glyph( + int optidx UNUSED, int req, boolean negated, + char *opts, char *op) { - struct menucoloring *tmp, *tmp2; + int glyph; - for (tmp = menu_colorings; tmp; tmp = tmp2) { - tmp2 = tmp->next; - regex_free(tmp->match); - free((genericptr_t) tmp->origstr); - free((genericptr_t) tmp); + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* OPTION=glyph:G_glyph/U+NNNN/r-g-b */ + if (negated) { + if (op != empty_optstr) { + bad_negation("glyph", TRUE); + return optn_err; + } + } + if (op == empty_optstr) + return optn_err; + /* strip leading/trailing spaces, condense internal ones (3.6.2) */ + mungspaces(op); + if (!glyphrep_to_custom_map_entries(op, &glyph)) + return optn_err; + return optn_ok; } + if (req == get_val) { + Sprintf(opts, "%s", to_be_done); + return optn_ok; + } + if (req == get_cnf_val) { + opts[0] = '\0'; + return optn_ok; + } + return optn_ok; } -STATIC_OVL void -free_one_menu_coloring(idx) -int idx; /* 0 .. */ +staticfn int +optfn_hilite_status( + int optidx UNUSED, + int req, + boolean negated, + char *opts, + char *op) { - struct menucoloring *tmp = menu_colorings; - struct menucoloring *prev = NULL; - - while (tmp) { - if (idx == 0) { - struct menucoloring *next = tmp->next; - - regex_free(tmp->match); - free((genericptr_t) tmp->origstr); - free((genericptr_t) tmp); - if (prev) - prev->next = next; - else - menu_colorings = next; - return; + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* hilite fields in status prompt */ +#ifdef STATUS_HILITES + op = string_for_opt(opts, TRUE); + if (op != empty_optstr && negated) { + clear_status_hilites(); + return optn_ok; + } else if (op == empty_optstr) { + config_error_add("Value is mandatory for hilite_status"); + return optn_err; } - idx--; - prev = tmp; - tmp = tmp->next; + if (!parse_status_hl1(op, go.opt_from_file)) + return optn_err; + return optn_ok; +#else + nhUse(negated); + nhUse(op); + config_error_add("'%s' is not supported", allopt[optidx].name); + return optn_err; +#endif + } + if (req == get_val || req == get_cnf_val) { + opts[0] = '\0'; +#ifdef STATUS_HILITES + if (req == get_val) + Strcpy(opts, count_status_hilites() + ? "(see \"status highlight rules\" below)" + : "(none)"); +#endif + return optn_ok; } + return optn_ok; } -STATIC_OVL int -count_menucolors(VOID_ARGS) +staticfn int +optfn_horsename( + int optidx, + int req, boolean negated, + char *opts, char *op) { - struct menucoloring *tmp; - int count = 0; - - for (tmp = menu_colorings; tmp; tmp = tmp->next) - count++; - return count; + return petname_optfn(optidx, req, negated, opts, op); } -STATIC_OVL boolean -parse_role_opts(negated, fullname, opts, opp) -boolean negated; -const char *fullname; -char *opts; -char **opp; +staticfn int +optfn_IBMgraphics( + int optidx, int req, boolean negated, + char *opts, char *op UNUSED) { - char *op = *opp; +#ifdef BACKWARD_COMPAT + const char *sym_name = allopt[optidx].name; + boolean badflag = FALSE; + int i; +#endif - if (negated) { - bad_negation(fullname, FALSE); - } else if ((op = string_for_env_opt(fullname, opts, FALSE)) - != empty_optstr) { - boolean val_negated = FALSE; + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* "IBMgraphics" */ - while ((*op == '!') || !strncmpi(op, "no", 2)) { - if (*op == '!') - op++; - else - op += 2; - val_negated = !val_negated; - } - if (val_negated) { - if (!setrolefilter(op)) { - config_error_add("Unknown negated parameter '%s'", op); - return FALSE; +#ifdef BACKWARD_COMPAT + + if (!negated) { + for (i = 0; i < NUM_GRAPHICS; ++i) { + if (gs.symset[i].name) { + badflag = TRUE; + } else { + if (i == ROGUESET) + sym_name = "RogueIBM"; + gs.symset[i].name = dupstr(sym_name); + if (!read_sym_file(i)) { + badflag = TRUE; + clear_symsetentry(i, TRUE); + break; + } + } + } + if (badflag) { + config_error_add("Failure to load symbol set %s.", sym_name); + return optn_err; + } else { + switch_symbols(TRUE); + if (!go.opt_initial && Is_rogue_level(&u.uz)) + assign_graphics(ROGUESET); } - } else { - if (duplicate_opt_detection(opts, 1)) - complain_about_duplicate(opts, 1); - *opp = op; - return TRUE; } + return optn_ok; +#else + config_error_add("'%s' no longer supported; use 'symset:%s' instead", + allopt[optidx].name, allopt[optidx].name); + return optn_err; +#endif } - return FALSE; -} - -/* Check if character c is illegal as a menu command key */ -boolean -illegal_menu_cmd_key(c) -char c; -{ - if (c == 0 || c == '\r' || c == '\n' || c == '\033' - || c == ' ' || digit(c) || (letter(c) && c != '@')) { - config_error_add("Reserved menu command key '%s'", visctrl(c)); - return TRUE; - } else { /* reject default object class symbols */ - int j; - for (j = 1; j < MAXOCLASSES; j++) - if (c == def_oc_syms[j].sym) { - config_error_add("Menu command key '%s' is an object class", - visctrl(c)); - return TRUE; - } + if (req == get_val || req == get_cnf_val) { + opts[0] = '\0'; + return optn_ok; } - return FALSE; + return optn_ok; } -boolean -parseoptions(opts, tinitial, tfrom_file) -register char *opts; -boolean tinitial, tfrom_file; +staticfn int +optfn_map_mode( + int optidx, int req, boolean negated, + char *opts, char *op) { - char *op; - unsigned num; - boolean negated, duplicate; int i; - const char *fullname; - boolean retval = TRUE; - initial = tinitial; - from_file = tfrom_file; - if ((op = index(opts, ',')) != 0) { - *op++ = 0; - if (!parseoptions(op, initial, from_file)) - retval = FALSE; + if (req == do_init) { + return optn_ok; } - if (strlen(opts) > BUFSZ / 2) { - config_error_add("Option too long, max length is %i characters", - (BUFSZ / 2)); - return FALSE; + if (req == do_set) { + /* WINCAP + * + * map_mode:[tiles|ascii4x6|ascii6x8|ascii8x8|ascii16x8|ascii7x12 + * |ascii8x12|ascii16x12|ascii12x16|ascii10x18|fit_to_screen + * |ascii_fit_to_screen|tiles_fit_to_screen] + */ + op = string_for_opt(opts, negated); + if (op != empty_optstr && !negated) { + int save_map_mode = iflags.wc_map_mode; + + if (!strcmpi(op, "tiles")) + iflags.wc_map_mode = MAP_MODE_TILES; + else if (!strncmpi(op, "ascii4x6", sizeof "ascii4x6" - 1)) + iflags.wc_map_mode = MAP_MODE_ASCII4x6; + else if (!strncmpi(op, "ascii6x8", sizeof "ascii6x8" - 1)) + iflags.wc_map_mode = MAP_MODE_ASCII6x8; + else if (!strncmpi(op, "ascii8x8", sizeof "ascii8x8" - 1)) + iflags.wc_map_mode = MAP_MODE_ASCII8x8; + else if (!strncmpi(op, "ascii16x8", sizeof "ascii16x8" - 1)) + iflags.wc_map_mode = MAP_MODE_ASCII16x8; + else if (!strncmpi(op, "ascii7x12", sizeof "ascii7x12" - 1)) + iflags.wc_map_mode = MAP_MODE_ASCII7x12; + else if (!strncmpi(op, "ascii8x12", sizeof "ascii8x12" - 1)) + iflags.wc_map_mode = MAP_MODE_ASCII8x12; + else if (!strncmpi(op, "ascii16x12", sizeof "ascii16x12" - 1)) + iflags.wc_map_mode = MAP_MODE_ASCII16x12; + else if (!strncmpi(op, "ascii12x16", sizeof "ascii12x16" - 1)) + iflags.wc_map_mode = MAP_MODE_ASCII12x16; + else if (!strncmpi(op, "ascii10x18", sizeof "ascii10x18" - 1)) + iflags.wc_map_mode = MAP_MODE_ASCII10x18; + else if (!strncmpi(op, "fit_to_screen", + sizeof "fit_to_screen" - 1)) + iflags.wc_map_mode = MAP_MODE_ASCII_FIT_TO_SCREEN; + else if (!strncmpi(op, "ascii_fit_to_screen", + sizeof "ascii_fit_to_screen" - 1)) + iflags.wc_map_mode = MAP_MODE_ASCII_FIT_TO_SCREEN; + else if (!strncmpi(op, "tiles_fit_to_screen", + sizeof "tiles_fit_to_screen" - 1)) + iflags.wc_map_mode = MAP_MODE_TILES_FIT_TO_SCREEN; + else { + config_error_add("Unknown %s parameter '%s'", + allopt[optidx].name, op); + return optn_err; + } + if (wc_supported("map_mode")) { + if (!iflags.wc_map_mode + || save_map_mode != iflags.wc_map_mode) + preference_update("map_mode"); + } + } else if (negated) { + bad_negation(allopt[optidx].name, TRUE); + return optn_err; + } + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + i = iflags.wc_map_mode; + Sprintf(opts, "%s", + (i == MAP_MODE_TILES) ? "tiles" + : (i == MAP_MODE_ASCII4x6) ? "ascii4x6" + : (i == MAP_MODE_ASCII6x8) ? "ascii6x8" + : (i == MAP_MODE_ASCII8x8) ? "ascii8x8" + : (i == MAP_MODE_ASCII16x8) ? "ascii16x8" + : (i == MAP_MODE_ASCII7x12) ? "ascii7x12" + : (i == MAP_MODE_ASCII8x12) ? "ascii8x12" + : (i == MAP_MODE_ASCII16x12) ? "ascii16x12" + : (i == MAP_MODE_ASCII12x16) ? "ascii12x16" + : (i == MAP_MODE_ASCII10x18) ? "ascii10x18" + : (i == MAP_MODE_ASCII_FIT_TO_SCREEN) + ? "fit_to_screen" + : defopt); + return optn_ok; } + return optn_ok; +} - /* strip leading and trailing white space */ - while (isspace((uchar) *opts)) - opts++; - op = eos(opts); - while (--op >= opts && isspace((uchar) *op)) - *op = '\0'; +/* all the key assignment options for menu_* commands are identical + but optlist.h treats them as distinct rather than sharing one */ +staticfn int +shared_menu_optfn(int optidx UNUSED, int req, boolean negated, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + int res = check_misc_menu_command(opts, op); - if (!*opts) { - config_error_add("Empty statement"); - return FALSE; + if (res < 0) + return optn_err; + return spcfn_misc_menu_cmd(res, req, negated, opts, op); } - negated = FALSE; - while ((*opts == '!') || !strncmpi(opts, "no", 2)) { - if (*opts == '!') - opts++; - else - opts += 2; - negated = !negated; + if (req == get_val) { + Sprintf(opts, "%s", to_be_done); + return optn_ok; } + if (req == get_cnf_val) { + opts[0] = '\0'; + return optn_ok; + } + return optn_ok; +} - /* variant spelling */ +staticfn int +optfn_menu_deselect_all( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + return shared_menu_optfn(optidx, req, negated, opts, op); +} - if (match_optname(opts, "colour", 5, FALSE)) - Strcpy(opts, "color"); /* fortunately this isn't longer */ +staticfn int +optfn_menu_deselect_page( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + return shared_menu_optfn(optidx, req, negated, opts, op); +} - /* special boolean options */ +staticfn int +optfn_menu_first_page( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + return shared_menu_optfn(optidx, req, negated, opts, op); +} - if (match_optname(opts, "female", 3, FALSE)) { - if (duplicate_opt_detection(opts, 0)) - complain_about_duplicate(opts, 0); - if (!initial && flags.female == negated) { - config_error_add("That is not anatomically possible."); - return FALSE; - } else - flags.initgend = flags.female = !negated; - return retval; - } +staticfn int +optfn_menu_invert_all( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + return shared_menu_optfn(optidx, req, negated, opts, op); +} - if (match_optname(opts, "male", 4, FALSE)) { - if (duplicate_opt_detection(opts, 0)) - complain_about_duplicate(opts, 0); - if (!initial && flags.female != negated) { - config_error_add("That is not anatomically possible."); - return FALSE; - } else - flags.initgend = flags.female = negated; - return retval; - } +staticfn int +optfn_menu_invert_page( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + return shared_menu_optfn(optidx, req, negated, opts, op); +} -#if defined(MICRO) && !defined(AMIGA) - /* included for compatibility with old NetHack.cnf files */ - if (match_optname(opts, "IBM_", 4, FALSE)) { - iflags.BIOS = !negated; - return retval; - } -#endif /* MICRO */ +staticfn int +optfn_menu_last_page( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + return shared_menu_optfn(optidx, req, negated, opts, op); +} - /* compound options */ +staticfn int +optfn_menu_next_page( + int optidx , int req, boolean negated, + char *opts, char *op) +{ + return shared_menu_optfn(optidx, req, negated, opts, op); +} - /* This first batch can be duplicated if their values are negated */ +staticfn int +optfn_menu_previous_page( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + return shared_menu_optfn(optidx, req, negated, opts, op); +} - /* align:string */ - fullname = "align"; - if (match_optname(opts, fullname, sizeof "align" - 1, TRUE)) { - if (parse_role_opts(negated, fullname, opts, &op)) { - if ((flags.initalign = str2align(op)) == ROLE_NONE) { - config_error_add("Unknown %s '%s'", fullname, op); - return FALSE; - } - } else - return FALSE; - return retval; - } +staticfn int +optfn_menu_search( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + return shared_menu_optfn(optidx, req, negated, opts, op); +} - /* role:string or character:string */ - fullname = "role"; - if (match_optname(opts, fullname, 4, TRUE) - || match_optname(opts, (fullname = "character"), 4, TRUE)) { - if (parse_role_opts(negated, fullname, opts, &op)) { - if ((flags.initrole = str2role(op)) == ROLE_NONE) { - config_error_add("Unknown %s '%s'", fullname, op); - return FALSE; - } else /* Backwards compatibility */ - nmcpy(pl_character, op, PL_NSIZ); - } else - return FALSE; - return retval; - } +staticfn int +optfn_menu_select_all( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + return shared_menu_optfn(optidx, req, negated, opts, op); +} - /* race:string */ - fullname = "race"; - if (match_optname(opts, fullname, 4, TRUE)) { - if (parse_role_opts(negated, fullname, opts, &op)) { - if ((flags.initrace = str2race(op)) == ROLE_NONE) { - config_error_add("Unknown %s '%s'", fullname, op); - return FALSE; - } else /* Backwards compatibility */ - pl_race = *op; - } else - return FALSE; - return retval; - } +staticfn int +optfn_menu_select_page( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + return shared_menu_optfn(optidx, req, negated, opts, op); +} - /* gender:string */ - fullname = "gender"; - if (match_optname(opts, fullname, 4, TRUE)) { - if (parse_role_opts(negated, fullname, opts, &op)) { - if ((flags.initgend = str2gend(op)) == ROLE_NONE) { - config_error_add("Unknown %s '%s'", fullname, op); - return FALSE; - } else - flags.female = flags.initgend; - } else - return FALSE; - return retval; - } +staticfn int +optfn_menu_shift_left( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + return shared_menu_optfn(optidx, req, negated, opts, op); +} - /* We always check for duplicates on the remaining compound options, - although individual option processing can choose to complain or not */ +staticfn int +optfn_menu_shift_right( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + return shared_menu_optfn(optidx, req, negated, opts, op); +} - duplicate = duplicate_opt_detection(opts, 1); /* 1: check compounds */ +/* end of shared key assignments for menu commands */ - fullname = "pettype"; - if (match_optname(opts, fullname, 3, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - if ((op = string_for_env_opt(fullname, opts, negated)) - != empty_optstr) { - if (negated) { - bad_negation(fullname, TRUE); - return FALSE; - } else - switch (lowc(*op)) { - case 'd': /* dog */ - preferred_pet = 'd'; - break; - case 'c': /* cat */ - case 'f': /* feline */ - preferred_pet = 'c'; - break; - case 'h': /* horse */ - case 'q': /* quadruped */ - /* avoids giving "unrecognized type of pet" but - pet_type(dog.c) won't actually honor this */ - preferred_pet = 'h'; - break; - case 'n': /* no pet */ - preferred_pet = 'n'; - break; - case '*': /* random */ - preferred_pet = '\0'; - break; - default: - config_error_add("Unrecognized pet type '%s'.", op); - return FALSE; - break; - } - } else if (negated) - preferred_pet = 'n'; - return retval; +staticfn int +optfn_menu_headings( + int optidx, + int req, boolean negated, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; } + if (req == do_set) { + color_attr ca; - fullname = "catname"; - if (match_optname(opts, fullname, 3, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - if (negated) { - bad_negation(fullname, FALSE); - return FALSE; - } else if ((op = string_for_env_opt(fullname, opts, FALSE)) - != empty_optstr) { - nmcpy(catname, op, PL_PSIZ); - } else - return FALSE; - sanitize_name(catname); - return retval; + if (op == empty_optstr) { + /* OPTIONS=menu_headings w/o value => no-color&inverse; + OPTIONS=!menu_headings => no-color&none */ + iflags.menu_headings.attr = negated ? ATR_NONE : ATR_INVERSE; + iflags.menu_headings.color = NO_COLOR; + return optn_ok; + } else if (negated) { /* 'op != empty_optstr' to get here */ + bad_negation(allopt[optidx].name, TRUE); + return optn_silenterr; + } + if (!color_attr_parse_str(&ca, op)) + return optn_err; + iflags.menu_headings = ca; + return optn_ok; } + if (req == get_val || req == get_cnf_val) { + char ca_buf[BUFSZ]; - fullname = "dogname"; - if (match_optname(opts, fullname, 3, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - if (negated) { - bad_negation(fullname, FALSE); - return FALSE; - } else if ((op = string_for_env_opt(fullname, opts, FALSE)) - != empty_optstr) { - nmcpy(dogname, op, PL_PSIZ); - } else - return FALSE; - sanitize_name(dogname); - return retval; + Strcpy(ca_buf, color_attr_to_str(&iflags.menu_headings)); + /* change "no color" to "no-color" or "light blue" to "light-blue" */ + (void) strNsubst(ca_buf, " ", "-", 0); + Strcpy(opts, ca_buf); + return optn_ok; } - - fullname = "horsename"; - if (match_optname(opts, fullname, 5, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - if (negated) { - bad_negation(fullname, FALSE); - return FALSE; - } else if ((op = string_for_env_opt(fullname, opts, FALSE)) - != empty_optstr) { - nmcpy(horsename, op, PL_PSIZ); - } else - return FALSE; - sanitize_name(horsename); - return retval; + if (req == do_handler) { + return handler_menu_headings(); } + return optn_ok; +} - fullname = "mouse_support"; - if (match_optname(opts, fullname, 13, TRUE)) { - boolean compat = (strlen(opts) <= 13); +staticfn int +optfn_menu_objsyms( + int optidx, int req, + boolean negated, + char *opts, char *op) +{ + if (req == do_init) { + /* set iflags.menu_objsyms to 4, "conditional"; also sets + iflags.menu_head_objsym to False and + iflags.use_menu_glyphs True */ + set_menuobjsyms_flags(4); + return optn_ok; + } + if (req == do_set) { + unsigned k, l; + int i, osyms; - if (duplicate) - complain_about_duplicate(opts, 1); - op = string_for_opt(opts, (compat || !initial)); - if (op == empty_optstr) { - if (compat || negated || initial) { - /* for backwards compatibility, "mouse_support" without a - value is a synonym for mouse_support:1 */ - iflags.wc_mouse_support = !negated; + if (negated) { + /* allow '!menu_objsyms' (and '!use_menu_glyphs') as + 'menu_objsyms:none' (0) */ + osyms = 0; + } else if (op == empty_optstr) { + /* treat boolean 'menu_objsyms' as 'menu_objsyms:headers' (1) + accept obsolete boolean 'use_menu_glyphs' as a synonym + for 'menu_objsyms:entries' (2) */ + osyms = !strncmp(opts, "use_menu_glyphs", 15) ? 2 : 1; + } else if (digit(*op)) { + i = atoi(op); + if (i >= SIZE(objsymvals)) { + config_error_add("Illegal %s parameter '%s'", + allopt[optidx].name, op); + return optn_err; } - } else if (negated) { - bad_negation(fullname, TRUE); - return FALSE; + osyms = i; } else { + /* stilted "one-or-other" is used to compress the menu width */ + static const char alt5[] = "one-or-the-other"; + unsigned l5 = (unsigned) (sizeof alt5 - sizeof ""); + + osyms = 0; + k = (unsigned) strlen(op); + for (i = 0; i < SIZE(objsymvals); ++i) { + l = (unsigned) strlen(objsymvals[i].nam); + if (k >= 4) + l = k; + if (!strncmpi(objsymvals[i].nam, op, l) + || (i == 5 && !strncmpi(alt5, op, l5))) { + osyms = i; + break; + } + } + } + set_menuobjsyms_flags(osyms); + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + Sprintf(opts, "%s", objsymvals[iflags.menuobjsyms].nam); + return optn_ok; + } + if (req == do_handler) { + return handler_menu_objsyms(); + } + return optn_ok; +} + +staticfn int +optfn_menuinvertmode( + int optidx, int req, + boolean negated UNUSED, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* menuinvertmode=0 or 1 or 2 (2 is experimental) */ + if (op != empty_optstr) { int mode = atoi(op); - if (mode < 0 || mode > 2 || (mode == 0 && *op != '0')) { - config_error_add("Illegal %s parameter '%s'", fullname, op); - return FALSE; - } else { /* mode >= 0 */ - iflags.wc_mouse_support = mode; + if (mode < 0 || mode > 2) { + config_error_add("Illegal %s parameter '%s'", + allopt[optidx].name, op); + return optn_err; } + iflags.menuinvertmode = mode; } - return retval; + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + Sprintf(opts, "%d", iflags.menuinvertmode); + return optn_ok; } + return optn_ok; +} - fullname = "number_pad"; - if (match_optname(opts, fullname, 10, TRUE)) { - boolean compat = (strlen(opts) <= 10); +staticfn int +optfn_menustyle( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + int tmp; + boolean val_required; /* no initializer based on opts because this can be + called with init and invalid opts and op */ - if (duplicate) - complain_about_duplicate(opts, 1); - op = string_for_opt(opts, (compat || !initial)); - if (op == empty_optstr) { - if (compat || negated || initial) { - /* for backwards compatibility, "number_pad" without a - value is a synonym for number_pad:1 */ - iflags.num_pad = !negated; - iflags.num_pad_mode = 0; - } - } else if (negated) { - bad_negation(fullname, TRUE); - return FALSE; - } else { - int mode = atoi(op); + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* menustyle:traditional or combination or full or partial */ - if (mode < -1 || mode > 4 || (mode == 0 && *op != '0')) { - config_error_add("Illegal %s parameter '%s'", fullname, op); - return FALSE; - } else if (mode <= 0) { - iflags.num_pad = FALSE; - /* German keyboard; y and z keys swapped */ - iflags.num_pad_mode = (mode < 0); /* 0 or 1 */ - } else { /* mode > 0 */ - iflags.num_pad = TRUE; - iflags.num_pad_mode = 0; - /* PC Hack / MSDOS compatibility */ - if (mode == 2 || mode == 4) - iflags.num_pad_mode |= 1; - /* phone keypad layout */ - if (mode == 3 || mode == 4) - iflags.num_pad_mode |= 2; - } + val_required = (strlen(opts) > 5 && !negated); + if ((op = string_for_opt(opts, !val_required)) == empty_optstr) { + if (val_required) + return optn_err; /* string_for_opt gave feedback */ + tmp = negated ? 'n' : 'f'; + } else { + tmp = lowc(*op); } - reset_commands(FALSE); - number_pad(iflags.num_pad ? 1 : 0); - return retval; + switch (tmp) { + case 'n': /* none */ + case 't': /* traditional: prompt for class(es) by symbol, + prompt for each item within class(es) one at a time */ + flags.menu_style = MENU_TRADITIONAL; + break; + case 'c': /* combination: prompt for class(es) by symbol, + choose items within selected class(es) by menu */ + flags.menu_style = MENU_COMBINATION; + break; + case 'f': /* full: choose class(es) by first menu, + choose items within selected class(es) by second menu */ + flags.menu_style = MENU_FULL; + break; + case 'p': /* partial: skip class filtering, choose items among all + classes by menu */ + flags.menu_style = MENU_PARTIAL; + break; + default: + config_error_add("Unknown %s parameter '%s'", allopt[optidx].name, + op); + return optn_err; + } + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + Sprintf(opts, "%s", menutype[(int) flags.menu_style][0]); + return optn_ok; } + if (req == do_handler) { + return handler_menustyle(); + } + return optn_ok; +} - fullname = "roguesymset"; - if (match_optname(opts, fullname, 7, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - if (negated) { - bad_negation(fullname, FALSE); - return FALSE; - } else if ((op = string_for_opt(opts, FALSE)) != empty_optstr) { - symset[ROGUESET].name = dupstr(op); - if (!read_sym_file(ROGUESET)) { - clear_symsetentry(ROGUESET, TRUE); - config_error_add( - "Unable to load symbol set \"%s\" from \"%s\"", - op, SYMBOLS); - return FALSE; - } else { - if (!initial && Is_rogue_level(&u.uz)) - assign_graphics(ROGUESET); - need_redraw = TRUE; - } - } else - return FALSE; - return retval; +staticfn int +optfn_monsters( + int optidx UNUSED, int req, boolean negated UNUSED, + char *opts, char *op UNUSED) +{ + if (req == do_init) { + return optn_ok; } + if (req == do_set) { + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + opts[0] = '\0'; + return optn_ok; + } + return optn_ok; +} - fullname = "symset"; - if (match_optname(opts, fullname, 6, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - if (negated) { - bad_negation(fullname, FALSE); - return FALSE; - } else if ((op = string_for_opt(opts, FALSE)) != empty_optstr) { - symset[PRIMARY].name = dupstr(op); - if (!read_sym_file(PRIMARY)) { - clear_symsetentry(PRIMARY, TRUE); - config_error_add( - "Unable to load symbol set \"%s\" from \"%s\"", - op, SYMBOLS); - return FALSE; - } else { - switch_symbols(symset[PRIMARY].name != (char *) 0); - need_redraw = TRUE; - } - } else - return FALSE; - return retval; +staticfn int +optfn_mouse_support( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + boolean compat; + + if (req == do_init) { + return optn_ok; } + if (req == do_set) { + compat = (strlen(opts) <= 13); + op = string_for_opt(opts, (compat || !go.opt_initial)); + if (op == empty_optstr) { + if (compat || negated || go.opt_initial) { + /* for backwards compatibility, "mouse_support" without a + value is a synonym for mouse_support:1 */ + iflags.wc_mouse_support = !negated; + } + } else { + int mode = atoi(op); - fullname = "runmode"; - if (match_optname(opts, fullname, 4, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - if (negated) { - flags.runmode = RUN_TPORT; - } else if ((op = string_for_opt(opts, FALSE)) != empty_optstr) { - if (!strncmpi(op, "teleport", strlen(op))) - flags.runmode = RUN_TPORT; - else if (!strncmpi(op, "run", strlen(op))) - flags.runmode = RUN_LEAP; - else if (!strncmpi(op, "walk", strlen(op))) - flags.runmode = RUN_STEP; - else if (!strncmpi(op, "crawl", strlen(op))) - flags.runmode = RUN_CRAWL; - else { - config_error_add("Unknown %s parameter '%s'", fullname, op); - return FALSE; + if (mode < 0 || mode > 2 || (mode == 0 && *op != '0')) { + config_error_add("Illegal %s parameter '%s'", + allopt[optidx].name, op); + return optn_err; + } else { /* mode >= 0 */ + iflags.wc_mouse_support = mode; } - } else - return FALSE; - return retval; + } + return optn_ok; } + if (req == get_val) { +#ifdef WIN32 +#define MOUSEFIX1 ", QuickEdit off" +#define MOUSEFIX2 ", QuickEdit unchanged" +#else +#define MOUSEFIX1 ", O/S adjusted" +#define MOUSEFIX2 ", O/S unchanged" +#endif + static const char *const mousemodes[][2] = { + { "0=off", "" }, + { "1=on", MOUSEFIX1 }, + { "2=on", MOUSEFIX2 }, + }; +#undef MOUSEFIX1 +#undef MOUSEFIX2 + int ms = iflags.wc_mouse_support; - /* menucolor:"regex_string"=color */ - fullname = "menucolor"; - if (match_optname(opts, fullname, 9, TRUE)) { - if (negated) { - bad_negation(fullname, FALSE); - return FALSE; - } else if ((op = string_for_env_opt(fullname, opts, FALSE)) - != empty_optstr) { - if (!add_menu_coloring(op)) - return FALSE; - } else - return FALSE; - return retval; + if (ms >= 0 && ms <= 2) + Sprintf(opts, "%s%s", mousemodes[ms][0], mousemodes[ms][1]); + return optn_ok; } - - fullname = "msghistory"; - if (match_optname(opts, fullname, 3, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - op = string_for_env_opt(fullname, opts, negated); - if ((negated && op == empty_optstr) - || (!negated && op != empty_optstr)) { - iflags.msg_history = negated ? 0 : atoi(op); - } else if (negated) { - bad_negation(fullname, TRUE); - return FALSE; - } - return retval; + if (req == get_cnf_val) { + Sprintf(opts, "%i", iflags.wc_mouse_support); + return optn_ok; } + return optn_ok; +} - fullname = "msg_window"; - /* msg_window:single, combo, full or reversed */ - if (match_optname(opts, fullname, 4, TRUE)) { -/* allow option to be silently ignored by non-tty ports */ -#ifdef TTY_GRAPHICS - int tmp; +staticfn int +optfn_msg_window( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + int retval = optn_ok; +#if PREV_MSGS + int tmp; +#else + nhUse(optidx); + nhUse(negated); + nhUse(op); +#endif - if (duplicate) - complain_about_duplicate(opts, 1); - if ((op = string_for_opt(opts, TRUE)) == empty_optstr) { + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* msg_window:single, combo, full or reversed */ + + /* allow option to be silently ignored by non-tty ports */ +#if PREV_MSGS + if (op == empty_optstr) { tmp = negated ? 's' : 'f'; } else { if (negated) { - bad_negation(fullname, TRUE); - return FALSE; + bad_negation(allopt[optidx].name, TRUE); + return optn_err; } tmp = lowc(*op); } switch (tmp) { case 's': /* single message history cycle (default if negated) */ - iflags.prevmsg_window = 's'; - break; - case 'c': /* combination: two singles, then full page */ - iflags.prevmsg_window = 'c'; - break; + case 'c': /* combination: first two as singles, then full page */ case 'f': /* full page (default if specified without argument) */ - iflags.prevmsg_window = 'f'; - break; - case 'r': /* full page (reversed) */ - iflags.prevmsg_window = 'r'; + case 'r': /* full page in reverse order (LIFO; default for curses) */ + iflags.prevmsg_window = (char) tmp; break; default: - config_error_add("Unknown %s parameter '%s'", fullname, op); - retval = FALSE; + config_error_add("Unknown %s parameter '%s'", allopt[optidx].name, + op); + retval = optn_err; } #endif return retval; } - - /* WINCAP - * setting font options */ - fullname = "font"; - if (!strncmpi(opts, fullname, 4)) { - int opttype = -1; - char *fontopts = opts + 4; - - if (!strncmpi(fontopts, "map", 3) || !strncmpi(fontopts, "_map", 4)) - opttype = MAP_OPTION; - else if (!strncmpi(fontopts, "message", 7) - || !strncmpi(fontopts, "_message", 8)) - opttype = MESSAGE_OPTION; - else if (!strncmpi(fontopts, "text", 4) - || !strncmpi(fontopts, "_text", 5)) - opttype = TEXT_OPTION; - else if (!strncmpi(fontopts, "menu", 4) - || !strncmpi(fontopts, "_menu", 5)) - opttype = MENU_OPTION; - else if (!strncmpi(fontopts, "status", 6) - || !strncmpi(fontopts, "_status", 7)) - opttype = STATUS_OPTION; - else if (!strncmpi(fontopts, "_size", 5)) { - if (!strncmpi(fontopts, "_size_map", 8)) - opttype = MAP_OPTION; - else if (!strncmpi(fontopts, "_size_message", 12)) - opttype = MESSAGE_OPTION; - else if (!strncmpi(fontopts, "_size_text", 9)) - opttype = TEXT_OPTION; - else if (!strncmpi(fontopts, "_size_menu", 9)) - opttype = MENU_OPTION; - else if (!strncmpi(fontopts, "_size_status", 11)) - opttype = STATUS_OPTION; - else { - config_error_add("Unknown %s parameter '%s'", fullname, opts); - return FALSE; - } - if (duplicate) - complain_about_duplicate(opts, 1); - if (opttype > 0 && !negated - && (op = string_for_opt(opts, FALSE)) != empty_optstr) { - switch (opttype) { - case MAP_OPTION: - iflags.wc_fontsiz_map = atoi(op); - break; - case MESSAGE_OPTION: - iflags.wc_fontsiz_message = atoi(op); - break; - case TEXT_OPTION: - iflags.wc_fontsiz_text = atoi(op); - break; - case MENU_OPTION: - iflags.wc_fontsiz_menu = atoi(op); - break; - case STATUS_OPTION: - iflags.wc_fontsiz_status = atoi(op); - break; - } - } - return retval; - } else { - config_error_add("Unknown %s parameter '%s'", fullname, opts); - return FALSE; + if (req == get_val || req == get_cnf_val) { + opts[0] = '\0'; +#if PREV_MSGS + tmp = iflags.prevmsg_window; + if (WINDOWPORT(curses)) { + if (tmp == 's' || tmp == 'c') + tmp = iflags.prevmsg_window = 'r'; } - if (opttype > 0 - && (op = string_for_opt(opts, FALSE)) != empty_optstr) { - wc_set_font_name(opttype, op); -#ifdef MAC - set_font_name(opttype, op); + Sprintf(opts, "%s", (tmp == 's') ? "single" + : (tmp == 'c') ? "combination" + : (tmp == 'f') ? "full" + : "reversed"); #endif - return retval; + return optn_ok; + } + if (req == do_handler) { + return handler_msg_window(); + } + return optn_ok; +} + +staticfn int +optfn_msghistory( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + op = string_for_env_opt(allopt[optidx].name, opts, negated); + if ((negated && op == empty_optstr) + || (!negated && op != empty_optstr)) { + iflags.msg_history = negated ? 0 : atoi(op); } else if (negated) { - bad_negation(fullname, TRUE); - return FALSE; + bad_negation(allopt[optidx].name, TRUE); + return optn_err; } - return retval; + return optn_ok; } + if (req == get_val || req == get_cnf_val) { + Sprintf(opts, "%u", iflags.msg_history); + return optn_ok; + } + return optn_ok; +} -#ifdef CHANGE_COLOR - if (match_optname(opts, "palette", 3, TRUE) -#ifdef MAC - || match_optname(opts, "hicolor", 3, TRUE) -#endif - ) { - int color_number, color_incr; +staticfn int +optfn_name( + int optidx, int req, boolean negated UNUSED, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* name:string */ -#ifndef WIN32 - if (duplicate) - complain_about_duplicate(opts, 1); -#endif -#ifdef MAC - if (match_optname(opts, "hicolor", 3, TRUE)) { - if (negated) { - bad_negation("hicolor", FALSE); - return FALSE; - } - color_number = CLR_MAX + 4; /* HARDCODED inverse number */ - color_incr = -1; + if ((op = string_for_env_opt(allopt[optidx].name, opts, FALSE)) + != empty_optstr) { + nmcpy(svp.plname, op, PL_NSIZ); } else -#endif - { - if (negated) { - bad_negation("palette", FALSE); - return FALSE; + return optn_err; + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + Sprintf(opts, "%s", svp.plname); + return optn_ok; + } + return optn_ok; +} + +staticfn int +optfn_number_pad( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + boolean compat; + + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + compat = (strlen(opts) <= 10); + op = string_for_opt(opts, (compat || !go.opt_initial)); + if (op == empty_optstr) { + if (compat || negated || go.opt_initial) { + /* for backwards compatibility, "number_pad" without a + value is a synonym for number_pad:1 */ + iflags.num_pad = !negated; + iflags.num_pad_mode = 0; + } + } else if (negated) { + bad_negation(allopt[optidx].name, TRUE); + return optn_err; + } else { + int mode = atoi(op); + + if (mode < -1 || mode > 4 || (mode == 0 && *op != '0')) { + config_error_add("Illegal %s parameter '%s'", + allopt[optidx].name, op); + return optn_err; + } else if (mode <= 0) { + iflags.num_pad = FALSE; + /* German keyboard; y and z keys swapped */ + iflags.num_pad_mode = (mode < 0); /* 0 or 1 */ + } else { /* mode > 0 */ + iflags.num_pad = TRUE; + iflags.num_pad_mode = 0; + /* PC Hack / MSDOS compatibility */ + if (mode == 2 || mode == 4) + iflags.num_pad_mode |= 1; + /* phone keypad layout */ + if (mode == 3 || mode == 4) + iflags.num_pad_mode |= 2; } - color_number = 0; - color_incr = 1; } -#ifdef WIN32 - op = string_for_opt(opts, TRUE); - if (op == empty_optstr || !alternative_palette(op)) { - config_error_add("Error in palette parameter '%s'", op); - return FALSE; + reset_commands(FALSE); + number_pad(iflags.num_pad ? 1 : 0); + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + static const char *const numpadmodes[] = { + "0=off", "1=on", "2=on, MSDOS compatible", + "3=on, phone-style layout", + "4=on, phone layout, MSDOS compatible", + "-1=off, y & z swapped", /*[5]*/ + }; + int indx = gc.Cmd.num_pad + ? (gc.Cmd.phone_layout ? (gc.Cmd.pcHack_compat ? 4 : 3) + : (gc.Cmd.pcHack_compat ? 2 : 1)) + : gc.Cmd.swap_yz ? 5 : 0; + + if (req == get_val) + Strcpy(opts, numpadmodes[indx]); + else { + Sprintf(opts, "%i", (indx == 5) ? -1 : indx); } -#else - if ((op = string_for_opt(opts, FALSE)) != empty_optstr) { - char *pt = op; - int cnt, tmp, reverse; - long rgb; - + return optn_ok; + } + if (req == do_handler) { + return handler_number_pad(); + } + return optn_ok; +} + +staticfn int +optfn_objects( + int optidx UNUSED, int req, boolean negated UNUSED, + char *opts, char *op UNUSED) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + return optn_ok; + } + if (req == get_val) { + Sprintf(opts, "%s", to_be_done); + return optn_ok; + } + if (req == get_cnf_val) { + opts[0] = '\0'; + return optn_ok; + } + return optn_ok; +} + +staticfn int +optfn_packorder( + int optidx UNUSED, int req, boolean negated UNUSED, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + if (op == empty_optstr) + return optn_err; + if (!change_inv_order(op)) + return optn_err; + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + char ocl[MAXOCLASSES + 1]; + + oc_to_str(flags.inv_order, ocl); + Sprintf(opts, "%s", ocl); + return optn_ok; + } + return optn_ok; +} + +#ifdef CHANGE_COLOR + +DISABLE_WARNING_FORMAT_NONLITERAL + +staticfn int +optfn_palette( + int optidx UNUSED, int req, boolean negated UNUSED, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + if (op == empty_optstr) + return optn_err; + + if (match_optname(opts, "palette", 3, TRUE)) { + /* + * palette (adjust an RGB color in palette (color/R-G-B) + */ + if (!alternative_palette(op)) { + config_error_add("Error in palette parameter '%s'", op); + return optn_err; + } + if (!go.opt_initial) + go.opt_update_basic_palette = TRUE; + } + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + if (!opts) + return optn_err; + Sprintf(opts, n_currently_set, count_alt_palette()); + return optn_ok; + } + return optn_ok; +} + +#if 0 +/* old MACOS9 OS9 code */ +staticfn int +optfn_palette( + int optidx UNUSED, int req, boolean negated UNUSED, + char *opts, char *op) +{ + int cnt, tmp, reverse; + char *pt = op; + long rgb; + + * Mac OS9 variant + * palette (00c/880/-fff is blue/yellow/reverse white) + */ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + if (op == empty_optstr) + return optn_err; + + if (match_optname(opts, "palette", 3, TRUE) + || match_optname(opts, "hicolor", 3, TRUE)) { + int color_number, color_incr; + + if (duplicate) + complain_about_duplicate(optidx); + if (match_optname(opts, "hicolor", 3, TRUE)) { + color_number = CLR_MAX + 4; /* HARDCODED inverse number */ + color_incr = -1; + } else { + color_number = 0; + color_incr = 1; + } + /* ----------- Mac OS 9 code -------------------------*/ while (*pt && color_number >= 0) { cnt = 3; rgb = 0L; @@ -2589,21 +2775,15 @@ boolean tinitial, tfrom_file; } while (cnt-- > 0) { if (*pt && *pt != '/') { -#ifdef AMIGA - rgb <<= 4; -#else rgb <<= 8; -#endif tmp = *pt++; if (isalpha((uchar) tmp)) { tmp = (tmp + 9) & 0xf; /* Assumes ASCII... */ } else { tmp &= 0xf; /* Digits in ASCII too... */ } -#ifndef AMIGA /* Add an extra so we fill f -> ff and 0 -> 00 */ rgb += tmp << 4; -#endif rgb += tmp; } } @@ -2612,376 +2792,479 @@ boolean tinitial, tfrom_file; change_color(color_number, rgb, reverse); color_number += color_incr; } + + if (!go.opt_initial) + go.opt_update_basic_palette = TRUE; } -#endif /* !WIN32 */ - if (!initial) { - need_redraw = TRUE; - } - return retval; + return optn_ok; } -#endif /* CHANGE_COLOR */ + if (req == get_val || req == get_cnf_val) { + if (!opts) + return optn_err; + Sprintf(opts, n_currently_set, count_alt_palette()); + return optn_ok; + } + return optn_ok; - if (match_optname(opts, "fruit", 2, TRUE)) { - struct fruit *forig = 0; +} +#endif /* 0 */ - if (duplicate) - complain_about_duplicate(opts, 1); - op = string_for_opt(opts, negated || !initial); - if (negated) { - if (op != empty_optstr) { - bad_negation("fruit", TRUE); - return FALSE; - } - op = empty_optstr; - goto goodfruit; - } - if (op == empty_optstr) - return FALSE; - /* strip leading/trailing spaces, condense internal ones (3.6.2) */ - mungspaces(op); - if (!initial) { - struct fruit *f; - int fnum = 0; +RESTORE_WARNING_FORMAT_NONLITERAL - /* count number of named fruits; if 'op' is found among them, - then the count doesn't matter because we won't be adding it */ - f = fruit_from_name(op, FALSE, &fnum); - if (!f) { - if (!flags.made_fruit) - forig = fruit_from_name(pl_fruit, FALSE, (int *) 0); +#endif /* CHANGE_COLOR */ - if (!forig && fnum >= 100) { - config_error_add( - "Doing that so many times isn't very fruitful."); - return retval; - } - } - } - goodfruit: - nmcpy(pl_fruit, op, PL_FSIZ); - sanitize_name(pl_fruit); - /* OBJ_NAME(objects[SLIME_MOLD]) won't work for this after - initialization; it gets changed to generic "fruit" */ - if (!*pl_fruit) - nmcpy(pl_fruit, "slime mold", PL_FSIZ); - if (!initial) { - /* if 'forig' is nonNull, we replace it rather than add - a new fruit; it can only be nonNull if no fruits have - been created since the previous name was put in place */ - (void) fruitadd(pl_fruit, forig); - pline("Fruit is now \"%s\".", pl_fruit); - } - /* If initial, then initoptions is allowed to do it instead - * of here (initoptions always has to do it even if there's - * no fruit option at all. Also, we don't want people - * setting multiple fruits in their options.) - */ - return retval; +/* for "paranoid_confirmation:foo" and alias "[!]prayconfirm" */ +staticfn int +optfn_paranoid_confirmation( + int optidx, int req, boolean opt_negated, + char *opts, char *op) +{ + boolean fld_negated; + int i; + /* + * Player can change required response for some prompts (quit, die, + * attack, save-bones, continue-eating, break-wand, Were-change to + * need to be "yes" instead of just 'y' keystroke to accept. + * + * For paranoid_confirm:Confirm, these prompts also need "no" + * instead of 'n' or or to reject. ( always + * works as a way to reject.) + * + * Player can add an extra prompt (pray, AutoAll) that isn't + * ordinarily there. (They ask for 'y' keystroke unless Confirm is + * also set, then they'll switch to "yes", "no".) + * + * Player can also change game's behavior. paranoid_confirm:swim + * can be used to prevent accidentally stepping into water or lava; + * player must use the 'm' movement prefix to do that intentionally. + * paranoid_confirm:Remove [with synonym parnoid_confirm:Takeoff] + * changes the 'R' and 'T' commands [which have differing criteria + * for "only one candidate item"] to prompt for inventory item to + * remove/takeoff when there is only one candidate, so allows player + * a chance to cancel at the pick-an-item prompt or menu. + */ + + if (req == do_init) { + return optn_ok; } + if (req == do_set) { + char prayconfirm[1 + sizeof "pray"]; + char *pp; + boolean plus_or_minus = FALSE; - fullname = "whatis_coord"; - if (match_optname(opts, fullname, 8, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - if (negated) { - iflags.getpos_coords = GPCOORDS_NONE; - return retval; - } else if ((op = string_for_env_opt(fullname, opts, FALSE)) - != empty_optstr) { - static char gpcoords[] = { GPCOORDS_NONE, GPCOORDS_COMPASS, - GPCOORDS_COMFULL, GPCOORDS_MAP, - GPCOORDS_SCREEN, '\0' }; - char c = lowc(*op); + /* + * "prayconfirm" used to be a separate boolean option, + * now it is a synonym for paranoid_confirm:+pray and + * "!prayconfirm" has become one for paranoid_confirm:-pray. + */ + if (!strncmpi(opts, "prayconfirm", 4)) { + if (*op) { + /* presence of any value is treated as an error whether + complaining about the 'prayconfirm' deprecation or not; + this will erroneously reject "prayconfirm:true"; too + bad; back when prayconfirm was in active use, tacking on + an explicit value to a boolean option wasn't supported */ + config_error_add( + "deprecated %sprayconfirm option takes no parameters (found '%s')", + opt_negated ? "!" : "", op); + return optn_silenterr; + } +#ifdef COMPLAIN_ABOUT_PRAYCONFIRM + /* config file summary of complaints includes this in the count + of errors; we'd prefer that it be described as a warning but + that isn't supported [not important since this is considered + temporary until 'prayconfirm' gets removed altogether] */ + config_error_add( + "%sprayconfirm option is deprecated; switching to %s:%cpray", + opt_negated ? "!" : "", + allopt[optidx].name, + opt_negated ? '-' : '+'); + /* keep going */ +#endif + /* convert prayconfirm to paranoid_confirm:+pray and + !prayconfirm to paranoid_confirm:-pray */ + Sprintf(prayconfirm, "%cpray", opt_negated ? '-' : '+'); + op = prayconfirm; + /* possibly changing !prayconfirm to paranoid_confirm:-pray + which clears a paranoia bit but isn't a negated option */ + opt_negated = FALSE; + /* + * end of 'prayconfirm' processing + */ - if (c && index(gpcoords, c)) - iflags.getpos_coords = c; - else { - config_error_add("Unknown %s parameter '%s'", fullname, op); - return FALSE; + } else if (opt_negated) { + /* "!paranoid_confirm" w/o args is same as paranoid_confirm:none; + "!paranoid_confirm:anything" is disallowed */ + if (!*op) { + flags.paranoia_bits = 0; + return optn_ok; + } else { + config_error_add("!%s does not accept a value", + allopt[optidx].name); + return optn_silenterr; } - } else - return FALSE; - return retval; - } + } else if (!*op) { + /* "paranoid_confirm" without any arguments is disallowed */ + config_error_add("%s requires a value; use 'none' to cancel all", + allopt[optidx].name); + return optn_silenterr; + } - fullname = "whatis_filter"; - if (match_optname(opts, fullname, 8, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - if (negated) { - iflags.getloc_filter = GFILTER_NONE; - return retval; - } else if ((op = string_for_env_opt(fullname, opts, FALSE)) - != empty_optstr) { - char c = lowc(*op); + /* + * Multiple settings for paranoid_confirmation are allowed. + * When a new instance is processed, the behavior depends on the + * first character of its value: + * + * paranoid_confirm:foo bar + * clears all confirmation bits (from previous settings, including + * default), then sets the bits for foo and bar; + * + * paranoid_confirm:+foo bar + * existing bits are kept, plus those for foo and bar are set; + * + * paranoid_confirm:-foo bar + * existing bits are kept except those for foo and bar get cleared; + * + * paranoid_confirm:+foo !bar + * combination of paranoid_confirm:+foo,paranoid_confirm:-bar; + * + * paranoid_confirm:-foo !bar + * the negation in '!bar' is ignored, treated as if '-foo bar'; + * + * !paranoid_confirm + * without a value is treated as paranoid_confirm:none and clears + * all bits; + * !paranoid_confirm:anything + * (including +anything_else or -anything_else) is disallowed; + * + * paranoid_confirm:+all is the same as paranoid_confirm:all; + * paranoid_confirm:-all is the same as paranoid_confirm:none; + * paranoid_confirm:+none and paranoid_confirm:-none are no-ops. + */ + (void) mungspaces(op); + if (*op != '+' && *op != '-') { + /* new value; first clear all old bits */ + flags.paranoia_bits = 0; + } else { + /* augmenting existing value; keep old bits */ + plus_or_minus = TRUE; /* only used for "+none" and "-none" */ + opt_negated = (*op == '-'); /* context is changed */ + if (*++op == ' ') /* skip '+' or '-', maybe whitespace */ + ++op; + } - switch (c) { - case 'n': - iflags.getloc_filter = GFILTER_NONE; - break; - case 'v': - iflags.getloc_filter = GFILTER_VIEW; - break; - case 'a': - iflags.getloc_filter = GFILTER_AREA; - break; - default: { - config_error_add("Unknown %s parameter '%s'", fullname, op); - return FALSE; + for (;;) { + fld_negated = (*op == '!'); + if (fld_negated) { + /* there shouldn't be a space after '!' because then + "! foo bar" looks like it might be intended to mean + "!foo !bar" but if there is one, skip it to prevent + a lookup attempt for "" which will fail and result in + an unhelpful error message; accepting the space is + simpler than another special case error message */ + if (*++op == ' ') /* skip '!', maybe whitespace */ + ++op; + } else { + /* accept "nofoo" to be same as "!foo", unless "no" is + followed by a space or 'foo' begins with "n" (to avoid + confusion for "none" */ + if (lowc(op[0]) == 'n' && lowc(op[1]) == 'o' + && lowc(op[2] != 'n' && lowc(op[2]) != '\0')) { + fld_negated = TRUE; + op += 2; /* skip "no"; we know next char isn't space */ + } } + /* We're looking to parse + "paranoid_confirm:whichone wheretwo whothree" + and "paranoid_confirm:" prefix has already + been stripped off by the time we get here */ + pp = strchr(op, ' '); + if (pp) + *pp = '\0'; + /* we aren't matching option names but match_optname() + does what we want once we've broken the space + delimited aggregate into separate tokens */ + for (i = 0; i < SIZE(paranoia); ++i) { + if (match_optname(op, paranoia[i].argname, + paranoia[i].argMinLen, FALSE) + || (paranoia[i].synonym + && match_optname(op, paranoia[i].synonym, + paranoia[i].synMinLen, FALSE))) { + if (!paranoia[i].flagmask) { + /* flagmask==0 is "none", clear all bits + but "+none" and "-none" are no-ops */ + if (!plus_or_minus) + flags.paranoia_bits = 0; /* clear all */ + } else if (opt_negated || fld_negated) { + flags.paranoia_bits &= ~paranoia[i].flagmask; + } else { + flags.paranoia_bits |= paranoia[i].flagmask; + } + break; + } } - } else - return FALSE; - return retval; + if (i == SIZE(paranoia)) { + /* didn't match anything, so arg is bad; + any flags already modified will stay modified */ + config_error_add("Unknown %s parameter '%s'", + allopt[optidx].name, op); + return optn_silenterr; + } + /* move on to next token */ + if (pp) + op = pp + 1; + else + break; /* no next token */ + } /* for(;;) */ + return optn_ok; } + if (req == get_val || req == get_cnf_val) { + char tmpbuf[BUFSZ]; - fullname = "warnings"; - if (match_optname(opts, fullname, 5, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - if (negated) { - bad_negation(fullname, FALSE); - return FALSE; + tmpbuf[0] = '\0'; + for (i = 0; paranoia[i].flagmask != 0; ++i) { + if ((flags.paranoia_bits & paranoia[i].flagmask) != 0 + /* hide paranoid_confirm:bones during play except for wizard + mode; keep it for any mode if rewriting the config file */ + && (paranoia[i].flagmask != PARANOID_BONES + || wizard || req == get_cnf_val)) + Snprintf(eos(tmpbuf), sizeof tmpbuf - strlen(tmpbuf), + " %s", paranoia[i].argname); } - return warning_opts(opts, fullname); + /* note: always leaves enough room for caller to tack on '\n' */ + opts[0] = '\0'; + (void) strncat(opts, tmpbuf[0] ? &tmpbuf[1] : "none", BUFSZ - 1); + return optn_ok; + } + if (req == do_handler) { + return handler_paranoid_confirmation(); } + return optn_ok; +} - /* boulder:symbol */ - fullname = "boulder"; - if (match_optname(opts, fullname, 7, TRUE)) { -#ifdef BACKWARD_COMPAT - int clash = 0; +staticfn int +optfn_perminv_mode( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + boolean old_perm_invent = iflags.perm_invent; + uchar old_perminv_mode = iflags.perminv_mode; + int retval = optn_ok; - if (duplicate) - complain_about_duplicate(opts, 1); - if (negated) { - bad_negation(fullname, FALSE); - return FALSE; - } - /* if ((opts = string_for_env_opt(fullname, opts, FALSE)) - == empty_optstr) - */ - if ((opts = string_for_opt(opts, FALSE)) == empty_optstr) - return FALSE; - escapes(opts, opts); - /* note: dummy monclass #0 has symbol value '\0'; we allow that-- - attempting to set bouldersym to '^@'/'\0' will reset to default */ - if (def_char_to_monclass(opts[0]) != MAXMCLASSES) - clash = opts[0] ? 1 : 0; - else if (opts[0] >= '1' && opts[0] < WARNCOUNT + '0') - clash = 2; - if (clash) { - /* symbol chosen matches a used monster or warning - symbol which is not good - reject it */ - config_error_add( - "Badoption - boulder symbol '%s' would conflict with a %s symbol", - visctrl(opts[0]), - (clash == 1) ? "monster" : "warning"); - } else { - /* - * Override the default boulder symbol. - */ - ov_primary_syms[SYM_BOULDER + SYM_OFF_X] = (nhsym) opts[0]; - ov_rogue_syms[SYM_BOULDER + SYM_OFF_X] = (nhsym) opts[0]; - /* for 'initial', update of BOULDER symbol is done in - initoptions_finish(), after all symset options - have been processed */ - if (!initial) { - nhsym sym = get_othersym(SYM_BOULDER, - Is_rogue_level(&u.uz) ? ROGUESET : PRIMARY); - if (sym) - showsyms[SYM_BOULDER + SYM_OFF_X] = sym; - need_redraw = TRUE; + if (req == do_init) { +#if 0 + /* old, TEMPORARY method of controlling 'perm_invent'; + note: the bits used now have been changed, hence 'n << 1' */ + char *envtmp = nh_getenv("TTYINV") : 0; + int invmode = envtmp ? (atoi(envtmp) << 1) : 0; + + iflags.perminv_mode = (uchar) invmode; + iflags.perm_invent = iflags.perminv_mode != 0; +#endif + return optn_ok; + } else if (req == do_set) { + op = string_for_opt(opts, negated); + if (op != empty_optstr && negated) { /* reject "!perminv_mode=foo" */ + bad_negation(allopt[optidx].name, TRUE); + retval = optn_silenterr; + } else if (op != empty_optstr) { /* "perminv_mode=foo" */ + const char *pi0, *pi1; + int i; + unsigned ln = (unsigned) strlen(op); /* guaranteed > 0 */ + + for (i = 0; i < SIZE(perminv_modes); ++i) { + if (!(pi0 = perminv_modes[i][0])) + continue; + pi1 = perminv_modes[i][1]; + if (!strncmpi(op, pi0, ln) || !strncmpi(op, pi1, ln) + || op[0] == i + '0') { /* also accept '0'..'8' */ +#if 1 /*#ifdef TTY_PERM_INVENT*/ + if (strstri(pi0, "+grid") && !WINDOWPORT(tty)) { + i &= ~InvSparse; + config_error_add( + "%s: unavailable perm_invent mode '%s', using '%s'", + allopt[optidx].name, pi0, + perminv_modes[i][0]); + } +#endif + iflags.perminv_mode = (uchar) i; + iflags.perm_invent = TRUE; + break; + } + } + if (i == SIZE(perminv_modes)) { + config_error_add("Unknown %s parameter '%s'", + allopt[optidx].name, op); + iflags.perminv_mode = InvOptNone; + iflags.perm_invent = FALSE; + retval = optn_silenterr; } + } else if (negated) { /* "!perminv_mode" */ + iflags.perminv_mode = InvOptNone; + iflags.perm_invent = FALSE; } - return retval; -#else - config_error_add("'%s' no longer supported; use S_boulder:c instead", - fullname); - return FALSE; -#endif + if (!go.opt_initial) { + if (iflags.perminv_mode != old_perminv_mode + || iflags.perm_invent != old_perm_invent) + go.opt_need_redraw = TRUE; + } + } else if (req == do_handler) { + /* use a menu to choose new value for perminv_mode */ + retval = handler_perminv_mode(); + } else if (req == get_val) { + /* value shown when examining current option settings; enclosed + within square brackets for 'O', shown as-is when setting value */ + Sprintf(opts, "%s", perminv_modes[iflags.perminv_mode][2]); + if (iflags.perminv_mode != InvOptNone && !iflags.perm_invent + /* 'op' is Null when called by handler_perminv_mode() while + setting, non-Null when 'm O' shows current option values */ + && op) { + /* perminv_mode is set but isn't useful because perm_invent is + Off; say so after squeezing out enough for it to barely fit */ + if (iflags.perminv_mode == InvOptInUse) + (void) strsubst(opts, " currently", ""); + else + (void) strsubst(opts, " inventory", " invent"); + Strcat(opts, (((iflags.perminv_mode & InvSparse) != 0) ? " (Off)" + : " ('perm_invent' is Off)")); + } + } else if (req == get_cnf_val) { + Sprintf(opts, "%s", perminv_modes[iflags.perminv_mode][0]); } + return retval; +} - /* name:string */ - fullname = "name"; - if (match_optname(opts, fullname, 4, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - if (negated) { - bad_negation(fullname, FALSE); - return FALSE; - } else if ((op = string_for_env_opt(fullname, opts, FALSE)) - != empty_optstr) { - nmcpy(plname, op, PL_NSIZ); - } else - return FALSE; - return retval; - } +staticfn int +optfn_petattr( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + int retval = optn_ok; - /* altkeyhandler:string */ - fullname = "altkeyhandler"; - if (match_optname(opts, fullname, 4, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - if (negated) { - bad_negation(fullname, FALSE); - return FALSE; - } else if ((op = string_for_opt(opts, negated)) != empty_optstr) { -#if defined(WIN32) && defined(TTY_GRAPHICS) - set_altkeyhandling(op); -#endif - } else - return FALSE; - return retval; + if (req == do_init) { + return optn_ok; } + if (req == do_set) { + /* WINCAP2 petattr:string */ - /* WINCAP - * align_status:[left|top|right|bottom] */ - fullname = "align_status"; - if (match_optname(opts, fullname, sizeof "align_status" - 1, TRUE)) { op = string_for_opt(opts, negated); - if ((op != empty_optstr) && !negated) { - if (!strncmpi(op, "left", sizeof "left" - 1)) - iflags.wc_align_status = ALIGN_LEFT; - else if (!strncmpi(op, "top", sizeof "top" - 1)) - iflags.wc_align_status = ALIGN_TOP; - else if (!strncmpi(op, "right", sizeof "right" - 1)) - iflags.wc_align_status = ALIGN_RIGHT; - else if (!strncmpi(op, "bottom", sizeof "bottom" - 1)) - iflags.wc_align_status = ALIGN_BOTTOM; - else { - config_error_add("Unknown %s parameter '%s'", fullname, op); - return FALSE; - } + if (op != empty_optstr && negated) { + bad_negation(allopt[optidx].name, TRUE); + retval = optn_err; + } else if (op != empty_optstr) { +#if defined(TTY_GRAPHICS) || defined(CURSES_GRAPHICS) + int itmp = match_str2attr(op, FALSE); + + if (itmp == -1) { + config_error_add("Unknown %s parameter '%s'", + allopt[optidx].name, opts); + retval = optn_err; + } else + iflags.wc2_petattr = itmp; +#else + iflags.wc2_petattr = ATR_INVERSE; +#endif } else if (negated) { - bad_negation(fullname, TRUE); - return FALSE; + iflags.wc2_petattr = ATR_NONE; + } + if (retval != optn_err) { + iflags.hilite_pet = (iflags.wc2_petattr != ATR_NONE); + if (!go.opt_initial) + go.opt_need_redraw = TRUE; } return retval; } - - /* WINCAP - * align_message:[left|top|right|bottom] */ - fullname = "align_message"; - if (match_optname(opts, fullname, sizeof "align_message" - 1, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - op = string_for_opt(opts, negated); - if ((op != empty_optstr) && !negated) { - if (!strncmpi(op, "left", sizeof "left" - 1)) - iflags.wc_align_message = ALIGN_LEFT; - else if (!strncmpi(op, "top", sizeof "top" - 1)) - iflags.wc_align_message = ALIGN_TOP; - else if (!strncmpi(op, "right", sizeof "right" - 1)) - iflags.wc_align_message = ALIGN_RIGHT; - else if (!strncmpi(op, "bottom", sizeof "bottom" - 1)) - iflags.wc_align_message = ALIGN_BOTTOM; - else { - config_error_add("Unknown %s parameter '%s'", fullname, op); - return FALSE; - } - } else if (negated) { - bad_negation(fullname, TRUE); - return FALSE; - } - return retval; + if (req == get_val || req == get_cnf_val) { +#if defined(TTY_GRAPHICS) || defined(CURSES_GRAPHICS) + if (WINDOWPORT(tty) || WINDOWPORT(curses)) { + Strcpy(opts, attr2attrname(iflags.wc2_petattr)); + } else +#endif + if (iflags.wc2_petattr != 0) + Sprintf(opts, "0x%08x", iflags.wc2_petattr); + else if (req == get_cnf_val) + opts[0] = '\0'; + else + Strcpy(opts, defopt); } - - /* the order to list inventory */ - fullname = "packorder"; - if (match_optname(opts, fullname, 4, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - if (negated) { - bad_negation(fullname, FALSE); - return FALSE; - } else if ((op = string_for_opt(opts, FALSE)) == empty_optstr) - return FALSE; - - if (!change_inv_order(op)) - return FALSE; - return retval; + if (req == do_handler) { + return handler_petattr(); } + return optn_ok; +} - /* user can change required response for some prompts (quit, die, hit), - or add an extra prompt (pray, Remove) that isn't ordinarily there */ - fullname = "paranoid_confirmation"; - if (match_optname(opts, fullname, 8, TRUE)) { - /* at present we don't complain about duplicates for this - option, but we do throw away the old settings whenever - we process a new one [clearing old flags is essential - for handling default paranoid_confirm:pray sanely] */ - flags.paranoia_bits = 0; /* clear all */ - if (negated) { - flags.paranoia_bits = 0; /* [now redundant...] */ - } else if ((op = string_for_opt(opts, TRUE)) != empty_optstr) { - char *pp, buf[BUFSZ]; - - strncpy(buf, op, sizeof buf - 1); - buf[sizeof buf - 1] = '\0'; - op = mungspaces(buf); - for (;;) { - /* We're looking to parse - "paranoid_confirm:whichone wheretwo whothree" - and "paranoid_confirm:" prefix has already - been stripped off by the time we get here */ - pp = index(op, ' '); - if (pp) - *pp = '\0'; - /* we aren't matching option names but match_optname() - does what we want once we've broken the space - delimited aggregate into separate tokens */ - for (i = 0; i < SIZE(paranoia); ++i) { - if (match_optname(op, paranoia[i].argname, - paranoia[i].argMinLen, FALSE) - || (paranoia[i].synonym - && match_optname(op, paranoia[i].synonym, - paranoia[i].synMinLen, FALSE))) { - if (paranoia[i].flagmask) - flags.paranoia_bits |= paranoia[i].flagmask; - else /* 0 == "none", so clear all */ - flags.paranoia_bits = 0; - break; - } - } - if (i == SIZE(paranoia)) { - /* didn't match anything, so arg is bad; - any flags already set will stay set */ - config_error_add("Unknown %s parameter '%s'", - fullname, op); - return FALSE; - } - /* move on to next token */ - if (pp) - op = pp + 1; - else - break; /* no next token */ - } /* for(;;) */ - } else - return FALSE; - return retval; +staticfn int +optfn_pettype( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; } - - /* accept deprecated boolean; superseded by paranoid_confirm:pray */ - fullname = "prayconfirm"; - if (match_optname(opts, fullname, 4, FALSE)) { - if (negated) - flags.paranoia_bits &= ~PARANOID_PRAY; + if (req == do_set) { + if ((op = string_for_env_opt(allopt[optidx].name, opts, negated)) + != empty_optstr) { + switch (lowc(*op)) { + case 'd': /* dog */ + gp.preferred_pet = 'd'; + break; + case 'c': /* cat */ + case 'f': /* feline */ + gp.preferred_pet = 'c'; + break; + case 'h': /* horse */ + case 'q': /* quadruped */ + /* avoids giving "unrecognized type of pet" but + pet_type(dog.c) won't actually honor this */ + gp.preferred_pet = 'h'; + break; + case 'n': /* no pet */ + gp.preferred_pet = 'n'; + break; + case 'r': /* random */ + case '*': /* random */ + gp.preferred_pet = '\0'; + break; + default: + config_error_add("Unrecognized pet type '%s'.", op); + return optn_err; + break; + } + } else if (negated) + gp.preferred_pet = 'n'; + return optn_ok; + } + if (req == get_val) { + Sprintf(opts, "%s", (gp.preferred_pet == 'c') ? "cat" + : (gp.preferred_pet == 'd') ? "dog" + : (gp.preferred_pet == 'h') ? "horse" + : (gp.preferred_pet == 'n') ? "none" + : "random"); + return optn_ok; + } + if (req == get_cnf_val) { + if (gp.preferred_pet) + Sprintf(opts, "%c", gp.preferred_pet); else - flags.paranoia_bits |= PARANOID_PRAY; - return retval; + opts[0] = '\0'; + return optn_ok; } + return optn_ok; +} - /* maximum burden picked up before prompt (Warren Cheung) */ - fullname = "pickup_burden"; - if (match_optname(opts, fullname, 8, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - if (negated) { - bad_negation(fullname, FALSE); - return FALSE; - } else if ((op = string_for_env_opt(fullname, opts, FALSE)) - != empty_optstr) { +staticfn int +optfn_pickup_burden( + int optidx, int req, boolean negated UNUSED, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* maximum burden picked up before prompt (Warren Cheung) */ + + if ((op = string_for_env_opt(allopt[optidx].name, opts, FALSE)) + != empty_optstr) { switch (lowc(*op)) { case 'u': /* Unencumbered */ flags.pickup_burden = UNENCUMBERED; @@ -3003,34 +3286,51 @@ boolean tinitial, tfrom_file; flags.pickup_burden = OVERLOADED; break; default: - config_error_add("Unknown %s parameter '%s'", fullname, op); - return FALSE; + config_error_add("Unknown %s parameter '%s'", + allopt[optidx].name, op); + return optn_err; } } else - return FALSE; - return retval; + return optn_err; + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + Sprintf(opts, "%s", burdentype[flags.pickup_burden]); + return optn_ok; + } + if (req == do_handler) { + return handler_pickup_burden(); } + return optn_ok; +} + +staticfn int +optfn_pickup_types( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + char ocl[MAXOCLASSES + 1], tbuf[MAXOCLASSES + 1], qbuf[QBUFSZ], + abuf[BUFSZ]; + int oc_sym; + unsigned num; + boolean badopt = FALSE, compat = (strlen(opts) <= 6), use_menu; - /* types of objects to pick up automatically */ - fullname = "pickup_types"; - if (match_optname(opts, fullname, 8, TRUE)) { - char ocl[MAXOCLASSES + 1], tbuf[MAXOCLASSES + 1], - qbuf[QBUFSZ], abuf[BUFSZ]; - int oc_sym; - boolean badopt = FALSE, compat = (strlen(opts) <= 6), use_menu; + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* types of objects to pick up automatically */ - if (duplicate) - complain_about_duplicate(opts, 1); oc_to_str(flags.pickup_types, tbuf); flags.pickup_types[0] = '\0'; /* all */ - op = string_for_opt(opts, (compat || !initial)); + op = string_for_opt(opts, (compat || !go.opt_initial)); if (op == empty_optstr) { - if (compat || negated || initial) { + if (compat || negated || go.opt_initial) { /* for backwards compatibility, "pickup" without a value is a synonym for autopickup of all types (and during initialization, we can't prompt yet) */ flags.pickup = !negated; - return retval; + return optn_ok; } oc_to_str(flags.inv_order, ocl); use_menu = TRUE; @@ -3039,8 +3339,8 @@ boolean tinitial, tfrom_file; boolean wasspace; use_menu = FALSE; - Sprintf(qbuf, "New %s: [%s am] (%s)", fullname, ocl, - *tbuf ? tbuf : "all"); + Sprintf(qbuf, "New %s: [%s am] (%s)", allopt[optidx].name, + ocl, *tbuf ? tbuf : "all"); abuf[0] = '\0'; getlin(qbuf, abuf); wasspace = (abuf[0] == ' '); /* before mungspaces */ @@ -3051,20 +3351,20 @@ boolean tinitial, tfrom_file; op = tbuf; /* restore */ else if (abuf[0] == 'm') use_menu = TRUE; - /* note: abuf[0]=='a' is already handled via clearing the + /* note: abuf[0]=='a' is already handled via clearing the old value (above) as a default action */ } if (use_menu) { - if (wizard && !index(ocl, VENOM_SYM)) + if (wizard && !strchr(ocl, VENOM_SYM)) strkitten(ocl, VENOM_SYM); - (void) choose_classes_menu("Autopickup what?", 1, TRUE, ocl, - tbuf); + (void) choose_classes_menu("Autopickup what?", + 1, TRUE, ocl, tbuf); op = tbuf; } } if (negated) { - bad_negation(fullname, TRUE); - return FALSE; + bad_negation(allopt[optidx].name, TRUE); + return optn_err; } while (*op == ' ') op++; @@ -3074,7 +3374,7 @@ boolean tinitial, tfrom_file; oc_sym = def_char_to_objclass(*op); /* make sure all are valid obj symbols occurring once */ if (oc_sym != MAXOCLASSES - && !index(flags.pickup_types, oc_sym)) { + && !strchr(flags.pickup_types, oc_sym)) { flags.pickup_types[num] = (char) oc_sym; flags.pickup_types[++num] = '\0'; } else @@ -3082,46 +3382,106 @@ boolean tinitial, tfrom_file; op++; } if (badopt) { - config_error_add("Unknown %s parameter '%s'", fullname, op); - return FALSE; + config_error_add("Unknown %s parameter '%s'", + allopt[optidx].name, op); + return optn_err; } } - return retval; + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + oc_to_str(flags.pickup_types, ocl); + Sprintf(opts, "%s", ocl[0] ? ocl : "all"); + return optn_ok; + } + if (req == do_handler) { + return handler_pickup_types(); + } + return optn_ok; +} + +staticfn int +optfn_pile_limit( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; } + if (req == do_set) { + /* pile limit: when walking over objects, number which triggers + "there are several/many objects here" instead of listing them + */ - /* pile limit: when walking over objects, number which triggers - "there are several/many objects here" instead of listing them */ - fullname = "pile_limit"; - if (match_optname(opts, fullname, 4, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); op = string_for_opt(opts, negated); if ((negated && op == empty_optstr) || (!negated && op != empty_optstr)) flags.pile_limit = negated ? 0 : atoi(op); else if (negated) { - bad_negation(fullname, TRUE); - return FALSE; + bad_negation(allopt[optidx].name, TRUE); + return optn_err; } else /* op == empty_optstr */ flags.pile_limit = PILE_LIMIT_DFLT; /* sanity check */ if (flags.pile_limit < 0) flags.pile_limit = PILE_LIMIT_DFLT; - return retval; + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + Sprintf(opts, "%d", flags.pile_limit); + return optn_ok; + } + return optn_ok; +} + +staticfn int +optfn_player_selection( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; } + if (req == do_set) { + /* WINCAP player_selection: dialog | prompt/prompts/prompting */ + + op = string_for_opt(opts, negated); + if (op != empty_optstr && !negated) { + if (!strncmpi(op, "dialog", sizeof "dialog" - 1)) { + iflags.wc_player_selection = VIA_DIALOG; + } else if (!strncmpi(op, "prompt", sizeof "prompt" - 1)) { + iflags.wc_player_selection = VIA_PROMPTS; + } else { + config_error_add("Unknown %s parameter '%s'", + allopt[optidx].name, op); + return optn_err; + } + } + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + Sprintf(opts, "%s", + iflags.wc_player_selection ? "prompts" : "dialog"); + return optn_ok; + } + return optn_ok; +} + +staticfn int +optfn_playmode( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* play mode: normal, explore/discovery, or debug/wizard */ - /* play mode: normal, explore/discovery, or debug/wizard */ - fullname = "playmode"; - if (match_optname(opts, fullname, 4, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - if (negated) - bad_negation(fullname, FALSE); if (duplicate || negated) - return FALSE; - op = string_for_opt(opts, FALSE); + return optn_err; if (op == empty_optstr) - return FALSE; + return optn_err; if (!strncmpi(op, "normal", 6) || !strcmpi(op, "play")) { wizard = discover = FALSE; } else if (!strncmpi(op, "explore", 6) @@ -3130,172 +3490,246 @@ boolean tinitial, tfrom_file; } else if (!strncmpi(op, "debug", 5) || !strncmpi(op, "wizard", 6)) { wizard = TRUE, discover = FALSE; } else { - config_error_add("Invalid value for \"%s\":%s", fullname, op); - return FALSE; + config_error_add("Invalid value for \"%s\":%s", + allopt[optidx].name, op); + return optn_err; } - return retval; + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + Strcpy(opts, wizard ? "debug" : discover ? "explore" : "normal"); + return optn_ok; } + return optn_ok; +} - /* WINCAP - * player_selection: dialog | prompt/prompts/prompting */ - fullname = "player_selection"; - if (match_optname(opts, fullname, sizeof "player_selection" - 1, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - op = string_for_opt(opts, negated); - if (op != empty_optstr && !negated) { - if (!strncmpi(op, "dialog", sizeof "dialog" - 1)) { - iflags.wc_player_selection = VIA_DIALOG; - } else if (!strncmpi(op, "prompt", sizeof "prompt" - 1)) { - iflags.wc_player_selection = VIA_PROMPTS; - } else { - config_error_add("Unknown %s parameter '%s'", fullname, op); - return FALSE; +staticfn int +optfn_race( + int optidx, + int req, + boolean negated, + char *opts, + char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* race:string */ + if (!parse_role_opt(optidx, negated, allopt[optidx].name, opts, &op)) + return optn_silenterr; + + if (*op != '!') { + if ((flags.initrace = str2race(op)) == ROLE_NONE) { + config_error_add("Unknown %s '%s'", allopt[optidx].name, op); + return optn_err; } - } else if (negated) { - bad_negation(fullname, TRUE); - return FALSE; + gp.pl_race = *op; /* Backwards compatibility */ + saveoptstr(optidx, rolestring(flags.initrace, races, noun)); } - return retval; + return optn_ok; } + if (req == get_val) { + Sprintf(opts, "%s", rolestring(flags.initrace, races, noun)); + return optn_ok; + } + if (req == get_cnf_val) { + op = get_cnf_role_opt(optidx); + Strcpy(opts, op ? op : "none"); + return optn_ok; + } + return optn_ok; +} - /* things to disclose at end of game */ - fullname = "disclose"; - if (match_optname(opts, fullname, 7, TRUE)) { - /* - * The order that the end_disclose options are stored: - * inventory, attribs, vanquished, genocided, - * conduct, overview. - * There is an array in flags: - * end_disclose[NUM_DISCLOSURE_OPT]; - * with option settings for the each of the following: - * iagvc [see disclosure_options in decl.c]: - * Allowed setting values in that array are: - * DISCLOSE_PROMPT_DEFAULT_YES ask with default answer yes - * DISCLOSE_PROMPT_DEFAULT_NO ask with default answer no - * DISCLOSE_YES_WITHOUT_PROMPT always disclose and don't ask - * DISCLOSE_NO_WITHOUT_PROMPT never disclose and don't ask - * DISCLOSE_PROMPT_DEFAULT_SPECIAL for 'vanquished' only... - * DISCLOSE_SPECIAL_WITHOUT_PROMPT ...to set up sort order. - * - * Those setting values can be used in the option - * string as a prefix to get the desired behaviour. - * - * For backward compatibility, no prefix is required, - * and the presence of a i,a,g,v, or c without a prefix - * sets the corresponding value to DISCLOSE_YES_WITHOUT_PROMPT. - */ - int idx, prefix_val; - - if (duplicate) - complain_about_duplicate(opts, 1); - op = string_for_opt(opts, TRUE); - if (op != empty_optstr && negated) { - bad_negation(fullname, TRUE); - return FALSE; - } - /* "disclose" without a value means "all with prompting" - and negated means "none without prompting" */ - if (op == empty_optstr - || !strcmpi(op, "all") || !strcmpi(op, "none")) { - if (op != empty_optstr && !strcmpi(op, "none")) - negated = TRUE; - for (num = 0; num < NUM_DISCLOSURE_OPTIONS; num++) - flags.end_disclose[num] = negated - ? DISCLOSE_NO_WITHOUT_PROMPT - : DISCLOSE_PROMPT_DEFAULT_YES; - return retval; - } +staticfn int +optfn_roguesymset( + int optidx, int req, boolean negated UNUSED, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + if (op != empty_optstr) { + if (gs.symset[ROGUESET].name) + free((genericptr_t) gs.symset[ROGUESET].name), + gs.symset[ROGUESET].name = 0; + gs.symset[ROGUESET].name = dupstr(op); + if (!read_sym_file(ROGUESET)) { + clear_symsetentry(ROGUESET, TRUE); + config_error_add( + "Unable to load symbol set \"%s\" from \"%s\"", op, + SYMBOLS); + return optn_err; + } else { + if (!go.opt_initial && Is_rogue_level(&u.uz)) + assign_graphics(ROGUESET); + go.opt_need_redraw = go.opt_need_glyph_reset = TRUE; + go.opt_symset_changed = TRUE; + } + } else + return optn_err; + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + Sprintf(opts, "%s", + gs.symset[ROGUESET].name ? gs.symset[ROGUESET].name + : "default"); + if (gc.currentgraphics == ROGUESET && gs.symset[ROGUESET].name) + Strcat(opts, ", active"); + return optn_ok; + } + if (req == do_handler) { + return handler_symset(optidx); + } + return optn_ok; +} - num = 0; - prefix_val = -1; - while (*op && num < sizeof flags.end_disclose - 1) { - static char valid_settings[] = { - DISCLOSE_PROMPT_DEFAULT_YES, DISCLOSE_PROMPT_DEFAULT_NO, - DISCLOSE_PROMPT_DEFAULT_SPECIAL, - DISCLOSE_YES_WITHOUT_PROMPT, DISCLOSE_NO_WITHOUT_PROMPT, - DISCLOSE_SPECIAL_WITHOUT_PROMPT, '\0' - }; - register char c, *dop; +staticfn int +optfn_role( + int optidx, + int req, + boolean negated, + char *opts, + char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* role:string */ + if (!parse_role_opt(optidx, negated, allopt[optidx].name, opts, &op)) + return optn_silenterr; - c = lowc(*op); - if (c == 'k') - c = 'v'; /* killed -> vanquished */ - if (c == 'd') - c = 'o'; /* dungeon -> overview */ - dop = index(disclosure_options, c); - if (dop) { - idx = (int) (dop - disclosure_options); - if (idx < 0 || idx > NUM_DISCLOSURE_OPTIONS - 1) { - impossible("bad disclosure index %d %c", idx, c); - continue; - } - if (prefix_val != -1) { - if (*dop != 'v') { - if (prefix_val == DISCLOSE_PROMPT_DEFAULT_SPECIAL) - prefix_val = DISCLOSE_PROMPT_DEFAULT_YES; - if (prefix_val == DISCLOSE_SPECIAL_WITHOUT_PROMPT) - prefix_val = DISCLOSE_YES_WITHOUT_PROMPT; - } - flags.end_disclose[idx] = prefix_val; - prefix_val = -1; - } else - flags.end_disclose[idx] = DISCLOSE_YES_WITHOUT_PROMPT; - } else if (index(valid_settings, c)) { - prefix_val = c; - } else if (c == ' ') { - ; /* do nothing */ - } else { - config_error_add("Unknown %s parameter '%c'", fullname, *op); - return FALSE; + if (*op != '!') { + if ((flags.initrole = str2role(op)) == ROLE_NONE) { + config_error_add("Unknown %s '%s'", allopt[optidx].name, op); + return optn_err; } - op++; + nmcpy(svp.pl_character, op, PL_NSIZ); /* Backwards compat */ + saveoptstr(optidx, rolestring(flags.initrole, roles, name.m)); } - return retval; + return optn_ok; } + if (req == get_val) { + Sprintf(opts, "%s", rolestring(flags.initrole, roles, name.m)); + return optn_ok; + } + if (req == get_cnf_val) { + op = get_cnf_role_opt(optidx); + Strcpy(opts, op ? op : "none"); + return optn_ok; + } + return optn_ok; +} - /* scores:5t[op] 5a[round] o[wn] */ - fullname = "scores"; - if (match_optname(opts, fullname, 4, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); +staticfn int +optfn_runmode( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { if (negated) { - bad_negation(fullname, FALSE); - return FALSE; + flags.runmode = RUN_TPORT; + } else if (op != empty_optstr) { + if (str_start_is("teleport", op, TRUE)) + flags.runmode = RUN_TPORT; + else if (str_start_is("run", op, TRUE)) + flags.runmode = RUN_LEAP; + else if (str_start_is("walk", op, TRUE)) + flags.runmode = RUN_STEP; + else if (str_start_is("crawl", op, TRUE)) + flags.runmode = RUN_CRAWL; + else { + config_error_add("Unknown %s parameter '%s'", + allopt[optidx].name, op); + return optn_err; + } + } else { + config_error_add("Value is mandatory for %s", + allopt[optidx].name); + return optn_err; } + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + Sprintf(opts, "%s", runmodes[flags.runmode]); + return optn_ok; + } + if (req == do_handler) { + return handler_runmode(); + } + return optn_ok; +} + +staticfn int +optfn_scores( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* scores:5t[op] 5a[round] o[wn] */ + if ((op = string_for_opt(opts, FALSE)) == empty_optstr) - return FALSE; + return optn_err; + + /* 5.0: earlier versions left old values for unspecified arguments + if player's scores:foo option only specified some of the three; + in particular, attempting to use 'scores:own' rather than + 'scores:0 top/0 around/own' didn't work as intended */ + flags.end_top = flags.end_around = 0, flags.end_own = FALSE; + + if (negated) + op = eos(op); while (*op) { int inum = 1; + negated = (*op == '!') || !strncmpi(op, "no", 2); + if (negated) + op += (*op == '!') ? 1 : (op[2] != '-') ? 2 : 3; + if (digit(*op)) { inum = atoi(op); while (digit(*op)) op++; - } else if (*op == '!') { - negated = !negated; - op++; } while (*op == ' ') op++; - switch (*op) { + switch (lowc(*op)) { case 't': - case 'T': - flags.end_top = inum; + flags.end_top = negated ? 0 : inum; break; case 'a': - case 'A': - flags.end_around = inum; + flags.end_around = negated ? 0 : inum; break; case 'o': - case 'O': - flags.end_own = !negated; + flags.end_own = (negated || !inum) ? FALSE : TRUE; + break; + case 'n': /* none */ + flags.end_top = flags.end_around = 0, flags.end_own = FALSE; break; + case '-': + if (digit(*(op + 1))) { + config_error_add( + "Values for %s:top and %s:around must not be negative", + allopt[optidx].name, + allopt[optidx].name); + return optn_silenterr; + } + FALLTHROUGH; + /*FALLTHRU*/ default: - config_error_add("Unknown %s parameter '%s'", fullname, op); - return FALSE; + config_error_add("Unknown %s parameter '%s'", + allopt[optidx].name, op); + return optn_silenterr; } /* "3a" is sufficient but accept "3around" (or "3abracadabra") */ while (letter(*op)) @@ -3306,12 +3740,188 @@ boolean tinitial, tfrom_file; if (*op == '/') op++; } - return retval; + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + *opts = '\0'; + if (flags.end_top > 0) + Sprintf(opts, "%d top", flags.end_top); + if (flags.end_around > 0) + Sprintf(eos(opts), "%s%d around", + (flags.end_top > 0) ? "/" : "", flags.end_around); + if (flags.end_own) + Sprintf(eos(opts), "%sown", + (flags.end_top > 0 || flags.end_around > 0) ? "/" : ""); + if (!*opts) + Strcpy(opts, "none"); + return optn_ok; + } + return optn_ok; +} + +staticfn int +optfn_scroll_amount( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; } + if (req == do_set) { + /* WINCAP scroll_amount:nn */ - fullname = "sortloot"; - if (match_optname(opts, fullname, 4, TRUE)) { - op = string_for_env_opt(fullname, opts, FALSE); + op = string_for_opt(opts, negated); + if ((negated && op == empty_optstr) + || (!negated && op != empty_optstr)) { + iflags.wc_scroll_amount = negated ? 1 : atoi(op); + } else if (negated) { + bad_negation(allopt[optidx].name, TRUE); + return optn_err; + } + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + if (iflags.wc_scroll_amount) + Sprintf(opts, "%d", iflags.wc_scroll_amount); + else + Strcpy(opts, defopt); + return optn_ok; + } + return optn_ok; +} + +staticfn int +optfn_scroll_margin( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* WINCAP scroll_margin:nn */ + op = string_for_opt(opts, negated); + if ((negated && op == empty_optstr) + || (!negated && op != empty_optstr)) { + iflags.wc_scroll_margin = negated ? 5 : atoi(op); + } else if (negated) { + bad_negation(allopt[optidx].name, TRUE); + return optn_err; + } + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + if (iflags.wc_scroll_margin) + Sprintf(opts, "%d", iflags.wc_scroll_margin); + else + Strcpy(opts, defopt); + return optn_ok; + } + return optn_ok; +} + +staticfn int +optfn_soundlib( + int optidx, int req, boolean negated UNUSED, + char *opts, char *op) +{ + char soundlibbuf[WINTYPELEN]; + + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* + * soundlib: option to choose the interface for binaries built + * with support for more than the default interface (nosound). + * + * Option processing sets gc.chosen_soundlib. A later call + * to activate_chosen_soundlib() actually activates it, and + * sets gc.active_soundlib. + */ + if ((op = string_for_env_opt(allopt[optidx].name, opts, FALSE)) + != empty_optstr) { + enum soundlib_ids option_id; + + get_soundlib_name(soundlibbuf, WINTYPELEN); + option_id = soundlib_id_from_opt(op); + gc.chosen_soundlib = option_id; + assign_soundlib(gc.chosen_soundlib); + } else + return optn_err; + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + get_soundlib_name(soundlibbuf, WINTYPELEN); + Sprintf(opts, "%s", soundlibbuf); + return optn_ok; + } + return optn_ok; +} + +staticfn int +optfn_sortdiscoveries( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + if (req == do_init) { + flags.discosort = 'o'; + return optn_ok; + } + if (req == do_set) { + op = string_for_env_opt(allopt[optidx].name, opts, FALSE); + if (negated) { + flags.discosort = 'o'; + } else if (op != empty_optstr) { + switch (lowc(*op)) { + case '0': + case 'o': /* order of discovery */ + flags.discosort = 'o'; + break; + case '1': + case 's': /* sortloot order (subclasses for some classes) */ + flags.discosort = 's'; + break; + case '2': + case 'c': /* alphabetical within each class */ + flags.discosort = 'c'; + break; + case '3': + case 'a': /* alphabetical across all classes */ + flags.discosort = 'a'; + break; + default: + config_error_add("Unknown %s parameter '%s'", + allopt[optidx].name, op); + return optn_silenterr; + } + } else + return optn_err; + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + get_sortdisco(opts, (req == get_cnf_val) ? TRUE : FALSE); + return optn_ok; + } + if (req == do_handler) { + /* return handler_sortdiscoveries(); */ + (void) choose_disco_sort(0); /* o_init.c */ + } + return optn_ok; +} + +staticfn int +optfn_sortloot( + int optidx, int req, boolean negated UNUSED, + char *opts, char *op) +{ + int i; + + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + op = string_for_env_opt(allopt[optidx].name, opts, FALSE); if (op != empty_optstr) { char c = lowc(*op); @@ -3322,943 +3932,4143 @@ boolean tinitial, tfrom_file; flags.sortloot = c; break; default: - config_error_add("Unknown %s parameter '%s'", fullname, op); - return FALSE; + config_error_add("Unknown %s parameter '%s'", + allopt[optidx].name, op); + return optn_err; } } else - return FALSE; - return retval; + return optn_err; + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + for (i = 0; i < SIZE(sortltype); i++) + if (flags.sortloot == sortltype[i][0]) { + Strcpy(opts, sortltype[i]); + break; + } + return optn_ok; } + if (req == do_handler) { + return handler_sortloot(); + } + return optn_ok; +} - fullname = "suppress_alert"; - if (match_optname(opts, fullname, 4, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - op = string_for_opt(opts, negated); +staticfn int +optfn_sortvanquished( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + extern const char *const vanqorders[][3]; /* insight.c */ + static const char vanqmodes[] = "tdaACcnz"; + const char *optname = allopt[optidx].name; + + if (req == do_init) { + flags.vanq_sortmode = VANQ_MLVL_MNDX; /* 0 => 't' */ + return optn_ok; + } + if (req == do_set) { + op = string_for_env_opt(allopt[optidx].name, opts, FALSE); if (negated) { - bad_negation(fullname, FALSE); - return FALSE; - } else if (op != empty_optstr) - (void) feature_alert_opts(op, fullname); - return retval; + flags.vanq_sortmode = VANQ_MLVL_MNDX; /* 0 => 't' */ + } else if (op != empty_optstr) { + const char *p; + int vndx = 0; + + if ((p = strchr(vanqmodes, *op)) != 0) { + vndx = (int) (p - vanqmodes); + } else if (strchr("01234567", *op)) { + vndx = *op - '0'; + } else { + config_error_add("Unknown %s parameter '%s'", optname, op); + return optn_silenterr; + } + flags.vanq_sortmode = (uchar) vndx; + } else + return optn_err; + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + Strcpy(opts, vanqorders[flags.vanq_sortmode][0]); + if (req == get_val) + Sprintf(eos(opts), ": %s", vanqorders[flags.vanq_sortmode][1]); + return optn_ok; + } + if (req == do_handler) { + uchar prev_sortmode = flags.vanq_sortmode; + + /* return handler_sortvanquished(); */ + (void) set_vanq_order(TRUE); /* insight.c */ + pline("'%s' %s \"%s: %s\".", optname, + (flags.vanq_sortmode == prev_sortmode) + ? "not changed, still" + : "changed to", + vanqorders[flags.vanq_sortmode][0], + vanqorders[flags.vanq_sortmode][1]); + } + return optn_ok; +} + +staticfn int +optfn_statushilites( + int optidx UNUSED, int req, boolean negated, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; } + if (req == do_set) { + /* control over whether highlights should be displayed (non-zero), and + also for how long to show temporary ones (N turns; default 3) */ -#ifdef VIDEOSHADES - /* videocolors:string */ - fullname = "videocolors"; - if (match_optname(opts, fullname, 6, TRUE) - || match_optname(opts, "videocolours", 10, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); +#ifdef STATUS_HILITES if (negated) { - bad_negation(fullname, FALSE); - return FALSE; - } else if ((opts = string_for_env_opt(fullname, opts, FALSE)) - == empty_optstr) { - return FALSE; - } - if (!assign_videocolors(opts)) { - config_error_add("Unknown error handling '%s'", fullname); - return FALSE; + iflags.hilite_delta = 0L; + } else { + op = string_for_opt(opts, TRUE); + iflags.hilite_delta = (op == empty_optstr || !*op) ? 3L + : atol(op); + if (iflags.hilite_delta < 0L) + iflags.hilite_delta = 1L; } - return retval; + if (!go.opt_from_file) + reset_status_hilites(); + return optn_ok; +#else + nhUse(negated); + nhUse(op); + config_error_add("'%s' is not supported", allopt[optidx].name); + return optn_err; +#endif } - /* videoshades:string */ - fullname = "videoshades"; - if (match_optname(opts, fullname, 6, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - if (negated) { - bad_negation(fullname, FALSE); - return FALSE; - } else if ((opts = string_for_env_opt(fullname, opts, FALSE)) - == empty_optstr) { - return FALSE; - } - if (!assign_videoshades(opts)) { - config_error_add("Unknown error handling '%s'", fullname); - return FALSE; - } - return retval; + if (req == get_val) { +#ifdef STATUS_HILITES + if (!iflags.hilite_delta) + Strcpy(opts, "0 (off: don't highlight status fields)"); + else + Sprintf(opts, "%ld (on: highlight status for %ld turns)", + iflags.hilite_delta, iflags.hilite_delta); +#else + Strcpy(opts, "unsupported"); +#endif + return optn_ok; } -#endif /* VIDEOSHADES */ + if (req == get_cnf_val) { +#ifdef STATUS_HILITES + Sprintf(opts, "%ld", iflags.hilite_delta); +#else + opts[0] = '\0'; +#endif + } + return optn_ok; +} -#ifdef MSDOS -#ifdef NO_TERMS - /* video:string -- must be after longer tests */ - fullname = "video"; - if (match_optname(opts, fullname, 5, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); +staticfn int +optfn_statuslines( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + int retval = optn_ok, itmp = 0; + + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* WINCAP2 + * statuslines:n */ + + op = string_for_opt(opts, negated); if (negated) { - bad_negation(fullname, FALSE); - return FALSE; - } else if ((opts = string_for_env_opt(fullname, opts, FALSE)) - == empty_optstr) { - return FALSE; + bad_negation(allopt[optidx].name, TRUE); + itmp = 2; + retval = optn_err; + } else if (op != empty_optstr) { + itmp = atoi(op); } - if (!assign_video(opts)) { - config_error_add("Unknown error handling '%s'", fullname); - return FALSE; + if (itmp < 2 || itmp > 3) { + config_error_add("'%s:%s' is invalid; must be 2 or 3", + allopt[optidx].name, op); + retval = optn_silenterr; + } else { + iflags.wc2_statuslines = itmp; + if (!go.opt_initial) + go.opt_need_redraw = TRUE; } return retval; } -#endif /* NO_TERMS */ - /* soundcard:string -- careful not to match boolean 'sound' */ - fullname = "soundcard"; - if (match_optname(opts, fullname, 6, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); + if (req == get_val || req == get_cnf_val) { + if (wc2_supported(allopt[optidx].name)) + Strcpy(opts, (iflags.wc2_statuslines < 3) ? "2" : "3"); + else + Strcpy(opts, "unknown"); + return optn_ok; + } + return optn_ok; +} + +#ifdef WIN32CON +staticfn int +optfn_subkeyvalue( + int optidx UNUSED, int req, boolean negated UNUSED, + char *opts, char *op UNUSED) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + if (op == empty_optstr) + return optn_err; +#ifdef TTY_GRAPHICS + map_subkeyvalue(op); +#endif + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + opts[0] = '\0'; + return optn_ok; + } + return optn_ok; +} +#endif /* WIN32CON */ + +staticfn int +optfn_suppress_alert( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { if (negated) { - bad_negation(fullname, FALSE); - return FALSE; - } else if ((opts = string_for_env_opt(fullname, opts, FALSE)) - == empty_optstr) { - return FALSE; - } - if (!assign_soundcard(opts)) { - config_error_add("Unknown error handling '%s'", fullname); - return FALSE; - } - return retval; + bad_negation(allopt[optidx].name, FALSE); + return optn_err; + } else if (op != empty_optstr) + (void) feature_alert_opts(op, allopt[optidx].name); + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + if (req == get_cnf_val && flags.suppress_alert == 0L) + opts[0] = '\0'; + else if (flags.suppress_alert == 0L) + Strcpy(opts, none); + else + Sprintf(opts, "%lu.%lu.%lu", FEATURE_NOTICE_VER_MAJ, + FEATURE_NOTICE_VER_MIN, FEATURE_NOTICE_VER_PATCH); + return optn_ok; } -#endif /* MSDOS */ + return optn_ok; +} - /* WINCAP - * - * map_mode:[tiles|ascii4x6|ascii6x8|ascii8x8|ascii16x8|ascii7x12 - * |ascii8x12|ascii16x12|ascii12x16|ascii10x18|fit_to_screen - * |ascii_fit_to_screen|tiles_fit_to_screen] - */ - fullname = "map_mode"; - if (match_optname(opts, fullname, sizeof "map_mode" - 1, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - op = string_for_opt(opts, negated); - if (op != empty_optstr && !negated) { - if (!strcmpi(op, "tiles")) - iflags.wc_map_mode = MAP_MODE_TILES; - else if (!strncmpi(op, "ascii4x6", sizeof "ascii4x6" - 1)) - iflags.wc_map_mode = MAP_MODE_ASCII4x6; - else if (!strncmpi(op, "ascii6x8", sizeof "ascii6x8" - 1)) - iflags.wc_map_mode = MAP_MODE_ASCII6x8; - else if (!strncmpi(op, "ascii8x8", sizeof "ascii8x8" - 1)) - iflags.wc_map_mode = MAP_MODE_ASCII8x8; - else if (!strncmpi(op, "ascii16x8", sizeof "ascii16x8" - 1)) - iflags.wc_map_mode = MAP_MODE_ASCII16x8; - else if (!strncmpi(op, "ascii7x12", sizeof "ascii7x12" - 1)) - iflags.wc_map_mode = MAP_MODE_ASCII7x12; - else if (!strncmpi(op, "ascii8x12", sizeof "ascii8x12" - 1)) - iflags.wc_map_mode = MAP_MODE_ASCII8x12; - else if (!strncmpi(op, "ascii16x12", sizeof "ascii16x12" - 1)) - iflags.wc_map_mode = MAP_MODE_ASCII16x12; - else if (!strncmpi(op, "ascii12x16", sizeof "ascii12x16" - 1)) - iflags.wc_map_mode = MAP_MODE_ASCII12x16; - else if (!strncmpi(op, "ascii10x18", sizeof "ascii10x18" - 1)) - iflags.wc_map_mode = MAP_MODE_ASCII10x18; - else if (!strncmpi(op, "fit_to_screen", - sizeof "fit_to_screen" - 1)) - iflags.wc_map_mode = MAP_MODE_ASCII_FIT_TO_SCREEN; - else if (!strncmpi(op, "ascii_fit_to_screen", - sizeof "ascii_fit_to_screen" - 1)) - iflags.wc_map_mode = MAP_MODE_ASCII_FIT_TO_SCREEN; - else if (!strncmpi(op, "tiles_fit_to_screen", - sizeof "tiles_fit_to_screen" - 1)) - iflags.wc_map_mode = MAP_MODE_TILES_FIT_TO_SCREEN; - else { - config_error_add("Unknown %s parameter '%s'", fullname, op); - return FALSE; +extern const char *const known_handling[]; /* symbols.c */ +extern const char *const known_restrictions[]; /* symbols.c */ + +staticfn int +optfn_symset( + int optidx, int req, boolean negated UNUSED, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + if (op != empty_optstr) { + if (gs.symset[PRIMARYSET].name) + free((genericptr_t) gs.symset[PRIMARYSET].name), + gs.symset[PRIMARYSET].name = 0; + gs.symset[PRIMARYSET].name = dupstr(op); + if (!read_sym_file(PRIMARYSET)) { + clear_symsetentry(PRIMARYSET, TRUE); + config_error_add( + "Unable to load symbol set \"%s\" from \"%s\"", op, + SYMBOLS); + return optn_err; + } else { + if (gs.symset[PRIMARYSET].handling) { +#ifndef ENHANCED_SYMBOLS + if (gs.symset[PRIMARYSET].handling == H_UTF8) { + config_error_add( + "Unavailable symset handler \"%s\" for %s", + known_handling[H_UTF8], op); + load_symset("default", PRIMARYSET); + } +#endif + } + switch_symbols(gs.symset[PRIMARYSET].name != (char *) 0); + go.opt_need_redraw = go.opt_need_glyph_reset = TRUE; + go.opt_symset_changed = TRUE; } - } else if (negated) { - bad_negation(fullname, TRUE); - return FALSE; + } else + return optn_err; + return optn_ok; + } + if (req == get_val) { + Sprintf(opts, "%s", + gs.symset[PRIMARYSET].name ? gs.symset[PRIMARYSET].name + : "default"); + if (gc.currentgraphics == PRIMARYSET && gs.symset[PRIMARYSET].name) + Strcat(opts, ", active"); + if (gs.symset[PRIMARYSET].handling) { + Sprintf(eos(opts), ", handler=%s", + known_handling[gs.symset[PRIMARYSET].handling]); } - return retval; + return optn_ok; + } + if (req == get_cnf_val) { + Sprintf(opts, "%s", + gs.symset[PRIMARYSET].name ? gs.symset[PRIMARYSET].name + : "default"); + return optn_ok; + } + if (req == do_handler) { + int reslt; + + if (!glyphid_cache_status()) + fill_glyphid_cache(); + reslt = handler_symset(optidx); + if (glyphid_cache_status()) + free_glyphid_cache(); + /* apply_customizations(gc.currentgraphics, + (do_custom_colors | do_custom_symbols)); */ + return reslt; + } + return optn_ok; +} + +staticfn int +optfn_term_cols( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + int retval = optn_ok; + long ltmp; + + if (req == do_init) { + return optn_ok; } + if (req == do_set) { + /* WINCAP2 + * term_cols:amount */ - /* WINCAP - * scroll_amount:nn */ - fullname = "scroll_amount"; - if (match_optname(opts, fullname, sizeof "scroll_amount" - 1, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - op = string_for_opt(opts, negated); - if ((negated && op == empty_optstr) - || (!negated && op != empty_optstr)) { - iflags.wc_scroll_amount = negated ? 1 : atoi(op); - } else if (negated) { - bad_negation(fullname, TRUE); - return FALSE; + if ((op = string_for_opt(opts, negated)) != empty_optstr) { + ltmp = atol(op); + /* just checks atol() sanity, not logical window size sanity + */ + if (ltmp <= 0L || ltmp >= (long) LARGEST_INT) { + config_error_add("Invalid %s: %ld", allopt[optidx].name, + ltmp); + retval = optn_err; + } else { + iflags.wc2_term_cols = (int) ltmp; + } } return retval; } + if (req == get_val || req == get_cnf_val) { + if (iflags.wc2_term_cols) + Sprintf(opts, "%d", iflags.wc2_term_cols); + else if (req == get_cnf_val) + opts[0] = '\0'; + else + Strcpy(opts, defopt); + return optn_ok; + } + return optn_ok; +} - /* WINCAP - * scroll_margin:nn */ - fullname = "scroll_margin"; - if (match_optname(opts, fullname, sizeof "scroll_margin" - 1, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - op = string_for_opt(opts, negated); - if ((negated && op == empty_optstr) - || (!negated && op != empty_optstr)) { - iflags.wc_scroll_margin = negated ? 5 : atoi(op); - } else if (negated) { - bad_negation(fullname, TRUE); - return FALSE; - } - return retval; +staticfn int +optfn_term_rows( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + int retval = optn_ok; + long ltmp; + + if (req == do_init) { + return optn_ok; } + if (req == do_set) { + /* WINCAP2 + * term_rows:amount */ - fullname = "subkeyvalue"; - if (match_optname(opts, fullname, 5, TRUE)) { - /* no duplicate complaint here */ - if (negated) { - bad_negation(fullname, FALSE); - return FALSE; -#if defined(WIN32) - } else { - op = string_for_opt(opts, 0); - if (op == empty_optstr) - return FALSE; -#ifdef TTY_GRAPHICS - map_subkeyvalue(op); -#endif -#endif + if ((op = string_for_opt(opts, negated)) != empty_optstr) { + ltmp = atol(op); + /* just checks atol() sanity, not logical window size sanity + */ + if (ltmp <= 0L || ltmp >= (long) LARGEST_INT) { + config_error_add("Invalid %s: %ld", allopt[optidx].name, + ltmp); + retval = optn_err; + } else { + iflags.wc2_term_rows = (int) ltmp; + } } return retval; } + if (req == get_val || req == get_cnf_val) { + if (iflags.wc2_term_rows) + Sprintf(opts, "%d", iflags.wc2_term_rows); + else if (req == get_cnf_val) + opts[0] = '\0'; + else + Strcpy(opts, defopt); + return optn_ok; + } + return optn_ok; +} - /* WINCAP - * tile_width:nn */ - fullname = "tile_width"; - if (match_optname(opts, fullname, sizeof "tile_width" - 1, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - op = string_for_opt(opts, negated); - if ((negated && op == empty_optstr) - || (!negated && op != empty_optstr)) { - iflags.wc_tile_width = negated ? 0 : atoi(op); - } else if (negated) { - bad_negation(fullname, TRUE); - return FALSE; - } - return retval; +staticfn int +optfn_tile_file( + int optidx UNUSED, int req, boolean negated UNUSED, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; } - /* WINCAP - * tile_file:name */ - fullname = "tile_file"; - if (match_optname(opts, fullname, sizeof "tile_file" - 1, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - if ((op = string_for_opt(opts, FALSE)) != empty_optstr) { + if (req == do_set) { + /* WINCAP tile_file:name */ + if (op != empty_optstr) { if (iflags.wc_tile_file) free(iflags.wc_tile_file); iflags.wc_tile_file = dupstr(op); } else - return FALSE; - return retval; + return optn_err; + return optn_ok; + } + if (req == get_val) { + Sprintf(opts, "%s", + iflags.wc_tile_file ? iflags.wc_tile_file : defopt); + return optn_ok; + } + if (req == get_cnf_val) { + if (iflags.wc_tile_file) + Sprintf(opts, "%s", iflags.wc_tile_file); + else + opts[0] = '\0'; + return optn_ok; + } + return optn_ok; +} + +staticfn int +optfn_tile_height( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; } - /* WINCAP - * tile_height:nn */ - fullname = "tile_height"; - if (match_optname(opts, fullname, sizeof "tile_height" - 1, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); + if (req == do_set) { + /* WINCAP tile_height:nn */ op = string_for_opt(opts, negated); if ((negated && op == empty_optstr) || (!negated && op != empty_optstr)) { iflags.wc_tile_height = negated ? 0 : atoi(op); } else if (negated) { - bad_negation(fullname, TRUE); - return FALSE; + bad_negation(allopt[optidx].name, TRUE); + return optn_err; } - return retval; + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + if (iflags.wc_tile_height) + Sprintf(opts, "%d", iflags.wc_tile_height); + else if (req == get_cnf_val) + opts[0] = '\0'; + else + Strcpy(opts, defopt); + return optn_ok; + } + return optn_ok; +} + +staticfn int +optfn_tile_width( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* WINCAP tile_width:nn */ + op = string_for_opt(opts, negated); + if ((negated && op == empty_optstr) + || (!negated && op != empty_optstr)) { + iflags.wc_tile_width = negated ? 0 : atoi(op); + } else if (negated) { + bad_negation(allopt[optidx].name, TRUE); + return optn_err; + } + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + if (iflags.wc_tile_width) + Sprintf(opts, "%d", iflags.wc_tile_width); + else if (req == get_cnf_val) + opts[0] = '\0'; + else + Strcpy(opts, defopt); + return optn_ok; } + return optn_ok; +} + +staticfn int +optfn_traps( + int optidx UNUSED, int req, boolean negated UNUSED, + char *opts, char *op UNUSED) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + return optn_ok; + } + if (req == get_val) { + Sprintf(opts, "%s", to_be_done); + return optn_ok; + } + if (req == get_cnf_val) { + opts[0] = '\0'; + return optn_ok; + } + return optn_ok; +} - /* WINCAP - * vary_msgcount:nn */ - fullname = "vary_msgcount"; - if (match_optname(opts, fullname, sizeof "vary_msgcount" - 1, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); +staticfn int +optfn_vary_msgcount( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* WINCAP vary_msgcount:nn */ op = string_for_opt(opts, negated); if ((negated && op == empty_optstr) || (!negated && op != empty_optstr)) { iflags.wc_vary_msgcount = negated ? 0 : atoi(op); } else if (negated) { - bad_negation(fullname, TRUE); - return FALSE; + bad_negation(allopt[optidx].name, TRUE); + return optn_err; } - return retval; + return optn_ok; } + if (req == get_val || req == get_cnf_val) { + if (iflags.wc_vary_msgcount) + Sprintf(opts, "%d", iflags.wc_vary_msgcount); + else if (req == get_cnf_val) + opts[0] = '\0'; + else + Strcpy(opts, defopt); + return optn_ok; + } + return optn_ok; +} + +staticfn int +optfn_versinfo( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + const char *optname = allopt[optidx].name; + unsigned vi = flags.versinfo; + + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* versinfo: what to include when 'showvers' displays version + on status lines; bitmask with up to three bits: + (1) x.y.z number, (2) program name, (4) git branch if available. + If branch is requested but unavailable, status_version will + treat 4 as 1. + */ + boolean have_branch = (nomakedefs.git_branch + && *nomakedefs.git_branch); + int val, dflt = have_branch ? VI_BRANCH : VI_NUMBER; - /* - * windowtype: option to choose the interface for binaries built - * with support for more than one interface (tty + X11, for instance). - * - * Ideally, 'windowtype' should be processed first, because it - * causes the wc_ and wc2_ flags to be set up. - * For user, making it be first in a config file is trivial, use - * OPTIONS=windowtype:Foo - * as the first non-comment line of the file. - * Making it first in NETHACKOPTIONS requires it to be at the _end_ - * because comma-separated option strings are processed from right - * to left. - */ - fullname = "windowtype"; - if (match_optname(opts, fullname, 3, TRUE)) { - if (iflags.windowtype_locked) - return retval; - if (duplicate) - complain_about_duplicate(opts, 1); if (negated) { - bad_negation(fullname, FALSE); - return FALSE; - } else if ((op = string_for_env_opt(fullname, opts, FALSE)) - != empty_optstr) { - if (!iflags.windowtype_deferred) { - char buf[WINTYPELEN]; + bad_negation(allopt[optidx].name, TRUE); + return optn_silenterr; + } + op = string_for_opt(opts, FALSE); + if (op == empty_optstr) { + config_error_add("'%s' requires a value; defaulting to %d", + optname, dflt); + return optn_silenterr; + } + val = atoi(op); + if (!val || (val & ~7) != 0) { + config_error_add("'%s' must be one of 1, 2, 4, or" + " the sum of two or all three of those", + optname); + return optn_silenterr; + } + flags.versinfo = (unsigned) val; + } else if (req == do_handler) { + /* return handler_versinfo(); */ + (void) handler_versinfo(); + pline("'%s' %s %u.", optname, + (flags.versinfo == vi) ? "not changed, still" : "changed to", + flags.versinfo); + } else if (req == get_val) { + char vbuf[QBUFSZ]; + boolean g = (vi & VI_NAME) != 0, + b = (vi & VI_BRANCH) != 0, + n = (vi & VI_NUMBER) != 0; + + Sprintf(opts, "%u: %s%s%s%s%s (%.99s)", flags.versinfo, + g ? "name" : "", (b && g) ? "+" : "", b ? "branch" : "", + (n && (b || g)) ? "+" : "", n ? "number" : "", + status_version(vbuf, sizeof vbuf, FALSE)); + } else if (req == get_cnf_val) { + Sprintf(opts, "%u", flags.versinfo); + } + if (flags.versinfo != vi && !go.opt_initial) + go.opt_need_redraw = TRUE; /* context.botlx = TRUE ought to suffice + * but doesn't for X11 fancy status */ + return optn_ok; +} - nmcpy(buf, op, WINTYPELEN); - choose_windows(buf); - } else { - nmcpy(chosen_windowtype, op, WINTYPELEN); +#ifdef VIDEOSHADES +staticfn int +optfn_videocolors( + int optidx, int req, boolean negated UNUSED, + char *opts, char *op UNUSED) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* videocolors:string */ + + if ((opts = string_for_env_opt(allopt[optidx].name, opts, FALSE)) + == empty_optstr) { + return optn_err; + } + if (!assign_videocolors(opts)) { + config_error_add("Unknown error handling '%s'", + allopt[optidx].name); + return optn_err; + } + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + Sprintf(opts, "%d-%d-%d-%d-%d-%d-%d-%d-%d-%d-%d-%d", + ttycolors[CLR_RED], ttycolors[CLR_GREEN], + ttycolors[CLR_BROWN], ttycolors[CLR_BLUE], + ttycolors[CLR_MAGENTA], ttycolors[CLR_CYAN], + ttycolors[CLR_ORANGE], ttycolors[CLR_BRIGHT_GREEN], + ttycolors[CLR_YELLOW], ttycolors[CLR_BRIGHT_BLUE], + ttycolors[CLR_BRIGHT_MAGENTA], ttycolors[CLR_BRIGHT_CYAN]); + return optn_ok; + } + return optn_ok; +} + +staticfn int +optfn_videoshades( + int optidx, int req, boolean negated UNUSED, + char *opts, char *op UNUSED) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* videoshades:string */ + + if ((opts = string_for_env_opt(allopt[optidx].name, opts, FALSE)) + == empty_optstr) { + return optn_err; + } + if (!assign_videoshades(opts)) { + config_error_add("Unknown error handling '%s'", + allopt[optidx].name); + return optn_err; + } + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + Sprintf(opts, "%s-%s-%s", shade[0], shade[1], shade[2]); + return optn_ok; + } + return optn_ok; +} +#endif /* VIDEOSHADES */ + +#ifdef MSDOS +staticfn int +optfn_video_width( + int optidx UNUSED, int req, boolean negated, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + if ((op = string_for_opt(opts, negated)) != empty_optstr) + iflags.wc_video_width = strtol(op, NULL, 10); + else + return optn_err; + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + opts[0] = '\0'; + } + return optn_ok; +} + +staticfn int +optfn_video_height( + int optidx UNUSED, int req, boolean negated, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + if ((op = string_for_opt(opts, negated)) != empty_optstr) + iflags.wc_video_height = strtol(op, NULL, 10); + else + return optn_err; + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + opts[0] = '\0'; + } + return optn_ok; +} + +#ifdef NO_TERMS +staticfn int +optfn_video( + int optidx, int req, boolean negated UNUSED, + char *opts, char *op UNUSED) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* video:string */ + if ((opts = string_for_env_opt(allopt[optidx].name, opts, FALSE)) + == empty_optstr) { + return optn_err; + } + if (!assign_video(opts)) { + config_error_add("Unknown error handling '%s'", + allopt[optidx].name); + return optn_err; + } + return optn_ok; + } + if (req == get_val) { + Sprintf(opts, "%s", to_be_done); + return optn_ok; + } + if (req == get_cnf_val) { + opts[0] = '\0'; + return optn_ok; + } + return optn_ok; +} + +#endif /* NO_TERMS */ +#endif /* MSDOS */ + +staticfn int +optfn_warnings( + int optidx, int req, boolean negated UNUSED, + char *opts, char *op UNUSED) +{ + int reslt; + + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + reslt = warning_opts(opts, allopt[optidx].name); + return reslt ? optn_ok : optn_err; + } + if (req == get_val || req == get_cnf_val) { + opts[0] = '\0'; + return optn_ok; + } + return optn_ok; +} + +staticfn int +optfn_whatis_coord( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + if (negated) { + iflags.getpos_coords = GPCOORDS_NONE; + return optn_ok; + } else if ((op = string_for_env_opt(allopt[optidx].name, opts, FALSE)) + != empty_optstr) { + static char gpcoords[] = { GPCOORDS_NONE, GPCOORDS_COMPASS, + GPCOORDS_COMFULL, GPCOORDS_MAP, + GPCOORDS_SCREEN, '\0' }; + char c = lowc(*op); + + if (c && strchr(gpcoords, c)) + iflags.getpos_coords = c; + else { + config_error_add("Unknown %s parameter '%s'", + allopt[optidx].name, op); + return optn_err; } } else - return FALSE; + return optn_err; + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + Sprintf(opts, "%s", + (iflags.getpos_coords == GPCOORDS_MAP) ? "map" + : (iflags.getpos_coords == GPCOORDS_COMPASS) ? "compass" + : (iflags.getpos_coords == GPCOORDS_COMFULL) ? "full compass" + : (iflags.getpos_coords == GPCOORDS_SCREEN) ? "screen" + : "none"); + return optn_ok; + } + if (req == do_handler) { + return handler_whatis_coord(); + } + return optn_ok; +} + +staticfn int +optfn_whatis_filter( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + if (negated) { + iflags.getloc_filter = GFILTER_NONE; + return optn_ok; + } else if ((op = string_for_env_opt(allopt[optidx].name, opts, FALSE)) + != empty_optstr) { + char c = lowc(*op); + + switch (c) { + case 'n': + iflags.getloc_filter = GFILTER_NONE; + break; + case 'v': + iflags.getloc_filter = GFILTER_VIEW; + break; + case 'a': + iflags.getloc_filter = GFILTER_AREA; + break; + default: { + config_error_add("Unknown %s parameter '%s'", + allopt[optidx].name, op); + return optn_err; + } + } + } else + return optn_err; + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + Sprintf(opts, "%s", + (iflags.getloc_filter == GFILTER_VIEW) ? "view" + : (iflags.getloc_filter == GFILTER_AREA) ? "area" + : "none"); + return optn_ok; + } + if (req == do_handler) { + return handler_whatis_filter(); + } + return optn_ok; +} + +staticfn int +optfn_windowborders( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + int retval = optn_ok; + + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + op = string_for_opt(opts, negated); + if (negated && op != empty_optstr) { + bad_negation(allopt[optidx].name, TRUE); + retval = optn_err; + } else { + int itmp; + + if (negated) + itmp = 0; /* Off */ + else if (op == empty_optstr) + itmp = 1; /* On */ + else /* Value supplied; expect 0 (off), 1 (on), 2 (auto) + * or 3 (on for most windows, off for perm_invent) + * or 4 (auto for most windows, off for perm_invent) */ + itmp = atoi(op); + + if (itmp < 0 || itmp > 4) { + config_error_add("Invalid %s (should be within 0 to 4): %s", + allopt[optidx].name, opts); + retval = optn_silenterr; + } else { + iflags.wc2_windowborders = itmp; + } + } return retval; } + if (req == get_val) { + Sprintf(opts, "%s", + (iflags.wc2_windowborders == 0) ? "0=off" + : (iflags.wc2_windowborders == 1) ? "1=on" + : (iflags.wc2_windowborders == 2) ? "2=auto" + : (iflags.wc2_windowborders == 3) + ? "3=on, except off for perm_invent" + : (iflags.wc2_windowborders == 4) + ? "4=auto, except off for perm_invent" + : defopt); + return optn_ok; + } + if (req == get_cnf_val) { + Sprintf(opts, "%i", iflags.wc2_windowborders); + return optn_ok; + } + if (req == do_handler) { + return handler_windowborders(); + } + return optn_ok; +} #ifdef WINCHAIN - fullname = "windowchain"; - if (match_optname(opts, fullname, 3, TRUE)) { - if (negated) { - bad_negation(fullname, FALSE); - return FALSE; - } else if ((op = string_for_env_opt(fullname, opts, FALSE)) - != empty_optstr) { +staticfn int +optfn_windowchain( + int optidx, int req, + boolean negated UNUSED, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + if ((op = string_for_env_opt(allopt[optidx].name, opts, FALSE)) + != empty_optstr) { char buf[WINTYPELEN]; - nmcpy(buf, op, WINTYPELEN); - addto_windowchain(buf); - } else - return FALSE; - return retval; + nmcpy(buf, op, WINTYPELEN); + addto_windowchain(buf); + } else + return optn_err; + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + opts[0] = '\0'; + return optn_ok; + } + return optn_ok; +} +#endif + +/* Win GUI and curses */ +static const char *const wcnames[WC_COUNT] = { + "menu", "message", "status", "text" +}; +static const char *const wcshortnames[WC_COUNT] = { + "mnu", "msg", "sts", "txt" +}; +int wcolors_opt[WC_COUNT]; + +staticfn int +optfn_windowcolors( + int optidx, int req, + boolean negated UNUSED, + char *opts, char *op) +{ + int wccount; + + if (req == do_init) { + for (wccount = 0; wccount < WC_COUNT; ++wccount) { + wcolors_opt[wccount] = 0; + } + return optn_ok; + } + if (req == do_set) { + /* WINCAP + * setting window colors + * syntax: windowcolors=menu foregrnd/backgrnd text foregrnd/backgrnd + */ + if ((op = string_for_opt(opts, FALSE)) != empty_optstr) { + if (!wc_set_window_colors(op)) { + config_error_add("Could not set %s '%s'", allopt[optidx].name, + op); + return optn_err; + } + } + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + const char *fg, *bg; + + /* TODO: wide 'get_val' may need to be wrapped in the menu display */ + opts[0] = '\0'; + for (wccount = 0; wccount < WC_COUNT; ++wccount) { + fg = iflags.wcolors[wccount].fg; + bg = iflags.wcolors[wccount].bg; + if (fg && (!*fg || !strcmp(fg, defbrief))) + fg = 0; + if (bg && (!*bg || !strcmp(bg, defbrief))) + bg = 0; + Sprintf(eos(opts), "%s%s %s/%s", !wccount ? "" : " ", + (fg || bg) ? wcnames[wccount] : wcshortnames[wccount], + fg ? fg : defbrief, bg ? bg : defbrief); + } + return optn_ok; + } + return optn_ok; +} + +staticfn int +optfn_windowtype( + int optidx, int req, + boolean negated UNUSED, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + /* + * windowtype: option to choose the interface for binaries built + * with support for more than one interface (tty + X11, for + * instance). + * + * Ideally, 'windowtype' should be processed first, because it + * causes the wc_ and wc2_ flags to be set up. + * For user, making it be first in a config file is trivial, use + * OPTIONS=windowtype:Foo + * as the first non-comment line of the file. + * Making it first in NETHACKOPTIONS requires it to be at the + * _end_ because comma-separated option strings are processed from + * right to left. + */ + if (!iflags.window_inited) { + if (iflags.windowtype_locked) + return optn_ok; + + if ((op = string_for_env_opt(allopt[optidx].name, opts, FALSE)) + != empty_optstr) { + nmcpy(gc.chosen_windowtype, op, WINTYPELEN); + if (!iflags.windowtype_deferred) { + choose_windows(gc.chosen_windowtype); + } + } else { + return optn_err; + } + } + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + Sprintf(opts, "%s", windowprocs.name); + return optn_ok; + } + return optn_ok; +} + +/* + * Prefix-handling functions + */ + +staticfn int +pfxfn_cond_( + int optidx UNUSED, + int req, + boolean negated, + char *opts, + char *op UNUSED) +{ + if (req == do_init) { + condopt(0, (boolean *) 0, 0); /* make the choices match defaults */ + return optn_ok; + } + if (req == do_set) { + int reslt = parse_cond_option(negated, opts); + + switch (reslt) { + case 0: + opt_set_in_config[pfx_cond_] = TRUE; + break; + case 3: + config_error_add("Ambiguous condition option %s", opts); + break; + case 1: + case 2: + default: + config_error_add("Unknown condition option %s (%d)", opts, reslt); + break; + } + if (reslt != 0) + return optn_err; + /* [FIXME? redraw seems like overkill; botl update should suffice] */ + go.opt_need_redraw = TRUE; + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + opts[0] = '\0'; + return optn_ok; + } + if (req == do_handler) { /* not used */ + (void) cond_menu(); /* in botl.c */ + return optn_ok; + } + return optn_ok; +} + +staticfn int +pfxfn_font(int optidx, int req, boolean negated, char *opts, char *op) +{ + int opttype = -1; + + if (req == do_init) { + return optn_ok; + } + + if (req == do_set) { + /* WINCAP setting font options */ + if (optidx == opt_font_map) + opttype = MAP_OPTION; + else if (optidx == opt_font_message) + opttype = MESSAGE_OPTION; + else if (optidx == opt_font_text) + opttype = TEXT_OPTION; + else if (optidx == opt_font_menu) + opttype = MENU_OPTION; + else if (optidx == opt_font_status) + opttype = STATUS_OPTION; + else if (optidx == opt_font_size_map + || optidx == opt_font_size_message + || optidx == opt_font_size_text + || optidx == opt_font_size_menu + || optidx == opt_font_size_status) { + if (optidx == opt_font_size_map) + opttype = MAP_OPTION; + else if (optidx == opt_font_size_message) + opttype = MESSAGE_OPTION; + else if (optidx == opt_font_size_text) + opttype = TEXT_OPTION; + else if (optidx == opt_font_size_menu) + opttype = MENU_OPTION; + else if (optidx == opt_font_size_status) + opttype = STATUS_OPTION; + else { + config_error_add("Unknown %s parameter '%s'", + allopt[optidx].name, opts); + return optn_err; + } + if (duplicate) + complain_about_duplicate(optidx); + if (opttype > 0 && !negated + && (op = string_for_opt(opts, FALSE)) != empty_optstr) { + switch (opttype) { + case MAP_OPTION: + iflags.wc_fontsiz_map = atoi(op); + break; + case MESSAGE_OPTION: + iflags.wc_fontsiz_message = atoi(op); + break; + case TEXT_OPTION: + iflags.wc_fontsiz_text = atoi(op); + break; + case MENU_OPTION: + iflags.wc_fontsiz_menu = atoi(op); + break; + case STATUS_OPTION: + iflags.wc_fontsiz_status = atoi(op); + break; + } + } + return optn_ok; + } else { + config_error_add("Unknown %s parameter '%s'", + "font", opts); + return FALSE; + } + if (opttype > 0 + && (op = string_for_opt(opts, FALSE)) != empty_optstr) { + wc_set_font_name(opttype, op); +#ifdef MACOS9 + set_font_name(opttype, op); +#endif + return optn_ok; + } else if (negated) { + bad_negation(allopt[optidx].name, TRUE); + return optn_err; + } + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + if (optidx == opt_font_map) { + Sprintf(opts, "%s", + iflags.wc_font_map ? iflags.wc_font_map : defopt); + } else if (optidx == opt_font_message) { + Sprintf(opts, "%s", + iflags.wc_font_message ? iflags.wc_font_message : defopt); + } else if (optidx == opt_font_status) { + Sprintf(opts, "%s", + iflags.wc_font_status ? iflags.wc_font_status : defopt); + } else if (optidx == opt_font_menu) { + Sprintf(opts, "%s", + iflags.wc_font_menu ? iflags.wc_font_menu : defopt); + } else if (optidx == opt_font_text) { + Sprintf(opts, "%s", + iflags.wc_font_text ? iflags.wc_font_text : defopt); + } else if (optidx == opt_font_size_map) { + if (iflags.wc_fontsiz_map) + Sprintf(opts, "%d", iflags.wc_fontsiz_map); + else + Strcpy(opts, defopt); + } else if (optidx == opt_font_size_message) { + if (iflags.wc_fontsiz_message) + Sprintf(opts, "%d", iflags.wc_fontsiz_message); + else + Strcpy(opts, defopt); + } else if (optidx == opt_font_size_status) { + if (iflags.wc_fontsiz_status) + Sprintf(opts, "%d", iflags.wc_fontsiz_status); + else + Strcpy(opts, defopt); + } else if (optidx == opt_font_size_menu) { + if (iflags.wc_fontsiz_menu) + Sprintf(opts, "%d", iflags.wc_fontsiz_menu); + else + Strcpy(opts, defopt); + } else if (optidx == opt_font_size_text) { + if (iflags.wc_fontsiz_text) + Sprintf(opts, "%d", iflags.wc_fontsiz_text); + else + Strcpy(opts, defopt); + } + return optn_ok; + } + return optn_ok; +} + +#if defined(MICRO) && !defined(AMIGA) +int +pfxfn_IBM_(int optidx UNUSED, int req, boolean negated UNUSED, + char *opts, char *op UNUSED) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + opts[0] = '\0'; + return optn_ok; + } + return optn_ok; +} +#endif + +/* + * General boolean option handler + * (Use optidx to reference the specific option) + */ + +int +optfn_boolean( + int optidx, int req, boolean negated, + char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + boolean nosexchange = FALSE; + int ln = 0; + + if (!allopt[optidx].addr) + return optn_ok; /* silent retreat */ + + /* option that must come from config file? */ + if (!go.opt_initial && (allopt[optidx].setwhere == set_in_config)) + return optn_err; + + /* options that must NOT come from config file */ + if (go.opt_initial && allopt[optidx].setwhere == set_wiznofuz) + return optn_err; + + op = string_for_opt(opts, TRUE); + if (op != empty_optstr) { + if (negated) { + config_error_add( + "Negated boolean '%s' should not have a parameter", + allopt[optidx].name); + return optn_silenterr; + } + /* length is greater than 0 or we wouldn't have gotten here */ + ln = (int) strlen(op); + if (!strncmpi(op, "true", ln) + || !strncmpi(op, "yes", ln) + || !strcmpi(op, "on") + || (digit(*op) && atoi(op) == 1)) { + negated = FALSE; + } else if (!strncmpi(op, "false", ln) + || !strncmpi(op, "no", ln) + || !strcmpi(op, "off") + || (digit(*op) && atoi(op) == 0)) { + negated = TRUE; + } else if (!allopt[optidx].valok) { + config_error_add("'%s' is not valid for a boolean", opts); + return optn_silenterr; + } + } + if (iflags.debug_fuzzer && !go.opt_initial) { + /* don't randomly toggle this/these */ + if ((optidx == opt_silent) + || (optidx == opt_perm_invent)) + return optn_ok; + } + /* Before the change */ + switch (optidx) { + case opt_female: + if (!strncmpi(opts, "female", max(ln, 3))) { + if (!go.opt_initial && flags.female == negated) { + nosexchange = TRUE; + } else { + flags.initgend = flags.female = !negated; + return optn_ok; + } + } + if (!strncmpi(opts, "male", max(ln, 3))) { + if (!go.opt_initial && flags.female != negated) { + nosexchange = TRUE; + } else { + flags.initgend = flags.female = negated; + return optn_ok; + } + } + break; + case opt_perm_invent: + if (!negated && !go.opt_initial && !can_set_perm_invent()) + return optn_silenterr; + break; + default: + break; + } + /* this dates from when 'O' prompted for a line of options text + rather than use a menu to control access to which options can + be modified during play; it was possible to attempt to use + 'O' to specify female or negate male when playing as male or + to specify male or negate female when playing as female; + options processing rejects that for !opt_initial; not possible + now but kept in case someone brings the old 'O' behavior back */ + if (nosexchange) { + /* can't arbitrarily change sex after game has started; + magic (amulet or polymorph) is required for that */ + config_error_add("'%s' is not anatomically possible.", opts); + return optn_silenterr; + } + + *(allopt[optidx].addr) = !negated; /* <==== SET IT HERE */ + + /* After the change */ + switch (optidx) { + case opt_pauper: + /* pauper implies nudist */ + u.uroleplay.nudist = u.uroleplay.pauper; + break; + case opt_ascii_map: + iflags.wc_tiled_map = negated; + break; + case opt_tiled_map: + iflags.wc_ascii_map = negated; + break; + case opt_hilite_pet: +#if defined(TTY_GRAPHICS) || defined(CURSES_GRAPHICS) + if (WINDOWPORT(tty) || WINDOWPORT(curses)) { + /* if we're enabling hilite_pet and petattr isn't set, + set it to Inverse; if we're disabling, leave petattr + alone so that re-enabling will get current value back + */ + if (iflags.hilite_pet && !iflags.wc2_petattr) + iflags.wc2_petattr = ATR_INVERSE; + } +#endif + go.opt_need_redraw = TRUE; + break; +#ifndef IDLECHECKPOINT + case opt_idlecheckpoint: + pline("There is no underlying support for 'idlecheckpoint'" + " compiled in."); + iflags.idlecheckpoint = FALSE; + give_opt_msg = FALSE; + break; +#endif + default: + break; + } + + /* only do processing below if setting with doset() */ + + if (go.opt_initial) + return optn_ok; + + switch (optidx) { + case opt_terrainstatus: + classify_terrain(); /* bring iflags.terrain_typ up to date */ + FALLTHROUGH; + /*FALLTHRU*/ + case opt_weaponstatus: + case opt_armorstatus: + if (!wc2_supported(allopt[optidx].name)) { + /* not actually an error */ + config_error_add("'%s' is not supported.", + allopt[optidx].name); + return optn_ok; + } + FALLTHROUGH; + /*FALLTHRU*/ + case opt_showscore: + case opt_showvers: + case opt_showexp: + case opt_time: + if (VIA_WINDOWPORT()) + status_initialize(REASSESS_ONLY); + disp.botl = TRUE; + break; + case opt_fixinv: + case opt_price_quotes: + case opt_sortpack: + case opt_implicit_uncursed: + case opt_wizweight: + if (!flags.invlet_constant) + reassign(); + update_inventory(); + break; + case opt_lit_corridor: + case opt_dark_room: + /* + * All corridor squares seen via night vision or + * candles & lamps change. Update them by calling + * newsym() on them. Don't do this if we are + * initializing the options --- the vision system + * isn't set up yet. + */ + vision_recalc(2); /* shut down vision */ + gv.vision_full_recalc = 1; /* delayed recalc */ + if (iflags.use_color) + go.opt_need_redraw = TRUE; /* darkroom refresh */ + break; + case opt_wizmgender: + case opt_showrace: + case opt_use_inverse: + case opt_hilite_pile: + case opt_perm_invent: + case opt_ascii_map: + case opt_tiled_map: + go.opt_need_redraw = TRUE; + go.opt_need_glyph_reset = TRUE; + break; + case opt_hitpointbar: + if (VIA_WINDOWPORT()) { + /* [is reassessment really needed here?] */ + status_initialize(REASSESS_ONLY); + go.opt_need_redraw = TRUE; +#ifdef QT_GRAPHICS + } else if (WINDOWPORT(Qt)) { + /* Qt doesn't support HILITE_STATUS or FLUSH_STATUS so fails + VIA_WINDOWPORT(), but it does support WC2_HITPOINTBAR */ + disp.botlx = TRUE; +#endif + } + break; + case opt_color: +#ifdef TOS + if (iflags.BIOS) { + if (colors_changed) + restore_colors(); + else + set_colors(); + } +#endif + go.opt_need_redraw = TRUE; + go.opt_need_glyph_reset = TRUE; + break; + case opt_customcolors: + go.opt_reset_customcolors = TRUE; + break; + case opt_customsymbols: + go.opt_reset_customsymbols = TRUE; + break; + case opt_menucolors: + case opt_guicolor: + update_inventory(); + go.opt_need_promptstyle = TRUE; + break; + case opt_mention_decor: + iflags.prev_decor = STONE; + break; + case opt_rest_on_space: + update_rest_on_space(); + break; + case opt_accessiblemsg: + a11y.msg_loc.x = a11y.msg_loc.y = 0; + break; + default: + break; + } + + /* boolean value has been toggled but some option changes can + still be pending at this point (mainly for opt_need_redraw); + give the toggled message now regardless */ + if (give_opt_msg) + pline("'%s' option toggled %s.", allopt[optidx].name, + !negated ? "on" : "off"); + + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + opts[0] = '\0'; + return optn_ok; + } + return optn_ok; +} + +staticfn int +spcfn_misc_menu_cmd(int midx, int req, boolean negated, char *opts, char *op) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + if (negated) { + bad_negation(default_menu_cmd_info[midx].name, FALSE); + return optn_err; + } else if ((op = string_for_opt(opts, FALSE)) != empty_optstr) { + char c = txt2key(op); + + if (illegal_menu_cmd_key((uchar) c)) + return optn_err; + add_menu_cmd_alias(c, default_menu_cmd_info[midx].cmd); + } + return optn_ok; + } + if (req == get_val || req == get_cnf_val) { + opts[0] = '\0'; + return optn_ok; + } + return optn_ok; +} + + +/* + ********************************** + * + * Special per-option handlers + * + ********************************** + */ + +/* test whether 'perm_invent' can be toggled On */ +staticfn boolean +can_set_perm_invent(void) +{ + /* + * Assumption: only called when iflags.perm_invent is False + * and is about to be changed to True. + */ + uchar old_perminv_mode = iflags.perminv_mode; + + if (!(windowprocs.wincap & WC_PERM_INVENT)) { +#ifdef TTY_GRAPHICS +#ifdef TTY_PERM_INVENT + /* check tty, not necessarily the active window port; + windows early startup can still be set to safeprocs */ + if (!check_tty_wincap(WC_PERM_INVENT)) +#endif +#endif + return FALSE; + } + + if (iflags.perminv_mode == InvOptNone) + iflags.perminv_mode = InvOptOn; + +#ifdef TTY_PERM_INVENT + if ((WINDOWPORT(tty) + ) && !go.opt_initial) { + perm_invent_toggled(FALSE); + /* perm_invent_toggled() + -> sync_perminvent() + -> tty_create_nhwindow(NHW_PERMINVENT) + gives feedback for failure (terminal too small) */ + if (WIN_INVEN == WIN_ERR) { + iflags.perminv_mode = old_perminv_mode; + return FALSE; + } + } +#else + nhUse(old_perminv_mode); +#endif + return TRUE; +} + + +#ifdef TTY_PERM_INVENT +void +check_perm_invent_again(void) +{ + if (iflags.perm_invent_pending) { + iflags.perm_invent = FALSE; + if (can_set_perm_invent()) + iflags.perm_invent = TRUE; + iflags.perm_invent_pending = FALSE; + } +} +#endif + +staticfn int +handler_menustyle(void) +{ + winid tmpwin; + anything any; + boolean chngd; + int i, n, old_menu_style = flags.menu_style; + char buf[BUFSZ], sep = iflags.menu_tab_sep ? '\t' : ' '; + menu_item *style_pick = (menu_item *) 0; + int clr = NO_COLOR; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + for (i = 0; i < SIZE(menutype); i++) { + Sprintf(buf, "%-12.12s%c%.60s", menutype[i][0], sep, menutype[i][1]); + any.a_int = i + 1; + add_menu(tmpwin, &nul_glyphinfo, &any, *buf, 0, ATR_NONE, clr, buf, + (i == flags.menu_style) ? MENU_ITEMFLAGS_SELECTED + : MENU_ITEMFLAGS_NONE); + /* second line is prefixed by spaces that "c - " would use */ + Sprintf(buf, "%4s%-12.12s%c%.60s", "", "", sep, menutype[i][2]); + add_menu_str(tmpwin, buf); + } + end_menu(tmpwin, "Select menustyle:"); + n = select_menu(tmpwin, PICK_ONE, &style_pick); + if (n > 0) { + i = style_pick[0].item.a_int - 1; + /* if there are two picks, use the one that wasn't pre-selected */ + if (n > 1 && i == old_menu_style) + i = style_pick[1].item.a_int - 1; + flags.menu_style = i; + free((genericptr_t) style_pick); + } + destroy_nhwindow(tmpwin); + chngd = (flags.menu_style != old_menu_style); + if (chngd || flags.verbose) + pline("'menustyle' %s \"%s\".", chngd ? "changed to" : "is still", + menutype[(int) flags.menu_style][0]); + return optn_ok; +} + +staticfn int +handler_align_misc(int optidx) +{ + winid tmpwin; + anything any; + menu_item *window_pick = (menu_item *) 0; + char abuf[BUFSZ]; + int clr = NO_COLOR; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + any.a_int = ALIGN_TOP; + add_menu(tmpwin, &nul_glyphinfo, &any, 't', 0, ATR_NONE, clr, "top", + MENU_ITEMFLAGS_NONE); + any.a_int = ALIGN_BOTTOM; + add_menu(tmpwin, &nul_glyphinfo, &any, 'b', 0, ATR_NONE, clr, "bottom", + MENU_ITEMFLAGS_NONE); + any.a_int = ALIGN_LEFT; + add_menu(tmpwin, &nul_glyphinfo, &any, 'l', 0, ATR_NONE, clr, "left", + MENU_ITEMFLAGS_NONE); + any.a_int = ALIGN_RIGHT; + add_menu(tmpwin, &nul_glyphinfo, &any, 'r', 0, ATR_NONE, clr, "right", + MENU_ITEMFLAGS_NONE); + Sprintf(abuf, "Select %s window placement relative to the map:", + (optidx == opt_align_message) ? "message" : "status"); + end_menu(tmpwin, abuf); + if (select_menu(tmpwin, PICK_ONE, &window_pick) > 0) { + if (optidx == opt_align_message) + iflags.wc_align_message = window_pick->item.a_int; + else + iflags.wc_align_status = window_pick->item.a_int; + free((genericptr_t) window_pick); + } + destroy_nhwindow(tmpwin); + return optn_ok; +} + +staticfn int +handler_autounlock(int optidx) +{ + winid tmpwin; + anything any; + boolean chngd; + unsigned oldflags = flags.autounlock; + const char *optname = allopt[optidx].name; + char buf[BUFSZ], sep = iflags.menu_tab_sep ? '\t' : ' '; + menu_item *window_pick = (menu_item *) 0; + int i, n, presel, res = optn_ok; + int clr = NO_COLOR; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + for (i = 0; i < SIZE(unlocktypes); ++i) { + Sprintf(buf, "%-10.10s%c%.40s", + unlocktypes[i][0], sep, unlocktypes[i][1]); + presel = (flags.autounlock & (1 << i)); + any.a_int = i + 1; + add_menu(tmpwin, &nul_glyphinfo, &any, *unlocktypes[i][0], 0, + ATR_NONE, clr, buf, + (presel ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE)); + } + Sprintf(buf, "Select '%.20s' actions:", optname); + end_menu(tmpwin, buf); + n = select_menu(tmpwin, PICK_ANY, &window_pick); + if (n > 0) { + unsigned newflags = 0; + + for (i = 0; i < n; ++i) + newflags |= (1 << (window_pick[i].item.a_int - 1)); + flags.autounlock = newflags; + free((genericptr_t) window_pick); + } else if (n == 0) { /* nothing was picked but menu wasn't cancelled */ + /* something that was preselected got unselected, leaving nothing; + treat that as picking 'none' (even though 'none' is no longer + among the choices) */ + flags.autounlock = 0; + } + destroy_nhwindow(tmpwin); + chngd = (flags.autounlock != oldflags); + if ((chngd || flags.verbose) && give_opt_msg) { + optfn_autounlock(optidx, get_val, FALSE, buf, (char *) NULL); + pline("'%s' %s '%s'.", optname, + chngd ? "changed to" : "is still", buf); + } + return res; +} + +staticfn int +handler_disclose(void) +{ + winid tmpwin; + anything any; + int i, n; + char buf[BUFSZ]; + /* order of disclose_names[] must correspond to + disclosure_options in decl.c */ + static const char *const disclosure_names[] = { + "inventory", "attributes", "vanquished", + "genocides", "conduct", "overview", + }; + int disc_cat[NUM_DISCLOSURE_OPTIONS]; + int pick_cnt, pick_idx, opt_idx; + char c; + menu_item *disclosure_pick = (menu_item *) 0; + int clr = NO_COLOR; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + for (i = 0; i < NUM_DISCLOSURE_OPTIONS; i++) { + Sprintf(buf, "%-12s[%c%c]", disclosure_names[i], + flags.end_disclose[i], disclosure_options[i]); + any.a_int = i + 1; + add_menu(tmpwin, &nul_glyphinfo, &any, disclosure_options[i], + 0, ATR_NONE, clr, buf, MENU_ITEMFLAGS_NONE); + disc_cat[i] = 0; + } + end_menu(tmpwin, "Change which disclosure options categories:"); + pick_cnt = select_menu(tmpwin, PICK_ANY, &disclosure_pick); + if (pick_cnt > 0) { + for (pick_idx = 0; pick_idx < pick_cnt; ++pick_idx) { + opt_idx = disclosure_pick[pick_idx].item.a_int - 1; + disc_cat[opt_idx] = 1; + } + free((genericptr_t) disclosure_pick); + disclosure_pick = (menu_item *) 0; + } + destroy_nhwindow(tmpwin); + + for (i = 0; i < NUM_DISCLOSURE_OPTIONS; i++) { + if (disc_cat[i]) { + c = flags.end_disclose[i]; + Sprintf(buf, "Disclosure options for %s:", + disclosure_names[i]); + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + /* 'y','n',and '+' work as alternate selectors; '-' doesn't */ + any.a_char = DISCLOSE_NO_WITHOUT_PROMPT; + add_menu(tmpwin, &nul_glyphinfo, &any, 0, + any.a_char, ATR_NONE, clr, + "Never disclose, without prompting", + (c == any.a_char) ? MENU_ITEMFLAGS_SELECTED + : MENU_ITEMFLAGS_NONE); + any.a_char = DISCLOSE_YES_WITHOUT_PROMPT; + add_menu(tmpwin, &nul_glyphinfo, &any, 0, + any.a_char, ATR_NONE, clr, + "Always disclose, without prompting", + (c == any.a_char) ? MENU_ITEMFLAGS_SELECTED + : MENU_ITEMFLAGS_NONE); + if (*disclosure_names[i] == 'v' || *disclosure_names[i] == 'g') { + any.a_char = DISCLOSE_SPECIAL_WITHOUT_PROMPT; /* '#' */ + add_menu(tmpwin, &nul_glyphinfo, &any, 0, + any.a_char, ATR_NONE, clr, + "Always disclose, pick sort order from menu", + (c == any.a_char) ? MENU_ITEMFLAGS_SELECTED + : MENU_ITEMFLAGS_NONE); + } + any.a_char = DISCLOSE_PROMPT_DEFAULT_NO; + add_menu(tmpwin, &nul_glyphinfo, &any, 0, + any.a_char, ATR_NONE, clr, + "Prompt, with default answer of \"No\"", + (c == any.a_char) ? MENU_ITEMFLAGS_SELECTED + : MENU_ITEMFLAGS_NONE); + any.a_char = DISCLOSE_PROMPT_DEFAULT_YES; + add_menu(tmpwin, &nul_glyphinfo, &any, 0, + any.a_char, ATR_NONE, clr, + "Prompt, with default answer of \"Yes\"", + (c == any.a_char) ? MENU_ITEMFLAGS_SELECTED + : MENU_ITEMFLAGS_NONE); + if (*disclosure_names[i] == 'v' || *disclosure_names[i] == 'g') { + any.a_char = DISCLOSE_PROMPT_DEFAULT_SPECIAL; /* '?' */ + add_menu(tmpwin, &nul_glyphinfo, &any, 0, + any.a_char, ATR_NONE, clr, + "Prompt, with default answer of \"Ask\" to request sort menu", + (c == any.a_char) ? MENU_ITEMFLAGS_SELECTED + : MENU_ITEMFLAGS_NONE); + } + end_menu(tmpwin, buf); + n = select_menu(tmpwin, PICK_ONE, &disclosure_pick); + if (n > 0) { + flags.end_disclose[i] = disclosure_pick[0].item.a_char; + if (n > 1 && flags.end_disclose[i] == c) + flags.end_disclose[i] = disclosure_pick[1].item.a_char; + free((genericptr_t) disclosure_pick); + } + destroy_nhwindow(tmpwin); + } + } + return optn_ok; +} + +staticfn int +handler_menu_headings(void) +{ + boolean gotca = query_color_attr(&iflags.menu_headings, + "How to highlight menu headings:"); + + if (gotca) { + /* header highlighting affects persistent inventory display */ + if (iflags.perm_invent) + update_inventory(); + } + adjust_menu_promptstyle(WIN_INVEN, &iflags.menu_headings); + return optn_ok; +} + +staticfn int +handler_menu_objsyms(void) +{ + winid tmpwin; + anything any; + char buf[BUFSZ]; + menu_item *picklist = (menu_item *) 0; + const char sep = iflags.menu_tab_sep ? '\t' : ' '; + int i, j, n, clr = NO_COLOR; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + for (i = 0; i < SIZE(objsymvals); ++i) { + Snprintf(buf, sizeof buf, "%-12.12s%c%.60s", + objsymvals[i].nam, sep, objsymvals[i].descr); + any.a_int = i + 1; + j = objsymvals[i].num; + add_menu(tmpwin, &nul_glyphinfo, &any, '0' + i, *buf, + ATR_NONE, clr, buf, + (j == iflags.menuobjsyms) ? MENU_ITEMFLAGS_SELECTED + : MENU_ITEMFLAGS_NONE); + } + end_menu(tmpwin, "Set object symbols in menus to what?"); + n = select_menu(tmpwin, PICK_ONE, &picklist); + if (n > 0) { + i = picklist[0].item.a_int - 1; + /* if there are two picks, use the one that wasn't pre-selected */ + if (n > 1 && i == iflags.menuobjsyms) + i = picklist[1].item.a_int - 1; + set_menuobjsyms_flags(i); + free((genericptr_t) picklist); + } + destroy_nhwindow(tmpwin); + return optn_ok; +} + +staticfn int +handler_msg_window(void) +{ +#if PREV_MSGS /* tty or curses */ + winid tmpwin; + anything any; + boolean is_tty = WINDOWPORT(tty), is_curses = WINDOWPORT(curses); + int clr = NO_COLOR; + + if (is_tty || is_curses) { + /* by Christian W. Cooper */ + boolean chngd; + int i, n; + char buf[BUFSZ], c, + sep = iflags.menu_tab_sep ? '\t' : ' ', + old_prevmsg_window = iflags.prevmsg_window; + menu_item *window_pick = (menu_item *) 0; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + + for (i = 0; i < SIZE(menutype); i++) { + if (i < 2 && is_curses) + continue; + Sprintf(buf, "%-12.12s%c%.60s", msgwind[i][0], sep, + msgwind[i][1]); + any.a_char = c = *msgwind[i][0]; + add_menu(tmpwin, &nul_glyphinfo, &any, *buf, 0, + ATR_NONE, clr, buf, + (c == iflags.prevmsg_window) ? MENU_ITEMFLAGS_SELECTED + : MENU_ITEMFLAGS_NONE); + /* second line is prefixed by spaces that "c - " would use */ + Sprintf(buf, "%4s%-12.12s%c%.60s", "", "", sep, msgwind[i][2]); + add_menu_str(tmpwin, buf); + } + end_menu(tmpwin, "Select message history display type:"); + n = select_menu(tmpwin, PICK_ONE, &window_pick); + if (n > 0) { + c = window_pick[0].item.a_char; + /* if there are two picks, use the one that wasn't pre-selected */ + if (n > 1 && c == old_prevmsg_window) + c = window_pick[1].item.a_char; + iflags.prevmsg_window = c; + free((genericptr_t) window_pick); + } + destroy_nhwindow(tmpwin); + chngd = (iflags.prevmsg_window != old_prevmsg_window); + if (chngd || flags.verbose) { + (void) optfn_msg_window(opt_msg_window, get_val, + FALSE, buf, empty_optstr); + pline("'msg_window' %.20s \"%.20s\".", + chngd ? "changed to" : "is still", buf); + } + } else +#endif /* PREV_MSGS (for tty or curses) */ + pline("'%s' option is not supported for '%s'.", + allopt[opt_msg_window].name, windowprocs.name); + return optn_ok; +} + +staticfn int +handler_number_pad(void) +{ + winid tmpwin; + anything any; + int i; + static const char *const npchoices[] = { + " 0 (off)", " 1 (on)", " 2 (on, MSDOS compatible)", + " 3 (on, phone-style digit layout)", + " 4 (on, phone-style layout, MSDOS compatible)", + "-1 (off, 'z' to move upper-left, 'y' to zap wands)" + }; + menu_item *mode_pick = (menu_item *) 0; + int clr = NO_COLOR; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + for (i = 0; i < SIZE(npchoices); i++) { + any.a_int = i + 1; + add_menu(tmpwin, &nul_glyphinfo, &any, 'a' + i, '0' + i, + ATR_NONE, clr, npchoices[i], MENU_ITEMFLAGS_NONE); + } + end_menu(tmpwin, "Select number_pad mode:"); + if (select_menu(tmpwin, PICK_ONE, &mode_pick) > 0) { + switch (mode_pick->item.a_int - 1) { + case 0: + iflags.num_pad = FALSE; + iflags.num_pad_mode = 0; + break; + case 1: + iflags.num_pad = TRUE; + iflags.num_pad_mode = 0; + break; + case 2: + iflags.num_pad = TRUE; + iflags.num_pad_mode = 1; + break; + case 3: + iflags.num_pad = TRUE; + iflags.num_pad_mode = 2; + break; + case 4: + iflags.num_pad = TRUE; + iflags.num_pad_mode = 3; + break; + /* last menu choice: number_pad == -1 */ + case 5: + iflags.num_pad = FALSE; + iflags.num_pad_mode = 1; + break; + } + reset_commands(FALSE); + number_pad(iflags.num_pad ? 1 : 0); + free((genericptr_t) mode_pick); + } + destroy_nhwindow(tmpwin); + return optn_ok; +} + +staticfn int +handler_paranoid_confirmation(void) +{ + winid tmpwin; + anything any; + int i; + char mkey, mbuf[QBUFSZ], ebuf[BUFSZ], cbuf[QBUFSZ]; + const char *explain, *cmdnm; + menu_item *paranoia_picks = (menu_item *) 0; + int clr = NO_COLOR; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + for (i = 0; paranoia[i].flagmask != 0; ++i) { + if (paranoia[i].flagmask == PARANOID_BONES && !wizard) + continue; + /* the 'swim' choice mentions the 'm' movement prefix in its + explanation; if that's been bound to something else or been + unbound altogether, substitute the replacement in the text */ + explain = paranoia[i].explain; + if (strstri(explain, "'m'") + && (mkey = cmd_from_func(do_reqmenu)) != 'm') { + if (mkey) { /* key for 'm' prefix */ + Sprintf(mbuf, "'%.9s'", visctrl(mkey)); /* .5 is enough */ + } else { /* extended command name for 'm' prefix */ + cmdnm = cmdname_from_func(do_reqmenu, cbuf, TRUE); + if (!cmdnm) + cmdnm = "reqmenu"; + Sprintf(mbuf, "'%s%.31s'", (*cmdnm != '#') ? "#" : "", cmdnm); + } + explain = strsubst(strcpy(ebuf, explain), "'m'", mbuf); + } + any.a_int = paranoia[i].flagmask; + add_menu(tmpwin, &nul_glyphinfo, &any, *paranoia[i].argname, + 0, ATR_NONE, clr, explain, + (flags.paranoia_bits & paranoia[i].flagmask) + ? MENU_ITEMFLAGS_SELECTED + : MENU_ITEMFLAGS_NONE); + } + end_menu(tmpwin, "Actions requiring extra confirmation:"); + i = select_menu(tmpwin, PICK_ANY, ¶noia_picks); + if (i >= 0) { + /* player didn't cancel; we reset all the paranoia options + here even if there were no items picked, since user + could have toggled off preselected ones to end up with 0 */ + flags.paranoia_bits = 0; + if (i > 0) { + /* at least 1 item set, either preselected or newly picked */ + while (--i >= 0) + flags.paranoia_bits |= paranoia_picks[i].item.a_int; + free((genericptr_t) paranoia_picks); + } + } + destroy_nhwindow(tmpwin); + return optn_ok; +} + +staticfn int +handler_perminv_mode(void) +{ + winid tmpwin; + anything any; + char let, buf[BUFSZ], sepbuf[10]; + const char *pi0, *pi1; + menu_item *pi_pick = (menu_item *) 0; + boolean old_perm_invent = iflags.perm_invent; + int i, n, old_pi = (int) iflags.perminv_mode, new_pi = old_pi, + widest = !WINDOWPORT(tty) ? 8 : 11; /* "in-use__" or "full+grid__" */ + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + for (i = 0; i < SIZE(perminv_modes); ++i) { + if (!(pi0 = perminv_modes[i][0])) + continue; +#ifdef TTY_PERM_INVENT + if (strstri(pi0, "+grid") != 0 && !WINDOWPORT(tty)) + continue; +#endif + pi1 = perminv_modes[i][1]; + if (!iflags.menu_tab_sep) { + int numspaces = widest - (int) strlen(pi0); + + Sprintf(sepbuf, "%*s", max(numspaces, 1), " "); + } else { + Strcpy(sepbuf, "\t"); + } + Sprintf(buf, "%s%s%s", pi0, sepbuf, perminv_modes[i][2]); + let = ((i & (int) InvSparse) != 0) ? highc(pi1[0]) : pi0[0]; + any.a_int = i + 1; + add_menu(tmpwin, &nul_glyphinfo, &any, let, '0' + i, + ATR_NONE, NO_COLOR, + buf, (i == old_pi) ? MENU_ITEMFLAGS_SELECTED + : MENU_ITEMFLAGS_NONE); + } + end_menu(tmpwin, "Choose permanent inventory mode:"); + n = select_menu(tmpwin, PICK_ONE, &pi_pick); + destroy_nhwindow(tmpwin); + if (n > 0) { + new_pi = pi_pick[0].item.a_int - 1; + if (n > 1 && new_pi == old_pi) + new_pi = pi_pick[1].item.a_int - 1; + free((genericptr_t) pi_pick); + iflags.perminv_mode = new_pi; + } + if (n >= 0) { /* not ESC */ + buf[0] = '\0'; + (void) optfn_perminv_mode(opt_perm_invent, get_val, FALSE, buf, NULL); + pline("'perminv_mode' %s '%s' (%s).", + (new_pi != old_pi) ? "changed to" : "is still", + perminv_modes[new_pi][0], buf); + if (new_pi != InvOptNone && !old_perm_invent) + iflags.perm_invent = can_set_perm_invent(); + else if (new_pi == InvOptNone && old_perm_invent) + iflags.perm_invent = FALSE; + + if (new_pi != old_pi || iflags.perm_invent != old_perm_invent) { +#ifdef TTY_PERM_INVENT + /* FIXME: TTY_PERM_INVENT will blank WIN_INVEN when changing + perminv_mode while perm_invent is already on; to remedy that, + turn it off and then back on when already on */ + if (WINDOWPORT(tty) && iflags.perm_invent && old_perm_invent) { + perm_invent_toggled(TRUE); /*TEMP?*/ + perm_invent_toggled(FALSE); /*TEMP?*/ + } +#endif + go.opt_need_redraw = TRUE; + } + } + return optn_ok; +} + +staticfn int +handler_pickup_burden(void) +{ + winid tmpwin; + anything any; + int i; + const char *burden_name, *burden_letters = "ubsntl"; + menu_item *burden_pick = (menu_item *) 0; + int clr = NO_COLOR; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + for (i = 0; i < SIZE(burdentype); i++) { + burden_name = burdentype[i]; + any.a_int = i + 1; + add_menu(tmpwin, &nul_glyphinfo, &any, burden_letters[i], + 0, ATR_NONE, clr, burden_name, MENU_ITEMFLAGS_NONE); + } + end_menu(tmpwin, "Select encumbrance level:"); + if (select_menu(tmpwin, PICK_ONE, &burden_pick) > 0) { + flags.pickup_burden = burden_pick->item.a_int - 1; + free((genericptr_t) burden_pick); + } + destroy_nhwindow(tmpwin); + return optn_ok; +} + +staticfn int +handler_pickup_types(void) +{ + char buf[BUFSZ]; + + /* parseoptions will prompt for the list of types */ + (void) parseoptions(strcpy(buf, "pickup_types"), FALSE, FALSE); + return optn_ok; +} + +staticfn int +handler_runmode(void) +{ + winid tmpwin; + anything any; + int i; + const char *mode_name; + menu_item *mode_pick = (menu_item *) 0; + int clr = NO_COLOR; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + for (i = 0; i < SIZE(runmodes); i++) { + mode_name = runmodes[i]; + any.a_int = i + 1; + add_menu(tmpwin, &nul_glyphinfo, &any, *mode_name, + 0, ATR_NONE, clr, mode_name, MENU_ITEMFLAGS_NONE); + } + end_menu(tmpwin, "Select run/travel display mode:"); + if (select_menu(tmpwin, PICK_ONE, &mode_pick) > 0) { + flags.runmode = mode_pick->item.a_int - 1; + free((genericptr_t) mode_pick); + } + destroy_nhwindow(tmpwin); + return optn_ok; +} + +staticfn int +handler_petattr(void) +{ + int tmp + = query_attr("Select pet highlight attribute", iflags.wc2_petattr); + + if (tmp != -1) { + iflags.wc2_petattr = tmp; + iflags.hilite_pet = (iflags.wc2_petattr != ATR_NONE); + if (!go.opt_initial) + go.opt_need_redraw = TRUE; + } + return optn_ok; +} + +staticfn int +handler_sortloot(void) +{ + winid tmpwin; + anything any; + int i, n; + const char *sortl_name; + menu_item *sortl_pick = (menu_item *) 0; + int clr = NO_COLOR; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + for (i = 0; i < SIZE(sortltype); i++) { + sortl_name = sortltype[i]; + any.a_char = *sortl_name; + add_menu(tmpwin, &nul_glyphinfo, &any, *sortl_name, + 0, ATR_NONE, clr, + sortl_name, (flags.sortloot == *sortl_name) + ? MENU_ITEMFLAGS_SELECTED + : MENU_ITEMFLAGS_NONE); + } + end_menu(tmpwin, "Select loot sorting type:"); + n = select_menu(tmpwin, PICK_ONE, &sortl_pick); + if (n > 0) { + char c = sortl_pick[0].item.a_char; + + if (n > 1 && c == flags.sortloot) + c = sortl_pick[1].item.a_char; + flags.sortloot = c; + /* changing to or from 'f' affects persistent inventory display */ + if (iflags.perm_invent) + update_inventory(); + free((genericptr_t) sortl_pick); + } + destroy_nhwindow(tmpwin); + return optn_ok; +} + +staticfn int +handler_whatis_coord(void) +{ + winid tmpwin; + anything any; + char buf[BUFSZ]; + menu_item *window_pick = (menu_item *) 0; + int pick_cnt; + char gpc = iflags.getpos_coords; + int clr = NO_COLOR; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + any.a_char = GPCOORDS_COMPASS; + add_menu(tmpwin, &nul_glyphinfo, &any, GPCOORDS_COMPASS, + 0, ATR_NONE, clr, + "compass ('east' or '3s' or '2n,4w')", + (gpc == GPCOORDS_COMPASS) + ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); + any.a_char = GPCOORDS_COMFULL; + add_menu(tmpwin, &nul_glyphinfo, &any, GPCOORDS_COMFULL, + 0, ATR_NONE, clr, + "full compass ('east' or '3south' or '2north,4west')", + (gpc == GPCOORDS_COMFULL) + ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); + any.a_char = GPCOORDS_MAP; + add_menu(tmpwin, &nul_glyphinfo, &any, GPCOORDS_MAP, + 0, ATR_NONE, clr, "map ", + (gpc == GPCOORDS_MAP) + ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); + any.a_char = GPCOORDS_SCREEN; + add_menu(tmpwin, &nul_glyphinfo, &any, GPCOORDS_SCREEN, + 0, ATR_NONE, clr, "screen [row,column]", + (gpc == GPCOORDS_SCREEN) + ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); + any.a_char = GPCOORDS_NONE; + add_menu(tmpwin, &nul_glyphinfo, &any, GPCOORDS_NONE, + 0, ATR_NONE, clr, "none (no coordinates displayed)", + (gpc == GPCOORDS_NONE) + ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); + add_menu_str(tmpwin, ""); + Sprintf(buf, "map: upper-left: <%d,%d>, lower-right: <%d,%d>%s", + 1, 0, COLNO - 1, ROWNO - 1, + flags.verbose ? "; column 0 unused, off left edge" : ""); + add_menu_str(tmpwin, buf); + if (strcmp(windowprocs.name, "tty")) /* only show for non-tty */ + add_menu_str(tmpwin, + "screen: row is offset to accommodate tty interface's use of top line"); +#if COLNO == 80 +#define COL80ARG flags.verbose ? "; column 80 is not used" : "" +#else +#define COL80ARG "" +#endif + Sprintf(buf, "screen: upper-left: [%02d,%02d], lower-right: [%d,%d]%s", + 0 + 2, 1, ROWNO - 1 + 2, COLNO - 1, COL80ARG); +#undef COL80ARG + add_menu_str(tmpwin, buf); + add_menu_str(tmpwin, ""); + end_menu(tmpwin, + "Select coordinate display when auto-describing a map position:"); + if ((pick_cnt = select_menu(tmpwin, PICK_ONE, &window_pick)) > 0) { + iflags.getpos_coords = window_pick[0].item.a_char; + /* PICK_ONE doesn't unselect preselected entry when + selecting another one */ + if (pick_cnt > 1 && iflags.getpos_coords == gpc) + iflags.getpos_coords = window_pick[1].item.a_char; + free((genericptr_t) window_pick); + } + destroy_nhwindow(tmpwin); + return optn_ok; +} + +staticfn int +handler_whatis_filter(void) +{ + winid tmpwin; + anything any; + menu_item *window_pick = (menu_item *) 0; + int pick_cnt; + char gfilt = iflags.getloc_filter; + int clr = NO_COLOR; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + any.a_char = (GFILTER_NONE + 1); + add_menu(tmpwin, &nul_glyphinfo, &any, 'n', + 0, ATR_NONE, clr, "no filtering", + (gfilt == GFILTER_NONE) + ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); + any.a_char = (GFILTER_VIEW + 1); + add_menu(tmpwin, &nul_glyphinfo, &any, 'v', + 0, ATR_NONE, clr, "in view only", + (gfilt == GFILTER_VIEW) + ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); + any.a_char = (GFILTER_AREA + 1); + add_menu(tmpwin, &nul_glyphinfo, &any, 'a', + 0, ATR_NONE, clr, "in same area", + (gfilt == GFILTER_AREA) + ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); + end_menu(tmpwin, + "Select location filtering when going for next/previous map position:"); + if ((pick_cnt = select_menu(tmpwin, PICK_ONE, &window_pick)) > 0) { + iflags.getloc_filter = (window_pick[0].item.a_char - 1); + /* PICK_ONE doesn't unselect preselected entry when + selecting another one */ + if (pick_cnt > 1 && iflags.getloc_filter == gfilt) + iflags.getloc_filter = (window_pick[1].item.a_char - 1); + free((genericptr_t) window_pick); + } + destroy_nhwindow(tmpwin); + return optn_ok; +} + +staticfn int +handler_symset(int optidx) +{ + int reslt; + + reslt = do_symset(optidx == opt_roguesymset); /* symbols.c */ + go.opt_need_redraw = TRUE; + return reslt; +} + +staticfn int +handler_autopickup_exception(void) +{ + winid tmpwin; + anything any; + int i; + int opt_idx, numapes = 0; + char apebuf[2 + BUFSZ]; /* so &apebuf[1] is BUFSZ long for getlin() */ + struct autopickup_exception *ape; + int clr = NO_COLOR; + + ape_again: + numapes = count_apes(); + opt_idx = handle_add_list_remove("autopickup exception", numapes); + if (opt_idx == 3) { /* done */ + return TRUE; + } else if (opt_idx == 0) { /* add new */ + /* EDIT_GETLIN: assume user doesn't user want previous + exception used as default input string for this one... */ + apebuf[0] = apebuf[1] = '\0'; + getlin("What new autopickup exception pattern?", &apebuf[1]); + mungspaces(&apebuf[1]); /* regularize whitespace */ + if (apebuf[1] == '\033') + return TRUE; + if (apebuf[1]) { + apebuf[0] = '\"'; + /* guarantee room for \" prefix and \"\0 suffix; + -2 is good enough for apebuf[] but -3 makes + sure the whole thing fits within normal BUFSZ */ + apebuf[sizeof apebuf - 2] = '\0'; + Strcat(apebuf, "\""); + add_autopickup_exception(apebuf); + } + goto ape_again; + } else { /* list (1) or remove (2) */ + int pick_idx, pick_cnt; + menu_item *pick_list = (menu_item *) 0; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + if (numapes) { + ape = ga.apelist; + any = cg.zeroany; + add_menu_heading(tmpwin, + "Always pickup '<'; never pickup '>'"); + for (i = 0; i < numapes && ape; i++) { + any.a_void = (opt_idx == 1) ? 0 : ape; + /* length of pattern plus quotes (plus '<'/'>') is + less than BUFSZ */ + Sprintf(apebuf, "\"%c%s\"", ape->grab ? '<' : '>', + ape->pattern); + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, + ATR_NONE, clr, apebuf, MENU_ITEMFLAGS_NONE); + ape = ape->next; + } + } + Sprintf(apebuf, "%s autopickup exceptions", + (opt_idx == 1) ? "List of" : "Remove which"); + end_menu(tmpwin, apebuf); + pick_cnt = select_menu(tmpwin, + (opt_idx == 1) ? PICK_NONE : PICK_ANY, + &pick_list); + if (pick_cnt > 0) { + for (pick_idx = 0; pick_idx < pick_cnt; ++pick_idx) + remove_autopickup_exception( + (struct autopickup_exception *) + pick_list[pick_idx].item.a_void); + free((genericptr_t) pick_list), pick_list = (menu_item *) 0; + } + destroy_nhwindow(tmpwin); + if (pick_cnt >= 0) + goto ape_again; + } + return optn_ok; +} + +staticfn int +handler_menu_colors(void) +{ + winid tmpwin; + anything any; + char buf[BUFSZ]; + int opt_idx, nmc, mcclr, mcattr; + char mcbuf[BUFSZ]; + int clr = NO_COLOR; + + menucolors_again: + nmc = count_menucolors(); + opt_idx = handle_add_list_remove("menucolor", nmc); + if (opt_idx == 3) { /* done */ + menucolors_done: + /* in case we've made a change which impacts current persistent + inventory window; we don't track whether an actual changed + occurred, so just assume there was one and that it matters; + if we're wrong, a redundant update is cheap... */ + if (iflags.use_menu_color) { + if (iflags.perm_invent) + update_inventory(); + + } + return optn_ok; + + } else if (opt_idx == 0) { /* add new */ + mcbuf[0] = '\0'; + getlin("What new menucolor pattern?", mcbuf); + if (*mcbuf == '\033') + goto menucolors_done; + if (*mcbuf + && test_regex_pattern(mcbuf, "MENUCOLORS regex") + && (mcclr = query_color((char *) 0, NO_COLOR)) != -1 + && (mcattr = query_attr((char *) 0, ATR_NONE)) != -1 + && !add_menu_coloring_parsed(mcbuf, mcclr, mcattr)) { + pline("Error adding the menu color."); + wait_synch(); + } + goto menucolors_again; + + } else { /* list (1) or remove (2) */ + int pick_idx, pick_cnt; + int mc_idx; + unsigned ln; + const char *sattr, *sclr; + menu_item *pick_list = (menu_item *) 0; + struct menucoloring *tmp = gm.menu_colorings; + char clrbuf[QBUFSZ]; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + mc_idx = 0; + while (tmp) { + sattr = attr2attrname(tmp->attr); + sclr = strcpy(clrbuf, clr2colorname(tmp->color)); + (void) strNsubst(clrbuf, " ", "-", 0); + any.a_int = ++mc_idx; + /* construct suffix */ + Sprintf(buf, "\"\"=%s%s%s", sclr, + (tmp->attr != ATR_NONE) ? "&" : "", + (tmp->attr != ATR_NONE) ? sattr : ""); + /* now main string */ + ln = sizeof buf - Strlen(buf) - 1; /* length available */ + Strcpy(mcbuf, "\""); + if (strlen(tmp->origstr) > ln) + Strcat(strncat(mcbuf, tmp->origstr, ln - 3), "..."); + else + Strcat(mcbuf, tmp->origstr); + /* combine main string and suffix */ + Strcat(mcbuf, &buf[1]); /* skip buf[]'s initial quote */ + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, + ATR_NONE, clr, mcbuf, MENU_ITEMFLAGS_NONE); + tmp = tmp->next; + } + Sprintf(mcbuf, "%s menu colors", + (opt_idx == 1) ? "List of" : "Remove which"); + end_menu(tmpwin, mcbuf); + pick_cnt = select_menu(tmpwin, + (opt_idx == 1) ? PICK_NONE : PICK_ANY, + &pick_list); + if (pick_cnt > 0) { + for (pick_idx = 0; pick_idx < pick_cnt; ++pick_idx) + free_one_menu_coloring(pick_list[pick_idx].item.a_int - 1 + - pick_idx); + free((genericptr_t) pick_list), pick_list = (menu_item *) 0; + } + destroy_nhwindow(tmpwin); + if (pick_cnt >= 0) + goto menucolors_again; + } + return optn_ok; +} + +staticfn int +handler_msgtype(void) +{ + winid tmpwin; + anything any; + int opt_idx, nmt, mttyp; + char mtbuf[BUFSZ]; + + msgtypes_again: + nmt = msgtype_count(); + opt_idx = handle_add_list_remove("message type", nmt); + if (opt_idx == 3) { /* done */ + return TRUE; + } else if (opt_idx == 0) { /* add new */ + mtbuf[0] = '\0'; + getlin("What new message pattern?", mtbuf); + if (*mtbuf == '\033') + return TRUE; + if (*mtbuf + && test_regex_pattern(mtbuf, "MSGTYPE regex") + && (mttyp = query_msgtype()) != -1 + && !msgtype_add(mttyp, mtbuf)) { + pline("Error adding the message type."); + wait_synch(); + } + goto msgtypes_again; + } else { /* list (1) or remove (2) */ + int pick_idx, pick_cnt; + int mt_idx; + unsigned ln; + const char *mtype; + menu_item *pick_list = (menu_item *) 0; + struct plinemsg_type *tmp = gp.plinemsg_types; + int clr = NO_COLOR; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + mt_idx = 0; + while (tmp) { + mtype = msgtype2name(tmp->msgtype); + any.a_int = ++mt_idx; + Sprintf(mtbuf, "%-5s \"", mtype); + ln = sizeof mtbuf - Strlen(mtbuf) - sizeof "\""; + if (strlen(tmp->pattern) > ln) + Strcat(strncat(mtbuf, tmp->pattern, ln - 3), "...\""); + else + Strcat(strcat(mtbuf, tmp->pattern), "\""); + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, + ATR_NONE, clr, mtbuf, MENU_ITEMFLAGS_NONE); + tmp = tmp->next; + } + Sprintf(mtbuf, "%s message types", + (opt_idx == 1) ? "List of" : "Remove which"); + end_menu(tmpwin, mtbuf); + pick_cnt = select_menu(tmpwin, + (opt_idx == 1) ? PICK_NONE : PICK_ANY, + &pick_list); + if (pick_cnt > 0) { + for (pick_idx = 0; pick_idx < pick_cnt; ++pick_idx) + free_one_msgtype(pick_list[pick_idx].item.a_int - 1 + - pick_idx); + free((genericptr_t) pick_list), pick_list = (menu_item *) 0; + } + destroy_nhwindow(tmpwin); + if (pick_cnt >= 0) + goto msgtypes_again; + } + return optn_ok; +} + +staticfn int +handler_versinfo(void) +{ + winid tmpwin; + anything any; + menu_item *vi_pick = (menu_item *) 0; + boolean have_branch = (nomakedefs.git_branch && *nomakedefs.git_branch); + int n, vi = (int) flags.versinfo; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + + any.a_int = n = VI_NUMBER; /* 1 */ + add_menu(tmpwin, &nul_glyphinfo, &any, 'n', n + '0', ATR_NONE, NO_COLOR, + "version number", + (vi & n) ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); + any.a_int = n = VI_NAME; /* 2 */ + add_menu(tmpwin, &nul_glyphinfo, &any, 'g', n + '0', ATR_NONE, NO_COLOR, + "game name", + (vi & n) ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); + any.a_int = n = VI_BRANCH; /* 4 */ + add_menu(tmpwin, &nul_glyphinfo, &any, 'b', n + '0', ATR_NONE, NO_COLOR, + (have_branch ? "development branch" +#if (NH_DEVEL_STATUS == NH_STATUS_RELEASED) + : "(not applicable)" +#else + : "(not available)" +#endif + ), (vi & n) ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); + + end_menu(tmpwin, "Select version information flags:"); + n = select_menu(tmpwin, PICK_ANY, &vi_pick); + if (n > 0) { + int i, newval = 0; + + for (i = 0; i < n; ++i) + newval |= vi_pick[i].item.a_int; + newval &= 7; + if (newval) + flags.versinfo = (unsigned) newval; + free((genericptr_t) vi_pick); + } + destroy_nhwindow(tmpwin); + return optn_ok; +} + +staticfn int +handler_windowborders(void) +{ + winid tmpwin; + anything any; + int i; + const char *mode_name; + menu_item *mode_pick = (menu_item *) 0; + int clr = NO_COLOR; + static const char *const windowborders_text[] = { + "Off, never show borders", + "On, always show borders", + "Auto, on if display is at least (24+2)x(80+2)", + "On, except forced off for perm_invent", + "Auto, except forced off for perm_invent" + }; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + for (i = 0; i < SIZE(windowborders_text); i++) { + mode_name = windowborders_text[i]; + any.a_int = i + 1; + /* index 'i' matches the numeric setting for windowborders, + so allow corresponding digit as group accelerator */ + add_menu(tmpwin, &nul_glyphinfo, &any, 'a' + i, '0' + i, + ATR_NONE, clr, mode_name, MENU_ITEMFLAGS_NONE); + } + end_menu(tmpwin, "Select window borders mode:"); + if (select_menu(tmpwin, PICK_ONE, &mode_pick) > 0) { + iflags.wc2_windowborders = mode_pick->item.a_int - 1; + free((genericptr_t) mode_pick); + } + destroy_nhwindow(tmpwin); + return optn_ok; +} + +/* + ********************************** + * + * Parsing Support Functions + * + ********************************** + */ + +staticfn char * +string_for_opt(char *opts, boolean val_optional) +{ + char *colon, *equals; + + colon = strchr(opts, ':'); + equals = strchr(opts, '='); + if (!colon || (equals && equals < colon)) + colon = equals; + + if (!colon || !*++colon) { + if (!val_optional) + config_error_add("Missing parameter for '%s'", opts); + return empty_optstr; + } + return colon; +} + +staticfn char * +string_for_env_opt(const char *optname, char *opts, boolean val_optional) +{ + if (!go.opt_initial) { + rejectoption(optname); + return empty_optstr; + } + return string_for_opt(opts, val_optional); +} + +staticfn void +bad_negation(const char *optname, boolean with_parameter) +{ + config_error_add("The %s option may not %sbe negated.", optname, + with_parameter ? "both have a value and " : ""); +} + +/* go through all of the options and set the minmatch value + based on what is needed for uniqueness of each individual + option. Set a minimum of 3 characters. */ +staticfn void +determine_ambiguities(void) +{ + int i, j, len, tmpneeded, needed[SIZE(allopt)]; + const char *p1, *p2; + + for (i = 0; i < SIZE(allopt) - 1; ++i) { + needed[i] = 0; + } + + for (i = 0; i < SIZE(allopt) - 1; ++i) { + for (j = 0; j < SIZE(allopt) - 1; ++j) { + if (j == i) + continue; + + p1 = allopt[i].name; /* back to the start */ + p2 = allopt[j].name; + tmpneeded = 1; + while (*p1 && *p2 && lowc(*p1) == lowc(*p2)) { + ++tmpneeded; + ++p1; + ++p2; + } + if (tmpneeded > needed[i]) + needed[i] = tmpneeded; + if (tmpneeded > needed[j]) + needed[j] = tmpneeded; + } + } + for (i = 0; i < SIZE(allopt) - 1; ++i) { + len = Strlen(allopt[i].name); + allopt[i].minmatch = (needed[i] < 3) ? 3 + : (needed[i] <= len) ? needed[i] : len; + } +} + +staticfn int +length_without_val(const char *user_string, int len) +{ + const char *p = strchr(user_string, ':'), + *q = strchr(user_string, '='); + + if (!p || (q && q < p)) + p = q; + if (p) { + /* 'user_string' hasn't necessarily been through mungspaces() + so might have tabs or consecutive spaces */ + while (p > user_string && isspace((uchar) *(p - 1))) + p--; + len = (int) (p - user_string); + } + return len; +} + +/* check whether a user-supplied option string is a proper leading + substring of a particular option name; option string might have + a colon or equals sign and arbitrary value appended to it */ +boolean +match_optname(const char *user_string, const char *optn_name, + int min_length, boolean val_allowed) +{ + int len = (int) strlen(user_string); + + if (val_allowed) + len = length_without_val(user_string, len); + + return (boolean) (len >= min_length + && !strncmpi(optn_name, user_string, len)); +} + +void +reset_duplicate_opt_detection(void) +{ + int k; + + for (k = 0; k < OPTCOUNT; ++k) + allopt[k].dupdetected = 0; +} + +staticfn boolean +duplicate_opt_detection(int optidx) +{ + if (go.opt_initial && go.opt_from_file) + return allopt[optidx].dupdetected++; + return FALSE; +} + +staticfn void +complain_about_duplicate(int optidx) +{ + char buf[BUFSZ]; + +#ifdef MACOS9 + /* the Mac has trouble dealing with the output of messages while + * processing the config file. That should get fixed one day. + * For now just return. + */ +#else /* !MACOS9 */ + buf[0] = '\0'; + if (using_alias) + Sprintf(buf, " (via alias: %s)", allopt[optidx].alias); + config_error_add("%s option specified multiple times: %s%s", + (allopt[optidx].opttyp == CompOpt) ? "compound" + : "boolean", + allopt[optidx].name, buf); +#endif /* ?MACOS9 */ + return; +} + +staticfn void +rejectoption(const char *optname) +{ +#ifdef MICRO + pline("\"%s\" settable only from %s.", optname, get_configfile()); +#else + pline("%s can be set only from NETHACKOPTIONS or %s.", optname, + get_configfile()); +#endif +} + +/* + +# errors: +OPTIONS=aaaaaaaaaa[ more than 247 (255 - 8 for 'OPTIONS=') total ]aaaaaaaaaa +OPTIONS +OPTIONS= +MSGTYPE=stop"You swap places with " +MSGTYPE=st.op "You swap places with " +MSGTYPE=stop "You swap places with \" +MENUCOLOR=" blessed "green&none +MENUCOLOR=" holy " = green&reverse +MENUCOLOR=" cursed " = red&uline +MENUCOLOR=" unholy " = reed +OPTIONS=!legacy:true,fooo +OPTIONS=align:!pin +OPTIONS=gender + +*/ + +/* Added for NLE. */ +extern char * nle_getenv(const char *); + +/* most environment variables will eventually be printed in an error + * message if they don't work, and most error message paths go through + * BUFSZ buffers, which could be overflowed by a maliciously long + * environment variable. If a variable can legitimately be long, or + * if it's put in a smaller buffer, the responsible code will have to + * bounds-check itself. + */ +char * +nh_getenv(const char *ev) +{ + fprintf(stderr, "Warning: NetHack asked for env variable %s\n", ev); + + char *getev = getenv(ev); + + if (getev && strlen(getev) <= (BUFSZ / 2)) + return getev; + else + return (char *) 0; +} + +/* copy up to maxlen-1 characters; 'dest' must be able to hold maxlen; + treat comma as alternate end of 'src' */ +staticfn void +nmcpy(char *dest, const char *src, int maxlen) +{ + int count; + + for (count = 1; count < maxlen; count++) { + if (*src == ',' || *src == '\0') + break; /*exit on \0 terminator*/ + *dest++ = *src++; + } + *dest = '\0'; +} + +/* + * escapes(): escape expansion for showsyms. C-style escapes understood + * include \n, \b, \t, \r, \xnnn (hex), \onnn (octal), \nnn (decimal). + * (Note: unlike in C, leading digit 0 is not used to indicate octal; + * the letter o (either upper or lower case) is used for that. + * The ^-prefix for control characters is also understood, and \[mM] + * has the effect of 'meta'-ing the value which follows (so that the + * alternate character set will be enabled). + * + * X normal key X + * ^X control-X + * \mX meta-X + * + * For 3.4.3 and earlier, input ending with "\M", backslash, or caret + * prior to terminating '\0' would pull that '\0' into the output and then + * keep processing past it, potentially overflowing the output buffer. + * Now, trailing \ or ^ will act like \\ or \^ and add '\\' or '^' to the + * output and stop there; trailing \M will fall through to \ and + * yield 'M', then stop. Any \X or \O followed by something other than + * an appropriate digit will also fall through to \ and yield 'X' + * or 'O', plus stop if the non-digit is end-of-string. + */ +staticfn void +escapes(const char *cp, /* might be 'tp', updating in place */ + char *tp) /* result is never longer than 'cp' */ +{ + static NEARDATA const char oct[] = "01234567", dec[] = "0123456789"; + /* hexdd[] is defined in decl.c */ + + const char *dp; + int cval, meta, dcount; + + while (*cp) { + /* \M has to be followed by something to do meta conversion, + otherwise it will just be \M which ultimately yields 'M' */ + meta = (*cp == '\\' && (cp[1] == 'm' || cp[1] == 'M') && cp[2]); + if (meta) + cp += 2; + + cval = dcount = 0; /* for decimal, octal, hexadecimal cases */ + if ((*cp != '\\' && *cp != '^') || !cp[1]) { + /* simple character, or nothing left for \ or ^ to escape */ + cval = *cp++; + } else if (*cp == '^') { /* expand control-character syntax */ + cval = (*++cp & 0x1f); + ++cp; + + /* remaining cases are all for backslash; we know cp[1] is not \0 */ + } else if (strchr(dec, cp[1])) { + ++cp; /* move past backslash to first digit */ + do { + cval = (cval * 10) + (*cp - '0'); + } while (*++cp && strchr(dec, *cp) && ++dcount < 3); + } else if ((cp[1] == 'o' || cp[1] == 'O') && cp[2] + && strchr(oct, cp[2])) { + cp += 2; /* move past backslash and 'O' */ + do { + cval = (cval * 8) + (*cp - '0'); + } while (*++cp && strchr(oct, *cp) && ++dcount < 3); + } else if ((cp[1] == 'x' || cp[1] == 'X') && cp[2] + && (dp = strchr(hexdd, cp[2])) != 0) { + cp += 2; /* move past backslash and 'X' */ + do { + cval = (cval * 16) + ((int) (dp - hexdd) / 2); + } while (*++cp && (dp = strchr(hexdd, *cp)) != 0 && ++dcount < 2); + } else { /* C-style character escapes */ + switch (*++cp) { + case '\\': + cval = '\\'; + break; + case 'n': + cval = '\n'; + break; + case 't': + cval = '\t'; + break; + case 'b': + cval = '\b'; + break; + case 'r': + cval = '\r'; + break; + default: + cval = *cp; + } + ++cp; + } + + if (meta) + cval |= 0x80; + *tp++ = (char) cval; + } + *tp = '\0'; +} + +/* returns a one-byte character from the text; may change txt[]; + moved from cmd.c in order to get access to escapes() */ +uchar +txt2key(char *txt) +{ + uchar uc; + boolean makemeta = FALSE; + + txt = trimspaces(txt); + if (!*txt) + return '\0'; + + /* simple character */ + if (!txt[1]) + return (uchar) txt[0]; + + /* a few special entries */ + if (!strcmp(txt, "")) + return '\n'; + if (!strcmp(txt, "")) + return ' '; + if (!strcmp(txt, "")) + return '\033'; + + /* handle things like \b and \7 and \mX */ + if (*txt == '\\') { + char tbuf[QBUFSZ]; + + if (strlen(txt) >= sizeof tbuf) + txt[sizeof tbuf - 1] = '\0'; + escapes(txt, tbuf); + return *tbuf; + } + + /* control and meta keys */ + if (highc(*txt) == 'M') { + /* + * M return 'M' + * M - return M-'-' + * M return M- + * otherwise M is pending until after ^/C- processing. + * Since trailing spaces are discarded, the only way to + * specify M-' ' is via "160". + */ + if (!txt[1]) + return (uchar) *txt; + /* skip past 'M' or 'm' and maybe '-' */ + ++txt; + if (*txt == '-' && txt[1]) + ++txt; + if (!txt[1]) + return M((uchar) *txt); + makemeta = TRUE; + } + if (*txt == '^' || highc(*txt) == 'C') { + /* + * C return 'C' or M-'C' + * C - return '-' or M-'-' + * C [-] return C- or M-C- + * C [-] ? return + * otherwise return C- or M-C- + */ + uc = (uchar) *txt; + if (!txt[1]) + return makemeta ? M(uc) : uc; + ++txt; + /* unlike M-x, lots of values of x are invalid for C-x; + checking and rejecting them is not worthwhile; GIGO; + we do accept "^-x" as synonym for "^x" or "C-x" */ + if (*txt == '-' && txt[1]) + ++txt; + /* and accept ^?, which gets used despite not being a control char */ + if (*txt == '?') + return (uchar) (makemeta ? '\377' : '\177'); /* rubout/delete */ + uc = C((uchar) *txt); + return makemeta ? M(uc) : uc; + } + if (makemeta && *txt) + return M((uchar) *txt); + + /* FIXME: should accept single-quote single-character single-quote + and probably single-quote backslash octal-digits single-quote; + if we do that, the M- and C- results should be pending until + after, so that C-'X' becomes valid for ^X */ + + /* ascii codes: must be three-digit decimal */ + if (*txt >= '0' && *txt <= '9') { + uchar key = 0; + int i; + + for (i = 0; i < 3; i++) { + if (txt[i] < '0' || txt[i] > '9') + return '\0'; + key = 10 * key + txt[i] - '0'; + } + return key; + } + + return '\0'; +} + +/* + ********************************** + * + * Options Initialization + * + ********************************** + */ + +/* process options, possibly including SYSCF */ +void +initoptions(void) +{ + /* + * Most places that call initoptions_init()/initoptions() would + * have the calls next to each other, so instead of adding + * initoptions_init() everywhere, just add it where it's needed in + * a non-adjacent place and call it here for all the other cases. + */ + if (go.opt_phase != builtin_opt) + initoptions_init(); +#ifdef SYSCF +/* someday there may be other SYSCF alternatives besides text file */ +#ifdef SYSCF_FILE + /* If SYSCF_FILE is specified, it _must_ exist... */ + assure_syscf_file(); + config_error_init(TRUE, SYSCF_FILE, FALSE); + + /* ... and _must_ parse correctly. */ + go.opt_phase = syscf_opt; + if (!read_config_file(SYSCF_FILE, set_in_sysconf)) { + if (config_error_done() && !iflags.initoptions_noterminate) + nh_terminate(EXIT_FAILURE); + } + config_error_done(); + /* + * TODO [maybe]: parse the sysopt entries which are space-separated + * lists of usernames into arrays with one name per element. + */ +#endif +#endif /* SYSCF */ + + /* Carry out options that got deferred from early_options */ + if (gd.deferred_showpaths) + do_deferred_showpaths(0); /* does not return */ + + initoptions_finish(); +} + +/* set up default values for options where 0 or False isn't sufficient */ +void +initoptions_init(void) +{ +#if (defined(UNIX) || defined(VMS)) && defined(TTY_GRAPHICS) + char *opts; +#endif + int i; + boolean have_branch = (nomakedefs.git_branch && *nomakedefs.git_branch); + + go.opt_phase = builtin_opt; /* Did I need to move this here? */ + /* initialize the function pointers for saving the game */ + sf_init(); + allopt_array_init(); + /* if windowtype has been specified on the command line, set it up + early so windowtype-specific options use it as their base */ + if (gc.cmdline_windowsys) { + nmcpy(gc.chosen_windowtype, gc.cmdline_windowsys, WINTYPELEN); + config_error_init(FALSE, "command line", FALSE); + choose_windows(gc.cmdline_windowsys); + config_error_done(); + /* + * FIXME? This continues even if setting windowtype to player's + * specified value fails. It doesn't lock the windowtype in + * that situation though, so the game will use whatever is in + * RC/NETHACKOPTIONS or resort to DEFAULT_WINDOW_SYS. + */ + if (windowprocs.name + && !strcmpi(windowprocs.name, gc.cmdline_windowsys)) + /* ignore any windowtype:foo in RC file or NETHACKOPTIONS */ + iflags.windowtype_locked = TRUE; + /* shouldn't need cmdline_windowsys beyond here */ + free((genericptr_t) gc.cmdline_windowsys), + gc.cmdline_windowsys = NULL; + } + + /* make any symbol parsing quicker */ + if (!glyphid_cache_status()) + fill_glyphid_cache(); + + /* set up the command parsing */ + reset_commands(TRUE); /* init */ + + /* initialize the random number generator(s) */ + init_random(rn2); + init_random(rn2_on_display_rng); + + go.opt_phase = builtin_opt; + for (i = 0; allopt[i].name; i++) { + if (allopt[i].addr) + *(allopt[i].addr) = allopt[i].initval; + } + + flags.end_own = FALSE; + flags.end_top = 3; + flags.end_around = 2; + flags.paranoia_bits = PARANOID_PRAY | PARANOID_SWIM | PARANOID_TRAP; + flags.versinfo = have_branch ? 4 : 1; + flags.pile_limit = PILE_LIMIT_DFLT; /* 5 */ + flags.runmode = RUN_LEAP; + iflags.msg_history = 20; + + /* msg_window has conflicting defaults for multi-interface binary */ +#ifdef TTY_GRAPHICS + iflags.prevmsg_window = 's'; +#else +#ifdef CURSES_GRAPHICS + iflags.prevmsg_window = 'r'; +#endif +#endif + + iflags.menu_headings.attr = ATR_INVERSE; + iflags.menu_headings.color = NO_COLOR; + iflags.getpos_coords = GPCOORDS_NONE; + + /* hero's role, race, &c haven't been chosen yet */ + flags.initrole = flags.initrace = flags.initgend = flags.initalign + = ROLE_NONE; + + init_ov_primary_symbols(); + init_ov_rogue_symbols(); + /* Set the default monster and object class symbols. */ + init_symbols(); + for (i = 0; i < WARNCOUNT; i++) + gw.warnsyms[i] = def_warnsyms[i].sym; + + /* assert( sizeof flags.inv_order == sizeof def_inv_order ); */ + (void) memcpy((genericptr_t) flags.inv_order, + (genericptr_t) def_inv_order, sizeof flags.inv_order); + flags.pickup_types[0] = '\0'; + flags.pickup_burden = MOD_ENCUMBER; + flags.sortloot = 'l'; /* sort only loot by default */ + + for (i = 0; i < NUM_DISCLOSURE_OPTIONS; i++) + flags.end_disclose[i] = DISCLOSE_PROMPT_DEFAULT_NO; + switch_symbols(FALSE); /* set default characters */ + init_rogue_symbols(); +#if defined(UNIX) && defined(TTY_GRAPHICS) + /* + * Set defaults for some options depending on what we can + * detect about the environment's capabilities. + * This has to be done after the global initialization above + * and before reading user-specific initialization via + * config file/environment variable below. + */ + /* this detects the IBM-compatible console on most 386 boxes */ + /* NLE: Use NLE-specific function */ + if ((opts = nle_getenv("TERM")) && !strncmp(opts, "AT", 2)) { + if (!gs.symset[PRIMARYSET].explicitly) + load_symset("IBMGraphics", PRIMARYSET); + if (!gs.symset[ROGUESET].explicitly) + load_symset("RogueIBM", ROGUESET); + switch_symbols(TRUE); + iflags.use_color = TRUE; + } +#endif /* UNIX && TTY_GRAPHICS */ +#if defined(UNIX) || defined(VMS) +#ifdef TTY_GRAPHICS + /* detect whether a "vt" terminal can handle alternate charsets */ + /* NLE: Use NLE-specific function */ + if ((opts = nle_getenv("TERM")) + /* [could also check "xterm" which emulates vtXXX by default] */ + && !strncmpi(opts, "vt", 2) + && AS && AE && strchr(AS, '\016') && strchr(AE, '\017')) { + if (!gs.symset[PRIMARYSET].explicitly) + load_symset("DECGraphics", PRIMARYSET); + switch_symbols(TRUE); + } +#endif +#endif /* UNIX || VMS */ + +#if defined(MSDOS) || defined(WIN32) + /* Use IBM defaults. Can be overridden via config file */ + if (!gs.symset[PRIMARYSET].explicitly) + load_symset("IBMGraphics_2", PRIMARYSET); + if (!gs.symset[ROGUESET].explicitly) + load_symset("RogueEpyx", ROGUESET); +#endif +#ifdef MAC_GRAPHICS_ENV + if (!gs.symset[PRIMARYSET].explicitly) + load_symset("MACGraphics", PRIMARYSET); + switch_symbols(TRUE); +#endif /* MAC_GRAPHICS_ENV */ + flags.menu_style = MENU_FULL; + + iflags.wc_align_message = ALIGN_TOP; + iflags.wc_align_status = ALIGN_BOTTOM; + /* used by tty and curses */ + iflags.wc2_statuslines = 2; + iflags.wc2_petattr = ATR_INVERSE; + /* only used by curses */ + iflags.wc2_windowborders = 2; /* 'Auto' */ + + /* + * A few menus have certain items (typically operate-on-everything or + * change-subset or sort or help entries) flagged as 'skip-invert' to + * control how whole-page and whole-menu operations affect them. + * 'menuinvertmode' controls how that functions: + * 0: ignore 'skip-invert' flag on menu items (used to be the default); + * 1: don't toggle 'skip-invert' items On for set-all/set-page/invert- + * all/invert-page but do toggle Off if already set (default); + * 2: don't toggle 'skip-invert' items either On of Off for set-all/ + * set-page/unset-all/unset-page/invert-all/invert-page. + */ + iflags.menuinvertmode = 1; + + /* since this is done before init_objects(), do partial init here */ + objects[SLIME_MOLD].oc_name_idx = SLIME_MOLD; + nmcpy(svp.pl_fruit, OBJ_NAME(objects[SLIME_MOLD]), PL_FSIZ); + +#ifdef SYSCF +/* someday there may be other SYSCF alternatives besides text file */ +#ifdef SYSCF_FILE + /* If SYSCF_FILE is specified, it _must_ exist... */ + assure_syscf_file(); + config_error_init(TRUE, SYSCF_FILE, FALSE); + + /* ... and _must_ parse correctly. */ + go.opt_phase = syscf_opt; + if (!read_config_file(SYSCF_FILE, set_in_sysconf)) { + if (config_error_done() && !iflags.initoptions_noterminate) + nh_terminate(EXIT_FAILURE); + } + config_error_done(); + /* + * TODO [maybe]: parse the sysopt entries which are space-separated + * lists of usernames into arrays with one name per element. + */ +#endif +#endif /* SYSCF */ +} + +/* + * Process user's run-time configuration file: + * get value of NETHACKOPTIONS; + * if command line specified -nethackrc=filename, use that; + * if NETHACKOPTIONS is present, + * honor it if it has a list of options to set + * or ignore it if it specifies a file name; + * else if not specified on command line and NETHACKOPTIONS names a file, + * use that as the config file; + * no extra options (normal use of NETHACKOPTIONS) will be set; + * otherwise (not on command line and either no NETHACKOPTIONS or that + * isn't a file name), + * pass Null to read_config_file() so that it will read ~/.nethackrc + * by default, + * then process the value of NETHACKOPTIONS as extra options. + */ +void +initoptions_finish(void) +{ nhsym sym = 0; + + disregard_this_option(opt_mention_decor); /* defer this */ + rcfile(); + + (void) fruitadd(svp.pl_fruit, (struct fruit *) 0); + /* + * Remove "slime mold" from list of object names. This will + * prevent it from being wished unless it's actually present + * as a named (or default) fruit. Wishing for "fruit" will + * result in the player's preferred fruit. [Once upon a time + * the override value used was "\033" which prevented wishing + * for the slime mold object at all except by asking for a + * specific named fruit.] Note that there are multiple fruit + * object types (apple, melon, &c) but the "fruit" object is + * slime mold or whatever custom name player assigns to that. + */ + obj_descr[SLIME_MOLD].oc_name = "fruit"; + + sym = get_othersym(SYM_BOULDER, + Is_rogue_level(&u.uz) ? ROGUESET : PRIMARYSET); + if (sym) + gs.showsyms[SYM_BOULDER + SYM_OFF_X] = sym; + reglyph_darkroom(); + reset_glyphmap(gm_optionchange); +#ifdef STATUS_HILITES + /* + * A multi-interface binary might only support status highlighting + * for some of the interfaces; check whether we asked for it but are + * using one which doesn't. + * + * Option processing can take place before a user-decided WindowPort + * is even initialized, so check for that too. + */ + if (iflags.hilite_delta && !wc2_supported("statushilites")) { + raw_printf("Status highlighting not supported for %s interface.", + windowprocs.name); + iflags.hilite_delta = 0; + } +#endif + update_rest_on_space(); + + /* these can't rely on compile-time initialization for their defaults + because a multi-interface binary might need different values for + different interfaces; if neither tiled_map nor ascii_map pass the + wc_supported() test, assume ascii_map */ + if (iflags.wc_tiled_map && !wc_supported("tiled_map")) + iflags.wc_tiled_map = FALSE, iflags.wc_ascii_map = TRUE; + else if (iflags.wc_ascii_map && !wc_supported("ascii_map") + && wc_supported("tiled_map")) + iflags.wc_ascii_map = FALSE, iflags.wc_tiled_map = TRUE; + +#ifdef ENHANCED_SYMBOLS + if (glyphid_cache_status()) + free_glyphid_cache(); + apply_customizations(gc.currentgraphics, + do_custom_symbols | do_custom_colors); +#endif + go.opt_initial = FALSE; + return; +} + +#if 0 + /* + * Do these after clearing the 'opt_initial' flag. + */ + + /* player's RC file might try to enable perm_invent before selecting + current interface, so the decision then would have been based on + default interface; re-check with the active interface now */ + if (iflags.perm_invent) { + /* can_set_perm_invent() expects to be called when perm_invent + is about to be toggled On, so start with it Off */ + iflags.perm_invent = FALSE; + if (can_set_perm_invent()) + iflags.perm_invent = TRUE; } +} #endif - /* WINCAP - * setting window colors - * syntax: windowcolors=menu foregrnd/backgrnd text foregrnd/backgrnd - */ - fullname = "windowcolors"; - if (match_optname(opts, fullname, 7, TRUE)) { - if (duplicate) - complain_about_duplicate(opts, 1); - if ((op = string_for_opt(opts, FALSE)) != empty_optstr) { - if (!wc_set_window_colors(op)) { - config_error_add("Could not set %s '%s'", fullname, op); - return FALSE; - } - } else if (negated) { - bad_negation(fullname, TRUE); - return FALSE; +void +allopt_array_init(void) +{ + int i; + static boolean options_array_inited_already = FALSE; + + if (!options_array_inited_already) { + memcpy(allopt, allopt_init, sizeof(allopt)); + determine_ambiguities(); + for (i = 0; allopt[i].name; i++) { + if (allopt[i].addr) + *(allopt[i].addr) = allopt[i].initval; } - return retval; + heed_all_options(); + /* + * Call each option function with an init flag and give it a chance + * to make any preparations that it might require. We do this + * whether or not the option itself is ever specified; that's + * irrelevant for the init call. Doing this allows the prep code for + * option settings to remain adjacent to, and in the same function as, + * the code that processes those options. + */ + for (i = 0; i < OPTCOUNT; ++i) { + if (allopt[i].optfn) + (*allopt[i].optfn)(i, do_init, FALSE, empty_optstr, + empty_optstr); + } + options_array_inited_already = TRUE; } +} -#ifdef CURSES_GRAPHICS - /* WINCAP2 - * term_cols:amount or term_rows:amount */ - fullname = "term_cols"; - if (match_optname(opts, fullname, 8, TRUE) - /* alternate spelling */ - || match_optname(opts, "term_columns", 9, TRUE) - /* different option but identical handlng */ - || (fullname = "term_rows", match_optname(opts, fullname, 8, TRUE))) { - long ltmp; +/* + ******************************************* + * + * Support Functions for Individual Options + * + ******************************************* + */ - if ((op = string_for_opt(opts, negated)) != empty_optstr) { - ltmp = atol(op); - if (negated) { - bad_negation(fullname, FALSE); - retval = FALSE; +/* iflags.menuobjsyms also controls iflags.menu_head_objsym, and + iflags.use_menu_glyphs; they affect execution but are no longer options */ +staticfn void +set_menuobjsyms_flags(int newobjsyms) +{ + iflags.menuobjsyms = newobjsyms; + iflags.menu_head_objsym = ((newobjsyms & 1) != 0) ? TRUE : FALSE; + iflags.use_menu_glyphs = ((newobjsyms & (2 | 4)) != 0) ? TRUE : FALSE; +} + +/* + * Change the inventory order, using the given string as the new order. + * Missing characters in the new order are filled in at the end from + * the current inv_order, except for gold, which is forced to be first + * if not explicitly present. + * + * This routine returns 1 unless there is a duplicate or bad char in + * the string. + * + * Used by: optfn_packorder() + * + */ +staticfn int +change_inv_order(char *op) +{ + int oc_sym, num; + char *sp, buf[QBUFSZ]; + int retval = 1; - /* just checks atol() sanity, not logical window size sanity */ - } else if (ltmp <= 0L || ltmp >= (long) LARGEST_INT) { - config_error_add("Invalid %s: %ld", fullname, ltmp); - retval = FALSE; + num = 0; + if (!strchr(op, GOLD_SYM)) + buf[num++] = COIN_CLASS; - } else { - if (!strcmp(fullname, "term_rows")) - iflags.wc2_term_rows = (int) ltmp; - else /* !strcmp(fullname, "term_cols") */ - iflags.wc2_term_cols = (int) ltmp; - } + for (sp = op; *sp; sp++) { + boolean fail = FALSE; + oc_sym = def_char_to_objclass(*sp); + /* reject bad or duplicate entries */ + if (oc_sym == MAXOCLASSES) { /* not an object class char */ + config_error_add("Not an object class '%c'", *sp); + retval = 0; + fail = TRUE; + } else if (!strchr(flags.inv_order, oc_sym)) { + /* VENOM_CLASS, RANDOM_CLASS, and ILLOBJ_CLASS are excluded + because they aren't in def_inv_order[] so don't make it + into flags.inv_order, hence always fail this strchr() test */ + config_error_add("Object class '%c' not allowed", *sp); + retval = 0; + fail = TRUE; + } else if (strchr(sp + 1, *sp)) { + config_error_add("Duplicate object class '%c'", *sp); + retval = 0; + fail = TRUE; } - return retval; + /* retain good ones */ + if (!fail) + buf[num++] = (char) oc_sym; } + buf[num] = '\0'; - /* WINCAP2 - * petattr:string */ - fullname = "petattr"; - if (match_optname(opts, fullname, sizeof "petattr" - 1, TRUE)) { - op = string_for_opt(opts, negated); - if (op != empty_optstr && negated) { - bad_negation(fullname, TRUE); - retval = FALSE; - } else if (op != empty_optstr) { -#ifdef CURSES_GRAPHICS - int itmp = curses_read_attrs(op); + /* fill in any omitted classes, using previous ordering */ + for (sp = flags.inv_order; *sp; sp++) + if (!strchr(buf, *sp)) + (void) strkitten(&buf[num++], *sp); + buf[MAXOCLASSES - 1] = '\0'; - if (itmp == -1) { - config_error_add("Unknown %s parameter '%s'", fullname, opts); - retval = FALSE; - } else - iflags.wc2_petattr = itmp; -#else - /* non-curses windowports will not use this flag anyway - * but the above will not compile if we don't have curses. - * Just set it to a sensible default: */ - iflags.wc2_petattr = ATR_INVERSE; -#endif - } else if (negated) { - iflags.wc2_petattr = ATR_NONE; - } - if (retval) { - iflags.hilite_pet = (iflags.wc2_petattr != ATR_NONE); - if (!initial) - need_redraw = TRUE; - } - return retval; - } + Strcpy(flags.inv_order, buf); + return retval; +} - /* WINCAP2 - * windowborders:n */ - fullname = "windowborders"; - if (match_optname(opts, fullname, 10, TRUE)) { - op = string_for_opt(opts, negated); - if (negated && op != empty_optstr) { - bad_negation(fullname, TRUE); - retval = FALSE; - } else { - int itmp; - if (negated) - itmp = 0; /* Off */ - else if (op == empty_optstr) - itmp = 1; /* On */ - else /* Value supplied; expect 0 (off), 1 (on), or 2 (auto) */ - itmp = atoi(op); +/* + * Support functions for "warning" + * + * Used by: optfn_warnings() + * + */ - if (itmp < 0 || itmp > 2) { - config_error_add("Invalid %s (should be 0, 1, or 2): %s", - fullname, opts); - retval = FALSE; - } else { - iflags.wc2_windowborders = itmp; - } - } - return retval; - } -#endif /* CURSES_GRAPHICS */ +staticfn boolean +warning_opts(char *opts, const char *optype) +{ + uchar translate[WARNCOUNT]; + int length, i; - /* WINCAP2 - * statuslines:n */ - fullname = "statuslines"; - if (match_optname(opts, fullname, 11, TRUE)) { - int itmp = 0; + if ((opts = string_for_env_opt(optype, opts, FALSE)) == empty_optstr) + return FALSE; + escapes(opts, opts); - op = string_for_opt(opts, negated); - if (negated) { - bad_negation(fullname, TRUE); - itmp = 2; - retval = FALSE; - } else if (op != empty_optstr) { - itmp = atoi(op); - } - if (itmp < 2 || itmp > 3) { - config_error_add("'%s' requires a value of 2 or 3", fullname); - retval = FALSE; - } else { - iflags.wc2_statuslines = itmp; - if (!initial) - need_redraw = TRUE; - } - return retval; - } + length = (int) strlen(opts); + /* match the form obtained from PC configuration files */ + for (i = 0; i < WARNCOUNT; i++) + translate[i] = (i >= length) ? 0 + : opts[i] ? (uchar) opts[i] + : def_warnsyms[i].sym; + assign_warnings(translate); + return TRUE; +} - /* menustyle:traditional or combination or full or partial */ - fullname = "menustyle"; - if (match_optname(opts, fullname, 4, TRUE)) { - int tmp; - boolean val_required = (strlen(opts) > 5 && !negated); +void +assign_warnings(uchar *graph_chars) +{ + int i; - if (duplicate) - complain_about_duplicate(opts, 1); - if ((op = string_for_opt(opts, !val_required)) == empty_optstr) { - if (val_required) - return FALSE; /* string_for_opt gave feedback */ - tmp = negated ? 'n' : 'f'; + for (i = 0; i < WARNCOUNT; i++) + if (graph_chars[i]) + gw.warnsyms[i] = graph_chars[i]; +} + +/* + * Support functions for "suppress_alert" + * + * Used by: optfn_suppress_alert() + * + */ + +staticfn int +feature_alert_opts(char *op, const char *optn) +{ + char buf[BUFSZ]; + unsigned long fnv = get_feature_notice_ver(op); /* version.c */ + + if (fnv == 0L) + return 0; + if (fnv > get_current_feature_ver()) { + if (!go.opt_initial) { + You_cant("disable new feature alerts for future versions."); } else { - tmp = lowc(*op); - } - switch (tmp) { - case 'n': /* none */ - case 't': /* traditional: prompt for class(es) by symbol, - prompt for each item within class(es) one at a time */ - flags.menu_style = MENU_TRADITIONAL; - break; - case 'c': /* combination: prompt for class(es) by symbol, - choose items within selected class(es) by menu */ - flags.menu_style = MENU_COMBINATION; - break; - case 'f': /* full: choose class(es) by first menu, - choose items within selected class(es) by second menu */ - flags.menu_style = MENU_FULL; - break; - case 'p': /* partial: skip class filtering, - choose items among all classes by menu */ - flags.menu_style = MENU_PARTIAL; - break; - default: - config_error_add("Unknown %s parameter '%s'", fullname, op); - return FALSE; + config_error_add( + "%s=%s Invalid reference to a future version ignored", + optn, op); } - return retval; + return 0; } - fullname = "menu_headings"; - if (match_optname(opts, fullname, 12, TRUE)) { - int tmpattr; - - if (duplicate) - complain_about_duplicate(opts, 1); - if (negated) { - bad_negation(fullname, FALSE); - return FALSE; - } else if ((opts = string_for_env_opt(fullname, opts, FALSE)) - == empty_optstr) { - return FALSE; - } - tmpattr = match_str2attr(opts, TRUE); - if (tmpattr == -1) - return FALSE; - else - iflags.menu_headings = tmpattr; - return retval; + flags.suppress_alert = fnv; + if (!go.opt_initial) { + Sprintf(buf, "%lu.%lu.%lu", FEATURE_NOTICE_VER_MAJ, + FEATURE_NOTICE_VER_MIN, FEATURE_NOTICE_VER_PATCH); + pline( + "Feature change alerts disabled for NetHack %s features and prior.", + buf); } + return 1; +} - /* check for menu command mapping */ - for (i = 0; i < SIZE(default_menu_cmd_info); i++) { - fullname = default_menu_cmd_info[i].name; - if (duplicate) - complain_about_duplicate(opts, 1); - if (match_optname(opts, fullname, (int) strlen(fullname), TRUE)) { - if (negated) { - bad_negation(fullname, FALSE); - return FALSE; - } else if ((op = string_for_opt(opts, FALSE)) != empty_optstr) { - char c, op_buf[BUFSZ]; +/* + * This is used by parse_config_line() in files.c + * + */ - escapes(op, op_buf); - c = *op_buf; +/* parse key:command[,key2:command2,...] after BINDINGS= prefix has been + stripped; returns False if any problem seen, True if every binding in + the comma-separated list is successful */ +boolean +parsebindings(char *bindings) +{ + char *bind; + uchar key; + int i; + boolean ret = TRUE; /* assume success */ + static const char *const mousebtn_names[NUM_MOUSE_BUTTONS] = { + "mouse1", "mouse2" + }; - if (illegal_menu_cmd_key(c)) - return FALSE; + /* look for first comma, then decide whether it is the key being bound + or a list element separator; if it's a key, find separator beyond it */ + if ((bind = strchr(bindings, ',')) != 0) { + /* at start so it represents a key */ + if (bind == bindings) + bind = strchr(bind + 1, ','); - add_menu_cmd_alias(c, default_menu_cmd_info[i].cmd); - } - return retval; - } + /* to get here, bind is non-Null and not equal to bindings, + so it is greater than bindings and bind[-1] is valid; check + whether current comma happens to be for "\,:cmd" or "',':cmd" + (":cmd" part is assumed if the comma has expected quoting) */ + else if (bind[-1] == '\\' || (bind[-1] == '\'' && bind[1] == '\'')) + bind = strchr(bind + 2, ','); } - - /* hilite fields in status prompt */ - fullname = "hilite_status"; - if (match_optname(opts, fullname, 13, TRUE)) { -#ifdef STATUS_HILITES - if (duplicate) - complain_about_duplicate(opts, 1); - op = string_for_opt(opts, TRUE); - if (op != empty_optstr && negated) { - clear_status_hilites(); - return retval; - } else if (op == empty_optstr) { - config_error_add("Value is mandatory for hilite_status"); - return FALSE; - } - if (!parse_status_hl1(op, tfrom_file)) - return FALSE; - return retval; -#else - config_error_add("'%s' is not supported", fullname); - return FALSE; -#endif + /* if a comma separator has been found, break off first binding from rest; + parse the rest and then handle this first one when recursion returns */ + if (bind) { + *bind++ = '\0'; + if (!parsebindings(bind)) + ret = FALSE; } - /* control over whether highlights should be displayed, and for how long */ - fullname = "statushilites"; - if (match_optname(opts, fullname, 9, TRUE)) { -#ifdef STATUS_HILITES - if (negated) { - iflags.hilite_delta = 0L; - } else { - op = string_for_opt(opts, TRUE); - iflags.hilite_delta = (op == empty_optstr || !*op) ? 3L : atol(op); - if (iflags.hilite_delta < 0L) - iflags.hilite_delta = 1L; - } - if (!tfrom_file) - reset_status_hilites(); - return retval; -#else - config_error_add("'%s' is not supported", fullname); - return FALSE; -#endif - } + /* parse a single binding: first split around : */ + if (! (bind = strchr(bindings, ':'))) + return FALSE; /* it's not a binding */ + *bind++ = 0; - fullname = "DECgraphics"; - if (match_optname(opts, fullname, 3, TRUE)) { -#ifdef BACKWARD_COMPAT - boolean badflag = FALSE; + bind = trimspaces(bind); - if (duplicate) - complain_about_duplicate(opts, 1); - if (!negated) { - /* There is no rogue level DECgraphics-specific set */ - if (symset[PRIMARY].name) { - badflag = TRUE; + for (i = 0; i < SIZE(mousebtn_names); i++) + if (!strcmp(bindings, mousebtn_names[i])) { + if (!bind_mousebtn(i + 1, bind)) { + config_error_add("Error binding mouse button %i", i + 1); } else { - symset[PRIMARY].name = dupstr(fullname); - if (!read_sym_file(PRIMARY)) { - badflag = TRUE; - clear_symsetentry(PRIMARY, TRUE); - } else - switch_symbols(TRUE); - } - if (badflag) { - config_error_add("Failure to load symbol set %s.", fullname); - return FALSE; + return ret; } } - return retval; -#else - config_error_add("'%s' no longer supported; use 'symset:%s' instead", - fullname, fullname); + + /* read the key to be bound */ + key = txt2key(bindings); + if (!key) { + config_error_add("Unknown key binding key '%s'", bindings); return FALSE; -#endif - } /* "DECgraphics" */ + } - fullname = "IBMgraphics"; - if (match_optname(opts, fullname, 3, TRUE)) { -#ifdef BACKWARD_COMPAT - const char *sym_name = fullname; - boolean badflag = FALSE; + /* is it a special key? */ + if (bind_specialkey(key, bind)) + return ret; - if (duplicate) - complain_about_duplicate(opts, 1); - if (!negated) { - for (i = 0; i < NUM_GRAPHICS; ++i) { - if (symset[i].name) { - badflag = TRUE; - } else { - if (i == ROGUESET) - sym_name = "RogueIBM"; - symset[i].name = dupstr(sym_name); - if (!read_sym_file(i)) { - badflag = TRUE; - clear_symsetentry(i, TRUE); - break; - } - } - } - if (badflag) { - config_error_add("Failure to load symbol set %s.", sym_name); + /* is it a menu command? */ + for (i = 0; default_menu_cmd_info[i].name; i++) { + if (!strcmp(default_menu_cmd_info[i].name, bind)) { + if (illegal_menu_cmd_key(key)) { + config_error_add("Bad menu key %s:%s", visctrl(key), bind); return FALSE; } else { - switch_symbols(TRUE); - if (!initial && Is_rogue_level(&u.uz)) - assign_graphics(ROGUESET); + add_menu_cmd_alias((char) key, default_menu_cmd_info[i].cmd); } + return ret; } - return retval; -#else - config_error_add("'%s' no longer supported; use 'symset:%s' instead", - fullname, fullname); + } + + /* extended command? */ + if (!bind_key(key, bind, TRUE)) { + config_error_add("Unknown key binding command '%s'", bind); return FALSE; -#endif - } /* "IBMgraphics" */ + } + return ret; +} + +static const struct { + const char *name; + xint8 msgtyp; + const char *descr; +} msgtype_names[] = { + { "show", MSGTYP_NORMAL, "Show message normally" }, + { "hide", MSGTYP_NOSHOW, "Hide message" }, + { "noshow", MSGTYP_NOSHOW, NULL }, + { "stop", MSGTYP_STOP, "Prompt for more after the message" }, + { "more", MSGTYP_STOP, NULL }, + { "norep", MSGTYP_NOREP, "Do not repeat the message" } +}; - fullname = "MACgraphics"; - if (match_optname(opts, fullname, 3, TRUE)) { -#if defined(MAC_GRAPHICS_ENV) && defined(BACKWARD_COMPAT) - boolean badflag = FALSE; +staticfn const char * +msgtype2name(int typ) +{ + int i; - if (duplicate) - complain_about_duplicate(opts, 1); - if (!negated) { - if (symset[PRIMARY].name) { - badflag = TRUE; - } else { - symset[PRIMARY].name = dupstr(fullname); - if (!read_sym_file(PRIMARY)) { - badflag = TRUE; - clear_symsetentry(PRIMARY, TRUE); - } - } - if (badflag) { - config_error_add("Failure to load symbol set %s.", fullname); - return FALSE; - } else { - switch_symbols(TRUE); - if (!initial && Is_rogue_level(&u.uz)) - assign_graphics(ROGUESET); - } + for (i = 0; i < SIZE(msgtype_names); i++) + if (msgtype_names[i].descr && msgtype_names[i].msgtyp == typ) + return msgtype_names[i].name; + return (char *) 0; +} + +staticfn int +query_msgtype(void) +{ + winid tmpwin; + anything any; + int i, pick_cnt; + menu_item *picks = (menu_item *) 0; + int clr = NO_COLOR; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + for (i = 0; i < SIZE(msgtype_names); i++) + if (msgtype_names[i].descr) { + any.a_int = msgtype_names[i].msgtyp + 1; + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, + ATR_NONE, clr, + msgtype_names[i].descr, MENU_ITEMFLAGS_NONE); } - return retval; -#else /* !(MAC_GRAPHICS_ENV && BACKWARD_COMPAT) */ - config_error_add("'%s' %s; use 'symset:%s' instead", - fullname, -#ifdef MAC_GRAPHICS_ENV /* implies BACKWARD_COMPAT is not defined */ - "no longer supported", -#else - "is not supported", -#endif - fullname); + end_menu(tmpwin, "How to show the message"); + pick_cnt = select_menu(tmpwin, PICK_ONE, &picks); + destroy_nhwindow(tmpwin); + if (pick_cnt > 0) { + i = picks->item.a_int - 1; + free((genericptr_t) picks); + return i; + } + return -1; +} + +staticfn boolean +msgtype_add(int typ, char *pattern) +{ + static const char *const re_error = "MSGTYPE regex error"; + struct plinemsg_type *tmp = (struct plinemsg_type *) alloc(sizeof *tmp); + + tmp->msgtype = typ; + tmp->regex = regex_init(); + /* test_regex_pattern() has already validated this regexp but parsing + it again could conceivably run out of memory */ + if (!regex_compile(pattern, tmp->regex)) { + char errbuf[BUFSZ]; + char *re_error_desc = regex_error_desc(tmp->regex, errbuf); + + /* free first in case reason for failure was insufficient memory */ + regex_free(tmp->regex); + free((genericptr_t) tmp); + config_error_add("%s: %s", re_error, re_error_desc); return FALSE; -#endif /* ?(MAC_GRAPHICS_ENV && BACKWARD_COMPAT) */ - } /* "MACgraphics" */ + } + tmp->pattern = dupstr(pattern); + tmp->next = gp.plinemsg_types; + gp.plinemsg_types = tmp; + return TRUE; +} - /* - * OK, if we still haven't recognized the option, check the boolean - * options list. - */ - for (i = 0; boolopt[i].name; i++) { - if (match_optname(opts, boolopt[i].name, 3, TRUE)) { - /* options that don't exist */ - if (!boolopt[i].addr) { - if (!initial && !negated) - pline_The("\"%s\" option is not available.", - boolopt[i].name); - return retval; - } - /* options that must come from config file */ - if (!initial && (boolopt[i].optflags == SET_IN_FILE)) { - rejectoption(boolopt[i].name); - return retval; - } +void +msgtype_free(void) +{ + struct plinemsg_type *tmp, *tmp2 = 0; - op = string_for_opt(opts, TRUE); - if (op != empty_optstr) { - if (negated) { - config_error_add( - "Negated boolean '%s' should not have a parameter", - boolopt[i].name); - return FALSE; - } - if (!strcmp(op, "true") || !strcmp(op, "yes")) { - negated = FALSE; - } else if (!strcmp(op, "false") || !strcmp(op, "no")) { - negated = TRUE; - } else { - config_error_add("Illegal parameter for a boolean"); - return FALSE; - } - } - if (iflags.debug_fuzzer && !initial) { - /* don't randomly toggle this/these */ - if (boolopt[i].addr == &flags.silent) - return TRUE; - } + for (tmp = gp.plinemsg_types; tmp; tmp = tmp2) { + tmp2 = tmp->next; + free((genericptr_t) tmp->pattern); + regex_free(tmp->regex); + tmp->regex = 0; + free((genericptr_t) tmp); + } + gp.plinemsg_types = (struct plinemsg_type *) 0; +} - *(boolopt[i].addr) = !negated; +staticfn void +free_one_msgtype(int idx) /* 0 .. */ +{ + struct plinemsg_type *tmp = gp.plinemsg_types; + struct plinemsg_type *prev = NULL; - /* 0 means boolean opts */ - if (duplicate_opt_detection(boolopt[i].name, 0)) - complain_about_duplicate(boolopt[i].name, 0); -#ifdef RLECOMP - if (boolopt[i].addr == &iflags.rlecomp) - set_savepref(iflags.rlecomp ? "rlecomp" : "!rlecomp"); -#endif -#ifdef ZEROCOMP - if (boolopt[i].addr == &iflags.zerocomp) - set_savepref(iflags.zerocomp ? "zerocomp" : "externalcomp"); -#endif - if (boolopt[i].addr == &iflags.wc_ascii_map) { - /* toggling ascii_map; set tiled_map to its opposite; - what does it mean to turn off ascii map if tiled map - isn't supported? -- right now, we do nothing */ - iflags.wc_tiled_map = negated; - } else if (boolopt[i].addr == &iflags.wc_tiled_map) { - /* toggling tiled_map; set ascii_map to its opposite; - as with ascii_map, what does it mean to turn off tiled - map if ascii map isn't supported? */ - iflags.wc_ascii_map = negated; - } - /* only do processing below if setting with doset() */ - if (initial) - return retval; + while (tmp) { + if (idx == 0) { + struct plinemsg_type *next = tmp->next; - if (boolopt[i].addr == &flags.time -#ifdef SCORE_ON_BOTL - || boolopt[i].addr == &flags.showscore -#endif - || boolopt[i].addr == &flags.showexp) { - if (VIA_WINDOWPORT()) - status_initialize(REASSESS_ONLY); - context.botl = TRUE; - } else if (boolopt[i].addr == &flags.invlet_constant - || boolopt[i].addr == &flags.sortpack - || boolopt[i].addr == &iflags.implicit_uncursed) { - if (!flags.invlet_constant) - reassign(); - update_inventory(); - } else if (boolopt[i].addr == &flags.lit_corridor - || boolopt[i].addr == &flags.dark_room) { - /* - * All corridor squares seen via night vision or - * candles & lamps change. Update them by calling - * newsym() on them. Don't do this if we are - * initializing the options --- the vision system - * isn't set up yet. - */ - vision_recalc(2); /* shut down vision */ - vision_full_recalc = 1; /* delayed recalc */ - if (iflags.use_color) - need_redraw = TRUE; /* darkroom refresh */ - } else if (boolopt[i].addr == &flags.showrace - || boolopt[i].addr == &iflags.use_inverse - || boolopt[i].addr == &iflags.hilite_pile - || boolopt[i].addr == &iflags.perm_invent -#ifdef CURSES_GRAPHICS - || boolopt[i].addr == &iflags.cursesgraphics -#endif - || boolopt[i].addr == &iflags.wc_ascii_map - || boolopt[i].addr == &iflags.wc_tiled_map) { - need_redraw = TRUE; - } else if (boolopt[i].addr == &iflags.hilite_pet) { -#ifdef CURSES_GRAPHICS - if (WINDOWPORT("curses")) { - /* if we're enabling hilite_pet and petattr isn't set, - set it to Inverse; if we're disabling, leave petattr - alone so that re-enabling will get current value back */ - if (iflags.hilite_pet && !iflags.wc2_petattr) - iflags.wc2_petattr = curses_read_attrs("I"); - } -#endif - need_redraw = TRUE; - } else if (boolopt[i].addr == &iflags.wc2_hitpointbar) { - if (VIA_WINDOWPORT()) { - /* [is reassessment really needed here?] */ - status_initialize(REASSESS_ONLY); - need_redraw = TRUE; - } -#ifdef TEXTCOLOR - } else if (boolopt[i].addr == &iflags.use_color) { - need_redraw = TRUE; -#ifdef TOS - if (iflags.BIOS) { - if (colors_changed) - restore_colors(); - else - set_colors(); - } -#endif - } else if (boolopt[i].addr == &iflags.use_menu_color - || boolopt[i].addr == &iflags.wc2_guicolor) { - update_inventory(); -#endif /* TEXTCOLOR */ - } - return retval; + regex_free(tmp->regex); + free((genericptr_t) tmp->pattern); + free((genericptr_t) tmp); + if (prev) + prev->next = next; + else + gp.plinemsg_types = next; + return; } + idx--; + prev = tmp; + tmp = tmp->next; } +} - /* Is it a symbol? */ - if (strstr(opts, "S_") == opts && parsesymbols(opts, PRIMARY)) { - switch_symbols(TRUE); - check_gold_symbol(); - return retval; +int +msgtype_type(const char *msg, + boolean norepeat) /* called from Norep(via pline) */ +{ + struct plinemsg_type *tmp = gp.plinemsg_types; + + while (tmp) { + /* we don't exclude entries with negative msgtype values + because then the msg might end up matching a later pattern */ + if (regex_match(msg, tmp->regex)) + return tmp->msgtype; + tmp = tmp->next; } + return norepeat ? MSGTYP_NOREP : MSGTYP_NORMAL; +} - /* out of valid options */ - config_error_add("Unknown option '%s'", opts); - return FALSE; +/* negate one or more types of messages so that their type handling will + be disabled or re-enabled; MSGTYPE_NORMAL (value 0) is not affected */ +void +hide_unhide_msgtypes(boolean hide, int hide_mask) +{ + struct plinemsg_type *tmp; + int mt; + + /* negative msgtype value won't be recognized by pline, so does nothing */ + for (tmp = gp.plinemsg_types; tmp; tmp = tmp->next) { + mt = tmp->msgtype; + if (!hide) + mt = -mt; /* unhide: negate negative, yielding positive */ + if (mt > 0 && ((1 << mt) & hide_mask)) + tmp->msgtype = -tmp->msgtype; + } +} + +staticfn int +msgtype_count(void) +{ + int c = 0; + struct plinemsg_type *tmp = gp.plinemsg_types; + + while (tmp) { + c++; + tmp = tmp->next; + } + return c; } -/* parse key:command */ boolean -parsebindings(bindings) -char* bindings; +msgtype_parse_add(char *str) { - char *bind; - char key; - int i; - boolean ret = FALSE; + char pattern[256]; + char msgtype[11]; + + if (sscanf(str, "%10s \"%255[^\"]\"", msgtype, pattern) == 2) { + int typ = -1; + int i; - /* break off first binding from the rest; parse the rest */ - if ((bind = index(bindings, ',')) != 0) { - *bind++ = 0; - ret |= parsebindings(bind); + for (i = 0; i < SIZE(msgtype_names); i++) + if (str_start_is(msgtype_names[i].name, msgtype, TRUE)) { + typ = msgtype_names[i].msgtyp; + break; + } + if (typ != -1) + return msgtype_add(typ, pattern); + else + config_error_add("Unknown message type '%s'", msgtype); + } else { + config_error_add("Malformed MSGTYPE"); } + return FALSE; +} - /* parse a single binding: first split around : */ - if (! (bind = index(bindings, ':'))) - return FALSE; /* it's not a binding */ - *bind++ = 0; +/* parse 'str' as a regular expression to check whether it's valid; + compiled regexp gets thrown away regardless of the outcome */ +staticfn boolean +test_regex_pattern(const char *str, const char *errmsg) +{ + static const char def_errmsg[] = "NHregex error"; + struct nhregex *match; + char *re_error_desc, errbuf[BUFSZ]; + boolean retval; - /* read the key to be bound */ - key = txt2key(bindings); - if (!key) { - config_error_add("Unknown key binding key '%s'", bindings); + if (!str) + return FALSE; + if (!errmsg) + errmsg = def_errmsg; + + match = regex_init(); + if (!match) { + config_error_add("%s", errmsg); return FALSE; } - bind = trimspaces(bind); + retval = regex_compile(str, match); + /* get potential error message before freeing regexp and free regexp + before issuing message in case the error is "ran out of memory" + since message delivery might need to allocate some memory */ + re_error_desc = !retval ? regex_error_desc(match, errbuf) : 0; + /* discard regexp; caller will re-parse it after validating other stuff */ + regex_free(match); + /* if returning failure, tell player */ + if (!retval) + config_error_add("%s: %s", errmsg, re_error_desc); - /* is it a special key? */ - if (bind_specialkey(key, bind)) - return TRUE; + return retval; +} - /* is it a menu command? */ - for (i = 0; i < SIZE(default_menu_cmd_info); i++) { - if (!strcmp(default_menu_cmd_info[i].name, bind)) { - if (illegal_menu_cmd_key(key)) { - config_error_add("Bad menu key %s:%s", visctrl(key), bind); +/* parse 'role' or 'race' or 'gender' or 'alignment' */ +staticfn boolean +parse_role_opt( + int optidx, + boolean negated, + const char *fullname, + char *opts, + char **opp) +{ + static char neg_opt[] = "!"; /* not 'const' but never modified */ + char *preval, *op; + int which = (optidx == opt_role) ? RS_ROLE + : (optidx == opt_race) ? RS_RACE + : (optidx == opt_gender) ? RS_GENDER + : (optidx == opt_alignment) ? RS_ALGNMNT + : RS_filter; /* none of the above */ + boolean ok = FALSE; + + /* + * Accepts multiple forms + * role:priest -- play as priest + * race:!orc -- any race other than orc + * role:!cav !mon -- any role other than caveman/cavewoman or monk + * !role:tour -- any role other than tourist + * !role:tou rog wiz -- any role other than tourist or rogue or wizard + * TODO: add support for + * role:arc bar kni -- only role archeologist or barbarian or knight + * Rejected: + * role:sam !val -+ invalid; need either positive or negative subset + * !role:!sam +- not a mixture of the two and not dual negation. + */ + if ((op = string_for_env_opt(fullname, opts, FALSE)) != empty_optstr) { + char *sp; + boolean val_negated, prev_negated = FALSE, first = TRUE; + + mungspaces(op); + while (*op) { + if (*op == ' ') + ++op; + val_negated = FALSE; + while (*op == '!' || !strncmpi(op, "no", 2)) { + val_negated = !val_negated; + op += (*op == '!') ? 1 : (op[2] != '-') ? 2 : 3; + } + if (!*op || *op == ' ') { + config_error_add("Negated nothing for '%s'", fullname); return FALSE; - } else - add_menu_cmd_alias(key, default_menu_cmd_info[i].cmd); - return TRUE; + } + if (!first) { + if ((val_negated ^ prev_negated) + || (negated && val_negated)) { + config_error_add("Invalid mixed negation for '%s%s'", + negated ? "!" : "", fullname); + return FALSE; + } else if (!negated && !val_negated) { + config_error_add( + "Multiple role values only allowed when list is negated"); + return FALSE; + } + } + first = FALSE; + prev_negated = val_negated; + + /* hide rest of list, if any */ + sp = strchr(op, ' '); + if (sp) + *sp = '\0'; + + preval = getoptstr(optidx, go.opt_phase); + if (val_negated || negated) { + char negbuf[BUFSZ]; + + /* for negative value, clear filter if there is a prior + value from a different phase; for same phase, duplicates + are allowed and setrolefilter() merges them */ + if (!preval || *preval != '!') + clearrolefilter(which); + if (!setrolefilter(op)) { + config_error_add("Invalid %s '%s'", fullname, op); + return FALSE; + } + saveoptstr(optidx, rolefilterstring(negbuf, which)); + *opp = neg_opt; + } else { + /* for positive value, allow duplicate if prior value + was a negative one or came from a different phase; + reject if prior value was positive and from same phase */ + if (duplicate) { + if (preval && *preval == '!') { + complain_about_duplicate(optidx); + return FALSE; + } + } + /* save raw string value; caller will validate it and + if it's ok, replace it with canonical form */ + saveoptstr(optidx, op); + *opp = op; + /*ok = TRUE; // redundant*/ + /* don't return yet; value might be a list that follows + this with something else which might make it invalid */ + } + + if (sp) { + *sp = ' '; + op = sp + 1; + } else { + op += strlen(op); /* break; */ + } } + /* '!ok' without config_error_add() implies a valid negation */ + ok = TRUE; } + return ok; +} - /* extended command? */ - if (!bind_key(key, bind)) { - config_error_add("Unknown key binding command '%s'", bind); - return FALSE; +/* fetch a saved role|race|gender|alignment value suitable for writing into + a new run-time config file */ +staticfn char * +get_cnf_role_opt(int optidx) +{ + int phase; + char *op = 0; + + for (phase = num_opt_phases - 1; phase >= 0 && !op; --phase) { + if (phase == cmdline_opt || phase == environ_opt + || phase == builtin_opt) + continue; + op = getoptstr(optidx, phase); } - return TRUE; + return op; } -static NEARDATA const char *menutype[] = { "traditional", "combination", - "full", "partial" }; - -static NEARDATA const char *burdentype[] = { "unencumbered", "burdened", - "stressed", "strained", - "overtaxed", "overloaded" }; +/* Check if character c is illegal as a menu command key */ +staticfn boolean +illegal_menu_cmd_key(uchar c) +{ + if (c == 0 || c == '\r' || c == '\n' || c == '\033' || c == ' ' + || digit((char) c) || (letter((char) c) && c != '@')) { + config_error_add("Reserved menu command key '%s'", visctrl((char) c)); + return TRUE; + } else { /* reject default object class symbols */ + int j; -static NEARDATA const char *runmodes[] = { "teleport", "run", "walk", - "crawl" }; + for (j = 1; j < MAXOCLASSES; j++) + if (c == (uchar) def_oc_syms[j].sym) { + config_error_add("Menu command key '%s' is an object class", + visctrl((char) c)); + return TRUE; + } + } + return FALSE; +} -static NEARDATA const char *sortltype[] = { "none", "loot", "full" }; /* * Convert the given string of object classes to a string of default object * symbols. */ void -oc_to_str(src, dest) -char *src, *dest; +oc_to_str(char *src, char *dest) { int i; @@ -4276,30 +8086,28 @@ char *src, *dest; * maps valid C strings. */ void -add_menu_cmd_alias(from_ch, to_ch) -char from_ch, to_ch; +add_menu_cmd_alias(char from_ch, char to_ch) { - if (n_menu_mapped >= MAX_MENU_MAPPED_CMDS) { + if (gn.n_menu_mapped >= MAX_MENU_MAPPED_CMDS) { pline("out of menu map space."); } else { - mapped_menu_cmds[n_menu_mapped] = from_ch; - mapped_menu_op[n_menu_mapped] = to_ch; - n_menu_mapped++; - mapped_menu_cmds[n_menu_mapped] = '\0'; - mapped_menu_op[n_menu_mapped] = '\0'; + gm.mapped_menu_cmds[gn.n_menu_mapped] = from_ch; + gm.mapped_menu_op[gn.n_menu_mapped] = to_ch; + gn.n_menu_mapped++; + gm.mapped_menu_cmds[gn.n_menu_mapped] = '\0'; + gm.mapped_menu_op[gn.n_menu_mapped] = '\0'; } } char -get_menu_cmd_key(ch) -char ch; +get_menu_cmd_key(char ch) { - char *found = index(mapped_menu_op, ch); + char *found = strchr(gm.mapped_menu_op, ch); if (found) { - int idx = (int) (found - mapped_menu_op); + int idx = (int) (found - gm.mapped_menu_op); - ch = mapped_menu_cmds[idx]; + ch = gm.mapped_menu_cmds[idx]; } return ch; } @@ -4309,1683 +8117,1152 @@ char ch; * doesn't match anything, just return the original. */ char -map_menu_cmd(ch) -char ch; +map_menu_cmd(char ch) { - char *found = index(mapped_menu_cmds, ch); + char *found = strchr(gm.mapped_menu_cmds, ch); if (found) { - int idx = (int) (found - mapped_menu_cmds); + int idx = (int) (found - gm.mapped_menu_cmds); - ch = mapped_menu_op[idx]; + ch = gm.mapped_menu_op[idx]; } return ch; } -void -show_menu_controls(win, dolist) -winid win; -boolean dolist; +/* get keystrokes that are used for menu scrolling operations which apply; + printable: for use in a prompt, non-printable: for yn_function() choices */ +char * +collect_menu_keys( + char *outbuf, /* at least big enough for 6 "M-^X" sequences +'\0'*/ + unsigned scrollmask, /* 1: backwards, "^<"; 2: forwards, ">|"; + * 4: left, "{"; 8: right, "}"; */ + boolean printable) /* False: output is string of raw characters, + * True: output is a string of visctrl() sequences; + * matters iff user has mapped any menu scrolling + * commands to control or meta characters */ { - char buf[BUFSZ]; + struct menuscrollinfo { + char cmdkey; + uchar maskindx; + }; + static const struct menuscrollinfo scroll_keys[] = { + { MENU_FIRST_PAGE, 1 }, + { MENU_PREVIOUS_PAGE, 1 }, + { MENU_NEXT_PAGE, 2 }, + { MENU_LAST_PAGE, 2 }, + { MENU_SHIFT_LEFT, 4 }, + { MENU_SHIFT_RIGHT, 8 }, + }; + int i; - putstr(win, 0, "Menu control keys:"); - if (dolist) { - int i; + outbuf[0] = '\0'; + for (i = 0; i < SIZE(scroll_keys); ++i) { + if (scrollmask & scroll_keys[i].maskindx) { + char c = get_menu_cmd_key(scroll_keys[i].cmdkey); - for (i = 0; i < SIZE(default_menu_cmd_info); i++) { - Sprintf(buf, "%-8s %s", - visctrl(get_menu_cmd_key(default_menu_cmd_info[i].cmd)), - default_menu_cmd_info[i].desc); - putstr(win, 0, buf); + if (printable) + Strcat(outbuf, visctrl(c)); + else + (void) strkitten(outbuf, c); } - } else { - const char - fmt3[] = " %-12s %-2s %-2s %s", - fmt2[] = " %-12s %-2s %-2s", - fmt1[] = " %10s %-2s %s", - fmt0[] = " %14s %s"; - - putstr(win, 0, ""); - putstr(win, 0, "Selection: On page Full menu"); - Sprintf(buf, fmt2, "Select all", - visctrl(get_menu_cmd_key(MENU_SELECT_PAGE)), - visctrl(get_menu_cmd_key(MENU_SELECT_ALL))); - putstr(win, 0, buf); - Sprintf(buf, fmt2, "Deselect all", - visctrl(get_menu_cmd_key(MENU_UNSELECT_PAGE)), - visctrl(get_menu_cmd_key(MENU_UNSELECT_ALL))); - putstr(win, 0, buf); - Sprintf(buf, fmt2, "Invert all", - visctrl(get_menu_cmd_key(MENU_INVERT_PAGE)), - visctrl(get_menu_cmd_key(MENU_INVERT_ALL))); - putstr(win, 0, buf); - Sprintf(buf, fmt3, "Text match", "", - visctrl(get_menu_cmd_key(MENU_SEARCH)), - "Search and toggle matching entries"); - putstr(win, 0, buf); - putstr(win, 0, ""); - putstr(win, 0, "Navigation:"); - Sprintf(buf, fmt1, "Go to ", - visctrl(get_menu_cmd_key(MENU_NEXT_PAGE)), - "Next page"); - putstr(win, 0, buf); - Sprintf(buf, fmt1, "", - visctrl(get_menu_cmd_key(MENU_PREVIOUS_PAGE)), - "Previous page"); - putstr(win, 0, buf); - Sprintf(buf, fmt1, "", - visctrl(get_menu_cmd_key(MENU_FIRST_PAGE)), - "First page"); - putstr(win, 0, buf); - Sprintf(buf, fmt1, "", - visctrl(get_menu_cmd_key(MENU_LAST_PAGE)), - "Last page"); - putstr(win, 0, buf); - Sprintf(buf, fmt0, "SPACE", "Next page, if any, otherwise RETURN"); - putstr(win, 0, buf); - Sprintf(buf, fmt0, "RETURN/ENTER", - "Finish menu with any selection(s) made"); - putstr(win, 0, buf); - Sprintf(buf, fmt0, "ESCAPE", - "Cancel menu without selecting anything"); - putstr(win, 0, buf); } + return outbuf; } -#if defined(MICRO) || defined(MAC) || defined(WIN32) -#define OPTIONS_HEADING "OPTIONS" -#else -#define OPTIONS_HEADING "NETHACKOPTIONS" -#endif - -static char fmtstr_doset[] = "%s%-15s [%s] "; -static char fmtstr_doset_tab[] = "%s\t[%s]"; -static char n_currently_set[] = "(%d currently set)"; - -/* doset('O' command) menu entries for compound options */ -STATIC_OVL void -doset_add_menu(win, option, indexoffset) -winid win; /* window to add to */ -const char *option; /* option name */ -int indexoffset; /* value to add to index in compopt[], or zero - if option cannot be changed */ +/* Returns the fid of the fruit type; if that type already exists, it + * returns the fid of that one; if it does not exist, it adds a new fruit + * type to the chain and returns the new one. + * If replace_fruit is sent in, replace the fruit in the chain rather than + * adding a new entry--for user specified fruits only. + */ +int +fruitadd(char *str, struct fruit *replace_fruit) { - const char *value = "unknown"; /* current value */ - char buf[BUFSZ], buf2[BUFSZ]; - anything any; int i; + struct fruit *f; + int highest_fruit_id = 0, globpfx; + char buf[PL_FSIZ], altname[PL_FSIZ]; + boolean user_specified = (str == svp.pl_fruit); + /* if not user-specified, then it's a fruit name for a fruit on + * a bones level or from orctown raider's loot... + */ - any = zeroany; - if (indexoffset == 0) { - any.a_int = 0; - value = get_compopt_value(option, buf2); - } else { - for (i = 0; compopt[i].name; i++) - if (strcmp(option, compopt[i].name) == 0) + /* Note: every fruit has an id (kept in obj->spe) of at least 1; + * 0 is an error. + */ + if (user_specified) { + boolean found = FALSE, numeric = FALSE; + + /* force fruit to be singular; this handling is not + needed--or wanted--for fruits from bones because + they already received it in their original game; + str==pl_fruit but makesingular() creates a copy + so we need to copy that back into pl_fruit */ + nmcpy(svp.pl_fruit, makesingular(str), PL_FSIZ); + + /* disallow naming after other foods (since it'd be impossible + * to tell the difference); globs might have a size prefix which + * needs to be skipped in order to match the object type name + */ + globpfx = (!strncmp(svp.pl_fruit, "small ", 6) + || !strncmp(svp.pl_fruit, "large ", 6)) ? 6 + : (!strncmp(svp.pl_fruit, "medium ", 7)) ? 7 + : (!strncmp(svp.pl_fruit, "very large ", 11)) ? 11 + : 0; + for (i = svb.bases[FOOD_CLASS]; objects[i].oc_class == FOOD_CLASS; + i++) { + if (!strcmp(OBJ_NAME(objects[i]), svp.pl_fruit) + || (globpfx > 0 && !strcmp(OBJ_NAME(objects[i]), + &svp.pl_fruit[globpfx]))) { + found = TRUE; break; + } + } + if (!found) { + char *c; - if (compopt[i].name) { - any.a_int = i + 1 + indexoffset; - value = get_compopt_value(option, buf2); - } else { - /* We are trying to add an option not found in compopt[]. - This is almost certainly bad, but we'll let it through anyway - (with a zero value, so it can't be selected). */ - any.a_int = 0; + for (c = svp.pl_fruit; *c >= '0' && *c <= '9'; c++) + continue; + if (!*c || isspace((uchar) *c)) + numeric = TRUE; + } + if (found || numeric + /* these checks for applying food attributes to actual items + are case sensitive; "glob of foo" is caught by 'found' + if 'foo' is a valid glob; when not valid, allow it as-is */ + || !strncmp(svp.pl_fruit, "cursed ", 7) + || !strncmp(svp.pl_fruit, "uncursed ", 9) + || !strncmp(svp.pl_fruit, "blessed ", 8) + || !strncmp(svp.pl_fruit, "partly eaten ", 13) + || (!strncmp(svp.pl_fruit, "tin of ", 7) + && (!strcmp(svp.pl_fruit + 7, "spinach") + || ismnum(name_to_mon(svp.pl_fruit + 7, (int *) 0)))) + || !strcmp(svp.pl_fruit, "empty tin") + || (!strcmp(svp.pl_fruit, "glob") + || (globpfx > 0 && !strcmp("glob", &svp.pl_fruit[globpfx]))) + || ((str_end_is(svp.pl_fruit, " corpse") + || str_end_is(svp.pl_fruit, " egg")) + && ismnum(name_to_mon(svp.pl_fruit, (int *) 0)))) { + Strcpy(buf, svp.pl_fruit); + Strcpy(svp.pl_fruit, "candied "); + nmcpy(svp.pl_fruit + 8, buf, PL_FSIZ - 8); } + *altname = '\0'; + /* This flag indicates that a fruit has been made since the + * last time the user set the fruit. If it hasn't, we can + * safely overwrite the current fruit, preventing the user from + * setting many fruits in a row and overflowing. + * Possible expansion: check for specific fruit IDs, not for + * any fruit. + */ + flags.made_fruit = FALSE; + if (replace_fruit) { + /* replace_fruit is already part of the fruit chain; + update it in place rather than looking it up again */ + f = replace_fruit; + copynchars(f->fname, svp.pl_fruit, PL_FSIZ - 1); + goto nonew; + } + } else { + /* not user_supplied, so assumed to be from bones (or orc gang) */ + copynchars(altname, str, PL_FSIZ - 1); + sanitize_name(altname); + flags.made_fruit = TRUE; /* for safety. Any fruit name added from a + * bones level should exist anyway. */ } - /* " " replaces "a - " -- assumes menus follow that style */ - if (!iflags.menu_tab_sep) - Sprintf(buf, fmtstr_doset, any.a_int ? "" : " ", option, - value); - else - Sprintf(buf, fmtstr_doset_tab, option, value); - add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, MENU_UNSELECTED); -} + f = fruit_from_name(*altname ? altname : str, FALSE, &highest_fruit_id); + if (f) + goto nonew; -STATIC_OVL void -opts_add_others(win, name, id, bufx, nset) -winid win; -const char *name; -int id; -char *bufx; -int nset; -{ - char buf[BUFSZ], buf2[BUFSZ]; - anything any = zeroany; + /* Maximum number of named fruits is 127, even if obj->spe can + handle bigger values. If adding another fruit would overflow, + use a random fruit instead... we've got a lot to choose from. + current_fruit remains as is. */ + if (highest_fruit_id >= 127) + return rnd(127); - any.a_int = id; - if (!bufx) - Sprintf(buf2, n_currently_set, nset); - else - Sprintf(buf2, "%s", bufx); - if (!iflags.menu_tab_sep) - Sprintf(buf, fmtstr_doset, any.a_int ? "" : " ", - name, buf2); - else - Sprintf(buf, fmtstr_doset_tab, name, buf2); - add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, MENU_UNSELECTED); + f = newfruit(); + (void) memset((genericptr_t) f, 0, sizeof (struct fruit)); + copynchars(f->fname, *altname ? altname : str, PL_FSIZ - 1); + f->fid = ++highest_fruit_id; + /* we used to go out of our way to add it at the end of the list, + but the order is arbitrary so use simpler insertion at start */ + f->nextf = gf.ffruit; + gf.ffruit = f; + nonew: + if (user_specified) + svc.context.current_fruit = f->fid; + return f->fid; } -int -count_apes(VOID_ARGS) -{ - int numapes = 0; - struct autopickup_exception *ape = apelist; - - while (ape) { - numapes++; - ape = ape->next; - } - - return numapes; -} +/* + ********************************** + * + * Option-setting, menus, + * displaying option values + * + ********************************** + */ -enum opt_other_enums { - OPT_OTHER_MSGTYPE = -4, - OPT_OTHER_MENUCOLOR = -3, - OPT_OTHER_STATHILITE = -2, - OPT_OTHER_APEXC = -1 - /* these must be < 0 */ -}; -static struct other_opts { - const char *name; - int optflags; - enum opt_other_enums code; - int NDECL((*othr_count_func)); -} othropt[] = { - { "autopickup exceptions", SET_IN_GAME, OPT_OTHER_APEXC, count_apes }, - { "menu colors", SET_IN_GAME, OPT_OTHER_MENUCOLOR, count_menucolors }, - { "message types", SET_IN_GAME, OPT_OTHER_MSGTYPE, msgtype_count }, -#ifdef STATUS_HILITES - { "status hilite rules", SET_IN_GAME, OPT_OTHER_STATHILITE, - count_status_hilites }, -#endif - { (char *) 0, 0, (enum opt_other_enums) 0 }, -}; +DISABLE_WARNING_FORMAT_NONLITERAL /* RESTORE is after show_menucontrols() */ -/* the 'O' command */ -int -doset() /* changing options via menu by Per Liboriussen */ +staticfn int +optfn_o_autopickup_exceptions( + int optidx UNUSED, int req, boolean negated UNUSED, + char *opts, char *op UNUSED) { - static boolean made_fmtstr = FALSE; - char buf[BUFSZ]; - const char *name; - int i = 0, pass, boolcount, pick_cnt, pick_idx, opt_indx; - boolean *bool_p; - winid tmpwin; - anything any; - menu_item *pick_list; - int indexoffset, startpass, endpass, optflags; - boolean setinitial = FALSE, fromfile = FALSE; - unsigned longest_name_len; - - tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - -#ifdef notyet /* SYSCF */ - /* XXX I think this is still fragile. Fixing initial/from_file and/or - changing the SET_* etc to bitmaps will let me make this better. */ - if (wizard) - startpass = SET_IN_SYS; - else -#endif - startpass = DISP_IN_GAME; - endpass = (wizard) ? SET_IN_WIZGAME : SET_IN_GAME; - - if (!made_fmtstr && !iflags.menu_tab_sep) { - /* spin through the options to find the longest name - and adjust the format string accordingly */ - longest_name_len = 0; - for (pass = 0; pass <= 2; pass++) - for (i = 0; (name = ((pass == 0) ? boolopt[i].name - : (pass == 1) ? compopt[i].name - : othropt[i].name)) != 0; i++) { - if (pass == 0 && !boolopt[i].addr) - continue; - optflags = (pass == 0) ? boolopt[i].optflags - : (pass == 1) ? compopt[i].optflags - : othropt[i].optflags; - if (optflags < startpass || optflags > endpass) - continue; - if ((is_wc_option(name) && !wc_supported(name)) - || (is_wc2_option(name) && !wc2_supported(name))) - continue; - - if (strlen(name) > longest_name_len) - longest_name_len = strlen(name); - } - Sprintf(fmtstr_doset, "%%s%%-%us [%%s]", longest_name_len); - made_fmtstr = TRUE; + if (req == do_init) { + return optn_ok; } + if (req == do_set) { + } + if (req == get_val || req == get_cnf_val) { + if (!opts) + return optn_err; + Sprintf(opts, n_currently_set, count_apes()); + return optn_ok; + } + if (req == do_handler) { + return handler_autopickup_exception(); + } + return optn_ok; +} - any = zeroany; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, iflags.menu_headings, - "Booleans (selecting will toggle value):", MENU_UNSELECTED); - any.a_int = 0; - /* first list any other non-modifiable booleans, then modifiable ones */ - for (pass = 0; pass <= 1; pass++) - for (i = 0; (name = boolopt[i].name) != 0; i++) - if ((bool_p = boolopt[i].addr) != 0 - && ((boolopt[i].optflags <= DISP_IN_GAME && pass == 0) - || (boolopt[i].optflags >= SET_IN_GAME && pass == 1))) { - if (bool_p == &flags.female) - continue; /* obsolete */ - if (boolopt[i].optflags == SET_IN_WIZGAME && !wizard) - continue; - if ((is_wc_option(name) && !wc_supported(name)) - || (is_wc2_option(name) && !wc2_supported(name))) - continue; - - any.a_int = (pass == 0) ? 0 : i + 1; - if (!iflags.menu_tab_sep) - Sprintf(buf, fmtstr_doset, (pass == 0) ? " " : "", - name, *bool_p ? "true" : "false"); - else - Sprintf(buf, fmtstr_doset_tab, - name, *bool_p ? "true" : "false"); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, - MENU_UNSELECTED); - } - - boolcount = i; - indexoffset = boolcount; - any = zeroany; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, iflags.menu_headings, - "Compounds (selecting will prompt for new value):", - MENU_UNSELECTED); - - /* deliberately put playmode, name, role+race+gender+align first */ - doset_add_menu(tmpwin, "playmode", 0); - doset_add_menu(tmpwin, "name", 0); - doset_add_menu(tmpwin, "role", 0); - doset_add_menu(tmpwin, "race", 0); - doset_add_menu(tmpwin, "gender", 0); - doset_add_menu(tmpwin, "align", 0); +staticfn int +optfn_o_bind_keys( + int optidx UNUSED, int req, boolean negated UNUSED, + char *opts, char *op UNUSED) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + } + if (req == get_val || req == get_cnf_val) { + if (!opts) + return optn_err; + Sprintf(opts, n_currently_set, count_bind_keys()); + return optn_ok; + } + if (req == do_handler) { + handler_rebind_keys(); + } + return optn_ok; +} - for (pass = startpass; pass <= endpass; pass++) - for (i = 0; (name = compopt[i].name) != 0; i++) - if (compopt[i].optflags == pass) { - if (!strcmp(name, "playmode") || !strcmp(name, "name") - || !strcmp(name, "role") || !strcmp(name, "race") - || !strcmp(name, "gender") || !strcmp(name, "align")) - continue; - if ((is_wc_option(name) && !wc_supported(name)) - || (is_wc2_option(name) && !wc2_supported(name))) - continue; +staticfn int +optfn_o_autocomplete( + int optidx UNUSED, int req, boolean negated UNUSED, + char *opts, char *op UNUSED) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + } + if (req == get_val || req == get_cnf_val) { + if (!opts) + return optn_err; + Sprintf(opts, n_currently_set, count_autocompletions()); + return optn_ok; + } + if (req == do_handler) { + handler_change_autocompletions(); + } + return optn_ok; +} - doset_add_menu(tmpwin, name, - (pass == DISP_IN_GAME) ? 0 : indexoffset); - } +staticfn int +optfn_o_menu_colors(int optidx UNUSED, int req, boolean negated UNUSED, + char *opts, char *op UNUSED) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + } + if (req == get_val || req == get_cnf_val) { + if (!opts) + return optn_err; + Sprintf(opts, n_currently_set, count_menucolors()); + return optn_ok; + } + if (req == do_handler) { + return handler_menu_colors(); + } + return optn_ok; +} - any = zeroany; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, iflags.menu_headings, - "Other settings:", MENU_UNSELECTED); +staticfn int +optfn_o_message_types( + int optidx UNUSED, + int req, + boolean negated UNUSED, + char *opts, + char *op UNUSED) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + } + if (req == get_val || req == get_cnf_val) { + if (!opts) + return optn_err; + Sprintf(opts, n_currently_set, msgtype_count()); + return optn_ok; + } + if (req == do_handler) { + return handler_msgtype(); + } + return optn_ok; +} - for (i = 0; (name = othropt[i].name) != 0; i++) { - if ((is_wc_option(name) && !wc_supported(name)) - || (is_wc2_option(name) && !wc2_supported(name))) - continue; - opts_add_others(tmpwin, name, othropt[i].code, - (char *) 0, othropt[i].othr_count_func()); +staticfn int +optfn_o_status_cond( + int optidx UNUSED, + int req, + boolean negated UNUSED, + char *opts, + char *op UNUSED) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + ; /* setting status condition options goes through pfxfn_cond_() */ + } + if (req == get_val) { + if (!opts) /* opts[] is used as an output argument */ + return optn_err; + Sprintf(opts, n_currently_set, count_cond()); + return optn_ok; + } + if (req == get_cnf_val) { + ; /* handled inline by all_options_strbuf() via all_options_conds() */ + } + if (req == do_handler) { + if (cond_menu()) + opt_set_in_config[pfx_cond_] = TRUE; + return optn_ok; } + return optn_ok; +} -#ifdef PREFIXES_IN_USE - any = zeroany; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, iflags.menu_headings, - "Variable playground locations:", MENU_UNSELECTED); - for (i = 0; i < PREFIX_COUNT; i++) - doset_add_menu(tmpwin, fqn_prefix_names[i], 0); -#endif - end_menu(tmpwin, "Set what options?"); - need_redraw = FALSE; - if ((pick_cnt = select_menu(tmpwin, PICK_ANY, &pick_list)) > 0) { - /* - * Walk down the selection list and either invert the booleans - * or prompt for new values. In most cases, call parseoptions() - * to take care of options that require special attention, like - * redraws. - */ - for (pick_idx = 0; pick_idx < pick_cnt; ++pick_idx) { - opt_indx = pick_list[pick_idx].item.a_int - 1; - if (opt_indx < -1) - opt_indx++; /* -1 offset for select_menu() */ - if (opt_indx == OPT_OTHER_APEXC) { - (void) special_handling("autopickup_exception", setinitial, - fromfile); #ifdef STATUS_HILITES - } else if (opt_indx == OPT_OTHER_STATHILITE) { - if (!status_hilite_menu()) { - pline("Bad status hilite(s) specified."); - } else { - if (wc2_supported("hilite_status")) - preference_update("hilite_status"); - } -#endif - } else if (opt_indx == OPT_OTHER_MENUCOLOR) { - (void) special_handling("menu_colors", setinitial, - fromfile); - } else if (opt_indx == OPT_OTHER_MSGTYPE) { - (void) special_handling("msgtype", setinitial, fromfile); - } else if (opt_indx < boolcount) { - /* boolean option */ - Sprintf(buf, "%s%s", *boolopt[opt_indx].addr ? "!" : "", - boolopt[opt_indx].name); - (void) parseoptions(buf, setinitial, fromfile); - if (wc_supported(boolopt[opt_indx].name) - || wc2_supported(boolopt[opt_indx].name)) - preference_update(boolopt[opt_indx].name); - } else { - /* compound option */ - opt_indx -= boolcount; +staticfn int +optfn_o_status_hilites( + int optidx UNUSED, + int req, + boolean negated UNUSED, + char *opts, + char *op UNUSED) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + } + if (req == get_val || req == get_cnf_val) { + if (!opts) + return optn_err; + Sprintf(opts, n_currently_set, count_status_hilites()); + return optn_ok; + } + if (req == do_handler) { + if (!status_hilite_menu()) { + return optn_err; /*pline("Bad status hilite(s) specified.");*/ + } else { + if (wc2_supported("hilite_status")) + preference_update("hilite_status"); + } + return optn_ok; + } + return optn_ok; +} +#endif /*STATUS_HILITES*/ - if (!special_handling(compopt[opt_indx].name, setinitial, - fromfile)) { - char abuf[BUFSZ]; +/* Get string value of configuration option. + * Currently handles only boolean and compound options. + */ +char * +get_option_value(const char *optname, boolean cnfvalid) +{ + static char retbuf[BUFSZ]; + boolean *bool_p; + int i; - Sprintf(buf, "Set %s to what?", compopt[opt_indx].name); - abuf[0] = '\0'; - getlin(buf, abuf); - if (abuf[0] == '\033') - continue; - Sprintf(buf, "%s:", compopt[opt_indx].name); - (void) strncat(eos(buf), abuf, - (sizeof buf - 1 - strlen(buf))); - /* pass the buck */ - (void) parseoptions(buf, setinitial, fromfile); - } - if (wc_supported(compopt[opt_indx].name) - || wc2_supported(compopt[opt_indx].name)) - preference_update(compopt[opt_indx].name); + for (i = 0; allopt[i].name != 0; i++) + if (!strcmp(optname, allopt[i].name)) { + if (allopt[i].opttyp == BoolOpt + && (bool_p = allopt[i].addr) != 0) { + Sprintf(retbuf, "%s", *bool_p ? "true" : "false"); + return retbuf; + } else if (allopt[i].opttyp == CompOpt && allopt[i].optfn) { + int reslt = optn_err; + + reslt = (*allopt[i].optfn)(allopt[i].idx, + cnfvalid ? get_cnf_val : get_val, + FALSE, retbuf, empty_optstr); + if (reslt == optn_ok && retbuf[0]) + return retbuf; + return (char *) 0; } } - free((genericptr_t) pick_list), pick_list = (menu_item *) 0; - } - - destroy_nhwindow(tmpwin); - if (need_redraw) { - check_gold_symbol(); - reglyph_darkroom(); - (void) doredraw(); - } - return 0; + return (char *) 0; } -/* common to msg-types, menu-colors, autopickup-exceptions */ -STATIC_OVL int -handle_add_list_remove(optname, numtotal) -const char *optname; -int numtotal; +staticfn unsigned int +longest_option_name(int startpass, int endpass) { - winid tmpwin; - anything any; - int i, pick_cnt, opt_idx; - menu_item *pick_list = (menu_item *) 0; - static const struct action { - char letr; - const char *desc; - } action_titles[] = { - { 'a', "add new %s" }, /* [0] */ - { 'l', "list %s" }, /* [1] */ - { 'r', "remove existing %s" }, /* [2] */ - { 'x', "exit this menu" }, /* [3] */ - }; + /* spin through the options to find the longest name */ + unsigned longest_name_len = 0; + int i, pass, optflags; + const char *name; - opt_idx = 0; - tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - any = zeroany; - for (i = 0; i < SIZE(action_titles); i++) { - char tmpbuf[BUFSZ]; + for (pass = 0; pass < 2; pass++) + for (i = 0; (name = allopt[i].name) != 0; i++) { + if (pass == 0 + && (allopt[i].opttyp != BoolOpt || !allopt[i].addr)) + continue; + optflags = allopt[i].setwhere; + if (optflags < startpass || optflags > endpass) + continue; + if ((is_wc_option(name) && !wc_supported(name)) + || (is_wc2_option(name) && !wc2_supported(name))) + continue; - any.a_int++; - /* omit list and remove if there aren't any yet */ - if (!numtotal && (i == 1 || i == 2)) - continue; - Sprintf(tmpbuf, action_titles[i].desc, - (i == 1) ? makeplural(optname) : optname); - add_menu(tmpwin, NO_GLYPH, &any, action_titles[i].letr, 0, ATR_NONE, - tmpbuf, (i == 3) ? MENU_SELECTED : MENU_UNSELECTED); - } - end_menu(tmpwin, "Do what?"); - if ((pick_cnt = select_menu(tmpwin, PICK_ONE, &pick_list)) > 0) { - opt_idx = pick_list[0].item.a_int - 1; - if (pick_cnt > 1 && opt_idx == 3) - opt_idx = pick_list[1].item.a_int - 1; - free((genericptr_t) pick_list); - } else - opt_idx = 3; /* none selected, exit menu */ - destroy_nhwindow(tmpwin); - return opt_idx; + unsigned len = Strlen(name); + if (len > longest_name_len) + longest_name_len = len; + } + return longest_name_len; } -struct symsetentry *symset_list = 0; /* files.c will populate this with - * list of available sets */ - -STATIC_OVL boolean -special_handling(optname, setinitial, setfromfile) -const char *optname; -boolean setinitial, setfromfile; +/* guts of doset_simple(); called repeatedly until no choice is made */ +staticfn int +doset_simple_menu(void) { + /* unlike doset()'s fmtstr, there is no leading %s for indentation */ + static const char fmtstr_tab_doset_simple[] = "%s\t[%s]"; + char fmtstr_doset_simple[sizeof "%-15s [%s] "]; + menu_item *pick_list; + boolean *bool_p; + const char *name, *fmtstr; + char buf[BUFSZ], buf2[BUFSZ], abuf[BUFSZ]; winid tmpwin; anything any; - int i, n; - char buf[BUFSZ]; - - /* Special handling of menustyle, pickup_burden, pickup_types, - * disclose, runmode, msg_window, menu_headings, sortloot, - * and number_pad options. - * Also takes care of interactive autopickup_exception_handling changes. - */ - if (!strcmp("menustyle", optname)) { - const char *style_name; - menu_item *style_pick = (menu_item *) 0; + enum OptSection section; + int i, k, pick_cnt, reslt; + boolean toggled_help = FALSE; - tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - any = zeroany; - for (i = 0; i < SIZE(menutype); i++) { - style_name = menutype[i]; - /* note: separate `style_name' variable used - to avoid an optimizer bug in VAX C V2.3 */ - any.a_int = i + 1; - add_menu(tmpwin, NO_GLYPH, &any, *style_name, 0, ATR_NONE, - style_name, MENU_UNSELECTED); - } - end_menu(tmpwin, "Select menustyle:"); - if (select_menu(tmpwin, PICK_ONE, &style_pick) > 0) { - flags.menu_style = style_pick->item.a_int - 1; - free((genericptr_t) style_pick); - } - destroy_nhwindow(tmpwin); - } else if (!strcmp("paranoid_confirmation", optname)) { - menu_item *paranoia_picks = (menu_item *) 0; + /* we do this each time we're called instead of once in doset_simple() + in case 'menu_tab_sep' ever gets included in the simple menu so + becomes subject to being changed while doset_simple() is running */ + if (!iflags.menu_tab_sep) + Sprintf(fmtstr_doset_simple, "%%-%us [%%s]", + longest_option_name(set_gameview, set_in_game)); + else + Strcpy(fmtstr_doset_simple, fmtstr_tab_doset_simple); + fmtstr = fmtstr_doset_simple; - tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - any = zeroany; - for (i = 0; paranoia[i].flagmask != 0; ++i) { - if (paranoia[i].flagmask == PARANOID_BONES && !wizard) + redo_opt_help: + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + + /* when showing 'help', also describe how to run full doset() */ + if (gs.simple_options_help) { + /* we could look up whether #optionsfull has been bound to a key + and show that, or whether #reqmenu and #options are both still + bound to keys and show those, but if meta keys are involved + the player might not know how to type them; keep this simple */ + Strcpy(buf, "Use command '#optionsfull'" + " to get the complete options list."); + add_menu_str(tmpwin, buf); + } + any = cg.zeroany; + any.a_int = -2 + 1; + add_menu(tmpwin, &nul_glyphinfo, &any, '?', 0, ATR_NONE, NO_COLOR, + gs.simple_options_help ? "hide help" : "show help", + MENU_ITEMFLAGS_NONE); + + for (section = OptS_General; section < OptS_Advanced; section++) { + any = cg.zeroany; + add_menu_str(tmpwin, ""); + Sprintf(buf, " %-30s ", OptS_type[section]); + add_menu_heading(tmpwin, buf); + for (i = 0; (name = allopt[i].name) != 0; i++) { + if (allopt[i].section != section) + continue; + if ((is_wc_option(name) && !wc_supported(name)) + || (is_wc2_option(name) && !wc2_supported(name))) continue; - any.a_int = paranoia[i].flagmask; - add_menu(tmpwin, NO_GLYPH, &any, *paranoia[i].argname, 0, - ATR_NONE, paranoia[i].explain, - (flags.paranoia_bits & paranoia[i].flagmask) - ? MENU_SELECTED - : MENU_UNSELECTED); - } - end_menu(tmpwin, "Actions requiring extra confirmation:"); - i = select_menu(tmpwin, PICK_ANY, ¶noia_picks); - if (i >= 0) { - /* player didn't cancel; we reset all the paranoia options - here even if there were no items picked, since user - could have toggled off preselected ones to end up with 0 */ - flags.paranoia_bits = 0; - if (i > 0) { - /* at least 1 item set, either preselected or newly picked */ - while (--i >= 0) - flags.paranoia_bits |= paranoia_picks[i].item.a_int; - free((genericptr_t) paranoia_picks); - } - } - destroy_nhwindow(tmpwin); - } else if (!strcmp("pickup_burden", optname)) { - const char *burden_name, *burden_letters = "ubsntl"; - menu_item *burden_pick = (menu_item *) 0; - tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - any = zeroany; - for (i = 0; i < SIZE(burdentype); i++) { - burden_name = burdentype[i]; any.a_int = i + 1; - add_menu(tmpwin, NO_GLYPH, &any, burden_letters[i], 0, ATR_NONE, - burden_name, MENU_UNSELECTED); - } - end_menu(tmpwin, "Select encumbrance level:"); - if (select_menu(tmpwin, PICK_ONE, &burden_pick) > 0) { - flags.pickup_burden = burden_pick->item.a_int - 1; - free((genericptr_t) burden_pick); - } - destroy_nhwindow(tmpwin); - } else if (!strcmp("pickup_types", optname)) { - /* parseoptions will prompt for the list of types */ - (void) parseoptions(strcpy(buf, "pickup_types"), - setinitial, setfromfile); - } else if (!strcmp("disclose", optname)) { - /* order of disclose_names[] must correspond to - disclosure_options in decl.c */ - static const char *disclosure_names[] = { - "inventory", "attributes", "vanquished", - "genocides", "conduct", "overview", - }; - int disc_cat[NUM_DISCLOSURE_OPTIONS]; - int pick_cnt, pick_idx, opt_idx; - char c; - menu_item *disclosure_pick = (menu_item *) 0; + switch (allopt[i].opttyp) { + case BoolOpt: + bool_p = allopt[i].addr; + if (!bool_p) + continue; + if (iflags.wc_tiled_map && allopt[i].idx == opt_color) + continue; + Sprintf(buf, fmtstr, name, *bool_p ? "X" : " "); + break; + case CompOpt: + case OthrOpt: + k = i; + if (allopt[i].optfn == optfn_symset + && Is_rogue_level(&u.uz)) { + k = opt_roguesymset; + name = allopt[k].name; + any.a_int = k + 1; + } - tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - any = zeroany; - for (i = 0; i < NUM_DISCLOSURE_OPTIONS; i++) { - Sprintf(buf, "%-12s[%c%c]", disclosure_names[i], - flags.end_disclose[i], disclosure_options[i]); - any.a_int = i + 1; - add_menu(tmpwin, NO_GLYPH, &any, disclosure_options[i], 0, - ATR_NONE, buf, MENU_UNSELECTED); - disc_cat[i] = 0; - } - end_menu(tmpwin, "Change which disclosure options categories:"); - pick_cnt = select_menu(tmpwin, PICK_ANY, &disclosure_pick); - if (pick_cnt > 0) { - for (pick_idx = 0; pick_idx < pick_cnt; ++pick_idx) { - opt_idx = disclosure_pick[pick_idx].item.a_int - 1; - disc_cat[opt_idx] = 1; + buf2[0] = '\0'; + reslt = optn_err; + if (allopt[k].optfn) + reslt = (*allopt[k].optfn)(allopt[k].idx, get_val, + FALSE, buf2, empty_optstr); + Sprintf(buf, fmtstr, name, + ((reslt == optn_ok && buf2[0]) + ? (const char *) buf2 : "unknown")); + break; + default: + Sprintf(buf, "ERROR"); + break; + } + /* pickup_types is separated from autopickup due to the + spelling of their names; emphasize what it means */ + if (allopt[i].idx == opt_pickup_types + || allopt[i].idx == opt_pickup_thrown + || allopt[i].idx == opt_pickup_stolen + || allopt[i].idx == opt_dropped_nopick) + Strcat(buf, " (for autopickup)"); + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, + ATR_NONE, NO_COLOR, buf, MENU_ITEMFLAGS_NONE); + if (gs.simple_options_help && allopt[i].descr) { + Sprintf(buf, " %s", allopt[i].descr); + add_menu_str(tmpwin, buf); + add_menu_str(tmpwin, ""); } - free((genericptr_t) disclosure_pick); - disclosure_pick = (menu_item *) 0; } - destroy_nhwindow(tmpwin); + } + end_menu(tmpwin, "Options"); - for (i = 0; i < NUM_DISCLOSURE_OPTIONS; i++) { - if (disc_cat[i]) { - c = flags.end_disclose[i]; - Sprintf(buf, "Disclosure options for %s:", - disclosure_names[i]); - tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - any = zeroany; - /* 'y','n',and '+' work as alternate selectors; '-' doesn't */ - any.a_char = DISCLOSE_NO_WITHOUT_PROMPT; - add_menu(tmpwin, NO_GLYPH, &any, 0, any.a_char, ATR_NONE, - "Never disclose, without prompting", - (c == any.a_char) ? MENU_SELECTED : MENU_UNSELECTED); - any.a_char = DISCLOSE_YES_WITHOUT_PROMPT; - add_menu(tmpwin, NO_GLYPH, &any, 0, any.a_char, ATR_NONE, - "Always disclose, without prompting", - (c == any.a_char) ? MENU_SELECTED : MENU_UNSELECTED); - if (*disclosure_names[i] == 'v') { - any.a_char = DISCLOSE_SPECIAL_WITHOUT_PROMPT; /* '#' */ - add_menu(tmpwin, NO_GLYPH, &any, 0, any.a_char, ATR_NONE, - "Always disclose, pick sort order from menu", - (c == any.a_char) ? MENU_SELECTED - : MENU_UNSELECTED); - } - any.a_char = DISCLOSE_PROMPT_DEFAULT_NO; - add_menu(tmpwin, NO_GLYPH, &any, 0, any.a_char, ATR_NONE, - "Prompt, with default answer of \"No\"", - (c == any.a_char) ? MENU_SELECTED : MENU_UNSELECTED); - any.a_char = DISCLOSE_PROMPT_DEFAULT_YES; - add_menu(tmpwin, NO_GLYPH, &any, 0, any.a_char, ATR_NONE, - "Prompt, with default answer of \"Yes\"", - (c == any.a_char) ? MENU_SELECTED : MENU_UNSELECTED); - if (*disclosure_names[i] == 'v') { - any.a_char = DISCLOSE_PROMPT_DEFAULT_SPECIAL; /* '?' */ - add_menu(tmpwin, NO_GLYPH, &any, 0, any.a_char, ATR_NONE, - "Prompt, with default answer of \"Ask\" to request sort menu", - (c == any.a_char) ? MENU_SELECTED - : MENU_UNSELECTED); - } - end_menu(tmpwin, buf); - n = select_menu(tmpwin, PICK_ONE, &disclosure_pick); - if (n > 0) { - flags.end_disclose[i] = disclosure_pick[0].item.a_char; - if (n > 1 && flags.end_disclose[i] == c) - flags.end_disclose[i] = disclosure_pick[1].item.a_char; - free((genericptr_t) disclosure_pick); + go.opt_need_redraw = FALSE; + go.opt_need_glyph_reset = FALSE; + go.opt_reset_customcolors = FALSE; + go.opt_reset_customsymbols = FALSE; + go.opt_update_basic_palette = FALSE; + pick_cnt = select_menu(tmpwin, PICK_ONE, &pick_list); + /* note: without the complication of a preselected entry, a PICK_ONE + menu returning pick_cnt > 0 implies exactly 1 */ + if (pick_cnt > 0) { + k = pick_list[0].item.a_int - 1; + + abuf[0] = '\0'; + if (k == -2) { + gs.simple_options_help = !gs.simple_options_help; + toggled_help = TRUE; + } else if (allopt[k].opttyp == BoolOpt) { + /* boolean option */ + Sprintf(buf, "%s%s", *allopt[k].addr ? "!" : "", allopt[k].name); + (void) parseoptions(buf, FALSE, FALSE); + } else { + /* compound option */ + if (allopt[k].has_handler && allopt[k].optfn) { + reslt = (*allopt[k].optfn)(allopt[k].idx, do_handler, FALSE, + empty_optstr, empty_optstr); + /* if player eventually saves options, include this one */ + if (reslt == optn_ok && allopt[k].idx != pfx_cond_) + opt_set_in_config[k] = TRUE; + } else { + Sprintf(buf, "Set %s to what?", allopt[k].name); + getlin(buf, abuf); + if (abuf[0] != '\033') { /* ESC */ + Sprintf(buf, "%s:", allopt[k].name); + copynchars(eos(buf), abuf, + (int) (sizeof buf - 1 - strlen(buf))); + /* pass the buck */ + (void) parseoptions(buf, FALSE, FALSE); } - destroy_nhwindow(tmpwin); + /* Note: using ESC to not set a new value will still return + 'picked 1' to caller which will loop for another choice */ } } - } else if (!strcmp("runmode", optname)) { - const char *mode_name; - menu_item *mode_pick = (menu_item *) 0; - - tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - any = zeroany; - for (i = 0; i < SIZE(runmodes); i++) { - mode_name = runmodes[i]; - any.a_int = i + 1; - add_menu(tmpwin, NO_GLYPH, &any, *mode_name, 0, ATR_NONE, - mode_name, MENU_UNSELECTED); - } - end_menu(tmpwin, "Select run/travel display mode:"); - if (select_menu(tmpwin, PICK_ONE, &mode_pick) > 0) { - flags.runmode = mode_pick->item.a_int - 1; - free((genericptr_t) mode_pick); - } - destroy_nhwindow(tmpwin); - } else if (!strcmp("whatis_coord", optname)) { - menu_item *window_pick = (menu_item *) 0; - int pick_cnt; - char gp = iflags.getpos_coords; - - tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - any = zeroany; - any.a_char = GPCOORDS_COMPASS; - add_menu(tmpwin, NO_GLYPH, &any, GPCOORDS_COMPASS, 0, ATR_NONE, - "compass ('east' or '3s' or '2n,4w')", - (gp == GPCOORDS_COMPASS) ? MENU_SELECTED : MENU_UNSELECTED); - any.a_char = GPCOORDS_COMFULL; - add_menu(tmpwin, NO_GLYPH, &any, GPCOORDS_COMFULL, 0, ATR_NONE, - "full compass ('east' or '3south' or '2north,4west')", - (gp == GPCOORDS_COMFULL) ? MENU_SELECTED : MENU_UNSELECTED); - any.a_char = GPCOORDS_MAP; - add_menu(tmpwin, NO_GLYPH, &any, GPCOORDS_MAP, 0, ATR_NONE, - "map ", - (gp == GPCOORDS_MAP) ? MENU_SELECTED : MENU_UNSELECTED); - any.a_char = GPCOORDS_SCREEN; - add_menu(tmpwin, NO_GLYPH, &any, GPCOORDS_SCREEN, 0, ATR_NONE, - "screen [row,column]", - (gp == GPCOORDS_SCREEN) ? MENU_SELECTED : MENU_UNSELECTED); - any.a_char = GPCOORDS_NONE; - add_menu(tmpwin, NO_GLYPH, &any, GPCOORDS_NONE, 0, ATR_NONE, - "none (no coordinates displayed)", - (gp == GPCOORDS_NONE) ? MENU_SELECTED : MENU_UNSELECTED); - any.a_long = 0L; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED); - Sprintf(buf, "map: upper-left: <%d,%d>, lower-right: <%d,%d>%s", - 1, 0, COLNO - 1, ROWNO - 1, - flags.verbose ? "; column 0 unused, off left edge" : ""); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, MENU_UNSELECTED); - if (strcmp(windowprocs.name, "tty")) /* only show for non-tty */ - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - "screen: row is offset to accommodate tty interface's use of top line", - MENU_UNSELECTED); -#if COLNO == 80 -#define COL80ARG flags.verbose ? "; column 80 is not used" : "" -#else -#define COL80ARG "" -#endif - Sprintf(buf, "screen: upper-left: [%02d,%02d], lower-right: [%d,%d]%s", - 0 + 2, 1, ROWNO - 1 + 2, COLNO - 1, COL80ARG); -#undef COL80ARG - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, MENU_UNSELECTED); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED); - end_menu(tmpwin, - "Select coordinate display when auto-describing a map position:"); - if ((pick_cnt = select_menu(tmpwin, PICK_ONE, &window_pick)) > 0) { - iflags.getpos_coords = window_pick[0].item.a_char; - /* PICK_ONE doesn't unselect preselected entry when - selecting another one */ - if (pick_cnt > 1 && iflags.getpos_coords == gp) - iflags.getpos_coords = window_pick[1].item.a_char; - free((genericptr_t) window_pick); - } - destroy_nhwindow(tmpwin); - } else if (!strcmp("whatis_filter", optname)) { - menu_item *window_pick = (menu_item *) 0; - int pick_cnt; - char gf = iflags.getloc_filter; - - tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - any = zeroany; - any.a_char = (GFILTER_NONE + 1); - add_menu(tmpwin, NO_GLYPH, &any, 'n', - 0, ATR_NONE, "no filtering", - (gf == GFILTER_NONE) ? MENU_SELECTED : MENU_UNSELECTED); - any.a_char = (GFILTER_VIEW + 1); - add_menu(tmpwin, NO_GLYPH, &any, 'v', - 0, ATR_NONE, "in view only", - (gf == GFILTER_VIEW) ? MENU_SELECTED : MENU_UNSELECTED); - any.a_char = (GFILTER_AREA + 1); - add_menu(tmpwin, NO_GLYPH, &any, 'a', - 0, ATR_NONE, "in same area", - (gf == GFILTER_AREA) ? MENU_SELECTED : MENU_UNSELECTED); - end_menu(tmpwin, - "Select location filtering when going for next/previous map position:"); - if ((pick_cnt = select_menu(tmpwin, PICK_ONE, &window_pick)) > 0) { - iflags.getloc_filter = (window_pick[0].item.a_char - 1); - /* PICK_ONE doesn't unselect preselected entry when - selecting another one */ - if (pick_cnt > 1 && iflags.getloc_filter == gf) - iflags.getloc_filter = (window_pick[1].item.a_char - 1); - free((genericptr_t) window_pick); - } - destroy_nhwindow(tmpwin); - } else if (!strcmp("msg_window", optname)) { -#if defined(TTY_GRAPHICS) || defined(CURSES_GRAPHICS) - if (WINDOWPORT("tty") || WINDOWPORT("curses")) { - /* by Christian W. Cooper */ - menu_item *window_pick = (menu_item *) 0; - - tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - any = zeroany; - if (!WINDOWPORT("curses")) { - any.a_char = 's'; - add_menu(tmpwin, NO_GLYPH, &any, 's', 0, ATR_NONE, - "single", MENU_UNSELECTED); - any.a_char = 'c'; - add_menu(tmpwin, NO_GLYPH, &any, 'c', 0, ATR_NONE, - "combination", MENU_UNSELECTED); - } - any.a_char = 'f'; - add_menu(tmpwin, NO_GLYPH, &any, 'f', 0, ATR_NONE, "full", - MENU_UNSELECTED); - any.a_char = 'r'; - add_menu(tmpwin, NO_GLYPH, &any, 'r', 0, ATR_NONE, "reversed", - MENU_UNSELECTED); - end_menu(tmpwin, "Select message history display type:"); - if (select_menu(tmpwin, PICK_ONE, &window_pick) > 0) { - iflags.prevmsg_window = window_pick->item.a_char; - free((genericptr_t) window_pick); - } - destroy_nhwindow(tmpwin); - } else -#endif /* msg_window for tty or curses */ - pline("'%s' option is not supported for '%s'.", - optname, windowprocs.name); - } else if (!strcmp("sortloot", optname)) { - const char *sortl_name; - menu_item *sortl_pick = (menu_item *) 0; + if (k >= 0 && abuf[0] != '\033' + && (wc_supported(allopt[k].name) + || wc2_supported(allopt[k].name))) + preference_update(allopt[k].name); - tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - any = zeroany; - for (i = 0; i < SIZE(sortltype); i++) { - sortl_name = sortltype[i]; - any.a_char = *sortl_name; - add_menu(tmpwin, NO_GLYPH, &any, *sortl_name, 0, ATR_NONE, - sortl_name, (flags.sortloot == *sortl_name) - ? MENU_SELECTED : MENU_UNSELECTED); - } - end_menu(tmpwin, "Select loot sorting type:"); - n = select_menu(tmpwin, PICK_ONE, &sortl_pick); - if (n > 0) { - char c = sortl_pick[0].item.a_char; + free((genericptr_t) pick_list), pick_list = (menu_item *) 0; + } + /* tear down this instance of the menu; if pick_cnt is 1, caller + will immediately call us back to put up another instance */ + destroy_nhwindow(tmpwin); - if (n > 1 && c == flags.sortloot) - c = sortl_pick[1].item.a_char; - flags.sortloot = c; - free((genericptr_t) sortl_pick); - } - destroy_nhwindow(tmpwin); - } else if (!strcmp("align_message", optname) - || !strcmp("align_status", optname)) { - menu_item *window_pick = (menu_item *) 0; - char abuf[BUFSZ]; - boolean msg = (*(optname + 6) == 'm'); + if (toggled_help) { + toggled_help = FALSE; + goto redo_opt_help; + } - tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - any = zeroany; - any.a_int = ALIGN_TOP; - add_menu(tmpwin, NO_GLYPH, &any, 't', 0, ATR_NONE, "top", - MENU_UNSELECTED); - any.a_int = ALIGN_BOTTOM; - add_menu(tmpwin, NO_GLYPH, &any, 'b', 0, ATR_NONE, "bottom", - MENU_UNSELECTED); - any.a_int = ALIGN_LEFT; - add_menu(tmpwin, NO_GLYPH, &any, 'l', 0, ATR_NONE, "left", - MENU_UNSELECTED); - any.a_int = ALIGN_RIGHT; - add_menu(tmpwin, NO_GLYPH, &any, 'r', 0, ATR_NONE, "right", - MENU_UNSELECTED); - Sprintf(abuf, "Select %s window placement relative to the map:", - msg ? "message" : "status"); - end_menu(tmpwin, abuf); - if (select_menu(tmpwin, PICK_ONE, &window_pick) > 0) { - if (msg) - iflags.wc_align_message = window_pick->item.a_int; - else - iflags.wc_align_status = window_pick->item.a_int; - free((genericptr_t) window_pick); - } - destroy_nhwindow(tmpwin); - } else if (!strcmp("number_pad", optname)) { - static const char *npchoices[] = { - " 0 (off)", " 1 (on)", " 2 (on, MSDOS compatible)", - " 3 (on, phone-style digit layout)", - " 4 (on, phone-style layout, MSDOS compatible)", - "-1 (off, 'z' to move upper-left, 'y' to zap wands)" - }; - menu_item *mode_pick = (menu_item *) 0; + return pick_cnt; +} - tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - any = zeroany; - for (i = 0; i < SIZE(npchoices); i++) { - any.a_int = i + 1; - add_menu(tmpwin, NO_GLYPH, &any, 'a' + i, 0, ATR_NONE, - npchoices[i], MENU_UNSELECTED); - } - end_menu(tmpwin, "Select number_pad mode:"); - if (select_menu(tmpwin, PICK_ONE, &mode_pick) > 0) { - switch (mode_pick->item.a_int - 1) { - case 0: - iflags.num_pad = FALSE; - iflags.num_pad_mode = 0; - break; - case 1: - iflags.num_pad = TRUE; - iflags.num_pad_mode = 0; - break; - case 2: - iflags.num_pad = TRUE; - iflags.num_pad_mode = 1; - break; - case 3: - iflags.num_pad = TRUE; - iflags.num_pad_mode = 2; - break; - case 4: - iflags.num_pad = TRUE; - iflags.num_pad_mode = 3; - break; - /* last menu choice: number_pad == -1 */ - case 5: - iflags.num_pad = FALSE; - iflags.num_pad_mode = 1; - break; - } - reset_commands(FALSE); - number_pad(iflags.num_pad ? 1 : 0); - free((genericptr_t) mode_pick); +/* #options - the user friendly version: get one option from a subset of + the zillion choices, act upon it, and prompt for another */ +int +doset_simple(void) +{ + int pickedone = 0; + boolean flush = FALSE; + + if (iflags.menu_requested) { + /* doset() checks for 'm' and calls doset_simple(); clear the + menu-requested flag to avoid doing that recursively */ + iflags.menu_requested = FALSE; + return doset(); + } + + go.opt_phase = play_opt; + /* select and change one option at a time, then reprocess the menu + with updated settings to offer chance for further change */ + give_opt_msg = FALSE; + do { + pickedone = doset_simple_menu(); + flush = go.opt_need_redraw; + + reset_needed_visuals(); + if (flush) { + flush_screen(1); + flush = FALSE; } - destroy_nhwindow(tmpwin); - } else if (!strcmp("menu_headings", optname)) { - int mhattr = query_attr("How to highlight menu headings:"); - - if (mhattr != -1) - iflags.menu_headings = mhattr; - } else if (!strcmp("msgtype", optname)) { - int opt_idx, nmt, mttyp; - char mtbuf[BUFSZ]; - - msgtypes_again: - nmt = msgtype_count(); - opt_idx = handle_add_list_remove("message type", nmt); - if (opt_idx == 3) { /* done */ - return TRUE; - } else if (opt_idx == 0) { /* add new */ - mtbuf[0] = '\0'; - getlin("What new message pattern?", mtbuf); - if (*mtbuf == '\033') - return TRUE; - if (*mtbuf - && test_regex_pattern(mtbuf, (const char *)0) - && (mttyp = query_msgtype()) != -1 - && !msgtype_add(mttyp, mtbuf)) { - pline("Error adding the message type."); - wait_synch(); - } - goto msgtypes_again; - } else { /* list (1) or remove (2) */ - int pick_idx, pick_cnt; - int mt_idx; - unsigned ln; - const char *mtype; - menu_item *pick_list = (menu_item *) 0; - struct plinemsg_type *tmp = plinemsg_types; + } while (pickedone > 0); + give_opt_msg = TRUE; + return ECMD_OK; +} - tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - any = zeroany; - mt_idx = 0; - while (tmp) { - mtype = msgtype2name(tmp->msgtype); - any.a_int = ++mt_idx; - Sprintf(mtbuf, "%-5s \"", mtype); - ln = sizeof mtbuf - strlen(mtbuf) - sizeof "\""; - if (strlen(tmp->pattern) > ln) - Strcat(strncat(mtbuf, tmp->pattern, ln - 3), "...\""); - else - Strcat(strcat(mtbuf, tmp->pattern), "\""); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, mtbuf, - MENU_UNSELECTED); - tmp = tmp->next; - } - Sprintf(mtbuf, "%s message types", - (opt_idx == 1) ? "List of" : "Remove which"); - end_menu(tmpwin, mtbuf); - pick_cnt = select_menu(tmpwin, - (opt_idx == 1) ? PICK_NONE : PICK_ANY, - &pick_list); - if (pick_cnt > 0) { - for (pick_idx = 0; pick_idx < pick_cnt; ++pick_idx) - free_one_msgtype(pick_list[pick_idx].item.a_int - 1 - - pick_idx); - free((genericptr_t) pick_list), pick_list = (menu_item *) 0; - } - destroy_nhwindow(tmpwin); - if (pick_cnt >= 0) - goto msgtypes_again; - } - } else if (!strcmp("menu_colors", optname)) { - int opt_idx, nmc, mcclr, mcattr; - char mcbuf[BUFSZ]; +staticfn const char * +term_for_boolean(int idx, boolean *b) +{ + int i, f_t = (*b) ? 1: 0; + const char *boolean_term; + static const char *const booleanterms[2][num_terms] = { + { "false", "off", "disabled", "excluded from build" }, + { "true", "on", "enabled", "included"}, + }; - menucolors_again: - nmc = count_menucolors(); - opt_idx = handle_add_list_remove("menucolor", nmc); - if (opt_idx == 3) { /* done */ - menucolors_done: - /* in case we've made a change which impacts current persistent - inventory window; we don't track whether an actual changed - occurred, so just assume there was one and that it matters; - if we're wrong, a redundant update is cheap... */ - if (iflags.use_menu_color) - update_inventory(); + boolean_term = booleanterms[f_t][0]; + i = (int) allopt[idx].termpref; + if (i > Term_False && i < num_terms && i < SIZE(booleanterms[0])) + boolean_term = booleanterms[f_t][i]; + return boolean_term; +} - /* menu colors aren't being used; if any are defined, remind - player how to use them */ - else if (nmc > 0) - pline( - "To have menu colors become active, toggle 'menucolors' option to True."); - return TRUE; +#define HELP_IDX (SIZE(allopt)) - } else if (opt_idx == 0) { /* add new */ - mcbuf[0] = '\0'; - getlin("What new menucolor pattern?", mcbuf); - if (*mcbuf == '\033') - goto menucolors_done; - if (*mcbuf - && test_regex_pattern(mcbuf, (const char *)0) - && (mcclr = query_color((char *) 0)) != -1 - && (mcattr = query_attr((char *) 0)) != -1 - && !add_menu_coloring_parsed(mcbuf, mcclr, mcattr)) { - pline("Error adding the menu color."); - wait_synch(); - } - goto menucolors_again; +/* the #optionsfull command */ +int +doset(void) /* changing options via menu by Per Liboriussen */ +{ + static const char fmtstr_tab_doset[] = "%s%s\t[%s]"; + char fmtstr_doset[sizeof "%s%-15s [%s] "]; + char buf[BUFSZ]; + const char *name, *indent; + int i = 0, pass, pick_cnt, pick_idx, opt_indx; + boolean *bool_p; + winid tmpwin; + anything any; + menu_item *pick_list; + int indexoffset, startpass, endpass; + boolean gavehelp = FALSE, skiphelp = !iflags.cmdassist; + int clr = NO_COLOR; - } else { /* list (1) or remove (2) */ - int pick_idx, pick_cnt; - int mc_idx; - unsigned ln; - const char *sattr, *sclr; - menu_item *pick_list = (menu_item *) 0; - struct menucoloring *tmp = menu_colorings; - char clrbuf[QBUFSZ]; + if (iflags.menu_requested) { + /* doset_simple() checks for 'm' and calls doset(); clear the + menu-requested flag to avoid doing that recursively */ + iflags.menu_requested = FALSE; + return doset_simple(); + } - tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - any = zeroany; - mc_idx = 0; - while (tmp) { - sattr = attr2attrname(tmp->attr); - sclr = strcpy(clrbuf, clr2colorname(tmp->color)); - (void) strNsubst(clrbuf, " ", "-", 0); - any.a_int = ++mc_idx; - /* construct suffix */ - Sprintf(buf, "\"\"=%s%s%s", sclr, - (tmp->attr != ATR_NONE) ? "&" : "", - (tmp->attr != ATR_NONE) ? sattr : ""); - /* now main string */ - ln = sizeof buf - strlen(buf) - 1; /* length available */ - Strcpy(mcbuf, "\""); - if (strlen(tmp->origstr) > ln) - Strcat(strncat(mcbuf, tmp->origstr, ln - 3), "..."); - else - Strcat(mcbuf, tmp->origstr); - /* combine main string and suffix */ - Strcat(mcbuf, &buf[1]); /* skip buf[]'s initial quote */ - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, mcbuf, - MENU_UNSELECTED); - tmp = tmp->next; - } - Sprintf(mcbuf, "%s menu colors", - (opt_idx == 1) ? "List of" : "Remove which"); - end_menu(tmpwin, mcbuf); - pick_cnt = select_menu(tmpwin, - (opt_idx == 1) ? PICK_NONE : PICK_ANY, - &pick_list); - if (pick_cnt > 0) { - for (pick_idx = 0; pick_idx < pick_cnt; ++pick_idx) - free_one_menu_coloring(pick_list[pick_idx].item.a_int - 1 - - pick_idx); - free((genericptr_t) pick_list), pick_list = (menu_item *) 0; + go.opt_phase = play_opt; + /* if we offer '?' as a choice and it is the only thing chosen, + we'll end up coming back here after showing the explanatory text */ + rerun: + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + + /* offer novices a chance to request helpful [sic] advice */ + if (!skiphelp) { + /* help text surrounding '?' choice should have exactly one NULL */ + static const char *const helptext[] = { + "For a brief explanation of how this works, type '?' to select", + "the next menu choice, then press or .", + NULL, /* actual '?' menu entry gets inserted here */ + ("[To suppress this menu help," + " toggle off the 'cmdassist' option.]"), + "", + }; + any = cg.zeroany; + for (i = 0; i < SIZE(helptext); ++i) { + if (helptext[i]) { + Sprintf(buf, "%4s%.75s", "", helptext[i]); + add_menu_str(tmpwin, buf); + } else { + any.a_int = HELP_IDX + 1; /* handling pick_list subtracts 1 */ + add_menu(tmpwin, &nul_glyphinfo, &any, '?', '?', ATR_NONE, + clr, "view help for options menu", + MENU_ITEMFLAGS_SKIPINVERT); } - destroy_nhwindow(tmpwin); - if (pick_cnt >= 0) - goto menucolors_again; } - } else if (!strcmp("autopickup_exception", optname)) { - int opt_idx, numapes = 0; - char apebuf[2 + BUFSZ]; /* so &apebuf[1] is BUFSZ long for getlin() */ - struct autopickup_exception *ape; + } - ape_again: - numapes = count_apes(); - opt_idx = handle_add_list_remove("autopickup exception", numapes); - if (opt_idx == 3) { /* done */ - return TRUE; - } else if (opt_idx == 0) { /* add new */ - /* EDIT_GETLIN: assume user doesn't user want previous - exception used as default input string for this one... */ - apebuf[0] = apebuf[1] = '\0'; - getlin("What new autopickup exception pattern?", &apebuf[1]); - mungspaces(&apebuf[1]); /* regularize whitespace */ - if (apebuf[1] == '\033') - return TRUE; - if (apebuf[1]) { - apebuf[0] = '\"'; - /* guarantee room for \" prefix and \"\0 suffix; - -2 is good enough for apebuf[] but -3 makes - sure the whole thing fits within normal BUFSZ */ - apebuf[sizeof apebuf - 2] = '\0'; - Strcat(apebuf, "\""); - add_autopickup_exception(apebuf); - } - goto ape_again; - } else { /* list (1) or remove (2) */ - int pick_idx, pick_cnt; - menu_item *pick_list = (menu_item *) 0; +#ifdef notyet /* SYSCF */ + /* XXX I think this is still fragile. Fixing initial/from_file and/or + changing the SET_* etc to bitmaps will let me make this better. */ + if (wizard) + startpass = set_in_sysconf; + else +#endif + startpass = set_gameview; + endpass = (wizard) ? set_wiznofuz : set_in_game; - tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - if (numapes) { - ape = apelist; - any = zeroany; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, iflags.menu_headings, - "Always pickup '<'; never pickup '>'", - MENU_UNSELECTED); - for (i = 0; i < numapes && ape; i++) { - any.a_void = (opt_idx == 1) ? 0 : ape; - /* length of pattern plus quotes (plus '<'/'>') is - less than BUFSZ */ - Sprintf(apebuf, "\"%c%s\"", ape->grab ? '<' : '>', - ape->pattern); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, apebuf, - MENU_UNSELECTED); - ape = ape->next; - } - } - Sprintf(apebuf, "%s autopickup exceptions", - (opt_idx == 1) ? "List of" : "Remove which"); - end_menu(tmpwin, apebuf); - pick_cnt = select_menu(tmpwin, - (opt_idx == 1) ? PICK_NONE : PICK_ANY, - &pick_list); - if (pick_cnt > 0) { - for (pick_idx = 0; pick_idx < pick_cnt; ++pick_idx) - remove_autopickup_exception( - (struct autopickup_exception *) - pick_list[pick_idx].item.a_void); - free((genericptr_t) pick_list), pick_list = (menu_item *) 0; - } - destroy_nhwindow(tmpwin); - if (pick_cnt >= 0) - goto ape_again; - } - } else if (!strcmp("symset", optname) - || !strcmp("roguesymset", optname)) { - menu_item *symset_pick = (menu_item *) 0; - boolean rogueflag = (*optname == 'r'), - ready_to_switch = FALSE, - nothing_to_do = FALSE; - char *symset_name, fmtstr[20]; - struct symsetentry *sl; - int res, which_set, setcount = 0, chosen = -2, defindx = 0; - - which_set = rogueflag ? ROGUESET : PRIMARY; - symset_list = (struct symsetentry *) 0; - /* clear symset[].name as a flag to read_sym_file() to build list */ - symset_name = symset[which_set].name; - symset[which_set].name = (char *) 0; - - res = read_sym_file(which_set); - /* put symset name back */ - symset[which_set].name = symset_name; - - if (res && symset_list) { - int thissize, - biggest = (int) (sizeof "Default Symbols" - sizeof ""), - big_desc = 0; - - for (sl = symset_list; sl; sl = sl->next) { - /* check restrictions */ - if (rogueflag ? sl->primary : sl->rogue) + if (!iflags.menu_tab_sep) + /* initial "%s" is for indentation of non-selectable items */ + Sprintf(fmtstr_doset, "%%s%%-%us [%%s]", + longest_option_name(startpass, endpass)); + else + Strcpy(fmtstr_doset, fmtstr_tab_doset); + + indexoffset = 1; + any = cg.zeroany; + add_menu_heading(tmpwin, "Booleans (selecting will toggle value):"); + any.a_int = 0; + /* first list any other non-modifiable booleans, then modifiable ones */ + for (pass = 0; pass <= 1; pass++) + for (i = 0; (name = allopt[i].name) != 0; i++) + if (allopt[i].opttyp == BoolOpt + && (bool_p = allopt[i].addr) != 0 + && ((allopt[i].setwhere <= set_gameview && pass == 0) + || (allopt[i].setwhere >= set_in_game && pass == 1))) { + if (bool_p == &flags.female) + continue; /* obsolete */ + if (allopt[i].setwhere == set_wizonly && !wizard) continue; -#ifndef MAC_GRAPHICS_ENV - if (sl->handling == H_MAC) + if (allopt[i].setwhere == set_wiznofuz + && (!wizard || iflags.debug_fuzzer)) + continue; + if ((is_wc_option(name) && !wc_supported(name)) + || (is_wc2_option(name) && !wc2_supported(name))) continue; -#endif - setcount++; - /* find biggest name */ - thissize = sl->name ? (int) strlen(sl->name) : 0; - if (thissize > biggest) - biggest = thissize; - thissize = sl->desc ? (int) strlen(sl->desc) : 0; - if (thissize > big_desc) - big_desc = thissize; - } - if (!setcount) { - pline("There are no appropriate %s symbol sets available.", - rogueflag ? "rogue level" : "primary"); - return TRUE; + any.a_int = (pass == 0) ? 0 : i + 1 + indexoffset; + indent = (pass == 0 && !iflags.menu_tab_sep) ? " " : ""; + Sprintf(buf, fmtstr_doset, indent, + name, term_for_boolean(i, bool_p)); + if (pass == 0) + enhance_menu_text(buf, sizeof buf, pass, bool_p, + &allopt[i]); + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, + ATR_NONE, clr, buf, MENU_ITEMFLAGS_SKIPINVERT); } - Sprintf(fmtstr, "%%-%ds %%s", biggest + 2); - tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - any = zeroany; - any.a_int = 1; /* -1 + 2 [see 'if (sl->name) {' below]*/ - if (!symset_name) - defindx = any.a_int; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - "Default Symbols", - (any.a_int == defindx) ? MENU_SELECTED - : MENU_UNSELECTED); - - for (sl = symset_list; sl; sl = sl->next) { - /* check restrictions */ - if (rogueflag ? sl->primary : sl->rogue) - continue; -#ifndef MAC_GRAPHICS_ENV - if (sl->handling == H_MAC) + add_menu_str(tmpwin, ""); + add_menu_heading(tmpwin, + "Compounds (selecting will prompt for new value):"); + + for (pass = startpass; pass <= endpass; pass++) + for (i = 0; (name = allopt[i].name) != 0; i++) { + if (allopt[i].opttyp != CompOpt) + continue; + if ((int) allopt[i].setwhere == pass) { + if ((is_wc_option(name) && !wc_supported(name)) + || (is_wc2_option(name) && !wc2_supported(name))) continue; -#endif - if (sl->name) { - /* +2: sl->idx runs from 0 to N-1 for N symsets; - +1 because Defaults are implicitly in slot [0]; - +1 again so that valid data is never 0 */ - any.a_int = sl->idx + 2; - if (symset_name && !strcmpi(sl->name, symset_name)) - defindx = any.a_int; - Sprintf(buf, fmtstr, sl->name, sl->desc ? sl->desc : ""); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, - (any.a_int == defindx) ? MENU_SELECTED - : MENU_UNSELECTED); - } - } - Sprintf(buf, "Select %ssymbol set:", - rogueflag ? "rogue level " : ""); - end_menu(tmpwin, buf); - n = select_menu(tmpwin, PICK_ONE, &symset_pick); - if (n > 0) { - chosen = symset_pick[0].item.a_int; - /* if picking non-preselected entry yields 2, make sure - that we're going with the non-preselected one */ - if (n == 2 && chosen == defindx) - chosen = symset_pick[1].item.a_int; - chosen -= 2; /* convert menu index to symset index; - * "Default symbols" have index -1 */ - free((genericptr_t) symset_pick); - } else if (n == 0 && defindx > 0) { - chosen = defindx - 2; + + doset_add_menu(tmpwin, name, fmtstr_doset, i, + (pass == set_gameview) ? 0 : indexoffset); } - destroy_nhwindow(tmpwin); + } - if (chosen > -1) { - /* chose an actual symset name from file */ - for (sl = symset_list; sl; sl = sl->next) - if (sl->idx == chosen) - break; - if (sl) { - /* free the now stale attributes */ - clear_symsetentry(which_set, TRUE); + add_menu_str(tmpwin, ""); + add_menu_heading(tmpwin, "Other settings:"); - /* transfer only the name of the symbol set */ - symset[which_set].name = dupstr(sl->name); - ready_to_switch = TRUE; - } - } else if (chosen == -1) { - /* explicit selection of defaults */ - /* free the now stale symset attributes */ - clear_symsetentry(which_set, TRUE); - } else - nothing_to_do = TRUE; - } else if (!res) { - /* The symbols file could not be accessed */ - pline("Unable to access \"%s\" file.", SYMBOLS); - return TRUE; - } else if (!symset_list) { - /* The symbols file was empty */ - pline("There were no symbol sets found in \"%s\".", SYMBOLS); - return TRUE; - } + for (pass = startpass; pass <= endpass; pass++) + for (i = 0; (name = allopt[i].name) != 0; i++) { + if (allopt[i].opttyp != OthrOpt) + continue; + if ((int) allopt[i].setwhere == pass) { + if ((is_wc_option(name) && !wc_supported(name)) + || (is_wc2_option(name) && !wc2_supported(name))) + continue; - /* clean up */ - while ((sl = symset_list) != 0) { - symset_list = sl->next; - if (sl->name) - free((genericptr_t) sl->name), sl->name = (char *) 0; - if (sl->desc) - free((genericptr_t) sl->desc), sl->desc = (char *) 0; - free((genericptr_t) sl); + doset_add_menu(tmpwin, name, fmtstr_doset, i, + (pass == set_gameview) ? 0 : indexoffset); + } } - if (nothing_to_do) - return TRUE; - - /* Set default symbols and clear the handling value */ - if (rogueflag) - init_rogue_symbols(); - else - init_primary_symbols(); +#ifdef PREFIXES_IN_USE + add_menu_str(tmpwin, ""); + add_menu_heading(tmpwin, "Variable playground locations:"); + for (i = 0; i < PREFIX_COUNT; i++) + doset_add_menu(tmpwin, fqn_prefix_names[i], fmtstr_doset, -1, 0); +#endif + end_menu(tmpwin, "Set what options?"); + go.opt_need_redraw = FALSE; + go.opt_need_glyph_reset = FALSE; - if (symset[which_set].name) { - /* non-default symbols */ - if (read_sym_file(which_set)) { - ready_to_switch = TRUE; + if ((pick_cnt = select_menu(tmpwin, PICK_ANY, &pick_list)) > 0) { + /* + * Walk down the selection list and either invert the booleans + * or prompt for new values. In most cases, call parseoptions() + * to take care of options that require special attention, like + * redraws. + */ + for (pick_idx = 0; pick_idx < pick_cnt; ++pick_idx) { + opt_indx = pick_list[pick_idx].item.a_int - 1; + if (opt_indx == HELP_IDX) { + display_file(OPTMENUHELP, FALSE); + gavehelp = TRUE; + continue; /* just handled '?'; there might be more picks */ + } + if (opt_indx < -1) + opt_indx++; /* -1 offset for select_menu() */ + opt_indx -= indexoffset; + assert(IndexOk(opt_indx, allopt)); + if (allopt[opt_indx].opttyp == BoolOpt) { + /* boolean option */ + Sprintf(buf, "%s%s", *allopt[opt_indx].addr ? "!" : "", + allopt[opt_indx].name); + (void) parseoptions(buf, FALSE, FALSE); } else { - clear_symsetentry(which_set, TRUE); - return TRUE; + /* compound option */ + int k = opt_indx, reslt; + + if (allopt[k].has_handler && allopt[k].optfn) { + reslt = (*allopt[k].optfn)(allopt[k].idx, do_handler, + FALSE, empty_optstr, + empty_optstr); + /* if player eventually saves options, include this one */ + if (reslt == optn_ok) + opt_set_in_config[k] = TRUE; + } else { + char abuf[BUFSZ]; + + Sprintf(buf, "Set %s to what?", allopt[opt_indx].name); + abuf[0] = '\0'; + getlin(buf, abuf); + if (abuf[0] == '\033') + continue; + Sprintf(buf, "%s:", allopt[opt_indx].name); + (void) strncat(eos(buf), abuf, + (sizeof buf - 1 - strlen(buf))); + /* pass the buck */ + (void) parseoptions(buf, FALSE, FALSE); + } } + if (wc_supported(allopt[opt_indx].name) + || wc2_supported(allopt[opt_indx].name)) + preference_update(allopt[opt_indx].name); } + free((genericptr_t) pick_list), pick_list = (menu_item *) 0; + } - if (ready_to_switch) - switch_symbols(TRUE); + destroy_nhwindow(tmpwin); - if (Is_rogue_level(&u.uz)) { - if (rogueflag) - assign_graphics(ROGUESET); - } else if (!rogueflag) - assign_graphics(PRIMARY); - preference_update("symset"); - need_redraw = TRUE; -#ifdef WIN32 - } else if (!strcmp("altkeyhandler", optname) - || !strcmp("altkeyhandling", optname)) { - return set_keyhandling_via_option(); -#endif - } else { - /* didn't match any of the special options */ - return FALSE; + if (pick_cnt == 1 && gavehelp) { + /* when '?' is only the thing selected, go back and pick all + over again without it as an available choice second time */ + skiphelp = TRUE; + gavehelp = FALSE; /* currently True; reset for second pass */ + goto rerun; } - return TRUE; -} - -#define rolestring(val, array, field) \ - ((val >= 0) ? array[val].field : (val == ROLE_RANDOM) ? randomrole : none) -/* This is ugly. We have all the option names in the compopt[] array, - but we need to look at each option individually to get the value. */ -STATIC_OVL const char * -get_compopt_value(optname, buf) -const char *optname; -char *buf; -{ - static const char none[] = "(none)", randomrole[] = "random", - to_be_done[] = "(to be done)", - defopt[] = "default", defbrief[] = "def"; - char ocl[MAXOCLASSES + 1]; - int i; + reset_needed_visuals(); + return ECMD_OK; +} - buf[0] = '\0'; - if (!strcmp(optname, "align_message") - || !strcmp(optname, "align_status")) { - int which = !strcmp(optname, "align_status") ? iflags.wc_align_status - : iflags.wc_align_message; - Sprintf(buf, "%s", - (which == ALIGN_TOP) ? "top" - : (which == ALIGN_LEFT) ? "left" - : (which == ALIGN_BOTTOM) ? "bottom" - : (which == ALIGN_RIGHT) ? "right" - : defopt); - } else if (!strcmp(optname, "align")) - Sprintf(buf, "%s", rolestring(flags.initalign, aligns, adj)); -#ifdef WIN32 - else if (!strcmp(optname, "altkeyhandler")) - Sprintf(buf, "%s", - (iflags.key_handling == ray_keyhandling) - ? "ray" - : (iflags.key_handling == nh340_keyhandling) - ? "340" - : "default"); -#endif -#ifdef BACKWARD_COMPAT - else if (!strcmp(optname, "boulder")) - Sprintf(buf, "%c", - ov_primary_syms[SYM_BOULDER + SYM_OFF_X] - ? ov_primary_syms[SYM_BOULDER + SYM_OFF_X] - : showsyms[(int) objects[BOULDER].oc_class + SYM_OFF_O]); -#endif - else if (!strcmp(optname, "catname")) - Sprintf(buf, "%s", catname[0] ? catname : none); - else if (!strcmp(optname, "disclose")) - for (i = 0; i < NUM_DISCLOSURE_OPTIONS; i++) { - if (i) - (void) strkitten(buf, ' '); - (void) strkitten(buf, flags.end_disclose[i]); - (void) strkitten(buf, disclosure_options[i]); - } - else if (!strcmp(optname, "dogname")) - Sprintf(buf, "%s", dogname[0] ? dogname : none); - else if (!strcmp(optname, "dungeon")) - Sprintf(buf, "%s", to_be_done); - else if (!strcmp(optname, "effects")) - Sprintf(buf, "%s", to_be_done); - else if (!strcmp(optname, "font_map")) - Sprintf(buf, "%s", iflags.wc_font_map ? iflags.wc_font_map : defopt); - else if (!strcmp(optname, "font_message")) - Sprintf(buf, "%s", - iflags.wc_font_message ? iflags.wc_font_message : defopt); - else if (!strcmp(optname, "font_status")) - Sprintf(buf, "%s", - iflags.wc_font_status ? iflags.wc_font_status : defopt); - else if (!strcmp(optname, "font_menu")) - Sprintf(buf, "%s", - iflags.wc_font_menu ? iflags.wc_font_menu : defopt); - else if (!strcmp(optname, "font_text")) - Sprintf(buf, "%s", - iflags.wc_font_text ? iflags.wc_font_text : defopt); - else if (!strcmp(optname, "font_size_map")) { - if (iflags.wc_fontsiz_map) - Sprintf(buf, "%d", iflags.wc_fontsiz_map); - else - Strcpy(buf, defopt); - } else if (!strcmp(optname, "font_size_message")) { - if (iflags.wc_fontsiz_message) - Sprintf(buf, "%d", iflags.wc_fontsiz_message); - else - Strcpy(buf, defopt); - } else if (!strcmp(optname, "font_size_status")) { - if (iflags.wc_fontsiz_status) - Sprintf(buf, "%d", iflags.wc_fontsiz_status); - else - Strcpy(buf, defopt); - } else if (!strcmp(optname, "font_size_menu")) { - if (iflags.wc_fontsiz_menu) - Sprintf(buf, "%d", iflags.wc_fontsiz_menu); - else - Strcpy(buf, defopt); - } else if (!strcmp(optname, "font_size_text")) { - if (iflags.wc_fontsiz_text) - Sprintf(buf, "%d", iflags.wc_fontsiz_text); - else - Strcpy(buf, defopt); - } else if (!strcmp(optname, "fruit")) - Sprintf(buf, "%s", pl_fruit); - else if (!strcmp(optname, "gender")) - Sprintf(buf, "%s", rolestring(flags.initgend, genders, adj)); - else if (!strcmp(optname, "horsename")) - Sprintf(buf, "%s", horsename[0] ? horsename : none); - else if (!strcmp(optname, "map_mode")) { - i = iflags.wc_map_mode; - Sprintf(buf, "%s", - (i == MAP_MODE_TILES) ? "tiles" - : (i == MAP_MODE_ASCII4x6) ? "ascii4x6" - : (i == MAP_MODE_ASCII6x8) ? "ascii6x8" - : (i == MAP_MODE_ASCII8x8) ? "ascii8x8" - : (i == MAP_MODE_ASCII16x8) ? "ascii16x8" - : (i == MAP_MODE_ASCII7x12) ? "ascii7x12" - : (i == MAP_MODE_ASCII8x12) ? "ascii8x12" - : (i == MAP_MODE_ASCII16x12) ? "ascii16x12" - : (i == MAP_MODE_ASCII12x16) ? "ascii12x16" - : (i == MAP_MODE_ASCII10x18) ? "ascii10x18" - : (i == MAP_MODE_ASCII_FIT_TO_SCREEN) - ? "fit_to_screen" - : defopt); - } else if (!strcmp(optname, "menustyle")) - Sprintf(buf, "%s", menutype[(int) flags.menu_style]); - else if (!strcmp(optname, "menu_deselect_all")) - Sprintf(buf, "%s", to_be_done); - else if (!strcmp(optname, "menu_deselect_page")) - Sprintf(buf, "%s", to_be_done); - else if (!strcmp(optname, "menu_first_page")) - Sprintf(buf, "%s", to_be_done); - else if (!strcmp(optname, "menu_invert_all")) - Sprintf(buf, "%s", to_be_done); - else if (!strcmp(optname, "menu_headings")) - Sprintf(buf, "%s", attr2attrname(iflags.menu_headings)); - else if (!strcmp(optname, "menu_invert_page")) - Sprintf(buf, "%s", to_be_done); - else if (!strcmp(optname, "menu_last_page")) - Sprintf(buf, "%s", to_be_done); - else if (!strcmp(optname, "menu_next_page")) - Sprintf(buf, "%s", to_be_done); - else if (!strcmp(optname, "menu_previous_page")) - Sprintf(buf, "%s", to_be_done); - else if (!strcmp(optname, "menu_search")) - Sprintf(buf, "%s", to_be_done); - else if (!strcmp(optname, "menu_select_all")) - Sprintf(buf, "%s", to_be_done); - else if (!strcmp(optname, "menu_select_page")) - Sprintf(buf, "%s", to_be_done); - else if (!strcmp(optname, "monsters")) { - Sprintf(buf, "%s", to_be_done); - } else if (!strcmp(optname, "msghistory")) { - Sprintf(buf, "%u", iflags.msg_history); -#ifdef TTY_GRAPHICS - } else if (!strcmp(optname, "msg_window")) { - Sprintf(buf, "%s", (iflags.prevmsg_window == 's') ? "single" - : (iflags.prevmsg_window == 'c') ? "combination" - : (iflags.prevmsg_window == 'f') ? "full" - : "reversed"); -#endif - } else if (!strcmp(optname, "name")) { - Sprintf(buf, "%s", plname); - } else if (!strcmp(optname, "mouse_support")) { -#ifdef WIN32 -#define MOUSEFIX1 ", QuickEdit off" -#define MOUSEFIX2 ", QuickEdit unchanged" -#else -#define MOUSEFIX1 ", O/S adjusted" -#define MOUSEFIX2 ", O/S unchanged" -#endif - static const char *mousemodes[][2] = { - { "0=off", "" }, - { "1=on", MOUSEFIX1 }, - { "2=on", MOUSEFIX2 }, - }; -#undef MOUSEFIX1 -#undef MOUSEFIX2 - int ms = iflags.wc_mouse_support; +#undef HELP_IDX - if (ms >= 0 && ms <= 2) - Sprintf(buf, "%s%s", mousemodes[ms][0], mousemodes[ms][1]); - } else if (!strcmp(optname, "number_pad")) { - static const char *numpadmodes[] = { - "0=off", "1=on", "2=on, MSDOS compatible", - "3=on, phone-style layout", - "4=on, phone layout, MSDOS compatible", - "-1=off, y & z swapped", /*[5]*/ - }; - int indx = Cmd.num_pad - ? (Cmd.phone_layout ? (Cmd.pcHack_compat ? 4 : 3) - : (Cmd.pcHack_compat ? 2 : 1)) - : Cmd.swap_yz ? 5 : 0; - - Strcpy(buf, numpadmodes[indx]); - } else if (!strcmp(optname, "objects")) { - Sprintf(buf, "%s", to_be_done); - } else if (!strcmp(optname, "packorder")) { - oc_to_str(flags.inv_order, ocl); - Sprintf(buf, "%s", ocl); +staticfn void +reset_needed_visuals(void) +{ + if (go.opt_need_glyph_reset) { + reset_glyphmap(gm_optionchange); + } + if (go.opt_reset_customcolors || go.opt_update_basic_palette + || go.opt_reset_customsymbols || go.opt_need_redraw) { + if (go.opt_update_basic_palette) { #ifdef CHANGE_COLOR - } else if (!strcmp(optname, "palette")) { - Sprintf(buf, "%s", get_color_string()); + change_palette(); #endif - } else if (!strcmp(optname, "paranoid_confirmation")) { - char tmpbuf[QBUFSZ]; - - tmpbuf[0] = '\0'; - for (i = 0; paranoia[i].flagmask != 0; ++i) - if (flags.paranoia_bits & paranoia[i].flagmask) - Sprintf(eos(tmpbuf), " %s", paranoia[i].argname); - Strcpy(buf, tmpbuf[0] ? &tmpbuf[1] : "none"); - } else if (!strcmp(optname, "petattr")) { -#ifdef CURSES_GRAPHICS - if (WINDOWPORT("curses")) { - char tmpbuf[QBUFSZ]; + go.opt_update_basic_palette = FALSE; + } + if (go.opt_reset_customcolors) + reset_customcolors(); + if (go.opt_reset_customsymbols) + reset_customsymbols(); + if (go.opt_need_redraw) { + check_gold_symbol(); + reglyph_darkroom(); + } + docrt(); + } + if (go.opt_need_promptstyle) { + adjust_menu_promptstyle(WIN_INVEN, &iflags.menu_headings); + } + if (disp.botl || disp.botlx) { + bot(); + } + go.opt_need_redraw = FALSE; + go.opt_need_glyph_reset = FALSE; + go.opt_reset_customcolors = FALSE; + go.opt_reset_customsymbols = FALSE; + go.opt_update_basic_palette = FALSE; +} - Strcpy(buf, curses_fmt_attrs(tmpbuf)); - } else -#endif - if (iflags.wc2_petattr != 0) - Sprintf(buf, "0x%08x", iflags.wc2_petattr); - else - Strcpy(buf, defopt); - } else if (!strcmp(optname, "pettype")) { - Sprintf(buf, "%s", (preferred_pet == 'c') ? "cat" - : (preferred_pet == 'd') ? "dog" - : (preferred_pet == 'h') ? "horse" - : (preferred_pet == 'n') ? "none" - : "random"); - } else if (!strcmp(optname, "pickup_burden")) { - Sprintf(buf, "%s", burdentype[flags.pickup_burden]); - } else if (!strcmp(optname, "pickup_types")) { - oc_to_str(flags.pickup_types, ocl); - Sprintf(buf, "%s", ocl[0] ? ocl : "all"); - } else if (!strcmp(optname, "pile_limit")) { - Sprintf(buf, "%d", flags.pile_limit); - } else if (!strcmp(optname, "playmode")) { - Strcpy(buf, wizard ? "debug" : discover ? "explore" : "normal"); - } else if (!strcmp(optname, "race")) { - Sprintf(buf, "%s", rolestring(flags.initrace, races, noun)); - } else if (!strcmp(optname, "roguesymset")) { - Sprintf(buf, "%s", - symset[ROGUESET].name ? symset[ROGUESET].name : "default"); - if (currentgraphics == ROGUESET && symset[ROGUESET].name) - Strcat(buf, ", active"); - } else if (!strcmp(optname, "role")) { - Sprintf(buf, "%s", rolestring(flags.initrole, roles, name.m)); - } else if (!strcmp(optname, "runmode")) { - Sprintf(buf, "%s", runmodes[flags.runmode]); - } else if (!strcmp(optname, "whatis_coord")) { - Sprintf(buf, "%s", - (iflags.getpos_coords == GPCOORDS_MAP) ? "map" - : (iflags.getpos_coords == GPCOORDS_COMPASS) ? "compass" - : (iflags.getpos_coords == GPCOORDS_COMFULL) ? "full compass" - : (iflags.getpos_coords == GPCOORDS_SCREEN) ? "screen" - : "none"); - } else if (!strcmp(optname, "whatis_filter")) { - Sprintf(buf, "%s", - (iflags.getloc_filter == GFILTER_VIEW) ? "view" - : (iflags.getloc_filter == GFILTER_AREA) ? "area" - : "none"); - } else if (!strcmp(optname, "scores")) { - Sprintf(buf, "%d top/%d around%s", flags.end_top, flags.end_around, - flags.end_own ? "/own" : ""); - } else if (!strcmp(optname, "scroll_amount")) { - if (iflags.wc_scroll_amount) - Sprintf(buf, "%d", iflags.wc_scroll_amount); - else - Strcpy(buf, defopt); - } else if (!strcmp(optname, "scroll_margin")) { - if (iflags.wc_scroll_margin) - Sprintf(buf, "%d", iflags.wc_scroll_margin); - else - Strcpy(buf, defopt); - } else if (!strcmp(optname, "sortloot")) { - for (i = 0; i < SIZE(sortltype); i++) - if (flags.sortloot == sortltype[i][0]) { - Strcpy(buf, sortltype[i]); - break; - } - } else if (!strcmp(optname, "player_selection")) { - Sprintf(buf, "%s", iflags.wc_player_selection ? "prompts" : "dialog"); -#ifdef MSDOS - } else if (!strcmp(optname, "soundcard")) { - Sprintf(buf, "%s", to_be_done); -#endif -#ifdef STATUS_HILITES - } else if (!strcmp("statushilites", optname)) { - if (!iflags.hilite_delta) - Strcpy(buf, "0 (off: don't highlight status fields)"); - else - Sprintf(buf, "%ld (on: highlight status for %ld turns)", - iflags.hilite_delta, iflags.hilite_delta); -#endif - } else if (!strcmp(optname,"statuslines")) { - if (wc2_supported(optname)) - Strcpy(buf, (iflags.wc2_statuslines < 3) ? "2" : "3"); - /* else default to "unknown" */ - } else if (!strcmp(optname, "suppress_alert")) { - if (flags.suppress_alert == 0L) - Strcpy(buf, none); - else - Sprintf(buf, "%lu.%lu.%lu", FEATURE_NOTICE_VER_MAJ, - FEATURE_NOTICE_VER_MIN, FEATURE_NOTICE_VER_PATCH); - } else if (!strcmp(optname, "symset")) { - Sprintf(buf, "%s", - symset[PRIMARY].name ? symset[PRIMARY].name : "default"); - if (currentgraphics == PRIMARY && symset[PRIMARY].name) - Strcat(buf, ", active"); - } else if (!strcmp(optname, "term_cols")) { - if (iflags.wc2_term_cols) - Sprintf(buf, "%d", iflags.wc2_term_cols); - else - Strcpy(buf, defopt); - } else if (!strcmp(optname, "term_rows")) { - if (iflags.wc2_term_rows) - Sprintf(buf, "%d", iflags.wc2_term_rows); - else - Strcpy(buf, defopt); - } else if (!strcmp(optname, "tile_file")) { - Sprintf(buf, "%s", - iflags.wc_tile_file ? iflags.wc_tile_file : defopt); - } else if (!strcmp(optname, "tile_height")) { - if (iflags.wc_tile_height) - Sprintf(buf, "%d", iflags.wc_tile_height); - else - Strcpy(buf, defopt); - } else if (!strcmp(optname, "tile_width")) { - if (iflags.wc_tile_width) - Sprintf(buf, "%d", iflags.wc_tile_width); - else - Strcpy(buf, defopt); - } else if (!strcmp(optname, "traps")) { - Sprintf(buf, "%s", to_be_done); - } else if (!strcmp(optname, "vary_msgcount")) { - if (iflags.wc_vary_msgcount) - Sprintf(buf, "%d", iflags.wc_vary_msgcount); - else - Strcpy(buf, defopt); -#ifdef MSDOS - } else if (!strcmp(optname, "video")) { - Sprintf(buf, "%s", to_be_done); -#endif -#ifdef VIDEOSHADES - } else if (!strcmp(optname, "videoshades")) { - Sprintf(buf, "%s-%s-%s", shade[0], shade[1], shade[2]); - } else if (!strcmp(optname, "videocolors")) { - Sprintf(buf, "%d-%d-%d-%d-%d-%d-%d-%d-%d-%d-%d-%d", - ttycolors[CLR_RED], ttycolors[CLR_GREEN], - ttycolors[CLR_BROWN], ttycolors[CLR_BLUE], - ttycolors[CLR_MAGENTA], ttycolors[CLR_CYAN], - ttycolors[CLR_ORANGE], ttycolors[CLR_BRIGHT_GREEN], - ttycolors[CLR_YELLOW], ttycolors[CLR_BRIGHT_BLUE], - ttycolors[CLR_BRIGHT_MAGENTA], ttycolors[CLR_BRIGHT_CYAN]); -#endif /* VIDEOSHADES */ - } else if (!strcmp(optname,"windowborders")) { - Sprintf(buf, "%s", - (iflags.wc2_windowborders == 0) ? "0=off" - : (iflags.wc2_windowborders == 1) ? "1=on" - : (iflags.wc2_windowborders == 2) ? "2=auto" - : defopt); - } else if (!strcmp(optname, "windowtype")) { - Sprintf(buf, "%s", windowprocs.name); - } else if (!strcmp(optname, "windowcolors")) { - Sprintf( - buf, "%s/%s %s/%s %s/%s %s/%s", - iflags.wc_foregrnd_menu ? iflags.wc_foregrnd_menu : defbrief, - iflags.wc_backgrnd_menu ? iflags.wc_backgrnd_menu : defbrief, - iflags.wc_foregrnd_message ? iflags.wc_foregrnd_message - : defbrief, - iflags.wc_backgrnd_message ? iflags.wc_backgrnd_message - : defbrief, - iflags.wc_foregrnd_status ? iflags.wc_foregrnd_status : defbrief, - iflags.wc_backgrnd_status ? iflags.wc_backgrnd_status : defbrief, - iflags.wc_foregrnd_text ? iflags.wc_foregrnd_text : defbrief, - iflags.wc_backgrnd_text ? iflags.wc_backgrnd_text : defbrief); +/* doset(#optionsfull command) menu entries for compound options */ +staticfn void +doset_add_menu( + winid win, /* window to add to */ + const char *option, /* option name */ + const char *fmtstr, /* fmtstr_doset */ + int idx, /* index in allopt[] */ + int indexoffset) /* value to add to index in allopt[], + * or zero if option cannot be changed */ +{ + const char *value = "unknown"; /* current value */ + const char *indent; + char buf[BUFSZ], buf2[BUFSZ]; + anything any; + int i = idx, reslt = optn_err; #ifdef PREFIXES_IN_USE + int j; +#endif + int clr = NO_COLOR; + + buf2[0] = '\0'; /* per opt functs may not guarantee this, so do it */ + any = cg.zeroany; + if (i >= 0 && i < OPTCOUNT && allopt[i].name && allopt[i].optfn) { + any.a_int = (indexoffset == 0) ? 0 : i + 1 + indexoffset; + if (allopt[i].optfn) + reslt = (*allopt[i].optfn)(allopt[i].idx, get_val, + FALSE, buf2, empty_optstr); + if (reslt == optn_ok && buf2[0]) + value = (const char *) buf2; } else { - for (i = 0; i < PREFIX_COUNT; ++i) - if (!strcmp(optname, fqn_prefix_names[i]) && fqn_prefix[i]) - Sprintf(buf, "%s", fqn_prefix[i]); + /* We are trying to add an option not found in allopt[]. + This is almost certainly bad, but we'll let it through anyway + (with a zero value, so it can't be selected). */ + any.a_int = 0; +#ifdef PREFIXES_IN_USE + for (j = 0; j < PREFIX_COUNT; ++j) + if (!strcmp(option, fqn_prefix_names[j]) && gf.fqn_prefix[j]) + Sprintf(buf2, "%s", gf.fqn_prefix[j]); #endif + if (!buf2[0]) + Strcpy(buf2, "unknown"); + value = (const char *) buf2; + } + + /* " " replaces "a - " -- assumes menus follow that style */ + indent = !any.a_int ? " " : ""; + Sprintf(buf, fmtstr, indent, option, value); + add_menu(win, &nul_glyphinfo, &any, 0, 0, + ATR_NONE, clr, buf, MENU_ITEMFLAGS_SKIPINVERT); +} + + +/* display keys for menu actions; used by cmd.c '?i' and pager.c '?k' */ +void +show_menu_controls(winid win, boolean dolist) +{ + struct xtra_cntrls { + const char *key, *desc; + }; + static const struct xtra_cntrls hardcoded[] = { + { "Return", "Accept current choice(s) and dismiss menu" }, + { "Enter", "Same as Return" }, + { "Space", "If not on last page, advance one page;" }, + { " ", "when on last page, treat like Return" }, + { "Escape", "Cancel menu without making any choice(s)" }, + { (char *) 0, (char *) 0} + }; + static const char mc_fmt[] = "%8s %-6s %s", + mc_altfmt[] = "%9s %-6s %s"; + char buf[BUFSZ]; + const char *fmt, *arg; + const struct xtra_cntrls *xcp; + boolean has_menu_shift = wc2_supported("menu_shift"); + + /* + * Relies on spaces to line things up in columns, so must be rendered + * with a fixed-width font or will look dreadful. + */ + + putstr(win, 0, "Menu control keys:"); + if (dolist) { /* key bindings help: '?i' */ + int i; + char ch; + + fmt = "%-7s %s"; + for (i = 0; default_menu_cmd_info[i].desc; i++) { + ch = default_menu_cmd_info[i].cmd; + if ((ch == MENU_SHIFT_RIGHT + || ch == MENU_SHIFT_LEFT) && !has_menu_shift) + continue; + Sprintf(buf, fmt, + visctrl(get_menu_cmd_key(ch)), + default_menu_cmd_info[i].desc); + putstr(win, 0, buf); + } + /* no separator before hardcoded */ + fmt = "%s%-7s %s"; /* extra specifier to absorb 'arg' */ + arg = ""; /* no extra prefix for 'dolist' */ + } else { /* menu controls help: '?k' */ + putstr(win, 0, ""); + Sprintf(buf, mc_altfmt, "", "Whole", "Current"); + putstr(win, 0, buf); + Sprintf(buf, mc_altfmt, "", " Menu", " Page"); + putstr(win, 0, buf); + Sprintf(buf, mc_fmt, "Select", + visctrl(get_menu_cmd_key(MENU_SELECT_ALL)), + visctrl(get_menu_cmd_key(MENU_SELECT_PAGE))); + putstr(win, 0, buf); + Sprintf(buf, mc_fmt, "Invert", + visctrl(get_menu_cmd_key(MENU_INVERT_ALL)), + visctrl(get_menu_cmd_key(MENU_INVERT_PAGE))); + putstr(win, 0, buf); + Sprintf(buf, mc_fmt, "Deselect", + visctrl(get_menu_cmd_key(MENU_UNSELECT_ALL)), + visctrl(get_menu_cmd_key(MENU_UNSELECT_PAGE))); + putstr(win, 0, buf); + putstr(win, 0, ""); + Sprintf(buf, mc_fmt, "Go to", + visctrl(get_menu_cmd_key(MENU_NEXT_PAGE)), + "Next page"); + putstr(win, 0, buf); + Sprintf(buf, mc_fmt, "", + visctrl(get_menu_cmd_key(MENU_PREVIOUS_PAGE)), + "Previous page"); + putstr(win, 0, buf); + Sprintf(buf, mc_fmt, "", + visctrl(get_menu_cmd_key(MENU_FIRST_PAGE)), + "First page"); + putstr(win, 0, buf); + Sprintf(buf, mc_fmt, "", + visctrl(get_menu_cmd_key(MENU_LAST_PAGE)), + "Last page"); + putstr(win, 0, buf); + if (has_menu_shift) { + Sprintf(buf, mc_fmt, "Pan view", + visctrl(get_menu_cmd_key(MENU_SHIFT_RIGHT)), + "Right (perm_invent only)"); + putstr(win, 0, buf); + Sprintf(buf, mc_fmt, "", + visctrl(get_menu_cmd_key(MENU_SHIFT_LEFT)), + "Left"); + putstr(win, 0, buf); + } + putstr(win, 0, ""); + Sprintf(buf, mc_fmt, "Search", + visctrl(get_menu_cmd_key(MENU_SEARCH)), + "Exter a target string and invert all matching entries"); + putstr(win, 0, buf); + /* separator before hardcoded */ + putstr(win, 0, ""); + fmt = "%9s %-8s %s"; + arg = "Other "; /* prefix for first hardcoded[] entry, then reset */ + } + for (xcp = hardcoded; xcp->key; ++xcp) { + Sprintf(buf, fmt, arg, xcp->key, xcp->desc); + putstr(win, 0, buf); + arg = ""; + } +} + +RESTORE_WARNING_FORMAT_NONLITERAL + +staticfn int +count_cond(void) +{ + int i, cnt = 0; + + for (i = 0; i < CONDITION_COUNT; ++i) { + if (condtests[i].enabled) + cnt++; } + return cnt; +} + +staticfn int +count_apes(void) +{ + int numapes = 0; + struct autopickup_exception *ape = ga.apelist; + + while (ape) { + numapes++; + ape = ape->next; + } + + return numapes; +} + +DISABLE_WARNING_FORMAT_NONLITERAL + +/* common to msg-types, menu-colors, autopickup-exceptions */ +staticfn int +handle_add_list_remove(const char *optname, int numtotal) +{ + winid tmpwin; + anything any; + int i, pick_cnt, opt_idx; + menu_item *pick_list = (menu_item *) 0; + static const struct action { + char letr; + const char *desc; + } action_titles[] = { + { 'a', "add new %s" }, /* [0] */ + { 'l', "list %s" }, /* [1] */ + { 'r', "remove existing %s" }, /* [2] */ + { 'x', "exit this menu" }, /* [3] */ + }; + int clr = NO_COLOR; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + for (i = 0; i < SIZE(action_titles); i++) { + char tmpbuf[BUFSZ]; - if (!buf[0]) - Strcpy(buf, "unknown"); - return buf; + any.a_int++; + /* omit list and remove if there aren't any yet */ + if (!numtotal && (i == 1 || i == 2)) + continue; + Sprintf(tmpbuf, action_titles[i].desc, + (i == 1) ? makeplural(optname) : optname); + add_menu(tmpwin, &nul_glyphinfo,&any, action_titles[i].letr, + 0, ATR_NONE, clr, tmpbuf, + (i == 3) ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); + } + end_menu(tmpwin, "Do what?"); + if ((pick_cnt = select_menu(tmpwin, PICK_ONE, &pick_list)) > 0) { + opt_idx = pick_list[0].item.a_int - 1; + if (pick_cnt > 1 && opt_idx == 3) + opt_idx = pick_list[1].item.a_int - 1; + free((genericptr_t) pick_list); + } else + opt_idx = 3; /* none selected, exit menu */ + destroy_nhwindow(tmpwin); + return opt_idx; } +RESTORE_WARNING_FORMAT_NONLITERAL + int -dotogglepickup() +dotogglepickup(void) { char buf[BUFSZ], ocl[MAXOCLASSES + 1]; @@ -5993,7 +9270,7 @@ dotogglepickup() if (flags.pickup) { oc_to_str(flags.pickup_types, ocl); Sprintf(buf, "ON, for %s objects%s", ocl[0] ? ocl : "all", - (apelist) + (ga.apelist) ? ((count_apes() == 1) ? ", with one exception" : ", with some exceptions") @@ -6002,12 +9279,34 @@ dotogglepickup() Strcpy(buf, "OFF"); } pline("Autopickup: %s.", buf); - return 0; + return ECMD_OK; +} + +/* toggle any (settable in-game) boolean option by name */ +int +toggle_bool_option(const char *p) +{ + int i; + int ret = ECMD_FAIL; + + for (i = 0; i < OPTCOUNT; i++) + if (!strncmpi(allopt[i].name, p, strlen(p)) + && allopt[i].opttyp == BoolOpt + && allopt[i].setwhere == set_in_game + && allopt[i].addr != 0) { + char buf[BUFSZ]; + + Sprintf(buf, "%s%s", *allopt[i].addr ? "!" : "", allopt[i].name); + if (parseoptions(buf, FALSE, FALSE)) + ret = ECMD_OK; + + reset_needed_visuals(); + } + return ret; } int -add_autopickup_exception(mapping) -const char *mapping; +add_autopickup_exception(const char *mapping) { static const char APE_regex_error[] = "regex error in AUTOPICKUP_EXCEPTION", @@ -6039,34 +9338,35 @@ const char *mapping; ape = (struct autopickup_exception *) alloc(sizeof *ape); ape->regex = regex_init(); if (!regex_compile(text, ape->regex)) { - config_error_add("%s: %s", APE_regex_error, - regex_error_desc(ape->regex)); + char errbuf[BUFSZ]; + char *re_error_desc = regex_error_desc(ape->regex, errbuf); + + /* free first in case reason for failure was insufficient memory */ regex_free(ape->regex); free((genericptr_t) ape); + config_error_add("%s: %s", APE_regex_error, re_error_desc); return 0; } - ape->pattern = dupstr(text); ape->grab = grab; - ape->next = apelist; - apelist = ape; + ape->next = ga.apelist; + ga.apelist = ape; return 1; } -STATIC_OVL void -remove_autopickup_exception(whichape) -struct autopickup_exception *whichape; +staticfn void +remove_autopickup_exception(struct autopickup_exception *whichape) { struct autopickup_exception *ape, *freeape, *prev = 0; - for (ape = apelist; ape;) { + for (ape = ga.apelist; ape;) { if (ape == whichape) { freeape = ape; ape = ape->next; if (prev) prev->next = ape; else - apelist = ape; + ga.apelist = ape; regex_free(freeape->regex); free((genericptr_t) freeape->pattern); free((genericptr_t) freeape); @@ -6078,123 +9378,23 @@ struct autopickup_exception *whichape; } void -free_autopickup_exceptions() +free_autopickup_exceptions(void) { - struct autopickup_exception *ape = apelist; - - while ((ape = apelist) != 0) { - regex_free(ape->regex); - free((genericptr_t) ape->pattern); - apelist = ape->next; - free((genericptr_t) ape); - } -} - -/* bundle some common usage into one easy-to-use routine */ -int -load_symset(s, which_set) -const char *s; -int which_set; -{ - clear_symsetentry(which_set, TRUE); - - if (symset[which_set].name) - free((genericptr_t) symset[which_set].name); - symset[which_set].name = dupstr(s); - - if (read_sym_file(which_set)) { - switch_symbols(TRUE); - } else { - clear_symsetentry(which_set, TRUE); - return 0; - } - return 1; -} - -void -free_symsets() -{ - clear_symsetentry(PRIMARY, TRUE); - clear_symsetentry(ROGUESET, TRUE); - - /* symset_list is cleaned up as soon as it's used, so we shouldn't - have to anything about it here */ - /* assert( symset_list == NULL ); */ -} - -/* Parse the value of a SYMBOLS line from a config file */ -boolean -parsesymbols(opts, which_set) -register char *opts; -int which_set; -{ - int val; - char *op, *symname, *strval; - struct symparse *symp; - - if ((op = index(opts, ',')) != 0) { - *op++ = 0; - if (!parsesymbols(op, which_set)) - return FALSE; - } - - /* S_sample:string */ - symname = opts; - strval = index(opts, ':'); - if (!strval) - strval = index(opts, '='); - if (!strval) - return FALSE; - *strval++ = '\0'; - - /* strip leading and trailing white space from symname and strval */ - mungspaces(symname); - mungspaces(strval); - - symp = match_sym(symname); - if (!symp) - return FALSE; - - if (symp->range && symp->range != SYM_CONTROL) { - val = sym_val(strval); - if (which_set == ROGUESET) - update_ov_rogue_symset(symp, val); - else - update_ov_primary_symset(symp, val); - } - return TRUE; -} - -struct symparse * -match_sym(buf) -char *buf; -{ - size_t len = strlen(buf); - const char *p = index(buf, ':'), *q = index(buf, '='); - struct symparse *sp = loadsyms; + struct autopickup_exception *ape; - if (!p || (q && q < p)) - p = q; - if (p) { - /* note: there will be at most one space before the '=' - because caller has condensed buf[] with mungspaces() */ - if (p > buf && p[-1] == ' ') - p--; - len = (int) (p - buf); - } - while (sp->range) { - if ((len >= strlen(sp->name)) && !strncmpi(buf, sp->name, len)) - return sp; - sp++; + while ((ape = ga.apelist) != 0) { + free((genericptr_t) ape->pattern); + regex_free(ape->regex); + ga.apelist = ape->next; + free((genericptr_t) ape); } - return (struct symparse *) 0; } int -sym_val(strval) -const char *strval; /* up to 4*BUFSZ-1 long; only first few chars matter */ +sym_val(const char *strval) /* up to 4*BUFSZ-1 long; only first few + chars matter */ { - char buf[QBUFSZ], tmp[QBUFSZ]; /* to hold trucated copy of 'strval' */ + char buf[QBUFSZ], tmp[QBUFSZ]; /* to hold truncated copy of 'strval' */ buf[0] = '\0'; if (!strval[0] || !strval[1]) { /* empty, or single character */ @@ -6209,7 +9409,7 @@ const char *strval; /* up to 4*BUFSZ-1 long; only first few chars matter */ /* if backslash, handle single or double quote or second backslash */ } else if (strval[1] == '\\' && strval[2] && strval[3] == '\'' - && index("'\"\\", strval[2]) && !strval[4]) { + && strchr("'\"\\", strval[2]) && !strval[4]) { buf[0] = strval[2]; /* not simple quote or basic backslash; @@ -6220,7 +9420,7 @@ const char *strval; /* up to 4*BUFSZ-1 long; only first few chars matter */ /* +1: skip opening single quote */ (void) strncpy(tmp, strval + 1, sizeof tmp - 1); tmp[sizeof tmp - 1] = '\0'; - if ((p = rindex(tmp, '\'')) != 0) { + if ((p = strrchr(tmp, '\'')) != 0) { *p = '\0'; escapes(tmp, buf); } /* else buf[0] stays '\0' */ @@ -6240,7 +9440,7 @@ static const char *opt_intro[] = { " NetHack Options Help:", "", #define CONFIG_SLOT 3 /* fill in next value at run-time */ (char *) 0, -#if !defined(MICRO) && !defined(MAC) +#if !defined(MICRO) && !defined(MACOS9) "or use `NETHACKOPTIONS=\"\"' in your environment", #endif "( is a list of options separated by commas)", @@ -6249,57 +9449,311 @@ static const char *opt_intro[] = { #endif "or press \"O\" while playing and use the menu.", "", - "Boolean options (which can be negated by prefixing them with '!' or \"no\"):", + ("Boolean options (which can be negated by prefixing them" + " with '!' or \"no\"):"), + (char *) 0 +}; + +static const char *const opt_epilog[] = { + "", + "Some of the options can only be set before the game is started;", + "those items will not be selectable in the 'O' command's menu.", + "Some options are stored in a game's save file, and will keep saved", + "values when restoring that game even if you have updated your config-", + "uration file to change them. Such changes will matter for new games.", + "The \"other settings\" can be set with 'O', but when set within the", + "configuration file they use their own directives rather than OPTIONS.", + "See NetHack's \"Guidebook\" for details.", (char *) 0 }; -static const char *opt_epilog[] = { - "", - "Some of the options can be set only before the game is started; those", - "items will not be selectable in the 'O' command's menu.", - (char *) 0 -}; +void +option_help(void) +{ + char buf[BUFSZ], buf2[BUFSZ]; + const char *optname; + int i; + winid datawin; + + datawin = create_nhwindow(NHW_TEXT); + Snprintf(buf, sizeof buf, + "Set options as OPTIONS= in %s", get_configfile()); + opt_intro[CONFIG_SLOT] = (const char *) buf; + for (i = 0; opt_intro[i]; i++) + putstr(datawin, 0, opt_intro[i]); + + /* Boolean options */ + for (i = 0; allopt[i].name; i++) { + if ((allopt[i].opttyp != BoolOpt || !allopt[i].addr) + || (allopt[i].setwhere == set_wizonly && !wizard)) + continue; + if (allopt[i].setwhere == set_wiznofuz + && (!wizard || iflags.debug_fuzzer)) + continue; + optname = allopt[i].name; + if ((is_wc_option(optname) && !wc_supported(optname)) + || (is_wc2_option(optname) && !wc2_supported(optname))) + continue; + next_opt(datawin, optname); + } + next_opt(datawin, ""); + + /* Compound options */ + putstr(datawin, 0, "Compound options:"); + for (i = 0; allopt[i].name; i++) { + if (allopt[i].opttyp != CompOpt + || (allopt[i].setwhere == set_wizonly && !wizard)) + continue; + if (allopt[i].setwhere == set_wiznofuz + && (!wizard || iflags.debug_fuzzer)) + continue; + optname = allopt[i].name; + if ((is_wc_option(optname) && !wc_supported(optname)) + || (is_wc2_option(optname) && !wc2_supported(optname))) + continue; + Sprintf(buf2, "`%s'", optname); + Snprintf(buf, sizeof(buf), "%-20s - %s%c", buf2, allopt[i].descr, + allopt[i + 1].name ? ',' : '.'); + putstr(datawin, 0, buf); + } + putstr(datawin, 0, ""); + + /* Compound options */ + putstr(datawin, 0, "Other settings:"); + for (i = 0; allopt[i].name; i++) { + if (allopt[i].opttyp != OthrOpt) + continue; + Sprintf(buf, " %s", allopt[i].name); + putstr(datawin, 0, buf); + } + + putstr(datawin, 0, ""); + + for (i = 0; opt_epilog[i]; i++) + putstr(datawin, 0, opt_epilog[i]); + + /* + * TODO: + * briefly describe interface-specific option-like settings for + * the currently active interface: + * X11 uses X-specific "application defaults" from NetHack.ad; + * Qt has menu accessible "game -> Qt settings" (non-OSX) or + * "nethack -> Preferences" (OSX) to maintain a few options + * (font size, map tile size, paperdoll show/hide flag and + * tile size) which persist across games; + * Windows GUI also has some port-specific menus; + * tty and curses: anything? + * Best done via a new windowprocs function rather than plugging + * in details here. + * + * Maybe: + * switch from text window to pick-none menu so that user can + * scroll back up. (Not necessary for Qt where text windows are + * already scrollable.) + */ + + display_nhwindow(datawin, FALSE); + destroy_nhwindow(datawin); + return; +} + +/* gather all non-default cond_xyz options into one OPTIONS=cond_foo,!cond_bar + entry spread across multiple lines with backslash+newline if needed; + conditions with their default settings (cond_blind, !cond_glowhands, &c) + are excluded */ +staticfn void +all_options_conds(strbuf_t *sbuf) +{ + char buf[BUFSZ], nextcond[BUFSZ]; + int idx = 0; + boolean gotone = FALSE; + + buf[0] = '\0'; + while (opt_next_cond(idx, nextcond)) { + /* 75: room for about 5 conditions, with enough space for player + to edit resulting file manually and insert '!' in front of them */ + if (idx == 0) { + Strcpy(buf, "OPTIONS="); + } else if (Strlen(buf) + 1 + Strlen(nextcond) >= 75) { + /* finish off previous line */ + Strcat(buf, ",\\\n"); /* comma and backslash+newline */ + strbuf_append(sbuf, buf); + /* indent continuation line */ + Sprintf(buf, "%8s", " "); /* 8: strlen("OPTIONS=") */ + } else if (nextcond[0] && gotone) { + Strcat(buf, ","); + } + if (nextcond[0]) { + gotone = TRUE; + Strcat(buf, nextcond); + } + ++idx; + } + /* finish off final line; value might be empty if one or more cond_xyz + options were changed in such a manner that they're all back to their + default values--which will produce "OPTIONS=" with nothing after the + equals sign; only add to the output when there is more present */ + if (strcmp(buf, "OPTIONS=")) { + Strcat(buf, "\n"); + strbuf_append(sbuf, buf); + } +} + +/* append menucolor lines to strbuf */ +staticfn void +all_options_menucolors(strbuf_t *sbuf) +{ + int i = 0, ncolors = count_menucolors(); + struct menucoloring *tmp = gm.menu_colorings; + char buf[BUFSZ*2]; /* see also: add_menu_coloring() */ + struct menucoloring **arr; + + if (!ncolors) + return; + + /* reverse the order */ + arr = (struct menucoloring **) alloc(ncolors * sizeof *arr); + while (tmp) { + arr[i++] = tmp; + tmp = tmp->next; + } + + for (i = ncolors; i > 0; i--) { + tmp = arr[i-1]; + const char *sattr = attr2attrname(tmp->attr); + const char *sclr = clr2colorname(tmp->color); + Sprintf(buf, "MENUCOLOR=\"%s\"=%s%s%s\n", + tmp->origstr, + sclr, + (tmp->attr != ATR_NONE) ? "&" : "", + (tmp->attr != ATR_NONE) ? sattr : ""); + strbuf_append(sbuf, buf); + } + + free(arr); +} + +staticfn void +all_options_msgtypes(strbuf_t *sbuf) +{ + struct plinemsg_type *tmp = gp.plinemsg_types; + char buf[BUFSZ]; + + while (tmp) { + const char *mtype = msgtype2name(tmp->msgtype); + Sprintf(buf, "MSGTYPE=%s \"%s\"\n", + mtype, tmp->pattern); + strbuf_append(sbuf, buf); + tmp = tmp->next; + } +} + +staticfn void +all_options_apes(strbuf_t *sbuf) +{ + struct autopickup_exception *tmp = ga.apelist; + char buf[BUFSZ]; + + while (tmp) { + Sprintf(buf, "autopickup_exception=\"%c%s\"\n", + tmp->grab ? '<' : '>', tmp->pattern); + strbuf_append(sbuf, buf); + tmp = tmp->next; + } +} + +#ifdef CHANGE_COLOR +staticfn void +all_options_palette(strbuf_t *sbuf) +{ + int clr, n = count_alt_palette(); + char buf[BUFSZ]; + + if (!n) + return; + + for (clr = 0; clr < CLR_MAX; ++clr) { + if (ga.altpalette[clr] != 0U) { + Sprintf(buf, "OPTIONS=palette:%s/#%06x\n", + clr2colorname(clr), COLORVAL(ga.altpalette[clr])); + strbuf_append(sbuf, buf); + } + } +} +#endif /* CHANGE_COLOR */ +/* return strbuf of all options, to write to file */ void -option_help() +all_options_strbuf(strbuf_t *sbuf) { - char buf[BUFSZ], buf2[BUFSZ]; - register int i; - winid datawin; + const char *name; + char tmp[BUFSZ]; + char *buf2; + boolean *bool_p; + int i; - datawin = create_nhwindow(NHW_TEXT); - Sprintf(buf, "Set options as OPTIONS= in %s", configfile); - opt_intro[CONFIG_SLOT] = (const char *) buf; - for (i = 0; opt_intro[i]; i++) - putstr(datawin, 0, opt_intro[i]); + strbuf_init(sbuf); + Sprintf(tmp, "# NetHack config, saved %s\n#\n", + yyyymmddhhmmss((time_t) 0)); + strbuf_append(sbuf, tmp); - /* Boolean options */ - for (i = 0; boolopt[i].name; i++) { - if (boolopt[i].addr) { - if (boolopt[i].addr == &iflags.sanity_check && !wizard) - continue; - if (boolopt[i].addr == &iflags.menu_tab_sep && !wizard) - continue; - next_opt(datawin, boolopt[i].name); + for (i = 0; (name = allopt[i].name) != 0; i++) { + if (!opt_set_in_config[i]) + continue; + switch (allopt[i].opttyp) { + case BoolOpt: + bool_p = allopt[i].addr; + if (!bool_p || bool_p == &flags.female) + break; /* obsolete */ + if (*bool_p != allopt[i].initval) { + Sprintf(tmp, "OPTIONS=%s%s\n", *bool_p ? "" : "!", name); + strbuf_append(sbuf, tmp); + } + break; + case CompOpt: + if (!(allopt[i].setwhere == set_in_config + || allopt[i].setwhere == set_gameview + || allopt[i].setwhere == set_in_game)) + break; + /* FIXME: get_option_value for: + - menu_deselect_all &c menu control keys, + - term_cols, term_rows */ + buf2 = get_option_value(name, TRUE); + if (buf2) { + Snprintf(tmp, sizeof tmp - 1, "OPTIONS=%s:%s", name, buf2); + Strcat(tmp, "\n"); /* guaranteed to fit */ + strbuf_append(sbuf, tmp); + } + break; + case OthrOpt: + break; } } - next_opt(datawin, ""); - /* Compound options */ - putstr(datawin, 0, "Compound options:"); - for (i = 0; compopt[i].name; i++) { - Sprintf(buf2, "`%s'", compopt[i].name); - Sprintf(buf, "%-20s - %s%c", buf2, compopt[i].descr, - compopt[i + 1].name ? ',' : '.'); - putstr(datawin, 0, buf); - } + /* cond_xyz are closer to regular options than the other 'other opts' + so put them next; [pfx_cond_] will be set if any cond_Foo were + present when RC file was read in or if player made any changes via + status conditions menu; ignore opt_set_in_config[opt_o_status_cond] */ + if (opt_set_in_config[pfx_cond_]) + all_options_conds(sbuf); - for (i = 0; opt_epilog[i]; i++) - putstr(datawin, 0, opt_epilog[i]); +#ifdef CHANGE_COLOR + all_options_palette(sbuf); +#endif + get_changed_key_binds(sbuf); + savedsym_strbuf(sbuf); + all_options_menucolors(sbuf); + all_options_msgtypes(sbuf); + all_options_apes(sbuf); + all_options_autocomplete(sbuf); +#ifdef STATUS_HILITES + all_options_statushilites(sbuf); +#endif - display_nhwindow(datawin, FALSE); - destroy_nhwindow(datawin); - return; + if (gw.wizkit[0]) { + Sprintf(tmp, "WIZKIT=%s\n", gw.wizkit); + strbuf_append(sbuf, tmp); + } } /* @@ -6307,24 +9761,22 @@ option_help() * line if not. End with next_opt(""). */ void -next_opt(datawin, str) -winid datawin; -const char *str; +next_opt(winid datawin, const char *str) { static char *buf = 0; int i; char *s; if (!buf) - *(buf = (char *) alloc(BUFSZ)) = '\0'; + *(buf = (char *) alloc(COLBUFSZ)) = '\0'; if (!*str) { s = eos(buf); if (s > &buf[1] && s[-2] == ',') - Strcpy(s - 2, "."); /* replace last ", " */ + s[-2] = '.', s[-1] = '\0'; /* replace ending ", " with "." */ i = COLNO; /* (greater than COLNO - 2) */ } else { - i = strlen(buf) + strlen(str) + 2; + i = Strlen(buf) + Strlen(str) + 2; } if (i > COLNO - 2) { /* rule of thumb */ @@ -6341,259 +9793,13 @@ const char *str; return; } -/* Returns the fid of the fruit type; if that type already exists, it - * returns the fid of that one; if it does not exist, it adds a new fruit - * type to the chain and returns the new one. - * If replace_fruit is sent in, replace the fruit in the chain rather than - * adding a new entry--for user specified fruits only. - */ -int -fruitadd(str, replace_fruit) -char *str; -struct fruit *replace_fruit; -{ - register int i; - register struct fruit *f; - int highest_fruit_id = 0, globpfx; - char buf[PL_FSIZ], altname[PL_FSIZ]; - boolean user_specified = (str == pl_fruit); - /* if not user-specified, then it's a fruit name for a fruit on - * a bones level or from orctown raider's loot... - */ - - /* Note: every fruit has an id (kept in obj->spe) of at least 1; - * 0 is an error. - */ - if (user_specified) { - boolean found = FALSE, numeric = FALSE; - - /* force fruit to be singular; this handling is not - needed--or wanted--for fruits from bones because - they already received it in their original game; - str==pl_fruit but makesingular() creates a copy - so we need to copy that back into pl_fruit */ - nmcpy(pl_fruit, makesingular(str), PL_FSIZ); - /* (assertion doesn't matter; we use 'pl_fruit' from here on out) */ - /* assert( str == pl_fruit ); */ - - /* disallow naming after other foods (since it'd be impossible - * to tell the difference); globs might have a size prefix which - * needs to be skipped in order to match the object type name - */ - globpfx = (!strncmp(pl_fruit, "small ", 6) - || !strncmp(pl_fruit, "large ", 6)) ? 6 - : (!strncmp(pl_fruit, "very large ", 11)) ? 11 - : 0; - for (i = bases[FOOD_CLASS]; objects[i].oc_class == FOOD_CLASS; i++) { - if (!strcmp(OBJ_NAME(objects[i]), pl_fruit) - || (globpfx > 0 - && !strcmp(OBJ_NAME(objects[i]), &pl_fruit[globpfx]))) { - found = TRUE; - break; - } - } - if (!found) { - char *c; - - for (c = pl_fruit; *c >= '0' && *c <= '9'; c++) - continue; - if (!*c || isspace((uchar) *c)) - numeric = TRUE; - } - if (found || numeric - /* these checks for applying food attributes to actual items - are case sensitive; "glob of foo" is caught by 'found' - if 'foo' is a valid glob; when not valid, allow it as-is */ - || !strncmp(pl_fruit, "cursed ", 7) - || !strncmp(pl_fruit, "uncursed ", 9) - || !strncmp(pl_fruit, "blessed ", 8) - || !strncmp(pl_fruit, "partly eaten ", 13) - || (!strncmp(pl_fruit, "tin of ", 7) - && (!strcmp(pl_fruit + 7, "spinach") - || name_to_mon(pl_fruit + 7) >= LOW_PM)) - || !strcmp(pl_fruit, "empty tin") - || (!strcmp(pl_fruit, "glob") - || (globpfx > 0 && !strcmp("glob", &pl_fruit[globpfx]))) - || ((str_end_is(pl_fruit, " corpse") - || str_end_is(pl_fruit, " egg")) - && name_to_mon(pl_fruit) >= LOW_PM)) { - Strcpy(buf, pl_fruit); - Strcpy(pl_fruit, "candied "); - nmcpy(pl_fruit + 8, buf, PL_FSIZ - 8); - } - *altname = '\0'; - /* This flag indicates that a fruit has been made since the - * last time the user set the fruit. If it hasn't, we can - * safely overwrite the current fruit, preventing the user from - * setting many fruits in a row and overflowing. - * Possible expansion: check for specific fruit IDs, not for - * any fruit. - */ - flags.made_fruit = FALSE; - if (replace_fruit) { - /* replace_fruit is already part of the fruit chain; - update it in place rather than looking it up again */ - f = replace_fruit; - copynchars(f->fname, pl_fruit, PL_FSIZ - 1); - goto nonew; - } - } else { - /* not user_supplied, so assumed to be from bones (or orc gang) */ - copynchars(altname, str, PL_FSIZ - 1); - sanitize_name(altname); - flags.made_fruit = TRUE; /* for safety. Any fruit name added from a - * bones level should exist anyway. */ - } - f = fruit_from_name(*altname ? altname : str, FALSE, &highest_fruit_id); - if (f) - goto nonew; - - /* Maximum number of named fruits is 127, even if obj->spe can - handle bigger values. If adding another fruit would overflow, - use a random fruit instead... we've got a lot to choose from. - current_fruit remains as is. */ - if (highest_fruit_id >= 127) - return rnd(127); - - f = newfruit(); - (void) memset((genericptr_t) f, 0, sizeof (struct fruit)); - copynchars(f->fname, *altname ? altname : str, PL_FSIZ - 1); - f->fid = ++highest_fruit_id; - /* we used to go out of our way to add it at the end of the list, - but the order is arbitrary so use simpler insertion at start */ - f->nextf = ffruit; - ffruit = f; - nonew: - if (user_specified) - context.current_fruit = f->fid; - return f->fid; -} - -/* - * This is a somewhat generic menu for taking a list of NetHack style - * class choices and presenting them via a description - * rather than the traditional NetHack characters. - * (Benefits users whose first exposure to NetHack is via tiles). - * - * prompt - * The title at the top of the menu. - * - * category: 0 = monster class - * 1 = object class - * - * way - * FALSE = PICK_ONE, TRUE = PICK_ANY - * - * class_list - * a null terminated string containing the list of choices. - * - * class_selection - * a null terminated string containing the selected characters. - * - * Returns number selected. - */ -int -choose_classes_menu(prompt, category, way, class_list, class_select) -const char *prompt; -int category; -boolean way; -char *class_list; -char *class_select; -{ - menu_item *pick_list = (menu_item *) 0; - winid win; - anything any; - char buf[BUFSZ]; - int i, n; - int ret; - int next_accelerator, accelerator; - - if (class_list == (char *) 0 || class_select == (char *) 0) - return 0; - accelerator = 0; - next_accelerator = 'a'; - any = zeroany; - win = create_nhwindow(NHW_MENU); - start_menu(win); - while (*class_list) { - const char *text; - boolean selected; - - text = (char *) 0; - selected = FALSE; - switch (category) { - case 0: - text = def_monsyms[def_char_to_monclass(*class_list)].explain; - accelerator = *class_list; - Sprintf(buf, "%s", text); - break; - case 1: - text = def_oc_syms[def_char_to_objclass(*class_list)].explain; - accelerator = next_accelerator; - Sprintf(buf, "%c %s", *class_list, text); - break; - default: - impossible("choose_classes_menu: invalid category %d", category); - } - if (way && *class_select) { /* Selections there already */ - if (index(class_select, *class_list)) { - selected = TRUE; - } - } - any.a_int = *class_list; - add_menu(win, NO_GLYPH, &any, accelerator, category ? *class_list : 0, - ATR_NONE, buf, selected); - ++class_list; - if (category > 0) { - ++next_accelerator; - if (next_accelerator == ('z' + 1)) - next_accelerator = 'A'; - if (next_accelerator == ('Z' + 1)) - break; - } - } - if (category == 1 && next_accelerator <= 'z') { - /* for objects, add "A - ' ' all classes", after a separator */ - any = zeroany; - add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED); - any.a_int = (int) ' '; - Sprintf(buf, "%c %s", (char) any.a_int, "all classes of objects"); - /* we won't preselect this even if the incoming list is empty; - having it selected means that it would have to be explicitly - de-selected in order to select anything else */ - add_menu(win, NO_GLYPH, &any, 'A', 0, ATR_NONE, buf, MENU_UNSELECTED); - } - end_menu(win, prompt); - n = select_menu(win, way ? PICK_ANY : PICK_ONE, &pick_list); - destroy_nhwindow(win); - if (n > 0) { - if (category == 1) { - /* for object classes, first check for 'all'; it means 'use - a blank list' rather than 'collect every possible choice' */ - for (i = 0; i < n; ++i) - if (pick_list[i].item.a_int == ' ') { - pick_list[0].item.a_int = ' '; - n = 1; /* return 1; also an implicit 'break;' */ - } - } - for (i = 0; i < n; ++i) - *class_select++ = (char) pick_list[i].item.a_int; - free((genericptr_t) pick_list); - ret = n; - } else if (n == -1) { - class_select = eos(class_select); - ret = -1; - } else - ret = 0; - *class_select = '\0'; - return ret; -} - static struct wc_Opt wc_options[] = { { "ascii_map", WC_ASCII_MAP }, { "color", WC_COLOR }, { "eight_bit_tty", WC_EIGHT_BIT_IN }, { "hilite_pet", WC_HILITE_PET }, + { "perm_invent", WC_PERM_INVENT }, + { "perminv_mode", WC_PERM_INVENT }, /* shares WC_PERM_INVENT */ { "popup_dialog", WC_POPUP_DIALOG }, { "player_selection", WC_PLAYER_SELECTION }, { "preload_tiles", WC_PRELOAD_TILES }, @@ -6601,15 +9807,11 @@ static struct wc_Opt wc_options[] = { { "tile_file", WC_TILE_FILE }, { "tile_width", WC_TILE_WIDTH }, { "tile_height", WC_TILE_HEIGHT }, - { "use_inverse", WC_INVERSE }, { "align_message", WC_ALIGN_MESSAGE }, { "align_status", WC_ALIGN_STATUS }, { "font_map", WC_FONT_MAP }, { "font_menu", WC_FONT_MENU }, { "font_message", WC_FONT_MESSAGE }, -#if 0 - {"perm_invent", WC_PERM_INVENT}, -#endif { "font_size_map", WC_FONTSIZ_MAP }, { "font_size_menu", WC_FONTSIZ_MENU }, { "font_size_message", WC_FONTSIZ_MESSAGE }, @@ -6621,42 +9823,45 @@ static struct wc_Opt wc_options[] = { { "scroll_amount", WC_SCROLL_AMOUNT }, { "scroll_margin", WC_SCROLL_MARGIN }, { "splash_screen", WC_SPLASH_SCREEN }, + { "use_inverse", WC_INVERSE }, { "vary_msgcount", WC_VARY_MSGCOUNT }, { "windowcolors", WC_WINDOWCOLORS }, { "mouse_support", WC_MOUSE_SUPPORT }, { (char *) 0, 0L } }; static struct wc_Opt wc2_options[] = { + { "armorstatus", WC2_EXTRASTATUS }, { "fullscreen", WC2_FULLSCREEN }, - { "softkeyboard", WC2_SOFTKEYBOARD }, - { "wraptext", WC2_WRAPTEXT }, - { "use_darkgray", WC2_DARKGRAY }, - { "hitpointbar", WC2_HITPOINTBAR }, + { "guicolor", WC2_GUICOLOR }, { "hilite_status", WC2_HILITE_STATUS }, + { "hitpointbar", WC2_HITPOINTBAR }, + { "menu_shift", WC2_MENU_SHIFT }, + { "petattr", WC2_PETATTR }, + { "softkeyboard", WC2_SOFTKEYBOARD }, /* name shown in 'O' menu is different */ { "status hilite rules", WC2_HILITE_STATUS }, /* statushilites doesn't have its own bit */ { "statushilites", WC2_HILITE_STATUS }, + { "statuslines", WC2_STATUSLINES }, { "term_cols", WC2_TERM_SIZE }, { "term_rows", WC2_TERM_SIZE }, - { "petattr", WC2_PETATTR }, - { "guicolor", WC2_GUICOLOR }, - { "statuslines", WC2_STATUSLINES }, + { "terrainstatus", WC2_EXTRASTATUS }, + { "use_darkgray", WC2_DARKGRAY }, + { "weaponstatus", WC2_EXTRASTATUS }, { "windowborders", WC2_WINDOWBORDERS }, + { "wraptext", WC2_WRAPTEXT }, { (char *) 0, 0L } }; /* - * If a port wants to change or ensure that the SET_IN_SYS, - * SET_IN_FILE, DISP_IN_GAME, or SET_IN_GAME status of an option is + * If a port wants to change or ensure that the set_in_sysconf, + * set_in_config, set_gameview, or set_in_game status of an option is * correct (for controlling its display in the option menu) call * set_option_mod_status() * with the appropriate second argument. */ void -set_option_mod_status(optnam, status) -const char *optnam; -int status; +set_option_mod_status(const char *optnam, int status) { int k; @@ -6664,15 +9869,9 @@ int status; impossible("set_option_mod_status: status out of range %d.", status); return; } - for (k = 0; boolopt[k].name; k++) { - if (!strncmpi(boolopt[k].name, optnam, strlen(optnam))) { - boolopt[k].optflags = status; - return; - } - } - for (k = 0; compopt[k].name; k++) { - if (!strncmpi(compopt[k].name, optnam, strlen(optnam))) { - compopt[k].optflags = status; + for (k = 0; allopt[k].name; k++) { + if (str_start_is(allopt[k].name, optnam, TRUE)) { + allopt[k].setwhere = status; return; } } @@ -6685,12 +9884,10 @@ int status; * are setting in the optmask argument * prior to calling. * example: set_wc_option_mod_status(WC_COLOR|WC_SCROLL_MARGIN, - * SET_IN_GAME); + * set_in_game); */ void -set_wc_option_mod_status(optmask, status) -unsigned long optmask; -int status; +set_wc_option_mod_status(unsigned long optmask, int status) { int k = 0; @@ -6707,9 +9904,8 @@ int status; } } -STATIC_OVL boolean -is_wc_option(optnam) -const char *optnam; +staticfn boolean +is_wc_option(const char *optnam) { int k = 0; @@ -6721,9 +9917,8 @@ const char *optnam; return FALSE; } -STATIC_OVL boolean -wc_supported(optnam) -const char *optnam; +staticfn boolean +wc_supported(const char *optnam) { int k; @@ -6742,13 +9937,11 @@ const char *optnam; * prior to calling. * example: * set_wc2_option_mod_status(WC2_FULLSCREEN|WC2_SOFTKEYBOARD|WC2_WRAPTEXT, - * SET_IN_FILE); + * set_in_config); */ void -set_wc2_option_mod_status(optmask, status) -unsigned long optmask; -int status; +set_wc2_option_mod_status(unsigned long optmask, int status) { int k = 0; @@ -6765,9 +9958,8 @@ int status; } } -STATIC_OVL boolean -is_wc2_option(optnam) -const char *optnam; +staticfn boolean +is_wc2_option(const char *optnam) { int k = 0; @@ -6779,9 +9971,8 @@ const char *optnam; return FALSE; } -STATIC_OVL boolean -wc2_supported(optnam) -const char *optnam; +staticfn boolean +wc2_supported(const char *optnam) { int k; @@ -6793,10 +9984,8 @@ const char *optnam; return FALSE; } -STATIC_OVL void -wc_set_font_name(opttype, fontname) -int opttype; -char *fontname; +staticfn void +wc_set_font_name(int opttype, char *fontname) { char **fn = (char **) 0; @@ -6829,76 +10018,70 @@ char *fontname; return; } -STATIC_OVL int -wc_set_window_colors(op) -char *op; +static char **fgp[] = { &iflags.wcolors[wcolor_menu].fg, + &iflags.wcolors[wcolor_message].fg, + &iflags.wcolors[wcolor_status].fg, + &iflags.wcolors[wcolor_text].fg }; +static char **bgp[] = { &iflags.wcolors[wcolor_menu].bg, + &iflags.wcolors[wcolor_message].bg, + &iflags.wcolors[wcolor_status].bg, + &iflags.wcolors[wcolor_text].bg }; +int options_set_window_colors_flag = 0; + +staticfn int +wc_set_window_colors(char *op) { /* syntax: * menu white/black message green/yellow status white/blue text * white/black */ + int j; + int32 clr; char buf[BUFSZ]; char *wn, *tfg, *tbg, *newop; - static const char *wnames[] = { "menu", "message", "status", "text" }; - static const char *shortnames[] = { "mnu", "msg", "sts", "txt" }; - static char **fgp[] = { &iflags.wc_foregrnd_menu, - &iflags.wc_foregrnd_message, - &iflags.wc_foregrnd_status, - &iflags.wc_foregrnd_text }; - static char **bgp[] = { &iflags.wc_backgrnd_menu, - &iflags.wc_backgrnd_message, - &iflags.wc_backgrnd_status, - &iflags.wc_backgrnd_text }; Strcpy(buf, op); newop = mungspaces(buf); - while (newop && *newop) { + while (*newop) { wn = tfg = tbg = (char *) 0; /* until first non-space in case there's leading spaces - before - * colorname*/ + colorname*/ if (*newop == ' ') newop++; - if (*newop) - wn = newop; - else + if (!*newop) return 0; + wn = newop; /* until first space - colorname*/ while (*newop && *newop != ' ') newop++; - if (*newop) - *newop = '\0'; - else + if (!*newop) return 0; - newop++; + *newop++ = '\0'; /* until first non-space - before foreground*/ if (*newop == ' ') newop++; - if (*newop) - tfg = newop; - else + if (!*newop) return 0; + tfg = newop; /* until slash - foreground */ while (*newop && *newop != '/') newop++; - if (*newop) - *newop = '\0'; - else + if (!*newop) return 0; - newop++; + *newop++ = '\0'; /* until first non-space (in case there's leading space after slash) - * before background */ if (*newop == ' ') newop++; - if (*newop) - tbg = newop; - else + if (!*newop) return 0; + tbg = newop; /* until first space - background */ while (*newop && *newop != ' ') @@ -6906,44 +10089,152 @@ char *op; if (*newop) *newop++ = '\0'; - for (j = 0; j < 4; ++j) { - if (!strcmpi(wn, wnames[j]) || !strcmpi(wn, shortnames[j])) { - if (tfg && !strstri(tfg, " ")) { + for (j = 0; j < WC_COUNT; ++j) { + if (!strcmpi(wn, wcnames[j]) || !strcmpi(wn, wcshortnames[j])) { + if (!strstri(tfg, " ")) { if (*fgp[j]) free((genericptr_t) *fgp[j]); - *fgp[j] = dupstr(tfg); + clr = check_enhanced_colors(tfg); + *fgp[j] = dupstr((clr >= 0) ? wc_color_name(clr) : tfg); } - if (tbg && !strstri(tbg, " ")) { + if (!strstri(tbg, " ")) { if (*bgp[j]) free((genericptr_t) *bgp[j]); - *bgp[j] = dupstr(tbg); + clr = check_enhanced_colors(tbg); + *bgp[j] = dupstr((clr >= 0) ? wc_color_name(clr) : tbg); + } + if (wcolors_opt[j] != 0) { + config_error_add( + "windowcolors for %s windows specified multiple times", + wcnames[j]); } + wcolors_opt[j]++; break; } } + if (j == WC_COUNT) { + config_error_add("windowcolors for unrecognized window type: %s", + wn); + } } + options_set_window_colors_flag = 1; return 1; } -/* set up for wizard mode if player or save file has requested to it; +void +options_free_window_colors(void) +{ + int j; + + for (j = 0; j < WC_COUNT; ++j) { + if (*fgp[j]) + free((genericptr_t) *fgp[j]), *fgp[j] = 0; + if (*bgp[j]) + free((genericptr_t) *bgp[j]), *bgp[j] = 0; + } + options_set_window_colors_flag = 0; +} + +/* set up for wizard mode if player or save file has requested it; called from port-specific startup code to handle `nethack -D' or OPTIONS=playmode:debug, or from dorecover()'s restgamestate() if restoring a game which was saved in wizard mode */ void -set_playmode() +set_playmode(void) { if (wizard) { if (authorize_wizard_mode()) - Strcpy(plname, "wizard"); + gp.plnamelen = (int) strlen(strcpy(svp.plname, "wizard")); else wizard = FALSE; /* not allowed or not available */ - /* force explore mode if we didn't make it into wizard mode */ + /* try explore mode if we didn't make it into wizard mode */ + /* if requesting wizard mode when restoring a normal game, this will + set iflags.deferred_X and prompt to activate explore mode after the + save file has already been deleted */ discover = !wizard; iflags.deferred_X = FALSE; } - /* don't need to do anything special for explore mode or normal play */ + if (discover && !authorize_explore_mode()) { + discover = iflags.deferred_X = FALSE; + } + /* don't need to do anything special for normal play */ +} + +staticfn void +enhance_menu_text( + char *buf, + size_t sz, + int whichpass UNUSED, + boolean *bool_p, + struct allopt_t *thisopt) +{ + size_t nowsz, availsz; + + if (!buf) + return; + nowsz = strlen(buf) + 1; + availsz = sz - nowsz; + +#if 0 /*#ifdef TTY_PERM_INVENT*/ + if (bool_p == &iflags.perm_invent && WINDOWPORT(tty)) { + if (thisopt->setwhere == set_gameview) + Snprintf(eos(buf), availsz, " *terminal size is too small"); + } +#else + nhUse(availsz); + nhUse(bool_p); + nhUse(thisopt); +#endif + return; +} + +void +heed_all_options(void) +{ + int i; + + /* ensure OPTIONS= lines are enabled */ + heed_this_config_statement(0); /* index 0 == OPTIONS */ + + for (i = 0; i < OPTCOUNT; i++) + allopt[i].disregarded = FALSE; +} + +void +disregard_all_options(void) +{ + int i; + + for (i = 0; i < OPTCOUNT ; i++) + allopt[i].disregarded = TRUE; +} + +void +heed_this_option(enum opt optidx) +{ + /* ensure OPTIONS= lines are enabled */ + heed_this_config_statement(0); /* index 0 == OPTIONS */ + + if (optidx >= 0 && optidx < (enum opt) OPTCOUNT) + allopt[optidx].disregarded = FALSE; +} +void +disregard_this_option(enum opt optidx) +{ + if (optidx >= 0 && optidx < (enum opt) OPTCOUNT) + allopt[optidx].disregarded = TRUE; } + + +#undef OPTIONS_HEADING +#undef CONFIG_SLOT + #endif /* OPTION_LISTS_ONLY */ +#undef BACKWARD_COMPAT +#undef COMPLAIN_ABOUT_PRAYCONFIRM +#undef PREV_MSGS +#undef PILE_LIMIT_DFLT + /*options.c*/ diff --git a/src/pager.c b/src/pager.c index c1c7b923d..6f9cd22e8 100644 --- a/src/pager.c +++ b/src/pager.c @@ -1,50 +1,76 @@ -/* NetHack 3.6 pager.c $NHDT-Date: 1574722864 2019/11/25 23:01:04 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.162 $ */ +/* NetHack 5.0 pager.c $NHDT-Date: 1774846177 2026/03/29 20:49:37 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.296 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2018. */ /* NetHack may be freely redistributed. See license for details. */ -/* This file contains the command routines dowhatis() and dohelp() and */ -/* a few other help related facilities */ +/* + * This file contains the command routines dowhatis() and dohelp() and + * a few other help related facilities such as data.base lookup. + */ #include "hack.h" #include "dlb.h" -STATIC_DCL boolean FDECL(is_swallow_sym, (int)); -STATIC_DCL int FDECL(append_str, (char *, const char *)); -STATIC_DCL void FDECL(look_at_object, (char *, int, int, int)); -STATIC_DCL void FDECL(look_at_monster, (char *, char *, - struct monst *, int, int)); -STATIC_DCL struct permonst *FDECL(lookat, (int, int, char *, char *)); -STATIC_DCL void FDECL(checkfile, (char *, struct permonst *, - BOOLEAN_P, BOOLEAN_P, char *)); -STATIC_DCL void FDECL(look_all, (BOOLEAN_P,BOOLEAN_P)); -STATIC_DCL void FDECL(do_supplemental_info, (char *, struct permonst *, - BOOLEAN_P)); -STATIC_DCL void NDECL(whatdoes_help); -STATIC_DCL void NDECL(docontact); -STATIC_DCL void NDECL(dispfile_help); -STATIC_DCL void NDECL(dispfile_shelp); -STATIC_DCL void NDECL(dispfile_optionfile); -STATIC_DCL void NDECL(dispfile_license); -STATIC_DCL void NDECL(dispfile_debughelp); -STATIC_DCL void NDECL(hmenu_doextversion); -STATIC_DCL void NDECL(hmenu_dohistory); -STATIC_DCL void NDECL(hmenu_dowhatis); -STATIC_DCL void NDECL(hmenu_dowhatdoes); -STATIC_DCL void NDECL(hmenu_doextlist); +staticfn boolean is_swallow_sym(int); +staticfn int append_str(char *, const char *) NONNULLPTRS; +staticfn void trap_description(char *, int, coordxy, coordxy) NONNULLARG1; +staticfn void look_at_object(char *, coordxy, coordxy, int) NONNULLARG1; +staticfn void look_at_monster(char *, char *, struct monst *, + coordxy, coordxy) NONNULLARG13; +/* lookat() can return Null */ +staticfn struct permonst *lookat(coordxy, coordxy, char *, char *) NONNULLPTRS; +staticfn boolean checkfile(char *, struct permonst *, unsigned, + char *) NO_NNARGS; +staticfn int add_cmap_descr(int, int, int, int, coord, + const char *, const char *, + boolean *, const char **, char *) NONNULLPTRS; +staticfn void look_region_nearby(coordxy *, coordxy *, coordxy *, coordxy *, + boolean) NONNULLPTRS; +staticfn void look_all(boolean, boolean); +staticfn void look_traps(boolean); +staticfn void look_engrs(boolean); +staticfn void do_supplemental_info(char *, struct permonst *, + boolean) NONNULLPTRS; +staticfn void whatdoes_help(void); +staticfn void docontact(void); +staticfn void dispfile_help(void); +staticfn void dispfile_shelp(void); +staticfn void dispfile_optionfile(void); +staticfn void dispfile_optmenu(void); +staticfn void dispfile_license(void); +staticfn void dispfile_debughelp(void); +staticfn void dispfile_usagehelp(void); +staticfn void hmenu_doextversion(void); +staticfn void hmenu_dohistory(void); +staticfn void hmenu_dowhatis(void); +staticfn void hmenu_dowhatdoes(void); +staticfn void hmenu_doextlist(void); +staticfn void domenucontrols(void); #ifdef PORT_HELP -extern void NDECL(port_help); +extern void port_help(void); #endif +staticfn char *setopt_cmd(char *) NONNULL NONNULLARG1; +staticfn boolean add_quoted_engraving(coordxy, coordxy, char *, boolean) + NONNULLARG3; + +enum checkfileflags { + chkfilNone = 0, + chkfilUsrTyped = 1, + chkfilDontAsk = 2, + chkfilIaCheck = 4, +}; + +static const char invisexplain[] = "remembered, unseen, creature", + altinvisexplain[] = "unseen creature"; /* for clairvoyance */ /* Returns "true" for characters that could represent a monster's stomach. */ -STATIC_OVL boolean -is_swallow_sym(c) -int c; +staticfn boolean +is_swallow_sym(int c) { int i; for (i = S_sw_tl; i <= S_sw_br; i++) - if ((int) showsyms[i] == c) + if ((int) gs.showsyms[i] == c) return TRUE; return FALSE; } @@ -52,14 +78,12 @@ int c; /* Append " or "+new_str to the end of buf if new_str doesn't already exist as a substring of buf. Return 1 if the string was appended, 0 otherwise. It is expected that buf is of size BUFSZ. */ -STATIC_OVL int -append_str(buf, new_str) -char *buf; -const char *new_str; +staticfn int +append_str(char *buf, const char *new_str) { static const char sep[] = " or "; size_t oldlen, space_left; - + if (strstri(buf, new_str)) return 0; /* already present */ @@ -70,7 +94,7 @@ const char *new_str; (unsigned long) oldlen); return 0; /* no space available */ } - + /* some space available, but not necessarily enough for full append */ space_left = BUFSZ - 1 - oldlen; /* space remaining in buf */ (void) strncat(buf, sep, space_left); @@ -81,66 +105,141 @@ const char *new_str; /* shared by monster probing (via query_objlist!) as well as lookat() */ char * -self_lookat(outbuf) -char *outbuf; +self_lookat(char *outbuf) { - char race[QBUFSZ]; + char race[QBUFSZ], trapbuf[QBUFSZ]; /* include race with role unless polymorphed */ race[0] = '\0'; if (!Upolyd) - Sprintf(race, "%s ", urace.adj); + Sprintf(race, "%s ", gu.urace.adj); Sprintf(outbuf, "%s%s%s called %s", /* being blinded may hide invisibility from self */ (Invis && (senseself() || !Blind)) ? "invisible " : "", race, - mons[u.umonnum].mname, plname); + pmname(&mons[u.umonnum], Ugender), svp.plname); if (u.usteed) Sprintf(eos(outbuf), ", mounted on %s", y_monnam(u.usteed)); - if (u.uundetected || (Upolyd && U_AP_TYPE)) - mhidden_description(&youmonst, FALSE, eos(outbuf)); + if (u.uundetected || (Upolyd && U_AP_TYPE) + || visible_region_at(u.ux, u.uy)) + mhidden_description(&gy.youmonst, + MHID_PREFIX | MHID_ARTICLE | MHID_REGION, + eos(outbuf)); + if (Punished) + Sprintf(eos(outbuf), ", chained to %s", + uball ? ansimpleoname(uball) : "nothing?"); + if (u.utrap) /* bear trap, pit, web, in-floor, in-lava, tethered */ + Sprintf(eos(outbuf), ", %s", trap_predicament(trapbuf, 0, FALSE)); + return outbuf; +} + +/* format a description of 'mon's health for look_at_monster(), done_in_by(); + result isn't Healer-specific (not trained for arbitrary creatures) */ +char * +monhealthdescr(struct monst *mon, boolean addspace, char *outbuf) +{ +#if 0 /* [disable this for the time being] */ + int mhp_max = max(mon->mhpmax, 1), /* bullet proofing */ + pct = (mon->mhp * 100) / mhp_max; + + if (mon->mhp >= mhp_max) + Strcpy(outbuf, "uninjured"); + else if (mon->mhp <= 1 || pct < 5) + Sprintf(outbuf, "%s%s", (mon->mhp > 0) ? "nearly " : "", + !nonliving(mon->data) ? "deceased" : "defunct"); + else + Sprintf(outbuf, "%swounded", + (pct >= 95) ? "barely " + : (pct >= 80) ? "slightly " + : (pct < 20) ? "heavily " + : ""); + if (addspace) + (void) strkitten(outbuf, ' '); +#else + nhUse(mon); + nhUse(addspace); + *outbuf = '\0'; +#endif return outbuf; } +/* copy a trap's description into outbuf[] */ +staticfn void +trap_description(char *outbuf, int tnum, coordxy x, coordxy y) +{ + /* + * Trap detection used to display a bear trap at locations having + * a trapped door or trapped container or both. They're semi-real + * traps now (defined trap types but not part of ftrap chain). + */ + if (trapped_chest_at(tnum, x, y)) + Strcpy(outbuf, "trapped chest"); /* might actually be a large box */ + else if (trapped_door_at(tnum, x, y)) + Strcpy(outbuf, "trapped door"); /* not "trap door"... */ + else + Strcpy(outbuf, trapname(tnum, FALSE)); + return; +} + /* describe a hidden monster; used for look_at during extended monster - detection and for probing; also when looking at self */ + detection and for probing; also when looking at self and camera feedback */ void -mhidden_description(mon, altmon, outbuf) -struct monst *mon; -boolean altmon; /* for probing: if mimicking a monster, say so */ -char *outbuf; +mhidden_description( + struct monst *mon, /* hidden monster to describe */ + unsigned mhid_flags, /* controls optional aspects of description */ + char *outbuf) /* output buffer */ { struct obj *otmp; - boolean fakeobj, isyou = (mon == &youmonst); - int x = isyou ? u.ux : mon->mx, y = isyou ? u.uy : mon->my, - glyph = (level.flags.hero_memory && !isyou) ? levl[x][y].glyph - : glyph_at(x, y); + const char *what; + NhRegion *reg; + size_t buflen; + boolean incl_prefix = (mhid_flags & MHID_PREFIX) != 0, + incl_article = (mhid_flags & MHID_ARTICLE) != 0, + show_altmon = (mhid_flags & MHID_ALTMON) != 0, + force_region = (mhid_flags & MHID_REGION) != 0; + boolean fakeobj, isyou = (mon == &gy.youmonst); + coordxy x = isyou ? u.ux : mon->mx, y = isyou ? u.uy : mon->my; + int glyph = (svl.level.flags.hero_memory && !isyou) ? levl[x][y].glyph + : glyph_at(x, y); *outbuf = '\0'; if (M_AP_TYPE(mon) == M_AP_FURNITURE || M_AP_TYPE(mon) == M_AP_OBJECT) { - Strcpy(outbuf, ", mimicking "); + if (incl_prefix) + Strcpy(outbuf, ", mimicking "); if (M_AP_TYPE(mon) == M_AP_FURNITURE) { - Strcat(outbuf, an(defsyms[mon->mappearance].explanation)); + what = defsyms[mon->mappearance].explanation; + if (incl_article) + what = an(what); + Strcat(outbuf, what); } else if (M_AP_TYPE(mon) == M_AP_OBJECT /* remembered glyph, not glyph_at() which is 'mon' */ && glyph_is_object(glyph)) { objfrommap: otmp = (struct obj *) 0; fakeobj = object_from_map(glyph, x, y, &otmp); - Strcat(outbuf, (otmp && otmp->otyp != STRANGE_OBJECT) - ? ansimpleoname(otmp) - : an(obj_descr[STRANGE_OBJECT].oc_name)); - if (fakeobj) { + what = (otmp && otmp->otyp != STRANGE_OBJECT) + ? simpleonames(otmp) + : obj_descr[STRANGE_OBJECT].oc_name; + if (incl_article && (!otmp || otmp->quan == 1L)) + what = an(what); + Strcat(outbuf, what); + + if (fakeobj && otmp) { otmp->where = OBJ_FREE; /* object_from_map set to OBJ_FLOOR */ - dealloc_obj(otmp); + dealloc_obj(otmp); /* has no contents */ } } else { Strcat(outbuf, something); } } else if (M_AP_TYPE(mon) == M_AP_MONSTER) { - if (altmon) - Sprintf(outbuf, ", masquerading as %s", - an(mons[mon->mappearance].mname)); + if (show_altmon) { + if (incl_prefix) + Strcat(outbuf, ", masquerading as "); + what = pmname(&mons[mon->mappearance], Mgender(mon)); + if (incl_prefix) + what = an(what); + Strcat(outbuf, what); + } } else if (isyou ? u.uundetected : mon->mundetected) { Strcpy(outbuf, ", hiding"); if (hides_under(mon->data)) { @@ -151,31 +250,55 @@ char *outbuf; Strcat(outbuf, something); } else if (is_hider(mon->data)) { Sprintf(eos(outbuf), " on the %s", - (is_flyer(mon->data) || mon->data->mlet == S_PIERCER) - ? "ceiling" + ceiling_hider(mon->data) ? "ceiling" : surface(x, y)); /* trapper */ } else { if (mon->data->mlet == S_EEL && is_pool(x, y)) Strcat(outbuf, " in murky water"); } } + + /* FIXME: isn't right when looking at long worm tails */ + if ((reg = visible_region_at(x, y)) != 0 + && (buflen = strlen(outbuf)) < BUFSZ - 1) { + int r = (u.xray_range > 1) ? u.xray_range : 1; + + /* at present, hero must be next to the monster; being able to see + from the hero's spot to the monster's spot would be much better, + but a visible region marks all its spots as can't-be-seen, so + this monster's spot is !cansee and !couldsee [maybe we need an + additional vision bit for "hero's side of edge of gas cloud"?] */ + if (distu(x, y) <= r * (r + 1) || force_region) { + int rglyph = reg->glyph; + boolean poison_gas = (glyph_is_cmap(rglyph) + && glyph_to_cmap(rglyph) == S_poisoncloud); + + Snprintf(eos(outbuf), BUFSZ - buflen, ", in a cloud of %s", + poison_gas ? "poison gas" : "vapor"); + } + } } /* extracted from lookat(); also used by namefloorobj() */ boolean -object_from_map(glyph, x, y, obj_p) -int glyph, x, y; -struct obj **obj_p; +object_from_map( + int glyph, + coordxy x, coordxy y, + struct obj **obj_p) { boolean fakeobj = FALSE, mimic_obj = FALSE; struct monst *mtmp; struct obj *otmp; - int glyphotyp = glyph_to_obj(glyph); + int glyphotyp = glyph_is_object(glyph) ? glyph_to_obj(glyph) + /* if not an object, probably a detected chest trap */ + : glyph_is_cmap(glyph) /* assume trapped chest|door */ + ? (sobj_at(CHEST, x, y) ? CHEST : LARGE_BOX) + : STRANGE_OBJECT; *obj_p = (struct obj *) 0; /* TODO: check inside containers in case glyph came from detection */ if ((otmp = sobj_at(glyphotyp, x, y)) == 0) - for (otmp = level.buriedobjlist; otmp; otmp = otmp->nobj) + for (otmp = svl.level.buriedobjlist; otmp; otmp = otmp->nobj) if (otmp->ox == x && otmp->oy == y && otmp->otyp == glyphotyp) break; @@ -189,26 +312,41 @@ struct obj **obj_p; if (!otmp || otmp->otyp != glyphotyp) { /* this used to exclude STRANGE_OBJECT; now caller deals with it */ - otmp = mksobj(glyphotyp, FALSE, FALSE); - if (!otmp) - return FALSE; + if (OBJ_NAME(objects[glyphotyp])) { + /* map shows a regular object, but one that's not actually here */ + otmp = mksobj(glyphotyp, FALSE, FALSE); + } else { + /* map shows a non-item that holds an extra object type (shown + on map due to hallucination) for a name which might have been + shuffled into play but wasn't (or was shuffled out of play); + pick another item that is a regular one in same object class */ + otmp = mkobj(objects[glyphotyp].oc_class, FALSE); + /* mkobj() doesn't provide any no-init option; however, there + aren't any extra tool items (or statues) so we won't get here + for tools and don't need to check for and delete container + contents or extinguish lights on the temporary object */ + } + /* even though we pass False for mksobj()'s 'init' arg, corpse-rot, + egg-hatch, and figurine-transform timers get initialized */ + if (otmp->timed) + obj_stop_timers(otmp); fakeobj = TRUE; if (otmp->oclass == COIN_CLASS) otmp->quan = 2L; /* to force pluralization */ else if (otmp->otyp == SLIME_MOLD) - otmp->spe = context.current_fruit; /* give it a type */ + otmp->spe = svc.context.current_fruit; /* give it a type */ if (mtmp && has_mcorpsenm(mtmp)) { /* mimic as corpse/statue */ if (otmp->otyp == SLIME_MOLD) - /* override context.current_fruit to avoid + /* override svc.context.current_fruit to avoid look, use 'O' to make new named fruit, look again giving different results when current_fruit changes */ otmp->spe = MCORPSENM(mtmp); else otmp->corpsenm = MCORPSENM(mtmp); } else if (otmp->otyp == CORPSE && glyph_is_body(glyph)) { - otmp->corpsenm = glyph - GLYPH_BODY_OFF; + otmp->corpsenm = glyph_to_body_corpsenm(glyph); } else if (otmp->otyp == STATUE && glyph_is_statue(glyph)) { - otmp->corpsenm = glyph - GLYPH_STATUE_OFF; + otmp->corpsenm = glyph_to_statue_corpsenm(glyph); } if (otmp->otyp == LEASH) otmp->leashmon = 0; @@ -220,7 +358,7 @@ struct obj **obj_p; /* if located at adjacent spot, mark it as having been seen up close (corpse type will be known even if dknown is 0, so we don't need a touch check for cockatrice corpse--we're looking without touching) */ - if (otmp && distu(x, y) <= 2 && !Blind && !Hallucination + if (otmp && next2u(x, y) && !Blind && !Hallucination /* redundant: we only look for an object which matches current glyph among floor and buried objects; when !Blind, any buried object's glyph will have been replaced by whatever is present @@ -228,20 +366,21 @@ struct obj **obj_p; && (fakeobj || otmp->where == OBJ_FLOOR) /* not buried */ /* terrain mode views what's already known, doesn't learn new stuff */ && !iflags.terrainmode) /* so don't set dknown when in terrain mode */ - otmp->dknown = 1; /* if a pile, clearly see the top item only */ - if (fakeobj && mtmp && mimic_obj && - (otmp->dknown || (M_AP_FLAG(mtmp) & M_AP_F_DKNOWN))) { - mtmp->m_ap_type |= M_AP_F_DKNOWN; - otmp->dknown = 1; + observe_object(otmp); /* if a pile, clearly see the top item only */ + if (fakeobj && mtmp && mimic_obj + && (otmp->dknown || (M_AP_FLAG(mtmp) & M_AP_F_DKNOWN))) { + mtmp->m_ap_type |= M_AP_F_DKNOWN; + observe_object(otmp); } *obj_p = otmp; return fakeobj; /* when True, caller needs to dealloc *obj_p */ } -STATIC_OVL void -look_at_object(buf, x, y, glyph) -char *buf; /* output buffer */ -int x, y, glyph; +staticfn void +look_at_object( + char *buf, /* output buffer */ + coordxy x, coordxy y, + int glyph) { struct obj *otmp = 0; boolean fakeobj = object_from_map(glyph, x, y, &otmp); @@ -253,13 +392,19 @@ int x, y, glyph; : obj_descr[STRANGE_OBJECT].oc_name); if (fakeobj) { otmp->where = OBJ_FREE; /* object_from_map set it to OBJ_FLOOR */ - dealloc_obj(otmp), otmp = 0; + dealloc_obj(otmp), otmp = NULL; /* has no contents */ } - } else + } else { Strcpy(buf, something); /* sanity precaution */ + } if (otmp && otmp->where == OBJ_BURIED) Strcat(buf, " (buried)"); + /* check TREE before STONE due to level.flags.arboreal */ + else if (IS_TREE(levl[x][y].typ)) + /* "dangling": "hanging" could imply that it's growing on this tree */ + Snprintf(eos(buf), BUFSZ - strlen(buf), " %s in a tree", + (otmp && is_treefruit(otmp)) ? "dangling" : "stuck"); else if (levl[x][y].typ == STONE || levl[x][y].typ == SCORR) Strcat(buf, " embedded in stone"); else if (IS_WALL(levl[x][y].typ) || levl[x][y].typ == SDOOR) @@ -273,22 +418,23 @@ int x, y, glyph; return; } -STATIC_OVL void -look_at_monster(buf, monbuf, mtmp, x, y) -char *buf, *monbuf; /* buf: output, monbuf: optional output */ -struct monst *mtmp; -int x, y; +staticfn void +look_at_monster( + char *buf, char *monbuf, /* buf: output, monbuf: optional output */ + struct monst *mtmp, + coordxy x, coordxy y) { - char *name, monnambuf[BUFSZ]; + char *name, monnambuf[BUFSZ], healthbuf[BUFSZ]; boolean accurate = !Hallucination; name = (mtmp->data == &mons[PM_COYOTE] && accurate) ? coyotename(mtmp, monnambuf) : distant_monnam(mtmp, ARTICLE_NONE, monnambuf); - Sprintf(buf, "%s%s%s", + Sprintf(buf, "%s%s%s%s", (mtmp->mx != x || mtmp->my != y) ? ((mtmp->isshk && accurate) ? "tail of " : "tail of a ") : "", + accurate ? monhealthdescr(mtmp, TRUE, healthbuf) : "", (mtmp->mtame && accurate) ? "tame " : (mtmp->mpeaceful && accurate) @@ -297,31 +443,44 @@ int x, y; name); if (u.ustuck == mtmp) { if (u.uswallow || iflags.save_uswallow) /* monster detection */ - Strcat(buf, is_animal(mtmp->data) - ? ", swallowing you" : ", engulfing you"); + Strcat(buf, digests(mtmp->data) ? ", swallowing you" + : ", engulfing you"); else - Strcat(buf, (Upolyd && sticks(youmonst.data)) + Strcat(buf, (Upolyd && sticks(gy.youmonst.data)) ? ", being held" : ", holding you"); } + /* if mtmp isn't able to move (other than because it is a type of + monster that never moves), say so [excerpt from mstatusline() for + stethoscope or wand of probing] */ + if (mtmp->mfrozen) + /* unfortunately mfrozen covers temporary sleep and being busy + (donning armor, for instance) as well as paralysis */ + Strcat(buf, ", can't move (paralyzed or sleeping or busy)"); + else if (mtmp->msleeping) + /* sleeping for an indeterminate duration */ + Strcat(buf, ", asleep"); + else if ((mtmp->mstrategy & STRAT_WAITMASK) != 0) + /* arbitrary reason why it isn't moving */ + Strcat(buf, ", meditating"); + if (mtmp->mleashed) Strcat(buf, ", leashed to you"); - if (mtmp->mtrapped && cansee(mtmp->mx, mtmp->my)) { struct trap *t = t_at(mtmp->mx, mtmp->my); int tt = t ? t->ttyp : NO_TRAP; /* newsym lets you know of the trap, so mention it here */ if (tt == BEAR_TRAP || is_pit(tt) || tt == WEB) { - Sprintf(eos(buf), ", trapped in %s", - an(defsyms[trap_to_defsym(tt)].explanation)); + Sprintf(eos(buf), ", trapped in %s", an(trapname(tt, FALSE))); t->tseen = 1; } } /* we know the hero sees a monster at this location, but if it's shown - due to persistant monster detection he might remember something else */ - if (mtmp->mundetected || M_AP_TYPE(mtmp)) - mhidden_description(mtmp, FALSE, eos(buf)); + due to persistent monster detection he might remember something else */ + if (mtmp->mundetected || M_AP_TYPE(mtmp) || visible_region_at(x, y)) + mhidden_description(mtmp, MHID_PREFIX | MHID_ARTICLE | MHID_REGION, + eos(buf)); if (monbuf) { unsigned how_seen = howmonseen(mtmp); @@ -370,14 +529,15 @@ int x, y; if (Hallucination) { Strcat(monbuf, "paranoid delusion"); } else { - unsigned long mW = (context.warntype.obj - | context.warntype.polyd), + unsigned long mW = (svc.context.warntype.obj + | svc.context.warntype.polyd), m2 = mtmp->data->mflags2; const char *whom = ((mW & M2_HUMAN & m2) ? "human" : (mW & M2_ELF & m2) ? "elf" : (mW & M2_ORC & m2) ? "orc" : (mW & M2_DEMON & m2) ? "demon" - : mtmp->data->mname); + : pmname(mtmp->data, + Mgender(mtmp))); Sprintf(eos(monbuf), "warned of %s", makeplural(whom)); } @@ -394,14 +554,107 @@ int x, y; } /* monbuf is non-null */ } +/* describe a pool location's contents; might return a static buffer so + caller should use it or copy it before calling waterbody_name() again + [5.0: moved here from mkmaze.c] */ +const char * +waterbody_name(coordxy x, coordxy y) +{ + static char pooltype[40]; + schar ltyp; + boolean hallucinate = Hallucination && !program_state.gameover; + + if (!isok(x, y)) + return "drink"; /* should never happen */ + ltyp = SURFACE_AT(x, y); + + if (ltyp == LAVAPOOL) { + Snprintf(pooltype, sizeof pooltype, "molten %s", hliquid("lava")); + return pooltype; + } else if (ltyp == ICE) { + if (!hallucinate) + return "ice"; + Snprintf(pooltype, sizeof pooltype, "frozen %s", hliquid("water")); + return pooltype; + } else if (ltyp == POOL) { + Snprintf(pooltype, sizeof pooltype, "pool of %s", hliquid("water")); + return pooltype; + } else if (ltyp == MOAT) { + /* a bit of extra flavor over general moat */ + if (hallucinate) { + Snprintf(pooltype, sizeof pooltype, "deep %s", hliquid("water")); + return pooltype; + } else if (Is_medusa_level(&u.uz)) { + /* somewhat iffy since ordinary stairs can take you beneath, + but previous generic "water" was rather anti-climactic */ + return "shallow sea"; + } else if (Is_juiblex_level(&u.uz)) { + return "swamp"; + } else if (Role_if(PM_SAMURAI) && Is_qstart(&u.uz)) { + /* samurai quest home level has two isolated moat spots; + they sound silly if farlook describes them as such */ + return "pond"; + } else { + return "moat"; + } + } else if (IS_WATERWALL(ltyp)) { + if (Is_waterlevel(&u.uz)) + return "limitless water"; /* even if hallucinating */ + Snprintf(pooltype, sizeof pooltype, "wall of %s", hliquid("water")); + return pooltype; + } else if (ltyp == LAVAWALL) { + Snprintf(pooltype, sizeof pooltype, "wall of %s", hliquid("lava")); + return pooltype; + } + /* default; should be unreachable */ + return "water"; /* don't hallucinate this as some other liquid */ +} + +char * +ice_descr(coordxy x, coordxy y, char *outbuf) +{ + static const char *const icetyp[] = { + "solid", /* 0: not melting */ + "sturdy", /* 1: more than 1000 turns left */ + "steady", /* 2: 101..1000 turns left */ + "unsteady", /* 3: 51..100 turns left */ + "thin", /* 4: 15..50 turns left */ + "slushy", /* 5: 1..14 turns left; matches Warning on ice */ + }; + /* same formula as is used in distant_name() for objects */ + int r = (u.xray_range > 2) ? u.xray_range : 2, + neardist = (r * r) * 2 - r; /* same as r*r + r*(r-1) */ + + iflags.ice_rating = -1; /* secondary output, for 'mention_decor' */ + if (SURFACE_AT(x, y) != ICE) { + Sprintf(outbuf, "[ice:%d?]", (int) levl[x][y].typ); + } else if ((distu(x, y) > neardist + || (!cansee(x, y) && (!u_at(x, y) || Levitation))) + && !gd.decor_levitate_override) { /* probe_decor(pickup.c) */ + Strcpy(outbuf, waterbody_name(x, y)); /* "ice" or "frozen " */ + } else { + long time_left = spot_time_left(x, y, MELT_ICE_AWAY); + + /* other, real ice thickness/strength terminology exists but seems + to be too unfamiliar for nethack's use */ + iflags.ice_rating = !time_left ? 0 /* solid */ + : (time_left > 1000L) ? 1 /* sturdy */ + : (time_left > 100L) ? 2 /* steady */ + : (time_left > 50L) ? 3 /* unsteady */ + : (time_left > 14L) ? 4 /* thin */ + : 5; /* slushy */ + Sprintf(outbuf, "%s %s", icetyp[(int) iflags.ice_rating], + waterbody_name(x, y)); + } + return outbuf; +} + /* * Return the name of the glyph found at (x,y). * If not hallucinating and the glyph is a monster, also monster data. */ -STATIC_OVL struct permonst * -lookat(x, y, buf, monbuf) -int x, y; -char *buf, *monbuf; +staticfn struct permonst * +lookat(coordxy x, coordxy y, char *buf, char *monbuf) { struct monst *mtmp = (struct monst *) 0; struct permonst *pm = (struct permonst *) 0; @@ -409,9 +662,9 @@ char *buf, *monbuf; buf[0] = monbuf[0] = '\0'; glyph = glyph_at(x, y); - if (u.ux == x && u.uy == y && canspotself() - && !(iflags.save_uswallow && - glyph == mon_to_glyph(u.ustuck, rn2_on_display_rng)) + if (u_at(x, y) && canspotself() + && !(iflags.save_uswallow + && glyph == mon_to_glyph(u.ustuck, rn2_on_display_rng)) && (!iflags.terrainmode || (iflags.terrainmode & TER_MON) != 0)) { /* fill in buf[] */ (void) self_lookat(buf); @@ -450,11 +703,9 @@ char *buf, *monbuf; } else if (u.uswallow) { /* when swallowed, we're only called for spots adjacent to hero, and blindness doesn't prevent hero from feeling what holds him */ - Sprintf(buf, "interior of %s", a_monnam(u.ustuck)); + Sprintf(buf, "interior of %s", mon_nam(u.ustuck)); pm = u.ustuck->data; } else if (glyph_is_monster(glyph)) { - bhitpos.x = x; - bhitpos.y = y; if ((mtmp = m_at(x, y)) != 0) { look_at_monster(buf, monbuf, mtmp, x, y); pm = mtmp->data; @@ -465,46 +716,42 @@ char *buf, *monbuf; } else if (glyph_is_object(glyph)) { look_at_object(buf, x, y, glyph); /* fill in buf[] */ } else if (glyph_is_trap(glyph)) { - int tnum = what_trap(glyph_to_trap(glyph), rn2_on_display_rng); + int tnum = glyph_to_trap(glyph); - /* Trap detection displays a bear trap at locations having - * a trapped door or trapped container or both. - * TODO: we should create actual trap types for doors and - * chests so that they can have their own glyphs and tiles. - */ - if (trapped_chest_at(tnum, x, y)) - Strcpy(buf, "trapped chest"); /* might actually be a large box */ - else if (trapped_door_at(tnum, x, y)) - Strcpy(buf, "trapped door"); /* not "trap door"... */ - else - Strcpy(buf, defsyms[trap_to_defsym(tnum)].explanation); + trap_description(buf, tnum, x, y); } else if (glyph_is_warning(glyph)) { int warnindx = glyph_to_warning(glyph); Strcpy(buf, def_warnsyms[warnindx].explanation); - } else if (!glyph_is_cmap(glyph)) { - Strcpy(buf, "unexplored area"); - } else { + } else if (glyph_is_invisible(glyph)) { + Strcpy(buf, invisexplain); /* redundant; handled by caller */ + } else if (glyph_is_nothing(glyph)) { + Strcpy(buf, "dark part of a room"); + } else if (glyph_is_unexplored(glyph)) { + if (Underwater && !Is_waterlevel(&u.uz)) { + /* "unknown" == previously mapped but not visible when + submerged; better terminology appreciated... */ + Strcpy(buf, (next2u(x, y)) ? "land" : "unknown"); + } else { + Strcpy(buf, "unexplored area"); + } + } else if (glyph_is_cmap(glyph)) { int amsk; aligntyp algn; + short symidx = glyph_to_cmap(glyph); - switch (glyph_to_cmap(glyph)) { + switch (symidx) { case S_altar: - amsk = ((mtmp = m_at(x, y)) != 0 && has_mcorpsenm(mtmp) - && M_AP_TYPE(mtmp) == M_AP_FURNITURE - && mtmp->mappearance == S_altar) ? MCORPSENM(mtmp) - : levl[x][y].altarmask; - algn = Amask2align(amsk & ~AM_SHRINE); + amsk = altarmask_at(x, y); + algn = Amask2align(amsk & AM_MASK); Sprintf(buf, "%s %saltar", /* like endgame high priests, endgame high altars are only recognizable when immediately adjacent */ - (Is_astralevel(&u.uz) && distu(x, y) > 2) + (Is_astralevel(&u.uz) && !next2u(x, y) + && (amsk & AM_SANCTUM)) ? "aligned" : align_str(algn), - ((amsk & AM_SHRINE) != 0 - && (Is_astralevel(&u.uz) || Is_sanctum(&u.uz))) - ? "high " - : ""); + (amsk & AM_SANCTUM) ? "high " : ""); break; case S_ndoor: if (is_drawbridge_wall(x, y) >= 0) @@ -518,6 +765,17 @@ char *buf, *monbuf; Strcpy(buf, Is_airlevel(&u.uz) ? "cloudy area" : "fog/vapor cloud"); break; + case S_pool: + case S_water: /* was Plane of Water, now that or "wall of water" */ + case S_lava: + case S_lavawall: + case S_ice: /* for hallucination; otherwise defsyms[] would be fine */ + Strcpy(buf, waterbody_name(x, y)); + break; + case S_engroom: + case S_engrcorr: + Strcpy(buf, "engraving"); + break; case S_stone: if (!levl[x][y].seenv) { Strcpy(buf, "unexplored"); @@ -525,21 +783,37 @@ char *buf, *monbuf; } else if (Underwater && !Is_waterlevel(&u.uz)) { /* "unknown" == previously mapped but not visible when submerged; better terminology appreciated... */ - Strcpy(buf, (distu(x, y) <= 2) ? "land" : "unknown"); + Strcpy(buf, (next2u(x, y)) ? "land" : "unknown"); break; } else if (levl[x][y].typ == STONE || levl[x][y].typ == SCORR) { Strcpy(buf, "stone"); break; } + FALLTHROUGH; /*FALLTHRU*/ default: - Strcpy(buf, defsyms[glyph_to_cmap(glyph)].explanation); + Strcpy(buf, defsyms[symidx].explanation); break; } + } else { /* not mon, obj, trap, or cmap */ + Strcpy(buf, "unexplored area"); } return (pm && !Hallucination) ? pm : (struct permonst *) 0; } +/* used to decide whether the context-sensitive inventory action menu for + item 'otmp' should include the "/ - look up this item" choice */ +boolean +ia_checkfile(struct obj *otmp) +{ + char itemnam[BUFSZ]; + + /* singular() of xname() of otmp is what "/i" looks up */ + Strcpy(itemnam, singular(otmp, xname)); + return checkfile(itemnam, (struct permonst *) 0, + chkfilIaCheck | chkfilDontAsk, (char *) 0); +} + /* * Look in the "data" file for more info. Called if the user typed in the * whole name (user_typed_name == TRUE), or we've found a possible match @@ -549,24 +823,30 @@ char *buf, *monbuf; * must not be changed directly, e.g. via lcase(). We want to force * lcase() for data.base lookup so that we can have a clean key. * Therefore, we create a copy of inp _just_ for data.base lookup. + * + * Returns True if an entry is found, False otherwise. */ -STATIC_OVL void -checkfile(inp, pm, user_typed_name, without_asking, supplemental_name) -char *inp; -struct permonst *pm; -boolean user_typed_name, without_asking; -char *supplemental_name; +staticfn boolean +checkfile( + char *inp, /* string to look up */ + struct permonst *pm, /* monster type to look up (overrides 'inp') */ + unsigned chkflags, + char *supplemental_name) { dlb *fp; char buf[BUFSZ], newstr[BUFSZ], givenname[BUFSZ]; char *ep, *dbase_str; + boolean user_typed_name = (chkflags & chkfilUsrTyped) != 0, + without_asking = (chkflags & chkfilDontAsk) != 0, + ia_checking = (chkflags & chkfilIaCheck) != 0; unsigned long txt_offset = 0L; winid datawin = WIN_ERR; + boolean res = FALSE; fp = dlb_fopen(DATAFILE, "r"); if (!fp) { pline("Cannot open 'data' file!"); - return; + return res; } /* If someone passed us garbage, prevent fault. */ if (!inp || strlen(inp) > (BUFSZ - 1)) { @@ -580,7 +860,7 @@ char *supplemental_name; * user_typed_name and picked name. */ if (pm != (struct permonst *) 0 && !user_typed_name) - dbase_str = strcpy(newstr, pm->mname); + dbase_str = strcpy(newstr, pm->pmnames[NEUTRAL]); else dbase_str = strcpy(newstr, inp); (void) lcase(dbase_str); @@ -588,7 +868,7 @@ char *supplemental_name; /* * TODO: * The switch from xname() to doname_vague_quan() in look_at_obj() - * had the unintendded side-effect of making names picked from + * had the unintended side-effect of making names picked from * pointing at map objects become harder to simplify for lookup. * We should split the prefix and suffix handling used by wish * parsing and also wizmode monster generation out into separate @@ -643,7 +923,7 @@ char *supplemental_name; /* remove enchantment ("+0 aklys"); [for 3.6.0 and earlier, this wasn't needed because looking at items on the map used xname() rather than doname() hence known enchantment was implicitly suppressed] */ - if (*dbase_str && index("+-", dbase_str[0]) && digit(dbase_str[1])) { + if (*dbase_str && strchr("+-", dbase_str[0]) && digit(dbase_str[1])) { ++dbase_str; /* skip sign */ while (digit(*dbase_str)) ++dbase_str; @@ -656,7 +936,7 @@ char *supplemental_name; (note: strncpy() only terminates output string if the specified count is bigger than the length of the substring being copied) */ if (!strncmp(dbase_str, "moist towel", 11)) - (void) strncpy(dbase_str += 2, "wet", 3); /* skip "mo" replace "ist" */ + memcpy(dbase_str += 2, "wet", 3); /* skip "mo" replace "ist" */ /* Make sure the name is non-empty. */ if (*dbase_str) { @@ -687,13 +967,19 @@ char *supplemental_name; if (alt && (!strncmpi(alt, "a ", 2) || !strncmpi(alt, "an ", 3) || !strncmpi(alt, "the ", 4))) - alt = index(alt, ' ') + 1; + alt = strchr(alt, ' ') + 1; /* remove charges or "(lit)" or wizmode "(N aum)" */ if ((ep = strstri(dbase_str, " (")) != 0 && ep > dbase_str) *ep = '\0'; if (alt && (ap = strstri(alt, " (")) != 0 && ap > alt) *ap = '\0'; + /* If the object's name matches the player-specified fruitname, + then "fruit" is the alternate description. We do this here so that + if the fruit name is an extant object, looking at the fruit yields + that object's description. */ + if (!alt && fruit_from_name(dbase_str, TRUE, (int *) 0)) + alt = strcpy(newstr, obj_descr[SLIME_MOLD].oc_name); /* * If the object is named, then the name is the alternate description; * otherwise, the result of makesingular() applied to the name is. @@ -701,7 +987,7 @@ char *supplemental_name; * user will usually be found under their name, rather than under * their object type, so looking for a singular form is pointless. */ - if (!alt) + else if (!alt) alt = makesingular(dbase_str); pass1found_in_file = FALSE; @@ -729,7 +1015,7 @@ char *supplemental_name; /* a number indicates the end of current entry */ skipping_entry = FALSE; } else if (!skipping_entry) { - if (!(ep = index(buf, '\n'))) + if (!(ep = strchr(buf, '\n'))) goto bad_data_file; (void) strip_newline((ep > buf) ? ep - 1 : ep); /* if we match a key that begins with "~", skip @@ -778,7 +1064,7 @@ char *supplemental_name; (int) (sizeof question - 1 - (strlen(question) + 2))); Strcat(question, "\"?"); - if (yn(question) == 'y') + if (y_n(question) == 'y') yes_to_moreinfo = TRUE; } @@ -787,20 +1073,48 @@ char *supplemental_name; pline("? Seek error on 'data' file!"); goto checkfile_done; } + res = TRUE; + if (ia_checking) + goto checkfile_done; + datawin = create_nhwindow(NHW_MENU); for (i = 0; i < entry_count; i++) { - if (!dlb_fgets(buf, BUFSZ, fp)) + /* room for 1-tab or 8-space prefix + BUFSZ-1 + \0 */ + char tabbuf[BUFSZ + 8], *tp; + + if (!dlb_fgets(tabbuf, BUFSZ, fp)) + goto bad_data_file; + tp = tabbuf; + if (!strchr(tp, '\n')) goto bad_data_file; - (void) strip_newline(buf); - if (index(buf + 1, '\t') != 0) - (void) tabexpand(buf + 1); - putstr(datawin, 0, buf + 1); + (void) strip_newline(tp); + /* text in this file is indented with one tab but + someone modifying it might use spaces instead */ + if (*tp == '\t') { + ++tp; + } else if (*tp == ' ') { + /* remove up to 8 spaces (we expect 8-column + tab stops but user might have them set at + something else so we don't require it) */ + do { + ++tp; + } while (tp < &tabbuf[8] && *tp == ' '); + } else if (*tp) { /* empty lines are ok */ + goto bad_data_file; + } + /* if a tab after the leading one is found, + convert tabs into spaces; the attributions + at the end of quotes typically have them */ + if (strchr(tp, '\t') != 0) + (void) tabexpand(tp); + putstr(datawin, 0, tp); } display_nhwindow(datawin, FALSE); destroy_nhwindow(datawin), datawin = WIN_ERR; } - } else if (user_typed_name && pass == 0 && !pass1found_in_file) - pline("I don't have any information on those things."); + } else if (user_typed_name && pass == 0 && !pass1found_in_file) { + pline("You don't have any information on those things."); + } } } goto checkfile_done; /* skip error feedback */ @@ -811,38 +1125,150 @@ char *supplemental_name; if (datawin != WIN_ERR) destroy_nhwindow(datawin); (void) dlb_fclose(fp); - return; + return res; +} + +/* extracted from do_screen_description() */ +staticfn int +add_cmap_descr( + int found, /* number of matching descriptions so far */ + int idx, /* cmap index into defsyms[] */ + int glyph, /* map glyph of screen symbol being described; + * anything other than NO_GLYPH implies 'looked' */ + int article, /* 0: (none), 1: a/an, 2: the */ + coord cc, /* map location */ + const char *x_str, /* description of defsyms[idx] */ + const char *prefix, /* text to insert in front of first match */ + boolean *hit_trap, /* input/output: True if a trap has been described */ + const char **firstmatch, /* output: pointer to 1st matching description */ + char *out_str) /* input/output: current description gets appended */ +{ + char *mbuf = NULL; + const char *p; + int absidx = abs(idx); + + if (glyph == NO_GLYPH) { + /* use x_str [almost] as-is */ + if (!strcmp(x_str, "water")) { + /* duplicate some transformations performed by waterbody_name() */ + if (idx == S_pool) + x_str = "pool of water"; + else if (idx == S_water) + x_str = !Is_waterlevel(&u.uz) ? "wall of water" + : "limitless water"; + } + if (absidx == S_pool) + idx = S_pool; + } else if (absidx == S_pool || idx == S_water + || idx == S_lava || idx == S_lavawall || idx == S_ice) { + /* replace some descriptions (x_str) with waterbody_name() */ + schar save_ltyp = levl[cc.x][cc.y].typ; + long save_prop = EHalluc_resistance; + + /* grab a scratch buffer we can safely return (via *firstmatch + when applicable) */ + mbuf = mon_nam(&gy.youmonst); + + if (absidx == S_pool) { + levl[cc.x][cc.y].typ = (idx == S_pool) ? POOL : MOAT; + idx = S_pool; /* force fake negative moat value to be positive */ + } else { + /* we might be examining a pool location but trying to match + water or lava; override the terrain with what we're matching + because that's what waterbody_name() bases its result on; + it's not pool so must be one of water/lava/ice to get here */ + levl[cc.x][cc.y].typ = (idx == S_water) ? WATER + : (idx == S_lava) ? LAVAPOOL + : (idx == S_lavawall) ? LAVAWALL + : ICE; + } + EHalluc_resistance = 1; + Strcpy(mbuf, waterbody_name(cc.x, cc.y)); + EHalluc_resistance = save_prop; + levl[cc.x][cc.y].typ = save_ltyp; + + /* shorten the feedback for farlook/quicklook: "pool or ..." */ + if (!strcmp(mbuf, "pool of water")) + mbuf[4] = '\0'; + else if (!strcmp(mbuf, "molten lava")) + Strcpy(mbuf, "lava"); + x_str = mbuf; + /* avoid "an ice" and so forth; "a pool", "a moat", and + "a wall of ..." are grammatically correct but make + "a pool or a moat or a wall of water" become too verbose */ + article = !(!strncmp(x_str, "water", 5) + || !strncmp(x_str, "ice", 3) + || !strncmp(x_str, "pool", 4) + || !strncmp(x_str, "moat", 4) + || !strncmp(x_str, "lava", 4) + || !strncmp(x_str, "swamp", 5) + || !strncmp(x_str, "molten", 6) + || !strncmp(x_str, "shallow", 7) + || !strncmp(x_str, "limitless", 9) + || !strncmp(x_str, "wall of lava", 12) + || !strncmp(x_str, "wall of water", 13) + /* ice while hallucinating */ + || !strncmp(x_str, "frozen", 6) + /* thawing ice ("solid ice", "thin ice", &c) */ + || ((p = strchr(x_str, ' ')) != 0 && !strcmpi(p, " ice")) + ); + } + + if (!found) { + /* this is the first match */ + if (is_cmap_trap(idx) && idx != S_vibrating_square) { + Sprintf(out_str, "%sa trap", prefix); + *hit_trap = TRUE; + } else { + Sprintf(out_str, "%s%s", prefix, (article == 2) ? the(x_str) + : (article == 1) ? an(x_str) + : x_str); + } + *firstmatch = x_str; + found = 1; + } else if (!(*hit_trap && is_cmap_trap(idx)) + && !(found >= 3 && is_cmap_drawbridge(idx)) + /* don't mention vibrating square outside of Gehennom + unless this happens to be one (hallucination?) */ + && (idx != S_vibrating_square || Inhell + || (glyph_is_trap(glyph) + && glyph_to_trap(glyph) == VIBRATING_SQUARE))) { + /* append unless out_str already contains the string to append */ + found += append_str(out_str, (article == 2) ? the(x_str) + : (article == 1) ? an(x_str) + : x_str); + if (is_cmap_trap(idx) && idx != S_vibrating_square) + *hit_trap = TRUE; + } + return found; } int -do_screen_description(cc, looked, sym, out_str, firstmatch, for_supplement) -coord cc; -boolean looked; -int sym; -char *out_str; -const char **firstmatch; -struct permonst **for_supplement; +do_screen_description( + coord cc, boolean looked, + int sym, char *out_str, + const char **firstmatch, + struct permonst **for_supplement) { static const char mon_interior[] = "the interior of a monster", unreconnoitered[] = "unreconnoitered"; static char look_buf[BUFSZ]; char prefix[BUFSZ]; - int i, alt_i, j, glyph = NO_GLYPH, + int i, j, alt_i, glyph = NO_GLYPH, skipped_venom = 0, found = 0; /* count of matching syms found */ boolean hit_trap, need_to_look = FALSE, - submerged = (Underwater && !Is_waterlevel(&u.uz)); + submerged = (Underwater && !Is_waterlevel(&u.uz)), + hallucinate = (Hallucination && !program_state.gameover); const char *x_str; nhsym tmpsym; + glyph_info glyphinfo = nul_glyphinfo; if (looked) { - int oc; - unsigned os; - glyph = glyph_at(cc.x, cc.y); /* Convert glyph at selected position to a symbol for use below. */ - (void) mapglyph(glyph, &sym, &oc, &os, cc.x, cc.y, 0); - - Sprintf(prefix, "%s ", encglyph(glyph)); + map_glyphinfo(cc.x, cc.y, glyph, 0, &glyphinfo); + sym = glyphinfo.ttychar; + Sprintf(prefix, "%s ", encglyph(glyphinfo.glyph)); } else Sprintf(prefix, "%c ", sym); @@ -870,7 +1296,7 @@ struct permonst **for_supplement; x_str = 0; if (!looked) { ; /* skip special handling */ - } else if (((u.uswallow || submerged) && distu(cc.x, cc.y) > 2) + } else if (((u.uswallow || submerged) && !next2u(cc.x, cc.y)) /* detection showing some category, so mostly background */ || ((iflags.terrainmode & (TER_DETECT | TER_MAP)) == TER_DETECT && glyph == cmap_to_glyph(S_stone))) { @@ -896,11 +1322,15 @@ struct permonst **for_supplement; if (x_str == unreconnoitered) goto didlook; } + check_monsters: /* Check for monsters */ if (!iflags.terrainmode || (iflags.terrainmode & TER_MON) != 0) { for (i = 1; i < MAXMCLASSES; i++) { - if (sym == (looked ? showsyms[i + SYM_OFF_M] : def_monsyms[i].sym) + if (i == S_invisible) /* avoid matching on this */ + continue; + if (sym == (looked ? gs.showsyms[i + SYM_OFF_M] + : def_monsyms[i].sym) && def_monsyms[i].explain && *def_monsyms[i].explain) { need_to_look = TRUE; if (!found) { @@ -916,8 +1346,8 @@ struct permonst **for_supplement; /* handle '@' as a special case if it refers to you and you're playing a character which isn't normally displayed by that symbol; firstmatch is assumed to already be set for '@' */ - if ((looked ? (sym == showsyms[S_HUMAN + SYM_OFF_M] - && cc.x == u.ux && cc.y == u.uy) + if ((looked ? (sym == gs.showsyms[S_HUMAN + SYM_OFF_M] + && u_at(cc.x, cc.y)) : (sym == def_monsyms[S_HUMAN].sym && !flags.showrace)) && !(Race_if(PM_HUMAN) || Race_if(PM_ELF)) && !Upolyd) found += append_str(out_str, "you"); /* tack on "or you" */ @@ -925,32 +1355,59 @@ struct permonst **for_supplement; /* Now check for objects */ if (!iflags.terrainmode || (iflags.terrainmode & TER_OBJ) != 0) { + const char *oc_ptr; + nhsym bouldersym; + + j = SYM_BOULDER + SYM_OFF_X; + bouldersym = Is_rogue_level(&u.uz) ? go.ov_rogue_syms[j] + : go.ov_primary_syms[j]; + if (!bouldersym) + bouldersym = def_oc_syms[ROCK_CLASS].sym; + for (i = 1; i < MAXOCLASSES; i++) { - if (sym == (looked ? showsyms[i + SYM_OFF_O] - : def_oc_syms[i].sym) - || (looked && i == ROCK_CLASS && glyph_is_statue(glyph))) { + if ((i != ROCK_CLASS) + ? (sym == (looked ? gs.showsyms[i + SYM_OFF_O] + : def_oc_syms[i].sym)) + /* ROCK_CLASS is complicated; statues are displayed as the + monster they depict rather than as S_rock; boulders might + be displayed as a custom symbol rather than as S_rock */ + : (glyph_is_statue(glyph) || sym == bouldersym)) { + oc_ptr = def_oc_syms[i].explain; + /* for added fun, engravings are shown with the same symbol + as S_rock which is why we want to shorten this */ + if (i == ROCK_CLASS && !strcmp(oc_ptr, "boulder or statue")) { + if (sym == bouldersym) + oc_ptr = "boulder"; /* discard "or statue" */ + else if (glyph_is_statue(glyph)) + oc_ptr = "statue"; /* discard "boulder or" */ + else if (looked) + continue; /* discard both */ + } need_to_look = TRUE; if (looked && i == VENOM_CLASS) { skipped_venom++; continue; } if (!found) { - Sprintf(out_str, "%s%s", - prefix, an(def_oc_syms[i].explain)); - *firstmatch = def_oc_syms[i].explain; + Sprintf(out_str, "%s%s", prefix, an(oc_ptr)); + /* note: if the value assigned to *firstmatch ever + becomes dynamically constructed, it will need to be + copied into a static buffer; as of now, all alternate + values are string literals and implicitly static */ + *firstmatch = oc_ptr; found++; } else { - found += append_str(out_str, an(def_oc_syms[i].explain)); + found += append_str(out_str, an(oc_ptr)); } } } } if (sym == DEF_INVISIBLE) { - extern const char altinvisexplain[]; /* drawing.c */ /* for active clairvoyance, use alternate "unseen creature" */ boolean usealt = (EDetect_monsters & I_SPECIAL) != 0L; - const char *unseen_explain = !usealt ? invisexplain : altinvisexplain; + const char *unseen_explain = (usealt || Blind) ? altinvisexplain + : invisexplain; if (!found) { Sprintf(out_str, "%s%s", prefix, an(unseen_explain)); @@ -960,60 +1417,93 @@ struct permonst **for_supplement; found += append_str(out_str, an(unseen_explain)); } } - - /* Now check for graphics symbols */ - alt_i = (sym == (looked ? showsyms[0] : defsyms[0].sym)) ? 0 : (2 + 1); - for (hit_trap = FALSE, i = 0; i < MAXPCHARS; i++) { - /* when sym is the default background character, we process - i == 0 three times: unexplored, stone, dark part of a room */ - if (alt_i < 2) { - x_str = !alt_i++ ? "unexplored" : submerged ? "unknown" : "stone"; - i = 0; /* for second iteration, undo loop increment */ - /* alt_i is now 1 or 2 */ + if ((glyph && glyph_is_nothing(glyph)) + || (looked && sym == gs.showsyms[SYM_NOTHING + SYM_OFF_X])) { + x_str = "the dark part of a room"; + if (!found) { + Sprintf(out_str, "%s%s", prefix, x_str); + *firstmatch = x_str; + found++; + } else { + found += append_str(out_str, x_str); + } + } + if ((glyph && glyph_is_unexplored(glyph)) + || (looked && sym == gs.showsyms[SYM_UNEXPLORED + SYM_OFF_X])) { + x_str = "unexplored"; + if (submerged) + x_str = "land"; /* replace "unexplored" */ + if (!found) { + Sprintf(out_str, "%s%s", prefix, x_str); + *firstmatch = x_str; + found++; } else { - if (alt_i++ == 2) - i = 0; /* undo loop increment */ - x_str = defsyms[i].explanation; - if (submerged && !strcmp(x_str, defsyms[0].explanation)) - x_str = "land"; /* replace "dark part of a room" */ - /* alt_i is now 3 or more and no longer of interest */ + found += append_str(out_str, x_str); } - if (sym == (looked ? showsyms[i] : defsyms[i].sym) && *x_str) { - /* avoid "an unexplored", "an stone", "an air", "a water", - "a floor of a room", "a dark part of a room"; - article==2 => "the", 1 => "an", 0 => (none) */ - int article = strstri(x_str, " of a room") ? 2 - : !(alt_i <= 2 - || strcmp(x_str, "air") == 0 - || strcmp(x_str, "land") == 0 - || strcmp(x_str, "water") == 0); + } + /* Now check for graphics symbols */ + for (hit_trap = FALSE, i = 0; i < MAXPCHARS; i++) { + /* + * Index hackery: we want + * "pool or moat or wall of water or lava or wall of lava" + * rather than + * "pool or moat or lava or wall of lava or wall of water" + * but S_lava comes before S_water so 'i' reaches it sooner. + * Use 'alt_i' for the rest of the loop to behave as if their + * places were swapped. + * This was much simpler when it just exchanged water and lava. + * Now it rotates water to the first of (lava, lavawall, water) + * lava to the middle of (lava, lavawall, water), and lavawall + * to last of (lava, lavawall, water); other values are used + * as-is. + * If S_water (and corresponding tile) were renumbered, this + * hackery could go away. + */ + alt_i = (i == S_lava) ? S_water /* do water first (of these 3) */ + : (i == S_lavawall) ? S_lava /* process lava second */ + : (i == S_water) ? S_lavawall /* and wall of lava third */ + : i; /* other; handle in defsyms[] order */ + x_str = defsyms[alt_i].explanation; + /* cmap includes beams, shield effects, swallow boundaries, and + explosions; skip all of those */ + if (!*x_str) + continue; - if (!found) { - if (is_cmap_trap(i)) { - Sprintf(out_str, "%sa trap", prefix); - hit_trap = TRUE; - } else { - Sprintf(out_str, "%s%s", prefix, - article == 2 ? the(x_str) - : article == 1 ? an(x_str) : x_str); - } - *firstmatch = x_str; - found++; - } else if (!(hit_trap && is_cmap_trap(i)) - && !(found >= 3 && is_cmap_drawbridge(i)) - /* don't mention vibrating square outside of Gehennom - unless this happens to be one (hallucination?) */ - && (i != S_vibrating_square || Inhell - || (looked && glyph_is_trap(glyph) - && glyph_to_trap(glyph) == VIBRATING_SQUARE))) { - found += append_str(out_str, (article == 2) ? the(x_str) - : (article == 1) ? an(x_str) - : x_str); - if (is_cmap_trap(i)) - hit_trap = TRUE; + if (sym == (looked ? gs.showsyms[alt_i] : defsyms[alt_i].sym)) { + int article; /* article==2 => "the", 1 => "an", 0 => (none) */ + + /* check if dark part of a room was already included above */ + if (alt_i == S_darkroom && glyph && glyph_is_nothing(glyph)) + continue; + + /* avoid "an unexplored", "an stone", "an air", + "a floor of a room", "a dark part of a room" */ + article = strstri(x_str, " of a room") ? 2 + : !(alt_i == S_stone + || strcmp(x_str, "air") == 0 + || strcmp(x_str, "land") == 0); + + found = add_cmap_descr(found, alt_i, glyph, article, + cc, x_str, prefix, + &hit_trap, firstmatch, out_str); + if (alt_i == S_pool) { + /* "pool of water" and "moat" use the same symbol and glyph + but have different descriptions; when handling pool, add + it a second time for moat but pass an alternate symbol; + skip incrementing 'found' to avoid "can be many things" */ + (void) add_cmap_descr(found, -S_pool, glyph, 1, + cc, "moat", prefix, + &hit_trap, firstmatch, out_str); + need_to_look = TRUE; } - if (i == S_altar || is_cmap_trap(i)) + if (alt_i == S_altar || is_cmap_trap(alt_i) + || (hallucinate && (alt_i == S_water /* S_pool already done */ + || alt_i == S_lava + || alt_i == S_lavawall + || alt_i == S_ice)) + || alt_i == S_engroom || alt_i == S_engrcorr + || alt_i == S_grave) /* 'need_to_look' to report engraving */ need_to_look = TRUE; } } @@ -1021,13 +1511,13 @@ struct permonst **for_supplement; /* Now check for warning symbols */ for (i = 1; i < WARNCOUNT; i++) { x_str = def_warnsyms[i].explanation; - if (sym == (looked ? warnsyms[i] : def_warnsyms[i].sym)) { + if (sym == (looked ? gw.warnsyms[i] : def_warnsyms[i].sym)) { if (!found) { - Sprintf(out_str, "%s%s", prefix, def_warnsyms[i].explanation); - *firstmatch = def_warnsyms[i].explanation; + Sprintf(out_str, "%s%s", prefix, x_str); + *firstmatch = x_str;; found++; } else { - found += append_str(out_str, def_warnsyms[i].explanation); + found += append_str(out_str, x_str); } /* Kludge: warning trumps boulders on the display. Reveal the boulder too or player can get confused */ @@ -1051,51 +1541,41 @@ struct permonst **for_supplement; /* Finally, handle some optional overriding symbols */ for (j = SYM_OFF_X; j < SYM_MAX; ++j) { - if (j == (SYM_INVISIBLE + SYM_OFF_X)) + if (j == SYM_INVISIBLE + SYM_OFF_X || j == SYM_BOULDER + SYM_OFF_X) continue; /* already handled above */ - tmpsym = Is_rogue_level(&u.uz) ? ov_rogue_syms[j] - : ov_primary_syms[j]; + tmpsym = Is_rogue_level(&u.uz) ? go.ov_rogue_syms[j] + : go.ov_primary_syms[j]; if (tmpsym && sym == tmpsym) { switch (j) { - case SYM_BOULDER + SYM_OFF_X: +#if 0 + case SYM_BOULDER + SYM_OFF_X: { + static const char boulder[] = "boulder"; + if (!found) { - *firstmatch = "boulder"; + *firstmatch = boulder; Sprintf(out_str, "%s%s", prefix, an(*firstmatch)); found++; } else { - found += append_str(out_str, "boulder"); + found += append_str(out_str, boulder); } break; + } +#endif case SYM_PET_OVERRIDE + SYM_OFF_X: if (looked) { - int oc = 0; - unsigned os = 0; - /* convert to symbol without override in effect */ - (void) mapglyph(glyph, &sym, &oc, &os, - cc.x, cc.y, MG_FLAG_NOOVERRIDE); + map_glyphinfo(cc.x, cc.y, glyph, MG_FLAG_NOOVERRIDE, + &glyphinfo); + sym = glyphinfo.ttychar; goto check_monsters; } break; case SYM_HERO_OVERRIDE + SYM_OFF_X: - sym = showsyms[S_HUMAN + SYM_OFF_M]; + sym = gs.showsyms[S_HUMAN + SYM_OFF_M]; goto check_monsters; } } } -#if 0 - /* handle optional boulder symbol as a special case */ - if (o_syms[SYM_BOULDER + SYM_OFF_X] - && sym == o_syms[SYM_BOULDER + SYM_OFF_X]) { - if (!found) { - *firstmatch = "boulder"; - Sprintf(out_str, "%s%s", prefix, an(*firstmatch)); - found++; - } else { - found += append_str(out_str, "boulder"); - } - } -#endif /* * If we are looking at the screen, follow multiple possibilities or @@ -1110,7 +1590,7 @@ struct permonst **for_supplement; didlook: if (looked) { - struct permonst *pm = (struct permonst *)0; + struct permonst *pm = (struct permonst *) 0; if (found > 1 || need_to_look) { char monbuf[BUFSZ]; @@ -1119,15 +1599,24 @@ struct permonst **for_supplement; pm = lookat(cc.x, cc.y, look_buf, monbuf); if (pm && for_supplement) *for_supplement = pm; - *firstmatch = look_buf; + if (!strcmp(look_buf, "ice")) + (void) ice_descr(cc.x, cc.y, look_buf); + if (!strcmp(look_buf, "staircase down") + && on_level(&u.uz, &qstart_level) && !ok_to_quest()) + Strcpy(look_buf, "blocked staircase down"); + + if (look_buf[0] != '\0') + *firstmatch = look_buf; if (*(*firstmatch)) { - Sprintf(temp_buf, " (%s)", *firstmatch); + Sprintf(temp_buf, " (%s", *firstmatch); + (void) add_quoted_engraving(cc.x, cc.y, temp_buf, FALSE); + Strcat(temp_buf, ")"); (void) strncat(out_str, temp_buf, BUFSZ - strlen(out_str) - 1); found = 1; /* we have something to look up */ } if (monbuf[0]) { - Sprintf(temp_buf, " [seen: %s]", monbuf); + Snprintf(temp_buf, sizeof temp_buf, " [seen: %s]", monbuf); (void) strncat(out_str, temp_buf, BUFSZ - strlen(out_str) - 1); } @@ -1137,17 +1626,56 @@ struct permonst **for_supplement; return found; } -/* also used by getpos hack in do_name.c */ -const char what_is_an_unknown_object[] = "an unknown object"; +/* when farlook is reporting on an engraving, include its text */ +staticfn boolean +add_quoted_engraving( + coordxy x, coordxy y, + char *buf, + boolean force) /* True: '/e' or '/E', False: '//' or ';' */ +{ + char temp_buf[BUFSZ]; + struct engr *ep = engr_at(x, y); + boolean floorengr = !strcmp(buf, " (engraving"), + headstone = !strcmp(buf, " (grave"); + + /* + * If there is no engraving here, there's nothing to do; just return. + * + * When buf[] is " (engraving" or " (grave" then we're looking at an + * engraving and we'll add its text. Caller supplies the closing paren. + * + * If buf[] contains anything else, we're looking at something (monster + * or object) that happens to be on top of an engraving, so we won't + * append the engraving text. + */ + if (!ep) + return FALSE; + + if (!floorengr && !headstone && !force) + return FALSE; + + if (ep->eread) + Snprintf(temp_buf, sizeof temp_buf, " with %s: \"%s\"", + headstone ? "headstone reading" : "remembered text", + ep->engr_txt[remembered_text]); + else + Snprintf(temp_buf, sizeof temp_buf, " %s you haven't read", + headstone ? "whose headstone" : "that"); + + (void) strncat(buf, temp_buf, BUFSZ - strlen(buf) - 1); + return TRUE; +} + +/* also used by getpos hack in getpos.c */ +const char what_is_a_location[] = "a monster, object or location"; int -do_look(mode, click_cc) -int mode; -coord *click_cc; +do_look(int mode, coord *click_cc) { boolean quick = (mode == 1); /* use cursor; don't search for "more info" */ boolean clicklook = (mode == 2); /* right mouse-click method */ char out_str[BUFSZ] = DUMMY; + struct _cmd_queue cq, *cmdq; const char *firstmatch = 0; struct permonst *pm = 0, *supplemental_pm = 0; int i = '\0', ans = 0; @@ -1156,61 +1684,121 @@ coord *click_cc; coord cc; /* screen pos of unknown glyph */ boolean save_verbose; /* saved value of flags.verbose */ boolean from_screen; /* question from the screen */ + int clr = NO_COLOR; cc.x = 0; cc.y = 0; + if ((cmdq = cmdq_pop()) != 0) { + cq = *cmdq; + free((genericptr_t) cmdq); + if (cq.typ == CMDQ_KEY) + i = cq.key; + else + cmdq_clear(CQ_CANNED); + goto dowhatiscmd; + } + if (!clicklook) { if (quick) { - from_screen = TRUE; /* yes, we want to use the cursor */ i = 'y'; } else { menu_item *pick_list = (menu_item *) 0; winid win; anything any; - any = zeroany; + any = cg.zeroany; win = create_nhwindow(NHW_MENU); - start_menu(win); + start_menu(win, MENU_BEHAVE_STANDARD); + + /* + * Originally this was just a y|n question about whether to + * use the cursor or to type a word. When other choices were + * added, it was changed to be a menu. Using 'y' and 'n' as + * unshown accelerators keeps backwards compatibility with + * the old y|n behavior. + * + * Initially the menu included a third choice and always used + * 'a', 'b', and 'c'. Then it was changed to be controlled by + * the 'lootabc' option instead, defaulting to '/', 'i', '?' + * when that's false. Eventually additional entries have been + * introduced. + * + * When lootabc is set, abandon the 'y'|'n' compatibility in + * favor of newer '/' and '?' compatibility instead. + */ + any.a_char = '/'; - /* 'y' and 'n' to keep backwards compatibility with previous - versions: "Specify unknown object by cursor?" */ - add_menu(win, NO_GLYPH, &any, - flags.lootabc ? 0 : any.a_char, 'y', ATR_NONE, - "something on the map", MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, + flags.lootabc ? 0 : any.a_char, + flags.lootabc ? '/' : 'y', ATR_NONE, + clr, "something on the map", MENU_ITEMFLAGS_NONE); any.a_char = 'i'; - add_menu(win, NO_GLYPH, &any, + add_menu(win, &nul_glyphinfo, &any, + /* [don't use 'i' as lootabc group accelerator because + it will make the regular 'i' choice inaccessible] */ flags.lootabc ? 0 : any.a_char, 0, ATR_NONE, - "something you're carrying", MENU_UNSELECTED); + clr, "something you're carrying", MENU_ITEMFLAGS_NONE); any.a_char = '?'; - add_menu(win, NO_GLYPH, &any, - flags.lootabc ? 0 : any.a_char, 'n', ATR_NONE, - "something else (by symbol or name)", MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, + flags.lootabc ? 0 : any.a_char, + flags.lootabc ? '?' : 'n', ATR_NONE, + clr, "something else (by symbol or name)", + MENU_ITEMFLAGS_NONE); if (!u.uswallow && !Hallucination) { - any = zeroany; - add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, - "", MENU_UNSELECTED); - /* these options work sensibly for the swallowed case, - but there's no reason for the player to use them then; + any = cg.zeroany; + add_menu_str(win, ""); + /* these options work sensibly for swallowed case, but + there's no reason for player to use them then because + the swallowed display hides all applicable targets; objects work fine when hallucinating, but screen symbol/monster class letter doesn't match up with bogus monster type, so suppress when hallucinating */ any.a_char = 'm'; - add_menu(win, NO_GLYPH, &any, - flags.lootabc ? 0 : any.a_char, 0, ATR_NONE, - "nearby monsters", MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, + flags.lootabc ? 0 : any.a_char, + flags.lootabc ? any.a_char : 0, ATR_NONE, + clr, "nearby monsters", MENU_ITEMFLAGS_NONE); any.a_char = 'M'; - add_menu(win, NO_GLYPH, &any, - flags.lootabc ? 0 : any.a_char, 0, ATR_NONE, - "all monsters shown on map", MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, + flags.lootabc ? 0 : any.a_char, + flags.lootabc ? any.a_char : 0, ATR_NONE, + clr, "all monsters shown on map", + MENU_ITEMFLAGS_NONE); any.a_char = 'o'; - add_menu(win, NO_GLYPH, &any, - flags.lootabc ? 0 : any.a_char, 0, ATR_NONE, - "nearby objects", MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, + flags.lootabc ? 0 : any.a_char, + flags.lootabc ? any.a_char : 0, ATR_NONE, + clr, "nearby objects", MENU_ITEMFLAGS_NONE); any.a_char = 'O'; - add_menu(win, NO_GLYPH, &any, - flags.lootabc ? 0 : any.a_char, 0, ATR_NONE, - "all objects shown on map", MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, + flags.lootabc ? 0 : any.a_char, + flags.lootabc ? any.a_char : 0, ATR_NONE, + clr, "all objects shown on map", + MENU_ITEMFLAGS_NONE); + any.a_char = 't'; + add_menu(win, &nul_glyphinfo, &any, + flags.lootabc ? 0 : any.a_char, + flags.lootabc ? any.a_char : '^', ATR_NONE, + clr, "nearby traps", MENU_ITEMFLAGS_NONE); + any.a_char = 'T'; + add_menu(win, &nul_glyphinfo, &any, + flags.lootabc ? 0 : any.a_char, + flags.lootabc ? any.a_char : '\"', ATR_NONE, + clr, "all seen or remembered traps", + MENU_ITEMFLAGS_NONE); + any.a_char = 'e'; + add_menu(win, &nul_glyphinfo, &any, + flags.lootabc ? 0 : any.a_char, + /* [don't use 'e' as lootabc group accelerator] */ + flags.lootabc ? 0 : '`', ATR_NONE, + clr, "nearby engravings", MENU_ITEMFLAGS_NONE); + any.a_char = 'E'; + add_menu(win, &nul_glyphinfo, &any, + flags.lootabc ? 0 : any.a_char, + flags.lootabc ? any.a_char : '|', ATR_NONE, + clr, "all seen or remembered engravings", + MENU_ITEMFLAGS_NONE); } end_menu(win, "What do you want to look at:"); if (select_menu(win, PICK_ONE, &pick_list) > 0) { @@ -1220,10 +1808,11 @@ coord *click_cc; destroy_nhwindow(win); } + dowhatiscmd: switch (i) { default: case 'q': - return 0; + return ECMD_OK; case 'y': case '/': from_screen = TRUE; @@ -1238,16 +1827,17 @@ coord *click_cc; invlet = display_inventory((const char *) 0, TRUE); if (!invlet || invlet == '\033') - return 0; + return ECMD_OK; *out_str = '\0'; - for (invobj = invent; invobj; invobj = invobj->nobj) + for (invobj = gi.invent; invobj; invobj = invobj->nobj) if (invobj->invlet == invlet) { - strcpy(out_str, singular(invobj, xname)); + Strcpy(out_str, singular(invobj, xname)); break; } if (*out_str) - checkfile(out_str, pm, TRUE, TRUE, (char *) 0); - return 0; + (void) checkfile(out_str, pm, chkfilUsrTyped | chkfilDontAsk, + (char *) 0); + return ECMD_OK; } case '?': from_screen = FALSE; @@ -1257,26 +1847,39 @@ coord *click_cc; condense consecutive internal whitespace */ mungspaces(out_str); if (out_str[0] == '\0' || out_str[0] == '\033') - return 0; + return ECMD_OK; if (out_str[1]) { /* user typed in a complete string */ - checkfile(out_str, pm, TRUE, TRUE, (char *) 0); - return 0; + (void) checkfile(out_str, pm, chkfilUsrTyped | chkfilDontAsk, + (char *) 0); + return ECMD_OK; } sym = out_str[0]; break; case 'm': look_all(TRUE, TRUE); /* list nearby monsters */ - return 0; + return ECMD_OK; case 'M': look_all(FALSE, TRUE); /* list all monsters */ - return 0; + return ECMD_OK; case 'o': look_all(TRUE, FALSE); /* list nearby objects */ - return 0; + return ECMD_OK; case 'O': look_all(FALSE, FALSE); /* list all objects */ - return 0; + return ECMD_OK; + case 't': + look_traps(TRUE); /* list nearby traps */ + return ECMD_OK; + case 'T': + look_traps(FALSE); /* list all traps (visible or remembered) */ + return ECMD_OK; + case 'e': + look_engrs(TRUE); /* list nearby engravings */ + return ECMD_OK; + case 'E': + look_engrs(FALSE); /* list all engravings (visible|remembered) */ + return ECMD_OK; } } else { /* clicklook */ cc.x = click_cc->x; @@ -1294,18 +1897,17 @@ coord *click_cc; do { /* Reset some variables. */ pm = (struct permonst *) 0; - found = 0; out_str[0] = '\0'; if (from_screen || clicklook) { if (from_screen) { if (flags.verbose) pline("Please move the cursor to %s.", - what_is_an_unknown_object); + what_is_a_location); else - pline("Pick an object."); + pline("Pick %s.", what_is_a_location); - ans = getpos(&cc, quick, what_is_an_unknown_object); + ans = getpos(&cc, quick, what_is_a_location); if (ans < 0 || cc.x < 0) break; /* done */ flags.verbose = FALSE; /* only print long question once */ @@ -1319,7 +1921,7 @@ coord *click_cc; if (found) { /* use putmixed() because there may be an encoded glyph present */ putmixed(WIN_MESSAGE, 0, out_str); -#ifdef DUMPLOG +#ifdef DUMPLOG_CORE { char dmpbuf[BUFSZ]; @@ -1343,8 +1945,10 @@ coord *click_cc; supplemental_name[0] = '\0'; Strcpy(temp_buf, firstmatch); - checkfile(temp_buf, pm, FALSE, - (boolean) (ans == LOOK_VERBOSE), supplemental_name); + (void) checkfile(temp_buf, pm, + (ans == LOOK_VERBOSE) ? chkfilDontAsk + : chkfilNone, + supplemental_name); if (supplemental_pm) do_supplemental_info(supplemental_name, supplemental_pm, (boolean) (ans == LOOK_VERBOSE)); @@ -1355,23 +1959,34 @@ coord *click_cc; } while (from_screen && !quick && ans != LOOK_ONCE && !clicklook); flags.verbose = save_verbose; - return 0; + return ECMD_OK; } -STATIC_OVL void -look_all(nearby, do_mons) -boolean nearby; /* True => within BOLTLIM, False => entire map */ -boolean do_mons; /* True => monsters, False => objects */ +staticfn void +look_region_nearby( + coordxy *lo_x, coordxy *lo_y, + coordxy *hi_x, coordxy *hi_y, boolean nearby) +{ + *lo_y = nearby ? max(u.uy - BOLT_LIM, 0) : 0; + *lo_x = nearby ? max(u.ux - BOLT_LIM, 1) : 1; + *hi_y = nearby ? min(u.uy + BOLT_LIM, ROWNO - 1) : ROWNO - 1; + *hi_x = nearby ? min(u.ux + BOLT_LIM, COLNO - 1) : COLNO - 1; +} + +DISABLE_WARNING_FORMAT_NONLITERAL /* RESTORE is after do_supplemental_info() */ + +staticfn void +look_all( + boolean nearby, /* True => within BOLTLIM, False => entire map */ + boolean do_mons) /* True => monsters, False => objects */ { winid win; - int x, y, lo_x, lo_y, hi_x, hi_y, glyph, count = 0; + int glyph, count = 0; + coordxy x, y, lo_x, lo_y, hi_x, hi_y; char lookbuf[BUFSZ], outbuf[BUFSZ]; win = create_nhwindow(NHW_TEXT); - lo_y = nearby ? max(u.uy - BOLT_LIM, 0) : 0; - lo_x = nearby ? max(u.ux - BOLT_LIM, 1) : 1; - hi_y = nearby ? min(u.uy + BOLT_LIM, ROWNO - 1) : ROWNO - 1; - hi_x = nearby ? min(u.ux + BOLT_LIM, COLNO - 1) : COLNO - 1; + look_region_nearby(&lo_x, &lo_y, &hi_x, &hi_y, nearby); for (y = lo_y; y <= hi_y; y++) { for (x = lo_x; x <= hi_x; x++) { lookbuf[0] = '\0'; @@ -1380,9 +1995,7 @@ boolean do_mons; /* True => monsters, False => objects */ if (glyph_is_monster(glyph)) { struct monst *mtmp; - bhitpos.x = x; /* [is this actually necessary?] */ - bhitpos.y = y; - if (x == u.ux && y == u.uy && canspotself()) { + if (u_at(x, y) && canspotself()) { (void) self_lookat(lookbuf); ++count; } else if ((mtmp = m_at(x, y)) != 0) { @@ -1422,13 +2035,27 @@ boolean do_mons; /* True => monsters, False => objects */ Sprintf(outbuf, "All %s currently shown on the map:", which); putstr(win, 0, outbuf); - putstr(win, 0, ""); + /* hack alert! Qt watches a text window for any line + with 4 consecutive spaces and renders the window + in a fixed-width font it if finds at least one */ + putstr(win, 0, " "); /* separator */ } + (void) coord_desc(x, y, coordbuf, cmode); + /* this format wrinkle makes the commas of line up; + it isn't needed when all the y values have same number + of digits but looks better when there is a mixture of 1 + and 2 digit values; done unconditionally because we + would need two passes over the map to determine whether + y width is uniform or a mixture; x width is not a factor + because the result gets right-justified by %8s; adding + a trailing space effectively pushes non-space text left */ + if (cmode == GPCOORDS_MAP && y < 10) + (void) strkitten(coordbuf, ' '); /* prefix: "coords C " where 'C' is mon or obj symbol */ Sprintf(outbuf, (cmode == GPCOORDS_SCREEN) ? "%s " : (cmode == GPCOORDS_MAP) ? "%8s " : "%12s ", - coord_desc(x, y, coordbuf, cmode)); + coordbuf); Sprintf(eos(outbuf), "%s ", encglyph(glyph)); /* guard against potential overflow */ lookbuf[sizeof lookbuf - 1 - strlen(outbuf)] = '\0'; @@ -1446,6 +2073,160 @@ boolean do_mons; /* True => monsters, False => objects */ destroy_nhwindow(win); } +/* give a /M style display of discovered traps, even when they're covered */ +staticfn void +look_traps(boolean nearby) +{ + winid win; + struct trap *t; + int glyph, tnum, count = 0; + coordxy x, y, lo_x, lo_y, hi_x, hi_y; + char lookbuf[BUFSZ], outbuf[BUFSZ]; + + win = create_nhwindow(NHW_TEXT); + look_region_nearby(&lo_x, &lo_y, &hi_x, &hi_y, nearby); + for (y = lo_y; y <= hi_y; y++) { + for (x = lo_x; x <= hi_x; x++) { + lookbuf[0] = '\0'; + glyph = glyph_at(x, y); + if (glyph_is_trap(glyph)) { + tnum = glyph_to_trap(glyph); + trap_description(lookbuf, tnum, x, y); + ++count; + } else if ((t = t_at(x, y)) != 0 && t->tseen + /* can't use /" to track traps moved by bubbles or + clouds except when hero has direct line of sight */ + && ((!Is_waterlevel(&u.uz) && !Is_airlevel(&u.uz)) + || couldsee(x, y))) { + Strcpy(lookbuf, trapname(t->ttyp, FALSE)); + Sprintf(eos(lookbuf), ", obscured by %s", encglyph(glyph)); + glyph = trap_to_glyph(t); + ++count; + } + if (*lookbuf) { + char coordbuf[20], cmode; + + cmode = (iflags.getpos_coords != GPCOORDS_NONE) + ? iflags.getpos_coords : GPCOORDS_MAP; + if (count == 1) { + Sprintf(outbuf, "%sseen or remembered traps%s:", + nearby ? "nearby " : "", + nearby ? "" : " on this level"); + putstr(win, 0, upstart(outbuf)); + /* hack alert! Qt watches a text window for any line + with 4 consecutive spaces and renders the window + in a fixed-width font it if finds at least one */ + putstr(win, 0, " "); /* separator */ + } + /* prefix: "coords C " where 'C' is trap symbol */ + Sprintf(outbuf, (cmode == GPCOORDS_SCREEN) ? "%s " + : (cmode == GPCOORDS_MAP) ? "%8s " + : "%12s ", + coord_desc(x, y, coordbuf, cmode)); + Sprintf(eos(outbuf), "%s ", encglyph(glyph)); + /* guard against potential overflow */ + lookbuf[sizeof lookbuf - 1 - strlen(outbuf)] = '\0'; + Strcat(outbuf, lookbuf); + putmixed(win, 0, outbuf); + } + } + } + if (count) + display_nhwindow(win, TRUE); + else + pline("No traps seen or remembered%s.", nearby ? " nearby" : ""); + destroy_nhwindow(win); +} + +/* display of discovered engravings including headstones, even when they're + covered provided they've been read */ +staticfn void +look_engrs(boolean nearby) +{ + winid win; + struct engr *e; + char lookbuf[BUFSZ], outbuf[BUFSZ]; + coordxy x, y, lo_x, lo_y, hi_x, hi_y; + boolean is_headstone; + nhsym sym; + int glyph, count = 0; + + win = create_nhwindow(NHW_TEXT); + look_region_nearby(&lo_x, &lo_y, &hi_x, &hi_y, nearby); + /*assert(lo_x >= 1 && lo_y >= 0 && hi_x < MAXCO && hi_y < MAXLI);*/ + for (y = lo_y; y <= hi_y; y++) { + for (x = lo_x; x <= hi_x; x++) { + lookbuf[0] = '\0'; + if (!levl[x][y].seenv) + continue; + /* this won't find remembered engravings which aren't there + anymore (in case the hero is unaware that they're gone; + scuffed away by monster movement or deleted during shop + or vault wall repair); not sure what to do about that */ + e = engr_at(x, y); + if (!e) + continue; + is_headstone = IS_GRAVE(svl.lastseentyp[x][y]); + Sprintf(lookbuf, " (%s", is_headstone ? "grave" : "engraving"); + (void) add_quoted_engraving(x, y, lookbuf, TRUE); + /* the paren is used by farlook and add_quoted_engraving() + expected to see it; we don't want it here */ + if (is_headstone) { + (void) strsubst(lookbuf, "(grave with ", ""); + (void) strsubst(lookbuf, "(grave whose ", ""); + } else { + (void) strsubst(lookbuf, "(engraving with ", ""); + (void) strsubst(lookbuf, "(engraving ", "engraving "); + } + + glyph = glyph_at(x, y); + sym = glyph_is_cmap(glyph) ? glyph_to_cmap(glyph) : SYM_NOTHING; + if (is_cmap_engraving(sym) || sym == S_grave) { + /* engraving or grave+headstone shown on the map */ + ++count; + } else { + /* engraving or grave covered by object(s) */ + Snprintf(eos(lookbuf), sizeof lookbuf - strlen(lookbuf), + ", obscured by %s", encglyph(glyph)); + glyph = is_headstone ? cmap_to_glyph(S_grave) + : engraving_to_glyph(e); + ++count; + } + if (*lookbuf) { /* (redundant) */ + char coordbuf[20], cmode; + + cmode = (iflags.getpos_coords != GPCOORDS_NONE) + ? iflags.getpos_coords : GPCOORDS_MAP; + if (count == 1) { + Sprintf(outbuf, "%sseen or remembered engravings%s:", + nearby ? "nearby " : "", + nearby ? "" : " on this level"); + putstr(win, 0, upstart(outbuf)); + /* hack alert! Qt watches a text window for any line + with 4 consecutive spaces and renders the window + in a fixed-width font it if finds at least one */ + putstr(win, 0, " "); /* separator */ + } + /* prefix: "coords C " where 'C' is engrvng|grave symbol */ + Sprintf(outbuf, (cmode == GPCOORDS_SCREEN) ? "%s " + : (cmode == GPCOORDS_MAP) ? "%8s " + : "%12s ", + coord_desc(x, y, coordbuf, cmode)); + Sprintf(eos(outbuf), "%s ", encglyph(glyph)); + /* guard against potential overflow */ + lookbuf[sizeof lookbuf - 1 - strlen(outbuf)] = '\0'; + Strcat(outbuf, lookbuf); + putmixed(win, 0, outbuf); + } + } + } + if (count) + display_nhwindow(win, TRUE); + else + pline("No engravings seen or remembered%s.", nearby ? " nearby" : ""); + destroy_nhwindow(win); +} + static const char *suptext1[] = { "%s is a member of a marauding horde of orcs", "rumored to have brutally attacked and plundered", @@ -1468,18 +2249,18 @@ static const char *suptext2[] = { (char *) 0, }; -STATIC_OVL void -do_supplemental_info(name, pm, without_asking) -char *name; -struct permonst *pm; -boolean without_asking; +staticfn void +do_supplemental_info( + char *name, + struct permonst *pm, + boolean without_asking) { const char **textp; winid datawin = WIN_ERR; char *entrytext = name, *bp = (char *) 0, *bp2 = (char *) 0; char question[QBUFSZ]; boolean yes_to_moreinfo = FALSE; - boolean is_marauder = (name && pm && is_orc(pm)); + boolean is_marauder = is_orc(pm); /* * Provide some info on some specific things @@ -1498,9 +2279,9 @@ boolean without_asking; Strcpy(question, "More info about \""); /* +2 => length of "\"?" */ copynchars(eos(question), entrytext, - (int) (sizeof question - 1 - (strlen(question) + 2))); + (int) (sizeof question - 1 - (strlen(question) + 2))); Strcat(question, "\"?"); - if (yn(question) == 'y') + if (y_n(question) == 'y') yes_to_moreinfo = TRUE; } if (yes_to_moreinfo) { @@ -1534,44 +2315,52 @@ boolean without_asking; } } -/* the '/' command */ +RESTORE_WARNING_FORMAT_NONLITERAL + +/* the #whatis command */ int -dowhatis() +dowhatis(void) { return do_look(0, (coord *) 0); } -/* the ';' command */ +/* the #glance command */ int -doquickwhatis() +doquickwhatis(void) { return do_look(1, (coord *) 0); } -/* the '^' command */ +/* the #showtrap command */ int -doidtrap() +doidtrap(void) { - register struct trap *trap; - int x, y, tt, glyph; + struct trap *trap; + int tt, glyph; + coordxy x, y; if (!getdir("^")) - return 0; + return ECMD_CANCEL; x = u.ux + u.dx; y = u.uy + u.dy; - /* check fake bear trap from confused gold detection */ + /* trapped doors and chests used to be shown as fake bear traps; + they have their own trap types now but aren't part of the ftrap + chain; usually they revert to normal door or chest when the hero + sees them but player might be using '^' while the hero is blind */ glyph = glyph_at(x, y); - if (glyph_is_trap(glyph) && (tt = glyph_to_trap(glyph)) == BEAR_TRAP) { + if (glyph_is_trap(glyph) + && ((tt = glyph_to_trap(glyph)) == BEAR_TRAP + || tt == TRAPPED_DOOR || tt == TRAPPED_CHEST)) { boolean chesttrap = trapped_chest_at(tt, x, y); if (chesttrap || trapped_door_at(tt, x, y)) { pline("That is a trapped %s.", chesttrap ? "chest" : "door"); - return 0; /* trap ID'd, but no time elapses */ + return ECMD_OK; /* trap ID'd, but no time elapses */ } } - for (trap = ftrap; trap; trap = trap->ntrap) + for (trap = gf.ftrap; trap; trap = trap->ntrap) if (trap->tx == x && trap->ty == y) { if (!trap->tseen) break; @@ -1580,9 +2369,8 @@ doidtrap() if (u.dz < 0 ? is_hole(tt) : tt == ROCKTRAP) break; } - tt = what_trap(tt, rn2_on_display_rng); pline("That is %s%s%s.", - an(defsyms[trap_to_defsym(tt)].explanation), + an(trapname(tt, FALSE)), !trap->madeby_u ? "" : (tt == WEB) @@ -1594,14 +2382,14 @@ doidtrap() ? " dug" : " set", !trap->madeby_u ? "" : " by you"); - return 0; + return ECMD_OK; } pline("I can't see a trap there."); - return 0; + return ECMD_OK; } /* - Implements a rudimentary if/elif/else/endif interpretor and use + Implements a rudimentary if/elif/else/endif interpreter and use conditionals in dat/cmdhelp to describe what command each keystroke currently invokes, so that there isn't a lot of "(debug mode only)" and "(if number_pad is off)" cluttering the feedback that the user @@ -1611,7 +2399,7 @@ doidtrap() keypad vs normal layout of digits, and QWERTZ keyboard swap between y/Y/^Y/M-y/M-Y/M-^Y and z/Z/^Z/M-z/M-Z/M-^Z.) - The interpretor understands + The interpreter understands '&#' for comment, '&? option' for 'if' (also '&? !option' or '&? option=value[,value2,...]' @@ -1629,8 +2417,8 @@ doidtrap() rest_on_space, #if SHELL, #if SUSPEND) are booleans. */ -STATIC_DCL void -whatdoes_help() +staticfn void +whatdoes_help(void) { dlb *fp; char *p, buf[BUFSZ]; @@ -1664,14 +2452,10 @@ struct wd_stack_frame { Bitfield(else_seen, 1); }; -STATIC_DCL boolean FDECL(whatdoes_cond, (char *, struct wd_stack_frame *, - int *, int)); +staticfn boolean whatdoes_cond(char *, struct wd_stack_frame *, int *, int); -STATIC_OVL boolean -whatdoes_cond(buf, stack, depth, lnum) -char *buf; -struct wd_stack_frame *stack; -int *depth, lnum; +staticfn boolean +whatdoes_cond(char *buf, struct wd_stack_frame *stack, int *depth, int lnum) { const char badstackfmt[] = "cmdhlp: too many &%c directives at line %d."; boolean newcond, neg, gotopt; @@ -1691,7 +2475,7 @@ int *depth, lnum; if ((neg = (*buf == '!')) != 0) if (*++buf == ' ') ++buf; - p = index(buf, '='), q = index(buf, ':'); + p = strchr(buf, '='), q = strchr(buf, ':'); if (!p || (q && q < p)) p = q; if (p) { /* we have a value specified */ @@ -1714,7 +2498,7 @@ int *depth, lnum; : (-1 * iflags.num_pad_mode); /* -1..0 */ newcond = FALSE; for (; p; p = q) { - q = index(p, ','); + q = strchr(p, ','); if (q) *q++ = '\0'; if (atoi(p) == np) { @@ -1790,9 +2574,7 @@ int *depth, lnum; #endif /* 0 */ char * -dowhatdoes_core(q, cbuf) -char q; -char *cbuf; +dowhatdoes_core(char q, char *cbuf) { char buf[BUFSZ]; #if 0 @@ -1806,6 +2588,8 @@ char *cbuf; if ((ec_desc = key2extcmddesc(q)) != NULL) { char keybuf[QBUFSZ]; + /* note: if "%-8s" gets changed, the "%8.8s" in dowhatdoes() will + need a comparable change */ Sprintf(buf, "%-8s%s.", key2txt(q, keybuf), ec_desc); Strcpy(cbuf, buf); return cbuf; @@ -1831,7 +2615,7 @@ char *cbuf; cond = stack[0].active = 1; while (dlb_fgets(buf, sizeof buf, fp)) { ++lnum; - if (buf[0] == '&' && buf[1] && index("?:.#", buf[1])) { + if (buf[0] == '&' && buf[1] && strchr("?:.#", buf[1])) { cond = whatdoes_cond(buf, stack, &depth, lnum); continue; } @@ -1843,7 +2627,7 @@ char *cbuf; : (ctrl ? buf[0] == '^' && highc(buf[1]) == q : buf[0] == q)) { (void) strip_newline(buf); - if (index(buf, '\t')) + if (strchr(buf, '\t')) (void) tabexpand(buf); if (meta && ctrl && buf[4] == ' ') { (void) strncpy(buf, "M-^? ", 8); @@ -1870,8 +2654,9 @@ char *cbuf; #endif /* 0 */ } +/* the whatdoes command */ int -dowhatdoes() +dowhatdoes(void) { static boolean once = FALSE; char bufr[BUFSZ]; @@ -1888,14 +2673,14 @@ dowhatdoes() #if defined(UNIX) || defined(VMS) introff(); /* disables ^C but not ^\ */ #endif - q = yn_function("What command?", (char *) 0, '\0'); + q = yn_function("What command?", (char *) 0, '\0', TRUE); #ifdef ALTMETA if (q == '\033' && iflags.altmeta) { /* in an ideal world, we would know whether another keystroke was already pending, but this is not an ideal world... if user typed ESC, we'll essentially hang until another character is typed */ - q = yn_function("]", (char *) 0, '\0'); + q = yn_function("]", (char *) 0, '\0', TRUE); if (q != '\033') q = (char) ((uchar) q | 0200); } @@ -1905,18 +2690,32 @@ dowhatdoes() #endif reslt = dowhatdoes_core(q, bufr); if (reslt) { + char *p = strchr(reslt, '\n'); /* 'm' prefix has two lines of output */ + if (q == '&' || q == '?') whatdoes_help(); - pline("%s", reslt); + if (!p) { + /* normal usage; 'reslt' starts with key, some indentation, and + then explanation followed by '.' for sentence punctuation */ + pline("%s", reslt); + } else { + /* for 'm' prefix, where 'reslt' has an embedded newline to + indicate and separate two lines of output; we add a comma to + first line so that the combination is a complete sentence */ + *p = '\0'; /* replace embedded newline with end of first line */ + pline("%s,", reslt); + /* cheat by knowing how dowhatdoes_core() handles key portion */ + pline("%8.8s%s", reslt, p + 1); + } } else { pline("No such command '%s', char code %d (0%03o or 0x%02x).", visctrl(q), (uchar) q, (uchar) q, (uchar) q); } - return 0; + return ECMD_OK; } -STATIC_OVL void -docontact(VOID_ARGS) +staticfn void +docontact(void) { winid cwin = create_nhwindow(NHW_TEXT); char buf[BUFSZ]; @@ -1945,78 +2744,91 @@ docontact(VOID_ARGS) destroy_nhwindow(cwin); } -STATIC_OVL void -dispfile_help(VOID_ARGS) +staticfn void +dispfile_help(void) { display_file(HELP, TRUE); } -STATIC_OVL void -dispfile_shelp(VOID_ARGS) +staticfn void +dispfile_shelp(void) { display_file(SHELP, TRUE); } -STATIC_OVL void -dispfile_optionfile(VOID_ARGS) +staticfn void +dispfile_optionfile(void) { display_file(OPTIONFILE, TRUE); } -STATIC_OVL void -dispfile_license(VOID_ARGS) +staticfn void +dispfile_optmenu(void) +{ + display_file(OPTMENUHELP, TRUE); +} + +staticfn void +dispfile_license(void) { display_file(LICENSE, TRUE); } -STATIC_OVL void -dispfile_debughelp(VOID_ARGS) +staticfn void +dispfile_debughelp(void) { display_file(DEBUGHELP, TRUE); } -STATIC_OVL void -hmenu_doextversion(VOID_ARGS) +staticfn void +dispfile_usagehelp(void) +{ + display_file(USAGEHELP, TRUE); +} + +staticfn void +hmenu_doextversion(void) { (void) doextversion(); } -STATIC_OVL void -hmenu_dohistory(VOID_ARGS) +staticfn void +hmenu_dohistory(void) { (void) dohistory(); } -STATIC_OVL void -hmenu_dowhatis(VOID_ARGS) +staticfn void +hmenu_dowhatis(void) { (void) dowhatis(); } -STATIC_OVL void -hmenu_dowhatdoes(VOID_ARGS) +staticfn void +hmenu_dowhatdoes(void) { (void) dowhatdoes(); } -STATIC_OVL void -hmenu_doextlist(VOID_ARGS) +staticfn void +hmenu_doextlist(void) { (void) doextlist(); } -void -domenucontrols(VOID_ARGS) +staticfn void +domenucontrols(void) { winid cwin = create_nhwindow(NHW_TEXT); + show_menu_controls(cwin, FALSE); display_nhwindow(cwin, FALSE); destroy_nhwindow(cwin); } /* data for dohelp() */ -static struct { - void NDECL((*f)); +static const struct { + void (*f)(void); const char *text; } help_menu_items[] = { { hmenu_doextversion, "About NetHack (version information)." }, @@ -2027,43 +2839,53 @@ static struct { { hmenu_dowhatdoes, "Info on what a given key does." }, { option_help, "List of game options." }, { dispfile_optionfile, "Longer explanation of game options." }, - { dokeylist, "Full list of keyboard commands" }, + { dispfile_optmenu, "Using the %s command to set options." }, + { dokeylist, "Full list of keyboard commands." }, { hmenu_doextlist, "List of extended commands." }, - { domenucontrols, "List menu control keys" }, + { domenucontrols, "List menu control keys." }, + { dispfile_usagehelp, "Description of NetHack's command line." }, { dispfile_license, "The NetHack license." }, { docontact, "Support information." }, #ifdef PORT_HELP { port_help, "%s-specific help and commands." }, #endif { dispfile_debughelp, "List of wizard-mode commands." }, - { (void NDECL((*))) 0, (char *) 0 } + { (void (*)(void)) 0, (char *) 0 } }; -/* the '?' command */ +DISABLE_WARNING_FORMAT_NONLITERAL + +/* the #help command */ int -dohelp() +dohelp(void) { winid tmpwin = create_nhwindow(NHW_MENU); - char helpbuf[QBUFSZ]; + char helpbuf[QBUFSZ], tmpbuf[QBUFSZ]; int i, n; menu_item *selected; anything any; int sel; + int clr = NO_COLOR; - any = zeroany; /* zero all bits */ - start_menu(tmpwin); + any = cg.zeroany; /* zero all bits */ + start_menu(tmpwin, MENU_BEHAVE_STANDARD); for (i = 0; help_menu_items[i].text; i++) { if (!wizard && help_menu_items[i].f == dispfile_debughelp) continue; + if (sysopt.hideusage && help_menu_items[i].f == dispfile_usagehelp) + continue; + if (help_menu_items[i].text[0] == '%') { Sprintf(helpbuf, help_menu_items[i].text, PORT_ID); + } else if (help_menu_items[i].f == dispfile_optmenu) { + Sprintf(helpbuf, help_menu_items[i].text, setopt_cmd(tmpbuf)); } else { Strcpy(helpbuf, help_menu_items[i].text); } any.a_int = i + 1; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - helpbuf, MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr, + helpbuf, MENU_ITEMFLAGS_NONE); } end_menu(tmpwin, "Select one item:"); n = select_menu(tmpwin, PICK_ONE, &selected); @@ -2073,15 +2895,73 @@ dohelp() free((genericptr_t) selected); (void) (*help_menu_items[sel].f)(); } - return 0; + return ECMD_OK; +} + +RESTORE_WARNING_FORMAT_NONLITERAL + +/* format the key or extended command name of command used to set options; + normally 'O' but could be bound to something else, or not bound at all; + with the implementation of a simple options subset, now need 'mO' to get + the full options command; format it as 'm O' */ +staticfn char * +setopt_cmd(char *outbuf) +{ + char cmdbuf[QBUFSZ]; + const char *cmdnm; + char key; + + Strcpy(outbuf, "\'"); + /* #optionsfull */ + key = cmd_from_func(doset); + if (key) { + Strcat(outbuf, visctrl(key)); + } else { + /* extended command name, with leading "#" */ + cmdnm = cmdname_from_func(doset, cmdbuf, TRUE); + if (!cmdnm) /* paranoia */ + cmdnm = "optionsfull"; + Sprintf(eos(outbuf), "%s%.31s", (*cmdnm != '#') ? "#" : "", cmdnm); + + /* since there's no key bound to #optionsfull, include 'm O' */ + Strcat(outbuf, "\' or \'"); + /* m prefix plus #options */ + key = cmd_from_func(do_reqmenu); + if (key) { + /* key for 'm' prefix */ + Strcat(outbuf, visctrl(key)); + } else { + /* extended command name for 'm' prefix */ + cmdnm = cmdname_from_func(do_reqmenu, cmdbuf, TRUE); + if (!cmdnm) + cmdnm = "reqmenu"; + Sprintf(eos(outbuf), "%s%.31s", (*cmdnm != '#') ? "#" : "", cmdnm); + } + /* this is slightly iffy because the user shouldn't type to + get the command we're describing, but it improves readability */ + Strcat(outbuf, " "); + /* now #options, normally 'O' */ + key = cmd_from_func(doset_simple); + if (key) { + Strcat(outbuf, visctrl(key)); + } else { + /* extended command name */ + cmdnm = cmdname_from_func(doset_simple, cmdbuf, TRUE); + if (!cmdnm) /* paranoia */ + cmdnm = "options"; + Sprintf(eos(outbuf), "%s%.31s", (*cmdnm != '#') ? "#" : "", cmdnm); + } + } + Strcat(outbuf, "\'"); + return outbuf; } /* the 'V' command; also a choice for '?' */ int -dohistory() +dohistory(void) { display_file(HISTORY, TRUE); - return 0; + return ECMD_OK; } /*pager.c*/ diff --git a/src/pickup.c b/src/pickup.c index 76f35aa8b..65e77fca6 100644 --- a/src/pickup.c +++ b/src/pickup.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 pickup.c $NHDT-Date: 1576282488 2019/12/14 00:14:48 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.237 $ */ +/* NetHack 5.0 pickup.c $NHDT-Date: 1773373633 2026/03/12 19:47:13 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.386 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2012. */ /* NetHack may be freely redistributed. See license for details. */ @@ -11,40 +11,47 @@ #define CONTAINED_SYM '>' /* from invent.c */ -STATIC_DCL void FDECL(simple_look, (struct obj *, BOOLEAN_P)); -STATIC_DCL boolean FDECL(query_classes, (char *, boolean *, boolean *, - const char *, struct obj *, - BOOLEAN_P, int *)); -STATIC_DCL boolean FDECL(fatal_corpse_mistake, (struct obj *, BOOLEAN_P)); -STATIC_DCL void FDECL(check_here, (BOOLEAN_P)); -STATIC_DCL boolean FDECL(n_or_more, (struct obj *)); -STATIC_DCL boolean FDECL(all_but_uchain, (struct obj *)); +staticfn void simple_look(struct obj *, boolean); +staticfn boolean query_classes(char *, boolean *, boolean *, const char *, + struct obj *, boolean, int *); +staticfn boolean fatal_corpse_mistake(struct obj *, boolean); +staticfn boolean describe_decor(void); +staticfn void check_here(boolean); +staticfn boolean n_or_more(struct obj *); +staticfn boolean all_but_uchain(struct obj *); #if 0 /* not used */ -STATIC_DCL boolean FDECL(allow_cat_no_uchain, (struct obj *)); +staticfn boolean allow_cat_no_uchain(struct obj *); #endif -STATIC_DCL int FDECL(autopick, (struct obj *, int, menu_item **)); -STATIC_DCL int FDECL(count_categories, (struct obj *, int)); -STATIC_DCL int FDECL(delta_cwt, (struct obj *, struct obj *)); -STATIC_DCL long FDECL(carry_count, (struct obj *, struct obj *, long, - BOOLEAN_P, int *, int *)); -STATIC_DCL int FDECL(lift_object, (struct obj *, struct obj *, long *, - BOOLEAN_P)); -STATIC_DCL boolean FDECL(mbag_explodes, (struct obj *, int)); -STATIC_DCL long FDECL(boh_loss, (struct obj *container, int)); -STATIC_PTR int FDECL(in_container, (struct obj *)); -STATIC_PTR int FDECL(out_container, (struct obj *)); -STATIC_DCL void FDECL(removed_from_icebox, (struct obj *)); -STATIC_DCL long FDECL(mbag_item_gone, (int, struct obj *)); -STATIC_DCL void FDECL(explain_container_prompt, (BOOLEAN_P)); -STATIC_DCL int FDECL(traditional_loot, (BOOLEAN_P)); -STATIC_DCL int FDECL(menu_loot, (int, BOOLEAN_P)); -STATIC_DCL char FDECL(in_or_out_menu, (const char *, struct obj *, BOOLEAN_P, - BOOLEAN_P, BOOLEAN_P, BOOLEAN_P)); -STATIC_DCL boolean FDECL(able_to_loot, (int, int, BOOLEAN_P)); -STATIC_DCL boolean NDECL(reverse_loot); -STATIC_DCL boolean FDECL(mon_beside, (int, int)); -STATIC_DCL int FDECL(do_loot_cont, (struct obj **, int, int)); -STATIC_DCL void FDECL(tipcontainer, (struct obj *)); +staticfn int autopick(struct obj *, int, menu_item **); +staticfn int count_categories(struct obj *, int); +staticfn int delta_cwt(struct obj *, struct obj *); +staticfn long carry_count(struct obj *, struct obj *, long, boolean, int *, + int *); +staticfn int lift_object(struct obj *, struct obj *, long *, boolean); +staticfn void pickup_prinv(struct obj *, long, const char *); +staticfn boolean mbag_explodes(struct obj *, int); +staticfn boolean is_boh_item_gone(void); +staticfn void do_boh_explosion(struct obj *, boolean); +staticfn long boh_loss(struct obj *, boolean); +staticfn int in_container(struct obj *); +staticfn int out_container(struct obj *); +staticfn long mbag_item_gone(boolean, struct obj *, boolean); +staticfn int stash_ok(struct obj *); +staticfn void explain_container_prompt(boolean); +staticfn int traditional_loot(boolean); +staticfn int menu_loot(int, boolean); +staticfn int tip_ok(struct obj *); +staticfn int choose_tip_container_menu(void); +staticfn struct obj *tipcontainer_gettarget(struct obj *, boolean *); +staticfn int tipcontainer_checks(struct obj *, struct obj *, boolean); +staticfn char in_or_out_menu(const char *, struct obj *, boolean, boolean, + boolean, boolean); +staticfn boolean able_to_loot(coordxy, coordxy, boolean); +staticfn boolean reverse_loot(void); +staticfn boolean mon_beside(coordxy, coordxy); +staticfn int do_loot_cont(struct obj **, int, int); +staticfn int doloot_core(void); +staticfn void tipcontainer(struct obj *); /* define for query_objlist() and autopickup() */ #define FOLLOW(curr, flags) \ @@ -54,25 +61,20 @@ STATIC_DCL void FDECL(tipcontainer, (struct obj *)); /* if you can figure this out, give yourself a hearty pat on the back... */ #define GOLD_CAPACITY(w, n) (((w) * -100L) - ((n) + 50L) - 1L) -/* A variable set in use_container(), to be used by the callback routines - in_container() and out_container() from askchain() and use_container(). - Also used by menu_loot() and container_gone(). */ -static NEARDATA struct obj *current_container; -static NEARDATA boolean abort_looting; -#define Icebox (current_container->otyp == ICE_BOX) +#define Icebox (gc.current_container->otyp == ICE_BOX) static const char - moderateloadmsg[] = "You have a little trouble lifting", - nearloadmsg[] = "You have much trouble lifting", - overloadmsg[] = "You have extreme difficulty lifting"; + slightloadpfx[] = "You have a little trouble", + moderateloadpfx[] = "You have trouble", + nearloadpfx[] = "You have much trouble", + overloadpfx[] = "You have extreme difficulty"; /* BUG: this lets you look at cockatrice corpses while blind without touching them */ /* much simpler version of the look-here code; used by query_classes() */ -STATIC_OVL void -simple_look(otmp, here) -struct obj *otmp; /* list of objects */ -boolean here; /* flag for type of obj list linkage */ +staticfn void +simple_look(struct obj *otmp, /* list of objects */ + boolean here) /* flag for type of obj list linkage */ { /* Neither of the first two cases is expected to happen, since * we're only called after multiple classes of objects have been @@ -96,21 +98,17 @@ boolean here; /* flag for type of obj list linkage */ } int -collect_obj_classes(ilets, otmp, here, filter, itemcount) -char ilets[]; -register struct obj *otmp; -boolean here; -boolean FDECL((*filter), (OBJ_P)); -int *itemcount; +collect_obj_classes(char ilets[], struct obj *otmp, boolean here, + boolean (*filter)(OBJ_P), int *itemcount) { - register int iletct = 0; - register char c; + int iletct = 0; + char c; *itemcount = 0; - ilets[iletct] = '\0'; /* terminate ilets so that index() will work */ + ilets[iletct] = '\0'; /* terminate ilets so that strchr() will work */ while (otmp) { c = def_oc_syms[(int) otmp->oclass].sym; - if (!index(ilets, c) && (!filter || (*filter)(otmp))) + if (!strchr(ilets, c) && (!filter || (*filter)(otmp))) ilets[iletct++] = c, ilets[iletct] = '\0'; *itemcount += 1; otmp = here ? otmp->nexthere : otmp->nobj; @@ -120,6 +118,8 @@ int *itemcount; } /* + * For menustyle:Traditional and menustyle:Combination. + * * Suppose some '?' and '!' objects are present, but '/' objects aren't: * "a" picks all items without further prompting; * "A" steps through all items, asking one by one; @@ -130,30 +130,36 @@ int *itemcount; * (bug fix: 3.1.0 thru 3.1.3 treated it as "a"); * "?/a" or "a?/" or "/a?",&c picks all '?' even though no '/' * (ie, treated as if it had just been "?a"). + * + * Note: the behavior and meaning of 'a' vs 'A' is effectively reversed + * when using menustyle:Full. For Traditional, the choice is based on + * ease of typing (using 'a' is much more common than 'A'); for Full, + * it was changed to enhance menu entry ordering ('A' stands out, but + * some players complain that it is too easy to choose accidentally). */ -STATIC_OVL boolean -query_classes(oclasses, one_at_a_time, everything, action, objs, here, - menu_on_demand) -char oclasses[]; -boolean *one_at_a_time, *everything; -const char *action; -struct obj *objs; -boolean here; -int *menu_on_demand; +staticfn boolean +query_classes( + char oclasses[], /* selected classes */ + boolean *one_at_a_time, /* to tell caller that user picked 'A' */ + boolean *everything, /* to tell caller that user picked 'a' */ + const char *action, /* verb for what activity needs objects */ + struct obj *objs, /* invent or container->cobj or level.objects[x][y] */ + boolean here, /* True: traverse by obj->nexthere; False: by obj->nobj */ + int *menu_on_demand) /* to tell caller that user picked 'm' */ { char ilets[36], inbuf[BUFSZ] = DUMMY; /* FIXME: hardcoded ilets[] length */ int iletct, oclassct; boolean not_everything, filtered; char qbuf[QBUFSZ]; boolean m_seen; - int itemcount, bcnt, ucnt, ccnt, xcnt, ocnt; + int itemcount, bcnt, ucnt, ccnt, xcnt, ocnt, jcnt; oclasses[oclassct = 0] = '\0'; *one_at_a_time = *everything = m_seen = FALSE; if (menu_on_demand) *menu_on_demand = 0; iletct = collect_obj_classes(ilets, objs, here, - (boolean FDECL((*), (OBJ_P))) 0, &itemcount); + (boolean (*)(OBJ_P)) 0, &itemcount); if (iletct == 0) return FALSE; @@ -165,14 +171,14 @@ int *menu_on_demand; ilets[iletct++] = ' '; ilets[iletct++] = 'a'; ilets[iletct++] = 'A'; - ilets[iletct++] = (objs == invent ? 'i' : ':'); + ilets[iletct++] = (objs == gi.invent ? 'i' : ':'); } if (itemcount && menu_on_demand) ilets[iletct++] = 'm'; if (count_unpaid(objs)) ilets[iletct++] = 'u'; - tally_BUCX(objs, here, &bcnt, &ucnt, &ccnt, &xcnt, &ocnt); + tally_BUCX(objs, here, &bcnt, &ucnt, &ccnt, &xcnt, &ocnt, &jcnt); if (bcnt) ilets[iletct++] = 'B'; if (ucnt) @@ -181,6 +187,8 @@ int *menu_on_demand; ilets[iletct++] = 'C'; if (xcnt) ilets[iletct++] = 'X'; + if (jcnt) + ilets[iletct++] = 'P'; ilets[iletct] = '\0'; if (iletct > 1) { @@ -216,12 +224,12 @@ int *menu_on_demand; goto ask_again; } else if (sym == 'm') { m_seen = TRUE; - } else if (index("uBUCX", sym)) { - add_valid_menu_class(sym); /* 'u' or 'B','U','C',or 'X' */ + } else if (strchr("uBUCXP", sym)) { + add_valid_menu_class(sym); /* 'u' or 'B','U','C','X','P' */ filtered = TRUE; } else { oc_of_sym = def_char_to_objclass(sym); - if (index(ilets, sym)) { + if (strchr(ilets, sym)) { add_valid_menu_class(oc_of_sym); oclasses[oclassct++] = oc_of_sym; oclasses[oclassct] = '\0'; @@ -253,17 +261,33 @@ int *menu_on_demand; return TRUE; } +/* + * tests: + * st_gloves wearing gloves? + * st_corpse is it a corpse obj? + * st_petrifies does the corpse petrify on touch? + * st_resists does hero have stoning resistance? + * st_all st_gloves | st_corpse | st_petrifies | st_resists + */ +boolean +u_safe_from_fatal_corpse(struct obj *obj, int tests) +{ + if (((tests & st_gloves) && uarmg) + || ((tests & st_corpse) && obj->otyp != CORPSE) + || ((tests & st_petrifies) && !touch_petrifies(&mons[obj->corpsenm])) + || ((tests & st_resists) && Stone_resistance)) + return TRUE; + return FALSE; +} + /* check whether hero is bare-handedly touching a cockatrice corpse */ -STATIC_OVL boolean -fatal_corpse_mistake(obj, remotely) -struct obj *obj; -boolean remotely; +staticfn boolean +fatal_corpse_mistake(struct obj *obj, boolean remotely) { - if (uarmg || remotely || obj->otyp != CORPSE - || !touch_petrifies(&mons[obj->corpsenm]) || Stone_resistance) + if (u_safe_from_fatal_corpse(obj, st_all) || remotely) return FALSE; - if (poly_when_stoned(youmonst.data) && polymon(PM_STONE_GOLEM)) { + if (poly_when_stoned(gy.youmonst.data) && polymon(PM_STONE_GOLEM)) { display_nhwindow(WIN_MESSAGE, FALSE); /* --More-- */ return FALSE; } @@ -276,9 +300,7 @@ boolean remotely; /* attempting to manipulate a Rider's corpse triggers its revival */ boolean -rider_corpse_revival(obj, remotely) -struct obj *obj; -boolean remotely; +rider_corpse_revival(struct obj *obj, boolean remotely) { if (!obj || obj->otyp != CORPSE || !is_rider(&mons[obj->corpsenm])) return FALSE; @@ -290,91 +312,201 @@ boolean remotely; return TRUE; } +/* wand of probing zapped down; perhaps hero is levitating while blind */ +void +force_decor(boolean via_probing) +{ + /* we don't want describe_decor() to defer feedback if hero is fumbling + with 1 turn left until next slip_or_trip(), or for ice_descr() to + omit thawing details if hero is probing when levitating while blind + (those will be skipped for look_here() and farlook() or autodescribe); + we can't control that by temporarily tweaking properties because that + could become noticeable if status gets updated while decor feedback + is being delivered */ + gd.decor_fumble_override = TRUE; + gd.decor_levitate_override = via_probing; + /* force current terrain to be different from previous location, or + uninteresting if previous location was actually inside solid stone */ + iflags.prev_decor = STONE; + (void) describe_decor(); + gd.decor_fumble_override = gd.decor_levitate_override = FALSE; + svl.lastseentyp[u.ux][u.uy] = levl[u.ux][u.uy].typ; +} + +void +deferred_decor( + boolean setup) /* True: deferring, False: catching up */ +{ + if (!flags.mention_decor) { + iflags.defer_decor = FALSE; + } else if (setup) { + iflags.defer_decor = TRUE; + } else { + (void) describe_decor(); + iflags.defer_decor = FALSE; + } +} + +/* handle 'mention_decor' (when walking onto a dungeon feature such as + stairs or altar, describe it even if it isn't covered up by an object) */ +staticfn boolean +describe_decor(void) +{ + char outbuf[BUFSZ], fbuf[QBUFSZ]; + boolean doorhere, waterhere, res = TRUE; + const char *dfeature; + int ltyp; + + if ((HFumbling & TIMEOUT) == 1L /* about to slip_or_trip */ + && !iflags.defer_decor + && !gd.decor_fumble_override) { /* probe_decor() */ + /* + * Work around a message sequencing issue if Fumbling's periodic + * timeout is about to kick in: avoid the combination + * |You are back on floor. + * |You trip over . or You flounder. + * when the trip is being caused by moving on ice as hero + * steps off ice onto non-ice. Defer the back-on-floor part if + * that is about to happen. + */ + deferred_decor(TRUE); + return FALSE; + } + + ltyp = SURFACE_AT(u.ux, u.uy); + dfeature = dfeature_at(u.ux, u.uy, fbuf); + + /* we don't mention "ordinary" doors but do mention broken ones (and + closed ones, which will only happen for Passes_walls) */ + doorhere = dfeature && (!strcmp(dfeature, "open door") + || !strcmp(dfeature, "doorway")); + waterhere = dfeature && !strcmp(dfeature, "pool of water"); + if (doorhere || Underwater + || (ltyp == ICE && IS_POOL(iflags.prev_decor))) /* pooleffects() */ + dfeature = 0; + /* + * TODO: if on ice, report moving between thicker and thinner ice (based + * on ice_descr()'s classification) as if moving onto different terrain. + */ + + if (ltyp == iflags.prev_decor && !IS_FURNITURE(ltyp)) { + res = FALSE; + } else if (dfeature) { + if (waterhere) + dfeature = strcpy(fbuf, waterbody_name(u.ux, u.uy)); + if (strcmp(dfeature, "swamp") && ltyp != ICE) + dfeature = an(dfeature); + + if (flags.verbose) { + Sprintf(outbuf, "There is %s here.", dfeature); + } else { + if (dfeature != fbuf) + Strcpy(fbuf, dfeature); + Sprintf(outbuf, "%s.", upstart(fbuf)); + } + if (ltyp == ICE && flags.mention_decor) + Norep("%s", outbuf); + else + pline("%s", outbuf); + } else if (!Underwater) { + if (IS_POOL(iflags.prev_decor) + || IS_LAVA(iflags.prev_decor) + || iflags.prev_decor == ICE) { + if (iflags.last_msg != PLNMSG_BACK_ON_GROUND) { + back_on_ground(FALSE); + } + } + } + /* describe_decor() is normally called when moving onto a different + type of terrain, but it is also called by pickup() even when + mention_decor is Off if hero can't reach floor; only adapt the next + describe_decor() by what has just occurred in this one when it's On */ + iflags.prev_decor = flags.mention_decor ? ltyp : STONE; + return res; +} + /* look at the objects at our location, unless there are too many of them */ -STATIC_OVL void -check_here(picked_some) -boolean picked_some; +staticfn void +check_here(boolean picked_some) { - register struct obj *obj; - register int ct = 0; + struct obj *obj; + int ct = 0; + unsigned lhflags = picked_some ? LOOKHERE_PICKED_SOME : LOOKHERE_NOFLAGS; + + if (flags.mention_decor) { + if (describe_decor()) + lhflags |= LOOKHERE_SKIP_DFEATURE; + } /* count the objects here */ - for (obj = level.objects[u.ux][u.uy]; obj; obj = obj->nexthere) { + for (obj = svl.level.objects[u.ux][u.uy]; obj; obj = obj->nexthere) { if (obj != uchain) ct++; } /* If there are objects here, take a look. */ if (ct) { - if (context.run) + if (svc.context.run) nomul(0); flush_screen(1); - (void) look_here(ct, picked_some); + (void) look_here(ct, lhflags); } else { read_engr_at(u.ux, u.uy); } } -/* Value set by query_objlist() for n_or_more(). */ -static long val_for_n_or_more; - /* query_objlist callback: return TRUE if obj's count is >= reference value */ -STATIC_OVL boolean -n_or_more(obj) -struct obj *obj; +staticfn boolean +n_or_more(struct obj *obj) { if (obj == uchain) return FALSE; - return (boolean) (obj->quan >= val_for_n_or_more); + return (boolean) (obj->quan >= gv.val_for_n_or_more); } -/* list of valid menu classes for query_objlist() and allow_category callback - (with room for all object classes, 'u'npaid, BUCX, and terminator) */ -static char valid_menu_classes[MAXOCLASSES + 1 + 4 + 1]; -static boolean class_filter, bucx_filter, shop_filter; - /* check valid_menu_classes[] for an entry; also used by askchain() */ boolean -menu_class_present(c) -int c; +menu_class_present(int c) { - return (c && index(valid_menu_classes, c)) ? TRUE : FALSE; + return (c && strchr(gv.valid_menu_classes, c)) ? TRUE : FALSE; } void -add_valid_menu_class(c) -int c; +add_valid_menu_class(int c) { static int vmc_count = 0; if (c == 0) { /* reset */ vmc_count = 0; - class_filter = bucx_filter = shop_filter = FALSE; + gc.class_filter = gb.bucx_filter = gs.shop_filter = FALSE; + gp.picked_filter = FALSE; } else if (!menu_class_present(c)) { - valid_menu_classes[vmc_count++] = (char) c; + gv.valid_menu_classes[vmc_count++] = (char) c; /* categorize the new class */ switch (c) { case 'B': case 'U': case 'C': /*FALLTHRU*/ case 'X': - bucx_filter = TRUE; + gb.bucx_filter = TRUE; + break; + case 'P': + gp.picked_filter = TRUE; break; case 'u': - shop_filter = TRUE; + gs.shop_filter = TRUE; break; default: - class_filter = TRUE; + gc.class_filter = TRUE; break; } } - valid_menu_classes[vmc_count] = '\0'; + gv.valid_menu_classes[vmc_count] = '\0'; } /* query_objlist callback: return TRUE if not uchain */ -STATIC_OVL boolean -all_but_uchain(obj) -struct obj *obj; +staticfn boolean +all_but_uchain(struct obj *obj) { return (boolean) (obj != uchain); } @@ -382,41 +514,38 @@ struct obj *obj; /* query_objlist callback: return TRUE */ /*ARGUSED*/ boolean -allow_all(obj) -struct obj *obj UNUSED; +allow_all(struct obj *obj UNUSED) { return TRUE; } boolean -allow_category(obj) -struct obj *obj; +allow_category(struct obj *obj) { + /* If no filters are active, nothing will match unless + paranoid_confirm:A is set. */ + if (!gc.class_filter && !gs.shop_filter && !gb.bucx_filter + && !gp.picked_filter && !ParanoidAutoAll) + return FALSE; + /* For coins, if any class filter is specified, accept if coins * are included regardless of whether either unpaid or BUC-status * is also specified since player has explicitly requested coins. - * If no class filtering is specified but bless/curse state is, - * coins are either unknown or uncursed based on an option setting. */ - if (obj->oclass == COIN_CLASS) - return class_filter - ? (index(valid_menu_classes, COIN_CLASS) ? TRUE : FALSE) - : shop_filter /* coins are never unpaid, but check anyway */ - ? (obj->unpaid ? TRUE : FALSE) - : bucx_filter - ? (index(valid_menu_classes, iflags.goldX ? 'X' : 'U') - ? TRUE : FALSE) - : TRUE; /* catchall: no filters specified, so accept */ - - if (Role_if(PM_PRIEST) && !obj->bknown) + if (obj->oclass == COIN_CLASS && gc.class_filter) + return strchr(gv.valid_menu_classes, COIN_CLASS) ? TRUE : FALSE; + + if (Role_if(PM_CLERIC) && !obj->bknown) set_bknown(obj, 1); /* - * There are three types of filters possible and the first and - * third can have more than one entry: + * Version 3.6 had three types of filters possible and the first + * and third can have more than one entry: * 1) object class (armor, potion, &c); * 2) unpaid shop item; * 3) bless/curse state (blessed, uncursed, cursed, BUC-unknown). + * Version 5.0 added a fourth: + * 4) 'novelty' ('P' for just picked up items). * When only one type is present, the situation is simple: * to be accepted, obj's status must match one of the entries. * When more than one type is present, the obj will now only @@ -429,36 +558,47 @@ struct obj *obj; */ /* if class is expected but obj's class is not in the list, reject */ - if (class_filter && !index(valid_menu_classes, obj->oclass)) + if (gc.class_filter && !strchr(gv.valid_menu_classes, obj->oclass)) return FALSE; /* if unpaid is expected and obj isn't unpaid, reject (treat a container holding any unpaid object as unpaid even if isn't unpaid itself) */ - if (shop_filter && !obj->unpaid + if (gs.shop_filter && !obj->unpaid && !(Has_contents(obj) && count_unpaid(obj->cobj) > 0)) return FALSE; /* check for particular bless/curse state */ - if (bucx_filter) { + if (gb.bucx_filter) { /* first categorize this object's bless/curse state */ - char bucx = !obj->bknown ? 'X' - : obj->blessed ? 'B' : obj->cursed ? 'C' : 'U'; + char bucx; + if (obj->oclass == COIN_CLASS) { + /* If no class filtering is specified but bless/curse state is, + coins are treated as either unknown or uncursed based on an + option setting. */ + bucx = flags.goldX ? 'X' : 'U'; + } else { + bucx = !obj->bknown ? 'X' + : obj->blessed ? 'B' + : obj->cursed ? 'C' + : 'U'; + } /* if its category is not in the list, reject */ - if (!index(valid_menu_classes, bucx)) + if (!strchr(gv.valid_menu_classes, bucx)) return FALSE; } + if (gp.picked_filter && !obj->pickup_prev) + return FALSE; /* obj didn't fail any of the filter checks, so accept */ return TRUE; } #if 0 /* not used */ /* query_objlist callback: return TRUE if valid category (class), no uchain */ -STATIC_OVL boolean -allow_cat_no_uchain(obj) -struct obj *obj; +staticfn boolean +allow_cat_no_uchain(struct obj *obj) { if (obj != uchain - && ((index(valid_menu_classes, 'u') && obj->unpaid) - || index(valid_menu_classes, obj->oclass))) + && ((strchr(gv.valid_menu_classes, 'u') && obj->unpaid) + || strchr(gv.valid_menu_classes, obj->oclass))) return TRUE; return FALSE; } @@ -466,12 +606,56 @@ struct obj *obj; /* query_objlist callback: return TRUE if valid class and worn */ boolean -is_worn_by_type(otmp) -register struct obj *otmp; +is_worn_by_type(struct obj *otmp) { return (is_worn(otmp) && allow_category(otmp)) ? TRUE : FALSE; } +/* reset last-picked-up flags */ +void +reset_justpicked(struct obj *olist) +{ + struct obj *otmp; + /* + * TODO? Possible enhancement: don't reset if hero is still at same + * spot where most recent pickup took place. Not resetting will be + * the correct behavior for autopickup immediately followed by manual + * pickup. It would probably be correct for either or both pickups + * followed by manual pickup of a newly arrived missile after some + * time has elapsed. Things becomes murkier for other activity. + * Taking anything out of a container ought to be treated as if + * having moved to another spot. + */ + + for (otmp = olist; otmp; otmp = otmp->nobj) + otmp->pickup_prev = 0; +} + +int +count_justpicked(struct obj *olist) +{ + struct obj *otmp; + int cnt = 0; + + for (otmp = olist; otmp; otmp = otmp->nobj) + if (otmp->pickup_prev) + cnt++; + + return cnt; +} + +struct obj * +find_justpicked(struct obj *olist) +{ + struct obj *otmp; + + for (otmp = olist; otmp; otmp = otmp->nobj) + if (otmp->pickup_prev) + return otmp; + + return (struct obj *) 0; +} + /* * Have the hero pick things from the ground * or a monster's inventory if swallowed. @@ -485,8 +669,7 @@ register struct obj *otmp; * or not it succeeded. */ int -pickup(what) -int what; /* should be a long */ +pickup(int what) /* should be a long */ { int i, n, res, count, n_tried = 0, n_picked = 0; menu_item *pick_list = (menu_item *) 0; @@ -499,8 +682,13 @@ int what; /* should be a long */ and read_engr_at in addition to bypassing autopickup itself [probably ought to check whether hero is using a cockatrice corpse for a pillow here... (also at initial faint/sleep)] */ - if (autopickup && multi < 0 && unconscious()) + if (autopickup && gm.multi < 0 && unconscious()) { + iflags.prev_decor = STONE; return 0; + } + + /* used by pickup_object() for encumbrance feedback */ + gp.pickup_encumbrance = 0; if (what < 0) /* pick N of something */ count = -what; @@ -508,48 +696,50 @@ int what; /* should be a long */ count = 0; if (!u.uswallow) { - struct trap *ttmp; + struct trap *t; /* no auto-pick if no-pick move, nothing there, or in a pool */ - if (autopickup && (context.nopick || !OBJ_AT(u.ux, u.uy) + if (autopickup && (svc.context.nopick || !OBJ_AT(u.ux, u.uy) || (is_pool(u.ux, u.uy) && !Underwater) || is_lava(u.ux, u.uy))) { + if (flags.mention_decor) + (void) describe_decor(); read_engr_at(u.ux, u.uy); return 0; } /* no pickup if levitating & not on air or water level */ - if (!can_reach_floor(TRUE)) { - if ((multi && !context.run) || (autopickup && !flags.pickup) - || ((ttmp = t_at(u.ux, u.uy)) != 0 - && (uteetering_at_seen_pit(ttmp) || uescaped_shaft(ttmp)))) + t = t_at(u.ux, u.uy); + if (!can_reach_floor(t && is_pit(t->ttyp))) { + (void) describe_decor(); /* even when !flags.mention_decor */ + if ((gm.multi && !svc.context.run) + || (autopickup && !flags.pickup) + || (t && (uteetering_at_seen_pit(t) || uescaped_shaft(t)))) read_engr_at(u.ux, u.uy); return 0; } - /* multi && !context.run means they are in the middle of some other - * action, or possibly paralyzed, sleeping, etc.... and they just - * teleported onto the object. They shouldn't pick it up. + /* multi && !svc.context.run means they are in the middle of some + * other action, or possibly paralyzed, sleeping, etc.... and they + * just teleported onto the object. They shouldn't pick it up. */ - if ((multi && !context.run) || (autopickup && !flags.pickup)) { + if ((gm.multi && !svc.context.run) + || (autopickup && !flags.pickup) + || notake(gy.youmonst.data)) { check_here(FALSE); - return 0; - } - if (notake(youmonst.data)) { - if (!autopickup) + if (notake(gy.youmonst.data) && OBJ_AT(u.ux, u.uy) + && (autopickup || flags.pickup)) You("are physically incapable of picking anything up."); - else - check_here(FALSE); return 0; } /* if there's anything here, stop running */ - if (OBJ_AT(u.ux, u.uy) && context.run && context.run != 8 - && !context.nopick) + if (OBJ_AT(u.ux, u.uy) && svc.context.run && svc.context.run != 8 + && !svc.context.nopick) nomul(0); } add_valid_menu_class(0); /* reset */ if (!u.uswallow) { - objchain_p = &level.objects[u.ux][u.uy]; + objchain_p = &svl.level.objects[u.ux][u.uy]; traverse_how = BY_NEXTHERE; } else { objchain_p = &u.ustuck->minvent; @@ -574,7 +764,7 @@ int what; /* should be a long */ char qbuf[QBUFSZ]; Sprintf(qbuf, "Pick %d of what?", count); - val_for_n_or_more = count; /* set up callback selector */ + gv.val_for_n_or_more = count; /* set up callback selector */ n = query_objlist(qbuf, objchain_p, traverse_how, &pick_list, PICK_ONE, n_or_more); /* correct counts, if any given */ @@ -587,6 +777,8 @@ int what; /* should be a long */ } menu_pickup: + if (n > 0) + reset_justpicked(gi.invent); n_tried = n; for (n_picked = i = 0; i < n; i++) { res = pickup_object(pick_list[i].item.a_obj, pick_list[i].count, @@ -619,6 +811,7 @@ int what; /* should be a long */ obj = *objchain_p; lcount = min(obj->quan, (long) count); n_tried++; + reset_justpicked(gi.invent); if (pickup_object(obj, lcount, FALSE) > 0) n_picked++; /* picked something */ goto end_query; @@ -649,7 +842,7 @@ int what; /* should be a long */ obj2 = FOLLOW(obj, traverse_how); if (bycat ? !allow_category(obj) : (!selective && oclasses[0] - && !index(oclasses, obj->oclass))) + && !strchr(oclasses, obj->oclass))) continue; lcount = -1L; @@ -677,6 +870,7 @@ int what; /* should be a long */ lcount = (long) yn_number; if (lcount > obj->quan) lcount = obj->quan; + FALLTHROUGH; /*FALLTHRU*/ default: /* 'y' */ break; @@ -685,6 +879,9 @@ int what; /* should be a long */ if (lcount == -1L) lcount = obj->quan; + if (!n_tried) /* reset just before the first item picked */ + reset_justpicked(gi.invent); + n_tried++; if ((res = pickup_object(obj, lcount, FALSE)) < 0) break; @@ -695,8 +892,8 @@ int what; /* should be a long */ } if (!u.uswallow) { - if (hides_under(youmonst.data)) - (void) hideunder(&youmonst); + if (hides_under(gy.youmonst.data)) + (void) hideunder(&gy.youmonst); /* position may need updating (invisible hero) */ if (n_picked) @@ -707,18 +904,18 @@ int what; /* should be a long */ check_here(n_picked > 0); } pickupdone: + gp.pickup_encumbrance = 0; add_valid_menu_class(0); /* reset */ return (n_tried > 0); } struct autopickup_exception * -check_autopickup_exceptions(obj) -struct obj *obj; +check_autopickup_exceptions(struct obj *obj) { /* * Does the text description of this match an exception? */ - struct autopickup_exception *ape = apelist; + struct autopickup_exception *ape = ga.apelist; if (ape) { char *objdesc = makesingular(doname(obj)); @@ -730,9 +927,7 @@ struct obj *obj; } boolean -autopick_testobj(otmp, calc_costly) -struct obj *otmp; -boolean calc_costly; +autopick_testobj(struct obj *otmp, boolean calc_costly) { struct autopickup_exception *ape; static boolean costly = FALSE; @@ -748,17 +943,24 @@ boolean calc_costly; if (costly && !otmp->no_charge) return FALSE; + /* pickup_thrown/pickup_stolen/nopick_dropped override pickup_types and + exceptions */ + if ((flags.pickup_thrown && otmp->how_lost == LOST_THROWN) + || (flags.pickup_stolen && otmp->how_lost == LOST_STOLEN)) + return TRUE; + if (flags.nopick_dropped && otmp->how_lost == LOST_DROPPED) + return FALSE; + if (otmp->how_lost == LOST_EXPLODING) + return FALSE; + /* check for pickup_types */ - pickit = (!*otypes || index(otypes, otmp->oclass)); + pickit = (!*otypes || strchr(otypes, otmp->oclass)); /* check for autopickup exceptions */ ape = check_autopickup_exceptions(otmp); if (ape) pickit = ape->grab; - /* pickup_thrown overrides pickup_types and exceptions */ - if (!pickit) - pickit = (flags.pickup_thrown && otmp->was_thrown); return pickit; } @@ -769,11 +971,11 @@ boolean calc_costly; * picked is zero, the pickup list is left alone. The caller of this * function must free the pickup list. */ -STATIC_OVL int -autopick(olist, follow, pick_list) -struct obj *olist; /* the object list */ -int follow; /* how to follow the object list */ -menu_item **pick_list; /* list of objects and counts to pick up */ +staticfn int +autopick( + struct obj *olist, /* the object list */ + int follow, /* how to follow the object list */ + menu_item **pick_list) /* list of objects and counts to pick up */ { menu_item *pi; /* pick item */ struct obj *curr; @@ -820,25 +1022,26 @@ menu_item **pick_list; /* list of objects and counts to pick up */ * FEEL_COCKATRICE - touch corpse. */ int -query_objlist(qstr, olist_p, qflags, pick_list, how, allow) -const char *qstr; /* query string */ -struct obj **olist_p; /* the list to pick from */ -int qflags; /* options to control the query */ -menu_item **pick_list; /* return list of items picked */ -int how; /* type of query */ -boolean FDECL((*allow), (OBJ_P)); /* allow function */ -{ - int i, n; +query_objlist(const char *qstr, /* query string */ + struct obj **olist_p, /* the list to pick from */ + int qflags, /* options to control the query */ + menu_item **pick_list, /* return list of items picked */ + int how, /* type of query */ + boolean (*allow)(OBJ_P)) /* allow function */ +{ + int i, n, tmpglyph; winid win; struct obj *curr, *last, fake_hero_object, *olist = *olist_p; - char *pack; + char *pack, packbuf[MAXOCLASSES + 1]; anything any; boolean printed_type_name, first, sorted = (qflags & INVORDER_SORT) != 0, engulfer = (qflags & INCLUDE_HERO) != 0, engulfer_minvent; unsigned sortflags; + glyph_info tmpglyphinfo = nul_glyphinfo; Loot *sortedolist, *srtoli; + int clr = NO_COLOR; *pick_list = (menu_item *) 0; if (!olist && !engulfer) @@ -853,7 +1056,7 @@ boolean FDECL((*allow), (OBJ_P)); /* allow function */ /* can't depend upon 'engulfer' because that's used to indicate whether hero should be shown as an extra, fake item */ engulfer_minvent = (olist && olist->where == OBJ_MINVENT - && u.uswallow && olist->ocarry == u.ustuck); + && engulfing_u(olist->ocarry)); if (engulfer_minvent && n == 1 && olist->owornmask != 0L) { qflags &= ~AUTOSELECT_SINGLE; } @@ -883,15 +1086,22 @@ boolean FDECL((*allow), (OBJ_P)); /* allow function */ (qflags & BY_NEXTHERE) ? TRUE : FALSE, allow); win = create_nhwindow(NHW_MENU); - start_menu(win); - any = zeroany; + start_menu(win, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + if (gt.this_title) { + /* dotypeinv() supplies gt.this_title to display as initial header; + intentionally avoid the menu_headings highlight attribute here */ + add_menu_str(win, gt.this_title); + } /* * Run through the list and add the objects to the menu. If * INVORDER_SORT is set, we'll run through the list once for * each type so we can group them. The allow function was * called by sortloot() and will be called once per item here. */ - pack = flags.inv_order; + pack = strcpy(packbuf, flags.inv_order); + if (qflags & INCLUDE_VENOM) + (void) strkitten(pack, VENOM_CLASS); /* venom is not in inv_order */ first = TRUE; do { printed_type_name = FALSE; @@ -901,28 +1111,31 @@ boolean FDECL((*allow), (OBJ_P)); /* allow function */ if ((qflags & FEEL_COCKATRICE) && curr->otyp == CORPSE && will_feel_cockatrice(curr, FALSE)) { destroy_nhwindow(win); /* stop the menu and revert */ - (void) look_here(0, FALSE); + (void) look_here(0, LOOKHERE_NOFLAGS); unsortloot(&sortedolist); return 0; } if ((*allow)(curr)) { /* if sorting, print type name (once only) */ if (sorted && !printed_type_name) { - any = zeroany; - add_menu(win, NO_GLYPH, &any, 0, 0, iflags.menu_headings, - let_to_name(*pack, FALSE, - ((how != PICK_NONE) - && iflags.menu_head_objsym)), - MENU_UNSELECTED); + boolean with_oc_sym = (how != PICK_NONE + && iflags.menu_head_objsym); + + any = cg.zeroany; + add_menu_heading(win, + let_to_name(*pack, FALSE, with_oc_sym)); printed_type_name = TRUE; } any.a_obj = curr; - add_menu(win, obj_to_glyph(curr, rn2_on_display_rng), &any, + tmpglyph = obj_to_glyph(curr, rn2_on_display_rng); + map_glyphinfo(0, 0, tmpglyph, 0U, &tmpglyphinfo); + add_menu(win, &tmpglyphinfo, &any, (qflags & USE_INVLET) ? curr->invlet : (first && curr->oclass == COIN_CLASS) ? '$' : 0, def_oc_syms[(int) objects[curr->otyp].oc_class].sym, - ATR_NONE, doname_with_price(curr), MENU_UNSELECTED); + ATR_NONE, clr, doname_with_price(curr), + MENU_ITEMFLAGS_NONE); first = FALSE; } } @@ -933,20 +1146,21 @@ boolean FDECL((*allow), (OBJ_P)); /* allow function */ if (engulfer) { char buf[BUFSZ]; - any = zeroany; + any = cg.zeroany; if (sorted && n > 1) { Sprintf(buf, "%s Creatures", - is_animal(u.ustuck->data) ? "Swallowed" : "Engulfed"); - add_menu(win, NO_GLYPH, &any, 0, 0, iflags.menu_headings, buf, - MENU_UNSELECTED); + digests(u.ustuck->data) ? "Swallowed" : "Engulfed"); + add_menu_heading(win, buf); } - fake_hero_object = zeroobj; + fake_hero_object = cg.zeroobj; fake_hero_object.quan = 1L; /* not strictly necessary... */ any.a_obj = &fake_hero_object; - add_menu(win, mon_to_glyph(&youmonst, rn2_on_display_rng), &any, + tmpglyph = mon_to_glyph(&gy.youmonst, rn2_on_display_rng); + map_glyphinfo(0, 0, tmpglyph, 0U, &tmpglyphinfo); + add_menu(win, &tmpglyphinfo, &any, /* fake inventory letter, no group accelerator */ - CONTAINED_SYM, 0, ATR_NONE, an(self_lookat(buf)), - MENU_UNSELECTED); + CONTAINED_SYM, 0, ATR_NONE, clr, an(self_lookat(buf)), + MENU_ITEMFLAGS_NONE); } end_menu(win, qstr); @@ -989,7 +1203,7 @@ boolean FDECL((*allow), (OBJ_P)); /* allow function */ worn by engulfer) */ while (n > k) { --n; - (*pick_list)[n].item = zeroany; + (*pick_list)[n].item = cg.zeroany; (*pick_list)[n].count = 0L; } } @@ -1002,59 +1216,77 @@ boolean FDECL((*allow), (OBJ_P)); /* allow function */ } /* + * For menustyle:Full. + * * allow menu-based category (class) selection (for Drop,take off etc.) * + * If ParanoidAutoAll, requires confirmation when 'A' has been picked. */ int -query_category(qstr, olist, qflags, pick_list, how) -const char *qstr; /* query string */ -struct obj *olist; /* the list to pick from */ -int qflags; /* behaviour modification flags */ -menu_item **pick_list; /* return list of items picked */ -int how; /* type of query */ +query_category( + const char *qstr, /* query string */ + struct obj *olist, /* the list to pick from */ + int qflags, /* behavior modification flags */ + menu_item **pick_list, /* return list of items picked */ + int how) /* type of query */ { int n; winid win; struct obj *curr; - char *pack; + char *pack, packbuf[MAXOCLASSES + 1]; anything any; boolean collected_type_name; char invlet; int ccount; - boolean FDECL((*ofilter), (OBJ_P)) = (boolean FDECL((*), (OBJ_P))) 0; - boolean do_unpaid = FALSE; - boolean do_blessed = FALSE, do_cursed = FALSE, do_uncursed = FALSE, - do_buc_unknown = FALSE; - int num_buc_types = 0; + boolean (*ofilter)(OBJ_P) = (boolean (*)(OBJ_P)) 0; + boolean show_a, + do_unpaid = FALSE, do_usedup = FALSE, + do_blessed = FALSE, do_cursed = FALSE, + do_uncursed = FALSE, do_buc_unknown = FALSE, + do_worn = FALSE, verify_All = FALSE; + int num_buc_types = 0, num_justpicked = 0, clr = NO_COLOR; *pick_list = (menu_item *) 0; if (!olist) return 0; - if ((qflags & UNPAID_TYPES) && count_unpaid(olist)) + if ((qflags & UNPAID_TYPES) != 0 && count_unpaid(olist)) do_unpaid = TRUE; - if (qflags & WORN_TYPES) + /* caller only passes BILLED_TYPES when there are some used up items + on shop's bill */ + if ((qflags & BILLED_TYPES) != 0) + do_usedup = TRUE; + /* for the 'A' command to remove worn/wielded */ + if ((qflags & WORN_TYPES) != 0) { + do_worn = TRUE; ofilter = is_worn; - if ((qflags & BUC_BLESSED) && count_buc(olist, BUC_BLESSED, ofilter)) { + } + if ((qflags & BUC_BLESSED) != 0 + && count_buc(olist, BUC_BLESSED, ofilter)) { do_blessed = TRUE; num_buc_types++; } - if ((qflags & BUC_CURSED) && count_buc(olist, BUC_CURSED, ofilter)) { + if ((qflags & BUC_CURSED) != 0 + && count_buc(olist, BUC_CURSED, ofilter)) { do_cursed = TRUE; num_buc_types++; } - if ((qflags & BUC_UNCURSED) && count_buc(olist, BUC_UNCURSED, ofilter)) { + if ((qflags & BUC_UNCURSED) != 0 + && count_buc(olist, BUC_UNCURSED, ofilter)) { do_uncursed = TRUE; num_buc_types++; } - if ((qflags & BUC_UNKNOWN) && count_buc(olist, BUC_UNKNOWN, ofilter)) { + if ((qflags & BUC_UNKNOWN) != 0 + && count_buc(olist, BUC_UNKNOWN, ofilter)) { do_buc_unknown = TRUE; num_buc_types++; } + if ((qflags & JUSTPICKED) != 0) { + num_justpicked = count_justpicked(olist); + } ccount = count_categories(olist, qflags); /* no point in actually showing a menu for a single category */ - if (ccount == 1 && !do_unpaid && num_buc_types <= 1 - && !(qflags & BILLED_TYPES)) { + if (ccount == 1 && !do_unpaid && !do_usedup && num_buc_types <= 1) { for (curr = olist; curr; curr = FOLLOW(curr, qflags)) { if (ofilter && !(*ofilter)(curr)) continue; @@ -1073,32 +1305,48 @@ int how; /* type of query */ } win = create_nhwindow(NHW_MENU); - start_menu(win); - pack = flags.inv_order; + start_menu(win, MENU_BEHAVE_STANDARD); - if (qflags & CHOOSE_ALL) { + pack = strcpy(packbuf, flags.inv_order); + if ((qflags & INCLUDE_VENOM) != 0) + (void) strkitten(pack, VENOM_CLASS); /* venom is not in inv_order */ + + show_a = ((qflags & ALL_TYPES) != 0 && ccount > 1); + + if ((qflags & CHOOSE_ALL) != 0) { invlet = 'A'; - any = zeroany; + any = cg.zeroany; any.a_int = 'A'; - add_menu(win, NO_GLYPH, &any, invlet, 0, ATR_NONE, - (qflags & WORN_TYPES) ? "Auto-select every item being worn" - : "Auto-select every item", - MENU_UNSELECTED); - - any = zeroany; - add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, invlet, 0, ATR_NONE, clr, + /* note: menu_remarm() doesn't pass the CHOOSE_ALL flag, + so do_worn handling here is moot */ + do_worn ? "Auto-select every item being worn or wielded" + : "Auto-select every relevant item", + MENU_ITEMFLAGS_SKIPINVERT); + verify_All = (how == PICK_ANY) && ParanoidAutoAll; + if (!verify_All) { + if (!ga.A_first_hint++ || iflags.cmdassist) + add_menu_str(win, + " (ignored unless some other choices are also picked)"); + } else if (show_a) { + if (!ga.A_second_hint++ || iflags.cmdassist) + add_menu_str(win, + " (if no other choices are picked, 'a' is implied)"); + } + /* blank separator */ + add_menu_str(win, ""); } - if ((qflags & ALL_TYPES) && (ccount > 1)) { - invlet = 'a'; - any = zeroany; + invlet = 'a'; + if (show_a) { + any = cg.zeroany; any.a_int = ALL_TYPES_SELECTED; - add_menu(win, NO_GLYPH, &any, invlet, 0, ATR_NONE, - (qflags & WORN_TYPES) ? "All worn types" : "All types", - MENU_UNSELECTED); - invlet = 'b'; - } else - invlet = 'a'; + add_menu(win, &nul_glyphinfo, &any, invlet, 0, ATR_NONE, clr, + do_worn ? "All worn and wielded types" : "All types", + MENU_ITEMFLAGS_SKIPINVERT); + ++invlet; /* invlet = 'b'; */ + } + do { collected_type_name = FALSE; for (curr = olist; curr; curr = FOLLOW(curr, qflags)) { @@ -1106,15 +1354,16 @@ int how; /* type of query */ if (ofilter && !(*ofilter)(curr)) continue; if (!collected_type_name) { - any = zeroany; - any.a_int = curr->oclass; - add_menu( - win, NO_GLYPH, &any, invlet++, - def_oc_syms[(int) objects[curr->otyp].oc_class].sym, - ATR_NONE, let_to_name(*pack, FALSE, - (how != PICK_NONE) - && iflags.menu_head_objsym), - MENU_UNSELECTED); + int oclass = (int) curr->oclass; + + any = cg.zeroany; + any.a_int = oclass; + add_menu(win, &nul_glyphinfo, &any, invlet++, + (int) def_oc_syms[oclass].sym, ATR_NONE, clr, + let_to_name(*pack, FALSE, + (how != PICK_NONE + && iflags.menu_head_objsym)), + MENU_ITEMFLAGS_NONE); collected_type_name = TRUE; } } @@ -1127,27 +1376,27 @@ int how; /* type of query */ } } while (*pack); - if (do_unpaid || (qflags & BILLED_TYPES) || do_blessed || do_cursed - || do_uncursed || do_buc_unknown) { - any = zeroany; - add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED); + if (do_unpaid || do_usedup + || do_blessed || do_cursed || do_uncursed || do_buc_unknown + || num_justpicked) { + add_menu_str(win, ""); } /* unpaid items if there are any */ if (do_unpaid) { invlet = 'u'; - any = zeroany; + any = cg.zeroany; any.a_int = 'u'; - add_menu(win, NO_GLYPH, &any, invlet, 0, ATR_NONE, "Unpaid items", - MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, invlet, 0, + ATR_NONE, clr, "Unpaid items", MENU_ITEMFLAGS_SKIPINVERT); } /* billed items: checked by caller, so always include if BILLED_TYPES */ - if (qflags & BILLED_TYPES) { + if (do_usedup) { invlet = 'x'; - any = zeroany; + any = cg.zeroany; any.a_int = 'x'; - add_menu(win, NO_GLYPH, &any, invlet, 0, ATR_NONE, - "Unpaid items already used up", MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, invlet, 0, ATR_NONE, clr, + "Unpaid items already used up", MENU_ITEMFLAGS_SKIPINVERT); } /* items with b/u/c/unknown if there are any; @@ -1155,59 +1404,125 @@ int how; /* type of query */ reversing the usual sequence of 'U' and 'C' in BUCX */ if (do_blessed) { invlet = 'B'; - any = zeroany; + any = cg.zeroany; any.a_int = 'B'; - add_menu(win, NO_GLYPH, &any, invlet, 0, ATR_NONE, - "Items known to be Blessed", MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, invlet, 0, ATR_NONE, clr, + "Items known to be Blessed", MENU_ITEMFLAGS_SKIPINVERT); } if (do_cursed) { invlet = 'C'; - any = zeroany; + any = cg.zeroany; any.a_int = 'C'; - add_menu(win, NO_GLYPH, &any, invlet, 0, ATR_NONE, - "Items known to be Cursed", MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, invlet, 0, ATR_NONE, clr, + "Items known to be Cursed", MENU_ITEMFLAGS_SKIPINVERT); } if (do_uncursed) { invlet = 'U'; - any = zeroany; + any = cg.zeroany; any.a_int = 'U'; - add_menu(win, NO_GLYPH, &any, invlet, 0, ATR_NONE, - "Items known to be Uncursed", MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, invlet, 0, ATR_NONE, clr, + "Items known to be Uncursed", MENU_ITEMFLAGS_SKIPINVERT); } if (do_buc_unknown) { invlet = 'X'; - any = zeroany; + any = cg.zeroany; any.a_int = 'X'; - add_menu(win, NO_GLYPH, &any, invlet, 0, ATR_NONE, - "Items of unknown Bless/Curse status", MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, invlet, 0, ATR_NONE, clr, + "Items of unknown Bless/Curse status", + MENU_ITEMFLAGS_SKIPINVERT); + } + if (num_justpicked) { + char tmpbuf[BUFSZ]; + + if (num_justpicked == 1) + Sprintf(tmpbuf, "Just picked up: %s", + doname(find_justpicked(olist))); + else + Strcpy(tmpbuf, "Items you just picked up"); + invlet = 'P'; + any = cg.zeroany; + any.a_int = 'P'; + add_menu(win, &nul_glyphinfo, &any, invlet, 0, ATR_NONE, clr, + tmpbuf, MENU_ITEMFLAGS_SKIPINVERT); } end_menu(win, qstr); n = select_menu(win, how, pick_list); + if (n > 0) { + assert(*pick_list != NULL); + } + + /* handle ParanoidAutoAll by confirming 'A' choice if present */ + if (n > 0 && verify_All) { + int i, j; + + for (i = 0; i < n; ++i) + if ((*pick_list)[i].item.a_int == 'A') { + /* ParanoidAutoAll is set (otherwise verify_All is false); + if ParanoidConfirm is also set, require "yes" rather than + just "y" to accept (and "no" rather than "n" to decline; + accepts "quit" and ESC without converting them to 'n') */ + switch (paranoid_ynq(ParanoidConfirm, + "Really autoselect All?", TRUE)) { + case 'y': + /* yes => honor Auto-select All */ + break; + case 'n': + /* no => remove 'A' from the list; if that would make + it empty then replace with 'a' */ + if (n > 1) { + for (j = i + 1; j < n; ++j) + (*pick_list)[j - 1] = (*pick_list)[j]; + --n; + break; /* from switch */ + } else if ((qflags & ALL_TYPES) != 0) { + /* 'A' was the only choice; convert it to 'a' and + then let the next menu offer a choice of all */ + (*pick_list)[0].item.a_int = ALL_TYPES_SELECTED; + /* assert( n == 1 ); */ + break; /* from switch */ + } + FALLTHROUGH; + /*FALLTHRU*/ + case 'q': + default: + /* quit | ESC => cancel, no Auto-select and no 2nd menu */ + n = 0; + free((genericptr_t) *pick_list), *pick_list = 0; + break; + } + break; /* from for => goto query_done; */ + } + } else if (n == 1 && !verify_All && (*pick_list)[0].item.a_int == 'A') { + /* without paranoid_confirm:A, choosing 'A' by itself is rejected */ + n = 0; + free((genericptr_t) *pick_list), *pick_list = 0; + /* the menu entry description is "Auto-select every relevant item" + [not sure whether issuing a message here is a good idea...] */ + pline("No relevant items selected."); + } query_done: destroy_nhwindow(win); - if (n < 0) - n = 0; /* caller's don't expect -1 */ + if (n < 0) /* closed menu with ESC */ + n = 0; /* callers don't expect -1 */ return n; } -STATIC_OVL int -count_categories(olist, qflags) -struct obj *olist; -int qflags; +staticfn int +count_categories(struct obj *olist, int qflags) { char *pack; boolean counted_category; int ccount = 0; struct obj *curr; + boolean do_worn = (qflags & WORN_TYPES) != 0; pack = flags.inv_order; do { counted_category = FALSE; for (curr = olist; curr; curr = FOLLOW(curr, qflags)) { if (curr->oclass == *pack) { - if ((qflags & WORN_TYPES) - && !(curr->owornmask & (W_ARMOR | W_ACCESSORY - | W_WEAPONS))) + if (do_worn && !(curr->owornmask + & (W_ARMOR | W_ACCESSORY | W_WEAPONS))) continue; if (!counted_category) { ccount++; @@ -1225,9 +1540,8 @@ int qflags; * object is removed from it. Use before and after weight amounts rather * than trying to match the calculation used by weight() in mkobj.c. */ -STATIC_OVL int -delta_cwt(container, obj) -struct obj *container, *obj; +staticfn int +delta_cwt(struct obj *container, struct obj *obj) { struct obj **prev; int owt, nwt; @@ -1252,12 +1566,12 @@ struct obj *container, *obj; } /* could we carry `obj'? if not, could we carry some of it/them? */ -STATIC_OVL long -carry_count(obj, container, count, telekinesis, wt_before, wt_after) -struct obj *obj, *container; /* object to pick up, bag it's coming out of */ -long count; -boolean telekinesis; -int *wt_before, *wt_after; +staticfn long +carry_count(struct obj *obj, /* object to pick up... */ + struct obj *container, /* ...bag it is coming out of */ + long count, + boolean telekinesis, + int *wt_before, int *wt_after) { boolean adjust_wt = container && carried(container), is_gold = obj->oclass == COIN_CLASS; @@ -1269,7 +1583,7 @@ int *wt_before, *wt_after; savequan = obj->quan; saveowt = obj->owt; - umoney = money_cnt(invent); + umoney = money_cnt(gi.invent); iw = max_capacity(); if (count != savequan) { @@ -1321,7 +1635,7 @@ int *wt_before, *wt_after; wt = iw + (int) GOLD_WT(umoney + qq); } else if (count > 1 || count < obj->quan) { /* - * Ugh. Calc num to lift by changing the quan of of the + * Ugh. Calc num to lift by changing the quan of the * object and calling weight. * * This works for containers only because containers @@ -1370,7 +1684,7 @@ int *wt_before, *wt_after; if (!container) Strcpy(where, "here"); /* slightly shorter form */ - if (invent || umoney) { + if (gi.invent || umoney) { prefx1 = "you cannot "; prefx2 = ""; suffx = " any more"; @@ -1387,12 +1701,12 @@ int *wt_before, *wt_after; } /* determine whether character is able and player is willing to carry `obj' */ -STATIC_OVL -int -lift_object(obj, container, cnt_p, telekinesis) -struct obj *obj, *container; /* object to pick up, bag it's coming out of */ -long *cnt_p; -boolean telekinesis; +staticfn int +lift_object( + struct obj *obj, /* object to pick up... */ + struct obj *container, /* ...bag it's coming out of */ + long *cnt_p, + boolean telekinesis) { int result, old_wt, new_wt, prev_encumbr, next_encumbr; @@ -1405,14 +1719,17 @@ boolean telekinesis; and for boulder picked up by hero poly'd into a giant; override availability of open inventory slot iff not already carrying one */ if (obj->otyp == LOADSTONE - || (obj->otyp == BOULDER && throws_rocks(youmonst.data))) { - if (inv_cnt(FALSE) < 52 || !carrying(obj->otyp) - || merge_choice(invent, obj)) + || (obj->otyp == BOULDER && throws_rocks(gy.youmonst.data))) { + if (inv_cnt(FALSE) < invlet_basic || !carrying(obj->otyp) + || merge_choice(gi.invent, obj)) return 1; /* lift regardless of current situation */ /* if we reach here, we're out of slots and already have at least - one of these, so treat this one more like a normal item */ + one of these, so treat this one more like a normal item + [this was using simpleonames(obj) for shortest description, but + that's suboptimal for loadstones because it omits user-assigned + type name which is something of interest for gray stones] */ You("are carrying too much stuff to pick up %s %s.", - (obj->quan == 1L) ? "another" : "more", simpleonames(obj)); + (obj->quan == 1L) ? "another" : "more", xname(obj)); return -1; } @@ -1423,7 +1740,8 @@ boolean telekinesis; } else if (obj->oclass != COIN_CLASS /* [exception for gold coins will have to change if silver/copper ones ever get implemented] */ - && inv_cnt(FALSE) >= 52 && !merge_choice(invent, obj)) { + && inv_cnt(FALSE) >= invlet_basic + && !merge_choice(gi.invent, obj)) { /* if there is some gold here (and we haven't already skipped it), we aren't limited by the 52 item limit for it, but caller and "grandcaller" aren't prepared to skip stuff and then pickup @@ -1447,14 +1765,12 @@ boolean telekinesis; long savequan = obj->quan; obj->quan = *cnt_p; - Strcpy(qbuf, (next_encumbr > HVY_ENCUMBER) - ? overloadmsg - : (next_encumbr > MOD_ENCUMBER) - ? nearloadmsg - : moderateloadmsg); - if (container) - (void) strsubst(qbuf, "lifting", "removing"); - Strcat(qbuf, " "); + Sprintf(qbuf, "%s %s ", + (next_encumbr >= EXT_ENCUMBER) ? overloadpfx + : (next_encumbr >= HVY_ENCUMBER) ? nearloadpfx + : (next_encumbr >= MOD_ENCUMBER) ? moderateloadpfx + : slightloadpfx, + !container ? "lifting" : "removing"); (void) safe_qbuf(qbuf, qbuf, ". Continue?", obj, doname, ansimpleoname, something); obj->quan = savequan; @@ -1484,12 +1800,12 @@ boolean telekinesis; * up, 1 if otherwise. */ int -pickup_object(obj, count, telekinesis) -struct obj *obj; -long count; -boolean telekinesis; /* not picking it up directly by hand */ +pickup_object( + struct obj *obj, + long count, /* if non-zero, pick up a subset of this amount */ + boolean telekinesis) /* not picking it up directly by hand */ { - int res, nearload; + int res; if (obj->quan < count) { impossible("pickup_object: count %ld > quan %ld?", count, obj->quan); @@ -1499,55 +1815,75 @@ boolean telekinesis; /* not picking it up directly by hand */ /* In case of auto-pickup, where we haven't had a chance to look at it yet; affects docall(SCR_SCARE_MONSTER). */ if (!Blind) - obj->dknown = 1; + observe_object(obj); if (obj == uchain) { /* do not pick up attached chain */ return 0; } else if (obj->where == OBJ_MINVENT && obj->owornmask != 0L - && u.uswallow && obj->ocarry == u.ustuck) { + && engulfing_u(obj->ocarry)) { You_cant("pick %s up.", ysimple_name(obj)); return 0; - } else if (obj->oartifact && !touch_artifact(obj, &youmonst)) { + } else if (obj->oartifact && !touch_artifact(obj, &gy.youmonst)) { return 0; } else if (obj->otyp == CORPSE) { if (fatal_corpse_mistake(obj, telekinesis) || rider_corpse_revival(obj, telekinesis)) return -1; } else if (obj->otyp == SCR_SCARE_MONSTER) { - if (obj->blessed) - obj->blessed = 0; - else if (!obj->spe && !obj->cursed) + int old_wt, new_wt; + + /* process a count before altering/deleting scrolls; + tricky because being unable to lift full stack imposes an + implicit count; unliftable ones should be treated as if + the count excluded them so that they don't change state */ + if ((count = carry_count(obj, (struct obj *) 0, + count ? count : obj->quan, + FALSE, &old_wt, &new_wt)) < 1L) + return -1; /* couldn't even pick up 1, so effectively untouched */ + /* all current callers handle a new object sanely when traversing + a list; other half of a split will be left as-is and whatever + already follows 'obj' will still be processed next */ + if (count > 0L && count < obj->quan) + obj = splitobj(obj, count); + + if (obj->blessed) { + unbless(obj); + } else if (!obj->spe && !obj->cursed) { obj->spe = 1; - else { + } else { pline_The("scroll%s %s to dust as you %s %s up.", plur(obj->quan), otense(obj, "turn"), telekinesis ? "raise" : "pick", (obj->quan == 1L) ? "it" : "them"); - if (!(objects[SCR_SCARE_MONSTER].oc_name_known) - && !(objects[SCR_SCARE_MONSTER].oc_uname)) - docall(obj); + trycall(obj); useupf(obj, obj->quan); return 1; /* tried to pick something up and failed, but don't want to terminate pickup loop yet */ } } - if ((res = lift_object(obj, (struct obj *) 0, &count, telekinesis)) <= 0) + /* obj has either already passed autopick_testobj or we are explicitly + picking it off the floor, so addinv() will override obj->how_lost; + otherwise we couldn't pick up a thrown, stolen, or dropped item that + was split off from a carried stack even while still carrying the + rest of the stack unless we have at least one free slot available */ + res = lift_object(obj, (struct obj *) 0, &count, telekinesis); + if (res <= 0) return res; - /* Whats left of the special case for gold :-) */ + /* What's left of the special case for gold :-) */ if (obj->oclass == COIN_CLASS) - context.botl = 1; + disp.botl = TRUE; if (obj->quan != count && obj->otyp != LOADSTONE) obj = splitobj(obj, count); obj = pick_obj(obj); if (uwep && uwep == obj) - mrg_to_wielded = TRUE; - nearload = near_capacity(); - prinv(nearload == SLT_ENCUMBER ? moderateloadmsg : (char *) 0, obj, - count); - mrg_to_wielded = FALSE; + gm.mrg_to_wielded = TRUE; + pickup_prinv(obj, count, "lifting"); + if (obj->ghostly) + fix_ghostly_obj(obj); + gm.mrg_to_wielded = FALSE; return 1; } @@ -1558,15 +1894,20 @@ boolean telekinesis; /* not picking it up directly by hand */ * from otmp because of merging. */ struct obj * -pick_obj(otmp) -struct obj *otmp; +pick_obj(struct obj *otmp) { struct obj *result; - int ox = otmp->ox, oy = otmp->oy; - boolean robshop = (!u.uswallow && otmp != uball && costly_spot(ox, oy)); + coordxy ox, oy; + boolean robshop, fromfloor = otmp->where == OBJ_FLOOR; + + /* otmp is either on the floor or in an engulfer's inventory; for the + latter, its probably won't be set */ + (void) get_obj_location(otmp, &ox, &oy, 0); + robshop = (!u.uswallow && otmp != uball && costly_spot(ox, oy)); obj_extract_self(otmp); - newsym(ox, oy); + if (fromfloor) + newsym(ox, oy); /* for shop items, addinv() needs to be after addtobill() (so that object merger can take otmp->unpaid into account) but before @@ -1589,7 +1930,7 @@ struct obj *otmp; /* sets obj->unpaid if necessary */ addtobill(otmp, TRUE, FALSE, FALSE); Strcpy(u.ushops, saveushops); - robshop = otmp->unpaid && !index(u.ushops, *fakeshop); + robshop = otmp->unpaid && !strchr(u.ushops, *fakeshop); } result = addinv(otmp); @@ -1600,17 +1941,45 @@ struct obj *otmp; return result; } +/* pickup_object()/out_container() helper; + print an added-to-invent message for current object, limiting feedback + about encumbrance to the first item which causes that to change */ +staticfn void +pickup_prinv( + struct obj *obj, + long count, + const char *verb) +{ + char pbuf[QBUFSZ]; + const char *prefix; + int nearload = near_capacity(); + + pbuf[0] = '\0'; + if (nearload == gp.pickup_encumbrance) { + prefix = (char *) 0; + } else { + prefix = (nearload >= EXT_ENCUMBER) ? overloadpfx + : (nearload >= HVY_ENCUMBER) ? nearloadpfx + : (nearload >= MOD_ENCUMBER) ? moderateloadpfx + : (nearload >= SLT_ENCUMBER) ? slightloadpfx + : (char *) 0; + gp.pickup_encumbrance = nearload; + } + if (prefix) + Sprintf(pbuf, "%s %s", prefix, verb); + + prinv(pbuf, obj, count); +} + /* - * prints a message if encumbrance changed since the last check and - * returns the new encumbrance value (from near_capacity()). + * prints a message if encumbrance changed since the last check */ -int -encumber_msg() +void +encumber_msg(void) { - static int oldcap = UNENCUMBERED; int newcap = near_capacity(); - if (oldcap < newcap) { + if (go.oldcap < newcap) { switch (newcap) { case 1: Your("movements are slowed slightly because of your load."); @@ -1620,15 +1989,15 @@ encumber_msg() break; case 3: You("%s under your heavy load. Movement is very hard.", - stagger(youmonst.data, "stagger")); + stagger(gy.youmonst.data, "stagger")); break; default: You("%s move a handspan with this load!", newcap == 4 ? "can barely" : "can't even"); break; } - context.botl = 1; - } else if (oldcap > newcap) { + disp.botl = TRUE; + } else if (go.oldcap > newcap) { switch (newcap) { case 0: Your("movements are now unencumbered."); @@ -1641,26 +2010,23 @@ encumber_msg() break; case 3: You("%s under your load. Movement is still very hard.", - stagger(youmonst.data, "stagger")); + stagger(gy.youmonst.data, "stagger")); break; } - context.botl = 1; + disp.botl = TRUE; } - oldcap = newcap; - return newcap; + go.oldcap = newcap; } /* Is there a container at x,y. Optional: return count of containers at x,y */ int -container_at(x, y, countem) -int x, y; -boolean countem; +container_at(coordxy x, coordxy y, boolean countem) { struct obj *cobj, *nobj; int container_count = 0; - for (cobj = level.objects[x][y]; cobj; cobj = nobj) { + for (cobj = svl.level.objects[x][y]; cobj; cobj = nobj) { nobj = cobj->nexthere; if (Is_container(cobj)) { container_count++; @@ -1671,18 +2037,19 @@ boolean countem; return container_count; } -STATIC_OVL boolean -able_to_loot(x, y, looting) -int x, y; -boolean looting; /* loot vs tip */ +staticfn boolean +able_to_loot( + coordxy x, coordxy y, + boolean looting) /* loot vs tip */ { const char *verb = looting ? "loot" : "tip"; + struct trap *t = t_at(x, y); - if (!can_reach_floor(TRUE)) { + if (!can_reach_floor(t && is_pit(t->ttyp))) { if (u.usteed && P_SKILL(P_RIDING) < P_BASIC) rider_cant_reach(); /* not skilled enough to reach */ else - cant_reach_floor(x, y, FALSE, TRUE); + cant_reach_floor(x, y, FALSE, TRUE, FALSE); return FALSE; } else if ((is_pool(x, y) && (looting || !Underwater)) || is_lava(x, y)) { /* at present, can't loot in water even when Underwater; @@ -1690,7 +2057,7 @@ boolean looting; /* loot vs tip */ You("cannot %s things that are deep in the %s.", verb, hliquid(is_lava(x, y) ? "lava" : "water")); return FALSE; - } else if (nolimbs(youmonst.data)) { + } else if (nolimbs(gy.youmonst.data)) { pline("Without limbs, you cannot %s anything.", verb); return FALSE; } else if (looting && !freehand()) { @@ -1701,11 +2068,11 @@ boolean looting; /* loot vs tip */ return TRUE; } -STATIC_OVL boolean -mon_beside(x, y) -int x, y; +staticfn boolean +mon_beside(coordxy x, coordxy y) { - int i, j, nx, ny; + int i, j; + coordxy nx, ny; for (i = -1; i <= 1; i++) for (j = -1; j <= 1; j++) { @@ -1717,25 +2084,66 @@ int x, y; return FALSE; } -STATIC_OVL int -do_loot_cont(cobjp, cindex, ccount) -struct obj **cobjp; -int cindex, ccount; /* index of this container (1..N), number of them (N) */ +staticfn int +do_loot_cont( + struct obj **cobjp, + int cindex, /* index of this container (1..N)... */ + int ccount) /* ...number of them (N) */ { struct obj *cobj = *cobjp; if (!cobj) - return 0; + return ECMD_OK; if (cobj->olocked) { - if (ccount < 2) + int res = ECMD_OK; + +#if 0 + if (ccount < 2 && (svl.level.objects[cobj->ox][cobj->oy] == cobj)) pline("%s locked.", cobj->lknown ? "It is" : "Hmmm, it turns out to be"); - else if (cobj->lknown) + else +#endif + if (cobj->lknown) pline("%s is locked.", The(xname(cobj))); else pline("Hmmm, %s turns out to be locked.", the(xname(cobj))); cobj->lknown = 1; - return 0; + + if (flags.autounlock) { + struct obj *otmp, *unlocktool = 0; + coordxy ox = cobj->ox, oy = cobj->oy; + + u.dz = 0; /* might be non-zero from previous command since + * #loot isn't a move command; pick_lock() cares */ + /* if both the untrap and apply_key bits are set, untrap + attempt will be performed first but we need to set up + unlocktool in case "check for trap?" is declined */ + if (((flags.autounlock & AUTOUNLOCK_APPLY_KEY) != 0 + && (unlocktool = autokey(TRUE)) != 0) + || (flags.autounlock & AUTOUNLOCK_UNTRAP) != 0) { + /* pass ox and oy to avoid direction prompt */ + if (pick_lock(unlocktool, ox, oy, cobj)) + res = ECMD_TIME; + /* attempting to untrap or unlock might trigger a trap + which destroys 'cobj'; inform caller if that happens */ + for (otmp = svl.level.objects[ox][oy]; otmp; + otmp = otmp->nexthere) + if (otmp == cobj) + break; + if (!otmp) + *cobjp = (struct obj *) 0; + return res; + } + if ((flags.autounlock & AUTOUNLOCK_FORCE) != 0 + && res != ECMD_TIME + && ccount == 1 && u_have_forceable_weapon()) { + /* single container, and we could #force it open... */ + /* note: doforce asks for confirmation */ + cmdq_add_ec(CQ_CANNED, doforce); + ga.abort_looting = TRUE; + } + } + return res; } cobj->lknown = 1; /* floor container, so no need for update_inventory() */ @@ -1747,47 +2155,56 @@ int cindex, ccount; /* index of this container (1..N), number of them (N) */ tmp = rnd(10); losehp(Maybe_Half_Phys(tmp), "carnivorous bag", KILLED_BY_AN); makeknown(BAG_OF_TRICKS); - abort_looting = TRUE; - return 1; + ga.abort_looting = TRUE; + return ECMD_TIME; } + return use_container(cobjp, FALSE, (boolean) (cindex < ccount)); +} + +/* #loot extended command */ +int +doloot(void) +{ + int res; - You("%sopen %s...", (!cobj->cknown || !cobj->lknown) ? "carefully " : "", - the(xname(cobj))); - return use_container(cobjp, 0, (boolean) (cindex < ccount)); + gl.loot_reset_justpicked = TRUE; + res = doloot_core(); + gl.loot_reset_justpicked = FALSE; + return res; } /* loot a container on the floor or loot saddle from mon. */ -int -doloot() +staticfn int +doloot_core(void) { struct obj *cobj, *nobj; - register int c = -1; + int c = -1; int timepassed = 0; coord cc; boolean underfoot = TRUE; const char *dont_find_anything = "don't find anything"; struct monst *mtmp; - char qbuf[BUFSZ]; int prev_inquiry = 0; boolean prev_loot = FALSE; int num_conts = 0; + int clr = NO_COLOR; - abort_looting = FALSE; + ga.abort_looting = FALSE; if (check_capacity((char *) 0)) { /* "Can't do that while carrying so much stuff." */ - return 0; + return ECMD_OK; } - if (nohands(youmonst.data)) { + if (nohands(gy.youmonst.data)) { You("have no hands!"); /* not `body_part(HAND)' */ - return 0; + return ECMD_OK; } if (Confusion) { if (rn2(6) && reverse_loot()) - return 1; + return ECMD_TIME; if (rn2(2)) { pline("Being confused, you find nothing to loot."); - return 1; /* costs a turn */ + return ECMD_TIME; /* costs a turn */ } /* else fallthrough to normal looting */ } cc.x = u.ux; @@ -1801,7 +2218,7 @@ doloot() boolean anyfound = FALSE; if (!able_to_loot(cc.x, cc.y, TRUE)) - return 0; + return ECMD_OK; if (Blind && !uarmg) { /* if blind and without gloves, attempting to #loot at the @@ -1813,7 +2230,7 @@ doloot() feel_cockatrice(nobj, FALSE); /* if life-saved (or poly'd into stone golem), terminate attempt to loot */ - return 1; + return ECMD_TIME; } } @@ -1826,14 +2243,14 @@ doloot() any.a_void = 0; win = create_nhwindow(NHW_MENU); - start_menu(win); + start_menu(win, MENU_BEHAVE_STANDARD); - for (cobj = level.objects[cc.x][cc.y]; cobj; + for (cobj = svl.level.objects[cc.x][cc.y]; cobj; cobj = cobj->nexthere) if (Is_container(cobj)) { any.a_obj = cobj; - add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, - doname(cobj), MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr, + doname(cobj), MENU_ITEMFLAGS_NONE); } end_menu(win, "Loot which containers?"); n = select_menu(win, PICK_ANY, &pick_list); @@ -1843,10 +2260,10 @@ doloot() for (i = 1; i <= n; i++) { cobj = pick_list[i - 1].item.a_obj; timepassed |= do_loot_cont(&cobj, i, n); - if (abort_looting) { + if (ga.abort_looting) { /* chest trap or magic bag explosion or */ free((genericptr_t) pick_list); - return timepassed; + return (timepassed ? ECMD_TIME : ECMD_OK); } } free((genericptr_t) pick_list); @@ -1854,23 +2271,15 @@ doloot() if (n != 0) c = 'y'; } else { - for (cobj = level.objects[cc.x][cc.y]; cobj; cobj = nobj) { + for (cobj = svl.level.objects[cc.x][cc.y]; cobj; cobj = nobj) { nobj = cobj->nexthere; if (Is_container(cobj)) { - c = ynq(safe_qbuf(qbuf, "There is ", " here, loot it?", - cobj, doname, ansimpleoname, - "a container")); - if (c == 'q') - return timepassed; - if (c == 'n') - continue; anyfound = TRUE; - timepassed |= do_loot_cont(&cobj, 1, 1); - if (abort_looting) + if (ga.abort_looting) /* chest trap or magic bag explosion or */ - return timepassed; + return (timepassed ? ECMD_TIME : ECMD_OK); } } if (anyfound) @@ -1884,60 +2293,61 @@ doloot() * 3.3.1 introduced directional looting for some things. */ lootmon: - if (c != 'y' && mon_beside(u.ux, u.uy)) { + if (c != 'y' && (mon_beside(u.ux, u.uy) || iflags.menu_requested)) { + boolean looted_mon = FALSE; if (!get_adjacent_loc("Loot in what direction?", "Invalid loot location", u.ux, u.uy, &cc)) - return 0; - if (cc.x == u.ux && cc.y == u.uy) { - underfoot = TRUE; - if (container_at(cc.x, cc.y, FALSE)) - goto lootcont; - } else - underfoot = FALSE; + return ECMD_OK; + underfoot = u_at(cc.x, cc.y); + if (underfoot && container_at(cc.x, cc.y, FALSE)) + goto lootcont; if (u.dz < 0) { You("%s to loot on the %s.", dont_find_anything, ceiling(cc.x, cc.y)); - timepassed = 1; - return timepassed; + return ECMD_TIME; } mtmp = m_at(cc.x, cc.y); - if (mtmp) + if (mtmp) { timepassed = loot_mon(mtmp, &prev_inquiry, &prev_loot); + if (timepassed) + looted_mon = TRUE; + } /* always use a turn when choosing a direction is impaired, - even if you've successfully targetted a saddled creature + even if you've successfully targeted a saddled creature and then answered "no" to the "remove its saddle?" prompt */ if (Confusion || Stunned) timepassed = 1; - /* Preserve pre-3.3.1 behaviour for containers. + /* Preserve pre-3.3.1 behavior for containers. * Adjust this if-block to allow container looting * from one square away to change that in the future. */ - if (!underfoot) { - if (container_at(cc.x, cc.y, FALSE)) { + if (!looted_mon) { + if (!underfoot && container_at(cc.x, cc.y, FALSE)) { if (mtmp) { You_cant("loot anything %sthere with %s in the way.", prev_inquiry ? "else " : "", mon_nam(mtmp)); - return timepassed; + return (timepassed ? ECMD_TIME : ECMD_OK); } else { You("have to be at a container to loot it."); } } else { - You("%s %sthere to loot.", dont_find_anything, - (prev_inquiry || prev_loot) ? "else " : ""); - return timepassed; + You("%s %s%shere to loot.", dont_find_anything, + (prev_inquiry || prev_loot) ? "else " : "", + !underfoot ? "t" : ""); + return (timepassed ? ECMD_TIME : ECMD_OK); } } } else if (c != 'y' && c != 'n') { You("%s %s to loot.", dont_find_anything, underfoot ? "here" : "there"); } - return timepassed; + return (timepassed ? ECMD_TIME : ECMD_OK); } /* called when attempting to #loot while confused */ -STATIC_OVL boolean -reverse_loot() +staticfn boolean +reverse_loot(void) { struct obj *goldob = 0, *coffers, *otmp, boxdummy; struct monst *mon; @@ -1945,9 +2355,9 @@ reverse_loot() int n, x = u.ux, y = u.uy; if (!rn2(3)) { - /* n objects: 1/(n+1) chance per object plus 1/(n+1) to fall off end - */ - for (n = inv_cnt(TRUE), otmp = invent; otmp; --n, otmp = otmp->nobj) + /* n objects: 1/(n+1) chance per object, 1/(n+1) to fall off end */ + for (n = inv_cnt(TRUE), otmp = gi.invent; otmp; + --n, otmp = otmp->nobj) if (!rn2(n + 1)) { prinv("You find old loot:", otmp, 0L); return TRUE; @@ -1956,7 +2366,7 @@ reverse_loot() } /* find a money object to mess with */ - for (goldob = invent; goldob; goldob = goldob->nobj) + for (goldob = gi.invent; goldob; goldob = goldob->nobj) if (goldob->oclass == COIN_CLASS) { contribution = ((long) rnd(5) * goldob->quan + 4L) / 5L; if (contribution < goldob->quan) @@ -1966,34 +2376,38 @@ reverse_loot() if (!goldob) return FALSE; + /* gold might be quivered; dropping would un-wear it, but freeinv() + expects caller to do that; do so now */ + remove_worn_item(goldob, FALSE); + if (!IS_THRONE(levl[x][y].typ)) { dropx(goldob); /* the dropped gold might have fallen to lower level */ if (g_at(x, y)) pline("Ok, now there is loot here."); } else { - /* find original coffers chest if present, otherwise use nearest one */ + /* find original coffers chest if present, otherwise use nearest */ otmp = 0; for (coffers = fobj; coffers; coffers = coffers->nobj) if (coffers->otyp == CHEST) { if (coffers->spe == 2) break; /* a throne room chest */ - if (!otmp - || distu(coffers->ox, coffers->oy) - < distu(otmp->ox, otmp->oy)) + if (!otmp || (distu(coffers->ox, coffers->oy) + < distu(otmp->ox, otmp->oy))) otmp = coffers; /* remember closest ordinary chest */ } if (!coffers) coffers = otmp; if (coffers) { + SetVoice((struct monst *) 0, 0, 80, 0); verbalize("Thank you for your contribution to reduce the debt."); freeinv(goldob); (void) add_to_container(coffers, goldob); coffers->owt = weight(coffers); coffers->cknown = 0; if (!coffers->olocked) { - boxdummy = zeroobj, boxdummy.otyp = SPE_WIZARD_LOCK; + boxdummy = cg.zeroobj, boxdummy.otyp = SPE_WIZARD_LOCK; (void) boxlock(coffers, &boxdummy); } } else if (levl[x][y].looted != T_LOOTED @@ -2014,10 +2428,7 @@ reverse_loot() /* loot_mon() returns amount of time passed. */ int -loot_mon(mtmp, passed_info, prev_loot) -struct monst *mtmp; -int *passed_info; -boolean *prev_loot; +loot_mon(struct monst *mtmp, int *passed_info, boolean *prev_loot) { int c = -1; int timepassed = 0; @@ -2029,15 +2440,13 @@ boolean *prev_loot; * *prev_loot is set to TRUE if something was actually acquired in here. */ if (mtmp && mtmp != u.usteed && (otmp = which_armor(mtmp, W_SADDLE))) { - long unwornmask; - if (passed_info) *passed_info = 1; Sprintf(qbuf, "Do you want to remove the saddle from %s?", x_monnam(mtmp, ARTICLE_THE, (char *) 0, SUPPRESS_SADDLE, FALSE)); - if ((c = yn_function(qbuf, ynqchars, 'n')) == 'y') { - if (nolimbs(youmonst.data)) { + if ((c = yn_function(qbuf, ynqchars, 'n', TRUE)) == 'y') { + if (nolimbs(gy.youmonst.data)) { You_cant("do that without limbs."); /* not body_part(HAND) */ return 0; } @@ -2048,12 +2457,10 @@ boolean *prev_loot; /* the attempt costs you time */ return 1; } - obj_extract_self(otmp); - if ((unwornmask = otmp->owornmask) != 0L) { - mtmp->misc_worn_check &= ~unwornmask; - otmp->owornmask = 0L; - update_mon_intrinsics(mtmp, otmp, FALSE, FALSE); - } + extract_from_minvent(mtmp, otmp, TRUE, FALSE); + if (flags.verbose) + You("take %s off of %s.", + thesimpleoname(otmp), mon_nam(mtmp)); otmp = hold_another_object(otmp, "You drop %s!", doname(otmp), (const char *) 0); nhUse(otmp); @@ -2077,10 +2484,8 @@ boolean *prev_loot; * Decide whether an object being placed into a magic bag will cause * it to explode. If the object is a bag itself, check recursively. */ -STATIC_OVL boolean -mbag_explodes(obj, depthin) -struct obj *obj; -int depthin; +staticfn boolean +mbag_explodes(struct obj *obj, int depthin) { /* these won't cause an explosion when they're empty */ if ((obj->otyp == WAN_CANCELLATION || obj->otyp == BAG_OF_TRICKS) @@ -2101,10 +2506,35 @@ int depthin; return FALSE; } -STATIC_OVL long -boh_loss(container, held) -struct obj *container; -int held; +staticfn boolean +is_boh_item_gone(void) +{ + return (boolean) (!rn2(13)); +} + +/* Scatter most of Bag of holding contents around. Some items will be + destroyed with the same chance as looting a cursed bag. */ +staticfn void +do_boh_explosion(struct obj *boh, boolean on_floor) +{ + struct obj *otmp, *nobj; + + boh->in_use = 1; /* in case scatter() leads to bones creation */ + for (otmp = boh->cobj; otmp; otmp = nobj) { + nobj = otmp->nobj; + if (is_boh_item_gone()) { + obj_extract_self(otmp); + mbag_item_gone(!on_floor, otmp, TRUE); + } else { + otmp->ox = u.ux, otmp->oy = u.uy; + (void) scatter(u.ux, u.uy, 4, MAY_HIT | MAY_DESTROY, otmp); + } + } + /* boh is about to be deleted so no need to reset its in_use flag here */ +} + +staticfn long +boh_loss(struct obj *container, boolean held) { /* sometimes toss objects if a cursed magic bag */ if (Is_mbag(container) && container->cursed && Has_contents(container)) { @@ -2113,9 +2543,9 @@ int held; for (curr = container->cobj; curr; curr = otmp) { otmp = curr->nobj; - if (!rn2(13)) { + if (is_boh_item_gone()) { obj_extract_self(curr); - loss += mbag_item_gone(held, curr); + loss += mbag_item_gone(held, curr, FALSE); } } return loss; @@ -2124,21 +2554,20 @@ int held; } /* Returns: -1 to stop, 1 item was inserted, 0 item was not inserted. */ -STATIC_PTR int -in_container(obj) -register struct obj *obj; +staticfn int +in_container(struct obj *obj) { - boolean floor_container = !carried(current_container); + boolean floor_container = !carried(gc.current_container); boolean was_unpaid = FALSE; char buf[BUFSZ]; - if (!current_container) { - impossible(" no current_container?"); + if (!gc.current_container) { + impossible(" no gc.current_container?"); return 0; } else if (obj == uball || obj == uchain) { You("must be kidding."); return 0; - } else if (obj == current_container) { + } else if (obj == gc.current_container) { pline("That would be an interesting topological exercise."); return 0; } else if (obj->owornmask & (W_ARMOR | W_ACCESSORY)) { @@ -2186,14 +2615,9 @@ register struct obj *obj; /* boxes, boulders, and big statues can't fit into any container */ if (obj->otyp == ICE_BOX || Is_box(obj) || obj->otyp == BOULDER || (obj->otyp == STATUE && bigmonst(&mons[obj->corpsenm]))) { - /* - * xname() uses a static result array. Save obj's name - * before current_container's name is computed. Don't - * use the result of strcpy() within You() --- the order - * of evaluation of the parameters is undefined. - */ + /* consumes multiple obufs but not enough to overwrite the result */ Strcpy(buf, the(xname(obj))); - You("cannot fit %s into %s.", buf, the(xname(current_container))); + You("cannot fit %s into %s.", buf, the(xname(gc.current_container))); return 0; } @@ -2207,74 +2631,84 @@ register struct obj *obj; if (obj->oclass != COIN_CLASS) { /* sellobj() will take an unpaid item off the shop bill */ was_unpaid = obj->unpaid ? TRUE : FALSE; - /* don't sell when putting the item into your own container, - * but handle billing correctly */ - sellobj_state(current_container->no_charge - ? SELL_DONTSELL : SELL_DELIBERATE); + if (gs.sellobj_first) { + /* don't sell when putting the item into your own container, + but handle billing correctly */ + sellobj_state(gc.current_container->no_charge + ? SELL_DONTSELL : SELL_DELIBERATE); + gs.sellobj_first = FALSE; + } sellobj(obj, u.ux, u.uy); - sellobj_state(SELL_NORMAL); } } if (Icebox && !age_is_relative(obj)) { - obj->age = monstermoves - obj->age; /* actual age */ + obj->age = svm.moves - obj->age; /* actual age */ /* stop any corpse timeouts when frozen */ - if (obj->otyp == CORPSE && obj->timed) { - long rot_alarm = stop_timer(ROT_CORPSE, obj_to_any(obj)); - - (void) stop_timer(REVIVE_MON, obj_to_any(obj)); - /* mark a non-reviving corpse as such */ - if (rot_alarm) - obj->norevive = 1; + if (obj->otyp == CORPSE) { + if (obj->timed) { + (void) stop_timer(ROT_CORPSE, obj_to_any(obj)); + (void) stop_timer(REVIVE_MON, obj_to_any(obj)); + } + /* if this is the corpse of a cancelled ice troll, uncancel it */ + if (obj->corpsenm == PM_ICE_TROLL && has_omonst(obj)) + OMONST(obj)->mcan = 0; + } else if (obj->globby && obj->timed) { + (void) stop_timer(SHRINK_GLOB, obj_to_any(obj)); } - } else if (Is_mbag(current_container) && mbag_explodes(obj, 0)) { + } else if (Is_mbag(gc.current_container) && mbag_explodes(obj, 0)) { + livelog_printf(LL_ACHIEVE, "just blew up %s bag of holding", uhis()); /* explicitly mention what item is triggering the explosion */ - pline("As you put %s inside, you are blasted by a magical explosion!", - doname(obj)); + urgent_pline( + "As you put %s inside, you are blasted by a magical explosion!", + doname(obj)); /* did not actually insert obj yet */ if (was_unpaid) addtobill(obj, FALSE, FALSE, TRUE); + if (obj->otyp == BAG_OF_HOLDING) /* one bag of holding into another */ + do_boh_explosion(obj, (boolean) (obj->where == OBJ_FLOOR)); obfree(obj, (struct obj *) 0); /* if carried, shop goods will be flagged 'unpaid' and obfree() will handle bill issues, but if on floor, we need to put them on bill - before deleting them (non-shop items will be flagged 'no_charge') */ - if (floor_container - && costly_spot(current_container->ox, current_container->oy)) { + before deleting them (non-shop items will be flagged 'no_charge')*/ + if (floor_container && costly_spot(gc.current_container->ox, + gc.current_container->oy)) { struct obj save_no_charge; - save_no_charge.no_charge = current_container->no_charge; - addtobill(current_container, FALSE, FALSE, FALSE); + save_no_charge.no_charge = gc.current_container->no_charge; + addtobill(gc.current_container, FALSE, FALSE, FALSE); /* addtobill() clears no charge; we need to set it back so that useupf() doesn't double bill */ - current_container->no_charge = save_no_charge.no_charge; + gc.current_container->no_charge = save_no_charge.no_charge; } - delete_contents(current_container); + do_boh_explosion(gc.current_container, floor_container); + if (!floor_container) - useup(current_container); - else if (obj_here(current_container, u.ux, u.uy)) - useupf(current_container, current_container->quan); + useup(gc.current_container); + else if (obj_here(gc.current_container, u.ux, u.uy)) + useupf(gc.current_container, gc.current_container->quan); else panic("in_container: bag not found."); losehp(d(6, 6), "magical explosion", KILLED_BY_AN); - current_container = 0; /* baggone = TRUE; */ + gc.current_container = 0; /* baggone = TRUE; */ } - if (current_container) { - Strcpy(buf, the(xname(current_container))); + if (gc.current_container) { + Strcpy(buf, the(xname(gc.current_container))); You("put %s into %s.", doname(obj), buf); /* gold in container always needs to be added to credit */ if (floor_container && obj->oclass == COIN_CLASS) - sellobj(obj, current_container->ox, current_container->oy); - (void) add_to_container(current_container, obj); - current_container->owt = weight(current_container); + sellobj(obj, gc.current_container->ox, gc.current_container->oy); + (void) add_to_container(gc.current_container, obj); + gc.current_container->owt = weight(gc.current_container); } /* gold needs this, and freeinv() many lines above may cause * the encumbrance to disappear from the status, so just always * update status immediately. */ bot(); - return (current_container ? 1 : -1); + return (gc.current_container ? 1 : -1); } /* askchain() filter used by in_container(); @@ -2283,37 +2717,35 @@ register struct obj *obj; * also used by getobj() when picking a single item to stash */ int -ck_bag(obj) -struct obj *obj; +ck_bag(struct obj *obj) { - return (current_container && obj != current_container); + return (gc.current_container && obj != gc.current_container); } /* Returns: -1 to stop, 1 item was removed, 0 item was not removed. */ -STATIC_PTR int -out_container(obj) -register struct obj *obj; +staticfn int +out_container(struct obj *obj) { - register struct obj *otmp; - boolean is_gold = (obj->oclass == COIN_CLASS); - int res, loadlev; + struct obj *otmp; + int res; long count; + boolean is_gold = (obj->oclass == COIN_CLASS); - if (!current_container) { - impossible(" no current_container?"); + if (!gc.current_container) { + impossible(" no gc.current_container?"); return -1; } else if (is_gold) { obj->owt = weight(obj); } - if (obj->oartifact && !touch_artifact(obj, &youmonst)) + if (obj->oartifact && !touch_artifact(obj, &gy.youmonst)) return 0; if (fatal_corpse_mistake(obj, FALSE)) return -1; count = obj->quan; - if ((res = lift_object(obj, current_container, &count, FALSE)) <= 0) + if ((res = lift_object(obj, gc.current_container, &count, FALSE)) <= 0) return res; if (obj->quan != count && obj->otyp != LOADSTONE) @@ -2321,27 +2753,22 @@ register struct obj *obj; /* Remove the object from the list. */ obj_extract_self(obj); - current_container->owt = weight(current_container); + gc.current_container->owt = weight(gc.current_container); if (Icebox) removed_from_icebox(obj); - if (!obj->unpaid && !carried(current_container) - && costly_spot(current_container->ox, current_container->oy)) { - obj->ox = current_container->ox; - obj->oy = current_container->oy; + if (!obj->unpaid && !carried(gc.current_container) + && costly_spot(gc.current_container->ox, gc.current_container->oy)) { + obj->ox = gc.current_container->ox; + obj->oy = gc.current_container->oy; addtobill(obj, FALSE, FALSE, FALSE); } if (is_pick(obj)) pick_pick(obj); /* shopkeeper feedback */ otmp = addinv(obj); - loadlev = near_capacity(); - prinv(loadlev ? ((loadlev < MOD_ENCUMBER) - ? "You have a little trouble removing" - : "You have much trouble removing") - : (char *) 0, - otmp, count); + pickup_prinv(otmp, count, "removing"); if (is_gold) { bot(); /* update character's gold piece count immediately */ @@ -2350,30 +2777,40 @@ register struct obj *obj; } /* taking a corpse out of an ice box needs a couple of adjustments */ -STATIC_OVL void -removed_from_icebox(obj) -struct obj *obj; +void +removed_from_icebox(struct obj *obj) { if (!age_is_relative(obj)) { - obj->age = monstermoves - obj->age; /* actual age */ - if (obj->otyp == CORPSE) + obj->age = svm.moves - obj->age; /* actual age */ + if (obj->otyp == CORPSE) { + struct monst *m = get_mtraits(obj, FALSE); + boolean iceT = m ? (m->data == &mons[PM_ICE_TROLL]) + : (obj->corpsenm == PM_ICE_TROLL); + + /* start a revive timer if this corpse is for an ice troll, + otherwise start a rot-away timer (even for other trolls) */ + obj->norevive = iceT ? 0 : 1; start_corpse_timeout(obj); + } else if (obj->globby) { + /* non-frozen globs gradually shrink away to nothing */ + start_glob_timeout(obj, 0L); + } } } /* an object inside a cursed bag of holding is being destroyed */ -STATIC_OVL long -mbag_item_gone(held, item) -int held; -struct obj *item; +staticfn long +mbag_item_gone(boolean held, struct obj *item, boolean silent) { struct monst *shkp; long loss = 0L; - if (item->dknown) - pline("%s %s vanished!", Doname2(item), otense(item, "have")); - else - You("%s %s disappear!", Blind ? "notice" : "see", doname(item)); + if (!silent) { + if (item->dknown) + pline("%s %s vanished!", Doname2(item), otense(item, "have")); + else + You("%s %s disappear!", Blind ? "notice" : "see", doname(item)); + } if (*u.ushops && (shkp = shop_keeper(*u.ushops)) != 0) { if (held ? (boolean) item->unpaid : costly_spot(u.ux, u.uy)) @@ -2386,14 +2823,12 @@ struct obj *item; /* used for #loot/apply, #tip, and final disclosure */ void -observe_quantum_cat(box, makecat, givemsg) -struct obj *box; -boolean makecat, givemsg; +observe_quantum_cat(struct obj *box, boolean makecat, boolean givemsg) { static NEARDATA const char sc[] = "Schroedinger's Cat"; struct obj *deadcat; struct monst *livecat = 0; - xchar ox, oy; + coordxy ox, oy; boolean itsalive = !rn2(2); if (get_obj_location(box, &ox, &oy, 0)) @@ -2409,7 +2844,7 @@ boolean makecat, givemsg; if (itsalive) { if (makecat) livecat = makemon(&mons[PM_HOUSECAT], box->ox, box->oy, - NO_MINVENT | MM_ADJACENTOK); + NO_MINVENT | MM_ADJACENTOK | MM_NOMSG); if (livecat) { livecat->mpeaceful = 1; set_malign(livecat); @@ -2428,20 +2863,34 @@ boolean makecat, givemsg; } box->owt = weight(box); box->spe = 0; + + if (!svc.context.mon_moving) { + /* give experience points for releasing live cat; slightly + different amount from what is given for "killing" it */ + more_experienced(10, 20); /* 10:current exp; 20:score bonus */ + newexplevel(); + } } } else { box->spe = 0; /* now an ordinary box (with a cat corpse inside) */ + if (givemsg) + pline_The("%s inside the box is dead!", + Hallucination ? rndmonnam((char *) 0) : "housecat"); if (deadcat) { /* set_corpsenm() will start the rot timer that was removed when makemon() created SchroedingersBox; start it from now rather than from when this special corpse got created */ - deadcat->age = monstermoves; + deadcat->age = svm.moves; set_corpsenm(deadcat, PM_HOUSECAT); - deadcat = oname(deadcat, sc); + deadcat = oname(deadcat, sc, ONAME_NO_FLAGS); + + if (!svc.context.mon_moving) { + /* give experience points for the death of the cat since + that has been finalized by the hero opening the box */ + more_experienced(20, 10); /* 20:current exp; 10:score bonus */ + newexplevel(); + } } - if (givemsg) - pline_The("%s inside the box is dead!", - Hallucination ? rndmonnam((char *) 0) : "housecat"); } nhUse(deadcat); return; @@ -2451,17 +2900,15 @@ boolean makecat, givemsg; /* used by askchain() to check for magic bag explosion */ boolean -container_gone(fn) -int FDECL((*fn), (OBJ_P)); +container_gone(int (*fn)(OBJ_P)) { /* result is only meaningful while use_container() is executing */ return ((fn == in_container || fn == out_container) - && !current_container); + && !gc.current_container); } -STATIC_OVL void -explain_container_prompt(more_containers) -boolean more_containers; +staticfn void +explain_container_prompt(boolean more_containers) { static const char *const explaintext[] = { "Container actions:", @@ -2493,9 +2940,9 @@ boolean more_containers; } boolean -u_handsy() +u_handsy(void) { - if (nohands(youmonst.data)) { + if (nohands(gy.youmonst.data)) { You("have no hands!"); /* not `body_part(HAND)' */ return FALSE; } else if (!freehand()) { @@ -2505,26 +2952,41 @@ u_handsy() return TRUE; } -static const char stashable[] = { ALLOW_COUNT, COIN_CLASS, ALL_CLASSES, 0 }; +/* getobj callback for object to be stashed into a container */ +staticfn int +stash_ok(struct obj *obj) +{ + if (!obj) + return GETOBJ_EXCLUDE; + + /* downplay the container being stashed into */ + if (!ck_bag(obj)) + return GETOBJ_EXCLUDE_SELECTABLE; + /* Possible extension: downplay things too big to fit into containers (in + * which case extract in_container()'s logic.) */ + + return GETOBJ_SUGGEST; +} int -use_container(objp, held, more_containers) -struct obj **objp; -int held; -boolean more_containers; /* True iff #loot multiple and this isn't last one */ +use_container( + struct obj **objp, + boolean held, + boolean more_containers) /* True iff #loot multiple and this isn't last */ { struct obj *otmp, *obj = *objp; boolean quantum_cat, cursed_mbag, loot_out, loot_in, loot_in_first, stash_one, inokay, outokay, outmaybe; char c, emptymsg[BUFSZ], qbuf[QBUFSZ], pbuf[QBUFSZ], xbuf[QBUFSZ]; - int used = 0; + int used = ECMD_OK; long loss; - abort_looting = FALSE; + ga.abort_looting = FALSE; + gs.sellobj_first = TRUE; /* in_container() should call sellobj_state() */ emptymsg[0] = '\0'; if (!u_handsy()) - return 0; + return ECMD_OK; if (!obj->lknown) { /* do this in advance */ obj->lknown = 1; @@ -2535,47 +2997,51 @@ boolean more_containers; /* True iff #loot multiple and this isn't last one */ pline("%s locked.", Tobjnam(obj, "are")); if (held) You("must put it down to unlock."); - return 0; + return ECMD_OK; } else if (obj->otrapped) { if (held) You("open %s...", the(xname(obj))); (void) chest_trap(obj, HAND, FALSE); /* even if the trap fails, you've used up this turn */ - if (multi >= 0) { /* in case we didn't become paralyzed */ + if (gm.multi >= 0) { /* in case we didn't become paralyzed */ nomul(-1); - multi_reason = "opening a container"; - nomovemsg = ""; + gm.multi_reason = "opening a container"; + gn.nomovemsg = ""; } - abort_looting = TRUE; - return 1; + ga.abort_looting = TRUE; + return ECMD_TIME; } - current_container = obj; /* for use by in/out_container */ + gc.current_container = obj; /* for use by in/out_container */ /* * From here on out, all early returns go through 'containerdone:'. */ /* check for Schroedinger's Cat */ - quantum_cat = SchroedingersBox(current_container); + quantum_cat = SchroedingersBox(gc.current_container); if (quantum_cat) { - observe_quantum_cat(current_container, TRUE, TRUE); - used = 1; + observe_quantum_cat(gc.current_container, TRUE, TRUE); + used = ECMD_TIME; } - cursed_mbag = Is_mbag(current_container) - && current_container->cursed - && Has_contents(current_container); + cursed_mbag = Is_mbag(gc.current_container) + && gc.current_container->cursed + && Has_contents(gc.current_container); if (cursed_mbag - && (loss = boh_loss(current_container, held)) != 0) { - used = 1; + && (loss = boh_loss(gc.current_container, held)) != 0) { + used = ECMD_TIME; You("owe %ld %s for lost merchandise.", loss, currency(loss)); - current_container->owt = weight(current_container); - } - inokay = (invent != 0 - && !(invent == current_container && !current_container->nobj)); - outokay = Has_contents(current_container); + gc.current_container->owt = weight(gc.current_container); + } + /* might put something in if carrying anything other than just the + container itself (invent is not the container or has a next object) */ + inokay = (gi.invent != 0 && (gi.invent != gc.current_container + || gi.invent->nobj)); + /* might take something out if container isn't empty */ + outokay = Has_contents(gc.current_container); if (!outokay) /* preformat the empty-container message */ - Sprintf(emptymsg, "%s is %sempty.", Ysimple_name2(current_container), + Sprintf(emptymsg, "%s is %sempty.", + Ysimple_name2(gc.current_container), (quantum_cat || cursed_mbag) ? "now " : ""); /* @@ -2586,7 +3052,8 @@ boolean more_containers; /* True iff #loot multiple and this isn't last one */ * that it's empty (latter can change on subsequent * iterations if player picks ':' response); * include the put-in choices ('i','s') if hero - * carries any inventory (including gold); + * carries any inventory (including gold) aside from + * the container itself; * include do-both when 'o' is available, even if * inventory is empty--taking out could alter that; * include do-both-reversed when 'i' is available, @@ -2594,7 +3061,7 @@ boolean more_containers; /* True iff #loot multiple and this isn't last one */ * include the next container choice ('n') when * relevant, and make it the default; * always include the quit choice ('q'), and make - * it the default if there's no next containter; + * it the default if there's no next container; * include the help choice (" or ?") if `cmdassist' * run-time option is set; * (Player can pick any of (o,i,b,r,n,s,?) even when @@ -2605,13 +3072,13 @@ boolean more_containers; /* True iff #loot multiple and this isn't last one */ * is empty. Do what with it? [:irs nq or ?] */ for (;;) { /* repeats iff '?' or ':' gets chosen */ - outmaybe = (outokay || !current_container->cknown); + outmaybe = (outokay || !gc.current_container->cknown); if (!outmaybe) (void) safe_qbuf(qbuf, (char *) 0, " is empty. Do what with it?", - current_container, Yname2, Ysimple_name2, + gc.current_container, Yname2, Ysimple_name2, "This"); else - (void) safe_qbuf(qbuf, "Do what with ", "?", current_container, + (void) safe_qbuf(qbuf, "Do what with ", "?", gc.current_container, yname, ysimple_name, "it"); /* ask player about what to do with this container */ if (flags.menu_style == MENU_PARTIAL @@ -2621,8 +3088,10 @@ boolean more_containers; /* True iff #loot multiple and this isn't last one */ trying to do both will yield proper feedback */ c = 'b'; } else { - c = in_or_out_menu(qbuf, current_container, outmaybe, inokay, - (boolean) (used != 0), more_containers); + c = in_or_out_menu(qbuf, gc.current_container, + outmaybe, inokay, + (boolean) (used != ECMD_OK), + more_containers); } } else { /* TRADITIONAL or COMBINATION */ xbuf[0] = '\0'; /* list of extra acceptable responses */ @@ -2642,21 +3111,21 @@ boolean more_containers; /* True iff #loot multiple and this isn't last one */ Strcat(xbuf, "?"); if (*xbuf) Strcat(strcat(pbuf, "\033"), xbuf); - c = yn_function(qbuf, pbuf, more_containers ? 'n' : 'q'); + c = yn_function(qbuf, pbuf, more_containers ? 'n' : 'q', TRUE); } /* PARTIAL|FULL vs other modes */ if (c == '?') { explain_container_prompt(more_containers); } else if (c == ':') { /* note: will set obj->cknown */ - if (!current_container->cknown) - used = 1; /* gaining info */ - container_contents(current_container, FALSE, FALSE, TRUE); + if (!gc.current_container->cknown) + used = ECMD_TIME; /* gaining info */ + container_contents(gc.current_container, FALSE, FALSE, TRUE); } else break; } /* loop until something other than '?' or ':' is picked */ if (c == 'q') - abort_looting = TRUE; + ga.abort_looting = TRUE; if (c == 'n' || c == 'q') /* [not strictly needed; falling thru works] */ goto containerdone; loot_out = (c == 'o' || c == 'b' || c == 'r'); @@ -2666,11 +3135,11 @@ boolean more_containers; /* True iff #loot multiple and this isn't last one */ /* out-only or out before in */ if (loot_out && !loot_in_first) { - if (!Has_contents(current_container)) { + if (!Has_contents(gc.current_container)) { pline1(emptymsg); /* is empty. */ - if (!current_container->cknown) - used = 1; - current_container->cknown = 1; + if (!gc.current_container->cknown) + used = ECMD_TIME; + gc.current_container->cknown = 1; } else { add_valid_menu_class(0); /* reset */ if (flags.menu_style == MENU_TRADITIONAL) @@ -2679,11 +3148,14 @@ boolean more_containers; /* True iff #loot multiple and this isn't last one */ used |= (menu_loot(0, FALSE) > 0); add_valid_menu_class(0); } + /* recalculate 'inokay' in case something was just taken out and + inventory is no longer empty or no longer just the container */ + inokay = (gi.invent && (gi.invent != gc.current_container + || gi.invent->nobj)); } - if ((loot_in || stash_one) - && (!invent || (invent == current_container && !invent->nobj))) { - You("don't have anything%s to %s.", invent ? " else" : "", + if ((loot_in || stash_one) && !inokay) { + You("don't have anything%s to %s.", gi.invent ? " else" : "", stash_one ? "stash" : "put in"); loot_in = stash_one = FALSE; } @@ -2701,7 +3173,8 @@ boolean more_containers; /* True iff #loot multiple and this isn't last one */ add_valid_menu_class(0); } else if (stash_one) { /* put one item into container */ - if ((otmp = getobj(stashable, "stash")) != 0) { + if ((otmp = getobj("stash", stash_ok, + GETOBJ_PROMPT | GETOBJ_ALLOWCNT)) != 0) { if (in_container(otmp)) { used = 1; } else { @@ -2712,16 +3185,16 @@ boolean more_containers; /* True iff #loot multiple and this isn't last one */ } } /* putting something in might have triggered magic bag explosion */ - if (!current_container) + if (!gc.current_container) loot_out = FALSE; /* out after in */ if (loot_out && loot_in_first) { - if (!Has_contents(current_container)) { + if (!Has_contents(gc.current_container)) { pline1(emptymsg); /* is empty. */ - if (!current_container->cknown) + if (!gc.current_container->cknown) used = 1; - current_container->cknown = 1; + gc.current_container->cknown = 1; } else { add_valid_menu_class(0); /* reset */ if (flags.menu_style == MENU_TRADITIONAL) @@ -2738,48 +3211,49 @@ boolean more_containers; /* True iff #loot multiple and this isn't last one */ whatever was already inside, now we suddenly do. That can't be helped unless we want to track things item by item and then deal with containers whose contents are "partly known". */ - if (current_container) - current_container->cknown = 1; + if (gc.current_container) + gc.current_container->cknown = 1; update_inventory(); } - *objp = current_container; /* might have become null */ - if (current_container) - current_container = 0; /* avoid hanging on to stale pointer */ + sellobj_state(SELL_NORMAL); /* in case in_container() set it */ + *objp = gc.current_container; /* might have become null */ + if (gc.current_container) + gc.current_container = 0; /* avoid hanging on to stale pointer */ else - abort_looting = TRUE; + ga.abort_looting = TRUE; return used; } /* loot current_container (take things out or put things in), by prompting */ -STATIC_OVL int -traditional_loot(put_in) -boolean put_in; +staticfn int +traditional_loot(boolean put_in) { - int FDECL((*actionfunc), (OBJ_P)), FDECL((*checkfunc), (OBJ_P)); + int (*actionfunc)(OBJ_P), (*checkfunc)(OBJ_P); struct obj **objlist; char selection[MAXOCLASSES + 10]; /* +10: room for B,U,C,X plus slop */ const char *action; boolean one_by_one, allflag; - int used = 0, menu_on_request = 0; + int used = ECMD_OK, menu_on_request = 0; if (put_in) { action = "put in"; - objlist = &invent; + objlist = &gi.invent; actionfunc = in_container; checkfunc = ck_bag; } else { action = "take out"; - objlist = &(current_container->cobj); + objlist = &(gc.current_container->cobj); actionfunc = out_container; - checkfunc = (int FDECL((*), (OBJ_P))) 0; + checkfunc = (int (*)(OBJ_P)) 0; + gp.pickup_encumbrance = 0; /* used to limit verbosity */ } if (query_classes(selection, &one_by_one, &allflag, action, *objlist, FALSE, &menu_on_request)) { if (askchain(objlist, (one_by_one ? (char *) 0 : selection), allflag, actionfunc, checkfunc, 0, action)) - used = 1; + used = ECMD_TIME; } else if (menu_on_request < 0) { used = (menu_loot(menu_on_request, put_in) > 0); } @@ -2787,103 +3261,146 @@ boolean put_in; } /* loot current_container (take things out or put things in), using a menu */ -STATIC_OVL int -menu_loot(retry, put_in) -int retry; -boolean put_in; +staticfn int +menu_loot(int retry, boolean put_in) { int n, i, n_looted = 0; - boolean all_categories = TRUE, loot_everything = FALSE; + boolean all_categories = TRUE, loot_everything = FALSE, autopick = FALSE; char buf[BUFSZ]; + boolean loot_justpicked = FALSE; const char *action = put_in ? "Put in" : "Take out"; struct obj *otmp, *otmp2; menu_item *pick_list; int mflags, res; - long count; + long count = 0; + gp.pickup_encumbrance = 0; /* used by out_container(); no harm in + * zeroing it if about to use in_container() */ if (retry) { all_categories = (retry == -2); } else if (flags.menu_style == MENU_FULL) { all_categories = FALSE; Sprintf(buf, "%s what type of objects?", action); - mflags = (ALL_TYPES | UNPAID_TYPES | BUCX_TYPES | CHOOSE_ALL); - n = query_category(buf, put_in ? invent : current_container->cobj, + mflags = (ALL_TYPES | UNPAID_TYPES | BUCX_TYPES | CHOOSE_ALL + | JUSTPICKED ); + n = query_category(buf, + put_in ? gi.invent : gc.current_container->cobj, mflags, &pick_list, PICK_ANY); + /* when paranoid_confirm:A is set, 'A' by itself implies + 'A'+'a' which will be followed by a confirmation prompt; + when that option isn't set, 'A' by itself is rejected + by query_categorry() and result here will be n==0 */ if (!n) - return 0; + return ECMD_OK; /* no non-autopick category filters specified */ + for (i = 0; i < n; i++) { - if (pick_list[i].item.a_int == 'A') - loot_everything = TRUE; - else if (pick_list[i].item.a_int == ALL_TYPES_SELECTED) + if (pick_list[i].item.a_int == 'A') { + loot_everything = autopick = TRUE; + } else if (put_in && pick_list[i].item.a_int == 'P') { + loot_justpicked = TRUE; + count = max(0, pick_list[i].count); + add_valid_menu_class(pick_list[i].item.a_int); + loot_everything = FALSE; + } else if (pick_list[i].item.a_int == ALL_TYPES_SELECTED) { all_categories = TRUE; - else + } else { add_valid_menu_class(pick_list[i].item.a_int); + loot_everything = FALSE; + } } free((genericptr_t) pick_list); } - if (loot_everything) { + if (autopick) { + int (*inout_func)(struct obj *); /* in_container or out_container */ + struct obj *firstobj; + if (!put_in) { - current_container->cknown = 1; - for (otmp = current_container->cobj; otmp; otmp = otmp2) { - otmp2 = otmp->nobj; - res = out_container(otmp); - if (res < 0) - break; - n_looted += res; - } + gc.current_container->cknown = 1; + inout_func = out_container; + firstobj = gc.current_container->cobj; } else { - for (otmp = invent; otmp && current_container; otmp = otmp2) { - otmp2 = otmp->nobj; - res = in_container(otmp); + inout_func = in_container; + firstobj = gi.invent; + } + /* + * Note: for put_in, current_container might be destroyed during + * mid-traversal by a magic bag explosion. + * Note too: items are processed in internal list order rather + * than menu display order ('sortpack') or 'sortloot' order; + * for put_in that should be item->invlet order so reasonable. + */ + for (otmp = firstobj; otmp && gc.current_container; otmp = otmp2) { + otmp2 = otmp->nobj; + if (loot_everything || all_categories || allow_category(otmp)) { + res = (*inout_func)(otmp); if (res < 0) break; n_looted += res; } } + } else if (put_in && loot_justpicked + && count_justpicked(gi.invent) == 1) { + otmp = find_justpicked(gi.invent); + if (otmp) { + n_looted = 1; + if (count > 0 && count < otmp->quan) { + otmp = splitobj(otmp, count); + } + (void) in_container(otmp); + /* return value doesn't matter, even if container blew up */ + } } else { - mflags = INVORDER_SORT; + mflags = INVORDER_SORT | INCLUDE_VENOM; if (put_in && flags.invlet_constant) mflags |= USE_INVLET; + if (put_in && loot_justpicked) + mflags |= JUSTPICKED; if (!put_in) - current_container->cknown = 1; + gc.current_container->cknown = 1; Sprintf(buf, "%s what?", action); - n = query_objlist(buf, put_in ? &invent : &(current_container->cobj), + n = query_objlist(buf, + put_in ? &gi.invent : &(gc.current_container->cobj), mflags, &pick_list, PICK_ANY, all_categories ? allow_all : allow_category); if (n) { n_looted = n; for (i = 0; i < n; i++) { otmp = pick_list[i].item.a_obj; + assert(otmp != 0); count = pick_list[i].count; if (count > 0 && count < otmp->quan) { otmp = splitobj(otmp, count); /* special split case also handled by askchain() */ } res = put_in ? in_container(otmp) : out_container(otmp); - if (res < 0) { - if (!current_container) { + if (res <= 0) { + if (!gc.current_container) { /* otmp caused current_container to explode; both are now gone */ otmp = 0; /* and break loop */ } else if (otmp && otmp != pick_list[i].item.a_obj) { /* split occurred, merge again */ - (void) merged(&pick_list[i].item.a_obj, &otmp); + (void) unsplitobj(otmp); } - break; + if (res < 0) + break; } } free((genericptr_t) pick_list); } } - return n_looted; + return n_looted ? ECMD_TIME : ECMD_OK; } -STATIC_OVL char -in_or_out_menu(prompt, obj, outokay, inokay, alreadyused, more_containers) -const char *prompt; -struct obj *obj; -boolean outokay, inokay, alreadyused, more_containers; +staticfn char +in_or_out_menu( + const char *prompt, + struct obj *obj, + boolean outokay, /* can take out */ + boolean inokay, /* can put in */ + boolean alreadyused, /* controls phrasing of the decline choice */ + boolean more_containers) { /* underscore is not a choice; it's used to skip element [0] */ static const char lootchars[] = "_:oibrsnq", abc_chars[] = "_:abcdenq"; @@ -2893,55 +3410,57 @@ boolean outokay, inokay, alreadyused, more_containers; char buf[BUFSZ]; int n; const char *menuselector = flags.lootabc ? abc_chars : lootchars; + int clr = NO_COLOR; - any = zeroany; + any = cg.zeroany; win = create_nhwindow(NHW_MENU); - start_menu(win); + start_menu(win, MENU_BEHAVE_STANDARD); any.a_int = 1; /* ':' */ Sprintf(buf, "Look inside %s", thesimpleoname(obj)); - add_menu(win, NO_GLYPH, &any, menuselector[any.a_int], 0, ATR_NONE, buf, - MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, menuselector[any.a_int], 0, + ATR_NONE, clr, buf, MENU_ITEMFLAGS_NONE); if (outokay) { any.a_int = 2; /* 'o' */ Sprintf(buf, "take %s out", something); - add_menu(win, NO_GLYPH, &any, menuselector[any.a_int], 0, ATR_NONE, - buf, MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, menuselector[any.a_int], 0, + ATR_NONE, clr, buf, MENU_ITEMFLAGS_NONE); } if (inokay) { any.a_int = 3; /* 'i' */ Sprintf(buf, "put %s in", something); - add_menu(win, NO_GLYPH, &any, menuselector[any.a_int], 0, ATR_NONE, - buf, MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, menuselector[any.a_int], 0, + ATR_NONE, clr, buf, MENU_ITEMFLAGS_NONE); } if (outokay) { any.a_int = 4; /* 'b' */ Sprintf(buf, "%stake out, then put in", inokay ? "both; " : ""); - add_menu(win, NO_GLYPH, &any, menuselector[any.a_int], 0, ATR_NONE, - buf, MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, menuselector[any.a_int], 0, + ATR_NONE, clr, buf, MENU_ITEMFLAGS_NONE); } if (inokay) { any.a_int = 5; /* 'r' */ Sprintf(buf, "%sput in, then take out", outokay ? "both reversed; " : ""); - add_menu(win, NO_GLYPH, &any, menuselector[any.a_int], 0, ATR_NONE, - buf, MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, menuselector[any.a_int], 0, + ATR_NONE, clr, buf, MENU_ITEMFLAGS_NONE); any.a_int = 6; /* 's' */ Sprintf(buf, "stash one item into %s", thesimpleoname(obj)); - add_menu(win, NO_GLYPH, &any, menuselector[any.a_int], 0, ATR_NONE, - buf, MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, menuselector[any.a_int], 0, + ATR_NONE, clr, buf, MENU_ITEMFLAGS_NONE); } - any.a_int = 0; - add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED); + add_menu_str(win, ""); if (more_containers) { any.a_int = 7; /* 'n' */ - add_menu(win, NO_GLYPH, &any, menuselector[any.a_int], 0, ATR_NONE, - "loot next container", MENU_SELECTED); + add_menu(win, &nul_glyphinfo, &any, menuselector[any.a_int], 0, + ATR_NONE, clr, "loot next container", + MENU_ITEMFLAGS_SELECTED); } any.a_int = 8; /* 'q' */ Strcpy(buf, alreadyused ? "done" : "do nothing"); - add_menu(win, NO_GLYPH, &any, menuselector[any.a_int], 0, ATR_NONE, buf, - more_containers ? MENU_UNSELECTED : MENU_SELECTED); + add_menu(win, &nul_glyphinfo, &any, menuselector[any.a_int], 0, + ATR_NONE, clr, buf, + more_containers ? MENU_ITEMFLAGS_NONE : MENU_ITEMFLAGS_SELECTED); end_menu(win, prompt); n = select_menu(win, PICK_ONE, &pick_list); @@ -2957,11 +3476,90 @@ boolean outokay, inokay, alreadyused, more_containers; return (n == 0 && more_containers) ? 'n' : 'q'; /* next or quit */ } -static const char tippables[] = { ALL_CLASSES, TOOL_CLASS, 0 }; +/* getobj callback for object to tip */ +staticfn int +tip_ok(struct obj *obj) +{ + if (!obj || obj->oclass == COIN_CLASS) + return GETOBJ_EXCLUDE; + + if (Is_container(obj)) { + return GETOBJ_SUGGEST; + } + + /* include horn of plenty if sufficiently discovered */ + if (obj->otyp == HORN_OF_PLENTY && obj->dknown + && objects[obj->otyp].oc_name_known) + return GETOBJ_SUGGEST; + + /* allow trying anything else in inventory */ + return GETOBJ_DOWNPLAY; +} + +/* show a menu of containers under hero, + and one extra entry for choosing an inventory. + returns ECMD_CANCEL if menu was canceled, + ECMD_TIME if a container was picked, + otherwise returns ECMD_OK. */ +staticfn int +choose_tip_container_menu(void) +{ + int n, i; + winid win; + anything any; + menu_item *pick_list = (menu_item *) 0; + struct obj dummyobj, *otmp; + int clr = NO_COLOR; + + any = cg.zeroany; + win = create_nhwindow(NHW_MENU); + start_menu(win, MENU_BEHAVE_STANDARD); + + for (otmp = svl.level.objects[u.ux][u.uy], i = 0; otmp; + otmp = otmp->nexthere) + if (Is_container(otmp)) { + ++i; + any.a_obj = otmp; + add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, + clr, doname(otmp), MENU_ITEMFLAGS_NONE); + } + if (gi.invent) { + add_menu_str(win, ""); + any.a_obj = &dummyobj; + /* use 'i' for inventory unless there are so many + containers that it's already being used */ + i = (i <= 'i' - 'a' && !flags.lootabc) ? 'i' : 0; + add_menu(win, &nul_glyphinfo, &any, i, 0, ATR_NONE, + clr, "tip something being carried", + MENU_ITEMFLAGS_SELECTED); + } + end_menu(win, "Tip which container?"); + n = select_menu(win, PICK_ONE, &pick_list); + destroy_nhwindow(win); + /* + * Deal with quirk of preselected item in pick-one menu: + * n == 0 => picked preselected entry, toggling it off; + * n == 1 => accepted preselected choice via SPACE or RETURN; + * n == 2 => picked something other than preselected entry; + * n == -1 => cancelled via ESC; + */ + otmp = (n <= 0) ? (struct obj *) 0 : pick_list[0].item.a_obj; + if (n > 1 && otmp == &dummyobj) + otmp = pick_list[1].item.a_obj; + if (pick_list) + free((genericptr_t) pick_list); + if (otmp && otmp != &dummyobj) { + tipcontainer(otmp); + return ECMD_TIME; + } + if (n == -1) + return ECMD_CANCEL; + return ECMD_OK; +} /* #tip command -- empty container contents onto floor */ int -dotip() +dotip(void) { struct obj *cobj, *nobj; coord cc; @@ -2970,74 +3568,39 @@ dotip() const char *spillage = 0; /* - * doesn't require free hands; - * limbs are needed to tip floor containers + * Doesn't require free hands; + * limbs are needed to tip floor containers. + * + * Note: for menustyle:Traditional, using m prefix forces a menu + * of floor containers when more than one is present. For other + * menustyle settings or when fewer than two floor containers are + * present, using 'm' skips floor and goes straight to invent. + * This somewhat unintuitive behavior is driven by the way that + * context-sensitive inventory item actions use m prefix. */ /* at present, can only tip things at current spot, not adjacent ones */ cc.x = u.ux, cc.y = u.uy; /* check floor container(s) first; at most one will be accessed */ - if ((boxes = container_at(cc.x, cc.y, TRUE)) > 0) { + boxes = container_at(cc.x, cc.y, TRUE); + /* this is iffy for menustyle:traditional; 'm' prefix is ambiguous + for it: skip floor vs handle multiple containers via menu */ + if (boxes > 0 + && (!iflags.menu_requested + || (flags.menu_style == MENU_TRADITIONAL && boxes > 1))) { Sprintf(buf, "You can't tip %s while carrying so much.", !flags.verbose ? "a container" : (boxes > 1) ? "one" : "it"); if (!check_capacity(buf) && able_to_loot(cc.x, cc.y, FALSE)) { - if (boxes > 1 && (flags.menu_style != MENU_TRADITIONAL - || iflags.menu_requested)) { - /* use menu to pick a container to tip */ - int n, i; - winid win; - anything any; - menu_item *pick_list = (menu_item *) 0; - struct obj dummyobj, *otmp; - - any = zeroany; - win = create_nhwindow(NHW_MENU); - start_menu(win); - - for (cobj = level.objects[cc.x][cc.y], i = 0; cobj; - cobj = cobj->nexthere) - if (Is_container(cobj)) { - ++i; - any.a_obj = cobj; - add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, - doname(cobj), MENU_UNSELECTED); - } - if (invent) { - any = zeroany; - add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, - "", MENU_UNSELECTED); - any.a_obj = &dummyobj; - /* use 'i' for inventory unless there are so many - containers that it's already being used */ - i = (i <= 'i' - 'a' && !flags.lootabc) ? 'i' : 0; - add_menu(win, NO_GLYPH, &any, i, 0, ATR_NONE, - "tip something being carried", MENU_SELECTED); - } - end_menu(win, "Tip which container?"); - n = select_menu(win, PICK_ONE, &pick_list); - destroy_nhwindow(win); - /* - * Deal with quirk of preselected item in pick-one menu: - * n == 0 => picked preselected entry, toggling it off; - * n == 1 => accepted preselected choice via SPACE or RETURN; - * n == 2 => picked something other than preselected entry; - * n == -1 => cancelled via ESC; - */ - otmp = (n <= 0) ? (struct obj *) 0 : pick_list[0].item.a_obj; - if (n > 1 && otmp == &dummyobj) - otmp = pick_list[1].item.a_obj; - if (pick_list) - free((genericptr_t) pick_list); - if (otmp && otmp != &dummyobj) { - tipcontainer(otmp); - return 1; - } - if (n == -1) - return 0; - /* else pick-from-invent below */ + if (boxes > 1) { + int res; + /* pick one container via menu or ... */ + if ((res = choose_tip_container_menu()) != ECMD_OK) + return res; + /* else pick-from-gi.invent below */ } else { - for (cobj = level.objects[cc.x][cc.y]; cobj; cobj = nobj) { + for (cobj = svl.level.objects[cc.x][cc.y]; cobj; + cobj = nobj) { nobj = cobj->nexthere; if (!Is_container(cobj)) continue; @@ -3045,26 +3608,27 @@ dotip() cobj, doname, ansimpleoname, "container")); if (c == 'q') - return 0; + return ECMD_OK; if (c == 'n') continue; tipcontainer(cobj); /* can only tip one container at a time */ - return 1; + return ECMD_TIME; } } } } - /* either no floor container(s) or couldn't tip one or didn't tip any */ - cobj = getobj(tippables, "tip"); + /* either no floor container(s) or 'm' prefix was used to ignore such + or couldn't tip one or didn't tip any */ + cobj = getobj("tip", tip_ok, GETOBJ_PROMPT); if (!cobj) - return 0; + return ECMD_CANCEL; /* normal case */ if (Is_container(cobj) || cobj->otyp == HORN_OF_PLENTY) { tipcontainer(cobj); - return 1; + return ECMD_TIME; } /* assorted other cases */ if (Is_candle(cobj) && cobj->lamplit) { @@ -3098,24 +3662,35 @@ dotip() consume_obj_charge(cobj, TRUE); } /* something [useless] happened */ - return 1; + return ECMD_TIME; } /* anything not covered yet */ if (cobj->oclass == POTION_CLASS) /* can't pour potions... */ pline_The("%s %s securely sealed.", xname(cobj), otense(cobj, "are")); + else if (uarmh && cobj == uarmh) + return tiphat() ? ECMD_TIME : ECMD_OK; else if (cobj->otyp == STATUE) pline("Nothing interesting happens."); else pline1(nothing_happens); - return 0; + return ECMD_OK; } -STATIC_OVL void -tipcontainer(box) -struct obj *box; /* or bag */ +enum tipping_check_values { + TIPCHECK_OK = 0, + TIPCHECK_LOCKED, + TIPCHECK_TRAPPED, + TIPCHECK_CANNOT, + TIPCHECK_EMPTY +}; + +staticfn void +tipcontainer(struct obj *box) /* or bag */ { - xchar ox = u.ux, oy = u.uy; /* #tip only works at hero's location */ - boolean empty_it = FALSE, maybeshopgoods; + coordxy ox = u.ux, oy = u.uy; /* #tip only works at hero's location */ + boolean srcheld = FALSE, dstheld = FALSE, maybeshopgoods; + struct obj *targetbox = (struct obj *) 0; + boolean cancelled = FALSE; /* box is either held or on floor at hero's spot; no need to check for nesting; when held, we need to update its location to match hero's; @@ -3123,6 +3698,15 @@ struct obj *box; /* or bag */ if (get_obj_location(box, &ox, &oy, 0)) box->ox = ox, box->oy = oy; + /* + * TODO? + * if 'box' is known to be empty or known to be locked, give up + * before choosing 'targetbox'. + */ + targetbox = tipcontainer_gettarget(box, &cancelled); + if (cancelled) + return; + /* Shop handling: can't rely on the container's own unpaid or no_charge status because contents might differ with it. A carried container's contents will be flagged as unpaid @@ -3137,77 +3721,21 @@ struct obj *box; /* or bag */ to reduce the chance of exhausting shk's billing capacity. */ maybeshopgoods = !carried(box) && costly_spot(box->ox, box->oy); - /* caveat: this assumes that cknown, lknown, olocked, and otrapped - fields haven't been overloaded to mean something special for the - non-standard "container" horn of plenty */ - if (!box->lknown) { - box->lknown = 1; - if (carried(box)) - update_inventory(); /* jumping the gun slightly; hope that's ok */ - } - if (box->olocked) { - pline("It's locked."); - } else if (box->otrapped) { - /* we're not reaching inside but we're still handling it... */ - (void) chest_trap(box, HAND, FALSE); - /* even if the trap fails, you've used up this turn */ - if (multi >= 0) { /* in case we didn't become paralyzed */ - nomul(-1); - multi_reason = "tipping a container"; - nomovemsg = ""; - } - } else if (box->otyp == BAG_OF_TRICKS || box->otyp == HORN_OF_PLENTY) { - boolean bag = box->otyp == BAG_OF_TRICKS; - int old_spe = box->spe, seen = 0; - - if (maybeshopgoods && !box->no_charge) - addtobill(box, FALSE, FALSE, TRUE); - /* apply this bag/horn until empty or monster/object creation fails - (if the latter occurs, force the former...) */ - do { - if (!(bag ? bagotricks(box, TRUE, &seen) - : hornoplenty(box, TRUE))) - break; - } while (box->spe > 0); - - if (box->spe < old_spe) { - if (bag) - pline((seen == 0) ? "Nothing seems to happen." - : (seen == 1) ? "A monster appears." - : "Monsters appear!"); - /* check_unpaid wants to see a non-zero charge count */ - box->spe = old_spe; - check_unpaid_usage(box, TRUE); - box->spe = 0; /* empty */ - box->cknown = 1; - } - if (maybeshopgoods && !box->no_charge) - subfrombill(box, shop_keeper(*in_rooms(ox, oy, SHOPBASE))); - } else if (SchroedingersBox(box)) { - char yourbuf[BUFSZ]; - - observe_quantum_cat(box, TRUE, TRUE); - if (!Has_contents(box)) /* evidently a live cat came out */ - /* container type of "large box" is inferred */ - pline("%sbox is now empty.", Shk_Your(yourbuf, box)); - else /* holds cat corpse */ - empty_it = TRUE; - box->cknown = 1; - } else if (!Has_contents(box)) { - box->cknown = 1; - pline("It's empty."); - } else { - empty_it = TRUE; - } + if (tipcontainer_checks(box, targetbox, FALSE) != TIPCHECK_OK) + return; + if (targetbox + && tipcontainer_checks(targetbox, NULL, TRUE) != TIPCHECK_OK) + return; - if (empty_it) { + { struct obj *otmp, *nobj; boolean terse, highdrop = !can_reach_floor(TRUE), altarizing = IS_ALTAR(levl[ox][oy].typ), cursed_mbag = (Is_mbag(box) && box->cursed); - int held = carried(box); long loss = 0L; + srcheld = carried(box); + dstheld = (targetbox && carried(targetbox)); if (u.uswallow) highdrop = altarizing = FALSE; terse = !(highdrop || altarizing || costly_spot(box->ox, box->oy)); @@ -3217,9 +3745,15 @@ struct obj *box; /* or bag */ * If any other messages intervene between objects, we revert to * "ObjK drops to the floor.", "ObjL drops to the floor.", &c. */ - pline("%s out%c", + if (targetbox) + pline("%s into %s.", + box->cobj->nobj ? "Objects tumble" : "An object tumbles", + the(xname(targetbox))); + else + pline("%s out%c", box->cobj->nobj ? "Objects spill" : "An object spills", terse ? ':' : '.'); + for (otmp = box->cobj; otmp; otmp = nobj) { nobj = otmp->nobj; obj_extract_self(otmp); @@ -3227,19 +3761,51 @@ struct obj *box; /* or bag */ if (box->otyp == ICE_BOX) { removed_from_icebox(otmp); /* resume rotting for corpse */ - } else if (cursed_mbag && !rn2(13)) { - loss += mbag_item_gone(held, otmp); + } else if (cursed_mbag && is_boh_item_gone()) { + loss += mbag_item_gone(srcheld, otmp, FALSE); /* abbreviated drop format is no longer appropriate */ terse = FALSE; continue; } - if (maybeshopgoods) { addtobill(otmp, FALSE, FALSE, TRUE); iflags.suppress_price++; /* doname formatting */ } - if (highdrop) { + if (targetbox) { + if (Is_mbag(targetbox) && mbag_explodes(otmp, 0)) { + livelog_printf(LL_ACHIEVE, + "just blew up %s bag of holding via tipping", + uhis()); + /* explicitly mention what item is triggering explosion */ + urgent_pline( + "As %s %s inside, you are blasted by a magical explosion!", + doname(otmp), otense(otmp, "tumble")); + + /* if putting one bag of holding into another, first + blow up the one going in, then (below) blow up the + one it's going into */ + if (otmp->otyp == BAG_OF_HOLDING) /* BoH into another */ + do_boh_explosion(otmp, !srcheld); + /* always delete the item which triggered the explosion */ + obfree(otmp, (struct obj *) 0); /* where==OBJ_FREE */ + + /* [assumes targetbox is carried, otherwise shop bill + handling becomes necessary here] */ + do_boh_explosion(targetbox, !dstheld); + if (dstheld) + useup(targetbox); + else + useupf(targetbox, targetbox->quan); + targetbox = 0; /* it's gone */ + nobj = 0; /* stop tipping; want loop to exit 'normally' */ + + losehp(d(6, 6), "magical explosion", KILLED_BY_AN); + } else { + (void) add_to_container(targetbox, otmp); + } + } else if (highdrop) { + otmp->how_lost = LOST_DROPPED; /* might break or fall down stairs; handles altars itself */ hitfloor(otmp, TRUE); } else { @@ -3252,21 +3818,240 @@ struct obj *box; /* or bag */ pline("%s%c", doname(otmp), nobj ? ',' : '.'); iflags.last_msg = PLNMSG_OBJNAM_ONLY; } + otmp->how_lost = LOST_DROPPED; dropy(otmp); if (iflags.last_msg != PLNMSG_OBJNAM_ONLY) terse = FALSE; /* terse formatting has been interrupted */ } + if (maybeshopgoods) iflags.suppress_price--; /* reset */ } if (loss) /* magic bag lost some shop goods */ You("owe %ld %s for lost merchandise.", loss, currency(loss)); box->owt = weight(box); /* mbag_item_gone() doesn't update this */ - if (held) - (void) encumber_msg(); + if (targetbox) + targetbox->owt = weight(targetbox); + if (srcheld || dstheld) + encumber_msg(); } - if (carried(box)) /* box is now empty with cknown set */ + + if (srcheld || dstheld) update_inventory(); } +#if 0 +staticfn int count_target_containers(struct obj *, struct obj *); + +/* returns number of containers in object chain; does not recurse into + containers; skips bags of tricks when they're known */ +staticfn int +count_target_containers( + struct obj *olist, /* list of objects (invent) */ + struct obj *excludo) /* particular object to exclude if found in list */ +{ + int ret = 0; + + while (olist) { + if (olist != excludo && Is_container(olist) + /* include bag of tricks when not known to be such */ + && (box->otyp != BAG_OF_TRICKS || !box->dknown + || !objects[box->otyp].oc_name_known)) + ret++; + olist = olist->nobj; + } + return ret; +} +#endif + +/* ask user for a carried container into which they want box to be emptied; + cancelled is TRUE if user cancelled the menu pick; hands aren't required + when tipping to the floor but are when tipping into another container */ +staticfn struct obj * +tipcontainer_gettarget( + struct obj *box, + boolean *cancelled) +{ + int n, n_conts; + winid win; + anything any; + char buf[BUFSZ]; + menu_item *pick_list = (menu_item *) 0; + struct obj dummyobj, *otmp; + boolean hands_available = TRUE, exclude_it; + int clr = NO_COLOR; + +#if 0 /* [skip potential early return so that menu response is needed + * regardless of whether other containers are being carried] */ + int n_conts = count_target_containers(gi.invent, box); + + if (n_conts < 1 || !u_handsy()) { + if (n_conts >= 1) + pline("Tipping contents to floor only..."); + *cancelled = FALSE; + return (struct obj *) 0; + } +#endif + + win = create_nhwindow(NHW_MENU); + start_menu(win, MENU_BEHAVE_STANDARD); + + dummyobj = cg.zeroobj; /* lint suppression; only its address matters */ + any = cg.zeroany; + any.a_obj = &dummyobj; + /* tip to floor does not require free hands */ + add_menu(win, &nul_glyphinfo, &any, '-', 0, ATR_NONE, clr, + /* [TODO? vary destination string depending on surface()] */ + "on the floor", MENU_ITEMFLAGS_SELECTED); + add_menu_str(win, ""); + + n_conts = 0; + for (otmp = gi.invent; otmp; otmp = otmp->nobj) { + if (otmp == box) + continue; + /* skip non-containers; bag of tricks passes Is_container() test, + only include it if it isn't known to be a bag of tricks */ + if (!Is_container(otmp) + || (otmp->otyp == BAG_OF_TRICKS && otmp->dknown + && objects[otmp->otyp].oc_name_known)) + continue; + if (!n_conts++) + hands_available = u_handsy(); /* might issue message */ + /* container-to-container tip requires free hands; + exclude container as possible target when known to be locked */ + exclude_it = !hands_available || (otmp->olocked && otmp->lknown); + any = cg.zeroany; + any.a_obj = !exclude_it ? otmp : 0; + Sprintf(buf, "%s%s", !exclude_it ? "" : " ", doname(otmp)); + add_menu(win, &nul_glyphinfo, &any, !exclude_it ? otmp->invlet : 0, 0, + ATR_NONE, clr, buf, MENU_ITEMFLAGS_NONE); + } + + Sprintf(buf, "Where to tip the contents of %s", doname(box)); + end_menu(win, buf); + n = select_menu(win, PICK_ONE, &pick_list); + destroy_nhwindow(win); + + otmp = 0; + if (pick_list) { + otmp = pick_list[0].item.a_obj; + /* PICK_ONE with a preselected item might return 2; + if so, choose the one that wasn't preselected */ + if (n > 1 && otmp == &dummyobj) + otmp = pick_list[1].item.a_obj; + if (otmp == &dummyobj) + otmp = 0; + free((genericptr_t) pick_list); + } + *cancelled = (boolean) (n == -1); + return otmp; +} + +/* Perform check on box if we can tip it. + Returns one of TIPCHECK_foo values. + If allowempty if TRUE, return TIPCHECK_OK instead of TIPCHECK_EMPTY. */ +staticfn int +tipcontainer_checks( + struct obj *box, /* container player wants to tip */ + struct obj *targetbox, /* destination (used here for horn of plenty) */ + boolean allowempty) /* affects result when box is empty */ +{ + /* undiscovered bag of tricks is acceptable as a container-to-container + destination but it can't receive items; it has to be opened in + preparation so apply it once before even trying to tip source box */ + if (targetbox && targetbox->otyp == BAG_OF_TRICKS) { + int seencount = 0; + + bagotricks(targetbox, FALSE, &seencount); + return TIPCHECK_CANNOT; + } + + /* caveat: this assumes that cknown, lknown, olocked, and otrapped + fields haven't been overloaded to mean something special for the + non-standard "container" horn of plenty */ + if (!box->lknown) { + box->lknown = 1; + if (carried(box)) + update_inventory(); /* jumping the gun slightly; hope that's ok */ + } + + if (box->olocked) { + pline("%s is locked.", upstart(thesimpleoname(box))); + return TIPCHECK_LOCKED; + + } else if (box->otrapped) { + /* we're not reaching inside but we're still handling it... */ + (void) chest_trap(box, HAND, FALSE); + /* even if the trap fails, you've used up this turn */ + if (gm.multi >= 0) { /* in case we didn't become paralyzed */ + nomul(-1); + gm.multi_reason = "tipping a container"; + gn.nomovemsg = ""; + } + return TIPCHECK_TRAPPED; + + } else if (box->otyp == BAG_OF_TRICKS || box->otyp == HORN_OF_PLENTY) { + int res = TIPCHECK_OK; + boolean bag = (box->otyp == BAG_OF_TRICKS); + int old_spe = box->spe, seen, totseen; + boolean maybeshopgoods = (!carried(box) + && costly_spot(box->ox, box->oy)); + coordxy ox = u.ux, oy = u.uy; + + if (targetbox + && ((res = tipcontainer_checks(targetbox, NULL, TRUE)) + != TIPCHECK_OK)) + return res; + + if (get_obj_location(box, &ox, &oy, 0)) + box->ox = ox, box->oy = oy; + + if (maybeshopgoods && !box->no_charge) + addtobill(box, FALSE, FALSE, TRUE); + /* apply this bag/horn until empty or monster/object creation fails + (if the latter occurs, force the former...) */ + seen = totseen = 0; + do { + if (!(bag ? bagotricks(box, TRUE, &seen) + : hornoplenty(box, TRUE, targetbox))) + break; + totseen += seen; + } while (box->spe > 0); + + if (box->spe < old_spe) { + if (bag && !totseen) + pline1(nothing_seems_to_happen); + /* check_unpaid wants to see a non-zero charge count */ + box->spe = old_spe; + check_unpaid_usage(box, TRUE); + box->spe = 0; /* empty */ + box->cknown = 1; + } + if (maybeshopgoods && !box->no_charge) + subfrombill(box, shop_keeper(*in_rooms(ox, oy, SHOPBASE))); + return TIPCHECK_CANNOT; /* actually means 'already done' */ + + } else if (SchroedingersBox(box)) { + char yourbuf[BUFSZ]; + boolean empty_it = FALSE; + + observe_quantum_cat(box, TRUE, TRUE); + if (!Has_contents(box)) /* evidently a live cat came out */ + /* container type of "large box" is inferred */ + pline("%sbox is now empty.", Shk_Your(yourbuf, box)); + else /* holds cat corpse */ + empty_it = TRUE; + box->cknown = 1; + return (empty_it || allowempty) ? TIPCHECK_OK : TIPCHECK_EMPTY; + + } else if (!allowempty && !Has_contents(box)) { + box->cknown = 1; + pline("%s is empty.", upstart(thesimpleoname(box))); + return TIPCHECK_EMPTY; + + } + + return TIPCHECK_OK; +} + /*pickup.c*/ diff --git a/src/pline.c b/src/pline.c index 24bdea211..49c293195 100644 --- a/src/pline.c +++ b/src/pline.c @@ -1,33 +1,25 @@ -/* NetHack 3.6 pline.c $NHDT-Date: 1549327495 2019/02/05 00:44:55 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.73 $ */ +/* NetHack 5.0 pline.c $NHDT-Date: 1719819280 2024/07/01 07:34:40 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.130 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2018. */ /* NetHack may be freely redistributed. See license for details. */ -#define NEED_VARARGS /* Uses ... */ /* comment line for pre-compiled headers */ #include "hack.h" #define BIGBUFSZ (5 * BUFSZ) /* big enough to format a 4*BUFSZ string (from * config file parsing) with modest decoration; * result will then be truncated to BUFSZ-1 */ -static unsigned pline_flags = 0; -static char prevmsg[BUFSZ]; - -static void FDECL(putmesg, (const char *)); -static char *FDECL(You_buf, (int)); -#if defined(MSGHANDLER) && (defined(POSIX_TYPES) || defined(__GNUC__)) -static void FDECL(execplinehandler, (const char *)); +staticfn void putmesg(const char *); +staticfn char *You_buf(int); +staticfn void execplinehandler(const char *); +#ifdef USER_SOUNDS +extern void maybe_play_sound(const char *); #endif - -#ifdef DUMPLOG -/* also used in end.c */ -unsigned saved_pline_index = 0; /* slot in saved_plines[] to use next */ -char *saved_plines[DUMPLOG_MSG_COUNT] = { (char *) 0 }; +#ifdef DUMPLOG_CORE /* keep the most recent DUMPLOG_MSG_COUNT messages */ void -dumplogmsg(line) -const char *line; +dumplogmsg(const char *line) { /* * TODO: @@ -36,9 +28,11 @@ const char *line; * The core should take responsibility for that and have * this share it. */ - unsigned indx = saved_pline_index; /* next slot to use */ - char *oldest = saved_plines[indx]; /* current content of that slot */ + unsigned indx = gs.saved_pline_index; /* next slot to use */ + char *oldest = gs.saved_plines[indx]; /* current content of that slot */ + if (!strncmp(line, "Unknown command", 15)) + return; if (oldest && strlen(oldest) >= strlen(line)) { /* this buffer will gradually shrink until the 'else' is needed; there's no pressing need to track allocation size instead */ @@ -46,90 +40,128 @@ const char *line; } else { if (oldest) free((genericptr_t) oldest); - saved_plines[indx] = dupstr(line); + gs.saved_plines[indx] = dupstr(line); } - saved_pline_index = (indx + 1) % DUMPLOG_MSG_COUNT; + gs.saved_pline_index = (indx + 1) % DUMPLOG_MSG_COUNT; } /* called during save (unlike the interface-specific message history, - this data isn't saved and restored); end-of-game releases saved_pline[] + this data isn't saved and restored); end-of-game releases saved_plines[] while writing its contents to the final dump log */ void -dumplogfreemessages() +dumplogfreemessages(void) { - unsigned indx; + unsigned i; - for (indx = 0; indx < DUMPLOG_MSG_COUNT; ++indx) - if (saved_plines[indx]) - free((genericptr_t) saved_plines[indx]), saved_plines[indx] = 0; - saved_pline_index = 0; + for (i = 0; i < DUMPLOG_MSG_COUNT; ++i) + if (gs.saved_plines[i]) + free((genericptr_t) gs.saved_plines[i]), gs.saved_plines[i] = 0; + gs.saved_pline_index = 0; } #endif /* keeps windowprocs usage out of pline() */ -static void -putmesg(line) -const char *line; +staticfn void +putmesg(const char *line) { int attr = ATR_NONE; - if ((pline_flags & URGENT_MESSAGE) != 0 + if (iflags.debug_prevent_pline) + return; + + if ((gp.pline_flags & URGENT_MESSAGE) != 0 && (windowprocs.wincap2 & WC2_URGENT_MESG) != 0) attr |= ATR_URGENT; - if ((pline_flags & SUPPRESS_HISTORY) != 0 + if ((gp.pline_flags & SUPPRESS_HISTORY) != 0 && (windowprocs.wincap2 & WC2_SUPPRESS_HIST) != 0) attr |= ATR_NOHISTORY; - putstr(WIN_MESSAGE, attr, line); + SoundSpeak(line); } -/* Note that these declarations rely on knowledge of the internals - * of the variable argument handling stuff in "tradstdc.h" - */ +/* set the direction where next message happens */ +void +set_msg_dir(int dir) +{ + dirtocoord(&a11y.msg_loc, dir); + a11y.msg_loc.x += u.ux; + a11y.msg_loc.y += u.uy; +} -#if defined(USE_STDARG) || defined(USE_VARARGS) -static void FDECL(vpline, (const char *, va_list)); +/* set the coordinate where next message happens */ +void +set_msg_xy(coordxy x, coordxy y) +{ + a11y.msg_loc.x = x; + a11y.msg_loc.y = y; +} + +staticfn void vpline(const char *, va_list); + +DISABLE_WARNING_FORMAT_NONLITERAL -/*VARARGS1*/ void -pline -VA_DECL(const char *, line) +pline(const char *line, ...) { - VA_START(line); - VA_INIT(line, char *); - vpline(line, VA_ARGS); - VA_END(); + va_list the_args; + + va_start(the_args, line); + vpline(line, the_args); + va_end(the_args); } -# ifdef USE_STDARG -static void -vpline(const char *line, va_list the_args) -# else -static void -vpline(line, the_args) -const char *line; -va_list the_args; -# endif +void +pline_dir(int dir, const char *line, ...) +{ + va_list the_args; -#else /* USE_STDARG | USE_VARARG */ + set_msg_dir(dir); -# define vpline pline + va_start(the_args, line); + vpline(line, the_args); + va_end(the_args); +} -/*VARARGS1*/ void -pline -VA_DECL(const char *, line) -#endif /* USE_STDARG | USE_VARARG */ -{ /* start of vpline() or of nested block in USE_OLDARG's pline() */ +pline_xy(coordxy x, coordxy y, const char *line, ...) +{ + va_list the_args; + + set_msg_xy(x, y); + + va_start(the_args, line); + vpline(line, the_args); + va_end(the_args); +} + +void +pline_mon(struct monst *mtmp, const char *line, ...) +{ + va_list the_args; + + if (mtmp == &gy.youmonst) + set_msg_xy(0, 0); + else + set_msg_xy(mtmp->mx, mtmp->my); + + va_start(the_args, line); + vpline(line, the_args); + va_end(the_args); +} + +staticfn void +vpline(const char *line, va_list the_args) +{ static int in_pline = 0; char pbuf[BIGBUFSZ]; /* will get chopped down to BUFSZ-1 if longer */ int ln; int msgtyp; -#if !defined(NO_VSNPRINTF) - int vlen = 0; -#endif boolean no_repeat; - /* Do NOT use VA_START and VA_END in here... see above */ + coord a11y_mesgxy; + + a11y_mesgxy = a11y.msg_loc; /* save a11y.msg_loc before reseting it */ + /* always reset a11y.msg_loc whether we end up using it or not */ + a11y.msg_loc.x = a11y.msg_loc.y = 0; if (!line || !*line) return; @@ -140,26 +172,50 @@ VA_DECL(const char *, line) if (program_state.wizkit_wishing) return; - if (index(line, '%')) { -#if !defined(NO_VSNPRINTF) - vlen = vsnprintf(pbuf, sizeof pbuf, line, VA_ARGS); -#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) && defined(DEBUG) - if (vlen >= (int) sizeof pbuf) - panic("%s: truncation of buffer at %zu of %d bytes", - "pline", sizeof pbuf, vlen); -#endif -#else - Vsprintf(pbuf, line, VA_ARGS); -#endif + /* when accessiblemsg is set and a11y.msg_loc is nonzero, use the latter + to insert a location prefix in front of current message */ + if (a11y.accessiblemsg && isok(a11y_mesgxy.x, a11y_mesgxy.y)) { + char *tmp, *dirstr, dirstrbuf[QBUFSZ]; + + dirstr = coord_desc(a11y_mesgxy.x, a11y_mesgxy.y, dirstrbuf, + ((iflags.getpos_coords == GPCOORDS_NONE) + ? GPCOORDS_COMFULL : iflags.getpos_coords)); + tmp = (char *) alloc(strlen(line) + sizeof ": " + strlen(dirstr)); + Strcpy(tmp, dirstr); + Strcat(tmp, ": "); + Strcat(tmp, line); + vpline(tmp, the_args); + free((genericptr_t) tmp); + return; + } + + if (!strchr(line, '%')) { + /* format does not specify any substitutions; use it as-is */ + ln = (int) strlen(line); + } else if (line[0] == '%' && line[1] == 's' && !line[2]) { + /* "%s" => single string; skip format and use its first argument; + unlike with the format, it is irrelevant whether the argument + contains any percent signs */ + line = va_arg(the_args, const char *); /*VA_NEXT(line,const char *);*/ + ln = (int) strlen(line); + } else { + /* perform printf() formatting */ + ln = vsnprintf(pbuf, sizeof pbuf, line, the_args); line = pbuf; + /* note: 'ln' is number of characters attempted, not necessarily + strlen(line); that matters for the overflow check; if we avoid + the extremely-too-long panic then 'ln' will be actual length */ } - if ((ln = (int) strlen(line)) > BUFSZ - 1) { - if (line != pbuf) /* no '%' was present */ - (void) strncpy(pbuf, line, BUFSZ - 1); /* caveat: unterminated */ - /* truncate, preserving the final 3 characters: - "___ extremely long text" -> "___ extremely l...ext" + if (ln > (int) sizeof pbuf - 1) /* extremely too long */ + panic("pline attempting to print %d characters!", ln); + + if (ln > BUFSZ - 1) { + /* too long but modestly so; allow but truncate, preserving final + 3 chars: "___ extremely long text" -> "___ extremely l...ext" (this may be suboptimal if overflow is less than 3) */ - (void) strncpy(pbuf + BUFSZ - 1 - 6, "...", 3); + if (line != pbuf) /* no '%' was present or format was just "%s" */ + (void) strncpy(pbuf, line, BUFSZ - 1); /* caveat: unterminated */ + pbuf[BUFSZ - 1 - 6] = pbuf[BUFSZ - 1 - 5] = pbuf[BUFSZ - 1 - 4] = '.'; /* avoid strncpy; buffers could overlap if excess is small */ pbuf[BUFSZ - 1 - 3] = line[ln - 3]; pbuf[BUFSZ - 1 - 2] = line[ln - 2]; @@ -167,13 +223,14 @@ VA_DECL(const char *, line) pbuf[BUFSZ - 1] = '\0'; line = pbuf; } + msgtyp = MSGTYP_NORMAL; -#ifdef DUMPLOG +#ifdef DUMPLOG_CORE /* We hook here early to have options-agnostic output. * Unfortunately, that means Norep() isn't honored (general issue) and * that short lines aren't combined into one longer one (tty behavior). */ - if ((pline_flags & SUPPRESS_HISTORY) == 0) + if ((gp.pline_flags & SUPPRESS_HISTORY) == 0) dumplogmsg(line); #endif /* use raw_print() if we're called too early (or perhaps too late @@ -186,13 +243,16 @@ VA_DECL(const char *, line) goto pline_done; } - msgtyp = MSGTYP_NORMAL; - no_repeat = (pline_flags & PLINE_NOREPEAT) ? TRUE : FALSE; - if ((pline_flags & OVERRIDE_MSGTYPE) == 0) { + no_repeat = (gp.pline_flags & PLINE_NOREPEAT) ? TRUE : FALSE; + if ((gp.pline_flags & OVERRIDE_MSGTYPE) == 0) { msgtyp = msgtype_type(line, no_repeat); - if ((pline_flags & URGENT_MESSAGE) == 0 +#ifdef USER_SOUNDS + if (msgtyp == MSGTYP_NORMAL || msgtyp == MSGTYP_NOSHOW) + maybe_play_sound(line); +#endif + if ((gp.pline_flags & URGENT_MESSAGE) == 0 && (msgtyp == MSGTYP_NOSHOW - || (msgtyp == MSGTYP_NOREP && !strcmp(line, prevmsg)))) + || (msgtyp == MSGTYP_NOREP && !strcmp(line, gp.prevmsg)))) /* FIXME: we need a way to tell our caller that this message * was suppressed so that caller doesn't set iflags.last_msg * for something that hasn't been shown, otherwise a subsequent @@ -203,86 +263,96 @@ VA_DECL(const char *, line) goto pline_done; } - if (vision_full_recalc) + if (gv.vision_full_recalc) { + int tmp_in_pline = in_pline; + + in_pline = 0; vision_recalc(0); + in_pline = tmp_in_pline; + } if (u.ux) - flush_screen(1); /* %% */ + flush_screen((gp.pline_flags & NO_CURS_ON_U) ? 0 : 1); /* %% */ putmesg(line); -#if defined(MSGHANDLER) && (defined(POSIX_TYPES) || defined(__GNUC__)) execplinehandler(line); -#endif /* this gets cleared after every pline message */ iflags.last_msg = PLNMSG_UNKNOWN; - (void) strncpy(prevmsg, line, BUFSZ), prevmsg[BUFSZ - 1] = '\0'; + (void) strncpy(gp.prevmsg, line, BUFSZ), gp.prevmsg[BUFSZ - 1] = '\0'; if (msgtyp == MSGTYP_STOP) display_nhwindow(WIN_MESSAGE, TRUE); /* --more-- */ - pline_done: - --in_pline; - return; - -#if !(defined(USE_STDARG) || defined(USE_VARARGS)) - /* provide closing brace for the nested block - which immediately follows USE_OLDARGS's VA_DECL() */ - VA_END(); +#ifdef SND_SPEECH + /* clear the SPEECH flag so caller never has to */ + gp.pline_flags &= ~PLINE_SPEECH; #endif + --in_pline; } +RESTORE_WARNING_FORMAT_NONLITERAL + /* pline() variant which can override MSGTYPE handling or suppress message history (tty interface uses pline() to issue prompts and they shouldn't be blockable via MSGTYPE=hide) */ -/*VARARGS2*/ -void custompline -VA_DECL2(unsigned, pflags, const char *, line) -{ - VA_START(line); - VA_INIT(line, const char *); - pline_flags = pflags; - vpline(line, VA_ARGS); - pline_flags = 0; - VA_END(); - return; -} - -/*VARARGS1*/ -void Norep -VA_DECL(const char *, line) -{ - VA_START(line); - VA_INIT(line, const char *); - pline_flags = PLINE_NOREPEAT; - vpline(line, VA_ARGS); - pline_flags = 0; - VA_END(); - return; -} - -/* work buffer for You(), &c and verbalize() */ -static char *you_buf = 0; -static int you_buf_siz = 0; - -static char * -You_buf(siz) -int siz; -{ - if (siz > you_buf_siz) { - if (you_buf) - free((genericptr_t) you_buf); - you_buf_siz = siz + 10; - you_buf = (char *) alloc((unsigned) you_buf_siz); +void +custompline(unsigned pflags, const char *line, ...) +{ + va_list the_args; + + va_start(the_args, line); + gp.pline_flags = pflags; + vpline(line, the_args); + gp.pline_flags = 0; + va_end(the_args); +} + +/* if player has dismissed --More-- with ESC to suppress further messages + until next input request, tell the interface that it should override that + and re-enable them; equivalent to custompline(URGENT_MESSAGE, line, ...) + but slightly simpler to use */ +void +urgent_pline(const char *line, ...) +{ + va_list the_args; + + va_start(the_args, line); + gp.pline_flags = URGENT_MESSAGE; + vpline(line, the_args); + gp.pline_flags = 0; + va_end(the_args); +} + +void +Norep(const char *line, ...) +{ + va_list the_args; + + va_start(the_args, line); + gp.pline_flags = PLINE_NOREPEAT; + vpline(line, the_args); + gp.pline_flags = 0; + va_end(the_args); +} + +staticfn char * +You_buf(int siz) +{ + if (siz > gy.you_buf_siz) { + if (gy.you_buf) + free((genericptr_t) gy.you_buf); + gy.you_buf_siz = siz + 10; + gy.you_buf = (char *) alloc((unsigned) gy.you_buf_siz); } - return you_buf; + return gy.you_buf; } void -free_youbuf() +free_youbuf(void) { - if (you_buf) - free((genericptr_t) you_buf), you_buf = (char *) 0; - you_buf_siz = 0; + if (gy.you_buf) + free((genericptr_t) gy.you_buf), gy.you_buf = (char *) 0; + gy.you_buf_siz = 0; } /* `prefix' must be a string literal, not a pointer */ @@ -292,182 +362,210 @@ free_youbuf() #define YouMessage(pointer, prefix, text) \ strcat((YouPrefix(pointer, prefix, text), pointer), text) -/*VARARGS1*/ -void You -VA_DECL(const char *, line) +void +You(const char *line, ...) { + va_list the_args; char *tmp; - VA_START(line); - VA_INIT(line, const char *); - vpline(YouMessage(tmp, "You ", line), VA_ARGS); - VA_END(); + va_start(the_args, line); + vpline(YouMessage(tmp, "You ", line), the_args); + va_end(the_args); } -/*VARARGS1*/ -void Your -VA_DECL(const char *, line) +void +Your(const char *line, ...) { + va_list the_args; char *tmp; - VA_START(line); - VA_INIT(line, const char *); - vpline(YouMessage(tmp, "Your ", line), VA_ARGS); - VA_END(); + va_start(the_args, line); + vpline(YouMessage(tmp, "Your ", line), the_args); + va_end(the_args); } -/*VARARGS1*/ -void You_feel -VA_DECL(const char *, line) +void +You_feel(const char *line, ...) { + va_list the_args; char *tmp; - VA_START(line); - VA_INIT(line, const char *); + va_start(the_args, line); if (Unaware) YouPrefix(tmp, "You dream that you feel ", line); else YouPrefix(tmp, "You feel ", line); - vpline(strcat(tmp, line), VA_ARGS); - VA_END(); + vpline(strcat(tmp, line), the_args); + va_end(the_args); } -/*VARARGS1*/ -void You_cant -VA_DECL(const char *, line) +void +You_cant(const char *line, ...) { + va_list the_args; char *tmp; - VA_START(line); - VA_INIT(line, const char *); - vpline(YouMessage(tmp, "You can't ", line), VA_ARGS); - VA_END(); + va_start(the_args, line); + vpline(YouMessage(tmp, "You can't ", line), the_args); + va_end(the_args); } -/*VARARGS1*/ -void pline_The -VA_DECL(const char *, line) +void +pline_The(const char *line, ...) { + va_list the_args; char *tmp; - VA_START(line); - VA_INIT(line, const char *); - vpline(YouMessage(tmp, "The ", line), VA_ARGS); - VA_END(); + va_start(the_args, line); + vpline(YouMessage(tmp, "The ", line), the_args); + va_end(the_args); } -/*VARARGS1*/ -void There -VA_DECL(const char *, line) +void +There(const char *line, ...) { + va_list the_args; char *tmp; - VA_START(line); - VA_INIT(line, const char *); - vpline(YouMessage(tmp, "There ", line), VA_ARGS); - VA_END(); + va_start(the_args, line); + vpline(YouMessage(tmp, "There ", line), the_args); + va_end(the_args); } -/*VARARGS1*/ -void You_hear -VA_DECL(const char *, line) +void +You_hear(const char *line, ...) { + va_list the_args; char *tmp; - if (Deaf || !flags.acoustics) + if ((Deaf && !Unaware) || !flags.acoustics) return; - VA_START(line); - VA_INIT(line, const char *); + va_start(the_args, line); if (Underwater) YouPrefix(tmp, "You barely hear ", line); else if (Unaware) YouPrefix(tmp, "You dream that you hear ", line); else YouPrefix(tmp, "You hear ", line); /* Deaf-aware */ - vpline(strcat(tmp, line), VA_ARGS); - VA_END(); + vpline(strcat(tmp, line), the_args); + va_end(the_args); } -/*VARARGS1*/ -void You_see -VA_DECL(const char *, line) +void +You_see(const char *line, ...) { + va_list the_args; char *tmp; - VA_START(line); - VA_INIT(line, const char *); + va_start(the_args, line); if (Unaware) YouPrefix(tmp, "You dream that you see ", line); else if (Blind) /* caller should have caught this... */ YouPrefix(tmp, "You sense ", line); else YouPrefix(tmp, "You see ", line); - vpline(strcat(tmp, line), VA_ARGS); - VA_END(); + vpline(strcat(tmp, line), the_args); + va_end(the_args); } /* Print a message inside double-quotes. * The caller is responsible for checking deafness. * Gods can speak directly to you in spite of deafness. */ -/*VARARGS1*/ -void verbalize -VA_DECL(const char *, line) +void +verbalize(const char *line, ...) { + va_list the_args; char *tmp; - VA_START(line); - VA_INIT(line, const char *); + va_start(the_args, line); + gp.pline_flags |= PLINE_VERBALIZE; tmp = You_buf((int) strlen(line) + sizeof "\"\""); Strcpy(tmp, "\""); Strcat(tmp, line); Strcat(tmp, "\""); - vpline(tmp, VA_ARGS); - VA_END(); + vpline(tmp, the_args); + gp.pline_flags &= ~PLINE_VERBALIZE; + va_end(the_args); } -/*VARARGS1*/ -/* Note that these declarations rely on knowledge of the internals - * of the variable argument handling stuff in "tradstdc.h" - */ +#ifdef CHRONICLE -#if defined(USE_STDARG) || defined(USE_VARARGS) -static void FDECL(vraw_printf, (const char *, va_list)); +void +gamelog_add(long glflags, long gltime, const char *str) +{ + struct gamelog_line *tmp; + struct gamelog_line *lst = gg.gamelog; + + tmp = (struct gamelog_line *) alloc(sizeof (struct gamelog_line)); + tmp->turn = gltime; + tmp->flags = glflags; + tmp->text = dupstr(str); + tmp->next = NULL; + while (lst && lst->next) + lst = lst->next; + if (!lst) + gg.gamelog = tmp; + else + lst->next = tmp; +} -void raw_printf -VA_DECL(const char *, line) +void +livelog_printf(long ll_type, const char *line, ...) { - VA_START(line); - VA_INIT(line, char *); - vraw_printf(line, VA_ARGS); - VA_END(); + char gamelogbuf[BUFSZ * 2]; + va_list the_args; + + va_start(the_args, line); + (void) vsnprintf(gamelogbuf, sizeof gamelogbuf, line, the_args); + va_end(the_args); + + gamelog_add(ll_type, svm.moves, gamelogbuf); + strNsubst(gamelogbuf, "\t", "_", 0); + livelog_add(ll_type, gamelogbuf); } -# ifdef USE_STDARG -static void -vraw_printf(const char *line, va_list the_args) -# else -static void -vraw_printf(line, the_args) -const char *line; -va_list the_args; -# endif +#else -#else /* USE_STDARG | USE_VARARG */ +void +gamelog_add( + long glflags UNUSED, long gltime UNUSED, const char *msg UNUSED) +{ + ; /* nothing here */ +} -void raw_printf -VA_DECL(const char *, line) -#endif +void +livelog_printf( + long ll_type UNUSED, const char *line UNUSED, ...) +{ + ; /* nothing here */ +} + +#endif /* !CHRONICLE */ + +staticfn void vraw_printf(const char *, va_list); + +void +raw_printf(const char *line, ...) +{ + va_list the_args; + + va_start(the_args, line); + vraw_printf(line, the_args); + va_end(the_args); + if (!program_state.beyond_savefile_load) + ge.early_raw_messages++; +} + +DISABLE_WARNING_FORMAT_NONLITERAL + +staticfn void +vraw_printf(const char *line, va_list the_args) { char pbuf[BIGBUFSZ]; /* will be chopped down to BUFSZ-1 if longer */ - /* Do NOT use VA_START and VA_END in here... see above */ - if (index(line, '%')) { -#if !defined(NO_VSNPRINTF) - (void) vsnprintf(pbuf, sizeof pbuf, line, VA_ARGS); -#else - Vsprintf(pbuf, line, VA_ARGS); -#endif + if (strchr(line, '%')) { + (void) vsnprintf(pbuf, sizeof(pbuf), line, the_args); line = pbuf; } if ((int) strlen(line) > BUFSZ - 1) { @@ -477,75 +575,90 @@ VA_DECL(const char *, line) pbuf[BUFSZ - 1] = '\0'; /* terminate strncpy or truncate vsprintf */ } raw_print(line); -#if defined(MSGHANDLER) && (defined(POSIX_TYPES) || defined(__GNUC__)) execplinehandler(line); -#endif -#if !(defined(USE_STDARG) || defined(USE_VARARGS)) - VA_END(); /* (see vpline) */ -#endif + if (!program_state.beyond_savefile_load) + ge.early_raw_messages++; } -/*VARARGS1*/ -void impossible -VA_DECL(const char *, s) +void +impossible(const char *s, ...) { + va_list the_args; char pbuf[BIGBUFSZ]; /* will be chopped down to BUFSZ-1 if longer */ + char pbuf2[BUFSZ]; - VA_START(s); - VA_INIT(s, const char *); + va_start(the_args, s); if (program_state.in_impossible) panic("impossible called impossible"); program_state.in_impossible = 1; -#if !defined(NO_VSNPRINTF) - (void) vsnprintf(pbuf, sizeof pbuf, s, VA_ARGS); -#else - Vsprintf(pbuf, s, VA_ARGS); -#endif + (void) vsnprintf(pbuf, sizeof pbuf, s, the_args); + va_end(the_args); pbuf[BUFSZ - 1] = '\0'; /* sanity */ paniclog("impossible", pbuf); - if (iflags.debug_fuzzer) + if (iflags.debug_fuzzer == fuzzer_impossible_panic) panic("%s", pbuf); - pline("%s", VA_PASS1(pbuf)); - /* reuse pbuf[] */ - Strcpy(pbuf, "Program in disorder!"); + + gp.pline_flags = URGENT_MESSAGE; + pline("%s", pbuf); + gp.pline_flags = 0; + + if (program_state.in_sanity_check) { + /* skip rest of multi-line feedback */ + program_state.in_impossible = 0; + return; + } + + Strcpy(pbuf2, "Program in disorder!"); if (program_state.something_worth_saving) - Strcat(pbuf, " (Saving and reloading may fix this problem.)"); - pline("%s", VA_PASS1(pbuf)); + Strcat(pbuf2, " (Saving and reloading may fix this problem.)"); + pline("%s", pbuf2); + pline("Please report these messages to %s.", DEVTEAM_EMAIL); + if (sysopt.support) { + pline("Alternatively, contact local support: %s", sysopt.support); + } + +#ifdef CRASHREPORT + if (sysopt.crashreporturl) { + boolean report = ('y' == yn_function("Report now?", ynchars, + 'n', FALSE)); + + raw_print(""); /* prove to the user the character was accepted */ + if (report) { + submit_web_report(1, "Impossible", pbuf); + } + } +#endif program_state.in_impossible = 0; - VA_END(); } -#if defined(MSGHANDLER) && (defined(POSIX_TYPES) || defined(__GNUC__)) +RESTORE_WARNING_FORMAT_NONLITERAL + static boolean use_pline_handler = TRUE; -static void -execplinehandler(line) -const char *line; +staticfn void +execplinehandler(const char *line) { +#if defined(UNIX) && (defined(POSIX_TYPES) || defined(__GNUC__)) int f; +#endif const char *args[3]; - char *env; - if (!use_pline_handler) + if (!use_pline_handler || !sysopt.msghandler) return; - if (!(env = nh_getenv("NETHACK_MSGHANDLER"))) { - use_pline_handler = FALSE; - return; - } - +#if defined(UNIX) && (defined(POSIX_TYPES) || defined(__GNUC__)) f = fork(); if (f == 0) { /* child */ - args[0] = env; + args[0] = sysopt.msghandler; args[1] = line; args[2] = NULL; (void) setgid(getgid()); (void) setuid(getuid()); (void) execv(args[0], (char *const *) args); perror((char *) 0); - (void) fprintf(stderr, "Exec to message handler %s failed.\n", env); + (void) fprintf(stderr, "Exec to message handler %s failed.\n", sysopt.msghandler); nh_terminate(EXIT_FAILURE); } else if (f > 0) { int status; @@ -554,67 +667,53 @@ const char *line; } else if (f == -1) { perror((char *) 0); use_pline_handler = FALSE; - pline("%s", VA_PASS1("Fork to message handler failed.")); + pline("%s", "Fork to message handler failed."); + } +#elif defined(WIN32) + { + intptr_t ret; + args[0] = sysopt.msghandler; + args[1] = line; + args[2] = NULL; + ret = _spawnv(_P_NOWAIT, sysopt.msghandler, args); + nhUse(ret); /* -Wunused-but-set-variable */ } +#else + use_pline_handler = FALSE; + nhUse(args); + nhUse(line); +#endif } -#endif /* MSGHANDLER && (POSIX_TYPES || __GNUC__) */ - -/* - * varargs handling for files.c - */ -#if defined(USE_STDARG) || defined(USE_VARARGS) -static void FDECL(vconfig_error_add, (const char *, va_list)); -/*VARARGS1*/ +/* nhassert_failed is called when an nhassert's condition is false */ void -config_error_add -VA_DECL(const char *, str) +nhassert_failed(const char *expression, const char *filepath, int line) { - VA_START(str); - VA_INIT(str, char *); - vconfig_error_add(str, VA_ARGS); - VA_END(); -} - -# ifdef USE_STDARG -static void -vconfig_error_add(const char *str, va_list the_args) -# else -static void -vconfig_error_add(str, the_args) -const char *str; -va_list the_args; -# endif - -#else /* !(USE_STDARG || USE_VARARG) => USE_OLDARGS */ - -/*VARARGS1*/ -void -config_error_add -VA_DECL(const char *, str) -#endif /* ?(USE_STDARG || USE_VARARG) */ -{ /* start of vconf...() or of nested block in USE_OLDARG's conf...() */ -#if !defined(NO_VSNPRINTF) - int vlen = 0; + const char *filename, *p; + + /* Attempt to get filename from path. + TODO: we really need a port provided function to return a filename + from a path. */ + filename = filepath; + if ((p = strrchr(filename, '/')) != 0) + filename = p + 1; + if ((p = strrchr(filename, '\\')) != 0) + filename = p + 1; +#ifdef VMS + /* usually "device:[directory]name" + but might be "device:[root.][directory]name" + and either "[directory]" or "[root.]" or both can be delimited + by <> rather than by []; find the last of ']', '>', and ':' */ + if ((p = strrchr(filename, ']')) != 0) + filename = p + 1; + if ((p = strrchr(filename, '>')) != 0) + filename = p + 1; + if ((p = strrchr(filename, ':')) != 0) + filename = p + 1; #endif - char buf[BIGBUFSZ]; /* will be chopped down to BUFSZ-1 if longer */ - -#if !defined(NO_VSNPRINTF) - vlen = vsnprintf(buf, sizeof buf, str, VA_ARGS); -#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) && defined(DEBUG) - if (vlen >= (int) sizeof buf) - panic("%s: truncation of buffer at %zu of %d bytes", - "config_error_add", sizeof buf, vlen); -#endif -#else - Vsprintf(buf, str, VA_ARGS); -#endif - buf[BUFSZ - 1] = '\0'; - config_erradd(buf); -#if !(defined(USE_STDARG) || defined(USE_VARARGS)) - VA_END(); /* (see pline/vpline -- ends nested block for USE_OLDARGS) */ -#endif + impossible("nhassert(%s) failed in file '%s' at line %d", + expression, filename, line); } /*pline.c*/ diff --git a/src/polyself.c b/src/polyself.c index b0530913b..a3f31a0ab 100644 --- a/src/polyself.c +++ b/src/polyself.c @@ -1,13 +1,13 @@ -/* NetHack 3.6 polyself.c $NHDT-Date: 1573290419 2019/11/09 09:06:59 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.135 $ */ +/* NetHack 5.0 polyself.c $NHDT-Date: 1772101811 2026/02/26 02:30:11 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.227 $ */ /* Copyright (C) 1987, 1988, 1989 by Ken Arromdee */ /* NetHack may be freely redistributed. See license for details. */ /* * Polymorph self routine. * - * Note: the light source handling code assumes that both youmonst.m_id - * and youmonst.mx will always remain 0 when it handles the case of the - * player polymorphed into a light-emitting monster. + * Note: the light source handling code assumes that gy.youmonst.m_id + * always remains 1 and gy.youmonst.mx will always remain 0 when it handles + * the case of the player polymorphed into a light-emitting monster. * * Transformation sequences: * /-> polymon poly into monster form @@ -21,29 +21,36 @@ #include "hack.h" -STATIC_DCL void FDECL(check_strangling, (BOOLEAN_P)); -STATIC_DCL void FDECL(polyman, (const char *, const char *)); -STATIC_DCL void FDECL(dropp, (struct obj *)); -STATIC_DCL void NDECL(break_armor); -STATIC_DCL void FDECL(drop_weapon, (int)); -STATIC_DCL int FDECL(armor_to_dragon, (int)); -STATIC_DCL void NDECL(newman); -STATIC_DCL void NDECL(polysense); +staticfn void check_strangling(boolean); +staticfn void polyman(const char *, const char *); +staticfn void dropp(struct obj *); +staticfn void break_armor(void); +staticfn void drop_weapon(int); +staticfn int armor_to_dragon(int); +staticfn void newman(void); +staticfn void polysense(void); -STATIC_VAR const char no_longer_petrify_resistant[] = +static const char no_longer_petrify_resistant[] = "No longer petrify-resistant, you"; -/* controls whether taking on new form or becoming new man can also - change sex (ought to be an arg to polymon() and newman() instead) */ -STATIC_VAR int sex_change_ok = 0; - -/* update the youmonst.data structure pointer and intrinsics */ +/* update the gy.youmonst.data structure pointer and intrinsics */ void -set_uasmon() +set_uasmon(void) { struct permonst *mdat = &mons[u.umonnum]; + boolean was_vampshifter = valid_vampshiftform(gy.youmonst.cham, u.umonnum); + + set_mon_data(&gy.youmonst, mdat); + gy.youmonst.m_id = 1; - set_mon_data(&youmonst, mdat); + if (Protection_from_shape_changers) + gy.youmonst.cham = NON_PM; + else if (is_vampire(gy.youmonst.data)) + gy.youmonst.cham = gy.youmonst.mnum; + /* assume hero-as-chameleon/doppelganger/sandestin doesn't change shape */ + else if (!was_vampshifter) + gy.youmonst.cham = NON_PM; + u.mcham = gy.youmonst.cham; /* for save/restore since youmonst isn't */ #define PROPSET(PropIndx, ON) \ do { \ @@ -52,21 +59,22 @@ set_uasmon() else \ u.uprops[PropIndx].intrinsic &= ~FROMFORM; \ } while (0) - - PROPSET(FIRE_RES, resists_fire(&youmonst)); - PROPSET(COLD_RES, resists_cold(&youmonst)); - PROPSET(SLEEP_RES, resists_sleep(&youmonst)); - PROPSET(DISINT_RES, resists_disint(&youmonst)); - PROPSET(SHOCK_RES, resists_elec(&youmonst)); - PROPSET(POISON_RES, resists_poison(&youmonst)); - PROPSET(ACID_RES, resists_acid(&youmonst)); - PROPSET(STONE_RES, resists_ston(&youmonst)); +#define resist_from_form(MRtyp) ((gy.youmonst.data->mresists & (MRtyp)) != 0) + + PROPSET(FIRE_RES, resist_from_form(MR_FIRE)); + PROPSET(COLD_RES, resist_from_form( MR_COLD)); + PROPSET(SLEEP_RES, resist_from_form(MR_SLEEP)); + PROPSET(DISINT_RES, resist_from_form(MR_DISINT)); + PROPSET(SHOCK_RES, resist_from_form(MR_ELEC)); + PROPSET(POISON_RES, resist_from_form(MR_POISON)); + PROPSET(ACID_RES, resist_from_form(MR_ACID)); + PROPSET(STONE_RES, resist_from_form(MR_STONE)); { /* resists_drli() takes wielded weapon into account; suppress it */ struct obj *save_uwep = uwep; uwep = 0; - PROPSET(DRAIN_RES, resists_drli(&youmonst)); + PROPSET(DRAIN_RES, resists_drli(&gy.youmonst)); uwep = save_uwep; } /* resists_magm() takes wielded, worn, and carried equipment into @@ -81,7 +89,7 @@ set_uasmon() PROPSET(SEE_INVIS, perceives(mdat)); PROPSET(TELEPAT, telepathic(mdat)); /* note that Infravision uses mons[race] rather than usual mons[role] */ - PROPSET(INFRAVISION, infravision(Upolyd ? mdat : &mons[urace.malenum])); + PROPSET(INFRAVISION, infravision(Upolyd ? mdat : &mons[gu.urace.mnum])); PROPSET(INVIS, pm_invisible(mdat)); PROPSET(TELEPORT, can_teleport(mdat)); PROPSET(TELEPORT_CONTROL, control_teleport(mdat)); @@ -96,20 +104,31 @@ set_uasmon() PROPSET(PASSES_WALLS, passes_walls(mdat)); PROPSET(REGENERATION, regenerates(mdat)); PROPSET(REFLECTING, (mdat == &mons[PM_SILVER_DRAGON])); + PROPSET(BLINDED, !haseyes(mdat)); + PROPSET(BLND_RES, (dmgtype_fromattack(mdat, AD_BLND, AT_EXPL) + || dmgtype_fromattack(mdat, AD_BLND, AT_GAZE))); #undef PROPSET - - float_vs_flight(); /* maybe toggle (BFlying & I_SPECIAL) */ +#undef resist_from_form + + /* whether the player is flying/floating depends on their steed, + which won't be known during the restore process: but BFlying + and BStealth should be set correctly already in that case, so + there's nothing to do */ + if (!program_state.restoring) + float_vs_flight(); /* maybe toggle (BFlying & I_SPECIAL) */ polysense(); #ifdef STATUS_HILITES if (VIA_WINDOWPORT()) status_initialize(REASSESS_ONLY); #endif + /* we can reset this now, having just done what it is meant to trigger */ + gw.were_changes = 0L; } /* Levitation overrides Flying; set or clear BFlying|I_SPECIAL */ void -float_vs_flight() +float_vs_flight(void) { boolean stuck_in_floor = (u.utrap && u.utraptype != TT_PIT); @@ -126,46 +145,64 @@ float_vs_flight() BLevitation |= I_SPECIAL; else BLevitation &= ~I_SPECIAL; - context.botl = TRUE; + + /* riding blocks stealth unless hero+steed fly, so a change in flying + might cause a change in stealth */ + steed_vs_stealth(); + + disp.botl = TRUE; +} + +/* riding blocks stealth unless hero+steed fly */ +void +steed_vs_stealth(void) +{ + if (u.usteed && !Flying && !Levitation) + BStealth |= FROMOUTSIDE; + else + BStealth &= ~FROMOUTSIDE; } /* for changing into form that's immune to strangulation */ -STATIC_OVL void -check_strangling(on) -boolean on; +staticfn void +check_strangling(boolean on) { /* on -- maybe resume strangling */ if (on) { + boolean was_strangled = (Strangled != 0L); + /* when Strangled is already set, polymorphing from one vulnerable form into another causes the counter to be reset */ if (uamul && uamul->otyp == AMULET_OF_STRANGULATION - && can_be_strangled(&youmonst)) { + && can_be_strangled(&gy.youmonst)) { Strangled = 6L; - context.botl = TRUE; + disp.botl = TRUE; Your("%s %s your %s!", simpleonames(uamul), - Strangled ? "still constricts" : "begins constricting", + was_strangled ? "still constricts" : "begins constricting", body_part(NECK)); /* "throat" */ makeknown(AMULET_OF_STRANGULATION); } /* off -- maybe block strangling */ } else { - if (Strangled && !can_be_strangled(&youmonst)) { + if (Strangled && !can_be_strangled(&gy.youmonst)) { Strangled = 0L; - context.botl = TRUE; + disp.botl = TRUE; You("are no longer being strangled."); } } } +DISABLE_WARNING_FORMAT_NONLITERAL + /* make a (new) human out of the player */ -STATIC_OVL void -polyman(fmt, arg) -const char *fmt, *arg; +staticfn void +polyman(const char *fmt, const char *arg) { - boolean sticky = (sticks(youmonst.data) && u.ustuck && !u.uswallow), + boolean sticking = (sticks(gy.youmonst.data) && u.ustuck && !u.uswallow), was_mimicking = (U_AP_TYPE != M_AP_NOTHING); - boolean was_blind = !!Blind; + boolean was_blind = !!Blind, + had_see_invis = !!See_invisible; if (Upolyd) { u.acurr = u.macurr; /* restore old attribs */ @@ -180,43 +217,46 @@ const char *fmt, *arg; skinback(FALSE); u.uundetected = 0; - if (sticky) + if (sticking) uunstick(); find_ac(); if (was_mimicking) { - if (multi < 0) + if (gm.multi < 0) unmul(""); - youmonst.m_ap_type = M_AP_NOTHING; - youmonst.mappearance = 0; + gy.youmonst.m_ap_type = M_AP_NOTHING; + gy.youmonst.mappearance = 0; } newsym(u.ux, u.uy); - You(fmt, arg); + urgent_pline(fmt, arg); /* check whether player foolishly genocided self while poly'd */ if (ugenocided()) { /* intervening activity might have clobbered genocide info */ struct kinfo *kptr = find_delayed_killer(POLYMORPH); if (kptr != (struct kinfo *) 0 && kptr->name[0]) { - killer.format = kptr->format; - Strcpy(killer.name, kptr->name); + svk.killer.format = kptr->format; + Strcpy(svk.killer.name, kptr->name); } else { - killer.format = KILLED_BY; - Strcpy(killer.name, "self-genocide"); + svk.killer.format = KILLED_BY; + Strcpy(svk.killer.name, "self-genocide"); } dealloc_killer(kptr); done(GENOCIDED); } - if (u.twoweap && !could_twoweap(youmonst.data)) + if (!!See_invisible ^ had_see_invis) + set_mimic_blocking(); /* See_invisible just toggled */ + + if (u.twoweap && !could_twoweap(gy.youmonst.data)) untwoweapon(); if (u.utrap && u.utraptype == TT_PIT) { set_utrap(rn1(6, 2), TT_PIT); /* time to escape resets */ } if (was_blind && !Blind) { /* reverting from eyeless */ - Blinded = 1L; + set_itimeout(&HBlinded, 1L); make_blinded(0L, TRUE); /* remove blindness */ } check_strangling(TRUE); @@ -227,48 +267,76 @@ const char *fmt, *arg; see_monsters(); } +RESTORE_WARNING_FORMAT_NONLITERAL + void -change_sex() +change_sex(void) { - /* setting u.umonster for caveman/cavewoman or priest/priestess - swap unintentionally makes `Upolyd' appear to be true */ - boolean already_polyd = (boolean) Upolyd; - /* Some monsters are always of one sex and their sex can't be changed; * Succubi/incubi can change, but are handled below. * - * !already_polyd check necessary because is_male() and is_female() - * are true if the player is a priest/priestess. + * !Upolyd check necessary because is_male() and is_female() + * may be true for certain roles */ - if (!already_polyd - || (!is_male(youmonst.data) && !is_female(youmonst.data) - && !is_neuter(youmonst.data))) + if (!Upolyd + || (!is_male(gy.youmonst.data) && !is_female(gy.youmonst.data) + && !is_neuter(gy.youmonst.data))) flags.female = !flags.female; - if (already_polyd) /* poly'd: also change saved sex */ + if (Upolyd) /* poly'd: also change saved sex */ u.mfemale = !u.mfemale; max_rank_sz(); /* [this appears to be superfluous] */ - if ((already_polyd ? u.mfemale : flags.female) && urole.name.f) - Strcpy(pl_character, urole.name.f); + if ((Upolyd ? u.mfemale : flags.female) && gu.urole.name.f) + Strcpy(svp.pl_character, gu.urole.name.f); else - Strcpy(pl_character, urole.name.m); - u.umonster = ((already_polyd ? u.mfemale : flags.female) - && urole.femalenum != NON_PM) - ? urole.femalenum - : urole.malenum; - if (!already_polyd) { + Strcpy(svp.pl_character, gu.urole.name.m); + if (!Upolyd) { u.umonnum = u.umonster; - } else if (u.umonnum == PM_SUCCUBUS || u.umonnum == PM_INCUBUS) { + } else if (u.umonnum == PM_AMOROUS_DEMON) { flags.female = !flags.female; - /* change monster type to match new sex */ +#if 0 + /* change monster type to match new sex; disabled with + PM_AMOROUS_DEMON */ u.umonnum = (u.umonnum == PM_SUCCUBUS) ? PM_INCUBUS : PM_SUCCUBUS; +#endif set_uasmon(); } } -STATIC_OVL void -newman() +/* log a message if non-poly'd hero's gender has changed */ +void +livelog_newform(boolean viapoly, int oldgend, int newgend) { - int i, oldlvl, newlvl, hpmax, enmax; + char buf[BUFSZ]; + const char *oldrole, *oldrank, *newrole, *newrank; + + /* + * TODO? + * Give other logging feedback here instead of in newman(). + */ + + if (!Upolyd) { + if (newgend != oldgend) { + oldrole = (oldgend && gu.urole.name.f) ? gu.urole.name.f + : gu.urole.name.m; + newrole = (newgend && gu.urole.name.f) ? gu.urole.name.f + : gu.urole.name.m; + oldrank = rank_of(u.ulevel, Role_switch, oldgend); + newrank = rank_of(u.ulevel, Role_switch, newgend); + Sprintf(buf, "%.10s %.30s", genders[flags.female].adj, newrank); + livelog_printf(LL_MINORAC, "%s into %s", + viapoly ? "polymorphed" : "transformed", + an(strcmp(newrole, oldrole) ? newrole + : strcmp(newrank, oldrank) ? newrank + : buf)); + } + } +} + +staticfn void +newman(void) +{ + const char *newform; + int i, oldlvl, newlvl, oldgend, newgend, hpmax, enmax; oldlvl = u.ulevel; newlvl = oldlvl + rn1(5, -2); /* new = old + {-2,-1,0,+1,+2} */ @@ -289,11 +357,11 @@ newman() u.ulevelmax = newlvl; u.ulevel = newlvl; - if (sex_change_ok && !rn2(10)) + oldgend = poly_gender(); + if (gs.sex_change_ok && !rn2(10)) change_sex(); adjabil(oldlvl, (int) u.ulevel); - reset_rndmonst(NON_PM); /* new monster generation criteria */ /* random experience points for the new experience level */ u.uexp = rndexp(FALSE); @@ -303,7 +371,7 @@ newman() /* * New hit points: - * remove level-gain based HP from any extra HP accumulated + * remove "level gain"-based HP from any extra HP accumulated * (the "extra" might actually be negative); * modify the extra, retaining {80%, 90%, 100%, or 110%}; * add in newly generated set of level-gain HP. @@ -326,7 +394,7 @@ newman() hpmax = u.ulevel; /* min of 1 HP per level */ /* retain same proportion for current HP; u.uhp * hpmax / u.uhpmax */ u.uhp = rounddiv((long) u.uhp * (long) hpmax, u.uhpmax); - u.uhpmax = hpmax; + setuhpmax(hpmax, TRUE); /* might reduce u.uhp */ /* * Do the same for spell power. */ @@ -353,32 +421,44 @@ newman() if (u.uhp <= 0) u.uhp = 1; } else { - dead: /* we come directly here if their experience level went to 0 or - less */ - Your("new form doesn't seem healthy enough to survive."); - killer.format = KILLED_BY_AN; - Strcpy(killer.name, "unsuccessful polymorph"); + dead: /* we come directly here if experience level went to 0 or less */ + urgent_pline( + "Your new form doesn't seem healthy enough to survive."); + svk.killer.format = KILLED_BY_AN; + Strcpy(svk.killer.name, "unsuccessful polymorph"); done(DIED); + /* must have been life-saved to get here */ newuhs(FALSE); + encumber_msg(); /* used to be done by redist_attr() */ return; /* lifesaved */ } } newuhs(FALSE); - polyman("feel like a new %s!", - /* use saved gender we're about to revert to, not current */ - ((Upolyd ? u.mfemale : flags.female) && urace.individual.f) - ? urace.individual.f - : (urace.individual.m) - ? urace.individual.m - : urace.noun); + /* use saved gender we're about to revert to, not current */ + newform = ((Upolyd ? u.mfemale : flags.female) && gu.urace.individual.f) + ? gu.urace.individual.f + : (gu.urace.individual.m) + ? gu.urace.individual.m + : gu.urace.noun; + polyman("You feel like a new %s!", newform); + + newgend = poly_gender(); + /* note: newman() bypasses achievements for new ranks attained and + doesn't log "new
" when that isn't accompanied by level change */ + if (newlvl != oldlvl) + livelog_printf(LL_MINORAC, "became experience level %d as a new %s", + newlvl, newform); + else + livelog_newform(TRUE, oldgend, newgend); + if (Slimed) { Your("body transforms, but there is still slime on you."); make_slimed(10L, (const char *) 0); } - context.botl = 1; + disp.botl = TRUE; see_monsters(); - (void) encumber_msg(); + encumber_msg(); retouch_equipment(2); if (!uarmg) @@ -386,18 +466,22 @@ newman() } void -polyself(psflags) -int psflags; +polyself(int psflags) { - char buf[BUFSZ] = DUMMY; - int old_light, new_light, mntmp, class, tryct; - boolean forcecontrol = (psflags == 1), monsterpoly = (psflags == 2), + char buf[BUFSZ]; + int old_light, new_light, mntmp, class, tryct, gvariant = NEUTRAL; + boolean forcecontrol = ((psflags & POLY_CONTROLLED) != 0), + low_control = ((psflags & POLY_LOW_CTRL) != 0), + monsterpoly = ((psflags & POLY_MONSTER) != 0), + formrevert = ((psflags & POLY_REVERT) != 0), draconian = (uarm && Is_dragon_armor(uarm)), - iswere = (u.ulycn >= LOW_PM), isvamp = is_vampire(youmonst.data), + iswere = (ismnum(u.ulycn)), + isvamp = (is_vampire(gy.youmonst.data) + || is_vampshifter(&gy.youmonst)), controllable_poly = Polymorph_control && !(Stunned || Unaware); if (Unchanging) { - pline("You fail to transform!"); + You("fail to transform!"); return; } /* being Stunned|Unaware doesn't negate this aspect of Poly_control */ @@ -410,14 +494,26 @@ int psflags; return; } } - old_light = emits_light(youmonst.data); + old_light = emits_light(gy.youmonst.data); mntmp = NON_PM; + if (formrevert) { + mntmp = gy.youmonst.cham; + monsterpoly = TRUE; + controllable_poly = FALSE; + } + + if (forcecontrol && low_control + && (draconian || monsterpoly || isvamp || iswere)) + forcecontrol = FALSE; + if (monsterpoly && isvamp) goto do_vampyr; if (controllable_poly || forcecontrol) { + buf[0] = '\0'; tryct = 5; + do { mntmp = NON_PM; getlin("Become what kind of monster? [type the name]", buf); @@ -436,29 +532,67 @@ int psflags; continue; /* end do-while(--tryct > 0) loop */ } class = 0; - mntmp = name_to_mon(buf); + mntmp = name_to_mon(buf, &gvariant); if (mntmp < LOW_PM) { - by_class: + by_class: class = name_to_monclass(buf, &mntmp); if (class && mntmp == NON_PM) - mntmp = mkclass_poly(class); + mntmp = (draconian && class == S_DRAGON) + ? armor_to_dragon(uarm->otyp) + : mkclass_poly(class); + + /* placeholder monsters are for corpses and all flagged + M2_NOPOLY but they are reasonable polymorph targets; + pick a suitable substitute (which might be geno'd) */ + } else if (is_placeholder(&mons[mntmp]) + /* when your own race, fall to !polyok() case */ + && !your_race(&mons[mntmp]) + /* same for generic human, even if hero isn't human */ + && mntmp != PM_HUMAN) { + /* far less general than mkclass() */ + if (mntmp == PM_ORC) + mntmp = rn2(3) ? PM_HILL_ORC : PM_MORDOR_ORC; + else if (mntmp == PM_ELF) + mntmp = rn2(3) ? PM_GREEN_ELF : PM_GREY_ELF; + else if (mntmp == PM_GIANT) + mntmp = rn2(3) ? PM_STONE_GIANT : PM_HILL_GIANT; + /* note: PM_DWARF and PM_GNOME are ordinary monsters and + no longer flagged no-poly so have no need for placeholder + handling; PM_HUMAN is a placeholder without a suitable + substitute so gets handled differently below */ } + if (mntmp < LOW_PM) { if (!class) pline("I've never heard of such monsters."); else You_cant("polymorph into any of those."); + } else if (wizard && Upolyd + && (mntmp == u.umonster + /* "priest" and "priestess" match the monster + rather than the role; override that unless + the text explicitly contains "aligned" */ + || (u.umonster == PM_CLERIC + && mntmp == PM_ALIGNED_CLERIC + && !strstri(buf, "aligned")))) { + /* in wizard mode, picking own role while poly'd reverts to + normal without newman()'s chance of level or sex change */ + rehumanize(); + old_light = 0; /* rehumanize() extinguishes u-as-mon light */ + goto made_change; } else if (iswere && (were_beastie(mntmp) == u.ulycn || mntmp == counter_were(u.ulycn) || (Upolyd && mntmp == PM_HUMAN))) { goto do_shift; - /* Note: humans are illegal as monsters, but an - * illegal monster forces newman(), which is what we - * want if they specified a human.... */ } else if (!polyok(&mons[mntmp]) - && !(mntmp == PM_HUMAN || your_race(&mons[mntmp]) - || mntmp == urole.malenum - || mntmp == urole.femalenum)) { + /* Note: humans are illegal as monsters, but an + illegal monster forces newman(), which is what + we want if they specified a human.... (unless + they specified a unique monster) */ + && !(mntmp == PM_HUMAN + || (your_race(&mons[mntmp]) + && (mons[mntmp].geno & G_UNIQ) == 0) + || mntmp == gu.urole.mnum)) { const char *pm_name; /* mkclass_poly() can pick a !polyok() @@ -471,7 +605,7 @@ int psflags; 0 and trigger thats_enough_tries message */ ++tryct; } - pm_name = mons[mntmp].mname; + pm_name = pmname(&mons[mntmp], flags.female ? FEMALE : MALE); if (the_unique_pm(&mons[mntmp])) pm_name = the(pm_name); else if (!type_is_pname(&mons[mntmp])) @@ -480,6 +614,7 @@ int psflags; } else break; } while (--tryct > 0); + if (!tryct) pline1(thats_enough_tries); /* allow skin merging, even when polymorph is controlled */ @@ -491,55 +626,62 @@ int psflags; } else if (draconian || iswere || isvamp) { /* special changes that don't require polyok() */ if (draconian) { - do_merge: + do_merge: mntmp = armor_to_dragon(uarm->otyp); - if (!(mvitals[mntmp].mvflags & G_GENOD)) { + if (!(svm.mvitals[mntmp].mvflags & G_GENOD)) { + unsigned was_lit = uarm->lamplit; + int arm_light = artifact_light(uarm) ? arti_light_radius(uarm) + : 0; + /* allow G_EXTINCT */ if (Is_dragon_scales(uarm)) { /* dragon scales remain intact as uskin */ You("merge with your scaly armor."); - } else { /* dragon scale mail */ - /* d.scale mail first reverts to scales */ - char *p, *dsmail; - + } else { /* dragon scale mail reverts to scales */ /* similar to noarmor(invent.c), shorten to " scale mail" */ - dsmail = strcpy(buf, simpleonames(uarm)); - if ((p = strstri(dsmail, " dragon ")) != 0) - while ((p[1] = p[8]) != '\0') - ++p; - /* tricky phrasing; dragon scale mail - is singular, dragon scales are plural */ - Your("%s reverts to scales as you merge with them.", - dsmail); + Strcpy(buf, simpleonames(uarm)); + strsubst(buf, " dragon ", " "); + /* tricky phrasing; dragon scale mail is singular, dragon + scales are plural (note: we don't use "set of scales", + which usually overrides the distinction, here) */ + Your("%s reverts to scales as you merge with them.", buf); /* uarm->spe enchantment remains unchanged; re-converting scales to mail poses risk of evaporation due to over enchanting */ uarm->otyp += GRAY_DRAGON_SCALES - GRAY_DRAGON_SCALE_MAIL; - uarm->dknown = 1; - context.botl = 1; /* AC is changing */ + observe_object(uarm); + disp.botl = TRUE; /* AC is changing */ } uskin = uarm; uarm = (struct obj *) 0; /* save/restore hack */ uskin->owornmask |= I_SPECIAL; + if (was_lit) + maybe_adjust_light(uskin, arm_light); update_inventory(); } } else if (iswere) { - do_shift: + do_shift: if (Upolyd && were_beastie(mntmp) != u.ulycn) mntmp = PM_HUMAN; /* Illegal; force newman() */ else mntmp = u.ulycn; } else if (isvamp) { - do_vampyr: - if (mntmp < LOW_PM || (mons[mntmp].geno & G_UNIQ)) - mntmp = (youmonst.data != &mons[PM_VAMPIRE] && !rn2(10)) - ? PM_WOLF - : !rn2(4) ? PM_FOG_CLOUD : PM_VAMPIRE_BAT; + do_vampyr: + if (mntmp < LOW_PM || (mons[mntmp].geno & G_UNIQ)) { + mntmp = (gy.youmonst.data == &mons[PM_VAMPIRE_LEADER] + && !rn2(10)) ? PM_WOLF + : !rn2(4) ? PM_FOG_CLOUD + : PM_VAMPIRE_BAT; + if (ismnum(gy.youmonst.cham) + && !is_vampire(gy.youmonst.data) && !rn2(2)) + mntmp = gy.youmonst.cham; + } if (controllable_poly) { - Sprintf(buf, "Become %s?", an(mons[mntmp].mname)); - if (yn(buf) != 'y') + Sprintf(buf, "Become %s?", + an(pmname(&mons[mntmp], gvariant))); + if (y_n(buf) != 'y') return; } } @@ -566,47 +708,50 @@ int psflags; /* The below polyok() fails either if everything is genocided, or if * we deliberately chose something illegal to force newman(). */ - sex_change_ok++; + gs.sex_change_ok++; if (!polyok(&mons[mntmp]) || (!forcecontrol && !rn2(5)) || your_race(&mons[mntmp])) { newman(); } else { (void) polymon(mntmp); } - sex_change_ok--; /* reset */ + gs.sex_change_ok--; /* reset */ -made_change: - new_light = emits_light(youmonst.data); + made_change: + new_light = emits_light(gy.youmonst.data); if (old_light != new_light) { if (old_light) - del_light_source(LS_MONSTER, monst_to_any(&youmonst)); + del_light_source(LS_MONSTER, monst_to_any(&gy.youmonst)); if (new_light == 1) ++new_light; /* otherwise it's undetectable */ if (new_light) new_light_source(u.ux, u.uy, new_light, LS_MONSTER, - monst_to_any(&youmonst)); + monst_to_any(&gy.youmonst)); } } -/* (try to) make a mntmp monster out of the player; - returns 1 if polymorph successful */ +/* (try to) make a mntmp monster out of the player; return 1 if successful */ int -polymon(mntmp) -int mntmp; +polymon(int mntmp) { - char buf[BUFSZ]; - boolean sticky = sticks(youmonst.data) && u.ustuck && !u.uswallow, - was_blind = !!Blind, dochange = FALSE; - int mlvl; - - if (mvitals[mntmp].mvflags & G_GENOD) { /* allow G_EXTINCT */ - You_feel("rather %s-ish.", mons[mntmp].mname); + char buf[BUFSZ], ustuckNam[BUFSZ]; + boolean sticking = sticks(gy.youmonst.data) && u.ustuck && !u.uswallow, + was_blind = !!Blind, dochange = FALSE, was_expelled = FALSE, + was_hiding_under = u.uundetected && hides_under(gy.youmonst.data); + int mlvl, newMaxStr; + + if (svm.mvitals[mntmp].mvflags & G_GENOD) { /* allow G_EXTINCT */ + You_feel("rather %s-ish.", + pmname(&mons[mntmp], flags.female ? FEMALE : MALE)); exercise(A_WIS, TRUE); return 0; } /* KMH, conduct */ - u.uconduct.polyselfs++; + if (!u.uconduct.polyselfs++) + livelog_printf(LL_CONDUCT, + "changed form for the first time, becoming %s", + an(pmname(&mons[mntmp], flags.female ? FEMALE : MALE))); /* exercise used to be at the very end but only Wis was affected there since the polymorph was always in effect by then */ @@ -628,14 +773,14 @@ int mntmp; } /* if stuck mimicking gold, stop immediately */ - if (multi < 0 && U_AP_TYPE == M_AP_OBJECT - && youmonst.data->mlet != S_MIMIC) + if (gm.multi < 0 && U_AP_TYPE == M_AP_OBJECT + && gy.youmonst.data->mlet != S_MIMIC) unmul(""); /* if becoming a non-mimic, stop mimicking anything */ if (mons[mntmp].mlet != S_MIMIC) { /* as in polyman() */ - youmonst.m_ap_type = M_AP_NOTHING; - youmonst.mappearance = 0; + gy.youmonst.m_ap_type = M_AP_NOTHING; + gy.youmonst.mappearance = 0; } if (is_male(&mons[mntmp])) { if (flags.female) @@ -644,17 +789,19 @@ int mntmp; if (!flags.female) dochange = TRUE; } else if (!is_neuter(&mons[mntmp]) && mntmp != u.ulycn) { - if (sex_change_ok && !rn2(10)) + if (gs.sex_change_ok && !rn2(10)) dochange = TRUE; } + Strcpy(ustuckNam, u.ustuck ? Some_Monnam(u.ustuck) : ""); + Strcpy(buf, (u.umonnum != mntmp) ? "" : "new "); if (dochange) { flags.female = !flags.female; Strcat(buf, (is_male(&mons[mntmp]) || is_female(&mons[mntmp])) ? "" : flags.female ? "female " : "male "); } - Strcat(buf, mons[mntmp].mname); + Strcat(buf, pmname(&mons[mntmp], flags.female ? FEMALE : MALE)); You("%s %s!", (u.umonnum != mntmp) ? "turn into" : "feel like", an(buf)); if (Stoned && poly_when_stoned(&mons[mntmp])) { @@ -670,8 +817,19 @@ int mntmp; /* New stats for monster, to last only as long as polymorphed. * Currently only strength gets changed. */ - if (strongmonst(&mons[mntmp])) - ABASE(A_STR) = AMAX(A_STR) = STR18(100); + newMaxStr = uasmon_maxStr(); + if (strongmonst(&mons[mntmp])) { + ABASE(A_STR) = AMAX(A_STR) = (schar) newMaxStr; + } else { + /* not a strongmonst(); if hero has exceptional strength, remove it + (note: removal is temporary until returning to original form); + we don't attempt to enforce lower maximum for wimpy forms; + unlike for strongmonst, current strength does not get set to max */ + AMAX(A_STR) = (schar) newMaxStr; + /* make sure current is not higher than max (strip exceptional Str) */ + if (ABASE(A_STR) > AMAX(A_STR)) + ABASE(A_STR) = AMAX(A_STR); + } if (Stone_resistance && Stoned) { /* parnes@eniac.seas.upenn.edu */ make_stoned(0L, "You no longer seem to be petrifying.", 0, @@ -682,7 +840,7 @@ int mntmp; You("no longer feel sick."); } if (Slimed) { - if (flaming(youmonst.data)) { + if (flaming(gy.youmonst.data)) { make_slimed(0L, "The slime burns away!"); } else if (mntmp == PM_GREEN_SLIME) { /* do it silently */ @@ -690,7 +848,7 @@ int mntmp; } } check_strangling(FALSE); /* maybe stop strangling */ - if (nohands(youmonst.data)) + if (nohands(gy.youmonst.data)) make_glib(0); /* @@ -699,9 +857,9 @@ int mntmp; * "experience level of you as a monster" for a polymorphed character. */ mlvl = (int) mons[mntmp].mlevel; - if (youmonst.data->mlet == S_DRAGON && mntmp >= PM_GRAY_DRAGON) { + if (gy.youmonst.data->mlet == S_DRAGON && mntmp >= PM_GRAY_DRAGON) { u.mhmax = In_endgame(&u.uz) ? (8 * mlvl) : (4 * mlvl + d(mlvl, 4)); - } else if (is_golem(youmonst.data)) { + } else if (is_golem(gy.youmonst.data)) { u.mhmax = golemhp(mntmp); } else { if (!mlvl) @@ -729,83 +887,92 @@ int mntmp; skinback(FALSE); break_armor(); drop_weapon(1); - (void) hideunder(&youmonst); + find_ac(); /* (repeated below) */ + /* if hiding under something and can't hide anymore, unhide now; + but don't auto-hide when not already hiding-under */ + if (was_hiding_under) + (void) hideunder(&gy.youmonst); if (u.utrap && u.utraptype == TT_PIT) { set_utrap(rn1(6, 2), TT_PIT); /* time to escape resets */ } if (was_blind && !Blind) { /* previous form was eyeless */ - Blinded = 1L; + set_itimeout(&HBlinded, 1L); make_blinded(0L, TRUE); /* remove blindness */ } newsym(u.ux, u.uy); /* Change symbol */ - /* [note: this 'sticky' handling is only sufficient for changing from + /* you now know what an egg of your type looks like; [moved from + below in case expels() -> spoteffects() drops hero onto any eggs] */ + if (lays_eggs(gy.youmonst.data)) { + learn_egg_type(u.umonnum); + /* make queen bees recognize killer bee eggs */ + learn_egg_type(egg_type_from_parent(u.umonnum, TRUE)); + } + + if (u.uswallow) { + uchar usiz; + + /* if new form can't be swallowed, make engulfer expel hero */ + if (unsolid(gy.youmonst.data) + /* subset of engulf_target() */ + || (usiz = gy.youmonst.data->msize) >= MZ_HUGE + || (u.ustuck->data->msize < usiz && !is_whirly(u.ustuck->data))) { + boolean expels_mesg = TRUE; + + if (unsolid(gy.youmonst.data)) { + if (canspotmon(u.ustuck)) /* [see below for explanation] */ + Strcpy(ustuckNam, Monnam(u.ustuck)); + pline("%s can no longer contain you.", ustuckNam); + expels_mesg = FALSE; + } + expels(u.ustuck, u.ustuck->data, expels_mesg); + was_expelled = TRUE; + /* FIXME? if expels() triggered rehumanize then we should + return early */ + } + + /* [note: this 'sticking' handling is only sufficient for changing from grabber to engulfer or vice versa because engulfing by poly'd hero always ends immediately so won't be in effect during a polymorph] */ - if (!sticky && !u.uswallow && u.ustuck && sticks(youmonst.data)) - u.ustuck = 0; - else if (sticky && !sticks(youmonst.data)) + } else if (u.ustuck && !sticking /* && !u.uswallow */ + /* being held; if now capable of holding, make holder + release so that hero doesn't automagically start holding + it; or, release if no longer capable of being held */ + && (sticks(gy.youmonst.data) || unsolid(gy.youmonst.data))) { + /* u.ustuck name was saved above in case we're changing from can-see + to can't-see; but might have changed from can't-see to can-see so + override here if hero knows who u.ustuck is */ + if (canspotmon(u.ustuck)) + Strcpy(ustuckNam, Monnam(u.ustuck)); + set_ustuck((struct monst *) 0); + pline("%s loses its grip on you.", ustuckNam); + } else if (sticking && !sticks(gy.youmonst.data)) { + /* was holding onto u.ustuck but no longer capable of that */ uunstick(); + } if (u.usteed) { if (touch_petrifies(u.usteed->data) && !Stone_resistance && rnl(3)) { pline("%s touch %s.", no_longer_petrify_resistant, mon_nam(u.usteed)); - Sprintf(buf, "riding %s", an(u.usteed->data->mname)); + Sprintf(buf, "riding %s", + an(pmname(u.usteed->data, Mgender(u.usteed)))); instapetrify(buf); } if (!can_ride(u.usteed)) dismount_steed(DISMOUNT_POLY); } - if (flags.verbose) { - static const char use_thec[] = "Use the command #%s to %s."; - static const char monsterc[] = "monster"; - - if (can_breathe(youmonst.data)) - pline(use_thec, monsterc, "use your breath weapon"); - if (attacktype(youmonst.data, AT_SPIT)) - pline(use_thec, monsterc, "spit venom"); - if (youmonst.data->mlet == S_NYMPH) - pline(use_thec, monsterc, "remove an iron ball"); - if (attacktype(youmonst.data, AT_GAZE)) - pline(use_thec, monsterc, "gaze at monsters"); - if (is_hider(youmonst.data)) - pline(use_thec, monsterc, "hide"); - if (is_were(youmonst.data)) - pline(use_thec, monsterc, "summon help"); - if (webmaker(youmonst.data)) - pline(use_thec, monsterc, "spin a web"); - if (u.umonnum == PM_GREMLIN) - pline(use_thec, monsterc, "multiply in a fountain"); - if (is_unicorn(youmonst.data)) - pline(use_thec, monsterc, "use your horn"); - if (is_mind_flayer(youmonst.data)) - pline(use_thec, monsterc, "emit a mental blast"); - if (youmonst.data->msound == MS_SHRIEK) /* worthless, actually */ - pline(use_thec, monsterc, "shriek"); - if (is_vampire(youmonst.data)) - pline(use_thec, monsterc, "change shape"); - - if (lays_eggs(youmonst.data) && flags.female && - !(youmonst.data == &mons[PM_GIANT_EEL] - || youmonst.data == &mons[PM_ELECTRIC_EEL])) - pline(use_thec, "sit", - eggs_in_water(youmonst.data) ? - "spawn in the water" : "lay an egg"); - } - - /* you now know what an egg of your type looks like */ - if (lays_eggs(youmonst.data)) { - learn_egg_type(u.umonnum); - /* make queen bees recognize killer bee eggs */ - learn_egg_type(egg_type_from_parent(u.umonnum, TRUE)); - } find_ac(); - if ((!Levitation && !u.ustuck && !Flying && is_pool_or_lava(u.ux, u.uy)) - || (Underwater && !Swimming)) + if (((!Levitation && !u.ustuck && !Flying && is_pool_or_lava(u.ux, u.uy)) + || (Underwater && !Swimming)) + /* if expelled above, expels() already called spoteffects() */ + && !was_expelled) { spoteffects(TRUE); + /* FIXME? if spoteffects() triggered rehumanize then we should + return early */ + } if (Passes_walls && u.utrap && (u.utraptype == TT_INFLOOR || u.utraptype == TT_BURIEDBALL)) { if (u.utraptype == TT_INFLOOR) { @@ -815,13 +982,13 @@ int mntmp; buried_ball_to_freedom(); } reset_utrap(TRUE); - } else if (likes_lava(youmonst.data) && u.utrap + } else if (likes_lava(gy.youmonst.data) && u.utrap && u.utraptype == TT_LAVA) { pline_The("%s now feels soothing.", hliquid("lava")); reset_utrap(TRUE); } - if (amorphous(youmonst.data) || is_whirly(youmonst.data) - || unsolid(youmonst.data)) { + if (amorphous(gy.youmonst.data) || is_whirly(gy.youmonst.data) + || unsolid(gy.youmonst.data)) { if (Punished) { You("slip out of the iron chain."); unpunish(); @@ -831,38 +998,129 @@ int mntmp; } } if (u.utrap && (u.utraptype == TT_WEB || u.utraptype == TT_BEARTRAP) - && (amorphous(youmonst.data) || is_whirly(youmonst.data) - || unsolid(youmonst.data) || (youmonst.data->msize <= MZ_SMALL - && u.utraptype == TT_BEARTRAP))) { + && (amorphous(gy.youmonst.data) || is_whirly(gy.youmonst.data) + || unsolid(gy.youmonst.data) + || (gy.youmonst.data->msize <= MZ_SMALL + && u.utraptype == TT_BEARTRAP))) { You("are no longer stuck in the %s.", u.utraptype == TT_WEB ? "web" : "bear trap"); /* probably should burn webs too if PM_FIRE_ELEMENTAL */ reset_utrap(TRUE); } - if (webmaker(youmonst.data) && u.utrap && u.utraptype == TT_WEB) { + if (webmaker(gy.youmonst.data) && u.utrap && u.utraptype == TT_WEB) { You("orient yourself on the web."); reset_utrap(TRUE); } check_strangling(TRUE); /* maybe start strangling */ - context.botl = 1; - vision_full_recalc = 1; + disp.botl = TRUE; + gv.vision_full_recalc = 1; see_monsters(); - (void) encumber_msg(); + encumber_msg(); retouch_equipment(2); /* this might trigger a recursive call to polymon() [stone golem wielding cockatrice corpse and hit by stone-to-flesh, becomes - flesh golem above, now gets transformed back into stone golem] */ + flesh golem above, now gets transformed back into stone golem; + fortunately neither form uses #monster] */ if (!uarmg) selftouch(no_longer_petrify_resistant); + + /* the explanation of '#monster' used to be shown sooner, but there are + possible fatalities above and it isn't useful unless hero survives */ + if (flags.verbose) { + static const char use_thec[] = "Use the command #%s to %s."; + static const char monsterc[] = "monster"; + struct permonst *uptr = gy.youmonst.data; + boolean might_hide = (is_hider(uptr) || hides_under(uptr)); + + if (can_breathe(uptr)) + pline(use_thec, monsterc, "use your breath weapon"); + if (attacktype(uptr, AT_SPIT)) + pline(use_thec, monsterc, "spit venom"); + if (uptr->mlet == S_NYMPH) + pline(use_thec, monsterc, "remove an iron ball"); + if (attacktype(uptr, AT_GAZE)) + pline(use_thec, monsterc, "gaze at monsters"); + if (might_hide && webmaker(uptr)) + pline(use_thec, monsterc, "hide or to spin a web"); + else if (might_hide) + pline(use_thec, monsterc, "hide"); + else if (webmaker(uptr)) + pline(use_thec, monsterc, "spin a web"); + if (is_were(uptr)) + pline(use_thec, monsterc, "summon help"); + if (u.umonnum == PM_GREMLIN) + pline(use_thec, monsterc, "multiply in a fountain"); + if (is_unicorn(uptr)) + pline(use_thec, monsterc, "use your horn"); + if (is_mind_flayer(uptr)) + pline(use_thec, monsterc, "emit a mental blast"); + if (uptr->msound == MS_SHRIEK) /* worthless, actually */ + pline(use_thec, monsterc, "shriek"); + if (is_vampire(uptr) || is_vampshifter(&gy.youmonst)) + pline(use_thec, monsterc, "change shape"); + + if (lays_eggs(uptr) && flags.female + && !(uptr == &mons[PM_GIANT_EEL] + || uptr == &mons[PM_ELECTRIC_EEL])) + pline(use_thec, "sit", + eggs_in_water(uptr) ? "spawn in the water" : "lay an egg"); + } return 1; } +/* determine hero's temporary strength value used while polymorphed; + hero poly'd into M2_STRONG monster usually gets 18/100 strength but + there are exceptions; non-M2_STRONG get maximum strength set to 18 */ +schar +uasmon_maxStr(void) +{ + const struct Race *R; + int newMaxStr; + int mndx = u.umonnum; + struct permonst *ptr = &mons[mndx]; + + if (is_orc(ptr)) { + if (mndx != PM_URUK_HAI && mndx != PM_ORC_CAPTAIN) + mndx = PM_ORC; + } else if (is_elf(ptr)) { + mndx = PM_ELF; + } else if (is_dwarf(ptr)) { + mndx = PM_DWARF; + } else if (is_gnome(ptr)) { + mndx = PM_GNOME; +#if 0 /* use the mons[] value for humans */ + } else if (is_human(ptr)) { + mndx = PM_HUMAN; +#endif + } + R = character_race(mndx); + + if (strongmonst(ptr)) { + /* ettins, titans and minotaurs don't pass the is_giant() test; + giant mummies and giant zombies do but we throttle those */ + boolean live_H = is_giant(ptr) && !is_undead(ptr); + + /* hero orcs are limited to 18/50 for maximum strength, so treat + hero poly'd into an orc the same; goblins, orc shamans, and orc + zombies don't have strongmonst() attribute so won't get here; + hobgoblins and orc mummies do get here and are limited to 18/50 + like normal orcs; however, orc captains and Uruk-hai retain 18/100 + strength; hero gnomes are also limited to 18/50; hero elves are + limited to 18/00 regardless of whether they're strongmonst, but + the two strongmonst types (monarchs and nobles) have current + strength set to 18 [by polymon()], the others don't */ + newMaxStr = R ? R->attrmax[A_STR] : live_H ? STR19(19) : STR18(100); + } else { + newMaxStr = R ? R->attrmax[A_STR] : 18; /* 18 is same as STR18(0) */ + } + return (schar) newMaxStr; +} + /* dropx() jacket for break_armor() */ -STATIC_OVL void -dropp(obj) -struct obj *obj; +staticfn void +dropp(struct obj *obj) { struct obj *otmp; @@ -878,53 +1136,81 @@ struct obj *obj; * applicable for armor) and no longer be a valid pointer, so scan * inventory for it instead of trusting obj->where. */ - for (otmp = invent; otmp; otmp = otmp->nobj) { + for (otmp = gi.invent; otmp; otmp = otmp->nobj) { if (otmp == obj) { dropx(obj); + /* Note that otmp->nobj is pointing at fobj now, + * as a result of: + * dropx() -> dropy() -> dropz() -> place_object(), + * and no longer pointing at the next obj in inventory. + * That would be an issue if this loop were allowed + * to continue, but the break statement that + * follows prevents the loop from continuing on with + * objects on the floor. + */ break; } } } -STATIC_OVL void -break_armor() +staticfn void +break_armor(void) { - register struct obj *otmp; + struct obj *otmp; + struct permonst *uptr = gy.youmonst.data; - if (breakarm(youmonst.data)) { + if (breakarm(uptr)) { if ((otmp = uarm) != 0) { if (donning(otmp)) cancel_don(); + /* for gold DSM, we don't want Armor_gone() to report that it + stops shining _after_ we've been told that it is destroyed */ + if (otmp->lamplit) + end_burn(otmp, FALSE); + You("break out of your armor!"); exercise(A_STR, FALSE); (void) Armor_gone(); useup(otmp); } - if ((otmp = uarmc) != 0) { - if (otmp->oartifact) { - Your("%s falls off!", cloak_simple_name(otmp)); + if ((otmp = uarmc) != 0 + /* mummy wrapping adapts to small and very big sizes */ + && (otmp->otyp != MUMMY_WRAPPING || !WrappingAllowed(uptr))) { + if (otmp->otyp == MUMMY_WRAPPING) { + /* doesn't have a clasp to break open */ + Your("%s tears apart!", cloak_simple_name(otmp)); + (void) Cloak_off(); + useup(otmp); + } else if (otmp->otyp == ALCHEMY_SMOCK) { + pline_The("knot on your %s is pulled apart!", cloak_simple_name(otmp)); (void) Cloak_off(); dropp(otmp); } else { - Your("%s tears apart!", cloak_simple_name(otmp)); + pline_The("clasp on your %s breaks open!", cloak_simple_name(otmp)); (void) Cloak_off(); - useup(otmp); + dropp(otmp); } } if (uarmu) { Your("shirt rips to shreds!"); useup(uarmu); } - } else if (sliparm(youmonst.data)) { - if (((otmp = uarm) != 0) && (racial_exception(&youmonst, otmp) < 1)) { + } else if (sliparm(uptr)) { + if ((otmp = uarm) != 0 && racial_exception(&gy.youmonst, otmp) < 1) { if (donning(otmp)) cancel_don(); Your("armor falls around you!"); + /* [note: _gone() instead of _off() dates to when life-saving + could force fire resisting armor back on if hero burned in + hell (3.0, predating Gehennom); the armor isn't actually + gone here but also isn't available to be put back on] */ (void) Armor_gone(); dropp(otmp); } - if ((otmp = uarmc) != 0) { - if (is_whirly(youmonst.data)) + if ((otmp = uarmc) != 0 + /* mummy wrapping adapts to small and very big sizes */ + && (otmp->otyp != MUMMY_WRAPPING || !WrappingAllowed(uptr))) { + if (is_whirly(uptr)) Your("%s falls, unsupported!", cloak_simple_name(otmp)); else You("shrink out of your %s!", cloak_simple_name(otmp)); @@ -932,7 +1218,7 @@ break_armor() dropp(otmp); } if ((otmp = uarmu) != 0) { - if (is_whirly(youmonst.data)) + if (is_whirly(uptr)) You("seep right through your shirt!"); else You("become much too small for your shirt!"); @@ -940,13 +1226,13 @@ break_armor() dropp(otmp); } } - if (has_horns(youmonst.data)) { + if (has_horns(uptr)) { if ((otmp = uarmh) != 0) { if (is_flimsy(otmp) && !donning(otmp)) { char hornbuf[BUFSZ]; /* Future possibilities: This could damage/destroy helmet */ - Sprintf(hornbuf, "horn%s", plur(num_horns(youmonst.data))); + Sprintf(hornbuf, "horn%s", plur(num_horns(uptr))); Your("%s %s through %s.", hornbuf, vtense(hornbuf, "pierce"), yname(otmp)); } else { @@ -959,7 +1245,7 @@ break_armor() } } } - if (nohands(youmonst.data) || verysmall(youmonst.data)) { + if (nohands(uptr) || verysmall(uptr)) { if ((otmp = uarmg) != 0) { if (donning(otmp)) cancel_don(); @@ -984,25 +1270,39 @@ break_armor() dropp(otmp); } } - if (nohands(youmonst.data) || verysmall(youmonst.data) - || slithy(youmonst.data) || youmonst.data->mlet == S_CENTAUR) { + if (nohands(uptr) || verysmall(uptr) + || slithy(uptr) || uptr->mlet == S_CENTAUR) { if ((otmp = uarmf) != 0) { if (donning(otmp)) cancel_don(); - if (is_whirly(youmonst.data)) + if (is_whirly(uptr)) Your("boots fall away!"); else Your("boots %s off your feet!", - verysmall(youmonst.data) ? "slide" : "are pushed"); + verysmall(uptr) ? "slide" : "are pushed"); (void) Boots_off(); dropp(otmp); } } + /* not armor, but eyewear shouldn't stay worn without a head to wear + it/them on (should also come off if head is too tiny or too huge, + but putting accessories on doesn't reject those cases [yet?]); + amulet stays worn */ + if ((otmp = ublindf) != 0 && !has_head(uptr)) { + int l; + const char *eyewear = simpleonames(otmp); /* blindfold|towel|lenses */ + + if (!strncmp(eyewear, "pair of ", l = 8)) /* lenses */ + eyewear += l; + Your("%s %s off!", eyewear, vtense(eyewear, "fall")); + (void) Blindf_off((struct obj *) 0); /* Null: skip usual off mesg */ + dropp(otmp); + } + /* rings stay worn even when no hands */ } -STATIC_OVL void -drop_weapon(alone) -int alone; +staticfn void +drop_weapon(int alone) { struct obj *otmp; const char *what, *which, *whichtoo; @@ -1013,7 +1313,7 @@ int alone; * future it might not be so if there are monsters which cannot * wear gloves but can wield weapons */ - if (!alone || cantwield(youmonst.data)) { + if (!alone || cantwield(gy.youmonst.data)) { candropwep = canletgo(uwep, ""); candropswapwep = !u.twoweap || canletgo(uswapwep, ""); if (alone) { @@ -1055,47 +1355,60 @@ int alone; if (updateinv) update_inventory(); - } else if (!could_twoweap(youmonst.data)) { + } else if (!could_twoweap(gy.youmonst.data)) { untwoweapon(); } } } +/* return to original form, usually either due to polymorph timing out + or dying from loss of hit points while being polymorphed */ void -rehumanize() +rehumanize(void) { boolean was_flying = (Flying != 0); /* You can't revert back while unchanging */ if (Unchanging) { if (u.mh < 1) { - killer.format = NO_KILLER_PREFIX; - Strcpy(killer.name, "killed while stuck in creature form"); + svk.killer.format = NO_KILLER_PREFIX; + Strcpy(svk.killer.name, "killed while stuck in creature form"); done(DIED); + /* can get to here if declining to die in explore or wizard + mode; since we're wearing an amulet of unchanging we can't + be wearing an amulet of life-saving */ + return; /* don't rehumanize after all */ } else if (uamul && uamul->otyp == AMULET_OF_UNCHANGING) { Your("%s %s!", simpleonames(uamul), otense(uamul, "fail")); - uamul->dknown = 1; + observe_object(uamul); makeknown(AMULET_OF_UNCHANGING); } } - if (emits_light(youmonst.data)) - del_light_source(LS_MONSTER, monst_to_any(&youmonst)); - polyman("return to %s form!", urace.adj); + /* + * Right now, dying while being a shifted vampire (bat, cloud, wolf) + * reverts to human rather than to vampire. + */ + + if (emits_light(gy.youmonst.data)) + del_light_source(LS_MONSTER, monst_to_any(&gy.youmonst)); + polyman("You return to %s form!", gu.urace.adj); if (u.uhp < 1) { /* can only happen if some bit of code reduces u.uhp instead of u.mh while poly'd */ Your("old form was not healthy enough to survive."); - Sprintf(killer.name, "reverting to unhealthy %s form", urace.adj); - killer.format = KILLED_BY; + Sprintf(svk.killer.name, "reverting to unhealthy %s form", + gu.urace.adj); + svk.killer.format = KILLED_BY; done(DIED); } nomul(0); - context.botl = 1; - vision_full_recalc = 1; - (void) encumber_msg(); + disp.botl = TRUE; + gv.vision_full_recalc = 1; + encumber_msg(); + update_inventory(); if (was_flying && !Flying && u.usteed) You("and %s return gently to the %s.", mon_nam(u.usteed), surface(u.ux, u.uy)); @@ -1105,44 +1418,43 @@ rehumanize() } int -dobreathe() +dobreathe(void) { struct attack *mattk; if (Strangled) { You_cant("breathe. Sorry."); - return 0; + return ECMD_OK; } if (u.uen < 15) { You("don't have enough energy to breathe!"); - return 0; + return ECMD_OK; } u.uen -= 15; - context.botl = 1; + disp.botl = TRUE; if (!getdir((char *) 0)) - return 0; + return ECMD_CANCEL; - mattk = attacktype_fordmg(youmonst.data, AT_BREA, AD_ANY); + mattk = attacktype_fordmg(gy.youmonst.data, AT_BREA, AD_ANY); if (!mattk) impossible("bad breath attack?"); /* mouthwash needed... */ else if (!u.dx && !u.dy && !u.dz) ubreatheu(mattk); else - buzz((int) (20 + mattk->adtyp - 1), (int) mattk->damn, u.ux, u.uy, - u.dx, u.dy); - return 1; + ubuzz(BZ_U_BREATH(BZ_OFS_AD(mattk->adtyp)), (int) mattk->damn); + return ECMD_TIME; } int -dospit() +dospit(void) { struct obj *otmp; struct attack *mattk; if (!getdir((char *) 0)) - return 0; - mattk = attacktype_fordmg(youmonst.data, AT_SPIT, AD_ANY); + return ECMD_CANCEL; + mattk = attacktype_fordmg(gy.youmonst.data, AT_SPIT, AD_ANY); if (!mattk) { impossible("bad spit attack?"); } else { @@ -1153,48 +1465,55 @@ dospit() break; default: impossible("bad attack type in dospit"); + FALLTHROUGH; /*FALLTHRU*/ case AD_ACID: otmp = mksobj(ACID_VENOM, TRUE, FALSE); break; } otmp->spe = 1; /* to indicate it's yours */ - throwit(otmp, 0L, FALSE); + throwit(otmp, 0L, FALSE, (struct obj *) 0); } - return 1; + return ECMD_TIME; } int -doremove() +doremove(void) { if (!Punished) { if (u.utrap && u.utraptype == TT_BURIEDBALL) { pline_The("ball and chain are buried firmly in the %s.", surface(u.ux, u.uy)); - return 0; + return ECMD_OK; } You("are not chained to anything!"); - return 0; + return ECMD_OK; } unpunish(); - return 1; + return ECMD_TIME; } int -dospinweb() +dospinweb(void) { - register struct trap *ttmp = t_at(u.ux, u.uy); - - if (Levitation || Is_airlevel(&u.uz) || Underwater - || Is_waterlevel(&u.uz)) { - You("must be on the ground to spin a web."); - return 0; + coordxy x = u.ux, y = u.uy; + struct trap *ttmp = t_at(x, y); + /* disallow webs on water, lava, air & cloud */ + boolean reject_terrain = is_pool_or_lava(x, y) || IS_AIR(levl[x][y].typ); + + /* [at the time this was written, it was not possible to be both a + webmaker and a flyer, but with the advent of amulet of flying that + became a possibility; at present hero can spin a web while flying] */ + if (Levitation || reject_terrain) { + You("must be on %s ground to spin a web.", + reject_terrain ? "solid" : "the"); + return ECMD_OK; } if (u.uswallow) { You("release web fluid inside %s.", mon_nam(u.ustuck)); if (is_animal(u.ustuck->data)) { expels(u.ustuck, u.ustuck->data, TRUE); - return 0; + return ECMD_OK; } if (is_whirly(u.ustuck->data)) { int i; @@ -1221,14 +1540,14 @@ dospinweb() } pline_The("web %sis swept away!", sweep); } - return 0; + return ECMD_OK; } /* default: a nasty jelly-like creature */ pline_The("web dissolves into %s.", mon_nam(u.ustuck)); - return 0; + return ECMD_OK; } if (u.utrap) { You("cannot spin webs while stuck in a trap."); - return 0; + return ECMD_OK; } exercise(A_DEX, TRUE); if (ttmp) { @@ -1237,35 +1556,35 @@ dospinweb() case SPIKED_PIT: You("spin a web, covering up the pit."); deltrap(ttmp); - bury_objs(u.ux, u.uy); - newsym(u.ux, u.uy); - return 1; + bury_objs(x, y); + newsym(x, y); + return ECMD_TIME; case SQKY_BOARD: pline_The("squeaky board is muffled."); deltrap(ttmp); - newsym(u.ux, u.uy); - return 1; + newsym(x, y); + return ECMD_TIME; case TELEP_TRAP: case LEVEL_TELEP: case MAGIC_PORTAL: case VIBRATING_SQUARE: Your("webbing vanishes!"); - return 0; + return ECMD_OK; case WEB: You("make the web thicker."); - return 1; + return ECMD_TIME; case HOLE: case TRAPDOOR: You("web over the %s.", (ttmp->ttyp == TRAPDOOR) ? "trap door" : "hole"); deltrap(ttmp); - newsym(u.ux, u.uy); - return 1; + newsym(x, y); + return ECMD_TIME; case ROLLING_BOULDER_TRAP: You("spin a web, jamming the trigger."); deltrap(ttmp); - newsym(u.ux, u.uy); - return 1; + newsym(x, y); + return ECMD_TIME; case ARROW_TRAP: case DART_TRAP: case BEAR_TRAP: @@ -1278,77 +1597,80 @@ dospinweb() case ANTI_MAGIC: case POLY_TRAP: You("have triggered a trap!"); - dotrap(ttmp, 0); - return 1; + dotrap(ttmp, NO_TRAP_FLAGS); + return ECMD_TIME; default: impossible("Webbing over trap type %d?", ttmp->ttyp); - return 0; + return ECMD_OK; } - } else if (On_stairs(u.ux, u.uy)) { + } else if (On_stairs(x, y)) { /* cop out: don't let them hide the stairs */ Your("web fails to impede access to the %s.", - (levl[u.ux][u.uy].typ == STAIRS) ? "stairs" : "ladder"); - return 1; + (levl[x][y].typ == STAIRS) ? "stairs" : "ladder"); + return ECMD_TIME; } - ttmp = maketrap(u.ux, u.uy, WEB); + ttmp = maketrap(x, y, WEB); if (ttmp) { + You("spin a web."); ttmp->madeby_u = 1; feeltrap(ttmp); + if (*in_rooms(x, y, SHOPBASE)) + add_damage(x, y, SHOP_WEB_COST); } - return 1; + return ECMD_TIME; } int -dosummon() +dosummon(void) { int placeholder; if (u.uen < 10) { You("lack the energy to send forth a call for help!"); - return 0; + return ECMD_OK; } u.uen -= 10; - context.botl = 1; + disp.botl = TRUE; You("call upon your brethren for help!"); exercise(A_WIS, TRUE); - if (!were_summon(youmonst.data, TRUE, &placeholder, (char *) 0)) + if (!were_summon(gy.youmonst.data, TRUE, &placeholder, (char *) 0)) pline("But none arrive."); - return 1; + return ECMD_TIME; } int -dogaze() +dogaze(void) { - register struct monst *mtmp; + struct monst *mtmp; int looked = 0; char qbuf[QBUFSZ]; int i; uchar adtyp = 0; for (i = 0; i < NATTK; i++) { - if (youmonst.data->mattk[i].aatyp == AT_GAZE) { - adtyp = youmonst.data->mattk[i].adtyp; + if (gy.youmonst.data->mattk[i].aatyp == AT_GAZE) { + adtyp = gy.youmonst.data->mattk[i].adtyp; break; } } if (adtyp != AD_CONF && adtyp != AD_FIRE) { impossible("gaze attack %d?", adtyp); - return 0; + return ECMD_OK; } if (Blind) { You_cant("see anything to gaze at."); - return 0; + return ECMD_OK; } else if (Hallucination) { You_cant("gaze at anything you can see."); - return 0; + return ECMD_OK; } if (u.uen < 15) { You("lack the energy to use your special gaze!"); - return 0; + return ECMD_OK; } u.uen -= 15; - context.botl = 1; + disp.botl = TRUE; for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { if (DEADMONSTER(mtmp)) @@ -1370,11 +1692,11 @@ dogaze() Sprintf(qbuf, "Really %s %s?", (adtyp == AD_CONF) ? "confuse" : "attack", mon_nam(mtmp)); - if (yn(qbuf) != 'y') + if (y_n(qbuf) != 'y') continue; } setmangry(mtmp, TRUE); - if (!mtmp->mcanmove || mtmp->mstun || mtmp->msleeping + if (helpless(mtmp) || mtmp->mstun || !mtmp->mcansee || !haseyes(mtmp->data)) { looked--; continue; @@ -1390,19 +1712,17 @@ dogaze() Monnam(mtmp)); mtmp->mconf = 1; } else if (adtyp == AD_FIRE) { - int dmg = d(2, 6), lev = (int) u.ulevel; + int dmg = d(2, 6), orig_dmg = dmg, lev = (int) u.ulevel; You("attack %s with a fiery gaze!", mon_nam(mtmp)); if (resists_fire(mtmp)) { pline_The("fire doesn't burn %s!", mon_nam(mtmp)); dmg = 0; } - if (lev > rn2(20)) - (void) destroy_mitem(mtmp, SCROLL_CLASS, AD_FIRE); - if (lev > rn2(20)) - (void) destroy_mitem(mtmp, POTION_CLASS, AD_FIRE); - if (lev > rn2(25)) - (void) destroy_mitem(mtmp, SPBOOK_CLASS, AD_FIRE); + if (lev > rn2(20)) { + dmg += destroy_items(mtmp, AD_FIRE, orig_dmg); + ignite_items(mtmp->minvent); + } if (dmg) mtmp->mhp -= dmg; if (DEADMONSTER(mtmp)) @@ -1422,9 +1742,9 @@ dogaze() ? -d((int) mtmp->m_lev + 1, (int) mtmp->data->mattk[0].damd) : -200); - multi_reason = "frozen by a monster's gaze"; - nomovemsg = 0; - return 1; + gm.multi_reason = "frozen by a monster's gaze"; + gn.nomovemsg = 0; + return ECMD_TIME; } else You("stiffen momentarily under %s gaze.", s_suffix(mon_nam(mtmp))); @@ -1438,9 +1758,10 @@ dogaze() pline("Gazing at the awake %s is not a very good idea.", l_monnam(mtmp)); /* as if gazing at a sleeping anything is fruitful... */ - You("turn to stone..."); - killer.format = KILLED_BY; - Strcpy(killer.name, "deliberately meeting Medusa's gaze"); + urgent_pline("You turn to stone..."); + svk.killer.format = KILLED_BY; + Strcpy(svk.killer.name, + "deliberately meeting Medusa's gaze"); done(STONING); } } @@ -1448,59 +1769,89 @@ dogaze() } if (!looked) You("gaze at no place in particular."); - return 1; + return ECMD_TIME; } +/* called by domonability() for #monster */ int -dohide() +dohide(void) { - boolean ismimic = youmonst.data->mlet == S_MIMIC, - on_ceiling = is_clinger(youmonst.data) || Flying; + boolean ismimic = gy.youmonst.data->mlet == S_MIMIC, + on_ceiling = is_clinger(gy.youmonst.data) || Flying; /* can't hide while being held (or holding) or while trapped (except for floor hiders [trapper or mimic] in pits) */ if (u.ustuck || (u.utrap && (u.utraptype != TT_PIT || on_ceiling))) { You_cant("hide while you're %s.", !u.ustuck ? "trapped" - : u.uswallow ? (is_animal(u.ustuck->data) ? "swallowed" - : "engulfed") - : !sticks(youmonst.data) ? "being held" + : u.uswallow ? (digests(u.ustuck->data) ? "swallowed" + : "engulfed") + : !sticks(gy.youmonst.data) ? "being held" : (humanoid(u.ustuck->data) ? "holding someone" : "holding that creature")); - if (u.uundetected - || (ismimic && U_AP_TYPE != M_AP_NOTHING)) { + if (u.uundetected || (ismimic && U_AP_TYPE != M_AP_NOTHING)) { u.uundetected = 0; - youmonst.m_ap_type = M_AP_NOTHING; + gy.youmonst.m_ap_type = M_AP_NOTHING; newsym(u.ux, u.uy); } - return 0; + return ECMD_OK; } - /* note: the eel and hides_under cases are hypothetical; + /* note: hero-as-eel handling is incomplete but unnecessary; such critters aren't offered the option of hiding via #monster */ - if (youmonst.data->mlet == S_EEL && !is_pool(u.ux, u.uy)) { + if (gy.youmonst.data->mlet == S_EEL && !is_pool(u.ux, u.uy)) { if (IS_FOUNTAIN(levl[u.ux][u.uy].typ)) - The("fountain is not deep enough to hide in."); + pline_The("fountain is not deep enough to hide in."); else There("is no %s to hide in here.", hliquid("water")); u.uundetected = 0; - return 0; + return ECMD_OK; } - if (hides_under(youmonst.data) && !level.objects[u.ux][u.uy]) { - There("is nothing to hide under here."); - u.uundetected = 0; - return 0; + if (hides_under(gy.youmonst.data)) { + long ct = 0L; + struct obj *otmp, *otop = svl.level.objects[u.ux][u.uy]; + + if (!otop) { + There("is nothing to hide under here."); + u.uundetected = 0; + return ECMD_OK; + } + for (otmp = otop; + otmp && otmp->otyp == CORPSE + && touch_petrifies(&mons[otmp->corpsenm]); + otmp = otmp->nexthere) + ct += otmp->quan; + /* otmp will be Null iff the entire pile consists of 'trice corpses */ + if (!otmp && !Stone_resistance) { + char kbuf[BUFSZ]; + const char *corpse_name = cxname(otop); + + /* for the plural case, we'll say "cockatrice corpses" or + "chickatrice corpses" depending on the top of the pile + even if both types are present */ + if (ct == 1) + corpse_name = an(corpse_name); + /* no need to check poly_when_stoned(); no hide-underers can + turn into stone golems instead of becoming petrified */ + pline("Hiding under %s%s is a fatal mistake...", + corpse_name, plur(ct)); + Sprintf(kbuf, "hiding under %s%s", corpse_name, plur(ct)); + instapetrify(kbuf); + /* only reach here if life-saved */ + u.uundetected = 0; + return ECMD_TIME; + } } /* Planes of Air and Water */ if (on_ceiling && !has_ceiling(&u.uz)) { There("is nowhere to hide above you."); u.uundetected = 0; - return 0; + return ECMD_OK; } - if ((is_hider(youmonst.data) && !Flying) /* floor hider */ + if ((is_hider(gy.youmonst.data) && !Flying) /* floor hider */ && (Is_airlevel(&u.uz) || Is_waterlevel(&u.uz))) { There("is nowhere to hide beneath you."); u.uundetected = 0; - return 0; + return ECMD_OK; } /* TODO? inhibit floor hiding at furniture locations, or * else make youhiding() give smarter messages at such spots. @@ -1508,46 +1859,49 @@ dohide() if (u.uundetected || (ismimic && U_AP_TYPE != M_AP_NOTHING)) { youhiding(FALSE, 1); /* "you are already hiding" */ - return 0; + return ECMD_OK; } if (ismimic) { /* should bring up a dialog "what would you like to imitate?" */ - youmonst.m_ap_type = M_AP_OBJECT; - youmonst.mappearance = STRANGE_OBJECT; + gy.youmonst.m_ap_type = M_AP_OBJECT; + gy.youmonst.mappearance = STRANGE_OBJECT; } else u.uundetected = 1; newsym(u.ux, u.uy); youhiding(FALSE, 0); /* "you are now hiding" */ - return 1; + return ECMD_TIME; } int -dopoly() +dopoly(void) { - struct permonst *savedat = youmonst.data; + struct permonst *savedat = gy.youmonst.data; - if (is_vampire(youmonst.data)) { - polyself(2); - if (savedat != youmonst.data) { - You("transform into %s.", an(youmonst.data->mname)); + if (is_vampire(gy.youmonst.data) || is_vampshifter(&gy.youmonst)) { + polyself(POLY_MONSTER); + if (savedat != gy.youmonst.data) { + You("transform into %s.", + an(pmname(gy.youmonst.data, Ugender))); newsym(u.ux, u.uy); } } - return 1; + return ECMD_TIME; } +/* #monster for hero-as-mind_flayer giving psychic blast */ int -domindblast() +domindblast(void) { struct monst *mtmp, *nmon; + int dmg; if (u.uen < 10) { You("concentrate but lack the energy to maintain doing so."); - return 0; + return ECMD_OK; } u.uen -= 10; - context.botl = 1; + disp.botl = TRUE; You("concentrate."); pline("A wave of psychic energy pours out."); @@ -1557,52 +1911,65 @@ domindblast() nmon = mtmp->nmon; if (DEADMONSTER(mtmp)) continue; - if (distu(mtmp->mx, mtmp->my) > BOLT_LIM * BOLT_LIM) + if (mdistu(mtmp) > BOLT_LIM * BOLT_LIM) continue; if (mtmp->mpeaceful) continue; + if (mindless(mtmp->data)) + continue; u_sen = telepathic(mtmp->data) && !mtmp->mcansee; if (u_sen || (telepathic(mtmp->data) && rn2(2)) || !rn2(10)) { + dmg = rnd(15); + /* wake it up first, to bring hidden monster out of hiding; + but in case it is currently peaceful, don't make it hostile + unless it will survive the psychic blast, otherwise hero + would avoid the penalty for killing it while peaceful */ + wakeup(mtmp, (dmg > mtmp->mhp) ? TRUE : FALSE); You("lock in on %s %s.", s_suffix(mon_nam(mtmp)), u_sen ? "telepathy" - : telepathic(mtmp->data) ? "latent telepathy" : "mind"); - mtmp->mhp -= rnd(15); + : telepathic(mtmp->data) ? "latent telepathy" + : "mind"); + mtmp->mhp -= dmg; if (DEADMONSTER(mtmp)) killed(mtmp); } } - return 1; + return ECMD_TIME; } void -uunstick() +uunstick(void) { - if (!u.ustuck) { + struct monst *mtmp = u.ustuck; + + if (!mtmp) { impossible("uunstick: no ustuck?"); return; } - pline("%s is no longer in your clutches.", Monnam(u.ustuck)); - u.ustuck = 0; + set_ustuck((struct monst *) 0); /* before pline() */ + pline("%s is no longer in your clutches.", Monnam(mtmp)); } void -skinback(silently) -boolean silently; +skinback(boolean silently) { if (uskin) { + int old_light = arti_light_radius(uskin); + if (!silently) Your("skin returns to its original form."); uarm = uskin; uskin = (struct obj *) 0; /* undo save/restore hack */ uarm->owornmask &= ~I_SPECIAL; + + if (artifact_light(uarm)) + maybe_adjust_light(uarm, old_light); } } const char * -mbodypart(mon, part) -struct monst *mon; -int part; +mbodypart(struct monst *mon, int part) { static NEARDATA const char *humanoid_parts[] = { "arm", "eye", "face", "finger", @@ -1668,6 +2035,11 @@ int part; "posterior", "over stretched", "clitellum", "length", "posterior setae", "setae", "blood", "skin", "prostomium", "stomach" }, + *spider_parts[] = { "pedipalp", "eye", "face", "pedipalp", "tarsus", + "claw", "pedipalp", "palped", "cephalothorax", + "leg", "spun out", "cephalothorax", "abdomen", + "claw", "hair", "hemolymph", "book lung", + "labrum", "digestive tract" }, *fish_parts[] = { "fin", "eye", "premaxillary", "pelvic axillary", "pelvic fin", "anal fin", "pectoral fin", "finned", "head", "peduncle", "played out", "gills", @@ -1682,6 +2054,11 @@ int part; }; struct permonst *mptr = mon->data; + if (part <= NO_PART) { + impossible("mbodypart: bad part %d", part); + return "mystery part"; + } + /* some special cases */ if (mptr->mlet == S_DOG || mptr->mlet == S_FELINE || mptr->mlet == S_RODENT || mptr == &mons[PM_OWLBEAR]) { @@ -1704,8 +2081,8 @@ int part; } if ((part == HAND || part == HANDED) && (humanoid(mptr) && attacktype(mptr, AT_CLAW) - && !index(not_claws, mptr->mlet) && mptr != &mons[PM_STONE_GOLEM] - && mptr != &mons[PM_INCUBUS] && mptr != &mons[PM_SUCCUBUS])) + && !strchr(not_claws, mptr->mlet) && mptr != &mons[PM_STONE_GOLEM] + && mptr != &mons[PM_AMOROUS_DEMON])) return (part == HAND) ? "claw" : "clawed"; if ((mptr == &mons[PM_MUMAK] || mptr == &mons[PM_MASTODON]) && part == NOSE) @@ -1721,9 +2098,12 @@ int part; if (humanoid(mptr) && (part == ARM || part == FINGER || part == FINGERTIP || part == HAND || part == HANDED)) return humanoid_parts[part]; + if (mptr->mlet == S_COCKATRICE) + return (part == HAIR) ? snake_parts[part] : bird_parts[part]; if (mptr == &mons[PM_RAVEN]) return bird_parts[part]; if (mptr->mlet == S_CENTAUR || mptr->mlet == S_UNICORN + || mptr == &mons[PM_KI_RIN] || (mptr == &mons[PM_ROTHE] && part != HAIR)) return horse_parts[part]; if (mptr->mlet == S_LIGHT) { @@ -1741,6 +2121,8 @@ int part; return fish_parts[part]; if (mptr->mlet == S_WORM) return worm_parts[part]; + if (mptr->mlet == S_SPIDER) + return spider_parts[part]; if (slithy(mptr) || (mptr->mlet == S_DRAGON && part == HAIR)) return snake_parts[part]; if (mptr->mlet == S_EYE) @@ -1758,26 +2140,24 @@ int part; } const char * -body_part(part) -int part; +body_part(int part) { - return mbodypart(&youmonst, part); + return mbodypart(&gy.youmonst, part); } int -poly_gender() +poly_gender(void) { /* Returns gender of polymorphed player; * 0/1=same meaning as flags.female, 2=none. */ - if (is_neuter(youmonst.data) || !humanoid(youmonst.data)) + if (is_neuter(gy.youmonst.data) || !humanoid(gy.youmonst.data)) return 2; return flags.female; } void -ugolemeffects(damtype, dam) -int damtype, dam; +ugolemeffects(int damtype, int dam) { int heal = 0; @@ -1801,15 +2181,14 @@ int damtype, dam; u.mh += heal; if (u.mh > u.mhmax) u.mh = u.mhmax; - context.botl = 1; + disp.botl = TRUE; pline("Strangely, you feel better than before."); exercise(A_STR, TRUE); } } -STATIC_OVL int -armor_to_dragon(atyp) -int atyp; +staticfn int +armor_to_dragon(int atyp) { switch (atyp) { case GRAY_DRAGON_SCALE_MAIL: @@ -1818,6 +2197,9 @@ int atyp; case SILVER_DRAGON_SCALE_MAIL: case SILVER_DRAGON_SCALES: return PM_SILVER_DRAGON; + case GOLD_DRAGON_SCALE_MAIL: + case GOLD_DRAGON_SCALES: + return PM_GOLD_DRAGON; #if 0 /* DEFERRED */ case SHIMMERING_DRAGON_SCALE_MAIL: case SHIMMERING_DRAGON_SCALES: @@ -1845,61 +2227,58 @@ int atyp; case YELLOW_DRAGON_SCALES: return PM_YELLOW_DRAGON; default: - return -1; + return NON_PM; } } /* some species have awareness of other species */ -static void -polysense() +staticfn void +polysense(void) { short warnidx = NON_PM; - context.warntype.speciesidx = NON_PM; - context.warntype.species = 0; - context.warntype.polyd = 0; + svc.context.warntype.speciesidx = NON_PM; + svc.context.warntype.species = 0; + svc.context.warntype.polyd = 0; HWarn_of_mon &= ~FROMRACE; switch (u.umonnum) { case PM_PURPLE_WORM: + case PM_BABY_PURPLE_WORM: warnidx = PM_SHRIEKER; break; case PM_VAMPIRE: - case PM_VAMPIRE_LORD: - context.warntype.polyd = M2_HUMAN | M2_ELF; + case PM_VAMPIRE_LEADER: + svc.context.warntype.polyd = M2_HUMAN | M2_ELF; HWarn_of_mon |= FROMRACE; return; } - if (warnidx >= LOW_PM) { - context.warntype.speciesidx = warnidx; - context.warntype.species = &mons[warnidx]; + if (ismnum(warnidx)) { + svc.context.warntype.speciesidx = warnidx; + svc.context.warntype.species = &mons[warnidx]; HWarn_of_mon |= FROMRACE; } } /* True iff hero's role or race has been genocided */ boolean -ugenocided() +ugenocided(void) { - return (boolean) ((mvitals[urole.malenum].mvflags & G_GENOD) - || (urole.femalenum != NON_PM - && (mvitals[urole.femalenum].mvflags & G_GENOD)) - || (mvitals[urace.malenum].mvflags & G_GENOD) - || (urace.femalenum != NON_PM - && (mvitals[urace.femalenum].mvflags & G_GENOD))); + return ((svm.mvitals[gu.urole.mnum].mvflags & G_GENOD) + || (svm.mvitals[gu.urace.mnum].mvflags & G_GENOD)); } /* how hero feels "inside" after self-genocide of role or race */ const char * -udeadinside() +udeadinside(void) { /* self-genocide used to always say "you feel dead inside" but that seems silly when you're polymorphed into something undead; monkilled() distinguishes between living (killed) and non (destroyed) for monster death message; we refine the nonliving aspect a bit */ - return !nonliving(youmonst.data) + return !nonliving(gy.youmonst.data) ? "dead" /* living, including demons */ - : !weirdnonliving(youmonst.data) + : !weirdnonliving(gy.youmonst.data) ? "condemned" /* undead plus manes */ : "empty"; /* golems plus vortices */ } diff --git a/src/potion.c b/src/potion.c index c6dffc5f9..cda436427 100644 --- a/src/potion.c +++ b/src/potion.c @@ -1,48 +1,78 @@ -/* NetHack 3.6 potion.c $NHDT-Date: 1573848199 2019/11/15 20:03:19 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.167 $ */ +/* NetHack 5.0 potion.c $NHDT-Date: 1770949988 2026/02/12 18:33:08 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.279 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2013. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -boolean notonhead = FALSE; - -static NEARDATA int nothing, unkn; -static NEARDATA const char beverages[] = { POTION_CLASS, 0 }; - -STATIC_DCL long FDECL(itimeout, (long)); -STATIC_DCL long FDECL(itimeout_incr, (long, int)); -STATIC_DCL void NDECL(ghost_from_bottle); -STATIC_DCL boolean -FDECL(H2Opotion_dip, (struct obj *, struct obj *, BOOLEAN_P, const char *)); -STATIC_DCL short FDECL(mixtype, (struct obj *, struct obj *)); +staticfn long itimeout(long); +staticfn long itimeout_incr(long, int); +staticfn void ghost_from_bottle(void); +staticfn int drink_ok(struct obj *); +staticfn void peffect_restore_ability(struct obj *); +staticfn void peffect_hallucination(struct obj *); +staticfn void peffect_water(struct obj *); +staticfn void peffect_booze(struct obj *); +staticfn void peffect_enlightenment(struct obj *); +staticfn void peffect_invisibility(struct obj *); +staticfn void peffect_see_invisible(struct obj *); +staticfn void peffect_paralysis(struct obj *); +staticfn void peffect_sleeping(struct obj *); +staticfn int peffect_monster_detection(struct obj *); +staticfn int peffect_object_detection(struct obj *); +staticfn void peffect_sickness(struct obj *); +staticfn void peffect_confusion(struct obj *); +staticfn void peffect_gain_ability(struct obj *); +staticfn void peffect_speed(struct obj *); +staticfn void peffect_blindness(struct obj *); +staticfn void peffect_gain_level(struct obj *); +staticfn void peffect_healing(struct obj *); +staticfn void peffect_extra_healing(struct obj *); +staticfn void peffect_full_healing(struct obj *); +staticfn void peffect_levitation(struct obj *); +staticfn void peffect_gain_energy(struct obj *); +staticfn void peffect_oil(struct obj *); +staticfn void peffect_acid(struct obj *); +staticfn void peffect_polymorph(struct obj *); +staticfn boolean H2Opotion_dip(struct obj *, struct obj *, boolean, + const char *); +staticfn short mixtype(struct obj *, struct obj *); +staticfn int dip_ok(struct obj *); +staticfn int dip_hands_ok(struct obj *); +staticfn void hold_potion(struct obj *, const char *, const char *, + const char *); +staticfn void poof(struct obj *); +staticfn boolean dip_potion_explosion(struct obj *, int); +staticfn int potion_dip(struct obj *obj, struct obj *potion); + +/* used to indicate whether quaff or dip has skipped an opportunity to + use a fountain or such, in order to vary the feedback if hero lacks + any potions [reinitialized every time it's used so does not need to + be placed in struct instance_globals gd] */ +static int drink_ok_extra = 0; /* force `val' to be within valid range for intrinsic timeout value */ -STATIC_OVL long -itimeout(val) -long val; +staticfn long +itimeout(long val) { if (val >= TIMEOUT) val = TIMEOUT; - else if (val < 1) - val = 0; + else if (val < 1L) + val = 0L; return val; } /* increment `old' by `incr' and force result to be valid intrinsic timeout */ -STATIC_OVL long -itimeout_incr(old, incr) -long old; -int incr; +staticfn long +itimeout_incr(long old, int incr) { return itimeout((old & TIMEOUT) + (long) incr); } /* set the timeout field of intrinsic `which' */ void -set_itimeout(which, val) -long *which, val; +set_itimeout(long *which, long val) { *which &= ~TIMEOUT; *which |= itimeout(val); @@ -50,17 +80,13 @@ long *which, val; /* increment the timeout field of intrinsic `which' */ void -incr_itimeout(which, incr) -long *which; -int incr; +incr_itimeout(long *which, int incr) { set_itimeout(which, itimeout_incr(*which, incr)); } void -make_confused(xtime, talk) -long xtime; -boolean talk; +make_confused(long xtime, boolean talk) { long old = HConfusion; @@ -72,15 +98,13 @@ boolean talk; You_feel("less %s now.", Hallucination ? "trippy" : "confused"); } if ((xtime && !old) || (!xtime && old)) - context.botl = TRUE; + disp.botl = TRUE; set_itimeout(&HConfusion, xtime); } void -make_stunned(xtime, talk) -long xtime; -boolean talk; +make_stunned(long xtime, boolean talk) { long old = HStun; @@ -97,11 +121,11 @@ boolean talk; if (u.usteed) You("wobble in the saddle."); else - You("%s...", stagger(youmonst.data, "stagger")); + You("%s...", stagger(gy.youmonst.data, "stagger")); } } if ((!xtime && old) || (xtime && !old)) - context.botl = TRUE; + disp.botl = TRUE; set_itimeout(&HStun, xtime); } @@ -110,11 +134,10 @@ boolean talk; u.usick_type bit mask), but delayed killer can only support one or the other at a time. They should become separate intrinsics.... */ void -make_sick(xtime, cause, talk, type) -long xtime; -const char *cause; /* sickness cause */ -boolean talk; -int type; +make_sick(long xtime, + const char *cause, /* sickness cause */ + boolean talk, + int type) { struct kinfo *kptr; long old = Sick; @@ -136,7 +159,7 @@ int type; } set_itimeout(&Sick, xtime); u.usick_type |= type; - context.botl = TRUE; + disp.botl = TRUE; } else if (old && (type & u.usick_type)) { /* was sick, now not */ u.usick_type &= ~type; @@ -149,7 +172,7 @@ int type; You_feel("cured. What a relief!"); Sick = 0L; /* set_itimeout(&Sick, 0L) */ } - context.botl = TRUE; + disp.botl = TRUE; } kptr = find_delayed_killer(SICK); @@ -169,9 +192,7 @@ int type; } void -make_slimed(xtime, msg) -long xtime; -const char *msg; +make_slimed(long xtime, const char *msg) { long old = Slimed; @@ -181,7 +202,7 @@ const char *msg; #endif set_itimeout(&Slimed, xtime); if ((xtime != 0L) ^ (old != 0L)) { - context.botl = TRUE; + disp.botl = TRUE; if (msg) pline("%s", msg); } @@ -189,20 +210,16 @@ const char *msg; dealloc_killer(find_delayed_killer(SLIMED)); /* fake appearance is set late in turn-to-slime countdown */ if (U_AP_TYPE == M_AP_MONSTER - && youmonst.mappearance == PM_GREEN_SLIME) { - youmonst.m_ap_type = M_AP_NOTHING; - youmonst.mappearance = 0; + && gy.youmonst.mappearance == PM_GREEN_SLIME) { + gy.youmonst.m_ap_type = M_AP_NOTHING; + gy.youmonst.mappearance = 0; } } } /* start or stop petrification */ void -make_stoned(xtime, msg, killedby, killername) -long xtime; -const char *msg; -int killedby; -const char *killername; +make_stoned(long xtime, const char *msg, int killedby, const char *killername) { long old = Stoned; @@ -212,7 +229,7 @@ const char *killername; #endif set_itimeout(&Stoned, xtime); if ((xtime != 0L) ^ (old != 0L)) { - context.botl = TRUE; + disp.botl = TRUE; if (msg) pline("%s", msg); } @@ -223,9 +240,7 @@ const char *killername; } void -make_vomiting(xtime, talk) -long xtime; -boolean talk; +make_vomiting(long xtime, boolean talk) { long old = Vomiting; @@ -233,7 +248,7 @@ boolean talk; talk = FALSE; set_itimeout(&Vomiting, xtime); - context.botl = TRUE; + disp.botl = TRUE; if (!xtime && old) if (talk) You_feel("much less nauseated now."); @@ -243,20 +258,18 @@ static const char vismsg[] = "vision seems to %s for a moment but is %s now."; static const char eyemsg[] = "%s momentarily %s."; void -make_blinded(xtime, talk) -long xtime; -boolean talk; +make_blinded(long xtime, boolean talk) { - long old = Blinded; + long old = BlindedTimeout; boolean u_could_see, can_see_now; const char *eyes; - /* we need to probe ahead in case the Eyes of the Overworld + /* we probe ahead in case the Eyes of the Overworld are or will be overriding blindness */ u_could_see = !Blind; - Blinded = xtime ? 1L : 0L; + set_itimeout(&HBlinded, xtime ? 1L : 0L); can_see_now = !Blind; - Blinded = old; /* restore */ + set_itimeout(&HBlinded, old); if (Unaware) talk = FALSE; @@ -271,11 +284,11 @@ boolean talk; } else if (old && !xtime) { /* clearing temporary blindness without toggling blindness */ if (talk) { - if (!haseyes(youmonst.data)) { + if (!haseyes(gy.youmonst.data) || PermaBlind) { strange_feeling((struct obj *) 0, (char *) 0); } else if (Blindfolded) { eyes = body_part(EYE); - if (eyecount(youmonst.data) != 1) + if (eyecount(gy.youmonst.data) != 1) eyes = makeplural(eyes); Your(eyemsg, eyes, vtense(eyes, "itch")); } else { /* Eyes of the Overworld */ @@ -297,11 +310,11 @@ boolean talk; } else if (!old && xtime) { /* setting temporary blindness without toggling blindness */ if (talk) { - if (!haseyes(youmonst.data)) { + if (!haseyes(gy.youmonst.data) || PermaBlind) { strange_feeling((struct obj *) 0, (char *) 0); } else if (Blindfolded) { eyes = body_part(EYE); - if (eyecount(youmonst.data) != 1) + if (eyecount(gy.youmonst.data) != 1) eyes = makeplural(eyes); Your(eyemsg, eyes, vtense(eyes, "twitch")); } else { /* Eyes of the Overworld */ @@ -310,7 +323,7 @@ boolean talk; } } - set_itimeout(&Blinded, xtime); + set_itimeout(&HBlinded, xtime); if (u_could_see ^ can_see_now) { /* one or the other but not both */ toggle_blindness(); @@ -320,13 +333,13 @@ boolean talk; /* blindness has just started or just ended--caller enforces that; called by Blindf_on(), Blindf_off(), and make_blinded() */ void -toggle_blindness() +toggle_blindness(void) { boolean Stinging = (uwep && (EWarn_of_mon & W_WEP) != 0L); /* blindness has just been toggled */ - context.botl = TRUE; /* status conditions need update */ - vision_full_recalc = 1; /* vision has changed */ + disp.botl = TRUE; /* status conditions need update */ + gv.vision_full_recalc = 1; /* vision has changed */ /* this vision recalculation used to be deferred until moveloop(), but that made it possible for vision irregularities to occur (cited case was force bolt hitting an adjacent potion of blindness @@ -350,11 +363,13 @@ toggle_blindness() learn_unseen_invent(); } +DISABLE_WARNING_FORMAT_NONLITERAL + boolean -make_hallucinated(xtime, talk, mask) -long xtime; /* nonzero if this is an attempt to turn on hallucination */ -boolean talk; -long mask; /* nonzero if resistance status should change by mask */ +make_hallucinated( + long xtime, /* nonzero if this is an attempt to turn on hallucination */ + boolean talk, + long mask) /* nonzero if resistance status should change by mask */ { long old = HHallucination; boolean changed = 0; @@ -382,12 +397,12 @@ long mask; /* nonzero if resistance status should change by mask */ /* clearing temporary hallucination without toggling vision */ if (!changed && !HHallucination && old && talk) { - if (!haseyes(youmonst.data)) { + if (!haseyes(gy.youmonst.data)) { strange_feeling((struct obj *) 0, (char *) 0); } else if (Blind) { const char *eyes = body_part(EYE); - if (eyecount(youmonst.data) != 1) + if (eyecount(gy.youmonst.data) != 1) eyes = makeplural(eyes); Your(eyemsg, eyes, vtense(eyes, "itch")); } else { /* Grayswandir */ @@ -415,17 +430,17 @@ long mask; /* nonzero if resistance status should change by mask */ (eg. Qt windowport's equipped items display) */ update_inventory(); - context.botl = TRUE; + disp.botl = TRUE; if (talk) pline(message, verb); } return changed; } +RESTORE_WARNING_FORMAT_NONLITERAL + void -make_deaf(xtime, talk) -long xtime; -boolean talk; +make_deaf(long xtime, boolean talk) { long old = HDeaf; @@ -434,17 +449,18 @@ boolean talk; set_itimeout(&HDeaf, xtime); if ((xtime != 0L) ^ (old != 0L)) { - context.botl = TRUE; + disp.botl = TRUE; if (talk) - You(old ? "can hear again." : "are unable to hear anything."); + You(old && !Deaf ? "can hear again." + : "are unable to hear anything."); } } /* set or clear "slippery fingers" */ void -make_glib(xtime) -int xtime; +make_glib(int xtime) { + disp.botl |= (!Glib ^ !!xtime); set_itimeout(&Glib, xtime); /* may change "(being worn)" to "(being worn; slippery)" or vice versa */ if (uarmg) @@ -452,7 +468,7 @@ int xtime; } void -self_invis_message() +self_invis_message(void) { pline("%s %s.", Hallucination ? "Far out, man! You" @@ -461,10 +477,10 @@ self_invis_message() : "can't see yourself"); } -STATIC_OVL void -ghost_from_bottle() +staticfn void +ghost_from_bottle(void) { - struct monst *mtmp = makemon(&mons[PM_GHOST], u.ux, u.uy, NO_MM_FLAGS); + struct monst *mtmp = makemon(&mons[PM_GHOST], u.ux, u.uy, MM_NOMSG); if (!mtmp) { pline("This bottle turns out to be empty."); @@ -479,677 +495,927 @@ ghost_from_bottle() if (flags.verbose) You("are frightened to death, and unable to move."); nomul(-3); - multi_reason = "being frightened to death"; - nomovemsg = "You regain your composure."; + gm.multi_reason = "being frightened to death"; + gn.nomovemsg = "You regain your composure."; +} + +/* getobj callback for object to drink from, which also does double duty as + the callback for dipping into (both just allow potions). */ +staticfn int +drink_ok(struct obj *obj) +{ + /* getobj()'s callback to test whether hands/self is a valid "item" to + pick is used here to communicate the fact that player has already + passed up an opportunity to perform the action (drink or dip) on a + non-inventory dungeon feature, so if there are no potions in invent + the message will be "you have nothing /else/ to {drink | dip into}"; + if player used 'm' prefix to bypass dungeon features, drink_ok_extra + will be 0 and the potential "else" will be omitted */ + if (!obj) + return drink_ok_extra ? GETOBJ_EXCLUDE_NONINVENT : GETOBJ_EXCLUDE; + + if (obj->oclass == POTION_CLASS) + return GETOBJ_SUGGEST; + + return GETOBJ_EXCLUDE; } /* "Quaffing is like drinking, except you spill more." - Terry Pratchett */ +/* the #quaff command */ int -dodrink() +dodrink(void) { - register struct obj *otmp; - const char *potion_descr; + struct obj *otmp; if (Strangled) { pline("If you can't breathe air, how can you drink liquid?"); - return 0; - } - /* Is there a fountain to drink from here? */ - if (IS_FOUNTAIN(levl[u.ux][u.uy].typ) - /* not as low as floor level but similar restrictions apply */ - && can_reach_floor(FALSE)) { - if (yn("Drink from the fountain?") == 'y') { - drinkfountain(); - return 1; - } + return ECMD_OK; } - /* Or a kitchen sink? */ - if (IS_SINK(levl[u.ux][u.uy].typ) - /* not as low as floor level but similar restrictions apply */ - && can_reach_floor(FALSE)) { - if (yn("Drink from the sink?") == 'y') { - drinksink(); - return 1; + + drink_ok_extra = 0; + /* preceding 'q'/#quaff with 'm' skips the possibility of drinking + from fountains, sinks, and surrounding water plus the prompting + which those entail; optional for interactive use, essential for + context-sensitive inventory item action 'quaff' */ + if (!iflags.menu_requested) { + /* Is there a fountain to drink from here? */ + if (IS_FOUNTAIN(levl[u.ux][u.uy].typ) + /* not as low as floor level but similar restrictions apply */ + && can_reach_floor(FALSE)) { + if (y_n("Drink from the fountain?") == 'y') { + drinkfountain(); + return ECMD_TIME; + } + ++drink_ok_extra; + } + /* Or a kitchen sink? */ + if (IS_SINK(levl[u.ux][u.uy].typ) + /* not as low as floor level but similar restrictions apply */ + && can_reach_floor(FALSE)) { + if (y_n("Drink from the sink?") == 'y') { + drinksink(); + return ECMD_TIME; + } + ++drink_ok_extra; } - } - /* Or are you surrounded by water? */ - if (Underwater && !u.uswallow) { - if (yn("Drink the water around you?") == 'y') { - pline("Do you know what lives in this water?"); - return 1; + /* Or are you surrounded by water? */ + if (Underwater && !u.uswallow) { + if (y_n("Drink the water around you?") == 'y') { + pline("Do you know what lives in this water?"); + return ECMD_TIME; + } + ++drink_ok_extra; } } - otmp = getobj(beverages, "drink"); + otmp = getobj("drink", drink_ok, GETOBJ_NOFLAGS); if (!otmp) - return 0; + return ECMD_CANCEL; - /* quan > 1 used to be left to useup(), but we need to force - the current potion to be unworn, and don't want to do - that for the entire stack when starting with more than 1. - [Drinking a wielded potion of polymorph can trigger a shape - change which causes hero's weapon to be dropped. In 3.4.x, - that led to an "object lost" panic since subsequent useup() - was no longer dealing with an inventory item. Unwearing - the current potion is intended to keep it in inventory.] */ - if (otmp->quan > 1L) { - otmp = splitobj(otmp, 1L); - otmp->owornmask = 0L; /* rest of original stuck unaffected */ - } else if (otmp->owornmask) { - remove_worn_item(otmp, FALSE); + /* + * 3.6: quan > 1 used to be left to useup(), but we need to + * force the current potion to be unworn, and don't want to do + * that for the entire stack when starting with more than 1. + * [Drinking a wielded potion of polymorph can trigger a shape + * change which causes hero's weapon to be dropped. In 3.4.x, + * that led to an "object lost" panic since subsequent useup() + * was no longer dealing with an inventory item. Unwearing + * the current potion is intended to keep it in inventory.] + * + * 5.0: switch back to relying on useup() unless the object is + * actually worn. Otherwise drinking a stack of unpaid potions + * one by one in a shop makes each one a separate used-up item + * for 'Ix' invent display and for itemized shop billing instead + * of having a single stack with quantity greater than 1. + */ + if (otmp->owornmask) { + if (otmp->quan > 1L) { + otmp = splitobj(otmp, 1L); + otmp->owornmask = 0L; /* rest of original stack is unaffected */ + } else { + remove_worn_item(otmp, FALSE); + } } otmp->in_use = TRUE; /* you've opened the stopper */ - potion_descr = OBJ_DESCR(objects[otmp->otyp]); - if (potion_descr) { - if (!strcmp(potion_descr, "milky") - && !(mvitals[PM_GHOST].mvflags & G_GONE) - && !rn2(POTION_OCCUPANT_CHANCE(mvitals[PM_GHOST].born))) { - ghost_from_bottle(); - useup(otmp); - return 1; - } else if (!strcmp(potion_descr, "smoky") - && !(mvitals[PM_DJINNI].mvflags & G_GONE) - && !rn2(POTION_OCCUPANT_CHANCE(mvitals[PM_DJINNI].born))) { - djinni_from_bottle(otmp); - useup(otmp); - return 1; - } + if (objdescr_is(otmp, "milky") + && !(svm.mvitals[PM_GHOST].mvflags & G_GONE) + && !rn2(POTION_OCCUPANT_CHANCE(svm.mvitals[PM_GHOST].born))) { + ghost_from_bottle(); + useup(otmp); + return ECMD_TIME; + } else if (objdescr_is(otmp, "smoky") + && !(svm.mvitals[PM_DJINNI].mvflags & G_GONE) + && !rn2(POTION_OCCUPANT_CHANCE(svm.mvitals[PM_DJINNI].born))) { + djinni_from_bottle(otmp); + useup(otmp); + return ECMD_TIME; } return dopotion(otmp); } int -dopotion(otmp) -register struct obj *otmp; +dopotion(struct obj *otmp) { int retval; otmp->in_use = TRUE; - nothing = unkn = 0; + gp.potion_nothing = gp.potion_unkn = 0; if ((retval = peffects(otmp)) >= 0) - return retval; + return retval ? ECMD_TIME : ECMD_OK; - if (nothing) { - unkn++; + if (gp.potion_nothing) { + gp.potion_unkn++; You("have a %s feeling for a moment, then it passes.", Hallucination ? "normal" : "peculiar"); } if (otmp->dknown && !objects[otmp->otyp].oc_name_known) { - if (!unkn) { + if (!gp.potion_unkn) { makeknown(otmp->otyp); more_experienced(0, 10); - } else if (!objects[otmp->otyp].oc_uname) - docall(otmp); + } else + trycall(otmp); } useup(otmp); - return 1; + return ECMD_TIME; } -int -peffects(otmp) -register struct obj *otmp; +/* potion or spell of restore ability; for spell, otmp is a temporary + spellbook object that will be blessed if hero is skilled in healing */ +staticfn void +peffect_restore_ability(struct obj *otmp) { - register int i, ii, lim; + gp.potion_unkn++; + if (otmp->cursed) { + pline("Ulch! This makes you feel mediocre!"); + return; + } else { + int i, ii; + + /* unlike unicorn horn, overrides Fixed_abil; + does not recover temporary strength loss due to hunger + or temporary dexterity loss due to wounded legs */ + pline("Wow! This makes you feel %s!", + (!otmp->blessed) ? "good" + : unfixable_trouble_count(FALSE) ? "better" + : "great"); + i = rn2(A_MAX); /* start at a random point */ + for (ii = 0; ii < A_MAX; ii++) { + int lim = AMAX(i); + + /* this used to adjust 'lim' for A_STR when u.uhs was + WEAK or worse, but that's handled via ATEMP(A_STR) now */ + if (ABASE(i) < lim) { + ABASE(i) = lim; + /* reset stat abuse (but not exercise) to 0 as well */ + AEXE(i) = max(AEXE(i), 0); + + disp.botl = TRUE; + /* only first found if not blessed */ + if (!otmp->blessed) + break; + } + if (++i >= A_MAX) + i = 0; + } - switch (otmp->otyp) { - case POT_RESTORE_ABILITY: - case SPE_RESTORE_ABILITY: - unkn++; - if (otmp->cursed) { - pline("Ulch! This makes you feel mediocre!"); - break; + /* when using the potion (not the spell) also restore lost levels, + to make the potion more worth keeping around for players with + the spell or with a unihorn; this is better than full healing + in that it can restore all of them, not just half, and a + blessed potion restores them all at once */ + if (otmp->otyp == POT_RESTORE_ABILITY && u.ulevel < u.ulevelmax) { + do { + pluslvl(FALSE); + } while (u.ulevel < u.ulevelmax && otmp->blessed); + } + } +} + +staticfn void +peffect_hallucination(struct obj *otmp) +{ + if (Halluc_resistance) { + gp.potion_nothing++; + return; + } else if (Hallucination) { + gp.potion_nothing++; + } + (void) make_hallucinated(itimeout_incr(HHallucination, + rn1(200, 600 - 300 * bcsign(otmp))), + TRUE, 0L); + if ((otmp->blessed && !rn2(3)) || (!otmp->cursed && !rn2(6))) { + You("perceive yourself..."); + display_nhwindow(WIN_MESSAGE, FALSE); + enlightenment(MAGICENLIGHTENMENT, ENL_GAMEINPROGRESS); + Your("awareness re-normalizes."); + exercise(A_WIS, TRUE); + } +} + +staticfn void +peffect_water(struct obj *otmp) +{ + if (!otmp->blessed && !otmp->cursed) { + pline("This tastes like %s.", hliquid("water")); + u.uhunger += rnd(10); + newuhs(FALSE); + return; + } + gp.potion_unkn++; + if (mon_hates_blessings(&gy.youmonst) /* undead or demon */ + || u.ualign.type == A_CHAOTIC) { + if (otmp->blessed) { + pline("This burns like %s!", hliquid("acid")); + exercise(A_CON, FALSE); + if (ismnum(u.ulycn)) { + Your("affinity to %s disappears!", + makeplural(mons[u.ulycn].pmnames[NEUTRAL])); + if (gy.youmonst.data == &mons[u.ulycn]) + you_unwere(FALSE); + set_ulycn(NON_PM); /* cure lycanthropy */ + } + losehp(Maybe_Half_Phys(d(2, 6)), "potion of holy water", + KILLED_BY_AN); + } else if (otmp->cursed) { + You_feel("quite proud of yourself."); + healup(d(2, 6), 0, 0, 0); + if (ismnum(u.ulycn) && !Upolyd) + you_were(); + exercise(A_CON, TRUE); + } + } else { + if (otmp->blessed) { + You_feel("full of awe."); + make_sick(0L, (char *) 0, TRUE, SICK_ALL); + exercise(A_WIS, TRUE); + exercise(A_CON, TRUE); + if (ismnum(u.ulycn)) + you_unwere(TRUE); /* "Purified" */ + /* make_confused(0L, TRUE); */ } else { - /* unlike unicorn horn, overrides Fixed_abil */ - pline("Wow! This makes you feel %s!", - (otmp->blessed) - ? (unfixable_trouble_count(FALSE) ? "better" : "great") - : "good"); - i = rn2(A_MAX); /* start at a random point */ - for (ii = 0; ii < A_MAX; ii++) { - lim = AMAX(i); - /* this used to adjust 'lim' for A_STR when u.uhs was - WEAK or worse, but that's handled via ATEMP(A_STR) now */ - if (ABASE(i) < lim) { - ABASE(i) = lim; - context.botl = 1; - /* only first found if not blessed */ - if (!otmp->blessed) - break; + if (u.ualign.type == A_LAWFUL) { + pline("This burns like %s!", hliquid("acid")); + losehp(Maybe_Half_Phys(d(2, 6)), "potion of unholy water", + KILLED_BY_AN); + } else + You_feel("full of dread."); + if (ismnum(u.ulycn) && !Upolyd) + you_were(); + exercise(A_CON, FALSE); + } + } +} + +staticfn void +peffect_booze(struct obj *otmp) +{ + gp.potion_unkn++; + pline("Ooph! This tastes like %s%s!", + otmp->odiluted ? "watered down " : "", + Hallucination ? "dandelion wine" : "liquid fire"); + if (!otmp->blessed) { + /* booze hits harder if drinking on an empty stomach */ + make_confused(itimeout_incr(HConfusion, d(2 + u.uhs, 8)), FALSE); + } + /* the whiskey makes us feel better */ + if (!otmp->odiluted) + healup(1, 0, FALSE, FALSE); + u.uhunger += 10 * (2 + bcsign(otmp)); + newuhs(FALSE); + exercise(A_WIS, FALSE); + if (otmp->cursed) { + You("pass out."); + gm.multi = -rnd(15); + gn.nomovemsg = "You awake with a headache."; + } +} + +staticfn void +peffect_enlightenment(struct obj *otmp) +{ + if (otmp->cursed) { + gp.potion_unkn++; + You("have an uneasy feeling..."); + exercise(A_WIS, FALSE); + } else { + if (otmp->blessed) { + (void) adjattrib(A_INT, 1, FALSE); + (void) adjattrib(A_WIS, 1, FALSE); + } + do_enlightenment_effect(); + } +} + +staticfn void +peffect_invisibility(struct obj *otmp) +{ + boolean is_spell = (otmp->oclass == SPBOOK_CLASS); + + /* spell cannot penetrate mummy wrapping */ + if (is_spell && BInvis && uarmc->otyp == MUMMY_WRAPPING) { + You_feel("rather itchy under %s.", yname(uarmc)); + return; + } + if (Invis || Blind || BInvis) { + gp.potion_nothing++; + } else { + self_invis_message(); + } + if (otmp->blessed && !rn2(HInvis ? 15 : 30)) + HInvis |= FROMOUTSIDE; + else + incr_itimeout(&HInvis, d(6 - 3 * bcsign(otmp), 100) + 100); + newsym(u.ux, u.uy); /* update position */ + if (otmp->cursed) { + pline("For some reason, you feel your presence is known."); + aggravate(); + + /* doing this gives temporary invisibility, but removes permanent + invisibility */ + HInvis &= ~FROMOUTSIDE; + } +} + +staticfn void +peffect_see_invisible(struct obj *otmp) +{ + int msg = Invisible && !Blind; + int permchance = 10 - (HInvis ? 3 : 0) - (HSee_invisible ? 6 : 0); + + gp.potion_unkn++; + if (otmp->cursed) + pline("Yecch! This tastes %s.", + Hallucination ? "overripe" : "rotten"); + else + pline( + Hallucination + ? "This tastes like 10%% real %s%s all-natural beverage." + : "This tastes like %s%s.", + otmp->odiluted ? "reconstituted " : "", fruitname(TRUE)); + if (otmp->otyp == POT_FRUIT_JUICE) { + u.uhunger += (otmp->odiluted ? 5 : 10) * (2 + bcsign(otmp)); + newuhs(FALSE); + return; + } + if (!otmp->cursed) { + /* Tell them they can see again immediately, which + * will help them identify the potion... + */ + make_blinded(0L, TRUE); + } + if (otmp->blessed && !rn2(permchance)) + HSee_invisible |= FROMOUTSIDE; + else + incr_itimeout(&HSee_invisible, rn1(100, 750)); + set_mimic_blocking(); /* do special mimic handling */ + see_monsters(); /* see invisible monsters */ + newsym(u.ux, u.uy); /* see yourself! */ + if (msg && !Blind) { /* Blind possible if polymorphed */ + You("can see through yourself, but you are visible!"); + gp.potion_unkn--; + } +} + +staticfn void +peffect_paralysis(struct obj *otmp) +{ + if (Free_action) { + You("stiffen momentarily."); + } else { + if (Levitation || Is_airlevel(&u.uz) || Is_waterlevel(&u.uz)) + You("are motionlessly suspended."); + else if (u.usteed) + You("are frozen in place!"); + else + Your("%s are frozen to the %s!", makeplural(body_part(FOOT)), + surface(u.ux, u.uy)); + nomul(-(rn1(10, 25 - 12 * bcsign(otmp)))); + gm.multi_reason = "frozen by a potion"; + gn.nomovemsg = You_can_move_again; + exercise(A_DEX, FALSE); + } +} + +staticfn void +peffect_sleeping(struct obj *otmp) +{ + if (Sleep_resistance || Free_action) { + monstseesu(M_SEEN_SLEEP); + You("yawn."); + } else { + You("suddenly fall asleep!"); + monstunseesu(M_SEEN_SLEEP); + fall_asleep(-rn1(10, 25 - 12 * bcsign(otmp)), TRUE); + } +} + +staticfn int +peffect_monster_detection(struct obj *otmp) +{ + if (otmp->blessed) { + int i, x, y; + + if (Detect_monsters) + gp.potion_nothing++; + gp.potion_unkn++; + /* after a while, repeated uses become less effective */ + if ((HDetect_monsters & TIMEOUT) >= 300L) + i = 1; + else if (otmp->oclass == SPBOOK_CLASS) + i = rn1(40, 21); + else /* potion */ + i = rn2(100) + 100; + incr_itimeout(&HDetect_monsters, i); + for (x = 1; x < COLNO; x++) { + for (y = 0; y < ROWNO; y++) { + if (levl[x][y].glyph == GLYPH_INVISIBLE) { + unmap_object(x, y); + newsym(x, y); } - if (++i >= A_MAX) - i = 0; + if (MON_AT(x, y)) + gp.potion_unkn = 0; } + } + /* if swallowed or underwater, fall through to uncursed case */ + if (!u.uswallow && !Underwater) { + see_monsters(); + if (gp.potion_unkn) + You_feel("lonely."); + return 0; + } + } + if (monster_detect(otmp, 0)) + return 1; /* nothing detected */ + exercise(A_WIS, TRUE); + return 0; +} - /* when using the potion (not the spell) also restore lost levels, - to make the potion more worth keeping around for players with - the spell or with a unihorn; this is better than full healing - in that it can restore all of them, not just half, and a - blessed potion restores them all at once */ - if (otmp->otyp == POT_RESTORE_ABILITY && u.ulevel < u.ulevelmax) { - do { - pluslvl(FALSE); - } while (u.ulevel < u.ulevelmax && otmp->blessed); +staticfn int +peffect_object_detection(struct obj *otmp) +{ + if (object_detect(otmp, 0)) + return 1; /* nothing detected */ + exercise(A_WIS, TRUE); + return 0; +} + +staticfn void +peffect_sickness(struct obj *otmp) +{ + pline("Yecch! This stuff tastes like poison."); + if (otmp->blessed) { + pline("(But in fact it was mildly stale %s.)", fruitname(TRUE)); + if (!Role_if(PM_HEALER)) { + /* NB: blessed otmp->fromsink is not possible */ + losehp(1, "mildly contaminated potion", KILLED_BY_AN); + } + } else { + if (Poison_resistance) + pline("(But in fact it was biologically contaminated %s.)", + fruitname(TRUE)); + if (Role_if(PM_HEALER)) { + pline("Fortunately, you have been immunized."); + } else { + char contaminant[BUFSZ]; + int typ = rn2(A_MAX); + + Sprintf(contaminant, "%s%s", + (Poison_resistance) ? "mildly " : "", + (otmp->fromsink) ? "contaminated tap water" + : "contaminated potion"); + if (!Fixed_abil) { + poisontell(typ, FALSE); + (void) adjattrib(typ, Poison_resistance ? -1 : -rn1(4, 3), + 1); } + if (!Poison_resistance) { + if (otmp->fromsink) + losehp(rnd(10) + 5 * !!(otmp->cursed), contaminant, + KILLED_BY); + else + losehp(rnd(10) + 5 * !!(otmp->cursed), contaminant, + KILLED_BY_AN); + } else { + /* rnd loss is so that unblessed poorer than blessed */ + losehp(1 + rn2(2), contaminant, + (otmp->fromsink) ? KILLED_BY : KILLED_BY_AN); + } + exercise(A_CON, FALSE); } - break; - case POT_HALLUCINATION: - if (Hallucination || Halluc_resistance) - nothing++; - (void) make_hallucinated(itimeout_incr(HHallucination, - rn1(200, 600 - 300 * bcsign(otmp))), - TRUE, 0L); - break; - case POT_WATER: - if (!otmp->blessed && !otmp->cursed) { - pline("This tastes like %s.", hliquid("water")); - u.uhunger += rnd(10); - newuhs(FALSE); - break; + } + if (Hallucination) { + You("are shocked back to your senses!"); + (void) make_hallucinated(0L, FALSE, 0L); + } +} + +staticfn void +peffect_confusion(struct obj *otmp) +{ + if (!Confusion) { + if (Hallucination) { + pline("What a trippy feeling!"); + gp.potion_unkn++; + } else + pline("Huh, What? Where am I?"); + } else + gp.potion_nothing++; + make_confused(itimeout_incr(HConfusion, + rn1(7, 16 - 8 * bcsign(otmp))), + FALSE); +} + +staticfn void +peffect_gain_ability(struct obj *otmp) +{ + if (otmp->cursed) { + pline("Ulch! That potion tasted foul!"); + gp.potion_unkn++; + } else if (Fixed_abil) { + gp.potion_nothing++; + } else { /* If blessed, increase all; if not, try up to */ + int itmp; /* 6 times to find one which can be increased. */ + int ii, i = -1; /* increment to 0 */ + for (ii = A_MAX; ii > 0; ii--) { + i = (otmp->blessed ? i + 1 : rn2(A_MAX)); + /* only give "your X is already as high as it can get" + message on last attempt (except blessed potions) */ + itmp = (otmp->blessed || ii == 1) ? 0 : -1; + if (adjattrib(i, 1, itmp) && !otmp->blessed) + break; } - unkn++; - if (is_undead(youmonst.data) || is_demon(youmonst.data) - || u.ualign.type == A_CHAOTIC) { - if (otmp->blessed) { - pline("This burns like %s!", hliquid("acid")); - exercise(A_CON, FALSE); - if (u.ulycn >= LOW_PM) { - Your("affinity to %s disappears!", - makeplural(mons[u.ulycn].mname)); - if (youmonst.data == &mons[u.ulycn]) - you_unwere(FALSE); - set_ulycn(NON_PM); /* cure lycanthropy */ + } +} + +staticfn void +peffect_speed(struct obj *otmp) +{ + boolean is_speed = (otmp->otyp == POT_SPEED); + + /* skip when mounted; heal_legs() would heal steed's legs */ + if (is_speed && Wounded_legs && !otmp->cursed && !u.usteed) { + heal_legs(0); + gp.potion_unkn++; + return; + } + + speed_up(rn1(10, 100 + 60 * bcsign(otmp))); + + /* non-cursed potion grants intrinsic speed */ + if (is_speed && !otmp->cursed && !(HFast & INTRINSIC)) { + Your("quickness feels very natural."); + HFast |= FROMOUTSIDE; + } +} + +staticfn void +peffect_blindness(struct obj *otmp) +{ + if (Blind || ((HBlinded || EBlinded) && BBlinded)) + gp.potion_nothing++; + make_blinded(itimeout_incr(BlindedTimeout, + rn1(200, 250 - 125 * bcsign(otmp))), + (boolean) !Blind); +} + +staticfn void +peffect_gain_level(struct obj *otmp) +{ + if (otmp->cursed) { + boolean on_lvl_1 = (ledger_no(&u.uz) == 1); + + gp.potion_unkn++; + /* they went up a level */ + if (on_lvl_1 ? u.uhave.amulet : Can_rise_up(u.ux, u.uy, &u.uz)) { + int newlev; + d_level newlevel; + + if (on_lvl_1) { + assign_level(&newlevel, &earth_level); + } else { + newlev = depth(&u.uz) - 1; + get_level(&newlevel, newlev); + if (on_level(&newlevel, &u.uz)) { + pline("It tasted bad."); + return; } - losehp(Maybe_Half_Phys(d(2, 6)), "potion of holy water", - KILLED_BY_AN); - } else if (otmp->cursed) { - You_feel("quite proud of yourself."); - healup(d(2, 6), 0, 0, 0); - if (u.ulycn >= LOW_PM && !Upolyd) - you_were(); - exercise(A_CON, TRUE); } + You("rise up, through the %s!", ceiling(u.ux, u.uy)); + goto_level(&newlevel, FALSE, FALSE, FALSE); } else { - if (otmp->blessed) { - You_feel("full of awe."); - make_sick(0L, (char *) 0, TRUE, SICK_ALL); - exercise(A_WIS, TRUE); - exercise(A_CON, TRUE); - if (u.ulycn >= LOW_PM) - you_unwere(TRUE); /* "Purified" */ - /* make_confused(0L, TRUE); */ - } else { - if (u.ualign.type == A_LAWFUL) { - pline("This burns like %s!", hliquid("acid")); - losehp(Maybe_Half_Phys(d(2, 6)), "potion of unholy water", - KILLED_BY_AN); - } else - You_feel("full of dread."); - if (u.ulycn >= LOW_PM && !Upolyd) - you_were(); - exercise(A_CON, FALSE); - } + You("have an uneasy feeling."); + } + return; + } + pluslvl(FALSE); + /* blessed potions place you at a random spot in the + middle of the new level instead of the low point */ + if (otmp->blessed) + u.uexp = rndexp(TRUE); +} + +staticfn void +peffect_healing(struct obj *otmp) +{ + You_feel("better."); + healup(8 + d(4 + 2 * bcsign(otmp), 4), !otmp->cursed ? 1 : 0, + !!otmp->blessed, !otmp->cursed); + exercise(A_CON, TRUE); +} + +staticfn void +peffect_extra_healing(struct obj *otmp) +{ + You_feel("much better."); + healup(16 + d(4 + 2 * bcsign(otmp), 8), + otmp->blessed ? 5 : !otmp->cursed ? 2 : 0, !otmp->cursed, + TRUE); + (void) make_hallucinated(0L, TRUE, 0L); + exercise(A_CON, TRUE); + exercise(A_STR, TRUE); + /* blessed potion also heals wounded legs unless riding (where leg + wounds apply to the steed rather than to the hero) */ + if (Wounded_legs && (otmp->blessed && !u.usteed)) + heal_legs(0); +} + +staticfn void +peffect_full_healing(struct obj *otmp) +{ + You_feel("completely healed."); + healup(400, 4 + 4 * bcsign(otmp), !otmp->cursed, TRUE); + /* Restore one lost level if blessed */ + if (otmp->blessed && u.ulevel < u.ulevelmax) { + /* when multiple levels have been lost, drinking + multiple potions will only get half of them back */ + u.ulevelmax -= 1; + pluslvl(FALSE); + } + (void) make_hallucinated(0L, TRUE, 0L); + exercise(A_STR, TRUE); + exercise(A_CON, TRUE); + /* blessed potion heals wounded legs even when riding (so heals steed's + legs--it's magic); uncursed potion heals hero's legs unless riding */ + if (Wounded_legs && (otmp->blessed || (!otmp->cursed && !u.usteed))) + heal_legs(0); +} + +staticfn void +peffect_levitation(struct obj *otmp) +{ + /* + * BLevitation will be set if levitation is blocked due to being + * inside rock (currently or formerly in phazing xorn form, perhaps) + * but it doesn't prevent setting or incrementing Levitation timeout + * (which will take effect after escaping from the rock if it hasn't + * expired by then). + */ + if (!Levitation && !BLevitation) { + /* kludge to ensure proper operation of float_up() */ + set_itimeout(&HLevitation, 1L); + float_up(); + /* This used to set timeout back to 0, then increment it below + for blessed and uncursed effects. But now we leave it so + that cursed effect yields "you float down" on next turn. + Blessed and uncursed get one extra turn duration. */ + } else /* already levitating, or can't levitate */ + gp.potion_nothing++; + + if (otmp->cursed) { + stairway *stway; + + /* 'already levitating' used to block the cursed effect(s) + aside from ~I_SPECIAL; it was not clear whether that was + intentional; either way, it no longer does (as of 3.6.1) */ + HLevitation &= ~I_SPECIAL; /* can't descend upon demand */ + if (BLevitation) { + ; /* rising via levitation is blocked */ + } else if ((stway = stairway_at(u.ux, u.uy)) != 0 && stway->up) { + (void) doup(); + /* in case we're already Levitating, which would have + resulted in incrementing 'nothing' */ + gp.potion_nothing = 0; /* not nothing after all */ + } else if (has_ceiling(&u.uz)) { + int dmg = rnd(!uarmh ? 10 : !hard_helmet(uarmh) ? 6 : 3); + + You("hit your %s on the %s.", body_part(HEAD), + ceiling(u.ux, u.uy)); + losehp(Maybe_Half_Phys(dmg), "colliding with the ceiling", + KILLED_BY); + gp.potion_nothing = 0; /* not nothing after all */ + } + } else if (otmp->blessed) { + /* at this point, timeout is already at least 1 */ + incr_itimeout(&HLevitation, rn1(50, 250)); + /* can descend at will (stop levitating via '>') provided timeout + is the only factor (ie, not also wearing Lev ring or boots) */ + HLevitation |= I_SPECIAL; + } else /* timeout is already at least 1 */ + incr_itimeout(&HLevitation, rn1(140, 10)); + + if (Levitation && IS_SINK(levl[u.ux][u.uy].typ)) + spoteffects(FALSE); + /* levitating blocks flying */ + float_vs_flight(); +} + +staticfn void +peffect_gain_energy(struct obj *otmp) +{ + int num; + + if (otmp->cursed) + You_feel("lackluster."); + else + pline("Magical energies course through your body."); + + /* old: num = rnd(5) + 5 * otmp->blessed + 1; + * blessed: +7..11 max & current (+9 avg) + * uncursed: +2.. 6 max & current (+4 avg) + * cursed: -2.. 6 max & current (-4 avg) + * new: (3.6.0) + * blessed: +3..18 max (+10.5 avg), +9..54 current (+31.5 avg) + * uncursed: +2..12 max (+ 7 avg), +6..36 current (+21 avg) + * cursed: -1.. 6 max (- 3.5 avg), -3..18 current (-10.5 avg) + */ + num = d(otmp->blessed ? 3 : !otmp->cursed ? 2 : 1, 6); + if (otmp->cursed) + num = -num; /* subtract instead of add when cursed */ + u.uenmax += num; + if (u.uenmax > u.uenpeak) + u.uenpeak = u.uenmax; + else if (u.uenmax <= 0) + u.uenmax = 0; + u.uen += 3 * num; + if (u.uen > u.uenmax) + u.uen = u.uenmax; + else if (u.uen <= 0) + u.uen = 0; + disp.botl = TRUE; + exercise(A_WIS, TRUE); +} + +staticfn void +peffect_oil(struct obj *otmp) +{ + boolean good_for_you = FALSE, vulnerable; + + if (otmp->lamplit) { + if (likes_fire(gy.youmonst.data)) { + pline("Ahh, a refreshing drink."); + good_for_you = TRUE; + } else { + /* + * Note: if poly'd into green slime, hero ought to take + * extra damage, but drinking potions in that form isn't + * possible so there's no need to try to handle that. + */ + You("burn your %s.", body_part(FACE)); + /* fire damage */ + vulnerable = !Fire_resistance || Cold_resistance; + losehp(d(vulnerable ? 4 : 2, 4), + "quaffing a burning potion of oil", + KILLED_BY); + } + /* + * This is slightly iffy because the burning isn't being + * spread across the body. But the message is "the slime + * that covers you burns away" and having that follow + * "you burn your face" seems consistent enough. + */ + burn_away_slime(); + } else if (otmp->cursed) { + pline("This tastes like castor oil."); + } else { + pline("That was smooth!"); + } + exercise(A_WIS, good_for_you); +} + +staticfn void +peffect_acid(struct obj *otmp) +{ + if (Acid_resistance) { + /* Not necessarily a creature who _likes_ acid */ + pline("This tastes %s.", Hallucination ? "tangy" : "sour"); + } else { + int dmg; + + pline("This burns%s!", + otmp->blessed ? " a little" : otmp->cursed ? " a lot" + : " like acid"); + dmg = d(otmp->cursed ? 2 : 1, otmp->blessed ? 4 : 8); + losehp(Maybe_Half_Phys(dmg), "potion of acid", KILLED_BY_AN); + exercise(A_CON, FALSE); + } + if (Stoned) + fix_petrification(); + gp.potion_unkn++; /* holy/unholy water can burn like acid too */ +} + +staticfn void +peffect_polymorph(struct obj *otmp) +{ + You_feel("a little %s.", Hallucination ? "normal" : "strange"); + if (!Unchanging) { + if (!otmp->blessed || (u.umonnum != u.umonster)) + polyself(POLY_NOFLAGS); + else { + polyself(POLY_CONTROLLED|POLY_LOW_CTRL); + if (u.mtimedone && u.umonnum != u.umonster) + u.mtimedone = min(u.mtimedone, rn2(15) + 10); } + } +} + +int +peffects(struct obj *otmp) +{ + switch (otmp->otyp) { + case POT_RESTORE_ABILITY: + case SPE_RESTORE_ABILITY: + peffect_restore_ability(otmp); + break; + case POT_HALLUCINATION: + peffect_hallucination(otmp); + break; + case POT_WATER: + peffect_water(otmp); break; case POT_BOOZE: - unkn++; - pline("Ooph! This tastes like %s%s!", - otmp->odiluted ? "watered down " : "", - Hallucination ? "dandelion wine" : "liquid fire"); - if (!otmp->blessed) - make_confused(itimeout_incr(HConfusion, d(3, 8)), FALSE); - /* the whiskey makes us feel better */ - if (!otmp->odiluted) - healup(1, 0, FALSE, FALSE); - u.uhunger += 10 * (2 + bcsign(otmp)); - newuhs(FALSE); - exercise(A_WIS, FALSE); - if (otmp->cursed) { - You("pass out."); - multi = -rnd(15); - nomovemsg = "You awake with a headache."; - } + peffect_booze(otmp); break; case POT_ENLIGHTENMENT: - if (otmp->cursed) { - unkn++; - You("have an uneasy feeling..."); - exercise(A_WIS, FALSE); - } else { - if (otmp->blessed) { - (void) adjattrib(A_INT, 1, FALSE); - (void) adjattrib(A_WIS, 1, FALSE); - } - You_feel("self-knowledgeable..."); - display_nhwindow(WIN_MESSAGE, FALSE); - enlightenment(MAGICENLIGHTENMENT, ENL_GAMEINPROGRESS); - pline_The("feeling subsides."); - exercise(A_WIS, TRUE); - } + peffect_enlightenment(otmp); break; case SPE_INVISIBILITY: - /* spell cannot penetrate mummy wrapping */ - if (BInvis && uarmc->otyp == MUMMY_WRAPPING) { - You_feel("rather itchy under %s.", yname(uarmc)); - break; - } - /* FALLTHRU */ case POT_INVISIBILITY: - if (Invis || Blind || BInvis) { - nothing++; - } else { - self_invis_message(); - } - if (otmp->blessed) - HInvis |= FROMOUTSIDE; - else - incr_itimeout(&HInvis, rn1(15, 31)); - newsym(u.ux, u.uy); /* update position */ - if (otmp->cursed) { - pline("For some reason, you feel your presence is known."); - aggravate(); - } + peffect_invisibility(otmp); break; case POT_SEE_INVISIBLE: /* tastes like fruit juice in Rogue */ - case POT_FRUIT_JUICE: { - int msg = Invisible && !Blind; - - unkn++; - if (otmp->cursed) - pline("Yecch! This tastes %s.", - Hallucination ? "overripe" : "rotten"); - else - pline( - Hallucination - ? "This tastes like 10%% real %s%s all-natural beverage." - : "This tastes like %s%s.", - otmp->odiluted ? "reconstituted " : "", fruitname(TRUE)); - if (otmp->otyp == POT_FRUIT_JUICE) { - u.uhunger += (otmp->odiluted ? 5 : 10) * (2 + bcsign(otmp)); - newuhs(FALSE); - break; - } - if (!otmp->cursed) { - /* Tell them they can see again immediately, which - * will help them identify the potion... - */ - make_blinded(0L, TRUE); - } - if (otmp->blessed) - HSee_invisible |= FROMOUTSIDE; - else - incr_itimeout(&HSee_invisible, rn1(100, 750)); - set_mimic_blocking(); /* do special mimic handling */ - see_monsters(); /* see invisible monsters */ - newsym(u.ux, u.uy); /* see yourself! */ - if (msg && !Blind) { /* Blind possible if polymorphed */ - You("can see through yourself, but you are visible!"); - unkn--; - } + case POT_FRUIT_JUICE: + peffect_see_invisible(otmp); break; - } case POT_PARALYSIS: - if (Free_action) { - You("stiffen momentarily."); - } else { - if (Levitation || Is_airlevel(&u.uz) || Is_waterlevel(&u.uz)) - You("are motionlessly suspended."); - else if (u.usteed) - You("are frozen in place!"); - else - Your("%s are frozen to the %s!", makeplural(body_part(FOOT)), - surface(u.ux, u.uy)); - nomul(-(rn1(10, 25 - 12 * bcsign(otmp)))); - multi_reason = "frozen by a potion"; - nomovemsg = You_can_move_again; - exercise(A_DEX, FALSE); - } + peffect_paralysis(otmp); break; case POT_SLEEPING: - if (Sleep_resistance || Free_action) { - You("yawn."); - } else { - You("suddenly fall asleep!"); - fall_asleep(-rn1(10, 25 - 12 * bcsign(otmp)), TRUE); - } + peffect_sleeping(otmp); break; case POT_MONSTER_DETECTION: case SPE_DETECT_MONSTERS: - if (otmp->blessed) { - int x, y; - - if (Detect_monsters) - nothing++; - unkn++; - /* after a while, repeated uses become less effective */ - if ((HDetect_monsters & TIMEOUT) >= 300L) - i = 1; - else - i = rn1(40, 21); - incr_itimeout(&HDetect_monsters, i); - for (x = 1; x < COLNO; x++) { - for (y = 0; y < ROWNO; y++) { - if (levl[x][y].glyph == GLYPH_INVISIBLE) { - unmap_object(x, y); - newsym(x, y); - } - if (MON_AT(x, y)) - unkn = 0; - } - } - see_monsters(); - if (unkn) - You_feel("lonely."); - break; - } - if (monster_detect(otmp, 0)) - return 1; /* nothing detected */ - exercise(A_WIS, TRUE); + if (peffect_monster_detection(otmp)) + return 1; break; case POT_OBJECT_DETECTION: case SPE_DETECT_TREASURE: - if (object_detect(otmp, 0)) - return 1; /* nothing detected */ - exercise(A_WIS, TRUE); + if (peffect_object_detection(otmp)) + return 1; break; case POT_SICKNESS: - pline("Yecch! This stuff tastes like poison."); - if (otmp->blessed) { - pline("(But in fact it was mildly stale %s.)", fruitname(TRUE)); - if (!Role_if(PM_HEALER)) { - /* NB: blessed otmp->fromsink is not possible */ - losehp(1, "mildly contaminated potion", KILLED_BY_AN); - } - } else { - if (Poison_resistance) - pline("(But in fact it was biologically contaminated %s.)", - fruitname(TRUE)); - if (Role_if(PM_HEALER)) { - pline("Fortunately, you have been immunized."); - } else { - char contaminant[BUFSZ]; - int typ = rn2(A_MAX); - - Sprintf(contaminant, "%s%s", - (Poison_resistance) ? "mildly " : "", - (otmp->fromsink) ? "contaminated tap water" - : "contaminated potion"); - if (!Fixed_abil) { - poisontell(typ, FALSE); - (void) adjattrib(typ, Poison_resistance ? -1 : -rn1(4, 3), - 1); - } - if (!Poison_resistance) { - if (otmp->fromsink) - losehp(rnd(10) + 5 * !!(otmp->cursed), contaminant, - KILLED_BY); - else - losehp(rnd(10) + 5 * !!(otmp->cursed), contaminant, - KILLED_BY_AN); - } else { - /* rnd loss is so that unblessed poorer than blessed */ - losehp(1 + rn2(2), contaminant, - (otmp->fromsink) ? KILLED_BY : KILLED_BY_AN); - } - exercise(A_CON, FALSE); - } - } - if (Hallucination) { - You("are shocked back to your senses!"); - (void) make_hallucinated(0L, FALSE, 0L); - } + peffect_sickness(otmp); break; case POT_CONFUSION: - if (!Confusion) { - if (Hallucination) { - pline("What a trippy feeling!"); - unkn++; - } else - pline("Huh, What? Where am I?"); - } else - nothing++; - make_confused(itimeout_incr(HConfusion, - rn1(7, 16 - 8 * bcsign(otmp))), - FALSE); + peffect_confusion(otmp); break; case POT_GAIN_ABILITY: - if (otmp->cursed) { - pline("Ulch! That potion tasted foul!"); - unkn++; - } else if (Fixed_abil) { - nothing++; - } else { /* If blessed, increase all; if not, try up to */ - int itmp; /* 6 times to find one which can be increased. */ - - i = -1; /* increment to 0 */ - for (ii = A_MAX; ii > 0; ii--) { - i = (otmp->blessed ? i + 1 : rn2(A_MAX)); - /* only give "your X is already as high as it can get" - message on last attempt (except blessed potions) */ - itmp = (otmp->blessed || ii == 1) ? 0 : -1; - if (adjattrib(i, 1, itmp) && !otmp->blessed) - break; - } - } + peffect_gain_ability(otmp); break; case POT_SPEED: - /* skip when mounted; heal_legs() would heal steed's legs */ - if (Wounded_legs && !otmp->cursed && !u.usteed) { - heal_legs(0); - unkn++; - break; - } - /* FALLTHRU */ case SPE_HASTE_SELF: - if (!Very_fast) { /* wwf@doe.carleton.ca */ - You("are suddenly moving %sfaster.", Fast ? "" : "much "); - } else { - Your("%s get new energy.", makeplural(body_part(LEG))); - unkn++; - } - exercise(A_DEX, TRUE); - incr_itimeout(&HFast, rn1(10, 100 + 60 * bcsign(otmp))); + peffect_speed(otmp); break; case POT_BLINDNESS: - if (Blind) - nothing++; - make_blinded(itimeout_incr(Blinded, - rn1(200, 250 - 125 * bcsign(otmp))), - (boolean) !Blind); + peffect_blindness(otmp); break; case POT_GAIN_LEVEL: - if (otmp->cursed) { - unkn++; - /* they went up a level */ - if ((ledger_no(&u.uz) == 1 && u.uhave.amulet) - || Can_rise_up(u.ux, u.uy, &u.uz)) { - const char *riseup = "rise up, through the %s!"; - - if (ledger_no(&u.uz) == 1) { - You(riseup, ceiling(u.ux, u.uy)); - goto_level(&earth_level, FALSE, FALSE, FALSE); - } else { - register int newlev = depth(&u.uz) - 1; - d_level newlevel; - - get_level(&newlevel, newlev); - if (on_level(&newlevel, &u.uz)) { - pline("It tasted bad."); - break; - } else - You(riseup, ceiling(u.ux, u.uy)); - goto_level(&newlevel, FALSE, FALSE, FALSE); - } - } else - You("have an uneasy feeling."); - break; - } - pluslvl(FALSE); - /* blessed potions place you at a random spot in the - middle of the new level instead of the low point */ - if (otmp->blessed) - u.uexp = rndexp(TRUE); + peffect_gain_level(otmp); break; case POT_HEALING: - You_feel("better."); - healup(d(6 + 2 * bcsign(otmp), 4), !otmp->cursed ? 1 : 0, - !!otmp->blessed, !otmp->cursed); - exercise(A_CON, TRUE); + peffect_healing(otmp); break; case POT_EXTRA_HEALING: - You_feel("much better."); - healup(d(6 + 2 * bcsign(otmp), 8), - otmp->blessed ? 5 : !otmp->cursed ? 2 : 0, !otmp->cursed, - TRUE); - (void) make_hallucinated(0L, TRUE, 0L); - exercise(A_CON, TRUE); - exercise(A_STR, TRUE); + peffect_extra_healing(otmp); break; case POT_FULL_HEALING: - You_feel("completely healed."); - healup(400, 4 + 4 * bcsign(otmp), !otmp->cursed, TRUE); - /* Restore one lost level if blessed */ - if (otmp->blessed && u.ulevel < u.ulevelmax) { - /* when multiple levels have been lost, drinking - multiple potions will only get half of them back */ - u.ulevelmax -= 1; - pluslvl(FALSE); - } - (void) make_hallucinated(0L, TRUE, 0L); - exercise(A_STR, TRUE); - exercise(A_CON, TRUE); + peffect_full_healing(otmp); break; case POT_LEVITATION: case SPE_LEVITATION: - /* - * BLevitation will be set if levitation is blocked due to being - * inside rock (currently or formerly in phazing xorn form, perhaps) - * but it doesn't prevent setting or incrementing Levitation timeout - * (which will take effect after escaping from the rock if it hasn't - * expired by then). - */ - if (!Levitation && !BLevitation) { - /* kludge to ensure proper operation of float_up() */ - set_itimeout(&HLevitation, 1L); - float_up(); - /* This used to set timeout back to 0, then increment it below - for blessed and uncursed effects. But now we leave it so - that cursed effect yields "you float down" on next turn. - Blessed and uncursed get one extra turn duration. */ - } else /* already levitating, or can't levitate */ - nothing++; - - if (otmp->cursed) { - /* 'already levitating' used to block the cursed effect(s) - aside from ~I_SPECIAL; it was not clear whether that was - intentional; either way, it no longer does (as of 3.6.1) */ - HLevitation &= ~I_SPECIAL; /* can't descend upon demand */ - if (BLevitation) { - ; /* rising via levitation is blocked */ - } else if ((u.ux == xupstair && u.uy == yupstair) - || (sstairs.up && u.ux == sstairs.sx && u.uy == sstairs.sy) - || (xupladder && u.ux == xupladder && u.uy == yupladder)) { - (void) doup(); - /* in case we're already Levitating, which would have - resulted in incrementing 'nothing' */ - nothing = 0; /* not nothing after all */ - } else if (has_ceiling(&u.uz)) { - int dmg = rnd(!uarmh ? 10 : !is_metallic(uarmh) ? 6 : 3); - - You("hit your %s on the %s.", body_part(HEAD), - ceiling(u.ux, u.uy)); - losehp(Maybe_Half_Phys(dmg), "colliding with the ceiling", - KILLED_BY); - nothing = 0; /* not nothing after all */ - } - } else if (otmp->blessed) { - /* at this point, timeout is already at least 1 */ - incr_itimeout(&HLevitation, rn1(50, 250)); - /* can descend at will (stop levitating via '>') provided timeout - is the only factor (ie, not also wearing Lev ring or boots) */ - HLevitation |= I_SPECIAL; - } else /* timeout is already at least 1 */ - incr_itimeout(&HLevitation, rn1(140, 10)); - - if (Levitation && IS_SINK(levl[u.ux][u.uy].typ)) - spoteffects(FALSE); - /* levitating blocks flying */ - float_vs_flight(); + peffect_levitation(otmp); break; - case POT_GAIN_ENERGY: { /* M. Stephenson */ - int num; - - if (otmp->cursed) - You_feel("lackluster."); - else - pline("Magical energies course through your body."); - - /* old: num = rnd(5) + 5 * otmp->blessed + 1; - * blessed: +7..11 max & current (+9 avg) - * uncursed: +2.. 6 max & current (+4 avg) - * cursed: -2.. 6 max & current (-4 avg) - * new: (3.6.0) - * blessed: +3..18 max (+10.5 avg), +9..54 current (+31.5 avg) - * uncursed: +2..12 max (+ 7 avg), +6..36 current (+21 avg) - * cursed: -1.. 6 max (- 3.5 avg), -3..18 current (-10.5 avg) - */ - num = d(otmp->blessed ? 3 : !otmp->cursed ? 2 : 1, 6); - if (otmp->cursed) - num = -num; /* subtract instead of add when cursed */ - u.uenmax += num; - if (u.uenmax <= 0) - u.uenmax = 0; - u.uen += 3 * num; - if (u.uen > u.uenmax) - u.uen = u.uenmax; - else if (u.uen <= 0) - u.uen = 0; - context.botl = 1; - exercise(A_WIS, TRUE); + case POT_GAIN_ENERGY: /* M. Stephenson */ + peffect_gain_energy(otmp); break; - } - case POT_OIL: { /* P. Winner */ - boolean good_for_you = FALSE; - - if (otmp->lamplit) { - if (likes_fire(youmonst.data)) { - pline("Ahh, a refreshing drink."); - good_for_you = TRUE; - } else { - You("burn your %s.", body_part(FACE)); - /* fire damage */ - losehp(d(Fire_resistance ? 1 : 3, 4), "burning potion of oil", - KILLED_BY_AN); - } - } else if (otmp->cursed) - pline("This tastes like castor oil."); - else - pline("That was smooth!"); - exercise(A_WIS, good_for_you); + case POT_OIL: /* P. Winner */ + peffect_oil(otmp); break; - } case POT_ACID: - if (Acid_resistance) { - /* Not necessarily a creature who _likes_ acid */ - pline("This tastes %s.", Hallucination ? "tangy" : "sour"); - } else { - int dmg; - - pline("This burns%s!", - otmp->blessed ? " a little" : otmp->cursed ? " a lot" - : " like acid"); - dmg = d(otmp->cursed ? 2 : 1, otmp->blessed ? 4 : 8); - losehp(Maybe_Half_Phys(dmg), "potion of acid", KILLED_BY_AN); - exercise(A_CON, FALSE); - } - if (Stoned) - fix_petrification(); - unkn++; /* holy/unholy water can burn like acid too */ + peffect_acid(otmp); break; case POT_POLYMORPH: - You_feel("a little %s.", Hallucination ? "normal" : "strange"); - if (!Unchanging) - polyself(0); + peffect_polymorph(otmp); break; default: impossible("What a funny potion! (%u)", otmp->otyp); @@ -1159,9 +1425,7 @@ register struct obj *otmp; } void -healup(nhp, nxtra, curesick, cureblind) -int nhp, nxtra; -register boolean curesick, cureblind; +healup(int nhp, int nxtra, boolean curesick, boolean cureblind) { if (nhp) { if (Upolyd) { @@ -1170,12 +1434,15 @@ register boolean curesick, cureblind; u.mh = (u.mhmax += nxtra); } else { u.uhp += nhp; - if (u.uhp > u.uhpmax) + if (u.uhp > u.uhpmax) { u.uhp = (u.uhpmax += nxtra); + if (u.uhpmax > u.uhppeak) + u.uhppeak = u.uhpmax; + } } } if (cureblind) { - /* 3.6.1: it's debatible whether healing magic should clean off + /* 3.6.1: it's debatable whether healing magic should clean off mundane 'dirt', but if it doesn't, blindness isn't cured */ u.ucreamed = 0; make_blinded(0L, TRUE); @@ -1186,14 +1453,12 @@ register boolean curesick, cureblind; make_vomiting(0L, TRUE); make_sick(0L, (char *) 0, TRUE, SICK_ALL); } - context.botl = 1; + disp.botl = TRUE; return; } void -strange_feeling(obj, txt) -struct obj *obj; -const char *txt; +strange_feeling(struct obj *obj, const char *txt) { if (flags.beginner || !txt) You("have a %s feeling for a moment, then it passes.", @@ -1204,30 +1469,39 @@ const char *txt; if (!obj) /* e.g., crystal ball finds no traps */ return; - if (obj->dknown && !objects[obj->otyp].oc_name_known - && !objects[obj->otyp].oc_uname) - docall(obj); + if (obj->dknown) + trycall(obj); useup(obj); } -const char *bottlenames[] = { "bottle", "phial", "flagon", "carafe", +static const char *bottlenames[] = { "bottle", "phial", "flagon", "carafe", "flask", "jar", "vial" }; +static const char *hbottlenames[] = { + "jug", "pitcher", "barrel", "tin", "bag", "box", "glass", "beaker", + "tumbler", "vase", "flowerpot", "pan", "thingy", "mug", "teacup", + "teapot", "keg", "bucket", "thermos", "amphora", "wineskin", "parcel", + "bowl", "ampoule" +}; const char * -bottlename() +bottlename(void) { - return bottlenames[rn2(SIZE(bottlenames))]; + if (Hallucination) + return ROLL_FROM(hbottlenames); + else + return ROLL_FROM(bottlenames); } /* handle item dipped into water potion or steed saddle splashed by same */ -STATIC_OVL boolean -H2Opotion_dip(potion, targobj, useeit, objphrase) -struct obj *potion, *targobj; -boolean useeit; -const char *objphrase; /* "Your widget glows" or "Steed's saddle glows" */ +staticfn boolean +H2Opotion_dip( + struct obj *potion, /* water */ + struct obj *targobj, /* item being dipped into the water */ + boolean useeit, /* will hero see the glow/aura? */ + const char *objphrase) /* "Your widget glows" or "Steed's saddle glows" */ { - void FDECL((*func), (OBJ_P)) = 0; + void (*func)(struct obj *) = (void (*)(struct obj *)) 0; const char *glowcolor = 0; #define COST_alter (-2) #define COST_none (-1) @@ -1262,8 +1536,12 @@ const char *objphrase; /* "Your widget glows" or "Steed's saddle glows" */ } else { /* dipping into uncursed water; carried() check skips steed saddle */ if (carried(targobj)) { + gm.mentioned_water = FALSE; /* water_damage() might set this */ if (water_damage(targobj, 0, TRUE) != ER_NOTHING) res = TRUE; + if (gm.mentioned_water) + makeknown(POT_WATER); + gm.mentioned_water = FALSE; } } if (func) { @@ -1306,17 +1584,48 @@ const char *objphrase; /* "Your widget glows" or "Steed's saddle glows" */ res = TRUE; } return res; +#undef COST_alter +#undef COST_none +} + +/* used when blessed or cursed scroll of light interacts with artifact light; + if the lit object (Sunsword or gold dragon scales/mail) doesn't resist, + treat like dipping it in holy or unholy water (BUC change, glow message) */ +void +impact_arti_light( + struct obj *obj, /* wielded Sunsword or worn gold dragon scales/mail */ + boolean worsen, /* True: lower BUC state unless already cursed; + * False: raise BUC state unless already blessed */ + boolean seeit) /* True: give " glows " message */ +{ + struct obj *otmp; + + /* if already worst/best BUC it can be, or if it resists, do nothing */ + if ((worsen ? obj->cursed : obj->blessed) || obj_resists(obj, 25, 75)) + return; + + /* curse() and bless() take care of maybe_adjust_light() */ + otmp = mksobj(POT_WATER, TRUE, FALSE); + if (worsen) + curse(otmp); + else + bless(otmp); + H2Opotion_dip(otmp, obj, seeit, seeit ? Yobjnam2(obj, "glow") : ""); + dealloc_obj(otmp); +#if 0 /* defer this until caller has used up the scroll so it won't be + * visible; player was told that it disappeared as hero read it */ + if (carried(obj)) /* carried() will always be True here */ + update_inventory(); +#endif + return; } /* potion obj hits monster mon, which might be youmonst; obj always used up */ void -potionhit(mon, obj, how) -struct monst *mon; -struct obj *obj; -int how; +potionhit(struct monst *mon, struct obj *obj, int how) { const char *botlnam = bottlename(); - boolean isyou = (mon == &youmonst); + boolean isyou = (mon == &gy.youmonst); int distance, tx, ty; struct obj *saddle = (struct obj *) 0; boolean hit_saddle = FALSE, your_fault = (how <= POTHIT_HERO_THROW); @@ -1342,6 +1651,7 @@ int how; hit_saddle = TRUE; distance = distu(tx, ty); if (!cansee(tx, ty)) { + Soundeffect(se_potion_crash_and_break, 60); pline("Crash!"); } else { char *mnam = mon_nam(mon); @@ -1354,10 +1664,11 @@ int how; FALSE))); } else if (has_head(mon->data)) { Sprintf(buf, "%s %s", s_suffix(mnam), - (notonhead ? "body" : "head")); + (gn.notonhead ? "body" : "head")); } else { Strcpy(buf, mnam); } + Soundeffect(se_potion_crash_and_break, 60); pline_The("%s crashes on %s and breaks into shards.", botlnam, buf); } @@ -1378,7 +1689,7 @@ int how; case POT_POLYMORPH: You_feel("a little %s.", Hallucination ? "normal" : "strange"); if (!Unchanging && !Antimagic) - polyself(0); + polyself(POLY_NOFLAGS); break; case POT_ACID: if (!Acid_resistance) { @@ -1403,7 +1714,8 @@ int how; switch (obj->otyp) { case POT_WATER: - Sprintf(saddle_glows, "%s %s", buf, aobjnam(saddle, "glow")); + Snprintf(saddle_glows, sizeof(saddle_glows), "%s %s", + buf, aobjnam(saddle, "glow")); affected = H2Opotion_dip(obj, saddle, useeit, saddle_glows); break; case POT_POLYMORPH: @@ -1418,23 +1730,26 @@ int how; switch (obj->otyp) { case POT_FULL_HEALING: cureblind = TRUE; + FALLTHROUGH; /*FALLTHRU*/ case POT_EXTRA_HEALING: if (!obj->cursed) cureblind = TRUE; + FALLTHROUGH; /*FALLTHRU*/ case POT_HEALING: if (obj->blessed) cureblind = TRUE; if (mon->data == &mons[PM_PESTILENCE]) goto do_illness; + FALLTHROUGH; /*FALLTHRU*/ case POT_RESTORE_ABILITY: case POT_GAIN_ABILITY: do_healing: angermon = FALSE; if (mon->mhp < mon->mhpmax) { - mon->mhp = mon->mhpmax; + healmon(mon, mon->mhpmax, 0); if (canseemon(mon)) pline("%s looks sound and hale again.", Monnam(mon)); } @@ -1454,14 +1769,11 @@ int how; break; } do_illness: - if ((mon->mhpmax > 3) && !resist(mon, POTION_CLASS, 0, NOTELL)) - mon->mhpmax /= 2; - if ((mon->mhp > 2) && !resist(mon, POTION_CLASS, 0, NOTELL)) + if (mon->mhp > 2) { mon->mhp /= 2; - if (mon->mhp > mon->mhpmax) - mon->mhp = mon->mhpmax; - if (canseemon(mon)) - pline("%s looks rather ill.", Monnam(mon)); + if (canseemon(mon)) + pline("%s looks rather ill.", Monnam(mon)); + } break; case POT_CONFUSION: case POT_BOOZE: @@ -1469,12 +1781,22 @@ int how; mon->mconf = TRUE; break; case POT_INVISIBILITY: { - boolean sawit = canspotmon(mon); - - angermon = FALSE; - mon_set_minvis(mon); - if (sawit && !canspotmon(mon) && cansee(mon->mx, mon->my)) - map_invisible(mon->mx, mon->my); + boolean sawit = canspotmon(mon), + cursed_potion = obj->cursed ? TRUE : FALSE; + + angermon = mon->minvis && cursed_potion; + mon_set_minvis(mon, cursed_potion); + if (sawit && !canspotmon(mon)) { + if (cansee(mon->mx, mon->my)) + map_invisible(mon->mx, mon->my); + } else if (sawit && cursed_potion) { + pline("%s briefly seems to be transparent.", Monnam(mon)); + /* see use_misc(muse.c) for comment about map_invisible() */ + } else if (!sawit && canspotmon(mon)) { + /* if an invisible mon glyph was present, mon_set_minvis()'s + newsym() has gotten rid of it */ + pline("%s appears!", Monnam(mon)); + } break; } case POT_SLEEPING: @@ -1497,7 +1819,7 @@ int how; mon_adjust_speed(mon, 1, obj); break; case POT_BLINDNESS: - if (haseyes(mon->data)) { + if (haseyes(mon->data) && !mon_perma_blind(mon)) { int btmp = 64 + rn2(32) + rn2(32) * !resist(mon, POTION_CLASS, 0, NOTELL); @@ -1507,7 +1829,7 @@ int how; } break; case POT_WATER: - if (is_undead(mon->data) || is_demon(mon->data) + if (mon_hates_blessings(mon) /* undead or demon */ || is_were(mon->data) || is_vampshifter(mon)) { if (obj->blessed) { pline("%s %s in pain!", Monnam(mon), @@ -1524,9 +1846,7 @@ int how; angermon = FALSE; if (canseemon(mon)) pline("%s looks healthier.", Monnam(mon)); - mon->mhp += d(2, 6); - if (mon->mhp > mon->mhpmax) - mon->mhp = mon->mhpmax; + healmon(mon, d(2, 6), 0); if (is_were(mon->data) && is_human(mon->data) && !Protection_from_shape_changers) new_were(mon); /* transform into beast */ @@ -1584,12 +1904,11 @@ int how; } /* Note: potionbreathe() does its own docall() */ - if ((distance == 0 || (distance < 3 && rn2(5))) - && (!breathless(youmonst.data) || haseyes(youmonst.data))) + if ((distance == 0 || (distance < 3 && !rn2((1+ACURR(A_DEX))/2))) + && (!breathless(gy.youmonst.data) || haseyes(gy.youmonst.data))) potionbreathe(obj); - else if (obj->dknown && !objects[obj->otyp].oc_name_known - && !objects[obj->otyp].oc_uname && cansee(tx, ty)) - docall(obj); + else if (obj->dknown && cansee(tx, ty)) + trycall(obj); if (*u.ushops && obj->unpaid) { struct monst *shkp = shop_keeper(*in_rooms(u.ux, u.uy, SHOPBASE)); @@ -1599,7 +1918,7 @@ int how; when inside a tended shop */ if (!shkp) /* if shkp was killed, unpaid ought to cleared already */ obj->unpaid = 0; - else if (context.mon_moving) /* obj thrown by monster */ + else if (svc.context.mon_moving) /* obj thrown by monster */ subfrombill(obj, shkp); else /* obj thrown by hero */ (void) stolen_value(obj, u.ux, u.uy, (boolean) shkp->mpeaceful, @@ -1610,27 +1929,33 @@ int how; /* vapors are inhaled or get in your eyes */ void -potionbreathe(obj) -register struct obj *obj; +potionbreathe(struct obj *obj) { int i, ii, isdone, kn = 0; boolean cureblind = FALSE; + unsigned already_in_use = obj->in_use; /* potion of unholy water might be wielded; prevent you_were() -> drop_weapon() from dropping it so that it remains in inventory where our caller expects it to be */ obj->in_use = 1; - switch (obj->otyp) { + /* wearing a wet towel protects both eyes and breathing, even when + the breath effect might be beneficial; we still pass down to the + naming opportunity in case potion was thrown at hero by a monster */ + switch (Half_gas_damage ? TOWEL : obj->otyp) { + case TOWEL: + pline("Some vapor passes harmlessly around you."); + break; case POT_RESTORE_ABILITY: case POT_GAIN_ABILITY: if (obj->cursed) { - if (!breathless(youmonst.data)) + if (!breathless(gy.youmonst.data)) { pline("Ulch! That potion smells terrible!"); - else if (haseyes(youmonst.data)) { + } else if (haseyes(gy.youmonst.data)) { const char *eyes = body_part(EYE); - if (eyecount(youmonst.data) != 1) + if (eyecount(gy.youmonst.data) != 1) eyes = makeplural(eyes); Your("%s %s!", eyes, vtense(eyes, "sting")); } @@ -1642,7 +1967,7 @@ register struct obj *obj; ABASE(i)++; /* only first found if not blessed */ isdone = !(obj->blessed); - context.botl = 1; + disp.botl = TRUE; } if (++i >= A_MAX) i = 0; @@ -1651,24 +1976,26 @@ register struct obj *obj; break; case POT_FULL_HEALING: if (Upolyd && u.mh < u.mhmax) - u.mh++, context.botl = 1; + u.mh++, disp.botl = TRUE; if (u.uhp < u.uhpmax) - u.uhp++, context.botl = 1; + u.uhp++, disp.botl = TRUE; cureblind = TRUE; + FALLTHROUGH; /*FALLTHRU*/ case POT_EXTRA_HEALING: if (Upolyd && u.mh < u.mhmax) - u.mh++, context.botl = 1; + u.mh++, disp.botl = TRUE; if (u.uhp < u.uhpmax) - u.uhp++, context.botl = 1; + u.uhp++, disp.botl = TRUE; if (!obj->cursed) cureblind = TRUE; + FALLTHROUGH; /*FALLTHRU*/ case POT_HEALING: if (Upolyd && u.mh < u.mhmax) - u.mh++, context.botl = 1; + u.mh++, disp.botl = TRUE; if (u.uhp < u.uhpmax) - u.uhp++, context.botl = 1; + u.uhp++, disp.botl = TRUE; if (obj->blessed) cureblind = TRUE; if (cureblind) { @@ -1690,7 +2017,7 @@ register struct obj *obj; else u.uhp -= 5; } - context.botl = 1; + disp.botl = TRUE; exercise(A_CON, FALSE); } break; @@ -1716,8 +2043,8 @@ register struct obj *obj; if (!Free_action) { pline("%s seems to be holding you.", Something); nomul(-rnd(5)); - multi_reason = "frozen by a potion"; - nomovemsg = You_can_move_again; + gm.multi_reason = "frozen by a potion"; + gn.nomovemsg = You_can_move_again; exercise(A_DEX, FALSE); } else You("stiffen momentarily."); @@ -1727,11 +2054,13 @@ register struct obj *obj; if (!Free_action && !Sleep_resistance) { You_feel("rather tired."); nomul(-rnd(5)); - multi_reason = "sleeping off a magical draught"; - nomovemsg = You_can_move_again; + gm.multi_reason = "sleeping off a magical draught"; + gn.nomovemsg = You_can_move_again; exercise(A_DEX, FALSE); - } else + } else { You("yawn."); + monstseesu(M_SEEN_SLEEP); + } break; case POT_SPEED: if (!Fast) @@ -1744,17 +2073,17 @@ register struct obj *obj; kn++; pline("It suddenly gets dark."); } - make_blinded(itimeout_incr(Blinded, rnd(5)), FALSE); + make_blinded(itimeout_incr(BlindedTimeout, rnd(5)), FALSE); if (!Blind && !Unaware) Your1(vision_clears); break; case POT_WATER: if (u.umonnum == PM_GREMLIN) { - (void) split_mon(&youmonst, (struct monst *) 0); - } else if (u.ulycn >= LOW_PM) { + (void) split_mon(&gy.youmonst, (struct monst *) 0); + } else if (ismnum(u.ulycn)) { /* vapor from [un]holy water will trigger transformation but won't cure lycanthropy */ - if (obj->blessed && youmonst.data == &mons[u.ulycn]) + if (obj->blessed && gy.youmonst.data == &mons[u.ulycn]) you_unwere(FALSE); else if (obj->cursed && !Upolyd) you_were(); @@ -1766,6 +2095,7 @@ register struct obj *obj; break; /* case POT_GAIN_LEVEL: + case POT_GAIN_ENERGY: case POT_LEVITATION: case POT_FRUIT_JUICE: case POT_MONSTER_DETECTION: @@ -1774,20 +2104,22 @@ register struct obj *obj; break; */ } + + if (!already_in_use) + obj->in_use = 0; /* note: no obfree() -- that's our caller's responsibility */ if (obj->dknown) { if (kn) makeknown(obj->otyp); - else if (!objects[obj->otyp].oc_name_known - && !objects[obj->otyp].oc_uname) - docall(obj); + else + trycall(obj); } + return; } /* returns the potion type when o1 is dipped in o2 */ -STATIC_OVL short -mixtype(o1, o2) -register struct obj *o1, *o2; +staticfn short +mixtype(struct obj *o1, struct obj *o2) { int o1typ = o1->otyp, o2typ = o2->otyp; @@ -1806,6 +2138,7 @@ register struct obj *o1, *o2; case POT_HEALING: if (o2typ == POT_SPEED) return POT_EXTRA_HEALING; + FALLTHROUGH; /*FALLTHRU*/ case POT_EXTRA_HEALING: case POT_FULL_HEALING: @@ -1813,6 +2146,7 @@ register struct obj *o1, *o2; return (o1typ == POT_HEALING) ? POT_EXTRA_HEALING : (o1typ == POT_EXTRA_HEALING) ? POT_FULL_HEALING : POT_GAIN_ABILITY; + FALLTHROUGH; /*FALLTHRU*/ case UNICORN_HORN: switch (o2typ) { @@ -1874,27 +2208,84 @@ register struct obj *o1, *o2; return STRANGE_OBJECT; } -/* #dip command */ +/* getobj callback for object to be dipped (not the thing being dipped into, + * that uses drink_ok) */ +staticfn int +dip_ok(struct obj *obj) +{ + if (!obj) + return GETOBJ_DOWNPLAY; + + /* dipping gold isn't currently implemented */ + if (obj->oclass == COIN_CLASS) + return GETOBJ_EXCLUDE; + + if (inaccessible_equipment(obj, (const char *) 0, FALSE)) + return GETOBJ_EXCLUDE_INACCESS; + + return GETOBJ_SUGGEST; +} + +/* getobj callback for object to be dipped when hero has slippery hands */ +staticfn int +dip_hands_ok(struct obj *obj) +{ + if (!obj && (Glib && can_reach_floor(FALSE))) + return GETOBJ_SUGGEST; + + return dip_ok(obj); +} + +/* call hold_another_object() to deal with a transformed potion; its weight + won't have changed but it might require an extra slot that isn't available + or it might merge into some other carried stack */ +staticfn void +hold_potion( + struct obj *potobj, + const char *drop_fmt, const char *drop_arg, + const char *hold_msg) +{ + int cap = near_capacity(), + save_pickup_burden = flags.pickup_burden; + + /* prevent a drop due to current setting of the 'pickup_burden' option */ + if (flags.pickup_burden < cap) + flags.pickup_burden = cap; + /* remove from inventory after calculating near_capacity() */ + obj_extract_self(potobj); + /* re-insert into inventory, possibly merging with compatible stack */ + potobj = hold_another_object(potobj, drop_fmt, drop_arg, hold_msg); + nhUse(potobj); + flags.pickup_burden = save_pickup_burden; + update_inventory(); + return; +} + +/* #dip command - get item to dip, then get potion to dip it into; + precede with 'm' to bypass fountain, pool, or sink at hero's spot */ int -dodip() +dodip(void) { static const char Dip_[] = "Dip "; - register struct obj *potion, *obj; - struct obj *singlepotion; - uchar here; - char allowall[2]; - short mixture; + struct obj *potion, *obj; char qbuf[QBUFSZ], obuf[QBUFSZ]; const char *shortestname; /* last resort obj name for prompt */ - - allowall[0] = ALL_CLASSES; - allowall[1] = '\0'; - if (!(obj = getobj(allowall, "dip"))) - return 0; + uchar here = levl[u.ux][u.uy].typ; + boolean is_hands, at_pool = is_pool(u.ux, u.uy), + at_fountain = IS_FOUNTAIN(here), at_sink = IS_SINK(here), + at_here = (!iflags.menu_requested + && (at_pool || at_fountain || at_sink)); + + obj = getobj("dip", at_here ? dip_hands_ok : dip_ok, GETOBJ_PROMPT); + if (!obj) + return ECMD_CANCEL; if (inaccessible_equipment(obj, "dip", FALSE)) - return 0; + return ECMD_OK; - shortestname = (is_plural(obj) || pair_of(obj)) ? "them" : "it"; + is_hands = (obj == &hands_obj); + shortestname = (is_hands || is_plural(obj) || pair_of(obj)) ? "them" + : "it"; + drink_ok_extra = 0; /* * Bypass safe_qbuf() since it doesn't handle varying suffix without * an awful lot of support work. Format the object once, even though @@ -1904,72 +2295,187 @@ dodip() * supplied type name. * getobj: "What do you want to dip into? [xyz or ?*] " */ - Strcpy(obuf, short_oname(obj, doname, thesimpleoname, - /* 128 - (24 + 54 + 1) leaves 49 for */ - QBUFSZ - sizeof "What do you want to dip \ + if (is_hands) { + Snprintf(obuf, sizeof obuf, "your %s", makeplural(body_part(HAND))); + } else { + Strcpy(obuf, short_oname(obj, doname, thesimpleoname, + /* 128 - (24 + 54 + 1) leaves 49 for + */ + QBUFSZ - sizeof "What do you want to dip\ into? [abdeghjkmnpqstvwyzBCEFHIKLNOQRTUWXZ#-# or ?*] ")); + } - here = levl[u.ux][u.uy].typ; - /* Is there a fountain to dip into here? */ - if (IS_FOUNTAIN(here)) { - Sprintf(qbuf, "%s%s into the fountain?", Dip_, - flags.verbose ? obuf : shortestname); - /* "Dip into the fountain?" */ - if (yn(qbuf) == 'y') { - dipfountain(obj); - return 1; - } - } else if (is_pool(u.ux, u.uy)) { - const char *pooltype = waterbody_name(u.ux, u.uy); - - Sprintf(qbuf, "%s%s into the %s?", Dip_, - flags.verbose ? obuf : shortestname, pooltype); - /* "Dip into the {pool, moat, &c}?" */ - if (yn(qbuf) == 'y') { - if (Levitation) { - floating_above(pooltype); - } else if (u.usteed && !is_swimmer(u.usteed->data) - && P_SKILL(P_RIDING) < P_BASIC) { - rider_cant_reach(); /* not skilled enough to reach */ - } else { - if (obj->otyp == POT_ACID) - obj->in_use = 1; - if (water_damage(obj, 0, TRUE) != ER_DESTROYED && obj->in_use) - useup(obj); + /* preceding #dip with 'm' skips the possibility of dipping into pools, + fountains, and sinks plus the extra prompting which those entail */ + if (!iflags.menu_requested) { + /* Is there a fountain to dip into here? */ + if (!can_reach_floor(FALSE)) { + ; /* can't dip something into fountain or pool if can't reach */ + } else if (at_fountain) { + Snprintf(qbuf, sizeof(qbuf), "%s%s into the fountain?", Dip_, + flags.verbose ? obuf : shortestname); + /* "Dip into the fountain?" */ + if (y_n(qbuf) == 'y') { + if (!is_hands) + obj->pickup_prev = 0; + dipfountain(obj); + return ECMD_TIME; } - return 1; + ++drink_ok_extra; + } else if (at_sink) { + Snprintf(qbuf, sizeof(qbuf), "%s%s into the sink?", Dip_, + flags.verbose ? obuf : shortestname); + if (y_n(qbuf) == 'y') { + if (!is_hands) + obj->pickup_prev = 0; + dipsink(obj); + return ECMD_TIME; + } + ++drink_ok_extra; + } else if (at_pool) { + const char *pooltype = waterbody_name(u.ux, u.uy); + + Snprintf(qbuf, sizeof(qbuf), "%s%s into the %s?", Dip_, + flags.verbose ? obuf : shortestname, pooltype); + /* "Dip into the {pool, moat, &c}?" */ + if (y_n(qbuf) == 'y') { + if (Levitation) { + floating_above(pooltype); + } else if (u.usteed && !is_swimmer(u.usteed->data) + && P_SKILL(P_RIDING) < P_BASIC) { + rider_cant_reach(); /* not skilled enough to reach */ + } else if (is_hands || obj == uarmg) { + if (!is_hands) + obj->pickup_prev = 0; + (void) wash_hands(); + } else { + obj->pickup_prev = 0; + if (obj->otyp == POT_ACID) + obj->in_use = 1; + if (water_damage(obj, 0, TRUE) != ER_DESTROYED + && obj->in_use) + useup(obj); + } + return ECMD_TIME; + } + ++drink_ok_extra; } } /* "What do you want to dip into? [xyz or ?*] " */ - Sprintf(qbuf, "dip %s into", flags.verbose ? obuf : shortestname); - potion = getobj(beverages, qbuf); + Snprintf(qbuf, sizeof qbuf, "dip %s into", + flags.verbose ? obuf : shortestname); + potion = getobj(qbuf, drink_ok, GETOBJ_NOFLAGS); if (!potion) - return 0; + return ECMD_CANCEL; + return potion_dip(obj, potion); +} + +/* #altdip - #dip with "what to dip?" and "what to dip it into?" asked + in the opposite order; ignores floor water; used for context-sensitive + inventory item-action: the potion has already been selected and is in + cmdq ready to answer the first getobj() prompt */ +int +dip_into(void) +{ + struct obj *obj, *potion; + char qbuf[QBUFSZ]; + + if (!cmdq_peek(CQ_CANNED)) { + impossible("dip_into: where is potion?"); + return ECMD_FAIL; + } + /* note: drink_ok() callback for quaffing is also used to validate + a potion to dip into */ + drink_ok_extra = 0; /* affects drink_ok(): haven't been asked about and + * declined to use a floor feature like a fountain */ + potion = getobj("dip", drink_ok, GETOBJ_NOFLAGS); + if (!potion || potion->oclass != POTION_CLASS) + return ECMD_CANCEL; + + /* "What do you want to dip into ? [abc or ?*] " */ + Snprintf(qbuf, sizeof qbuf, "dip into %s%s", + is_plural(potion) ? "one of " : "", thesimpleoname(potion)); + obj = getobj(qbuf, dip_ok, GETOBJ_PROMPT); + if (!obj) + return ECMD_CANCEL; + if (inaccessible_equipment(obj, "dip", FALSE)) + return ECMD_OK; + return potion_dip(obj, potion); +} + +staticfn void +poof(struct obj *potion) +{ + if (potion->dknown) + trycall(potion); + useup(potion); +} + +/* do dipped potion(s) explode? */ +staticfn boolean +dip_potion_explosion(struct obj *obj, int dmg) +{ + if (obj->cursed || obj->otyp == POT_ACID + || (obj->otyp == POT_OIL && obj->lamplit) + || !rn2((uarmc && uarmc->otyp == ALCHEMY_SMOCK) ? 30 : 10)) { + /* it would be better to use up the whole stack in advance + of the message, but we can't because we need to keep it + around for potionbreathe() [and we can't set obj->in_use + to 'amt' because that's not implemented] */ + obj->in_use = 1; + pline("%sThey explode!", !Deaf ? "BOOM! " : ""); + wake_nearto(u.ux, u.uy, (BOLT_LIM + 1) * (BOLT_LIM + 1)); + exercise(A_STR, FALSE); + if (!breathless(gy.youmonst.data) || haseyes(gy.youmonst.data)) + potionbreathe(obj); + useupall(obj); + losehp(dmg, /* not physical damage */ + "alchemic blast", KILLED_BY_AN); + return TRUE; + } + return FALSE; +} + +/* called by dodip() or dip_into() after obj and potion have been chosen */ +staticfn int +potion_dip(struct obj *obj, struct obj *potion) +{ + struct obj *singlepotion; + char qbuf[QBUFSZ]; + short mixture; + if (potion == obj && potion->quan == 1L) { pline("That is a potion bottle, not a Klein bottle!"); - return 0; + return ECMD_OK; + } + if (obj == &hands_obj) { + You("can't fit your %s into the mouth of the bottle!", + body_part(HAND)); + return ECMD_OK; } + + obj->pickup_prev = 0; /* no longer 'recently picked up' */ potion->in_use = TRUE; /* assume it will be used up */ if (potion->otyp == POT_WATER) { boolean useeit = !Blind || (obj == ublindf && Blindfolded_only); const char *obj_glows = Yobjnam2(obj, "glow"); - if (H2Opotion_dip(potion, obj, useeit, obj_glows)) - goto poof; + if (H2Opotion_dip(potion, obj, useeit, obj_glows)) { + poof(potion); + return ECMD_TIME; + } } else if (obj->otyp == POT_POLYMORPH || potion->otyp == POT_POLYMORPH) { /* some objects can't be polymorphed */ - if (obj->otyp == potion->otyp /* both POT_POLY */ - || obj->otyp == WAN_POLYMORPH || obj->otyp == SPE_POLYMORPH - || obj == uball || obj == uskin - || obj_resists(obj->otyp == POT_POLYMORPH ? potion : obj, - 5, 95)) { + if (obj_unpolyable(obj->otyp == POT_POLYMORPH ? potion : obj)) { pline1(nothing_happens); } else { short save_otyp = obj->otyp; /* KMH, conduct */ - u.uconduct.polypiles++; + if (!u.uconduct.polypiles++) + livelog_printf(LL_CONDUCT, "polymorphed %s first item", + uhis()); obj = poly_obj(obj, STRANGE_OBJECT); @@ -1980,19 +2486,20 @@ dodip() */ if (!obj) { makeknown(POT_POLYMORPH); - return 1; + return ECMD_TIME; } else if (obj->otyp != save_otyp) { makeknown(POT_POLYMORPH); useup(potion); prinv((char *) 0, obj, 0L); - return 1; + return ECMD_TIME; } else { - pline("Nothing seems to happen."); - goto poof; + pline1(nothing_seems_to_happen); + poof(potion); + return ECMD_TIME; } } potion->in_use = FALSE; /* didn't go poof */ - return 1; + return ECMD_TIME; } else if (obj->oclass == POTION_CLASS && obj->otyp != potion->otyp) { int amt = (int) obj->quan; boolean magic; @@ -2002,11 +2509,18 @@ dodip() magic = (mixture != STRANGE_OBJECT) ? objects[mixture].oc_magic : (objects[obj->otyp].oc_magic || objects[potion->otyp].oc_magic); Strcpy(qbuf, "The"); /* assume full stack */ - if (amt > (magic ? 3 : 7)) { - /* trying to dip multiple potions will usually affect only a + if (amt > (obj->odiluted ? 2 : magic ? 3 : 7)) { + /* Trying to dip multiple potions will usually affect only a subset; pick an amount between 3 and 8, inclusive, for magic - potion result, between 7 and N for non-magic */ - if (magic) + potion result, between 7 and N for non-magic. If diluted + potions are being dipped, only two are affected; this is a + balance fix to prevent cheap mass alchemy of the (very + common) potion of healing into the (very valuable) potion of + full healing, whilst permitting both healing->extra healing + and extra healing->full healing. */ + if (obj->odiluted) + amt = 2; + else if (magic) amt = rnd(min(amt, 8) - (3 - 1)) + (3 - 1); /* 1..6 + 2 */ else amt = rnd(amt - (7 - 1)) + (7 - 1); /* 1..(N-6) + 6 */ @@ -2024,22 +2538,8 @@ dodip() useup(potion); /* now gone */ /* Mixing potions is dangerous... KMH, balance patch -- acid is particularly unstable */ - if (obj->cursed || obj->otyp == POT_ACID || !rn2(10)) { - /* it would be better to use up the whole stack in advance - of the message, but we can't because we need to keep it - around for potionbreathe() [and we can't set obj->in_use - to 'amt' because that's not implemented] */ - obj->in_use = 1; - pline("%sThey explode!", !Deaf ? "BOOM! " : ""); - wake_nearto(u.ux, u.uy, (BOLT_LIM + 1) * (BOLT_LIM + 1)); - exercise(A_STR, FALSE); - if (!breathless(youmonst.data) || haseyes(youmonst.data)) - potionbreathe(obj); - useupall(obj); - losehp(amt + rnd(9), /* not physical damage */ - "alchemic blast", KILLED_BY_AN); - return 1; - } + if (dip_potion_explosion(obj, amt + rnd(9))) + return ECMD_TIME; obj->blessed = obj->cursed = obj->bknown = 0; if (Blind || Hallucination) @@ -2061,13 +2561,16 @@ dodip() case 4: otmp = mkobj(POTION_CLASS, FALSE); obj->otyp = otmp->otyp; + /* oil uses obj->age field differently from other potions */ + if (obj->otyp == POT_OIL || otmp->otyp == POT_OIL) + fixup_oil(obj, otmp); obfree(otmp, (struct obj *) 0); break; default: useupall(obj); pline_The("mixture %sevaporates.", !Blind ? "glows brightly and " : ""); - return 1; + return ECMD_TIME; } } obj->odiluted = (obj->otyp != POT_WATER); @@ -2086,9 +2589,8 @@ dodip() been made in order to get the merge result for both cases; as a consequence, mixing while Fumbling drops the mixture */ freeinv(obj); - (void) hold_another_object(obj, "You drop %s!", doname(obj), - (const char *) 0); - return 1; + hold_potion(obj, "You drop %s!", doname(obj), (const char *) 0); + return ECMD_TIME; } if (potion->otyp == POT_ACID && obj->otyp == CORPSE @@ -2098,17 +2600,16 @@ dodip() : potion->odiluted ? hcolor(NH_ORANGE) : hcolor(NH_RED)); potion->in_use = FALSE; /* didn't go poof */ - if (potion->dknown - && !objects[potion->otyp].oc_name_known - && !objects[potion->otyp].oc_uname) - docall(potion); - return 1; + if (potion->dknown) + trycall(potion); + return ECMD_TIME; } if (potion->otyp == POT_WATER && obj->otyp == TOWEL) { pline_The("towel soaks it up!"); /* wetting towel already done via water_damage() in H2Opotion_dip */ - goto poof; + poof(potion); + return ECMD_TIME; } if (is_poisonable(obj)) { @@ -2121,19 +2622,24 @@ dodip() Strcpy(buf, The(xname(potion))); pline("%s forms a coating on %s.", buf, the(xname(obj))); obj->opoisoned = TRUE; - goto poof; - } else if (obj->opoisoned && (potion->otyp == POT_HEALING - || potion->otyp == POT_EXTRA_HEALING - || potion->otyp == POT_FULL_HEALING)) { + poof(potion); + return ECMD_TIME; + } else if (obj->opoisoned && !permapoisoned(obj) + && (potion->otyp == POT_HEALING + || potion->otyp == POT_EXTRA_HEALING + || potion->otyp == POT_FULL_HEALING)) { pline("A coating wears off %s.", the(xname(obj))); obj->opoisoned = 0; - goto poof; + poof(potion); + return ECMD_TIME; } } if (potion->otyp == POT_ACID) { - if (erode_obj(obj, 0, ERODE_CORRODE, EF_GREASE) != ER_NOTHING) - goto poof; + if (erode_obj(obj, 0, ERODE_CORRODE, EF_GREASE) != ER_NOTHING) { + poof(potion); + return ECMD_TIME; + } } if (potion->otyp == POT_OIL) { @@ -2148,8 +2654,8 @@ dodip() } else if (obj->oclass != WEAPON_CLASS && !is_weptool(obj)) { /* the following cases apply only to weapons */ goto more_dips; - /* Oil removes rust and corrosion, but doesn't unburn. - * Arrows, etc are classed as metallic due to arrowhead + /* Oil removes rust and corrosion, but doesn't unburn or repair + * cracks. Arrows, etc are classed as metallic due to arrowhead * material, but dipping in oil shouldn't repair them. */ } else if ((!is_rustprone(obj) && !is_corrodeable(obj)) @@ -2176,7 +2682,7 @@ dodip() if (potion->dknown) makeknown(potion->otyp); useup(potion); - return 1; + return ECMD_TIME; } more_dips: @@ -2188,7 +2694,7 @@ dodip() useup(potion); explode(u.ux, u.uy, 11, d(6, 6), 0, EXPL_FIERY); exercise(A_WIS, FALSE); - return 1; + return ECMD_TIME; } /* Adding oil to an empty magic lamp renders it into an oil lamp */ if ((obj->otyp == MAGIC_LAMP) && obj->spe == 0) { @@ -2214,7 +2720,7 @@ dodip() makeknown(POT_OIL); obj->spe = 1; update_inventory(); - return 1; + return ECMD_TIME; } potion->in_use = FALSE; /* didn't go poof */ @@ -2245,10 +2751,10 @@ dodip() else singlepotion->cursed = obj->cursed; /* odiluted left as-is */ singlepotion->bknown = FALSE; - if (Blind) { - singlepotion->dknown = FALSE; - } else { - singlepotion->dknown = !Hallucination; + singlepotion->dknown = FALSE; /* provisionally */ + if (!Blind) { + if (!Hallucination) + observe_object(singlepotion); *newbuf = '\0'; if (mixture == POT_WATER && singlepotion->dknown) Sprintf(newbuf, "clears"); @@ -2267,39 +2773,27 @@ dodip() && !objects[old_otyp].oc_uname) { struct obj fakeobj; - fakeobj = zeroobj; - fakeobj.dknown = 1; + fakeobj = cg.zeroobj; + fakeobj.dknown = 1; /* no need to observe_object */ fakeobj.otyp = old_otyp; fakeobj.oclass = POTION_CLASS; docall(&fakeobj); } } - obj_extract_self(singlepotion); - singlepotion = hold_another_object(singlepotion, - "You juggle and drop %s!", - doname(singlepotion), - (const char *) 0); - nhUse(singlepotion); - update_inventory(); - return 1; + /* remove potion from inventory and re-insert it, possibly stacking + with compatible ones; override 'pickup_burden' while doing so */ + hold_potion(singlepotion, "You juggle and drop %s!", + doname(singlepotion), (const char *) 0); + return ECMD_TIME; } pline("Interesting..."); - return 1; - - poof: - if (potion->dknown - && !objects[potion->otyp].oc_name_known - && !objects[potion->otyp].oc_uname) - docall(potion); - useup(potion); - return 1; + return ECMD_TIME; } /* *monp grants a wish and then leaves the game */ void -mongrantswish(monp) -struct monst **monp; +mongrantswish(struct monst **monp) { struct monst *mon = *monp; int mx = mon->mx, my = mon->my, glyph = glyph_at(mx, my); @@ -2318,13 +2812,12 @@ struct monst **monp; } void -djinni_from_bottle(obj) -struct obj *obj; +djinni_from_bottle(struct obj *obj) { struct monst *mtmp; int chance; - if (!(mtmp = makemon(&mons[PM_DJINNI], u.ux, u.uy, NO_MM_FLAGS))) { + if (!(mtmp = makemon(&mons[PM_DJINNI], u.ux, u.uy, MM_NOMSG))) { pline("It turns out to be empty."); return; } @@ -2344,6 +2837,7 @@ struct obj *obj; chance = (chance == 0) ? rn2(4) : 4; /* 0,1,2,3,4: b=80%,5,5,5,5; nc=20%,20,20,20,20; c=5%,5,5,5,80 */ + SetVoice(mtmp, 0, 80, 0); switch (chance) { case 0: verbalize("I am in your debt. I will grant one wish!"); @@ -2352,7 +2846,7 @@ struct obj *obj; break; case 1: verbalize("Thank you for freeing me!"); - (void) tamedog(mtmp, (struct obj *) 0); + (void) tamedog(mtmp, (struct obj *) 0, FALSE); break; case 2: verbalize("You freed me!"); @@ -2376,9 +2870,9 @@ struct obj *obj; /* clone a gremlin or mold (2nd arg non-null implies heat as the trigger); hit points are cut in half (odd HP stays with original) */ struct monst * -split_mon(mon, mtmp) -struct monst *mon, /* monster being split */ - *mtmp; /* optional attacker whose heat triggered it */ +split_mon( + struct monst *mon, /* monster being split */ + struct monst *mtmp) /* optional attacker whose heat triggered it */ { struct monst *mtmp2; char reason[BUFSZ]; @@ -2386,20 +2880,31 @@ struct monst *mon, /* monster being split */ reason[0] = '\0'; if (mtmp) Sprintf(reason, " from %s heat", - (mtmp == &youmonst) ? the_your[1] + (mtmp == &gy.youmonst) ? the_your[1] : (const char *) s_suffix(mon_nam(mtmp))); - if (mon == &youmonst) { - mtmp2 = cloneu(); + if (mon == &gy.youmonst) { + if (u.mh > u.mhmax) /* sanity precaution */ + u.mh = u.mhmax; + mtmp2 = (u.mh > 1) ? cloneu() : (struct monst *) 0; if (mtmp2) { + /* mtmp2 has been created with mhpmax = u.mhmax, mhp = u.mh / 2, + and u.mh -= mtmp2->mhp; these reductions for both max hp + can't make either of them exceed corresponding current hp */ mtmp2->mhpmax = u.mhmax / 2; u.mhmax -= mtmp2->mhpmax; - context.botl = 1; + disp.botl = TRUE; You("multiply%s!", reason); } } else { - mtmp2 = clone_mon(mon, 0, 0); + if (mon->mhp > mon->mhpmax) /* sanity precaution */ + mon->mhp = mon->mhpmax; + mtmp2 = (mon->mhp > 1) ? clone_mon(mon, 0, 0) : (struct monst *) 0; if (mtmp2) { + assert(mon->mhpmax >= mon->mhp); /* mon->mhpmax > 1 */ + /* mtmp2 has been created with mhpmax = mon->mhpmax, + mhp = mon->mhp / 2, and mon->mh -= mtmp2->mhp; + dividing max by 2 can't result in it exceeding current */ mtmp2->mhpmax = mon->mhpmax / 2; mon->mhpmax -= mtmp2->mhpmax; if (canspotmon(mon)) @@ -2409,4 +2914,17 @@ struct monst *mon, /* monster being split */ return mtmp2; } +/* Character becomes very fast temporarily. */ +void +speed_up(long duration) +{ + if (!Very_fast) + You("are suddenly moving %sfaster.", Fast ? "" : "much "); + else + Your("%s get new energy.", makeplural(body_part(LEG))); + + exercise(A_DEX, TRUE); + incr_itimeout(&HFast, duration); +} + /*potion.c*/ diff --git a/src/pray.c b/src/pray.c index 79369fc02..a00f81a9f 100644 --- a/src/pray.c +++ b/src/pray.c @@ -1,25 +1,39 @@ -/* NetHack 3.6 pray.c $NHDT-Date: 1573346192 2019/11/10 00:36:32 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.118 $ */ +/* NetHack 5.0 pray.c $NHDT-Date: 1762680996 2025/11/09 01:36:36 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.244 $ */ /* Copyright (c) Benson I. Margulies, Mike Stephenson, Steve Linhart, 1989. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -STATIC_PTR int NDECL(prayer_done); -STATIC_DCL struct obj *NDECL(worst_cursed_item); -STATIC_DCL int NDECL(in_trouble); -STATIC_DCL void FDECL(fix_worst_trouble, (int)); -STATIC_DCL void FDECL(angrygods, (ALIGNTYP_P)); -STATIC_DCL void FDECL(at_your_feet, (const char *)); -STATIC_DCL void NDECL(gcrownu); -STATIC_DCL void FDECL(pleased, (ALIGNTYP_P)); -STATIC_DCL void FDECL(godvoice, (ALIGNTYP_P, const char *)); -STATIC_DCL void FDECL(god_zaps_you, (ALIGNTYP_P)); -STATIC_DCL void FDECL(fry_by_god, (ALIGNTYP_P, BOOLEAN_P)); -STATIC_DCL void FDECL(gods_angry, (ALIGNTYP_P)); -STATIC_DCL void FDECL(gods_upset, (ALIGNTYP_P)); -STATIC_DCL void FDECL(consume_offering, (struct obj *)); -STATIC_DCL boolean FDECL(water_prayer, (BOOLEAN_P)); -STATIC_DCL boolean FDECL(blocked_boulder, (int, int)); +staticfn int prayer_done(void); +staticfn void maybe_turn_mon_iter(struct monst *); +staticfn struct obj *worst_cursed_item(void); +staticfn int in_trouble(void); +staticfn void fix_curse_trouble(struct obj *, const char *); +staticfn void fix_worst_trouble(int); +staticfn void angrygods(aligntyp); +staticfn void at_your_feet(const char *); +staticfn void gcrownu(void); +staticfn void give_spell(void); +staticfn void pleased(aligntyp); +staticfn void godvoice(aligntyp, const char *); +staticfn void god_zaps_you(aligntyp); +staticfn void fry_by_god(aligntyp, boolean); +staticfn void gods_angry(aligntyp); +staticfn void gods_upset(aligntyp); +staticfn void consume_offering(struct obj *); +staticfn void offer_too_soon(aligntyp); +staticfn void offer_real_amulet(struct obj *, aligntyp); /* NORETURN */ +staticfn void offer_negative_valued(boolean, aligntyp); +staticfn void offer_fake_amulet(struct obj *, boolean, aligntyp); +staticfn void offer_different_alignment_altar(struct obj *, aligntyp); +staticfn void sacrifice_your_race(struct obj *, boolean, aligntyp); +staticfn int bestow_artifact(uchar); +staticfn int sacrifice_value(struct obj *); +staticfn int eval_offering(struct obj *, aligntyp); +staticfn void offer_corpse(struct obj *, boolean, aligntyp); +staticfn boolean pray_revive(void); +staticfn boolean water_prayer(boolean); +staticfn boolean blocked_boulder(int, int); /* simplify a few tests */ #define Cursed_obj(obj, typ) ((obj) && (obj)->otyp == (typ) && (obj)->cursed) @@ -41,17 +55,12 @@ STATIC_DCL boolean FDECL(blocked_boulder, (int, int)); * responsible for the theft of the Amulet from Marduk, the Creator. * Moloch is unaligned. */ -static const char *Moloch = "Moloch"; +static const char *const Moloch = "Moloch"; -static const char *godvoices[] = { +static const char *const godvoices[] = { "booms out", "thunders", "rings out", "booms", }; -/* values calculated when prayer starts, and used when completed */ -static aligntyp p_aligntyp; -static int p_trouble; -static int p_type; /* (-1)-3: (-1)=really naughty, 3=really good */ - #define PIOUS 20 #define DEVOUT 14 #define FERVENT 9 @@ -97,13 +106,19 @@ static int p_type; /* (-1)-3: (-1)=really naughty, 3=really good */ #define on_shrine() ((levl[u.ux][u.uy].altarmask & AM_SHRINE) != 0) #define a_align(x, y) ((aligntyp) Amask2align(levl[x][y].altarmask & AM_MASK)) +/* used by turn undead iteration function; always reinitialized + before iterating that, so don't need to be globals */ +static int turn_undead_range; +static int turn_undead_msg_cnt; + /* critically low hit points if hp <= 5 or hp <= maxhp/N for some N */ boolean -critically_low_hp(only_if_injured) -boolean only_if_injured; /* determines whether maxhp <= 5 matters */ +critically_low_hp( + boolean only_if_injured) /* determines whether maxhp <= 5 matters */ { - int divisor, hplim, curhp = Upolyd ? u.mh : u.uhp, - maxhp = Upolyd ? u.mhmax : u.uhpmax; + int divisor, hplim, + curhp = Upolyd ? u.mh : u.uhp, + maxhp = Upolyd ? u.mhmax : u.uhpmax; if (only_if_injured && !(curhp < maxhp)) return FALSE; @@ -143,7 +158,7 @@ boolean only_if_injured; /* determines whether maxhp <= 5 matters */ /* return True if surrounded by impassible rock, regardless of the state of your own location (for example, inside a doorless closet) */ boolean -stuck_in_wall() +stuck_in_wall(void) { int i, j, x, y, count = 0; @@ -156,9 +171,9 @@ stuck_in_wall() continue; y = u.uy + j; if (!isok(x, y) - || (IS_ROCK(levl[x][y].typ) + || (IS_OBSTRUCTED(levl[x][y].typ) && (levl[x][y].typ != SDOOR && levl[x][y].typ != SCORR)) - || (blocked_boulder(i, j) && !throws_rocks(youmonst.data))) + || (blocked_boulder(i, j) && !throws_rocks(gy.youmonst.data))) ++count; } } @@ -179,8 +194,8 @@ stuck_in_wall() * 3.4.2: make an exception if polymorphed into a form which lacks * hands; that's a case where the ramifications override this doubt. */ -STATIC_OVL int -in_trouble() +staticfn int +in_trouble(void) { struct obj *otmp; int i; @@ -202,9 +217,9 @@ in_trouble() return TROUBLE_STARVING; if (region_danger()) return TROUBLE_REGION; - if (critically_low_hp(FALSE)) + if ((!Upolyd || Unchanging) && critically_low_hp(FALSE)) return TROUBLE_HIT; - if (u.ulycn >= LOW_PM) + if (ismnum(u.ulycn)) return TROUBLE_LYCANTHROPE; if (near_capacity() >= EXT_ENCUMBER && AMAX(A_STR) - ABASE(A_STR) > 3) return TROUBLE_COLLAPSING; @@ -214,13 +229,13 @@ in_trouble() || stuck_ring(uleft, RIN_LEVITATION) || stuck_ring(uright, RIN_LEVITATION)) return TROUBLE_CURSED_LEVITATION; - if (nohands(youmonst.data) || !freehand()) { + if (nohands(gy.youmonst.data) || !freehand()) { /* for bag/box access [cf use_container()]... make sure it's a case that we know how to handle; otherwise "fix all troubles" would get stuck in a loop */ if (welded(uwep)) return TROUBLE_UNUSEABLE_HANDS; - if (Upolyd && nohands(youmonst.data) + if (Upolyd && nohands(gy.youmonst.data) && (!Unchanging || ((otmp = unchanger()) != 0 && otmp->cursed))) return TROUBLE_UNUSEABLE_HANDS; } @@ -243,11 +258,11 @@ in_trouble() return TROUBLE_SADDLE; } - if (Blinded > 1 && haseyes(youmonst.data) + if (BlindedTimeout > 1L && !(HBlinded & ~TIMEOUT) && (!u.uswallow || !attacktype_fordmg(u.ustuck->data, AT_ENGL, AD_BLND))) return TROUBLE_BLIND; - /* deafness isn't it's own trouble; healing magic cures deafness + /* deafness isn't its own trouble; healing magic cures deafness when it cures blindness, so do the same with trouble repair */ if ((HDeaf & TIMEOUT) > 1L) return TROUBLE_BLIND; @@ -269,14 +284,14 @@ in_trouble() } /* select an item for TROUBLE_CURSED_ITEMS */ -STATIC_OVL struct obj * -worst_cursed_item() +staticfn struct obj * +worst_cursed_item(void) { - register struct obj *otmp; + struct obj *otmp; /* if strained or worse, check for loadstone first */ if (near_capacity() >= HVY_ENCUMBER) { - for (otmp = invent; otmp; otmp = otmp->nobj) + for (otmp = gi.invent; otmp; otmp = otmp->nobj) if (Cursed_obj(otmp, LOADSTONE)) return otmp; } @@ -320,7 +335,7 @@ worst_cursed_item() otmp = uswapwep; /* all worn items ought to be handled by now */ } else { - for (otmp = invent; otmp; otmp = otmp->nobj) { + for (otmp = gi.invent; otmp; otmp = otmp->nobj) { if (!otmp->cursed) continue; if (otmp->otyp == LOADSTONE || confers_luck(otmp)) @@ -330,11 +345,34 @@ worst_cursed_item() return otmp; } -STATIC_OVL void -fix_worst_trouble(trouble) -int trouble; +staticfn void +fix_curse_trouble(struct obj *otmp, const char *what) { - int i; + if (!otmp) { + impossible("fix_curse_trouble: nothing to uncurse."); + return; + } + if (otmp == uarmg && Glib) { + make_glib(0); + Your("%s are no longer slippery.", gloves_simple_name(uarmg)); + if (!otmp->cursed) + return; + } + if (!Blind || (otmp == ublindf && Blindfolded_only)) { + pline("%s %s.", + what ? what : (const char *) Yobjnam2(otmp, "softly glow"), + hcolor(NH_AMBER)); + iflags.last_msg = PLNMSG_OBJ_GLOWS; + otmp->bknown = !Hallucination; /* ok to skip set_bknown() */ + } + uncurse(otmp); + update_inventory(); +} + +staticfn void +fix_worst_trouble(int trouble) +{ + int i, maxhp; struct obj *otmp = 0; const char *what = (const char *) 0; static NEARDATA const char leftglow[] = "Your left ring softly glows", @@ -354,21 +392,23 @@ int trouble; } You("can breathe again."); Strangled = 0; - context.botl = 1; + disp.botl = TRUE; break; case TROUBLE_LAVA: - You("are back on solid ground."); /* teleport should always succeed, but if not, just untrap them */ - if (!safe_teleds(FALSE)) + if (!safe_teleds(TELEDS_NO_FLAGS)) reset_utrap(TRUE); + rescued_from_terrain(DISSOLVED); /* DISSOLVED: pending cause of death + * if trouble didn't get cured */ break; case TROUBLE_STARVING: /* temporarily lost strength recovery now handled by init_uhunger() */ - /*FALLTHRU*/ + FALLTHROUGH; + /* FALLTHRU*/ case TROUBLE_HUNGRY: Your("%s feels content.", body_part(STOMACH)); init_uhunger(); - context.botl = 1; + disp.botl = TRUE; break; case TROUBLE_SICK: You_feel("better."); @@ -384,39 +424,43 @@ int trouble; boosted to be more than that */ You_feel("much better."); if (Upolyd) { - u.mhmax += rnd(5); - if (u.mhmax <= 5) - u.mhmax = 5 + 1; + maxhp = u.mhmax + rnd(5); + setuhpmax(max(maxhp, 5 + 1), FALSE); /* acts as setmhmax() */ u.mh = u.mhmax; } - if (u.uhpmax < u.ulevel * 5 + 11) - u.uhpmax += rnd(5); - if (u.uhpmax <= 5) - u.uhpmax = 5 + 1; - u.uhp = u.uhpmax; - context.botl = 1; + maxhp = u.uhpmax; + if (maxhp < u.ulevel * 5 + 11) + maxhp += rnd(5); + /* True: update u.uhpmax even if currently poly'd */ + setuhpmax(max(maxhp, 5 + 1), TRUE); + u.uhp = u.uhpmax; /* setuhpmax() will do this when u.uhp is higher + * than u.uhpmax; prayer also does this if lower */ + disp.botl = TRUE; break; case TROUBLE_COLLAPSING: /* override Fixed_abil; uncurse that if feasible */ You_feel("%sstronger.", (AMAX(A_STR) - ABASE(A_STR) > 6) ? "much " : ""); ABASE(A_STR) = AMAX(A_STR); - context.botl = 1; + disp.botl = TRUE; if (Fixed_abil) { if ((otmp = stuck_ring(uleft, RIN_SUSTAIN_ABILITY)) != 0) { if (otmp == uleft) what = leftglow; - } else if ((otmp = stuck_ring(uright, RIN_SUSTAIN_ABILITY)) != 0) { + } else if ((otmp = stuck_ring(uright, RIN_SUSTAIN_ABILITY)) + != 0) { if (otmp == uright) what = rightglow; } - if (otmp) - goto decurse; + if (otmp) { + fix_curse_trouble(otmp, what); + break; + } } break; case TROUBLE_STUCK_IN_WALL: /* no control, but works on no-teleport levels */ - if (safe_teleds(FALSE)) { + if (safe_teleds(TELEDS_NO_FLAGS)) { Your("surroundings change."); } else { /* safe_teleds() couldn't find a safe place; perhaps the @@ -442,27 +486,31 @@ int trouble; if (otmp == uright) what = rightglow; } - goto decurse; + fix_curse_trouble(otmp, what); + break; case TROUBLE_UNUSEABLE_HANDS: if (welded(uwep)) { otmp = uwep; - goto decurse; + fix_curse_trouble(otmp, what); + break; } - if (Upolyd && nohands(youmonst.data)) { + if (Upolyd && nohands(gy.youmonst.data)) { if (!Unchanging) { Your("shape becomes uncertain."); rehumanize(); /* "You return to {normal} form." */ } else if ((otmp = unchanger()) != 0 && otmp->cursed) { /* otmp is an amulet of unchanging */ - goto decurse; + fix_curse_trouble(otmp, what); + break; } } - if (nohands(youmonst.data) || !freehand()) + if (nohands(gy.youmonst.data) || !freehand()) impossible("fix_worst_trouble: couldn't cure hands."); break; case TROUBLE_CURSED_BLINDFOLD: otmp = ublindf; - goto decurse; + fix_curse_trouble(otmp, what); + break; case TROUBLE_LYCANTHROPE: you_unwere(TRUE); break; @@ -480,8 +528,7 @@ int trouble; otmp = uarmg; else if (Cursed_obj(uarmf, FUMBLE_BOOTS)) otmp = uarmf; - goto decurse; - /*NOTREACHED*/ + fix_curse_trouble(otmp, what); break; case TROUBLE_CURSED_ITEMS: otmp = worst_cursed_item(); @@ -489,26 +536,7 @@ int trouble; what = rightglow; else if (otmp == uleft) what = leftglow; - decurse: - if (!otmp) { - impossible("fix_worst_trouble: nothing to uncurse."); - return; - } - if (otmp == uarmg && Glib) { - make_glib(0); - Your("%s are no longer slippery.", gloves_simple_name(uarmg)); - if (!otmp->cursed) - break; - } - if (!Blind || (otmp == ublindf && Blindfolded_only)) { - pline("%s %s.", - what ? what : (const char *) Yobjnam2(otmp, "softly glow"), - hcolor(NH_AMBER)); - iflags.last_msg = PLNMSG_OBJ_GLOWS; - otmp->bknown = !Hallucination; /* ok to skip set_bknown() */ - } - uncurse(otmp); - update_inventory(); + fix_curse_trouble(otmp, what); break; case TROUBLE_POISONED: /* override Fixed_abil; ignore items which confer that */ @@ -519,10 +547,10 @@ int trouble; for (i = 0; i < A_MAX; i++) { if (ABASE(i) < AMAX(i)) { ABASE(i) = AMAX(i); - context.botl = 1; + disp.botl = TRUE; } } - (void) encumber_msg(); + encumber_msg(); break; case TROUBLE_BLIND: { /* handles deafness as well as blindness */ char msgbuf[BUFSZ]; @@ -531,7 +559,7 @@ int trouble; msgbuf[0] = '\0'; if (Blinded) { - if (eyecount(youmonst.data) != 1) + if (eyecount(gy.youmonst.data) != 1) eyes = makeplural(eyes); Sprintf(msgbuf, "Your %s %s better", eyes, vtense(eyes, "feel")); u.ucreamed = 0; @@ -578,9 +606,8 @@ int trouble; * bathroom walls, but who is foiled by bathrobes." --Bertrand Russell, 1943 * Divine wrath, dungeon walls, and armor follow the same principle. */ -STATIC_OVL void -god_zaps_you(resp_god) -aligntyp resp_god; +staticfn void +god_zaps_you(aligntyp resp_god) { if (u.uswallow) { pline( @@ -604,11 +631,16 @@ aligntyp resp_god; pline("For some reason you're unaffected."); else (void) ureflects("%s reflects from your %s.", "It"); + monstseesu(M_SEEN_REFL); } else if (Shock_resistance) { shieldeff(u.ux, u.uy); pline("It seems not to affect you."); - } else + monstseesu(M_SEEN_ELEC); + monstunseesu(M_SEEN_REFL); + } else { fry_by_god(resp_god, FALSE); + monstunseesu(M_SEEN_REFL | M_SEEN_ELEC); + } } pline("%s is not deterred...", align_gname(resp_god)); @@ -628,53 +660,54 @@ aligntyp resp_god; */ if (uarms && !(EReflecting & W_ARMS) && !(EDisint_resistance & W_ARMS)) - (void) destroy_arm(uarms); + (void) disintegrate_arm(uarms); if (uarmc && !(EReflecting & W_ARMC) && !(EDisint_resistance & W_ARMC)) - (void) destroy_arm(uarmc); + (void) disintegrate_arm(uarmc); if (uarm && !(EReflecting & W_ARM) && !(EDisint_resistance & W_ARM) && !uarmc) - (void) destroy_arm(uarm); + (void) disintegrate_arm(uarm); if (uarmu && !uarm && !uarmc) - (void) destroy_arm(uarmu); + (void) disintegrate_arm(uarmu); if (!Disint_resistance) { fry_by_god(resp_god, TRUE); + monstunseesu(M_SEEN_DISINT); } else { You("bask in its %s glow for a minute...", NH_BLACK); godvoice(resp_god, "I believe it not!"); + monstseesu(M_SEEN_DISINT); } if (Is_astralevel(&u.uz) || Is_sanctum(&u.uz)) { /* one more try for high altars */ + SetVoice((struct monst *) 0, 0, 80, voice_deity); verbalize("Thou cannot escape my wrath, mortal!"); summon_minion(resp_god, FALSE); summon_minion(resp_god, FALSE); summon_minion(resp_god, FALSE); + SetVoice((struct monst *) 0, 0, 80, voice_deity); verbalize("Destroy %s, my servants!", uhim()); } } } -STATIC_OVL void -fry_by_god(resp_god, via_disintegration) -aligntyp resp_god; -boolean via_disintegration; +staticfn void +fry_by_god(aligntyp resp_god, boolean via_disintegration) { You("%s!", !via_disintegration ? "fry to a crisp" : "disintegrate into a pile of dust"); - killer.format = KILLED_BY; - Sprintf(killer.name, "the wrath of %s", align_gname(resp_god)); + svk.killer.format = KILLED_BY; + Sprintf(svk.killer.name, "the wrath of %s", align_gname(resp_god)); done(DIED); } -STATIC_OVL void -angrygods(resp_god) -aligntyp resp_god; +staticfn void +angrygods(aligntyp resp_god) { - int maxanger; + int maxanger, new_ublesscnt; if (Inhell) resp_god = A_NONE; - u.ublessed = 0; + u.ublessed = 0; /* lose divine protection */ /* changed from tmp = u.ugangr + abs (u.uluck) -- rph */ /* added test for alignment diff -dlc */ @@ -702,7 +735,8 @@ aligntyp resp_god; (ugod_is_angry() && resp_god == u.ualign.type) ? "hast strayed from the path" : "art arrogant", - youmonst.data->mlet == S_HUMAN ? "mortal" : "creature"); + gy.youmonst.data->mlet == S_HUMAN ? "mortal" : "creature"); + SetVoice((struct monst *) 0, 0, 80, voice_deity); verbalize("Thou must relearn thy lessons!"); (void) adjattrib(A_WIS, -1, FALSE); losexp((char *) 0); @@ -712,24 +746,28 @@ aligntyp resp_god; gods_angry(resp_god); punish((struct obj *) 0); break; - } /* else fall thru */ + } + FALLTHROUGH; + /* FALLTHRU */ case 4: case 5: gods_angry(resp_god); if (!Blind && !Antimagic) pline("%s glow surrounds you.", An(hcolor(NH_BLACK))); - rndcurse(); + if (rn2(2) || !attrcurse()) + rndcurse(); break; case 7: case 8: godvoice(resp_god, (char *) 0); + SetVoice((struct monst *) 0, 0, 80, voice_deity); verbalize("Thou durst %s me?", (on_altar() && (a_align(u.ux, u.uy) != resp_god)) ? "scorn" : "call upon"); /* [why isn't this using verbalize()?] */ pline("\"Then die, %s!\"", - (youmonst.data->mlet == S_HUMAN) ? "mortal" : "creature"); + (gy.youmonst.data->mlet == S_HUMAN) ? "mortal" : "creature"); summon_minion(resp_god, FALSE); break; @@ -738,14 +776,16 @@ aligntyp resp_god; god_zaps_you(resp_god); break; } - u.ublesscnt = rnz(300); + /* even though this might not be in response to prayer, set pray timer */ + new_ublesscnt = rnz(300); + if (new_ublesscnt > u.ublesscnt) + u.ublesscnt = new_ublesscnt; return; } /* helper to print "str appears at your feet", or appropriate */ -static void -at_your_feet(str) -const char *str; +staticfn void +at_your_feet(const char *str) { if (Blind) str = Something; @@ -755,18 +795,19 @@ const char *str; s_suffix(mon_nam(u.ustuck)), mbodypart(u.ustuck, STOMACH)); } else { pline("%s %s %s your %s!", str, - Blind ? "lands" : vtense(str, "appear"), - Levitation ? "beneath" : "at", makeplural(body_part(FOOT))); + vtense(str, Blind ? "land" : "appear"), + Levitation ? "beneath" : "at", + makeplural(body_part(FOOT))); } } -STATIC_OVL void -gcrownu() +staticfn void +gcrownu(void) { struct obj *obj; + const char *what; boolean already_exists, in_hand; short class_gift; - int sp_no; #define ok_wep(o) ((o) && ((o)->oclass == WEAPON_CLASS || is_weptool(o))) HSee_invisible |= FROMOUTSIDE; @@ -781,8 +822,8 @@ gcrownu() /* 3.3.[01] had this in the A_NEUTRAL case, preventing chaotic wizards from receiving a spellbook */ if (Role_if(PM_WIZARD) - && (!uwep || (uwep->oartifact != ART_VORPAL_BLADE - && uwep->oartifact != ART_STORMBRINGER)) + && !u_wield_art(ART_VORPAL_BLADE) + && !u_wield_art(ART_STORMBRINGER) && !carrying(SPE_FINGER_OF_DEATH)) { class_gift = SPE_FINGER_OF_DEATH; } else if (Role_if(PM_MONK) && (!uwep || !uwep->oartifact) @@ -796,42 +837,61 @@ gcrownu() switch (u.ualign.type) { case A_LAWFUL: u.uevent.uhand_of_elbereth = 1; + SetVoice((struct monst *) 0, 0, 80, voice_deity); verbalize("I crown thee... The Hand of Elbereth!"); + livelog_printf(LL_DIVINEGIFT, + "was crowned \"The Hand of Elbereth\" by %s", + u_gname()); break; case A_NEUTRAL: u.uevent.uhand_of_elbereth = 2; - in_hand = (uwep && uwep->oartifact == ART_VORPAL_BLADE); - already_exists = - exist_artifact(LONG_SWORD, artiname(ART_VORPAL_BLADE)); + in_hand = u_wield_art(ART_VORPAL_BLADE); + already_exists = exist_artifact(LONG_SWORD, + artiname(ART_VORPAL_BLADE)); + SetVoice((struct monst *) 0, 0, 80, voice_deity); verbalize("Thou shalt be my Envoy of Balance!"); + livelog_printf(LL_DIVINEGIFT, "became %s Envoy of Balance", + s_suffix(u_gname())); break; case A_CHAOTIC: u.uevent.uhand_of_elbereth = 3; - in_hand = (uwep && uwep->oartifact == ART_STORMBRINGER); - already_exists = - exist_artifact(RUNESWORD, artiname(ART_STORMBRINGER)); - verbalize("Thou art chosen to %s for My Glory!", - ((already_exists && !in_hand) - || class_gift != STRANGE_OBJECT) ? "take lives" - : "steal souls"); + in_hand = u_wield_art(ART_STORMBRINGER); + already_exists = exist_artifact(RUNESWORD, + artiname(ART_STORMBRINGER)); + what = (((already_exists && !in_hand) || class_gift != STRANGE_OBJECT) + ? "take lives" + : "steal souls"); + SetVoice((struct monst *) 0, 0, 80, voice_deity); + verbalize("Thou art chosen to %s for My Glory!", what); + livelog_printf(LL_DIVINEGIFT, "was chosen to %s for the Glory of %s", + what, u_gname()); break; } if (objects[class_gift].oc_class == SPBOOK_CLASS) { + char bbuf[BUFSZ]; + obj = mksobj(class_gift, TRUE, FALSE); + /* get book type before dropping (don't think that could destroy + the book because we need to be on an altar in order to become + crowned, but be paranoid about it) */ + Strcpy(bbuf, actualoname(obj)); /* for livelog; "spellbook of " + * even if hero doesn't know book */ bless(obj); obj->bknown = 1; /* ok to skip set_bknown() */ - at_your_feet("A spellbook"); + observe_object(obj); + at_your_feet(upstart(ansimpleoname(obj))); dropy(obj); u.ugifts++; + /* not an artifact, but treat like one for this situation; + classify as a spoiler in case player hasn't IDed the book yet */ + livelog_printf(LL_DIVINEGIFT | LL_ARTIFACT | LL_SPOILER, + "was bestowed with %s", bbuf); + /* when getting a new book for known spell, enhance currently wielded weapon rather than the book */ - for (sp_no = 0; sp_no < MAXSPELL; sp_no++) - if (spl_book[sp_no].sp_id == class_gift) { - if (ok_wep(uwep)) - obj = uwep; /* to be blessed,&c */ - break; - } + if (known_spell(class_gift) != spe_Unknown && ok_wep(uwep)) + obj = uwep; /* to be blessed,&c */ } switch (u.ualign.type) { @@ -839,15 +899,23 @@ gcrownu() if (class_gift != STRANGE_OBJECT) { ; /* already got bonus above */ } else if (obj && obj->otyp == LONG_SWORD && !obj->oartifact) { + char lbuf[BUFSZ]; + + Strcpy(lbuf, simpleonames(obj)); /* before transformation */ if (!Blind) Your("sword shines brightly for a moment."); - obj = oname(obj, artiname(ART_EXCALIBUR)); - if (obj && obj->oartifact == ART_EXCALIBUR) + obj = oname(obj, artiname(ART_EXCALIBUR), + ONAME_GIFT | ONAME_KNOW_ARTI); + if (is_art(obj, ART_EXCALIBUR)) { u.ugifts++; + livelog_printf(LL_DIVINEGIFT | LL_ARTIFACT, + "had %s wielded %s transformed into %s", + uhis(), lbuf, artiname(ART_EXCALIBUR)); + } } /* acquire Excalibur's skill regardless of weapon or gift */ unrestrict_weapon_skill(P_LONG_SWORD); - if (obj && obj->oartifact == ART_EXCALIBUR) + if (is_art(obj, ART_EXCALIBUR)) discover_artifact(ART_EXCALIBUR); break; case A_NEUTRAL: @@ -855,18 +923,22 @@ gcrownu() ; /* already got bonus above */ } else if (obj && in_hand) { Your("%s goes snicker-snack!", xname(obj)); - obj->dknown = TRUE; + observe_object(obj); } else if (!already_exists) { obj = mksobj(LONG_SWORD, FALSE, FALSE); - obj = oname(obj, artiname(ART_VORPAL_BLADE)); + obj = oname(obj, artiname(ART_VORPAL_BLADE), + ONAME_GIFT | ONAME_KNOW_ARTI); obj->spe = 1; at_your_feet("A sword"); dropy(obj); u.ugifts++; + livelog_printf(LL_DIVINEGIFT | LL_ARTIFACT, + "was bestowed with %s", + artiname(ART_VORPAL_BLADE)); } /* acquire Vorpal Blade's skill regardless of weapon or gift */ unrestrict_weapon_skill(P_LONG_SWORD); - if (obj && obj->oartifact == ART_VORPAL_BLADE) + if (is_art(obj, ART_VORPAL_BLADE)) discover_artifact(ART_VORPAL_BLADE); break; case A_CHAOTIC: { @@ -877,18 +949,22 @@ gcrownu() ; /* already got bonus above */ } else if (obj && in_hand) { Your("%s hums ominously!", swordbuf); - obj->dknown = TRUE; + observe_object(obj); } else if (!already_exists) { obj = mksobj(RUNESWORD, FALSE, FALSE); - obj = oname(obj, artiname(ART_STORMBRINGER)); + obj = oname(obj, artiname(ART_STORMBRINGER), + ONAME_GIFT | ONAME_KNOW_ARTI); obj->spe = 1; at_your_feet(An(swordbuf)); dropy(obj); u.ugifts++; + livelog_printf(LL_DIVINEGIFT | LL_ARTIFACT, + "was bestowed with %s", + artiname(ART_STORMBRINGER)); } /* acquire Stormbringer's skill regardless of weapon or gift */ unrestrict_weapon_skill(P_BROAD_SWORD); - if (obj && obj->oartifact == ART_STORMBRINGER) + if (is_art(obj, ART_STORMBRINGER)) discover_artifact(ART_STORMBRINGER); break; } @@ -919,9 +995,80 @@ gcrownu() return; } -STATIC_OVL void -pleased(g_align) -aligntyp g_align; +staticfn void +give_spell(void) +{ + struct obj *otmp; + char spe_let; + int spe_knowledge, trycnt = u.ulevel + 1; + + /* not yet known spells and forgotten spells are given preference over + usable ones; also, try to grant spell that hero could gain skill in + (even though being restricted doesn't prevent learning and casting) */ + otmp = mkobj(SPBOOK_no_NOVEL, TRUE); + while (--trycnt > 0) { + if (otmp->otyp != SPE_BLANK_PAPER) { + if (known_spell(otmp->otyp) <= spe_Unknown + && !P_RESTRICTED(spell_skilltype(otmp->otyp))) + break; /* forgotten or not yet known */ + } else { + /* blank paper is acceptable if not discovered yet or + if hero has a magic marker to write something on it + (doesn't matter if marker is out of charges); it will + become discovered (below) without needing to be read */ + if (!objects[SPE_BLANK_PAPER].oc_name_known + || carrying(MAGIC_MARKER)) + break; + } + otmp->otyp = rnd_class(svb.bases[SPBOOK_CLASS], SPE_BLANK_PAPER); + } + /* + * 25% chance of learning the spell directly instead of + * receiving the book for it, unless it's already well known. + * The chance is not influenced by whether hero is illiterate. + */ + if (otmp->otyp != SPE_BLANK_PAPER && !rn2(4) + && (spe_knowledge = known_spell(otmp->otyp)) != spe_Fresh) { + /* force_learn_spell() should only return '\0' if the book + is blank paper or the spell is known and has retention + of spe_Fresh, so no 'else' case is needed here */ + if ((spe_let = force_learn_spell(otmp->otyp)) != '\0') { + /* for spellbook class, OBJ_NAME() yields the name of + the spell rather than "spellbook of " */ + const char *spe_name = OBJ_NAME(objects[otmp->otyp]); + + if (spe_knowledge == spe_Unknown) /* prior to learning */ + /* appending "spell 'a'" seems slightly silly but + is similar to "added to your repertoire, as 'a'" + and without any spellbook on hand a novice player + might not recognize that 'spe_name' is a spell */ + pline("Divine knowledge of %s fills your mind! Spell '%c'.", + spe_name, spe_let); + else + Your("knowledge of spell '%c' - %s is %s.", + spe_let, spe_name, + (spe_knowledge == spe_Forgotten) ? "restored" + : "refreshed"); + } + obfree(otmp, (struct obj *) 0); /* discard the book */ + } else { + observe_object(otmp); + /* don't set bknown */ + /* discovering blank paper will make it less likely to + be given again; small chance to arbitrarily discover + some other book type without having to read it first */ + if (otmp->otyp == SPE_BLANK_PAPER || !rn2(100)) + makeknown(otmp->otyp); + bless(otmp); + at_your_feet(upstart(ansimpleoname(otmp))); + place_object(otmp, u.ux, u.uy); + newsym(u.ux, u.uy); + } + return; +} + +staticfn void +pleased(aligntyp g_align) { /* don't use p_trouble, worst trouble may get fixed while praying */ int trouble = in_trouble(); /* what's your worst difficulty? */ @@ -935,7 +1082,7 @@ aligntyp g_align; : Hallucination ? "full" : "satisfied"); /* not your deity */ - if (on_altar() && p_aligntyp != u.ualign.type) { + if (on_altar() && gp.p_aligntyp != u.ualign.type) { adjalign(-1); return; } else if (u.ualign.record < 2 && trouble <= 0) @@ -957,7 +1104,7 @@ aligntyp g_align; */ if (!trouble && u.ualign.record >= DEVOUT) { /* if hero was in trouble, but got better, no special favor */ - if (p_trouble == 0) + if (gp.p_trouble == 0) pat_on_head = 1; } else { int action, prayer_luck; @@ -985,6 +1132,7 @@ aligntyp g_align; switch (min(action, 5)) { case 5: pat_on_head = 1; + FALLTHROUGH; /*FALLTHRU*/ case 4: do @@ -993,9 +1141,12 @@ aligntyp g_align; break; case 3: + /* up to 10 troubles */ fix_worst_trouble(trouble); + FALLTHROUGH; + /*FALLTHRU*/ case 2: - /* arbitrary number of tries */ + /* up to 9 troubles */ while ((trouble = in_trouble()) > 0 && (++tryct < 10)) fix_worst_trouble(trouble); break; @@ -1003,6 +1154,7 @@ aligntyp g_align; case 1: if (trouble > 0) fix_worst_trouble(trouble); + break; case 0: break; /* your god blows you off, too bad */ } @@ -1071,20 +1223,25 @@ aligntyp g_align; if (!u.uevent.uopened_dbridge && !u.uevent.gehennom_entered) { if (u.uevent.uheard_tune < 1) { godvoice(g_align, (char *) 0); - verbalize("Hark, %s!", youmonst.data->mlet == S_HUMAN + SetVoice((struct monst *) 0, 0, 80, voice_deity); + verbalize("Hark, %s!", is_human(gy.youmonst.data) ? "mortal" : "creature"); + SetVoice((struct monst *) 0, 0, 80, voice_deity); verbalize( "To enter the castle, thou must play the right tune!"); u.uevent.uheard_tune++; break; } else if (u.uevent.uheard_tune < 2) { + Soundeffect(se_divine_music, 50); You_hear("a divine music..."); - pline("It sounds like: \"%s\".", tune); + pline("It sounds like: \"%s\".", svt.tune); u.uevent.uheard_tune++; + record_achievement(ACH_TUNE); break; } } + FALLTHROUGH; /*FALLTHRU*/ case 2: if (!Blind) @@ -1096,6 +1253,8 @@ aligntyp g_align; pluslvl(FALSE); } else { u.uhpmax += 5; + if (u.uhpmax > u.uhppeak) + u.uhppeak = u.uhpmax; if (Upolyd) u.mhmax += 5; } @@ -1104,8 +1263,8 @@ aligntyp g_align; u.mh = u.mhmax; if (ABASE(A_STR) < AMAX(A_STR)) { ABASE(A_STR) = AMAX(A_STR); - context.botl = 1; /* before potential message */ - (void) encumber_msg(); + disp.botl = TRUE; /* before potential message */ + encumber_msg(); } if (u.uhunger < 900) init_uhunger(); @@ -1119,17 +1278,18 @@ aligntyp g_align; rather than issuing a pat-on-head */ u.ucreamed = 0; make_blinded(0L, TRUE); - context.botl = 1; + disp.botl = TRUE; break; case 4: { - register struct obj *otmp; + struct obj *otmp, *nextobj; int any = 0; if (Blind) You_feel("the power of %s.", u_gname()); else You("are surrounded by %s aura.", an(hcolor(NH_LIGHT_BLUE))); - for (otmp = invent; otmp; otmp = otmp->nobj) { + for (otmp = gi.invent; otmp; otmp = nextobj) { + nextobj = otmp->nobj; if (otmp->cursed && (otmp != uarmh /* [see worst_cursed_item()] */ || uarmh->otyp != HELM_OF_OPPOSITE_ALIGNMENT)) { @@ -1173,6 +1333,7 @@ aligntyp g_align; u.ublessed++; pline(msg, "my protection"); } + SetVoice((struct monst *) 0, 0, 80, voice_deity); verbalize("Use it wisely in my name!"); break; } @@ -1182,36 +1343,11 @@ aligntyp g_align; gcrownu(); break; } + FALLTHROUGH; /*FALLTHRU*/ - case 6: { - struct obj *otmp; - int sp_no, trycnt = u.ulevel + 1; - - /* not yet known spells given preference over already known ones - */ - /* Also, try to grant a spell for which there is a skill slot */ - otmp = mkobj(SPBOOK_CLASS, TRUE); - while (--trycnt > 0) { - if (otmp->otyp != SPE_BLANK_PAPER) { - for (sp_no = 0; sp_no < MAXSPELL; sp_no++) - if (spl_book[sp_no].sp_id == otmp->otyp) - break; - if (sp_no == MAXSPELL - && !P_RESTRICTED(spell_skilltype(otmp->otyp))) - break; /* usable, but not yet known */ - } else { - if (!objects[SPE_BLANK_PAPER].oc_name_known - || carrying(MAGIC_MARKER)) - break; - } - otmp->otyp = rnd_class(bases[SPBOOK_CLASS], SPE_BLANK_PAPER); - } - bless(otmp); - at_your_feet("A spellbook"); - place_object(otmp, u.ux, u.uy); - newsym(u.ux, u.uy); + case 6: + give_spell(); break; - } default: impossible("Confused deity!"); break; @@ -1224,21 +1360,37 @@ aligntyp g_align; if (kick_on_butt) u.ublesscnt += kick_on_butt * rnz(1000); + /* Avoid games that go into infinite loops of copy-pasted commands + with no human interaction; this is a DoS vector against the + computer running NetHack. Once the turn counter is over 100000, + every additional 100 turns increases the prayer timeout by 1, + thus eventually hunger prayers will fail and some other source + of nutrition will be required. The increase gets throttled if + it ever reaches 32K so that configurations using 16-bit ints are + still viable. */ + if (svm.moves > 100000L) { + long incr = (svm.moves - 100000L) / 100L, + largest_ublesscnt_incr = (long) (LARGEST_INT - u.ublesscnt); + + if (incr > largest_ublesscnt_incr) + incr = largest_ublesscnt_incr; + u.ublesscnt += (int) incr; + } + return; } /* either blesses or curses water on the altar, * returns true if it found any water here. */ -STATIC_OVL boolean -water_prayer(bless_water) -boolean bless_water; +staticfn boolean +water_prayer(boolean bless_water) { - register struct obj *otmp; - register long changed = 0; + struct obj *otmp; + long changed = 0; boolean other = FALSE, bc_known = !(Blind || Hallucination); - for (otmp = level.objects[u.ux][u.uy]; otmp; otmp = otmp->nexthere) { + for (otmp = svl.level.objects[u.ux][u.uy]; otmp; otmp = otmp->nexthere) { /* turn water into (un)holy water */ if (otmp->otyp == POT_WATER && (bless_water ? !otmp->blessed : !otmp->cursed)) { @@ -1259,10 +1411,8 @@ boolean bless_water; return (boolean) (changed > 0L); } -STATIC_OVL void -godvoice(g_align, words) -aligntyp g_align; -const char *words; +staticfn void +godvoice(aligntyp g_align, const char *words) { const char *quot = ""; @@ -1272,20 +1422,18 @@ const char *words; words = ""; pline_The("voice of %s %s: %s%s%s", align_gname(g_align), - godvoices[rn2(SIZE(godvoices))], quot, words, quot); + ROLL_FROM(godvoices), quot, words, quot); } -STATIC_OVL void -gods_angry(g_align) -aligntyp g_align; +staticfn void +gods_angry(aligntyp g_align) { godvoice(g_align, "Thou hast angered me."); } /* The g_align god is upset with you. */ -STATIC_OVL void -gods_upset(g_align) -aligntyp g_align; +staticfn void +gods_upset(aligntyp g_align) { if (g_align == u.ualign.type) u.ugangr++; @@ -1294,9 +1442,8 @@ aligntyp g_align; angrygods(g_align); } -STATIC_OVL void -consume_offering(otmp) -register struct obj *otmp; +staticfn void +consume_offering(struct obj *otmp) { if (Hallucination) switch (rn2(3)) { @@ -1315,7 +1462,11 @@ register struct obj *otmp; Your("sacrifice disappears!"); else Your("sacrifice is consumed in a %s!", - u.ualign.type == A_LAWFUL ? "flash of light" : "burst of flame"); + (u.ualign.type == A_LAWFUL) + ? "flash of light" + : (u.ualign.type == A_NEUTRAL) + ? "plume of smoke" + : "burst of flame"); if (carried(otmp)) useup(otmp); else @@ -1323,570 +1474,824 @@ register struct obj *otmp; exercise(A_WIS, TRUE); } -int -dosacrifice() +/* feedback when attempting to offer the Amulet on a "low altar" (not one of + the high altars in the temples on the Astral Plane or Moloch's Sanctum) */ +staticfn void +offer_too_soon(aligntyp altaralign) { - static NEARDATA const char cloud_of_smoke[] = - "A cloud of %s smoke surrounds you..."; - register struct obj *otmp; - int value = 0, pm; - boolean highaltar; - aligntyp altaralign = a_align(u.ux, u.uy); - - if (!on_altar() || u.uswallow) { - You("are not standing on an altar."); - return 0; + if (altaralign == A_NONE && Inhell) { + /* offering on an unaligned altar in Gehennom; + hero has left Moloch's Sanctum (caller handles that) + so is in the process of getting away with the Amulet; + for any unaligned altar outside of Gehennom, give the + "you feel ashamed" feedback for wrong alignment below */ + gods_upset(A_NONE); /* Moloch becomes angry */ + return; } - highaltar = ((Is_astralevel(&u.uz) || Is_sanctum(&u.uz)) - && (levl[u.ux][u.uy].altarmask & AM_SHRINE)); + You_feel("%s.", Hallucination + ? "homesick" + /* if on track, give a big hint */ + : (altaralign == u.ualign.type) + ? "an urge to return to the surface" + /* else headed towards celestial disgrace */ + : "ashamed"); +} + +void +desecrate_altar(boolean highaltar, aligntyp altaralign) +{ + char gvbuf[BUFSZ]; - otmp = floorfood("sacrifice", 1); - if (!otmp) - return 0; /* - * Was based on nutritional value and aging behavior (< 50 moves). - * Sacrificing a food ration got you max luck instantly, making the - * gods as easy to please as an angry dog! - * - * Now only accepts corpses, based on the game's evaluation of their - * toughness. Human and pet sacrifice, as well as sacrificing unicorns - * of your alignment, is strongly discouraged. + * REAL BAD NEWS!!! High altars cannot be converted. Even an attempt + * gets the god who owns it truly pissed off. The same effect for + * deliberately destroying a normal altar. */ -#define MAXVALUE 24 /* Highest corpse value (besides Wiz) */ + /* if you did this to your own altar, your god will hold a grudge... */ + if (altaralign == u.ualign.type) { + adjalign(-20); + u.ugangr += 5; + } + You_feel("the air around you grow charged..."); + pline("Suddenly, you realize that %s has noticed you...", + align_gname(altaralign)); + Sprintf(gvbuf, "So, mortal! You dare desecrate my %s!", + highaltar ? "High Temple" : "altar"); + godvoice(altaralign, gvbuf); + /* Throw everything we have at the player */ + god_zaps_you(altaralign); +} - if (otmp->otyp == CORPSE) { - register struct permonst *ptr = &mons[otmp->corpsenm]; - struct monst *mtmp; +/* offering the Amulet on a high altar (checked by caller) ends the game; + we don't declare this 'NORETURN' because done() can return (if called + with some reasons other than ASCENDED and ESCAPED) */ +staticfn void +offer_real_amulet(struct obj *otmp, aligntyp altaralign) +{ + static NEARDATA const char + cloud_of_smoke[] = "A cloud of %s smoke surrounds you..."; - /* KMH, conduct */ - u.uconduct.gnostic++; + /* The final Test. Did you win? */ + if (uamul == otmp) + Amulet_off(); + if (carried(otmp)) + useup(otmp); /* well, it's gone now */ + else + useupf(otmp, 1L); - /* you're handling this corpse, even if it was killed upon the altar - */ - feel_cockatrice(otmp, TRUE); - if (rider_corpse_revival(otmp, FALSE)) - return 1; + You("offer the Amulet of Yendor to %s...", a_gname()); + + if (altaralign == A_NONE) { + /* Moloch's high altar at the bottom of Gehennom. */ + if (u.ualign.record > -99) + u.ualign.record = -99; + pline("An invisible choir chants, and you are bathed in darkness..."); + /*[apparently shrug/snarl can be sensed without being seen]*/ + pline("%s shrugs and retains dominion over %s,", Moloch, u_gname()); + pline("then mercilessly snuffs out your life."); + Sprintf(svk.killer.name, "%s indifference", s_suffix(Moloch)); + svk.killer.format = KILLED_BY; + done(DIED); + /* life-saved (or declined to die in wizard/explore mode) */ + pline("%s snarls and tries again...", Moloch); + fry_by_god(A_NONE, TRUE); /* wrath of Moloch */ + /* declined to die in wizard or explore mode */ + pline(cloud_of_smoke, hcolor(NH_BLACK)); + done(ESCAPED); + /*NOTREACHED*/ + } else if (u.ualign.type != altaralign) { + /* And the opposing team picks you up and carries you off + on their shoulders. */ + adjalign(-99); + pline("%s accepts your gift, and gains dominion over %s...", + a_gname(), u_gname()); + pline("%s is enraged...", u_gname()); + pline("Fortunately, %s permits you to live...", a_gname()); + pline(cloud_of_smoke, hcolor(NH_ORANGE)); + done(ESCAPED); + /*NOTREACHED*/ + } else { + /* You've won the game! Feedback-wise, it's a bit of a let down. */ + u.uevent.ascended = 1; + adjalign(10); + pline("An invisible choir sings, and you are bathed in radiance..."); + godvoice(altaralign, "Mortal, thou hast done well!"); + display_nhwindow(WIN_MESSAGE, FALSE); + SetVoice((struct monst *) 0, 0, 80, voice_deity); + verbalize( + "In return for thy service, I grant thee the gift of Immortality!"); + You("ascend to the status of Demigod%s...", + flags.female ? "dess" : ""); + done(ASCENDED); + /*NOTREACHED*/ + } + /*NOTREACHED*/ +} - if (otmp->corpsenm == PM_ACID_BLOB - || (monstermoves <= peek_at_iced_corpse_age(otmp) + 50)) { - value = mons[otmp->corpsenm].difficulty + 1; - if (otmp->oeaten) - value = eaten_stat(value, otmp); - } +staticfn void +offer_negative_valued(boolean highaltar, aligntyp altaralign) +{ + if (altaralign != u.ualign.type && highaltar) { + desecrate_altar(highaltar, altaralign); + } else { + gods_upset(altaralign); + } +} - if (your_race(ptr)) { - if (is_demon(youmonst.data)) { - You("find the idea very satisfying."); - exercise(A_WIS, TRUE); - } else if (u.ualign.type != A_CHAOTIC) { - pline("You'll regret this infamous offense!"); - exercise(A_WIS, FALSE); - } +staticfn void +offer_fake_amulet( + struct obj *otmp, + boolean highaltar, + aligntyp altaralign) +{ + if (!highaltar && !otmp->known) { + offer_too_soon(altaralign); + return; + } + Soundeffect(se_thunderclap, 100); + You_hear("a nearby thunderclap."); + if (!otmp->known) { + You("realize you have made a %s.", + Hallucination ? "boo-boo" : "mistake"); + otmp->known = TRUE; + change_luck(-1); + } else { + /* don't you dare try to fool the gods */ + if (Deaf) + pline("Oh, no."); /* didn't hear thunderclap */ + change_luck(-3); + adjalign(-1); + u.ugangr += 3; + offer_negative_valued(highaltar, altaralign); + } +} - if (highaltar - && (altaralign != A_CHAOTIC || u.ualign.type != A_CHAOTIC)) { - goto desecrate_high_altar; - } else if (altaralign != A_CHAOTIC && altaralign != A_NONE) { - /* curse the lawful/neutral altar */ - pline_The("altar is stained with %s blood.", urace.adj); - levl[u.ux][u.uy].altarmask = AM_CHAOTIC; +/* possibly convert an altar's alignment or the hero's alignment */ +staticfn void +offer_different_alignment_altar( + struct obj *otmp, + aligntyp altaralign) +{ + /* Is this a conversion ? */ + /* An unaligned altar in Gehennom will always elicit rejection. */ + if (ugod_is_angry() || (altaralign == A_NONE && Inhell)) { + if (u.ualignbase[A_CURRENT] == u.ualignbase[A_ORIGINAL] + && altaralign != A_NONE) { + You("have a strong feeling that %s is angry...", u_gname()); + consume_offering(otmp); + pline("%s accepts your allegiance.", a_gname()); + + uchangealign(altaralign, A_CG_CONVERT); + /* Beware, Conversion is costly */ + change_luck(-3); + u.ublesscnt += 300; + } else { + u.ugangr += 3; + adjalign(-5); + pline("%s rejects your sacrifice!", a_gname()); + godvoice(altaralign, "Suffer, infidel!"); + change_luck(-5); + (void) adjattrib(A_WIS, -2, TRUE); + if (!Inhell) + angrygods(u.ualign.type); + } + } else { + consume_offering(otmp); + You("sense a conflict between %s and %s.", u_gname(), a_gname()); + if (rn2(8 + u.ulevel) > 5) { + struct monst *pri; + boolean shrine; + + You_feel("the power of %s increase.", u_gname()); + exercise(A_WIS, TRUE); + change_luck(1); + shrine = on_shrine(); + levl[u.ux][u.uy].altarmask = Align2amask(u.ualign.type); + if (shrine) + levl[u.ux][u.uy].altarmask |= AM_SHRINE; + newsym(u.ux, u.uy); /* in case Invisible to self */ + if (!Blind) + pline_The("altar glows %s.", + hcolor((u.ualign.type == A_LAWFUL) ? NH_WHITE + : u.ualign.type ? NH_BLACK + : (const char *) "gray")); + + if (rnl(u.ulevel) > 6 && u.ualign.record > 0 + && rnd(u.ualign.record) > (3 * ALIGNLIM) / 4) + summon_minion(altaralign, TRUE); + /* anger priest; test handles bones files */ + if ((pri = findpriest(temple_occupied(u.urooms))) + && !p_coaligned(pri)) angry_priest(); - } else { - struct monst *dmon; - const char *demonless_msg; - - /* Human sacrifice on a chaotic or unaligned altar */ - /* is equivalent to demon summoning */ - if (altaralign == A_CHAOTIC && u.ualign.type != A_CHAOTIC) { - pline( - "The blood floods the altar, which vanishes in %s cloud!", - an(hcolor(NH_BLACK))); - levl[u.ux][u.uy].typ = ROOM; - levl[u.ux][u.uy].altarmask = 0; - newsym(u.ux, u.uy); - angry_priest(); - demonless_msg = "cloud dissipates"; - } else { - /* either you're chaotic or altar is Moloch's or both */ - pline_The("blood covers the altar!"); - change_luck(altaralign == A_NONE ? -2 : 2); - demonless_msg = "blood coagulates"; - } - if ((pm = dlord(altaralign)) != NON_PM - && (dmon = makemon(&mons[pm], u.ux, u.uy, NO_MM_FLAGS)) - != 0) { - char dbuf[BUFSZ]; - - Strcpy(dbuf, a_monnam(dmon)); - if (!strcmpi(dbuf, "it")) - Strcpy(dbuf, "something dreadful"); - else - dmon->mstrategy &= ~STRAT_APPEARMSG; - You("have summoned %s!", dbuf); - if (sgn(u.ualign.type) == sgn(dmon->data->maligntyp)) - dmon->mpeaceful = TRUE; - You("are terrified, and unable to move."); - nomul(-3); - multi_reason = "being terrified of a demon"; - nomovemsg = 0; - } else - pline_The("%s.", demonless_msg); - } + } else { + pline("Unluckily, you feel the power of %s decrease.", u_gname()); + change_luck(-1); + exercise(A_WIS, FALSE); + if (rnl(u.ulevel) > 6 && u.ualign.record > 0 + && rnd(u.ualign.record) > (7 * ALIGNLIM) / 8) + summon_minion(altaralign, TRUE); + } + } +} - if (u.ualign.type != A_CHAOTIC) { - adjalign(-5); - u.ugangr += 3; - (void) adjattrib(A_WIS, -1, TRUE); - if (!Inhell) - angrygods(u.ualign.type); - change_luck(-5); - } else - adjalign(5); - if (carried(otmp)) - useup(otmp); +staticfn void +sacrifice_your_race( + struct obj *otmp, + boolean highaltar, + aligntyp altaralign) +{ + int pm; + + if (is_demon(gy.youmonst.data)) { + You("find the idea very satisfying."); + exercise(A_WIS, TRUE); + } else if (u.ualign.type != A_CHAOTIC) { + pline("You'll regret this infamous offense!"); + exercise(A_WIS, FALSE); + } + + if (highaltar + && (altaralign != A_CHAOTIC || u.ualign.type != A_CHAOTIC)) { + desecrate_altar(highaltar, altaralign); + return; + } else if (altaralign != A_CHAOTIC && altaralign != A_NONE) { + /* curse the lawful/neutral altar */ + pline_The("altar is stained with %s blood.", gu.urace.adj); + levl[u.ux][u.uy].altarmask = AM_CHAOTIC; + newsym(u.ux, u.uy); /* in case Invisible to self */ + angry_priest(); + } else { + struct monst *dmon; + const char *demonless_msg; + + /* Human sacrifice on a chaotic or unaligned altar */ + /* is equivalent to demon summoning */ + if (altaralign == A_CHAOTIC && u.ualign.type != A_CHAOTIC) { + pline( + "The blood floods the altar, which vanishes in %s cloud!", + an(hcolor(NH_BLACK))); + levl[u.ux][u.uy].typ = ROOM; + levl[u.ux][u.uy].altarmask = 0; + newsym(u.ux, u.uy); + angry_priest(); + demonless_msg = "cloud dissipates"; + } else { + /* either you're chaotic or altar is Moloch's or both */ + pline_The("blood covers the altar!"); + change_luck(altaralign == A_NONE ? -2 : 2); + demonless_msg = "blood coagulates"; + } + if ((pm = dlord(altaralign)) != NON_PM + && (dmon = makemon(&mons[pm], u.ux, u.uy, MM_NOMSG)) + != 0) { + char dbuf[BUFSZ]; + + Strcpy(dbuf, a_monnam(dmon)); + if (!strcmpi(dbuf, "it")) + Strcpy(dbuf, "something dreadful"); else - useupf(otmp, 1L); - return 1; - } else if (has_omonst(otmp) - && (mtmp = get_mtraits(otmp, FALSE)) != 0 - && mtmp->mtame) { - /* mtmp is a temporary pointer to a tame monster's attributes, - * not a real monster */ - pline("So this is how you repay loyalty?"); - adjalign(-3); - value = -1; - HAggravate_monster |= FROMOUTSIDE; - } else if (is_undead(ptr)) { /* Not demons--no demon corpses */ - if (u.ualign.type != A_CHAOTIC) - value += 1; - } else if (is_unicorn(ptr)) { - int unicalign = sgn(ptr->maligntyp); - - if (unicalign == altaralign) { - /* When same as altar, always a very bad action. - */ - pline("Such an action is an insult to %s!", - (unicalign == A_CHAOTIC) ? "chaos" - : unicalign ? "law" : "balance"); - (void) adjattrib(A_WIS, -1, TRUE); - value = -5; - } else if (u.ualign.type == altaralign) { - /* When different from altar, and altar is same as yours, - * it's a very good action. - */ - if (u.ualign.record < ALIGNLIM) - You_feel("appropriately %s.", align_str(u.ualign.type)); - else - You_feel("you are thoroughly on the right path."); - adjalign(5); - value += 3; - } else if (unicalign == u.ualign.type) { - /* When sacrificing unicorn of your alignment to altar not of - * your alignment, your god gets angry and it's a conversion. - */ - u.ualign.record = -1; - value = 1; - } else { - /* Otherwise, unicorn's alignment is different from yours - * and different from the altar's. It's an ordinary (well, - * with a bonus) sacrifice on a cross-aligned altar. - */ - value += 3; + dmon->mstrategy &= ~STRAT_APPEARMSG; + You("have summoned %s!", dbuf); + if (sgn(u.ualign.type) == sgn(dmon->data->maligntyp)) + dmon->mpeaceful = TRUE; + You("are terrified, and unable to move."); + nomul(-3); + gm.multi_reason = "being terrified of a demon"; + gn.nomovemsg = 0; + } else + pline_The("%s.", demonless_msg); + } + + if (u.ualign.type != A_CHAOTIC) { + adjalign(-5); + u.ugangr += 3; + (void) adjattrib(A_WIS, -1, TRUE); + if (!Inhell) + angrygods(u.ualign.type); + change_luck(-5); + } else + adjalign(5); + if (carried(otmp)) + useup(otmp); + else + useupf(otmp, 1L); +} + +staticfn int +bestow_artifact(uchar max_giftvalue) +{ + int nartifacts = nartifact_exist(); + boolean do_bestow = u.ulevel > 2 && u.uluck >= 0; + if (do_bestow) { + /* you were already in pretty good standing */ + /* The player can gain an artifact */ + /* The chance goes down as the number of artifacts goes up */ + if (wizard) + do_bestow = y_n("Gift an artifact?") == 'y'; + else + do_bestow = !rn2(6 + (2 * u.ugifts * nartifacts)); + } + + if (do_bestow) { + struct obj *otmp; + /* mk_artifact() with NULL obj and a_align() arg can return NULL */ + otmp = mk_artifact((struct obj *) 0, a_align(u.ux, u.uy), + max_giftvalue, TRUE); + if (otmp) { + char buf[BUFSZ]; + + artifact_origin(otmp, ONAME_GIFT | ONAME_KNOW_ARTI); + if (otmp->spe < 0) + otmp->spe = 0; + if (otmp->cursed) + uncurse(otmp); + otmp->oerodeproof = TRUE; + Strcpy(buf, (Hallucination ? "a doodad" + : Blind ? "an object" + : ansimpleoname(otmp))); + if (!Blind) + Sprintf(eos(buf), " named %s", + bare_artifactname(otmp)); + at_your_feet(upstart(buf)); + dropy(otmp); + godvoice(u.ualign.type, "Use my gift wisely!"); + u.ugifts++; + u.ublesscnt = rnz(300 + (50 * nartifacts)); + exercise(A_WIS, TRUE); + livelog_printf (LL_DIVINEGIFT | LL_ARTIFACT, + "was bestowed with %s by %s", + artiname(otmp->oartifact), + align_gname(u.ualign.type)); + /* make sure we can use this weapon */ + unrestrict_weapon_skill(weapon_type(otmp)); + if (!Hallucination && !Blind) { + observe_object(otmp); + makeknown(otmp->otyp); + discover_artifact(otmp->oartifact); } + return TRUE; } - } /* corpse */ + } + return FALSE; +} + +staticfn int +sacrifice_value(struct obj *otmp) +{ + int value = 0; + + if (otmp->corpsenm == PM_ACID_BLOB + || (svm.moves <= peek_at_iced_corpse_age(otmp) + 50)) { + value = mons[otmp->corpsenm].difficulty + 1; + if (otmp->oeaten) + value = eaten_stat(value, otmp); + } + return value; +} + +/* the #offer command - sacrifice something to the gods */ +int +dosacrifice(void) +{ + struct obj *otmp; + boolean highaltar; + aligntyp altaralign = a_align(u.ux, u.uy); + + if (!on_altar() || u.uswallow) { + You("are not %s an altar.", + (Levitation || Flying) ? "over" : "on"); + return ECMD_OK; + } else if (Confusion || Stunned) { + You("are too impaired to perform the rite."); + return ECMD_OK; + } + highaltar = (levl[u.ux][u.uy].altarmask & AM_SANCTUM); + + otmp = floorfood("sacrifice", 1); + if (!otmp) + return ECMD_OK; if (otmp->otyp == AMULET_OF_YENDOR) { if (!highaltar) { - too_soon: - if (altaralign == A_NONE && Inhell) - /* hero has left Moloch's Sanctum so is in the process - of getting away with the Amulet (outside of Gehennom, - fall through to the "ashamed" feedback) */ - gods_upset(A_NONE); - else - You_feel("%s.", - Hallucination - ? "homesick" - /* if on track, give a big hint */ - : (altaralign == u.ualign.type) - ? "an urge to return to the surface" - /* else headed towards celestial disgrace */ - : "ashamed"); - return 1; + offer_too_soon(altaralign); + return ECMD_TIME; } else { - /* The final Test. Did you win? */ - if (uamul == otmp) - Amulet_off(); - u.uevent.ascended = 1; - if (carried(otmp)) - useup(otmp); /* well, it's gone now */ - else - useupf(otmp, 1L); - You("offer the Amulet of Yendor to %s...", a_gname()); - if (altaralign == A_NONE) { - /* Moloch's high altar */ - if (u.ualign.record > -99) - u.ualign.record = -99; - /*[apparently shrug/snarl can be sensed without being seen]*/ - pline("%s shrugs and retains dominion over %s,", Moloch, - u_gname()); - pline("then mercilessly snuffs out your life."); - Sprintf(killer.name, "%s indifference", s_suffix(Moloch)); - killer.format = KILLED_BY; - done(DIED); - /* life-saved (or declined to die in wizard/explore mode) */ - pline("%s snarls and tries again...", Moloch); - fry_by_god(A_NONE, TRUE); /* wrath of Moloch */ - /* declined to die in wizard or explore mode */ - pline(cloud_of_smoke, hcolor(NH_BLACK)); - done(ESCAPED); - } else if (u.ualign.type != altaralign) { - /* And the opposing team picks you up and - carries you off on their shoulders */ - adjalign(-99); - pline("%s accepts your gift, and gains dominion over %s...", - a_gname(), u_gname()); - pline("%s is enraged...", u_gname()); - pline("Fortunately, %s permits you to live...", a_gname()); - pline(cloud_of_smoke, hcolor(NH_ORANGE)); - done(ESCAPED); - } else { /* super big win */ - adjalign(10); - u.uachieve.ascended = 1; - pline( - "An invisible choir sings, and you are bathed in radiance..."); - godvoice(altaralign, "Mortal, thou hast done well!"); - display_nhwindow(WIN_MESSAGE, FALSE); - verbalize( - "In return for thy service, I grant thee the gift of Immortality!"); - You("ascend to the status of Demigod%s...", - flags.female ? "dess" : ""); - done(ASCENDED); - } + offer_real_amulet(otmp, altaralign); + /*NOTREACHED*/ } } /* real Amulet */ if (otmp->otyp == FAKE_AMULET_OF_YENDOR) { - if (!highaltar && !otmp->known) - goto too_soon; - You_hear("a nearby thunderclap."); - if (!otmp->known) { - You("realize you have made a %s.", - Hallucination ? "boo-boo" : "mistake"); - otmp->known = TRUE; - change_luck(-1); - return 1; + offer_fake_amulet(otmp, highaltar, altaralign); + return ECMD_TIME; + } /* fake Amulet */ + + if (otmp->otyp == CORPSE) { + offer_corpse(otmp, highaltar, altaralign); + return ECMD_TIME; + } + + pline1(nothing_happens); + return ECMD_TIME; +} + +staticfn int +eval_offering(struct obj *otmp, aligntyp altaralign) +{ + struct permonst *ptr; + int value; + + value = sacrifice_value(otmp); + + if (!value) + return 0; + + ptr = &mons[otmp->corpsenm]; + + if (is_undead(ptr)) { /* Not demons--no demon corpses */ + /* most undead that leave a corpse yield 'human' (or other race) + corpse so won't get here; the exception is wraith; give the + bonus for wraith to chaotics too because they are sacrificing + something valuable (unless hero refuses to eat such things) */ + if (u.ualign.type != A_CHAOTIC + /* reaching this side of the 'or' means hero is chaotic */ + || (ptr == &mons[PM_WRAITH] && u.uconduct.unvegetarian)) + value += 1; + } else if (is_unicorn(ptr)) { + int unicalign = sgn(ptr->maligntyp); + + if (unicalign == altaralign) { + /* When same as altar, always a very bad action. + */ + pline("Such an action is an insult to %s!", + (unicalign == A_CHAOTIC) ? "chaos" + : unicalign ? "law" : "balance"); + (void) adjattrib(A_WIS, -1, TRUE); + return -1; + } else if (u.ualign.type == altaralign) { + /* When different from altar, and altar is same as yours, + * it's a very good action. + */ + if (u.ualign.record < ALIGNLIM) + You_feel("appropriately %s.", align_str(u.ualign.type)); + else + You_feel("you are thoroughly on the right path."); + adjalign(5); + value += 3; + } else if (unicalign == u.ualign.type) { + /* When sacrificing unicorn of your alignment to altar not of + * your alignment, your god gets angry and it's a conversion. + */ + u.ualign.record = -1; + value = 1; } else { - /* don't you dare try to fool the gods */ - if (Deaf) - pline("Oh, no."); /* didn't hear thunderclap */ - change_luck(-3); - adjalign(-1); - u.ugangr += 3; - value = -3; + /* Otherwise, unicorn's alignment is different from yours + * and different from the altar's. It's an ordinary (well, + * with a bonus) sacrifice on a cross-aligned altar. + */ + value += 3; } - } /* fake Amulet */ + } + return value; +} + +staticfn void +offer_corpse(struct obj *otmp, boolean highaltar, aligntyp altaralign) +{ + int value; + struct permonst *ptr; + struct monst *mtmp; + + /* + * Was based on nutritional value and aging behavior (< 50 moves). + * Sacrificing a food ration got you max luck instantly, making the + * gods as easy to please as an angry dog! + * + * Now only accepts corpses, based on the game's evaluation of their + * toughness. Human and pet sacrifice, as well as sacrificing unicorns + * of your alignment, is strongly discouraged. + */ +#define MAXVALUE 24 /* Highest corpse value (besides Wiz) */ + + /* KMH, conduct */ + if (!u.uconduct.gnostic++) + livelog_printf(LL_CONDUCT, "rejected atheism" + " by offering %s on an altar of %s", + corpse_xname(otmp, (const char *) 0, CXN_ARTICLE), + a_gname()); + + /* you're handling this corpse, even if it was killed upon the altar + */ + feel_cockatrice(otmp, TRUE); + if (rider_corpse_revival(otmp, FALSE)) + return; + + ptr = &mons[otmp->corpsenm]; + /* same race or former pet results apply even if the corpse is + too old (value==0) */ + if (your_race(ptr)) { + sacrifice_your_race(otmp, highaltar, altaralign); + return; + } + if (has_omonst(otmp) + && (mtmp = get_mtraits(otmp, FALSE)) != 0 + && mtmp->mtame) { + /* mtmp is a temporary pointer to a tame monster's attributes, + * not a real monster */ + pline("So this is how you repay loyalty?"); + adjalign(-3); + HAggravate_monster |= FROMOUTSIDE; + offer_negative_valued(highaltar, altaralign); + return; + } + + value = eval_offering(otmp, altaralign); if (value == 0) { + /* too old; don't give undead or unicorn bonus or penalty */ pline1(nothing_happens); - return 1; + return; + } + if (value < 0) { + offer_negative_valued(highaltar, altaralign); + return; } if (altaralign != u.ualign.type && highaltar) { - desecrate_high_altar: - /* - * REAL BAD NEWS!!! High altars cannot be converted. Even an attempt - * gets the god who owns it truly pissed off. - */ - You_feel("the air around you grow charged..."); - pline("Suddenly, you realize that %s has noticed you...", a_gname()); - godvoice(altaralign, - "So, mortal! You dare desecrate my High Temple!"); - /* Throw everything we have at the player */ - god_zaps_you(altaralign); - } else if (value - < 0) { /* I don't think the gods are gonna like this... */ - gods_upset(altaralign); - } else { + desecrate_altar(highaltar, altaralign); + return; + } + if (u.ualign.type != altaralign) { + /* Sacrificing at an altar of a different alignment */ + offer_different_alignment_altar(otmp, altaralign); + return; + } + consume_offering(otmp); + /* OK, you get brownie points. */ + if (u.ugangr) { int saved_anger = u.ugangr; - int saved_cnt = u.ublesscnt; - int saved_luck = u.uluck; + u.ugangr -= ((value * (u.ualign.type == A_CHAOTIC ? 2 : 3)) + / MAXVALUE); + if (u.ugangr < 0) + u.ugangr = 0; + if (u.ugangr != saved_anger) { + if (u.ugangr) { + pline("%s seems %s.", u_gname(), + Hallucination ? "groovy" : "slightly mollified"); - /* Sacrificing at an altar of a different alignment */ - if (u.ualign.type != altaralign) { - /* Is this a conversion ? */ - /* An unaligned altar in Gehennom will always elicit rejection. */ - if (ugod_is_angry() || (altaralign == A_NONE && Inhell)) { - if (u.ualignbase[A_CURRENT] == u.ualignbase[A_ORIGINAL] - && altaralign != A_NONE) { - You("have a strong feeling that %s is angry...", - u_gname()); - consume_offering(otmp); - pline("%s accepts your allegiance.", a_gname()); - - uchangealign(altaralign, 0); - /* Beware, Conversion is costly */ - change_luck(-3); - u.ublesscnt += 300; - } else { - u.ugangr += 3; - adjalign(-5); - pline("%s rejects your sacrifice!", a_gname()); - godvoice(altaralign, "Suffer, infidel!"); - change_luck(-5); - (void) adjattrib(A_WIS, -2, TRUE); - if (!Inhell) - angrygods(u.ualign.type); - } - return 1; - } else { - consume_offering(otmp); - You("sense a conflict between %s and %s.", u_gname(), - a_gname()); - if (rn2(8 + u.ulevel) > 5) { - struct monst *pri; - You_feel("the power of %s increase.", u_gname()); - exercise(A_WIS, TRUE); + if ((int) u.uluck < 0) change_luck(1); - /* Yes, this is supposed to be &=, not |= */ - levl[u.ux][u.uy].altarmask &= AM_SHRINE; - /* the following accommodates stupid compilers */ - levl[u.ux][u.uy].altarmask = - levl[u.ux][u.uy].altarmask - | (Align2amask(u.ualign.type)); - if (!Blind) - pline_The("altar glows %s.", - hcolor((u.ualign.type == A_LAWFUL) - ? NH_WHITE - : u.ualign.type - ? NH_BLACK - : (const char *) "gray")); - - if (rnl(u.ulevel) > 6 && u.ualign.record > 0 - && rnd(u.ualign.record) > (3 * ALIGNLIM) / 4) - summon_minion(altaralign, TRUE); - /* anger priest; test handles bones files */ - if ((pri = findpriest(temple_occupied(u.urooms))) - && !p_coaligned(pri)) - angry_priest(); - } else { - pline("Unluckily, you feel the power of %s decrease.", - u_gname()); - change_luck(-1); - exercise(A_WIS, FALSE); - if (rnl(u.ulevel) > 6 && u.ualign.record > 0 - && rnd(u.ualign.record) > (7 * ALIGNLIM) / 8) - summon_minion(altaralign, TRUE); - } - return 1; + } else { + pline("%s seems %s.", u_gname(), + Hallucination ? "cosmic (not a new fact)" + : "mollified"); + + if ((int) u.uluck < 0) + u.uluck = 0; } + } else { /* not satisfied yet */ + if (Hallucination) + pline_The("gods seem tall."); + else + You("have a feeling of inadequacy."); } - - consume_offering(otmp); - /* OK, you get brownie points. */ - if (u.ugangr) { - u.ugangr -= ((value * (u.ualign.type == A_CHAOTIC ? 2 : 3)) - / MAXVALUE); - if (u.ugangr < 0) - u.ugangr = 0; - if (u.ugangr != saved_anger) { - if (u.ugangr) { - pline("%s seems %s.", u_gname(), - Hallucination ? "groovy" : "slightly mollified"); - - if ((int) u.uluck < 0) - change_luck(1); - } else { - pline("%s seems %s.", u_gname(), - Hallucination ? "cosmic (not a new fact)" - : "mollified"); - - if ((int) u.uluck < 0) - u.uluck = 0; - } - } else { /* not satisfied yet */ + } else if (ugod_is_angry()) { + if (value > MAXVALUE) + value = MAXVALUE; + if (value > -u.ualign.record) + value = -u.ualign.record; + adjalign(value); + You_feel("partially absolved."); + } else if (u.ublesscnt > 0) { + int saved_cnt = u.ublesscnt; + u.ublesscnt -= ((value * (u.ualign.type == A_CHAOTIC ? 500 : 300)) + / MAXVALUE); + if (u.ublesscnt < 0) + u.ublesscnt = 0; + if (u.ublesscnt != saved_cnt) { + if (u.ublesscnt) { if (Hallucination) - pline_The("gods seem tall."); + You("realize that the gods are not like you and I."); else - You("have a feeling of inadequacy."); - } - } else if (ugod_is_angry()) { - if (value > MAXVALUE) - value = MAXVALUE; - if (value > -u.ualign.record) - value = -u.ualign.record; - adjalign(value); - You_feel("partially absolved."); - } else if (u.ublesscnt > 0) { - u.ublesscnt -= ((value * (u.ualign.type == A_CHAOTIC ? 500 : 300)) - / MAXVALUE); - if (u.ublesscnt < 0) - u.ublesscnt = 0; - if (u.ublesscnt != saved_cnt) { - if (u.ublesscnt) { - if (Hallucination) - You("realize that the gods are not like you and I."); - else - You("have a hopeful feeling."); - if ((int) u.uluck < 0) - change_luck(1); - } else { - if (Hallucination) - pline("Overall, there is a smell of fried onions."); - else - You("have a feeling of reconciliation."); - if ((int) u.uluck < 0) - u.uluck = 0; - } - } - } else { - int nartifacts = nartifact_exist(); - - /* you were already in pretty good standing */ - /* The player can gain an artifact */ - /* The chance goes down as the number of artifacts goes up */ - if (u.ulevel > 2 && u.uluck >= 0 - && !rn2(10 + (2 * u.ugifts * nartifacts))) { - otmp = mk_artifact((struct obj *) 0, a_align(u.ux, u.uy)); - if (otmp) { - if (otmp->spe < 0) - otmp->spe = 0; - if (otmp->cursed) - uncurse(otmp); - otmp->oerodeproof = TRUE; - at_your_feet("An object"); - dropy(otmp); - godvoice(u.ualign.type, "Use my gift wisely!"); - u.ugifts++; - u.ublesscnt = rnz(300 + (50 * nartifacts)); - exercise(A_WIS, TRUE); - /* make sure we can use this weapon */ - unrestrict_weapon_skill(weapon_type(otmp)); - if (!Hallucination && !Blind) { - otmp->dknown = 1; - makeknown(otmp->otyp); - discover_artifact(otmp->oartifact); - } - return 1; - } - } - change_luck((value * LUCKMAX) / (MAXVALUE * 2)); - if ((int) u.uluck < 0) - u.uluck = 0; - if (u.uluck != saved_luck) { - if (Blind) - You("think %s brushed your %s.", something, - body_part(FOOT)); + You("have a hopeful feeling."); + if ((int) u.uluck < 0) + change_luck(1); + } else { + if (Hallucination) + pline("Overall, there is a smell of fried onions."); else - You(Hallucination - ? "see crabgrass at your %s. A funny thing in a dungeon." - : "glimpse a four-leaf clover at your %s.", - makeplural(body_part(FOOT))); + You("have a feeling of reconciliation."); + if ((int) u.uluck < 0) + u.uluck = 0; } } + } else { + int orig_luck, luck_increase; + + if (bestow_artifact(value)) + return; + + orig_luck = u.uluck; + luck_increase = (value * LUCKMAX) / (MAXVALUE * 2); + + /* sacrificing can't increase non-bonus Luck to above the value of the + sacrifice; this prevents players immediately maxing their Luck as + soon as they find an altar and a few rations via sacrificing lots + of low-valued corpses, which can unbalance the early game */ + if (orig_luck > value) + luck_increase = 0; + else if (orig_luck + luck_increase > value) + luck_increase = value - orig_luck; + + change_luck(luck_increase); + if ((int) u.uluck < 0) + u.uluck = 0; + if (u.uluck != orig_luck) { + if (Blind) + You("think %s brushed your %s.", something, + body_part(FOOT)); + else + You(Hallucination + ? "see crabgrass at your %s. A funny thing in a dungeon." + : "glimpse a four-leaf clover at your %s.", + makeplural(body_part(FOOT))); + } } - return 1; } /* determine prayer results in advance; also used for enlightenment */ boolean -can_pray(praying) -boolean praying; /* false means no messages should be given */ +can_pray(boolean praying) /* false means no messages should be given */ { int alignment; - p_aligntyp = on_altar() ? a_align(u.ux, u.uy) : u.ualign.type; - p_trouble = in_trouble(); + gp.p_aligntyp = on_altar() ? a_align(u.ux, u.uy) : u.ualign.type; + gp.p_trouble = in_trouble(); - if (is_demon(youmonst.data) && (p_aligntyp != A_CHAOTIC)) { + if (is_demon(gy.youmonst.data) /* ok if chaotic or none (Moloch) */ + && (gp.p_aligntyp == A_LAWFUL || gp.p_aligntyp != A_NEUTRAL)) { if (praying) pline_The("very idea of praying to a %s god is repugnant to you.", - p_aligntyp ? "lawful" : "neutral"); + gp.p_aligntyp ? "lawful" : "neutral"); return FALSE; } if (praying) - You("begin praying to %s.", align_gname(p_aligntyp)); + You("begin praying to %s.", align_gname(gp.p_aligntyp)); - if (u.ualign.type && u.ualign.type == -p_aligntyp) + if (u.ualign.type && u.ualign.type == -gp.p_aligntyp) alignment = -u.ualign.record; /* Opposite alignment altar */ - else if (u.ualign.type != p_aligntyp) + else if (u.ualign.type != gp.p_aligntyp) alignment = u.ualign.record / 2; /* Different alignment altar */ else alignment = u.ualign.record; - if ((p_trouble > 0) ? (u.ublesscnt > 200) /* big trouble */ - : (p_trouble < 0) ? (u.ublesscnt > 100) /* minor difficulties */ - : (u.ublesscnt > 0)) /* not in trouble */ - p_type = 0; /* too soon... */ + if (gp.p_aligntyp == A_NONE) /* praying to Moloch */ + gp.p_type = -2; + else if ((gp.p_trouble > 0) ? (u.ublesscnt > 200) /* big trouble */ + : (gp.p_trouble < 0) ? (u.ublesscnt > 100) /* minor difficulty */ + : (u.ublesscnt > 0)) /* not in trouble */ + gp.p_type = 0; /* too soon... */ else if ((int) Luck < 0 || u.ugangr || alignment < 0) - p_type = 1; /* too naughty... */ + gp.p_type = 1; /* too naughty... */ else /* alignment >= 0 */ { - if (on_altar() && u.ualign.type != p_aligntyp) - p_type = 2; + if (on_altar() && u.ualign.type != gp.p_aligntyp) + gp.p_type = 2; else - p_type = 3; + gp.p_type = 3; } - if (is_undead(youmonst.data) && !Inhell - && (p_aligntyp == A_LAWFUL || (p_aligntyp == A_NEUTRAL && !rn2(10)))) - p_type = -1; + if (is_undead(gy.youmonst.data) && !Inhell + && (gp.p_aligntyp == A_LAWFUL + || (gp.p_aligntyp == A_NEUTRAL && !rn2(10)))) + gp.p_type = -1; /* Note: when !praying, the random factor for neutrals makes the return value a non-deterministic approximation for enlightenment. This case should be uncommon enough to live with... */ - return !praying ? (boolean) (p_type == 3 && !Inhell) : TRUE; + return !praying ? (boolean) (gp.p_type == 3 && !Inhell) : TRUE; +} + +/* return TRUE if praying revived a pet corpse */ +staticfn boolean +pray_revive(void) +{ + struct obj *otmp; + + for (otmp = svl.level.objects[u.ux][u.uy]; otmp; otmp = otmp->nexthere) + if ((otmp->otyp == CORPSE || otmp->otyp == STATUE) + && has_omonst(otmp) + && OMONST(otmp)->mtame && !OMONST(otmp)->isminion) + break; + + if (!otmp) + return FALSE; + + if (otmp->otyp == CORPSE) + return (revive(otmp, TRUE) != NULL); + else { + return (animate_statue(otmp, u.ux, u.uy, ANIMATE_SPELL, NULL) != NULL); + } } -/* #pray commmand */ +/* #pray command */ int -dopray() +dopray(void) { - /* Confirm accidental slips of Alt-P */ - if (ParanoidPray && yn("Are you sure you want to pray?") != 'y') - return 0; + boolean ok; + + /* + * If ParanoidPray is set, confirm prayer to avoid accidental slips + * of Alt+p. If ParanoidConfirm is also set, require "yes" rather + * than just "y" (will also require "no" to decline). + */ + if (ParanoidPray) { + ok = paranoid_query(ParanoidConfirm, + "Are you sure you want to pray?"); +#if 0 + /* clear command recall buffer; otherwise ^A to repeat p(ray) would + do so without confirmation (if 'ok') or do nothing (if '!ok') */ + cmdq_clear(CQ_REPEAT); + cmdq_add_ec(CQ_REPEAT, dopray); +#endif + if (!ok) /* declined the "are you sure?" confirmation */ + return ECMD_OK; + } - u.uconduct.gnostic++; + if (!u.uconduct.gnostic++) + /* breaking conduct should probably occur in can_pray() at + * "You begin praying to %s", as demons who find praying repugnant + * should not break conduct. Also we can add more detail to the + * livelog message as p_aligntyp will be known. + */ + livelog_printf(LL_CONDUCT, "rejected atheism with a prayer"); /* set up p_type and p_alignment */ if (!can_pray(TRUE)) - return 0; + return ECMD_OK; - if (wizard && p_type >= 0) { - if (yn("Force the gods to be pleased?") == 'y') { + if (wizard && gp.p_type >= 0) { + static const char forcesuccess[] = "Force the gods to be pleased?"; + + /* if we asked "are you sure?" above we suppressed the response + from the do-again buffer, so need to suppress this response too; + otherwise subsequent ^A would use this answer for "are you sure?" + and bypass confirmation */ + if (ParanoidPray) { + boolean save_doagain = gi.in_doagain; + + gi.in_doagain = FALSE; + ok = (YN(forcesuccess) == 'y'); + gi.in_doagain = save_doagain; + } else { + ok = (y_n(forcesuccess) == 'y'); + } + if (ok) { u.ublesscnt = 0; if (u.uluck < 0) u.uluck = 0; if (u.ualign.record <= 0) u.ualign.record = 1; u.ugangr = 0; - if (p_type < 2) - p_type = 3; + if (gp.p_type < 2) + gp.p_type = 3; } } nomul(-3); - multi_reason = "praying"; - nomovemsg = "You finish your prayer."; - afternmv = prayer_done; + gm.multi_reason = "praying"; + gn.nomovemsg = "You finish your prayer."; + ga.afternmv = prayer_done; - if (p_type == 3 && !Inhell) { + if (gp.p_type == 3 && !Inhell) { /* if you've been true to your god you can't die while you pray */ if (!Blind) You("are surrounded by a shimmering light."); u.uinvulnerable = TRUE; } - return 1; + return ECMD_TIME; } -STATIC_PTR int -prayer_done() /* M. Stephenson (1.0.3b) */ +staticfn int +prayer_done(void) /* M. Stephenson (1.0.3b) */ { - aligntyp alignment = p_aligntyp; + aligntyp alignment = gp.p_aligntyp; u.uinvulnerable = FALSE; - if (p_type == -1) { + if (gp.p_type == -2) { + /* praying at an unaligned altar, not necessarily in Gehennom */ + You("%s diabolical laughter all around you...", + !Deaf ? "hear" : "intuit"); + wake_nearby(FALSE); + adjalign(-2); + exercise(A_WIS, FALSE); + if (!Inhell) { + /* hero's god[dess] seems to be keeping his/her head down */ + pline("Nothing else happens."); /* not actually true... */ + return 1; + } /* else use regular Inhell result below */ + } else if (gp.p_type == -1) { + /* praying while poly'd into an undead creature while non-chaotic */ godvoice(alignment, (alignment == A_LAWFUL) ? "Vile creature, thou durst call upon me?" @@ -1908,17 +2313,17 @@ prayer_done() /* M. Stephenson (1.0.3b) */ return 0; } - if (p_type == 0) { + if (gp.p_type == 0) { if (on_altar() && u.ualign.type != alignment) (void) water_prayer(FALSE); u.ublesscnt += rnz(250); change_luck(-3); gods_upset(u.ualign.type); - } else if (p_type == 1) { + } else if (gp.p_type == 1) { if (on_altar() && u.ualign.type != alignment) (void) water_prayer(FALSE); angrygods(u.ualign.type); /* naughty */ - } else if (p_type == 2) { + } else if (gp.p_type == 2) { if (water_prayer(FALSE)) { /* attempted water prayer on a non-coaligned altar */ u.ublesscnt += rnz(250); @@ -1928,61 +2333,119 @@ prayer_done() /* M. Stephenson (1.0.3b) */ pleased(alignment); } else { /* coaligned */ - if (on_altar()) + if (on_altar()) { + (void) pray_revive(); (void) water_prayer(TRUE); + } pleased(alignment); /* nice */ } return 1; } +/* iterable for undead turning by priest/knight */ +staticfn void +maybe_turn_mon_iter(struct monst *mtmp) +{ + /* 3.6.3: used to use cansee() here but the purpose is to prevent + #turn operating through walls, not to require that the hero be + able to see the target location */ + if (!couldsee(mtmp->mx, mtmp->my) + || mdistu(mtmp) > turn_undead_range) + return; + + if (!mtmp->mpeaceful + && (is_undead(mtmp->data) || is_vampshifter(mtmp) + || (is_demon(mtmp->data) && (u.ulevel > (MAXULEV / 2))))) { + mtmp->msleeping = 0; + if (Confusion) { + if (!turn_undead_msg_cnt++) + pline("Unfortunately, your voice falters."); + mtmp->mflee = 0; + mtmp->mfrozen = 0; + mtmp->mcanmove = 1; + } else if (!resist(mtmp, '\0', 0, TELL)) { + int xlev = 6; + + switch (mtmp->data->mlet) { + /* this is intentional, lichs are tougher + than zombies. */ + case S_LICH: + xlev += 2; + FALLTHROUGH; + /*FALLTHRU*/ + case S_GHOST: + xlev += 2; + FALLTHROUGH; + /*FALLTHRU*/ + case S_VAMPIRE: + xlev += 2; + FALLTHROUGH; + /*FALLTHRU*/ + case S_WRAITH: + xlev += 2; + FALLTHROUGH; + /*FALLTHRU*/ + case S_MUMMY: + xlev += 2; + FALLTHROUGH; + /*FALLTHRU*/ + case S_ZOMBIE: + if (u.ulevel >= xlev && !resist(mtmp, '\0', 0, NOTELL)) { + if (u.ualign.type == A_CHAOTIC) { + mtmp->mpeaceful = 1; + set_malign(mtmp); + } else { /* damn them */ + killed(mtmp); + } + break; + } /* else flee */ + FALLTHROUGH; + /*FALLTHRU*/ + default: + monflee(mtmp, 0, FALSE, TRUE); + break; + } + } + } +} + /* #turn command */ int -doturn() +doturn(void) { /* Knights & Priest(esse)s only please */ - struct monst *mtmp, *mtmp2; const char *Gname; - int once, range, xlev; - - if (!Role_if(PM_PRIEST) && !Role_if(PM_KNIGHT)) { - /* Try to use the "turn undead" spell. - * - * This used to be based on whether hero knows the name of the - * turn undead spellbook, but it's possible to know--and be able - * to cast--the spell while having lost the book ID to amnesia. - * (It also used to tell spelleffects() to cast at self?) - */ - int sp_no; - for (sp_no = 0; sp_no < MAXSPELL; ++sp_no) { - if (spl_book[sp_no].sp_id == NO_SPELL) - break; - else if (spl_book[sp_no].sp_id == SPE_TURN_UNDEAD) - return spelleffects(sp_no, FALSE); - } + if (!Role_if(PM_CLERIC) && !Role_if(PM_KNIGHT)) { + /* Try to use the "turn undead" spell. */ + if (known_spell(SPE_TURN_UNDEAD)) + return spelleffects(SPE_TURN_UNDEAD, FALSE, FALSE); You("don't know how to turn undead!"); - return 0; + return ECMD_OK; } - u.uconduct.gnostic++; + if (!u.uconduct.gnostic++) + livelog_printf(LL_CONDUCT, "rejected atheism by turning undead"); + Gname = halu_gname(u.ualign.type); /* [What about needing free hands (does #turn involve any gesturing)?] */ - if (!can_chant(&youmonst)) { + if (!can_chant(&gy.youmonst)) { /* "evilness": "demons and undead" is too verbose and too precise */ You("are %s upon %s to turn aside evilness.", Strangled ? "not able to call" : "incapable of calling", Gname); /* violates agnosticism due to intent; conduct tracking is not supposed to affect play but we make an exception here: use a move if this is the first time agnostic conduct has been broken */ - return (u.uconduct.gnostic == 1); + return (u.uconduct.gnostic == 1) ? ECMD_TIME : ECMD_OK; } if ((u.ualign.type != A_CHAOTIC - && (is_demon(youmonst.data) || is_undead(youmonst.data))) + && (is_demon(gy.youmonst.data) + || is_undead(gy.youmonst.data) || is_vampshifter(&gy.youmonst))) || u.ugangr > 6) { /* "Die, mortal!" */ pline("For some reason, %s seems to ignore you.", Gname); aggravate(); exercise(A_WIS, FALSE); - return 1; + return ECMD_TIME; } if (Inhell) { pline("Since you are in Gehennom, %s %s help you.", @@ -1990,74 +2453,17 @@ doturn() phrasing anyway if hallucinatory feedback says it's him */ Gname, !strcmp(Gname, Moloch) ? "won't" : "can't"); aggravate(); - return 1; + return ECMD_TIME; } pline("Calling upon %s, you chant an arcane formula.", Gname); exercise(A_WIS, TRUE); /* note: does not perform unturn_dead() on victims' inventories */ - range = BOLT_LIM + (u.ulevel / 5); /* 8 to 14 */ - range *= range; - once = 0; - for (mtmp = fmon; mtmp; mtmp = mtmp2) { - mtmp2 = mtmp->nmon; - if (DEADMONSTER(mtmp)) - continue; - /* 3.6.3: used to use cansee() here but the purpose is to prevent - #turn operating through walls, not to require that the hero be - able to see the target location */ - if (!couldsee(mtmp->mx, mtmp->my) - || distu(mtmp->mx, mtmp->my) > range) - continue; - - if (!mtmp->mpeaceful - && (is_undead(mtmp->data) || is_vampshifter(mtmp) - || (is_demon(mtmp->data) && (u.ulevel > (MAXULEV / 2))))) { - mtmp->msleeping = 0; - if (Confusion) { - if (!once++) - pline("Unfortunately, your voice falters."); - mtmp->mflee = 0; - mtmp->mfrozen = 0; - mtmp->mcanmove = 1; - } else if (!resist(mtmp, '\0', 0, TELL)) { - xlev = 6; - switch (mtmp->data->mlet) { - /* this is intentional, lichs are tougher - than zombies. */ - case S_LICH: - xlev += 2; - /*FALLTHRU*/ - case S_GHOST: - xlev += 2; - /*FALLTHRU*/ - case S_VAMPIRE: - xlev += 2; - /*FALLTHRU*/ - case S_WRAITH: - xlev += 2; - /*FALLTHRU*/ - case S_MUMMY: - xlev += 2; - /*FALLTHRU*/ - case S_ZOMBIE: - if (u.ulevel >= xlev && !resist(mtmp, '\0', 0, NOTELL)) { - if (u.ualign.type == A_CHAOTIC) { - mtmp->mpeaceful = 1; - set_malign(mtmp); - } else { /* damn them */ - killed(mtmp); - } - break; - } /* else flee */ - /*FALLTHRU*/ - default: - monflee(mtmp, 0, FALSE, TRUE); - break; - } - } - } - } + turn_undead_range = BOLT_LIM + (u.ulevel / 5); /* 8 to 14 */ + turn_undead_range *= turn_undead_range; + turn_undead_msg_cnt = 0; + + iter_mons(maybe_turn_mon_iter); /* * There is no detrimental effect on self for successful #turn @@ -2065,7 +2471,7 @@ doturn() * chaotic oneself (see "For some reason" above) and chaotic * turning only makes targets peaceful. * - * Paralysis duration probably ought to be based on the strengh + * Paralysis duration probably ought to be based on the strength * of turned creatures rather than on turner's level. * Why doesn't this honor Free_action? [Because being able to * repeat #turn every turn would be too powerful. Maybe instead @@ -2075,21 +2481,37 @@ doturn() * the brief paralysis?] */ nomul(-(5 - ((u.ulevel - 1) / 6))); /* -5 .. -1 */ - multi_reason = "trying to turn the monsters"; - nomovemsg = You_can_move_again; - return 1; + gm.multi_reason = "trying to turn the monsters"; + gn.nomovemsg = You_can_move_again; + return ECMD_TIME; +} + +int +altarmask_at(coordxy x, coordxy y) +{ + int res = 0; + + if (isok(x, y)) { + struct monst *mon = m_at(x, y); + + if (mon && M_AP_TYPE(mon) == M_AP_FURNITURE + && mon->mappearance == S_altar) + res = has_mcorpsenm(mon) ? MCORPSENM(mon) : 0; + else if (IS_ALTAR(levl[x][y].typ)) + res = levl[x][y].altarmask; + } + return res; } const char * -a_gname() +a_gname(void) { return a_gname_at(u.ux, u.uy); } /* returns the name of an altar's deity */ const char * -a_gname_at(x, y) -xchar x, y; +a_gname_at(coordxy x, coordxy y) { if (!IS_ALTAR(levl[x][y].typ)) return (char *) 0; @@ -2099,14 +2521,13 @@ xchar x, y; /* returns the name of the hero's deity */ const char * -u_gname() +u_gname(void) { return align_gname(u.ualign.type); } const char * -align_gname(alignment) -aligntyp alignment; +align_gname(aligntyp alignment) { const char *gnam; @@ -2115,13 +2536,13 @@ aligntyp alignment; gnam = Moloch; break; case A_LAWFUL: - gnam = urole.lgod; + gnam = gu.urole.lgod; break; case A_NEUTRAL: - gnam = urole.ngod; + gnam = gu.urole.ngod; break; case A_CHAOTIC: - gnam = urole.cgod; + gnam = gu.urole.cgod; break; default: impossible("unknown alignment."); @@ -2133,7 +2554,7 @@ aligntyp alignment; return gnam; } -static const char *hallu_gods[] = { +static const char *const hallu_gods[] = { "the Flying Spaghetti Monster", /* Church of the FSM */ "Eris", /* Discordianism */ "the Martians", /* every science fiction ever */ @@ -2153,8 +2574,7 @@ static const char *hallu_gods[] = { /* hallucination handling for priest/minion names: select a random god iff character is hallucinating */ const char * -halu_gname(alignment) -aligntyp alignment; +halu_gname(aligntyp alignment) { const char *gnam = NULL; int which; @@ -2205,20 +2625,19 @@ aligntyp alignment; /* deity's title */ const char * -align_gtitle(alignment) -aligntyp alignment; +align_gtitle(aligntyp alignment) { const char *gnam, *result = "god"; switch (alignment) { case A_LAWFUL: - gnam = urole.lgod; + gnam = gu.urole.lgod; break; case A_NEUTRAL: - gnam = urole.ngod; + gnam = gu.urole.ngod; break; case A_CHAOTIC: - gnam = urole.cgod; + gnam = gu.urole.cgod; break; default: gnam = 0; @@ -2230,8 +2649,7 @@ aligntyp alignment; } void -altar_wrath(x, y) -register int x, y; +altar_wrath(coordxy x, coordxy y) { aligntyp altaralign = a_align(x, y); @@ -2245,6 +2663,7 @@ register int x, y; : "Despite your deafness, you seem to hear", align_gname(altaralign), !Deaf ? "?) whispers" : " say"); + SetVoice((struct monst *) 0, 0, 80, voice_deity); verbalize("Thou shalt pay, infidel!"); /* higher luck is more likely to be reduced; as it approaches -5 the chance to lose another point drops down, eventually to 0 */ @@ -2254,15 +2673,14 @@ register int x, y; } /* assumes isok() at one space away, but not necessarily at two */ -STATIC_OVL boolean -blocked_boulder(dx, dy) -int dx, dy; +staticfn boolean +blocked_boulder(int dx, int dy) { - register struct obj *otmp; + struct obj *otmp; int nx, ny; long count = 0L; - for (otmp = level.objects[u.ux + dx][u.uy + dy]; otmp; + for (otmp = svl.level.objects[u.ux + dx][u.uy + dy]; otmp; otmp = otmp->nexthere) { if (otmp->otyp == BOULDER) count += otmp->quan; @@ -2280,6 +2698,7 @@ int dx, dy; /* this is only approximate since multiple boulders might sink */ if (is_pool_or_lava(nx, ny)) /* does its own isok() check */ break; /* still need Sokoban check below */ + FALLTHROUGH; /*FALLTHRU*/ default: /* more than one boulder--blocked after they push the top one; @@ -2291,7 +2710,7 @@ int dx, dy; return TRUE; if (!isok(nx, ny)) return TRUE; - if (IS_ROCK(levl[nx][ny].typ)) + if (IS_OBSTRUCTED(levl[nx][ny].typ)) return TRUE; if (sobj_at(BOULDER, nx, ny)) return TRUE; diff --git a/src/priest.c b/src/priest.c index 5c56bc2cb..643fe425d 100644 --- a/src/priest.c +++ b/src/priest.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 priest.c $NHDT-Date: 1545131519 2018/12/18 11:11:59 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.45 $ */ +/* NetHack 5.0 priest.c $NHDT-Date: 1764567778 2025/11/30 21:42:58 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.106 $ */ /* Copyright (c) Izchak Miller, Steve Linhart, 1989. */ /* NetHack may be freely redistributed. See license for details. */ @@ -7,26 +7,25 @@ /* these match the categorizations shown by enlightenment */ #define ALGN_SINNED (-4) /* worse than strayed (-1..-3) */ -#define ALGN_PIOUS 14 /* better than fervent (9..13) */ +#define ALGN_DEVOUT 14 /* better than fervent (9..13) */ -STATIC_DCL boolean FDECL(histemple_at, (struct monst *, XCHAR_P, XCHAR_P)); -STATIC_DCL boolean FDECL(has_shrine, (struct monst *)); +staticfn boolean histemple_at(struct monst *, coordxy, coordxy); +staticfn boolean has_shrine(struct monst *); void -newepri(mtmp) -struct monst *mtmp; +newepri(struct monst *mtmp) { if (!mtmp->mextra) mtmp->mextra = newmextra(); if (!EPRI(mtmp)) { EPRI(mtmp) = (struct epri *) alloc(sizeof(struct epri)); (void) memset((genericptr_t) EPRI(mtmp), 0, sizeof(struct epri)); + EPRI(mtmp)->parentmid = mtmp->m_id; } } void -free_epri(mtmp) -struct monst *mtmp; +free_epri(struct monst *mtmp) { if (mtmp->mextra && EPRI(mtmp)) { free((genericptr_t) EPRI(mtmp)); @@ -40,24 +39,21 @@ struct monst *mtmp; * Valid returns are 1: moved 0: didn't -1: let m_move do it -2: died. */ int -move_special(mtmp, in_his_shop, appr, uondoor, avoid, omx, omy, gx, gy) -register struct monst *mtmp; -boolean in_his_shop; -schar appr; -boolean uondoor, avoid; -register xchar omx, omy, gx, gy; +move_special(struct monst *mtmp, boolean in_his_shop, schar appr, + boolean uondoor, boolean avoid, + coordxy omx, coordxy omy, coordxy ggx, coordxy ggy) { - register xchar nx, ny, nix, niy; - register schar i; + coordxy nx, ny, nix, niy; + schar i; schar chcnt, cnt; - coord poss[9]; - long info[9]; + struct mfndposdata mfp; + long ninfo = 0; long allowflags; #if 0 /* dead code; see below */ struct obj *ib = (struct obj *) 0; #endif - if (omx == gx && omy == gy) + if (omx == ggx && omy == ggy) return 0; if (mtmp->mconf) { avoid = FALSE; @@ -66,49 +62,36 @@ register xchar omx, omy, gx, gy; nix = omx; niy = omy; - if (mtmp->isshk) - allowflags = ALLOW_SSM; - else - allowflags = ALLOW_SSM | ALLOW_SANCT; - if (passes_walls(mtmp->data)) - allowflags |= (ALLOW_ROCK | ALLOW_WALL); - if (throws_rocks(mtmp->data)) - allowflags |= ALLOW_ROCK; - if (tunnels(mtmp->data)) - allowflags |= ALLOW_DIG; - if (!nohands(mtmp->data) && !verysmall(mtmp->data)) { - allowflags |= OPENDOOR; - if (monhaskey(mtmp, TRUE)) - allowflags |= UNLOCKDOOR; - } - if (is_giant(mtmp->data)) - allowflags |= BUSTDOOR; - cnt = mfndpos(mtmp, poss, info, allowflags); + allowflags = mon_allowflags(mtmp); + cnt = mfndpos(mtmp, &mfp, allowflags); if (mtmp->isshk && avoid && uondoor) { /* perhaps we cannot avoid him */ for (i = 0; i < cnt; i++) - if (!(info[i] & NOTONL)) + if (!(mfp.info[i] & NOTONL)) goto pick_move; avoid = FALSE; } -#define GDIST(x, y) (dist2(x, y, gx, gy)) -pick_move: +#define GDIST(x, y) (dist2(x, y, ggx, ggy)) + pick_move: chcnt = 0; for (i = 0; i < cnt; i++) { - nx = poss[i].x; - ny = poss[i].y; + nx = mfp.poss[i].x; + ny = mfp.poss[i].y; if (IS_ROOM(levl[nx][ny].typ) || (mtmp->isshk && (!in_his_shop || ESHK(mtmp)->following))) { - if (avoid && (info[i] & NOTONL)) + if (avoid && (mfp.info[i] & NOTONL) && !(mfp.info[i] & ALLOW_M)) continue; if ((!appr && !rn2(++chcnt)) - || (appr && GDIST(nx, ny) < GDIST(nix, niy))) { + || (appr && GDIST(nx, ny) < GDIST(nix, niy)) + || (mfp.info[i] & ALLOW_M)) { nix = nx; niy = ny; + ninfo = mfp.info[i]; } } } +#undef GDIST if (mtmp->ispriest && avoid && nix == omx && niy == omy && onlineu(omx, omy)) { /* might as well move closer as long it's going to stay @@ -118,7 +101,23 @@ register xchar omx, omy, gx, gy; } if (nix != omx || niy != omy) { - if (MON_AT(nix, niy)) + + if (ninfo & ALLOW_ROCK) { + m_break_boulder(mtmp, nix, niy); + return 1; + } else if (ninfo & ALLOW_M) { + /* mtmp is deciding it would like to attack this turn. + * Returns from m_move_aggress don't correspond to the same things + * as this function should return, so we need to translate. */ + switch (m_move_aggress(mtmp, nix, niy)) { + case 2: + return -2; /* died making the attack */ + case 3: + return 1; /* attacked and spent this move */ + } + } + + if (MON_AT(nix, niy) || u_at(nix, niy)) return 0; remove_monster(omx, omy); place_monster(mtmp, nix, niy); @@ -140,21 +139,18 @@ register xchar omx, omy, gx, gy; } char -temple_occupied(array) -register char *array; +temple_occupied(char *array) { - register char *ptr; + char *ptr; for (ptr = array; *ptr; ptr++) - if (rooms[*ptr - ROOMOFFSET].rtype == TEMPLE) + if (svr.rooms[*ptr - ROOMOFFSET].rtype == TEMPLE) return *ptr; return '\0'; } -STATIC_OVL boolean -histemple_at(priest, x, y) -register struct monst *priest; -register xchar x, y; +staticfn boolean +histemple_at(struct monst *priest, coordxy x, coordxy y) { return (boolean) (priest && priest->ispriest && (EPRI(priest)->shroom == *in_rooms(x, y, TEMPLE)) @@ -162,8 +158,7 @@ register xchar x, y; } boolean -inhistemple(priest) -struct monst *priest; +inhistemple(struct monst *priest) { /* make sure we have a priest */ if (!priest || !priest->ispriest) @@ -179,10 +174,9 @@ struct monst *priest; * pri_move: return 1: moved 0: didn't -1: let m_move do it -2: died */ int -pri_move(priest) -register struct monst *priest; +pri_move(struct monst *priest) { - register xchar gx, gy, omx, omy; + coordxy ggx, ggy, omx, omy; schar temple; boolean avoid = TRUE; @@ -194,57 +188,68 @@ register struct monst *priest; temple = EPRI(priest)->shroom; - gx = EPRI(priest)->shrpos.x; - gy = EPRI(priest)->shrpos.y; + ggx = EPRI(priest)->shrpos.x; + ggy = EPRI(priest)->shrpos.y; - gx += rn1(3, -1); /* mill around the altar */ - gy += rn1(3, -1); + ggx += rn1(3, -1); /* mill around the altar */ + ggy += rn1(3, -1); if (!priest->mpeaceful - || (Conflict && !resist(priest, RING_CLASS, 0, 0))) { + || (Conflict && !resist_conflict(priest))) { if (monnear(priest, u.ux, u.uy)) { if (Displaced) Your("displaced image doesn't fool %s!", mon_nam(priest)); (void) mattacku(priest); return 0; - } else if (index(u.urooms, temple)) { + } else if (strchr(u.urooms, temple)) { /* chase player if inside temple & can see him */ if (priest->mcansee && m_canseeu(priest)) { - gx = u.ux; - gy = u.uy; + ggx = u.ux; + ggy = u.uy; } avoid = FALSE; } } else if (Invis) avoid = FALSE; - return move_special(priest, FALSE, TRUE, FALSE, avoid, omx, omy, gx, gy); + return move_special(priest, FALSE, TRUE, FALSE, avoid, omx, omy, ggx, ggy); } /* exclusively for mktemple() */ void -priestini(lvl, sroom, sx, sy, sanctum) -d_level *lvl; -struct mkroom *sroom; -int sx, sy; -boolean sanctum; /* is it the seat of the high priest? */ +priestini( + d_level *lvl, + struct mkroom *sroom, + int sx, int sy, + boolean sanctum) /* is it the seat of the high priest? */ { struct monst *priest; struct obj *otmp; int cnt; + int px = 0, py = 0, i, si = rn2(N_DIRS); + struct permonst *prim = &mons[sanctum ? PM_HIGH_CLERIC + : PM_ALIGNED_CLERIC]; + + for (i = 0; i < N_DIRS; i++) { + px = sx + xdir[DIR_CLAMP(i+si)]; + py = sy + ydir[DIR_CLAMP(i+si)]; + if (pm_good_location(px, py, prim)) + break; + } + if (i == N_DIRS) + px = sx, py = sy; - if (MON_AT(sx + 1, sy)) - (void) rloc(m_at(sx + 1, sy), FALSE); /* insurance */ + if (MON_AT(px, py)) + (void) rloc(m_at(px, py), RLOC_NOMSG); /* insurance */ - priest = makemon(&mons[sanctum ? PM_HIGH_PRIEST : PM_ALIGNED_PRIEST], - sx + 1, sy, MM_EPRI); + priest = makemon(prim, px, py, MM_EPRI); if (priest) { - EPRI(priest)->shroom = (schar) ((sroom - rooms) + ROOMOFFSET); + EPRI(priest)->shroom = (schar) ((sroom - svr.rooms) + ROOMOFFSET); EPRI(priest)->shralign = Amask2align(levl[sx][sy].altarmask); EPRI(priest)->shrpos.x = sx; EPRI(priest)->shrpos.y = sy; assign_level(&(EPRI(priest)->shrlevel), lvl); - priest->mtrapseen = ~0; /* traps are known */ + mon_learns_traps(priest, ALL_TRAPS); /* traps are known */ priest->mpeaceful = 1; priest->ispriest = 1; priest->isminion = 0; @@ -258,7 +263,7 @@ boolean sanctum; /* is it the seat of the high priest? */ } /* 2 to 4 spellbooks */ for (cnt = rn1(3, 2); cnt > 0; --cnt) { - (void) mpickobj(priest, mkobj(SPBOOK_CLASS, FALSE)); + (void) mpickobj(priest, mkobj(SPBOOK_no_NOVEL, FALSE)); } /* robe [via makemon()] */ if (rn2(2) && (otmp = which_armor(priest, W_ARMC)) != 0) { @@ -272,8 +277,7 @@ boolean sanctum; /* is it the seat of the high priest? */ /* get a monster's alignment type without caller needing EPRI & EMIN */ aligntyp -mon_aligntyp(mon) -struct monst *mon; +mon_aligntyp(struct monst *mon) { aligntyp algn = mon->ispriest ? EPRI(mon)->shralign : mon->isminion ? EMIN(mon)->min_align @@ -295,40 +299,57 @@ struct monst *mon; * the true name even when under that influence */ char * -priestname(mon, pname) -register struct monst *mon; -char *pname; /* caller-supplied output buffer */ +priestname( + struct monst *mon, + int article, + boolean reveal_high_priest, + char *pname) /* caller-supplied output buffer */ { boolean do_hallu = Hallucination, - aligned_priest = mon->data == &mons[PM_ALIGNED_PRIEST], - high_priest = mon->data == &mons[PM_HIGH_PRIEST]; + aligned_priest = mon->data == &mons[PM_ALIGNED_CLERIC], + high_priest = mon->data == &mons[PM_HIGH_CLERIC]; char whatcode = '\0'; - const char *what = do_hallu ? rndmonnam(&whatcode) : mon->data->mname; + const char *what = do_hallu ? rndmonnam(&whatcode) : mon_pmname(mon); if (!mon->ispriest && !mon->isminion) /* should never happen... */ return strcpy(pname, what); /* caller must be confused */ + /* for high priest(ess), "high" (or "grand" for poohbah) will be inserted + [this was done near the end but we want 'what' to be updated sooner] */ + if (mon->ispriest || aligned_priest || high_priest) + what = do_hallu ? "poohbah" : mon->female ? "priestess" : "priest"; + *pname = '\0'; - if (!do_hallu || !bogon_is_pname(whatcode)) - Strcat(pname, "the "); - if (mon->minvis) + if (article != ARTICLE_NONE && (!do_hallu || !bogon_is_pname(whatcode))) { + if (article == ARTICLE_YOUR || (article == ARTICLE_A && high_priest)) + article = ARTICLE_THE; + if (article == ARTICLE_THE) { + Strcpy(pname, "the "); + } else if (!strcmp(what, "Angel")) { + /* bypass just_an(); it would yield "" due to treating capital A + as indicating a personal name */ + Strcpy(pname, "an "); + } else { + (void) just_an(pname, what); + } + } + /* pname[] contains "" or {"a ","an ","the "} */ + if (mon->minvis) { + /* avoid "a invisible priest" */ + if (!strcmp(pname, "a ")) + Strcpy(pname, "an "); Strcat(pname, "invisible "); - if (mon->isminion && EMIN(mon)->renegade) + } + if (mon->isminion && EMIN(mon)->renegade) { + /* avoid "an renegade Angel" */ + if (!strcmp(pname, "an ") && !mon->minvis) + Strcpy(pname, "a "); Strcat(pname, "renegade "); + } - if (mon->ispriest || aligned_priest) { /* high_priest implies ispriest */ - if (!aligned_priest && !high_priest) { - ; /* polymorphed priest; use ``what'' as is */ - } else { - if (high_priest) - Strcat(pname, "high "); - if (Hallucination) - what = "poohbah"; - else if (mon->female) - what = "priestess"; - else - what = "priest"; - } + if (mon->ispriest || aligned_priest) { + if (high_priest) + Strcat(pname, do_hallu ? "grand " : "high "); } else { if (mon->mtame && !strcmpi(what, "Angel")) Strcat(pname, "guardian "); @@ -336,8 +357,9 @@ char *pname; /* caller-supplied output buffer */ Strcat(pname, what); /* same as distant_monnam(), more or less... */ - if (do_hallu || !high_priest || !Is_astralevel(&u.uz) - || distu(mon->mx, mon->my) <= 2 || program_state.gameover) { + if (do_hallu || !high_priest || reveal_high_priest + || !Is_astralevel(&u.uz) + || m_next2u(mon) || program_state.gameover) { Strcat(pname, " of "); Strcat(pname, halu_gname(mon_aligntyp(mon))); } @@ -345,15 +367,13 @@ char *pname; /* caller-supplied output buffer */ } boolean -p_coaligned(priest) -struct monst *priest; +p_coaligned(struct monst *priest) { return (boolean) (u.ualign.type == mon_aligntyp(priest)); } -STATIC_OVL boolean -has_shrine(pri) -struct monst *pri; +staticfn boolean +has_shrine(struct monst *pri) { struct rm *lev; struct epri *epri_p; @@ -369,10 +389,9 @@ struct monst *pri; } struct monst * -findpriest(roomno) -char roomno; +findpriest(char roomno) { - register struct monst *mtmp; + struct monst *mtmp; for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { if (DEADMONSTER(mtmp)) @@ -384,10 +403,11 @@ char roomno; return (struct monst *) 0; } +DISABLE_WARNING_FORMAT_NONLITERAL + /* called from check_special_room() when the player enters the temple room */ void -intemple(roomno) -int roomno; +intemple(int roomno) { struct monst *priest, *mtmp; struct epri *epri_p; @@ -402,13 +422,14 @@ int roomno; if ((priest = findpriest((char) roomno)) != 0) { /* tended */ + record_achievement(ACH_TMPL); epri_p = EPRI(priest); shrined = has_shrine(priest); - sanctum = (priest->data == &mons[PM_HIGH_PRIEST] + sanctum = (priest->data == &mons[PM_HIGH_CLERIC] && (Is_sanctum(&u.uz) || In_endgame(&u.uz))); - can_speak = (priest->mcanmove && !priest->msleeping); - if (can_speak && !Deaf && moves >= epri_p->intone_time) { + can_speak = !helpless(priest); + if (can_speak && !Deaf && svm.moves >= epri_p->intone_time) { unsigned save_priest = priest->ispriest; /* don't reveal the altar's owner upon temple entry in @@ -419,7 +440,7 @@ int roomno; pline("%s intones:", canseemon(priest) ? Monnam(priest) : "A nearby voice"); priest->ispriest = save_priest; - epri_p->intone_time = moves + (long) d(10, 500); /* ~2505 */ + epri_p->intone_time = svm.moves + (long) d(10, 500); /* ~2505 */ /* make sure that we don't suppress entry message when we've just given its "priest intones" introduction */ epri_p->enter_time = 0L; @@ -437,16 +458,17 @@ int roomno; /* repeat visit, or attacked priest before entering */ msg1 = "You desecrate this place by your presence!"; } - } else if (moves >= epri_p->enter_time) { + } else if (svm.moves >= epri_p->enter_time) { Sprintf(buf, "Pilgrim, you enter a %s place!", !shrined ? "desecrated" : "sacred"); msg1 = buf; } if (msg1 && can_speak && !Deaf) { + SetVoice(priest, 0, 80, 0); verbalize1(msg1); if (msg2) verbalize1(msg2); - epri_p->enter_time = moves + (long) d(10, 100); /* ~505 */ + epri_p->enter_time = svm.moves + (long) d(10, 100); /* ~505 */ } if (!sanctum) { if (!shrined || !p_coaligned(priest) @@ -457,16 +479,16 @@ int roomno; other_time = &epri_p->peaceful_time; } else { msg1 = "experience %s sense of peace."; - msg2 = (u.ualign.record >= ALGN_PIOUS) ? "a" : "an unusual"; + msg2 = (u.ualign.record >= ALGN_DEVOUT) ? "a" : "an unusual"; this_time = &epri_p->peaceful_time; other_time = &epri_p->hostile_time; } /* give message if we haven't seen it recently or if alignment update has caused it to switch from forbidding to sense-of-peace or vice versa */ - if (moves >= *this_time || *other_time >= *this_time) { + if (svm.moves >= *this_time || *other_time >= *this_time) { You(msg1, msg2); - *this_time = moves + (long) d(10, 20); /* ~55 */ + *this_time = svm.moves + (long) d(10, 20); /* ~55 */ /* avoid being tricked by the RNG: switch might have just happened and previous random threshold could be larger */ if (*this_time <= *other_time) @@ -495,9 +517,9 @@ int roomno; make sure we give one the first time */ } if (!rn2(5) - && (mtmp = makemon(&mons[PM_GHOST], u.ux, u.uy, NO_MM_FLAGS)) + && (mtmp = makemon(&mons[PM_GHOST], u.ux, u.uy, MM_NOMSG)) != 0) { - int ngen = mvitals[PM_GHOST].born; + int ngen = svm.mvitals[PM_GHOST].born; if (canspotmon(mtmp)) pline("A%s ghost appears next to you%c", ngen < 5 ? "n enormous" : "", @@ -509,17 +531,18 @@ int roomno; if (flags.verbose) You("are frightened to death, and unable to move."); nomul(-3); - multi_reason = "being terrified of a ghost"; - nomovemsg = "You regain your composure."; + gm.multi_reason = "being terrified of a ghost"; + gn.nomovemsg = "You regain your composure."; } } } +RESTORE_WARNING_FORMAT_NONLITERAL + /* reset the move counters used to limit temple entry feedback; leaving the level and then returning yields a fresh start */ void -forget_temple_entry(priest) -struct monst *priest; +forget_temple_entry(struct monst *priest) { struct epri *epri_p = priest->ispriest ? EPRI(priest) : 0; @@ -532,14 +555,24 @@ struct monst *priest; } void -priest_talk(priest) -register struct monst *priest; +priest_talk(struct monst *priest) { boolean coaligned = p_coaligned(priest); boolean strayed = (u.ualign.record < 0); + unsigned *cheapskate = NULL; + if (EPRI(priest)) cheapskate = &EPRI(priest)->cheapskate_count; + + /* + * Note: we won't be called if hero is Deaf [since dochat() will + * return before calling domonnoise()], so we don't need to check + * for that before the various calls to verbalize() here. + */ /* KMH, conduct */ - u.uconduct.gnostic++; + if (!u.uconduct.gnostic++) + livelog_printf(LL_CONDUCT, + "rejected atheism by consulting with %s", + mon_nam(priest)); if (priest->mflee || (!priest->ispriest && coaligned && strayed)) { pline("%s doesn't want anything to do with you!", Monnam(priest)); @@ -548,21 +581,21 @@ register struct monst *priest; } /* priests don't chat unless peaceful and in their own temple */ - if (!inhistemple(priest) || !priest->mpeaceful - || !priest->mcanmove || priest->msleeping) { - static const char *cranky_msg[3] = { + if (!inhistemple(priest) || !priest->mpeaceful || helpless(priest)) { + static const char *const cranky_msg[3] = { "Thou wouldst have words, eh? I'll give thee a word or two!", "Talk? Here is what I have to say!", "Pilgrim, I would speak no longer with thee." }; - if (!priest->mcanmove || priest->msleeping) { + if (helpless(priest)) { pline("%s breaks out of %s reverie!", Monnam(priest), mhis(priest)); priest->mfrozen = priest->msleeping = 0; priest->mcanmove = 1; } priest->mpeaceful = 0; + SetVoice(priest, 0, 80, 0); verbalize1(cranky_msg[rn2(3)]); return; } @@ -570,18 +603,22 @@ register struct monst *priest; /* you desecrated the temple and now you want to chat? */ if (priest->mpeaceful && *in_rooms(priest->mx, priest->my, TEMPLE) && !has_shrine(priest)) { + SetVoice(priest, 0, 80, 0); verbalize( "Begone! Thou desecratest this holy place with thy presence."); priest->mpeaceful = 0; return; } - if (!money_cnt(invent)) { + if (!money_cnt(gi.invent)) { if (coaligned && !strayed) { long pmoney = money_cnt(priest->minvent); if (pmoney > 0L) { + const char *bits; + bits = (Hallucination) ? currency(pmoney) + : (pmoney == 1L) ? "bit" : "bits"; /* Note: two bits is actually 25 cents. Hmm. */ - pline("%s gives you %s for an ale.", Monnam(priest), - (pmoney == 1L) ? "one bit" : "two bits"); + pline("%s gives you %s%s for an ale.", Monnam(priest), + (pmoney == 1L) ? "one " : "two ", bits); money2u(priest, pmoney > 1L ? 2 : 1); } else pline("%s preaches the virtues of poverty.", Monnam(priest)); @@ -590,51 +627,91 @@ register struct monst *priest; pline("%s is not interested.", Monnam(priest)); return; } else { + /* there's now some randomization in how much you need to donate, but + you are given suggested donation values that will guarantee + clairvoyance and protection respectively; with more gold visible + you need to donate more but get a greater effect; and if you + cheapskate out to rerandomize the donation amounts they will be + higher next time */ long offer; + long suggested = (u.ulevelpeak ? u.ulevelpeak : 1 ) * + rn1(101, 150 + (cheapskate ? *cheapskate : 0) * 40); + long quan = money_cnt(gi.invent) / (suggested * 3); + char buf[BUFSZ]; - pline("%s asks you for a contribution for the temple.", - Monnam(priest)); - if ((offer = bribe(priest)) == 0) { + if (quan < 1) + quan = 1; + + Sprintf(buf, "How much will you offer (suggested: %ld or %ld)?", + suggested * quan, suggested * quan * 2); + + if (flags.debug) + pline("%s asks you for a contribution for the temple (base %ld).", + Monnam(priest), suggested); + else + pline("%s asks you for a contribution for the temple.", + Monnam(priest)); + if ((offer = bribe(priest, buf)) == 0) { + SetVoice(priest, 0, 80, 0); verbalize("Thou shalt regret thine action!"); if (coaligned) adjalign(-1); - } else if (offer < (u.ulevel * 200)) { - if (money_cnt(invent) > (offer * 2L)) { + if (cheapskate) ++*cheapskate; + } else if (offer < suggested * quan) { + if (money_cnt(gi.invent) > (offer * 2L)) { + SetVoice(priest, 0, 80, 0); verbalize("Cheapskate."); + if (cheapskate) ++*cheapskate; } else { + SetVoice(priest, 0, 80, 0); verbalize("I thank thee for thy contribution."); /* give player some token */ exercise(A_WIS, TRUE); } - } else if (offer < (u.ulevel * 400)) { + } else if (offer < suggested * quan * 2) { + SetVoice(priest, 0, 80, 0); verbalize("Thou art indeed a pious individual."); - if (money_cnt(invent) < (offer * 2L)) { + if (money_cnt(gi.invent) < (offer * 2L)) { if (coaligned && u.ualign.record <= ALGN_SINNED) adjalign(1); - verbalize("I bestow upon thee a blessing."); - incr_itimeout(&HClairvoyant, rn1(500, 500)); } - } else if (offer < (u.ulevel * 600) - /* u.ublessed is only active when Protection is - enabled via something other than worn gear - (theft by gremlin clears the intrinsic but not - its former magnitude, making it recoverable) */ - && (!(HProtection & INTRINSIC) - || (u.ublessed < 20 - && (u.ublessed < 9 || !rn2(u.ublessed))))) { - verbalize("Thou hast been rewarded for thy devotion."); + verbalize("I bestow upon thee a blessing."); + incr_itimeout(&HClairvoyant, rn1(500 * offer / suggested, + 500 * offer / suggested)); + } else if (offer < suggested * quan * 3) { + int orig_ublessed = u.ublessed; + + /* u.ublessed is only active when Protection is enabled via + something other than worn gear (theft by gremlin clears the + intrinsic but not its former magnitude, making it + recoverable) */ if (!(HProtection & INTRINSIC)) { HProtection |= FROMOUTSIDE; + orig_ublessed = -1; /* force "rewarded" message */ + } + + for (; offer >= (2 * suggested); offer -= (2 * suggested)) { if (!u.ublessed) u.ublessed = rn1(3, 2); - } else - u.ublessed++; + else if (u.ublessed < 20 && + (u.ublessed < 9 || !rn2(u.ublessed))) + u.ublessed++; + } + SetVoice(priest, 0, 80, 0); + if (u.ublessed > orig_ublessed) { + verbalize("Thou hast been rewarded for thy devotion."); + } else { + verbalize("Thy selfless generosity is deeply appreciated."); + } } else { + SetVoice(priest, 0, 80, 0); verbalize("Thy selfless generosity is deeply appreciated."); - if (money_cnt(invent) < (offer * 2L) && coaligned) { - if (strayed && (moves - u.ucleansed) > 5000L) { + /* money_cnt check is preserved for futureproofing but probably + can't fail in the current code */ + if (money_cnt(gi.invent) < (offer * 2L) && coaligned) { + if (strayed && (svm.moves - u.ucleansed) > 5000L) { u.ualign.record = 0; /* cleanse thee */ - u.ucleansed = moves; + u.ucleansed = svm.moves; } else { adjalign(2); } @@ -644,31 +721,28 @@ register struct monst *priest; } struct monst * -mk_roamer(ptr, alignment, x, y, peaceful) -register struct permonst *ptr; -aligntyp alignment; -xchar x, y; -boolean peaceful; +mk_roamer(struct permonst *ptr, aligntyp alignment, coordxy x, coordxy y, + boolean peaceful) { - register struct monst *roamer; - register boolean coaligned = (u.ualign.type == alignment); + struct monst *roamer; + boolean coaligned = (u.ualign.type == alignment); #if 0 /* this was due to permonst's pxlth field which is now gone */ - if (ptr != &mons[PM_ALIGNED_PRIEST] && ptr != &mons[PM_ANGEL]) + if (ptr != &mons[PM_ALIGNED_CLERIC] && ptr != &mons[PM_ANGEL]) return (struct monst *) 0; #endif if (MON_AT(x, y)) - (void) rloc(m_at(x, y), FALSE); /* insurance */ + (void) rloc(m_at(x, y), RLOC_NOMSG); /* insurance */ - if (!(roamer = makemon(ptr, x, y, MM_ADJACENTOK | MM_EMIN))) + if (!(roamer = makemon(ptr, x, y, MM_ADJACENTOK | MM_EMIN | MM_NOMSG))) return (struct monst *) 0; EMIN(roamer)->min_align = alignment; EMIN(roamer)->renegade = (coaligned && !peaceful); roamer->ispriest = 0; roamer->isminion = 1; - roamer->mtrapseen = ~0; /* traps are known */ + mon_learns_traps(roamer, ALL_TRAPS); /* traps are known */ roamer->mpeaceful = peaceful; roamer->msleeping = 0; set_malign(roamer); /* peaceful may have changed */ @@ -678,12 +752,11 @@ boolean peaceful; } void -reset_hostility(roamer) -register struct monst *roamer; +reset_hostility(struct monst *roamer) { if (!roamer->isminion) return; - if (roamer->data != &mons[PM_ALIGNED_PRIEST] + if (roamer->data != &mons[PM_ALIGNED_CLERIC] && roamer->data != &mons[PM_ANGEL]) return; @@ -695,12 +768,12 @@ register struct monst *roamer; } boolean -in_your_sanctuary(mon, x, y) -struct monst *mon; /* if non-null, overrides */ -xchar x, y; +in_your_sanctuary( + struct monst *mon, /* if non-null, overrides */ + coordxy x, coordxy y) { - register char roomno; - register struct monst *priest; + char roomno; + struct monst *priest; if (mon) { if (is_minion(mon->data) || is_rider(mon->data)) @@ -720,20 +793,22 @@ xchar x, y; /* when attacking "priest" in his temple */ void -ghod_hitsu(priest) -struct monst *priest; +ghod_hitsu(struct monst *priest) { - int x, y, ax, ay, roomno = (int) temple_occupied(u.urooms); struct mkroom *troom; + struct monst *oldbuzzer; + struct obj *oldcurrwand; + coordxy x, y, ax, ay; + int roomno = (int) temple_occupied(u.urooms); if (!roomno || !has_shrine(priest)) return; ax = x = EPRI(priest)->shrpos.x; ay = y = EPRI(priest)->shrpos.y; - troom = &rooms[roomno - ROOMOFFSET]; + troom = &svr.rooms[roomno - ROOMOFFSET]; - if ((u.ux == x && u.uy == y) || !linedup(u.ux, u.uy, x, y, 1)) { + if (u_at(x, y) || !linedup(u.ux, u.uy, x, y, 1)) { if (IS_DOOR(levl[u.ux][u.uy].typ)) { if (u.ux == troom->lx - 1) { x = troom->hx; @@ -787,15 +862,21 @@ struct monst *priest; break; } - buzz(-10 - (AD_ELEC - 1), 6, x, y, sgn(tbx), - sgn(tby)); /* bolt of lightning */ + /* bolt of lightning cast by unspecified monster */ + oldcurrwand = gc.current_wand; + gc.current_wand = 0; + oldbuzzer = gb.buzzer; + gb.buzzer = 0; + buzz(BZ_M_SPELL(BZ_OFS_AD(AD_ELEC)), 6, x, y, sgn(gt.tbx), sgn(gt.tby)); + gb.buzzer = oldbuzzer; + gc.current_wand = oldcurrwand; exercise(A_WIS, FALSE); } void -angry_priest() +angry_priest(void) { - register struct monst *priest; + struct monst *priest; struct rm *lev; if ((priest = findpriest(temple_occupied(u.urooms))) != 0) { @@ -818,6 +899,7 @@ angry_priest() newemin(priest); priest->ispriest = 0; /* now a roaming minion */ priest->isminion = 1; + assert(has_emin(priest)); EMIN(priest)->min_align = eprip->shralign; EMIN(priest)->renegade = FALSE; /* discard priest's memory of his former shrine; @@ -834,7 +916,7 @@ angry_priest() * [Perhaps we should convert them into roamers instead?] */ void -clearpriests() +clearpriests(void) { struct monst *mtmp; @@ -848,9 +930,7 @@ clearpriests() /* munge priest-specific structure when restoring -dlc */ void -restpriest(mtmp, ghostly) -register struct monst *mtmp; -boolean ghostly; +restpriest(struct monst *mtmp, boolean ghostly) { if (u.uz.dlevel) { if (ghostly) @@ -858,242 +938,7 @@ boolean ghostly; } } -/* - * align_str(), piousness(), mstatusline() and ustatusline() used to be - * in pline.c, presumeably because the latter two generate one line of - * output. The USE_OLDARGS config gets warnings from 2016ish-vintage - * gcc (for -Wint-to-pointer-cast, activated by -Wall or -W) when they - * follow pline() itself. Fixing up the variadic calls like is done for - * lev_comp would be needlessly messy there. - * - * They don't belong here. If/when enlightenment ever gets split off - * from cmd.c (which definitely doesn't belong there), they should go - * with it. - */ - -const char * -align_str(alignment) -aligntyp alignment; -{ - switch ((int) alignment) { - case A_CHAOTIC: - return "chaotic"; - case A_NEUTRAL: - return "neutral"; - case A_LAWFUL: - return "lawful"; - case A_NONE: - return "unaligned"; - } - return "unknown"; -} - -/* used for self-probing */ -char * -piousness(showneg, suffix) -boolean showneg; -const char *suffix; -{ - static char buf[32]; /* bigger than "insufficiently neutral" */ - const char *pio; - - /* note: piousness 20 matches MIN_QUEST_ALIGN (quest.h) */ - if (u.ualign.record >= 20) - pio = "piously"; - else if (u.ualign.record > 13) - pio = "devoutly"; - else if (u.ualign.record > 8) - pio = "fervently"; - else if (u.ualign.record > 3) - pio = "stridently"; - else if (u.ualign.record == 3) - pio = ""; - else if (u.ualign.record > 0) - pio = "haltingly"; - else if (u.ualign.record == 0) - pio = "nominally"; - else if (!showneg) - pio = "insufficiently"; - else if (u.ualign.record >= -3) - pio = "strayed"; - else if (u.ualign.record >= -8) - pio = "sinned"; - else - pio = "transgressed"; - - Sprintf(buf, "%s", pio); - if (suffix && (!showneg || u.ualign.record >= 0)) { - if (u.ualign.record != 3) - Strcat(buf, " "); - Strcat(buf, suffix); - } - return buf; -} - -/* stethoscope or probing applied to monster -- one-line feedback */ -void -mstatusline(mtmp) -struct monst *mtmp; -{ - aligntyp alignment = mon_aligntyp(mtmp); - char info[BUFSZ], monnambuf[BUFSZ]; - - info[0] = 0; - if (mtmp->mtame) { - Strcat(info, ", tame"); - if (wizard) { - Sprintf(eos(info), " (%d", mtmp->mtame); - if (!mtmp->isminion) - Sprintf(eos(info), "; hungry %ld; apport %d", - EDOG(mtmp)->hungrytime, EDOG(mtmp)->apport); - Strcat(info, ")"); - } - } else if (mtmp->mpeaceful) - Strcat(info, ", peaceful"); - - if (mtmp->data == &mons[PM_LONG_WORM]) { - int segndx, nsegs = count_wsegs(mtmp); - - /* the worm code internals don't consider the head of be one of - the worm's segments, but we count it as such when presenting - worm feedback to the player */ - if (!nsegs) { - Strcat(info, ", single segment"); - } else { - ++nsegs; /* include head in the segment count */ - segndx = wseg_at(mtmp, bhitpos.x, bhitpos.y); - Sprintf(eos(info), ", %d%s of %d segments", - segndx, ordin(segndx), nsegs); - } - } - if (mtmp->cham >= LOW_PM && mtmp->data != &mons[mtmp->cham]) - /* don't reveal the innate form (chameleon, vampire, &c), - just expose the fact that this current form isn't it */ - Strcat(info, ", shapechanger"); - /* pets eating mimic corpses mimic while eating, so this comes first */ - if (mtmp->meating) - Strcat(info, ", eating"); - /* a stethoscope exposes mimic before getting here so this - won't be relevant for it, but wand of probing doesn't */ - if (mtmp->mundetected || mtmp->m_ap_type) - mhidden_description(mtmp, TRUE, eos(info)); - if (mtmp->mcan) - Strcat(info, ", cancelled"); - if (mtmp->mconf) - Strcat(info, ", confused"); - if (mtmp->mblinded || !mtmp->mcansee) - Strcat(info, ", blind"); - if (mtmp->mstun) - Strcat(info, ", stunned"); - if (mtmp->msleeping) - Strcat(info, ", asleep"); -#if 0 /* unfortunately mfrozen covers temporary sleep and being busy \ - (donning armor, for instance) as well as paralysis */ - else if (mtmp->mfrozen) - Strcat(info, ", paralyzed"); -#else - else if (mtmp->mfrozen || !mtmp->mcanmove) - Strcat(info, ", can't move"); -#endif - /* [arbitrary reason why it isn't moving] */ - else if (mtmp->mstrategy & STRAT_WAITMASK) - Strcat(info, ", meditating"); - if (mtmp->mflee) - Strcat(info, ", scared"); - if (mtmp->mtrapped) - Strcat(info, ", trapped"); - if (mtmp->mspeed) - Strcat(info, (mtmp->mspeed == MFAST) ? ", fast" - : (mtmp->mspeed == MSLOW) ? ", slow" - : ", [? speed]"); - if (mtmp->minvis) - Strcat(info, ", invisible"); - if (mtmp == u.ustuck) - Strcat(info, sticks(youmonst.data) ? ", held by you" - : !u.uswallow ? ", holding you" - : attacktype_fordmg(u.ustuck->data, AT_ENGL, AD_DGST) - ? ", digesting you" - : is_animal(u.ustuck->data) ? ", swallowing you" - : ", engulfing you"); - if (mtmp == u.usteed) - Strcat(info, ", carrying you"); - - /* avoid "Status of the invisible newt ..., invisible" */ - /* and unlike a normal mon_nam, use "saddled" even if it has a name */ - Strcpy(monnambuf, x_monnam(mtmp, ARTICLE_THE, (char *) 0, - (SUPPRESS_IT | SUPPRESS_INVISIBLE), FALSE)); - - pline("Status of %s (%s): Level %d HP %d(%d) AC %d%s.", monnambuf, - align_str(alignment), mtmp->m_lev, mtmp->mhp, mtmp->mhpmax, - find_mac(mtmp), info); -} - -/* stethoscope or probing applied to hero -- one-line feedback */ -void -ustatusline() -{ - char info[BUFSZ]; - - info[0] = '\0'; - if (Sick) { - Strcat(info, ", dying from"); - if (u.usick_type & SICK_VOMITABLE) - Strcat(info, " food poisoning"); - if (u.usick_type & SICK_NONVOMITABLE) { - if (u.usick_type & SICK_VOMITABLE) - Strcat(info, " and"); - Strcat(info, " illness"); - } - } - if (Stoned) - Strcat(info, ", solidifying"); - if (Slimed) - Strcat(info, ", becoming slimy"); - if (Strangled) - Strcat(info, ", being strangled"); - if (Vomiting) - Strcat(info, ", nauseated"); /* !"nauseous" */ - if (Confusion) - Strcat(info, ", confused"); - if (Blind) { - Strcat(info, ", blind"); - if (u.ucreamed) { - if ((long) u.ucreamed < Blinded || Blindfolded - || !haseyes(youmonst.data)) - Strcat(info, ", cover"); - Strcat(info, "ed by sticky goop"); - } /* note: "goop" == "glop"; variation is intentional */ - } - if (Stunned) - Strcat(info, ", stunned"); - if (!u.usteed && Wounded_legs) { - const char *what = body_part(LEG); - if ((Wounded_legs & BOTH_SIDES) == BOTH_SIDES) - what = makeplural(what); - Sprintf(eos(info), ", injured %s", what); - } - if (Glib) - Sprintf(eos(info), ", slippery %s", makeplural(body_part(HAND))); - if (u.utrap) - Strcat(info, ", trapped"); - if (Fast) - Strcat(info, Very_fast ? ", very fast" : ", fast"); - if (u.uundetected) - Strcat(info, ", concealed"); - if (Invis) - Strcat(info, ", invisible"); - if (u.ustuck) { - if (sticks(youmonst.data)) - Strcat(info, ", holding "); - else - Strcat(info, ", held by "); - Strcat(info, mon_nam(u.ustuck)); - } - - pline("Status of %s (%s): Level %d HP %d(%d) AC %d%s.", plname, - piousness(FALSE, align_str(u.ualign.type)), - Upolyd ? mons[u.umonnum].mlevel : u.ulevel, Upolyd ? u.mh : u.uhp, - Upolyd ? u.mhmax : u.uhpmax, u.uac, info); -} +#undef ALGN_SINNED +#undef ALGN_DEVOUT /*priest.c*/ diff --git a/src/quest.c b/src/quest.c index df4b83155..d57f1712b 100644 --- a/src/quest.c +++ b/src/quest.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 quest.c $NHDT-Date: 1505170343 2017/09/11 22:52:23 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.21 $ */ +/* NetHack 5.0 quest.c $NHDT-Date: 1774269965 2026/03/23 04:46:05 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.46 $ */ /* Copyright 1991, M. Stephenson */ /* NetHack may be freely redistributed. See license for details. */ @@ -7,38 +7,37 @@ /* quest dungeon branch routines. */ #include "quest.h" -#include "qtext.h" #define Not_firsttime (on_level(&u.uz0, &u.uz)) -#define Qstat(x) (quest_status.x) - -STATIC_DCL void NDECL(on_start); -STATIC_DCL void NDECL(on_locate); -STATIC_DCL void NDECL(on_goal); -STATIC_DCL boolean NDECL(not_capable); -STATIC_DCL int FDECL(is_pure, (BOOLEAN_P)); -STATIC_DCL void FDECL(expulsion, (BOOLEAN_P)); -STATIC_DCL void NDECL(chat_with_leader); -STATIC_DCL void NDECL(chat_with_nemesis); -STATIC_DCL void NDECL(chat_with_guardian); -STATIC_DCL void FDECL(prisoner_speaks, (struct monst *)); - -STATIC_OVL void -on_start() +#define Qstat(x) (svq.quest_status.x) + +staticfn void on_start(void); +staticfn void on_locate(void); +staticfn void on_goal(void); +staticfn boolean not_capable(void); +staticfn int is_pure(boolean); +staticfn void expulsion(boolean); +staticfn void chat_with_leader(struct monst *); +staticfn void chat_with_nemesis(void); +staticfn void chat_with_guardian(void); +staticfn void prisoner_speaks(struct monst *); + +staticfn void +on_start(void) { if (!Qstat(first_start)) { - qt_pager(QT_FIRSTTIME); + qt_pager("firsttime"); Qstat(first_start) = TRUE; } else if ((u.uz0.dnum != u.uz.dnum) || (u.uz0.dlevel < u.uz.dlevel)) { if (Qstat(not_ready) <= 2) - qt_pager(QT_NEXTTIME); + qt_pager("nexttime"); else - qt_pager(QT_OTHERTIME); + qt_pager("othertime"); } } -STATIC_OVL void -on_locate() +staticfn void +on_locate(void) { /* the locate messages are phrased in a manner such that they only make sense when arriving on the level from above */ @@ -48,24 +47,24 @@ on_locate() return; } else if (!Qstat(first_locate)) { if (from_above) - qt_pager(QT_FIRSTLOCATE); + qt_pager("locate_first"); /* if we've arrived from below this will be a lie, but there won't be any point in delivering the message upon a return visit from above later since the level has now been seen */ Qstat(first_locate) = TRUE; } else { if (from_above) - qt_pager(QT_NEXTLOCATE); + qt_pager("locate_next"); } } -STATIC_OVL void -on_goal() +staticfn void +on_goal(void) { if (Qstat(killed_nemesis)) { return; } else if (!Qstat(made_goal)) { - qt_pager(QT_FIRSTGOAL); + qt_pager("goal_first"); Qstat(made_goal) = 1; } else { /* @@ -81,14 +80,14 @@ on_goal() | (1 << OBJ_BURIED)); struct obj *qarti = find_quest_artifact(whichobjchains); - qt_pager(qarti ? QT_NEXTGOAL : QT_ALTGOAL); + qt_pager(qarti ? "goal_next" : "goal_alt"); if (Qstat(made_goal) < 7) Qstat(made_goal)++; } } void -onquest() +onquest(void) { if (u.uevent.qcompleted || Not_firsttime) return; @@ -105,46 +104,53 @@ onquest() } void -nemdead() +nemdead(void) { if (!Qstat(killed_nemesis)) { Qstat(killed_nemesis) = TRUE; - qt_pager(QT_KILLEDNEM); + qt_pager("killed_nemesis"); } } void -artitouch(obj) -struct obj *obj; +leaddead(void) +{ + if (!Qstat(killed_leader)) { + Qstat(killed_leader) = TRUE; + /* TODO: qt_pager("killed_leader"); ? */ + } +} + +void +artitouch(struct obj *obj) { if (!Qstat(touched_artifact)) { /* in case we haven't seen the item yet (ie, currently blinded), this quest message describes it by name so mark it as seen */ - obj->dknown = 1; + observe_object(obj); /* only give this message once */ Qstat(touched_artifact) = TRUE; - qt_pager(QT_GOTIT); + qt_pager("gotit"); exercise(A_WIS, TRUE); } } /* external hook for do.c (level change check) */ boolean -ok_to_quest() +ok_to_quest(void) { - return (boolean) ((Qstat(got_quest) || Qstat(got_thanks)) - && is_pure(FALSE) > 0); + return (boolean) (((Qstat(got_quest) || Qstat(got_thanks)) + && is_pure(FALSE) > 0) || Qstat(killed_leader)); } -STATIC_OVL boolean -not_capable() +staticfn boolean +not_capable(void) { return (boolean) (u.ulevel < MIN_QUEST_LEVEL); } -STATIC_OVL int -is_pure(talk) -boolean talk; +staticfn int +is_pure(boolean talk) { int purity; aligntyp original_alignment = u.ualignbase[A_ORIGINAL]; @@ -158,7 +164,7 @@ boolean talk; } else if (u.ualign.record < MIN_QUEST_ALIGN) { You("are currently %d and require %d.", u.ualign.record, MIN_QUEST_ALIGN); - if (yn_function("adjust?", (char *) 0, 'y') == 'y') + if (yn_function("adjust?", (char *) 0, 'y', TRUE) == 'y') u.ualign.record = MIN_QUEST_ALIGN; } } @@ -176,20 +182,20 @@ boolean talk; * This assumes that the hero is currently _in_ the quest dungeon and that * there is a single branch to and from it. */ -STATIC_OVL void -expulsion(seal) -boolean seal; +staticfn void +expulsion(boolean seal) { branch *br; d_level *dest; struct trap *t; - int portal_flag; + int portal_flag = u.uevent.qexpelled ? UTOTYPE_NONE : UTOTYPE_PORTAL; br = dungeon_branch("The Quest"); dest = (br->end1.dnum == u.uz.dnum) ? &br->end2 : &br->end1; - portal_flag = u.uevent.qexpelled ? 0 /* returned via artifact? */ - : !seal ? 1 : -1; - schedule_goto(dest, FALSE, FALSE, portal_flag, (char *) 0, (char *) 0); + if (seal) + portal_flag |= UTOTYPE_RMPORTAL; + nomul(0); /* stop running */ + schedule_goto(dest, portal_flag, (char *) 0, (char *) 0); if (seal) { /* remove the portal to the quest - sealing it off */ int reexpelled = u.uevent.qexpelled; @@ -199,7 +205,7 @@ boolean seal; portal will be deleted as part of arrival on that level. If monster movement is in progress, any who haven't moved yet will now miss out on a chance to wander through it... */ - for (t = ftrap; t; t = t->ntrap) + for (t = gf.ftrap; t; t = t->ntrap) if (t->ttyp == MAGIC_PORTAL) break; if (t) @@ -213,24 +219,53 @@ boolean seal; artifact or you've just thrown it to/at him or her. If quest completion text hasn't been given yet, give it now. Otherwise give another message about the character keeping the artifact - and using the magic portal to return to the dungeon. */ + and using the magic portal to return to the dungeon. Also called + if hero throws or kicks an invocation item (probably the Bell) + at the leader. */ void -finish_quest(obj) -struct obj *obj; /* quest artifact; possibly null if carrying Amulet */ +finish_quest(struct obj *obj) /* quest artifact or thrown unique item or faux + * AoY; possibly null if carrying the Amulet */ { struct obj *otmp; - if (u.uhave.amulet) { /* unlikely but not impossible */ - qt_pager(QT_HASAMULET); + if (obj && !is_quest_artifact(obj)) { + /* tossed an invocation item (or [fake] AoY) at the quest leader */ + if (Deaf) + return; /* optional (unlike quest completion) so skip if deaf */ + /* do ID first so that the message identifying the item will refer to + it by name (and so justify the ID we already gave...) */ + fully_identify_obj(obj); + /* update_inventory() is not necessary or helpful here because item + was thrown, so isn't currently in inventory anyway */ + if (obj->otyp == AMULET_OF_YENDOR) { + qt_pager("hasamulet"); + } else if (obj->otyp == FAKE_AMULET_OF_YENDOR) { + verbalize( + "Sorry to say, this is a mere imitation of the true Amulet of Yendor."); + } else { + verbalize("Ah, I see you've found %s.", the(xname(obj))); + } + return; + } + + if (u.uhave.amulet) { + /* has the amulet in inventory -- most likely the player has already + completed the quest and stopped in on her way back up, but it's not + impossible to have gotten the amulet before formally presenting the + quest artifact to the leader. */ + qt_pager("hasamulet"); /* leader IDs the real amulet but ignores any fakes */ - if ((otmp = carrying(AMULET_OF_YENDOR)) != 0) + if ((otmp = carrying(AMULET_OF_YENDOR)) != (struct obj *) 0) { fully_identify_obj(otmp); + update_inventory(); + } } else { - qt_pager(!Qstat(got_thanks) ? QT_OFFEREDIT : QT_OFFEREDIT2); + /* normal quest completion; threw artifact or walked up carrying it */ + qt_pager(!Qstat(got_thanks) ? "offeredit" : "offeredit2"); /* should have obtained bell during quest; if not, suggest returning for it now */ if ((otmp = carrying(BELL_OF_OPENING)) == 0) - com_pager(5); + com_pager("quest_complete_no_bell"); } Qstat(got_thanks) = TRUE; @@ -243,9 +278,12 @@ struct obj *obj; /* quest artifact; possibly null if carrying Amulet */ } } -STATIC_OVL void -chat_with_leader() +staticfn void +chat_with_leader(struct monst *mtmp) { + if (!mtmp->mpeaceful || Qstat(pissed_off)) + return; + /* Rule 0: Cheater checks. */ if (u.uhave.questart && !Qstat(met_nemesis)) Qstat(cheater) = TRUE; @@ -260,13 +298,13 @@ chat_with_leader() /* Rule 2: You've gone back before going for the amulet. */ else - qt_pager(QT_POSTHANKS); + qt_pager("posthanks"); /* Rule 3: You've got the artifact and are back to return it. */ } else if (u.uhave.questart) { struct obj *otmp; - for (otmp = invent; otmp; otmp = otmp->nobj) + for (otmp = gi.invent; otmp; otmp = otmp->nobj) if (is_quest_artifact(otmp)) break; @@ -274,16 +312,18 @@ chat_with_leader() /* Rule 4: You haven't got the artifact yet. */ } else if (Qstat(got_quest)) { - qt_pager(rn1(10, QT_ENCOURAGE)); + qt_pager("encourage"); /* Rule 5: You aren't yet acceptable - or are you? */ } else { + int purity = 0; + if (!Qstat(met_leader)) { - qt_pager(QT_FIRSTLEADER); + qt_pager("leader_first"); Qstat(met_leader) = TRUE; Qstat(not_ready) = 0; } else - qt_pager(QT_NEXTLEADER); + qt_pager("leader_next"); /* the quest leader might have passed through the portal into the regular dungeon; none of the remaining make sense there */ @@ -291,36 +331,53 @@ chat_with_leader() return; if (not_capable()) { - qt_pager(QT_BADLEVEL); + qt_pager("badlevel"); exercise(A_WIS, TRUE); expulsion(FALSE); - } else if (is_pure(TRUE) < 0) { - com_pager(QT_BANISHED); - expulsion(TRUE); - } else if (is_pure(TRUE) == 0) { - qt_pager(QT_BADALIGN); - if (Qstat(not_ready) == MAX_QUEST_TRIES) { - qt_pager(QT_LASTLEADER); - expulsion(TRUE); - } else { - Qstat(not_ready)++; - exercise(A_WIS, TRUE); + } else if ((purity = is_pure(TRUE)) < 0) { + if (!Qstat(pissed_off)) { + com_pager("banished"); + Qstat(pissed_off) = TRUE; expulsion(FALSE); + + /* being expelled is hardly an achievement but none of the + other livelog classifications fit */ + livelog_printf(LL_ACHIEVE, + "%s has expelled you from the quest", + noit_mon_nam(mtmp)); } + } else if (purity == 0) { + qt_pager("badalign"); + Qstat(not_ready) = 1; + exercise(A_WIS, TRUE); + expulsion(FALSE); } else { /* You are worthy! */ - qt_pager(QT_ASSIGNQUEST); + qt_pager("assignquest"); exercise(A_WIS, TRUE); Qstat(got_quest) = TRUE; + + /* phrasing is a bit clumsy but allows #chronicle to provide a + clue to players who are reaching the quest for first time; + matters most for Home 1 that has stairs down which aren't + easily found */ + livelog_printf(LL_ACHIEVE, + "%s has granted access to proceed deeper into the quest", + noit_mon_nam(mtmp)); } } } void -leader_speaks(mtmp) -struct monst *mtmp; +leader_speaks(struct monst *mtmp) { /* maybe you attacked leader? */ if (!mtmp->mpeaceful) { + if (!Qstat(pissed_off)) { + /* again, don't end it permanently if the leader gets angry + * since you're going to have to kill him to go questing... :) + * ...but do only show this crap once. */ + qt_pager("leader_last"); + } Qstat(pissed_off) = TRUE; mtmp->mstrategy &= ~STRAT_WAITMASK; /* end the inaction */ } @@ -329,63 +386,76 @@ struct monst *mtmp; if (!on_level(&u.uz, &qstart_level)) return; - if (Qstat(pissed_off)) { - qt_pager(QT_LASTLEADER); - expulsion(TRUE); - } else - chat_with_leader(); + if (!Qstat(pissed_off)) + chat_with_leader(mtmp); } -STATIC_OVL void -chat_with_nemesis() +staticfn void +chat_with_nemesis(void) { /* The nemesis will do most of the talking, but... */ - qt_pager(rn1(10, QT_DISCOURAGE)); + qt_pager("discourage"); if (!Qstat(met_nemesis)) Qstat(met_nemesis++); } void -nemesis_speaks() +nemesis_speaks(void) { if (!Qstat(in_battle)) { if (u.uhave.questart) - qt_pager(QT_NEMWANTSIT); + qt_pager("nemesis_wantsit"); else if (Qstat(made_goal) == 1 || !Qstat(met_nemesis)) - qt_pager(QT_FIRSTNEMESIS); + qt_pager("nemesis_first"); else if (Qstat(made_goal) < 4) - qt_pager(QT_NEXTNEMESIS); + qt_pager("nemesis_next"); else if (Qstat(made_goal) < 7) - qt_pager(QT_OTHERNEMESIS); + qt_pager("nemesis_other"); else if (!rn2(5)) - qt_pager(rn1(10, QT_DISCOURAGE)); + qt_pager("discourage"); if (Qstat(made_goal) < 7) Qstat(made_goal)++; Qstat(met_nemesis) = TRUE; } else /* he will spit out random maledictions */ if (!rn2(5)) - qt_pager(rn1(10, QT_DISCOURAGE)); + qt_pager("discourage"); } -STATIC_OVL void -chat_with_guardian() +/* create cloud of stinking gas around dying nemesis */ +void +nemesis_stinks(coordxy mx, coordxy my) +{ + boolean save_mon_moving = svc.context.mon_moving; + + /* + * Some nemeses (determined by caller) release a cloud of noxious + * gas when they die. Don't make the hero be responsible for such + * a cloud even if hero has just killed nemesis. + */ + svc.context.mon_moving = TRUE; + create_gas_cloud(mx, my, 5, 8); + svc.context.mon_moving = save_mon_moving; +} + +staticfn void +chat_with_guardian(void) { /* These guys/gals really don't have much to say... */ if (u.uhave.questart && Qstat(killed_nemesis)) - qt_pager(rn1(5, QT_GUARDTALK2)); + qt_pager("guardtalk_after"); else - qt_pager(rn1(5, QT_GUARDTALK)); + qt_pager("guardtalk_before"); } -STATIC_OVL void -prisoner_speaks(mtmp) -struct monst *mtmp; +staticfn void +prisoner_speaks(struct monst *mtmp) { if (mtmp->data == &mons[PM_PRISONER] && (mtmp->mstrategy & STRAT_WAITMASK)) { /* Awaken the prisoner */ if (canseemon(mtmp)) pline("%s speaks:", Monnam(mtmp)); + SetVoice(mtmp, 0, 80, 0); verbalize("I'm finally free!"); mtmp->mstrategy &= ~STRAT_WAITMASK; mtmp->mpeaceful = 1; @@ -400,11 +470,13 @@ struct monst *mtmp; } void -quest_chat(mtmp) -register struct monst *mtmp; +quest_chat(struct monst *mtmp) { if (mtmp->m_id == Qstat(leader_m_id)) { - chat_with_leader(); + chat_with_leader(mtmp); + /* leader might have become pissed during the chat */ + if (Qstat(pissed_off)) + setmangry(mtmp, FALSE); return; } switch (mtmp->data->msound) { @@ -420,8 +492,7 @@ register struct monst *mtmp; } void -quest_talk(mtmp) -struct monst *mtmp; +quest_talk(struct monst *mtmp) { if (mtmp->m_id == Qstat(leader_m_id)) { leader_speaks(mtmp); @@ -440,12 +511,13 @@ struct monst *mtmp; } void -quest_stat_check(mtmp) -struct monst *mtmp; +quest_stat_check(struct monst *mtmp) { if (mtmp->data->msound == MS_NEMESIS) - Qstat(in_battle) = (mtmp->mcanmove && !mtmp->msleeping - && monnear(mtmp, u.ux, u.uy)); + Qstat(in_battle) = (!helpless(mtmp) && monnear(mtmp, u.ux, u.uy)); } +#undef Not_firsttime +#undef Qstat + /*quest.c*/ diff --git a/src/questpgr.c b/src/questpgr.c index 5c04c6cdf..96a3c5ad7 100644 --- a/src/questpgr.c +++ b/src/questpgr.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 questpgr.c $NHDT-Date: 1505172128 2017/09/11 23:22:08 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.38 $ */ +/* NetHack 5.0 questpgr.c $NHDT-Date: 1704043695 2023/12/31 17:28:15 $ $NHDT-Branch: keni-luabits2 $:$NHDT-Revision: 1.87 $ */ /* Copyright 1991, M. Stephenson */ /* NetHack may be freely redistributed. See license for details. */ @@ -7,165 +7,38 @@ /* quest-specific pager routines. */ -#include "qtext.h" - -#define QTEXT_FILE "quest.dat" +#define QTEXT_FILE "quest.lua" #ifdef TTY_GRAPHICS #include "wintty.h" #endif -/* from sp_lev.c, for deliver_splev_message() */ -extern char *lev_message; - -static void NDECL(dump_qtlist); -static void FDECL(Fread, (genericptr_t, int, int, dlb *)); -STATIC_DCL struct qtmsg *FDECL(construct_qtlist, (long)); -STATIC_DCL const char *NDECL(intermed); -STATIC_DCL struct obj *FDECL(find_qarti, (struct obj *)); -STATIC_DCL const char *NDECL(neminame); -STATIC_DCL const char *NDECL(guardname); -STATIC_DCL const char *NDECL(homebase); -STATIC_DCL void FDECL(qtext_pronoun, (CHAR_P, CHAR_P)); -STATIC_DCL struct qtmsg *FDECL(msg_in, (struct qtmsg *, int)); -STATIC_DCL void FDECL(convert_arg, (CHAR_P)); -STATIC_DCL void FDECL(convert_line, (char *,char *)); -STATIC_DCL void FDECL(deliver_by_pline, (struct qtmsg *)); -STATIC_DCL void FDECL(deliver_by_window, (struct qtmsg *, int)); -STATIC_DCL boolean FDECL(skip_pager, (BOOLEAN_P)); - -static char cvt_buf[64]; -static struct qtlists qt_list; -static dlb *msg_file; -/* used by ldrname() and neminame(), then copied into cvt_buf */ -static char nambuf[sizeof cvt_buf]; - -/* dump the character msg list to check appearance; - build with DEBUG enabled and use DEBUGFILES=questpgr.c - in sysconf file or environment */ -static void -dump_qtlist() -{ -#ifdef DEBUG - struct qtmsg *msg; - - if (!explicitdebug(__FILE__)) - return; - - for (msg = qt_list.chrole; msg->msgnum > 0; msg++) { - (void) dlb_fseek(msg_file, msg->offset, SEEK_SET); - deliver_by_window(msg, NHW_MAP); - } -#endif /* DEBUG */ - return; -} - -static void -Fread(ptr, size, nitems, stream) -genericptr_t ptr; -int size, nitems; -dlb *stream; -{ - int cnt; - - if ((cnt = dlb_fread(ptr, size, nitems, stream)) != nitems) { - panic("PREMATURE EOF ON QUEST TEXT FILE! Expected %d bytes, got %d", - (size * nitems), (size * cnt)); - } -} - -STATIC_OVL struct qtmsg * -construct_qtlist(hdr_offset) -long hdr_offset; -{ - struct qtmsg *msg_list; - int n_msgs; - - (void) dlb_fseek(msg_file, hdr_offset, SEEK_SET); - Fread(&n_msgs, sizeof(int), 1, msg_file); - msg_list = (struct qtmsg *) alloc((unsigned) (n_msgs + 1) - * sizeof (struct qtmsg)); - - /* - * Load up the list. - */ - Fread((genericptr_t) msg_list, n_msgs * sizeof (struct qtmsg), 1, - msg_file); - - msg_list[n_msgs].msgnum = -1; - return msg_list; -} - -void -load_qtlist() -{ - int n_classes, i; - char qt_classes[N_HDR][LEN_HDR]; - long qt_offsets[N_HDR]; - - msg_file = dlb_fopen(QTEXT_FILE, RDBMODE); - if (!msg_file) - panic("CANNOT OPEN QUEST TEXT FILE %s.", QTEXT_FILE); - - /* - * Read in the number of classes, then the ID's & offsets for - * each header. - */ - - Fread(&n_classes, sizeof (int), 1, msg_file); - Fread(&qt_classes[0][0], sizeof (char) * LEN_HDR, n_classes, msg_file); - Fread(qt_offsets, sizeof (long), n_classes, msg_file); - - /* - * Now construct the message lists for quick reference later - * on when we are actually paging the messages out. - */ - - qt_list.common = qt_list.chrole = (struct qtmsg *) 0; - - for (i = 0; i < n_classes; i++) { - if (!strncmp(COMMON_ID, qt_classes[i], LEN_HDR)) - qt_list.common = construct_qtlist(qt_offsets[i]); - else if (!strncmp(urole.filecode, qt_classes[i], LEN_HDR)) - qt_list.chrole = construct_qtlist(qt_offsets[i]); -#if 0 /* UNUSED but available */ - else if (!strncmp(urace.filecode, qt_classes[i], LEN_HDR)) - qt_list.chrace = construct_qtlist(qt_offsets[i]); -#endif - } - - if (!qt_list.common || !qt_list.chrole) - impossible("load_qtlist: cannot load quest text."); - dump_qtlist(); - return; /* no ***DON'T*** close the msg_file */ -} - -/* called at program exit */ -void -unload_qtlist() -{ - if (msg_file) - (void) dlb_fclose(msg_file), msg_file = 0; - if (qt_list.common) - free((genericptr_t) qt_list.common), qt_list.common = 0; - if (qt_list.chrole) - free((genericptr_t) qt_list.chrole), qt_list.chrole = 0; - return; -} +staticfn const char *intermed(void); +/* sometimes find_qarti(gi.invent), and gi.invent can be null */ +staticfn struct obj *find_qarti(struct obj *) NO_NNARGS; +staticfn const char *neminame(void); +staticfn const char *guardname(void); +staticfn const char *homebase(void); +staticfn void qtext_pronoun(char, char); +staticfn void convert_arg(char); +staticfn void convert_line(char *,char *); +staticfn void deliver_by_pline(const char *); +staticfn void deliver_by_window(const char *, int); +staticfn boolean skip_pager(boolean); +staticfn boolean com_pager_core(const char *, const char *, boolean, char **); short -quest_info(typ) -int typ; +quest_info(int typ) { switch (typ) { case 0: - return urole.questarti; + return gu.urole.questarti; case MS_LEADER: - return urole.ldrnum; + return gu.urole.ldrnum; case MS_NEMESIS: - return urole.neminum; + return gu.urole.neminum; case MS_GUARDIAN: - return urole.guardnum; + return gu.urole.guardnum; default: impossible("quest_info(%d)", typ); } @@ -174,32 +47,30 @@ int typ; /* return your role leader's name */ const char * -ldrname() +ldrname(void) { - int i = urole.ldrnum; + int i = gu.urole.ldrnum; - Sprintf(nambuf, "%s%s", type_is_pname(&mons[i]) ? "" : "the ", - mons[i].mname); - return nambuf; + Sprintf(gn.nambuf, "%s%s", type_is_pname(&mons[i]) ? "" : "the ", + mons[i].pmnames[NEUTRAL]); + return gn.nambuf; } /* return your intermediate target string */ -STATIC_OVL const char * -intermed() +staticfn const char * +intermed(void) { - return urole.intermed; + return gu.urole.intermed; } boolean -is_quest_artifact(otmp) -struct obj *otmp; +is_quest_artifact(struct obj *otmp) { - return (boolean) (otmp->oartifact == urole.questarti); + return (boolean) (otmp->oartifact == gu.urole.questarti); } -STATIC_OVL struct obj * -find_qarti(ochain) -struct obj *ochain; +staticfn struct obj * +find_qarti(struct obj *ochain) { struct obj *otmp, *qarti; @@ -215,14 +86,13 @@ struct obj *ochain; /* check several object chains for the quest artifact to determine whether it is present on the current level */ struct obj * -find_quest_artifact(whichchains) -unsigned whichchains; +find_quest_artifact(unsigned whichchains) { struct monst *mtmp; struct obj *qarti = 0; if ((whichchains & (1 << OBJ_INVENT)) != 0) - qarti = find_qarti(invent); + qarti = find_qarti(gi.invent); if (!qarti && (whichchains & (1 << OBJ_FLOOR)) != 0) qarti = find_qarti(fobj); if (!qarti && (whichchains & (1 << OBJ_MINVENT)) != 0) @@ -234,55 +104,104 @@ unsigned whichchains; } if (!qarti && (whichchains & (1 << OBJ_MIGRATING)) != 0) { /* check migrating objects and minvent of migrating monsters */ - for (mtmp = migrating_mons; mtmp; mtmp = mtmp->nmon) { + for (mtmp = gm.migrating_mons; mtmp; mtmp = mtmp->nmon) { if (DEADMONSTER(mtmp)) continue; if ((qarti = find_qarti(mtmp->minvent)) != 0) break; } if (!qarti) - qarti = find_qarti(migrating_objs); + qarti = find_qarti(gm.migrating_objs); } if (!qarti && (whichchains & (1 << OBJ_BURIED)) != 0) - qarti = find_qarti(level.buriedobjlist); + qarti = find_qarti(svl.level.buriedobjlist); return qarti; } /* return your role nemesis' name */ -STATIC_OVL const char * -neminame() +staticfn const char * +neminame(void) { - int i = urole.neminum; + int i = gu.urole.neminum; - Sprintf(nambuf, "%s%s", type_is_pname(&mons[i]) ? "" : "the ", - mons[i].mname); - return nambuf; + Sprintf(gn.nambuf, "%s%s", type_is_pname(&mons[i]) ? "" : "the ", + mons[i].pmnames[NEUTRAL]); + return gn.nambuf; } -STATIC_OVL const char * -guardname() /* return your role leader's guard monster name */ +staticfn const char * +guardname(void) /* return your role leader's guard monster name */ { - int i = urole.guardnum; + int i = gu.urole.guardnum; - return mons[i].mname; + return mons[i].pmnames[NEUTRAL]; } -STATIC_OVL const char * -homebase() /* return your role leader's location */ +staticfn const char * +homebase(void) /* return your role leader's location */ { - return urole.homebase; + return gu.urole.homebase; +} + +/* returns 1 if nemesis death message mentions noxious fumes, otherwise 0; + does not display the message */ +int +stinky_nemesis(struct monst *mon) +{ + char *mesg = 0; + int res = 0; + +#if 0 + /* get the quest text for dying nemesis; don't assume that mon is + hero's own role's nemesis (overkill since m_detach() and nemdead() + both make that assumption--valid for normal play but not necessarily + valid for wizard mode) */ + int r, mndx = monsndx(mon->data); + for (r = 0; roles[r].name.m || roles[r].name.f; ++r) + if (roles[r].neminum == mndx) { + (void) com_pager_core(roles[r].filecode, "killed_nemesis", + FALSE, &mesg); + break; + } +#else + nhUse(mon); + /* since nemdead() just gave the message for hero's nemesis even if 'mon' + is some other role's nemesis (feasible in wizard mode), base any gas + cloud on the text that was shown even if not appropriate for 'mon' */ + (void) com_pager_core(gu.urole.filecode, "killed_nemesis", FALSE, &mesg); +#endif + + /* this is somewhat fragile; it assumes that when both {noxious or + poisonous or toxic} and {gas or fumes} are present, the latter + refers to the former rather than to something unrelated; it does + make sure that fumes occurs after noxious rather than before */ + if (mesg) { + char *p; + + /* change newlines into spaces to cope with "...noxious\nfumes..." */ + (void) strNsubst(mesg, "\n", " ", 0); + + if (((p = strstri(mesg, "noxious")) != 0 + || (p = strstri(mesg, "poisonous")) != 0 + || (p = strstri(mesg, "toxic")) != 0) + && (strstri(p, " gas") || strstri(p, " fumes"))) + res = 1; + + free((genericptr_t) mesg); + } + return res; } /* replace deity, leader, nemesis, or artifact name with pronoun; overwrites cvt_buf[] */ -STATIC_OVL void -qtext_pronoun(who, which) -char who, /* 'd' => deity, 'l' => leader, 'n' => nemesis, 'o' => artifact */ - which; /* 'h'|'H'|'i'|'I'|'j'|'J' */ +staticfn void +qtext_pronoun( + char who, /* 'd' => deity, 'l' => leader, 'n' => nemesis, 'o' => arti */ + char which) /* 'h'|'H'|'i'|'I'|'j'|'J' */ { const char *pnoun; - int g; + int godgend; char lwhich = lowc(which); /* H,I,J -> h,i,j */ /* @@ -292,53 +211,39 @@ char who, /* 'd' => deity, 'l' => leader, 'n' => nemesis, 'o' => artifact */ * which genders[] doesn't handle; cvt_buf[] already contains name. */ if (who == 'o' - && (strstri(cvt_buf, "Eyes ") - || strcmpi(cvt_buf, makesingular(cvt_buf)))) { + && (strstri(gc.cvt_buf, "Eyes ") + || strcmpi(gc.cvt_buf, makesingular(gc.cvt_buf)))) { pnoun = (lwhich == 'h') ? "they" : (lwhich == 'i') ? "them" : (lwhich == 'j') ? "their" : "?"; } else { - g = (who == 'd') ? quest_status.godgend - : (who == 'l') ? quest_status.ldrgend - : (who == 'n') ? quest_status.nemgend + godgend = (who == 'd') ? svq.quest_status.godgend + : (who == 'l') ? svq.quest_status.ldrgend + : (who == 'n') ? svq.quest_status.nemgend : 2; /* default to neuter */ - pnoun = (lwhich == 'h') ? genders[g].he - : (lwhich == 'i') ? genders[g].him - : (lwhich == 'j') ? genders[g].his : "?"; + pnoun = (lwhich == 'h') ? genders[godgend].he + : (lwhich == 'i') ? genders[godgend].him + : (lwhich == 'j') ? genders[godgend].his : "?"; } - Strcpy(cvt_buf, pnoun); + Strcpy(gc.cvt_buf, pnoun); /* capitalize for H,I,J */ if (lwhich != which) - cvt_buf[0] = highc(cvt_buf[0]); + gc.cvt_buf[0] = highc(gc.cvt_buf[0]); return; } -STATIC_OVL struct qtmsg * -msg_in(qtm_list, msgnum) -struct qtmsg *qtm_list; -int msgnum; +staticfn void +convert_arg(char c) { - struct qtmsg *qt_msg; - - for (qt_msg = qtm_list; qt_msg->msgnum > 0; qt_msg++) - if (qt_msg->msgnum == msgnum) - return qt_msg; - - return (struct qtmsg *) 0; -} - -STATIC_OVL void -convert_arg(c) -char c; -{ - register const char *str; + const char *str; switch (c) { case 'p': - str = plname; + str = svp.plname; break; case 'c': - str = (flags.female && urole.name.f) ? urole.name.f : urole.name.m; + str = (flags.female && gu.urole.name.f) ? gu.urole.name.f + : gu.urole.name.m; break; case 'r': str = rank_of(u.ulevel, Role_switch, flags.female); @@ -360,7 +265,7 @@ char c; break; case 'O': case 'o': - str = the(artiname(urole.questarti)); + str = the(artiname(gu.urole.questarti)); if (c == 'O') { /* shorten "the Foo of Bar" to "the Foo" (buffer returned by the() is modifiable) */ @@ -407,7 +312,7 @@ char c; str = Blind ? "sense" : "see"; break; case 'Z': - str = dungeons[0].dname; + str = svd.dungeons[0].dname; break; case '%': str = "%"; @@ -416,18 +321,16 @@ char c; str = ""; break; } - Strcpy(cvt_buf, str); + Strcpy(gc.cvt_buf, str); } -STATIC_OVL void -convert_line(in_line, out_line) -char *in_line, *out_line; +staticfn void +convert_line(char *in_line, char *out_line) { char *c, *cc; - char xbuf[BUFSZ]; cc = out_line; - for (c = xcrypt(in_line, xbuf); *c; c++) { + for (c = in_line; *c; c++) { *cc = 0; switch (*c) { case '\r': @@ -441,17 +344,17 @@ char *in_line, *out_line; switch (*(++c)) { /* insert "a"/"an" prefix */ case 'A': - Strcat(cc, An(cvt_buf)); + Strcat(cc, An(gc.cvt_buf)); cc += strlen(cc); continue; /* for */ case 'a': - Strcat(cc, an(cvt_buf)); + Strcat(cc, an(gc.cvt_buf)); cc += strlen(cc); continue; /* for */ /* capitalize */ case 'C': - cvt_buf[0] = highc(cvt_buf[0]); + gc.cvt_buf[0] = highc(gc.cvt_buf[0]); break; /* replace name with pronoun; @@ -462,7 +365,7 @@ char *in_line, *out_line; case 'I': case 'j': /* his/her */ case 'J': - if (index("dlno", lowc(*(c - 1)))) + if (strchr("dlno", lowc(*(c - 1)))) qtext_pronoun(*(c - 1), *c); else --c; /* default action */ @@ -470,24 +373,26 @@ char *in_line, *out_line; /* pluralize */ case 'P': - cvt_buf[0] = highc(cvt_buf[0]); + gc.cvt_buf[0] = highc(gc.cvt_buf[0]); + FALLTHROUGH; /*FALLTHRU*/ case 'p': - Strcpy(cvt_buf, makeplural(cvt_buf)); + Strcpy(gc.cvt_buf, makeplural(gc.cvt_buf)); break; /* append possessive suffix */ case 'S': - cvt_buf[0] = highc(cvt_buf[0]); + gc.cvt_buf[0] = highc(gc.cvt_buf[0]); + FALLTHROUGH; /*FALLTHRU*/ case 's': - Strcpy(cvt_buf, s_suffix(cvt_buf)); + Strcpy(gc.cvt_buf, s_suffix(gc.cvt_buf)); break; /* strip any "the" prefix */ case 't': - if (!strncmpi(cvt_buf, "the ", 4)) { - Strcat(cc, &cvt_buf[4]); + if (!strncmpi(gc.cvt_buf, "the ", 4)) { + Strcat(cc, &gc.cvt_buf[4]); cc += strlen(cc); continue; /* for */ } @@ -497,204 +402,267 @@ char *in_line, *out_line; --c; /* undo switch increment */ break; } - Strcat(cc, cvt_buf); - cc += strlen(cvt_buf); + Strcat(cc, gc.cvt_buf); + cc += strlen(gc.cvt_buf); break; - } /* else fall through */ - + } + FALLTHROUGH; + /* FALLTHRU */ default: *cc++ = *c; break; } + if (cc > &out_line[BUFSZ - 1]) + panic("convert_line: overflow"); } - if (cc > &out_line[BUFSZ-1]) - panic("convert_line: overflow"); *cc = 0; return; } -STATIC_OVL void -deliver_by_pline(qt_msg) -struct qtmsg *qt_msg; +staticfn void +deliver_by_pline(const char *str) { - long size; char in_line[BUFSZ], out_line[BUFSZ]; + const char *msgp = str, *msgend = eos((char *) str); + + while (msgp < msgend) { + /* copynchars() will stop at newline if it finds one */ + copynchars(in_line, msgp, (int) sizeof in_line - 1); + msgp += strlen(in_line) + 1; - *in_line = '\0'; - for (size = 0; size < qt_msg->size; size += (long) strlen(in_line)) { - (void) dlb_fgets(in_line, sizeof in_line, msg_file); convert_line(in_line, out_line); pline("%s", out_line); } } -STATIC_OVL void -deliver_by_window(qt_msg, how) -struct qtmsg *qt_msg; -int how; +staticfn void +deliver_by_window(const char *msg, int how) { - long size; char in_line[BUFSZ], out_line[BUFSZ]; - boolean qtdump = (how == NHW_MAP); - winid datawin = create_nhwindow(qtdump ? NHW_TEXT : how); + const char *msgp = msg, *msgend = eos((char *) msg); + winid datawin = create_nhwindow(how); -#ifdef DEBUG - if (qtdump) { - char buf[BUFSZ]; + while (msgp < msgend) { + /* copynchars() will stop at newline if it finds one */ + copynchars(in_line, msgp, (int) sizeof in_line - 1); + msgp += strlen(in_line) + 1; - /* when dumping quest messages at startup, all of them are passed to - * deliver_by_window(), even if normally given to deliver_by_pline() - */ - Sprintf(buf, "msgnum: %d, delivery: %c", - qt_msg->msgnum, qt_msg->delivery); - putstr(datawin, 0, buf); - putstr(datawin, 0, ""); - } -#endif - for (size = 0; size < qt_msg->size; size += (long) strlen(in_line)) { - (void) dlb_fgets(in_line, sizeof in_line, msg_file); convert_line(in_line, out_line); putstr(datawin, 0, out_line); } + display_nhwindow(datawin, TRUE); destroy_nhwindow(datawin); - - /* block messages delivered by window aren't kept in message history - but have a one-line summary which is put there for ^P recall */ - *out_line = '\0'; - if (qt_msg->summary_size) { - (void) dlb_fgets(in_line, sizeof in_line, msg_file); - convert_line(in_line, out_line); -#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) - } else if (qt_msg->delivery == 'c') { /* skip for 'qtdump' of 'p' */ - /* delivery 'c' and !summary_size, summary expected but not present; - this doesn't prefix the number with role code vs 'general' - but should be good enough for summary verification purposes */ - Sprintf(out_line, "[missing block message summary for #%05d]", - qt_msg->msgnum); -#endif - } - if (*out_line) - putmsghistory(out_line, FALSE); } -STATIC_OVL boolean -skip_pager(common) -boolean common; +staticfn boolean +skip_pager(boolean common UNUSED) { /* WIZKIT: suppress plot feedback if starting with quest artifact */ if (program_state.wizkit_wishing) return TRUE; - if (!(common ? qt_list.common : qt_list.chrole)) { - panic("%s: no %s quest text data available", - common ? "com_pager" : "qt_pager", - common ? "common" : "role-specific"); - /*NOTREACHED*/ - return TRUE; - } return FALSE; } -void -com_pager(msgnum) -int msgnum; +staticfn boolean +com_pager_core( + const char *section, + const char *msgid, + boolean showerror, + char **rawtext) { - struct qtmsg *qt_msg; + static const char *const howtoput[] = { + "pline", "window", "text", "menu", "default", NULL + }; + static const int howtoput2i[] = { 1, 2, 2, 3, 0, 0 }; + int output; + lua_State *L; + char *text = NULL, *synopsis = NULL, *fallback_msgid = NULL; + boolean res = FALSE; + nhl_sandbox_info sbi = {NHL_SB_SAFE, 1*1024*1024, 0, 1*1024*1024}; if (skip_pager(TRUE)) - return; + return FALSE; + + L = nhl_init(&sbi); + if (!L) { + if (showerror) + impossible("com_pager: nhl_init() failed"); + goto compagerdone; + } + + if (!nhl_loadlua(L, QTEXT_FILE)) { + if (showerror) + impossible("com_pager: %s not found.", QTEXT_FILE); + goto compagerdone; + } + + lua_settop(L, 0); + lua_getglobal(L, "questtext"); + if (!lua_istable(L, -1)) { + if (showerror) + impossible("com_pager: questtext in %s is not a lua table", + QTEXT_FILE); + goto compagerdone; + } + + lua_getfield(L, -1, section); + if (!lua_istable(L, -1)) { + if (showerror) + impossible("com_pager: questtext[%s] in %s is not a lua table", + section, QTEXT_FILE); + goto compagerdone; + } + + tryagain: + lua_getfield(L, -1, fallback_msgid ? fallback_msgid : msgid); + if (!lua_istable(L, -1)) { + if (!fallback_msgid) { + /* Do we have questtxt[msg_fallbacks][]? */ + lua_getfield(L, -3, "msg_fallbacks"); + if (lua_istable(L, -1)) { + fallback_msgid = get_table_str_opt(L, msgid, NULL); + lua_pop(L, 2); + if (fallback_msgid) + goto tryagain; + } + } + if (showerror) { + if (!fallback_msgid) + impossible( + "com_pager: questtext[%s][%s] in %s is not a lua table", + section, msgid, QTEXT_FILE); + else + impossible( + "com_pager: questtext[%s][%s] and [][%s] in %s are not lua tables", + section, msgid, fallback_msgid, QTEXT_FILE); + } + goto compagerdone; + } + + text = get_table_str_opt(L, "text", NULL); + if (rawtext) { + *rawtext = dupstr(text); + res = TRUE; + goto compagerdone; + } + synopsis = get_table_str_opt(L, "synopsis", NULL); + output = howtoput2i[get_table_option(L, "output", "default", howtoput)]; + + if (!text) { + int nelems; + + lua_len(L, -1); + nelems = (int) lua_tointeger(L, -1); + lua_pop(L, 1); + if (nelems < 2) { + if (showerror) + impossible( + "com_pager: questtext[%s][%s] in %s is not an array of strings", + section, fallback_msgid ? fallback_msgid : msgid, + QTEXT_FILE); + goto compagerdone; + } + nelems = rn2(nelems) + 1; + lua_pushinteger(L, nelems); + lua_gettable(L, -2); + text = dupstr(luaL_checkstring(L, -1)); + } + + /* switch from by_pline to by_window if line has multiple segments or + is unreasonably long (the latter ought to checked after formatting + conversions rather than before...) */ + if (output == 0 && (strchr(text, '\n') || strlen(text) >= BUFSZ - 1)) { + output = 2; - if (!(qt_msg = msg_in(qt_list.common, msgnum))) { - impossible("com_pager: message %d not found.", msgnum); - return; + /* + * FIXME: should update quest.lua to include proper synopsis line + * for any item subject to having its delivery converted to by_window. + */ + if (!synopsis) { + char tmpbuf[BUFSZ]; + + Sprintf(tmpbuf, "[%.*s]", BUFSZ - 1 - 2, text); + /* change every newline character to a space */ + (void) strNsubst(tmpbuf, "\n", " ", 0); + synopsis = dupstr(tmpbuf); + } } - (void) dlb_fseek(msg_file, qt_msg->offset, SEEK_SET); - if (qt_msg->delivery == 'p') - deliver_by_pline(qt_msg); - else if (msgnum == 1) - deliver_by_window(qt_msg, NHW_MENU); + if (output == 0 || output == 1) + deliver_by_pline(text); else - deliver_by_window(qt_msg, NHW_TEXT); - return; + deliver_by_window(text, (output == 3) ? NHW_MENU : NHW_TEXT); + + if (synopsis) { + char in_line[BUFSZ], out_line[BUFSZ]; + +#if 0 /* not yet -- brackets need to be removed from quest.lua */ + Sprintf(in_line, "[%.*s]", + (int) (sizeof in_line - sizeof "[]"), synopsis); +#else + Strcpy(in_line, synopsis); +#endif + convert_line(in_line, out_line); + /* bypass message delivery but be available for ^P recall */ + putmsghistory(out_line, FALSE); + } + res = TRUE; + + compagerdone: + if (text) + free((genericptr_t) text); + if (synopsis) + free((genericptr_t) synopsis); + if (fallback_msgid) + free((genericptr_t) fallback_msgid); + nhl_done(L); + return res; } void -qt_pager(msgnum) -int msgnum; +com_pager(const char *msgid) { - struct qtmsg *qt_msg; - - if (skip_pager(FALSE)) - return; - - qt_msg = msg_in(qt_list.chrole, msgnum); - if (!qt_msg) { - /* some roles have an alternate message for return to the goal - level when the quest artifact is absent (handled by caller) - but some don't; for the latter, use the normal goal message; - note: for first visit, artifact is assumed to always be - present which might not be true for wizard mode but we don't - worry about quest message references in that situation */ - if (msgnum == QT_ALTGOAL) - qt_msg = msg_in(qt_list.chrole, QT_NEXTGOAL); - } - if (!qt_msg) { - impossible("qt_pager: message %d not found.", msgnum); - return; - } + (void) com_pager_core("common", msgid, TRUE, (char **) 0); +} - (void) dlb_fseek(msg_file, qt_msg->offset, SEEK_SET); - if (qt_msg->delivery == 'p' && strcmp(windowprocs.name, "X11")) - deliver_by_pline(qt_msg); - else - deliver_by_window(qt_msg, NHW_TEXT); - return; +void +qt_pager(const char *msgid) +{ + if (!com_pager_core(gu.urole.filecode, msgid, FALSE, (char **) 0)) + (void) com_pager_core("common", msgid, TRUE, (char **) 0); } struct permonst * -qt_montype() +qt_montype(void) { int qpm; if (rn2(5)) { - qpm = urole.enemy1num; - if (qpm != NON_PM && rn2(5) && !(mvitals[qpm].mvflags & G_GENOD)) + qpm = gu.urole.enemy1num; + if (qpm != NON_PM && rn2(5) && !(svm.mvitals[qpm].mvflags & G_GENOD)) return &mons[qpm]; - return mkclass(urole.enemy1sym, 0); + return mkclass(gu.urole.enemy1sym, 0); } - qpm = urole.enemy2num; - if (qpm != NON_PM && rn2(5) && !(mvitals[qpm].mvflags & G_GENOD)) + qpm = gu.urole.enemy2num; + if (qpm != NON_PM && rn2(5) && !(svm.mvitals[qpm].mvflags & G_GENOD)) return &mons[qpm]; - return mkclass(urole.enemy2sym, 0); + return mkclass(gu.urole.enemy2sym, 0); } /* special levels can include a custom arrival message; display it */ void -deliver_splev_message() +deliver_splev_message(void) { - char *str, *nl, in_line[BUFSZ], out_line[BUFSZ]; - /* there's no provision for delivering via window instead of pline */ - if (lev_message) { - /* lev_message can span multiple lines using embedded newline chars; - any segments too long to fit within in_line[] will be truncated */ - for (str = lev_message; *str; str = nl + 1) { - /* copying will stop at newline if one is present */ - copynchars(in_line, str, (int) (sizeof in_line) - 1); - - /* convert_line() expects encrypted input */ - (void) xcrypt(in_line, in_line); - convert_line(in_line, out_line); - pline("%s", out_line); - - if ((nl = index(str, '\n')) == 0) - break; /* done if no newline */ - } + if (gl.lev_message) { + deliver_by_pline(gl.lev_message); - free((genericptr_t) lev_message); - lev_message = 0; + free((genericptr_t) gl.lev_message); + gl.lev_message = NULL; } } +#undef QTEXT_FILE + /*questpgr.c*/ diff --git a/src/read.c b/src/read.c index aaa3341d5..d08a9acb2 100644 --- a/src/read.c +++ b/src/read.c @@ -1,45 +1,61 @@ -/* NetHack 3.6 read.c $NHDT-Date: 1561485713 2019/06/25 18:01:53 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.172 $ */ +/* NetHack 5.0 read.c $NHDT-Date: 1762577372 2025/11/07 20:49:32 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.323 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2012. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -#define Your_Own_Role(mndx) \ - ((mndx) == urole.malenum \ - || (urole.femalenum != NON_PM && (mndx) == urole.femalenum)) -#define Your_Own_Race(mndx) \ - ((mndx) == urace.malenum \ - || (urace.femalenum != NON_PM && (mndx) == urace.femalenum)) - -boolean known; - -static NEARDATA const char readable[] = { ALL_CLASSES, SCROLL_CLASS, - SPBOOK_CLASS, 0 }; -static const char all_count[] = { ALLOW_COUNT, ALL_CLASSES, 0 }; - -STATIC_DCL boolean FDECL(learnscrolltyp, (SHORT_P)); -STATIC_DCL char *FDECL(erode_obj_text, (struct obj *, char *)); -STATIC_DCL char *FDECL(apron_text, (struct obj *, char *buf)); -STATIC_DCL void FDECL(stripspe, (struct obj *)); -STATIC_DCL void FDECL(p_glow1, (struct obj *)); -STATIC_DCL void FDECL(p_glow2, (struct obj *, const char *)); -STATIC_DCL void FDECL(forget_single_object, (int)); -#if 0 /* not used */ -STATIC_DCL void FDECL(forget_objclass, (int)); -#endif -STATIC_DCL void FDECL(randomize, (int *, int)); -STATIC_DCL void FDECL(forget, (int)); -STATIC_DCL int FDECL(maybe_tame, (struct monst *, struct obj *)); -STATIC_DCL boolean FDECL(get_valid_stinking_cloud_pos, (int, int)); -STATIC_DCL boolean FDECL(is_valid_stinking_cloud_pos, (int, int, BOOLEAN_P)); -STATIC_PTR void FDECL(display_stinking_cloud_positions, (int)); -STATIC_PTR void FDECL(set_lit, (int, int, genericptr)); -STATIC_DCL void NDECL(do_class_genocide); - -STATIC_OVL boolean -learnscrolltyp(scrolltyp) -short scrolltyp; +#define Your_Own_Role(mndx) ((mndx) == gu.urole.mnum) +#define Your_Own_Race(mndx) ((mndx) == gu.urace.mnum) + +staticfn boolean learnscrolltyp(short); +staticfn void cap_spe(struct obj *); +staticfn char *erode_obj_text(struct obj *, char *); +staticfn char *hawaiian_design(struct obj *, char *); +staticfn int read_ok(struct obj *); +staticfn void stripspe(struct obj *); +staticfn void p_glow1(struct obj *); +staticfn void p_glow2(struct obj *, const char *); +staticfn void p_glow3(struct obj *, const char *); +staticfn void forget(int); +staticfn int maybe_tame(struct monst *, struct obj *); +staticfn boolean can_center_cloud(coordxy, coordxy); +staticfn void display_stinking_cloud_positions(boolean); +staticfn void seffect_enchant_armor(struct obj **); +staticfn boolean disintegrate_cursed_armor(void); +staticfn void seffect_destroy_armor(struct obj **); +staticfn void seffect_confuse_monster(struct obj **); +staticfn void seffect_scare_monster(struct obj **); +staticfn void seffect_remove_curse(struct obj **); +staticfn void seffect_create_monster(struct obj **); +staticfn void seffect_enchant_weapon(struct obj **); +staticfn void seffect_taming(struct obj **); +staticfn void seffect_genocide(struct obj **); +staticfn void seffect_light(struct obj **); +staticfn void seffect_charging(struct obj **); +staticfn void seffect_amnesia(struct obj **); +staticfn void seffect_fire(struct obj **); +staticfn void seffect_earth(struct obj **); +staticfn void seffect_punishment(struct obj **); +staticfn void seffect_stinking_cloud(struct obj **); +staticfn void seffect_blank_paper(struct obj **); +staticfn void seffect_teleportation(struct obj **); +staticfn void seffect_gold_detection(struct obj **); +staticfn void seffect_food_detection(struct obj **); +staticfn void seffect_identify(struct obj **); +staticfn void seffect_magic_mapping(struct obj **); +#ifdef MAIL_STRUCTURES +staticfn void seffect_mail(struct obj **); +#endif /* MAIL_STRUCTURES */ +staticfn void set_lit(coordxy, coordxy, genericptr); +staticfn void do_class_genocide(void); +staticfn void do_stinking_cloud(struct obj *, boolean); +staticfn boolean create_particular_parse(char *, + struct _create_particular_data *); +staticfn boolean create_particular_creation(struct _create_particular_data *); + +staticfn boolean +learnscrolltyp(short scrolltyp) { if (!objects[scrolltyp].oc_name_known) { makeknown(scrolltyp); @@ -51,8 +67,7 @@ short scrolltyp; /* also called from teleport.c for scroll of teleportation */ void -learnscroll(sobj) -struct obj *sobj; +learnscroll(struct obj *sobj) { /* it's implied that sobj->dknown is set; we couldn't be reading this scroll otherwise */ @@ -60,10 +75,18 @@ struct obj *sobj; (void) learnscrolltyp(sobj->otyp); } -STATIC_OVL char * -erode_obj_text(otmp, buf) -struct obj *otmp; -char *buf; +/* max spe is +99, min is -99 */ +staticfn void +cap_spe(struct obj *obj) +{ + if (obj) { + if (abs(obj->spe) > SPE_LIM) + obj->spe = sgn(obj->spe) * SPE_LIM; + } +} + +staticfn char * +erode_obj_text(struct obj *otmp, char *buf) { int erosion = greatest_erosion(otmp); @@ -74,11 +97,9 @@ char *buf; } char * -tshirt_text(tshirt, buf) -struct obj *tshirt; -char *buf; +tshirt_text(struct obj *tshirt, char *buf) { - static const char *shirt_msgs[] = { + static const char *const shirt_msgs[] = { /* Scott Bigham */ "I explored the Dungeons of Doom and all I got was this lousy T-shirt!", "Is that Mjollnir in your pocket or are you just happy to see me?", @@ -110,6 +131,7 @@ char *buf; "Hey! Nymphs! Steal This T-Shirt!", "I <3 Dungeon of Doom", "I <3 Maud", + /* note: there is a similarly worded apron (alchemy smock) slogan */ "I am a Valkyrie. If you see me running, try to keep up.", "I am not a pack rat - I am a collector", "I bounced off a rubber tree", /* Monkey Island */ @@ -123,7 +145,9 @@ char *buf; "Meat is Mordor", "Minetown Better Business Bureau", "Minetown Watch", - "Ms. Palm's House of Negotiable Affection -- A Very Reputable House Of Disrepute", + /* Discworld riff; unfortunately long */ + ("Ms. Palm's House of Negotiable Affection--A Very Reputable" + " House Of Disrepute"), "Protection Racketeer", "Real men love Crom", "Somebody stole my Mojo!", @@ -152,7 +176,7 @@ char *buf; Theory" although they didn't create it (and an actual T-shirt with pentagonal diagram showing which choices defeat which) */ "rock--paper--scissors--lizard--Spock!", - /* "All men must die -- all men must serve" challange and response + /* "All men must die -- all men must serve" challenge and response from book series _A_Song_of_Ice_and_Fire_ by George R.R. Martin, TV show "Game of Thrones" (probably an actual T-shirt too...) */ "/Valar morghulis/ -- /Valar dohaeris/", @@ -162,12 +186,74 @@ char *buf; return erode_obj_text(tshirt, buf); } -STATIC_OVL char * -apron_text(apron, buf) -struct obj *apron; -char *buf; +char * +hawaiian_motif(struct obj *shirt, char *buf) +{ + static const char *const hawaiian_motifs[] = { + /* birds */ + "flamingo", + "parrot", + "toucan", + "bird of paradise", /* could be a bird or a flower */ + /* sea creatures */ + "sea turtle", + "tropical fish", + "jellyfish", + "giant eel", + "water nymph", + /* plants */ + "plumeria", + "orchid", + "hibiscus flower", + "palm tree", + /* other */ + "hula dancer", + "sailboat", + "ukulele", + }; + + /* a tourist's starting shirt always has the same o_id; we need some + additional randomness or else its design will never differ */ + unsigned motif = shirt->o_id ^ (unsigned) ubirthday; + + Strcpy(buf, hawaiian_motifs[motif % SIZE(hawaiian_motifs)]); + return buf; +} + +staticfn char * +hawaiian_design(struct obj *shirt, char *buf) { - static const char *apron_msgs[] = { + static const char *const hawaiian_bgs[] = { + /* solid colors */ + "purple", + "yellow", + "red", + "blue", + "orange", + "black", + "green", + /* adjectives */ + "abstract", + "geometric", + "patterned", + "naturalistic", + }; + + /* This hash method is slightly different than the one in hawaiian_motif; + using the same formula in both cases may lead to some shirt combos + never appearing, if the sizes of the two lists have common factors. */ + unsigned bg = shirt->o_id ^ (unsigned) ~ubirthday; + + Sprintf(buf, "%s on %s background", + makeplural(hawaiian_motif(shirt, buf)), + an(hawaiian_bgs[bg % SIZE(hawaiian_bgs)])); + return buf; +} + +char * +apron_text(struct obj *apron, char *buf) +{ + static const char *const apron_msgs[] = { "Kiss the cook", "I'm making SCIENCE!", "Don't mess with the chef", @@ -177,66 +263,191 @@ char *buf; "If you can't stand the heat, get out of Gehennom!", "If we weren't meant to eat animals, why are they made out of meat?", "If you don't like the food, I'll stab you", + /* In the movie "The Sum of All Fears", a Russian worker in a weapons + facility wears a T-shirt that a translator says reads, "I am a + bomb technician, if you see me running ... try to catch up." + In nethack, the quote is far more suitable to an alchemy smock + (particularly since so many of these others are about cooking) + than a T-shirt and is paraphrased to simplify/shorten it. + [later... turns out that this is already a T-shirt message: + "I am a Valkyrie. If you see me running, try to keep up." + so this one has been revised a little: added alchemist prefix, + changed "keep up" to original source's "catch up"] */ + "I am an alchemist; if you see me running, try to catch up...", }; Strcpy(buf, apron_msgs[apron->o_id % SIZE(apron_msgs)]); return erode_obj_text(apron, buf); } +static const char *const candy_wrappers[] = { + "", /* (none -- should never happen) */ + "Apollo", /* Lost */ + "Moon Crunchy", /* South Park */ + "Snacky Cake", "Chocolate Nuggie", "The Small Bar", + "Crispy Yum Yum", "Nilla Crunchie", "Berry Bar", + "Choco Nummer", "Om-nom", /* Cat Macro */ + "Fruity Oaty", /* Serenity */ + "Wonka Bar", /* Charlie and the Chocolate Factory */ +}; + +/* return the text of a candy bar's wrapper */ +const char * +candy_wrapper_text(struct obj *obj) +{ + /* modulo operation is just bullet proofing; 'spe' is already in range */ + return candy_wrappers[obj->spe % SIZE(candy_wrappers)]; +} + +/* assign a wrapper to a candy bar stack */ +void +assign_candy_wrapper(struct obj *obj) +{ + if (obj->otyp == CANDY_BAR) { + /* skips candy_wrappers[0] */ + obj->spe = 1 + rn2(SIZE(candy_wrappers) - 1); + } + return; +} + +/* getobj callback for object to read */ +staticfn int +read_ok(struct obj *obj) +{ + if (!obj) + return GETOBJ_EXCLUDE; + + if (obj->oclass == SCROLL_CLASS || obj->oclass == SPBOOK_CLASS) + return GETOBJ_SUGGEST; + + return GETOBJ_DOWNPLAY; +} + +DISABLE_WARNING_FORMAT_NONLITERAL + +/* the #read command; read a scroll or spell book or various other things */ int -doread() +doread(void) { - register struct obj *scroll; + static const char find_any_braille[] = "feel any Braille writing."; + struct obj *scroll; boolean confused, nodisappear; + int otyp; - known = FALSE; + /* + * Reading while blind is allowed in most cases, including the + * Book of the Dead but not regular spellbooks. For scrolls, the + * description has to have been seen or magically learned (so only + * when scroll->dknown is true): hero recites the label while + * holding the unfurled scroll. We deliberately don't require + * free hands because that would cripple scroll of remove curse, + * but we ought to be requiring hands or at least limbs. The + * recitation could be sub-vocal; actual speech isn't required. + * + * Reading while confused is allowed and can produce alternate + * outcome. + * + * Reading while stunned is currently allowed but probably should + * be prevented.... + */ + + gk.known = FALSE; if (check_capacity((char *) 0)) - return 0; - scroll = getobj(readable, "read"); + return ECMD_OK; + + scroll = getobj("read", read_ok, GETOBJ_PROMPT); if (!scroll) - return 0; + return ECMD_CANCEL; + otyp = scroll->otyp; + scroll->pickup_prev = 0; /* no longer 'just picked up' */ /* outrumor has its own blindness check */ - if (scroll->otyp == FORTUNE_COOKIE) { + if (otyp == FORTUNE_COOKIE) { if (flags.verbose) You("break up the cookie and throw away the pieces."); outrumor(bcsign(scroll), BY_COOKIE); if (!Blind) - u.uconduct.literate++; + if (!u.uconduct.literate++) + livelog_printf(LL_CONDUCT, + "became literate by reading a fortune cookie"); useup(scroll); - return 1; - } else if (scroll->otyp == T_SHIRT || scroll->otyp == ALCHEMY_SMOCK) { + return ECMD_TIME; + } else if (otyp == T_SHIRT || otyp == ALCHEMY_SMOCK + || otyp == HAWAIIAN_SHIRT) { char buf[BUFSZ], *mesg; const char *endpunct; if (Blind) { - You_cant("feel any Braille writing."); - return 0; + You_cant(find_any_braille); + return ECMD_OK; } /* can't read shirt worn under suit (under cloak is ok though) */ - if (scroll->otyp == T_SHIRT && uarm && scroll == uarmu) { + if ((otyp == T_SHIRT || otyp == HAWAIIAN_SHIRT) && uarm + && scroll == uarmu) { pline("%s shirt is obscured by %s%s.", scroll->unpaid ? "That" : "Your", shk_your(buf, uarm), suit_simple_name(uarm)); - return 0; + return ECMD_OK; + } + if (otyp == HAWAIIAN_SHIRT) { + pline("%s features %s.", flags.verbose ? "The design" : "It", + hawaiian_design(scroll, buf)); + return ECMD_TIME; } - u.uconduct.literate++; + if (!u.uconduct.literate++) + livelog_printf(LL_CONDUCT, "became literate by reading %s", + (scroll->otyp == T_SHIRT) ? "a T-shirt" + : "an apron"); + /* populate 'buf[]' */ - mesg = (scroll->otyp == T_SHIRT) ? tshirt_text(scroll, buf) - : apron_text(scroll, buf); + mesg = (otyp == T_SHIRT) ? tshirt_text(scroll, buf) + : apron_text(scroll, buf); endpunct = ""; if (flags.verbose) { int ln = (int) strlen(mesg); /* we will be displaying a sentence; need ending punctuation */ - if (ln > 0 && !index(".!?", mesg[ln - 1])) + if (ln > 0 && !strchr(".!?", mesg[ln - 1])) endpunct = "."; pline("It reads:"); } pline("\"%s\"%s", mesg, endpunct); - return 1; - } else if (scroll->otyp == CREDIT_CARD) { - static const char *card_msgs[] = { + return ECMD_TIME; + } else if ((otyp == DUNCE_CAP || otyp == CORNUTHAUM) + /* note: "DUNCE" isn't directly connected to tourists but + if everyone could read it, they would always be able to + trivially distinguish between the two types of conical hat; + limiting this to tourists is better than rejecting it */ + && Role_if(PM_TOURIST)) { + /* another note: the misspelling, "wizzard", is correct; + that's what is written on Rincewind's pointy hat from + Pratchett's Discworld series, along with a lot of stars; + rather than inked on or painted on, treat them as stitched + or even separate pieces of fabric which have been attached + (don't recall whether the books mention anything like that...) */ + const char *cap_text = (otyp == DUNCE_CAP) ? "DUNCE" : "WIZZARD"; + + if (scroll->o_id % 3) { + /* no need to vary this when blind; "on this ___" is important + because it suggests that there might be something on others */ + You_cant("find anything to read on this %s.", + simpleonames(scroll)); + return ECMD_OK; + } + pline("%s on the %s. It reads: %s.", + !Blind ? "There is writing" : "You feel lettering", + simpleonames(scroll), cap_text); + if (!u.uconduct.literate++) + livelog_printf(LL_CONDUCT, "became literate by reading %s", + (otyp == DUNCE_CAP) ? "a dunce cap" + : "a cornuthaum"); + + /* yet another note: despite the fact that player will recognize + the object type, don't make it become a discovery for hero */ + trycall(scroll); + return ECMD_TIME; + } else if (otyp == CREDIT_CARD) { + static const char *const card_msgs[] = { "Leprechaun Gold Tru$t - Shamrock Card", "Magic Memory Vault Charge Card", "Larn National Bank", /* Larn */ @@ -272,76 +483,101 @@ doread() (!((int) scroll->o_id % 3)), (((int) scroll->o_id * 7) % 10), (flags.verbose || Blind) ? "." : ""); - u.uconduct.literate++; - return 1; - } else if (scroll->otyp == CAN_OF_GREASE) { + if (!u.uconduct.literate++) + livelog_printf(LL_CONDUCT, + "became literate by reading a credit card"); + + return ECMD_TIME; + } else if (otyp == CAN_OF_GREASE) { pline("This %s has no label.", singular(scroll, xname)); - return 0; - } else if (scroll->otyp == MAGIC_MARKER) { + return ECMD_OK; + } else if (otyp == MAGIC_MARKER) { + static const int red_mons[] = { + PM_FIRE_ANT, PM_PYROLISK, PM_HELL_HOUND, PM_IMP, + PM_LARGE_MIMIC, PM_LEOCROTTA, PM_SCORPION, PM_XAN, + PM_GIANT_BAT, PM_WATER_MOCCASIN, PM_FLESH_GOLEM, + PM_BARBED_DEVIL, PM_MARILITH, PM_PIRANHA + }; + char buf[BUFSZ]; + struct permonst *pm = &mons[red_mons[scroll->o_id % SIZE(red_mons)]]; + if (Blind) { - You_cant("feel any Braille writing."); - return 0; + You_cant(find_any_braille); + return ECMD_OK; } if (flags.verbose) pline("It reads:"); - pline("\"Magic Marker(TM) Red Ink Marker Pen. Water Soluble.\""); - u.uconduct.literate++; - return 1; + Sprintf(buf, "%s", pmname(pm, NEUTRAL)); + pline("\"Magic Marker(TM) %s Red Ink Marker Pen. Water Soluble.\"", + upwords(buf)); + if (!u.uconduct.literate++) + livelog_printf(LL_CONDUCT, + "became literate by reading a magic marker"); + + return ECMD_TIME; } else if (scroll->oclass == COIN_CLASS) { if (Blind) You("feel the embossed words:"); else if (flags.verbose) You("read:"); pline("\"1 Zorkmid. 857 GUE. In Frobs We Trust.\""); - u.uconduct.literate++; - return 1; - } else if (scroll->oartifact == ART_ORB_OF_FATE) { + if (!u.uconduct.literate++) + livelog_printf(LL_CONDUCT, + "became literate by reading a coin's engravings"); + + return ECMD_TIME; + } else if (is_art(scroll, ART_ORB_OF_FATE)) { if (Blind) You("feel the engraved signature:"); else pline("It is signed:"); pline("\"Odin.\""); - u.uconduct.literate++; - return 1; - } else if (scroll->otyp == CANDY_BAR) { - static const char *wrapper_msgs[] = { - "Apollo", /* Lost */ - "Moon Crunchy", /* South Park */ - "Snacky Cake", "Chocolate Nuggie", "The Small Bar", - "Crispy Yum Yum", "Nilla Crunchie", "Berry Bar", - "Choco Nummer", "Om-nom", /* Cat Macro */ - "Fruity Oaty", /* Serenity */ - "Wonka Bar" /* Charlie and the Chocolate Factory */ - }; + if (!u.uconduct.literate++) + livelog_printf(LL_CONDUCT, + "became literate by reading the divine signature of Odin"); + + return ECMD_TIME; + } else if (otyp == CANDY_BAR) { + const char *wrapper = candy_wrapper_text(scroll); if (Blind) { - You_cant("feel any Braille writing."); - return 0; + You_cant(find_any_braille); + return ECMD_OK; } - pline("The wrapper reads: \"%s\".", - wrapper_msgs[scroll->o_id % SIZE(wrapper_msgs)]); - u.uconduct.literate++; - return 1; + if (!*wrapper) { + pline("The candy bar's wrapper is blank."); + return ECMD_OK; + } + pline("The wrapper reads: \"%s\".", wrapper); + if (!u.uconduct.literate++) + livelog_printf(LL_CONDUCT, + "became literate by reading a candy bar wrapper"); + + return ECMD_TIME; } else if (scroll->oclass != SCROLL_CLASS && scroll->oclass != SPBOOK_CLASS) { pline(silly_thing_to, "read"); - return 0; - } else if (Blind && (scroll->otyp != SPE_BOOK_OF_THE_DEAD)) { + return ECMD_OK; + } else if (Blind && otyp != SPE_BOOK_OF_THE_DEAD) { const char *what = 0; - if (scroll->oclass == SPBOOK_CLASS) + if (otyp == SPE_NOVEL) + /* unseen novels are already distinguishable from unseen + spellbooks so this isn't revealing any extra information */ + what = "words"; + else if (scroll->oclass == SPBOOK_CLASS) what = "mystic runes"; else if (!scroll->dknown) what = "formula on the scroll"; if (what) { pline("Being blind, you cannot read the %s.", what); - return 0; + return ECMD_OK; } } confused = (Confusion != 0); -#ifdef MAIL - if (scroll->otyp == SCR_MAIL) { +#ifdef MAIL_STRUCTURES + if (otyp == SCR_MAIL) { confused = FALSE; /* override */ /* reading mail is a convenience for the player and takes place outside the game, so shouldn't affect gameplay; @@ -351,33 +587,35 @@ doread() maintained illiterate conduct so far, and this mail scroll didn't come from bones, ask for confirmation */ if (!u.uconduct.literate) { - if (!scroll->spe && yn( + if (!scroll->spe && y_n( "Reading mail will violate \"illiterate\" conduct. Read anyway?" ) != 'y') - return 0; + return ECMD_OK; } } #endif /* Actions required to win the game aren't counted towards conduct */ - /* Novel conduct is handled in read_tribute so exclude it too*/ - if (scroll->otyp != SPE_BOOK_OF_THE_DEAD - && scroll->otyp != SPE_BLANK_PAPER && scroll->otyp != SCR_BLANK_PAPER - && scroll->otyp != SPE_NOVEL) - u.uconduct.literate++; + /* Novel conduct is handled in read_tribute so exclude it too */ + if (otyp != SPE_BOOK_OF_THE_DEAD && otyp != SPE_NOVEL + && otyp != SPE_BLANK_PAPER && otyp != SCR_BLANK_PAPER) + if (!u.uconduct.literate++) + livelog_printf(LL_CONDUCT, "became literate by reading %s", + (scroll->oclass == SPBOOK_CLASS) ? "a book" + : (scroll->oclass == SCROLL_CLASS) ? "a scroll" + : something); if (scroll->oclass == SPBOOK_CLASS) { - return study_book(scroll); + return study_book(scroll) ? ECMD_TIME : ECMD_OK; } scroll->in_use = TRUE; /* scroll, not spellbook, now being read */ - if (scroll->otyp != SCR_BLANK_PAPER) { - boolean silently = !can_chant(&youmonst); + if (otyp != SCR_BLANK_PAPER) { + boolean silently = !can_chant(&gy.youmonst); /* a few scroll feedback messages describe something happening to the scroll itself, so avoid "it disappears" for those */ - nodisappear = (scroll->otyp == SCR_FIRE - || (scroll->otyp == SCR_REMOVE_CURSE - && scroll->cursed)); + nodisappear = (otyp == SCR_FIRE + || (otyp == SCR_REMOVE_CURSE && scroll->cursed)); if (Blind) pline(nodisappear ? "You %s the formula on the scroll." @@ -395,22 +633,23 @@ doread() } } if (!seffects(scroll)) { - if (!objects[scroll->otyp].oc_name_known) { - if (known) + if (!objects[otyp].oc_name_known) { + if (gk.known) learnscroll(scroll); - else if (!objects[scroll->otyp].oc_uname) - docall(scroll); + else + trycall(scroll); } scroll->in_use = FALSE; - if (scroll->otyp != SCR_BLANK_PAPER) + if (otyp != SCR_BLANK_PAPER) useup(scroll); } - return 1; + return ECMD_TIME; } -STATIC_OVL void -stripspe(obj) -register struct obj *obj; +RESTORE_WARNING_FORMAT_NONLITERAL + +staticfn void +stripspe(struct obj *obj) { if (obj->blessed || obj->spe <= 0) { pline1(nothing_happens); @@ -424,51 +663,72 @@ register struct obj *obj; } } -STATIC_OVL void -p_glow1(otmp) -register struct obj *otmp; +staticfn void +p_glow1(struct obj *otmp) { pline("%s briefly.", Yobjnam2(otmp, Blind ? "vibrate" : "glow")); } -STATIC_OVL void -p_glow2(otmp, color) -register struct obj *otmp; -register const char *color; +staticfn void +p_glow2(struct obj *otmp, const char *color) { pline("%s%s%s for a moment.", Yobjnam2(otmp, Blind ? "vibrate" : "glow"), Blind ? "" : " ", Blind ? "" : hcolor(color)); } -/* Is the object chargeable? For purposes of inventory display; it is - possible to be able to charge things for which this returns FALSE. */ -boolean -is_chargeable(obj) -struct obj *obj; +staticfn void +p_glow3(struct obj *otmp, const char *color) +{ + pline("%s feebly%s%s for a moment.", + Yobjnam2(otmp, Blind ? "vibrate" : "glow"), + Blind ? "" : " ", Blind ? "" : hcolor(color)); +} + +/* getobj callback for object to charge */ +int +charge_ok(struct obj *obj) { + if (!obj) + return GETOBJ_EXCLUDE; + if (obj->oclass == WAND_CLASS) - return TRUE; - /* known && !oc_name_known is possible after amnesia/mind flayer */ - if (obj->oclass == RING_CLASS) - return (boolean) (objects[obj->otyp].oc_charged - && (obj->known - || (obj->dknown - && objects[obj->otyp].oc_name_known))); + return GETOBJ_SUGGEST; + + if (obj->oclass == RING_CLASS && objects[obj->otyp].oc_charged + && obj->dknown && objects[obj->otyp].oc_name_known) + return GETOBJ_SUGGEST; + if (is_weptool(obj)) /* specific check before general tools */ - return FALSE; - if (obj->oclass == TOOL_CLASS) - return (boolean) objects[obj->otyp].oc_charged; - return FALSE; /* why are weapons/armor considered charged anyway? */ + return GETOBJ_EXCLUDE; + + if (obj->oclass == TOOL_CLASS) { + /* suggest tools that aren't oc_charged but can still be recharged */ + if (obj->otyp == BRASS_LANTERN + || (obj->otyp == OIL_LAMP) + /* only list magic lamps if they are not identified yet */ + || (obj->otyp == MAGIC_LAMP + && !objects[MAGIC_LAMP].oc_name_known)) { + return GETOBJ_SUGGEST; + } + /* suggest chargeable tools only if discovered, to prevent leaking + info (e.g. revealing if an unidentified 'flute' is magic or not) */ + if (objects[obj->otyp].oc_charged) { + return (obj->dknown && objects[obj->otyp].oc_name_known) + ? GETOBJ_SUGGEST : GETOBJ_DOWNPLAY; + } + return GETOBJ_EXCLUDE; + } + /* why are weapons/armor considered charged anyway? + * make them selectable even so for "feeling of loss" message */ + return GETOBJ_EXCLUDE_SELECTABLE; } /* recharge an object; curse_bless is -1 if the recharging implement was cursed, +1 if blessed, 0 otherwise. */ void -recharge(obj, curse_bless) -struct obj *obj; -int curse_bless; +recharge(struct obj *obj, int curse_bless) { - register int n; + int n; boolean is_cursed, is_blessed; is_cursed = curse_bless < 0; @@ -476,7 +736,7 @@ int curse_bless; if (obj->oclass == WAND_CLASS) { int lim = (obj->otyp == WAN_WISHING) - ? 3 + ? 1 : (objects[obj->otyp].oc_dir != NODIR) ? 8 : 15; /* undo any prior cancellation, even when is_cursed */ @@ -510,7 +770,7 @@ int curse_bless; if (is_cursed) { stripspe(obj); } else { - n = (lim == 3) ? 3 : rn1(5, lim + 1 - 5); + n = (lim == 1) ? 1 : rn1(5, lim + 1 - 5); if (!is_blessed) n = rnd(n); @@ -519,10 +779,15 @@ int curse_bless; else obj->spe++; if (obj->otyp == WAN_WISHING && obj->spe > 3) { + /* wands can't give more than three wishes; this code is + currently unreachable but left in case the rules for + wands of wishing change in future */ wand_explode(obj, 1); return; } - if (obj->spe >= lim) + if (lim == 1) + p_glow3(obj, NH_BLUE); + else if (obj->spe >= lim) p_glow2(obj, NH_BLUE); else p_glow1(obj); @@ -545,7 +810,7 @@ int curse_bless; if (is_on) Ring_gone(obj); s = rnd(3 * abs(obj->spe)); /* amount of damage */ - useup(obj); + useup(obj), obj = 0; losehp(Maybe_Half_Phys(s), "exploding ring", KILLED_BY_AN); } else { long mask = is_on ? (obj == uleft ? LEFT_RING : RIGHT_RING) : 0L; @@ -589,11 +854,10 @@ int curse_bless; case MAGIC_MARKER: case TINNING_KIT: case EXPENSIVE_CAMERA: - if (is_cursed) + if (is_cursed) { stripspe(obj); - else if (rechrg - && obj->otyp - == MAGIC_MARKER) { /* previously recharged */ + } else if (rechrg && obj->otyp == MAGIC_MARKER) { + /* previously recharged */ obj->recharged = 1; /* override increment done above */ if (obj->spe < 3) Your("marker seems permanently dried out."); @@ -619,8 +883,9 @@ int curse_bless; obj->spe = 50; else { int chrg = (int) obj->spe; - if ((chrg + n) > 127) - obj->spe = 127; + + if (chrg + n > SPE_LIM) + obj->spe = SPE_LIM; else obj->spe += n; } @@ -649,17 +914,43 @@ int curse_bless; } break; case CRYSTAL_BALL: + if (obj->spe == -1) /* like wands, first uncancel */ + obj->spe = 0; + if (is_cursed) { - stripspe(obj); + /* cursed scroll removes charges and curses ball */ + /*stripspe(obj); -- doesn't do quite what we want...*/ + if (!obj->cursed) { + p_glow2(obj, NH_BLACK); + curse(obj); + } else { + pline("%s briefly.", Yobjnam2(obj, "vibrate")); + } + if (obj->spe > 0) + costly_alteration(obj, COST_UNCHRG); + obj->spe = 0; } else if (is_blessed) { - obj->spe = 6; - p_glow2(obj, NH_BLUE); + /* blessed scroll sets charges to max and blesses ball */ + obj->spe = 7; + p_glow2(obj, !obj->blessed ? NH_LIGHT_BLUE : NH_BLUE); + if (!obj->blessed) + bless(obj); + /* [shop price stays the same regardless of charges or BUC] */ } else { - if (obj->spe < 5) { - obj->spe++; - p_glow1(obj); - } else + /* uncursed scroll increments charges and uncurses ball */ + if (obj->spe < 7 || obj->cursed) { + n = rnd(2); + obj->spe = min(obj->spe + n, 7); + if (!obj->cursed) { + p_glow1(obj); + } else { + p_glow2(obj, NH_AMBER); + uncurse(obj); + } + } else { + /* charges at max and ball not being uncursed */ pline1(nothing_happens); + } } break; case HORN_OF_PLENTY: @@ -676,7 +967,7 @@ int curse_bless; obj->spe = 50; p_glow2(obj, NH_BLUE); } else { - obj->spe += rnd(5); + obj->spe += rn1(5, 2); if (obj->spe > 50) obj->spe = 50; p_glow1(obj); @@ -708,1013 +999,1286 @@ int curse_bless; } /* switch */ } else { - not_chargable: + not_chargable: You("have a feeling of loss."); } + + /* prevent enchantment from getting out of range */ + cap_spe(obj); } -/* Forget known information about this object type. */ -STATIC_OVL void -forget_single_object(obj_id) -int obj_id; +/* + * Forget some things (e.g. after reading a scroll of amnesia). When called, + * the following are always forgotten: + * - felt ball & chain + * - skill training + * + * Other things are subject to flags: + * howmuch & ALL_SPELLS = forget all spells + */ +staticfn void +forget(int howmuch) { - objects[obj_id].oc_name_known = 0; - objects[obj_id].oc_pre_discovered = 0; /* a discovery when relearned */ - if (objects[obj_id].oc_uname) { - free((genericptr_t) objects[obj_id].oc_uname); - objects[obj_id].oc_uname = 0; - } - undiscover_object(obj_id); /* after clearing oc_name_known */ + struct monst *mtmp; + + if (Punished) + u.bc_felt = 0; /* forget felt ball&chain */ + + if (howmuch & ALL_SPELLS) + losespells(); + + /* Forget some skills. */ + drain_weapon_skill(rnd(howmuch ? 5 : 3)); - /* clear & free object names from matching inventory items too? */ + /* forget having seen monsts (affects recognizing unseen ones by sound) */ + for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) + if (mtmp != u.usteed && mtmp != u.ustuck) + mtmp->meverseen = 0; + /* [perhaps ought to forget having seen every monster on every level] */ + for (mtmp = gm.migrating_mons; mtmp; mtmp = mtmp->nmon) + mtmp->meverseen = 0; } -#if 0 /* here if anyone wants it.... */ -/* Forget everything known about a particular object class. */ -STATIC_OVL void -forget_objclass(oclass) -int oclass; +/* monster is hit by scroll of taming's effect */ +staticfn int +maybe_tame(struct monst *mtmp, struct obj *sobj) { - int i; + int was_tame = mtmp->mtame; + unsigned was_peaceful = mtmp->mpeaceful; + + if (sobj->cursed) { + setmangry(mtmp, FALSE); + if (was_peaceful && !mtmp->mpeaceful) + return -1; + } else { + /* for a shopkeeper, tamedog() will call make_happy_shk() but + not tame the target, so call it even if taming gets resisted */ + if (!resist(mtmp, sobj->oclass, 0, NOTELL) || mtmp->isshk) + (void) tamedog(mtmp, sobj, FALSE); - for (i = bases[oclass]; - i < NUM_OBJECTS && objects[i].oc_class == oclass; i++) - forget_single_object(i); + if ((!was_peaceful && mtmp->mpeaceful) || was_tame != mtmp->mtame) + return 1; + } + return 0; +} + +/* Can a stinking cloud physically exist at a certain position? + * NOT the same thing as can_center_cloud. + */ +boolean +valid_cloud_pos(coordxy x, coordxy y) +{ + if (!isok(x,y)) + return FALSE; + return ACCESSIBLE(levl[x][y].typ) || is_pool(x, y) || is_lava(x, y); +} + +/* Callback for getpos_sethilite, also used in determining whether a scroll + * should have its regular effects, or not because it was out of range. + */ +staticfn boolean +can_center_cloud(coordxy x, coordxy y) +{ + if (!valid_cloud_pos(x, y)) + return FALSE; + return (cansee(x, y) && distu(x, y) < 32); } -#endif -/* randomize the given list of numbers 0 <= i < count */ -STATIC_OVL void -randomize(indices, count) -int *indices; -int count; +staticfn void +display_stinking_cloud_positions(boolean on_off) { - int i, iswap, temp; + coordxy x, y, dx, dy; + int dist = 6; - for (i = count - 1; i > 0; i--) { - if ((iswap = rn2(i + 1)) == i) - continue; - temp = indices[i]; - indices[i] = indices[iswap]; - indices[iswap] = temp; + if (on_off) { + /* on */ + tmp_at(DISP_BEAM, cmap_to_glyph(S_goodpos)); + for (dx = -dist; dx <= dist; dx++) + for (dy = -dist; dy <= dist; dy++) { + x = u.ux + dx; + y = u.uy + dy; + /* hero's location is allowed but highlighting the hero's + spot makes map harder to read (if using '$' rather than + by changing background color) */ + if (u_at(x, y)) + continue; + if (can_center_cloud(x, y)) + tmp_at(x, y); + } + } else { + /* off */ + tmp_at(DISP_END, 0); } } -/* Forget % of known objects. */ -void -forget_objects(percent) -int percent; +staticfn void +seffect_enchant_armor(struct obj **sobjp) { - int i, count; - int indices[NUM_OBJECTS]; - - if (percent == 0) + struct obj *sobj = *sobjp; + schar s; + boolean special_armor; + boolean same_color; + struct obj *otmp = some_armor(&gy.youmonst); + boolean sblessed = sobj->blessed; + boolean scursed = sobj->cursed; + boolean confused = (Confusion != 0); + boolean old_erodeproof, new_erodeproof; + + if (!otmp) { + strange_feeling(sobj, !Blind + ? "Your skin glows then fades." + : "Your skin feels warm for a moment."); + *sobjp = 0; /* useup() in strange_feeling() */ + exercise(A_CON, !scursed); + exercise(A_STR, !scursed); + return; + } + if (confused) { + old_erodeproof = (otmp->oerodeproof != 0); + new_erodeproof = !scursed; + otmp->oerodeproof = 0; /* for messages */ + if (Blind) { + otmp->rknown = FALSE; + pline("%s warm for a moment.", Yobjnam2(otmp, "feel")); + } else { + otmp->rknown = TRUE; + pline("%s covered by a %s %s %s!", Yobjnam2(otmp, "are"), + scursed ? "mottled" : "shimmering", + hcolor(scursed ? NH_BLACK : NH_GOLDEN), + scursed ? "glow" + : (is_shield(otmp) ? "layer" : "shield")); + } + if (new_erodeproof && (otmp->oeroded || otmp->oeroded2)) { + otmp->oeroded = otmp->oeroded2 = 0; + pline("%s as good as new!", + Yobjnam2(otmp, Blind ? "feel" : "look")); + } + if (old_erodeproof && !new_erodeproof) { + /* restore old_erodeproof before shop charges */ + otmp->oerodeproof = 1; + costly_alteration(otmp, COST_DEGRD); + } + otmp->oerodeproof = new_erodeproof ? 1 : 0; + return; + } + /* elven armor vibrates warningly when enchanted beyond a limit */ + special_armor = is_elven_armor(otmp) + || (Role_if(PM_WIZARD) && otmp->otyp == CORNUTHAUM); + if (scursed) + same_color = (otmp->otyp == BLACK_DRAGON_SCALE_MAIL + || otmp->otyp == BLACK_DRAGON_SCALES); + else + same_color = (otmp->otyp == SILVER_DRAGON_SCALE_MAIL + || otmp->otyp == SILVER_DRAGON_SCALES + || otmp->otyp == SHIELD_OF_REFLECTION); + if (Blind) + same_color = FALSE; + + /* KMH -- catch underflow */ + s = scursed ? -otmp->spe : otmp->spe; + if (s > (special_armor ? 5 : 3) && rn2(s)) { + otmp->in_use = TRUE; + pline("%s violently %s%s%s for a while, then %s.", Yname2(otmp), + otense(otmp, Blind ? "vibrate" : "glow"), + (!Blind && !same_color) ? " " : "", + (Blind || same_color) ? "" : hcolor(scursed ? NH_BLACK + : NH_SILVER), + otense(otmp, "evaporate")); + remove_worn_item(otmp, FALSE); + useup(otmp); return; - if (percent <= 0 || percent > 100) { - impossible("forget_objects: bad percent %d", percent); + } + if (s < -100) + s = -100; /* avoid integer overflow with very negative armor */ + + /* Base power of the enchantment: + + 2 for -1 to +0 armor; + 1 for +1 to +2 armor; + 0 for +3 to +4 armor, etc. + + When disenchanting, everything is done with reversed signs. */ + s = (4 - s) / 2; + + /* Elven/artifact and nonmagical armor is easier to enchant; + blessed scrolls are more effective. */ + if (special_armor) + ++s; + if (!objects[otmp->otyp].oc_magic) + ++s; + if (sblessed) + ++s; + + if (s <= 0) { + s = 0; + if (otmp->spe > 0 && !rn2(otmp->spe)) + s = 1; + } else { + s = rnd(s); + } + if (s > 11) + s = 11; /* unlikely but possible: avoids an overflow later */ + + if (scursed) + s = -s; + + if (s >= 0 && Is_dragon_scales(otmp)) { + unsigned was_lit = otmp->lamplit; + int old_light = artifact_light(otmp) ? arti_light_radius(otmp) : 0; + + /* dragon scales get turned into dragon scale mail */ + pline("%s merges and hardens!", Yname2(otmp)); + setworn((struct obj *) 0, W_ARM); + /* assumes same order */ + otmp->otyp += GRAY_DRAGON_SCALE_MAIL - GRAY_DRAGON_SCALES; + otmp->lamplit = 0; /* don't want bless() or uncurse() to adjust + * light radius because scales -> scale_mail will + * result in a second increase with own message */ + if (sblessed) { + otmp->spe++; + cap_spe(otmp); + if (!otmp->blessed) + bless(otmp); + } else if (otmp->cursed) + uncurse(otmp); + otmp->known = 1; + setworn(otmp, W_ARM); + if (otmp->unpaid) + alter_cost(otmp, 0L); /* shop bill */ + otmp->lamplit = was_lit; + if (old_light) + maybe_adjust_light(otmp, old_light); return; } + pline("%s %s%s%s%s for a %s.", Yname2(otmp), + (s == 0) ? "violently " : "", + otense(otmp, Blind ? "vibrate" : "glow"), + (!Blind && !same_color) ? " " : "", + (Blind || same_color) + ? "" : hcolor(scursed ? NH_BLACK : NH_SILVER), + (s * s > 1) ? "while" : "moment"); + /* [this cost handling will need updating if shop pricing is + ever changed to care about curse/bless status of armor] */ + if (s < 0) + costly_alteration(otmp, COST_DECHNT); + if (scursed && !otmp->cursed) + curse(otmp); + else if (sblessed && !otmp->blessed) + bless(otmp); + else if (!scursed && otmp->cursed) + uncurse(otmp); + if (s) { + int oldspe = otmp->spe; + /* despite being schar, it shouldn't be possible for spe to wrap + here because it has been capped at 99 and s is quite small; + however, might need to change s if it takes spe past 99 */ + otmp->spe += s; + cap_spe(otmp); /* make sure that it doesn't exceed SPE_LIM */ + s = otmp->spe - oldspe; /* cap_spe() might have throttled 's' */ + if (s) /* skip if it got changed to 0 */ + adj_abon(otmp, s); /* adjust armor bonus for Dex or Int+Wis */ + gk.known = otmp->known; + /* update shop bill to reflect new higher price */ + if (s > 0 && otmp->unpaid) + alter_cost(otmp, 0L); + } - indices[0] = 0; /* lint suppression */ - for (count = 0, i = 1; i < NUM_OBJECTS; i++) - if (OBJ_DESCR(objects[i]) - && (objects[i].oc_name_known || objects[i].oc_uname)) - indices[count++] = i; + if ((otmp->spe > (special_armor ? 5 : 3)) + && (special_armor || !rn2(7))) + pline("%s %s.", Yobjnam2(otmp, "suddenly vibrate"), + Blind ? "again" : "unexpectedly"); +} + +/* destroy a random cursed armor worn by hero */ +staticfn boolean +disintegrate_cursed_armor(void) +{ + struct obj *armors[10]; + int idx = 0; + + armors[0] = NULL; + if (uarm && uarm->cursed) + armors[idx++] = uarm; + if (uarmc && uarmc->cursed) + armors[idx++] = uarmc; + if (uarmh && uarmh->cursed) + armors[idx++] = uarmh; + if (uarms && uarms->cursed) + armors[idx++] = uarms; + if (uarmg && uarmg->cursed) + armors[idx++] = uarmg; + if (uarmf && uarmf->cursed) + armors[idx++] = uarmf; + if (uarmu && uarmu->cursed) + armors[idx++] = uarmu; + if (!idx) + return FALSE; - if (count > 0) { - randomize(indices, count); + if (disintegrate_arm(armors[rn2(idx)])) + return TRUE; - /* forget first % of randomized indices */ - count = ((count * percent) + rn2(100)) / 100; - for (i = 0; i < count; i++) - forget_single_object(indices[i]); - } + return FALSE; } -/* Forget some or all of map (depends on parameters). */ -void -forget_map(howmuch) -int howmuch; +staticfn void +seffect_destroy_armor(struct obj **sobjp) { - register int zx, zy; + struct obj *sobj = *sobjp; + struct obj *otmp = some_armor(&gy.youmonst); + boolean scursed = sobj->cursed; + boolean confused = (Confusion != 0); + boolean old_erodeproof, new_erodeproof; - if (Sokoban) + if (confused) { + if (!otmp) { + strange_feeling(sobj, "Your bones itch."); + *sobjp = 0; /* useup() in strange_feeling() */ + exercise(A_STR, FALSE); + exercise(A_CON, FALSE); + return; + } + old_erodeproof = (otmp->oerodeproof != 0); + new_erodeproof = scursed; + otmp->oerodeproof = 0; /* for messages */ + p_glow2(otmp, NH_PURPLE); + if (old_erodeproof && !new_erodeproof) { + /* restore old_erodeproof before shop charges */ + otmp->oerodeproof = 1; + costly_alteration(otmp, COST_DEGRD); + } + otmp->oerodeproof = new_erodeproof ? 1 : 0; return; + } - known = TRUE; - for (zx = 0; zx < COLNO; zx++) - for (zy = 0; zy < ROWNO; zy++) - if (howmuch & ALL_MAP || rn2(7)) { - /* Zonk all memory of this location. */ - levl[zx][zy].seenv = 0; - levl[zx][zy].waslit = 0; - levl[zx][zy].glyph = cmap_to_glyph(S_stone); - lastseentyp[zx][zy] = STONE; + if (scursed) { + if (otmp && otmp->cursed) { + /* armor and scroll both cursed */ + pline("%s.", Yobjnam2(otmp, "vibrate")); + if (otmp->spe >= -6) { + otmp->spe += -1; + adj_abon(otmp, -1); + } + make_stunned((HStun & TIMEOUT) + (long) rn1(10, 10), TRUE); + } else if (disintegrate_arm(otmp)) { + gk.known = TRUE; + return; + } + } else { + boolean gets_choice = (otmp && sobj && sobj->blessed + && count_worn_armor() > 1); + + if (gets_choice) { + struct obj *atmp; + + if (!objects[sobj->otyp].oc_name_known) + pline("This is %s!", an(actualoname(sobj))); + gk.known = TRUE; + atmp = getobj("destroy", any_worn_armor_ok, GETOBJ_PROMPT); + /* check the return value, if user picked non-valid obj */ + if (any_worn_armor_ok(atmp) == GETOBJ_SUGGEST) + otmp = atmp; + if (disintegrate_arm(otmp)) { + gk.known = TRUE; + return; } - /* forget overview data for this level */ - forget_mapseen(ledger_no(&u.uz)); + } else if (sobj->blessed && disintegrate_cursed_armor()) { + gk.known = TRUE; + return; + } else if (!destroy_arm()) { + strange_feeling(sobj, "Your skin itches."); + *sobjp = 0; /* useup() in strange_feeling() */ + exercise(A_STR, FALSE); + exercise(A_CON, FALSE); + return; + } else + gk.known = TRUE; + } } -/* Forget all traps on the level. */ -void -forget_traps() +staticfn void +seffect_confuse_monster(struct obj **sobjp) { - register struct trap *trap; + struct obj *sobj = *sobjp; + boolean sblessed = sobj->blessed, + scursed = sobj->cursed, + confused = (Confusion != 0), + altfeedback = (Blind || Invisible); + const char *const hands = makeplural(body_part(HAND)); + + if (gy.youmonst.data->mlet != S_HUMAN || scursed) { + if (!HConfusion) + You_feel("confused."); + make_confused(HConfusion + rnd(100), FALSE); + } else if (confused) { + if (!sblessed) { + Your("%s begin to %s%s.", hands, + altfeedback ? "tingle" : "glow ", + altfeedback ? "" : hcolor(NH_PURPLE)); + make_confused(HConfusion + rnd(100), FALSE); + } else { + pline("A %s%s surrounds your %s.", + altfeedback ? "" : hcolor(NH_RED), + altfeedback ? "faint buzz" : " glow", body_part(HEAD)); + make_confused(0L, TRUE); + } + } else { + /* scroll vs spell */ + int incr = (sobj->oclass == SCROLL_CLASS) ? 3 : 0; + + if (!sblessed) { + if (altfeedback) + Your("%s tingle%s.", hands, u.umconf ? " even more" : ""); + else if (!u.umconf) + Your("%s begin to glow %s.", hands, hcolor(NH_RED)); + else + pline_The("%s glow of your %s intensifies.", hcolor(NH_RED), + hands); + incr += rnd(2); + } else { + if (altfeedback) + Your("%s tingle %s sharply.", hands, + u.umconf ? "even more" : "very"); + else + Your("%s glow %s brilliant %s.", hands, + u.umconf ? "an even more" : "a", hcolor(NH_RED)); + incr += rn1(8, 2); + } + /* after a while, repeated uses become less effective */ + if (u.umconf >= 40) + incr = 1; + u.umconf += (unsigned) incr; + } +} + +staticfn void +seffect_scare_monster(struct obj **sobjp) +{ + struct obj *sobj = *sobjp; + int otyp = sobj->otyp; + boolean scursed = sobj->cursed; + boolean confused = (Confusion != 0); + int ct = 0; + struct monst *mtmp; - /* forget all traps (except the one the hero is in :-) */ - for (trap = ftrap; trap; trap = trap->ntrap) - if ((trap->tx != u.ux || trap->ty != u.uy) && (trap->ttyp != HOLE)) - trap->tseen = 0; + for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { + if (DEADMONSTER(mtmp)) + continue; + if (cansee(mtmp->mx, mtmp->my)) { + if (confused || scursed) { + mtmp->mflee = mtmp->mfrozen = mtmp->msleeping = 0; + mtmp->mcanmove = 1; + } else if (!resist(mtmp, sobj->oclass, 0, NOTELL)) + monflee(mtmp, 0, FALSE, FALSE); + if (!mtmp->mtame) + ct++; /* pets don't laugh at you */ + } + } + if (otyp == SCR_SCARE_MONSTER || !ct) { + if (confused || scursed) { + Soundeffect(se_sad_wailing, 50); + } else { + Soundeffect(se_maniacal_laughter, 50); + } + You_hear("%s %s.", (confused || scursed) ? "sad wailing" + : "maniacal laughter", + !ct ? "in the distance" : "close by"); + } } -/* - * Forget given % of all levels that the hero has visited and not forgotten, - * except this one. - */ -void -forget_levels(percent) -int percent; +staticfn void +seffect_remove_curse(struct obj **sobjp) { - int i, count; - xchar maxl, this_lev; - int indices[MAXLINFO]; + struct obj *sobj = *sobjp; /* scroll or fake spellbook */ + int otyp = sobj->otyp; + boolean sblessed = sobj->blessed; + boolean scursed = sobj->cursed; + boolean confused = (Confusion != 0); + struct obj *obj, *nxto; + long wornmask; + + You_feel(!Hallucination + ? (!confused ? "like someone is helping you." + : "like you need some help.") + : (!confused ? "in touch with the Universal Oneness." + : "the power of the Force against you!")); + + if (scursed) { + pline_The("scroll disintegrates."); + } else { + /* 5.0: this used to use a straight + for (obj = invent; obj; obj = obj->nobj) {} + traversal, but for the confused case, secondary weapon might + become cursed and be dropped, moving it from the invent chain + to the floor chain at hero's spot, so we have to remember the + next object prior to processing the current one */ + for (obj = gi.invent; obj; obj = nxto) { + nxto = obj->nobj; + /* gold isn't subject to cursing and blessing */ + if (obj->oclass == COIN_CLASS) + continue; + /* hide current scroll from itself so that perm_invent won't + show known blessed scroll losing bknown when confused */ + if (obj == sobj && obj->quan == 1L) + continue; + wornmask = (obj->owornmask & ~(W_BALL | W_ART | W_ARTI)); + if (wornmask && !sblessed) { + /* handle a couple of special cases; we don't + allow auxiliary weapon slots to be used to + artificially increase number of worn items */ + if (obj == uswapwep) { + if (!u.twoweap) + wornmask = 0L; + } else if (obj == uquiver) { + if (obj->oclass == WEAPON_CLASS) { + /* mergeable weapon test covers ammo, + missiles, spears, daggers & knives */ + if (!objects[obj->otyp].oc_merge) + wornmask = 0L; + } else if (obj->oclass == GEM_CLASS) { + /* possibly ought to check whether + alternate weapon is a sling... */ + if (!uslinging()) + wornmask = 0L; + } else { + /* weptools don't merge and aren't + reasonable quivered weapons */ + wornmask = 0L; + } + } + } + if (sblessed || wornmask || obj->otyp == LOADSTONE + /* this treats an in-use leash as a worn item but does not + do the same for lit lamp/candle [seems inconsistent] */ + || (obj->otyp == LEASH && obj->leashmon)) { + /* water price varies by curse/bless status */ + boolean shop_h2o = (obj->unpaid && obj->otyp == POT_WATER); + + if (confused) { + blessorcurse(obj, 2); + /* lose knowledge of this object's curse/bless + state (even if it didn't actually change) */ + obj->bknown = 0; + /* blessorcurse() only affects uncursed items + so no need to worry about price of water + going down (hence no costly_alteration) */ + if (shop_h2o && (obj->cursed || obj->blessed)) + alter_cost(obj, 0L); /* price goes up */ + } else if (obj->cursed) { + if (shop_h2o) + costly_alteration(obj, COST_UNCURS); + uncurse(obj); + /* if the object was known to be cursed and is now + known not to be, make the scroll known; it's + trivial to identify anyway by comparing inventory + before and after */ + if (obj->bknown && otyp == SCR_REMOVE_CURSE) + learnscrolltyp(SCR_REMOVE_CURSE); + } + } + } + /* if riding, treat steed's saddle as if part of hero's invent */ + if (u.usteed && (obj = which_armor(u.usteed, W_SADDLE)) != 0) { + if (confused) { + blessorcurse(obj, 2); + obj->bknown = 0; /* skip set_bknown() */ + } else if (obj->cursed) { + uncurse(obj); + /* like rndcurse(sit.c), effect on regular inventory + doesn't show things glowing but saddle does */ + if (!Blind) { + pline("%s %s.", Yobjnam2(obj, "glow"), + hcolor("amber")); + obj->bknown = Hallucination ? 0 : 1; + } else { + obj->bknown = 0; /* skip set_bknown() */ + } + } + } + } + if (Punished && !confused) + unpunish(); + if (u.utrap && u.utraptype == TT_BURIEDBALL) { + buried_ball_to_freedom(); + pline_The("clasp on your %s vanishes.", body_part(LEG)); + } + update_inventory(); +} - if (percent == 0) - return; +staticfn void +seffect_create_monster(struct obj **sobjp) +{ + struct obj *sobj = *sobjp; + boolean sblessed = sobj->blessed; + boolean scursed = sobj->cursed; + boolean confused = (Confusion != 0); + + if (create_critters(1 + ((confused || scursed) ? 12 : 0) + + ((sblessed || rn2(73)) ? 0 : rnd(4)), + confused ? &mons[PM_ACID_BLOB] + : (struct permonst *) 0, + FALSE)) + gk.known = TRUE; + /* no need to flush monsters; we ask for identification only if the + * monsters are not visible + */ +} - if (percent <= 0 || percent > 100) { - impossible("forget_levels: bad percent %d", percent); +staticfn void +seffect_enchant_weapon(struct obj **sobjp) +{ + struct obj *sobj = *sobjp; + boolean sblessed = sobj->blessed; + boolean scursed = sobj->cursed; + boolean confused = (Confusion != 0); + boolean old_erodeproof, new_erodeproof; + int s; + + /* [What about twoweapon mode? Proofing/repairing/enchanting both + would be too powerful, but shouldn't we choose randomly between + primary and secondary instead of always acting on primary?] */ + if (confused && uwep + && erosion_matters(uwep) && uwep->oclass != ARMOR_CLASS) { + old_erodeproof = (uwep->oerodeproof != 0); + new_erodeproof = !scursed; + uwep->oerodeproof = 0; /* for messages */ + if (Blind) { + uwep->rknown = FALSE; + Your("weapon feels warm for a moment."); + } else { + uwep->rknown = TRUE; + pline("%s covered by a %s %s %s!", Yobjnam2(uwep, "are"), + scursed ? "mottled" : "shimmering", + hcolor(scursed ? NH_PURPLE : NH_GOLDEN), + scursed ? "glow" : "shield"); + } + if (new_erodeproof && (uwep->oeroded || uwep->oeroded2)) { + uwep->oeroded = uwep->oeroded2 = 0; + pline("%s as good as new!", + Yobjnam2(uwep, Blind ? "feel" : "look")); + } + if (old_erodeproof && !new_erodeproof) { + /* restore old_erodeproof before shop charges */ + uwep->oerodeproof = 1; + costly_alteration(uwep, COST_DEGRD); + } + uwep->oerodeproof = new_erodeproof ? 1 : 0; return; } + s = scursed ? -1 + : !uwep ? 1 /* guard further tests against null pointer */ + : (uwep->spe >= 9) ? (rn2(uwep->spe) == 0) /* usually 0, maybe 1 */ + : sblessed ? rnd(3 - uwep->spe / 3) /* >= 9 case prevents rnd(0) */ + : 1; /* uncursed */ + if (!chwepon(sobj, s)) + *sobjp = 0; /* nothing enchanted: strange_feeling -> useup */ + if (uwep) + cap_spe(uwep); +} - this_lev = ledger_no(&u.uz); - maxl = maxledgerno(); +staticfn void +seffect_taming(struct obj **sobjp) +{ + struct obj *sobj = *sobjp; + boolean confused = (Confusion != 0); + int candidates, res, results, vis_results; - /* count & save indices of non-forgotten visited levels */ - /* Sokoban levels are pre-mapped for the player, and should stay - * so, or they become nearly impossible to solve. But try to - * shift the forgetting elsewhere by fiddling with percent - * instead of forgetting fewer levels. - */ - indices[0] = 0; /* lint suppression */ - for (count = 0, i = 0; i <= maxl; i++) - if ((level_info[i].flags & VISITED) - && !(level_info[i].flags & FORGOTTEN) && i != this_lev) { - if (ledger_to_dnum(i) == sokoban_dnum) - percent += 2; - else - indices[count++] = i; + if (u.uswallow) { + candidates = 1; + results = vis_results = maybe_tame(u.ustuck, sobj); + } else { + int i, j, bd = confused ? 5 : 1; + struct monst *mtmp; + + /* note: maybe_tame() can return either positive or + negative values, but not both for the same scroll */ + candidates = results = vis_results = 0; + for (i = -bd; i <= bd; i++) + for (j = -bd; j <= bd; j++) { + if (!isok(u.ux + i, u.uy + j)) + continue; + if ((mtmp = m_at(u.ux + i, u.uy + j)) != 0 + || (!i && !j && (mtmp = u.usteed) != 0)) { + ++candidates; + res = maybe_tame(mtmp, sobj); + results += res; + if (canspotmon(mtmp)) + vis_results += res; + } + } + } + if (!results) { + pline("Nothing interesting %s.", + !candidates ? "happens" : "seems to happen"); + } else { + pline_The("neighborhood %s %sfriendlier.", + vis_results ? "is" : "seems", + (results < 0) ? "un" : ""); + if (vis_results > 0) + gk.known = TRUE; + } +} + +staticfn void +seffect_genocide(struct obj **sobjp) +{ + struct obj *sobj = *sobjp; + int otyp = sobj->otyp; + boolean sblessed = sobj->blessed; + boolean scursed = sobj->cursed; + boolean already_known = (sobj->oclass == SPBOOK_CLASS /* spell */ + || objects[otyp].oc_name_known); + + if (!already_known) + You("have found a scroll of genocide!"); + gk.known = TRUE; + if (sblessed) + do_class_genocide(); + else + do_genocide((!scursed) | (2 * !!Confusion)); +} + +staticfn void +seffect_light(struct obj **sobjp) +{ + struct obj *sobj = *sobjp; + boolean sblessed = sobj->blessed; + boolean scursed = sobj->cursed; + boolean confused = (Confusion != 0); + + if (!confused) { + if (!Blind) + gk.known = TRUE; + litroom(!scursed, sobj); + if (!scursed) { + if (lightdamage(sobj, TRUE, 5)) + gk.known = TRUE; } + } else { + int pm = scursed ? PM_BLACK_LIGHT : PM_YELLOW_LIGHT; - if (percent > 100) - percent = 100; + if ((svm.mvitals[pm].mvflags & G_GONE)) { + pline("Tiny lights sparkle in the air momentarily."); + } else { + /* surround with cancelled tame lights which won't explode */ + struct monst *mon; + boolean sawlights = FALSE; + int i, numlights = rn1(2, 3) + (sblessed * 2); + + for (i = 0; i < numlights; ++i) { + mon = makemon(&mons[pm], u.ux, u.uy, + MM_EDOG | NO_MINVENT | MM_NOMSG); + if (mon) { + initedog(mon, TRUE); + mon->msleeping = 0; + mon->mcan = TRUE; + if (canspotmon(mon)) + sawlights = TRUE; + newsym(mon->mx, mon->my); + } + } + if (sawlights) { + pline("Lights appear all around you!"); + gk.known = TRUE; + } + } + } +} - if (count > 0) { - randomize(indices, count); +staticfn void +seffect_charging(struct obj **sobjp) +{ + struct obj *sobj = *sobjp; + int otyp = sobj->otyp; + boolean sblessed = sobj->blessed; + boolean scursed = sobj->cursed; + boolean confused = (Confusion != 0); + boolean already_known = (sobj->oclass == SPBOOK_CLASS /* spell */ + || objects[otyp].oc_name_known); + struct obj *otmp; - /* forget first % of randomized indices */ - count = ((count * percent) + 50) / 100; - for (i = 0; i < count; i++) { - level_info[indices[i]].flags |= FORGOTTEN; - forget_mapseen(indices[i]); + if (confused) { + if (scursed) { + You_feel("discharged."); + u.uen = 0; + } else { + You_feel("charged up!"); + u.uen += d(sblessed ? 6 : 4, 4); + if (u.uen > u.uenmax) /* if current energy is already at */ + u.uenmax = u.uen; /* or near maximum, increase maximum */ + else + u.uen = u.uenmax; /* otherwise restore current to max */ } + disp.botl = TRUE; + return; + } + /* known = TRUE; -- handled inline here */ + if (!already_known) { + pline("This is a charging scroll."); + learnscroll(sobj); } + /* use it up now to prevent it from showing in the + getobj picklist because the "disappears" message + was already delivered */ + useup(sobj); + *sobjp = 0; /* it's gone */ + otmp = getobj("charge", charge_ok, GETOBJ_PROMPT | GETOBJ_ALLOWCNT); + if (otmp) + recharge(otmp, scursed ? -1 : sblessed ? 1 : 0); } -/* - * Forget some things (e.g. after reading a scroll of amnesia). When called, - * the following are always forgotten: - * - felt ball & chain - * - traps - * - part (6 out of 7) of the map - * - * Other things are subject to flags: - * howmuch & ALL_MAP = forget whole map - * howmuch & ALL_SPELLS = forget all spells - */ -STATIC_OVL void -forget(howmuch) -int howmuch; +staticfn void +seffect_amnesia(struct obj **sobjp) { - if (Punished) - u.bc_felt = 0; /* forget felt ball&chain */ + struct obj *sobj = *sobjp; + boolean sblessed = sobj->blessed; + + gk.known = TRUE; + forget((!sblessed ? ALL_SPELLS : 0)); + if (Hallucination) /* Ommmmmm! */ + Your("mind releases itself from mundane concerns."); + else if (!strncmpi(svp.plname, "Maud", 4)) + pline("As your mind turns inward on itself," + " you forget everything else."); + else if (rn2(2)) + pline("Who was that Maud person anyway?"); + else + pline("Thinking of Maud you forget everything else."); + exercise(A_WIS, FALSE); +} + +staticfn void +seffect_fire(struct obj **sobjp) +{ + struct obj *sobj = *sobjp; + int otyp = sobj->otyp; + boolean sblessed = sobj->blessed; + boolean confused = (Confusion != 0); + boolean already_known = (sobj->oclass == SPBOOK_CLASS /* spell */ + || objects[otyp].oc_name_known); + coord cc; + int dam, cval; + + cc.x = u.ux; + cc.y = u.uy; + cval = bcsign(sobj); + dam = (2 * (rn1(3, 3) + 2 * cval) + 1) / 3; + useup(sobj); + *sobjp = 0; /* it's gone */ + if (!already_known) + (void) learnscrolltyp(SCR_FIRE); + if (confused) { + if (Underwater) { + pline("A little %s around you vaporizes.", hliquid("water")); + } + else if (Fire_resistance) { + shieldeff(u.ux, u.uy); + monstseesu(M_SEEN_FIRE); + if (!Blind) + pline("Oh, look, what a pretty fire in your %s.", + makeplural(body_part(HAND))); + else + You_feel("a pleasant warmth in your %s.", + makeplural(body_part(HAND))); + } else { + monstunseesu(M_SEEN_FIRE); + pline_The("scroll catches fire and you burn your %s.", + makeplural(body_part(HAND))); + losehp(1, "scroll of fire", KILLED_BY_AN); + } + return; + } + if (Underwater) { + pline_The("%s around you vaporizes violently!", hliquid("water")); + } else { + if (sblessed) { + if (!already_known) + pline("This is a scroll of fire!"); + dam *= 5; + pline("Where do you want to center the explosion?"); + getpos_sethilite(display_stinking_cloud_positions, + can_center_cloud); + (void) getpos(&cc, TRUE, "the desired position"); + if (!can_center_cloud(cc.x, cc.y)) { + /* try to reach too far, get burned */ + cc.x = u.ux; + cc.y = u.uy; + } + } + if (u_at(cc.x, cc.y)) { + pline_The("scroll erupts in a tower of flame!"); + iflags.last_msg = PLNMSG_TOWER_OF_FLAME; /* for explode() */ + burn_away_slime(); + } + } +#define ZT_SPELL_O_FIRE 11 /* explained in splatter_burning_oil(explode.c) */ + explode(cc.x, cc.y, ZT_SPELL_O_FIRE, dam, SCROLL_CLASS, EXPL_FIERY); +#undef ZT_SPELL_O_FIRE +} - forget_map(howmuch); - forget_traps(); +staticfn void +seffect_earth(struct obj **sobjp) +{ + struct obj *sobj = *sobjp; + boolean sblessed = sobj->blessed; + boolean scursed = sobj->cursed; + boolean confused = (Confusion != 0); + + /* TODO: handle steeds */ + if (!Is_rogue_level(&u.uz) && has_ceiling(&u.uz) + && (!In_endgame(&u.uz) || Is_earthlevel(&u.uz))) { + coordxy x, y; + int nboulders = 0; + + /* Identify the scroll */ + if (u.uswallow) { + You_hear("rumbling."); + } else { + if (!avoid_ceiling(&u.uz)) { + pline_The("%s rumbles %s you!", ceiling(u.ux, u.uy), + sblessed ? "around" : "above"); + } else { + char matbuf[BUFSZ]; + const char *const avalanche = "avalanche"; + + Sprintf(matbuf, "%s", + sblessed ? makeplural(avalanche) : an(avalanche)); + pline("%s of boulders %s %s you!", + upstart(matbuf), vtense(matbuf, "materialize"), + sblessed ? "around" : "above"); + } + } + gk.known = 1; + sokoban_guilt(); + + /* Loop through the surrounding squares */ + if (!scursed) + for (x = u.ux - 1; x <= u.ux + 1; x++) { + for (y = u.uy - 1; y <= u.uy + 1; y++) { + /* Is this a suitable spot? */ + if (isok(x, y) && !closed_door(x, y) + && !IS_OBSTRUCTED(levl[x][y].typ) + && !IS_AIR(levl[x][y].typ) + && (x != u.ux || y != u.uy)) { + nboulders += + drop_boulder_on_monster(x, y, confused, TRUE); + } + } + } + /* Attack the player */ + if (!sblessed) { + drop_boulder_on_player(confused, !scursed, TRUE, FALSE); + } else if (!nboulders) + pline("But nothing else happens."); + } +} - /* 1 in 3 chance of forgetting some levels */ - if (!rn2(3)) - forget_levels(rn2(25)); +staticfn void +seffect_punishment(struct obj **sobjp) +{ + struct obj *sobj = *sobjp; + boolean sblessed = sobj->blessed; + boolean confused = (Confusion != 0); - /* 1 in 3 chance of forgetting some objects */ - if (!rn2(3)) - forget_objects(rn2(25)); + gk.known = TRUE; + if (confused || sblessed) { + You_feel("guilty."); + return; + } + punish(sobj); +} - if (howmuch & ALL_SPELLS) - losespells(); - /* - * Make sure that what was seen is restored correctly. To do this, - * we need to go blind for an instant --- turn off the display, - * then restart it. All this work is needed to correctly handle - * walls which are stone on one side and wall on the other. Turning - * off the seen bits above will make the wall revert to stone, but - * there are cases where we don't want this to happen. The easiest - * thing to do is to run it through the vision system again, which - * is always correct. - */ - docrt(); /* this correctly will reset vision */ +staticfn void +seffect_stinking_cloud(struct obj **sobjp) +{ + struct obj *sobj = *sobjp; + int otyp = sobj->otyp; + boolean already_known = (sobj->oclass == SPBOOK_CLASS /* spell */ + || objects[otyp].oc_name_known); + + if (!already_known) + You("have found a scroll of stinking cloud!"); + gk.known = TRUE; + do_stinking_cloud(sobj, already_known); } -/* monster is hit by scroll of taming's effect */ -STATIC_OVL int -maybe_tame(mtmp, sobj) -struct monst *mtmp; -struct obj *sobj; +staticfn void +seffect_blank_paper(struct obj **sobjp UNUSED) { - int was_tame = mtmp->mtame; - unsigned was_peaceful = mtmp->mpeaceful; + if (Blind) + You("don't remember there being any magic words on this scroll."); + else + pline("This scroll seems to be blank."); + gk.known = TRUE; +} - if (sobj->cursed) { - setmangry(mtmp, FALSE); - if (was_peaceful && !mtmp->mpeaceful) - return -1; +staticfn void +seffect_teleportation(struct obj **sobjp) +{ + struct obj *sobj = *sobjp; + boolean scursed = sobj->cursed; + boolean confused = (Confusion != 0); + + if (confused || scursed) { + level_tele(); + /* gives "materialize on different/same level!" message, must + be a teleport scroll */ + gk.known = TRUE; } else { - if (mtmp->isshk) - make_happy_shk(mtmp, FALSE); - else if (!resist(mtmp, sobj->oclass, 0, NOTELL)) - (void) tamedog(mtmp, (struct obj *) 0); - if ((!was_peaceful && mtmp->mpeaceful) || (!was_tame && mtmp->mtame)) - return 1; + scrolltele(sobj); + /* this will call learnscroll() as appropriate, and has results + which maybe shouldn't result in the scroll becoming known; + either way, no need to set gk.known here */ } - return 0; } -STATIC_OVL boolean -get_valid_stinking_cloud_pos(x,y) -int x,y; +staticfn void +seffect_gold_detection(struct obj **sobjp) { - return (!(!isok(x,y) || !cansee(x, y) - || !ACCESSIBLE(levl[x][y].typ) - || distu(x, y) >= 32)); + struct obj *sobj = *sobjp; + boolean scursed = sobj->cursed; + boolean confused = (Confusion != 0); + + if ((confused || scursed) ? trap_detect(sobj) : gold_detect(sobj)) + *sobjp = 0; /* failure: strange_feeling() -> useup() */ } -STATIC_OVL boolean -is_valid_stinking_cloud_pos(x, y, showmsg) -int x, y; -boolean showmsg; +staticfn void +seffect_food_detection(struct obj **sobjp) { - if (!get_valid_stinking_cloud_pos(x,y)) { - if (showmsg) - You("smell rotten eggs."); - return FALSE; - } - return TRUE; + struct obj *sobj = *sobjp; + + if (food_detect(sobj)) + *sobjp = 0; /* nothing detected: strange_feeling -> useup */ } -STATIC_PTR void -display_stinking_cloud_positions(state) -int state; +staticfn void +seffect_identify(struct obj **sobjp) { - if (state == 0) { - tmp_at(DISP_BEAM, cmap_to_glyph(S_goodpos)); - } else if (state == 1) { - int x, y, dx, dy; - int dist = 6; + struct obj *sobj = *sobjp; + int otyp = sobj->otyp; + boolean is_scroll = (sobj->oclass == SCROLL_CLASS); + boolean sblessed = sobj->blessed; + boolean scursed = sobj->cursed; + boolean confused = (Confusion != 0); + boolean already_known = (sobj->oclass == SPBOOK_CLASS /* spell */ + || objects[otyp].oc_name_known); + + if (is_scroll) { /* scroll of identify */ + /* known = TRUE; -- handled inline here */ + /* use up the scroll first, before learnscrolltyp() -> makeknown() + performs perm_invent update; also simplifies empty invent check */ + useup(sobj); + *sobjp = 0; /* it's gone */ + /* scroll just identifies itself for any scroll read while confused + or for cursed scroll read without knowing identify yet */ + if (confused || (scursed && !already_known)) + You("identify this as an identify scroll."); + else if (!already_known) + pline("This is an identify scroll."); + if (!already_known) + (void) learnscrolltyp(SCR_IDENTIFY); + if (confused || (scursed && !already_known)) + return; + } - for (dx = -dist; dx <= dist; dx++) - for (dy = -dist; dy <= dist; dy++) { - x = u.ux + dx; - y = u.uy + dy; - if (get_valid_stinking_cloud_pos(x,y)) - tmp_at(x, y); - } + if (gi.invent) { + int cval = 1; + if (sblessed || (!scursed && !rn2(5))) { + cval = rn2(5); + /* note: if cval==0, identify all items */ + if (cval == 1 && sblessed && Luck > 0) + ++cval; + } + identify_pack(cval, !already_known); } else { - tmp_at(DISP_END, 0); + /* spell cast with inventory empty or scroll read when it's + the only item leaving empty inventory after being used up */ + pline("You're not carrying anything%s to be identified.", + (is_scroll) ? " else" : ""); + } +} + +staticfn void +seffect_magic_mapping(struct obj **sobjp) +{ + struct obj *sobj = *sobjp; + boolean is_scroll = (sobj->oclass == SCROLL_CLASS); + boolean sblessed = sobj->blessed; + boolean scursed = sobj->cursed; + boolean confused = (Confusion != 0); + int cval; + + if (is_scroll) { + if (svl.level.flags.nommap) { + Your("mind is filled with crazy lines!"); + if (Hallucination) + pline("Wow! Modern art."); + else + Your("%s spins in bewilderment.", body_part(HEAD)); + make_confused(HConfusion + rnd(30), FALSE); + return; + } + if (sblessed) { + coordxy x, y; + + for (x = 1; x < COLNO; x++) + for (y = 0; y < ROWNO; y++) + if (levl[x][y].typ == SDOOR) { + cvt_sdoor_to_door(&levl[x][y]); + if (Is_rogue_level(&u.uz)) + unblock_point(x, y); + } + /* do_mapping() already reveals secret passages */ + } + gk.known = TRUE; + } + + if (svl.level.flags.nommap) { + Your("%s spins as %s blocks the spell!", body_part(HEAD), + something); + make_confused(HConfusion + rnd(30), FALSE); + return; + } + pline("A map coalesces in your mind!"); + cval = (scursed && !confused); + if (cval) + HConfusion = 1; /* to screw up map */ + notice_mon_off(); + do_mapping(); + notice_mon_on(); + if (cval) { + HConfusion = 0; /* restore */ + pline("Unfortunately, you can't grasp the details."); + } +} + +#ifdef MAIL_STRUCTURES +staticfn void +seffect_mail(struct obj **sobjp) +{ + struct obj *sobj = *sobjp; + boolean odd = (sobj->o_id % 2) == 1; + + gk.known = TRUE; + switch (sobj->spe) { + case 2: + /* "stamped scroll" created via magic marker--without a stamp */ + pline("This scroll is marked \"%s\".", + odd ? "Postage Due" : "Return to Sender"); + break; + case 1: + /* scroll of mail obtained from bones file or from wishing; + note to the puzzled: the game Larn actually sends you junk + mail if you win! */ + pline("This seems to be %s.", + odd ? "a chain letter threatening your luck" + : "junk mail addressed to the finder of the Eye of Larn"); + break; + default: +#ifdef MAIL + readmail(sobj); +#else + /* unreachable since with MAIL undefined, sobj->spe won't be 0; + as a precaution, be prepared to give arbitrary feedback; + caller has already reported that it disappears upon reading */ + pline("That was a scroll of mail?"); +#endif + break; } } +#endif /* MAIL_STRUCTURES */ /* scroll effects; return 1 if we use up the scroll and possibly make it become discovered, 0 if caller should take care of those side-effects */ int -seffects(sobj) -struct obj *sobj; /* scroll, or fake spellbook object for scroll-like spell */ +seffects( + struct obj *sobj) /* sobj - scroll or fake spellbook for spell */ { - int cval, otyp = sobj->otyp; - boolean confused = (Confusion != 0), sblessed = sobj->blessed, - scursed = sobj->cursed, already_known, old_erodeproof, - new_erodeproof; - struct obj *otmp; + int otyp = sobj->otyp; if (objects[otyp].oc_magic) exercise(A_WIS, TRUE); /* just for trying */ - already_known = (sobj->oclass == SPBOOK_CLASS /* spell */ - || objects[otyp].oc_name_known); switch (otyp) { -#ifdef MAIL +#ifdef MAIL_STRUCTURES case SCR_MAIL: - known = TRUE; - if (sobj->spe == 2) - /* "stamped scroll" created via magic marker--without a stamp */ - pline("This scroll is marked \"postage due\"."); - else if (sobj->spe) - /* scroll of mail obtained from bones file or from wishing; - * note to the puzzled: the game Larn actually sends you junk - * mail if you win! - */ - pline( - "This seems to be junk mail addressed to the finder of the Eye of Larn."); - else - readmail(sobj); + seffect_mail(&sobj); break; #endif - case SCR_ENCHANT_ARMOR: { - register schar s; - boolean special_armor; - boolean same_color; - - otmp = some_armor(&youmonst); - if (!otmp) { - strange_feeling(sobj, !Blind - ? "Your skin glows then fades." - : "Your skin feels warm for a moment."); - sobj = 0; /* useup() in strange_feeling() */ - exercise(A_CON, !scursed); - exercise(A_STR, !scursed); - break; - } - if (confused) { - old_erodeproof = (otmp->oerodeproof != 0); - new_erodeproof = !scursed; - otmp->oerodeproof = 0; /* for messages */ - if (Blind) { - otmp->rknown = FALSE; - pline("%s warm for a moment.", Yobjnam2(otmp, "feel")); - } else { - otmp->rknown = TRUE; - pline("%s covered by a %s %s %s!", Yobjnam2(otmp, "are"), - scursed ? "mottled" : "shimmering", - hcolor(scursed ? NH_BLACK : NH_GOLDEN), - scursed ? "glow" - : (is_shield(otmp) ? "layer" : "shield")); - } - if (new_erodeproof && (otmp->oeroded || otmp->oeroded2)) { - otmp->oeroded = otmp->oeroded2 = 0; - pline("%s as good as new!", - Yobjnam2(otmp, Blind ? "feel" : "look")); - } - if (old_erodeproof && !new_erodeproof) { - /* restore old_erodeproof before shop charges */ - otmp->oerodeproof = 1; - costly_alteration(otmp, COST_DEGRD); - } - otmp->oerodeproof = new_erodeproof ? 1 : 0; - break; - } - /* elven armor vibrates warningly when enchanted beyond a limit */ - special_armor = is_elven_armor(otmp) - || (Role_if(PM_WIZARD) && otmp->otyp == CORNUTHAUM); - if (scursed) - same_color = (otmp->otyp == BLACK_DRAGON_SCALE_MAIL - || otmp->otyp == BLACK_DRAGON_SCALES); - else - same_color = (otmp->otyp == SILVER_DRAGON_SCALE_MAIL - || otmp->otyp == SILVER_DRAGON_SCALES - || otmp->otyp == SHIELD_OF_REFLECTION); - if (Blind) - same_color = FALSE; - - /* KMH -- catch underflow */ - s = scursed ? -otmp->spe : otmp->spe; - if (s > (special_armor ? 5 : 3) && rn2(s)) { - otmp->in_use = TRUE; - pline("%s violently %s%s%s for a while, then %s.", Yname2(otmp), - otense(otmp, Blind ? "vibrate" : "glow"), - (!Blind && !same_color) ? " " : "", - (Blind || same_color) ? "" : hcolor(scursed ? NH_BLACK - : NH_SILVER), - otense(otmp, "evaporate")); - remove_worn_item(otmp, FALSE); - useup(otmp); - break; - } - s = scursed ? -1 - : (otmp->spe >= 9) - ? (rn2(otmp->spe) == 0) - : sblessed - ? rnd(3 - otmp->spe / 3) - : 1; - if (s >= 0 && Is_dragon_scales(otmp)) { - /* dragon scales get turned into dragon scale mail */ - pline("%s merges and hardens!", Yname2(otmp)); - setworn((struct obj *) 0, W_ARM); - /* assumes same order */ - otmp->otyp += GRAY_DRAGON_SCALE_MAIL - GRAY_DRAGON_SCALES; - if (sblessed) { - otmp->spe++; - if (!otmp->blessed) - bless(otmp); - } else if (otmp->cursed) - uncurse(otmp); - otmp->known = 1; - setworn(otmp, W_ARM); - if (otmp->unpaid) - alter_cost(otmp, 0L); /* shop bill */ - break; - } - pline("%s %s%s%s%s for a %s.", Yname2(otmp), - s == 0 ? "violently " : "", - otense(otmp, Blind ? "vibrate" : "glow"), - (!Blind && !same_color) ? " " : "", - (Blind || same_color) - ? "" : hcolor(scursed ? NH_BLACK : NH_SILVER), - (s * s > 1) ? "while" : "moment"); - /* [this cost handling will need updating if shop pricing is - ever changed to care about curse/bless status of armor] */ - if (s < 0) - costly_alteration(otmp, COST_DECHNT); - if (scursed && !otmp->cursed) - curse(otmp); - else if (sblessed && !otmp->blessed) - bless(otmp); - else if (!scursed && otmp->cursed) - uncurse(otmp); - if (s) { - otmp->spe += s; - adj_abon(otmp, s); - known = otmp->known; - /* update shop bill to reflect new higher price */ - if (s > 0 && otmp->unpaid) - alter_cost(otmp, 0L); - } - - if ((otmp->spe > (special_armor ? 5 : 3)) - && (special_armor || !rn2(7))) - pline("%s %s.", Yobjnam2(otmp, "suddenly vibrate"), - Blind ? "again" : "unexpectedly"); + case SCR_ENCHANT_ARMOR: + seffect_enchant_armor(&sobj); + break; + case SCR_DESTROY_ARMOR: + seffect_destroy_armor(&sobj); break; - } - case SCR_DESTROY_ARMOR: { - otmp = some_armor(&youmonst); - if (confused) { - if (!otmp) { - strange_feeling(sobj, "Your bones itch."); - sobj = 0; /* useup() in strange_feeling() */ - exercise(A_STR, FALSE); - exercise(A_CON, FALSE); - break; - } - old_erodeproof = (otmp->oerodeproof != 0); - new_erodeproof = scursed; - otmp->oerodeproof = 0; /* for messages */ - p_glow2(otmp, NH_PURPLE); - if (old_erodeproof && !new_erodeproof) { - /* restore old_erodeproof before shop charges */ - otmp->oerodeproof = 1; - costly_alteration(otmp, COST_DEGRD); - } - otmp->oerodeproof = new_erodeproof ? 1 : 0; - break; - } - if (!scursed || !otmp || !otmp->cursed) { - if (!destroy_arm(otmp)) { - strange_feeling(sobj, "Your skin itches."); - sobj = 0; /* useup() in strange_feeling() */ - exercise(A_STR, FALSE); - exercise(A_CON, FALSE); - break; - } else - known = TRUE; - } else { /* armor and scroll both cursed */ - pline("%s.", Yobjnam2(otmp, "vibrate")); - if (otmp->spe >= -6) { - otmp->spe += -1; - adj_abon(otmp, -1); - } - make_stunned((HStun & TIMEOUT) + (long) rn1(10, 10), TRUE); - } - } break; case SCR_CONFUSE_MONSTER: case SPE_CONFUSE_MONSTER: - if (youmonst.data->mlet != S_HUMAN || scursed) { - if (!HConfusion) - You_feel("confused."); - make_confused(HConfusion + rnd(100), FALSE); - } else if (confused) { - if (!sblessed) { - Your("%s begin to %s%s.", makeplural(body_part(HAND)), - Blind ? "tingle" : "glow ", - Blind ? "" : hcolor(NH_PURPLE)); - make_confused(HConfusion + rnd(100), FALSE); - } else { - pline("A %s%s surrounds your %s.", - Blind ? "" : hcolor(NH_RED), - Blind ? "faint buzz" : " glow", body_part(HEAD)); - make_confused(0L, TRUE); - } - } else { - if (!sblessed) { - Your("%s%s %s%s.", makeplural(body_part(HAND)), - Blind ? "" : " begin to glow", - Blind ? (const char *) "tingle" : hcolor(NH_RED), - u.umconf ? " even more" : ""); - u.umconf++; - } else { - if (Blind) - Your("%s tingle %s sharply.", makeplural(body_part(HAND)), - u.umconf ? "even more" : "very"); - else - Your("%s glow a%s brilliant %s.", - makeplural(body_part(HAND)), - u.umconf ? "n even more" : "", hcolor(NH_RED)); - /* after a while, repeated uses become less effective */ - if (u.umconf >= 40) - u.umconf++; - else - u.umconf += rn1(8, 2); - } - } + seffect_confuse_monster(&sobj); break; case SCR_SCARE_MONSTER: - case SPE_CAUSE_FEAR: { - register int ct = 0; - register struct monst *mtmp; - - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) - continue; - if (cansee(mtmp->mx, mtmp->my)) { - if (confused || scursed) { - mtmp->mflee = mtmp->mfrozen = mtmp->msleeping = 0; - mtmp->mcanmove = 1; - } else if (!resist(mtmp, sobj->oclass, 0, NOTELL)) - monflee(mtmp, 0, FALSE, FALSE); - if (!mtmp->mtame) - ct++; /* pets don't laugh at you */ - } - } - if (otyp == SCR_SCARE_MONSTER || !ct) - You_hear("%s %s.", (confused || scursed) ? "sad wailing" - : "maniacal laughter", - !ct ? "in the distance" : "close by"); + case SPE_CAUSE_FEAR: + seffect_scare_monster(&sobj); break; - } case SCR_BLANK_PAPER: - if (Blind) - You("don't remember there being any magic words on this scroll."); - else - pline("This scroll seems to be blank."); - known = TRUE; + seffect_blank_paper(&sobj); break; case SCR_REMOVE_CURSE: - case SPE_REMOVE_CURSE: { - register struct obj *obj; - - You_feel(!Hallucination - ? (!confused ? "like someone is helping you." - : "like you need some help.") - : (!confused ? "in touch with the Universal Oneness." - : "the power of the Force against you!")); - - if (scursed) { - pline_The("scroll disintegrates."); - } else { - for (obj = invent; obj; obj = obj->nobj) { - long wornmask; - - /* gold isn't subject to cursing and blessing */ - if (obj->oclass == COIN_CLASS) - continue; - /* hide current scroll from itself so that perm_invent won't - show known blessed scroll losing bknown when confused */ - if (obj == sobj && obj->quan == 1L) - continue; - wornmask = (obj->owornmask & ~(W_BALL | W_ART | W_ARTI)); - if (wornmask && !sblessed) { - /* handle a couple of special cases; we don't - allow auxiliary weapon slots to be used to - artificially increase number of worn items */ - if (obj == uswapwep) { - if (!u.twoweap) - wornmask = 0L; - } else if (obj == uquiver) { - if (obj->oclass == WEAPON_CLASS) { - /* mergeable weapon test covers ammo, - missiles, spears, daggers & knives */ - if (!objects[obj->otyp].oc_merge) - wornmask = 0L; - } else if (obj->oclass == GEM_CLASS) { - /* possibly ought to check whether - alternate weapon is a sling... */ - if (!uslinging()) - wornmask = 0L; - } else { - /* weptools don't merge and aren't - reasonable quivered weapons */ - wornmask = 0L; - } - } - } - if (sblessed || wornmask || obj->otyp == LOADSTONE - || (obj->otyp == LEASH && obj->leashmon)) { - /* water price varies by curse/bless status */ - boolean shop_h2o = (obj->unpaid && obj->otyp == POT_WATER); - - if (confused) { - blessorcurse(obj, 2); - /* lose knowledge of this object's curse/bless - state (even if it didn't actually change) */ - obj->bknown = 0; - /* blessorcurse() only affects uncursed items - so no need to worry about price of water - going down (hence no costly_alteration) */ - if (shop_h2o && (obj->cursed || obj->blessed)) - alter_cost(obj, 0L); /* price goes up */ - } else if (obj->cursed) { - if (shop_h2o) - costly_alteration(obj, COST_UNCURS); - uncurse(obj); - } - } - } - } - if (Punished && !confused) - unpunish(); - if (u.utrap && u.utraptype == TT_BURIEDBALL) { - buried_ball_to_freedom(); - pline_The("clasp on your %s vanishes.", body_part(LEG)); - } - update_inventory(); + case SPE_REMOVE_CURSE: + seffect_remove_curse(&sobj); break; - } case SCR_CREATE_MONSTER: case SPE_CREATE_MONSTER: - if (create_critters(1 + ((confused || scursed) ? 12 : 0) - + ((sblessed || rn2(73)) ? 0 : rnd(4)), - confused ? &mons[PM_ACID_BLOB] - : (struct permonst *) 0, - FALSE)) - known = TRUE; - /* no need to flush monsters; we ask for identification only if the - * monsters are not visible - */ + seffect_create_monster(&sobj); break; case SCR_ENCHANT_WEAPON: - /* [What about twoweapon mode? Proofing/repairing/enchanting both - would be too powerful, but shouldn't we choose randomly between - primary and secondary instead of always acting on primary?] */ - if (confused && uwep - && erosion_matters(uwep) && uwep->oclass != ARMOR_CLASS) { - old_erodeproof = (uwep->oerodeproof != 0); - new_erodeproof = !scursed; - uwep->oerodeproof = 0; /* for messages */ - if (Blind) { - uwep->rknown = FALSE; - Your("weapon feels warm for a moment."); - } else { - uwep->rknown = TRUE; - pline("%s covered by a %s %s %s!", Yobjnam2(uwep, "are"), - scursed ? "mottled" : "shimmering", - hcolor(scursed ? NH_PURPLE : NH_GOLDEN), - scursed ? "glow" : "shield"); - } - if (new_erodeproof && (uwep->oeroded || uwep->oeroded2)) { - uwep->oeroded = uwep->oeroded2 = 0; - pline("%s as good as new!", - Yobjnam2(uwep, Blind ? "feel" : "look")); - } - if (old_erodeproof && !new_erodeproof) { - /* restore old_erodeproof before shop charges */ - uwep->oerodeproof = 1; - costly_alteration(uwep, COST_DEGRD); - } - uwep->oerodeproof = new_erodeproof ? 1 : 0; - break; - } - if (!chwepon(sobj, scursed ? -1 - : !uwep ? 1 - : (uwep->spe >= 9) ? !rn2(uwep->spe) - : sblessed ? rnd(3 - uwep->spe / 3) - : 1)) - sobj = 0; /* nothing enchanted: strange_feeling -> useup */ + seffect_enchant_weapon(&sobj); break; case SCR_TAMING: - case SPE_CHARM_MONSTER: { - int candidates, res, results, vis_results; - - if (u.uswallow) { - candidates = 1; - results = vis_results = maybe_tame(u.ustuck, sobj); - } else { - int i, j, bd = confused ? 5 : 1; - struct monst *mtmp; - - /* note: maybe_tame() can return either positive or - negative values, but not both for the same scroll */ - candidates = results = vis_results = 0; - for (i = -bd; i <= bd; i++) - for (j = -bd; j <= bd; j++) { - if (!isok(u.ux + i, u.uy + j)) - continue; - if ((mtmp = m_at(u.ux + i, u.uy + j)) != 0 - || (!i && !j && (mtmp = u.usteed) != 0)) { - ++candidates; - res = maybe_tame(mtmp, sobj); - results += res; - if (canspotmon(mtmp)) - vis_results += res; - } - } - } - if (!results) { - pline("Nothing interesting %s.", - !candidates ? "happens" : "seems to happen"); - } else { - pline_The("neighborhood %s %sfriendlier.", - vis_results ? "is" : "seems", - (results < 0) ? "un" : ""); - if (vis_results > 0) - known = TRUE; - } + case SPE_CHARM_MONSTER: + seffect_taming(&sobj); break; - } case SCR_GENOCIDE: - if (!already_known) - You("have found a scroll of genocide!"); - known = TRUE; - if (sblessed) - do_class_genocide(); - else - do_genocide((!scursed) | (2 * !!Confusion)); + seffect_genocide(&sobj); break; case SCR_LIGHT: - if (!confused || rn2(5)) { - if (!Blind) - known = TRUE; - litroom(!confused && !scursed, sobj); - if (!confused && !scursed) { - if (lightdamage(sobj, TRUE, 5)) - known = TRUE; - } - } else { - /* could be scroll of create monster, don't set known ...*/ - (void) create_critters(1, !scursed ? &mons[PM_YELLOW_LIGHT] - : &mons[PM_BLACK_LIGHT], - TRUE); - } + seffect_light(&sobj); break; case SCR_TELEPORTATION: - if (confused || scursed) { - level_tele(); - } else { - known = scrolltele(sobj); - } + seffect_teleportation(&sobj); break; case SCR_GOLD_DETECTION: - if ((confused || scursed) ? trap_detect(sobj) : gold_detect(sobj)) - sobj = 0; /* failure: strange_feeling() -> useup() */ + seffect_gold_detection(&sobj); break; case SCR_FOOD_DETECTION: case SPE_DETECT_FOOD: - if (food_detect(sobj)) - sobj = 0; /* nothing detected: strange_feeling -> useup */ + seffect_food_detection(&sobj); break; case SCR_IDENTIFY: - /* known = TRUE; -- handled inline here */ - /* use up the scroll first, before makeknown() performs a - perm_invent update; also simplifies empty invent check */ - useup(sobj); - sobj = 0; /* it's gone */ - if (confused) - You("identify this as an identify scroll."); - else if (!already_known || !invent) - /* force feedback now if invent became - empty after using up this scroll */ - pline("This is an identify scroll."); - if (!already_known) - (void) learnscrolltyp(SCR_IDENTIFY); - /*FALLTHRU*/ case SPE_IDENTIFY: - cval = 1; - if (sblessed || (!scursed && !rn2(5))) { - cval = rn2(5); - /* note: if cval==0, identify all items */ - if (cval == 1 && sblessed && Luck > 0) - ++cval; - } - if (invent && !confused) { - identify_pack(cval, !already_known); - } else if (otyp == SPE_IDENTIFY) { - /* when casting a spell we know we're not confused, - so inventory must be empty (another message has - already been given above if reading a scroll) */ - pline("You're not carrying anything to be identified."); - } + seffect_identify(&sobj); break; case SCR_CHARGING: - if (confused) { - if (scursed) { - You_feel("discharged."); - u.uen = 0; - } else { - You_feel("charged up!"); - u.uen += d(sblessed ? 6 : 4, 4); - if (u.uen > u.uenmax) /* if current energy is already at */ - u.uenmax = u.uen; /* or near maximum, increase maximum */ - else - u.uen = u.uenmax; /* otherwise restore current to max */ - } - context.botl = 1; - break; - } - /* known = TRUE; -- handled inline here */ - if (!already_known) { - pline("This is a charging scroll."); - learnscroll(sobj); - } - /* use it up now to prevent it from showing in the - getobj picklist because the "disappears" message - was already delivered */ - useup(sobj); - sobj = 0; /* it's gone */ - otmp = getobj(all_count, "charge"); - if (otmp) - recharge(otmp, scursed ? -1 : sblessed ? 1 : 0); + seffect_charging(&sobj); break; case SCR_MAGIC_MAPPING: - if (level.flags.nommap) { - Your("mind is filled with crazy lines!"); - if (Hallucination) - pline("Wow! Modern art."); - else - Your("%s spins in bewilderment.", body_part(HEAD)); - make_confused(HConfusion + rnd(30), FALSE); - break; - } - if (sblessed) { - register int x, y; - - for (x = 1; x < COLNO; x++) - for (y = 0; y < ROWNO; y++) - if (levl[x][y].typ == SDOOR) - cvt_sdoor_to_door(&levl[x][y]); - /* do_mapping() already reveals secret passages */ - } - known = TRUE; - /*FALLTHRU*/ case SPE_MAGIC_MAPPING: - if (level.flags.nommap) { - Your("%s spins as %s blocks the spell!", body_part(HEAD), - something); - make_confused(HConfusion + rnd(30), FALSE); - break; - } - pline("A map coalesces in your mind!"); - cval = (scursed && !confused); - if (cval) - HConfusion = 1; /* to screw up map */ - do_mapping(); - if (cval) { - HConfusion = 0; /* restore */ - pline("Unfortunately, you can't grasp the details."); - } + seffect_magic_mapping(&sobj); break; case SCR_AMNESIA: - known = TRUE; - forget((!sblessed ? ALL_SPELLS : 0) - | (!confused || scursed ? ALL_MAP : 0)); - if (Hallucination) /* Ommmmmm! */ - Your("mind releases itself from mundane concerns."); - else if (!strncmpi(plname, "Maud", 4)) - pline( - "As your mind turns inward on itself, you forget everything else."); - else if (rn2(2)) - pline("Who was that Maud person anyway?"); - else - pline("Thinking of Maud you forget everything else."); - exercise(A_WIS, FALSE); + seffect_amnesia(&sobj); break; - case SCR_FIRE: { - coord cc; - int dam; - - cc.x = u.ux; - cc.y = u.uy; - cval = bcsign(sobj); - dam = (2 * (rn1(3, 3) + 2 * cval) + 1) / 3; - useup(sobj); - sobj = 0; /* it's gone */ - if (!already_known) - (void) learnscrolltyp(SCR_FIRE); - if (confused) { - if (Fire_resistance) { - shieldeff(u.ux, u.uy); - if (!Blind) - pline("Oh, look, what a pretty fire in your %s.", - makeplural(body_part(HAND))); - else - You_feel("a pleasant warmth in your %s.", - makeplural(body_part(HAND))); - } else { - pline_The("scroll catches fire and you burn your %s.", - makeplural(body_part(HAND))); - losehp(1, "scroll of fire", KILLED_BY_AN); - } - break; - } - if (Underwater) { - pline_The("%s around you vaporizes violently!", hliquid("water")); - } else { - if (sblessed) { - if (!already_known) - pline("This is a scroll of fire!"); - dam *= 5; - pline("Where do you want to center the explosion?"); - getpos_sethilite(display_stinking_cloud_positions, - get_valid_stinking_cloud_pos); - (void) getpos(&cc, TRUE, "the desired position"); - if (!is_valid_stinking_cloud_pos(cc.x, cc.y, FALSE)) { - /* try to reach too far, get burned */ - cc.x = u.ux; - cc.y = u.uy; - } - } - if (cc.x == u.ux && cc.y == u.uy) { - pline_The("scroll erupts in a tower of flame!"); - iflags.last_msg = PLNMSG_TOWER_OF_FLAME; /* for explode() */ - burn_away_slime(); - } - } - explode(cc.x, cc.y, 11, dam, SCROLL_CLASS, EXPL_FIERY); + case SCR_FIRE: + seffect_fire(&sobj); break; - } case SCR_EARTH: - /* TODO: handle steeds */ - 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; - - /* Identify the scroll */ - if (u.uswallow) - You_hear("rumbling."); - else - pline_The("%s rumbles %s you!", ceiling(u.ux, u.uy), - sblessed ? "around" : "above"); - known = 1; - sokoban_guilt(); - - /* Loop through the surrounding squares */ - if (!scursed) - for (x = u.ux - 1; x <= u.ux + 1; x++) { - for (y = u.uy - 1; y <= u.uy + 1; y++) { - /* Is this a suitable spot? */ - if (isok(x, y) && !closed_door(x, y) - && !IS_ROCK(levl[x][y].typ) - && !IS_AIR(levl[x][y].typ) - && (x != u.ux || y != u.uy)) { - nboulders += - drop_boulder_on_monster(x, y, confused, TRUE); - } - } - } - /* Attack the player */ - if (!sblessed) { - drop_boulder_on_player(confused, !scursed, TRUE, FALSE); - } else if (!nboulders) - pline("But nothing else happens."); - } + seffect_earth(&sobj); break; case SCR_PUNISHMENT: - known = TRUE; - if (confused || sblessed) { - You_feel("guilty."); - break; - } - punish(sobj); + seffect_punishment(&sobj); break; - case SCR_STINKING_CLOUD: { - coord cc; - - if (!already_known) - You("have found a scroll of stinking cloud!"); - known = TRUE; - pline("Where do you want to center the %scloud?", - already_known ? "stinking " : ""); - cc.x = u.ux; - cc.y = u.uy; - getpos_sethilite(display_stinking_cloud_positions, - get_valid_stinking_cloud_pos); - if (getpos(&cc, TRUE, "the desired position") < 0) { - pline1(Never_mind); - break; - } - if (!is_valid_stinking_cloud_pos(cc.x, cc.y, TRUE)) - break; - (void) create_gas_cloud(cc.x, cc.y, 3 + bcsign(sobj), - 8 + 4 * bcsign(sobj)); + case SCR_STINKING_CLOUD: + seffect_stinking_cloud(&sobj); break; - } default: impossible("What weird effect is this? (%u)", otyp); } @@ -1727,8 +2291,11 @@ struct obj *sobj; /* scroll, or fake spellbook object for scroll-like spell */ } void -drop_boulder_on_player(confused, helmet_protects, byu, skip_uswallow) -boolean confused, helmet_protects, byu, skip_uswallow; +drop_boulder_on_player( + boolean confused, + boolean helmet_protects, + boolean byu, + boolean skip_uswallow) { int dmg; struct obj *otmp2; @@ -1744,12 +2311,12 @@ boolean confused, helmet_protects, byu, skip_uswallow; return; otmp2->quan = confused ? rn1(5, 2) : 1; otmp2->owt = weight(otmp2); - if (!amorphous(youmonst.data) && !Passes_walls - && !noncorporeal(youmonst.data) && !unsolid(youmonst.data)) { + if (!amorphous(gy.youmonst.data) && !Passes_walls + && !noncorporeal(gy.youmonst.data) && !unsolid(gy.youmonst.data)) { You("are hit by %s!", doname(otmp2)); - dmg = dmgval(otmp2, &youmonst) * otmp2->quan; + dmg = (int) (dmgval(otmp2, &gy.youmonst) * otmp2->quan); if (uarmh && helmet_protects) { - if (is_metallic(uarmh)) { + if (hard_helmet(uarmh)) { pline("Fortunately, you are wearing a hard helmet."); if (dmg > 2) dmg = 2; @@ -1771,12 +2338,10 @@ boolean confused, helmet_protects, byu, skip_uswallow; } boolean -drop_boulder_on_monster(x, y, confused, byu) -int x, y; -boolean confused, byu; +drop_boulder_on_monster(coordxy x, coordxy y, boolean confused, boolean byu) { - register struct obj *otmp2; - register struct monst *mtmp; + struct obj *otmp2; + struct monst *mtmp; /* Make the object(s) */ otmp2 = mksobj(confused ? ROCK : BOULDER, FALSE, FALSE); @@ -1790,20 +2355,20 @@ boolean confused, byu; if (mtmp && !amorphous(mtmp->data) && !passes_walls(mtmp->data) && !noncorporeal(mtmp->data) && !unsolid(mtmp->data)) { struct obj *helmet = which_armor(mtmp, W_ARMH); - int mdmg; + long mdmg; if (cansee(mtmp->mx, mtmp->my)) { pline("%s is hit by %s!", Monnam(mtmp), doname(otmp2)); if (mtmp->minvis && !canspotmon(mtmp)) map_invisible(mtmp->mx, mtmp->my); - } else if (u.uswallow && mtmp == u.ustuck) + } else if (engulfing_u(mtmp)) You_hear("something hit %s %s over your %s!", s_suffix(mon_nam(mtmp)), mbodypart(mtmp, STOMACH), body_part(HEAD)); mdmg = dmgval(otmp2, mtmp) * otmp2->quan; if (helmet) { - if (is_metallic(helmet)) { + if (hard_helmet(helmet)) { if (canspotmon(mtmp)) pline("Fortunately, %s is wearing a hard helmet.", mon_nam(mtmp)); @@ -1829,7 +2394,7 @@ boolean confused, byu; wakeup(mtmp, byu); } wake_nearto(x, y, 4 * 4); - } else if (u.uswallow && mtmp == u.ustuck) { + } else if (engulfing_u(mtmp)) { obfree(otmp2, (struct obj *) 0); /* fall through to player */ drop_boulder_on_player(confused, TRUE, FALSE, TRUE); @@ -1846,9 +2411,7 @@ boolean confused, byu; /* overcharging any wand or zapping/engraving cursed wand */ void -wand_explode(obj, chg) -struct obj *obj; -int chg; /* recharging */ +wand_explode(struct obj *obj, int chg /* recharging */) { const char *expl = !chg ? "suddenly" : "vibrates violently and"; int dmg, n, k; @@ -1899,15 +2462,13 @@ struct litmon { struct monst *mon; struct litmon *nxt; }; -STATIC_VAR struct litmon *gremlins = 0; +static struct litmon *gremlins = 0; /* * Low-level lit-field update routine. */ -STATIC_PTR void -set_lit(x, y, val) -int x, y; -genericptr_t val; +staticfn void +set_lit(coordxy x, coordxy y, genericptr_t val) { struct monst *mtmp; struct litmon *gremlin; @@ -1927,50 +2488,86 @@ genericptr_t val; } void -litroom(on, obj) -register boolean on; -struct obj *obj; +litroom( + boolean on, /* True: make nearby area lit; False: cursed scroll */ + struct obj *obj) /* scroll, spellbook (for spell), or wand of light */ { - char is_lit; /* value is irrelevant; we use its address - as a `not null' flag for set_lit() */ - - /* first produce the text (provided you're not blind) */ + struct obj *otmp, *nextobj; + boolean blessed_effect = (obj && obj->oclass == SCROLL_CLASS + && obj->blessed); + boolean no_op = (u.uswallow || Underwater || Is_waterlevel(&u.uz)); + char is_lit = 0; /* value is irrelevant but assign something anyway; its + * address is used as a 'not null' flag for set_lit() */ + + /* update object lights and produce message (provided you're not blind) */ if (!on) { - register struct obj *otmp; + int still_lit = 0; - if (!Blind) { - if (u.uswallow) { - pline("It seems even darker in here than before."); - } else { - if (uwep && artifact_light(uwep) && uwep->lamplit) - pline("Suddenly, the only light left comes from %s!", - the(xname(uwep))); + /* + * The magic douses lamps,&c too and might curse artifact lights. + * + * FIXME? + * Shouldn't this affect all lit objects in the area of effect + * rather than just those carried by the hero? + */ + for (otmp = gi.invent; otmp; otmp = nextobj) { + nextobj = otmp->nobj; + if (otmp->lamplit) { + if (!artifact_light(otmp)) + (void) snuff_lit(otmp); else - You("are surrounded by darkness!"); + /* wielded Sunsword or worn gold dragon scales/mail; + maybe lower its BUC state if not already cursed */ + impact_arti_light(otmp, TRUE, (boolean) !Blind); + + if (otmp->lamplit) + ++still_lit; } } - - /* the magic douses lamps, et al, too */ - for (otmp = invent; otmp; otmp = otmp->nobj) - if (otmp->lamplit) - (void) snuff_lit(otmp); + /* scroll of light becomes discovered when not blind, so some + message to justify that is needed */ + if (!Blind) { + /* for the still_lit case, we don't know at this point whether + anything currently visibly lit is going to go dark; if this + message came after the darkening, we could count visibly + lit squares before and after to know; we do know that being + swallowed won't be affected--the interior is still lit */ + if (still_lit) + pline_The("ambient light seems dimmer."); + else if (u.uswallow) + pline("It seems even darker in here than before."); + else + You("are surrounded by darkness!"); + } } else { /* on */ + if (blessed_effect) { + /* might bless artifact lights; no effect on ordinary lights */ + for (otmp = gi.invent; otmp; otmp = nextobj) { + nextobj = otmp->nobj; + if (otmp->lamplit && artifact_light(otmp)) + /* wielded Sunsword or worn gold dragon scales/mail; + maybe raise its BUC state if not already blessed */ + impact_arti_light(otmp, FALSE, (boolean) !Blind); + } + } if (u.uswallow) { if (Blind) ; /* no feedback */ - else if (is_animal(u.ustuck->data)) + else if (digests(u.ustuck->data)) pline("%s %s is lit.", s_suffix(Monnam(u.ustuck)), mbodypart(u.ustuck, STOMACH)); else if (is_whirly(u.ustuck->data)) pline("%s shines briefly.", Monnam(u.ustuck)); else pline("%s glistens.", Monnam(u.ustuck)); - } else if (!Blind) - pline("A lit field surrounds you!"); + } else if (!Blind && (!Is_rogue_level(&u.uz) + || levl[u.ux][u.uy].typ != CORR)) { + pline("A lit field %ssurrounds you!", no_op ? "briefly " : ""); + } } /* No-op when swallowed or in water */ - if (u.uswallow || Underwater || Is_waterlevel(&u.uz)) + if (no_op) return; /* * If we are darkening the room and the hero is punished but not @@ -1987,18 +2584,23 @@ struct obj *obj; int rx, ry; if (rnum >= 0) { - for (rx = rooms[rnum].lx - 1; rx <= rooms[rnum].hx + 1; rx++) - for (ry = rooms[rnum].ly - 1; ry <= rooms[rnum].hy + 1; ry++) + for (rx = svr.rooms[rnum].lx - 1; rx <= svr.rooms[rnum].hx + 1; + rx++) + for (ry = svr.rooms[rnum].ly - 1; + ry <= svr.rooms[rnum].hy + 1; ry++) set_lit(rx, ry, (genericptr_t) (on ? &is_lit : (char *) 0)); - rooms[rnum].rlit = on; + svr.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, + } else if (is_art(obj, ART_SUNSWORD)) { + /* Sunsword's #invoke power directed up or down lights hero's spot + (do_clear_area() rejects radius 0 so call set_lit() directly) */ + set_lit(u.ux, u.uy, (genericptr_t) &is_lit); + } else { + do_clear_area(u.ux, u.uy, blessed_effect ? 9 : 5, set_lit, (genericptr_t) (on ? &is_lit : (char *) 0)); + } /* * If we are not blind, then force a redraw on all positions in sight @@ -2014,7 +2616,7 @@ struct obj *obj; move_bc(0, 0, uball->ox, uball->oy, uchain->ox, uchain->oy); } - vision_full_recalc = 1; /* delayed vision recalculation */ + gv.vision_full_recalc = 1; /* delayed vision recalculation */ if (gremlins) { struct litmon *gremlin; @@ -2029,51 +2631,79 @@ struct obj *obj; free((genericptr_t) gremlin); } while (gremlins); } + return; } -STATIC_OVL void -do_class_genocide() +staticfn void +do_class_genocide(void) { int i, j, immunecnt, gonecnt, goodcnt, class, feel_dead = 0; - char buf[BUFSZ] = DUMMY; + int ll_done = 0; + char buf[BUFSZ], promptbuf[QBUFSZ]; boolean gameover = FALSE; /* true iff killed self */ - for (j = 0;; j++) { + buf[0] = '\0'; /* for EDIT_GETLIN */ + for (j = 0; ; j++) { if (j >= 5) { pline1(thats_enough_tries); return; } - do { - getlin("What class of monsters do you wish to genocide?", buf); - (void) mungspaces(buf); - } while (!*buf); + Strcpy(promptbuf, "What class of monsters do you want to genocide?"); + if (j > 0) + Snprintf(eos(promptbuf), sizeof promptbuf - strlen(promptbuf), + " [enter %s]", + iflags.cmdassist + ? "the symbol or name representing a class, or '?'" + : "'?' to see previous genocides"); + getlin(promptbuf, buf); + (void) mungspaces(buf); + /* avoid 'that does not represent any monster' for empty input */ + if (!*buf) { + pline("%s.", (j + 1 < 5) + ? "Type letter (or punctuation)" + " or name used for a class of monsters or 'none'" + /* next iteration gives "that's enough tries" + so don't suggest typing anything this time */ + : "No class of monsters specified"); + continue; /* try again */ + } /* choosing "none" preserves genocideless conduct */ if (*buf == '\033' || !strcmpi(buf, "none") - || !strcmpi(buf, "nothing")) + || !strcmpi(buf, "'none'") || !strcmpi(buf, "nothing")) { + livelog_printf(LL_GENOCIDE, + "declined to perform class genocide"); return; + } + /* "?" runs #genocided to show existing genocides, then re-prompts; + accept "'?'" too because the prompt's hint shows it that way */ + if (!strcmp(buf, "?") || !strcmp(buf, "'?'")) { + list_genocided('g', FALSE); + --j; /* don't count this iteration as one of the tries */ + continue; + } class = name_to_monclass(buf, (int *) 0); - if (class == 0 && (i = name_to_mon(buf)) != NON_PM) + if (class == 0 && (i = name_to_mon(buf, (int *) 0)) != NON_PM) class = mons[i].mlet; immunecnt = gonecnt = goodcnt = 0; for (i = LOW_PM; i < NUMMONS; i++) { if (mons[i].mlet == class) { if (!(mons[i].geno & G_GENO)) immunecnt++; - else if (mvitals[i].mvflags & G_GENOD) + else if (svm.mvitals[i].mvflags & G_GENOD) gonecnt++; else goodcnt++; } } - if (!goodcnt && class != mons[urole.malenum].mlet - && class != mons[urace.malenum].mlet) { + if (!goodcnt && class != mons[gu.urole.mnum].mlet + && class != mons[gu.urace.mnum].mlet) { if (gonecnt) pline("All such monsters are already nonexistent."); else if (immunecnt || class == S_invisible) You("aren't permitted to genocide such monsters."); else if (wizard && buf[0] == '*') { - register struct monst *mtmp, *mtmp2; + struct monst *mtmp, *mtmp2; gonecnt = 0; for (mtmp = fmon; mtmp; mtmp = mtmp2) { @@ -2095,26 +2725,39 @@ do_class_genocide() if (mons[i].mlet == class) { char nam[BUFSZ]; - Strcpy(nam, makeplural(mons[i].mname)); + Strcpy(nam, makeplural(mons[i].pmnames[NEUTRAL])); /* Although "genus" is Latin for race, the hero benefits * from both race and role; thus genocide affects either. */ if (Your_Own_Role(i) || Your_Own_Race(i) || ((mons[i].geno & G_GENO) - && !(mvitals[i].mvflags & G_GENOD))) { + && !(svm.mvitals[i].mvflags & G_GENOD))) { /* This check must be first since player monsters might * have G_GENOD or !G_GENO. */ - mvitals[i].mvflags |= (G_GENOD | G_NOCORPSE); - reset_rndmonst(i); + if (!ll_done++) { + if (!num_genocides()) + livelog_printf(LL_CONDUCT | LL_GENOCIDE, + "performed %s first genocide (class %c)", + uhis(), def_monsyms[class].sym); + else + livelog_printf(LL_GENOCIDE, "genocided class %c", + def_monsyms[class].sym); + } + + svm.mvitals[i].mvflags |= (G_GENOD | G_NOCORPSE); kill_genocided_monsters(); update_inventory(); /* eggs & tins */ pline("Wiped out all %s.", nam); + if (Upolyd && vampshifted(&gy.youmonst) + /* current shifted form or base vampire form */ + && (i == u.umonnum || i == gy.youmonst.cham)) + polyself(POLY_REVERT); /* vampshifter to vampire */ if (Upolyd && i == u.umonnum) { u.mh = -1; if (Unchanging) { if (!feel_dead++) - You("die."); + urgent_pline("You die."); /* finish genociding this class of monsters before ultimately dying */ gameover = TRUE; @@ -2124,20 +2767,20 @@ do_class_genocide() /* Self-genocide if it matches either your race or role. Assumption: male and female forms share same monster class. */ - if (i == urole.malenum || i == urace.malenum) { + if (i == gu.urole.mnum || i == gu.urace.mnum) { u.uhp = -1; if (Upolyd) { if (!feel_dead++) You_feel("%s inside.", udeadinside()); } else { if (!feel_dead++) - You("die."); + urgent_pline("You die."); gameover = TRUE; } } - } else if (mvitals[i].mvflags & G_GENOD) { + } else if (svm.mvitals[i].mvflags & G_GENOD) { if (!gameover) - pline("All %s are already nonexistent.", nam); + pline("%s are already nonexistent.", upstart(nam)); } else if (!gameover) { /* suppress feedback about quest beings except for those applicable to our own role */ @@ -2156,19 +2799,19 @@ do_class_genocide() named = type_is_pname(&mons[i]) ? TRUE : FALSE; uniq = (mons[i].geno & G_UNIQ) ? TRUE : FALSE; /* one special case */ - if (i == PM_HIGH_PRIEST) + if (i == PM_HIGH_CLERIC) uniq = FALSE; You("aren't permitted to genocide %s%s.", (uniq && !named) ? "the " : "", - (uniq || named) ? mons[i].mname : nam); + (uniq || named) ? mons[i].pmnames[NEUTRAL] : nam); } } } } if (gameover || u.uhp == -1) { - killer.format = KILLED_BY_AN; - Strcpy(killer.name, "scroll of genocide"); + svk.killer.format = KILLED_BY_AN; + Strcpy(svk.killer.name, "scroll of genocide"); if (gameover) done(GENOCIDED); } @@ -2180,26 +2823,26 @@ do_class_genocide() #define PLAYER 2 #define ONTHRONE 4 void -do_genocide(how) -int how; -/* 0 = no genocide; create monsters (cursed scroll) */ -/* 1 = normal genocide */ -/* 3 = forced genocide of player */ -/* 5 (4 | 1) = normal genocide from throne */ +do_genocide( + int how) /* 0 = no genocide; create monsters (cursed scroll) + * 1 = normal genocide + * 3 = forced genocide of player + * 5 (4 | 1) = normal genocide from throne */ { - char buf[BUFSZ] = DUMMY; - register int i, killplayer = 0; - register int mndx; - register struct permonst *ptr; + char buf[BUFSZ], realbuf[BUFSZ], promptbuf[QBUFSZ]; + int i, killplayer = 0; + int mndx; + struct permonst *ptr; const char *which; if (how & PLAYER) { mndx = u.umonster; /* non-polymorphed mon num */ ptr = &mons[mndx]; - Strcpy(buf, ptr->mname); + Strcpy(buf, pmname(ptr, Ugender)); killplayer++; } else { - for (i = 0;; i++) { + buf[0] = '\0'; /* init for EDIT_GETLIN */ + for (i = 0; ; i++) { if (i >= 5) { /* cursed effect => no free pass (unless rndmonst() fails) */ if (!(how & REALLY) && (ptr = rndmonst()) != 0) @@ -2208,26 +2851,53 @@ int how; pline1(thats_enough_tries); return; } - getlin("What monster do you want to genocide? [type the name]", - buf); + Strcpy(promptbuf, + "What type of monster do you want to genocide?"); + if (i > 0) + Snprintf(eos(promptbuf), sizeof promptbuf - strlen(promptbuf), + " [enter %s]", + iflags.cmdassist + ? "the name of a type of monster, or '?'" + : "'?' to see previous genocides"); + getlin(promptbuf, buf); (void) mungspaces(buf); + /* avoid 'such creatures do not exist' for empty input */ + if (!*buf) { + pline("%s.", (i + 1 < 5) + ? "Type the name of a type of monster or 'none'" + /* next iteration gives "that's enough tries" + so don't suggest typing anything this time */ + : "No type of monster specified"); + continue; /* try again */ + } /* choosing "none" preserves genocideless conduct */ if (*buf == '\033' || !strcmpi(buf, "none") - || !strcmpi(buf, "nothing")) { + || !strcmpi(buf, "'none'") || !strcmpi(buf, "nothing")) { /* ... but no free pass if cursed */ if (!(how & REALLY) && (ptr = rndmonst()) != 0) break; /* remaining checks don't apply */ + livelog_printf(LL_GENOCIDE, "declined to perform genocide"); return; } + /* "?" or "'?'" runs #genocided to show existing genocides */ + if (!strcmp(buf, "?") || !strcmp(buf, "'?'")) { + list_genocided('g', FALSE); + --i; /* don't count this iteration as one of the tries */ + continue; + } - mndx = name_to_mon(buf); - if (mndx == NON_PM || (mvitals[mndx].mvflags & G_GENOD)) { + mndx = name_to_mon(buf, (int *) 0); + if (mndx == NON_PM || (svm.mvitals[mndx].mvflags & G_GENOD)) { pline("Such creatures %s exist in this world.", (mndx == NON_PM) ? "do not" : "no longer"); continue; } ptr = &mons[mndx]; + /* first revert if current shifted form or base vampire form */ + if (Upolyd && vampshifted(&gy.youmonst) + && (mndx == u.umonnum || mndx == gy.youmonst.cham)) + polyself(POLY_REVERT); /* vampshifter (bat, &c) to vampire */ /* Although "genus" is Latin for race, the hero benefits * from both race and role; thus genocide affects either. */ @@ -2247,13 +2917,16 @@ int how; * aren't supposed to be hampered by deafness.... */ if (flags.verbose) - pline("A thunderous voice booms through the caverns:"); + pline("A thunderous voice booms" + " through the caverns:"); + SetVoice((struct monst *) 0, 0, 80, voice_deity); + /* FIXME? shouldn't this override deafness? */ verbalize("No, mortal! That will not be done."); } continue; } /* KMH -- Unchanging prevents rehumanization */ - if (Unchanging && ptr == youmonst.data) + if (Unchanging && ptr == gy.youmonst.data) killplayer++; break; } @@ -2261,73 +2934,74 @@ int how; } which = "all "; + Strcpy(realbuf, ptr->pmnames[NEUTRAL]); /* standard singular */ if (Hallucination) { - if (Upolyd) - Strcpy(buf, youmonst.data->mname); - else { - Strcpy(buf, (flags.female && urole.name.f) ? urole.name.f - : urole.name.m); + /* hallucinate hero's type */ + if (Upolyd) { + Strcpy(buf, pmname(gy.youmonst.data, + flags.female ? FEMALE : MALE)); + } else { + Strcpy(buf, (flags.female && gu.urole.name.f) ? gu.urole.name.f + : gu.urole.name.m); buf[0] = lowc(buf[0]); } } else { - Strcpy(buf, ptr->mname); /* make sure we have standard singular */ - if ((ptr->geno & G_UNIQ) && ptr != &mons[PM_HIGH_PRIEST]) + /* use actual type */ + Strcpy(buf, realbuf); + if ((ptr->geno & G_UNIQ) && ptr != &mons[PM_HIGH_CLERIC]) which = !type_is_pname(ptr) ? "the " : ""; } + if (how & REALLY) { + if (!num_genocides()) + livelog_printf(LL_CONDUCT | LL_GENOCIDE, + "performed %s first genocide (%s)", + uhis(), makeplural(realbuf)); + else + livelog_printf(LL_GENOCIDE, "genocided %s", makeplural(realbuf)); + /* setting no-corpse affects wishing and random tin generation */ - mvitals[mndx].mvflags |= (G_GENOD | G_NOCORPSE); + svm.mvitals[mndx].mvflags |= (G_GENOD | G_NOCORPSE); pline("Wiped out %s%s.", which, (*which != 'a') ? buf : makeplural(buf)); if (killplayer) { - /* might need to wipe out dual role */ - if (urole.femalenum != NON_PM && mndx == urole.malenum) - mvitals[urole.femalenum].mvflags |= (G_GENOD | G_NOCORPSE); - if (urole.femalenum != NON_PM && mndx == urole.femalenum) - mvitals[urole.malenum].mvflags |= (G_GENOD | G_NOCORPSE); - if (urace.femalenum != NON_PM && mndx == urace.malenum) - mvitals[urace.femalenum].mvflags |= (G_GENOD | G_NOCORPSE); - if (urace.femalenum != NON_PM && mndx == urace.femalenum) - mvitals[urace.malenum].mvflags |= (G_GENOD | G_NOCORPSE); - u.uhp = -1; if (how & PLAYER) { - killer.format = KILLED_BY; - Strcpy(killer.name, "genocidal confusion"); + svk.killer.format = KILLED_BY; + Strcpy(svk.killer.name, "genocidal confusion"); } else if (how & ONTHRONE) { /* player selected while on a throne */ - killer.format = KILLED_BY_AN; - Strcpy(killer.name, "imperious order"); + svk.killer.format = KILLED_BY_AN; + Strcpy(svk.killer.name, "imperious order"); } else { /* selected player deliberately, not confused */ - killer.format = KILLED_BY_AN; - Strcpy(killer.name, "scroll of genocide"); + svk.killer.format = KILLED_BY_AN; + Strcpy(svk.killer.name, "scroll of genocide"); } /* Polymorphed characters will die as soon as they're rehumanized. - */ - /* KMH -- Unchanging prevents rehumanization */ - if (Upolyd && ptr != youmonst.data) { - delayed_killer(POLYMORPH, killer.format, killer.name); + KMH -- Unchanging prevents rehumanization. */ + if (Upolyd && ptr != gy.youmonst.data) { + delayed_killer(POLYMORPH, svk.killer.format, svk.killer.name); You_feel("%s inside.", udeadinside()); - } else + } else { done(GENOCIDED); - } else if (ptr == youmonst.data) { + } + } else if (ptr == gy.youmonst.data) { rehumanize(); } - reset_rndmonst(mndx); kill_genocided_monsters(); update_inventory(); /* in case identified eggs were affected */ } else { int cnt = 0, census = monster_census(FALSE); if (!(mons[mndx].geno & G_UNIQ) - && !(mvitals[mndx].mvflags & (G_GENOD | G_EXTINCT))) + && !(svm.mvitals[mndx].mvflags & (G_GENOD | G_EXTINCT))) for (i = rn1(3, 4); i > 0; i--) { - if (!makemon(ptr, u.ux, u.uy, NO_MINVENT)) + if (!makemon(ptr, u.ux, u.uy, NO_MINVENT | MM_NOMSG)) break; /* couldn't make one */ ++cnt; - if (mvitals[mndx].mvflags & G_EXTINCT) + if (svm.mvitals[mndx].mvflags & G_EXTINCT) break; /* just made last one */ } if (cnt) { @@ -2342,22 +3016,25 @@ int how; } void -punish(sobj) -struct obj *sobj; +punish(struct obj *sobj) { + /* angrygods() calls this with NULL sobj arg */ struct obj *reuse_ball = (sobj && sobj->otyp == HEAVY_IRON_BALL) ? sobj : (struct obj *) 0; + /* analyzer doesn't know that the one caller that passes a NULL + * sobj (angrygods) checks !Punished first, so add a guard */ + int cursed_levy = (sobj && sobj->cursed) ? 1 : 0; /* KMH -- Punishment is still okay when you are riding */ if (!reuse_ball) You("are being punished for your misbehavior!"); if (Punished) { Your("iron ball gets heavier."); - uball->owt += IRON_BALL_W_INCR * (1 + sobj->cursed); + uball->owt += WT_IRON_BALL_INCR * (1 + cursed_levy); return; } - if (amorphous(youmonst.data) || is_whirly(youmonst.data) - || unsolid(youmonst.data)) { + if (amorphous(gy.youmonst.data) || is_whirly(gy.youmonst.data) + || unsolid(gy.youmonst.data)) { if (!reuse_ball) { pline("A ball and chain appears, then falls away."); dropy(mkobj(BALL_CLASS, TRUE)); @@ -2371,7 +3048,6 @@ struct obj *sobj; setworn(mkobj(BALL_CLASS, TRUE), W_BALL); else setworn(reuse_ball, W_BALL); - uball->spe = 1; /* special ball (see save) */ /* * Place ball & chain if not swallowed. If swallowed, the ball & chain @@ -2387,33 +3063,60 @@ struct obj *sobj; /* remove the ball and chain */ void -unpunish() +unpunish(void) { struct obj *savechain = uchain; /* chain goes away */ - obj_extract_self(uchain); - newsym(uchain->ox, uchain->oy); setworn((struct obj *) 0, W_CHAIN); /* sets 'uchain' to Null */ - dealloc_obj(savechain); - /* ball persists */ - uball->spe = 0; + /* for floor, unhides monster hidden under chain, calls newsym() */ + delobj(savechain); + + /* the chain is gone but the no longer attached ball persists */ setworn((struct obj *) 0, W_BALL); /* sets 'uball' to Null */ } +/* prompt the player to create a stinking cloud and then create it if they + give a location */ +staticfn void +do_stinking_cloud(struct obj *sobj, boolean mention_stinking) +{ + coord cc; + + pline("Where do you want to center the %scloud?", + mention_stinking ? "stinking " : ""); + cc.x = u.ux; + cc.y = u.uy; + getpos_sethilite(display_stinking_cloud_positions, can_center_cloud); + if (getpos(&cc, TRUE, "the desired position") < 0) { + pline1(Never_mind); + return; + } else if (!can_center_cloud(cc.x, cc.y)) { + if (Hallucination) + pline("Ugh... someone cut the cheese."); + else + pline("%s a whiff of rotten eggs.", + sobj->oclass == SCROLL_CLASS ? "The scroll crumbles with" + : "You smell"); + return; + } + (void) create_gas_cloud(cc.x, cc.y, 15 + 10 * bcsign(sobj), + 8 + 4 * bcsign(sobj)); +} + /* some creatures have special data structures that only make sense in their * normal locations -- if the player tries to create one elsewhere, or to * revive one, the disoriented creature becomes a zombie */ boolean -cant_revive(mtype, revival, from_obj) -int *mtype; -boolean revival; -struct obj *from_obj; +cant_revive( + int *mtype, + boolean revival, + struct obj *from_obj) { /* SHOPKEEPERS can be revived now */ if (*mtype == PM_GUARD || (*mtype == PM_SHOPKEEPER && !revival) - || *mtype == PM_HIGH_PRIEST || *mtype == PM_ALIGNED_PRIEST + || *mtype == PM_HIGH_CLERIC || *mtype == PM_ALIGNED_CLERIC || *mtype == PM_ANGEL) { *mtype = PM_HUMAN_ZOMBIE; return TRUE; @@ -2430,34 +3133,44 @@ struct obj *from_obj; return FALSE; } -struct _create_particular_data { - int which; - int fem; - char monclass; - boolean randmonst; - boolean maketame, makepeaceful, makehostile; - boolean sleeping, saddled, invisible, hidden; -}; - -boolean -create_particular_parse(str, d) -char *str; -struct _create_particular_data *d; +staticfn boolean +create_particular_parse( + char *str, + struct _create_particular_data *d) { + int gender_name_var = NEUTRAL; char *bufp = str; char *tmpp; + d->quan = 1 + ((gm.multi > 0) ? (int) gm.multi : 0); d->monclass = MAXMCLASSES; - d->which = urole.malenum; /* an arbitrary index into mons[] */ - d->fem = -1; /* gender not specified */ + d->which = gu.urole.mnum; /* an arbitrary index into mons[] */ + d->fem = -1; /* gender not specified */ + d->genderconf = -1; /* no confusion on which gender to assign */ d->randmonst = FALSE; d->maketame = d->makepeaceful = d->makehostile = FALSE; d->sleeping = d->saddled = d->invisible = d->hidden = FALSE; + /* quantity */ + if (digit(*bufp)) { + d->quan = atoi(bufp); + while (digit(*bufp)) + bufp++; + while (*bufp == ' ') + bufp++; + } +#define QUAN_LIMIT (ROWNO * (COLNO - 1)) + /* maximum possible quantity is one per cell: (0..ROWNO-1) x (1..COLNO-1) + [21*79==1659 for default map size; could subtract 1 for hero's spot] */ + if (d->quan < 1 || d->quan > QUAN_LIMIT) + d->quan = QUAN_LIMIT - monster_census(FALSE); +#undef QUAN_LIMIT + /* gear -- extremely limited number of possibilities supported */ if ((tmpp = strstri(bufp, "saddled ")) != 0) { d->saddled = TRUE; (void) memset(tmpp, ' ', sizeof "saddled " - 1); } + /* state -- limited number of possibilities supported */ if ((tmpp = strstri(bufp, "sleeping ")) != 0) { d->sleeping = TRUE; (void) memset(tmpp, ' ', sizeof "sleeping " - 1); @@ -2496,12 +3209,28 @@ struct _create_particular_data *d; d->randmonst = TRUE; return TRUE; } - d->which = name_to_mon(bufp); - if (d->which >= LOW_PM) + d->which = name_to_mon(bufp, &gender_name_var); + /* + * With the introduction of male and female monster names + * in 5.0, preserve that detail. + * + * If d->fem is already set to MALE or FEMALE at this juncture, it means + * one of those terms was explicitly specified. + */ + if (d->fem == MALE || d->fem == FEMALE) { /* explicitly expressed */ + if ((gender_name_var != NEUTRAL) && (d->fem != gender_name_var)) { + /* apparent selection incompatibility */ + d->genderconf = gender_name_var; /* resolve later */ + } + /* otherwise keep the value of d->fem, as it's okay */ + } else { /* no explicit gender term was specified */ + d->fem = gender_name_var; + } + if (ismnum(d->which)) return TRUE; /* got one */ d->monclass = name_to_monclass(bufp, &d->which); - if (d->which >= LOW_PM) { + if (ismnum(d->which)) { d->monclass = MAXMCLASSES; /* matters below */ return TRUE; } else if (d->monclass == S_invisible) { /* not an actual monster class */ @@ -2513,15 +3242,15 @@ struct _create_particular_data *d; d->monclass = MAXMCLASSES; return TRUE; } else if (d->monclass > 0) { - d->which = urole.malenum; /* reset from NON_PM */ + d->which = gu.urole.mnum; /* reset from NON_PM */ return TRUE; } return FALSE; } -boolean -create_particular_creation(d) -struct _create_particular_data *d; +staticfn boolean +create_particular_creation( + struct _create_particular_data *d) { struct permonst *whichpm = NULL; int i, mx, my, firstchoice = NON_PM; @@ -2536,18 +3265,54 @@ struct _create_particular_data *d; char buf[BUFSZ]; Sprintf(buf, "Creating %s instead; force %s?", - mons[d->which].mname, mons[firstchoice].mname); - if (yn(buf) == 'y') + mons[d->which].pmnames[NEUTRAL], + mons[firstchoice].pmnames[NEUTRAL]); + if (y_n(buf) == 'y') d->which = firstchoice; } whichpm = &mons[d->which]; } - for (i = 0; i <= multi; i++) { + for (i = 0; i < d->quan; i++) { + mmflags_nht mmflags = NO_MM_FLAGS; + if (d->monclass != MAXMCLASSES) whichpm = mkclass(d->monclass, 0); else if (d->randmonst) whichpm = rndmonst(); - mtmp = makemon(whichpm, u.ux, u.uy, NO_MM_FLAGS); + if (d->genderconf == -1) { + /* no conflict exists between explicit gender term and + the specified monster name */ + if (d->fem != -1 && (!whichpm || (!is_male(whichpm) + && !is_female(whichpm)))) + mmflags |= (d->fem == FEMALE) ? MM_FEMALE + : (d->fem == MALE) ? MM_MALE : 0; + /* no surprise; " appears." rather than " appears!" */ + mmflags |= MM_NOEXCLAM; + } else { + /* conundrum alert: an explicit gender term conflicts with an + explicit gender-tied naming term (i.e. male cavewoman) */ + + /* option not gone with: name overrides the explicit gender as + commented out here */ + /* d->fem = d->genderconf; */ + + /* option chosen: let the explicit gender term (male or female) + override the gender-tied naming term, so leave d->fem as-is */ + + mmflags |= (d->fem == FEMALE) ? MM_FEMALE + : (d->fem == MALE) ? MM_MALE : 0; + + /* another option would be to consider it a faulty specification + and reject the request completely and produce a random monster + with a gender matching that specified instead (i.e. there is + no such thing as a male cavewoman) */ + /* whichpm = rndmonst(); */ + /* mmflags |= (d->fem == FEMALE) ? MM_FEMALE : MM_MALE; */ + } + if (d->invisible) + mmflags |= MM_MINVIS; + + mtmp = makemon(whichpm, u.ux, u.uy, mmflags); if (!mtmp) { /* quit trying if creation failed and is going to repeat */ if (d->monclass == MAXMCLASSES && !d->randmonst) @@ -2556,55 +3321,37 @@ struct _create_particular_data *d; continue; } mx = mtmp->mx, my = mtmp->my; - /* 'is_FOO()' ought to be called 'always_FOO()' */ - if (d->fem != -1 && !is_male(mtmp->data) && !is_female(mtmp->data)) - mtmp->female = d->fem; /* ignored for is_neuter() */ if (d->maketame) { - (void) tamedog(mtmp, (struct obj *) 0); + (void) tamedog(mtmp, (struct obj *) 0, FALSE); } else if (d->makepeaceful || d->makehostile) { mtmp->mtame = 0; /* sanity precaution */ mtmp->mpeaceful = d->makepeaceful ? 1 : 0; set_malign(mtmp); } if (d->saddled && can_saddle(mtmp) && !which_armor(mtmp, W_SADDLE)) { - struct obj *otmp = mksobj(SADDLE, TRUE, FALSE); - - put_saddle_on_mon(otmp, mtmp); - } - if (d->invisible) { - mon_set_minvis(mtmp); - if (does_block(mx, my, &levl[mx][my])) - block_point(mx, my); - else - unblock_point(mx, my); + /* NULL obj arg means put_saddle_on_mon() + * will create the saddle itself */ + put_saddle_on_mon((struct obj *) 0, mtmp); } - if (d->hidden + if (d->hidden && ((is_hider(mtmp->data) && mtmp->data->mlet != S_MIMIC) || (hides_under(mtmp->data) && OBJ_AT(mx, my)) || (mtmp->data->mlet == S_EEL && is_pool(mx, my)))) mtmp->mundetected = 1; if (d->sleeping) mtmp->msleeping = 1; - /* iff asking for 'hidden', show locaton of every created monster + /* if asking for 'hidden', show location of every created monster that can't be seen--whether that's due to successfully hiding or vision issues (line-of-sight, invisibility, blindness) */ - if (d->hidden && !canspotmon(mtmp)) { - int count = couldsee(mx, my) ? 8 : 4; - char saveviz = viz_array[my][mx]; + if ((d->hidden || d->invisible) && !canspotmon(mtmp)) + flash_mon(mtmp); - if (!flags.sparkle) - count /= 2; - viz_array[my][mx] |= (IN_SIGHT | COULD_SEE); - flash_glyph_at(mx, my, mon_to_glyph(mtmp, newsym_rn2), count); - viz_array[my][mx] = saveviz; - newsym(mx, my); - } madeany = TRUE; /* in case we got a doppelganger instead of what was asked for, make it start out looking like what was asked for */ if (mtmp->cham != NON_PM && firstchoice != NON_PM && mtmp->cham != firstchoice) - (void) newcham(mtmp, &mons[firstchoice], FALSE, FALSE); + (void) newcham(mtmp, &mons[firstchoice], NO_NC_FLAGS); } return madeany; } @@ -2622,14 +3369,17 @@ struct _create_particular_data *d; * this code was also used for the scroll/spell in explore mode. */ boolean -create_particular() +create_particular(void) { - char buf[BUFSZ] = DUMMY, *bufp; - int tryct = 5; +#define CP_TRYLIM 5 struct _create_particular_data d; + char *bufp, buf[BUFSZ], prompt[QBUFSZ]; + int tryct = CP_TRYLIM, altmsg = 0; + buf[0] = '\0'; /* for EDIT_GETLIN */ + Strcpy(prompt, "Create what kind of monster?"); do { - getlin("Create what kind of monster? [type the name or symbol]", buf); + getlin(prompt, buf); bufp = mungspaces(buf); if (*bufp == '\033') return FALSE; @@ -2638,7 +3388,15 @@ create_particular() break; /* no good; try again... */ - pline("I've never heard of such monsters."); + if (*bufp || altmsg || tryct < 2) { + pline("I've never heard of such monsters."); + } else { + pline("Try again (type * for random, ESC to cancel)."); + ++altmsg; + } + /* when a second try is needed, expand the prompt */ + if (tryct == CP_TRYLIM) + Strcat(prompt, " [type name or symbol]"); } while (--tryct > 0); if (!tryct) diff --git a/src/rect.c b/src/rect.c index e8f2b5a3b..aebd8216b 100644 --- a/src/rect.c +++ b/src/rect.c @@ -1,51 +1,67 @@ -/* NetHack 3.6 rect.c $NHDT-Date: 1432512774 2015/05/25 00:12:54 $ $NHDT-Branch: master $:$NHDT-Revision: 1.11 $ */ +/* NetHack 5.0 rect.c $NHDT-Date: 1596498203 2020/08/03 23:43:23 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.14 $ */ /* Copyright (c) 1990 by Jean-Christophe Collet */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -int FDECL(get_rect_ind, (NhRect *)); +int get_rect_ind(NhRect *); -STATIC_DCL boolean FDECL(intersect, (NhRect *, NhRect *, NhRect *)); +staticfn boolean intersect(NhRect *, NhRect *, NhRect *); /* * In this file, we will handle the various rectangle functions we * need for room generation. */ -#define MAXRECT 50 #define XLIM 4 #define YLIM 3 -static NhRect rect[MAXRECT + 1]; +static NhRect *rect = (NhRect *) 0; +static int n_rects = 0; static int rect_cnt; /* - * Initialisation of internal structures. Should be called for every + * Initialization of internal structures. Should be called for every * new level to be build... */ void -init_rect() +init_rect(void) { + if (!rect) { + n_rects = (COLNO * ROWNO) / 30; + rect = (NhRect *) alloc(sizeof(NhRect) * n_rects); + if (!rect) + panic("Could not alloc rect"); + } + rect_cnt = 1; rect[0].lx = rect[0].ly = 0; rect[0].hx = COLNO - 1; rect[0].hy = ROWNO - 1; } +void +free_rect(void) +{ + if (rect) + free(rect); + rect = 0; + n_rects = rect_cnt = 0; +} + + /* * Search Index of one precise NhRect. * */ int -get_rect_ind(r) -NhRect *r; +get_rect_ind(NhRect *r) { - register NhRect *rectp; - register int lx, ly, hx, hy; - register int i; + NhRect *rectp; + int lx, ly, hx, hy; + int i; lx = r->lx; ly = r->ly; @@ -63,12 +79,11 @@ NhRect *r; */ NhRect * -get_rect(r) -NhRect *r; +get_rect(NhRect *r) { - register NhRect *rectp; - register int lx, ly, hx, hy; - register int i; + NhRect *rectp; + int lx, ly, hx, hy; + int i; lx = r->lx; ly = r->ly; @@ -86,7 +101,7 @@ NhRect *r; */ NhRect * -rnd_rect() +rnd_rect(void) { return rect_cnt > 0 ? &rect[rn2(rect_cnt)] : 0; } @@ -97,9 +112,8 @@ rnd_rect() * otherwise returns FALSE */ -STATIC_OVL boolean -intersect(r1, r2, r3) -NhRect *r1, *r2, *r3; +staticfn boolean +intersect(NhRect *r1, NhRect *r2, NhRect *r3) { if (r2->lx > r1->hx || r2->ly > r1->hy || r2->hx < r1->lx || r2->hy < r1->ly) @@ -115,13 +129,22 @@ NhRect *r1, *r2, *r3; return TRUE; } +/* Put the rectangle containing both r1 and r2 into r3 */ +void +rect_bounds(NhRect r1, NhRect r2, NhRect *r3) +{ + r3->lx = min(r1.lx, r2.lx); + r3->ly = min(r1.ly, r2.ly); + r3->hx = max(r1.hx, r2.hx); + r3->hy = max(r1.hy, r2.hy); +} + /* * Remove a rectangle from the list of free NhRect. */ void -remove_rect(r) -NhRect *r; +remove_rect(NhRect *r) { int ind; @@ -135,12 +158,10 @@ NhRect *r; */ void -add_rect(r) -NhRect *r; +add_rect(NhRect *r) { - if (rect_cnt >= MAXRECT) { - if (wizard) - pline("MAXRECT may be too small."); + if (rect_cnt >= n_rects) { + impossible("n_rects may be too small."); return; } /* Check that this NhRect is not included in another one */ @@ -158,8 +179,7 @@ NhRect *r; */ void -split_rects(r1, r2) -NhRect *r1, *r2; +split_rects(NhRect *r1, NhRect *r2) { NhRect r, old_r; int i; diff --git a/src/region.c b/src/region.c index f0b775d67..9fa2530e4 100644 --- a/src/region.c +++ b/src/region.c @@ -1,9 +1,8 @@ -/* NetHack 3.6 region.c $NHDT-Date: 1573957877 2019/11/17 02:31:17 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.45 $ */ +/* NetHack 5.0 region.c $NHDT-Date: 1727251269 2024/09/25 08:01:09 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.104 $ */ /* Copyright (c) 1996 by Jean-Christophe Collet */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -#include "lev.h" /* * This should really go into the level structure, but @@ -11,41 +10,39 @@ * structure eventually. */ -static NhRegion **regions; -static int n_regions = 0; -static int max_regions = 0; - #define NO_CALLBACK (-1) -boolean FDECL(inside_gas_cloud, (genericptr, genericptr)); -boolean FDECL(expire_gas_cloud, (genericptr, genericptr)); -boolean FDECL(inside_rect, (NhRect *, int, int)); -boolean FDECL(inside_region, (NhRegion *, int, int)); -NhRegion *FDECL(create_region, (NhRect *, int)); -void FDECL(add_rect_to_reg, (NhRegion *, NhRect *)); -void FDECL(add_mon_to_reg, (NhRegion *, struct monst *)); -void FDECL(remove_mon_from_reg, (NhRegion *, struct monst *)); -boolean FDECL(mon_in_region, (NhRegion *, struct monst *)); +void free_region(NhRegion *); +#ifndef SFCTOOL +boolean inside_gas_cloud(genericptr, genericptr); +boolean expire_gas_cloud(genericptr, genericptr); +boolean inside_rect(NhRect *, int, int); +NhRegion *create_region(NhRect *, int); +void add_rect_to_reg(NhRegion *, NhRect *); +void add_mon_to_reg(NhRegion *, struct monst *); +void remove_mon_from_reg(NhRegion *, struct monst *); +boolean mon_in_region(NhRegion *, struct monst *); #if 0 -NhRegion *FDECL(clone_region, (NhRegion *)); +NhRegion *clone_region(NhRegion *); #endif -void FDECL(free_region, (NhRegion *)); -void FDECL(add_region, (NhRegion *)); -void FDECL(remove_region, (NhRegion *)); +void add_region(NhRegion *); +void remove_region(NhRegion *); #if 0 -void FDECL(replace_mon_regions, (struct monst *,struct monst *)); -void FDECL(remove_mon_from_regions, (struct monst *)); -NhRegion *FDECL(create_msg_region, (XCHAR_P,XCHAR_P,XCHAR_P,XCHAR_P, - const char *,const char *)); -boolean FDECL(enter_force_field, (genericptr,genericptr)); -NhRegion *FDECL(create_force_field, (XCHAR_P,XCHAR_P,int,long)); +void replace_mon_regions(struct monst *,struct monst *); +void remove_mon_from_regions(struct monst *); +NhRegion *create_msg_region(coordxy,coordxy,coordxy,coordxy, const char *, + const char *); +boolean enter_force_field(genericptr,genericptr); +NhRegion *create_force_field(coordxy,coordxy,int,long); #endif -STATIC_DCL void FDECL(reset_region_mids, (NhRegion *)); +staticfn void reset_region_mids(NhRegion *); +staticfn boolean is_hero_inside_gas_cloud(void); +staticfn void make_gas_cloud(NhRegion *, int, boolean) NONNULLARG1; -static callback_proc callbacks[] = { +static const callback_proc callbacks[] = { #define INSIDE_GAS_CLOUD 0 inside_gas_cloud, #define EXPIRE_GAS_CLOUD 1 @@ -54,9 +51,7 @@ static callback_proc callbacks[] = { /* Should be inlined. */ boolean -inside_rect(r, x, y) -NhRect *r; -int x, y; +inside_rect(NhRect *r, int x, int y) { return (boolean) (x >= r->lx && x <= r->hx && y >= r->ly && y <= r->hy); } @@ -65,9 +60,7 @@ int x, y; * Check if a point is inside a region. */ boolean -inside_region(reg, x, y) -NhRegion *reg; -int x, y; +inside_region(NhRegion *reg, int x, int y) { int i; @@ -83,15 +76,13 @@ int x, y; * Create a region. It does not activate it. */ NhRegion * -create_region(rects, nrect) -NhRect *rects; -int nrect; +create_region(NhRect *rects, int nrect) { int i; NhRegion *reg; reg = (NhRegion *) alloc(sizeof(NhRegion)); - (void) memset((genericptr_t)reg, 0, sizeof(NhRegion)); + (void) memset((genericptr_t) reg, 0, sizeof(NhRegion)); /* Determines bounding box */ if (nrect > 0) { reg->bounding_box = rects[0]; @@ -130,8 +121,8 @@ int nrect; clear_heros_fault(reg); reg->n_monst = 0; reg->max_monst = 0; - reg->monsters = (unsigned int *) 0; - reg->arg = zeroany; + reg->monsters = (unsigned *) 0; + reg->arg = cg.zeroany; return reg; } @@ -139,9 +130,7 @@ int nrect; * Add rectangle to region. */ void -add_rect_to_reg(reg, rect) -NhRegion *reg; -NhRect *rect; +add_rect_to_reg(NhRegion *reg, NhRect *rect) { NhRect *tmp_rect; @@ -169,13 +158,19 @@ NhRect *rect; * Add a monster to the region */ void -add_mon_to_reg(reg, mon) -NhRegion *reg; -struct monst *mon; +add_mon_to_reg(NhRegion *reg, struct monst *mon) { int i; unsigned *tmp_m; + /* if this is a long worm, it might already be present in the region; + only include it once no matter how segments the region contains */ + if (mon_in_region(reg, mon)) { + if (mon->data != &mons[PM_LONG_WORM]) + impossible("add_mon_to_reg: %s [#%u] already in region.", + m_monnam(mon), mon->m_id); + return; + } if (reg->max_monst <= reg->n_monst) { tmp_m = (unsigned *) alloc(sizeof (unsigned) * (reg->max_monst + MONST_INC)); @@ -194,11 +189,9 @@ struct monst *mon; * Remove a monster from the region list (it left or died...) */ void -remove_mon_from_reg(reg, mon) -NhRegion *reg; -struct monst *mon; +remove_mon_from_reg(NhRegion *reg, struct monst *mon) { - register int i; + int i; for (i = 0; i < reg->n_monst; i++) if (reg->monsters[i] == mon->m_id) { @@ -214,9 +207,7 @@ struct monst *mon; * than to check for coordinates. */ boolean -mon_in_region(reg, mon) -NhRegion *reg; -struct monst *mon; +mon_in_region(NhRegion *reg, struct monst *mon) { int i; @@ -233,10 +224,11 @@ struct monst *mon; * Clone (make a standalone copy) the region. */ NhRegion * -clone_region(reg) -NhRegion *reg; +clone_region(NhRegion *reg) { NhRegion *ret_reg; + unsigned *m_id_list; + short i; ret_reg = create_region(reg->rects, reg->nrects); ret_reg->ttl = reg->ttl; @@ -250,25 +242,25 @@ NhRegion *reg; ret_reg->can_leave_f = reg->can_leave_f; ret_reg->player_flags = reg->player_flags; /* set/clear_hero_inside,&c*/ ret_reg->n_monst = reg->n_monst; - if (reg->n_monst > 0) { - ret_reg->monsters = (unsigned int *) - alloc((sizeof (unsigned)) * reg->n_monst); - (void) memcpy((genericptr_t) ret_reg->monsters, - (genericptr_t) reg->monsters, - sizeof (unsigned) * reg->n_monst); + ret_reg->max_monst = reg->max_monst; + if (reg->max_monst > 0) { + m_id_list = (unsigned *) alloc(reg->max_monst * sizeof (unsigned)); + for (i = 0; i < reg->max_monst; ++i) + m_id_list[i] = reg->monsters[i]; } else - ret_reg->monsters = (unsigned int *) 0; + m_id_list = (unsigned *) 0; + ret_reg->monsters = m_id_list; return ret_reg; } #endif /*0*/ +#endif /* !SFCTOOL */ /* * Free mem from region. */ void -free_region(reg) -NhRegion *reg; +free_region(NhRegion *reg) { if (reg) { if (reg->rects) @@ -283,40 +275,60 @@ NhRegion *reg; } } +#ifndef SFCTOOL /* * Add a region to the list. * This actually activates the region. */ void -add_region(reg) -NhRegion *reg; +add_region(NhRegion *reg) { NhRegion **tmp_reg; int i, j; - if (max_regions <= n_regions) { - tmp_reg = regions; - regions = - (NhRegion **) alloc((max_regions + 10) * sizeof (NhRegion *)); - if (max_regions > 0) { - (void) memcpy((genericptr_t) regions, (genericptr_t) tmp_reg, - max_regions * sizeof (NhRegion *)); + if (gm.max_regions <= svn.n_regions) { + tmp_reg = gr.regions; + gr.regions = + (NhRegion **) alloc((gm.max_regions + 10) * sizeof (NhRegion *)); + if (gm.max_regions > 0) { + (void) memcpy((genericptr_t) gr.regions, (genericptr_t) tmp_reg, + gm.max_regions * sizeof (NhRegion *)); free((genericptr_t) tmp_reg); } - max_regions += 10; + gm.max_regions += 10; } - regions[n_regions] = reg; - n_regions++; + gr.regions[svn.n_regions] = reg; + svn.n_regions++; /* Check for monsters inside the region */ for (i = reg->bounding_box.lx; i <= reg->bounding_box.hx; i++) for (j = reg->bounding_box.ly; j <= reg->bounding_box.hy; j++) { + struct monst *mtmp; + boolean is_inside = FALSE; + /* Some regions can cross the level boundaries */ if (!isok(i, j)) continue; - if (MON_AT(i, j) && inside_region(reg, i, j)) - add_mon_to_reg(reg, level.monsters[i][j]); - if (reg->visible && cansee(i, j)) - newsym(i, j); + if (inside_region(reg, i, j)) { + is_inside = TRUE; + /* if there's a monster here, add it to the region */ + if ((mtmp = m_at(i, j)) != 0 +#if 0 + /* leave this bit (to exclude long worm tails) out; + assume that worms use "cutaneous respiration" (they + breath through their skin rather than nose/gills/&c) + so their tails are susceptible to poison gas */ + && mtmp->mx == i && mtmp->my == j +#endif + ) { + add_mon_to_reg(reg, mtmp); + } + } + if (reg->visible) { + if (is_inside) + block_point(i, j); + if (cansee(i, j)) + newsym(i, j); + } } /* Check for player now... */ if (inside_region(reg, u.ux, u.uy)) @@ -329,148 +341,185 @@ NhRegion *reg; * Remove a region from the list & free it. */ void -remove_region(reg) -NhRegion *reg; +remove_region(NhRegion *reg) { - register int i, x, y; + int i, x, y; - for (i = 0; i < n_regions; i++) - if (regions[i] == reg) + for (i = 0; i < svn.n_regions; i++) + if (gr.regions[i] == reg) break; - if (i == n_regions) + if (i == svn.n_regions) return; /* remove region before potential newsym() calls, but don't free it yet */ - if (--n_regions != i) - regions[i] = regions[n_regions]; - regions[n_regions] = (NhRegion *) 0; + if (--svn.n_regions != i) + gr.regions[i] = gr.regions[svn.n_regions]; + gr.regions[svn.n_regions] = (NhRegion *) 0; /* Update screen if necessary */ reg->ttl = -2L; /* for visible_region_at */ - if (reg->visible) - for (x = reg->bounding_box.lx; x <= reg->bounding_box.hx; x++) - for (y = reg->bounding_box.ly; y <= reg->bounding_box.hy; y++) - if (isok(x, y) && inside_region(reg, x, y) && cansee(x, y)) - newsym(x, y); - + if (reg->visible) { + int pass; + boolean tmp_uinwater = u.uinwater; + + /* need to process the region's spots twice, first unblocking all + locations which no longer block line-of-sight, then redrawing + spots within revised line-of-sight; skip second pass if blind */ + for (pass = 1; pass <= (Blind ? 1 : 2); ++pass) { + u.uinwater = (pass == 1) ? 0 : tmp_uinwater; + + for (x = reg->bounding_box.lx; x <= reg->bounding_box.hx; x++) + for (y = reg->bounding_box.ly; y <= reg->bounding_box.hy; y++) + if (isok(x, y) && inside_region(reg, x, y)) { + if (pass == 1) { + if (!does_block(x, y, &levl[x][y])) + unblock_point(x, y); + } else { /* pass==2 */ + if (cansee(x, y)) + newsym(x, y); + } + } + } + u.uinwater = tmp_uinwater; + } free_region(reg); } +#endif /* !SFCTOOL */ /* - * Remove all regions and clear all related data (This must be down - * when changing level, for instance). + * Remove all regions and clear all related data. This must be done + * when changing level, for instance. */ void -clear_regions() +clear_regions(void) { - register int i; - - for (i = 0; i < n_regions; i++) - free_region(regions[i]); - n_regions = 0; - if (max_regions > 0) - free((genericptr_t) regions); - max_regions = 0; - regions = (NhRegion **) 0; + int i; + + for (i = 0; i < svn.n_regions; i++) + free_region(gr.regions[i]); + svn.n_regions = 0; + if (gm.max_regions > 0) + free((genericptr_t) gr.regions); + gm.max_regions = 0; + gr.regions = (NhRegion **) 0; } +#ifndef SFCTOOL /* * This function is called every turn. * It makes the regions age, if necessary and calls the appropriate * callbacks when needed. */ void -run_regions() +run_regions(void) { - register int i, j, k; + int i, j, k; int f_indx; + /* reset some messaging variables */ + gg.gas_cloud_diss_within = FALSE; + gg.gas_cloud_diss_seen = 0; + /* End of life ? */ /* Do it backward because the array will be modified */ - for (i = n_regions - 1; i >= 0; i--) { - if (regions[i]->ttl == 0L) { - if ((f_indx = regions[i]->expire_f) == NO_CALLBACK - || (*callbacks[f_indx])(regions[i], (genericptr_t) 0)) - remove_region(regions[i]); + for (i = svn.n_regions - 1; i >= 0; i--) { + if (gr.regions[i]->ttl == 0L) { + if ((f_indx = gr.regions[i]->expire_f) == NO_CALLBACK + || (*callbacks[f_indx])(gr.regions[i], (genericptr_t) 0)) + remove_region(gr.regions[i]); } } /* Process remaining regions */ - for (i = 0; i < n_regions; i++) { + for (i = 0; i < svn.n_regions; i++) { /* Make the region age */ - if (regions[i]->ttl > 0L) - regions[i]->ttl--; + if (gr.regions[i]->ttl > 0L) + gr.regions[i]->ttl--; /* Check if player is inside region */ - f_indx = regions[i]->inside_f; - if (f_indx != NO_CALLBACK && hero_inside(regions[i])) - (void) (*callbacks[f_indx])(regions[i], (genericptr_t) 0); + f_indx = gr.regions[i]->inside_f; + if (f_indx != NO_CALLBACK && hero_inside(gr.regions[i])) + (void) (*callbacks[f_indx])(gr.regions[i], (genericptr_t) 0); /* Check if any monster is inside region */ if (f_indx != NO_CALLBACK) { - for (j = 0; j < regions[i]->n_monst; j++) { + for (j = 0; j < gr.regions[i]->n_monst; j++) { struct monst *mtmp = - find_mid(regions[i]->monsters[j], FM_FMON); + find_mid(gr.regions[i]->monsters[j], FM_FMON); if (!mtmp || DEADMONSTER(mtmp) - || (*callbacks[f_indx])(regions[i], mtmp)) { + || (*callbacks[f_indx])(gr.regions[i], mtmp)) { /* The monster died, remove it from list */ - k = (regions[i]->n_monst -= 1); - regions[i]->monsters[j] = regions[i]->monsters[k]; - regions[i]->monsters[k] = 0; + k = (gr.regions[i]->n_monst -= 1); + gr.regions[i]->monsters[j] = gr.regions[i]->monsters[k]; + gr.regions[i]->monsters[k] = 0; --j; /* current slot has been reused; recheck it next */ } } } } + + if (gg.gas_cloud_diss_within) { + pline_The("gas cloud around you dissipates."); + /* normally won't see additional dissipation when within */ + /* FIXME? this assumes that additional dissipation is close by */ + if (u.xray_range <= 1) + gg.gas_cloud_diss_seen = 0; + gg.gas_cloud_diss_within = FALSE; + } + if (gg.gas_cloud_diss_seen) { + You_see("%s gas cloud%s dissipate.", + (gg.gas_cloud_diss_seen == 1) ? "a" : "some", + plur(gg.gas_cloud_diss_seen)); + gg.gas_cloud_diss_seen = 0; + } } /* * check whether player enters/leaves one or more regions. */ boolean -in_out_region(x, y) -xchar x, y; +in_out_region(coordxy x, coordxy y) { int i, f_indx = 0; /* First check if hero can do the move */ - for (i = 0; i < n_regions; i++) { - if (regions[i]->attach_2_u) + for (i = 0; i < svn.n_regions; i++) { + if (gr.regions[i]->attach_2_u) continue; - if (inside_region(regions[i], x, y) - ? (!hero_inside(regions[i]) - && (f_indx = regions[i]->can_enter_f) != NO_CALLBACK) - : (hero_inside(regions[i]) - && (f_indx = regions[i]->can_leave_f) != NO_CALLBACK)) { - if (!(*callbacks[f_indx])(regions[i], (genericptr_t) 0)) + if (inside_region(gr.regions[i], x, y) + ? (!hero_inside(gr.regions[i]) + && (f_indx = gr.regions[i]->can_enter_f) != NO_CALLBACK) + : (hero_inside(gr.regions[i]) + && (f_indx = gr.regions[i]->can_leave_f) != NO_CALLBACK)) { + if (!(*callbacks[f_indx])(gr.regions[i], (genericptr_t) 0)) return FALSE; } } /* Callbacks for the regions hero does leave */ - for (i = 0; i < n_regions; i++) { - if (regions[i]->attach_2_u) + for (i = 0; i < svn.n_regions; i++) { + if (gr.regions[i]->attach_2_u) continue; - if (hero_inside(regions[i]) - && !inside_region(regions[i], x, y)) { - clear_hero_inside(regions[i]); - if (regions[i]->leave_msg != (const char *) 0) - pline1(regions[i]->leave_msg); - if ((f_indx = regions[i]->leave_f) != NO_CALLBACK) - (void) (*callbacks[f_indx])(regions[i], (genericptr_t) 0); + if (hero_inside(gr.regions[i]) + && !inside_region(gr.regions[i], x, y)) { + clear_hero_inside(gr.regions[i]); + if (gr.regions[i]->leave_msg != (const char *) 0) + pline1(gr.regions[i]->leave_msg); + if ((f_indx = gr.regions[i]->leave_f) != NO_CALLBACK) + (void) (*callbacks[f_indx])(gr.regions[i], (genericptr_t) 0); } } /* Callbacks for the regions hero does enter */ - for (i = 0; i < n_regions; i++) { - if (regions[i]->attach_2_u) + for (i = 0; i < svn.n_regions; i++) { + if (gr.regions[i]->attach_2_u) continue; - if (!hero_inside(regions[i]) - && inside_region(regions[i], x, y)) { - set_hero_inside(regions[i]); - if (regions[i]->enter_msg != (const char *) 0) - pline1(regions[i]->enter_msg); - if ((f_indx = regions[i]->enter_f) != NO_CALLBACK) - (void) (*callbacks[f_indx])(regions[i], (genericptr_t) 0); + if (!hero_inside(gr.regions[i]) + && inside_region(gr.regions[i], x, y)) { + set_hero_inside(gr.regions[i]); + if (gr.regions[i]->enter_msg != (const char *) 0) + pline1(gr.regions[i]->enter_msg); + if ((f_indx = gr.regions[i]->enter_f) != NO_CALLBACK) + (void) (*callbacks[f_indx])(gr.regions[i], (genericptr_t) 0); } } @@ -481,47 +530,45 @@ xchar x, y; * check whether a monster enters/leaves one or more regions. */ boolean -m_in_out_region(mon, x, y) -struct monst *mon; -xchar x, y; +m_in_out_region(struct monst *mon, coordxy x, coordxy y) { int i, f_indx = 0; /* First check if mon can do the move */ - for (i = 0; i < n_regions; i++) { - if (regions[i]->attach_2_m == mon->m_id) + for (i = 0; i < svn.n_regions; i++) { + if (gr.regions[i]->attach_2_m == mon->m_id) continue; - if (inside_region(regions[i], x, y) - ? (!mon_in_region(regions[i], mon) - && (f_indx = regions[i]->can_enter_f) != NO_CALLBACK) - : (mon_in_region(regions[i], mon) - && (f_indx = regions[i]->can_leave_f) != NO_CALLBACK)) { - if (!(*callbacks[f_indx])(regions[i], mon)) + if (inside_region(gr.regions[i], x, y) + ? (!mon_in_region(gr.regions[i], mon) + && (f_indx = gr.regions[i]->can_enter_f) != NO_CALLBACK) + : (mon_in_region(gr.regions[i], mon) + && (f_indx = gr.regions[i]->can_leave_f) != NO_CALLBACK)) { + if (!(*callbacks[f_indx])(gr.regions[i], mon)) return FALSE; } } /* Callbacks for the regions mon does leave */ - for (i = 0; i < n_regions; i++) { - if (regions[i]->attach_2_m == mon->m_id) + for (i = 0; i < svn.n_regions; i++) { + if (gr.regions[i]->attach_2_m == mon->m_id) continue; - if (mon_in_region(regions[i], mon) - && !inside_region(regions[i], x, y)) { - remove_mon_from_reg(regions[i], mon); - if ((f_indx = regions[i]->leave_f) != NO_CALLBACK) - (void) (*callbacks[f_indx])(regions[i], mon); + if (mon_in_region(gr.regions[i], mon) + && !inside_region(gr.regions[i], x, y)) { + remove_mon_from_reg(gr.regions[i], mon); + if ((f_indx = gr.regions[i]->leave_f) != NO_CALLBACK) + (void) (*callbacks[f_indx])(gr.regions[i], mon); } } /* Callbacks for the regions mon does enter */ - for (i = 0; i < n_regions; i++) { - if (regions[i]->attach_2_m == mon->m_id) + for (i = 0; i < svn.n_regions; i++) { + if (gr.regions[i]->attach_2_m == mon->m_id) continue; - if (!mon_in_region(regions[i], mon) - && inside_region(regions[i], x, y)) { - add_mon_to_reg(regions[i], mon); - if ((f_indx = regions[i]->enter_f) != NO_CALLBACK) - (void) (*callbacks[f_indx])(regions[i], mon); + if (!mon_in_region(gr.regions[i], mon) + && inside_region(gr.regions[i], x, y)) { + add_mon_to_reg(gr.regions[i], mon); + if ((f_indx = gr.regions[i]->enter_f) != NO_CALLBACK) + (void) (*callbacks[f_indx])(gr.regions[i], mon); } } @@ -532,33 +579,33 @@ xchar x, y; * Checks player's regions after a teleport for instance. */ void -update_player_regions() +update_player_regions(void) { - register int i; + int i; - for (i = 0; i < n_regions; i++) - if (!regions[i]->attach_2_u && inside_region(regions[i], u.ux, u.uy)) - set_hero_inside(regions[i]); + for (i = 0; i < svn.n_regions; i++) + if (!gr.regions[i]->attach_2_u + && inside_region(gr.regions[i], u.ux, u.uy)) + set_hero_inside(gr.regions[i]); else - clear_hero_inside(regions[i]); + clear_hero_inside(gr.regions[i]); } /* * Ditto for a specified monster. */ void -update_monster_region(mon) -struct monst *mon; +update_monster_region(struct monst *mon) { - register int i; + int i; - for (i = 0; i < n_regions; i++) { - if (inside_region(regions[i], mon->mx, mon->my)) { - if (!mon_in_region(regions[i], mon)) - add_mon_to_reg(regions[i], mon); + for (i = 0; i < svn.n_regions; i++) { + if (inside_region(gr.regions[i], mon->mx, mon->my)) { + if (!mon_in_region(gr.regions[i], mon)) + add_mon_to_reg(gr.regions[i], mon); } else { - if (mon_in_region(regions[i], mon)) - remove_mon_from_reg(regions[i], mon); + if (mon_in_region(gr.regions[i], mon)) + remove_mon_from_reg(gr.regions[i], mon); } } } @@ -567,7 +614,7 @@ struct monst *mon; /* not yet used */ /* - * Change monster pointer in regions + * Change monster pointer in gr.regions * This happens, for instance, when a monster grows and * need a new structure (internally that is). */ @@ -575,12 +622,12 @@ void replace_mon_regions(monold, monnew) struct monst *monold, *monnew; { - register int i; + int i; - for (i = 0; i < n_regions; i++) - if (mon_in_region(regions[i], monold)) { - remove_mon_from_reg(regions[i], monold); - add_mon_to_reg(regions[i], monnew); + for (i = 0; i < svn.n_regions; i++) + if (mon_in_region(gr.regions[i], monold)) { + remove_mon_from_reg(gr.regions[i], monold); + add_mon_to_reg(gr.regions[i], monnew); } } @@ -588,41 +635,101 @@ struct monst *monold, *monnew; * Remove monster from all regions it was in (ie monster just died) */ void -remove_mon_from_regions(mon) -struct monst *mon; +remove_mon_from_regions(struct monst *mon) { - register int i; + int i; - for (i = 0; i < n_regions; i++) - if (mon_in_region(regions[i], mon)) - remove_mon_from_reg(regions[i], mon); + for (i = 0; i < svn.n_regions; i++) + if (mon_in_region(gr.regions[i], mon)) + remove_mon_from_reg(gr.regions[i], mon); } #endif /*0*/ +/* per-turn damage inflicted by visible region; hides details from caller */ +int +reg_damg(NhRegion *reg) +{ + int damg = (!reg->visible || reg->ttl == -2L) ? 0 : reg->arg.a_int; + + return damg; +} + +/* check whether current level has any visible regions */ +boolean +any_visible_region(void) +{ + int i; + + for (i = 0; i < svn.n_regions; i++) { + if (!gr.regions[i]->visible || gr.regions[i]->ttl == -2L) + continue; + return TRUE; + } + return FALSE; +} + +/* for the wizard mode #timeout command */ +void +visible_region_summary(winid win) +{ + NhRegion *reg; + char buf[BUFSZ], typbuf[QBUFSZ]; + int i, damg, hdr_done = 0; + const char *fldsep = iflags.menu_tab_sep ? "\t" : " "; + + for (i = 0; i < svn.n_regions; i++) { + reg = gr.regions[i]; + if (!reg->visible || reg->ttl == -2L) + continue; + + if (!hdr_done++) { + putstr(win, 0, ""); + putstr(win, 0, "Visible regions"); + } + /* + * TODO? sort the regions by time-to-live or by bounding box. + */ + + /* we display relative time (turns left) rather than absolute + (the turn when region will go away); + since time-to-live has already been decremented, regions + which are due to timeout on the next turn have ttl==0; + adding 1 is intended to make the display be less confusing */ + Sprintf(buf, "%5ld", reg->ttl + 1L); + damg = reg->arg.a_int; + if (damg) + Sprintf(typbuf, "poison gas (%d)", damg); + else + Strcpy(typbuf, "vapor"); + Sprintf(eos(buf), "%s%-16s", fldsep, typbuf); + Sprintf(eos(buf), "%s@[%d,%d..%d,%d]", fldsep, + reg->bounding_box.lx, reg->bounding_box.ly, + reg->bounding_box.hx, reg->bounding_box.hy); + putstr(win, 0, buf); + } +} + /* * Check if a spot is under a visible region (eg: gas cloud). * Returns NULL if not, otherwise returns region. */ NhRegion * -visible_region_at(x, y) -xchar x, y; +visible_region_at(coordxy x, coordxy y) { - register int i; + int i; - for (i = 0; i < n_regions; i++) { - if (!regions[i]->visible || regions[i]->ttl == -2L) + for (i = 0; i < svn.n_regions; i++) { + if (!gr.regions[i]->visible || gr.regions[i]->ttl == -2L) continue; - if (inside_region(regions[i], x, y)) - return regions[i]; + if (inside_region(gr.regions[i], x, y)) + return gr.regions[i]; } return (NhRegion *) 0; } void -show_region(reg, x, y) -NhRegion *reg; -xchar x, y; +show_region(NhRegion *reg, coordxy x, coordxy y) { show_glyph(x, y, reg->glyph); } @@ -631,168 +738,179 @@ xchar x, y; * save_regions : */ void -save_regions(fd, mode) -int fd; -int mode; +save_regions(NHFILE *nhfp) { + NhRegion *r; int i, j; unsigned n; - if (!perform_bwrite(mode)) + if (!update_file(nhfp)) goto skip_lots; - - bwrite(fd, (genericptr_t) &moves, sizeof(moves)); /* timestamp */ - bwrite(fd, (genericptr_t) &n_regions, sizeof(n_regions)); - for (i = 0; i < n_regions; i++) { - bwrite(fd, (genericptr_t) ®ions[i]->bounding_box, sizeof(NhRect)); - bwrite(fd, (genericptr_t) ®ions[i]->nrects, sizeof(short)); - for (j = 0; j < regions[i]->nrects; j++) - bwrite(fd, (genericptr_t) ®ions[i]->rects[j], sizeof(NhRect)); - bwrite(fd, (genericptr_t) ®ions[i]->attach_2_u, sizeof(boolean)); + /* timestamp */ + Sfo_long(nhfp, &svm.moves, "region-tmstamp"); + Sfo_int(nhfp, &svn.n_regions, "region-region_count"); + + for (i = 0; i < svn.n_regions; i++) { + r = gr.regions[i]; + Sfo_nhrect(nhfp, &r->bounding_box, "region-bounding_box"); + Sfo_short(nhfp, &r->nrects, "region-nrects"); + for (j = 0; j < r->nrects; j++) { + Sfo_nhrect(nhfp, &r->rects[j], "region-rect"); + } + Sfo_boolean(nhfp, &r->attach_2_u, "region-attach_2_u"); + Sfo_unsigned(nhfp, &r->attach_2_m, "region-attach_2_m"); n = 0; - bwrite(fd, (genericptr_t) ®ions[i]->attach_2_m, sizeof(unsigned)); - n = regions[i]->enter_msg != (const char *) 0 - ? strlen(regions[i]->enter_msg) - : 0; - bwrite(fd, (genericptr_t) &n, sizeof n); - if (n > 0) - bwrite(fd, (genericptr_t) regions[i]->enter_msg, n); - n = regions[i]->leave_msg != (const char *) 0 - ? strlen(regions[i]->leave_msg) - : 0; - bwrite(fd, (genericptr_t) &n, sizeof n); - if (n > 0) - bwrite(fd, (genericptr_t) regions[i]->leave_msg, n); - bwrite(fd, (genericptr_t) ®ions[i]->ttl, sizeof(long)); - bwrite(fd, (genericptr_t) ®ions[i]->expire_f, sizeof(short)); - bwrite(fd, (genericptr_t) ®ions[i]->can_enter_f, sizeof(short)); - bwrite(fd, (genericptr_t) ®ions[i]->enter_f, sizeof(short)); - bwrite(fd, (genericptr_t) ®ions[i]->can_leave_f, sizeof(short)); - bwrite(fd, (genericptr_t) ®ions[i]->leave_f, sizeof(short)); - bwrite(fd, (genericptr_t) ®ions[i]->inside_f, sizeof(short)); - bwrite(fd, (genericptr_t) ®ions[i]->player_flags, - sizeof(unsigned int)); - bwrite(fd, (genericptr_t) ®ions[i]->n_monst, sizeof(short)); - for (j = 0; j < regions[i]->n_monst; j++) - bwrite(fd, (genericptr_t) ®ions[i]->monsters[j], - sizeof(unsigned)); - bwrite(fd, (genericptr_t) ®ions[i]->visible, sizeof(boolean)); - bwrite(fd, (genericptr_t) ®ions[i]->glyph, sizeof(int)); - bwrite(fd, (genericptr_t) ®ions[i]->arg, sizeof(anything)); + n = !r->enter_msg ? 0U : (unsigned) strlen(r->enter_msg); + Sfo_unsigned(nhfp, &n, "region-enter_msg_length"); + if (n > 0) { + Sfo_char(nhfp, (char *) r->enter_msg, + "region-enter_msg", (int) n); + } + n = !r->leave_msg ? 0U : (unsigned) strlen(r->leave_msg); + Sfo_unsigned(nhfp, &n, "region-leave_msg_length"); + if (n > 0) { + Sfo_char(nhfp, (char *) r->leave_msg, "region-leave_msg", (int) n); + } + Sfo_long(nhfp, &r->ttl, "region-ttl"); + Sfo_short(nhfp, &r->expire_f, "region-expire_f"); + Sfo_short(nhfp, &r->can_enter_f, "region-can_enter_f"); + Sfo_short(nhfp, &r->enter_f, "region-enter_f"); + Sfo_short(nhfp, &r->can_leave_f, "region-can_leave_f"); + Sfo_short(nhfp, &r->leave_f, "region-leave_f"); + Sfo_short(nhfp, &r->inside_f, "region-inside_f"); + Sfo_unsigned(nhfp, &r->player_flags, "region-player_flags"); + Sfo_short(nhfp, &r->n_monst, "region-monster_count"); + + for (j = 0; j < r->n_monst; j++) { + Sfo_unsigned(nhfp, &r->monsters[j], "region-monster"); + } + Sfo_boolean(nhfp, &r->visible, "region-visible"); + Sfo_int(nhfp, &r->glyph, "region-glyph"); + Sfo_any(nhfp, &r->arg, "region-arg"); } -skip_lots: - if (release_data(mode)) + skip_lots: + if (release_data(nhfp)) clear_regions(); } +#endif /* !SFCTOOL */ void -rest_regions(fd, ghostly) -int fd; -boolean ghostly; /* If a bones file restore */ +rest_regions(NHFILE *nhfp) { + NhRegion *r; int i, j; - unsigned n; - long tmstamp; + unsigned n = 0; + long tmstamp = 0L; char *msg_buf; + boolean ghostly = (nhfp->ftype == NHF_BONESFILE); clear_regions(); /* Just for security */ - mread(fd, (genericptr_t) &tmstamp, sizeof(tmstamp)); + Sfi_long(nhfp, &tmstamp, "region-tmstamp"); if (ghostly) tmstamp = 0; else - tmstamp = (moves - tmstamp); - mread(fd, (genericptr_t) &n_regions, sizeof(n_regions)); - max_regions = n_regions; - if (n_regions > 0) - regions = (NhRegion **) alloc(sizeof(NhRegion *) * n_regions); - for (i = 0; i < n_regions; i++) { - regions[i] = (NhRegion *) alloc(sizeof(NhRegion)); - mread(fd, (genericptr_t) ®ions[i]->bounding_box, sizeof(NhRect)); - mread(fd, (genericptr_t) ®ions[i]->nrects, sizeof(short)); - - if (regions[i]->nrects > 0) - regions[i]->rects = - (NhRect *) alloc(sizeof(NhRect) * regions[i]->nrects); - for (j = 0; j < regions[i]->nrects; j++) - mread(fd, (genericptr_t) ®ions[i]->rects[j], sizeof(NhRect)); - mread(fd, (genericptr_t) ®ions[i]->attach_2_u, sizeof(boolean)); - mread(fd, (genericptr_t) ®ions[i]->attach_2_m, sizeof(unsigned)); - - mread(fd, (genericptr_t) &n, sizeof n); + tmstamp = (svm.moves - tmstamp); + Sfi_int(nhfp, &svn.n_regions, "region-region_count"); + gm.max_regions = svn.n_regions; + if (svn.n_regions > 0) + gr.regions = (NhRegion **) alloc(svn.n_regions * sizeof (NhRegion *)); + for (i = 0; i < svn.n_regions; i++) { + r = gr.regions[i] = (NhRegion *) alloc(sizeof (NhRegion)); + Sfi_nhrect(nhfp, &r->bounding_box, "region-bounding box"); + Sfi_short(nhfp, &r->nrects, "region-nrects"); + if (r->nrects > 0) + r->rects = (NhRect *) alloc(r->nrects * sizeof (NhRect)); + else + r->rects = (NhRect *) 0; + for (j = 0; j < r->nrects; j++) { + Sfi_nhrect(nhfp, &r->rects[j], "region-rect"); + } + + Sfi_boolean(nhfp, &r->attach_2_u, "region-attach_2_u"); + Sfi_unsigned(nhfp, &r->attach_2_m, "region-attach_2_m"); + Sfi_unsigned(nhfp, &n, "region-enter_msg_length"); if (n > 0) { msg_buf = (char *) alloc(n + 1); - mread(fd, (genericptr_t) msg_buf, n); + Sfi_char(nhfp, msg_buf, "region-enter_msg", n); msg_buf[n] = '\0'; - regions[i]->enter_msg = (const char *) msg_buf; - } else - regions[i]->enter_msg = (const char *) 0; + } else { + msg_buf = (char *) 0; + } + r->enter_msg = (const char *) msg_buf; - mread(fd, (genericptr_t) &n, sizeof n); + Sfi_unsigned(nhfp, &n, "region-leave_msg_length"); if (n > 0) { msg_buf = (char *) alloc(n + 1); - mread(fd, (genericptr_t) msg_buf, n); + Sfi_char(nhfp, msg_buf, "region-leave_msg", n); msg_buf[n] = '\0'; - regions[i]->leave_msg = (const char *) msg_buf; - } else - regions[i]->leave_msg = (const char *) 0; + r->leave_msg = (const char *) msg_buf; + } else { + msg_buf = (char *) 0; + } + r->leave_msg = (const char *) msg_buf; - mread(fd, (genericptr_t) ®ions[i]->ttl, sizeof(long)); + Sfi_long(nhfp, &r->ttl, "region-ttl"); /* check for expired region */ - if (regions[i]->ttl >= 0L) - regions[i]->ttl = - (regions[i]->ttl > tmstamp) ? regions[i]->ttl - tmstamp : 0L; - mread(fd, (genericptr_t) ®ions[i]->expire_f, sizeof(short)); - mread(fd, (genericptr_t) ®ions[i]->can_enter_f, sizeof(short)); - mread(fd, (genericptr_t) ®ions[i]->enter_f, sizeof(short)); - mread(fd, (genericptr_t) ®ions[i]->can_leave_f, sizeof(short)); - mread(fd, (genericptr_t) ®ions[i]->leave_f, sizeof(short)); - mread(fd, (genericptr_t) ®ions[i]->inside_f, sizeof(short)); - mread(fd, (genericptr_t) ®ions[i]->player_flags, - sizeof(unsigned int)); + if (r->ttl >= 0L) + r->ttl = (r->ttl > tmstamp) ? r->ttl - tmstamp : 0L; + Sfi_short(nhfp, &r->expire_f, "region-expire_f"); + Sfi_short(nhfp, &r->can_enter_f, "region-can_enter_f"); + Sfi_short(nhfp, &r->enter_f, "region-enter_f"); + Sfi_short(nhfp, &r->can_leave_f, "region-can_leave_f"); + Sfi_short(nhfp, &r->leave_f, "region-leave_f"); + Sfi_short(nhfp, &r->inside_f, "region-inside_f"); + Sfi_unsigned(nhfp, &r->player_flags, "region-player_flags"); if (ghostly) { /* settings pertained to old player */ - clear_hero_inside(regions[i]); - clear_heros_fault(regions[i]); + clear_hero_inside(r); + clear_heros_fault(r); } - mread(fd, (genericptr_t) ®ions[i]->n_monst, sizeof(short)); - if (regions[i]->n_monst > 0) - regions[i]->monsters = - (unsigned *) alloc(sizeof(unsigned) * regions[i]->n_monst); + Sfi_short(nhfp, &r->n_monst, "region-monster_count"); + if (r->n_monst > 0) + r->monsters = (unsigned *) alloc(r->n_monst * sizeof (unsigned)); else - regions[i]->monsters = (unsigned int *) 0; - regions[i]->max_monst = regions[i]->n_monst; - for (j = 0; j < regions[i]->n_monst; j++) - mread(fd, (genericptr_t) ®ions[i]->monsters[j], - sizeof(unsigned)); - mread(fd, (genericptr_t) ®ions[i]->visible, sizeof(boolean)); - mread(fd, (genericptr_t) ®ions[i]->glyph, sizeof(int)); - mread(fd, (genericptr_t) ®ions[i]->arg, sizeof(anything)); + r->monsters = (unsigned *) 0; + r->max_monst = r->n_monst; + for (j = 0; j < r->n_monst; j++) { + Sfi_unsigned(nhfp, &r->monsters[j], "region-monster"); + } + Sfi_boolean(nhfp, &r->visible, "region-visible"); + Sfi_int(nhfp, &r->glyph, "region-glyph"); + Sfi_any(nhfp, &r->arg, "region-arg"); } +#ifndef SFCTOOL /* remove expired regions, do not trigger the expire_f callback (yet!); also update monster lists if this data is coming from a bones file */ - for (i = n_regions - 1; i >= 0; i--) - if (regions[i]->ttl == 0L) - remove_region(regions[i]); - else if (ghostly && regions[i]->n_monst > 0) - reset_region_mids(regions[i]); + for (i = svn.n_regions - 1; i >= 0; i--) { + r = gr.regions[i]; + if (r->ttl == 0L) + remove_region(r); + else if (ghostly && r->n_monst > 0) + reset_region_mids(r); + } +#endif /* !SFCTOOL */ } +#ifndef SFCTOOL +DISABLE_WARNING_FORMAT_NONLITERAL + /* to support '#stats' wizard-mode command */ void -region_stats(hdrfmt, hdrbuf, count, size) -const char *hdrfmt; -char *hdrbuf; -long *count, *size; +region_stats( + const char *hdrfmt, + char *hdrbuf, + long *count, + long *size) { NhRegion *rg; int i; /* other stats formats take one parameter; this takes two */ Sprintf(hdrbuf, hdrfmt, (long) sizeof (NhRegion), (long) sizeof (NhRect)); - *count = (long) n_regions; /* might be 0 even though max_regions isn't */ - *size = (long) max_regions * (long) sizeof (NhRegion); - for (i = 0; i < n_regions; ++i) { - rg = regions[i]; + *count = (long) svn.n_regions; /* might be 0 even tho max_regions isn't */ + *size = (long) gm.max_regions * (long) sizeof (NhRegion); + for (i = 0; i < svn.n_regions; ++i) { + rg = gr.regions[i]; *size += (long) rg->nrects * (long) sizeof (NhRect); if (rg->enter_msg) *size += (long) (strlen(rg->enter_msg) + 1); @@ -803,10 +921,11 @@ long *count, *size; /* ? */ } +RESTORE_WARNING_FORMAT_NONLITERAL + /* update monster IDs for region being loaded from bones; `ghostly' implied */ -STATIC_OVL void -reset_region_mids(reg) -NhRegion *reg; +staticfn void +reset_region_mids(NhRegion *reg) { int i = 0, n = reg->n_monst; unsigned *mid_list = reg->monsters; @@ -833,11 +952,9 @@ NhRegion *reg; *--------------------------------------------------------------*/ NhRegion * -create_msg_region(x, y, w, h, msg_enter, msg_leave) -xchar x, y; -xchar w, h; -const char *msg_enter; -const char *msg_leave; +create_msg_region( + coordxy x, coordxy y, coordxy w, coordxy h, + const char *msg_enter, const char *msg_leave) { NhRect tmprect; NhRegion *reg = create_region((NhRect *) 0, 0); @@ -863,9 +980,7 @@ const char *msg_leave; *--------------------------------------------------------------*/ boolean -enter_force_field(p1, p2) -genericptr_t p1; -genericptr_t p2; +enter_force_field(genericptr_t p1, genericptr_t p2) { struct monst *mtmp; @@ -885,10 +1000,7 @@ genericptr_t p2; } NhRegion * -create_force_field(x, y, radius, ttl) -xchar x, y; -int radius; -long ttl; +create_force_field(coordxy x, coordxy y, int radius, long ttl) { int i; NhRegion *ff; @@ -909,7 +1021,7 @@ long ttl; tmprect.hy--; } ff->ttl = ttl; - if (!in_mklev && !context.mon_moving) + if (!gi.in_mklev && !svc.context.mon_moving) set_heros_fault(ff); /* assume player has created it */ /* ff->can_enter_f = enter_force_field; */ /* ff->can_leave_f = enter_force_field; */ @@ -931,12 +1043,11 @@ long ttl; */ /*ARGSUSED*/ boolean -expire_gas_cloud(p1, p2) -genericptr_t p1; -genericptr_t p2 UNUSED; +expire_gas_cloud(genericptr_t p1, genericptr_t p2 UNUSED) { NhRegion *reg; - int damage; + int damage, pass; + coordxy x, y; reg = (NhRegion *) p1; damage = reg->arg.a_int; @@ -944,33 +1055,60 @@ genericptr_t p2 UNUSED; /* If it was a thick cloud, it dissipates a little first */ if (damage >= 5) { damage /= 2; /* It dissipates, let's do less damage */ - reg->arg = zeroany; + reg->arg = cg.zeroany; reg->arg.a_int = damage; reg->ttl = 2L; /* Here's the trick : reset ttl */ return FALSE; /* THEN return FALSE, means "still there" */ } + + /* The cloud no longer blocks vision. cansee() checks shouldn't be made + until all blocked spots have been unblocked, so we need two passes */ + for (pass = 1; pass <= (Blind ? 1 : 2); ++pass) { + for (x = reg->bounding_box.lx; x <= reg->bounding_box.hx; x++) { + for (y = reg->bounding_box.ly; y <= reg->bounding_box.hy; y++) { + if (inside_region(reg, x, y)) { + if (pass == 1) { + if (!does_block(x, y, &levl[x][y])) + unblock_point(x, y); + } else { /* pass==2 */ + if (!u.uswallow) { + if (u_at(x, y)) + gg.gas_cloud_diss_within = TRUE; + else if (cansee(x, y)) + gg.gas_cloud_diss_seen++; + } + } + } + } + } + } + return TRUE; /* OK, it's gone, you can free it! */ } +/* returns True if p2 is killed by region p1, False otherwise */ boolean -inside_gas_cloud(p1, p2) -genericptr_t p1; -genericptr_t p2; +inside_gas_cloud(genericptr_t p1, genericptr_t p2) { - NhRegion *reg; - struct monst *mtmp; - int dam; + NhRegion *reg = (NhRegion *) p1; + struct monst *mtmp = (struct monst *) p2; + struct monst *umon = mtmp ? mtmp : &gy.youmonst; + int dam = reg->arg.a_int; /* - * Gas clouds can't be targetted at water locations, but they can + * Gas clouds can't be targeted at water locations, but they can * start next to water and spread over it. */ - reg = (NhRegion *) p1; - dam = reg->arg.a_int; - if (p2 == (genericptr_t) 0) { /* This means *YOU* Bozo! */ - if (u.uinvulnerable || nonliving(youmonst.data) || Breathless - || Underwater) + /* fog clouds maintain gas clouds, even poisonous ones */ + if (reg->ttl < 20 && umon && umon->data == &mons[PM_FOG_CLOUD]) + reg->ttl += 5; + + if (dam < 1) + return FALSE; /* if no damage then there's nothing to do here... */ + + if (!mtmp) { /* hero is indicated by Null rather than by &youmonst */ + if (m_poisongas_ok(&gy.youmonst) == M_POISONGAS_OK) return FALSE; if (!Blind) { Your("%s sting.", makeplural(body_part(EYE))); @@ -980,31 +1118,29 @@ genericptr_t p2; pline("%s is burning your %s!", Something, makeplural(body_part(LUNG))); You("cough and spit blood!"); - losehp(Maybe_Half_Phys(rnd(dam) + 5), "gas cloud", KILLED_BY_AN); + wake_nearto(u.ux, u.uy, 2); + dam = Maybe_Half_Phys(rnd(dam) + 5); + if (Half_gas_damage) /* worn towel */ + dam = (dam + 1) / 2; + losehp(dam, "gas cloud", KILLED_BY_AN); + monstunseesu(M_SEEN_POISON); return FALSE; } else { You("cough!"); + wake_nearto(u.ux, u.uy, 2); + monstseesu(M_SEEN_POISON); return FALSE; } } else { /* A monster is inside the cloud */ mtmp = (struct monst *) p2; - /* Non living and non breathing monsters are not concerned; - adult green dragon is not affected by gas cloud, baby one is */ - if (!(nonliving(mtmp->data) || is_vampshifter(mtmp)) - && !breathless(mtmp->data) - /* not is_swimmer(); assume that non-fish are swimming on - the surface and breathing the air above it periodically - unless located at water spot on plane of water */ - && !((mtmp->data->mlet == S_EEL || Is_waterlevel(&u.uz)) - && is_pool(mtmp->mx, mtmp->my)) - /* exclude monsters with poison gas breath attack: - adult green dragon and Chromatic Dragon (and iron golem, - but nonliving() and breathless() tests also catch that) */ - && !(attacktype_fordmg(mtmp->data, AT_BREA, AD_DRST) - || attacktype_fordmg(mtmp->data, AT_BREA, AD_RBRE))) { - if (cansee(mtmp->mx, mtmp->my)) - pline("%s coughs!", Monnam(mtmp)); + if (m_poisongas_ok(mtmp) != M_POISONGAS_OK) { + if (!is_silent(mtmp->data)) { + if (cansee(mtmp->mx, mtmp->my) + || (distu(mtmp->mx, mtmp->my) < 8)) + pline("%s coughs!", Monnam(mtmp)); + wake_nearto(mtmp->mx, mtmp->my, 2); + } if (heros_fault(reg)) setmangry(mtmp, TRUE); if (haseyes(mtmp->data) && mtmp->mcansee) { @@ -1028,57 +1164,193 @@ genericptr_t p2; return FALSE; /* Monster is still alive */ } -NhRegion * -create_gas_cloud(x, y, radius, damage) -xchar x, y; -int radius; -int damage; +staticfn boolean +is_hero_inside_gas_cloud(void) { - NhRegion *cloud; - int i, nrect; - NhRect tmprect; + int i; - cloud = create_region((NhRect *) 0, 0); - nrect = radius; - tmprect.lx = x; - tmprect.hx = x; - tmprect.ly = y - (radius - 1); - tmprect.hy = y + (radius - 1); - for (i = 0; i < nrect; i++) { - add_rect_to_reg(cloud, &tmprect); - tmprect.lx--; - tmprect.hx++; - tmprect.ly++; - tmprect.hy--; - } - cloud->ttl = rn1(3, 4); - if (!in_mklev && !context.mon_moving) + for (i = 0; i < svn.n_regions; i++) + if (hero_inside(gr.regions[i]) + && gr.regions[i]->inside_f == INSIDE_GAS_CLOUD) + return TRUE; + return FALSE; +} + +/* details of gas cloud creation which are common to create_gas_cloud() + and create_gas_cloud_selection() */ +staticfn void +make_gas_cloud( + NhRegion *cloud, + int damage, + boolean inside_cloud) +{ + if (!gi.in_mklev && !svc.context.mon_moving) set_heros_fault(cloud); /* assume player has created it */ cloud->inside_f = INSIDE_GAS_CLOUD; cloud->expire_f = EXPIRE_GAS_CLOUD; - cloud->arg = zeroany; + cloud->arg = cg.zeroany; cloud->arg.a_int = damage; cloud->visible = TRUE; cloud->glyph = cmap_to_glyph(damage ? S_poisoncloud : S_cloud); add_region(cloud); + + if (!gi.in_mklev && !inside_cloud && is_hero_inside_gas_cloud()) { + You("are enveloped in a cloud of %s!", + /* FIXME: "steam" is wrong if this cloud is just the trail of + a fog cloud's movement; changing to "vapor" would handle + that but seems a step backward when it really is steam */ + damage ? "noxious gas" : "steam"); + iflags.last_msg = PLNMSG_ENVELOPED_IN_GAS; + } +} + +/* Create a gas cloud which starts at (x,y) and grows outward from it via + * breadth-first search. + * cloudsize is the number of squares the cloud will attempt to fill. + * damage is how much it deals to afflicted creatures. */ +#define MAX_CLOUD_SIZE 150 +NhRegion * +create_gas_cloud( + coordxy x, coordxy y, + int cloudsize, + int damage) +{ + NhRegion *cloud; + int i, j; + NhRect tmprect; + + /* store visited coords */ + coordxy xcoords[MAX_CLOUD_SIZE]; + coordxy ycoords[MAX_CLOUD_SIZE]; + xcoords[0] = x; + ycoords[0] = y; + int curridx; + int newidx = 1; /* initial spot is already taken */ + boolean inside_cloud = is_hero_inside_gas_cloud(); + + /* a single-point cloud on hero and it deals no damage. + probably a natural cause of being polyed. don't message about it */ + if (!svc.context.mon_moving && u_at(x, y) && cloudsize == 1 + && (!damage + || (damage && m_poisongas_ok(&gy.youmonst) == M_POISONGAS_OK))) + inside_cloud = TRUE; + + if (cloudsize > MAX_CLOUD_SIZE) { + impossible("create_gas_cloud: cloud too large (%d)!", cloudsize); + cloudsize = MAX_CLOUD_SIZE; + } + + for (curridx = 0; curridx < newidx; curridx++) { + if (newidx >= cloudsize) + break; + int xx = xcoords[curridx]; + int yy = ycoords[curridx]; + /* Do NOT check for if there is already a gas cloud created at some + * other time at this position. They can overlap. */ + + /* Primitive Fisher-Yates-Knuth shuffle to randomize the order of + * directions chosen. */ + coord dirs[4] = { {0, -1}, {0, 1}, {-1, 0}, {1, 0} }; + for (i = 4; i > 0; --i) { + coordxy swapidx = rn2(i); + coord tmp = dirs[swapidx]; + + dirs[swapidx] = dirs[i - 1]; + dirs[i - 1] = tmp; + } + int nvalid = 0; /* # of valid adjacent spots */ + for (i = 0; i < 4; ++i) { + /* try all 4 cardinal directions */ + int dx = dirs[i].x, dy = dirs[i].y; + boolean isunpicked = TRUE; + + if (valid_cloud_pos(xx + dx, yy + dy)) { + nvalid++; + /* don't pick a location we've already picked */ + for (j = 0; j < newidx; ++j) { + if (xcoords[j] == xx + dx && ycoords[j] == yy + dy) { + isunpicked = FALSE; + break; + } + } + /* randomly disrupt the natural breadth-first search, so that + * clouds released in open spaces don't always tend towards a + * rhombus shape */ + if (nvalid == 4 && !rn2(2)) + continue; + + if (isunpicked) { + xcoords[newidx] = xx + dx; + ycoords[newidx] = yy + dy; + newidx++; + } + } + if (newidx >= cloudsize) { + /* don't try further directions */ + break; + } + } + } + /* We have now either filled up xcoord and ycoord entirely or run out + of space. In either case, newidx is the correct total number of + coordinates inserted. */ + cloud = create_region((NhRect *) 0, 0); + for (i = 0; i < newidx; ++i) { + tmprect.lx = tmprect.hx = xcoords[i]; + tmprect.ly = tmprect.hy = ycoords[i]; + add_rect_to_reg(cloud, &tmprect); + } + cloud->ttl = rn1(3, 4); + /* If cloud was constrained in small space, give it more time to live. */ + cloud->ttl = (cloud->ttl * cloudsize) / newidx; + + make_gas_cloud(cloud, damage, inside_cloud); return cloud; } +/* create a single gas cloud from selection */ +NhRegion * +create_gas_cloud_selection( + struct selectionvar *sel, + int damage) +{ + NhRegion *cloud; + NhRect tmprect; + coordxy x, y; + NhRect r = cg.zeroNhRect; + boolean inside_cloud = is_hero_inside_gas_cloud(); + + selection_getbounds(sel, &r); + + cloud = create_region((NhRect *) 0, 0); + for (x = r.lx; x <= r.hx; x++) + for (y = r.ly; y <= r.hy; y++) + if (selection_getpoint(x, y, sel)) { + tmprect.lx = tmprect.hx = x; + tmprect.ly = tmprect.hy = y; + add_rect_to_reg(cloud, &tmprect); + } + + make_gas_cloud(cloud, damage, inside_cloud); + return cloud; +} + + /* for checking troubles during prayer; is hero at risk? */ boolean -region_danger() +region_danger(void) { int i, f_indx, n = 0; - for (i = 0; i < n_regions; i++) { + for (i = 0; i < svn.n_regions; i++) { /* only care about regions that hero is in */ - if (!hero_inside(regions[i])) + if (!hero_inside(gr.regions[i])) continue; - f_indx = regions[i]->inside_f; + f_indx = gr.regions[i]->inside_f; /* the only type of region we understand is gas_cloud */ if (f_indx == INSIDE_GAS_CLOUD) { /* completely harmless if you don't need to breathe */ - if (nonliving(youmonst.data) || Breathless) + if (nonliving(gy.youmonst.data) || Breathless) continue; /* minor inconvenience if you're poison resistant; not harmful enough to be a prayer-level trouble */ @@ -1093,26 +1365,33 @@ region_danger() /* for fixing trouble at end of prayer; danger detected at start of prayer might have expired by now */ void -region_safety() +region_safety(void) { NhRegion *r = 0; int i, f_indx, n = 0; - for (i = 0; i < n_regions; i++) { + for (i = 0; i < svn.n_regions; i++) { /* only care about regions that hero is in */ - if (!hero_inside(regions[i])) + if (!hero_inside(gr.regions[i])) continue; - f_indx = regions[i]->inside_f; + f_indx = gr.regions[i]->inside_f; /* the only type of region we understand is gas_cloud */ if (f_indx == INSIDE_GAS_CLOUD) { - if (!n++ && regions[i]->ttl >= 0) - r = regions[i]; + if (!n++ && gr.regions[i]->ttl >= 0) + r = gr.regions[i]; } } if (n > 1 || (n == 1 && !r)) { /* multiple overlapping cloud regions or non-expiring one */ - safe_teleds(FALSE); + (void) safe_teleds(TELEDS_NO_FLAGS); + /* maybe there's no safe place available; must get hero out of danger + or prayer's "fix all troubles" result will get stuck in a loop */ + if (region_danger()) { + set_itimeout(&HMagical_breathing, (long) (d(4, 4) + 4)); + /* not already Breathless or wouldn't be in region danger */ + You_feel("able to breathe."); + } } else if (r) { remove_region(r); pline_The("gas cloud enveloping you dissipates."); @@ -1121,8 +1400,9 @@ region_safety() pline_The("gas cloud has dissipated."); } /* maybe cure blindness too */ - if ((Blinded & TIMEOUT) == 1L) + if (BlindedTimeout == 1L) make_blinded(0L, TRUE); } +#endif /* !SFCTOOL */ /*region.c*/ diff --git a/src/report.c b/src/report.c new file mode 100644 index 000000000..e5f701287 --- /dev/null +++ b/src/report.c @@ -0,0 +1,665 @@ +/* NetHack 5.0 report.c $NHDT-Date: 1777240823 2026/04/26 22:00:23 $ $NHDT-Branch: to500 $:$NHDT-Revision: 1.19 $ */ +/* Copyright (c) Kenneth Lorber, Kensington, Maryland, 2024 */ +/* NetHack may be freely redistributed. See license for details. */ + +#include "hack.h" + +/* NB: CRASHREPORT implies PANICTRACE */ + +# ifndef NO_SIGNAL +#include +# endif +# ifndef LONG_MAX +#include +# endif +#include "dlb.h" +#include + +#include +# ifdef PANICTRACE_LIBC +#include +# endif + +#ifdef CRASHREPORT +# ifdef WIN32 +# define HASH_PRAGMA_START +# define HASH_PRAGMA_END +# else +# define HASH_PRAGMA_START \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +# define HASH_PRAGMA_END _Pragma("GCC diagnostic pop"); +# endif +# ifdef MACOS +#include +# define HASH_CONTEXTPTR(CTXP) \ + unsigned char tmp[CC_MD4_DIGEST_LENGTH]; \ + CC_MD4_CTX CTXP ## _; \ + CC_MD4_CTX *CTXP = &CTXP ## _ +# define HASH_INIT(ctxp) !CC_MD4_Init(ctxp) +# define HASH_UPDATE(ctx, ptr, len) !CC_MD4_Update(ctx, ptr, len) +# define HASH_FINISH(ctxp) !CC_MD4_Final(tmp, ctxp) +# define HASH_RESULT_SIZE(ctxp) CC_MD4_DIGEST_LENGTH +# define HASH_RESULT(ctx, inp) *inp = (unsigned char *) ctx +# define HASH_CLEANUP(ctxp) +# define HASH_OFLAGS O_RDONLY +# define HASH_BINFILE_DECL char *binfile = argv[0]; +# if (NH_DEVEL_STATUS == NH_STATUS_BETA) +# define HASH_BINFILE() \ + if (!binfile || !*binfile) { \ + /* If this triggers, investigate CFBundleGetMainBundle */ \ + /* or CFBundleCopyExecutableURL. */ \ + raw_print( \ + "BETA warning: crashreport_init called without useful info"); \ + goto skip; \ + } +# else /* BETA */ +# define HASH_BINFILE() \ + if (!binfile || !*binfile) { \ + goto skip; \ + } +# endif /* BETA */ +# endif /* MACOS */ + +# ifdef __linux__ +#include "nhmd4.h" +/* v0 is just to suppress compiler warnings about unreachable code */ +# define HASH_CONTEXTPTR(CTXP) \ + volatile int v0 = 0; \ + unsigned char tmp[NHMD4_DIGEST_LENGTH]; \ + NHMD4_CTX CTXP ## _; \ + NHMD4_CTX *CTXP = &CTXP ## _ +# define HASH_INIT(ctxp) (nhmd4_init(ctxp), v0) +# define HASH_UPDATE(ctx, ptr, len) (nhmd4_update(ctx, ptr, len), v0) +# define HASH_FINISH(ctxp) (nhmd4_final(ctxp, tmp), v0) +# define HASH_RESULT_SIZE(ctxp) NHMD4_RESULTLEN +# define HASH_RESULT(ctx, inp) *inp = tmp +# define HASH_CLEANUP(ctxp) +# define HASH_OFLAGS O_RDONLY +# define HASH_BINFILE_DECL char binfile[PATH_MAX+1]; +# define HASH_BINFILE() \ + int len = readlink("/proc/self/exe", binfile, sizeof binfile - 1); \ + if (len > 0) { \ + binfile[len] = '\0'; \ + } else { \ + goto skip; \ + } +# endif // __linux__ + +# ifdef WIN32 +/* WIN32 takes too much code and is dependent on OS includes we can't + * pull in here, so we call out to code in sys/windows/windsys.c */ +# define HASH_CONTEXTPTR(CTXP) +# define HASH_INIT(ctxp) win32_cr_helper('i', ctxp, NULL, 0) +# define HASH_UPDATE(ctxp, ptr, len) win32_cr_helper('u', ctxp, ptr, len) +# define HASH_FINISH(ctxp) win32_cr_helper('f', ctxp, NULL, 0) +# define HASH_CLEANUP(ctxp) win32_cr_helper('c', ctxp, NULL, 0) +# define HASH_RESULT_SIZE(ctxp) win32_cr_helper('s', ctxp, NULL, 0) +# define HASH_RESULT(ctxp, inp) win32_cr_helper('r', ctxp, inp, 0) +# define HASH_OFLAGS _O_RDONLY | _O_BINARY +# define HASH_BINFILE_DECL char *binfile; +# define HASH_BINFILE() \ + if (win32_cr_helper('b', NULL, &binfile, 0)) { \ + goto skip; \ + } +# endif // WIN32 + +/* Binary ID - Use only as a hint to contact.html for recognizing our own + binaries. This is easily spoofed! */ +static char bid[40]; + +/* ARGSUSED */ +void +crashreport_init(int argc, char *argv[]) +{ + static int once = 0; + if (once++) /* NetHackW.exe calls us twice */ + return; + HASH_BINFILE_DECL; + HASH_PRAGMA_START + HASH_CONTEXTPTR(ctxp); + if (HASH_INIT(ctxp)) + goto skip; + HASH_BINFILE(); /* Does "goto skip" on error. */ + + int fd = open(binfile, HASH_OFLAGS, 0); + if (fd == -1) { +# ifdef BETA + raw_printf("open e=%s", strerror(errno)); +# endif + goto skip; + } + + int segsize; + unsigned char segment[4096]; + + while (0 < (segsize = read(fd, segment, sizeof segment))) { + if (HASH_UPDATE(ctxp, segment, segsize)) + goto skip; + } + if (segsize < 0) { + close(fd); + goto skip; + } + if (HASH_FINISH(ctxp)) + goto skip; + close(fd); + + static const char hexdigits[] = "0123456789abcdef"; + char *p = bid; + unsigned char *in; + HASH_RESULT(ctxp, &in); + uint8 cnt = (uint8) HASH_RESULT_SIZE(ctxp); + /* Just in case, make sure not to overflow the bid buffer. + Divide size by 2 because each octet in the hash uses two slots + in bid[] when formatted as a pair of hexadecimal digits. */ + if (cnt >= (uint8) sizeof bid / 2) + cnt = (uint8) sizeof bid / 2 - 1; + while (cnt) { + /* sprintf(p, "%02x", *in++), p += 2; */ + *p++ = hexdigits[(*in >> 4) & 0x0f]; + *p++ = hexdigits[*in++ & 0x0f]; + --cnt; + } + *p = '\0'; + HASH_CLEANUP(ctxp); + return; + + skip: + Strcpy(bid, "unknown"); + HASH_CLEANUP(ctxp); + HASH_PRAGMA_END + nhUse(argc); + nhUse(argv); +} + +#undef HASH_CONTEXTPTR +#undef HASH_INIT +#undef HASH_UPDATE +#undef HASH_FINISH +#undef HASH_CLEANUP +#undef HASH_RESULT +#undef HASH_RESULT_SIZE +#undef HASH_PRAGMA_START +#undef HASH_PRAGMA_END +#undef HASH_BINFILE_DECL +#undef HASH_BINFILE + +void +crashreport_bidshow(void) +{ +# if defined(WIN32) && !defined(WIN32CON) + if (0 == win32_cr_helper('D', ctxp, bid, 0)) +# endif + { + raw_print(bid); +# ifdef WIN32notyet + wait_synch(); +# endif + } +} + +/* Build a URL with a query string and try to launch a new browser window + * to report from panic() or impossible(). Requires libc support for + * the stacktrace. Uses memory on the stack to avoid memory allocation + * (on most platforms) (but libc can still do anything it wants). */ + +// No theoretial limit for URL length but reality is messy. +// This should work on all modern platforms. +# ifndef MAX_URL +# define MAX_URL 8192 +# endif +# ifndef SWR_FRAMES +# define SWR_FRAMES 20 +# endif + +// mark holds the initial eos; if we can't get the value in +// then we can remove the whole item if desired. For other +// semantics, caller can handle mark. +#define SWR_ADD(str) \ + utmp = strlen(str); \ + mark = uend; \ + if (utmp >= urem) \ + goto full; \ + memcpy(uend, str, utmp); \ + uend += utmp; urem -= utmp; \ + *uend = '\0'; + +// NB: on overflow this rolls us back to mark, so if we don't +// want to roll back to the last SWR_ADD, update mark before +// calling this macro. +#define SWR_ADD_URIcoded(str) \ + if (swr_add_uricoded(str, &uend, &urem, mark)) \ + goto full; + +/* On overflow, truncate to markp (but only if markp != NULL). */ +boolean +swr_add_uricoded( + const char *in, + char **out, + int *remaining, + char *markp) +{ + while (*in) { + if (isalnum(*in) || strchr("_-.~", *in)) { + **out = *in; + (*out)++; + (*remaining)--; + } else if (*in == ' ') { + **out = '+'; + (*out)++; + (*remaining)--; + } else { + if (*remaining <= 3) { + if (markp) + *out = markp, *remaining = 0; + **out = '\0'; + return TRUE; + } else { + char chr[40]; /* [4] should suffice */ + int x; + + Sprintf(chr, "%%%02X", *in); + x = (int) strlen(chr); + if (x <= *remaining) { + Strcpy(*out, chr); + *out += x; + *remaining -= x; + } + } + } + in++; + if (!*remaining) { + if (markp) + *out = markp, *remaining = 0; + **out = '\0'; + return TRUE; + } + **out = '\0'; + } + return FALSE; /* normal return */ +} + +static char url[MAX_URL]; // XXX too bad this isn't allocated as needed +static int urem = MAX_URL; // adjusted for gc.crash_urlmax below +static char *uend = url; +static int utmp; // used inside macros +static char *mark; // holds previous terminator (generally) + +boolean +submit_web_report(int cos, const char *msg, const char *why) +{ + urem = (gc.crash_urlmax < 0 || gc.crash_urlmax > MAX_URL) + ? MAX_URL : min(MAX_URL,gc.crash_urlmax); + char temp[200]; + char temp2[200]; + int countpp = 0; /* pre and post traceback lines */ +// URL loaded for creating reports to the NetHack DevTeam +// CRASHREPORTURL=https://nethack.org/links/cr-5.0.0.html + if (!sysopt.crashreporturl) + return FALSE; + SWR_ADD(sysopt.crashreporturl); + /* + * Note: all snprintf() calls here changed to sprintf() to avoid + * complaints from static analyzer. All but one were unnecessary + * since they were formatting int or unsigned into a large buffer. + */ + /* cos - operation, v - version */ + Sprintf(temp, "?cos=%d&v=1", cos); + SWR_ADD(temp); + + /* msg==NULL for #bugreport */ + if (msg) { + SWR_ADD("&subject="); + Sprintf(temp, "%.40s report for NetHack %.40s", + msg, version_string(temp2, sizeof temp2 )); + SWR_ADD_URIcoded(temp); + } + + SWR_ADD("&gitver="); + SWR_ADD_URIcoded(getversionstring(temp2, sizeof temp2)); + + if (gc.crash_name) { + SWR_ADD("&name="); + SWR_ADD_URIcoded(gc.crash_name); + } + + if(gc.crash_email) { + SWR_ADD("&email="); + SWR_ADD_URIcoded(gc.crash_email); + } + // hardware: leave for user + // software: leave for user + // comments: leave for user + + SWR_ADD("&details="); + if (why) { + SWR_ADD_URIcoded(why); + SWR_ADD_URIcoded("\n"); + mark = uend; + countpp++; + } + + SWR_ADD_URIcoded("bid: "); + SWR_ADD_URIcoded(bid); + SWR_ADD_URIcoded("\n"); + mark = uend; + countpp++; + + int count = 0; + if (cos == 1) { +# ifdef WIN32 + count = win32_cr_gettrace(SWR_FRAMES, uend, MAX_URL - (uend - url)); + uend = eos(url); +# else + void *bt[SWR_FRAMES]; + int x; + char **info; + + count = backtrace(bt, SIZE(bt)); + info = backtrace_symbols(bt, count); + for (x = 0; x < count; x++) { + copynchars(temp, info[x], (int) sizeof temp - 1 - 1); /* \n\0 */ + /* try to remove up to 16 blank spaces by removing 8 twice */ + (void) strsubst(temp, " ", ""); + (void) strsubst(temp, " ", ""); + (void) strncat(temp, "\n", sizeof temp - 1); +# if 0 // __linux__ +// not needed for MacOS +// XXX is it actually needed for linux? TBD + Sprintf(temp2, "[%02lu]\n", (unsigned long) x); + uend--; // remove the \n we added above + SWR_ADD_URIcoded(temp2); +# endif // linux + SWR_ADD_URIcoded(temp); + mark = uend; + } +# endif // !WIN32 + } + +# ifdef DUMPLOG_CORE + // config.h turns this on, but make it easy to turn off if needed + if (cos == 1) { + int k; + SWR_ADD_URIcoded("Latest messages:\n"); + mark=uend; + countpp++; + for (k = 0; k < 5; k++) { + const char *line = get_saved_pline(k); + if (!line) + break; + SWR_ADD_URIcoded(line); + SWR_ADD_URIcoded("\n"); + countpp++; + mark = uend; + } + } +# endif /* DUMPLOG_CORE */ + + // detailrows: Guess since we can't know the + // width of the window. + SWR_ADD("&detailrows="); + Sprintf(temp, "%d", min(count + countpp, 30)); + SWR_ADD_URIcoded(temp); + + full: + ; +//printf("URL=%ld '%s'\n",strlen(url),url); +# ifdef WIN32 + int *rv = win32_cr_shellexecute(url); +// XXX TESTING +printf("ShellExecute returned: %p\n",rv); // >32 is ok +# else /* !WIN32 */ + const char *xargv[] = { + CRASHREPORT, + url, + NULL + }; + int pid = fork(); + extern char **environ; + if (pid == 0) { + char err[400]; +# ifdef CRASHREPORT_EXEC_NOSTDERR + int devnull; + /* Keep the output clean - firefox spews useless errors on + * my system. */ + (void) close(2); + devnull = open("/dev/null", O_WRONLY); +# endif + + (void) execve(CRASHREPORT, (char * const *) xargv, environ); + Sprintf(err, "Can't start " CRASHREPORT ": %.*s", + (int) (sizeof err + - sizeof "Can't start " CRASHREPORT ": "), + strerror(errno)); + raw_print(err); +# ifdef CRASHREPORT_EXEC_NOSTDERR + (void) close(devnull); +# endif + exit(1); + } else { + int status; + errno = 0; + (void) waitpid(pid, &status, 0); + if (status) { /* XXX check could be more precise */ +# ifdef BETA + /* Not useful at the moment. XXX */ + char err[100]; + + Sprintf(err, "pid=%d e=%d status=%0x", wpid, errno, status); + raw_print(err); +# endif + return FALSE; + } + } + /* free(info); -- Don't risk it. */ +# endif /* !WIN32 */ + return TRUE; +} + +int +dobugreport(void) +{ + if (!submit_web_report(2, NULL, "#bugreport command")) { + pline("Unable to send bug report. Please visit %s instead.", + (sysopt.crashreporturl && *sysopt.crashreporturl) + ? sysopt.crashreporturl + : DEVTEAM_URL + ); + } + return ECMD_OK; +} + +#undef SWR_ADD +#undef SWR_ADD_URIcoded +#undef SWR_FRAMES +#undef SWR_HDR +#undef SWR_LINES + +#endif /* CRASHREPORT */ + +#ifdef PANICTRACE + +/*ARGSUSED*/ +boolean +NH_panictrace_libc(void) +{ +# if 0 /* XXX how did this get left here? */ + if (submit_web_report("Panic", why)) + return TRUE; +# endif + +# ifdef PANICTRACE_LIBC + void *bt[20]; + int count, x; + char **info, buf[BUFSZ]; + + raw_print(" Generating more information you may report:\n"); + count = backtrace(bt, SIZE(bt)); + info = backtrace_symbols(bt, count); + for (x = 0; x < count; x++) { + copynchars(buf, info[x], (int) sizeof buf - 1); + /* try to remove up to 16 blank spaces by removing 8 twice */ + (void) strsubst(buf, " ", ""); + (void) strsubst(buf, " ", ""); + raw_printf("[%02lu] %s", (unsigned long) x, buf); + } + /* free(info); -- Don't risk it. */ + return TRUE; +# else + return FALSE; +# endif /* !PANICTRACE_LIBC */ +} + +/* + * fooPATH file system path for foo + * fooVAR (possibly const) variable containing fooPATH + */ +# ifdef PANICTRACE_GDB +# ifdef SYSCF +# define GDBVAR sysopt.gdbpath +# define GREPVAR sysopt.greppath +# else /* SYSCF */ +# define GDBVAR GDBPATH +# define GREPVAR GREPPATH +# endif /* SYSCF */ +# endif /* PANICTRACE_GDB */ + +boolean +NH_panictrace_gdb(void) +{ +# ifdef PANICTRACE_GDB + /* A (more) generic method to get a stack trace - invoke + * gdb on ourself. */ + const char *gdbpath = GDBVAR; + const char *greppath = GREPVAR; + char buf[BUFSZ]; + FILE *gdb; + + if (gdbpath == NULL || gdbpath[0] == 0) + return FALSE; + if (greppath == NULL || greppath[0] == 0) + return FALSE; + + Snprintf(buf, sizeof buf, "%s -n -q %s %d 2>&1 | %s '^#'", + gdbpath, ARGV0, getpid(), greppath); + gdb = popen(buf, "w"); + if (gdb) { + raw_print(" Generating more information you may report:\n"); + (void) fprintf(gdb, "bt\nquit\ny"); + (void) fflush(gdb); + sleep(4); /* ugly */ + (void) pclose(gdb); + return TRUE; + } else { + return FALSE; + } +# else + return FALSE; +# endif /* !PANICTRACE_GDB */ +} + +#ifdef DUMPLOG_CORE +#define USED_if_dumplog +#else +#define USED_if_dumplog UNUSED +#endif + +/* lineno==0 gives the most recent message (e.g. + "Do you want to call panic..." if called from #panic) */ +const char * +get_saved_pline(int lineno USED_if_dumplog) +{ +#ifdef DUMPLOG_CORE + int p; + int limit = DUMPLOG_MSG_COUNT; + + if (lineno >= DUMPLOG_MSG_COUNT) + return NULL; + p = (gs.saved_pline_index - 1) % DUMPLOG_MSG_COUNT; + + while (limit--) { + if (gs.saved_plines[p]) { /* valid line */ + if (lineno--) { + p = (p - 1 + DUMPLOG_MSG_COUNT) % DUMPLOG_MSG_COUNT; + } else { + return gs.saved_plines[p]; + } + } + } +#endif /* DUMPLOG_CORE */ + return NULL; +} + +#undef USED_if_dumplog + +# ifndef NO_SIGNAL +/* called as signal() handler, so sent at least one arg */ +/*ARGUSED*/ +void +panictrace_handler(int sig_unused UNUSED) +{ +#define SIG_MSG "\nSignal received.\n" + int f2; + +# ifdef CURSES_GRAPHICS + if (iflags.window_inited && WINDOWPORT(curses)) { + extern void curses_uncurse_terminal(void); /* wincurs.h */ + + /* it is risky calling this during a program-terminating signal, + but without it the subsequent backtrace is useless because + that ends up being scrawled all over the screen; call is + here rather than in NH_abort() because panic() calls both + exit_nhwindows(), which makes this same call under curses, + then NH_abort() and we don't want to call this twice */ + curses_uncurse_terminal(); + } +# endif + + f2 = (int) write(2, SIG_MSG, sizeof SIG_MSG - 1); + nhUse(f2); /* what could we do if write to fd#2 (stderr) fails */ + NH_abort(NULL); /* ... and we're already in the process of quitting? */ +} + +void +panictrace_setsignals(boolean set) +{ +#define SETSIGNAL(sig) \ + (void) signal(sig, set ? (SIG_RET_TYPE) panictrace_handler : SIG_DFL); +# ifdef SIGILL + SETSIGNAL(SIGILL); +# endif +# ifdef SIGTRAP + SETSIGNAL(SIGTRAP); +# endif +# ifdef SIGIOT + SETSIGNAL(SIGIOT); +# endif +# ifdef SIGBUS + SETSIGNAL(SIGBUS); +# endif +# ifdef SIGFPE + SETSIGNAL(SIGFPE); +# endif +# ifdef SIGSEGV + SETSIGNAL(SIGSEGV); +# endif +# ifdef SIGSTKFLT + SETSIGNAL(SIGSTKFLT); +# endif +# ifdef SIGSYS + SETSIGNAL(SIGSYS); +# endif +# ifdef SIGEMT + SETSIGNAL(SIGEMT); +# endif +#undef SETSIGNAL +} +# endif /* NO_SIGNAL */ +#endif /* PANICTRACE */ + +/* + * FIXME: this should have a lot of '#undef's for onefile support. + */ + +/*report.c*/ diff --git a/src/restore.c b/src/restore.c index 8c8703ba8..fab825de3 100644 --- a/src/restore.c +++ b/src/restore.c @@ -1,10 +1,9 @@ -/* NetHack 3.6 restore.c $NHDT-Date: 1575245087 2019/12/02 00:04:47 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.136 $ */ +/* NetHack 5.0 restore.c $NHDT-Date: 1736530208 2025/01/10 09:30:08 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.234 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Michael Allison, 2009. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -#include "lev.h" #include "tcap.h" /* for TERMLIB and ASCIIGRAPH */ #if defined(MICRO) @@ -12,50 +11,32 @@ extern int dotcnt; /* shared with save */ extern int dotrow; /* shared with save */ #endif -#ifdef USE_TILES -extern void FDECL(substitute_tiles, (d_level *)); /* from tile.c */ +staticfn void find_lev_obj(void); +staticfn void restlevchn(NHFILE *); +staticfn void restdamage(NHFILE *); +staticfn void restobj(NHFILE *, struct obj *); +staticfn struct obj *restobjchn(NHFILE *, boolean); +staticfn void restmon(NHFILE *, struct monst *); +staticfn struct monst *restmonchn(NHFILE *); +staticfn struct fruit *loadfruitchn(NHFILE *); +staticfn void freefruitchn(struct fruit *); +staticfn void rest_levl(NHFILE *); +staticfn void rest_stairs(NHFILE *); +#ifndef SFCTOOL +staticfn void ghostfruit(struct obj *); +staticfn boolean restgamestate(NHFILE *); +staticfn void restlevelstate(void); +staticfn int restlevelfile(xint8); +staticfn void rest_bubbles(NHFILE *); +staticfn void restore_gamelog(NHFILE *); +staticfn void reset_oattached_mids(boolean); +/* these ones are declared non-static in extern.h if SFCTOOL is defined */ +staticfn boolean restgamestate(NHFILE *); +staticfn void rest_bubbles(NHFILE *); +staticfn void restore_gamelog(NHFILE *); +staticfn void restore_msghistory(NHFILE *); #endif -#ifdef ZEROCOMP -STATIC_DCL void NDECL(zerocomp_minit); -STATIC_DCL void FDECL(zerocomp_mread, (int, genericptr_t, unsigned int)); -STATIC_DCL int NDECL(zerocomp_mgetc); -#endif - -STATIC_DCL void NDECL(def_minit); -STATIC_DCL void FDECL(def_mread, (int, genericptr_t, unsigned int)); - -STATIC_DCL void NDECL(find_lev_obj); -STATIC_DCL void FDECL(restlevchn, (int)); -STATIC_DCL void FDECL(restdamage, (int, BOOLEAN_P)); -STATIC_DCL void FDECL(restobj, (int, struct obj *)); -STATIC_DCL struct obj *FDECL(restobjchn, (int, BOOLEAN_P, BOOLEAN_P)); -STATIC_OVL void FDECL(restmon, (int, struct monst *)); -STATIC_DCL struct monst *FDECL(restmonchn, (int, BOOLEAN_P)); -STATIC_DCL struct fruit *FDECL(loadfruitchn, (int)); -STATIC_DCL void FDECL(freefruitchn, (struct fruit *)); -STATIC_DCL void FDECL(ghostfruit, (struct obj *)); -STATIC_DCL boolean FDECL(restgamestate, (int, unsigned int *, unsigned int *)); -STATIC_DCL void FDECL(restlevelstate, (unsigned int, unsigned int)); -STATIC_DCL int FDECL(restlevelfile, (int, XCHAR_P)); -STATIC_OVL void FDECL(restore_msghistory, (int)); -STATIC_DCL void FDECL(reset_oattached_mids, (BOOLEAN_P)); -STATIC_DCL void FDECL(rest_levl, (int, BOOLEAN_P)); - -static struct restore_procs { - const char *name; - int mread_flags; - void NDECL((*restore_minit)); - void FDECL((*restore_mread), (int, genericptr_t, unsigned int)); - void FDECL((*restore_bclose), (int)); -} restoreprocs = { -#if !defined(ZEROCOMP) || (defined(COMPRESS) || defined(ZLIB_COMP)) - "externalcomp", 0, def_minit, def_mread, def_bclose, -#else - "zerocomp", 0, zerocomp_minit, zerocomp_mread, zerocomp_bclose, -#endif -}; - /* * Save a mapping of IDs from ghost levels to the current level. This * map is used by the timer routines when restoring ghost levels. @@ -69,36 +50,33 @@ struct bucket { } map[N_PER_BUCKET]; }; -STATIC_DCL void NDECL(clear_id_mapping); -STATIC_DCL void FDECL(add_id_mapping, (unsigned, unsigned)); - -static int n_ids_mapped = 0; -static struct bucket *id_map = 0; +#ifndef SFCTOOL +staticfn void clear_id_mapping(void); +staticfn void add_id_mapping(unsigned, unsigned); +#endif /* SFCTOOL */ #ifdef AMII_GRAPHICS -void FDECL(amii_setpens, (int)); /* use colors from save file */ +void amii_setpens(int); /* use colors from save file */ extern int amii_numcolors; #endif #include "display.h" -boolean restoring = FALSE; -static NEARDATA struct fruit *oldfruit; -static NEARDATA long omoves; - #define Is_IceBox(o) ((o)->otyp == ICE_BOX ? TRUE : FALSE) -/* Recalculate level.objects[x][y], since this info was not saved. */ -STATIC_OVL void -find_lev_obj() +#ifndef SFCTOOL +/* Recalculate svl.level.objects[x][y], since this info was not saved. */ + +staticfn void +find_lev_obj(void) { - register struct obj *fobjtmp = (struct obj *) 0; - register struct obj *otmp; + struct obj *fobjtmp = (struct obj *) 0; + struct obj *otmp; int x, y; for (x = 0; x < COLNO; x++) for (y = 0; y < ROWNO; y++) - level.objects[x][y] = (struct obj *) 0; + svl.level.objects[x][y] = (struct obj *) 0; /* * Reverse the entire fobj chain, which is necessary so that we can @@ -113,10 +91,18 @@ find_lev_obj() } /* fobj should now be empty */ - /* Set level.objects (as well as reversing the chain back again) */ + /* Set svl.level.objects (as well as reversing the chain back again) */ while ((otmp = fobjtmp) != 0) { fobjtmp = otmp->nobj; place_object(otmp, otmp->ox, otmp->oy); + + /* fixup(s) performed when restoring the level that the hero + is on, rather than just an arbitrary one */ + if (u.uz.dlevel) { /* 0 during full restore until current level */ + /* handle uchain and uball when they're on the floor */ + if (otmp->owornmask & (W_BALL | W_CHAIN)) + setworn(otmp, otmp->owornmask); + } } } @@ -124,12 +110,11 @@ find_lev_obj() * infamous "HUP" cheat) get used up here. */ void -inven_inuse(quietly) -boolean quietly; +inven_inuse(boolean quietly) { - register struct obj *otmp, *otmp2; + struct obj *otmp, *otmp2; - for (otmp = invent; otmp; otmp = otmp2) { + for (otmp = gi.invent; otmp; otmp = otmp2) { otmp2 = otmp->nobj; if (otmp->in_use) { if (!quietly) @@ -139,22 +124,24 @@ boolean quietly; } } -STATIC_OVL void -restlevchn(fd) -register int fd; +#endif /* SFCTOOL */ + +staticfn void +restlevchn(NHFILE *nhfp) { - int cnt; + int cnt = 0; s_level *tmplev, *x; - sp_levchn = (s_level *) 0; - mread(fd, (genericptr_t) &cnt, sizeof(int)); + svs.sp_levchn = (s_level *) 0; + Sfi_int(nhfp, &cnt, "levchn-lev_count"); for (; cnt > 0; cnt--) { tmplev = (s_level *) alloc(sizeof(s_level)); - mread(fd, (genericptr_t) tmplev, sizeof(s_level)); - if (!sp_levchn) - sp_levchn = tmplev; + Sfi_s_level(nhfp, tmplev, "levchn-s_level"); + + if (!svs.sp_levchn) + svs.sp_levchn = tmplev; else { - for (x = sp_levchn; x->next; x = x->next) + for (x = svs.sp_levchn; x->next; x = x->next) ; x->next = tmplev; } @@ -162,59 +149,44 @@ register int fd; } } -STATIC_OVL void -restdamage(fd, ghostly) -int fd; -boolean ghostly; +staticfn void +restdamage(NHFILE *nhfp) { + unsigned int dmgcount = 0; int counter; struct damage *tmp_dam; +#ifndef SFCTOOL + boolean ghostly = (nhfp->ftype == NHF_BONESFILE); +#endif + + Sfi_unsigned(nhfp, &dmgcount, "damage-damage_count"); + counter = (int) dmgcount; - mread(fd, (genericptr_t) &counter, sizeof(counter)); if (!counter) return; - tmp_dam = (struct damage *) alloc(sizeof(struct damage)); - while (--counter >= 0) { - char damaged_shops[5], *shp = (char *) 0; + do { + tmp_dam = (struct damage *) alloc(sizeof *tmp_dam); - mread(fd, (genericptr_t) tmp_dam, sizeof(*tmp_dam)); + Sfi_damage(nhfp, tmp_dam, "damage"); +#ifndef SFCTOOL if (ghostly) - tmp_dam->when += (monstermoves - omoves); - Strcpy(damaged_shops, - in_rooms(tmp_dam->place.x, tmp_dam->place.y, SHOPBASE)); - if (u.uz.dlevel) { - /* when restoring, there are two passes over the current - * level. the first time, u.uz isn't set, so neither is - * shop_keeper(). just wait and process the damage on - * the second pass. - */ - for (shp = damaged_shops; *shp; shp++) { - struct monst *shkp = shop_keeper(*shp); + tmp_dam->when += (svm.moves - svo.omoves); - if (shkp && inhishop(shkp) - && repair_damage(shkp, tmp_dam, (int *) 0, TRUE)) - break; - } - } - if (!shp || !*shp) { - tmp_dam->next = level.damagelist; - level.damagelist = tmp_dam; - tmp_dam = (struct damage *) alloc(sizeof(*tmp_dam)); - } - } - free((genericptr_t) tmp_dam); + tmp_dam->next = svl.level.damagelist; + svl.level.damagelist = tmp_dam; +#endif /* !SFCTOOL */ + } while (--counter > 0); } /* restore one object */ -STATIC_OVL void -restobj(fd, otmp) -int fd; -struct obj *otmp; +staticfn void +restobj(NHFILE *nhfp, struct obj *otmp) { - int buflen; - - mread(fd, (genericptr_t) otmp, sizeof(struct obj)); + int buflen = 0; + unsigned omid = 0; + Sfi_obj(nhfp, otmp, "obj"); + otmp->lua_ref_cnt = 0; /* next object pointers are invalid; otmp->cobj needs to be left as is--being non-null is key to restoring container contents */ otmp->nobj = otmp->nexthere = (struct obj *) 0; @@ -223,66 +195,65 @@ struct obj *otmp; otmp->oextra = newoextra(); /* oname - object's name */ - mread(fd, (genericptr_t) &buflen, sizeof(buflen)); + Sfi_int(nhfp, &buflen, "obj-oname_length"); if (buflen > 0) { /* includes terminating '\0' */ new_oname(otmp, buflen); - mread(fd, (genericptr_t) ONAME(otmp), buflen); + Sfi_char(nhfp, ONAME(otmp), "obj-oname", buflen); } + /* omonst - corpse or statue might retain full monster details */ - mread(fd, (genericptr_t) &buflen, sizeof(buflen)); + Sfi_int(nhfp, &buflen, "obj-omonst_length"); if (buflen > 0) { newomonst(otmp); /* this is actually a monst struct, so we can just defer to restmon() here */ - restmon(fd, OMONST(otmp)); - } - /* omid - monster id number, connecting corpse to ghost */ - mread(fd, (genericptr_t) &buflen, sizeof(buflen)); - if (buflen > 0) { - newomid(otmp); - mread(fd, (genericptr_t) OMID(otmp), buflen); - } - /* olong - temporary gold */ - mread(fd, (genericptr_t) &buflen, sizeof(buflen)); - if (buflen > 0) { - newolong(otmp); - mread(fd, (genericptr_t) OLONG(otmp), buflen); + restmon(nhfp, OMONST(otmp)); } + /* omailcmd - feedback mechanism for scroll of mail */ - mread(fd, (genericptr_t) &buflen, sizeof(buflen)); + Sfi_int(nhfp, &buflen, "obj-omailcmd_length"); if (buflen > 0) { char *omailcmd = (char *) alloc(buflen); - mread(fd, (genericptr_t) omailcmd, buflen); + Sfi_char(nhfp, omailcmd, "obj-omailcmd", buflen); new_omailcmd(otmp, omailcmd); free((genericptr_t) omailcmd); } + + /* omid - monster id number, connecting corpse to ghost */ + newomid(otmp); /* superfluous; we're already allocated otmp->oextra */ + Sfi_unsigned(nhfp, &omid, "obj-omid"); + OMID(otmp) = omid; } } -STATIC_OVL struct obj * -restobjchn(fd, ghostly, frozen) -register int fd; -boolean ghostly, frozen; +staticfn struct obj * +restobjchn(NHFILE *nhfp, boolean frozen) { - register struct obj *otmp, *otmp2 = 0; - register struct obj *first = (struct obj *) 0; - int buflen; + struct obj *otmp, *otmp2 = 0; + struct obj *first = (struct obj *) 0; + int buflen = 0; +#ifndef SFCTOOL + boolean ghostly = (nhfp->ftype == NHF_BONESFILE); +#endif while (1) { - mread(fd, (genericptr_t) &buflen, sizeof buflen); + Sfi_int(nhfp, &buflen, "obj-obj_length"); if (buflen == -1) break; otmp = newobj(); - restobj(fd, otmp); + assert(otmp != 0); + restobj(nhfp, otmp); if (!first) first = otmp; else otmp2->nobj = otmp; +#ifndef SFCTOOL if (ghostly) { - unsigned nid = context.ident++; + unsigned nid = next_ident(); + add_id_mapping(otmp->o_id, nid); otmp->o_id = nid; } @@ -293,68 +264,51 @@ boolean ghostly, frozen; * immediately after old player died. */ if (ghostly && !frozen && !age_is_relative(otmp)) - otmp->age = monstermoves - omoves + otmp->age; + otmp->age = svm.moves - svo.omoves + otmp->age; +#endif /* !SFCTOOL */ /* get contents of a container or statue */ if (Has_contents(otmp)) { struct obj *otmp3; - otmp->cobj = restobjchn(fd, ghostly, Is_IceBox(otmp)); + otmp->cobj = restobjchn(nhfp, Is_IceBox(otmp)); /* restore container back pointers */ for (otmp3 = otmp->cobj; otmp3; otmp3 = otmp3->nobj) otmp3->ocontainer = otmp; - } else if (SchroedingersBox(otmp)) { - struct obj *catcorpse; - - /* - * TODO: Remove this after 3.6.x save compatibility is dropped. - * - * As of 3.6.2, SchroedingersBox() always has a cat corpse in it. - * For 3.6.[01], it was empty and its weight was falsified - * to have the value it would have had if there was one inside. - * Put a non-rotting cat corpse in this box to convert to 3.6.2. - * - * [Note: after this fix up, future save/restore of this object - * will take the Has_contents() code path above.] - */ - if ((catcorpse = mksobj(CORPSE, TRUE, FALSE)) != 0) { - otmp->spe = 1; /* flag for special SchroedingersBox */ - set_corpsenm(catcorpse, PM_HOUSECAT); - (void) stop_timer(ROT_CORPSE, obj_to_any(catcorpse)); - add_to_container(otmp, catcorpse); - otmp->owt = weight(otmp); - } } + +#ifndef SFCTOOL if (otmp->bypass) otmp->bypass = 0; if (!ghostly) { /* fix the pointers */ - if (otmp->o_id == context.victual.o_id) - context.victual.piece = otmp; - if (otmp->o_id == context.tin.o_id) - context.tin.tin = otmp; - if (otmp->o_id == context.spbook.o_id) - context.spbook.book = otmp; + if (otmp->o_id == svc.context.victual.o_id) + svc.context.victual.piece = otmp; + if (otmp->o_id == svc.context.tin.o_id) + svc.context.tin.tin = otmp; + if (otmp->o_id == svc.context.spbook.o_id) + svc.context.spbook.book = otmp; } +#endif /* !SFADUSTER */ otmp2 = otmp; } if (first && otmp2->nobj) { impossible("Restobjchn: error reading objchn."); otmp2->nobj = 0; } - +#ifdef SFCTOOL + nhUse(frozen); +#endif return first; } /* restore one monster */ -STATIC_OVL void -restmon(fd, mtmp) -int fd; -struct monst *mtmp; +staticfn void +restmon(NHFILE *nhfp, struct monst *mtmp) { - int buflen; + int buflen = 0, mc = 0; - mread(fd, (genericptr_t) mtmp, sizeof(struct monst)); + Sfi_monst(nhfp, mtmp, "monst"); /* next monster pointer is invalid */ mtmp->nmon = (struct monst *) 0; @@ -362,90 +316,119 @@ struct monst *mtmp; if (mtmp->mextra) { mtmp->mextra = newmextra(); - /* mname - monster's name */ - mread(fd, (genericptr_t) &buflen, sizeof(buflen)); + /* mgivenname - monster's name */ + Sfi_int(nhfp, &buflen, "monst-mgivenname_length"); if (buflen > 0) { /* includes terminating '\0' */ - new_mname(mtmp, buflen); - mread(fd, (genericptr_t) MNAME(mtmp), buflen); + new_mgivenname(mtmp, buflen); + Sfi_char(nhfp, MGIVENNAME(mtmp), "monst-mgivenname", (int) buflen); } /* egd - vault guard */ - mread(fd, (genericptr_t) &buflen, sizeof(buflen)); + Sfi_int(nhfp, &buflen, "monst-egd_length"); if (buflen > 0) { newegd(mtmp); - mread(fd, (genericptr_t) EGD(mtmp), sizeof(struct egd)); + Sfi_egd(nhfp, EGD(mtmp), "monst-egd"); } /* epri - temple priest */ - mread(fd, (genericptr_t) &buflen, sizeof(buflen)); + Sfi_int(nhfp, &buflen, "monst-epri_length"); if (buflen > 0) { newepri(mtmp); - mread(fd, (genericptr_t) EPRI(mtmp), sizeof(struct epri)); + Sfi_epri(nhfp, EPRI(mtmp), "monst-epri"); } /* eshk - shopkeeper */ - mread(fd, (genericptr_t) &buflen, sizeof(buflen)); + Sfi_int(nhfp, &buflen, "monst-eshk_length"); if (buflen > 0) { neweshk(mtmp); - mread(fd, (genericptr_t) ESHK(mtmp), sizeof(struct eshk)); + Sfi_eshk(nhfp, ESHK(mtmp), "monst-eshk"); } /* emin - minion */ - mread(fd, (genericptr_t) &buflen, sizeof(buflen)); + Sfi_int(nhfp, &buflen, "monst-emin_length"); if (buflen > 0) { newemin(mtmp); - mread(fd, (genericptr_t) EMIN(mtmp), sizeof(struct emin)); + Sfi_emin(nhfp, EMIN(mtmp), "monst-emin"); } /* edog - pet */ - mread(fd, (genericptr_t) &buflen, sizeof(buflen)); + Sfi_int(nhfp, &buflen, "monst-edog_length"); if (buflen > 0) { newedog(mtmp); - mread(fd, (genericptr_t) EDOG(mtmp), sizeof(struct edog)); + Sfi_edog(nhfp, EDOG(mtmp), "monst-edog"); + /* save or bones held a relative time */ + relative_time_to_moves(&EDOG(mtmp)->droptime); + relative_time_to_moves(&EDOG(mtmp)->hungrytime); + /* sanity check to prevent rn2(0) */ + if (EDOG(mtmp)->apport <= 0) { + EDOG(mtmp)->apport = 1; + } + } + /* ebones */ + Sfi_int(nhfp, &buflen, "monst-ebones_length"); + if (buflen > 0) { + newebones(mtmp); + Sfi_ebones(nhfp, EBONES(mtmp), "monst-ebones"); } /* mcorpsenm - obj->corpsenm for mimic posing as corpse or statue (inline int rather than pointer to something) */ - mread(fd, (genericptr_t) &MCORPSENM(mtmp), sizeof MCORPSENM(mtmp)); + Sfi_int(nhfp, &mc, "monst-mcorpsenm"); + MCORPSENM(mtmp) = mc; } /* mextra */ } -STATIC_OVL struct monst * -restmonchn(fd, ghostly) -register int fd; -boolean ghostly; +staticfn struct monst * +restmonchn(NHFILE *nhfp) { - register struct monst *mtmp, *mtmp2 = 0; - register struct monst *first = (struct monst *) 0; - int offset, buflen; + struct monst *mtmp, *mtmp2 = 0; + struct monst *first = (struct monst *) 0; + int buflen = 0; +#ifndef SFCTOOL + int offset; + boolean ghostly = (nhfp->ftype == NHF_BONESFILE); +#endif while (1) { - mread(fd, (genericptr_t) &buflen, sizeof(buflen)); + Sfi_int(nhfp, &buflen, "monst-monst_length"); if (buflen == -1) break; mtmp = newmonst(); - restmon(fd, mtmp); + assert(mtmp != 0); + restmon(nhfp, mtmp); if (!first) first = mtmp; else mtmp2->nmon = mtmp; +#ifndef SFCTOOL if (ghostly) { - unsigned nid = context.ident++; + unsigned nid = next_ident(); + add_id_mapping(mtmp->m_id, nid); mtmp->m_id = nid; } offset = mtmp->mnum; mtmp->data = &mons[offset]; if (ghostly) { - int mndx = monsndx(mtmp->data); + int mndx = (mtmp->cham == NON_PM) ? monsndx(mtmp->data) + : mtmp->cham; + if (propagate(mndx, TRUE, ghostly) == 0) { /* cookie to trigger purge in getbones() */ mtmp->mhpmax = DEFUNCT_MONSTER; } } +#endif /* !SFCTOOL */ + if (mtmp->minvent) { struct obj *obj; - mtmp->minvent = restobjchn(fd, ghostly, FALSE); + mtmp->minvent = restobjchn(nhfp, FALSE); +#ifndef SFCTOOL /* restore monster back pointer */ for (obj = mtmp->minvent; obj; obj = obj->nobj) obj->ocarry = mtmp; +#else + nhUse(obj); +#endif } + +#ifndef SFCTOOL if (mtmp->mw) { struct obj *obj; @@ -466,39 +449,44 @@ boolean ghostly; restpriest(mtmp, ghostly); if (!ghostly) { - if (mtmp->m_id == context.polearm.m_id) - context.polearm.hitmon = mtmp; + if (mtmp->m_id == svc.context.polearm.m_id) + svc.context.polearm.hitmon = mtmp; } +#endif /* !SFCTOOL */ mtmp2 = mtmp; } +#ifndef SFCTOOL if (first && mtmp2->nmon) { impossible("Restmonchn: error reading monchn."); mtmp2->nmon = 0; } +#endif return first; } -STATIC_OVL struct fruit * -loadfruitchn(fd) -int fd; +staticfn struct fruit * +loadfruitchn(NHFILE *nhfp) { - register struct fruit *flist, *fnext; + struct fruit *flist, *fnext; flist = 0; - while (fnext = newfruit(), mread(fd, (genericptr_t) fnext, sizeof *fnext), - fnext->fid != 0) { - fnext->nextf = flist; - flist = fnext; + for (;;) { + fnext = newfruit(); + Sfi_fruit(nhfp, fnext, "fruit"); + if (fnext->fid != 0) { + fnext->nextf = flist; + flist = fnext; + } else + break; } dealloc_fruit(fnext); return flist; } -STATIC_OVL void -freefruitchn(flist) -register struct fruit *flist; +staticfn void +freefruitchn(struct fruit *flist) { - register struct fruit *fnext; + struct fruit *fnext; while (flist) { fnext = flist->nextf; @@ -507,13 +495,13 @@ register struct fruit *flist; } } -STATIC_OVL void -ghostfruit(otmp) -register struct obj *otmp; +#ifndef SFCTOOL +staticfn void +ghostfruit(struct obj *otmp) { - register struct fruit *oldf; + struct fruit *oldf; - for (oldf = oldfruit; oldf; oldf = oldf->nextf) + for (oldf = go.oldfruit; oldf; oldf = oldf->nextf) if (oldf->fid == otmp->spe) break; @@ -522,6 +510,7 @@ register struct obj *otmp; else otmp->spe = fruitadd(oldf->fname, (struct fruit *) 0); } +#endif /* !SFCTOOL */ #ifdef SYSCF #define SYSOPT_CHECK_SAVE_UID sysopt.check_save_uid @@ -529,37 +518,47 @@ register struct obj *otmp; #define SYSOPT_CHECK_SAVE_UID TRUE #endif -STATIC_OVL +#ifndef SFCTOOL +staticfn +#endif boolean -restgamestate(fd, stuckid, steedid) -register int fd; -unsigned int *stuckid, *steedid; +restgamestate(NHFILE *nhfp) { + int i; struct flag newgameflags; -#ifdef SYSFLAGS - struct sysflag newgamesysflags; -#endif struct context_info newgamecontext; /* all 0, but has some pointers */ - struct obj *otmp; struct obj *bc_obj; char timebuf[15]; - unsigned long uid; - boolean defer_perm_invent; + unsigned long uid = 0; +#ifndef SFCTOOL + boolean defer_perm_invent, restoring_special; + struct obj *otmp; +#endif - mread(fd, (genericptr_t) &uid, sizeof uid); + Sfi_ulong(nhfp, &uid, "gamestate-uid"); + Sfi_char(nhfp, &svn.nhuuid[0], "nhuuid", sizeof svn.nhuuid); + Sfi_long(nhfp, &svm.moves, "gamestate-moves"); +#ifndef SFCTOOL if (SYSOPT_CHECK_SAVE_UID && uid != (unsigned long) getuid()) { /* strange ... */ - /* for wizard mode, issue a reminder; for others, treat it - as an attempt to cheat and refuse to restore this file */ - pline("Saved game was not yours."); - if (!wizard) + if (!gc.converted_savefile_loaded) + /* for wizard mode, issue a reminder; for others, treat it + * as an attempt to cheat and refuse to restore this file */ + pline("Saved game was not yours."); + if (wizard || gc.converted_savefile_loaded) { + if (gc.converted_savefile_loaded) + gc.converted_savefile_loaded = FALSE; + } else { return FALSE; + } } - - newgamecontext = context; /* copy statically init'd context */ - mread(fd, (genericptr_t) &context, sizeof (struct context_info)); - context.warntype.species = (context.warntype.speciesidx >= LOW_PM) - ? &mons[context.warntype.speciesidx] +#endif /* SFCTOOL */ + newgamecontext = svc.context; /* copy statically init'd context */ + Sfi_context_info(nhfp, &svc.context, "gamestate-context"); + relative_time_to_moves(&svc.context.seer_turn); + relative_time_to_moves(&svc.context.digging.lastdigtime); + svc.context.warntype.species = (ismnum(svc.context.warntype.speciesidx)) + ? &mons[svc.context.warntype.speciesidx] : (struct permonst *) 0; /* context.victual.piece, .tin.tin, .spellbook.book, and .polearm.hitmon are pointers which get set to Null during save and will be recovered @@ -569,13 +568,15 @@ unsigned int *stuckid, *steedid; file option values instead of keeping old save file option values if partial restore fails and we resort to starting a new game */ newgameflags = flags; - mread(fd, (genericptr_t) &flags, sizeof (struct flag)); + Sfi_flag(nhfp, &flags, "gamestate-flags"); + +#ifndef SFCTOOL /* avoid keeping permanent inventory window up to date during restore (setworn() calls update_inventory); attempting to include the cost of unpaid items before shopkeeper's bill is available is a no-no; named fruit names aren't accessible yet either [3.6.2: moved perm_invent from flags to iflags to keep it out of - save files; retaining the override here is simpler than trying to + save files; retaining the override here is simpler than trying to figure out where it really belongs now] */ defer_perm_invent = iflags.perm_invent; iflags.perm_invent = FALSE; @@ -584,32 +585,42 @@ unsigned int *stuckid, *steedid; in the discover case, we don't want to set that for a normal game until after the save file has been removed */ iflags.deferred_X = (newgameflags.explore && !discover); + restoring_special = (wizard || discover); if (newgameflags.debug) { /* authorized by startup code; wizard mode exists and is allowed */ wizard = TRUE, discover = iflags.deferred_X = FALSE; - } else if (wizard) { - /* specified by save file; check authorization now */ + } else if (restoring_special) { + /* specified by save file; check authorization now. */ set_playmode(); } -#ifdef SYSFLAGS - newgamesysflags = sysflags; - mread(fd, (genericptr_t) &sysflags, sizeof(struct sysflag)); -#endif - role_init(); /* Reset the initial role, race, gender, and alignment */ #ifdef AMII_GRAPHICS amii_setpens(amii_numcolors); /* use colors from save file */ #endif - mread(fd, (genericptr_t) &u, sizeof(struct you)); +#endif /* !SFCTOOL */ + Sfi_long(nhfp, &svw.wreserve, "wreserve"); + Sfi_int32(nhfp, &svw.wtreserved, "wtreserved"); + Sfi_you(nhfp, &u, "gamestate-you"); + gy.youmonst.cham = u.mcham; + +#ifndef SFCTOOL + if (restoring_special && iflags.explore_error_flag) { + /* savefile has wizard or explore mode, but player is no longer + authorized to access either; can't downgrade mode any further, so + fail restoration. */ + u.uhp = 0; + } +#endif -#define ReadTimebuf(foo) \ - mread(fd, (genericptr_t) timebuf, 14); \ - timebuf[14] = '\0'; \ - foo = time_from_yyyymmddhhmmss(timebuf); + Sfi_char(nhfp, timebuf, "gamestate-ubirthday", 14); + timebuf[14] = '\0'; + ubirthday = time_from_yyyymmddhhmmss(timebuf); + Sfi_long(nhfp, &urealtime.realtime, "gamestate-realtime"); + Sfi_char(nhfp, timebuf, "gamestate-start_timing", 14); + timebuf[14] = '\0'; +#ifndef SFCTOOL + urealtime.start_timing = time_from_yyyymmddhhmmss(timebuf); - ReadTimebuf(ubirthday); - mread(fd, &urealtime.realtime, sizeof urealtime.realtime); - ReadTimebuf(urealtime.start_timing); /** [not used] **/ /* current time is the time to use for next urealtime.realtime update */ urealtime.start_timing = getnow(); @@ -630,45 +641,50 @@ unsigned int *stuckid, *steedid; iflags.deferred_X = FALSE; iflags.perm_invent = defer_perm_invent; flags = newgameflags; -#ifdef SYSFLAGS - sysflags = newgamesysflags; -#endif - context = newgamecontext; + svc.context = newgamecontext; + gy.youmonst = cg.zeromonst; return FALSE; } /* in case hangup save occurred in midst of level change */ assign_level(&u.uz0, &u.uz); +#endif /* !SFCTOOL */ /* this stuff comes after potential aborted restore attempts */ - restore_killers(fd); - restore_timers(fd, RANGE_GLOBAL, FALSE, 0L); - restore_light_sources(fd); - invent = restobjchn(fd, FALSE, FALSE); + restore_killers(nhfp); + restore_timers(nhfp, RANGE_GLOBAL, 0L); + restore_light_sources(nhfp); + + gi.invent = restobjchn(nhfp, FALSE); /* restore dangling (not on floor or in inventory) ball and/or chain */ - bc_obj = restobjchn(fd, FALSE, FALSE); + bc_obj = restobjchn(nhfp, FALSE); +#ifndef SFCTOOL while (bc_obj) { struct obj *nobj = bc_obj->nobj; + bc_obj->nobj = (struct obj *) 0; if (bc_obj->owornmask) setworn(bc_obj, bc_obj->owornmask); - bc_obj->nobj = (struct obj *) 0; bc_obj = nobj; } +#endif + gm.migrating_objs = restobjchn(nhfp, FALSE); + gm.migrating_mons = restmonchn(nhfp); - migrating_objs = restobjchn(fd, FALSE, FALSE); - migrating_mons = restmonchn(fd, FALSE); - mread(fd, (genericptr_t) mvitals, sizeof mvitals); + for (i = 0; i < NUMMONS; ++i) { + Sfi_mvitals(nhfp, &svm.mvitals[i], "gamestate-mvitals"); + } +#ifndef SFCTOOL /* * There are some things after this that can have unintended display * side-effects too early in the game. * Disable see_monsters() here, re-enable it at the top of moveloop() */ - defer_see_monsters = TRUE; + gd.defer_see_monsters = TRUE; /* this comes after inventory has been loaded */ - for (otmp = invent; otmp; otmp = otmp->nobj) + for (otmp = gi.invent; otmp; otmp = otmp->nobj) if (otmp->owornmask) setworn(otmp, otmp->owornmask); @@ -680,151 +696,144 @@ unsigned int *stuckid, *steedid; uwep = 0; /* clear it and have setuwep() reinit */ setuwep(otmp); /* (don't need any null check here) */ if (!uwep || uwep->otyp == PICK_AXE || uwep->otyp == GRAPPLING_HOOK) - unweapon = TRUE; - - restore_dungeon(fd); - restlevchn(fd); - mread(fd, (genericptr_t) &moves, sizeof moves); - mread(fd, (genericptr_t) &monstermoves, sizeof monstermoves); - mread(fd, (genericptr_t) &quest_status, sizeof (struct q_score)); - mread(fd, (genericptr_t) spl_book, (MAXSPELL + 1) * sizeof (struct spell)); - restore_artifacts(fd); - restore_oracles(fd); - if (u.ustuck) - mread(fd, (genericptr_t) stuckid, sizeof *stuckid); - if (u.usteed) - mread(fd, (genericptr_t) steedid, sizeof *steedid); - mread(fd, (genericptr_t) pl_character, sizeof pl_character); - - mread(fd, (genericptr_t) pl_fruit, sizeof pl_fruit); - freefruitchn(ffruit); /* clean up fruit(s) made by initoptions() */ - ffruit = loadfruitchn(fd); - - restnames(fd); - restore_waterlevel(fd); - restore_msghistory(fd); + gu.unweapon = TRUE; +#endif /* !SFCTOOL */ + + restore_dungeon(nhfp); + restlevchn(nhfp); + /* hero_seq isn't saved and restored because it can be recalculated */ + gh.hero_seq = svm.moves << 3; /* normally handled in moveloop() */ + Sfi_q_score(nhfp, &svq.quest_status, "gamestate-quest_status"); + + for (i = 0; i < (MAXSPELL + 1); ++i) { + Sfi_spell(nhfp, &svs.spl_book[i], "gamestate-spl_book"); + } + restore_artifacts(nhfp); + restore_oracles(nhfp); + Sfi_char(nhfp, svp.pl_character, + "gamestate-pl_character", sizeof svp.pl_character); + Sfi_char(nhfp, svp.pl_fruit, "gamestate-pl_fruit", sizeof svp.pl_fruit); + freefruitchn(gf.ffruit); /* clean up fruit(s) made by initoptions() */ + gf.ffruit = loadfruitchn(nhfp); + + restnames(nhfp); + restore_msghistory(nhfp); + restore_gamelog(nhfp); + restore_luadata(nhfp); +#ifndef SFCTOOL /* must come after all mons & objs are restored */ relink_timers(FALSE); relink_light_sources(FALSE); + adj_erinys(u.ualign.abuse); /* inventory display is now viable */ iflags.perm_invent = defer_perm_invent; +#else + nhUse(bc_obj); + nhUse(newgamecontext); + nhUse(newgameflags); +#endif /* !SFCTOOL */ return TRUE; } +#ifndef SFCTOOL /* update game state pointers to those valid for the current level (so we - * don't dereference a wild u.ustuck when saving the game state, for instance) - */ -STATIC_OVL void -restlevelstate(stuckid, steedid) -unsigned int stuckid, steedid; + don't dereference a wild u.ustuck when saving game state, for instance) */ +staticfn void +restlevelstate(void) { - register struct monst *mtmp; - - if (stuckid) { - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) - if (mtmp->m_id == stuckid) - break; - if (!mtmp) - panic("Cannot find the monster ustuck."); - u.ustuck = mtmp; - } - if (steedid) { - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) - if (mtmp->m_id == steedid) - break; - if (!mtmp) - panic("Cannot find the monster usteed."); - u.usteed = mtmp; - remove_monster(mtmp->mx, mtmp->my); - } + /* + * Note: restoring steed and engulfer/holder/holdee is now handled + * in getlev() and there's nothing left for restlevelstate() to do. + */ + return; } +/* after getlev(), put current level into a level/lock file; + essential when splitting a save file into individual level files */ /*ARGSUSED*/ -STATIC_OVL int -restlevelfile(fd, ltmp) -int fd; /* fd used in MFLOPPY only */ -xchar ltmp; +staticfn int +restlevelfile(xint8 ltmp) { - int nfd; char whynot[BUFSZ]; + NHFILE *nhfp = (NHFILE *) 0; -#ifndef MFLOPPY - nhUse(fd); -#endif - nfd = create_levelfile(ltmp, whynot); - if (nfd < 0) { - /* BUG: should suppress any attempt to write a panic - save file if file creation is now failing... */ + nhfp = create_levelfile(ltmp, whynot); + if (!nhfp) { + /* failed to create a new file; don't attempt to make a panic save */ + program_state.something_worth_saving = 0; panic("restlevelfile: %s", whynot); } -#ifdef MFLOPPY - if (!savelev(nfd, ltmp, COUNT_SAVE)) { - /* The savelev can't proceed because the size required - * is greater than the available disk space. - */ - pline("Not enough space on `%s' to restore your game.", levels); - - /* Remove levels and bones that may have been created. - */ - (void) nhclose(nfd); -#ifdef AMIGA - clearlocks(); -#else /* !AMIGA */ - eraseall(levels, alllevels); - eraseall(levels, allbones); - - /* Perhaps the person would like to play without a - * RAMdisk. - */ - if (ramdisk) { - /* PlaywoRAMdisk may not return, but if it does - * it is certain that ramdisk will be 0. - */ - playwoRAMdisk(); - /* Rewind save file and try again */ - (void) lseek(fd, (off_t) 0, 0); - (void) validate(fd, (char *) 0); /* skip version etc */ - return dorecover(fd); /* 0 or 1 */ - } -#endif /* ?AMIGA */ - pline("Be seeing you..."); - nh_terminate(EXIT_SUCCESS); - } -#endif /* MFLOPPY */ - bufon(nfd); - savelev(nfd, ltmp, WRITE_SAVE | FREE_SAVE); - bclose(nfd); + bufon(nhfp->fd); + nhfp->mode = WRITING | FREEING; + savelev(nhfp, ltmp); + close_nhfile(nhfp); return 2; } +/* + * restore_saved_game() prior to this left us at this position in + * the savefile for dorecover(): + * + * format indicator (1 byte) + * n = count of critical size list (1 byte) + * n bytes of critical sizes (n bytes) + * version info + * --> plnametmp = player name size (int, 2 bytes) + * player name (PL_NSIZ_PLUS) + * current level (including pets) + * (non-level-based) game state + * other levels + */ + int -dorecover(fd) -register int fd; +dorecover(NHFILE *nhfp) { - unsigned int stuckid = 0, steedid = 0; /* not a register */ - xchar ltmp; + xint8 ltmp = 0; int rtmp; - struct obj *otmp; - restoring = TRUE; - get_plname_from_file(fd, plname); - getlev(fd, 0, (xchar) 0, FALSE); - if (!restgamestate(fd, &stuckid, &steedid)) { + /* suppress map display if some part of the code tries to update that */ + program_state.restoring = REST_GSTATE; + + get_plname_from_file(nhfp, svp.plname, TRUE); + /* + * The position in the save file is now here: + * + * format indicator (1 byte) + * n = count of critical size list (1 byte) + * n bytes of critical sizes (n bytes) + * version info + * plnametmp = player name size (int, 2 bytes) + * player name (PL_NSIZ_PLUS) + * --> current level (including pets) + * (non-level-based) game state + * other levels + */ + getlev(nhfp, 0, (xint8) 0); + if (!restgamestate(nhfp)) { + NHFILE *tnhfp = get_freeing_nhfile(); + display_nhwindow(WIN_MESSAGE, TRUE); - savelev(-1, 0, FREE_SAVE); /* discard current level */ - (void) nhclose(fd); + savelev(tnhfp, 0); /* discard current level */ + close_nhfile(tnhfp); + close_nhfile(nhfp); (void) delete_savefile(); - restoring = FALSE; + u.usteed_mid = u.ustuck_mid = 0; + program_state.restoring = 0; return 0; } - restlevelstate(stuckid, steedid); + /* after restgamestate() -> restnames() so that 'bases[]' is populated */ + init_oclass_probs(); /* recompute go.oclass_prob_totals[] */ + + restlevelstate(); #ifdef INSURANCE savestateinlock(); #endif - rtmp = restlevelfile(fd, ledger_no(&u.uz)); + rtmp = restlevelfile(ledger_no(&u.uz)); if (rtmp < 2) return rtmp; /* dorecover called recursively */ + program_state.restoring = REST_LEVELS; + /* these pointers won't be valid while we're processing the * other levels, but they'll be reset again by restlevelstate() * afterwards, and in the meantime at least u.usteed may mislead @@ -837,7 +846,7 @@ register int fd; #ifdef AMII_GRAPHICS { extern struct window_procs amii_procs; - if (WINDOWPORT("amii") { + if (WINDOWPORT(amii)) { extern winid WIN_BASE; clear_nhwindow(WIN_BASE); /* hack until there's a hook for this */ } @@ -847,72 +856,56 @@ register int fd; #endif clear_nhwindow(WIN_MESSAGE); You("return to level %d in %s%s.", depth(&u.uz), - dungeons[u.uz.dnum].dname, + svd.dungeons[u.uz.dnum].dname, flags.debug ? " while in debug mode" : flags.explore ? " while in explore mode" : ""); curs(WIN_MAP, 1, 1); dotcnt = 0; dotrow = 2; - if (!WINDOWPORT("X11")) + if (!WINDOWPORT(X11)) putstr(WIN_MAP, 0, "Restoring:"); #endif - restoreprocs.mread_flags = 1; /* return despite error */ + restoreinfo.mread_flags = 1; /* return despite error */ while (1) { - mread(fd, (genericptr_t) <mp, sizeof ltmp); - if (restoreprocs.mread_flags == -1) + Sfi_xint8(nhfp, <mp, "gamestate-level_number"); + if (nhfp->eof) break; - getlev(fd, 0, ltmp, FALSE); + getlev(nhfp, 0, ltmp); #ifdef MICRO curs(WIN_MAP, 1 + dotcnt++, dotrow); if (dotcnt >= (COLNO - 1)) { dotrow++; dotcnt = 0; } - if (!WINDOWPORT("X11")) { + if (!WINDOWPORT(X11)) { putstr(WIN_MAP, 0, "."); } mark_synch(); #endif - rtmp = restlevelfile(fd, ltmp); + rtmp = restlevelfile(ltmp); if (rtmp < 2) return rtmp; /* dorecover called recursively */ } - restoreprocs.mread_flags = 0; + restoreinfo.mread_flags = 0; -#ifdef BSD - (void) lseek(fd, 0L, 0); -#else - (void) lseek(fd, (off_t) 0, 0); -#endif - (void) validate(fd, (char *) 0); /* skip version and savefile info */ - get_plname_from_file(fd, plname); + rewind_nhfile(nhfp); /* return to beginning of file */ + (void) validate(nhfp, (char *) 0, FALSE); + get_plname_from_file(nhfp, svp.plname, TRUE); - getlev(fd, 0, (xchar) 0, FALSE); - (void) nhclose(fd); + /* not 0 nor REST_GSTATE nor REST_LEVELS */ + program_state.restoring = REST_CURRENT_LEVEL; - /* Now set the restore settings to match the - * settings used by the save file output routines - */ - reset_restpref(); - - restlevelstate(stuckid, steedid); + getlev(nhfp, 0, (xint8) 0); + close_nhfile(nhfp); + restlevelstate(); program_state.something_worth_saving = 1; /* useful data now exists */ if (!wizard && !discover) (void) delete_savefile(); if (Is_rogue_level(&u.uz)) assign_graphics(ROGUESET); -#ifdef USE_TILES - substitute_tiles(&u.uz); -#endif -#ifdef MFLOPPY - gameDiskPrompt(); -#endif - max_rank_sz(); /* to recompute mrank_sz (botl.c) */ - /* take care of iron ball & chain */ - for (otmp = fobj; otmp; otmp = otmp->nobj) - if (otmp->owornmask) - setworn(otmp, otmp->owornmask); + reset_glyphmap(gm_levelchange); + max_rank_sz(); /* to recompute gm.mrank_sz (botl.c) */ if ((uball && !uchain) || (uchain && !uball)) { impossible("restgamestate: lost ball & chain"); @@ -928,16 +921,27 @@ register int fd; */ inven_inuse(FALSE); - load_qtlist(); /* re-load the quest text info */ /* Set up the vision internals, after levl[] data is loaded but before docrt(). */ reglyph_darkroom(); vision_reset(); - vision_full_recalc = 1; /* recompute vision (not saved) */ + gv.vision_full_recalc = 1; /* recompute vision (not saved) */ run_timers(); /* expire all timers that have gone off while away */ + program_state.restoring = 0; /* affects bot() so clear before docrt() */ + + if (ge.early_raw_messages && !program_state.beyond_savefile_load) { + /* + * We're about to obliterate some potentially important + * startup messages, so give the player a chance to see them. + */ + ge.early_raw_messages = 0; + wait_synch(); + } + u.usteed_mid = u.ustuck_mid = 0; + program_state.beyond_savefile_load = 1; + docrt(); - restoring = FALSE; clear_nhwindow(WIN_MESSAGE); /* Success! */ @@ -945,116 +949,143 @@ register int fd; check_special_room(FALSE); return 1; } +#endif /* !SFCTOOL */ + +staticfn void +rest_stairs(NHFILE *nhfp) +{ + int buflen = 0; + stairway stway = UNDEFINED_VALUES; +#ifndef SFCTOOL + stairway *newst; +#endif + +#ifndef SFCTOOL + stairway_free_all(); +#endif + while (1) { + Sfi_int(nhfp, &buflen, "stairs-staircount"); + if (buflen == -1) + break; + + Sfi_stairway(nhfp, &stway, "stairs-stairway"); + if (program_state.restoring != REST_GSTATE + && stway.tolev.dnum == u.uz.dnum) { + /* stairway dlevel is relative, make it absolute */ + stway.tolev.dlevel += u.uz.dlevel; + } +#ifndef SFCTOOL + stairway_add(stway.sx, stway.sy, stway.up, stway.isladder, + &(stway.tolev)); + newst = stairway_at(stway.sx, stway.sy); + if (newst) + newst->u_traversed = stway.u_traversed; +#endif + } +} void -restcemetery(fd, cemeteryaddr) -int fd; -struct cemetery **cemeteryaddr; +restcemetery(NHFILE *nhfp, struct cemetery **cemeteryaddr) { struct cemetery *bonesinfo, **bonesaddr; - int flag; + int cflag = 0; - mread(fd, (genericptr_t) &flag, sizeof flag); - if (flag == 0) { + Sfi_int(nhfp, &cflag, "cemetery-cemetery_flag"); + if (cflag == 0) { bonesaddr = cemeteryaddr; do { bonesinfo = (struct cemetery *) alloc(sizeof *bonesinfo); - mread(fd, (genericptr_t) bonesinfo, sizeof *bonesinfo); + Sfi_cemetery(nhfp, bonesinfo, "cemetery-bonesinfo"); *bonesaddr = bonesinfo; bonesaddr = &(*bonesaddr)->next; } while (*bonesaddr); } else { *cemeteryaddr = 0; } + if (((nhfp->mode & CONVERTING) != 0) + || ((nhfp->mode & UNCONVERTING) != 0)) { + struct cemetery *thisbones, *nextbones; + + /* free the memory */ + nextbones = *cemeteryaddr; + while ((thisbones = nextbones) != 0) { + nextbones = thisbones->next; + free((genericptr_t)thisbones); + } + *cemeteryaddr = 0; + } } /*ARGSUSED*/ -STATIC_OVL void -rest_levl(fd, rlecomp) -int fd; -boolean rlecomp; +staticfn void +rest_levl(NHFILE *nhfp) { -#ifdef RLECOMP - short i, j; - uchar len; - struct rm r; - - if (rlecomp) { - (void) memset((genericptr_t) &r, 0, sizeof(r)); - i = 0; - j = 0; - len = 0; - while (i < ROWNO) { - while (j < COLNO) { - if (len > 0) { - levl[j][i] = r; - len -= 1; - j += 1; - } else { - mread(fd, (genericptr_t) &len, sizeof(uchar)); - mread(fd, (genericptr_t) &r, sizeof(struct rm)); - } - } - j = 0; - i += 1; + int c, r; + + for (c = 0; c < COLNO; ++c) { + for (r = 0; r < ROWNO; ++r) { + Sfi_rm(nhfp, &levl[c][r], "location-rm"); } - return; } -#else /* !RLECOMP */ - nhUse(rlecomp); -#endif /* ?RLECOMP */ - mread(fd, (genericptr_t) levl, sizeof levl); } +#ifndef SFCTOOL + void -trickery(reason) -char *reason; +trickery(char *reason) { pline("Strange, this map is not as I remember it."); pline("Somebody is trying some trickery here..."); pline("This game is void."); - Strcpy(killer.name, reason ? reason : ""); + Strcpy(svk.killer.name, reason ? reason : ""); done(TRICKED); } +#endif /* !SFCTOOL */ void -getlev(fd, pid, lev, ghostly) -int fd, pid; -xchar lev; -boolean ghostly; +getlev(NHFILE *nhfp, int pid, xint8 lev) { - register struct trap *trap; - register struct monst *mtmp; - long elapsed; + struct trap *trap; +#ifndef SFCTOOL + struct monst *mtmp; branch *br; - int hpid; - xchar dlvl; int x, y; +#endif + long elapsed = 0L; + int hpid = 0; + xint8 dlvl = 0; + int i, c, r; + boolean ghostly = (nhfp->ftype == NHF_BONESFILE); + coord *tmpc = 0; #ifdef TOS short tlev; #endif + program_state.in_getlev = TRUE; +#ifndef SFCTOOL + if (ghostly) clear_id_mapping(); +#endif /* !SFCTOOL */ +#if 0 #if defined(MSDOS) || defined(OS2) - setmode(fd, O_BINARY); + if (nhfp->structlevel) + setmode(nhfp->fd, O_BINARY); +#endif #endif + /* Load the old fruit info. We have to do it first, so the * information is available when restoring the objects. */ if (ghostly) - oldfruit = loadfruitchn(fd); + go.oldfruit = loadfruitchn(nhfp); /* First some sanity checks */ - mread(fd, (genericptr_t) &hpid, sizeof(hpid)); + Sfi_int(nhfp, &hpid, "gamestate-hackpid"); /* CHECK: This may prevent restoration */ -#ifdef TOS - mread(fd, (genericptr_t) &tlev, sizeof(tlev)); - dlvl = tlev & 0x00ff; -#else - mread(fd, (genericptr_t) &dlvl, sizeof(dlvl)); -#endif + Sfi_xint8(nhfp, &dlvl, "gamestate-dlvl"); +#ifndef SFCTOOL if ((pid && pid != hpid) || (lev && dlvl != lev)) { char trickbuf[BUFSZ]; @@ -1067,71 +1098,116 @@ boolean ghostly; pline1(trickbuf); trickery(trickbuf); } - restcemetery(fd, &level.bonesinfo); - rest_levl(fd, - (boolean) ((sfrestinfo.sfi1 & SFI1_RLECOMP) == SFI1_RLECOMP)); - mread(fd, (genericptr_t) lastseentyp, sizeof(lastseentyp)); - mread(fd, (genericptr_t) &omoves, sizeof(omoves)); - elapsed = monstermoves - omoves; - mread(fd, (genericptr_t) &upstair, sizeof(stairway)); - mread(fd, (genericptr_t) &dnstair, sizeof(stairway)); - mread(fd, (genericptr_t) &upladder, sizeof(stairway)); - mread(fd, (genericptr_t) &dnladder, sizeof(stairway)); - mread(fd, (genericptr_t) &sstairs, sizeof(stairway)); - mread(fd, (genericptr_t) &updest, sizeof(dest_area)); - mread(fd, (genericptr_t) &dndest, sizeof(dest_area)); - mread(fd, (genericptr_t) &level.flags, sizeof(level.flags)); - mread(fd, (genericptr_t) doors, sizeof(doors)); - rest_rooms(fd); /* No joke :-) */ - if (nroom) - doorindex = rooms[nroom - 1].fdoor + rooms[nroom - 1].doorct; - else - doorindex = 0; - - restore_timers(fd, RANGE_LEVEL, ghostly, elapsed); - restore_light_sources(fd); - fmon = restmonchn(fd, ghostly); - - rest_worm(fd); /* restore worm information */ - ftrap = 0; - while (trap = newtrap(), - mread(fd, (genericptr_t) trap, sizeof(struct trap)), - trap->tx != 0) { /* need "!= 0" to work around DICE 3.0 bug */ - trap->ntrap = ftrap; - ftrap = trap; +#endif + restcemetery(nhfp, &svl.level.bonesinfo); + rest_levl(nhfp); + + for (c = 0; c < COLNO; ++c) { + for (r = 0; r < ROWNO; ++r) { + Sfi_schar(nhfp, &svl.lastseentyp[c][r], "lastseentyp"); + } + } + Sfi_long(nhfp, &svo.omoves, "lev-timestmp"); + elapsed = (svm.moves - svo.omoves); + + rest_stairs(nhfp); + Sfi_dest_area(nhfp, &svu.updest, "lev-updest"); + Sfi_dest_area(nhfp, &svd.dndest, "lev-dndest"); + Sfi_levelflags(nhfp, &svl.level.flags, "lev-level_flags"); + rest_adjust_levelflags(); + if (svd.doors) { + free(svd.doors); + svd.doors = 0; + } + + Sfi_int(nhfp, &svd.doors_alloc, "lev-doors_alloc"); + if (svd.doors_alloc) { /* avoid pointless alloc(0) */ + svd.doors = (coord *) alloc(svd.doors_alloc * sizeof (coord)); + tmpc = svd.doors; + for (i = 0; i < svd.doors_alloc; ++i) { + Sfi_nhcoord(nhfp, tmpc, "lev-doors"); + tmpc++; + } + } + rest_rooms(nhfp); /* No joke :-) */ + if (svn.nroom) { +#ifndef SFCTOOL + gd.doorindex = svr.rooms[svn.nroom - 1].fdoor + + svr.rooms[svn.nroom - 1].doorct; + } else { + gd.doorindex = 0; +#else + gd.doorindex = 0; +#endif /* !SFCTOOL */ + } + + restore_timers(nhfp, RANGE_LEVEL, elapsed); + restore_light_sources(nhfp); + fmon = restmonchn(nhfp); + rest_worm(nhfp); /* restore worm information */ + + gf.ftrap = 0; + for (;;) { + trap = newtrap(); + Sfi_trap(nhfp, trap, "trap"); + if (trap->tx != 0) { + if (program_state.restoring != REST_GSTATE + && trap->dst.dnum == u.uz.dnum) { + /* convert relative destination to absolute */ + trap->dst.dlevel += u.uz.dlevel; + } + trap->ntrap = gf.ftrap; + gf.ftrap = trap; + } else + break; } dealloc_trap(trap); - fobj = restobjchn(fd, ghostly, FALSE); + + fobj = restobjchn(nhfp, FALSE); +#ifndef SFCTOOL find_lev_obj(); +#endif /* !SFCTOOL */ /* restobjchn()'s `frozen' argument probably ought to be a callback routine so that we can check for objects being buried under ice */ - level.buriedobjlist = restobjchn(fd, ghostly, FALSE); - billobjs = restobjchn(fd, ghostly, FALSE); - rest_engravings(fd); + svl.level.buriedobjlist = restobjchn(nhfp, FALSE); + gb.billobjs = restobjchn(nhfp, FALSE); + rest_engravings(nhfp); +#ifndef SFCTOOL /* reset level.monsters for new level */ for (x = 0; x < COLNO; x++) for (y = 0; y < ROWNO; y++) - level.monsters[x][y] = (struct monst *) 0; + svl.level.monsters[x][y] = (struct monst *) 0; for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { if (mtmp->isshk) set_residency(mtmp, FALSE); - place_monster(mtmp, mtmp->mx, mtmp->my); - if (mtmp->wormno) - place_wsegs(mtmp, NULL); + if (mtmp->m_id == u.usteed_mid) { + /* steed is kept on fmon list but off the map */ + u.usteed = mtmp; + u.usteed_mid = 0; + } else { + if (mtmp->m_id == u.ustuck_mid) { + set_ustuck(mtmp); + u.ustuck_mid = 0; + } + place_monster(mtmp, mtmp->mx, mtmp->my); + if (mtmp->wormno) + place_wsegs(mtmp, NULL); + if (hides_under(mtmp->data) && mtmp->mundetected) + (void) hideunder(mtmp); + } /* regenerate monsters while on another level */ - if (!u.uz.dlevel) + if (!u.uz.dlevel || program_state.restoring == REST_LEVELS) continue; if (ghostly) { /* reset peaceful/malign relative to new character; shopkeepers will reset based on name */ if (!mtmp->isshk) - mtmp->mpeaceful = - (is_unicorn(mtmp->data) - && sgn(u.ualign.type) == sgn(mtmp->data->maligntyp)) - ? TRUE - : peace_minded(mtmp->data); + mtmp->mpeaceful = (is_unicorn(mtmp->data) + && (sgn(u.ualign.type) + == sgn(mtmp->data->maligntyp))) ? 1 + : peace_minded(mtmp->data); set_malign(mtmp); } else if (elapsed > 0L) { mon_catchup_elapsed_time(mtmp, elapsed); @@ -1140,23 +1216,40 @@ boolean ghostly; them is different now than when the level was saved */ restore_cham(mtmp); /* give hiders a chance to hide before their next move */ - if (ghostly || (elapsed > 00 && elapsed > (long) rnd(10))) + if (ghostly || (elapsed > 0L && elapsed > (long) rnd(10))) hide_monst(mtmp); } +#endif /* !SFCTOOL */ + + restdamage(nhfp); + rest_regions(nhfp); + rest_bubbles(nhfp); /* for water and air; empty marker on other levels */ + load_exclusions(nhfp); + rest_track(nhfp); - restdamage(fd, ghostly); - rest_regions(fd, ghostly); +#ifndef SFCTOOL if (ghostly) { + stairway *stway = gs.stairs; + while (stway) { + if (!stway->isladder && !stway->up + && stway->tolev.dnum == u.uz.dnum) + break; + stway = stway->next; + } + /* Now get rid of all the temp fruits... */ - freefruitchn(oldfruit), oldfruit = 0; + freefruitchn(go.oldfruit), go.oldfruit = 0; if (lev > ledger_no(&medusa_level) - && lev < ledger_no(&stronghold_level) && xdnstair == 0) { + && lev < ledger_no(&stronghold_level) && !stway) { coord cc; + d_level dest; + + dest.dnum = u.uz.dnum; + dest.dlevel = u.uz.dlevel + 1; mazexy(&cc); - xdnstair = cc.x; - ydnstair = cc.y; + stairway_add(cc.x, cc.y, FALSE, FALSE, &dest); levl[cc.x][cc.y].typ = STAIRS; } @@ -1172,11 +1265,18 @@ boolean ghostly; switch (br->type) { case BR_STAIR: case BR_NO_END1: - case BR_NO_END2: /* OK to assign to sstairs if it's not used */ - assign_level(&sstairs.tolev, <mp); + case BR_NO_END2: + stway = gs.stairs; + while (stway) { + if (stway->tolev.dnum != u.uz.dnum) + break; + stway = stway->next; + } + if (stway) + assign_level(&(stway->tolev), <mp); break; case BR_PORTAL: /* max of 1 portal per level */ - for (trap = ftrap; trap; trap = trap->ntrap) + for (trap = gf.ftrap; trap; trap = trap->ntrap) if (trap->ttyp == MAGIC_PORTAL) break; if (!trap) @@ -1188,14 +1288,13 @@ boolean ghostly; struct trap *ttmp = 0; /* Remove any dangling portals. */ - for (trap = ftrap; trap; trap = ttmp) { + for (trap = gf.ftrap; trap; trap = ttmp) { ttmp = trap->ntrap; if (trap->ttyp == MAGIC_PORTAL) deltrap(trap); } } } - /* must come after all mons & objs are restored */ relink_timers(ghostly); relink_light_sources(ghostly); @@ -1203,74 +1302,177 @@ boolean ghostly; if (ghostly) clear_id_mapping(); + program_state.in_getlev = FALSE; +#else + nhUse(pid); + nhUse(lev); +#endif /* !SFCTOOL */ + program_state.in_getlev = FALSE; +} + +void +rest_adjust_levelflags(void) +{ + /* adjust timestamps */ + relative_time_to_moves(&svl.level.flags.stasis_until); +} +void +moves_to_relative_time(long *timestamp) +{ + long prevts = *timestamp; + + *timestamp = prevts - svm.moves; +} + +void +relative_time_to_moves(long *timestamp) +{ + long prevts = *timestamp; + + *timestamp = svm.moves + prevts; } +/* "name-role-race-gend-algn" occurs very early in a save file; sometimes we + want the whole thing, other times just "name" (for svp.plname[]) */ void -get_plname_from_file(fd, plbuf) -int fd; -char *plbuf; +get_plname_from_file( + NHFILE *nhfp, + char *outbuf, /* size must be at least [PL_NSIZ_PLUS] even if name_only */ + boolean name_only) /* True: just name; False: name-role-race-gend-algn */ { + char plbuf[PL_NSIZ_PLUS]; int pltmpsiz = 0; - (void) read(fd, (genericptr_t) &pltmpsiz, sizeof(pltmpsiz)); - (void) read(fd, (genericptr_t) plbuf, pltmpsiz); + + plbuf[0] = '\0'; + + Sfi_int(nhfp, &pltmpsiz, "plname-size"); + /* pltmpsiz should now be PL_NSIZ_PLUS */ + Sfi_char(nhfp, plbuf, "plname", pltmpsiz); + /* plbuf[PL_NSIZ_PLUS-2] should be '\0'; + plbuf[PL_NSIZ_PLUS-1] should be '-' or 'X' or 'D' */ + /* "-race-role-gend-algn" is already present except that it has been + hidden by replacing the initial dash with NUL; if we want that + information, replace the NUL with a dash */ + if (!name_only) + *eos(plbuf) = '-'; + /* not simple strcpy(); playmode is in the last slot and could (probably + will) be preceded by NULs */ + (void) memcpy((genericptr_t) outbuf, (genericptr_t) plbuf, PL_NSIZ_PLUS); return; } -STATIC_OVL void -restore_msghistory(fd) -register int fd; +/* restore Plane of Water's air bubbles and Plane of Air's clouds */ +#ifndef SFCTOOL +staticfn +#endif +void +rest_bubbles(NHFILE *nhfp) +{ + xint8 bbubbly; + + /* whether or not the Plane of Water's air bubbles or Plane of Air's + clouds are present is recorded during save so that we don't have to + know what level is being restored */ + bbubbly = 0; + Sfi_xint8(nhfp, &bbubbly, "bubbles-bbubbly"); +#if 0 + if (nhfp->structlevel) + xxread(nhfp->fd, &bbubbly, sizeof bbubbly); +#endif + if (bbubbly) + restore_waterlevel(nhfp); +} + +#ifndef SFCTOOL +staticfn +#endif +void +restore_gamelog(NHFILE *nhfp) +{ + int slen = 0; + char msg[BUFSZ*2]; + struct gamelog_line tmp = { 0 }; + + while (1) { + Sfi_int(nhfp, &slen, "gamelog-length"); + if (slen == -1) + break; + if (slen > ((BUFSZ*2) - 1)) + panic("restore_gamelog: msg too big (%d)", slen); + Sfi_char(nhfp, msg, "gamelog-gamelog_text", slen); + msg[slen] = '\0'; + Sfi_gamelog_line(nhfp, &tmp, "gamelog-gamelog_line"); +#ifndef SFCTOOL + gamelog_add(tmp.flags, tmp.turn, msg); +#endif /* !SFCTOOL */ + } +} + +#ifndef SFCTOOL +staticfn +#endif +void +restore_msghistory(NHFILE *nhfp) { - int msgsize, msgcount = 0; + int msgsize = 0; +#ifndef SFCTOOL + int msgcount = 0; +#endif char msg[BUFSZ]; while (1) { - mread(fd, (genericptr_t) &msgsize, sizeof(msgsize)); + Sfi_int(nhfp, &msgsize, "msghistory-length"); if (msgsize == -1) break; - if (msgsize > (BUFSZ - 1)) + if (msgsize > BUFSZ - 1) panic("restore_msghistory: msg too big (%d)", msgsize); - mread(fd, (genericptr_t) msg, msgsize); + Sfi_char(nhfp, msg, "msghistory-msg", msgsize); msg[msgsize] = '\0'; +#ifndef SFCTOOL putmsghistory(msg, TRUE); ++msgcount; +#endif /* !SFCTOOL */ } +#ifndef SFCTOOL if (msgcount) putmsghistory((char *) 0, TRUE); debugpline1("Read %d messages from savefile.", msgcount); +#endif /* !SFCTOOL */ } +#ifndef SFCTOOL + /* Clear all structures for object and monster ID mapping. */ -STATIC_OVL void -clear_id_mapping() +staticfn void +clear_id_mapping(void) { struct bucket *curr; - while ((curr = id_map) != 0) { - id_map = curr->next; + while ((curr = gi.id_map) != 0) { + gi.id_map = curr->next; free((genericptr_t) curr); } - n_ids_mapped = 0; + gn.n_ids_mapped = 0; } /* Add a mapping to the ID map. */ -STATIC_OVL void -add_id_mapping(gid, nid) -unsigned gid, nid; +staticfn void +add_id_mapping(unsigned int gid, unsigned int nid) { int idx; - idx = n_ids_mapped % N_PER_BUCKET; + idx = gn.n_ids_mapped % N_PER_BUCKET; /* idx is zero on first time through, as well as when a new bucket is */ /* needed */ if (idx == 0) { struct bucket *gnu = (struct bucket *) alloc(sizeof(struct bucket)); - gnu->next = id_map; - id_map = gnu; + gnu->next = gi.id_map; + gi.id_map = gnu; } - id_map->map[idx].gid = gid; - id_map->map[idx].nid = nid; - n_ids_mapped++; + gi.id_map->map[idx].gid = gid; + gi.id_map->map[idx].nid = nid; + gn.n_ids_mapped++; } /* @@ -1279,17 +1481,16 @@ unsigned gid, nid; * ID. */ boolean -lookup_id_mapping(gid, nidp) -unsigned gid, *nidp; +lookup_id_mapping(unsigned int gid, unsigned int *nidp) { int i; struct bucket *curr; - if (n_ids_mapped) - for (curr = id_map; curr; curr = curr->next) { + if (gn.n_ids_mapped) + for (curr = gi.id_map; curr; curr = curr->next) { /* first bucket might not be totally full */ - if (curr == id_map) { - i = n_ids_mapped % N_PER_BUCKET; + if (curr == gi.id_map) { + i = gn.n_ids_mapped % N_PER_BUCKET; if (i == 0) i = N_PER_BUCKET; } else @@ -1305,12 +1506,12 @@ unsigned gid, *nidp; return FALSE; } -STATIC_OVL void -reset_oattached_mids(ghostly) -boolean ghostly; +staticfn void +reset_oattached_mids(boolean ghostly) { struct obj *otmp; unsigned oldid, nid; + for (otmp = fobj; otmp; otmp = otmp->nobj) { if (ghostly && has_omonst(otmp)) { struct monst *mtmp = OMONST(otmp); @@ -1319,11 +1520,9 @@ boolean ghostly; mtmp->mpeaceful = mtmp->mtame = 0; /* pet's owner died! */ } if (ghostly && has_omid(otmp)) { - (void) memcpy((genericptr_t) &oldid, (genericptr_t) OMID(otmp), - sizeof(oldid)); + oldid = OMID(otmp); if (lookup_id_mapping(oldid, &nid)) - (void) memcpy((genericptr_t) OMID(otmp), (genericptr_t) &nid, - sizeof(nid)); + OMID(otmp) = nid; else free_omid(otmp); } @@ -1332,54 +1531,72 @@ boolean ghostly; #ifdef SELECTSAVED /* put up a menu listing each character from this player's saved games; - returns 1: use plname[], 0: new game, -1: quit */ + returns 1: use svp.plname[], 0: new game, -1: quit */ int -restore_menu(bannerwin) -winid bannerwin; /* if not WIN_ERR, clear window and show copyright in menu */ +restore_menu( + winid bannerwin) /* if not WIN_ERR, clear window + * and show copyright in menu */ { winid tmpwin; anything any; - char **saved; + char **saved, *next, mode, menutext[BUFSZ]; + boolean all_normal; menu_item *chosen_game = (menu_item *) 0; int k, clet, ch = 0; /* ch: 0 => new game */ + int clr = NO_COLOR; - *plname = '\0'; + *svp.plname = '\0'; saved = get_saved_games(); /* array of character names */ if (saved && *saved) { tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - any = zeroany; /* no selection */ + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; /* no selection */ if (bannerwin != WIN_ERR) { /* for tty; erase copyright notice and redo it in the menu */ clear_nhwindow(bannerwin); /* COPYRIGHT_BANNER_[ABCD] */ for (k = 1; k <= 4; ++k) - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - copyright_banner_line(k), MENU_UNSELECTED); - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", - MENU_UNSELECTED); + add_menu_str(tmpwin, copyright_banner_line(k)); + add_menu_str(tmpwin, ""); + } + add_menu_str(tmpwin, "Select one of your saved games"); + /* if all the save files have a playmode of '-' then we'll just list + their character name-role-race-gend-algn values, but if any are + 'X' or 'D', we'll list playmode along with name-role-&c values + for every entry; first, figure out if they're all normal play */ + for (all_normal = TRUE, k = 0; all_normal && saved[k]; ++k) { + next = saved[k]; + mode = next[PL_NSIZ_PLUS - 1]; /* fixed last char, beyond '\0' */ + if (mode != '-') + all_normal = FALSE; } - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, - "Select one of your saved games", MENU_UNSELECTED); for (k = 0; saved[k]; ++k) { any.a_int = k + 1; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, saved[k], - MENU_UNSELECTED); + next = saved[k]; + mode = next[PL_NSIZ_PLUS - 1]; + if (all_normal) + Sprintf(menutext, "%.*s", PL_NSIZ_PLUS - 1, next); + else + Sprintf(menutext, "%c %.*s", mode, PL_NSIZ_PLUS - 1, next); + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr, + menutext, MENU_ITEMFLAGS_SKIPMENUCOLORS); } - clet = (k <= 'n' - 'a') ? 'n' : 0; /* new game */ + clet = (k <= 'n' - 'a') ? 'n' /* new game */ + : (k <= 26 + 'N' - 'A') ? 'N' : 0; any.a_int = -1; /* not >= 0 */ - add_menu(tmpwin, NO_GLYPH, &any, clet, 0, ATR_NONE, - "Start a new character", MENU_UNSELECTED); - clet = (k + 1 <= 'q' - 'a') ? 'q' : 0; /* quit */ + add_menu(tmpwin, &nul_glyphinfo, &any, clet, 'N', ATR_NONE, clr, + "Start a new character", MENU_ITEMFLAGS_NONE); + clet = (k + 1 <= 'q' - 'a' && clet == 'n') ? 'q' /* quit */ + : (k + 1 <= 26 + 'Q' - 'A' && clet == 'N') ? 'Q' : 0; any.a_int = -2; - add_menu(tmpwin, NO_GLYPH, &any, clet, 0, ATR_NONE, - "Never mind (quit)", MENU_SELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, clet, 'Q', ATR_NONE, clr, + "Never mind (quit)", MENU_ITEMFLAGS_SELECTED); /* no prompt on end_menu, as we've done our own at the top */ end_menu(tmpwin, (char *) 0); if (select_menu(tmpwin, PICK_ONE, &chosen_game) > 0) { ch = chosen_game->item.a_int; if (ch > 0) - Strcpy(plname, saved[ch - 1]); + Strcpy(svp.plname, saved[ch - 1]); else if (ch < 0) ++ch; /* -1 -> 0 (new game), -2 -> -1 (quit) */ free((genericptr_t) chosen_game); @@ -1400,253 +1617,6 @@ winid bannerwin; /* if not WIN_ERR, clear window and show copyright in menu */ return (ch > 0) ? 1 : ch; } #endif /* SELECTSAVED */ - -void -minit() -{ - (*restoreprocs.restore_minit)(); - return; -} - -void -mread(fd, buf, len) -register int fd; -register genericptr_t buf; -register unsigned int len; -{ - (*restoreprocs.restore_mread)(fd, buf, len); - return; -} - -/* examine the version info and the savefile_info data - that immediately follows it. - Return 0 if it passed the checks. - Return 1 if it failed the version check. - Return 2 if it failed the savefile feature check. - Return -1 if it failed for some unknown reason. - */ -int -validate(fd, name) -int fd; -const char *name; -{ - int rlen; - struct savefile_info sfi; - unsigned long compatible; - boolean verbose = name ? TRUE : FALSE, reslt = FALSE; - - if (!(reslt = uptodate(fd, name))) - return 1; - - rlen = read(fd, (genericptr_t) &sfi, sizeof sfi); - minit(); /* ZEROCOMP */ - if (rlen == 0) { - if (verbose) { - pline("File \"%s\" is empty during save file feature check?", - name); - wait_synch(); - } - return -1; - } - - compatible = (sfi.sfi1 & sfcap.sfi1); - - if ((sfi.sfi1 & SFI1_ZEROCOMP) == SFI1_ZEROCOMP) { - if ((compatible & SFI1_ZEROCOMP) != SFI1_ZEROCOMP) { - if (verbose) { - pline("File \"%s\" has incompatible ZEROCOMP compression.", - name); - wait_synch(); - } - return 2; - } else if ((sfrestinfo.sfi1 & SFI1_ZEROCOMP) != SFI1_ZEROCOMP) { - set_restpref("zerocomp"); - } - } - - if ((sfi.sfi1 & SFI1_EXTERNALCOMP) == SFI1_EXTERNALCOMP) { - if ((compatible & SFI1_EXTERNALCOMP) != SFI1_EXTERNALCOMP) { - if (verbose) { - pline("File \"%s\" lacks required internal compression.", - name); - wait_synch(); - } - return 2; - } else if ((sfrestinfo.sfi1 & SFI1_EXTERNALCOMP) - != SFI1_EXTERNALCOMP) { - set_restpref("externalcomp"); - } - } - - /* RLECOMP check must be last, after ZEROCOMP or INTERNALCOMP adjustments - */ - if ((sfi.sfi1 & SFI1_RLECOMP) == SFI1_RLECOMP) { - if ((compatible & SFI1_RLECOMP) != SFI1_RLECOMP) { - if (verbose) { - pline("File \"%s\" has incompatible run-length compression.", - name); - wait_synch(); - } - return 2; - } else if ((sfrestinfo.sfi1 & SFI1_RLECOMP) != SFI1_RLECOMP) { - set_restpref("rlecomp"); - } - } - /* savefile does not have RLECOMP level location compression, so adjust */ - else - set_restpref("!rlecomp"); - - return 0; -} - -void -reset_restpref() -{ -#ifdef ZEROCOMP - if (iflags.zerocomp) - set_restpref("zerocomp"); - else -#endif - set_restpref("externalcomp"); -#ifdef RLECOMP - if (iflags.rlecomp) - set_restpref("rlecomp"); - else -#endif - set_restpref("!rlecomp"); -} - -void -set_restpref(suitename) -const char *suitename; -{ - if (!strcmpi(suitename, "externalcomp")) { - restoreprocs.name = "externalcomp"; - restoreprocs.restore_mread = def_mread; - restoreprocs.restore_minit = def_minit; - sfrestinfo.sfi1 |= SFI1_EXTERNALCOMP; - sfrestinfo.sfi1 &= ~SFI1_ZEROCOMP; - def_minit(); - } - if (!strcmpi(suitename, "!rlecomp")) { - sfrestinfo.sfi1 &= ~SFI1_RLECOMP; - } -#ifdef ZEROCOMP - if (!strcmpi(suitename, "zerocomp")) { - restoreprocs.name = "zerocomp"; - restoreprocs.restore_mread = zerocomp_mread; - restoreprocs.restore_minit = zerocomp_minit; - sfrestinfo.sfi1 |= SFI1_ZEROCOMP; - sfrestinfo.sfi1 &= ~SFI1_EXTERNALCOMP; - zerocomp_minit(); - } -#endif -#ifdef RLECOMP - if (!strcmpi(suitename, "rlecomp")) { - sfrestinfo.sfi1 |= SFI1_RLECOMP; - } -#endif -} - -#ifdef ZEROCOMP -#define RLESC '\0' /* Leading character for run of RLESC's */ - -#ifndef ZEROCOMP_BUFSIZ -#define ZEROCOMP_BUFSIZ BUFSZ -#endif -static NEARDATA unsigned char inbuf[ZEROCOMP_BUFSIZ]; -static NEARDATA unsigned short inbufp = 0; -static NEARDATA unsigned short inbufsz = 0; -static NEARDATA short inrunlength = -1; -static NEARDATA int mreadfd; - -STATIC_OVL int -zerocomp_mgetc() -{ - if (inbufp >= inbufsz) { - inbufsz = read(mreadfd, (genericptr_t) inbuf, sizeof inbuf); - if (!inbufsz) { - if (inbufp > sizeof inbuf) - error("EOF on file #%d.\n", mreadfd); - inbufp = 1 + sizeof inbuf; /* exactly one warning :-) */ - return -1; - } - inbufp = 0; - } - return inbuf[inbufp++]; -} - -STATIC_OVL void -zerocomp_minit() -{ - inbufsz = 0; - inbufp = 0; - inrunlength = -1; -} - -STATIC_OVL void -zerocomp_mread(fd, buf, len) -int fd; -genericptr_t buf; -register unsigned len; -{ - /*register int readlen = 0;*/ - if (fd < 0) - error("Restore error; mread attempting to read file %d.", fd); - mreadfd = fd; - while (len--) { - if (inrunlength > 0) { - inrunlength--; - *(*((char **) &buf))++ = '\0'; - } else { - register short ch = zerocomp_mgetc(); - if (ch < 0) { - restoreprocs.mread_flags = -1; - return; - } - if ((*(*(char **) &buf)++ = (char) ch) == RLESC) { - inrunlength = zerocomp_mgetc(); - } - } - /*readlen++;*/ - } -} -#endif /* ZEROCOMP */ - -STATIC_OVL void -def_minit() -{ - return; -} - -STATIC_OVL void -def_mread(fd, buf, len) -register int fd; -register genericptr_t buf; -register unsigned int len; -{ - register int rlen; -#if defined(BSD) || defined(ULTRIX) -#define readLenType int -#else /* e.g. SYSV, __TURBOC__ */ -#define readLenType unsigned -#endif - - rlen = read(fd, buf, (readLenType) len); - if ((readLenType) rlen != (readLenType) len) { - if (restoreprocs.mread_flags == 1) { /* means "return anyway" */ - restoreprocs.mread_flags = -1; - return; - } else { - pline("Read %d instead of %u bytes.", rlen, len); - if (restoring) { - (void) nhclose(fd); - (void) delete_savefile(); - error("Error restoring old game."); - } - panic("Error reading level file."); - } - } -} +#endif /* !SFCTOOL */ /*restore.c*/ diff --git a/src/rip.c b/src/rip.c index f6fba01bc..b7c7fbd72 100644 --- a/src/rip.c +++ b/src/rip.c @@ -1,26 +1,30 @@ -/* NetHack 3.6 rip.c $NHDT-Date: 1488788514 2017/03/06 08:21:54 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.23 $ */ +/* NetHack 5.0 rip.c $NHDT-Date: 1597967808 2020/08/20 23:56:48 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.33 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2017. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" +/* Defining TEXT_TOMBSTONE causes genl_outrip() to exist, but it doesn't + necessarily have to be used by a binary with multiple window-ports */ + #if defined(TTY_GRAPHICS) || defined(X11_GRAPHICS) || defined(GEM_GRAPHICS) \ - || defined(MSWIN_GRAPHICS) || defined(DUMPLOG) || defined(CURSES_GRAPHICS) + || defined(DUMPLOG) || defined(CURSES_GRAPHICS) || defined(SHIM_GRAPHICS) \ + || defined(AMII_GRAPHICS) #define TEXT_TOMBSTONE #endif -#if defined(mac) || defined(__BEOS__) || defined(WIN32_GRAPHICS) +#if defined(mac) || defined(__BEOS__) #ifndef TEXT_TOMBSTONE #define TEXT_TOMBSTONE #endif #endif #ifdef TEXT_TOMBSTONE -STATIC_DCL void FDECL(center, (int, char *)); +staticfn void center(int, char *); #ifndef NH320_DEDICATION /* A normal tombstone for end of game display. */ -static const char *rip_txt[] = { +static const char *const rip_txt[] = { " ----------", " / \\", " / REST \\", @@ -40,7 +44,7 @@ static const char *rip_txt[] = { #define STONE_LINE_CENT 28 /* char[] element of center of stone face */ #else /* NH320_DEDICATION */ /* NetHack 3.2.x displayed a dual tombstone as a tribute to Izchak. */ -static const char *rip_txt[] = { +static const char *const rip_txt[] = { " ---------- ----------", " / \\ / \\", " / REST \\ / This \\", @@ -55,60 +59,54 @@ static const char *rip_txt[] = { " | | | Ascended |", " | 1001 | | |", " * | * * * | * * | * * * | *", - " _____)/\\|\\__//(\\/(/\\)/\\//\\/|_)________)/|\\\\_/_/(\\/(/\\)/\\/\\/|_)____", + (" _____)/\\|\\__//(\\/(/\\)/\\//\\/|_)___" + "_____)/|\\\\_/_/(\\/(/\\)/\\/\\/|_)____"), 0 }; #define STONE_LINE_CENT 19 /* char[] element of center of stone face */ #endif /* NH320_DEDICATION */ -#define STONE_LINE_LEN \ - 16 /* # chars that fit on one line \ - * (note 1 ' ' border) \ - */ -#define NAME_LINE 6 /* *char[] line # for player name */ -#define GOLD_LINE 7 /* *char[] line # for amount of gold */ +#define STONE_LINE_LEN 16 /* # chars that fit on one line + * (note 1 ' ' border) */ +#define NAME_LINE 6 /* *char[] line # for player name */ +#define GOLD_LINE 7 /* *char[] line # for amount of gold */ #define DEATH_LINE 8 /* *char[] line # for death description */ #define YEAR_LINE 12 /* *char[] line # for year */ -static char **rip; - -STATIC_OVL void -center(line, text) -int line; -char *text; +staticfn void +center(int line, char *text) { - register char *ip, *op; + char *ip, *op; ip = text; - op = &rip[line][STONE_LINE_CENT - ((strlen(text) + 1) >> 1)]; + op = &gr.rip[line][STONE_LINE_CENT - ((strlen(text) + 1) >> 1)]; while (*ip) *op++ = *ip++; } void -genl_outrip(tmpwin, how, when) -winid tmpwin; -int how; -time_t when; +genl_outrip(winid tmpwin, int how, time_t when) { - register char **dp; - register char *dpx; + char **dp; + char *dpx; char buf[BUFSZ]; - long year; - register int x; - int line; + int x; + int line, year; + long cash; - rip = dp = (char **) alloc(sizeof(rip_txt)); + gr.rip = dp = (char **) alloc(sizeof(rip_txt)); for (x = 0; rip_txt[x]; ++x) dp[x] = dupstr(rip_txt[x]); dp[x] = (char *) 0; /* Put name on stone */ - Sprintf(buf, "%s", plname); - buf[STONE_LINE_LEN] = 0; + Sprintf(buf, "%.*s", (int) STONE_LINE_LEN, svp.plname); center(NAME_LINE, buf); /* Put $ on stone */ - Sprintf(buf, "%ld Au", done_money); - buf[STONE_LINE_LEN] = 0; /* It could be a *lot* of gold :-) */ + cash = max(gd.done_money, 0L); + /* arbitrary upper limit; practical upper limit is quite a bit less */ + if (cash > 999999999L) + cash = 999999999L; + Sprintf(buf, "%ld Au", cash); center(GOLD_LINE, buf); /* Put together death description */ @@ -116,11 +114,11 @@ time_t when; /* Put death type on stone */ for (line = DEATH_LINE, dpx = buf; line < YEAR_LINE; line++) { - register int i, i0; char tmpchar; + int i, i0 = (int) strlen(dpx); - if ((i0 = strlen(dpx)) > STONE_LINE_LEN) { - for (i = STONE_LINE_LEN; ((i0 > STONE_LINE_LEN) && i); i--) + if (i0 > STONE_LINE_LEN) { + for (i = STONE_LINE_LEN; (i > 0) && (i0 > STONE_LINE_LEN); --i) if (dpx[i] == ' ') i0 = i; if (!i) @@ -137,8 +135,8 @@ time_t when; } /* Put year on stone */ - year = yyyymmdd(when) / 10000L; - Sprintf(buf, "%4ld", year); + year = (int) ((yyyymmdd(when) / 10000L) % 10000L); + Sprintf(buf, "%4d", year); center(YEAR_LINE, buf); #ifdef DUMPLOG @@ -158,10 +156,10 @@ time_t when; putstr(tmpwin, 0, ""); for (x = 0; rip_txt[x]; x++) { - free((genericptr_t) rip[x]); + free((genericptr_t) gr.rip[x]); } - free((genericptr_t) rip); - rip = 0; + free((genericptr_t) gr.rip); + gr.rip = 0; } #endif /* TEXT_TOMBSTONE */ diff --git a/src/rnd.c b/src/rnd.c index 2d5074ec6..864b48cf6 100644 --- a/src/rnd.c +++ b/src/rnd.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 rnd.c $NHDT-Date: 1524689470 2018/04/25 20:51:10 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.18 $ */ +/* NetHack 5.0 rnd.c $NHDT-Date: 1596498205 2020/08/03 23:43:25 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.30 $ */ /* Copyright (c) 2004 by Robert Patrick Rankin */ /* NetHack may be freely redistributed. See license for details. */ @@ -7,28 +7,31 @@ #ifdef USE_ISAAC64 #include "isaac64.h" +staticfn int whichrng(int (*fn)(int)); +staticfn int RND(int); +staticfn void set_random(unsigned long, int (*)(int)); + #if 0 static isaac64_ctx rng_state; #endif struct rnglist_t { - int FDECL((*fn), (int)); + int (*fn)(int); boolean init; isaac64_ctx rng_state; }; enum { CORE = 0, DISP = 1 }; -/* For NLE purposes, remove the static storage class so - that we can see the RNG states from our own functions. */ +/* NLE: remove the static storage class so that we can + see the RNG states from our own RNG functions. */ struct rnglist_t rnglist[] = { { rn2, FALSE, { 0 } }, /* CORE */ { rn2_on_display_rng, FALSE, { 0 } }, /* DISP */ }; -int -whichrng(fn) -int FDECL((*fn), (int)); +staticfn int +whichrng(int (*fn)(int)) { int i; @@ -39,9 +42,7 @@ int FDECL((*fn), (int)); } void -init_isaac64(seed, fn) -unsigned long seed; -int FDECL((*fn), (int)); +init_isaac64(unsigned long seed, int (*fn)(int)) { unsigned char new_rng_state[sizeof seed]; unsigned i; @@ -58,7 +59,7 @@ int FDECL((*fn), (int)); (int) sizeof seed); } -static int +staticfn int RND(int x) { return (isaac64_next_uint64(&rnglist[CORE].rng_state) % x); @@ -68,8 +69,7 @@ RND(int x) used in cases where the answer doesn't affect gameplay and we don't want to give users easy control over the main RNG sequence. */ int -rn2_on_display_rng(x) -register int x; +rn2_on_display_rng(int x) { return (isaac64_next_uint64(&rnglist[DISP].rng_state) % x); } @@ -77,31 +77,24 @@ register int x; #else /* USE_ISAAC64 */ /* "Rand()"s definition is determined by [OS]conf.h */ -#if defined(LINT) && defined(UNIX) /* rand() is long... */ -extern int NDECL(rand); -#define RND(x) (rand() % x) -#else /* LINT */ #if defined(UNIX) || defined(RANDOM) #define RND(x) ((int) (Rand() % (long) (x))) #else /* Good luck: the bottom order bits are cyclic. */ #define RND(x) ((int) ((Rand() >> 3) % (x))) #endif -#endif /* LINT */ int -rn2_on_display_rng(x) -register int x; +rn2_on_display_rng(int x) { static unsigned seed = 1; seed *= 2739110765; - return (int)((seed >> 16) % (unsigned)x); + return (int) ((seed >> 16) % (unsigned) x); } #endif /* USE_ISAAC64 */ /* 0 <= rn2(x) < x */ int -rn2(x) -register int x; +rn2(int x) { #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) if (x <= 0) { @@ -118,10 +111,9 @@ register int x; /* 0 <= rnl(x) < x; sometimes subtracting Luck; good luck approaches 0, bad luck approaches (x-1) */ int -rnl(x) -register int x; +rnl(int x) { - register int i, adjustment; + int i, adjustment; #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) if (x <= 0) { @@ -162,8 +154,7 @@ register int x; /* 1 <= rnd(x) <= x */ int -rnd(x) -register int x; +rnd(int x) { #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) if (x <= 0) { @@ -175,12 +166,17 @@ register int x; return x; } +int +rnd_on_display_rng(int x) +{ + return rn2_on_display_rng(x) + 1; +} + /* d(N,X) == NdX == dX+dX+...+dX N times; n <= d(n,x) <= (n*x) */ int -d(n, x) -register int n, x; +d(int n, int x) { - register int tmp = n; + int tmp = n; #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) if (x < 0 || n < 0 || (x == 0 && n != 0)) { @@ -195,10 +191,9 @@ register int n, x; /* 1 <= rne(x) <= max(u.ulevel/3,5) */ int -rne(x) -register int x; +rne(int x) { - register int tmp, utmp; + int tmp, utmp; utmp = (u.ulevel < 15) ? 5 : u.ulevel / 3; tmp = 1; @@ -218,16 +213,10 @@ register int x; /* rnz: everyone's favorite! */ int -rnz(i) -int i; +rnz(int i) { -#ifdef LINT - int x = i; - int tmp = 1000; -#else - register long x = (long) i; - register long tmp = 1000L; -#endif + long x = (long) i; + long tmp = 1000L; tmp += rn2(1000); tmp *= rne(4); @@ -241,4 +230,92 @@ int i; return (int) x; } +/* Sets the seed for the random number generator */ +#ifdef USE_ISAAC64 + +staticfn void +set_random(unsigned long seed, + int (*fn)(int)) +{ + /* NLE: Store the seed for this RNG */ + nle_seeds[whichrng(fn)] = seed; + init_isaac64(seed, fn); +} + +#else /* USE_ISAAC64 */ + +/*ARGSUSED*/ +staticfn void +set_random(unsigned long seed, + int (*fn)(int) UNUSED) +{ + /* + * The types are different enough here that sweeping the different + * routine names into one via #defines is even more confusing. + */ +# ifdef RANDOM /* srandom() from sys/share/random.c */ + srandom((unsigned int) seed); +# else +# if defined(__APPLE__) || defined(BSD) || defined(LINUX) \ + || defined(ULTRIX) || defined(CYGWIN32) /* system srandom() */ +# if defined(BSD) && !defined(POSIX_TYPES) && defined(SUNOS4) + (void) +# endif + srandom((int) seed); +# else +# ifdef UNIX /* system srand48() */ + srand48((long) seed); +# else /* poor quality system routine */ + srand((int) seed); +# endif +# endif +# endif +} +#endif /* USE_ISAAC64 */ + +/* An appropriate version of this must always be provided in + port-specific code somewhere. It returns a number suitable + as seed for the random number generator */ +extern unsigned long sys_random_seed(void); + +/* + * Initializes the random number generator. + * Only call once. + */ +/* NLE: Use our version in nlernd.c instead to support + multiple independent random number generators for seeding + the dungeon levels */ +/* +void +init_random(int (*fn)(int)) +{ + set_random(sys_random_seed(), fn); +} +*/ + +/* Reshuffles the random number generator. */ +void +reseed_random(int (*fn)(int)) +{ + /* only reseed if we are certain that the seed generation is unguessable + * by the players. */ + if (has_strong_rngseed) + init_random(fn); +} + +/* randomize the given list of numbers 0 <= i < count */ +void +shuffle_int_array(int *indices, int count) +{ + int i, iswap, temp; + + for (i = count - 1; i > 0; i--) { + if ((iswap = rn2(i + 1)) == i) + continue; + temp = indices[i]; + indices[i] = indices[iswap]; + indices[iswap] = temp; + } +} + /*rnd.c*/ diff --git a/src/role.c b/src/role.c index f263609a1..ee3e9a836 100644 --- a/src/role.c +++ b/src/role.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 role.c $NHDT-Date: 1547086250 2019/01/10 02:10:50 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.56 $ */ +/* NetHack 5.0 role.c $NHDT-Date: 1737607158 2025/01/22 20:39:18 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.107 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985-1999. */ /*-Copyright (c) Robert Patrick Rankin, 2012. */ /* NetHack may be freely redistributed. See license for details. */ @@ -24,7 +24,10 @@ * * God names use a leading underscore to flag goddesses. */ -const struct Role roles[] = { + +/* NUM_ROLES is defined in hack.h */ + +const struct Role roles[NUM_ROLES+1] = { { { "Archeologist", 0 }, { { "Digger", 0 }, { "Field Worker", 0 }, @@ -41,7 +44,6 @@ const struct Role roles[] = { "the Tomb of the Toltec Kings", PM_ARCHEOLOGIST, NON_PM, - NON_PM, PM_LORD_CARNARVON, PM_STUDENT, PM_MINION_OF_HUHETOTL, @@ -83,7 +85,6 @@ const struct Role roles[] = { "the Duali Oasis", PM_BARBARIAN, NON_PM, - NON_PM, PM_PELIAS, PM_CHIEFTAIN, PM_THOTH_AMON, @@ -123,8 +124,7 @@ const struct Role roles[] = { "Cav", "the Caves of the Ancestors", "the Dragon's Lair", - PM_CAVEMAN, - PM_CAVEWOMAN, + PM_CAVE_DWELLER, PM_LITTLE_DOG, PM_SHAMAN_KARNOV, PM_NEANDERTHAL, @@ -167,7 +167,6 @@ const struct Role roles[] = { "the Temple of Coeus", PM_HEALER, NON_PM, - NON_PM, PM_HIPPOCRATES, PM_ATTENDANT, PM_CYCLOPS, @@ -207,7 +206,6 @@ const struct Role roles[] = { "Camelot Castle", "the Isle of Glass", PM_KNIGHT, - NON_PM, PM_PONY, PM_KING_ARTHUR, PM_PAGE, @@ -249,7 +247,6 @@ const struct Role roles[] = { "the Monastery of the Earth-Lord", PM_MONK, NON_PM, - NON_PM, PM_GRAND_MASTER, PM_ABBOT, PM_MASTER_KAEN, @@ -289,8 +286,7 @@ const struct Role roles[] = { "Pri", "the Great Temple", "the Temple of Nalzok", - PM_PRIEST, - PM_PRIESTESS, + PM_CLERIC, NON_PM, PM_ARCH_PRIEST, PM_ACOLYTE, @@ -335,7 +331,6 @@ const struct Role roles[] = { "the Assassins' Guild Hall", PM_ROGUE, NON_PM, - NON_PM, PM_MASTER_OF_THIEVES, PM_THUG, PM_MASTER_ASSASSIN, @@ -389,7 +384,6 @@ const struct Role roles[] = { "Orion's camp", "the cave of the wumpus", PM_RANGER, - NON_PM, PM_LITTLE_DOG /* Orion & canis major */, PM_ORION, PM_HUNTER, @@ -431,7 +425,6 @@ const struct Role roles[] = { "the Castle of the Taro Clan", "the Shogun's Castle", PM_SAMURAI, - NON_PM, PM_LITTLE_DOG, PM_LORD_SATO, PM_ROSHI, @@ -473,7 +466,6 @@ const struct Role roles[] = { "the Thieves' Guild Hall", PM_TOURIST, NON_PM, - NON_PM, PM_TWOFLOWER, PM_GUIDE, PM_MASTER_OF_THIEVES, @@ -513,7 +505,6 @@ const struct Role roles[] = { "the Shrine of Destiny", "the cave of Surtur", PM_VALKYRIE, - NON_PM, NON_PM /*PM_WINTER_WOLF_CUB*/, PM_NORN, PM_WARRIOR, @@ -554,7 +545,6 @@ const struct Role roles[] = { "the Lonely Tower", "the Tower of Darkness", PM_WIZARD, - NON_PM, PM_KITTEN, PM_NEFERET_THE_GREEN, PM_APPRENTICE, @@ -582,39 +572,13 @@ const struct Role roles[] = { SPE_MAGIC_MISSILE, -4 }, /* Array terminator */ - { { 0, 0 } } -}; - -/* The player's role, created at runtime from initial - * choices. This may be munged in role_init(). - */ -struct Role urole = { - { "Undefined", 0 }, - { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, - { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } }, - "L", "N", "C", - "Xxx", "home", "locate", - NON_PM, NON_PM, NON_PM, NON_PM, NON_PM, NON_PM, NON_PM, NON_PM, - 0, 0, 0, 0, - /* Str Int Wis Dex Con Cha */ - { 7, 7, 7, 7, 7, 7 }, - { 20, 15, 15, 20, 20, 10 }, - /* Init Lower Higher */ - { 10, 0, 0, 8, 1, 0 }, /* Hit points */ - { 2, 0, 0, 2, 0, 3 }, - 14, /* Energy */ - 0, - 10, - 0, - 0, - 4, - A_INT, - 0, - -3 + UNDEFINED_ROLE, }; /* Table of all races */ -const struct Race races[] = { + +/* NUM_RACES is defined in hack.h */ +const struct Race races[NUM_RACES + 1] = { { "human", "human", @@ -622,7 +586,6 @@ const struct Race races[] = { "Hum", { "man", "woman" }, PM_HUMAN, - NON_PM, PM_HUMAN_MUMMY, PM_HUMAN_ZOMBIE, MH_HUMAN | ROLE_MALE | ROLE_FEMALE | ROLE_LAWFUL | ROLE_NEUTRAL @@ -644,7 +607,6 @@ const struct Race races[] = { "Elf", { 0, 0 }, PM_ELF, - NON_PM, PM_ELF_MUMMY, PM_ELF_ZOMBIE, MH_ELF | ROLE_MALE | ROLE_FEMALE | ROLE_CHAOTIC, @@ -665,7 +627,6 @@ const struct Race races[] = { "Dwa", { 0, 0 }, PM_DWARF, - NON_PM, PM_DWARF_MUMMY, PM_DWARF_ZOMBIE, MH_DWARF | ROLE_MALE | ROLE_FEMALE | ROLE_LAWFUL, @@ -686,7 +647,6 @@ const struct Race races[] = { "Gno", { 0, 0 }, PM_GNOME, - NON_PM, PM_GNOME_MUMMY, PM_GNOME_ZOMBIE, MH_GNOME | ROLE_MALE | ROLE_FEMALE | ROLE_NEUTRAL, @@ -707,7 +667,6 @@ const struct Race races[] = { "Orc", { 0, 0 }, PM_ORC, - NON_PM, PM_ORC_MUMMY, PM_ORC_ZOMBIE, MH_ORC | ROLE_MALE | ROLE_FEMALE | ROLE_CHAOTIC, @@ -722,39 +681,16 @@ const struct Race races[] = { { 1, 0, 1, 0, 1, 0 } /* Energy */ }, /* Array terminator */ - { 0, 0, 0, 0 } -}; - -/* The player's race, created at runtime from initial - * choices. This may be munged in role_init(). - */ -struct Race urace = { - "something", - "undefined", - "something", - "Xxx", - { 0, 0 }, - NON_PM, - NON_PM, - NON_PM, - NON_PM, - 0, - 0, - 0, - 0, - /* Str Int Wis Dex Con Cha */ - { 3, 3, 3, 3, 3, 3 }, - { STR18(100), 18, 18, 18, 18, 18 }, - /* Init Lower Higher */ - { 2, 0, 0, 2, 1, 0 }, /* Hit points */ - { 1, 0, 2, 0, 2, 0 } /* Energy */ + UNDEFINED_RACE, }; /* Table of all genders */ const struct Gender genders[] = { { "male", "he", "him", "his", "Mal", ROLE_MALE }, { "female", "she", "her", "her", "Fem", ROLE_FEMALE }, - { "neuter", "it", "it", "its", "Ntr", ROLE_NEUTER } + { "neuter", "it", "it", "its", "Ntr", ROLE_NEUTER }, + /* used by pronoun_gender() when hallucinating */ + { "group", "they", "them", "their", "Grp", 0 }, }; /* Table of all alignments */ @@ -765,30 +701,22 @@ const struct Align aligns[] = { { "evil", "unaligned", "Una", 0, A_NONE } }; -/* Filters */ -static struct { - boolean roles[SIZE(roles)]; - short mask; -} rfilter; - -STATIC_DCL int NDECL(randrole_filtered); -STATIC_DCL char *FDECL(promptsep, (char *, int)); -STATIC_DCL int FDECL(role_gendercount, (int)); -STATIC_DCL int FDECL(race_alignmentcount, (int)); +staticfn int randrole_filtered(void); +staticfn char *promptsep(char *, int); +staticfn int role_gendercount(int); +staticfn int race_alignmentcount(int); /* used by str2XXX() */ static char NEARDATA randomstr[] = "random"; boolean -validrole(rolenum) -int rolenum; +validrole(int rolenum) { - return (boolean) (rolenum >= 0 && rolenum < SIZE(roles) - 1); + return (boolean) (IndexOkT(rolenum, roles)); } int -randrole(for_display) -boolean for_display; +randrole(boolean for_display) { int res = SIZE(roles) - 1; @@ -799,14 +727,14 @@ boolean for_display; return res; } -STATIC_OVL int -randrole_filtered() +staticfn int +randrole_filtered(void) { int i, n = 0, set[SIZE(roles)]; /* this doesn't rule out impossible combinations but attempts to honor all the filter masks */ - for (i = 0; i < SIZE(roles); ++i) + for (i = 0; i < SIZE(roles) - 1; ++i) /* -1: avoid terminating element */ if (ok_role(i, ROLE_NONE, ROLE_NONE, ROLE_NONE) && ok_race(i, ROLE_RANDOM, ROLE_NONE, ROLE_NONE) && ok_gend(i, ROLE_NONE, ROLE_RANDOM, ROLE_NONE) @@ -816,8 +744,7 @@ randrole_filtered() } int -str2role(str) -const char *str; +str2role(const char *str) { int i, len; @@ -826,7 +753,7 @@ const char *str; return ROLE_NONE; /* Match as much of str as is provided */ - len = strlen(str); + len = Strlen(str); for (i = 0; roles[i].name.m; i++) { /* Does it match the male name? */ if (!strncmpi(str, roles[i].name.m, len)) @@ -848,18 +775,16 @@ const char *str; } boolean -validrace(rolenum, racenum) -int rolenum, racenum; +validrace(int rolenum, int racenum) { /* Assumes validrole */ - return (boolean) (racenum >= 0 && racenum < SIZE(races) - 1 + return (boolean) (IndexOkT(racenum, races) && (roles[rolenum].allow & races[racenum].allow & ROLE_RACEMASK)); } int -randrace(rolenum) -int rolenum; +randrace(int rolenum) { int i, n = 0; @@ -885,8 +810,7 @@ int rolenum; } int -str2race(str) -const char *str; +str2race(const char *str) { int i, len; @@ -895,11 +819,14 @@ const char *str; return ROLE_NONE; /* Match as much of str as is provided */ - len = strlen(str); + len = Strlen(str); for (i = 0; races[i].noun; i++) { /* Does it match the noun? */ if (!strncmpi(str, races[i].noun, len)) return i; + /* check adjective too */ + if (races[i].adj && !strncmpi(str, races[i].adj, len)) + return i; /* Or the filecode? */ if (!strcmpi(str, races[i].filecode)) return i; @@ -914,8 +841,7 @@ const char *str; } boolean -validgend(rolenum, racenum, gendnum) -int rolenum, racenum, gendnum; +validgend(int rolenum, int racenum, int gendnum) { /* Assumes validrole and validrace */ return (boolean) (gendnum >= 0 && gendnum < ROLE_GENDERS @@ -924,8 +850,7 @@ int rolenum, racenum, gendnum; } int -randgend(rolenum, racenum) -int rolenum, racenum; +randgend(int rolenum, int racenum) { int i, n = 0; @@ -952,8 +877,7 @@ int rolenum, racenum; } int -str2gend(str) -const char *str; +str2gend(const char *str) { int i, len; @@ -962,7 +886,7 @@ const char *str; return ROLE_NONE; /* Match as much of str as is provided */ - len = strlen(str); + len = Strlen(str); for (i = 0; i < ROLE_GENDERS; i++) { /* Does it match the adjective? */ if (!strncmpi(str, genders[i].adj, len)) @@ -980,8 +904,7 @@ const char *str; } boolean -validalign(rolenum, racenum, alignnum) -int rolenum, racenum, alignnum; +validalign(int rolenum, int racenum, int alignnum) { /* Assumes validrole and validrace */ return (boolean) (alignnum >= 0 && alignnum < ROLE_ALIGNS @@ -990,8 +913,7 @@ int rolenum, racenum, alignnum; } int -randalign(rolenum, racenum) -int rolenum, racenum; +randalign(int rolenum, int racenum) { int i, n = 0; @@ -1018,8 +940,7 @@ int rolenum, racenum; } int -str2align(str) -const char *str; +str2align(const char *str) { int i, len; @@ -1028,7 +949,7 @@ const char *str; return ROLE_NONE; /* Match as much of str as is provided */ - len = strlen(str); + len = Strlen(str); for (i = 0; i < ROLE_ALIGNS; i++) { /* Does it match the adjective? */ if (!strncmpi(str, aligns[i].adj, len)) @@ -1047,17 +968,16 @@ const char *str; /* is rolenum compatible with any racenum/gendnum/alignnum constraints? */ boolean -ok_role(rolenum, racenum, gendnum, alignnum) -int rolenum, racenum, gendnum, alignnum; +ok_role(int rolenum, int racenum, int gendnum, int alignnum) { int i; short allow; - if (rolenum >= 0 && rolenum < SIZE(roles) - 1) { - if (rfilter.roles[rolenum]) + if (IndexOkT(rolenum, roles)) { + if (gr.rfilter.roles[rolenum]) return FALSE; allow = roles[rolenum].allow; - if (racenum >= 0 && racenum < SIZE(races) - 1 + if (IndexOkT(racenum, races) && !(allow & races[racenum].allow & ROLE_RACEMASK)) return FALSE; if (gendnum >= 0 && gendnum < ROLE_GENDERS @@ -1070,10 +990,10 @@ int rolenum, racenum, gendnum, alignnum; } else { /* random; check whether any selection is possible */ for (i = 0; i < SIZE(roles) - 1; i++) { - if (rfilter.roles[i]) + if (gr.rfilter.roles[i]) continue; allow = roles[i].allow; - if (racenum >= 0 && racenum < SIZE(races) - 1 + if (IndexOkT(racenum, races) && !(allow & races[racenum].allow & ROLE_RACEMASK)) continue; if (gendnum >= 0 && gendnum < ROLE_GENDERS @@ -1092,8 +1012,7 @@ int rolenum, racenum, gendnum, alignnum; /* If pickhow == PICK_RIGID a role is returned only if there is */ /* a single possibility */ int -pick_role(racenum, gendnum, alignnum, pickhow) -int racenum, gendnum, alignnum, pickhow; +pick_role(int racenum, int gendnum, int alignnum, int pickhow) { int i; int roles_ok = 0, set[SIZE(roles)]; @@ -1104,8 +1023,8 @@ int racenum, gendnum, alignnum, pickhow; gendnum, alignnum) && ok_gend(i, racenum, (gendnum >= 0) ? gendnum : ROLE_RANDOM, alignnum) - && ok_race(i, racenum, - gendnum, (alignnum >= 0) ? alignnum : ROLE_RANDOM)) + && ok_align(i, racenum, + gendnum, (alignnum >= 0) ? alignnum : ROLE_RANDOM)) set[roles_ok++] = i; } if (roles_ok == 0 || (roles_ok > 1 && pickhow == PICK_RIGID)) @@ -1115,17 +1034,16 @@ int racenum, gendnum, alignnum, pickhow; /* is racenum compatible with any rolenum/gendnum/alignnum constraints? */ boolean -ok_race(rolenum, racenum, gendnum, alignnum) -int rolenum, racenum, gendnum, alignnum; +ok_race(int rolenum, int racenum, int gendnum, int alignnum) { int i; short allow; - if (racenum >= 0 && racenum < SIZE(races) - 1) { - if (rfilter.mask & races[racenum].selfmask) + if (IndexOkT(racenum, races)) { + if (gr.rfilter.mask & races[racenum].selfmask) return FALSE; allow = races[racenum].allow; - if (rolenum >= 0 && rolenum < SIZE(roles) - 1 + if (IndexOkT(rolenum, roles) && !(allow & roles[rolenum].allow & ROLE_RACEMASK)) return FALSE; if (gendnum >= 0 && gendnum < ROLE_GENDERS @@ -1138,10 +1056,10 @@ int rolenum, racenum, gendnum, alignnum; } else { /* random; check whether any selection is possible */ for (i = 0; i < SIZE(races) - 1; i++) { - if (rfilter.mask & races[i].selfmask) + if (gr.rfilter.mask & races[i].selfmask) continue; allow = races[i].allow; - if (rolenum >= 0 && rolenum < SIZE(roles) - 1 + if (IndexOkT(rolenum, roles) && !(allow & roles[rolenum].allow & ROLE_RACEMASK)) continue; if (gendnum >= 0 && gendnum < ROLE_GENDERS @@ -1160,8 +1078,7 @@ int rolenum, racenum, gendnum, alignnum; If pickhow == PICK_RIGID a race is returned only if there is a single possibility. */ int -pick_race(rolenum, gendnum, alignnum, pickhow) -int rolenum, gendnum, alignnum, pickhow; +pick_race(int rolenum, int gendnum, int alignnum, int pickhow) { int i; int races_ok = 0; @@ -1187,34 +1104,32 @@ int rolenum, gendnum, alignnum, pickhow; /* is gendnum compatible with any rolenum/racenum/alignnum constraints? */ /* gender and alignment are not comparable (and also not constrainable) */ boolean -ok_gend(rolenum, racenum, gendnum, alignnum) -int rolenum, racenum, gendnum; -int alignnum UNUSED; +ok_gend(int rolenum, int racenum, int gendnum, int alignnum UNUSED) { int i; short allow; if (gendnum >= 0 && gendnum < ROLE_GENDERS) { - if (rfilter.mask & genders[gendnum].allow) + if (gr.rfilter.mask & genders[gendnum].allow) return FALSE; allow = genders[gendnum].allow; - if (rolenum >= 0 && rolenum < SIZE(roles) - 1 + if (IndexOkT(rolenum, roles) && !(allow & roles[rolenum].allow & ROLE_GENDMASK)) return FALSE; - if (racenum >= 0 && racenum < SIZE(races) - 1 + if (IndexOkT(racenum, races) && !(allow & races[racenum].allow & ROLE_GENDMASK)) return FALSE; return TRUE; } else { /* random; check whether any selection is possible */ for (i = 0; i < ROLE_GENDERS; i++) { - if (rfilter.mask & genders[i].allow) + if (gr.rfilter.mask & genders[i].allow) continue; allow = genders[i].allow; - if (rolenum >= 0 && rolenum < SIZE(roles) - 1 + if (IndexOkT(rolenum, roles) && !(allow & roles[rolenum].allow & ROLE_GENDMASK)) continue; - if (racenum >= 0 && racenum < SIZE(races) - 1 + if (IndexOkT(racenum, races) && !(allow & races[racenum].allow & ROLE_GENDMASK)) continue; return TRUE; @@ -1228,8 +1143,7 @@ int alignnum UNUSED; /* If pickhow == PICK_RIGID a gender is returned only if there is */ /* a single possibility */ int -pick_gend(rolenum, racenum, alignnum, pickhow) -int rolenum, racenum, alignnum, pickhow; +pick_gend(int rolenum, int racenum, int alignnum, int pickhow) { int i; int gends_ok = 0; @@ -1255,35 +1169,32 @@ int rolenum, racenum, alignnum, pickhow; /* is alignnum compatible with any rolenum/racenum/gendnum constraints? */ /* alignment and gender are not comparable (and also not constrainable) */ boolean -ok_align(rolenum, racenum, gendnum, alignnum) -int rolenum, racenum; -int gendnum UNUSED; -int alignnum; +ok_align(int rolenum, int racenum, int gendnum UNUSED, int alignnum) { int i; short allow; if (alignnum >= 0 && alignnum < ROLE_ALIGNS) { - if (rfilter.mask & aligns[alignnum].allow) + if (gr.rfilter.mask & aligns[alignnum].allow) return FALSE; allow = aligns[alignnum].allow; - if (rolenum >= 0 && rolenum < SIZE(roles) - 1 + if (IndexOkT(rolenum, roles) && !(allow & roles[rolenum].allow & ROLE_ALIGNMASK)) return FALSE; - if (racenum >= 0 && racenum < SIZE(races) - 1 + if (IndexOkT(racenum, races) && !(allow & races[racenum].allow & ROLE_ALIGNMASK)) return FALSE; return TRUE; } else { /* random; check whether any selection is possible */ for (i = 0; i < ROLE_ALIGNS; i++) { - if (rfilter.mask & aligns[i].allow) - return FALSE; + if (gr.rfilter.mask & aligns[i].allow) + continue; allow = aligns[i].allow; - if (rolenum >= 0 && rolenum < SIZE(roles) - 1 + if (IndexOkT(rolenum, roles) && !(allow & roles[rolenum].allow & ROLE_ALIGNMASK)) continue; - if (racenum >= 0 && racenum < SIZE(races) - 1 + if (IndexOkT(racenum, races) && !(allow & races[racenum].allow & ROLE_ALIGNMASK)) continue; return TRUE; @@ -1297,8 +1208,7 @@ int alignnum; If pickhow == PICK_RIGID an alignment is returned only if there is a single possibility. */ int -pick_align(rolenum, racenum, gendnum, pickhow) -int rolenum, racenum, gendnum, pickhow; +pick_align(int rolenum, int racenum, int gendnum, int pickhow) { int i; int aligns_ok = 0; @@ -1322,7 +1232,7 @@ int rolenum, racenum, gendnum, pickhow; } void -rigid_role_checks() +rigid_role_checks(void) { int tmp; @@ -1371,76 +1281,122 @@ rigid_role_checks() } boolean -setrolefilter(bufp) -const char *bufp; +setrolefilter(const char *bufp) { int i; boolean reslt = TRUE; if ((i = str2role(bufp)) != ROLE_NONE && i != ROLE_RANDOM) - rfilter.roles[i] = TRUE; + gr.rfilter.roles[i] = TRUE; else if ((i = str2race(bufp)) != ROLE_NONE && i != ROLE_RANDOM) - rfilter.mask |= races[i].selfmask; + gr.rfilter.mask |= races[i].selfmask; else if ((i = str2gend(bufp)) != ROLE_NONE && i != ROLE_RANDOM) - rfilter.mask |= genders[i].allow; + gr.rfilter.mask |= genders[i].allow; else if ((i = str2align(bufp)) != ROLE_NONE && i != ROLE_RANDOM) - rfilter.mask |= aligns[i].allow; + gr.rfilter.mask |= aligns[i].allow; else reslt = FALSE; return reslt; } boolean -gotrolefilter() +gotrolefilter(void) { int i; - if (rfilter.mask) + if (gr.rfilter.mask) return TRUE; - for (i = 0; i < SIZE(roles); ++i) - if (rfilter.roles[i]) + for (i = 0; i < SIZE(roles) - 1; ++i) + if (gr.rfilter.roles[i]) return TRUE; return FALSE; } -void -clearrolefilter() +/* create a string like " !Bar !Kni" or " !chaotic" that can be + put back into an RC file by #saveoptions */ +char * +rolefilterstring(char *outbuf, int which) { int i; - for (i = 0; i < SIZE(roles); ++i) - rfilter.roles[i] = FALSE; - rfilter.mask = 0; + outbuf[0] = outbuf[1] = '\0'; + switch (which) { + case RS_ROLE: + for (i = 0; i < SIZE(roles) - 1; ++i) { + if (gr.rfilter.roles[i]) + Sprintf(eos(outbuf), " !%.3s", roles[i].name.m); + } + break; + case RS_RACE: + for (i = 0; i < SIZE(races) - 1; ++i) { + if ((gr.rfilter.mask & races[i].selfmask) != 0) + Sprintf(eos(outbuf), " !%s", races[i].noun); + } + break; + case RS_GENDER: + for (i = 0; i < SIZE(genders) - 1; ++i) { + if ((gr.rfilter.mask & genders[i].allow) != 0) + Sprintf(eos(outbuf), " !%s", genders[i].adj); + } + break; + case RS_ALGNMNT: + for (i = 0; i < SIZE(aligns) - 1; ++i) { + if ((gr.rfilter.mask & aligns[i].allow) != 0) + Sprintf(eos(outbuf), " !%s", aligns[i].adj); + } + break; + default: + impossible("rolefilterstring: bad role aspect (%d)", which); + Strcpy(outbuf, " ?"); + break; + } + /* constructed with a leading space; drop it */ + return &outbuf[1]; } -#define BP_ALIGN 0 -#define BP_GEND 1 -#define BP_RACE 2 -#define BP_ROLE 3 -#define NUM_BP 4 +void +clearrolefilter(int which) +{ + int i; -STATIC_VAR char pa[NUM_BP], post_attribs; + switch (which) { + case RS_filter: + gr.rfilter.mask = 0; /* clear race, gender, and alignment filters */ + FALLTHROUGH; + /*FALLTHRU*/ + case RS_ROLE: + for (i = 0; i < SIZE(roles) - 1; ++i) + gr.rfilter.roles[i] = FALSE; + break; + case RS_RACE: + gr.rfilter.mask &= ~ROLE_RACEMASK; + break; + case RS_GENDER: + gr.rfilter.mask &= ~ROLE_GENDMASK; + break; + case RS_ALGNMNT: + gr.rfilter.mask &= ~ROLE_ALIGNMASK; + break; + } +} -STATIC_OVL char * -promptsep(buf, num_post_attribs) -char *buf; -int num_post_attribs; +staticfn char * +promptsep(char *buf, int num_post_attribs) { const char *conjuct = "and "; - if (num_post_attribs > 1 && post_attribs < num_post_attribs - && post_attribs > 1) + if (num_post_attribs > 1 && gr.role_post_attribs < num_post_attribs + && gr.role_post_attribs > 1) Strcat(buf, ","); Strcat(buf, " "); - --post_attribs; - if (!post_attribs && num_post_attribs > 1) + --gr.role_post_attribs; + if (!gr.role_post_attribs && num_post_attribs > 1) Strcat(buf, conjuct); return buf; } -STATIC_OVL int -role_gendercount(rolenum) -int rolenum; +staticfn int +role_gendercount(int rolenum) { int gendcount = 0; @@ -1455,9 +1411,8 @@ int rolenum; return gendcount; } -STATIC_OVL int -race_alignmentcount(racenum) -int racenum; +staticfn int +race_alignmentcount(int racenum) { int aligncount = 0; @@ -1473,10 +1428,9 @@ int racenum; } char * -root_plselection_prompt(suppliedbuf, buflen, rolenum, racenum, gendnum, - alignnum) -char *suppliedbuf; -int buflen, rolenum, racenum, gendnum, alignnum; +root_plselection_prompt( + char *suppliedbuf, int buflen, + int rolenum, int racenum, int gendnum, int alignnum) { int k, gendercount = 0, aligncount = 0; char buf[BUFSZ]; @@ -1487,9 +1441,9 @@ int buflen, rolenum, racenum, gendnum, alignnum; return err_ret; /* initialize these static variables each time this is called */ - post_attribs = 0; + gr.role_post_attribs = 0; for (k = 0; k < NUM_BP; ++k) - pa[k] = 0; + gr.role_pa[k] = 0; buf[0] = '\0'; *suppliedbuf = '\0'; @@ -1499,18 +1453,19 @@ int buflen, rolenum, racenum, gendnum, alignnum; if (alignnum != ROLE_NONE && alignnum != ROLE_RANDOM && ok_align(rolenum, racenum, gendnum, alignnum)) { +#if 0 /* 'if' and 'else' had duplicate code here; probably a copy+parse + * oversight; if a problem with filtering of random role selection + * crops up, this is probably the place to start looking */ + /* if race specified, and multiple choice of alignments for it */ if ((racenum >= 0) && (aligncount > 1)) { - if (donefirst) - Strcat(buf, " "); - Strcat(buf, aligns[alignnum].adj); - donefirst = TRUE; } else { - if (donefirst) - Strcat(buf, " "); - Strcat(buf, aligns[alignnum].adj); - donefirst = TRUE; } +#endif /* the four lines of code below were in both 'if' and 'else' above */ + if (donefirst) + Strcat(buf, " "); + Strcat(buf, aligns[alignnum].adj); + donefirst = TRUE; } else { /* in case we got here by failing the ok_align() test */ if (alignnum != ROLE_RANDOM) @@ -1522,8 +1477,8 @@ int buflen, rolenum, racenum, gendnum, alignnum; && ok_race(rolenum, racenum, gendnum, alignnum)) && (aligncount > 1)) || (racenum == ROLE_NONE || racenum == ROLE_RANDOM)) { - pa[BP_ALIGN] = 1; - post_attribs++; + gr.role_pa[BP_ALIGN] = 1; + gr.role_post_attribs++; } } /* */ @@ -1555,8 +1510,8 @@ int buflen, rolenum, racenum, gendnum, alignnum; don't include it in the later list */ if ((validrole(rolenum) && (gendercount > 1)) || !validrole(rolenum)) { - pa[BP_GEND] = 1; - post_attribs++; + gr.role_pa[BP_GEND] = 1; + gr.role_post_attribs++; } } /* */ @@ -1575,16 +1530,17 @@ int buflen, rolenum, racenum, gendnum, alignnum; Strcat(buf, races[racenum].noun); donefirst = TRUE; } else { - pa[BP_RACE] = 1; - post_attribs++; + gr.role_pa[BP_RACE] = 1; + gr.role_post_attribs++; } } else { - pa[BP_RACE] = 1; - post_attribs++; + gr.role_pa[BP_RACE] = 1; + gr.role_post_attribs++; } /* || */ if (validrole(rolenum)) { + assert(IndexOkT(rolenum, roles)); if (donefirst) Strcat(buf, " "); if (gendnum != ROLE_NONE) { @@ -1602,8 +1558,8 @@ int buflen, rolenum, racenum, gendnum, alignnum; } donefirst = TRUE; } else if (rolenum == ROLE_NONE) { - pa[BP_ROLE] = 1; - post_attribs++; + gr.role_pa[BP_ROLE] = 1; + gr.role_post_attribs++; } if ((racenum == ROLE_NONE || racenum == ROLE_RANDOM) @@ -1611,7 +1567,7 @@ int buflen, rolenum, racenum, gendnum, alignnum; if (donefirst) Strcat(buf, " "); Strcat(buf, "character"); - donefirst = TRUE; + /*donefirst = TRUE;*/ } /* || * || @@ -1624,9 +1580,9 @@ int buflen, rolenum, racenum, gendnum, alignnum; } char * -build_plselection_prompt(buf, buflen, rolenum, racenum, gendnum, alignnum) -char *buf; -int buflen, rolenum, racenum, gendnum, alignnum; +build_plselection_prompt( + char *buf, int buflen, + int rolenum, int racenum, int gendnum, int alignnum) { const char *defprompt = "Shall I pick a character for you? [ynaq] "; int num_post_attribs = 0; @@ -1642,7 +1598,7 @@ int buflen, rolenum, racenum, gendnum, alignnum; Strcat(tmpbuf, "a "); /* */ - (void) root_plselection_prompt(eos(tmpbuf), buflen - strlen(tmpbuf), + (void) root_plselection_prompt(eos(tmpbuf), buflen - Strlen(tmpbuf), rolenum, racenum, gendnum, alignnum); /* "Shall I pick a character's role, race, gender, and alignment for you?" plus " [ynaq] (y)" is a little too long for a conventional 80 columns; @@ -1663,34 +1619,34 @@ int buflen, rolenum, racenum, gendnum, alignnum; * * Now append the post attributes to it */ - num_post_attribs = post_attribs; + num_post_attribs = gr.role_post_attribs; if (!num_post_attribs) { /* some constraints might have been mutually exclusive, in which case some prompting that would have been omitted is needed after all */ - if (flags.initrole == ROLE_NONE && !pa[BP_ROLE]) - pa[BP_ROLE] = ++post_attribs; - if (flags.initrace == ROLE_NONE && !pa[BP_RACE]) - pa[BP_RACE] = ++post_attribs; - if (flags.initalign == ROLE_NONE && !pa[BP_ALIGN]) - pa[BP_ALIGN] = ++post_attribs; - if (flags.initgend == ROLE_NONE && !pa[BP_GEND]) - pa[BP_GEND] = ++post_attribs; - num_post_attribs = post_attribs; + if (flags.initrole == ROLE_NONE && !gr.role_pa[BP_ROLE]) + gr.role_pa[BP_ROLE] = ++gr.role_post_attribs; + if (flags.initrace == ROLE_NONE && !gr.role_pa[BP_RACE]) + gr.role_pa[BP_RACE] = ++gr.role_post_attribs; + if (flags.initalign == ROLE_NONE && !gr.role_pa[BP_ALIGN]) + gr.role_pa[BP_ALIGN] = ++gr.role_post_attribs; + if (flags.initgend == ROLE_NONE && !gr.role_pa[BP_GEND]) + gr.role_pa[BP_GEND] = ++gr.role_post_attribs; + num_post_attribs = gr.role_post_attribs; } if (num_post_attribs) { - if (pa[BP_RACE]) { + if (gr.role_pa[BP_RACE]) { (void) promptsep(eos(buf), num_post_attribs); Strcat(buf, "race"); } - if (pa[BP_ROLE]) { + if (gr.role_pa[BP_ROLE]) { (void) promptsep(eos(buf), num_post_attribs); Strcat(buf, "role"); } - if (pa[BP_GEND]) { + if (gr.role_pa[BP_GEND]) { (void) promptsep(eos(buf), num_post_attribs); Strcat(buf, "gender"); } - if (pa[BP_ALIGN]) { + if (gr.role_pa[BP_ALIGN]) { (void) promptsep(eos(buf), num_post_attribs); Strcat(buf, "alignment"); } @@ -1706,7 +1662,7 @@ int buflen, rolenum, racenum, gendnum, alignnum; #undef NUM_BP void -plnamesuffix() +plnamesuffix(void) { char *sptr, *eptr; int i; @@ -1714,27 +1670,38 @@ plnamesuffix() /* some generic user names will be ignored in favor of prompting */ if (sysopt.genericusers) { if (*sysopt.genericusers == '*') { - *plname = '\0'; + svp.plname[0] = '\0'; } else { - i = (int) strlen(plname); - if ((sptr = strstri(sysopt.genericusers, plname)) != 0 - && (sptr == sysopt.genericusers || sptr[-1] == ' ') - && (sptr[i] == ' ' || sptr[i] == '\0')) - *plname = '\0'; /* call askname() */ + /* need to ignore appended '-role-race-gender-alignment'; + 'plnamelen' is non-zero when dealing with plname[] value that + contains a username with dash(es) in it and is usually 0 */ + i = ((eptr = strchr(svp.plname + gp.plnamelen, '-')) != 0) + ? (int) (eptr - svp.plname) + : (int) Strlen(svp.plname); + /* look for plname[] in the 'genericusers' space-separated list */ + if (findword(sysopt.genericusers, svp.plname, i, FALSE)) + /* it's generic; remove it so that askname() will be called */ + svp.plname[0] = '\0'; } + if (!svp.plname[0]) + gp.plnamelen = 0; } do { - if (!*plname) - askname(); /* fill plname[] if necessary, or set defer_plname */ + if (!svp.plname[0]) { + askname(); /* fill svp.plname[] if necessary, or set + * defer_plname */ + gp.plnamelen = 0; /* plname[] might have -role-race-&c attached */ + } /* Look for tokens delimited by '-' */ - if ((eptr = index(plname, '-')) != (char *) 0) + sptr = svp.plname + gp.plnamelen; + if ((eptr = strchr(sptr, '-')) != (char *) 0) *eptr++ = '\0'; while (eptr) { /* Isolate the next token */ sptr = eptr; - if ((eptr = index(sptr, '-')) != (char *) 0) + if ((eptr = strchr(sptr, '-')) != (char *) 0) *eptr++ = '\0'; /* Try to match it to something */ @@ -1747,110 +1714,106 @@ plnamesuffix() else if ((i = str2align(sptr)) != ROLE_NONE) flags.initalign = i; } - } while (!*plname && !iflags.defer_plname); + } while (!svp.plname[0] && !iflags.defer_plname); - /* commas in the plname confuse the record file, convert to spaces */ - for (sptr = plname; *sptr; sptr++) { - if (*sptr == ',') - *sptr = ' '; - } + /* commas in the svp.plname confuse the record file, convert to spaces */ + (void) strNsubst(svp.plname, ",", " ", 0); } /* show current settings for name, role, race, gender, and alignment in the specified window */ void -role_selection_prolog(which, where) -int which; -winid where; +role_selection_prolog(int which, winid where) { static const char NEARDATA choosing[] = " choosing now", not_yet[] = " not yet specified", rand_choice[] = " random"; char buf[BUFSZ]; - int r, c, g, a, allowmask; + int r, c, gend, a, allowmask; r = flags.initrole; c = flags.initrace; - g = flags.initgend; + gend = flags.initgend; a = flags.initalign; if (r >= 0) { + assert(IndexOkT(r, roles)); allowmask = roles[r].allow; if ((allowmask & ROLE_RACEMASK) == MH_HUMAN) c = 0; /* races[human] */ - else if (c >= 0 && !(allowmask & ROLE_RACEMASK & races[c].allow)) + else if (IndexOkT(c, races) + && !(allowmask & ROLE_RACEMASK & races[c].allow)) c = ROLE_RANDOM; if ((allowmask & ROLE_GENDMASK) == ROLE_MALE) - g = 0; /* role forces male (hypothetical) */ + gend = 0; /* role forces male (hypothetical) */ else if ((allowmask & ROLE_GENDMASK) == ROLE_FEMALE) - g = 1; /* role forces female (valkyrie) */ + gend = 1; /* role forces female (valkyrie) */ if ((allowmask & ROLE_ALIGNMASK) == AM_LAWFUL) a = 0; /* aligns[lawful] */ else if ((allowmask & ROLE_ALIGNMASK) == AM_NEUTRAL) a = 1; /* aligns[neutral] */ else if ((allowmask & ROLE_ALIGNMASK) == AM_CHAOTIC) - a = 2; /* alings[chaotic] */ + a = 2; /* aligns[chaotic] */ } if (c >= 0) { + assert(IndexOkT(c, races)); allowmask = races[c].allow; if ((allowmask & ROLE_ALIGNMASK) == AM_LAWFUL) a = 0; /* aligns[lawful] */ else if ((allowmask & ROLE_ALIGNMASK) == AM_NEUTRAL) a = 1; /* aligns[neutral] */ else if ((allowmask & ROLE_ALIGNMASK) == AM_CHAOTIC) - a = 2; /* alings[chaotic] */ + a = 2; /* aligns[chaotic] */ /* [c never forces gender] */ } /* [g and a don't constrain anything sufficiently to narrow something done to a single choice] */ Sprintf(buf, "%12s ", "name:"); - Strcat(buf, (which == RS_NAME) ? choosing : !*plname ? not_yet : plname); + Strcat(buf, (which == RS_NAME) ? choosing + : !*svp.plname ? not_yet : svp.plname); putstr(where, 0, buf); Sprintf(buf, "%12s ", "role:"); - Strcat(buf, (which == RS_ROLE) ? choosing : (r == ROLE_NONE) - ? not_yet - : (r == ROLE_RANDOM) - ? rand_choice - : roles[r].name.m); + assert(which == RS_ROLE || r == ROLE_NONE || r == ROLE_RANDOM + || IndexOkT(r, roles)); + Strcat(buf, (which == RS_ROLE) ? choosing + : (r == ROLE_NONE) ? not_yet + : (r == ROLE_RANDOM) ? rand_choice + : roles[r].name.m); if (r >= 0 && roles[r].name.f) { /* distinct female name [caveman/cavewoman, priest/priestess] */ - if (g == 1) + if (gend == 1) /* female specified; replace male role name with female one */ - Sprintf(index(buf, ':'), ": %s", roles[r].name.f); - else if (g < 0) + Sprintf(strchr(buf, ':'), ": %s", roles[r].name.f); + else if (gend < 0) /* gender unspecified; append slash and female role name */ Sprintf(eos(buf), "/%s", roles[r].name.f); } putstr(where, 0, buf); Sprintf(buf, "%12s ", "race:"); - Strcat(buf, (which == RS_RACE) ? choosing : (c == ROLE_NONE) - ? not_yet - : (c == ROLE_RANDOM) - ? rand_choice - : races[c].noun); + assert(which == RS_RACE || c == ROLE_NONE || c == ROLE_RANDOM + || IndexOkT(c, races)); + Strcat(buf, (which == RS_RACE) ? choosing + : (c == ROLE_NONE) ? not_yet + : (c == ROLE_RANDOM) ? rand_choice + : races[c].noun); putstr(where, 0, buf); Sprintf(buf, "%12s ", "gender:"); - Strcat(buf, (which == RS_GENDER) ? choosing : (g == ROLE_NONE) - ? not_yet - : (g == ROLE_RANDOM) - ? rand_choice - : genders[g].adj); + Strcat(buf, (which == RS_GENDER) ? choosing + : (gend == ROLE_NONE) ? not_yet + : (gend == ROLE_RANDOM) ? rand_choice + : genders[gend].adj); putstr(where, 0, buf); Sprintf(buf, "%12s ", "alignment:"); - Strcat(buf, (which == RS_ALGNMNT) ? choosing : (a == ROLE_NONE) - ? not_yet - : (a == ROLE_RANDOM) - ? rand_choice - : aligns[a].adj); + Strcat(buf, (which == RS_ALGNMNT) ? choosing + : (a == ROLE_NONE) ? not_yet + : (a == ROLE_RANDOM) ? rand_choice + : aligns[a].adj); putstr(where, 0, buf); } /* add a "pick alignment first"-type entry to the specified menu */ void -role_menu_extra(which, where, preselect) -int which; -winid where; -boolean preselect; +role_menu_extra(int which, winid where, boolean preselect) { static NEARDATA const char RS_menu_let[] = { '=', /* name */ @@ -1862,7 +1825,8 @@ boolean preselect; anything any; char buf[BUFSZ]; const char *what = 0, *constrainer = 0, *forcedvalue = 0; - int f = 0, r, c, g, a, i, allowmask; + int f = 0, r, c, gend, a, i, allowmask; + int clr = NO_COLOR; r = flags.initrole; c = flags.initrace; @@ -1873,10 +1837,10 @@ boolean preselect; case RS_ROLE: what = "role"; f = r; - for (i = 0; i < SIZE(roles); ++i) - if (i != f && !rfilter.roles[i]) + for (i = 0; i < SIZE(roles) - 1; ++i) + if (i != f && !gr.rfilter.roles[i]) break; - if (i == SIZE(roles)) { + if (i == SIZE(roles) - 1) { constrainer = "filter"; forcedvalue = "role"; } @@ -1892,8 +1856,8 @@ boolean preselect; if (c >= 0) { constrainer = "role"; forcedvalue = races[c].noun; - } else if (f >= 0 - && (allowmask & ~rfilter.mask) == races[f].selfmask) { + } else if (f >= 0 && ((allowmask & ~gr.rfilter.mask) + == races[f].selfmask)) { /* if there is only one race choice available due to user options disallowing others, race menu entry is disabled */ constrainer = "filter"; @@ -1904,18 +1868,18 @@ boolean preselect; case RS_GENDER: what = "gender"; f = flags.initgend; - g = ROLE_NONE; + gend = ROLE_NONE; if (r >= 0) { allowmask = roles[r].allow & ROLE_GENDMASK; if (allowmask == ROLE_MALE) - g = 0; /* genders[male] */ + gend = 0; /* genders[male] */ else if (allowmask == ROLE_FEMALE) - g = 1; /* genders[female] */ - if (g >= 0) { + gend = 1; /* genders[female] */ + if (gend >= 0) { constrainer = "role"; - forcedvalue = genders[g].adj; - } else if (f >= 0 - && (allowmask & ~rfilter.mask) == genders[f].allow) { + forcedvalue = genders[gend].adj; + } else if (f >= 0 && ((allowmask & ~gr.rfilter.mask) + == genders[f].allow)) { /* if there is only one gender choice available due to user options disallowing other, gender menu entry is disabled */ constrainer = "filter"; @@ -1950,7 +1914,7 @@ boolean preselect; constrainer = "race"; } if (f >= 0 && !constrainer - && (ROLE_ALIGNMASK & ~rfilter.mask) == aligns[f].allow) { + && (ROLE_ALIGNMASK & ~gr.rfilter.mask) == aligns[f].allow) { /* if there is only one alignment choice available due to user options disallowing others, algn menu entry is disabled */ constrainer = "filter"; @@ -1961,30 +1925,35 @@ boolean preselect; break; } - any = zeroany; /* zero out all bits */ + any = cg.zeroany; /* zero out all bits */ if (constrainer) { any.a_int = 0; /* use four spaces of padding to fake a grayed out menu choice */ Sprintf(buf, "%4s%s forces %s", "", constrainer, forcedvalue); - add_menu(where, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, - MENU_UNSELECTED); + add_menu_str(where, buf); } else if (what) { any.a_int = RS_menu_arg(which); Sprintf(buf, "Pick%s %s first", (f >= 0) ? " another" : "", what); - add_menu(where, NO_GLYPH, &any, RS_menu_let[which], 0, ATR_NONE, buf, - MENU_UNSELECTED); + add_menu(where, &nul_glyphinfo, &any, RS_menu_let[which], 0, + ATR_NONE, clr, buf, MENU_ITEMFLAGS_NONE); } else if (which == RS_filter) { + char setfiltering[40]; + any.a_int = RS_menu_arg(RS_filter); - add_menu(where, NO_GLYPH, &any, '~', 0, ATR_NONE, - "Reset role/race/&c filtering", MENU_UNSELECTED); + Sprintf(setfiltering, "%s role/race/&c filtering", + gotrolefilter() ? "Reset" : "Set"); + add_menu(where, &nul_glyphinfo, &any, '~', 0, ATR_NONE, + clr, setfiltering, MENU_ITEMFLAGS_NONE); } else if (which == ROLE_RANDOM) { any.a_int = ROLE_RANDOM; - add_menu(where, NO_GLYPH, &any, '*', 0, ATR_NONE, "Random", - preselect ? MENU_SELECTED : MENU_UNSELECTED); + add_menu(where, &nul_glyphinfo, &any, '*', 0, + ATR_NONE, clr, "Random", + preselect ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); } else if (which == ROLE_NONE) { any.a_int = ROLE_NONE; - add_menu(where, NO_GLYPH, &any, 'q', 0, ATR_NONE, "Quit", - preselect ? MENU_SELECTED : MENU_UNSELECTED); + add_menu(where, &nul_glyphinfo, &any, 'q', 0, + ATR_NONE, clr, "Quit", + preselect ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); } else { impossible("role_menu_extra: bad arg (%d)", which); } @@ -2000,7 +1969,7 @@ boolean preselect; * 1 - The Rogue Leader is the Tourist Nemesis. * 2 - Priests start with a random alignment - convert the leader and * guardians here. - * 3 - Priests also get their of deities from a randomly chosen role. + * 3 - Priests also get their set of deities from a randomly chosen role. * 4 - [obsolete] Elves can have one of two different leaders, * but can't work it out here because it requires hacking the * level file data (see sp_lev.c). @@ -2008,7 +1977,7 @@ boolean preselect; * This code also replaces quest_init(). */ void -role_init() +role_init(void) { int alignmnt; struct permonst *pm; @@ -2021,15 +1990,15 @@ role_init() /* Check for a valid role. Try flags.initrole first. */ if (!validrole(flags.initrole)) { /* Try the player letter second */ - if ((flags.initrole = str2role(pl_character)) < 0) + if ((flags.initrole = str2role(svp.pl_character)) < 0) /* None specified; pick a random role */ flags.initrole = randrole_filtered(); } /* We now have a valid role index. Copy the role name back. */ /* This should become OBSOLETE */ - Strcpy(pl_character, roles[flags.initrole].name.m); - pl_character[PL_CSIZ - 1] = '\0'; + Strcpy(svp.pl_character, roles[flags.initrole].name.m); + svp.pl_character[PL_CSIZ - 1] = '\0'; /* Check for a valid race */ if (!validrace(flags.initrole, flags.initrace)) @@ -2051,35 +2020,35 @@ role_init() flags.initalign = randalign(flags.initrole, flags.initrace); alignmnt = aligns[flags.initalign].value; - /* Initialize urole and urace */ - urole = roles[flags.initrole]; - urace = races[flags.initrace]; + /* Initialize gu.urole and gu.urace */ + gu.urole = roles[flags.initrole]; + gu.urace = races[flags.initrace]; /* Fix up the quest leader */ - if (urole.ldrnum != NON_PM) { - pm = &mons[urole.ldrnum]; + if (gu.urole.ldrnum != NON_PM) { + pm = &mons[gu.urole.ldrnum]; pm->msound = MS_LEADER; pm->mflags2 |= (M2_PEACEFUL); pm->mflags3 |= M3_CLOSE; pm->maligntyp = alignmnt * 3; /* if gender is random, we choose it now instead of waiting until the leader monster is created */ - quest_status.ldrgend = + svq.quest_status.ldrgend = is_neuter(pm) ? 2 : is_female(pm) ? 1 : is_male(pm) ? 0 : (rn2(100) < 50); } /* Fix up the quest guardians */ - if (urole.guardnum != NON_PM) { - pm = &mons[urole.guardnum]; + if (gu.urole.guardnum != NON_PM) { + pm = &mons[gu.urole.guardnum]; pm->mflags2 |= (M2_PEACEFUL); pm->maligntyp = alignmnt * 3; } /* Fix up the quest nemesis */ - if (urole.neminum != NON_PM) { - pm = &mons[urole.neminum]; + if (gu.urole.neminum != NON_PM) { + pm = &mons[gu.urole.neminum]; pm->msound = MS_NEMESIS; pm->mflags2 &= ~(M2_PEACEFUL); pm->mflags2 |= (M2_NASTY | M2_STALK | M2_HOSTILE); @@ -2087,23 +2056,36 @@ role_init() pm->mflags3 |= M3_WANTSARTI | M3_WAITFORU; /* if gender is random, we choose it now instead of waiting until the nemesis monster is created */ - quest_status.nemgend = is_neuter(pm) ? 2 : is_female(pm) ? 1 + svq.quest_status.nemgend = is_neuter(pm) ? 2 : is_female(pm) ? 1 : is_male(pm) ? 0 : (rn2(100) < 50); } /* Fix up the god names */ if (flags.pantheon == -1) { /* new game */ + int trycnt = 0; flags.pantheon = flags.initrole; /* use own gods */ - while (!roles[flags.pantheon].lgod) /* unless they're missing */ + /* unless they're missing */ + while (!roles[flags.pantheon].lgod && ++trycnt < 100) flags.pantheon = randrole(FALSE); + if (!roles[flags.pantheon].lgod) { + int i; + for (i = 0; i < SIZE(roles) - 1; i++) + if (roles[i].lgod) { + flags.pantheon = i; + break; + } + } } - if (!urole.lgod) { - urole.lgod = roles[flags.pantheon].lgod; - urole.ngod = roles[flags.pantheon].ngod; - urole.cgod = roles[flags.pantheon].cgod; + if (!gu.urole.lgod) { + gu.urole.lgod = roles[flags.pantheon].lgod; + gu.urole.ngod = roles[flags.pantheon].ngod; + gu.urole.cgod = roles[flags.pantheon].cgod; } /* 0 or 1; no gods are neuter, nor is gender randomized */ - quest_status.godgend = !strcmpi(align_gtitle(alignmnt), "goddess"); + svq.quest_status.godgend = !strcmpi(align_gtitle(alignmnt), "goddess"); + + if (Role_if(PM_CLERIC)) + objects[SPE_LIGHT].oc_skill = P_CLERIC_SPELL; #if 0 /* @@ -2111,10 +2093,10 @@ role_init() * place where it actually matters for the hero is in set_uasmon() * and that can use mons[race] rather than mons[role] for this * particular property. Despite the comment, it is checked--where - * needed--via instrinsic 'Infravision' which set_uasmon() manages. + * needed--via intrinsic 'Infravision' which set_uasmon() manages. */ /* Fix up infravision */ - if (mons[urace.malenum].mflags3 & M3_INFRAVISION) { + if (mons[gu.urace.mnum].mflags3 & M3_INFRAVISION) { /* although an infravision intrinsic is possible, infravision * is purely a property of the physical race. This means that we * must put the infravision flag in the player's current race @@ -2124,9 +2106,7 @@ role_init() * but since infravision has no effect for NPCs anyway we can * ignore this. */ - mons[urole.malenum].mflags3 |= M3_INFRAVISION; - if (urole.femalenum != NON_PM) - mons[urole.femalenum].mflags3 |= M3_INFRAVISION; + mons[gu.urole.mnum].mflags3 |= M3_INFRAVISION; } #endif /*0*/ @@ -2137,8 +2117,7 @@ role_init() } const char * -Hello(mtmp) -struct monst *mtmp; +Hello(struct monst *mtmp) { switch (Role_switch) { case PM_KNIGHT: @@ -2151,7 +2130,7 @@ struct monst *mtmp; return "Aloha"; /* Hawaiian */ case PM_VALKYRIE: return -#ifdef MAIL +#ifdef MAIL_STRUCTURES (mtmp && mtmp->data == &mons[PM_MAIL_DAEMON]) ? "Hallo" : #endif "Velkommen"; /* Norse */ @@ -2161,7 +2140,7 @@ struct monst *mtmp; } const char * -Goodbye() +Goodbye(void) { switch (Role_switch) { case PM_KNIGHT: @@ -2177,4 +2156,869 @@ Goodbye() } } +/* if pmindex is any player race (not necessarily the hero's), + return a pointer to the races[] entry for it; if pmindex is for some + other type of monster which isn't a player race, return Null */ +const struct Race * +character_race(short pmindex) +{ + const struct Race *r; + + for (r = races; r->noun != NULL; ++r) + if (r->mnum == pmindex) + return r; + return (const struct Race *) NULL; +} + +/*--------------------------------------------------------------------------*/ + +/* potential interface routine */ +void +genl_player_selection(void) +{ + if (genl_player_setup(0)) + return; + + /* player cancelled role/race/&c selection, so quit */ + nh_terminate(EXIT_SUCCESS); + /*NOTREACHED*/ +} + +#if defined(TTY_GRAPHICS) || defined(CURSES_GRAPHICS) || defined(SHIM_GRAPHICS) +/* ['#else' far below] */ + +staticfn boolean reset_role_filtering(void); +staticfn winid plsel_startmenu(int, int); +staticfn int maybe_skip_seps(int, int); +staticfn void setup_rolemenu(winid, boolean, int, int, int); +staticfn void setup_racemenu(winid, boolean, int, int, int); +staticfn void setup_gendmenu(winid, boolean, int, int, int); +staticfn void setup_algnmenu(winid, boolean, int, int, int); + +/* try to reduce clutter in the code below... */ +#define ROLE flags.initrole +#define RACE flags.initrace +#define GEND flags.initgend +#define ALGN flags.initalign + +/* guts of tty's player_selection() */ +int +genl_player_setup(int screenheight) +{ + char pbuf[QBUFSZ]; + anything any; + int i, k, n, choice, nextpick; + boolean getconfirmation, picksomething; + winid win = WIN_ERR; + menu_item *selected = 0; + int clr = NO_COLOR; + char pick4u = 'n'; + int result = 0; /* assume failure (player chooses to 'quit') */ + + program_state.in_role_selection++; /* affects tty menu cleanup */ + /* Used to avoid "Is this ok?" if player has already specified all + * four facets of role. + * Note that rigid_role_checks might force any unspecified facets to + * have a specific value, but that will still require confirmation; + * player can specify the forced ones if avoiding that is demanded. + */ + picksomething = (ROLE == ROLE_NONE || RACE == ROLE_NONE + || GEND == ROLE_NONE || ALGN == ROLE_NONE); + /* Used for '-@'; + * choose randomly without asking for all unspecified facets. + */ + if (flags.randomall && picksomething) { + if (ROLE == ROLE_NONE) + ROLE = ROLE_RANDOM; + if (RACE == ROLE_NONE) + RACE = ROLE_RANDOM; + if (GEND == ROLE_NONE) + GEND = ROLE_RANDOM; + if (ALGN == ROLE_NONE) + ALGN = ROLE_RANDOM; + } + + /* prevent unnecessary prompting if role forces race (samurai) or gender + (valkyrie) or alignment (rogue), or race forces alignment (orc), &c */ + rigid_role_checks(); + + if (ROLE == ROLE_NONE || RACE == ROLE_NONE + || GEND == ROLE_NONE || ALGN == ROLE_NONE) { + char *prompt = build_plselection_prompt(pbuf, QBUFSZ, + ROLE, RACE, GEND, ALGN); + /* prompt[] contains "Shall I pick ... for you? [ynaq] " + y - game picks role,&c then asks player to confirm; + n - player manually chooses via menu selections; + a - like 'y', but skips confirmation and starts game; + q - quit + */ +#if 1 + trimspaces(prompt); /* 'prompt' is constructed with trailing space */ + /* accept any character and do validation ourselves so that we can + shorten prompt; it will be "Shall I pick ... for you? [ynaq] " + with final space appended by yn_function() [for tty at least] */ + do { + pick4u = yn_function(prompt, (char *) 0, '\0', FALSE); + pick4u = lowc(pick4u); + if (pick4u == '\033' || pick4u == 'q') /* handle [q] */ + goto setup_done; + if (pick4u == ' ' || pick4u == '\n' || pick4u == '\r') + pick4u = 'y'; /* default */ + else if (pick4u == '@' || pick4u == '*') + pick4u = 'a'; /* similar to '-@' on command line */ + /* TODO? handle response of '?' */ + } while (pick4u != 'y' && pick4u != 'n' && pick4u != 'a'); /* [yna] */ + +#else /* slightly simpler but more likely to end up being wrapped */ + + char *p; + /* strip choices off prompt string; yn_function() will show them */ + if ((p = strchr(prompt, '[')) != 0) + *p = '\0'; + trimspaces(prompt); /* remove trailing space */ + /* prompt becomes "Shall I pick ... for you? [ynaq] (y) " + with " [ynaq] (y) " appended by yn_function() which also changes + user's and to 'y', to 'q' */ + pick4u = yn_function(prompt, ynaqchars, 'y', FALSE); + if (pick4u != 'y' && pick4u != 'a' && pick4u != 'n') + goto setup_done; /* bail */ +#endif + } + + makepicks: + nextpick = RS_ROLE; + do { + if (nextpick == RS_ROLE) { + nextpick = RS_RACE; + /* Select a role, if necessary; + we'll try to be compatible with pre-selected + race/gender/alignment, but may not succeed. */ + if (ROLE < 0) { + /* process the choice */ + if (pick4u == 'y' || pick4u == 'a' || ROLE == ROLE_RANDOM) { + /* pick a random role */ + k = pick_role(RACE, GEND, ALGN, PICK_RANDOM); + if (k < 0) { + pline("Incompatible role!"); + k = randrole(FALSE); + } + } else { + /* 'excess' is used to try to avoid tty pagination */ + int excess = maybe_skip_seps(screenheight, RS_ROLE); + + /* prompt for a role */ + win = plsel_startmenu(screenheight, RS_ROLE); + /* populate the menu with role choices */ + setup_rolemenu(win, TRUE, RACE, GEND, ALGN); + /* add miscellaneous menu entries */ + role_menu_extra(ROLE_RANDOM, win, TRUE); + any = cg.zeroany; /* separator, not a choice */ + if (excess < 1 || excess > 2) + add_menu_str(win, ""); + role_menu_extra(RS_RACE, win, FALSE); + role_menu_extra(RS_GENDER, win, FALSE); + role_menu_extra(RS_ALGNMNT, win, FALSE); + role_menu_extra(RS_filter, win, FALSE); + role_menu_extra(ROLE_NONE, win, FALSE); /* quit */ + Strcpy(pbuf, "Pick a role or profession"); + end_menu(win, pbuf); + n = select_menu(win, PICK_ONE, &selected); + /* + * PICK_ONE with preselected choice behaves strangely: + * n == -1 -- , so use quit choice; + * n == 0 -- explicitly chose preselected entry, + * toggling it off, so use it; + * n == 1 -- implicitly chose preselected entry + * with or ; + * n == 2 -- explicitly chose a different entry, so + * both it and preselected one are in list. + */ + if (n > 0) { + choice = selected[0].item.a_int; + if (n > 1 && choice == ROLE_RANDOM) + choice = selected[1].item.a_int; + } else + choice = (n == 0) ? ROLE_RANDOM : ROLE_NONE; + if (selected) + free((genericptr_t) selected), selected = 0; + destroy_nhwindow(win), win = WIN_ERR; + + if (choice == ROLE_NONE) { + goto setup_done; /* selected quit */ + } else if (choice == RS_menu_arg(RS_ALGNMNT)) { + ALGN = k = ROLE_NONE; + nextpick = RS_ALGNMNT; + } else if (choice == RS_menu_arg(RS_GENDER)) { + GEND = k = ROLE_NONE; + nextpick = RS_GENDER; + } else if (choice == RS_menu_arg(RS_RACE)) { + RACE = k = ROLE_NONE; + nextpick = RS_RACE; + } else if (choice == RS_menu_arg(RS_filter)) { + ROLE = k = ROLE_NONE; + (void) reset_role_filtering(); + nextpick = RS_ROLE; + } else if (choice == ROLE_RANDOM) { + k = pick_role(RACE, GEND, ALGN, PICK_RANDOM); + if (k < 0) + k = randrole(FALSE); + } else { + k = choice - 1; + } + } + ROLE = k; + } /* needed role */ + } /* picking role */ + + if (nextpick == RS_RACE) { + nextpick = (ROLE < 0) ? RS_ROLE : RS_GENDER; + /* Select a race, if necessary; + force compatibility with role, try for compatibility + with pre-selected gender/alignment. */ + if (RACE < 0 || !validrace(ROLE, RACE)) { + /* no race yet, or pre-selected race not valid */ + if (pick4u == 'y' || pick4u == 'a' || RACE == ROLE_RANDOM) { + k = pick_race(ROLE, GEND, ALGN, PICK_RANDOM); + if (k < 0) { + pline("Incompatible race!"); + k = randrace(ROLE); + } + } else { /* pick4u == 'n' */ + /* Count the number of valid races */ + n = 0; /* number valid */ + k = 0; /* valid race */ + for (i = 0; races[i].noun; i++) + if (ok_race(ROLE, i, GEND, ALGN)) { + n++; + k = i; + } + if (n == 0) { + for (i = 0; races[i].noun; i++) + if (validrace(ROLE, i)) { + n++; + k = i; + } + } + /* Permit the user to pick, if there is more than one */ + if (n > 1) { + win = plsel_startmenu(screenheight, RS_RACE); + any = cg.zeroany; /* zero out all bits */ + /* populate the menu with role choices */ + setup_racemenu(win, TRUE, ROLE, GEND, ALGN); + /* add miscellaneous menu entries */ + role_menu_extra(ROLE_RANDOM, win, TRUE); + any.a_int = 0; /* separator, not a choice */ + add_menu_str(win, ""); + role_menu_extra(RS_ROLE, win, FALSE); + role_menu_extra(RS_GENDER, win, FALSE); + role_menu_extra(RS_ALGNMNT, win, FALSE); + role_menu_extra(RS_filter, win, FALSE); + role_menu_extra(ROLE_NONE, win, FALSE); /* quit */ + Strcpy(pbuf, "Pick a race or species"); + end_menu(win, pbuf); + n = select_menu(win, PICK_ONE, &selected); + if (n > 0) { + choice = selected[0].item.a_int; + if (n > 1 && choice == ROLE_RANDOM) + choice = selected[1].item.a_int; + } else + choice = (n == 0) ? ROLE_RANDOM : ROLE_NONE; + if (selected) + free((genericptr_t) selected), selected = 0; + destroy_nhwindow(win), win = WIN_ERR; + + if (choice == ROLE_NONE) { + goto setup_done; /* selected quit */ + } else if (choice == RS_menu_arg(RS_ALGNMNT)) { + ALGN = k = ROLE_NONE; + nextpick = RS_ALGNMNT; + } else if (choice == RS_menu_arg(RS_GENDER)) { + GEND = k = ROLE_NONE; + nextpick = RS_GENDER; + } else if (choice == RS_menu_arg(RS_ROLE)) { + ROLE = k = ROLE_NONE; + nextpick = RS_ROLE; + } else if (choice == RS_menu_arg(RS_filter)) { + RACE = k = ROLE_NONE; + if (reset_role_filtering()) + nextpick = RS_ROLE; + else + nextpick = RS_RACE; + } else if (choice == ROLE_RANDOM) { + k = pick_race(ROLE, GEND, ALGN, PICK_RANDOM); + if (k < 0) + k = randrace(ROLE); + } else { + k = choice - 1; + } + } + } + RACE = k; + } /* needed race */ + } /* picking race */ + + if (nextpick == RS_GENDER) { + nextpick = (ROLE < 0) ? RS_ROLE : (RACE < 0) ? RS_RACE + : RS_ALGNMNT; + /* Select a gender, if necessary; + force compatibility with role/race, try for compatibility + with pre-selected alignment. */ + if (GEND < 0 || !validgend(ROLE, RACE, GEND)) { + /* no gender yet, or pre-selected gender not valid */ + if (pick4u == 'y' || pick4u == 'a' || GEND == ROLE_RANDOM) { + k = pick_gend(ROLE, RACE, ALGN, PICK_RANDOM); + if (k < 0) { + pline("Incompatible gender!"); + k = randgend(ROLE, RACE); + } + } else { /* pick4u == 'n' */ + /* Count the number of valid genders */ + n = 0; /* number valid */ + k = 0; /* valid gender */ + for (i = 0; i < ROLE_GENDERS; i++) + if (ok_gend(ROLE, RACE, i, ALGN)) { + n++; + k = i; + } + if (n == 0) { + for (i = 0; i < ROLE_GENDERS; i++) + if (validgend(ROLE, RACE, i)) { + n++; + k = i; + } + } + /* Permit the user to pick, if there is more than one */ + if (n > 1) { + win = plsel_startmenu(screenheight, RS_GENDER); + any = cg.zeroany; /* zero out all bits */ + /* populate the menu with gender choices */ + setup_gendmenu(win, TRUE, ROLE, RACE, ALGN); + /* add miscellaneous menu entries */ + role_menu_extra(ROLE_RANDOM, win, TRUE); + any.a_int = 0; /* separator, not a choice */ + add_menu_str(win, ""); + role_menu_extra(RS_ROLE, win, FALSE); + role_menu_extra(RS_RACE, win, FALSE); + role_menu_extra(RS_ALGNMNT, win, FALSE); + role_menu_extra(RS_filter, win, FALSE); + role_menu_extra(ROLE_NONE, win, FALSE); /* quit */ + Strcpy(pbuf, "Pick a gender or sex"); + end_menu(win, pbuf); + n = select_menu(win, PICK_ONE, &selected); + if (n > 0) { + choice = selected[0].item.a_int; + if (n > 1 && choice == ROLE_RANDOM) + choice = selected[1].item.a_int; + } else + choice = (n == 0) ? ROLE_RANDOM : ROLE_NONE; + if (selected) + free((genericptr_t) selected), selected = 0; + destroy_nhwindow(win), win = WIN_ERR; + + if (choice == ROLE_NONE) { + goto setup_done; /* selected quit */ + } else if (choice == RS_menu_arg(RS_ALGNMNT)) { + ALGN = k = ROLE_NONE; + nextpick = RS_ALGNMNT; + } else if (choice == RS_menu_arg(RS_RACE)) { + RACE = k = ROLE_NONE; + nextpick = RS_RACE; + } else if (choice == RS_menu_arg(RS_ROLE)) { + ROLE = k = ROLE_NONE; + nextpick = RS_ROLE; + } else if (choice == RS_menu_arg(RS_filter)) { + GEND = k = ROLE_NONE; + if (reset_role_filtering()) + nextpick = RS_ROLE; + else + nextpick = RS_GENDER; + } else if (choice == ROLE_RANDOM) { + k = pick_gend(ROLE, RACE, ALGN, PICK_RANDOM); + if (k < 0) + k = randgend(ROLE, RACE); + } else { + k = choice - 1; + } + } + } + GEND = k; + } /* needed gender */ + } /* picking gender */ + + if (nextpick == RS_ALGNMNT) { + nextpick = (ROLE < 0) ? RS_ROLE + : (RACE < 0) ? RS_RACE + : RS_GENDER; + /* Select an alignment, if necessary; + force compatibility with role/race/gender. */ + if (ALGN < 0 || !validalign(ROLE, RACE, ALGN)) { + /* no alignment yet, or pre-selected alignment not valid */ + if (pick4u == 'y' || pick4u == 'a' || ALGN == ROLE_RANDOM) { + k = pick_align(ROLE, RACE, GEND, PICK_RANDOM); + if (k < 0) { + pline("Incompatible alignment!"); + k = randalign(ROLE, RACE); + } + } else { /* pick4u == 'n' */ + /* Count the number of valid alignments */ + n = 0; /* number valid */ + k = 0; /* valid alignment */ + for (i = 0; i < ROLE_ALIGNS; i++) + if (ok_align(ROLE, RACE, GEND, i)) { + n++; + k = i; + } + if (n == 0) { + for (i = 0; i < ROLE_ALIGNS; i++) + if (validalign(ROLE, RACE, i)) { + n++; + k = i; + } + } + /* Permit the user to pick, if there is more than one */ + if (n > 1) { + win = plsel_startmenu(screenheight, RS_ALGNMNT); + any = cg.zeroany; /* zero out all bits */ + setup_algnmenu(win, TRUE, ROLE, RACE, GEND); + role_menu_extra(ROLE_RANDOM, win, TRUE); + any.a_int = 0; /* separator, not a choice */ + add_menu_str(win, ""); + role_menu_extra(RS_ROLE, win, FALSE); + role_menu_extra(RS_RACE, win, FALSE); + role_menu_extra(RS_GENDER, win, FALSE); + role_menu_extra(RS_filter, win, FALSE); + role_menu_extra(ROLE_NONE, win, FALSE); /* quit */ + Strcpy(pbuf, "Pick an alignment or creed"); + end_menu(win, pbuf); + n = select_menu(win, PICK_ONE, &selected); + if (n > 0) { + choice = selected[0].item.a_int; + if (n > 1 && choice == ROLE_RANDOM) + choice = selected[1].item.a_int; + } else + choice = (n == 0) ? ROLE_RANDOM : ROLE_NONE; + if (selected) + free((genericptr_t) selected), selected = 0; + destroy_nhwindow(win), win = WIN_ERR; + + if (choice == ROLE_NONE) { + goto setup_done; /* selected quit */ + } else if (choice == RS_menu_arg(RS_GENDER)) { + GEND = k = ROLE_NONE; + nextpick = RS_GENDER; + } else if (choice == RS_menu_arg(RS_RACE)) { + RACE = k = ROLE_NONE; + nextpick = RS_RACE; + } else if (choice == RS_menu_arg(RS_ROLE)) { + ROLE = k = ROLE_NONE; + nextpick = RS_ROLE; + } else if (choice == RS_menu_arg(RS_filter)) { + ALGN = k = ROLE_NONE; + if (reset_role_filtering()) + nextpick = RS_ROLE; + else + nextpick = RS_ALGNMNT; + } else if (choice == ROLE_RANDOM) { + k = pick_align(ROLE, RACE, GEND, PICK_RANDOM); + if (k < 0) + k = randalign(ROLE, RACE); + } else { + k = choice - 1; + } + } + } + ALGN = k; + } /* needed alignment */ + } /* picking alignment */ + + } while (ROLE < 0 || RACE < 0 || GEND < 0 || ALGN < 0); + + /* + * Role, race, &c have now been determined; + * ask for confirmation and maybe go back to choose all over again. + * + * Uses ynaq for familiarity, although 'a' is usually a + * superset of 'y' but here is an alternate form of 'n'. + * Menu layout: + * title: Is this ok? [ynaq] + * blank: + * text: $name, $alignment $gender $race $role + * blank: + * menu: y + yes; play + * n - no; pick again + * maybe: a - no; rename hero + * q - quit + * (end) + */ + getconfirmation = (picksomething && pick4u != 'a' && !flags.randomall); + while (getconfirmation) { + win = plsel_startmenu(screenheight, RS_filter); /* filter: not ROLE */ + any = cg.zeroany; /* zero out all bits */ + /* [ynaq] menu choices */ + any.a_int = 1; + add_menu(win, &nul_glyphinfo, &any, 'y', 0, + ATR_NONE, clr, "Yes; start game", MENU_ITEMFLAGS_SELECTED); + any.a_int = 2; + add_menu(win, &nul_glyphinfo, &any, 'n', 0, + ATR_NONE, clr, "No; choose role again", MENU_ITEMFLAGS_NONE); + if (iflags.renameallowed) { + any.a_int = 3; + add_menu(win, &nul_glyphinfo, &any, 'a', 0, ATR_NONE, + clr, "Not yet; choose another name", + MENU_ITEMFLAGS_NONE); + } + any.a_int = -1; + add_menu(win, &nul_glyphinfo, &any, 'q', 0, + ATR_NONE, clr, "Quit", MENU_ITEMFLAGS_NONE); + Sprintf(pbuf, "Is this ok? [yn%sq]", iflags.renameallowed ? "a" : ""); + end_menu(win, pbuf); + n = select_menu(win, PICK_ONE, &selected); + /* [pick-one menus with a preselected entry behave oddly...] */ + choice = (n > 0) ? selected[n - 1].item.a_int : (n == 0) ? 1 : -1; + if (selected) + free((genericptr_t) selected), selected = 0; + destroy_nhwindow(win); + + switch (choice) { + default: /* 'q' or ESC */ + goto setup_done; /* quit */ + break; + case 3: { /* 'a' */ + /* + * TODO: what, if anything, should be done if the name is + * changed to or from "wizard" after port-specific startup + * code has set flags.debug based on the original name? + */ + int saveROLE, saveRACE, saveGEND, saveALGN; + + iflags.renameinprogress = TRUE; /* affects main() in unixmain.c */ + /* plnamesuffix() can change any or all of ROLE, RACE, + GEND, ALGN; we'll override that and honor only the name */ + saveROLE = ROLE, saveRACE = RACE, + saveGEND = GEND, saveALGN = ALGN; + svp.plname[0] = '\0'; + plnamesuffix(); /* calls askname() when svp.plname[] is empty */ + ROLE = saveROLE, RACE = saveRACE, + GEND = saveGEND, ALGN = saveALGN; + break; /* getconfirmation is still True */ + } + case 2: /* 'n' */ + /* start fresh, but bypass "shall I pick everything for you?" + step; any partial role selection via config file, command + line, or name suffix is discarded this time */ + pick4u = 'n'; + ROLE = RACE = GEND = ALGN = ROLE_NONE; + goto makepicks; + break; + case 1: /* 'y' or Space or Return/Enter */ + /* success; drop out through end of function */ + getconfirmation = FALSE; + break; + } + } /* while 'getconfirmation' */ + /* Success! */ + result = 1; + + setup_done: + program_state.in_role_selection--; + return result; +} + +staticfn boolean +reset_role_filtering(void) +{ + winid win; + int i, n; + char filterprompt[QBUFSZ]; + menu_item *selected = 0; + + win = create_nhwindow(NHW_MENU); + start_menu(win, MENU_BEHAVE_STANDARD); + + /* no extra blank line preceding this entry; end_menu supplies one */ + add_menu_str(win, "Unacceptable roles"); + setup_rolemenu(win, FALSE, ROLE_NONE, ROLE_NONE, ROLE_NONE); + + add_menu_str(win, ""); + add_menu_str(win, "Unacceptable races"); + setup_racemenu(win, FALSE, ROLE_NONE, ROLE_NONE, ROLE_NONE); + + add_menu_str(win, ""); + add_menu_str(win, "Unacceptable genders"); + setup_gendmenu(win, FALSE, ROLE_NONE, ROLE_NONE, ROLE_NONE); + + add_menu_str(win, ""); + add_menu_str(win, "Unacceptable alignments"); + setup_algnmenu(win, FALSE, ROLE_NONE, ROLE_NONE, ROLE_NONE); + + Sprintf(filterprompt, "Pick all that apply%s", + gotrolefilter() ? " and/or unpick any that no longer apply" : ""); + end_menu(win, filterprompt); + n = select_menu(win, PICK_ANY, &selected); + + if (n >= 0) { /* n==0: clear current filters and don't set new ones */ + clearrolefilter(RS_filter); + for (i = 0; i < n; i++) + setrolefilter(selected[i].item.a_string); + + ROLE = RACE = GEND = ALGN = ROLE_NONE; + } + if (selected) + free((genericptr_t) selected), selected = 0; + destroy_nhwindow(win); + return (n > 0) ? TRUE : FALSE; +} + +/* the change in format when this extended role selection was converted from + tty-only to tty+curses+? made the role selection menu require two pages + on a traditional 24-line tty; that wasn't fair to tty, so squeeze out + some blank separator lines from the menu if that will make it fit on one */ +staticfn int +maybe_skip_seps(int rows, int aspect) +{ + int i, n = 0; + + /* not much point to generalizing this to other aspects */ + if (aspect != RS_ROLE) + return 0; + /* + * If there are one or two excess lines, setup_rolemenu() will omit + * the separator between 'random' and 'pick race first'. If there are + * two, plsel_startmenu() will omit the one between role info so far + * (" ...") and the set of role entries. + */ + + n += 4; /* title and ensuing separator, role info so far and separator */ + for (i = 0; roles[i].name.m; ++i) + if (ok_role(i, RACE, GEND, ALGN) && ok_race(i, RACE, GEND, ALGN) + && ok_gend(i, RACE, GEND, ALGN) && ok_align(i, RACE, GEND, ALGN)) + ++n; + n += 2; /* 'random' and separator */ + n += 5; /* race 1st, gender 1st, alignment 1st, reset filter, quit */ + n += 1; /* footer/prompt */ + if (rows > 0 && n > rows) + return n - rows; + return 0; +} + +/* start a menu; show role aspects specified so far as a header line */ +staticfn winid +plsel_startmenu(int ttyrows, int aspect) +{ + char qbuf[QBUFSZ]; + winid win; + const char *rolename; + + /* whatever aspect was just chosen might force others (Orc => chaotic, + Samurai => Human+lawful, Valkyrie => female) */ + rigid_role_checks(); + + rolename = (ROLE < 0) ? "" + : (GEND == 1 && roles[ROLE].name.f) ? roles[ROLE].name.f + : roles[ROLE].name.m; + if (!svp.plname[0] || ROLE < 0 || RACE < 0 || GEND < 0 || ALGN < 0) { + /* " " */ + Sprintf(qbuf, "%.20s %.20s %.20s %.20s", + rolename, + (RACE < 0) ? "" : races[RACE].noun, + (GEND < 0) ? "" : genders[GEND].adj, + (ALGN < 0) ? "" : aligns[ALGN].adj); + } else { + /* " the " */ + Sprintf(qbuf, "%.20s the %.20s %.20s %.20s %.20s", + svp.plname, + aligns[ALGN].adj, + genders[GEND].adj, + races[RACE].adj, + rolename); + } + + win = create_nhwindow(NHW_MENU); + if (win == WIN_ERR) + panic("could not create role selection window"); + start_menu(win, MENU_BEHAVE_STANDARD); + + add_menu_str(win, qbuf); + if (maybe_skip_seps(ttyrows, aspect) != 2) + add_menu_str(win, ""); + return win; +} + +#undef ROLE +#undef RACE +#undef GEND +#undef ALGN + +/* add entries a-Archeologist, b-Barbarian, &c to menu being built in 'win' */ +staticfn void +setup_rolemenu( + winid win, + boolean filtering, /* True => exclude filtered roles; + * False => filter reset */ + int race, int gend, int algn) /* all ROLE_NONE for !filtering case */ +{ + anything any; + int i; + boolean role_ok; + char thisch, lastch = '\0', rolenamebuf[50]; + int clr = NO_COLOR; + + any = cg.zeroany; /* zero out all bits */ + for (i = 0; roles[i].name.m; i++) { + /* role can be constrained by any of race, gender, or alignment */ + role_ok = (ok_role(i, race, gend, algn) + && ok_race(i, race, gend, algn) + && ok_gend(i, race, gend, algn) + && ok_align(i, race, gend, algn)); + if (filtering && !role_ok) + continue; + if (filtering) + any.a_int = i + 1; + else + any.a_string = roles[i].name.m; + thisch = lowc(*roles[i].name.m); + if (thisch == lastch) + thisch = highc(thisch); + Strcpy(rolenamebuf, roles[i].name.m); + if (roles[i].name.f) { + /* role has distinct name for female (C,P) */ + if (gend == 1) { + /* female already chosen; replace male name */ + Strcpy(rolenamebuf, roles[i].name.f); + } else if (gend < 0) { + /* not chosen yet; append slash+female name */ + Strcat(rolenamebuf, "/"); + Strcat(rolenamebuf, roles[i].name.f); + } + } + /* !filtering implies reset_role_filtering() where we want to + mark this role as preselected if current filter excludes it */ + add_menu(win, &nul_glyphinfo, &any, thisch, 0, + ATR_NONE, clr, an(rolenamebuf), + (!filtering && !role_ok) + ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); + lastch = thisch; + } +} + +staticfn void +setup_racemenu( + winid win, + boolean filtering, + int role, int gend, int algn) +{ + anything any; + boolean race_ok; + int i; + char this_ch; + int clr = NO_COLOR; + + any = cg.zeroany; + for (i = 0; races[i].noun; i++) { + /* no ok_gend(); race isn't constrained by gender */ + race_ok = (ok_race(role, i, gend, algn) + && ok_role(role, i, gend, algn) + && ok_align(role, i, gend, algn)); + if (filtering && !race_ok) + continue; + if (filtering) + any.a_int = i + 1; + else + any.a_string = races[i].noun; + this_ch = *races[i].noun; + /* filtering: picking race, so choose by first letter, with + capital letter as unseen accelerator; + !filtering: resetting filter rather than picking, choose by + capital letter since lowercase role letters will be present */ + add_menu(win, &nul_glyphinfo, &any, + filtering ? this_ch : highc(this_ch), + filtering ? highc(this_ch) : 0, + ATR_NONE, clr, races[i].noun, + (!filtering && !race_ok) + ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); + } +} + +staticfn void +setup_gendmenu( + winid win, + boolean filtering, + int role, int race, int algn) +{ + anything any; + boolean gend_ok; + int i; + char this_ch; + int clr = NO_COLOR; + + any = cg.zeroany; + for (i = 0; i < ROLE_GENDERS; i++) { + /* no ok_align(); gender isn't constrained by alignment */ + gend_ok = (ok_gend(role, race, i, algn) + && ok_role(role, race, i, algn) + && ok_race(role, race, i, algn)); + if (filtering && !gend_ok) + continue; + if (filtering) + any.a_int = i + 1; + else + any.a_string = genders[i].adj; + this_ch = *genders[i].adj; + /* (see setup_racemenu for explanation of selector letters + and setup_rolemenu for preselection) */ + add_menu(win, &nul_glyphinfo, &any, + filtering ? this_ch : highc(this_ch), + filtering ? highc(this_ch) : 0, + ATR_NONE, clr, genders[i].adj, + (!filtering && !gend_ok) + ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); + } +} + +staticfn void +setup_algnmenu( + winid win, + boolean filtering, + int role, int race, int gend) +{ + anything any; + boolean algn_ok; + int i; + char this_ch; + int clr = NO_COLOR; + + any = cg.zeroany; + for (i = 0; i < ROLE_ALIGNS; i++) { + /* no ok_gend(); alignment isn't constrained by gender */ + algn_ok = (ok_align(role, race, gend, i) + && ok_role(role, race, gend, i) + && ok_race(role, race, gend, i)); + if (filtering && !algn_ok) + continue; + if (filtering) + any.a_int = i + 1; + else + any.a_string = aligns[i].adj; + this_ch = *aligns[i].adj; + /* (see setup_racemenu for explanation of selector letters + and setup_rolemenu for preselection) */ + add_menu(win, &nul_glyphinfo, &any, + filtering ? this_ch : highc(this_ch), + filtering ? highc(this_ch) : 0, + ATR_NONE, clr, aligns[i].adj, + (!filtering && !algn_ok) + ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); + } +} + +#else /* !TTY_GRAPHICS */ + +int +genl_player_setup(int screenheight UNUSED) +{ + return 0; +} + +#endif /* ?TTY_GRAPHICS */ + /* role.c */ diff --git a/src/rumors.c b/src/rumors.c index ac82b44fe..22d3e3077 100644 --- a/src/rumors.c +++ b/src/rumors.c @@ -1,13 +1,13 @@ -/* NetHack 3.6 rumors.c $NHDT-Date: 1583445339 2020/03/05 21:55:39 $ $NHDT-Branch: NetHack-3.6-Mar2020 $:$NHDT-Revision: 1.38 $ */ +/* NetHack 5.0 rumors.c $NHDT-Date: 1594370241 2020/07/10 08:37:21 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.56 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2012. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -#include "lev.h" #include "dlb.h" -/* [note: this comment is fairly old, but still accurate for 3.1] +/* [Note: this comment is fairly old, but still accurate for 3.1; + * it's no longer accurate for 5.0 but may still be of interest.] * Rumors have been entirely rewritten to speed up the access. This is * essential when working from floppies. Using fseek() the way that's done * here means rumors following longer rumors are output more often than those @@ -41,24 +41,48 @@ * and placed there by 'makedefs'. */ -STATIC_DCL void FDECL(init_rumors, (dlb *)); -STATIC_DCL void FDECL(init_oracles, (dlb *)); -STATIC_DCL void FDECL(couldnt_open_file, (const char *)); - -/* rumor size variables are signed so that value -1 can be used as a flag */ -static long true_rumor_size = 0L, false_rumor_size; -/* rumor start offsets are unsigned because they're handled via %lx format */ -static unsigned long true_rumor_start, false_rumor_start; -/* rumor end offsets are signed because they're compared with [dlb_]ftell() */ -static long true_rumor_end, false_rumor_end; -/* oracles are handled differently from rumors... */ -static int oracle_flg = 0; /* -1=>don't use, 0=>need init, 1=>init done */ -static unsigned oracle_cnt = 0; -static unsigned long *oracle_loc = 0; - -STATIC_OVL void -init_rumors(fp) -dlb *fp; +#ifndef SFCTOOL +staticfn void unpadline(char *); +staticfn void init_rumors(dlb *); +staticfn char *get_rnd_line(dlb *, char *, unsigned, int (*)(int), + long, long, unsigned); +staticfn void init_oracles(dlb *); +staticfn void others_check(const char *ftype, const char *, winid *); +staticfn void couldnt_open_file(const char *); +staticfn void init_CapMons(void); + +/* used by CapitalMon(); set up by init_CapMons(), released by free_CapMons(); + there's no need for these to be put into 'struct instance_globals g' */ +static unsigned CapMonstCnt = 0, CapBogonCnt = 0, + CapMonSiz = 0; /* CapMonstCnt+CapBogonCnt+1 when non-zero */ +static const char **CapMons = 0; + +/* list of bogusmons prefixes used to indicate special monster type such as + unique or always a particular gender; see dat/bogusmon.txt */ +extern const char bogon_codes[]; /* from do_name.c */ + +/* makedefs pads short rumors, epitaphs, engravings, and hallucinatory + monster names with trailing underscores; strip those off */ +staticfn void +unpadline(char *line) +{ + char *p = eos(line); + + /* remove newline if still present; caller should have stripped it */ + if (p > line && p[-1] == '\n') + --p; + + /* remove padding */ + while (p > line && p[-1] == '_') + --p; + + *p = '\0'; +} + +DISABLE_WARNING_FORMAT_NONLITERAL + +staticfn void +init_rumors(dlb *fp) { static const char rumors_header[] = "%d,%ld,%lx;%d,%ld,%lx;0,0,%lx\n"; int true_count, false_count; /* in file but not used here */ @@ -67,51 +91,54 @@ dlb *fp; (void) dlb_fgets(line, sizeof line, fp); /* skip "don't edit" comment */ (void) dlb_fgets(line, sizeof line, fp); - if (sscanf(line, rumors_header, &true_count, &true_rumor_size, - &true_rumor_start, &false_count, &false_rumor_size, - &false_rumor_start, &eof_offset) == 7 - && true_rumor_size > 0L - && false_rumor_size > 0L) { - true_rumor_end = (long) true_rumor_start + true_rumor_size; - /* assert( true_rumor_end == false_rumor_start ); */ - false_rumor_end = (long) false_rumor_start + false_rumor_size; - /* assert( false_rumor_end == eof_offset ); */ + if (sscanf(line, rumors_header, &true_count, >.true_rumor_size, + >.true_rumor_start, &false_count, &gf.false_rumor_size, + &gf.false_rumor_start, &eof_offset) == 7 + && gt.true_rumor_size > 0L + && gf.false_rumor_size > 0L) { + gt.true_rumor_end = (long) gt.true_rumor_start + gt.true_rumor_size; + /* assert( gt.true_rumor_end == false_rumor_start ); */ + gf.false_rumor_end = (long) gf.false_rumor_start + gf.false_rumor_size; + /* assert( gf.false_rumor_end == eof_offset ); */ } else { - true_rumor_size = -1L; /* init failed */ + gt.true_rumor_size = -1L; /* init failed */ (void) dlb_fclose(fp); } } +RESTORE_WARNING_FORMAT_NONLITERAL + /* exclude_cookie is a hack used because we sometimes want to get rumors in a * context where messages such as "You swallowed the fortune!" that refer to * cookies should not appear. This has no effect for true rumors since none * of them contain such references anyway. */ char * -getrumor(truth, rumor_buf, exclude_cookie) -int truth; /* 1=true, -1=false, 0=either */ -char *rumor_buf; -boolean exclude_cookie; +getrumor( + int truth, /* 1=true, -1=false, 0=either */ + char *rumor_buf, + boolean exclude_cookie) { dlb *rumors; - long tidbit, beginning; - char *endp, line[BUFSZ], xbuf[BUFSZ]; + long beginning, ending; + char line[BUFSZ]; + static const char *cookie_marker = "[cookie] "; + const int marklen = strlen(cookie_marker); rumor_buf[0] = '\0'; - if (true_rumor_size < 0L) /* we couldn't open RUMORFILE */ + if (gt.true_rumor_size < 0L) /* a previous try failed to open RUMORFILE */ return rumor_buf; rumors = dlb_fopen(RUMORFILE, "r"); - if (rumors) { int count = 0; int adjtruth; do { rumor_buf[0] = '\0'; - if (true_rumor_size == 0L) { /* if this is 1st outrumor() */ + if (gt.true_rumor_size == 0L) { /* if this is 1st outrumor() */ init_rumors(rumors); - if (true_rumor_size < 0L) { /* init failed */ + if (gt.true_rumor_size < 0L) { /* init failed */ Sprintf(rumor_buf, "Error reading \"%.80s\".", RUMORFILE); return rumor_buf; } @@ -124,85 +151,62 @@ boolean exclude_cookie; switch (adjtruth = truth + rn2(2)) { case 2: /*(might let a bogus input arg sneak thru)*/ case 1: - beginning = (long) true_rumor_start; - tidbit = rn2(true_rumor_size); + beginning = (long) gt.true_rumor_start; + ending = gt.true_rumor_end; break; case 0: /* once here, 0 => false rather than "either"*/ case -1: - beginning = (long) false_rumor_start; - tidbit = rn2(false_rumor_size); + beginning = (long) gf.false_rumor_start; + ending = gf.false_rumor_end; break; default: impossible("strange truth value for rumor"); return strcpy(rumor_buf, "Oops..."); } - (void) dlb_fseek(rumors, beginning + tidbit, SEEK_SET); - (void) dlb_fgets(line, sizeof line, rumors); - if (!dlb_fgets(line, sizeof line, rumors) - || (adjtruth > 0 && dlb_ftell(rumors) > true_rumor_end)) { - /* reached end of rumors -- go back to beginning */ - (void) dlb_fseek(rumors, beginning, SEEK_SET); - (void) dlb_fgets(line, sizeof line, rumors); - } - if ((endp = index(line, '\n')) != 0) - *endp = 0; - Strcat(rumor_buf, xcrypt(line, xbuf)); - } while ( - count++ < 50 && exclude_cookie - && (strstri(rumor_buf, "fortune") || strstri(rumor_buf, "pity"))); + Strcpy(rumor_buf, + get_rnd_line(rumors, line, (unsigned) sizeof line, rn2, + beginning, ending, MD_PAD_RUMORS)); + } while (count++ < 50 && exclude_cookie + && !strncmp(rumor_buf, cookie_marker, marklen)); (void) dlb_fclose(rumors); if (count >= 50) impossible("Can't find non-cookie rumor?"); - else if (!in_mklev) /* avoid exercizing wisdom for graffiti */ + else if (!gi.in_mklev) /* avoid exercising wisdom for graffiti */ exercise(A_WIS, (adjtruth > 0)); } else { couldnt_open_file(RUMORFILE); - true_rumor_size = -1; /* don't try to open it again */ + gt.true_rumor_size = -1; /* don't try to open it again */ } - - /* this is safe either way, so do it always since we can't get the - * definition out of makedefs.c - */ -#define PAD_RUMORS_TO -#ifdef PAD_RUMORS_TO - /* remove padding */ - { - char *x = eos(rumor_buf) - 1; - - while (x > rumor_buf && *x == '_') - x--; - *++x = '\n'; - *x = '\0'; + if (!exclude_cookie + && !strncmp(rumor_buf, cookie_marker, marklen)) { + /* remove cookie_marker from the string */ + char *src = rumor_buf + marklen; + char *dst = rumor_buf; + for (; *src != '\0'; ++src, ++dst) { + *dst = *src; + } + *dst = '\0'; /* terminator wasn't copied */ } -#endif return rumor_buf; } -/* - * test that the true/false rumor boundaries are valid. - */ +/* test that the true/false rumor boundaries are valid and show the first + two and very last epitaphs, engravings, and bogus monsters */ void -rumor_check() +rumor_check(void) { - dlb *rumors = 0; - winid tmpwin; + dlb *rumors; + winid tmpwin = WIN_ERR; char *endp, line[BUFSZ], xbuf[BUFSZ], rumor_buf[BUFSZ]; - if (true_rumor_size < 0L) { /* we couldn't open RUMORFILE */ - no_rumors: - pline("rumors not accessible."); - return; - } - - rumors = dlb_fopen(RUMORFILE, "r"); - + rumors = (gt.true_rumor_size >= 0) ? dlb_fopen(RUMORFILE, "r") : 0; if (rumors) { long ftell_rumor_start = 0L; rumor_buf[0] = '\0'; - if (true_rumor_size == 0L) { /* if this is 1st outrumor() */ + if (gt.true_rumor_size == 0L) { /* if this is 1st outrumor() */ init_rumors(rumors); - if (true_rumor_size < 0L) { + if (gt.true_rumor_size < 0L) { rumors = (dlb *) 0; /* init_rumors() closes it upon failure */ goto no_rumors; /* init failed */ } @@ -214,15 +218,15 @@ rumor_check() */ Sprintf(rumor_buf, "T start=%06ld (%06lx), end=%06ld (%06lx), size=%06ld (%06lx)", - (long) true_rumor_start, true_rumor_start, - true_rumor_end, (unsigned long) true_rumor_end, - true_rumor_size, (unsigned long) true_rumor_size); + (long) gt.true_rumor_start, gt.true_rumor_start, + gt.true_rumor_end, (unsigned long) gt.true_rumor_end, + gt.true_rumor_size,(unsigned long) gt.true_rumor_size); putstr(tmpwin, 0, rumor_buf); Sprintf(rumor_buf, "F start=%06ld (%06lx), end=%06ld (%06lx), size=%06ld (%06lx)", - (long) false_rumor_start, false_rumor_start, - false_rumor_end, (unsigned long) false_rumor_end, - false_rumor_size, (unsigned long) false_rumor_size); + (long) gf.false_rumor_start, gf.false_rumor_start, + gf.false_rumor_end, (unsigned long) gf.false_rumor_end, + gf.false_rumor_size, (unsigned long) gf.false_rumor_size); putstr(tmpwin, 0, rumor_buf); /* @@ -233,103 +237,298 @@ rumor_check() * the value read in rumors, and display it. */ rumor_buf[0] = '\0'; - (void) dlb_fseek(rumors, (long) true_rumor_start, SEEK_SET); + (void) dlb_fseek(rumors, (long) gt.true_rumor_start, SEEK_SET); ftell_rumor_start = dlb_ftell(rumors); (void) dlb_fgets(line, sizeof line, rumors); - if ((endp = index(line, '\n')) != 0) + if ((endp = strchr(line, '\n')) != 0) *endp = 0; Sprintf(rumor_buf, "T %06ld %s", ftell_rumor_start, xcrypt(line, xbuf)); putstr(tmpwin, 0, rumor_buf); /* find last true rumor */ while (dlb_fgets(line, sizeof line, rumors) - && dlb_ftell(rumors) < true_rumor_end) + && dlb_ftell(rumors) < gt.true_rumor_end) continue; - if ((endp = index(line, '\n')) != 0) + if ((endp = strchr(line, '\n')) != 0) *endp = 0; Sprintf(rumor_buf, " %6s %s", "", xcrypt(line, xbuf)); putstr(tmpwin, 0, rumor_buf); rumor_buf[0] = '\0'; - (void) dlb_fseek(rumors, (long) false_rumor_start, SEEK_SET); + (void) dlb_fseek(rumors, (long) gf.false_rumor_start, SEEK_SET); ftell_rumor_start = dlb_ftell(rumors); (void) dlb_fgets(line, sizeof line, rumors); - if ((endp = index(line, '\n')) != 0) + if ((endp = strchr(line, '\n')) != 0) *endp = 0; Sprintf(rumor_buf, "F %06ld %s", ftell_rumor_start, xcrypt(line, xbuf)); putstr(tmpwin, 0, rumor_buf); /* find last false rumor */ while (dlb_fgets(line, sizeof line, rumors) - && dlb_ftell(rumors) < false_rumor_end) + && dlb_ftell(rumors) < gf.false_rumor_end) continue; - if ((endp = index(line, '\n')) != 0) + if ((endp = strchr(line, '\n')) != 0) *endp = 0; Sprintf(rumor_buf, " %6s %s", "", xcrypt(line, xbuf)); putstr(tmpwin, 0, rumor_buf); (void) dlb_fclose(rumors); + + /* if a previous attempt couldn't open file or rejected its contents, + we didn't bother trying again this time */ + } else if (gt.true_rumor_size < 0L) { + no_rumors: /* file could be opened but init_rumors() didn't like it */ + pline("rumors not accessible."); + /* engravings, epitaphs, and bogus monsters will still be shown, + and in tmpwin rather than via additional pline() calls */ + display_nhwindow(WIN_MESSAGE, TRUE); /* --more-- */ + + /* first attempt to open file has just failed */ + } else { + couldnt_open_file(RUMORFILE); + gt.true_rumor_size = -1; /* don't try to open it again */ + } + + /* initial implementation of default epitaph/engraving/bogusmon + contained an error; check those along with rumors */ + others_check("Engravings:", ENGRAVEFILE, &tmpwin); + others_check("Epitaphs:", EPITAPHFILE, &tmpwin); + others_check("Bogus monsters:", BOGUSMONFILE, &tmpwin); + + if (tmpwin != WIN_ERR) { display_nhwindow(tmpwin, TRUE); destroy_nhwindow(tmpwin); + } +} + +DISABLE_WARNING_FORMAT_NONLITERAL + +/* 5.0: augments rumors_check(); test 'engrave' or 'epitaph' or 'bogusmon' */ +staticfn void +others_check( + const char *ftype, /* header: "{Engravings|Epitaphs|Bogus monsters}:" */ + const char *fname, /* filename: {ENGRAVEFILE|EPITAPHFILE|BOGUSMONFILE} */ + winid *winptr) /* text window for output; created here if necessary */ +{ + static const char errfmt[] = "others_check(\"%s\"): %s"; + dlb *fh; + char line[BUFSZ], xbuf[BUFSZ], *endp; + winid tmpwin = *winptr; + int entrycount = 0; + + fh = dlb_fopen(fname, "r"); + if (fh) { + if (tmpwin == WIN_ERR) { + *winptr = tmpwin = create_nhwindow(NHW_TEXT); + if (tmpwin == WIN_ERR) { + /* should panic, but won't for wizard mode check operation */ + impossible(errfmt, fname, "can't create temporary window"); + goto closeit; + } + } + putstr(tmpwin, 0, ""); + putstr(tmpwin, 0, ftype); + /* "don't edit" comment */ + *line = '\0'; + if (!dlb_fgets(line, sizeof line, fh)) { + Sprintf(xbuf, errfmt, fname, "error; can't read comment line"); + putstr(tmpwin, 0, xbuf); + goto closeit; + } + if (*line != '#') { + Sprintf(xbuf, errfmt, fname, + "malformed; first line is not a comment line:"); + putstr(tmpwin, 0, xbuf); + /* show the bad line; we don't know whether it has been + encrypted via xcrypt() so show it both ways */ + if ((endp = strchr(line, '\n')) != 0) + *endp = 0; + putstr(tmpwin, 0, "- first line, as is"); + putstr(tmpwin, 0, line); + putstr(tmpwin, 0, "- xcrypt of first line"); + putstr(tmpwin, 0, xcrypt(line, xbuf)); + goto closeit; + } + /* first line; should be default one inserted by makedefs when + building the file but we don't have the expected value so + can only require a line to exist */ + *line = '\0'; + if (!dlb_fgets(line, sizeof line, fh) || *line == '\n') { + Sprintf(xbuf, errfmt, fname, + !*line ? "can't read first non-comment line" + : "first non-comment line is empty"); + putstr(tmpwin, 0, xbuf); + goto closeit; + } + ++entrycount; + if ((endp = strchr(line, '\n')) != 0) + *endp = 0; + putstr(tmpwin, 0, xcrypt(line, xbuf)); + if (!dlb_fgets(line, sizeof line, fh)) { + putstr(tmpwin, 0, "(no second entry)"); + } else { + ++entrycount; + if ((endp = strchr(line, '\n')) != 0) + *endp = 0; + putstr(tmpwin, 0, xcrypt(line, xbuf)); + while (dlb_fgets(line, sizeof line, fh)) { + ++entrycount; + if ((endp = strchr(line, '\n')) != 0) + *endp = 0; + (void) xcrypt(line, xbuf); + } + /* count will be 2 if the default entry and the first ordinary + entry are the only ones present (if either of those were + missing, we wouldn't have gotten here...) */ + if (entrycount == 2) { + putstr(tmpwin, 0, "(only two entries)"); + } else { + /* showing an ellipsis avoids ambiguity about whether + there are other lines; doing so three times (once for + each file) results in total output being 24 lines, + forcing a --More-- prompt if using a 24 line screen; + displaying 23 lines and --More-- followed by second + page with 1 line doesn't look very good but isn't + incorrect, and taller screens where that won't be an + issue are more common than 24 line terminals nowadays */ + if (entrycount > 3) + putstr(tmpwin, 0, " ..."); + putstr(tmpwin, 0, xbuf); /* already decrypted */ + } + } + + closeit: + (void) dlb_fclose(fh); } else { - couldnt_open_file(RUMORFILE); - true_rumor_size = -1; /* don't try to open it again */ + /* since this comes out via impossible(), it won't be integrated + with the text window of values, but it shouldn't ever happen + so we won't waste effort integrating it */ + couldnt_open_file(fname); + } +} + +RESTORE_WARNING_FORMAT_NONLITERAL + +/* load one randomly chosen line from a section of a file; undoes + decryption and strips trailing underscore padding and final newline; + if padlength is non-zero, every line is expected to be at least that + long and every line in the file will have an equal chance of being + chosen; however, if padlength is 0, lines following long lines are + more likely than average to be picked, and lines after short lines + are less likely */ +staticfn char * +get_rnd_line( + dlb *fh, /* already opened file */ + char *buf, /* output buffer */ + unsigned bufsiz, /* (unsigned) sizeof buf */ + int (*rng)(int), /* random number routine; rn2(N) or similar, 0..N-1 */ + long startpos, /* location in file of first line of interest */ + long endpos, /* location one byte past last line of interest; + * if 0, end-of-file will be used */ + unsigned padlength) /* expected line length; 0 if no expectations */ +{ + char *newl, *xbufp, xbuf[BUFSZ]; + long filechunksize, chunkoffset; + int trylimit; + + *buf = '\0'; + if (!endpos) { + (void) dlb_fseek(fh, 0L, SEEK_END); + endpos = dlb_ftell(fh); + } + filechunksize = endpos - startpos; + + /* might be zero (only if file is empty); should complain in that + case but it could happen over and over, also the suggestion + that save and restore might fix the problem wouldn't be useful */ + if (filechunksize < 1L) + return buf; + /* 'rumors' is about 3/4 of the way to the limit on a 16-bit config + for the whole, roughly 3/8 of the way for either half; all active + configurations these days are at least 32-bits anyway */ + nhassert(filechunksize <= INT_MAX); /* essential for rn2() */ + + /* + * Position randomly which will probably be in the middle of a line. + * (Occasionally by chance it will happen to be at the very start of + * a line, but we'll have no way of knowing that so have to behave + * as if it were positioned in the middle.) + * Read the rest of that line, then use the next one. If there's no + * next line (ie, end of file), go back to beginning and use first. + * + * When short lines have been padded to length N, only accept long + * lines if we land within last N+1 characters (+1 is for newline + * which hasn't been stripped away yet), effectively shortening + * them to normal length. That yields even selection distribution. + */ + for (trylimit = 10; trylimit > 0; --trylimit) { + chunkoffset = (long) (*rng)((int) filechunksize); + (void) dlb_fseek(fh, startpos + chunkoffset, SEEK_SET); + (void) dlb_fgets(buf, bufsiz, fh); + /* if padlength is 0, accept any position; when non-zero, + padlength does not count the newline but strlen(buf) does */ + if (!padlength || (unsigned) strlen(buf) <= padlength + 1) + break; + } + /* use next line; for rumors, caller takes care of whether startpos + and endpos cover just true rumors or just false rumors; reaching + endpos is equivalent to end-of-file in order to avoid using the + first false rumor if fseek for a true one lands within the last one */ + if (dlb_ftell(fh) >= endpos || !dlb_fgets(buf, bufsiz, fh)) { + /* assume failure is due to end-of-file; go back to start */ + (void) dlb_fseek(fh, startpos, SEEK_SET); + (void) dlb_fgets(buf, bufsiz, fh); } + if ((newl = strchr(buf, '\n')) != 0) + *newl = '\0'; + /* decrypt line; make sure that our intermediate buffer is big enough */ + xbufp = (strlen(buf) <= sizeof xbuf - 1) ? &xbuf[0] + : (char *) alloc((unsigned) strlen(buf) + 1); + Strcpy(buf, xcrypt(buf, xbufp)); + if (xbufp != &xbuf[0]) + free((genericptr_t) xbufp); + /* strip padding that makedefs adds to short lines */ + if (padlength) + unpadline(buf); + return buf; } /* Gets a random line of text from file 'fname', and returns it. rng is the random number generator to use, and should act like rn2 does. */ char * -get_rnd_text(fname, buf, rng) -const char *fname; -char *buf; -int FDECL((*rng), (int)); +get_rnd_text( + const char *fname, + char *buf, + int (*rng)(int), + unsigned padlength) { - dlb *fh; + dlb *fh = dlb_fopen(fname, "r"); buf[0] = '\0'; - fh = dlb_fopen(fname, "r"); if (fh) { - /* TODO: cache sizetxt, starttxt, endtxt. maybe cache file contents? */ - long sizetxt = 0L, starttxt = 0L, endtxt = 0L, tidbit = 0L; - char *endp, line[BUFSZ], xbuf[BUFSZ]; + long starttxt = 0L; + char line[BUFSZ]; /* skip "don't edit" comment */ (void) dlb_fgets(line, sizeof line, fh); - + /* obtain current file position */ (void) dlb_fseek(fh, 0L, SEEK_CUR); starttxt = dlb_ftell(fh); - (void) dlb_fseek(fh, 0L, SEEK_END); - endtxt = dlb_ftell(fh); - sizetxt = endtxt - starttxt; - /* might be zero (only if file is empty); should complain in that - case but if could happen over and over, also the suggestion - that save and restore might fix the problem wouldn't be useful */ - if (sizetxt < 1L) - return buf; - tidbit = (*rng)(sizetxt); - - (void) dlb_fseek(fh, starttxt + tidbit, SEEK_SET); - (void) dlb_fgets(line, sizeof line, fh); - if (!dlb_fgets(line, sizeof line, fh)) { - (void) dlb_fseek(fh, starttxt, SEEK_SET); - (void) dlb_fgets(line, sizeof line, fh); - } - if ((endp = index(line, '\n')) != 0) - *endp = 0; - Strcat(buf, xcrypt(line, xbuf)); + + /* get a randomly chosen line; it comes back decrypted and unpadded */ + Strcpy(buf, get_rnd_line(fh, line, (unsigned) sizeof line, rng, + starttxt, 0L, padlength)); (void) dlb_fclose(fh); } else { couldnt_open_file(fname); } - return buf; } void -outrumor(truth, mechanism) -int truth; /* 1=true, -1=false, 0=either */ -int mechanism; +outrumor( + int truth, /* 1=true, -1=false, 0=either */ + int mechanism) { static const char fortune_msg[] = "This cookie has a scrap of paper inside."; @@ -339,15 +538,16 @@ int mechanism; if (reading) { /* deal with various things that prevent reading */ - if (is_fainted() && mechanism == BY_COOKIE) + if (is_fainted() && mechanism == BY_COOKIE) { return; - else if (Blind) { + } else if (Blind) { if (mechanism == BY_COOKIE) pline(fortune_msg); pline("What a pity that you cannot read it!"); return; } } + line = getrumor(truth, buf, reading ? FALSE : TRUE); if (!*line) line = "NetHack rumors file closed for renovation."; @@ -358,11 +558,13 @@ int mechanism; (!rn2(4) ? "offhandedly " : (!rn2(3) ? "casually " : (rn2(2) ? "nonchalantly " : "")))); + SetVoice((struct monst *) 0, 0, 80, voice_oracle); verbalize1(line); - /* [WIS exercized by getrumor()] */ + /* [WIS exercised by getrumor()] */ return; case BY_COOKIE: pline(fortune_msg); + FALLTHROUGH; /* FALLTHRU */ case BY_PAPER: pline("It reads:"); @@ -371,11 +573,10 @@ int mechanism; pline1(line); } -STATIC_OVL void -init_oracles(fp) -dlb *fp; +staticfn void +init_oracles(dlb *fp) { - register int i; + int i; char line[BUFSZ]; int cnt = 0; @@ -383,50 +584,60 @@ dlb *fp; (void) dlb_fgets(line, sizeof line, fp); /* skip "don't edit" comment*/ (void) dlb_fgets(line, sizeof line, fp); if (sscanf(line, "%5d\n", &cnt) == 1 && cnt > 0) { - oracle_cnt = (unsigned) cnt; - oracle_loc = (unsigned long *) alloc((unsigned) cnt * sizeof(long)); + svo.oracle_cnt = (unsigned) cnt; + svo.oracle_loc = (unsigned long *) alloc((unsigned) cnt * sizeof(long)); for (i = 0; i < cnt; i++) { (void) dlb_fgets(line, sizeof line, fp); - (void) sscanf(line, "%5lx\n", &oracle_loc[i]); + (void) sscanf(line, "%5lx\n", &svo.oracle_loc[i]); } } return; } void -save_oracles(fd, mode) -int fd, mode; +save_oracles(NHFILE *nhfp) { - if (perform_bwrite(mode)) { - bwrite(fd, (genericptr_t) &oracle_cnt, sizeof oracle_cnt); - if (oracle_cnt) - bwrite(fd, (genericptr_t) oracle_loc, - oracle_cnt * sizeof(long)); + int i; + + if (update_file(nhfp)) { + Sfo_unsigned(nhfp, &svo.oracle_cnt, "oracle-oracle_cnt"); + if (svo.oracle_cnt) { + for (i = 0; (unsigned) i < svo.oracle_cnt; ++i) { + Sfo_ulong(nhfp, &svo.oracle_loc[i], "oracle-oracle_loc"); + } + } } - if (release_data(mode)) { - if (oracle_cnt) { - free((genericptr_t) oracle_loc); - oracle_loc = 0, oracle_cnt = 0, oracle_flg = 0; + if (release_data(nhfp)) { + if (svo.oracle_cnt) { + svo.oracle_cnt = 0, go.oracle_flg = 0; + } + if (svo.oracle_loc) { + free((genericptr_t) svo.oracle_loc); + svo.oracle_loc = 0; } } } +#endif /* !SFCTOOL */ void -restore_oracles(fd) -int fd; +restore_oracles(NHFILE *nhfp) { - mread(fd, (genericptr_t) &oracle_cnt, sizeof oracle_cnt); - if (oracle_cnt) { - oracle_loc = (unsigned long *) alloc(oracle_cnt * sizeof(long)); - mread(fd, (genericptr_t) oracle_loc, oracle_cnt * sizeof(long)); - oracle_flg = 1; /* no need to call init_oracles() */ + int i; + + Sfi_unsigned(nhfp, &svo.oracle_cnt, "oracle-oracle_cnt"); + if (svo.oracle_cnt) { + svo.oracle_loc = + (unsigned long *) alloc(svo.oracle_cnt * sizeof (unsigned long)); + for (i = 0; (unsigned) i < svo.oracle_cnt; ++i) { + Sfi_ulong(nhfp, &svo.oracle_loc[i], "oracle-oracle_loc"); + } + go.oracle_flg = 1; /* no need to call init_oracles() */ } } +#ifndef SFCTOOL void -outoracle(special, delphi) -boolean special; -boolean delphi; +outoracle(boolean special, boolean delphi) { winid tmpwin; dlb *oracles; @@ -435,39 +646,39 @@ boolean delphi; /* early return if we couldn't open ORACLEFILE on previous attempt, or if all the oracularities are already exhausted */ - if (oracle_flg < 0 || (oracle_flg > 0 && oracle_cnt == 0)) + if (go.oracle_flg < 0 || (go.oracle_flg > 0 && svo.oracle_cnt == 0)) return; oracles = dlb_fopen(ORACLEFILE, "r"); if (oracles) { - if (oracle_flg == 0) { /* if this is the first outoracle() */ + if (go.oracle_flg == 0) { /* if this is the first outoracle() */ init_oracles(oracles); - oracle_flg = 1; - if (oracle_cnt == 0) + go.oracle_flg = 1; + if (svo.oracle_cnt == 0) goto close_oracles; } /* oracle_loc[0] is the special oracle; oracle_loc[1..oracle_cnt-1] are normal ones */ - if (oracle_cnt <= 1 && !special) + if (svo.oracle_cnt <= 1 && !special) goto close_oracles; /*(shouldn't happen)*/ - oracle_idx = special ? 0 : rnd((int) oracle_cnt - 1); - (void) dlb_fseek(oracles, (long) oracle_loc[oracle_idx], SEEK_SET); + oracle_idx = special ? 0 : rnd((int) svo.oracle_cnt - 1); + (void) dlb_fseek(oracles, (long) svo.oracle_loc[oracle_idx], SEEK_SET); if (!special) /* move offset of very last one into this slot */ - oracle_loc[oracle_idx] = oracle_loc[--oracle_cnt]; + svo.oracle_loc[oracle_idx] = svo.oracle_loc[--svo.oracle_cnt]; tmpwin = create_nhwindow(NHW_TEXT); if (delphi) putstr(tmpwin, 0, special - ? "The Oracle scornfully takes all your money and says:" + ? "The Oracle scornfully takes all your gold and says:" : "The Oracle meditates for a moment and then intones:"); else putstr(tmpwin, 0, "The message reads:"); putstr(tmpwin, 0, ""); while (dlb_fgets(line, COLNO, oracles) && strcmp(line, "---\n")) { - if ((endp = index(line, '\n')) != 0) + if ((endp = strchr(line, '\n')) != 0) *endp = 0; putstr(tmpwin, 0, xcrypt(line, xbuf)); } @@ -477,31 +688,30 @@ boolean delphi; (void) dlb_fclose(oracles); } else { couldnt_open_file(ORACLEFILE); - oracle_flg = -1; /* don't try to open it again */ + go.oracle_flg = -1; /* don't try to open it again */ } } int -doconsult(oracl) -struct monst *oracl; +doconsult(struct monst *oracl) { long umoney; int u_pay, minor_cost = 50, major_cost = 500 + 50 * u.ulevel; int add_xpts; char qbuf[QBUFSZ]; - multi = 0; - umoney = money_cnt(invent); + gm.multi = 0; + umoney = money_cnt(gi.invent); if (!oracl) { There("is no one here to consult."); - return 0; + return ECMD_OK; } else if (!oracl->mpeaceful) { pline("%s is in no mood for consultations.", Monnam(oracl)); - return 0; + return ECMD_OK; } else if (!umoney) { - You("have no money."); - return 0; + You("have no gold."); + return ECMD_OK; } Sprintf(qbuf, "\"Wilt thou settle for a minor consultation?\" (%d %s)", @@ -509,27 +719,29 @@ struct monst *oracl; switch (ynq(qbuf)) { default: case 'q': - return 0; + return ECMD_OK; case 'y': if (umoney < (long) minor_cost) { - You("don't even have enough money for that!"); - return 0; + You("don't even have enough gold for that!"); + return ECMD_OK; } u_pay = minor_cost; break; case 'n': if (umoney <= (long) minor_cost /* don't even ask */ - || (oracle_cnt == 1 || oracle_flg < 0)) - return 0; + || (svo.oracle_cnt == 1 || go.oracle_flg < 0)) + return ECMD_OK; Sprintf(qbuf, "\"Then dost thou desire a major one?\" (%d %s)", major_cost, currency((long) major_cost)); - if (yn(qbuf) != 'y') - return 0; + if (y_n(qbuf) != 'y') + return ECMD_OK; u_pay = (umoney < (long) major_cost) ? (int) umoney : major_cost; break; } money2mon(oracl, (long) u_pay); - context.botl = 1; + disp.botl = TRUE; + if (!u.uevent.major_oracle && !u.uevent.minor_oracle) + record_achievement(ACH_ORCL); add_xpts = 0; /* first oracle of each type gives experience points */ if (u_pay == minor_cost) { outrumor(1, BY_ORACLE); @@ -551,12 +763,11 @@ struct monst *oracl; more_experienced(add_xpts, u_pay / 50); newexplevel(); } - return 1; + return ECMD_TIME; } -STATIC_OVL void -couldnt_open_file(filename) -const char *filename; +staticfn void +couldnt_open_file(const char *filename) { int save_something = program_state.something_worth_saving; @@ -570,4 +781,176 @@ const char *filename; program_state.something_worth_saving = save_something; } +/* is 'word' a capitalized monster name that should be preceded by "the"? + (non-unique monster like Mordor Orc, or capitalized title like Norn + rather than a name); used by the() on a string without any context; + this sets up a list of names rather than scan all of mons[] every time + the decision is needed (resulting list currently contains 27 monster + entries and 20 hallucination entries) */ +boolean +CapitalMon( + const char *word) /* potential monster name; a name might be followed by + * something like " corpse" */ +{ + const char *nam; + unsigned i, wln, nln; + + if (!word || !*word || *word == lowc(*word)) + return FALSE; /* 'word' is not a capitalized monster name */ + + if (!CapMons) + init_CapMons(); + assert(CapMons != 0); + + wln = (unsigned) strlen(word); + for (i = 0; i < CapMonSiz - 1; ++i) { + nam = CapMons[i]; + nln = (unsigned) strlen(nam); + if (wln < nln) + continue; + /* + * Unlike name_to_mon(), we don't need to find the longest match + * or return the gender or a pointer to trailing stuff. We do + * check full words though: "Foo" matches "Foo" and "Foo bar" and + * "Foo's bar" but not "Foobar". We use case-sensitive matching. + */ + if (!strncmp(nam, word, nln) + && (!word[nln] || word[nln] == ' ' || word[nln] == '\'')) + return TRUE; /* 'word' is a capitalized monster name */ + } + return FALSE; +} + +/* one-time initialization of CapMons[], a list of non-unique monsters + having a capitalized type name like Green-elf or Archon, plus unique + monsters whose "name" is a title rather than a personal name, plus + hallucinatory monster names that fall into either of those categories */ +staticfn void +init_CapMons(void) +{ + unsigned pass; + dlb *bogonfile = dlb_fopen(BOGUSMONFILE, "r"); + + if (CapMons) /* sanity precaution */ + free_CapMons(); + + /* first pass: count the number of relevant monster names, then + allocate memory for CapMons[]; second pass: populate CapMons[] */ + for (pass = 1; pass <= 2; ++pass) { + struct permonst *mptr; + const char *nam; + unsigned mndx, mgend; + + /* the first CapMonstCnt entries come from mons[].pmnames[] and + the next CapBogonCnt entries from the 'bogusmons' file; + there is an extra entry for Null at the end, but that is only + useful to force non-zero array size in case both mons[] and + bogusmons get modified to have no applicable monster names */ + CapMonstCnt = CapBogonCnt = 0; + + /* gather applicable actual monsters */ + for (mndx = LOW_PM; mndx < NUMMONS; ++mndx) { + mptr = &mons[mndx]; + if ((mptr->geno & G_UNIQ) != 0 && !the_unique_pm(mptr)) + continue; + for (mgend = MALE; mgend < NUM_MGENDERS; ++mgend) { + nam = mptr->pmnames[mgend]; + if (nam && *nam != lowc(*nam)) { + if (pass == 2) + CapMons[CapMonstCnt] = nam; + ++CapMonstCnt; + } + } + } + + /* now gather applicable hallucinatory monsters */ + if (bogonfile) { + char hline[BUFSZ], xbuf[BUFSZ], *endp, *startp, code; + + /* rewind; effectively a no-op for pass 1; essential for pass 2 */ + (void) dlb_fseek(bogonfile, 0L, SEEK_SET); + /* skip "don't edit" comment (first line of file) */ + (void) dlb_fgets(hline, sizeof hline, bogonfile); + + /* one monster name per line in rudimentary encrypted format; + some are prefixed by a classification code to indicate + gender and/or to distinguish an individual from a type + (code is a single punctuation character when present) */ + while (dlb_fgets(hline, sizeof hline, bogonfile)) { + if ((endp = strchr(hline, '\n')) != 0) + *endp = '\0'; /* strip newline */ + (void) xcrypt(hline, xbuf); + unpadline(xbuf); + + if (!xbuf[0] || !strchr(bogon_codes, xbuf[0])) + code = '\0', startp = &xbuf[0]; /* ordinary */ + else + code = xbuf[0], startp = &xbuf[1]; /* special */ + + if (*startp != lowc(*startp) && !bogon_is_pname(code)) { + if (pass == 2) + CapMons[CapMonstCnt + CapBogonCnt] = dupstr(startp); + ++CapBogonCnt; + } + } + } + + /* finish the current pass */ + if (pass == 1) { + CapMonSiz = CapMonstCnt + CapBogonCnt + 1; /* +1: terminator */ + CapMons = (const char **) alloc(CapMonSiz * sizeof *CapMons); + } else { /* pass == 2 */ + /* terminator; not strictly needed */ + CapMons[CapMonSiz - 1] = (const char *) 0; + + if (bogonfile) + (void) dlb_fclose(bogonfile), bogonfile = (dlb *) 0; + } + } +#ifdef DEBUG + /* + * CapMons[] init doesn't kick in until needed. To force this name + * dump, set DEBUGFILES to "CapMons" in your environment (or in + * sysconf) prior to starting nethack, wish for a statue of an Archon + * and drop it if held, then step away and apply a stethoscope towards + * it to trigger a message that passes "Archon" to the() which will + * then call CapitalMon() which in turn will call init_CapMons(). + */ + if (wizard && explicitdebug("CapMons")) { + char buf[BUFSZ]; + unsigned i; + winid tmpwin = create_nhwindow(NHW_TEXT); + + putstr(tmpwin, 0, + "Capitalized monster type names normally preceded by \"the\":"); + for (i = 0; i < CapMonSiz - 1; ++i) { + Sprintf(buf, " %.77s", CapMons[i]); + putstr(tmpwin, 0, buf); + } + display_nhwindow(tmpwin, TRUE); + destroy_nhwindow(tmpwin); + } +#endif + return; +} + +/* release memory allocated for the list of capitalized monster type names */ +void +free_CapMons(void) +{ + /* note: some elements of CapMons[] are string literals from + mons[].pmnames[] and should not be freed, others are dynamically + allocated copies of hallucinatory monster names and should be freed */ + if (CapMons) { + unsigned idx; + + /* skip 0..MonstCnt-1, free MonstCnt..(MonstCnt+BogonCnt-1) */ + for (idx = CapMonstCnt; idx < CapMonSiz - 1; ++idx) + free((genericptr_t) CapMons[idx]); /* cast: discard 'const' */ + free((genericptr_t) CapMons), CapMons = (const char **) 0; + } + CapMonSiz = 0; +} +#endif /* !SFCTOOL */ + /*rumors.c*/ diff --git a/src/save.c b/src/save.c index e0af4b8d2..1d61ee751 100644 --- a/src/save.c +++ b/src/save.c @@ -1,10 +1,9 @@ -/* NetHack 3.6 save.c $NHDT-Date: 1559994625 2019/06/08 11:50:25 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.121 $ */ +/* NetHack 5.0 save.c $NHDT-Date: 1737610109 2025/01/22 21:28:29 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.232 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Michael Allison, 2009. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -#include "lev.h" #ifndef NO_SIGNAL #include @@ -13,105 +12,75 @@ #include #endif -#ifdef MFLOPPY -long bytes_counted; -static int count_only; -#endif - #ifdef MICRO int dotcnt, dotrow; /* also used in restore */ #endif -STATIC_DCL void FDECL(savelevchn, (int, int)); -STATIC_DCL void FDECL(savedamage, (int, int)); -STATIC_DCL void FDECL(saveobj, (int, struct obj *)); -STATIC_DCL void FDECL(saveobjchn, (int, struct obj *, int)); -STATIC_DCL void FDECL(savemon, (int, struct monst *)); -STATIC_DCL void FDECL(savemonchn, (int, struct monst *, int)); -STATIC_DCL void FDECL(savetrapchn, (int, struct trap *, int)); -STATIC_DCL void FDECL(savegamestate, (int, int)); -STATIC_OVL void FDECL(save_msghistory, (int, int)); -#ifdef MFLOPPY -STATIC_DCL void FDECL(savelev0, (int, XCHAR_P, int)); -STATIC_DCL boolean NDECL(swapout_oldest); -STATIC_DCL void FDECL(copyfile, (char *, char *)); -#endif /* MFLOPPY */ -STATIC_DCL void FDECL(savelevl, (int fd, BOOLEAN_P)); -STATIC_DCL void FDECL(def_bufon, (int)); -STATIC_DCL void FDECL(def_bufoff, (int)); -STATIC_DCL void FDECL(def_bflush, (int)); -STATIC_DCL void FDECL(def_bwrite, (int, genericptr_t, unsigned int)); -#ifdef ZEROCOMP -STATIC_DCL void FDECL(zerocomp_bufon, (int)); -STATIC_DCL void FDECL(zerocomp_bufoff, (int)); -STATIC_DCL void FDECL(zerocomp_bflush, (int)); -STATIC_DCL void FDECL(zerocomp_bwrite, (int, genericptr_t, unsigned int)); -STATIC_DCL void FDECL(zerocomp_bputc, (int)); -#endif - -static struct save_procs { - const char *name; - void FDECL((*save_bufon), (int)); - void FDECL((*save_bufoff), (int)); - void FDECL((*save_bflush), (int)); - void FDECL((*save_bwrite), (int, genericptr_t, unsigned int)); - void FDECL((*save_bclose), (int)); -} saveprocs = { -#if !defined(ZEROCOMP) || (defined(COMPRESS) || defined(ZLIB_COMP)) - "externalcomp", def_bufon, def_bufoff, def_bflush, def_bwrite, def_bclose, -#else - "zerocomp", zerocomp_bufon, zerocomp_bufoff, - zerocomp_bflush, zerocomp_bwrite, zerocomp_bclose, -#endif -}; - -#if defined(UNIX) || defined(VMS) || defined(__EMX__) || defined(WIN32) +staticfn void savelevchn(NHFILE *); +staticfn void savelevl(NHFILE *); +staticfn void savedamage(NHFILE *); +staticfn void save_bubbles(NHFILE *, xint8); +staticfn void save_stairs(NHFILE *); +staticfn void save_bc(NHFILE *); +staticfn void saveobj(NHFILE *, struct obj *); +staticfn void saveobjchn(NHFILE *, struct obj **) NO_NNARGS; +staticfn void savemon(NHFILE *, struct monst *); +staticfn void savemonchn(NHFILE *, struct monst *) NO_NNARGS; +staticfn void savetrapchn(NHFILE *, struct trap *) NO_NNARGS; +staticfn void save_gamelog(NHFILE *); +staticfn void savegamestate(NHFILE *); +staticfn void savelev_core(NHFILE *, xint8); +staticfn void save_msghistory(NHFILE *); +staticfn void save_adjust_levelflags(void); +#if defined(HANGUPHANDLING) #define HUP if (!program_state.done_hup) #else #define HUP #endif -/* need to preserve these during save to avoid accessing freed memory */ -static unsigned ustuck_id = 0, usteed_id = 0; - +/* the #save command */ int -dosave() +dosave(void) { - if (iflags.debug_fuzzer) - return 0; clear_nhwindow(WIN_MESSAGE); - if (yn("Really save?") == 'n') { + if (y_n("Really save?") == 'n') { clear_nhwindow(WIN_MESSAGE); - if (multi > 0) + if (gm.multi > 0) nomul(0); } else { clear_nhwindow(WIN_MESSAGE); pline("Saving..."); -#if defined(UNIX) || defined(VMS) || defined(__EMX__) +#if defined(HANGUPHANDLING) program_state.done_hup = 0; #endif if (dosave0()) { + program_state.savefile_completed++; u.uhp = -1; /* universal game's over indicator */ + if (soundprocs.sound_exit_nhsound) + (*soundprocs.sound_exit_nhsound)("dosave"); + /* make sure they see the Saving message */ display_nhwindow(WIN_MESSAGE, TRUE); exit_nhwindows("Be seeing you..."); nh_terminate(EXIT_SUCCESS); } else - (void) doredraw(); + docrt(); } - return 0; + return ECMD_OK; } /* returns 1 if save successful */ int -dosave0() +dosave0(void) { const char *fq_save; - register int fd, ofd; - xchar ltmp; - d_level uz_save; + xint8 ltmp; char whynot[BUFSZ]; + NHFILE *nhfp, *onhfp; + int res = 0; + program_state.saving++; /* inhibit status and perm_invent updates */ + notice_mon_off(); /* we may get here via hangup signal, in which case we want to fix up a few of things before saving so that they won't be restored in an improper state; these will be no-ops for normal save sequence */ @@ -119,47 +88,51 @@ dosave0() if (iflags.save_uswallow) u.uswallow = 1, iflags.save_uswallow = 0; if (iflags.save_uinwater) - u.uinwater = 1, iflags.save_uinwater = 0; + u.uinwater = 1, iflags.save_uinwater = 0; /* bypass set_uinwater() */ if (iflags.save_uburied) u.uburied = 1, iflags.save_uburied = 0; + /* extra handling for hangup save or panic save; without this, + a thrown light source might trigger an "obj_is_local" panic; + if a thrown or kicked object is in transit, put it on the map; + when punished, make sure ball and chain are placed too */ + done_object_cleanup(); /* maybe force some items onto map */ - if (!program_state.something_worth_saving || !SAVEF[0]) - return 0; - fq_save = fqname(SAVEF, SAVEPREFIX, 1); /* level files take 0 */ + if (!program_state.something_worth_saving || !gs.SAVEF[0]) + goto done; + fq_save = fqname(gs.SAVEF, SAVEPREFIX, 1); /* level files take 0 */ +#ifndef NO_SIGNAL #if defined(UNIX) || defined(VMS) - sethanguphandler((void FDECL((*), (int) )) SIG_IGN); + sethanguphandler((void (*)(int) ) SIG_IGN); #endif -#ifndef NO_SIGNAL (void) signal(SIGINT, SIG_IGN); #endif -#if defined(MICRO) && defined(MFLOPPY) - if (!saveDiskPrompt(0)) - return 0; -#endif - HUP if (iflags.window_inited) { nh_uncompress(fq_save); - fd = open_savefile(); - if (fd > 0) { - (void) nhclose(fd); + nhfp = open_savefile(); + if (nhfp) { + close_nhfile(nhfp); clear_nhwindow(WIN_MESSAGE); There("seems to be an old save file."); - if (yn("Overwrite the old file?") == 'n') { + if (y_n("Overwrite the old file?") == 'n') { + nh_sfconvert(fq_save); nh_compress(fq_save); - return 0; + goto done; } } } HUP mark_synch(); /* flush any buffered screen output */ - fd = create_savefile(); - if (fd < 0) { + nhfp = create_savefile(); + if (!nhfp) { HUP pline("Cannot open save file."); (void) delete_savefile(); /* ab@unido */ - return 0; + goto done; + } + if (nhfp && nhfp->fplog) { + nhfp->rcount = nhfp->wcount = 0L; } vision_recalc(2); /* shut down vision to prevent problems @@ -177,63 +150,42 @@ dosave0() dotcnt = 0; dotrow = 2; curs(WIN_MAP, 1, 1); - if (!WINDOWPORT("X11")) + if (!WINDOWPORT(X11)) putstr(WIN_MAP, 0, "Saving:"); #endif -#ifdef MFLOPPY - /* make sure there is enough disk space */ - if (iflags.checkspace) { - long fds, needed; - - savelev(fd, ledger_no(&u.uz), COUNT_SAVE); - savegamestate(fd, COUNT_SAVE); - needed = bytes_counted; - - for (ltmp = 1; ltmp <= maxledgerno(); ltmp++) - if (ltmp != ledger_no(&u.uz) && level_info[ltmp].where) - needed += level_info[ltmp].size + (sizeof ltmp); - fds = freediskspace(fq_save); - if (needed > fds) { - HUP - { - There("is insufficient space on SAVE disk."); - pline("Require %ld bytes but only have %ld.", needed, fds); - } - flushout(); - (void) nhclose(fd); - (void) delete_savefile(); - return 0; - } - - co_false(); - } -#endif /* MFLOPPY */ - - store_version(fd); - store_savefileinfo(fd); - store_plname_in_file(fd); - ustuck_id = (u.ustuck ? u.ustuck->m_id : 0); - usteed_id = (u.usteed ? u.usteed->m_id : 0); - savelev(fd, ledger_no(&u.uz), WRITE_SAVE | FREE_SAVE); - savegamestate(fd, WRITE_SAVE | FREE_SAVE); + nhfp->mode = WRITING | FREEING; + store_version(nhfp); + store_plname_in_file(nhfp); + /* savelev() might save uball and uchain, releasing their memory if + FREEING, so we need to check their status now; if hero is swallowed, + uball and uchain will persist beyond saving map floor and inventory + so these copies of their pointers will be valid and savegamestate() + will know to save them separately (from floor and invent); when not + swallowed, uchain will be stale by then, and uball will be too if + ball is on the floor rather than carried */ + gl.looseball = BALL_IN_MON ? uball : 0; + gl.loosechain = CHAIN_IN_MON ? uchain : 0; + savelev(nhfp, ledger_no(&u.uz)); + savegamestate(nhfp); /* While copying level files around, zero out u.uz to keep * parts of the restore code from completely initializing all * in-core data structures, since all we're doing is copying. * This also avoids at least one nasty core dump. + * [gu.uz_save is used by save_bubbles() as well as to restore u.uz] */ - uz_save = u.uz; + gu.uz_save = u.uz; u.uz.dnum = u.uz.dlevel = 0; /* these pointers are no longer valid, and at least u.usteed * may mislead place_monster() on other levels */ - u.ustuck = (struct monst *) 0; + set_ustuck((struct monst *) 0); /* also clears u.uswallow */ u.usteed = (struct monst *) 0; - for (ltmp = (xchar) 1; ltmp <= maxledgerno(); ltmp++) { - if (ltmp == ledger_no(&uz_save)) + for (ltmp = (xint8) 1; ltmp <= maxledgerno(); ltmp++) { + if (ltmp == ledger_no(&gu.uz_save)) continue; - if (!(level_info[ltmp].flags & LFILE_EXISTS)) + if (!(svl.level_info[ltmp].flags & LFILE_EXISTS)) continue; #ifdef MICRO curs(WIN_MAP, 1 + dotcnt++, dotrow); @@ -241,127 +193,153 @@ dosave0() dotrow++; dotcnt = 0; } - if (!WINDOWPORT("X11")) { + if (!WINDOWPORT(X11)) { putstr(WIN_MAP, 0, "."); } mark_synch(); #endif - ofd = open_levelfile(ltmp, whynot); - if (ofd < 0) { + onhfp = open_levelfile(ltmp, whynot); + if (!onhfp) { HUP pline1(whynot); - (void) nhclose(fd); + close_nhfile(nhfp); (void) delete_savefile(); - HUP Strcpy(killer.name, whynot); + HUP Strcpy(svk.killer.name, whynot); HUP done(TRICKED); - return 0; + goto done; } - minit(); /* ZEROCOMP */ - getlev(ofd, hackpid, ltmp, FALSE); - (void) nhclose(ofd); - bwrite(fd, (genericptr_t) <mp, sizeof ltmp); /* level number*/ - savelev(fd, ltmp, WRITE_SAVE | FREE_SAVE); /* actual level*/ + getlev(onhfp, svh.hackpid, ltmp); + close_nhfile(onhfp); + Sfo_xint8(nhfp, <mp, "gamestate-level_number"); + savelev(nhfp, ltmp); /* actual level*/ delete_levelfile(ltmp); } - bclose(fd); + close_nhfile(nhfp); - u.uz = uz_save; + u.uz = gu.uz_save; + gu.uz_save.dnum = gu.uz_save.dlevel = 0; /* get rid of current level --jgm */ delete_levelfile(ledger_no(&u.uz)); delete_levelfile(0); + nh_sfconvert(fq_save); nh_compress(fq_save); /* this should probably come sooner... */ program_state.something_worth_saving = 0; - return 1; + res = 1; + + done: + notice_mon_on(); + program_state.saving--; + return res; } -STATIC_OVL void -savegamestate(fd, mode) -register int fd, mode; +staticfn void +save_gamelog(NHFILE *nhfp) { + struct gamelog_line *tmp = gg.gamelog, *tmp2; + int slen; + + while (tmp) { + tmp2 = tmp->next; + if (nhfp->mode & (COUNTING | WRITING)) { + slen = Strlen(tmp->text); + Sfo_int(nhfp, &slen, "gamelog-length"); + Sfo_char(nhfp, tmp->text, "gamelog-gamelog_text", slen); + Sfo_gamelog_line(nhfp, tmp, "gamelog-gamelog_line"); + } + if (nhfp->mode & FREEING) { + free((genericptr_t) tmp->text); + free((genericptr_t) tmp); + } + tmp = tmp2; + } + if (nhfp->mode & (COUNTING | WRITING)) { + slen = -1; + Sfo_int(nhfp, &slen, "gamelog-length"); + } + if (nhfp->mode & FREEING) + gg.gamelog = NULL; +} + +staticfn void +savegamestate(NHFILE *nhfp) +{ + int i; unsigned long uid; - struct obj * bc_objs = (struct obj *)0; -#ifdef MFLOPPY - count_only = (mode & COUNT_SAVE); -#endif + program_state.saving++; /* caller should/did already set this... */ uid = (unsigned long) getuid(); - bwrite(fd, (genericptr_t) &uid, sizeof uid); - bwrite(fd, (genericptr_t) &context, sizeof context); - bwrite(fd, (genericptr_t) &flags, sizeof flags); -#ifdef SYSFLAGS - bwrite(fd, (genericptr_t) &sysflags, sysflags); -#endif + Sfo_ulong(nhfp, &uid, "gamestate-uid"); + Sfo_char(nhfp, &svn.nhuuid[0], "nhuuid", sizeof svn.nhuuid); + Sfo_long(nhfp, &svm.moves, "gamestate-moves"); + moves_to_relative_time(&svc.context.seer_turn); + moves_to_relative_time(&svc.context.digging.lastdigtime); + Sfo_context_info(nhfp, &svc.context, "gamestate-context"); + relative_time_to_moves(&svc.context.seer_turn); + relative_time_to_moves(&svc.context.digging.lastdigtime); + + Sfo_flag(nhfp, &flags, "gamestate-flags"); urealtime.finish_time = getnow(); - urealtime.realtime += (long) (urealtime.finish_time - - urealtime.start_timing); - bwrite(fd, (genericptr_t) &u, sizeof u); - bwrite(fd, yyyymmddhhmmss(ubirthday), 14); - bwrite(fd, (genericptr_t) &urealtime.realtime, sizeof urealtime.realtime); - bwrite(fd, yyyymmddhhmmss(urealtime.start_timing), 14); /** Why? **/ + urealtime.realtime += timet_delta(urealtime.finish_time, + urealtime.start_timing); + Sfo_long(nhfp, &svw.wreserve, "wreserve"); + Sfo_int32(nhfp, &svw.wtreserved, "wtreserved"); + Sfo_you(nhfp, &u, "gamestate-you"); + Sfo_char(nhfp, yyyymmddhhmmss(ubirthday), "gamestate-ubirthday", 14); + Sfo_long(nhfp, &urealtime.realtime, "gamestate-realtime"); + Sfo_char(nhfp, yyyymmddhhmmss(urealtime.start_timing), "gamestate-start_timing", 14); /* this is the value to use for the next update of urealtime.realtime */ urealtime.start_timing = urealtime.finish_time; - save_killers(fd, mode); + save_killers(nhfp); - /* must come before migrating_objs and migrating_mons are freed */ - save_timers(fd, mode, RANGE_GLOBAL); - save_light_sources(fd, mode, RANGE_GLOBAL); + /* must come before gm.migrating_objs and gm.migrating_mons are freed */ + save_timers(nhfp, RANGE_GLOBAL); + save_light_sources(nhfp, RANGE_GLOBAL); - saveobjchn(fd, invent, mode); + /* when FREEING, deletes objects in invent and sets invent to Null; + pointers into invent (uwep, uarmg, uamul, &c) are set to Null too */ + saveobjchn(nhfp, &gi.invent); - /* save ball and chain if they are currently dangling free (i.e. not on - floor or in inventory) */ - if (CHAIN_IN_MON) { - uchain->nobj = bc_objs; - bc_objs = uchain; - } - if (BALL_IN_MON) { - uball->nobj = bc_objs; - bc_objs = uball; + /* save ball and chain if they happen to be in an unusual state */ + save_bc(nhfp); + + saveobjchn(nhfp, &gm.migrating_objs); /* frees objs and sets to Null */ + savemonchn(nhfp, gm.migrating_mons); + if (release_data(nhfp)) + gm.migrating_mons = (struct monst *) 0; + for (i = 0; i < NUMMONS; ++i) { + Sfo_mvitals(nhfp, &svm.mvitals[i], "gamestate-mvitals"); } - saveobjchn(fd, bc_objs, mode); - - saveobjchn(fd, migrating_objs, mode); - savemonchn(fd, migrating_mons, mode); - if (release_data(mode)) { - invent = 0; - migrating_objs = 0; - migrating_mons = 0; + save_dungeon(nhfp, (boolean) !!update_file(nhfp), + (boolean) !!release_data(nhfp)); + savelevchn(nhfp); + Sfo_q_score(nhfp, &svq.quest_status, "gamestate-quest_status"); + for (i = 0; i < (MAXSPELL + 1); ++i) { + Sfo_spell(nhfp, &svs.spl_book[i], "gamestate-spl_book"); } - bwrite(fd, (genericptr_t) mvitals, sizeof mvitals); - - save_dungeon(fd, (boolean) !!perform_bwrite(mode), - (boolean) !!release_data(mode)); - savelevchn(fd, mode); - bwrite(fd, (genericptr_t) &moves, sizeof moves); - bwrite(fd, (genericptr_t) &monstermoves, sizeof monstermoves); - bwrite(fd, (genericptr_t) &quest_status, sizeof quest_status); - bwrite(fd, (genericptr_t) spl_book, - sizeof(struct spell) * (MAXSPELL + 1)); - save_artifacts(fd); - save_oracles(fd, mode); - if (ustuck_id) - bwrite(fd, (genericptr_t) &ustuck_id, sizeof ustuck_id); - if (usteed_id) - bwrite(fd, (genericptr_t) &usteed_id, sizeof usteed_id); - bwrite(fd, (genericptr_t) pl_character, sizeof pl_character); - bwrite(fd, (genericptr_t) pl_fruit, sizeof pl_fruit); - savefruitchn(fd, mode); - savenames(fd, mode); - save_waterlevel(fd, mode); - save_msghistory(fd, mode); - bflush(fd); + save_artifacts(nhfp); + save_oracles(nhfp); + Sfo_char(nhfp, svp.pl_character, "gamestate-pl_character", sizeof svp.pl_character); + Sfo_char(nhfp, svp.pl_fruit, "gamestate-pl_fruit", sizeof svp.pl_fruit); + savefruitchn(nhfp); + savenames(nhfp); + save_msghistory(nhfp); + save_gamelog(nhfp); + save_luadata(nhfp); + if (nhfp->structlevel) + bflush(nhfp->fd); + program_state.saving--; + return; } +/* potentially called from goto_level(do.c) as well as savestateinlock() */ boolean -tricked_fileremoved(fd, whynot) -int fd; -char *whynot; +tricked_fileremoved(NHFILE *nhfp, char *whynot) { - if (fd < 0) { + if (!nhfp) { pline1(whynot); pline("Probably someone removed it."); - Strcpy(killer.name, whynot); + Strcpy(svk.killer.name, whynot); done(TRICKED); return TRUE; } @@ -370,12 +348,13 @@ char *whynot; #ifdef INSURANCE void -savestateinlock() +savestateinlock(void) { - int fd, hpid; - static boolean havestate = TRUE; + int hpid = 0; char whynot[BUFSZ]; + NHFILE *nhfp; + program_state.saving++; /* inhibit status and perm_invent updates */ /* When checkpointing is on, the full state needs to be written * on each checkpoint. When checkpointing is off, only the pid * needs to be in the level.0 file, so it does not need to be @@ -383,103 +362,105 @@ savestateinlock() * a game, however, the file has to be rewritten once to truncate * it and avoid restoring from outdated information. * - * Restricting havestate to this routine means that an additional + * Restricting gh.havestate to this routine means that an additional * noop pid rewriting will take place on the first "checkpoint" after * the game is started or restored, if checkpointing is off. */ - if (flags.ins_chkpt || havestate) { + if (flags.ins_chkpt || gh.havestate) { /* save the rest of the current game state in the lock file, * following the original int pid, the current level number, * and the current savefile name, which should not be subject * to any internal compression schemes since they must be * readable by an external utility */ - fd = open_levelfile(0, whynot); - if (tricked_fileremoved(fd, whynot)) + nhfp = open_levelfile(0, whynot); + if (tricked_fileremoved(nhfp, whynot)) { + program_state.saving--; return; + } - (void) read(fd, (genericptr_t) &hpid, sizeof hpid); - if (hackpid != hpid) { + Sfi_int(nhfp, &hpid, "gamestate-hackpid"); + if (svh.hackpid != hpid) { Sprintf(whynot, "Level #0 pid (%d) doesn't match ours (%d)!", - hpid, hackpid); - pline1(whynot); - Strcpy(killer.name, whynot); - done(TRICKED); + hpid, svh.hackpid); + goto giveup; } - (void) nhclose(fd); + close_nhfile(nhfp); - fd = create_levelfile(0, whynot); - if (fd < 0) { + nhfp = create_levelfile(0, whynot); + if (!nhfp) { pline1(whynot); - Strcpy(killer.name, whynot); + giveup: + Strcpy(svk.killer.name, whynot); + /* done(TRICKED) will return when running in wizard mode; + clear the display-update-suppression flag before rather + than after so that screen updating behaves normally; + game data shouldn't be inconsistent yet, unlike it would + become midway through saving */ + program_state.saving--; done(TRICKED); return; } - (void) write(fd, (genericptr_t) &hackpid, sizeof hackpid); + nhfp->mode = WRITING; + Sfo_int(nhfp, &svh.hackpid, "gamestate-hackpid"); if (flags.ins_chkpt) { int currlev = ledger_no(&u.uz); - (void) write(fd, (genericptr_t) &currlev, sizeof currlev); - save_savefile_name(fd); - store_version(fd); - store_savefileinfo(fd); - store_plname_in_file(fd); + Sfo_int(nhfp, &currlev, "gamestate-savestateinlock"); + save_savefile_name(nhfp); + store_version(nhfp); + store_plname_in_file(nhfp); - ustuck_id = (u.ustuck ? u.ustuck->m_id : 0); - usteed_id = (u.usteed ? u.usteed->m_id : 0); - savegamestate(fd, WRITE_SAVE); + /* if ball and/or chain aren't on floor or in invent, keep a copy + of their pointers; not valid when on floor or in invent */ + gl.looseball = BALL_IN_MON ? uball : 0; + gl.loosechain = CHAIN_IN_MON ? uchain : 0; + savegamestate(nhfp); } - bclose(fd); + close_nhfile(nhfp); } - havestate = flags.ins_chkpt; + program_state.saving--; + gh.havestate = flags.ins_chkpt; + return; } #endif -#ifdef MFLOPPY -boolean -savelev(fd, lev, mode) -int fd; -xchar lev; -int mode; +void +savelev(NHFILE *nhfp, xint8 lev) { - if (mode & COUNT_SAVE) { - bytes_counted = 0; - savelev0(fd, lev, COUNT_SAVE); - /* probably bytes_counted will be filled in again by an - * immediately following WRITE_SAVE anyway, but we'll - * leave it out of checkspace just in case */ - if (iflags.checkspace) { - while (bytes_counted > freediskspace(levels)) - if (!swapout_oldest()) - return FALSE; + boolean set_uz_save = (gu.uz_save.dnum == 0 && gu.uz_save.dlevel == 0); + + /* caller might have already set up gu.uz_save and zeroed u.uz; + if not, we need to set it for save_bubbles(); caveat: if the + player quits during character selection, u.uz won't be set yet + but we'll be called during run-down */ + if (set_uz_save && (nhfp->mode & (COUNTING | WRITING))) { + if (u.uz.dnum == 0 && u.uz.dlevel == 0) { + program_state.something_worth_saving = 0; + panic("savelev: where are we?"); } + gu.uz_save = u.uz; } - if (mode & (WRITE_SAVE | FREE_SAVE)) { - bytes_counted = 0; - savelev0(fd, lev, mode); - } - if (mode != FREE_SAVE) { - level_info[lev].where = ACTIVE; - level_info[lev].time = moves; - level_info[lev].size = bytes_counted; - } - return TRUE; + + savelev_core(nhfp, lev); + + if (set_uz_save) + gu.uz_save.dnum = gu.uz_save.dlevel = 0; /* unset */ } -STATIC_OVL void -savelev0(fd, lev, mode) -#else -void -savelev(fd, lev, mode) -#endif -int fd; -xchar lev; -int mode; +staticfn void +savelev_core(NHFILE *nhfp, xint8 lev) { #ifdef TOS short tlev; #endif + int i, c, r; + coord *tmpc; + program_state.saving++; /* even if current mode is FREEING */ + + if (!nhfp) + panic("Save on bad file!"); /* impossible */ /* * Level file contents: * version info (handled by caller); @@ -496,8 +477,8 @@ int mode; * portion (which has some freeing to do), then jump quite a bit * further ahead to the middle of the 'actual level data' portion. */ - if (mode != FREE_SAVE) { - /* WRITE_SAVE (probably ORed with FREE_SAVE), or COUNT_SAVE */ + if (nhfp->mode != FREEING) { + /* WRITING (probably ORed with FREEING), or COUNTING */ /* purge any dead monsters (necessary if we're starting a panic save rather than a normal one, or sometimes @@ -505,22 +486,14 @@ int mode; create statue trap then immediately level teleport) */ if (iflags.purge_monsters) dmonsfree(); + /* clear objs_deleted list too */ + if (go.objs_deleted) + dobjsfree(); /* really free deleted objects */ - if (fd < 0) - panic("Save on bad file!"); /* impossible */ -#ifdef MFLOPPY - count_only = (mode & COUNT_SAVE); -#endif if (lev >= 0 && lev <= maxledgerno()) - level_info[lev].flags |= VISITED; - bwrite(fd, (genericptr_t) &hackpid, sizeof hackpid); -#ifdef TOS - tlev = lev; - tlev &= 0x00ff; - bwrite(fd, (genericptr_t) &tlev, sizeof tlev); -#else - bwrite(fd, (genericptr_t) &lev, sizeof lev); -#endif + svl.level_info[lev].flags |= VISITED; + Sfo_int(nhfp, &svh.hackpid, "gamestate-hackpid"); + Sfo_xint8(nhfp, &lev, "gamestate-dlvl"); } /* bones info comes before level data; the intent is for an external @@ -529,648 +502,444 @@ int mode; the guessing that was needed in 3.4.3 and without having to interpret level data to find where to start; unfortunately it still needs to handle all the data compression schemes */ - savecemetery(fd, mode, &level.bonesinfo); - if (mode == FREE_SAVE) /* see above */ + savecemetery(nhfp, &svl.level.bonesinfo); + if (nhfp->mode == FREEING) /* see above */ goto skip_lots; - savelevl(fd, (boolean) ((sfsaveinfo.sfi1 & SFI1_RLECOMP) == SFI1_RLECOMP)); - bwrite(fd, (genericptr_t) lastseentyp, sizeof lastseentyp); - bwrite(fd, (genericptr_t) &monstermoves, sizeof monstermoves); - bwrite(fd, (genericptr_t) &upstair, sizeof (stairway)); - bwrite(fd, (genericptr_t) &dnstair, sizeof (stairway)); - bwrite(fd, (genericptr_t) &upladder, sizeof (stairway)); - bwrite(fd, (genericptr_t) &dnladder, sizeof (stairway)); - bwrite(fd, (genericptr_t) &sstairs, sizeof (stairway)); - bwrite(fd, (genericptr_t) &updest, sizeof (dest_area)); - bwrite(fd, (genericptr_t) &dndest, sizeof (dest_area)); - bwrite(fd, (genericptr_t) &level.flags, sizeof level.flags); - bwrite(fd, (genericptr_t) doors, sizeof doors); - save_rooms(fd); /* no dynamic memory to reclaim */ - - /* from here on out, saving also involves allocated memory cleanup */ - skip_lots: - /* timers and lights must be saved before monsters and objects */ - save_timers(fd, mode, RANGE_LEVEL); - save_light_sources(fd, mode, RANGE_LEVEL); - - savemonchn(fd, fmon, mode); - save_worm(fd, mode); /* save worm information */ - savetrapchn(fd, ftrap, mode); - saveobjchn(fd, fobj, mode); - saveobjchn(fd, level.buriedobjlist, mode); - saveobjchn(fd, billobjs, mode); - if (release_data(mode)) { - int x,y; - - for (y = 0; y < ROWNO; y++) - for (x = 0; x < COLNO; x++) - level.monsters[x][y] = 0; - fmon = 0; - ftrap = 0; - fobj = level.buriedobjlist = billobjs = 0; - /* level.bonesinfo = 0; -- handled by savecemetery() */ - } - save_engravings(fd, mode); - savedamage(fd, mode); /* pending shop wall and/or floor repair */ - save_regions(fd, mode); - if (mode != FREE_SAVE) - bflush(fd); -} - -STATIC_OVL void -savelevl(fd, rlecomp) -int fd; -boolean rlecomp; -{ -#ifdef RLECOMP - struct rm *prm, *rgrm; - int x, y; - uchar match; - - if (rlecomp) { - /* perform run-length encoding of rm structs */ - - rgrm = &levl[0][0]; /* start matching at first rm */ - match = 0; - - for (y = 0; y < ROWNO; y++) { - for (x = 0; x < COLNO; x++) { - prm = &levl[x][y]; - if (prm->glyph == rgrm->glyph && prm->typ == rgrm->typ - && prm->seenv == rgrm->seenv - && prm->horizontal == rgrm->horizontal - && prm->flags == rgrm->flags && prm->lit == rgrm->lit - && prm->waslit == rgrm->waslit - && prm->roomno == rgrm->roomno && prm->edge == rgrm->edge - && prm->candig == rgrm->candig) { - match++; - if (match > 254) { - match = 254; /* undo this match */ - goto writeout; - } - } else { - /* run has been broken, write out run-length encoding */ - writeout: - bwrite(fd, (genericptr_t) &match, sizeof (uchar)); - bwrite(fd, (genericptr_t) rgrm, sizeof (struct rm)); - /* start encoding again. we have at least 1 rm - in the next run, viz. this one. */ - match = 1; - rgrm = prm; - } - } + savelevl(nhfp); + for (c = 0; c < COLNO; ++c) { + for (r = 0; r < ROWNO; ++r) { + Sfo_schar(nhfp, &svl.lastseentyp[c][r], "lastseentyp"); } - if (match > 0) { - bwrite(fd, (genericptr_t) &match, sizeof (uchar)); - bwrite(fd, (genericptr_t) rgrm, sizeof (struct rm)); + } + /* svm.moves will actually be read back into svo.omoves on restore */ + Sfo_long(nhfp, &svm.moves, "lev-timestmp"); + save_stairs(nhfp); + Sfo_dest_area(nhfp, &svu.updest, "lev-updest"); + Sfo_dest_area(nhfp, &svd.dndest, "lev-dndest"); + save_adjust_levelflags(); + Sfo_levelflags(nhfp, &svl.level.flags, "lev-level_flags"); + rest_adjust_levelflags(); + + Sfo_int(nhfp, &svd.doors_alloc, "lev-doors_alloc"); + /* don't rely on underlying write() behavior to write + * nothing if count arg is 0, just skip it */ + if (svd.doors_alloc) { + tmpc = svd.doors; + for (i = 0; i < svd.doors_alloc; ++i) { + Sfo_nhcoord(nhfp, tmpc, "lev-doors"); + tmpc++; } - return; } -#else /* !RLECOMP */ - nhUse(rlecomp); -#endif /* ?RLECOMP */ - bwrite(fd, (genericptr_t) levl, sizeof levl); -} + save_rooms(nhfp); /* no dynamic memory to reclaim */ -/*ARGSUSED*/ -void -bufon(fd) -int fd; -{ - (*saveprocs.save_bufon)(fd); - return; -} - -/*ARGSUSED*/ -void -bufoff(fd) -int fd; -{ - (*saveprocs.save_bufoff)(fd); - return; -} - -/* flush run and buffer */ -void -bflush(fd) -register int fd; -{ - (*saveprocs.save_bflush)(fd); - return; -} - -void -bwrite(fd, loc, num) -int fd; -genericptr_t loc; -register unsigned num; -{ - (*saveprocs.save_bwrite)(fd, loc, num); - return; -} - -void -bclose(fd) -int fd; -{ - (*saveprocs.save_bclose)(fd); - return; -} - -static int bw_fd = -1; -static FILE *bw_FILE = 0; -static boolean buffering = FALSE; - -STATIC_OVL void -def_bufon(fd) -int fd; -{ -#ifdef UNIX - if (bw_fd != fd) { - if (bw_fd >= 0) - panic("double buffering unexpected"); - bw_fd = fd; - if ((bw_FILE = fdopen(fd, "w")) == 0) - panic("buffering of file %d failed", fd); + /* from here on out, saving also involves allocated memory cleanup */ + skip_lots: + /* timers and lights must be saved before monsters and objects */ + save_timers(nhfp, RANGE_LEVEL); + save_light_sources(nhfp, RANGE_LEVEL); + + savemonchn(nhfp, fmon); + save_worm(nhfp); /* save worm information */ + savetrapchn(nhfp, gf.ftrap); + saveobjchn(nhfp, &fobj); + saveobjchn(nhfp, &svl.level.buriedobjlist); + saveobjchn(nhfp, &gb.billobjs); + save_engravings(nhfp); + savedamage(nhfp); /* pending shop wall and/or floor repair */ + save_regions(nhfp); + save_bubbles(nhfp, lev); /* for water and air */ + save_exclusions(nhfp); + save_track(nhfp); + + if (nhfp->mode != FREEING) { + if (nhfp->structlevel) + bflush(nhfp->fd); } -#endif - buffering = TRUE; -} - -STATIC_OVL void -def_bufoff(fd) -int fd; -{ - def_bflush(fd); - buffering = FALSE; -} - -STATIC_OVL void -def_bflush(fd) -int fd; -{ -#ifdef UNIX - if (fd == bw_fd) { - if (fflush(bw_FILE) == EOF) - panic("flush of savefile failed!"); + program_state.saving--; + if (release_data(nhfp)) { + clear_level_structures(); + gf.ftrap = 0; + gb.billobjs = 0; + (void) memset(svr.rooms, 0, sizeof(svr.rooms)); } -#endif return; } -STATIC_OVL void -def_bwrite(fd, loc, num) -register int fd; -register genericptr_t loc; -register unsigned num; -{ - boolean failed; - -#ifdef MFLOPPY - bytes_counted += num; - if (count_only) - return; -#endif - -#ifdef UNIX - if (buffering) { - if (fd != bw_fd) - panic("unbuffered write to fd %d (!= %d)", fd, bw_fd); - - failed = (fwrite(loc, (int) num, 1, bw_FILE) != 1); - } else -#endif /* UNIX */ - { - /* lint wants 3rd arg of write to be an int; lint -p an unsigned */ -#if defined(BSD) || defined(ULTRIX) || defined(WIN32) || defined(_MSC_VER) - failed = ((long) write(fd, loc, (int) num) != (long) num); -#else /* e.g. SYSV, __TURBOC__ */ - failed = ((long) write(fd, loc, num) != (long) num); -#endif - } - - if (failed) { -#if defined(UNIX) || defined(VMS) || defined(__EMX__) - if (program_state.done_hup) - nh_terminate(EXIT_FAILURE); - else -#endif - panic("cannot write %u bytes to file #%d", num, fd); - } -} - void -def_bclose(fd) -int fd; -{ - bufoff(fd); -#ifdef UNIX - if (fd == bw_fd) { - (void) fclose(bw_FILE); - bw_fd = -1; - bw_FILE = 0; - } else -#endif - (void) nhclose(fd); - return; -} - -#ifdef ZEROCOMP -/* The runs of zero-run compression are flushed after the game state or a - * level is written out. This adds a couple bytes to a save file, where - * the runs could be mashed together, but it allows gluing together game - * state and level files to form a save file, and it means the flushing - * does not need to be specifically called for every other time a level - * file is written out. - */ - -#define RLESC '\0' /* Leading character for run of LRESC's */ -#define flushoutrun(ln) (zerocomp_bputc(RLESC), zerocomp_bputc(ln), ln = -1) - -#ifndef ZEROCOMP_BUFSIZ -#define ZEROCOMP_BUFSIZ BUFSZ -#endif -static NEARDATA unsigned char outbuf[ZEROCOMP_BUFSIZ]; -static NEARDATA unsigned short outbufp = 0; -static NEARDATA short outrunlength = -1; -static NEARDATA int bwritefd; -static NEARDATA boolean compressing = FALSE; - -/*dbg() -{ - HUP printf("outbufp %d outrunlength %d\n", outbufp,outrunlength); -}*/ - -STATIC_OVL void -zerocomp_bputc(c) -int c; +save_adjust_levelflags(void) { -#ifdef MFLOPPY - bytes_counted++; - if (count_only) - return; -#endif - if (outbufp >= sizeof outbuf) { - (void) write(bwritefd, outbuf, sizeof outbuf); - outbufp = 0; - } - outbuf[outbufp++] = (unsigned char) c; -} - -/*ARGSUSED*/ -void STATIC_OVL -zerocomp_bufon(fd) -int fd; -{ - compressing = TRUE; - return; + /* adjust any timestamps */ + moves_to_relative_time(&svl.level.flags.stasis_until); } -/*ARGSUSED*/ -STATIC_OVL void -zerocomp_bufoff(fd) -int fd; +staticfn void +savelevl(NHFILE *nhfp) { - if (outbufp) { - outbufp = 0; - panic("closing file with buffered data still unwritten"); - } - outrunlength = -1; - compressing = FALSE; - return; -} - -/* flush run and buffer */ -STATIC_OVL void -zerocomp_bflush(fd) -register int fd; -{ - bwritefd = fd; - if (outrunlength >= 0) { /* flush run */ - flushoutrun(outrunlength); - } -#ifdef MFLOPPY - if (count_only) - outbufp = 0; -#endif - - if (outbufp) { - if (write(fd, outbuf, outbufp) != outbufp) { -#if defined(UNIX) || defined(VMS) || defined(__EMX__) - if (program_state.done_hup) - nh_terminate(EXIT_FAILURE); - else -#endif - zerocomp_bclose(fd); /* panic (outbufp != 0) */ - } - outbufp = 0; - } -} - -STATIC_OVL void -zerocomp_bwrite(fd, loc, num) -int fd; -genericptr_t loc; -register unsigned num; -{ - register unsigned char *bp = (unsigned char *) loc; + int x, y; - if (!compressing) { -#ifdef MFLOPPY - bytes_counted += num; - if (count_only) - return; -#endif - if ((unsigned) write(fd, loc, num) != num) { -#if defined(UNIX) || defined(VMS) || defined(__EMX__) - if (program_state.done_hup) - nh_terminate(EXIT_FAILURE); - else -#endif - panic("cannot write %u bytes to file #%d", num, fd); - } - } else { - bwritefd = fd; - for (; num; num--, bp++) { - if (*bp == RLESC) { /* One more char in run */ - if (++outrunlength == 0xFF) { - flushoutrun(outrunlength); - } - } else { /* end of run */ - if (outrunlength >= 0) { /* flush run */ - flushoutrun(outrunlength); - } - zerocomp_bputc(*bp); - } + for (x = 0; x < COLNO; x++) { + for (y = 0; y < ROWNO; y++) { + Sfo_rm(nhfp, &levl[x][y], "location-rm"); } } -} - -void -zerocomp_bclose(fd) -int fd; -{ - zerocomp_bufoff(fd); - (void) nhclose(fd); return; } -#endif /* ZEROCOMP */ -STATIC_OVL void -savelevchn(fd, mode) -register int fd, mode; +/* save Plane of Water's air bubbles and Plane of Air's clouds */ +staticfn void +save_bubbles(NHFILE *nhfp, xint8 lev) { - s_level *tmplev, *tmplev2; - int cnt = 0; - - for (tmplev = sp_levchn; tmplev; tmplev = tmplev->next) - cnt++; - if (perform_bwrite(mode)) - bwrite(fd, (genericptr_t) &cnt, sizeof cnt); + xint8 bbubbly; + + /* air bubbles and clouds used to be saved as part of game state + because restoring them needs dungeon data that isn't available + during the first pass of their levels; now that they are part of + the current level instead, we write a zero or non-zero marker + so that restore can determine whether they are present even when + u.uz and ledger_no() aren't available to it yet */ + bbubbly = 0; + if (lev == ledger_no(&water_level) || lev == ledger_no(&air_level)) + bbubbly = lev; /* non-zero */ + if (update_file(nhfp)) + Sfo_xint8(nhfp, &bbubbly, "bubbles-bbubbly"); +#if 0 + if (nhfp->structlevel) + xxwrite(nhfp->fd, (genericptr_t) &bbubbly, sizeof bbubbly); +#endif - for (tmplev = sp_levchn; tmplev; tmplev = tmplev2) { - tmplev2 = tmplev->next; - if (perform_bwrite(mode)) - bwrite(fd, (genericptr_t) tmplev, sizeof *tmplev); - if (release_data(mode)) - free((genericptr_t) tmplev); - } - if (release_data(mode)) - sp_levchn = 0; + if (bbubbly) + save_waterlevel(nhfp); /* save air bubbles or clouds */ } /* used when saving a level and also when saving dungeon overview data */ void -savecemetery(fd, mode, cemeteryaddr) -int fd; -int mode; -struct cemetery **cemeteryaddr; +savecemetery(NHFILE *nhfp, struct cemetery **cemeteryaddr) { struct cemetery *thisbones, *nextbones; int flag; flag = *cemeteryaddr ? 0 : -1; - if (perform_bwrite(mode)) - bwrite(fd, (genericptr_t) &flag, sizeof flag); + if (update_file(nhfp)) { + Sfo_int(nhfp, &flag, "cemetery-cemetery_flag"); + } nextbones = *cemeteryaddr; while ((thisbones = nextbones) != 0) { nextbones = thisbones->next; - if (perform_bwrite(mode)) - bwrite(fd, (genericptr_t) thisbones, sizeof *thisbones); - if (release_data(mode)) + if (update_file(nhfp)) { + Sfo_cemetery(nhfp, thisbones, "cemetery-bonesinfo"); + } + if (release_data(nhfp)) free((genericptr_t) thisbones); } - if (release_data(mode)) + if (release_data(nhfp)) *cemeteryaddr = 0; } -STATIC_OVL void -savedamage(fd, mode) -register int fd, mode; +staticfn void +savedamage(NHFILE *nhfp) { - register struct damage *damageptr, *tmp_dam; + struct damage *damageptr, *tmp_dam; unsigned int xl = 0; - damageptr = level.damagelist; + damageptr = svl.level.damagelist; for (tmp_dam = damageptr; tmp_dam; tmp_dam = tmp_dam->next) xl++; - if (perform_bwrite(mode)) - bwrite(fd, (genericptr_t) &xl, sizeof xl); - - while (xl--) { - if (perform_bwrite(mode)) - bwrite(fd, (genericptr_t) damageptr, sizeof *damageptr); + if (update_file(nhfp)) { + Sfo_unsigned(nhfp, &xl, "damage-damage_count"); + } + while (damageptr) { + if (update_file(nhfp)) { + Sfo_damage(nhfp, damageptr, "damage"); + } tmp_dam = damageptr; damageptr = damageptr->next; - if (release_data(mode)) + if (release_data(nhfp)) free((genericptr_t) tmp_dam); } - if (release_data(mode)) - level.damagelist = 0; + if (release_data(nhfp)) + svl.level.damagelist = 0; +} + +staticfn void +save_stairs(NHFILE *nhfp) +{ + stairway *stway = gs.stairs; + int buflen = (int) sizeof *stway; + + while (stway) { + if (update_file(nhfp)) { + boolean use_relative = (program_state.restoring != REST_GSTATE + && stway->tolev.dnum == u.uz.dnum); + if (use_relative) { + /* make dlevel relative to current level */ + stway->tolev.dlevel -= u.uz.dlevel; + } + Sfo_int(nhfp, &buflen, "stairs-staircount"); + Sfo_stairway(nhfp, stway, "stairs-stairway"); + if (use_relative) { + /* reset stairway dlevel back to absolute */ + stway->tolev.dlevel += u.uz.dlevel; + } + } + stway = stway->next; + } + if (update_file(nhfp)) { + buflen = -1; + Sfo_int(nhfp, &buflen, "stairs-staircount"); + } +} + +/* if ball and/or chain are loose, make an object chain for it/them and + save that separately from other objects */ +staticfn void +save_bc(NHFILE *nhfp) +{ + struct obj *bc_objs = 0; + + /* save ball and chain if they are currently dangling free (i.e. not + on floor or in inventory); 'looseball' and 'loosechain' have been + set up in caller because ball and chain will be gone by now if on + floor, or ball gone if carried */ + if (gl.loosechain) { + gl.loosechain->nobj = bc_objs; /* uchain */ + bc_objs = gl.loosechain; + if (nhfp->mode & FREEING) { + setworn((struct obj *) 0, W_CHAIN); /* sets 'uchain' to Null */ + gl.loosechain = (struct obj *) 0; + } + } + if (gl.looseball) { + gl.looseball->nobj = bc_objs; + bc_objs = gl.looseball; + if (nhfp->mode & FREEING) { + setworn((struct obj *) 0, W_BALL); /* sets 'uball' to Null */ + gl.looseball = (struct obj *) 0; + } + } + saveobjchn(nhfp, &bc_objs); /* frees objs in list, sets bc_objs to Null */ } -STATIC_OVL void -saveobj(fd, otmp) -int fd; -struct obj *otmp; +/* save one object; + caveat: this is only for update_file(); caller handles release_data() */ +staticfn void +saveobj(NHFILE *nhfp, struct obj *otmp) { int buflen, zerobuf = 0; buflen = (int) sizeof (struct obj); - bwrite(fd, (genericptr_t) &buflen, sizeof buflen); - bwrite(fd, (genericptr_t) otmp, buflen); + Sfo_int(nhfp, &buflen, "obj-obj_length"); + Sfo_obj(nhfp, otmp, "obj"); if (otmp->oextra) { buflen = ONAME(otmp) ? (int) strlen(ONAME(otmp)) + 1 : 0; - bwrite(fd, (genericptr_t) &buflen, sizeof buflen); - if (buflen > 0) - bwrite(fd, (genericptr_t) ONAME(otmp), buflen); + Sfo_int(nhfp, &buflen, "obj-oname_length"); + if (buflen > 0) { + Sfo_char(nhfp, ONAME(otmp), "obj-oname", buflen); + } /* defer to savemon() for this one */ - if (OMONST(otmp)) - savemon(fd, OMONST(otmp)); - else - bwrite(fd, (genericptr_t) &zerobuf, sizeof zerobuf); - - buflen = OMID(otmp) ? (int) sizeof (unsigned) : 0; - bwrite(fd, (genericptr_t) &buflen, sizeof buflen); - if (buflen > 0) - bwrite(fd, (genericptr_t) OMID(otmp), buflen); - - /* TODO: post 3.6.x, get rid of this */ - buflen = OLONG(otmp) ? (int) sizeof (long) : 0; - bwrite(fd, (genericptr_t) &buflen, sizeof buflen); - if (buflen > 0) - bwrite(fd, (genericptr_t) OLONG(otmp), buflen); - + if (OMONST(otmp)) { + savemon(nhfp, OMONST(otmp)); + } else { + Sfo_int(nhfp, &zerobuf, "obj-omonst_length"); + } + /* extra info about scroll of mail */ buflen = OMAILCMD(otmp) ? (int) strlen(OMAILCMD(otmp)) + 1 : 0; - bwrite(fd, (genericptr_t) &buflen, sizeof buflen); - if (buflen > 0) - bwrite(fd, (genericptr_t) OMAILCMD(otmp), buflen); + Sfo_int(nhfp, &buflen, "obj-omailcmd_length"); + if (buflen > 0) { + Sfo_char(nhfp, OMAILCMD(otmp), "obj-omailcmd", buflen); + } + /* omid used to be indirect via a pointer in oextra but has + become part of oextra itself; 0 means not applicable and + gets saved/restored whenever any other oextra components do */ + Sfo_unsigned(nhfp, &OMID(otmp), "obj-omid"); } } -STATIC_OVL void -saveobjchn(fd, otmp, mode) -register int fd, mode; -register struct obj *otmp; +/* save an object chain; sets head of list to Null when done; + handles release_data() for each object in the list */ +staticfn void +saveobjchn(NHFILE *nhfp, struct obj **obj_p) { - register struct obj *otmp2; + struct obj *otmp = *obj_p; + struct obj *otmp2; + boolean is_invent = (otmp && otmp == gi.invent); int minusone = -1; while (otmp) { otmp2 = otmp->nobj; - if (perform_bwrite(mode)) { - saveobj(fd, otmp); + if (update_file(nhfp)) { + saveobj(nhfp, otmp); } if (Has_contents(otmp)) - saveobjchn(fd, otmp->cobj, mode); - if (release_data(mode)) { + saveobjchn(nhfp, &otmp->cobj); + if (release_data(nhfp)) { /* * If these are on the floor, the discarding could be * due to game save, or we could just be changing levels. * Always invalidate the pointer, but ensure that we have * the o_id in order to restore the pointer on reload. */ - if (otmp == context.victual.piece) { - context.victual.o_id = otmp->o_id; - context.victual.piece = (struct obj *) 0; + if (otmp == svc.context.victual.piece) { + svc.context.victual.o_id = otmp->o_id; + svc.context.victual.piece = (struct obj *) 0; } - if (otmp == context.tin.tin) { - context.tin.o_id = otmp->o_id; - context.tin.tin = (struct obj *) 0; + if (otmp == svc.context.tin.tin) { + svc.context.tin.o_id = otmp->o_id; + svc.context.tin.tin = (struct obj *) 0; } - if (otmp == context.spbook.book) { - context.spbook.o_id = otmp->o_id; - context.spbook.book = (struct obj *) 0; + if (otmp == svc.context.spbook.book) { + svc.context.spbook.o_id = otmp->o_id; + svc.context.spbook.book = (struct obj *) 0; } otmp->where = OBJ_FREE; /* set to free so dealloc will work */ otmp->nobj = NULL; /* nobj saved into otmp2 */ otmp->cobj = NULL; /* contents handled above */ otmp->timed = 0; /* not timed any more */ otmp->lamplit = 0; /* caller handled lights */ + otmp->leashmon = 0; /* mon->mleashed could still be set; + * unfortunately, we can't clear that + * until after the monster is saved */ + /* clear 'uball' and 'uchain' pointers if resetting their mask; + could also do same for other worn items but don't need to */ + if ((otmp->owornmask & (W_BALL | W_CHAIN)) != 0) + setworn((struct obj *) 0, + otmp->owornmask & (W_BALL | W_CHAIN)); + otmp->owornmask = 0L; /* no longer care */ + program_state.freeingdata++; dealloc_obj(otmp); + program_state.freeingdata--; } otmp = otmp2; } - if (perform_bwrite(mode)) - bwrite(fd, (genericptr_t) &minusone, sizeof (int)); + if (update_file(nhfp)) { + Sfo_int(nhfp, &minusone, "obj-obj_length"); + } + if (release_data(nhfp)) { + if (is_invent) + allunworn(); /* clear uwep, uarm, uball, &c pointers */ + *obj_p = (struct obj *) 0; + } } -STATIC_OVL void -savemon(fd, mtmp) -int fd; -struct monst *mtmp; +staticfn void +savemon(NHFILE *nhfp, struct monst *mtmp) { int buflen; mtmp->mtemplit = 0; /* normally clear; if set here then a panic save * is being written while bhit() was executing */ buflen = (int) sizeof (struct monst); - bwrite(fd, (genericptr_t) &buflen, sizeof buflen); - bwrite(fd, (genericptr_t) mtmp, buflen); + Sfo_int(nhfp, &buflen, "monst-monst_length"); + Sfo_monst(nhfp, mtmp, "monst"); if (mtmp->mextra) { - buflen = MNAME(mtmp) ? (int) strlen(MNAME(mtmp)) + 1 : 0; - bwrite(fd, (genericptr_t) &buflen, sizeof buflen); - if (buflen > 0) - bwrite(fd, (genericptr_t) MNAME(mtmp), buflen); + buflen = MGIVENNAME(mtmp) ? (int) strlen(MGIVENNAME(mtmp)) + 1 : 0; + Sfo_int(nhfp, &buflen, "monst-mgivenname_length"); + if (buflen > 0) { + Sfo_char(nhfp, MGIVENNAME(mtmp), "monst-mgivenname", buflen); + } buflen = EGD(mtmp) ? (int) sizeof (struct egd) : 0; - bwrite(fd, (genericptr_t) &buflen, sizeof buflen); - if (buflen > 0) - bwrite(fd, (genericptr_t) EGD(mtmp), buflen); + Sfo_int(nhfp, &buflen, "monst-egd_length"); + if (buflen > 0) { + Sfo_egd(nhfp, EGD(mtmp), "monst-egd"); + } buflen = EPRI(mtmp) ? (int) sizeof (struct epri) : 0; - bwrite(fd, (genericptr_t) &buflen, sizeof buflen); - if (buflen > 0) - bwrite(fd, (genericptr_t) EPRI(mtmp), buflen); + Sfo_int(nhfp, &buflen, "monst-epri_length"); + if (buflen > 0) { + Sfo_epri(nhfp, EPRI(mtmp), "monst-epri"); + } buflen = ESHK(mtmp) ? (int) sizeof (struct eshk) : 0; - bwrite(fd, (genericptr_t) &buflen, sizeof(int)); - if (buflen > 0) - bwrite(fd, (genericptr_t) ESHK(mtmp), buflen); + Sfo_int(nhfp, &buflen, "monst-eshk_length"); + if (buflen > 0) { + Sfo_eshk(nhfp, ESHK(mtmp), "monst-eshk"); + } buflen = EMIN(mtmp) ? (int) sizeof (struct emin) : 0; - bwrite(fd, (genericptr_t) &buflen, sizeof(int)); - if (buflen > 0) - bwrite(fd, (genericptr_t) EMIN(mtmp), buflen); + Sfo_int(nhfp, &buflen, "monst-emin_length"); + if (buflen > 0) { + Sfo_emin(nhfp, EMIN(mtmp), "monst-emin"); + } buflen = EDOG(mtmp) ? (int) sizeof (struct edog) : 0; - bwrite(fd, (genericptr_t) &buflen, sizeof(int)); - if (buflen > 0) - bwrite(fd, (genericptr_t) EDOG(mtmp), buflen); + Sfo_int(nhfp, &buflen, "monst-edog_length"); + if (buflen > 0) { + /* we only store relative times in save and bones */ + moves_to_relative_time(&EDOG(mtmp)->droptime); + moves_to_relative_time(&EDOG(mtmp)->hungrytime); + Sfo_edog(nhfp, EDOG(mtmp), "monst-edog"); + relative_time_to_moves(&EDOG(mtmp)->droptime); + relative_time_to_moves(&EDOG(mtmp)->hungrytime); + } + buflen = EBONES(mtmp) ? (int) sizeof (struct ebones) : 0; + Sfo_int(nhfp, &buflen, "monst-ebones_length"); + if (buflen > 0) { + Sfo_ebones(nhfp, EBONES(mtmp), "monst-ebones"); + } /* mcorpsenm is inline int rather than pointer to something, so doesn't need to be preceded by a length field */ - bwrite(fd, (genericptr_t) &MCORPSENM(mtmp), sizeof MCORPSENM(mtmp)); + buflen = (int) MCORPSENM(mtmp); + Sfo_int(nhfp, &buflen, "monst-mcorpsenm"); } } -STATIC_OVL void -savemonchn(fd, mtmp, mode) -register int fd, mode; -register struct monst *mtmp; +staticfn void +savemonchn(NHFILE *nhfp, struct monst *mtmp) { - register struct monst *mtmp2; + struct monst *mtmp2; int minusone = -1; while (mtmp) { mtmp2 = mtmp->nmon; - if (perform_bwrite(mode)) { + if (update_file(nhfp)) { mtmp->mnum = monsndx(mtmp->data); if (mtmp->ispriest) forget_temple_entry(mtmp); /* EPRI() */ - savemon(fd, mtmp); + savemon(nhfp, mtmp); } if (mtmp->minvent) - saveobjchn(fd, mtmp->minvent, mode); - if (release_data(mode)) { - if (mtmp == context.polearm.hitmon) { - context.polearm.m_id = mtmp->m_id; - context.polearm.hitmon = NULL; + saveobjchn(nhfp, &mtmp->minvent); + if (release_data(nhfp)) { + if (mtmp == svc.context.polearm.hitmon) { + svc.context.polearm.m_id = mtmp->m_id; + svc.context.polearm.hitmon = NULL; } + if (mtmp == u.ustuck) + u.ustuck_mid = u.ustuck->m_id; + if (mtmp == u.usteed) + u.usteed_mid = u.usteed->m_id; mtmp->nmon = NULL; /* nmon saved into mtmp2 */ dealloc_monst(mtmp); } mtmp = mtmp2; } - if (perform_bwrite(mode)) - bwrite(fd, (genericptr_t) &minusone, sizeof (int)); + if (update_file(nhfp)) { + Sfo_int(nhfp, &minusone, "monst-monst_length"); + } } -/* save traps; ftrap is the only trap chain so the 2nd arg is superfluous */ -STATIC_OVL void -savetrapchn(fd, trap, mode) -int fd; -register struct trap *trap; -int mode; +/* save traps; gf.ftrap is the only trap chain so 2nd arg is superfluous */ +staticfn void +savetrapchn(NHFILE *nhfp, struct trap *trap) { static struct trap zerotrap; - register struct trap *trap2; + struct trap *trap2; while (trap) { + boolean use_relative = (program_state.restoring != REST_GSTATE + && trap->dst.dnum == u.uz.dnum); trap2 = trap->ntrap; - if (perform_bwrite(mode)) - bwrite(fd, (genericptr_t) trap, sizeof *trap); - if (release_data(mode)) + if (use_relative) + trap->dst.dlevel -= u.uz.dlevel; /* make it relative */ + if (update_file(nhfp)) { + Sfo_trap(nhfp, trap, "trap"); + } + if (use_relative) + trap->dst.dlevel += u.uz.dlevel; /* reset back to absolute */ + if (release_data(nhfp)) dealloc_trap(trap); trap = trap2; } - if (perform_bwrite(mode)) - bwrite(fd, (genericptr_t) &zerotrap, sizeof zerotrap); + if (update_file(nhfp)) { + Sfo_trap(nhfp, &zerotrap, "trap"); + } } /* save all the fruit names and ID's; this is used only in saving whole games @@ -1179,179 +948,177 @@ int mode; * level routine marks nonexistent fruits by making the fid negative. */ void -savefruitchn(fd, mode) -int fd, mode; +savefruitchn(NHFILE *nhfp) { static struct fruit zerofruit; - register struct fruit *f2, *f1; + struct fruit *f2, *f1; - f1 = ffruit; + f1 = gf.ffruit; while (f1) { f2 = f1->nextf; - if (f1->fid >= 0 && perform_bwrite(mode)) - bwrite(fd, (genericptr_t) f1, sizeof *f1); - if (release_data(mode)) + if (f1->fid >= 0 && update_file(nhfp)) { + Sfo_fruit(nhfp, f1, "fruit"); + } + if (release_data(nhfp)) dealloc_fruit(f1); f1 = f2; } - if (perform_bwrite(mode)) - bwrite(fd, (genericptr_t) &zerofruit, sizeof zerofruit); - if (release_data(mode)) - ffruit = 0; + if (update_file(nhfp)) { + Sfo_fruit(nhfp, &zerofruit, "fruit"); + } + if (release_data(nhfp)) + gf.ffruit = 0; } -void -store_plname_in_file(fd) -int fd; +staticfn void +savelevchn(NHFILE *nhfp) { - int plsiztmp = PL_NSIZ; + s_level *tmplev, *tmplev2; + int cnt = 0; - bufoff(fd); - /* bwrite() before bufon() uses plain write() */ - bwrite(fd, (genericptr_t) &plsiztmp, sizeof plsiztmp); - bwrite(fd, (genericptr_t) plname, plsiztmp); - bufon(fd); + for (tmplev = svs.sp_levchn; tmplev; tmplev = tmplev->next) + cnt++; + if (update_file(nhfp)) { + Sfo_int(nhfp, &cnt, "levchn-lev_count"); + } + for (tmplev = svs.sp_levchn; tmplev; tmplev = tmplev2) { + tmplev2 = tmplev->next; + if (update_file(nhfp)) { + Sfo_s_level(nhfp, tmplev, "levchn-s_level"); + } + if (release_data(nhfp)) + free((genericptr_t) tmplev); + } + if (release_data(nhfp)) + svs.sp_levchn = 0; +} + +/* write "name-role-race-gend-algn" into save file for menu-based restore; + the first dash is actually stored as '\0' instead of '-' */ +void +store_plname_in_file(NHFILE *nhfp) +{ + char hero[PL_NSIZ_PLUS]; /* [PL_NSIZ + 4*(1+3) + 1] */ + int plsiztmp = (int) sizeof hero; + + (void) memset((genericptr_t) hero, '\0', sizeof hero); + /* augment svp.plname[]; the gender and alignment values reflect those + in effect at time of saving rather than at start of game */ + Snprintf(hero, sizeof hero, "%s-%.3s-%.3s-%.3s-%.3s", + svp.plname, gu.urole.filecode, + gu.urace.filecode, genders[flags.female].filecode, + aligns[1 - u.ualign.type].filecode); + /* replace "-role-race..." with "\0role-race..." so that we can include + or exclude the role-&c suffix easily, without worrying about whether + plname contains any dashes; but don't rely on snprintf() for this */ + hero[strlen(svp.plname)] = '\0'; + /* insert playmode into final slot of hero[]; + 'D','X','-' are the same characters as are used for paniclog entries */ + assert(hero[PL_NSIZ_PLUS - 1 - 1] == '\0'); + hero[PL_NSIZ_PLUS - 1] = wizard ? 'D' : discover ? 'X' : '-'; + + if (nhfp->structlevel) + bufoff(nhfp->fd); /* bwrite() before bufon() uses plain write() */ + Sfo_int(nhfp, &plsiztmp, "plname-size"); + Sfo_char(nhfp, hero, "plname", plsiztmp); + if (nhfp->structlevel) + bufon(nhfp->fd); return; } -STATIC_OVL void -save_msghistory(fd, mode) -int fd, mode; +staticfn void +save_msghistory(NHFILE *nhfp) { char *msg; - int msgcount = 0, msglen; - int minusone = -1; + int msgcount = 0; + int minusone = -1, msglen; boolean init = TRUE; - if (perform_bwrite(mode)) { + if (update_file(nhfp)) { /* ask window port for each message in sequence */ while ((msg = getmsghistory(init)) != 0) { init = FALSE; - msglen = strlen(msg); + msglen = (int) Strlen(msg); if (msglen < 1) continue; /* sanity: truncate if necessary (shouldn't happen); no need to modify msg[] since terminator isn't written */ if (msglen > BUFSZ - 1) msglen = BUFSZ - 1; - bwrite(fd, (genericptr_t) &msglen, sizeof msglen); - bwrite(fd, (genericptr_t) msg, msglen); + Sfo_int(nhfp, &msglen, "msghistory-length"); + Sfo_char(nhfp, msg, "msghistory-msg", msglen); ++msgcount; } - bwrite(fd, (genericptr_t) &minusone, sizeof (int)); + Sfo_int(nhfp, &minusone, "msghistory-length"); } debugpline1("Stored %d messages into savefile.", msgcount); /* note: we don't attempt to handle release_data() here */ } -void -store_savefileinfo(fd) -int fd; -{ - /* sfcap (decl.c) describes the savefile feature capabilities - * that are supported by this port/platform build. - * - * sfsaveinfo (decl.c) describes the savefile info that actually - * gets written into the savefile, and is used to determine the - * save file being written. - * - * sfrestinfo (decl.c) describes the savefile info that is - * being used to read the information from an existing savefile. - */ - - bufoff(fd); - /* bwrite() before bufon() uses plain write() */ - bwrite(fd, (genericptr_t) &sfsaveinfo, (unsigned) sizeof sfsaveinfo); - bufon(fd); - return; -} - -void -set_savepref(suitename) -const char *suitename; -{ - if (!strcmpi(suitename, "externalcomp")) { - saveprocs.name = "externalcomp"; - saveprocs.save_bufon = def_bufon; - saveprocs.save_bufoff = def_bufoff; - saveprocs.save_bflush = def_bflush; - saveprocs.save_bwrite = def_bwrite; - saveprocs.save_bclose = def_bclose; - sfsaveinfo.sfi1 |= SFI1_EXTERNALCOMP; - sfsaveinfo.sfi1 &= ~SFI1_ZEROCOMP; - } - if (!strcmpi(suitename, "!rlecomp")) { - sfsaveinfo.sfi1 &= ~SFI1_RLECOMP; - } -#ifdef ZEROCOMP - if (!strcmpi(suitename, "zerocomp")) { - saveprocs.name = "zerocomp"; - saveprocs.save_bufon = zerocomp_bufon; - saveprocs.save_bufoff = zerocomp_bufoff; - saveprocs.save_bflush = zerocomp_bflush; - saveprocs.save_bwrite = zerocomp_bwrite; - saveprocs.save_bclose = zerocomp_bclose; - sfsaveinfo.sfi1 |= SFI1_ZEROCOMP; - sfsaveinfo.sfi1 &= ~SFI1_EXTERNALCOMP; - } -#endif -#ifdef RLECOMP - if (!strcmpi(suitename, "rlecomp")) { - sfsaveinfo.sfi1 |= SFI1_RLECOMP; - } -#endif -} - /* also called by prscore(); this probably belongs in dungeon.c... */ void -free_dungeons() +free_dungeons(void) { #ifdef FREE_ALL_MEMORY - savelevchn(0, FREE_SAVE); - save_dungeon(0, FALSE, TRUE); + NHFILE *tnhfp = get_freeing_nhfile(); + + savelevchn(tnhfp); + save_dungeon(tnhfp, FALSE, TRUE); + free_luathemes(all_themes); + close_nhfile(tnhfp); #endif return; } +extern int options_set_window_colors_flag; /* options.c */ + +/* free a lot of allocated memory which is ordinarily freed during save */ void -freedynamicdata() +freedynamicdata(void) { + NHFILE *tnhfp = get_freeing_nhfile(); + #if defined(UNIX) && defined(MAIL) free_maildata(); #endif - unload_qtlist(); free_menu_coloring(); free_invbuf(); /* let_to_name (invent.c) */ free_youbuf(); /* You_buf,&c (pline.c) */ msgtype_free(); + savedsym_free(); tmp_at(DISP_FREEMEM, 0); /* temporary display effects */ + purge_all_custom_entries(); #ifdef FREE_ALL_MEMORY -#define free_current_level() savelev(-1, -1, FREE_SAVE) -#define freeobjchn(X) (saveobjchn(0, X, FREE_SAVE), X = 0) -#define freemonchn(X) (savemonchn(0, X, FREE_SAVE), X = 0) -#define freefruitchn() savefruitchn(0, FREE_SAVE) -#define freenames() savenames(0, FREE_SAVE) -#define free_killers() save_killers(0, FREE_SAVE) -#define free_oracles() save_oracles(0, FREE_SAVE) -#define free_waterlevel() save_waterlevel(0, FREE_SAVE) -#define free_timers(R) save_timers(0, FREE_SAVE, R) -#define free_light_sources(R) save_light_sources(0, FREE_SAVE, R) +#define free_current_level() savelev(tnhfp, -1) +#define freeobjchn(X) (saveobjchn(tnhfp, &X), X = 0) +#define freemonchn(X) (savemonchn(tnhfp, X), X = 0) +#define freefruitchn() savefruitchn(tnhfp) +#define freenames() savenames(tnhfp) +#define free_killers() save_killers(tnhfp) +#define free_oracles() save_oracles(tnhfp) +#define free_waterlevel() save_waterlevel(tnhfp) +#define free_timers(R) save_timers(tnhfp, R) +#define free_light_sources(R) save_light_sources(tnhfp, R) #define free_animals() mon_animal_list(FALSE) +#define discard_gamelog() save_gamelog(tnhfp); /* move-specific data */ dmonsfree(); /* release dead monsters */ + /* dobjsfree(); // handled below */ + alloc_itermonarr(0U); /* a request of 0 releases existing allocation */ /* level-specific data */ + done_object_cleanup(); /* maybe force some OBJ_FREE items onto map */ free_current_level(); /* game-state data [ought to reorganize savegamestate() to handle this] */ free_killers(); free_timers(RANGE_GLOBAL); free_light_sources(RANGE_GLOBAL); - freeobjchn(invent); - freeobjchn(migrating_objs); - freemonchn(migrating_mons); - freemonchn(mydogs); /* ascension or dungeon escape */ + freeobjchn(gi.invent); + freeobjchn(gm.migrating_objs); + freemonchn(gm.migrating_mons); + freemonchn(gm.mydogs); /* ascension or dungeon escape */ /* freelevchn(); -- [folded into free_dungeons()] */ free_animals(); free_oracles(); @@ -1359,6 +1126,17 @@ freedynamicdata() freenames(); free_waterlevel(); free_dungeons(); + free_CapMons(); + free_rect(); + freeroleoptvals(); /* saveoptvals(&tnhfp) */ + cmdq_clear(CQ_CANNED); + cmdq_clear(CQ_REPEAT); + cmdbind_freeall(); + free_tutorial(); /* (only needed if quitting while in tutorial) */ + wish_history_flush(); + + /* per-turn data, but might get added to when freeing other stuff */ + dobjsfree(); /* really free deleted objects */ /* some pointers in iflags */ if (iflags.wc_font_map) @@ -1379,111 +1157,35 @@ freedynamicdata() /* miscellaneous */ /* free_pickinv_cache(); -- now done from really_done()... */ free_symsets(); +#ifdef USER_SOUNDS + release_sound_mappings(); +#endif +#ifdef DUMPLOG_CORE + dumplogfreemessages(); +#endif + discard_gamelog(); + release_runtime_info(); /* build-time options and version stuff */ + free_convert_filenames(); #endif /* FREE_ALL_MEMORY */ + free_nhuuid(); + if (VIA_WINDOWPORT()) status_finish(); -#ifdef DUMPLOG - dumplogfreemessages(); -#endif - /* last, because it frees data that might be used by panic() to provide - feedback to the user; conceivably other freeing might trigger panic */ - sysopt_release(); /* SYSCF strings */ - return; -} + if (options_set_window_colors_flag) + options_free_window_colors(); -#ifdef MFLOPPY -boolean -swapin_file(lev) -int lev; -{ - char to[PATHLEN], from[PATHLEN]; - - Sprintf(from, "%s%s", permbones, alllevels); - Sprintf(to, "%s%s", levels, alllevels); - set_levelfile_name(from, lev); - set_levelfile_name(to, lev); - if (iflags.checkspace) { - while (level_info[lev].size > freediskspace(to)) - if (!swapout_oldest()) - return FALSE; - } - if (wizard) { - pline("Swapping in `%s'.", from); - wait_synch(); - } - copyfile(from, to); - (void) unlink(from); - level_info[lev].where = ACTIVE; - return TRUE; -} + if (glyphid_cache_status()) + free_glyphid_cache(); -STATIC_OVL boolean -swapout_oldest() -{ - char to[PATHLEN], from[PATHLEN]; - int i, oldest; - long oldtime; - - if (!ramdisk) - return FALSE; - for (i = 1, oldtime = 0, oldest = 0; i <= maxledgerno(); i++) - if (level_info[i].where == ACTIVE - && (!oldtime || level_info[i].time < oldtime)) { - oldest = i; - oldtime = level_info[i].time; - } - if (!oldest) - return FALSE; - Sprintf(from, "%s%s", levels, alllevels); - Sprintf(to, "%s%s", permbones, alllevels); - set_levelfile_name(from, oldest); - set_levelfile_name(to, oldest); - if (wizard) { - pline("Swapping out `%s'.", from); - wait_synch(); + if (tnhfp) { + close_nhfile(tnhfp); + tnhfp = 0; } - copyfile(from, to); - (void) unlink(from); - level_info[oldest].where = SWAPPED; - return TRUE; -} -STATIC_OVL void -copyfile(from, to) -char *from, *to; -{ -#ifdef TOS - if (_copyfile(from, to)) - panic("Can't copy %s to %s", from, to); -#else - char buf[BUFSIZ]; /* this is system interaction, therefore - * BUFSIZ instead of NetHack's BUFSZ */ - int nfrom, nto, fdfrom, fdto; - - if ((fdfrom = open(from, O_RDONLY | O_BINARY, FCMASK)) < 0) - panic("Can't copy from %s !?", from); - if ((fdto = open(to, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, FCMASK)) < 0) - panic("Can't copy to %s", to); - do { - nfrom = read(fdfrom, buf, BUFSIZ); - nto = write(fdto, buf, nfrom); - if (nto != nfrom) - panic("Copyfile failed!"); - } while (nfrom == BUFSIZ); - (void) nhclose(fdfrom); - (void) nhclose(fdto); -#endif /* TOS */ -} - -/* see comment in bones.c */ -void -co_false() -{ - count_only = FALSE; - return; + /* last, because it frees data that might be used by panic() to provide + feedback to the user; conceivably other freeing might trigger panic */ + sysopt_release(); /* SYSCF strings */ } -#endif /* MFLOPPY */ - /*save.c*/ diff --git a/src/selvar.c b/src/selvar.c new file mode 100644 index 000000000..e1dad31af --- /dev/null +++ b/src/selvar.c @@ -0,0 +1,812 @@ +/* NetHack 5.0 selvar.c $NHDT-Date: 1769840272 2026/01/30 22:17:52 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.4 $ */ +/* Copyright (c) 2024 by Pasi Kallinen */ +/* NetHack may be freely redistributed. See license for details. */ + +#include "hack.h" +#include "selvar.h" +#include "sp_lev.h" + +staticfn boolean sel_flood_havepoint(coordxy, coordxy, coordxy *, coordxy *, + int); +staticfn long line_dist_coord(long, long, long, long, long, long); + +/* selection */ +struct selectionvar * +selection_new(void) +{ + struct selectionvar *tmps = (struct selectionvar *) alloc(sizeof *tmps); + + tmps->wid = COLNO; + tmps->hei = ROWNO; + tmps->bounds_dirty = FALSE; + tmps->bounds.lx = COLNO; + tmps->bounds.ly = ROWNO; + tmps->bounds.hx = tmps->bounds.hy = 0; + tmps->map = (char *) alloc((COLNO * ROWNO) + 1); + (void) memset(tmps->map, 1, (COLNO * ROWNO)); + tmps->map[(COLNO * ROWNO)] = '\0'; + + return tmps; +} + +void +selection_free(struct selectionvar *sel, boolean freesel) +{ + if (sel) { + if (sel->map) + free(sel->map); + sel->map = NULL; + if (freesel) + free((genericptr_t) sel); + else + (void) memset((genericptr_t) sel, 0, sizeof *sel); + } +} + +/* clear selection, setting all locations to value val */ +void +selection_clear(struct selectionvar *sel, int val) +{ + (void) memset(sel->map, 1 + val, (COLNO * ROWNO)); + if (val) { + sel->bounds.lx = 0; + sel->bounds.ly = 0; + sel->bounds.hx = COLNO - 1; + sel->bounds.hy = ROWNO - 1; + } else { + sel->bounds.lx = COLNO; + sel->bounds.ly = ROWNO; + sel->bounds.hx = sel->bounds.hy = 0; + } + sel->bounds_dirty = FALSE; +} + +struct selectionvar * +selection_clone(struct selectionvar *sel) +{ + struct selectionvar *tmps = (struct selectionvar *) alloc(sizeof *tmps); + + *tmps = *sel; + tmps->map = dupstr(sel->map); + + return tmps; +} + +/* get boundary rect of selection sel into b */ +void +selection_getbounds(struct selectionvar *sel, NhRect *b) +{ + if (!sel || !b) + return; + + selection_recalc_bounds(sel); + + if (sel->bounds.lx >= sel->wid) { + b->lx = 0; + b->ly = 0; + b->hx = COLNO - 1; + b->hy = ROWNO - 1; + } else { + b->lx = sel->bounds.lx; + b->ly = sel->bounds.ly; + b->hx = sel->bounds.hx; + b->hy = sel->bounds.hy; + } +} + +/* recalc the boundary of selection, if necessary */ +void +selection_recalc_bounds(struct selectionvar *sel) +{ + coordxy x, y; + NhRect r; + + if (!sel->bounds_dirty) + return; + + sel->bounds.lx = COLNO; + sel->bounds.ly = ROWNO; + sel->bounds.hx = sel->bounds.hy = 0; + + r.lx = r.ly = r.hx = r.hy = -1; + + /* left */ + for (x = 0; x < sel->wid; x++) { + for (y = 0; y < sel->hei; y++) { + if (selection_getpoint(x, y, sel)) { + r.lx = x; + break; + } + } + if (r.lx > -1) + break; + } + + if (r.lx > -1) { + /* right */ + for (x = sel->wid-1; x >= r.lx; x--) { + for (y = 0; y < sel->hei; y++) { + if (selection_getpoint(x, y, sel)) { + r.hx = x; + break; + } + } + if (r.hx > -1) + break; + } + + /* top */ + for (y = 0; y < sel->hei; y++) { + for (x = r.lx; x <= r.hx; x++) { + if (selection_getpoint(x, y, sel)) { + r.ly = y; + break; + } + } + if (r.ly > -1) + break; + } + + /* bottom */ + for (y = sel->hei-1; y >= r.ly; y--) { + for (x = r.lx; x <= r.hx; x++) { + if (selection_getpoint(x, y, sel)) { + r.hy = y; + break; + } + } + if (r.hy > -1) + break; + } + sel->bounds = r; + } + + sel->bounds_dirty = FALSE; +} + +coordxy +selection_getpoint( + coordxy x, coordxy y, + struct selectionvar *sel) +{ + if (!sel || !sel->map) + return 0; + if (x < 0 || y < 0 || x >= sel->wid || y >= sel->hei) + return 0; + + return (sel->map[sel->wid * y + x] - 1); +} + +void +selection_setpoint( + coordxy x, coordxy y, + struct selectionvar *sel, + int c) +{ + if (!sel || !sel->map) + return; + if (x < 0 || y < 0 || x >= sel->wid || y >= sel->hei) + return; + + if (c && !sel->bounds_dirty) { + if (sel->bounds.lx > x) + sel->bounds.lx = x; + if (sel->bounds.ly > y) + sel->bounds.ly = y; + if (sel->bounds.hx < x) + sel->bounds.hx = x; + if (sel->bounds.hy < y) + sel->bounds.hy = y; + + /* only set bounds_dirty if changing a point from 1 to 0; if changing + a point from 0 to 0, nothing has really changed with the bounds */ + } else if (sel->map[sel->wid * y + x] != 0) { + sel->bounds_dirty = TRUE; + } + + sel->map[sel->wid * y + x] = (char) (c + 1); +} + +struct selectionvar * +selection_not(struct selectionvar *s) +{ + int x, y; + NhRect tmprect = cg.zeroNhRect; + + for (x = 0; x < s->wid; x++) + for (y = 0; y < s->hei; y++) + selection_setpoint(x, y, s, selection_getpoint(x, y, s) ? 0 : 1); + selection_getbounds(s, &tmprect); + return s; +} + +struct selectionvar * +selection_filter_percent( + struct selectionvar *ov, + int percent) +{ + int x, y; + struct selectionvar *ret; + NhRect rect = cg.zeroNhRect; + + if (!ov) + return NULL; + + ret = selection_new(); + + selection_getbounds(ov, &rect); + + for (x = rect.lx; x <= rect.hx; x++) + for (y = rect.ly; y <= rect.hy; y++) + if (selection_getpoint(x, y, ov) && (rn2(100) < percent)) + selection_setpoint(x, y, ret, 1); + + return ret; +} + +struct selectionvar * +selection_filter_mapchar(struct selectionvar *ov, xint16 typ, int lit) +{ + int x, y; + struct selectionvar *ret; + NhRect rect = cg.zeroNhRect; + + if (!ov) + return NULL; + + ret = selection_new(); + + selection_getbounds(ov, &rect); + + for (x = rect.lx; x <= rect.hx; x++) + for (y = rect.ly; y <= rect.hy; y++) + if (selection_getpoint(x, y, ov) + && match_maptyps(typ, levl[x][y].typ)) { + switch (lit) { + default: + case -2: + selection_setpoint(x, y, ret, 1); + break; + case -1: + selection_setpoint(x, y, ret, rn2(2)); + break; + case 0: + case 1: + if (levl[x][y].lit == (unsigned int) lit) + selection_setpoint(x, y, ret, 1); + break; + } + } + return ret; +} + +int +selection_rndcoord( + struct selectionvar *ov, + coordxy *x, coordxy *y, + boolean removeit) +{ + int idx = 0; + int c; + int dx, dy; + NhRect rect = cg.zeroNhRect; + + selection_getbounds(ov, &rect); + + for (dx = rect.lx; dx <= rect.hx; dx++) + for (dy = rect.ly; dy <= rect.hy; dy++) + if (selection_getpoint(dx, dy, ov)) + idx++; + + if (idx) { + c = rn2(idx); + for (dx = rect.lx; dx <= rect.hx; dx++) + for (dy = rect.ly; dy <= rect.hy; dy++) + if (selection_getpoint(dx, dy, ov)) { + if (!c) { + *x = dx; + *y = dy; + if (removeit) + selection_setpoint(dx, dy, ov, 0); + return 1; + } + c--; + } + } + *x = *y = -1; + return 0; +} + +void +selection_do_grow(struct selectionvar *ov, int dir) +{ + coordxy x, y; + struct selectionvar *tmp; + NhRect rect = cg.zeroNhRect; + + if (!ov) + return; + + tmp = selection_new(); + + if (dir == W_RANDOM) + dir = random_wdir(); + + selection_getbounds(ov, &rect); + + for (x = max(0, rect.lx-1); x <= min(COLNO-1, rect.hx+1); x++) + for (y = max(0, rect.ly-1); y <= min(ROWNO-1, rect.hy+1); y++) { + /* note: dir is a mask of multiple directions, but the only + way to specify diagonals is by including the two adjacent + orthogonal directions, which effectively specifies three- + way growth [WEST|NORTH => WEST plus WEST|NORTH plus NORTH] */ + if (((dir & W_WEST) && selection_getpoint(x + 1, y, ov)) + || (((dir & (W_WEST | W_NORTH)) == (W_WEST | W_NORTH)) + && selection_getpoint(x + 1, y + 1, ov)) + || ((dir & W_NORTH) && selection_getpoint(x, y + 1, ov)) + || (((dir & (W_NORTH | W_EAST)) == (W_NORTH | W_EAST)) + && selection_getpoint(x - 1, y + 1, ov)) + || ((dir & W_EAST) && selection_getpoint(x - 1, y, ov)) + || (((dir & (W_EAST | W_SOUTH)) == (W_EAST | W_SOUTH)) + && selection_getpoint(x - 1, y - 1, ov)) + || ((dir & W_SOUTH) && selection_getpoint(x, y - 1, ov)) + || (((dir & (W_SOUTH | W_WEST)) == (W_SOUTH | W_WEST)) + && selection_getpoint(x + 1, y - 1, ov))) { + selection_setpoint(x, y, tmp, 1); + } + } + + selection_getbounds(tmp, &rect); + + for (x = rect.lx; x <= rect.hx; x++) + for (y = rect.ly; y <= rect.hy; y++) + if (selection_getpoint(x, y, tmp)) + selection_setpoint(x, y, ov, 1); + + selection_free(tmp, TRUE); +} + +staticfn int (*selection_flood_check_func)(coordxy, coordxy); + +void +set_selection_floodfillchk(int (*f)(coordxy, coordxy)) +{ + selection_flood_check_func = f; +} + +/* check whethere is already in xs[],ys[] */ +staticfn boolean +sel_flood_havepoint( + coordxy x, coordxy y, + coordxy xs[], coordxy ys[], + int n) +{ + coordxy xx = x, yy = y; + + while (n > 0) { + --n; + if (xs[n] == xx && ys[n] == yy) + return TRUE; + } + return FALSE; +} + +void +selection_floodfill( + struct selectionvar *ov, + coordxy x, coordxy y, + boolean diagonals) +{ + struct selectionvar *tmp = selection_new(); +#define SEL_FLOOD_STACK (COLNO * ROWNO) +#define SEL_FLOOD(nx, ny) \ + do { \ + if (idx < SEL_FLOOD_STACK) { \ + dx[idx] = (nx); \ + dy[idx] = (ny); \ + idx++; \ + } else \ + panic(floodfill_stack_overrun); \ + } while (0) +#define SEL_FLOOD_CHKDIR(mx, my, sel) \ + do { \ + if (isok((mx), (my)) \ + && (*selection_flood_check_func)((mx), (my)) \ + && !selection_getpoint((mx), (my), (sel)) \ + && !sel_flood_havepoint((mx), (my), dx, dy, idx)) \ + SEL_FLOOD((mx), (my)); \ + } while (0) + static const char floodfill_stack_overrun[] = "floodfill stack overrun"; + int idx = 0; + coordxy dx[SEL_FLOOD_STACK]; + coordxy dy[SEL_FLOOD_STACK]; + + if (selection_flood_check_func == (int (*)(coordxy, coordxy)) 0) { + selection_free(tmp, TRUE); + return; + } + SEL_FLOOD(x, y); + do { + idx--; + x = dx[idx]; + y = dy[idx]; + if (isok(x, y)) { + selection_setpoint(x, y, ov, 1); + selection_setpoint(x, y, tmp, 1); + } + SEL_FLOOD_CHKDIR((x + 1), y, tmp); + SEL_FLOOD_CHKDIR((x - 1), y, tmp); + SEL_FLOOD_CHKDIR(x, (y + 1), tmp); + SEL_FLOOD_CHKDIR(x, (y - 1), tmp); + if (diagonals) { + SEL_FLOOD_CHKDIR((x + 1), (y + 1), tmp); + SEL_FLOOD_CHKDIR((x - 1), (y - 1), tmp); + SEL_FLOOD_CHKDIR((x - 1), (y + 1), tmp); + SEL_FLOOD_CHKDIR((x + 1), (y - 1), tmp); + } + } while (idx > 0); +#undef SEL_FLOOD +#undef SEL_FLOOD_STACK +#undef SEL_FLOOD_CHKDIR + selection_free(tmp, TRUE); +} + +/* McIlroy's Ellipse Algorithm */ +void +selection_do_ellipse( + struct selectionvar *ov, + int xc, int yc, + int a, int b, + int filled) +{ /* e(x,y) = b^2*x^2 + a^2*y^2 - a^2*b^2 */ + int x = 0, y = b; + long a2 = (long) a * a, b2 = (long) b * b; + long crit1 = -(a2 / 4 + a % 2 + b2); + long crit2 = -(b2 / 4 + b % 2 + a2); + long crit3 = -(b2 / 4 + b % 2); + long t = -a2 * y; /* e(x+1/2,y-1/2) - (a^2+b^2)/4 */ + long dxt = 2 * b2 * x, dyt = -2 * a2 * y; + long d2xt = 2 * b2, d2yt = 2 * a2; + long width = 1; + long i; + + if (!ov) + return; + + filled = !filled; + + if (!filled) { + while (y >= 0 && x <= a) { + selection_setpoint(xc + x, yc + y, ov, 1); + if (x != 0 || y != 0) + selection_setpoint(xc - x, yc - y, ov, 1); + if (x != 0 && y != 0) { + selection_setpoint(xc + x, yc - y, ov, 1); + selection_setpoint(xc - x, yc + y, ov, 1); + } + if (t + b2 * x <= crit1 /* e(x+1,y-1/2) <= 0 */ + || t + a2 * y <= crit3) { /* e(x+1/2,y) <= 0 */ + x++; + dxt += d2xt; + t += dxt; + } else if (t - a2 * y > crit2) { /* e(x+1/2,y-1) > 0 */ + y--; + dyt += d2yt; + t += dyt; + } else { + x++; + dxt += d2xt; + t += dxt; + y--; + dyt += d2yt; + t += dyt; + } + } + } else { + while (y >= 0 && x <= a) { + if (t + b2 * x <= crit1 /* e(x+1,y-1/2) <= 0 */ + || t + a2 * y <= crit3) { /* e(x+1/2,y) <= 0 */ + x++; + dxt += d2xt; + t += dxt; + width += 2; + } else if (t - a2 * y > crit2) { /* e(x+1/2,y-1) > 0 */ + for (i = 0; i < width; i++) + selection_setpoint(xc - x + i, yc - y, ov, 1); + if (y != 0) + for (i = 0; i < width; i++) + selection_setpoint(xc - x + i, yc + y, ov, 1); + y--; + dyt += d2yt; + t += dyt; + } else { + for (i = 0; i < width; i++) + selection_setpoint(xc - x + i, yc - y, ov, 1); + if (y != 0) + for (i = 0; i < width; i++) + selection_setpoint(xc - x + i, yc + y, ov, 1); + x++; + dxt += d2xt; + t += dxt; + y--; + dyt += d2yt; + t += dyt; + width += 2; + } + } + } +} + +/* square of distance from line segment (x1,y1, x2,y2) to point (x3,y3) */ +staticfn long +line_dist_coord(long x1, long y1, long x2, long y2, long x3, long y3) +{ + long px = x2 - x1; + long py = y2 - y1; + long s = px * px + py * py; + long x, y, dx, dy, distsq = 0; + float lu = 0; + + if (x1 == x2 && y1 == y2) + return dist2(x1, y1, x3, y3); + + lu = ((x3 - x1) * px + (y3 - y1) * py) / (float) s; + if (lu > 1) + lu = 1; + else if (lu < 0) + lu = 0; + + x = x1 + lu * px; + y = y1 + lu * py; + dx = x - x3; + dy = y - y3; + distsq = dx * dx + dy * dy; + + return distsq; +} + +/* guts of l_selection_gradient */ +void +selection_do_gradient( + struct selectionvar *ov, + long x, long y, + long x2,long y2, + long gtyp, + long mind, long maxd) +{ + long dx, dy, dofs; + + if (mind > maxd) { + long tmp = mind; + mind = maxd; + maxd = tmp; + } + + dofs = maxd * maxd - mind * mind; + if (dofs < 1) + dofs = 1; + + switch (gtyp) { + default: + impossible("Unrecognized gradient type! Defaulting to radial..."); + FALLTHROUGH; + /* FALLTHRU */ + case SEL_GRADIENT_RADIAL: { + for (dx = 0; dx < COLNO; dx++) + for (dy = 0; dy < ROWNO; dy++) { + long d0 = line_dist_coord(x, y, x2, y2, dx, dy); + + if (d0 <= mind * mind + || (d0 <= maxd * maxd && d0 - mind * mind < rn2(dofs))) + selection_setpoint(dx, dy, ov, 1); + } + break; + } + case SEL_GRADIENT_SQUARE: { + for (dx = 0; dx < COLNO; dx++) + for (dy = 0; dy < ROWNO; dy++) { + long d1 = line_dist_coord(x, y, x2, y2, x, dy); + long d2 = line_dist_coord(x, y, x2, y2, dx, y); + long d3 = line_dist_coord(x, y, x2, y2, x2, dy); + long d4 = line_dist_coord(x, y, x2, y2, dx, y2); + long d5 = line_dist_coord(x, y, x2, y2, dx, dy); + long d0 = min(d5, min(max(d1, d2), max(d3, d4))); + + if (d0 <= mind * mind + || (d0 <= maxd * maxd && d0 - mind * mind < rn2(dofs))) + selection_setpoint(dx, dy, ov, 1); + } + break; + } /*case*/ + } /*switch*/ +} + +/* bresenham line algo */ +void +selection_do_line( + coordxy x1, coordxy y1, + coordxy x2, coordxy y2, + struct selectionvar *ov) +{ + int d0, dx, dy, ai, bi, xi, yi; + + if (x1 < x2) { + xi = 1; + dx = x2 - x1; + } else { + xi = -1; + dx = x1 - x2; + } + if (y1 < y2) { + yi = 1; + dy = y2 - y1; + } else { + yi = -1; + dy = y1 - y2; + } + + selection_setpoint(x1, y1, ov, 1); + + if (!dx && !dy) { + /* single point - already all done */ + ; + } else if (dx > dy) { + ai = (dy - dx) * 2; + bi = dy * 2; + d0 = bi - dx; + do { + if (d0 >= 0) { + y1 += yi; + d0 += ai; + } else + d0 += bi; + x1 += xi; + selection_setpoint(x1, y1, ov, 1); + } while (x1 != x2); + } else { + ai = (dx - dy) * 2; + bi = dx * 2; + d0 = bi - dy; + do { + if (d0 >= 0) { + x1 += xi; + d0 += ai; + } else + d0 += bi; + y1 += yi; + selection_setpoint(x1, y1, ov, 1); + } while (y1 != y2); + } +} + +void +selection_do_randline( + coordxy x1, coordxy y1, + coordxy x2, coordxy y2, + schar rough, + schar rec, + struct selectionvar *ov) +{ + int mx, my; + int dx, dy; + + if (rec < 1 || (x2 == x1 && y2 == y1)) + return; + + if (rough > max(abs(x2 - x1), abs(y2 - y1))) + rough = max(abs(x2 - x1), abs(y2 - y1)); + + if (rough < 2) { + mx = ((x1 + x2) / 2); + my = ((y1 + y2) / 2); + } else { + do { + dx = rn2(rough) - (rough / 2); + dy = rn2(rough) - (rough / 2); + mx = ((x1 + x2) / 2) + dx; + my = ((y1 + y2) / 2) + dy; + } while ((mx > COLNO - 1 || mx < 0 || my < 0 || my > ROWNO - 1)); + } + + if (!selection_getpoint(mx, my, ov)) { + selection_setpoint(mx, my, ov, 1); + } + + rough = (rough * 2) / 3; + + rec--; + + selection_do_randline(x1, y1, mx, my, rough, rec, ov); + selection_do_randline(mx, my, x2, y2, rough, rec, ov); + + selection_setpoint(x2, y2, ov, 1); +} + +void +selection_iterate( + struct selectionvar *ov, + select_iter_func func, + genericptr_t arg) +{ + coordxy x, y; + NhRect rect = cg.zeroNhRect; + + if (!ov) + return; + + selection_getbounds(ov, &rect); + + for (x = rect.lx; x <= rect.hx; x++) + for (y = rect.ly; y <= rect.hy; y++) + if (isok(x,y) && selection_getpoint(x, y, ov)) + (*func)(x, y, arg); +} + +/* selection is not rectangular, or has holes in it */ +boolean +selection_is_irregular(struct selectionvar *sel) +{ + coordxy x, y; + NhRect rect = cg.zeroNhRect; + + selection_getbounds(sel, &rect); + + for (x = rect.lx; x <= rect.hx; x++) + for (y = rect.ly; y <= rect.hy; y++) + if (isok(x,y) && !selection_getpoint(x, y, sel)) + return TRUE; + + return FALSE; +} + +/* return a description of the selection size */ +char * +selection_size_description(struct selectionvar *sel, char *buf) +{ + NhRect rect = cg.zeroNhRect; + coordxy dx, dy; + + selection_getbounds(sel, &rect); + dx = rect.hx - rect.lx + 1; + dy = rect.hy - rect.ly + 1; + Sprintf(buf, "%s %i by %i", + selection_is_irregular(sel) ? "irregularly shaped" + : (dx == dy) ? "square" + : "rectangular", + dx, dy); + return buf; +} + +struct selectionvar * +selection_from_mkroom(struct mkroom *croom) +{ + struct selectionvar *sel = selection_new(); + coordxy x, y; + unsigned rmno; + + if (!croom && gc.coder && gc.coder->croom) + croom = gc.coder->croom; + if (!croom) + return sel; + + rmno = (unsigned)((croom - svr.rooms) + ROOMOFFSET); + for (y = croom->ly; y <= croom->hy; y++) + for (x = croom->lx; x <= croom->hx; x++) + if (isok(x, y) && !levl[x][y].edge + && levl[x][y].roomno == rmno) + selection_setpoint(x, y, sel, 1); + return sel; +} + +void +selection_force_newsyms(struct selectionvar *sel) +{ + coordxy x, y; + + for (x = 1; x < sel->wid; x++) + for (y = 0; y < sel->hei; y++) + if (selection_getpoint(x, y, sel)) + newsym_force(x, y); +} + +/*selvar.c*/ diff --git a/src/sfbase.c b/src/sfbase.c new file mode 100644 index 000000000..00c165c68 --- /dev/null +++ b/src/sfbase.c @@ -0,0 +1,1117 @@ +/* NetHack 5.0 sfbase.c.template $NHDT-Date$ $NHDT-Branch$:$NHDT-Revision$ */ +/* Copyright (c) Michael Allison, 2025. */ +/* NetHack may be freely redistributed. See license for details. */ + +#include "hack.h" +#include "sfprocs.h" + +#ifdef SFCTOOL +//#include "sfproto.h" +#endif + +/* #define DO_DEBUG */ + +//#define TURN_OFF_LOGGING 0x20 +#define TURN_OFF_LOGGING (UNCONVERTING << 1) + +struct sf_structlevel_procs sfoprocs[NUM_SAVEFORMATS], sfiprocs[NUM_SAVEFORMATS], + zerosfoprocs = {0}, zerosfiprocs = {0}; +struct sf_fieldlevel_procs sfoflprocs[NUM_SAVEFORMATS], sfiflprocs[NUM_SAVEFORMATS], + zerosfoflprocs = {0}, zerosfiflprocs = {0}; + +char *sfvalue_aligntyp(aligntyp *a); +char *sfvalue_any(anything *a); +char *sfvalue_genericptr(genericptr a); +char *sfvalue_int16(int16 *a); +char *sfvalue_int32(int32 *a); +char *sfvalue_int64(int64 *a); +char *sfvalue_uchar(uchar *a); +char *sfvalue_uint16(uint16 *a); +char *sfvalue_uint32(uint32 *a); +char *sfvalue_uint64(uint64 *a); +char *sfvalue_size_t(size_t *a); +char *sfvalue_time_t(time_t *a); +char *sfvalue_short(short *a); +char *sfvalue_ushort(ushort *a); +char *sfvalue_int(int *a); +char *sfvalue_unsigned(unsigned *a); +char *sfvalue_long(long *a); +char *sfvalue_ulong(ulong *a); +char *sfvalue_xint8(xint8 *a); +char *sfvalue_xint16(xint16 *a); +char *sfvalue_char(char *a, int n); +char *sfvalue_boolean(boolean *a); +char *sfvalue_schar(schar *a); +char *sfvalue_bitfield(uint8 *a); +char *complex_dump(uchar *a); +char *bitfield_dump(uint8 *a); + +void sf_log(NHFILE *, const char *, size_t, int, char *); + +#if NH_C < 202300L +#define Sfvalue_aligntyp(a) sfvalue_aligntyp(a) +#define Sfvalue_any(a) sfvalue_any(a) +#define Sfvalue_genericptr(a) sfvalue_genericptr(a) +#define Sfvalue_coordxy(a) sfvalue_int16(a) +#define Sfvalue_int16(a) sfvalue_int16(a) +#define Sfvalue_int32(a) sfvalue_int32(a) +#define Sfvalue_int64(a) sfvalue_int64(a) +#define Sfvalue_uchar(a) sfvalue_uchar(a) +#define Sfvalue_uint16(a) sfvalue_uint16(a) +#define Sfvalue_uint32(a) sfvalue_uint32(a) +#define Sfvalue_uint64(a) sfvalue_uint64(a) +#define Sfvalue_size_t(a) sfvalue_size_t(a) +#define Sfvalue_time_t(a) sfvalue_time_t(a) +#define Sfvalue_short(a) sfvalue_short(a) +#define Sfvalue_ushort(a) sfvalue_ushort(a) +#define Sfvalue_int(a) sfvalue_int(a) +#define Sfvalue_unsigned(a) sfvalue_unsigned(a) +#define Sfvalue_xint8(a) sfvalue_xint8(a) +#define Sfvalue_xint16(a) sfvalue_xint16(a) + +#else + +#define sfvalue(x) \ + _Generic( (x), \ + anything *: sfvalue_any, \ + genericptr_t *: sfvalue_genericptr, \ + int16_t *: sfvalue_int16, \ + int32_t *: sfvalue_int32, \ + int64_t *: sfvalue_int64, \ + uchar *: sfvalue_uchar, \ + uint16_t *: sfvalue_uint16, \ + uint32_t *: sfvalue_uint32, \ + uint64_t *: sfvalue_uint64, \ + xint8 *: sfvalue_xint8 \ + )(x) + +#define Sfvalue_any(a) sfvalue(a) +#define Sfvalue_aligntyp(a) sfvalue(a) +#define Sfvalue_genericptr(a) sfvalue(a) +#define Sfvalue_coordxy(a) sfvalue(a) +#define Sfvalue_int16(a) sfvalue(a) +#define Sfvalue_int32(a) sfvalue(a) +#define Sfvalue_int64(a) sfvalue(a) +#define Sfvalue_uchar(a) sfvalue(a) +#define Sfvalue_unsigned(a) sfvalue(a) +#define Sfvalue_uchar(a) sfvalue(a) +#define Sfvalue_uint16(a) sfvalue(a) +#define Sfvalue_uint32(a) sfvalue(a) +#define Sfvalue_uint64(a) sfvalue(a) +#define Sfvalue_short(a) sfvalue(a) +#define Sfvalue_ushort(a) sfvalue(a) +#define Sfvalue_int(a) sfvalue(a) +#define Sfvalue_unsigned(a) sfvalue(a) +#define Sfvalue_xint8(a) sfvalue(a) +#define Sfvalue_xint16(a) sfvalue(a) +#endif + +/* not in _Generic */ +#define Sfvalue_long(a) sfvalue_long(a) +#define Sfvalue_ulong(a) sfvalue_ulong(a) +#define Sfvalue_char(a, d) sfvalue_char(a, d) +#define Sfvalue_boolean(a) sfvalue_boolean(a) +#define Sfvalue_schar(a) sfvalue_schar(a) +#define Sfvalue_bitfield(a) sfvalue_bitfield(a) +#define Sfvalue_time_t(a) sfvalue_time_t(a) +#define Sfvalue_size_t(a) sfvalue_size_t(a) + +#define SF_A(dtyp) \ +void sfo_##dtyp(NHFILE *nhfp, dtyp *d_##dtyp, const char *myname) \ +{ \ + if (nhfp->fplog) \ + sf_log(nhfp, myname, sizeof *d_##dtyp, 1, Sfvalue_##dtyp(d_##dtyp)); \ + if (nhfp->structlevel) { \ + (*sfoprocs[nhfp->fnidx].fn.sf_##dtyp)(nhfp, d_##dtyp, myname); \ + } else { \ + FILE *save_fplog = nhfp->fplog; \ + \ + nhfp->fplog = 0; \ + (*sfoflprocs[nhfp->fnidx].fn_x.sf_##dtyp)(nhfp, d_##dtyp, myname); \ + nhfp->fplog = save_fplog; \ + } \ +} \ + \ +void sfi_##dtyp(NHFILE *nhfp, dtyp *d_##dtyp, const char *myname) \ +{ \ + if (nhfp->structlevel) { \ + (*sfiprocs[nhfp->fnidx].fn.sf_##dtyp)(nhfp, d_##dtyp, myname); \ + } else { \ + int save_mode = nhfp->mode; \ + \ + nhfp->mode &= ~(CONVERTING | UNCONVERTING); \ + nhfp->mode |= TURN_OFF_LOGGING; \ + (*sfiflprocs[nhfp->fnidx].fn_x.sf_##dtyp)(nhfp, d_##dtyp, myname); \ + nhfp->mode = save_mode; \ + } \ + if (!nhfp->eof) { \ + if ((((nhfp->mode & CONVERTING) != 0) \ + || ((nhfp->mode & UNCONVERTING) != 0)) && nhfp->nhfpconvert) { \ + sfo_##dtyp(nhfp->nhfpconvert, d_##dtyp, myname); \ + } \ + if (nhfp->fplog) \ + sf_log(nhfp, myname, sizeof *d_##dtyp, 1, \ + Sfvalue_##dtyp(d_##dtyp)); \ + } \ +} + +#define SF_C(keyw, dtyp) \ +void sfo_##dtyp(NHFILE *nhfp, keyw dtyp *d_##dtyp, const char *myname) \ +{ \ + if (nhfp->fplog) \ + sf_log(nhfp, myname, sizeof *d_##dtyp, 1, \ + complex_dump((uchar *) d_##dtyp)); \ + if (nhfp->structlevel) { \ + (*sfoprocs[nhfp->fnidx].fn.sf_##dtyp)(nhfp, d_##dtyp, myname); \ + } else { \ + FILE *save_fplog = nhfp->fplog; \ + \ + nhfp->fplog = 0; \ + (*sfoflprocs[nhfp->fnidx].fn_x.sf_##dtyp)(nhfp, d_##dtyp, myname); \ + nhfp->fplog = save_fplog; \ + } \ +} \ + \ +void sfi_##dtyp(NHFILE *nhfp, keyw dtyp *d_##dtyp, const char *myname) \ +{ \ + if (nhfp->structlevel) { \ + (*sfiprocs[nhfp->fnidx].fn.sf_##dtyp)(nhfp, d_##dtyp, myname); \ + } else { \ + int save_mode = nhfp->mode; \ + \ + nhfp->mode &= ~(CONVERTING | UNCONVERTING); \ + nhfp->mode |= TURN_OFF_LOGGING; \ + (*sfiflprocs[nhfp->fnidx].fn_x.sf_##dtyp)(nhfp, d_##dtyp, myname); \ + nhfp->mode = save_mode; \ + } \ + if (!nhfp->eof) { \ + if ((((nhfp->mode & CONVERTING) != 0) \ + || ((nhfp->mode & UNCONVERTING) != 0)) \ + && nhfp->nhfpconvert) { \ + sfo_##dtyp(nhfp->nhfpconvert, d_##dtyp, myname); \ + } \ + if (nhfp->fplog) \ + sf_log(nhfp, myname, sizeof *d_##dtyp, 1, \ + complex_dump((uchar *) d_##dtyp)); \ + } \ +} + +#define SF_X(xxx, dtyp) \ +void sfo_##dtyp(NHFILE *nhfp, xxx *d_##dtyp, const char *myname, int bfsz) \ +{ \ + if (nhfp->fplog) \ + sf_log(nhfp, myname, sizeof *d_##dtyp, 1, \ + Sfvalue_##dtyp(d_##dtyp)); \ + if (nhfp->structlevel) { \ + (*sfoprocs[nhfp->fnidx].fn.sf_##dtyp)(nhfp, d_##dtyp, myname, bfsz); \ + } else { \ + FILE *save_fplog = nhfp->fplog; \ + \ + nhfp->fplog = 0; \ + (*sfoflprocs[nhfp->fnidx].fn_x.sf_##dtyp)(nhfp, d_##dtyp, \ + myname, bfsz); \ + nhfp->fplog = save_fplog; \ + } \ + if (nhfp->fplog && !nhfp->eof) \ + sf_log(nhfp, myname, sizeof *d_##dtyp, 1, Sfvalue_##dtyp(d_##dtyp)); \ +} \ + \ +void sfi_##dtyp(NHFILE *nhfp, xxx *d_##dtyp, const char *myname, int bfsz) \ +{ \ + if (nhfp->structlevel) { \ + (*sfiprocs[nhfp->fnidx].fn.sf_##dtyp)(nhfp, d_##dtyp, myname, bfsz); \ + } else { \ + int save_mode = nhfp->mode; \ + \ + nhfp->mode &= ~(CONVERTING | UNCONVERTING); \ + nhfp->mode |= TURN_OFF_LOGGING; \ + (*sfiflprocs[nhfp->fnidx].fn_x.sf_##dtyp)(nhfp, d_##dtyp, \ + myname, bfsz); \ + nhfp->mode = save_mode; \ + } \ + if (!nhfp->eof) { \ + if ((((nhfp->mode & CONVERTING) != 0) \ + || ((nhfp->mode & UNCONVERTING) != 0)) \ + && nhfp->nhfpconvert) { \ + sfo_##dtyp(nhfp->nhfpconvert, d_##dtyp, myname, bfsz); \ + } \ + if (nhfp->fplog) \ + sf_log(nhfp, myname, sizeof *d_##dtyp, 1, \ + dtyp##_dump(d_##dtyp)); \ + } \ +} + +#include "sfmacros.h" + +SF_X(uint8_t, bitfield) + +void +sfo_char(NHFILE *nhfp, char *d_char, const char *myname, int cnt) +{ + if (nhfp->fplog) + sf_log(nhfp, myname, sizeof(char), cnt, Sfvalue_char(d_char, cnt)); + if (nhfp->structlevel) { + (*sfoprocs[nhfp->fnidx].fn.sf_char)(nhfp, d_char, myname, cnt); + } else { + FILE *save_fplog = nhfp->fplog; + + nhfp->fplog = 0; + (*sfoflprocs[nhfp->fnidx].fn_x.sf_char)(nhfp, d_char, myname, cnt); + nhfp->fplog = save_fplog; + } +} + +void +sfi_char(NHFILE *nhfp, char *d_char, const char *myname, int cnt) +{ + if (nhfp->structlevel) { + (*sfiprocs[nhfp->fnidx].fn.sf_char)(nhfp, d_char, myname, cnt); + } else { + int save_mode = nhfp->mode; + + nhfp->mode &= ~(CONVERTING | UNCONVERTING); + nhfp->mode |= TURN_OFF_LOGGING; + (*sfiflprocs[nhfp->fnidx].fn_x.sf_char)(nhfp, d_char, myname, cnt); + nhfp->mode = save_mode; + } + if (!nhfp->eof) { + if ((((nhfp->mode & CONVERTING) != 0) + || ((nhfp->mode & UNCONVERTING) != 0)) + && nhfp->nhfpconvert) { + sfo_char(nhfp->nhfpconvert, d_char, myname, cnt); + } + if (nhfp->fplog) + sf_log(nhfp, myname, sizeof(char), cnt, + Sfvalue_char(d_char, cnt)); + } +} + +void +sfo_genericptr(NHFILE *nhfp, void **d_genericptr, const char *myname) +{ + if (nhfp->fplog) + sf_log(nhfp, myname, sizeof *d_genericptr, 1, + Sfvalue_genericptr(d_genericptr)); + if (nhfp->structlevel) { + (*sfoprocs[nhfp->fnidx].fn.sf_genericptr)(nhfp, d_genericptr, myname); + } else { + FILE *save_fplog = nhfp->fplog; + nhfp->fplog = 0; + (*sfoflprocs[nhfp->fnidx].fn_x.sf_genericptr)(nhfp, d_genericptr, + myname); + nhfp->fplog = save_fplog; + } +} +void +sfi_genericptr(NHFILE *nhfp, void **d_genericptr, const char *myname) +{ + if (nhfp->structlevel) { + (*sfiprocs[nhfp->fnidx].fn.sf_genericptr)(nhfp, d_genericptr, myname); + } else { + int save_mode = nhfp->mode; + nhfp->mode &= ~(CONVERTING | UNCONVERTING); + nhfp->mode |= TURN_OFF_LOGGING; + (*sfiflprocs[nhfp->fnidx].fn_x.sf_genericptr)(nhfp, d_genericptr, + myname); + nhfp->mode = save_mode; + } + if (!nhfp->eof) { + if ((((nhfp->mode & CONVERTING) != 0) || ((nhfp->mode & UNCONVERTING) != 0)) + && nhfp->nhfpconvert) { + sfo_genericptr(nhfp->nhfpconvert, d_genericptr, myname); + } + if (nhfp->fplog) + sf_log(nhfp, myname, sizeof *d_genericptr, 1, + Sfvalue_genericptr(d_genericptr)); + } +} + +void +sfo_version_info(NHFILE *nhfp, struct version_info *d_version_info, + const char *myname) +{ + if (nhfp->fplog) + sf_log(nhfp, myname, sizeof *d_version_info, 1, + complex_dump((uchar *) d_version_info)); + if (nhfp->structlevel) { + (*sfoprocs[nhfp->fnidx].fn.sf_version_info)(nhfp, d_version_info, + myname); + } else { + FILE *save_fplog = nhfp->fplog; + nhfp->fplog = 0; + (*sfoflprocs[nhfp->fnidx].fn_x.sf_version_info)(nhfp, d_version_info, + myname); + nhfp->fplog = save_fplog; + } +} +void +sfi_version_info(NHFILE *nhfp, struct version_info *d_version_info, + const char *myname) +{ + if (nhfp->structlevel) { + (*sfiprocs[nhfp->fnidx].fn.sf_version_info)(nhfp, d_version_info, + myname); + } else { + int save_mode = nhfp->mode; + nhfp->mode &= ~(CONVERTING | UNCONVERTING); + nhfp->mode |= TURN_OFF_LOGGING; + (*sfiflprocs[nhfp->fnidx].fn_x.sf_version_info)(nhfp, d_version_info, + myname); + nhfp->mode = save_mode; + } + if (!nhfp->eof) { + if ((((nhfp->mode & CONVERTING) != 0) || ((nhfp->mode & UNCONVERTING) != 0)) + && nhfp->nhfpconvert) { + d_version_info->feature_set |= SFCTOOL_BIT; + sfo_version_info(nhfp->nhfpconvert, d_version_info, myname); + } + if (nhfp->fplog) + sf_log(nhfp, myname, sizeof *d_version_info, 1, + complex_dump((uchar *) d_version_info)); + } +} + +/* ---------------------------------------------------------------*/ + +void +sf_log(NHFILE *nhfp, const char *t1, size_t sz, int cnt, char *txtvalue) +{ + FILE *fp = nhfp->fplog; + long *iocount; + boolean dolog = ((nhfp->mode & TURN_OFF_LOGGING) == 0); + + if (fp && dolog) { + iocount = ((nhfp->mode & WRITING) == 0) ? &nhfp->rcount : &nhfp->wcount; + (void) fprintf(fp, +#ifndef VMS + "%08ld %s sz=%zu cnt=%d |%s|\n", +#else + "%08ld %s sz=%lu cnt=%d |%s|\n", +#endif + *iocount, + t1, +#ifndef VMS + sz, +#else + (unsigned long) sz, +#endif + cnt, txtvalue); +// (*iocount)++; +// if (*iocount == 87) +// __debugbreak(); + fflush(fp); + } +} + +char *sfvalue_char(char *a, int n) +{ + int i; + static char buf[120]; + char *cp; + + cp = &buf[0]; + if (n < (int) (sizeof buf - 1)) + buf[n] = '\0'; + else + buf[(int) (sizeof buf - 1)] = '\0'; + for (i = 0; i < n; ++i, ++cp, ++a) + *cp = *a; + *cp = '\0'; + return buf; +} + +char *sfvalue_boolean(boolean *a) +{ + static char buf[20]; + + Snprintf(buf, sizeof buf, "%s", + (*a == 0) ? "false" : "true"); + return buf; +} + +char *sfvalue_schar(schar *a) +{ + static char buf[20]; + + Snprintf(buf, sizeof buf, "%d", (int) *a); + return buf; +} + +char * sfvalue_aligntyp(aligntyp *a) +{ + static char buf[20]; + + Snprintf(buf, sizeof buf, "%d", (int) *a); + return buf; +} + +char * +sfvalue_any(anything *a) +{ + static char buf[20]; + + Snprintf(buf, sizeof buf, + "%" PRId64, + a->a_int64); + return buf; +} + +char * +sfvalue_genericptr(genericptr a) +{ + static char buf[20]; + + Snprintf(buf, sizeof buf, "%s", + (a == 0) ? "0" : "glorkum"); + return buf; +} + +char * sfvalue_int16(int16 *a) +{ + static char buf[20]; + + Snprintf(buf, sizeof buf, "%d", *a); + return buf; +} + +char * sfvalue_int32(int32 *a) +{ + static char buf[20]; + + Snprintf(buf, sizeof buf, "%" PRId32, *a); + return buf; +} + +char * sfvalue_int64(int64 *a) +{ + static char buf[20]; + Snprintf(buf, sizeof buf, "%" PRId64, *a); + return buf; +} + +char * sfvalue_uchar(uchar *a) +{ + static char buf[20]; + unsigned x; + + x = *a; + Snprintf(buf, sizeof buf, "%03u", x); + return buf; +} + +char * sfvalue_uint16(uint16 *a) +{ + static char buf[20]; + + Snprintf(buf, sizeof buf, "%u", (uint) *a); + return buf; +} + +char * sfvalue_uint32(uint32 *a) +{ + static char buf[20]; + + Snprintf(buf, sizeof buf, "%" PRIu32, *a); + return buf; +} + +char * sfvalue_uint64(uint64 *a) +{ + static char buf[20]; + + Snprintf(buf, sizeof buf, "%" PRIu64, *a); + return buf; +} + +char * sfvalue_size_t(size_t *a UNUSED) +{ + static char buf[20]; + + Snprintf(buf, sizeof buf, "%s", (char *) ""); + return buf; +} + +char * sfvalue_time_t(time_t *a UNUSED) +{ + static char buf[20]; + + Snprintf(buf, sizeof buf, "%s", (char *) ""); + return buf; +} + +char * sfvalue_short(short *a) +{ + static char buf[20]; + + Snprintf(buf, sizeof buf, "%d", (int) *a); + return buf; +} + +char * sfvalue_ushort(ushort *a) +{ + static char buf[20]; + + Snprintf(buf, sizeof buf, "%u", (unsigned) *a); + return buf; +} + +char * sfvalue_int(int *a) +{ + static char buf[20]; + + Snprintf(buf, sizeof buf, "%d", *a); + return buf; +} + +char * sfvalue_unsigned(unsigned *a) +{ + static char buf[20]; + + Snprintf(buf, sizeof buf, "%u", *a); + return buf; +} + +char * sfvalue_long(long *a) +{ + static char buf[20]; + + Snprintf(buf, sizeof buf, "%ld", *a); + return buf; +} + +char * sfvalue_ulong(ulong *a) +{ + static char buf[20]; + + Snprintf(buf, sizeof buf, "%lu", *a); + return buf; +} + +char * sfvalue_xint8(xint8 *a) +{ + static char buf[20]; + + Snprintf(buf, sizeof buf, "%d", (int) *a); + return buf; +} + +char * sfvalue_xint16(xint16 *a) +{ + static char buf[20]; + + + Snprintf(buf, sizeof buf, "%d", (int) *a); + return buf; +} + +char * +sfvalue_bitfield(uint8 *a) +{ + static char buf[20]; + + Snprintf(buf, sizeof buf, "%u", (uint) *a); + return buf; +} + +char * +bitfield_dump(uint8 *a) +{ + static char buf[20]; + + Snprintf(buf, sizeof buf, "%u", (uint) *a); + return buf; +} +char * +complex_dump(uchar *a) +{ + int i; + uchar *uc = a; + static char buf[50]; + unsigned x[10]; + + for (i = 0; i < SIZE(x); ++i) { + x[i] = *uc++; + } + Snprintf(buf, sizeof buf, "%03x %03x %03x %03x %03x %03x %03x %03x %03x %03x", + x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7], x[8], x[9]); + buf[40] = '\0'; + return buf; +} +/* + *---------------------------------------------------------------------------- + * initialize the function pointers. These are called from initoptions_init(). + *---------------------------------------------------------------------------- + */ + +void +sf_init(void) +{ + sfoprocs[invalid] = zerosfoprocs; + sfiprocs[invalid] = zerosfiprocs; + sfoprocs[historical] = historical_sfo_procs; + sfiprocs[historical] = historical_sfi_procs; + sfoflprocs[exportascii] = zerosfoflprocs; + sfiflprocs[exportascii] = zerosfiflprocs; +} + +void +sf_setprocs(int idx, struct sf_structlevel_procs *sfi, struct sf_structlevel_procs *sfo) +{ + sfoprocs[idx] = *sfo; + sfiprocs[idx] = *sfi; +} +void +sf_setflprocs(int idx, struct sf_fieldlevel_procs *flsfi, + struct sf_fieldlevel_procs *flsfo) +{ + sfoflprocs[idx] = *flsfo; + sfiflprocs[idx] = *flsfi; +} + +#ifndef SFCTOOL +void norm_ptrs_any(union any *d_any); +void norm_ptrs_align(struct align *d_align); +void norm_ptrs_arti_info(struct arti_info *d_arti_info); +void norm_ptrs_attribs(struct attribs *d_attribs); +void norm_ptrs_bill_x(struct bill_x *d_bill_x); +void norm_ptrs_branch(struct branch *d_branch); +void norm_ptrs_bubble(struct bubble *d_bubble); +void norm_ptrs_cemetery(struct cemetery *d_cemetery); +void norm_ptrs_context_info(struct context_info *d_context_info); +void norm_ptrs_achievement_tracking( + struct achievement_tracking *d_achievement_tracking); +void norm_ptrs_book_info(struct book_info *d_book_info); +void norm_ptrs_dig_info(struct dig_info *d_dig_info); +void norm_ptrs_engrave_info(struct engrave_info *d_engrave_info); +void norm_ptrs_obj_split(struct obj_split *d_obj_split); +void norm_ptrs_polearm_info(struct polearm_info *d_polearm_info); +void norm_ptrs_takeoff_info(struct takeoff_info *d_takeoff_info); +void norm_ptrs_tin_info(struct tin_info *d_tin_info); +void norm_ptrs_tribute_info(struct tribute_info *d_tribute_info); +void norm_ptrs_victual_info(struct victual_info *d_victual_info); +void norm_ptrs_warntype_info(struct warntype_info *d_warntype_info); +void norm_ptrs_d_flags(struct d_flags *d_d_flags); +void norm_ptrs_d_level(struct d_level *d_d_level); +void norm_ptrs_damage(struct damage *d_damage); +void norm_ptrs_dest_area(struct dest_area *d_dest_area); +void norm_ptrs_dgn_topology(struct dgn_topology *d_dgn_topology); +void norm_ptrs_dungeon(struct dungeon *d_dungeon); +void norm_ptrs_ebones(struct ebones *d_ebones); +void norm_ptrs_edog(struct edog *d_edog); +void norm_ptrs_egd(struct egd *d_egd); +void norm_ptrs_emin(struct emin *d_emin); +void norm_ptrs_engr(struct engr *d_engr); +void norm_ptrs_epri(struct epri *d_epri); +void norm_ptrs_eshk(struct eshk *d_eshk); +void norm_ptrs_fakecorridor(struct fakecorridor *d_fakecorridor); +void norm_ptrs_fe(struct fe *d_fe); +void norm_ptrs_flag(struct flag *d_flag); +void norm_ptrs_fruit(struct fruit *d_fruit); +void norm_ptrs_gamelog_line(struct gamelog_line *d_gamelog_line); +void norm_ptrs_kinfo(struct kinfo *d_kinfo); +void norm_ptrs_levelflags(struct levelflags *d_levelflags); +void norm_ptrs_linfo(struct linfo *d_linfo); +void norm_ptrs_ls_t(struct ls_t *d_ls_t); +void norm_ptrs_mapseen_feat(struct mapseen_feat *d_mapseen_feat); +void norm_ptrs_mapseen_flags(struct mapseen_flags *d_mapseen_flags); +void norm_ptrs_mapseen_rooms(struct mapseen_rooms *d_mapseen_rooms); +void norm_ptrs_mapseen(struct mapseen *d_mapseen); +void norm_ptrs_mextra(struct mextra *d_mextra); +void norm_ptrs_mkroom(struct mkroom *d_mkroom); +void norm_ptrs_monst(struct monst *d_monst); +void norm_ptrs_mvitals(struct mvitals *d_mvitals); +void norm_ptrs_nhcoord(struct nhcoord *d_nhcoord); +void norm_ptrs_nhrect(struct nhrect *d_nhrect); +void norm_ptrs_novel_tracking(struct novel_tracking *d_novel_tracking); +void norm_ptrs_obj(struct obj *d_obj); +void norm_ptrs_objclass(struct objclass *d_objclass); +void norm_ptrs_oextra(struct oextra *d_oextra); +void norm_ptrs_prop(struct prop *d_prop); +void norm_ptrs_q_score(struct q_score *d_q_score); +void norm_ptrs_rm(struct rm *d_rm); +void norm_ptrs_s_level(struct s_level *d_s_level); +void norm_ptrs_skills(struct skills *d_skills); +void norm_ptrs_spell(struct spell *d_spell); +void norm_ptrs_stairway(struct stairway *d_stairway); +void norm_ptrs_trap(struct trap *d_trap); +void norm_ptrs_u_conduct(struct u_conduct *d_u_conduct); +void norm_ptrs_u_event(struct u_event *d_u_event); +void norm_ptrs_u_have(struct u_have *d_u_have); +void norm_ptrs_u_realtime(struct u_realtime *d_u_realtime); +void norm_ptrs_u_roleplay(struct u_roleplay *d_u_roleplay); +void norm_ptrs_version_info(struct version_info *d_version_info); +void norm_ptrs_vlaunchinfo(union vlaunchinfo *d_vlaunchinfo); +void norm_ptrs_vptrs(union vptrs *d_vptrs); +void norm_ptrs_you(struct you *d_you); + +void +norm_ptrs_any(union any *d_any UNUSED) +{ +} +void +norm_ptrs_align(struct align *d_align UNUSED) +{ +} + +void +norm_ptrs_arti_info(struct arti_info *d_arti_info UNUSED) +{ +} + +void +norm_ptrs_attribs(struct attribs *d_attribs UNUSED) +{ +} + +void +norm_ptrs_bill_x(struct bill_x *d_bill_x UNUSED) +{ +} + +void +norm_ptrs_branch(struct branch *d_branch UNUSED) +{ +} + +void +norm_ptrs_bubble(struct bubble *d_bubble UNUSED) +{ +} + +void +norm_ptrs_cemetery(struct cemetery *d_cemetery UNUSED) +{ +} + +void +norm_ptrs_context_info(struct context_info *d_context_info UNUSED) +{ +} + +void +norm_ptrs_achievement_tracking(struct achievement_tracking *d_achievement_tracking UNUSED) +{ +} + +void +norm_ptrs_book_info(struct book_info *d_book_info UNUSED) +{ +} + +void +norm_ptrs_dig_info(struct dig_info *d_dig_info UNUSED) +{ +} + +void +norm_ptrs_engrave_info(struct engrave_info *d_engrave_info UNUSED) +{ +} + +void +norm_ptrs_obj_split(struct obj_split *d_obj_split UNUSED) +{ +} + +void +norm_ptrs_polearm_info(struct polearm_info *d_polearm_info UNUSED) +{ +} + +void +norm_ptrs_takeoff_info(struct takeoff_info *d_takeoff_info UNUSED) +{ +} + +void +norm_ptrs_tin_info(struct tin_info *d_tin_info UNUSED) +{ +} + +void +norm_ptrs_tribute_info(struct tribute_info *d_tribute_info UNUSED) +{ +} + +void +norm_ptrs_victual_info(struct victual_info *d_victual_info UNUSED) +{ +} + +void +norm_ptrs_warntype_info(struct warntype_info *d_warntype_info UNUSED) +{ +} + +void +norm_ptrs_d_flags(struct d_flags *d_d_flags UNUSED) +{ +} + +void +norm_ptrs_d_level(struct d_level *d_d_level UNUSED) +{ +} + +void +norm_ptrs_damage(struct damage *d_damage UNUSED) +{ +} + +void +norm_ptrs_dest_area(struct dest_area *d_dest_area UNUSED) +{ +} + +void +norm_ptrs_dgn_topology(struct dgn_topology *d_dgn_topology UNUSED) +{ +} + +void +norm_ptrs_dungeon(struct dungeon *d_dungeon UNUSED) +{ +} + +void +norm_ptrs_ebones(struct ebones *d_ebones UNUSED) +{ +} + +void +norm_ptrs_edog(struct edog *d_edog UNUSED) +{ +} + +void +norm_ptrs_egd(struct egd *d_egd UNUSED) +{ +} + +void +norm_ptrs_emin(struct emin *d_emin UNUSED) +{ +} + +void +norm_ptrs_engr(struct engr *d_engr UNUSED) +{ +} + +void +norm_ptrs_epri(struct epri *d_epri UNUSED) +{ +} + +void +norm_ptrs_eshk(struct eshk *d_eshk UNUSED) +{ +} + +void +norm_ptrs_fakecorridor(struct fakecorridor *d_fakecorridor UNUSED) +{ +} + +void +norm_ptrs_fe(struct fe *d_fe UNUSED) +{ +} + +void +norm_ptrs_flag(struct flag *d_flag UNUSED) +{ +} + +void +norm_ptrs_fruit(struct fruit *d_fruit UNUSED) +{ +} + +void +norm_ptrs_gamelog_line(struct gamelog_line *d_gamelog_line UNUSED) +{ +} + +void +norm_ptrs_kinfo(struct kinfo *d_kinfo UNUSED) +{ +} + +void +norm_ptrs_levelflags(struct levelflags *d_levelflags UNUSED) +{ +} + +void +norm_ptrs_linfo(struct linfo *d_linfo UNUSED) +{ +} + +void +norm_ptrs_ls_t(struct ls_t *d_ls_t UNUSED) +{ +} + +void +norm_ptrs_mapseen_feat(struct mapseen_feat *d_mapseen_feat UNUSED) +{ +} + +void +norm_ptrs_mapseen_flags(struct mapseen_flags *d_mapseen_flags UNUSED) +{ +} + +void +norm_ptrs_mapseen_rooms(struct mapseen_rooms *d_mapseen_rooms UNUSED) +{ +} + +void +norm_ptrs_mapseen(struct mapseen *d_mapseen UNUSED) +{ +} + +void +norm_ptrs_mextra(struct mextra *d_mextra UNUSED) +{ +} + +void +norm_ptrs_mkroom(struct mkroom *d_mkroom UNUSED) +{ +} + +void +norm_ptrs_monst(struct monst *d_monst UNUSED) +{ +} + +void +norm_ptrs_mvitals(struct mvitals *d_mvitals UNUSED) +{ +} + +void +norm_ptrs_nhcoord(struct nhcoord *d_nhcoord UNUSED) +{ +} + +void +norm_ptrs_nhrect(struct nhrect *d_nhrect UNUSED) +{ +} + +void +norm_ptrs_novel_tracking(struct novel_tracking *d_novel_tracking UNUSED) +{ +} + +void +norm_ptrs_obj(struct obj *d_obj UNUSED) +{ +} + +void +norm_ptrs_objclass(struct objclass *d_objclass UNUSED) +{ +} + +void +norm_ptrs_oextra(struct oextra *d_oextra UNUSED) +{ +} + +void +norm_ptrs_prop(struct prop *d_prop UNUSED) +{ +} + +void +norm_ptrs_q_score(struct q_score *d_q_score UNUSED) +{ +} + +void +norm_ptrs_rm(struct rm *d_rm UNUSED) +{ +} + +void +norm_ptrs_s_level(struct s_level *d_s_level UNUSED) +{ +} + +void +norm_ptrs_skills(struct skills *d_skills UNUSED) +{ +} + +void +norm_ptrs_spell(struct spell *d_spell UNUSED) +{ +} + +void +norm_ptrs_stairway(struct stairway *d_stairway UNUSED) +{ +} + +void +norm_ptrs_trap(struct trap *d_trap UNUSED) +{ +} + +void +norm_ptrs_u_conduct(struct u_conduct *d_u_conduct UNUSED) +{ +} + +void +norm_ptrs_u_event(struct u_event *d_u_event UNUSED) +{ +} + +void +norm_ptrs_u_have(struct u_have *d_u_have UNUSED) +{ +} + +void +norm_ptrs_u_realtime(struct u_realtime *d_u_realtime UNUSED) +{ +} + +void +norm_ptrs_u_roleplay(struct u_roleplay *d_u_roleplay UNUSED) +{ +} + +void +norm_ptrs_version_info(struct version_info *d_version_info UNUSED) +{ +} + +void +norm_ptrs_vlaunchinfo(union vlaunchinfo *d_vlaunchinfo UNUSED) +{ +} + +void +norm_ptrs_vptrs(union vptrs *d_vptrs UNUSED) +{ +} + +void +norm_ptrs_you(struct you *d_you UNUSED) +{ +} +#endif /* SFCTOOL */ + +#undef SF_X +#undef SF_C +#undef SF_A + +/* end of sfbase.c */ + diff --git a/src/sfstruct.c b/src/sfstruct.c new file mode 100644 index 000000000..3c8b8fd66 --- /dev/null +++ b/src/sfstruct.c @@ -0,0 +1,631 @@ +/* NetHack 5.0 sfstruct.c $NHDT-Date: 1606765215 2020/11/30 19:40:15 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.4 $ */ +/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ +/*-Copyright (c) Michael Allison, 2025. */ +/* NetHack may be freely redistributed. See license for details. */ + +#include "hack.h" +#include "sfprocs.h" + +/* #define SFLOGGING */ /* debugging */ + +staticfn void sfstruct_read_error(void); + +/* historical full struct savings */ + +#ifdef SAVEFILE_DEBUGGING +#if defined(__GNUC__) +#define DEBUGFORMATSTR64 "%s %s %ld %ld %d\n" +#elif defined(_MSC_VER) +#define DEBUGFORMATSTR64 "%s %s %lld %ld %d\n" +#endif +#endif + +#ifdef SFLOGGING +staticfn void logging_finish(void); +#endif + +#define SFO_BODY(dt) \ +{ \ + bwrite(nhfp->fd, (genericptr_t) d_##dt, sizeof *d_##dt); \ +} + +#define SFI_BODY(dt) \ +{ \ + if (nhfp->eof) { \ + sfstruct_read_error(); \ + } \ + mread(nhfp->fd, (genericptr_t) d_##dt, sizeof *d_##dt); \ + if (restoreinfo.mread_flags == -1) \ + nhfp->eof = TRUE; \ +} + +#define SF_A(dtyp) \ +void historical_sfo_##dtyp(NHFILE *, dtyp *d_##dtyp, const char *); \ +void historical_sfi_##dtyp(NHFILE *, dtyp *d_##dtyp, const char *); \ + \ +void historical_sfo_##dtyp(NHFILE *nhfp, dtyp *d_##dtyp, \ + const char *myname UNUSED) \ + SFO_BODY(dtyp) \ + \ +void historical_sfi_##dtyp(NHFILE *nhfp, dtyp *d_##dtyp, \ + const char *myname UNUSED) \ + SFI_BODY(dtyp) + +#define SFO_CBODY(dt) \ + { \ + norm_ptrs_##dt(d_##dt); \ + bwrite(nhfp->fd, (genericptr_t) d_##dt, sizeof *d_##dt); \ + } + +#define SFI_CBODY(dt) \ + { \ + if (nhfp->eof) { \ + sfstruct_read_error(); \ + } \ + mread(nhfp->fd, (genericptr_t) d_##dt, sizeof *d_##dt); \ + norm_ptrs_##dt(d_##dt); \ + if (restoreinfo.mread_flags == -1) \ + nhfp->eof = TRUE; \ + } + +#define SF_C(keyw, dtyp) \ +void historical_sfo_##dtyp(NHFILE *, keyw dtyp *d_##dtyp, \ + const char *); \ +void historical_sfi_##dtyp(NHFILE *, keyw dtyp *d_##dtyp, \ + const char *); \ +extern void norm_ptrs_##dtyp(keyw dtyp *d_##dtyp); \ + \ +void historical_sfo_##dtyp(NHFILE *nhfp, keyw dtyp *d_##dtyp, \ + const char *myname UNUSED) \ + SFO_CBODY(dtyp) \ + \ +void historical_sfi_##dtyp(NHFILE *nhfp, keyw dtyp *d_##dtyp, \ + const char *myname UNUSED) \ + SFI_CBODY(dtyp) + +#define SF_X(xxx, dtyp) \ +void historical_sfo_##dtyp(NHFILE *, xxx *d_##dtyp, const char *, int); \ +void historical_sfi_##dtyp(NHFILE *, xxx *d_##dtyp, const char *, int); \ + \ +void historical_sfo_##dtyp(NHFILE *nhfp, xxx *d_##dtyp, \ + const char *myname UNUSED, int bflen UNUSED) \ + SFO_BODY(dtyp) \ + \ +void historical_sfi_##dtyp(NHFILE *nhfp, xxx *d_##dtyp, \ + const char *myname UNUSED, int bflen UNUSED) \ + SFI_BODY(dtyp) + + +#include "sfmacros.h" +SF_C(struct, version_info) + +void historical_sfo_char(NHFILE *, char *d_char, const char *, int); +void historical_sfi_char(NHFILE *, char *d_char, const char *, int); + +void +historical_sfo_char(NHFILE *nhfp, char *d_char, + const char *myname UNUSED, int cnt) +{ + bwrite(nhfp->fd, (genericptr_t) d_char, cnt * sizeof (char)); +} + +void +historical_sfi_char(NHFILE *nhfp, char *d_char, + const char *myname UNUSED, int cnt) +{ + mread(nhfp->fd, (genericptr_t) d_char, cnt * sizeof (char)); + if (restoreinfo.mread_flags == -1) + nhfp->eof = TRUE; +} +//extern void sfo_genericptr(NHFILE *, void **, const char *); +//extern void sfi_genericptr(NHFILE *, void **, const char *); +//extern void sfo_x_genericptr(NHFILE *, void **, const char *); +//extern void sfi_x_genericptr(NHFILE *, void **, const char *); + +void historical_sfo_genericptr_t(NHFILE *, genericptr_t *d_genericptr_t, + const char *); +void historical_sfi_genericptr_t(NHFILE *, genericptr_t *d_genericptr_t, + const char *); +void +historical_sfo_genericptr_t(NHFILE *nhfp, genericptr_t *d_genericptr_t, + const char *myname UNUSED) +{ + bwrite(nhfp->fd, (genericptr_t) d_genericptr_t, sizeof *d_genericptr_t); +} +void +historical_sfi_genericptr_t(NHFILE *nhfp, genericptr_t *d_genericptr_t, + const char *myname UNUSED) +{ + if (nhfp->eof) { + sfstruct_read_error(); + } + mread(nhfp->fd, (genericptr_t) d_genericptr_t, sizeof *d_genericptr_t); + if (restoreinfo.mread_flags == -1) + nhfp->eof = TRUE; +} + +SF_X(uint8_t, bitfield) + +struct sf_structlevel_procs historical_sfo_procs = { + "", + /* sf */ + { + historical_sfo_arti_info, + historical_sfo_nhrect, + historical_sfo_branch, + historical_sfo_bubble, + historical_sfo_cemetery, + historical_sfo_context_info, + historical_sfo_nhcoord, + historical_sfo_damage, + historical_sfo_dest_area, + historical_sfo_dgn_topology, + historical_sfo_dungeon, + historical_sfo_d_level, + historical_sfo_ebones, + historical_sfo_edog, + historical_sfo_egd, + historical_sfo_emin, + historical_sfo_engr, + historical_sfo_epri, + historical_sfo_eshk, + historical_sfo_fe, + historical_sfo_flag, + historical_sfo_fruit, + historical_sfo_gamelog_line, + historical_sfo_kinfo, + historical_sfo_levelflags, + historical_sfo_ls_t, + historical_sfo_linfo, + historical_sfo_mapseen_feat, + historical_sfo_mapseen_flags, + historical_sfo_mapseen_rooms, + historical_sfo_mkroom, + historical_sfo_monst, + historical_sfo_mvitals, + historical_sfo_obj, + historical_sfo_objclass, + historical_sfo_q_score, + historical_sfo_rm, + historical_sfo_spell, + historical_sfo_stairway, + historical_sfo_s_level, + historical_sfo_trap, + historical_sfo_version_info, + historical_sfo_you, + historical_sfo_any, + + historical_sfo_aligntyp, + historical_sfo_boolean, + historical_sfo_coordxy, + historical_sfo_genericptr_t, + historical_sfo_int, + historical_sfo_int16, + historical_sfo_int32, + historical_sfo_int64, + historical_sfo_long, + historical_sfo_schar, + historical_sfo_short, + historical_sfo_size_t, + historical_sfo_time_t, + historical_sfo_uchar, + historical_sfo_uint16, + historical_sfo_uint32, + historical_sfo_uint64, + historical_sfo_ulong, + historical_sfo_unsigned, + historical_sfo_ushort, + historical_sfo_xint16, + historical_sfo_xint8, + historical_sfo_char, + historical_sfo_bitfield, + } +}; + +struct sf_structlevel_procs historical_sfi_procs = { + "", + /* sf */ + { + historical_sfi_arti_info, + historical_sfi_nhrect, + historical_sfi_branch, + historical_sfi_bubble, + historical_sfi_cemetery, + historical_sfi_context_info, + historical_sfi_nhcoord, + historical_sfi_damage, + historical_sfi_dest_area, + historical_sfi_dgn_topology, + historical_sfi_dungeon, + historical_sfi_d_level, + historical_sfi_ebones, + historical_sfi_edog, + historical_sfi_egd, + historical_sfi_emin, + historical_sfi_engr, + historical_sfi_epri, + historical_sfi_eshk, + historical_sfi_fe, + historical_sfi_flag, + historical_sfi_fruit, + historical_sfi_gamelog_line, + historical_sfi_kinfo, + historical_sfi_levelflags, + historical_sfi_ls_t, + historical_sfi_linfo, + historical_sfi_mapseen_feat, + historical_sfi_mapseen_flags, + historical_sfi_mapseen_rooms, + historical_sfi_mkroom, + historical_sfi_monst, + historical_sfi_mvitals, + historical_sfi_obj, + historical_sfi_objclass, + historical_sfi_q_score, + historical_sfi_rm, + historical_sfi_spell, + historical_sfi_stairway, + historical_sfi_s_level, + historical_sfi_trap, + historical_sfi_version_info, + historical_sfi_you, + historical_sfi_any, + + historical_sfi_aligntyp, + historical_sfi_boolean, + historical_sfi_coordxy, + historical_sfi_genericptr_t, + historical_sfi_int, + historical_sfi_int16, + historical_sfi_int32, + historical_sfi_int64, + historical_sfi_long, + historical_sfi_schar, + historical_sfi_short, + historical_sfi_size_t, + historical_sfi_time_t, + historical_sfi_uchar, + historical_sfi_uint16, + historical_sfi_uint32, + historical_sfi_uint64, + historical_sfi_ulong, + historical_sfi_unsigned, + historical_sfi_ushort, + historical_sfi_xint16, + historical_sfi_xint8, + historical_sfi_char, + historical_sfi_bitfield, + } +}; + +/* + * The historical bwrite() and mread() functions follow + */ + +#ifdef SAVEFILE_DEBUGGING +static long floc = 0L; +#endif + +/* + * historical structlevel savefile writing and reading routines follow. + * These were moved here from save.c and restore.c between 3.6.3 and 5.0.0,. + */ + +staticfn int getidx(int, int); + +#if defined(UNIX) || defined(WIN32) +#define USE_BUFFERING +#endif + +struct restore_info restoreinfo = { + "externalcomp", 0, +}; + +#define MAXFD 5 +enum {NOFLG = 0, NOSLOT = 1}; +static int bw_sticky[MAXFD] = {-1,-1,-1,-1,-1}; +static int bw_buffered[MAXFD] = {0,0,0,0,0}; +#ifdef USE_BUFFERING +static FILE *bw_FILE[MAXFD] = {0,0,0,0,0}; +#endif + +#ifdef SFLOGGING +static FILE *ofp[20] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + char ofnamebuf[80]; + static long ocnt = 0L; + + static FILE *ifp[20] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + char ifnamebuf[80]; + static long icnt = 0L; +#endif + + +/* + * Presumably, the fdopen() to allow use of stdio fwrite() + * over write() was done for performance or functionality + * reasons to help some particular platform long ago. + * + * There have been some issues being encountered with the + * implementation due to having an individual set of + * tracking variables, even though there were nested + * sets of open fd (like INSURANCE). + * + * This uses an individual tracking entry for each fd + * being used. + * + * Some notes: + * + * Once buffered IO (stdio) has been enabled on the file + * associated with a descriptor via fdopen(): + * + * 1. If you use bufoff and bufon to try to toggle the + * use of write vs fwrite; the code just tracks which + * routine is to be called through the tracking + * variables and acts accordingly. + * bw_sticky[] - used to find the index number for + * the fd that is stored in it, or -1 + * if it is a free slot. + * bw_buffered[] - indicator that buffered IO routines + * are available for use. + * bw_FILE[] - the non-zero FILE * for use in calling + * fwrite() when bw_buffered[] is also + * non-zero. + * + * 2. It is illegal to call close(fd) after fdopen(), you + * must always use fclose() on the FILE * from + * that point on, so care must be taken to never call + * close(fd) on the underlying fd or bad things will + * happen. + */ + +staticfn int +getidx(int fd, int flg) +{ + int i, retval = -1; + + for (i = 0; i < MAXFD; ++i) + if (bw_sticky[i] == fd) + return i; + if (flg == NOSLOT) + return retval; + for (i = 0; i < MAXFD; ++i) + if (bw_sticky[i] < 0) { + bw_sticky[i] = fd; + retval = i; + break; + } + return retval; +} + +/* Let caller know that bclose() should handle it (TRUE) */ +boolean +close_check(int fd) +{ + int idx = getidx(fd, NOSLOT); + boolean retval = FALSE; + + if (idx >= 0) + retval = TRUE; + return retval; +} + +void +bufon(int fd) +{ + int idx = getidx(fd, NOFLG); + + if (idx >= 0) { + bw_sticky[idx] = fd; +#ifdef USE_BUFFERING + if (bw_buffered[idx]) + panic("buffering already enabled"); + if (!bw_FILE[idx]) { + if ((bw_FILE[idx] = fdopen(fd, "w")) == 0) + panic("buffering of file %d failed", fd); + } + bw_buffered[idx] = (bw_FILE[idx] != 0); +#else + bw_buffered[idx] = 1; +#endif + } +} + +void +bufoff(int fd) +{ + int idx = getidx(fd, NOFLG); + + if (idx >= 0) { + bflush(fd); + bw_buffered[idx] = 0; /* just a flag that says "use write(fd)" */ + } +} + +void +bclose(int fd) +{ + int idx = getidx(fd, NOSLOT); + + bufoff(fd); /* sets bw_buffered[idx] = 0 */ + if (idx >= 0) { +#ifdef USE_BUFFERING + if (bw_FILE[idx]) { + (void) fclose(bw_FILE[idx]); + bw_FILE[idx] = 0; + } else +#endif + close(fd); + /* return the idx to the pool */ + bw_sticky[idx] = -1; + } +#ifdef SFLOGGING + if (fd >= 0 && fd <= SIZE(ofp)) { + if (ofp[fd]) { + fclose(ofp[fd]); + ofp[fd] = 0; + } else if (ifp[fd]) { + fclose(ifp[fd]); + ifp[fd] = 0; + } + } +#endif + return; +} + +void +bflush(int fd) +{ + int idx = getidx(fd, NOFLG); + + if (idx >= 0) { +#ifdef USE_BUFFERING + if (bw_FILE[idx]) { + if (fflush(bw_FILE[idx]) == EOF) + panic("flush of savefile failed!"); + } +#endif + } + return; +} + +void +bwrite(int fd, const genericptr_t loc, unsigned num) +{ + boolean failed; + int idx = getidx(fd, NOFLG); + +#ifdef SFLOGGING + if (fd >= 0 && fd < SIZE(ofp)) { + if (!ofp[fd]) { + Snprintf(ofnamebuf, sizeof ofnamebuf, "bwrite_%02d.log", fd); + ofp[fd] = fopen(ofnamebuf, "w"); + } + if (ofp[fd]) { + fprintf(ofp[fd], "%08ld, %08ld, %d\n", ocnt, + ftell(ofp[fd]), num); + ocnt++; + } + } +#endif + + if (idx >= 0) { + if (num == 0) { + /* nothing to do; we need a special case to exit early + because glibc fwrite doesn't give reliable + success/failure indication when writing 0 bytes */ + return; + } + +#ifdef USE_BUFFERING + if (bw_buffered[idx] && bw_FILE[idx]) { + failed = (fwrite(loc, (int) num, 1, bw_FILE[idx]) != 1); + } else +#endif /* UNIX */ + { + /* lint wants 3rd arg of write to be an int; lint -p an unsigned */ +#if defined(BSD) || defined(ULTRIX) || defined(WIN32) || defined(_MSC_VER) + failed = ((long) write(fd, loc, (int) num) != (long) num); +#else /* e.g. SYSV, __TURBOC__ */ + failed = ((long) write(fd, loc, num) != (long) num); +#endif + } + if (failed) { +#if defined(HANGUPHANDLING) + if (program_state.done_hup) + nh_terminate(EXIT_FAILURE); + else +#endif + panic("cannot write %u bytes to file #%d", num, fd); + } + } else + impossible("fd not in list (%d)?", fd); +} + +/* ===================================================== */ + +void +mread(int fd, genericptr_t buf, unsigned len) +{ + +#ifdef SFLOGGING + if (fd >= 0 && fd < 9) { + if (!ifp[fd]) { + Snprintf(ifnamebuf, sizeof ifnamebuf, "mread_%02d.log", fd); + ifp[fd] = fopen(ifnamebuf, "w"); + } + if (ifp[fd]) { + fprintf(ifp[fd], "%08ld, %08ld, %d\n", icnt, + ftell(ifp[fd]), (int) len); + icnt++; + } + } +#endif + +#if defined(BSD) || defined(ULTRIX) || defined(WIN32) +#define readLenType int +#else /* e.g. SYSV, __TURBOC__ */ +#define readLenType unsigned +#endif + readLenType rlen; + /* Not perfect, but we don't have ssize_t available. */ + rlen = (readLenType) read(fd, buf, (readLenType) len); + if ((readLenType) rlen != (readLenType) len) { + if ((restoreinfo.mread_flags == 1) /* means "return anyway" */ + || (program_state.reading_bonesfile == 1)) { + restoreinfo.mread_flags = -1; + return; + } else { +#ifndef SFCTOOL + pline("Read %d instead of %u bytes.", (int) rlen, len); + display_nhwindow(WIN_MESSAGE, TRUE); /* flush before error() */ + if (program_state.restoring) { + (void) nhclose(fd); + (void) delete_savefile(); + error("Error restoring old game."); + } + panic("Error reading level file."); +#else + printf("Read %d instead of %u bytes.\n", (int) rlen, len); +#endif + } + } +} + +staticfn void +sfstruct_read_error(void) +{ + /* problem */; +} + +#ifdef SFLOGGING +staticfn void +logging_finish(void) +{ + int i; + + for (i = 0; i < SIZE(ofp); ++i) { + if (ofp[i]) { + fclose(ofp[i]); + ofp[i] = 0; + } + } + ocnt = 0L; + + for (i = 0; i < SIZE(ifp); ++i) { + if (ifp[i]) { + fclose(ifp[i]); + ifp[i] = 0; + } + } + icnt = 0L; +} +#endif /* SFLOGGING */ + +#undef SF_X +#undef SF_C +#undef SF_A + +/* end of sfstruct.c */ + diff --git a/src/shk.c b/src/shk.c index 6772da453..bd87b8849 100644 --- a/src/shk.c +++ b/src/shk.c @@ -1,88 +1,144 @@ -/* NetHack 3.6 shk.c $NHDT-Date: 1571436007 2019/10/18 22:00:07 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.171 $ */ +/* NetHack 5.0 shk.c $NHDT-Date: 1736516428 2025/01/10 05:40:28 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.306 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2012. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -#define PAY_SOME 2 +/* + * FIXME: + * The normal shop messages are verbal. There are a lot of cases + * where an alternate message is given if the hero is deaf or shk + * is mute (when poly'd), but that is usually visual-based. It is + * possible for hero to pay for items while blind (only if adjacent + * to shk) and the alternate messages fail to account for that. + */ + #define PAY_BUY 1 #define PAY_CANT 0 /* too poor */ #define PAY_SKIP (-1) #define PAY_BROKE (-2) -STATIC_DCL void FDECL(makekops, (coord *)); -STATIC_DCL void FDECL(call_kops, (struct monst *, BOOLEAN_P)); -STATIC_DCL void FDECL(kops_gone, (BOOLEAN_P)); +enum billitem_status { + FullyUsedUp = 1, /* completely used up; obj->where==OBJ_ONBILL */ + PartlyUsedUp = 2, /* partly used up; obj->where==OBJ_INVENT or similar */ + PartlyIntact = 3, /* intact portion of partly used up item */ + FullyIntact = 4, /* normal unpaid item */ + KnownContainer = 5, /* container->cknown==1, holding unpaid item(s) */ + UndisclosedContainer = 6, /* container->cknown==0 */ +}; +/* this is similar to sortloot; the shop bill gets converted into an array of + struct sortbill_item so that sorting and traversal don't need to access + the original bill or even the shk; the array gets sorted by usedup vs + unpaid and by cost within each of those two categories */ +struct sortbill_item { + struct obj *obj; + long cost; /* full amount for current quantity, not per-unit amount */ + long quan; /* count for this entry; subset if this is partly used or + * partly intact */ + int bidx; /* index into ESHK(shkp)->bill_p[]; hero-owned container, + * which isn't in bill_p[], uses bidx == -1 */ + int8 usedup; /* billitem_status, small but needs to be signed for qsort() + * [for an earlier edition; 'signed' no longer necessary] */ + boolean queuedpay; /* buy without asking when containers are involved + * or purchase targets have been picked via menu */ +}; +typedef struct sortbill_item Bill; + +staticfn void makekops(coord *); +staticfn void getcad(struct monst *, const char *, coordxy, coordxy, boolean, + boolean, boolean); +staticfn void call_kops(struct monst *, boolean); +staticfn void kops_gone(boolean); #define NOTANGRY(mon) ((mon)->mpeaceful) #define ANGRY(mon) (!NOTANGRY(mon)) -#define IS_SHOP(x) (rooms[x].rtype >= SHOPBASE) +#define IS_SHOP(x) (svr.rooms[x].rtype >= SHOPBASE) -#define muteshk(shkp) \ - ((shkp)->msleeping || !(shkp)->mcanmove \ - || (shkp)->data->msound <= MS_ANIMAL) +#define muteshk(shkp) (helpless(shkp) || (shkp)->data->msound <= MS_ANIMAL) extern const struct shclass shtypes[]; /* defined in shknam.c */ -STATIC_VAR NEARDATA long int followmsg; /* last time of follow message */ -STATIC_VAR const char and_its_contents[] = " and its contents"; -STATIC_VAR const char the_contents_of[] = "the contents of "; - -STATIC_DCL void FDECL(append_honorific, (char *)); -STATIC_DCL long FDECL(addupbill, (struct monst *)); -STATIC_DCL void FDECL(pacify_shk, (struct monst *)); -STATIC_DCL struct bill_x *FDECL(onbill, (struct obj *, struct monst *, - BOOLEAN_P)); -STATIC_DCL struct monst *FDECL(next_shkp, (struct monst *, BOOLEAN_P)); -STATIC_DCL long FDECL(shop_debt, (struct eshk *)); -STATIC_DCL char *FDECL(shk_owns, (char *, struct obj *)); -STATIC_DCL char *FDECL(mon_owns, (char *, struct obj *)); -STATIC_DCL void FDECL(clear_unpaid_obj, (struct monst *, struct obj *)); -STATIC_DCL void FDECL(clear_unpaid, (struct monst *, struct obj *)); -STATIC_DCL long FDECL(check_credit, (long, struct monst *)); -STATIC_DCL void FDECL(pay, (long, struct monst *)); -STATIC_DCL long FDECL(get_cost, (struct obj *, struct monst *)); -STATIC_DCL long FDECL(set_cost, (struct obj *, struct monst *)); -STATIC_DCL const char *FDECL(shk_embellish, (struct obj *, long)); -STATIC_DCL long FDECL(cost_per_charge, (struct monst *, struct obj *, - BOOLEAN_P)); -STATIC_DCL long FDECL(cheapest_item, (struct monst *)); -STATIC_DCL int FDECL(dopayobj, (struct monst *, struct bill_x *, - struct obj **, int, BOOLEAN_P)); -STATIC_DCL long FDECL(stolen_container, (struct obj *, struct monst *, - long, BOOLEAN_P)); -STATIC_DCL long FDECL(getprice, (struct obj *, BOOLEAN_P)); -STATIC_DCL void FDECL(shk_names_obj, (struct monst *, struct obj *, - const char *, long, const char *)); -STATIC_DCL boolean FDECL(inherits, (struct monst *, int, int, BOOLEAN_P)); -STATIC_DCL void FDECL(set_repo_loc, (struct monst *)); -STATIC_DCL struct obj *FDECL(bp_to_obj, (struct bill_x *)); -STATIC_DCL long FDECL(get_pricing_units, (struct obj *)); -STATIC_DCL boolean NDECL(angry_shk_exists); -STATIC_DCL void FDECL(rile_shk, (struct monst *)); -STATIC_DCL void FDECL(rouse_shk, (struct monst *, BOOLEAN_P)); -STATIC_DCL void FDECL(remove_damage, (struct monst *, BOOLEAN_P)); -STATIC_DCL void FDECL(sub_one_frombill, (struct obj *, struct monst *)); -STATIC_DCL void FDECL(add_one_tobill, (struct obj *, BOOLEAN_P, - struct monst *)); -STATIC_DCL void FDECL(dropped_container, (struct obj *, struct monst *, - BOOLEAN_P)); -STATIC_DCL void FDECL(add_to_billobjs, (struct obj *)); -STATIC_DCL void FDECL(bill_box_content, (struct obj *, BOOLEAN_P, BOOLEAN_P, - struct monst *)); -STATIC_DCL boolean FDECL(rob_shop, (struct monst *)); -STATIC_DCL void FDECL(deserted_shop, (char *)); -STATIC_DCL boolean FDECL(special_stock, (struct obj *, struct monst *, - BOOLEAN_P)); -STATIC_DCL const char *FDECL(cad, (BOOLEAN_P)); +static const char and_its_contents[] = " and its contents"; +static const char the_contents_of[] = "the contents of "; + +staticfn void append_honorific(char *); +staticfn long addupbill(struct monst *); +staticfn void pacify_shk(struct monst *, boolean); +staticfn struct bill_x *onbill(struct obj *, struct monst *, boolean); +staticfn struct monst *next_shkp(struct monst *, boolean); +staticfn long shop_debt(struct eshk *); +staticfn char *shk_owns(char *, struct obj *); +staticfn char *mon_owns(char *, struct obj *); +staticfn void clear_unpaid_obj(struct monst *, struct obj *); +staticfn void clear_unpaid(struct monst *, struct obj *); +staticfn void clear_no_charge_obj(struct monst *, struct obj *); +staticfn void clear_no_charge(struct monst *, struct obj *); +staticfn void clear_no_charge_pets(struct monst *); +staticfn long check_credit(long, struct monst *); +staticfn void pay(long, struct monst *); +staticfn long get_cost(struct obj *, struct monst *); +staticfn long set_cost(struct obj *, struct monst *); +staticfn const char *shk_embellish(struct obj *, long); +staticfn long cost_per_charge(struct monst *, struct obj *, boolean); + +staticfn int QSORTCALLBACK sortbill_cmp(const genericptr, const genericptr) + NONNULLPTRS; +staticfn long cheapest_item(int, Bill *) NONNULLPTRS; +staticfn int make_itemized_bill(struct monst *shkp, Bill **ibill) NONNULLPTRS; +staticfn int menu_pick_pay_items(int, Bill *) NONNULLPTRS; +staticfn boolean pay_billed_items(struct monst *, int, Bill *, boolean, + boolean *) NONNULLPTRS; +staticfn void update_bill(int, int, Bill *, struct eshk *, struct bill_x *, + struct obj *) NONNULLPTRS; +staticfn int dopayobj(struct monst *, struct bill_x *, struct obj *, int, + boolean, boolean) NONNULLPTRS; +staticfn int buy_container(struct monst *, int, int, Bill *) NONNULLPTRS; +staticfn void reject_purchase(struct monst *, struct obj *, long) NONNULLPTRS; +staticfn boolean insufficient_funds(struct monst *, struct obj *, long) + NONNULLPTRS; +staticfn long stolen_container(struct obj *, struct monst *, long, boolean); +staticfn long corpsenm_price_adj(struct obj *); +staticfn long getprice(struct obj *, boolean); +staticfn void shk_names_obj(struct monst *, struct obj *, const char *, long, + const char *); +staticfn boolean inherits(struct monst *, int, int, boolean); +staticfn void set_repo_loc(struct monst *); +staticfn struct obj *bp_to_obj(struct bill_x *); +staticfn long get_pricing_units(struct obj *); +staticfn boolean angry_shk_exists(void); +staticfn void home_shk(struct monst *, boolean); +staticfn void rile_shk(struct monst *); +staticfn void rouse_shk(struct monst *, boolean); +staticfn boolean shk_impaired(struct monst *); +staticfn boolean repairable_damage(struct damage *, struct monst *); +staticfn struct damage *find_damage(struct monst *); +staticfn void discard_damage_struct(struct damage *); +staticfn void discard_damage_owned_by(struct monst *); +staticfn void shk_fixes_damage(struct monst *); +staticfn uint8 litter_getpos(uint8 *, coordxy, coordxy, struct monst *); +staticfn void litter_scatter(uint8 *, coordxy, coordxy, struct monst *); +staticfn void litter_newsyms(uint8 *, coordxy, coordxy); +staticfn int repair_damage(struct monst *, struct damage *, boolean); +staticfn void sub_one_frombill(struct obj *, struct monst *) NONNULLPTRS; +staticfn void add_one_tobill(struct obj *, boolean, struct monst *); +staticfn void dropped_container(struct obj *, struct monst *, boolean); +staticfn void add_to_billobjs(struct obj *); +staticfn void bill_box_content(struct obj *, boolean, boolean, + struct monst *); +staticfn boolean rob_shop(struct monst *); +staticfn void deserted_shop(char *); +staticfn boolean special_stock(struct obj *, struct monst *, boolean); +staticfn const char *cad(boolean); /* invariants: obj->unpaid iff onbill(obj) [unless bp->useup] obj->quan <= bp->bquan */ -static const char *angrytexts[] = { "quite upset", "ticked off", "furious" }; +static const char *const angrytexts[] = { + "quite upset", "ticked off", "furious" +}; /* * Transfer money from inventory to monster when paying @@ -98,18 +154,16 @@ static const char *angrytexts[] = { "quite upset", "ticked off", "furious" }; * if the monster kept the change. */ long -money2mon(mon, amount) -struct monst *mon; -long amount; +money2mon(struct monst *mon, long amount) { - struct obj *ygold = findgold(invent); + struct obj *ygold = findgold(gi.invent); if (amount <= 0) { impossible("%s payment in money2mon!", amount ? "negative" : "zero"); return 0L; } if (!ygold || ygold->quan < amount) { - impossible("Paying without %s money?", ygold ? "enough" : ""); + impossible("Paying without %s gold?", ygold ? "enough" : ""); return 0L; } @@ -119,7 +173,7 @@ long amount; remove_worn_item(ygold, FALSE); /* quiver */ freeinv(ygold); add_to_minv(mon, ygold); - context.botl = 1; + disp.botl = TRUE; return amount; } @@ -129,9 +183,7 @@ long amount; * the priest gives you money for an ale. */ void -money2u(mon, amount) -struct monst *mon; -long amount; +money2u(struct monst *mon, long amount) { struct obj *mongold = findgold(mon->minvent); @@ -140,7 +192,7 @@ long amount; return; } if (!mongold || mongold->quan < amount) { - impossible("%s paying without %s money?", a_monnam(mon), + impossible("%s paying without %s gold?", a_monnam(mon), mongold ? "enough" : ""); return; } @@ -149,19 +201,18 @@ long amount; mongold = splitobj(mongold, amount); obj_extract_self(mongold); - if (!merge_choice(invent, mongold) && inv_cnt(FALSE) >= 52) { - You("have no room for the money!"); + if (!merge_choice(gi.invent, mongold) + && inv_cnt(FALSE) >= invlet_basic) { + You("have no room for the gold!"); dropy(mongold); } else { addinv(mongold); - context.botl = 1; + disp.botl = TRUE; } } -STATIC_OVL struct monst * -next_shkp(shkp, withbill) -register struct monst *shkp; -register boolean withbill; +staticfn struct monst * +next_shkp(struct monst *shkp, boolean withbill) { for (; shkp; shkp = shkp->nmon) { if (DEADMONSTER(shkp)) @@ -171,10 +222,7 @@ register boolean withbill; } if (shkp) { - if (NOTANGRY(shkp)) { - if (ESHK(shkp)->surcharge) - pacify_shk(shkp); - } else { + if (ANGRY(shkp)) { if (!ESHK(shkp)->surcharge) rile_shk(shkp); } @@ -184,11 +232,10 @@ register boolean withbill; /* called in mon.c */ void -shkgone(mtmp) -struct monst *mtmp; +shkgone(struct monst *mtmp) { struct eshk *eshk = ESHK(mtmp); - struct mkroom *sroom = &rooms[eshk->shoproom - ROOMOFFSET]; + struct mkroom *sroom = &svr.rooms[eshk->shoproom - ROOMOFFSET]; struct obj *otmp; char *p; int sx, sy; @@ -196,21 +243,21 @@ struct monst *mtmp; /* [BUG: some of this should be done on the shop level */ /* even when the shk dies on a different level.] */ if (on_level(&eshk->shoplevel, &u.uz)) { - remove_damage(mtmp, TRUE); + discard_damage_owned_by(mtmp); sroom->resident = (struct monst *) 0; if (!search_special(ANY_SHOP)) - level.flags.has_shop = 0; + svl.level.flags.has_shop = 0; /* items on shop floor revert to ordinary objects */ for (sx = sroom->lx; sx <= sroom->hx; sx++) for (sy = sroom->ly; sy <= sroom->hy; sy++) - for (otmp = level.objects[sx][sy]; otmp; + for (otmp = svl.level.objects[sx][sy]; otmp; otmp = otmp->nexthere) otmp->no_charge = 0; /* Make sure bill is set only when the dead shk is the resident shk. */ - if ((p = index(u.ushops, eshk->shoproom)) != 0) { + if ((p = strchr(u.ushops, eshk->shoproom)) != 0) { setpaid(mtmp); eshk->bill_p = (struct bill_x *) 0; /* remove eshk->shoproom from u.ushops */ @@ -222,20 +269,17 @@ struct monst *mtmp; } void -set_residency(shkp, zero_out) -register struct monst *shkp; -register boolean zero_out; +set_residency(struct monst *shkp, boolean zero_out) { if (on_level(&(ESHK(shkp)->shoplevel), &u.uz)) - rooms[ESHK(shkp)->shoproom - ROOMOFFSET].resident = + svr.rooms[ESHK(shkp)->shoproom - ROOMOFFSET].resident = (zero_out) ? (struct monst *) 0 : shkp; } void -replshk(mtmp, mtmp2) -register struct monst *mtmp, *mtmp2; +replshk(struct monst *mtmp, struct monst *mtmp2) { - rooms[ESHK(mtmp2)->shoproom - ROOMOFFSET].resident = mtmp2; + svr.rooms[ESHK(mtmp2)->shoproom - ROOMOFFSET].resident = mtmp2; if (inhishop(mtmp) && *u.ushops == ESHK(mtmp)->shoproom) { ESHK(mtmp2)->bill_p = &(ESHK(mtmp2)->bill[0]); } @@ -243,9 +287,7 @@ register struct monst *mtmp, *mtmp2; /* do shopkeeper specific structure munging -dlc */ void -restshk(shkp, ghostly) -struct monst *shkp; -boolean ghostly; +restshk(struct monst *shkp, boolean ghostly) { if (u.uz.dlevel) { struct eshk *eshkp = ESHK(shkp); @@ -256,17 +298,15 @@ boolean ghostly; /* savebones guarantees that non-homed shk's will be gone */ if (ghostly) { assign_level(&eshkp->shoplevel, &u.uz); - if (ANGRY(shkp) && strncmpi(eshkp->customer, plname, PL_NSIZ)) - pacify_shk(shkp); + if (ANGRY(shkp) && strncmpi(eshkp->customer, svp.plname, PL_NSIZ)) + pacify_shk(shkp, TRUE); } } } -/* Clear the unpaid bit on a single object and its contents. */ -STATIC_OVL void -clear_unpaid_obj(shkp, otmp) -struct monst *shkp; -struct obj *otmp; +/* clear the unpaid bit on a single object and its contents */ +staticfn void +clear_unpaid_obj(struct monst *shkp, struct obj *otmp) { if (Has_contents(otmp)) clear_unpaid(shkp, otmp->cobj); @@ -274,11 +314,9 @@ struct obj *otmp; otmp->unpaid = 0; } -/* Clear the unpaid bit on all of the objects in the list. */ -STATIC_OVL void -clear_unpaid(shkp, list) -struct monst *shkp; -struct obj *list; +/* clear the unpaid bit on all of the objects in the list */ +staticfn void +clear_unpaid(struct monst *shkp, struct obj *list) { while (list) { clear_unpaid_obj(shkp, list); @@ -286,27 +324,104 @@ struct obj *list; } } +/* clear the no_charge bit on a single object and its contents */ +staticfn void +clear_no_charge_obj( + struct monst *shkp, /* if null, clear regardless of shop */ + struct obj *otmp) +{ + if (Has_contents(otmp)) + clear_no_charge(shkp, otmp->cobj); + if (otmp->no_charge) { + struct monst *rm_shkp; + int rno; + coordxy x, y; + + /* + * Clear no_charge if + * shkp is Null (clear all items on specified list) + * or not located somewhere that we expect no_charge (which is + * floor [of shop] or inside container [on shop floor]) + * or can't find object's map coordinates (should never happen + * for floor or contained; conceivable if on shop bill somehow + * but would have failed the floor-or-contained test since + * containers get emptied before going onto bill) + * or fails location sanity check (should always be good when + * location successfully found) + * or not inside any room + * or the room isn't a shop + * or the shop has no shopkeeper (deserted) + * or shopkeeper is the current one (to avoid clearing no_charge + * for items located in some rival's shop). + * + * no_charge items in a shop which is only temporarily deserted + * become owned by the shop now and will be for-sale once the shk + * returns. + */ + if (!shkp + || (otmp->where != OBJ_FLOOR + && otmp->where != OBJ_CONTAINED + && otmp->where != OBJ_BURIED) + || !get_obj_location(otmp, &x, &y, OBJ_CONTAINED | OBJ_BURIED) + || !isok(x, y) + || (rno = levl[x][y].roomno) < ROOMOFFSET + || !IS_SHOP(rno - ROOMOFFSET) + || (rm_shkp = svr.rooms[rno - ROOMOFFSET].resident) == 0 + || rm_shkp == shkp) + otmp->no_charge = 0; + } +} + +/* clear the no_charge bit on all of the objects in the list */ +staticfn void +clear_no_charge(struct monst *shkp, struct obj *list) +{ + while (list) { + /* handle first element of list and any contents it may have */ + clear_no_charge_obj(shkp, list); + /* move on to next element of list */ + list = list->nobj; + } +} + +/* clear no_charge from objects in pets' inventories belonging to shkp */ +staticfn void +clear_no_charge_pets(struct monst *shkp) +{ + struct monst *mtmp; + + for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) + if (mtmp->mtame && mtmp->minvent) + clear_no_charge(shkp, mtmp->minvent); +} + /* either you paid or left the shop or the shopkeeper died */ void -setpaid(shkp) -register struct monst *shkp; +setpaid(struct monst *shkp) { - register struct obj *obj; - register struct monst *mtmp; + struct obj *obj; + struct monst *mtmp; - clear_unpaid(shkp, invent); + clear_unpaid(shkp, gi.invent); clear_unpaid(shkp, fobj); - clear_unpaid(shkp, level.buriedobjlist); - if (thrownobj) - clear_unpaid_obj(shkp, thrownobj); - if (kickedobj) - clear_unpaid_obj(shkp, kickedobj); + if (svl.level.buriedobjlist) + clear_unpaid(shkp, svl.level.buriedobjlist); + if (gt.thrownobj) + clear_unpaid_obj(shkp, gt.thrownobj); + if (gk.kickedobj) + clear_unpaid_obj(shkp, gk.kickedobj); for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) - clear_unpaid(shkp, mtmp->minvent); - for (mtmp = migrating_mons; mtmp; mtmp = mtmp->nmon) - clear_unpaid(shkp, mtmp->minvent); + if (mtmp->minvent) + clear_unpaid(shkp, mtmp->minvent); + for (mtmp = gm.migrating_mons; mtmp; mtmp = mtmp->nmon) + if (mtmp->minvent) + clear_unpaid(shkp, mtmp->minvent); - while ((obj = billobjs) != 0) { + /* clear obj->no_charge for all obj in shkp's shop */ + clear_no_charge(shkp, fobj); + clear_no_charge(shkp, svl.level.buriedobjlist); + + while ((obj = gb.billobjs) != 0) { obj_extract_self(obj); dealloc_obj(obj); } @@ -318,13 +433,71 @@ register struct monst *shkp; } } -STATIC_OVL long -addupbill(shkp) -register struct monst *shkp; +/* Remembers that a shopkeeper has quoted a particular price for a + particular type of object. */ +void +record_price_quote(int otyp, unsigned long price, boolean buyprice) { + struct objclass *oc = &objects[otyp]; + if (buyprice) { + if (price > oc->oc_buy_maxseen) oc->oc_buy_maxseen = price; + if (price < oc->oc_buy_minseen) oc->oc_buy_minseen = price; + } else { + if (price > oc->oc_sell_maxseen) oc->oc_sell_maxseen = price; + if (price < oc->oc_sell_minseen) oc->oc_sell_minseen = price; + } +} + +/* Appends price-quote information to the given buffer, updating the + given end of string position. *eos mut be buf + strlen(buf). If the + update would make bug longer than BUFSZ, instead does nothing. */ +void +append_price_quote(char *buf, char **eos, int otyp) { + char buf2[BUFSZ]; + char *eos2 = buf2; + const char *sep = ""; + size_t len = *eos - buf; + size_t len2; + + if (objects[otyp].oc_sell_minseen > objects[otyp].oc_sell_maxseen && + objects[otyp].oc_buy_minseen > objects[otyp].oc_buy_maxseen) + return; + + eos2 += sprintf(eos2, " {"); + + if (objects[otyp].oc_buy_minseen < objects[otyp].oc_buy_maxseen) { + eos2 += sprintf(eos2, "buy %lu-%lu", + objects[otyp].oc_buy_minseen, + objects[otyp].oc_buy_maxseen); + sep = " "; + } else if (objects[otyp].oc_buy_minseen == objects[otyp].oc_buy_maxseen) { + eos2 += sprintf(eos2, "buy %lu", + objects[otyp].oc_buy_minseen); + sep = " "; + } + + if (objects[otyp].oc_sell_minseen < objects[otyp].oc_sell_maxseen) { + eos2 += sprintf(eos2, "%ssell %lu-%lu", sep, + objects[otyp].oc_sell_minseen, + objects[otyp].oc_sell_maxseen); + } else if (objects[otyp].oc_sell_minseen == objects[otyp].oc_sell_maxseen) { + eos2 += sprintf(eos2, "%ssell %lu", sep, + objects[otyp].oc_sell_minseen); + } + + eos2 += sprintf(eos2, "}"); + len2 = eos2 - buf2; + if (len2 < BUFSZ - len - 1) { + Strcpy(*eos, buf2); + *eos += len2; + } +} + +staticfn long +addupbill(struct monst *shkp) { - register int ct = ESHK(shkp)->billct; - register struct bill_x *bp = ESHK(shkp)->bill_p; - register long total = 0L; + int ct = ESHK(shkp)->billct; + struct bill_x *bp = ESHK(shkp)->bill_p; + long total = 0L; while (ct--) { total += bp->price * bp->bquan; @@ -333,24 +506,23 @@ register struct monst *shkp; return total; } -STATIC_OVL void -call_kops(shkp, nearshop) -register struct monst *shkp; -register boolean nearshop; +staticfn void +call_kops(struct monst *shkp, boolean nearshop) { /* Keystone Kops srt@ucla */ - register boolean nokops; + boolean nokops; if (!shkp) return; + Soundeffect(se_alarm, 80); if (!Deaf) pline("An alarm sounds!"); - nokops = ((mvitals[PM_KEYSTONE_KOP].mvflags & G_GONE) - && (mvitals[PM_KOP_SERGEANT].mvflags & G_GONE) - && (mvitals[PM_KOP_LIEUTENANT].mvflags & G_GONE) - && (mvitals[PM_KOP_KAPTAIN].mvflags & G_GONE)); + nokops = ((svm.mvitals[PM_KEYSTONE_KOP].mvflags & G_GONE) + && (svm.mvitals[PM_KOP_SERGEANT].mvflags & G_GONE) + && (svm.mvitals[PM_KOP_LIEUTENANT].mvflags & G_GONE) + && (svm.mvitals[PM_KOP_KAPTAIN].mvflags & G_GONE)); if (!angry_guards(!!Deaf) && nokops) { if (flags.verbose && !Deaf) @@ -363,6 +535,9 @@ register boolean nearshop; { coord mm; + coordxy sx = 0, sy = 0; + + choose_stairs(&sx, &sy, TRUE); if (nearshop) { /* Create swarm around you, if you merely "stepped out" */ @@ -376,9 +551,11 @@ register boolean nearshop; if (flags.verbose) pline_The("Keystone Kops are after you!"); /* Create swarm near down staircase (hinders return to level) */ - mm.x = xdnstair; - mm.y = ydnstair; - makekops(&mm); + if (isok(sx, sy)) { + mm.x = sx; + mm.y = sy; + makekops(&mm); + } /* Create swarm near shopkeeper (hinders return to shop) */ mm.x = shkp->mx; mm.y = shkp->my; @@ -388,10 +565,9 @@ register boolean nearshop; /* x,y is strictly inside shop */ char -inside_shop(x, y) -register xchar x, y; +inside_shop(coordxy x, coordxy y) { - register char rno; + char rno; rno = levl[x][y].roomno; if ((rno < ROOMOFFSET) || levl[x][y].edge || !IS_SHOP(rno - ROOMOFFSET)) @@ -400,9 +576,7 @@ register xchar x, y; } void -u_left_shop(leavestring, newlev) -char *leavestring; -boolean newlev; +u_left_shop(char *leavestring, boolean newlev) { struct monst *shkp; struct eshk *eshkp; @@ -417,7 +591,7 @@ boolean newlev; if (!*leavestring && (!levl[u.ux][u.uy].edge || levl[u.ux0][u.uy0].edge)) return; - shkp = shop_keeper(*u.ushops0); + shkp = shop_keeper(*leavestring ? *leavestring : *u.ushops0); if (!shkp || !inhishop(shkp)) return; /* shk died, teleported, changed levels... */ @@ -430,15 +604,18 @@ boolean newlev; * Player just stepped onto shop-boundary (known from above logic). * Try to intimidate him into paying his bill */ - if (!Deaf && !muteshk(shkp)) - verbalize(NOTANGRY(shkp) ? "%s! Please pay before leaving." - : "%s! Don't you leave without paying!", - plname); - else + boolean not_upset = !eshkp->surcharge; + if (!Deaf && !muteshk(shkp)) { + SetVoice(shkp, 0, 80, 0); + verbalize(not_upset ? "%s! Please pay before leaving." + : "%s! Don't you leave without paying!", + svp.plname); + } else { pline("%s %s that you need to pay before leaving%s", Shknam(shkp), - NOTANGRY(shkp) ? "points out" : "makes it clear", - NOTANGRY(shkp) ? "." : "!"); + not_upset ? "points out" : "makes it clear", + not_upset ? "." : "!"); + } return; } @@ -447,10 +624,45 @@ boolean newlev; } } +void +credit_report(struct monst *shkp, int idx, boolean silent) +{ + struct eshk *eshkp = ESHK(shkp); + static long credit_snap[2][3] = {{0L, 0L, 0L}, {0L, 0L, 0L}}; + + if (!idx) { + credit_snap[BEFORE][0] = credit_snap[NOW][0] = 0L; + credit_snap[BEFORE][1] = credit_snap[NOW][1] = 0L; + credit_snap[BEFORE][2] = credit_snap[NOW][2] = 0L; + } else { + idx = 1; + } + + credit_snap[idx][0] = eshkp->credit; + credit_snap[idx][1] = eshkp->debit; + credit_snap[idx][2] = eshkp->loan; + + if (idx && !silent) { + long amt = 0L; + const char *msg = "debt has increased"; + + if (credit_snap[NOW][0] < credit_snap[BEFORE][0]) { + amt = credit_snap[BEFORE][0] - credit_snap[NOW][0]; + msg = "credit has been reduced"; + } else if (credit_snap[NOW][1] > credit_snap[BEFORE][1]) { + amt = credit_snap[NOW][1] - credit_snap[BEFORE][1]; + } else if (credit_snap[NOW][2] > credit_snap[BEFORE][2]) { + amt = credit_snap[NOW][2] - credit_snap[BEFORE][2]; + } + if (amt) + Your("%s by %ld %s.", msg, amt, currency(amt)); + + } +} + /* robbery from outside the shop via telekinesis or grappling hook */ void -remote_burglary(x, y) -xchar x, y; +remote_burglary(coordxy x, coordxy y) { struct monst *shkp; struct eshk *eshkp; @@ -471,9 +683,8 @@ xchar x, y; /* shop merchandise has been taken; pay for it with any credit available; return false if the debt is fully covered by credit, true otherwise */ -STATIC_OVL boolean -rob_shop(shkp) -struct monst *shkp; +staticfn boolean +rob_shop(struct monst *shkp) { struct eshk *eshkp; long total; @@ -496,6 +707,10 @@ struct monst *shkp; /* by this point, we know an actual robbery has taken place */ eshkp->robbed += total; You("stole %ld %s worth of merchandise.", total, currency(total)); + livelog_printf(LL_ACHIEVE, "stole %ld %s worth of merchandise from %s %s", + total, currency(total), s_suffix(shkname(shkp)), + shtypes[eshkp->shoptype - SHOPBASE].name); + if (!Role_if(PM_ROGUE)) /* stealing is unlawful */ adjalign(-sgn(u.ualign.type)); @@ -504,17 +719,16 @@ struct monst *shkp; } /* give a message when entering an untended shop (caller has verified that) */ -STATIC_OVL void -deserted_shop(enterstring) -/*const*/ char *enterstring; +staticfn void +deserted_shop(/*const*/ char *enterstring) { struct monst *mtmp; - struct mkroom *r = &rooms[(int) *enterstring - ROOMOFFSET]; + struct mkroom *r = &svr.rooms[(int) *enterstring - ROOMOFFSET]; int x, y, m = 0, n = 0; for (x = r->lx; x <= r->hx; ++x) for (y = r->ly; y <= r->hy; ++y) { - if (x == u.ux && y == u.uy) + if (u_at(x, y)) continue; if ((mtmp = m_at(x, y)) != 0) { ++n; @@ -532,22 +746,23 @@ deserted_shop(enterstring) !n ? "deserted" : "untended"); } +/* called from check_special_room(hack.c) */ void -u_entered_shop(enterstring) -char *enterstring; +u_entered_shop(char *enterstring) { - register int rt; - register struct monst *shkp; - register struct eshk *eshkp; static char empty_shops[5]; + struct monst *shkp; + struct eshk *eshkp; + int rt; if (!*enterstring) return; - if (!(shkp = shop_keeper(*enterstring))) { - if (!index(empty_shops, *enterstring) - && in_rooms(u.ux, u.uy, SHOPBASE) - != in_rooms(u.ux0, u.uy0, SHOPBASE)) + shkp = shop_keeper(*enterstring); + if (!shkp) { + if (!strchr(empty_shops, *enterstring) + && (in_rooms(u.ux, u.uy, SHOPBASE) + != in_rooms(u.ux0, u.uy0, SHOPBASE))) deserted_shop(enterstring); Strcpy(empty_shops, u.ushops); u.ushops[0] = '\0'; @@ -559,22 +774,23 @@ char *enterstring; if (!inhishop(shkp)) { /* dump core when referenced */ eshkp->bill_p = (struct bill_x *) -1000; - if (!index(empty_shops, *enterstring)) + if (!strchr(empty_shops, *enterstring)) deserted_shop(enterstring); Strcpy(empty_shops, u.ushops); u.ushops[0] = '\0'; return; } + record_achievement(ACH_SHOP); eshkp->bill_p = &(eshkp->bill[0]); if ((!eshkp->visitct || *eshkp->customer) - && strncmpi(eshkp->customer, plname, PL_NSIZ)) { + && strncmpi(eshkp->customer, svp.plname, PL_NSIZ)) { /* You seem to be new here */ eshkp->visitct = 0; eshkp->following = 0; - (void) strncpy(eshkp->customer, plname, PL_NSIZ); - pacify_shk(shkp); + (void) strncpy(eshkp->customer, svp.plname, PL_NSIZ); + pacify_shk(shkp, TRUE); } if (muteshk(shkp) || eshkp->following) @@ -582,45 +798,62 @@ char *enterstring; if (Invis) { pline("%s senses your presence.", Shknam(shkp)); - if (!Deaf && !muteshk(shkp)) + if (!Deaf && !muteshk(shkp)) { + SetVoice(shkp, 0, 80, 0); verbalize("Invisible customers are not welcome!"); - else + } else { pline("%s stands firm as if %s knows you are there.", Shknam(shkp), noit_mhe(shkp)); + } return; } - rt = rooms[*enterstring - ROOMOFFSET].rtype; + rt = svr.rooms[*enterstring - ROOMOFFSET].rtype; if (ANGRY(shkp)) { - if (!Deaf && !muteshk(shkp)) - verbalize("So, %s, you dare return to %s %s?!", plname, + if (!Deaf && !muteshk(shkp)) { + SetVoice(shkp, 0, 80, 0); + verbalize("So, %s, you dare return to %s %s?!", svp.plname, s_suffix(shkname(shkp)), shtypes[rt - SHOPBASE].name); - else + } else { pline("%s seems %s over your return to %s %s!", - Shknam(shkp), angrytexts[rn2(SIZE(angrytexts))], + Shknam(shkp), ROLL_FROM(angrytexts), noit_mhis(shkp), shtypes[rt - SHOPBASE].name); + } + } else if (eshkp->surcharge) { + if (!Deaf && !muteshk(shkp)) { + SetVoice(shkp, 0, 80, 0); + verbalize("Back again, %s? I've got my %s on you.", + svp.plname, mbodypart(shkp, EYE)); + } else { + pline_The("atmosphere at %s %s seems unwelcoming.", + s_suffix(shkname(shkp)), shtypes[rt - SHOPBASE].name); + } } else if (eshkp->robbed) { - if (!Deaf) + if (!Deaf) { + Soundeffect(se_mutter_imprecations, 50); pline("%s mutters imprecations against shoplifters.", Shknam(shkp)); - else + } else { pline("%s is combing through %s inventory list.", Shknam(shkp), noit_mhis(shkp)); + } } else { - if (!Deaf && !muteshk(shkp)) - verbalize("%s, %s! Welcome%s to %s %s!", Hello(shkp), plname, + if (!Deaf && !muteshk(shkp)) { + set_voice(shkp, 0, 80, 0); + verbalize("%s, %s! Welcome%s to %s %s!", Hello(shkp), svp.plname, eshkp->visitct++ ? " again" : "", s_suffix(shkname(shkp)), shtypes[rt - SHOPBASE].name); - else + } else { You("enter %s %s%s!", s_suffix(shkname(shkp)), shtypes[rt - SHOPBASE].name, eshkp->visitct++ ? " again" : ""); + } } /* can't do anything about blocking if teleported in */ if (!inside_shop(u.ux, u.uy)) { - boolean should_block; + boolean should_block, not_upset = !eshkp->surcharge; int cnt; const char *tool; struct obj *pick = carrying(PICK_AXE), @@ -646,27 +879,31 @@ char *enterstring; if (!Blind) makeknown(DWARVISH_MATTOCK); } - if (!Deaf && !muteshk(shkp)) - verbalize(NOTANGRY(shkp) + if (!Deaf && !muteshk(shkp)) { + SetVoice(shkp, 0, 80, 0); + verbalize(not_upset ? "Will you please leave your %s%s outside?" : "Leave the %s%s outside.", tool, plur(cnt)); - else + } else { pline("%s %s to let you in with your %s%s.", Shknam(shkp), - NOTANGRY(shkp) ? "is hesitant" : "refuses", + not_upset ? "is hesitant" : "refuses", tool, plur(cnt)); + } should_block = TRUE; } else if (u.usteed) { - if (!Deaf && !muteshk(shkp)) - verbalize(NOTANGRY(shkp) ? "Will you please leave %s outside?" - : "Leave %s outside.", + if (!Deaf && !muteshk(shkp)) { + SetVoice(shkp, 0, 80, 0); + verbalize(not_upset ? "Will you please leave %s outside?" + : "Leave %s outside.", y_monnam(u.usteed)); - else + } else { pline("%s %s to let you in while you're riding %s.", Shknam(shkp), - NOTANGRY(shkp) ? "doesn't want" : "refuses", + not_upset ? "doesn't want" : "refuses", y_monnam(u.usteed)); + } should_block = TRUE; } else { should_block = @@ -681,8 +918,7 @@ char *enterstring; /* called when removing a pick-axe or mattock from a container */ void -pick_pick(obj) -struct obj *obj; +pick_pick(struct obj *obj) { struct monst *shkp; @@ -694,30 +930,31 @@ struct obj *obj; /* if you bring a sack of N picks into a shop to sell, don't repeat this N times when they're taken out */ - if (moves != pickmovetime) { - if (!Deaf && !muteshk(shkp)) + if (svm.moves != pickmovetime) { + if (!Deaf && !muteshk(shkp)) { + SetVoice(shkp, 0, 80, 0); verbalize("You sneaky %s! Get out of here with that pick!", cad(FALSE)); - else + } else { pline("%s %s your pick!", Shknam(shkp), haseyes(shkp->data) ? "glares at" : "is dismayed because of"); + } } - pickmovetime = moves; + pickmovetime = svm.moves; } } /* - Decide whether two unpaid items are mergable; caller is responsible for + Decide whether two unpaid items are mergeable; caller is responsible for making sure they're unpaid and the same type of object; we check the price quoted by the shopkeeper and also that they both belong to the same shk. */ boolean -same_price(obj1, obj2) -struct obj *obj1, *obj2; +same_price(struct obj *obj1, struct obj *obj2) { - register struct monst *shkp1, *shkp2; + struct monst *shkp1, *shkp2; struct bill_x *bp1 = 0, *bp2 = 0; boolean are_mergable = FALSE; @@ -749,9 +986,8 @@ struct obj *obj1, *obj2; * turning the `$' command into a way to discover that the current * level is bones data which has a shk on the warpath. */ -STATIC_OVL long -shop_debt(eshkp) -struct eshk *eshkp; +staticfn long +shop_debt(struct eshk *eshkp) { struct bill_x *bp; int ct; @@ -764,7 +1000,7 @@ struct eshk *eshkp; /* called in response to the `$' command */ void -shopper_financial_report() +shopper_financial_report(void) { struct monst *shkp, *this_shkp = shop_keeper(inside_shop(u.ux, u.uy)); struct eshk *eshkp; @@ -794,33 +1030,33 @@ shopper_financial_report() if ((amt = shop_debt(eshkp)) != 0) You("owe %s %ld %s.", shkname(shkp), amt, currency(amt)); else if (shkp == this_shkp) - You("don't owe any money here."); + You("don't owe any gold here."); } } +/* 1: shopkeeper is currently in her shop or its boundary; 0: not */ int -inhishop(mtmp) -register struct monst *mtmp; +inhishop(struct monst *shkp) { - struct eshk *eshkp = ESHK(mtmp); + char *shkrooms; + struct eshk *eshkp = ESHK(shkp); - return (index(in_rooms(mtmp->mx, mtmp->my, SHOPBASE), eshkp->shoproom) - && on_level(&eshkp->shoplevel, &u.uz)); + if (!on_level(&eshkp->shoplevel, &u.uz)) + return FALSE; + shkrooms = in_rooms(shkp->mx, shkp->my, SHOPBASE); + return (strchr(shkrooms, eshkp->shoproom) != 0); } +/* return the shopkeeper for rooms[rmno-2]; returns Null if there isn't one */ struct monst * -shop_keeper(rmno) -char rmno; +shop_keeper(char rmno) { struct monst *shkp; - shkp = (rmno >= ROOMOFFSET) ? rooms[rmno - ROOMOFFSET].resident : 0; + shkp = (rmno >= ROOMOFFSET) ? svr.rooms[rmno - ROOMOFFSET].resident : 0; if (shkp) { if (has_eshk(shkp)) { - if (NOTANGRY(shkp)) { - if (ESHK(shkp)->surcharge) - pacify_shk(shkp); - } else { + if (ANGRY(shkp)) { if (!ESHK(shkp)->surcharge) rile_shk(shkp); } @@ -830,54 +1066,105 @@ char rmno; shkp->isshk ? "shopkeeper career change" : "shop resident not shopkeeper", (int) rmno, - (int) rooms[rmno - ROOMOFFSET].rtype, + (int) svr.rooms[rmno - ROOMOFFSET].rtype, shkp->mnum, - /* [real shopkeeper name is kept in ESHK, not MNAME] */ - has_mname(shkp) ? MNAME(shkp) : "anonymous"); + /* [real shopkeeper name is kept in ESHK, + not MGIVENNAME] */ + has_mgivenname(shkp) ? MGIVENNAME(shkp) : "anonymous"); /* not sure if this is appropriate, because it does nothing to - correct the underlying rooms[].resident issue but... */ + correct the underlying svr.rooms[].resident issue but... */ return (struct monst *) 0; } } return shkp; } +/* find the shopkeeper who owns 'obj'; needed to handle shared shop walls */ +struct monst * +find_objowner( + struct obj *obj, + coordxy x, coordxy y) /* caller passes obj's location since obj->ox,oy + * might be stale; don't update coordinates here + * because if we're called during sanity checking + * they shouldn't be modified */ +{ + struct monst *shkp, *deflt_shkp = 0; + + if (obj->where == OBJ_ONBILL) { + /* used up item; bill obj coordinates are useless and so are x,y */ + for (shkp = next_shkp(fmon, TRUE); shkp; + shkp = next_shkp(shkp->nmon, TRUE)) + if (onshopbill(obj, shkp, TRUE)) + return shkp; + } else { + char *roomindx, *where = in_rooms(x, y, SHOPBASE); + + /* conceptually object could be inside up to 4 rooms simultaneously; + in practice it will usually be one room but can sometimes be two; + check shk and bill for each room rather than just the first; + fallback to the first shk if obj isn't on the relevant bill(s) */ + for (roomindx = where; *roomindx; ++roomindx) + if ((shkp = shop_keeper(*roomindx)) != 0) { + if (onshopbill(obj, shkp, TRUE)) + return shkp; + if (!deflt_shkp) + deflt_shkp = shkp; + } + } + return deflt_shkp; +} + boolean -tended_shop(sroom) -struct mkroom *sroom; +tended_shop(struct mkroom *sroom) { struct monst *mtmp = sroom->resident; return !mtmp ? FALSE : (boolean) inhishop(mtmp); } -STATIC_OVL struct bill_x * -onbill(obj, shkp, silent) -struct obj *obj; -struct monst *shkp; -boolean silent; +void +noisy_shop(struct mkroom *sroom) +{ + struct monst *mtmp = sroom->resident; + + if (mtmp && inhishop(mtmp)) { + wake_nearto(mtmp->mx, mtmp->my, 11 * 11); + } +} + +staticfn struct bill_x * +onbill(struct obj *obj, struct monst *shkp, boolean silent) { if (shkp) { - register struct bill_x *bp = ESHK(shkp)->bill_p; - register int ct = ESHK(shkp)->billct; + struct bill_x *bp; + int ct; - while (--ct >= 0) + for (ct = ESHK(shkp)->billct, bp = ESHK(shkp)->bill_p; + ct > 0; --ct, ++bp) { if (bp->bo_id == obj->o_id) { if (!obj->unpaid) - pline("onbill: paid obj on bill?"); + impossible("onbill: paid obj on bill?"); return bp; - } else - bp++; + } + } } if (obj->unpaid && !silent) - pline("onbill: unpaid obj not on bill?"); + impossible("onbill: unpaid obj %s?", + !shkp ? "without shopkeeper" : "not on shk's bill"); return (struct bill_x *) 0; } +/* used outside of shk.c when caller wants to know whether item is on bill + but doesn't need to know any details about the bill itself */ +boolean +onshopbill(struct obj *obj, struct monst *shkp, boolean silent) +{ + return onbill(obj, shkp, silent) ? TRUE : FALSE; +} + /* check whether an object or any of its contents belongs to a shop */ boolean -is_unpaid(obj) -struct obj *obj; +is_unpaid(struct obj *obj) { return (boolean) (obj->unpaid || (Has_contents(obj) && count_unpaid(obj->cobj))); @@ -885,10 +1172,9 @@ struct obj *obj; /* Delete the contents of the given object. */ void -delete_contents(obj) -register struct obj *obj; +delete_contents(struct obj *obj) { - register struct obj *curr; + struct obj *curr; while ((curr = obj->cobj) != 0) { obj_extract_self(curr); @@ -898,12 +1184,11 @@ register struct obj *obj; /* called with two args on merge */ void -obfree(obj, merge) -register struct obj *obj, *merge; +obfree(struct obj *obj, struct obj *merge) { - register struct bill_x *bp; - register struct bill_x *bpm; - register struct monst *shkp; + struct bill_x *bp; + struct bill_x *bpm; + struct monst *shkp; if (obj->otyp == LEASH && obj->leashmon) o_unleash(obj); @@ -915,6 +1200,8 @@ register struct obj *obj, *merge; delete_contents(obj); if (Is_container(obj)) maybe_reset_pick(obj); + if (obj->otyp == BOULDER) + obj->next_boulder = 0; shkp = 0; if (obj->unpaid) { @@ -936,8 +1223,12 @@ register struct obj *obj, *merge; if ((bp = onbill(obj, shkp, FALSE)) != 0) { if (!merge) { - bp->useup = 1; + bp->useup = TRUE; obj->unpaid = 0; /* only for doinvbill */ + /* for used up glob, put back original weight in case it gets + formatted ('I x' or itemized billing) with 'wizweight' On */ + if (obj->globby && !obj->owt && has_omid(obj)) + obj->owt = OMID(obj); add_to_billobjs(obj); return; } @@ -945,26 +1236,20 @@ register struct obj *obj, *merge; if (!bpm) { /* this used to be a rename */ /* !merge already returned */ - impossible("obfree: not on bill, %s = (%d,%d,%ld,%d) (%d,%d,%ld,%d)??", - "otyp,where,quan,unpaid", - obj->otyp, obj->where, obj->quan, obj->unpaid ? 1 : 0, - merge->otyp, merge->where, merge->quan, - merge->unpaid ? 1 : 0); + impossible( + "obfree: not on bill, %s = (%d,%d,%ld,%d) (%d,%d,%ld,%d)?", + "otyp,where,quan,unpaid", + obj->otyp, obj->where, obj->quan, obj->unpaid ? 1 : 0, + merge->otyp, merge->where, merge->quan, + merge->unpaid ? 1 : 0); return; } else { + struct eshk *eshkp = ESHK(shkp); + /* this was a merger */ bpm->bquan += bp->bquan; - ESHK(shkp)->billct--; -#ifdef DUMB - { - /* DRS/NS 2.2.6 messes up -- Peter Kendell */ - int indx = ESHK(shkp)->billct; - - *bp = ESHK(shkp)->bill_p[indx]; - } -#else - *bp = ESHK(shkp)->bill_p[ESHK(shkp)->billct]; -#endif + eshkp->billct--; + *bp = eshkp->bill_p[eshkp->billct]; } } else { /* not on bill; if the item is being merged away rather than @@ -989,10 +1274,8 @@ register struct obj *obj, *merge; dealloc_obj(obj); } -STATIC_OVL long -check_credit(tmp, shkp) -long tmp; -register struct monst *shkp; +staticfn long +check_credit(long tmp, struct monst *shkp) { long credit = ESHK(shkp)->credit; @@ -1010,10 +1293,8 @@ register struct monst *shkp; return tmp; } -STATIC_OVL void -pay(tmp, shkp) -long tmp; -register struct monst *shkp; +staticfn void +pay(long tmp, struct monst *shkp) { long robbed = ESHK(shkp)->robbed; long balance = ((tmp <= 0L) ? tmp : check_credit(tmp, shkp)); @@ -1022,7 +1303,7 @@ register struct monst *shkp; money2mon(shkp, balance); else if (balance < 0) money2u(shkp, -balance); - context.botl = 1; + disp.botl = TRUE; if (robbed) { robbed -= tmp; if (robbed < 0) @@ -1032,15 +1313,13 @@ register struct monst *shkp; } /* return shkp to home position */ -void -home_shk(shkp, killkops) -register struct monst *shkp; -register boolean killkops; +staticfn void +home_shk(struct monst *shkp, boolean killkops) { - register xchar x = ESHK(shkp)->shk.x, y = ESHK(shkp)->shk.y; + coordxy x = ESHK(shkp)->shk.x, y = ESHK(shkp)->shk.y; - (void) mnearto(shkp, x, y, TRUE); - level.flags.has_shop = 1; + (void) mnearto(shkp, x, y, TRUE, RLOC_NOMSG); + svl.level.flags.has_shop = 1; if (killkops) { kops_gone(TRUE); pacify_guards(); @@ -1048,10 +1327,10 @@ register boolean killkops; after_shk_move(shkp); } -STATIC_OVL boolean -angry_shk_exists() +staticfn boolean +angry_shk_exists(void) { - register struct monst *shkp; + struct monst *shkp; for (shkp = next_shkp(fmon, FALSE); shkp; shkp = next_shkp(shkp->nmon, FALSE)) @@ -1061,18 +1340,17 @@ angry_shk_exists() } /* remove previously applied surcharge from all billed items */ -STATIC_OVL void -pacify_shk(shkp) -register struct monst *shkp; +staticfn void +pacify_shk(struct monst *shkp, boolean clear_surcharge) { NOTANGRY(shkp) = TRUE; /* make peaceful */ - if (ESHK(shkp)->surcharge) { - register struct bill_x *bp = ESHK(shkp)->bill_p; - register int ct = ESHK(shkp)->billct; + if (clear_surcharge && ESHK(shkp)->surcharge) { + struct bill_x *bp = ESHK(shkp)->bill_p; + int ct = ESHK(shkp)->billct; ESHK(shkp)->surcharge = FALSE; while (ct-- > 0) { - register long reduction = (bp->price + 3L) / 4L; + long reduction = (bp->price + 3L) / 4L; bp->price -= reduction; /* undo 33% increase */ bp++; } @@ -1080,18 +1358,18 @@ register struct monst *shkp; } /* add aggravation surcharge to all billed items */ -STATIC_OVL void -rile_shk(shkp) -register struct monst *shkp; +staticfn void +rile_shk(struct monst *shkp) { NOTANGRY(shkp) = FALSE; /* make angry */ if (!ESHK(shkp)->surcharge) { - register struct bill_x *bp = ESHK(shkp)->bill_p; - register int ct = ESHK(shkp)->billct; + long surcharge; + struct bill_x *bp = ESHK(shkp)->bill_p; + int ct = ESHK(shkp)->billct; ESHK(shkp)->surcharge = TRUE; while (ct-- > 0) { - register long surcharge = (bp->price + 2L) / 3L; + surcharge = (bp->price + 2L) / 3L; bp->price += surcharge; bp++; } @@ -1099,12 +1377,10 @@ register struct monst *shkp; } /* wakeup and/or unparalyze shopkeeper */ -STATIC_OVL void -rouse_shk(shkp, verbosely) -struct monst *shkp; -boolean verbosely; +staticfn void +rouse_shk(struct monst *shkp, boolean verbosely) { - if (!shkp->mcanmove || shkp->msleeping) { + if (helpless(shkp)) { /* greed induced recovery... */ if (verbosely && canspotmon(shkp)) pline("%s %s.", Shknam(shkp), @@ -1116,14 +1392,12 @@ boolean verbosely; } void -make_happy_shk(shkp, silentkops) -register struct monst *shkp; -register boolean silentkops; +make_happy_shk(struct monst *shkp, boolean silentkops) { boolean wasmad = ANGRY(shkp); struct eshk *eshkp = ESHK(shkp); - pacify_shk(shkp); + pacify_shk(shkp, FALSE); eshkp->following = 0; eshkp->robbed = 0L; if (!Role_if(PM_ROGUE)) @@ -1135,9 +1409,11 @@ register boolean silentkops; Strcpy(shk_nam, shkname(shkp)); if (on_level(&eshkp->shoplevel, &u.uz)) { home_shk(shkp, FALSE); - /* didn't disappear if shk can still be seen */ - if (canseemon(shkp)) - vanished = FALSE; + if (canspotmon(shkp)) { + pline("%s returns to %s shop.", Shknam(shkp), + noit_mhis(shkp)); + vanished = FALSE; /* don't give 'Shk disappears' message */ + } } else { /* if sensed, does disappear regardless whether seen */ if (sensemon(shkp)) @@ -1161,8 +1437,7 @@ register boolean silentkops; /* called by make_happy_shk() and also by losedogs() for migrating shk */ void -make_happy_shoppers(silentkops) -boolean silentkops; +make_happy_shoppers(boolean silentkops) { if (!angry_shk_exists()) { kops_gone(silentkops); @@ -1171,15 +1446,20 @@ boolean silentkops; } void -hot_pursuit(shkp) -register struct monst *shkp; +hot_pursuit(struct monst *shkp) { if (!shkp->isshk) return; rile_shk(shkp); - (void) strncpy(ESHK(shkp)->customer, plname, PL_NSIZ); + (void) strncpy(ESHK(shkp)->customer, svp.plname, PL_NSIZ); ESHK(shkp)->following = 1; + + /* shopkeeper networking: clear obj->no_charge for all obj on the + floor of this level (including inside containers on floor), even + those that are in other shopkeepers' shops */ + clear_no_charge((struct monst *) NULL, fobj); + clear_no_charge_pets(shkp); } /* Used when the shkp is teleported or falls (ox == 0) out of his shop, or @@ -1187,10 +1467,10 @@ register struct monst *shkp; the shop. These conditions must be checked by the calling function. */ /*ARGSUSED*/ void -make_angry_shk(shkp, ox, oy) -struct monst *shkp; -xchar ox UNUSED; /* predate 'noit_Monnam()', let alone Shknam() */ -xchar oy UNUSED; +make_angry_shk( + struct monst *shkp, + coordxy ox UNUSED, coordxy oy UNUSED) + /* predate 'noit_Monnam()', let alone Shknam() */ { struct eshk *eshkp = ESHK(shkp); @@ -1208,39 +1488,271 @@ xchar oy UNUSED; hot_pursuit(shkp); } -STATIC_VAR const char - no_money[] = "Moreover, you%s have no money.", +static const char + no_money[] = "Moreover, you%s have no gold.", not_enough_money[] = "Besides, you don't have enough to interest %s."; +/* if one item is used-up and the other isn't, the used-up one comes first; + otherwise, if their costs differ, the more expensive one comes first; + if costs are the same, use internal index as tie-breaker for stable sort */ +staticfn int QSORTCALLBACK +sortbill_cmp(const genericptr vptr1, const genericptr vptr2) +{ + const struct sortbill_item *sbi1 = (struct sortbill_item *) vptr1, + *sbi2 = (struct sortbill_item *) vptr2; + long cost1 = sbi1->cost, cost2 = sbi2->cost; + int bidx1 = sbi1->bidx, bidx2 = sbi2->bidx, + /* sort such that FullyUsedUp and PartlyUsedUp come before + PartlyIntact, FullyIntact, KnownContainer, UndisclosedContainer */ + used1 = sbi1->usedup <= PartlyUsedUp, /* 0=>unpaid, 1=>used */ + used2 = sbi2->usedup <= PartlyUsedUp; + + if (used1 != used2) + return (used2 - used1); /* bigger comes before smaller here */ + if (cost1 != cost2) + return (cost2 - cost1); /* bigger comes before smaller here too */ + /* index into eshkp->bill_p[] isn't unique (an item that is partly + used and partly intact will have two ibill[] entries indexing same + bill_p[] element) but duplicates won't reach here (used1 vs used2) */ + return (bidx1 - bidx2); +} + /* delivers the cheapest item on the list */ -STATIC_OVL long -cheapest_item(shkp) -register struct monst *shkp; +staticfn long +cheapest_item(int ibillct, Bill *ibill) { - register int ct = ESHK(shkp)->billct; - register struct bill_x *bp = ESHK(shkp)->bill_p; - register long gmin = (bp->price * bp->bquan); + int i; + long gmin = ibill[0].cost; - while (ct--) { - if (bp->price * bp->bquan < gmin) - gmin = bp->price * bp->bquan; - bp++; - } + /* + * 5.0: old version didn't determine cheapest item correctly if it + * was either the partly used or partly intact portion of a partially + * used stack. Rather than modify it to use bp_to_obj() in order to + * obtain quanities for every entry on eshkp->bill_p[], switch to + * ibill[] which has already split such items into separate entries. + */ + + for (i = 1; i < ibillct; ++i) + if (ibill[i].cost < gmin) + gmin = ibill[i].cost; return gmin; } + +/* for itemized purchasing, create an alternate shop bill that hides + container contents */ +staticfn int /* returns number of entries */ +make_itemized_bill( + struct monst *shkp, + Bill **ibill_p) /* output, augmented bill similar to a 'sortloot array' */ +{ + static Bill zerosbi; /* Null sortbill item */ + Bill *ibill; + struct bill_x *bp; + struct obj *otmp; + struct eshk *eshkp = ESHK(shkp); + int i, n, bidx, ebillct = eshkp->billct; + int8 used; + long quan, cost; + + /* this overallocates unless there happens to be a used-up portion + and an intact potion for every object on the bill; doing it this + way avoids the need to look up every object on the bill an extra + time; (the +1 for a terminator isn't actually needed) */ + n = 2 * ebillct + 1; + ibill = *ibill_p = (Bill *) alloc(n * sizeof *ibill); + for (i = 0; i < n; ++i) + ibill[i] = zerosbi; + + n = 0; /* number of entries in ibill[]; won't necessary match ebillct */ + for (i = 0; i < ebillct; ++i) { + bp = &eshkp->bill_p[i]; + /* find the object on the bill */ + otmp = bp_to_obj(bp); + if (!otmp) { + impossible("Can't find shop bill entry for #%d", bp->bo_id); + continue; + } + bidx = i; /* index into bill_p[], except for hero-owner container */ + + if (otmp->quan == 0L || otmp->where == OBJ_ONBILL) { + /* item is completely used up; restore quantity from when it + was first unpaid; otmp is on billobjs list where it can + only be seen via Ix and itemized billing while paying shk */ + otmp->quan = bp->bquan; + bp->useup = TRUE; /* (expected to be set already) */ + } else if (otmp->quan < bp->bquan) { + /* item is partly used up; we will create two entries in the + augmented bill: one for the used up part here, another for + the intact part (which might be inside a container if put in + after using part of a stack; used up part isn't) below */ + ibill[n].obj = otmp; + ibill[n].quan = bp->bquan - otmp->quan; + ibill[n].cost = bp->price * ibill[n].quan; + ibill[n].bidx = bidx; /* duplicate index into eshkp->bill_p[] */ + ibill[n].usedup = PartlyUsedUp; /* for sorting */ + ++n; /* intact portion will be a separate entry, next */ + } + + if (otmp->where == OBJ_ONBILL) { + /* completely used up */ + quan = bp->bquan; + cost = bp->price * quan; + used = FullyUsedUp; + } else if (otmp->where == OBJ_CONTAINED || Has_contents(otmp)) { + int j; + struct obj *item = otmp; + boolean cknown = TRUE; /* assume container contents are known */ + + /* when it's in a container, put the container rather than the + specific object into ibill[]; find outermost container */ + while (otmp->where == OBJ_CONTAINED) { + otmp = otmp->ocontainer; + if (!otmp->cknown) + cknown = FALSE; + } + /* this container might already be in ibill[] if it is unpaid + itself or if it holds more than one unpaid item and another + besides this one has already been processed; only include + first instance */ + for (j = 0; j < n; ++j) + if (otmp == ibill[j].obj) + break; + if (j < n) { + /* when already on bill as FullyIntact, update; the cost + saved in ibill[j] is based on the container even if the + entry was initially created for an item of its contents */ + if (ibill[j].usedup == FullyIntact) + ibill[j].usedup = cknown ? KnownContainer + : UndisclosedContainer; + continue; /* 'i' loop */ + } + /* include 1 container containing unpaid item(s) */ + quan = 1L; + cost = unpaid_cost(otmp, COST_CONTENTS); + if (!otmp->unpaid) + bidx = -1; + /* an unpaid container without any unpaid contents is classified + as 'FullyIntact'; a container with unpaid contents will be + '*Container' regardless of whether it is unpaid itself */ + used = (otmp == item) ? FullyIntact + : cknown ? KnownContainer + : UndisclosedContainer; + } else { + /* ordinary unpaid; when partly used, these are values for the + intact portion; might be an empty shop-owned container */ + quan = otmp->quan; + cost = bp->price * quan; + used = (quan < bp->bquan) ? PartlyIntact : FullyIntact; + } + + ibill[n].obj = otmp; + ibill[n].quan = quan; + ibill[n].cost = cost; + ibill[n].bidx = bidx; + ibill[n].usedup = used; + ++n; + } + ibill[n].bidx = -1; /* end of list; not strictly needed */ + + /* ibill[0..n-1] contains data, ibill[n] has Null obj and -1 bidx and + is excluded from the sort */ + if (n > 1) + qsort((genericptr_t) ibill, n, sizeof *ibill, sortbill_cmp); + return n; +} + +/* show items on your bill in a menu, and ask which to pay. + returns the number of entries selected. */ +staticfn int +menu_pick_pay_items( + int ibillct, /* number of entries in ibill[] */ + Bill *ibill) /* all used up items, if any, precede all intact items */ +{ + struct obj *otmp; + winid win; + anything any; + menu_item *pick_list = (menu_item *) 0; + char *p, buf[BUFSZ]; + long amt, largest_amt, save_quan; + int i, j, n, amt_width; + + any = cg.zeroany; + win = create_nhwindow(NHW_MENU); + start_menu(win, MENU_BEHAVE_STANDARD); + + /* we go through ibill[] twice, first time to control price formatting + during the second */ + largest_amt = 0L; + for (i = 0; i < ibillct; ++i) + if (ibill[i].cost > largest_amt) + largest_amt = ibill[i].cost; + Sprintf(buf, "%ld", largest_amt); + amt_width = (int) strlen(buf); + + /* show the "used up items" header if there are any used up items on + the bill, no matter whether there are also any intact items; + note: ibill[] has been sorted to hold used-up items first */ + if (ibill[0].usedup <= PartlyUsedUp) { + Sprintf(buf, "Used up item%s:", + (ibillct > 1 && ibill[1].usedup <= PartlyUsedUp) ? "s" : ""); + add_menu_heading(win, buf); + } + for (i = 0; i < ibillct; ++i) { + /* the "unpaid items" header is only shown if the "used up items" + one was shown before the first menu entry */ + if (i > 0 && ibill[i - 1].usedup <= PartlyUsedUp + && ibill[i].usedup >= PartlyIntact) { + Sprintf(buf, "Unpaid item%s:", (i < ibillct - 1) ? "s" : ""); + add_menu_heading(win, buf); + } + otmp = ibill[i].obj; + save_quan = otmp->quan; + otmp->quan = ibill[i].quan; /* in case it's partly used */ + p = paydoname(otmp); + otmp->quan = save_quan; + amt = ibill[i].cost; + /* this doesn't support hallucinatory currency because shopkeeper + isn't hallucinating; also, that would mess up the alignment */ + Snprintf(buf, sizeof buf, "%*ld Zm, %s", amt_width, amt, p); + any.a_int = i + 1; /* +1: avoid 0 */ + add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, NO_COLOR, buf, + MENU_ITEMFLAGS_NONE); + } + + end_menu(win, "Pay for which items?"); + n = select_menu(win, PICK_ANY, &pick_list); + destroy_nhwindow(win); + + for (j = 0; j < n; ++j) { + /* + * FIXME: + * The menu will accept a subset count for each entry but buying + * doesn't have any support for that. + */ + i = pick_list[j].item.a_int - 1; /* -1: reverse +1 above */ + ibill[i].queuedpay = TRUE; + } + free(pick_list); + /* for ESC, return 0 instead of usual -1 */ + return max(n, 0); +} + +/* the #pay command */ int -dopay() +dopay(void) { - register struct eshk *eshkp; - register struct monst *shkp; + struct eshk *eshkp; + struct monst *shkp; struct monst *nxtm, *resident; + Bill *ibill = (Bill *) NULL; long ltmp; long umoney; - int pass, tmp, sk = 0, seensk = 0; - boolean paid = FALSE, stashed_gold = (hidden_gold() > 0L); + int sk = 0, seensk = 0, nexttosk = 0; + boolean paid = FALSE, stashed_gold = (hidden_gold(TRUE) > 0L), + pay_done; - multi = 0; + gm.multi = 0; /* Find how many shk's there are, how many are in * sight, and are you in a shop room with one. @@ -1249,27 +1761,32 @@ dopay() for (shkp = next_shkp(fmon, FALSE); shkp; shkp = next_shkp(shkp->nmon, FALSE)) { sk++; - if (ANGRY(shkp) && distu(shkp->mx, shkp->my) <= 2) + if (m_next2u(shkp)) { + /* next to an irate shopkeeper? prioritize that */ + if (nxtm && ANGRY(nxtm)) + continue; + nexttosk++; nxtm = shkp; + } if (canspotmon(shkp)) seensk++; if (inhishop(shkp) && (*u.ushops == ESHK(shkp)->shoproom)) resident = shkp; } - if (nxtm) { /* Player should always appease an */ - shkp = nxtm; /* irate shk standing next to them. */ + if (nxtm && nexttosk == 1) { + shkp = nxtm; goto proceed; } if ((!sk && (!Blind || Blind_telepat)) || (!Blind && !seensk)) { There("appears to be no shopkeeper here to receive your payment."); - return 0; + return ECMD_OK; } if (!seensk) { You_cant("see..."); - return 0; + return ECMD_OK; } /* The usual case. Allow paying at a distance when @@ -1285,10 +1802,11 @@ dopay() shkp = next_shkp(shkp->nmon, FALSE)) if (canspotmon(shkp)) break; - if (shkp != resident && distu(shkp->mx, shkp->my) > 2) { + assert(shkp != NULL); /* seensk==1 => traversal will spot one shk */ + if (shkp != resident && !m_next2u(shkp)) { pline("%s is not near enough to receive your payment.", Shknam(shkp)); - return 0; + return ECMD_OK; } } else { struct monst *mtmp; @@ -1299,40 +1817,40 @@ dopay() cc.x = u.ux; cc.y = u.uy; if (getpos(&cc, TRUE, "the creature you want to pay") < 0) - return 0; /* player pressed ESC */ + return ECMD_CANCEL; /* player pressed ESC */ cx = cc.x; cy = cc.y; if (cx < 0) { pline("Try again..."); - return 0; + return ECMD_OK; } - if (u.ux == cx && u.uy == cy) { + if (u_at(cx, cy)) { You("are generous to yourself."); - return 0; + return ECMD_OK; } mtmp = m_at(cx, cy); if (!cansee(cx, cy) && (!mtmp || !canspotmon(mtmp))) { You("can't %s anyone there.", !Blind ? "see" : "sense"); - return 0; + return ECMD_OK; } if (!mtmp) { There("is no one there to receive your payment."); - return 0; + return ECMD_OK; } if (!mtmp->isshk) { pline("%s is not interested in your payment.", Monnam(mtmp)); - return 0; + return ECMD_OK; } - if (mtmp != resident && distu(mtmp->mx, mtmp->my) > 2) { + if (mtmp != resident && !m_next2u(mtmp)) { pline("%s is too far to receive your payment.", Shknam(mtmp)); - return 0; + return ECMD_OK; } shkp = mtmp; } if (!shkp) { debugpline0("dopay: null shkp."); - return 0; + return ECMD_OK; } proceed: eshkp = ESHK(shkp); @@ -1342,18 +1860,18 @@ dopay() if (ltmp || eshkp->billct || eshkp->debit) rouse_shk(shkp, TRUE); - if (!shkp->mcanmove || shkp->msleeping) { /* still asleep/paralyzed */ + if (helpless(shkp)) { /* still asleep/paralyzed */ pline("%s %s.", Shknam(shkp), rn2(2) ? "seems to be napping" : "doesn't respond"); - return 0; + return ECMD_OK; } if (shkp != resident && NOTANGRY(shkp)) { - umoney = money_cnt(invent); - if (!ltmp) + umoney = money_cnt(gi.invent); + if (!ltmp) { You("do not owe %s anything.", shkname(shkp)); - else if (!umoney) { - You("%shave no money.", stashed_gold ? "seem to " : ""); + } else if (!umoney) { + You("%shave no gold.", stashed_gold ? "seem to " : ""); if (stashed_gold) pline("But you have some gold stashed away."); } else { @@ -1374,24 +1892,24 @@ dopay() else make_happy_shk(shkp, FALSE); } - return 1; + return ECMD_TIME; } /* ltmp is still eshkp->robbed here */ if (!eshkp->billct && !eshkp->debit) { - umoney = money_cnt(invent); + umoney = money_cnt(gi.invent); if (!ltmp && NOTANGRY(shkp)) { You("do not owe %s anything.", shkname(shkp)); if (!umoney) pline(no_money, stashed_gold ? " seem to" : ""); } else if (ltmp) { - pline("%s is after blood, not money!", shkname(shkp)); + pline("%s is after blood, not gold!", shkname(shkp)); if (umoney < ltmp / 2L || (umoney < ltmp && stashed_gold)) { if (!umoney) pline(no_money, stashed_gold ? " seem to" : ""); else pline(not_enough_money, noit_mhim(shkp)); - return 1; + return ECMD_TIME; } pline("But since %s shop has been robbed recently,", noit_mhis(shkp)); @@ -1403,13 +1921,13 @@ dopay() } else { /* shopkeeper is angry, but has not been robbed -- * door broken, attacked, etc. */ - pline("%s is after your hide, not your money!", Shknam(shkp)); + pline("%s is after your hide, not your gold!", Shknam(shkp)); if (umoney < 1000L) { if (!umoney) pline(no_money, stashed_gold ? " seem to" : ""); else pline(not_enough_money, noit_mhim(shkp)); - return 1; + return ECMD_TIME; } You("try to appease %s by giving %s 1000 gold pieces.", canspotmon(shkp) @@ -1417,18 +1935,18 @@ dopay() : shkname(shkp), noit_mhim(shkp)); pay(1000L, shkp); - if (strncmp(eshkp->customer, plname, PL_NSIZ) || rn2(3)) + if (strncmp(eshkp->customer, svp.plname, PL_NSIZ) || rn2(3)) make_happy_shk(shkp, FALSE); else pline("But %s is as angry as ever.", shkname(shkp)); } - return 1; + return ECMD_TIME; } if (shkp != resident) { impossible("dopay: not to shopkeeper?"); if (resident) setpaid(resident); - return 0; + return ECMD_OK; } /* pay debt, if any, first */ if (eshkp->debit) { @@ -1436,7 +1954,7 @@ dopay() long loan = eshkp->loan; char sbuf[BUFSZ]; - umoney = money_cnt(invent); + umoney = money_cnt(gi.invent); Sprintf(sbuf, "You owe %s %ld %s ", shkname(shkp), dtmp, currency(dtmp)); if (loan) { @@ -1445,14 +1963,15 @@ dopay() else Strcat(sbuf, "for gold picked up and the use of merchandise."); - } else + } else { Strcat(sbuf, "for the use of merchandise."); + } pline1(sbuf); if (umoney + eshkp->credit < dtmp) { pline("But you don't%s have enough gold%s.", stashed_gold ? " seem to" : "", eshkp->credit ? " or credit" : ""); - return 1; + return ECMD_TIME; } else { if (eshkp->credit >= dtmp) { eshkp->credit -= dtmp; @@ -1464,7 +1983,7 @@ dopay() eshkp->debit = 0L; eshkp->loan = 0L; You("pay that debt."); - context.botl = 1; + disp.botl = TRUE; } else { dtmp -= eshkp->credit; eshkp->credit = 0L; @@ -1473,101 +1992,222 @@ dopay() eshkp->loan = 0L; pline("That debt is partially offset by your credit."); You("pay the remainder."); - context.botl = 1; + disp.botl = TRUE; } paid = TRUE; } } + /* now check items on bill */ + pay_done = TRUE; /* assume success */ if (eshkp->billct) { - register boolean itemize; - int iprompt; + int ibillct = make_itemized_bill(shkp, &ibill); - umoney = money_cnt(invent); - if (!umoney && !eshkp->credit) { - You("%shave no money or credit%s.", - stashed_gold ? "seem to " : "", paid ? " left" : ""); - return 0; + if (!pay_billed_items(shkp, ibillct, ibill, stashed_gold, &paid)) + pay_done = FALSE; /* skip thank you message */ + } + + /* {mute shk,deaf hero}-aware thank you message */ + if (pay_done && !ANGRY(shkp) && paid) { + if (!Deaf && !muteshk(shkp)) { + SetVoice(shkp, 0, 80, 0); + verbalize("Thank you for shopping in %s %s%s", + s_suffix(shkname(shkp)), + shtypes[eshkp->shoptype - SHOPBASE].name, + !eshkp->surcharge ? "!" : "."); + } else { + pline("%s nods%s at you for shopping in %s %s%s", + Shknam(shkp), !eshkp->surcharge ? " appreciatively" : "", + noit_mhis(shkp), shtypes[eshkp->shoptype - SHOPBASE].name, + !eshkp->surcharge ? "!" : "."); } - if ((umoney + eshkp->credit) < cheapest_item(shkp)) { - You("don't have enough money to buy%s the item%s you picked.", - eshkp->billct > 1 ? " any of" : "", plur(eshkp->billct)); - if (stashed_gold) - pline("Maybe you have some gold stashed away?"); - return 0; + } + + if (paid) + update_inventory(); + iflags.menu_requested = FALSE; /* reset */ + /* free the sortbill array used for itemized billing */ + if (ibill) { + free((genericptr_t) ibill), ibill = NULL; + nhUse(ibill); + } + return paid ? ECMD_TIME : ECMD_OK; +} + +/* for menustyle=Traditional, choose between paying for everything (by + declining to itemize), asking item-by-item (by accepting itemization), + or switch to selecting via menu (special 'm' answer at "Itemize? [ynq m]" + prompt); for other menustyles, always select via menu; + player can use 'm' prefix before 'p' command to invert those behaviors; + once the method is chosen, actually pay for the selected items, item by + item for as long as hero has enough credit+cash */ +staticfn boolean +pay_billed_items( + struct monst *shkp, + int ibillct, + Bill *ibill, + boolean stashed_gold, + boolean *paid_p) /* output */ +{ + struct bill_x *bp; + struct obj *otmp; + long umoney; + boolean itemize, more_than_one; + boolean queuedpay = FALSE, via_menu; + int buy, indx, bidx, pass, iprompt, ebillct; + struct eshk *eshkp = ESHK(shkp); + + umoney = money_cnt(gi.invent); + if (!umoney && !eshkp->credit) { + You("%shave no gold or credit%s.", + stashed_gold ? "seem to " : "", *paid_p ? " left" : ""); + return TRUE; + } + bp = eshkp->bill_p; + otmp = bp_to_obj(bp); + ebillct = eshkp->billct; + more_than_one = (ebillct > 1 || otmp->quan < bp->bquan + /* note: will only get here for a single item, so + we can deduce that it is ibill[0] */ + || ibill[0].usedup == UndisclosedContainer); + if ((umoney + eshkp->credit) < cheapest_item(ibillct, ibill)) { + You("don't have enough gold to buy%s the item%s %s.", + more_than_one ? " any of" : "", plur(more_than_one ? 2 : 1), + (ebillct > 1) ? "you've picked" : "on your bill"); + if (stashed_gold) + pline("Maybe you have some gold stashed away?"); + return TRUE; + } + + via_menu = (flags.menu_style != MENU_TRADITIONAL); + /* allow 'm p' to request a menu for menustyle:traditional; + for other styles, it will do the opposite; that doesn't make + a whole lot of sense for a 'request-menu' prefix, but otherwise + it would simply be redundant and there wouldn't be any way to + skip the menu when hero owes for multiple items */ + if (iflags.menu_requested) + via_menu = !via_menu; + /* this will loop for a second iteration iff not initially using a + menu and player answers 'm' at custom ynq prompt */ + do { + if (via_menu /*&& more_than_one*/ ) { + if (!menu_pick_pay_items(ibillct, ibill)) + return TRUE; + queuedpay = TRUE; + itemize = FALSE; + via_menu = FALSE; /* reset so that we don't loop */ + } else { + iprompt = !more_than_one ? 'y' + : yn_function("Itemized billing?", "ynq m", 'q', TRUE); + if (iprompt == 'q') + return TRUE; + itemize = (iprompt == 'y'); + via_menu = (iprompt == 'm'); } + } while (via_menu); - /* this isn't quite right; it itemizes without asking if the - * single item on the bill is partly used up and partly unpaid */ - iprompt = (eshkp->billct > 1 ? ynq("Itemized billing?") : 'y'); - itemize = (iprompt == 'y'); - if (iprompt == 'q') - goto thanks; - - for (pass = 0; pass <= 1; pass++) { - tmp = 0; - while (tmp < eshkp->billct) { - struct obj *otmp; - register struct bill_x *bp = &(eshkp->bill_p[tmp]); - - /* find the object on one of the lists */ - if ((otmp = bp_to_obj(bp)) != 0) { - /* if completely used up, object quantity is stale; - restoring it to its original value here avoids - making the partly-used-up code more complicated */ - if (bp->useup) - otmp->quan = bp->bquan; - } else { - impossible("Shopkeeper administration out of order."); - setpaid(shkp); /* be nice to the player */ - return 1; - } - if (pass == bp->useup && otmp->quan == bp->bquan) { - /* pay for used-up items on first pass and others - * on second, so player will be stuck in the store - * less often; things which are partly used up - * are processed on both passes */ - tmp++; - } else { - switch (dopayobj(shkp, bp, &otmp, pass, itemize)) { - case PAY_CANT: - return 1; /*break*/ - case PAY_BROKE: - paid = TRUE; - goto thanks; /*break*/ - case PAY_SKIP: - tmp++; - continue; /*break*/ - case PAY_SOME: - paid = TRUE; - if (itemize) - bot(); - continue; /*break*/ - case PAY_BUY: - paid = TRUE; - break; - } - if (itemize) - bot(); - *bp = eshkp->bill_p[--eshkp->billct]; - } + /* + * 5.0: this used to make two passes through eshkp->bill_p[], + * the first for used up items and the second for unpaid ones. + * Items which were partly used were processed on both passes. + * + * Now it makes one pass through ibill[], which has all used up + * items sorted to the beginning and unpaid ones sorted to the end. + * Partly used items have two entries for same base item, one in + * each section. + */ + for (indx = 0; indx < ibillct; ++indx) { + if (queuedpay && !ibill[indx].queuedpay) + continue; + + otmp = ibill[indx].obj; /* ordinary object or outermost container */ + if (ibill[indx].usedup >= KnownContainer) { + /* when successfull, buy_container() will call both + dopayobj() and update_bill(), possibly multiple times */ + int boxbag_result = buy_container(shkp, indx, ibillct, ibill); + + if (boxbag_result == 0) { + buy = PAY_BUY; + } else { /* buy_container() failed... */ + if (boxbag_result == 2) /* ... but didn't explain why */ + verbalize("You need to remove any unpaid items from" + " that %s and buy them separately.", + simpleonames(otmp)); + buy = PAY_CANT; + } + } else { + bidx = ibill[indx].bidx; + bp = &eshkp->bill_p[bidx]; + pass = (ibill[indx].usedup <= PartlyUsedUp) ? 0 : 1; + + buy = dopayobj(shkp, bp, otmp, pass, itemize, FALSE); + + if (buy == PAY_BUY) + update_bill(indx, ibillct, ibill, eshkp, bp, otmp); + } + switch (buy) { + case PAY_CANT: + return FALSE; + case PAY_BROKE: + *paid_p = TRUE; + return TRUE; + case PAY_SKIP: + continue; + /* case PAY_SOME: //no longer used */ + case PAY_BUY: + *paid_p = TRUE; + if (itemize || queuedpay) { + update_inventory(); + bot(); } + break; } - thanks: - if (!itemize) - update_inventory(); /* Done in dopayobj() if itemize. */ } - if (!ANGRY(shkp) && paid) { - if (!Deaf && !muteshk(shkp)) - verbalize("Thank you for shopping in %s %s!", - s_suffix(shkname(shkp)), - shtypes[eshkp->shoptype - SHOPBASE].name); - else - pline("%s nods appreciatively at you for shopping in %s %s!", - Shknam(shkp), noit_mhis(shkp), - shtypes[eshkp->shoptype - SHOPBASE].name); + return TRUE; +} + +/* update shk's bill and augmented bill after an item has been purchased */ +staticfn void +update_bill( + int indx, /* index into ibill[]; -1 for unpaid contained item */ + int ibillct, + Bill *ibill, + struct eshk *eshkp, + struct bill_x *bp, + struct obj *paiditem) +{ + int j, newebillct; + + /* remove from eshkp->bill_p[] unless this was the used up portion + of partly used item (since removal would take out both; note: + can't buy PartlyIntact until PartlyUsedUp has been paid for) */ + if (indx >= 0 && ibill[indx].usedup == PartlyUsedUp) { + /* 'paiditem' points to the partly intact portion still in invent or + inside a container (ibill[indx].obj points to the container) */ + bp->bquan = paiditem->quan; + for (j = 0; j < ibillct; ++j) + if (ibill[j].obj == paiditem && ibill[j].usedup == PartlyIntact) { + ibill[j].usedup = FullyIntact; + break; + } + } else { + /* if we get here, something was bought and needs to be removed + from shop bill; if it was used up, remove it from the billobjs + list and delete it; update shop's bill by moving last bill_p[] + entry into vacated slot; also update ibill[] indices for that */ + paiditem->unpaid = 0; /* clear before maybe deallocating */ + if (paiditem->where == OBJ_ONBILL) { + obj_extract_self(paiditem); + dealloc_obj(paiditem); + } + newebillct = eshkp->billct - 1; + *bp = eshkp->bill_p[newebillct]; + for (j = 0; j < ibillct; ++j) + if (ibill[j].bidx == newebillct) + ibill[j].bidx = (int) (bp - eshkp->bill_p); + eshkp->billct = newebillct; /* eshkp->billct - 1 */ } - return 1; + return; } /* return 2 if used-up portion paid @@ -1576,31 +2216,30 @@ dopay() * -1 if skip this object * -2 if no money/credit left */ -STATIC_OVL int -dopayobj(shkp, bp, obj_p, which, itemize) -register struct monst *shkp; -register struct bill_x *bp; -struct obj **obj_p; -int which; /* 0 => used-up item, 1 => other (unpaid or lost) */ -boolean itemize; -{ - register struct obj *obj = *obj_p; +staticfn int +dopayobj( + struct monst *shkp, + struct bill_x *bp, + struct obj *obj, + int which, /* 0 => used-up item, 1 => other (unpaid or lost) */ + boolean itemize, + boolean unseen) +{ long ltmp, quan, save_quan; - long umoney = money_cnt(invent); int buy; - boolean stashed_gold = (hidden_gold() > 0L), consumed = (which == 0); + boolean consumed = (which == 0); - if (!obj->unpaid && !bp->useup) { + if (!obj->unpaid && !bp->useup + && !(Has_contents(obj) && unpaid_cost(obj, COST_CONTENTS))) { impossible("Paid object on bill??"); return PAY_BUY; } - if (itemize && umoney + ESHK(shkp)->credit == 0L) { - You("%shave no money or credit left.", - stashed_gold ? "seem to " : ""); + if (itemize && insufficient_funds(shkp, obj, 0L)) { return PAY_BROKE; } /* we may need to temporarily adjust the object, if part of the - original quantity has been used up but part remains unpaid */ + original quantity has been used up but part remains unpaid; [note: + this predates 'ibill[]' and feels redundant but still works] */ save_quan = obj->quan; if (consumed) { /* either completely used up (simple), or split needed */ @@ -1611,90 +2250,241 @@ boolean itemize; /* dealing with ordinary unpaid item */ quan = obj->quan; } + ltmp = bp->price * quan; + obj->quan = quan; /* to be used by doname() */ - obj->unpaid = 0; /* ditto */ iflags.suppress_price++; /* affects containers */ - ltmp = bp->price * quan; buy = PAY_BUY; /* flag; if changed then return early */ if (itemize) { char qbuf[BUFSZ], qsfx[BUFSZ]; + /* + * TODO: + * This should also accept 'a' and 'q' to end itemized paying: + * 'a' to buy the rest without asking, 'q' to just stop. + */ + Sprintf(qsfx, " for %ld %s. Pay?", ltmp, currency(ltmp)); (void) safe_qbuf(qbuf, (char *) 0, qsfx, obj, (quan == 1L) ? Doname2 : doname, ansimpleoname, (quan == 1L) ? "that" : "those"); - if (yn(qbuf) == 'n') { + if (y_n(qbuf) == 'n') { buy = PAY_SKIP; /* don't want to buy */ - } else if (quan < bp->bquan && !consumed) { /* partly used goods */ - obj->quan = bp->bquan - save_quan; /* used up amount */ - if (!Deaf && !muteshk(shkp)) { - verbalize("%s for the other %s before buying %s.", - ANGRY(shkp) ? "Pay" : "Please pay", - simpleonames(obj), /* short name suffices */ - save_quan > 1L ? "these" : "this one"); - } else { - pline("%s %s%s your bill for the other %s first.", - Shknam(shkp), - ANGRY(shkp) ? "angrily " : "", - nolimbs(shkp->data) ? "motions to" : "points out", - simpleonames(obj)); - } - buy = PAY_SKIP; /* shk won't sell */ } + } /* itemize */ + + if (quan < bp->bquan && !consumed) { /* partly used goods */ + /* shk won't sell the intact portion until the used up portion has + been paid for (once it has been, bp->bquan will match quan) */ + reject_purchase(shkp, obj, bp->bquan); + buy = PAY_SKIP; } - if (buy == PAY_BUY && umoney + ESHK(shkp)->credit < ltmp) { - You("don't%s have gold%s enough to pay for %s.", - stashed_gold ? " seem to" : "", - (ESHK(shkp)->credit > 0L) ? " or credit" : "", - thesimpleoname(obj)); + if (buy == PAY_BUY && insufficient_funds(shkp, obj, ltmp)) { buy = itemize ? PAY_SKIP : PAY_CANT; } - if (buy != PAY_BUY) { - /* restore unpaid object to original state */ - obj->quan = save_quan; - obj->unpaid = 1; - iflags.suppress_price--; - return buy; + if (buy == PAY_BUY) { + pay(ltmp, shkp); + if (!unseen) + shk_names_obj(shkp, obj, + consumed + ? "paid for %s at a cost of %ld gold piece%s.%s" + : "bought %s for %ld gold piece%s.%s", + ltmp, ""); } - pay(ltmp, shkp); - shk_names_obj(shkp, obj, - consumed ? "paid for %s at a cost of %ld gold piece%s.%s" - : "bought %s for %ld gold piece%s.%s", - ltmp, ""); + /* restore obj to original state */ obj->quan = save_quan; /* restore original count */ - /* quan => amount just bought, save_quan => remaining unpaid count */ - if (consumed) { - if (quan != bp->bquan) { - /* eliminate used-up portion; remainder is still unpaid */ - bp->bquan = obj->quan; - obj->unpaid = 1; - bp->useup = 0; - buy = PAY_SOME; - } else { /* completely used-up, so get rid of it */ - obj_extract_self(obj); - /* assert( obj == *obj_p ); */ - dealloc_obj(obj); - *obj_p = 0; /* destroy pointer to freed object */ - } - } else if (itemize) - update_inventory(); /* Done just once in dopay() if !itemize. */ iflags.suppress_price--; + return buy; } -static struct repo { /* repossession context */ - struct monst *shopkeeper; - coord location; -} repo; +/* pay for the unpaid contents of a container without itemizing, + and for the container itself if it is unpaid too; + returns 0==successfully bought; 1==rejected, message given here; + 2=rejected, caller should issue message */ +staticfn int +buy_container( + struct monst *shkp, + int indx, + int ibillct, + Bill *ibill) +{ + unsigned boid, boids[BILLSZ]; + int i, j, buy, buycount = 0, boidsct = 0; + struct eshk *eshkp = ESHK(shkp); + int ebillct = eshkp->billct; + struct bill_x *bp; + struct obj *otmp, *otop, + *container = ibill[indx].obj; + unsigned unpaidcontainer = container->unpaid; + long totalcost = ibill[indx].cost; + boolean sightunseen = ibill[indx].usedup == UndisclosedContainer + /* give feedback just for container+contents rather + than for individiual contents even when those + contents are known */ + || ibill[indx].usedup == KnownContainer; + + /* check for no-gold first, then for not-enough-gold; feedback is + different for the two cases */ + if (insufficient_funds(shkp, container, 0L) + || insufficient_funds(shkp, container, totalcost)) + return 1; /* message given by insufficent_funds() */ + + /* check for partly intact portion of a not-yet-paid partly used item */ + for (i = 0; i < ebillct; ++i) { + bp = &eshkp->bill_p[i]; + otmp = bp_to_obj(bp); /* ibill[bidx].obj is the container */ + if (!otmp) { + impossible("Can't find contained item on shop bill (#%d).", + bp->bo_id); + return 2; /* failure; have caller give a generic message */ + } + if (otmp->where != OBJ_CONTAINED && !Has_contents(otmp)) + continue; + /* otmp is contained, but possibly inside a different container */ + for (otop = otmp; otop->where == OBJ_CONTAINED; + otop = otop->ocontainer) + continue; /* where==OBJ_CONTAINED loop */ + if (otop != container) + continue; /* 'i' loop */ + /* now check for partly intact portion of partly used item */ + if (otmp->quan < bp->bquan) { + reject_purchase(shkp, otmp, bp->bquan); + return 1; /* message given by reject_purchase() */ + } + /* record this for the second pass; unless it's the container--that + will be deferred until after the loop so that it will be last */ + if (bp->bo_id != container->o_id) + boids[boidsct++] = bp->bo_id; + } + if (unpaidcontainer) + boids[boidsct++] = container->o_id; + + /* now make the actual purchasing pass; we've collected a set of + o_id values in order to avoid traversing the shk's bill while it + undergoes updates */ + for (j = 0; j < boidsct; ++j) { + boid = boids[j]; + for (i = 0, bp = eshkp->bill_p; i < ebillct; ++i, ++bp) + if (bp->bo_id == boid) + break; + if (i == ebillct) { + impossible("Buying %s contents: item #%u disappeared from bill.", + simpleonames(container), boid); + return 2; + } + otmp = bp_to_obj(bp); + + buy = dopayobj(shkp, bp, otmp, 1, FALSE, sightunseen); + if (buy != PAY_BUY) { + impossible("Buying %s contents failed unexpectedly (#%u %d).", + simpleonames(container), otmp->o_id, buy); + continue; + } + /* [updating cost here is not necessary but useful when debugging] */ + ibill[indx].cost -= (bp->price * bp->bquan); /* update container */ + update_bill((boid == container->o_id) ? indx : -1, + ibillct, ibill, eshkp, bp, otmp); + ++buycount; + } + if (buycount && sightunseen) { + /* if the container was unpaid, the hero has just purchased it; + normally paydoname()--called by shk_names_obj()--would give + "contents of your " when it's hero-owned but we + want it to reflect container's state before purchase; + since paydoname() isn't called for no_charge items, we use + obj->no_charge as a hack to avoid that phrasing in favor of + "a/an and its contents"; temporarily set + obj->unpaid to reflect the before-purchase state too */ + if (unpaidcontainer) + container->unpaid = container->no_charge = 1; + shk_names_obj(shkp, container, + "bought %s for %ld gold piece%s.%s", + totalcost, ""); + container->unpaid = container->no_charge = 0; + } + + return buycount ? 0 : 2; /* we don't expect buycount to be 0 */ +} + +/* called if an item on shop bill is partly used up and partly intact and + player tries to buy the intact portion before paying for used up portion + (not actually very effective since player can just drop the unpaid + portion then pick it back up to have it get its own distinct bill entry; + the former partly used up portion becomes a fully used up separate item) */ +staticfn void +reject_purchase( + struct monst *shkp, + struct obj *obj, + long billed_quan) +{ + long intact_quan = obj->quan; + + assert(intact_quan < billed_quan); + /* temporarily change obj to refer to the used up portion */ + obj->quan = billed_quan - intact_quan; + if (!Deaf && !muteshk(shkp)) { + char which[BUFSZ]; + + if (obj->where == OBJ_CONTAINED) + Snprintf(which, sizeof which, "the one%s in %s", + plur(intact_quan), thesimpleoname(obj->ocontainer)); + else + Sprintf(which, "%s", (intact_quan > 1L) ? "these" : "this one"); + + SetVoice(shkp, 0, 80, 0); + verbalize("%s for the other %s before buying %s.", + ANGRY(shkp) ? "Pay" : "Please pay", + simpleonames(obj), /* short name suffices */ + which); + } else { + pline("%s %s%s your bill for the other %s first.", + Shknam(shkp), + ANGRY(shkp) ? "angrily " : "", + nolimbs(shkp->data) ? "motions to" : "points out", + simpleonames(obj)); + } + obj->quan = intact_quan; +} + +/* gold+credit checking+feedback common to dopayobj() and buy_container() */ +staticfn boolean +insufficient_funds( + struct monst *shkp, + struct obj *item, + long cost) /* 0: check for no-gold; >0: check for specified amount */ +{ + long stashed_gold; + long umoney = money_cnt(gi.invent), + ecredit = ESHK(shkp)->credit; + + /* dopayobj() checks for no-gold early and not-enough-gold later; + buy_container() checks for both early but uses separate calls to us */ + if (!cost && umoney + ecredit == 0L) { + stashed_gold = hidden_gold(TRUE); + You("%shave no gold or credit left.", + (stashed_gold > 0) ? "seem to " : ""); + return TRUE; + } + if (cost && umoney + ecredit < cost) { + stashed_gold = hidden_gold(TRUE); + You("don't%s have gold%s enough to pay for %s.", + (stashed_gold > 0L) ? " seem to" : "", + (ecredit > 0L) ? " or credit" : "", + paydoname(item)); + return TRUE; + } + return FALSE; +} /* routine called after dying (or quitting) */ boolean -paybill(croaked, silently) -int croaked; /* -1: escaped dungeon; 0: quit; 1: died */ -boolean silently; /* maybe avoid messages */ +paybill( + int croaked, /* -1: escaped dungeon; 0: quit; 1: died */ + boolean silently) /* maybe avoid messages */ { struct monst *mtmp, *mtmp2, *firstshk, *resident, *creditor, *hostile, *localshk; @@ -1712,8 +2502,8 @@ boolean silently; /* maybe avoid messages */ which has been shut inside a statue] */ /* this is where inventory will end up if any shk takes it */ - repo.location.x = repo.location.y = 0; - repo.shopkeeper = 0; + gr.repo.location.x = gr.repo.location.y = 0; + gr.repo.shopkeeper = 0; /* * Scan all shopkeepers on the level, to prioritize them: @@ -1731,7 +2521,7 @@ boolean silently; /* maybe avoid messages */ mtmp2 = mtmp->nmon; eshkp = ESHK(mtmp); local = on_level(&eshkp->shoplevel, &u.uz); - if (local && index(u.ushops, eshkp->shoproom)) { + if (local && strchr(u.ushops, eshkp->shoproom)) { /* inside this shk's shop [there might be more than one resident shk if hero is standing in a breech of a shared wall, so give priority to one who's also owed money] */ @@ -1779,25 +2569,29 @@ boolean silently; /* maybe avoid messages */ return taken; } -STATIC_OVL boolean -inherits(shkp, numsk, croaked, silently) -struct monst *shkp; -int numsk; -int croaked; -boolean silently; +/* decide whether a shopkeeper will take possession of dying hero's invent; + when this returns True, it should call set_repo_loc() before returning; + when it returns False, it should not do such because that might have + already been called for some shopkeeper */ +staticfn boolean +inherits( + struct monst *shkp, + int numsk, + int croaked, + boolean silently) { long loss = 0L; long umoney; struct eshk *eshkp = ESHK(shkp); - boolean take = FALSE, taken = FALSE; - unsigned save_minvis = shkp->minvis; - int roomno = *u.ushops; + boolean take = FALSE, taken = FALSE, + uinshop = (strchr(u.ushops, eshkp->shoproom) != (char *) 0); char takes[BUFSZ]; /* not strictly consistent; affects messages and prevents next player - (if bones are saved) from blundering into or being ambused by an + (if bones are saved) from blundering into or being ambushed by an invisible shopkeeper */ - shkp->minvis = 0; + shkp->minvis = shkp->perminvis = 0; + /* The simplifying principle is that first-come already took everything you had. */ if (numsk > 1) { @@ -1807,29 +2601,27 @@ boolean silently; Sprintf(takes, ", shakes %s %s,", noit_mhis(shkp), mbodypart(shkp, HEAD)); pline("%s %slooks at your corpse%s and %s.", Shknam(shkp), - (!shkp->mcanmove || shkp->msleeping) ? "wakes up, " : "", + helpless(shkp) ? "wakes up, " : "", takes, !inhishop(shkp) ? "disappears" : "sighs"); } - rouse_shk(shkp, FALSE); /* wake shk for bones */ - taken = (roomno == eshkp->shoproom); + taken = uinshop; goto skip; } - /* get one case out of the way: you die in the shop, the */ - /* shopkeeper is peaceful, nothing stolen, nothing owed. */ - if (roomno == eshkp->shoproom && inhishop(shkp) && !eshkp->billct + /* get one case out of the way: you die in the shop, the + shopkeeper is peaceful, nothing stolen, nothing owed */ + if (uinshop && inhishop(shkp) && !eshkp->billct && !eshkp->robbed && !eshkp->debit && NOTANGRY(shkp) && !eshkp->following && u.ugrave_arise < LOW_PM) { - taken = (invent != 0); + taken = (gi.invent != 0); if (taken && !silently) pline("%s gratefully inherits all your possessions.", Shknam(shkp)); - set_repo_loc(shkp); goto clear; } if (eshkp->billct || eshkp->debit || eshkp->robbed) { - if (roomno == eshkp->shoproom && inhishop(shkp)) + if (uinshop && inhishop(shkp)) loss = addupbill(shkp) + eshkp->debit; if (loss < eshkp->robbed) loss = eshkp->robbed; @@ -1837,39 +2629,38 @@ boolean silently; } if (eshkp->following || ANGRY(shkp) || take) { - if (!invent) + if (!gi.invent) goto skip; - umoney = money_cnt(invent); + umoney = money_cnt(gi.invent); takes[0] = '\0'; - if (!shkp->mcanmove || shkp->msleeping) + if (helpless(shkp)) Strcat(takes, "wakes up and "); - if (distu(shkp->mx, shkp->my) > 2) + if (!m_next2u(shkp)) Strcat(takes, "comes and "); Strcat(takes, "takes"); - if (loss > umoney || !loss || roomno == eshkp->shoproom) { + if (loss > umoney || !loss || uinshop) { eshkp->robbed -= umoney; if (eshkp->robbed < 0L) eshkp->robbed = 0L; if (umoney > 0L) { money2mon(shkp, umoney); - context.botl = 1; + disp.botl = TRUE; } if (!silently) pline("%s %s all your possessions.", Shknam(shkp), takes); taken = TRUE; - /* where to put player's invent (after disclosure) */ - set_repo_loc(shkp); } else { money2mon(shkp, loss); - context.botl = 1; + disp.botl = TRUE; if (!silently) pline("%s %s the %ld %s %sowed %s.", Shknam(shkp), takes, loss, currency(loss), - strncmp(eshkp->customer, plname, PL_NSIZ) ? "" : "you ", + strncmp(eshkp->customer, svp.plname, PL_NSIZ) ? "" + : "you ", noit_mhim(shkp)); /* shopkeeper has now been paid in full */ - pacify_shk(shkp); + pacify_shk(shkp, FALSE); eshkp->following = 0; eshkp->robbed = 0L; } @@ -1880,56 +2671,81 @@ boolean silently; home_shk(shkp, FALSE); } clear: - shkp->minvis = save_minvis; - setpaid(shkp); + setpaid(shkp); /* clear this shk's bill */ + /* where to put player's invent (after disclosure) */ + if (taken) + set_repo_loc(shkp); return taken; } -STATIC_OVL void -set_repo_loc(shkp) -struct monst *shkp; +staticfn void +set_repo_loc(struct monst *shkp) { - register xchar ox, oy; + coordxy ox, oy; struct eshk *eshkp = ESHK(shkp); + /* when multiple shopkeepers are present, we might get called more + than once; don't override previous setting */ + if (gr.repo.shopkeeper) + return; + + /* savebones() sets u.ux,u.uy to 0,0 to remove hero from map but that + takes place after finish_paybill() has been called so we expect + u.ux,u.uy to be valid; however, there has been a report of + impossible "place_object: \"\" off map <0,0>" when hero died + in a gap in a shop's wall (in Minetown, so multiple shopkeepers in + play, and prior to adding 'if (gr.repo.shopkeeper) return' above) */ + ox = u.ux ? u.ux : u.ux0; + oy = u.ux ? u.uy : u.uy0; /* [testing u.ux when setting oy is correct] */ + /* if you're not in this shk's shop room, or if you're in its doorway - or entry spot, then your gear gets dumped all the way inside */ - if (*u.ushops != eshkp->shoproom || IS_DOOR(levl[u.ux][u.uy].typ) - || (u.ux == eshkp->shk.x && u.uy == eshkp->shk.y)) { - /* shk.x,shk.y is the position immediately in - * front of the door -- move in one more space - */ + or entry spot or one of its walls (temporary gap or Passes_walls), + then your gear gets dumped all the way inside */ + if (!strchr(u.ushops, eshkp->shoproom) || costly_adjacent(shkp, ox, oy)) { + /* shk.x,shk.y is the position immediately in front of the door; + move in one more space */ ox = eshkp->shk.x; oy = eshkp->shk.y; ox += sgn(ox - eshkp->shd.x); oy += sgn(oy - eshkp->shd.y); - } else { /* already inside this shk's shop */ - ox = u.ux; - oy = u.uy; + } else { + ; /* already inside this shk's shop so use ox,oy as-is */ } /* finish_paybill will deposit invent here */ - repo.location.x = ox; - repo.location.y = oy; - repo.shopkeeper = shkp; + gr.repo.location.x = ox; + gr.repo.location.y = oy; + gr.repo.shopkeeper = shkp; } /* called at game exit, after inventory disclosure but before making bones; shouldn't issue any messages */ void -finish_paybill() +finish_paybill(void) { - struct monst *shkp = repo.shopkeeper; - int ox = repo.location.x, oy = repo.location.y; + struct monst *shkp = gr.repo.shopkeeper; + int ox = gr.repo.location.x, oy = gr.repo.location.y; -#if 0 /* don't bother */ - if (ox == 0 && oy == 0) - impossible("finish_paybill: no location"); -#endif + /* + * If set_repo_loc() didn't get called for some reason (good luck + * untangling inherits() to figure out why...), ox,oy will be 0,0 + * and shkp will be Null. Fix coordinates if that happens. + */ + + if (!isok(ox, oy)) { + /* this used to be suppressed as "don't bother" (too late to matter) + but that led to "place_object: \"\" off map <0,0>" warning */ + if (shkp) + impossible("finish_paybill: bad location <%d,%d>.", ox, oy); + /* force a valid location */ + ox = u.ux ? u.ux : u.ux0; + oy = u.ux ? u.uy : u.uy0; /* [note: testing u.ux when setting oy + * is correct here]*/ + } /* normally done by savebones(), but that's too late in this case */ unleash_all(); /* if hero has any gold left, take it into shopkeeper's possession */ if (shkp) { - long umoney = money_cnt(invent); + long umoney = money_cnt(gi.invent); if (umoney) money2mon(shkp, umoney); @@ -1939,15 +2755,14 @@ finish_paybill() } /* find obj on one of the lists */ -STATIC_OVL struct obj * -bp_to_obj(bp) -register struct bill_x *bp; +staticfn struct obj * +bp_to_obj(struct bill_x *bp) { - register struct obj *obj; - register unsigned int id = bp->bo_id; + struct obj *obj; + unsigned int id = bp->bo_id; if (bp->useup) - obj = o_on(id, billobjs); + obj = o_on(id, gb.billobjs); else obj = find_oid(id); return obj; @@ -1956,30 +2771,29 @@ register struct bill_x *bp; /* * Look for o_id on all lists but billobj. Return obj or NULL if not found. * Its OK for restore_timers() to call this function, there should not - * be any timeouts on the billobjs chain. + * be any timeouts on the gb.billobjs chain. */ struct obj * -find_oid(id) -unsigned id; +find_oid(unsigned int id) { struct obj *obj; struct monst *mon, *mmtmp[3]; int i; /* first check various obj lists directly */ - if ((obj = o_on(id, invent)) != 0) + if ((obj = o_on(id, gi.invent)) != 0) return obj; if ((obj = o_on(id, fobj)) != 0) return obj; - if ((obj = o_on(id, level.buriedobjlist)) != 0) + if ((obj = o_on(id, svl.level.buriedobjlist)) != 0) return obj; - if ((obj = o_on(id, migrating_objs)) != 0) + if ((obj = o_on(id, gm.migrating_objs)) != 0) return obj; /* not found yet; check inventory for members of various monst lists */ mmtmp[0] = fmon; - mmtmp[1] = migrating_mons; - mmtmp[2] = mydogs; /* for use during level changes */ + mmtmp[1] = gm.migrating_mons; + mmtmp[2] = gm.mydogs; /* for use during level changes */ for (i = 0; i < 3; i++) for (mon = mmtmp[i]; mon; mon = mon->nmon) if ((obj = o_on(id, mon->minvent)) != 0) @@ -1992,13 +2806,14 @@ unsigned id; /* Returns the price of an arbitrary item in the shop, 0 if the item doesn't belong to a shopkeeper or hero is not in the shop. */ long -get_cost_of_shop_item(obj, nochrg) -register struct obj *obj; -int *nochrg; /* alternate return value: 1: no charge, 0: shop owned, */ -{ /* -1: not in a shop (so should't be formatted as "no charge") */ +get_cost_of_shop_item( + struct obj *obj, + int *nochrg) /* alternate return value: 1: no charge, 0: shop owned, + * -1: not in a shop (so don't format as "no charge") */ +{ struct monst *shkp; struct obj *top; - xchar x, y; + coordxy x, y; boolean freespot; long cost = 0L; @@ -2027,9 +2842,8 @@ int *nochrg; /* alternate return value: 1: no charge, 0: shop owned, */ return cost; } -STATIC_OVL long -get_pricing_units(obj) -struct obj *obj; +staticfn long +get_pricing_units(struct obj *obj) { long units = obj->quan; @@ -2047,9 +2861,7 @@ struct obj *obj; /* decide whether to apply a surcharge (or hypothetically, a discount) to obj if it had ID number 'oid'; returns 1: increase, 0: normal, -1: decrease */ int -oid_price_adjustment(obj, oid) -struct obj *obj; -unsigned oid; +oid_price_adjustment(struct obj *obj, unsigned int oid) { int res = 0, otyp = obj->otyp; @@ -2061,11 +2873,18 @@ unsigned oid; } /* calculate the value that the shk will charge for [one of] an object */ -STATIC_OVL long -get_cost(obj, shkp) -register struct obj *obj; -register struct monst *shkp; /* if angry, impose a surcharge */ +staticfn long +get_cost( + struct obj *obj, + struct monst *shkp) /* if angry, impose a surcharge */ { + /* + * FIXME: + * If this obj is already on the shop's bill, use the price which + * has been set there. Otherwise, the amount could be different + * (if billed while undiscovered and now become discovered or + * hero's charisma and/or visible worn gear have changed). + */ long tmp = getprice(obj, FALSE), /* used to perform a single calculation even when multiple adjustments (unID'd, dunce/tourist, charisma) are made */ @@ -2085,32 +2904,32 @@ register struct monst *shkp; /* if angry, impose a surcharge */ (((int) ubirthday % obj->otyp) >= obj->otyp / 2); /* all gems are priced high - real or not */ - switch (obj->otyp - LAST_GEM) { - case 1: /* white */ + switch (obj->otyp - FIRST_GLASS_GEM) { + case 0: /* white */ i = pseudorand ? DIAMOND : OPAL; break; - case 2: /* blue */ + case 1: /* blue */ i = pseudorand ? SAPPHIRE : AQUAMARINE; break; - case 3: /* red */ + case 2: /* red */ i = pseudorand ? RUBY : JASPER; break; - case 4: /* yellowish brown */ + case 3: /* yellowish brown */ i = pseudorand ? AMBER : TOPAZ; break; - case 5: /* orange */ + case 4: /* orange */ i = pseudorand ? JACINTH : AGATE; break; - case 6: /* yellow */ + case 5: /* yellow */ i = pseudorand ? CITRINE : CHRYSOBERYL; break; - case 7: /* black */ + case 6: /* black */ i = pseudorand ? BLACK_OPAL : JET; break; - case 8: /* green */ + case 7: /* green */ i = pseudorand ? EMERALD : JADE; break; - case 9: /* violet */ + case 8: /* violet */ i = pseudorand ? AMETHYST : FLUORITE; break; default: @@ -2173,15 +2992,15 @@ register struct monst *shkp; /* if angry, impose a surcharge */ * a different price quoted for selling as vs. buying. */ long -contained_cost(obj, shkp, price, usell, unpaid_only) -struct obj *obj; -struct monst *shkp; -long price; -boolean usell; -boolean unpaid_only; -{ - register struct obj *otmp, *top; - xchar x, y; +contained_cost( + struct obj *obj, + struct monst *shkp, + long price, + boolean usell, + boolean unpaid_only) +{ + struct obj *otmp, *top; + coordxy x, y; boolean on_floor, freespot; for (top = obj; top->where == OBJ_CONTAINED; top = top->ocontainer) @@ -2224,29 +3043,30 @@ boolean unpaid_only; /* count amount of gold inside container 'obj' and any nested containers */ long -contained_gold(obj) -struct obj *obj; +contained_gold( + struct obj *obj, + boolean even_if_unknown) /* T: all gold; F: limit to known contents */ { - register struct obj *otmp; - register long value = 0L; + struct obj *otmp; + long value = 0L; /* accumulate contained gold */ for (otmp = obj->cobj; otmp; otmp = otmp->nobj) if (otmp->oclass == COIN_CLASS) value += otmp->quan; - else if (Has_contents(otmp)) - value += contained_gold(otmp); + else if (Has_contents(otmp) && (otmp->cknown || even_if_unknown)) + value += contained_gold(otmp, even_if_unknown); return value; } -STATIC_OVL void -dropped_container(obj, shkp, sale) -register struct obj *obj; -register struct monst *shkp; -register boolean sale; +staticfn void +dropped_container( + struct obj *obj, + struct monst *shkp, + boolean sale) { - register struct obj *otmp; + struct obj *otmp; /* the "top" container is treated in the calling fn */ for (otmp = obj->cobj; otmp; otmp = otmp->nobj) { @@ -2262,10 +3082,9 @@ register boolean sale; } void -picked_container(obj) -register struct obj *obj; +picked_container(struct obj *obj) { - register struct obj *otmp; + struct obj *otmp; /* the "top" container is treated in the calling fn */ for (otmp = obj->cobj; otmp; otmp = otmp->nobj) { @@ -2280,11 +3099,11 @@ register struct obj *obj; } } -STATIC_OVL boolean -special_stock(obj, shkp, quietly) -struct obj *obj; -struct monst *shkp; -boolean quietly; +staticfn boolean +special_stock( + struct obj *obj, + struct monst *shkp, + boolean quietly) { /* for unique situations */ if (ESHK(shkp)->shoptype == CANDLESHOP @@ -2296,22 +3115,27 @@ boolean quietly; Shknam(shkp), (obj->spe < 7) ? "horrified" : "concerned"); } else { + SetVoice(shkp, 0, 80, 0); verbalize("No thanks, I'd hang onto that if I were you."); - if (obj->spe < 7) + if (obj->spe < 7) { + SetVoice(shkp, 0, 80, 0); verbalize( "You'll need %d%s candle%s to go along with it.", (7 - obj->spe), (obj->spe > 0) ? " more" : "", plur(7 - obj->spe)); + } /* [what if hero is already carrying enough candles? should Izchak explain how to attach them instead?] */ } } else { - if (!Deaf && !muteshk(shkp)) + if (!Deaf && !muteshk(shkp)) { + SetVoice(shkp, 0, 80, 0); verbalize("I won't stock that. Take it out of here!"); - else + } else { pline("%s shakes %s %s in refusal.", Shknam(shkp), noit_mhis(shkp), mbodypart(shkp, HEAD)); + } } } return TRUE; @@ -2320,10 +3144,8 @@ boolean quietly; } /* calculate how much the shk will pay when buying [all of] an object */ -STATIC_OVL long -set_cost(obj, shkp) -register struct obj *obj; -register struct monst *shkp; +staticfn long +set_cost(struct obj *obj, struct monst *shkp) { long tmp, unit_price = getprice(obj, TRUE), multiplier = 1L, divisor = 1L; @@ -2344,8 +3166,9 @@ register struct monst *shkp; /* different shop keepers give different prices */ if (objects[obj->otyp].oc_material == GEMSTONE || objects[obj->otyp].oc_material == GLASS) { - tmp = (obj->otyp % (6 - shkp->m_id % 3)); + tmp = ((obj->otyp - FIRST_REAL_GEM) % (6 - shkp->m_id % 3)); tmp = (tmp + 3) * obj->quan; + divisor = 1L; } } else if (tmp > 1L && !(shkp->m_id % 4)) multiplier *= 3L, divisor *= 4L; @@ -2372,8 +3195,7 @@ register struct monst *shkp; /* unlike alter_cost() which operates on a specific item, identifying or forgetting a gem causes all unpaid gems of its type to change value */ void -gem_learned(oindx) -int oindx; +gem_learned(int oindx) { struct obj *obj; struct monst *shkp; @@ -2395,7 +3217,7 @@ int oindx; for (shkp = next_shkp(fmon, TRUE); shkp; shkp = next_shkp(shkp->nmon, TRUE)) { ct = ESHK(shkp)->billct; - bp = ESHK(shkp)->bill; + bp = ESHK(shkp)->bill_p; while (--ct >= 0) { obj = find_oid(bp->bo_id); if (!obj) /* shouldn't happen */ @@ -2412,10 +3234,10 @@ int oindx; on any shop bill, update that bill to reflect the new higher price [if the new price drops for some reason, keep the old one in place] */ void -alter_cost(obj, amt) -struct obj *obj; -long amt; /* if 0, use regular shop pricing, otherwise force amount; - if negative, use abs(amt) even if it's less than old cost */ +alter_cost( + struct obj *obj, + long amt) /* if 0, use regular shop pricing, otherwise force amount; + if negative, use abs(amt) even if it's less than old cost */ { struct bill_x *bp = 0; struct monst *shkp; @@ -2435,14 +3257,17 @@ long amt; /* if 0, use regular shop pricing, otherwise force amount; /* called from doinv(invent.c) for inventory of unpaid objects */ long -unpaid_cost(unp_obj, include_contents) -struct obj *unp_obj; /* known to be unpaid or contain unpaid */ -boolean include_contents; +unpaid_cost( + struct obj *unp_obj, /* known to be unpaid or contain unpaid */ + uchar cost_type) /* COST_NOCONTENTS, COST_CONTENTS, or COST_SINGLEOBJ */ { struct bill_x *bp = (struct bill_x *) 0; - struct monst *shkp; + struct monst *shkp = 0; + char *shop; long amt = 0L; - xchar ox, oy; + +#if 0 /* if two shops share a wall, this might find wrong shk */ + coordxy ox, oy; if (!get_obj_location(unp_obj, &ox, &oy, BURIED_TOO | CONTAINED_TOO)) ox = u.ux, oy = u.uy; /* (shouldn't happen) */ @@ -2455,83 +3280,117 @@ boolean include_contents; if ((bp = onbill(unp_obj, shkp, TRUE)) != 0) break; } +#endif + for (shop = u.ushops; *shop; shop++) { + if ((shkp = shop_keeper(*shop)) != 0) { + if ((bp = onbill(unp_obj, shkp, TRUE))) { + amt = bp->price; + if (cost_type != COST_SINGLEOBJ) { + /* use quan rather than get_pricing_units -- glob weight + should already be factored into bp->price */ + amt *= unp_obj->quan; + } + } + if (cost_type == COST_CONTENTS && Has_contents(unp_obj)) + amt = contained_cost(unp_obj, shkp, amt, FALSE, TRUE); + if (bp || (!unp_obj->unpaid && amt)) + break; + } + } /* onbill() gave no message if unexpected problem occurred */ - if (!shkp || (unp_obj->unpaid && !bp)) { + if (!shkp || (unp_obj->unpaid && !bp)) impossible("unpaid_cost: object wasn't on any bill."); - } else { - if (bp) - amt = unp_obj->quan * bp->price; - if (include_contents && Has_contents(unp_obj)) - amt = contained_cost(unp_obj, shkp, amt, FALSE, TRUE); - } return amt; } -STATIC_OVL void -add_one_tobill(obj, dummy, shkp) -struct obj *obj; -boolean dummy; -struct monst *shkp; +/* add 'obj' to 'shkp's bill */ +staticfn void +add_one_tobill( + struct obj *obj, + boolean dummy, /* True: obj is used up so goes on bill differently */ + struct monst *shkp) { struct eshk *eshkp; struct bill_x *bp; int bct; + boolean unbilled = FALSE; - if (!billable(&shkp, obj, *u.ushops, TRUE)) - return; eshkp = ESHK(shkp); - - if (eshkp->billct == BILLSZ) { - You("got that for free!"); - return; - } - /* normally bill_p gets set up whenever you enter the shop, but obj might be going onto the bill because hero just snagged it with a grappling hook from outside without ever having been inside */ if (!eshkp->bill_p) - eshkp->bill_p = &(eshkp->bill[0]); + eshkp->bill_p = &eshkp->bill[0]; + + if (!billable(&shkp, obj, *u.ushops, TRUE)) { + /* shk doesn't want it */ + unbilled = TRUE; + } else if (eshkp->billct == BILLSZ) { + /* shk's bill is completely full */ + You("got that for free!"); + unbilled = TRUE; + } + /* if not on any list (probably from bill_dummy_object() which creates + a new OBJ_FREE object), don't leave unmanaged object hanging around */ + if (unbilled) { + if (obj->where == OBJ_FREE) + dealloc_obj(obj); /* change to obj->where==OBJ_DELETED */ + return; + } bct = eshkp->billct; - bp = &(eshkp->bill_p[bct]); + bp = &eshkp->bill_p[bct]; bp->bo_id = obj->o_id; bp->bquan = obj->quan; if (dummy) { /* a dummy object must be inserted into */ - bp->useup = 1; /* the billobjs chain here. crucial for */ + bp->useup = TRUE; /* the gb.billobjs chain here. crucial for */ add_to_billobjs(obj); /* eating floorfood in shop. see eat.c */ } else - bp->useup = 0; + bp->useup = FALSE; bp->price = get_cost(obj, shkp); - if (obj->globby) + if (obj->globby) { /* for globs, the amt charged for quan 1 depends on owt */ bp->price *= get_pricing_units(obj); + /* remember the weight this glob had when it was added to bill; + glob oextra_owt field overlays corpse omid field */ + newomid(obj); + OMID(obj) = obj->owt; + } eshkp->billct++; obj->unpaid = 1; + record_price_quote(obj->otyp, bp->price, TRUE); } -STATIC_OVL void -add_to_billobjs(obj) -struct obj *obj; +staticfn void +add_to_billobjs(struct obj *obj) { if (obj->where != OBJ_FREE) panic("add_to_billobjs: obj not free"); if (obj->timed) obj_stop_timers(obj); - obj->nobj = billobjs; - billobjs = obj; + obj->nobj = gb.billobjs; + gb.billobjs = obj; obj->where = OBJ_ONBILL; + + /* if hero drinks a shop-owned potion, it will have been flagged + in_use by dodrink/dopotion but isn't being used up yet because + it stays on the bill; only object sanity checking actually cares */ + obj->in_use = 0; + /* ... same for bypass by destroy_items */ + obj->bypass = 0; } /* recursive billing of objects within containers. */ -STATIC_OVL void -bill_box_content(obj, ininv, dummy, shkp) -register struct obj *obj; -register boolean ininv, dummy; -register struct monst *shkp; +staticfn void +bill_box_content( + struct obj *obj, + boolean ininv, + boolean dummy, + struct monst *shkp) { - register struct obj *otmp; + struct obj *otmp; if (SchroedingersBox(obj)) return; @@ -2547,19 +3406,21 @@ register struct monst *shkp; } } +DISABLE_WARNING_FORMAT_NONLITERAL + /* shopkeeper tells you what you bought or sold, sometimes partly IDing it */ -STATIC_OVL void -shk_names_obj(shkp, obj, fmt, amt, arg) -struct monst *shkp; -struct obj *obj; -const char *fmt; /* "%s %ld %s %s", doname(obj), amt, plur(amt), arg */ -long amt; -const char *arg; +staticfn void +shk_names_obj( + struct monst *shkp, + struct obj *obj, + const char *fmt, /* "%s %ld %s %s", doname(obj), amt, plur(amt), arg */ + long amt, + const char *arg) { char *obj_name, fmtbuf[BUFSZ]; boolean was_unknown = !obj->dknown; - obj->dknown = TRUE; + observe_object(obj); /* Use real name for ordinary weapons/armor, and spell-less * scrolls/books (that is, blank and mail), but only if the * object is within the shk's area of interest/expertise. @@ -2571,7 +3432,7 @@ const char *arg; was_unknown |= !objects[obj->otyp].oc_name_known; makeknown(obj->otyp); } - obj_name = doname(obj); + obj_name = paydoname(obj); /* Use an alternate message when extra information is being provided */ if (was_unknown) { Sprintf(fmtbuf, "%%s; you %s", fmt); @@ -2583,13 +3444,16 @@ const char *arg; } } +RESTORE_WARNING_FORMAT_NONLITERAL + /* decide whether a shopkeeper thinks an item belongs to her */ boolean -billable(shkpp, obj, roomno, reset_nocharge) -struct monst **shkpp; /* in: non-null if shk has been validated; out: shk */ -struct obj *obj; -char roomno; -boolean reset_nocharge; +billable( + struct monst **shkpp, /* in: non-null if shk has been validated; + * out: shk */ + struct obj *obj, + char roomno, + boolean reset_nocharge) { struct monst *shkp = *shkpp; @@ -2609,7 +3473,7 @@ boolean reset_nocharge; /* outer container might be marked no_charge but still have contents which should be charged for; clear no_charge when picking things up */ if (obj->no_charge) { - if (!Has_contents(obj) || (contained_gold(obj) == 0L + if (!Has_contents(obj) || (contained_gold(obj, TRUE) == 0L && contained_cost(obj, shkp, 0L, FALSE, !reset_nocharge) == 0L)) shkp = 0; /* not billable */ @@ -2623,9 +3487,11 @@ boolean reset_nocharge; } void -addtobill(obj, ininv, dummy, silent) -struct obj *obj; -boolean ininv, dummy, silent; +addtobill( + struct obj *obj, + boolean ininv, + boolean dummy, + boolean silent) { struct monst *shkp = 0; long ltmp, cltmp, gltmp; @@ -2636,7 +3502,7 @@ boolean ininv, dummy, silent; return; if (obj->oclass == COIN_CLASS) { - costly_gold(obj->ox, obj->oy, obj->quan); + costly_gold(obj->ox, obj->oy, obj->quan, silent); return; } else if (ESHK(shkp)->billct == BILLSZ) { if (!silent) @@ -2659,7 +3525,7 @@ boolean ininv, dummy, silent; if (container) { cltmp = contained_cost(obj, shkp, cltmp, FALSE, FALSE); - gltmp = contained_gold(obj); + gltmp = contained_gold(obj, TRUE); if (ltmp) add_one_tobill(obj, dummy, shkp); @@ -2670,7 +3536,7 @@ boolean ininv, dummy, silent; ltmp += cltmp; if (gltmp) { - costly_gold(obj->ox, obj->oy, gltmp); + costly_gold(obj->ox, obj->oy, gltmp, silent); if (!ltmp) return; } @@ -2686,6 +3552,9 @@ boolean ininv, dummy, silent; if (!Deaf && !muteshk(shkp) && !silent) { char buf[BUFSZ]; + /* no need to update price quotes here; it was done by + add_one_tobill above */ + if (!ltmp) { pline("%s has no interest in %s.", Shknam(shkp), the(xname(obj))); return; @@ -2696,14 +3565,16 @@ boolean ininv, dummy, silent; } else { long save_quan = obj->quan; - Strcpy(buf, "\"For you, "); + Strcpy(buf, "\"For you,"); if (ANGRY(shkp)) { - Strcat(buf, "scum;"); - } else { + Strcat(buf, " scum;"); + } else if (!ESHK(shkp)->surcharge) { + Strcat(buf, " "); append_honorific(buf); Strcat(buf, "; only"); } obj->quan = 1L; /* fool xname() into giving singular */ + set_voice(shkp, 0, 80, 0); pline("%s %ld %s %s %s%s.\"", buf, ltmp, currency(ltmp), (save_quan > 1L) ? "per" : (contentscount && !obj->unpaid) @@ -2714,46 +3585,47 @@ boolean ininv, dummy, silent; obj->quan = save_quan; } } else if (!silent) { - if (ltmp) + if (ltmp) { + set_voice(shkp, 0, 80, 0); pline_The("list price of %s%s%s is %ld %s%s.", (contentscount && !obj->unpaid) ? the_contents_of : "", the(xname(obj)), (contentscount && obj->unpaid) ? and_its_contents : "", ltmp, currency(ltmp), (obj->quan > 1L) ? " each" : ""); - else + } else { pline("%s does not notice.", Shknam(shkp)); + } } } -STATIC_OVL void -append_honorific(buf) -char *buf; +staticfn void +append_honorific(char *buf) { /* (chooses among [0]..[3] normally; [1]..[4] after the Wizard has been killed or invocation ritual performed) */ - static const char *const honored[] = { "good", "honored", "most gracious", - "esteemed", - "most renowned and sacred" }; + static const char *const honored[] = { + "good", "honored", "most gracious", "esteemed", + "most renowned and sacred" + }; Strcat(buf, honored[rn2(SIZE(honored) - 1) + u.uevent.udemigod]); - if (is_vampire(youmonst.data)) + if (is_vampire(gy.youmonst.data)) Strcat(buf, (flags.female) ? " dark lady" : " dark lord"); - else if (is_elf(youmonst.data)) + else if (maybe_polyd(is_elf(gy.youmonst.data), Race_if(PM_ELF))) Strcat(buf, (flags.female) ? " hiril" : " hir"); else - Strcat(buf, !is_human(youmonst.data) ? " creature" - : (flags.female) ? " lady" - : " sir"); + Strcat(buf, !is_human(gy.youmonst.data) ? " creature" + : (flags.female) ? " lady" + : " sir"); } void -splitbill(obj, otmp) -register struct obj *obj, *otmp; +splitbill(struct obj *obj, struct obj *otmp) { /* otmp has been split off from obj */ - register struct bill_x *bp; - register long tmp; - register struct monst *shkp = shop_keeper(*u.ushops); + struct bill_x *bp; + long tmp; + struct monst *shkp = shop_keeper(*u.ushops); if (!shkp || !inhishop(shkp)) { impossible("splitbill: no resident shopkeeper??"); @@ -2772,53 +3644,44 @@ register struct obj *obj, *otmp; } bp->bquan -= otmp->quan; - if (ESHK(shkp)->billct == BILLSZ) + if (ESHK(shkp)->billct == BILLSZ) { otmp->unpaid = 0; - else { + } else { tmp = bp->price; bp = &(ESHK(shkp)->bill_p[ESHK(shkp)->billct]); bp->bo_id = otmp->o_id; bp->bquan = otmp->quan; - bp->useup = 0; + bp->useup = FALSE; bp->price = tmp; ESHK(shkp)->billct++; } } -STATIC_OVL void -sub_one_frombill(obj, shkp) -register struct obj *obj; -register struct monst *shkp; +staticfn void +sub_one_frombill(struct obj *obj, struct monst *shkp) { - register struct bill_x *bp; + struct bill_x *bp; + struct eshk *eshkp; if ((bp = onbill(obj, shkp, FALSE)) != 0) { - register struct obj *otmp; + struct obj *otmp; obj->unpaid = 0; if (bp->bquan > obj->quan) { otmp = newobj(); *otmp = *obj; otmp->oextra = (struct oextra *) 0; - bp->bo_id = otmp->o_id = context.ident++; + bp->bo_id = otmp->o_id = next_ident(); /* svc.context.ident++ */ otmp->where = OBJ_FREE; otmp->quan = (bp->bquan -= obj->quan); otmp->owt = 0; /* superfluous */ - bp->useup = 1; + bp->useup = TRUE; add_to_billobjs(otmp); return; } - ESHK(shkp)->billct--; -#ifdef DUMB - { - /* DRS/NS 2.2.6 messes up -- Peter Kendell */ - int indx = ESHK(shkp)->billct; - - *bp = ESHK(shkp)->bill_p[indx]; - } -#else - *bp = ESHK(shkp)->bill_p[ESHK(shkp)->billct]; -#endif + eshkp = ESHK(shkp); + eshkp->billct--; + *bp = eshkp->bill_p[eshkp->billct]; return; } else if (obj->unpaid) { impossible("sub_one_frombill: unpaid object not on bill"); @@ -2828,11 +3691,9 @@ register struct monst *shkp; /* recursive check of unpaid objects within nested containers. */ void -subfrombill(obj, shkp) -register struct obj *obj; -register struct monst *shkp; +subfrombill(struct obj *obj, struct monst *shkp) { - register struct obj *otmp; + struct obj *otmp; sub_one_frombill(obj, shkp); @@ -2848,12 +3709,12 @@ register struct monst *shkp; } } -STATIC_OVL long -stolen_container(obj, shkp, price, ininv) -struct obj *obj; -struct monst *shkp; -long price; -boolean ininv; +staticfn long +stolen_container( + struct obj *obj, + struct monst *shkp, + long price, + boolean ininv) { struct obj *otmp; struct bill_x *bp; @@ -2868,6 +3729,7 @@ boolean ininv; /* billable() returns false for objects already on bill */ if ((bp = onbill(otmp, shkp, FALSE)) == 0) continue; + assert(shkp != NULL); /* onbill() found shkp so it's not Null */ /* this assumes that we're being called by stolen_value() (or by a recursive call to self on behalf of it) where the cost of this object is about to be added to shop @@ -2889,18 +3751,26 @@ boolean ininv; } long -stolen_value(obj, x, y, peaceful, silent) -struct obj *obj; -xchar x, y; -boolean peaceful, silent; +stolen_value( + struct obj *obj, + coordxy x, + coordxy y, + boolean peaceful, + boolean silent) { long value = 0L, gvalue = 0L, billamt = 0L; - char roomno = *in_rooms(x, y, SHOPBASE); + char roomno; struct bill_x *bp; - struct monst *shkp = 0; + struct monst *shkp; boolean was_unpaid; long c_count = 0L, u_count = 0L; + if ((shkp = find_objowner(obj, x, y)) != (struct monst *) 0) { + roomno = ESHK(shkp)->shoproom; + } else { + roomno = *in_rooms(x, y, SHOPBASE); + } + /* gather information for message(s) prior to manipulating bill */ was_unpaid = obj->unpaid ? TRUE : FALSE; if (Has_contents(obj)) { @@ -2908,10 +3778,12 @@ boolean peaceful, silent; u_count = count_contents(obj, TRUE, FALSE, FALSE, FALSE); } - if (!billable(&shkp, obj, roomno, FALSE)) { + shkp = (struct monst *) 0; + if (!billable(&shkp, obj, roomno, TRUE)) { /* things already on the bill yield a not-billable result, so we need to check bill before deciding that shk doesn't care */ if ((bp = onbill(obj, shkp, FALSE)) != 0) { + assert(shkp != NULL); /* onbill() found shkp so it's not Null */ /* shk does care; take obj off bill to avoid double billing */ billamt = bp->bquan * bp->price; sub_one_frombill(obj, shkp); @@ -2934,7 +3806,7 @@ boolean peaceful, silent; value += stolen_container(obj, shkp, 0L, ininv); if (!ininv) - gvalue += contained_gold(obj); + gvalue += contained_gold(obj, TRUE); } } @@ -2988,7 +3860,7 @@ boolean peaceful, silent; if (!silent) { if (canseemon(shkp)) { Norep("%s booms: \"%s, you are a thief!\"", - Shknam(shkp), plname); + Shknam(shkp), svp.plname); } else if (!Deaf) { Norep("You hear a scream, \"Thief!\""); /* Deaf-aware */ } @@ -2999,16 +3871,46 @@ boolean peaceful, silent; return value; } -/* auto-response flag for/from "sell foo?" 'a' => 'y', 'q' => 'n' */ -static char sell_response = 'a'; -static int sell_how = SELL_NORMAL; -/* can't just use sell_response='y' for auto_credit because the 'a' response - shouldn't carry over from ordinary selling to credit selling */ -static boolean auto_credit = FALSE; +/* opposite of costly_gold(); hero has dropped gold in a shop; + called from sellobj(); ought to be called from subfrombill() too */ +void +donate_gold( + long gltmp, + struct monst *shkp, + boolean selling) /* T: dropped in shop; F: kicked and landed in shop */ +{ + struct eshk *eshkp = ESHK(shkp); + + if (eshkp->debit >= gltmp) { + if (eshkp->loan) { /* you carry shop's gold */ + if (eshkp->loan > gltmp) + eshkp->loan -= gltmp; + else + eshkp->loan = 0L; + } + eshkp->debit -= gltmp; + Your("debt is %spaid off.", eshkp->debit ? "partially " : ""); + } else { + long delta = gltmp - eshkp->debit; + + eshkp->credit += delta; + if (eshkp->debit) { + eshkp->debit = 0L; + eshkp->loan = 0L; + Your("debt is paid off."); + } + if (eshkp->credit == delta) + You("have %sestablished %ld %s credit.", + !selling ? "re-" : "", delta, currency(delta)); + else + pline("%ld %s added%s to your credit; total is now %ld %s.", + delta, currency(delta), !selling ? " back" : "", + eshkp->credit, currency(eshkp->credit)); + } +} void -sellobj_state(deliberate) -int deliberate; +sellobj_state(int deliberate) { /* If we're deliberately dropping something, there's no automatic response to the shopkeeper's "want to sell" query; however, if we @@ -3016,18 +3918,18 @@ int deliberate; This retains the old pre-query risk that slippery fingers while in shops entailed: you drop it, you've lost it. */ - sell_response = (deliberate != SELL_NORMAL) ? '\0' : 'a'; - sell_how = deliberate; - auto_credit = FALSE; + gs.sell_response = (deliberate != SELL_NORMAL) ? '\0' : 'a'; + gs.sell_how = deliberate; + ga.auto_credit = FALSE; } void -sellobj(obj, x, y) -register struct obj *obj; -xchar x, y; +sellobj( + struct obj *obj, + coordxy x, coordxy y) { - register struct monst *shkp; - register struct eshk *eshkp; + struct monst *shkp; + struct eshk *eshkp; long ltmp = 0L, cltmp = 0L, gltmp = 0L, offer, shkmoney; boolean saleitem, cgold = FALSE, container = Has_contents(obj); boolean isgold = (obj->oclass == COIN_CLASS); @@ -3035,7 +3937,8 @@ xchar x, y; if (!*u.ushops) /* do cheapest exclusion test first */ return; - if (!(shkp = shop_keeper(*in_rooms(x, y, SHOPBASE))) || !inhishop(shkp)) + shkp = shop_keeper(*in_rooms(x, y, SHOPBASE)); + if (!shkp || !inhishop(shkp)) return; if (!costly_spot(x, y)) return; @@ -3048,7 +3951,7 @@ xchar x, y; /* find the price of content before subfrombill */ cltmp = contained_cost(obj, shkp, cltmp, TRUE, FALSE); /* find the value of contained gold */ - gltmp += contained_gold(obj); + gltmp += contained_gold(obj, TRUE); cgold = (gltmp > 0L); } @@ -3058,9 +3961,24 @@ xchar x, y; offer = ltmp + cltmp; + /* you dropped something of your own - probably want to sell it */ + rouse_shk(shkp, TRUE); /* wake up sleeping or paralyzed shk */ + eshkp = ESHK(shkp); + + if (ANGRY(shkp)) { /* they become shop-objects, no pay */ + if (!Deaf && !muteshk(shkp)) { + SetVoice(shkp, 0, 80, 0); + verbalize("Thank you, scum!"); + } else { + pline("%s smirks with satisfaction.", Shknam(shkp)); + } + subfrombill(obj, shkp); + return; + } + /* get one case out of the way: nothing to sell, and no gold */ if (!(isgold || cgold) - && ((offer + gltmp) == 0L || sell_how == SELL_DONTSELL)) { + && ((offer + gltmp) == 0L || gs.sell_how == SELL_DONTSELL)) { boolean unpaid = is_unpaid(obj); if (container) { @@ -3072,35 +3990,24 @@ xchar x, y; } else obj->no_charge = 1; - if (!unpaid && (sell_how != SELL_DONTSELL) + if (!unpaid && (gs.sell_how != SELL_DONTSELL) && !special_stock(obj, shkp, FALSE)) pline("%s seems uninterested.", Shknam(shkp)); return; } - /* you dropped something of your own - probably want to sell it */ - rouse_shk(shkp, TRUE); /* wake up sleeping or paralyzed shk */ - eshkp = ESHK(shkp); - - if (ANGRY(shkp)) { /* they become shop-objects, no pay */ - if (!Deaf && !muteshk(shkp)) - verbalize("Thank you, scum!"); - else - pline("%s smirks with satisfaction.", Shknam(shkp)); - subfrombill(obj, shkp); - return; - } - - if (eshkp->robbed) { /* shkp is not angry? */ + if (eshkp->robbed) { /* bones; shop robbed by previous customer */ if (isgold) offer = obj->quan; else if (cgold) offer += cgold; if ((eshkp->robbed -= offer < 0L)) eshkp->robbed = 0L; - if (offer && !Deaf && !muteshk(shkp)) + if (offer && !Deaf && !muteshk(shkp)) { + SetVoice(shkp, 0, 80, 0); verbalize( "Thank you for your contribution to restock this recently plundered shop."); + } subfrombill(obj, shkp); return; } @@ -3109,34 +4016,9 @@ xchar x, y; if (!cgold) gltmp = obj->quan; - if (eshkp->debit >= gltmp) { - if (eshkp->loan) { /* you carry shop's gold */ - if (eshkp->loan >= gltmp) - eshkp->loan -= gltmp; - else - eshkp->loan = 0L; - } - eshkp->debit -= gltmp; - Your("debt is %spaid off.", eshkp->debit ? "partially " : ""); - } else { - long delta = gltmp - eshkp->debit; - - eshkp->credit += delta; - if (eshkp->debit) { - eshkp->debit = 0L; - eshkp->loan = 0L; - Your("debt is paid off."); - } - if (eshkp->credit == delta) - You("have established %ld %s credit.", delta, - currency(delta)); - else - pline("%ld %s added to your credit; total is now %ld %s.", - delta, currency(delta), eshkp->credit, - currency(eshkp->credit)); - } + donate_gold(gltmp, shkp, TRUE); - if (!offer || sell_how == SELL_DONTSELL) { + if (!offer || gs.sell_how == SELL_DONTSELL) { if (!isgold) { if (container) dropped_container(obj, shkp, FALSE); @@ -3166,33 +4048,35 @@ xchar x, y; char c, qbuf[BUFSZ]; long tmpcr = ((offer * 9L) / 10L) + (offer <= 1L); - if (sell_how == SELL_NORMAL || auto_credit) { - c = sell_response = 'y'; - } else if (sell_response != 'n') { + if (gs.sell_how == SELL_NORMAL || ga.auto_credit) { + c = gs.sell_response = 'y'; + } else if (gs.sell_response != 'n') { pline("%s cannot pay you at present.", Shknam(shkp)); Sprintf(qbuf, "Will you accept %ld %s in credit for ", tmpcr, currency(tmpcr)); + record_price_quote(obj->otyp, tmpcr / obj->quan, FALSE); c = ynaq(safe_qbuf(qbuf, qbuf, "?", obj, doname, thesimpleoname, (obj->quan == 1L) ? "that" : "those")); if (c == 'a') { c = 'y'; - auto_credit = TRUE; + ga.auto_credit = TRUE; } } else /* previously specified "quit" */ c = 'n'; if (c == 'y') { - shk_names_obj( - shkp, obj, - (sell_how != SELL_NORMAL) - ? "traded %s for %ld zorkmid%s in %scredit." - : "relinquish %s and acquire %ld zorkmid%s in %scredit.", - tmpcr, (eshkp->credit > 0L) ? "additional " : ""); + shk_names_obj(shkp, obj, + ((gs.sell_how != SELL_NORMAL) + ? "traded %s for %ld zorkmid%s in %scredit." + : "relinquish %s and acquire %ld zorkmid%s in %scredit."), + tmpcr, (eshkp->credit > 0L) ? "additional " : ""); eshkp->credit += tmpcr; + if (container) + dropped_container(obj, shkp, TRUE); subfrombill(obj, shkp); } else { if (c == 'q') - sell_response = 'n'; + gs.sell_response = 'n'; if (container) dropped_container(obj, shkp, FALSE); if (!obj->unpaid) @@ -3205,7 +4089,7 @@ xchar x, y; if (short_funds) offer = shkmoney; - if (!sell_response) { + if (!gs.sell_response) { long yourc = 0L, shksc; if (container) { @@ -3266,14 +4150,16 @@ xchar x, y; : and_its_contents) : "", one ? "it" : "them"); + record_price_quote(obj->otyp, offer / obj->quan, FALSE); (void) safe_qbuf(qbuf, qbuf, qsfx, obj, xname, simpleonames, one ? "that" : "those"); } else qbuf[0] = '\0'; /* just to pacify lint */ - switch (sell_response ? sell_response : ynaq(qbuf)) { + switch (gs.sell_response ? gs.sell_response : nyaq(qbuf)) { case 'q': - sell_response = 'n'; + gs.sell_response = 'n'; + FALLTHROUGH; /*FALLTHRU*/ case 'n': if (container) @@ -3283,7 +4169,8 @@ xchar x, y; subfrombill(obj, shkp); break; case 'a': - sell_response = 'y'; + gs.sell_response = 'y'; + FALLTHROUGH; /*FALLTHRU*/ case 'y': if (container) @@ -3293,7 +4180,7 @@ xchar x, y; subfrombill(obj, shkp); pay(-offer, shkp); shk_names_obj(shkp, obj, - (sell_how != SELL_NORMAL) + (gs.sell_how != SELL_NORMAL) ? ((!ltmp && cltmp && only_partially_your_contents) ? "sold some items inside %s for %ld gold piece%s.%s" : "sold %s for %ld gold piece%s.%s") @@ -3307,12 +4194,9 @@ xchar x, y; } int -doinvbill(mode) -int mode; /* 0: deliver count 1: paged */ +doinvbill( + int mode) /* 0: deliver count 1: paged */ { -#ifdef __SASC - void sasc_bug(struct obj *, unsigned); -#endif struct monst *shkp; struct eshk *eshkp; struct bill_x *bp, *end_bp; @@ -3386,12 +4270,55 @@ int mode; /* 0: deliver count 1: paged */ return 0; } -STATIC_OVL long -getprice(obj, shk_buying) -register struct obj *obj; -boolean shk_buying; +/* adjust tin, egg, or corpse price based on monster data */ +staticfn long +corpsenm_price_adj(struct obj *obj) { - register long tmp = (long) objects[obj->otyp].oc_cost; + long val = 0L; + + if ((obj->otyp == TIN || obj->otyp == EGG || obj->otyp == CORPSE) + && ismnum(obj->corpsenm)) { + int i; + long tmp = 1L; + struct permonst *ptr = &mons[obj->corpsenm]; + struct { + int trinsic; + int cost; + } const icost[] = { + { FIRE_RES, 2 }, + { SLEEP_RES, 3 }, + { COLD_RES, 2 }, + { DISINT_RES, 5 }, + { SHOCK_RES, 4 }, + { POISON_RES, 2 }, + { ACID_RES, 1 }, + { STONE_RES, 3 }, + { TELEPORT, 2 }, + { TELEPORT_CONTROL, 3 }, + { TELEPAT, 5 } + }; + + for (i = 0; i < SIZE(icost); i++) + if (intrinsic_possible(icost[i].trinsic, ptr)) + tmp += icost[i].cost; + if (unique_corpstat(ptr)) + tmp += 50; + + + val = max(1, ((ptr->mlevel - 1) * 2)); + if (obj->otyp == CORPSE) + val += max(1, (ptr->cnutrit / 30)); + + val = val * tmp; + } + + return val; +} + +staticfn long +getprice(struct obj *obj, boolean shk_buying) +{ + long tmp = (long) objects[obj->otyp].oc_cost; if (obj->oartifact) { tmp = arti_cost(obj); @@ -3400,6 +4327,8 @@ boolean shk_buying; } switch (obj->oclass) { case FOOD_CLASS: + tmp += corpsenm_price_adj(obj); + /* simpler hunger check, (2-4)*cost */ if (u.uhs >= HUNGRY && !shk_buying) tmp *= (long) u.uhs; @@ -3430,29 +4359,33 @@ boolean shk_buying; /* shk catches thrown pick-axe */ struct monst * -shkcatch(obj, x, y) -register struct obj *obj; -register xchar x, y; +shkcatch( + struct obj *obj, + coordxy x, coordxy y) { - register struct monst *shkp; + struct monst *shkp; - if (!(shkp = shop_keeper(inside_shop(x, y))) || !inhishop(shkp)) + shkp = shop_keeper(inside_shop(x, y)); + if (!shkp || !inhishop(shkp)) return 0; - if (shkp->mcanmove && !shkp->msleeping + if (!helpless(shkp) && (*u.ushops != ESHK(shkp)->shoproom || !inside_shop(u.ux, u.uy)) && dist2(shkp->mx, shkp->my, x, y) < 3 /* if it is the shk's pos, you hit and anger him */ && (shkp->mx != x || shkp->my != y)) { - if (mnearto(shkp, x, y, TRUE) == 2 && !Deaf && !muteshk(shkp)) + if (mnearto(shkp, x, y, TRUE, RLOC_NOMSG) == 2 + && !Deaf && !muteshk(shkp)) { + SetVoice(shkp, 0, 80, 0); verbalize("Out of my way, scum!"); + } if (cansee(x, y)) { pline("%s nimbly%s catches %s.", Shknam(shkp), (x == shkp->mx && y == shkp->my) ? "" : " reaches over and", the(xname(obj))); if (!canspotmon(shkp)) map_invisible(x, y); - delay_output(); + nh_delay_output(); mark_synch(); } subfrombill(obj, shkp); @@ -3463,9 +4396,10 @@ register xchar x, y; } void -add_damage(x, y, cost) -register xchar x, y; -long cost; +add_damage( + coordxy x, + coordxy y, + long cost) { struct damage *tmp_dam; char *shops; @@ -3481,273 +4415,225 @@ long cost; if (!*shops) return; } - for (tmp_dam = level.damagelist; tmp_dam; tmp_dam = tmp_dam->next) + for (tmp_dam = svl.level.damagelist; tmp_dam; tmp_dam = tmp_dam->next) if (tmp_dam->place.x == x && tmp_dam->place.y == y) { tmp_dam->cost += cost; - tmp_dam->when = monstermoves; /* needed by pay_for_damage() */ + tmp_dam->when = svm.moves; /* needed by pay_for_damage() */ return; } tmp_dam = (struct damage *) alloc((unsigned) sizeof *tmp_dam); (void) memset((genericptr_t) tmp_dam, 0, sizeof *tmp_dam); - tmp_dam->when = monstermoves; + tmp_dam->when = svm.moves; tmp_dam->place.x = x; tmp_dam->place.y = y; tmp_dam->cost = cost; tmp_dam->typ = levl[x][y].typ; - tmp_dam->next = level.damagelist; - level.damagelist = tmp_dam; - /* If player saw damage, display as a wall forever */ + tmp_dam->flags = levl[x][y].flags; + tmp_dam->next = svl.level.damagelist; + svl.level.damagelist = tmp_dam; + /* If player saw damage, display walls post-repair as walls, not stone */ if (cansee(x, y)) levl[x][y].seenv = SVALL; } -/* - * Do something about damage. Either (!croaked) try to repair it, or - * (croaked) just discard damage structs for non-shared locations, since - * they'll never get repaired. Assume that shared locations will get - * repaired eventually by the other shopkeeper(s). This might be an erroneous - * assumption (they might all be dead too), but we have no reasonable way of - * telling that. - */ -STATIC_OVL -void -remove_damage(shkp, croaked) -struct monst *shkp; -boolean croaked; -{ - struct damage *tmp_dam, *tmp2_dam; - struct obj *shk_inv = shkp->minvent; - boolean did_repair = FALSE, saw_door = FALSE, saw_floor = FALSE, - stop_picking = FALSE, doorway_trap = FALSE, skip_msg = FALSE; - int saw_walls = 0, saw_untrap = 0, feedback; - char trapmsg[BUFSZ]; - - feedback = !croaked; /* 1 => give feedback, 0 => don't or already did */ - tmp_dam = level.damagelist; - tmp2_dam = 0; - while (tmp_dam) { - register xchar x = tmp_dam->place.x, y = tmp_dam->place.y; - char shops[5]; - int disposition; - unsigned old_doormask = 0; - - disposition = 0; - Strcpy(shops, in_rooms(x, y, SHOPBASE)); - if (index(shops, ESHK(shkp)->shoproom)) { - if (IS_DOOR(levl[x][y].typ)) - old_doormask = levl[x][y].doormask; - - if (croaked) { - disposition = (shops[1]) ? 0 : 1; - } else if (stop_picking) { - disposition = repair_damage(shkp, tmp_dam, &feedback, FALSE); - } else { - /* Defer the stop_occupation() until after repair msgs */ - if (closed_door(x, y)) - stop_picking = picking_at(x, y); - disposition = repair_damage(shkp, tmp_dam, &feedback, FALSE); - if (!disposition) - stop_picking = FALSE; - } - } +/* is shopkeeper impaired, so they cannot act? */ +staticfn boolean +shk_impaired(struct monst *shkp) +{ + if (!shkp || !shkp->isshk || !inhishop(shkp)) + return TRUE; + if (helpless(shkp) || ESHK(shkp)->following) + return TRUE; + return FALSE; +} - if (!disposition) { - tmp2_dam = tmp_dam; - tmp_dam = tmp_dam->next; - continue; - } +/* is damage dam repairable by shopkeeper shkp? */ +staticfn boolean +repairable_damage(struct damage *dam, struct monst *shkp) +{ + coordxy x, y; + struct trap *ttmp; + struct monst *mtmp; - if (disposition > 1) { - did_repair = TRUE; - if (cansee(x, y)) { - if (IS_WALL(levl[x][y].typ)) { - saw_walls++; - } else if (IS_DOOR(levl[x][y].typ) - /* an existing door here implies trap removal */ - && !(old_doormask & (D_ISOPEN | D_CLOSED))) { - saw_door = TRUE; - } else if (disposition == 3) { /* untrapped */ - saw_untrap++; - if (IS_DOOR(levl[x][y].typ)) - doorway_trap = TRUE; - } else { - saw_floor = TRUE; - } - } - } + if (!dam || shk_impaired(shkp)) + return FALSE; - tmp_dam = tmp_dam->next; - if (!tmp2_dam) { - free((genericptr_t) level.damagelist); - level.damagelist = tmp_dam; - } else { - free((genericptr_t) tmp2_dam->next); - tmp2_dam->next = tmp_dam; - } + x = dam->place.x; + y = dam->place.y; + + /* too soon to fix it? */ + if ((svm.moves - dam->when) < REPAIR_DELAY) + return FALSE; + /* is it a wall? don't fix if anyone is in the way */ + if (!IS_ROOM(dam->typ)) { + if ((u_at(x, y) && !Passes_walls) + || (x == shkp->mx && y == shkp->my) + || ((mtmp = m_at(x, y)) != 0 && !passes_walls(mtmp->data))) + return FALSE; } - if (!did_repair) + /* is it a trap? don't fix if hero or monster is in it */ + ttmp = t_at(x, y); + if (ttmp) { + if (u_at(x, y)) + return FALSE; + if ((mtmp = m_at(x,y)) != 0 && mtmp->mtrapped) + return FALSE; + } + /* does it belong to shkp? */ + if (!strchr(in_rooms(x, y, SHOPBASE), ESHK(shkp)->shoproom)) + return FALSE; + + return TRUE; +} + +/* find any damage shopkeeper shkp could repair. returns NULL is none found */ +staticfn struct damage * +find_damage(struct monst *shkp) +{ + struct damage *dam = svl.level.damagelist; + + if (shk_impaired(shkp)) + return NULL; + + while (dam) { + if (repairable_damage(dam, shkp)) + return dam; + + dam = dam->next; + } + + return NULL; +} + +staticfn void +discard_damage_struct(struct damage *dam) +{ + if (!dam) return; - trapmsg[0] = '\0'; /* not just lint suppression... */ - shk_inv = (shkp->minvent != shk_inv) ? shkp->minvent : 0; - if (saw_untrap == 1 && shk_inv - && (shk_inv->otyp == BEARTRAP || shk_inv->otyp == LAND_MINE) - && canseemon(shkp)) { - pline("%s untraps %s.", Shknam(shkp), ansimpleoname(shk_inv)); - /* we've already reported this trap (and know it's the only one) */ - saw_untrap = 0; - skip_msg = !(saw_walls || saw_door || saw_floor); - } else if (saw_untrap) { - Sprintf(trapmsg, "%s trap%s", - (saw_untrap > 3) ? "several" : (saw_untrap > 1) ? "some" - : "a", - plur(saw_untrap)); - Sprintf(eos(trapmsg), " %s", vtense(trapmsg, "are")); - Sprintf(eos(trapmsg), " removed from the %s", - (doorway_trap && saw_untrap == 1) ? "doorway" : "floor"); - } - - if (skip_msg) { - ; /* already gave an untrap message which covered the only repair */ - } else if (saw_walls) { - char wallbuf[BUFSZ]; - - Sprintf(wallbuf, "section%s", plur(saw_walls)); - pline("Suddenly, %s %s of wall %s up!", - (saw_walls == 1) ? "a" : (saw_walls <= 3) ? "some" : "several", - wallbuf, vtense(wallbuf, "close")); - - if (saw_door) - pline_The("shop door reappears!"); - if (saw_floor) - pline_The("floor is repaired!"); - if (saw_untrap) - pline("%s!", upstart(trapmsg)); + if (dam == svl.level.damagelist) { + svl.level.damagelist = dam->next; } else { - if (saw_door || saw_floor || saw_untrap) - pline("Suddenly, %s%s%s%s%s!", - saw_door ? "the shop door reappears" : "", - (saw_door && saw_floor) ? " and " : "", - saw_floor ? "the floor damage is gone" : "", - ((saw_door || saw_floor) && *trapmsg) ? " and " : "", - trapmsg); - /* FIXME: - * these messages aren't right if the unseen repairs were only - * for trap removal (except for hole and possibly trap door). - */ - else if (inside_shop(u.ux, u.uy) == ESHK(shkp)->shoproom) - You_feel("more claustrophobic than before."); - else if (!Deaf && !rn2(10)) - Norep("The dungeon acoustics noticeably change."); + struct damage *prev = svl.level.damagelist; + + while (prev && prev->next != dam) + prev = prev->next; + if (prev) + prev->next = dam->next; } - if (stop_picking) - stop_occupation(); + (void) memset(dam, 0, sizeof *dam); + free((genericptr_t) dam); } -/* - * 0: repair postponed, 1: silent repair (no messages), 2: normal repair - * 3: untrap - */ -int -repair_damage(shkp, tmp_dam, once, catchup) -struct monst *shkp; -struct damage *tmp_dam; -int *once; -boolean catchup; /* restoring a level */ -{ - xchar x, y; - xchar litter[9]; - struct monst *mtmp; - struct obj *otmp; - struct trap *ttmp; - int i, k, ix, iy, disposition = 1; +/* discard all damage structs owned by shopkeeper */ +staticfn void +discard_damage_owned_by(struct monst *shkp) +{ + struct damage *dam = svl.level.damagelist, *dam2, *prevdam = NULL; + + while (dam) { + coordxy x = dam->place.x, y = dam->place.y; + + if (strchr(in_rooms(x, y, SHOPBASE), ESHK(shkp)->shoproom)) { + dam2 = dam->next; + if (prevdam) + prevdam->next = dam2; + if (dam == svl.level.damagelist) + svl.level.damagelist = dam2; + (void) memset(dam, 0, sizeof *dam); + free((genericptr_t) dam), dam = (struct damage *) NULL; + } else { + prevdam = dam; + dam2 = dam->next; + } - if ((monstermoves - tmp_dam->when) < REPAIR_DELAY) - return 0; - if (shkp->msleeping || !shkp->mcanmove || ESHK(shkp)->following) - return 0; - x = tmp_dam->place.x; - y = tmp_dam->place.y; - if (!IS_ROOM(tmp_dam->typ)) { - if ((x == u.ux && y == u.uy && !Passes_walls) - || (x == shkp->mx && y == shkp->my) - || ((mtmp = m_at(x, y)) != 0 && !passes_walls(mtmp->data))) - return 0; + dam = dam2; } - ttmp = t_at(x, y); - if (ttmp && x == u.ux && y == u.uy && !Passes_walls) - return 0; +} + +/* Shopkeeper tries to repair damage belonging to them */ +staticfn void +shk_fixes_damage(struct monst *shkp) +{ + struct damage *dam = find_damage(shkp); + boolean shk_closeby; - if (once && *once) { - boolean shk_closeby = (distu(shkp->mx, shkp->my) - <= (BOLT_LIM / 2) * (BOLT_LIM / 2)); + if (!dam) + return; - /* this is suboptimal if we eventually give a "shk untraps" - message for the only repair, but perhaps the shop repair - incantation means that shk's untrap attempt will never fail */ - if (canseemon(shkp)) - pline("%s whispers %s.", Shknam(shkp), - shk_closeby ? "an incantation" : "something"); - else if (!Deaf && shk_closeby) - You_hear("someone muttering an incantation."); - *once = 0; - } - if (ttmp) { - if ((ttmp->ttyp == LANDMINE || ttmp->ttyp == BEAR_TRAP) - && dist2(x, y, shkp->mx, shkp->my) <= 2) { - /* convert to an object */ - otmp = mksobj((ttmp->ttyp == LANDMINE) ? LAND_MINE : BEARTRAP, - TRUE, FALSE); - otmp->quan = 1L; - otmp->owt = weight(otmp); - (void) mpickobj(shkp, otmp); - } - deltrap(ttmp); - if (cansee(x, y)) - newsym(x, y); - if (!catchup) - disposition = 3; + shk_closeby = (mdistu(shkp) <= (BOLT_LIM / 2) * (BOLT_LIM / 2)); + + if (canseemon(shkp)) { + pline("%s whispers %s.", Shknam(shkp), + shk_closeby ? "an incantation" : "something"); + } else if (!Deaf && shk_closeby) { + Soundeffect(se_mutter_incantation, 100); + You_hear("someone muttering an incantation."); } - if (IS_ROOM(tmp_dam->typ) - || (tmp_dam->typ == levl[x][y].typ - && (!IS_DOOR(tmp_dam->typ) || levl[x][y].doormask > D_BROKEN))) - /* no terrain fix necessary (trap removal or manually repaired) */ - return disposition; - /* door or wall repair; trap, if any, is now gone; - restore original terrain type and move any items away */ - levl[x][y].typ = tmp_dam->typ; - if (IS_DOOR(tmp_dam->typ)) - levl[x][y].doormask = D_CLOSED; /* arbitrary */ + (void) repair_damage(shkp, dam, FALSE); + + discard_damage_struct(dam); +} - (void) memset((genericptr_t) litter, 0, sizeof litter); -#define NEED_UPDATE 1 -#define OPEN 2 -#define INSHOP 4 +#define LITTER_UPDATE 0x01U +#define LITTER_OPEN 0x02U +#define LITTER_INSHOP 0x04U #define horiz(i) ((i % 3) - 1) #define vert(i) ((i / 3) - 1) - k = 0; /* number of adjacent shop spots */ - if (level.objects[x][y] && !IS_ROOM(levl[x][y].typ)) { + +/* find eligible spots to move items from a gap in a shop's wall that is + being repaired; this guarantees that items will end up inside shkp's + shop (possibly in the "free spot" or even in doorway or an adjacent + wall gap), but if they are in a gap in a wall shared by two shops + they might have started in the other shop */ +staticfn uint8 +litter_getpos( + uint8 *litter, /* array of 9 uint8's */ + coordxy x, coordxy y, + struct monst *shkp) +{ + int i, ix, iy; + uint8 k = 0; /* number of adjacent shop spots */ + + (void) memset((genericptr_t) litter, 0, 9 * sizeof *litter); + + if (svl.level.objects[x][y] && !IS_ROOM(levl[x][y].typ)) { for (i = 0; i < 9; i++) { ix = x + horiz(i); iy = y + vert(i); if (i == 4 || !isok(ix, iy) || !ZAP_POS(levl[ix][iy].typ)) continue; - litter[i] = OPEN; + litter[i] = LITTER_OPEN; if (inside_shop(ix, iy) == ESHK(shkp)->shoproom) { - litter[i] |= INSHOP; + litter[i] |= LITTER_INSHOP; ++k; } } } - /* placement below assumes there is always at least one adjacent - spot; the 'k' check guards against getting stuck in an infinite - loop if some irregularly shaped room breaks that assumption */ - if (k > 0) { + return k; +} + +/* move items from a gap in a shop's wall that is being repaired; + litter[] guarantees that items will end up inside shkp's shop, but + if the wall being repaired is shared by two shops the items might + have started in the other shop */ +staticfn void +litter_scatter( + uint8 *litter, + coordxy x, coordxy y, + struct monst *shkp) +{ + struct obj *otmp; + + /* placement below assumes there is always at least one adjacent spot + that's inside the shop; caller guarantees that */ + { /* Scatter objects haphazardly into the shop */ if (Punished && !u.uswallow && ((uchain->ox == x && uchain->oy == y) - || (uball->ox == x && uball->oy == y))) { + || (uball->where == OBJ_FLOOR + && uball->ox == x && uball->oy == y))) { /* * Either the ball or chain is in the repair location. * Take the easy way out and put ball&chain under hero. @@ -3758,25 +4644,28 @@ boolean catchup; /* restoring a level */ * a slang connotation which could be applicable if hero * has Passes_walls ability. */ - if (!Deaf && !muteshk(shkp)) + if (!Deaf && !muteshk(shkp)) { + SetVoice(shkp, 0, 80, 0); verbalize("Get your junk out of my wall!"); + } unplacebc(); /* pick 'em up */ placebc(); /* put 'em down */ } - while ((otmp = level.objects[x][y]) != 0) + while ((otmp = svl.level.objects[x][y]) != 0) { /* Don't mess w/ boulders -- just merge into wall */ if (otmp->otyp == BOULDER || otmp->otyp == ROCK) { obj_extract_self(otmp); obfree(otmp, (struct obj *) 0); } else { - int trylimit = 50; + int trylimit = 10; + int i = rn2(9), ix, iy; - /* otmp must be moved otherwise level.objects[x][y] will + /* otmp must be moved otherwise svl.level.objects[x][y] will never become Null and while-loop won't terminate */ do { - i = rn2(9); - } while (--trylimit && !(litter[i] & INSHOP)); - if ((litter[i] & (OPEN | INSHOP)) != 0) { + i = (i + 1) % 9; + } while (--trylimit && !(litter[i] & LITTER_INSHOP)); + if ((litter[i] & (LITTER_OPEN | LITTER_INSHOP)) != 0) { ix = x + horiz(i); iy = y + vert(i); } else { @@ -3785,43 +4674,212 @@ boolean catchup; /* restoring a level */ ix = shkp->mx; iy = shkp->my; } + /* if the wall being repaired is shared by two adjacent + shops, might be in a different shop than the + one that is billing for otmp or decided it was free; + control of the item goes to the shk repairing the wall + but otmp->no_charge isn't recalculated for new shop */ + if (otmp->unpaid) { + struct monst *oshk = shkp; + + /* !costly_spot() happens if otmp is moved from wall + to shop's "free spot", still costly_adjacent() and + still unpaid/on-bill; otherwise, it is being moved + all the way into the shop so take it off the bill */ + if (costly_spot(ix, iy) + && ((onbill(otmp, oshk, TRUE) + || ((oshk = find_objowner(otmp, ix, iy)) != 0 + && onbill(otmp, oshk, FALSE))))) + subfrombill(otmp, oshk); + } + if (otmp->no_charge) { + /* not strictly necessary; destination is inside a + shop so existing no_charge remains relevant */ + if (!costly_spot(ix, iy) + && !costly_adjacent(shkp, ix, iy)) + otmp->no_charge = 0; + } + remove_object(otmp); place_object(otmp, ix, iy); - litter[i] |= NEED_UPDATE; + litter[i] |= LITTER_UPDATE; } + } /* while level.objects[x][y] != 0 */ } +} + +staticfn void +litter_newsyms(uint8 *litter, coordxy x, coordxy y) +{ + int i; + + for (i = 0; i < 9; i++) + if (litter[i] & LITTER_UPDATE) + newsym(x + horiz(i), y + vert(i)); +} + +#undef LITTER_UPDATE +#undef LITTER_OPEN +#undef LITTER_INSHOP +#undef vert +#undef horiz + +/* + * 0: repair postponed, 1: silent repair (no messages), 2: normal repair + * 3: untrap + */ +staticfn int +repair_damage( + struct monst *shkp, + struct damage *tmp_dam, + boolean catchup) +{ + coordxy x, y; + uint8 litter[9]; + struct obj *otmp; + struct trap *ttmp; + int disposition = 1; + boolean seeit, stop_picking = FALSE; + + if (!repairable_damage(tmp_dam, shkp)) + return 0; + + x = tmp_dam->place.x; + y = tmp_dam->place.y; + seeit = cansee(x, y); + + ttmp = t_at(x, y); + if (ttmp) { + switch (ttmp->ttyp) { + case LANDMINE: + case BEAR_TRAP: + /* convert to an object */ + otmp = mksobj((ttmp->ttyp == LANDMINE) ? LAND_MINE : BEARTRAP, + TRUE, FALSE); + otmp->quan = 1L; + otmp->owt = weight(otmp); + if (!catchup) { + if (canseemon(shkp) && dist2(x, y, shkp->mx, shkp->my) <= 2) + pline("%s untraps %s.", Shknam(shkp), ansimpleoname(otmp)); + else if (ttmp->tseen && cansee(ttmp->tx, ttmp->ty)) + pline("The %s vanishes.", trapname(ttmp->ttyp, TRUE)); + } + (void) mpickobj(shkp, otmp); + break; + case HOLE: + case PIT: + case SPIKED_PIT: + if (!catchup && ttmp->tseen && cansee(ttmp->tx, ttmp->ty)) + pline("The %s is filled in.", trapname(ttmp->ttyp, TRUE)); + break; + default: + if (!catchup && ttmp->tseen && cansee(ttmp->tx, ttmp->ty)) + pline("The %s vanishes.", trapname(ttmp->ttyp, TRUE)); + break; + } + deltrap(ttmp); + del_engr_at(x, y); + if (seeit) + newsym(x, y); + if (!catchup) + disposition = 3; + } + if (IS_ROOM(tmp_dam->typ) + || (tmp_dam->typ == levl[x][y].typ + && (!IS_DOOR(tmp_dam->typ) || levl[x][y].doormask > D_BROKEN))) + /* no terrain fix necessary (trap removal or manually repaired) */ + return disposition; + + if (closed_door(x, y)) + stop_picking = picking_at(x, y); + + /* door or wall repair; trap, if any, is now gone; + restore original terrain type and move any items away; + rm.doormask and rm.wall_info are both overlaid on rm.flags + so the new flags value needs to match the restored typ */ + levl[x][y].typ = tmp_dam->typ; + if (IS_DOOR(tmp_dam->typ)) + levl[x][y].doormask = D_CLOSED; /* arbitrary */ + else /* not a door; set rm.wall_info or whatever old flags are relevant */ + levl[x][y].flags = tmp_dam->flags; + + if (litter_getpos(litter, x, y, shkp)) + litter_scatter(litter, x, y, shkp); + del_engr_at(x, y); + + /* needed if hero has line-of-sight to the former gap from outside + the shop but is farther than one step away; once the light inside + the shop is blocked, the other newsym() below won't redraw the + spot showing its repaired wall */ + if (seeit) + newsym(x, y); + block_point(x, y); + if (catchup) return 1; /* repair occurred while off level so no messages */ - block_point(x, y); - if (cansee(x, y)) { - if (IS_WALL(tmp_dam->typ)) + if (seeit) { + if (IS_WALL(tmp_dam->typ)) { /* player sees actual repair process, so KNOWS it's a wall */ levl[x][y].seenv = SVALL; + pline("Suddenly, a section of the wall closes up!"); + } else if (IS_DOOR(tmp_dam->typ)) { + pline("Suddenly, the shop door reappears!"); + } newsym(x, y); + } else if (IS_WALL(tmp_dam->typ)) { + if (inside_shop(u.ux, u.uy) == ESHK(shkp)->shoproom) + You_feel("more claustrophobic than before."); + else if (!Deaf && !rn2(10)) + Norep("The dungeon acoustics noticeably change."); } - for (i = 0; i < 9; i++) - if (litter[i] & NEED_UPDATE) - newsym(x + horiz(i), y + vert(i)); + + if (stop_picking) + stop_occupation(); + + litter_newsyms(litter, x, y); if (disposition < 3) disposition = 2; return disposition; -#undef NEED_UPDATE -#undef OPEN -#undef INSHOP -#undef vert -#undef horiz +} + +/* normally repair is done when a shopkeeper moves, but we also try to + catch up for lost time when reloading a previously visited level */ +void +fix_shop_damage(void) +{ + struct monst *shkp; + struct damage *damg, *nextdamg; + + /* if this level has no shop damage, there's nothing to do */ + if (!svl.level.damagelist) + return; + + /* go through all shopkeepers on the level */ + for (shkp = next_shkp(fmon, FALSE); shkp; + shkp = next_shkp(shkp->nmon, FALSE)) { + /* if this shopkeeper isn't in his shop or can't move, skip */ + if (shk_impaired(shkp)) + continue; + /* go through all damage data trying to have this shopkeeper + fix it; repair_damage() will only make repairs for damage + matching shop controlled by specified shopkeeper */ + for (damg = svl.level.damagelist; damg; damg = nextdamg) { + nextdamg = damg->next; + if (repair_damage(shkp, damg, TRUE)) + discard_damage_struct(damg); + } + } } /* * shk_move: return 1: moved 0: didn't -1: let m_move do it -2: died */ int -shk_move(shkp) -struct monst *shkp; +shk_move(struct monst *shkp) { - xchar gx, gy, omx, omy; + coordxy gtx, gty, omx, omy; int udist; schar appr; struct eshk *eshkp = ESHK(shkp); @@ -3832,33 +4890,37 @@ struct monst *shkp; omy = shkp->my; if (inhishop(shkp)) - remove_damage(shkp, FALSE); + shk_fixes_damage(shkp); if ((udist = distu(omx, omy)) < 3 && (shkp->data != &mons[PM_GRID_BUG] || (omx == u.ux || omy == u.uy))) { - if (ANGRY(shkp) || (Conflict && !resist(shkp, RING_CLASS, 0, 0))) { + if (ANGRY(shkp) || (Conflict && !resist_conflict(shkp))) { if (Displaced) Your("displaced image doesn't fool %s!", shkname(shkp)); (void) mattacku(shkp); return 0; } if (eshkp->following) { - if (strncmp(eshkp->customer, plname, PL_NSIZ)) { - if (!Deaf && !muteshk(shkp)) + if (strncmp(eshkp->customer, svp.plname, PL_NSIZ)) { + if (!Deaf && !muteshk(shkp)) { + SetVoice(shkp, 0, 80, 0); verbalize("%s, %s! I was looking for %s.", Hello(shkp), - plname, eshkp->customer); + svp.plname, eshkp->customer); + } eshkp->following = 0; return 0; } - if (moves > followmsg + 4) { - if (!Deaf && !muteshk(shkp)) + if (svm.moves > gf.followmsg + 4) { + if (!Deaf && !muteshk(shkp)) { + SetVoice(shkp, 0, 80, 0); verbalize("%s, %s! Didn't you forget to pay?", - Hello(shkp), plname); - else + Hello(shkp), svp.plname); + } else { pline("%s holds out %s upturned %s.", Shknam(shkp), noit_mhis(shkp), mbodypart(shkp, HAND)); - followmsg = moves; + } + gf.followmsg = svm.moves; if (!rn2(9)) { pline("%s doesn't like customers who don't pay.", Shknam(shkp)); @@ -3871,9 +4933,9 @@ struct monst *shkp; } appr = 1; - gx = eshkp->shk.x; - gy = eshkp->shk.y; - satdoor = (gx == omx && gy == omy); + gtx = eshkp->shk.x; + gty = eshkp->shk.y; + satdoor = (gtx == omx && gty == omy); if (eshkp->following || ((z = holetime()) >= 0 && z * z <= udist)) { /* [This distance check used to apply regardless of whether the shk was following, but that resulted in @@ -3884,31 +4946,30 @@ struct monst *shkp; next level once the character fell through the hole.] */ if (udist > 4 && eshkp->following && !eshkp->billct) return -1; /* leave it to m_move */ - gx = u.ux; - gy = u.uy; + gtx = u.ux; + gty = u.uy; } else if (ANGRY(shkp)) { /* Move towards the hero if the shopkeeper can see him. */ if (shkp->mcansee && m_canseeu(shkp)) { - gx = u.ux; - gy = u.uy; + gtx = u.ux; + gty = u.uy; } avoid = FALSE; } else { -#define GDIST(x, y) (dist2(x, y, gx, gy)) +#define GDIST(x, y) (dist2(x, y, gtx, gty)) if (Invis || u.usteed) { avoid = FALSE; } else { - uondoor = (u.ux == eshkp->shd.x && u.uy == eshkp->shd.y); + uondoor = u_at(eshkp->shd.x, eshkp->shd.y); if (uondoor) { - badinv = - (carrying(PICK_AXE) || carrying(DWARVISH_MATTOCK) - || (Fast && (sobj_at(PICK_AXE, u.ux, u.uy) + badinv = (carrying(PICK_AXE) || carrying(DWARVISH_MATTOCK) + || (Fast && (sobj_at(PICK_AXE, u.ux, u.uy) || sobj_at(DWARVISH_MATTOCK, u.ux, u.uy)))); if (satdoor && badinv) return 0; avoid = !badinv; } else { - avoid = (*u.ushops && distu(gx, gy) > 8); + avoid = (*u.ushops && distu(gtx, gty) > 8); badinv = FALSE; } @@ -3917,62 +4978,52 @@ struct monst *shkp; if (!badinv && !onlineu(omx, omy)) return 0; if (satdoor) - appr = gx = gy = 0; + appr = gtx = gty = 0; } } +#undef GDIST } - z = move_special(shkp, inhishop(shkp), appr, uondoor, avoid, omx, omy, gx, - gy); + z = move_special(shkp, inhishop(shkp), appr, uondoor, avoid, omx, omy, + gtx, gty); if (z > 0) after_shk_move(shkp); return z; } -/* called after shopkeeper moves, in case themove causes re-entry into shop */ +/* called after shopkeeper moves, in case move causes re-entry into shop */ void -after_shk_move(shkp) -struct monst *shkp; +after_shk_move(struct monst *shkp) { struct eshk *eshkp = ESHK(shkp); if (eshkp->bill_p == (struct bill_x *) -1000 && inhishop(shkp)) { /* reset bill_p, need to re-calc player's occupancy too */ eshkp->bill_p = &eshkp->bill[0]; - check_special_room(FALSE); + /* only re-check occupancy if game hasn't just ended */ + if (!program_state.gameover) + check_special_room(FALSE); } } /* for use in levl_follower (mondata.c) */ boolean -is_fshk(mtmp) -register struct monst *mtmp; +is_fshk(struct monst *mtmp) { return (boolean) (mtmp->isshk && ESHK(mtmp)->following); } /* You are digging in the shop. */ void -shopdig(fall) -register int fall; +shopdig(int fall) { - register struct monst *shkp = shop_keeper(*u.ushops); + struct monst *shkp = shop_keeper(*u.ushops); int lang; const char *grabs = "grabs"; if (!shkp) return; - - /* 0 == can't speak, 1 == makes animal noises, 2 == speaks */ - lang = 0; - if (shkp->msleeping || !shkp->mcanmove || is_silent(shkp->data)) - ; /* lang stays 0 */ - else if (shkp->data->msound <= MS_ANIMAL) - lang = 1; - else if (shkp->data->msound >= MS_HUMANOID) - lang = 2; - if (!inhishop(shkp)) { if (Role_if(PM_KNIGHT)) { You_feel("like a common thief."); @@ -3980,17 +5031,27 @@ register int fall; } return; } + /* 0 == can't speak, 1 == makes animal noises, 2 == speaks */ + lang = 0; + if (helpless(shkp) || is_silent(shkp->data)) + ; /* lang stays 0 */ + else if (shkp->data->msound <= MS_ANIMAL) + lang = 1; + else if (shkp->data->msound >= MS_HUMANOID) + lang = 2; if (!fall) { if (lang == 2) { if (!Deaf && !muteshk(shkp)) { - if (u.utraptype == TT_PIT) + SetVoice(shkp, 0, 80, 0); + if (u.utraptype == TT_PIT) { verbalize( - "Be careful, %s, or you might fall through the floor.", - flags.female ? "madam" : "sir"); - else + "Be careful, %s, or you might fall through the floor.", + flags.female ? "madam" : "sir"); + } else { verbalize("%s, do not damage the floor here!", - flags.female ? "Madam" : "Sir"); + flags.female ? "Madam" : "Sir"); + } } } if (Role_if(PM_KNIGHT)) { @@ -3998,9 +5059,9 @@ register int fall; adjalign(-sgn(u.ualign.type)); } } else if (!um_dist(shkp->mx, shkp->my, 5) - && !shkp->msleeping && shkp->mcanmove + && !helpless(shkp) && (ESHK(shkp)->billct || ESHK(shkp)->debit)) { - register struct obj *obj, *obj2; + struct obj *obj, *obj2; if (nolimbs(shkp->data)) { grabs = "knocks off"; @@ -4015,10 +5076,10 @@ register int fall; return; #endif } - if (distu(shkp->mx, shkp->my) > 2) { - mnexto(shkp); + if (!m_next2u(shkp)) { + mnexto(shkp, RLOC_MSG); /* for some reason the shopkeeper can't come next to you */ - if (distu(shkp->mx, shkp->my) > 2) { + if (!m_next2u(shkp)) { if (lang == 2) pline("%s curses you in anger and frustration!", Shknam(shkp)); @@ -4032,13 +5093,13 @@ register int fall; } else pline("%s %s your backpack!", Shknam(shkp), grabs); - for (obj = invent; obj; obj = obj2) { + for (obj = gi.invent; obj; obj = obj2) { obj2 = obj->nobj; if ((obj->owornmask & ~(W_SWAPWEP | W_QUIVER)) != 0 || (obj == uswapwep && u.twoweap) || (obj->otyp == LEASH && obj->leashmon)) continue; - if (obj == current_wand) + if (obj == gc.current_wand) continue; setnotworn(obj); freeinv(obj); @@ -4048,9 +5109,8 @@ register int fall; } } -STATIC_OVL void -makekops(mm) -coord *mm; +staticfn void +makekops(coord *mm) { static const short k_mndx[4] = { PM_KEYSTONE_KOP, PM_KOP_SERGEANT, PM_KOP_LIEUTENANT, PM_KOP_KAPTAIN }; @@ -4065,27 +5125,59 @@ coord *mm; if ((cnt = k_cnt[k]) == 0) break; mndx = k_mndx[k]; - if (mvitals[mndx].mvflags & G_GONE) + if (svm.mvitals[mndx].mvflags & G_GONE) continue; while (cnt--) if (enexto(mm, mm->x, mm->y, &mons[mndx])) - (void) makemon(&mons[mndx], mm->x, mm->y, NO_MM_FLAGS); + (void) makemon(&mons[mndx], mm->x, mm->y, MM_NOMSG); } } +staticfn void +getcad( + struct monst *shkp, const char *dmgstr, coordxy x, coordxy y, + boolean uinshp, boolean animal, boolean pursue) +{ + boolean dugwall = (!strcmp(dmgstr, "dig into") /* wand */ + || !strcmp(dmgstr, "damage")); /* pick-axe */ + + if (muteshk(shkp)) { + if (animal && !helpless(shkp)) + yelp(shkp); + } else if (pursue || uinshp || !um_dist(x, y, 1)) { + if (!Deaf) { + SetVoice(shkp, 0, 80, 0); + verbalize("How dare you %s my %s?", dmgstr, + dugwall ? "shop" : "door"); + } else { + pline("%s is %s that you decided to %s %s %s!", + Shknam(shkp), ROLL_FROM(angrytexts), + dmgstr, noit_mhis(shkp), dugwall ? "shop" : "door"); + } + } else { + if (!Deaf) { + pline("%s shouts:", Shknam(shkp)); + SetVoice(shkp, 0, 80, 0); + verbalize("Who dared %s my %s?", dmgstr, + dugwall ? "shop" : "door"); + } else { + pline("%s is %s that someone decided to %s %s %s!", + Shknam(shkp), ROLL_FROM(angrytexts), + dmgstr, noit_mhis(shkp), dugwall ? "shop" : "door"); + } + } + hot_pursuit(shkp); +} + void -pay_for_damage(dmgstr, cant_mollify) -const char *dmgstr; -boolean cant_mollify; +pay_for_damage(const char *dmgstr, boolean cant_mollify) { - register struct monst *shkp = (struct monst *) 0; + struct monst *shkp = (struct monst *) 0; char shops_affected[5]; boolean uinshp = (*u.ushops != '\0'); char qbuf[80]; - xchar x, y; - boolean dugwall = (!strcmp(dmgstr, "dig into") /* wand */ - || !strcmp(dmgstr, "damage")); /* pick-axe */ + coordxy x, y; boolean animal, pursue; struct damage *tmp_dam, *appear_here = 0; long cost_of_damage = 0L; @@ -4093,10 +5185,10 @@ boolean cant_mollify; nearest_damage = nearest_shk; int picks = 0; - for (tmp_dam = level.damagelist; tmp_dam; tmp_dam = tmp_dam->next) { + for (tmp_dam = svl.level.damagelist; tmp_dam; tmp_dam = tmp_dam->next) { char *shp; - if (tmp_dam->when != monstermoves || !tmp_dam->cost) + if (tmp_dam->when != svm.moves || !tmp_dam->cost) continue; cost_of_damage += tmp_dam->cost; Strcpy(shops_affected, @@ -4119,7 +5211,7 @@ boolean cant_mollify; } if (!inhishop(tmp_shk)) continue; - shk_distance = distu(tmp_shk->mx, tmp_shk->my); + shk_distance = mdistu(tmp_shk); if (shk_distance > nearest_shk) continue; if ((shk_distance == nearest_shk) && picks) { @@ -4143,7 +5235,7 @@ boolean cant_mollify; y = appear_here->place.y; /* not the best introduction to the shk... */ - (void) strncpy(ESHK(shkp)->customer, plname, PL_NSIZ); + (void) strncpy(ESHK(shkp)->customer, svp.plname, PL_NSIZ); /* if the shk is already on the war path, be sure it's all out */ if (ANGRY(shkp) || ESHK(shkp)->following) { @@ -4156,18 +5248,21 @@ boolean cant_mollify; if (!cansee(shkp->mx, shkp->my)) return; pursue = TRUE; - goto getcad; + getcad(shkp, dmgstr, x, y, uinshp, animal, pursue); + return; } if (uinshp) { if (um_dist(shkp->mx, shkp->my, 1) && !um_dist(shkp->mx, shkp->my, 3)) { pline("%s leaps towards you!", Shknam(shkp)); - mnexto(shkp); + mnexto(shkp, RLOC_NOMSG); } pursue = um_dist(shkp->mx, shkp->my, 1); - if (pursue) - goto getcad; + if (pursue) { + getcad(shkp, dmgstr, x, y, uinshp, animal, pursue); + return; + } } else { /* * Make shkp show up at the door. Effect: If there is a monster @@ -4178,7 +5273,9 @@ boolean cant_mollify; if (MON_AT(x, y)) { if (!animal) { if (!Deaf && !muteshk(shkp)) { + /* Soundeffect(se_angry_voice, 75); */ You_hear("an angry voice:"); + SetVoice(shkp, 0, 80, 0); verbalize("Out of my way, scum!"); } wait_synch(); @@ -4192,36 +5289,13 @@ boolean cant_mollify; growl(shkp); } } - (void) mnearto(shkp, x, y, TRUE); + (void) mnearto(shkp, x, y, TRUE, RLOC_MSG); } if ((um_dist(x, y, 1) && !uinshp) || cant_mollify - || (money_cnt(invent) + ESHK(shkp)->credit) < cost_of_damage + || (money_cnt(gi.invent) + ESHK(shkp)->credit) < cost_of_damage || !rn2(50)) { - getcad: - if (muteshk(shkp)) { - if (animal && shkp->mcanmove && !shkp->msleeping) - yelp(shkp); - } else if (pursue || uinshp || !um_dist(x, y, 1)) { - if (!Deaf) - verbalize("How dare you %s my %s?", dmgstr, - dugwall ? "shop" : "door"); - else - pline("%s is %s that you decided to %s %s %s!", - Shknam(shkp), angrytexts[rn2(SIZE(angrytexts))], - dmgstr, noit_mhis(shkp), dugwall ? "shop" : "door"); - } else { - if (!Deaf) { - pline("%s shouts:", Shknam(shkp)); - verbalize("Who dared %s my %s?", dmgstr, - dugwall ? "shop" : "door"); - } else { - pline("%s is %s that someone decided to %s %s %s!", - Shknam(shkp), angrytexts[rn2(SIZE(angrytexts))], - dmgstr, noit_mhis(shkp), dugwall ? "shop" : "door"); - } - } - hot_pursuit(shkp); + getcad(shkp, dmgstr, x, y, uinshp, animal, pursue); return; } @@ -4230,24 +5304,40 @@ boolean cant_mollify; Sprintf(qbuf, "%sYou did %ld %s worth of damage!%s Pay?", !animal ? cad(TRUE) : "", cost_of_damage, currency(cost_of_damage), !animal ? "\"" : ""); - if (yn(qbuf) != 'n') { + if (y_n(qbuf) != 'n') { + boolean is_seen, was_seen = canseemon(shkp), + was_outside = !inhishop(shkp); + coordxy sx = shkp->mx, sy = shkp->my; + cost_of_damage = check_credit(cost_of_damage, shkp); if (cost_of_damage > 0L) { money2mon(shkp, cost_of_damage); - context.botl = 1; + disp.botl = TRUE; } pline("Mollified, %s accepts your restitution.", shkname(shkp)); /* move shk back to his home loc */ home_shk(shkp, FALSE); - pacify_shk(shkp); + pacify_shk(shkp, FALSE); + /* home_shk() suppresses rloc()'s vanish/appear messages */ + if (shkp->mx != sx || shkp->my != sy) { + if (was_outside && canspotmon(shkp)) + pline("%s returns to %s shop.", Shknam(shkp), + noit_mhis(shkp)); + else if ((is_seen = canseemon(shkp)) == TRUE || was_seen) + pline("%s %s.", Shknam(shkp), !was_seen ? "appears" + : is_seen ? "shifts location" + : "disappears"); + } } else { if (!animal) { - if (!Deaf && !muteshk(shkp)) + if (!Deaf && !muteshk(shkp)) { + SetVoice(shkp, 0, 80, 0); verbalize("Oh, yes! You'll pay!"); - else + } else { pline("%s lunges %s %s toward your %s!", Shknam(shkp), noit_mhis(shkp), mbodypart(shkp, HAND), body_part(NECK)); + } } else growl(shkp); hot_pursuit(shkp); @@ -4257,13 +5347,12 @@ boolean cant_mollify; /* called in dokick.c when we kick an object that might be in a store */ boolean -costly_spot(x, y) -register xchar x, y; +costly_spot(coordxy x, coordxy y) { struct monst *shkp; struct eshk *eshkp; - if (!level.flags.has_shop) + if (!svl.level.flags.has_shop) return FALSE; shkp = shop_keeper(*in_rooms(x, y, SHOPBASE)); if (!shkp || !inhishop(shkp)) @@ -4273,40 +5362,62 @@ register xchar x, y; && !(x == eshkp->shk.x && y == eshkp->shk.y)); } +/* called by sanity checking when an unpaid or no_charge item is not at a + costly_spot; it might still be within the boundary of the shop; if so, + those flags are still valid */ +boolean +costly_adjacent( + struct monst *shkp, + coordxy x, coordxy y) +{ + struct eshk *eshkp; + + if (!shkp || !inhishop(shkp) || !isok(x, y)) + return FALSE; + eshkp = ESHK(shkp); + /* adjacent if is a shop wall spot, including door; + also treat "free spot" one step inside the door as adjacent */ + return (levl[x][y].edge || (x == eshkp->shk.x && y == eshkp->shk.y)); +} + /* called by dotalk(sounds.c) when #chatting; returns obj if location contains shop goods and shopkeeper is willing & able to speak */ struct obj * -shop_object(x, y) -register xchar x, y; +shop_object(coordxy x, coordxy y) { - register struct obj *otmp; - register struct monst *shkp; + struct obj *otmp; + struct monst *shkp; - if (!(shkp = shop_keeper(*in_rooms(x, y, SHOPBASE))) || !inhishop(shkp)) + shkp = shop_keeper(*in_rooms(x, y, SHOPBASE)); + if (!shkp || !inhishop(shkp)) return (struct obj *) 0; - for (otmp = level.objects[x][y]; otmp; otmp = otmp->nexthere) + for (otmp = svl.level.objects[x][y]; otmp; otmp = otmp->nexthere) if (otmp->oclass != COIN_CLASS) break; /* note: otmp might have ->no_charge set, but that's ok */ - return (otmp && costly_spot(x, y) - && NOTANGRY(shkp) && shkp->mcanmove && !shkp->msleeping) + return (otmp && costly_spot(x, y) && NOTANGRY(shkp) && !muteshk(shkp)) ? otmp : (struct obj *) 0; } /* give price quotes for all objects linked to this one (ie, on this spot) */ void -price_quote(first_obj) -register struct obj *first_obj; +price_quote(struct obj *first_obj) { - register struct obj *otmp; + struct obj *otmp; char buf[BUFSZ], price[40]; long cost = 0L; int cnt = 0; boolean contentsonly = FALSE; winid tmpwin; - struct monst *shkp = shop_keeper(inside_shop(u.ux, u.uy)); + struct monst *shkp; + + shkp = shop_keeper(inside_shop(u.ux, u.uy)); + /* caller has verified that there is a shopkeeper, but the static + analyzer doesn't realize it */ + if (!shkp || !inhishop(shkp)) + return; tmpwin = create_nhwindow(NHW_MENU); putstr(tmpwin, 0, "Fine goods for sale:"); @@ -4337,27 +5448,27 @@ register struct obj *first_obj; } else if (cnt == 1) { if (!cost) { /* ", no charge" */ - pline("%s!", upstart(buf)); /* buf still contains the string */ + SetVoice(shkp, 0, 80, 0); + verbalize("%s!", upstart(buf)); /* buf contains the string */ } else { /* print cost in slightly different format, so can't reuse buf; cost and contentsonly are already set up */ Sprintf(buf, "%s%s", contentsonly ? the_contents_of : "", doname(first_obj)); - pline("%s, price %ld %s%s%s", upstart(buf), cost, currency(cost), - (first_obj->quan > 1L) ? " each" : "", - contentsonly ? "." : shk_embellish(first_obj, cost)); + SetVoice(shkp, 0, 80, 0); + verbalize("%s, price %ld %s%s%s", upstart(buf), cost, + currency(cost), (first_obj->quan > 1L) ? " each" : "", + contentsonly ? "." : shk_embellish(first_obj, cost)); } } destroy_nhwindow(tmpwin); } -STATIC_OVL const char * -shk_embellish(itm, cost) -register struct obj *itm; -long cost; +staticfn const char * +shk_embellish(struct obj *itm, long cost) { if (!rn2(3)) { - register int o, choice = rn2(5); + int o, choice = rn2(5); if (choice == 0) choice = (cost < 100L ? 1 : cost < 500L ? 2 : 3); @@ -4391,8 +5502,10 @@ long cost; return "."; } +DISABLE_WARNING_FORMAT_NONLITERAL + /* First 4 supplied by Ronen and Tamar, remainder by development team */ -const char *Izchak_speaks[] = { +static const char *Izchak_speaks[] = { "%s says: 'These shopping malls give me a headache.'", "%s says: 'Slow down. Think clearly.'", "%s says: 'You need to take things one at a time.'", @@ -4405,8 +5518,7 @@ const char *Izchak_speaks[] = { }; void -shk_chat(shkp) -struct monst *shkp; +shk_chat(struct monst *shkp) { struct eshk *eshk; long shkmoney; @@ -4431,21 +5543,25 @@ struct monst *shkp; (!Deaf && !muteshk(shkp)) ? "mentions" : "indicates", noit_mhe(shkp), eshk->robbed ? "non-paying" : "rude"); } else if (eshk->following) { - if (strncmp(eshk->customer, plname, PL_NSIZ)) { - if (!Deaf && !muteshk(shkp)) + if (strncmp(eshk->customer, svp.plname, PL_NSIZ)) { + if (!Deaf && !muteshk(shkp)) { + SetVoice(shkp, 0, 80, 0); verbalize("%s %s! I was looking for %s.", - Hello(shkp), plname, eshk->customer); + Hello(shkp), svp.plname, eshk->customer); + } eshk->following = 0; } else { - if (!Deaf && !muteshk(shkp)) + if (!Deaf && !muteshk(shkp)) { + SetVoice(shkp, 0, 80, 0); verbalize("%s %s! Didn't you forget to pay?", - Hello(shkp), plname); - else + Hello(shkp), svp.plname); + } else { pline("%s taps you on the %s.", Shknam(shkp), body_part(ARM)); + } } } else if (eshk->billct) { - register long total = addupbill(shkp) + eshk->debit; + long total = addupbill(shkp) + eshk->debit; pline("%s %s that your bill comes to %ld %s.", Shknam(shkp), @@ -4463,6 +5579,10 @@ struct monst *shkp; pline("%s %s about a recent robbery.", Shknam(shkp), (!Deaf && !muteshk(shkp)) ? "complains" : "indicates concern"); + } else if (eshk->surcharge) { + pline("%s %s that %s is watching you carefully.", Shknam(shkp), + (!Deaf && !muteshk(shkp)) ? "warns you" : "indicates", + noit_mhe(shkp)); } else if ((shkmoney = money_cnt(shkp->minvent)) < 50L) { pline("%s %s that business is bad.", Shknam(shkp), @@ -4473,22 +5593,25 @@ struct monst *shkp; (!Deaf && !muteshk(shkp)) ? "says" : "indicates"); } else if (is_izchak(shkp, FALSE)) { if (!Deaf && !muteshk(shkp)) - pline(Izchak_speaks[rn2(SIZE(Izchak_speaks))], shkname(shkp)); + pline(ROLL_FROM(Izchak_speaks), shkname(shkp)); } else { if (!Deaf && !muteshk(shkp)) pline("%s talks about the problem of shoplifters.", Shknam(shkp)); } } -STATIC_OVL void -kops_gone(silent) -boolean silent; +RESTORE_WARNING_FORMAT_NONLITERAL + +staticfn void +kops_gone(boolean silent) { - register int cnt = 0; - register struct monst *mtmp, *mtmp2; + int cnt = 0; + struct monst *mtmp, *mtmp2; for (mtmp = fmon; mtmp; mtmp = mtmp2) { mtmp2 = mtmp->nmon; + if (DEADMONSTER(mtmp)) + continue; if (mtmp->data->mlet == S_KOP) { if (canspotmon(mtmp)) cnt++; @@ -4500,11 +5623,11 @@ boolean silent; plur(cnt), (cnt == 1) ? "es" : ""); } -STATIC_OVL long -cost_per_charge(shkp, otmp, altusage) -struct monst *shkp; -struct obj *otmp; -boolean altusage; /* some items have an "alternate" use with different cost */ +staticfn long +cost_per_charge( + struct monst *shkp, + struct obj *otmp, + boolean altusage) /* some items have "alternate" use with different cost */ { long tmp = 0L; @@ -4554,15 +5677,15 @@ boolean altusage; /* some items have an "alternate" use with different cost */ return tmp; } +DISABLE_WARNING_FORMAT_NONLITERAL + /* Charge the player for partial use of an unpaid object. * * Note that bill_dummy_object() should be used instead * when an object is completely used. */ void -check_unpaid_usage(otmp, altusage) -struct obj *otmp; -boolean altusage; +check_unpaid_usage(struct obj *otmp, boolean altusage) { struct monst *shkp; const char *fmt, *arg1, *arg2; @@ -4572,7 +5695,8 @@ boolean altusage; if (!otmp->unpaid || !*u.ushops || (otmp->spe <= 0 && objects[otmp->otyp].oc_charged)) return; - if (!(shkp = shop_keeper(*u.ushops)) || !inhishop(shkp)) + shkp = shop_keeper(*u.ushops); + if (!shkp || !inhishop(shkp)) return; if ((tmp = cost_per_charge(shkp, otmp, altusage)) == 0L) return; @@ -4601,49 +5725,60 @@ boolean altusage; } if (!Deaf && !muteshk(shkp)) { + SetVoice(shkp, 0, 80, 0); verbalize(fmt, arg1, arg2, tmp, currency(tmp)); exercise(A_WIS, TRUE); /* you just got info */ } ESHK(shkp)->debit += tmp; } +RESTORE_WARNING_FORMAT_NONLITERAL + /* for using charges of unpaid objects "used in the normal manner" */ void -check_unpaid(otmp) -struct obj *otmp; +check_unpaid(struct obj *otmp) { check_unpaid_usage(otmp, FALSE); /* normal item use */ } void -costly_gold(x, y, amount) -register xchar x, y; -register long amount; +costly_gold( + coordxy x, coordxy y, + long amount, + boolean silent) { - register long delta; - register struct monst *shkp; - register struct eshk *eshkp; + long delta; + struct monst *shkp; + struct eshk *eshkp; if (!costly_spot(x, y)) return; - /* shkp now guaranteed to exist by costly_spot() */ + /* shkp is guaranteed to exist after successful costly_spot(), but + the static analyzer isn't smart enough to realize that, so follow + the shkp assignment with a redundant test that will always fail */ shkp = shop_keeper(*in_rooms(x, y, SHOPBASE)); + if (!shkp) + return; eshkp = ESHK(shkp); if (eshkp->credit >= amount) { - if (eshkp->credit > amount) - Your("credit is reduced by %ld %s.", amount, currency(amount)); - else - Your("credit is erased."); + if (!silent) { + if (eshkp->credit > amount) + Your("credit is reduced by %ld %s.", amount, currency(amount)); + else + Your("credit is erased."); + } eshkp->credit -= amount; } else { delta = amount - eshkp->credit; - if (eshkp->credit) - Your("credit is erased."); - if (eshkp->debit) - Your("debt increases by %ld %s.", delta, currency(delta)); - else - You("owe %s %ld %s.", shkname(shkp), delta, currency(delta)); + if (!silent) { + if (eshkp->credit) + Your("credit is erased."); + if (eshkp->debit) + Your("debt increases by %ld %s.", delta, currency(delta)); + else + You("owe %s %ld %s.", shkname(shkp), delta, currency(delta)); + } eshkp->debit += delta; eshkp->loan += delta; eshkp->credit = 0L; @@ -4653,11 +5788,10 @@ register long amount; /* used in domove to block diagonal shop-exit */ /* x,y should always be a door */ boolean -block_door(x, y) -register xchar x, y; +block_door(coordxy x, coordxy y) { - register int roomno = *in_rooms(x, y, SHOPBASE); - register struct monst *shkp; + int roomno = *in_rooms(x, y, SHOPBASE); + struct monst *shkp; if (roomno < 0 || !IS_SHOP(roomno)) return FALSE; @@ -4666,7 +5800,8 @@ register xchar x, y; if (roomno != *u.ushops) return FALSE; - if (!(shkp = shop_keeper((char) roomno)) || !inhishop(shkp)) + shkp = shop_keeper((char) roomno); + if (!shkp || !inhishop(shkp)) return FALSE; if (shkp->mx == ESHK(shkp)->shk.x && shkp->my == ESHK(shkp)->shk.y @@ -4676,7 +5811,7 @@ register xchar x, y; */ && ESHK(shkp)->shd.x == x && ESHK(shkp)->shd.y == y - && shkp->mcanmove && !shkp->msleeping + && !helpless(shkp) && (ESHK(shkp)->debit || ESHK(shkp)->billct || ESHK(shkp)->robbed)) { pline("%s%s blocks your way!", Shknam(shkp), Invis ? " senses your motion and" : ""); @@ -4688,12 +5823,11 @@ register xchar x, y; /* used in domove to block diagonal shop-entry; u.ux, u.uy should always be a door */ boolean -block_entry(x, y) -register xchar x, y; +block_entry(coordxy x, coordxy y) { - register xchar sx, sy; - register int roomno; - register struct monst *shkp; + coordxy sx, sy; + int roomno; + struct monst *shkp; if (!(IS_DOOR(levl[u.ux][u.uy].typ) && levl[u.ux][u.uy].doormask == D_BROKEN)) @@ -4702,7 +5836,8 @@ register xchar x, y; roomno = *in_rooms(x, y, SHOPBASE); if (roomno < 0 || !IS_SHOP(roomno)) return FALSE; - if (!(shkp = shop_keeper((char) roomno)) || !inhishop(shkp)) + shkp = shop_keeper((char) roomno); + if (!shkp || !inhishop(shkp)) return FALSE; if (ESHK(shkp)->shd.x != u.ux || ESHK(shkp)->shd.y != u.uy) @@ -4711,7 +5846,7 @@ register xchar x, y; sx = ESHK(shkp)->shk.x; sy = ESHK(shkp)->shk.y; - if (shkp->mx == sx && shkp->my == sy && shkp->mcanmove && !shkp->msleeping + if (shkp->mx == sx && shkp->my == sy && !helpless(shkp) && (x == sx - 1 || x == sx + 1 || y == sy - 1 || y == sy + 1) && (Invis || carrying(PICK_AXE) || carrying(DWARVISH_MATTOCK) || u.usteed)) { @@ -4724,32 +5859,33 @@ register xchar x, y; /* "your " or "Foobar's " (note the trailing space) */ char * -shk_your(buf, obj) -char *buf; -struct obj *obj; +shk_your(char *buf, struct obj *obj) { - if (!shk_owns(buf, obj) && !mon_owns(buf, obj)) + boolean chk_pm = obj->otyp == CORPSE && ismnum(obj->corpsenm); + + buf[0] = '\0'; + if (chk_pm && type_is_pname(&mons[obj->corpsenm])) + return buf; /* skip ownership prefix and space: "Medusa's corpse" */ + else if (chk_pm && the_unique_pm(&mons[obj->corpsenm])) + Strcpy(buf, "the"); /* override ownership: "the Oracle's corpse" */ + else if (!shk_owns(buf, obj) && !mon_owns(buf, obj)) Strcpy(buf, the_your[carried(obj) ? 1 : 0]); return strcat(buf, " "); } char * -Shk_Your(buf, obj) -char *buf; -struct obj *obj; +Shk_Your(char *buf, struct obj *obj) { (void) shk_your(buf, obj); *buf = highc(*buf); return buf; } -STATIC_OVL char * -shk_owns(buf, obj) -char *buf; -struct obj *obj; +staticfn char * +shk_owns(char *buf, struct obj *obj) { struct monst *shkp; - xchar x, y; + coordxy x, y; if (get_obj_location(obj, &x, &y, 0) && (obj->unpaid || (obj->where == OBJ_FLOOR && !obj->no_charge @@ -4760,23 +5896,21 @@ struct obj *obj; return (char *) 0; } -STATIC_OVL char * -mon_owns(buf, obj) -char *buf; -struct obj *obj; +staticfn char * +mon_owns(char *buf, struct obj *obj) { if (obj->where == OBJ_MINVENT) return strcpy(buf, s_suffix(y_monnam(obj->ocarry))); return (char *) 0; } -STATIC_OVL const char * -cad(altusage) -boolean altusage; /* used as a verbalized exclamation: \"Cad! ...\" */ +staticfn const char * +cad( + boolean altusage) /* used as a verbalized exclamation: \"Cad! ...\" */ { const char *res = 0; - switch (is_demon(youmonst.data) ? 3 : poly_gender()) { + switch (is_demon(gy.youmonst.data) ? 3 : poly_gender()) { case 0: res = "cad"; break; @@ -4795,7 +5929,7 @@ boolean altusage; /* used as a verbalized exclamation: \"Cad! ...\" */ break; } if (altusage) { - char *cadbuf = mon_nam(&youmonst); /* snag an output buffer */ + char *cadbuf = mon_nam(&gy.youmonst); /* snag an output buffer */ /* alternate usage adds a leading double quote and trailing exclamation point plus sentence separating spaces */ @@ -4839,14 +5973,13 @@ sasc_bug(struct obj *op, unsigned x) * 4. player_owned glob merging into player_owned glob */ void -globby_bill_fixup(obj_absorber, obj_absorbed) -struct obj *obj_absorber, *obj_absorbed; +globby_bill_fixup(struct obj *obj_absorber, struct obj *obj_absorbed) { int x = 0, y = 0; struct bill_x *bp, *bp_absorber = (struct bill_x *) 0; struct monst *shkp = 0; struct eshk *eshkp; - long amount, per_unit_cost = set_cost(obj_absorbed, shkp); + long amount, per_unit_cost; boolean floor_absorber = (obj_absorber->where == OBJ_FLOOR); if (!obj_absorber->globby) @@ -4875,6 +6008,7 @@ struct obj *obj_absorber, *obj_absorbed; bp_absorber = onbill(obj_absorber, shkp, FALSE); bp = onbill(obj_absorbed, shkp, FALSE); eshkp = ESHK(shkp); + per_unit_cost = set_cost(obj_absorbed, shkp); /************************************************************** * Scenario 1. Shop-owned glob absorbing into shop-owned glob @@ -4884,21 +6018,12 @@ struct obj *obj_absorber, *obj_absorbed; /* the glob being absorbed has a billing record */ amount = bp->price; eshkp->billct--; -#ifdef DUMB - { - /* DRS/NS 2.2.6 messes up -- Peter Kendell */ - int indx = eshkp->billct; - - *bp = eshkp->bill_p[indx]; - } -#else *bp = eshkp->bill_p[eshkp->billct]; -#endif clear_unpaid_obj(shkp, obj_absorbed); if (bp_absorber) { /* the absorber has a billing record */ - bp_absorber->price += amount; + bp_absorber->price += amount; } else { /* the absorber has no billing record */ ; @@ -4906,7 +6031,7 @@ struct obj *obj_absorber, *obj_absorbed; return; } /************************************************************** - * Scenario 2. Player-owned glob absorbing into shop-owned glob + * Scenario 2. Player-owned glob absorbing into shop-owned glob **************************************************************/ if (!bp_absorber && !bp && !obj_absorber->no_charge) { /* there are no billing records */ @@ -4957,6 +6082,7 @@ struct obj *obj_absorber, *obj_absorbed; || (floor_absorber && !costly_spot(x, y)))) { amount = bp->price; bill_dummy_object(obj_absorbed); + SetVoice(shkp, 0, 80, 0); verbalize("You owe me %ld %s for my %s that you %s with your%s", amount, currency(amount), obj_typename(obj_absorbed->otyp), ANGRY(shkp) ? "had the audacity to mix" : "just mixed", @@ -4970,4 +6096,30 @@ struct obj *obj_absorber, *obj_absorbed; return; } +/* Shopkeeper bills for use of a land mine or bear trap they own */ +void +use_unpaid_trapobj(struct obj *otmp, coordxy x, coordxy y) +{ + if (otmp->unpaid) { + if (!Deaf) { + struct monst *shkp = find_objowner(otmp, x, y); + + if (shkp && !muteshk(shkp)) { + SetVoice(shkp, 0, 80, 0); + verbalize("You set it, you buy it!"); + } + } + bill_dummy_object(otmp); + } +} + +#undef PAY_BUY +#undef PAY_CANT +#undef PAY_SKIP +#undef PAY_BROKE +#undef NOTANGRY +#undef ANGRY +#undef IS_SHOP +#undef muteshk + /*shk.c*/ diff --git a/src/shknam.c b/src/shknam.c index a36b67ec4..d93f6adab 100644 --- a/src/shknam.c +++ b/src/shknam.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 shknam.c $NHDT-Date: 1454485432 2016/02/03 07:43:52 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.41 $ */ +/* NetHack 5.0 shknam.c $NHDT-Date: 1764109114 2025/11/25 22:18:34 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.86 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2011. */ /* NetHack may be freely redistributed. See license for details. */ @@ -7,14 +7,14 @@ #include "hack.h" -STATIC_DCL boolean FDECL(stock_room_goodpos, (struct mkroom *, int, int, int, int)); -STATIC_DCL boolean FDECL(veggy_item, (struct obj * obj, int)); -STATIC_DCL int NDECL(shkveg); -STATIC_DCL void FDECL(mkveggy_at, (int, int)); -STATIC_DCL void FDECL(mkshobj_at, (const struct shclass *, int, int, - BOOLEAN_P)); -STATIC_DCL void FDECL(nameshk, (struct monst *, const char *const *)); -STATIC_DCL int FDECL(shkinit, (const struct shclass *, struct mkroom *)); +staticfn boolean stock_room_goodpos(struct mkroom *, int, int, int, int); +staticfn boolean veggy_item(struct obj * obj, int); +staticfn int shkveg(void); +staticfn void mkveggy_at(int, int); +staticfn void mkshobj_at(const struct shclass *, int, int, boolean); +staticfn void nameshk(struct monst *, const char *const *); +staticfn int good_shopdoor(struct mkroom *, coordxy *, coordxy *); +staticfn int shkinit(const struct shclass *, struct mkroom *); #define VEGETARIAN_CLASS (MAXOCLASSES + 1) @@ -128,7 +128,7 @@ static const char *const shktools[] = { #ifdef WIN32 "Lexa", "Niod", #endif -#ifdef MAC +#ifdef MACOS9 "Nhoj-lee", "Evad\'kh", "Ettaw-noj", "Tsew-mot", "Ydna-s", "Yao-hang", "Tonbar", "Kivenhoug", "Llardom", #endif @@ -193,17 +193,21 @@ static const char *const shkhealthfoods[] = { * have to lower some or all of the probability fields in old entries to * free up some percentage for the new type. * - * The placement type field is not yet used but will be in the near future. + * The placement type field is not yet used but might be someday. * * The iprobs array in each entry defines the probabilities for various kinds * of objects to be present in the given shop type. You can associate with * each percentage either a generic object type (represented by one of the - * *_CLASS macros) or a specific object (represented by an onames.h define). + * *_CLASS enum value) or a specific object enum value. * In the latter case, prepend it with a unary minus so the code can know * (by testing the sign) whether to use mkobj() or mksobj(). + * shtypes[] is externally referenced from mkroom.c, mon.c and shk.c. + * + * The second, usually shorter, store type name is used in automatically + * generated annotations for #overview. If Null, the first name gets used. */ const struct shclass shtypes[] = { - { "general store", + { "general store", NULL, RANDOM_CLASS, 42, D_SHOP, @@ -214,7 +218,7 @@ const struct shclass shtypes[] = { { 0, 0 }, { 0, 0 } }, shkgeneral }, - { "used armor dealership", + { "used armor dealership", "armor shop", ARMOR_CLASS, 14, D_SHOP, @@ -225,7 +229,7 @@ const struct shclass shtypes[] = { { 0, 0 }, { 0, 0 } }, shkarmors }, - { "second-hand bookstore", + { "second-hand bookstore", "scroll shop", SCROLL_CLASS, 10, D_SHOP, @@ -236,7 +240,7 @@ const struct shclass shtypes[] = { { 0, 0 }, { 0, 0 } }, shkbooks }, - { "liquor emporium", + { "liquor emporium", "potion shop", POTION_CLASS, 10, D_SHOP, @@ -247,7 +251,7 @@ const struct shclass shtypes[] = { { 0, 0 }, { 0, 0 } }, shkliquors }, - { "antique weapons outlet", + { "antique weapons outlet", "weapon shop", WEAPON_CLASS, 5, D_SHOP, @@ -258,7 +262,7 @@ const struct shclass shtypes[] = { { 0, 0 }, { 0, 0 } }, shkweapons }, - { "delicatessen", + { "delicatessen", "food shop", FOOD_CLASS, 5, D_SHOP, @@ -269,7 +273,7 @@ const struct shclass shtypes[] = { { 3, -ICE_BOX }, { 0, 0 } }, shkfoods }, - { "jewelers", + { "jewelers", "ring shop", RING_CLASS, 3, D_SHOP, @@ -280,7 +284,7 @@ const struct shclass shtypes[] = { { 0, 0 }, { 0, 0 } }, shkrings }, - { "quality apparel and accessories", + { "quality apparel and accessories", "wand shop", WAND_CLASS, 3, D_SHOP, @@ -289,7 +293,7 @@ const struct shclass shtypes[] = { { 5, -ELVEN_CLOAK }, { 0, 0 } }, shkwands }, - { "hardware store", + { "hardware store", "tool shop", TOOL_CLASS, 3, D_SHOP, @@ -300,7 +304,7 @@ const struct shclass shtypes[] = { { 0, 0 }, { 0, 0 } }, shktools }, - { "rare books", + { "rare books", "bookstore", SPBOOK_CLASS, 3, D_SHOP, @@ -311,7 +315,7 @@ const struct shclass shtypes[] = { { 0, 0 }, { 0, 0 } }, shkbooks }, - { "health food store", + { "health food store", "vegetarian food shop", FOOD_CLASS, 2, D_SHOP, @@ -326,19 +330,22 @@ const struct shclass shtypes[] = { * probability of zero. They are only created via the special level * loader. */ - { "lighting store", + { "lighting store", "lighting shop", TOOL_CLASS, 0, D_SHOP, { { 30, -WAX_CANDLE }, - { 48, -TALLOW_CANDLE }, + { 44, -TALLOW_CANDLE }, { 5, -BRASS_LANTERN }, { 9, -OIL_LAMP }, { 3, -MAGIC_LAMP }, - { 5, -POT_OIL } }, + { 5, -POT_OIL }, + { 2, -WAN_LIGHT }, + { 1, -SCR_LIGHT }, + { 1, -SPE_LIGHT } }, shklight }, /* sentinel */ - { (char *) 0, + { (char *) 0, NULL, 0, 0, 0, @@ -352,7 +359,7 @@ const struct shclass shtypes[] = { void init_shop_selection() { - register int i, j, item_prob, shop_prob; + int i, j, item_prob, shop_prob; for (shop_prob = 0, i = 0; i < SIZE(shtypes); i++) { shop_prob += shtypes[i].prob; @@ -369,10 +376,8 @@ init_shop_selection() /* decide whether an object or object type is considered vegetarian; for types, items which might go either way are assumed to be veggy */ -STATIC_OVL boolean -veggy_item(obj, otyp) -struct obj *obj; -int otyp; /* used iff obj is null */ +staticfn boolean +veggy_item(struct obj* obj, int otyp /* used iff obj is null */) { int corpsenm; char oclass; @@ -394,22 +399,21 @@ int otyp; /* used iff obj is null */ if (otyp == TIN && corpsenm == NON_PM) /* implies obj is non-null */ return (boolean) (obj->spe == 1); /* 0 = empty, 1 = spinach */ if (otyp == TIN || otyp == CORPSE) - return (boolean) (corpsenm >= LOW_PM - && vegetarian(&mons[corpsenm])); + return (boolean) (ismnum(corpsenm) && vegetarian(&mons[corpsenm])); } return FALSE; } -STATIC_OVL int -shkveg() +staticfn int +shkveg(void) { int i, j, maxprob, prob; char oclass = FOOD_CLASS; int ok[NUM_OBJECTS]; + (void) memset((genericptr_t) ok, 0, sizeof ok); /* lint suppression */ j = maxprob = 0; - ok[0] = 0; /* lint suppression */ - for (i = bases[(int) oclass]; i < NUM_OBJECTS; ++i) { + for (i = svb.bases[(int) oclass]; i < NUM_OBJECTS; ++i) { if (objects[i].oc_class != oclass) break; @@ -435,9 +439,8 @@ shkveg() } /* make a random item for health food store */ -STATIC_OVL void -mkveggy_at(sx, sy) -int sx, sy; +staticfn void +mkveggy_at(int sx, int sy) { struct obj *obj = mksobj_at(shkveg(), sx, sy, TRUE, TRUE); @@ -447,11 +450,8 @@ int sx, sy; } /* make an object of the appropriate type for a shop square */ -STATIC_OVL void -mkshobj_at(shp, sx, sy, mkspecl) -const struct shclass *shp; -int sx, sy; -boolean mkspecl; +staticfn void +mkshobj_at(const struct shclass *shp, int sx, int sy, boolean mkspecl) { struct monst *mtmp; struct permonst *ptr; @@ -463,18 +463,14 @@ boolean mkspecl; struct obj *novel = mksobj_at(SPE_NOVEL, sx, sy, FALSE, FALSE); if (novel) - context.tribute.bookstock = TRUE; + svc.context.tribute.bookstock = TRUE; return; } if (rn2(100) < depth(&u.uz) && !MON_AT(sx, sy) && (ptr = mkclass(S_MIMIC, 0)) != 0 && (mtmp = makemon(ptr, sx, sy, NO_MM_FLAGS)) != 0) { - /* note: makemon will set the mimic symbol to a shop item */ - if (rn2(10) >= depth(&u.uz)) { - mtmp->m_ap_type = M_AP_OBJECT; - mtmp->mappearance = STRANGE_OBJECT; - } + /* nothing */ } else { atype = get_shop_item((int) (shp - shtypes)); if (atype == VEGETARIAN_CLASS) @@ -487,25 +483,17 @@ boolean mkspecl; } /* extract a shopkeeper name for the given shop type */ -STATIC_OVL void -nameshk(shk, nlp) -struct monst *shk; -const char *const *nlp; +staticfn void +nameshk(struct monst *shk, const char *const *nlp) { int i, trycnt, names_avail; const char *shname = 0; struct monst *mtmp; - int name_wanted; + int name_wanted = shk->m_id; s_level *sptr; - if (nlp == shkfoods && In_mines(&u.uz) && Role_if(PM_MONK) + if (nlp == shklight && In_mines(&u.uz) && (sptr = Is_special(&u.uz)) != 0 && sptr->flags.town) { - /* special-case override for minetown food store for monks */ - nlp = shkhealthfoods; - } - - if (nlp == shklight && In_mines(&u.uz) && (sptr = Is_special(&u.uz)) != 0 - && sptr->flags.town) { /* special-case minetown lighting shk */ shname = "+Izchak"; shk->female = FALSE; @@ -513,16 +501,18 @@ const char *const *nlp; /* We want variation from game to game, without needing the save and restore support which would be necessary for randomization; try not to make too many assumptions about time_t's internals; - use ledger_no rather than depth to keep mine town distinct. */ + use ledger_no rather than depth to keep minetown distinct. */ int nseed = (int) ((long) ubirthday / 257L); - name_wanted = ledger_no(&u.uz) + (nseed % 13) - (nseed % 5); + name_wanted += ledger_no(&u.uz) + (nseed % 13) - (nseed % 5); if (name_wanted < 0) name_wanted += (13 + 5); shk->female = name_wanted & 1; for (names_avail = 0; nlp[names_avail]; names_avail++) continue; + assert(names_avail > 0); + name_wanted = name_wanted % names_avail; for (trycnt = 0; trycnt < 50; trycnt++) { if (nlp == shktools) { @@ -549,8 +539,10 @@ const char *const *nlp; for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { if (DEADMONSTER(mtmp) || (mtmp == shk) || !mtmp->isshk) continue; + assert(has_eshk(mtmp)); if (strcmp(ESHK(mtmp)->shknam, shname)) continue; + name_wanted = names_avail; /* try a random name */ break; } if (!mtmp) @@ -562,20 +554,19 @@ const char *const *nlp; } void -neweshk(mtmp) -struct monst *mtmp; +neweshk(struct monst *mtmp) { if (!mtmp->mextra) mtmp->mextra = newmextra(); if (!ESHK(mtmp)) ESHK(mtmp) = (struct eshk *) alloc(sizeof(struct eshk)); (void) memset((genericptr_t) ESHK(mtmp), 0, sizeof(struct eshk)); + ESHK(mtmp)->parentmid = mtmp->m_id; ESHK(mtmp)->bill_p = (struct bill_x *) 0; } void -free_eshk(mtmp) -struct monst *mtmp; +free_eshk(struct monst *mtmp) { if (mtmp->mextra && ESHK(mtmp)) { free((genericptr_t) ESHK(mtmp)); @@ -584,62 +575,79 @@ struct monst *mtmp; mtmp->isshk = 0; } +/* find a door in room sroom which is good for shop entrance. + returns -1 if no good door found, or the svd.doors index + and the door coordinates in sx, sy */ +staticfn int +good_shopdoor(struct mkroom *sroom, coordxy *sx, coordxy *sy) +{ + int i; + + for (i = 0; i < sroom->doorct; i++) { + int di = sroom->fdoor + i; + + *sx = svd.doors[di].x; + *sy = svd.doors[di].y; + + /* check that the shopkeeper placement is sane */ + if (sroom->irregular) { + int rmno = (int) ((sroom - svr.rooms) + ROOMOFFSET); + + if (isok(*sx - 1, *sy) && !levl[*sx - 1][*sy].edge + && (int) levl[*sx - 1][*sy].roomno == rmno) + (*sx)--; + else if (isok(*sx + 1, *sy) && !levl[*sx + 1][*sy].edge + && (int) levl[*sx + 1][*sy].roomno == rmno) + (*sx)++; + else if (isok(*sx, *sy - 1) && !levl[*sx][*sy - 1].edge + && (int) levl[*sx][*sy - 1].roomno == rmno) + (*sy)--; + else if (isok(*sx, *sy + 1) && !levl[*sx][*sy + 1].edge + && (int) levl[*sx][*sy + 1].roomno == rmno) + (*sy)++; + else + continue; + } else if (*sx == sroom->lx - 1) { + (*sx)++; + } else if (*sx == sroom->hx + 1) { + (*sx)--; + } else if (*sy == sroom->ly - 1) { + (*sy)++; + } else if (*sy == sroom->hy + 1) { + (*sy)--; + } else { + continue; + } + return di; + } + return -1; +} + /* create a new shopkeeper in the given room */ -STATIC_OVL int -shkinit(shp, sroom) -const struct shclass *shp; -struct mkroom *sroom; +staticfn int +shkinit(const struct shclass *shp, struct mkroom *sroom) { - register int sh, sx, sy; + int sh; + coordxy sx, sy; struct monst *shk; struct eshk *eshkp; /* place the shopkeeper in the given room */ - sh = sroom->fdoor; - sx = doors[sh].x; - sy = doors[sh].y; - - /* check that the shopkeeper placement is sane */ - if (sroom->irregular) { - int rmno = (int) ((sroom - rooms) + ROOMOFFSET); - - if (isok(sx - 1, sy) && !levl[sx - 1][sy].edge - && (int) levl[sx - 1][sy].roomno == rmno) - sx--; - else if (isok(sx + 1, sy) && !levl[sx + 1][sy].edge - && (int) levl[sx + 1][sy].roomno == rmno) - sx++; - else if (isok(sx, sy - 1) && !levl[sx][sy - 1].edge - && (int) levl[sx][sy - 1].roomno == rmno) - sy--; - else if (isok(sx, sy + 1) && !levl[sx][sy + 1].edge - && (int) levl[sx][sy + 1].roomno == rmno) - sy++; - else - goto shk_failed; - } else if (sx == sroom->lx - 1) { - sx++; - } else if (sx == sroom->hx + 1) { - sx--; - } else if (sy == sroom->ly - 1) { - sy++; - } else if (sy == sroom->hy + 1) { - sy--; - } else { - shk_failed: + sh = good_shopdoor(sroom, &sx, &sy); + if (sh < 0) { #ifdef DEBUG /* Said to happen sometimes, but I have never seen it. */ /* Supposedly fixed by fdoor change in mklev.c */ if (wizard) { - register int j = sroom->doorct; + int j = sroom->doorct; - pline("Where is shopdoor?"); + impossible("Where is shopdoor?"); pline("Room at (%d,%d),(%d,%d).", sroom->lx, sroom->ly, sroom->hx, sroom->hy); - pline("doormax=%d doorct=%d fdoor=%d", doorindex, sroom->doorct, + pline("doormax=%d doorct=%d fdoor=%d", gd.doorindex, sroom->doorct, sh); while (j--) { - pline("door [%d,%d]", doors[sh].x, doors[sh].y); + pline("door [%d,%d]", svd.doors[sh].x, svd.doors[sh].y); sh++; } display_nhwindow(WIN_MESSAGE, FALSE); @@ -649,7 +657,7 @@ struct mkroom *sroom; } if (MON_AT(sx, sy)) - (void) rloc(m_at(sx, sy), FALSE); /* insurance */ + (void) rloc(m_at(sx, sy), RLOC_NOMSG); /* insurance */ /* now initialize the shopkeeper monster structure */ if (!(shk = makemon(&mons[PM_SHOPKEEPER], sx, sy, MM_ESHK))) @@ -658,12 +666,12 @@ struct mkroom *sroom; shk->isshk = shk->mpeaceful = 1; set_malign(shk); shk->msleeping = 0; - shk->mtrapseen = ~0; /* we know all the traps already */ - eshkp->shoproom = (schar) ((sroom - rooms) + ROOMOFFSET); + mon_learns_traps(shk, ALL_TRAPS); /* we know all the traps already */ + eshkp->shoproom = (schar) ((sroom - svr.rooms) + ROOMOFFSET); sroom->resident = shk; eshkp->shoptype = sroom->rtype; assign_level(&eshkp->shoplevel, &u.uz); - eshkp->shd = doors[sh]; + eshkp->shd = svd.doors[sh]; eshkp->shk.x = sx; eshkp->shk.y = sy; eshkp->robbed = eshkp->credit = eshkp->debit = eshkp->loan = 0L; @@ -674,34 +682,40 @@ struct mkroom *sroom; mkmonmoney(shk, 1000L + 30L * (long) rnd(100)); /* initial capital */ if (shp->shknms == shkrings) (void) mongets(shk, TOUCHSTONE); + if (shp->shknms == shktools || shp->shknms == shkwands || + (shp->shknms == shkrings && rn2(2)) || + (shp->shknms == shkgeneral && rn2(5))) + (void) mongets(shk, SCR_CHARGING); nameshk(shk, shp->shknms); return sh; } -STATIC_OVL boolean -stock_room_goodpos(sroom, rmno, sh, sx, sy) -struct mkroom *sroom; -int rmno, sh, sx,sy; +staticfn boolean +stock_room_goodpos(struct mkroom *sroom, int rmno, int sh, int sx, int sy) { if (sroom->irregular) { if (levl[sx][sy].edge || (int) levl[sx][sy].roomno != rmno - || distmin(sx, sy, doors[sh].x, doors[sh].y) <= 1) + || distmin(sx, sy, svd.doors[sh].x, svd.doors[sh].y) <= 1) return FALSE; - } else if ((sx == sroom->lx && doors[sh].x == sx - 1) - || (sx == sroom->hx && doors[sh].x == sx + 1) - || (sy == sroom->ly && doors[sh].y == sy - 1) - || (sy == sroom->hy && doors[sh].y == sy + 1)) + } else if ((sx == sroom->lx && svd.doors[sh].x == sx - 1) + || (sx == sroom->hx && svd.doors[sh].x == sx + 1) + || (sy == sroom->ly && svd.doors[sh].y == sy - 1) + || (sy == sroom->hy && svd.doors[sh].y == sy + 1)) + return FALSE; + + /* only generate items on solid floor squares */ + if (!IS_ROOM(levl[sx][sy].typ)) { return FALSE; + } + return TRUE; } /* stock a newly-created room with objects */ void -stock_room(shp_indx, sroom) -int shp_indx; -register struct mkroom *sroom; +stock_room(int shp_indx, struct mkroom *sroom) { /* * Someday soon we'll dispatch on the shdist field of shclass to do @@ -712,7 +726,7 @@ register struct mkroom *sroom; int sx, sy, sh; int stockcount = 0, specialspot = 0; char buf[BUFSZ]; - int rmno = (int) ((sroom - rooms) + ROOMOFFSET); + int rmno = (int) ((sroom - svr.rooms) + ROOMOFFSET); const struct shclass *shp = &shtypes[shp_indx]; /* first, try to place a shopkeeper in the room */ @@ -720,8 +734,8 @@ register struct mkroom *sroom; return; /* make sure no doorways without doors, and no trapped doors, in shops */ - sx = doors[sroom->fdoor].x; - sy = doors[sroom->fdoor].y; + sx = svd.doors[sroom->fdoor].x; + sy = svd.doors[sroom->fdoor].y; if (levl[sx][sy].doormask == D_NODOOR) { levl[sx][sy].doormask = D_ISOPEN; newsym(sx, sy); @@ -734,7 +748,7 @@ register struct mkroom *sroom; levl[sx][sy].doormask = D_LOCKED; if (levl[sx][sy].doormask == D_LOCKED) { - register int m = sx, n = sy; + int m = sx, n = sy; if (inside_shop(sx + 1, sy)) m--; @@ -745,10 +759,13 @@ register struct mkroom *sroom; else if (inside_shop(sx, sy - 1)) n++; Sprintf(buf, "Closed for inventory"); - make_engr_at(m, n, buf, 0L, DUST); + make_engr_at(m, n, buf, NULL, 0L, DUST); + if (levl[m][n].typ != CORR && levl[m][n].typ != ROOM) + levl[m][n].typ = (Is_special(&u.uz) + || *in_rooms(m, n, 0)) ? ROOM : CORR; } - if (context.tribute.enabled && !context.tribute.bookstock) { + if (svc.context.tribute.enabled && !svc.context.tribute.bookstock) { /* * Out of the number of spots where we're actually * going to put stuff, randomly single out one in particular. @@ -774,14 +791,18 @@ register struct mkroom *sroom; * monsters will sit on top of objects and not the other way around. */ - level.flags.has_shop = TRUE; + /* Hack for Orcus's level: it's a ghost town, get rid of shopkeepers */ + if (on_level(&u.uz, &orcus_level)) { + struct monst *mtmp = shop_keeper(rmno); + mongone(mtmp); + } + + svl.level.flags.has_shop = TRUE; } /* does shkp's shop stock this item type? */ boolean -saleable(shkp, obj) -struct monst *shkp; -struct obj *obj; +saleable(struct monst *shkp, struct obj *obj) { int i, shp_indx = ESHK(shkp)->shoptype - SHOPBASE; const struct shclass *shp = &shtypes[shp_indx]; @@ -802,13 +823,13 @@ struct obj *obj; return FALSE; } -/* positive value: class; negative value: specific object type */ +/* positive value: class; negative value: specific object type. + can also return non-existing object class (eg. VEGETARIAN_CLASS) */ int -get_shop_item(type) -int type; +get_shop_item(int type) { const struct shclass *shp = shtypes + type; - register int i, j; + int i, j; /* select an appropriate object type at random */ for (j = rnd(100), i = 0; (j -= shp->iprobs[i].iprob) > 0; i++) @@ -819,8 +840,7 @@ int type; /* version of shkname() for beginning of sentence */ char * -Shknam(mtmp) -struct monst *mtmp; +Shknam(struct monst *mtmp) { char *nam = shkname(mtmp); @@ -833,8 +853,7 @@ struct monst *mtmp; will yield some other shopkeeper's name (not necessarily one residing in the current game's dungeon, or who keeps same type of shop) */ char * -shkname(mtmp) -struct monst *mtmp; +shkname(struct monst *mtmp) { char *nam; unsigned save_isshk = mtmp->isshk; @@ -878,8 +897,7 @@ struct monst *mtmp; } boolean -shkname_is_pname(mtmp) -struct monst *mtmp; +shkname_is_pname(struct monst *mtmp) { const char *shknm = ESHK(mtmp)->shknam; @@ -887,9 +905,7 @@ struct monst *mtmp; } boolean -is_izchak(shkp, override_hallucination) -struct monst *shkp; -boolean override_hallucination; +is_izchak(struct monst *shkp, boolean override_hallucination) { const char *shknm; @@ -907,4 +923,6 @@ boolean override_hallucination; return (boolean) !strcmp(shknm, "Izchak"); } +#undef VEGETARIAN_CLASS + /*shknam.c*/ diff --git a/src/sit.c b/src/sit.c index 371e1ffa6..15c772cfd 100644 --- a/src/sit.c +++ b/src/sit.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 sit.c $NHDT-Date: 1559670609 2019/06/04 17:50:09 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.61 $ */ +/* NetHack 5.0 sit.c $NHDT-Date: 1718136168 2024/06/11 20:02:48 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.95 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2012. */ /* NetHack may be freely redistributed. See license for details. */ @@ -6,15 +6,17 @@ #include "hack.h" #include "artifact.h" +staticfn void throne_sit_effect(void); +staticfn int lay_an_egg(void); /* take away the hero's money */ void -take_gold() +take_gold(void) { struct obj *otmp, *nobj; int lost_money = 0; - for (otmp = invent; otmp; otmp = nobj) { + for (otmp = gi.invent; otmp; otmp = nobj) { nobj = otmp->nobj; if (otmp->oclass == COIN_CLASS) { lost_money = 1; @@ -25,24 +27,388 @@ take_gold() if (!lost_money) { You_feel("a strange sensation."); } else { - You("notice you have no money!"); - context.botl = 1; + You("notice you have no gold!"); + disp.botl = TRUE; } } +staticfn void special_throne_effect(int effect); + +/* maybe do something when hero sits on a throne */ +staticfn void +throne_sit_effect(void) +{ + coordxy tx = u.ux, ty = u.uy; + + boolean special_throne = !!In_V_tower(&u.uz); + + if (rnd(6) > 4) { /* [why so convoluted? it's the same as '!rn2(3)'] */ + int effect = rnd(13); + + if (wizard && !iflags.debug_fuzzer) { + char buf[BUFSZ]; + int which; + + buf[0] = '\0'; + getlin("Throne sit effect (1..13) [0=random]", buf); + if (buf[0] == '\033') { + pline("%s", Never_mind); + return; /* caller will still cause a move to elapse */ + } + which = atoi(buf); + if (which >= 1 && which <= 13) + effect = which; + } + + if (special_throne) { + special_throne_effect(effect); + return; + } + + switch (effect) { + case 1: + (void) adjattrib(rn2(A_MAX), -rn1(4, 3), FALSE); + losehp(rnd(10), "cursed throne", KILLED_BY_AN); + break; + case 2: + (void) adjattrib(rn2(A_MAX), 1, FALSE); + break; + case 3: + pline("A%s electric shock shoots through your body!", + (Shock_resistance) ? "n" : " massive"); + losehp(Shock_resistance ? rnd(6) : rnd(30), "electric chair", + KILLED_BY_AN); + exercise(A_CON, FALSE); + break; + case 4: + You_feel("much, much better!"); + if (Upolyd) { + if (u.mh >= (u.mhmax - 5)) + u.mhmax += 4; + u.mh = u.mhmax; + } + if (u.uhp >= (u.uhpmax - 5)) { + u.uhpmax += 4; + if (u.uhpmax > u.uhppeak) + u.uhppeak = u.uhpmax; + } + u.uhp = u.uhpmax; + u.ucreamed = 0; + make_blinded(0L, TRUE); + make_sick(0L, (char *) 0, FALSE, SICK_ALL); + heal_legs(0); + disp.botl = TRUE; + break; + case 5: + take_gold(); + break; + case 6: + if (u.uluck + rn2(5) < 0) { + You_feel("your luck is changing."); + change_luck(1); + } else + makewish(); + break; + case 7: + { + int cnt = rnd(10); + + /* Magical voice not affected by deafness */ + pline("A voice echoes:"); + SetVoice((struct monst *) 0, 0, 80, voice_throne); + verbalize("Thine audience hath been summoned, %s!", + flags.female ? "Dame" : "Sire"); + while (cnt--) + (void) makemon(courtmon(), tx, ty, NO_MM_FLAGS); + break; + } + case 8: + /* Magical voice not affected by deafness */ + pline("A voice echoes:"); + SetVoice((struct monst *) 0, 0, 80, voice_throne); + verbalize("By thine Imperious order, %s...", + flags.female ? "Dame" : "Sire"); + do_genocide(5); /* REALLY|ONTHRONE, see do_genocide() */ + break; + case 9: + /* Magical voice not affected by deafness */ + pline("A voice echoes:"); + SetVoice((struct monst *) 0, 0, 80, voice_throne); + verbalize( + "A curse upon thee for sitting upon this most holy throne!"); + if (Luck > 0) { + make_blinded(BlindedTimeout + rn1(100, 250), TRUE); + change_luck((Luck > 1) ? -rnd(2) : -1); + } else + rndcurse(); + break; + case 10: + if (Luck < 0 || (HSee_invisible & INTRINSIC)) { + if (svl.level.flags.nommap) { + pline("A terrible drone fills your head!"); + make_confused((HConfusion & TIMEOUT) + (long) rnd(30), + FALSE); + } else { + pline("An image forms in your mind."); + do_mapping(); + } + } else { + /* avoid "vision clears" if hero can't see */ + if (!Blind) { + Your("vision becomes clear."); + } else { + int num_of_eyes = eyecount(gy.youmonst.data); + const char *eye = body_part(EYE); + + /* note: 1 eye case won't actually happen--can't + sit on throne when poly'd into always-levitating + floating eye and can't polymorph into Cyclops */ + switch (num_of_eyes) { /* 2, 1, or 0 */ + default: + case 2: /* more than 1 eye */ + eye = makeplural(eye); + FALLTHROUGH; + /*FALLTHRU*/ + case 1: /* one eye (Cyclops, floating eye) */ + Your("%s %s...", eye, vtense(eye, "tingle")); + break; + case 0: /* no eyes */ + You("have a very strange feeling in your %s.", + body_part(HEAD)); + break; + } + } + HSee_invisible |= FROMOUTSIDE; + newsym(u.ux, u.uy); + } + break; + case 11: + if (Luck < 0) { + You_feel("threatened."); + aggravate(); + } else { + You_feel("a wrenching sensation."); + tele(); /* teleport him */ + } + break; + case 12: + You("are granted an insight!"); + if (gi.invent) { + /* rn2(5) agrees w/seffects() */ + identify_pack(rn2(5), FALSE); + } + break; + case 13: + Your("mind turns into a pretzel!"); + make_confused((HConfusion & TIMEOUT) + (long) rn1(7, 16), + FALSE); + break; + default: + impossible("throne effect"); + break; + } + } else { + if (is_prince(gy.youmonst.data) || u.uevent.uhand_of_elbereth) + You_feel("very comfortable here."); + else + You_feel("somehow out of place..."); + } + + /* 5.0: when the random chance for removal is hit, ask for confirmation + if in wizard mode, and remove the throne even if hero was teleported + away from it. [This used to remove a throne at hero's current + location if there happened to be one, so for the teleport case that + only happened when teleporting back to the same point where hero + started from.] "Analyzing a throne" doesn't really make any sense + but if the answer is yes than it will vanish in a puff of logic. */ + if (!special_throne && + !rn2(3) && (!wizard || y_n("Analyze throne?") == 'y')) { + levl[tx][ty].typ = ROOM, levl[tx][ty].flags = 0; + map_background(tx, ty, FALSE); + newsym_force(tx, ty); + /* "[God] promptly vanishes in a puff of logic" is from + Douglas Adams' _The_Hitchhiker's_Guide_to_the_Galaxy_. */ + pline_The("throne %s in a puff of logic.", + cansee(tx, ty) ? "vanishes" : "has vanished"); + } +} + +/* special throne in Vlad's tower: effect is 1 to 13 inclusive */ +staticfn void +special_throne_effect(int effect) { + coordxy tx = u.ux, ty = u.uy; + + switch (effect) { + case 1: + case 2: + case 3: + case 4: + /* 4 chances of a wish, but then the throne disappears. + + This is the only way the throne can disappear from sitting + on it, so if you sit on it enough (enduring the negative + effects) you are guaranteed an eventual wish. */ + makewish(); + levl[tx][ty].typ = ROOM, levl[tx][ty].flags = 0; + map_background(tx, ty, FALSE); + newsym_force(tx, ty); + pline_The("throne disintegrates, having spent its power."); + break; + case 5: + /* permanent level drain */ + pline("Sitting on the throne was a terrible experience."); + if (!Drain_resistance) { + losexp("a bad experience sitting on a throne"); + if (u.ulevelmax > u.ulevel) + u.ulevelmax -= 1; + } + break; + case 6: + { + /* grease hands and inventory + + Same rules for which items can be affected as grease_ok in apply.c */ + struct obj *otmp; + + pline("A greasy liquid sprays all over you!"); + for (otmp = gi.invent; otmp; otmp = otmp->nobj) + if (otmp->oclass != COIN_CLASS) + otmp->greased = 1; + make_glib(rn1(101, 100)); + update_inventory(); + break; + } + case 7: + /* lose an intrinsic */ + attrcurse(); + pline_The("throne somehow seems to be amused."); + break; + case 8: + { + /* level teleport to Vibrating Square level */ + d_level vs_level; + find_hell(&vs_level); + vs_level.dlevel = svd.dungeons[vs_level.dnum].num_dunlevs - 1; + if (u.uhave.amulet) + You_feel("extremely disoriented for a moment."); + else + schedule_goto( + &vs_level, UTOTYPE_NONE, (char *) 0, + "You feel extremely out of place."); + break; + } + case 9: + { + /* summon demons; a NULL argument to msummon summons demons as + though they were summoned by the Wizard of Yendor */ + pline_The("throne seeems to be calling for help!"); + msummon(NULL); + msummon(NULL); + msummon(NULL); + break; + } + case 10: + { + /* confused blessed remove curse effect */ + struct obj fake_spellbook; + long save_confusion = HConfusion; + + fake_spellbook = cg.zeroobj; + fake_spellbook.otyp = SPE_REMOVE_CURSE; + fake_spellbook.oclass = SPBOOK_CLASS; + fake_spellbook.blessed = 1; + HConfusion = 1L; + (void) seffects(&fake_spellbook); + HConfusion = save_confusion; + break; + } + case 11: + /* polymorph effect (not blocked by magic resistance, but other things + that protect from polymorphs work) */ + if (is_vampire(gy.youmonst.data)) { + You_feel("unworthy."); + } else { + pline("This throne was not meant for those such as you!"); + You_feel("a change coming over you."); + polyself(POLY_NOFLAGS); + } + break; + case 12: + /* acid damage */ + pline("The throne is covered in acid!"); + losehp(Acid_resistance ? rnd(16) : rnd(80), "acidic chair", + KILLED_BY_AN); + exercise(A_CON, FALSE); + break; + case 13: + { + /* ability shuffle */ + int ability; + pline("As you sit on the throne, your body and mind start to warp."); + for (ability = 0; ability < A_MAX; ++ability) { + adjattrib(ability, rn2(5) - 2, -1); + } + break; + } + } +} + +/* hero lays an egg */ +staticfn int +lay_an_egg(void) +{ + struct obj *uegg; + + if (!flags.female) { + pline("%s can't lay eggs!", + Hallucination + ? "You may think you are a platypus, but a male still" + : "Males"); + return ECMD_OK; + } else if (u.uhunger < (int) objects[EGG].oc_nutrition) { + You("don't have enough energy to lay an egg."); + return ECMD_OK; + } else if (eggs_in_water(gy.youmonst.data)) { + if (!(Underwater || Is_waterlevel(&u.uz))) { + pline("A splash tetra you are not."); + return ECMD_OK; + } + if (Upolyd + && (gy.youmonst.data == &mons[PM_GIANT_EEL] + || gy.youmonst.data == &mons[PM_ELECTRIC_EEL])) { + You("yearn for the Sargasso Sea."); + return ECMD_OK; + } + } + uegg = mksobj(EGG, FALSE, FALSE); + uegg->spe = 1; + uegg->quan = 1L; + uegg->owt = weight(uegg); + /* this sets hatch timers if appropriate */ + set_corpsenm(uegg, egg_type_from_parent(u.umonnum, FALSE)); + uegg->known = 1; + observe_object(uegg); + You("%s an egg.", eggs_in_water(gy.youmonst.data) ? "spawn" : "lay"); + dropy(uegg); + stackobj(uegg); + morehungry((int) objects[EGG].oc_nutrition); + return ECMD_TIME; +} + /* #sit command */ int -dosit() +dosit(void) { static const char sit_message[] = "sit on the %s."; - register struct trap *trap = t_at(u.ux, u.uy); - register int typ = levl[u.ux][u.uy].typ; + struct trap *trap = t_at(u.ux, u.uy); + int typ = levl[u.ux][u.uy].typ; if (u.usteed) { You("are already sitting on %s.", mon_nam(u.usteed)); - return 0; + return ECMD_OK; } - if (u.uundetected && is_hider(youmonst.data) && u.umonnum != PM_TRAPPER) + if (u.uundetected && is_hider(gy.youmonst.data) + && u.umonnum != PM_TRAPPER) /* trapper can stay hidden on floor */ u.uundetected = 0; /* no longer on the ceiling */ if (!can_reach_floor(FALSE)) { @@ -52,32 +418,49 @@ dosit() You("tumble in place."); else You("are sitting on air."); - return 0; - } else if (u.ustuck && !sticks(youmonst.data)) { + return ECMD_OK; + } else if (u.ustuck && !sticks(gy.youmonst.data)) { /* holding monster is next to hero rather than beneath, but hero is in no condition to actually sit at has/her own spot */ if (humanoid(u.ustuck->data)) pline("%s won't offer %s lap.", Monnam(u.ustuck), mhis(u.ustuck)); else pline("%s has no lap.", Monnam(u.ustuck)); - return 0; + return ECMD_OK; } else if (is_pool(u.ux, u.uy) && !Underwater) { /* water walking */ goto in_water; + } else if (Upolyd && u.umonnum == PM_GREMLIN + && (levl[u.ux][u.uy].typ == FOUNTAIN || is_pool(u.ux, u.uy))) { + goto in_water; } if (OBJ_AT(u.ux, u.uy) /* ensure we're not standing on the precipice */ && !(uteetering_at_seen_pit(trap) || uescaped_shaft(trap))) { - register struct obj *obj; + struct obj *obj; - obj = level.objects[u.ux][u.uy]; - if (youmonst.data->mlet == S_DRAGON && obj->oclass == COIN_CLASS) { + obj = svl.level.objects[u.ux][u.uy]; + if (gy.youmonst.data->mlet == S_DRAGON && obj->oclass == COIN_CLASS) { You("coil up around your %shoard.", - (obj->quan + money_cnt(invent) < u.ulevel * 1000) ? "meager " - : ""); + (obj->quan + money_cnt(gi.invent) < u.ulevel * 1000) + ? "meager " : ""); + } else if (obj->otyp == TOWEL) { + pline("It's probably not a good time for a picnic..."); } else { - You("sit on %s.", the(xname(obj))); - if (!(Is_box(obj) || objects[obj->otyp].oc_material == CLOTH)) + if (slithy(gy.youmonst.data)) + You("coil up around %s.", the(xname(obj))); + else + You("sit on %s.", the(xname(obj))); + if (obj->otyp == CORPSE && amorphous(&mons[obj->corpsenm])) + pline("It's squishy..."); + else if (obj->otyp == CREAM_PIE) { + if (!Deaf) { + Soundeffect(se_squelch, 30); + pline("Squelch!"); + } + useupf(obj, obj->quan); + } else if (!(Is_box(obj) + || objects[obj->otyp].oc_material == CLOTH)) pline("It's not very comfortable..."); } } else if (trap != 0 || (u.utrap && (u.utraptype >= TT_LAVA))) { @@ -113,25 +496,37 @@ dosit() u.utrap++; } } else { - You("sit down."); + /* when flying, "you land" might need some refinement; it sounds + as if you're staying on the ground but you will immediately + take off again unless you become stuck in a holding trap */ + You("%s.", Flying ? "land" : "sit down"); dotrap(trap, VIASITTING); } } else if ((Underwater || Is_waterlevel(&u.uz)) - && !eggs_in_water(youmonst.data)) { + && !eggs_in_water(gy.youmonst.data)) { if (Is_waterlevel(&u.uz)) There("are no cushions floating nearby."); else You("sit down on the muddy bottom."); - } else if (is_pool(u.ux, u.uy) && !eggs_in_water(youmonst.data)) { - in_water: + } else if (is_pool(u.ux, u.uy) && !eggs_in_water(gy.youmonst.data)) { + in_water: You("sit in the %s.", hliquid("water")); - if (!rn2(10) && uarm) - (void) water_damage(uarm, "armor", TRUE); - if (!rn2(10) && uarmf && uarmf->otyp != WATER_WALKING_BOOTS) - (void) water_damage(uarm, "armor", TRUE); + if (Upolyd && u.umonnum == PM_GREMLIN) { + if (split_mon(&gy.youmonst, (struct monst *) 0)) { + if (levl[u.ux][u.uy].typ == FOUNTAIN) + dryup(u.ux, u.uy, TRUE); + } + /* splitting--or failing to do so--protects gear from the water */ + } else { + if (!rn2(10) && uarm) + (void) water_damage(uarm, "armor", TRUE); + if (!rn2(10) && uarmf && uarmf->otyp != WATER_WALKING_BOOTS) + (void) water_damage(uarm, "armor", TRUE); + } } else if (IS_SINK(typ)) { You(sit_message, defsyms[S_sink].explanation); - Your("%s gets wet.", humanoid(youmonst.data) ? "rump" : "underside"); + Your("%s gets wet.", + humanoid(gy.youmonst.data) ? "rump" : "underside"); } else if (IS_ALTAR(typ)) { You(sit_message, defsyms[S_altar].explanation); altar_wrath(u.ux, u.uy); @@ -145,9 +540,9 @@ dosit() /* must be WWalking */ You(sit_message, hliquid("lava")); burn_away_slime(); - if (likes_lava(youmonst.data)) { + if (likes_lava(gy.youmonst.data)) { pline_The("%s feels warm.", hliquid("lava")); - return 1; + return ECMD_TIME; } pline_The("%s burns you!", hliquid("lava")); losehp(d((Fire_resistance ? 2 : 10), 10), /* lava damage */ @@ -160,203 +555,46 @@ dosit() You(sit_message, "drawbridge"); } else if (IS_THRONE(typ)) { You(sit_message, defsyms[S_throne].explanation); - if (rnd(6) > 4) { - switch (rnd(13)) { - case 1: - (void) adjattrib(rn2(A_MAX), -rn1(4, 3), FALSE); - losehp(rnd(10), "cursed throne", KILLED_BY_AN); - break; - case 2: - (void) adjattrib(rn2(A_MAX), 1, FALSE); - break; - case 3: - pline("A%s electric shock shoots through your body!", - (Shock_resistance) ? "n" : " massive"); - losehp(Shock_resistance ? rnd(6) : rnd(30), "electric chair", - KILLED_BY_AN); - exercise(A_CON, FALSE); - break; - case 4: - You_feel("much, much better!"); - if (Upolyd) { - if (u.mh >= (u.mhmax - 5)) - u.mhmax += 4; - u.mh = u.mhmax; - } - if (u.uhp >= (u.uhpmax - 5)) - u.uhpmax += 4; - u.uhp = u.uhpmax; - u.ucreamed = 0; - make_blinded(0L, TRUE); - make_sick(0L, (char *) 0, FALSE, SICK_ALL); - heal_legs(0); - context.botl = 1; - break; - case 5: - take_gold(); - break; - case 6: - if (u.uluck + rn2(5) < 0) { - You_feel("your luck is changing."); - change_luck(1); - } else - makewish(); - break; - case 7: - { - int cnt = rnd(10); - - /* Magical voice not affected by deafness */ - pline("A voice echoes:"); - verbalize("Thy audience hath been summoned, %s!", - flags.female ? "Dame" : "Sire"); - while (cnt--) - (void) makemon(courtmon(), u.ux, u.uy, NO_MM_FLAGS); - break; - } - case 8: - /* Magical voice not affected by deafness */ - pline("A voice echoes:"); - verbalize("By thine Imperious order, %s...", - flags.female ? "Dame" : "Sire"); - do_genocide(5); /* REALLY|ONTHRONE, see do_genocide() */ - break; - case 9: - /* Magical voice not affected by deafness */ - pline("A voice echoes:"); - verbalize( - "A curse upon thee for sitting upon this most holy throne!"); - if (Luck > 0) { - make_blinded(Blinded + rn1(100, 250), TRUE); - change_luck((Luck > 1) ? -rnd(2) : -1); - } else - rndcurse(); - break; - case 10: - if (Luck < 0 || (HSee_invisible & INTRINSIC)) { - if (level.flags.nommap) { - pline("A terrible drone fills your head!"); - make_confused((HConfusion & TIMEOUT) + (long) rnd(30), - FALSE); - } else { - pline("An image forms in your mind."); - do_mapping(); - } - } else { - Your("vision becomes clear."); - HSee_invisible |= FROMOUTSIDE; - newsym(u.ux, u.uy); - } - break; - case 11: - if (Luck < 0) { - You_feel("threatened."); - aggravate(); - } else { - You_feel("a wrenching sensation."); - tele(); /* teleport him */ - } - break; - case 12: - You("are granted an insight!"); - if (invent) { - /* rn2(5) agrees w/seffects() */ - identify_pack(rn2(5), FALSE); - } - break; - case 13: - Your("mind turns into a pretzel!"); - make_confused((HConfusion & TIMEOUT) + (long) rn1(7, 16), - FALSE); - break; - default: - impossible("throne effect"); - break; - } - } else { - if (is_prince(youmonst.data)) - You_feel("very comfortable here."); - else - You_feel("somehow out of place..."); - } - - if (!rn2(3) && IS_THRONE(levl[u.ux][u.uy].typ)) { - /* may have teleported */ - levl[u.ux][u.uy].typ = ROOM, levl[u.ux][u.uy].flags = 0; - pline_The("throne vanishes in a puff of logic."); - newsym(u.ux, u.uy); - } - } else if (lays_eggs(youmonst.data)) { - struct obj *uegg; - - if (!flags.female) { - pline("%s can't lay eggs!", - Hallucination - ? "You may think you are a platypus, but a male still" - : "Males"); - return 0; - } else if (u.uhunger < (int) objects[EGG].oc_nutrition) { - You("don't have enough energy to lay an egg."); - return 0; - } else if (eggs_in_water(youmonst.data)) { - if (!(Underwater || Is_waterlevel(&u.uz))) { - pline("A splash tetra you are not."); - return 0; - } - if (Upolyd && - (youmonst.data == &mons[PM_GIANT_EEL] - || youmonst.data == &mons[PM_ELECTRIC_EEL])) { - You("yearn for the Sargasso Sea."); - return 0; - } - } - uegg = mksobj(EGG, FALSE, FALSE); - uegg->spe = 1; - uegg->quan = 1L; - uegg->owt = weight(uegg); - /* this sets hatch timers if appropriate */ - set_corpsenm(uegg, egg_type_from_parent(u.umonnum, FALSE)); - uegg->known = uegg->dknown = 1; - You("%s an egg.", eggs_in_water(youmonst.data) ? "spawn" : "lay"); - dropy(uegg); - stackobj(uegg); - morehungry((int) objects[EGG].oc_nutrition); + throne_sit_effect(); + } else if (lays_eggs(gy.youmonst.data)) { + return lay_an_egg(); } else { pline("Having fun sitting on the %s?", surface(u.ux, u.uy)); } - return 1; + return ECMD_TIME; } /* curse a few inventory items at random! */ void -rndcurse() +rndcurse(void) { int nobj = 0; int cnt, onum; struct obj *otmp; static const char mal_aura[] = "feel a malignant aura surround %s."; - if (uwep && (uwep->oartifact == ART_MAGICBANE) && rn2(20)) { + if (u_wield_art(ART_MAGICBANE) && rn2(20)) { You(mal_aura, "the magic-absorbing blade"); return; } if (Antimagic) { shieldeff(u.ux, u.uy); - You(mal_aura, "you"); } - for (otmp = invent; otmp; otmp = otmp->nobj) { + You(mal_aura, "you"); + + for (otmp = gi.invent; otmp; otmp = otmp->nobj) { /* gold isn't subject to being cursed or blessed */ if (otmp->oclass == COIN_CLASS) continue; nobj++; } + cnt = rnd(6 / ((!!Antimagic) + (!!Half_spell_damage) + 1)); if (nobj) { - for (cnt = rnd(6 / ((!!Antimagic) + (!!Half_spell_damage) + 1)); - cnt > 0; cnt--) { + for (; cnt > 0; cnt--) { onum = rnd(nobj); - for (otmp = invent; otmp; otmp = otmp->nobj) { + for (otmp = gi.invent; otmp; otmp = otmp->nobj) { /* as above */ if (otmp->oclass == COIN_CLASS) continue; @@ -392,36 +630,48 @@ rndcurse() if (!Blind) { pline("%s %s.", Yobjnam2(otmp, "glow"), hcolor(otmp->cursed ? NH_BLACK : (const char *) "brown")); - otmp->bknown = 1; /* ok to bypass set_bknown() here */ + otmp->bknown = Hallucination ? 0 : 1; /* bypass set_bknown() */ + } else { + otmp->bknown = 0; /* bypass set_bknown() */ } } } -/* remove a random INTRINSIC ability */ -void -attrcurse() +/* remove a random INTRINSIC ability from hero. + returns the intrinsic property which was removed, + or 0 if nothing was removed. */ +int +attrcurse(void) { + int ret = 0; + switch (rnd(11)) { case 1: if (HFire_resistance & INTRINSIC) { HFire_resistance &= ~INTRINSIC; You_feel("warmer."); + ret = FIRE_RES; break; } + FALLTHROUGH; /*FALLTHRU*/ case 2: if (HTeleportation & INTRINSIC) { HTeleportation &= ~INTRINSIC; You_feel("less jumpy."); + ret = TELEPORT; break; } + FALLTHROUGH; /*FALLTHRU*/ case 3: if (HPoison_resistance & INTRINSIC) { HPoison_resistance &= ~INTRINSIC; You_feel("a little sick!"); + ret = POISON_RES; break; } + FALLTHROUGH; /*FALLTHRU*/ case 4: if (HTelepat & INTRINSIC) { @@ -429,63 +679,86 @@ attrcurse() if (Blind && !Blind_telepat) see_monsters(); /* Can't sense mons anymore! */ Your("senses fail!"); + ret = TELEPAT; break; } + FALLTHROUGH; /*FALLTHRU*/ case 5: if (HCold_resistance & INTRINSIC) { HCold_resistance &= ~INTRINSIC; You_feel("cooler."); + ret = COLD_RES; break; } + FALLTHROUGH; /*FALLTHRU*/ case 6: if (HInvis & INTRINSIC) { HInvis &= ~INTRINSIC; You_feel("paranoid."); + ret = INVIS; break; } + FALLTHROUGH; /*FALLTHRU*/ case 7: if (HSee_invisible & INTRINSIC) { HSee_invisible &= ~INTRINSIC; + if (!See_invisible) { + set_mimic_blocking(); + see_monsters(); + /* might not be able to see self anymore */ + newsym(u.ux, u.uy); + } You("%s!", Hallucination ? "tawt you taw a puttie tat" : "thought you saw something"); + ret = SEE_INVIS; break; } + FALLTHROUGH; /*FALLTHRU*/ case 8: if (HFast & INTRINSIC) { HFast &= ~INTRINSIC; You_feel("slower."); + ret = FAST; break; } + FALLTHROUGH; /*FALLTHRU*/ case 9: if (HStealth & INTRINSIC) { HStealth &= ~INTRINSIC; You_feel("clumsy."); + ret = STEALTH; break; } + FALLTHROUGH; /*FALLTHRU*/ case 10: /* intrinsic protection is just disabled, not set back to 0 */ if (HProtection & INTRINSIC) { HProtection &= ~INTRINSIC; You_feel("vulnerable."); + ret = PROTECTION; break; } + FALLTHROUGH; /*FALLTHRU*/ case 11: if (HAggravate_monster & INTRINSIC) { HAggravate_monster &= ~INTRINSIC; You_feel("less attractive."); + ret = AGGRAVATE_MONSTER; break; } + FALLTHROUGH; /*FALLTHRU*/ default: break; } + return ret; } /*sit.c*/ diff --git a/src/sounds.c b/src/sounds.c index 1bf77165f..be48f9e30 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -1,34 +1,208 @@ -/* NetHack 3.6 sounds.c $NHDT-Date: 1570844005 2019/10/12 01:33:25 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.83 $ */ +/* NetHack 5.0 sounds.c $NHDT-Date: 1736530208 2025/01/10 09:30:08 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.165 $ */ /* Copyright (c) 1989 Janet Walz, Mike Threepoint */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -STATIC_DCL boolean FDECL(mon_is_gecko, (struct monst *)); -STATIC_DCL int FDECL(domonnoise, (struct monst *)); -STATIC_DCL int NDECL(dochat); -STATIC_DCL int FDECL(mon_in_room, (struct monst *, int)); +staticfn boolean throne_mon_sound(struct monst *); +staticfn boolean beehive_mon_sound(struct monst *); +staticfn boolean morgue_mon_sound(struct monst *); +staticfn boolean zoo_mon_sound(struct monst *); +staticfn boolean temple_priest_sound(struct monst *); +staticfn boolean mon_is_gecko(struct monst *); +staticfn int dochat(void); +staticfn struct monst *responsive_mon_at(int, int); +staticfn int mon_in_room(struct monst *, int); +staticfn boolean oracle_sound(struct monst *); /* this easily could be a macro, but it might overtax dumb compilers */ -STATIC_OVL int -mon_in_room(mon, rmtyp) -struct monst *mon; -int rmtyp; +staticfn int +mon_in_room(struct monst *mon, int rmtyp) { int rno = levl[mon->mx][mon->my].roomno; if (rno >= ROOMOFFSET) - return rooms[rno - ROOMOFFSET].rtype == rmtyp; + return svr.rooms[rno - ROOMOFFSET].rtype == rmtyp; return FALSE; } + +staticfn boolean +throne_mon_sound(struct monst *mtmp) +{ + if ((mtmp->msleeping || is_lord(mtmp->data) + || is_prince(mtmp->data)) && !is_animal(mtmp->data) + && mon_in_room(mtmp, COURT)) { + static const char *const throne_msg[4] = { + "the tones of courtly conversation.", + "a sceptre pounded in judgment.", + "Someone shouts \"Off with %s head!\"", + "Queen Beruthiel's cats!", + }; + int which = rn2(3) + (Hallucination ? 1 : 0); + + if (which != 2) { + if (which == 0) { + Soundeffect(se_courtly_conversation, 30); + } else if (which == 1) { + Soundeffect(se_sceptor_pounding, 100); + } + You_hear1(throne_msg[which]); + } else { + DISABLE_WARNING_FORMAT_NONLITERAL + pline(throne_msg[2], uhis()); + RESTORE_WARNING_FORMAT_NONLITERAL + } + return TRUE; + } + return FALSE; +} + + +staticfn boolean +beehive_mon_sound(struct monst *mtmp) +{ + if ((mtmp->data->mlet == S_ANT && is_flyer(mtmp->data)) + && mon_in_room(mtmp, BEEHIVE)) { + int hallu = Hallucination ? 1 : 0; + + switch (rn2(2) + hallu) { + case 0: + Soundeffect(se_low_buzzing, 30); + You_hear("a low buzzing."); + break; + case 1: + Soundeffect(se_angry_drone, 100); + You_hear("an angry drone."); + break; + case 2: + Soundeffect(se_bees, 100); + You_hear("bees in your %sbonnet!", + uarmh ? "" : "(nonexistent) "); + break; + } + return TRUE; + } + return FALSE; +} + +staticfn boolean +morgue_mon_sound(struct monst *mtmp) +{ + if ((is_undead(mtmp->data) || is_vampshifter(mtmp)) + && mon_in_room(mtmp, MORGUE)) { + int hallu = Hallucination ? 1 : 0; + const char *hair = body_part(HAIR); /* hair/fur/scales */ + + switch (rn2(2) + hallu) { + case 0: + You("suddenly realize it is unnaturally quiet."); + break; + case 1: + pline_The("%s on the back of your %s %s up.", hair, + body_part(NECK), vtense(hair, "stand")); + break; + case 2: + pline_The("%s on your %s %s to stand up.", hair, + body_part(HEAD), vtense(hair, "seem")); + break; + } + return TRUE; + } + return FALSE; +} + +staticfn boolean +zoo_mon_sound(struct monst *mtmp) +{ + if ((mtmp->msleeping || is_animal(mtmp->data)) + && mon_in_room(mtmp, ZOO)) { + int hallu = Hallucination ? 1 : 0, selection = rn2(2) + hallu; + static const char *const zoo_msg[3] = { + "a sound reminiscent of an elephant stepping on a peanut.", + "a sound reminiscent of a seal barking.", "Doctor Dolittle!", + }; + You_hear1(zoo_msg[selection]); + return TRUE; + } + return FALSE; +} + +staticfn boolean +temple_priest_sound(struct monst *mtmp) +{ + if (mtmp->ispriest && inhistemple(mtmp) + /* priest must be active */ + && !helpless(mtmp) + /* hero must be outside this temple */ + && temple_occupied(u.urooms) != EPRI(mtmp)->shroom) { + /* Generic temple messages; no attempt to match topic or tone + to the pantheon involved, let alone to the specific deity. + These are assumed to be coming from the attending priest; + asterisk means that the priest must be capable of speech; + pound sign (octathorpe,&c--don't go there) means that the + priest and the altar must not be directly visible (we don't + care if telepathy or extended detection reveals that the + priest is not currently standing on the altar; he's mobile). */ + static const char *const temple_msg[] = { + "*someone praising %s.", "*someone beseeching %s.", + "#an animal carcass being offered in sacrifice.", + "*a strident plea for donations.", + }; + const char *msg; + int hallu = Hallucination ? 1 : 0; + int trycount = 0, + ax = EPRI(mtmp)->shrpos.x, + ay = EPRI(mtmp)->shrpos.y; + boolean speechless = (mtmp->data->msound <= MS_ANIMAL), + in_sight = canseemon(mtmp) || cansee(ax, ay); + + do { + msg = temple_msg[rn2(SIZE(temple_msg) - 1 + hallu)]; + if (strchr(msg, '*') && speechless) + continue; + if (strchr(msg, '#') && in_sight) + continue; + break; /* msg is acceptable */ + } while (++trycount < 50); + while (!letter(*msg)) + ++msg; /* skip control flags */ + if (strchr(msg, '%')) { + DISABLE_WARNING_FORMAT_NONLITERAL + You_hear(msg, halu_gname(EPRI(mtmp)->shralign)); + RESTORE_WARNING_FORMAT_NONLITERAL + } else + You_hear1(msg); + return TRUE; + } + return FALSE; +} + +staticfn boolean +oracle_sound(struct monst *mtmp) +{ + if (mtmp->data != &mons[PM_ORACLE]) + return FALSE; + + /* and don't produce silly effects when she's clearly visible */ + if (Hallucination || !canseemon(mtmp)) { + int hallu = Hallucination ? 1 : 0; + static const char *const ora_msg[5] = { + "a strange wind.", /* Jupiter at Dodona */ + "convulsive ravings.", /* Apollo at Delphi */ + "snoring snakes.", /* AEsculapius at Epidaurus */ + "someone say \"No more woodchucks!\"", + "a loud ZOT!" /* both rec.humor.oracle */ + }; + You_hear1(ora_msg[rn2(3) + hallu * 2]); + } + return TRUE; +} + void -dosounds() +dosounds(void) { - register struct mkroom *sroom; - register int hallu, vx, vy; -#if defined(AMIGA) && defined(AZTEC_C_WORKAROUND) - int xx; -#endif + struct mkroom *sroom; + int hallu, vx, vy; struct monst *mtmp; if (Deaf || !flags.acoustics || u.uswallow || Underwater) @@ -36,43 +210,24 @@ dosounds() hallu = Hallucination ? 1 : 0; - if (level.flags.nfountains && !rn2(400)) { + if (svl.level.flags.nfountains && !rn2(400)) { static const char *const fountain_msg[4] = { "bubbling water.", "water falling on coins.", "the splashing of a naiad.", "a soda fountain!", }; You_hear1(fountain_msg[rn2(3) + hallu]); } - if (level.flags.nsinks && !rn2(300)) { + if (svl.level.flags.nsinks && !rn2(300)) { static const char *const sink_msg[3] = { "a slow drip.", "a gurgling noise.", "dishes being washed!", }; You_hear1(sink_msg[rn2(2) + hallu]); } - if (level.flags.has_court && !rn2(200)) { - static const char *const throne_msg[4] = { - "the tones of courtly conversation.", - "a sceptre pounded in judgment.", - "Someone shouts \"Off with %s head!\"", "Queen Beruthiel's cats!", - }; - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) - continue; - if ((mtmp->msleeping || is_lord(mtmp->data) - || is_prince(mtmp->data)) && !is_animal(mtmp->data) - && mon_in_room(mtmp, COURT)) { - /* finding one is enough, at least for now */ - int which = rn2(3) + hallu; - - if (which != 2) - You_hear1(throne_msg[which]); - else - pline(throne_msg[2], uhis()); - return; - } - } + if (svl.level.flags.has_court && !rn2(200)) { + if (get_iter_mons(throne_mon_sound)) + return; } - if (level.flags.has_swamp && !rn2(200)) { + if (svl.level.flags.has_swamp && !rn2(200)) { static const char *const swamp_msg[3] = { "hear mosquitoes!", "smell marsh gas!", /* so it's a smell...*/ "hear Donald Duck!", @@ -80,10 +235,10 @@ dosounds() You1(swamp_msg[rn2(2) + hallu]); return; } - if (level.flags.has_vault && !rn2(200)) { + if (svl.level.flags.has_vault && !rn2(200)) { if (!(sroom = search_special(VAULT))) { /* strange ... */ - level.flags.has_vault = 0; + svl.level.flags.has_vault = 0; return; } if (gd_sound()) @@ -95,27 +250,23 @@ dosounds() for (vy = sroom->ly; vy <= sroom->hy; vy++) if (g_at(vx, vy)) gold_in_vault = TRUE; -#if defined(AMIGA) && defined(AZTEC_C_WORKAROUND) - /* Bug in aztec assembler here. Workaround below */ - xx = ROOM_INDEX(sroom) + ROOMOFFSET; - xx = (xx != vault_occupied(u.urooms)); - if (xx) -#else if (vault_occupied(u.urooms) - != (ROOM_INDEX(sroom) + ROOMOFFSET)) -#endif /* AZTEC_C_WORKAROUND */ - { - if (gold_in_vault) + != (ROOM_INDEX(sroom) + ROOMOFFSET)) { + if (gold_in_vault) { You_hear(!hallu - ? "someone counting money." + ? "someone counting gold coins." : "the quarterback calling the play."); - else + } else { + Soundeffect(se_someone_searching, 30); You_hear("someone searching."); + } break; } } + FALLTHROUGH; /*FALLTHRU*/ case 0: + Soundeffect(se_guards_footsteps, 30); You_hear("the footsteps of a guard on patrol."); break; case 2: @@ -124,54 +275,15 @@ dosounds() } return; } - if (level.flags.has_beehive && !rn2(200)) { - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) - continue; - if ((mtmp->data->mlet == S_ANT && is_flyer(mtmp->data)) - && mon_in_room(mtmp, BEEHIVE)) { - switch (rn2(2) + hallu) { - case 0: - You_hear("a low buzzing."); - break; - case 1: - You_hear("an angry drone."); - break; - case 2: - You_hear("bees in your %sbonnet!", - uarmh ? "" : "(nonexistent) "); - break; - } - return; - } - } + if (svl.level.flags.has_beehive && !rn2(200)) { + if (get_iter_mons(beehive_mon_sound)) + return; } - if (level.flags.has_morgue && !rn2(200)) { - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) - continue; - if ((is_undead(mtmp->data) || is_vampshifter(mtmp)) - && mon_in_room(mtmp, MORGUE)) { - const char *hair = body_part(HAIR); /* hair/fur/scales */ - - switch (rn2(2) + hallu) { - case 0: - You("suddenly realize it is unnaturally quiet."); - break; - case 1: - pline_The("%s on the back of your %s %s up.", hair, - body_part(NECK), vtense(hair, "stand")); - break; - case 2: - pline_The("%s on your %s %s to stand up.", hair, - body_part(HEAD), vtense(hair, "seem")); - break; - } - return; - } - } + if (svl.level.flags.has_morgue && !rn2(200)) { + if (get_iter_mons(morgue_mon_sound)) + return; } - if (level.flags.has_barracks && !rn2(200)) { + if (svl.level.flags.has_barracks && !rn2(200)) { static const char *const barracks_msg[4] = { "blades being honed.", "loud snoring.", "dice being thrown.", "General MacArthur!", @@ -183,8 +295,8 @@ dosounds() continue; if (is_mercenary(mtmp->data) #if 0 /* don't bother excluding these */ - && !strstri(mtmp->data->mname, "watch") - && !strstri(mtmp->data->mname, "guard") + && !strstri(mtmp->data->pmnames[NEUTRAL], "watch") + && !strstri(mtmp->data->pmnames[NEUTRAL], "guard") #endif && mon_in_room(mtmp, BARRACKS) /* sleeping implies not-yet-disturbed (usually) */ @@ -194,106 +306,35 @@ dosounds() } } } - if (level.flags.has_zoo && !rn2(200)) { - static const char *const zoo_msg[3] = { - "a sound reminiscent of an elephant stepping on a peanut.", - "a sound reminiscent of a seal barking.", "Doctor Dolittle!", - }; - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) - continue; - if ((mtmp->msleeping || is_animal(mtmp->data)) - && mon_in_room(mtmp, ZOO)) { - You_hear1(zoo_msg[rn2(2) + hallu]); - return; - } - } + if (svl.level.flags.has_zoo && !rn2(200)) { + if (get_iter_mons(zoo_mon_sound)) + return; } - if (level.flags.has_shop && !rn2(200)) { + if (svl.level.flags.has_shop && !rn2(200)) { if (!(sroom = search_special(ANY_SHOP))) { /* strange... */ - level.flags.has_shop = 0; + svl.level.flags.has_shop = 0; return; } if (tended_shop(sroom) - && !index(u.ushops, (int) (ROOM_INDEX(sroom) + ROOMOFFSET))) { + && !strchr(u.ushops, (int) (ROOM_INDEX(sroom) + ROOMOFFSET))) { static const char *const shop_msg[3] = { "someone cursing shoplifters.", "the chime of a cash register.", "Neiman and Marcus arguing!", }; You_hear1(shop_msg[rn2(2) + hallu]); + noisy_shop(sroom); } return; } - if (level.flags.has_temple && !rn2(200) + if (svl.level.flags.has_temple && !rn2(200) && !(Is_astralevel(&u.uz) || Is_sanctum(&u.uz))) { - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) - continue; - if (mtmp->ispriest && inhistemple(mtmp) - /* priest must be active */ - && mtmp->mcanmove && !mtmp->msleeping - /* hero must be outside this temple */ - && temple_occupied(u.urooms) != EPRI(mtmp)->shroom) - break; - } - if (mtmp) { - /* Generic temple messages; no attempt to match topic or tone - to the pantheon involved, let alone to the specific deity. - These are assumed to be coming from the attending priest; - asterisk means that the priest must be capable of speech; - pound sign (octathorpe,&c--don't go there) means that the - priest and the altar must not be directly visible (we don't - care if telepathy or extended detection reveals that the - priest is not currently standing on the altar; he's mobile). */ - static const char *const temple_msg[] = { - "*someone praising %s.", "*someone beseeching %s.", - "#an animal carcass being offered in sacrifice.", - "*a strident plea for donations.", - }; - const char *msg; - int trycount = 0, ax = EPRI(mtmp)->shrpos.x, - ay = EPRI(mtmp)->shrpos.y; - boolean speechless = (mtmp->data->msound <= MS_ANIMAL), - in_sight = canseemon(mtmp) || cansee(ax, ay); - - do { - msg = temple_msg[rn2(SIZE(temple_msg) - 1 + hallu)]; - if (index(msg, '*') && speechless) - continue; - if (index(msg, '#') && in_sight) - continue; - break; /* msg is acceptable */ - } while (++trycount < 50); - while (!letter(*msg)) - ++msg; /* skip control flags */ - if (index(msg, '%')) - You_hear(msg, halu_gname(EPRI(mtmp)->shralign)); - else - You_hear1(msg); + if (get_iter_mons(temple_priest_sound)) return; - } } if (Is_oracle_level(&u.uz) && !rn2(400)) { - /* make sure the Oracle is still here */ - for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) - continue; - if (mtmp->data == &mons[PM_ORACLE]) - break; - } - /* and don't produce silly effects when she's clearly visible */ - if (mtmp && (hallu || !canseemon(mtmp))) { - static const char *const ora_msg[5] = { - "a strange wind.", /* Jupiter at Dodona */ - "convulsive ravings.", /* Apollo at Delphi */ - "snoring snakes.", /* AEsculapius at Epidaurus */ - "someone say \"No more woodchucks!\"", - "a loud ZOT!" /* both rec.humor.oracle */ - }; - You_hear1(ora_msg[rn2(3) + hallu * 2]); - } - return; + if (get_iter_mons(oracle_sound)) + return; } } @@ -303,12 +344,11 @@ static const char *const h_sounds[] = { "eep", "clatter", "hum", "sizzle", "twitter", "wheeze", "rustle", "honk", "lisp", "yodel", "coo", "burp", "moo", "boom", "murmur", "oink", "quack", "rumble", - "twang", "bellow", "toot", "gargle", "hoot", "warble" + "twang", "toot", "gargle", "hoot", "warble" }; const char * -growl_sound(mtmp) -register struct monst *mtmp; +growl_sound(struct monst *mtmp) { const char *ret; @@ -324,6 +364,9 @@ register struct monst *mtmp; case MS_ROAR: ret = "roar"; break; + case MS_BELLOW: + ret = "bellow"; + break; case MS_BUZZ: ret = "buzz"; break; @@ -339,6 +382,12 @@ register struct monst *mtmp; case MS_WAIL: ret = "wail"; break; + case MS_GROAN: + ret = "groan"; + break; + case MS_MOO: + ret = "low"; + break; case MS_SILENT: ret = "commotion"; break; @@ -350,83 +399,93 @@ register struct monst *mtmp; /* the sounds of a seriously abused pet, including player attacking it */ void -growl(mtmp) -register struct monst *mtmp; +growl(struct monst *mtmp) { - register const char *growl_verb = 0; + const char *growl_verb = 0; - if (mtmp->msleeping || !mtmp->mcanmove || !mtmp->data->msound) + if (helpless(mtmp) || mtmp->data->msound == MS_SILENT) return; /* presumably nearness and soundok checks have already been made */ if (Hallucination) - growl_verb = h_sounds[rn2(SIZE(h_sounds))]; + growl_verb = ROLL_FROM(h_sounds); else growl_verb = growl_sound(mtmp); if (growl_verb) { - pline("%s %s!", Monnam(mtmp), vtense((char *) 0, growl_verb)); - if (context.run) - nomul(0); + if (canseemon(mtmp) || !Deaf) { + pline("%s %s!", Monnam(mtmp), vtense((char *) 0, growl_verb)); + iflags.last_msg = PLNMSG_GROWL; + if (svc.context.run) + nomul(0); + } wake_nearto(mtmp->mx, mtmp->my, mtmp->data->mlevel * 18); } } /* the sounds of mistreated pets */ void -yelp(mtmp) -register struct monst *mtmp; +yelp(struct monst *mtmp) { - register const char *yelp_verb = 0; + const char *yelp_verb = 0; + enum sound_effect_entries se = se_yelp; - if (mtmp->msleeping || !mtmp->mcanmove || !mtmp->data->msound) + if (helpless(mtmp) || !mtmp->data->msound) return; /* presumably nearness and soundok checks have already been made */ if (Hallucination) - yelp_verb = h_sounds[rn2(SIZE(h_sounds))]; + yelp_verb = ROLL_FROM(h_sounds); else switch (mtmp->data->msound) { case MS_MEW: + se = se_feline_yelp; yelp_verb = (!Deaf) ? "yowl" : "arch"; break; case MS_BARK: case MS_GROWL: + se = se_canine_yelp; yelp_verb = (!Deaf) ? "yelp" : "recoil"; break; case MS_ROAR: yelp_verb = (!Deaf) ? "snarl" : "bluff"; break; case MS_SQEEK: + se = se_squeal; yelp_verb = (!Deaf) ? "squeal" : "quiver"; break; case MS_SQAWK: + se = se_avian_screak; yelp_verb = (!Deaf) ? "screak" : "thrash"; break; case MS_WAIL: + se = se_wail; yelp_verb = (!Deaf) ? "wail" : "cringe"; break; } if (yelp_verb) { + Soundeffect(se, 70); /* Soundeffect() handles Deaf or not Deaf */ pline("%s %s!", Monnam(mtmp), vtense((char *) 0, yelp_verb)); - if (context.run) + if (svc.context.run) nomul(0); wake_nearto(mtmp->mx, mtmp->my, mtmp->data->mlevel * 12); } +#ifndef SND_LIB_INTEGRATED + nhUse(se); +#endif } /* the sounds of distressed pets */ void -whimper(mtmp) -register struct monst *mtmp; +whimper(struct monst *mtmp) { - register const char *whimper_verb = 0; - - if (mtmp->msleeping || !mtmp->mcanmove || !mtmp->data->msound) + const char *whimper_verb = 0; + enum sound_effect_entries se = se_canine_whine; + if (helpless(mtmp) || !mtmp->data->msound) return; /* presumably nearness and soundok checks have already been made */ if (Hallucination) - whimper_verb = h_sounds[rn2(SIZE(h_sounds))]; + whimper_verb = ROLL_FROM(h_sounds); else switch (mtmp->data->msound) { case MS_MEW: @@ -437,40 +496,167 @@ register struct monst *mtmp; whimper_verb = "whine"; break; case MS_SQEEK: + se = se_squeal; whimper_verb = "squeal"; break; } if (whimper_verb) { + if (!Hallucination) { + Soundeffect(se, 50); + } pline("%s %s.", Monnam(mtmp), vtense((char *) 0, whimper_verb)); - if (context.run) + if (svc.context.run) nomul(0); wake_nearto(mtmp->mx, mtmp->my, mtmp->data->mlevel * 6); } +#ifndef SND_LIB_INTEGRATED + nhUse(se); +#endif } /* pet makes "I'm hungry" noises */ void -beg(mtmp) -register struct monst *mtmp; +beg(struct monst *mtmp) { - if (mtmp->msleeping || !mtmp->mcanmove + if (helpless(mtmp) || !(carnivorous(mtmp->data) || herbivorous(mtmp->data))) return; /* presumably nearness and soundok checks have already been made */ - if (!is_silent(mtmp->data) && mtmp->data->msound <= MS_ANIMAL) + if (!is_silent(mtmp->data) && mtmp->data->msound <= MS_ANIMAL) { (void) domonnoise(mtmp); - else if (mtmp->data->msound >= MS_HUMANOID) { + } else if (mtmp->data->msound >= MS_HUMANOID) { if (!canspotmon(mtmp)) map_invisible(mtmp->mx, mtmp->my); + SetVoice(mtmp, 0, 80, 0); verbalize("I'm hungry."); + } else { + /* this is pretty lame but is better than leaving out the block + of speech types between animal and humanoid; this covers + MS_SILENT too (if caller lets that get this far) since it's + excluded by the first two cases */ + if (canspotmon(mtmp)) + pline("%s seems famished.", Monnam(mtmp)); + /* looking famished will be a good trick for a tame skeleton... */ + } +} + +/* hero has attacked a peaceful monster within 'mon's view */ +const char * +maybe_gasp(struct monst *mon) +{ + static const char *const Exclam[] = { + "Gasp!", "Uh-oh.", "Oh my!", "What?", "Why?", + }; + struct permonst *mptr = mon->data; + int msound = mptr->msound; + boolean dogasp = FALSE; + + /* other roles' guardians and cross-aligned priests don't gasp */ + if ((msound == MS_GUARDIAN && mptr != &mons[gu.urole.guardnum]) + || (msound == MS_PRIEST && !p_coaligned(mon))) + msound = MS_SILENT; + /* co-aligned angels do gasp */ + else if (msound == MS_CUSS && has_emin(mon) + && (p_coaligned(mon) ? !EMIN(mon)->renegade : EMIN(mon)->renegade)) + msound = MS_HUMANOID; + + /* + * Only called for humanoids so animal noise handling is ignored. + */ + switch (msound) { + case MS_HUMANOID: + case MS_ARREST: /* Kops */ + case MS_SOLDIER: /* solider, watchman */ + case MS_GUARD: /* vault guard */ + case MS_NURSE: + case MS_SEDUCE: /* nymph, succubus/incubus */ + case MS_LEADER: /* quest leader */ + case MS_GUARDIAN: /* leader's guards */ + case MS_SELL: /* shopkeeper */ + case MS_ORACLE: + case MS_PRIEST: /* temple priest, roaming aligned priest (not mplayer) */ + case MS_BOAST: /* giants */ + case MS_IMITATE: /* doppelganger, leocrotta, Aleax */ + dogasp = TRUE; + break; + /* issue comprehensible word(s) if hero is similar type of creature */ + case MS_ORC: /* used to be synonym for MS_GRUNT */ + case MS_GRUNT: /* ogres, trolls, gargoyles, one or two others */ + case MS_LAUGH: /* leprechaun, gremlin */ + case MS_ROAR: /* dragon, xorn, owlbear */ + case MS_BELLOW: /* crocodile */ + /* capable of speech but only do so if hero is similar type */ + case MS_DJINNI: + case MS_VAMPIRE: /* vampire in its own form */ + case MS_WERE: /* lycanthrope in human form */ + case MS_SPELL: /* titan, barrow wight, Nazgul, nalfeshnee */ + dogasp = (mptr->mlet == gy.youmonst.data->mlet); + break; + /* capable of speech but don't care if you attack peacefuls */ + case MS_BRIBE: + case MS_CUSS: + case MS_RIDER: + case MS_NEMESIS: + /* can't speak */ + case MS_SILENT: + default: + break; } + if (dogasp) { + return ROLL_FROM(Exclam); /* [mon->m_id % SIZE(Exclam)]; */ + } + return (const char *) 0; +} + +/* for egg hatching; caller will apply "ing" suffix + [the old message when a carried egg hatches was + "its cries sound like {mommy,daddy}" + regardless of what type of sound--if any--the creature made] */ +const char * +cry_sound(struct monst *mtmp) +{ + const char *ret = 0; + struct permonst *ptr = mtmp->data; + + /* a relatively small subset of MS_ sound values are used by oviparous + species so we don't try to supply something for every MS_ type */ + switch (ptr->msound) { + default: + case MS_SILENT: /* insects, arthropods, worms, sea creatures */ + /* "chitter": have silent critters make some noise + or the mommy/daddy gag when hatching doesn't work */ + ret = (ptr->mlet == S_EEL) ? "gurgle" : "chitter"; + break; + case MS_HISS: /* chickatrice, pyrolisk, snakes */ + ret = "hiss"; + break; + case MS_ROAR: /* baby dragons; have them growl instead of roar */ + case MS_GROWL: /* (none) */ + ret = "growl"; + break; + case MS_CHIRP: /* adult crocodiles bellow, babies chirp */ + ret = "chirp"; + break; + case MS_BUZZ: /* killer bees */ + ret = "buzz"; + break; + case MS_SQAWK: /* ravens */ + ret = "screech"; + break; + case MS_GRUNT: /* gargoyles */ + ret = "grunt"; + break; + case MS_MUMBLE: /* naga hatchlingss */ + ret = "mumble"; + break; + } + return ret; } /* return True if mon is a gecko or seems to look like one (hallucination) */ -STATIC_OVL boolean -mon_is_gecko(mon) -struct monst *mon; +staticfn boolean +mon_is_gecko(struct monst *mon) { int glyph; @@ -487,12 +673,13 @@ struct monst *mon; return (boolean) (glyph_to_mon(glyph) == PM_GECKO); } -STATIC_OVL int -domonnoise(mtmp) -register struct monst *mtmp; +DISABLE_WARNING_FORMAT_NONLITERAL + +int /* check calls to this */ +domonnoise(struct monst *mtmp) { char verbuf[BUFSZ]; - register const char *pline_msg = 0, /* Monnam(mtmp) will be prepended */ + const char *pline_msg = 0, /* Monnam(mtmp) will be prepended */ *verbl_msg = 0, /* verbalize() */ *verbl_msg_mcan = 0; /* verbalize() if cancelled */ struct permonst *ptr = mtmp->data; @@ -500,23 +687,29 @@ register struct monst *mtmp; /* presumably nearness and sleep checks have already been made */ if (Deaf) - return 0; - if (is_silent(ptr)) - return 0; + return ECMD_OK; + /* shk_chat can handle nonverbal monsters */ + if (is_silent(ptr) && !mtmp->isshk) + return ECMD_OK; /* leader might be poly'd; if he can still speak, give leader speech */ - if (mtmp->m_id == quest_status.leader_m_id && msound > MS_ANIMAL) + if (mtmp->m_id == svq.quest_status.leader_m_id && msound > MS_ANIMAL) msound = MS_LEADER; /* make sure it's your role's quest guardian; adjust if not */ - else if (msound == MS_GUARDIAN && ptr != &mons[urole.guardnum]) + else if (msound == MS_GUARDIAN && ptr != &mons[gu.urole.guardnum]) msound = mons[genus(monsndx(ptr), 1)].msound; + /* even polymorphed, shopkeepers retain their minds and capitalist bent */ + else if (mtmp->isshk) + msound = MS_SELL; /* some normally non-speaking types can/will speak if hero is similar */ - else if (msound == MS_ORC /* note: MS_ORC is same as MS_GRUNT */ - && ((same_race(ptr, youmonst.data) /* current form, */ + else if (msound == MS_ORC + && ((same_race(ptr, gy.youmonst.data) /* current form, */ || same_race(ptr, &mons[Race_switch])) /* unpoly'd form */ || Hallucination)) msound = MS_HUMANOID; - /* silliness, with slight chance to interfere with shopping */ + else if (msound == MS_MOO && !mtmp->mtame) + msound = MS_BELLOW; + /* silliness; formerly had a slight chance to interfere with shopping */ else if (Hallucination && mon_is_gecko(mtmp)) msound = MS_SELL; @@ -528,7 +721,7 @@ register struct monst *mtmp; switch (msound) { case MS_ORACLE: - return doconsult(mtmp); + return doconsult(mtmp); /* check this */ case MS_PRIEST: priest_talk(mtmp); break; @@ -538,7 +731,7 @@ register struct monst *mtmp; quest_chat(mtmp); break; case MS_SELL: /* pitch, pay, total */ - if (!Hallucination || (mtmp->isshk && !rn2(2))) { + if (!Hallucination || is_silent(ptr) || (mtmp->isshk && !rn2(2))) { shk_chat(mtmp); } else { /* approximation of GEICO's advertising slogan (it actually @@ -550,17 +743,18 @@ register struct monst *mtmp; break; case MS_VAMPIRE: { /* vampire messages are varied by tameness, peacefulness, and time of - * night */ + 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 = - (flags.female && urace.individual.f) - ? urace.individual.f - : (urace.individual.m) ? urace.individual.m : urace.noun; + || u.umonnum == PM_VAMPIRE_LEADER)); + boolean nightchild = (Upolyd && (u.umonnum == PM_WOLF + || u.umonnum == PM_WINTER_WOLF + || u.umonnum == PM_WINTER_WOLF_CUB)); + const char *racenoun = (flags.female && gu.urace.individual.f) + ? gu.urace.individual.f + : (gu.urace.individual.m) + ? gu.urace.individual.m + : gu.urace.noun; if (mtmp->mtame) { if (kindred) { @@ -597,14 +791,15 @@ register struct monst *mtmp; }; int vampindex; - 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]) { + if (kindred) { + verbl_msg = "This is my hunting ground" + " that you dare to prowl!"; + } else if (gy.youmonst.data == &mons[PM_SILVER_DRAGON] + || gy.youmonst.data == &mons[PM_BABY_SILVER_DRAGON]) { /* Silver dragons are silver in color, not made of silver */ - Sprintf(verbuf, "%s! Your silver sheen does not frighten me!", - youmonst.data == &mons[PM_SILVER_DRAGON] + Sprintf(verbuf, + "%s! Your silver sheen"" does not frighten me!", + (gy.youmonst.data == &mons[PM_SILVER_DRAGON]) ? "Fool" : "Young Fool"); verbl_msg = verbuf; @@ -615,23 +810,29 @@ register struct monst *mtmp; verbl_msg = verbuf; } else if (vampindex == 1) { Sprintf(verbuf, vampmsg[vampindex], - Upolyd ? an(mons[u.umonnum].mname) + Upolyd ? an(pmname(&mons[u.umonnum], + flags.female ? FEMALE : MALE)) : an(racenoun)); verbl_msg = verbuf; } else verbl_msg = vampmsg[vampindex]; } } - } break; + 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"); + (ptr == &mons[PM_HUMAN_WERERAT]) ? "shriek" : "howl"); + Soundeffect((ptr == &mons[PM_HUMAN_WERERAT]) ? se_scream + : se_canine_howl, + 80); wake_nearto(mtmp->mx, mtmp->my, 11 * 11); - } else + } else { pline_msg = "whispers inaudibly. All you can make out is \"moon\"."; + } break; case MS_BARK: if (flags.moonphase == FULL_MOON && night()) { @@ -639,13 +840,12 @@ register struct monst *mtmp; } else if (mtmp->mpeaceful) { if (mtmp->mtame && (mtmp->mconf || mtmp->mflee || mtmp->mtrapped - || moves > EDOG(mtmp)->hungrytime || mtmp->mtame < 5)) + || svm.moves > EDOG(mtmp)->hungrytime || mtmp->mtame < 5)) pline_msg = "whines."; - else if (mtmp->mtame && EDOG(mtmp)->hungrytime > moves + 1000) + else if (mtmp->mtame && EDOG(mtmp)->hungrytime > svm.moves + 1000) pline_msg = "yips."; else { - if (mtmp->data - != &mons[PM_DINGO]) /* dingos do not actually bark */ + if (ptr != &mons[PM_DINGO]) /* dingos do not actually bark */ pline_msg = "barks."; } } else { @@ -655,62 +855,110 @@ register struct monst *mtmp; case MS_MEW: if (mtmp->mtame) { if (mtmp->mconf || mtmp->mflee || mtmp->mtrapped - || mtmp->mtame < 5) + || mtmp->mtame < 5) { + Soundeffect(se_feline_yowl, 80); pline_msg = "yowls."; - else if (moves > EDOG(mtmp)->hungrytime) + } else if (svm.moves > EDOG(mtmp)->hungrytime) { + Soundeffect(se_feline_meow, 80); pline_msg = "meows."; - else if (EDOG(mtmp)->hungrytime > moves + 1000) + } else if (EDOG(mtmp)->hungrytime > svm.moves + 1000) { + Soundeffect(se_feline_purr, 40); pline_msg = "purrs."; - else + } else { + Soundeffect(se_feline_mew, 60); pline_msg = "mews."; + } break; } + FALLTHROUGH; /*FALLTHRU*/ case MS_GROWL: + Soundeffect((mtmp->mpeaceful ? se_snarl : se_growl), 80); pline_msg = mtmp->mpeaceful ? "snarls." : "growls!"; break; case MS_ROAR: + Soundeffect((mtmp->mpeaceful ? se_snarl : se_roar), 80); pline_msg = mtmp->mpeaceful ? "snarls." : "roars!"; break; case MS_SQEEK: + Soundeffect(se_squeak, 80); pline_msg = "squeaks."; break; case MS_SQAWK: - if (ptr == &mons[PM_RAVEN] && !mtmp->mpeaceful) + if (ptr == &mons[PM_RAVEN] && !mtmp->mpeaceful) { verbl_msg = "Nevermore!"; - else + } else { + Soundeffect(se_squawk, 80); pline_msg = "squawks."; + } break; case MS_HISS: - if (!mtmp->mpeaceful) + if (!mtmp->mpeaceful) { + Soundeffect(se_hiss, 80); pline_msg = "hisses!"; - else - return 0; /* no sound */ + } else { + return ECMD_OK; /* no sound */ + } break; case MS_BUZZ: + Soundeffect((mtmp->mpeaceful ? se_buzz : se_angry_drone), 80); pline_msg = mtmp->mpeaceful ? "drones." : "buzzes angrily."; break; case MS_GRUNT: + Soundeffect(se_grunt, 60); pline_msg = "grunts."; break; case MS_NEIGH: - if (mtmp->mtame < 5) + if (mtmp->mtame < 5) { + Soundeffect(se_equine_neigh, 60); pline_msg = "neighs."; - else if (moves > EDOG(mtmp)->hungrytime) + } else if (svm.moves > EDOG(mtmp)->hungrytime) { + Soundeffect(se_equine_whinny, 60); pline_msg = "whinnies."; - else + } else { + Soundeffect(se_equine_whicker, 60); pline_msg = "whickers."; + } + break; + case MS_MOO: + Soundeffect(se_bovine_moo, 80); + pline_msg = "moos."; + break; + case MS_BELLOW: + Soundeffect((ptr->mlet == S_QUADRUPED) ? se_bovine_bellow + : se_croc_bellow, + 80); + pline_msg = "bellows!"; + break; + case MS_CHIRP: + Soundeffect(se_chirp, 60); + pline_msg = "chirps."; break; case MS_WAIL: + Soundeffect(se_sad_wailing, 60); pline_msg = "wails mournfully."; break; + case MS_GROAN: + if (!rn2(3)) { + Soundeffect(se_groan, 60); + pline_msg = "groans."; + } + break; case MS_GURGLE: + Soundeffect(se_gurgle, 60); pline_msg = "gurgles."; break; case MS_BURBLE: + Soundeffect(se_jabberwock_burble, 60); pline_msg = "burbles."; break; + case MS_TRUMPET: + Soundeffect(se_elephant_trumpet, 60); + pline_msg = "trumpets!"; + wake_nearto(mtmp->mx, mtmp->my, 11 * 11); + break; case MS_SHRIEK: + Soundeffect(se_shriek, 60); pline_msg = "shrieks."; aggravate(); break; @@ -718,21 +966,28 @@ register struct monst *mtmp; pline_msg = "imitates you."; break; case MS_BONES: + Soundeffect(se_bone_rattle, 60); pline("%s rattles noisily.", Monnam(mtmp)); You("freeze for a moment."); nomul(-2); - multi_reason = "scared by rattling"; - nomovemsg = 0; + gm.multi_reason = "scared by rattling"; + gn.nomovemsg = 0; break; case MS_LAUGH: { static const char *const laugh_msg[4] = { "giggles.", "chuckles.", "snickers.", "laughs.", }; + Soundeffect(se_laughter, 60); pline_msg = laugh_msg[rn2(4)]; - } break; + break; + } case MS_MUMBLE: pline_msg = "mumbles incomprehensibly."; break; + case MS_ORC: /* this used to be an alias for grunt, now it is distinct */ + Soundeffect(se_orc_grunt, 60); + pline_msg = "grunts."; + break; case MS_DJINNI: if (mtmp->mtame) { verbl_msg = "Sorry, I'm all out of wishes."; @@ -744,10 +999,8 @@ register struct monst *mtmp; } else { if (ptr != &mons[PM_PRISONER]) verbl_msg = "This will teach you not to disturb me!"; -#if 0 - else - verbl_msg = "??????????"; -#endif + else /* vague because prisoner might already be out of cell */ + verbl_msg = "Get me out of here."; } break; case MS_BOAST: /* giants */ @@ -767,6 +1020,7 @@ register struct monst *mtmp; } break; } + FALLTHROUGH; /*FALLTHRU*/ case MS_HUMANOID: if (!mtmp->mpeaceful) { @@ -776,7 +1030,7 @@ register struct monst *mtmp; pline_msg = "threatens you."; break; } - /* Generic peaceful humanoid behaviour. */ + /* Generic peaceful humanoid behavior. */ if (mtmp->mflee) pline_msg = "wants nothing to do with you."; else if (mtmp->mhp < mtmp->mhpmax / 4) @@ -794,7 +1048,7 @@ register struct monst *mtmp; } 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) + && svm.moves > EDOG(mtmp)->hungrytime) verbl_msg = "I'm hungry."; /* Specific monsters' interests */ else if (is_elf(ptr)) @@ -805,25 +1059,36 @@ register struct monst *mtmp; pline_msg = "talks about spellcraft."; else if (ptr->mlet == S_CENTAUR) pline_msg = "discusses hunting."; - else if (is_gnome(ptr) && Hallucination && (gnomeplan = rn2(4)) % 2) - /* skipped for rn2(4) result of 0 or 2; - gag from an early episode of South Park called "Gnomes"; - initially, Tweek (introduced in that episode) is the only - one aware of the tiny gnomes after spotting them sneaking - about; they are embarked upon a three-step business plan; - a diagram of the plan shows: - Phase 1 Phase 2 Phase 3 - Collect underpants ? Profit - and they never verbalize step 2 so we don't either */ - verbl_msg = (gnomeplan == 1) ? "Phase one, collect underpants." - : "Phase three, profit!"; - else + else if (is_gnome(ptr)) { + if (Hallucination && (gnomeplan = rn2(4)) % 2) { + /* skipped for rn2(4) result of 0 or 2; + gag from an early episode of South Park called "Gnomes"; + initially, Tweek (introduced in that episode) is the only + one aware of the tiny gnomes after spotting them sneaking + about; they are embarked upon a three-step business plan; + a diagram of the plan shows: + Phase 1 Phase 2 Phase 3 + Collect underpants ? Profit + and they never verbalize step 2 so we don't either */ + verbl_msg = (gnomeplan == 1) + ? "Phase one, collect underpants." + : "Phase three, profit!"; + } else { + verbl_msg = "Many enter the dungeon," + " and few return to the sunlit lands."; + } + } 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."; + /* 5.0: the 'complains' message used to be given if the + hobbit's current hit points were at 10 below max or + less, but their max is normally less than 10 so it + would almost never occur */ + pline_msg = (mtmp->mhp < mtmp->mhpmax + && (mtmp->mhpmax <= 10 + || mtmp->mhp <= mtmp->mhpmax - 10)) + ? "complains about unpleasant dungeon conditions." + : "asks you about the One Ring."; break; case PM_ARCHEOLOGIST: pline_msg = @@ -842,7 +1107,8 @@ register struct monst *mtmp; if (SYSOPT_SEDUCE) { if (ptr->mlet != S_NYMPH - && could_seduce(mtmp, &youmonst, (struct attack *) 0) == 1) { + && (could_seduce(mtmp, &gy.youmonst, (struct attack *) 0) + == 1)) { (void) doseduce(mtmp); break; } @@ -861,9 +1127,10 @@ register struct monst *mtmp; } } break; case MS_ARREST: - if (mtmp->mpeaceful) + if (mtmp->mpeaceful) { + SetVoice(mtmp, 0, 80, 0); verbalize("Just the facts, %s.", flags.female ? "Ma'am" : "Sir"); - else { + } 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!", @@ -876,7 +1143,8 @@ register struct monst *mtmp; (void) demon_talk(mtmp); break; } - /* fall through */ + FALLTHROUGH; + /* FALLTHRU */ case MS_CUSS: if (!mtmp->mpeaceful) cuss(mtmp); @@ -903,22 +1171,21 @@ register struct monst *mtmp; verbl_msg = "Relax, this won't hurt a bit."; break; case MS_GUARD: - if (money_cnt(invent)) + if (money_cnt(gi.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!", - }; + *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; @@ -929,7 +1196,7 @@ register struct monst *mtmp; boolean ms_Death = (ptr == &mons[PM_DEATH]); /* 3.6 tribute */ - if (ms_Death && !context.tribute.Deathnotice + if (ms_Death && !svc.context.tribute.Deathnotice && (book = u_have_novel()) != 0) { if ((tribtitle = noveltitle(&book->novelidx)) != 0) { Sprintf(verbuf, "Ah, so you have a copy of /%s/.", tribtitle); @@ -939,7 +1206,7 @@ register struct monst *mtmp; Strcat(verbuf, " I may have been misquoted there."); verbl_msg = verbuf; } - context.tribute.Deathnotice = 1; + svc.context.tribute.Deathnotice = 1; } else if (ms_Death && rn2(3) && Death_quote(verbuf, sizeof verbuf)) { verbl_msg = verbuf; /* end of tribute addition */ @@ -955,6 +1222,7 @@ register struct monst *mtmp; if (pline_msg) { pline("%s %s", Monnam(mtmp), pline_msg); } else if (mtmp->mcan && verbl_msg_mcan) { + SetVoice(mtmp, 0, 80, 0); verbalize1(verbl_msg_mcan); } else if (verbl_msg) { /* more 3.6 tribute */ @@ -962,18 +1230,22 @@ register struct monst *mtmp; /* Death talks in CAPITAL LETTERS and without quotation marks */ char tmpbuf[BUFSZ]; - pline1(ucase(strcpy(tmpbuf, verbl_msg))); + SetVoice((struct monst *) 0, 0, 80, voice_death); + sound_speak(tmpbuf); } else { + SetVoice(mtmp, 0, 80, 0); verbalize1(verbl_msg); } } - return 1; + return ECMD_TIME; } +RESTORE_WARNING_FORMAT_NONLITERAL + /* #chat command */ int -dotalk() +dotalk(void) { int result; @@ -981,35 +1253,31 @@ dotalk() return result; } -STATIC_OVL int -dochat() +staticfn int +dochat(void) { struct monst *mtmp; int tx, ty; struct obj *otmp; - if (is_silent(youmonst.data)) { - pline("As %s, you cannot speak.", an(youmonst.data->mname)); - return 0; + if (is_silent(gy.youmonst.data)) { + pline("As %s, you cannot speak.", + an(pmname(gy.youmonst.data, flags.female ? FEMALE : MALE))); + return ECMD_OK; } if (Strangled) { You_cant("speak. You're choking!"); - return 0; + return ECMD_OK; } if (u.uswallow) { pline("They won't hear you out there."); - return 0; + return ECMD_OK; } if (Underwater) { Your("speech is unintelligible underwater."); - return 0; + return ECMD_OK; } - if (Deaf) { - pline("How can you hold a conversation when you cannot hear?"); - return 0; - } - - if (!Blind && (otmp = shop_object(u.ux, u.uy)) != (struct obj *) 0) { + if (!Deaf && !Blind && (otmp = shop_object(u.ux, u.uy)) != 0) { /* standing on something in a shop and chatting causes the shopkeeper to describe the price(s). This can inhibit other chatting inside a shop, but that shouldn't matter much. shop_object() returns an @@ -1018,25 +1286,25 @@ dochat() contains any objects other than just gold. */ price_quote(otmp); - return 1; + return ECMD_TIME; } if (!getdir("Talk to whom? (in what direction)")) { /* decided not to chat */ - return 0; + return ECMD_CANCEL; } if (u.usteed && u.dz > 0) { - if (!u.usteed->mcanmove || u.usteed->msleeping) { + if (helpless(u.usteed)) { pline("%s seems not to notice you.", Monnam(u.usteed)); - return 1; + return ECMD_TIME; } else return domonnoise(u.usteed); } if (u.dz) { pline("They won't hear you %s there.", u.dz < 0 ? "up" : "down"); - return 0; + return ECMD_OK; } if (u.dx == 0 && u.dy == 0) { @@ -1051,103 +1319,297 @@ dochat() } */ pline("Talking to yourself is a bad habit for a dungeoneer."); - return 0; + return ECMD_OK; } tx = u.ux + u.dx; ty = u.uy + u.dy; if (!isok(tx, ty)) - return 0; + return ECMD_OK; mtmp = m_at(tx, ty); - if ((!mtmp || mtmp->mundetected) - && (otmp = vobj_at(tx, ty)) != 0 && otmp->otyp == STATUE) { - /* Talking to a statue */ - if (!Blind) { - pline_The("%s seems not to notice you.", - /* if hallucinating, you can't tell it's a statue */ - Hallucination ? rndmonnam((char *) 0) : "statue"); + if (!mtmp || mtmp->mundetected) { + if ((otmp = vobj_at(tx, ty)) != 0 && otmp->otyp == STATUE) { + /* Talking to a statue */ + if (!Blind) + pline_The("%s seems not to notice you.", + /* if hallucinating, you can't tell it's a statue */ + Hallucination ? rndmonnam((char *) 0) : "statue"); + return ECMD_OK; + } + if (!Deaf && (IS_WALL(levl[tx][ty].typ) + || levl[tx][ty].typ == SDOOR)) { + /* Talking to a wall; secret door remains hidden by behaving + like a wall; IS_WALL() test excludes solid rock even when + that serves as a wall bordering a corridor */ + if (Blind && !IS_WALL(svl.lastseentyp[tx][ty])) { + /* when blind, you can only talk to a wall if it has + already been mapped as a wall */ + ; + } else if (!Hallucination) { + pline("It's like talking to a wall."); + } else { + static const char *const walltalk[] = { + "gripes about its job.", + "tells you a funny joke!", + "insults your heritage!", + "chuckles.", + "guffaws merrily!", + "deprecates your exploration efforts.", + "suggests a stint of rehab...", + "doesn't seem to be interested.", + }; + int idx = rn2(10); + + if (idx >= SIZE(walltalk)) + idx = SIZE(walltalk) - 1; + pline_The("wall %s", walltalk[idx]); + } + return ECMD_OK; } - return 0; } - if (!mtmp || mtmp->mundetected || M_AP_TYPE(mtmp) == M_AP_FURNITURE + if (!mtmp || mtmp->mundetected + || M_AP_TYPE(mtmp) == M_AP_FURNITURE || M_AP_TYPE(mtmp) == M_AP_OBJECT) - return 0; + return ECMD_OK; /* sleeping monsters won't talk, except priests (who wake up) */ - if ((!mtmp->mcanmove || mtmp->msleeping) && !mtmp->ispriest) { + if (helpless(mtmp) && !mtmp->ispriest) { /* If it is unseen, the player can't tell the difference between not noticing him and just not existing, so skip the message. */ if (canspotmon(mtmp)) pline("%s seems not to notice you.", Monnam(mtmp)); - return 0; + return ECMD_OK; } /* if this monster is waiting for something, prod it into action */ mtmp->mstrategy &= ~STRAT_WAITMASK; - if (mtmp->mtame && mtmp->meating) { + if (!Deaf && mtmp->mtame && mtmp->meating) { if (!canspotmon(mtmp)) map_invisible(mtmp->mx, mtmp->my); pline("%s is eating noisily.", Monnam(mtmp)); + return ECMD_OK; + } + if (Deaf) { + const char *xresponse = humanoid(gy.youmonst.data) + ? "falls on deaf ears" + : "is inaudible"; + + pline("Any response%s%s %s.", + canspotmon(mtmp) ? " from " : "", + canspotmon(mtmp) ? mon_nam(mtmp) : "", + xresponse); + return ECMD_OK; + } + return domonnoise(mtmp); +} + +/* is there a monster at that can see the hero and react? */ +staticfn struct monst * +responsive_mon_at(int x, int y) +{ + struct monst *mtmp = isok(x, y) ? m_at(x, y) : 0; + + if (mtmp && (helpless(mtmp) /* immobilized monst */ + || !mtmp->mcansee || !haseyes(mtmp->data) /* blind monst */ + || (Invis && !perceives(mtmp->data)) /* unseen hero */ + || (x != mtmp->mx || y != mtmp->my))) /* worm tail */ + mtmp = (struct monst *) 0; + return mtmp; +} + +/* player chose 'uarmh' for #tip (pickup.c); visual #chat, sort of... */ +int +tiphat(void) +{ + struct monst *mtmp; + struct obj *otmp; + int x, y, range, glyph, vismon, unseen, statue, res; + + if (!uarmh) /* can't get here from there */ return 0; + + res = uarmh->bknown ? 0 : 1; + if (cursed(uarmh)) /* "You can't. It is cursed." */ + return res; /* if learned of curse, use a move */ + + /* might choose a position, but dealing with direct lines is simpler */ + if (!getdir("At whom? (in what direction)")) /* bail on ESC */ + return res; /* iffy; now know it's not cursed for sure (since we got + * past prior test) but might have already known that */ + res = 1; /* physical action is going to take place */ + + /* most helmets have a short wear/take-off delay and we could set + 'multi' to account for that, but we'll pretend that no extra time + beyond the current move is necessary */ + You("briefly doff your %s.", helm_simple_name(uarmh)); + + if (!u.dx && !u.dy) { + if (u.usteed && u.dz > 0) { + if (helpless(u.usteed)) + pline("%s doesn't notice.", Monnam(u.usteed)); + else + (void) domonnoise(u.usteed); + } else if (u.dz) { + pline("There's no one %s there.", (u.dz < 0) ? "up" : "down"); + } else { + pline_The("lout here doesn't acknowledge you..."); + } + return res; } - return domonnoise(mtmp); + mtmp = (struct monst *) 0; + vismon = unseen = statue = 0, glyph = GLYPH_MON_OFF; + x = u.ux, y = u.uy; + for (range = 1; range <= BOLT_LIM + 1; ++range) { + x += u.dx, y += u.dy; + if (!isok(x, y) || (range > 1 && !couldsee(x, y))) { + /* switch back to coordinates for previous iteration's 'mtmp' */ + x -= u.dx, y -= u.dy; + break; + } + mtmp = m_at(x, y); + vismon = (mtmp && canseemon(mtmp)); + glyph = glyph_at(x, y); + unseen = glyph_is_invisible(glyph); + statue = (glyph_is_statue(glyph) /* mimic or hallucinatory statue */ + || (!vismon && !unseen && (otmp = vobj_at(x, y)) != 0 + && otmp->otyp == STATUE)); /* or actual statue */ + if (vismon && (M_AP_TYPE(mtmp) == M_AP_FURNITURE + || M_AP_TYPE(mtmp) == M_AP_OBJECT)) + vismon = 0, mtmp = (struct monst *) 0; + if (vismon || unseen || (statue && Hallucination) + /* unseen adjacent monster will respond if able */ + || (range == 1 && mtmp && responsive_mon_at(x, y) + && !is_silent(mtmp->data)) + /* we check accessible() after m_at() in case there's a + visible monster phazing through a wall here */ + || !(accessible(x, y) || levl[x][y].typ == IRONBARS)) + break; + } + + if (unseen || (statue && Hallucination)) { + pline("That %screature is ignoring you!", unseen ? "unseen " : ""); + } else if (!mtmp || !responsive_mon_at(x, y)) { + if (vismon) /* 'vismon' is only True when 'mtmp' is non-Null */ + pline("%s seems not to notice you.", Monnam(mtmp)); + else + goto nada; + } else { /* 'mtmp' is guaranteed to be non-Null if we get here */ + /* if this monster is waiting for something, prod it into action */ + mtmp->mstrategy &= ~STRAT_WAITMASK; + + if (vismon && humanoid(mtmp->data) && mtmp->mpeaceful && !Conflict) { + if ((otmp = which_armor(mtmp, W_ARMH)) == 0) { + pline("%s waves.", Monnam(mtmp)); + } else if (otmp->cursed) { + pline("%s grasps %s %s but can't remove it.", Monnam(mtmp), + mhis(mtmp), helm_simple_name(otmp)); + otmp->bknown = 1; + } else { + pline("%s tips %s %s in response.", Monnam(mtmp), + mhis(mtmp), helm_simple_name(otmp)); + } + } else if (vismon && humanoid(mtmp->data)) { + static const char *const reaction[3] = { + "curses", "gestures rudely", "gestures offensively", + }; + int which = !Deaf ? rn2(3) : rn1(2, 1), + twice = (Deaf || which > 0 || rn2(3)) ? 0 : rn1(2, 1); + + pline("%s %s%s%s at you...", Monnam(mtmp), reaction[which], + twice ? " and " : "", twice ? reaction[twice] : ""); + } else if (next2u(x, y) && !Deaf && domonnoise(mtmp)) { + if (!vismon) + map_invisible(x, y); + } else if (vismon) { + pline("%s doesn't respond.", Monnam(mtmp)); + } else { + nada: + pline("%s", nothing_happens); + } + } + return res; } #ifdef USER_SOUNDS -extern void FDECL(play_usersound, (const char *, int)); - typedef struct audio_mapping_rec { struct nhregex *regex; char *filename; int volume; + int idx; struct audio_mapping_rec *next; } audio_mapping; static audio_mapping *soundmap = 0; +static audio_mapping *sound_matches_message(const char *); -char *sounddir = "."; +char *sounddir = 0; /* set in files.c */ /* adds a sound file mapping, returns 0 on failure, 1 on success */ int -add_sound_mapping(mapping) -const char *mapping; +add_sound_mapping(const char *mapping) { char text[256]; char filename[256]; char filespec[256]; - int volume; - - if (sscanf(mapping, "MESG \"%255[^\"]\"%*[\t ]\"%255[^\"]\" %d", text, - filename, &volume) == 3) { + char msgtyp[11]; + int volume, idx = -1; + + msgtyp[0] = '\0'; + filename[sizeof filename - 1] = '\0'; + filespec[sizeof filespec - 1] = '\0'; + text[sizeof text - 1] = '\0'; + if (sscanf(mapping, "MESG \"%255[^\"]\"%*[\t ]\"%255[^\"]\" %d %d", + text, filename, &volume, &idx) == 4 + || sscanf(mapping, + "MESG %10[^\"] \"%255[^\"]\"%*[\t ]\"%255[^\"]\" %d %d", + msgtyp, text, filename, &volume, &idx) == 5 + || sscanf(mapping, + "MESG %10[^\"] \"%255[^\"]\"%*[\t ]\"%255[^\"]\" %d", + msgtyp, text, filename, &volume) == 4 + || sscanf(mapping, "MESG \"%255[^\"]\"%*[\t ]\"%255[^\"]\" %d", + text, filename, &volume) == 3) { audio_mapping *new_map; - if (strlen(sounddir) + strlen(filename) > 254) { + if (!sounddir) + sounddir = dupstr("."); + if (strlen(sounddir) + 1 + strlen(filename) >= sizeof filespec) { raw_print("sound file name too long"); return 0; } - Sprintf(filespec, "%s/%s", sounddir, filename); + Snprintf(filespec, sizeof filespec, "%s/%s", sounddir, filename); - if (can_read_file(filespec)) { - new_map = (audio_mapping *) alloc(sizeof(audio_mapping)); + if (idx >= 0 || can_read_file(filespec)) { + new_map = (audio_mapping *) alloc(sizeof *new_map); new_map->regex = regex_init(); new_map->filename = dupstr(filespec); new_map->volume = volume; + new_map->idx = idx; new_map->next = soundmap; if (!regex_compile(text, new_map->regex)) { - raw_print(regex_error_desc(new_map->regex)); + char errbuf[BUFSZ]; + char *re_error_desc + = regex_error_desc(new_map->regex, errbuf); + regex_free(new_map->regex); - free(new_map->filename); - free(new_map); + free((genericptr_t) new_map->filename); + free((genericptr_t) new_map); + raw_print(re_error_desc); return 0; } else { + if (*msgtyp) { + char tmpbuf[BUFSZ]; + + Sprintf(tmpbuf, "%.10s \"%.230s\"", msgtyp, text); + (void) msgtype_parse_add(tmpbuf); + } soundmap = new_map; } } else { @@ -1163,20 +1625,598 @@ const char *mapping; return 1; } +staticfn audio_mapping * +sound_matches_message(const char *msg) +{ + audio_mapping *snd = soundmap; + + while (snd) { + if (regex_match(msg, snd->regex)) + return snd; + snd = snd->next; + } + return (audio_mapping *) 0; +} + void -play_sound_for_message(msg) -const char *msg; +play_sound_for_message(const char *msg) { - audio_mapping *cursor = soundmap; + audio_mapping *snd; - while (cursor) { - if (regex_match(msg, cursor->regex)) { - play_usersound(cursor->filename, cursor->volume); + /* we do this check here first, in order to completely + * avoid doing the regex search when there won't be a + * sound anyway, despite a match. + */ + if (soundprocs.sound_play_usersound) { + snd = sound_matches_message(msg); + if (snd) { + Play_usersound(snd->filename, snd->volume, snd->idx); } - cursor = cursor->next; } } +void +maybe_play_sound(const char *msg) +{ + audio_mapping *snd; + + /* we do this check here first, in order to completely + * avoid doing the regex search when there won't be a + * sound anyway, despite a match. + */ + if (soundprocs.sound_play_usersound) { + snd = sound_matches_message(msg); + if (snd) { + Play_usersound(snd->filename, snd->volume, snd->idx); + } + } +} + +void +release_sound_mappings(void) +{ + audio_mapping *nextsound = 0; + + while (soundmap) { + nextsound = soundmap->next; + regex_free(soundmap->regex); + free((genericptr_t) soundmap->filename); + free((genericptr_t) soundmap); + soundmap = nextsound; + } + + if (sounddir) + free((genericptr_t) sounddir), sounddir = 0; +} #endif /* USER_SOUNDS */ +struct sound_procs soundprocs; + +#ifdef SND_LIB_PORTAUDIO +extern struct sound_procs portaudio_procs; +#endif +#ifdef SND_LIB_OPENAL +extern struct sound_procs openal_procs; +#endif +#ifdef SND_LIB_SDL_MIXER +extern struct sound_procs sdl_mixer_procs; +#endif +#ifdef SND_LIB_MINIAUDIO +extern struct sound_procs miniaudio_procs; +#endif +#ifdef SND_LIB_FMOD +extern struct sound_procs fmod_procs; +#endif +#ifdef SND_LIB_SOUND_ESCCODES +extern struct sound_procs esccodes_procs; +#endif +#ifdef SND_LIB_VISSOUND +extern struct sound_procs vissound_procs; +#endif +#ifdef SND_LIB_WINDSOUND +extern struct sound_procs windsound_procs; +#endif +#ifdef SND_LIB_MACSOUND +extern struct sound_procs macsound_procs; +#endif +#ifdef SND_LIB_QTSOUND +extern struct sound_procs qtsound_procs; +#endif + +static struct sound_procs nosound_procs = { + SOUNDID(nosound), + 0L, + (void (*)(void)) 0, /* init_nhsound */ + (void (*)(const char *)) 0, /* exit_nhsound */ + (void (*)(schar, schar, int32_t)) 0, /* achievement */ + (void (*)(char *, int32_t, int32_t)) 0, /* sound effect */ + (void (*)(int32_t, const char *, int32_t)) 0, /* hero_playnotes */ + (void (*)(char *, int32_t, int32_t)) 0, /* play_usersound */ + (void (*)(int32_t, int32_t, int32_t)) 0, /* ambience */ + (void (*)(char *, int32_t, int32_t, int32_t, int32_t)) 0, /* verbal */ +}; + +/* The order of these array entries must match the + order of the enum soundlib_ids in sndprocs.h */ + +static struct sound_choices { + struct sound_procs *sndprocs; +} soundlib_choices[] = { + { &nosound_procs }, /* default, built-in */ +#ifdef SND_LIB_PORTAUDIO + { &portaudio_procs }, +#endif +#ifdef SND_LIB_OPENAL + { &openal_procs }, +#endif +#ifdef SND_LIB_SDL_MIXER + { &sdl_mixer_procs }, +#endif +#ifdef SND_LIB_MINIAUDIO + { &miniaudio_procs }, +#endif +#ifdef SND_LIB_FMOD + { &fmod_procs }, +#endif +#ifdef SND_LIB_SOUND_ESCCODES + { &esccodes_procs }, +#endif +#ifdef SND_LIB_VISSOUND + { &vissound_procs }, +#endif +#ifdef SND_LIB_WINDSOUND + { &windsound_procs }, +#endif +#ifdef SND_LIB_MACSOUND + { &macsound_procs }, +#endif +#ifdef SND_LIB_QTSOUND + { &qtsound_procs }, +#endif +}; + +void +activate_chosen_soundlib(void) +{ + int idx = gc.chosen_soundlib; + + if (!IndexOk(idx, soundlib_choices)) + panic("activate_chosen_soundlib: invalid soundlib (%d)", idx); + + if (ga.active_soundlib != soundlib_nosound || idx != soundlib_nosound) { + if (soundprocs.sound_exit_nhsound) + (*soundprocs.sound_exit_nhsound)("assigning a new sound library"); + } + soundprocs = *soundlib_choices[idx].sndprocs; + if (soundprocs.sound_init_nhsound) + (*soundprocs.sound_init_nhsound)(); + ga.active_soundlib = soundprocs.soundlib_id; + gc.chosen_soundlib = ga.active_soundlib; +} + +void +assign_soundlib(int idx) +{ + if (!IndexOk(idx, soundlib_choices)) + panic("assign_soundlib: invalid soundlib (%d)", idx); + + gc.chosen_soundlib + = (uint32_t) soundlib_choices[idx].sndprocs->soundlib_id; +} + +#if 0 +staticfn void +choose_soundlib(const char *s) +{ + int i; + char *tmps = 0; + + for (i = 1; soundlib_choices[i].sndprocs; i++) { + if (!strcmpi(s, soundlib_choices[i].sndprocs->soundname)) { + assign_soundlib(i); + return; + } + } + assign_soundlib((int) soundlib_nosound); + + /* The code below here mimics that in windows.c error handling + for choosing Window type */ + + /* 50: arbitrary, no real soundlib names are anywhere near that long; + used to prevent potential raw_printf() overflow if user supplies a + very long string (on the order of 1200 chars) on the command line + (config file options can't get that big; they're truncated at 1023) */ +#define SOUNDLIB_NAME_MAXLEN 50 + if (strlen(s) >= SOUNDLIB_NAME_MAXLEN) { + tmps = (char *) alloc(SOUNDLIB_NAME_MAXLEN); + (void) strncpy(tmps, s, SOUNDLIB_NAME_MAXLEN - 1); + tmps[SOUNDLIB_NAME_MAXLEN - 1] = '\0'; + s = tmps; + } +#undef SOUNDLIB_NAME_MAXLEN + + if (!soundlib_choices[1].sndprocs) { + config_error_add( + "Soundlib type %s not recognized. The only choice is: %s", + s, soundlib_choices[0].sndprocs->soundname); + } else { + char buf[BUFSZ]; + boolean first = TRUE; + + buf[0] = '\0'; + for (i = 0; soundlib_choices[i].sndprocs; i++) { + Sprintf(eos(buf), "%s%s", + first ? "" : ", ", + soundlib_choices[i].sndprocs->soundname); + first = FALSE; + } + config_error_add("Soundlib type %s not recognized. Choices are: %s", + s, buf); + } + if (tmps) + free((genericptr_t) tmps) /*, tmps = 0*/ ; +} +#endif + +/* copy up to maxlen-1 characters; 'dest' must be able to hold maxlen; + treat comma as alternate end of 'src' */ +void +get_soundlib_name(char *dest, int maxlen) +{ + int count, idx; + const char *src; + + idx = ga.active_soundlib; + if (!IndexOk(idx, soundlib_choices)) + panic("get_soundlib_name: invalid active_soundlib (%d)", idx); + + src = soundlib_choices[idx].sndprocs->soundname; + for (count = 1; count < maxlen; count++) { + if (*src == ',' || *src == '\0') + break; /*exit on \0 terminator*/ + *dest++ = *src++; + } + *dest = '\0'; +} + +enum soundlib_ids +soundlib_id_from_opt(char *op) +{ + int idx; + struct sound_procs *defproc = &nosound_procs, + *sp = 0; + + for (idx = 0; idx < SIZE(soundlib_choices); ++idx) { + sp = soundlib_choices[idx].sndprocs; + if (!strcmp(sp->soundname, op)) + return sp->soundlib_id; + } + return defproc->soundlib_id; +} + +/* + * The default sound interface + * + * 3rd party sound_procs should be placed in ../sound/x + * and build procedures should reference them there. + */ + +#if 0 +staticfn void nosound_init_nhsound(void); +staticfn void nosound_exit_nhsound(const char *); +staticfn void nosound_suspend_nhsound(const char *); +staticfn void nosound_resume_nhsound(void); +staticfn void nosound_achievement(schar, schar, int32_t); +staticfn void nosound_soundeffect(int32_t, int32_t); +staticfn void nosound_play_usersound(char *, int32_t, int32_t); +staticfn void nosound_ambience(int32_t, int32_t, int32_t); +staticfn void nosound_verbal(char *text, int32_t gender, int32_t tone, + int32_t vol, int32_t moreinfo); + +staticfn void +nosound_init_nhsound(void) +{ +} + +staticfn void +nosound_exit_nhsound(const char *reason) +{ +} + +staticfn void +nosound_achievement(schar ach1, schar ach2, int32_t repeat) +{ +} + +staticfn void +nosound_soundeffect(int32_t seid, int volume) +{ +} + +staticfn void +nosound_hero_playnotes(int32_t instr, const char *notes, int32_t vol) +{ +} + +staticfn void +nosound_play_usersound(char *filename, int volume, int idx) +{ +} + +staticfn void +nosound_ambience(int32_t ambienceid, int32_t ambience_action, + int32_t hero_proximity) +{ +} + +staticfn void +nosound_verbal(char *text, int32_t gender, int32_t tone, + int32_t vol, int32_t moreinfo) +{ +} +#endif + +#ifdef SND_SOUNDEFFECTS_AUTOMAP + +/* prototype in case a build defines staticfn to nothing */ +staticfn void initialize_semap_basenames(void); + +struct soundeffect_automapping { + enum sound_effect_entries seid; + const char *base_filename; +}; + +#define SEFFECTS_AUTOMAP +static const struct soundeffect_automapping + se_mappings_init[number_of_se_entries] = { + { se_zero_invalid, "" }, +#include "seffects.h" +}; +#undef SEFFECTS_AUTOMAP + +static const char *semap_basenames[SIZE(se_mappings_init)]; +static boolean basenames_initialized = FALSE; + +staticfn void +initialize_semap_basenames(void) +{ + int i; + + /* to avoid things getting out of sequence; seid an index to the name */ + for (i = 1; i < SIZE(se_mappings_init); ++i) { + if (se_mappings_init[i].seid > 0 + && se_mappings_init[i].seid < SIZE(semap_basenames)) + semap_basenames[se_mappings_init[i].seid] + = se_mappings_init[i].base_filename; + } +} + +char * +get_sound_effect_filename( + int32_t seidint, + char *buf, + size_t bufsz, + int32_t approach) +{ + static const char prefix[] = "se_", suffix[] = ".wav"; + size_t consumes = 0, baselen = 0, existinglen = 0; +/* enum sound_effect_entries seid = (enum sound_effect_entries) seidint; */ + char *ourdir = sounddir; /* sounddir would get set in files.c */ + char *cp = buf; + boolean needslash = TRUE; + + if (!buf || (!ourdir && approach == sff_default)) + return (char *) 0; + + if (!basenames_initialized) { + initialize_semap_basenames(); + basenames_initialized = TRUE; + } + + if (semap_basenames[seidint]) + baselen = strlen(semap_basenames[seidint]); + + consumes = (sizeof prefix - 1) + baselen; + if (approach == sff_default) { + consumes += (sizeof suffix - 1) + strlen(ourdir) + 1; /* 1 for '/' */ + } else if (approach == sff_havedir_append_rest) { + /* consumes += (sizeof suffix - 1); */ + existinglen = strlen(buf); + if (existinglen > 0) { + cp = buf + existinglen; /* points at trailing NUL */ + cp--; /* points at last character */ + if (*cp == '/' || *cp == '\\') + needslash = FALSE; + cp++; /* points back at trailing NUL */ + } + if (needslash) + consumes++; /* for '/' */ + consumes += existinglen; + consumes += (sizeof suffix - 1); + } + consumes += 1; /* for trailing NUL */ + /* existinglen could be >= bufsz if caller didn't initialize buf + * to properly include a trailing NUL */ + if (baselen <= 0 || consumes > bufsz || existinglen >= bufsz) + return (char *) 0; + +#if 0 + if (approach == sff_default) { + Strcpy(buf, ourdir); + Strcat(buf, "/"); + } else if (approach == sff_havdir_append_rest) { + if (needslash) + Strcat(buf, "/"); + } else if (approach == sff_base_only) { + buf[0] = '\0'; + } else { + return (char *) 0; + } + Strcat(buf, prefix); + Strcat(buf, semap_basenames[seidint]); + if (approach != sff_base_only) { + Strcat(buf, suffix); + } +#else + if (approach == sff_default) { + Snprintf(buf, bufsz , "%s/%s%s%s", ourdir, prefix, + semap_basenames[seidint], suffix); + } else if (approach == sff_havedir_append_rest) { + if (needslash) { + *cp = '/'; + cp++; + *cp = '\0'; + existinglen++; + } + Snprintf(cp, bufsz - (existinglen + 1) , "%s%s%s", + prefix, semap_basenames[seidint], suffix); + } else if (approach == sff_base_only) { + Snprintf(buf, bufsz, "%s%s", prefix, semap_basenames[seidint]); + } else { + return (char *) 0; + } +#endif + return buf; +} +#endif /* SND_SOUNDEFFECTS_AUTOMAP */ + +char * +base_soundname_to_filename( + char *basename, + char *buf, + size_t bufsz, + int32_t approach) +{ + static const char suffix[] = ".wav"; + size_t consumes = 0, baselen = 0, existinglen = 0; + char *cp = buf; + boolean needslash = TRUE; + + if (!buf) + return (char *) 0; + + baselen = strlen(basename); + consumes = baselen; + + if (approach == sff_havedir_append_rest) { + /* consumes += (sizeof suffix - 1); */ + existinglen = strlen(buf); + if (existinglen > 0) { + cp = buf + existinglen; /* points at trailing NUL */ + cp--; /* points at last character */ + if (*cp == '/' || *cp == '\\') + needslash = FALSE; + cp++; /* points back at trailing NUL */ + } + if (needslash) + consumes++; /* for '/' */ + consumes += existinglen; + consumes += (sizeof suffix - 1); + } + consumes += 1; /* for trailing NUL */ + /* existinglen could be >= bufsz if caller didn't initialize buf + * to properly include a trailing NUL */ + if (!baselen || consumes > bufsz || existinglen >= bufsz) + return (char *) 0; + +#if 0 + if (approach == sff_havedir_append_rest) { + if (needslash) + Strcat(buf, "/"); + } else if (approach == sff_base_only) { + buf[0] = '\0'; + } else { + return (char *) 0; + } + Strcat(buf, basename); + if (approach != sff_base_only) { + Strcat(buf, suffix); + } +#else + if (approach == sff_havedir_append_rest) { + if (needslash) { + *cp = '/'; + cp++; + *cp = '\0'; + existinglen++; + } + Snprintf(cp, bufsz - (existinglen + 1) , "%s%s", + basename, suffix); + } else if (approach == sff_base_only) { + Snprintf(buf, bufsz, "%s", basename); + } else { + return (char *) 0; + } +#endif + return buf; +} + +#ifdef SND_SPEECH +#define SPEECHONLY +#else +#define SPEECHONLY UNUSED +#endif + +void +set_voice( + struct monst *mtmp SPEECHONLY, + int32_t tone SPEECHONLY, + int32_t volume SPEECHONLY, + int32_t moreinfo SPEECHONLY) +{ +#ifdef SND_SPEECH + int32_t gender = (mtmp && mtmp->female) ? FEMALE : MALE; + + if (gv.voice.nameid) + free((genericptr_t) gv.voice.nameid); + gv.voice.gender = gender; + gv.voice.serialno = mtmp ? mtmp->m_id + : ((moreinfo & voice_talking_artifact) != 0) ? 3 + : ((moreinfo & voice_deity) != 0) ? 4 : 2; + gv.voice.tone = tone; + gv.voice.volume = volume; + gv.voice.moreinfo = moreinfo; + gv.voice.nameid = (const char *) 0; + gp.pline_flags |= PLINE_SPEECH; +#endif +} + +void +sound_speak(const char *text SPEECHONLY) +{ +#ifdef SND_SPEECH + const char *cp1, *cp2; + char *cpdst; + char buf[BUFSZ + BUFSZ]; + + if (!text || (text && *text == '\0')) + return; + if (iflags.voices && soundprocs.sound_verbal + && (soundprocs.sound_triggers & SOUND_TRIGGER_VERBAL)) { + cp1 = text; + cpdst = buf; + cp2 = c_eos(cp1); + cp2--; /* last non-NUL */ + *cpdst = '\0'; + if ((gp.pline_flags & PLINE_VERBALIZE) != 0) { + if (*cp1 == '"') + cp1++; + if (*cp2 == '"') + cp2--; + } + /* cp1 -> 1st, cp2 -> last non-nul) */ + if ((unsigned)(cp2 - cp1) < (sizeof buf - 1U)) { + while (cp1 <= cp2) { + *cpdst = *cp1; + cp1++; + cpdst++; + } + *cpdst = '\0'; + } + (*soundprocs.sound_verbal)(buf, gv.voice.gender, gv.voice.tone, + gv.voice.volume, gv.voice.moreinfo); + } +#endif +} + /*sounds.c*/ diff --git a/src/sp_lev.c b/src/sp_lev.c index 75208bbde..fc9acb258 100644 --- a/src/sp_lev.c +++ b/src/sp_lev.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 sp_lev.c $NHDT-Date: 1567805254 2019/09/06 21:27:34 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.117 $ */ +/* NetHack 5.0 sp_lev.c $NHDT-Date: 1737610109 2025/01/22 21:28:29 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.373 $ */ /* Copyright (c) 1989 by Jean-Christophe Collet */ /* NetHack may be freely redistributed. See license for details. */ @@ -9,169 +9,165 @@ * It contains also the special level loader. */ +#define IN_SP_LEV_C + #include "hack.h" -#include "dlb.h" #include "sp_lev.h" -#ifdef _MSC_VER - #pragma warning(push) - #pragma warning(disable : 4244) -#endif +extern void mkmap(lev_init *); + +staticfn void solidify_map(void); +staticfn void map_cleanup(void); +staticfn void lvlfill_maze_grid(int, int, int, int, schar); +staticfn void lvlfill_solid(schar, schar); +staticfn void lvlfill_swamp(schar, schar, schar); +staticfn void flip_dbridge_horizontal(struct rm *); +staticfn void flip_dbridge_vertical(struct rm *); +staticfn void flip_visuals(int, int, int, int, int); +staticfn int flip_encoded_dir_bits(int, int); +staticfn void flip_vault_guard(int, struct monst *, + coordxy, coordxy, coordxy, coordxy); +staticfn void sel_set_wall_property(coordxy, coordxy, genericptr_t); +staticfn void set_wall_property(coordxy, coordxy, coordxy, coordxy, int); +staticfn void remove_boundary_syms(void); +staticfn void set_door_orientation(int, int); +staticfn boolean shared_with_room(int, int, struct mkroom *); +staticfn void maybe_add_door(int, int, struct mkroom *); +staticfn void link_doors_rooms(void); +staticfn int rnddoor(void); +staticfn int rndtrap(void); +staticfn void get_location(coordxy *, coordxy *, getloc_flags_t, + struct mkroom *); +staticfn void set_ok_location_func(boolean (*)(coordxy, coordxy)); +staticfn boolean is_ok_location(coordxy, coordxy, getloc_flags_t); +staticfn unpacked_coord get_unpacked_coord(long, int); +staticfn void get_room_loc(coordxy *, coordxy *, struct mkroom *); +staticfn void get_free_room_loc(coordxy *, coordxy *, struct mkroom *, + packed_coord); +staticfn boolean create_subroom(struct mkroom *, coordxy, coordxy, coordxy, + coordxy, xint16, xint16); +staticfn void create_door(room_door *, struct mkroom *); +staticfn void create_trap(spltrap *, struct mkroom *); +staticfn int noncoalignment(aligntyp); +staticfn boolean m_bad_boulder_spot(coordxy, coordxy); +staticfn int pm_to_humidity(struct permonst *); +staticfn unsigned int sp_amask_to_amask(unsigned int sp_amask); +staticfn void create_monster(monster *, struct mkroom *); +staticfn struct obj *create_object(object *, struct mkroom *); +staticfn void create_altar(altar *, struct mkroom *); +staticfn boolean search_door(struct mkroom *, coordxy *, coordxy *, xint16, + int) NONNULLPTRS; +staticfn void create_corridor(corridor *); +staticfn struct mkroom *build_room(room *, struct mkroom *); +staticfn void light_region(region *); +staticfn void maze1xy(coord *, int); +staticfn void fill_empty_maze(void); +staticfn void splev_initlev(lev_init *); +staticfn boolean generate_way_out_method(coordxy nx, coordxy ny, + struct selectionvar *ov); +staticfn void l_push_wid_hei_table(lua_State *, int, int); +staticfn boolean good_stair_loc(coordxy, coordxy); +staticfn void ensure_way_out(void); -typedef void FDECL((*select_iter_func), (int, int, genericptr)); - -extern void FDECL(mkmap, (lev_init *)); - -STATIC_DCL void NDECL(solidify_map); -STATIC_DCL void FDECL(splev_stack_init, (struct splevstack *)); -STATIC_DCL void FDECL(splev_stack_done, (struct splevstack *)); -STATIC_DCL void FDECL(splev_stack_push, (struct splevstack *, - struct opvar *)); -STATIC_DCL struct opvar *FDECL(splev_stack_pop, (struct splevstack *)); -STATIC_DCL struct splevstack *FDECL(splev_stack_reverse, - (struct splevstack *)); -STATIC_DCL struct opvar *FDECL(opvar_new_str, (char *)); -STATIC_DCL struct opvar *FDECL(opvar_new_int, (long)); -STATIC_DCL struct opvar *FDECL(opvar_new_coord, (int, int)); #if 0 -STATIC_DCL struct opvar * FDECL(opvar_new_region, (int,int, int,int)); -#endif /*0*/ -STATIC_DCL struct opvar *FDECL(opvar_clone, (struct opvar *)); -STATIC_DCL struct opvar *FDECL(opvar_var_conversion, (struct sp_coder *, - struct opvar *)); -STATIC_DCL struct splev_var *FDECL(opvar_var_defined, (struct sp_coder *, - char *)); -STATIC_DCL struct opvar *FDECL(splev_stack_getdat, (struct sp_coder *, - XCHAR_P)); -STATIC_DCL struct opvar *FDECL(splev_stack_getdat_any, (struct sp_coder *)); -STATIC_DCL void FDECL(variable_list_del, (struct splev_var *)); -STATIC_DCL void FDECL(lvlfill_maze_grid, (int, int, int, int, SCHAR_P)); -STATIC_DCL void FDECL(lvlfill_solid, (SCHAR_P, SCHAR_P)); -STATIC_DCL void FDECL(set_wall_property, (XCHAR_P, XCHAR_P, XCHAR_P, XCHAR_P, - int)); -STATIC_DCL void NDECL(shuffle_alignments); -STATIC_DCL void NDECL(count_features); -STATIC_DCL void NDECL(remove_boundary_syms); -STATIC_DCL void FDECL(set_door_orientation, (int, int)); -STATIC_DCL void FDECL(maybe_add_door, (int, int, struct mkroom *)); -STATIC_DCL void NDECL(link_doors_rooms); -STATIC_DCL void NDECL(fill_rooms); -STATIC_DCL int NDECL(rnddoor); -STATIC_DCL int NDECL(rndtrap); -STATIC_DCL void FDECL(get_location, (schar *, schar *, int, struct mkroom *)); -STATIC_DCL boolean FDECL(is_ok_location, (SCHAR_P, SCHAR_P, int)); -STATIC_DCL unpacked_coord FDECL(get_unpacked_coord, (long, int)); -STATIC_DCL void FDECL(get_location_coord, (schar *, schar *, int, - struct mkroom *, long)); -STATIC_DCL void FDECL(get_room_loc, (schar *, schar *, struct mkroom *)); -STATIC_DCL void FDECL(get_free_room_loc, (schar *, schar *, - struct mkroom *, packed_coord)); -STATIC_DCL boolean FDECL(create_subroom, (struct mkroom *, XCHAR_P, XCHAR_P, - XCHAR_P, XCHAR_P, XCHAR_P, XCHAR_P)); -STATIC_DCL void FDECL(create_door, (room_door *, struct mkroom *)); -STATIC_DCL void FDECL(create_trap, (spltrap *, struct mkroom *)); -STATIC_DCL int FDECL(noncoalignment, (ALIGNTYP_P)); -STATIC_DCL boolean FDECL(m_bad_boulder_spot, (int, int)); -STATIC_DCL int FDECL(pm_to_humidity, (struct permonst *)); -STATIC_DCL void FDECL(create_monster, (monster *, struct mkroom *)); -STATIC_DCL void FDECL(create_object, (object *, struct mkroom *)); -STATIC_DCL void FDECL(create_altar, (altar *, struct mkroom *)); -STATIC_DCL void FDECL(replace_terrain, (replaceterrain *, struct mkroom *)); -STATIC_DCL boolean FDECL(search_door, (struct mkroom *, - xchar *, xchar *, XCHAR_P, int)); -STATIC_DCL void NDECL(fix_stair_rooms); -STATIC_DCL void FDECL(create_corridor, (corridor *)); -STATIC_DCL struct mkroom *FDECL(build_room, (room *, struct mkroom *)); -STATIC_DCL void FDECL(light_region, (region *)); -STATIC_DCL void FDECL(wallify_map, (int, int, int, int)); -STATIC_DCL void FDECL(maze1xy, (coord *, int)); -STATIC_DCL void NDECL(fill_empty_maze); -STATIC_DCL boolean FDECL(sp_level_loader, (dlb *, sp_lev *)); -STATIC_DCL boolean FDECL(sp_level_free, (sp_lev *)); -STATIC_DCL void FDECL(splev_initlev, (lev_init *)); -STATIC_DCL struct sp_frame *FDECL(frame_new, (long)); -STATIC_DCL void FDECL(frame_del, (struct sp_frame *)); -STATIC_DCL void FDECL(spo_frame_push, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_frame_pop, (struct sp_coder *)); -STATIC_DCL long FDECL(sp_code_jmpaddr, (long, long)); -STATIC_DCL void FDECL(spo_call, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_return, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_end_moninvent, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_pop_container, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_message, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_monster, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_object, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_level_flags, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_initlevel, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_engraving, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_mineralize, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_room, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_endroom, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_stair, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_ladder, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_grave, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_altar, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_trap, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_gold, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_corridor, (struct sp_coder *)); -STATIC_DCL void FDECL(selection_setpoint, (int, int, struct opvar *, XCHAR_P)); -STATIC_DCL struct opvar *FDECL(selection_not, (struct opvar *)); -STATIC_DCL struct opvar *FDECL(selection_logical_oper, (struct opvar *, - struct opvar *, CHAR_P)); -STATIC_DCL struct opvar *FDECL(selection_filter_mapchar, (struct opvar *, - struct opvar *)); -STATIC_DCL void FDECL(selection_filter_percent, (struct opvar *, int)); -STATIC_DCL int FDECL(selection_rndcoord, (struct opvar *, schar *, schar *, - BOOLEAN_P)); -STATIC_DCL void FDECL(selection_do_grow, (struct opvar *, int)); -STATIC_DCL int FDECL(floodfillchk_match_under, (int, int)); -STATIC_DCL int FDECL(floodfillchk_match_accessible, (int, int)); -STATIC_DCL boolean FDECL(sel_flood_havepoint, (int, int, - xchar *, xchar *, int)); -STATIC_DCL void FDECL(selection_do_ellipse, (struct opvar *, int, int, - int, int, int)); -STATIC_DCL long FDECL(line_dist_coord, (long, long, long, long, long, long)); -STATIC_DCL void FDECL(selection_do_gradient, (struct opvar *, long, long, long, - long, long, long, long, long)); -STATIC_DCL void FDECL(selection_do_line, (SCHAR_P, SCHAR_P, SCHAR_P, SCHAR_P, - struct opvar *)); -STATIC_DCL void FDECL(selection_do_randline, (SCHAR_P, SCHAR_P, SCHAR_P, - SCHAR_P, SCHAR_P, SCHAR_P, - struct opvar *)); -STATIC_DCL void FDECL(selection_iterate, (struct opvar *, select_iter_func, - genericptr_t)); -STATIC_DCL void FDECL(sel_set_ter, (int, int, genericptr_t)); -STATIC_DCL void FDECL(sel_set_feature, (int, int, genericptr_t)); -STATIC_DCL void FDECL(sel_set_door, (int, int, genericptr_t)); -STATIC_DCL void FDECL(spo_door, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_feature, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_terrain, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_replace_terrain, (struct sp_coder *)); -STATIC_DCL boolean FDECL(generate_way_out_method, (int, int, struct opvar *)); -STATIC_DCL void NDECL(ensure_way_out); -STATIC_DCL void FDECL(spo_levregion, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_region, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_drawbridge, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_mazewalk, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_wall_property, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_room_door, (struct sp_coder *)); -STATIC_DCL void FDECL(sel_set_wallify, (int, int, genericptr_t)); -STATIC_DCL void FDECL(spo_wallify, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_map, (struct sp_coder *)); -STATIC_DCL void FDECL(spo_jmp, (struct sp_coder *, sp_lev *)); -STATIC_DCL void FDECL(spo_conditional_jump, (struct sp_coder *, sp_lev *)); -STATIC_DCL void FDECL(spo_var_init, (struct sp_coder *)); -#if 0 -STATIC_DCL long FDECL(opvar_array_length, (struct sp_coder *)); -#endif /*0*/ -STATIC_DCL void FDECL(spo_shuffle_array, (struct sp_coder *)); -STATIC_DCL boolean FDECL(sp_level_coder, (sp_lev *)); - -#define LEFT 1 -#define H_LEFT 2 -#define CENTER 3 -#define H_RIGHT 4 -#define RIGHT 5 +/* macosx complains that these are unused */ +staticfn long sp_code_jmpaddr(long, long); +staticfn void spo_room(struct sp_coder *); +staticfn void spo_trap(struct sp_coder *); +staticfn void spo_gold(struct sp_coder *); +staticfn void spo_corridor(struct sp_coder *); +staticfn void spo_feature(struct sp_coder *); +staticfn void spo_terrain(struct sp_coder *); +staticfn void spo_replace_terrain(struct sp_coder *); +staticfn void spo_levregion(struct sp_coder *); +staticfn void spo_region(struct sp_coder *); +staticfn void spo_drawbridge(struct sp_coder *); +staticfn void spo_mazewalk(struct sp_coder *); +staticfn void spo_wall_property(struct sp_coder *); +staticfn void spo_room_door(struct sp_coder *); +staticfn void spo_wallify(struct sp_coder *); +staticfn void sel_set_wallify(coordxy, coordxy, genericptr_t); +#endif +staticfn void spo_end_moninvent(void); +staticfn void spo_pop_container(void); +staticfn int l_create_stairway(lua_State *, boolean); +staticfn void spo_endroom(struct sp_coder *); +staticfn void l_table_getset_feature_flag(lua_State *, int, int, const char *, + int); +staticfn void l_get_lregion(lua_State *, lev_region *); +staticfn void sel_set_lit(coordxy, coordxy, genericptr_t); +staticfn void add_doors_to_room(struct mkroom *); +staticfn void get_table_coords_or_region(lua_State *, + coordxy *, coordxy *, coordxy *, coordxy *); +staticfn void sel_set_ter(coordxy, coordxy, genericptr_t); +staticfn void sel_set_door(coordxy, coordxy, genericptr_t); +staticfn void sel_set_feature(coordxy, coordxy, genericptr_t); +staticfn void levregion_add(lev_region *); +staticfn void get_table_xy_or_coord(lua_State *, lua_Integer *, + lua_Integer *) NONNULLPTRS; +staticfn int get_table_region(lua_State *, const char *, lua_Integer *, + lua_Integer *, lua_Integer *, lua_Integer *, boolean); +staticfn void set_wallprop_in_selection(lua_State *, int); +staticfn int floodfillchk_match_under(coordxy, coordxy); +staticfn int floodfillchk_match_accessible(coordxy, coordxy); +staticfn void l_push_mkroom_table(lua_State *, struct mkroom *); +staticfn int get_table_align(lua_State *); +staticfn int get_table_monclass(lua_State *); +staticfn int find_montype(lua_State *, const char *, int *); +staticfn int get_table_montype(lua_State *, int *); +staticfn lua_Integer get_table_int_or_random(lua_State *, const char *, int); +staticfn int get_table_buc(lua_State *); +staticfn int find_objtype(lua_State *, const char *, char); +staticfn const char *get_mkroom_name(int) NONNULL; +staticfn int get_table_roomtype_opt(lua_State *, const char *, int); +staticfn int get_table_traptype_opt(lua_State *, const char *, int); +staticfn int get_traptype_byname(const char *); +staticfn lua_Integer get_table_intarray_entry(lua_State *, int, int); +staticfn struct sp_coder *sp_level_coder_init(void); + +/* lua_CFunction prototypes */ +int lspo_altar(lua_State *); +int lspo_branch(lua_State *); +int lspo_corridor(lua_State *); +int lspo_door(lua_State *); +int lspo_drawbridge(lua_State *); +int lspo_engraving(lua_State *); +int lspo_feature(lua_State *); +int lspo_gold(lua_State *); +int lspo_grave(lua_State *); +int lspo_ladder(lua_State *); +int lspo_level_flags(lua_State *); +int lspo_level_init(lua_State *); +int lspo_levregion(lua_State *); +int lspo_exclusion(lua_State *); +int lspo_map(lua_State *); +int lspo_mazewalk(lua_State *); +int lspo_message(lua_State *); +int lspo_mineralize(lua_State *); +int lspo_monster(lua_State *); +int lspo_non_diggable(lua_State *); +int lspo_non_passwall(lua_State *); +int lspo_object(lua_State *); +int lspo_portal(lua_State *); +int lspo_random_corridors(lua_State *); +int lspo_region(lua_State *); +int lspo_replace_terrain(lua_State *); +int lspo_reset_level(lua_State *); +int lspo_finalize_level(lua_State *); +int lspo_room(lua_State *); +int lspo_stair(lua_State *); +int lspo_teleport_region(lua_State *); +int lspo_gas_cloud(lua_State *); +int lspo_terrain(lua_State *); +int lspo_trap(lua_State *); +int lspo_wall_property(lua_State *); +int lspo_wallify(lua_State *); + +#define SPLEV_LEFT 1 +#define SPLEV_H_LEFT 2 +#define SPLEV_CENTER 3 +#define SPLEV_H_RIGHT 4 +#define SPLEV_RIGHT 5 #define TOP 1 #define BOTTOM 5 @@ -181,463 +177,830 @@ STATIC_DCL boolean FDECL(sp_level_coder, (sp_lev *)); #define XLIM 4 #define YLIM 3 -#define Fread (void) dlb_fread -#define Fgetc (schar) dlb_fgetc -#define New(type) (type *) alloc(sizeof(type)) -#define NewTab(type, size) (type **) alloc(sizeof(type *) * (unsigned) size) -#define Free(ptr) if (ptr) free((genericptr_t) (ptr)) +#define New(type) (type *) alloc(sizeof (type)) +#define NewTab(type, size) (type **) alloc(sizeof (type *) * (unsigned) size) +#define Free(ptr) \ + do { \ + if (ptr) \ + free((genericptr_t) (ptr)); \ + } while (0) -extern struct engr *head_engr; + /* + * No need for 'struct instance_globals g' to contain these. + * sp_level_coder_init() always re-initializes them prior to use. + */ +static boolean splev_init_present, icedpools; +/* positions touched by level elements explicitly defined in the level */ +static char SpLev_Map[COLNO][ROWNO]; +#define MAX_CONTAINMENT 10 +static int container_idx = 0; /* next slot in container_obj[] to use */ +static struct obj *container_obj[MAX_CONTAINMENT]; +static struct monst *invent_carrying_monster = (struct monst *) 0; + /* + * end of no 'g.' + */ -extern int min_rx, max_rx, min_ry, max_ry; /* from mkmap.c */ +#define TYP_CANNOT_MATCH(typ) ((typ) == MAX_TYPE || (typ) == INVALID_TYPE) -/* positions touched by level elements explicitly defined in the des-file */ -static char SpLev_Map[COLNO][ROWNO]; +void +reset_xystart_size(void) +{ + gx.xstart = 1; /* column [0] is off limits */ + gy.ystart = 0; + gx.xsize = COLNO - 1; /* 1..COLNO-1 */ + gy.ysize = ROWNO; /* 0..ROWNO-1 */ +} -static aligntyp ralign[3] = { AM_CHAOTIC, AM_NEUTRAL, AM_LAWFUL }; -static NEARDATA xchar xstart, ystart; -static NEARDATA char xsize, ysize; +/* Does typ match with levl[][].typ, considering special types + MATCH_WALL and MAX_TYPE (aka transparency)? */ +boolean +match_maptyps(xint16 typ, xint16 levltyp) +{ + if ((typ == MATCH_WALL) && !IS_STWALL(levltyp)) + return FALSE; + if ((typ < MAX_TYPE) && (typ != levltyp)) + return FALSE; + return TRUE; +} -char *lev_message = 0; -lev_region *lregions = 0; -int num_lregions = 0; +struct mapfragment * +mapfrag_fromstr(char *str) +{ + struct mapfragment *mf = (struct mapfragment *) alloc(sizeof *mf); -static boolean splev_init_present = FALSE; -static boolean icedpools = FALSE; -static int mines_prize_count = 0, soko_prize_count = 0; /* achievements */ + char *tmps; -static struct obj *container_obj[MAX_CONTAINMENT]; -static int container_idx = 0; -static struct monst *invent_carrying_monster = NULL; + mf->data = dupstr(str); -#define SPLEV_STACK_RESERVE 128 + (void) stripdigits(mf->data); + mf->wid = str_lines_maxlen(mf->data); + mf->hei = 0; + tmps = mf->data; + while (tmps && *tmps) { + char *s1 = strchr(tmps, '\n'); + + if (mf->hei > MAP_Y_LIM) { + free(mf->data); + free(mf); + return NULL; + } + if (s1) + s1++; + tmps = s1; + mf->hei++; + } + return mf; +} void -solidify_map() +mapfrag_free(struct mapfragment **mf) { - xchar x, y; + if (mf && *mf) { + free((*mf)->data); + free(*mf); + *mf = NULL; + } +} - for (x = 0; x < COLNO; x++) - for (y = 0; y < ROWNO; y++) - if (IS_STWALL(levl[x][y].typ) && !SpLev_Map[x][y]) - levl[x][y].wall_info |= (W_NONDIGGABLE | W_NONPASSWALL); +schar +mapfrag_get(struct mapfragment *mf, int x, int y) +{ + if (y < 0 || x < 0 || y > mf->hei - 1 || x > mf->wid - 1) + panic("outside mapfrag (%i,%i), wanted (%i,%i)", + mf->wid, mf->hei, x, y); + return splev_chr2typ(mf->data[y * (mf->wid + 1) + x]); } -void -splev_stack_init(st) -struct splevstack *st; +boolean +mapfrag_canmatch(struct mapfragment *mf) { - if (st) { - st->depth = 0; - st->depth_alloc = SPLEV_STACK_RESERVE; - st->stackdata = - (struct opvar **) alloc(st->depth_alloc * sizeof (struct opvar *)); - } + return ((mf->wid % 2) && (mf->hei % 2)); } -void -splev_stack_done(st) -struct splevstack *st; +const char * +mapfrag_error(struct mapfragment *mf) { - if (st) { - int i; + const char *res = NULL; - if (st->stackdata && st->depth) { - for (i = 0; i < st->depth; i++) { - switch (st->stackdata[i]->spovartyp) { - default: - case SPOVAR_NULL: - case SPOVAR_COORD: - case SPOVAR_REGION: - case SPOVAR_MAPCHAR: - case SPOVAR_MONST: - case SPOVAR_OBJ: - case SPOVAR_INT: - break; - case SPOVAR_VARIABLE: - case SPOVAR_STRING: - case SPOVAR_SEL: - Free(st->stackdata[i]->vardata.str); - st->stackdata[i]->vardata.str = NULL; - break; - } - Free(st->stackdata[i]); - st->stackdata[i] = NULL; - } - } - Free(st->stackdata); - st->stackdata = NULL; - st->depth = st->depth_alloc = 0; - Free(st); + if (!mf) { + res = "mapfragment error"; + } else if (!mapfrag_canmatch(mf)) { + mapfrag_free(&mf); + res = "mapfragment needs to have odd height and width"; + } else if (TYP_CANNOT_MATCH(mapfrag_get(mf, mf->wid / 2, mf->hei / 2))) { + mapfrag_free(&mf); + res = "mapfragment center must be valid terrain"; } + return res; } -void -splev_stack_push(st, v) -struct splevstack *st; -struct opvar *v; +boolean +mapfrag_match(struct mapfragment *mf, int x, int y) { - if (!st || !v) - return; - if (!st->stackdata) - panic("splev_stack_push: no stackdata allocated?"); + int rx, ry; - if (st->depth >= st->depth_alloc) { - struct opvar **tmp = (struct opvar **) alloc( - (st->depth_alloc + SPLEV_STACK_RESERVE) * sizeof (struct opvar *)); + for (rx = -(mf->wid / 2); rx <= (mf->wid / 2); rx++) + for (ry = -(mf->hei / 2); ry <= (mf->hei / 2); ry++) { + schar mapc = mapfrag_get(mf, rx + (mf->wid / 2), + ry + (mf->hei / 2)); + schar levc = isok(x+rx, y+ry) ? levl[x+rx][y+ry].typ : STONE; - (void) memcpy(tmp, st->stackdata, - st->depth_alloc * sizeof(struct opvar *)); - Free(st->stackdata); - st->stackdata = tmp; - st->depth_alloc += SPLEV_STACK_RESERVE; - } + if (!match_maptyps(mapc, levc)) + return FALSE; + } + return TRUE; +} + +staticfn void +solidify_map(void) +{ + coordxy x, y; - st->stackdata[st->depth] = v; - st->depth++; + for (x = 0; x < COLNO; x++) + for (y = 0; y < ROWNO; y++) + if (IS_STWALL(levl[x][y].typ) && !SpLev_Map[x][y]) + levl[x][y].wall_info |= (W_NONDIGGABLE | W_NONPASSWALL); } -struct opvar * -splev_stack_pop(st) -struct splevstack *st; +/* do a post-level-creation cleanup of map, such as + removing boulders and traps from lava */ +staticfn void +map_cleanup(void) { - struct opvar *ret = NULL; + struct obj *otmp; + struct trap *ttmp; + struct engr *etmp; + coordxy x, y; + + for (x = 0; x < COLNO; x++) + for (y = 0; y < ROWNO; y++) { + schar typ = levl[x][y].typ; - if (!st) - return ret; - if (!st->stackdata) - panic("splev_stack_pop: no stackdata allocated?"); + if (IS_LAVA(typ) || IS_POOL(typ)) { + /* in case any boulders are on liquid, delete them */ + while ((otmp = sobj_at(BOULDER, x, y)) != 0) { + obj_extract_self(otmp); + obfree(otmp, (struct obj *) 0); + } - if (st->depth) { - st->depth--; - ret = st->stackdata[st->depth]; - st->stackdata[st->depth] = NULL; - return ret; - } else - impossible("splev_stack_pop: empty stack?"); - return ret; + /* traps on liquid? */ + if (((ttmp = t_at(x, y)) != 0) + && !undestroyable_trap(ttmp->ttyp)) + deltrap(ttmp); + + /* engravings? */ + if ((etmp = engr_at(x, y)) != 0) + del_engr(etmp); + } + } } -struct splevstack * -splev_stack_reverse(st) -struct splevstack *st; +staticfn void +lvlfill_maze_grid(int x1, int y1, int x2, int y2, schar filling) { - long i; - struct opvar *tmp; + int x, y; - if (!st) - return NULL; - if (!st->stackdata) - panic("splev_stack_reverse: no stackdata allocated?"); - for (i = 0; i < (st->depth / 2); i++) { - tmp = st->stackdata[i]; - st->stackdata[i] = st->stackdata[st->depth - i - 1]; - st->stackdata[st->depth - i - 1] = tmp; - } - return st; + for (x = x1; x <= x2; x++) + for (y = y1; y <= y2; y++) { + if (svl.level.flags.corrmaze) + levl[x][y].typ = STONE; + else + levl[x][y].typ = (y < 2 || ((x % 2) && (y % 2))) ? STONE + : filling; + } } -#define OV_typ(o) (o->spovartyp) -#define OV_i(o) (o->vardata.l) -#define OV_s(o) (o->vardata.str) +staticfn void +lvlfill_solid(schar filling, schar lit) +{ + int x, y; -#define OV_pop_i(x) (x = splev_stack_getdat(coder, SPOVAR_INT)) -#define OV_pop_c(x) (x = splev_stack_getdat(coder, SPOVAR_COORD)) -#define OV_pop_r(x) (x = splev_stack_getdat(coder, SPOVAR_REGION)) -#define OV_pop_s(x) (x = splev_stack_getdat(coder, SPOVAR_STRING)) -#define OV_pop(x) (x = splev_stack_getdat_any(coder)) -#define OV_pop_typ(x, typ) (x = splev_stack_getdat(coder, typ)) + for (x = 2; x <= gx.x_maze_max; x++) + for (y = 0; y <= gy.y_maze_max; y++) { + if (!set_levltyp_lit(x, y, filling, lit)) + continue; + /* TODO: consolidate this w lspo_map ? */ + levl[x][y].flags = 0; + levl[x][y].horizontal = 0; + levl[x][y].roomno = 0; + levl[x][y].edge = 0; + } +} -struct opvar * -opvar_new_str(s) -char *s; +staticfn void +lvlfill_swamp(schar fg, schar bg, schar lit) { - struct opvar *tmpov = (struct opvar *) alloc(sizeof (struct opvar)); + int x, y; - tmpov->spovartyp = SPOVAR_STRING; - if (s) { - int len = strlen(s); - tmpov->vardata.str = (char *) alloc(len + 1); - (void) memcpy((genericptr_t) tmpov->vardata.str, (genericptr_t) s, - len); - tmpov->vardata.str[len] = '\0'; - } else - tmpov->vardata.str = NULL; - return tmpov; + lvlfill_solid(bg, lit); + + /* "relaxed blockwise maze" algorithm, Jamis Buck */ + for (x = 2; x <= min(gx.x_maze_max, COLNO-2); x += 2) + for (y = 0; y <= min(gy.y_maze_max, ROWNO-2); y += 2) { + int c = 0; + + (void) set_levltyp_lit(x, y, fg, lit); + if (levl[x + 1][y].typ == bg) + ++c; + if (levl[x][y + 1].typ == bg) + ++c; + if (levl[x + 1][y + 1].typ == bg) + ++c; + if (c == 3) { + switch (rn2(3)) { + case 0: + (void) set_levltyp_lit(x + 1,y, fg, lit); + break; + case 1: + (void) set_levltyp_lit(x, y + 1, fg, lit); + break; + case 2: + (void) set_levltyp_lit(x + 1, y + 1, fg, lit); + break; + default: + break; + } + } + } } -struct opvar * -opvar_new_int(i) -long i; +staticfn void +flip_dbridge_horizontal(struct rm *lev) { - struct opvar *tmpov = (struct opvar *) alloc(sizeof (struct opvar)); + if (IS_DRAWBRIDGE(lev->typ)) { + if ((lev->drawbridgemask & DB_DIR) == DB_WEST) { + lev->drawbridgemask &= ~DB_WEST; + lev->drawbridgemask |= DB_EAST; + } else if ((lev->drawbridgemask & DB_DIR) == DB_EAST) { + lev->drawbridgemask &= ~DB_EAST; + lev->drawbridgemask |= DB_WEST; + } + } +} - tmpov->spovartyp = SPOVAR_INT; - tmpov->vardata.l = i; - return tmpov; +staticfn void +flip_dbridge_vertical(struct rm *lev) +{ + if (IS_DRAWBRIDGE(lev->typ)) { + if ((lev->drawbridgemask & DB_DIR) == DB_NORTH) { + lev->drawbridgemask &= ~DB_NORTH; + lev->drawbridgemask |= DB_SOUTH; + } else if ((lev->drawbridgemask & DB_DIR) == DB_SOUTH) { + lev->drawbridgemask &= ~DB_SOUTH; + lev->drawbridgemask |= DB_NORTH; + } + } } -struct opvar * -opvar_new_coord(x, y) -int x, y; +/* for #wizfliplevel; not needed when flipping during level creation; + update seen vector for whole flip area and glyph for known walls */ +staticfn void +flip_visuals(int flp, int minx, int miny, int maxx, int maxy) { - struct opvar *tmpov = (struct opvar *) alloc(sizeof (struct opvar)); + struct rm *lev; + int x, y, seenv; - tmpov->spovartyp = SPOVAR_COORD; - tmpov->vardata.l = SP_COORD_PACK(x, y); - return tmpov; + for (y = miny; y <= maxy; ++y) { + for (x = minx; x <= maxx; ++x) { + lev = &levl[x][y]; + seenv = lev->seenv & 0xff; + /* locations which haven't been seen can be skipped */ + if (seenv == 0) + continue; + /* flip 's seen vector; not necessary for locations seen + from all directions (the whole level after magic mapping) */ + if (seenv != SVALL) { + /* SV2 SV1 SV0 * + * SV3 -+- SV7 * + * SV4 SV5 SV6 */ + if (flp & 1) { /* swap top and bottom */ + seenv = swapbits(seenv, 2, 4); + seenv = swapbits(seenv, 1, 5); + seenv = swapbits(seenv, 0, 6); + } + if (flp & 2) { /* swap left and right */ + seenv = swapbits(seenv, 2, 0); + seenv = swapbits(seenv, 3, 7); + seenv = swapbits(seenv, 4, 6); + } + lev->seenv = (uchar) seenv; + } + /* if is displayed as a wall, reset its display glyph so + that remembered, out of view T's and corners get flipped */ + if ((IS_WALL(lev->typ) || lev->typ == SDOOR) + && glyph_is_cmap(lev->glyph)) + lev->glyph = back_to_glyph(x, y); + } + } } -#if 0 -struct opvar * -opvar_new_region(x1,y1,x2,y2) - int x1,y1,x2,y2; +/* transpose an encoded direction */ +staticfn int +flip_encoded_dir_bits(int flp, int val) { - struct opvar *tmpov = (struct opvar *) alloc(sizeof (struct opvar)); + /* these depend on xdir[] and ydir[] order */ + if (flp & 1) { + val = swapbits(val, 1, 7); + val = swapbits(val, 2, 6); + val = swapbits(val, 3, 5); + } + if (flp & 2) { + val = swapbits(val, 1, 3); + val = swapbits(val, 0, 4); + val = swapbits(val, 7, 5); + } - tmpov->spovartyp = SPOVAR_REGION; - tmpov->vardata.l = SP_REGION_PACK(x1,y1,x2,y2); - return tmpov; -} -#endif /*0*/ + return val; +} + +#define FlipX(val) ((maxx - (val)) + minx) +#define FlipY(val) ((maxy - (val)) + miny) +#define inFlipArea(x,y) \ + ((x) >= minx && (x) <= maxx && (y) >= miny && (y) <= maxy) +#define Flip_coord(cc) \ + do { \ + if ((cc).x && inFlipArea((cc).x, (cc).y)) { \ + if (flp & 1) \ + (cc).y = FlipY((cc).y); \ + if (flp & 2) \ + (cc).x = FlipX((cc).x); \ + } \ + } while (0) +/* transpose top with bottom or left with right or both; sometimes called + for new special levels, or for any level via the #wizfliplevel command */ void -opvar_free_x(ov) -struct opvar *ov; -{ - if (!ov) +flip_level( + int flp, /* mask for orientation(s) to transpose */ + boolean extras) /* False: level creation; True: #wizfliplevel is + * altering an active level so more needs to be done */ +{ + int x, y, i, itmp; + coordxy minx, miny, maxx, maxy; + struct rm trm; + struct trap *ttmp; + struct obj *otmp; + struct monst *mtmp; + struct engr *etmp; + struct mkroom *sroom; + timer_element *timer; + boolean ball_active = FALSE, ball_fliparea = FALSE; + stairway *stway; + struct exclusion_zone *ez; + + /* nothing to do unless (flp & 1) or (flp & 2) or both */ + if ((flp & 3) == 0) return; - switch (ov->spovartyp) { - case SPOVAR_COORD: - case SPOVAR_REGION: - case SPOVAR_MAPCHAR: - case SPOVAR_MONST: - case SPOVAR_OBJ: - case SPOVAR_INT: - break; - case SPOVAR_VARIABLE: - case SPOVAR_STRING: - case SPOVAR_SEL: - Free(ov->vardata.str); - break; - default: - impossible("Unknown opvar value type (%i)!", ov->spovartyp); + + get_level_extends(&minx, &miny, &maxx, &maxy); + /* get_level_extends() returns -1,-1 to COLNO,ROWNO at max */ + if (miny < 0) + miny = 0; + if (minx < 1) + minx = 1; + if (maxx >= COLNO) + maxx = (COLNO - 1); + if (maxy >= ROWNO) + maxy = (ROWNO - 1); + + if (extras) { + if (Punished && uball->where != OBJ_FREE) { + ball_active = TRUE; + /* if hero and ball and chain are all inside flip area, + flip b&c coordinates along with other objects; if they + are all outside, leave them to be rejected when flipping + so that they stay as is; if some are inside and some are + outside, un-place here and subsequently re-place them on + hero's [possibly new] spot below */ + if (carried(uball)) + uball->ox = u.ux, uball->oy = u.uy; + ball_fliparea = ((inFlipArea(uball->ox, uball->oy) + == inFlipArea(uchain->ox, uchain->oy)) + && (inFlipArea(uball->ox, uball->oy) + == inFlipArea(u.ux, u.uy))); + if (!ball_fliparea) + unplacebc(); + } } - Free(ov); -} -/* - * Name of current function for use in messages: - * __func__ -- C99 standard; - * __FUNCTION__ -- gcc extension, starting before C99 and continuing after; - * picked up by other compilers (or vice versa?); - * __FUNC__ -- supported by Borland; - * nhFunc -- slightly intrusive but fully portable nethack construct - * for any version of any compiler. - */ -#define opvar_free(ov) \ - do { \ - if (ov) { \ - opvar_free_x(ov); \ - ov = NULL; \ - } else \ - impossible("opvar_free(), %s", nhFunc); \ - } while (0) + /* stairs and ladders */ + for (stway = gs.stairs; stway; stway = stway->next) { + if (flp & 1) + stway->sy = FlipY(stway->sy); + if (flp & 2) + stway->sx = FlipX(stway->sx); + } -struct opvar * -opvar_clone(ov) -struct opvar *ov; -{ - struct opvar *tmpov; - - if (!ov) - panic("no opvar to clone"); - tmpov = (struct opvar *) alloc(sizeof(struct opvar)); - tmpov->spovartyp = ov->spovartyp; - switch (ov->spovartyp) { - case SPOVAR_COORD: - case SPOVAR_REGION: - case SPOVAR_MAPCHAR: - case SPOVAR_MONST: - case SPOVAR_OBJ: - case SPOVAR_INT: - tmpov->vardata.l = ov->vardata.l; - break; - case SPOVAR_VARIABLE: - case SPOVAR_STRING: - case SPOVAR_SEL: - tmpov->vardata.str = dupstr(ov->vardata.str); - break; - default: - impossible("Unknown push value type (%i)!", ov->spovartyp); - } - return tmpov; -} - -struct opvar * -opvar_var_conversion(coder, ov) -struct sp_coder *coder; -struct opvar *ov; -{ - static const char nhFunc[] = "opvar_var_conversion"; - struct splev_var *tmp; - struct opvar *tmpov; - struct opvar *array_idx = NULL; - - if (!coder || !ov) - return NULL; - if (ov->spovartyp != SPOVAR_VARIABLE) - return ov; - tmp = coder->frame->variables; - while (tmp) { - if (!strcmp(tmp->name, OV_s(ov))) { - if ((tmp->svtyp & SPOVAR_ARRAY)) { - array_idx = opvar_var_conversion(coder, - splev_stack_pop(coder->stack)); - if (!array_idx || OV_typ(array_idx) != SPOVAR_INT) - panic("array idx not an int"); - if (tmp->array_len < 1) - panic("array len < 1"); - OV_i(array_idx) = (OV_i(array_idx) % tmp->array_len); - tmpov = opvar_clone(tmp->data.arrayvalues[OV_i(array_idx)]); - opvar_free(array_idx); - return tmpov; - } else { - tmpov = opvar_clone(tmp->data.value); - return tmpov; + /* traps */ + for (ttmp = gf.ftrap; ttmp; ttmp = ttmp->ntrap) { + if (!inFlipArea(ttmp->tx, ttmp->ty)) + continue; + if (flp & 1) { + ttmp->ty = FlipY(ttmp->ty); + if (ttmp->ttyp == ROLLING_BOULDER_TRAP) { + ttmp->launch.y = FlipY(ttmp->launch.y); + ttmp->launch2.y = FlipY(ttmp->launch2.y); + } else if (is_pit(ttmp->ttyp) && ttmp->conjoined) { + ttmp->conjoined = flip_encoded_dir_bits(flp, ttmp->conjoined); + } + } + if (flp & 2) { + ttmp->tx = FlipX(ttmp->tx); + if (ttmp->ttyp == ROLLING_BOULDER_TRAP) { + ttmp->launch.x = FlipX(ttmp->launch.x); + ttmp->launch2.x = FlipX(ttmp->launch2.x); + } else if (is_pit(ttmp->ttyp) && ttmp->conjoined) { + ttmp->conjoined = flip_encoded_dir_bits(flp, ttmp->conjoined); } } - tmp = tmp->next; } - return NULL; -} -struct splev_var * -opvar_var_defined(coder, name) -struct sp_coder *coder; -char *name; -{ - struct splev_var *tmp; + /* objects */ + for (otmp = fobj; otmp; otmp = otmp->nobj) { + if (!inFlipArea(otmp->ox, otmp->oy)) + continue; + if (flp & 1) + otmp->oy = FlipY(otmp->oy); + if (flp & 2) + otmp->ox = FlipX(otmp->ox); + } - if (!coder) - return NULL; - tmp = coder->frame->variables; - while (tmp) { - if (!strcmp(tmp->name, name)) - return tmp; - tmp = tmp->next; + /* buried objects */ + for (otmp = svl.level.buriedobjlist; otmp; otmp = otmp->nobj) { + if (!inFlipArea(otmp->ox, otmp->oy)) + continue; + if (flp & 1) + otmp->oy = FlipY(otmp->oy); + if (flp & 2) + otmp->ox = FlipX(otmp->ox); } - return NULL; -} -struct opvar * -splev_stack_getdat(coder, typ) -struct sp_coder *coder; -xchar typ; -{ - static const char nhFunc[] = "splev_stack_getdat"; - if (coder && coder->stack) { - struct opvar *tmp = splev_stack_pop(coder->stack); - struct opvar *ret = NULL; + /* monsters */ + for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { + if (mtmp->isgd) { + if (extras) /* flip mtmp->mextra->egd */ + flip_vault_guard(flp, mtmp, minx, miny, maxx, maxy); + if (mtmp->mx == 0) /* not on map so don't flip guard->mx,my */ + continue; + } + /* skip the occasional earth elemental outside the flip area */ + if (!inFlipArea(mtmp->mx, mtmp->my)) + continue; + if (flp & 1) + mtmp->my = FlipY(mtmp->my); + if (flp & 2) + mtmp->mx = FlipX(mtmp->mx); + + Flip_coord(mtmp->mgoal); + + if (mtmp->ispriest) { + Flip_coord(EPRI(mtmp)->shrpos); + } else if (mtmp->isshk) { + Flip_coord(ESHK(mtmp)->shk); /* shk's preferred spot */ + Flip_coord(ESHK(mtmp)->shd); /* shop door */ + } else if (mtmp->wormno) { + if (flp & 1) + flip_worm_segs_vertical(mtmp, miny, maxy); + if (flp & 2) + flip_worm_segs_horizontal(mtmp, minx, maxx); + } +#if 0 /* not useful unless tracking also gets flipped */ + if (extras) { + if (mtmp->tame && has_edog(mtmp)) + Flip_coord(EDOG(mtmp)->ogoal); + } +#endif + } + if (extras) { /* #wizfliplevel rather than level creation */ + for (mtmp = gm.migrating_mons; mtmp; mtmp = mtmp->nmon) { + if (mtmp->isgd && on_level(&u.uz, &EGD(mtmp)->gdlevel)) { + flip_vault_guard(flp, mtmp, minx, miny, maxx, maxy); /* egd */ + } else if (mtmp->ispriest + && on_level(&u.uz, &EPRI(mtmp)->shrlevel)) { + Flip_coord(EPRI(mtmp)->shrpos); /* priest's altar */ + } else if (mtmp->isshk + && on_level(&u.uz, &ESHK(mtmp)->shoplevel)) { + Flip_coord(ESHK(mtmp)->shk); /* shk's preferred spot */ + Flip_coord(ESHK(mtmp)->shd); /* shop door */ + } + } + } + + /* engravings */ + for (etmp = head_engr; etmp; etmp = etmp->nxt_engr) { + if (flp & 1) + etmp->engr_y = FlipY(etmp->engr_y); + if (flp & 2) + etmp->engr_x = FlipX(etmp->engr_x); + } + + /* level (teleport) regions */ + for (i = 0; i < gn.num_lregions; i++) { + if (flp & 1) { + gl.lregions[i].inarea.y1 = FlipY(gl.lregions[i].inarea.y1); + gl.lregions[i].inarea.y2 = FlipY(gl.lregions[i].inarea.y2); + if (gl.lregions[i].inarea.y1 > gl.lregions[i].inarea.y2) { + itmp = gl.lregions[i].inarea.y1; + gl.lregions[i].inarea.y1 = gl.lregions[i].inarea.y2; + gl.lregions[i].inarea.y2 = itmp; + } - if (!tmp) - panic("no value type %i in stack.", typ); - if (tmp->spovartyp == SPOVAR_VARIABLE) { - ret = opvar_var_conversion(coder, tmp); - opvar_free(tmp); - tmp = ret; + gl.lregions[i].delarea.y1 = FlipY(gl.lregions[i].delarea.y1); + gl.lregions[i].delarea.y2 = FlipY(gl.lregions[i].delarea.y2); + if (gl.lregions[i].delarea.y1 > gl.lregions[i].delarea.y2) { + itmp = gl.lregions[i].delarea.y1; + gl.lregions[i].delarea.y1 = gl.lregions[i].delarea.y2; + gl.lregions[i].delarea.y2 = itmp; + } + } + if (flp & 2) { + gl.lregions[i].inarea.x1 = FlipX(gl.lregions[i].inarea.x1); + gl.lregions[i].inarea.x2 = FlipX(gl.lregions[i].inarea.x2); + if (gl.lregions[i].inarea.x1 > gl.lregions[i].inarea.x2) { + itmp = gl.lregions[i].inarea.x1; + gl.lregions[i].inarea.x1 = gl.lregions[i].inarea.x2; + gl.lregions[i].inarea.x2 = itmp; + } + + gl.lregions[i].delarea.x1 = FlipX(gl.lregions[i].delarea.x1); + gl.lregions[i].delarea.x2 = FlipX(gl.lregions[i].delarea.x2); + if (gl.lregions[i].delarea.x1 > gl.lregions[i].delarea.x2) { + itmp = gl.lregions[i].delarea.x1; + gl.lregions[i].delarea.x1 = gl.lregions[i].delarea.x2; + gl.lregions[i].delarea.x2 = itmp; + } } - if (tmp->spovartyp == typ) - return tmp; - else opvar_free(tmp); } - return NULL; -} -struct opvar * -splev_stack_getdat_any(coder) -struct sp_coder *coder; -{ - static const char nhFunc[] = "splev_stack_getdat_any"; - if (coder && coder->stack) { - struct opvar *tmp = splev_stack_pop(coder->stack); - if (tmp && tmp->spovartyp == SPOVAR_VARIABLE) { - struct opvar *ret = opvar_var_conversion(coder, tmp); - opvar_free(tmp); - return ret; + /* regions (poison clouds, etc) */ + for (i = 0; i < svn.n_regions; i++) { + int j, tmp1, tmp2; + if (flp & 1) { + tmp1 = FlipY(gr.regions[i]->bounding_box.ly); + tmp2 = FlipY(gr.regions[i]->bounding_box.hy); + gr.regions[i]->bounding_box.ly = min(tmp1, tmp2); + gr.regions[i]->bounding_box.hy = max(tmp1, tmp2); + for (j = 0; j < gr.regions[i]->nrects; j++) { + tmp1 = FlipY(gr.regions[i]->rects[j].ly); + tmp2 = FlipY(gr.regions[i]->rects[j].hy); + gr.regions[i]->rects[j].ly = min(tmp1, tmp2); + gr.regions[i]->rects[j].hy = max(tmp1, tmp2); + } + } + if (flp & 2) { + tmp1 = FlipX(gr.regions[i]->bounding_box.lx); + tmp2 = FlipX(gr.regions[i]->bounding_box.hx); + gr.regions[i]->bounding_box.lx = min(tmp1, tmp2); + gr.regions[i]->bounding_box.hx = max(tmp1, tmp2); + for (j = 0; j < gr.regions[i]->nrects; j++) { + tmp1 = FlipX(gr.regions[i]->rects[j].lx); + tmp2 = FlipX(gr.regions[i]->rects[j].hx); + gr.regions[i]->rects[j].lx = min(tmp1, tmp2); + gr.regions[i]->rects[j].hx = max(tmp1, tmp2); + } } - return tmp; } - return NULL; + + /* rooms */ + for (sroom = &svr.rooms[0]; ; sroom++) { + if (sroom->hx < 0) + break; + + if (flp & 1) { + sroom->ly = FlipY(sroom->ly); + sroom->hy = FlipY(sroom->hy); + if (sroom->ly > sroom->hy) { + itmp = sroom->ly; + sroom->ly = sroom->hy; + sroom->hy = itmp; + } + } + if (flp & 2) { + sroom->lx = FlipX(sroom->lx); + sroom->hx = FlipX(sroom->hx); + if (sroom->lx > sroom->hx) { + itmp = sroom->lx; + sroom->lx = sroom->hx; + sroom->hx = itmp; + } + } + + if (sroom->nsubrooms) + for (i = 0; i < sroom->nsubrooms; i++) { + struct mkroom *rroom = sroom->sbrooms[i]; + + if (flp & 1) { + rroom->ly = FlipY(rroom->ly); + rroom->hy = FlipY(rroom->hy); + if (rroom->ly > rroom->hy) { + itmp = rroom->ly; + rroom->ly = rroom->hy; + rroom->hy = itmp; + } + } + if (flp & 2) { + rroom->lx = FlipX(rroom->lx); + rroom->hx = FlipX(rroom->hx); + if (rroom->lx > rroom->hx) { + itmp = rroom->lx; + rroom->lx = rroom->hx; + rroom->hx = itmp; + } + } + } + } + + /* doors */ + for (i = 0; i < gd.doorindex; i++) { + Flip_coord(svd.doors[i]); + } + + /* the map */ + if (flp & 1) { + for (x = minx; x <= maxx; x++) + for (y = miny; y < (miny + ((maxy - miny + 1) / 2)); y++) { + int ny = FlipY(y); + + flip_dbridge_vertical(&levl[x][y]); + flip_dbridge_vertical(&levl[x][ny]); + + trm = levl[x][y]; + levl[x][y] = levl[x][ny]; + levl[x][ny] = trm; + + otmp = svl.level.objects[x][y]; + svl.level.objects[x][y] = svl.level.objects[x][ny]; + svl.level.objects[x][ny] = otmp; + + mtmp = svl.level.monsters[x][y]; + svl.level.monsters[x][y] = svl.level.monsters[x][ny]; + svl.level.monsters[x][ny] = mtmp; + } + } + if (flp & 2) { + for (x = minx; x < (minx + ((maxx - minx + 1) / 2)); x++) + for (y = miny; y <= maxy; y++) { + int nx = FlipX(x); + + flip_dbridge_horizontal(&levl[x][y]); + flip_dbridge_horizontal(&levl[nx][y]); + + trm = levl[x][y]; + levl[x][y] = levl[nx][y]; + levl[nx][y] = trm; + + otmp = svl.level.objects[x][y]; + svl.level.objects[x][y] = svl.level.objects[nx][y]; + svl.level.objects[nx][y] = otmp; + + mtmp = svl.level.monsters[x][y]; + svl.level.monsters[x][y] = svl.level.monsters[nx][y]; + svl.level.monsters[nx][y] = mtmp; + } + } + + /* timed effects */ + for (timer = gt.timer_base; timer; timer = timer->next) { + if (timer->func_index == MELT_ICE_AWAY) { + long ty = timer->arg.a_long & 0xffff; + long tx = (timer->arg.a_long >> 16) & 0xffff; + + if (flp & 1) + ty = FlipY(ty); + if (flp & 2) + tx = FlipX(tx); + timer->arg.a_long = ((tx << 16) | ty); + } + } + + /* exclusion zones */ + for (ez = sve.exclusion_zones; ez; ez = ez->next) { + if (flp & 1) { + ez->ly = FlipY(ez->ly); + ez->hy = FlipY(ez->hy); + if (ez->ly > ez->hy) { + itmp = ez->ly; + ez->ly = ez->hy; + ez->hy = itmp; + } + } + if (flp & 2) { + ez->lx = FlipX(ez->lx); + ez->hx = FlipX(ez->hx); + if (ez->lx > ez->hx) { + itmp = ez->lx; + ez->lx = ez->hx; + ez->hx = itmp; + } + } + } + + if (extras) { /* for #wizfliplevel rather than during level creation */ + /* flip hero location only if inside the flippable area */ + if (inFlipArea(u.ux, u.uy)) { + if (flp & 1) + u.uy = FlipY(u.uy); + if (flp & 2) + u.ux = FlipX(u.ux); + /* we could flip too if it's inside the flip area, + but have to resort to this if outside, so just do this */ + u.ux0 = u.ux, u.uy0 = u.uy; + } + if (ball_active && !ball_fliparea) + placebc(); + Flip_coord(iflags.travelcc); + Flip_coord(svc.context.digging.pos); + } + + fix_wall_spines(1, 0, COLNO - 1, ROWNO - 1); + if (extras && flp) { + set_wall_state(); + /* after wall_spines; flips seenv and wall joins */ + flip_visuals(flp, minx, miny, maxx, maxy); + } + vision_reset(); } -void -variable_list_del(varlist) -struct splev_var *varlist; +/* for #wizfliplevel, flip guard's egd data; not needed for level creation */ +staticfn void +flip_vault_guard( + int flp, /* 1: transpose vertically, 2: transpose horizontally, 3: both */ + struct monst *grd, /* the vault guard, has monst->mextra->egd data */ + coordxy minx, coordxy miny, /* needed by FlipX(), FlipY(), */ + coordxy maxx, coordxy maxy) /* and inFlipArea() macros */ { - static const char nhFunc[] = "variable_list_del"; - struct splev_var *tmp = varlist; + int i; + struct egd *egd = EGD(grd); - if (!tmp) - return; - while (tmp) { - Free(tmp->name); - if ((tmp->svtyp & SPOVAR_ARRAY)) { - long idx = tmp->array_len; - - while (idx-- > 0) { - opvar_free(tmp->data.arrayvalues[idx]); - }; - Free(tmp->data.arrayvalues); - } else { - opvar_free(tmp->data.value); + if (inFlipArea(egd->gdx, egd->gdy)) { + if (flp & 1) + egd->gdy = FlipY(egd->gdy); + if (flp & 2) + egd->gdx = FlipX(egd->gdx); + } + if (inFlipArea(egd->ogx, egd->ogy)) { + if (flp & 1) + egd->ogy = FlipY(egd->ogy); + if (flp & 2) + egd->ogx = FlipX(egd->ogx); + } + for (i = egd->fcbeg; i < egd->fcend; ++i) { + coordxy fx = egd->fakecorr[i].fx, fy = egd->fakecorr[i].fy; + + if (inFlipArea(fx, fy)) { + if (flp & 1) + egd->fakecorr[i].fy = FlipY(fy); + if (flp & 2) + egd->fakecorr[i].fx = FlipX(fx); } - tmp = varlist->next; - Free(varlist); - varlist = tmp; } + return; } +#undef FlipX +#undef FlipY +#undef inFlipArea + +/* randomly transpose top with bottom or left with right or both; + caller controls which transpositions are allowed */ void -lvlfill_maze_grid(x1, y1, x2, y2, filling) -int x1, y1, x2, y2; -schar filling; +flip_level_rnd(int flp, boolean extras) { - int x, y; + int c = 0; - for (x = x1; x <= x2; x++) - for (y = y1; y <= y2; y++) { - if (level.flags.corrmaze) - levl[x][y].typ = STONE; - else - levl[x][y].typ = (y < 2 || ((x % 2) && (y % 2))) ? STONE - : filling; - } + /* TODO? + * Might change rn2(2) to !rn2(3) or (rn2(5) < 2) in order to bias + * the outcome towards the traditional orientation. + */ + if ((flp & 1) && rn2(2)) + c |= 1; + if ((flp & 2) && rn2(2)) + c |= 2; + + if (c) + flip_level(c, extras); } -void -lvlfill_solid(filling, lit) -schar filling; -schar lit; + +staticfn void +sel_set_wall_property(coordxy x, coordxy y, genericptr_t arg) { - int x, y; + int prop = *(int *) arg; - for (x = 2; x <= x_maze_max; x++) - for (y = 0; y <= y_maze_max; y++) { - SET_TYPLIT(x, y, filling, lit); - } + if (IS_STWALL(levl[x][y].typ) || IS_TREE(levl[x][y].typ) + /* 3.6.2: made iron bars eligible to be flagged nondiggable + (checked by chewing(hack.c) and zap_over_floor(zap.c)) */ + || levl[x][y].typ == IRONBARS) + levl[x][y].wall_info |= prop; } /* * Make walls of the area (x1, y1, x2, y2) non diggable/non passwall-able */ -STATIC_OVL void -set_wall_property(x1, y1, x2, y2, prop) -xchar x1, y1, x2, y2; -int prop; +staticfn void +set_wall_property(coordxy x1, coordxy y1, coordxy x2, coordxy y2, int prop) { - register xchar x, y; - struct rm *lev; + coordxy x, y; x1 = max(x1, 1); x2 = min(x2, COLNO - 1); @@ -645,61 +1008,19 @@ int prop; y2 = min(y2, ROWNO - 1); for (y = y1; y <= y2; y++) for (x = x1; x <= x2; x++) { - lev = &levl[x][y]; - if (IS_STWALL(lev->typ) || IS_TREE(lev->typ) - /* 3.6.2: made iron bars eligible to be flagged nondiggable - (checked by chewing(hack.c) and zap_over_floor(zap.c)) */ - || lev->typ == IRONBARS) - lev->wall_info |= prop; - } -} - -STATIC_OVL void -shuffle_alignments() -{ - int i; - aligntyp atmp; - - /* shuffle 3 alignments */ - i = rn2(3); - atmp = ralign[2]; - ralign[2] = ralign[i]; - ralign[i] = atmp; - if (rn2(2)) { - atmp = ralign[1]; - ralign[1] = ralign[0]; - ralign[0] = atmp; - } -} - -/* - * Count the different features (sinks, fountains) in the level. - */ -STATIC_OVL void -count_features() -{ - xchar x, y; - - level.flags.nfountains = level.flags.nsinks = 0; - for (y = 0; y < ROWNO; y++) - for (x = 0; x < COLNO; x++) { - int typ = levl[x][y].typ; - if (typ == FOUNTAIN) - level.flags.nfountains++; - else if (typ == SINK) - level.flags.nsinks++; + sel_set_wall_property(x, y, (genericptr_t) &prop); } } -void -remove_boundary_syms() +staticfn void +remove_boundary_syms(void) { /* * If any CROSSWALLs are found, must change to ROOM after REGION's * are laid out. CROSSWALLS are used to specify "invisible" * boundaries where DOOR syms look bad or aren't desirable. */ - xchar x, y; + coordxy x, y; boolean has_bounds = FALSE; for (x = 0; x < COLNO - 1; x++) @@ -709,17 +1030,16 @@ remove_boundary_syms() break; } if (has_bounds) { - for (x = 0; x < x_maze_max; x++) - for (y = 0; y < y_maze_max; y++) + for (x = 0; x < gx.x_maze_max; x++) + for (y = 0; y < gy.y_maze_max; y++) if ((levl[x][y].typ == CROSSWALL) && SpLev_Map[x][y]) levl[x][y].typ = ROOM; } } /* used by sel_set_door() and link_doors_rooms() */ -STATIC_OVL void -set_door_orientation(x, y) -int x, y; +staticfn void +set_door_orientation(int x, int y) { boolean wleft, wright, wup, wdown; @@ -764,18 +1084,43 @@ int x, y; levl[x][y].horizontal = ((wleft || wright) && !(wup && wdown)) ? 1 : 0; } -STATIC_OVL void -maybe_add_door(x, y, droom) -int x, y; -struct mkroom *droom; +/* is x,y right next to room droom? */ +staticfn boolean +shared_with_room(int x, int y, struct mkroom *droom) { - if (droom->hx >= 0 && doorindex < DOORMAX && inside_room(droom, x, y)) - add_door(x, y, droom); -} + int rmno = (droom - svr.rooms) + ROOMOFFSET; -STATIC_OVL void -link_doors_rooms() -{ + if (!isok(x,y)) + return FALSE; + if ((int) levl[x][y].roomno == rmno && !levl[x][y].edge) + return FALSE; + if (isok(x-1, y) && (int) levl[x-1][y].roomno == rmno && x-1 <= droom->hx) + return TRUE; + if (isok(x+1, y) && (int) levl[x+1][y].roomno == rmno && x+1 >= droom->lx) + return TRUE; + if (isok(x, y-1) && (int) levl[x][y-1].roomno == rmno && y-1 <= droom->hy) + return TRUE; + if (isok(x, y+1) && (int) levl[x][y+1].roomno == rmno && y+1 >= droom->ly) + return TRUE; + return FALSE; +} + +/* maybe add door at x,y to room droom */ +staticfn void +maybe_add_door(int x, int y, struct mkroom *droom) +{ + if (droom->hx >= 0 + && ((!droom->irregular && inside_room(droom, x, y)) + || (int) levl[x][y].roomno == (droom - svr.rooms) + ROOMOFFSET + || shared_with_room(x, y, droom))) { + add_door(x, y, droom); + } +} + +/* link all doors in the map to their corresponding rooms */ +staticfn void +link_doors_rooms(void) +{ int x, y; int tmpi, m; @@ -787,46 +1132,31 @@ link_doors_rooms() directive, set/clear levl[][].horizontal for it */ set_door_orientation(x, y); - for (tmpi = 0; tmpi < nroom; tmpi++) { - maybe_add_door(x, y, &rooms[tmpi]); - for (m = 0; m < rooms[tmpi].nsubrooms; m++) { - maybe_add_door(x, y, rooms[tmpi].sbrooms[m]); + for (tmpi = 0; tmpi < svn.nroom; tmpi++) { + maybe_add_door(x, y, &svr.rooms[tmpi]); + for (m = 0; m < svr.rooms[tmpi].nsubrooms; m++) { + maybe_add_door(x, y, svr.rooms[tmpi].sbrooms[m]); } } } } -void -fill_rooms() -{ - int tmpi, m; - - for (tmpi = 0; tmpi < nroom; tmpi++) { - if (rooms[tmpi].needfill) - fill_room(&rooms[tmpi], (rooms[tmpi].needfill == 2)); - for (m = 0; m < rooms[tmpi].nsubrooms; m++) - if (rooms[tmpi].sbrooms[m]->needfill) - fill_room(rooms[tmpi].sbrooms[m], FALSE); - } -} - /* * Choose randomly the state (nodoor, open, closed or locked) for a door */ -STATIC_OVL int -rnddoor() +staticfn int +rnddoor(void) { - int i = 1 << rn2(5); + static int state[] = { D_NODOOR, D_BROKEN, D_ISOPEN, D_CLOSED, D_LOCKED }; - i >>= 1; - return i; + return ROLL_FROM(state); } /* * Select a random trap */ -STATIC_OVL int -rndtrap() +staticfn int +rndtrap(void) { int rtrap; @@ -844,7 +1174,7 @@ rndtrap() break; case LEVEL_TELEP: case TELEP_TRAP: - if (level.flags.noteleport) + if (svl.level.flags.noteleport) rtrap = NO_TRAP; break; case ROLLING_BOULDER_TRAP: @@ -858,17 +1188,21 @@ rndtrap() } /* - * Coordinates in special level files are handled specially: + * Translate a given coordinate from a special level definition into an actual + * location on the map. * - * if x or y is < 0, we generate a random coordinate. - * The "humidity" flag is used to insure that engravings aren't - * created underwater, or eels on dry land. + * If x or y is negative, we generate a random coordinate within the area. If + * not negative, they are interpreted as relative to the last defined map or + * room, and are output as absolute svl.level.locations coordinates. + * + * The "humidity" flag is used to ensure that engravings aren't created + * underwater, or eels on dry land. */ -STATIC_OVL void -get_location(x, y, humidity, croom) -schar *x, *y; -int humidity; -struct mkroom *croom; +staticfn void +get_location( + coordxy *x, coordxy *y, + getloc_flags_t humidity, + struct mkroom *croom) { int cpt = 0; int mx, my, sx, sy; @@ -879,10 +1213,10 @@ struct mkroom *croom; sx = croom->hx - mx + 1; sy = croom->hy - my + 1; } else { - mx = xstart; - my = ystart; - sx = xsize; - sy = ysize; + mx = gx.xstart; + my = gy.ystart; + sx = gx.xsize; + sy = gy.ysize; } if (*x >= 0) { /* normal locations */ @@ -892,7 +1226,7 @@ struct mkroom *croom; do { if (croom) { /* handle irregular areas */ coord tmpc; - somexy(croom, &tmpc); + (void) somexy(croom, &tmpc); *x = tmpc.x; *y = tmpc.y; } else { @@ -903,7 +1237,7 @@ struct mkroom *croom; break; } while (++cpt < 100); if (cpt >= 100) { - register int xx, yy; + int xx, yy; /* last try */ for (xx = 0; xx < sx; xx++) @@ -920,45 +1254,52 @@ struct mkroom *croom; } } } -found_it: + found_it: ; if (!(humidity & ANY_LOC) && !isok(*x, *y)) { if (!(humidity & NO_LOC_WARN)) { /*warning("get_location: (%d,%d) out of bounds", *x, *y);*/ - *x = x_maze_max; - *y = y_maze_max; + *x = gx.x_maze_max; + *y = gy.y_maze_max; } else { *x = *y = -1; } } } -STATIC_OVL boolean -is_ok_location(x, y, humidity) -register schar x, y; -register int humidity; +static boolean (*is_ok_location_func)(coordxy, coordxy) = NULL; + +staticfn void +set_ok_location_func(boolean (*func)(coordxy, coordxy)) +{ + is_ok_location_func = func; +} + +staticfn boolean +is_ok_location(coordxy x, coordxy y, getloc_flags_t humidity) { - register int typ; + int typ = levl[x][y].typ; if (Is_waterlevel(&u.uz)) return TRUE; /* accept any spot */ + if (is_ok_location_func) + return is_ok_location_func(x, y); + /* TODO: Should perhaps check if wall is diggable/passwall? */ if (humidity & ANY_LOC) return TRUE; - if ((humidity & SOLID) && IS_ROCK(levl[x][y].typ)) + if ((humidity & SOLID) && IS_OBSTRUCTED(typ)) return TRUE; - if (humidity & DRY) { - typ = levl[x][y].typ; - if (typ == ROOM || typ == AIR || typ == CLOUD || typ == ICE - || typ == CORR) + if ((humidity & (DRY|SPACELOC)) && SPACE_POS(typ)) { + boolean bould = (sobj_at(BOULDER, x, y) != NULL); + + if (!bould || (bould && (humidity & SOLID))) return TRUE; } - if ((humidity & SPACELOC) && SPACE_POS(levl[x][y].typ)) - return TRUE; if ((humidity & WET) && is_pool(x, y)) return TRUE; if ((humidity & HOT) && is_lava(x, y)) @@ -966,17 +1307,21 @@ register int humidity; return FALSE; } -unpacked_coord -get_unpacked_coord(loc, defhumidity) -long loc; -int defhumidity; +boolean +pm_good_location(coordxy x, coordxy y, struct permonst *pm) +{ + return is_ok_location(x, y, pm_to_humidity(pm)); +} + +staticfn unpacked_coord +get_unpacked_coord(long loc, int defhumidity) { static unpacked_coord c; if (loc & SP_COORD_IS_RANDOM) { c.x = c.y = -1; c.is_random = 1; - c.getloc_flags = (loc & ~SP_COORD_IS_RANDOM); + c.getloc_flags = (getloc_flags_t)(loc & ~SP_COORD_IS_RANDOM); if (!c.getloc_flags) c.getloc_flags = defhumidity; } else { @@ -988,12 +1333,12 @@ int defhumidity; return c; } -STATIC_OVL void -get_location_coord(x, y, humidity, croom, crd) -schar *x, *y; -int humidity; -struct mkroom *croom; -long crd; +void +get_location_coord( + coordxy *x, coordxy *y, + int humidity, + struct mkroom *croom, + long crd) { unpacked_coord c; @@ -1002,6 +1347,7 @@ long crd; *y = c.y; get_location(x, y, c.getloc_flags | (c.is_random ? NO_LOC_WARN : 0), croom); + if (*x == -1 && *y == -1 && c.is_random) get_location(x, y, humidity, croom); } @@ -1010,11 +1356,8 @@ long crd; * Get a relative position inside a room. * negative values for x or y means RANDOM! */ - -STATIC_OVL void -get_room_loc(x, y, croom) -schar *x, *y; -struct mkroom *croom; +staticfn void +get_room_loc(coordxy *x, coordxy *y, struct mkroom *croom) { coord c; @@ -1038,14 +1381,14 @@ struct mkroom *croom; * Get a relative position inside a room. * negative values for x or y means RANDOM! */ -STATIC_OVL void -get_free_room_loc(x, y, croom, pos) -schar *x, *y; -struct mkroom *croom; -packed_coord pos; +staticfn void +get_free_room_loc( + coordxy *x, coordxy *y, + struct mkroom *croom, + packed_coord pos) { - schar try_x, try_y; - register int trycnt = 0; + coordxy try_x, try_y; + int trycnt = 0; get_location_coord(&try_x, &try_y, DRY, croom, pos); if (levl[try_x][try_y].typ != ROOM) { @@ -1061,13 +1404,18 @@ packed_coord pos; } boolean -check_room(lowx, ddx, lowy, ddy, vault) -xchar *lowx, *ddx, *lowy, *ddy; -boolean vault; +check_room( + coordxy *lowx, coordxy *ddx, + coordxy *lowy, coordxy *ddy, + boolean vault) { - register int x, y, hix = *lowx + *ddx, hiy = *lowy + *ddy; - register struct rm *lev; + int x, y, hix = *lowx + *ddx, hiy = *lowy + *ddy; + struct rm *lev; int xlim, ylim, ymax; + coordxy s_lowx, s_ddx, s_lowy, s_ddy; + + s_lowx = *lowx; s_ddx = *ddx; + s_lowy = *lowy; s_ddy = *ddy; xlim = XLIM + (vault ? 1 : 0); ylim = YLIM + (vault ? 1 : 0); @@ -1080,10 +1428,14 @@ boolean vault; hix = COLNO - 3; if (hiy > ROWNO - 3) hiy = ROWNO - 3; -chk: + chk: if (hix <= *lowx || hiy <= *lowy) return FALSE; + if (gi.in_mk_themerooms && (s_lowx != *lowx) && (s_ddx != *ddx) + && (s_lowy != *lowy) && (s_ddy != *ddy)) + return FALSE; + /* check area around room (and make room smaller if necessary) */ for (x = *lowx - xlim; x <= hix + xlim; x++) { if (x <= 0 || x >= COLNO) @@ -1096,12 +1448,14 @@ boolean vault; ymax = (ROWNO - 1); lev = &levl[x][y]; for (; y <= ymax; y++) { - if (lev++->typ) { + if (lev++->typ != STONE) { if (!vault) { debugpline2("strange area [%d,%d] in check_room.", x, y); } if (!rn2(3)) return FALSE; + if (gi.in_mk_themerooms) + return FALSE; if (x < *lowx) *lowx = x + xlim + 1; else @@ -1116,6 +1470,11 @@ boolean vault; } *ddx = hix - *lowx; *ddy = hiy - *lowy; + + if (gi.in_mk_themerooms && (s_lowx != *lowx) && (s_ddx != *ddx) + && (s_lowy != *lowy) && (s_ddy != *ddy)) + return FALSE; + return TRUE; } @@ -1124,13 +1483,13 @@ boolean vault; * This is still very incomplete... */ boolean -create_room(x, y, w, h, xal, yal, rtype, rlit) -xchar x, y; -xchar w, h; -xchar xal, yal; -xchar rtype, rlit; +create_room( + coordxy x, coordxy y, + coordxy w, coordxy h, + coordxy xal, coordxy yal, + xint16 rtype, xint16 rlit) { - xchar xabs = 0, yabs = 0; + coordxy xabs = 0, yabs = 0; int wtmp, htmp, xaltmp, yaltmp, xtmp, ytmp; NhRect *r1 = 0, r2; int trycnt = 0; @@ -1150,8 +1509,7 @@ xchar rtype, rlit; /* some other rooms may require lighting */ /* is light state random ? */ - if (rlit == -1) - rlit = (rnd(1 + abs(depth(&u.uz))) < 11 && rn2(77)) ? TRUE : FALSE; + rlit = litstate_rnd(rlit); /* * Here we will try to create a room. If some parameters are @@ -1159,7 +1517,8 @@ xchar rtype, rlit; * it up. */ do { - xchar xborder, yborder; + coordxy xborder, yborder; + wtmp = w; htmp = h; xtmp = x; @@ -1171,7 +1530,8 @@ xchar rtype, rlit; if ((xtmp < 0 && ytmp < 0 && wtmp < 0 && xaltmp < 0 && yaltmp < 0) || vault) { - xchar hx, hy, lx, ly, dx, dy; + coordxy hx, hy, lx, ly, dx, dy; + r1 = rnd_rect(); /* Get a random rectangle */ if (!r1) { /* No more free rectangles ! */ @@ -1182,9 +1542,9 @@ xchar rtype, rlit; hy = r1->hy; lx = r1->lx; ly = r1->ly; - if (vault) + if (vault) { dx = dy = 1; - else { + } else { dx = 2 + rn2((hx - lx > 28) ? 12 : 8); dy = 2 + rn2(4); if (dx * dy > 50) @@ -1200,10 +1560,11 @@ xchar rtype, rlit; + rn2(hx - (lx > 0 ? lx : 3) - dx - xborder + 1); yabs = ly + (ly > 0 ? ylim : 2) + rn2(hy - (ly > 0 ? ly : 2) - dy - yborder + 1); - if (ly == 0 && hy >= (ROWNO - 1) && (!nroom || !rn2(nroom)) + if (ly == 0 && hy >= ROWNO - 1 + && (!svn.nroom || !rn2(svn.nroom)) && (yabs + dy > ROWNO / 2)) { yabs = rn1(3, 2); - if (nroom < 4 && dy > 1) + if (svn.nroom < 4 && dy > 1) dy--; } if (!check_room(&xabs, &dx, &yabs, &dy, vault)) { @@ -1218,6 +1579,8 @@ xchar rtype, rlit; r2.hy = yabs + htmp; } else { /* Only some parameters are random */ int rndpos = 0; + coordxy dx, dy; + if (xtmp < 0 && ytmp < 0) { /* Position is RANDOM */ xtmp = rnd(5); ytmp = rnd(5); @@ -1237,12 +1600,12 @@ xchar rtype, rlit; xabs = (((xtmp - 1) * COLNO) / 5) + 1; yabs = (((ytmp - 1) * ROWNO) / 5) + 1; switch (xaltmp) { - case LEFT: + case SPLEV_LEFT: break; - case RIGHT: + case SPLEV_RIGHT: xabs += (COLNO / 5) - wtmp; break; - case CENTER: + case SPLEV_CENTER: xabs += ((COLNO / 5) - wtmp) / 2; break; } @@ -1252,7 +1615,7 @@ xchar rtype, rlit; case BOTTOM: yabs += (ROWNO / 5) - htmp; break; - case CENTER: + case SPLEV_CENTER: yabs += ((ROWNO / 5) - htmp) / 2; break; } @@ -1273,6 +1636,12 @@ xchar rtype, rlit; r2.hx = xabs + wtmp + rndpos; r2.hy = yabs + htmp + rndpos; r1 = get_rect(&r2); + dx = wtmp; + dy = htmp; + + if (r1 && !check_room(&xabs, &dx, &yabs, &dy, vault)) { + r1 = 0; + } } } while (++trycnt <= 100 && !r1); if (!r1) { /* creation of room failed ? */ @@ -1281,12 +1650,12 @@ xchar rtype, rlit; split_rects(r1, &r2); if (!vault) { - smeq[nroom] = nroom; + gs.smeq[svn.nroom] = svn.nroom; add_room(xabs, yabs, xabs + wtmp - 1, yabs + htmp - 1, rlit, rtype, FALSE); } else { - rooms[nroom].lx = xabs; - rooms[nroom].ly = yabs; + svr.rooms[svn.nroom].lx = xabs; + svr.rooms[svn.nroom].ly = yabs; } return TRUE; } @@ -1295,14 +1664,14 @@ xchar rtype, rlit; * Create a subroom in room proom at pos x,y with width w & height h. * x & y are relative to the parent room. */ -STATIC_OVL boolean -create_subroom(proom, x, y, w, h, rtype, rlit) -struct mkroom *proom; -xchar x, y; -xchar w, h; -xchar rtype, rlit; +staticfn boolean +create_subroom( + struct mkroom *proom, + coordxy x, coordxy y, + coordxy w, coordxy h, + xint16 rtype, xint16 rlit) { - xchar width, height; + coordxy width, height; width = proom->hx - proom->lx + 1; height = proom->hy - proom->ly + 1; @@ -1318,9 +1687,9 @@ xchar rtype, rlit; if (h == -1) h = rnd(height - 3); if (x == -1) - x = rnd(width - w - 1) - 1; + x = rnd(width - w); if (y == -1) - y = rnd(height - h - 1) - 1; + y = rnd(height - h); if (x == 1) x = 0; if (y == 1) @@ -1331,8 +1700,7 @@ xchar rtype, rlit; y++; if (rtype == -1) rtype = OROOM; - if (rlit == -1) - rlit = (rnd(1 + abs(depth(&u.uz))) < 11 && rn2(77)) ? TRUE : FALSE; + rlit = litstate_rnd(rlit); add_subroom(proom, proom->lx + x, proom->ly + y, proom->lx + x + w - 1, proom->ly + y + h - 1, rlit, rtype, FALSE); return TRUE; @@ -1342,17 +1710,18 @@ xchar rtype, rlit; * Create a new door in a room. * It's placed on a wall (north, south, east or west). */ -STATIC_OVL void -create_door(dd, broom) -room_door *dd; -struct mkroom *broom; +staticfn void +create_door(room_door *dd, struct mkroom *broom) { int x = 0, y = 0; - int trycnt = 0, wtry = 0; + int trycnt; if (dd->secret == -1) dd->secret = rn2(2); + if (dd->wall == W_RANDOM) + dd->wall = W_ANY; /* speeds things up in the below loop */ + if (dd->mask == -1) { /* is it a locked door, closed, or a doorway? */ if (!dd->secret) { @@ -1378,136 +1747,83 @@ struct mkroom *broom; } } - do { - register int dwall, dpos; - - dwall = dd->wall; - if (dwall == -1) /* The wall is RANDOM */ - dwall = 1 << rn2(4); - - dpos = dd->pos; + for (trycnt = 0; trycnt < 100; ++trycnt) { + int dwall = dd->wall, dpos = dd->pos; /* Convert wall and pos into an absolute coordinate! */ - wtry = rn2(4); - switch (wtry) { + switch (rn2(4)) { case 0: if (!(dwall & W_NORTH)) - goto redoloop; + continue; y = broom->ly - 1; - x = broom->lx - + ((dpos == -1) ? rn2(1 + (broom->hx - broom->lx)) : dpos); - if (IS_ROCK(levl[x][y - 1].typ)) - goto redoloop; - goto outdirloop; + x = broom->lx + ((dpos == -1) ? rn2(1 + broom->hx - broom->lx) + : dpos); + if (!isok(x, y - 1) || IS_OBSTRUCTED(levl[x][y - 1].typ)) + continue; + break; case 1: if (!(dwall & W_SOUTH)) - goto redoloop; + continue; y = broom->hy + 1; - x = broom->lx - + ((dpos == -1) ? rn2(1 + (broom->hx - broom->lx)) : dpos); - if (IS_ROCK(levl[x][y + 1].typ)) - goto redoloop; - goto outdirloop; + x = broom->lx + ((dpos == -1) ? rn2(1 + broom->hx - broom->lx) + : dpos); + if (!isok(x, y + 1) || IS_OBSTRUCTED(levl[x][y + 1].typ)) + continue; + break; case 2: if (!(dwall & W_WEST)) - goto redoloop; + continue; x = broom->lx - 1; - y = broom->ly - + ((dpos == -1) ? rn2(1 + (broom->hy - broom->ly)) : dpos); - if (IS_ROCK(levl[x - 1][y].typ)) - goto redoloop; - goto outdirloop; + y = broom->ly + ((dpos == -1) ? rn2(1 + broom->hy - broom->ly) + : dpos); + if (!isok(x - 1, y) || IS_OBSTRUCTED(levl[x - 1][y].typ)) + continue; + break; case 3: if (!(dwall & W_EAST)) - goto redoloop; + continue; x = broom->hx + 1; - y = broom->ly - + ((dpos == -1) ? rn2(1 + (broom->hy - broom->ly)) : dpos); - if (IS_ROCK(levl[x + 1][y].typ)) - goto redoloop; - goto outdirloop; + y = broom->ly + ((dpos == -1) ? rn2(1 + broom->hy - broom->ly) + : dpos); + if (!isok(x + 1, y) || IS_OBSTRUCTED(levl[x + 1][y].typ)) + continue; + break; default: - x = y = 0; - panic("create_door: No wall for door!"); - goto outdirloop; + /*NOTREACHED*/ + break; } - outdirloop: + if (okdoor(x, y)) break; - redoloop: - ; - } while (++trycnt <= 100); - if (trycnt > 100) { + } + if (trycnt >= 100) { impossible("create_door: Can't find a proper place!"); return; } - levl[x][y].typ = (dd->secret ? SDOOR : DOOR); + if (!set_levltyp(x, y, (dd->secret ? SDOOR : DOOR))) + return; levl[x][y].doormask = dd->mask; } -/* - * Create a secret door in croom on any one of the specified walls. - */ -void -create_secret_door(croom, walls) -struct mkroom *croom; -xchar walls; /* any of W_NORTH | W_SOUTH | W_EAST | W_WEST (or W_ANY) */ -{ - xchar sx, sy; /* location of the secret door */ - int count; - - for (count = 0; count < 100; count++) { - sx = rn1(croom->hx - croom->lx + 1, croom->lx); - sy = rn1(croom->hy - croom->ly + 1, croom->ly); - - switch (rn2(4)) { - case 0: /* top */ - if (!(walls & W_NORTH)) - continue; - sy = croom->ly - 1; - break; - case 1: /* bottom */ - if (!(walls & W_SOUTH)) - continue; - sy = croom->hy + 1; - break; - case 2: /* left */ - if (!(walls & W_EAST)) - continue; - sx = croom->lx - 1; - break; - case 3: /* right */ - if (!(walls & W_WEST)) - continue; - sx = croom->hx + 1; - break; - } - - if (okdoor(sx, sy)) { - levl[sx][sy].typ = SDOOR; - levl[sx][sy].doormask = D_CLOSED; - return; - } - } - - impossible("couldn't create secret door on any walls 0x%x", walls); -} - /* * Create a trap in a room. */ -STATIC_OVL void -create_trap(t, croom) -spltrap *t; -struct mkroom *croom; +staticfn void +create_trap(spltrap *t, struct mkroom *croom) { - schar x = -1, y = -1; + coordxy x = -1, y = -1; coord tm; + unsigned mktrap_flags = MKTRAP_MAZEFLAG; - if (croom) + if (t->type == VIBRATING_SQUARE) { + pick_vibrasquare_location(); + maketrap(svi.inv_pos.x, svi.inv_pos.y, VIBRATING_SQUARE); + return; + } else if (croom) { get_free_room_loc(&x, &y, croom, t->coord); - else { + } else { int trycnt = 0; + do { get_location_coord(&x, &y, DRY, croom, t->coord); } while ((levl[x][y].typ == STAIRS || levl[x][y].typ == LADDER) @@ -1516,18 +1832,24 @@ struct mkroom *croom; return; } + if (!t->spider_on_web) + mktrap_flags |= MKTRAP_NOSPIDERONWEB; + if (t->seen) + mktrap_flags |= MKTRAP_SEEN; + if (t->novictim) + mktrap_flags |= MKTRAP_NOVICTIM; + tm.x = x; tm.y = y; - mktrap(t->type, 1, (struct mkroom *) 0, &tm); + mktrap(t->type, mktrap_flags, (struct mkroom *) 0, &tm); } /* * Create a monster in a room. */ -STATIC_OVL int -noncoalignment(alignment) -aligntyp alignment; +staticfn int +noncoalignment(aligntyp alignment) { int k; @@ -1538,9 +1860,8 @@ aligntyp alignment; } /* attempt to screen out locations where a mimic-as-boulder shouldn't occur */ -STATIC_OVL boolean -m_bad_boulder_spot(x, y) -int x, y; +staticfn boolean +m_bad_boulder_spot(coordxy x, coordxy y) { struct rm *lev; @@ -1559,11 +1880,11 @@ int x, y; return FALSE; } -STATIC_OVL int -pm_to_humidity(pm) -struct permonst *pm; +staticfn int +pm_to_humidity(struct permonst *pm) { int loc = DRY; + if (!pm) return loc; if (pm->mlet == S_EEL || amphibious(pm) || is_swimmer(pm)) @@ -1572,20 +1893,41 @@ struct permonst *pm; loc |= (HOT | WET); if (passes_walls(pm) || noncorporeal(pm)) loc |= SOLID; - if (flaming(pm)) + if (likes_fire(pm)) loc |= HOT; return loc; } -STATIC_OVL void -create_monster(m, croom) -monster *m; -struct mkroom *croom; +/* + * Convert a special level alignment mask (an alignment mask with possible + * extra values/flags) to a "normal" alignment mask (no extra flags). + * + * When random: there is an 80% chance that the altar will be co-aligned. + */ +staticfn unsigned int +sp_amask_to_amask(unsigned int sp_amask) +{ + unsigned int amask; + + if (sp_amask == AM_SPLEV_CO) + amask = Align2amask(u.ualignbase[A_ORIGINAL]); + else if (sp_amask == AM_SPLEV_NONCO) + amask = Align2amask(noncoalignment(u.ualignbase[A_ORIGINAL])); + else if (sp_amask == AM_SPLEV_RANDOM) + amask = induced_align(80); + else + amask = sp_amask & AM_MASK; + + return amask; +} + +staticfn void +create_monster(monster *m, struct mkroom *croom) { struct monst *mtmp; - schar x, y; + coordxy x, y; char class; - aligntyp amask; + unsigned int amask; coord cc; struct permonst *pm; unsigned g_mvflags; @@ -1598,22 +1940,16 @@ struct mkroom *croom; if (class == MAXMCLASSES) panic("create_monster: unknown monster class '%c'", m->class); - amask = (m->align == AM_SPLEV_CO) - ? Align2amask(u.ualignbase[A_ORIGINAL]) - : (m->align == AM_SPLEV_NONCO) - ? Align2amask(noncoalignment(u.ualignbase[A_ORIGINAL])) - : (m->align <= -(MAX_REGISTERS + 1)) - ? induced_align(80) - : (m->align < 0 ? ralign[-m->align - 1] : m->align); + amask = sp_amask_to_amask(m->sp_amask); - if (!class) + if (!class) { pm = (struct permonst *) 0; - else if (m->id != NON_PM) { + } else if (m->id != NON_PM) { pm = &mons[m->id]; - g_mvflags = (unsigned) mvitals[monsndx(pm)].mvflags; + g_mvflags = (unsigned) svm.mvitals[monsndx(pm)].mvflags; if ((pm->geno & G_UNIQ) && (g_mvflags & G_EXTINCT)) return; - else if (g_mvflags & G_GONE) /* genocided or extinct */ + if (g_mvflags & G_GONE) /* genocided or extinct */ pm = (struct permonst *) 0; /* make random monster */ } else { pm = mkclass(class, G_NOGEN); @@ -1626,6 +1962,7 @@ struct mkroom *croom; if (pm) { int loc = pm_to_humidity(pm); + /* If water-liking monster, first try is without DRY */ get_location_coord(&x, &y, loc | NO_LOC_WARN, croom, m->coord); if (x == -1 && y == -1) { @@ -1640,12 +1977,15 @@ struct mkroom *croom; if (MON_AT(x, y) && enexto(&cc, x, y, pm)) x = cc.x, y = cc.y; - if (m->align != -(MAX_REGISTERS + 2)) + if (croom && !inside_room(croom, x, y)) + return; + + if (m->sp_amask != AM_SPLEV_RANDOM) mtmp = mk_roamer(pm, Amask2align(amask), x, y, m->peaceful); else if (PM_ARCHEOLOGIST <= m->id && m->id <= PM_WIZARD) mtmp = mk_mplayer(pm, x, y, FALSE); else - mtmp = makemon(pm, x, y, NO_MM_FLAGS); + mtmp = makemon(pm, x, y, m->mm_flags); if (mtmp) { x = mtmp->mx, y = mtmp->my; /* sanity precaution */ @@ -1662,7 +2002,7 @@ struct mkroom *croom; if (m->appear_as.str && ((mtmp->data->mlet == S_MIMIC) /* shapechanger (chameleons, et al, and vampires) */ - || (mtmp->cham >= LOW_PM && m->appear == M_AP_MONSTER)) + || (ismnum(mtmp->cham) && m->appear == M_AP_MONSTER)) && !Protection_from_shape_changers) { int i; @@ -1721,12 +2061,12 @@ struct mkroom *croom; break; case M_AP_MONSTER: { - int mndx; + int mndx, gender_name_var = NEUTRAL; if (!strcmpi(m->appear_as.str, "random")) mndx = select_newcham_form(mtmp); else - mndx = name_to_mon(m->appear_as.str); + mndx = name_to_mon(m->appear_as.str, &gender_name_var); if (mndx == NON_PM || (is_vampshifter(mtmp) && !validvamp(mtmp, &mndx, S_HUMAN))) { @@ -1754,16 +2094,18 @@ struct mkroom *croom; struct permonst *olddata = mtmp->data; mgender_from_permonst(mtmp, mdat); + if (gender_name_var != NEUTRAL) + mtmp->female = gender_name_var; set_mon_data(mtmp, mdat); if (emits_light(olddata) != emits_light(mtmp->data)) { /* used to give light, now doesn't, or vice versa, or light's range has changed */ if (emits_light(olddata)) - del_light_source(LS_MONSTER, (genericptr_t) mtmp); + del_light_source(LS_MONSTER, monst_to_any(mtmp)); if (emits_light(mtmp->data)) new_light_source(mtmp->mx, mtmp->my, emits_light(mtmp->data), - LS_MONSTER, (genericptr_t) mtmp); + LS_MONSTER, monst_to_any(mtmp)); } if (!mtmp->perminvis || pm_invisible(olddata)) mtmp->perminvis = pm_invisible(mdat); @@ -1780,26 +2122,16 @@ struct mkroom *croom; block_point(x, y); } - if (m->peaceful >= 0) { + mtmp->female = m->female; + if (m->peaceful > BOOL_RANDOM) { mtmp->mpeaceful = m->peaceful; /* changed mpeaceful again; have to reset malign */ set_malign(mtmp); } - if (m->asleep >= 0) { -#ifdef UNIXPC - /* optimizer bug strikes again */ - if (m->asleep) - mtmp->msleeping = 1; - else - mtmp->msleeping = 0; -#else + if (m->asleep > BOOL_RANDOM) mtmp->msleeping = m->asleep; -#endif - } if (m->seentraps) mtmp->mtrapseen = m->seentraps; - if (m->female) - mtmp->female = 1; if (m->cancelled) mtmp->mcan = 1; if (m->revived) @@ -1825,9 +2157,30 @@ struct mkroom *croom; mtmp->mflee = 1; mtmp->mfleetim = (m->fleeing % 127); } - - if (m->has_invent) { - discard_minvent(mtmp); + if (m->waiting) { + mtmp->mstrategy |= STRAT_WAITFORU; + /* if this is a vampire that got created already shifted into + bat/fog/wolf form and the special level or theme room didn't + explicitly request that, shift back to vampire */ + if (vampshifted(mtmp) && m->appear != M_AP_MONSTER) + (void) newcham(mtmp, &mons[mtmp->cham], NO_NC_FLAGS); + } + if (m->m_lev_adj) { + if (mtmp->m_lev + m->m_lev_adj > 49) + mtmp->m_lev = 49; + else if (mtmp->m_lev + m->m_lev_adj < 0) + mtmp->m_lev = 0; + else + mtmp->m_lev += m->m_lev_adj; + } + if (!(m->has_invent & DEFAULT_INVENT)) { + /* guard against someone accidentally specifying e.g. quest nemesis + * with custom inventory that lacks Bell or quest artifact but + * forgetting to flag them as receiving their default inventory */ + mdrop_special_objs(mtmp); + discard_minvent(mtmp, TRUE); + } + if (m->has_invent & CUSTOM_INVENT) { invent_carrying_monster = mtmp; } } @@ -1836,13 +2189,11 @@ struct mkroom *croom; /* * Create an object in a room. */ -STATIC_OVL void -create_object(o, croom) -object *o; -struct mkroom *croom; +staticfn struct obj * +create_object(object *o, struct mkroom *croom) { struct obj *otmp; - schar x, y; + coordxy x, y; char c; boolean named; /* has a name been supplied in level description? */ @@ -1855,11 +2206,11 @@ struct mkroom *croom; else c = 0; - if (!c) + if (!c) { otmp = mkobj_at(RANDOM_CLASS, x, y, !named); - else if (o->id != -1) + } else if (o->id != -1) { otmp = mksobj_at(o->id, x, y, TRUE, !named); - else { + } else { /* * The special levels are compiled with the default "text" object * class characters. We must convert them to the internal format. @@ -1880,19 +2231,27 @@ struct mkroom *croom; otmp->spe = (schar) o->spe; switch (o->curse_state) { - case 1: + case 1: /* blessed */ bless(otmp); - break; /* BLESSED */ - case 2: + break; + case 2: /* uncursed */ unbless(otmp); uncurse(otmp); - break; /* uncursed */ - case 3: + break; + case 3: /* cursed */ curse(otmp); - break; /* CURSED */ - default: - break; /* Otherwise it's random and we're happy - * with what mkobj gave us! */ + break; + case 4: /* not cursed */ + uncurse(otmp); + break; + case 5: /* not uncursed */ + blessorcurse(otmp, 1); + break; + case 6: /* not blessed */ + unbless(otmp); + break; + default: /* random */ + break; /* keep what mkobj gave us */ } /* corpsenm is "empty" if -1, random if -2, otherwise specific */ @@ -1904,9 +2263,13 @@ struct mkroom *croom; } /* set_corpsenm() took care of egg hatch and corpse timers */ - if (named) - otmp = oname(otmp, o->name.str); - + if (named) { + otmp = oname(otmp, o->name.str, ONAME_LEVEL_DEF); + if (otmp->otyp == SPE_NOVEL) { + /* needs to be an existing title */ + (void) lookup_novel(o->name.str, &otmp->novelidx); + } + } if (o->eroded) { if (o->eroded < 0) { otmp->oerodeproof = 1; @@ -1914,23 +2277,23 @@ struct mkroom *croom; otmp->oeroded = (o->eroded % 4); otmp->oeroded2 = ((o->eroded >> 2) % 4); } + } else { + otmp->oeroded = otmp->oeroded2 = 0; + otmp->oerodeproof = 0; } if (o->recharged) otmp->recharged = (o->recharged % 8); - if (o->locked) { - otmp->olocked = 1; + if (o->locked == 0 || o->locked == 1) { + otmp->olocked = o->locked; } else if (o->broken) { otmp->obroken = 1; otmp->olocked = 0; /* obj generation may set */ } if (o->trapped == 0 || o->trapped == 1) otmp->otrapped = o->trapped; - if (o->greased) - otmp->greased = 1; -#ifdef INVISIBLE_OBJECTS - if (o->invis) - otmp->oinvis = 1; -#endif + if (o->trapped && (o->tknown == 0 || o->tknown == 1)) + otmp->tknown = o->tknown; + otmp->greased = o->greased ? 1 : 0; if (o->quan > 0 && objects[otmp->otyp].oc_merge) { otmp->quan = o->quan; @@ -1938,7 +2301,7 @@ struct mkroom *croom; } /* contents (of a container or monster's inventory) */ - if (o->containment & SP_OBJ_CONTENT) { + if (o->containment & SP_OBJ_CONTENT || invent_carrying_monster) { if (!container_idx) { if (!invent_carrying_monster) { /*impossible("create_object: no container");*/ @@ -1952,19 +2315,27 @@ struct mkroom *croom; ; /* ['otmp' remains on floor] */ } else { remove_object(otmp); - (void) mpickobj(invent_carrying_monster, otmp); + if (otmp->otyp == SADDLE && can_saddle(invent_carrying_monster)) + put_saddle_on_mon(otmp, invent_carrying_monster); + else + (void) mpickobj(invent_carrying_monster, otmp); } } else { struct obj *cobj = container_obj[container_idx - 1]; remove_object(otmp); if (cobj) { - (void) add_to_container(cobj, otmp); + otmp = add_to_container(cobj, otmp); cobj->owt = weight(cobj); } else { obj_extract_self(otmp); + /* uncreate a random artifact created in a container */ + /* FIXME: it could be intentional rather than random */ + if (otmp->oartifact) + artifact_exists(otmp, safe_oname(otmp), FALSE, + ONAME_NO_FLAGS); /* flags don't matter */ obfree(otmp, NULL); - return; + return NULL; } } } @@ -1994,9 +2365,9 @@ struct mkroom *croom; */ for (wastyp = otmp->corpsenm; i < 1000; i++, wastyp = rndmonnum()) { /* makemon without rndmonst() might create a group */ - was = makemon(&mons[wastyp], 0, 0, MM_NOCOUNTBIRTH); + was = makemon(&mons[wastyp], 0, 0, MM_NOCOUNTBIRTH|MM_NOMSG); if (was) { - if (!resists_ston(was)) { + if (!resists_ston(was) && !poly_when_stoned(&mons[wastyp])) { (void) propagate(wastyp, TRUE, FALSE); break; } @@ -2017,67 +2388,67 @@ struct mkroom *croom; } } - if (o->id != -1) { + if (o->achievement) { static const char prize_warning[] = "multiple prizes on %s level"; - /* if this is a specific item of the right type and it is being - created on the right level, flag it as the designated item - used to detect a special achievement (to whit, reaching and - exploring the target level, although the exploration part - might be short-circuited if a monster brings object to hero) */ if (Is_mineend_level(&u.uz)) { - if (otmp->otyp == iflags.mines_prize_type) { - if (!mines_prize_count++) { - /* Note: the first luckstone on lev will become the prize - even if its not the explicit one, but random */ - otmp->record_achieve_special = MINES_PRIZE; - /* prevent stacking; cleared when achievement is recorded */ - otmp->nomerge = 1; - } + if (!svc.context.achieveo.mines_prize_oid) { + svc.context.achieveo.mines_prize_oid = otmp->o_id; + svc.context.achieveo.mines_prize_otyp = otmp->otyp; + /* prevent stacking; cleared when achievement is recorded; + will be reset in addinv_core1() */ + otmp->nomerge = 1; + } else { + impossible(prize_warning, "mines end"); } } else if (Is_sokoend_level(&u.uz)) { - if (otmp->otyp == iflags.soko_prize_type1) { - otmp->record_achieve_special = SOKO_PRIZE1; - otmp->nomerge = 1; /* redundant; Sokoban prizes don't stack */ - if (++soko_prize_count > 1) - impossible(prize_warning, "sokoban end"); - } else if (otmp->otyp == iflags.soko_prize_type2) { - otmp->record_achieve_special = SOKO_PRIZE2; - otmp->nomerge = 1; /* redundant; Sokoban prizes don't stack */ - if (++soko_prize_count > 1) - impossible(prize_warning, "sokoban end"); + if (!svc.context.achieveo.soko_prize_oid) { + svc.context.achieveo.soko_prize_oid = otmp->o_id; + svc.context.achieveo.soko_prize_otyp = otmp->otyp; + otmp->nomerge = 1; /* redundant; Sokoban prizes don't stack; + * will be reset in addinv_core1() */ + } else { + impossible(prize_warning, "sokoban end"); } + } else if (!iflags.lua_testing) { + char lbuf[QBUFSZ]; + + (void) describe_level(lbuf, 1 | 2); + impossible("create_object: unknown achievement (%s\"%s\")", + lbuf, simpleonames(otmp)); } } - stackobj(otmp); + if (!(o->containment & SP_OBJ_CONTENT)) { + stackobj(otmp); - if (o->lit) { - begin_burn(otmp, FALSE); - } + if (o->lit) + begin_burn(otmp, FALSE); - if (o->buried) { - boolean dealloced; + if (o->buried) { + boolean dealloced; - (void) bury_an_obj(otmp, &dealloced); - if (dealloced && container_idx) { - container_obj[container_idx - 1] = NULL; + (void) bury_an_obj(otmp, &dealloced); + if (dealloced) { + if (container_idx) + container_obj[container_idx - 1] = NULL; + otmp = NULL; + } } } + return otmp; } /* * Create an altar in a room. */ -STATIC_OVL void -create_altar(a, croom) -altar *a; -struct mkroom *croom; +staticfn void +create_altar(altar *a, struct mkroom *croom) { - schar sproom, x = -1, y = -1; - aligntyp amask; + schar sproom; + coordxy x = -1, y = -1; + unsigned int amask; boolean croom_is_temple = TRUE; - int oldtyp; if (croom) { get_free_room_loc(&x, &y, croom, a->coord); @@ -2086,32 +2457,17 @@ struct mkroom *croom; } else { get_location_coord(&x, &y, DRY, croom, a->coord); if ((sproom = (schar) *in_rooms(x, y, TEMPLE)) != 0) - croom = &rooms[sproom - ROOMOFFSET]; + croom = &svr.rooms[sproom - ROOMOFFSET]; else croom_is_temple = FALSE; } /* check for existing features */ - oldtyp = levl[x][y].typ; - if (oldtyp == STAIRS || oldtyp == LADDER) + if (!set_levltyp(x, y, ALTAR)) return; - /* Is the alignment random ? - * If so, it's an 80% chance that the altar will be co-aligned. - * - * The alignment is encoded as amask values instead of alignment - * values to avoid conflicting with the rest of the encoding, - * shared by many other parts of the special level code. - */ - amask = (a->align == AM_SPLEV_CO) - ? Align2amask(u.ualignbase[A_ORIGINAL]) - : (a->align == AM_SPLEV_NONCO) - ? Align2amask(noncoalignment(u.ualignbase[A_ORIGINAL])) - : (a->align == -(MAX_REGISTERS + 1)) - ? induced_align(80) - : (a->align < 0 ? ralign[-a->align - 1] : a->align); - - levl[x][y].typ = ALTAR; + amask = sp_amask_to_amask(a->sp_amask); + levl[x][y].altarmask = amask; if (a->shrine < 0) @@ -2123,56 +2479,32 @@ struct mkroom *croom; if (a->shrine) { /* Is it a shrine or sanctum? */ priestini(&u.uz, croom, x, y, (a->shrine > 1)); levl[x][y].altarmask |= AM_SHRINE; - level.flags.has_temple = TRUE; + if (a->shrine == 2) /* high altar or sanctum */ + levl[x][y].altarmask |= AM_SANCTUM; + svl.level.flags.has_temple = TRUE; } } -void -replace_terrain(terr, croom) -replaceterrain *terr; -struct mkroom *croom; -{ - schar x, y, x1, y1, x2, y2; - - if (terr->toter >= MAX_TYPE) - return; - - x1 = terr->x1; - y1 = terr->y1; - get_location(&x1, &y1, ANY_LOC, croom); - - x2 = terr->x2; - y2 = terr->y2; - get_location(&x2, &y2, ANY_LOC, croom); - - for (x = max(x1, 0); x <= min(x2, COLNO - 1); x++) - for (y = max(y1, 0); y <= min(y2, ROWNO - 1); y++) - if (levl[x][y].typ == terr->fromter && rn2(100) < terr->chance) { - SET_TYPLIT(x, y, terr->toter, terr->tolit); - } -} - /* * Search for a door in a room on a specified wall. */ -STATIC_OVL boolean -search_door(croom, x, y, wall, cnt) -struct mkroom *croom; -xchar *x, *y; -xchar wall; -int cnt; +staticfn boolean +search_door( + struct mkroom *croom, + coordxy *x, coordxy *y, + xint16 wall, int cnt) { int dx, dy; int xx, yy; switch (wall) { - case W_NORTH: + case W_SOUTH: dy = 0; dx = 1; xx = croom->lx; yy = croom->hy + 1; break; - case W_SOUTH: + case W_NORTH: dy = 0; dx = 1; xx = croom->lx; @@ -2191,9 +2523,8 @@ int cnt; yy = croom->ly; break; default: - dx = dy = xx = yy = 0; panic("search_door: Bad wall!"); - break; + /*NOTREACHED*/ } while (xx <= croom->hx + 1 && yy <= croom->hy + 1) { if (IS_DOOR(levl[xx][yy].typ) || levl[xx][yy].typ == SDOOR) { @@ -2209,18 +2540,24 @@ int cnt; } /* - * Dig a corridor between two points. + * Dig a corridor between two points, using terrain ftyp. + * if nxcor is TRUE, he corridor may be blocked by a boulder, + * or just end without reaching the destination. + * if not null, npoints has the number of map locations used */ boolean -dig_corridor(org, dest, nxcor, ftyp, btyp) -coord *org, *dest; -boolean nxcor; -schar ftyp, btyp; +dig_corridor( + coord *org, coord *dest, + int *npoints, + boolean nxcor, + schar ftyp, schar btyp) { int dx = 0, dy = 0, dix, diy, cct; struct rm *crm; int tx, ty, xx, yy; + if (npoints) + *npoints = 0; xx = org->x; yy = org->y; tx = dest->x; @@ -2256,12 +2593,16 @@ schar ftyp, btyp; crm = &levl[xx][yy]; if (crm->typ == btyp) { - if (ftyp != CORR || rn2(100)) { + if (ftyp == CORR && maybe_sdoor(100)) { + if (npoints) + (*npoints)++; + crm->typ = SCORR; + } else { + if (npoints) + (*npoints)++; crm->typ = ftyp; if (nxcor && !rn2(50)) (void) mksobj_at(BOULDER, xx, yy, TRUE, FALSE); - } else { - crm->typ = SCORR; } } else if (crm->typ != ftyp && crm->typ != SCORR) { /* strange ... */ @@ -2272,15 +2613,15 @@ schar ftyp, btyp; dix = abs(xx - tx); diy = abs(yy - ty); - if ((dix > diy) && diy && !rn2(dix-diy+1)) { + if ((dix > diy) && diy && !rn2(dix - diy + 1)) { dix = 0; - } else if ((diy > dix) && dix && !rn2(diy-dix+1)) { + } else if ((diy > dix) && dix && !rn2(diy - dix + 1)) { diy = 0; } /* do we have to change direction ? */ if (dy && dix > diy) { - register int ddx = (xx > tx) ? -1 : 1; + int ddx = (xx > tx) ? -1 : 1; crm = &levl[xx + ddx][yy]; if (crm->typ == btyp || crm->typ == ftyp || crm->typ == SCORR) { @@ -2289,7 +2630,7 @@ schar ftyp, btyp; continue; } } else if (dx && diy > dix) { - register int ddy = (yy > ty) ? -1 : 1; + int ddy = (yy > ty) ? -1 : 1; crm = &levl[xx][yy + ddy]; if (crm->typ == btyp || crm->typ == ftyp || crm->typ == SCORR) { @@ -2321,74 +2662,35 @@ schar ftyp, btyp; return TRUE; } -/* - * Disgusting hack: since special levels have their rooms filled before - * sorting the rooms, we have to re-arrange the speed values upstairs_room - * and dnstairs_room after the rooms have been sorted. On normal levels, - * stairs don't get created until _after_ sorting takes place. - */ -STATIC_OVL void -fix_stair_rooms() -{ - int i; - struct mkroom *croom; - - if (xdnstair - && !((dnstairs_room->lx <= xdnstair && xdnstair <= dnstairs_room->hx) - && (dnstairs_room->ly <= ydnstair - && ydnstair <= dnstairs_room->hy))) { - for (i = 0; i < nroom; i++) { - croom = &rooms[i]; - if ((croom->lx <= xdnstair && xdnstair <= croom->hx) - && (croom->ly <= ydnstair && ydnstair <= croom->hy)) { - dnstairs_room = croom; - break; - } - } - if (i == nroom) - panic("Couldn't find dnstair room in fix_stair_rooms!"); - } - if (xupstair - && !((upstairs_room->lx <= xupstair && xupstair <= upstairs_room->hx) - && (upstairs_room->ly <= yupstair - && yupstair <= upstairs_room->hy))) { - for (i = 0; i < nroom; i++) { - croom = &rooms[i]; - if ((croom->lx <= xupstair && xupstair <= croom->hx) - && (croom->ly <= yupstair && yupstair <= croom->hy)) { - upstairs_room = croom; - break; - } - } - if (i == nroom) - panic("Couldn't find upstair room in fix_stair_rooms!"); - } -} - /* * Corridors always start from a door. But it can end anywhere... * Basically we search for door coordinates or for endpoints coordinates * (from a distance). */ -STATIC_OVL void -create_corridor(c) -corridor *c; +staticfn void +create_corridor(corridor *c) { coord org, dest; if (c->src.room == -1) { - fix_stair_rooms(); makecorridors(); /*makecorridors(c->src.door);*/ return; } - if (!search_door(&rooms[c->src.room], &org.x, &org.y, c->src.wall, + /* Safety railings - if there's ever a case where des.corridor() needs + * to be called with src/destwall="random", that logic first needs to be + * implemented in search_door. */ + if (c->src.wall == W_ANY || c->src.wall == W_RANDOM + || c->dest.wall == W_ANY || c->dest.wall == W_RANDOM) { + impossible("create_corridor to/from a random wall"); + return; + } + if (!search_door(&svr.rooms[c->src.room], &org.x, &org.y, c->src.wall, c->src.door)) return; - if (c->dest.room != -1) { - if (!search_door(&rooms[c->dest.room], &dest.x, &dest.y, c->dest.wall, - c->dest.door)) + if (!search_door(&svr.rooms[c->dest.room], + &dest.x, &dest.y, c->dest.wall, c->dest.door)) return; switch (c->src.wall) { case W_NORTH: @@ -2418,7 +2720,7 @@ corridor *c; dest.x++; break; } - (void) dig_corridor(&org, &dest, FALSE, CORR, STONE); + (void) dig_corridor(&org, &dest, NULL, FALSE, CORR, STONE); } } @@ -2426,20 +2728,31 @@ corridor *c; * Fill a room (shop, zoo, etc...) with appropriate stuff. */ void -fill_room(croom, prefilled) -struct mkroom *croom; -boolean prefilled; +fill_special_room(struct mkroom *croom) { - if (!croom || croom->rtype == OROOM) + int i; + + if (!croom) + return; + + /* First recurse into subrooms. We don't want to block an ordinary room + * with a special subroom from having the subroom filled, or an unfilled + * outer room preventing a special subroom from being filled. */ + for (i = 0; i < croom->nsubrooms; ++i) { + fill_special_room(croom->sbrooms[i]); + } + + if (croom->rtype == OROOM || croom->rtype == THEMEROOM + || croom->needfill == FILL_NONE) return; - if (!prefilled) { + if (croom->needfill == FILL_NORMAL) { int x, y; /* Shop ? */ if (croom->rtype >= SHOPBASE) { stock_room(croom->rtype - SHOPBASE, croom); - level.flags.has_shop = TRUE; + svl.level.flags.has_shop = TRUE; return; } @@ -2464,46 +2777,44 @@ boolean prefilled; } switch (croom->rtype) { case VAULT: - level.flags.has_vault = TRUE; + svl.level.flags.has_vault = TRUE; break; case ZOO: - level.flags.has_zoo = TRUE; + svl.level.flags.has_zoo = TRUE; break; case COURT: - level.flags.has_court = TRUE; + svl.level.flags.has_court = TRUE; break; case MORGUE: - level.flags.has_morgue = TRUE; + svl.level.flags.has_morgue = TRUE; break; case BEEHIVE: - level.flags.has_beehive = TRUE; + svl.level.flags.has_beehive = TRUE; break; case BARRACKS: - level.flags.has_barracks = TRUE; + svl.level.flags.has_barracks = TRUE; break; case TEMPLE: - level.flags.has_temple = TRUE; + svl.level.flags.has_temple = TRUE; break; case SWAMP: - level.flags.has_swamp = TRUE; + svl.level.flags.has_swamp = TRUE; break; } } -struct mkroom * -build_room(r, mkr) -room *r; -struct mkroom *mkr; +staticfn struct mkroom * +build_room(room *r, struct mkroom *mkr) { boolean okroom; struct mkroom *aroom; - xchar rtype = (!r->chance || rn2(100) < r->chance) ? r->rtype : OROOM; + xint16 rtype = (!r->chance || rn2(100) < r->chance) ? r->rtype : OROOM; if (mkr) { - aroom = &subrooms[nsubroom]; + aroom = &gs.subrooms[gn.nsubroom]; okroom = create_subroom(mkr, r->x, r->y, r->w, r->h, rtype, r->rlit); } else { - aroom = &rooms[nroom]; + aroom = &svr.rooms[svn.nroom]; okroom = create_room(r->x, r->y, r->w, r->h, r->xalign, r->yalign, rtype, r->rlit); } @@ -2514,7 +2825,7 @@ struct mkroom *mkr; #else topologize(aroom); /* set roomno */ #endif - aroom->needfill = r->filled; + aroom->needfill = r->needfill; aroom->needjoining = r->joined; return aroom; } @@ -2524,14 +2835,13 @@ struct mkroom *mkr; /* * set lighting in a region that will not become a room. */ -STATIC_OVL void -light_region(tmpregion) -region *tmpregion; -{ - register boolean litstate = tmpregion->rlit ? 1 : 0; - register int hiy = tmpregion->y2; - register int x, y; - register struct rm *lev; +staticfn void +light_region(region *tmpregion) +{ + boolean litstate = tmpregion->rlit ? 1 : 0; + int hiy = tmpregion->y2; + int x, y; + struct rm *lev; int lowy = tmpregion->y1; int lowx = tmpregion->x1, hix = tmpregion->x2; @@ -2545,18 +2855,16 @@ region *tmpregion; for (x = lowx; x <= hix; x++) { lev = &levl[x][lowy]; for (y = lowy; y <= hiy; y++) { - if (lev->typ != LAVAPOOL) /* this overrides normal lighting */ - lev->lit = litstate; + lev->lit = IS_LAVA(lev->typ) ? 1 : litstate; lev++; } } } -STATIC_OVL void -wallify_map(x1, y1, x2, y2) -int x1, y1, x2, y2; +void +wallify_map(coordxy x1, coordxy y1, coordxy x2, coordxy y2) { - int x, y, xx, yy, lo_xx, lo_yy, hi_xx, hi_yy; + coordxy x, y, xx, yy, lo_xx, lo_yy, hi_xx, hi_yy; y1 = max(y1, 0); x1 = max(x1, 1); @@ -2588,25 +2896,23 @@ int x1, y1, x2, y2; * We want a place not 'touched' by the loader. That is, a place in * the maze outside every part of the special level. */ -STATIC_OVL void -maze1xy(m, humidity) -coord *m; -int humidity; +staticfn void +maze1xy(coord *m, int humidity) { - register int x, y, tryct = 2000; + int x, y, tryct = 2000; /* tryct: normally it won't take more than ten or so tries due to the circumstances under which we'll be called, but the `humidity' screening might drastically change the chances */ do { - x = rn1(x_maze_max - 3, 3); - y = rn1(y_maze_max - 3, 3); + x = rn1(gx.x_maze_max - 3, 3); + y = rn1(gy.y_maze_max - 3, 3); if (--tryct < 0) break; /* give up */ } while (!(x % 2) || !(y % 2) || SpLev_Map[x][y] - || !is_ok_location((schar) x, (schar) y, humidity)); + || !is_ok_location((coordxy) x, (coordxy) y, humidity)); - m->x = (xchar) x, m->y = (xchar) y; + m->x = (coordxy) x, m->y = (coordxy) y; } /* @@ -2616,18 +2922,18 @@ int humidity; * Makes the number of traps, monsters, etc. proportional * to the size of the maze. */ -STATIC_OVL void -fill_empty_maze() +staticfn void +fill_empty_maze(void) { int mapcountmax, mapcount, mapfact; - xchar x, y; + coordxy x, y; coord mm; - mapcountmax = mapcount = (x_maze_max - 2) * (y_maze_max - 2); + mapcountmax = mapcount = (gx.x_maze_max - 2) * (gy.y_maze_max - 2); mapcountmax = mapcountmax / 2; - for (x = 2; x < x_maze_max; x++) - for (y = 0; y < y_maze_max; y++) + for (x = 2; x < gx.x_maze_max; x++) + for (y = 0; y < gy.y_maze_max; y++) if (SpLev_Map[x][y]) mapcount--; @@ -2639,7 +2945,12 @@ fill_empty_maze() TRUE); } for (x = rnd((int) (12 * mapfact) / 100); x; x--) { + struct trap *ttmp; + maze1xy(&mm, DRY); + if ((ttmp = t_at(mm.x, mm.y)) != 0 + && (is_pit(ttmp->ttyp) || is_hole(ttmp->ttyp))) + continue; (void) mksobj_at(BOULDER, mm.x, mm.y, TRUE, FALSE); } for (x = rn2(2); x; x--) { @@ -2667,107 +2978,8 @@ fill_empty_maze() } } -/* - * special level loader - */ -STATIC_OVL boolean -sp_level_loader(fd, lvl) -dlb *fd; -sp_lev *lvl; -{ - long n_opcode = 0; - struct opvar *opdat; - int opcode; - - Fread((genericptr_t) & (lvl->n_opcodes), 1, sizeof(lvl->n_opcodes), fd); - lvl->opcodes = (_opcode *) alloc(sizeof(_opcode) * (lvl->n_opcodes)); - - while (n_opcode < lvl->n_opcodes) { - Fread((genericptr_t) &lvl->opcodes[n_opcode].opcode, 1, - sizeof(lvl->opcodes[n_opcode].opcode), fd); - opcode = lvl->opcodes[n_opcode].opcode; - - opdat = NULL; - - if (opcode < SPO_NULL || opcode >= MAX_SP_OPCODES) - panic("sp_level_loader: impossible opcode %i.", opcode); - - if (opcode == SPO_PUSH) { - int nsize; - struct opvar *ov = (struct opvar *) alloc(sizeof(struct opvar)); - - opdat = ov; - ov->spovartyp = SPO_NULL; - ov->vardata.l = 0; - Fread((genericptr_t) & (ov->spovartyp), 1, sizeof(ov->spovartyp), - fd); - - switch (ov->spovartyp) { - case SPOVAR_NULL: - break; - case SPOVAR_COORD: - case SPOVAR_REGION: - case SPOVAR_MAPCHAR: - case SPOVAR_MONST: - case SPOVAR_OBJ: - case SPOVAR_INT: - Fread((genericptr_t) & (ov->vardata.l), 1, - sizeof(ov->vardata.l), fd); - break; - case SPOVAR_VARIABLE: - case SPOVAR_STRING: - case SPOVAR_SEL: { - char *opd; - - Fread((genericptr_t) &nsize, 1, sizeof(nsize), fd); - opd = (char *) alloc(nsize + 1); - - if (nsize) - Fread(opd, 1, nsize, fd); - opd[nsize] = 0; - ov->vardata.str = opd; - break; - } - default: - panic("sp_level_loader: unknown opvar type %i", - ov->spovartyp); - } - } - - lvl->opcodes[n_opcode].opdat = opdat; - n_opcode++; - } /*while*/ - - return TRUE; -} - -/* Frees the memory allocated for special level creation structs */ -STATIC_OVL boolean -sp_level_free(lvl) -sp_lev *lvl; -{ - static const char nhFunc[] = "sp_level_free"; - long n_opcode = 0; - - while (n_opcode < lvl->n_opcodes) { - int opcode = lvl->opcodes[n_opcode].opcode; - struct opvar *opdat = lvl->opcodes[n_opcode].opdat; - - if (opcode < SPO_NULL || opcode >= MAX_SP_OPCODES) - panic("sp_level_free: unknown opcode %i", opcode); - - if (opdat) - opvar_free(opdat); - n_opcode++; - } - Free(lvl->opcodes); - lvl->opcodes = NULL; - return TRUE; -} - -void -splev_initlev(linit) -lev_init *linit; +staticfn void +splev_initlev(lev_init *linit) { switch (linit->init_style) { default: @@ -2776,217 +2988,245 @@ lev_init *linit; case LVLINIT_NONE: break; case LVLINIT_SOLIDFILL: - if (linit->lit == -1) + if (linit->lit == BOOL_RANDOM) linit->lit = rn2(2); lvlfill_solid(linit->filling, linit->lit); break; case LVLINIT_MAZEGRID: - lvlfill_maze_grid(2, 0, x_maze_max, y_maze_max, linit->filling); + lvlfill_maze_grid(2, 0, gx.x_maze_max, gy.y_maze_max, linit->bg); + break; + case LVLINIT_MAZE: + create_maze(linit->corrwid, linit->wallthick, linit->rm_deadends); break; case LVLINIT_ROGUE: makeroguerooms(); break; case LVLINIT_MINES: - if (linit->lit == -1) + if (linit->lit == BOOL_RANDOM) linit->lit = rn2(2); if (linit->filling > -1) lvlfill_solid(linit->filling, 0); linit->icedpools = icedpools; mkmap(linit); break; + case LVLINIT_SWAMP: + if (linit->lit == BOOL_RANDOM) + linit->lit = rn2(2); + lvlfill_swamp(linit->fg, linit->bg, linit->lit); + break; } } -struct sp_frame * -frame_new(execptr) -long execptr; +#if 0 +staticfn long +sp_code_jmpaddr(long curpos, long jmpaddr) { - struct sp_frame *frame = - (struct sp_frame *) alloc(sizeof(struct sp_frame)); - - frame->next = NULL; - frame->variables = NULL; - frame->n_opcode = execptr; - frame->stack = (struct splevstack *) alloc(sizeof(struct splevstack)); - splev_stack_init(frame->stack); - return frame; + return (curpos + jmpaddr); } +#endif -void -frame_del(frame) -struct sp_frame *frame; -{ - if (!frame) - return; - if (frame->stack) { - splev_stack_done(frame->stack); - frame->stack = NULL; - } - if (frame->variables) { - variable_list_del(frame->variables); - frame->variables = NULL; - } - Free(frame); -} -void -spo_frame_push(coder) -struct sp_coder *coder; +/*ARGUSED*/ +staticfn void +spo_end_moninvent(void) { - struct sp_frame *tmpframe = frame_new(coder->frame->n_opcode); - - tmpframe->next = coder->frame; - coder->frame = tmpframe; + if (invent_carrying_monster) + m_dowear(invent_carrying_monster, TRUE); + invent_carrying_monster = NULL; } -void -spo_frame_pop(coder) -struct sp_coder *coder; +/*ARGUSED*/ +staticfn void +spo_pop_container(void) { - if (coder->frame && coder->frame->next) { - struct sp_frame *tmpframe = coder->frame->next; - - frame_del(coder->frame); - coder->frame = tmpframe; - coder->stack = coder->frame->stack; + if (container_idx > 0) { + container_idx--; + container_obj[container_idx] = NULL; } } -long -sp_code_jmpaddr(curpos, jmpaddr) -long curpos, jmpaddr; +/* push a table on lua stack: {width=wid, height=hei} */ +staticfn void +l_push_wid_hei_table(lua_State *L, int wid, int hei) { - return (curpos + jmpaddr); + lua_newtable(L); + nhl_add_table_entry_int(L, "width", wid); + nhl_add_table_entry_int(L, "height", hei); } -void -spo_call(coder) -struct sp_coder *coder; +/* push a table on lua stack containing room data */ +staticfn void +l_push_mkroom_table(lua_State *L, struct mkroom *tmpr) { - static const char nhFunc[] = "spo_call"; - struct opvar *addr; - struct opvar *params; - struct sp_frame *tmpframe; + lua_newtable(L); + nhl_add_table_entry_int(L, "width", 1 + (tmpr->hx - tmpr->lx)); + nhl_add_table_entry_int(L, "height", 1 + (tmpr->hy - tmpr->ly)); + nhl_add_table_entry_region(L, "region", tmpr->lx, tmpr->ly, + tmpr->hx, tmpr->hy); + nhl_add_table_entry_bool(L, "lit", (boolean) tmpr->rlit); + nhl_add_table_entry_bool(L, "irregular", tmpr->irregular); + nhl_add_table_entry_bool(L, "needjoining", tmpr->needjoining); + nhl_add_table_entry_str(L, "type", get_mkroom_name(tmpr->rtype)); +} - if (!OV_pop_i(addr) || !OV_pop_i(params)) - return; - if (OV_i(params) < 0) - return; +DISABLE_WARNING_UNREACHABLE_CODE + +/* message("What a strange feeling!"); */ +int +lspo_message(lua_State *L) +{ + char *levmsg; + int old_n, n; + const char *msg; - tmpframe = frame_new(sp_code_jmpaddr(coder->frame->n_opcode, - OV_i(addr) - 1)); + int argc = lua_gettop(L); - while (OV_i(params)-- > 0) { - splev_stack_push(tmpframe->stack, splev_stack_getdat_any(coder)); + if (argc < 1) { + nhl_error(L, "Wrong parameters"); + /*NOTREACHED*/ + return 0; } - splev_stack_reverse(tmpframe->stack); - /* push a frame */ - tmpframe->next = coder->frame; - coder->frame = tmpframe; + create_des_coder(); - opvar_free(addr); - opvar_free(params); -} + msg = luaL_checkstring(L, 1); -void -spo_return(coder) -struct sp_coder *coder; -{ - static const char nhFunc[] = "spo_return"; - struct opvar *params; + old_n = gl.lev_message ? (Strlen(gl.lev_message) + 1) : 0; + n = Strlen(msg); - if (!coder->frame || !coder->frame->next) - panic("return: no frame."); - if (!OV_pop_i(params)) - return; - if (OV_i(params) < 0) - return; + levmsg = (char *) alloc(old_n + n + 1); + if (old_n) + levmsg[old_n - 1] = '\n'; + if (gl.lev_message) + (void) memcpy((genericptr_t) levmsg, (genericptr_t) gl.lev_message, + old_n - 1); + (void) memcpy((genericptr_t) &levmsg[old_n], msg, n); + levmsg[old_n + n] = '\0'; + Free(gl.lev_message); + gl.lev_message = levmsg; - while (OV_i(params)-- > 0) { - splev_stack_push(coder->frame->next->stack, - splev_stack_pop(coder->stack)); - } + return 0; /* number of results */ +} - /* pop the frame */ - if (coder->frame->next) { - struct sp_frame *tmpframe = coder->frame->next; - frame_del(coder->frame); - coder->frame = tmpframe; - coder->stack = coder->frame->stack; - } +RESTORE_WARNING_UNREACHABLE_CODE + +staticfn int +get_table_align(lua_State *L) +{ + static const char *const gtaligns[] = { + "noalign", "law", "neutral", "chaos", + "coaligned", "noncoaligned", "random", NULL + }; + static const int aligns2i[] = { + AM_NONE, AM_LAWFUL, AM_NEUTRAL, AM_CHAOTIC, + AM_SPLEV_CO, AM_SPLEV_NONCO, AM_SPLEV_RANDOM, 0 + }; + + int a = aligns2i[get_table_option(L, "align", "random", gtaligns)]; - opvar_free(params); + return a; } -/*ARGUSED*/ -void -spo_end_moninvent(coder) -struct sp_coder *coder UNUSED; +staticfn int +get_table_monclass(lua_State *L) { - if (invent_carrying_monster) - m_dowear(invent_carrying_monster, TRUE); - invent_carrying_monster = NULL; + char *s = get_table_str_opt(L, "class", NULL); + int ret = -1; + + if (s && strlen(s) == 1) + ret = (int) *s; + Free(s); + return ret; } -/*ARGUSED*/ -void -spo_pop_container(coder) -struct sp_coder *coder UNUSED; +staticfn int +find_montype( + lua_State *L UNUSED, + const char *s, + int *mgender) { - if (container_idx > 0) { - container_idx--; - container_obj[container_idx] = NULL; + int i, mgend = NEUTRAL; + + i = name_to_monplus(s, (const char **) 0, &mgend); + if (i >= LOW_PM && i < NUMMONS) { + if (is_male(&mons[i]) || is_female(&mons[i])) + mgend = is_female(&mons[i]) ? FEMALE : MALE; + else + mgend = (mgend == FEMALE) ? FEMALE + : (mgend == MALE) ? MALE : rn2(2); + if (mgender) + *mgender = mgend; + return i; } + if (mgender) + *mgender = NEUTRAL; + return NON_PM; } -void -spo_message(coder) -struct sp_coder *coder; +staticfn int +get_table_montype(lua_State *L, int *mgender) { - static const char nhFunc[] = "spo_message"; - struct opvar *op; - char *msg, *levmsg; - int old_n, n; + char *s = get_table_str_opt(L, "id", NULL); + int ret = NON_PM; - if (!OV_pop_s(op)) - return; - msg = OV_s(op); - if (!msg) - return; + if (s) { + ret = find_montype(L, s, mgender); + Free(s); + if (ret == NON_PM) + nhl_error(L, "Unknown monster id"); + } + return ret; +} - old_n = lev_message ? (strlen(lev_message) + 1) : 0; - n = strlen(msg); +/* Get x and y values from a table (which the caller has already checked for + * the existence of), handling both a table with x= and y= specified and a + * table with coord= specified. + * Returns absolute rather than map-relative coordinates; the caller of this + * function must decide if it wants to interpret the coordinates as + * map-relative and adjust accordingly. */ +staticfn void +get_table_xy_or_coord( + lua_State *L, + lua_Integer *x, + lua_Integer *y) +{ + lua_Integer mx = get_table_int_opt(L, "x", -1); + lua_Integer my = get_table_int_opt(L, "y", -1); + + if (mx == -1 && my == -1) { + lua_getfield(L, 1, "coord"); + (void) get_coord(L, -1, &mx, &my); + lua_pop(L, 1); + } - levmsg = (char *) alloc(old_n + n + 1); - if (old_n) - levmsg[old_n - 1] = '\n'; - if (lev_message) - (void) memcpy((genericptr_t) levmsg, (genericptr_t) lev_message, - old_n - 1); - (void) memcpy((genericptr_t) &levmsg[old_n], msg, n); - levmsg[old_n + n] = '\0'; - Free(lev_message); - lev_message = levmsg; - opvar_free(op); + *x = mx; + *y = my; } -void -spo_monster(coder) -struct sp_coder *coder; +/* monster(); */ +/* monster("wood nymph"); */ +/* monster("D"); */ +/* monster("giant eel",11,06); */ +/* monster("hill giant", {08,06}); */ +/* monster({ id = "giant mimic", appear_as = "obj:boulder" }); */ +/* monster({ class = "H", peaceful = 0 }); */ +int +lspo_monster(lua_State *L) { - static const char nhFunc[] = "spo_monster"; - int nparams = 0; - struct opvar *varparam; - struct opvar *id, *mcoord, *has_inv; + int argc = lua_gettop(L); monster tmpmons; + lua_Integer mx = -1, my = -1; + int mgend = NEUTRAL; + char *mappear = NULL; + + create_des_coder(); tmpmons.peaceful = -1; tmpmons.asleep = -1; - tmpmons.name.str = (char *) 0; + tmpmons.name.str = NULL; tmpmons.appear = 0; tmpmons.appear_as.str = (char *) 0; - tmpmons.align = -MAX_REGISTERS - 2; + tmpmons.sp_amask = AM_SPLEV_RANDOM; tmpmons.female = 0; tmpmons.invis = 0; tmpmons.cancelled = 0; @@ -2998,1287 +3238,1416 @@ struct sp_coder *coder; tmpmons.stunned = 0; tmpmons.confused = 0; tmpmons.seentraps = 0; - tmpmons.has_invent = 0; + tmpmons.has_invent = DEFAULT_INVENT; + tmpmons.waiting = 0; + tmpmons.mm_flags = NO_MM_FLAGS; + tmpmons.m_lev_adj = 0; - if (!OV_pop_i(has_inv)) - return; + if (argc == 1 && lua_type(L, 1) == LUA_TSTRING) { + const char *paramstr = luaL_checkstring(L, 1); - if (!OV_pop_i(varparam)) - return; + if (strlen(paramstr) == 1) { + tmpmons.class = *paramstr; + tmpmons.id = NON_PM; + } else { + tmpmons.class = -1; + tmpmons.id = find_montype(L, paramstr, &mgend); + tmpmons.female = (mgend == FEMALE) ? FEMALE + : (mgend == MALE) ? MALE : rn2(2); + } + } else if (argc == 2 && lua_type(L, 1) == LUA_TSTRING + && lua_type(L, 2) == LUA_TTABLE) { + const char *paramstr = luaL_checkstring(L, 1); - while ((nparams++ < (SP_M_V_END + 1)) && (OV_typ(varparam) == SPOVAR_INT) - && (OV_i(varparam) >= 0) && (OV_i(varparam) < SP_M_V_END)) { - struct opvar *parm = NULL; + (void) get_coord(L, 2, &mx, &my); - OV_pop(parm); - switch (OV_i(varparam)) { - case SP_M_V_NAME: - if ((OV_typ(parm) == SPOVAR_STRING) && !tmpmons.name.str) - tmpmons.name.str = dupstr(OV_s(parm)); - break; - case SP_M_V_APPEAR: - if ((OV_typ(parm) == SPOVAR_INT) && !tmpmons.appear_as.str) { - tmpmons.appear = OV_i(parm); - opvar_free(parm); - OV_pop(parm); - tmpmons.appear_as.str = dupstr(OV_s(parm)); + if (strlen(paramstr) == 1) { + tmpmons.class = *paramstr; + tmpmons.id = NON_PM; + } else { + tmpmons.class = -1; + tmpmons.id = find_montype(L, paramstr, &mgend); + tmpmons.female = (mgend == FEMALE) ? FEMALE + : (mgend == MALE) ? MALE : rn2(2); + } + + } else if (argc == 3) { + const char *paramstr = luaL_checkstring(L, 1); + + mx = luaL_checkinteger(L, 2); + my = luaL_checkinteger(L, 3); + + if (strlen(paramstr) == 1) { + tmpmons.class = *paramstr; + tmpmons.id = NON_PM; + } else { + tmpmons.class = -1; + tmpmons.id = find_montype(L, paramstr, &mgend); + tmpmons.female = (mgend == FEMALE) ? FEMALE + : (mgend == MALE) ? MALE : rn2(2); + } + } else { + int keep_default_invent = -1; /* -1 = unspecified */ + lcheck_param_table(L); + + tmpmons.peaceful = get_table_boolean_opt(L, "peaceful", BOOL_RANDOM); + tmpmons.asleep = get_table_boolean_opt(L, "asleep", BOOL_RANDOM); + tmpmons.name.str = get_table_str_opt(L, "name", NULL); + tmpmons.appear = 0; + tmpmons.appear_as.str = (char *) 0; + tmpmons.sp_amask = get_table_align(L); + tmpmons.female = get_table_boolean_opt(L, "female", BOOL_RANDOM); + tmpmons.invis = get_table_boolean_opt(L, "invisible", FALSE); + tmpmons.cancelled = get_table_boolean_opt(L, "cancelled", FALSE); + tmpmons.revived = get_table_boolean_opt(L, "revived", FALSE); + tmpmons.avenge = get_table_boolean_opt(L, "avenge", FALSE); + tmpmons.fleeing = get_table_int_opt(L, "fleeing", 0); + tmpmons.blinded = get_table_int_opt(L, "blinded", 0); + tmpmons.paralyzed = get_table_int_opt(L, "paralyzed", 0); + tmpmons.stunned = get_table_boolean_opt(L, "stunned", FALSE); + tmpmons.confused = get_table_boolean_opt(L, "confused", FALSE); + tmpmons.waiting = get_table_boolean_opt(L, "waiting", FALSE); + tmpmons.m_lev_adj = get_table_int_opt(L, "m_lev_adj", 0); + tmpmons.seentraps = 0; /* TODO: list of trap names to bitfield */ + keep_default_invent = + get_table_boolean_opt(L, "keep_default_invent", -1); + + if (!get_table_boolean_opt(L, "tail", TRUE)) + tmpmons.mm_flags |= MM_NOTAIL; + if (!get_table_boolean_opt(L, "group", TRUE)) + tmpmons.mm_flags |= MM_NOGRP; + if (get_table_boolean_opt(L, "adjacentok", FALSE)) + tmpmons.mm_flags |= MM_ADJACENTOK; + if (get_table_boolean_opt(L, "ignorewater", FALSE)) + tmpmons.mm_flags |= MM_IGNOREWATER; + if (!get_table_boolean_opt(L, "countbirth", TRUE)) + tmpmons.mm_flags |= MM_NOCOUNTBIRTH; + + mappear = get_table_str_opt(L, "appear_as", NULL); + if (mappear) { + if (!strncmp("obj:", mappear, 4)) { + tmpmons.appear = M_AP_OBJECT; + } else if (!strncmp("mon:", mappear, 4)) { + tmpmons.appear = M_AP_MONSTER; + } else if (!strncmp("ter:", mappear, 4)) { + tmpmons.appear = M_AP_FURNITURE; + } else { + nhl_error(L, "Unknown appear_as type"); } - break; - case SP_M_V_ASLEEP: - if (OV_typ(parm) == SPOVAR_INT) - tmpmons.asleep = OV_i(parm); - break; - case SP_M_V_ALIGN: - if (OV_typ(parm) == SPOVAR_INT) - tmpmons.align = OV_i(parm); - break; - case SP_M_V_PEACEFUL: - if (OV_typ(parm) == SPOVAR_INT) - tmpmons.peaceful = OV_i(parm); - break; - case SP_M_V_FEMALE: - if (OV_typ(parm) == SPOVAR_INT) - tmpmons.female = OV_i(parm); - break; - case SP_M_V_INVIS: - if (OV_typ(parm) == SPOVAR_INT) - tmpmons.invis = OV_i(parm); - break; - case SP_M_V_CANCELLED: - if (OV_typ(parm) == SPOVAR_INT) - tmpmons.cancelled = OV_i(parm); - break; - case SP_M_V_REVIVED: - if (OV_typ(parm) == SPOVAR_INT) - tmpmons.revived = OV_i(parm); - break; - case SP_M_V_AVENGE: - if (OV_typ(parm) == SPOVAR_INT) - tmpmons.avenge = OV_i(parm); - break; - case SP_M_V_FLEEING: - if (OV_typ(parm) == SPOVAR_INT) - tmpmons.fleeing = OV_i(parm); - break; - case SP_M_V_BLINDED: - if (OV_typ(parm) == SPOVAR_INT) - tmpmons.blinded = OV_i(parm); - break; - case SP_M_V_PARALYZED: - if (OV_typ(parm) == SPOVAR_INT) - tmpmons.paralyzed = OV_i(parm); - break; - case SP_M_V_STUNNED: - if (OV_typ(parm) == SPOVAR_INT) - tmpmons.stunned = OV_i(parm); - break; - case SP_M_V_CONFUSED: - if (OV_typ(parm) == SPOVAR_INT) - tmpmons.confused = OV_i(parm); - break; - case SP_M_V_SEENTRAPS: - if (OV_typ(parm) == SPOVAR_INT) - tmpmons.seentraps = OV_i(parm); - break; - case SP_M_V_END: - nparams = SP_M_V_END + 1; - break; - default: - impossible("MONSTER with unknown variable param type!"); - break; + tmpmons.appear_as.str = dupstr(&mappear[4]); + Free(mappear); } - opvar_free(parm); - if (OV_i(varparam) != SP_M_V_END) { - opvar_free(varparam); - OV_pop(varparam); + + get_table_xy_or_coord(L, &mx, &my); + + tmpmons.id = get_table_montype(L, &mgend); + /* get_table_montype will return a random gender if the species isn't + * all-male or all-female; if the level designer specified a certain + * gender, override that random one now, unless it *is* a one-gender + * species, in which case don't override (don't permit creation of a + * male nymph or female Nazgul, etc.) */ + if (mgend != NEUTRAL + && (tmpmons.female == BOOL_RANDOM || is_female(&mons[tmpmons.id]) + || is_male(&mons[tmpmons.id]))) + tmpmons.female = mgend; + /* safety net - if find_montype did not find a gender for this species + * (should cause a lua error anyway) */ + if (tmpmons.female == BOOL_RANDOM) + tmpmons.female = 0; + + tmpmons.class = get_table_monclass(L); + + lua_getfield(L, 1, "inventory"); + if (!lua_isnil(L, -1)) { + /* overwrite DEFAULT_INVENT - most times inventory is specified, + * the monster should not get its species' default inventory. Only + * provide it if explicitly requested. */ + tmpmons.has_invent = CUSTOM_INVENT; + if (keep_default_invent == TRUE) + tmpmons.has_invent |= DEFAULT_INVENT; + } + else { + /* if keep_default_invent was not specified (-1), keep has_invent as + * DEFAULT_INVENT and provide the species' default inventory. + * But if it was explicitly set to false, provide *no* inventory. */ + if (keep_default_invent == FALSE) + tmpmons.has_invent = NO_INVENT; } } - if (!OV_pop_c(mcoord)) - panic("no monster coord?"); + if (mx == -1 && my == -1) + tmpmons.coord = SP_COORD_PACK_RANDOM(0); + else + tmpmons.coord = SP_COORD_PACK(mx, my); - if (!OV_pop_typ(id, SPOVAR_MONST)) - panic("no mon type"); + if (tmpmons.id != NON_PM && tmpmons.class == -1) + tmpmons.class = monsym(&mons[tmpmons.id]); - tmpmons.id = SP_MONST_PM(OV_i(id)); - tmpmons.class = SP_MONST_CLASS(OV_i(id)); - tmpmons.coord = OV_i(mcoord); - tmpmons.has_invent = OV_i(has_inv); + create_monster(&tmpmons, gc.coder->croom); - create_monster(&tmpmons, coder->croom); + if ((tmpmons.has_invent & CUSTOM_INVENT) + && lua_type(L, -1) == LUA_TFUNCTION) { + lua_remove(L, -2); + nhl_pcall_handle(L, 0, 0, "lspo_monster", NHLpa_panic); + spo_end_moninvent(); + } else + lua_pop(L, 1); Free(tmpmons.name.str); Free(tmpmons.appear_as.str); - opvar_free(id); - opvar_free(mcoord); - opvar_free(has_inv); - opvar_free(varparam); + + return 0; } -void -spo_object(coder) -struct sp_coder *coder; +DISABLE_WARNING_UNREACHABLE_CODE + +/* the hash key 'name' is an integer or "random", + or if not existent, also return rndval */ +staticfn lua_Integer +get_table_int_or_random(lua_State *L, const char *name, int rndval) +{ + lua_Integer ret; + char buf[BUFSZ]; + + lua_getfield(L, 1, name); + if (lua_type(L, -1) == LUA_TNIL) { + lua_pop(L, 1); + return rndval; + } + if (!lua_isnumber(L, -1)) { + const char *tmp = lua_tostring(L, -1); + + if (tmp && !strcmpi("random", tmp)) { + lua_pop(L, 1); + return rndval; + } + Sprintf(buf, "Expected integer or \"random\" for \"%s\", got ", name); + if (tmp) + Sprintf(eos(buf), "\"%s\"", tmp); + else + Strcat(buf, ""); + nhl_error(L, buf); + /*NOTREACHED*/ + lua_pop(L, 1); + return 0; + } + ret = luaL_optinteger(L, -1, rndval); + lua_pop(L, 1); + return ret; +} + +RESTORE_WARNING_UNREACHABLE_CODE + +staticfn int +get_table_buc(lua_State *L) +{ + static const char *const bucs[] = { + "random", "blessed", "uncursed", "cursed", + "not-cursed", "not-uncursed", "not-blessed", NULL, + }; + static const int bucs2i[] = { 0, 1, 2, 3, 4, 5, 6, 0 }; + int curse_state = bucs2i[get_table_option(L, "buc", "random", bucs)]; + + return curse_state; +} + +int +get_table_objclass(lua_State *L) +{ + char *s = get_table_str_opt(L, "class", NULL); + int ret = -1; + + if (s && strlen(s) == 1) + ret = (int) *s; + Free(s); + return ret; +} + +/* find object otyp by text s (optionally considering oclass) */ +staticfn int +find_objtype(lua_State *L, const char *s, char oclass) +{ + if (s && *s) { + int i; + const char *objname; + char class = def_char_to_objclass(oclass); + + /* In objects.h, some item classes are defined without prefixes + (such as "scroll of ") in their names, making some names (such + as "teleportation") ambiguous. Get the object class if it is + specified, and only return an object of the matching class. */ + static struct objclasspfx { + const char *prefix; + char class; + } class_prefixes[] = { + { "ring of ", RING_CLASS }, + { "potion of ", POTION_CLASS }, + { "scroll of ", SCROLL_CLASS }, + { "spellbook of ", SPBOOK_CLASS }, + { "wand of ", WAND_CLASS }, + { NULL, 0 } + }; + + if (class == MAXOCLASSES) + class = 0; + + if (strstri(s, " of ")) { + for (i = 0; class_prefixes[i].prefix; i++) { + const char *p = class_prefixes[i].prefix; + + if (!strncmpi(s, p, strlen(p))) { + class = class_prefixes[i].class; + s = s + strlen(p); + break; + } + } + } + + /* find by object name */ + for (i = 0; i < NUM_OBJECTS; i++) { + objname = OBJ_NAME(objects[i]); + if ((!class || class == objects[i].oc_class) + && objname && !strcmpi(s, objname)) + return i; + } + + /* + * FIXME: + * If the file specifies "orange potion", the actual object + * description is just "orange" and won't match. [There's a + * reason that wish handling is insanely complicated.] And + * even if that gets fixed, if the file specifies "gray stone" + * it will start matching but would always pick the first one. + * + * "orange potion" is an unlikely thing to have in a special + * level description but "gray stone" is not.... + */ + + /* find by object description */ + for (i = 0; i < NUM_OBJECTS; i++) { + objname = OBJ_DESCR(objects[i]); + if (objname && !strcmpi(s, objname)) + return i; + } + + nhl_error(L, "Unknown object id"); + } + return STRANGE_OBJECT; +} + +int +get_table_objtype(lua_State *L) { - static const char nhFunc[] = "spo_object"; + char *s = get_table_str_opt(L, "id", NULL); + char oclass = get_table_objclass(L); + int ret = find_objtype(L, s, oclass); + + Free(s); + return ret; +} + +/* object(); */ +/* object("sack"); */ +/* object("scimitar", 6,7); */ +/* object("scimitar", {6,7}); */ +/* object({ class = "%" }); */ +/* object({ id = "boulder", x = 03, y = 12}); */ +/* object({ id = "boulder", coord = {03,12} }); */ +int +lspo_object(lua_State *L) +{ + static object zeroobject = { + { 0 }, /* Str_or_len name */ + 0, /* corpsenm */ + 0, 0, /* id, spe */ + 0, /* coord */ + 0, 0, /* coordxy x,y */ + 0, 0, /* class, containment */ + 0, /* curse_state */ + 0, /* quan */ + 0, /* buried */ + 0, /* lit */ + 0, 0, 0, 0, 0, /* eroded, locked, trapped, tknown, recharged */ + 0, 0, 0, 0, /* invis, greased, broken, achievement */ + }; +#if 0 int nparams = 0; +#endif long quancnt; - struct opvar *varparam; - struct opvar *id, *containment; object tmpobj; + lua_Integer ox = -1, oy = -1; + int argc = lua_gettop(L); + int maybe_contents = 0; + struct obj *otmp = NULL; - tmpobj.spe = -127; - tmpobj.curse_state = -1; - tmpobj.corpsenm = NON_PM; + create_des_coder(); + + tmpobj = zeroobject; tmpobj.name.str = (char *) 0; + tmpobj.spe = -127; tmpobj.quan = -1; - tmpobj.buried = 0; - tmpobj.lit = 0; - tmpobj.eroded = 0; - tmpobj.locked = 0; tmpobj.trapped = -1; - tmpobj.recharged = 0; - tmpobj.invis = 0; - tmpobj.greased = 0; - tmpobj.broken = 0; - tmpobj.coord = SP_COORD_PACK_RANDOM(0); + tmpobj.tknown = -1; + tmpobj.locked = -1; + tmpobj.corpsenm = NON_PM; - if (!OV_pop_i(containment)) - return; + if (argc == 1 && lua_type(L, 1) == LUA_TSTRING) { + const char *paramstr = luaL_checkstring(L, 1); - if (!OV_pop_i(varparam)) - return; + if (strlen(paramstr) == 1) { + tmpobj.class = *paramstr; + tmpobj.id = STRANGE_OBJECT; + } else { + tmpobj.class = -1; + tmpobj.id = find_objtype(L, paramstr, -1); + } + } else if (argc == 2 && lua_type(L, 1) == LUA_TSTRING + && lua_type(L, 2) == LUA_TTABLE) { + const char *paramstr = luaL_checkstring(L, 1); - while ((nparams++ < (SP_O_V_END + 1)) && (OV_typ(varparam) == SPOVAR_INT) - && (OV_i(varparam) >= 0) && (OV_i(varparam) < SP_O_V_END)) { - struct opvar *parm; + (void) get_coord(L, 2, &ox, &oy); - OV_pop(parm); - switch (OV_i(varparam)) { - case SP_O_V_NAME: - if ((OV_typ(parm) == SPOVAR_STRING) && !tmpobj.name.str) - tmpobj.name.str = dupstr(OV_s(parm)); - break; - case SP_O_V_CORPSENM: - if (OV_typ(parm) == SPOVAR_MONST) { - char monclass = SP_MONST_CLASS(OV_i(parm)); - int monid = SP_MONST_PM(OV_i(parm)); - - if (monid >= LOW_PM && monid < NUMMONS) { - tmpobj.corpsenm = monid; - break; /* we're done! */ - } else { - struct permonst *pm = (struct permonst *) 0; + if (strlen(paramstr) == 1) { + tmpobj.class = *paramstr; + tmpobj.id = STRANGE_OBJECT; + } else { + tmpobj.class = -1; + tmpobj.id = find_objtype(L, paramstr, -1); + } + } else if (argc == 3 && lua_type(L, 2) == LUA_TNUMBER + && lua_type(L, 3) == LUA_TNUMBER) { + const char *paramstr = luaL_checkstring(L, 1); - if (def_char_to_monclass(monclass) != MAXMCLASSES) { - pm = mkclass(def_char_to_monclass(monclass), G_NOGEN); - } else { - pm = rndmonst(); + ox = luaL_checkinteger(L, 2); + oy = luaL_checkinteger(L, 3); + + if (strlen(paramstr) == 1) { + tmpobj.class = *paramstr; + tmpobj.id = STRANGE_OBJECT; + } else { + tmpobj.class = -1; + tmpobj.id = find_objtype(L, paramstr, -1); + } + } else { + lcheck_param_table(L); + + tmpobj.spe = get_table_int_or_random(L, "spe", -127); + tmpobj.curse_state = get_table_buc(L); + tmpobj.corpsenm = NON_PM; + tmpobj.name.str = get_table_str_opt(L, "name", (char *) 0); + tmpobj.quan = get_table_int_or_random(L, "quantity", -1); + tmpobj.buried = get_table_boolean_opt(L, "buried", 0); + tmpobj.lit = get_table_boolean_opt(L, "lit", 0); + tmpobj.eroded = get_table_int_opt(L, "eroded", 0); + tmpobj.locked = get_table_boolean_opt(L, "locked", -1); + tmpobj.trapped = get_table_boolean_opt(L, "trapped", -1); + tmpobj.tknown = get_table_boolean_opt(L, "trap_known", -1); + tmpobj.recharged = get_table_int_opt(L, "recharged", 0); + tmpobj.greased = get_table_boolean_opt(L, "greased", 0); + tmpobj.broken = get_table_boolean_opt(L, "broken", 0); + tmpobj.achievement = get_table_boolean_opt(L, "achievement", 0); + + get_table_xy_or_coord(L, &ox, &oy); + + tmpobj.id = get_table_objtype(L); + tmpobj.class = get_table_objclass(L); + maybe_contents = 1; + } + + if (ox == -1 && oy == -1) + tmpobj.coord = SP_COORD_PACK_RANDOM(0); + else + tmpobj.coord = SP_COORD_PACK(ox, oy); + + if (tmpobj.class == -1 && tmpobj.id > STRANGE_OBJECT) + tmpobj.class = objects[tmpobj.id].oc_class; + else if (tmpobj.class > -1 && tmpobj.id == STRANGE_OBJECT) + tmpobj.id = -1; + + if (tmpobj.id == STATUE || tmpobj.id == EGG + || tmpobj.id == CORPSE || tmpobj.id == TIN + || tmpobj.id == FIGURINE) { + struct permonst *pm = NULL; + boolean nonpmobj = FALSE; + int i; + char *montype = get_table_str_opt(L, "montype", NULL); + + if (montype) { + if ((tmpobj.id == TIN && (!strcmpi(montype, "spinach") + /* id="tin",montype="empty" produces an empty tin */ + || !strcmpi(montype, "empty"))) + /* id="egg",montype="empty" produces a generic, unhatchable + egg rather than an "empty egg" */ + || (tmpobj.id == EGG && !strcmpi(montype, "empty"))) { + tmpobj.corpsenm = NON_PM; + tmpobj.spe = !strcmpi(montype, "spinach") ? 1 : 0; + nonpmobj = TRUE; + } else if (strlen(montype) == 1 + && def_char_to_monclass(*montype) != MAXMCLASSES) { + pm = mkclass(def_char_to_monclass(*montype), + G_NOGEN | G_IGNORE); + } else { + for (i = LOW_PM; i < NUMMONS; i++) + if (!strcmpi(mons[i].pmnames[NEUTRAL], montype) + || (mons[i].pmnames[MALE] != 0 + && !strcmpi(mons[i].pmnames[MALE], montype)) + || (mons[i].pmnames[FEMALE] != 0 + && !strcmpi(mons[i].pmnames[FEMALE], montype))) { + pm = &mons[i]; + break; } - if (pm) - tmpobj.corpsenm = monsndx(pm); - } } - break; - case SP_O_V_CURSE: - if (OV_typ(parm) == SPOVAR_INT) - tmpobj.curse_state = OV_i(parm); - break; - case SP_O_V_SPE: - if (OV_typ(parm) == SPOVAR_INT) - tmpobj.spe = OV_i(parm); - break; - case SP_O_V_QUAN: - if (OV_typ(parm) == SPOVAR_INT) - tmpobj.quan = OV_i(parm); - break; - case SP_O_V_BURIED: - if (OV_typ(parm) == SPOVAR_INT) - tmpobj.buried = OV_i(parm); - break; - case SP_O_V_LIT: - if (OV_typ(parm) == SPOVAR_INT) - tmpobj.lit = OV_i(parm); - break; - case SP_O_V_ERODED: - if (OV_typ(parm) == SPOVAR_INT) - tmpobj.eroded = OV_i(parm); - break; - case SP_O_V_LOCKED: - if (OV_typ(parm) == SPOVAR_INT) - tmpobj.locked = OV_i(parm); - break; - case SP_O_V_TRAPPED: - if (OV_typ(parm) == SPOVAR_INT) - tmpobj.trapped = OV_i(parm); - break; - case SP_O_V_RECHARGED: - if (OV_typ(parm) == SPOVAR_INT) - tmpobj.recharged = OV_i(parm); - break; - case SP_O_V_INVIS: - if (OV_typ(parm) == SPOVAR_INT) - tmpobj.invis = OV_i(parm); - break; - case SP_O_V_GREASED: - if (OV_typ(parm) == SPOVAR_INT) - tmpobj.greased = OV_i(parm); - break; - case SP_O_V_BROKEN: - if (OV_typ(parm) == SPOVAR_INT) - tmpobj.broken = OV_i(parm); - break; - case SP_O_V_COORD: - if (OV_typ(parm) != SPOVAR_COORD) - panic("no coord for obj?"); - tmpobj.coord = OV_i(parm); - break; - case SP_O_V_END: - nparams = SP_O_V_END + 1; - break; - default: - impossible("OBJECT with unknown variable param type!"); - break; + free((genericptr_t) montype); + if (pm) + tmpobj.corpsenm = monsndx(pm); + else if (!nonpmobj) + nhl_error(L, "Unknown montype"); } - opvar_free(parm); - if (OV_i(varparam) != SP_O_V_END) { - opvar_free(varparam); - OV_pop(varparam); + if (tmpobj.id == STATUE || tmpobj.id == CORPSE) { + int lflags = 0; + + if (get_table_boolean_opt(L, "historic", 0)) + lflags |= CORPSTAT_HISTORIC; + if (get_table_boolean_opt(L, "male", 0)) + lflags |= CORPSTAT_MALE; + if (get_table_boolean_opt(L, "female", 0)) + lflags |= CORPSTAT_FEMALE; + tmpobj.spe = lflags; + } else if (tmpobj.id == EGG) { + tmpobj.spe = get_table_boolean_opt(L, "laid_by_you", 0) ? 1 : 0; + } else if (!nonpmobj) { /* tmpobj.spe is already set for nonpmobj */ + tmpobj.spe = 0; } } - if (!OV_pop_typ(id, SPOVAR_OBJ)) - panic("no obj type"); + quancnt = (tmpobj.id > STRANGE_OBJECT) ? tmpobj.quan : 0; - tmpobj.id = SP_OBJ_TYP(OV_i(id)); - tmpobj.class = SP_OBJ_CLASS(OV_i(id)); - tmpobj.containment = OV_i(containment); + if (container_idx) + tmpobj.containment |= SP_OBJ_CONTENT; - quancnt = (tmpobj.id > STRANGE_OBJECT) ? tmpobj.quan : 0; + if (maybe_contents) { + lua_getfield(L, 1, "contents"); + if (!lua_isnil(L, -1)) + tmpobj.containment |= SP_OBJ_CONTAINER; + } do { - create_object(&tmpobj, coder->croom); + otmp = create_object(&tmpobj, gc.coder->croom); quancnt--; } while ((quancnt > 0) && ((tmpobj.id > STRANGE_OBJECT) && !objects[tmpobj.id].oc_merge)); + if (lua_type(L, -1) == LUA_TFUNCTION) { + lua_remove(L, -2); + nhl_push_obj(L, otmp); + nhl_pcall_handle(L, 1, 0, "lspo_object", NHLpa_panic); + } else + lua_pop(L, 1); + + if ((tmpobj.containment & SP_OBJ_CONTAINER) != 0) + spo_pop_container(); + Free(tmpobj.name.str); - opvar_free(varparam); - opvar_free(id); - opvar_free(containment); + + nhl_push_obj(L, otmp); + + return 1; } -void -spo_level_flags(coder) -struct sp_coder *coder; +/* level_flags("noteleport", "mazelevel", ... ); */ +int +lspo_level_flags(lua_State *L) { - static const char nhFunc[] = "spo_level_flags"; - struct opvar *flagdata; - long lflags; + int argc = lua_gettop(L); + int i; - if (!OV_pop_i(flagdata)) - return; - lflags = OV_i(flagdata); - - if (lflags & NOTELEPORT) - level.flags.noteleport = 1; - if (lflags & HARDFLOOR) - level.flags.hardfloor = 1; - if (lflags & NOMMAP) - level.flags.nommap = 1; - if (lflags & SHORTSIGHTED) - level.flags.shortsighted = 1; - if (lflags & ARBOREAL) - level.flags.arboreal = 1; - if (lflags & MAZELEVEL) - level.flags.is_maze_lev = 1; - if (lflags & PREMAPPED) - coder->premapped = TRUE; - if (lflags & SHROUD) - level.flags.hero_memory = 0; - if (lflags & GRAVEYARD) - level.flags.graveyard = 1; - if (lflags & ICEDPOOLS) - icedpools = TRUE; - if (lflags & SOLIDIFY) - coder->solidify = TRUE; - if (lflags & CORRMAZE) - level.flags.corrmaze = TRUE; - if (lflags & CHECK_INACCESSIBLES) - coder->check_inaccessibles = TRUE; - - opvar_free(flagdata); + create_des_coder(); + + if (argc < 1) + nhl_error(L, "expected string params"); + + for (i = 1; i <= argc; i++) { + const char *s = luaL_checkstring(L, i); + + if (!strcmpi(s, "noteleport")) + svl.level.flags.noteleport = 1; + else if (!strcmpi(s, "hardfloor")) + svl.level.flags.hardfloor = 1; + else if (!strcmpi(s, "nommap")) + svl.level.flags.nommap = 1; + else if (!strcmpi(s, "shortsighted")) + svl.level.flags.shortsighted = 1; + else if (!strcmpi(s, "arboreal")) + svl.level.flags.arboreal = 1; + else if (!strcmpi(s, "mazelevel")) + svl.level.flags.is_maze_lev = 1; + else if (!strcmpi(s, "shroud")) + svl.level.flags.hero_memory = 1; + else if (!strcmpi(s, "graveyard")) + svl.level.flags.graveyard = 1; + else if (!strcmpi(s, "icedpools")) + icedpools = 1; + else if (!strcmpi(s, "corrmaze")) + svl.level.flags.corrmaze = 1; + else if (!strcmpi(s, "premapped")) + gc.coder->premapped = 1; + else if (!strcmpi(s, "solidify")) + gc.coder->solidify = 1; + else if (!strcmpi(s, "sokoban")) + Sokoban = 1; /* svl.level.flags.sokoban_rules */ + else if (!strcmpi(s, "inaccessibles")) + gc.coder->check_inaccessibles = 1; + else if (!strcmpi(s, "noflipx")) + gc.coder->allow_flips &= ~2; + else if (!strcmpi(s, "noflipy")) + gc.coder->allow_flips &= ~1; + else if (!strcmpi(s, "noflip")) + gc.coder->allow_flips = 0; + else if (!strcmpi(s, "temperate")) + svl.level.flags.temperature = 0; + else if (!strcmpi(s, "hot")) + svl.level.flags.temperature = 1; + else if (!strcmpi(s, "cold")) + svl.level.flags.temperature = -1; + else if (!strcmpi(s, "nomongen")) + svl.level.flags.rndmongen = 0; + else if (!strcmpi(s, "nodeathdrops")) + svl.level.flags.deathdrops = 0; + else if (!strcmpi(s, "noautosearch")) + svl.level.flags.noautosearch = 1; + else if (!strcmpi(s, "fumaroles")) + svl.level.flags.fumaroles = 1; + else if (!strcmpi(s, "stormy")) + svl.level.flags.stormy = 1; + else { + char buf[BUFSZ]; + + Sprintf(buf, "Unknown level flag %s", s); + nhl_error(L, buf); + } + } + + return 0; } -void -spo_initlevel(coder) -struct sp_coder *coder; +/* level_init({ style = "solidfill", fg = " " }); */ +/* level_init({ style = "mines", fg = ".", bg = "}", + smoothed=true, joined=true, lit=0 }) */ +int +lspo_level_init(lua_State *L) { - static const char nhFunc[] = "spo_initlevel"; + static const char *const initstyles[] = { + "solidfill", "mazegrid", "maze", "rogue", "mines", "swamp", NULL + }; + static const int initstyles2i[] = { + LVLINIT_SOLIDFILL, LVLINIT_MAZEGRID, LVLINIT_MAZE, LVLINIT_ROGUE, + LVLINIT_MINES, LVLINIT_SWAMP, 0 + }; lev_init init_lev; - struct opvar *init_style, *fg, *bg, *smoothed, *joined, *lit, *walled, - *filling; - if (!OV_pop_i(fg) || !OV_pop_i(bg) || !OV_pop_i(smoothed) - || !OV_pop_i(joined) || !OV_pop_i(lit) || !OV_pop_i(walled) - || !OV_pop_i(filling) || !OV_pop_i(init_style)) - return; + create_des_coder(); + + lcheck_param_table(L); splev_init_present = TRUE; - init_lev.init_style = OV_i(init_style); - init_lev.fg = OV_i(fg); - init_lev.bg = OV_i(bg); - init_lev.smoothed = OV_i(smoothed); - init_lev.joined = OV_i(joined); - init_lev.lit = OV_i(lit); - init_lev.walled = OV_i(walled); - init_lev.filling = OV_i(filling); + init_lev.init_style + = initstyles2i[get_table_option(L, "style", "solidfill", initstyles)]; + init_lev.fg = get_table_mapchr_opt(L, "fg", ROOM); + init_lev.bg = get_table_mapchr_opt(L, "bg", INVALID_TYPE); + init_lev.smoothed = get_table_boolean_opt(L, "smoothed", FALSE); + init_lev.joined = get_table_boolean_opt(L, "joined", FALSE); + init_lev.lit = get_table_boolean_opt(L, "lit", BOOL_RANDOM); + init_lev.walled = get_table_boolean_opt(L, "walled", FALSE); + init_lev.filling = get_table_mapchr_opt(L, "filling", init_lev.fg); + init_lev.corrwid = get_table_int_opt(L, "corrwid", -1); + init_lev.wallthick = get_table_int_opt(L, "wallthick", -1); + init_lev.rm_deadends = !get_table_boolean_opt(L, "deadends", TRUE); + + gc.coder->lvl_is_joined = init_lev.joined; - coder->lvl_is_joined = OV_i(joined); + if (init_lev.bg == INVALID_TYPE) + init_lev.bg = (init_lev.init_style == LVLINIT_SWAMP) ? MOAT : STONE; splev_initlev(&init_lev); - opvar_free(init_style); - opvar_free(fg); - opvar_free(bg); - opvar_free(smoothed); - opvar_free(joined); - opvar_free(lit); - opvar_free(walled); - opvar_free(filling); + return 0; } -void -spo_engraving(coder) -struct sp_coder *coder; +/* engraving({ x = 1, y = 1, type="burn", text="Foo" }); */ +/* engraving({ coord={1, 1}, type="burn", text="Foo" }); */ +/* engraving({x,y}, "engrave", "Foo"); */ +int +lspo_engraving(lua_State *L) +{ + static const char *const engrtypes[] = { + "dust", "engrave", "burn", "mark", "blood", NULL + }; + static const int engrtypes2i[] = { + DUST, ENGRAVE, BURN, MARK, ENGR_BLOOD, 0 + }; + int etyp = DUST; + char *txt = (char *) 0; + long ecoord; + coordxy x = -1, y = -1; + int argc = lua_gettop(L); + boolean guardobjs = FALSE; + boolean wipeout = TRUE; + struct engr *ep; + + create_des_coder(); + + if (argc == 1) { + lua_Integer ex, ey; + lcheck_param_table(L); + + get_table_xy_or_coord(L, &ex, &ey); + x = ex; + y = ey; + etyp = engrtypes2i[get_table_option(L, "type", "engrave", engrtypes)]; + txt = get_table_str(L, "text"); + wipeout = get_table_boolean_opt(L, "degrade", TRUE); + guardobjs = get_table_boolean_opt(L, "guardobjects", FALSE); + } else if (argc == 3) { + lua_Integer ex, ey; + (void) get_coord(L, 1, &ex, &ey); + x = ex; + y = ey; + etyp = engrtypes2i[luaL_checkoption(L, 2, "engrave", engrtypes)]; + txt = dupstr(luaL_checkstring(L, 3)); + } else { + nhl_error(L, "Wrong parameters"); + } + + if (x == -1 && y == -1) + ecoord = SP_COORD_PACK_RANDOM(0); + else + ecoord = SP_COORD_PACK(x, y); + + get_location_coord(&x, &y, DRY, gc.coder->croom, ecoord); + make_engr_at(x, y, txt, NULL, 0L, etyp); + Free(txt); + ep = engr_at(x, y); + if (ep) { + ep->guardobjects = guardobjs; + ep->nowipeout = !wipeout; + } + return 0; +} + +int +lspo_mineralize(lua_State *L) { - static const char nhFunc[] = "spo_engraving"; - struct opvar *etyp, *txt, *ecoord; - xchar x, y; + int gem_prob, gold_prob, kelp_moat, kelp_pool; - if (!OV_pop_i(etyp) || !OV_pop_s(txt) || !OV_pop_c(ecoord)) - return; + create_des_coder(); + + lcheck_param_table(L); + /* -1 produces default mineralize behavior */ + gem_prob = get_table_int_opt(L, "gem_prob", -1); + gold_prob = get_table_int_opt(L, "gold_prob", -1); + kelp_moat = get_table_int_opt(L, "kelp_moat", -1); + kelp_pool = get_table_int_opt(L, "kelp_pool", -1); - get_location_coord(&x, &y, DRY, coder->croom, OV_i(ecoord)); - make_engr_at(x, y, OV_s(txt), 0L, OV_i(etyp)); + mineralize(kelp_pool, kelp_moat, gold_prob, gem_prob, TRUE); - opvar_free(etyp); - opvar_free(txt); - opvar_free(ecoord); + return 0; } -void -spo_mineralize(coder) -struct sp_coder *coder; +static const struct { + const char *name; + int type; +} room_types[] = { + { "ordinary", OROOM }, + { "themed", THEMEROOM }, + { "throne", COURT }, + { "swamp", SWAMP }, + { "vault", VAULT }, + { "beehive", BEEHIVE }, + { "morgue", MORGUE }, + { "barracks", BARRACKS }, + { "zoo", ZOO }, + { "delphi", DELPHI }, + { "temple", TEMPLE }, + { "anthole", ANTHOLE }, + { "cocknest", COCKNEST }, + { "leprehall", LEPREHALL }, + { "shop", SHOPBASE }, + { "armor shop", ARMORSHOP }, + { "scroll shop", SCROLLSHOP }, + { "potion shop", POTIONSHOP }, + { "weapon shop", WEAPONSHOP }, + { "food shop", FOODSHOP }, + { "ring shop", RINGSHOP }, + { "wand shop", WANDSHOP }, + { "tool shop", TOOLSHOP }, + { "book shop", BOOKSHOP }, + { "health food shop", FODDERSHOP }, + { "candle shop", CANDLESHOP }, + { 0, 0 } +}; + +staticfn const char * +get_mkroom_name(int rtype) { - static const char nhFunc[] = "spo_mineralize"; - struct opvar *kelp_pool, *kelp_moat, *gold_prob, *gem_prob; + int i; - if (!OV_pop_i(gem_prob) || !OV_pop_i(gold_prob) || !OV_pop_i(kelp_moat) - || !OV_pop_i(kelp_pool)) - return; + for (i = 0; room_types[i].name; i++) + if (room_types[i].type == rtype) + return room_types[i].name; - mineralize(OV_i(kelp_pool), OV_i(kelp_moat), OV_i(gold_prob), - OV_i(gem_prob), TRUE); + impossible("get_mkroom_name unknown rtype %d", rtype); + return "unknown"; /* not NULL */ +} + +staticfn int +get_table_roomtype_opt(lua_State *L, const char *name, int defval) +{ + char *roomstr = get_table_str_opt(L, name, emptystr); + int i, res = defval; - opvar_free(gem_prob); - opvar_free(gold_prob); - opvar_free(kelp_moat); - opvar_free(kelp_pool); + if (roomstr && *roomstr) { + for (i = 0; room_types[i].name; i++) + if (!strcmpi(roomstr, room_types[i].name)) { + res = room_types[i].type; + break; + } + if (!room_types[i].name) + impossible("Unknown room type '%s'", roomstr); + } + Free(roomstr); + return res; } -void -spo_room(coder) -struct sp_coder *coder; +/* room({ type="ordinary", lit=1, x=3,y=3, xalign="center",yalign="center", + * w=11,h=9 }); */ +/* room({ lit=1, coord={3,3}, xalign="center",yalign="center", w=11,h=9 }); */ +/* room({ coord={3,3}, xalign="center",yalign="center", w=11,h=9, + * contents=function(room) ... end }); */ +int +lspo_room(lua_State *L) { - static const char nhFunc[] = "spo_room"; + create_des_coder(); - if (coder->n_subroom > MAX_NESTED_ROOMS) { + if (gi.in_mk_themerooms && gt.themeroom_failed) + return 0; + + lcheck_param_table(L); + + if (gc.coder->n_subroom > MAX_NESTED_ROOMS) { panic("Too deeply nested rooms?!"); } else { - struct opvar *rflags, *h, *w, *yalign, *xalign, *y, *x, *rlit, - *chance, *rtype; + static const char *const left_or_right[] = { + "left", "half-left", "center", "half-right", "right", + "none", "random", NULL + }; + static const int l_or_r2i[] = { + SPLEV_LEFT, SPLEV_H_LEFT, SPLEV_CENTER, SPLEV_H_RIGHT, + SPLEV_RIGHT, -1, -1, -1 + }; + static const char *const top_or_bot[] = { + "top", "center", "bottom", "none", "random", NULL + }; + static const int t_or_b2i[] = { TOP, SPLEV_CENTER, BOTTOM, + -1, -1, -1 }; room tmproom; struct mkroom *tmpcr; - - if (!OV_pop_i(h) || !OV_pop_i(w) || !OV_pop_i(y) || !OV_pop_i(x) - || !OV_pop_i(yalign) || !OV_pop_i(xalign) || !OV_pop_i(rflags) - || !OV_pop_i(rlit) || !OV_pop_i(chance) || !OV_pop_i(rtype)) - return; - - tmproom.x = OV_i(x); - tmproom.y = OV_i(y); - tmproom.w = OV_i(w); - tmproom.h = OV_i(h); - tmproom.xalign = OV_i(xalign); - tmproom.yalign = OV_i(yalign); - tmproom.rtype = OV_i(rtype); - tmproom.chance = OV_i(chance); - tmproom.rlit = OV_i(rlit); - tmproom.filled = (OV_i(rflags) & (1 << 0)); - /*tmproom.irregular = (OV_i(rflags) & (1 << 1));*/ - tmproom.joined = !(OV_i(rflags) & (1 << 2)); - - opvar_free(x); - opvar_free(y); - opvar_free(w); - opvar_free(h); - opvar_free(xalign); - opvar_free(yalign); - opvar_free(rtype); - opvar_free(chance); - opvar_free(rlit); - opvar_free(rflags); - - if (!coder->failed_room[coder->n_subroom - 1]) { - tmpcr = build_room(&tmproom, coder->croom); + lua_Integer rx, ry; + + get_table_xy_or_coord(L, &rx, &ry); + tmproom.x = rx, tmproom.y = ry; + if ((tmproom.x == -1 || tmproom.y == -1) && tmproom.x != tmproom.y) + nhl_error(L, "Room must have both x and y"); + + tmproom.w = get_table_int_opt(L, "w", -1); + tmproom.h = get_table_int_opt(L, "h", -1); + + if ((tmproom.w == -1 || tmproom.h == -1) && tmproom.w != tmproom.h) + nhl_error(L, "Room must have both w and h"); + + tmproom.xalign = l_or_r2i[get_table_option(L, "xalign", "random", + left_or_right)]; + tmproom.yalign = t_or_b2i[get_table_option(L, "yalign", "random", + top_or_bot)]; + tmproom.rtype = get_table_roomtype_opt(L, "type", OROOM); + tmproom.chance = get_table_int_opt(L, "chance", 100); + tmproom.rlit = get_table_int_opt(L, "lit", -1); + /* theme rooms default to unfilled */ + tmproom.needfill = get_table_int_opt(L, "filled", + gi.in_mk_themerooms ? 0 : 1); + tmproom.joined = get_table_boolean_opt(L, "joined", TRUE); + + if (!gc.coder->failed_room[gc.coder->n_subroom - 1]) { + tmpcr = build_room(&tmproom, gc.coder->croom); if (tmpcr) { - coder->tmproomlist[coder->n_subroom] = tmpcr; - coder->failed_room[coder->n_subroom] = FALSE; - coder->n_subroom++; - return; + int n = gc.coder->n_subroom; + + gc.coder->tmproomlist[n] = tmpcr; /* TRUE to get here... */ + gc.coder->failed_room[n] = FALSE; + /* added a subroom, make parent room irregular */ + if (gc.coder->tmproomlist[n - 1]) + gc.coder->tmproomlist[n - 1]->irregular = TRUE; + gc.coder->n_subroom++; + update_croom(); + lua_getfield(L, 1, "contents"); + if (lua_type(L, -1) == LUA_TFUNCTION) { + lua_remove(L, -2); + l_push_mkroom_table(L, tmpcr); + nhl_pcall_handle(L, 1, 0, "lspo_room", NHLpa_panic); + } else + lua_pop(L, 1); + spo_endroom(gc.coder); + add_doors_to_room(tmpcr); + return 0; } + if (gi.in_mk_themerooms) + gt.themeroom_failed = TRUE; } /* failed to create parent room, so fail this too */ } - coder->tmproomlist[coder->n_subroom] = (struct mkroom *) 0; - coder->failed_room[coder->n_subroom] = TRUE; - coder->n_subroom++; + gc.coder->tmproomlist[gc.coder->n_subroom] = (struct mkroom *) 0; + gc.coder->failed_room[gc.coder->n_subroom] = TRUE; + gc.coder->n_subroom++; + update_croom(); + spo_endroom(gc.coder); + if (gi.in_mk_themerooms) + gt.themeroom_failed = TRUE; + + return 0; } -void -spo_endroom(coder) -struct sp_coder *coder; +staticfn void +spo_endroom(struct sp_coder *coder UNUSED) { - if (coder->n_subroom > 1) { - coder->n_subroom--; - coder->tmproomlist[coder->n_subroom] = NULL; - coder->failed_room[coder->n_subroom] = TRUE; + if (gc.coder->n_subroom > 1) { + gc.coder->n_subroom--; + gc.coder->tmproomlist[gc.coder->n_subroom] = NULL; + gc.coder->failed_room[gc.coder->n_subroom] = TRUE; } else { /* no subroom, get out of top-level room */ /* Need to ensure xstart/ystart/xsize/ysize have something sensible, in case there's some stuff to be created outside the outermost - room, - and there's no MAP. - */ - if (xsize <= 1 && ysize <= 1) { - xstart = 1; - ystart = 0; - xsize = COLNO - 1; - ysize = ROWNO; - } + room, and there's no MAP. */ + if (gx.xsize <= 1 && gy.ysize <= 1) + reset_xystart_size(); } + update_croom(); } -void -spo_stair(coder) -struct sp_coder *coder; +/* callback for is_ok_location. + stairs generated at random location shouldn't overwrite special terrain */ +staticfn boolean +good_stair_loc(coordxy x, coordxy y) { - static const char nhFunc[] = "spo_stair"; - xchar x, y; - struct opvar *up, *scoord; - struct trap *badtrap; - - if (!OV_pop_i(up) || !OV_pop_c(scoord)) - return; - - get_location_coord(&x, &y, DRY, coder->croom, OV_i(scoord)); - if ((badtrap = t_at(x, y)) != 0) - deltrap(badtrap); - mkstairs(x, y, (char) OV_i(up), coder->croom); - SpLev_Map[x][y] = 1; + schar typ = levl[x][y].typ; - opvar_free(scoord); - opvar_free(up); + return (typ == ROOM || typ == CORR || typ == ICE); } -void -spo_ladder(coder) -struct sp_coder *coder; +staticfn int +l_create_stairway(lua_State *L, boolean using_ladder) { - static const char nhFunc[] = "spo_ladder"; - xchar x, y; - struct opvar *up, *lcoord; + static const char *const stairdirs[] = { "down", "up", NULL }; + static const int stairdirs2i[] = { 0, 1 }; + int argc = lua_gettop(L); + coordxy x = -1, y = -1; + struct trap *badtrap; - if (!OV_pop_i(up) || !OV_pop_c(lcoord)) - return; + long scoord; + int up = 0; /* default is down */ + int ltype = lua_type(L, 1); - get_location_coord(&x, &y, DRY, coder->croom, OV_i(lcoord)); + create_des_coder(); - levl[x][y].typ = LADDER; - SpLev_Map[x][y] = 1; - if (OV_i(up)) { - xupladder = x; - yupladder = y; - levl[x][y].ladder = LA_UP; + if (argc == 1 && ltype == LUA_TTABLE) { + lua_Integer ax = -1, ay = -1; + lcheck_param_table(L); + get_table_xy_or_coord(L, &ax, &ay); + up = stairdirs2i[get_table_option(L, "dir", "down", stairdirs)]; + x = (coordxy) ax; + y = (coordxy) ay; } else { - xdnladder = x; - ydnladder = y; - levl[x][y].ladder = LA_DOWN; + lua_Integer ix = -1, iy = -1; + if (argc > 0 && ltype == LUA_TSTRING) { + up = stairdirs2i[luaL_checkoption(L, 1, "down", stairdirs)]; + lua_remove(L, 1); + } + nhl_get_xy_params(L, &ix, &iy); + x = (coordxy) ix; + y = (coordxy) iy; } - opvar_free(lcoord); - opvar_free(up); -} -void -spo_grave(coder) -struct sp_coder *coder; -{ - static const char nhFunc[] = "spo_grave"; - struct opvar *gcoord, *typ, *txt; - schar x, y; + if (x == -1 && y == -1) { + set_ok_location_func(good_stair_loc); + scoord = SP_COORD_PACK_RANDOM(0); + } else + scoord = SP_COORD_PACK(x, y); - if (!OV_pop_i(typ) || !OV_pop_s(txt) || !OV_pop_c(gcoord)) - return; + get_location_coord(&x, &y, DRY, gc.coder->croom, scoord); + set_ok_location_func(NULL); + if ((badtrap = t_at(x, y)) != 0) + deltrap(badtrap); + SpLev_Map[x][y] = 1; - get_location_coord(&x, &y, DRY, coder->croom, OV_i(gcoord)); + if (using_ladder) { + levl[x][y].typ = LADDER; + if (up) { + d_level dest; - if (isok(x, y) && !t_at(x, y)) { - levl[x][y].typ = GRAVE; - switch (OV_i(typ)) { - case 2: - make_grave(x, y, OV_s(txt)); - break; - case 1: - make_grave(x, y, NULL); - break; - default: - del_engr_at(x, y); - break; + dest.dnum = u.uz.dnum; + dest.dlevel = u.uz.dlevel - 1; + stairway_add(x, y, TRUE, TRUE, &dest); + levl[x][y].ladder = LA_UP; + } else { + d_level dest; + + dest.dnum = u.uz.dnum; + dest.dlevel = u.uz.dlevel + 1; + stairway_add(x, y, FALSE, TRUE, &dest); + levl[x][y].ladder = LA_DOWN; } + } else { + mkstairs(x, y, (char) up, gc.coder->croom, + !(scoord & SP_COORD_IS_RANDOM)); } - - opvar_free(gcoord); - opvar_free(typ); - opvar_free(txt); -} - -void -spo_altar(coder) -struct sp_coder *coder; -{ - static const char nhFunc[] = "spo_altar"; - struct opvar *al, *shrine, *acoord; - altar tmpaltar; - - if (!OV_pop_i(al) || !OV_pop_i(shrine) || !OV_pop_c(acoord)) - return; - - tmpaltar.coord = OV_i(acoord); - tmpaltar.align = OV_i(al); - tmpaltar.shrine = OV_i(shrine); - - create_altar(&tmpaltar, coder->croom); - - opvar_free(acoord); - opvar_free(shrine); - opvar_free(al); + return 0; } -void -spo_trap(coder) -struct sp_coder *coder; +/* stair("up"); */ +/* stair({ dir = "down" }); */ +/* stair({ dir = "down", x = 4, y = 7 }); */ +/* stair({ dir = "down", coord = {x,y} }); */ +/* stair("down", 4, 7); */ +/* TODO: stair(selection, "down"); */ +/* TODO: stair("up", {x,y}); */ +int +lspo_stair(lua_State *L) { - static const char nhFunc[] = "spo_trap"; - struct opvar *type; - struct opvar *tcoord; - spltrap tmptrap; - - if (!OV_pop_i(type) || !OV_pop_c(tcoord)) - return; - - tmptrap.coord = OV_i(tcoord); - tmptrap.type = OV_i(type); - - create_trap(&tmptrap, coder->croom); - opvar_free(tcoord); - opvar_free(type); + return l_create_stairway(L, FALSE); } -void -spo_gold(coder) -struct sp_coder *coder; +/* ladder("down"); */ +/* ladder("up", 6,10); */ +/* ladder({ x=11, y=05, dir="down" }); */ +int +lspo_ladder(lua_State *L) { - static const char nhFunc[] = "spo_gold"; - struct opvar *gcoord, *amt; - schar x, y; - long amount; - - if (!OV_pop_c(gcoord) || !OV_pop_i(amt)) - return; - amount = OV_i(amt); - get_location_coord(&x, &y, DRY, coder->croom, OV_i(gcoord)); - if (amount == -1) - amount = rnd(200); - mkgold(amount, x, y); - opvar_free(gcoord); - opvar_free(amt); + return l_create_stairway(L, TRUE); } -void -spo_corridor(coder) -struct sp_coder *coder; +/* grave(); */ +/* grave(x,y, "text"); */ +/* grave({ x = 1, y = 1 }); */ +/* grave({ x = 1, y = 1, text = "Foo" }); */ +/* grave({ coord = {1, 1}, text = "Foo" }); */ +int +lspo_grave(lua_State *L) { - static const char nhFunc[] = "spo_corridor"; - struct opvar *deswall, *desdoor, *desroom, *srcwall, *srcdoor, *srcroom; - corridor tc; + int argc = lua_gettop(L); + coordxy x, y; + long scoord; + lua_Integer ax,ay; + char *txt; - if (!OV_pop_i(deswall) || !OV_pop_i(desdoor) || !OV_pop_i(desroom) - || !OV_pop_i(srcwall) || !OV_pop_i(srcdoor) || !OV_pop_i(srcroom)) - return; + create_des_coder(); - tc.src.room = OV_i(srcroom); - tc.src.door = OV_i(srcdoor); - tc.src.wall = OV_i(srcwall); - tc.dest.room = OV_i(desroom); - tc.dest.door = OV_i(desdoor); - tc.dest.wall = OV_i(deswall); + if (argc == 3) { + x = ax = luaL_checkinteger(L, 1); + y = ay = luaL_checkinteger(L, 2); + txt = dupstr(luaL_checkstring(L, 3)); + } else { + lcheck_param_table(L); - create_corridor(&tc); + get_table_xy_or_coord(L, &ax, &ay); + x = ax, y = ay; + txt = get_table_str_opt(L, "text", NULL); + } - opvar_free(deswall); - opvar_free(desdoor); - opvar_free(desroom); - opvar_free(srcwall); - opvar_free(srcdoor); - opvar_free(srcroom); -} + if (x == -1 && y == -1) + scoord = SP_COORD_PACK_RANDOM(0); + else + scoord = SP_COORD_PACK(ax, ay); -struct opvar * -selection_opvar(nbuf) -char *nbuf; -{ - struct opvar *ov; - char buf[(COLNO * ROWNO) + 1]; + get_location_coord(&x, &y, DRY, gc.coder->croom, scoord); - if (!nbuf) { - (void) memset(buf, 1, sizeof(buf)); - buf[(COLNO * ROWNO)] = '\0'; - ov = opvar_new_str(buf); - } else { - ov = opvar_new_str(nbuf); + if (isok(x, y) && !t_at(x, y)) { + levl[x][y].typ = GRAVE; + make_grave(x, y, txt); /* note: 'txt' might be Null */ } - ov->spovartyp = SPOVAR_SEL; - return ov; + Free(txt); + return 0; } -xchar -selection_getpoint(x, y, ov) -int x, y; -struct opvar *ov; +/* altar({ x=NN, y=NN, align=ALIGNMENT, type=SHRINE }); */ +/* des.altar({ coord = {5, 10}, align="noalign", type="altar" }); */ +int +lspo_altar(lua_State *L) { - if (!ov || ov->spovartyp != SPOVAR_SEL) - return 0; - if (x < 0 || y < 0 || x >= COLNO || y >= ROWNO) - return 0; - - return (ov->vardata.str[COLNO * y + x] - 1); -} + static const char *const shrines[] = { + "altar", "shrine", "sanctum", NULL + }; + static const int shrines2i[] = { 0, 1, 2, 0 }; -void -selection_setpoint(x, y, ov, c) -int x, y; -struct opvar *ov; -xchar c; -{ - if (!ov || ov->spovartyp != SPOVAR_SEL) - return; - if (x < 0 || y < 0 || x >= COLNO || y >= ROWNO) - return; + altar tmpaltar; - ov->vardata.str[COLNO * y + x] = (char) (c + 1); -} + lua_Integer x, y; + long acoord; + int shrine; + int al; -struct opvar * -selection_not(s) -struct opvar *s; -{ - struct opvar *ov; - int x, y; + create_des_coder(); - ov = selection_opvar((char *) 0); - if (!ov) - return NULL; + lcheck_param_table(L); - for (x = 0; x < COLNO; x++) - for (y = 0; y < ROWNO; y++) - if (!selection_getpoint(x, y, s)) - selection_setpoint(x, y, ov, 1); + get_table_xy_or_coord(L, &x, &y); - return ov; -} + al = get_table_align(L); + shrine = shrines2i[get_table_option(L, "type", "altar", shrines)]; -struct opvar * -selection_logical_oper(s1, s2, oper) -struct opvar *s1, *s2; -char oper; -{ - struct opvar *ov; - int x, y; + if (x == -1 && y == -1) + acoord = SP_COORD_PACK_RANDOM(0); + else + acoord = SP_COORD_PACK(x, y); - ov = selection_opvar((char *) 0); - if (!ov) - return NULL; + tmpaltar.coord = acoord; + tmpaltar.sp_amask = al; + tmpaltar.shrine = shrine; - for (x = 0; x < COLNO; x++) - for (y = 0; y < ROWNO; y++) { - switch (oper) { - default: - case '|': - if (selection_getpoint(x, y, s1) - || selection_getpoint(x, y, s2)) - selection_setpoint(x, y, ov, 1); - break; - case '&': - if (selection_getpoint(x, y, s1) - && selection_getpoint(x, y, s2)) - selection_setpoint(x, y, ov, 1); - break; - } - } + create_altar(&tmpaltar, gc.coder->croom); - return ov; + return 0; } -struct opvar * -selection_filter_mapchar(ov, mc) -struct opvar *ov; -struct opvar *mc; -{ - int x, y; - schar mapc; - xchar lit; - struct opvar *ret = selection_opvar((char *) 0); - - if (!ov || !mc || !ret) - return NULL; - mapc = SP_MAPCHAR_TYP(OV_i(mc)); - lit = SP_MAPCHAR_LIT(OV_i(mc)); - for (x = 0; x < COLNO; x++) - for (y = 0; y < ROWNO; y++) - if (selection_getpoint(x, y, ov) && (levl[x][y].typ == mapc)) { - switch (lit) { - default: - case -2: - selection_setpoint(x, y, ret, 1); - break; - case -1: - selection_setpoint(x, y, ret, rn2(2)); - break; - case 0: - case 1: - if (levl[x][y].lit == lit) - selection_setpoint(x, y, ret, 1); - break; - } +static const struct { + const char *name; + int type; +} trap_types[] = { { "arrow", ARROW_TRAP }, + { "dart", DART_TRAP }, + { "falling rock", ROCKTRAP }, + { "board", SQKY_BOARD }, + { "bear", BEAR_TRAP }, + { "land mine", LANDMINE }, + { "rolling boulder", ROLLING_BOULDER_TRAP }, + { "sleep gas", SLP_GAS_TRAP }, + { "rust", RUST_TRAP }, + { "fire", FIRE_TRAP }, + { "pit", PIT }, + { "spiked pit", SPIKED_PIT }, + { "hole", HOLE }, + { "trap door", TRAPDOOR }, + { "teleport", TELEP_TRAP }, + { "level teleport", LEVEL_TELEP }, + { "magic portal", MAGIC_PORTAL }, + { "web", WEB }, + { "statue", STATUE_TRAP }, + { "magic", MAGIC_TRAP }, + { "anti magic", ANTI_MAGIC }, + { "polymorph", POLY_TRAP }, + { "vibrating square", VIBRATING_SQUARE }, + { "random", -1 }, + { 0, NO_TRAP } }; + +staticfn int +get_table_traptype_opt(lua_State *L, const char *name, int defval) +{ + char *trapstr = get_table_str_opt(L, name, emptystr); + int i, res = defval; + + if (trapstr && *trapstr) { + for (i = 0; trap_types[i].name; i++) + if (!strcmpi(trapstr, trap_types[i].name)) { + res = trap_types[i].type; + break; } - return ret; -} - -void -selection_filter_percent(ov, percent) -struct opvar *ov; -int percent; -{ - int x, y; - - if (!ov) - return; - for (x = 0; x < COLNO; x++) - for (y = 0; y < ROWNO; y++) - if (selection_getpoint(x, y, ov) && (rn2(100) >= percent)) - selection_setpoint(x, y, ov, 0); -} - -STATIC_OVL int -selection_rndcoord(ov, x, y, removeit) -struct opvar *ov; -schar *x, *y; -boolean removeit; -{ - int idx = 0; - int c; - int dx, dy; - - for (dx = 0; dx < COLNO; dx++) - for (dy = 0; dy < ROWNO; dy++) - if (isok(dx, dy) && selection_getpoint(dx, dy, ov)) - idx++; - - if (idx) { - c = rn2(idx); - for (dx = 0; dx < COLNO; dx++) - for (dy = 0; dy < ROWNO; dy++) - if (isok(dx, dy) && selection_getpoint(dx, dy, ov)) { - if (!c) { - *x = dx; - *y = dy; - if (removeit) selection_setpoint(dx, dy, ov, 0); - return 1; - } - c--; - } } - *x = *y = -1; - return 0; + Free(trapstr); + return res; } -void -selection_do_grow(ov, dir) -struct opvar *ov; -int dir; +const char * +get_trapname_bytype(int ttyp) { - int x, y; - char tmp[COLNO][ROWNO]; - - if (ov->spovartyp != SPOVAR_SEL) - return; - if (!ov) - return; - - (void) memset(tmp, 0, sizeof tmp); + int i; - for (x = 1; x < COLNO; x++) - for (y = 0; y < ROWNO; y++) { - /* note: dir is a mask of multiple directions, but the only - way to specify diagonals is by including the two adjacent - orthogonal directions, which effectively specifies three- - way growth [WEST|NORTH => WEST plus WEST|NORTH plus NORTH] */ - if (((dir & W_WEST) && selection_getpoint(x + 1, y, ov)) - || (((dir & (W_WEST | W_NORTH)) == (W_WEST | W_NORTH)) - && selection_getpoint(x + 1, y + 1, ov)) - || ((dir & W_NORTH) && selection_getpoint(x, y + 1, ov)) - || (((dir & (W_NORTH | W_EAST)) == (W_NORTH | W_EAST)) - && selection_getpoint(x - 1, y + 1, ov)) - || ((dir & W_EAST) && selection_getpoint(x - 1, y, ov)) - || (((dir & (W_EAST | W_SOUTH)) == (W_EAST | W_SOUTH)) - && selection_getpoint(x - 1, y - 1, ov)) - || ((dir & W_SOUTH) && selection_getpoint(x, y - 1, ov)) - || (((dir & (W_SOUTH | W_WEST)) == (W_SOUTH | W_WEST)) - && selection_getpoint(x + 1, y - 1, ov))) { - tmp[x][y] = 1; - } - } + for (i = 0; trap_types[i].name; i++) + if (ttyp == trap_types[i].type) + return trap_types[i].name; - for (x = 1; x < COLNO; x++) - for (y = 0; y < ROWNO; y++) - if (tmp[x][y]) - selection_setpoint(x, y, ov, 1); + return NULL; } -STATIC_VAR int FDECL((*selection_flood_check_func), (int, int)); -STATIC_VAR schar floodfillchk_match_under_typ; - -void -set_selection_floodfillchk(f) -int FDECL((*f), (int, int)); +staticfn int +get_traptype_byname(const char *trapname) { - selection_flood_check_func = f; -} + int i; -STATIC_OVL int -floodfillchk_match_under(x,y) -int x,y; -{ - return (floodfillchk_match_under_typ == levl[x][y].typ); -} + for (i = 0; trap_types[i].name; i++) + if (!strcmpi(trapname, trap_types[i].name)) + return trap_types[i].type; -STATIC_OVL int -floodfillchk_match_accessible(x, y) -int x, y; -{ - return (ACCESSIBLE(levl[x][y].typ) - || levl[x][y].typ == SDOOR - || levl[x][y].typ == SCORR); + return NO_TRAP; } -/* check whethere is already in xs[],ys[] */ -STATIC_OVL boolean -sel_flood_havepoint(x, y, xs, ys, n) -int x, y; -xchar xs[], ys[]; -int n; +/* trap({ type = "hole", x = 1, y = 1 }); */ +/* trap({ type = "web", spider_on_web = 0 }); */ +/* trap("hole", 3, 4); */ +/* trap("level teleport", {5, 8}); */ +/* trap("rust") */ +/* trap(); */ +int +lspo_trap(lua_State *L) { - xchar xx = (xchar) x, yy = (xchar) y; + spltrap tmptrap; + lua_Integer x, y; + int argc = lua_gettop(L); - while (n > 0) { - --n; - if (xs[n] == xx && ys[n] == yy) - return TRUE; - } - return FALSE; -} + create_des_coder(); -void -selection_floodfill(ov, x, y, diagonals) -struct opvar *ov; -int x, y; -boolean diagonals; -{ - static const char nhFunc[] = "selection_floodfill"; - struct opvar *tmp = selection_opvar((char *) 0); -#define SEL_FLOOD_STACK (COLNO * ROWNO) -#define SEL_FLOOD(nx, ny) \ - do { \ - if (idx < SEL_FLOOD_STACK) { \ - dx[idx] = (nx); \ - dy[idx] = (ny); \ - idx++; \ - } else \ - panic(floodfill_stack_overrun); \ - } while (0) -#define SEL_FLOOD_CHKDIR(mx, my, sel) \ - do { \ - if (isok((mx), (my)) \ - && (*selection_flood_check_func)((mx), (my)) \ - && !selection_getpoint((mx), (my), (sel)) \ - && !sel_flood_havepoint((mx), (my), dx, dy, idx)) \ - SEL_FLOOD((mx), (my)); \ - } while (0) - static const char floodfill_stack_overrun[] = "floodfill stack overrun"; - int idx = 0; - xchar dx[SEL_FLOOD_STACK]; - xchar dy[SEL_FLOOD_STACK]; + tmptrap.spider_on_web = TRUE; + tmptrap.seen = FALSE; + tmptrap.novictim = FALSE; - if (selection_flood_check_func == (int FDECL((*), (int, int))) 0) { - opvar_free(tmp); - return; - } - SEL_FLOOD(x, y); - do { - idx--; - x = dx[idx]; - y = dy[idx]; - if (isok(x, y)) { - selection_setpoint(x, y, ov, 1); - selection_setpoint(x, y, tmp, 1); - } - SEL_FLOOD_CHKDIR((x + 1), y, tmp); - SEL_FLOOD_CHKDIR((x - 1), y, tmp); - SEL_FLOOD_CHKDIR(x, (y + 1), tmp); - SEL_FLOOD_CHKDIR(x, (y - 1), tmp); - if (diagonals) { - SEL_FLOOD_CHKDIR((x + 1), (y + 1), tmp); - SEL_FLOOD_CHKDIR((x - 1), (y - 1), tmp); - SEL_FLOOD_CHKDIR((x - 1), (y + 1), tmp); - SEL_FLOOD_CHKDIR((x + 1), (y - 1), tmp); - } - } while (idx > 0); -#undef SEL_FLOOD -#undef SEL_FLOOD_STACK -#undef SEL_FLOOD_CHKDIR - opvar_free(tmp); -} + if (argc == 1 && lua_type(L, 1) == LUA_TSTRING) { + const char *trapstr = luaL_checkstring(L, 1); -/* McIlroy's Ellipse Algorithm */ -void -selection_do_ellipse(ov, xc, yc, a, b, filled) -struct opvar *ov; -int xc, yc, a, b, filled; -{ /* e(x,y) = b^2*x^2 + a^2*y^2 - a^2*b^2 */ - int x = 0, y = b; - long a2 = (long) a * a, b2 = (long) b * b; - long crit1 = -(a2 / 4 + a % 2 + b2); - long crit2 = -(b2 / 4 + b % 2 + a2); - long crit3 = -(b2 / 4 + b % 2); - long t = -a2 * y; /* e(x+1/2,y-1/2) - (a^2+b^2)/4 */ - long dxt = 2 * b2 * x, dyt = -2 * a2 * y; - long d2xt = 2 * b2, d2yt = 2 * a2; - long width = 1; - long i; - - if (!ov) - return; + tmptrap.type = get_traptype_byname(trapstr); + x = y = -1; + } else if (argc == 2 && lua_type(L, 1) == LUA_TSTRING + && lua_type(L, 2) == LUA_TTABLE) { + const char *trapstr = luaL_checkstring(L, 1); - filled = !filled; + tmptrap.type = get_traptype_byname(trapstr); + (void) get_coord(L, 2, &x, &y); + } else if (argc == 3) { + const char *trapstr = luaL_checkstring(L, 1); - if (!filled) { - while (y >= 0 && x <= a) { - selection_setpoint(xc + x, yc + y, ov, 1); - if (x != 0 || y != 0) - selection_setpoint(xc - x, yc - y, ov, 1); - if (x != 0 && y != 0) { - selection_setpoint(xc + x, yc - y, ov, 1); - selection_setpoint(xc - x, yc + y, ov, 1); - } - if (t + b2 * x <= crit1 /* e(x+1,y-1/2) <= 0 */ - || t + a2 * y <= crit3) { /* e(x+1/2,y) <= 0 */ - x++; - dxt += d2xt; - t += dxt; - } else if (t - a2 * y > crit2) { /* e(x+1/2,y-1) > 0 */ - y--; - dyt += d2yt; - t += dyt; - } else { - x++; - dxt += d2xt; - t += dxt; - y--; - dyt += d2yt; - t += dyt; - } - } + tmptrap.type = get_traptype_byname(trapstr); + x = luaL_checkinteger(L, 2); + y = luaL_checkinteger(L, 3); } else { - while (y >= 0 && x <= a) { - if (t + b2 * x <= crit1 /* e(x+1,y-1/2) <= 0 */ - || t + a2 * y <= crit3) { /* e(x+1/2,y) <= 0 */ - x++; - dxt += d2xt; - t += dxt; - width += 2; - } else if (t - a2 * y > crit2) { /* e(x+1/2,y-1) > 0 */ - for (i = 0; i < width; i++) - selection_setpoint(xc - x + i, yc - y, ov, 1); - if (y != 0) - for (i = 0; i < width; i++) - selection_setpoint(xc - x + i, yc + y, ov, 1); - y--; - dyt += d2yt; - t += dyt; - } else { - for (i = 0; i < width; i++) - selection_setpoint(xc - x + i, yc - y, ov, 1); - if (y != 0) - for (i = 0; i < width; i++) - selection_setpoint(xc - x + i, yc + y, ov, 1); - x++; - dxt += d2xt; - t += dxt; - y--; - dyt += d2yt; - t += dyt; - width += 2; - } - } - } -} + lcheck_param_table(L); + + get_table_xy_or_coord(L, &x, &y); + tmptrap.type = get_table_traptype_opt(L, "type", -1); + tmptrap.spider_on_web = get_table_boolean_opt(L, "spider_on_web", 1); + tmptrap.seen = get_table_boolean_opt(L, "seen", FALSE); + tmptrap.novictim = !get_table_boolean_opt(L, "victim", TRUE); + + lua_getfield(L, -1, "launchfrom"); + if (lua_type(L, -1) == LUA_TTABLE) { + lua_Integer lx = -1, ly = -1; + + (void) get_coord(L, -1, &lx, &ly); + lua_pop(L, 1); + gl.launchplace.x = lx; + gl.launchplace.y = ly; + } else + lua_pop(L, 1); -/* distance from line segment (x1,y1, x2,y2) to point (x3,y3) */ -long -line_dist_coord(x1, y1, x2, y2, x3, y3) -long x1, y1, x2, y2, x3, y3; -{ - long px = x2 - x1; - long py = y2 - y1; - long s = px * px + py * py; - long x, y, dx, dy, dist = 0; - float lu = 0; + lua_getfield(L, -1, "teledest"); + if (lua_type(L, -1) == LUA_TTABLE) { + lua_Integer lx = -1, ly = -1; - if (x1 == x2 && y1 == y2) - return isqrt(dist2(x1, y1, x3, y3)); + (void) get_coord(L, -1, &lx, &ly); + lua_pop(L, 1); + gl.launchplace.x = lx; + gl.launchplace.y = ly; + } else + lua_pop(L, 1); + } - lu = ((x3 - x1) * px + (y3 - y1) * py) / (float) s; - if (lu > 1) - lu = 1; - else if (lu < 0) - lu = 0; + if (tmptrap.type == NO_TRAP) + nhl_error(L, "Unknown trap type"); + + if (x == -1 && y == -1) + tmptrap.coord = SP_COORD_PACK_RANDOM(0); + else + tmptrap.coord = SP_COORD_PACK(x, y); - x = x1 + lu * px; - y = y1 + lu * py; - dx = x - x3; - dy = y - y3; - dist = isqrt(dx * dx + dy * dy); + create_trap(&tmptrap, gc.coder->croom); + gl.launchplace.x = gl.launchplace.y = 0; - return dist; + return 0; } -void -selection_do_gradient(ov, x, y, x2, y2, gtyp, mind, maxd, limit) -struct opvar *ov; -long x, y, x2, y2, gtyp, mind, maxd, limit; -{ - long dx, dy, dofs; +DISABLE_WARNING_UNREACHABLE_CODE - if (mind > maxd) { - long tmp = mind; - mind = maxd; - maxd = tmp; +/* gold(500, 3,5); */ +/* gold(500, {5, 6}); */ +/* gold({ amount = 500, x = 2, y = 5 });*/ +/* gold({ amount = 500, coord = {2, 5} });*/ +/* gold(); */ +int +lspo_gold(lua_State *L) +{ + int argc = lua_gettop(L); + coordxy x, y; + long amount; + long gcoord; + lua_Integer gldx, gldy; + + create_des_coder(); + + if (argc == 3) { + amount = luaL_checkinteger(L, 1); + x = gldx = luaL_checkinteger(L, 2); + y = gldy = luaL_checkinteger(L, 3); + } else if (argc == 2 && lua_type(L, 2) == LUA_TTABLE) { + amount = luaL_checkinteger(L, 1); + (void) get_coord(L, 2, &gldx, &gldy); + x = gldx; + y = gldy; + } else if (argc == 0 || (argc == 1 && lua_type(L, 1) == LUA_TTABLE)) { + lcheck_param_table(L); + + amount = get_table_int_opt(L, "amount", -1); + get_table_xy_or_coord(L, &gldx, &gldy); + x = gldx, y = gldy; + } else { + nhl_error(L, "Wrong parameters"); + /*NOTREACHED*/ + return 0; } - dofs = maxd - mind; - if (dofs < 1) - dofs = 1; + if (x == -1 && y == -1) + gcoord = SP_COORD_PACK_RANDOM(0); + else + gcoord = SP_COORD_PACK(gldx, gldy); - switch (gtyp) { - default: - case SEL_GRADIENT_RADIAL: { - for (dx = 0; dx < COLNO; dx++) - for (dy = 0; dy < ROWNO; dy++) { - long d0 = line_dist_coord(x, y, x2, y2, dx, dy); - if (d0 >= mind && (!limit || d0 <= maxd)) { - if (d0 - mind > rn2(dofs)) - selection_setpoint(dx, dy, ov, 1); - } - } - break; - } - case SEL_GRADIENT_SQUARE: { - for (dx = 0; dx < COLNO; dx++) - for (dy = 0; dy < ROWNO; dy++) { - long d1 = line_dist_coord(x, y, x2, y2, x, dy); - long d2 = line_dist_coord(x, y, x2, y2, dx, y); - long d3 = line_dist_coord(x, y, x2, y2, x2, dy); - long d4 = line_dist_coord(x, y, x2, y2, dx, y2); - long d5 = line_dist_coord(x, y, x2, y2, dx, dy); - long d0 = min(d5, min(max(d1, d2), max(d3, d4))); - - if (d0 >= mind && (!limit || d0 <= maxd)) { - if (d0 - mind > rn2(dofs)) - selection_setpoint(dx, dy, ov, 1); - } - } - break; - } /*case*/ - } /*switch*/ + get_location_coord(&x, &y, DRY, gc.coder->croom, gcoord); + if (amount < 0) + amount = rnd(200); + mkgold(amount, x, y); + + return 0; } -/* bresenham line algo */ -void -selection_do_line(x1, y1, x2, y2, ov) -schar x1, y1, x2, y2; -struct opvar *ov; +RESTORE_WARNING_UNREACHABLE_CODE + +/* corridor({ srcroom=1, srcdoor=2, srcwall="north", + * destroom=2, destdoor=1, destwall="west" }); */ +int +lspo_corridor(lua_State *L) { - int d0, dx, dy, ai, bi, xi, yi; + static const char *const walldirs[] = { + "all", "random", "north", "west", "east", "south", NULL + }; + static const int walldirs2i[] = { + W_ANY, W_RANDOM, W_NORTH, W_WEST, W_EAST, W_SOUTH, 0 + }; + corridor tc; - if (x1 < x2) { - xi = 1; - dx = x2 - x1; - } else { - xi = -1; - dx = x1 - x2; - } + create_des_coder(); - if (y1 < y2) { - yi = 1; - dy = y2 - y1; - } else { - yi = -1; - dy = y1 - y2; - } + lcheck_param_table(L); - selection_setpoint(x1, y1, ov, 1); + tc.src.room = get_table_int(L, "srcroom"); + tc.src.door = get_table_int(L, "srcdoor"); + tc.src.wall = walldirs2i[get_table_option(L, "srcwall", "all", walldirs)]; + tc.dest.room = get_table_int(L, "destroom"); + tc.dest.door = get_table_int(L, "destdoor"); + tc.dest.wall = walldirs2i[get_table_option(L, "destwall", "all", + walldirs)]; - if (dx > dy) { - ai = (dy - dx) * 2; - bi = dy * 2; - d0 = bi - dx; - do { - if (d0 >= 0) { - y1 += yi; - d0 += ai; - } else - d0 += bi; - x1 += xi; - selection_setpoint(x1, y1, ov, 1); - } while (x1 != x2); - } else { - ai = (dx - dy) * 2; - bi = dx * 2; - d0 = bi - dy; - do { - if (d0 >= 0) { - x1 += xi; - d0 += ai; - } else - d0 += bi; - y1 += yi; - selection_setpoint(x1, y1, ov, 1); - } while (y1 != y2); - } + create_corridor(&tc); + + return 0; } -void -selection_do_randline(x1, y1, x2, y2, rough, rec, ov) -schar x1, y1, x2, y2, rough, rec; -struct opvar *ov; +/* random_corridors(); */ +int +lspo_random_corridors(lua_State *L UNUSED) { - int mx, my; - int dx, dy; - - if (rec < 1) { - return; - } + corridor tc; - if ((x2 == x1) && (y2 == y1)) { - selection_setpoint(x1, y1, ov, 1); - return; - } + create_des_coder(); - if (rough > max(abs(x2 - x1), abs(y2 - y1))) - rough = max(abs(x2 - x1), abs(y2 - y1)); + tc.src.room = -1; + tc.src.door = -1; + tc.src.wall = -1; + tc.dest.room = -1; + tc.dest.door = -1; + tc.dest.wall = -1; - if (rough < 2) { - mx = ((x1 + x2) / 2); - my = ((y1 + y2) / 2); - } else { - do { - dx = rn2(rough) - (rough / 2); - dy = rn2(rough) - (rough / 2); - mx = ((x1 + x2) / 2) + dx; - my = ((y1 + y2) / 2) + dy; - } while ((mx > COLNO - 1 || mx < 0 || my < 0 || my > ROWNO - 1)); - } + create_corridor(&tc); - selection_setpoint(mx, my, ov, 1); + return 0; +} - rough = (rough * 2) / 3; +/* Choose a single random W_* direction. */ +coordxy +random_wdir(void) +{ + static const coordxy wdirs[4] = { W_NORTH, W_SOUTH, W_EAST, W_WEST }; + return wdirs[rn2(4)]; +} - rec--; +static schar floodfillchk_match_under_typ; - selection_do_randline(x1, y1, mx, my, rough, rec, ov); - selection_do_randline(mx, my, x2, y2, rough, rec, ov); +staticfn int +floodfillchk_match_under(coordxy x, coordxy y) +{ + return (floodfillchk_match_under_typ == levl[x][y].typ); } void -selection_iterate(ov, func, arg) -struct opvar *ov; -select_iter_func func; -genericptr_t arg; +set_floodfillchk_match_under(coordxy typ) { - int x, y; + floodfillchk_match_under_typ = typ; + set_selection_floodfillchk(floodfillchk_match_under); +} - /* yes, this is very naive, but it's not _that_ expensive. */ - for (x = 0; x < COLNO; x++) - for (y = 0; y < ROWNO; y++) - if (selection_getpoint(x, y, ov)) - (*func)(x, y, arg); +staticfn int +floodfillchk_match_accessible(coordxy x, coordxy y) +{ + return (ACCESSIBLE(levl[x][y].typ) + || levl[x][y].typ == SDOOR + || levl[x][y].typ == SCORR); } -void -sel_set_ter(x, y, arg) -int x, y; -genericptr_t arg; +/* change map location terrain type during level creation */ +staticfn void +sel_set_ter(coordxy x, coordxy y, genericptr_t arg) { terrain terr; terr = *(terrain *) arg; - SET_TYPLIT(x, y, terr.ter, terr.tlit); + if (!set_levltyp_lit(x, y, terr.ter, terr.tlit)) + return; + /* TODO: move this below into set_levltyp? */ /* handle doors and secret doors */ if (levl[x][y].typ == SDOOR || IS_DOOR(levl[x][y].typ)) { if (levl[x][y].typ == SDOOR) levl[x][y].doormask = D_CLOSED; if (x && (IS_WALL(levl[x - 1][y].typ) || levl[x - 1][y].horizontal)) levl[x][y].horizontal = 1; + } else if (levl[x][y].typ == HWALL || levl[x][y].typ == IRONBARS) { + levl[x][y].horizontal = 1; + } else if (splev_init_present && levl[x][y].typ == ICE) { + levl[x][y].icedpool = icedpools ? ICED_POOL : ICED_MOAT; + } else if (levl[x][y].typ == CLOUD) { + del_engr_at(x, y); /* clouds cannot have engravings */ } } -void -sel_set_feature(x, y, arg) -int x, y; -genericptr_t arg; +staticfn void +sel_set_feature(coordxy x, coordxy y, genericptr_t arg) { + if (!isok(x, y)) { +#ifdef EXTRA_SANITY_CHECKS + impossible("sel_set_feature(%i,%i,%i) !isok", x, y, (*(int *) arg)); +#endif /*EXTRA_SANITY_CHECKS*/ + return; + } if (IS_FURNITURE(levl[x][y].typ)) return; levl[x][y].typ = (*(int *) arg); } -void -sel_set_door(dx, dy, arg) -int dx, dy; -genericptr_t arg; +staticfn void +sel_set_door(coordxy dx, coordxy dy, genericptr_t arg) { - xchar typ = *(xchar *) arg; - xchar x = dx, y = dy; + coordxy typ = *(coordxy *) arg; + coordxy x = dx, y = dy; if (!IS_DOOR(levl[x][y].typ) && levl[x][y].typ != SDOOR) levl[x][y].typ = (typ & D_SECRET) ? SDOOR : DOOR; @@ -4292,200 +4661,577 @@ genericptr_t arg; SpLev_Map[x][y] = 1; } -void -spo_door(coder) -struct sp_coder *coder; +DISABLE_WARNING_UNREACHABLE_CODE + +/* door({ x = 1, y = 1, state = "nodoor" }); */ +/* door({ coord = {1, 1}, state = "nodoor" }); */ +/* door({ wall = "north", pos = 3, state="secret" }); */ +/* door("nodoor", 1, 2); */ +int +lspo_door(lua_State *L) { - static const char nhFunc[] = "spo_door"; - struct opvar *msk, *sel; - xchar typ; + static const char *const doorstates[] = { + "random", "open", "closed", "locked", "nodoor", "broken", + "secret", NULL + }; + static const int doorstates2i[] = { + -1, D_ISOPEN, D_CLOSED, D_LOCKED, D_NODOOR, D_BROKEN, D_SECRET + }; + int msk; + coordxy x, y; + coordxy typ; + int argc = lua_gettop(L); - if (!OV_pop_i(msk) || !OV_pop_typ(sel, SPOVAR_SEL)) - return; + create_des_coder(); + + if (argc == 3) { + msk = doorstates2i[luaL_checkoption(L, 1, "random", doorstates)]; + x = luaL_checkinteger(L, 2); + y = luaL_checkinteger(L, 3); - typ = OV_i(msk) == -1 ? rnddoor() : (xchar) OV_i(msk); + } else { + lua_Integer dx, dy; + lcheck_param_table(L); + + get_table_xy_or_coord(L, &dx, &dy); + x = dx, y = dy; + msk = doorstates2i[get_table_option(L, "state", "random", + doorstates)]; + } - selection_iterate(sel, sel_set_door, (genericptr_t) &typ); + typ = (msk == -1) ? rnddoor() : (coordxy) msk; + + if (x == -1 && y == -1) { + static const char *const walldirs[] = { + "all", "random", "north", "west", "east", "south", NULL + }; + /* Note that "random" is also W_ANY, because create_door just wants a + * mask of acceptable walls */ + static const int walldirs2i[] = { + W_ANY, W_ANY, W_NORTH, W_WEST, W_EAST, W_SOUTH, 0 + }; + room_door tmpd; + + tmpd.secret = (typ == D_SECRET) ? 1 : 0; + tmpd.mask = msk; + tmpd.pos = get_table_int_opt(L, "pos", -1); + tmpd.wall = walldirs2i[get_table_option(L, "wall", "all", walldirs)]; + + create_door(&tmpd, gc.coder->croom); + } else { + /*selection_iterate(sel, sel_set_door, (genericptr_t) &typ);*/ + get_location_coord(&x, &y, ANY_LOC, gc.coder->croom, + SP_COORD_PACK(x, y)); + if (!isok(x, y)) { + nhl_error(L, "door coord not ok"); + /*NOTREACHED*/ + return 0; + } + sel_set_door(x, y, (genericptr_t) &typ); + } - opvar_free(sel); - opvar_free(msk); + return 0; } -void -spo_feature(coder) -struct sp_coder *coder; -{ - static const char nhFunc[] = "spo_feature"; - struct opvar *sel; - int typ; +RESTORE_WARNING_UNREACHABLE_CODE - if (!OV_pop_typ(sel, SPOVAR_SEL)) - return; +staticfn void +l_table_getset_feature_flag( + lua_State *L, + int x, int y, + const char *name, + int flag) +{ + int val = get_table_boolean_opt(L, name, -2); - switch (coder->opcode) { - default: - impossible("spo_feature called with wrong opcode %i.", coder->opcode); - break; - case SPO_FOUNTAIN: - typ = FOUNTAIN; - break; - case SPO_SINK: - typ = SINK; - break; - case SPO_POOL: - typ = POOL; - break; + if (val != -2) { + if (val == -1) + val = rn2(2); + if (val) + levl[x][y].flags |= flag; + else + levl[x][y].flags &= ~flag; } - selection_iterate(sel, sel_set_feature, (genericptr_t) &typ); - opvar_free(sel); } -void -spo_terrain(coder) -struct sp_coder *coder; -{ - static const char nhFunc[] = "spo_terrain"; +/* guts of nhl_abs_coord; convert a coordinate relative to a map or room + * into an absolute coordinate in svl.level.locations. + * + * If there is no enclosing map or room, the coordinates are assumed to be + * absolute already. + * + * Part of the reason this is a function is to make it clearer in the calling + * code that this conversion is what is intended. + * + * NOTE: if the coordinates are going to get passed to one of the get_location + * family of functions, this should NOT be called; get_location already makes + * an adjustment like this. (What this function supports which get_location + * doesn't is the input coordinates being negative. get_location will treat + * that as "level designer wants a random coordinate".) */ +void +cvt_to_abscoord(coordxy *x, coordxy *y) +{ + /* since commit 99715e0, xstart and ystart are only relevant in mklev when + * maps are being used, and 0 otherwise. It is possible in the future that + * map positions and dimensions can be saved and retrieved outside of + * mklev which would reintroduce nonzero xstart/ystart/xsiz/ysiz, but + * this is not currently implemented, so this function can be assumed to + * have no effect outside of mklev. + */ + if (gc.coder && gc.coder->croom) { + *x += gc.coder->croom->lx; + *y += gc.coder->croom->ly; + } else { + *x += gx.xstart; + *y += gy.ystart; + } +} + +/* inverse of cvt_to_abscoord; turn an absolute svl.level.locations coordinate + * into one relative to the current map or room. */ +void +cvt_to_relcoord(coordxy *x, coordxy *y) +{ + if (gc.coder && gc.coder->croom) { + *x -= gc.coder->croom->lx; + *y -= gc.coder->croom->ly; + } else { + *x -= gx.xstart; + *y -= gy.ystart; + } +} + +DISABLE_WARNING_UNREACHABLE_CODE + +/* convert map-relative coordinate to absolute. + local ax,ay = nh.abscoord(rx, ry); + local pt = nh.abscoord({ x = 10, y = 5 }); + */ +int +nhl_abs_coord(lua_State *L) +{ + int argc = lua_gettop(L); + coordxy x = -1, y = -1; + + if (argc == 2) { + x = (coordxy) lua_tointeger(L, 1); + y = (coordxy) lua_tointeger(L, 2); + cvt_to_abscoord(&x, &y); + lua_pushinteger(L, x); + lua_pushinteger(L, y); + return 2; + } else if (argc == 1 && lua_type(L, 1) == LUA_TTABLE) { + x = (coordxy) get_table_int(L, "x"); + y = (coordxy) get_table_int(L, "y"); + cvt_to_abscoord(&x, &y); + lua_newtable(L); + nhl_add_table_entry_int(L, "x", x); + nhl_add_table_entry_int(L, "y", y); + return 1; + } else { + nhl_error(L, "nhl_abs_coord: Wrong args"); + /*NOTREACHED*/ + return 0; + } +} + +/* feature("fountain", x, y); */ +/* feature("fountain", {x,y}); */ +/* feature({ type="fountain", x=NN, y=NN }); */ +/* feature({ type="fountain", coord={NN, NN} }); */ +/* feature({ type="tree", coord={NN, NN}, swarm=true, looted=false }); */ +int +lspo_feature(lua_State *L) +{ + static const char *const features[] = { "fountain", "sink", "pool", + "throne", "tree", NULL }; + static const int features2i[] = { FOUNTAIN, SINK, POOL, + THRONE, TREE, STONE }; + coordxy x, y; + int typ; + int argc = lua_gettop(L); + boolean can_have_flags = FALSE; + long fcoord; + int humidity; + + create_des_coder(); + + if (argc == 1 && lua_type(L, 1) == LUA_TSTRING) { + typ = features2i[luaL_checkoption(L, 1, NULL, features)]; + x = y = -1; + } else if (argc == 2 && lua_type(L, 1) == LUA_TSTRING + && lua_type(L, 2) == LUA_TTABLE) { + lua_Integer fx, fy; + typ = features2i[luaL_checkoption(L, 1, NULL, features)]; + (void) get_coord(L, 2, &fx, &fy); + x = fx; + y = fy; + } else if (argc == 3) { + typ = features2i[luaL_checkoption(L, 1, NULL, features)]; + x = luaL_checkinteger(L, 2); + y = luaL_checkinteger(L, 3); + } else { + lua_Integer fx, fy; + lcheck_param_table(L); + + get_table_xy_or_coord(L, &fx, &fy); + x = fx, y = fy; + typ = features2i[get_table_option(L, "type", NULL, features)]; + can_have_flags = TRUE; + } + + if (x == -1 && y == -1) { + fcoord = SP_COORD_PACK_RANDOM(0); + humidity = DRY; /* pick a regular space, no rock or other furniture */ + } + else { + fcoord = SP_COORD_PACK(x, y); + humidity = ANY_LOC; /* assume the author knows what they're doing */ + } + get_location_coord(&x, &y, humidity, gc.coder->croom, fcoord); + + if (typ == STONE) + impossible("feature has unknown type param."); + else + sel_set_feature(x, y, (genericptr_t) &typ); + + if (levl[x][y].typ != typ || !can_have_flags) + return 0; + + switch (typ) { + default: + break; + case FOUNTAIN: + l_table_getset_feature_flag(L, x, y, "looted", F_LOOTED); + l_table_getset_feature_flag(L, x, y, "warned", F_WARNED); + break; + case SINK: + l_table_getset_feature_flag(L, x, y, "pudding", S_LPUDDING); + l_table_getset_feature_flag(L, x, y, "dishwasher", S_LDWASHER); + l_table_getset_feature_flag(L, x, y, "ring", S_LRING); + break; + case THRONE: + l_table_getset_feature_flag(L, x, y, "looted", T_LOOTED); + break; + case TREE: + l_table_getset_feature_flag(L, x, y, "looted", TREE_LOOTED); + l_table_getset_feature_flag(L, x, y, "swarm", TREE_SWARM); + break; + } + + return 0; +} + +/* gas_cloud({ selection=SELECTION }); */ +/* gas_cloud({ selection=SELECTION, damage=N }); */ +/* gas_cloud({ selection=SELECTION, damage=N, ttl=N }); */ +int +lspo_gas_cloud(lua_State *L) +{ + coordxy x = 0, y = 0; + struct selectionvar *sel = NULL; + int argc = lua_gettop(L); + int damage = 0; + int ttl = -2; + + create_des_coder(); + + if (argc == 1 && lua_type(L, 1) == LUA_TTABLE) { + lua_Integer tx, ty; + NhRegion *reg; + + lcheck_param_table(L); + + get_table_xy_or_coord(L, &tx, &ty); + x = tx, y = ty; + if (tx == -1 && ty == -1) { + lua_getfield(L, 1, "selection"); + sel = l_selection_check(L, -1); + lua_pop(L, 1); + } + damage = get_table_int_opt(L, "damage", 0); + ttl = get_table_int_opt(L, "ttl", -2); + if (!sel) { + reg = create_gas_cloud(x, y, 1, damage); + } else + reg = create_gas_cloud_selection(sel, damage); + if (ttl > -2) + reg->ttl = ttl; + } else { + nhl_error(L, "wrong parameters"); + } + + return 0; +} + + +/* + * [lit_state: 1 On, 0 Off, -1 random, -2 leave as-is] + * terrain({ x=NN, y=NN, typ=MAPCHAR, lit=lit_state }); + * terrain({ coord={X, Y}, typ=MAPCHAR, lit=lit_state }); + * terrain({ selection=SELECTION, typ=MAPCHAR, lit=lit_state }); + * terrain( SELECTION, MAPCHAR [, lit_state ] ); + * terrain({x,y}, MAPCHAR); + * terrain(x,y, MAPCHAR); + */ +int +lspo_terrain(lua_State *L) +{ terrain tmpterrain; - struct opvar *ter, *sel; + coordxy x = 0, y = 0; + struct selectionvar *sel = NULL; + int argc = lua_gettop(L); + + create_des_coder(); + tmpterrain.tlit = SET_LIT_NOCHANGE; + tmpterrain.ter = INVALID_TYPE; + + if (argc == 1) { + lua_Integer tx, ty; + lcheck_param_table(L); + + get_table_xy_or_coord(L, &tx, &ty); + x = tx, y = ty; + if (tx == -1 && ty == -1) { + lua_getfield(L, 1, "selection"); + sel = l_selection_check(L, -1); + lua_pop(L, 1); + } + tmpterrain.ter = get_table_mapchr(L, "typ"); + tmpterrain.tlit = get_table_int_opt(L, "lit", SET_LIT_NOCHANGE); + } else if (argc == 2 && lua_type(L, 1) == LUA_TTABLE + && lua_type(L, 2) == LUA_TSTRING) { + lua_Integer tx, ty; + tmpterrain.ter = check_mapchr(luaL_checkstring(L, 2)); + lua_pop(L, 1); + (void) get_coord(L, 1, &tx, &ty); + x = tx; + y = ty; + } else if (argc == 2) { + sel = l_selection_check(L, 1); + tmpterrain.ter = check_mapchr(luaL_checkstring(L, 2)); + } else if (argc == 3) { + x = luaL_checkinteger(L, 1); + y = luaL_checkinteger(L, 2); + tmpterrain.ter = check_mapchr(luaL_checkstring(L, 3)); + } else { + nhl_error(L, "wrong parameters"); + } - if (!OV_pop_typ(ter, SPOVAR_MAPCHAR) || !OV_pop_typ(sel, SPOVAR_SEL)) - return; + if (tmpterrain.ter == INVALID_TYPE) + nhl_error(L, "Erroneous map char"); - tmpterrain.ter = SP_MAPCHAR_TYP(OV_i(ter)); - tmpterrain.tlit = SP_MAPCHAR_LIT(OV_i(ter)); - selection_iterate(sel, sel_set_ter, (genericptr_t) &tmpterrain); + if (sel) { + selection_iterate(sel, sel_set_ter, (genericptr_t) &tmpterrain); + } else { + get_location_coord(&x, &y, ANY_LOC, gc.coder->croom, + SP_COORD_PACK(x, y)); + if (!isok(x, y)) { + nhl_error(L, "terrain coord not ok"); + /*NOTREACHED*/ + return 0; + } + sel_set_ter(x, y, (genericptr_t) &tmpterrain); + } - opvar_free(ter); - opvar_free(sel); + return 0; } -void -spo_replace_terrain(coder) -struct sp_coder *coder; +/* + * replace_terrain({ x1=NN,y1=NN, x2=NN,y2=NN, fromterrain=MAPCHAR, + * toterrain=MAPCHAR, lit=N, chance=NN }); + * replace_terrain({ region={x1,y1, x2,y2}, fromterrain=MAPCHAR, + * toterrain=MAPCHAR, lit=N, chance=NN }); + * replace_terrain({ selection=selection.area(2,5, 40,10), + * fromterrain=MAPCHAR, toterrain=MAPCHAR }); + * replace_terrain({ selection=SEL, mapfragment=[[...]], + * toterrain=MAPCHAR }); + */ +int +lspo_replace_terrain(lua_State *L) { - static const char nhFunc[] = "spo_replace_terrain"; - replaceterrain rt; - struct opvar *reg, *from_ter, *to_ter, *chance; + coordxy totyp, fromtyp; + struct mapfragment *mf = NULL; + struct selectionvar *sel = NULL; + boolean freesel = FALSE; + coordxy x, y; + lua_Integer x1, y1, x2, y2; + int chance; + int tolit; + NhRect rect = cg.zeroNhRect; - if (!OV_pop_i(chance) || !OV_pop_typ(to_ter, SPOVAR_MAPCHAR) - || !OV_pop_typ(from_ter, SPOVAR_MAPCHAR) || !OV_pop_r(reg)) - return; + create_des_coder(); + + lcheck_param_table(L); + + totyp = get_table_mapchr(L, "toterrain"); + + if (totyp >= MAX_TYPE) + return 0; + + fromtyp = get_table_mapchr_opt(L, "fromterrain", INVALID_TYPE); + + if (fromtyp == INVALID_TYPE) { + const char *err; + char *tmpstr = get_table_str(L, "mapfragment"); + mf = mapfrag_fromstr(tmpstr); + free(tmpstr); + + if ((err = mapfrag_error(mf)) != NULL) { + nhl_error(L, err); + /*NOTREACHED*/ + } + } + + chance = get_table_int_opt(L, "chance", 100); + tolit = get_table_int_opt(L, "lit", SET_LIT_NOCHANGE); + x1 = get_table_int_opt(L, "x1", -1); + y1 = get_table_int_opt(L, "y1", -1); + x2 = get_table_int_opt(L, "x2", -1); + y2 = get_table_int_opt(L, "y2", -1); + + if (x1 == -1 && y1 == -1 && x2 == -1 && y2 == -1) { + get_table_region(L, "region", &x1, &y1, &x2, &y2, TRUE); + } + + if (x1 == -1 && y1 == -1 && x2 == -1 && y2 == -1) { + lua_getfield(L, 1, "selection"); + if (lua_type(L, -1) != LUA_TNIL) + sel = l_selection_check(L, -1); + lua_pop(L, 1); + } + + if (!sel) { + sel = selection_new(); + freesel = TRUE; + + if (x1 == -1 && y1 == -1 && x2 == -1 && y2 == -1) { + (void) selection_clear(sel, 1); + } else { + coordxy rx1, ry1, rx2, ry2; + rx1 = x1, ry1 = y1, rx2 = x2, ry2 = y2; + get_location(&rx1, &ry1, ANY_LOC, gc.coder->croom); + get_location(&rx2, &ry2, ANY_LOC, gc.coder->croom); + for (x = max(rx1, 0); x <= min(rx2, COLNO - 1); x++) + for (y = max(ry1, 0); y <= min(ry2, ROWNO - 1); y++) + selection_setpoint(x, y, sel, 1); + } + } + + selection_getbounds(sel, &rect); - rt.chance = OV_i(chance); - rt.tolit = SP_MAPCHAR_LIT(OV_i(to_ter)); - rt.toter = SP_MAPCHAR_TYP(OV_i(to_ter)); - rt.fromter = SP_MAPCHAR_TYP(OV_i(from_ter)); - /* TODO: use SP_MAPCHAR_LIT(OV_i(from_ter)) too */ - rt.x1 = SP_REGION_X1(OV_i(reg)); - rt.y1 = SP_REGION_Y1(OV_i(reg)); - rt.x2 = SP_REGION_X2(OV_i(reg)); - rt.y2 = SP_REGION_Y2(OV_i(reg)); - - replace_terrain(&rt, coder->croom); - - opvar_free(reg); - opvar_free(from_ter); - opvar_free(to_ter); - opvar_free(chance); -} - -STATIC_OVL boolean -generate_way_out_method(nx,ny, ov) -int nx,ny; -struct opvar *ov; -{ - static const char nhFunc[] = "generate_way_out_method"; - const int escapeitems[] = { PICK_AXE, - DWARVISH_MATTOCK, - WAN_DIGGING, - WAN_TELEPORTATION, - SCR_TELEPORTATION, - RIN_TELEPORTATION }; - struct opvar *ov2 = selection_opvar((char *) 0), *ov3; - schar x, y; + for (x = max(1, rect.lx); x <= rect.hx; x++) + for (y = rect.ly; y <= rect.hy; y++) + if (selection_getpoint(x, y,sel)) { + if (mf) { + if (mapfrag_match(mf, x, y) && (rn2(100)) < chance) + (void) set_levltyp_lit(x, y, totyp, tolit); + } else { + if (((fromtyp == MATCH_WALL && IS_STWALL(levl[x][y].typ)) + || levl[x][y].typ == fromtyp) + && rn2(100) < chance) + (void) set_levltyp_lit(x, y, totyp, tolit); + } + } + + if (freesel) + selection_free(sel, TRUE); + + mapfrag_free(&mf); + + return 0; +} + +staticfn boolean +generate_way_out_method( + coordxy nx, coordxy ny, + struct selectionvar *ov) +{ + static const int escapeitems[] = { + PICK_AXE, DWARVISH_MATTOCK, WAN_DIGGING, + WAN_TELEPORTATION, SCR_TELEPORTATION, RIN_TELEPORTATION + }; + struct selectionvar *ov2 = selection_new(), *ov3; + coordxy x, y; boolean res = TRUE; selection_floodfill(ov2, nx, ny, TRUE); - ov3 = opvar_clone(ov2); + ov3 = selection_clone(ov2); /* try to make a secret door */ while (selection_rndcoord(ov3, &x, &y, TRUE)) { - if (isok(x+1, y) && !selection_getpoint(x+1, y, ov) - && IS_WALL(levl[x+1][y].typ) - && isok(x+2, y) && selection_getpoint(x+2, y, ov) - && ACCESSIBLE(levl[x+2][y].typ)) { - levl[x+1][y].typ = SDOOR; + if (isok(x + 1, y) && !selection_getpoint(x + 1, y, ov) + && IS_WALL(levl[x + 1][y].typ) + && isok(x + 2, y) && selection_getpoint(x + 2, y, ov) + && ACCESSIBLE(levl[x + 2][y].typ)) { + levl[x + 1][y].typ = SDOOR; goto gotitdone; } - if (isok(x-1, y) && !selection_getpoint(x-1, y, ov) - && IS_WALL(levl[x-1][y].typ) - && isok(x-2, y) && selection_getpoint(x-2, y, ov) - && ACCESSIBLE(levl[x-2][y].typ)) { - levl[x-1][y].typ = SDOOR; + if (isok(x - 1, y) && !selection_getpoint(x - 1, y, ov) + && IS_WALL(levl[x - 1][y].typ) + && isok(x - 2, y) && selection_getpoint(x - 2, y, ov) + && ACCESSIBLE(levl[x - 2][y].typ)) { + levl[x - 1][y].typ = SDOOR; goto gotitdone; } - if (isok(x, y+1) && !selection_getpoint(x, y+1, ov) - && IS_WALL(levl[x][y+1].typ) - && isok(x, y+2) && selection_getpoint(x, y+2, ov) - && ACCESSIBLE(levl[x][y+2].typ)) { - levl[x][y+1].typ = SDOOR; + if (isok(x, y + 1) && !selection_getpoint(x, y + 1, ov) + && IS_WALL(levl[x][y + 1].typ) + && isok(x, y + 2) && selection_getpoint(x, y + 2, ov) + && ACCESSIBLE(levl[x][y + 2].typ)) { + levl[x][y + 1].typ = SDOOR; goto gotitdone; } - if (isok(x, y-1) && !selection_getpoint(x, y-1, ov) - && IS_WALL(levl[x][y-1].typ) - && isok(x, y-2) && selection_getpoint(x, y-2, ov) - && ACCESSIBLE(levl[x][y-2].typ)) { - levl[x][y-1].typ = SDOOR; + if (isok(x, y - 1) && !selection_getpoint(x, y - 1, ov) + && IS_WALL(levl[x][y - 1].typ) + && isok(x, y - 2) && selection_getpoint(x, y - 2, ov) + && ACCESSIBLE(levl[x][y - 2].typ)) { + levl[x][y - 1].typ = SDOOR; goto gotitdone; } } /* try to make a hole or a trapdoor */ if (Can_fall_thru(&u.uz)) { - opvar_free(ov3); - ov3 = opvar_clone(ov2); + selection_free(ov3, TRUE); + ov3 = selection_clone(ov2); while (selection_rndcoord(ov3, &x, &y, TRUE)) { - if (maketrap(x,y, rn2(2) ? HOLE : TRAPDOOR)) + if (maketrap(x, y, rn2(2) ? HOLE : TRAPDOOR)) goto gotitdone; } } /* generate one of the escape items */ if (selection_rndcoord(ov2, &x, &y, FALSE)) { - mksobj_at(escapeitems[rn2(SIZE(escapeitems))], x, y, TRUE, FALSE); + mksobj_at(ROLL_FROM(escapeitems), x, y, TRUE, FALSE); goto gotitdone; } res = FALSE; gotitdone: - opvar_free(ov2); - opvar_free(ov3); + selection_free(ov2, TRUE); + selection_free(ov3, TRUE); return res; } -STATIC_OVL void -ensure_way_out() +staticfn void +ensure_way_out(void) { - static const char nhFunc[] = "ensure_way_out"; - struct opvar *ov = selection_opvar((char *) 0); - struct trap *ttmp = ftrap; - int x,y; + struct selectionvar *ov = selection_new(); + struct trap *ttmp = gf.ftrap; + coordxy x, y; boolean ret = TRUE; + stairway *stway = gs.stairs; set_selection_floodfillchk(floodfillchk_match_accessible); - if (xupstair && !selection_getpoint(xupstair, yupstair, ov)) - selection_floodfill(ov, xupstair, yupstair, TRUE); - if (xdnstair && !selection_getpoint(xdnstair, ydnstair, ov)) - selection_floodfill(ov, xdnstair, ydnstair, TRUE); - if (xupladder && !selection_getpoint(xupladder, yupladder, ov)) - selection_floodfill(ov, xupladder, yupladder, TRUE); - if (xdnladder && !selection_getpoint(xdnladder, ydnladder, ov)) - selection_floodfill(ov, xdnladder, ydnladder, TRUE); + while (stway) { + if (stway->tolev.dnum == u.uz.dnum) + selection_floodfill(ov, stway->sx, stway->sy, TRUE); + stway = stway->next; + } while (ttmp) { - if ((ttmp->ttyp == MAGIC_PORTAL || ttmp->ttyp == VIBRATING_SQUARE - || is_hole(ttmp->ttyp)) + if ((undestroyable_trap(ttmp->ttyp) || is_hole(ttmp->ttyp)) && !selection_getpoint(ttmp->tx, ttmp->ty, ov)) selection_floodfill(ov, ttmp->tx, ttmp->ty, TRUE); ttmp = ttmp->ntrap; @@ -4493,180 +5239,450 @@ ensure_way_out() do { ret = TRUE; - for (x = 0; x < COLNO; x++) + for (x = 1; x < COLNO; x++) for (y = 0; y < ROWNO; y++) if (ACCESSIBLE(levl[x][y].typ) && !selection_getpoint(x, y, ov)) { - if (generate_way_out_method(x,y, ov)) - selection_floodfill(ov, x,y, TRUE); + if (generate_way_out_method(x, y, ov)) + selection_floodfill(ov, x, y, TRUE); ret = FALSE; goto outhere; } - outhere: ; + outhere: + ; } while (!ret); - opvar_free(ov); + selection_free(ov, TRUE); } -void -spo_levregion(coder) -struct sp_coder *coder; +DISABLE_WARNING_UNREACHABLE_CODE + +staticfn lua_Integer +get_table_intarray_entry(lua_State *L, int tableidx, int entrynum) +{ + lua_Integer ret = 0; + if (tableidx < 0) + tableidx--; + + lua_pushinteger(L, entrynum); + lua_gettable(L, tableidx); + if (lua_isnumber(L, -1)) { + ret = lua_tointeger(L, -1); + } else { + char buf[BUFSZ]; + + Sprintf(buf, "Array entry #%i is %s, expected number", + 1, luaL_typename(L, -1)); + nhl_error(L, buf); + } + lua_pop(L, 1); + return ret; +} + +staticfn int +get_table_region( + lua_State *L, + const char *name, + lua_Integer *x1, lua_Integer *y1, + lua_Integer *x2, lua_Integer *y2, + boolean optional) { - static const char nhFunc[] = "spo_levregion"; - struct opvar *rname, *padding, *rtype, *del_islev, *dy2, *dx2, *dy1, *dx1, - *in_islev, *iy2, *ix2, *iy1, *ix1; + lua_Integer arrlen; + + lua_getfield(L, 1, name); + if (optional && lua_type(L, -1) == LUA_TNIL) { + lua_pop(L, 1); + return 1; + } - lev_region *tmplregion; + luaL_checktype(L, -1, LUA_TTABLE); - if (!OV_pop_s(rname) || !OV_pop_i(padding) || !OV_pop_i(rtype) - || !OV_pop_i(del_islev) || !OV_pop_i(dy2) || !OV_pop_i(dx2) - || !OV_pop_i(dy1) || !OV_pop_i(dx1) || !OV_pop_i(in_islev) - || !OV_pop_i(iy2) || !OV_pop_i(ix2) || !OV_pop_i(iy1) - || !OV_pop_i(ix1)) - return; + lua_len(L, -1); + arrlen = lua_tointeger(L, -1); + lua_pop(L, 1); + if (arrlen != 4) { + nhl_error(L, "Not a region"); + /*NOTREACHED*/ + lua_pop(L, 1); + return 0; + } + + *x1 = get_table_intarray_entry(L, -1, 1); + *y1 = get_table_intarray_entry(L, -1, 2); + *x2 = get_table_intarray_entry(L, -1, 3); + *y2 = get_table_intarray_entry(L, -1, 4); + + lua_pop(L, 1); + return 1; +} + +boolean +get_coord(lua_State *L, int i, lua_Integer *x, lua_Integer *y) +{ + boolean ret = FALSE; + int ltyp = lua_type(L, i); - tmplregion = (lev_region *) alloc(sizeof(lev_region)); + if (ltyp == LUA_TTABLE) { + int arrlen; + boolean gotx = FALSE; + + lua_getfield(L, i, "x"); + if (!lua_isnil(L, -1)) { + *x = luaL_checkinteger(L, -1); + gotx = TRUE; + } + lua_pop(L, 1); + + if (gotx) { + lua_getfield(L, i, "y"); + if (!lua_isnil(L, -1)) { + *y = luaL_checkinteger(L, -1); + lua_pop(L, 1); + ret = TRUE; + } else { + nhl_error(L, "Not a coordinate"); + /*NOTREACHED*/ + return FALSE; + } + } else { + lua_len(L, i); + arrlen = lua_tointeger(L, -1); + lua_pop(L, 1); + if (arrlen != 2) { + nhl_error(L, "Not a coordinate"); + /*NOTREACHED*/ + return FALSE; + } - tmplregion->inarea.x1 = OV_i(ix1); - tmplregion->inarea.y1 = OV_i(iy1); - tmplregion->inarea.x2 = OV_i(ix2); - tmplregion->inarea.y2 = OV_i(iy2); + *x = get_table_intarray_entry(L, i, 1); + *y = get_table_intarray_entry(L, i, 2); - tmplregion->delarea.x1 = OV_i(dx1); - tmplregion->delarea.y1 = OV_i(dy1); - tmplregion->delarea.x2 = OV_i(dx2); - tmplregion->delarea.y2 = OV_i(dy2); + return TRUE; + } + } else if (ltyp != LUA_TNIL) { + /* non-existent coord is ok */ + nhl_error(L, "non-table coord specified"); + } + return ret; +} - tmplregion->in_islev = OV_i(in_islev); - tmplregion->del_islev = OV_i(del_islev); - tmplregion->rtype = OV_i(rtype); - tmplregion->padding = OV_i(padding); - tmplregion->rname.str = dupstr(OV_s(rname)); +RESTORE_WARNING_UNREACHABLE_CODE - if (!tmplregion->in_islev) { - get_location(&tmplregion->inarea.x1, &tmplregion->inarea.y1, ANY_LOC, +staticfn void +levregion_add(lev_region *lregion) +{ + if (!lregion->in_islev) { + get_location(&lregion->inarea.x1, &lregion->inarea.y1, ANY_LOC, (struct mkroom *) 0); - get_location(&tmplregion->inarea.x2, &tmplregion->inarea.y2, ANY_LOC, + get_location(&lregion->inarea.x2, &lregion->inarea.y2, ANY_LOC, (struct mkroom *) 0); } - if (!tmplregion->del_islev) { - get_location(&tmplregion->delarea.x1, &tmplregion->delarea.y1, + if (!lregion->del_islev) { + get_location(&lregion->delarea.x1, &lregion->delarea.y1, ANY_LOC, (struct mkroom *) 0); - get_location(&tmplregion->delarea.x2, &tmplregion->delarea.y2, + get_location(&lregion->delarea.x2, &lregion->delarea.y2, ANY_LOC, (struct mkroom *) 0); } - if (num_lregions) { + if (gn.num_lregions) { /* realloc the lregion space to add the new one */ lev_region *newl = (lev_region *) alloc( - sizeof(lev_region) * (unsigned) (1 + num_lregions)); + sizeof (lev_region) * (unsigned) (1 + gn.num_lregions)); - (void) memcpy((genericptr_t) (newl), (genericptr_t) lregions, - sizeof(lev_region) * num_lregions); - Free(lregions); - num_lregions++; - lregions = newl; + (void) memcpy((genericptr_t) (newl), (genericptr_t) gl.lregions, + sizeof (lev_region) * gn.num_lregions); + Free(gl.lregions); + gn.num_lregions++; + gl.lregions = newl; } else { - num_lregions = 1; - lregions = (lev_region *) alloc(sizeof(lev_region)); + gn.num_lregions = 1; + gl.lregions = (lev_region *) alloc(sizeof (lev_region)); } - (void) memcpy(&lregions[num_lregions - 1], tmplregion, - sizeof(lev_region)); - free(tmplregion); + (void) memcpy(&gl.lregions[gn.num_lregions - 1], lregion, + sizeof (lev_region)); +} + +/* get params from topmost lua hash: + - region = {x1,y1,x2,y2} + - exclude = {x1,y1,x2,y2} (optional) + - region_islev=true, exclude_islev=true (optional) + - negative x and y are invalid */ +staticfn void +l_get_lregion(lua_State *L, lev_region *tmplregion) +{ + lua_Integer x1,y1,x2,y2; + + get_table_region(L, "region", &x1, &y1, &x2, &y2, FALSE); + tmplregion->inarea.x1 = x1; + tmplregion->inarea.y1 = y1; + tmplregion->inarea.x2 = x2; + tmplregion->inarea.y2 = y2; + + x1 = y1 = x2 = y2 = -1; + get_table_region(L, "exclude", &x1, &y1, &x2, &y2, TRUE); + tmplregion->delarea.x1 = x1; + tmplregion->delarea.y1 = y1; + tmplregion->delarea.x2 = x2; + tmplregion->delarea.y2 = y2; + + tmplregion->in_islev = get_table_boolean_opt(L, "region_islev", 0); + tmplregion->del_islev = get_table_boolean_opt(L, "exclude_islev", 0); + + /* if x1 is still negative, exclude wasn't specified, so we should treat + * it as if there is no exclude region at all. Force exclude_islev to + * true so the -1,-1,-1,-1 region is safely off the map and won't + * interfere with branch or portal placement. */ + if (x1 < 0) + tmplregion->del_islev = TRUE; +} + +/* teleport_region({ region = { x1,y1, x2,y2 } }); */ +/* teleport_region({ region = { x1,y1, x2,y2 }, [ region_islev = 1, ] + * exclude = { x1,y1, x2,y2 }, [ exclude_islen = 1, ] [ dir = "up" ] }); */ +/* TODO: maybe allow using selection, with a new method "getextents()"? */ +int +lspo_teleport_region(lua_State *L) +{ + static const char *const teledirs[] = { "both", "down", "up", NULL }; + static const int teledirs2i[] = { LR_TELE, LR_DOWNTELE, LR_UPTELE, -1 }; + lev_region tmplregion; + + create_des_coder(); + lcheck_param_table(L); + l_get_lregion(L, &tmplregion); + tmplregion.rtype = teledirs2i[get_table_option(L, "dir", "both", + teledirs)]; + tmplregion.padding = 0; + tmplregion.rname.str = NULL; + + levregion_add(&tmplregion); - opvar_free(dx1); - opvar_free(dy1); - opvar_free(dx2); - opvar_free(dy2); + return 0; +} - opvar_free(ix1); - opvar_free(iy1); - opvar_free(ix2); - opvar_free(iy2); +/* TODO: FIXME + from lev_comp SPO_LEVREGION was called as: + - STAIR:(x1,y1,x2,y2),(x1,y1,x2,y2),dir + - PORTAL:(x1,y1,x2,y2),(x1,y1,x2,y2),string + - BRANCH:(x1,y1,x2,y2),(x1,y1,x2,y2),dir +*/ +/* levregion({ region = { x1,y1, x2,y2 }, exclude = { x1,y1, x2,y2 }, + * type = "portal", name="air" }); */ +/* TODO: allow region to be optional, defaulting to whole level */ +int +lspo_levregion(lua_State *L) +{ + static const char *const regiontypes[] = { + "stair-down", "stair-up", "portal", "branch", + "teleport", "teleport-up", "teleport-down", NULL + }; + static const int regiontypes2i[] = { + LR_DOWNSTAIR, LR_UPSTAIR, LR_PORTAL, LR_BRANCH, + LR_TELE, LR_UPTELE, LR_DOWNTELE, 0 + }; + lev_region tmplregion; + + create_des_coder(); + lcheck_param_table(L); + l_get_lregion(L, &tmplregion); + tmplregion.rtype = regiontypes2i[get_table_option(L, "type", "stair-down", + regiontypes)]; + tmplregion.padding = get_table_int_opt(L, "padding", 0); + tmplregion.rname.str = get_table_str_opt(L, "name", NULL); + + levregion_add(&tmplregion); + return 0; +} - opvar_free(del_islev); - opvar_free(in_islev); - opvar_free(rname); - opvar_free(rtype); - opvar_free(padding); +/* exclusion({ type = "teleport", region = { x1,y1, x2,y2 } }); */ +int +lspo_exclusion(lua_State *L) +{ + static const char *const ez_types[] = { + "teleport", "teleport-up", "teleport-down", "monster-generation", NULL + }; + static const int ez_types2i[] = { + LR_TELE, LR_UPTELE, LR_DOWNTELE, LR_MONGEN, 0 + }; + struct exclusion_zone *ez = (struct exclusion_zone *) alloc(sizeof *ez); + lua_Integer x1,y1,x2,y2; + coordxy a1,b1,a2,b2; + + create_des_coder(); + lcheck_param_table(L); + ez->zonetype = ez_types2i[get_table_option(L, "type", "teleport", + ez_types)]; + get_table_region(L, "region", &x1, &y1, &x2, &y2, FALSE); + + a1 = x1, b1 = y1; + a2 = x2, b2 = y2; + + get_location_coord(&a1, &b1, ANY_LOC|NO_LOC_WARN, gc.coder->croom, + SP_COORD_PACK(a1, b1)); + get_location_coord(&a2, &b2, ANY_LOC|NO_LOC_WARN, gc.coder->croom, + SP_COORD_PACK(a2, b2)); + + ez->lx = a1; + ez->ly = b1; + ez->hx = a2; + ez->hy = b2; + + ez->next = sve.exclusion_zones; + sve.exclusion_zones = ez; + return 0; } -void -spo_region(coder) -struct sp_coder *coder; +staticfn void +sel_set_lit(coordxy x, coordxy y, genericptr_t arg) { - static const char nhFunc[] = "spo_region"; - struct opvar *rtype, *rlit, *rflags, *area; - xchar dx1, dy1, dx2, dy2; - register struct mkroom *troom; - boolean prefilled, room_not_needed, irregular, joined; + int lit = *(int *) arg; - if (!OV_pop_i(rflags) || !OV_pop_i(rtype) || !OV_pop_i(rlit) - || !OV_pop_r(area)) - return; + levl[x][y].lit = (IS_LAVA(levl[x][y].typ) || lit) ? 1 : 0; +} - prefilled = !(OV_i(rflags) & (1 << 0)); - irregular = (OV_i(rflags) & (1 << 1)); - joined = !(OV_i(rflags) & (1 << 2)); +/* Add to the room any doors within/bordering it */ +staticfn void +add_doors_to_room(struct mkroom *croom) +{ + coordxy x, y; + int i; - if (OV_i(rtype) > MAXRTYPE) { - OV_i(rtype) -= MAXRTYPE + 1; - prefilled = TRUE; - } else - prefilled = FALSE; + for (x = croom->lx - 1; x <= croom->hx + 1; x++) + for (y = croom->ly - 1; y <= croom->hy + 1; y++) + if (IS_DOOR(levl[x][y].typ) || levl[x][y].typ == SDOOR) + maybe_add_door(x, y, croom); + for (i = 0; i < croom->nsubrooms; i++) + add_doors_to_room(croom->sbrooms[i]); +} + +DISABLE_WARNING_UNREACHABLE_CODE + +/* inside a lua table, get fields x1,y1,x2,y2 or region table */ +staticfn void +get_table_coords_or_region(lua_State *L, + coordxy *dx1, coordxy *dy1, + coordxy *dx2, coordxy *dy2) +{ + *dx1 = get_table_int_opt(L, "x1", -1); + *dy1 = get_table_int_opt(L, "y1", -1); + *dx2 = get_table_int_opt(L, "x2", -1); + *dy2 = get_table_int_opt(L, "y2", -1); + + if (*dx1 == -1 && *dy1 == -1 && *dx2 == -1 && *dy2 == -1) { + lua_Integer rx1, ry1, rx2, ry2; - if (OV_i(rlit) < 0) - OV_i(rlit) = - (rnd(1 + abs(depth(&u.uz))) < 11 && rn2(77)) ? TRUE : FALSE; + get_table_region(L, "region", &rx1, &ry1, &rx2, &ry2, FALSE); + *dx1 = rx1; *dy1 = ry1; + *dx2 = rx2; *dy2 = ry2; + } +} + +/* region(selection, lit); */ +/* region({ x1=NN, y1=NN, x2=NN, y2=NN, lit=BOOL, type=ROOMTYPE, joined=BOOL, + * irregular=BOOL, filled=NN [ , contents = FUNCTION ] }); */ +/* region({ region={x1,y1, x2,y2}, type="ordinary" }); */ +int +lspo_region(lua_State *L) +{ + coordxy dx1, dy1, dx2, dy2; + struct mkroom *troom; + boolean do_arrival_room = FALSE, room_not_needed, + irregular = FALSE, joined = TRUE; + int rtype = OROOM, rlit = 1, needfill = 0; + int argc = lua_gettop(L); + + create_des_coder(); + + if (argc <= 1) { + lcheck_param_table(L); + + /* TODO: "unfilled" => filled=0, "filled" => filled=1, and + * "lvflags_only" => filled=2, probably in a get_table_needfill_opt */ + needfill = get_table_int_opt(L, "filled", 0); + irregular = get_table_boolean_opt(L, "irregular", 0); + joined = get_table_boolean_opt(L, "joined", TRUE); + do_arrival_room = get_table_boolean_opt(L, "arrival_room", 0); + rtype = get_table_roomtype_opt(L, "type", OROOM); + rlit = get_table_int_opt(L, "lit", -1); + + get_table_coords_or_region(L, &dx1, &dy1, &dx2, &dy2); + + if (dx1 == -1 && dy1 == -1 && dx2 == -1 && dy2 == -1) { + nhl_error(L, "region needs region"); + } + + } else if (argc == 2) { + /* region(selection, "lit"); */ + static const char *const lits[] = { "unlit", "lit", NULL }; + struct selectionvar *orig = l_selection_check(L, 1), + *sel = selection_clone(orig); + + rlit = luaL_checkoption(L, 2, "lit", lits); + + /* + TODO: lit=random + */ + if (rlit) + selection_do_grow(sel, W_ANY); + selection_iterate(sel, sel_set_lit, (genericptr_t) &rlit); + + selection_free(sel, TRUE); - dx1 = SP_REGION_X1(OV_i(area)); - dy1 = SP_REGION_Y1(OV_i(area)); - dx2 = SP_REGION_X2(OV_i(area)); - dy2 = SP_REGION_Y2(OV_i(area)); + /* TODO: skip the rest of this function? */ + return 0; + } else { + nhl_error(L, "Wrong parameters"); + /*NOTREACHED*/ + return 0; + } + + rlit = litstate_rnd(rlit); get_location(&dx1, &dy1, ANY_LOC, (struct mkroom *) 0); get_location(&dx2, &dy2, ANY_LOC, (struct mkroom *) 0); - /* for an ordinary room, `prefilled' is a flag to force - an actual room to be created (such rooms are used to - control placement of migrating monster arrivals) */ - room_not_needed = (OV_i(rtype) == OROOM && !irregular && !prefilled); - if (room_not_needed || nroom >= MAXNROFROOMS) { + /* Many regions are simple, rectangular areas that just need to set + * lighting in an area. In that case, we don't need to do anything + * complicated by creating a room. The exceptions are: + * - Special rooms (which usually need to be filled). + * - Irregular regions (more convenient to use the room-making code). + * - Themed room regions (which often have contents). + * - When a room is desired to constrain the arrival of migrating + * monsters (see the mon_arrive function for details). + */ + room_not_needed = (rtype == OROOM && !irregular + && !do_arrival_room && !gi.in_mk_themerooms); + if (room_not_needed || svn.nroom >= MAXNROFROOMS) { region tmpregion; if (!room_not_needed) impossible("Too many rooms on new level!"); - tmpregion.rlit = OV_i(rlit); + tmpregion.rlit = rlit; tmpregion.x1 = dx1; tmpregion.y1 = dy1; tmpregion.x2 = dx2; tmpregion.y2 = dy2; light_region(&tmpregion); - opvar_free(area); - opvar_free(rflags); - opvar_free(rlit); - opvar_free(rtype); - - return; + return 0; } - troom = &rooms[nroom]; + troom = &svr.rooms[svn.nroom]; /* mark rooms that must be filled, but do it later */ - if (OV_i(rtype) != OROOM) - troom->needfill = (prefilled ? 2 : 1); + troom->needfill = needfill; troom->needjoining = joined; if (irregular) { - min_rx = max_rx = dx1; - min_ry = max_ry = dy1; - smeq[nroom] = nroom; - flood_fill_rm(dx1, dy1, nroom + ROOMOFFSET, OV_i(rlit), TRUE); - add_room(min_rx, min_ry, max_rx, max_ry, FALSE, OV_i(rtype), TRUE); - troom->rlit = OV_i(rlit); + gm.min_rx = gm.max_rx = dx1; + gm.min_ry = gm.max_ry = dy1; + gs.smeq[svn.nroom] = svn.nroom; + flood_fill_rm(dx1, dy1, svn.nroom + ROOMOFFSET, rlit, TRUE); + add_room(gm.min_rx, gm.min_ry, gm.max_rx, gm.max_ry, FALSE, rtype, + TRUE); + troom->rlit = rlit; troom->irregular = TRUE; } else { - add_room(dx1, dy1, dx2, dy2, OV_i(rlit), OV_i(rtype), TRUE); + add_room(dx1, dy1, dx2, dy2, rlit, rtype, TRUE); #ifdef SPECIALIZATION topologize(troom, FALSE); /* set roomno */ #else @@ -4675,75 +5691,136 @@ struct sp_coder *coder; } if (!room_not_needed) { - if (coder->n_subroom > 1) + if (gc.coder->n_subroom > 1) { impossible("region as subroom"); - else { - coder->tmproomlist[coder->n_subroom] = troom; - coder->failed_room[coder->n_subroom] = FALSE; - coder->n_subroom++; + } else { + gc.coder->tmproomlist[gc.coder->n_subroom] = troom; + gc.coder->failed_room[gc.coder->n_subroom] = FALSE; + gc.coder->n_subroom++; + update_croom(); + lua_getfield(L, 1, "contents"); + if (lua_type(L, -1) == LUA_TFUNCTION) { + lua_remove(L, -2); + l_push_mkroom_table(L, troom); + nhl_pcall_handle(L, 1, 0, "lspo_region", NHLpa_panic); + } else { + lua_pop(L, 1); + } + spo_endroom(gc.coder); + add_doors_to_room(troom); } } - opvar_free(area); - opvar_free(rflags); - opvar_free(rlit); - opvar_free(rtype); + return 0; } -void -spo_drawbridge(coder) -struct sp_coder *coder; -{ - static const char nhFunc[] = "spo_drawbridge"; - xchar x, y; - int dopen; - struct opvar *dir, *db_open, *dcoord; +/* drawbridge({ dir="east", state="closed", x=05,y=08 }); */ +/* drawbridge({ dir="east", state="closed", coord={05,08} }); */ +int +lspo_drawbridge(lua_State *L) +{ + static const char *const mwdirs[] = { + "north", "south", "west", "east", "random", NULL + }; + static const int mwdirs2i[] = { + DB_NORTH, DB_SOUTH, DB_WEST, DB_EAST, -1, -2 + }; + static const char *const dbopens[] = { + "open", "closed", "random", NULL + }; + static const int dbopens2i[] = { 1, 0, -1, -2 }; + coordxy x, y; + lua_Integer mx, my; + int dir; + int db_open; + long dcoord; - if (!OV_pop_i(dir) || !OV_pop_i(db_open) || !OV_pop_c(dcoord)) - return; + create_des_coder(); + + lcheck_param_table(L); + + get_table_xy_or_coord(L, &mx, &my); - get_location_coord(&x, &y, DRY | WET | HOT, coder->croom, OV_i(dcoord)); - if ((dopen = OV_i(db_open)) == -1) - dopen = !rn2(2); - if (!create_drawbridge(x, y, OV_i(dir), dopen ? TRUE : FALSE)) + dir = mwdirs2i[get_table_option(L, "dir", "random", mwdirs)]; + dcoord = SP_COORD_PACK(mx, my); + db_open = dbopens2i[get_table_option(L, "state", "random", dbopens)]; + x = mx; + y = my; + + get_location_coord(&x, &y, DRY | WET | HOT, gc.coder->croom, dcoord); + if (!isok(mx, my)) { + nhl_error(L, "drawbridge coord not ok"); + /*NOTREACHED*/ + return 0; + } + if (db_open == -1) + db_open = !rn2(2); + if (!create_drawbridge(x, y, dir, db_open ? TRUE : FALSE)) impossible("Cannot create drawbridge."); SpLev_Map[x][y] = 1; - opvar_free(dcoord); - opvar_free(db_open); - opvar_free(dir); + return 0; } /* * nle_spawn_monsters() indicates whether to spawn monsters randomly * after every step with some probability (true by default). */ -extern int FDECL(nle_spawn_monsters, ()); - +extern int nle_spawn_monsters(void); + +/* mazewalk({ x = NN, y = NN, typ = ".", dir = "north", stocked = 0 }); */ +/* mazewalk({ coord = {XX, YY}, typ = ".", dir = "north", stocked = 0 }); */ +/* mazewalk(x,y,dir); */ +int +lspo_mazewalk(lua_State *L) +{ + static const char *const mwdirs[] = { + "north", "south", "east", "west", "random", NULL + }; + static const int mwdirs2i[] = { + W_NORTH, W_SOUTH, W_EAST, W_WEST, W_RANDOM, -2 + }; + coordxy x, y; + lua_Integer mx, my; + coordxy ftyp = ROOM; + int fstocked = 1, dir = -1; + long mcoord; + int argc = lua_gettop(L); + + create_des_coder(); + + if (argc == 3) { + mx = luaL_checkinteger(L, 1); + my = luaL_checkinteger(L, 2); + dir = mwdirs2i[luaL_checkoption(L, 3, "random", mwdirs)]; + } else { + lcheck_param_table(L); -void -spo_mazewalk(coder) -struct sp_coder *coder; -{ - static const char nhFunc[] = "spo_mazewalk"; - xchar x, y; - struct opvar *ftyp, *fstocked, *fdir, *mcoord; - int dir; + get_table_xy_or_coord(L, &mx, &my); + ftyp = get_table_mapchr_opt(L, "typ", ROOM); + fstocked = get_table_boolean_opt(L, "stocked", 1); + dir = mwdirs2i[get_table_option(L, "dir", "random", mwdirs)]; + } - if (!OV_pop_i(ftyp) || !OV_pop_i(fstocked) || !OV_pop_i(fdir) - || !OV_pop_c(mcoord)) - return; + mcoord = SP_COORD_PACK(mx, my); + x = mx; + y = my; - dir = OV_i(fdir); + get_location_coord(&x, &y, ANY_LOC, gc.coder->croom, mcoord); - get_location_coord(&x, &y, ANY_LOC, coder->croom, OV_i(mcoord)); - if (!isok(x, y)) - return; + if (!isok(x, y)) { + nhl_error(L, "mazewalk coord not ok"); + /*NOTREACHED*/ + return 0; + } - if (OV_i(ftyp) < 1) { - OV_i(ftyp) = level.flags.corrmaze ? CORR : ROOM; + if (ftyp < 1) { + ftyp = svl.level.flags.corrmaze ? CORR : ROOM; } + if (dir == W_RANDOM) + dir = random_wdir(); + /* don't use move() - it doesn't use W_NORTH, etc. */ switch (dir) { case W_NORTH: @@ -4759,11 +5836,11 @@ struct sp_coder *coder; --x; break; default: - impossible("spo_mazewalk: Bad MAZEWALK direction"); + impossible("mazewalk: Bad direction"); } if (!IS_DOOR(levl[x][y].typ)) { - levl[x][y].typ = OV_i(ftyp); + levl[x][y].typ = ftyp; levl[x][y].flags = 0; } @@ -4779,7 +5856,7 @@ struct sp_coder *coder; x--; /* no need for IS_DOOR check; out of map bounds */ - levl[x][y].typ = OV_i(ftyp); + levl[x][y].typ = ftyp; levl[x][y].flags = 0; } @@ -4790,557 +5867,502 @@ struct sp_coder *coder; y--; } - walkfrom(x, y, OV_i(ftyp)); - /* Change for NLE: Optionally disable monster spawning */ - if (OV_i(fstocked) && nle_spawn_monsters()) + walkfrom(x, y, ftyp); + /* NLE: Optionally disable monster spawning */ + if (fstocked && nle_spawn_monsters()) fill_empty_maze(); - opvar_free(mcoord); - opvar_free(fdir); - opvar_free(fstocked); - opvar_free(ftyp); + return 0; } -void -spo_wall_property(coder) -struct sp_coder *coder; +RESTORE_WARNING_UNREACHABLE_CODE + +/* wall_property({ x1=0, y1=0, x2=78, y2=20, property="nondiggable" }); */ +/* wall_property({ region = {1,0, 78,20}, property="nonpasswall" }); */ +int +lspo_wall_property(lua_State *L) { - static const char nhFunc[] = "spo_wall_property"; - struct opvar *r; - xchar dx1, dy1, dx2, dy2; - int wprop = (coder->opcode == SPO_NON_DIGGABLE) - ? W_NONDIGGABLE - : W_NONPASSWALL; + static const char *const wprops[] = { + "nondiggable", "nonpasswall", NULL + }; + static const int wprop2i[] = { W_NONDIGGABLE, W_NONPASSWALL, -1 }; + coordxy dx1 = -1, dy1 = -1, dx2 = -1, dy2 = -1; + int wprop; - if (!OV_pop_r(r)) - return; + create_des_coder(); + + lcheck_param_table(L); - dx1 = SP_REGION_X1(OV_i(r)); - dy1 = SP_REGION_Y1(OV_i(r)); - dx2 = SP_REGION_X2(OV_i(r)); - dy2 = SP_REGION_Y2(OV_i(r)); + get_table_coords_or_region(L, &dx1, &dy1, &dx2, &dy2); + + wprop = wprop2i[get_table_option(L, "property", "nondiggable", wprops)]; + + if (dx1 == -1) + dx1 = gx.xstart - 1; + if (dy1 == -1) + dy1 = gy.ystart - 1; + if (dx2 == -1) + dx2 = gx.xstart + gx.xsize + 1; + if (dy2 == -1) + dy2 = gy.ystart + gy.ysize + 1; get_location(&dx1, &dy1, ANY_LOC, (struct mkroom *) 0); get_location(&dx2, &dy2, ANY_LOC, (struct mkroom *) 0); set_wall_property(dx1, dy1, dx2, dy2, wprop); - opvar_free(r); + return 0; } -void -spo_room_door(coder) -struct sp_coder *coder; +staticfn void +set_wallprop_in_selection(lua_State *L, int prop) { - static const char nhFunc[] = "spo_room_door"; - struct opvar *wall, *secret, *mask, *pos; - room_door tmpd; + int argc = lua_gettop(L); + boolean freesel = FALSE; + struct selectionvar *sel = (struct selectionvar *) 0; - if (!OV_pop_i(wall) || !OV_pop_i(secret) || !OV_pop_i(mask) - || !OV_pop_i(pos) || !coder->croom) - return; + create_des_coder(); + + if (argc == 1) { + sel = l_selection_check(L, -1); + } else if (argc == 0) { + freesel = TRUE; + sel = selection_new(); + selection_clear(sel, 1); + } - tmpd.secret = OV_i(secret); - tmpd.mask = OV_i(mask); - tmpd.pos = OV_i(pos); - tmpd.wall = OV_i(wall); + if (sel) { + selection_iterate(sel, sel_set_wall_property, (genericptr_t) &prop); + if (freesel) + selection_free(sel, TRUE); + } +} - create_door(&tmpd, coder->croom); +/* non_diggable(selection); */ +/* non_diggable(); */ +int +lspo_non_diggable(lua_State *L) +{ + set_wallprop_in_selection(L, W_NONDIGGABLE); + return 0; +} - opvar_free(wall); - opvar_free(secret); - opvar_free(mask); - opvar_free(pos); +/* non_passwall(selection); */ +/* non_passwall(); */ +int +lspo_non_passwall(lua_State *L) +{ + set_wallprop_in_selection(L, W_NONPASSWALL); + return 0; } +#if 0 /*ARGSUSED*/ -void -sel_set_wallify(x, y, arg) -int x, y; -genericptr_t arg UNUSED; +staticfn void +sel_set_wallify(coordxy x, coordxy y, genericptr_t arg UNUSED) { wallify_map(x, y, x, y); } +#endif -void -spo_wallify(coder) -struct sp_coder *coder; +/* TODO: wallify(selection) */ +/* wallify({ x1=NN,y1=NN, x2=NN,y2=NN }); */ +/* wallify(); */ +int +lspo_wallify(lua_State *L) { - static const char nhFunc[] = "spo_wallify"; - struct opvar *typ, *r; - int dx1, dy1, dx2, dy2; + int dx1 = -1, dy1 = -1, dx2 = -1, dy2 = -1; - if (!OV_pop_i(typ)) - return; - switch (OV_i(typ)) { - default: - case 0: - if (!OV_pop_r(r)) - return; - dx1 = (xchar) SP_REGION_X1(OV_i(r)); - dy1 = (xchar) SP_REGION_Y1(OV_i(r)); - dx2 = (xchar) SP_REGION_X2(OV_i(r)); - dy2 = (xchar) SP_REGION_Y2(OV_i(r)); - wallify_map(dx1 < 0 ? (xstart - 1) : dx1, - dy1 < 0 ? (ystart - 1) : dy1, - dx2 < 0 ? (xstart + xsize + 1) : dx2, - dy2 < 0 ? (ystart + ysize + 1) : dy2); - break; - case 1: - if (!OV_pop_typ(r, SPOVAR_SEL)) - return; - selection_iterate(r, sel_set_wallify, NULL); - break; + /* TODO: clamp coord values */ + /* TODO: maybe allow wallify({x1,y1}, {x2,y2}) */ + /* TODO: is_table_coord(), is_table_area(), + get_table_coord(), get_table_area() */ + + create_des_coder(); + + if (lua_gettop(L) == 1) { + dx1 = get_table_int(L, "x1"); + dy1 = get_table_int(L, "y1"); + dx2 = get_table_int(L, "x2"); + dy2 = get_table_int(L, "y2"); } - opvar_free(r); - opvar_free(typ); + + wallify_map(dx1 < 0 ? (gx.xstart - 1) : dx1, + dy1 < 0 ? (gy.ystart - 1) : dy1, + dx2 < 0 ? (gx.xstart + gx.xsize + 1) : dx2, + dy2 < 0 ? (gy.ystart + gy.ysize + 1) : dy2); + + return 0; } -void -spo_map(coder) -struct sp_coder *coder; -{ - static const char nhFunc[] = "spo_map"; - mazepart tmpmazepart; - struct opvar *mpxs, *mpys, *mpmap, *mpa, *mpkeepr, *mpzalign; - xchar halign, valign; - xchar tmpxstart, tmpystart, tmpxsize, tmpysize; - unpacked_coord upc; - - if (!OV_pop_i(mpxs) || !OV_pop_i(mpys) || !OV_pop_s(mpmap) - || !OV_pop_i(mpkeepr) || !OV_pop_i(mpzalign) || !OV_pop_c(mpa)) - return; +/* reset_level is only needed for testing purposes */ +int +lspo_reset_level(lua_State *L) +{ + boolean wtower = In_W_tower(u.ux, u.uy, &u.uz); - tmpmazepart.xsize = OV_i(mpxs); - tmpmazepart.ysize = OV_i(mpys); - tmpmazepart.zaligntyp = OV_i(mpzalign); + iflags.lua_testing = TRUE; + if (L) { + if (gc.coder) { + Free(gc.coder); + gc.coder = NULL; + } + create_des_coder(); + } + makemap_prepost(TRUE, wtower); + gi.in_mklev = TRUE; + oinit(); /* assign level dependent obj probabilities */ + clear_level_structures(); + return 0; +} - upc = get_unpacked_coord(OV_i(mpa), ANY_LOC); - tmpmazepart.halign = upc.x; - tmpmazepart.valign = upc.y; +/* finalize_level is only needed for testing purposes */ +int +lspo_finalize_level(lua_State *L) +{ + boolean wtower = In_W_tower(u.ux, u.uy, &u.uz); + int i; - tmpxsize = xsize; - tmpysize = ysize; - tmpxstart = xstart; - tmpystart = ystart; + if (L) + create_des_coder(); - halign = tmpmazepart.halign; - valign = tmpmazepart.valign; - xsize = tmpmazepart.xsize; - ysize = tmpmazepart.ysize; - switch (tmpmazepart.zaligntyp) { - default: - case 0: - break; - case 1: - switch ((int) halign) { - case LEFT: - xstart = splev_init_present ? 1 : 3; + link_doors_rooms(); + remove_boundary_syms(); + + /* TODO: ensure_way_out() needs rewrite */ + if (L && gc.coder->check_inaccessibles) + ensure_way_out(); + + map_cleanup(); + + /* FIXME: Ideally, we want this call to only cover areas of the map + * which were not inserted directly by the special level file (see + * the insect legs on Baalzebub's level, for instance). Since that + * is currently not possible, we overload the corrmaze flag for this + * purpose. + */ + if (!svl.level.flags.corrmaze) + wallification(1, 0, COLNO - 1, ROWNO - 1); + + if (L) + flip_level_rnd(gc.coder->allow_flips, FALSE); + + count_level_features(); + + if (L && gc.coder->solidify) + solidify_map(); + + /* This must be done before premap_detect(), + * otherwise branch stairs won't be premapped. */ + fixup_special(); + + if (L && gc.coder->premapped) + premap_detect(); + + level_finalize_topology(); + + for (i = 0; i < svn.nroom; ++i) { + fill_special_room(&svr.rooms[i]); + } + + makemap_prepost(FALSE, wtower); + iflags.lua_testing = FALSE; + return 0; +} + +DISABLE_WARNING_UNREACHABLE_CODE + +/* map({ x = 10, y = 10, map = [[...]] }); */ +/* map({ coord = {10, 10}, map = [[...]] }); */ +/* map({ halign = "center", valign = "center", map = [[...]] }); */ +/* map({ map = [[...]], contents = function(map) ... end }); */ +/* map([[...]]) */ +/* local selection = map( ... ); */ +int +lspo_map(lua_State *L) +{ + /* +TODO: allow passing an array of strings as map data +TODO: handle if map lines aren't same length +TODO: gc.coder->croom needs to be updated + */ + + static const char *const left_or_right[] = { + "left", "half-left", "center", "half-right", "right", "none", NULL + }; + static const int l_or_r2i[] = { + SPLEV_LEFT, SPLEV_H_LEFT, SPLEV_CENTER, SPLEV_H_RIGHT, + SPLEV_RIGHT, -1, -1 + }; + static const char *const top_or_bot[] = { + "top", "center", "bottom", "none", NULL + }; + static const int t_or_b2i[] = { TOP, SPLEV_CENTER, BOTTOM, -1, -1 }; + int lr, tb; + lua_Integer x = -1, y = -1; + struct mapfragment *mf; + char *tmpstr; + int argc = lua_gettop(L); + boolean has_contents = FALSE; + int tryct = 0; + int ox, oy; + boolean lit = FALSE; + struct selectionvar *sel; + + create_des_coder(); + + if (gi.in_mk_themerooms && gt.themeroom_failed) + return 0; + + if (argc == 1 && lua_type(L, 1) == LUA_TSTRING) { + tmpstr = dupstr(luaL_checkstring(L, 1)); + lr = tb = SPLEV_CENTER; + mf = mapfrag_fromstr(tmpstr); + free(tmpstr); + } else { + lcheck_param_table(L); + lr = l_or_r2i[get_table_option(L, "halign", "none", left_or_right)]; + tb = t_or_b2i[get_table_option(L, "valign", "none", top_or_bot)]; + get_table_xy_or_coord(L, &x, &y); + tmpstr = get_table_str(L, "map"); + lit = (boolean) get_table_boolean_opt(L, "lit", FALSE); + lua_getfield(L, 1, "contents"); + if (lua_type(L, -1) == LUA_TFUNCTION) { + lua_remove(L, -2); + has_contents = TRUE; + } else { + lua_pop(L, 1); + } + mf = mapfrag_fromstr(tmpstr); + free(tmpstr); + } + + if (!mf) { + nhl_error(L, "Map data error"); + /*NOTREACHED*/ + return 0; + } + + sel = selection_new(); + ox = x; + oy = y; + redo_maploc: + gx.xsize = mf->wid; + gy.ysize = mf->hei; + + if (lr == -1 && tb == -1) { + if (gi.in_mk_themerooms && (ox == -1 || oy == -1)) { + if (ox == -1) { + if (gc.coder->croom) { + x = somex(gc.coder->croom) - mf->wid; + if (x < 1) + x = 1; + } else { + x = 1 + rn2(COLNO - 1 - mf->wid); + } + } + + if (oy == -1) { + if (gc.coder->croom) { + y = somey(gc.coder->croom) - mf->hei; + if (y < 1) + y = 1; + } else { + y = rn2(ROWNO - mf->hei); + } + } + } + + if (isok(x, y)) { + /* x,y is given, place map starting at x,y */ + if (gc.coder->croom) { + /* in a room? adjust to room relative coords */ + gx.xstart = x + gc.coder->croom->lx; + gy.ystart = y + gc.coder->croom->ly; + gx.xsize = min(mf->wid, + (gc.coder->croom->hx - gc.coder->croom->lx)); + gy.ysize = min(mf->hei, + (gc.coder->croom->hy - gc.coder->croom->ly)); + } else { + gx.xsize = mf->wid; + gy.ysize = mf->hei; + gx.xstart = x; + gy.ystart = y; + } + } else { + mapfrag_free(&mf); + nhl_error(L, "Map requires either x,y or halign,valign params"); + selection_free(sel, TRUE); + return 0; + } + } else { + /* place map starting at halign,valign */ + switch (lr) { + case SPLEV_LEFT: + gx.xstart = splev_init_present ? 1 : 3; break; - case H_LEFT: - xstart = 2 + ((x_maze_max - 2 - xsize) / 4); + case SPLEV_H_LEFT: + gx.xstart = 2 + ((gx.x_maze_max - 2 - gx.xsize) / 4); break; - case CENTER: - xstart = 2 + ((x_maze_max - 2 - xsize) / 2); + case SPLEV_CENTER: + gx.xstart = 2 + ((gx.x_maze_max - 2 - gx.xsize) / 2); break; - case H_RIGHT: - xstart = 2 + ((x_maze_max - 2 - xsize) * 3 / 4); + case SPLEV_H_RIGHT: + gx.xstart = 2 + ((gx.x_maze_max - 2 - gx.xsize) * 3 / 4); break; - case RIGHT: - xstart = x_maze_max - xsize - 1; + case SPLEV_RIGHT: + gx.xstart = gx.x_maze_max - gx.xsize - 1; break; } - switch ((int) valign) { + switch (tb) { case TOP: - ystart = 3; + gy.ystart = 3; break; - case CENTER: - ystart = 2 + ((y_maze_max - 2 - ysize) / 2); + case SPLEV_CENTER: + gy.ystart = 2 + ((gy.y_maze_max - 2 - gy.ysize) / 2); break; case BOTTOM: - ystart = y_maze_max - ysize - 1; + gy.ystart = gy.y_maze_max - gy.ysize - 1; break; } - if (!(xstart % 2)) - xstart++; - if (!(ystart % 2)) - ystart++; - break; - case 2: - if (!coder->croom) { - xstart = 1; - ystart = 0; - xsize = COLNO - 1 - tmpmazepart.xsize; - ysize = ROWNO - tmpmazepart.ysize; - } - get_location_coord(&halign, &valign, ANY_LOC, coder->croom, - OV_i(mpa)); - xsize = tmpmazepart.xsize; - ysize = tmpmazepart.ysize; - xstart = halign; - ystart = valign; - break; + if (!(gx.xstart % 2)) + gx.xstart++; + if (!(gy.ystart % 2)) + gy.ystart++; } - if (ystart < 0 || ystart + ysize > ROWNO) { + + if (gy.ystart < 0 || gy.ystart + gy.ysize > ROWNO) { + if (gi.in_mk_themerooms) { + gt.themeroom_failed = TRUE; + goto skipmap; + } /* try to move the start a bit */ - ystart += (ystart > 0) ? -2 : 2; - if (ysize == ROWNO) - ystart = 0; - if (ystart < 0 || ystart + ysize > ROWNO) - panic("reading special level with ysize too large"); - } - if (xsize <= 1 && ysize <= 1) { - xstart = 1; - ystart = 0; - xsize = COLNO - 1; - ysize = ROWNO; + gy.ystart += (gy.ystart > 0) ? -2 : 2; + if (gy.ysize == ROWNO) + gy.ystart = 0; + if (gy.ystart < 0 || gy.ystart + gy.ysize > ROWNO) + gy.ystart = 0; + } + if (gx.xsize <= 1 && gy.ysize <= 1) { + reset_xystart_size(); } else { - xchar x, y, mptyp; + coordxy mptyp; + terrain terr; + + /* Themed rooms should never overwrite anything */ + if (gi.in_mk_themerooms) { + boolean isokp = TRUE; + for (y = gy.ystart - 1; + y < min(ROWNO, gy.ystart + gy.ysize) + 1; y++) + for (x = gx.xstart - 1; + x < min(COLNO, gx.xstart + gx.xsize) + 1; x++) { + if (!isok(x, y)) { + isokp = FALSE; + } else if (y < gy.ystart || y >= (gy.ystart + gy.ysize) + || x < gx.xstart || x >= (gx.xstart + gx.xsize)) { + if (levl[x][y].typ != STONE + || levl[x][y].roomno != NO_ROOM) + isokp = FALSE; + } else { + mptyp = mapfrag_get(mf, x - gx.xstart, y - gy.ystart); + if (mptyp >= MAX_TYPE) + continue; + if ((levl[x][y].typ != STONE + && levl[x][y].typ != mptyp) + || levl[x][y].roomno != NO_ROOM) + isokp = FALSE; + } + if (!isokp) { + if (tryct++ < 100 && (lr == -1 || tb == -1)) + goto redo_maploc; + gt.themeroom_failed = TRUE; + goto skipmap; + } + } + } /* Load the map */ - for (y = ystart; y < ystart + ysize; y++) - for (x = xstart; x < xstart + xsize; x++) { - mptyp = (mpmap->vardata.str[(y - ystart) * xsize - + (x - xstart)] - 1); + for (y = gy.ystart; y < min(ROWNO, gy.ystart + gy.ysize); y++) + for (x = gx.xstart; x < min(COLNO, gx.xstart + gx.xsize); x++) { + mptyp = mapfrag_get(mf, (x - gx.xstart), (y - gy.ystart)); + if (mptyp == INVALID_TYPE) { + /* TODO: warn about illegal map char */ + continue; + } if (mptyp >= MAX_TYPE) continue; - levl[x][y].typ = mptyp; - levl[x][y].lit = FALSE; /* clear out levl: load_common_data may set them */ levl[x][y].flags = 0; levl[x][y].horizontal = 0; levl[x][y].roomno = 0; levl[x][y].edge = 0; SpLev_Map[x][y] = 1; - /* - * Set secret doors to closed (why not trapped too?). Set - * the horizontal bit. - */ - if (levl[x][y].typ == SDOOR || IS_DOOR(levl[x][y].typ)) { - if (levl[x][y].typ == SDOOR) - levl[x][y].doormask = D_CLOSED; - /* - * If there is a wall to the left that connects to a - * (secret) door, then it is horizontal. This does - * not allow (secret) doors to be corners of rooms. - */ - if (x != xstart && (IS_WALL(levl[x - 1][y].typ) - || levl[x - 1][y].horizontal)) - levl[x][y].horizontal = 1; - } else if (levl[x][y].typ == HWALL - || levl[x][y].typ == IRONBARS) - levl[x][y].horizontal = 1; - else if (levl[x][y].typ == LAVAPOOL) - levl[x][y].lit = 1; - else if (splev_init_present && levl[x][y].typ == ICE) - levl[x][y].icedpool = icedpools ? ICED_POOL : ICED_MOAT; + selection_setpoint(x, y, sel, 1); + terr.ter = mptyp; + terr.tlit = lit; + sel_set_ter(x, y, &terr); } - if (coder->lvl_is_joined) - remove_rooms(xstart, ystart, xstart + xsize, ystart + ysize); - } - if (!OV_i(mpkeepr)) { - xstart = tmpxstart; - ystart = tmpystart; - xsize = tmpxsize; - ysize = tmpysize; } - opvar_free(mpxs); - opvar_free(mpys); - opvar_free(mpmap); - opvar_free(mpa); - opvar_free(mpkeepr); - opvar_free(mpzalign); -} - -void -spo_jmp(coder, lvl) -struct sp_coder *coder; -sp_lev *lvl; -{ - static const char nhFunc[] = "spo_jmp"; - struct opvar *tmpa; - long a; - - if (!OV_pop_i(tmpa)) - return; - a = sp_code_jmpaddr(coder->frame->n_opcode, (OV_i(tmpa) - 1)); - if ((a >= 0) && (a < lvl->n_opcodes) && (a != coder->frame->n_opcode)) - coder->frame->n_opcode = a; - opvar_free(tmpa); -} - -void -spo_conditional_jump(coder, lvl) -struct sp_coder *coder; -sp_lev *lvl; -{ - static const char nhFunc[] = "spo_conditional_jump"; - struct opvar *oa, *oc; - long a, c; - int test = 0; - - if (!OV_pop_i(oa) || !OV_pop_i(oc)) - return; - - a = sp_code_jmpaddr(coder->frame->n_opcode, (OV_i(oa) - 1)); - c = OV_i(oc); + skipmap: + mapfrag_free(&mf); - switch (coder->opcode) { - default: - impossible("spo_conditional_jump: illegal opcode"); - break; - case SPO_JL: - test = (c & SP_CPUFLAG_LT); - break; - case SPO_JLE: - test = (c & (SP_CPUFLAG_LT | SP_CPUFLAG_EQ)); - break; - case SPO_JG: - test = (c & SP_CPUFLAG_GT); - break; - case SPO_JGE: - test = (c & (SP_CPUFLAG_GT | SP_CPUFLAG_EQ)); - break; - case SPO_JE: - test = (c & SP_CPUFLAG_EQ); - break; - case SPO_JNE: - test = (c & ~SP_CPUFLAG_EQ); - break; + if (gi.in_mk_themerooms && gt.themeroom_failed) { + /* this mutated xstart and ystart in the process of trying to make a + * themed room, so undo them */ + reset_xystart_size(); } - - if ((test) && (a >= 0) && (a < lvl->n_opcodes) - && (a != coder->frame->n_opcode)) - coder->frame->n_opcode = a; - - opvar_free(oa); - opvar_free(oc); -} - -void -spo_var_init(coder) -struct sp_coder *coder; -{ - static const char nhFunc[] = "spo_var_init"; - struct opvar *vname; - struct opvar *arraylen; - struct opvar *vvalue; - struct splev_var *tmpvar; - struct splev_var *tmp2; - long idx; - - OV_pop_s(vname); - OV_pop_i(arraylen); - - if (!vname || !arraylen) - panic("no values for SPO_VAR_INIT"); - - tmpvar = opvar_var_defined(coder, OV_s(vname)); - - if (tmpvar) { - /* variable redefinition */ - if (OV_i(arraylen) < 0) { - /* copy variable */ - if (tmpvar->array_len) { - idx = tmpvar->array_len; - while (idx-- > 0) { - opvar_free(tmpvar->data.arrayvalues[idx]); - } - Free(tmpvar->data.arrayvalues); - } else { - opvar_free(tmpvar->data.value); - } - tmpvar->data.arrayvalues = NULL; - goto copy_variable; - } else if (OV_i(arraylen)) { - /* redefined array */ - idx = tmpvar->array_len; - while (idx-- > 0) { - opvar_free(tmpvar->data.arrayvalues[idx]); - } - Free(tmpvar->data.arrayvalues); - tmpvar->data.arrayvalues = NULL; - goto create_new_array; - } else { - /* redefined single value */ - OV_pop(vvalue); - if (tmpvar->svtyp != vvalue->spovartyp) - panic("redefining variable as different type"); - opvar_free(tmpvar->data.value); - tmpvar->data.value = vvalue; - tmpvar->array_len = 0; - } - } else { - /* new variable definition */ - tmpvar = (struct splev_var *) alloc(sizeof(struct splev_var)); - tmpvar->next = coder->frame->variables; - tmpvar->name = dupstr(OV_s(vname)); - coder->frame->variables = tmpvar; - - if (OV_i(arraylen) < 0) { - /* copy variable */ - copy_variable: - OV_pop(vvalue); - tmp2 = opvar_var_defined(coder, OV_s(vvalue)); - if (!tmp2) - panic("no copyable var"); - tmpvar->svtyp = tmp2->svtyp; - tmpvar->array_len = tmp2->array_len; - if (tmpvar->array_len) { - idx = tmpvar->array_len; - tmpvar->data.arrayvalues = - (struct opvar **) alloc(sizeof(struct opvar *) * idx); - while (idx-- > 0) { - tmpvar->data.arrayvalues[idx] = - opvar_clone(tmp2->data.arrayvalues[idx]); - } - } else { - tmpvar->data.value = opvar_clone(tmp2->data.value); - } - opvar_free(vvalue); - } else if (OV_i(arraylen)) { - /* new array */ - create_new_array: - idx = OV_i(arraylen); - tmpvar->array_len = idx; - tmpvar->data.arrayvalues = - (struct opvar **) alloc(sizeof(struct opvar *) * idx); - while (idx-- > 0) { - OV_pop(vvalue); - if (!vvalue) - panic("no value for arrayvariable"); - tmpvar->data.arrayvalues[idx] = vvalue; - } - tmpvar->svtyp = SPOVAR_ARRAY; - } else { - /* new single value */ - OV_pop(vvalue); - if (!vvalue) - panic("no value for variable"); - tmpvar->svtyp = OV_typ(vvalue); - tmpvar->data.value = vvalue; - tmpvar->array_len = 0; - } + else if (has_contents) { + l_push_wid_hei_table(L, gx.xsize, gy.ysize); + nhl_pcall_handle(L, 1, 0, "lspo_map", NHLpa_panic); + reset_xystart_size(); } - opvar_free(vname); - opvar_free(arraylen); + /* return selection where map locations were put */ + l_selection_push_copy(L, sel); + selection_free(sel, TRUE); + return 1; } -#if 0 -STATIC_OVL long -opvar_array_length(coder) -struct sp_coder *coder; -{ - static const char nhFunc[] = "opvar_array_length"; - struct opvar *vname; - struct splev_var *tmp; - long len = 0; - - if (!coder) - return 0; - - vname = splev_stack_pop(coder->stack); - if (!vname) - return 0; - if (vname->spovartyp != SPOVAR_VARIABLE) - goto pass; - - tmp = coder->frame->variables; - while (tmp) { - if (!strcmp(tmp->name, OV_s(vname))) { - if ((tmp->svtyp & SPOVAR_ARRAY)) { - len = tmp->array_len; - if (len < 1) - len = 0; - } - goto pass; - } - tmp = tmp->next; - } - -pass: - opvar_free(vname); - return len; -} -#endif /*0*/ +RESTORE_WARNING_UNREACHABLE_CODE void -spo_shuffle_array(coder) -struct sp_coder *coder; +update_croom(void) { - static const char nhFunc[] = "spo_shuffle_array"; - struct opvar *vname; - struct splev_var *tmp; - struct opvar *tmp2; - long i, j; - - if (!OV_pop_s(vname)) + if (!gc.coder) return; - tmp = opvar_var_defined(coder, OV_s(vname)); - if (!tmp || (tmp->array_len < 1)) { - opvar_free(vname); - return; - } - for (i = tmp->array_len - 1; i > 0; i--) { - if ((j = rn2(i + 1)) == i) - continue; - tmp2 = tmp->data.arrayvalues[j]; - tmp->data.arrayvalues[j] = tmp->data.arrayvalues[i]; - tmp->data.arrayvalues[i] = tmp2; - } - - opvar_free(vname); + if (gc.coder->n_subroom) + gc.coder->croom = gc.coder->tmproomlist[gc.coder->n_subroom - 1]; + else + gc.coder->croom = NULL; } -/* Special level coder, creates the special level from the sp_lev codes. - * Does not free the allocated memory. - */ -STATIC_OVL boolean -sp_level_coder(lvl) -sp_lev *lvl; +staticfn struct sp_coder * +sp_level_coder_init(void) { - static const char nhFunc[] = "sp_level_coder"; - unsigned long exec_opcodes = 0; int tmpi; - long room_stack = 0; - unsigned long max_execution = SPCODER_MAX_RUNTIME; - struct sp_coder *coder = - (struct sp_coder *) alloc(sizeof (struct sp_coder)); + struct sp_coder *coder = (struct sp_coder *) alloc(sizeof *coder); - coder->frame = frame_new(0); - coder->stack = NULL; coder->premapped = FALSE; coder->solidify = FALSE; coder->check_inaccessibles = FALSE; + coder->allow_flips = 3; /* allow flipping level horiz/vert */ coder->croom = NULL; coder->n_subroom = 1; - coder->exit_script = FALSE; - coder->lvl_is_joined = 0; + coder->lvl_is_joined = FALSE; + coder->room_stack = 0; splev_init_present = FALSE; icedpools = FALSE; - /* achievement tracking; static init would suffice except we need to - reset if #wizmakemap is used to recreate mines' end or sokoban end; - once either level is created, these values can be forgotten */ - mines_prize_count = soko_prize_count = 0; - - if (wizard) { - char *met = nh_getenv("SPCODER_MAX_RUNTIME"); - - if (met && met[0] == '1') - max_execution = (1 << 30) - 1; - } for (tmpi = 0; tmpi <= MAX_NESTED_ROOMS; tmpi++) { coder->tmproomlist[tmpi] = (struct mkroom *) 0; coder->failed_room[tmpi] = FALSE; } - shuffle_alignments(); + update_croom(); for (tmpi = 0; tmpi < MAX_CONTAINMENT; tmpi++) container_obj[tmpi] = NULL; @@ -5348,720 +6370,142 @@ sp_lev *lvl; invent_carrying_monster = NULL; - (void) memset((genericptr_t) &SpLev_Map[0][0], 0, sizeof SpLev_Map); - - level.flags.is_maze_lev = 0; - - xstart = 1; - ystart = 0; - xsize = COLNO - 1; - ysize = ROWNO; - - while (coder->frame->n_opcode < lvl->n_opcodes && !coder->exit_script) { - coder->opcode = lvl->opcodes[coder->frame->n_opcode].opcode; - coder->opdat = lvl->opcodes[coder->frame->n_opcode].opdat; - - coder->stack = coder->frame->stack; - - if (exec_opcodes++ > max_execution) { - impossible("Level script is taking too much time, stopping."); - coder->exit_script = TRUE; - } - - if (coder->failed_room[coder->n_subroom - 1] - && coder->opcode != SPO_ENDROOM && coder->opcode != SPO_ROOM - && coder->opcode != SPO_SUBROOM) - goto next_opcode; - - coder->croom = coder->tmproomlist[coder->n_subroom - 1]; - - switch (coder->opcode) { - case SPO_NULL: - break; - case SPO_EXIT: - coder->exit_script = TRUE; - break; - case SPO_FRAME_PUSH: - spo_frame_push(coder); - break; - case SPO_FRAME_POP: - spo_frame_pop(coder); - break; - case SPO_CALL: - spo_call(coder); - break; - case SPO_RETURN: - spo_return(coder); - break; - case SPO_END_MONINVENT: - spo_end_moninvent(coder); - break; - case SPO_POP_CONTAINER: - spo_pop_container(coder); - break; - case SPO_POP: { - struct opvar *ov = splev_stack_pop(coder->stack); - - opvar_free(ov); - break; - } - case SPO_PUSH: - splev_stack_push(coder->stack, opvar_clone(coder->opdat)); - break; - case SPO_MESSAGE: - spo_message(coder); - break; - case SPO_MONSTER: - spo_monster(coder); - break; - case SPO_OBJECT: - spo_object(coder); - break; - case SPO_LEVEL_FLAGS: - spo_level_flags(coder); - break; - case SPO_INITLEVEL: - spo_initlevel(coder); - break; - case SPO_ENGRAVING: - spo_engraving(coder); - break; - case SPO_MINERALIZE: - spo_mineralize(coder); - break; - case SPO_SUBROOM: - case SPO_ROOM: - if (!coder->failed_room[coder->n_subroom - 1]) { - spo_room(coder); - } else - room_stack++; - break; - case SPO_ENDROOM: - if (coder->failed_room[coder->n_subroom - 1]) { - if (!room_stack) - spo_endroom(coder); - else - room_stack--; - } else { - spo_endroom(coder); - } - break; - case SPO_DOOR: - spo_door(coder); - break; - case SPO_STAIR: - spo_stair(coder); - break; - case SPO_LADDER: - spo_ladder(coder); - break; - case SPO_GRAVE: - spo_grave(coder); - break; - case SPO_ALTAR: - spo_altar(coder); - break; - case SPO_SINK: - case SPO_POOL: - case SPO_FOUNTAIN: - spo_feature(coder); - break; - case SPO_TRAP: - spo_trap(coder); - break; - case SPO_GOLD: - spo_gold(coder); - break; - case SPO_CORRIDOR: - spo_corridor(coder); - break; - case SPO_TERRAIN: - spo_terrain(coder); - break; - case SPO_REPLACETERRAIN: - spo_replace_terrain(coder); - break; - case SPO_LEVREGION: - spo_levregion(coder); - break; - case SPO_REGION: - spo_region(coder); - break; - case SPO_DRAWBRIDGE: - spo_drawbridge(coder); - break; - case SPO_MAZEWALK: - spo_mazewalk(coder); - break; - case SPO_NON_PASSWALL: - case SPO_NON_DIGGABLE: - spo_wall_property(coder); - break; - case SPO_ROOM_DOOR: - spo_room_door(coder); - break; - case SPO_WALLIFY: - spo_wallify(coder); - break; - case SPO_COPY: { - struct opvar *a = splev_stack_pop(coder->stack); - - splev_stack_push(coder->stack, opvar_clone(a)); - splev_stack_push(coder->stack, opvar_clone(a)); - opvar_free(a); - break; - } - case SPO_DEC: { - struct opvar *a; - - if (!OV_pop_i(a)) - break; - OV_i(a)--; - splev_stack_push(coder->stack, a); - break; - } - case SPO_INC: { - struct opvar *a; - - if (!OV_pop_i(a)) - break; - OV_i(a)++; - splev_stack_push(coder->stack, a); - break; - } - case SPO_MATH_SIGN: { - struct opvar *a; - - if (!OV_pop_i(a)) - break; - OV_i(a) = ((OV_i(a) < 0) ? -1 : ((OV_i(a) > 0) ? 1 : 0)); - splev_stack_push(coder->stack, a); - break; - } - case SPO_MATH_ADD: { - struct opvar *a, *b; + (void) memset((genericptr_t) SpLev_Map, 0, sizeof SpLev_Map); + + svl.level.flags.is_maze_lev = 0; + svl.level.flags.temperature = In_hell(&u.uz) ? 1 : 0; + svl.level.flags.rndmongen = 1; + svl.level.flags.deathdrops = 1; + + reset_xystart_size(); + + return coder; +} + + +static const struct luaL_Reg nhl_functions[] = { + { "message", lspo_message }, + { "monster", lspo_monster }, + { "object", lspo_object }, + { "level_flags", lspo_level_flags }, + { "level_init", lspo_level_init }, + { "engraving", lspo_engraving }, + { "mineralize", lspo_mineralize }, + { "door", lspo_door }, + { "stair", lspo_stair }, + { "ladder", lspo_ladder }, + { "grave", lspo_grave }, + { "altar", lspo_altar }, + { "map", lspo_map }, + { "feature", lspo_feature }, + { "terrain", lspo_terrain }, + { "replace_terrain", lspo_replace_terrain }, + { "room", lspo_room }, + { "corridor", lspo_corridor }, + { "random_corridors", lspo_random_corridors }, + { "gold", lspo_gold }, + { "trap", lspo_trap }, + { "mazewalk", lspo_mazewalk }, + { "drawbridge", lspo_drawbridge }, + { "region", lspo_region }, + { "levregion", lspo_levregion }, + { "exclusion", lspo_exclusion }, + { "wallify", lspo_wallify }, + { "wall_property", lspo_wall_property }, + { "non_diggable", lspo_non_diggable }, + { "non_passwall", lspo_non_passwall }, + { "teleport_region", lspo_teleport_region }, + { "reset_level", lspo_reset_level }, + { "finalize_level", lspo_finalize_level }, + { "gas_cloud", lspo_gas_cloud }, + /* TODO: { "branch", lspo_branch }, */ + /* TODO: { "portal", lspo_portal }, */ + { NULL, NULL } +}; + +/* TODO: + + - if des-file used MAZE_ID to start a level, the level needs + des.level_flags("mazelevel") + - expose gc.coder->croom or gx.xstart gy.ystart and gx.xsize gy.ysize to lua. + - detect a "subroom" automatically. + - new function get_mapchar(x,y) to return the mapchar on map + - many params should accept their normal type (eg, int or bool), AND "random" + - automatically add shuffle(array) + - automatically add align = { "law", "neutral", "chaos" } and shuffle it. + (remove from lua files) + - grab the header comments from des-files and add them to the lua files + +*/ - if (!OV_pop(b) || !OV_pop(a)) - break; - if (OV_typ(b) == OV_typ(a)) { - if (OV_typ(a) == SPOVAR_INT) { - OV_i(a) = OV_i(a) + OV_i(b); - splev_stack_push(coder->stack, a); - opvar_free(b); - } else if (OV_typ(a) == SPOVAR_STRING) { - struct opvar *c; - char *tmpbuf = (char *) alloc(strlen(OV_s(a)) - + strlen(OV_s(b)) + 1); - - (void) sprintf(tmpbuf, "%s%s", OV_s(a), OV_s(b)); - c = opvar_new_str(tmpbuf); - splev_stack_push(coder->stack, c); - opvar_free(a); - opvar_free(b); - Free(tmpbuf); - } else { - splev_stack_push(coder->stack, a); - opvar_free(b); - impossible("adding weird types"); - } - } else { - splev_stack_push(coder->stack, a); - opvar_free(b); - impossible("adding different types"); - } - break; - } - case SPO_MATH_SUB: { - struct opvar *a, *b; - - if (!OV_pop_i(b) || !OV_pop_i(a)) - break; - OV_i(a) = OV_i(a) - OV_i(b); - splev_stack_push(coder->stack, a); - opvar_free(b); - break; - } - case SPO_MATH_MUL: { - struct opvar *a, *b; - - if (!OV_pop_i(b) || !OV_pop_i(a)) - break; - OV_i(a) = OV_i(a) * OV_i(b); - splev_stack_push(coder->stack, a); - opvar_free(b); - break; - } - case SPO_MATH_DIV: { - struct opvar *a, *b; - - if (!OV_pop_i(b) || !OV_pop_i(a)) - break; - if (OV_i(b) >= 1) { - OV_i(a) = OV_i(a) / OV_i(b); - } else { - OV_i(a) = 0; - } - splev_stack_push(coder->stack, a); - opvar_free(b); - break; - } - case SPO_MATH_MOD: { - struct opvar *a, *b; +void +l_register_des(lua_State *L) +{ + /* register des -table, and functions for it */ + lua_newtable(L); + luaL_setfuncs(L, nhl_functions, 0); + lua_setglobal(L, "des"); +} - if (!OV_pop_i(b) || !OV_pop_i(a)) - break; - if (OV_i(b) > 0) { - OV_i(a) = OV_i(a) % OV_i(b); - } else { - OV_i(a) = 0; - } - splev_stack_push(coder->stack, a); - opvar_free(b); - break; - } - case SPO_CMP: { - struct opvar *a; - struct opvar *b; - struct opvar *c; - long val = 0; - - OV_pop(b); - OV_pop(a); - if (!a || !b) { - impossible("spo_cmp: no values in stack"); - break; - } - if (OV_typ(a) != OV_typ(b)) { - impossible("spo_cmp: trying to compare differing datatypes"); - break; - } - switch (OV_typ(a)) { - case SPOVAR_COORD: - case SPOVAR_REGION: - case SPOVAR_MAPCHAR: - case SPOVAR_MONST: - case SPOVAR_OBJ: - case SPOVAR_INT: - if (OV_i(b) > OV_i(a)) - val |= SP_CPUFLAG_LT; - if (OV_i(b) < OV_i(a)) - val |= SP_CPUFLAG_GT; - if (OV_i(b) == OV_i(a)) - val |= SP_CPUFLAG_EQ; - c = opvar_new_int(val); - break; - case SPOVAR_STRING: - c = opvar_new_int(!strcmp(OV_s(b), OV_s(a)) - ? SP_CPUFLAG_EQ - : 0); - break; - default: - c = opvar_new_int(0); - break; - } - splev_stack_push(coder->stack, c); - opvar_free(a); - opvar_free(b); - break; - } - case SPO_JMP: - spo_jmp(coder, lvl); - break; - case SPO_JL: - case SPO_JLE: - case SPO_JG: - case SPO_JGE: - case SPO_JE: - case SPO_JNE: - spo_conditional_jump(coder, lvl); - break; - case SPO_RN2: { - struct opvar *tmpv; - struct opvar *t; +void +create_des_coder(void) +{ + if (!gc.coder) + gc.coder = sp_level_coder_init(); +} - if (!OV_pop_i(tmpv)) - break; - t = opvar_new_int((OV_i(tmpv) > 1) ? rn2(OV_i(tmpv)) : 0); - splev_stack_push(coder->stack, t); - opvar_free(tmpv); - break; - } - case SPO_DICE: { - struct opvar *a, *b, *t; +/* + * General loader + */ +boolean +load_special(const char *name) +{ + boolean result = FALSE; + nhl_sandbox_info sbi = {NHL_SB_SAFE, 1*1024*1024, 0, 1*1024*1024}; - if (!OV_pop_i(b) || !OV_pop_i(a)) - break; - if (OV_i(b) < 1) - OV_i(b) = 1; - if (OV_i(a) < 1) - OV_i(a) = 1; - t = opvar_new_int(d(OV_i(a), OV_i(b))); - splev_stack_push(coder->stack, t); - opvar_free(a); - opvar_free(b); - break; - } - case SPO_MAP: - spo_map(coder); - break; - case SPO_VAR_INIT: - spo_var_init(coder); - break; - case SPO_SHUFFLE_ARRAY: - spo_shuffle_array(coder); - break; - case SPO_SEL_ADD: /* actually, logical or */ - { - struct opvar *sel1, *sel2, *pt; - - if (!OV_pop_typ(sel1, SPOVAR_SEL)) - panic("no sel1 for add"); - if (!OV_pop_typ(sel2, SPOVAR_SEL)) - panic("no sel2 for add"); - pt = selection_logical_oper(sel1, sel2, '|'); - opvar_free(sel1); - opvar_free(sel2); - splev_stack_push(coder->stack, pt); - break; - } - case SPO_SEL_COMPLEMENT: { - struct opvar *sel, *pt; - - if (!OV_pop_typ(sel, SPOVAR_SEL)) - panic("no sel for not"); - pt = selection_not(sel); - opvar_free(sel); - splev_stack_push(coder->stack, pt); - break; - } - case SPO_SEL_FILTER: /* sorta like logical and */ - { - struct opvar *filtertype; - - if (!OV_pop_i(filtertype)) - panic("no sel filter type"); - switch (OV_i(filtertype)) { - case SPOFILTER_PERCENT: { - struct opvar *tmp1, *sel; - - if (!OV_pop_i(tmp1)) - panic("no sel filter percent"); - if (!OV_pop_typ(sel, SPOVAR_SEL)) - panic("no sel filter"); - selection_filter_percent(sel, OV_i(tmp1)); - splev_stack_push(coder->stack, sel); - opvar_free(tmp1); - break; - } - case SPOFILTER_SELECTION: /* logical and */ - { - struct opvar *pt, *sel1, *sel2; - - if (!OV_pop_typ(sel1, SPOVAR_SEL)) - panic("no sel filter sel1"); - if (!OV_pop_typ(sel2, SPOVAR_SEL)) - panic("no sel filter sel2"); - pt = selection_logical_oper(sel1, sel2, '&'); - splev_stack_push(coder->stack, pt); - opvar_free(sel1); - opvar_free(sel2); - break; - } - case SPOFILTER_MAPCHAR: { - struct opvar *pt, *tmp1, *sel; - - if (!OV_pop_typ(sel, SPOVAR_SEL)) - panic("no sel filter"); - if (!OV_pop_typ(tmp1, SPOVAR_MAPCHAR)) - panic("no sel filter mapchar"); - pt = selection_filter_mapchar(sel, tmp1); - splev_stack_push(coder->stack, pt); - opvar_free(tmp1); - opvar_free(sel); - break; - } - default: - panic("unknown sel filter type"); - } - opvar_free(filtertype); - break; - } - case SPO_SEL_POINT: { - struct opvar *tmp; - struct opvar *pt = selection_opvar((char *) 0); - schar x, y; - - if (!OV_pop_c(tmp)) - panic("no ter sel coord"); - get_location_coord(&x, &y, ANY_LOC, coder->croom, OV_i(tmp)); - selection_setpoint(x, y, pt, 1); - splev_stack_push(coder->stack, pt); - opvar_free(tmp); - break; - } - case SPO_SEL_RECT: - case SPO_SEL_FILLRECT: { - struct opvar *tmp, *pt = selection_opvar((char *) 0); - schar x, y, x1, y1, x2, y2; - - if (!OV_pop_r(tmp)) - panic("no ter sel region"); - x1 = min(SP_REGION_X1(OV_i(tmp)), SP_REGION_X2(OV_i(tmp))); - y1 = min(SP_REGION_Y1(OV_i(tmp)), SP_REGION_Y2(OV_i(tmp))); - x2 = max(SP_REGION_X1(OV_i(tmp)), SP_REGION_X2(OV_i(tmp))); - y2 = max(SP_REGION_Y1(OV_i(tmp)), SP_REGION_Y2(OV_i(tmp))); - get_location(&x1, &y1, ANY_LOC, coder->croom); - get_location(&x2, &y2, ANY_LOC, coder->croom); - x1 = (x1 < 0) ? 0 : x1; - y1 = (y1 < 0) ? 0 : y1; - x2 = (x2 >= COLNO) ? COLNO - 1 : x2; - y2 = (y2 >= ROWNO) ? ROWNO - 1 : y2; - if (coder->opcode == SPO_SEL_RECT) { - for (x = x1; x <= x2; x++) { - selection_setpoint(x, y1, pt, 1); - selection_setpoint(x, y2, pt, 1); - } - for (y = y1; y <= y2; y++) { - selection_setpoint(x1, y, pt, 1); - selection_setpoint(x2, y, pt, 1); - } - } else { - for (x = x1; x <= x2; x++) - for (y = y1; y <= y2; y++) - selection_setpoint(x, y, pt, 1); - } - splev_stack_push(coder->stack, pt); - opvar_free(tmp); - break; - } - case SPO_SEL_LINE: { - struct opvar *tmp = NULL, *tmp2 = NULL, - *pt = selection_opvar((char *) 0); - schar x1, y1, x2, y2; - - if (!OV_pop_c(tmp)) - panic("no ter sel linecoord1"); - if (!OV_pop_c(tmp2)) - panic("no ter sel linecoord2"); - get_location_coord(&x1, &y1, ANY_LOC, coder->croom, OV_i(tmp)); - get_location_coord(&x2, &y2, ANY_LOC, coder->croom, OV_i(tmp2)); - x1 = (x1 < 0) ? 0 : x1; - y1 = (y1 < 0) ? 0 : y1; - x2 = (x2 >= COLNO) ? COLNO - 1 : x2; - y2 = (y2 >= ROWNO) ? ROWNO - 1 : y2; - selection_do_line(x1, y1, x2, y2, pt); - splev_stack_push(coder->stack, pt); - opvar_free(tmp); - opvar_free(tmp2); - break; - } - case SPO_SEL_RNDLINE: { - struct opvar *tmp = NULL, *tmp2 = NULL, *tmp3, - *pt = selection_opvar((char *) 0); - schar x1, y1, x2, y2; - - if (!OV_pop_i(tmp3)) - panic("no ter sel randline1"); - if (!OV_pop_c(tmp)) - panic("no ter sel randline2"); - if (!OV_pop_c(tmp2)) - panic("no ter sel randline3"); - get_location_coord(&x1, &y1, ANY_LOC, coder->croom, OV_i(tmp)); - get_location_coord(&x2, &y2, ANY_LOC, coder->croom, OV_i(tmp2)); - x1 = (x1 < 0) ? 0 : x1; - y1 = (y1 < 0) ? 0 : y1; - x2 = (x2 >= COLNO) ? COLNO - 1 : x2; - y2 = (y2 >= ROWNO) ? ROWNO - 1 : y2; - selection_do_randline(x1, y1, x2, y2, OV_i(tmp3), 12, pt); - splev_stack_push(coder->stack, pt); - opvar_free(tmp); - opvar_free(tmp2); - opvar_free(tmp3); - break; - } - case SPO_SEL_GROW: { - struct opvar *dirs, *pt; - - if (!OV_pop_i(dirs)) - panic("no dirs for grow"); - if (!OV_pop_typ(pt, SPOVAR_SEL)) - panic("no selection for grow"); - selection_do_grow(pt, OV_i(dirs)); - splev_stack_push(coder->stack, pt); - opvar_free(dirs); - break; - } - case SPO_SEL_FLOOD: { - struct opvar *tmp; - schar x, y; - - if (!OV_pop_c(tmp)) - panic("no ter sel flood coord"); - get_location_coord(&x, &y, ANY_LOC, coder->croom, OV_i(tmp)); - if (isok(x, y)) { - struct opvar *pt = selection_opvar((char *) 0); - - set_selection_floodfillchk(floodfillchk_match_under); - floodfillchk_match_under_typ = levl[x][y].typ; - selection_floodfill(pt, x, y, FALSE); - splev_stack_push(coder->stack, pt); - } - opvar_free(tmp); - break; - } - case SPO_SEL_RNDCOORD: { - struct opvar *pt; - schar x, y; - - if (!OV_pop_typ(pt, SPOVAR_SEL)) - panic("no selection for rndcoord"); - if (selection_rndcoord(pt, &x, &y, FALSE)) { - x -= xstart; - y -= ystart; - } - splev_stack_push(coder->stack, opvar_new_coord(x, y)); - opvar_free(pt); - break; - } - case SPO_SEL_ELLIPSE: { - struct opvar *filled, *xaxis, *yaxis, *pt; - struct opvar *sel = selection_opvar((char *) 0); - schar x, y; - - if (!OV_pop_i(filled)) - panic("no filled for ellipse"); - if (!OV_pop_i(yaxis)) - panic("no yaxis for ellipse"); - if (!OV_pop_i(xaxis)) - panic("no xaxis for ellipse"); - if (!OV_pop_c(pt)) - panic("no pt for ellipse"); - get_location_coord(&x, &y, ANY_LOC, coder->croom, OV_i(pt)); - selection_do_ellipse(sel, x, y, OV_i(xaxis), OV_i(yaxis), - OV_i(filled)); - splev_stack_push(coder->stack, sel); - opvar_free(filled); - opvar_free(yaxis); - opvar_free(xaxis); - opvar_free(pt); - break; - } - case SPO_SEL_GRADIENT: { - struct opvar *gtyp, *glim, *mind, *maxd, *gcoord, *coord2; - struct opvar *sel; - schar x, y, x2, y2; - - if (!OV_pop_i(gtyp)) - panic("no gtyp for grad"); - if (!OV_pop_i(glim)) - panic("no glim for grad"); - if (!OV_pop_c(coord2)) - panic("no coord2 for grad"); - if (!OV_pop_c(gcoord)) - panic("no coord for grad"); - if (!OV_pop_i(maxd)) - panic("no maxd for grad"); - if (!OV_pop_i(mind)) - panic("no mind for grad"); - get_location_coord(&x, &y, ANY_LOC, coder->croom, OV_i(gcoord)); - get_location_coord(&x2, &y2, ANY_LOC, coder->croom, OV_i(coord2)); - - sel = selection_opvar((char *) 0); - selection_do_gradient(sel, x, y, x2, y2, OV_i(gtyp), OV_i(mind), - OV_i(maxd), OV_i(glim)); - splev_stack_push(coder->stack, sel); - - opvar_free(gtyp); - opvar_free(glim); - opvar_free(gcoord); - opvar_free(coord2); - opvar_free(maxd); - opvar_free(mind); - break; - } - default: - panic("sp_level_coder: Unknown opcode %i", coder->opcode); - } + create_des_coder(); - next_opcode: - coder->frame->n_opcode++; - } /*while*/ + if (!load_lua(name, &sbi)) + goto give_up; link_doors_rooms(); - fill_rooms(); remove_boundary_syms(); - if (coder->check_inaccessibles) + /* TODO: ensure_way_out() needs rewrite */ + if (gc.coder->check_inaccessibles) ensure_way_out(); + + map_cleanup(); + /* FIXME: Ideally, we want this call to only cover areas of the map * which were not inserted directly by the special level file (see * the insect legs on Baalzebub's level, for instance). Since that * is currently not possible, we overload the corrmaze flag for this * purpose. */ - if (!level.flags.corrmaze) + if (!svl.level.flags.corrmaze) wallification(1, 0, COLNO - 1, ROWNO - 1); - count_features(); + flip_level_rnd(gc.coder->allow_flips, FALSE); - if (coder->solidify) + count_level_features(); + + if (gc.coder->solidify) solidify_map(); - /* This must be done before sokoban_detect(), + /* This must be done before premap_detect(), * otherwise branch stairs won't be premapped. */ fixup_special(); - if (coder->premapped) - sokoban_detect(); - - if (coder->frame) { - struct sp_frame *tmpframe; - do { - tmpframe = coder->frame->next; - frame_del(coder->frame); - coder->frame = tmpframe; - } while (coder->frame); - } - Free(coder); - - return TRUE; -} - -/* - * General loader - */ -boolean -load_special(name) -const char *name; -{ - dlb *fd; - sp_lev *lvl = NULL; - boolean result = FALSE; - struct version_info vers_info; - - fd = dlb_fopen(name, RDBMODE); - if (!fd) - return FALSE; - Fread((genericptr_t) &vers_info, sizeof vers_info, 1, fd); - if (!check_version(&vers_info, name, TRUE)) { - (void) dlb_fclose(fd); - goto give_up; - } + if (gc.coder->premapped) + premap_detect(); - lvl = (sp_lev *) alloc(sizeof (sp_lev)); - result = sp_level_loader(fd, lvl); - (void) dlb_fclose(fd); - if (result) - result = sp_level_coder(lvl); - sp_level_free(lvl); - Free(lvl); + result = TRUE; + give_up: + Free(gc.coder); + gc.coder = NULL; -give_up: return result; } -#ifdef _MSC_VER - #pragma warning(pop) -#endif - /*sp_lev.c*/ diff --git a/src/spell.c b/src/spell.c index 9cbb9a480..247997f43 100644 --- a/src/spell.c +++ b/src/spell.c @@ -1,10 +1,11 @@ -/* NetHack 3.6 spell.c $NHDT-Date: 1546565814 2019/01/04 01:36:54 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.88 $ */ +/* NetHack 5.0 spell.c $NHDT-Date: 1769498874 2026/01/26 23:27:54 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.185 $ */ /* Copyright (c) M. Stephenson 1988 */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -/* spellmenu arguments; 0 thru n-1 used as spl_book[] index when swapping */ +/* spellmenu arguments; 0..n-1 used as svs.spl_book[] index when swapping */ +#define SPELLMENU_DUMP (-3) #define SPELLMENU_CAST (-2) #define SPELLMENU_VIEW (-1) #define SPELLMENU_SORT (MAXSPELL) /* special menu entry */ @@ -18,32 +19,41 @@ initialization; spell memory is decremented at the end of each turn, including the turn on which the spellbook is read; without the extra increment, the hero used to get cheated out of 1 turn of retention */ -#define incrnknow(spell, x) (spl_book[spell].sp_know = KEEN + (x)) +#define incrnknow(spell, x) (svs.spl_book[spell].sp_know = KEEN + (x)) -#define spellev(spell) spl_book[spell].sp_lev +#define spellev(spell) svs.spl_book[spell].sp_lev #define spellname(spell) OBJ_NAME(objects[spellid(spell)]) #define spellet(spell) \ ((char) ((spell < 26) ? ('a' + spell) : ('A' + spell - 26))) -STATIC_DCL int FDECL(spell_let_to_idx, (CHAR_P)); -STATIC_DCL boolean FDECL(cursed_book, (struct obj * bp)); -STATIC_DCL boolean FDECL(confused_book, (struct obj *)); -STATIC_DCL void FDECL(deadbook, (struct obj *)); -STATIC_PTR int NDECL(learn); -STATIC_DCL boolean NDECL(rejectcasting); -STATIC_DCL boolean FDECL(getspell, (int *)); -STATIC_PTR int FDECL(CFDECLSPEC spell_cmp, (const genericptr, - const genericptr)); -STATIC_DCL void NDECL(sortspells); -STATIC_DCL boolean NDECL(spellsortmenu); -STATIC_DCL boolean FDECL(dospellmenu, (const char *, int, int *)); -STATIC_DCL int FDECL(percent_success, (int)); -STATIC_DCL char *FDECL(spellretention, (int, char *)); -STATIC_DCL int NDECL(throwspell); -STATIC_DCL void NDECL(cast_protection); -STATIC_DCL void FDECL(spell_backfire, (int)); -STATIC_DCL const char *FDECL(spelltypemnemonic, (int)); -STATIC_DCL boolean FDECL(spell_aim_step, (genericptr_t, int, int)); +struct chain_lightning_queue; +struct chain_lightning_zap; + +staticfn int spell_let_to_idx(char); +staticfn boolean cursed_book(struct obj * bp); +staticfn boolean confused_book(struct obj *); +staticfn void deadbook_pacify_undead(struct monst *); +staticfn void deadbook(struct obj *); +staticfn int learn(void); +staticfn boolean rejectcasting(void); +staticfn boolean getspell(int *); +staticfn int QSORTCALLBACK spell_cmp(const genericptr, const genericptr); +staticfn void sortspells(void); +staticfn boolean spellsortmenu(void); +staticfn boolean dospellmenu(const char *, int, int *); +staticfn int percent_success(int); +staticfn char *spellretention(int, char *); +staticfn int throwspell(void); +staticfn void cast_protection(void); +staticfn void cast_chain_lightning(void); +staticfn void spell_backfire(int); +staticfn boolean spelleffects_check(int, int *, int *); +staticfn const char *spelltypemnemonic(int); +staticfn boolean can_center_spell_location(coordxy, coordxy); +staticfn void display_spell_target_positions(boolean); +staticfn boolean spell_aim_step(genericptr_t, coordxy, coordxy); +staticfn void propagate_chain_lightning(struct chain_lightning_queue *, + struct chain_lightning_zap); /* The roles[] table lists the role-specific values for tuning * percent_success(). @@ -90,7 +100,7 @@ STATIC_DCL boolean FDECL(spell_aim_step, (genericptr_t, int, int)); * Fighters find body armour & shield a little less limiting. * Headgear, Gauntlets and Footwear are not role-specific (but * still have an effect, except helm of brilliance, which is designed - * to permit magic-use). + * to permit magic use). */ #define uarmhbon 4 /* Metal helmets interfere with the mind */ @@ -101,9 +111,8 @@ STATIC_DCL boolean FDECL(spell_aim_step, (genericptr_t, int, int)); static const char explodes[] = "radiates explosive energy"; /* convert a letter into a number in the range 0..51, or -1 if not a letter */ -STATIC_OVL int -spell_let_to_idx(ilet) -char ilet; +staticfn int +spell_let_to_idx(char ilet) { int indx; @@ -117,9 +126,8 @@ char ilet; } /* TRUE: book should be destroyed by caller */ -STATIC_OVL boolean -cursed_book(bp) -struct obj *bp; +staticfn boolean +cursed_book(struct obj *bp) { boolean was_in_use; int lev = objects[bp->otyp].oc_level; @@ -135,7 +143,7 @@ struct obj *bp; aggravate(); break; case 2: - make_blinded(Blinded + rn1(100, 250), TRUE); + make_blinded(BlindedTimeout + rn1(100, 250), TRUE); break; case 3: take_gold(); @@ -153,9 +161,9 @@ struct obj *bp; /* temp disable in_use; death should not destroy the book */ was_in_use = bp->in_use; bp->in_use = FALSE; - losestr(Poison_resistance ? rn1(2, 1) : rn1(4, 3)); - losehp(rnd(Poison_resistance ? 6 : 10), "contact-poisoned spellbook", - KILLED_BY_AN); + poison_strdmg(Poison_resistance ? rn1(2, 1) : rn1(4, 3), + rnd(Poison_resistance ? 6 : 10), + "contact-poisoned spellbook", KILLED_BY_AN); bp->in_use = was_in_use; break; case 6: @@ -177,62 +185,83 @@ struct obj *bp; } /* study while confused: returns TRUE if the book is destroyed */ -STATIC_OVL boolean -confused_book(spellbook) -struct obj *spellbook; +staticfn boolean +confused_book(struct obj *spellbook) { boolean gone = FALSE; if (!rn2(3) && spellbook->otyp != SPE_BOOK_OF_THE_DEAD) { - spellbook->in_use = TRUE; /* in case called from learn */ + spellbook->in_use = TRUE; /* in case called from learn() */ pline( "Being confused you have difficulties in controlling your actions."); display_nhwindow(WIN_MESSAGE, FALSE); You("accidentally tear the spellbook to pieces."); - if (!objects[spellbook->otyp].oc_name_known - && !objects[spellbook->otyp].oc_uname) - docall(spellbook); + trycall(spellbook); useup(spellbook); gone = TRUE; } else { You("find yourself reading the %s line over and over again.", - spellbook == context.spbook.book ? "next" : "first"); + spellbook == svc.context.spbook.book ? "next" : "first"); } return gone; } -/* special effects for The Book of the Dead */ -STATIC_OVL void -deadbook(book2) -struct obj *book2; +/* pacify or tame an undead monster */ +staticfn void +deadbook_pacify_undead(struct monst *mtmp) { - struct monst *mtmp, *mtmp2; + if ((is_undead(mtmp->data) || is_vampshifter(mtmp)) + && cansee(mtmp->mx, mtmp->my)) { + mtmp->mpeaceful = TRUE; + if (sgn(mtmp->data->maligntyp) == sgn(u.ualign.type) + && mdistu(mtmp) < 4) + if (mtmp->mtame) { + if (mtmp->mtame < 20) + mtmp->mtame++; + } else + (void) tamedog(mtmp, (struct obj *) 0, TRUE); + else + monflee(mtmp, 0, FALSE, TRUE); + } +} + +/* special effects for the Book of the Dead; reading it while blind is + allowed so that needs to be taken into account too */ +staticfn void +deadbook(struct obj *book2) +{ + struct monst *mtmp; coord mm; You("turn the pages of the Book of the Dead..."); makeknown(SPE_BOOK_OF_THE_DEAD); + observe_object(book2); /* in case blind now and hasn't been seen yet */ /* KMH -- Need ->known to avoid "_a_ Book of the Dead" */ book2->known = 1; if (invocation_pos(u.ux, u.uy) && !On_stairs(u.ux, u.uy)) { - register struct obj *otmp; - register boolean arti1_primed = FALSE, arti2_primed = FALSE, + struct obj *otmp; + boolean arti1_primed = FALSE, arti2_primed = FALSE, arti_cursed = FALSE; if (book2->cursed) { - pline_The("runes appear scrambled. You can't read them!"); + pline_The("%s!", + Blind ? "Book seems to be ignoring you" + : "runes appear scrambled. You can't read them"); return; } if (!u.uhave.bell || !u.uhave.menorah) { pline("A chill runs down your %s.", body_part(SPINE)); - if (!u.uhave.bell) + if (!u.uhave.bell) { + Soundeffect(se_faint_chime, 30); You_hear("a faint chime..."); + } if (!u.uhave.menorah) pline("Vlad's doppelganger is amused."); return; } - for (otmp = invent; otmp; otmp = otmp->nobj) { + for (otmp = gi.invent; otmp; otmp = otmp->nobj) { if (otmp->otyp == CANDELABRUM_OF_INVOCATION && otmp->spe == 7 && otmp->lamplit) { if (!otmp->cursed) @@ -241,7 +270,7 @@ struct obj *book2; arti_cursed = TRUE; } if (otmp->otyp == BELL_OF_OPENING - && (moves - otmp->age) < 5L) { /* you rang it recently */ + && (svm.moves - otmp->age) < 5L) { /* you rang it recently */ if (!otmp->cursed) arti2_primed = TRUE; else @@ -251,20 +280,22 @@ struct obj *book2; if (arti_cursed) { pline_The("invocation fails!"); - pline("At least one of your artifacts is cursed..."); + /* this used to say "your artifacts" but the invocation tools + are not artifacts */ + pline("At least one of your relics is cursed..."); } else if (arti1_primed && arti2_primed) { - unsigned soon = - (unsigned) d(2, 6); /* time til next intervene() */ + unsigned soon = (unsigned) d(2, 6); /* time til next intervene() */ /* successful invocation */ mkinvokearea(); u.uevent.invoked = 1; + record_achievement(ACH_INVK); /* in case you haven't killed the Wizard yet, behave as if you just did */ - u.uevent.udemigod = 1; /* wizdead() */ + u.uevent.udemigod = 1; /* wizdeadorgone() */ if (!u.udg_cnt || u.udg_cnt > soon) u.udg_cnt = soon; - } else { /* at least one artifact not prepared properly */ + } else { /* at least one relic not prepared properly */ You("have a feeling that %s is amiss...", something); goto raise_dead; } @@ -273,7 +304,7 @@ struct obj *book2; /* when not an invocation situation */ if (book2->cursed) { - raise_dead: + raise_dead: You("raised the dead!"); /* first maybe place a dangerous adversary */ @@ -285,31 +316,13 @@ struct obj *book2; set_malign(mtmp); } /* next handle the affect on things you're carrying */ - (void) unturn_dead(&youmonst); + (void) unturn_dead(&gy.youmonst); /* last place some monsters around you */ mm.x = u.ux; mm.y = u.uy; mkundead(&mm, TRUE, NO_MINVENT); } else if (book2->blessed) { - for (mtmp = fmon; mtmp; mtmp = mtmp2) { - mtmp2 = mtmp->nmon; /* tamedog() changes chain */ - if (DEADMONSTER(mtmp)) - continue; - - if ((is_undead(mtmp->data) || is_vampshifter(mtmp)) - && cansee(mtmp->mx, mtmp->my)) { - mtmp->mpeaceful = TRUE; - if (sgn(mtmp->data->maligntyp) == sgn(u.ualign.type) - && distu(mtmp->mx, mtmp->my) < 4) - if (mtmp->mtame) { - if (mtmp->mtame < 20) - mtmp->mtame++; - } else - (void) tamedog(mtmp, (struct obj *) 0); - else - monflee(mtmp, 0, FALSE, TRUE); - } - } + iter_mons(deadbook_pacify_undead); } else { switch (rn2(3)) { case 0: @@ -325,42 +338,46 @@ struct obj *book2; return; } -/* 'book' has just become cursed; if we're reading it and realize it is - now cursed, interrupt */ +/* 'book' has just become cursed; if we're reading it, interrupt */ void -book_cursed(book) -struct obj *book; +book_cursed(struct obj *book) { - if (occupation == learn && context.spbook.book == book - && book->cursed && book->bknown && multi >= 0) + if (book->cursed && gm.multi >= 0 + && go.occupation == learn && svc.context.spbook.book == book) { + pline("%s shut!", Tobjnam(book, "slam")); + set_bknown(book, 1); stop_occupation(); + } } -STATIC_PTR int -learn(VOID_ARGS) +DISABLE_WARNING_FORMAT_NONLITERAL + +staticfn int +learn(void) { int i; short booktype; char splname[BUFSZ]; - boolean costly = TRUE; - struct obj *book = context.spbook.book; + boolean costly = TRUE, faded_to_blank = FALSE; + struct obj *book = svc.context.spbook.book; /* JDS: lenses give 50% faster reading; 33% smaller read time */ - if (context.spbook.delay && ublindf && ublindf->otyp == LENSES && rn2(2)) - context.spbook.delay++; + if (svc.context.spbook.delay && ublindf + && ublindf->otyp == LENSES && rn2(2)) + svc.context.spbook.delay++; if (Confusion) { /* became confused while learning */ (void) confused_book(book); - context.spbook.book = 0; /* no longer studying */ - context.spbook.o_id = 0; - nomul(context.spbook.delay); /* remaining delay is uninterrupted */ - multi_reason = "reading a book"; - nomovemsg = 0; - context.spbook.delay = 0; + svc.context.spbook.book = 0; /* no longer studying */ + svc.context.spbook.o_id = 0; + nomul(svc.context.spbook.delay); /* remaining delay is uninterrupted */ + gm.multi_reason = "reading a book"; + gn.nomovemsg = 0; + svc.context.spbook.delay = 0; return 0; } - if (context.spbook.delay) { - /* not if (context.spbook.delay++), so at end delay == 0 */ - context.spbook.delay++; + if (svc.context.spbook.delay) { + /* not if (svc.context.spbook.delay++), so at end delay == 0 */ + svc.context.spbook.delay++; return 1; /* still busy */ } exercise(A_WIS, TRUE); /* you're studying. */ @@ -384,21 +401,16 @@ learn(VOID_ARGS) if (book->spestudied > MAX_SPELL_STUDY) { pline("This spellbook is too faint to be read any more."); book->otyp = booktype = SPE_BLANK_PAPER; + faded_to_blank = TRUE; /* reset spestudied as if polymorph had taken place */ book->spestudied = rn2(book->spestudied); - } else if (spellknow(i) > KEEN / 10) { - You("know %s quite well already.", splname); - costly = FALSE; - } else { /* spellknow(i) <= KEEN/10 */ + } else { Your("knowledge of %s is %s.", splname, spellknow(i) ? "keener" : "restored"); incrnknow(i, 1); book->spestudied++; exercise(A_WIS, TRUE); /* extra study */ } - /* make book become known even when spell is already - known, in case amnesia made you forget the book */ - makeknown((int) booktype); } else { /* (spellid(i) == NO_SPELL) */ /* for a normal book, spestudied will be zero, but for a polymorphed one, spestudied will be non-zero and @@ -407,54 +419,71 @@ learn(VOID_ARGS) /* pre-used due to being the product of polymorph */ pline("This spellbook is too faint to read even once."); book->otyp = booktype = SPE_BLANK_PAPER; + faded_to_blank = TRUE; /* reset spestudied as if polymorph had taken place */ book->spestudied = rn2(book->spestudied); } else { - spl_book[i].sp_id = booktype; - spl_book[i].sp_lev = objects[booktype].oc_level; + svs.spl_book[i].sp_id = booktype; + svs.spl_book[i].sp_lev = objects[booktype].oc_level; incrnknow(i, 1); book->spestudied++; - You(i > 0 ? "add %s to your repertoire." : "learn %s.", splname); + if (!i) + /* first is always 'a', so no need to mention the letter */ + You("learn %s.", splname); + else + You("add %s to your repertoire, as '%c'.", + splname, spellet(i)); } + } + if (i < MAXSPELL) { + /* might be learning a new spellbook type or spellbook of blank paper; + if so, persistent inventory will get updated */ makeknown((int) booktype); + /* makeknown() calls update_inventory() when discovering something + new but is a no-op for something that's already known so wouldn't + update persistent inventory to reflect faded book if spellbook of + blank paper happens to already be discovered */ + if (faded_to_blank) + update_inventory(); } if (book->cursed) { /* maybe a demon cursed it */ if (cursed_book(book)) { useup(book); - context.spbook.book = 0; - context.spbook.o_id = 0; + svc.context.spbook.book = 0; + svc.context.spbook.o_id = 0; return 0; } } if (costly) check_unpaid(book); - context.spbook.book = 0; - context.spbook.o_id = 0; + svc.context.spbook.book = 0; + svc.context.spbook.o_id = 0; return 0; } +RESTORE_WARNING_FORMAT_NONLITERAL + int -study_book(spellbook) -register struct obj *spellbook; +study_book(struct obj *spellbook) { - int booktype = spellbook->otyp; + int booktype = spellbook->otyp, i; boolean confused = (Confusion != 0); boolean too_hard = FALSE; /* attempting to read dull book may make hero fall asleep */ if (!confused && !Sleep_resistance - && !strcmp(OBJ_DESCR(objects[booktype]), "dull")) { + && objdescr_is(spellbook, "dull")) { const char *eyes; int dullbook = rnd(25) - ACURR(A_WIS); /* adjust chance if hero stayed awake, got interrupted, retries */ - if (context.spbook.delay && spellbook == context.spbook.book) + if (svc.context.spbook.delay && spellbook == svc.context.spbook.book) dullbook -= rnd(objects[booktype].oc_level); if (dullbook > 0) { eyes = body_part(EYE); - if (eyecount(youmonst.data) > 1) + if (eyecount(gy.youmonst.data) > 1) eyes = makeplural(eyes); pline("This book is so dull that you can't keep your %s open.", eyes); @@ -464,12 +493,14 @@ register struct obj *spellbook; } } - if (context.spbook.delay && !confused && spellbook == context.spbook.book + if (svc.context.spbook.delay && !confused + && spellbook == svc.context.spbook.book /* handle the sequence: start reading, get interrupted, have - context.spbook.book become erased somehow, resume reading it */ + svc.context.spbook.book become erased somehow, resume reading it */ && booktype != SPE_BLANK_PAPER) { You("continue your efforts to %s.", - (booktype == SPE_NOVEL) ? "read the novel" : "memorize the spell"); + (booktype == SPE_NOVEL) ? "read the novel" + : "memorize the spell"); } else { /* KMH -- Simplified this code */ if (booktype == SPE_BLANK_PAPER) { @@ -485,10 +516,15 @@ register struct obj *spellbook; if (read_tribute("books", tribtitle, 0, (char *) 0, 0, spellbook->o_id)) { - u.uconduct.literate++; + if (!u.uconduct.literate++) + livelog_printf(LL_CONDUCT, + "became literate by reading %s", + tribtitle); + check_unpaid(spellbook); makeknown(booktype); if (!u.uevent.read_tribute) { + record_achievement(ACH_NOVL); /* give bonus of 20 xp and 4*20+0 pts */ more_experienced(20, 0); newexplevel(); @@ -501,20 +537,20 @@ register struct obj *spellbook; switch (objects[booktype].oc_level) { case 1: case 2: - context.spbook.delay = -objects[booktype].oc_delay; + svc.context.spbook.delay = -objects[booktype].oc_delay; break; case 3: case 4: - context.spbook.delay = -(objects[booktype].oc_level - 1) + svc.context.spbook.delay = -(objects[booktype].oc_level - 1) * objects[booktype].oc_delay; break; case 5: case 6: - context.spbook.delay = + svc.context.spbook.delay = -objects[booktype].oc_level * objects[booktype].oc_delay; break; case 7: - context.spbook.delay = -8 * objects[booktype].oc_delay; + svc.context.spbook.delay = -8 * objects[booktype].oc_delay; break; default: impossible("Unknown spellbook level %d, book %d;", @@ -522,6 +558,20 @@ register struct obj *spellbook; return 0; } + /* check to see if we already know it and want to refresh our memory */ + for (i = 0; i < MAXSPELL; i++) + if (spellid(i) == booktype || spellid(i) == NO_SPELL) + break; + if (spellid(i) == booktype && spellknow(i) > KEEN / 10) { + You("know \"%s\" quite well already.", + OBJ_NAME(objects[booktype])); + /* hero has just been told what spell this book is for; it may + have been undiscovered if spell was learned via divine gift */ + makeknown(booktype); + if (y_n("Refresh your memory anyway?") == 'n') + return 0; + } + /* Books are often wiser than their readers (Rus.) */ spellbook->in_use = TRUE; if (!spellbook->blessed && spellbook->otyp != SPE_BOOK_OF_THE_DEAD) { @@ -540,7 +590,7 @@ register struct obj *spellbook; Sprintf(qbuf, "This spellbook is %sdifficult to comprehend. Continue?", (read_ability < 12 ? "very " : "")); - if (yn(qbuf) != 'y') { + if (y_n(qbuf) != 'y') { spellbook->in_use = FALSE; return 1; } @@ -555,16 +605,14 @@ register struct obj *spellbook; if (too_hard) { boolean gone = cursed_book(spellbook); - nomul(context.spbook.delay); /* study time */ - multi_reason = "reading a book"; - nomovemsg = 0; - context.spbook.delay = 0; + nomul(svc.context.spbook.delay); /* study time */ + gm.multi_reason = "reading a book"; + gn.nomovemsg = 0; + svc.context.spbook.delay = 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); + trycall(spellbook); useup(spellbook); } else spellbook->in_use = FALSE; @@ -573,10 +621,10 @@ register struct obj *spellbook; if (!confused_book(spellbook)) { spellbook->in_use = FALSE; } - nomul(context.spbook.delay); - multi_reason = "reading a book"; - nomovemsg = 0; - context.spbook.delay = 0; + nomul(svc.context.spbook.delay); + gm.multi_reason = "reading a book"; + gn.nomovemsg = 0; + svc.context.spbook.delay = 0; return 1; } spellbook->in_use = FALSE; @@ -585,9 +633,9 @@ register struct obj *spellbook; spellbook->otyp == SPE_BOOK_OF_THE_DEAD ? "recite" : "memorize"); } - context.spbook.book = spellbook; - if (context.spbook.book) - context.spbook.o_id = context.spbook.book->o_id; + svc.context.spbook.book = spellbook; + if (svc.context.spbook.book) + svc.context.spbook.o_id = svc.context.spbook.book->o_id; set_occupation(learn, "studying", 0); return 1; } @@ -595,12 +643,11 @@ register struct obj *spellbook; /* a spellbook has been destroyed or the character has changed levels; the stored address for the current book is no longer valid */ void -book_disappears(obj) -struct obj *obj; +book_disappears(struct obj *obj) { - if (obj == context.spbook.book) { - context.spbook.book = (struct obj *) 0; - context.spbook.o_id = 0; + if (obj == svc.context.spbook.book) { + svc.context.spbook.book = (struct obj *) 0; + svc.context.spbook.o_id = 0; } } @@ -608,19 +655,18 @@ struct obj *obj; so the sequence start reading, get interrupted, name the book, resume reading would read the "new" book from scratch */ void -book_substitution(old_obj, new_obj) -struct obj *old_obj, *new_obj; +book_substitution(struct obj *old_obj, struct obj *new_obj) { - if (old_obj == context.spbook.book) { - context.spbook.book = new_obj; - if (context.spbook.book) - context.spbook.o_id = context.spbook.book->o_id; + if (old_obj == svc.context.spbook.book) { + svc.context.spbook.book = new_obj; + if (svc.context.spbook.book) + svc.context.spbook.o_id = svc.context.spbook.book->o_id; } } /* called from moveloop() */ void -age_spells() +age_spells(void) { int i; /* @@ -637,17 +683,17 @@ age_spells() /* return True if spellcasting is inhibited; only covers a small subset of reasons why casting won't work */ -STATIC_OVL boolean -rejectcasting() +staticfn boolean +rejectcasting(void) { /* rejections which take place before selecting a particular spell */ if (Stunned) { You("are too impaired to cast a spell."); return TRUE; - } else if (!can_chant(&youmonst)) { + } else if (!can_chant(&gy.youmonst)) { You("are unable to chant the incantation."); return TRUE; - } else if (!freehand()) { + } else if (!freehand() && !(uwep && uwep->otyp == QUARTERSTAFF)) { /* Note: !freehand() occurs when weapon and shield (or two-handed * weapon) are welded to hands, so "arms" probably doesn't need * to be makeplural(bodypart(ARM)). @@ -665,43 +711,63 @@ rejectcasting() * Return TRUE if a spell was picked, with the spell index in the return * parameter. Otherwise return FALSE. */ -STATIC_OVL boolean -getspell(spell_no) -int *spell_no; +staticfn boolean +getspell(int *spell_no) { - int nspells, idx; + int nspells, idx, retry_limit; char ilet, lets[BUFSZ], qbuf[QBUFSZ]; + struct _cmd_queue cq, *cmdq; - if (spellid(0) == NO_SPELL) { + nspells = num_spells(); + if (!nspells) { You("don't know any spells right now."); return FALSE; } if (rejectcasting()) return FALSE; /* no spell chosen */ - if (flags.menu_style == MENU_TRADITIONAL) { - /* we know there is at least 1 known spell */ - for (nspells = 1; nspells < MAXSPELL && spellid(nspells) != NO_SPELL; - nspells++) - continue; + if ((cmdq = cmdq_pop()) != 0) { + cq = *cmdq; + free(cmdq); + if (cq.typ == CMDQ_KEY) { + idx = spell_let_to_idx(cq.key); + if (idx < 0 || idx >= nspells) + return FALSE; + *spell_no = idx; + return TRUE; + } else { + return FALSE; + } + } + if (flags.menu_style == MENU_TRADITIONAL) { + /* if we get here, we know there is at least 1 known spell */ if (nspells == 1) Strcpy(lets, "a"); else if (nspells < 27) Sprintf(lets, "a-%c", 'a' + nspells - 1); else if (nspells == 27) - Sprintf(lets, "a-zA"); + Strcpy(lets, "a-zA"); /* this assumes that there are at most 52 spells... */ else Sprintf(lets, "a-zA-%c", 'A' + nspells - 27); - for (;;) { - Sprintf(qbuf, "Cast which spell? [%s *?]", lets); - ilet = yn_function(qbuf, (char *) 0, '\0'); + Snprintf(qbuf, sizeof qbuf, "Cast which spell? [%s *?]", lets); + for (retry_limit = 0; ; ++retry_limit) { + if (retry_limit == 10) { + /* limit is mainly to prevent the fuzzer from getting stuck + since hangup should hit the 'quitchars' case; fuzzer + would too, but after an arbitrary number of attempts */ + pline("That's enough tries."); + return FALSE; + } + ilet = yn_function(qbuf, (char *) 0, '\0', TRUE); if (ilet == '*' || ilet == '?') break; /* use menu mode */ - if (index(quitchars, ilet)) + if (strchr(quitchars, ilet)) { + pline1(Never_mind); return FALSE; + } idx = spell_let_to_idx(ilet); if (idx < 0 || idx >= nspells) { @@ -716,20 +782,54 @@ int *spell_no; spell_no); } -/* the 'Z' command -- cast a spell */ +/* #wizcast - cast any spell even without knowing it */ +int +dowizcast(void) +{ + winid win; + menu_item *selected; + anything any; + int i, n; + + win = create_nhwindow(NHW_MENU); + start_menu(win, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + + for (i = 0; i < MAXSPELL; i++) { + n = (SPE_DIG + i); + if (n >= SPE_BLANK_PAPER) + break; + any.a_int = n; + add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, NO_COLOR, + OBJ_NAME(objects[n]), MENU_ITEMFLAGS_NONE); + } + end_menu(win, "Cast which spell?"); + n = select_menu(win, PICK_ONE, &selected); + destroy_nhwindow(win); + if (n > 0) { + i = selected[0].item.a_int; + free((genericptr_t) selected); + return spelleffects(i, FALSE, TRUE); + } + return ECMD_OK; +} + + +/* the #cast command -- cast a spell */ int -docast() +docast(void) { int spell_no; - if (getspell(&spell_no)) - return spelleffects(spell_no, FALSE); - return 0; + if (getspell(&spell_no)) { + cmdq_add_key(CQ_REPEAT, spellet(spell_no)); + return spelleffects(svs.spl_book[spell_no].sp_id, FALSE, FALSE); + } + return ECMD_FAIL; } -STATIC_OVL const char * -spelltypemnemonic(skill) -int skill; +staticfn const char * +spelltypemnemonic(int skill) { switch (skill) { case P_ATTACK_SPELL: @@ -753,14 +853,255 @@ int skill; } int -spell_skilltype(booktype) -int booktype; +spell_skilltype(int booktype) { return objects[booktype].oc_skill; } -STATIC_OVL void -cast_protection() +/* Wizards learn what spellbooks look like based on their skill in the + spell's school */ +void +skill_based_spellbook_id(void) +{ + if (!Role_if(PM_WIZARD)) + return; + + int booktype; + const uchar spbook_class = (uchar) SPBOOK_CLASS; + + for (booktype = svb.bases[spbook_class]; + booktype < svb.bases[spbook_class + 1]; + booktype++) { + int known_up_to_level; + int skill = spell_skilltype(booktype); + + if (skill == P_NONE) + continue; + + switch (P_SKILL(skill)) { + case P_BASIC: + known_up_to_level = 3; + break; + case P_SKILLED: + known_up_to_level = 5; + break; + case P_EXPERT: + case P_MASTER: + case P_GRAND_MASTER: + known_up_to_level = 7; + break; + case P_UNSKILLED: + default: + /* paupers need more skill than this to ID books, but most wizards + know the basics */ + known_up_to_level = u.uroleplay.pauper ? 0 : 1; + break; + } + + if (objects[booktype].oc_level <= known_up_to_level) + /* makeknown(booktype) but don't exercise Wisdom or mark as + encountered */ + discover_object(booktype, TRUE, FALSE, FALSE); + } +} + + +/* Limit the total area chain lightning can cover; this is both for + technical reasons (making it possible to limit the size of arrays + here and in the display code) and for gameplay balance reasons; + this value should be smaller than TMP_AT_MAX_GLYPHS (display.c) in + order for chain lightning to display properly */ +#define CHAIN_LIGHTNING_LIMIT 100 +/* Unlike most zaps, chain lightning can't hit solid terrain (it + doesn't have enough power), it only covers open space; this also + means that it can't hit monsters inside walls, which makes sense as + they would be earthed */ +#define CHAIN_LIGHTNING_TYP(typ) \ + (SPACE_POS(typ) || (typ) == POOL || (typ) == MOAT /* not WATER */ \ + || (typ) == DRAWBRIDGE_UP || (typ) == LAVAPOOL) /* not LAVAWALL */ +#define CHAIN_LIGHTNING_POS(x, y) \ + (isok(x, y) && (CHAIN_LIGHTNING_TYP(levl[x][y].typ) \ + || (IS_DOOR(levl[x][y].typ) \ + && !(levl[x][y].doormask & (D_CLOSED | D_LOCKED))))) + +struct chain_lightning_zap { + /* direction in which this zap is currently moving; this is an + enum movementdirs, clamped to the range 0 inclusive to N_DIRS + exclusive */ + uchar dir; + /* current location of the zap */ + coordxy x, y; + /* distance this zap can cover without chaining */ + char strength; +}; + +struct chain_lightning_queue { + struct chain_lightning_zap q[CHAIN_LIGHTNING_LIMIT]; + int head; + int tail; + int displayed_beam; +}; + +/* Given a potential chain lightning zap, moves it one square forward in + the given direction, then adds it to the queue unless it would hit an + invalid square or is out of power. + + zap is passed by value, so the move-forward doesn't change the passed + argument. */ +staticfn void +propagate_chain_lightning( + struct chain_lightning_queue *clq, + struct chain_lightning_zap zap) +{ + struct monst *mon; + + zap.x += xdir[zap.dir]; + zap.y += ydir[zap.dir]; + + if (clq->tail >= CHAIN_LIGHTNING_LIMIT) + return; /* zap has covered too many squares */ + if (!CHAIN_LIGHTNING_POS(zap.x, zap.y)) + return; /* zap can't go to this square */ + + mon = m_at(zap.x, zap.y); + if (mon && mon->mpeaceful) + return; /* chain lightning avoids peaceful and tame monsters */ + + /* When hitting a monster that isn't electricity-resistant, a + particular chain lightning zap regains all its power, allowing it to + chain to other monsters; upon hitting a shock-resistant monster it + can't continue any further, but we let it hit the monster to show + the shield effect */ + if (mon && !resists_elec(mon) && !defended(mon, AD_ELEC)) + zap.strength = 3; + else if (mon) + zap.strength = 0; + + /* Unless it hits a monster, the last square of a zap isn't drawn on + screen and can't propagate further, so it may as well be discarded + now */ + if (!mon && !zap.strength) + return; + + /* The same square can't be chained to twice. */ + for (int i = 0; i < clq->tail; i++) { + if (clq->q[i].x == zap.x && clq->q[i].y == zap.y) + return; + } + + /* This array access must be inbounds due to the CHAIN_LIGHTNING_LIMIT + check earlier. */ + clq->q[clq->tail++] = zap; + + /* Draw it. */ + tmp_at(DISP_CHANGE, zapdir_to_glyph(xdir[zap.dir], ydir[zap.dir], + clq->displayed_beam)); + tmp_at(zap.x, zap.y); +} + +staticfn void +cast_chain_lightning(void) +{ + struct chain_lightning_queue clq = { + {{0}}, 0, 0, Hallucination ? rn2_on_display_rng(6) : (AD_ELEC - 1) + }; + + if (u.uswallow) { + // TODO: damage the engulfer + return; + } + + /* set the type of beam we're using; the direction here is arbitrary + because we change the beam direction just before drawing the beam + anyway */ + tmp_at(DISP_BEAM, zapdir_to_glyph(0, 1, clq.displayed_beam)); + + /* start by propagating in all directions from the caster */ + for (int dir = 0; dir < N_DIRS; dir++) { + struct chain_lightning_zap zap = { dir, u.ux, u.uy, 2 }; + + propagate_chain_lightning(&clq, zap); + } + nh_delay_output(); + + while (clq.head < clq.tail) { + int delay_tail = clq.tail; + + while (clq.head < delay_tail) { + struct chain_lightning_zap zap = clq.q[clq.head++]; + /* damage any monster that was hit */ + struct monst *mon = m_at(zap.x, zap.y); + + if (mon) { + struct obj *unused = 0; /* AD_ELEC can't destroy armor */ + int dmg; + + gn.notonhead = (mon->mx != gb.bhitpos.x + || mon->my != gb.bhitpos.y); + dmg = zhitm(mon, BZ_U_SPELL(AD_ELEC - 1), 2, &unused); + + if (dmg) { + /* mon has been damaged, but we haven't yet printed the + messages or given kill credit; assume the hero can + sense their spell hitting monsters, because they can + steer it away from peacefuls */ + if (DEADMONSTER(mon)) { + xkilled(mon, XKILL_GIVEMSG); + } else { + pline("You shock %s%s", mon_nam(mon), exclam(dmg)); + /* if a long worm, only map 'I' for its head */ + if (!canseemon(mon) && !gn.notonhead) + /* FIXME: this doesn't work, possibly because + cleaning up tmp_at() restores old glyph? */ + map_invisible(zap.x, zap.y); + } + } else if (canseemon(mon)) { + pline("%s resists.", Monnam(mon)); + } + if (!DEADMONSTER(mon)) { + /* wakeup is via attack, but since mon is already + hostile we pass via_attack==False rather than True, + otherwise other monsters witnessing this would treat + it as seeing hero attack a peaceful; mimic will be + exposed; forcefight makes hider unhide */ + svc.context.forcefight++; + wakeup(mon, FALSE); + svc.context.forcefight--; + } + } + + /* each zap propagates forwards with 1 less strength, and + diagonally with 0 strength (thus the diagonal zaps aren't + drawn and don't spread unless they hit a monster); + exception: if the zap just hit a monster, the diagonals have + as much strength as the forwards zap */ + if (!zap.strength) + continue; /* happens upon hitting a shock-resistant monster */ + zap.strength--; + + propagate_chain_lightning(&clq, zap); + + if (zap.strength < 2) + zap.strength = 0; + else if (u.uen > 0) + u.uen--; /* propagating past mons increases Pw cost a bit */ + zap.dir = DIR_LEFT(zap.dir); + propagate_chain_lightning(&clq, zap); + + zap.dir = DIR_RIGHT2(zap.dir); + propagate_chain_lightning(&clq, zap); + } + nh_delay_output(); + } + nh_delay_output(); + nh_delay_output(); + + tmp_at(DISP_END, 0); +} + + +staticfn void +cast_protection(void) { int l = u.ulevel, loglev = 0, gain, natac = u.uac + u.uspellprot; @@ -806,24 +1147,20 @@ cast_protection() if (u.uspellprot) { pline_The("%s haze around you becomes more dense.", hgolden); } else { + struct permonst *pm = u.ustuck ? u.ustuck->data : 0; + rmtyp = levl[u.ux][u.uy].typ; - atmosphere = u.uswallow - ? ((u.ustuck->data == &mons[PM_FOG_CLOUD]) - ? "mist" - : is_whirly(u.ustuck->data) - ? "maelstrom" - : is_animal(u.ustuck->data) - ? "maw" + atmosphere = (pm && u.uswallow) + ? ((pm == &mons[PM_FOG_CLOUD]) ? "mist" + : is_whirly(pm) ? "maelstrom" + : enfolds(pm) ? "folds" + : is_animal(pm) ? "maw" : "ooze") - : (u.uinwater - ? hliquid("water") - : (rmtyp == CLOUD) - ? "cloud" - : IS_TREE(rmtyp) - ? "vegetation" - : IS_STWALL(rmtyp) - ? "stone" - : "air"); + : (u.uinwater ? hliquid("water") + : (rmtyp == CLOUD) ? "cloud" + : IS_TREE(rmtyp) ? "vegetation" + : IS_STWALL(rmtyp) ? "stone" + : "air"); pline_The("%s around you begins to shimmer with %s haze.", atmosphere, an(hgolden)); } @@ -840,9 +1177,8 @@ cast_protection() } /* attempting to cast a forgotten spell will cause disorientation */ -STATIC_OVL void -spell_backfire(spell) -int spell; +staticfn void +spell_backfire(int spell) { long duration = (long) ((spellev(spell) + 1) * 3), /* 6..24 */ old_stun = (HStun & TIMEOUT), old_conf = (HConfusion & TIMEOUT); @@ -880,17 +1216,13 @@ int spell; return; } -int -spelleffects(spell, atme) -int spell; -boolean atme; +staticfn boolean +spelleffects_check(int spell, int *res, int *energy) { - int energy, damage, chance, n, intell; - int otyp, skill, role_skill, res = 0; + int chance; boolean confused = (Confusion != 0); - boolean physical_damage = FALSE; - struct obj *pseudo; - coord cc; + + *energy = 0; /* * Reject attempting to cast while stunned or with no free hands. @@ -901,10 +1233,17 @@ boolean atme; * (There's no duplication of messages; when the rejection takes * place in getspell(), we don't get called.) */ - if (rejectcasting()) { - return 0; /* no time elapses */ + if ((spell == UNKNOWN_SPELL) || rejectcasting()) { + *res = ECMD_OK; /* no time elapses */ + return TRUE; } + /* + * Note: dotele() also calculates energy use and checks nutrition + * and strength requirements; if any of these change, update it too. + */ + *energy = SPELL_LEV_PW(spellev(spell)); /* 5 <= energy <= 35 */ + /* * Spell casting no longer affects knowledge of the spell. A * decrement of spell knowledge is done every turn. @@ -913,7 +1252,12 @@ boolean atme; Your("knowledge of this spell is twisted."); pline("It invokes nightmarish images in your mind..."); spell_backfire(spell); - return 1; + u.uen -= rnd(*energy); + if (u.uen < 0) + u.uen = 0; + disp.botl = TRUE; + *res = ECMD_TIME; + return TRUE; } else if (spellknow(spell) <= KEEN / 200) { /* 100 turns left */ You("strain to recall the spell."); } else if (spellknow(spell) <= KEEN / 40) { /* 500 turns left */ @@ -923,21 +1267,19 @@ boolean atme; } else if (spellknow(spell) <= KEEN / 10) { /* 2000 turns left */ Your("recall of this spell is gradually fading."); } - /* - * Note: dotele() also calculates energy use and checks nutrition - * and strength requirements; it any of these change, update it too. - */ - energy = (spellev(spell) * 5); /* 5 <= energy <= 35 */ if (u.uhunger <= 10 && spellid(spell) != SPE_DETECT_FOOD) { You("are too hungry to cast that spell."); - return 0; + *res = ECMD_OK; + return TRUE; } else if (ACURR(A_STR) < 4 && spellid(spell) != SPE_RESTORE_ABILITY) { You("lack the strength to cast spells."); - return 0; + *res = ECMD_OK; + return TRUE; } else if (check_capacity( "Your concentration falters while carrying so much stuff.")) { - return 1; + *res = ECMD_TIME; + return TRUE; } /* if the cast attempt is already going to fail due to insufficient @@ -945,7 +1287,7 @@ boolean atme; in and no turn will be consumed; however, when it does kick in, the attempt may fail due to lack of energy after the draining, in which case a turn will be used up in addition to the energy loss */ - if (u.uhave.amulet && u.uen >= energy) { + if (u.uhave.amulet && u.uen >= *energy) { You_feel("the amulet draining your energy away."); /* this used to be 'energy += rnd(2 * energy)' (without 'res'), so if amulet-induced cost was more than u.uen, nothing @@ -953,19 +1295,31 @@ boolean atme; and player could just try again (and again and again...); now we drain some energy immediately, which has a side-effect of not increasing the hunger aspect of casting */ - u.uen -= rnd(2 * energy); + u.uen -= rnd(2 * *energy); if (u.uen < 0) u.uen = 0; - context.botl = 1; - res = 1; /* time is going to elapse even if spell doesn't get cast */ + disp.botl = TRUE; + *res = ECMD_TIME; /* time is used even if spell doesn't get cast */ } - if (energy > u.uen) { - You("don't have enough energy to cast that spell."); - return res; + if (*energy > u.uen) { + /* + * Hero has insufficient energy/power to cast the spell. + * Augment the message when current energy is at maximum. + * "yet": mainly for level 1 characters who already know a spell + * but don't start with enough energy to cast it. + * "anymore": maximum energy was high enough at some point but + * isn't now (lost energy when losing levels or polymorphing into + * new person or had some stripped away by traps or monsters). + */ + You("don't have enough energy to cast that spell%s.", + (u.uen < u.uenmax) ? "" /* not at full energy => normal message */ + : (*energy > u.uenpeak) ? " yet" /* haven't ever had enough */ + : " anymore"); /* once had enough but have lost some since */ + return TRUE; } else { if (spellid(spell) != SPE_DETECT_FOOD) { - int hungr = energy * 2; + int hungr = *energy * 2; /* If hero is a wizard, their current intelligence * (bonuses + temporary + current) @@ -980,7 +1334,7 @@ boolean atme; * b) Wizards have spent their life at magic and * understand quite well how to cast spells. */ - intell = acurr(A_INT); + int intell = acurr(A_INT); if (!Role_if(PM_WIZARD)) intell = 10; switch (intell) { @@ -1017,16 +1371,34 @@ boolean atme; chance = percent_success(spell); if (confused || (rnd(100) > chance)) { You("fail to cast the spell correctly."); - u.uen -= energy / 2; - context.botl = 1; - return 1; + u.uen -= *energy / 2; + disp.botl = TRUE; + *res = ECMD_TIME; + return TRUE; } + return FALSE; +} + +/* hero casts a spell of type spell_otyp, eg. SPE_SLEEP. + hero must know the spell (unless force is TRUE). */ +int +spelleffects(int spell_otyp, boolean atme, boolean force) +{ + int spell = force ? spell_otyp : spell_idx(spell_otyp); + int energy = 0, damage, n; + int otyp, skill, role_skill, res = ECMD_OK; + boolean physical_damage = FALSE; + struct obj *pseudo; + coord cc; + + if (!force && spelleffects_check(spell, &res, &energy)) + return res; u.uen -= energy; - context.botl = 1; + disp.botl = TRUE; exercise(A_WIS, TRUE); /* pseudo is a temporary "false" object containing the spell stats */ - pseudo = mksobj(spellid(spell), FALSE, FALSE); + pseudo = mksobj(force ? spell : spellid(spell), FALSE, FALSE); pseudo->blessed = pseudo->cursed = 0; pseudo->quan = 20L; /* do not let useup get it */ /* @@ -1079,11 +1451,13 @@ boolean atme; } break; } /* else */ + FALLTHROUGH; /*FALLTHRU*/ /* these spells are all duplicates of wand effects */ case SPE_FORCE_BOLT: physical_damage = TRUE; + FALLTHROUGH; /*FALLTHRU*/ case SPE_SLEEP: case SPE_MAGIC_MISSILE: @@ -1145,11 +1519,12 @@ boolean atme; case SPE_DETECT_FOOD: case SPE_CAUSE_FEAR: case SPE_IDENTIFY: + case SPE_CHARM_MONSTER: /* high skill yields effect equivalent to blessed scroll */ if (role_skill >= P_SKILLED) pseudo->blessed = 1; + FALLTHROUGH; /*FALLTHRU*/ - case SPE_CHARM_MONSTER: case SPE_MAGIC_MAPPING: case SPE_CREATE_MONSTER: (void) seffects(pseudo); @@ -1164,6 +1539,7 @@ boolean atme; /* high skill yields effect equivalent to blessed potion */ if (role_skill >= P_SKILLED) pseudo->blessed = 1; + FALLTHROUGH; /*FALLTHRU*/ case SPE_INVISIBILITY: (void) peffects(pseudo); @@ -1173,13 +1549,23 @@ boolean atme; case SPE_CURE_BLINDNESS: healup(0, 0, FALSE, TRUE); break; - case SPE_CURE_SICKNESS: - if (Sick) - You("are no longer ill."); - if (Slimed) - make_slimed(0L, "The slime disappears!"); + case SPE_CURE_SICKNESS: { + boolean was_sick = !!Sick, was_slimed = !!Slimed; + + /* cure conditions (which updates status) before feedback */ healup(0, 0, TRUE, FALSE); + /* + * Sick + !Slimed -- You are no longer ill. + * !Sick + !Slimed -- You are not ill. + * !Sick + Slimed -- The slime disappears. + * Sick + Slimed -- You are no longer ill. The slime disappears. + */ + if (was_sick || !was_slimed) + You("are %s ill.", was_sick ? "no longer" : "not"); + if (was_slimed) + make_slimed(0L, "The slime disappears!"); break; + } case SPE_CREATE_FAMILIAR: (void) make_familiar((struct obj *) 0, u.ux, u.uy, FALSE); break; @@ -1196,27 +1582,29 @@ boolean atme; cast_protection(); break; case SPE_JUMPING: - if (!jump(max(role_skill, 1))) + if (!(jump(max(role_skill, 1)) & ECMD_TIME)) pline1(nothing_happens); break; + case SPE_CHAIN_LIGHTNING: + cast_chain_lightning(); + break; default: impossible("Unknown spell %d attempted.", spell); obfree(pseudo, (struct obj *) 0); - return 0; + return ECMD_OK; } /* gain skill for successful cast */ - use_skill(skill, spellev(spell)); + if (!force) + use_skill(skill, spellev(spell)); obfree(pseudo, (struct obj *) 0); /* now, get rid of it */ - return 1; + return ECMD_TIME; } /*ARGSUSED*/ -STATIC_OVL boolean -spell_aim_step(arg, x, y) -genericptr_t arg UNUSED; -int x, y; +staticfn boolean +spell_aim_step(genericptr_t arg UNUSED, coordxy x, coordxy y) { if (!isok(x,y)) return FALSE; @@ -1226,9 +1614,45 @@ int x, y; return TRUE; } +/* not quite the same as throwspell limits, but close enough */ +staticfn boolean +can_center_spell_location(coordxy x, coordxy y) +{ + if (distmin(u.ux, u.uy, x, y) > 10) + return FALSE; + return (isok(x, y) && cansee(x, y) && !(IS_STWALL(levl[x][y].typ))); +} + +staticfn void +display_spell_target_positions(boolean on_off) +{ + coordxy x, y, dx, dy; + int dist = 10; + + if (on_off) { + /* on */ + tmp_at(DISP_BEAM, cmap_to_glyph(S_goodpos)); + for (dx = -dist; dx <= dist; dx++) + for (dy = -dist; dy <= dist; dy++) { + x = u.ux + dx; + y = u.uy + dy; + /* hero's location is allowed but highlighting the hero's + spot makes map harder to read (if using '$' rather than + by changing background color) */ + if (u_at(x, y)) + continue; + if (can_center_spell_location(x, y)) + tmp_at(x, y); + } + } else { + /* off */ + tmp_at(DISP_END, 0); + } +} + /* Choose location where spell takes effect. */ -STATIC_OVL int -throwspell() +staticfn int +throwspell(void) { coord cc, uc; struct monst *mtmp; @@ -1244,8 +1668,12 @@ throwspell() pline("Where do you want to cast the spell?"); cc.x = u.ux; cc.y = u.uy; + getpos_sethilite(display_spell_target_positions, + can_center_spell_location); if (getpos(&cc, TRUE, "the desired position") < 0) return 0; /* user pressed ESC */ + clear_nhwindow(WIN_MESSAGE); /* discard any autodescribe feedback */ + /* The number of moves from hero to where the spell drops.*/ if (distmin(u.ux, u.uy, cc.x, cc.y) > 10) { pline_The("spell dissipates over the distance!"); @@ -1256,7 +1684,7 @@ throwspell() u.dx = 0; u.dy = 0; return 1; - } else if ((!cansee(cc.x, cc.y) + } else if (((cc.x != u.ux || cc.y != u.uy) && !cansee(cc.x, cc.y) && (!(mtmp = m_at(cc.x, cc.y)) || !canspotmon(mtmp))) || IS_STWALL(levl[cc.x][cc.y].typ)) { Your("mind fails to lock onto that location!"); @@ -1276,8 +1704,7 @@ throwspell() /* add/hide/remove/unhide teleport-away on behalf of dotelecmd() to give more control to behavior of ^T when used in wizard mode */ int -tport_spell(what) -int what; +tport_spell(int what) { static struct tport_hideaway { struct spell savespell; @@ -1302,14 +1729,14 @@ int what; save_tport.tport_indx = MAXSPELL; } else if (what == UNHIDESPELL) { /*assert( save_tport.savespell.sp_id == SPE_TELEPORT_AWAY );*/ - spl_book[save_tport.tport_indx] = save_tport.savespell; + svs.spl_book[save_tport.tport_indx] = save_tport.savespell; save_tport.tport_indx = MAXSPELL; /* burn bridge... */ } else if (what == ADD_SPELL) { - save_tport.savespell = spl_book[i]; + save_tport.savespell = svs.spl_book[i]; save_tport.tport_indx = i; - spl_book[i].sp_id = SPE_TELEPORT_AWAY; - spl_book[i].sp_lev = objects[SPE_TELEPORT_AWAY].oc_level; - spl_book[i].sp_know = KEEN; + svs.spl_book[i].sp_id = SPE_TELEPORT_AWAY; + svs.spl_book[i].sp_lev = objects[SPE_TELEPORT_AWAY].oc_level; + svs.spl_book[i].sp_know = KEEN; return REMOVESPELL; /* operation needed to reverse */ } } else { /* spellid(i) == SPE_TELEPORT_AWAY */ @@ -1317,12 +1744,12 @@ int what; save_tport.tport_indx = MAXSPELL; } else if (what == REMOVESPELL) { /*assert( i == save_tport.tport_indx );*/ - spl_book[i] = save_tport.savespell; + svs.spl_book[i] = save_tport.savespell; save_tport.tport_indx = MAXSPELL; } else if (what == HIDE_SPELL) { - save_tport.savespell = spl_book[i]; + save_tport.savespell = svs.spl_book[i]; save_tport.tport_indx = i; - spl_book[i].sp_id = NO_SPELL; + svs.spl_book[i].sp_id = NO_SPELL; return UNHIDESPELL; /* operation needed to reverse */ } } @@ -1333,13 +1760,13 @@ int what; they used to be lost entirely, as if never learned, but now we just set the memory retention to zero so that they can't be cast */ void -losespells() +losespells(void) { int n, nzap, i; /* in case reading has been interrupted earlier, discard context */ - context.spbook.book = 0; - context.spbook.o_id = 0; + svc.context.spbook.book = 0; + svc.context.spbook.o_id = 0; /* count the number of known spells */ for (n = 0; n < MAXSPELL; ++n) if (spellid(n) == NO_SPELL) @@ -1425,7 +1852,7 @@ enum spl_sort_types { NUM_SPELL_SORTBY }; -static const char *spl_sortchoices[NUM_SPELL_SORTBY] = { +static const char *const spl_sortchoices[NUM_SPELL_SORTBY] = { "by casting letter", "alphabetically", "by level, low to high", @@ -1437,29 +1864,25 @@ static const char *spl_sortchoices[NUM_SPELL_SORTBY] = { /* a menu choice rather than a sort choice */ "reassign casting letters to retain current order", }; -static int spl_sortmode = 0; /* index into spl_sortchoices[] */ -static int *spl_orderindx = 0; /* array of spl_book[] indices */ /* qsort callback routine */ -STATIC_PTR int CFDECLSPEC -spell_cmp(vptr1, vptr2) -const genericptr vptr1; -const genericptr vptr2; +staticfn int QSORTCALLBACK +spell_cmp(const genericptr vptr1, const genericptr vptr2) { /* * gather up all of the possible parameters except spell name * in advance, even though some might not be needed: - * indx. = spl_orderindx[] index into spl_book[]; - * otyp. = spl_book[] index into objects[]; + * indx. = spl_orderindx[] index into svs.spl_book[]; + * otyp. = svs.spl_book[] index into objects[]; * levl. = spell level; * skil. = skill group aka spell class. */ int indx1 = *(int *) vptr1, indx2 = *(int *) vptr2, - otyp1 = spl_book[indx1].sp_id, otyp2 = spl_book[indx2].sp_id, + otyp1 = svs.spl_book[indx1].sp_id, otyp2 = svs.spl_book[indx2].sp_id, levl1 = objects[otyp1].oc_level, levl2 = objects[otyp2].oc_level, skil1 = objects[otyp1].oc_skill, skil2 = objects[otyp2].oc_skill; - switch (spl_sortmode) { + switch (gs.spl_sortmode) { case SORTBY_LETTER: return indx1 - indx2; case SORTBY_ALPHA: @@ -1500,8 +1923,8 @@ const genericptr vptr2; /* sort the index used for display order of the "view known spells" list (sortmode == SORTBY_xxx), or sort the spellbook itself to make the current display order stick (sortmode == SORTRETAINORDER) */ -STATIC_OVL void -sortspells() +staticfn void +sortspells(void) { int i; #if defined(SYSV) || defined(DGUX) @@ -1510,70 +1933,72 @@ sortspells() int n; #endif - if (spl_sortmode == SORTBY_CURRENT) + if (gs.spl_sortmode == SORTBY_CURRENT) return; for (n = 0; n < MAXSPELL && spellid(n) != NO_SPELL; ++n) continue; if (n < 2) return; /* not enough entries to need sorting */ - if (!spl_orderindx) { + if (!gs.spl_orderindx) { /* we haven't done any sorting yet; list is in casting order */ - if (spl_sortmode == SORTBY_LETTER /* default */ - || spl_sortmode == SORTRETAINORDER) + if (gs.spl_sortmode == SORTBY_LETTER /* default */ + || gs.spl_sortmode == SORTRETAINORDER) return; /* allocate enough for full spellbook rather than just N spells */ - spl_orderindx = (int *) alloc(MAXSPELL * sizeof(int)); + gs.spl_orderindx = (int *) alloc(MAXSPELL * sizeof(int)); for (i = 0; i < MAXSPELL; i++) - spl_orderindx[i] = i; + gs.spl_orderindx[i] = i; } - if (spl_sortmode == SORTRETAINORDER) { + if (gs.spl_sortmode == SORTRETAINORDER) { struct spell tmp_book[MAXSPELL]; - /* sort spl_book[] rather than spl_orderindx[]; + /* sort svs.spl_book[] rather than spl_orderindx[]; this also updates the index to reflect the new ordering (we could just free it since that ordering becomes the default) */ for (i = 0; i < MAXSPELL; i++) - tmp_book[i] = spl_book[spl_orderindx[i]]; + tmp_book[i] = svs.spl_book[gs.spl_orderindx[i]]; for (i = 0; i < MAXSPELL; i++) - spl_book[i] = tmp_book[i], spl_orderindx[i] = i; - spl_sortmode = SORTBY_LETTER; /* reset */ + svs.spl_book[i] = tmp_book[i], gs.spl_orderindx[i] = i; + gs.spl_sortmode = SORTBY_LETTER; /* reset */ return; } /* usual case, sort the index rather than the spells themselves */ - qsort((genericptr_t) spl_orderindx, n, sizeof *spl_orderindx, spell_cmp); + qsort((genericptr_t) gs.spl_orderindx, n, + sizeof *gs.spl_orderindx, spell_cmp); return; } /* called if the [sort spells] entry in the view spells menu gets chosen */ -STATIC_OVL boolean -spellsortmenu() +staticfn boolean +spellsortmenu(void) { winid tmpwin; menu_item *selected; anything any; char let; int i, n, choice; + int clr = NO_COLOR; tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - any = zeroany; /* zero out all bits */ + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; /* zero out all bits */ for (i = 0; i < SIZE(spl_sortchoices); i++) { if (i == SORTRETAINORDER) { let = 'z'; /* assumes fewer than 26 sort choices... */ /* separate final choice from others with a blank line */ - any.a_int = 0; - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", - MENU_UNSELECTED); + add_menu_str(tmpwin, ""); } else { let = 'a' + i; } any.a_int = i + 1; - add_menu(tmpwin, NO_GLYPH, &any, let, 0, ATR_NONE, spl_sortchoices[i], - (i == spl_sortmode) ? MENU_SELECTED : MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, let, 0, + ATR_NONE, clr, spl_sortchoices[i], + (i == gs.spl_sortmode) ? MENU_ITEMFLAGS_SELECTED + : MENU_ITEMFLAGS_NONE); } end_menu(tmpwin, "View known spells list sorted"); @@ -1582,18 +2007,18 @@ spellsortmenu() if (n > 0) { choice = selected[0].item.a_int - 1; /* skip preselected entry if we have more than one item chosen */ - if (n > 1 && choice == spl_sortmode) + if (n > 1 && choice == gs.spl_sortmode) choice = selected[1].item.a_int - 1; free((genericptr_t) selected); - spl_sortmode = choice; + gs.spl_sortmode = choice; return TRUE; } return FALSE; } -/* the '+' command -- view known spells */ +/* the #showspells command -- view known spells */ int -dovspell() +dovspell(void) { char qbuf[QBUFSZ]; int splnum, othnum; @@ -1613,64 +2038,97 @@ dovspell() if (!dospellmenu(qbuf, splnum, &othnum)) break; - spl_tmp = spl_book[splnum]; - spl_book[splnum] = spl_book[othnum]; - spl_book[othnum] = spl_tmp; + spl_tmp = svs.spl_book[splnum]; + svs.spl_book[splnum] = svs.spl_book[othnum]; + svs.spl_book[othnum] = spl_tmp; } } } - if (spl_orderindx) { - free((genericptr_t) spl_orderindx); - spl_orderindx = 0; + if (gs.spl_orderindx) { + free((genericptr_t) gs.spl_orderindx); + gs.spl_orderindx = 0; } - spl_sortmode = SORTBY_LETTER; /* 0 */ - return 0; + gs.spl_sortmode = SORTBY_LETTER; /* 0 */ + return ECMD_OK; } -STATIC_OVL boolean -dospellmenu(prompt, splaction, spell_no) -const char *prompt; -int splaction; /* SPELLMENU_CAST, SPELLMENU_VIEW, or spl_book[] index */ -int *spell_no; +DISABLE_WARNING_FORMAT_NONLITERAL + +/* lists spells for endgame dumplog purposes */ +void +show_spells(void) +{ + int unused = SPELLMENU_DUMP; + if (spellid(0) == NO_SPELL) { + pline("You didn't know any spells."); + pline("%s", ""); + } else { + pline("Spells:"); + nhUse(dospellmenu("", SPELLMENU_DUMP, &unused)); + } +} + +/* shows menu of known spells, with options to sort them. + return FALSE on cancel, TRUE otherwise. + spell_no is set to the internal spl_book index, if any selected */ +staticfn boolean +dospellmenu( + const char *prompt, + int splaction, /* SPELLMENU_CAST, SPELLMENU_VIEW, SPELLMENU_DUMP or + * svs.spl_book[] index */ + int *spell_no) { winid tmpwin; int i, n, how, splnum; - char buf[BUFSZ], retentionbuf[24]; + char buf[BUFSZ], retentionbuf[24], sep; const char *fmt; menu_item *selected; anything any; + int clr = NO_COLOR; tmpwin = create_nhwindow(NHW_MENU); - start_menu(tmpwin); - any = zeroany; /* zero out all bits */ + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; /* zero out all bits */ /* * The correct spacing of the columns when not using * tab separation depends on the following: * (1) that the font is monospaced, and - * (2) that selection letters are pre-pended to the - * given string and are of the form "a - ". + * (2) that selection letters are prepended to the + * given string and are of the form "a - ". + * For SPELLMENU_DUMP, (2) is untrue, so four spaces + * need to be subtracted. */ if (!iflags.menu_tab_sep) { - Sprintf(buf, "%-20s Level %-12s Fail Retention", " Name", + Sprintf(buf, "%s%-20s Level %-12s Fail Retention", + splaction == SPELLMENU_DUMP ? "" : " ", + "Name", "Category"); fmt = "%-20s %2d %-12s %3d%% %9s"; + sep = ' '; } else { Sprintf(buf, "Name\tLevel\tCategory\tFail\tRetention"); fmt = "%s\t%-d\t%s\t%-d%%\t%s"; + sep = '\t'; } - add_menu(tmpwin, NO_GLYPH, &any, 0, 0, iflags.menu_headings, buf, - MENU_UNSELECTED); + if (wizard) + Sprintf(eos(buf), "%c%6s", sep, "turns"); + + add_menu_heading(tmpwin, buf); for (i = 0; i < MAXSPELL && spellid(i) != NO_SPELL; i++) { - splnum = !spl_orderindx ? i : spl_orderindx[i]; + splnum = !gs.spl_orderindx ? i : gs.spl_orderindx[i]; Sprintf(buf, fmt, spellname(splnum), spellev(splnum), spelltypemnemonic(spell_skilltype(spellid(splnum))), 100 - percent_success(splnum), spellretention(splnum, retentionbuf)); + if (wizard) + Sprintf(eos(buf), "%c%6d", sep, spellknow(i)); any.a_int = splnum + 1; /* must be non-zero */ - add_menu(tmpwin, NO_GLYPH, &any, spellet(splnum), 0, ATR_NONE, buf, - (splnum == splaction) ? MENU_SELECTED : MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, spellet(splnum), 0, + ATR_NONE, clr, buf, + (splnum == splaction) + ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); } how = PICK_ONE; if (splaction == SPELLMENU_VIEW) { @@ -1680,8 +2138,8 @@ int *spell_no; } else { /* more than 1 spell, add an extra menu entry */ any.a_int = SPELLMENU_SORT + 1; - add_menu(tmpwin, NO_GLYPH, &any, '+', 0, ATR_NONE, - "[sort spells]", MENU_UNSELECTED); + add_menu(tmpwin, &nul_glyphinfo, &any, '+', 0, + ATR_NONE, clr, "[sort spells]", MENU_ITEMFLAGS_NONE); } } end_menu(tmpwin, prompt); @@ -1709,40 +2167,49 @@ int *spell_no; return FALSE; } -STATIC_OVL int -percent_success(spell) -int spell; +RESTORE_WARNING_FORMAT_NONLITERAL + +staticfn int +percent_success(int spell) { /* Intrinsic and learned ability are combined to calculate - * the probability of player's success at cast a given spell. + * the probability of player's success at casting a given spell. */ int chance, splcaster, special, statused; int difficulty; - int skill; + int skill, skilltype = spell_skilltype(spellid(spell)); + /* Knights don't get metal armor penalty for clerical spells */ + boolean paladin_bonus = (Role_if(PM_KNIGHT) + && skilltype == P_CLERIC_SPELL); /* Calculate intrinsic ability (splcaster) */ - splcaster = urole.spelbase; - special = urole.spelheal; - statused = ACURR(urole.spelstat); + splcaster = gu.urole.spelbase; + special = gu.urole.spelheal; + statused = ACURR(gu.urole.spelstat); - if (uarm && is_metallic(uarm)) - splcaster += (uarmc && uarmc->otyp == ROBE) ? urole.spelarmr / 2 - : urole.spelarmr; + if (uarm && is_metallic(uarm) && !paladin_bonus) + splcaster += (uarmc && uarmc->otyp == ROBE) ? gu.urole.spelarmr / 2 + : gu.urole.spelarmr; else if (uarmc && uarmc->otyp == ROBE) - splcaster -= urole.spelarmr; + splcaster -= gu.urole.spelarmr; if (uarms) - splcaster += urole.spelshld; - - if (uarmh && is_metallic(uarmh) && uarmh->otyp != HELM_OF_BRILLIANCE) - splcaster += uarmhbon; - if (uarmg && is_metallic(uarmg)) - splcaster += uarmgbon; - if (uarmf && is_metallic(uarmf)) - splcaster += uarmfbon; + splcaster += gu.urole.spelshld; + + if (uwep && uwep->otyp == QUARTERSTAFF) + splcaster -= 3; /* Small bonus */ + + if (!paladin_bonus) { + if (uarmh && is_metallic(uarmh)) /* && otyp != HELM_OF_BRILLIANCE */ + splcaster += uarmhbon; + if (uarmg && is_metallic(uarmg)) + splcaster += uarmgbon; + if (uarmf && is_metallic(uarmf)) + splcaster += uarmfbon; + } - if (spellid(spell) == urole.spelspec) - splcaster += urole.spelsbon; + if (spellid(spell) == gu.urole.spelspec) + splcaster += gu.urole.spelsbon; /* `healing spell' bonus */ if (spellid(spell) == SPE_HEALING || spellid(spell) == SPE_EXTRA_HEALING @@ -1757,17 +2224,17 @@ int spell; /* Calculate learned ability */ - /* Players basic likelihood of being able to cast any spell + /* The player's basic likelihood of being able to cast any spell * is based of their `magic' statistic. (Int or Wis) */ chance = 11 * statused / 2; /* - * High level spells are harder. Easier for higher level casters. + * High-level spells are harder. Easier for higher-level casters. * The difficulty is based on the hero's level and their skill level * in that spell type. */ - skill = P_SKILL(spell_skilltype(spellid(spell))); + skill = P_SKILL(skilltype); skill = max(skill, P_UNSKILLED) - 1; /* unskilled => 0 */ difficulty = (spellev(spell) - 1) * 4 - ((skill * 6) + (u.ulevel / 3) + 1); @@ -1800,7 +2267,7 @@ int spell; * player's role-specific spell. */ if (uarms && weight(uarms) > (int) objects[SMALL_SHIELD].oc_weight) { - if (spellid(spell) == urole.spelspec) { + if (spellid(spell) == gu.urole.spelspec) { chance /= 2; } else { chance /= 4; @@ -1824,10 +2291,8 @@ int spell; return chance; } -STATIC_OVL char * -spellretention(idx, outbuf) -int idx; -char *outbuf; +staticfn char * +spellretention(int idx, char * outbuf) { long turnsleft, percent, accuracy; int skill; @@ -1859,10 +2324,10 @@ char *outbuf; * KEEN is a multiple of 100; KEEN/100 loses no precision. */ percent = (turnsleft - 1L) / ((long) KEEN / 100L) + 1L; - accuracy = - (skill == P_EXPERT) ? 2L : (skill == P_SKILLED) - ? 5L - : (skill == P_BASIC) ? 10L : 25L; + accuracy = (skill == P_EXPERT) ? 2L + : (skill == P_SKILLED) ? 5L + : (skill == P_BASIC) ? 10L + : 25L; /* round up to the high end of this range */ percent = accuracy * ((percent - 1L) / accuracy + 1L); Sprintf(outbuf, "%ld%%-%ld%%", percent - accuracy + 1L, percent); @@ -1872,8 +2337,7 @@ char *outbuf; /* Learn a spell during creation of the initial inventory */ void -initialspell(obj) -struct obj *obj; +initialspell(struct obj *obj) { int i, otyp = obj->otyp; @@ -1887,11 +2351,77 @@ struct obj *obj; /* initial inventory shouldn't contain duplicate spellbooks */ impossible("Spell %s already known.", OBJ_NAME(objects[otyp])); } else { - spl_book[i].sp_id = otyp; - spl_book[i].sp_lev = objects[otyp].oc_level; + svs.spl_book[i].sp_id = otyp; + svs.spl_book[i].sp_lev = objects[otyp].oc_level; incrnknow(i, 0); } return; } +/* returns one of spe_Unknown, spe_Fresh, spe_GoingStale, spe_Forgotten */ +int +known_spell(short otyp) +{ + int i, k; + + for (i = 0; (i < MAXSPELL) && (spellid(i) != NO_SPELL); i++) + if (spellid(i) == otyp) { + k = spellknow(i); + return (k > KEEN / 10) ? spe_Fresh + : (k > 0) ? spe_GoingStale + : spe_Forgotten; + } + return spe_Unknown; +} + +/* return index for spell otyp, or UNKNOWN_SPELL if not found */ +int +spell_idx(short otyp) +{ + int i; + + for (i = 0; (i < MAXSPELL) && (spellid(i) != NO_SPELL); i++) + if (spellid(i) == otyp) + return i; + return UNKNOWN_SPELL; +} + +/* learn or refresh spell otyp, if feasible; return casting letter or '\0' */ +char +force_learn_spell(short otyp) +{ + int i; + + if (otyp == SPE_BLANK_PAPER || otyp == SPE_BOOK_OF_THE_DEAD + || known_spell(otyp) == spe_Fresh) + return '\0'; + + for (i = 0; i < MAXSPELL; i++) + if (spellid(i) == NO_SPELL || spellid(i) == otyp) + break; + if (i == MAXSPELL) { + impossible("Too many spells memorized"); + return '\0'; + } + /* for a going-stale or forgotten spell the sp_id and sp_lev assignments + are redundant but harmless; for an unknown spell, they're essential */ + svs.spl_book[i].sp_id = otyp; + svs.spl_book[i].sp_lev = objects[otyp].oc_level; + incrnknow(i, 0); /* set spl_book[i].sp_know to KEEN; unlike when learning + * a spell by reading its book, we don't need to add 1 */ + return spellet(i); +} + +/* number of spells hero knows */ +int +num_spells(void) +{ + int i; + + for (i = 0; i < MAXSPELL; i++) + if (spellid(i) == NO_SPELL) + break; + return i; +} + /*spell.c*/ diff --git a/src/stairs.c b/src/stairs.c new file mode 100644 index 000000000..bf052aaf9 --- /dev/null +++ b/src/stairs.c @@ -0,0 +1,237 @@ +/* NetHack 5.0 stairs.c $NHDT-Date: 1704043695 2023/12/31 17:28:15 $ $NHDT-Branch: keni-luabits2 $:$NHDT-Revision: 1.207 $ */ +/* Copyright (c) 2024 by Pasi Kallinen */ +/* NetHack may be freely redistributed. See license for details. */ + +#include "hack.h" + +void +stairway_add( + coordxy x, coordxy y, + boolean up, boolean isladder, + d_level *dest) +{ + stairway *tmp = (stairway *) alloc(sizeof (stairway)); + + (void) memset((genericptr_t) tmp, 0, sizeof (stairway)); + tmp->sx = x; + tmp->sy = y; + tmp->up = up; + tmp->isladder = isladder; + tmp->u_traversed = FALSE; + assign_level(&(tmp->tolev), dest); + tmp->next = gs.stairs; + gs.stairs = tmp; +} + +void +stairway_free_all(void) +{ + stairway *tmp = gs.stairs; + + while (tmp) { + stairway *tmp2 = tmp->next; + free(tmp); + tmp = tmp2; + } + gs.stairs = NULL; +} + +stairway * +stairway_at(coordxy x, coordxy y) +{ + stairway *tmp = gs.stairs; + + while (tmp && !(tmp->sx == x && tmp->sy == y)) + tmp = tmp->next; + return tmp; +} + +stairway * +stairway_find(d_level *fromdlev) +{ + stairway *tmp = gs.stairs; + + while (tmp) { + if (tmp->tolev.dnum == fromdlev->dnum + && tmp->tolev.dlevel == fromdlev->dlevel) + break; /* return */ + tmp = tmp->next; + } + return tmp; +} + +stairway * +stairway_find_from(d_level *fromdlev, boolean isladder) +{ + stairway *tmp = gs.stairs; + + while (tmp) { + if (tmp->tolev.dnum == fromdlev->dnum + && tmp->tolev.dlevel == fromdlev->dlevel + && tmp->isladder == isladder) + break; /* return */ + tmp = tmp->next; + } + return tmp; +} + +stairway * +stairway_find_dir(boolean up) +{ + stairway *tmp = gs.stairs; + + while (tmp && !(tmp->up == up)) + tmp = tmp->next; + return tmp; +} + +stairway * +stairway_find_type_dir(boolean isladder, boolean up) +{ + stairway *tmp = gs.stairs; + + while (tmp && !(tmp->isladder == isladder && tmp->up == up)) + tmp = tmp->next; + return tmp; +} + +stairway * +stairway_find_special_dir(boolean up) +{ + stairway *tmp = gs.stairs; + + while (tmp) { + if (tmp->tolev.dnum != u.uz.dnum && tmp->up != up) + return tmp; + tmp = tmp->next; + } + return tmp; +} + +/* place you on the special staircase */ +void +u_on_sstairs(int upflag) +{ + stairway *stway = stairway_find_special_dir(upflag); + + if (stway) + u_on_newpos(stway->sx, stway->sy); + else + u_on_rndspot(upflag); +} + +/* place you on upstairs (or special equivalent) */ +void +u_on_upstairs(void) +{ + stairway *stway = stairway_find_dir(TRUE); + + if (stway) + u_on_newpos(stway->sx, stway->sy); + else + u_on_sstairs(0); /* destination upstairs implies moving down */ +} + +/* place you on dnstairs (or special equivalent) */ +void +u_on_dnstairs(void) +{ + stairway *stway = stairway_find_dir(FALSE); + + if (stway) + u_on_newpos(stway->sx, stway->sy); + else + u_on_sstairs(1); /* destination dnstairs implies moving up */ +} + +boolean +On_stairs(coordxy x, coordxy y) +{ + return (stairway_at(x, y) != NULL); +} + +boolean +On_ladder(coordxy x, coordxy y) +{ + stairway *stway = stairway_at(x, y); + + return (boolean) (stway && stway->isladder); +} + +boolean +On_stairs_up(coordxy x, coordxy y) +{ + stairway *stway = stairway_at(x, y); + + return (boolean) (stway && stway->up); +} + +boolean +On_stairs_dn(coordxy x, coordxy y) +{ + stairway *stway = stairway_at(x, y); + + return (boolean) (stway && !stway->up); +} + +/* return True if 'sway' is a branch staircase and hero has used these stairs + to visit the branch */ +boolean +known_branch_stairs(stairway *sway) +{ + return (sway && sway->tolev.dnum != u.uz.dnum && sway->u_traversed); +} + +/* describe staircase 'sway' based on whether hero knows the destination */ +char * +stairs_description( + stairway *sway, /* stairs/ladder to describe */ + char *outbuf, /* result buffer */ + boolean stcase) /* True: "staircase" or "ladder", always singular; + * False: "stairs" or "ladder"; caller needs to deal + * with singular vs plural when forming a sentence */ +{ + d_level tolev; + const char *stairs, *updown; + + tolev = sway->tolev; + stairs = sway->isladder ? "ladder" : stcase ? "staircase" : "stairs"; + updown = sway->up ? "up" : "down"; + + if (!known_branch_stairs(sway)) { + /* ordinary stairs or branch stairs to not-yet-visited branch */ + Sprintf(outbuf, "%s %s", stairs, updown); + if (sway->u_traversed) { + boolean specialdepth = (tolev.dnum == quest_dnum + || single_level_branch(&tolev)); /* knox */ + int to_dlev = specialdepth ? dunlev(&tolev) : depth(&tolev); + + Sprintf(eos(outbuf), " to level %d", to_dlev); + } + } else if (u.uz.dnum == 0 && u.uz.dlevel == 1 && sway->up) { + /* stairs up from level one are a special case; they are marked + as having been traversed because the hero obviously started + the game by coming down them, but the remote side varies + depending on whether the Amulet is being carried */ + Sprintf(outbuf, "%s%s %s %s", + !u.uhave.amulet ? "" : "branch ", + stairs, updown, + !u.uhave.amulet ? "out of the dungeon" + /* minimize our expectations about what comes next */ + : (on_level(&tolev, &earth_level) + || on_level(&tolev, &air_level) + || on_level(&tolev, &fire_level) + || on_level(&tolev, &water_level)) + ? "to the Elemental Planes" + : "to the end game"); + } else { + /* known branch stairs; tacking on destination level is too verbose */ + Sprintf(outbuf, "branch %s %s to %s", + stairs, updown, svd.dungeons[tolev.dnum].dname); + /* dungeons[].dname is capitalized; undo that for "The " */ + (void) strsubst(outbuf, "The ", "the "); + } + return outbuf; +} + +/*stairs.c*/ diff --git a/src/steal.c b/src/steal.c index 86933c73a..524b2dd00 100644 --- a/src/steal.c +++ b/src/steal.c @@ -1,43 +1,19 @@ -/* NetHack 3.6 steal.c $NHDT-Date: 1570566382 2019/10/08 20:26:22 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.75 $ */ +/* NetHack 5.0 steal.c $NHDT-Date: 1720895742 2024/07/13 18:35:42 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.132 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2012. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -STATIC_PTR int NDECL(stealarm); - -STATIC_DCL const char *FDECL(equipname, (struct obj *)); - -STATIC_OVL const char * -equipname(otmp) -register struct obj *otmp; -{ - return ((otmp == uarmu) - ? "shirt" - : (otmp == uarmf) - ? "boots" - : (otmp == uarms) - ? "shield" - : (otmp == uarmg) - ? "gloves" - : (otmp == uarmc) - ? cloak_simple_name(otmp) - : (otmp == uarmh) - ? helm_simple_name(otmp) - : suit_simple_name(otmp)); -} +staticfn int unstolenarm(void); +staticfn int stealarm(void); +staticfn void worn_item_removal(struct monst *, struct obj *); /* proportional subset of gold; return value actually fits in an int */ long -somegold(lmoney) -long lmoney; +somegold(long lmoney) { -#ifdef LINT /* long conv. ok */ - int igold = 0; -#else int igold = (lmoney >= (long) LARGEST_INT) ? LARGEST_INT : (int) lmoney; -#endif if (igold < 50) ; /* all gold */ @@ -66,9 +42,10 @@ long lmoney; * Deals in gold only, as leprechauns don't care for lesser coins. */ struct obj * -findgold(chain) -register struct obj *chain; +findgold(struct obj *argchain) { + struct obj *chain = argchain; /* allow arg to be nonnull */ + while (chain && chain->otyp != GOLD_PIECE) chain = chain->nobj; return chain; @@ -78,12 +55,11 @@ register struct obj *chain; * Steal gold coins only. Leprechauns don't care for lesser coins. */ void -stealgold(mtmp) -register struct monst *mtmp; +stealgold(struct monst *mtmp) { - register struct obj *fgold = g_at(u.ux, u.uy); - register struct obj *ygold; - register long tmp; + struct obj *fgold = g_at(u.ux, u.uy); + struct obj *ygold; + long tmp; struct monst *who; const char *whose, *what; @@ -92,7 +68,7 @@ register struct monst *mtmp; fgold = fgold->nexthere; /* Do you have real gold? */ - ygold = findgold(invent); + ygold = findgold(gi.invent); if (fgold && (!ygold || fgold->quan > ygold->quan || !rn2(5))) { obj_extract_self(fgold); @@ -103,7 +79,7 @@ register struct monst *mtmp; whose = s_suffix(y_monnam(who)); what = makeplural(mbodypart(who, FOOT)); } else { - who = &youmonst; + who = &gy.youmonst; whose = "your"; what = makeplural(body_part(FOOT)); } @@ -117,13 +93,13 @@ register struct monst *mtmp; (Levitation || Flying) ? "beneath" : "between", whose, what); if (!ygold || !rn2(5)) { if (!tele_restrict(mtmp)) - (void) rloc(mtmp, TRUE); + (void) rloc(mtmp, RLOC_MSG); monflee(mtmp, 0, FALSE, FALSE); } } else if (ygold) { const int gold_price = objects[GOLD_PIECE].oc_cost; - tmp = (somegold(money_cnt(invent)) + gold_price - 1) / gold_price; + tmp = (somegold(money_cnt(gi.invent)) + gold_price - 1) / gold_price; tmp = min(tmp, ygold->quan); if (tmp < ygold->quan) ygold = splitobj(ygold, tmp); @@ -133,30 +109,83 @@ register struct monst *mtmp; add_to_minv(mtmp, ygold); Your("purse feels lighter."); if (!tele_restrict(mtmp)) - (void) rloc(mtmp, TRUE); + (void) rloc(mtmp, RLOC_MSG); monflee(mtmp, 0, FALSE, FALSE); - context.botl = 1; + disp.botl = TRUE; } } -/* steal armor after you finish taking it off */ -unsigned int stealoid; /* object to be stolen */ -unsigned int stealmid; /* monster doing the stealing */ +/* monster who was stealing from hero has just died */ +void +thiefdead(void) +{ + /* hero is busy taking off an item of armor which takes multiple turns */ + gs.stealmid = 0; + if (ga.afternmv == stealarm) { + ga.afternmv = unstolenarm; + gn.nomovemsg = (char *) 0; + } +} + +/* checks whether hero can be responsive to seduction attempts; similar to + Unaware but also includes paralysis */ +boolean +unresponsive(void) +{ + if (gm.multi >= 0) + return FALSE; + + return (unconscious() || is_fainted() + || (gm.multi_reason + && (!strncmp(gm.multi_reason, "frozen", 6) + || !strncmp(gm.multi_reason, "paralyzed", 9)))); +} + +/* called via (*ga.afternmv)() when hero finishes taking off armor that + was slated to be stolen but the thief died in the interim */ +staticfn int +unstolenarm(void) +{ + struct obj *obj; -STATIC_PTR int -stealarm(VOID_ARGS) + /* find the object before clearing stealoid; it has already become + not-worn and is still in hero's inventory */ + for (obj = gi.invent; obj; obj = obj->nobj) + if (obj->o_id == gs.stealoid) + break; + gs.stealoid = 0; + if (obj) { + You("finish taking off your %s.", armor_simple_name(obj)); + } + return 0; +} + +/* finish stealing an item of armor which takes multiple turns to take off */ +staticfn int +stealarm(void) { - register struct monst *mtmp; - register struct obj *otmp; + struct monst *mtmp; + struct obj *otmp, *nextobj; - for (otmp = invent; otmp; otmp = otmp->nobj) { - if (otmp->o_id == stealoid) { + if (!gs.stealoid || !gs.stealmid) + goto botm; + + for (otmp = gi.invent; otmp; otmp = nextobj) { + nextobj = otmp->nobj; + if (otmp->o_id == gs.stealoid) { for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (mtmp->m_id == stealmid) { - if (DEADMONSTER(mtmp)) + if (mtmp->m_id == gs.stealmid) { + if (DEADMONSTER(mtmp)) { impossible("stealarm(): dead monster stealing"); - if (!dmgtype(mtmp->data, AD_SITM)) /* polymorphed */ - goto botm; + goto botm; /* (could just use 'break' here) */ + } + /* maybe the thief polymorphed into something without a + steal attack, or perhaps while stealing hero's suit + the thief took away other items causing hero to fall + into water or lava and then teleport to safety */ + if (!dmgtype(mtmp->data, AD_SITM) + || distu(mtmp->mx, mtmp->my) > 2) + goto botm; /* (could just use 'break' here) */ if (otmp->unpaid) subfrombill(otmp, shop_keeper(*u.ushops)); freeinv(otmp); @@ -166,7 +195,7 @@ stealarm(VOID_ARGS) so we don't set mavenge bit here. */ monflee(mtmp, 0, FALSE, FALSE); if (!tele_restrict(mtmp)) - (void) rloc(mtmp, TRUE); + (void) rloc(mtmp, RLOC_MSG); break; } } @@ -174,22 +203,42 @@ stealarm(VOID_ARGS) } } botm: - stealoid = 0; + gs.stealoid = gs.stealmid = 0; /* in case only one has been reset so far */ return 0; } /* An object you're wearing has been taken off by a monster (theft or seduction). Also used if a worn item gets transformed (stone to flesh). */ void -remove_worn_item(obj, unchain_ball) -struct obj *obj; -boolean unchain_ball; /* whether to unpunish or just unwield */ +remove_worn_item( + struct obj *obj, + boolean unchain_ball) /* whether to unpunish or just unwield */ { + unsigned oldinuse; + if (donning(obj)) cancel_don(); if (!obj->owornmask) return; + /* + * Losing worn gear might drop hero into water or lava or onto a + * location-changing trap or take away the ability to breathe in water. + * Marking it 'in_use' prevents emergency_disrobe() from dropping it + * and lava_effects() from destroying it; other cases impacting object + * location (or destruction) might still have issues. + * + * Note: if a hangup save occurs when 'in_use' is set, the item will + * be destroyed via useup() during restore. Maybe remove_worn_item() + * and emergency_disrobe() should switch to using obj->bypass instead + * but that would need a lot more cooperation by callers. It's a + * tradeoff between protecting the player against unintentional hangup + * and defending the game against deliberate hangup when player sees a + * message about something undesirable followed by --More--. + */ + oldinuse = obj->in_use; + obj->in_use = 1; + if (obj->owornmask & W_ARMOR) { if (obj == uskin) { impossible("Removing embedded scales?"); @@ -234,22 +283,71 @@ boolean unchain_ball; /* whether to unpunish or just unwield */ /* catchall */ setnotworn(obj); } + + if (obj->where == OBJ_DELETED) + debugpline1("remove_worn_item() \"%s\" deleted!", simpleonames(obj)); + obj->in_use = oldinuse; +} + +/* during theft of a worn item: remove_worn_item(), prefaced by a message */ +staticfn void +worn_item_removal( + struct monst *mon, + struct obj *obj) +{ + char objbuf[BUFSZ], article[20], *p; + const char *verb; + int strip_art; + + Strcpy(objbuf, doname(obj)); + /* massage the object description */ + strip_art = !strncmp(objbuf, "the ", 4) ? 4 + : !strncmp(objbuf, "an ", 3) ? 3 + : !strncmp(objbuf, "a ", 2) ? 2 + : 0; + if (strip_art) { /* convert "a/an/the " to "your object" */ + copynchars(article, objbuf, strip_art); + /* when removing attached iron ball, caller passes 'uchain'; + when formatted, it will be "an iron chain (attached to you)"; + change "an" to "the" rather than to "your" in that situation */ + (void) strsubst(objbuf, article, (obj == uchain) ? "the " : "your "); + } + /* these ought to be guarded against matching user-supplied name */ + (void) strsubst(objbuf, " (being worn)", ""); + (void) strsubst(objbuf, " (alternate weapon; not wielded)", ""); + /* convert "ring (on left hand)" to "ring (from left hand)" */ + if ((p = strstri(objbuf, " (on ")) + && (!strncmp(p + 5, "left ", 5) || !strncmp(p + 5, "right ", 6))) + (void) strsubst(p + 2, "on", "from"); + + /* slightly iffy for alternate weapon that isn't actively dual-wielded, + but it's better to alert the player to the change in equipment than + to suppress the message for that case */ + verb = ((obj->owornmask & W_WEAPONS) != 0L) ? "disarms" + : ((obj->owornmask & W_ACCESSORY) != 0L) ? "removes" + : "takes off"; + pline("%s %s %s.", Some_Monnam(mon), verb, objbuf); + iflags.last_msg = PLNMSG_MON_TAKES_OFF_ITEM; + /* removal might trigger more messages (due to loss of Lev|Fly; + descending happens before the theft in progress finishes) */ + remove_worn_item(obj, TRUE); } -/* 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 - * Nymphs and monkeys won't steal coins +/* 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'. + * Nymphs and monkeys won't steal coins (so that their "steal item" attack + * doesn't become a superset of leprechaun's "steal gold" attack). */ int -steal(mtmp, objnambuf) -struct monst *mtmp; -char *objnambuf; +steal(struct monst *mtmp, char *objnambuf) { struct obj *otmp; + char Monnambuf[BUFSZ]; int tmp, could_petrify, armordelay, olddelay, icnt, named = 0, retrycnt = 0; - boolean monkey_business, /* true iff an animal is doing the thievery */ + boolean monkey_business = is_animal(mtmp->data), + seen = canspotmon(mtmp), was_doffing, was_punished = Punished; if (objnambuf) @@ -258,28 +356,50 @@ char *objnambuf; if (!monnear(mtmp, u.ux, u.uy)) return 0; + /* stealing a worn item might drop hero into water or lava where + teleporting to safety could result in a previously visible thief + no longer being visible; it could also be a case of a blinded + hero being able to see via wearing the Eyes of the Overworld and + having those stolen; remember the name as it is now; if unseen, + nymphs will be "Someone" and monkeys will be "Something" */ + Strcpy(Monnambuf, Some_Monnam(mtmp)); + /* food being eaten might already be used up but will not have been removed from inventory yet; we don't want to steal that, so this will cause it to be removed now */ - if (occupation) + if (go.occupation) (void) maybe_finished_meal(FALSE); icnt = inv_cnt(FALSE); /* don't include gold */ if (!icnt || (icnt == 1 && uskin)) { - nothing_to_steal: /* Not even a thousand men in armor can strip a naked man. */ - if (Blind) + nothing_to_steal: + /* nymphs might target uchain if invent is empty; monkeys won't; + hero becomes unpunished but nymph ends up empty handed */ + if (Punished && !monkey_business && rn2(4)) { + /* uball is not carried (uchain never is) */ + assert(uball != NULL && uball->where == OBJ_FLOOR); + worn_item_removal(mtmp, uchain); + } else if (u.utrap && u.utraptype == TT_BURIEDBALL + && !monkey_business && !rn2(4)) { + boolean dummy; + + /* buried ball is not tracked via 'uball' and there is no chain + at all (hence no uchain to take off) */ + pline("%s takes off your unseen chain.", Monnambuf); + (void) openholdingtrap(&gy.youmonst, &dummy); + } else if (Blind) { pline("Somebody tries to rob you, but finds nothing to steal."); - else if (inv_cnt(TRUE) > inv_cnt(FALSE)) /* ('icnt' might be stale) */ + } else if (inv_cnt(TRUE) > inv_cnt(FALSE)) { pline("%s tries to rob you, but isn't interested in gold.", - Monnam(mtmp)); - else + Monnambuf); + } else { pline("%s tries to rob you, but there is nothing to steal!", - Monnam(mtmp)); + Monnambuf); + } return 1; /* let her flee */ } - monkey_business = is_animal(mtmp->data); if (monkey_business || uarmg) { ; /* skip ring special cases */ } else if (Adornment & LEFT_RING) { @@ -292,14 +412,14 @@ char *objnambuf; retry: tmp = 0; - for (otmp = invent; otmp; otmp = otmp->nobj) + for (otmp = gi.invent; otmp; otmp = otmp->nobj) if ((!uarm || otmp != uarmc) && otmp != uskin && otmp->oclass != COIN_CLASS) tmp += (otmp->owornmask & (W_ARMOR | W_ACCESSORY)) ? 5 : 1; if (!tmp) goto nothing_to_steal; tmp = rn2(tmp); - for (otmp = invent; otmp; otmp = otmp->nobj) + for (otmp = gi.invent; otmp; otmp = otmp->nobj) if ((!uarm || otmp != uarmc) && otmp != uskin && otmp->oclass != COIN_CLASS) { tmp -= (otmp->owornmask & (W_ARMOR | W_ACCESSORY)) ? 5 : 1; @@ -326,7 +446,7 @@ char *objnambuf; otmp = uarm; gotobj: - if (otmp->o_id == stealoid) + if (otmp->o_id == gs.stealoid) return 0; if (otmp->otyp == BOULDER && !throws_rocks(mtmp->data)) { @@ -348,17 +468,19 @@ char *objnambuf; ostuck = ((otmp->cursed && otmp->owornmask) /* nymphs can steal rings from under cursed weapon but animals can't */ - || (otmp == uright && welded(uwep)) - || (otmp == uleft && welded(uwep) && bimanual(uwep))); + || (otmp == RING_ON_PRIMARY && welded(uwep)) + || (otmp == RING_ON_SECONDARY && welded(uwep) + && bimanual(uwep))); if (ostuck || can_carry(mtmp, otmp) == 0) { - static const char *const how[] = { "steal", "snatch", "grab", - "take" }; + static const char *const how[] = { + "steal", "snatch", "grab", "take" + }; cant_take: - pline("%s tries to %s %s%s but gives up.", Monnam(mtmp), - how[rn2(SIZE(how))], + pline("%s tries to %s %s%s but gives up.", Monnambuf, + ROLL_FROM(how), (otmp->owornmask & W_ARMOR) ? "your " : "", - (otmp->owornmask & W_ARMOR) ? equipname(otmp) + (otmp->owornmask & W_ARMOR) ? armor_simple_name(otmp) : yname(otmp)); /* the fewer items you have, the less likely the thief is going to stick around to try again (0) instead of @@ -386,61 +508,53 @@ char *objnambuf; case AMULET_CLASS: case RING_CLASS: case FOOD_CLASS: /* meat ring */ - remove_worn_item(otmp, TRUE); + worn_item_removal(mtmp, otmp); break; case ARMOR_CLASS: armordelay = objects[otmp->otyp].oc_delay; if (olddelay > 0 && olddelay < armordelay) armordelay = olddelay; - if (monkey_business) { - /* animals usually don't have enough patience - to take off items which require extra time */ + if (monkey_business || unresponsive()) { + /* animals usually don't have enough patience to take off + items which require extra time; unconscious or paralyzed + hero can't be charmed into taking off his own armor */ if (armordelay >= 1 && !olddelay && rn2(10)) goto cant_take; - remove_worn_item(otmp, TRUE); + worn_item_removal(mtmp, otmp); break; } else { int curssv = otmp->cursed; int slowly; - boolean seen = canspotmon(mtmp); otmp->cursed = 0; - /* can't charm you without first waking you */ - if (Unaware) - unmul((char *) 0); - slowly = (armordelay >= 1 || multi < 0); + slowly = (armordelay >= 1 || gm.multi < 0); if (flags.female) - pline("%s charms you. You gladly %s your %s.", - !seen ? "She" : Monnam(mtmp), - curssv ? "let her take" + urgent_pline("%s charms you. You gladly %s your %s.", + !seen ? "She" : Monnambuf, + curssv ? "let her take" : !slowly ? "hand over" - : was_doffing ? "continue removing" - : "start removing", - equipname(otmp)); + : was_doffing ? "continue removing" + : "start removing", + armor_simple_name(otmp)); else - pline("%s seduces you and %s off your %s.", - !seen ? "She" : Adjmonnam(mtmp, "beautiful"), - curssv - ? "helps you to take" - : !slowly ? "you take" - : was_doffing ? "you continue taking" - : "you start taking", - equipname(otmp)); + urgent_pline("%s seduces you and %s off your %s.", + !seen ? "She" : Adjmonnam(mtmp, "beautiful"), + curssv ? "helps you to take" + : !slowly ? "you take" + : was_doffing ? "you continue taking" + : "you start taking", + armor_simple_name(otmp)); named++; /* the following is to set multi for later on */ nomul(-armordelay); - multi_reason = "taking off clothes"; - nomovemsg = 0; + gm.multi_reason = "taking off clothes"; + gn.nomovemsg = 0; remove_worn_item(otmp, TRUE); otmp->cursed = curssv; - if (multi < 0) { - /* - multi = 0; - afternmv = 0; - */ - stealoid = otmp->o_id; - stealmid = mtmp->m_id; - afternmv = stealarm; + if (gm.multi < 0) { + gs.stealoid = otmp->o_id; + gs.stealmid = mtmp->m_id; + ga.afternmv = stealarm; return 0; } } @@ -449,8 +563,25 @@ char *objnambuf; impossible("Tried to steal a strange worn thing. [%d]", otmp->oclass); } - } else if (otmp->owornmask) /* weapon or ball&chain */ - remove_worn_item(otmp, TRUE); + /* hero's blindfold might have just been stolen; if so, replace + cached "Someone" or "Something" with Monnam */ + if (!seen && canspotmon(mtmp)) + Strcpy(Monnambuf, Monnam(mtmp)); + } else if (otmp->owornmask) { /* weapon or ball&chain */ + struct obj *item = otmp; + + if (otmp == uball) /* non-Null uball implies non-Null uchain */ + item = uchain; /* yields a more accurate 'takes off' message */ + worn_item_removal(mtmp, item); + /* if we switched from uball to uchain for the preface message, + then unpunish() took place and both those pointers are now Null, + with 'item' a stale pointer to freed chain; the ball is still + present though and 'otmp' is still valid; if uball was also + wielded or quivered, the corresponding weapon pointer hasn't + been cleared yet; do that, with no preface message this time */ + if ((otmp->owornmask & W_WEAPONS) != 0L) + remove_worn_item(otmp, FALSE); + } /* do this before removing it from inventory */ if (objnambuf) @@ -463,58 +594,85 @@ char *objnambuf; if (otmp->unpaid) subfrombill(otmp, shop_keeper(*u.ushops)); freeinv(otmp); - /* if attached ball was taken, uball and uchain are now Null */ - pline("%s%s stole %s.", named ? "She" : Monnam(mtmp), - (was_punished && !Punished) ? " removed your chain and" : "", - doname(otmp)); + + /* if we just gave a message about removing a worn item and there have + been no intervening messages, shorten ' stole ' message */ + if (iflags.last_msg == PLNMSG_MON_TAKES_OFF_ITEM + && mtmp->data->mlet == S_NYMPH) + ++named; + urgent_pline("%s stole %s.", named ? "She" : Monnambuf, doname(otmp)); + encumber_msg(); could_petrify = (otmp->otyp == CORPSE && touch_petrifies(&mons[otmp->corpsenm])); + otmp->how_lost = LOST_STOLEN; (void) mpickobj(mtmp, otmp); /* may free otmp */ if (could_petrify && !(mtmp->misc_worn_check & W_ARMG)) { minstapetrify(mtmp, TRUE); return -1; } - return (multi < 0) ? 0 : 1; + return (gm.multi < 0) ? 0 : 1; } /* Returns 1 if otmp is free'd, 0 otherwise. */ int -mpickobj(mtmp, otmp) -register struct monst *mtmp; -register struct obj *otmp; +mpickobj(struct monst *mtmp, struct obj *otmp) { int freed_otmp; boolean snuff_otmp = FALSE; if (!otmp) { impossible("monster (%s) taking or picking up nothing?", - mtmp->data->mname); + pmname(mtmp->data, Mgender(mtmp))); return 1; } else if (otmp == uball || otmp == uchain) { impossible("monster (%s) taking or picking up attached %s (%s)?", - mtmp->data->mname, + pmname(mtmp->data, Mgender(mtmp)), (otmp == uchain) ? "chain" : "ball", simpleonames(otmp)); return 0; } /* if monster is acquiring a thrown or kicked object, the throwing or kicking code shouldn't continue to track and place it */ - if (otmp == thrownobj) - thrownobj = 0; - else if (otmp == kickedobj) - kickedobj = 0; + if (otmp == gt.thrownobj) + gt.thrownobj = 0; + else if (otmp == gk.kickedobj) + gk.kickedobj = 0; + /* an unpaid item can be on the floor; if a monster picks it up, take + it off the shop bill */ + if (otmp->unpaid || (Has_contents(otmp) && count_unpaid(otmp->cobj))) { + subfrombill(otmp, find_objowner(otmp, otmp->ox, otmp->oy)); + } /* don't want hidden light source inside the monster; assumes that engulfers won't have external inventories; whirly monsters cause - the light to be extinguished rather than letting it shine thru */ + the light to be extinguished rather than letting it shine through */ if (obj_sheds_light(otmp) && attacktype(mtmp->data, AT_ENGL)) { /* this is probably a burning object that you dropped or threw */ - if (u.uswallow && mtmp == u.ustuck && !Blind) + if (engulfing_u(mtmp) && !Blind) pline("%s out.", Tobjnam(otmp, "go")); snuff_otmp = TRUE; } /* for hero owned object on shop floor, mtmp is taking possession and if it's eventually dropped in a shop, shk will claim it */ - if (!mtmp->mtame) - otmp->no_charge = 0; + otmp->no_charge = 0; + /* some object handling is only done if mtmp isn't a pet */ + if (!mtmp->mtame) { + /* if monst is unseen, some info hero knows about this object becomes + lost; continual pickup and drop by pets makes this too annoying if + it is applied to them; when engulfed (where monster can't be seen + because vision is disabled), or when held (or poly'd and holding) + while blind, behave as if the monster can be 'seen' by touch */ + if (!canseemon(mtmp) && mtmp != u.ustuck) + unknow_object(otmp); + /* if otmp has flags set for how it left hero's inventory, change + those flags; if thrown, now stolen and autopickup might override + pickup_types and autopickup exceptions based on 'pickup_stolen' + rather than 'pickup_thrown'; if previously stolen, stays stolen; + if previously dropped, now forgotten and autopickup will operate + normally regardless of the setting for 'dropped_nopick' */ + if (otmp->how_lost == LOST_THROWN) + otmp->how_lost = LOST_STOLEN; + else if (otmp->how_lost == LOST_DROPPED) + otmp->how_lost = LOST_NONE; + } /* Must do carrying effects on object prior to add_to_minv() */ carry_obj_effects(otmp); /* add_to_minv() might free otmp [if merged with something else], @@ -528,8 +686,7 @@ register struct obj *otmp; /* called for AD_SAMU (the Wizard and quest nemeses) */ void -stealamulet(mtmp) -struct monst *mtmp; +stealamulet(struct monst *mtmp) { char buf[BUFSZ]; struct obj *otmp = 0, *obj = 0; @@ -538,18 +695,18 @@ struct monst *mtmp; /* target every quest artifact, not just current role's; if hero has more than one, choose randomly so that player can't use inventory ordering to influence the theft */ - for (n = 0, obj = invent; obj; obj = obj->nobj) + for (n = 0, obj = gi.invent; obj; obj = obj->nobj) if (any_quest_artifact(obj)) ++n, otmp = obj; if (n > 1) { n = rnd(n); - for (otmp = invent; otmp; otmp = otmp->nobj) + for (otmp = gi.invent; otmp; otmp = otmp->nobj) if (any_quest_artifact(otmp) && !--n) break; } if (!otmp) { - /* if we didn't find any quest arifact, find another valuable item */ + /* if we didn't find any quest artifact, find another valuable item */ if (u.uhave.amulet) { real = AMULET_OF_YENDOR; fake = FAKE_AMULET_OF_YENDOR; @@ -564,12 +721,12 @@ struct monst *mtmp; return; /* you have nothing of special interest */ /* If we get here, real and fake have been set up. */ - for (n = 0, obj = invent; obj; obj = obj->nobj) + for (n = 0, obj = gi.invent; obj; obj = obj->nobj) if (obj->otyp == real || (obj->otyp == fake && !mtmp->iswiz)) ++n, otmp = obj; if (n > 1) { n = rnd(n); - for (otmp = invent; otmp; otmp = otmp->nobj) + for (otmp = gi.invent; otmp; otmp = otmp->nobj) if ((otmp->otyp == real || (otmp->otyp == fake && !mtmp->iswiz)) && !--n) break; @@ -577,44 +734,45 @@ struct monst *mtmp; } if (otmp) { /* we have something to snatch */ - /* take off outer gear if we're targetting [hypothetical] + /* take off outer gear if we're targeting [hypothetical] quest artifact suit, shirt, gloves, or rings */ if ((otmp == uarm || otmp == uarmu) && uarmc) - remove_worn_item(uarmc, FALSE); + worn_item_removal(mtmp, uarmc); if (otmp == uarmu && uarm) - remove_worn_item(uarm, FALSE); + worn_item_removal(mtmp, uarm); if ((otmp == uarmg || ((otmp == uright || otmp == uleft) && uarmg)) && uwep) { /* gloves are about to be unworn; unwield weapon(s) first */ if (u.twoweap) /* remove_worn_item(uswapwep) indirectly */ - remove_worn_item(uswapwep, FALSE); /* clears u.twoweap */ - remove_worn_item(uwep, FALSE); + worn_item_removal(mtmp, uswapwep); /* clears u.twoweap */ + worn_item_removal(mtmp, uwep); } if ((otmp == uright || otmp == uleft) && uarmg) /* calls Gloves_off() to handle wielded cockatrice corpse */ - remove_worn_item(uarmg, FALSE); + worn_item_removal(mtmp, uarmg); /* finally, steal the target item */ if (otmp->owornmask) - remove_worn_item(otmp, TRUE); + worn_item_removal(mtmp, otmp); if (otmp->unpaid) subfrombill(otmp, shop_keeper(*u.ushops)); freeinv(otmp); Strcpy(buf, doname(otmp)); (void) mpickobj(mtmp, otmp); /* could merge and free otmp but won't */ - pline("%s steals %s!", Monnam(mtmp), buf); + pline("%s steals %s!", Some_Monnam(mtmp), buf); if (can_teleport(mtmp->data) && !tele_restrict(mtmp)) - (void) rloc(mtmp, TRUE); + (void) rloc(mtmp, RLOC_MSG); + encumber_msg(); } } /* when a mimic gets poked with something, it might take that thing (at present, only implemented for when the hero does the poking) */ void -maybe_absorb_item(mon, obj, ochance, achance) -struct monst *mon; -struct obj *obj; -int ochance, achance; /* percent chance for ordinary item, artifact */ +maybe_absorb_item( + struct monst *mon, + struct obj *obj, + int ochance, int achance) /* percent chance for ordinary item, artifact */ { if (obj == uball || obj == uchain || obj->oclass == ROCK_CLASS || obj_resists(obj, 100 - ochance, 100 - achance) @@ -627,12 +785,10 @@ int ochance, achance; /* percent chance for ordinary item, artifact */ if (obj->unpaid) subfrombill(obj, shop_keeper(*u.ushops)); if (cansee(mon->mx, mon->my)) { - const char *MonName = Monnam(mon); - - /* mon might be invisible; avoid "It pulls ... and absorbs it!" */ - if (!strcmp(MonName, "It")) - MonName = "Something"; - pline("%s pulls %s away from you and absorbs %s!", MonName, + /* Some_Monnam() avoids "It pulls ... and absorbs it!" + if hero can see the location but not the monster */ + pline("%s pulls %s away from you and absorbs %s!", + Some_Monnam(mon), /* Monnam() or "Something" */ yname(obj), (obj->quan > 1L) ? "them" : "it"); } else { const char *hand_s = body_part(HAND); @@ -643,6 +799,7 @@ int ochance, achance; /* percent chance for ordinary item, artifact */ otense(obj, "are"), hand_s); } freeinv(obj); + encumber_msg(); } else { /* not carried; presumably thrown or kicked */ if (canspotmon(mon)) @@ -654,53 +811,45 @@ int ochance, achance; /* percent chance for ordinary item, artifact */ /* drop one object taken from a (possibly dead) monster's inventory */ void -mdrop_obj(mon, obj, verbosely) -struct monst *mon; -struct obj *obj; -boolean verbosely; +mdrop_obj( + struct monst *mon, + struct obj *obj, + boolean verbosely) { - int omx = mon->mx, omy = mon->my; - boolean update_mon = FALSE; - - if (obj->owornmask) { - /* perform worn item handling if the monster is still alive */ - if (!DEADMONSTER(mon)) { - mon->misc_worn_check &= ~obj->owornmask; - update_mon = TRUE; - - /* don't charge for an owned saddle on dead steed (provided - that the hero is within the same shop at the time) */ - } else if (mon->mtame && (obj->owornmask & W_SADDLE) != 0L - && !obj->unpaid && costly_spot(omx, omy) - /* being at costly_spot guarantees lev->roomno is not 0 */ - && index(in_rooms(u.ux, u.uy, SHOPBASE), - levl[omx][omy].roomno)) { - obj->no_charge = 1; - } - /* this should be done even if the monster has died */ - if (obj->owornmask & W_WEP) - setmnotwielded(mon, obj); - obj->owornmask = 0L; + coordxy omx = mon->mx, omy = mon->my; + long unwornmask = obj->owornmask; + /* call distant_name() for its possible side-effects even if the result + might not be printed, and do it before extracting obj from minvent */ + char *obj_name = distant_name(obj, doname); + + extract_from_minvent(mon, obj, FALSE, TRUE); + /* don't charge for an owned saddle on dead steed (provided + that the hero is within the same shop at the time) */ + if (unwornmask && mon->mtame && (unwornmask & W_SADDLE) != 0L + && !obj->unpaid && costly_spot(omx, omy) + /* being at costly_spot guarantees lev->roomno is not 0 */ + && strchr(in_rooms(u.ux, u.uy, SHOPBASE), levl[omx][omy].roomno)) { + obj->no_charge = 1; } /* obj_no_longer_held(obj); -- done by place_object */ if (verbosely && cansee(omx, omy)) - pline("%s drops %s.", Monnam(mon), distant_name(obj, doname)); + pline_mon(mon, "%s drops %s.", Monnam(mon), obj_name); if (!flooreffects(obj, omx, omy, "fall")) { place_object(obj, omx, omy); stackobj(obj); } /* do this last, after placing obj on floor; removing steed's saddle - throws rider, possibly inflicting fatal damage and producing bones */ - if (update_mon) - update_mon_intrinsics(mon, obj, FALSE, TRUE); + throws rider, possibly inflicting fatal damage and producing bones; this + is why we had to call extract_from_minvent() with do_intrinsics=FALSE */ + if (!DEADMONSTER(mon) && unwornmask) + update_mon_extrinsics(mon, obj, FALSE, TRUE); } /* some monsters bypass the normal rules for moving between levels or even leaving the game entirely; when that happens, prevent them from taking the Amulet, invocation items, or quest artifact with them */ void -mdrop_special_objs(mon) -struct monst *mon; +mdrop_special_objs(struct monst *mon) { struct obj *obj, *otmp; @@ -711,16 +860,10 @@ struct monst *mon; current role's quest artifact is rescued too--quest artifacts for the other roles are not */ if (obj_resists(obj, 0, 0) || is_quest_artifact(obj)) { - obj_extract_self(obj); if (mon->mx) { mdrop_obj(mon, obj, FALSE); } else { /* migrating monster not on map */ - if (obj->owornmask) { - mon->misc_worn_check &= ~obj->owornmask; - if (obj->owornmask & W_WEP) - setmnotwielded(mon, obj); - obj->owornmask = 0L; - } + extract_from_minvent(mon, obj, TRUE, TRUE); rloco(obj); } } @@ -729,10 +872,10 @@ struct monst *mon; /* release the objects the creature is carrying */ void -relobj(mtmp, show, is_pet) -struct monst *mtmp; -int show; -boolean is_pet; /* If true, pet should keep wielded/worn items */ +relobj( + struct monst *mtmp, + int show, + boolean is_pet) /* If true, pet should keep wielded/worn items */ { struct obj *otmp; int omx = mtmp->mx, omy = mtmp->my; @@ -747,7 +890,6 @@ boolean is_pet; /* If true, pet should keep wielded/worn items */ } /* isgd && has gold */ while ((otmp = (is_pet ? droppables(mtmp) : mtmp->minvent)) != 0) { - obj_extract_self(otmp); mdrop_obj(mtmp, otmp, is_pet && flags.verbose); } diff --git a/src/steed.c b/src/steed.c index 9de2935f1..a9c1ad7d0 100644 --- a/src/steed.c +++ b/src/steed.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 steed.c $NHDT-Date: 1575245090 2019/12/02 00:04:50 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.68 $ */ +/* NetHack 5.0 steed.c $NHDT-Date: 1720128167 2024/07/04 21:22:47 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.121 $ */ /* Copyright (c) Kevin Hugo, 1998-1999. */ /* NetHack may be freely redistributed. See license for details. */ @@ -9,12 +9,12 @@ static NEARDATA const char steeds[] = { S_QUADRUPED, S_UNICORN, S_ANGEL, S_CENTAUR, S_DRAGON, S_JABBERWOCK, '\0' }; -STATIC_DCL boolean FDECL(landing_spot, (coord *, int, int)); -STATIC_DCL void FDECL(maybewakesteed, (struct monst *)); +staticfn boolean landing_spot(coord *, int, int); +staticfn void maybewakesteed(struct monst *); /* caller has decided that hero can't reach something while mounted */ void -rider_cant_reach() +rider_cant_reach(void) { You("aren't skilled enough to reach from %s.", y_monnam(u.usteed)); } @@ -23,71 +23,70 @@ rider_cant_reach() /* Can this monster wear a saddle? */ boolean -can_saddle(mtmp) -struct monst *mtmp; +can_saddle(struct monst *mtmp) { struct permonst *ptr = mtmp->data; - return (index(steeds, ptr->mlet) && (ptr->msize >= MZ_MEDIUM) + return (strchr(steeds, ptr->mlet) && (ptr->msize >= MZ_MEDIUM) && (!humanoid(ptr) || ptr->mlet == S_CENTAUR) && !amorphous(ptr) && !noncorporeal(ptr) && !is_whirly(ptr) && !unsolid(ptr)); } int -use_saddle(otmp) -struct obj *otmp; +use_saddle(struct obj *otmp) { struct monst *mtmp; struct permonst *ptr; int chance; - const char *s; if (!u_handsy()) - return 0; + return ECMD_OK; /* Select an animal */ if (u.uswallow || Underwater || !getdir((char *) 0)) { pline1(Never_mind); - return 0; + return ECMD_CANCEL; } if (!u.dx && !u.dy) { pline("Saddle yourself? Very funny..."); - return 0; + return ECMD_OK; } if (!isok(u.ux + u.dx, u.uy + u.dy) || !(mtmp = m_at(u.ux + u.dx, u.uy + u.dy)) || !canspotmon(mtmp)) { pline("I see nobody there."); - return 1; + return ECMD_TIME; } /* Is this a valid monster? */ - if (mtmp->misc_worn_check & W_SADDLE || which_armor(mtmp, W_SADDLE)) { + if ((mtmp->misc_worn_check & W_SADDLE) != 0L + || which_armor(mtmp, W_SADDLE)) { pline("%s doesn't need another one.", Monnam(mtmp)); - return 1; + return ECMD_TIME; } ptr = mtmp->data; if (touch_petrifies(ptr) && !uarmg && !Stone_resistance) { char kbuf[BUFSZ]; You("touch %s.", mon_nam(mtmp)); - if (!(poly_when_stoned(youmonst.data) && polymon(PM_STONE_GOLEM))) { - Sprintf(kbuf, "attempting to saddle %s", an(mtmp->data->mname)); + if (!(poly_when_stoned(gy.youmonst.data) && polymon(PM_STONE_GOLEM))) { + Sprintf(kbuf, "attempting to saddle %s", + an(pmname(mtmp->data, Mgender(mtmp)))); instapetrify(kbuf); } } - if (ptr == &mons[PM_INCUBUS] || ptr == &mons[PM_SUCCUBUS]) { + if (ptr == &mons[PM_AMOROUS_DEMON]) { pline("Shame on you!"); exercise(A_WIS, FALSE); - return 1; + return ECMD_TIME; } if (mtmp->isminion || mtmp->isshk || mtmp->ispriest || mtmp->isgd || mtmp->iswiz) { pline("I think %s would mind.", mon_nam(mtmp)); - return 1; + return ECMD_TIME; } if (!can_saddle(mtmp)) { You_cant("saddle such a creature."); - return 1; + return ECMD_TIME; } /* Calculate your chance */ @@ -114,12 +113,10 @@ struct obj *otmp; } if (Confusion || Fumbling || Glib) chance -= 20; - else if (uarmg && (s = OBJ_DESCR(objects[uarmg->otyp])) != (char *) 0 - && !strncmp(s, "riding ", 7)) + else if (uarmg && objdescr_is(uarmg, "riding gloves")) /* Bonus for wearing "riding" (but not fumbling) gloves */ chance += 10; - else if (uarmf && (s = OBJ_DESCR(objects[uarmf->otyp])) != (char *) 0 - && !strncmp(s, "riding ", 7)) + else if (uarmf && objdescr_is(uarmf, "riding boots")) /* ... or for "riding boots" */ chance += 10; if (otmp->cursed) @@ -134,61 +131,72 @@ struct obj *otmp; if (otmp->owornmask) remove_worn_item(otmp, FALSE); freeinv(otmp); + /* !can_saddle(mtmp) already eliminated above */ put_saddle_on_mon(otmp, mtmp); } else pline("%s resists!", Monnam(mtmp)); - return 1; + return ECMD_TIME; } void -put_saddle_on_mon(saddle, mtmp) -struct obj *saddle; -struct monst *mtmp; +put_saddle_on_mon(struct obj *saddle, struct monst *mtmp) { - if (!can_saddle(mtmp) || which_armor(mtmp, W_SADDLE)) + if (!can_saddle(mtmp) || which_armor(mtmp, W_SADDLE)) { + if (saddle) + impossible("put_saddle_on_mon: saddle obj could get orphaned"); return; + } + if (!saddle) { + if ((saddle = mksobj(SADDLE, TRUE, FALSE)) != 0) { + fully_identify_obj(saddle); + /* mpickobj can later override identification if out-of-view */ + } else { + return; + } + } if (mpickobj(mtmp, saddle)) panic("merged saddle?"); mtmp->misc_worn_check |= W_SADDLE; saddle->owornmask = W_SADDLE; saddle->leashmon = mtmp->m_id; - update_mon_intrinsics(mtmp, saddle, TRUE, FALSE); + update_mon_extrinsics(mtmp, saddle, TRUE, FALSE); } /*** Riding the monster ***/ /* Can we ride this monster? Caller should also check can_saddle() */ boolean -can_ride(mtmp) -struct monst *mtmp; +can_ride(struct monst *mtmp) { - return (mtmp->mtame && humanoid(youmonst.data) - && !verysmall(youmonst.data) && !bigmonst(youmonst.data) + return (mtmp->mtame && humanoid(gy.youmonst.data) + && !verysmall(gy.youmonst.data) && !bigmonst(gy.youmonst.data) && (!Underwater || is_swimmer(mtmp->data))); } +/* the #ride command */ int -doride() +doride(void) { boolean forcemount = FALSE; if (u.usteed) { dismount_steed(DISMOUNT_BYCHOICE); } else if (getdir((char *) 0) && isok(u.ux + u.dx, u.uy + u.dy)) { - if (wizard && yn("Force the mount to succeed?") == 'y') + if (wizard && y_n("Force the mount to succeed?") == 'y') forcemount = TRUE; - return (mount_steed(m_at(u.ux + u.dx, u.uy + u.dy), forcemount)); + return (mount_steed(m_at(u.ux + u.dx, u.uy + u.dy), forcemount) + ? ECMD_TIME : ECMD_OK); } else { - return 0; + return ECMD_CANCEL; } - return 1; + return ECMD_TIME; } /* Start riding, with the given monster */ boolean -mount_steed(mtmp, force) -struct monst *mtmp; /* The animal */ -boolean force; /* Quietly force this animal */ +mount_steed( + struct monst *mtmp, /* The animal */ + boolean force) /* Quietly force this animal */ { struct obj *otmp; char buf[BUFSZ]; @@ -205,8 +213,8 @@ boolean force; /* Quietly force this animal */ pline("Maybe you should find a designated driver."); return (FALSE); } - /* While riding Wounded_legs refers to the steed's, - * not the hero's legs. + /* While riding, Wounded_legs refers to the steed's + * legs, not the hero's legs. * That opens up a potential abuse where the player * can mount a steed, then dismount immediately to * heal leg damage, because leg damage is always @@ -219,15 +227,21 @@ boolean force; /* Quietly force this animal */ * temporary 1 point Dex loss become permanent.] */ if (Wounded_legs) { - Your("%s are in no shape for riding.", makeplural(body_part(LEG))); - if (force && wizard && yn("Heal your legs?") == 'y') - HWounded_legs = EWounded_legs = 0L; + char qbuf[QBUFSZ]; + + legs_in_no_shape("riding", FALSE); + Sprintf(qbuf, "Heal your leg%s?", + ((HWounded_legs & BOTH_SIDES) == BOTH_SIDES) ? "s" : ""); + if (force && wizard && y_n(qbuf) == 'y') + heal_legs(0); else return (FALSE); } - if (Upolyd && (!humanoid(youmonst.data) || verysmall(youmonst.data) - || bigmonst(youmonst.data) || slithy(youmonst.data))) { + if (Upolyd && (!humanoid(gy.youmonst.data) + || verysmall(gy.youmonst.data) + || bigmonst(gy.youmonst.data) + || slithy(gy.youmonst.data))) { You("won't fit on a saddle."); return (FALSE); } @@ -269,12 +283,14 @@ boolean force; /* Quietly force this animal */ pline("%s is not saddled.", Monnam(mtmp)); return (FALSE); } + ptr = mtmp->data; if (touch_petrifies(ptr) && !Stone_resistance) { char kbuf[BUFSZ]; You("touch %s.", mon_nam(mtmp)); - Sprintf(kbuf, "attempting to ride %s", an(mtmp->data->mname)); + Sprintf(kbuf, "attempting to ride %s", + an(pmname(mtmp->data, Mgender(mtmp)))); instapetrify(kbuf); } if (!mtmp->mtame || mtmp->isminion) { @@ -285,7 +301,7 @@ boolean force; /* Quietly force this animal */ struct trap *t = t_at(mtmp->mx, mtmp->my); You_cant("mount %s while %s's trapped in %s.", mon_nam(mtmp), - mhe(mtmp), an(defsyms[trap_to_defsym(t->ttyp)].explanation)); + mhe(mtmp), an(trapname(t->ttyp, FALSE))); return (FALSE); } @@ -321,6 +337,7 @@ boolean force; /* Quietly force this animal */ } if (!force && (Confusion || Fumbling || Glib || Wounded_legs || otmp->cursed + || otmp->greased || (u.ulevel + mtmp->mtame < rnd(MAXULEV / 2 + 5)))) { if (Levitation) { pline("%s slips away from you.", Monnam(mtmp)); @@ -350,17 +367,24 @@ boolean force; /* Quietly force this animal */ } /* setuwep handles polearms differently when you're mounted */ if (uwep && is_pole(uwep)) - unweapon = FALSE; + gu.unweapon = FALSE; u.usteed = mtmp; + { + boolean was_stealthy = Stealth != 0; + + steed_vs_stealth(); + if (was_stealthy && !Stealth) + You("aren't stealthy anymore."); + } remove_monster(mtmp->mx, mtmp->my); - teleds(mtmp->mx, mtmp->my, TRUE); - context.botl = TRUE; + teleds(mtmp->mx, mtmp->my, TELEDS_ALLOW_DRAG); + disp.botl = TRUE; return TRUE; } /* You and your steed have moved */ void -exercise_steed() +exercise_steed(void) { if (!u.usteed) return; @@ -375,14 +399,14 @@ exercise_steed() /* The player kicks or whips the steed */ void -kick_steed() +kick_steed(void) { - char He[4]; + char He[BUFSZ]; /* monverbself() appends to the "He"/"She"/"It" value */ if (!u.usteed) return; /* [ALI] Various effects of kicking sleeping/paralyzed steeds */ - if (u.usteed->msleeping || !u.usteed->mcanmove) { + if (helpless(u.usteed)) { /* We assume a message has just been output of the form * "You kick ." */ @@ -397,10 +421,12 @@ kick_steed() u.usteed->mfrozen = 0; u.usteed->mcanmove = 1; } - if (u.usteed->msleeping || !u.usteed->mcanmove) + if (helpless(u.usteed)) pline("%s stirs.", He); else - pline("%s rouses %sself!", He, mhim(u.usteed)); + /* if hallucinating, might yield "He rouses herself" or + "She rouses himself" */ + pline("%s!", monverbself(u.usteed, He, "rouse", (char *) 0)); } else pline("%s does not respond.", He); return; @@ -430,81 +456,164 @@ kick_steed() * room's walls, which is not what we want. * Adapted from mail daemon code. */ -STATIC_OVL boolean -landing_spot(spot, reason, forceit) -coord *spot; /* landing position (we fill it in) */ -int reason; -int forceit; +staticfn boolean +landing_spot( + coord *spot, /* landing position (we fill it in) */ + int reason, + int forceit) { - int i = 0, x, y, distance, min_distance = -1; - boolean found = FALSE; + coord cc, try[8]; /* 8: the 8 spots adjacent to the hero's spot */ + int i, j, best_j, clockwise_j, counterclk_j, + n, viable, distance, min_distance = -1; + coordxy x, y; + boolean found, impaird, kn_trap, boulder; struct trap *t; - /* avoid known traps (i == 0) and boulders, but allow them as a backup */ - if (reason != DISMOUNT_BYCHOICE || Stunned || Confusion || Fumbling) - i = 1; - for (; !found && i < 2; ++i) { - for (x = u.ux - 1; x <= u.ux + 1; x++) - for (y = u.uy - 1; y <= u.uy + 1; y++) { - if (!isok(x, y) || (x == u.ux && y == u.uy)) - continue; - - if (accessible(x, y) && !MON_AT(x, y)) { - distance = distu(x, y); - if (min_distance < 0 || distance < min_distance - || (distance == min_distance && rn2(2))) { - if (i > 0 || (((t = t_at(x, y)) == 0 || !t->tseen) - && (!sobj_at(BOULDER, x, y) - || throws_rocks(youmonst.data)))) { - spot->x = x; - spot->y = y; - min_distance = distance; - found = TRUE; - } + (void) memset((genericptr_t) try, 0, sizeof try); + n = 0; + j = xytodir(u.dx, u.dy); + if (reason == DISMOUNT_KNOCKED && j != DIR_ERR) { + /* we'll check preferred location first; if viable it'll be picked */ + best_j = j; + try[0].x = u.dx, try[0].y = u.dy; + /* the two next best locations are checked second and third */ + i = rn2(2); + clockwise_j = DIR_RIGHT(j); /* (j + 1) % 8 */ + dirtocoord(&cc, clockwise_j); + try[1 + i].x = cc.x, try[1 + i].y = cc.y; /* [1] or [2] */ + counterclk_j = DIR_LEFT(j); /* (j + 8 - 1) % 8 */ + dirtocoord(&cc, counterclk_j); + try[2 - i].x = cc.x, try[2 - i].y = cc.y; /* [2] or [1] */ + n = 3; + debugpline3("knock from saddle: best %s, next %s or %s", + directionname(best_j), + directionname(clockwise_j), directionname(counterclk_j)); + } else { + best_j = clockwise_j = counterclk_j = -1; + } + for (j = 0; j < N_DIRS; ++j) { + /* fortunately NODIAG() handling isn't needed for DISMOUNT_KNOCKED + because hero can only ride when humanoid */ + if (j == best_j || j == clockwise_j || j == counterclk_j) + continue; + /* j==0 is W, j==1 NW, j==2 N, j==3 NE, ..., around to j==7 SW; + so odd j values are diagonal directions here */ + if (reason == DISMOUNT_POLY && NODIAG(u.umonnum) && (j % 1) != 0) + continue; + dirtocoord(&cc, j); + try[n++] = cc; + } + + /* + * Up to three passes; + * i==0: voluntary dismount without impairment avoids known traps and + * boulders; + * i==1: voluntary dismount with impairment or knocked out of saddle + * avoids boulders but allows known traps; + * i==2: other, allow traps and boulders. + * + * Fallback to i==1 if nothing appropriate was found for i==0 and + * to i==2 as last resort. + */ + impaird = (Stunned || Confusion || Fumbling); + viable = 0; + found = FALSE; + for (i = (reason == DISMOUNT_BYCHOICE && !impaird) ? 0 + : ((reason == DISMOUNT_BYCHOICE && impaird) + || reason == DISMOUNT_KNOCKED) ? 1 + : 2; + i <= 2 && !found; ++i) { + for (j = 0; j < n; ++j) { + x = u.ux + try[j].x; + y = u.uy + try[j].y; + if (!isok(x, y) || u_at(x, y)) /* [note: u_at() can't happen] */ + continue; + + if (accessible(x, y) && !MON_AT(x, y) + && test_move(u.ux, u.uy, x - u.ux, y - u.uy, TEST_MOVE)) { + ++viable; + distance = distu(x, y); + if (min_distance < 0 /* no viable candidate yet */ + /* or better than pending candidate (note: orthogonal + spots are distance 1 and diagonal ones distance 2; + treating one as better than the other is arbitrary + and not wanted for DISMOUNT_KNOCKED) */ + || ((best_j == -1) ? (distance < min_distance) : (j < 3)) + /* or equally good, maybe substitute this one */ + || (distance == min_distance && !rn2(viable))) { + /* traps avoided on pass 0; boulders avoided on 0 and 1 */ + kn_trap = i == 0 && ((t = t_at(x, y)) != 0 && t->tseen + && t->ttyp != VIBRATING_SQUARE); + boulder = i <= 1 && (sobj_at(BOULDER, x, y) + && !throws_rocks(gy.youmonst.data)); + if (!kn_trap && !boulder) { + spot->x = x; + spot->y = y; + min_distance = distance; + found = TRUE; + if (best_j != -1 && j < 3) + /* since best_j is first candidate (j==0), j==1 + and j==2 can only get here when best_j was + not viable; 50:50 chance for clockwise_j to + come before counterclk_j so each has same + chance to be next after best_j */ + break; } } } + } } /* If we didn't find a good spot and forceit is on, try enexto(). */ - if (forceit && min_distance < 0 - && !enexto(spot, u.ux, u.uy, youmonst.data)) - return FALSE; + if (forceit && !found) + found = enexto(spot, u.ux, u.uy, gy.youmonst.data); return found; } /* Stop riding the current steed */ void -dismount_steed(reason) -int reason; /* Player was thrown off etc. */ +dismount_steed( + int reason) /* Player was thrown off etc. */ { struct monst *mtmp; struct obj *otmp; + const char *verb; coord cc, steedcc; - const char *verb = "fall"; - boolean repair_leg_damage = (Wounded_legs != 0L); unsigned save_utrap = u.utrap; - boolean have_spot = landing_spot(&cc, reason, 0); + boolean ulev, ufly, + repair_leg_damage = (Wounded_legs != 0L), + have_spot = landing_spot(&cc, reason, 0); mtmp = u.usteed; /* make a copy of steed pointer */ /* Sanity check */ if (!mtmp) /* Just return silently */ return; + u.usteed = 0; /* affects Fly test; could hypothetically affect Lev; + * also affects u_locomotion() */ + ufly = Flying ? TRUE : FALSE; + ulev = Levitation ? TRUE : FALSE; + verb = u_locomotion("fall"); /* only used for _FELL and _KNOCKED */ + u.usteed = mtmp; /* Check the reason for dismounting */ otmp = which_armor(mtmp, W_SADDLE); switch (reason) { case DISMOUNT_THROWN: verb = "are thrown"; + FALLTHROUGH; /*FALLTHRU*/ + case DISMOUNT_KNOCKED: case DISMOUNT_FELL: You("%s off of %s!", verb, mon_nam(mtmp)); if (!have_spot) have_spot = landing_spot(&cc, reason, 1); - losehp(Maybe_Half_Phys(rn1(10, 10)), "riding accident", KILLED_BY_AN); - set_wounded_legs(BOTH_SIDES, (int) HWounded_legs + rn1(5, 5)); - repair_leg_damage = FALSE; + if (!ulev && !ufly) { + losehp(Maybe_Half_Phys(rn1(10, 10)), "riding accident", + KILLED_BY_AN); + set_wounded_legs(BOTH_SIDES, (int) HWounded_legs + rn1(5, 5)); + repair_leg_damage = FALSE; + } break; case DISMOUNT_POLY: You("can no longer ride %s.", mon_nam(u.usteed)); @@ -532,9 +641,9 @@ int reason; /* Player was thrown off etc. */ You("can't. There isn't anywhere for you to stand."); return; } - if (!has_mname(mtmp)) { + if (!has_mgivenname(mtmp)) { pline("You've been through the dungeon on %s with no name.", - an(mtmp->data->mname)); + an(pmname(mtmp->data, Mgender(mtmp)))); if (Hallucination) pline("It felt good to get out of the rain."); } else @@ -545,9 +654,23 @@ int reason; /* Player was thrown off etc. */ if (repair_leg_damage) heal_legs(1); - /* Release the steed and saddle */ - u.usteed = 0; + /* Release the steed */ + u.usteed = (struct monst *) NULL; u.ugallop = 0L; + { + boolean was_stealthy = Stealth != 0; + + steed_vs_stealth(); + if (Stealth && !was_stealthy) + You("seem less noisy now."); + } + + if (u.utraptype == TT_BEARTRAP + || u.utraptype == TT_PIT + || u.utraptype == TT_WEB) { + mtmp->mtrapped = 1; + } + /* * rloc(), rloc_to(), and monkilled()->mondead()->m_detach() all * expect mtmp to be on the map or else have mtmp->mx be 0, but @@ -574,17 +697,12 @@ int reason; /* Player was thrown off etc. */ /* still no spot; last resort is any spot within bounds */ (void) enexto(&steedcc, u.ux, u.uy, &mons[PM_GHOST]); } - if (!m_at(steedcc.x, steedcc.y)) { - if (mtmp->mhp < 1) - mtmp->mhp = 0; /* make sure it isn't negative */ - mtmp->mhp++; /* force at least one hit point, possibly resurrecting */ - place_monster(mtmp, steedcc.x, steedcc.y); - mtmp->mhp--; /* take the extra hit point away: cancel resurrection */ - } else { - impossible("Dismounting: can't place former steed on map."); - } if (!DEADMONSTER(mtmp)) { + gi.in_steed_dismounting++; + place_monster(mtmp, steedcc.x, steedcc.y); + gi.in_steed_dismounting--; + /* if for bones, there's no reason to place the hero; we want to make room for potential ghost, so move steed */ if (reason == DISMOUNT_BONES) { @@ -592,21 +710,23 @@ int reason; /* Player was thrown off etc. */ if (enexto(&cc, u.ux, u.uy, mtmp->data)) rloc_to(mtmp, cc.x, cc.y); else /* evidently no room nearby; move steed elsewhere */ - (void) rloc(mtmp, FALSE); + (void) rloc(mtmp, RLOC_ERR | RLOC_NOMSG); return; } - /* Set hero's and/or steed's positions. Try moving the hero first. */ + /* Set hero's and/or steed's positions. Usually try moving the + hero first. Note: for DISMOUNT_ENGULFED, caller hasn't set + u.uswallow yet but has set u.ustuck. */ if (!u.uswallow && !u.ustuck && have_spot) { struct permonst *mdat = mtmp->data; /* The steed may drop into water/lava */ - if (!is_flyer(mdat) && !is_floater(mdat) && !is_clinger(mdat)) { + if (grounded(mdat)) { if (is_pool(u.ux, u.uy)) { if (!Underwater) pline("%s falls into the %s!", Monnam(mtmp), surface(u.ux, u.uy)); - if (!is_swimmer(mdat) && !amphibious(mdat)) { + if (!cant_drown(mdat)) { killed(mtmp); adjalign(-1); } @@ -630,25 +750,26 @@ int reason; /* Player was thrown off etc. */ * * Clearly this is not the best way to do it. A full fix would * involve having these functions not call pickup() at all, - * instead - * calling them first and calling pickup() afterwards. But it - * would take a lot of work to keep this change from having any - * unforeseen side effects (for instance, you would no longer be - * able to walk onto a square with a hole, and autopickup before - * falling into the hole). + * instead calling them first and calling pickup() afterwards. + * But it would take a lot of work to keep this change from + * having any unforeseen side effects (for instance, you would + * no longer be able to walk onto a square with a hole, and + * autopickup before falling into the hole). */ /* [ALI] No need to move the player if the steed died. */ if (!DEADMONSTER(mtmp)) { /* Keep steed here, move the player to cc; * teleds() clears u.utrap */ - in_steed_dismounting = TRUE; - teleds(cc.x, cc.y, TRUE); - in_steed_dismounting = FALSE; + gi.in_steed_dismounting = TRUE; + teleds(cc.x, cc.y, TELEDS_ALLOW_DRAG); + if (sobj_at(BOULDER, cc.x, cc.y)) + sokoban_guilt(); + gi.in_steed_dismounting = FALSE; /* Put your steed in your trap */ if (save_utrap) - (void) mintrap(mtmp); + (void) mintrap(mtmp, NO_TRAP_FLAGS); } /* Couldn't move hero... try moving the steed. */ @@ -657,8 +778,9 @@ int reason; /* Player was thrown off etc. */ rloc_to(mtmp, cc.x, cc.y); /* Player stays put */ - /* Otherwise, kill the steed. */ + /* Otherwise, steed goes bye-bye. */ } else { +#if 1 /* original there's-no-room handling */ if (reason == DISMOUNT_BYCHOICE) { /* [un]#ride: hero gets credit/blame for killing steed */ killed(mtmp); @@ -668,33 +790,44 @@ int reason; /* Player was thrown off etc. */ damage type is just "neither AD_DGST nor -AD_RBRE" */ monkilled(mtmp, "", -AD_PHYS); } +#else + /* Can't use this [yet?] because it violates monmove()'s + * assumption that a moving monster (engulfer) can't cause + * another monster (steed) to be removed from the fmon list. + * That other monster (steed) might be cached as the next one + * to move. + */ + /* migrate back to this level if hero leaves and returns + or to next level if it is happening in the endgame */ + mdrop_special_objs(mtmp); + deal_with_overcrowding(mtmp); +#endif } } /* !DEADMONST(mtmp) */ /* usually return the hero to the surface */ if (reason != DISMOUNT_ENGULFED && reason != DISMOUNT_BONES) { - in_steed_dismounting = TRUE; + gi.in_steed_dismounting = TRUE; (void) float_down(0L, W_SADDLE); - in_steed_dismounting = FALSE; - context.botl = TRUE; - (void) encumber_msg(); - vision_full_recalc = 1; + gi.in_steed_dismounting = FALSE; + disp.botl = TRUE; + encumber_msg(); + gv.vision_full_recalc = 1; } else - context.botl = TRUE; + disp.botl = TRUE; /* polearms behave differently when not mounted */ if (uwep && is_pole(uwep)) - unweapon = TRUE; + gu.unweapon = TRUE; return; } /* when attempting to saddle or mount a sleeping steed, try to wake it up (for the saddling case, it won't be u.usteed yet) */ -STATIC_OVL void -maybewakesteed(steed) -struct monst *steed; +staticfn void +maybewakesteed(struct monst *steed) { int frozen = (int) steed->mfrozen; - boolean wasimmobile = steed->msleeping || !steed->mcanmove; + boolean wasimmobile = helpless(steed); steed->msleeping = 0; if (frozen) { @@ -708,29 +841,53 @@ struct monst *steed; steed->mfrozen = frozen; } } - if (wasimmobile && !steed->msleeping && steed->mcanmove) + if (wasimmobile && !helpless(steed)) pline("%s wakes up.", Monnam(steed)); /* regardless of waking, terminate any meal in progress */ finish_meating(steed); } +/* steed has taken on a new shape */ +void +poly_steed( + struct monst *steed, + struct permonst *oldshape) +{ + if (!can_saddle(steed) || !can_ride(steed)) { + /* can't get here; newcham() -> mon_break_armor() -> m_lose_armor() + removes saddle and/or forces hero to dismount, if applicable, + before newcham() calls us */ + dismount_steed(DISMOUNT_FELL); + } else { + char buf[BUFSZ]; + + Strcpy(buf, x_monnam(steed, ARTICLE_YOUR, (char *) 0, + SUPPRESS_SADDLE, FALSE)); + if (oldshape != steed->data) + (void) strsubst(buf, "your ", "your new "); + You("adjust yourself in the saddle on %s.", buf); + + /* riding blocks stealth unless hero+steed fly */ + steed_vs_stealth(); + } +} + /* decide whether hero's steed is able to move; doesn't check for holding traps--those affect the hero directly */ boolean -stucksteed(checkfeeding) -boolean checkfeeding; +stucksteed(boolean checkfeeding) { struct monst *steed = u.usteed; if (steed) { /* check whether steed can move */ - if (steed->msleeping || !steed->mcanmove) { - pline("%s won't move!", upstart(y_monnam(steed))); + if (helpless(steed)) { + pline("%s won't move!", YMonnam(steed)); return TRUE; } /* optionally check whether steed is in the midst of a meal */ if (checkfeeding && steed->meating) { - pline("%s is still eating.", upstart(y_monnam(steed))); + pline("%s is still eating.", YMonnam(steed)); return TRUE; } } @@ -738,9 +895,7 @@ boolean checkfeeding; } void -place_monster(mon, x, y) -struct monst *mon; -int x, y; +place_monster(struct monst *mon, coordxy x, coordxy y) { struct monst *othermon; const char *monnm, *othnm; @@ -750,31 +905,30 @@ int x, y; /* normal map bounds are <1..COLNO-1,0..ROWNO-1> but sometimes vault guards (either living or dead) are parked at <0,0> */ if (!isok(x, y) && (x != 0 || y != 0 || !mon->isgd)) { - describe_level(buf); + describe_level(buf, 0); impossible("trying to place %s at <%d,%d> mstate:%lx on %s", minimal_monnam(mon, TRUE), x, y, mon->mstate, buf); x = y = 0; } - if (mon == u.usteed + if ((mon == u.usteed && !gi.in_steed_dismounting) /* special case is for convoluted vault guard handling */ || (DEADMONSTER(mon) && !(mon->isgd && x == 0 && y == 0))) { - describe_level(buf); + describe_level(buf, 0); impossible("placing %s onto map, mstate:%lx, on %s?", (mon == u.usteed) ? "steed" : "defunct monster", mon->mstate, buf); return; } - if ((othermon = level.monsters[x][y]) != 0) { - describe_level(buf); + if ((othermon = svl.level.monsters[x][y]) != 0) { + describe_level(buf, 0); monnm = minimal_monnam(mon, FALSE); othnm = (mon != othermon) ? minimal_monnam(othermon, TRUE) : "itself"; impossible("placing %s over %s at <%d,%d>, mstates:%lx %lx on %s?", monnm, othnm, x, y, othermon->mstate, mon->mstate, buf); } mon->mx = x, mon->my = y; - level.monsters[x][y] = mon; - mon->mstate &= ~(MON_OFFMAP | MON_MIGRATING | MON_LIMBO | MON_BUBBLEMOVE - | MON_ENDGAME_FREE | MON_ENDGAME_MIGR); + svl.level.monsters[x][y] = mon; + mon->mstate = MON_FLOOR; } /*steed.c*/ diff --git a/src/strutil.c b/src/strutil.c new file mode 100644 index 000000000..8047281b6 --- /dev/null +++ b/src/strutil.c @@ -0,0 +1,158 @@ +/* NetHack 5.0 strutil.c $NHDT-Date: 1709571807 2024/03/04 17:03:27 $ $NHDT-Branch: keni-mdlib-followup $:$NHDT-Revision: 1.0 $ */ +/* Copyright (c) Robert Patrick Rankin, 1991 */ +/* NetHack may be freely redistributed. See license for details. */ + +#include "hack.h" /* for config.h+extern.h */ + +/* strbuf_init() initializes strbuf state for use */ +void +strbuf_init(strbuf_t *strbuf) +{ + strbuf->str = NULL; + strbuf->len = 0; +} + +/* strbuf_append() appends given str to strbuf->str */ +void +strbuf_append(strbuf_t *strbuf, const char *str) +{ + int len = (int) strlen(str) + 1; + + strbuf_reserve(strbuf, + len + (strbuf->str ? (int) strlen(strbuf->str) : 0)); + Strcat(strbuf->str, str); +} + +/* strbuf_reserve() ensure strbuf->str has storage for len characters */ +void +strbuf_reserve(strbuf_t *strbuf, int len) +{ + if (strbuf->str == NULL) { + strbuf->str = strbuf->buf; + strbuf->str[0] = '\0'; + strbuf->len = (int) sizeof strbuf->buf; + } + + if (len > strbuf->len) { + char *oldbuf = strbuf->str; + + strbuf->len = len + (int) sizeof strbuf->buf; + strbuf->str = (char *) alloc(strbuf->len); + Strcpy(strbuf->str, oldbuf); + if (oldbuf != strbuf->buf) + free((genericptr_t) oldbuf); + } +} + +/* strbuf_empty() frees allocated memory and set strbuf to initial state */ +void +strbuf_empty(strbuf_t *strbuf) +{ + if (strbuf->str != NULL && strbuf->str != strbuf->buf) + free((genericptr_t) strbuf->str); + strbuf_init(strbuf); +} + +/* strbuf_nl_to_crlf() converts all occurrences of \n to \r\n */ +void +strbuf_nl_to_crlf(strbuf_t *strbuf) +{ + if (strbuf->str) { + int len = (int) strlen(strbuf->str); + int count = 0; + char *cp = strbuf->str; + + while (*cp) + if (*cp++ == '\n') + count++; + if (count) { + strbuf_reserve(strbuf, len + count + 1); + for (cp = strbuf->str + len + count; count; --cp) + if ((*cp = cp[-count]) == '\n') { + *--cp = '\r'; + --count; + } + } + } +} + +/* strlen() but returns unsigned and panics if string is unreasonably long; + used by dlb as well as by nethack */ +unsigned +Strlen_( + const char *str, + const char *file, + int line) +{ + const char *p; + size_t len; + + /* strnlen(str, LARGEST_INT) w/o requiring posix.1 headers or libraries */ + for (p = str, len = 0; len < LARGEST_INT; ++len) + if (*p++ == '\0') + break; + + if (len == LARGEST_INT) + panic("%s:%d string too long", file, line); + return (unsigned) len; +} + +staticfn boolean pmatch_internal(const char *, const char *, boolean, + const char *); +/* guts of pmatch(), pmatchi(), and pmatchz(); + match a string against a pattern */ +staticfn boolean +pmatch_internal(const char *patrn, const char *strng, + boolean ci, /* True => case-insensitive, + False => case-sensitive */ + const char *sk) /* set of characters to skip */ +{ + char s, p; + /* + * Simple pattern matcher: '*' matches 0 or more characters, '?' matches + * any single character. Returns TRUE if 'strng' matches 'patrn'. + */ + pmatch_top: + if (!sk) { + s = *strng++; + p = *patrn++; /* get next chars and pre-advance */ + } else { + /* fuzzy match variant of pmatch; particular characters are ignored */ + do { + s = *strng++; + } while (strchr(sk, s)); + do { + p = *patrn++; + } while (strchr(sk, p)); + } + if (!p) /* end of pattern */ + return (boolean) (s == '\0'); /* matches iff end of string too */ + else if (p == '*') /* wildcard reached */ + return (boolean) ((!*patrn + || pmatch_internal(patrn, strng - 1, ci, sk)) + ? TRUE + : s ? pmatch_internal(patrn - 1, strng, ci, sk) + : FALSE); + else if ((ci ? lowc(p) != lowc(s) : p != s) /* check single character */ + && (p != '?' || !s)) /* & single-char wildcard */ + return FALSE; /* doesn't match */ + else /* return pmatch_internal(patrn, strng, ci, sk); */ + goto pmatch_top; /* optimize tail recursion */ +} + +/* case-sensitive wildcard match */ +boolean +pmatch(const char *patrn, const char *strng) +{ + return pmatch_internal(patrn, strng, FALSE, (const char *) 0); +} + +/* case-insensitive wildcard match */ +boolean +pmatchi(const char *patrn, const char *strng) +{ + return pmatch_internal(patrn, strng, TRUE, (const char *) 0); +} + + +/*strutil.c*/ diff --git a/src/symbols.c b/src/symbols.c new file mode 100644 index 000000000..428c354d4 --- /dev/null +++ b/src/symbols.c @@ -0,0 +1,1103 @@ +/* NetHack 5.0 symbols.c $NHDT-Date: 1736530208 2025/01/10 09:30:08 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.123 $ */ +/* Copyright (c) NetHack Development Team 2020. */ +/* NetHack may be freely redistributed. See license for details. */ + +#include "hack.h" +#include "tcap.h" + +staticfn void savedsym_add(const char *, const char *, int); +staticfn struct _savedsym *savedsym_find(const char *, int); + +extern const uchar def_r_oc_syms[MAXOCLASSES]; /* drawing.c */ + +#if defined(TERMLIB) || defined(CURSES_GRAPHICS) +void (*decgraphics_mode_callback)(void) = 0; /* set in term_start_screen() */ +#endif /* TERMLIB || CURSES */ + +#ifdef PC9800 +void (*ibmgraphics_mode_callback)(void) = 0; /* set in term_start_screen() */ +void (*ascgraphics_mode_callback)(void) = 0; /* set in term_start_screen() */ +#endif +#ifdef CURSES_GRAPHICS +void (*cursesgraphics_mode_callback)(void) = 0; +#endif +#if defined(TTY_GRAPHICS) && defined(WIN32) +void (*ibmgraphics_mode_callback)(void) = 0; +#endif +#ifdef ENHANCED_SYMBOLS +void (*utf8graphics_mode_callback)(void) = 0; /* set in term_start_screen and + * found in unixtty,windtty,&c */ +#endif + +/* + * Explanations of the functions found below: + * + * init_symbols() + * Sets the current display symbols, the + * loadable symbols to the default NetHack + * symbols, including the rogue_syms rogue level + * symbols. This would typically be done + * immediately after execution begins. Any + * previously loaded external symbol sets are + * discarded. + * + * switch_symbols(arg) + * Called to swap in new current display symbols + * (showsyms) from either the default symbols, + * or from the loaded symbols. + * + * If (arg == 0) then showsyms are taken from + * defsyms, def_oc_syms, and def_monsyms. + * + * If (arg != 0), which is the normal expected + * usage, then showsyms are taken from the + * adjustable display symbols found in gp.primary_syms. + * gp.primary_syms may have been loaded from an external + * symbol file by config file options or interactively + * in the Options menu. + * + * assign_graphics(arg) + * + * This is used in the game core to toggle in and + * out of other {rogue} level display modes. + * + * If arg is ROGUESET, this places the rogue level + * symbols from gr.rogue_syms into gs.showsyms. + * + * If arg is PRIMARYSET, this places the symbols + * from gp.primary_syms into gs.showsyms. + * + * update_primary_symset() + * Update a member of the primary(primary_*) symbol set. + * + * update_rogue_symset() + * Update a member of the rogue (rogue_*) symbol set. + * + * update_ov_primary_symset() + * Update a member of the overrides for primary symbol set. + * + * update_ov_rogue_symset() + * Update a member of the overrides for rogue symbol set. + * + */ + +void +init_symbols(void) +{ + init_ov_primary_symbols(); + init_ov_rogue_symbols(); + init_primary_symbols(); + init_showsyms(); + init_rogue_symbols(); +} + +void +init_showsyms(void) +{ + int i; + + for (i = 0; i < MAXPCHARS; i++) + gs.showsyms[i + SYM_OFF_P] = defsyms[i].sym; + for (i = 0; i < MAXOCLASSES; i++) + gs.showsyms[i + SYM_OFF_O] = def_oc_syms[i].sym; + for (i = 0; i < MAXMCLASSES; i++) + gs.showsyms[i + SYM_OFF_M] = def_monsyms[i].sym; + for (i = 0; i < WARNCOUNT; i++) + gs.showsyms[i + SYM_OFF_W] = def_warnsyms[i].sym; + for (i = 0; i < MAXOTHER; i++) + gs.showsyms[i + SYM_OFF_X] = get_othersym(i, PRIMARYSET); +} + +/* initialize defaults for the overrides to the rogue symset */ +void +init_ov_rogue_symbols(void) +{ + int i; + + for (i = 0; i < SYM_MAX; i++) + go.ov_rogue_syms[i] = (nhsym) 0; +} +/* initialize defaults for the overrides to the primary symset */ +void +init_ov_primary_symbols(void) +{ + int i; + + for (i = 0; i < SYM_MAX; i++) + go.ov_primary_syms[i] = (nhsym) 0; +} + +nhsym +get_othersym(int idx, int which_set) +{ + nhsym sym = (nhsym) 0; + int oidx = idx + SYM_OFF_X; + + if (which_set == ROGUESET) + sym = go.ov_rogue_syms[oidx] ? go.ov_rogue_syms[oidx] + : gr.rogue_syms[oidx]; + else + sym = go.ov_primary_syms[oidx] ? go.ov_primary_syms[oidx] + : gp.primary_syms[oidx]; + if (!sym) { + switch(idx) { + case SYM_NOTHING: + case SYM_UNEXPLORED: + sym = DEF_NOTHING; + break; + case SYM_BOULDER: + sym = def_oc_syms[ROCK_CLASS].sym; + break; + case SYM_INVISIBLE: + sym = DEF_INVISIBLE; + break; +#if 0 + /* these intentionally have no defaults */ + case SYM_PET_OVERRIDE: + case SYM_HERO_OVERRIDE: + break; +#endif + } + } + return sym; +} + +/* initialize defaults for the primary symset */ +void +init_primary_symbols(void) +{ + int i; + + for (i = 0; i < MAXPCHARS; i++) + gp.primary_syms[i + SYM_OFF_P] = defsyms[i].sym; + for (i = 0; i < MAXOCLASSES; i++) + gp.primary_syms[i + SYM_OFF_O] = def_oc_syms[i].sym; + for (i = 0; i < MAXMCLASSES; i++) + gp.primary_syms[i + SYM_OFF_M] = def_monsyms[i].sym; + for (i = 0; i < WARNCOUNT; i++) + gp.primary_syms[i + SYM_OFF_W] = def_warnsyms[i].sym; + for (i = 0; i < MAXOTHER; i++) + gp.primary_syms[i + SYM_OFF_X] = get_othersym(i, PRIMARYSET); + + clear_symsetentry(PRIMARYSET, FALSE); +} + +/* initialize defaults for the rogue symset */ +void +init_rogue_symbols(void) +{ + int i; + + /* These are defaults that can get overwritten + later by the roguesymbols option */ + + for (i = 0; i < MAXPCHARS; i++) + gr.rogue_syms[i + SYM_OFF_P] = defsyms[i].sym; + gr.rogue_syms[S_vodoor] = gr.rogue_syms[S_hodoor] + = gr.rogue_syms[S_ndoor] = '+'; + gr.rogue_syms[S_upstair] = gr.rogue_syms[S_dnstair] = '%'; + + for (i = 0; i < MAXOCLASSES; i++) + gr.rogue_syms[i + SYM_OFF_O] = def_r_oc_syms[i]; + for (i = 0; i < MAXMCLASSES; i++) + gr.rogue_syms[i + SYM_OFF_M] = def_monsyms[i].sym; + for (i = 0; i < WARNCOUNT; i++) + gr.rogue_syms[i + SYM_OFF_W] = def_warnsyms[i].sym; + for (i = 0; i < MAXOTHER; i++) + gr.rogue_syms[i + SYM_OFF_X] = get_othersym(i, ROGUESET); + + clear_symsetentry(ROGUESET, FALSE); + /* default on Rogue level is no color + * but some symbol sets can override that + */ + gs.symset[ROGUESET].nocolor = 1; +} + +void +assign_graphics(int whichset) +{ + int i; + + switch (whichset) { + case ROGUESET: + /* Adjust graphics display characters on Rogue levels */ + + for (i = 0; i < SYM_MAX; i++) + gs.showsyms[i] = go.ov_rogue_syms[i] ? go.ov_rogue_syms[i] + : gr.rogue_syms[i]; + +#if defined(MSDOS) && defined(TILES_IN_GLYPHMAP) + if (iflags.grmode) + tileview(FALSE); +#endif + gc.currentgraphics = ROGUESET; + break; + + case PRIMARYSET: + default: + for (i = 0; i < SYM_MAX; i++) + gs.showsyms[i] = go.ov_primary_syms[i] ? go.ov_primary_syms[i] + : gp.primary_syms[i]; + +#if defined(MSDOS) && defined(TILES_IN_GLYPHMAP) + if (iflags.grmode) + tileview(TRUE); +#endif + gc.currentgraphics = PRIMARYSET; + break; + } + reset_glyphmap(gm_symchange); +} + +void +switch_symbols(int nondefault) +{ + int i; + + if (nondefault) { + for (i = 0; i < SYM_MAX; i++) + gs.showsyms[i] = go.ov_primary_syms[i] ? go.ov_primary_syms[i] + : gp.primary_syms[i]; +#ifdef PC9800 + if (SYMHANDLING(H_IBM) && ibmgraphics_mode_callback) + (*ibmgraphics_mode_callback)(); + else if (SYMHANDLING(H_UNK) && ascgraphics_mode_callback) + (*ascgraphics_mode_callback)(); +#endif +#if defined(TERMLIB) || defined(CURSES_GRAPHICS) + /* curses doesn't assign any routine to dec..._callback but + probably does the expected initialization under the hood + for terminals capable of rendering DECgraphics */ + if (SYMHANDLING(H_DEC) && decgraphics_mode_callback) + (*decgraphics_mode_callback)(); +# ifdef CURSES_GRAPHICS + /* there aren't any symbol sets with CURS handling, and the + curses interface never assigns a routine to curses..._callback */ + if (SYMHANDLING(H_CURS) && cursesgraphics_mode_callback) + (*cursesgraphics_mode_callback)(); +# endif +#endif +#if defined(TTY_GRAPHICS) && defined(WIN32) + if (SYMHANDLING(H_IBM) && ibmgraphics_mode_callback) + (*ibmgraphics_mode_callback)(); +#endif +#ifdef ENHANCED_SYMBOLS + if (SYMHANDLING(H_UTF8) && utf8graphics_mode_callback) + (*utf8graphics_mode_callback)(); +#endif + } else { + init_primary_symbols(); + init_showsyms(); + } +} + +void +update_ov_primary_symset(const struct symparse *symp, int val) +{ + go.ov_primary_syms[symp->idx] = val; +} + +void +update_ov_rogue_symset(const struct symparse *symp, int val) +{ + go.ov_rogue_syms[symp->idx] = val; +} + +void +update_primary_symset(const struct symparse *symp, int val) +{ + gp.primary_syms[symp->idx] = val; +} + +void +update_rogue_symset(const struct symparse *symp, int val) +{ + gr.rogue_syms[symp->idx] = val; +} + +void +clear_symsetentry(int which_set, boolean name_too) +{ +#ifdef ENHANCED_SYMBOLS + int other_set = (which_set == PRIMARYSET) ? ROGUESET : PRIMARYSET; + enum symset_handling_types old_handling = gs.symset[which_set].handling; +#endif + + if (gs.symset[which_set].desc) + free((genericptr_t) gs.symset[which_set].desc); + gs.symset[which_set].desc = (char *) 0; + + gs.symset[which_set].handling = H_UNK; + gs.symset[which_set].nocolor = 0; + /* initialize restriction bits */ + gs.symset[which_set].primary = 0; + gs.symset[which_set].rogue = 0; + + if (name_too) { + if (gs.symset[which_set].name) + free((genericptr_t) gs.symset[which_set].name); + gs.symset[which_set].name = (char *) 0; + } +#ifdef ENHANCED_SYMBOLS + /* if 'which_set' was using UTF8, it isn't anymore; if the other set + isn't using UTF8, discard the data for that */ + if (old_handling == H_UTF8 && gs.symset[other_set].handling != H_UTF8) + free_all_glyphmap_u(); +#endif + purge_custom_entries(which_set); + clear_all_glyphmap_colors(); +} + +/* called from windmain.c */ +boolean +symset_is_compatible( + enum symset_handling_types handling, + unsigned long wincap2) +{ +#ifdef ENHANCED_SYMBOLS +#define WC2_utf8_bits (WC2_U_UTF8STR) + if (handling == H_UTF8 && ((wincap2 & WC2_utf8_bits) != WC2_utf8_bits)) + return FALSE; +#undef WC2_bits +#else + nhUse(handling); + nhUse(wincap2); +#endif + return TRUE; +} + +/* + * If you are adding code somewhere to be able to recognize + * particular types of symset "handling", define a + * H_XXX macro in include/sym.h and add the name + * to this array at the matching offset. + * Externally referenced from files.c, options.c, utf8map.c. + */ +const char *const known_handling[] = { + "UNKNOWN", /* H_UNK */ + "IBM", /* H_IBM */ + "DEC", /* H_DEC */ + "CURS", /* H_CURS */ + "MAC", /* H_MAC -- pre-OSX MACgraphics */ + "UTF8", /* H_UTF8 */ + (const char *) 0, +}; + +/* + * Accepted keywords for symset restrictions. + * These can be virtually anything that you want to + * be able to test in the code someplace. + * Be sure to: + * - add a corresponding Bitfield to the symsetentry struct in sym.h + * - initialize the field to zero in parse_sym_line in the SYM_CONTROL + * case 0 section of the idx switch. The location is prefaced with + * with a comment stating "initialize restriction bits". + * - set the value appropriately based on the index of your keyword + * under the case 5 sections of the same SYM_CONTROL idx switches. + * - add the field to clear_symsetentry() + */ +const char *const known_restrictions[] = { + "primary", "rogue", (const char *) 0, +}; + +const struct symparse loadsyms[] = { + { SYM_CONTROL, 0, "start" }, + { SYM_CONTROL, 0, "begin" }, + { SYM_CONTROL, 1, "finish" }, + { SYM_CONTROL, 2, "handling" }, + { SYM_CONTROL, 3, "description" }, + { SYM_CONTROL, 4, "color" }, + { SYM_CONTROL, 4, "colour" }, + { SYM_CONTROL, 5, "restrictions" }, +#define PCHAR_PARSE +#include "defsym.h" +#undef PCHAR_PARSE +#define OBJCLASS_PARSE +#include "defsym.h" +#undef OBJCLASS_PARSE +#define MONSYMS_PARSE +#include "defsym.h" +#undef MONSYMS_PARSE + { SYM_OTH, SYM_NOTHING + SYM_OFF_X, "S_nothing" }, + { SYM_OTH, SYM_UNEXPLORED + SYM_OFF_X, "S_unexplored" }, + { SYM_OTH, SYM_BOULDER + SYM_OFF_X, "S_boulder" }, + { SYM_OTH, SYM_INVISIBLE + SYM_OFF_X, "S_invisible" }, + { SYM_OTH, SYM_PET_OVERRIDE + SYM_OFF_X, "S_pet_override" }, + { SYM_OTH, SYM_HERO_OVERRIDE + SYM_OFF_X, "S_hero_override" }, + { SYM_INVALID, 0, (const char *) 0 } /* fence post */ +}; + +boolean +proc_symset_line(char *buf) +{ + return !((boolean) parse_sym_line(buf, gs.symset_which_set)); +} + +/* returns 0 on error */ +int +parse_sym_line(char *buf, int which_set) +{ + int val, i; + const struct symparse *symp = (struct symparse *) 0; + char *bufp, *commentp, *altp; + int glyph = NO_GLYPH; + boolean enhanced_unavailable = FALSE, is_glyph = FALSE; + + if (strlen(buf) >= BUFSZ) + buf[BUFSZ - 1] = '\0'; + /* convert each instance of whitespace (tabs, consecutive spaces) + into a single space; leading and trailing spaces are stripped */ + mungspaces(buf); + + /* remove trailing comment, if any (this isn't strictly needed for + individual symbols, and it won't matter if "X#comment" without + separating space slips through; for handling or set description, + symbol set creator is responsible for preceding '#' with a space + and that comment itself doesn't contain " #") */ + if ((commentp = strrchr(buf, '#')) != 0 && commentp[-1] == ' ') + commentp[-1] = '\0'; + + /* find the '=' or ':' */ + bufp = strchr(buf, '='); + altp = strchr(buf, ':'); + + if (!bufp || (altp && altp < bufp)) + bufp = altp; + + if (!bufp) { + if (strncmpi(buf, "finish", 6) == 0) { + /* end current graphics set */ + if (gc.chosen_symset_start) + gc.chosen_symset_end = TRUE; + gc.chosen_symset_start = FALSE; + return 1; + } + config_error_add("No \"finish\""); + return 0; + } + /* skip '=' and space which follows, if any */ + ++bufp; + if (*bufp == ' ') + ++bufp; + + symp = match_sym(buf); + if (!symp && buf[0] == 'G' && buf[1] == '_') { + if (gc.chosen_symset_start) { + is_glyph = match_glyph(buf); + } else { + is_glyph = TRUE; /* report error only once */ + } +#ifdef ENHANCED_SYMBOLS + enhanced_unavailable = FALSE; +#else + enhanced_unavailable = TRUE; +#endif + } + if (!symp && !is_glyph && !enhanced_unavailable) { + config_error_add("Unknown sym keyword"); + return 0; + } + if (symp) { + if (!gs.symset[which_set].name) { + /* A null symset name indicates that we're just + building a pick-list of possible symset + values from the file, so only do that */ + if (symp->range == SYM_CONTROL) { + struct symsetentry *tmpsp, *lastsp; + + for (lastsp = gs.symset_list; lastsp; lastsp = lastsp->next) + if (!lastsp->next) + break; + switch (symp->idx) { + case 0: + tmpsp = (struct symsetentry *) alloc(sizeof *tmpsp); + tmpsp->next = (struct symsetentry *) 0; + if (!lastsp) + gs.symset_list = tmpsp; + else + lastsp->next = tmpsp; + tmpsp->idx = gs.symset_count++; + tmpsp->name = dupstr(bufp); + tmpsp->desc = (char *) 0; + tmpsp->handling = H_UNK; + /* initialize restriction bits */ + tmpsp->nocolor = 0; + tmpsp->primary = 0; + tmpsp->rogue = 0; + break; + case 2: + /* handler type identified */ + tmpsp = lastsp; /* most recent symset */ + for (i = 0; known_handling[i]; ++i) + if (!strcmpi(known_handling[i], bufp)) { + if (tmpsp) + tmpsp->handling = i; + break; /* for loop */ + } + break; + case 3: + /* description:something */ + tmpsp = lastsp; /* most recent symset */ + if (tmpsp && !tmpsp->desc) + tmpsp->desc = dupstr(bufp); + break; + case 5: + /* restrictions: xxxx*/ + tmpsp = lastsp; /* most recent symset */ + for (i = 0; known_restrictions[i]; ++i) { + if (!strcmpi(known_restrictions[i], bufp)) { + if (tmpsp) { + switch (i) { + case 0: + tmpsp->primary = 1; + break; + case 1: + tmpsp->rogue = 1; + break; + } + } + break; /* while loop */ + } + } + break; + } + } + return 1; + } + if (symp->range && symp->range == SYM_CONTROL) { + switch (symp->idx) { + case 0: + /* start of symset */ + if (!strcmpi(bufp, gs.symset[which_set].name)) { + /* matches desired one */ + gc.chosen_symset_start = TRUE; + /* these init_*() functions clear symset fields too */ + if (which_set == ROGUESET) + init_rogue_symbols(); + else if (which_set == PRIMARYSET) + init_primary_symbols(); + } + break; + case 1: + /* finish symset */ + if (gc.chosen_symset_start) + gc.chosen_symset_end = TRUE; + gc.chosen_symset_start = FALSE; + break; + case 2: + /* handler type identified */ + if (gc.chosen_symset_start) + set_symhandling(bufp, which_set); + break; + /* case 3: (description) is ignored here */ + case 4: /* color:off */ + if (gc.chosen_symset_start) { + if (bufp) { + if (!strcmpi(bufp, "true") || !strcmpi(bufp, "yes") + || !strcmpi(bufp, "on")) + gs.symset[which_set].nocolor = 0; + else if (!strcmpi(bufp, "false") + || !strcmpi(bufp, "no") + || !strcmpi(bufp, "off")) + gs.symset[which_set].nocolor = 1; + } + } + break; + case 5: /* restrictions: xxxx*/ + if (gc.chosen_symset_start) { + int n = 0; + + while (known_restrictions[n]) { + if (!strcmpi(known_restrictions[n], bufp)) { + switch (n) { + case 0: + gs.symset[which_set].primary = 1; + break; + case 1: + gs.symset[which_set].rogue = 1; + break; + } + break; /* while loop */ + } + n++; + } + } + break; + } + } else { + /* Not SYM_CONTROL */ + if (gs.symset[which_set].handling != H_UTF8) { + if (gc.chosen_symset_start) { + val = sym_val(bufp); + if (which_set == PRIMARYSET) { + update_primary_symset(symp, val); + } else if (which_set == ROGUESET) { + update_rogue_symset(symp, val); + } + } +#ifdef ENHANCED_SYMBOLS + } else { + if (gc.chosen_symset_start) { + (void) glyphrep_to_custom_map_entries(buf, &glyph); + } +#endif + } + } + } else if (gc.chosen_symset_start) { + /* glyph, not symbol */ + (void) glyphrep_to_custom_map_entries(buf, &glyph); + } +#ifndef ENHANCED_SYMBOLS + nhUse(glyph); +#endif + return 1; +} + +void +set_symhandling(char *handling, int which_set) +{ + int i = 0; + + gs.symset[which_set].handling = H_UNK; + while (known_handling[i]) { + if (!strcmpi(known_handling[i], handling)) { + gs.symset[which_set].handling = i; + return; + } + i++; + } +} + +/* bundle some common usage into one easy-to-use routine */ +int +load_symset(const char *s, int which_set) +{ + clear_symsetentry(which_set, TRUE); + + if (gs.symset[which_set].name) + free((genericptr_t) gs.symset[which_set].name); + gs.symset[which_set].name = dupstr(s); + + if (read_sym_file(which_set)) { + switch_symbols(TRUE); + apply_customizations(gc.currentgraphics, + do_custom_symbols | do_custom_colors); + } else { + clear_symsetentry(which_set, TRUE); + return 0; + } + return 1; +} + +void +free_symsets(void) +{ + clear_symsetentry(PRIMARYSET, TRUE); + clear_symsetentry(ROGUESET, TRUE); + + /* symset_list is cleaned up as soon as it's used, so we shouldn't + have to anything about it here */ + /* assert( symset_list == NULL ); */ +} + +struct _savedsym { + char *name; + char *val; + int which_set; + struct _savedsym *next; +}; +struct _savedsym *saved_symbols = NULL; + +void +savedsym_free(void) +{ + struct _savedsym *tmp = saved_symbols, *tmp2; + + while (tmp) { + tmp2 = tmp->next; + free(tmp->name); + free(tmp->val); + free(tmp); + tmp = tmp2; + } +} + +staticfn struct _savedsym * +savedsym_find(const char *name, int which_set) +{ + struct _savedsym *tmp = saved_symbols; + + while (tmp) { + if (which_set == tmp->which_set && !strcmp(name, tmp->name)) + return tmp; + tmp = tmp->next; + } + return NULL; +} + +staticfn void +savedsym_add(const char *name, const char *val, int which_set) +{ + struct _savedsym *tmp = NULL; + + if ((tmp = savedsym_find(name, which_set)) != 0) { + free(tmp->val); + tmp->val = dupstr(val); + } else { + tmp = (struct _savedsym *) alloc(sizeof *tmp); + tmp->name = dupstr(name); + tmp->val = dupstr(val); + tmp->which_set = which_set; + tmp->next = saved_symbols; + saved_symbols = tmp; + } +} + +void +savedsym_strbuf(strbuf_t *sbuf) +{ + struct _savedsym *tmp = saved_symbols; + char buf[BUFSZ]; + + while (tmp) { + Sprintf(buf, "%sSYMBOLS=%s:%s\n", + (tmp->which_set == ROGUESET) ? "ROGUE" : "", + tmp->name, tmp->val); + strbuf_append(sbuf, buf); + tmp = tmp->next; + } +} + +/* Parse the value of a SYMBOLS line from a config file */ +boolean +parsesymbols(char *opts, int which_set) +{ + int val; + char *symname, *strval, *ch, + *first_unquoted_comma = 0, *first_unquoted_colon = 0; + const struct symparse *symp; + boolean is_glyph = FALSE; + + /* are there any commas or colons that aren't quoted? */ + for (ch = opts + 1; *ch; ++ch) { + char *prech, *postch; + + prech = ch - 1; + postch = ch + 1; + if (!*postch) + break; + if (*ch == ',') { + if (*prech == '\'' && *postch == '\'') + continue; + if (*prech == '\\') + continue; + } + if (*ch == ':') { + if (*prech == '\'' && *postch == '\'') + continue; + } + if (*ch == ',' && !first_unquoted_comma) + first_unquoted_comma = ch; + if (*ch == ':' && !first_unquoted_colon) + first_unquoted_colon = ch; + } + if (first_unquoted_comma != 0) { + *first_unquoted_comma++ = '\0'; + if (!parsesymbols(first_unquoted_comma, which_set)) + return FALSE; + } + + /* S_sample:string */ + symname = opts; + strval = first_unquoted_colon; + if (!strval) + strval = strchr(opts, '='); + if (!strval) + return FALSE; + *strval++ = '\0'; + + /* strip leading and trailing white space from symname and strval */ + mungspaces(symname); + mungspaces(strval); + + symp = match_sym(symname); + if (!symp && symname[0] == 'G' && symname[1] == '_') { + is_glyph = match_glyph(symname); + } + if (!symp && !is_glyph) + return FALSE; + if (symp) { + if (symp->range && symp->range != SYM_CONTROL) { + if (gs.symset[which_set].handling == H_UTF8 + || (lowc(strval[0]) == 'u' && strval[1] == '+')) { + char buf[BUFSZ]; + int glyph; + + Snprintf(buf, sizeof buf, "%s:%s", opts, strval); + glyphrep_to_custom_map_entries(buf, &glyph); + } else { + val = sym_val(strval); + if (which_set == ROGUESET) + update_ov_rogue_symset(symp, val); + else + update_ov_primary_symset(symp, val); + } + } + } + savedsym_add(opts, strval, which_set); + return TRUE; +} + +const struct symparse * +match_sym(char *buf) +{ + static struct alternate_parse { + const char *altnm; + const char *nm; + } alternates[] = { + { "S_armour", "S_armor" }, + /* alt explosion names are numbered in phone key/button layout */ + { "S_explode1", "S_expl_tl" }, + { "S_explode2", "S_expl_tc" }, { "S_explode3", "S_expl_tr" }, + { "S_explode4", "S_expl_ml" }, { "S_explode5", "S_expl_mc" }, + { "S_explode6", "S_expl_mr" }, { "S_explode7", "S_expl_bl" }, + { "S_explode8", "S_expl_bc" }, { "S_explode9", "S_expl_br" }, + }; + int i; + size_t len = strlen(buf); + const char *p = strchr(buf, ':'), *q = strchr(buf, '='); + const struct symparse *sp = loadsyms; + + /* G_ lines will never match here */ + if ((buf[0] == 'G' || buf[0] == 'g') && buf[1] == '_') + return (struct symparse *) 0; + + if (!p || (q && q < p)) + p = q; + if (p) { + /* note: there will be at most one space before the '=' + because caller has condensed buf[] with mungspaces() */ + if (p > buf && p[-1] == ' ') + p--; + len = (int) (p - buf); + } + while (sp->range) { + if ((len >= strlen(sp->name)) && !strncmpi(buf, sp->name, len)) + return sp; + sp++; + } + for (i = 0; i < SIZE(alternates); ++i) { + if ((len >= strlen(alternates[i].altnm)) + && !strncmpi(buf, alternates[i].altnm, len)) { + sp = loadsyms; + while (sp->range) { + if (!strcmp(alternates[i].nm, sp->name)) + return sp; + sp++; + } + } + } + return (struct symparse *) 0; +} + +DISABLE_WARNING_FORMAT_NONLITERAL + +/* + * this is called from options.c to do the symset work. + */ +int +do_symset(boolean rogueflag) +{ + winid tmpwin; + anything any; + int n; + char buf[BUFSZ]; + menu_item *symset_pick = (menu_item *) 0; + boolean ready_to_switch = FALSE, + nothing_to_do = FALSE; + char *symset_name, fmtstr[20]; + struct symsetentry *sl; + int res, which_set, setcount = 0, chosen = -2, defindx = 0; + int clr = NO_COLOR; + + which_set = rogueflag ? ROGUESET : PRIMARYSET; + gs.symset_list = (struct symsetentry *) 0; + /* clear symset[].name as a flag to read_sym_file() to build list */ + symset_name = gs.symset[which_set].name; + gs.symset[which_set].name = (char *) 0; + + res = read_sym_file(which_set); + /* put symset name back */ + gs.symset[which_set].name = symset_name; + + if (res && gs.symset_list) { + int thissize, + biggest = (int) (sizeof "Default Symbols" - sizeof ""), + big_desc = 0; + + for (sl = gs.symset_list; sl; sl = sl->next) { + /* check restrictions */ + if (rogueflag ? sl->primary : sl->rogue) + continue; +#ifndef MAC_GRAPHICS_ENV + if (sl->handling == H_MAC) + continue; +#endif +#ifndef ENHANCED_SYMBOLS + if (sl->handling == H_UTF8) + continue; +#endif + setcount++; + /* find biggest name */ + thissize = sl->name ? (int) strlen(sl->name) : 0; + if (thissize > biggest) + biggest = thissize; + thissize = sl->desc ? (int) strlen(sl->desc) : 0; + if (thissize > big_desc) + big_desc = thissize; + } + if (!setcount) { + There("are no appropriate %s symbol sets available.", + rogueflag ? "rogue level" : "primary"); + return TRUE; + } + + Sprintf(fmtstr, "%%-%ds %%s", biggest + 2); + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + any.a_int = 1; /* -1 + 2 [see 'if (sl->name) {' below]*/ + if (!symset_name) + defindx = any.a_int; + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, + clr, "Default Symbols", + (any.a_int == defindx) ? MENU_ITEMFLAGS_SELECTED + : MENU_ITEMFLAGS_NONE); + + for (sl = gs.symset_list; sl; sl = sl->next) { + /* check restrictions */ + if (rogueflag ? sl->primary : sl->rogue) + continue; +#ifndef MAC_GRAPHICS_ENV + if (sl->handling == H_MAC) + continue; +#endif +#ifndef ENHANCED_SYMBOLS + if (sl->handling == H_UTF8) + continue; +#endif + if (sl->name) { + /* +2: sl->idx runs from 0 to N-1 for N symsets; + +1 because Defaults are implicitly in slot [0]; + +1 again so that valid data is never 0 */ + any.a_int = sl->idx + 2; + if (symset_name && !strcmpi(sl->name, symset_name)) + defindx = any.a_int; + Sprintf(buf, fmtstr, sl->name, sl->desc ? sl->desc : ""); + add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, + ATR_NONE, clr, buf, + (any.a_int == defindx) ? MENU_ITEMFLAGS_SELECTED + : MENU_ITEMFLAGS_NONE); + } + } + Sprintf(buf, "Select %ssymbol set:", + rogueflag ? "rogue level " : ""); + end_menu(tmpwin, buf); + n = select_menu(tmpwin, PICK_ONE, &symset_pick); + if (n > 0) { + chosen = symset_pick[0].item.a_int; + /* if picking non-preselected entry yields 2, make sure + that we're going with the non-preselected one */ + if (n == 2 && chosen == defindx) + chosen = symset_pick[1].item.a_int; + chosen -= 2; /* convert menu index to symset index; + * "Default symbols" have index -1 */ + free((genericptr_t) symset_pick); + } else if (n == 0 && defindx > 0) { + chosen = defindx - 2; + } + destroy_nhwindow(tmpwin); + + if (chosen > -1) { + /* chose an actual symset name from file */ + for (sl = gs.symset_list; sl; sl = sl->next) + if (sl->idx == chosen) + break; + if (sl) { + /* free the now stale attributes */ + clear_symsetentry(which_set, TRUE); + + /* transfer only the name of the symbol set */ + gs.symset[which_set].name = dupstr(sl->name); + ready_to_switch = TRUE; + } + } else if (chosen == -1) { + /* explicit selection of defaults */ + /* free the now stale symset attributes */ + clear_symsetentry(which_set, TRUE); + } else + nothing_to_do = TRUE; + } else if (!res) { + /* The symbols file could not be accessed */ + pline("Unable to access \"%s\" file.", SYMBOLS); + return TRUE; + } else if (!gs.symset_list) { + /* The symbols file was empty */ + There("were no symbol sets found in \"%s\".", SYMBOLS); + return TRUE; + } + + /* clean up */ + while ((sl = gs.symset_list) != 0) { + gs.symset_list = sl->next; + if (sl->name) + free((genericptr_t) sl->name), sl->name = (char *) 0; + if (sl->desc) + free((genericptr_t) sl->desc), sl->desc = (char *) 0; + free((genericptr_t) sl); + } + + if (nothing_to_do) + return TRUE; + + /* Set default symbols and clear the handling value */ + if (rogueflag) + init_rogue_symbols(); + else + init_primary_symbols(); + + if (gs.symset[which_set].name) { + /* non-default symbols */ + int ok; + if (!glyphid_cache_status()) { + fill_glyphid_cache(); + } + ok = read_sym_file(which_set); + if (glyphid_cache_status()) { + free_glyphid_cache(); + } + if (ok) { + ready_to_switch = TRUE; + } else { + clear_symsetentry(which_set, TRUE); + return TRUE; + } + } + + if (ready_to_switch) + switch_symbols(TRUE); + + if (Is_rogue_level(&u.uz)) { + if (rogueflag) + assign_graphics(ROGUESET); + } else if (!rogueflag) + assign_graphics(PRIMARYSET); + apply_customizations(rogueflag ? ROGUESET : PRIMARYSET, + (do_custom_symbols | do_custom_colors)); + preference_update("symset"); + return TRUE; +} + +RESTORE_WARNING_FORMAT_NONLITERAL + +/*symbols.c*/ diff --git a/src/sys.c b/src/sys.c index 6a0c52da8..758a93667 100644 --- a/src/sys.c +++ b/src/sys.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 sys.c $NHDT-Date: 1575665952 2019/12/06 20:59:12 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.46 $ */ +/* NetHack 5.0 sys.c $NHDT-Date: 1717449153 2024/06/03 21:12:33 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.64 $ */ /* Copyright (c) Kenneth Lorber, Kensington, Maryland, 2008. */ /* NetHack may be freely redistributed. See license for details. */ @@ -15,53 +15,71 @@ at runtime by setting up a value for "DEBUGFILES" in the environment */ #endif -struct sysopt sysopt; +struct sysopt_s sysopt; void -sys_early_init() +sys_early_init(void) { + const char *p; + + /* Don't assume that these are not already set, and that it is + * safe to dupstr() without orphaning any pointers. Check them. */ + sysopt.support = (char *) 0; sysopt.recover = (char *) 0; #ifdef SYSCF sysopt.wizards = (char *) 0; #else + if (sysopt.wizards) + free((genericptr_t) sysopt.wizards); sysopt.wizards = dupstr(WIZARD_NAME); #endif + + if ((p = getenv("DEBUGFILES")) != 0) { + if (sysopt.debugfiles) + free((genericptr_t) sysopt.debugfiles); + sysopt.debugfiles = dupstr(p); + sysopt.env_dbgfl = 1; /* prevent sysconf processing from overriding */ + } else { #if defined(SYSCF) || !defined(DEBUGFILES) - sysopt.debugfiles = (char *) 0; + sysopt.debugfiles = (char *) 0; #else - sysopt.debugfiles = dupstr(DEBUGFILES); + if (sysopt.debugfiles) + free((genericptr_t) sysopt.debugfiles); + sysopt.debugfiles = dupstr(DEBUGFILES); #endif + sysopt.env_dbgfl = 0; + } + #ifdef DUMPLOG sysopt.dumplogfile = (char *) 0; #endif - sysopt.env_dbgfl = 0; /* haven't checked getenv("DEBUGFILES") yet */ sysopt.shellers = (char *) 0; sysopt.explorers = (char *) 0; sysopt.genericusers = (char *) 0; + sysopt.msghandler = (char *) 0; sysopt.maxplayers = 0; /* XXX eventually replace MAX_NR_OF_PLAYERS */ sysopt.bones_pools = 0; + sysopt.livelog = LL_NONE; /* record file */ - sysopt.persmax = PERSMAX; - sysopt.entrymax = ENTRYMAX; - sysopt.pointsmin = POINTSMIN; + sysopt.persmax = max(PERSMAX, 1); + sysopt.entrymax = max(ENTRYMAX, 10); + sysopt.pointsmin = max(POINTSMIN, 1); sysopt.pers_is_uid = PERS_IS_UID; sysopt.tt_oname_maxrank = 10; /* sanity checks */ - if (PERSMAX < 1) - sysopt.persmax = 1; - if (ENTRYMAX < 10) - sysopt.entrymax = 10; - if (POINTSMIN < 1) - sysopt.pointsmin = 1; - if (PERS_IS_UID != 0 && PERS_IS_UID != 1) + if (sysopt.pers_is_uid != 0 && sysopt.pers_is_uid != 1) panic("config error: PERS_IS_UID must be either 0 or 1"); #ifdef PANICTRACE /* panic options */ + if (sysopt.gdbpath) + free((genericptr_t) sysopt.gdbpath); sysopt.gdbpath = dupstr(GDBPATH); + if (sysopt.greppath) + free((genericptr_t) sysopt.greppath); sysopt.greppath = dupstr(GREPPATH); #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) sysopt.panictrace_gdb = 1; @@ -75,20 +93,26 @@ sys_early_init() #endif #endif #endif + sysopt.crashreporturl = NULL; sysopt.check_save_uid = 1; sysopt.check_plname = 0; sysopt.seduce = 1; /* if it's compiled in, default to on */ sysopt_seduce_set(sysopt.seduce); + sysopt.saveformat[0] = sysopt.bonesformat[0] = historical; sysopt.accessibility = 0; #ifdef WIN32 sysopt.portable_device_paths = 0; #endif + + /* help menu */ + sysopt.hideusage = 0; + return; } void -sysopt_release() +sysopt_release(void) { if (sysopt.support) free((genericptr_t) sysopt.support), sysopt.support = (char *) 0; @@ -103,38 +127,48 @@ sysopt_release() if (sysopt.debugfiles) free((genericptr_t) sysopt.debugfiles), sysopt.debugfiles = (char *) 0; + sysopt.env_dbgfl = 0; + if (sysopt.msghandler) + free((genericptr_t) sysopt.msghandler), sysopt.msghandler = (char *) 0; #ifdef DUMPLOG if (sysopt.dumplogfile) - free((genericptr_t)sysopt.dumplogfile), sysopt.dumplogfile=(char *)0; + free((genericptr_t) sysopt.dumplogfile), sysopt.dumplogfile=(char *) 0; #endif if (sysopt.genericusers) free((genericptr_t) sysopt.genericusers), - sysopt.genericusers = (char *) 0; + sysopt.genericusers = (char *) 0; if (sysopt.gdbpath) free((genericptr_t) sysopt.gdbpath), sysopt.gdbpath = (char *) 0; if (sysopt.greppath) free((genericptr_t) sysopt.greppath), sysopt.greppath = (char *) 0; +#ifdef CRASHREPORT + if (gc.crash_email) + free((genericptr_t) gc.crash_email), gc.crash_email = (char *) NULL; + if (gc.crash_name) + free((genericptr_t) gc.crash_name), gc.crash_name = (char *) NULL; +#endif + /* this one's last because it might be used in panic feedback, although none of the preceding ones are likely to trigger a controlled panic */ if (sysopt.fmtd_wizard_list) free((genericptr_t) sysopt.fmtd_wizard_list), - sysopt.fmtd_wizard_list = (char *) 0; + sysopt.fmtd_wizard_list = (char *) 0; return; } -extern const struct attack sa_yes[NATTK]; -extern const struct attack sa_no[NATTK]; +extern const struct attack c_sa_yes[NATTK]; +extern const struct attack c_sa_no[NATTK]; void -sysopt_seduce_set(val) -int val; -{ +sysopt_seduce_set( #if 0 +int val) +{ /* * Attack substitution is now done on the fly in getmattk(mhitu.c). */ - struct attack *setval = val ? sa_yes : sa_no; + struct attack *setval = val ? c_sa_yes : c_sa_no; int x; for (x = 0; x < NATTK; x++) { @@ -142,8 +176,9 @@ int val; mons[PM_SUCCUBUS].mattk[x] = setval[x]; } #else - nhUse(val); -#endif /*0*/ +int val UNUSED) +{ +#endif return; } diff --git a/src/teleport.c b/src/teleport.c index 23af60aa2..3792e02f6 100644 --- a/src/teleport.c +++ b/src/teleport.c @@ -1,18 +1,79 @@ -/* NetHack 3.6 teleport.c $NHDT-Date: 1576281515 2019/12/13 23:58:35 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.95 $ */ +/* NetHack 5.0 teleport.c $NHDT-Date: 1769342601 2026/01/25 04:03:21 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.239 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2011. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" +#define NEW_ENEXTO + +staticfn boolean goodpos_onscary(coordxy, coordxy, struct permonst *); +staticfn boolean tele_jump_ok(coordxy, coordxy, coordxy, coordxy); +staticfn boolean teleok(coordxy, coordxy, boolean); +staticfn void vault_tele(void); +staticfn boolean rloc_pos_ok(coordxy, coordxy, struct monst *); +staticfn void rloc_to_core(struct monst *, coordxy, coordxy, unsigned); +staticfn void mvault_tele(struct monst *); +staticfn boolean m_blocks_teleporting(struct monst *); +staticfn stairway *stairway_find_forwiz(boolean, boolean); + +/* does monster block others from teleporting? */ +staticfn boolean +m_blocks_teleporting(struct monst *mtmp) +{ + if (is_dlord(mtmp->data) || is_dprince(mtmp->data)) + return TRUE; + return FALSE; +} + +/* teleporting is prevented on this level for this monster? */ +boolean +noteleport_level(struct monst *mon) +{ + /* demon court in Gehennom prevent others from teleporting */ + if (In_hell(&u.uz) && !(is_dlord(mon->data) || is_dprince(mon->data))) + if (get_iter_mons(m_blocks_teleporting)) + return TRUE; -STATIC_DCL boolean FDECL(tele_jump_ok, (int, int, int, int)); -STATIC_DCL boolean FDECL(teleok, (int, int, BOOLEAN_P)); -STATIC_DCL void NDECL(vault_tele); -STATIC_DCL boolean FDECL(rloc_pos_ok, (int, int, struct monst *)); -STATIC_DCL void FDECL(mvault_tele, (struct monst *)); + /* natural no-teleport level; covetous monsters can bypass these */ + if (svl.level.flags.noteleport && !is_covetous(mon->data)) + return TRUE; + + /* wand of stasis prevents teleportation while the effect is active + (even for covetous monsters) */ + if (svl.level.flags.stasis_until >= svm.moves) + return TRUE; -/* non-null when teleporting via having read this scroll */ -STATIC_VAR struct obj *telescroll = 0; + return FALSE; +} + +/* this is an approximation of onscary() that doesn't use any 'struct monst' + fields aside from 'monst->data'; used primarily for new monster creation + and monster teleport destination, not for ordinary monster movement */ +staticfn boolean +goodpos_onscary( + coordxy x, coordxy y, + struct permonst *mptr) +{ + /* onscary() checks Angels and lawful minions; this oversimplifies */ + if (mptr->mlet == S_HUMAN || mptr->mlet == S_ANGEL + || is_rider(mptr) || unique_corpstat(mptr)) + return FALSE; + /* onscary() checks for vampshifted vampire bats/fog clouds/wolves too */ + if (IS_ALTAR(levl[x][y].typ) && mptr->mlet == S_VAMPIRE) + return TRUE; + /* scare monster scroll doesn't have any of the below restrictions, + being its own source of power */ + if (sobj_at(SCR_SCARE_MONSTER, x, y)) + return TRUE; + /* engraved Elbereth doesn't work in Gehennom or the end-game */ + if (Inhell || In_endgame(&u.uz)) + return FALSE; + /* creatures who don't (or can't) fear a written Elbereth and weren't + caught by the minions check */ + if (mptr == &mons[PM_MINOTAUR] || !haseyes(mptr)) + return FALSE; + return sengr_at("Elbereth", x, y, TRUE) ? TRUE : FALSE; +} /* * Is (x,y) a good position of mtmp? If mtmp is NULL, then is (x,y) good @@ -22,13 +83,17 @@ STATIC_VAR struct obj *telescroll = 0; * call it to generate new monster positions with fake monster structures. */ boolean -goodpos(x, y, mtmp, gpflags) -int x, y; -struct monst *mtmp; -unsigned gpflags; +goodpos( + coordxy x, coordxy y, + struct monst *mtmp, + mmflags_nht gpflags) { struct permonst *mdat = (struct permonst *) 0; - boolean ignorewater = ((gpflags & MM_IGNOREWATER) != 0); + boolean ignorewater = ((gpflags & MM_IGNOREWATER) != 0), + ignorelava = ((gpflags & MM_IGNORELAVA) != 0), + checkscary = ((gpflags & GP_CHECKSCARY) != 0), + allow_u = ((gpflags & GP_ALLOW_U) != 0), + avoid_monpos = ((gpflags & GP_AVOID_MONPOS) != 0); if (!isok(x, y)) return FALSE; @@ -39,9 +104,14 @@ unsigned gpflags; * which could be co-located and thus get restricted a bit too much. * oh well. */ - if (x == u.ux && y == u.uy - && mtmp != &youmonst && (mtmp != u.ustuck || !u.uswallow) - && (!u.usteed || mtmp != u.usteed)) + if (!allow_u) { + if (u_at(x, y) && mtmp != &gy.youmonst + && (mtmp != u.ustuck || !u.uswallow) + && (!u.usteed || mtmp != u.usteed)) + return FALSE; + } + + if (MON_AT(x, y) && avoid_monpos) return FALSE; if (mtmp) { @@ -63,45 +133,54 @@ unsigned gpflags; mdat = mtmp->data; if (is_pool(x, y) && !ignorewater) { /* [what about Breathless?] */ - if (mtmp == &youmonst) + if (mtmp == &gy.youmonst) return (Swimming || Amphibious || (!Is_waterlevel(&u.uz) + && !is_waterwall(x, y) /* water on the Plane of Water has no surface so there's no way to be on or above that */ && (Levitation || Flying || Wwalking))); else return (is_swimmer(mdat) || (!Is_waterlevel(&u.uz) - && (is_floater(mdat) || is_flyer(mdat) - || is_clinger(mdat)))); + && !is_waterwall(x, y) + && m_in_air(mtmp))); } else if (mdat->mlet == S_EEL && rn2(13) && !ignorewater) { return FALSE; - } else if (is_lava(x, y)) { + } else if (is_lava(x, y) && !ignorelava) { /* 3.6.3: floating eye can levitate over lava but it avoids that due the effect of the heat causing it to dry out */ if (mdat == &mons[PM_FLOATING_EYE]) return FALSE; - else if (mtmp == &youmonst) + else if (mtmp == &gy.youmonst) return (Levitation || Flying || (Fire_resistance && Wwalking && uarmf && uarmf->oerodeproof) - || (Upolyd && likes_lava(youmonst.data))); + || (Upolyd && likes_lava(gy.youmonst.data))); else - return (is_floater(mdat) || is_flyer(mdat) - || likes_lava(mdat)); + return (m_in_air(mtmp) || likes_lava(mdat)); } if (passes_walls(mdat) && may_passwall(x, y)) return TRUE; if (amorphous(mdat) && closed_door(x, y)) return TRUE; + /* avoid onscary() if caller has specified that restriction */ + if (checkscary && (mtmp->m_id ? onscary(x, y, mtmp) + : goodpos_onscary(x, y, mdat))) + return FALSE; } if (!accessible(x, y)) { - if (!(is_pool(x, y) && ignorewater)) + if (!(is_pool(x, y) && ignorewater) + && !(is_lava(x, y) && ignorelava)) return FALSE; } - + /* skip boulder locations for most creatures */ if (sobj_at(BOULDER, x, y) && (!mdat || !throws_rocks(mdat))) return FALSE; + /* pretend GP_AVOID_MONPOS == monster creation */ + if (avoid_monpos && is_exclusion_zone(LR_MONGEN, x, y)) + return FALSE; + return TRUE; } @@ -114,25 +193,101 @@ unsigned gpflags; * Return TRUE and the position chosen when successful, FALSE otherwise. */ boolean -enexto(cc, xx, yy, mdat) -coord *cc; -register xchar xx, yy; -struct permonst *mdat; +enexto( + coord *cc, + coordxy xx, coordxy yy, + struct permonst *mdat) +{ + return (enexto_core(cc, xx, yy, mdat, GP_CHECKSCARY) + || enexto_core(cc, xx, yy, mdat, NO_MM_FLAGS)); +} + +boolean +enexto_gpflags( + coord *cc, + coordxy xx, coordxy yy, + struct permonst *mdat, + mmflags_nht entflags) { - return enexto_core(cc, xx, yy, mdat, NO_MM_FLAGS); + return (enexto_core(cc, xx, yy, mdat, GP_CHECKSCARY | entflags) + || enexto_core(cc, xx, yy, mdat, entflags)); } +#ifdef NEW_ENEXTO + boolean -enexto_core(cc, xx, yy, mdat, entflags) -coord *cc; -xchar xx, yy; -struct permonst *mdat; -unsigned entflags; +enexto_core( + coord *cc, /* output; as close as feasible to */ + coordxy xx, coordxy yy, /* input coordinates */ + struct permonst *mdat, /* type of monster; affects whether water or + * lava or boulder spots will be considered */ + mmflags_nht entflags) /* flags for goodpos() */ +{ + coord candy[ROWNO * (COLNO - 1)]; /* enough room for every location */ + int i, nearcandyct, allcandyct; + struct monst fakemon; /* dummy monster */ + boolean allow_xx_yy = (boolean) ((entflags & GP_ALLOW_XY) != 0); + /* note: GP_ALLOW_XY isn't used by goodpos(); old enext_core() used to + mask it off to hide it from goodpos but that isn't required and we + want to keep it in case the debugpline4() gets called */ + + if (!mdat) { + debugpline0("enexto() called with null mdat"); + /* default to player's original monster type */ + mdat = &mons[u.umonster]; + } + fakemon = cg.zeromonst; + set_mon_data(&fakemon, mdat); /* set up for goodpos() */ + + /* gather candidate coordinates within 3 steps, those 1 step away in + random order first, then those 2 steps away in random order, then 3; + this will usually find a good spot without scanning the whole map */ + nearcandyct = collect_coords(candy, xx, yy, 3, CC_NO_FLAGS, + (boolean (*)(coordxy, coordxy)) 0); + for (i = 0; i < nearcandyct; ++i) { + *cc = candy[i]; + if (goodpos(cc->x, cc->y, &fakemon, entflags)) + return TRUE; + } + + /* didn't find a spot; gather coordinates for the whole map except + for itself, ordered in expanding distance from + (subsets of equal distance grouped together with order randomized) */ + allcandyct = collect_coords(candy, xx, yy, 0, CC_NO_FLAGS, + (boolean (*)(coordxy, coordxy)) 0); + /* skip first 'nearcandyct' spots, they have already been rejected; + they will occur in different random order but same overall total */ + for (i = nearcandyct; i < allcandyct; ++i) { + *cc = candy[i]; + if (goodpos(cc->x, cc->y, &fakemon, entflags)) + return TRUE; + } + + /* still didn't find a spot; maybe try itself */ + cc->x = xx, cc->y = yy; /* final value for 'cc' in case we return False */ + if (allow_xx_yy && goodpos(cc->x, cc->y, &fakemon, entflags)) + return TRUE; + + /* failed to find any acceptable spot */ + debugpline4("enexto(\"%s\",%d,%d,0x%08lx) failed", + mdat->pmnames[NEUTRAL], (int) xx, (int) yy, + (unsigned long) entflags); + return FALSE; +} + +#else /* !NEW_ENEXTO */ + +boolean +enexto_core( + coord *cc, + coordxy xx, coordxy yy, + struct permonst *mdat, + mmflags_nht entflags) { #define MAX_GOOD 15 coord good[MAX_GOOD], *good_ptr; - int x, y, range, i; - int xmin, xmax, ymin, ymax, rangemax; + coordxy x, y, range, i; + coordxy xmin, xmax, ymin, ymax, rangemax; struct monst fakemon; /* dummy monster */ boolean allow_xx_yy = (boolean) ((entflags & GP_ALLOW_XY) != 0); @@ -142,7 +297,7 @@ unsigned entflags; /* default to player's original monster type */ mdat = &mons[u.umonster]; } - fakemon = zeromonst; + fakemon = cg.zeromonst; set_mon_data(&fakemon, mdat); /* set up for goodpos */ /* used to use 'if (range > ROWNO && range > COLNO) return FALSE' below, @@ -205,72 +360,82 @@ unsigned entflags; if (allow_xx_yy && goodpos(xx, yy, &fakemon, entflags)) { return TRUE; /* 'cc' is set */ } else { - debugpline3("enexto(\"%s\",%d,%d) failed", mdat->mname, xx, yy); + debugpline3("enexto(\"%s\",%d,%d) failed", + mdat->pmnames[NEUTRAL], (int) xx, (int) yy); return FALSE; } } full: /* we've got between 1 and SIZE(good) candidates; choose one */ - i = rn2((int) (good_ptr - good)); + i = (coordxy) rn2((int) (good_ptr - good)); cc->x = good[i].x; cc->y = good[i].y; return TRUE; +#undef MAX_GOOD } +#endif /* ?NEW_ENEXTO */ + /* * Check for restricted areas present in some special levels. (This might * need to be augmented to allow deliberate passage in wizard mode, but * only for explicitly chosen destinations.) */ -STATIC_OVL boolean -tele_jump_ok(x1, y1, x2, y2) -int x1, y1, x2, y2; +staticfn boolean +tele_jump_ok(coordxy x1, coordxy y1, coordxy x2, coordxy y2) { if (!isok(x2, y2)) return FALSE; - if (dndest.nlx > 0) { + if (svd.dndest.nlx > 0) { /* if inside a restricted region, can't teleport outside */ - if (within_bounded_area(x1, y1, dndest.nlx, dndest.nly, dndest.nhx, - dndest.nhy) - && !within_bounded_area(x2, y2, dndest.nlx, dndest.nly, - dndest.nhx, dndest.nhy)) + if (within_bounded_area(x1, y1, svd.dndest.nlx, svd.dndest.nly, + svd.dndest.nhx, svd.dndest.nhy) + && !within_bounded_area(x2, y2, svd.dndest.nlx, svd.dndest.nly, + svd.dndest.nhx, svd.dndest.nhy)) return FALSE; /* and if outside, can't teleport inside */ - if (!within_bounded_area(x1, y1, dndest.nlx, dndest.nly, dndest.nhx, - dndest.nhy) - && within_bounded_area(x2, y2, dndest.nlx, dndest.nly, dndest.nhx, - dndest.nhy)) + if (!within_bounded_area(x1, y1, svd.dndest.nlx, svd.dndest.nly, + svd.dndest.nhx, svd.dndest.nhy) + && within_bounded_area(x2, y2, svd.dndest.nlx, svd.dndest.nly, + svd.dndest.nhx, svd.dndest.nhy)) return FALSE; } - if (updest.nlx > 0) { /* ditto */ - if (within_bounded_area(x1, y1, updest.nlx, updest.nly, updest.nhx, - updest.nhy) - && !within_bounded_area(x2, y2, updest.nlx, updest.nly, - updest.nhx, updest.nhy)) + if (svu.updest.nlx > 0) { /* ditto */ + if (within_bounded_area(x1, y1, svu.updest.nlx, svu.updest.nly, + svu.updest.nhx, svu.updest.nhy) + && !within_bounded_area(x2, y2, svu.updest.nlx, svu.updest.nly, + svu.updest.nhx, svu.updest.nhy)) return FALSE; - if (!within_bounded_area(x1, y1, updest.nlx, updest.nly, updest.nhx, - updest.nhy) - && within_bounded_area(x2, y2, updest.nlx, updest.nly, updest.nhx, - updest.nhy)) + if (!within_bounded_area(x1, y1, svu.updest.nlx, svu.updest.nly, + svu.updest.nhx, svu.updest.nhy) + && within_bounded_area(x2, y2, svu.updest.nlx, svu.updest.nly, + svu.updest.nhx, svu.updest.nhy)) return FALSE; } return TRUE; } -STATIC_OVL boolean -teleok(x, y, trapok) -register int x, y; -boolean trapok; +staticfn boolean +teleok(coordxy x, coordxy y, boolean trapok) { if (!trapok) { - /* allow teleportation onto vibrating square, it's not a real trap */ + /* allow teleportation onto vibrating square, it's not a real trap; + also allow pits and holes if levitating or flying */ struct trap *trap = t_at(x, y); - if (trap && trap->ttyp != VIBRATING_SQUARE) + if (!trap) + trapok = TRUE; + else if (trap->ttyp == VIBRATING_SQUARE) + trapok = TRUE; + else if ((is_pit(trap->ttyp) || is_hole(trap->ttyp)) + && (Levitation || Flying)) + trapok = TRUE; + + if (!trapok) return FALSE; } - if (!goodpos(x, y, &youmonst, 0)) + if (!goodpos(x, y, &gy.youmonst, 0)) return FALSE; if (!tele_jump_ok(u.ux, u.uy, x, y)) return FALSE; @@ -280,11 +445,12 @@ boolean trapok; } void -teleds(nux, nuy, allow_drag) -register int nux, nuy; -boolean allow_drag; +teleds(coordxy nux, coordxy nuy, int teleds_flags) { - boolean ball_active, ball_still_in_range; + unsigned was_swallowed; + boolean ball_active, ball_still_in_range = FALSE, + allow_drag = (teleds_flags & TELEDS_ALLOW_DRAG) != 0, + is_teleport = (teleds_flags & TELEDS_TELEPORT) != 0; struct monst *vault_guard = vault_occupied(u.urooms) ? findgd() : 0; if (u.utraptype == TT_BURIEDBALL) { @@ -292,13 +458,16 @@ boolean allow_drag; buried_ball_to_punishment(); } ball_active = (Punished && uball->where != OBJ_FREE); - ball_still_in_range = FALSE; + if (!ball_active + || near_capacity() > SLT_ENCUMBER + || distmin(u.ux, u.uy, nux, nuy) > 1) + allow_drag = FALSE; /* If they have to move the ball, then drag if allow_drag is true; * otherwise they are teleporting, so unplacebc(). * If they don't have to move the ball, then always "drag" whether or * not allow_drag is true, because we are calling that function, not - * to drag, but to move the chain. *However* there are some dumb + * to drag, but to move the chain. *However*, there are some dumb * special cases: * 0 0 * _X move east -----> X_ @@ -310,63 +479,53 @@ boolean allow_drag; * rock in the way), in which case it teleports the ball on its own. */ if (ball_active) { - if (!carried(uball) && distmin(nux, nuy, uball->ox, uball->oy) <= 2) { + if (!carried(uball) && distmin(nux, nuy, uball->ox, uball->oy) <= 2) ball_still_in_range = TRUE; /* don't have to move the ball */ - } else { - /* have to move the ball */ - if (!allow_drag || distmin(u.ux, u.uy, nux, nuy) > 1) { - /* we should not have dist > 1 and allow_drag at the same - * time, but just in case, we must then revert to teleport. - */ - allow_drag = FALSE; - unplacebc(); - } - } + else if (!allow_drag) + unplacebc(); /* have to move the ball */ } reset_utrap(FALSE); - u.ustuck = 0; + was_swallowed = u.uswallow; /* set_ustuck(Null) clears uswallow */ + set_ustuck((struct monst *) 0); u.ux0 = u.ux; u.uy0 = u.uy; - if (!hideunder(&youmonst) && youmonst.data->mlet == S_MIMIC) { + if (!hideunder(&gy.youmonst) && gy.youmonst.data->mlet == S_MIMIC) { /* mimics stop being unnoticed */ - youmonst.m_ap_type = M_AP_NOTHING; + gy.youmonst.m_ap_type = M_AP_NOTHING; } - if (u.uswallow) { - u.uswldtim = u.uswallow = 0; - if (Punished && !ball_active) { - /* ensure ball placement, like unstuck */ - ball_active = TRUE; - allow_drag = FALSE; + if (was_swallowed) { + if (Punished) { /* ball&chain are off map while swallowed */ + ball_active = TRUE; /* to put chain and non-carried ball on map */ + ball_still_in_range = allow_drag = FALSE; /* (redundant) */ } docrt(); } - if (ball_active) { - if (ball_still_in_range || allow_drag) { - int bc_control; - xchar ballx, bally, chainx, chainy; - boolean cause_delay; - - if (drag_ball(nux, nuy, &bc_control, &ballx, &bally, &chainx, - &chainy, &cause_delay, allow_drag)) { - move_bc(0, bc_control, ballx, bally, chainx, chainy); - } else { - /* dragging fails if hero is encumbered beyond 'burdened' */ - allow_drag = FALSE; /* teleport b&c to hero's new spot */ + if (ball_active && (ball_still_in_range || allow_drag)) { + int bc_control; + coordxy ballx, bally, chainx, chainy; + boolean cause_delay; + + if (drag_ball(nux, nuy, &bc_control, &ballx, &bally, &chainx, + &chainy, &cause_delay, allow_drag)) + move_bc(0, bc_control, ballx, bally, chainx, chainy); + else { + /* dragging fails if hero is encumbered beyond 'burdened' */ + /* uball might've been cleared via drag_ball -> spoteffects -> + dotrap -> magic trap unpunishment */ + ball_active = (Punished && uball->where != OBJ_FREE); + if (ball_active) unplacebc(); /* to match placebc() below */ - } } } + /* must set u.ux, u.uy after drag_ball(), which may need to know the old position if allow_drag is true... */ u_on_newpos(nux, nuy); /* set u., usteed->; cliparound() */ fill_pit(u.ux0, u.uy0); - if (ball_active) { - if (!ball_still_in_range && !allow_drag) - placebc(); - } - initrack(); /* teleports mess up tracking monsters without this */ + if (ball_active && uchain && uchain->where == OBJ_FREE) + placebc(); /* put back the ball&chain if they were taken off map */ update_player_regions(); /* * Make sure the hero disappears from the old location. This will @@ -376,23 +535,21 @@ boolean allow_drag; */ newsym(u.ux0, u.uy0); see_monsters(); - vision_full_recalc = 1; + gv.vision_full_recalc = 1; nomul(0); + notice_mon_off(); vision_recalc(0); /* vision before effects */ + + /* this used to take place sooner, but if a --More-- prompt was issued + then the old map display was shown instead of the new one */ + if (is_teleport && flags.verbose) + You("materialize in %s location!", + (nux == u.ux0 && nuy == u.uy0) ? "the same" : "a different"); /* if terrain type changes, levitation or flying might become blocked or unblocked; might issue message, so do this after map+vision has been updated for new location instead of right after u_on_newpos() */ if (levl[u.ux][u.uy].typ != levl[u.ux0][u.uy0].typ) switch_terrain(); - if (telescroll) { - /* when teleporting by scroll, we need to handle discovery - now before getting feedback about any objects at our - destination since we might land on another such scroll */ - if (distu(u.ux0, u.uy0) >= 16 || !couldsee(u.ux0, u.uy0)) - learnscroll(telescroll); - else - telescroll = 0; /* no discovery by scrolltele()'s caller */ - } /* sequencing issue: we want guard's alarm, if any, to occur before room entry message, if any, so need to check for vault exit prior to spoteffects; but spoteffects() sets up new value for u.urooms @@ -410,45 +567,225 @@ boolean allow_drag; /* possible shop entry message comes after guard's shrill whistle */ spoteffects(TRUE); invocation_message(); + notice_mon_on(); + notice_all_mons(TRUE); + return; } +/* make a list of coordinates in expanding distance from ; + return value is number of coordinates inserted into ccc[] */ +int +collect_coords( + coord *ccc, /* pointer to array of at least size ROWNO*(COLNO-1) */ + coordxy cx, coordxy cy, /* center point, not necessarily */ + int maxradius, /* how far from center to go collecting spots; + * 0 means collect entire map */ + unsigned cc_flags, /* incl_center: put in output list + * (provided that it passes filter, if any); + * unshuffled: keep output in collection order; + * ring_pairs: shuffle pairs of rings together + * instead of keeping each ring distinct; + * skip_mons: reject occupied spots; + * skip_inaccs: reject !ZAP_POS() spots */ + boolean (*filter)(coordxy, coordxy)) /* if Null, no filtering */ +{ + coordxy x, y, lox, hix, loy, hiy; + int radius, rowrange, colrange, k, n = 0; + coord cc, *passcc = NULL; + boolean newpass, passend, + /* is a candidate? */ + include_cxcy = (cc_flags & CC_INCL_CENTER) != 0, + /* flag is negative; turn local variable into positive */ + scramble = (cc_flags & CC_UNSHUFFLED) == 0, + /* if scrambling, shuffle rings 1+2, 3+4, &c together */ + ring_pairs = (scramble && (cc_flags & CC_RING_PAIRS) != 0), + /* exclude locations containing monsters from output */ + skip_mons = (cc_flags & CC_SKIP_MONS) != 0, + /* exclude !ZAP_POS() locations from output; allows pools+lava */ + skip_inaccessible = (cc_flags & CC_SKIP_INACCS) != 0; + int result = 0; + + rowrange = (cy < ROWNO / 2) ? (ROWNO - 1 - cy) : cy; + colrange = (cx < COLNO / 2) ? (COLNO - 1 - cx) : cx; + k = max(rowrange, colrange); + /* if no radius limit has been specified, cover the whole map */ + if (!maxradius) + maxradius = k; + else + maxradius = min(maxradius, k); + /* + * We operate on an expanding radius around the center, optionally + * starting with the center spot itself, and shuffle the edges of + * each expanding square or "ring". (So all 1's are shuffled at + * end of the pass for radius==1, then all 2's at end of radius==2, + * and so on. Shuffling of each ring doesn't encroach on any of + * the others except when ring_pairs mode is specified.) + * + * Diagram of first three rings (four if 'include_cxcy' is specified) + * rings unshuffled output sample shuffled output (varies) + * 3333333 25 26 . . . . 31 33 29 . . . . 44 + * 3222223 32 9 10 11 12 13 33 35 22 16 14 24 13 40 + * 3211123 34 14 1 2 3 15 . 38 20 2 8 3 15 . + * 3210123 . 16 4 0 5 17 . . 11 1 0 6 9 . + * 3211123 . 18 6 7 8 19 39 . 19 5 7 4 12 25 + * 3222223 40 20 21 22 23 24 41 43 10 21 17 23 18 27 + * 3333333 42 . . . . 47 48 37 . . . . 30 36 + * . == entry not shown to reduce clutter when viewing inner portion. + * + * The digits under 'rings' are ring number, which is also distmin + * from center, indicating the order in which sets of spots are + * evaluated. Output gets collected in expanding rings. For the + * two output grids, the number shown represents the position in the + * returned list of coordinates. When shuffling while ring_pairs is + * specified, the 1's and 2's will be mixed together, the 3's and + * (unshown) 4's will be mixed together, and so forth. + * + * If caller processes the output list in order, the closest viable + * spot will be chosen. If a completely random spot is preferred, + * the list can be requested to be unscrambled and then the caller + * can shuffle it, overriding the collection rings. A filter function + * could be used to skip everything after the first acceptable spot. + * 'ring_pairs' mode allows for choosing a very close spot that isn't + * immediately adjacent to the center point, useful for emergency + * teleport to not always end up at the closest possible spot. + * + * TODO: + * Redo filter interface to have caller pass in a context cookie + * that can be passed through to filter so that it could have access + * to more info than just . Also add a way to stop collecting + * when an optimal value is found, without checking and skipping the + * rest of the map. + */ + for (radius = include_cxcy ? 0 : 1; radius <= maxradius; ++radius) { + if (!ring_pairs) { + newpass = passend = TRUE; + } else { + /* 0 (if include_cxcy) and maxradius override odd/even */ + newpass = ((radius % 2) != 0 || radius == 0); /* odd */ + passend = ((radius % 2) == 0 || radius == maxradius); /* even */ + } + if (newpass || !passcc) { /* !passcc is redundant but used to fend + * off analyzers thinking use of passcc + * below might occur while still Null */ + passcc = ccc; /* start of output entries for current radius (or + * first half of radius pair depending on flags) */ + n = 0; /* number of entries for passcc; used for shuffling */ + } + lox = cx - radius, hix = cx + radius; + loy = cy - radius, hiy = cy + radius; + for (y = max(loy, 0); y <= hiy; ++y) { + if (y > ROWNO - 1) + break; /* done with collection for current radius */ + for (x = max(lox, 1); x <= hix; ++x) { /* column 0 is not used */ + if (x > COLNO - 1) + break; /* advance to next 'y' */ + if (x != lox && x != hix && y != loy && y != hiy) + continue; /* not any edge of ring/square */ + if ((skip_mons && m_at(x, y)) + /* note: !ACCESSIBLE() would reject water and lava; + !ZAP_POS() accepts them; caller needs to handle such */ + || (skip_inaccessible && !ZAP_POS(levl[x][y].typ))) + continue; /* quick filters */ + if (filter && !(*filter)(x, y)) + continue; + cc.x = x, cc.y = y; + *ccc++ = cc; + ++n; + ++result; + } + } + if (scramble && passend) { + /* shuffle entries gathered for current radius (or pair) */ + while (n > 1) { + k = rn2(n); /* 0..n-1 */ + if (k) { /* swap [k] with [0] when k is 1..n-1 */ + cc = passcc[0]; + passcc[0] = passcc[k]; + passcc[k] = cc; + } + ++passcc; /* passcc[0] has reached its final place */ + --n; /* and become exempt from further shuffling */ + } + } + } /* radius loop */ + debugpline4("collect_coords(,%d,%d,%d,,)=%d", cx, cy, maxradius, result); + return result; +} + +/* [try to] teleport hero to a safe spot */ boolean -safe_teleds(allow_drag) -boolean allow_drag; +safe_teleds(int teleds_flags) { - register int nux, nuy, tcnt = 0; + coordxy nux, nuy; + unsigned cc_flags; + coord candy[ROWNO * (COLNO - 1)], backupspot; + int tcnt, candycount; - do { + /* + * This used to try random locations up to 400 times, with first 200 + * tries disallowing trap locations and remaining 200 accepting such. + * On levels without many accessible locations (either due to being + * mostly stone or high monster population) it could fail to find a + * spot. + * + * Now it tries completely randomly only 40 times, all disallowing + * traps, then resorts to checking the entire map, near hero's spot + * first then expanding out from there. If no non-trap spot is found, + * first trap spot is used. + */ + for (tcnt = 0; tcnt < 40; ++tcnt) { nux = rnd(COLNO - 1); nuy = rn2(ROWNO); - } while (!teleok(nux, nuy, (boolean) (tcnt > 200)) && ++tcnt <= 400); + if (teleok(nux, nuy, FALSE)) { + teleds(nux, nuy, teleds_flags); + return TRUE; + } + } - if (tcnt <= 400) { - teleds(nux, nuy, allow_drag); + /* get a shuffled list of candidate locations, starting with spots + 1 or 2 steps from hero, then 3 or 4 steps, then 5 or 6, on up */ + cc_flags = CC_RING_PAIRS | CC_SKIP_MONS; + if (!Passes_walls) + cc_flags |= CC_SKIP_INACCS; + candycount = collect_coords(candy, u.ux, u.uy, 0, cc_flags, + (boolean (*)(coordxy, coordxy)) 0); + backupspot.x = backupspot.y = 0; + /* skip trap locations via teleok(,,FALSE) but remember first + encountered trap spot that is acceptable to teleok(,,TRUE) */ + for (tcnt = 0; tcnt < candycount; ++tcnt) { + nux = candy[tcnt].x, nuy = candy[tcnt].y; + if (teleok(nux, nuy, FALSE)) { + teleds(nux, nuy, teleds_flags); + return TRUE; + } + if (!backupspot.x && t_at(nux, nuy) && teleok(nux, nuy, TRUE)) + backupspot.x = nux, backupspot.y = nuy; + } + /* no non-trap spot found; if we skipped a viable trap spot, use it */ + if (backupspot.x) { + teleds(backupspot.x, backupspot.y, teleds_flags); return TRUE; - } else - return FALSE; + } + return FALSE; } -STATIC_OVL void -vault_tele() +staticfn void +vault_tele(void) { - register struct mkroom *croom = search_special(VAULT); + struct mkroom *croom = search_special(VAULT); coord c; - if (croom && somexy(croom, &c) && teleok(c.x, c.y, FALSE)) { - teleds(c.x, c.y, FALSE); + if (croom && somexyspace(croom, &c) && teleok(c.x, c.y, FALSE)) { + teleds(c.x, c.y, TELEDS_TELEPORT); return; } tele(); } boolean -teleport_pet(mtmp, force_it) -register struct monst *mtmp; -boolean force_it; +teleport_pet(struct monst *mtmp, boolean force_it) { - register struct obj *otmp; + struct obj *otmp; if (mtmp == u.usteed) return FALSE; @@ -472,30 +809,53 @@ boolean force_it; return TRUE; } +/* teleport to random pet, if valid location next to it */ +void +tele_to_rnd_pet(void) +{ + struct monst *mtmp, *pet = (struct monst *) 0; + int cnt = 0; + + if (noteleport_level(&gy.youmonst)) { + impossible("%s", "attempt to teleport hero to be near a pet" + " on no-teleport level"); + return; + } + + for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) + if (!DEADMONSTER(mtmp) && mtmp->mtame && !mon_offmap(mtmp)) { + cnt++; + if (!rn2(cnt)) + pet = mtmp; + } + if (pet && !m_next2u(pet)) { + coordxy tx = pet->mx + rn2(3) - 1, + ty = pet->my + rn2(3) - 1; + + if (isok(tx, ty) && teleok(tx, ty, FALSE)) + teleds(tx, ty, TELEDS_TELEPORT); + } +} + /* teleport the hero via some method other than scroll of teleport */ void -tele() +tele(void) { - (void) scrolltele((struct obj *) 0); + scrolltele((struct obj *) 0); } -/* teleport the hero; return true if scroll of teleportation should become - discovered; teleds() will usually do the actual discovery, since the - outcome sometimes depends upon destination and discovery needs to be - performed before arrival, in case we land on another teleport scroll */ -boolean -scrolltele(scroll) -struct obj *scroll; +/* teleport the hero; usually discover scroll of teleportation if via scroll */ +void +scrolltele(struct obj *scroll) { coord cc; - boolean result = FALSE; /* don't learn scroll */ /* Disable teleportation in stronghold && Vlad's Tower */ - if (level.flags.noteleport) { - if (!wizard) { - pline("A mysterious force prevents you from teleporting!"); - return TRUE; - } + if (noteleport_level(&gy.youmonst) && !wizard) { + pline("A mysterious force prevents you from teleporting!"); + if (scroll) + learnscroll(scroll); /* this is obviously a teleport scroll */ + return; } /* don't show trap if "Sorry..." */ @@ -504,10 +864,13 @@ struct obj *scroll; if ((u.uhave.amulet || On_W_tower_level(&u.uz)) && !rn2(3)) { You_feel("disoriented for a moment."); - if (!wizard || yn("Override?") != 'y') - return FALSE; + /* don't discover the scroll [at least not yet for wizard override]; + disorientation doesn't reveal that this is a teleport attempt */ + if (!wizard || y_n("Override?") != 'y') + return; } - if ((Teleport_control && !Stunned) || wizard) { + if (((Teleport_control || (scroll && scroll->blessed)) && !Stunned) + || wizard) { if (unconscious()) { pline("Being unconscious, you cannot control your teleport."); } else { @@ -517,42 +880,43 @@ struct obj *scroll; if (u.usteed) Sprintf(eos(whobuf), " and %s", mon_nam(u.usteed)); pline("Where do %s want to be teleported?", whobuf); + if (scroll) + learnscroll(scroll); cc.x = u.ux; cc.y = u.uy; + if (isok(iflags.travelcc.x, iflags.travelcc.y)) { + /* The player showed some interest in traveling here; + * pre-suggest this coordinate. */ + cc = iflags.travelcc; + } if (getpos(&cc, TRUE, "the desired position") < 0) - return TRUE; /* abort */ + return; /* abort */ /* possible extensions: introduce a small error if magic power is low; allow transfer to solid rock */ if (teleok(cc.x, cc.y, FALSE)) { /* for scroll, discover it regardless of destination */ - if (scroll) - learnscroll(scroll); - teleds(cc.x, cc.y, FALSE); - return TRUE; + teleds(cc.x, cc.y, TELEDS_TELEPORT); + if (u_at(iflags.travelcc.x, iflags.travelcc.y)) + iflags.travelcc.x = iflags.travelcc.y = 0; + return; } pline("Sorry..."); - result = TRUE; } - } else if (scroll && scroll->blessed) { - /* (this used to be handled in seffects()) */ - if (yn("Do you wish to teleport?") == 'n') - return TRUE; - result = TRUE; } - telescroll = scroll; - (void) safe_teleds(FALSE); - /* teleds() will leave telescroll intact iff random destination - is far enough away for scroll discovery to be warranted */ - if (telescroll) - result = TRUE; - telescroll = 0; /* reset */ - return result; + /* we used to suppress discovery if hero teleported to a nearby + spot which was already within view, but now there is always a + "materialize" message regardless of how far you teleported so + discovery of scroll type is unconditional */ + if (scroll) + learnscroll(scroll); + + (void) safe_teleds(TELEDS_TELEPORT); } -/* ^T command; 'm ^T' == choose among several teleport modes */ +/* the #teleport command; 'm ^T' == choose among several teleport modes */ int -dotelecmd() +dotelecmd(void) { long save_HTele, save_ETele; int res, added, hidden; @@ -566,7 +930,7 @@ dotelecmd() /* normal mode; ignore 'm' prefix if it was given */ if (!wizard) - return dotele(FALSE); + return dotele(FALSE) ? ECMD_TIME : ECMD_OK; added = hidden = NOOP_SPELL; save_HTele = HTeleportation, save_ETele = ETeleportation; @@ -601,17 +965,17 @@ dotelecmd() menu_item *picks = (menu_item *) 0; anything any; winid win; - int i, tmode; + int i, tmode, clr = NO_COLOR; win = create_nhwindow(NHW_MENU); - start_menu(win); - any = zeroany; + start_menu(win, MENU_BEHAVE_STANDARD); + any = cg.zeroany; for (i = 0; i < SIZE(tports); ++i) { any.a_int = (int) tports[i].menulet; - add_menu(win, NO_GLYPH, &any, (char) any.a_int, 0, ATR_NONE, - tports[i].menudesc, - (tports[i].menulet == 'w') ? MENU_SELECTED - : MENU_UNSELECTED); + add_menu(win, &nul_glyphinfo, &any, (char) any.a_int, 0, + ATR_NONE, clr, tports[i].menudesc, + (tports[i].menulet == 'w') ? MENU_ITEMFLAGS_SELECTED + : MENU_ITEMFLAGS_NONE); } end_menu(win, "Which way do you want to teleport?"); i = select_menu(win, PICK_ONE, &picks); @@ -626,7 +990,7 @@ dotelecmd() /* preselected one was explicitly chosen and got toggled off */ tmode = 'w'; } else { /* ESC */ - return 0; + return ECMD_OK; } switch (tmode) { case 'n': @@ -658,58 +1022,67 @@ dotelecmd() /* can't both be non-NOOP so addition will yield the non-NOOP one */ (void) tport_spell(added + hidden - NOOP_SPELL); - return res; + return res ? ECMD_TIME : ECMD_OK; +#undef NOOP_SPELL +#undef HIDE_SPELL +#undef ADD_SPELL +#undef UNHIDESPELL +#undef REMOVESPELL } int -dotele(break_the_rules) -boolean break_the_rules; /* True: wizard mode ^T */ +dotele( + boolean break_the_rules) /* True: wizard mode ^T */ { struct trap *trap; const char *cantdoit; boolean trap_once = FALSE; trap = t_at(u.ux, u.uy); - if (trap && (!trap->tseen || trap->ttyp != TELEP_TRAP)) + if (trap && !trap->tseen) trap = 0; if (trap) { - trap_once = trap->once; /* trap may get deleted, save this */ - if (trap->once) { - pline("This is a vault teleport, usable once only."); - if (yn("Jump in?") == 'n') { - trap = 0; - } else { - deltrap(trap); - newsym(u.ux, u.uy); + if (trap->ttyp == LEVEL_TELEP && trap->tseen) { + if (y_n("There is a level teleporter here. Trigger it?") == 'y') { + level_tele_trap(trap, FORCETRAP); + /* deliberate jumping will always take time even if it doesn't + * work */ + return 1; + } else + trap = 0; /* continue with normal horizontal teleport */ + } else if (trap->ttyp == TELEP_TRAP) { + trap_once = trap->once; /* trap may get deleted, save this */ + if (trap->once) { + pline("This is a vault teleport, usable once only."); + if (y_n("Jump in?") == 'n') { + trap = 0; + } else { + deltrap(trap); + newsym(u.ux, u.uy); + } } - } - if (trap) - You("%s onto the teleportation trap.", - locomotion(youmonst.data, "jump")); + if (trap) + You("%s onto the teleportation trap.", u_locomotion("jump")); + } else + trap = 0; } - if (!trap) { + if (!trap && !break_the_rules) { boolean castit = FALSE; - register int sp_no = 0, energy = 0; + int energy = 0; if (!Teleportation || (u.ulevel < (Role_if(PM_WIZARD) ? 8 : 12) - && !can_teleport(youmonst.data))) { - /* Try to use teleport away spell. - Prior to 3.6.2 this used to require that you know the spellbook - (probably just intended as an optimization to skip the - lookup loop) but it is possible to know and cast a spell - after forgetting its book due to amnesia. */ - for (sp_no = 0; sp_no < MAXSPELL; sp_no++) - if (spl_book[sp_no].sp_id == SPE_TELEPORT_AWAY) - break; + && !can_teleport(gy.youmonst.data))) { + /* Try to use teleport away spell. */ + int knownsp = known_spell(SPE_TELEPORT_AWAY); + /* casting isn't inhibited by being Stunned (...it ought to be) */ - castit = (sp_no < MAXSPELL && !Confusion); + castit = (knownsp >= spe_Fresh && !Confusion); if (!castit && !break_the_rules) { - You("%s.", - !Teleportation ? ((sp_no < MAXSPELL) - ? "can't cast that spell" - : "don't know that spell") - : "are not able to teleport at will"); + You("%s.", (!Teleportation ? ((knownsp != spe_Unknown) + ? "can't cast that spell" + : "don't know that spell") + : "are not able to teleport at will")); return 0; } } @@ -723,6 +1096,10 @@ boolean break_the_rules; /* True: wizard mode ^T */ but they both yield the same result.] */ #define spellev(spell_otyp) ((int) objects[spell_otyp].oc_level) energy = 5 * spellev(SPE_TELEPORT_AWAY); +#undef spelllev +#if 0 + /* the addition of !break_the_rules to the outer if-block in + 1ada454f rendered this dead code */ if (break_the_rules) { if (!castit) energy = 0; @@ -732,8 +1109,10 @@ boolean break_the_rules; /* True: wizard mode ^T */ the extra energy is spent even if that results in not having enough to cast (which also uses the move) */ else if (u.uen < energy) - u.uen = energy; - } else if (u.uhunger <= 10) { + energy = u.uen; + } else +#endif + if (u.uhunger <= 10) { cantdoit = "are too weak from hunger"; } else if (ACURR(A_STR) < 4) { cantdoit = "lack the strength"; @@ -752,22 +1131,26 @@ boolean break_the_rules; /* True: wizard mode ^T */ if (castit) { /* energy cost is deducted in spelleffects() */ exercise(A_WIS, TRUE); - if (spelleffects(sp_no, TRUE)) + if ((spelleffects(SPE_TELEPORT_AWAY, TRUE, FALSE) & ECMD_TIME)) return 1; else if (!break_the_rules) return 0; } else { /* bypassing spelleffects(); apply energy cost directly */ u.uen -= energy; - context.botl = 1; + disp.botl = TRUE; } } if (next_to_u()) { - if (trap && trap_once) + if (trap && trap_once) { vault_tele(); - else + } else if (trap && isok(trap->teledest.x, trap->teledest.y)) { + teleds(trap->teledest.x, trap->teledest.y, TELEDS_TELEPORT); + } else { + iflags.travelcc.x = iflags.travelcc.y = 0; tele(); + } (void) next_to_u(); } else { You("%s", shudder_for_moment); @@ -779,16 +1162,26 @@ boolean break_the_rules; /* True: wizard mode ^T */ } void -level_tele() +level_tele(void) { - register int newlev; + static const char get_there_from[] = "get there from %s."; + int newlev; d_level newlevel; const char *escape_by_flying = 0; /* when surviving dest of -N */ char buf[BUFSZ]; boolean force_dest = FALSE; - if (iflags.debug_fuzzer) - goto random_levtport; + if (iflags.debug_fuzzer) { + do { + newlevel.dnum = rn2(svn.n_dgns); + } while (newlevel.dnum == astral_level.dnum + || svd.dungeons[newlevel.dnum].flags.unconnected + || !svd.dungeons[newlevel.dnum].num_dunlevs); + newlevel.dlevel = 1 + rn2(dunlevs_in_dungeon(&newlevel)); + assign_level(&u.ucamefrom, &u.uz); + schedule_goto(&newlevel, UTOTYPE_NONE, (char *) 0, (char *) 0); + return; + } if ((u.uhave.amulet || In_endgame(&u.uz) || In_sokoban(&u.uz)) && !wizard) { You_feel("very disoriented for a moment."); @@ -817,21 +1210,17 @@ level_tele() the previous input was invalid so don't use it as getlin()'s preloaded default answer */ getlin(qbuf, buf); - if (!strcmp(buf, "\033")) { /* cancelled */ - if (Confusion && rnl(5)) { - pline("Oops..."); - goto random_levtport; - } - return; - } else if (!strcmp(buf, "*")) { + if (!strcmp(buf, "*")) { goto random_levtport; } else if (Confusion && rnl(5)) { pline("Oops..."); goto random_levtport; + } else if (!strcmp(buf, "\033")) { /* cancelled */ + return; } if (wizard && !strcmp(buf, "?")) { schar destlev; - xchar destdnum; + xint16 destdnum; levTport_menu: destlev = 0; @@ -868,25 +1257,25 @@ level_tele() if (ynq("Go to Nowhere. Are you sure?") != 'y') return; You("%s in agony as your body begins to warp...", - is_silent(youmonst.data) ? "writhe" : "scream"); + is_silent(gy.youmonst.data) ? "writhe" : "scream"); display_nhwindow(WIN_MESSAGE, FALSE); You("cease to exist."); - if (invent) + if (gi.invent) Your("possessions land on the %s with a thud.", surface(u.ux, u.uy)); - killer.format = NO_KILLER_PREFIX; - Strcpy(killer.name, "committed suicide"); + svk.killer.format = NO_KILLER_PREFIX; + Strcpy(svk.killer.name, "committed suicide"); done(DIED); pline("An energized cloud of dust begins to coalesce."); Your("body rematerializes%s.", - invent ? ", and you gather up all your possessions" : ""); + gi.invent ? ", and you gather up all your possessions" : ""); return; } /* if in Knox and the requested level > 0, stay put. * we let negative values requests fall into the "heaven" loop. */ - if (Is_knox(&u.uz) && newlev > 0 && !force_dest) { + if (single_level_branch(&u.uz) && newlev > 0 && !force_dest) { You1(shudder_for_moment); return; } @@ -896,10 +1285,10 @@ level_tele() * status line, and consequently it should be incremented to * the value of the logical depth of the target level. * - * we let negative values requests fall into the "heaven" loop. + * we let negative values requests fall into the "heaven" handling. */ if (In_quest(&u.uz) && newlev > 0) - newlev = newlev + dungeons[u.uz.dnum].depth_start - 1; + newlev = newlev + svd.dungeons[u.uz.dnum].depth_start - 1; } else { /* involuntary level tele */ random_levtport: newlev = random_teleport_level(); @@ -920,33 +1309,34 @@ level_tele() int llimit = dunlevs_in_dungeon(&u.uz); if (newlev >= 0 || newlev <= -llimit) { - You_cant("get there from here."); + You_cant(get_there_from, "here"); return; } newlevel.dnum = u.uz.dnum; newlevel.dlevel = llimit + newlev; - schedule_goto(&newlevel, FALSE, FALSE, 0, (char *) 0, (char *) 0); + schedule_goto(&newlevel, UTOTYPE_NONE, (char *) 0, (char *) 0); return; } - killer.name[0] = 0; /* still alive, so far... */ + svk.killer.name[0] = 0; /* still alive, so far... */ if (iflags.debug_fuzzer && newlev < 0) goto random_levtport; if (newlev < 0 && !force_dest) { if (*u.ushops0) { /* take unpaid inventory items off of shop bills */ - in_mklev = TRUE; /* suppress map update */ + gi.in_mklev = TRUE; /* suppress map update */ u_left_shop(u.ushops0, TRUE); /* you're now effectively out of the shop */ *u.ushops0 = *u.ushops = '\0'; - in_mklev = FALSE; + gi.in_mklev = FALSE; } if (newlev <= -10) { You("arrive in heaven."); + SetVoice((struct monst *) 0, 0, 80, voice_deity); verbalize("Thou art early, but we'll admit thee."); - killer.format = NO_KILLER_PREFIX; - Strcpy(killer.name, "went to heaven prematurely"); + svk.killer.format = NO_KILLER_PREFIX; + Strcpy(svk.killer.name, "went to heaven prematurely"); } else if (newlev == -9) { You_feel("deliriously happy."); pline("(In fact, you're on Cloud 9!)"); @@ -954,7 +1344,7 @@ level_tele() } else You("are now high above the clouds..."); - if (killer.name[0]) { + if (svk.killer.name[0]) { ; /* arrival in heaven is pending */ } else if (Levitation) { escape_by_flying = "float gently down to earth"; @@ -963,18 +1353,18 @@ level_tele() } else { pline("Unfortunately, you don't know how to fly."); You("plummet a few thousand feet to your death."); - Sprintf(killer.name, + Sprintf(svk.killer.name, "teleported out of the dungeon and fell to %s death", uhis()); - killer.format = NO_KILLER_PREFIX; + svk.killer.format = NO_KILLER_PREFIX; } } - if (killer.name[0]) { /* the chosen destination was not survivable */ + if (svk.killer.name[0]) { /* the chosen destination was not survivable */ d_level lsav; /* set specific death location; this also suppresses bones */ - lsav = u.uz; /* save current level, see below */ + lsav = u.uz; /* save current level; see below */ u.uz.dnum = 0; /* main dungeon */ u.uz.dlevel = (newlev <= -10) ? -10 : 0; /* heaven or surface */ done(DIED); @@ -987,25 +1377,34 @@ level_tele() /* calls done(ESCAPED) if newlevel==0 */ if (escape_by_flying) { You("%s.", escape_by_flying); - newlevel.dnum = 0; /* specify main dungeon */ - newlevel.dlevel = 0; /* escape the dungeon */ /* [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] */ + newlevel.dnum = 0; /* specify main dungeon */ + newlevel.dlevel = 0; /* escape the dungeon */ + } else if (force_dest) { + /* wizard mode menu; no further validation needed */ + ; } else if (u.uz.dnum == medusa_level.dnum - && newlev >= dungeons[u.uz.dnum].depth_start + && newlev >= svd.dungeons[u.uz.dnum].depth_start + dunlevs_in_dungeon(&u.uz)) { - if (!(wizard && force_dest)) - find_hell(&newlevel); + find_hell(&newlevel); } else { + /* FIXME: we should avoid using hard-coded knowledge of + which branches don't connect to anything deeper; + mainly used to distinguish "can't get there from here" + vs "from anywhere" rather than to control destination */ + d_level *qbranch = In_quest(&u.uz) ? &qstart_level + : In_mines(&u.uz) ? &mineend_level + : &sanctum_level; + int deepest = svd.dungeons[qbranch->dnum].depth_start + + dunlevs_in_dungeon(qbranch) - 1; + /* if invocation did not yet occur, teleporting into * the last level of Gehennom is forbidden. */ - if (!wizard && Inhell && !u.uevent.invoked - && newlev >= (dungeons[u.uz.dnum].depth_start - + dunlevs_in_dungeon(&u.uz) - 1)) { - newlev = dungeons[u.uz.dnum].depth_start - + dunlevs_in_dungeon(&u.uz) - 2; + if (!wizard && Inhell && !u.uevent.invoked && newlev >= deepest) { + newlev = deepest - 1; pline("Sorry..."); } /* no teleporting out of quest dungeon */ @@ -1015,21 +1414,38 @@ level_tele() * we must translate newlev to a number relative to the * current dungeon. */ - if (!(wizard && force_dest)) - get_level(&newlevel, newlev); + get_level(&newlevel, newlev); + + if (on_level(&newlevel, &u.uz) && newlev != depth(&u.uz)) { + You_cant(get_there_from, + (newlev > deepest) ? "anywhere" : "here"); + return; + } } - schedule_goto(&newlevel, FALSE, FALSE, 0, (char *) 0, (char *) 0); + + schedule_goto(&newlevel, UTOTYPE_NONE, (char *) 0, + flags.verbose ? "You materialize on a different level!" + : (char *) 0); +#if 0 /* always wait until end of turn to change level, otherwise code + * that references monsters as this call stack unwinds won't be + * able to access them reliably; the do-the-change-now code here + * dates from when reading a scroll of teleportation wouldn't + * always make the scroll become discovered but that's no longer + * the case so it shouldn't be needed anymore */ + /* in case player just read a scroll and is about to be asked to call it something, we can't defer until the end of the turn */ - if (u.utotype && !context.mon_moving) + if (u.utotype && !svc.context.mon_moving) deferred_goto(); +#endif } void -domagicportal(ttmp) -register struct trap *ttmp; +domagicportal(struct trap *ttmp) { struct d_level target_level; + int totype; + const char *stunmsg = (char *) 0; if (u.utrap && u.utraptype == TT_BURIEDBALL) buried_ball_to_punishment(); @@ -1056,16 +1472,34 @@ register struct trap *ttmp; } target_level = ttmp->dst; - schedule_goto(&target_level, FALSE, FALSE, 1, - "You feel dizzy for a moment, but the sensation passes.", - (char *) 0); + + /* coming back from tutorial doesn't trigger stunning */ + if (In_tutorial(&u.uz) && !In_tutorial(&target_level)) { + /* returning to normal play => arrive on level 1 stairs */ + totype = UTOTYPE_ATSTAIRS; + stunmsg = "Resuming regular play."; + } else { + totype = UTOTYPE_PORTAL; + stunmsg = !Stunned ? "You feel slightly dizzy." + : "You feel dizzier."; + make_stunned((HStun & TIMEOUT) + 3L, FALSE); + } + + schedule_goto(&target_level, totype, stunmsg, (char *) 0); } void -tele_trap(trap) -struct trap *trap; +tele_trap(struct trap *trap) { - if (In_endgame(&u.uz) || Antimagic) { + /* a fixed-destination teleport trap could theoretically place hero onto a + * second teleport trap; prevent the recursive call from spoteffects() from + * triggering the trap at the destination */ + static boolean in_tele_trap = FALSE; + if (in_tele_trap) + return; + + in_tele_trap = TRUE; + if (In_endgame(&u.uz) || Antimagic || noteleport_level(&gy.youmonst)) { if (Antimagic) shieldeff(u.ux, u.uy); You_feel("a wrenching sensation."); @@ -1075,50 +1509,76 @@ struct trap *trap; deltrap(trap); newsym(u.ux, u.uy); /* get rid of trap symbol */ vault_tele(); + } else if (isok(trap->teledest.x, trap->teledest.y)) { + coord cc; + struct monst *mtmp = m_at(trap->teledest.x, trap->teledest.y); + + settrack(); + if (mtmp) { + if (!enexto(&cc, mtmp->mx, mtmp->my, mtmp->data)) { + /* could not find some other place to put mtmp; the level must + * be nearly or completely full */ + You1(shudder_for_moment); + } + else { + rloc_to(mtmp, cc.x, cc.y); + mtmp = (struct monst *) 0; /* no longer a monster at dest */ + } + } + if (!mtmp) { + teleds(trap->teledest.x, trap->teledest.y, TELEDS_TELEPORT); + } } else tele(); + + in_tele_trap = FALSE; } void -level_tele_trap(trap, trflags) -struct trap *trap; -unsigned trflags; +level_tele_trap(struct trap *trap, unsigned int trflags) { char verbbuf[BUFSZ]; + boolean intentional = FALSE; - if ((trflags & VIASITTING) != 0) + if ((trflags & (VIASITTING | FORCETRAP)) != 0) { Strcpy(verbbuf, "trigger"); /* follows "You sit down." */ - else - Sprintf(verbbuf, "%s onto", - Levitation ? (const char *) "float" - : locomotion(youmonst.data, "step")); + intentional = TRUE; + } else + Sprintf(verbbuf, "%s onto", u_locomotion("step")); You("%s a level teleport trap!", verbbuf); - if (Antimagic) { + if (Antimagic && !intentional) { shieldeff(u.ux, u.uy); } - if (Antimagic || In_endgame(&u.uz)) { + if ((Antimagic && !intentional) || In_endgame(&u.uz)) { You_feel("a wrenching sensation."); return; } - if (!Blind) - You("are momentarily blinded by a flash of light."); - else - You("are momentarily disoriented."); deltrap(trap); newsym(u.ux, u.uy); /* get rid of trap symbol */ level_tele(); + + if (Hallucination || Teleport_control) + You("briefly feel %s.", Hallucination ? "oriented" : "centered"); + else + You_feel("%sdisoriented.", Confusion ? "even more " : ""); + /* magic portal traversal causes brief Stun; for level teleport, use + confusion instead, and only when hero lacks control; do this after + processing the level teleportation attempt because being confused + can affect the outcome ("Oops" result) */ + if (!Teleport_control) + make_confused((HConfusion & TIMEOUT) + 3L, FALSE); } /* check whether monster can arrive at location via Tport (or fall) */ -STATIC_OVL boolean -rloc_pos_ok(x, y, mtmp) -register int x, y; /* coordinates of candidate location */ -struct monst *mtmp; +staticfn boolean +rloc_pos_ok( + coordxy x, coordxy y, /* coordinates of candidate location */ + struct monst *mtmp) { - register int xx, yy; + coordxy xx, yy; - if (!goodpos(x, y, mtmp, 0)) + if (!goodpos(x, y, mtmp, GP_CHECKSCARY)) return FALSE; /* * Check for restricted areas present in some special levels. @@ -1131,32 +1591,37 @@ struct monst *mtmp; yy = mtmp->my; if (!xx) { /* no current location (migrating monster arrival) */ - if (dndest.nlx && On_W_tower_level(&u.uz)) + if (svd.dndest.nlx && On_W_tower_level(&u.uz)) return (((yy & 2) != 0) /* inside xor not within */ - ^ !within_bounded_area(x, y, dndest.nlx, dndest.nly, - dndest.nhx, dndest.nhy)); - if (updest.lx && (yy & 1) != 0) /* moving up */ - return (within_bounded_area(x, y, updest.lx, updest.ly, - updest.hx, updest.hy) - && (!updest.nlx - || !within_bounded_area(x, y, updest.nlx, updest.nly, - updest.nhx, updest.nhy))); - if (dndest.lx && (yy & 1) == 0) /* moving down */ - return (within_bounded_area(x, y, dndest.lx, dndest.ly, - dndest.hx, dndest.hy) - && (!dndest.nlx - || !within_bounded_area(x, y, dndest.nlx, dndest.nly, - dndest.nhx, dndest.nhy))); + ^ !within_bounded_area(x, y, + svd.dndest.nlx, svd.dndest.nly, + svd.dndest.nhx, svd.dndest.nhy)); + if (svu.updest.lx && (yy & 1) != 0) /* moving up */ + return (within_bounded_area(x, y, + svu.updest.lx, svu.updest.ly, + svu.updest.hx, svu.updest.hy) + && (!svu.updest.nlx + || !within_bounded_area(x, y, + svu.updest.nlx, svu.updest.nly, + svu.updest.nhx, svu.updest.nhy))); + if (svd.dndest.lx && (yy & 1) == 0) /* moving down */ + return (within_bounded_area(x, y, + svd.dndest.lx, svd.dndest.ly, + svd.dndest.hx, svd.dndest.hy) + && (!svd.dndest.nlx + || !within_bounded_area(x, y, + svd.dndest.nlx, svd.dndest.nly, + svd.dndest.nhx, svd.dndest.nhy))); } else { /* [try to] prevent a shopkeeper or temple priest from being sent out of his room (caller might resort to goodpos() if we report failure here, so this isn't full prevention) */ if (mtmp->isshk && inhishop(mtmp)) { - if (levl[x][y].roomno != ESHK(mtmp)->shoproom) + if (levl[x][y].roomno != (unsigned char) ESHK(mtmp)->shoproom) return FALSE; } else if (mtmp->ispriest && inhistemple(mtmp)) { - if (levl[x][y].roomno != EPRI(mtmp)->shroom) + if (levl[x][y].roomno != (unsigned char) EPRI(mtmp)->shroom) return FALSE; } /* current location is */ @@ -1176,18 +1641,37 @@ struct monst *mtmp; * a value because mtmp is a migrating_mon. Worm tails are always * placed randomly around the head of the worm. */ -void -rloc_to(mtmp, x, y) -struct monst *mtmp; -register int x, y; +staticfn void +rloc_to_core( + struct monst *mtmp, + coordxy x, coordxy y, + unsigned rlocflags) { - register int oldx = mtmp->mx, oldy = mtmp->my; + coordxy oldx = mtmp->mx, oldy = mtmp->my; boolean resident_shk = mtmp->isshk && inhishop(mtmp); + boolean preventmsg = (rlocflags & RLOC_NOMSG) != 0; + boolean vanishmsg = (rlocflags & RLOC_MSG) != 0; + boolean appearmsg = (mtmp->mstrategy & STRAT_APPEARMSG) != 0; + boolean domsg = !gi.in_mklev && (vanishmsg || appearmsg) && !preventmsg; + boolean telemsg = FALSE; if (x == mtmp->mx && y == mtmp->my && m_at(x, y) == mtmp) return; /* that was easy */ if (oldx) { /* "pick up" monster */ + if (domsg && canspotmon(mtmp)) { + if (couldsee(x, y) || sensemon(mtmp)) { + telemsg = TRUE; + } else { + pline("%s vanishes!", Monnam(mtmp)); + } + /* avoid "It suddenly appears!" for a STRAT_APPEARMSG monster + that has just teleported away if we won't see it after this + vanishing (the regular appears message will be given if we + do see it) */ + appearmsg = FALSE; + } + if (mtmp->wormno) { remove_worm(mtmp); } else { @@ -1196,7 +1680,7 @@ register int x, y; } } - memset(mtmp->mtrack, 0, sizeof mtmp->mtrack); + mon_track_clear(mtmp); place_monster(mtmp, x, y); /* put monster down */ update_monster_region(mtmp); @@ -1206,30 +1690,120 @@ register int x, y; if (u.ustuck == mtmp) { if (u.uswallow) { u_on_newpos(mtmp->mx, mtmp->my); + check_special_room(FALSE); docrt(); - } else if (distu(mtmp->mx, mtmp->my) > 2) { + } else if (!m_next2u(mtmp)) { unstuck(mtmp); } } + maybe_unhide_at(x, y); newsym(x, y); /* update new location */ set_apparxy(mtmp); /* orient monster */ + if (domsg && (canspotmon(mtmp) || appearmsg || mtmp == u.ustuck)) { + int du = distu(x, y), olddu; + const char *next = (du <= 2) ? " next to you" : 0, /* next2u() */ + *nearu = (du <= BOLT_LIM * BOLT_LIM) ? " close by" : 0; + + set_msg_xy(x, y); + mtmp->mstrategy &= ~STRAT_APPEARMSG; /* one chance only */ + if (mtmp == u.ustuck && !u_at(u.ux0, u.uy0)) { + You("and %s teleport together.", mon_nam(mtmp)); + } else if (telemsg && (couldsee(x, y) || sensemon(mtmp))) { + pline("%s vanishes and reappears%s.", + Monnam(mtmp), + next ? next + : nearu ? nearu + : ((olddu = distu(oldx, oldy)) == du) ? "" + : (du < olddu) ? " closer to you" + : " farther away"); + } else { + pline("%s %s%s%s!", + appearmsg ? Amonnam(mtmp) : Monnam(mtmp), + appearmsg ? "suddenly " : "", + !Blind ? "appears" : "arrives", + next ? next : nearu ? nearu : ""); + } + /* wand discovery only happens if a messaage is delivered (bug?); + if spell or q.mechanic attack or artifact #invoke for banish + then current_wand will be Null */ + if (gc.current_wand && gc.current_wand->otyp == WAN_TELEPORTATION) + makeknown(WAN_TELEPORTATION); + } /* shopkeepers will only teleport if you zap them with a wand of teleportation or if they've been transformed into a jumpy monster; - the latter only happens if you've attacked them with polymorph */ + the latter only happens if you've attacked them with polymorph + [FIXME? or they've been hit by a genetic engineer, which won't + necessarily be due to Conflict by hero] */ if (resident_shk && !inhishop(mtmp)) make_angry_shk(mtmp, oldx, oldy); + + /* if a monster carrying shop goods teleports out of the shop, blame + it on the hero; chance of an unpaid item is vanishingly small, but + no_charge is easily possible and needs to be cleared if not in shop; + a for-sale item is ordinary here--shk won't notice it leaving; if + mtmp teleports from one shop into another, no_charge status sticks + and an item on the first shk's bill stays there */ + if (mtmp->minvent && !costly_spot(x, y)) { + struct obj *otmp; + struct monst *shkp = find_objowner(mtmp->minvent, oldx, oldy); + boolean peaceful = !shkp || shkp->mpeaceful; + + for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj) { + if (otmp->no_charge) + otmp->no_charge = 0; + else if (shkp && onshopbill(otmp, shkp, TRUE)) + stolen_value(otmp, oldx, oldy, peaceful, FALSE); + } + } + + /* if hero is busy, maybe stop occupation */ + if (go.occupation) + (void) dochugw(mtmp, FALSE); + + /* trapped monster teleported away */ + if (mtmp->mtrapped && !mtmp->wormno) + (void) mintrap(mtmp, NO_TRAP_FLAGS); } -/* place a monster at a random location, typically due to teleport */ -/* return TRUE if successful, FALSE if not */ +void +rloc_to(struct monst *mtmp, coordxy x, coordxy y) +{ + rloc_to_core(mtmp, x, y, RLOC_NOMSG); +} + +void +rloc_to_flag( + struct monst *mtmp, + coordxy x, coordxy y, + unsigned rlocflags) +{ + rloc_to_core(mtmp, x, y, rlocflags); +} + +staticfn stairway * +stairway_find_forwiz(boolean isladder, boolean up) +{ + stairway *stway = gs.stairs; + + while (stway && !(stway->isladder == isladder + && stway->up == up && stway->tolev.dnum == u.uz.dnum)) + stway = stway->next; + return stway; +} + +/* place a monster at a random location, typically due to teleport; + return TRUE if successful, FALSE if not; rlocflags is RLOC_foo flags */ boolean -rloc(mtmp, suppress_impossible) -struct monst *mtmp; /* mx==0 implies migrating monster arrival */ -boolean suppress_impossible; +rloc( + struct monst *mtmp, /* mtmp->mx==0 implies migrating monster arrival */ + unsigned rlocflags) { - register int x, y, trycount; + coord cc, backupcc, candy[ROWNO * (COLNO - 1)]; /* room for entire map */ + unsigned cc_flags; + coordxy x, y; + int trycount, i, j, candycount; if (mtmp == u.usteed) { tele(); @@ -1237,63 +1811,145 @@ boolean suppress_impossible; } if (mtmp->iswiz && mtmp->mx) { /* Wizard, not just arriving */ - if (!In_W_tower(u.ux, u.uy, &u.uz)) - x = xupstair, y = yupstair; - else if (!xdnladder) /* bottom level of tower */ - x = xupladder, y = yupladder; - else - x = xdnladder, y = ydnladder; + stairway *stway; + + if (!In_W_tower(u.ux, u.uy, &u.uz)) { + stway = stairway_find_forwiz(FALSE, TRUE); + } else if (!stairway_find_forwiz(TRUE, FALSE)) { /* bottom of tower */ + stway = stairway_find_forwiz(TRUE, TRUE); + } else { + stway = stairway_find_forwiz(TRUE, FALSE); + } + + x = stway ? stway->sx : 0; + y = stway ? stway->sy : 0; + /* if the wiz teleports away to heal, try the up staircase, to block the player's escaping before he's healed (deliberately use `goodpos' rather than `rloc_pos_ok' here) */ - if (goodpos(x, y, mtmp, 0)) + if (goodpos(x, y, mtmp, NO_MM_FLAGS)) goto found_xy; } - trycount = 0; - do { - x = rn1(COLNO - 3, 2); - y = rn2(ROWNO); - if ((trycount < 500) ? rloc_pos_ok(x, y, mtmp) - : goodpos(x, y, mtmp, 0)) + /* wizard-mode player can choose destination by setting 'montelecontrol' + option; ignored if/when this is arrival of a migrating monster */ + if (iflags.mon_telecontrol && mtmp->mx) { + cc.x = mtmp->mx, cc.y = mtmp->my; + if (control_mon_tele(mtmp, &cc, rlocflags, TRUE)) { + x = cc.x, y = cc.y; goto found_xy; - } while (++trycount < 1000); + } + } - /* last ditch attempt to find a good place */ - for (x = 2; x < COLNO - 1; x++) - for (y = 0; y < ROWNO; y++) - if (goodpos(x, y, mtmp, 0)) - goto found_xy; + /* this used to try randomly 1000 times, then fallback to left-to-right + top-to-bottom exhaustive check; now that the exhaustive check uses + randomized order, reduce the number of random attempts to 50; + on levels with lots of available space, random can find a spot more + quickly but might fail to find one no matter how many tries it makes */ + for (trycount = 0; trycount < 50; ++trycount) { + x = rnd(COLNO - 1); /* 1..COLNO-1 */ + y = rn2(ROWNO); /* 0..ROWNO-1 */ + if (rloc_pos_ok(x, y, mtmp)) /* rejects 'onscary' */ + goto found_xy; + } - /* level either full of monsters or somehow faulty */ - if (!suppress_impossible) - impossible("rloc(): couldn't relocate monster"); - return FALSE; + /* try harder to find a good place; gather a list of all candidate + locations (every accessible unoccupied spot except for hero's; + goodpos() will reject that), then shuffle them ourselves instead + of having collect_coords() do it (which would be in rings centered + around arbitrary ) */ + cc_flags = CC_INCL_CENTER | CC_UNSHUFFLED | CC_SKIP_MONS; + if (!passes_walls(mtmp->data)) + cc_flags |= CC_SKIP_INACCS; + candycount = collect_coords(candy, COLNO / 2, ROWNO / 2, 0, cc_flags, + (boolean (*)(coordxy, coordxy)) 0); + backupcc.x = backupcc.y = 0; + for (i = 0; i < candycount; ++i) { + if ((j = rn2(candycount - i)) > 0) { + cc = candy[i]; + candy[i] = candy[i + j]; + candy[i + j] = cc; + } + x = candy[i].x, y = candy[i].y; + if (rloc_pos_ok(x, y, mtmp)) + goto found_xy; + if (!backupcc.x && goodpos(x, y, mtmp, NO_MM_FLAGS)) + backupcc.x = x, backupcc.y = y; + } + /* we didn't find any spot acceptable to rloc_pos_ok() which avoids + 'onscary' and honors teleport regions, but if we did find a spot + that was acceptable to goodpos() (which ignores 'onscary' and + teleport regions) we'll use that; otherwise give up */ + if (!backupcc.x) { + /* level either full of monsters or somehow faulty */ + if ((rlocflags & RLOC_ERR) != 0) + impossible("rloc(): couldn't relocate monster"); + return FALSE; + } + x = backupcc.x, y = backupcc.y; + /*goto found_xy;*/ found_xy: - rloc_to(mtmp, x, y); + rloc_to_core(mtmp, x, y, rlocflags); return TRUE; } -STATIC_OVL void -mvault_tele(mtmp) -struct monst *mtmp; +/* let wizard-mode player choose a teleporting monster's destination */ +boolean +control_mon_tele( + struct monst *mon, + coord *cc_p, /* input: default spot; output: player selected spot */ + unsigned rlocflags, + boolean via_rloc) +{ + char tcbuf[BUFSZ]; + + if (!isok(cc_p->x, cc_p->y)) { + cc_p->x = mon->mx, cc_p->y = mon->my; + if (!isok(cc_p->x, cc_p->y)) + cc_p->x = u.ux, cc_p->y = u.uy; + } + + if (!wizard || !iflags.mon_telecontrol) + return FALSE; + + pline("Teleport %s @ <%d,%d> where?", + noit_mon_nam(mon), mon->mx, mon->my); + /* getpos '?' will show "Move the cursor to :" */ + Sprintf(tcbuf, "where to teleport %s", noit_mon_nam(mon)); + if (getpos(cc_p, FALSE, tcbuf) >= 0 && !u_at(cc_p->x, cc_p->y)) { + if (via_rloc + ? rloc_pos_ok(cc_p->x, cc_p->y, mon) + : goodpos(cc_p->x, cc_p->y, mon, rlocflags)) + return TRUE; + if (!iflags.debug_fuzzer) { + Sprintf(tcbuf, "<%d,%d> is not considered viable; force anyway?", + mon->mx, mon->my); + if (y_n(tcbuf) == 'y') + return TRUE; + } + } + pline("%s destination.", via_rloc ? "Picking random" : "Using derived"); + return FALSE; +} + +staticfn void +mvault_tele(struct monst *mtmp) { struct mkroom *croom = search_special(VAULT); coord c; - if (croom && somexy(croom, &c) && goodpos(c.x, c.y, mtmp, 0)) { + if (croom && somexyspace(croom, &c) && goodpos(c.x, c.y, mtmp, 0)) { rloc_to(mtmp, c.x, c.y); return; } - (void) rloc(mtmp, TRUE); + (void) rloc(mtmp, RLOC_NONE); } boolean -tele_restrict(mon) -struct monst *mon; +tele_restrict(struct monst *mon) { - if (level.flags.noteleport) { + if (noteleport_level(mon)) { if (canseemon(mon)) pline("A mysterious force prevents %s from teleporting!", mon_nam(mon)); @@ -1303,15 +1959,15 @@ struct monst *mon; } void -mtele_trap(mtmp, trap, in_sight) -struct monst *mtmp; -struct trap *trap; -int in_sight; +mtele_trap(struct monst *mtmp, struct trap *trap, int in_sight) { char *monname; - if (tele_restrict(mtmp)) + /* don't print feedback here: a monster stepping on a trap and not + teleporting from it isn't visible */ + if (noteleport_level(mtmp)) return; + if (teleport_pet(mtmp, FALSE)) { /* save name with pre-movement visibility */ monname = Monnam(mtmp); @@ -1322,8 +1978,18 @@ int in_sight; */ if (trap->once) mvault_tele(mtmp); - else - (void) rloc(mtmp, TRUE); + else if (isok(trap->teledest.x, trap->teledest.y)) { + /* monster teleporting onto hero's or another monster's spot does + * not work the same as hero teleporting onto monster's spot where + * the incoming monster displaces the resident to the nearest + * possible space - instead it just doesn't work. */ + if (!(m_at(trap->teledest.x, trap->teledest.y) + || u_at(trap->teledest.x, trap->teledest.y))) { + rloc_to_core(mtmp, trap->teledest.x, trap->teledest.y, + RLOC_MSG); + } + } else + (void) rloc(mtmp, RLOC_NONE); if (in_sight) { if (canseemon(mtmp)) @@ -1335,18 +2001,18 @@ int in_sight; } } -/* return 0 if still on level, 3 if not */ +/* return Trap_Effect_Finished if still on level, Trap_Moved_Mon if not */ int -mlevel_tele_trap(mtmp, trap, force_it, in_sight) -struct monst *mtmp; -struct trap *trap; -boolean force_it; -int in_sight; +mlevel_tele_trap( + struct monst *mtmp, + struct trap *trap, + boolean force_it, + int in_sight) { int tt = (trap ? trap->ttyp : NO_TRAP); if (mtmp == u.ustuck) /* probably a vortex */ - return 0; /* temporary? kludge */ + return Trap_Effect_Finished; /* temporary? kludge */ if (teleport_pet(mtmp, force_it)) { d_level tolevel; int migrate_typ = MIGR_RANDOM; @@ -1356,21 +2022,25 @@ int in_sight; assign_level(&tolevel, &valley_level); } else if (Is_botlevel(&u.uz)) { if (in_sight && trap->tseen) - pline("%s avoids the %s.", Monnam(mtmp), - (tt == HOLE) ? "hole" : "trap"); - return 0; + pline_mon(mtmp, "%s avoids the %s.", + Monnam(mtmp), + (tt == HOLE) ? "hole" : "trap"); + return Trap_Effect_Finished; } else { - get_level(&tolevel, depth(&u.uz) + 1); + assign_level(&tolevel, &trap->dst); + (void) clamp_hole_destination(&tolevel); } } else if (tt == MAGIC_PORTAL) { if (In_endgame(&u.uz) && (mon_has_amulet(mtmp) || is_home_elemental(mtmp->data) || rn2(7))) { if (in_sight && mtmp->data->mlet != S_ELEMENTAL) { - pline("%s seems to shimmer for a moment.", Monnam(mtmp)); + pline_mon(mtmp, + "%s seems to shimmer for a moment.", + Monnam(mtmp)); seetrap(trap); } - return 0; + return Trap_Effect_Finished; } else { assign_level(&tolevel, &trap->dst); migrate_typ = MIGR_PORTAL; @@ -1385,9 +2055,10 @@ int in_sight; currently inside his or her own special room */ || (tt == NO_TRAP && onscary(0, 0, mtmp))) { if (in_sight) - pline("%s seems very disoriented for a moment.", - Monnam(mtmp)); - return 0; + pline_mon(mtmp, + "%s seems very disoriented for a moment.", + Monnam(mtmp)); + return Trap_Effect_Finished; } if (tt == NO_TRAP) { /* creature is being forced off the level to make room; @@ -1399,33 +2070,38 @@ int in_sight; nlev = random_teleport_level(); if (nlev == depth(&u.uz)) { if (in_sight) - pline("%s shudders for a moment.", Monnam(mtmp)); - return 0; + pline_mon(mtmp, "%s shudders for a moment.", + Monnam(mtmp)); + return Trap_Effect_Finished; } get_level(&tolevel, nlev); } } else { impossible("mlevel_tele_trap: unexpected trap type (%d)", tt); - return 0; + return Trap_Effect_Finished; } if (in_sight) { - pline("Suddenly, %s disappears out of sight.", mon_nam(mtmp)); + pline_mon(mtmp, "Suddenly, %s %s.", mon_nam(mtmp), + (tt == HOLE) ? "falls into a hole" + : (tt == TRAPDOOR) ? "falls through a trap door" + : "disappears out of sight"); if (trap) seetrap(trap); } + if (is_xport(tt) && !control_teleport(mtmp->data)) + mtmp->mconf = 1; migrate_to_level(mtmp, ledger_no(&tolevel), migrate_typ, (coord *) 0); - return 3; /* no longer on this level */ + return Trap_Moved_Mon; /* no longer on this level */ } - return 0; + return Trap_Effect_Finished; } /* place object randomly, returns False if it's gone (eg broken) */ boolean -rloco(obj) -register struct obj *obj; +rloco(struct obj *obj) { - register xchar tx, ty, otx, oty; + coordxy tx, ty, otx, oty; boolean restricted_fall; int try_limit = 4000; @@ -1437,7 +2113,7 @@ register struct obj *obj; obj_extract_self(obj); otx = obj->ox; oty = obj->oy; - restricted_fall = (otx == 0 && dndest.lx); + restricted_fall = (otx == 0 && svd.dndest.lx); do { tx = rn1(COLNO - 3, 2); ty = rn2(ROWNO); @@ -1445,37 +2121,63 @@ register struct obj *obj; break; } while (!goodpos(tx, ty, (struct monst *) 0, 0) || (restricted_fall - && (!within_bounded_area(tx, ty, dndest.lx, dndest.ly, - dndest.hx, dndest.hy) - || (dndest.nlx + && (!within_bounded_area(tx, ty, + svd.dndest.lx, svd.dndest.ly, + svd.dndest.hx, svd.dndest.hy) + || (svd.dndest.nlx && within_bounded_area(tx, ty, - dndest.nlx, dndest.nly, - dndest.nhx, dndest.nhy)))) + svd.dndest.nlx, svd.dndest.nly, + svd.dndest.nhx, svd.dndest.nhy)))) /* on the Wizard Tower levels, objects inside should stay inside and objects outside should stay outside */ - || (dndest.nlx && On_W_tower_level(&u.uz) - && within_bounded_area(tx, ty, dndest.nlx, dndest.nly, - dndest.nhx, dndest.nhy) - != within_bounded_area(otx, oty, dndest.nlx, dndest.nly, - dndest.nhx, dndest.nhy))); + || (svd.dndest.nlx && On_W_tower_level(&u.uz) + && within_bounded_area(tx, ty, + svd.dndest.nlx, svd.dndest.nly, + svd.dndest.nhx, svd.dndest.nhy) + != within_bounded_area(otx, oty, + svd.dndest.nlx, svd.dndest.nly, + svd.dndest.nhx, svd.dndest.nhy))); if (flooreffects(obj, tx, ty, "fall")) { - /* update old location since flooreffects() couldn't; + /* update old location (if any) since flooreffects() couldn't; unblock_point() for boulder handled by obj_extract_self() */ - newsym(otx, oty); + if (!(otx == 0 && oty == 0)) + newsym(otx, oty); return FALSE; } else if (otx == 0 && oty == 0) { ; /* fell through a trap door; no update of old loc needed */ } else { - if (costly_spot(otx, oty) - && (!costly_spot(tx, ty) - || !index(in_rooms(tx, ty, 0), *in_rooms(otx, oty, 0)))) { - if (costly_spot(u.ux, u.uy) - && index(u.urooms, *in_rooms(otx, oty, 0))) - addtobill(obj, FALSE, FALSE, FALSE); - else + struct monst *shkp = find_objowner(obj, otx, oty); + boolean objinshop = shkp && costly_spot(otx, oty), + onboundary = shkp && costly_adjacent(shkp, otx, oty); + + /* + * If object starts inside shop or is unpaid and on shop boundary: + * if hero is outside the shop, treat this as theft; + * otherwise, if it arrives inside same shop, remove it from bill; + * otherwise, if it arrives on the boundary, add it to bill; + * if it arrives outside the shop, treat this as a theft. + * Billing routines deal with obj->no_charge. + */ + if (objinshop || (obj->unpaid && onboundary)) { + char h = *in_rooms(u.ux, u.uy, SHOPBASE), + oo = *in_rooms(otx, oty, 0); + boolean hinshop = h && strchr(in_rooms(shkp->mx, shkp->my, 0), h); + + if (hinshop && costly_spot(tx, ty) + /* verify that it's the same shop */ + && oo && strchr(in_rooms(tx, ty, 0), oo)) { + if (obj->unpaid) + subfrombill(obj, shkp); + } else if (hinshop && costly_adjacent(shkp, tx, ty) + && oo && strchr(in_rooms(tx, ty, 0), oo)) { + if (!obj->unpaid) + addtobill(obj, FALSE, FALSE, FALSE); + } else { (void) stolen_value(obj, otx, oty, FALSE, FALSE); + } } + newsym(otx, oty); /* update old location */ } place_object(obj, tx, ty); @@ -1486,12 +2188,12 @@ register struct obj *obj; /* Returns an absolute depth */ int -random_teleport_level() +random_teleport_level(void) { int nlev, max_depth, min_depth, cur_depth = (int) depth(&u.uz); /* [the endgame case can only occur in wizard mode] */ - if (!rn2(5) || Is_knox(&u.uz) || In_endgame(&u.uz)) + if (!rn2(5) || single_level_branch(&u.uz) || In_endgame(&u.uz)) return cur_depth; /* What I really want to do is as follows: @@ -1521,12 +2223,12 @@ random_teleport_level() no one can randomly teleport past it */ if (dunlev_reached(&u.uz) < qlocate_depth) bottom = qlocate_depth; - min_depth = dungeons[u.uz.dnum].depth_start; - max_depth = bottom + (dungeons[u.uz.dnum].depth_start - 1); + min_depth = svd.dungeons[u.uz.dnum].depth_start; + max_depth = bottom + (svd.dungeons[u.uz.dnum].depth_start - 1); } else { min_depth = 1; - max_depth = - dunlevs_in_dungeon(&u.uz) + (dungeons[u.uz.dnum].depth_start - 1); + max_depth = dunlevs_in_dungeon(&u.uz) + + (svd.dungeons[u.uz.dnum].depth_start - 1); /* can't reach Sanctum if the invocation hasn't been performed */ if (Inhell && !u.uevent.invoked) max_depth -= 1; @@ -1558,26 +2260,34 @@ random_teleport_level() /* you teleport a monster (via wand, spell, or poly'd q.mechanic attack); return false iff the attempt fails */ boolean -u_teleport_mon(mtmp, give_feedback) -struct monst *mtmp; -boolean give_feedback; +u_teleport_mon( + struct monst *mtmp, + boolean give_feedback) { coord cc; - if (mtmp->ispriest && *in_rooms(mtmp->mx, mtmp->my, TEMPLE)) { + if (svl.level.flags.stasis_until >= svm.moves) { + if (give_feedback) + pline("A mysterious force prevents you teleporting %s!", + mon_nam(mtmp)); + return FALSE; + } else if (mtmp->ispriest && *in_rooms(mtmp->mx, mtmp->my, TEMPLE)) { if (give_feedback) pline("%s resists your magic!", Monnam(mtmp)); return FALSE; - } else if (level.flags.noteleport && u.uswallow && mtmp == u.ustuck) { + } else if (engulfing_u(mtmp) && noteleport_level(mtmp)) { if (give_feedback) You("are no longer inside %s!", mon_nam(mtmp)); unstuck(mtmp); - (void) rloc(mtmp, TRUE); - } else if (is_rider(mtmp->data) && rn2(13) - && enexto(&cc, u.ux, u.uy, mtmp->data)) + if (!rloc(mtmp, RLOC_MSG)) + m_into_limbo(mtmp); + } else if ((is_rider(mtmp->data) || control_teleport(mtmp->data)) + && rn2(13) && enexto(&cc, u.ux, u.uy, mtmp->data)) { rloc_to(mtmp, cc.x, cc.y); - else - (void) rloc(mtmp, TRUE); + } else { + if (!rloc(mtmp, RLOC_MSG)) + return FALSE; + } return TRUE; } diff --git a/src/timeout.c b/src/timeout.c index f9df132db..03f2bdcc3 100644 --- a/src/timeout.c +++ b/src/timeout.c @@ -1,27 +1,30 @@ -/* NetHack 3.6 timeout.c $NHDT-Date: 1573290422 2019/11/09 09:07:02 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.93 $ */ +/* NetHack 5.0 timeout.c $NHDT-Date: 1776080125 2026/04/13 03:35:25 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.207 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2018. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -#include "lev.h" /* for checking save modes */ - -STATIC_DCL void NDECL(stoned_dialogue); -STATIC_DCL void NDECL(vomiting_dialogue); -STATIC_DCL void NDECL(choke_dialogue); -STATIC_DCL void NDECL(levitation_dialogue); -STATIC_DCL void NDECL(slime_dialogue); -STATIC_DCL void FDECL(slimed_to_death, (struct kinfo *)); -STATIC_DCL void NDECL(phaze_dialogue); -STATIC_DCL void FDECL(done_timeout, (int, int)); -STATIC_DCL void NDECL(slip_or_trip); -STATIC_DCL void FDECL(see_lamp_flicker, (struct obj *, const char *)); -STATIC_DCL void FDECL(lantern_message, (struct obj *)); -STATIC_DCL void FDECL(cleanup_burn, (ANY_P *, long)); + +#ifndef SFCTOOL +staticfn void stoned_dialogue(void); +staticfn void vomiting_dialogue(void); +staticfn void sleep_dialogue(void); +staticfn void choke_dialogue(void); +staticfn void levitation_dialogue(void); +staticfn void slime_dialogue(void); +staticfn void slimed_to_death(struct kinfo *) NO_NNARGS; +staticfn void sickness_dialogue(void); +staticfn void phaze_dialogue(void); +staticfn void region_dialogue(void); +staticfn void done_timeout(int, int); +staticfn void slip_or_trip(void); +staticfn void see_lamp_flicker(struct obj *, const char *) NONNULLPTRS; +staticfn void lantern_message(struct obj *) NONNULLARG1; +staticfn void cleanup_burn(ANY_P *, long) NONNULLARG1; /* used by wizard mode #timeout and #wizintrinsic; order by 'interest' for timeout countdown, where most won't occur in normal play */ -const struct propname { +static const struct propname { int prop_num; const char *prop_name; } propertynames[] = { @@ -47,21 +50,37 @@ const struct propname { { DETECT_MONSTERS, "monster detection" }, { SEE_INVIS, "see invisible" }, { INVIS, "invisible" }, - /* properties beyond here don't have timed values during normal play, - so there's not much point in trying to order them sensibly; - they're either on or off based on equipment, role, actions, &c */ + /* temporary acid resistance and stone resistance can come from eating */ + { ACID_RES, "acid resistance" }, + { STONE_RES, "stoning resistance" }, + /* timed displacement is possible via eating a displacer beast corpse */ + { DISPLACED, "displaced" }, + /* timed pass-walls is a potential prayer result if surrounded by stone + with nowhere to be safely teleported to */ + { PASSES_WALLS, "pass thru walls" }, + /* likewise for magical breathing vs poison gas regions */ + { MAGICAL_BREATHING, "magical breathing" }, + /* timed fire resistance and water walking are possible in explore mode + (as well as in wizard mode) after life-saving in lava if it fails to + teleport the hero to safety and player declines to die */ + { WWALKING, "water walking" }, { FIRE_RES, "fire resistance" }, + /* + * Properties beyond here don't have timed values during normal play, + * so there's not much point in trying to order them sensibly. + * They're either on or off based on equipment, role, actions, &c, + * but in wizard mode, #wizintrinsic can give them as timed effects. + */ { COLD_RES, "cold resistance" }, { SLEEP_RES, "sleep resistance" }, { DISINT_RES, "disintegration resistance" }, { SHOCK_RES, "shock resistance" }, { POISON_RES, "poison resistance" }, - { ACID_RES, "acid resistance" }, - { STONE_RES, "stoning resistance" }, { DRAIN_RES, "drain resistance" }, { SICK_RES, "sickness resistance" }, { ANTIMAGIC, "magic resistance" }, { HALLUC_RES, "hallucination resistance" }, + { BLND_RES, "light-induced blindness resistance" }, { FUMBLING, "fumbling" }, { HUNGER, "voracious hunger" }, { TELEPAT, "telepathic" }, @@ -71,17 +90,13 @@ const struct propname { { SEARCHING, "searching" }, { INFRAVISION, "infravision" }, { ADORNED, "adorned (+/- Cha)" }, - { DISPLACED, "displaced" }, { STEALTH, "stealthy" }, { AGGRAVATE_MONSTER, "monster aggravation" }, { CONFLICT, "conflict" }, { JUMPING, "jumping" }, { TELEPORT_CONTROL, "teleport control" }, { FLYING, "flying" }, - { WWALKING, "water walking" }, { SWIMMING, "swimming" }, - { MAGICAL_BREATHING, "magical breathing" }, - { PASSES_WALLS, "pass thru walls" }, { SLOW_DIGESTION, "slow digestion" }, { HALF_SPDAM, "half spell damage" }, { HALF_PHDAM, "half physical damage" }, @@ -98,6 +113,17 @@ const struct propname { { 0, 0 }, }; +const char * +property_by_index(int idx, int *propertynum) +{ + if (!IndexOkT(idx, propertynames)) + idx = SIZE(propertynames) - 1; + + if (propertynum) + *propertynum = propertynames[idx].prop_num; + return propertynames[idx].prop_name; +} + /* He is being petrified - dialogue by inmet!tower */ static NEARDATA const char *const stoned_texts[] = { "You are slowing down.", /* 5 */ @@ -107,23 +133,23 @@ static NEARDATA const char *const stoned_texts[] = { "You are a statue." /* 1 */ }; -STATIC_OVL void -stoned_dialogue() +staticfn void +stoned_dialogue(void) { - register long i = (Stoned & TIMEOUT); + long i = (Stoned & TIMEOUT); if (i > 0L && i <= SIZE(stoned_texts)) { char buf[BUFSZ]; Strcpy(buf, stoned_texts[SIZE(stoned_texts) - i]); - if (nolimbs(youmonst.data) && strstri(buf, "limbs")) + if (nolimbs(gy.youmonst.data) && strstri(buf, "limbs")) (void) strsubst(buf, "limbs", "extremities"); - pline1(buf); + urgent_pline("%s", buf); } switch ((int) i) { case 5: /* slowing down */ HFast = 0L; - if (multi > 0) + if (gm.multi > 0) nomul(0); break; case 4: /* limbs stiffening */ @@ -131,14 +157,14 @@ stoned_dialogue() don't stop attempt to eat tin--might be lizard or acidic */ if (!Popeye(STONED)) stop_occupation(); - if (multi > 0) + if (gm.multi > 0) nomul(0); break; case 3: /* limbs turned to stone */ stop_occupation(); nomul(-3); /* can't move anymore */ - multi_reason = "getting stoned"; - nomovemsg = You_can_move_again; /* not unconscious */ + gm.multi_reason = "getting stoned"; + gn.nomovemsg = You_can_move_again; /* not unconscious */ /* "your limbs have turned to stone" so terminate wounded legs */ if (Wounded_legs && !u.usteed) heal_legs(2); @@ -167,10 +193,11 @@ static NEARDATA const char *const vomiting_texts[] = { "are about to vomit." /* 2 */ }; -STATIC_OVL void -vomiting_dialogue() +staticfn void +vomiting_dialogue(void) { const char *txt = 0; + char buf[BUFSZ]; long v = (Vomiting & TIMEOUT); /* note: nhtimeout() hasn't decremented timed properties for the @@ -181,26 +208,31 @@ vomiting_dialogue() break; case 11: txt = vomiting_texts[1]; + if (strstri(txt, " confused") && Confusion) + txt = strsubst(strcpy(buf, txt), " confused", " more confused"); break; case 6: make_stunned((HStun & TIMEOUT) + (long) d(2, 4), FALSE); if (!Popeye(VOMITING)) stop_occupation(); + FALLTHROUGH; /*FALLTHRU*/ case 9: make_confused((HConfusion & TIMEOUT) + (long) d(2, 4), FALSE); - if (multi > 0) + if (gm.multi > 0) nomul(0); break; case 8: txt = vomiting_texts[2]; + if (strstri(txt, " think") && Stunned) + txt = strsubst(strcpy(buf, txt), "can't seem to ", "can't "); break; case 5: txt = vomiting_texts[3]; break; case 2: txt = vomiting_texts[4]; - if (cantvomit(youmonst.data)) + if (cantvomit(gy.youmonst.data)) txt = "gag uncontrollably."; else if (Hallucination) /* "hurl" is short for "hurl chunks" which is slang for @@ -209,7 +241,7 @@ vomiting_dialogue() break; case 0: stop_occupation(); - if (!cantvomit(youmonst.data)) { + if (!cantvomit(gy.youmonst.data)) { morehungry(20); /* case 2 used to be "You suddenly vomit!" but it wasn't sudden since you've just been through the earlier messages of the @@ -232,6 +264,17 @@ vomiting_dialogue() exercise(A_CON, FALSE); } +staticfn void +sleep_dialogue(void) +{ + long i = (HSleepy & TIMEOUT); + + if (i == 4) + You("yawn."); +} + +DISABLE_WARNING_FORMAT_NONLITERAL /* RESTORE is after slime_dialogue */ + static NEARDATA const char *const choke_texts[] = { "You find it hard to breathe.", "You're gasping for air.", @@ -248,33 +291,66 @@ static NEARDATA const char *const choke_texts2[] = { "You suffocate." }; -STATIC_OVL void -choke_dialogue() +staticfn void +choke_dialogue(void) { - register long i = (Strangled & TIMEOUT); + long i = (Strangled & TIMEOUT); if (i > 0 && i <= SIZE(choke_texts)) { - if (Breathless || !rn2(50)) - pline(choke_texts2[SIZE(choke_texts2) - i], body_part(NECK)); - else { + if (Breathless || !rn2(50)) { + urgent_pline(choke_texts2[SIZE(choke_texts2) - i], + body_part(NECK)); + } else { const char *str = choke_texts[SIZE(choke_texts) - i]; - if (index(str, '%')) - pline(str, hcolor(NH_BLUE)); + if (strchr(str, '%')) + urgent_pline(str, hcolor(NH_BLUE)); else - pline1(str); + urgent_pline("%s", str); + stop_occupation(); } } exercise(A_STR, FALSE); } +static NEARDATA const char *const sickness_texts[] = { + "Your illness feels worse.", + "Your illness is severe.", + "You are at Death's door.", +}; + +staticfn void +sickness_dialogue(void) +{ + long j = (Sick & TIMEOUT), i = j / 2L; + + if (i > 0L && i <= SIZE(sickness_texts) && (j % 2) != 0) { + char buf[BUFSZ], pronounbuf[40]; + + Strcpy(buf, sickness_texts[SIZE(sickness_texts) - i]); + /* change the message slightly for food poisoning */ + if ((u.usick_type & SICK_NONVOMITABLE) == 0) + (void) strsubst(buf, "illness", "sickness"); + if (Hallucination && strstri(buf, "Death's door")) { + /* youmonst: for Hallucination, mhe()'s mon argument isn't used */ + Strcpy(pronounbuf, mhe(&gy.youmonst)); + Sprintf(eos(buf), " %s %s inviting you in.", + /* upstart() modifies its argument but vtense() doesn't + care whether or not that has already happened */ + upstart(pronounbuf), vtense(pronounbuf, "are")); + } + urgent_pline("%s", buf); + } + exercise(A_CON, FALSE); +} + static NEARDATA const char *const levi_texts[] = { "You float slightly lower.", "You wobble unsteadily %s the %s." }; -STATIC_OVL void -levitation_dialogue() +staticfn void +levitation_dialogue(void) { /* -1 because the last message comes via float_down() */ long i = (((HLevitation & TIMEOUT) - 1L) / 2L); @@ -289,14 +365,15 @@ levitation_dialogue() if (((HLevitation & TIMEOUT) % 2L) && i > 0L && i <= SIZE(levi_texts)) { const char *s = levi_texts[SIZE(levi_texts) - i]; - if (index(s, '%')) { + if (strchr(s, '%')) { boolean danger = (is_pool_or_lava(u.ux, u.uy) && !Is_waterlevel(&u.uz)); - pline(s, danger ? "over" : "in", - danger ? surface(u.ux, u.uy) : "air"); + urgent_pline(s, danger ? "over" : "in", + danger ? surface(u.ux, u.uy) : "air"); } else pline1(s); + stop_occupation(); } } @@ -308,34 +385,40 @@ static NEARDATA const char *const slime_texts[] = { "You have become %s." /* 1 */ }; -STATIC_OVL void -slime_dialogue() +staticfn void +slime_dialogue(void) { - register long i = (Slimed & TIMEOUT) / 2L; + long t = (Slimed & TIMEOUT), i = t / 2L; - if (i == 1L) { + if (t == 1L) { /* display as green slime during "You have become green slime." but don't worry about not being able to see self; if already mimicking something else at the time, implicitly be revealed */ - youmonst.m_ap_type = M_AP_MONSTER; - youmonst.mappearance = PM_GREEN_SLIME; + gy.youmonst.m_ap_type = M_AP_MONSTER; + gy.youmonst.mappearance = PM_GREEN_SLIME; + /* no message given when 't' is odd, so no automatic update of + self; force one */ + newsym(u.ux, u.uy); } - if (((Slimed & TIMEOUT) % 2L) && i >= 0L && i < SIZE(slime_texts)) { + + if ((t % 2L) != 0L && i >= 0L && i < SIZE(slime_texts)) { char buf[BUFSZ]; Strcpy(buf, slime_texts[SIZE(slime_texts) - i - 1L]); - if (nolimbs(youmonst.data) && strstri(buf, "limbs")) + if (nolimbs(gy.youmonst.data) && strstri(buf, "limbs")) (void) strsubst(buf, "limbs", "extremities"); - if (index(buf, '%')) { + if (strchr(buf, '%')) { if (i == 4L) { /* "you are turning green" */ if (!Blind) /* [what if you're already green?] */ - pline(buf, hcolor(NH_GREEN)); - } else - pline(buf, - an(Hallucination ? rndmonnam(NULL) : "green slime")); - } else - pline1(buf); + urgent_pline(buf, hcolor(NH_GREEN)); + } else { + urgent_pline(buf, an(Hallucination ? rndmonnam(NULL) + : "green slime")); + } + } else { + urgent_pline("%s", buf); + } } switch (i) { @@ -343,7 +426,7 @@ slime_dialogue() HFast = 0L; /* lose intrinsic speed */ if (!Popeye(SLIMED)) stop_occupation(); - if (multi > 0) + if (gm.multi > 0) nomul(0); break; case 2L: /* skin begins to peel */ @@ -359,8 +442,10 @@ slime_dialogue() exercise(A_DEX, FALSE); } +RESTORE_WARNING_FORMAT_NONLITERAL + void -burn_away_slime() +burn_away_slime(void) { if (Slimed) { make_slimed(0L, "The slime that covers you is burned away!"); @@ -368,24 +453,23 @@ burn_away_slime() } /* countdown timer for turning into green slime has run out; kill our hero */ -STATIC_OVL void -slimed_to_death(kptr) -struct kinfo *kptr; +staticfn void +slimed_to_death(struct kinfo *kptr) { uchar save_mvflags; /* redundant: polymon() cures sliming when polying into green slime */ - if (Upolyd && youmonst.data == &mons[PM_GREEN_SLIME]) { + if (Upolyd && gy.youmonst.data == &mons[PM_GREEN_SLIME]) { dealloc_killer(kptr); return; } /* more sure killer reason is set up */ if (kptr && kptr->name[0]) { - killer.format = kptr->format; - Strcpy(killer.name, kptr->name); + svk.killer.format = kptr->format; + Strcpy(svk.killer.name, kptr->name); } else { - killer.format = NO_KILLER_PREFIX; - Strcpy(killer.name, "turned into green slime"); + svk.killer.format = NO_KILLER_PREFIX; + Strcpy(svk.killer.name, "turned into green slime"); } dealloc_killer(kptr); @@ -401,31 +485,31 @@ struct kinfo *kptr; * [formerly implicit] change of form; polymon() takes care of that. * Temporarily ungenocide if necessary. */ - if (emits_light(youmonst.data)) - del_light_source(LS_MONSTER, monst_to_any(&youmonst)); - save_mvflags = mvitals[PM_GREEN_SLIME].mvflags; - mvitals[PM_GREEN_SLIME].mvflags = save_mvflags & ~G_GENOD; + if (emits_light(gy.youmonst.data)) + del_light_source(LS_MONSTER, monst_to_any(&gy.youmonst)); + save_mvflags = svm.mvitals[PM_GREEN_SLIME].mvflags; + svm.mvitals[PM_GREEN_SLIME].mvflags = save_mvflags & ~G_GENOD; /* become a green slime; also resets youmonst.m_ap_type+.mappearance */ (void) polymon(PM_GREEN_SLIME); - mvitals[PM_GREEN_SLIME].mvflags = save_mvflags; + svm.mvitals[PM_GREEN_SLIME].mvflags = save_mvflags; done_timeout(TURNED_SLIME, SLIMED); /* life-saved; even so, hero still has turned into green slime; player may have genocided green slimes after being infected */ - if ((mvitals[PM_GREEN_SLIME].mvflags & G_GENOD) != 0) { + if ((svm.mvitals[PM_GREEN_SLIME].mvflags & G_GENOD) != 0) { char slimebuf[BUFSZ]; - killer.format = KILLED_BY; - Strcpy(killer.name, "slimicide"); + svk.killer.format = KILLED_BY; + Strcpy(svk.killer.name, "slimicide"); /* vary the message depending upon whether life-save was due to amulet or due to declining to die in explore or wizard mode */ Strcpy(slimebuf, "green slime has been genocided..."); if (iflags.last_msg == PLNMSG_OK_DONT_DIE) /* follows "OK, so you don't die." and arg is second sentence */ - pline("Yes, you do. %s", upstart(slimebuf)); + urgent_pline("Yes, you do. %s", upstart(slimebuf)); else /* follows "The medallion crumbles to dust." */ - pline("Unfortunately, %s", slimebuf); + urgent_pline("Unfortunately, %s", slimebuf); /* die again; no possibility of amulet this time */ done(GENOCIDED); /* [should it be done_timeout(GENOCIDED, SLIMED)?] */ /* could be life-saved again (only in explore or wizard mode) @@ -446,8 +530,8 @@ static NEARDATA const char *const phaze_texts[] = { "You are feeling rather flabby.", }; -STATIC_OVL void -phaze_dialogue() +staticfn void +phaze_dialogue(void) { long i = ((HPasses_walls & TIMEOUT) / 2L); @@ -455,15 +539,40 @@ phaze_dialogue() return; if (((HPasses_walls & TIMEOUT) % 2L) && i > 0L && i <= SIZE(phaze_texts)) - pline1(phaze_texts[SIZE(phaze_texts) - i]); + pline("%s", phaze_texts[SIZE(phaze_texts) - i]); +} + +/* Similar to Passes_walls, if prayer tries to save hero from a poison + gas region but can't, (HMagical_breathing & TIMEOUT) will be set to + a small value. Unlike Passes_walls, there's no joke message. */ +static NEARDATA const char *const region_texts[] = { + "You seem to have some trouble breathing.", + "The air here seems foul.", +}; + +staticfn void +region_dialogue(void) +{ + boolean no_need_to_breathe, in_poison_gas_cloud; + long r = (HMagical_breathing & TIMEOUT), i = r / 2L; + + /* might have poly'd into non-breather or moved out of gas cloud */ + HMagical_breathing &= ~TIMEOUT; + no_need_to_breathe = Breathless; + in_poison_gas_cloud = region_danger(); + HMagical_breathing |= r; + if (no_need_to_breathe || !in_poison_gas_cloud) + return; + + if ((r % 2L) && i > 0L && i <= SIZE(region_texts)) + pline("%s", region_texts[SIZE(region_texts) - i]); } /* when a status timeout is fatal, keep the status line indicator shown during end of game rundown (and potential dumplog); timeout has already counted down to 0 by the time we get here */ -STATIC_OVL void -done_timeout(how, which) -int how, which; +staticfn void +done_timeout(int how, int which) { long *intrinsic_p = &u.uprops[which].intrinsic; @@ -472,13 +581,13 @@ int how, which; /* life-saved */ *intrinsic_p &= ~I_SPECIAL; - context.botl = TRUE; + disp.botl = TRUE; } void -nh_timeout() +nh_timeout(void) { - register struct prop *upp; + struct prop *upp; struct kinfo *kptr; boolean was_flying; int sleeptime; @@ -488,14 +597,20 @@ nh_timeout() if (flags.friday13) baseluck -= 1; + if (svq.quest_status.killed_leader) + baseluck -= 4; + + if (Role_if(PM_ARCHEOLOGIST) && uarmh && uarmh->otyp == FEDORA) + baseluck += 1; + if (u.uluck != baseluck - && moves % ((u.uhave.amulet || u.ugangr) ? 300 : 600) == 0) { + && svm.moves % ((u.uhave.amulet || u.ugangr) ? 300 : 600) == 0) { /* Cursed luckstones stop bad luck from timing out; blessed luckstones * stop good luck from timing out; normal luckstones stop both; * neither is stopped if you don't have a luckstone. * Luck is based at 0 usually, +1 if a full moon and -1 on Friday 13th */ - register int time_luck = stone_luck(FALSE); + int time_luck = stone_luck(FALSE); boolean nostone = !carrying(LUCKSTONE) && !stone_luck(TRUE); if (u.uluck > baseluck && (nostone || time_luck < 0)) @@ -513,14 +628,20 @@ nh_timeout() vomiting_dialogue(); if (Strangled) choke_dialogue(); + if (Sick) + sickness_dialogue(); if (HLevitation & TIMEOUT) levitation_dialogue(); if (HPasses_walls & TIMEOUT) phaze_dialogue(); + if (HMagical_breathing & TIMEOUT) + region_dialogue(); + if (HSleepy & TIMEOUT) + sleep_dialogue(); if (u.mtimedone && !--u.mtimedone) { if (Unchanging) - u.mtimedone = rnd(100 * youmonst.data->mlevel + 1); - else if (is_were(youmonst.data)) + u.mtimedone = rnd(100 * gy.youmonst.data->mlevel + 1); + else if (is_were(gy.youmonst.data)) you_unwere(FALSE); /* if polycontrl, asks whether to rehumanize */ else rehumanize(); @@ -552,11 +673,11 @@ nh_timeout() switch (upp - u.uprops) { case STONED: if (kptr && kptr->name[0]) { - killer.format = kptr->format; - Strcpy(killer.name, kptr->name); + svk.killer.format = kptr->format; + Strcpy(svk.killer.name, kptr->name); } else { - killer.format = NO_KILLER_PREFIX; - Strcpy(killer.name, "killed by petrification"); + svk.killer.format = NO_KILLER_PREFIX; + Strcpy(svk.killer.name, "killed by petrification"); } dealloc_killer(kptr); /* (unlike sliming, you aren't changing form here) */ @@ -569,22 +690,33 @@ nh_timeout() make_vomiting(0L, TRUE); break; case SICK: - You("die from your illness."); + /* hero might be able to bounce back from food poisoning, + but not other forms of illness */ + if ((u.usick_type & SICK_NONVOMITABLE) == 0 + && rn2(100) < ACURR(A_CON)) { + You("have recovered from your illness."); + make_sick(0, NULL, FALSE, SICK_ALL); + exercise(A_CON, FALSE); + adjattrib(A_CON, -1, 1); + break; + } + urgent_pline("You die from your illness."); if (kptr && kptr->name[0]) { - killer.format = kptr->format; - Strcpy(killer.name, kptr->name); + svk.killer.format = kptr->format; + Strcpy(svk.killer.name, kptr->name); } else { - killer.format = KILLED_BY_AN; - killer.name[0] = 0; /* take the default */ + svk.killer.format = KILLED_BY_AN; + svk.killer.name[0] = 0; /* take the default */ } dealloc_killer(kptr); - if ((m_idx = name_to_mon(killer.name)) >= LOW_PM) { + if ((m_idx = name_to_mon(svk.killer.name, + (int *) 0)) >= LOW_PM) { if (type_is_pname(&mons[m_idx])) { - killer.format = KILLED_BY; + svk.killer.format = KILLED_BY; } else if (mons[m_idx].geno & G_UNIQ) { - Strcpy(killer.name, the(killer.name)); - killer.format = KILLED_BY; + Strcpy(svk.killer.name, the(svk.killer.name)); + svk.killer.format = KILLED_BY; } } done_timeout(POISONING, SICK); @@ -592,7 +724,7 @@ nh_timeout() break; case FAST: if (!Very_fast) - You_feel("yourself slowing down%s.", + You_feel("yourself slow down%s.", Fast ? " a bit" : ""); break; case CONFUSION: @@ -608,16 +740,19 @@ nh_timeout() if (!Stunned) stop_occupation(); break; - case BLINDED: - set_itimeout(&Blinded, 1L); + case BLINDED: { + boolean was_blind = !!Blind; + + set_itimeout(&HBlinded, 1L); make_blinded(0L, TRUE); - if (!Blind) + if (was_blind && !Blind) stop_occupation(); break; + } case DEAF: set_itimeout(&HDeaf, 1L); make_deaf(0L, TRUE); - context.botl = TRUE; + disp.botl = TRUE; if (!Deaf) stop_occupation(); break; @@ -657,25 +792,83 @@ nh_timeout() } break; case LEVITATION: + /* timed Levitation is ordinary, timed Flying is via + #wizintrinsic only; still, we want to avoid float_down() + reporting "you have stopped levitating and are now flying" + when both are timing out together; if that is about to + happen, end Flying early to skip feedback about it; + assumes Levitation is handled before Flying */ + if ((HFlying & TIMEOUT) == 1L) + set_itimeout(&HFlying, 0L); /* bypass 'case FLYING' */ (void) float_down(I_SPECIAL | TIMEOUT, 0L); break; case FLYING: /* timed Flying is via #wizintrinsic only */ if (was_flying && !Flying) { - context.botl = 1; + disp.botl = TRUE; You("land."); spoteffects(TRUE); } break; + case ACID_RES: + if (!Acid_resistance) { + if (eating_dangerous_corpse(ACID_RES)) { + /* extend temporary acid resistance if in midst + of eating an acidic corpse; this will repeat + until eating is finished or interrupted */ + set_itimeout(&u.uprops[ACID_RES].intrinsic, 1L); + break; + } + if (!Unaware) + You("no longer feel safe from acid."); + } + break; + case STONE_RES: + if (!Stone_resistance) { + if (eating_dangerous_corpse(STONE_RES)) { + /* extend temporary stoning resistance if in midst + of eating a stoning corpse; this will repeat + until eating is finished or interrupted */ + set_itimeout(&u.uprops[STONE_RES].intrinsic, 1L); + break; + } + if (!Unaware) + You("no longer feel secure from petrification."); + /* no-op if not wielding a cockatrice corpse; + uswapwep case is always a no-op because two-weapon + combat is only possible with two one-handed weapons + or weapon tools, not corpses */ + wielding_corpse(uwep, (struct obj *) 0, FALSE); + wielding_corpse(uswapwep, (struct obj *) 0, FALSE); + } + break; + case FIRE_RES: + /* timed fire resistance and timed water walking combine + as a way to survive lava after multiple life-saving + attempts fail to relocate hero; skip timeout message + if hero has acquired fire resistance in the meantime */ + if (!Fire_resistance) + Your("temporary ability to survive burning has ended."); + break; + case WWALKING: + /* [see fire resistance] */ + if (!Wwalking) + Your("temporary ability to walk on liquid has ended."); + break; + case DISPLACED: + if (!Displaced) /* give a message */ + toggle_displacement((struct obj *) 0, 0L, FALSE); + break; case WARN_OF_MON: /* timed Warn_of_mon is via #wizintrinsic only */ if (!Warn_of_mon) { - context.warntype.speciesidx = NON_PM; - if (context.warntype.species) { + struct permonst *wptr = svc.context.warntype.species; + + svc.context.warntype.species = (struct permonst *) 0; + svc.context.warntype.speciesidx = NON_PM; + if (wptr) You("are no longer warned about %s.", - makeplural(context.warntype.species->mname)); - context.warntype.species = (struct permonst *) 0; - } + makeplural(wptr->pmnames[NEUTRAL])); } break; case PASSES_WALLS: @@ -687,9 +880,16 @@ nh_timeout() !Upolyd ? "normal" : "unusual"); } break; + case MAGICAL_BREATHING: + if (!Breathless) { + if (region_danger()) + You("cough%s", + Poison_resistance ? "." : " and spit blood!"); + } + break; case STRANGLED: - killer.format = KILLED_BY; - Strcpy(killer.name, + svk.killer.format = KILLED_BY; + Strcpy(svk.killer.name, (u.uburied) ? "suffocation" : "strangulation"); done_timeout(DIED, STRANGLED); /* must be declining to die in explore|wizard mode; @@ -702,18 +902,19 @@ nh_timeout() case FUMBLING: /* call this only when a move took place. */ /* otherwise handle fumbling msgs locally. */ - if (u.umoved && !Levitation) { + if (u.umoved && !(Levitation || Flying)) { slip_or_trip(); nomul(-2); - multi_reason = "fumbling"; - nomovemsg = ""; + gm.multi_reason = "fumbling"; + gn.nomovemsg = ""; /* The more you are carrying the more likely you * are to make noise when you fumble. Adjustments * to this number must be thoroughly play tested. */ - if ((inv_weight() > -500)) { - You("make a lot of noise!"); - wake_nearby(); + if ((inv_weight() > (WT_NOISY_INV * -1))) { + if (!Deaf) + You("make a lot of noise!"); + wake_nearby(FALSE); } } /* from outside means slippery ice; don't reset @@ -721,6 +922,12 @@ nh_timeout() HFumbling &= ~FROMOUTSIDE; if (Fumbling) incr_itimeout(&HFumbling, rnd(20)); + + if (iflags.defer_decor) { + /* 'mention_decor' was deferred for message sequencing + reasons; catch up now */ + deferred_decor(FALSE); + } break; case DETECT_MONSTERS: see_monsters(); @@ -728,6 +935,12 @@ nh_timeout() case GLIB: make_glib(0); /* might update persistent inventory */ break; + case PROT_FROM_SHAPE_CHANGERS: + /* timed Protection_from_shape_changers is via + #wizintrinsic only */ + if (!Protection_from_shape_changers) + restartcham(); + break; } } @@ -735,24 +948,29 @@ nh_timeout() } void -fall_asleep(how_long, wakeup_msg) -int how_long; -boolean wakeup_msg; +fall_asleep(int how_long, boolean wakeup_msg) { stop_occupation(); nomul(how_long); - multi_reason = "sleeping"; - /* generally don't notice sounds while sleeping */ - if (wakeup_msg && multi == how_long) { + gm.multi_reason = "sleeping"; +#if 0 /* this was broken; the fix for 'how_long' will result in changed + * behavior for sounds that don't go through You_hear() so needs + * testing */ + /* You_hear() produces "You dream that you hear ..." when sleeping; + other sound messages will either honor or ignore Deaf */ + if (wakeup_msg && gm.multi == how_long) { /* caller can follow with a direct call to Hear_again() if there's a need to override this when wakeup_msg is true */ - incr_itimeout(&HDeaf, how_long); - context.botl = TRUE; - afternmv = Hear_again; /* this won't give any messages */ + /* 5.0: how_long is negative so wasn't actually incrementing the + deafness timeout when it used to be passed as-is */ + incr_itimeout(&HDeaf, abs(how_long)); + disp.botl = TRUE; + ga.afternmv = Hear_again; /* this won't give any messages */ } +#endif /* early wakeup from combat won't be possible until next monster turn */ - u.usleep = monstermoves; - nomovemsg = wakeup_msg ? "You wake up." : You_can_move_again; + u.usleep = svm.moves; + gn.nomovemsg = wakeup_msg ? "You wake up." : You_can_move_again; } /* Attach an egg hatch timeout to the given egg. @@ -760,9 +978,7 @@ boolean wakeup_msg; * existing hatch timer. Pass 0L for random hatch time. */ void -attach_egg_hatch_timeout(egg, when) -struct obj *egg; -long when; +attach_egg_hatch_timeout(struct obj *egg, long when) { int i; @@ -790,8 +1006,7 @@ long when; /* prevent an egg from ever hatching */ void -kill_egg(egg) -struct obj *egg; +kill_egg(struct obj *egg) { /* stop previous timer, if any */ (void) stop_timer(HATCH_EGG, obj_to_any(egg)); @@ -799,14 +1014,12 @@ struct obj *egg; /* timer callback routine: hatch the given egg */ void -hatch_egg(arg, timeout) -anything *arg; -long timeout; +hatch_egg(anything *arg, long timeout) { struct obj *egg; struct monst *mon, *mon2; coord cc; - xchar x, y; + coordxy x, y; boolean yours, silent, knows_egg = FALSE; boolean cansee_hatchspot = FALSE; int i, mnum, hatchcount = 0; @@ -820,29 +1033,32 @@ long timeout; mnum = big_to_little(egg->corpsenm); /* The identity of one's father is learned, not innate */ yours = (egg->spe || (!flags.female && carried(egg) && !rn2(2))); - silent = (timeout != monstermoves); /* hatched while away */ + silent = (timeout != svm.moves); /* hatched while away */ - /* only can hatch when in INVENT, FLOOR, MINVENT */ + /* only can hatch when in INVENT, FLOOR, MINVENT; + get_obj_location() will fail for MIGRATING, also for CONTAINED + and BURIED when the flags for those aren't included in the call */ if (get_obj_location(egg, &x, &y, 0)) { hatchcount = rnd((int) egg->quan); cansee_hatchspot = cansee(x, y) && !silent; if (!(mons[mnum].geno & G_UNIQ) - && !(mvitals[mnum].mvflags & (G_GENOD | G_EXTINCT))) { + && !(svm.mvitals[mnum].mvflags & (G_GENOD | G_EXTINCT))) { for (i = hatchcount; i > 0; i--) { if (!enexto(&cc, x, y, &mons[mnum]) - || !(mon = makemon(&mons[mnum], cc.x, cc.y, NO_MINVENT))) + || !(mon = makemon(&mons[mnum], cc.x, cc.y, + NO_MINVENT | MM_NOMSG))) break; /* tame if your own egg hatches while you're on the same dungeon level, or any dragon egg which hatches while it's in your inventory */ if ((yours && !silent) || (carried(egg) && mon->data->mlet == S_DRAGON)) { - if (tamedog(mon, (struct obj *) 0)) { + if (tamedog(mon, (struct obj *) 0, FALSE)) { if (carried(egg) && mon->data->mlet != S_DRAGON) mon->mtame = 20; } } - if (mvitals[mnum].mvflags & G_EXTINCT) + if (svm.mvitals[mnum].mvflags & G_EXTINCT) break; /* just made last one */ mon2 = mon; /* in case makemon() fails on 2nd egg */ } @@ -862,7 +1078,7 @@ long timeout; * mind are: * + Create the hatched monster then place it on the migrating * mons list. This is tough because all makemon() is made - * to place the monster as well. Makemon() also doesn't lend + * to place the monster as well. Makemon() also doesn't lend * itself well to splitting off a "not yet placed" subroutine. * + Mark the egg as hatched, then place the monster when we * place the migrating objects. @@ -900,10 +1116,13 @@ long timeout; You_see("%s %s out of your pack!", monnambuf, locomotion(mon->data, "drop")); if (yours) { - pline("%s cries sound like \"%s%s\"", + pline("%s %s %s like \"%s%s\"", siblings ? "Their" : "Its", + ing_suffix(cry_sound(mon)), + (is_silent(mon->data) || Deaf) ? "seems" : "sounds", flags.female ? "mommy" : "daddy", egg->spe ? "." : "?"); } else if (mon->data->mlet == S_DRAGON && !Deaf) { + SetVoice(mon, 0, 80, 0); verbalize("Gleep!"); /* Mything eggs :-) */ } break; @@ -947,15 +1166,22 @@ long timeout; learn_egg_type(mnum); if (egg->quan > 0) { - /* still some eggs left */ - /* Instead of ordinary egg timeout use a short one */ + /* still some eggs left; we didn't split the stack, just + subtracted from quantity so weight needs to be updated; + for remainder of stack, add a new, short hatch timer */ attach_egg_hatch_timeout(egg, (long) rnd(12)); + /* container_weight(arg) updates arg->owt, and if contained, + its enclosing container arg->ocontainer (recursively) + [egg won't be contained due to conditions imposed above] */ + container_weight(egg); } else if (carried(egg)) { useup(egg); } else { /* free egg here because we use it above */ obj_extract_self(egg); obfree(egg, (struct obj *) 0); + if ((mon = m_at(x,y)) && !hideunder(mon) && cansee(x, y)) + redraw = TRUE; } if (redraw) newsym(x, y); @@ -964,20 +1190,18 @@ long timeout; /* Learn to recognize eggs of the given type. */ void -learn_egg_type(mnum) -int mnum; +learn_egg_type(int mnum) { /* baby monsters hatch from grown-up eggs */ mnum = little_to_big(mnum); - mvitals[mnum].mvflags |= MV_KNOWS_EGG; + svm.mvitals[mnum].mvflags |= MV_KNOWS_EGG; /* we might have just learned about other eggs being carried */ update_inventory(); } /* Attach a fig_transform timeout to the given figurine. */ void -attach_fig_transform_timeout(figurine) -struct obj *figurine; +attach_fig_transform_timeout(struct obj *figurine) { int i; @@ -994,15 +1218,13 @@ struct obj *figurine; } /* give a fumble message */ -STATIC_OVL void -slip_or_trip() +staticfn void +slip_or_trip(void) { - struct obj *otmp = vobj_at(u.ux, u.uy), *otmp2; + struct obj *otmp = vobj_at(u.ux, u.uy), *otmp2, *saddle; const char *what; char buf[BUFSZ]; - boolean on_foot = TRUE; - if (u.usteed) - on_foot = FALSE; + boolean on_foot = !u.usteed; if (otmp && on_foot && !u.uinwater && is_pool(u.ux, u.uy)) otmp = 0; @@ -1010,7 +1232,7 @@ slip_or_trip() if (otmp && on_foot) { /* trip over something in particular */ /* If there is only one item, it will have just been named - during the move, so refer to by via pronoun; otherwise, + during the move, so refer to it by pronoun; otherwise, if the top item has been or can be seen, refer to it by name; if not, look for rocks to trip over; trip over anonymous "something" if there aren't any rocks. @@ -1033,18 +1255,48 @@ slip_or_trip() } if (!uarmf && otmp->otyp == CORPSE && touch_petrifies(&mons[otmp->corpsenm]) && !Stone_resistance) { - Sprintf(killer.name, "tripping over %s corpse", - an(mons[otmp->corpsenm].mname)); - instapetrify(killer.name); + Sprintf(svk.killer.name, "tripping over %s corpse", + an(mons[otmp->corpsenm].pmnames[NEUTRAL])); + instapetrify(svk.killer.name); } - } else if (rn2(3) && is_ice(u.ux, u.uy)) { - pline("%s %s%s on the ice.", - u.usteed ? upstart(x_monnam(u.usteed, - (has_mname(u.usteed)) ? ARTICLE_NONE - : ARTICLE_THE, - (char *) 0, SUPPRESS_SADDLE, FALSE)) + } else if ((HFumbling & FROMOUTSIDE) || (is_ice(u.ux, u.uy) && !rn2(3))) { + /* is fumbling from ice alone? */ + boolean ice_only = !(EFumbling || (HFumbling & ~FROMOUTSIDE)); + + pline("%s %s %s the ice.", + u.usteed ? upstart(x_monnam(u.usteed, ARTICLE_THE, (char *) 0, + SUPPRESS_SADDLE, FALSE)) : "You", - rn2(2) ? "slip" : "slide", on_foot ? "" : "s"); + /* "steed": arbitrary value that will use third person verb + regardless of what u.usteed might be named, as opposed to + "you" (second person, which won't have final 's' added) */ + vtense(u.usteed ? "steed" : "you", rn2(2) ? "slip" : "slide"), + /* sometimes slipping due to ice occurs during turn that hero + has just moved off the ice; phrase things differently then */ + is_ice(u.ux, u.uy) ? "on" : "off"); + /* fumbling outside of ice while mounted always causes the hero to + fall from the saddle (unless it is cursed), so to avoid a + counterintuitive effect where ice makes riding _less_ hazardous, + unconditionally dismount if fumbling is from a non-ice source */ + if (!on_foot + && ((saddle = which_armor(u.usteed, W_SADDLE)) == 0 + || !saddle->cursed) + && (!ice_only || !rn2(3))) { + You("lose your balance."); + dismount_steed(DISMOUNT_FELL); + } else if (!rn2(10 + ACURR(A_DEX))) { + /* Maybe slip in a random direction. This takes place after + the hero has already changed location. If the hero is + in grid bug form, only allow forward hurtle, otherwise a + 90 degree orthogonal one after the step would make the + combined move appear to be a single diagonal step. */ + if (!NODIAG(u.umonnum)) + confdir(TRUE); /* sets u.dx and u.dy */ + /* Only hurtle if the random direction won't move hero back + to same spot where this move started. */ + if (u.ux + u.dx != u.ux0 || u.uy + u.dy != u.uy0) + hurtle(u.dx, u.dy, 1, FALSE); + } } else { if (on_foot) { switch (rn2(4)) { @@ -1063,7 +1315,11 @@ slip_or_trip() You("stumble."); break; } - } else { + + /* mounted; saddle should never end up being Null here; + don't fall off when it happens to be cursed */ + } else if ((saddle = which_armor(u.usteed, W_SADDLE)) == 0 + || !saddle->cursed) { switch (rn2(4)) { case 1: Your("%s slip out of the stirrups.", @@ -1084,11 +1340,9 @@ slip_or_trip() } } -/* Print a lamp flicker message with tailer. */ -STATIC_OVL void -see_lamp_flicker(obj, tailer) -struct obj *obj; -const char *tailer; +/* Print a lamp flicker message with tailer. Only called if seen. */ +staticfn void +see_lamp_flicker(struct obj *obj, const char *tailer) { switch (obj->where) { case OBJ_INVENT: @@ -1101,10 +1355,9 @@ const char *tailer; } } -/* Print a dimming message for brass lanterns. */ -STATIC_OVL void -lantern_message(obj) -struct obj *obj; +/* Print a dimming message for brass lanterns. Only called if seen. */ +staticfn void +lantern_message(struct obj *obj) { /* from adventure */ switch (obj->where) { @@ -1123,25 +1376,23 @@ struct obj *obj; } /* - * Timeout callback for for objects that are burning. E.g. lamps, candles. + * Timeout callback for objects that are burning. E.g. lamps, candles. * See begin_burn() for meanings of obj->age and obj->spe. */ void -burn_object(arg, timeout) -anything *arg; -long timeout; +burn_object(anything *arg, long timeout) { struct obj *obj = arg->a_obj; - boolean canseeit, many, menorah, need_newsym, need_invupdate; - xchar x, y; + boolean canseeit, many, menorah, need_newsym, need_invupdate, bytouch; + coordxy x, y; char whose[BUFSZ]; menorah = obj->otyp == CANDELABRUM_OF_INVOCATION; many = menorah ? obj->spe > 1 : obj->quan > 1L; /* timeout while away */ - if (timeout != monstermoves) { - long how_long = monstermoves - timeout; + if (timeout != svm.moves) { + long how_long = svm.moves - timeout; if (how_long >= obj->age) { obj->age = 0; @@ -1151,12 +1402,18 @@ long timeout; obj->spe = 0; /* no more candles */ obj->owt = weight(obj); } else if (Is_candle(obj) || obj->otyp == POT_OIL) { + struct monst *mtmp = NULL; + + if (obj->where == OBJ_FLOOR) + mtmp = m_at(obj->ox, obj->oy); /* get rid of candles and burning oil potions; we know this object isn't carried by hero, nor is it migrating */ obj_extract_self(obj); obfree(obj, (struct obj *) 0); obj = (struct obj *) 0; + if (mtmp) + maybe_unhide_at(mtmp->mx, mtmp->my); } } else { @@ -1174,6 +1431,11 @@ long timeout; } else { canseeit = FALSE; } + /* when carrying the light source, you can feel the heat from lit lamp + or candle so you'll be notified when it burns out even if blind at + the time; brass lantern doesn't radiate sufficient heat for that + (however, inventory formatting drops "(lit)" so player can tell) */ + bytouch = (obj->where == OBJ_INVENT && obj->otyp != BRASS_LANTERN); need_newsym = need_invupdate = FALSE; /* obj->age is the age remaining at this point. */ @@ -1184,6 +1446,7 @@ long timeout; switch (obj->where) { case OBJ_INVENT: need_invupdate = TRUE; + FALLTHROUGH; /*FALLTHRU*/ case OBJ_MINVENT: pline("%spotion of oil has burnt away.", whose); @@ -1225,9 +1488,9 @@ long timeout; case 25: if (canseeit) { - if (obj->otyp == BRASS_LANTERN) + if (obj->otyp == BRASS_LANTERN) { lantern_message(obj); - else { + } else { switch (obj->where) { case OBJ_INVENT: case OBJ_MINVENT: @@ -1243,10 +1506,11 @@ long timeout; case 0: /* even if blind you'll know if holding it */ - if (canseeit || obj->where == OBJ_INVENT) { + if (canseeit || bytouch) { switch (obj->where) { case OBJ_INVENT: need_invupdate = TRUE; + FALLTHROUGH; /*FALLTHRU*/ case OBJ_MINVENT: if (obj->otyp == BRASS_LANTERN) @@ -1268,7 +1532,7 @@ long timeout; default: /* * Someone added fuel to the lamp while it was - * lit. Just fall through and let begin burn + * lit. Just fall through and let begin_burn() * handle the new age. */ break; @@ -1321,11 +1585,12 @@ long timeout; case 0: /* we know even if blind and in our inventory */ - if (canseeit || obj->where == OBJ_INVENT) { + if (canseeit || bytouch) { if (menorah) { switch (obj->where) { case OBJ_INVENT: need_invupdate = TRUE; + FALLTHROUGH; /*FALLTHRU*/ case OBJ_MINVENT: pline("%scandelabrum's flame%s.", whose, @@ -1341,6 +1606,7 @@ long timeout; case OBJ_INVENT: /* no need_invupdate for update_inventory() necessary; useupall() -> freeinv() handles it */ + FALLTHROUGH; /*FALLTHRU*/ case OBJ_MINVENT: pline("%s %s consumed!", Yname2(obj), @@ -1367,17 +1633,23 @@ long timeout; end_burn(obj, FALSE); if (menorah) { - obj->spe = 0; + obj->spe = 0; /* no candles */ obj->owt = weight(obj); + if (carried(obj)) + need_invupdate = TRUE; } else { if (carried(obj)) { useupall(obj); } else { + boolean onfloor = (obj->where == OBJ_FLOOR); + /* clear migrating obj's destination code so obfree won't think this item is worn */ if (obj->where == OBJ_MIGRATING) obj->owornmask = 0L; obj_extract_self(obj); + if (onfloor) + maybe_unhide_at(x, y); obfree(obj, (struct obj *) 0); } obj = (struct obj *) 0; @@ -1387,7 +1659,7 @@ long timeout; default: /* * Someone added fuel (candles) to the menorah while - * it was lit. Just fall through and let begin burn + * it was lit. Just fall through and let begin_burn() * handle the new age. */ break; @@ -1437,9 +1709,7 @@ long timeout; * This is a "silent" routine - it should not print anything out. */ void -begin_burn(obj, already_lit) -struct obj *obj; -boolean already_lit; +begin_burn(struct obj *obj, boolean already_lit) { int radius = 3; long turns = 0; @@ -1517,7 +1787,7 @@ boolean already_lit; } if (obj->lamplit && !already_lit) { - xchar x, y; + coordxy x, y; if (get_obj_location(obj, &x, &y, CONTAINED_TOO | BURIED_TOO)) new_light_source(x, y, radius, LS_OBJECT, obj_to_any(obj)); @@ -1531,9 +1801,7 @@ boolean already_lit; * light source. */ void -end_burn(obj, timer_attached) -struct obj *obj; -boolean timer_attached; +end_burn(struct obj *obj, boolean timer_attached) { if (!obj->lamplit) { impossible("end_burn: obj %s not lit", xname(obj)); @@ -1556,22 +1824,19 @@ boolean timer_attached; /* * Cleanup a burning object if timer stopped. */ -static void -cleanup_burn(arg, expire_time) -anything *arg; -long expire_time; +staticfn void +cleanup_burn(anything *arg, long expire_time) { struct obj *obj = arg->a_obj; + if (!obj->lamplit) { impossible("cleanup_burn: obj %s not lit", xname(obj)); return; } del_light_source(LS_OBJECT, obj_to_any(obj)); - /* restore unused time */ - obj->age += expire_time - monstermoves; - + obj->age += expire_time - svm.moves; obj->lamplit = 0; if (obj->where == OBJ_INVENT) @@ -1579,15 +1844,15 @@ long expire_time; } void -do_storms() +do_storms(void) { int nstrike; - register int x, y; + int x, y; int dirx, diry; int count; - /* no lightning if not the air level or too often, even then */ - if (!Is_airlevel(&u.uz) || rn2(8)) + /* no lightning if not stormy level or too often, even then */ + if (!svl.level.flags.stormy || rn2(8)) return; /* the number of strikes is 8-log2(nstrike) */ @@ -1601,23 +1866,26 @@ do_storms() if (count < 100) { dirx = rn2(3) - 1; diry = rn2(3) - 1; - if (dirx != 0 || diry != 0) - buzz(-15, /* "monster" LIGHTNING spell */ - 8, x, y, dirx, diry); + if (dirx != 0 || diry != 0) { + /* BZ_M_SPELL(BZ_OFS_AD(AD_ELEC)): monster LIGHTNING spell */ + gb.buzzer = 0; /* unspecified attacker */ + buzz(BZ_M_SPELL(BZ_OFS_AD(AD_ELEC)), 8, x, y, dirx, diry); + } } } if (levl[u.ux][u.uy].typ == CLOUD) { - /* Inside a cloud during a thunder storm is deafening. */ + /* Inside a cloud during a thunderstorm is deafening. */ /* Even if already deaf, we sense the thunder's vibrations. */ + Soundeffect(se_kaboom_boom_boom, 80); pline("Kaboom!!! Boom!! Boom!!"); incr_itimeout(&HDeaf, rn1(20, 30)); - context.botl = TRUE; + disp.botl = TRUE; if (!u.uinvulnerable) { stop_occupation(); nomul(-3); - multi_reason = "hiding from thunderstorm"; - nomovemsg = 0; + gm.multi_reason = "hiding from thunderstorm"; + gn.nomovemsg = 0; } } else You_hear("a rumbling noise."); @@ -1634,7 +1902,7 @@ do_storms() * boolean start_timer(long timeout,short kind,short func_index, * anything *arg) * Start a timer of kind 'kind' that will expire at time - * monstermoves+'timeout'. Call the function at 'func_index' + * svm.moves+'timeout'. Call the function at 'func_index' * in the timeout table using argument 'arg'. Return TRUE if * a timer was started. This places the timer on a list ordered * "sooner" to "later". If an object, increment the object's @@ -1653,13 +1921,13 @@ do_storms() * Call timers that have timed out. * * Save/Restore: - * void save_timers(int fd, int mode, int range) + * void save_timers(NHFILE *, int range) * Save all timers of range 'range'. Range is either global * or local. Global timers follow game play, local timers * are saved with a level. Object and monster timers are - * saved using their respective id's instead of pointers. + * saved using their respective ids instead of pointers. * - * void restore_timers(int fd, int range, boolean ghostly, long adjust) + * void restore_timers(NHFILE *, int range, long adjust) * Restore timers of range 'range'. If from a ghost pile, * adjust the timeout by 'adjust'. The object and monster * ids are not restored until later. @@ -1682,19 +1950,14 @@ do_storms() * Check whether object has a timer of type timer_type. */ -STATIC_DCL const char *FDECL(kind_name, (SHORT_P)); -STATIC_DCL void FDECL(print_queue, (winid, timer_element *)); -STATIC_DCL void FDECL(insert_timer, (timer_element *)); -STATIC_DCL timer_element *FDECL(remove_timer, - (timer_element **, SHORT_P, ANY_P *)); -STATIC_DCL void FDECL(write_timer, (int, timer_element *)); -STATIC_DCL boolean FDECL(mon_is_local, (struct monst *)); -STATIC_DCL boolean FDECL(timer_is_local, (timer_element *)); -STATIC_DCL int FDECL(maybe_write_timer, (int, int, BOOLEAN_P)); - -/* ordered timer list */ -static timer_element *timer_base; /* "active" */ -static unsigned long timer_id = 1; +staticfn const char *kind_name(short); +staticfn void print_queue(winid, timer_element *); +staticfn void insert_timer(timer_element *); +staticfn timer_element *remove_timer(timer_element **, short, ANY_P *); +staticfn void write_timer(NHFILE *, timer_element *); +staticfn boolean mon_is_local(struct monst *); +staticfn boolean timer_is_local(timer_element *); +staticfn int maybe_write_timer(NHFILE *, int, boolean); /* If defined, then include names when printing out the timer queue */ #define VERBOSE_TIMER @@ -1703,35 +1966,38 @@ typedef struct { timeout_proc f, cleanup; #ifdef VERBOSE_TIMER const char *name; -#define TTAB(a, b, c) \ - { \ - a, b, c \ - } +#define TTAB(a, b, c) { a, b, c } #else -#define TTAB(a, b, c) \ - { \ - a, b \ - } +#define TTAB(a, b, c) { a, b } /* ignore c for !VERBOSE_TIMER */ #endif } ttable; -/* table of timeout functions */ +/* + * Table of timeout functions, listed in order of enum timeout_types: + */ static const ttable timeout_funcs[NUM_TIME_FUNCS] = { + /* object timers */ TTAB(rot_organic, (timeout_proc) 0, "rot_organic"), TTAB(rot_corpse, (timeout_proc) 0, "rot_corpse"), TTAB(revive_mon, (timeout_proc) 0, "revive_mon"), + TTAB(zombify_mon, (timeout_proc) 0, "zombify_mon"), TTAB(burn_object, cleanup_burn, "burn_object"), TTAB(hatch_egg, (timeout_proc) 0, "hatch_egg"), TTAB(fig_transform, (timeout_proc) 0, "fig_transform"), - TTAB(melt_ice_away, (timeout_proc) 0, "melt_ice_away") + TTAB(shrink_glob, (timeout_proc) 0, "shrink_glob"), + /* level timers */ + TTAB(melt_ice_away, (timeout_proc) 0, "melt_ice_away"), + /* currently no monster or global timers */ }; #undef TTAB -STATIC_OVL const char * -kind_name(kind) -short kind; +staticfn const char * +kind_name(short kind) { switch (kind) { + case TIMER_NONE: + impossible("no timer type"); + return "none"; case TIMER_LEVEL: return "level"; case TIMER_GLOBAL: @@ -1744,10 +2010,8 @@ short kind; return "unknown"; } -STATIC_OVL void -print_queue(win, base) -winid win; -timer_element *base; +staticfn void +print_queue(winid win, timer_element *base) { timer_element *curr; char buf[BUFSZ]; @@ -1759,7 +2023,7 @@ timer_element *base; for (curr = base; curr; curr = curr->next) { #ifdef VERBOSE_TIMER Sprintf(buf, " %4ld %4ld %-6s %s(%s)", curr->timeout, - curr->tid, kind_name(curr->kind), + (long) curr->tid, kind_name(curr->kind), timeout_funcs[curr->func_index].name, fmt_ptr((genericptr_t) curr->arg.a_void)); #else @@ -1772,8 +2036,9 @@ timer_element *base; } } +/* the #timeout command */ int -wiz_timeout_queue() +wiz_timeout_queue(void) { winid win; char buf[BUFSZ]; @@ -1783,16 +2048,16 @@ wiz_timeout_queue() win = create_nhwindow(NHW_MENU); /* corner text window */ if (win == WIN_ERR) - return 0; + return ECMD_OK; - Sprintf(buf, "Current time = %ld.", monstermoves); + Sprintf(buf, "Current time = %ld.", svm.moves); putstr(win, 0, buf); putstr(win, 0, ""); putstr(win, 0, "Active timeout queue:"); putstr(win, 0, ""); - print_queue(win, timer_base); + print_queue(win, gt.timer_base); - /* Timed properies: + /* Timed properties: * check every one; the majority can't obtain temporary timeouts in * normal play but those can be forced via the #wizintrinsic command. */ @@ -1805,7 +2070,7 @@ wiz_timeout_queue() if ((ln = (int) strlen(propname)) > longestlen) longestlen = ln; } - if (specindx == 0 && p == FIRE_RES) + if (specindx == 0 && p == COLD_RES) /* was FIRE_RES but has changed */ specindx = i; } putstr(win, 0, ""); @@ -1819,7 +2084,7 @@ wiz_timeout_queue() intrinsic = u.uprops[p].intrinsic; if (intrinsic & TIMEOUT) { if (specindx > 0 && i >= specindx) { - putstr(win, 0, " -- settable via #wizinstrinc only --"); + putstr(win, 0, " -- settable via #wizintrinsic only --"); specindx = 0; } /* timeout value can be up to 16777215 (0x00ffffff) but @@ -1832,27 +2097,121 @@ wiz_timeout_queue() } } } + if (u.uswldtim) { + putstr(win, 0, ""); + /* decremented when engulfer makes a move, so can last longer than + the number of turns reported if engulfer is slow */ + Sprintf(buf, "Swallow countdown is %u.", u.uswldtim); + putstr(win, 0, buf); + } + if (u.uinvault) { + putstr(win, 0, ""); + Sprintf(buf, "Vault counter is %d.", u.uinvault); + putstr(win, 0, buf); + } + if (any_visible_region()) { + visible_region_summary(win); + } + if (svl.level.flags.stasis_until >= svm.moves) { + putstr(win, 0, ""); + Sprintf(buf, "Level is no-teleport for %ld %s.", + svl.level.flags.stasis_until - svm.moves + 1L, + (svl.level.flags.stasis_until - svm.moves > 0L) + ? "turns" : "more turn"); + putstr(win, 0, buf); + } display_nhwindow(win, FALSE); destroy_nhwindow(win); - return 0; + return ECMD_OK; } void -timer_sanity_check() +timer_sanity_check(void) { timer_element *curr; - - /* this should be much more complete */ - for (curr = timer_base; curr; curr = curr->next) - if (curr->kind == TIMER_OBJECT) { - struct obj *obj = curr->arg.a_obj; + unsigned long t_id; + coordxy x, y; + + for (curr = gt.timer_base; curr; curr = curr->next) { + t_id = curr->tid; + switch (curr->kind) { + case TIMER_OBJECT: { + /* TODO? verify that the timer type is attached to applicable + object (egg for hatch, glob for shrink, and so forth) */ + struct obj *obj = curr->arg.a_obj, *top; + char *obj_adr = fmt_ptr((genericptr_t) obj); + int owhere = obj->where; if (obj->timed == 0) { - impossible("timer sanity: untimed obj %s, timer %ld", - fmt_ptr((genericptr_t) obj), curr->tid); + impossible("timer sanity: untimed obj %s, timer %lu", + obj_adr, t_id); + } + x = y = 0; + /* if obj is in a container, possibly a nested one, figure out + where the outermost container is */ + for (top = obj; top; top = top->ocontainer) + if ((owhere = top->where) != OBJ_CONTAINED) + break; + assert(top != NULL); + if (owhere == OBJ_MIGRATING + || (owhere == OBJ_MINVENT && !mon_is_local(top->ocarry))) { + /* migrating directly or carried by migrating monster */ + ; /* not able to validate location so skip checks */ + } else if (!get_obj_location(obj, &x, &y, + CONTAINED_TOO | BURIED_TOO)) { + /* free? or on a shop's used-up bill? */ + impossible( + "timer sanity: can't locate obj %s [where=%d], timer %lu", + obj_adr, obj->where, t_id); + } else if (!isok(x, y)) { + impossible( + "timer sanity: obj %s [where=%d] located at <%d,%d>, timer %lu", + obj_adr, obj->where, x, y, t_id); + } + break; + } + case TIMER_MONSTER: + impossible("timer sanity: unexpected monster timer %lu", t_id); + break; + case TIMER_LEVEL: { + long lwhere = curr->arg.a_long; + + x = (coordxy) ((lwhere >> 16) & 0xFFFF); + y = (coordxy) (lwhere & 0xFFFF); + if (isok(x, y)) { + /* replicate isok() in order to convince static analysis + that the decoding via '& 0xFFFF' hasn't produced a value + too big for levl[][] and that the cast to a narrower type + hasn't intruded on the sign bit to yield a negative value; + the analyzer isn't aware that isok() filters such things */ + assert(x > 0 && x < COLNO && y >= 0 && y < ROWNO); + + if (curr->func_index == MELT_ICE_AWAY && !is_ice(x, y) + /* the terrain under the span of an open drawbridge might + be frozen moat; is_ice() only checks for that when + the drawbridge is closed (and terrain here would be + DRAWBRIGE_UP) */ + && !(levl[x][y].typ == DRAWBRIDGE_DOWN + && (levl[x][y].drawbridgemask & DB_UNDER) == DB_ICE)) + impossible( + "timer sanity: melt timer %lu on non-ice %d <%d,%d>", + t_id, levl[x][y].typ, x, y); + } else { + impossible("timer sanity: spot timer %lu at <%d,%d>", + t_id, x, y); } + break; + } + case TIMER_GLOBAL: + impossible("timer sanity: unexpected global timer %lu", t_id); + break; + default: + impossible("timer sanity: unknown timer %lu, type: %d", + t_id, curr->kind); + break; } + } } /* @@ -1860,22 +2219,23 @@ timer_sanity_check() * Do this until their time is less than or equal to the move count. */ void -run_timers() +run_timers(void) { timer_element *curr; /* * Always use the first element. Elements may be added or deleted at - * any time. The list is ordered, we are done when the first element + * any time. The list is ordered; we are done when the first element * is in the future. */ - while (timer_base && timer_base->timeout <= monstermoves) { - curr = timer_base; - timer_base = curr->next; + while (gt.timer_base && gt.timer_base->timeout <= svm.moves) { + curr = gt.timer_base; + gt.timer_base = curr->next; if (curr->kind == TIMER_OBJECT) (curr->arg.a_obj)->timed--; (*timeout_funcs[curr->func_index].f)(&curr->arg, curr->timeout); + (void) memset((genericptr_t) curr, 0, sizeof(timer_element)); free((genericptr_t) curr); } } @@ -1884,20 +2244,20 @@ run_timers() * Start a timer. Return TRUE if successful. */ boolean -start_timer(when, kind, func_index, arg) -long when; -short kind; -short func_index; -anything *arg; +start_timer( + long when, + short kind, + short func_index, + anything *arg) { timer_element *gnu, *dup; - if (kind < 0 || kind >= NUM_TIMER_KINDS + if (kind <= TIMER_NONE || kind >= NUM_TIMER_KINDS || func_index < 0 || func_index >= NUM_TIME_FUNCS) panic("start_timer (%s: %d)", kind_name(kind), (int) func_index); /* fail if already has a timer running */ - for (dup = timer_base; dup; dup = dup->next) + for (dup = gt.timer_base; dup; dup = dup->next) if (dup->kind == kind && dup->func_index == func_index && dup->arg.a_void == arg->a_void) @@ -1917,8 +2277,8 @@ anything *arg; gnu = (timer_element *) alloc(sizeof *gnu); (void) memset((genericptr_t) gnu, 0, sizeof *gnu); gnu->next = 0; - gnu->tid = timer_id++; - gnu->timeout = monstermoves + when; + gnu->tid = svt.timer_id++; + gnu->timeout = svm.moves + when; gnu->kind = kind; gnu->needs_fixup = 0; gnu->func_index = func_index; @@ -1936,23 +2296,23 @@ anything *arg; * remaining until it would have gone off, 0 if not found. */ long -stop_timer(func_index, arg) -short func_index; -anything *arg; +stop_timer(short func_index, anything *arg) { + timeout_proc cleanup_func; timer_element *doomed; long timeout; - doomed = remove_timer(&timer_base, func_index, arg); + doomed = remove_timer(>.timer_base, func_index, arg); if (doomed) { timeout = doomed->timeout; if (doomed->kind == TIMER_OBJECT) (arg->a_obj)->timed--; - if (timeout_funcs[doomed->func_index].cleanup) - (*timeout_funcs[doomed->func_index].cleanup)(arg, timeout); + if ((cleanup_func = timeout_funcs[doomed->func_index].cleanup) != 0) + (*cleanup_func)(arg, timeout); + (void) memset((genericptr_t) doomed, 0, sizeof(timer_element)); free((genericptr_t) doomed); - return (timeout - monstermoves); + return (timeout - svm.moves); } return 0L; } @@ -1961,13 +2321,11 @@ anything *arg; * Find the timeout of specified timer; return 0 if none. */ long -peek_timer(type, arg) -short type; -anything *arg; +peek_timer(short type, anything *arg) { timer_element *curr; - for (curr = timer_base; curr; curr = curr->next) { + for (curr = gt.timer_base; curr; curr = curr->next) { if (curr->func_index == type && curr->arg.a_void == arg->a_void) return curr->timeout; } @@ -1978,13 +2336,12 @@ anything *arg; * Move all object timers from src to dest, leaving src untimed. */ void -obj_move_timers(src, dest) -struct obj *src, *dest; +obj_move_timers(struct obj *src, struct obj *dest) { int count; timer_element *curr; - for (count = 0, curr = timer_base; curr; curr = curr->next) + for (count = 0, curr = gt.timer_base; curr; curr = curr->next) if (curr->kind == TIMER_OBJECT && curr->arg.a_obj == src) { curr->arg.a_obj = dest; dest->timed++; @@ -1999,15 +2356,14 @@ struct obj *src, *dest; * Find all object timers and duplicate them for the new object "dest". */ void -obj_split_timers(src, dest) -struct obj *src, *dest; +obj_split_timers(struct obj *src, struct obj *dest) { timer_element *curr, *next_timer = 0; - for (curr = timer_base; curr; curr = next_timer) { + for (curr = gt.timer_base; curr; curr = next_timer) { next_timer = curr->next; /* things may be inserted */ if (curr->kind == TIMER_OBJECT && curr->arg.a_obj == src) { - (void) start_timer(curr->timeout - monstermoves, TIMER_OBJECT, + (void) start_timer(curr->timeout - svm.moves, TIMER_OBJECT, curr->func_index, obj_to_any(dest)); } } @@ -2018,21 +2374,21 @@ struct obj *src, *dest; * all object pointers are unique. */ void -obj_stop_timers(obj) -struct obj *obj; +obj_stop_timers(struct obj *obj) { + timeout_proc cleanup_func; timer_element *curr, *prev, *next_timer = 0; - for (prev = 0, curr = timer_base; curr; curr = next_timer) { + for (prev = 0, curr = gt.timer_base; curr; curr = next_timer) { next_timer = curr->next; if (curr->kind == TIMER_OBJECT && curr->arg.a_obj == obj) { if (prev) prev->next = curr->next; else - timer_base = curr->next; - if (timeout_funcs[curr->func_index].cleanup) - (*timeout_funcs[curr->func_index].cleanup)(&curr->arg, - curr->timeout); + gt.timer_base = curr->next; + if ((cleanup_func = timeout_funcs[curr->func_index].cleanup) != 0) + (*cleanup_func)(&curr->arg, curr->timeout); + (void) memset((genericptr_t) curr, 0, sizeof(timer_element)); free((genericptr_t) curr); } else { prev = curr; @@ -2045,9 +2401,7 @@ struct obj *obj; * Check whether object has a timer of type timer_type. */ boolean -obj_has_timer(object, timer_type) -struct obj *object; -short timer_type; +obj_has_timer(struct obj *object, short timer_type) { long timeout = peek_timer(timer_type, obj_to_any(object)); @@ -2059,24 +2413,23 @@ short timer_type; * */ void -spot_stop_timers(x, y, func_index) -xchar x, y; -short func_index; +spot_stop_timers(coordxy x, coordxy y, short func_index) { + timeout_proc cleanup_func; timer_element *curr, *prev, *next_timer = 0; long where = (((long) x << 16) | ((long) y)); - for (prev = 0, curr = timer_base; curr; curr = next_timer) { + for (prev = 0, curr = gt.timer_base; curr; curr = next_timer) { next_timer = curr->next; if (curr->kind == TIMER_LEVEL && curr->func_index == func_index && curr->arg.a_long == where) { if (prev) prev->next = curr->next; else - timer_base = curr->next; - if (timeout_funcs[curr->func_index].cleanup) - (*timeout_funcs[curr->func_index].cleanup)(&curr->arg, - curr->timeout); + gt.timer_base = curr->next; + if ((cleanup_func = timeout_funcs[curr->func_index].cleanup) != 0) + (*cleanup_func)(&curr->arg, curr->timeout); + (void) memset((genericptr_t) curr, 0, sizeof(timer_element)); free((genericptr_t) curr); } else { prev = curr; @@ -2089,14 +2442,12 @@ short func_index; * Returns 0L if no such timer. */ long -spot_time_expires(x, y, func_index) -xchar x, y; -short func_index; +spot_time_expires(coordxy x, coordxy y, short func_index) { timer_element *curr; long where = (((long) x << 16) | ((long) y)); - for (curr = timer_base; curr; curr = curr->next) { + for (curr = gt.timer_base; curr; curr = curr->next) { if (curr->kind == TIMER_LEVEL && curr->func_index == func_index && curr->arg.a_long == where) return curr->timeout; @@ -2105,22 +2456,19 @@ short func_index; } long -spot_time_left(x, y, func_index) -xchar x, y; -short func_index; +spot_time_left(coordxy x, coordxy y, short func_index) { long expires = spot_time_expires(x, y, func_index); - return (expires > 0L) ? expires - monstermoves : 0L; + return (expires > 0L) ? expires - svm.moves : 0L; } /* Insert timer into the global queue */ -STATIC_OVL void -insert_timer(gnu) -timer_element *gnu; +staticfn void +insert_timer(timer_element *gnu) { timer_element *curr, *prev; - for (prev = 0, curr = timer_base; curr; prev = curr, curr = curr->next) + for (prev = 0, curr = gt.timer_base; curr; prev = curr, curr = curr->next) if (curr->timeout >= gnu->timeout) break; @@ -2128,14 +2476,14 @@ timer_element *gnu; if (prev) prev->next = gnu; else - timer_base = gnu; + gt.timer_base = gnu; } -STATIC_OVL timer_element * -remove_timer(base, func_index, arg) -timer_element **base; -short func_index; -anything *arg; +staticfn timer_element * +remove_timer( + timer_element **base, + short func_index, + anything *arg) { timer_element *prev, *curr; @@ -2153,46 +2501,44 @@ anything *arg; return curr; } -STATIC_OVL void -write_timer(fd, timer) -int fd; -timer_element *timer; +staticfn void +write_timer(NHFILE *nhfp, timer_element *timer) { anything arg_save; - arg_save = zeroany; + arg_save = cg.zeroany; switch (timer->kind) { case TIMER_GLOBAL: case TIMER_LEVEL: /* assume no pointers in arg */ - bwrite(fd, (genericptr_t) timer, sizeof(timer_element)); + Sfo_fe(nhfp, timer, "timer"); break; case TIMER_OBJECT: - if (timer->needs_fixup) - bwrite(fd, (genericptr_t) timer, sizeof(timer_element)); - else { + if (timer->needs_fixup) { + Sfo_fe(nhfp, timer, "timer"); + } else { /* replace object pointer with id */ arg_save.a_obj = timer->arg.a_obj; - timer->arg = zeroany; + timer->arg = cg.zeroany; timer->arg.a_uint = (arg_save.a_obj)->o_id; timer->needs_fixup = 1; - bwrite(fd, (genericptr_t) timer, sizeof(timer_element)); + Sfo_fe(nhfp, timer, "timer"); timer->arg.a_obj = arg_save.a_obj; timer->needs_fixup = 0; } break; case TIMER_MONSTER: - if (timer->needs_fixup) - bwrite(fd, (genericptr_t) timer, sizeof(timer_element)); - else { + if (timer->needs_fixup) { + Sfo_fe(nhfp, timer, "timer"); + } else { /* replace monster pointer with id */ arg_save.a_monst = timer->arg.a_monst; - timer->arg = zeroany; + timer->arg = cg.zeroany; timer->arg.a_uint = (arg_save.a_monst)->m_id; timer->needs_fixup = 1; - bwrite(fd, (genericptr_t) timer, sizeof(timer_element)); + Sfo_fe(nhfp, timer, "timer"); timer->arg.a_monst = arg_save.a_monst; timer->needs_fixup = 0; } @@ -2204,13 +2550,14 @@ timer_element *timer; } } +DISABLE_WARNING_UNREACHABLE_CODE + /* * Return TRUE if the object will stay on the level when the level is * saved. */ boolean -obj_is_local(obj) -struct obj *obj; +obj_is_local(struct obj *obj) { switch (obj->where) { case OBJ_INVENT: @@ -2225,6 +2572,7 @@ struct obj *obj; return mon_is_local(obj->ocarry); } panic("obj_is_local"); + /*NOTREACHED*/ return FALSE; } @@ -2232,17 +2580,16 @@ struct obj *obj; * Return TRUE if the given monster will stay on the level when the * level is saved. */ -STATIC_OVL boolean -mon_is_local(mon) -struct monst *mon; +staticfn boolean +mon_is_local(struct monst *mon) { struct monst *curr; - for (curr = migrating_mons; curr; curr = curr->nmon) + for (curr = gm.migrating_mons; curr; curr = curr->nmon) if (curr == mon) return FALSE; - /* `mydogs' is used during level changes, never saved and restored */ - for (curr = mydogs; curr; curr = curr->nmon) + /* `gm.mydogs' is used during level changes, never saved and restored */ + for (curr = gm.mydogs; curr; curr = curr->nmon) if (curr == mon) return FALSE; return TRUE; @@ -2252,9 +2599,8 @@ struct monst *mon; * Return TRUE if the timer is attached to something that will stay on the * level when the level is saved. */ -STATIC_OVL boolean -timer_is_local(timer) -timer_element *timer; +staticfn boolean +timer_is_local(timer_element *timer) { switch (timer->kind) { case TIMER_LEVEL: @@ -2267,38 +2613,36 @@ timer_element *timer; return mon_is_local(timer->arg.a_monst); } panic("timer_is_local"); + /*NOTREACHED*/ return FALSE; } +RESTORE_WARNING_UNREACHABLE_CODE + /* * Part of the save routine. Count up the number of timers that would * be written. If write_it is true, actually write the timer. */ -STATIC_OVL int -maybe_write_timer(fd, range, write_it) -int fd, range; -boolean write_it; +staticfn int +maybe_write_timer(NHFILE *nhfp, int range, boolean write_it) { int count = 0; timer_element *curr; - for (curr = timer_base; curr; curr = curr->next) { + for (curr = gt.timer_base; curr; curr = curr->next) { if (range == RANGE_GLOBAL) { /* global timers */ if (!timer_is_local(curr)) { count++; - if (write_it) - write_timer(fd, curr); + if (write_it) write_timer(nhfp, curr); } - } else { /* local timers */ if (timer_is_local(curr)) { count++; - if (write_it) - write_timer(fd, curr); + if (write_it) write_timer(nhfp, curr); } } } @@ -2306,6 +2650,7 @@ boolean write_it; return count; } + /* * Save part of the timer list. The parameter 'range' specifies either * global or level timers to save. The timer ID is saved with the global @@ -2316,34 +2661,34 @@ boolean write_it; * + timeouts that follow obj & monst that are migrating * * Level range: - * + timeouts that are level specific (e.g. storms) + * + timeouts that are level-specific (e.g. storms) * + timeouts that stay with the level (obj & monst) */ void -save_timers(fd, mode, range) -int fd, mode, range; +save_timers(NHFILE *nhfp, int range) { timer_element *curr, *prev, *next_timer = 0; int count; - if (perform_bwrite(mode)) { - if (range == RANGE_GLOBAL) - bwrite(fd, (genericptr_t) &timer_id, sizeof(timer_id)); - - count = maybe_write_timer(fd, range, FALSE); - bwrite(fd, (genericptr_t) &count, sizeof count); - (void) maybe_write_timer(fd, range, TRUE); + if (update_file(nhfp)) { + if (range == RANGE_GLOBAL) { + Sfo_ulong(nhfp, &svt.timer_id, "timer-timer_id"); + } + count = maybe_write_timer(nhfp, range, FALSE); + Sfo_int(nhfp, &count, "timer-timer_count"); + (void) maybe_write_timer(nhfp, range, TRUE); } - if (release_data(mode)) { - for (prev = 0, curr = timer_base; curr; curr = next_timer) { + if (release_data(nhfp)) { + for (prev = 0, curr = gt.timer_base; curr; curr = next_timer) { next_timer = curr->next; /* in case curr is removed */ if (!(!!(range == RANGE_LEVEL) ^ !!timer_is_local(curr))) { if (prev) prev->next = curr->next; else - timer_base = curr->next; + gt.timer_base = curr->next; + (void) memset((genericptr_t) curr, 0, sizeof(timer_element)); free((genericptr_t) curr); /* prev stays the same */ } else { @@ -2352,60 +2697,63 @@ int fd, mode, range; } } } +#endif /* !SFCTOOL */ /* * Pull in the structures from disk, but don't recalculate the object and * monster pointers. */ void -restore_timers(fd, range, ghostly, adjust) -int fd, range; -boolean ghostly; /* restoring from a ghost level */ -long adjust; /* how much to adjust timeout */ +restore_timers(NHFILE *nhfp, int range, long adjust) { - int count; + int count = 0; timer_element *curr; + boolean ghostly = (nhfp->ftype == NHF_BONESFILE); /* from a ghost level */ - if (range == RANGE_GLOBAL) - mread(fd, (genericptr_t) &timer_id, sizeof timer_id); + if (range == RANGE_GLOBAL) { + Sfi_ulong(nhfp, &svt.timer_id, "timer-timer_id"); + } /* restore elements */ - mread(fd, (genericptr_t) &count, sizeof count); + Sfi_int(nhfp, &count, "timer-timer_count"); while (count-- > 0) { curr = (timer_element *) alloc(sizeof(timer_element)); - mread(fd, (genericptr_t) curr, sizeof(timer_element)); + Sfi_fe(nhfp, curr, "timer"); if (ghostly) curr->timeout += adjust; +#ifndef SFCTOOL insert_timer(curr); +#endif } } +#ifndef SFCTOOL +DISABLE_WARNING_FORMAT_NONLITERAL + /* to support '#stats' wizard-mode command */ void -timer_stats(hdrfmt, hdrbuf, count, size) -const char *hdrfmt; -char *hdrbuf; -long *count, *size; +timer_stats(const char *hdrfmt, char *hdrbuf, long *count, long *size) { timer_element *te; Sprintf(hdrbuf, hdrfmt, (long) sizeof (timer_element)); *count = *size = 0L; - for (te = timer_base; te; te = te->next) { + for (te = gt.timer_base; te; te = te->next) { ++*count; *size += (long) sizeof *te; } } -/* reset all timers that are marked for reseting */ +RESTORE_WARNING_FORMAT_NONLITERAL + +/* reset all timers that are marked for resetting */ void -relink_timers(ghostly) -boolean ghostly; +relink_timers(boolean ghostly) { timer_element *curr; unsigned nid; - for (curr = timer_base; curr; curr = curr->next) { + for (curr = gt.timer_base; curr; curr = curr->next) { if (curr->needs_fixup) { if (curr->kind == TIMER_OBJECT) { if (ghostly) { @@ -2415,7 +2763,7 @@ boolean ghostly; nid = curr->arg.a_uint; curr->arg.a_obj = find_oid(nid); if (!curr->arg.a_obj) - panic("cant find o_id %d", nid); + panic("can't find o_id %d", nid); curr->needs_fixup = 0; } else if (curr->kind == TIMER_MONSTER) { panic("relink_timers: no monster timer implemented"); @@ -2424,5 +2772,6 @@ boolean ghostly; } } } +#endif /* !SFCTOOL */ /*timeout.c*/ diff --git a/src/topten.c b/src/topten.c index 8ed5d2f54..f88e7a7d4 100644 --- a/src/topten.c +++ b/src/topten.c @@ -1,30 +1,27 @@ -/* NetHack 3.6 topten.c $NHDT-Date: 1450451497 2015/12/18 15:11:37 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.44 $ */ +/* NetHack 5.0 topten.c $NHDT-Date: 1606009004 2020/11/22 01:36:44 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.74 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2012. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" #include "dlb.h" -#ifdef SHORT_FILENAMES -#include "patchlev.h" -#else -#include "patchlevel.h" -#endif -#ifdef VMS +#if defined(VMS) && !defined(UPDATE_RECORD_IN_PLACE) /* We don't want to rewrite the whole file, because that entails - creating a new version which requires that the old one be deletable. */ + creating a new version which requires that the old one be deletable. + [Write and Delete are separate permissions on VMS. 'record' should + be writable but not deletable there.] */ #define UPDATE_RECORD_IN_PLACE #endif /* * Updating in place can leave junk at the end of the file in some - * circumstances (if it shrinks and the O.S. doesn't have a straightforward + * circumstances (if it shrinks and the OS doesn't have a straightforward * way to truncate it). The trailing junk is harmless and the code * which reads the scores will ignore it. */ #ifdef UPDATE_RECORD_IN_PLACE -static long final_fpos; +static long final_fpos; /* [note: do not move this to the 'g' struct] */ #endif #define done_stopprint program_state.stopprint @@ -38,8 +35,9 @@ static long final_fpos; #define DTHSZ 100 #define ROLESZ 3 +/* NLE: Use NLE specific function */ #define NLE_XLOG_INCLUDE_FILE -extern char * FDECL(nle_ttyrecname, ()); +extern char *nle_ttyrecname(void); struct toptenentry { struct toptenentry *tt_next; @@ -58,42 +56,46 @@ struct toptenentry { char plalign[ROLESZ + 1]; char name[NAMSZ + 1]; char death[DTHSZ + 1]; -} * tt_head; +}; +static struct toptenentry *tt_head; /* size big enough to read in all the string fields at once; includes room for separating space or trailing newline plus string terminator */ #define SCANBUFSZ (4 * (ROLESZ + 1) + (NAMSZ + 1) + (DTHSZ + 1) + 1) -STATIC_DCL void FDECL(topten_print, (const char *)); -STATIC_DCL void FDECL(topten_print_bold, (const char *)); -STATIC_DCL void NDECL(outheader); -STATIC_DCL void FDECL(outentry, (int, struct toptenentry *, BOOLEAN_P)); -STATIC_DCL void FDECL(discardexcess, (FILE *)); -STATIC_DCL void FDECL(readentry, (FILE *, struct toptenentry *)); -STATIC_DCL void FDECL(writeentry, (FILE *, struct toptenentry *)); +static struct toptenentry zerott; + +staticfn void topten_print(const char *); +staticfn void topten_print_bold(const char *); +staticfn void outheader(void); +staticfn void outentry(int, struct toptenentry *, boolean); +staticfn void discardexcess(FILE *); +staticfn void readentry(FILE *, struct toptenentry *); +staticfn void writeentry(FILE *, struct toptenentry *); #ifdef XLOGFILE -STATIC_DCL void FDECL(writexlentry, (FILE *, struct toptenentry *, int)); -STATIC_DCL long NDECL(encodexlogflags); -STATIC_DCL long NDECL(encodeconduct); -STATIC_DCL long NDECL(encodeachieve); +staticfn void writexlentry(FILE *, struct toptenentry *, int); +staticfn long encodexlogflags(void); +staticfn long encodeconduct(void); +staticfn long encodeachieve(boolean); +staticfn void add_achieveX(char *, const char *, boolean); +staticfn char *encode_extended_achievements(char *); +staticfn char *encode_extended_conducts(char *); #endif -STATIC_DCL void FDECL(free_ttlist, (struct toptenentry *)); -STATIC_DCL int FDECL(classmon, (char *, BOOLEAN_P)); -STATIC_DCL int FDECL(score_wanted, (BOOLEAN_P, int, struct toptenentry *, int, - const char **, int)); +staticfn void free_ttlist(struct toptenentry *); +staticfn int classmon(char *); +staticfn int score_wanted(boolean, int, struct toptenentry *, int, + const char **, int); #ifdef NO_SCAN_BRACK -STATIC_DCL void FDECL(nsb_mung_line, (char *)); -STATIC_DCL void FDECL(nsb_unmung_line, (char *)); +staticfn void nsb_mung_line(char *); +staticfn void nsb_unmung_line(char *); #endif -static winid toptenwin = WIN_ERR; - -/* "killed by",&c ["an"] 'killer.name' */ +/* "killed by",&c ["an"] 'svk.killer.name' */ void -formatkiller(buf, siz, how, incl_helpless) -char *buf; -unsigned siz; -int how; -boolean incl_helpless; +formatkiller( + char *buf, + unsigned siz, + int how, + boolean incl_helpless) { static NEARDATA const char *const killed_by_prefix[] = { /* DIED, CHOKING, POISONING, STARVING, */ @@ -106,21 +108,23 @@ boolean incl_helpless; "", "", "", "", "" }; unsigned l; - char c, *kname = killer.name; + char c, *kname = svk.killer.name; buf[0] = '\0'; /* lint suppression */ - switch (killer.format) { + switch (svk.killer.format) { default: - impossible("bad killer format? (%d)", killer.format); + impossible("bad killer format? (%d)", svk.killer.format); + FALLTHROUGH; /*FALLTHRU*/ case NO_KILLER_PREFIX: break; case KILLED_BY_AN: kname = an(kname); + FALLTHROUGH; /*FALLTHRU*/ case KILLED_BY: (void) strncat(buf, killed_by_prefix[how], siz - 1); - l = strlen(buf); + l = Strlen(buf); buf += l, siz -= l; break; } @@ -149,40 +153,38 @@ boolean incl_helpless; } *buf = '\0'; - if (incl_helpless && multi) { + if (incl_helpless && gm.multi < 0) { /* X <= siz: 'sizeof "string"' includes 1 for '\0' terminator */ - if (multi_reason && strlen(multi_reason) + sizeof ", while " <= siz) - Sprintf(buf, ", while %s", multi_reason); - /* either multi_reason wasn't specified or wouldn't fit */ + if (gm.multi_reason + && strlen(gm.multi_reason) + sizeof ", while " <= siz) + Sprintf(buf, ", while %s", gm.multi_reason); + /* either gm.multi_reason wasn't specified or wouldn't fit */ else if (sizeof ", while helpless" <= siz) Strcpy(buf, ", while helpless"); /* else extra death info won't fit, so leave it out */ } } -STATIC_OVL void -topten_print(x) -const char *x; +staticfn void +topten_print(const char *x) { - if (toptenwin == WIN_ERR) + if (gt.toptenwin == WIN_ERR) raw_print(x); else - putstr(toptenwin, ATR_NONE, x); + putstr(gt.toptenwin, ATR_NONE, x); } -STATIC_OVL void -topten_print_bold(x) -const char *x; +staticfn void +topten_print_bold(const char *x) { - if (toptenwin == WIN_ERR) + if (gt.toptenwin == WIN_ERR) raw_print_bold(x); else - putstr(toptenwin, ATR_BOLD, x); + putstr(gt.toptenwin, ATR_BOLD, x); } int -observable_depth(lev) -d_level *lev; +observable_depth(d_level *lev) { #if 0 /* if we ever randomize the order of the elemental planes, we @@ -206,9 +208,8 @@ d_level *lev; } /* throw away characters until current record has been entirely consumed */ -STATIC_OVL void -discardexcess(rfile) -FILE *rfile; +staticfn void +discardexcess(FILE *rfile) { int c; @@ -217,13 +218,13 @@ FILE *rfile; } while (c != '\n' && c != EOF); } -STATIC_OVL void -readentry(rfile, tt) -FILE *rfile; -struct toptenentry *tt; +DISABLE_WARNING_FORMAT_NONLITERAL + +staticfn void +readentry(FILE *rfile, struct toptenentry *tt) { char inbuf[SCANBUFSZ], s1[SCANBUFSZ], s2[SCANBUFSZ], s3[SCANBUFSZ], - s4[SCANBUFSZ], s5[SCANBUFSZ], s6[SCANBUFSZ]; + s4[SCANBUFSZ], s5[SCANBUFSZ], s6[SCANBUFSZ]; #ifdef NO_SCAN_BRACK /* Version_ Pts DgnLevs_ Hp___ Died__Born id */ static const char fmt[] = "%d %d %d %ld %d %d %d %d %d %d %ld %ld %d%*c"; @@ -254,7 +255,7 @@ struct toptenentry *tt; if (!fgets(inbuf, sizeof inbuf, rfile)) { /* sscanf will fail and tt->points will be set to 0 */ *inbuf = '\0'; - } else if (!index(inbuf, '\n')) { + } else if (!strchr(inbuf, '\n')) { Strcpy(&inbuf[sizeof inbuf - 2], "\n"); discardexcess(rfile); } @@ -300,10 +301,8 @@ struct toptenentry *tt; } } -STATIC_OVL void -writeentry(rfile, tt) -FILE *rfile; -struct toptenentry *tt; +staticfn void +writeentry(FILE *rfile, struct toptenentry *tt) { static const char fmt32[] = "%c%c "; /* role,gender */ static const char fmt33[] = "%s %s %s %s "; /* role,race,gndr,algn */ @@ -336,18 +335,18 @@ struct toptenentry *tt; #endif } +RESTORE_WARNING_FORMAT_NONLITERAL + #ifdef XLOGFILE -/* as tab is never used in eg. plname or death, no need to mangle those. */ -STATIC_OVL void -writexlentry(rfile, tt, how) -FILE *rfile; -struct toptenentry *tt; -int how; +/* as tab is never used in eg. svp.plname or death, no need to mangle those. */ +staticfn void +writexlentry(FILE *rfile, struct toptenentry *tt, int how) { #define Fprintf (void) fprintf #define XLOG_SEP '\t' /* xlogfile field separator. */ char buf[BUFSZ], tmpbuf[DTHSZ + 1]; + char achbuf[N_ACH * 40]; Sprintf(buf, "version=%d.%d.%d", tt->ver_major, tt->ver_minor, tt->patchlevel); @@ -366,19 +365,31 @@ int how; formatkiller(tmpbuf, sizeof tmpbuf, how, FALSE); Fprintf(rfile, "%s%cname=%s%cdeath=%s", buf, /* (already includes separator) */ - XLOG_SEP, plname, XLOG_SEP, tmpbuf); - if (multi) + XLOG_SEP, svp.plname, XLOG_SEP, tmpbuf); + if (gm.multi < 0) Fprintf(rfile, "%cwhile=%s", XLOG_SEP, - multi_reason ? multi_reason : "helpless"); + gm.multi_reason ? gm.multi_reason : "helpless"); Fprintf(rfile, "%cconduct=0x%lx%cturns=%ld%cachieve=0x%lx", XLOG_SEP, - encodeconduct(), XLOG_SEP, moves, XLOG_SEP, encodeachieve()); + encodeconduct(), XLOG_SEP, svm.moves, XLOG_SEP, + encodeachieve(FALSE)); + Fprintf(rfile, "%cachieveX=%s", XLOG_SEP, + encode_extended_achievements(achbuf)); + Fprintf(rfile, "%cconductX=%s", XLOG_SEP, + encode_extended_conducts(buf)); /* reuse 'buf[]' */ Fprintf(rfile, "%crealtime=%ld%cstarttime=%ld%cendtime=%ld", XLOG_SEP, - (long) urealtime.realtime, XLOG_SEP, - (long) ubirthday, XLOG_SEP, (long) urealtime.finish_time); + urealtime.realtime, XLOG_SEP, + timet_to_seconds(ubirthday), XLOG_SEP, + timet_to_seconds(urealtime.finish_time)); Fprintf(rfile, "%cgender0=%s%calign0=%s", XLOG_SEP, genders[flags.initgend].filecode, XLOG_SEP, aligns[1 - u.ualignbase[A_ORIGINAL]].filecode); Fprintf(rfile, "%cflags=0x%lx", XLOG_SEP, encodexlogflags()); + Fprintf(rfile, "%cgold=%ld", XLOG_SEP, + money_cnt(gi.invent) + hidden_gold(TRUE)); + Fprintf(rfile, "%cwish_cnt=%ld", XLOG_SEP, u.uconduct.wishes); + Fprintf(rfile, "%carti_wish_cnt=%ld", XLOG_SEP, u.uconduct.wisharti); + Fprintf(rfile, "%cbones=%ld", XLOG_SEP, u.uroleplay.numbones); + Fprintf(rfile, "%crerolls=%ld", XLOG_SEP, u.uroleplay.numrerolls); #ifdef NLE_XLOG_INCLUDE_FILE if (nle_ttyrecname()[0] != '\0') Fprintf(rfile, "%cttyrecname=%s", XLOG_SEP, nle_ttyrecname()); @@ -387,8 +398,8 @@ int how; #undef XLOG_SEP } -STATIC_OVL long -encodexlogflags() +staticfn long +encodexlogflags(void) { long e = 0L; @@ -398,12 +409,14 @@ encodexlogflags() e |= 1L << 1; if (!u.uroleplay.numbones) e |= 1L << 2; + if (u.uroleplay.reroll) + e |= 1L << 3; return e; } -STATIC_OVL long -encodeconduct() +staticfn long +encodeconduct(void) { long e = 0L; @@ -431,52 +444,183 @@ encodeconduct() e |= 1L << 10; if (!num_genocides()) e |= 1L << 11; + /* one bit isn't really adequate for sokoban conduct: + reporting "obeyed sokoban rules" is misleading if sokoban wasn't + completed or at least attempted; however, suppressing that when + sokoban was never entered, as we do here, risks reporting + "violated sokoban rules" when no such thing occurred; this can + be disambiguated in xlogfile post-processors by testing the + entered-sokoban bit in the 'achieve' field */ + if (!u.uconduct.sokocheat && sokoban_in_play()) + e |= 1L << 12; + if (!u.uconduct.pets) + e |= 1L << 13; return e; } -STATIC_OVL long -encodeachieve() +staticfn long +encodeachieve( + boolean secondlong) /* False: handle achievements 1..31, True: 32..62 */ { + int i, achidx, offset; long r = 0L; - if (u.uachieve.bell) - r |= 1L << 0; - if (u.uachieve.enter_gehennom) - r |= 1L << 1; - if (u.uachieve.menorah) - r |= 1L << 2; - if (u.uachieve.book) - r |= 1L << 3; - if (u.uevent.invoked) - r |= 1L << 4; - if (u.uachieve.amulet) - r |= 1L << 5; - if (In_endgame(&u.uz)) - r |= 1L << 6; - if (Is_astralevel(&u.uz)) - r |= 1L << 7; - if (u.uachieve.ascended) - r |= 1L << 8; - if (u.uachieve.mines_luckstone) - r |= 1L << 9; - if (u.uachieve.finish_sokoban) - r |= 1L << 10; - if (u.uachieve.killed_medusa) - r |= 1L << 11; - if (u.uroleplay.blind) - r |= 1L << 12; - if (u.uroleplay.nudist) - r |= 1L << 13; - + /* + * 32: portable limit for 'long'. + * Force 32 even on configurations that are using 64 bit longs. + * + * We use signed long and limit ourselves to 31 bits since tools + * that post-process xlogfile might not be able to cope with + * 'unsigned long'. + */ + offset = secondlong ? (32 - 1) : 0; + for (i = 0; u.uachieved[i]; ++i) { + achidx = u.uachieved[i] - offset; + if (achidx > 0 && achidx < 32) /* value 1..31 sets bit 0..30 */ + r |= 1L << (achidx - 1); + } return r; } +/* add the achievement or conduct comma-separated to string */ +staticfn void +add_achieveX(char *buf, const char *achievement, boolean condition) +{ + if (condition) { + if (buf[0] != '\0') { + Strcat(buf, ","); + } + Strcat(buf, achievement); + } +} + +staticfn char * +encode_extended_achievements(char *buf) +{ + char rnkbuf[40]; + const char *achievement = NULL; + int i, achidx, absidx; + + buf[0] = '\0'; + for (i = 0; u.uachieved[i]; i++) { + achidx = u.uachieved[i]; + absidx = abs(achidx); + switch (absidx) { + case ACH_UWIN: + achievement = "ascended"; + break; + case ACH_ASTR: + achievement = "entered_astral_plane"; + break; + case ACH_ENDG: + achievement = "entered_elemental_planes"; + break; + case ACH_AMUL: + achievement = "obtained_the_amulet_of_yendor"; + break; + case ACH_INVK: + achievement = "performed_the_invocation_ritual"; + break; + case ACH_BOOK: + achievement = "obtained_the_book_of_the_dead"; + break; + case ACH_BELL: + achievement = "obtained_the_bell_of_opening"; + break; + case ACH_CNDL: + achievement = "obtained_the_candelabrum_of_invocation"; + break; + case ACH_HELL: + achievement = "entered_gehennom"; + break; + case ACH_MEDU: + achievement = "defeated_medusa"; + break; + case ACH_MINE_PRIZE: + achievement = "obtained_the_luckstone_from_the_mines"; + break; + case ACH_SOKO_PRIZE: + achievement = "obtained_the_sokoban_prize"; + break; + case ACH_ORCL: + achievement = "consulted_the_oracle"; + break; + case ACH_NOVL: + achievement = "read_a_discworld_novel"; + break; + case ACH_MINE: + achievement = "entered_the_gnomish_mines"; + break; + case ACH_TOWN: + achievement = "entered_mine_town"; + break; + case ACH_SHOP: + achievement = "entered_a_shop"; + break; + case ACH_TMPL: + achievement = "entered_a_temple"; + break; + case ACH_SOKO: + achievement = "entered_sokoban"; + break; + case ACH_BGRM: + achievement = "entered_bigroom"; + break; + case ACH_TUNE: + achievement = "learned_castle_drawbridge_tune"; + break; + /* rank 0 is the starting condition, not an achievement; 8 is Xp 30 */ + case ACH_RNK1: case ACH_RNK2: case ACH_RNK3: case ACH_RNK4: + case ACH_RNK5: case ACH_RNK6: case ACH_RNK7: case ACH_RNK8: + Sprintf(rnkbuf, "attained_the_rank_of_%s", + rank_of(rank_to_xlev(absidx - (ACH_RNK1 - 1)), + Role_switch, (achidx < 0) ? TRUE : FALSE)); + strNsubst(rnkbuf, " ", "_", 0); /* replace every ' ' with '_' */ + achievement = lcase(rnkbuf); + break; + default: + continue; + } + add_achieveX(buf, achievement, TRUE); + } + + return buf; +} + +staticfn char * +encode_extended_conducts(char *buf) +{ + buf[0] = '\0'; + add_achieveX(buf, "foodless", !u.uconduct.food); + add_achieveX(buf, "vegan", !u.uconduct.unvegan); + add_achieveX(buf, "vegetarian", !u.uconduct.unvegetarian); + add_achieveX(buf, "atheist", !u.uconduct.gnostic); + add_achieveX(buf, "weaponless", !u.uconduct.weaphit); + add_achieveX(buf, "pacifist", !u.uconduct.killer); + add_achieveX(buf, "illiterate", !u.uconduct.literate); + add_achieveX(buf, "polyless", !u.uconduct.polypiles); + add_achieveX(buf, "polyselfless", !u.uconduct.polyselfs); + add_achieveX(buf, "wishless", !u.uconduct.wishes); + add_achieveX(buf, "artiwishless", !u.uconduct.wisharti); + add_achieveX(buf, "genocideless", !num_genocides()); + if (sokoban_in_play()) + add_achieveX(buf, "sokoban", !u.uconduct.sokocheat); + add_achieveX(buf, "blind", u.uroleplay.blind); + add_achieveX(buf, "deaf", u.uroleplay.deaf); + add_achieveX(buf, "nudist", u.uroleplay.nudist); + add_achieveX(buf, "pauper", u.uroleplay.pauper); + add_achieveX(buf, "bonesless", !flags.bones); + add_achieveX(buf, "petless", !u.uconduct.pets); + add_achieveX(buf, "unrerolled", !u.uroleplay.reroll); + + return buf; +} + #endif /* XLOGFILE */ -STATIC_OVL void -free_ttlist(tt) -struct toptenentry *tt; +staticfn void +free_ttlist(struct toptenentry *tt) { struct toptenentry *ttnext; @@ -489,33 +633,26 @@ struct toptenentry *tt; } void -topten(how, when) -int how; -time_t when; +topten(int how, time_t when) { - int uid = getuid(); - int rank, rank0 = -1, rank1 = 0; - int occ_cnt = sysopt.persmax; - register struct toptenentry *t0, *tprev; + struct toptenentry *t0, *tprev; struct toptenentry *t1; FILE *rfile; - register int flg = 0; - boolean t0_used; #ifdef LOGFILE FILE *lfile; -#endif /* LOGFILE */ +#endif #ifdef XLOGFILE FILE *xlfile; -#endif /* XLOGFILE */ - -#ifdef _DCC - /* Under DICE 3.0, this crashes the system consistently, apparently due to - * corruption of *rfile somewhere. Until I figure this out, just cut out - * topten support entirely - at least then the game exits cleanly. --AC - */ - return; #endif + int uid = getuid(); + int rank, rank0 = -1, rank1 = 0; + int occ_cnt = sysopt.persmax; + int flg = 0; + boolean t0_used, skip_scores; +#ifdef UPDATE_RECORD_IN_PLACE + final_fpos = 0L; +#endif /* If we are in the midst of a panic, cut out topten entirely. * topten uses alloc() several times, which will lead to * problems if the panic was the result of an alloc() failure. @@ -524,10 +661,10 @@ time_t when; return; if (iflags.toptenwin) { - toptenwin = create_nhwindow(NHW_TEXT); + gt.toptenwin = create_nhwindow(NHW_TEXT); } -#if defined(UNIX) || defined(VMS) || defined(__EMX__) +#if defined(HANGUPHANDLING) #define HUP if (!program_state.done_hup) #else #define HUP @@ -539,6 +676,7 @@ time_t when; /* create a new 'topten' entry */ t0_used = FALSE; t0 = newttentry(); + *t0 = zerott; t0->ver_major = VERSION_MAJOR; t0->ver_minor = VERSION_MINOR; t0->patchlevel = PATCHLEVEL; @@ -556,11 +694,11 @@ time_t when; t0->maxhp = u.uhpmax; t0->deaths = u.umortality; t0->uid = uid; - copynchars(t0->plrole, urole.filecode, ROLESZ); - copynchars(t0->plrace, urace.filecode, ROLESZ); + copynchars(t0->plrole, gu.urole.filecode, ROLESZ); + copynchars(t0->plrace, gu.urace.filecode, ROLESZ); copynchars(t0->plgend, genders[flags.female].filecode, ROLESZ); copynchars(t0->plalign, aligns[1 - u.ualign.type].filecode, ROLESZ); - copynchars(t0->name, plname, NAMSZ); + copynchars(t0->name, svp.plname, NAMSZ); formatkiller(t0->death, sizeof t0->death, how, TRUE); t0->birthdate = yyyymmdd(ubirthday); t0->deathdate = yyyymmdd(when); @@ -630,7 +768,7 @@ time_t when; t1 = tt_head = newttentry(); tprev = 0; /* rank0: -1 undefined, 0 not_on_list, n n_th on list */ - for (rank = 1;;) { + for (rank = 1; ; ) { readentry(rfile, t1); if (t1->points < sysopt.pointsmin) t1->points = 0; @@ -662,7 +800,7 @@ time_t when; char pbuf[BUFSZ]; Sprintf(pbuf, - "You didn't beat your previous score of %ld points.", + "You didn't beat your previous score of %ld points.", t1->points); topten_print(pbuf); topten_print(""); @@ -685,8 +823,7 @@ time_t when; } if (flg) { /* rewrite record file */ #ifdef UPDATE_RECORD_IN_PLACE - (void) fseek(rfile, (t0->fpos >= 0 ? t0->fpos : final_fpos), - SEEK_SET); + (void) fseek(rfile, (t0->fpos >= 0) ? t0->fpos : final_fpos, SEEK_SET); #else (void) fclose(rfile); if (!(rfile = fopen_datafile(RECORD, "w", SCOREPREFIX))) { @@ -711,43 +848,45 @@ time_t when; topten_print(""); } } + skip_scores = !flags.end_top && !flags.end_around && !flags.end_own; if (rank0 == 0) rank0 = rank1; if (rank0 <= 0) rank0 = rank; - if (!done_stopprint) + if (!skip_scores && !done_stopprint) outheader(); - t1 = tt_head; - for (rank = 1; t1->points != 0; rank++, t1 = t1->tt_next) { + for (t1 = tt_head, rank = 1; t1->points != 0; t1 = t1->tt_next, ++rank) { if (flg #ifdef UPDATE_RECORD_IN_PLACE && rank >= rank0 #endif ) writeentry(rfile, t1); - if (done_stopprint) + if (skip_scores || done_stopprint) continue; - if (rank > flags.end_top && (rank < rank0 - flags.end_around - || rank > rank0 + flags.end_around) - && (!flags.end_own - || (sysopt.pers_is_uid - ? t1->uid == t0->uid - : strncmp(t1->name, t0->name, NAMSZ) == 0))) - continue; - if (rank == rank0 - flags.end_around - && rank0 > flags.end_top + flags.end_around + 1 && !flags.end_own) - topten_print(""); - if (rank != rank0) - outentry(rank, t1, FALSE); - else if (!rank1) - outentry(rank, t1, TRUE); - else { - outentry(rank, t1, TRUE); - outentry(0, t0, TRUE); + if (rank <= flags.end_top + || (rank >= rank0 - flags.end_around + && rank <= rank0 + flags.end_around) + || (flags.end_own && (sysopt.pers_is_uid + ? t1->uid == t0->uid + : !strncmp(t1->name, t0->name, NAMSZ)))) { + if (rank == rank0 - flags.end_around + && rank0 > flags.end_top + flags.end_around + 1 + && !flags.end_own) + topten_print(""); + + if (rank != rank0) { + outentry(rank, t1, FALSE); + } else if (!rank1) { + outentry(rank, t1, TRUE); + } else { + outentry(rank, t1, TRUE); + outentry(0, t0, TRUE); + } } } if (rank0 >= rank) - if (!done_stopprint) + if (!skip_scores && !done_stopprint) outentry(0, t0, TRUE); #ifdef UPDATE_RECORD_IN_PLACE if (flg) { @@ -756,16 +895,16 @@ time_t when; truncate_file(rfile); #else /* use sentinel record rather than relying on truncation */ - t1->points = 0L; /* terminates file when read back in */ - t1->ver_major = t1->ver_minor = t1->patchlevel = 0; - t1->uid = t1->deathdnum = t1->deathlev = 0; - t1->maxlvl = t1->hp = t1->maxhp = t1->deaths = 0; + *t1 = zerott; + t1->points = 0L; /* [redundant] terminates file when read back in */ t1->plrole[0] = t1->plrace[0] = t1->plgend[0] = t1->plalign[0] = '-'; - t1->plrole[1] = t1->plrace[1] = t1->plgend[1] = t1->plalign[1] = 0; t1->birthdate = t1->deathdate = yyyymmdd((time_t) 0L); Strcpy(t1->name, "@"); - Strcpy(t1->death, "\n"); + Strcpy(t1->death, "\n"); /* end of data */ writeentry(rfile, t1); + /* note: there might be junk (if file has shrunk due to shorter + entries supplanting longer ones) after this dummy entry, but + reading and/or updating will ignore it */ (void) fflush(rfile); #endif /* TRUNCATE_FILE */ } @@ -774,23 +913,31 @@ time_t when; unlock_file(RECORD); free_ttlist(tt_head); -showwin: - if (iflags.toptenwin && !done_stopprint) - display_nhwindow(toptenwin, 1); -destroywin: + showwin: + if (!done_stopprint) { + if (iflags.toptenwin) { + display_nhwindow(gt.toptenwin, TRUE); + } else { + /* when not a window, we need something comparable to more() + but can't use it directly because we aren't dealing with + the message window */ + ; + } + } + destroywin: if (!t0_used) dealloc_ttentry(t0); if (iflags.toptenwin) { - destroy_nhwindow(toptenwin); - toptenwin = WIN_ERR; + destroy_nhwindow(gt.toptenwin); + gt.toptenwin = WIN_ERR; } } -STATIC_OVL void -outheader() +staticfn void +outheader(void) { char linebuf[BUFSZ]; - register char *bp; + char *bp; Strcpy(linebuf, " No Points Name"); bp = eos(linebuf); @@ -800,12 +947,11 @@ outheader() topten_print(linebuf); } +DISABLE_WARNING_FORMAT_NONLITERAL + /* so>0: standout line; so=0: ordinary line */ -STATIC_OVL void -outentry(rank, t1, so) -struct toptenentry *t1; -int rank; -boolean so; +staticfn void +outentry(int rank, struct toptenentry *t1, boolean so) { boolean second_line = TRUE; char linebuf[BUFSZ]; @@ -837,7 +983,7 @@ boolean so; !strncmp(" (", t1->death + 7, 2) ? t1->death + 7 + 2 : "", t1->maxlvl); /* fixup for closing paren in "escaped... with...Amulet)[max..." */ - if ((bp = index(linebuf, ')')) != 0) + if ((bp = strchr(linebuf, ')')) != 0) *bp = (t1->deathdnum == astral_level.dnum) ? '\0' : ' '; second_line = FALSE; } else if (!strncmp("ascended", t1->death, 8)) { @@ -889,7 +1035,7 @@ boolean so; } Sprintf(eos(linebuf), fmt, arg); } else { - Sprintf(eos(linebuf), " in %s", dungeons[t1->deathdnum].dname); + Sprintf(eos(linebuf), " in %s", svd.dungeons[t1->deathdnum].dname); if (t1->deathdnum != knox_level.dnum) Sprintf(eos(linebuf), " on level %d", t1->deathlev); if (t1->deathlev != t1->maxlvl) @@ -903,8 +1049,14 @@ boolean so; Strcat(linebuf, "."); /* Quit, starved, ascended, and escaped contain no second line */ - if (second_line) - Sprintf(eos(linebuf), " %c%s.", highc(*(t1->death)), t1->death + 1); + if (second_line) { + bp = eos(linebuf); + Sprintf(bp, " %c%s.", highc(*(t1->death)), t1->death + 1); + /* fix up "Killed by Mr. Asidonhopo; the shopkeeper"; that starts + with a comma but has it changed to semi-colon to keep the comma + out of 'record'; change it back for display */ + (void) strsubst(bp, "; the ", ", the "); + } lngr = (int) strlen(linebuf); if (t1->hp <= 0) @@ -912,9 +1064,9 @@ boolean so; else Sprintf(hpbuf, "%d", t1->hp); /* beginning of hp column after padding (not actually padded yet) */ - hppos = COLNO - (sizeof(" Hp [max]") - 1); /* sizeof(str) includes \0 */ + hppos = COLNO - (int) (sizeof " Hp [max]" - sizeof ""); while (lngr >= hppos) { - for (bp = eos(linebuf); !(*bp == ' ' && (bp - linebuf < hppos)); bp--) + for (bp = eos(linebuf); !(*bp == ' ' && bp - linebuf < hppos); bp--) ; /* special case: word is too long, wrap in the middle */ if (linebuf + 15 >= bp) @@ -927,16 +1079,16 @@ boolean so; Strcpy(linebuf3, bp); else Strcpy(linebuf3, bp + 1); - *bp = 0; + *bp = '\0'; if (so) { while (bp < linebuf + (COLNO - 1)) *bp++ = ' '; - *bp = 0; + *bp = '\0'; topten_print_bold(linebuf); } else topten_print(linebuf); - Sprintf(linebuf, "%15s %s", "", linebuf3); - lngr = strlen(linebuf); + Snprintf(linebuf, sizeof(linebuf), "%15s %s", "", linebuf3); + lngr = Strlen(linebuf); } /* beginning of hp column not including padding */ hppos = COLNO - 7 - (int) strlen(hpbuf); @@ -954,50 +1106,87 @@ boolean so; if (so) { bp = eos(linebuf); - if (so >= COLNO) - so = COLNO - 1; - while (bp < linebuf + so) + while (bp < linebuf + (COLNO - 1)) *bp++ = ' '; - *bp = 0; + *bp = '\0'; topten_print_bold(linebuf); } else topten_print(linebuf); } -STATIC_OVL int -score_wanted(current_ver, rank, t1, playerct, players, uid) -boolean current_ver; -int rank; -struct toptenentry *t1; -int playerct; -const char **players; -int uid; +RESTORE_WARNING_FORMAT_NONLITERAL + +staticfn int +score_wanted( + boolean current_ver, + int rank, + struct toptenentry *t1, + int playerct, + const char **players, + int uid) { + const char *arg, *nxt; int i; - if (current_ver - && (t1->ver_major != VERSION_MAJOR || t1->ver_minor != VERSION_MINOR - || t1->patchlevel != PATCHLEVEL)) + if (current_ver && (t1->ver_major != VERSION_MAJOR + || t1->ver_minor != VERSION_MINOR + || t1->patchlevel != PATCHLEVEL)) return 0; if (sysopt.pers_is_uid && !playerct && t1->uid == uid) return 1; + /* + * FIXME: + * This selection produces a union (OR) of criteria rather than + * an intersection (AND). So + * nethack -s -u igor -p Cav -r Hum + * will list all entries for name igor regardless of role or race + * plus all entries for cave dwellers regardless of name or race + * plus all entries for humans regardless of name or role. + * + * It would be more useful if it only chose human cave dwellers + * named igor. That would be pretty straightforward if only one + * instance of each of the criteria were possible, but + * nethack -s -u igor -u ayn -p Cav -p Pri -r Hum -r Dwa + * should list human cave dwellers named igor and human cave + * dwellers named ayn plus dwarven cave dwellers named igor and + * dwarven cave dwellers named ayn plus human priest[esse]s named + * igor and human priest[esse]s named ayn (the combination of + * dwarven priest[esse]s doesn't occur but the selection can test + * entries without being aware of such; it just won't find any + * matches for that). An extra initial pass of the command line + * to collect all criteria before testing any entry is needed to + * accomplish this. And we might need to drop support for + * pre-3.3.0 entries (old elf role) depending on how the criteria + * matching is performed. + * + * It also ought to extended to handle + * nethack -s -u igor-Cav-Hum + * Alignment and gender could be useful too but no one has ever + * clamored for them. Presumably if they care they postprocess + * with some custom tool. + */ + for (i = 0; i < playerct; i++) { - if (players[i][0] == '-' && index("pr", players[i][1]) - && players[i][2] == 0 && i + 1 < playerct) { - const char *arg = players[i + 1]; - if ((players[i][1] == 'p' - && str2role(arg) == str2role(t1->plrole)) - || (players[i][1] == 'r' - && str2race(arg) == str2race(t1->plrace))) + arg = players[i]; + if (arg[0] == '-' && arg[1] == 'u' && arg[2] != '\0') + arg += 2; /* handle '-uname' */ + + if (arg[0] == '-' && strchr("pru", arg[1]) && !arg[2] + && i + 1 < playerct) { + nxt = players[i + 1]; + if ((arg[1] == 'p' && str2role(nxt) == str2role(t1->plrole)) + || (arg[1] == 'r' && str2race(nxt) == str2race(t1->plrace)) + /* handle '-u name' */ + || (arg[1] == 'u' && (!strcmp(nxt, "all") + || !strncmp(t1->name, nxt, NAMSZ)))) return 1; i++; - } else if (strcmp(players[i], "all") == 0 - || strncmp(t1->name, players[i], NAMSZ) == 0 - || (players[i][0] == '-' && players[i][1] == t1->plrole[0] - && players[i][2] == 0) - || (digit(players[i][0]) && rank <= atoi(players[i]))) + } else if (!strcmp(arg, "all") + || !strncmp(t1->name, arg, NAMSZ) + || (arg[0] == '-' && arg[1] == t1->plrole[0] && !arg[2]) + || (digit(arg[0]) && rank <= atoi(arg))) return 1; } return 0; @@ -1010,22 +1199,23 @@ int uid; * caveat: some shells might allow argv elements to be arbitrarily long. */ void -prscore(argc, argv) -int argc; -char **argv; +prscore(int argc, char **argv) { - const char **players; - int playerct, rank; - boolean current_ver = TRUE, init_done = FALSE; - register struct toptenentry *t1; + const char **players, *player0; + int i, playerct, rank; + struct toptenentry *t1; FILE *rfile; - boolean match_found = FALSE; - register int i; - char pbuf[BUFSZ]; + char pbuf[BUFSZ], *p; + unsigned ln; int uid = -1; - const char *player0; - - if (argc < 2 || strncmp(argv[1], "-s", 2)) { + boolean current_ver = TRUE, init_done = FALSE, match_found = FALSE; + + /* expect "-s" or "--scores"; "-s is accepted */ + ln = (argc < 2) ? 0U + : ((p = strchr(argv[1], ' ')) != 0) ? (unsigned) (p - argv[1]) + : Strlen(argv[1]); + if (ln < 2 || (strncmp(argv[1], "-s", 2) + && strcmp(argv[1], "--scores"))) { raw_printf("prscore: bad arguments (%d)", argc); return; } @@ -1053,12 +1243,17 @@ char **argv; init_done = TRUE; } - if (!argv[1][2]) { /* plain "-s" */ + /* to get here, argv[1] either starts with "-s" or is "--scores" without + trailing stuff; for "-s" treat as separate arg */ + if (argv[1][1] == '-' || !argv[1][2]) { argc--; argv++; - } else + } else { /* concatenated arg string; use up "-s" but keep argc,argv */ argv[1] += 2; - + } + /* -v doesn't take a version number arg; it means 'all versions present + in the file' instead of the default of only the current version; + unlike -s, we don't accept "-v" for non-empty */ if (argc > 1 && !strcmp(argv[1], "-v")) { current_ver = FALSE; argc--; @@ -1071,13 +1266,10 @@ char **argv; playerct = 0; players = (const char **) 0; } else { - player0 = plname; + player0 = svp.plname; if (!*player0) -#ifdef AMIGA - player0 = "all"; /* single user system */ -#else - player0 = "hackplayer"; -#endif + player0 = "all"; /* if no plname[], show all scores + * (possibly filtered by '-v') */ playerct = 1; players = &player0; } @@ -1088,7 +1280,7 @@ char **argv; raw_print(""); t1 = tt_head = newttentry(); - for (rank = 1;; rank++) { + for (rank = 1; ; rank++) { readentry(rfile, t1); if (t1->points == 0) break; @@ -1115,12 +1307,21 @@ char **argv; } else { Sprintf(pbuf, "Cannot find any %sentries for ", current_ver ? "current " : ""); - if (playerct < 1) - Strcat(pbuf, "you."); - else { + if (playerct < 1) { + Strcat(pbuf, "you"); + } else { + /* minor bug: 'nethack -s -u ziggy' will say "any of" + even though the '-u' doesn't indicate multiple names */ if (playerct > 1) Strcat(pbuf, "any of "); for (i = 0; i < playerct; i++) { + /* accept '-u name' and '-uname' as well as just 'name' + so skip '-u' for the none-found feedback */ + if (!strncmp(players[i], "-u", 2)) { + if (!players[i][2]) + continue; + players[i] += 2; + } /* stop printing players if there are too many to fit */ if (strlen(pbuf) + strlen(players[i]) + 2 >= BUFSZ) { if (strlen(pbuf) < BUFSZ - 4) @@ -1131,7 +1332,7 @@ char **argv; } Strcat(pbuf, players[i]); if (i < playerct - 1) { - if (players[i][0] == '-' && index("pr", players[i][1]) + if (players[i][0] == '-' && strchr("pr", players[i][1]) && players[i][2] == 0) Strcat(pbuf, " "); else @@ -1139,10 +1340,12 @@ char **argv; } } } + /* append end-of-sentence punctuation if there is room */ + if (strlen(pbuf) < BUFSZ - 1) + Strcat(pbuf, "."); raw_print(pbuf); raw_printf("Usage: %s -s [-v] [maxrank] [playernames]", - - hname); + gh.hname); raw_printf("Player types are: [-p role] [-r race]"); } free_ttlist(tt_head); @@ -1157,23 +1360,20 @@ char **argv; #endif } -STATIC_OVL int -classmon(plch, fem) -char *plch; -boolean fem; +staticfn int +classmon(char *plch) { int i; /* Look for this role in the role table */ - for (i = 0; roles[i].name.m; i++) + for (i = 0; roles[i].name.m; i++) { if (!strncmp(plch, roles[i].filecode, ROLESZ)) { - if (fem && roles[i].femalenum != NON_PM) - return roles[i].femalenum; - else if (roles[i].malenum != NON_PM) - return roles[i].malenum; + if (roles[i].mnum != NON_PM) + return roles[i].mnum; else return PM_HUMAN; } + } /* this might be from a 3.2.x score for former Elf class */ if (!strcmp(plch, "E")) return PM_RANGER; @@ -1186,11 +1386,11 @@ boolean fem; * Get a random player name and class from the high score list, */ struct toptenentry * -get_rnd_toptenentry() +get_rnd_toptenentry(void) { int rank, i; FILE *rfile; - register struct toptenentry *tt; + struct toptenentry *tt; static struct toptenentry tt_buf; rfile = fopen_datafile(RECORD, "r", SCOREPREFIX); @@ -1201,7 +1401,7 @@ get_rnd_toptenentry() tt = &tt_buf; rank = rnd(sysopt.tt_oname_maxrank); -pickentry: + pickentry: for (i = rank; i; i--) { readentry(rfile, tt); if (tt->points == 0) @@ -1227,8 +1427,7 @@ get_rnd_toptenentry() * to an object (for statues or morgue corpses). */ struct obj * -tt_oname(otmp) -struct obj *otmp; +tt_oname(struct obj *otmp) { struct toptenentry *tt; if (!otmp) @@ -1239,29 +1438,56 @@ struct obj *otmp; if (!tt) return (struct obj *) 0; - set_corpsenm(otmp, classmon(tt->plrole, (tt->plgend[0] == 'F'))); - otmp = oname(otmp, tt->name); + set_corpsenm(otmp, classmon(tt->plrole)); + if (tt->plgend[0] == 'F') + otmp->spe = CORPSTAT_FEMALE; + else if (tt->plgend[0] == 'M') + otmp->spe = CORPSTAT_MALE; + otmp = oname(otmp, tt->name, ONAME_NO_FLAGS); return otmp; } +/* Randomly select a topten entry to mimic */ +int +tt_doppel(struct monst *mon) { + struct toptenentry *tt = rn2(13) ? get_rnd_toptenentry() : NULL; + int ret; + + if (!tt) + ret = rn1(PM_WIZARD - PM_ARCHEOLOGIST + 1, PM_ARCHEOLOGIST); + else { + if (tt->plgend[0] == 'F') + mon->female = 1; + else if (tt->plgend[0] == 'M') + mon->female = 0; + ret = classmon(tt->plrole); + /* Only take on a name if the player can see + the doppelganger, otherwise we end up with + named monsters spoiling the fun - Kes */ + if (canseemon(mon)) + christen_monst(mon, tt->name); + } + return ret; +} + #ifdef NO_SCAN_BRACK /* Lattice scanf isn't up to reading the scorefile. What */ /* follows deals with that; I admit it's ugly. (KL) */ /* Now generally available (KL) */ -STATIC_OVL void +staticfn void nsb_mung_line(p) char *p; { - while ((p = index(p, ' ')) != 0) + while ((p = strchr(p, ' ')) != 0) *p = '|'; } -STATIC_OVL void +staticfn void nsb_unmung_line(p) char *p; { - while ((p = index(p, '|')) != 0) + while ((p = strchr(p, '|')) != 0) *p = ' '; } #endif /* NO_SCAN_BRACK */ diff --git a/src/track.c b/src/track.c index 67b46467c..660e900c7 100644 --- a/src/track.c +++ b/src/track.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 track.c $NHDT-Date: 1432512769 2015/05/25 00:12:49 $ $NHDT-Branch: master $:$NHDT-Revision: 1.9 $ */ +/* NetHack 5.0 track.c $NHDT-Date: 1596498219 2020/08/03 23:43:39 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.12 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Kenneth Lorber, Kensington, Maryland, 2015. */ /* NetHack may be freely redistributed. See license for details. */ @@ -6,21 +6,27 @@ #include "hack.h" -#define UTSZ 50 +#define UTSZ 100 -STATIC_VAR NEARDATA int utcnt, utpnt; -STATIC_VAR NEARDATA coord utrack[UTSZ]; +static NEARDATA int utcnt, utpnt; +static NEARDATA coord utrack[UTSZ]; void -initrack() +initrack(void) { utcnt = utpnt = 0; + (void) memset((genericptr_t) &utrack, 0, sizeof(utrack)); } +#ifndef SFCTOOL /* add to track */ void -settrack() +settrack(void) { + if ((uleft && uleft->otyp == RIN_STEALTH) + || (uright && uright->otyp == RIN_STEALTH)) + return; + if (utcnt < UTSZ) utcnt++; if (utpnt == UTSZ) @@ -30,12 +36,13 @@ settrack() utpnt++; } +/* get a track coord on or next to x,y and last tracked by hero, + returns null if no such track */ coord * -gettrack(x, y) -register int x, y; +gettrack(coordxy x, coordxy y) { - register int cnt, ndist; - register coord *tc; + int cnt, ndist; + coord *tc; cnt = utcnt; for (tc = &utrack[utpnt]; cnt--;) { if (tc == utrack) @@ -44,20 +51,57 @@ register int x, y; tc--; ndist = distmin(x, y, tc->x, tc->y); - /* if far away, skip track entries til we're closer */ - if (ndist > 2) { - ndist -= 2; /* be careful due to extra decrement at top of loop */ - cnt -= ndist; - if (cnt <= 0) - return (coord *) 0; /* too far away, no matches possible */ - if (tc < &utrack[ndist]) - tc += (UTSZ - ndist); - else - tc -= ndist; - } else if (ndist <= 1) + if (ndist <= 1) return (ndist ? tc : 0); } return (coord *) 0; } +#endif /* !SFCTOOL */ + +/* return TRUE if x,y has hero tracks on it */ +boolean +hastrack(coordxy x, coordxy y) +{ + int i; + + for (i = 0; i < utcnt; i++) + if (utrack[i].x == x && utrack[i].y == y) + return TRUE; + + return FALSE; +} + +/* save the hero tracking info */ +void +save_track(NHFILE *nhfp) +{ + if (update_file(nhfp)) { + int i; + + Sfo_int(nhfp, &utcnt, "track-utcnt"); + Sfo_int(nhfp, &utpnt, "track-utpnt"); + for (i = 0; i < utcnt; i++) { + Sfo_nhcoord(nhfp, &utrack[i], "utrack"); + } + } + if (release_data(nhfp)) + initrack(); +} + +/* restore the hero tracking info */ +void +rest_track(NHFILE *nhfp) +{ + int i; + + Sfi_int(nhfp, &utcnt, "track-utcnt"); + Sfi_int(nhfp, &utpnt, "track-utpnt"); + + if (utcnt > UTSZ || utpnt > UTSZ) + panic("rest_track: impossible pt counts"); + for (i = 0; i < utcnt; i++) { + Sfi_nhcoord(nhfp, &utrack[i], "utrack"); + } +} /*track.c*/ diff --git a/src/trap.c b/src/trap.c index f390e19c2..1804f7fc8 100644 --- a/src/trap.c +++ b/src/trap.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 trap.c $NHDT-Date: 1576638501 2019/12/18 03:08:21 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.329 $ */ +/* NetHack 5.0 trap.c $NHDT-Date: 1741926700 2025/03/13 20:31:40 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.621 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2013. */ /* NetHack may be freely redistributed. See license for details. */ @@ -7,54 +7,85 @@ extern const char *const destroy_strings[][3]; /* from zap.c */ -STATIC_DCL boolean FDECL(keep_saddle_with_steedcorpse, (unsigned, struct obj *, - struct obj *)); -STATIC_DCL struct obj *FDECL(t_missile, (int, struct trap *)); -STATIC_DCL char *FDECL(trapnote, (struct trap *, BOOLEAN_P)); -STATIC_DCL int FDECL(steedintrap, (struct trap *, struct obj *)); -STATIC_DCL void FDECL(launch_drop_spot, (struct obj *, XCHAR_P, XCHAR_P)); -STATIC_DCL int FDECL(mkroll_launch, (struct trap *, XCHAR_P, XCHAR_P, - SHORT_P, long)); -STATIC_DCL boolean FDECL(isclearpath, (coord *, int, SCHAR_P, SCHAR_P)); -STATIC_DCL void FDECL(dofiretrap, (struct obj *)); -STATIC_DCL void NDECL(domagictrap); -STATIC_DCL boolean FDECL(emergency_disrobe, (boolean *)); -STATIC_DCL int FDECL(untrap_prob, (struct trap *)); -STATIC_DCL void FDECL(move_into_trap, (struct trap *)); -STATIC_DCL int FDECL(try_disarm, (struct trap *, BOOLEAN_P)); -STATIC_DCL void FDECL(reward_untrap, (struct trap *, struct monst *)); -STATIC_DCL int FDECL(disarm_holdingtrap, (struct trap *)); -STATIC_DCL int FDECL(disarm_landmine, (struct trap *)); -STATIC_DCL int FDECL(disarm_squeaky_board, (struct trap *)); -STATIC_DCL int FDECL(disarm_shooting_trap, (struct trap *, int)); -STATIC_DCL void FDECL(clear_conjoined_pits, (struct trap *)); -STATIC_DCL boolean FDECL(adj_nonconjoined_pit, (struct trap *)); -STATIC_DCL int FDECL(try_lift, (struct monst *, struct trap *, int, - BOOLEAN_P)); -STATIC_DCL int FDECL(help_monster_out, (struct monst *, struct trap *)); +staticfn void mk_trap_statue(coordxy, coordxy); +staticfn int dng_bottom(d_level *lev); +staticfn void hole_destination(d_level *); +staticfn boolean keep_saddle_with_steedcorpse(unsigned, struct obj *, + struct obj *); +staticfn boolean mu_maybe_destroy_web(struct monst *, boolean, struct trap *); +staticfn struct obj *t_missile(int, struct trap *); +staticfn boolean floor_trigger(int); +staticfn boolean check_in_air(struct monst *, unsigned); +staticfn int trapeffect_arrow_trap(struct monst *, struct trap *, unsigned); +staticfn int trapeffect_dart_trap(struct monst *, struct trap *, unsigned); +staticfn int trapeffect_rocktrap(struct monst *, struct trap *, unsigned); +staticfn int trapeffect_sqky_board(struct monst *, struct trap *, unsigned); +staticfn int trapeffect_bear_trap(struct monst *, struct trap *, unsigned); +staticfn int trapeffect_slp_gas_trap(struct monst *, struct trap *, unsigned); +staticfn int trapeffect_rust_trap(struct monst *, struct trap *, unsigned); +staticfn int trapeffect_fire_trap(struct monst *, struct trap *, unsigned); +staticfn int trapeffect_pit(struct monst *, struct trap *, unsigned); +staticfn int trapeffect_hole(struct monst *, struct trap *, unsigned); +staticfn int trapeffect_telep_trap(struct monst *, struct trap *, unsigned); +staticfn int trapeffect_level_telep(struct monst *, struct trap *, unsigned); +staticfn int trapeffect_web(struct monst *, struct trap *, unsigned); +staticfn int trapeffect_statue_trap(struct monst *, struct trap *, unsigned); +staticfn int trapeffect_magic_trap(struct monst *, struct trap *, unsigned); +staticfn int trapeffect_anti_magic(struct monst *, struct trap *, unsigned); +staticfn int trapeffect_poly_trap(struct monst *, struct trap *, unsigned); +staticfn int trapeffect_landmine(struct monst *, struct trap *, unsigned); +staticfn int trapeffect_rolling_boulder_trap(struct monst *, struct trap *, + unsigned); +staticfn int trapeffect_magic_portal(struct monst *, struct trap *, unsigned); +staticfn int trapeffect_vibrating_square(struct monst *, struct trap *, + unsigned); +staticfn int trapeffect_selector(struct monst *, struct trap *, unsigned); +staticfn char *trapnote(struct trap *, boolean); +staticfn int choose_trapnote(struct trap *); +staticfn int steedintrap(struct trap *, struct obj *); +staticfn void launch_drop_spot(struct obj *, coordxy, coordxy); +staticfn boolean find_random_launch_coord(struct trap *, coord *); +staticfn int mkroll_launch(struct trap *, coordxy, coordxy, short, long); +staticfn boolean isclearpath(coord *, int, schar, schar); +staticfn boolean m_easy_escape_pit(struct monst *) NONNULLARG1; +staticfn void dofiretrap(struct obj *); +staticfn void domagictrap(void); +staticfn void pot_acid_damage(struct obj *, boolean, boolean); +staticfn boolean emergency_disrobe(boolean *); +staticfn int untrap_prob(struct trap *); +staticfn void move_into_trap(struct trap *); +staticfn int try_disarm(struct trap *, boolean); +staticfn void reward_untrap(struct trap *, struct monst *); +staticfn int disarm_holdingtrap(struct trap *); +staticfn int disarm_landmine(struct trap *); +staticfn int unsqueak_ok(struct obj *); +staticfn int disarm_squeaky_board(struct trap *); +staticfn int disarm_shooting_trap(struct trap *, int); +staticfn void clear_conjoined_pits(struct trap *); +staticfn boolean adj_nonconjoined_pit(struct trap *); +staticfn int try_lift(struct monst *, struct trap *, int, boolean); +staticfn int help_monster_out(struct monst *, struct trap *); +staticfn void disarm_box(struct obj *, boolean, boolean); +staticfn void untrap_box(struct obj *, boolean, boolean); #if 0 -STATIC_DCL void FDECL(join_adjacent_pits, (struct trap *)); +staticfn void join_adjacent_pits(struct trap *); #endif -STATIC_DCL boolean FDECL(thitm, (int, struct monst *, struct obj *, int, - BOOLEAN_P)); -STATIC_DCL void NDECL(maybe_finish_sokoban); - -/* mintrap() should take a flags argument, but for time being we use this */ -STATIC_VAR int force_mintrap = 0; - -STATIC_VAR const char *const a_your[2] = { "a", "your" }; -STATIC_VAR const char *const A_Your[2] = { "A", "Your" }; -STATIC_VAR const char tower_of_flame[] = "tower of flame"; -STATIC_VAR const char *const A_gush_of_water_hits = "A gush of water hits"; -STATIC_VAR const char *const blindgas[6] = { "humid", "odorless", - "pungent", "chilling", - "acrid", "biting" }; +staticfn boolean thitm(int, struct monst *, struct obj *, int, + boolean) NONNULLARG2; +staticfn void maybe_finish_sokoban(void); + +static const char *const a_your[2] = { "a", "your" }; +static const char *const A_Your[2] = { "A", "Your" }; +static const char tower_of_flame[] = "tower of flame"; +static const char *const A_gush_of_water_hits = "A gush of water hits"; +static const char *const blindgas[6] = { "humid", "odorless", + "pungent", "chilling", + "acrid", "biting" }; /* called when you're hit by fire (dofiretrap,buzz,zapyourself,explode); returns TRUE if hit on torso */ boolean -burnarmor(victim) -struct monst *victim; +burnarmor(struct monst *victim) { struct obj *item; char buf[BUFSZ]; @@ -63,12 +94,12 @@ struct monst *victim; if (!victim) return 0; - hitting_u = (victim == &youmonst); + hitting_u = (victim == &gy.youmonst); /* burning damage may dry wet towel */ item = hitting_u ? carrying(TOWEL) : m_carrying(victim, TOWEL); while (item) { - if (is_wet_towel(item)) { + if (is_wet_towel(item)) { /* True => (item->spe > 0) */ oldspe = item->spe; dry_a_towel(item, rn2(oldspe + 1), TRUE); if (item->spe != oldspe) @@ -137,19 +168,21 @@ struct monst *victim; * Returns an erosion return value (ER_*) */ int -erode_obj(otmp, ostr, type, ef_flags) -register struct obj *otmp; -const char *ostr; -int type; -int ef_flags; +erode_obj( + struct obj *otmp, + const char *ostr, + int type, + int ef_flags) { static NEARDATA const char - *const action[] = { "smoulder", "rust", "rot", "corrode" }, - *const msg[] = { "burnt", "rusted", "rotten", "corroded" }, - *const bythe[] = { "heat", "oxidation", "decay", "corrosion" }; + *const action[] = { "smoulder", "rust", "rot", "corrode", "crack" }, + *const msg[] = { "burnt", "rusted", "rotten", "corroded", "cracked" }, + *const bythe[] = { "heat", "oxidation", "decay", "corrosion", + "impact" }; /* this could use improvement... */ boolean vulnerable = FALSE, is_primary = TRUE, check_grease = (ef_flags & EF_GREASE) ? TRUE : FALSE, print = (ef_flags & EF_VERBOSE) ? TRUE : FALSE, + crackers = FALSE, /* True: different feedback if otmp destroyed */ uvictim, vismon, visobj; int erosion, cost_type; struct monst *victim; @@ -157,14 +190,20 @@ int ef_flags; if (!otmp) return ER_NOTHING; - victim = carried(otmp) ? &youmonst : mcarried(otmp) ? otmp->ocarry : NULL; - uvictim = (victim == &youmonst); - vismon = victim && (victim != &youmonst) && canseemon(victim); - /* Is bhitpos correct here? Ugh. */ - visobj = !victim && cansee(bhitpos.x, bhitpos.y); + victim = carried(otmp) ? &gy.youmonst + : mcarried(otmp) ? otmp->ocarry + : (struct monst *) 0; + uvictim = (victim == &gy.youmonst); + vismon = victim && (victim != &gy.youmonst) && canseemon(victim); + /* Is gb.bhitpos correct here? Ugh. */ + visobj = (!victim && cansee(gb.bhitpos.x, gb.bhitpos.y) + && (!is_pool(gb.bhitpos.x, gb.bhitpos.y) + || (next2u(gb.bhitpos.x,gb.bhitpos.y) && Underwater))); switch (type) { case ERODE_BURN: + if (uvictim && inventory_resistance_check(AD_FIRE)) + return ER_NOTHING; vulnerable = is_flammable(otmp); check_grease = FALSE; cost_type = COST_BURN; @@ -180,10 +219,18 @@ int ef_flags; cost_type = COST_ROT; break; case ERODE_CORRODE: + if (uvictim && inventory_resistance_check(AD_ACID)) + return ER_NOTHING; vulnerable = is_corrodeable(otmp); is_primary = FALSE; cost_type = COST_CORRODE; break; + case ERODE_CRACK: /* crystal armor */ + vulnerable = is_crackable(otmp); + is_primary = TRUE; + crackers = TRUE; + cost_type = COST_CRACK; + break; default: impossible("Invalid erosion type in erode_obj"); return ER_NOTHING; @@ -212,8 +259,8 @@ int ef_flags; && (uvictim || vismon || visobj)) pline("Somehow, %s %s %s not affected by the %s.", uvictim ? "your" - : !vismon ? "the" /* visobj */ - : s_suffix(mon_nam(victim)), + : !vismon ? "the" /* visobj */ + : s_suffix(mon_nam(victim)), ostr, vtense(ostr, "are"), bythe[type]); /* We assume here that if the object is protected because it * is blessed, it still shows some minor signs of wear, and @@ -222,21 +269,21 @@ int ef_flags; */ if (otmp->oerodeproof) { otmp->rknown = TRUE; - if (victim == &youmonst) + if (victim == &gy.youmonst) update_inventory(); } return ER_NOTHING; } else if (erosion < MAX_ERODE) { - const char *adverb = (erosion + 1 == MAX_ERODE) - ? " completely" - : erosion ? " further" : ""; + const char *adverb = (erosion + 1 == MAX_ERODE) ? " completely" + : erosion ? " further" + : ""; if (uvictim || vismon || visobj) pline("%s %s %s%s!", uvictim ? "Your" - : !vismon ? "The" /* visobj */ - : s_suffix(Monnam(victim)), + : !vismon ? "The" /* visobj */ + : s_suffix(Monnam(victim)), ostr, vtense(ostr, action[type]), adverb); if (ef_flags & EF_PAY) @@ -247,22 +294,49 @@ int ef_flags; else otmp->oeroded2++; - if (victim == &youmonst) + if (victim == &gy.youmonst) update_inventory(); return ER_DAMAGED; } else if (ef_flags & EF_DESTROY) { - if (uvictim || vismon || visobj) - pline("%s %s %s away!", - uvictim ? "Your" - : !vismon ? "The" /* visobj */ - : s_suffix(Monnam(victim)), - ostr, vtense(ostr, action[type])); + otmp->in_use = 1; /* in case of hangup during message w/ --More-- */ + if (uvictim || vismon || visobj) { + char actbuf[BUFSZ]; + if (!crackers) + Sprintf(actbuf, "%s away", vtense(ostr, action[type])); + else + Sprintf(actbuf, "shatters"); + pline("%s %s %s!", + uvictim ? "Your" + : !vismon ? "The" /* visobj */ + : s_suffix(Monnam(victim)), + ostr, actbuf); + } if (ef_flags & EF_PAY) costly_alteration(otmp, cost_type); - setnotworn(otmp); + if (otmp->owornmask) { + /* unwear otmp before deleting it */ + if (carried(otmp)) { + /* otmp remains in hero's invent; if we get here because + it is being burned up by lava, we don't need to worry + about unwearing levitation boots and having that + trigger float_down to then fall in again; if such + were being worn, they wouldn't be in the lava now */ + remove_worn_item(otmp, TRUE); /* calls Cloak_off(),&c */ + } else if (mcarried(otmp)) { + /* results in otmp->where==OBJ_FREE; delobj() doesn't care */ + extract_from_minvent(otmp->ocarry, otmp, TRUE, FALSE); + } else { /* worn but not in hero invent or monster minvent ? */ + impossible( + "erode_obj(%d): destroying strangely worn item [%d, 0x%08lx: %s]", + type, + otmp->where, otmp->owornmask, simpleonames(otmp)); + otmp->owornmask = 0L; /* otherwise a second complaint (about + * deleting a worn item) will ensue */ + } + } delobj(otmp); return ER_DESTROYED; } else { @@ -283,21 +357,21 @@ int ef_flags; * wears off. */ boolean -grease_protect(otmp, ostr, victim) -register struct obj *otmp; -const char *ostr; -struct monst *victim; +grease_protect( + struct obj *otmp, + const char *ostr, + struct monst *victim) { static const char txt[] = "protected by the layer of grease!"; - boolean vismon = victim && (victim != &youmonst) && canseemon(victim); + boolean vismon = victim && (victim != &gy.youmonst) && canseemon(victim); if (ostr) { - if (victim == &youmonst) + if (victim == &gy.youmonst) Your("%s %s %s", ostr, vtense(ostr, "are"), txt); else if (vismon) pline("%s's %s %s %s", Monnam(victim), ostr, vtense(ostr, "are"), txt); - } else if (victim == &youmonst || vismon) { + } else if (victim == &gy.youmonst || vismon) { pline("%s %s", Yobjnam2(otmp, "are"), txt); } if (!rn2(2)) { @@ -311,34 +385,109 @@ struct monst *victim; return FALSE; } +/* create a "living" statue at x,y */ +staticfn void +mk_trap_statue(coordxy x, coordxy y) +{ + struct monst *mtmp; + struct obj *otmp, *statue; + struct permonst *mptr; + int trycount = 10; + + do { /* avoid ultimately hostile co-aligned unicorn */ + mptr = &mons[rndmonnum_adj(3, 6)]; + } while (--trycount > 0 && is_unicorn(mptr) + && sgn(u.ualign.type) == sgn(mptr->maligntyp)); + statue = mkcorpstat(STATUE, (struct monst *) 0, mptr, x, y, + CORPSTAT_NONE); + mtmp = makemon(&mons[statue->corpsenm], 0, 0, MM_NOCOUNTBIRTH | MM_NOMSG); + if (!mtmp) + return; /* should never happen */ + while (mtmp->minvent) { + otmp = mtmp->minvent; + otmp->owornmask = 0; + obj_extract_self(otmp); + (void) add_to_container(statue, otmp); + } + statue->owt = weight(statue); + mongone(mtmp); +} + +/* find "bottom" level of specified dungeon, stopping at quest locate */ +staticfn int +dng_bottom(d_level *lev) +{ + int bottom = dunlevs_in_dungeon(lev); + + /* when in the upper half of the quest, don't fall past the + middle "quest locate" level if hero hasn't been there yet */ + if (In_quest(lev)) { + int qlocate_depth = qlocate_level.dlevel; + + /* deepest reached < qlocate implies current < qlocate */ + if (dunlev_reached(lev) < qlocate_depth) + bottom = qlocate_depth; /* early cut-off */ + } else if (In_hell(lev)) { + /* if the invocation hasn't been performed yet, the vibrating square + level is effectively the bottom of Gehennom; the sanctum level is + out of reach until after the invocation */ + if (!u.uevent.invoked) + bottom -= 1; + } + return bottom; +} + +/* destination dlevel for holes or trapdoors */ +staticfn void +hole_destination(d_level *dst) +{ + int bottom = dng_bottom(&u.uz); + + dst->dnum = u.uz.dnum; + dst->dlevel = dunlev(&u.uz); + while (dst->dlevel < bottom) { + dst->dlevel++; + if (rn2(4)) + break; + } +} + struct trap * -maketrap(x, y, typ) -int x, y, typ; +maketrap(coordxy x, coordxy y, int typ) { static union vlaunchinfo zero_vl; - boolean oldplace; + boolean oldplace, was_ice, clear_flags; struct trap *ttmp; struct rm *lev = &levl[x][y]; + if (typ == TRAPPED_DOOR || typ == TRAPPED_CHEST) + return (struct trap *) 0; + if ((ttmp = t_at(x, y)) != 0) { - if (ttmp->ttyp == MAGIC_PORTAL || ttmp->ttyp == VIBRATING_SQUARE) + if (undestroyable_trap(ttmp->ttyp)) return (struct trap *) 0; oldplace = TRUE; - if (u.utrap && x == u.ux && y == u.uy + if (u.utrap && u_at(x, y) && ((u.utraptype == TT_BEARTRAP && typ != BEAR_TRAP) || (u.utraptype == TT_WEB && typ != WEB) - || (u.utraptype == TT_PIT && !is_pit(typ)))) - u.utrap = 0; + || (u.utraptype == TT_PIT && !is_pit(typ)) + || (u.utraptype == TT_LAVA && !is_lava(x, y)))) + reset_utrap(FALSE); /* old remain valid */ - } else if (IS_FURNITURE(lev->typ) - && (!IS_GRAVE(lev->typ) || (typ != PIT && typ != HOLE))) { + } else if (!CAN_OVERWRITE_TERRAIN(lev->typ) /* stairs */ + || is_pool_or_lava(x, y) + || (IS_FURNITURE(lev->typ) && (typ != PIT && typ != HOLE)) + || (lev->typ == DRAWBRIDGE_UP && typ == MAGIC_PORTAL) + || (IS_AIR(lev->typ) && typ != MAGIC_PORTAL) + || (typ == LEVEL_TELEP && single_level_branch(&u.uz))) { /* no trap on top of furniture (caller usually screens the - location to inhibit this, but wizard mode wishing doesn't) */ + location to inhibit this, but wizard mode wishing doesn't) + and no level teleporter in branch with only one level */ return (struct trap *) 0; } else { oldplace = FALSE; ttmp = newtrap(); - (void) memset((genericptr_t)ttmp, 0, sizeof(struct trap)); + (void) memset((genericptr_t) ttmp, 0, sizeof(struct trap)); ttmp->ntrap = 0; ttmp->tx = x; ttmp->ty = y; @@ -349,89 +498,85 @@ int x, y, typ; ttmp->dst.dnum = ttmp->dst.dlevel = -1; ttmp->madeby_u = 0; ttmp->once = 0; - ttmp->tseen = (typ == HOLE); /* hide non-holes */ + ttmp->tseen = unhideable_trap(typ); ttmp->ttyp = typ; switch (typ) { - case SQKY_BOARD: { - int tavail[12], tpick[12], tcnt = 0, k; - struct trap *t; - - for (k = 0; k < 12; ++k) - tavail[k] = tpick[k] = 0; - for (t = ftrap; t; t = t->ntrap) - if (t->ttyp == SQKY_BOARD && t != ttmp) - tavail[t->tnote] = 1; - /* now populate tpick[] with the available indices */ - for (k = 0; k < 12; ++k) - if (tavail[k] == 0) - tpick[tcnt++] = k; - /* choose an unused note; if all are in use, pick a random one */ - ttmp->tnote = (short) ((tcnt > 0) ? tpick[rn2(tcnt)] : rn2(12)); + case SQKY_BOARD: + ttmp->tnote = choose_trapnote(ttmp); break; - } - case STATUE_TRAP: { /* create a "living" statue */ - struct monst *mtmp; - struct obj *otmp, *statue; - struct permonst *mptr; - int trycount = 10; - - do { /* avoid ultimately hostile co-aligned unicorn */ - mptr = &mons[rndmonnum()]; - } while (--trycount > 0 && is_unicorn(mptr) - && sgn(u.ualign.type) == sgn(mptr->maligntyp)); - statue = mkcorpstat(STATUE, (struct monst *) 0, mptr, x, y, - CORPSTAT_NONE); - mtmp = makemon(&mons[statue->corpsenm], 0, 0, MM_NOCOUNTBIRTH); - if (!mtmp) - break; /* should never happen */ - while (mtmp->minvent) { - otmp = mtmp->minvent; - otmp->owornmask = 0; - obj_extract_self(otmp); - (void) add_to_container(statue, otmp); - } - statue->owt = weight(statue); - mongone(mtmp); + case STATUE_TRAP: /* create a "living" statue */ + mk_trap_statue(x, y); break; - } case ROLLING_BOULDER_TRAP: /* boulder will roll towards trigger */ (void) mkroll_launch(ttmp, x, y, BOULDER, 1L); break; case PIT: case SPIKED_PIT: ttmp->conjoined = 0; + FALLTHROUGH; /*FALLTHRU*/ case HOLE: case TRAPDOOR: + if (is_hole(typ)) + hole_destination(&(ttmp->dst)); if (*in_rooms(x, y, SHOPBASE) && (is_hole(typ) || IS_DOOR(lev->typ) || IS_WALL(lev->typ))) add_damage(x, y, /* schedule repair */ ((IS_DOOR(lev->typ) || IS_WALL(lev->typ)) - && !context.mon_moving) - ? SHOP_HOLE_COST - : 0L); - lev->doormask = 0; /* subsumes altarmask, icedpool... */ - if (IS_ROOM(lev->typ)) /* && !IS_AIR(lev->typ) */ - lev->typ = ROOM; + && !svc.context.mon_moving) ? SHOP_HOLE_COST : 0L); + + clear_flags = TRUE; /* assume lev->flags needs to be reset */ + /* DRAWBRIDGE_UP passes the IS_ROOM() test so check it first; + it also needs to retain lev->drawbridgemask */ + if (lev->typ == DRAWBRIDGE_UP) { + /* bridge is closed and we're putting a hole or pit at the span + spot; this trap will be deleted if/when the bridge is opened; + terrain becomes room floor even if it was moat, lava, or ice */ + clear_flags = FALSE; /* keep lev->drawbridgemask */ + was_ice = (lev->drawbridgemask & DB_UNDER) == DB_ICE; + lev->drawbridgemask &= ~DB_UNDER; + lev->drawbridgemask |= DB_FLOOR; + if (was_ice) { + /* subset of set_levltyp() after changing ice to floor; + frozen corpses resume rotting, no more ice to melt away */ + obj_ice_effects(x, y, TRUE); + spot_stop_timers(x, y, MELT_ICE_AWAY); + } + } else if (IS_ROOM(lev->typ)) { + (void) set_levltyp(x, y, ROOM); + /* * some cases which can happen when digging - * down while phazing thru solid areas + * down while phasing thru solid areas */ - else if (lev->typ == STONE || lev->typ == SCORR) - lev->typ = CORR; - else if (IS_WALL(lev->typ) || lev->typ == SDOOR) - lev->typ = level.flags.is_maze_lev - ? ROOM - : level.flags.is_cavernous_lev ? CORR : DOOR; + } else if (lev->typ == STONE || lev->typ == SCORR) { + (void) set_levltyp(x, y, CORR); + } else if (IS_WALL(lev->typ) || lev->typ == SDOOR) { + (void) set_levltyp(x, y, svl.level.flags.is_maze_lev ? ROOM + : svl.level.flags.is_cavernous_lev ? CORR + : DOOR); + } + if (clear_flags) + lev->flags = 0; /* set_levltyp doesn't take care of this [yet?] */ unearth_objs(x, y); + recalc_block_point(x, y); + break; + case TELEP_TRAP: + if (isok(gl.launchplace.x, gl.launchplace.y)) { + ttmp->teledest.x = gx.xstart + gl.launchplace.x; + ttmp->teledest.y = gy.ystart + gl.launchplace.y; + if (ttmp->teledest.x == x && ttmp->teledest.y == y) { + impossible("making fixed-dest tele trap pointing to itself"); + } + } break; } if (!oldplace) { - ttmp->ntrap = ftrap; - ftrap = ttmp; + ttmp->ntrap = gf.ftrap; + gf.ftrap = ttmp; } else { /* oldplace; it shouldn't be possible to override a sokoban pit or hole @@ -442,36 +587,36 @@ int x, y, typ; return ttmp; } +/* limit the destination of a hole or trapdoor to the furthest level you + should be able to fall to */ +d_level * +clamp_hole_destination(d_level *dlev) +{ + int bottom = dng_bottom(dlev); + + dlev->dlevel = min(dlev->dlevel, bottom); + return dlev; +} + void -fall_through(td, ftflags) -boolean td; /* td == TRUE : trap door or hole */ -unsigned ftflags; +fall_through( + boolean td, /* td == TRUE : trap door or hole */ + unsigned ftflags) { d_level dtmp; char msgbuf[BUFSZ]; const char *dont_fall = 0; - int newlevel, bottom; + int newlevel; struct trap *t = (struct trap *) 0; + boolean controlled_flight = FALSE; /* we'll fall even while levitating in Sokoban; otherwise, if we won't fall and won't be told that we aren't falling, give up now */ if (Blind && Levitation && !Sokoban) return; - bottom = dunlevs_in_dungeon(&u.uz); - /* when in the upper half of the quest, don't fall past the - middle "quest locate" level if hero hasn't been there yet */ - if (In_quest(&u.uz)) { - int qlocate_depth = qlocate_level.dlevel; - - /* deepest reached < qlocate implies current < qlocate */ - if (dunlev_reached(&u.uz) < qlocate_depth) - bottom = qlocate_depth; /* early cut-off */ - } newlevel = dunlev(&u.uz); /* current level */ - do { - newlevel++; - } while (!rn2(4) && newlevel < bottom); + newlevel++; if (td) { t = t_at(u.ux, u.uy); @@ -485,16 +630,15 @@ unsigned ftflags; } else pline_The("%s opens up under you!", surface(u.ux, u.uy)); - if (Sokoban && Can_fall_thru(&u.uz)) + if (Sokoban && Can_fall_thru(&u.uz)) { ; /* KMH -- You can't escape the Sokoban level traps */ - else if (Levitation || u.ustuck + } else if (Levitation || u.ustuck || (!Can_fall_thru(&u.uz) && !levl[u.ux][u.uy].candig) - || ((Flying || is_clinger(youmonst.data) - || (ceiling_hider(youmonst.data) && u.uundetected)) - && !(ftflags & TOOKPLUNGE)) - || (Inhell && !u.uevent.invoked && newlevel == bottom)) { + || ((Flying || is_clinger(gy.youmonst.data) + || (ceiling_hider(gy.youmonst.data) && u.uundetected)) + && !(ftflags & TOOKPLUNGE))) { dont_fall = "don't fall in."; - } else if (youmonst.data->msize >= MZ_HUGE) { + } else if (gy.youmonst.data->msize >= MZ_HUGE) { dont_fall = "don't fit through."; } else if (!next_to_u()) { dont_fall = "are jerked back by your pet!"; @@ -509,31 +653,45 @@ unsigned ftflags; } return; } - if ((Flying || is_clinger(youmonst.data)) - && (ftflags & TOOKPLUNGE) && td && t) + if ((Flying || is_clinger(gy.youmonst.data)) + && (ftflags & TOOKPLUNGE) && td && t) { + if (Flying) + controlled_flight = TRUE; You("%s down %s!", Flying ? "swoop" : "deliberately drop", (t->ttyp == TRAPDOOR) ? "through the trap door" : "into the gaping hole"); + } if (*u.ushops) shopdig(1); if (Is_stronghold(&u.uz)) { find_hell(&dtmp); } else { - int dist = newlevel - dunlev(&u.uz); - dtmp.dnum = u.uz.dnum; - dtmp.dlevel = newlevel; + int dist; + + if (t) { + assign_level(&dtmp, &t->dst); + /* don't fall beyond the bottom, in case this came from a bones + file with different dungeon size */ + (void) clamp_hole_destination(&dtmp); + } else { + dtmp.dnum = u.uz.dnum; + dtmp.dlevel = newlevel; + } + dist = depth(&dtmp) - depth(&u.uz); if (dist > 1) - You("fall down a %s%sshaft!", dist > 3 ? "very " : "", + You("%s down a %s%sshaft!", + controlled_flight ? "fly" : "fall", + dist > 3 ? "very " : "", dist > 2 ? "deep " : ""); } if (!td) Sprintf(msgbuf, "The hole in the %s above you closes up.", ceiling(u.ux, u.uy)); - schedule_goto(&dtmp, FALSE, TRUE, 0, (char *) 0, + schedule_goto(&dtmp, !Flying ? UTOTYPE_FALLING : UTOTYPE_NONE, (char *) 0, !td ? msgbuf : (char *) 0); } @@ -565,24 +723,25 @@ unsigned ftflags; * shop status--it's not worth the hassle.] */ struct monst * -animate_statue(statue, x, y, cause, fail_reason) -struct obj *statue; -xchar x, y; -int cause; -int *fail_reason; +animate_statue( + struct obj *statue, + coordxy x, + coordxy y, + int cause, + int *fail_reason) { + static const char + historic_statue_is_gone[] = "that the historic statue is now gone"; int mnum = statue->corpsenm; struct permonst *mptr = &mons[mnum]; struct monst *mon = 0, *shkp; struct obj *item; coord cc; boolean historic = (Role_if(PM_ARCHEOLOGIST) - && (statue->spe & STATUE_HISTORIC) != 0), + && (statue->spe & CORPSTAT_HISTORIC) != 0), golem_xform = FALSE, use_saved_traits; const char *comes_to_life; char statuename[BUFSZ], tmpbuf[BUFSZ]; - static const char historic_statue_is_gone[] = - "that the historic statue is now gone"; if (cant_revive(&mnum, TRUE, statue)) { /* mnum has changed; we won't be animating this statue as itself */ @@ -606,6 +765,11 @@ int *fail_reason; if (mon && mon->mtame && !mon->isminion) wary_dog(mon, TRUE); } else { + int sgend = (statue->spe & CORPSTAT_GENDER); + mmflags_nht mmflags = (NO_MINVENT | MM_NOMSG + | ((sgend == CORPSTAT_MALE) ? MM_MALE : 0) + | ((sgend == CORPSTAT_FEMALE) ? MM_FEMALE : 0)); + /* statues of unique monsters from bones or wishing end up here (cant_revive() sets mnum to be doppelganger; mptr reflects the original form for use by newcham()) */ @@ -613,16 +777,17 @@ int *fail_reason; /* block quest guards from other roles */ || (mptr->msound == MS_GUARDIAN && quest_info(MS_GUARDIAN) != mnum)) { - mon = makemon(&mons[PM_DOPPELGANGER], x, y, - NO_MINVENT | MM_NOCOUNTBIRTH | MM_ADJACENTOK); + mmflags |= MM_NOCOUNTBIRTH | MM_ADJACENTOK; + mon = makemon(&mons[PM_DOPPELGANGER], x, y, mmflags); /* if hero has protection from shape changers, cham field will be NON_PM; otherwise, set form to match the statue */ - if (mon && mon->cham >= LOW_PM) - (void) newcham(mon, mptr, FALSE, FALSE); - } else - mon = makemon(mptr, x, y, (cause == ANIMATE_SPELL) - ? (NO_MINVENT | MM_ADJACENTOK) - : NO_MINVENT); + if (mon && ismnum(mon->cham)) + (void) newcham(mon, mptr, NO_NC_FLAGS); + } else { + if (cause == ANIMATE_SPELL) + mmflags |= MM_ADJACENTOK; + mon = makemon(mptr, x, y, mmflags); + } } if (!mon) { @@ -633,11 +798,6 @@ int *fail_reason; return (struct monst *) 0; } - /* a non-montraits() statue might specify gender */ - if (statue->spe & STATUE_MALE) - mon->female = FALSE; - else if (statue->spe & STATUE_FEMALE) - mon->female = TRUE; /* if statue has been named, give same name to the monster */ if (has_oname(statue) && !unique_corpstat(mon->data)) mon = christen_monst(mon, ONAME(statue)); @@ -654,14 +814,12 @@ int *fail_reason; set_malign(mon); } - comes_to_life = !canspotmon(mon) - ? "disappears" - : golem_xform - ? "turns into flesh" - : (nonliving(mon->data) || is_vampshifter(mon)) - ? "moves" - : "comes to life"; - if ((x == u.ux && y == u.uy) || cause == ANIMATE_SPELL) { + comes_to_life = !canspotmon(mon) ? "disappears" + : golem_xform ? "turns into flesh" + : (nonliving(mon->data) || is_vampshifter(mon)) + ? "moves" + : "comes to life"; + if (u_at(x, y) || cause == ANIMATE_SPELL) { /* "the|your|Manlobbi's statue [of a wombat]" */ shkp = shop_keeper(*in_rooms(mon->mx, mon->my, SHOPBASE)); Sprintf(statuename, "%s%s", shk_your(tmpbuf, statue), @@ -685,6 +843,7 @@ int *fail_reason; pline("Instead of shattering, %s suddenly %s!", statuename, comes_to_life); } else { /* cause == ANIMATE_NORMAL */ + set_msg_xy(x, y); You("find %s posing as a statue.", canspotmon(mon) ? a_monnam(mon) : something); if (!canspotmon(mon) && Blind) @@ -694,7 +853,7 @@ int *fail_reason; /* if this isn't caused by a monster using a wand of striking, there might be consequences for the hero */ - if (!context.mon_moving) { + if (!svc.context.mon_moving) { /* if statue is owned by a shop, hero will have to pay for it; stolen_value gives a message (about debt or use of credit) which refers to "it" so needs to follow a message describing @@ -731,7 +890,7 @@ int *fail_reason; delobj(statue); /* avoid hiding under nothing */ - if (x == u.ux && y == u.uy && Upolyd && hides_under(youmonst.data) + if (u_at(x, y) && Upolyd && hides_under(gy.youmonst.data) && !OBJ_AT(x, y)) u.uundetected = 0; @@ -746,10 +905,11 @@ int *fail_reason; * or pick-axe. */ struct monst * -activate_statue_trap(trap, x, y, shatter) -struct trap *trap; -xchar x, y; -boolean shatter; +activate_statue_trap( + struct trap *trap, + coordxy x, + coordxy y, + boolean shatter) { struct monst *mtmp = (struct monst *) 0; struct obj *otmp = sobj_at(STATUE, x, y); @@ -775,10 +935,11 @@ boolean shatter; return mtmp; } -STATIC_OVL boolean -keep_saddle_with_steedcorpse(steed_mid, objchn, saddle) -unsigned steed_mid; -struct obj *objchn, *saddle; +staticfn boolean +keep_saddle_with_steedcorpse( + unsigned steed_mid, + struct obj *objchn, + struct obj *saddle) { if (!saddle) return FALSE; @@ -788,7 +949,7 @@ struct obj *objchn, *saddle; if (mtmp->m_id == steed_mid) { /* move saddle */ - xchar x, y; + coordxy x, y; if (get_obj_location(objchn, &x, &y, 0)) { obj_extract_self(saddle); place_object(saddle, x, y); @@ -807,19 +968,19 @@ struct obj *objchn, *saddle; /* monster or you go through and possibly destroy a web. return TRUE if could go through. */ -boolean -mu_maybe_destroy_web(mtmp, domsg, trap) -struct monst *mtmp; -boolean domsg; -struct trap *trap; +staticfn boolean +mu_maybe_destroy_web( + struct monst *mtmp, + boolean domsg, + struct trap *trap) { - boolean isyou = (mtmp == &youmonst); + boolean isyou = (mtmp == &gy.youmonst); struct permonst *mptr = mtmp->data; if (amorphous(mptr) || is_whirly(mptr) || flaming(mptr) || unsolid(mptr) || mptr == &mons[PM_GELATINOUS_CUBE]) { - xchar x = trap->tx; - xchar y = trap->ty; + coordxy x = trap->tx; + coordxy y = trap->ty; if (flaming(mptr) || acidic(mptr)) { if (domsg) { @@ -828,7 +989,8 @@ struct trap *trap; (flaming(mptr)) ? "burn" : "dissolve", a_your[trap->madeby_u]); else - pline("%s %s %s spider web!", Monnam(mtmp), + pline_mon(mtmp, + "%s %s %s spider web!", Monnam(mtmp), (flaming(mptr)) ? "burns" : "dissolves", a_your[trap->madeby_u]); } @@ -840,7 +1002,8 @@ struct trap *trap; if (isyou) { You("flow through %s spider web.", a_your[trap->madeby_u]); } else { - pline("%s flows through %s spider web.", Monnam(mtmp), + pline_mon(mtmp, + "%s flows through %s spider web.", Monnam(mtmp), a_your[trap->madeby_u]); seetrap(trap); } @@ -851,10 +1014,8 @@ struct trap *trap; } /* make a single arrow/dart/rock for a trap to shoot or drop */ -STATIC_OVL struct obj * -t_missile(otyp, trap) -int otyp; -struct trap *trap; +staticfn struct obj * +t_missile(int otyp, struct trap *trap) { struct obj *otmp = mksobj(otyp, TRUE, FALSE); @@ -866,23 +1027,22 @@ struct trap *trap; } void -set_utrap(tim, typ) -unsigned tim, typ; +set_utrap(unsigned int tim, unsigned int typ) { + /* if we get here through reset_utrap(), the caller of that might + have already set u.utrap to 0 so this check won't be sufficient + in that situation; caller will need to set context.botl itself */ + if (!u.utrap ^ !tim) + disp.botl = TRUE; + u.utrap = tim; - /* FIXME: - * utraptype==0 is bear trap rather than 'none'; we probably ought - * to change that but can't do so until save file compatability is - * able to be broken. - */ - u.utraptype = tim ? typ : 0; + u.utraptype = tim ? typ : TT_NONE; float_vs_flight(); /* maybe block Lev and/or Fly */ } void -reset_utrap(msg) -boolean msg; +reset_utrap(boolean msg) { boolean was_Lev = (Levitation != 0), was_Fly = (Flying != 0); @@ -896,99 +1056,215 @@ boolean msg; } } -void -dotrap(trap, trflags) -register struct trap *trap; -unsigned trflags; +/* is trap type ttyp triggered by touching the floor? */ +staticfn boolean +floor_trigger(int ttyp) { - register int ttype = trap->ttyp; - struct obj *otmp; - boolean already_seen = trap->tseen, - forcetrap = ((trflags & FORCETRAP) != 0 - || (trflags & FAILEDUNTRAP) != 0), - webmsgok = (trflags & NOWEBMSG) == 0, - forcebungle = (trflags & FORCEBUNGLE) != 0, - plunged = (trflags & TOOKPLUNGE) != 0, - viasitting = (trflags & VIASITTING) != 0, - conj_pit = conjoined_pits(trap, t_at(u.ux0, u.uy0), TRUE), - adj_pit = adj_nonconjoined_pit(trap); - int oldumort; - int steed_article = ARTICLE_THE; + switch (ttyp) { + case ARROW_TRAP: + case DART_TRAP: + case ROCKTRAP: + case SQKY_BOARD: + case BEAR_TRAP: + case LANDMINE: + case ROLLING_BOULDER_TRAP: + case SLP_GAS_TRAP: + case RUST_TRAP: + case FIRE_TRAP: + case PIT: + case SPIKED_PIT: + case HOLE: + case TRAPDOOR: + return TRUE; + default: + return FALSE; + } +} - nomul(0); +/* return TRUE if monster mtmp is up in the air, considering trap flags */ +staticfn boolean +check_in_air(struct monst *mtmp, unsigned trflags) +{ + boolean is_you = mtmp == &gy.youmonst, + plunged = (trflags & (TOOKPLUNGE | VIASITTING)) != 0; - /* KMH -- You can't escape the Sokoban level traps */ - if (Sokoban && (is_pit(ttype) || is_hole(ttype))) { - /* The "air currents" message is still appropriate -- even when - * the hero isn't flying or levitating -- because it conveys the - * reason why the player cannot escape the trap with a dexterity - * check, clinging to the ceiling, etc. - */ - pline("Air currents pull you down into %s %s!", - a_your[trap->madeby_u], - defsyms[trap_to_defsym(ttype)].explanation); - /* then proceed to normal trap effect */ - } else if (already_seen && !forcetrap) { - if ((Levitation || (Flying && !plunged)) - && (is_pit(ttype) || ttype == HOLE || ttype == BEAR_TRAP)) { - You("%s over %s %s.", Levitation ? "float" : "fly", - a_your[trap->madeby_u], - defsyms[trap_to_defsym(ttype)].explanation); - return; - } - if (!Fumbling && ttype != MAGIC_PORTAL && ttype != VIBRATING_SQUARE - && ttype != ANTI_MAGIC && !forcebungle && !plunged - && !conj_pit && !adj_pit - && (!rn2(5) || (is_pit(ttype) - && is_clinger(youmonst.data)))) { - You("escape %s %s.", (ttype == ARROW_TRAP && !trap->madeby_u) - ? "an" - : a_your[trap->madeby_u], - defsyms[trap_to_defsym(ttype)].explanation); - return; - } - } + return ((trflags & HURTLING) != 0 + || (is_you ? Levitation : is_floater(mtmp->data)) + || ((is_you ? Flying : is_flyer(mtmp->data)) && !plunged)); +} - if (u.usteed) { - u.usteed->mtrapseen |= (1 << (ttype - 1)); - /* suppress article in various steed messages when using its - name (which won't occur when hallucinating) */ - if (has_mname(u.usteed) && !Hallucination) - steed_article = ARTICLE_NONE; - } +/* return TRUE if mtmp is wearing shoes made of iron (iron/kicking) */ +boolean +wearing_iron_shoes(struct monst *mtmp) +{ + struct obj *armf = which_armor(mtmp, W_ARMF); + return armf && objects[armf->otyp].oc_material == IRON; +} - switch (ttype) { +/* is trap ttmp harmless to monster mtmp? */ +boolean +m_harmless_trap(struct monst *mtmp, struct trap *ttmp) +{ + struct permonst *mdat = mtmp->data; + + /* this handles most of the traps, but those are still included + in the switch case below for completeness */ + if (!Sokoban && floor_trigger(ttmp->ttyp) && check_in_air(mtmp, 0L)) + return TRUE; + + switch (ttmp->ttyp) { case ARROW_TRAP: + break; + case DART_TRAP: + break; + case ROCKTRAP: + break; + case SQKY_BOARD: + break; + case BEAR_TRAP: + if (mdat->msize <= MZ_SMALL || amorphous(mdat) + || is_whirly(mdat) || unsolid(mdat)) + return TRUE; + break; + case LANDMINE: + break; + case ROLLING_BOULDER_TRAP: + break; + case SLP_GAS_TRAP: + if (resists_sleep(mtmp) || defended(mtmp, AD_SLEE)) + return TRUE; + break; + case RUST_TRAP: + if (mdat != &mons[PM_IRON_GOLEM]) + return TRUE; + break; + case FIRE_TRAP: + if (resists_fire(mtmp) || defended(mtmp, AD_FIRE)) + return TRUE; + break; + case PIT: + FALLTHROUGH; + /*FALLTHRU*/ + case SPIKED_PIT: + FALLTHROUGH; + /*FALLTHRU*/ + case HOLE: + FALLTHROUGH; + /*FALLTHRU*/ + case TRAPDOOR: + if (is_clinger(mdat) && !Sokoban) + return TRUE; + break; + case TELEP_TRAP: + break; + case LEVEL_TELEP: + break; + case MAGIC_PORTAL: + break; + case WEB: + if (amorphous(mdat) || webmaker(mdat) + || is_whirly(mdat) || unsolid(mdat)) + return TRUE; + break; + case STATUE_TRAP: + return TRUE; + case MAGIC_TRAP: + return TRUE; /* usually */ + case ANTI_MAGIC: + if (resists_magm(mtmp) || defended(mtmp, AD_MAGM)) + return TRUE; + break; + case POLY_TRAP: + break; + case VIBRATING_SQUARE: + return TRUE; + default: + impossible("m_harmless_trap: unknown trap %i", ttmp->ttyp); + break; + } + + return FALSE; +} + +staticfn int +trapeffect_arrow_trap( + struct monst *mtmp, + struct trap *trap, + unsigned trflags UNUSED) +{ + struct obj *otmp; + int dam; + + if (mtmp == &gy.youmonst) { if (trap->once && trap->tseen && !rn2(15)) { + Soundeffect(se_loud_click, 100); You_hear("a loud click!"); deltrap(trap); newsym(u.ux, u.uy); - break; + return Trap_Is_Gone; } trap->once = 1; seetrap(trap); pline("An arrow shoots out at you!"); otmp = t_missile(ARROW, trap); + dam = dmgval(otmp, &gy.youmonst); if (u.usteed && !rn2(2) && steedintrap(trap, otmp)) { ; /* nothing */ - } else if (thitu(8, dmgval(otmp, &youmonst), &otmp, "arrow")) { + } else if (thitu(8, Maybe_Half_Phys(dam), &otmp, "arrow")) { if (otmp) obfree(otmp, (struct obj *) 0); } else { place_object(otmp, u.ux, u.uy); if (!Blind) - otmp->dknown = 1; + observe_object(otmp); stackobj(otmp); newsym(u.ux, u.uy); } - break; + } else { + boolean in_sight = canseemon(mtmp) || (mtmp == u.usteed); + boolean see_it = cansee(mtmp->mx, mtmp->my); + boolean trapkilled = FALSE; - case DART_TRAP: if (trap->once && trap->tseen && !rn2(15)) { + if (in_sight && see_it) + pline_mon(mtmp, + "%s triggers a trap but nothing happens.", + Monnam(mtmp)); + deltrap(trap); + newsym(mtmp->mx, mtmp->my); + return Trap_Is_Gone; + } + trap->once = 1; + otmp = t_missile(ARROW, trap); + if (in_sight) + seetrap(trap); + if (thitm(8, mtmp, otmp, 0, FALSE)) + trapkilled = TRUE; + + return trapkilled ? Trap_Killed_Mon : mtmp->mtrapped + ? Trap_Caught_Mon : Trap_Effect_Finished; + } + return Trap_Effect_Finished; +} + +staticfn int +trapeffect_dart_trap( + struct monst *mtmp, + struct trap *trap, + unsigned int trflags UNUSED) +{ + struct obj *otmp; + int dam; + + if (mtmp == &gy.youmonst) { + int oldumort = u.umortality; + + if (trap->once && trap->tseen && !rn2(15)) { + Soundeffect(se_soft_click, 30); You_hear("a soft click."); deltrap(trap); newsym(u.ux, u.uy); - break; + return Trap_Is_Gone; } trap->once = 1; seetrap(trap); @@ -996,10 +1272,10 @@ unsigned trflags; otmp = t_missile(DART, trap); if (!rn2(6)) otmp->opoisoned = 1; - oldumort = u.umortality; + dam = dmgval(otmp, &gy.youmonst); if (u.usteed && !rn2(2) && steedintrap(trap, otmp)) { ; /* nothing */ - } else if (thitu(7, dmgval(otmp, &youmonst), &otmp, "little dart")) { + } else if (thitu(7, Maybe_Half_Phys(dam), &otmp, "little dart")) { if (otmp) { if (otmp->opoisoned) poisoned("dart", A_CON, "little dart", @@ -1011,13 +1287,49 @@ unsigned trflags; } else { place_object(otmp, u.ux, u.uy); if (!Blind) - otmp->dknown = 1; + observe_object(otmp); stackobj(otmp); newsym(u.ux, u.uy); } - break; + } else { + boolean in_sight = canseemon(mtmp) || (mtmp == u.usteed); + boolean see_it = cansee(mtmp->mx, mtmp->my); + boolean trapkilled = FALSE; - case ROCKTRAP: + if (trap->once && trap->tseen && !rn2(15)) { + if (in_sight && see_it) + pline_mon(mtmp, + "%s triggers a trap but nothing happens.", + Monnam(mtmp)); + deltrap(trap); + newsym(mtmp->mx, mtmp->my); + return Trap_Is_Gone; + } + trap->once = 1; + otmp = t_missile(DART, trap); + if (!rn2(6)) + otmp->opoisoned = 1; + if (in_sight) + seetrap(trap); + if (thitm(7, mtmp, otmp, 0, FALSE)) + trapkilled = TRUE; + + return trapkilled ? Trap_Killed_Mon : mtmp->mtrapped + ? Trap_Caught_Mon : Trap_Effect_Finished; + } + return Trap_Effect_Finished; +} + +staticfn int +trapeffect_rocktrap( + struct monst *mtmp, + struct trap *trap, + unsigned trflags UNUSED) +{ + struct obj *otmp; + boolean harmless = FALSE; + + if (mtmp == &gy.youmonst) { if (trap->once && trap->tseen && !rn2(15)) { pline("A trap door in %s opens, but nothing falls out!", the(ceiling(u.ux, u.uy))); @@ -1034,24 +1346,76 @@ unsigned trflags; pline("A trap door in %s opens and %s falls on your %s!", the(ceiling(u.ux, u.uy)), an(xname(otmp)), body_part(HEAD)); if (uarmh) { - if (is_metallic(uarmh)) { + /* normally passes_rocks() would protect against a falling + rock, but not when wearing a helmet */ + if (passes_rocks(gy.youmonst.data)) { + pline("Unfortunately, you are wearing %s.", + an(helm_simple_name(uarmh))); /* helm or hat */ + dmg = 2; + } else if (hard_helmet(uarmh)) { pline("Fortunately, you are wearing a hard helmet."); dmg = 2; } else if (flags.verbose) { pline("%s does not protect you.", Yname2(uarmh)); } + } else if (passes_rocks(gy.youmonst.data)) { + pline("It passes harmlessly through you."); + harmless = TRUE; } if (!Blind) - otmp->dknown = 1; + observe_object(otmp); stackobj(otmp); newsym(u.ux, u.uy); /* map the rock */ - losehp(Maybe_Half_Phys(dmg), "falling rock", KILLED_BY_AN); - exercise(A_STR, FALSE); + if (!harmless) { + losehp(Maybe_Half_Phys(dmg), "falling rock", KILLED_BY_AN); + exercise(A_STR, FALSE); + } } - break; + } else { + boolean in_sight = canseemon(mtmp) || (mtmp == u.usteed); + boolean see_it = cansee(mtmp->mx, mtmp->my); + boolean trapkilled = FALSE; + + if (trap->once && trap->tseen && !rn2(15)) { + if (in_sight && see_it) + pline_mon(mtmp, + "A trap door above %s opens, but nothing falls out!", + mon_nam(mtmp)); + deltrap(trap); + newsym(mtmp->mx, mtmp->my); + return Trap_Is_Gone; + } + trap->once = 1; + otmp = t_missile(ROCK, trap); + if (in_sight) + seetrap(trap); + if (thitm(0, mtmp, otmp, d(2, 6), FALSE)) + trapkilled = TRUE; + + return trapkilled ? Trap_Killed_Mon : mtmp->mtrapped + ? Trap_Caught_Mon : Trap_Effect_Finished; + } + return Trap_Effect_Finished; +} - case SQKY_BOARD: /* stepped on a squeaky board */ +staticfn int +trapeffect_sqky_board( + struct monst *mtmp, + struct trap *trap, + unsigned trflags) +{ + enum sound_effect_entries tsnds[] = { + se_squeak_C, se_squeak_D_flat, se_squeak_D, + se_squeak_E_flat, se_squeak_E, se_squeak_F, + se_squeak_F_sharp, se_squeak_G, se_squeak_G_sharp, + se_squeak_A, se_squeak_B_flat, se_squeak_B, + }; + boolean forcetrap = ((trflags & FORCETRAP) != 0 + || (trflags & FAILEDUNTRAP) != 0 + || (Flying && (trflags & VIASITTING) != 0)); + + if (mtmp == &gy.youmonst) { if ((Levitation || Flying) && !forcetrap) { if (!Blind) { seetrap(trap); @@ -1062,29 +1426,82 @@ unsigned trflags; } } else { seetrap(trap); + if (IndexOk(trap->tnote, tsnds)) { + Soundeffect(tsnds[trap->tnote], 50); + } pline("A board beneath you %s%s%s.", Deaf ? "vibrates" : "squeaks ", - Deaf ? "" : trapnote(trap, 0), Deaf ? "" : " loudly"); - wake_nearby(); + Deaf ? "" : trapnote(trap, FALSE), + Deaf ? "" : " loudly"); + wake_nearby(FALSE); } - break; - - case BEAR_TRAP: { - int dmg = d(2, 4); + } else { + boolean in_sight = canseemon(mtmp) || (mtmp == u.usteed); + + if (m_in_air(mtmp)) + return Trap_Effect_Finished; + /* stepped on a squeaky board */ + if (in_sight) { + if (!Deaf) { + if (IndexOk(trap->tnote, tsnds)) { + Soundeffect(tsnds[trap->tnote], 50); + } + pline_mon(mtmp, + "A board beneath %s squeaks %s loudly.", + mon_nam(mtmp), trapnote(trap, FALSE)); + seetrap(trap); + } else if (!mindless(mtmp->data)) { + pline_mon(mtmp, + "%s stops momentarily and appears to cringe.", + Monnam(mtmp)); + } + } else { + /* same near/far threshold as mzapmsg() */ + int range = couldsee(mtmp->mx, mtmp->my) /* 9 or 5 */ + ? (BOLT_LIM + 1) : (BOLT_LIM - 3); + + if (IndexOk(trap->tnote, tsnds)) { + Soundeffect(tsnds[trap->tnote], + ((mdistu(mtmp) <= range * range) + ? 40 : 20)); + } + You_hear("%s squeak %s.", trapnote(trap, FALSE), + (mdistu(mtmp) <= range * range) + ? "nearby" : "in the distance"); + } + /* wake up nearby monsters */ + wake_nearto(mtmp->mx, mtmp->my, 40); + } + return Trap_Effect_Finished; +} + +staticfn int +trapeffect_bear_trap( + struct monst *mtmp, + struct trap *trap, + unsigned trflags) +{ + boolean is_you = mtmp == &gy.youmonst, + forcetrap = ((trflags & FORCETRAP) != 0 + || (trflags & FAILEDUNTRAP) != 0 + || (is_you && (trflags & VIASITTING) != 0)); + + if (is_you) { + int dmg = d(2, 4); if ((Levitation || Flying) && !forcetrap) - break; + return Trap_Effect_Finished; feeltrap(trap); - if (amorphous(youmonst.data) || is_whirly(youmonst.data) - || unsolid(youmonst.data)) { + if (amorphous(gy.youmonst.data) || is_whirly(gy.youmonst.data) + || unsolid(gy.youmonst.data)) { pline("%s bear trap closes harmlessly through you.", A_Your[trap->madeby_u]); - break; + return Trap_Effect_Finished; } - if (!u.usteed && youmonst.data->msize <= MZ_SMALL) { + if (!u.usteed && gy.youmonst.data->msize <= MZ_SMALL) { pline("%s bear trap closes harmlessly over you.", A_Your[trap->madeby_u]); - break; + return Trap_Effect_Finished; } set_utrap((unsigned) rn1(4, 4), TT_BEARTRAP); if (u.usteed) { @@ -1095,27 +1512,94 @@ unsigned trflags; } else { pline("%s bear trap closes on your %s!", A_Your[trap->madeby_u], body_part(FOOT)); - set_wounded_legs(rn2(2) ? RIGHT_SIDE : LEFT_SIDE, rn1(10, 10)); if (u.umonnum == PM_OWLBEAR || u.umonnum == PM_BUGBEAR) You("howl in anger!"); - losehp(Maybe_Half_Phys(dmg), "bear trap", KILLED_BY_AN); + if (wearing_iron_shoes(mtmp)) + pline("%s protects your leg.", Yname2(uarmf)); + else { + set_wounded_legs(rn2(2) ? RIGHT_SIDE : LEFT_SIDE, rn1(10, 10)); + losehp(Maybe_Half_Phys(dmg), "bear trap", KILLED_BY_AN); + } } exercise(A_DEX, FALSE); - break; + } else { + struct permonst *mptr = mtmp->data; + boolean in_sight = canseemon(mtmp) || (mtmp == u.usteed); + boolean trapkilled = FALSE; + + if (mptr->msize > MZ_SMALL && !amorphous(mptr) && !m_in_air(mtmp) + && !is_whirly(mptr) && !unsolid(mptr)) { + mtmp->mtrapped = 1; + if (in_sight) { + pline_mon(mtmp, + "%s is caught in %s bear trap!", Monnam(mtmp), + a_your[trap->madeby_u]); + seetrap(trap); + } else { + if (mptr == &mons[PM_OWLBEAR] + || mptr == &mons[PM_BUGBEAR]) { + Soundeffect(se_roar, 100); + You_hear("the roaring of an angry bear!"); + } + } + } else if (forcetrap) { + if (in_sight) { + pline_mon(mtmp, + "%s evades %s bear trap!", Monnam(mtmp), + a_your[trap->madeby_u]); + seetrap(trap); + } + } + if (mtmp->mtrapped && !wearing_iron_shoes(mtmp)) + trapkilled = thitm(0, mtmp, (struct obj *) 0, d(2, 4), FALSE); + + return trapkilled ? Trap_Killed_Mon : mtmp->mtrapped + ? Trap_Caught_Mon : Trap_Effect_Finished; } + return Trap_Effect_Finished; +} - case SLP_GAS_TRAP: +staticfn int +trapeffect_slp_gas_trap( + struct monst *mtmp, + struct trap *trap, + unsigned int trflags UNUSED) +{ + if (mtmp == &gy.youmonst) { seetrap(trap); - if (Sleep_resistance || breathless(youmonst.data)) { + if (Sleep_resistance || breathless(gy.youmonst.data)) { You("are enveloped in a cloud of gas!"); + monstseesu(M_SEEN_SLEEP); } else { pline("A cloud of gas puts you to sleep!"); fall_asleep(-rnd(25), TRUE); + monstunseesu(M_SEEN_SLEEP); } (void) steedintrap(trap, (struct obj *) 0); - break; + } else { + boolean in_sight = canseemon(mtmp) || (mtmp == u.usteed); - case RUST_TRAP: + if (!resists_sleep(mtmp) && !breathless(mtmp->data) + && !helpless(mtmp)) { + if (sleep_monst(mtmp, rnd(25), -1) && in_sight) { + pline_mon(mtmp, + "%s suddenly falls asleep!", Monnam(mtmp)); + seetrap(trap); + } + } + } + return Trap_Effect_Finished; +} + +staticfn int +trapeffect_rust_trap( + struct monst *mtmp, + struct trap *trap, + unsigned int trflags UNUSED) +{ + struct obj *otmp, *nextobj; + + if (mtmp == &gy.youmonst) { seetrap(trap); /* Unlike monsters, traps cannot aim their rust attacks at @@ -1134,22 +1618,23 @@ unsigned trflags; break; if (u.twoweap || (uwep && bimanual(uwep))) (void) water_damage(u.twoweap ? uswapwep : uwep, 0, TRUE); - glovecheck: - (void) water_damage(uarmg, "gauntlets", TRUE); - /* Not "metal gauntlets" since it gets called - * even if it's leather for the message - */ + uglovecheck: + (void) water_damage(uarmg, gloves_simple_name(uarmg), TRUE); break; case 2: pline("%s your right %s!", A_gush_of_water_hits, body_part(ARM)); (void) water_damage(uwep, 0, TRUE); - goto glovecheck; + goto uglovecheck; default: pline("%s you!", A_gush_of_water_hits); - for (otmp = invent; otmp; otmp = otmp->nobj) + /* note: exclude primary and secondary weapons from splashing + because cases 1 and 2 target them [via water_damage()] */ + for (otmp = gi.invent; otmp; otmp = nextobj) { + nextobj = otmp->nobj; if (otmp->lamplit && otmp != uwep && (otmp != uswapwep || !u.twoweap)) - (void) snuff_lit(otmp); + (void) splash_lit(otmp); + } if (uarmc) (void) water_damage(uarmc, cloak_simple_name(uarmc), TRUE); else if (uarm) @@ -1165,24 +1650,208 @@ unsigned trflags; You("are covered with rust!"); losehp(Maybe_Half_Phys(dam), "rusting away", KILLED_BY); } else if (u.umonnum == PM_GREMLIN && rn2(3)) { - (void) split_mon(&youmonst, (struct monst *) 0); + (void) split_mon(&gy.youmonst, (struct monst *) 0); } + } else { + boolean in_sight = canseemon(mtmp) || (mtmp == u.usteed); + boolean trapkilled = FALSE; + struct permonst *mptr = mtmp->data; + struct obj *target; - break; + if (in_sight) + seetrap(trap); + switch (rn2(5)) { + case 0: + if (in_sight) + pline_mon(mtmp, + "%s %s on the %s!", A_gush_of_water_hits, + mon_nam(mtmp), mbodypart(mtmp, HEAD)); + target = which_armor(mtmp, W_ARMH); + (void) water_damage(target, helm_simple_name(target), TRUE); + break; + case 1: + if (in_sight) + pline_mon(mtmp, + "%s %s's left %s!", A_gush_of_water_hits, + mon_nam(mtmp), mbodypart(mtmp, ARM)); + target = which_armor(mtmp, W_ARMS); + if (water_damage(target, "shield", TRUE) != ER_NOTHING) + break; + target = MON_WEP(mtmp); + if (target && bimanual(target)) + (void) water_damage(target, 0, TRUE); + mglovecheck: + target = which_armor(mtmp, W_ARMG); + (void) water_damage(target, gloves_simple_name(target), TRUE); + break; + case 2: + if (in_sight) + pline_mon(mtmp, + "%s %s's right %s!", A_gush_of_water_hits, + mon_nam(mtmp), mbodypart(mtmp, ARM)); + (void) water_damage(MON_WEP(mtmp), 0, TRUE); + goto mglovecheck; + default: + if (in_sight) + pline("%s %s!", A_gush_of_water_hits, mon_nam(mtmp)); + for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj) + if (otmp->lamplit + /* exclude weapon(s) because cases 1 and 2 do them */ + && (otmp->owornmask & (W_WEP | W_SWAPWEP)) == 0) + (void) splash_lit(otmp); + if ((target = which_armor(mtmp, W_ARMC)) != 0) + (void) water_damage(target, cloak_simple_name(target), + TRUE); + else if ((target = which_armor(mtmp, W_ARM)) != 0) + (void) water_damage(target, suit_simple_name(target), + TRUE); + else if ((target = which_armor(mtmp, W_ARMU)) != 0) + (void) water_damage(target, "shirt", TRUE); + } - case FIRE_TRAP: + if (completelyrusts(mptr)) { + if (in_sight) + pline_mon(mtmp, "%s %s to pieces!", Monnam(mtmp), + !mlifesaver(mtmp) ? "falls" : "starts to fall"); + monkilled(mtmp, (const char *) 0, AD_RUST); + if (DEADMONSTER(mtmp)) + trapkilled = TRUE; + } else if (mptr == &mons[PM_GREMLIN] && rn2(3)) { + (void) split_mon(mtmp, (struct monst *) 0); + } + + return trapkilled ? Trap_Killed_Mon : mtmp->mtrapped + ? Trap_Caught_Mon : Trap_Effect_Finished; + } + return Trap_Effect_Finished; +} + +staticfn int +trapeffect_fire_trap( + struct monst *mtmp, + struct trap *trap, + unsigned int trflags UNUSED) +{ + if (mtmp == &gy.youmonst) { seetrap(trap); dofiretrap((struct obj *) 0); - break; + } else { + coordxy tx = trap->tx, ty = trap->ty; + boolean in_sight = canseemon(mtmp) || (mtmp == u.usteed); + boolean see_it = cansee(tx, ty); + boolean trapkilled = FALSE; + struct permonst *mptr = mtmp->data; + int orig_dmg = d(2, 4); + + if (in_sight) + pline_mon(mtmp, + "A %s erupts from the %s under %s!", tower_of_flame, + surface(mtmp->mx, mtmp->my), mon_nam(mtmp)); + else if (see_it) { /* evidently `mtmp' is invisible */ + set_msg_xy(mtmp->mx, mtmp->my); + You_see("a %s erupt from the %s!", tower_of_flame, + surface(mtmp->mx, mtmp->my)); + } + if (resists_fire(mtmp)) { + if (in_sight) { + shieldeff(mtmp->mx, mtmp->my); + pline("%s is uninjured.", Monnam(mtmp)); + } + } else { + int num = orig_dmg, alt; + boolean immolate = FALSE; + + /* paper burns very fast, assume straw is tightly packed + and burns a bit slower + (note: this is inconsistent with mattackm()'s AD_FIRE + damage where completelyburns() includes straw golem) */ + switch (monsndx(mptr)) { + case PM_PAPER_GOLEM: + immolate = TRUE; + alt = mtmp->mhpmax; + break; + case PM_STRAW_GOLEM: + alt = mtmp->mhpmax / 2; + break; + case PM_WOOD_GOLEM: + alt = mtmp->mhpmax / 4; + break; + case PM_LEATHER_GOLEM: + alt = mtmp->mhpmax / 8; + break; + default: + alt = 0; + break; + } + if (alt > num) + num = alt; + + if (thitm(0, mtmp, (struct obj *) 0, num, immolate)) + trapkilled = TRUE; + else { + mtmp->mhpmax -= rn2(num + 1); + if (mtmp->mhp > mtmp->mhpmax) + mtmp->mhp = mtmp->mhpmax; + } + } + if (burnarmor(mtmp) || rn2(3)) { + int xtradmg = destroy_items(mtmp, AD_FIRE, orig_dmg); + ignite_items(mtmp->minvent); + if (!DEADMONSTER(mtmp)) { + mtmp->mhp -= xtradmg; + if (DEADMONSTER(mtmp)) { /* NOW it's dead */ + monkilled(mtmp, "", AD_FIRE); + trapkilled = TRUE; + } + } + } + if (burn_floor_objects(tx, ty, see_it, FALSE) + && !see_it && distu(tx, ty) <= 3 * 3) + You("smell smoke."); + if (is_ice(tx, ty)) + melt_ice(tx, ty, (char *) 0); + if (DEADMONSTER(mtmp)) + trapkilled = TRUE; + if (see_it && t_at(tx, ty)) + seetrap(t_at(tx, ty)); + + return trapkilled ? Trap_Killed_Mon : mtmp->mtrapped + ? Trap_Caught_Mon : Trap_Effect_Finished; + } + return Trap_Effect_Finished; +} + +staticfn int +trapeffect_pit( + struct monst *mtmp, + struct trap *trap, + unsigned int trflags) +{ + int ttype = trap->ttyp; + /* relevant_spikes is initially always true for spiked pits, but + set to false if the spikes are found to not be relevant */ + boolean relevant_spikes = ttype == SPIKED_PIT; + + if (mtmp == &gy.youmonst) { + boolean plunged = (trflags & TOOKPLUNGE) != 0; + boolean viasitting = (trflags & VIASITTING) != 0; + boolean conj_pit = conjoined_pits(trap, t_at(u.ux0, u.uy0), TRUE); + boolean adj_pit = adj_nonconjoined_pit(trap); + boolean already_known = trap->tseen ? TRUE : FALSE; + boolean deliberate = FALSE; + int steed_article = ARTICLE_THE; + + /* suppress article in various steed messages when using its + name (which won't occur when hallucinating) */ + if (u.usteed && has_mgivenname(u.usteed) && !Hallucination) + steed_article = ARTICLE_NONE; - case PIT: - case SPIKED_PIT: /* KMH -- You can't escape the Sokoban level traps */ - if (!Sokoban && (Levitation || (Flying && !plunged))) - break; + if (!Sokoban && (Levitation || (Flying && !plunged && !viasitting))) + return Trap_Effect_Finished; feeltrap(trap); - if (!Sokoban && is_clinger(youmonst.data) && !plunged) { - if (trap->tseen) { + if (!Sokoban && is_clinger(gy.youmonst.data) && !plunged) { + if (already_known) { You_see("%s %spit below you.", a_your[trap->madeby_u], ttype == SPIKED_PIT ? "spiked " : ""); } else { @@ -1190,7 +1859,7 @@ unsigned trflags; ttype == SPIKED_PIT ? "full of spikes " : ""); You("don't fall in!"); } - break; + return Trap_Effect_Finished; } if (!Sokoban) { char verbbuf[BUFSZ]; @@ -1205,6 +1874,10 @@ unsigned trflags; Sprintf(verbbuf, "lead %s", x_monnam(u.usteed, steed_article, "poor", SUPPRESS_SADDLE, FALSE)); + } else if (iflags.menu_requested && already_known) { + You("carefully %s into the pit.", + u_locomotion("lower yourself")); + deliberate = TRUE; } else if (conj_pit) { You("move into an adjacent pit."); } else if (adj_pit) { @@ -1225,7 +1898,10 @@ unsigned trflags; } else if (u.umonnum == PM_PIT_VIPER || u.umonnum == PM_PIT_FIEND) { pline("How pitiful. Isn't that the pits?"); } - if (ttype == SPIKED_PIT) { + if (relevant_spikes && wearing_iron_shoes(mtmp)) { + pline("%s protects you from the sharp iron spikes.", Yname2(uarmf)); + relevant_spikes = FALSE; + } else if (relevant_spikes) { const char *predicament = "on a set of sharp iron spikes"; if (u.usteed) { @@ -1243,36 +1919,37 @@ unsigned trflags; */ set_utrap((unsigned) rn1(6, 2), TT_PIT); if (!steedintrap(trap, (struct obj *) 0)) { - if (ttype == SPIKED_PIT) { - oldumort = u.umortality; + if (relevant_spikes) { + int oldumort = u.umortality; + losehp(Maybe_Half_Phys(rnd(conj_pit ? 4 : adj_pit ? 6 : 10)), /* note: these don't need locomotion() handling; if fatal while poly'd and Unchanging, the death reason will be overridden with "killed while stuck in creature form" */ plunged - ? "deliberately plunged into a pit of iron spikes" - : conj_pit - ? "stepped into a pit of iron spikes" - : adj_pit - ? "stumbled into a pit of iron spikes" - : "fell into a pit of iron spikes", + ? "deliberately plunged into a pit of iron spikes" + : (conj_pit || deliberate) + ? "stepped into a pit of iron spikes" + : adj_pit + ? "stumbled into a pit of iron spikes" + : "fell into a pit of iron spikes", NO_KILLER_PREFIX); if (!rn2(6)) poisoned("spikes", A_STR, - (conj_pit || adj_pit) - ? "stepping on poison spikes" - : "fall onto poison spikes", + (conj_pit || adj_pit || deliberate) + ? "stepping on poison spikes" + : "fall onto poison spikes", /* if damage triggered life-saving, poison is limited to attrib loss */ (u.umortality > oldumort) ? 0 : 8, FALSE); } else { /* plunging flyers take spike damage but not pit damage */ - if (!conj_pit - && !(plunged && (Flying || is_clinger(youmonst.data)))) + if (!conj_pit && !deliberate + && !(plunged && (Flying || is_clinger(gy.youmonst.data)))) losehp(Maybe_Half_Phys(rnd(adj_pit ? 3 : 6)), plunged ? "deliberately plunged into a pit" - : "fell into a pit", + : "fell into a pit", NO_KILLER_PREFIX); } if (Punished && !carried(uball)) { @@ -1282,42 +1959,175 @@ unsigned trflags; } if (!conj_pit) selftouch("Falling, you"); - vision_full_recalc = 1; /* vision limits change */ + gv.vision_full_recalc = 1; /* vision limits change */ exercise(A_STR, FALSE); exercise(A_DEX, FALSE); } - break; + } else { + boolean in_sight = canseemon(mtmp) || (mtmp == u.usteed); + boolean trapkilled = FALSE; + boolean forcetrap = ((trflags & FORCETRAP) != 0); + boolean inescapable = (forcetrap || (Sokoban && !trap->madeby_u)); + struct permonst *mptr = mtmp->data; + const char *fallverb; - case HOLE: - case TRAPDOOR: + fallverb = "falls"; + if (!grounded(mptr) || (mtmp->wormno && count_wsegs(mtmp) > 5)) { + if (forcetrap && !Sokoban) { + /* openfallingtrap; not inescapable here */ + if (in_sight) { + seetrap(trap); + pline_mon(mtmp, + "%s doesn't fall into the pit.", Monnam(mtmp)); + } + return Trap_Effect_Finished; + } + if (!inescapable) + return Trap_Effect_Finished; /* avoids trap */ + fallverb = "is dragged"; /* sokoban pit */ + } + if (!passes_walls(mptr)) + mtmp->mtrapped = 1; + if (in_sight) { + pline_mon(mtmp, + "%s %s into %s pit!", Monnam(mtmp), fallverb, + a_your[trap->madeby_u]); + if (mptr == &mons[PM_PIT_VIPER] + || mptr == &mons[PM_PIT_FIEND]) + pline("How pitiful. Isn't that the pits?"); + seetrap(trap); + } + mselftouch(mtmp, "Falling, ", FALSE); + if (wearing_iron_shoes(mtmp)) relevant_spikes = FALSE; + if (DEADMONSTER(mtmp) || thitm(0, mtmp, (struct obj *) 0, + rnd(relevant_spikes ? 10 : 6), FALSE)) + trapkilled = TRUE; + + return trapkilled ? Trap_Killed_Mon : mtmp->mtrapped + ? Trap_Caught_Mon : Trap_Effect_Finished; + } + return Trap_Effect_Finished; +} + +staticfn int +trapeffect_hole( + struct monst *mtmp, + struct trap *trap, + unsigned int trflags) +{ + if (mtmp == &gy.youmonst) { if (!Can_fall_thru(&u.uz)) { seetrap(trap); /* normally done in fall_through */ impossible("dotrap: %ss cannot exist on this level.", - defsyms[trap_to_defsym(ttype)].explanation); - break; /* don't activate it after all */ + trapname(trap->ttyp, TRUE)); + return Trap_Effect_Finished; /* don't activate it after all */ } fall_through(TRUE, (trflags & TOOKPLUNGE)); - break; + } else { + int tt = trap->ttyp; + struct permonst *mptr = mtmp->data; + boolean in_sight = canseemon(mtmp) || (mtmp == u.usteed); + boolean forcetrap = ((trflags & FORCETRAP) != 0); + boolean inescapable = (forcetrap || (Sokoban && !trap->madeby_u)); - case TELEP_TRAP: + if (!Can_fall_thru(&u.uz)) { + impossible("mintrap: %ss cannot exist on this level.", + trapname(tt, TRUE)); + return Trap_Effect_Finished; /* don't activate it after all */ + } + if (!grounded(mptr) || (mtmp->wormno && count_wsegs(mtmp) > 5) + || mptr->msize >= MZ_HUGE) { + if (forcetrap && !Sokoban) { + /* openfallingtrap; not inescapable here */ + if (in_sight) { + seetrap(trap); + if (tt == TRAPDOOR) + pline_mon(mtmp, + "A trap door opens, but %s doesn't fall through.", + mon_nam(mtmp)); + else /* (tt == HOLE) */ + pline_mon(mtmp, + "%s doesn't fall through the hole.", + Monnam(mtmp)); + } + return Trap_Effect_Finished; /* inescapable = FALSE; */ + } + if (inescapable) { /* sokoban hole */ + if (in_sight) { + pline_mon(mtmp, + "%s seems to be yanked down!", Monnam(mtmp)); + seetrap(trap); + } + } else + return Trap_Effect_Finished; + } + return trapeffect_level_telep(mtmp, trap, trflags); + } + return Trap_Effect_Finished; +} + +staticfn int +trapeffect_telep_trap( + struct monst *mtmp, + struct trap *trap, + unsigned int trflags UNUSED) +{ + if (mtmp == &gy.youmonst) { seetrap(trap); tele_trap(trap); - break; + } else { + boolean in_sight = canseemon(mtmp) || (mtmp == u.usteed); - case LEVEL_TELEP: + mtele_trap(mtmp, trap, in_sight); + return Trap_Moved_Mon; + } + return Trap_Effect_Finished; +} + +staticfn int +trapeffect_level_telep( + struct monst *mtmp, + struct trap *trap, + unsigned int trflags) +{ + if (mtmp == &gy.youmonst) { seetrap(trap); level_tele_trap(trap, trflags); - break; + } else { + boolean in_sight = canseemon(mtmp) || (mtmp == u.usteed); + boolean forcetrap = ((trflags & FORCETRAP) != 0); + + return mlevel_tele_trap(mtmp, trap, forcetrap, in_sight); + } + return Trap_Effect_Finished; +} + +staticfn int +trapeffect_web( + struct monst *mtmp, + struct trap *trap, + unsigned int trflags) +{ + if (mtmp == &gy.youmonst) { + boolean webmsgok = (trflags & NOWEBMSG) == 0; + boolean forcetrap = ((trflags & FORCETRAP) != 0 + || (trflags & FAILEDUNTRAP) != 0); + boolean viasitting = (trflags & VIASITTING) != 0; + int steed_article = ARTICLE_THE; + + /* suppress article in various steed messages when using its + name (which won't occur when hallucinating) */ + if (u.usteed && has_mgivenname(u.usteed) && !Hallucination) + steed_article = ARTICLE_NONE; - case WEB: /* Our luckless player has stumbled into a web. */ feeltrap(trap); - if (mu_maybe_destroy_web(&youmonst, webmsgok, trap)) - break; - if (webmaker(youmonst.data)) { + if (mu_maybe_destroy_web(&gy.youmonst, webmsgok, trap)) + return Trap_Effect_Finished; + if (webmaker(gy.youmonst.data)) { if (webmsgok) pline(trap->madeby_u ? "You take a walk on your web." - : "There is a spider web here."); - break; + : "There is a spider web here."); + return Trap_Effect_Finished; } if (webmsgok) { char verbbuf[BUFSZ]; @@ -1329,9 +2139,7 @@ unsigned trflags; x_monnam(u.usteed, steed_article, "poor", SUPPRESS_SADDLE, FALSE)); } else { - Sprintf(verbbuf, "%s into", - Levitation ? (const char *) "float" - : locomotion(youmonst.data, "stumble")); + Sprintf(verbbuf, "%s into", u_locomotion("stumble")); } You("%s %s spider web!", verbbuf, a_your[trap->madeby_u]); } @@ -1339,7 +2147,7 @@ unsigned trflags; /* time will be adjusted below */ set_utrap(1, TT_WEB); - /* Time stuck in the web depends on your/steed strength. */ + /* Time stuck in the web depends on your/steed's strength. */ { int tim, str = ACURR(A_STR); @@ -1357,14 +2165,15 @@ unsigned trflags; u.usteed->mx = u.ux; u.usteed->my = u.uy; - /* mintrap currently does not return 2(died) for webs */ - if (mintrap(u.usteed)) { + /* mintrap currently does not return Trap_Killed_Mon + (mon died) for webs */ + if (mintrap(u.usteed, trflags) != Trap_Effect_Finished) { u.usteed->mtrapped = 0; if (strongmonst(u.usteed->data)) str = 17; } else { reset_utrap(FALSE); - break; + return Trap_Effect_Finished; } webmsgok = FALSE; /* mintrap printed the messages */ @@ -1392,13 +2201,101 @@ unsigned trflags; } set_utrap((unsigned) tim, TT_WEB); } - break; + } else { + /* Monster in a web. */ + boolean tear_web; + boolean in_sight = canseemon(mtmp) || (mtmp == u.usteed); + boolean forcetrap = ((trflags & FORCETRAP) != 0); + struct permonst *mptr = mtmp->data; + + if (webmaker(mptr)) + return Trap_Effect_Finished; + if (mu_maybe_destroy_web(mtmp, in_sight, trap)) + return Trap_Effect_Finished; + tear_web = FALSE; + switch (monsndx(mptr)) { + case PM_OWLBEAR: /* Eric Backus */ + case PM_BUGBEAR: + if (!in_sight) { + Soundeffect(se_roar, 60); + You_hear("the roaring of a confused bear!"); + mtmp->mtrapped = 1; + break; + } + FALLTHROUGH; + /*FALLTHRU*/ + default: + if (mptr->mlet == S_GIANT + /* exclude baby dragons and relatively short worms */ + || (mptr->mlet == S_DRAGON && extra_nasty(mptr)) + || (mtmp->wormno && count_wsegs(mtmp) > 5)) { + tear_web = TRUE; + } else if (in_sight) { + pline_mon(mtmp, + "%s is caught in %s spider web.", Monnam(mtmp), + a_your[trap->madeby_u]); + seetrap(trap); + } + mtmp->mtrapped = tear_web ? 0 : 1; + break; + /* this list is fairly arbitrary; it deliberately + excludes wumpus & giant/ettin zombies/mummies */ + case PM_TITANOTHERE: + case PM_BALUCHITHERIUM: + case PM_PURPLE_WORM: + case PM_JABBERWOCK: + case PM_IRON_GOLEM: + case PM_BALROG: + case PM_KRAKEN: + case PM_MASTODON: + case PM_ORION: + case PM_NORN: + case PM_CYCLOPS: + case PM_LORD_SURTUR: + tear_web = TRUE; + break; + } + if (tear_web) { + if (in_sight) + pline_mon(mtmp, + "%s tears through %s spider web!", Monnam(mtmp), + a_your[trap->madeby_u]); + deltrap(trap); + newsym(mtmp->mx, mtmp->my); + } else if (forcetrap && !mtmp->mtrapped) { + if (in_sight) { + pline_mon(mtmp, + "%s avoids %s spider web!", Monnam(mtmp), + a_your[trap->madeby_u]); + seetrap(trap); + } + } + return mtmp->mtrapped ? Trap_Caught_Mon : Trap_Effect_Finished; + } + return Trap_Effect_Finished; +} - case STATUE_TRAP: +staticfn int +trapeffect_statue_trap( + struct monst *mtmp, + struct trap *trap, + unsigned int trflags UNUSED) +{ + if (mtmp == &gy.youmonst) { (void) activate_statue_trap(trap, u.ux, u.uy, FALSE); - break; + } else { + /* monsters don't trigger statue traps */ + } + return Trap_Effect_Finished; +} - case MAGIC_TRAP: /* A magic trap. */ +staticfn int +trapeffect_magic_trap( + struct monst *mtmp, + struct trap *trap, + unsigned int trflags) +{ + if (mtmp == &gy.youmonst) { seetrap(trap); if (!rn2(30)) { deltrap(trap); @@ -1407,21 +2304,52 @@ unsigned trflags; losehp(rnd(10), "magical explosion", KILLED_BY_AN); Your("body absorbs some of the magical energy!"); u.uen = (u.uenmax += 2); - break; + if (u.uenmax > u.uenpeak) + u.uenpeak = u.uenmax; + return Trap_Effect_Finished; } else { domagictrap(); } (void) steedintrap(trap, (struct obj *) 0); - break; + } else { + /* A magic trap. Monsters usually immune. */ + if (!rn2(21)) + return trapeffect_fire_trap(mtmp, trap, trflags); + } + return Trap_Effect_Finished; +} + +staticfn int +trapeffect_anti_magic( + struct monst *mtmp, /* monster, possibly youmonst */ + struct trap *trap, /* trap->ttyp == ANTI_MAGIC */ + unsigned int trflags UNUSED) +{ + if (wearing_iron_shoes(mtmp)) { + struct obj *shoes = which_armor(mtmp, W_ARMF); + /* iron shoes protect against antimagic traps only if + positively enchanted; the trap drains the enchantment + rather than the wearer */ + if (shoes->spe > 0) { + /* no message if a monster does this, it isn't visible enough */ + if (mtmp == &gy.youmonst) { + seetrap(trap); + pline("A lethargic aura surrounds %s.", yname(shoes)); + costly_alteration(shoes, COST_DECHNT); + } + shoes->spe -= 1; + update_inventory(); + return Trap_Effect_Finished; + } + } + + if (mtmp == &gy.youmonst) { + int drain, halfd; + boolean exclaim_it = FALSE; - case ANTI_MAGIC: seetrap(trap); - /* hero without magic resistance loses spell energy, - hero with magic resistance takes damage instead; - possibly non-intuitive but useful for play balance */ - if (!Antimagic) { - drain_en(rnd(u.ulevel) + 1); - } else { + if (Antimagic) { + struct obj *otmp; int dmgval2 = rnd(4), hp = Upolyd ? u.mh : u.uhp; /* Half_XXX_damage has opposite its usual effect (approx) @@ -1429,12 +2357,12 @@ unsigned trflags; if (Half_physical_damage || Half_spell_damage) dmgval2 += rnd(4); /* give Magicbane wielder dose of own medicine */ - if (uwep && uwep->oartifact == ART_MAGICBANE) + if (u_wield_art(ART_MAGICBANE)) dmgval2 += rnd(4); /* having an artifact--other than own quest one--which confers magic resistance simply by being carried also increases the effect */ - for (otmp = invent; otmp; otmp = otmp->nobj) + for (otmp = gi.invent; otmp; otmp = otmp->nobj) if (otmp->oartifact && !is_quest_artifact(otmp) && defends_when_carried(AD_MAGM, otmp)) break; @@ -1444,16 +2372,99 @@ unsigned trflags; dmgval2 = (dmgval2 + 3) / 4; You_feel((dmgval2 >= hp) ? "unbearably torpid!" - : (dmgval2 >= hp / 4) ? "very lethargic." - : "sluggish."); + : (dmgval2 >= hp / 4) ? "very lethargic." + : "sluggish."); /* opposite of magical explosion */ losehp(dmgval2, "anti-magic implosion", KILLED_BY_AN); } - break; - case POLY_TRAP: { + /* if the drain amount is more than hero's maximum energy then up + to half of the amount comes directly out of maximum, the rest + comes out of current energy; drain_en() lowers the current + amount and when doing so it will take even more from maximum + if the new current value would drop below zero */ + drain = d(2, 6); /* 2d6 => 2..12 */ + halfd = rnd(drain / 2); /* 1..drain/2 (round down) */ + if (u.uenmax > drain) { /* [was u.uenmax > halfd] */ + /* note: since 'halfd' is no more than half, 'drain -= halfd' + is at least as big, so drain_en() is never asked to remove + less from current than what we're removing from maximum; + however, it might do that anyway (via its throttle check) so + it needs to make sure uen doesn't end up exceeding uenmax */ + u.uenmax -= halfd; /* drain_en() will set context.botl */ + drain -= halfd; + exclaim_it = TRUE; + } + drain_en(drain, exclaim_it); + } else { + boolean trapkilled = FALSE; + boolean in_sight = canseemon(mtmp) || (mtmp == u.usteed); + boolean see_it = cansee(mtmp->mx, mtmp->my); + struct permonst *mptr = mtmp->data; + + /* similar to hero's case, more or less */ + if (!resists_magm(mtmp)) { /* lose spell energy */ + if (!mtmp->mcan && (attacktype(mptr, AT_MAGC) + || attacktype(mptr, AT_BREA))) { + mtmp->mspec_used += d(2, 6); + if (in_sight) { + seetrap(trap); + pline_mon(mtmp, "%s seems lethargic.", + Monnam(mtmp)); + } + } + } else { /* take some damage */ + struct obj *otmp; + int dmgval2 = rnd(4); + + if ((otmp = MON_WEP(mtmp)) != 0 + && is_art(otmp, ART_MAGICBANE)) + dmgval2 += rnd(4); + for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj) + if (otmp->oartifact + && defends_when_carried(AD_MAGM, otmp)) + break; + if (otmp) + dmgval2 += rnd(4); + if (passes_walls(mptr)) + dmgval2 = (dmgval2 + 3) / 4; + + if (in_sight) + seetrap(trap); + mtmp->mhp -= dmgval2; + if (DEADMONSTER(mtmp)) + monkilled(mtmp, + in_sight + ? "compression from an anti-magic field" + : (const char *) 0, + -AD_MAGM); + if (DEADMONSTER(mtmp)) + trapkilled = TRUE; + if (see_it) + newsym(trap->tx, trap->ty); + } + return trapkilled ? Trap_Killed_Mon : mtmp->mtrapped + ? Trap_Caught_Mon : Trap_Effect_Finished; + } + return Trap_Effect_Finished; +} + +staticfn int +trapeffect_poly_trap( + struct monst *mtmp, + struct trap *trap, + unsigned int trflags) +{ + if (mtmp == &gy.youmonst) { + boolean viasitting = (trflags & VIASITTING) != 0; + int steed_article = ARTICLE_THE; char verbbuf[BUFSZ]; + /* suppress article in various steed messages when using its + name (which won't occur when hallucinating) */ + if (u.usteed && has_mgivenname(u.usteed) && !Hallucination) + steed_article = ARTICLE_NONE; + seetrap(trap); if (viasitting) Strcpy(verbbuf, "trigger"); /* follows "You sit down." */ @@ -1462,11 +2473,17 @@ unsigned trflags; x_monnam(u.usteed, steed_article, (char *) 0, SUPPRESS_SADDLE, FALSE)); else - Sprintf(verbbuf, "%s onto", - Levitation ? (const char *) "float" - : locomotion(youmonst.data, "step")); + Sprintf(verbbuf, "%s onto", u_locomotion("step")); You("%s a polymorph trap!", verbbuf); - if (Antimagic || Unchanging) { + if (wearing_iron_shoes(mtmp)) { + deltrap(trap); + pline("%s warps strangely.", Yname2(uarmf)); + poly_obj( + uarmf, uarmf->otyp == IRON_SHOES ? KICKING_BOOTS : IRON_SHOES); + update_inventory(); + if (uarmf) + prinv(NULL, uarmf, 0); + } else if (Antimagic || Unchanging) { shieldeff(u.ux, u.uy); You_feel("momentarily different."); /* Trap did nothing; don't remove it --KAA */ @@ -1475,37 +2492,83 @@ unsigned trflags; deltrap(trap); /* delete trap before polymorph */ newsym(u.ux, u.uy); /* get rid of trap symbol */ You_feel("a change coming over you."); - polyself(0); + polyself(POLY_NOFLAGS); + } + } else { + boolean in_sight = canseemon(mtmp) || (mtmp == u.usteed); + + if (wearing_iron_shoes(mtmp)) { + /* remove and readd the shoes to forcibly unwear them */ + struct obj *shoes = which_armor(mtmp, W_ARMF); + extract_from_minvent(mtmp, shoes, TRUE, TRUE); + if (mpickobj(mtmp, shoes)) { + impossible("re-equipping iron shoes destroyed them?"); + return Trap_Effect_Finished; + } + shoes = poly_obj( + shoes, shoes->otyp == IRON_SHOES ? KICKING_BOOTS : IRON_SHOES); + /* now equip them again */ + if (shoes) { + mtmp->misc_worn_check |= W_ARMF; + shoes->owornmask = W_ARMF; + update_mon_extrinsics(mtmp, shoes, TRUE, TRUE); + } + } else if (resists_magm(mtmp)) { + shieldeff_mon(mtmp); + } else if (!resist(mtmp, WAND_CLASS, 0, NOTELL)) { + (void) newcham(mtmp, (struct permonst *) 0, NC_SHOW_MSG); + if (in_sight) + seetrap(trap); } - break; } - case LANDMINE: { + return Trap_Effect_Finished; +} + +staticfn int +trapeffect_landmine( + struct monst *mtmp, + struct trap *trap, + unsigned int trflags) +{ + int damage = rnd(16); + /* iron shoes protect against much of the damage from the + explosion, but you still take some damage (and wound legs) + because they can't fully block the blast */ + if (wearing_iron_shoes(mtmp)) + damage = (damage + 3) / 4; + + if (mtmp == &gy.youmonst) { + boolean already_seen = trap->tseen; + boolean forcetrap = ((trflags & FORCETRAP) != 0 + || (trflags & FAILEDUNTRAP) != 0); + boolean forcebungle = (trflags & FORCEBUNGLE) != 0; unsigned steed_mid = 0; struct obj *saddle = 0; if ((Levitation || Flying) && !forcetrap) { if (!already_seen && rn2(3)) - break; + return Trap_Effect_Finished; feeltrap(trap); pline("%s %s in a pile of soil below you.", already_seen ? "There is" : "You discover", trap->madeby_u ? "the trigger of your mine" : "a trigger"); if (already_seen && rn2(3)) - break; + return Trap_Effect_Finished; + Soundeffect(se_kaablamm_of_mine, 80); pline("KAABLAMM!!! %s %s%s off!", forcebungle ? "Your inept attempt sets" - : "The air currents set", + : "The air currents set", already_seen ? a_your[trap->madeby_u] : "", already_seen ? " land mine" : "it"); } else { /* prevent landmine from killing steed, throwing you to - * the ground, and you being affected again by the same - * mine because it hasn't been deleted yet + * the ground, and then that same landmine affecting you + * again because it hasn't been deleted yet */ static boolean recursive_mine = FALSE; if (recursive_mine) - break; + return Trap_Effect_Finished; feeltrap(trap); pline("KAABLAMM!!! You triggered %s land mine!", a_your[trap->madeby_u]); @@ -1519,80 +2582,531 @@ unsigned trflags; set_wounded_legs(RIGHT_SIDE, rn1(35, 41)); exercise(A_DEX, FALSE); } + /* add a pit before calling losehp so bones won't keep the landmine; + blow_up_landmine() will remove pit afterwards if inappropriate */ + trap->ttyp = PIT; + trap->madeby_u = FALSE; + losehp(Maybe_Half_Phys(damage), "land mine", KILLED_BY_AN); blow_up_landmine(trap); if (steed_mid && saddle && !u.usteed) (void) keep_saddle_with_steedcorpse(steed_mid, fobj, saddle); newsym(u.ux, u.uy); /* update trap symbol */ - losehp(Maybe_Half_Phys(rnd(16)), "land mine", KILLED_BY_AN); /* fall recursively into the pit... */ if ((trap = t_at(u.ux, u.uy)) != 0) dotrap(trap, RECURSIVETRAP); fill_pit(u.ux, u.uy); - break; + } else { + boolean trapkilled = FALSE; + boolean in_sight = canseemon(mtmp) || (mtmp == u.usteed); + coordxy tx = trap->tx, ty = trap->ty; + + /* heavier monsters are more likely to set off a land mine; on the + other hand, any mon lighter than the trigger weight is immune */ +#define MINE_TRIGGER_WT (WT_ELF / 2U) + if (rn2(mtmp->data->cwt + 1) < (int) MINE_TRIGGER_WT) + return Trap_Effect_Finished; + if (m_in_air(mtmp)) { + boolean already_seen = trap->tseen; + + if (in_sight && !already_seen) { + pline_mon(mtmp, + "A trigger appears in a pile of soil below %s.", + mon_nam(mtmp)); + seetrap(trap); + } + if (rn2(3)) + return Trap_Effect_Finished; + if (in_sight) { + newsym(mtmp->mx, mtmp->my); + pline_The("air currents set %s off!", + already_seen ? "a land mine" : "it"); + } + } else if (in_sight) { + newsym(mtmp->mx, mtmp->my); + pline_mon(mtmp, + "%s%s triggers %s land mine!", + !Deaf ? "KAABLAMM!!! " : "", Monnam(mtmp), + a_your[trap->madeby_u]); + } + if (!in_sight && !Deaf) + pline("Kaablamm! %s an explosion in the distance!", + "You hear"); /* Deaf-aware */ + blow_up_landmine(trap); + /* explosion might have destroyed a drawbridge; don't + dish out more damage if monster is already dead */ + if (DEADMONSTER(mtmp) + || thitm(0, mtmp, (struct obj *) 0, damage, FALSE)) { + trapkilled = TRUE; + } else { + /* monsters recursively fall into new pit */ + if (mintrap(mtmp, trflags | FORCETRAP) == Trap_Killed_Mon) + trapkilled = TRUE; + } + /* a boulder may fill the new pit, crushing monster */ + fill_pit(tx, ty); /* thitm may have already destroyed the trap */ + if (DEADMONSTER(mtmp)) + trapkilled = TRUE; + if (unconscious()) { + gm.multi = -1; + gn.nomovemsg = "The explosion awakens you!"; + } + return trapkilled ? Trap_Killed_Mon : mtmp->mtrapped + ? Trap_Caught_Mon : Trap_Effect_Finished; } + return Trap_Effect_Finished; +} +#undef MINE_TRIGGER_WT - case ROLLING_BOULDER_TRAP: { +staticfn int +trapeffect_rolling_boulder_trap( + struct monst *mtmp, + struct trap *trap, + unsigned int trflags UNUSED) +{ + if (mtmp == &gy.youmonst) { int style = ROLL | (trap->tseen ? LAUNCH_KNOWN : 0); feeltrap(trap); - pline("Click! You trigger a rolling boulder trap!"); + pline("%sYou trigger a rolling boulder trap!", + !Deaf ? "Click! " : ""); if (!launch_obj(BOULDER, trap->launch.x, trap->launch.y, trap->launch2.x, trap->launch2.y, style)) { - deltrap(trap); - newsym(u.ux, u.uy); /* get rid of trap symbol */ - pline("Fortunately for you, no boulder was released."); + /* if this is a known trap, the player may have known there wasn't + a lined up boulder, so use a shorter message to avoid --More-- + spam */ + if (style & LAUNCH_KNOWN) + pline("No boulder was released."); + else + pline("Fortunately for you, no boulder was released."); + } + } else { + if (!m_in_air(mtmp)) { + boolean in_sight = (mtmp == u.usteed + || (cansee(mtmp->mx, mtmp->my) + && canspotmon(mtmp))); + int style = ROLL | (in_sight ? 0 : LAUNCH_UNSEEN); + boolean trapkilled = FALSE; + + newsym(mtmp->mx, mtmp->my); + if (in_sight) + pline_mon(mtmp, "%s%s triggers %s.", + !Deaf ? "Click! " : "", Monnam(mtmp), + trap->tseen ? "a rolling boulder trap" : something); + if (launch_obj(BOULDER, trap->launch.x, trap->launch.y, + trap->launch2.x, trap->launch2.y, style)) { + if (in_sight) + trap->tseen = TRUE; + if (DEADMONSTER(mtmp)) + trapkilled = TRUE; + } + return trapkilled ? Trap_Killed_Mon : mtmp->mtrapped + ? Trap_Caught_Mon : Trap_Effect_Finished; } - break; } + return Trap_Effect_Finished; +} - case MAGIC_PORTAL: +staticfn int +trapeffect_magic_portal( + struct monst *mtmp, + struct trap *trap, + unsigned int trflags) +{ + if (mtmp == &gy.youmonst) { feeltrap(trap); domagicportal(trap); - break; + } else { + return trapeffect_level_telep(mtmp, trap, trflags); + } + return Trap_Effect_Finished; +} - case VIBRATING_SQUARE: +staticfn int +trapeffect_vibrating_square( + struct monst *mtmp, + struct trap *trap, + unsigned int trflags UNUSED) +{ + if (mtmp == &gy.youmonst) { feeltrap(trap); /* messages handled elsewhere; the trap symbol is merely to mark the - * square for future reference */ + square for future reference */ + } else { + boolean in_sight = canseemon(mtmp) || (mtmp == u.usteed); + boolean see_it = cansee(mtmp->mx, mtmp->my); + + if (see_it && !Blind) { + seetrap(trap); /* before messages */ + if (in_sight) { + char buf[BUFSZ], *p, *monnm = mon_nam(mtmp); + + if (nolimbs(mtmp->data) || m_in_air(mtmp)) { + /* just "beneath " */ + Strcpy(buf, monnm); + } else { + Strcpy(buf, s_suffix(monnm)); + p = eos(strcat(buf, " ")); + Strcpy(p, makeplural(mbodypart(mtmp, FOOT))); + /* avoid "beneath 'rear paws'" or 'rear hooves' */ + (void) strsubst(p, "rear ", ""); + } + You_see("a strange vibration beneath %s.", buf); + } else { + /* notice something (hearing uses a larger threshold + for 'nearby') */ + You_see("the ground vibrate %s.", + (mdistu(mtmp) <= 2 * 2) + ? "nearby" : "in the distance"); + } + } + } + return Trap_Effect_Finished; +} + +/* + * for PR#259 - paranoid_confirm:trap + * + * Will a monster suffer any adverse effects from a certain trap? + * Note: does NOT mean "will a monster trigger a trap in the first place", + * though if it won't that does imply that they'll not suffer adverse effects. + * For example, an elf is considered immune to sleeping gas traps even though + * they'll set the trap off. + * Return value: + * TRAP_NOT_IMMUNE = not immune at the moment; + * TRAP_CLEARLY_IMMUNE = obviously immune (if player is polymorphed, assume + * they know which traps they are immune to in their current form); + * TRAP_HIDDEN_IMMUNE = immune but in non-obvious way such as an unidentified + * item or hidden intrinsic providing a resistance; the player should still + * be warned of this trap, while monsters implicitly know they're immune. + */ +int +immune_to_trap(struct monst *mon, unsigned ttype) +{ + struct permonst *pm; + struct obj *obj; + boolean is_you; + + if (!mon) { + impossible("immune_to_trap: null monster"); + return TRAP_NOT_IMMUNE; + } + pm = mon->data; + is_you = (mon == &gy.youmonst); + + switch (ttype) { + case ARROW_TRAP: + case DART_TRAP: + case ROCKTRAP: + /* can hit anything; even noncorporeal monsters might get a blessed + projectile */ + return TRAP_NOT_IMMUNE; + case BEAR_TRAP: + if (pm->msize <= MZ_SMALL + || amorphous(pm) || is_whirly(pm) || unsolid(pm)) + return TRAP_CLEARLY_IMMUNE; + FALLTHROUGH; + /*FALLTHRU*/ + case SQKY_BOARD: + case LANDMINE: + case ROLLING_BOULDER_TRAP: + case HOLE: + case TRAPDOOR: + case PIT: + case SPIKED_PIT: + /* ground-based traps, which can be evaded by levitation, flying, or + hanging to the ceiling */ + if (Sokoban && (is_pit(ttype) || is_hole(ttype))) + return TRAP_NOT_IMMUNE; + if (In_sokoban(&u.uz) && ttype == ROLLING_BOULDER_TRAP) + return TRAP_CLEARLY_IMMUNE; /* not dangerous in Sokoban */ + if (is_floater(pm) || is_flyer(pm) + || (is_clinger(pm) && has_ceiling(&u.uz))) + return TRAP_CLEARLY_IMMUNE; + else if (is_you && (Levitation || Flying)) + return TRAP_CLEARLY_IMMUNE; + return TRAP_NOT_IMMUNE; + case SLP_GAS_TRAP: + if (breathless(pm)) + return TRAP_CLEARLY_IMMUNE; + else if (!is_you && resists_sleep(mon)) + return TRAP_CLEARLY_IMMUNE; + else if (is_you && Sleep_resistance) + return TRAP_HIDDEN_IMMUNE; + return TRAP_NOT_IMMUNE; + case LEVEL_TELEP: + case TELEP_TRAP: + /* consider unintended teleporting to be an adverse effect; if in + the endgame or carrying the Amulet, the teleport trap won't work + anyway, so anything hitting it is immune. */ + if (In_endgame(&u.uz) || mon_has_amulet(mon)) + return TRAP_CLEARLY_IMMUNE; + return TRAP_NOT_IMMUNE; + case POLY_TRAP: + if (resists_magm(mon)) + /* covers Antimagic for player */ + return (is_you ? TRAP_HIDDEN_IMMUNE : TRAP_CLEARLY_IMMUNE); + return TRAP_NOT_IMMUNE; + case STATUE_TRAP: + /* no effect on monsters, only affects players; only trap detection + can let player know that this is a statue trap there ahead of time; + in the rare case this happens, do consider it an adverse effect */ + if (!is_you) + return TRAP_CLEARLY_IMMUNE; + return TRAP_NOT_IMMUNE; + case WEB: + /* most of this code is lifted from mu_maybe_destroy_web */ + if (webmaker(pm) || amorphous(pm) || is_whirly(pm) || flaming(pm) + || unsolid(pm) || pm == &mons[PM_GELATINOUS_CUBE]) + return TRAP_CLEARLY_IMMUNE; + return TRAP_NOT_IMMUNE; + case ANTI_MAGIC: + /* doesn't hurt any non-magic-resistant monster with no magic */ + if (is_you) { + if (Antimagic) + return TRAP_NOT_IMMUNE; + else if (u.uenmax == 0) + /* player won't lose HP and can't lose more Pw */ + return TRAP_HIDDEN_IMMUNE; + + /* following conditional lifted from mintrap ANTI_MAGIC logic */ + } else if (!resists_magm(mon) + && (mon->mcan || (!attacktype(pm, AT_MAGC) + && !attacktype(pm, AT_BREA)))) { + return TRAP_CLEARLY_IMMUNE; + } + return TRAP_NOT_IMMUNE; + case RUST_TRAP: + /* harmful if wearing anything rustable or if mon is an iron golem */ + if (pm == &mons[PM_IRON_GOLEM]) + return TRAP_NOT_IMMUNE; + + for (obj = is_you ? gi.invent : mon->minvent; obj; obj = obj->nobj) { + /* rust traps can currently hit only worn armor and weapons */ + if (is_rustprone(obj) && obj->owornmask) { + if (is_you && (obj == uquiver + || (obj == uswapwep && !u.twoweap))) + continue; + return TRAP_NOT_IMMUNE; + } + } + return TRAP_CLEARLY_IMMUNE; + case MAGIC_TRAP: + /* for player, any number of bad effects; + for monsters, only replicates fire trap, so fall through */ + if (is_you) + return TRAP_NOT_IMMUNE; + FALLTHROUGH; + /*FALLTHRU*/ + case FIRE_TRAP: /* can always destroy items being carried */ + /* harmful if not resistant or if carrying anything that could burn */ + if (is_you ? !Fire_resistance : !resists_fire(mon)) + return TRAP_NOT_IMMUNE; + + for (obj = is_you ? gi.invent : mon->minvent; obj; obj = obj->nobj) { + if (obj->oclass == SCROLL_CLASS || obj->oclass == POTION_CLASS + || obj->oclass == SPBOOK_CLASS + || (obj->owornmask && is_flammable(obj))) { + if ((obj->otyp == SCR_FIRE || obj->otyp == SPE_FIREBALL) + /* mon knows scroll of fire or spellbook of fireball + won't be affected; hero knows iff this one has been + seen and its type has been discovered */ + && (!is_you + || (obj->dknown && objects[obj->otyp].oc_name_known))) + continue; + return TRAP_NOT_IMMUNE; + } + } + return (is_you ? TRAP_HIDDEN_IMMUNE : TRAP_CLEARLY_IMMUNE); + case MAGIC_PORTAL: + /* never hurts anything, but player is considered non-immune so they + can be asked about entering it */ + if (!is_you) + return TRAP_CLEARLY_IMMUNE; + return TRAP_NOT_IMMUNE; + case VIBRATING_SQUARE: + /* no adverse effects */ + return TRAP_CLEARLY_IMMUNE; + default: + impossible("immune_to_trap: bad ttype %u", ttype); break; + } + return TRAP_NOT_IMMUNE; +} +staticfn int +trapeffect_selector( + struct monst *mtmp, + struct trap *trap, + unsigned int trflags) +{ + switch (trap->ttyp) { + case ARROW_TRAP: + return trapeffect_arrow_trap(mtmp, trap, trflags); + case DART_TRAP: + return trapeffect_dart_trap(mtmp, trap, trflags); + case ROCKTRAP: + return trapeffect_rocktrap(mtmp, trap, trflags); + case SQKY_BOARD: + return trapeffect_sqky_board(mtmp, trap, trflags); + case BEAR_TRAP: + return trapeffect_bear_trap(mtmp, trap, trflags); + case SLP_GAS_TRAP: + return trapeffect_slp_gas_trap(mtmp, trap, trflags); + case RUST_TRAP: + return trapeffect_rust_trap(mtmp, trap, trflags); + case FIRE_TRAP: + return trapeffect_fire_trap(mtmp, trap, trflags); + case PIT: + case SPIKED_PIT: + return trapeffect_pit(mtmp, trap, trflags); + case HOLE: + case TRAPDOOR: + return trapeffect_hole(mtmp, trap, trflags); + case LEVEL_TELEP: + return trapeffect_level_telep(mtmp, trap, trflags); + case MAGIC_PORTAL: + return trapeffect_magic_portal(mtmp, trap, trflags); + case TELEP_TRAP: + return trapeffect_telep_trap(mtmp, trap, trflags); + case WEB: + return trapeffect_web(mtmp, trap, trflags); + case STATUE_TRAP: + return trapeffect_statue_trap(mtmp, trap, trflags); + case MAGIC_TRAP: + return trapeffect_magic_trap(mtmp, trap, trflags); + case ANTI_MAGIC: + return trapeffect_anti_magic(mtmp, trap, trflags); + case LANDMINE: + return trapeffect_landmine(mtmp, trap, trflags); + case POLY_TRAP: + return trapeffect_poly_trap(mtmp, trap, trflags); + case ROLLING_BOULDER_TRAP: + return trapeffect_rolling_boulder_trap(mtmp, trap, trflags); + case VIBRATING_SQUARE: + return trapeffect_vibrating_square(mtmp, trap, trflags); default: - feeltrap(trap); - impossible("You hit a trap of type %u", trap->ttyp); + impossible("%s encountered a strange trap of type %d.", + (mtmp == &gy.youmonst) ? "You" : "Some monster", + trap->ttyp); + } + return Trap_Effect_Finished; +} + +void +dotrap(struct trap *trap, unsigned trflags) +{ + int ttype = trap->ttyp; + boolean already_seen = trap->tseen, + forcetrap = ((trflags & FORCETRAP) != 0 + || (trflags & FAILEDUNTRAP) != 0), + forcebungle = (trflags & FORCEBUNGLE) != 0, + plunged = (trflags & TOOKPLUNGE) != 0, + conj_pit = conjoined_pits(trap, t_at(u.ux0, u.uy0), TRUE), + adj_pit = adj_nonconjoined_pit(trap); + + nomul(0); + + if (fixed_tele_trap(trap)) { + trflags |= FORCETRAP; + forcetrap = TRUE; + } + + /* KMH -- You can't escape the Sokoban level traps */ + if (Sokoban && (is_pit(ttype) || is_hole(ttype))) { + /* The "air currents" message is still appropriate -- even when + * the hero isn't flying or levitating -- because it conveys the + * reason why the player cannot escape the trap with a dexterity + * check, clinging to the ceiling, etc. + */ + pline("Air currents pull you down into %s %s!", + a_your[trap->madeby_u], + trapname(ttype, TRUE)); /* do force "pit" while hallucinating */ + /* then proceed to normal trap effect */ + } else if (!forcetrap) { + if (floor_trigger(ttype) && check_in_air(&gy.youmonst, trflags)) { + if (already_seen) { + You("%s over %s %s.", u_locomotion("step"), + (ttype == ARROW_TRAP && !trap->madeby_u) + ? "an" : a_your[trap->madeby_u], + trapname(ttype, FALSE)); + } + return; + } + if (already_seen && !Fumbling && !undestroyable_trap(ttype) + && ttype != ANTI_MAGIC && !forcebungle && !plunged + && !conj_pit && !adj_pit + && (!rn2(5) || (is_pit(ttype) && is_clinger(gy.youmonst.data)))) { + You("escape %s %s.", (ttype == ARROW_TRAP && !trap->madeby_u) + ? "an" + : a_your[trap->madeby_u], + trapname(ttype, FALSE)); + return; + } } + + if (u.usteed) + mon_learns_traps(u.usteed, ttype); + mons_see_trap(trap); + + /* + * Note: + * Most references to trap types here don't use trapname() for + * hallucination. This could be considered to be a bug but doing + * that would hide the actual trap situation from the player which + * would be somewhat harsh for what's usually a minor impairment. + */ + + (void) trapeffect_selector(&gy.youmonst, trap, trflags); } -STATIC_OVL char * -trapnote(trap, noprefix) -struct trap *trap; -boolean noprefix; +staticfn char * +trapnote(struct trap *trap, boolean noprefix) { - static char tnbuf[12]; - const char *tn, - *tnnames[12] = { "C note", "D flat", "D note", "E flat", - "E note", "F note", "F sharp", "G note", - "G sharp", "A note", "B flat", "B note" }; + static const char *const tnnames[] = { + "C note", "D flat", "D note", "E flat", + "E note", "F note", "F sharp", "G note", + "G sharp", "A note", "B flat", "B note", + }; + static char tnbuf[12]; /* result buffer */ + const char *tn; tnbuf[0] = '\0'; tn = tnnames[trap->tnote]; if (!noprefix) - Sprintf(tnbuf, "%s ", - (*tn == 'A' || *tn == 'E' || *tn == 'F') ? "an" : "a"); - Sprintf(eos(tnbuf), "%s", tn); - return tnbuf; + (void) just_an(tnbuf, tn); + return strcat(tnbuf, tn); } -STATIC_OVL int -steedintrap(trap, otmp) -struct trap *trap; -struct obj *otmp; +/* choose a note not used by any trap on current level, + ignoring ttmp; if all are in use, pick a random one */ +staticfn int +choose_trapnote(struct trap *ttmp) +{ + int tavail[12], tpick[12], tcnt = 0, k; + struct trap *t; + + for (k = 0; k < 12; ++k) + tavail[k] = tpick[k] = 0; + for (t = gf.ftrap; t; t = t->ntrap) + if (t->ttyp == SQKY_BOARD && t != ttmp) + tavail[t->tnote] = 1; + /* now populate tpick[] with the available indices */ + for (k = 0; k < 12; ++k) + if (tavail[k] == 0) + tpick[tcnt++] = k; + /* choose an unused note; if all are in use, pick a random one */ + return ((tcnt > 0) ? tpick[rn2(tcnt)] : rn2(12)); +} + +staticfn int +steedintrap(struct trap *trap, struct obj *otmp) { struct monst *steed = u.usteed; int tt; boolean trapkilled, steedhit; if (!steed || !trap) - return 0; + return Trap_Effect_Finished; tt = trap->ttyp; steed->mx = u.ux; steed->my = u.uy; @@ -1602,7 +3116,7 @@ struct obj *otmp; case ARROW_TRAP: if (!otmp) { impossible("steed hit by non-existent arrow?"); - return 0; + return Trap_Effect_Finished; } trapkilled = thitm(8, steed, otmp, 0, FALSE); steedhit = TRUE; @@ -1610,14 +3124,14 @@ struct obj *otmp; case DART_TRAP: if (!otmp) { impossible("steed hit by non-existent dart?"); - return 0; + return Trap_Effect_Finished; } trapkilled = thitm(7, steed, otmp, 0, FALSE); steedhit = TRUE; break; case SLP_GAS_TRAP: if (!resists_sleep(steed) && !breathless(steed->data) - && !steed->msleeping && steed->mcanmove) { + && !helpless(steed)) { if (sleep_monst(steed, rnd(25), -1)) /* no in_sight check here; you can feel it even if blind */ pline("%s suddenly falls asleep!", Monnam(steed)); @@ -1637,20 +3151,8 @@ struct obj *otmp; break; case POLY_TRAP: if (!resists_magm(steed) && !resist(steed, WAND_CLASS, 0, NOTELL)) { - struct permonst *mdat = steed->data; - - (void) newcham(steed, (struct permonst *) 0, FALSE, FALSE); - if (!can_saddle(steed) || !can_ride(steed)) { - dismount_steed(DISMOUNT_POLY); - } else { - char buf[BUFSZ]; - - Strcpy(buf, x_monnam(steed, ARTICLE_YOUR, (char *) 0, - SUPPRESS_SADDLE, FALSE)); - if (mdat != steed->data) - (void) strsubst(buf, "your ", "your new "); - You("adjust yourself in the saddle on %s.", buf); - } + /* newcham() will probably end up calling poly_steed() */ + (void) newcham(steed, (struct permonst *) 0, NC_SHOW_MSG); } steedhit = TRUE; break; @@ -1660,19 +3162,20 @@ struct obj *otmp; if (trapkilled) { dismount_steed(DISMOUNT_POLY); - return 2; + return Trap_Killed_Mon; } return steedhit ? 1 : 0; } /* some actions common to both player and monsters for triggered landmine */ void -blow_up_landmine(trap) -struct trap *trap; +blow_up_landmine(struct trap *trap) { - int x = trap->tx, y = trap->ty, dbx, dby; + coordxy x = trap->tx, y = trap->ty, dbx, dby; struct rm *lev = &levl[x][y]; + schar old_typ, typ; + old_typ = lev->typ; (void) scatter(x, y, 4, MAY_DESTROY | MAY_HIT | MAY_FRACTURE | VIS_EFFECTS, (struct obj *) 0); @@ -1686,61 +3189,63 @@ struct trap *trap; /* if under the portcullis, the bridge is adjacent */ if (find_drawbridge(&dbx, &dby)) destroy_drawbridge(dbx, dby); - trap = t_at(x, y); /* expected to be null after destruction */ } + trap = t_at(x, y); /* expected to be null after destruction */ + /* or could be null if scatter blew up oil which melted ice */ /* convert landmine into pit */ if (trap) { if (Is_waterlevel(&u.uz) || Is_airlevel(&u.uz)) { /* no pits here */ deltrap(trap); } else { - trap->ttyp = PIT; /* explosion creates a pit */ - trap->madeby_u = FALSE; /* resulting pit isn't yours */ - seetrap(trap); /* and it isn't concealed */ + /* fill pit with water, if applicable */ + typ = fillholetyp(x, y, FALSE); + if (typ != ROOM) { + lev->typ = typ; + liquid_flow(x, y, typ, trap, + cansee(x, y) ? "The hole fills with %s!" + : (char *) 0); + } else { + trap->ttyp = PIT; /* explosion creates a pit */ + trap->madeby_u = FALSE; /* resulting pit isn't yours */ + seetrap(trap); /* and it isn't concealed */ + } } } + fill_pit(x, y); + maybe_dunk_boulders(x, y); + recalc_block_point(x, y); + spot_checks(x, y, old_typ); } -/* - * The following are used to track launched objects to - * prevent them from vanishing if you are killed. They - * will reappear at the launchplace in bones files. - */ -static struct { - struct obj *obj; - xchar x, y; -} launchplace; - -STATIC_OVL void -launch_drop_spot(obj, x, y) -struct obj *obj; -xchar x, y; +staticfn void +launch_drop_spot(struct obj *obj, coordxy x, coordxy y) { if (!obj) { - launchplace.obj = (struct obj *) 0; - launchplace.x = 0; - launchplace.y = 0; + gl.launchplace.obj = (struct obj *) 0; + gl.launchplace.x = 0; + gl.launchplace.y = 0; } else { - launchplace.obj = obj; - launchplace.x = x; - launchplace.y = y; + gl.launchplace.obj = obj; + gl.launchplace.x = x; + gl.launchplace.y = y; } } boolean -launch_in_progress() +launch_in_progress(void) { - if (launchplace.obj) + if (gl.launchplace.obj) return TRUE; return FALSE; } void -force_launch_placement() +force_launch_placement(void) { - if (launchplace.obj) { - launchplace.obj->otrapped = 0; - place_object(launchplace.obj, launchplace.x, launchplace.y); + if (gl.launchplace.obj) { + gl.launchplace.obj->otrapped = 0; + place_object(gl.launchplace.obj, gl.launchplace.x, gl.launchplace.y); } } @@ -1752,20 +3257,19 @@ force_launch_placement() * 2 if an object was launched, but used up. */ int -launch_obj(otyp, x1, y1, x2, y2, style) -short otyp; -register int x1, y1, x2, y2; -int style; +launch_obj( + short otyp, + coordxy x1, coordxy y1, + coordxy x2, coordxy y2, + int style) { - register struct monst *mtmp; - register struct obj *otmp, *otmp2; - register int dx, dy; + struct monst *mtmp; + struct obj *otmp, *otmp2; + int dx, dy; + coordxy x, y; struct obj *singleobj; - boolean used_up = FALSE; - boolean otherside = FALSE; - int dist; - int tmp; - int delaycnt = 0; + boolean used_up = FALSE, otherside = FALSE; + int dist, tmp, delaycnt = 0; otmp = sobj_at(otyp, x1, y1); /* Try the other side too, for rolling boulder traps */ @@ -1788,6 +3292,7 @@ int style; if (otmp->quan == 1L) { obj_extract_self(otmp); + maybe_unhide_at(otmp->ox, otmp->oy); singleobj = otmp; otmp = (struct obj *) 0; } else { @@ -1797,23 +3302,32 @@ int style; newsym(x1, y1); /* in case you're using a pick-axe to chop the boulder that's being launched (perhaps a monster triggered it), destroy context so that - next dig attempt never thinks you're resuming previous effort */ + the next dig attempt never thinks that you're resuming + the previous effort */ if ((otyp == BOULDER || otyp == STATUE) - && singleobj->ox == context.digging.pos.x - && singleobj->oy == context.digging.pos.y) - (void) memset((genericptr_t) &context.digging, 0, + && singleobj->ox == svc.context.digging.pos.x + && singleobj->oy == svc.context.digging.pos.y) + (void) memset((genericptr_t) &svc.context.digging, 0, sizeof(struct dig_info)); dist = distmin(x1, y1, x2, y2); - bhitpos.x = x1; - bhitpos.y = y1; + x = gb.bhitpos.x = x1; + y = gb.bhitpos.y = y1; dx = sgn(x2 - x1); dy = sgn(y2 - y1); switch (style) { case ROLL | LAUNCH_UNSEEN: if (otyp == BOULDER) { - You_hear(Hallucination ? "someone bowling." - : "rumbling in the distance."); + if (cansee(x1, y1)) { + You_see("%s start to roll.", an(xname(singleobj))); + } else if (Hallucination) { + Soundeffect(se_someone_bowling, 60); + You_hear("someone bowling."); + } else { + Soundeffect(se_rumbling, 60); + You_hear("rumbling %s.", (distu(x1, y1) <= 4 * 4) ? "nearby" + : "in the distance"); + } } style &= ~LAUNCH_UNSEEN; goto roll; @@ -1821,18 +3335,20 @@ int style; /* use otrapped as a flag to ohitmon */ singleobj->otrapped = 1; style &= ~LAUNCH_KNOWN; - /* fall through */ - roll: + FALLTHROUGH; + /*FALLTHRU*/ case ROLL: + roll: delaycnt = 2; - /* fall through */ + FALLTHROUGH; + /*FALLTHRU*/ default: if (!delaycnt) delaycnt = 1; - if (!cansee(bhitpos.x, bhitpos.y)) + if (!cansee(x, y)) curs_on_u(); tmp_at(DISP_FLASH, obj_to_glyph(singleobj, rn2_on_display_rng)); - tmp_at(bhitpos.x, bhitpos.y); + tmp_at(x, y); } /* Mark a spot to place object in bones files to prevent * loss of object. Use the starting spot to ensure that @@ -1842,35 +3358,46 @@ int style; * that would prevent it from ever getting there (bars), and we * can't tell that yet. */ - launch_drop_spot(singleobj, bhitpos.x, bhitpos.y); + launch_drop_spot(singleobj, x, y); /* Set the object in motion */ while (dist-- > 0 && !used_up) { struct trap *t; - tmp_at(bhitpos.x, bhitpos.y); + + tmp_at(x, y); tmp = delaycnt; /* dstage@u.washington.edu -- Delay only if hero sees it */ - if (cansee(bhitpos.x, bhitpos.y)) + if (cansee(x, y)) while (tmp-- > 0) - delay_output(); + nh_delay_output(); - bhitpos.x += dx; - bhitpos.y += dy; - - /* check if the obj is not out of bounds */ - if (!isok(bhitpos.x, bhitpos.y)) { - bhitpos.x -= dx; - bhitpos.y -= dy; - x2 = bhitpos.x, y2 = bhitpos.y; /* object stops here */ - break; + /* + * TEMPORARY? github issue #1490 by BartekCupial reports a + * segfault when boulder rolls out of bounds. That should be + * impossible because trap creation validates the path that + * the boulder will traverse. + * + * The suggested fix increments bhitpos, verifies with isok(), + * then undoes the increment if not ok. This is simpler. + */ + if (!isok(gb.bhitpos.x + dx, gb.bhitpos.y + dy)) { + x2 = x, y2 = y; /* use current spot for final boulder placement */ + break; } + /* + * end TEMPORARY? + */ + + x = (gb.bhitpos.x += dx); + y = (gb.bhitpos.y += dy); - if ((mtmp = m_at(bhitpos.x, bhitpos.y)) != 0) { + if ((mtmp = m_at(x, y)) != 0) { if (otyp == BOULDER && throws_rocks(mtmp->data)) { if (rn2(3)) { - if (cansee(bhitpos.x, bhitpos.y)) - pline("%s snatches the boulder.", Monnam(mtmp)); + if (cansee(x, y)) + pline_mon(mtmp, "%s snatches the boulder.", + Monnam(mtmp)); singleobj->otrapped = 0; (void) mpickobj(mtmp, singleobj); used_up = TRUE; @@ -1884,60 +3411,71 @@ int style; launch_drop_spot((struct obj *) 0, 0, 0); break; } - } else if (bhitpos.x == u.ux && bhitpos.y == u.uy) { - if (multi) + } else if (u_at(x, y)) { + int dam = dmgval(singleobj, &gy.youmonst); + + if (gm.multi) nomul(0); - if (thitu(9 + singleobj->spe, dmgval(singleobj, &youmonst), + if (thitu(9 + singleobj->spe, Maybe_Half_Phys(dam), &singleobj, (char *) 0)) stop_occupation(); } if (style == ROLL) { - if (down_gate(bhitpos.x, bhitpos.y) != -1) { - if (ship_object(singleobj, bhitpos.x, bhitpos.y, FALSE)) { + if (down_gate(x, y) != -1) { + if (ship_object(singleobj, x, y, FALSE)) { used_up = TRUE; launch_drop_spot((struct obj *) 0, 0, 0); break; } } - if ((t = t_at(bhitpos.x, bhitpos.y)) != 0 && otyp == BOULDER) { + if ((t = t_at(x, y)) != 0 + && otyp == BOULDER) { + int newlev = 0; + d_level dest; + switch (t->ttyp) { case LANDMINE: if (rn2(10) > 2) { - pline( - "KAABLAMM!!!%s", - cansee(bhitpos.x, bhitpos.y) - ? " The rolling boulder triggers a land mine." - : ""); + if (cansee(x, y)) + set_msg_xy(x, y); + pline("KAABLAMM!!!%s", + cansee(x, y) + ? " The rolling boulder triggers a land mine." + : ""); deltrap(t); - del_engr_at(bhitpos.x, bhitpos.y); - place_object(singleobj, bhitpos.x, bhitpos.y); + del_engr_at(x, y); + place_object(singleobj, x, y); singleobj->otrapped = 0; fracture_rock(singleobj); - (void) scatter(bhitpos.x, bhitpos.y, 4, + (void) scatter(x, y, 4, MAY_DESTROY | MAY_HIT | MAY_FRACTURE | VIS_EFFECTS, (struct obj *) 0); - if (cansee(bhitpos.x, bhitpos.y)) - newsym(bhitpos.x, bhitpos.y); + if (cansee(x, y)) + newsym(x, y); used_up = TRUE; launch_drop_spot((struct obj *) 0, 0, 0); } break; case LEVEL_TELEP: + /* 20% chance of picking current level; 100% chance for + that if in single-level branch (Knox) or in endgame */ + newlev = random_teleport_level(); + /* if trap doesn't work, skip "disappears" message */ + if (newlev == depth(&u.uz)) + break; + FALLTHROUGH; + /*FALLTHRU*/ case TELEP_TRAP: - if (cansee(bhitpos.x, bhitpos.y)) - pline("Suddenly the rolling boulder disappears!"); - else + if (cansee(x, y)) + pline_xy(x, y, + "Suddenly the rolling boulder disappears!"); + else if (!Deaf) You_hear("a rumbling stop abruptly."); singleobj->otrapped = 0; - if (t->ttyp == TELEP_TRAP) + if (t->ttyp == TELEP_TRAP) { (void) rloco(singleobj); - else { - int newlev = random_teleport_level(); - d_level dest; - - if (newlev == depth(&u.uz) || In_endgame(&u.uz)) - continue; + } else { add_to_migration(singleobj); get_level(&dest, newlev); singleobj->ox = dest.dnum; @@ -1954,65 +3492,77 @@ int style; case TRAPDOOR: /* the boulder won't be used up if there is a monster in the trap; stop rolling anyway */ - x2 = bhitpos.x, y2 = bhitpos.y; /* stops here */ + x2 = x, y2 = y; /* stops here */ if (flooreffects(singleobj, x2, y2, "fall")) { used_up = TRUE; launch_drop_spot((struct obj *) 0, 0, 0); } dist = -1; /* stop rolling immediately */ break; + default: + break; } + if (used_up || dist == -1) - break; + break; /* from 'while' loop */ } - if (flooreffects(singleobj, bhitpos.x, bhitpos.y, "fall")) { + if (flooreffects(singleobj, x, y, "fall")) { used_up = TRUE; launch_drop_spot((struct obj *) 0, 0, 0); break; } - if (otyp == BOULDER - && (otmp2 = sobj_at(BOULDER, bhitpos.x, bhitpos.y)) != 0) { + if (otyp == BOULDER && (otmp2 = sobj_at(BOULDER, x, y)) != 0) { const char *bmsg = " as one boulder sets another in motion"; + coordxy fx = x + dx, fy = y + dy; - if (!isok(bhitpos.x + dx, bhitpos.y + dy) || !dist - || IS_ROCK(levl[bhitpos.x + dx][bhitpos.y + dy].typ)) + if (!isok(fx, fy) || !dist || IS_OBSTRUCTED(levl[fx][fy].typ)) bmsg = " as one boulder hits another"; - You_hear("a loud crash%s!", - cansee(bhitpos.x, bhitpos.y) ? bmsg : ""); + Soundeffect(se_loud_crash, 80); + You_hear("a loud crash%s!", cansee(x, y) ? bmsg : ""); obj_extract_self(otmp2); /* pass off the otrapped flag to the next boulder */ otmp2->otrapped = singleobj->otrapped; singleobj->otrapped = 0; - place_object(singleobj, bhitpos.x, bhitpos.y); + place_object(singleobj, x, y); singleobj = otmp2; otmp2 = (struct obj *) 0; - wake_nearto(bhitpos.x, bhitpos.y, 10 * 10); + wake_nearto(x, y, 10 * 10); } } - if (otyp == BOULDER && closed_door(bhitpos.x, bhitpos.y)) { - if (cansee(bhitpos.x, bhitpos.y)) + if (otyp == BOULDER && closed_door(x, y)) { + if (cansee(x, y)) { + set_msg_xy(x, y); pline_The("boulder crashes through a door."); - levl[bhitpos.x][bhitpos.y].doormask = D_BROKEN; + } + levl[x][y].doormask = D_BROKEN; if (dist) - unblock_point(bhitpos.x, bhitpos.y); + recalc_block_point(x, y); } - /* if about to hit iron bars, do so now */ - if (dist > 0 && isok(bhitpos.x + dx, bhitpos.y + dy) - && levl[bhitpos.x + dx][bhitpos.y + dy].typ == IRONBARS) { - x2 = bhitpos.x, y2 = bhitpos.y; /* object stops here */ - if (hits_bars(&singleobj, - x2, y2, x2+dx, y2+dy, - !rn2(20), 0)) { - if (!singleobj) { - used_up = TRUE; - launch_drop_spot((struct obj *) 0, 0, 0); + /* if about to hit something, do so now */ + if (dist > 0 && isok(x + dx, y + dy)) { + coordxy fx = x + dx, fy = y + dy; + uchar typ = levl[fx][fy].typ; + + if (typ == IRONBARS) { + x2 = x, y2 = y; /* object stops here */ + if (hits_bars(&singleobj, x2, y2, fx, fy, !rn2(20), 0)) { + if (!singleobj) { + used_up = TRUE; + launch_drop_spot((struct obj *) 0, 0, 0); + } + break; } + } else if (IS_STWALL(typ) || IS_TREE(typ)) { + x2 = x, y2 = y; /* object stops here */ + if (!Deaf) + pline("Thump!"); + wake_nearto(x2, y2, 16); break; } } - } + } /* while dist > 0 */ tmp_at(DISP_END, 0); launch_drop_spot((struct obj *) 0, 0, 0); if (!used_up) { @@ -2020,13 +3570,12 @@ int style; place_object(singleobj, x2, y2); newsym(x2, y2); return 1; - } else - return 2; + } + return 2; } void -seetrap(trap) -struct trap *trap; +seetrap(struct trap *trap) { if (!trap->tseen) { trap->tseen = 1; @@ -2036,8 +3585,7 @@ struct trap *trap; /* like seetrap() but overrides vision */ void -feeltrap(trap) -struct trap *trap; +feeltrap(struct trap *trap) { trap->tseen = 1; map_trap(trap, 1); @@ -2045,38 +3593,49 @@ struct trap *trap; newsym(trap->tx, trap->ty); } -STATIC_OVL int -mkroll_launch(ttmp, x, y, otyp, ocount) -struct trap *ttmp; -xchar x, y; -short otyp; -long ocount; +/* try to find a random coordinate where launching a rolling boulder + could work. return TRUE if found, with coordinate in cc. */ +staticfn boolean +find_random_launch_coord(struct trap *ttmp, coord *cc) { - struct obj *otmp; - register int tmp; - schar dx, dy; - int distance; - coord cc; - coord bcc; - int trycount = 0; + int tmp; boolean success = FALSE; + coord bcc = UNDEFINED_VALUES; + int distance; int mindist = 4; + int trycount = 0; + coordxy dx, dy; + coordxy x, y; + + if (!ttmp || !cc || Sokoban) + return FALSE; + + x = ttmp->tx; + y = ttmp->ty; + + bcc.x = ttmp->tx + gl.launchplace.x; + bcc.y = ttmp->ty + gl.launchplace.y; + if (isok(bcc.x, bcc.y) && linedup(ttmp->tx, ttmp->ty, bcc.x, bcc.y, 1)) { + cc->x = bcc.x; + cc->y = bcc.y; + return TRUE; + } if (ttmp->ttyp == ROLLING_BOULDER_TRAP) mindist = 2; distance = rn1(5, 4); /* 4..8 away */ - tmp = rn2(8); /* randomly pick a direction to try first */ + tmp = rn2(N_DIRS); /* randomly pick a direction to try first */ while (distance >= mindist) { dx = xdir[tmp]; dy = ydir[tmp]; - cc.x = x; - cc.y = y; + cc->x = x; + cc->y = y; /* Prevent boulder from being placed on water */ if (ttmp->ttyp == ROLLING_BOULDER_TRAP && is_pool_or_lava(x + distance * dx, y + distance * dy)) success = FALSE; else - success = isclearpath(&cc, distance, dx, dy); + success = isclearpath(cc, distance, dx, dy); if (ttmp->ttyp == ROLLING_BOULDER_TRAP) { boolean success_otherway; @@ -2093,10 +3652,27 @@ long ocount; if ((++trycount % 8) == 0) --distance; } + return success; +} + +staticfn int +mkroll_launch( + struct trap *ttmp, + coordxy x, + coordxy y, + short otyp, + long ocount) +{ + struct obj *otmp; + coord cc = UNDEFINED_VALUES; + boolean success = FALSE; + + success = find_random_launch_coord(ttmp, &cc); + if (!success) { /* create the trap without any ammo, launch pt at trap location */ - cc.x = bcc.x = x; - cc.y = bcc.y = y; + cc.x = x; + cc.y = y; } else { otmp = mksobj(otyp, TRUE, FALSE); otmp->quan = ocount; @@ -2107,30 +3683,37 @@ long ocount; ttmp->launch.x = cc.x; ttmp->launch.y = cc.y; if (ttmp->ttyp == ROLLING_BOULDER_TRAP) { - ttmp->launch2.x = bcc.x; - ttmp->launch2.y = bcc.y; + ttmp->launch2.x = x - (cc.x - x); + ttmp->launch2.y = y - (cc.y - y); } else ttmp->launch_otyp = otyp; newsym(ttmp->launch.x, ttmp->launch.y); return 1; } -STATIC_OVL boolean -isclearpath(cc, distance, dx, dy) -coord *cc; -int distance; -schar dx, dy; +staticfn boolean +isclearpath( + coord *cc, + int distance, + schar dx, + schar dy) { + struct trap *t; uchar typ; - xchar x, y; + coordxy x, y; x = cc->x; y = cc->y; while (distance-- > 0) { x += dx; y += dy; + if (!isok(x, y)) + return FALSE; typ = levl[x][y].typ; - if (!isok(x, y) || !ZAP_POS(typ) || closed_door(x, y)) + if (!ZAP_POS(typ) || closed_door(x, y)) + return FALSE; + if ((t = t_at(x, y)) != 0 + && (is_pit(t->ttyp) || is_hole(t->ttyp) || is_xport(t->ttyp))) return FALSE; } cc->x = x; @@ -2138,14 +3721,20 @@ schar dx, dy; return TRUE; } +/* can monster escape from a pit easily */ +staticfn boolean +m_easy_escape_pit(struct monst *mtmp) +{ + return (mtmp->data == &mons[PM_PIT_FIEND] + || mtmp->data->msize >= MZ_HUGE); +} + int -mintrap(mtmp) -register struct monst *mtmp; +mintrap(struct monst *mtmp, unsigned mintrapflags) { - register struct trap *trap = t_at(mtmp->mx, mtmp->my); - boolean trapkilled = FALSE; + struct trap *trap = t_at(mtmp->mx, mtmp->my); struct permonst *mptr = mtmp->data; - struct obj *otmp; + int trap_result = Trap_Effect_Finished; if (!trap) { mtmp->mtrapped = 0; /* perhaps teleported? */ @@ -2155,623 +3744,118 @@ register struct monst *mtmp; || trap->ttyp == HOLE || trap->ttyp == WEB)) { /* If you come upon an obviously trapped monster, then - * you must be able to see the trap it's in too. - */ + you must be able to see the trap it's in too. */ seetrap(trap); } - if (!rn2(40)) { + if (!rn2(40) || (is_pit(trap->ttyp) && m_easy_escape_pit(mtmp))) { if (sobj_at(BOULDER, mtmp->mx, mtmp->my) && is_pit(trap->ttyp)) { if (!rn2(2)) { mtmp->mtrapped = 0; if (canseemon(mtmp)) - pline("%s pulls free...", Monnam(mtmp)); + pline_mon(mtmp, "%s pulls free...", + Monnam(mtmp)); fill_pit(mtmp->mx, mtmp->my); } } else { + if (canseemon(mtmp)) { + set_msg_xy(mtmp->mx, mtmp->my); + if (is_pit(trap->ttyp)) + pline("%s climbs %sout of the pit.", Monnam(mtmp), + m_easy_escape_pit(mtmp) ? "easily " : ""); + else if (trap->ttyp == BEAR_TRAP || trap->ttyp == WEB) + pline("%s pulls free of the %s.", Monnam(mtmp), + trapname(trap->ttyp, FALSE)); + } mtmp->mtrapped = 0; } } else if (metallivorous(mptr)) { if (trap->ttyp == BEAR_TRAP) { if (canseemon(mtmp)) - pline("%s eats a bear trap!", Monnam(mtmp)); + pline_mon(mtmp, "%s eats a bear trap!", + Monnam(mtmp)); deltrap(trap); mtmp->meating = 5; mtmp->mtrapped = 0; } else if (trap->ttyp == SPIKED_PIT) { if (canseemon(mtmp)) - pline("%s munches on some spikes!", Monnam(mtmp)); + pline_mon(mtmp, "%s munches on some spikes!", + Monnam(mtmp)); trap->ttyp = PIT; mtmp->meating = 5; } } + trap_result = mtmp->mtrapped ? Trap_Caught_Mon : Trap_Effect_Finished; } else { - register int tt = trap->ttyp; - boolean in_sight, tear_web, see_it, - inescapable = force_mintrap || ((tt == HOLE || tt == PIT) - && Sokoban && !trap->madeby_u); - const char *fallverb; - xchar tx = trap->tx, ty = trap->ty; - - /* true when called from dotrap, inescapable is not an option */ - if (mtmp == u.usteed) - inescapable = TRUE; - if (!inescapable && ((mtmp->mtrapseen & (1 << (tt - 1))) != 0 - || (tt == HOLE && !mindless(mptr)))) { - /* it has been in such a trap - perhaps it escapes */ - if (rn2(4)) - return 0; - } else { - mtmp->mtrapseen |= (1 << (tt - 1)); + int tt = trap->ttyp; + boolean forcetrap = ((mintrapflags & FORCETRAP) != 0); + boolean forcebungle = (mintrapflags & FORCEBUNGLE) != 0; + /* monster has seen such a trap before */ + boolean already_seen = (mon_knows_traps(mtmp, tt) + || (tt == HOLE && !mindless(mptr))); + + if (fixed_tele_trap(trap)) { + mintrapflags |= FORCETRAP; + forcetrap = TRUE; } - /* Monster is aggravated by being trapped by you. - Recognizing who made the trap isn't completely - unreasonable; everybody has their own style. */ - if (trap->madeby_u && rnl(5)) - setmangry(mtmp, TRUE); - - in_sight = canseemon(mtmp); - see_it = cansee(mtmp->mx, mtmp->my); - /* assume hero can tell what's going on for the steed */ - if (mtmp == u.usteed) - in_sight = TRUE; - switch (tt) { - case ARROW_TRAP: - if (trap->once && trap->tseen && !rn2(15)) { - if (in_sight && see_it) - pline("%s triggers a trap but nothing happens.", - Monnam(mtmp)); - deltrap(trap); - newsym(mtmp->mx, mtmp->my); - break; - } - trap->once = 1; - otmp = t_missile(ARROW, trap); - if (in_sight) - seetrap(trap); - if (thitm(8, mtmp, otmp, 0, FALSE)) - trapkilled = TRUE; - break; - case DART_TRAP: - if (trap->once && trap->tseen && !rn2(15)) { - if (in_sight && see_it) - pline("%s triggers a trap but nothing happens.", - Monnam(mtmp)); - deltrap(trap); - newsym(mtmp->mx, mtmp->my); - break; - } - trap->once = 1; - otmp = t_missile(DART, trap); - if (!rn2(6)) - otmp->opoisoned = 1; - if (in_sight) - seetrap(trap); - if (thitm(7, mtmp, otmp, 0, FALSE)) - trapkilled = TRUE; - break; - case ROCKTRAP: - if (trap->once && trap->tseen && !rn2(15)) { - if (in_sight && see_it) - pline( - "A trap door above %s opens, but nothing falls out!", - mon_nam(mtmp)); - deltrap(trap); - newsym(mtmp->mx, mtmp->my); - break; - } - trap->once = 1; - otmp = t_missile(ROCK, trap); - if (in_sight) - seetrap(trap); - if (thitm(0, mtmp, otmp, d(2, 6), FALSE)) - trapkilled = TRUE; - break; - case SQKY_BOARD: - if (is_flyer(mptr)) - break; - /* stepped on a squeaky board */ - if (in_sight) { - if (!Deaf) { - pline("A board beneath %s squeaks %s loudly.", - mon_nam(mtmp), trapnote(trap, 0)); - seetrap(trap); - } else { - pline("%s stops momentarily and appears to cringe.", - Monnam(mtmp)); - } - } else { - /* same near/far threshold as mzapmsg() */ - int range = couldsee(mtmp->mx, mtmp->my) /* 9 or 5 */ - ? (BOLT_LIM + 1) : (BOLT_LIM - 3); - - You_hear("a %s squeak %s.", trapnote(trap, 1), - (distu(mtmp->mx, mtmp->my) <= range * range) - ? "nearby" : "in the distance"); - } - /* wake up nearby monsters */ - wake_nearto(mtmp->mx, mtmp->my, 40); - break; - case BEAR_TRAP: - if (mptr->msize > MZ_SMALL && !amorphous(mptr) && !is_flyer(mptr) - && !is_whirly(mptr) && !unsolid(mptr)) { - mtmp->mtrapped = 1; - if (in_sight) { - pline("%s is caught in %s bear trap!", Monnam(mtmp), - a_your[trap->madeby_u]); - seetrap(trap); - } else { - if (mptr == &mons[PM_OWLBEAR] - || mptr == &mons[PM_BUGBEAR]) - You_hear("the roaring of an angry bear!"); - } - } else if (force_mintrap) { - if (in_sight) { - pline("%s evades %s bear trap!", Monnam(mtmp), - a_your[trap->madeby_u]); - seetrap(trap); - } - } - if (mtmp->mtrapped) - trapkilled = thitm(0, mtmp, (struct obj *) 0, d(2, 4), FALSE); - break; - case SLP_GAS_TRAP: - if (!resists_sleep(mtmp) && !breathless(mptr) && !mtmp->msleeping - && mtmp->mcanmove) { - if (sleep_monst(mtmp, rnd(25), -1) && in_sight) { - pline("%s suddenly falls asleep!", Monnam(mtmp)); - seetrap(trap); - } - } - break; - case RUST_TRAP: { - struct obj *target; - - if (in_sight) - seetrap(trap); - switch (rn2(5)) { - case 0: - if (in_sight) - pline("%s %s on the %s!", A_gush_of_water_hits, - mon_nam(mtmp), mbodypart(mtmp, HEAD)); - target = which_armor(mtmp, W_ARMH); - (void) water_damage(target, helm_simple_name(target), TRUE); - break; - case 1: - if (in_sight) - pline("%s %s's left %s!", A_gush_of_water_hits, - mon_nam(mtmp), mbodypart(mtmp, ARM)); - target = which_armor(mtmp, W_ARMS); - if (water_damage(target, "shield", TRUE) != ER_NOTHING) - break; - target = MON_WEP(mtmp); - if (target && bimanual(target)) - (void) water_damage(target, 0, TRUE); - glovecheck: - target = which_armor(mtmp, W_ARMG); - (void) water_damage(target, "gauntlets", TRUE); - break; - case 2: - if (in_sight) - pline("%s %s's right %s!", A_gush_of_water_hits, - mon_nam(mtmp), mbodypart(mtmp, ARM)); - (void) water_damage(MON_WEP(mtmp), 0, TRUE); - goto glovecheck; - default: - if (in_sight) - pline("%s %s!", A_gush_of_water_hits, mon_nam(mtmp)); - for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj) - if (otmp->lamplit - && (otmp->owornmask & (W_WEP | W_SWAPWEP)) == 0) - (void) snuff_lit(otmp); - if ((target = which_armor(mtmp, W_ARMC)) != 0) - (void) water_damage(target, cloak_simple_name(target), - TRUE); - else if ((target = which_armor(mtmp, W_ARM)) != 0) - (void) water_damage(target, suit_simple_name(target), - TRUE); - else if ((target = which_armor(mtmp, W_ARMU)) != 0) - (void) water_damage(target, "shirt", TRUE); - } - - if (mptr == &mons[PM_IRON_GOLEM]) { - if (in_sight) - pline("%s falls to pieces!", Monnam(mtmp)); - else if (mtmp->mtame) - pline("May %s rust in peace.", mon_nam(mtmp)); - mondied(mtmp); - if (DEADMONSTER(mtmp)) - trapkilled = TRUE; - } else if (mptr == &mons[PM_GREMLIN] && rn2(3)) { - (void) split_mon(mtmp, (struct monst *) 0); - } - break; - } /* RUST_TRAP */ - case FIRE_TRAP: - mfiretrap: - if (in_sight) - pline("A %s erupts from the %s under %s!", tower_of_flame, - surface(mtmp->mx, mtmp->my), mon_nam(mtmp)); - else if (see_it) /* evidently `mtmp' is invisible */ - You_see("a %s erupt from the %s!", tower_of_flame, - surface(mtmp->mx, mtmp->my)); - - if (resists_fire(mtmp)) { - if (in_sight) { - shieldeff(mtmp->mx, mtmp->my); - pline("%s is uninjured.", Monnam(mtmp)); - } - } else { - int num = d(2, 4), alt; - boolean immolate = FALSE; - - /* paper burns very fast, assume straw is tightly - * packed and burns a bit slower */ - switch (monsndx(mptr)) { - case PM_PAPER_GOLEM: - immolate = TRUE; - alt = mtmp->mhpmax; - break; - case PM_STRAW_GOLEM: - alt = mtmp->mhpmax / 2; - break; - case PM_WOOD_GOLEM: - alt = mtmp->mhpmax / 4; - break; - case PM_LEATHER_GOLEM: - alt = mtmp->mhpmax / 8; - break; - default: - alt = 0; - break; - } - if (alt > num) - num = alt; - if (thitm(0, mtmp, (struct obj *) 0, num, immolate)) - trapkilled = TRUE; - else - /* we know mhp is at least `num' below mhpmax, - so no (mhp > mhpmax) check is needed here */ - mtmp->mhpmax -= rn2(num + 1); - } - if (burnarmor(mtmp) || rn2(3)) { - (void) destroy_mitem(mtmp, SCROLL_CLASS, AD_FIRE); - (void) destroy_mitem(mtmp, SPBOOK_CLASS, AD_FIRE); - (void) destroy_mitem(mtmp, POTION_CLASS, AD_FIRE); + if (mtmp == u.usteed) { + ; /* true when called from dotrap, inescapable is not an option */ + } else if (Sokoban && (is_pit(tt) || is_hole(tt)) + && !trap->madeby_u) { + ; /* nothing here, the trap effects will handle messaging */ + } else if (!forcetrap) { + if (floor_trigger(tt) && check_in_air(mtmp, mintrapflags)) { + return Trap_Effect_Finished; } - if (burn_floor_objects(mtmp->mx, mtmp->my, see_it, FALSE) - && !see_it && distu(mtmp->mx, mtmp->my) <= 3 * 3) - You("smell smoke."); - if (is_ice(mtmp->mx, mtmp->my)) - melt_ice(mtmp->mx, mtmp->my, (char *) 0); - if (see_it && t_at(mtmp->mx, mtmp->my)) - seetrap(trap); - break; - case PIT: - case SPIKED_PIT: - fallverb = "falls"; - if (is_flyer(mptr) || is_floater(mptr) - || (mtmp->wormno && count_wsegs(mtmp) > 5) - || is_clinger(mptr)) { - if (force_mintrap && !Sokoban) { - /* openfallingtrap; not inescapable here */ - if (in_sight) { - seetrap(trap); - pline("%s doesn't fall into the pit.", Monnam(mtmp)); - } - break; /* inescapable = FALSE; */ - } - if (!inescapable) - break; /* avoids trap */ - fallverb = "is dragged"; /* sokoban pit */ - } - if (!passes_walls(mptr)) - mtmp->mtrapped = 1; - if (in_sight) { - pline("%s %s into %s pit!", Monnam(mtmp), fallverb, - a_your[trap->madeby_u]); - if (mptr == &mons[PM_PIT_VIPER] - || mptr == &mons[PM_PIT_FIEND]) - pline("How pitiful. Isn't that the pits?"); - seetrap(trap); - } - mselftouch(mtmp, "Falling, ", FALSE); - if (DEADMONSTER(mtmp) || thitm(0, mtmp, (struct obj *) 0, - rnd((tt == PIT) ? 6 : 10), FALSE)) - trapkilled = TRUE; - break; - case HOLE: - case TRAPDOOR: - if (!Can_fall_thru(&u.uz)) { - impossible("mintrap: %ss cannot exist on this level.", - defsyms[trap_to_defsym(tt)].explanation); - break; /* don't activate it after all */ - } - if (is_flyer(mptr) || is_floater(mptr) || mptr == &mons[PM_WUMPUS] - || (mtmp->wormno && count_wsegs(mtmp) > 5) - || mptr->msize >= MZ_HUGE) { - if (force_mintrap && !Sokoban) { - /* openfallingtrap; not inescapable here */ - if (in_sight) { - seetrap(trap); - if (tt == TRAPDOOR) - pline( - "A trap door opens, but %s doesn't fall through.", - mon_nam(mtmp)); - else /* (tt == HOLE) */ - pline("%s doesn't fall through the hole.", - Monnam(mtmp)); - } - break; /* inescapable = FALSE; */ - } - if (inescapable) { /* sokoban hole */ - if (in_sight) { - pline("%s seems to be yanked down!", Monnam(mtmp)); - /* suppress message in mlevel_tele_trap() */ - in_sight = FALSE; - seetrap(trap); - } - } else - break; - } - /*FALLTHRU*/ - case LEVEL_TELEP: - case MAGIC_PORTAL: { - int mlev_res; - - mlev_res = mlevel_tele_trap(mtmp, trap, inescapable, in_sight); - if (mlev_res) - return mlev_res; - break; + if (already_seen && rn2(4) && !forcebungle) + return Trap_Effect_Finished; } - case TELEP_TRAP: - mtele_trap(mtmp, trap, in_sight); - break; - case WEB: - /* Monster in a web. */ - if (webmaker(mptr)) - break; - if (mu_maybe_destroy_web(mtmp, in_sight, trap)) - break; - tear_web = FALSE; - switch (monsndx(mptr)) { - case PM_OWLBEAR: /* Eric Backus */ - case PM_BUGBEAR: - if (!in_sight) { - You_hear("the roaring of a confused bear!"); - mtmp->mtrapped = 1; - break; - } - /*FALLTHRU*/ - default: - if (mptr->mlet == S_GIANT - /* exclude baby dragons and relatively short worms */ - || (mptr->mlet == S_DRAGON && extra_nasty(mptr)) - || (mtmp->wormno && count_wsegs(mtmp) > 5)) { - tear_web = TRUE; - } else if (in_sight) { - pline("%s is caught in %s spider web.", Monnam(mtmp), - a_your[trap->madeby_u]); - seetrap(trap); - } - mtmp->mtrapped = tear_web ? 0 : 1; - break; - /* this list is fairly arbitrary; it deliberately - excludes wumpus & giant/ettin zombies/mummies */ - case PM_TITANOTHERE: - case PM_BALUCHITHERIUM: - case PM_PURPLE_WORM: - case PM_JABBERWOCK: - case PM_IRON_GOLEM: - case PM_BALROG: - case PM_KRAKEN: - case PM_MASTODON: - case PM_ORION: - case PM_NORN: - case PM_CYCLOPS: - case PM_LORD_SURTUR: - tear_web = TRUE; - break; - } - if (tear_web) { - if (in_sight) - pline("%s tears through %s spider web!", Monnam(mtmp), - a_your[trap->madeby_u]); - deltrap(trap); - newsym(mtmp->mx, mtmp->my); - } else if (force_mintrap && !mtmp->mtrapped) { - if (in_sight) { - pline("%s avoids %s spider web!", Monnam(mtmp), - a_your[trap->madeby_u]); - seetrap(trap); - } - } - break; - case STATUE_TRAP: - break; - case MAGIC_TRAP: - /* A magic trap. Monsters usually immune. */ - if (!rn2(21)) - goto mfiretrap; - break; - case ANTI_MAGIC: - /* similar to hero's case, more or less */ - if (!resists_magm(mtmp)) { /* lose spell energy */ - if (!mtmp->mcan && (attacktype(mptr, AT_MAGC) - || attacktype(mptr, AT_BREA))) { - mtmp->mspec_used += d(2, 2); - if (in_sight) { - seetrap(trap); - pline("%s seems lethargic.", Monnam(mtmp)); - } - } - } else { /* take some damage */ - int dmgval2 = rnd(4); - - if ((otmp = MON_WEP(mtmp)) != 0 - && otmp->oartifact == ART_MAGICBANE) - dmgval2 += rnd(4); - for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj) - if (otmp->oartifact - && defends_when_carried(AD_MAGM, otmp)) - break; - if (otmp) - dmgval2 += rnd(4); - if (passes_walls(mptr)) - dmgval2 = (dmgval2 + 3) / 4; - if (in_sight) - seetrap(trap); - mtmp->mhp -= dmgval2; - if (DEADMONSTER(mtmp)) - monkilled(mtmp, - in_sight - ? "compression from an anti-magic field" - : (const char *) 0, - -AD_MAGM); - if (DEADMONSTER(mtmp)) - trapkilled = TRUE; - if (see_it) - newsym(trap->tx, trap->ty); - } - break; - case LANDMINE: - if (rn2(3)) - break; /* monsters usually don't set it off */ - if (is_flyer(mptr)) { - boolean already_seen = trap->tseen; + mon_learns_traps(mtmp, tt); + mons_see_trap(trap); - if (in_sight && !already_seen) { - pline("A trigger appears in a pile of soil below %s.", - mon_nam(mtmp)); - seetrap(trap); - } - if (rn2(3)) - break; - if (in_sight) { - newsym(mtmp->mx, mtmp->my); - pline_The("air currents set %s off!", - already_seen ? "a land mine" : "it"); - } - } else if (in_sight) { - newsym(mtmp->mx, mtmp->my); - pline("%s%s triggers %s land mine!", - !Deaf ? "KAABLAMM!!! " : "", Monnam(mtmp), - a_your[trap->madeby_u]); - } - if (!in_sight && !Deaf) - pline("Kaablamm! %s an explosion in the distance!", - "You hear"); /* Deaf-aware */ - blow_up_landmine(trap); - /* explosion might have destroyed a drawbridge; don't - dish out more damage if monster is already dead */ - if (DEADMONSTER(mtmp) - || thitm(0, mtmp, (struct obj *) 0, rnd(16), FALSE)) { - trapkilled = TRUE; - } else { - /* monsters recursively fall into new pit */ - if (mintrap(mtmp) == 2) - trapkilled = TRUE; - } - /* a boulder may fill the new pit, crushing monster */ - fill_pit(tx, ty); /* thitm may have already destroyed the trap */ - if (DEADMONSTER(mtmp)) - trapkilled = TRUE; - if (unconscious()) { - multi = -1; - nomovemsg = "The explosion awakens you!"; - } - break; - case POLY_TRAP: - 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)) - /* we're done with mptr but keep it up to date */ - mptr = mtmp->data; - if (in_sight) - seetrap(trap); - } - break; - case ROLLING_BOULDER_TRAP: - if (!is_flyer(mptr)) { - int style = ROLL | (in_sight ? 0 : LAUNCH_UNSEEN); + /* Monster is aggravated by being trapped by you. + Recognizing who made the trap isn't completely + unreasonable; everybody has their own style. */ + if (trap->madeby_u && rnl(5)) + setmangry(mtmp, FALSE); - newsym(mtmp->mx, mtmp->my); - if (in_sight) - pline("Click! %s triggers %s.", Monnam(mtmp), - trap->tseen ? "a rolling boulder trap" : something); - if (launch_obj(BOULDER, trap->launch.x, trap->launch.y, - trap->launch2.x, trap->launch2.y, style)) { - if (in_sight) - trap->tseen = TRUE; - if (DEADMONSTER(mtmp)) - trapkilled = TRUE; - } else { - deltrap(trap); - newsym(mtmp->mx, mtmp->my); - } - } - break; - case VIBRATING_SQUARE: - if (see_it && !Blind) { - seetrap(trap); /* before messages */ - if (in_sight) { - char buf[BUFSZ], *p, *monnm = mon_nam(mtmp); + trap_result = trapeffect_selector(mtmp, trap, mintrapflags); - if (nolimbs(mtmp->data) - || is_floater(mtmp->data) || is_flyer(mtmp->data)) { - /* just "beneath " */ - Strcpy(buf, monnm); - } else { - Strcpy(buf, s_suffix(monnm)); - p = eos(strcat(buf, " ")); - Strcpy(p, makeplural(mbodypart(mtmp, FOOT))); - /* avoid "beneath 'rear paws'" or 'rear hooves' */ - (void) strsubst(p, "rear ", ""); - } - You_see("a strange vibration beneath %s.", buf); - } else { - /* notice something (hearing uses a larger threshold - for 'nearby') */ - You_see("the ground vibrate %s.", - (distu(mtmp->mx, mtmp->my) <= 2 * 2) - ? "nearby" : "in the distance"); - } - } - break; - default: - impossible("Some monster encountered a strange trap of type %d.", - tt); + /* mtmp can't stay hiding under an object if trapped in non-pit + (mtmp hiding under object at armed bear trap location, hero + zaps wand of locking or spell of wizard lock at spot triggering + the trap and trapping mtmp there) */ + if (!DEADMONSTER(mtmp) && mtmp->mtrapped) { + boolean alreadyspotted = canspotmon(mtmp); + + maybe_unhide_at(mtmp->mx, mtmp->my); + if (!alreadyspotted && canseemon(mtmp)) + pline_mon(mtmp, "%s appears.", Amonnam(mtmp)); } } - if (trapkilled) - return 2; - return mtmp->mtrapped; + return trap_result; } /* Combine cockatrice checks into single functions to avoid repeating code. */ void -instapetrify(str) -const char *str; +instapetrify(const char *str) { if (Stone_resistance) return; - if (poly_when_stoned(youmonst.data) && polymon(PM_STONE_GOLEM)) + if (poly_when_stoned(gy.youmonst.data) && polymon(PM_STONE_GOLEM)) return; - You("turn to stone..."); - killer.format = KILLED_BY; - if (str != killer.name) - Strcpy(killer.name, str ? str : ""); + urgent_pline("You turn to stone..."); + svk.killer.format = KILLED_BY; + if (str != svk.killer.name) + Strcpy(svk.killer.name, str ? str : ""); done(STONING); } void -minstapetrify(mon, byplayer) -struct monst *mon; -boolean byplayer; +minstapetrify(struct monst *mon, boolean byplayer) { if (resists_ston(mon)) return; @@ -2787,24 +3871,25 @@ boolean byplayer; mon_adjust_speed(mon, -3, (struct obj *) 0); if (cansee(mon->mx, mon->my)) - pline("%s turns to stone.", Monnam(mon)); + pline_mon(mon, "%s turns to stone.", Monnam(mon)); if (byplayer) { - stoned = TRUE; + gs.stoned = TRUE; xkilled(mon, XKILL_NOMSG); } else monstone(mon); } void -selftouch(arg) -const char *arg; +selftouch(const char *arg) { char kbuf[BUFSZ]; + const char *corpse_pmname; if (uwep && uwep->otyp == CORPSE && touch_petrifies(&mons[uwep->corpsenm]) && !Stone_resistance) { - pline("%s touch the %s corpse.", arg, mons[uwep->corpsenm].mname); - Sprintf(kbuf, "%s corpse", an(mons[uwep->corpsenm].mname)); + corpse_pmname = obj_pmname(uwep); + pline("%s touch the %s corpse.", arg, corpse_pmname); + Sprintf(kbuf, "%s corpse", an(corpse_pmname)); instapetrify(kbuf); /* life-saved; unwield the corpse if we can't handle it */ if (!uarmg && !Stone_resistance) @@ -2814,8 +3899,9 @@ const char *arg; allow two-weapon combat when either weapon is a corpse] */ if (u.twoweap && uswapwep && uswapwep->otyp == CORPSE && touch_petrifies(&mons[uswapwep->corpsenm]) && !Stone_resistance) { - pline("%s touch the %s corpse.", arg, mons[uswapwep->corpsenm].mname); - Sprintf(kbuf, "%s corpse", an(mons[uswapwep->corpsenm].mname)); + corpse_pmname = obj_pmname(uswapwep); + pline("%s touch the %s corpse.", arg, corpse_pmname); + Sprintf(kbuf, "%s corpse", an(corpse_pmname)); instapetrify(kbuf); /* life-saved; unwield the corpse */ if (!uarmg && !Stone_resistance) @@ -2824,37 +3910,38 @@ const char *arg; } void -mselftouch(mon, arg, byplayer) -struct monst *mon; -const char *arg; -boolean byplayer; +mselftouch( + struct monst *mon, + const char *arg, + boolean byplayer) { struct obj *mwep = MON_WEP(mon); if (mwep && mwep->otyp == CORPSE && touch_petrifies(&mons[mwep->corpsenm]) && !resists_ston(mon)) { if (cansee(mon->mx, mon->my)) { - pline("%s%s touches %s.", arg ? arg : "", + pline_mon(mon, "%s%s touches %s.", arg ? arg : "", arg ? mon_nam(mon) : Monnam(mon), corpse_xname(mwep, (const char *) 0, CXN_PFX_THE)); } minstapetrify(mon, byplayer); /* if life-saved, might not be able to continue wielding */ - if (!DEADMONSTER(mon) && !which_armor(mon, W_ARMG) && !resists_ston(mon)) + if (!DEADMONSTER(mon) + && !which_armor(mon, W_ARMG) && !resists_ston(mon)) mwepgone(mon); } } /* start levitating */ void -float_up() +float_up(void) { - context.botl = TRUE; + disp.botl = TRUE; if (u.utrap) { if (u.utraptype == TT_PIT) { reset_utrap(FALSE); - You("float up, out of the pit!"); - vision_full_recalc = 1; /* vision limits change */ + You("float up, out of the %s!", trapname(PIT, FALSE)); + gv.vision_full_recalc = 1; /* vision limits change */ fill_pit(u.ux, u.uy); } else if (u.utraptype == TT_LAVA /* molten lava */ || u.utraptype == TT_INFLOOR) { /* solidified lava */ @@ -2874,7 +3961,8 @@ float_up() body_part(LEG), IS_ROOM(levl[cc.x][cc.y].typ) ? "floor" : "ground"); } else if (u.utraptype == WEB) { - You("float up slightly, but you are still stuck in the web."); + You("float up slightly, but you are still stuck in the %s.", + trapname(WEB, FALSE)); } else { /* bear trap */ You("float up slightly, but your %s is still stuck.", body_part(LEG)); @@ -2887,10 +3975,11 @@ float_up() } else if (u.uinwater) { spoteffects(TRUE); } else if (u.uswallow) { - You(is_animal(u.ustuck->data) ? "float away from the %s." - : "spiral up into %s.", - is_animal(u.ustuck->data) ? surface(u.ux, u.uy) - : mon_nam(u.ustuck)); + /* FIXME: this isn't correct for trapper/lurker above */ + if (is_animal(u.ustuck->data)) + You("float away from the %s.", surface(u.ux, u.uy)); + else + You("spiral up into %s.", mon_nam(u.ustuck)); } else if (Hallucination) { pline("Up, up, and awaaaay! You're walking on air!"); } else if (Is_airlevel(&u.uz)) { @@ -2898,7 +3987,8 @@ float_up() } else { You("start to float in the air!"); } - if (u.usteed && !is_floater(u.usteed->data) && !is_flyer(u.usteed->data)) { + if (u.usteed && !is_floater(u.usteed->data) + && !is_flyer(u.usteed->data)) { if (Lev_at_will) { pline("%s magically floats up!", Monnam(u.usteed)); } else { @@ -2911,19 +4001,19 @@ float_up() float_vs_flight(); /* set BFlying, also BLevitation if still trapped */ /* levitation gives maximum carrying capacity, so encumbrance state might be reduced */ - (void) encumber_msg(); + encumber_msg(); return; } +/* a boulder fills a pit or a hole at x,y */ void -fill_pit(x, y) -int x, y; +fill_pit(coordxy x, coordxy y) { struct obj *otmp; struct trap *t; - if ((t = t_at(x, y)) && is_pit(t->ttyp) - && (otmp = sobj_at(BOULDER, x, y))) { + if ((t = t_at(x, y)) != 0 && (is_pit(t->ttyp) || is_hole(t->ttyp)) + && (otmp = sobj_at(BOULDER, x, y)) != 0) { obj_extract_self(otmp); (void) flooreffects(otmp, x, y, "settle"); } @@ -2931,10 +4021,11 @@ int x, y; /* stop levitating */ int -float_down(hmask, emask) -long hmask, emask; /* might cancel timeout */ +float_down( + long hmask, + long emask) /* might cancel timeout */ { - register struct trap *trap = (struct trap *) 0; + struct trap *trap = (struct trap *) 0; d_level current_dungeon_level; boolean no_msg = FALSE; @@ -2957,10 +4048,10 @@ long hmask, emask; /* might cancel timeout */ : (u.utraptype == TT_BURIEDBALL) ? "chain" : (u.utraptype == TT_LAVA) ? "lava" : "ground"); /* TT_INFLOOR */ - (void) encumber_msg(); /* carrying capacity might have changed */ + encumber_msg(); /* carrying capacity might have changed */ return 0; } - context.botl = TRUE; + disp.botl = TRUE; nomul(0); /* stop running or resting */ if (BFlying) { /* controlled flight no longer overridden by levitation */ @@ -2968,18 +4059,18 @@ long hmask, emask; /* might cancel timeout */ * unless hero is stuck in floor */ if (Flying) { You("have stopped levitating and are now flying."); - (void) encumber_msg(); /* carrying capacity might have changed */ + encumber_msg(); /* carrying capacity might have changed */ return 1; } } if (u.uswallow) { You("float down, but you are still %s.", - is_animal(u.ustuck->data) ? "swallowed" : "engulfed"); - (void) encumber_msg(); + digests(u.ustuck->data) ? "swallowed" : "engulfed"); + encumber_msg(); return 1; } - if (Punished && !carried(uball) + if (Punished && !carried(uball) && !m_at(uball->ox, uball->oy) && (is_pool(uball->ox, uball->oy) || ((trap = t_at(uball->ox, uball->oy)) && (is_pit(trap->ttyp) || is_hole(trap->ttyp))))) { @@ -2989,18 +4080,18 @@ long hmask, emask; /* might cancel timeout */ u.uy = uball->oy; movobj(uchain, uball->ox, uball->oy); newsym(u.ux0, u.uy0); - vision_full_recalc = 1; /* in case the hero moved. */ + gv.vision_full_recalc = 1; /* in case the hero moved. */ } /* check for falling into pool - added by GAN 10/20/86 */ if (!Flying) { if (!u.uswallow && u.ustuck) { - if (sticks(youmonst.data)) + if (sticks(gy.youmonst.data)) You("aren't able to maintain your hold on %s.", mon_nam(u.ustuck)); else pline("Startled, %s can no longer hold you!", mon_nam(u.ustuck)); - u.ustuck = 0; + set_ustuck((struct monst *) 0); } /* kludge alert: * drown() and lava_effects() print various messages almost @@ -3013,7 +4104,7 @@ long hmask, emask; /* might cancel timeout */ if (is_pool(u.ux, u.uy) && !Wwalking && !Swimming && !u.uinwater) no_msg = drown(); - if (is_lava(u.ux, u.uy)) { + if (is_lava(u.ux, u.uy) && !iflags.in_lava_effects) { (void) lava_effects(); no_msg = TRUE; } @@ -3059,7 +4150,7 @@ long hmask, emask; /* might cancel timeout */ /* levitation gives maximum carrying capacity, so having it end potentially triggers greater encumbrance; do this after 'come down' messages, before trap activation or autopickup */ - (void) encumber_msg(); + encumber_msg(); /* can't rely on u.uz0 for detecting trap door-induced level change; it gets changed to reflect the new level before we can check it */ @@ -3072,10 +4163,11 @@ long hmask, emask; /* might cancel timeout */ case TRAPDOOR: if (!Can_fall_thru(&u.uz) || u.ustuck) break; + FALLTHROUGH; /*FALLTHRU*/ default: if (!u.utrap) /* not already in the trap */ - dotrap(trap, 0); + dotrap(trap, NO_TRAP_FLAGS); } } if (!Is_airlevel(&u.uz) && !Is_waterlevel(&u.uz) && !u.uswallow @@ -3088,40 +4180,48 @@ long hmask, emask; /* might cancel timeout */ /* shared code for climbing out of a pit */ void -climb_pit() +climb_pit(void) { + const char *pitname; + if (!u.utrap || u.utraptype != TT_PIT) return; + pitname = trapname(PIT, FALSE); if (Passes_walls) { /* marked as trapped so they can pick things up */ - You("ascend from the pit."); + You("ascend from the %s.", pitname); reset_utrap(FALSE); fill_pit(u.ux, u.uy); - vision_full_recalc = 1; /* vision limits change */ + gv.vision_full_recalc = 1; /* vision limits change */ } else if (!rn2(2) && sobj_at(BOULDER, u.ux, u.uy)) { Your("%s gets stuck in a crevice.", body_part(LEG)); display_nhwindow(WIN_MESSAGE, FALSE); clear_nhwindow(WIN_MESSAGE); You("free your %s.", body_part(LEG)); - } else if ((Flying || is_clinger(youmonst.data)) && !Sokoban) { + } else if ((Flying || is_clinger(gy.youmonst.data)) && !Sokoban) { /* eg fell in pit, then poly'd to a flying monster; or used '>' to deliberately enter it */ - You("%s from the pit.", Flying ? "fly" : "climb"); + You("%s from the %s.", u_locomotion("climb"), pitname); reset_utrap(FALSE); fill_pit(u.ux, u.uy); - vision_full_recalc = 1; /* vision limits change */ - } else if (!(--u.utrap)) { + gv.vision_full_recalc = 1; /* vision limits change */ + } else if (!(--u.utrap) || m_easy_escape_pit(&gy.youmonst)) { reset_utrap(FALSE); - You("%s to the edge of the pit.", + You("%s to the edge of the %s.", (Sokoban && Levitation) ? "struggle against the air currents and float" - : u.usteed ? "ride" : "crawl"); + : u.usteed ? "ride" : "crawl", + pitname); fill_pit(u.ux, u.uy); - vision_full_recalc = 1; /* vision limits change */ + gv.vision_full_recalc = 1; /* vision limits change */ } else if (u.dz || flags.verbose) { + /* these should use 'pitname' rather than "pit" for hallucination + but that would nullify Norep (this message can be repeated + many times without further user intervention by using a run + attempt to keep retrying to escape from the pit) */ if (u.usteed) - Norep("%s is still in a pit.", upstart(y_monnam(u.usteed))); + Norep("%s is still in a pit.", YMonnam(u.usteed)); else Norep((Hallucination && !rn2(5)) ? "You've fallen, and you can't get up." @@ -3129,12 +4229,13 @@ climb_pit() } } -STATIC_OVL void -dofiretrap(box) -struct obj *box; /* null for floor trap */ +staticfn void +dofiretrap( + struct obj *box) /* null for floor trap */ { boolean see_it = !Blind; - int num, alt; + int orig_dmg, num, alt; + orig_dmg = num = d(2, 4); /* Bug: for box case, the equivalent of burn_floor_objects() ought * to be done upon its contents. @@ -3153,9 +4254,9 @@ struct obj *box; /* null for floor trap */ the(box ? xname(box) : surface(u.ux, u.uy))); if (Fire_resistance) { shieldeff(u.ux, u.uy); + monstseesu(M_SEEN_FIRE); num = rn2(2); } else if (Upolyd) { - num = d(2, 4); switch (u.umonnum) { case PM_PAPER_GOLEM: alt = u.mhmax; @@ -3176,11 +4277,25 @@ struct obj *box; /* null for floor trap */ if (alt > num) num = alt; if (u.mhmax > mons[u.umonnum].mlevel) - u.mhmax -= rn2(min(u.mhmax, num + 1)), context.botl = 1; + u.mhmax -= rn2(min(u.mhmax, num + 1)), disp.botl = TRUE; + if (u.mh > u.mhmax) + u.mh = u.mhmax, disp.botl = TRUE; + monstunseesu(M_SEEN_FIRE); } else { + int uhpmin = minuhpmax(1), olduhpmax = u.uhpmax; + num = d(2, 4); - if (u.uhpmax > u.ulevel) - u.uhpmax -= rn2(min(u.uhpmax, num + 1)), context.botl = 1; + if (u.uhpmax > uhpmin) { + u.uhpmax -= rn2(min(u.uhpmax, num + 1)), disp.botl = TRUE; + } /* note: no 'else' here */ + if (u.uhpmax < uhpmin) { + setuhpmax(min(olduhpmax, uhpmin), FALSE); /* sets disp.botl */ + if (!Drain_resistance) + losexp(NULL); /* never fatal when 'drainer' is Null */ + } + if (u.uhp > u.uhpmax) + u.uhp = u.uhpmax, disp.botl = TRUE; + monstunseesu(M_SEEN_FIRE); } if (!num) You("are uninjured."); @@ -3188,10 +4303,9 @@ struct obj *box; /* null for floor trap */ losehp(num, tower_of_flame, KILLED_BY_AN); /* fire damage */ burn_away_slime(); - if (burnarmor(&youmonst) || rn2(3)) { - destroy_item(SCROLL_CLASS, AD_FIRE); - destroy_item(SPBOOK_CLASS, AD_FIRE); - destroy_item(POTION_CLASS, AD_FIRE); + if (burnarmor(&gy.youmonst) || rn2(3)) { + (void) destroy_items(&gy.youmonst, AD_FIRE, orig_dmg); + ignite_items(gi.invent); } if (!box && burn_floor_objects(u.ux, u.uy, see_it, TRUE) && !see_it) You("smell paper burning."); @@ -3199,10 +4313,10 @@ struct obj *box; /* null for floor trap */ melt_ice(u.ux, u.uy, (char *) 0); } -STATIC_OVL void -domagictrap() +staticfn void +domagictrap(void) { - register int fate = rnd(20); + int fate = rnd(20); /* What happened to the poor sucker? */ @@ -3211,7 +4325,7 @@ domagictrap() int cnt = rnd(4); /* blindness effects */ - if (!resists_blnd(&youmonst)) { + if (!resists_blnd(&gy.youmonst)) { You("are momentarily blinded by a flash of light!"); make_blinded((long) rn1(5, 10), FALSE); if (!Blind) @@ -3222,26 +4336,47 @@ domagictrap() /* deafness effects */ if (!Deaf) { + Soundeffect(se_deafening_roar_atmospheric, 100); You_hear("a deafening roar!"); incr_itimeout(&HDeaf, rn1(20, 30)); - context.botl = TRUE; + disp.botl = TRUE; } else { /* magic vibrations still hit you */ You_feel("rankled."); incr_itimeout(&HDeaf, rn1(5, 15)); - context.botl = TRUE; + disp.botl = TRUE; } while (cnt--) (void) makemon((struct permonst *) 0, u.ux, u.uy, NO_MM_FLAGS); /* roar: wake monsters in vicinity, after placing trap-created ones */ wake_nearto(u.ux, u.uy, 7 * 7); /* [flash: should probably also hit nearby gremlins with light] */ - } else + } else { switch (fate) { case 10: - case 11: /* sometimes nothing happens */ break; + case 11: /* toggle intrinsic invisibility */ + Soundeffect(se_low_hum, 100); + You_hear("a low hum."); + if (!Invis) { + if (!Blind) + self_invis_message(); + } else if (!EInvis && !pm_invisible(gy.youmonst.data)) { + if (!Blind) { + if (!See_invisible) + You("can see yourself again!"); + else + You_cant("see through yourself anymore."); + } + } else { + /* If we're invisible from another source */ + You_feel("a little more %s now.", + HInvis ? "obvious" : "hidden"); + } + HInvis = HInvis ? 0 : HInvis | FROMOUTSIDE; + newsym(u.ux, u.uy); + break; case 12: /* a flash of fire */ dofiretrap((struct obj *) 0); break; @@ -3258,7 +4393,7 @@ domagictrap() if (on_level(&u.uz, &qstart_level)) You_feel( "%slike the prodigal son.", - (flags.female || (Upolyd && is_neuter(youmonst.data))) + (flags.female || (Upolyd && is_neuter(gy.youmonst.data))) ? "oddly " : ""); else @@ -3291,7 +4426,7 @@ domagictrap() continue; mtmp = m_at(u.ux + i, u.uy + j); if (mtmp) - (void) tamedog(mtmp, (struct obj *) 0); + (void) tamedog(mtmp, (struct obj *) 0, TRUE); } break; } @@ -3299,9 +4434,11 @@ domagictrap() struct obj pseudo; long save_conf = HConfusion; - pseudo = zeroobj; /* neither cursed nor blessed, - and zero out oextra */ - pseudo.otyp = SCR_REMOVE_CURSE; + pseudo = cg.zeroobj; /* force 'uncursed' and zero out oextra */ + /* used to be SCR_REMOVE_CURSE but that could cause seffects() + to have hero discover scroll of remove curse */ + pseudo.otyp = SPE_REMOVE_CURSE; + pseudo.oclass = SPBOOK_CLASS; HConfusion = 0L; (void) seffects(&pseudo); HConfusion = save_conf; @@ -3310,21 +4447,15 @@ domagictrap() default: break; } + } } -/* Set an item on fire. - * "force" means not to roll a luck-based protection check for the - * item. - * "x" and "y" are the coordinates to dump the contents of a - * container, if it burns up. - * - * Return whether the object was destroyed. - */ +/* Set an item on fire. Return whether the object was destroyed. */ boolean -fire_damage(obj, force, x, y) -struct obj *obj; -boolean force; -xchar x, y; +fire_damage( + struct obj *obj, + boolean force, /* if True, skip luck-based protection check */ + coordxy x, coordxy y) /* where to place contents of burned up container */ { int chance; struct obj *otmp, *ncobj; @@ -3335,8 +4466,9 @@ xchar x, y; if (catch_lit(obj)) return FALSE; - if (Is_container(obj)) { + if (Is_container(obj) || obj->otyp == STATUE) { switch (obj->otyp) { + case STATUE: case ICE_BOX: return FALSE; /* Immune */ case CHEST: @@ -3350,8 +4482,12 @@ xchar x, y; break; } if ((!force && (Luck + 5) > rn2(chance)) - || (is_flammable(obj) && obj->oerodeproof)) + /* note: containers aren't subject to erosion so are never + marked fireproof/corrodeproof/&c */ + /*|| (is_flammable(obj) && obj->oerodeproof)*/ + ) { return FALSE; + } /* Container is burnt up - dump contents out */ if (in_sight) pline("%s catches fire and burns.", Yname2(obj)); @@ -3411,14 +4547,19 @@ xchar x, y; * Return number of objects destroyed. --ALI */ int -fire_damage_chain(chain, force, here, x, y) -struct obj *chain; -boolean force, here; -xchar x, y; +fire_damage_chain( + struct obj *chain, + boolean force, + boolean here, + coordxy x, coordxy y) { struct obj *obj, *nobj; int num = 0; + /* erode_obj() relies on bhitpos if target objects aren't carried by + the hero or a monster, to check visibility controlling feedback */ + gb.bhitpos.x = x, gb.bhitpos.y = y; + for (obj = chain; obj; obj = nobj) { nobj = here ? obj->nexthere : obj->nobj; if (fire_damage(obj, force, x, y)) @@ -3432,9 +4573,7 @@ xchar x, y; /* obj has been thrown or dropped into lava; damage is worse than mere fire */ boolean -lava_damage(obj, x, y) -struct obj *obj; -xchar x, y; +lava_damage(struct obj *obj, coordxy x, coordxy y) { int otyp = obj->otyp, ocls = obj->oclass; @@ -3459,7 +4598,7 @@ xchar x, y; /* this feedback is pretty clunky and can become very verbose when former contents of a burned container get here via flooreffects() */ - if (obj == thrownobj || obj == kickedobj) + if (obj == gt.thrownobj || obj == gk.kickedobj) pline("%s %s up!", is_plural(obj) ? "They" : "It", otense(obj, "burn")); else @@ -3476,8 +4615,7 @@ xchar x, y; } void -acid_damage(obj) -struct obj *obj; +acid_damage(struct obj *obj) { /* Scrolls but not spellbooks can be erased by acid. */ struct monst *victim; @@ -3486,20 +4624,23 @@ struct obj *obj; if (!obj) return; - victim = carried(obj) ? &youmonst : mcarried(obj) ? obj->ocarry : NULL; - vismon = victim && (victim != &youmonst) && canseemon(victim); + victim = carried(obj) ? &gy.youmonst : mcarried(obj) ? obj->ocarry : NULL; + vismon = victim && (victim != &gy.youmonst) && canseemon(victim); + + if (victim == &gy.youmonst && inventory_resistance_check(AD_ACID)) + return; if (obj->greased) { grease_protect(obj, (char *) 0, victim); } else if (obj->oclass == SCROLL_CLASS && obj->otyp != SCR_BLANK_PAPER) { if (obj->otyp != SCR_BLANK_PAPER -#ifdef MAIL +#ifdef MAIL_STRUCTURES && obj->otyp != SCR_MAIL #endif ) { if (!Blind) { - if (victim == &youmonst) - pline("Your %s.", aobjnam(obj, "fade")); + if (victim == &gy.youmonst) + Your("%s.", aobjnam(obj, "fade")); else if (vismon) pline("%s %s.", s_suffix(Monnam(victim)), aobjnam(obj, "fade")); @@ -3512,30 +4653,73 @@ struct obj *obj; erode_obj(obj, (char *) 0, ERODE_CORRODE, EF_GREASE | EF_VERBOSE); } -/* context for water_damage(), managed by water_damage_chain(); - when more than one stack of potions of acid explode while processing - a chain of objects, use alternate phrasing after the first message */ -static struct h2o_ctx { - int dkn_boom, unk_boom; /* track dknown, !dknown separately */ - boolean ctx_valid; -} acid_ctx = { 0, 0, FALSE }; +staticfn void +pot_acid_damage( + struct obj *obj, + boolean in_invent, + boolean described) +{ + char *bufp; + boolean one, exploded; + + one = (obj->quan == 1L); + exploded = FALSE; + + if (Blind && !in_invent) + obj->dknown = 0; + if (ga.acid_ctx.ctx_valid) + exploded = ((obj->dknown ? ga.acid_ctx.dkn_boom + : ga.acid_ctx.unk_boom) > 0); + if (described) { + /* just gave "The grease washes off your potion of acid." + or "...your potion." (or just "...your potion."); + don't re-describe potion here; if we used "It explodes!" + then "it" might be misconstrued as applying to "grease" */ + pline_The("potion%s %s!", + plur(obj->quan), otense(obj, "explode")); + } else { + /* First message is + * "a [potion| potion|potion of acid] explodes" + * depending on obj->dknown (potion has been seen) and + * objects[POT_ACID].oc_name_known (fully discovered), + * or "some {plural version} explode" when relevant. + * Second and subsequent messages for same chain and + * matching dknown status are + * "another [potion| &c] explodes" or plural + * variant. + */ + bufp = simpleonames(obj); + pline("%s%s %s!", /* "A potion explodes!" */ + !exploded ? (one ? "A " : "Some ") + : (one ? "Another " : "More "), + bufp, vtense(bufp, "explode")); + } + if (ga.acid_ctx.ctx_valid) { + if (obj->dknown) + ga.acid_ctx.dkn_boom++; + else + ga.acid_ctx.unk_boom++; + } + setnotworn(obj); + delobj(obj); + if (in_invent) + update_inventory(); +} /* Get an object wet and damage it appropriately. - * "ostr", if present, is used instead of the object name in some - * messages. - * "force" means not to roll luck to protect some objects. - * Returns an erosion return value (ER_*) - */ + Returns an erosion return value (ER_*). */ int -water_damage(obj, ostr, force) -struct obj *obj; -const char *ostr; -boolean force; +water_damage( + struct obj *obj, /* might be Null; return ER_NOTHING if so */ + const char *ostr, /* if non-Null, use instead of cxname() in messages */ + boolean force) /* if True, skip luck-based protection check */ { + boolean in_invent = obj && carried(obj), described = FALSE; + if (!obj) return ER_NOTHING; - if (snuff_lit(obj)) + if (splash_lit(obj)) return ER_DAMAGED; if (!ostr) @@ -3544,25 +4728,41 @@ boolean force; if (obj->otyp == CAN_OF_GREASE && obj->spe > 0) { return ER_NOTHING; } else if (obj->otyp == TOWEL && obj->spe < 7) { - wet_a_towel(obj, rnd(7), TRUE); + /* a negative change induces a reverse increment, adding abs(change); + spe starts 0..6, arg passed to rnd() is 1..7, change is -7..-1, + final spe is 1..7 and always greater than its starting value */ + wet_a_towel(obj, -rnd(7 - obj->spe), TRUE); return ER_NOTHING; } else if (obj->greased) { - if (!rn2(2)) + if (!rn2(2)) { obj->greased = 0; - if (carried(obj)) - update_inventory(); + if (in_invent) { + pline_The("grease on %s washes off.", yname(obj)); + described = TRUE; /* used to modify potion feedback */ + update_inventory(); + } + /* ungreased potions of acid will always be destroyed by water */ + if (obj->otyp == POT_ACID) { + pot_acid_damage(obj, in_invent, described); + return ER_DESTROYED; + } + } return ER_GREASED; - } else if (Is_container(obj) && !Is_box(obj) - && (obj->otyp != OILSKIN_SACK || (obj->cursed && !rn2(3)))) { - if (carried(obj)) - pline("Water gets into your %s!", ostr); - + } else if (Is_container(obj) + && (!Waterproof_container(obj) || (obj->cursed && !rn2(3)))) { + if (in_invent) { + pline("Some %s gets into your %s!", hliquid("water"), ostr); + gm.mentioned_water = !Hallucination; + } water_damage_chain(obj->cobj, FALSE); return ER_DAMAGED; /* contents were damaged */ - } else if (obj->otyp == OILSKIN_SACK) { - if (carried(obj)) - pline("Some water slides right off your %s.", ostr); - makeknown(OILSKIN_SACK); + } else if (Waterproof_container(obj)) { + if (in_invent && !Blind && !Underwater) { + pline_The("%s cannot get into your %s.", hliquid("water"), ostr); + gm.mentioned_water = !Hallucination; + makeknown(obj->otyp); /* if an oilskin sack, discover it; doesn't + * matter for chest, large box, ice box */ + } /* not actually damaged, but because we /didn't/ get the "water gets into!" message, the player now has more information and thus we need to waste any potion they may have used (also, @@ -3577,93 +4777,71 @@ boolean force; return ER_NOTHING; } else if (obj->oclass == SCROLL_CLASS) { if (obj->otyp == SCR_BLANK_PAPER -#ifdef MAIL +#ifdef MAIL_STRUCTURES || obj->otyp == SCR_MAIL #endif ) return 0; - if (carried(obj)) - pline("Your %s %s.", ostr, vtense(ostr, "fade")); + if (in_invent) + Your("%s %s.", ostr, vtense(ostr, "fade")); obj->otyp = SCR_BLANK_PAPER; obj->dknown = 0; obj->spe = 0; - if (carried(obj)) + if (in_invent) update_inventory(); return ER_DAMAGED; } else if (obj->oclass == SPBOOK_CLASS) { - if (obj->otyp == SPE_BOOK_OF_THE_DEAD) { - pline("Steam rises from %s.", the(xname(obj))); + int otyp = obj->otyp; + + if (otyp == SPE_BOOK_OF_THE_DEAD) { + coordxy ox = 0, oy = 0; + + /* note: The Book of the Dead can't be contained or buried */ + if (get_obj_location(obj, &ox, &oy, CONTAINED_TOO | BURIED_TOO)) + obj->ox = ox, obj->oy = oy; + if (isok(ox, oy) && cansee(ox, oy)) + pline("Steam rises from %s.", the(xname(obj))); return 0; - } else if (obj->otyp == SPE_BLANK_PAPER) { + } else if (otyp == SPE_BLANK_PAPER) { return 0; } - if (carried(obj)) - pline("Your %s %s.", ostr, vtense(ostr, "fade")); - - if (obj->otyp == SPE_NOVEL) { - obj->novelidx = 0; - free_oname(obj); - } + if (in_invent) + Your("%s %s.", ostr, vtense(ostr, "fade")); obj->otyp = SPE_BLANK_PAPER; + /* same re-init as over-reading or polymorph; matters if it gets + polymorphed into non-blank; doesn't matter if eventually written + on since that replaces it with new book and studied count of 0 */ + if (obj->spestudied) + obj->spestudied = rn2(obj->spestudied); obj->dknown = 0; - if (carried(obj)) + /* blanking a novel is more involved than blanking a spellbook */ + if (otyp == SPE_NOVEL) /* old type */ + blank_novel(obj); + if (in_invent) update_inventory(); return ER_DAMAGED; } else if (obj->oclass == POTION_CLASS) { if (obj->otyp == POT_ACID) { - char *bufp; - boolean one = (obj->quan == 1L), update = carried(obj), - exploded = FALSE; - - if (Blind && !carried(obj)) - obj->dknown = 0; - if (acid_ctx.ctx_valid) - exploded = ((obj->dknown ? acid_ctx.dkn_boom - : acid_ctx.unk_boom) > 0); - /* First message is - * "a [potion| potion|potion of acid] explodes" - * depending on obj->dknown (potion has been seen) and - * objects[POT_ACID].oc_name_known (fully discovered), - * or "some {plural version} explode" when relevant. - * Second and subsequent messages for same chain and - * matching dknown status are - * "another [potion| &c] explodes" or plural - * variant. - */ - bufp = simpleonames(obj); - pline("%s %s %s!", /* "A potion explodes!" */ - !exploded ? (one ? "A" : "Some") - : (one ? "Another" : "More"), - bufp, vtense(bufp, "explode")); - if (acid_ctx.ctx_valid) { - if (obj->dknown) - acid_ctx.dkn_boom++; - else - acid_ctx.unk_boom++; - } - setnotworn(obj); - delobj(obj); - if (update) - update_inventory(); + pot_acid_damage(obj, in_invent, described); return ER_DESTROYED; } else if (obj->odiluted) { - if (carried(obj)) - pline("Your %s %s further.", ostr, vtense(ostr, "dilute")); + if (in_invent) + Your("%s %s further.", ostr, vtense(ostr, "dilute")); obj->otyp = POT_WATER; obj->dknown = 0; obj->blessed = obj->cursed = 0; obj->odiluted = 0; - if (carried(obj)) + if (in_invent) update_inventory(); return ER_DAMAGED; } else if (obj->otyp != POT_WATER) { - if (carried(obj)) - pline("Your %s %s.", ostr, vtense(ostr, "dilute")); + if (in_invent) + Your("%s %s.", ostr, vtense(ostr, "dilute")); obj->odiluted++; - if (carried(obj)) + if (in_invent) update_inventory(); return ER_DAMAGED; } @@ -3674,25 +4852,39 @@ boolean force; } void -water_damage_chain(obj, here) -struct obj *obj; -boolean here; +water_damage_chain( + struct obj *obj, + boolean here) { struct obj *otmp; + coordxy x, y; + coord save_bhitpos; + + if (!obj) + return; /* initialize acid context: so far, neither seen (dknown) potions of acid nor unseen have exploded during this water damage sequence */ - acid_ctx.dkn_boom = acid_ctx.unk_boom = 0; - acid_ctx.ctx_valid = TRUE; + ga.acid_ctx.dkn_boom = ga.acid_ctx.unk_boom = 0; + ga.acid_ctx.ctx_valid = TRUE; + /* we don't want to permanently overwrite bhitpos below, since we can get + here from scenarios where it was in use up the call stack (e.g. thrown + item hurtling the levitating hero into a wall of water) */ + save_bhitpos = gb.bhitpos; + /* erode_obj() relies on bhitpos if target objects aren't carried by + the hero or a monster, to check visibility controlling feedback */ + if (get_obj_location(obj, &x, &y, CONTAINED_TOO)) + gb.bhitpos.x = x, gb.bhitpos.y = y; for (; obj; obj = otmp) { otmp = here ? obj->nexthere : obj->nobj; water_damage(obj, (char *) 0, FALSE); } - /* reset acid context */ - acid_ctx.dkn_boom = acid_ctx.unk_boom = 0; - acid_ctx.ctx_valid = FALSE; + /* reset acid context and bhitpos */ + ga.acid_ctx.dkn_boom = ga.acid_ctx.unk_boom = 0; + ga.acid_ctx.ctx_valid = FALSE; + gb.bhitpos = save_bhitpos; } /* @@ -3701,32 +4893,34 @@ boolean here; * Returns TRUE if disrobing made player unencumbered enough to * crawl out of the current predicament. */ -STATIC_OVL boolean -emergency_disrobe(lostsome) -boolean *lostsome; +staticfn boolean +emergency_disrobe(boolean *lostsome) { int invc = inv_cnt(TRUE); while (near_capacity() > (Punished ? UNENCUMBERED : SLT_ENCUMBER)) { - register struct obj *obj, *otmp = (struct obj *) 0; - register int i; + struct obj *obj, *nextobj, *otmp = (struct obj *) 0; + int i; /* Pick a random object */ if (invc > 0) { i = rn2(invc); - for (obj = invent; obj; obj = obj->nobj) { + for (obj = gi.invent; obj; obj = nextobj) { + nextobj = obj->nobj; /* * Undroppables are: body armor, boots, gloves, * amulets, and rings because of the time and effort * in removing them + loadstone and other cursed stuff - * for obvious reasons. + * for obvious reasons. Also, any item in the midst + * of being taken off or stolen. */ if (!((obj->otyp == LOADSTONE && obj->cursed) || obj == uamul || obj == uleft || obj == uright || obj == ublindf || obj == uarm || obj == uarmc || obj == uarmg || obj == uarmf || obj == uarmu || (obj->cursed && (obj == uarmh || obj == uarms)) - || welded(obj))) + || welded(obj) + || obj->o_id == gs.stealoid || obj->in_use)) otmp = obj; /* reached the mark and found some stuff to drop? */ if (--i < 0 && otmp) @@ -3746,19 +4940,134 @@ boolean *lostsome; return TRUE; } +/* pick a random goodpos() next to x,y for monster mtmp. + mtmp could be &gy.youmonst, uses then crawl_destination(). + returns TRUE if any good position found, with the coord in x,y */ +boolean +rnd_nextto_goodpos(coordxy *x, coordxy *y, struct monst *mtmp) +{ + int i, j; + boolean is_u = (mtmp == &gy.youmonst); + coordxy nx, ny, k, dirs[N_DIRS]; + + for (i = 0; i < N_DIRS; ++i) + dirs[i] = i; + for (i = N_DIRS; i > 0; --i) { + j = rn2(i); + k = dirs[j]; + dirs[j] = dirs[i - 1]; + dirs[i - 1] = k; + } + for (i = 0; i < N_DIRS; ++i) { + nx = *x + xdir[dirs[i]]; + ny = *y + ydir[dirs[i]]; + /* crawl_destination and goodpos both include an isok() check */ + if (is_u ? crawl_destination(nx, ny) : goodpos(nx, ny, mtmp, 0)) { + *x = nx; + *y = ny; + return TRUE; + } + } + return FALSE; +} + +/* print a message about being back on the ground after leaving a pool */ +void +back_on_ground(boolean rescued) +{ + const char *preposit = (Levitation || Flying) ? "over" : "on", + *surf = surface(u.ux, u.uy), *you_are_back; + char icebuf[QBUFSZ]; + + if (is_ice(u.ux, u.uy)) { + /* "on ice" */ + surf = ice_descr(u.ux, u.uy, icebuf); + } else if (!strcmpi(surf, "floor") || !strcmpi(surf, "ground")) { + /* "on solid ground" */ + surf = "solid ground"; + } else if (!strcmpi(surf, "bridge") || !strcmpi(surf, "altar") + || !strcmpi(surf, "headstone")) { + /* "on a bridge" */ + surf = an(surf); + } else if (!strcmpi(surf, "stairs") || !strcmpi(surf, "lava") + || !strcmpi(surf, "bottom")) { + /* "on the stairs" */ + surf = the(surf); + } else { /* "cloud", "air", "air bubble", "wall", "fountain", "doorway" */ + /* "in a cloud", "in the air" */ + surf = !strcmp(surf, "air") ? the(surf) : an(surf); + preposit = "in"; + } + if (rescued) { + you_are_back = "You find yourself"; + } else { + you_are_back = flags.verbose ? "You are back" : "Back"; + } + pline("%s %s %s.", you_are_back, preposit, surf); + iflags.last_msg = PLNMSG_BACK_ON_GROUND; +} + +/* life-saving or divine rescue has attempted to get the hero out of hostile + terrain and put hero in an unexpected spot or failed due to overfull level + and just prevented death so "back on solid ground" may be inappropriate */ +void +rescued_from_terrain(int how) +{ + static const char find_yourself[] = "find yourself"; + struct rm *lev = &levl[u.ux][u.uy]; + boolean mesggiven = FALSE; + + switch (how) { + case DROWNING: + if (is_pool(u.ux, u.uy)) { + You("%s %s of %s.", find_yourself, + (Is_waterlevel(&u.uz) || IS_WATERWALL(lev->typ)) + ? "in the midst" : "on top", + hliquid("water")); + mesggiven = TRUE; + } else if (IS_AIR(lev->typ)) { + You("%s in %s.", find_yourself, + Is_waterlevel(&u.uz) ? "an air bubble" : "mid air"); + mesggiven = TRUE; + } + break; + case BURNING: /* moved onto lava without fire resistance */ + case DISSOLVED: /* sunk into lava while fire resistant */ + if (is_pool(u.ux, u.uy)) { + You("%s %s %s.", find_yourself, + u.uinwater ? "in" : "on", hliquid("water")); + mesggiven = TRUE; + } else if (is_lava(u.ux, u.uy)) { + You("%s on top of %s.", find_yourself, hliquid("molten lava")); + mesggiven = TRUE; + } + break; + default: + break; + } + if (!mesggiven) + back_on_ground(TRUE); + + iflags.last_msg = PLNMSG_BACK_ON_GROUND; /* for describe_decor() */ + /* feedback just disclosed this */ + update_lastseentyp(u.ux, u.uy); + iflags.prev_decor = svl.lastseentyp[u.ux][u.uy]; +} -/* return TRUE iff player relocated */ +/* return TRUE iff player relocated */ boolean -drown() +drown(void) { const char *pool_of_water; - boolean inpool_ok = FALSE, crawl_ok; - int i, x, y; + boolean inpool_ok = FALSE; + int i; + coordxy x, y; + boolean is_solid = is_waterwall(u.ux, u.uy); feel_newsym(u.ux, u.uy); /* in case Blind, map the water here */ /* happily wading in the same contiguous pool */ if (u.uinwater && is_pool(u.ux - u.dx, u.uy - u.dy) - && (Swimming || Amphibious)) { + && (Swimming || Amphibious || Breathless)) { /* water effects on objects every now and then */ if (!rn2(5)) inpool_ok = TRUE; @@ -3767,18 +5076,18 @@ drown() } if (!u.uinwater) { - You("%s into the %s%c", Is_waterlevel(&u.uz) ? "plunge" : "fall", - hliquid("water"), - Amphibious || Swimming ? '.' : '!'); - if (!Swimming && !Is_waterlevel(&u.uz)) + You("%s into the %s%c", is_solid ? "plunge" : "fall", + waterbody_name(u.ux, u.uy), + (Amphibious || Swimming || Breathless) ? '.' : '!'); + if (!Swimming && !is_solid) You("sink like %s.", Hallucination ? "the Titanic" : "a rock"); } - water_damage_chain(invent, FALSE); + water_damage_chain(gi.invent, FALSE); - if (u.umonnum == PM_GREMLIN && rn2(3)) - (void) split_mon(&youmonst, (struct monst *) 0); - else if (u.umonnum == PM_IRON_GOLEM) { + if (u.umonnum == PM_GREMLIN && rn2(3)) { + (void) split_mon(&gy.youmonst, (struct monst *) 0); + } else if (u.umonnum == PM_IRON_GOLEM) { You("rust!"); i = Maybe_Half_Phys(d(2, 6)); if (u.mhmax > i) @@ -3794,8 +5103,8 @@ drown() unleash_all(); } - if (Amphibious || Swimming) { - if (Amphibious) { + if (Amphibious || Breathless || Swimming) { + if (Amphibious || Breathless) { if (flags.verbose) pline("But you aren't drowning."); if (!Is_waterlevel(&u.uz)) { @@ -3810,15 +5119,15 @@ drown() placebc(); } vision_recalc(2); /* unsee old position */ - u.uinwater = 1; + set_uinwater(1); /* u.uinwater = 1 */ under_water(1); - vision_full_recalc = 1; + gv.vision_full_recalc = 1; return FALSE; } - if ((Teleportation || can_teleport(youmonst.data)) && !Unaware + if ((Teleportation || can_teleport(gy.youmonst.data)) && !Unaware && (Teleport_control || rn2(3) < Luck + 2)) { You("attempt a teleport spell."); /* utcsri!carroll */ - if (!level.flags.noteleport) { + if (!noteleport_level(&gy.youmonst)) { (void) dotele(FALSE); if (!is_pool(u.ux, u.uy)) return TRUE; @@ -3830,8 +5139,6 @@ drown() if (!is_pool(u.ux, u.uy)) return TRUE; } - crawl_ok = FALSE; - x = y = 0; /* lint suppression */ /* if sleeping, wake up now so that we don't crawl out of water while still asleep; we can't do that the same way that waking due to combat is handled; note unmul() clears u.usleep */ @@ -3840,27 +5147,11 @@ drown() /* being doused will revive from fainting */ if (is_fainted()) reset_faint(); - /* can't crawl if unable to move (crawl_ok flag stays false) */ - if (multi < 0 || (Upolyd && !youmonst.data->mmove)) - goto crawl; - /* look around for a place to crawl to */ - for (i = 0; i < 100; i++) { - x = rn1(3, u.ux - 1); - y = rn1(3, u.uy - 1); - if (crawl_destination(x, y)) { - crawl_ok = TRUE; - goto crawl; - } - } - /* one more scan */ - for (x = u.ux - 1; x <= u.ux + 1; x++) - for (y = u.uy - 1; y <= u.uy + 1; y++) - if (crawl_destination(x, y)) { - crawl_ok = TRUE; - goto crawl; - } -crawl: - if (crawl_ok) { + + x = u.ux, y = u.uy; + /* have to be able to move in order to crawl */ + if (gm.multi >= 0 && gy.youmonst.data->mmove + && rnd_nextto_goodpos(&x, &y, &gy.youmonst)) { boolean lost = FALSE; /* time to do some strip-tease... */ boolean succ = Is_waterlevel(&u.uz) ? TRUE : emergency_disrobe(&lost); @@ -3870,95 +5161,156 @@ drown() You("dump some of your gear to lose weight..."); if (succ) { pline("Pheew! That was close."); - teleds(x, y, TRUE); + teleds(x, y, TELEDS_ALLOW_DRAG); return TRUE; } /* still too much weight */ pline("But in vain."); } - u.uinwater = 1; - You("drown."); - for (i = 0; i < 5; i++) { /* arbitrary number of loops */ + set_uinwater(1); /* u.uinwater = 1 */ + urgent_pline("You drown."); + /* first pass is survivable by using up an amulet of life-saving or by + answering no to "Die?" in explore|wizard mode; second pass can only + be survivable via the latter */ + for (i = 0; i < 2; i++) { /* killer format and name are reconstructed every iteration because lifesaving resets them */ pool_of_water = waterbody_name(u.ux, u.uy); - killer.format = KILLED_BY_AN; + svk.killer.format = KILLED_BY_AN; /* avoid "drowned in [a] water" */ if (!strcmp(pool_of_water, "water")) - pool_of_water = "deep water", killer.format = KILLED_BY; - Strcpy(killer.name, pool_of_water); + pool_of_water = "deep water", svk.killer.format = KILLED_BY; + /* avoid "drowned in _a_ limitless water" on Plane of Water */ + else if (!strcmp(pool_of_water, "limitless water")) + svk.killer.format = KILLED_BY; + Strcpy(svk.killer.name, pool_of_water); done(DROWNING); /* oops, we're still alive. better get out of the water. */ - if (safe_teleds(TRUE)) + if (safe_teleds(TELEDS_ALLOW_DRAG | TELEDS_TELEPORT)) break; /* successful life-save */ /* nowhere safe to land; repeat drowning loop... */ pline("You're still drowning."); } - if (u.uinwater) { - u.uinwater = 0; - You("find yourself back %s.", - Is_waterlevel(&u.uz) ? "in an air bubble" : "on land"); - } + + if (u.uinwater) + set_uinwater(0); /* u.uinwater = 0 */ + rescued_from_terrain(DROWNING); return TRUE; } void -drain_en(n) -int n; +drain_en(int n, boolean max_already_drained) { - if (!u.uenmax) { + const char *mesg; + char punct = max_already_drained ? '!' : '.'; + + /* + * FIXME? + * u.uenmax should probably have a higher minimum than 0; + * perhaps u.ulevel or (u.ulevel + 1) / 2 + */ + if (u.uenmax < 1) { /* energy is completely gone */ - You_feel("momentarily lethargic."); + if (u.uen || u.uenmax) { /* paranoia */ + u.uen = u.uenmax = 0; + disp.botl = TRUE; + } + mesg = "momentarily lethargic"; } else { /* throttle further loss a bit when there's not much left to lose */ - if (n > u.uenmax || n > u.ulevel) + if (n > (u.uen + u.uenmax) / 3) n = rnd(n); - You_feel("your magical energy drain away%c", (n > u.uen) ? '!' : '.'); + mesg = "your magical energy drain away"; + if (n > u.uen) + punct = '!'; + u.uen -= n; if (u.uen < 0) { u.uenmax -= rnd(-u.uen); if (u.uenmax < 0) u.uenmax = 0; u.uen = 0; + } else if (u.uen > u.uenmax) { + /* uen might be greater than uenmax if caller reduced uenmax + and then we throttled the loss being applied to current */ + u.uen = u.uenmax; } - context.botl = 1; + disp.botl = TRUE; } + /* after manipulating u.uen,uenmax and setting context.botl, so + that You_feel() -> pline() will update status before the message */ + You_feel("%s%c", mesg, punct); } -/* disarm a trap */ +/* the #untrap command - disarm a trap */ int -dountrap() +dountrap(void) { + if (!could_untrap(TRUE, FALSE)) + return ECMD_OK; + + return untrap(FALSE, 0, 0, (struct obj *) 0) ? ECMD_TIME : ECMD_OK; +} + +/* preliminary checks for dountrap(); also used for autounlock */ +int +could_untrap(boolean verbosely, boolean check_floor) +{ + char buf[BUFSZ]; + + buf[0] = '\0'; if (near_capacity() >= HVY_ENCUMBER) { - pline("You're too strained to do that."); - return 0; + Strcpy(buf, "You're too strained to do that."); + } else if ((nohands(gy.youmonst.data) && !webmaker(gy.youmonst.data)) + || !gy.youmonst.data->mmove) { + Strcpy(buf, "And just how do you expect to do that?"); + } else if (u.ustuck && sticks(gy.youmonst.data)) { + Sprintf(buf, "You'll have to let go of %s first.", mon_nam(u.ustuck)); + } else if (u.ustuck || (welded(uwep) && bimanual(uwep))) { + Sprintf(buf, "Your %s seem to be too busy for that.", + makeplural(body_part(HAND))); + } else if (check_floor && !can_reach_floor(FALSE)) { + /* only checked here for autounlock of chest/box and that will + be !verbosely so precise details of the message don't matter */ + Sprintf(buf, "You can't reach the %s.", surface(u.ux, u.uy)); } - if ((nohands(youmonst.data) && !webmaker(youmonst.data)) - || !youmonst.data->mmove) { - pline("And just how do you expect to do that?"); - return 0; - } else if (u.ustuck && sticks(youmonst.data)) { - pline("You'll have to let go of %s first.", mon_nam(u.ustuck)); - return 0; - } - if (u.ustuck || (welded(uwep) && bimanual(uwep))) { - Your("%s seem to be too busy for that.", makeplural(body_part(HAND))); + if (buf[0]) { + if (verbosely) + pline("%s", buf); return 0; } - return untrap(FALSE); + return 1; } -/* Probability of disabling a trap. Helge Hafting */ -STATIC_OVL int -untrap_prob(ttmp) -struct trap *ttmp; +/* Probability of disabling a trap. Helge Hafting; + Returns 0 for success, non-0 for failure. */ +staticfn int +untrap_prob( + struct trap *ttmp) /* must not be Null */ { int chance = 3; - /* Only spiders know how to deal with webs reliably */ - if (ttmp->ttyp == WEB && !webmaker(youmonst.data)) - chance = 30; + /* non-spiders are less adept at dealing with webs */ + if (ttmp->ttyp == WEB) { + /* this assumes that all fiery artifacts are blades; no need to + make it more complicated unless/until that changes */ + struct obj *wep = (uwep && is_blade(uwep)) ? uwep + : (uswapwep && u.twoweap && is_blade(uswapwep)) + ? uswapwep : NULL; + + /* FIXME? Forcefight of adjacent web works with bare-handed and + martial arts but #untrap of same resorts to !webmaker() chance */ + if (wep && !m_at(ttmp->tx, ttmp->ty)) { + /* primary or secondary weapon is a blade (which includes + daggers but not axes or bladed polearms) */ + if (u_wield_art(ART_STING) || attacks(AD_FIRE, wep)) + chance = 1; + /* else chance stays 3 */ + } else if (!webmaker(gy.youmonst.data)) { + chance = 7; /* 5.0: used to be 30 */ + } + } if (Confusion || Hallucination) chance++; if (Blind) @@ -3968,8 +5320,10 @@ struct trap *ttmp; if (Fumbling) chance *= 2; /* Your own traps are better known than others. */ - if (ttmp && ttmp->madeby_u) + if (ttmp->madeby_u) chance--; + if (Role_if(PM_RANGER) && ttmp->ttyp == BEAR_TRAP && chance <= 3) + return 0; /* always succeeds */ if (Role_if(PM_ROGUE)) { if (rn2(2 * MAXULEV) < u.ulevel) chance--; @@ -3977,18 +5331,21 @@ struct trap *ttmp; chance--; } else if (Role_if(PM_RANGER) && chance > 1) chance--; + if (chance < 1) + chance = 1; return rn2(chance); } /* Replace trap with object(s). Helge Hafting */ void -cnv_trap_obj(otyp, cnt, ttmp, bury_it) -int otyp; -int cnt; -struct trap *ttmp; -boolean bury_it; +cnv_trap_obj( + int otyp, + int cnt, + struct trap *ttmp, + boolean bury_it) { struct obj *otmp = mksobj(otyp, TRUE, FALSE); + struct monst *mtmp; otmp->quan = cnt; otmp->owt = weight(otmp); @@ -4006,26 +5363,50 @@ boolean bury_it; stackobj(otmp); } newsym(ttmp->tx, ttmp->ty); - if (u.utrap && ttmp->tx == u.ux && ttmp->ty == u.uy) + if (u.utrap && u_at(ttmp->tx, ttmp->ty)) reset_utrap(TRUE); + if (((mtmp = m_at(ttmp->tx, ttmp->ty)) != 0) && mtmp->mtrapped) + mtmp->mtrapped = 0; deltrap(ttmp); } +/* whether moving to a trap location is moving "into" the trap or "onto" it */ +boolean +into_vs_onto(int traptype) +{ + switch (traptype) { + case BEAR_TRAP: + case PIT: + case SPIKED_PIT: + case HOLE: + case TELEP_TRAP: + case LEVEL_TELEP: + case MAGIC_PORTAL: + case WEB: + return TRUE; + } + return FALSE; +} + /* while attempting to disarm an adjacent trap, we've fallen into it */ -STATIC_OVL void -move_into_trap(ttmp) -struct trap *ttmp; +staticfn void +move_into_trap(struct trap *ttmp) { int bc = 0; - xchar x = ttmp->tx, y = ttmp->ty, bx, by, cx, cy; + coordxy x = ttmp->tx, y = ttmp->ty, bx, by, cx, cy; boolean unused; bx = by = cx = cy = 0; /* lint suppression */ - /* we know there's no monster in the way, and we're not trapped */ - if (!Punished - || drag_ball(x, y, &bc, &bx, &by, &cx, &cy, &unused, TRUE)) { + /* we know there's no monster in the way and we're not trapped, but + need to make sure the move is not diagonally into or out of a + doorway; the sgn() calls are redundant since ttmp is adjacent */ + if (test_move(u.ux, u.uy, sgn(x - u.ux), sgn(y - u.uy), TEST_MOVE) + && (!Punished + || drag_ball(x, y, &bc, &bx, &by, &cx, &cy, &unused, TRUE))) { + /* move hero and update map */ u.ux0 = u.ux, u.uy0 = u.uy; - u.ux = x, u.uy = y; + /* set u.ux,u.uy and u.usteed->mx,my plus handle CLIPPING */ + u_on_newpos(x, y); u.umoved = TRUE; newsym(u.ux0, u.uy0); vision_recalc(1); @@ -4047,17 +5428,19 @@ struct trap *ttmp; if ((ttmp = t_at(u.ux, u.uy)) != 0) ttmp->tseen = 1; exercise(A_WIS, FALSE); + } else { + /* caller has just printed "Whoops..." so if hero is prevented from + moving, a followup message is needed */ + pline("Fortunately, you don't move %s it.", + into_vs_onto(ttmp->ttyp) ? "into" : "onto"); } } -/* 0: doesn't even try - * 1: tries and fails - * 2: succeeds - */ -STATIC_OVL int -try_disarm(ttmp, force_failure) -struct trap *ttmp; -boolean force_failure; +/* 0: doesn't even try; 1: tries and fails; 2: succeeds */ +staticfn int +try_disarm( + struct trap *ttmp, + boolean force_failure) { struct monst *mtmp = m_at(ttmp->tx, ttmp->ty); int ttype = ttmp->ttyp; @@ -4075,13 +5458,12 @@ boolean force_failure; return 0; } /* duplicate tight-space checks from test_move */ - if (u.dx && u.dy && bad_rock(youmonst.data, u.ux, ttmp->ty) - && bad_rock(youmonst.data, ttmp->tx, u.uy)) { - if ((invent && (inv_weight() + weight_cap() > 600)) - || bigmonst(youmonst.data)) { + if (u.dx && u.dy && bad_rock(gy.youmonst.data, u.ux, ttmp->ty) + && bad_rock(gy.youmonst.data, ttmp->tx, u.uy)) { + if ((gi.invent && (inv_weight() + weight_cap() > WT_TOOMUCH_DIAGONAL)) + || bigmonst(gy.youmonst.data)) { /* don't allow untrap if they can't get thru to it */ - You("are unable to reach the %s!", - defsyms[trap_to_defsym(ttype)].explanation); + You("are unable to reach the %s!", trapname(ttype, FALSE)); return 0; } } @@ -4090,8 +5472,7 @@ boolean force_failure; if (u.usteed && P_SKILL(P_RIDING) < P_BASIC) rider_cant_reach(); else - You("are unable to reach the %s!", - defsyms[trap_to_defsym(ttype)].explanation); + You("are unable to reach the %s!", trapname(ttype, FALSE)); return 0; } @@ -4107,19 +5488,25 @@ boolean force_failure; if (DEADMONSTER(mtmp)) killed(mtmp); } else if (ttype == WEB) { - if (!webmaker(youmonst.data)) { - struct trap *ttmp2 = maketrap(u.ux, u.uy, WEB); - - if (ttmp2) { - pline_The( - "webbing sticks to you. You're caught too!"); - dotrap(ttmp2, NOWEBMSG); - if (u.usteed && u.utrap) { - /* you, not steed, are trapped */ - dismount_steed(DISMOUNT_FELL); - } + struct trap *ttmp2 = t_at(u.ux, u.uy); + + if (!webmaker(gy.youmonst.data) + /* don't always try to spread the web */ + && !rn2(3) + /* is there already a trap at hero's spot? + if so, don't clobber it with spreading web */ + && (ttmp2 + ? (ttmp2->ttyp == WEB) + /* make a new web to trap hero in */ + : (ttmp2 = maketrap(u.ux, u.uy, WEB)) != 0)) { + pline_The("web sticks to you. You're caught too!"); + dotrap(ttmp2, NOWEBMSG); + if (u.usteed && u.utrap) { + /* you, not steed, are trapped */ + dismount_steed(DISMOUNT_FELL); } - } else + } + if (mtmp->mtrapped) pline("%s remains entangled.", Monnam(mtmp)); } } else if (under_u) { @@ -4131,7 +5518,7 @@ boolean force_failure; } else { pline("%s %s is difficult to %s.", ttmp->madeby_u ? "Your" : under_u ? "This" : "That", - defsyms[trap_to_defsym(ttype)].explanation, + trapname(ttype, FALSE), (ttype == WEB) ? "remove" : "disarm"); } return 1; @@ -4139,21 +5526,20 @@ boolean force_failure; return 2; } -STATIC_OVL void -reward_untrap(ttmp, mtmp) -struct trap *ttmp; -struct monst *mtmp; +staticfn void +reward_untrap(struct trap *ttmp, struct monst *mtmp) { if (!ttmp->madeby_u) { - if (rnl(10) < 8 && !mtmp->mpeaceful && !mtmp->msleeping + if (rnl(10) < 8 && !mtmp->mpeaceful && !helpless(mtmp) && !mtmp->mfrozen && !mindless(mtmp->data) + && !unique_corpstat(mtmp->data) && mtmp->data->mlet != S_HUMAN) { mtmp->mpeaceful = 1; set_malign(mtmp); /* reset alignment */ pline("%s is grateful.", Monnam(mtmp)); } - /* Helping someone out of a trap is a nice thing to do, - * A lawful may be rewarded, but not too often. */ + /* Helping someone out of a trap is a nice thing to do. + A lawful may be rewarded, but not too often. */ if (!rn2(3) && !rnl(8) && u.ualign.type == A_LAWFUL) { adjalign(1); You_feel("that you did the right thing."); @@ -4161,11 +5547,13 @@ struct monst *mtmp; } } -STATIC_OVL int -disarm_holdingtrap(ttmp) /* Helge Hafting */ -struct trap *ttmp; +/* Help a monster out of a bear trap or web, or if no monster is + present, disarm a bear trap or destroy a web. Helge Hafting */ +staticfn int +disarm_holdingtrap(struct trap *ttmp) { struct monst *mtmp; + const char *which = the_your[ttmp->madeby_u]; int fails = try_disarm(ttmp, FALSE); if (fails < 2) @@ -4177,26 +5565,33 @@ struct trap *ttmp; There's no need for a cockatrice test, only the trap is touched */ if ((mtmp = m_at(ttmp->tx, ttmp->ty)) != 0) { mtmp->mtrapped = 0; - You("remove %s %s from %s.", the_your[ttmp->madeby_u], - (ttmp->ttyp == BEAR_TRAP) ? "bear trap" : "webbing", - mon_nam(mtmp)); + You("extract %s from %s %s.", mon_nam(mtmp), + which, (ttmp->ttyp == BEAR_TRAP) ? "bear trap" : "web"); reward_untrap(ttmp, mtmp); - } else { - if (ttmp->ttyp == BEAR_TRAP) { - You("disarm %s bear trap.", the_your[ttmp->madeby_u]); - cnv_trap_obj(BEARTRAP, 1, ttmp, FALSE); - } else /* if (ttmp->ttyp == WEB) */ { - You("succeed in removing %s web.", the_your[ttmp->madeby_u]); - deltrap(ttmp); - } + } else if (ttmp->ttyp == BEAR_TRAP) { + You("disarm %s bear trap.", which); + cnv_trap_obj(BEARTRAP, 1, ttmp, FALSE); + } else if (ttmp->ttyp == WEB) { + struct obj *wep = (uwep && is_blade(uwep)) ? uwep + : (uswapwep && u.twoweap && is_blade(uswapwep)) + ? uswapwep : NULL; + + if (wep && wep->oartifact + && (u_wield_art(ART_STING) || attacks(AD_FIRE, wep))) + pline("%s %s through %s web!", bare_artifactname(uwep), + u_wield_art(ART_STING) ? "cuts" : "burns", which); + else if (wep) + You("cut through %s web.", which); + else + You("succeed in removing %s web.", which); + deltrap(ttmp); } newsym(u.ux + u.dx, u.uy + u.dy); return 1; } -STATIC_OVL int -disarm_landmine(ttmp) /* Helge Hafting */ -struct trap *ttmp; +staticfn int +disarm_landmine(struct trap *ttmp) /* Helge Hafting */ { int fails = try_disarm(ttmp, FALSE); @@ -4207,20 +5602,38 @@ struct trap *ttmp; return 1; } -/* getobj will filter down to cans of grease and known potions of oil */ -static NEARDATA const char oil[] = { ALL_CLASSES, TOOL_CLASS, POTION_CLASS, - 0 }; +/* getobj callback for object to disarm a squeaky board with */ +staticfn int +unsqueak_ok(struct obj *obj) +{ + if (!obj) + return GETOBJ_EXCLUDE; + + if (obj->otyp == CAN_OF_GREASE) + return GETOBJ_SUGGEST; + + if (obj->otyp == POT_OIL && obj->dknown + && objects[POT_OIL].oc_name_known) + return GETOBJ_SUGGEST; + + /* downplay all other potions, including unidentified oil + * Potential extension: if oil is known, skip this and exclude all other + * potions. */ + if (obj->oclass == POTION_CLASS) + return GETOBJ_DOWNPLAY; + + return GETOBJ_EXCLUDE; +} /* it may not make much sense to use grease on floor boards, but so what? */ -STATIC_OVL int -disarm_squeaky_board(ttmp) -struct trap *ttmp; +staticfn int +disarm_squeaky_board(struct trap *ttmp) { struct obj *obj; boolean bad_tool; int fails; - obj = getobj(oil, "untrap with"); + obj = getobj("untrap with", unsqueak_ok, GETOBJ_PROMPT); if (!obj) return 0; @@ -4247,10 +5660,8 @@ struct trap *ttmp; } /* removes traps that shoot arrows, darts, etc. */ -STATIC_OVL int -disarm_shooting_trap(ttmp, otyp) -struct trap *ttmp; -int otyp; +staticfn int +disarm_shooting_trap(struct trap *ttmp, int otyp) { int fails = try_disarm(ttmp, FALSE); @@ -4261,18 +5672,15 @@ int otyp; return 1; } -/* Is the weight too heavy? - * Formula as in near_capacity() & check_capacity() */ -STATIC_OVL int -try_lift(mtmp, ttmp, wt, stuff) -struct monst *mtmp; -struct trap *ttmp; -int wt; -boolean stuff; +/* trying to #untrap a monster from a pit; is the weight too heavy? */ +staticfn int +try_lift( + struct monst *mtmp, /* trapped monster */ + struct trap *ttmp, /* pit, possibly made by hero, or spiked pit */ + int xtra_wt, /* monster (corpse weight) + (stuff ? minvent weight : 0) */ + boolean stuff) /* False: monster w/o minvent; True: w/ minvent */ { - int wc = weight_cap(); - - if (((wt * 2) / wc) >= HVY_ENCUMBER) { + if (calc_capacity(xtra_wt) >= HVY_ENCUMBER) { pline("%s is %s for you to lift.", Monnam(mtmp), stuff ? "carrying too much" : "too heavy"); if (!ttmp->madeby_u && !mtmp->mpeaceful && mtmp->mcanmove @@ -4288,12 +5696,12 @@ boolean stuff; } /* Help trapped monster (out of a (spiked) pit) */ -STATIC_OVL int -help_monster_out(mtmp, ttmp) -struct monst *mtmp; -struct trap *ttmp; +staticfn int +help_monster_out( + struct monst *mtmp, + struct trap *ttmp) { - int wt; + int xtra_wt; struct obj *otmp; boolean uprob; @@ -4315,7 +5723,7 @@ struct trap *ttmp; return 1; /* Will our hero succeed? */ - if ((uprob = untrap_prob(ttmp)) && !mtmp->msleeping && mtmp->mcanmove) { + if ((uprob = untrap_prob(ttmp)) != 0 && !helpless(mtmp)) { You("try to reach out your %s, but %s backs away skeptically.", makeplural(body_part(ARM)), mon_nam(mtmp)); return 1; @@ -4323,16 +5731,17 @@ struct trap *ttmp; /* is it a cockatrice?... */ if (touch_petrifies(mtmp->data) && !uarmg && !Stone_resistance) { - You("grab the trapped %s using your bare %s.", mtmp->data->mname, - makeplural(body_part(HAND))); + const char *mtmp_pmname = mon_pmname(mtmp); - if (poly_when_stoned(youmonst.data) && polymon(PM_STONE_GOLEM)) { + You("grab the trapped %s using your bare %s.", + mtmp_pmname, makeplural(body_part(HAND))); + + if (poly_when_stoned(gy.youmonst.data) && polymon(PM_STONE_GOLEM)) { display_nhwindow(WIN_MESSAGE, FALSE); } else { char kbuf[BUFSZ]; - Sprintf(kbuf, "trying to help %s out of a pit", - an(mtmp->data->mname)); + Sprintf(kbuf, "trying to help %s out of a pit", an(mtmp_pmname)); instapetrify(kbuf); return 1; } @@ -4361,15 +5770,18 @@ struct trap *ttmp; } /* is the monster too heavy? */ - wt = inv_weight() + mtmp->data->cwt; - if (!try_lift(mtmp, ttmp, wt, FALSE)) + xtra_wt = mtmp->data->cwt; + if (!try_lift(mtmp, ttmp, xtra_wt, FALSE)) return 1; - /* is the monster with inventory too heavy? */ - for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj) - wt += otmp->owt; - if (!try_lift(mtmp, ttmp, wt, TRUE)) - return 1; + /* monster without its inventory isn't too heavy; if it carries + anything, include that minvent weight and check again */ + if (mtmp->minvent) { + for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj) + xtra_wt += otmp->owt; + if (!try_lift(mtmp, ttmp, xtra_wt, TRUE)) + return 1; + } You("pull %s out of the pit.", mon_nam(mtmp)); mtmp->mtrapped = 0; @@ -4378,50 +5790,121 @@ struct trap *ttmp; return 1; } +staticfn void +disarm_box(struct obj *box, boolean force, boolean confused) +{ + if (box->otrapped) { + int ch = ACURR(A_DEX) + u.ulevel; + + if (Role_if(PM_ROGUE)) + ch *= 2; + if (!force && (confused || Fumbling + || rnd(75 + level_difficulty() / 2) > ch)) { + (void) chest_trap(box, FINGER, TRUE); + /* 'box' might be gone now */ + } else { + You("disarm it!"); + box->otrapped = 0; + box->tknown = 1; + more_experienced(8, 0); + newexplevel(); + } + exercise(A_DEX, TRUE); + } else { + pline("That %s was not trapped.", xname(box)); + box->tknown = 0; + } +} + +/* check a particular container for a trap and optionally disarm it */ +staticfn void +untrap_box( + struct obj *box, + boolean force, + boolean confused) +{ + if ((box->otrapped + && (force || (!confused && rn2(MAXULEV + 1 - u.ulevel) < 10))) + || box->tknown + || (!force && confused && !rn2(3))) { + if (!(box->tknown && box->dknown)) + You("find a trap on %s!", the(xname(box))); + else + pline("There's a trap on %s.", the(xname(box))); + box->tknown = 1; + observe_object(box); + if (!confused) + exercise(A_WIS, TRUE); + + if (ynq("Disarm it?") == 'y') + disarm_box(box, force, confused); + } else { + You("find no traps on %s.", the(xname(box))); + } +} + +/* hero is able to attempt untrap, so do so */ int -untrap(force) -boolean force; +untrap( + boolean force, + coordxy rx, coordxy ry, + struct obj *container) { - register struct obj *otmp; - register int x, y; + struct obj *otmp; + coordxy x, y; int ch; struct trap *ttmp; struct monst *mtmp; const char *trapdescr; boolean here, useplural, deal_with_floor_trap, confused = (Confusion || Hallucination), - trap_skipped = FALSE; + trap_skipped = FALSE, autounlock_door = FALSE; int boxcnt = 0; char the_trap[BUFSZ], qbuf[QBUFSZ]; - if (!getdir((char *) 0)) - return 0; - x = u.ux + u.dx; - y = u.uy + u.dy; + /* 'force' is true for #invoke; if carrying MKoT, make it be true + for #untrap or autounlock */ + if (!force && has_magic_key(&gy.youmonst)) + force = TRUE; + + if (!rx && !container) { + /* usual case */ + if (!getdir((char *) 0)) + return 0; + x = u.ux + u.dx; + y = u.uy + u.dy; + } else { + /* autounlock's untrap; skip most prompting */ + if (container) { + untrap_box(container, force, confused); + return 1; + } + /* levl[rx][ry] is a locked or trapped door */ + x = rx, y = ry; + autounlock_door = TRUE; + } if (!isok(x, y)) { pline_The("perils lurking there are beyond your grasp."); return 0; } - /* 'force' is true for #invoke; make it be true for #untrap if - carrying MKoT */ - if (!force && has_magic_key(&youmonst)) - force = TRUE; ttmp = t_at(x, y); if (ttmp && !ttmp->tseen) ttmp = 0; - trapdescr = ttmp ? defsyms[trap_to_defsym(ttmp->ttyp)].explanation : 0; - here = (x == u.ux && y == u.uy); /* !u.dx && !u.dy */ + trapdescr = ttmp ? trapname(ttmp->ttyp, FALSE) : 0; + here = u_at(x, y); /* !u.dx && !u.dy */ if (here) /* are there are one or more containers here? */ - for (otmp = level.objects[x][y]; otmp; otmp = otmp->nexthere) + for (otmp = svl.level.objects[x][y]; otmp; otmp = otmp->nexthere) if (Is_box(otmp)) { if (++boxcnt > 1) break; } deal_with_floor_trap = can_reach_floor(FALSE); - if (!deal_with_floor_trap) { + if (autounlock_door) { + ; /* skip a bunch */ + } else if (!deal_with_floor_trap) { *the_trap = '\0'; if (ttmp) Strcat(the_trap, an(trapdescr)); @@ -4449,11 +5932,14 @@ boolean force; trap_skipped = TRUE; deal_with_floor_trap = FALSE; } else { - Sprintf( - qbuf, "There %s and %s here. %s %s?", - (boxcnt == 1) ? "is a container" : "are containers", - an(trapdescr), - (ttmp->ttyp == WEB) ? "Remove" : "Disarm", the_trap); + Snprintf(qbuf, sizeof(qbuf), + "There %s and %s here. %s %s?", + (boxcnt == 1) ? "is a container" + : "are containers", + an(trapdescr), + (ttmp->ttyp == WEB) ? "Remove" + : "Disarm", + the_trap); switch (ynq(qbuf)) { case 'q': return 0; @@ -4467,7 +5953,7 @@ boolean force; if (deal_with_floor_trap) { if (u.utrap) { You("cannot deal with %s while trapped%s!", the_trap, - (x == u.ux && y == u.uy) ? " in it" : ""); + u_at(x, y) ? " in it" : ""); return 1; } if ((mtmp = m_at(x, y)) != 0 @@ -4507,67 +5993,55 @@ boolean force; } /* end if */ if (boxcnt) { - for (otmp = level.objects[x][y]; otmp; otmp = otmp->nexthere) - if (Is_box(otmp)) { + /* 5.0: this used to allow searching for traps on multiple + containers on the same move and needed to keep track of + whether any had been found but not attempted to untrap; + now at most one per move may be checked and we only + continue on to door handling if they are all declined */ + for (otmp = svl.level.objects[x][y]; otmp; otmp = otmp->nexthere) { + if (!Is_box(otmp)) + continue; + if (otmp->tknown && otmp->dknown) + (void) safe_qbuf(qbuf, "Disarm this ", NULL, + otmp, xname, ansimpleoname, "a box"); + else (void) safe_qbuf(qbuf, "There is ", " here. Check it for traps?", otmp, doname, ansimpleoname, "a box"); - switch (ynq(qbuf)) { + switch (ynq(qbuf)) { case 'q': return 0; - case 'n': - continue; - } - - if ((otmp->otrapped - && (force || (!confused - && rn2(MAXULEV + 1 - u.ulevel) < 10))) - || (!force && confused && !rn2(3))) { - You("find a trap on %s!", the(xname(otmp))); - if (!confused) - exercise(A_WIS, TRUE); - - switch (ynq("Disarm it?")) { - case 'q': - return 1; - case 'n': - trap_skipped = TRUE; - continue; - } - - if (otmp->otrapped) { - exercise(A_DEX, TRUE); - ch = ACURR(A_DEX) + u.ulevel; - if (Role_if(PM_ROGUE)) - ch *= 2; - if (!force && (confused || Fumbling - || rnd(75 + level_difficulty() / 2) - > ch)) { - (void) chest_trap(otmp, FINGER, TRUE); - } else { - You("disarm it!"); - otmp->otrapped = 0; - } - } else - pline("That %s was not trapped.", xname(otmp)); - return 1; - } else { - You("find no traps on %s.", the(xname(otmp))); - return 1; + case 'y': + if (otmp->tknown && otmp->dknown) + disarm_box(otmp, force, confused); + else + untrap_box(otmp, force, confused); + return 1; /* even for 'no' at "Disarm it?" prompt */ } - } - - You(trap_skipped ? "find no other traps here." - : "know of no traps here."); - return 0; + /* 'n' => continue to next box */ + } + There("are no other chests or boxes here."); } if (stumble_on_door_mimic(x, y)) return 1; - } /* deal_with_floor_trap */ - /* doors can be manipulated even while levitating/unskilled riding */ + /* + * Doors can be manipulated even while levitating/unskilled riding. + * + * Ordinarily there won't be a closed or locked door at the same + * location as a floor trap or a container. However, there could + * be a container at a closed/locked door spot if it was dropped + * there by a monster or poly'd hero with Passes_walls capability, + * and poly'd hero could move onto that spot and attempt #untrap + * in direction '.' or '>'. We'll get here for that situation if + * player declines to check all containers for traps. + * + * The usual situation is #untrap toward an adjacent closed door. + * No floor trap would be present and any containers would be + * ignored because they're only checked when direction is '.'/'>'. + */ if (!IS_DOOR(levl[x][y].typ)) { if (!trap_skipped) You("know of no traps there."); @@ -4609,6 +6083,8 @@ boolean force; } else { You("disarm it!"); levl[x][y].doormask &= ~D_TRAPPED; + more_experienced(8, 0); + newexplevel(); } } else pline("This door was not trapped."); @@ -4619,17 +6095,18 @@ boolean force; } } -/* for magic unlocking; returns true if targetted monster (which might +/* for magic unlocking; returns true if targeted monster (which might be hero) gets untrapped; the trap remains intact */ boolean -openholdingtrap(mon, noticed) -struct monst *mon; -boolean *noticed; /* set to true iff hero notices the effect; */ -{ /* otherwise left with its previous value intact */ - struct trap *t; +openholdingtrap( + struct monst *mon, + boolean *noticed) /* set to true iff hero notices the effect; + * otherwise left with its previous value intact */ +{ + struct trap *t, tdummy; char buf[BUFSZ], whichbuf[20]; const char *trapdescr = 0, *which = 0; - boolean ishero = (mon == &youmonst); + boolean ishero = (mon == &gy.youmonst); if (!mon) return FALSE; @@ -4639,7 +6116,15 @@ boolean *noticed; /* set to true iff hero notices the effect; */ t = t_at(ishero ? u.ux : mon->mx, ishero ? u.uy : mon->my); if (ishero && u.utrap) { /* all u.utraptype values are holding traps */ + /* there might not be any trap at hero's spot for tt_buriedball; + conversely, there might be an unrelated trap at that spot */ + if (!t) { + t = &tdummy; + (void) memset(t, 0, sizeof *t), t->ntrap = NULL; + /* fallback 't' is now nonNull, t->tseen and t->madeby_u are 0 */ + } which = the_your[(!t || !t->tseen || !t->madeby_u) ? 0 : 1]; + switch (u.utraptype) { case TT_LAVA: trapdescr = "molten lava"; @@ -4655,7 +6140,9 @@ boolean *noticed; /* set to true iff hero notices the effect; */ case TT_BEARTRAP: case TT_PIT: case TT_WEB: - trapdescr = 0; /* use defsyms[].explanation */ + trapdescr = defsyms[(u.utraptype == TT_WEB) ? S_web + : (u.utraptype == TT_PIT) ? S_pit + : S_bear_trap].explanation; break; default: /* lint suppression in case 't' is unexpectedly Null @@ -4667,13 +6154,13 @@ boolean *noticed; /* set to true iff hero notices the effect; */ /* if no trap here or it's not a holding trap, we're done */ if (!t || (t->ttyp != BEAR_TRAP && t->ttyp != WEB)) return FALSE; + trapdescr = trapname(t->ttyp, FALSE); } - - if (!trapdescr) - trapdescr = defsyms[trap_to_defsym(t->ttyp)].explanation; + assert(t != NULL); if (!which) which = t->tseen ? the_your[t->madeby_u] - : index(vowels, *trapdescr) ? "an" : "a"; + : strchr(vowels, *trapdescr) ? "an" : "a"; + assert(which != 0); if (*which) which = strcat(strcpy(whichbuf, which), " "); @@ -4681,13 +6168,19 @@ boolean *noticed; /* set to true iff hero notices the effect; */ if (!u.utrap) return FALSE; *noticed = TRUE; - if (u.usteed) - Sprintf(buf, "%s is", noit_Monnam(u.usteed)); - else + if (!u.usteed) Strcpy(buf, "You are"); - reset_utrap(TRUE); - vision_full_recalc = 1; /* vision limits can change (pit escape) */ + else if (u.utraptype == TT_BURIEDBALL) + Sprintf(buf, "You and %s are", y_monnam(u.usteed)); + else + Sprintf(buf, "%s is", noit_Monnam(u.usteed)); + /* give release message before untrap in case it triggers a message */ pline("%s released from %s%s.", buf, which, trapdescr); + /* might float up if Levitation is being unblocked */ + gv.vision_full_recalc = 1; /* vision limits can change (pit escape) */ + reset_utrap(TRUE); + if (gv.vision_full_recalc) + vision_recalc(0); } else { if (!mon->mtrapped) return FALSE; @@ -4705,22 +6198,23 @@ boolean *noticed; /* set to true iff hero notices the effect; */ pline("%s%s opens.", upstart(strcpy(buf, which)), trapdescr); } /* might pacify monster if adjacent */ - if (rn2(2) && distu(mon->mx, mon->my) <= 2) + if (rn2(2) && m_next2u(mon)) reward_untrap(t, mon); } return TRUE; } -/* for magic locking; returns true if targetted monster (which might +/* for magic locking; returns true if targeted monster (which might be hero) gets hit by a trap (might avoid actually becoming trapped) */ boolean -closeholdingtrap(mon, noticed) -struct monst *mon; -boolean *noticed; /* set to true iff hero notices the effect; */ -{ /* otherwise left with its previous value intact */ +closeholdingtrap( + struct monst *mon, + boolean *noticed) /* set to true iff hero notices the effect; + * otherwise left with its previous value intact */ +{ struct trap *t; unsigned dotrapflags; - boolean ishero = (mon == &youmonst), result; + boolean ishero = (mon == &gy.youmonst), result; if (!mon) return FALSE; @@ -4739,9 +6233,7 @@ boolean *noticed; /* set to true iff hero notices the effect; */ /* dotrap calls mintrap when mounted hero encounters a web */ if (u.usteed) dotrapflags |= NOWEBMSG; - ++force_mintrap; - dotrap(t, dotrapflags); - --force_mintrap; + dotrap(t, dotrapflags | FORCETRAP); result = (u.utrap != 0); } else { if (mon->mtrapped) @@ -4749,23 +6241,21 @@ boolean *noticed; /* set to true iff hero notices the effect; */ /* you notice it if you see the trap close/tremble/whatever or if you sense the monster who becomes trapped */ *noticed = cansee(t->tx, t->ty) || canspotmon(mon); - ++force_mintrap; - result = (mintrap(mon) != 0); - --force_mintrap; + result = (mintrap(mon, FORCETRAP) != Trap_Effect_Finished); } return result; } -/* for magic unlocking; returns true if targetted monster (which might +/* for magic unlocking; returns true if targeted monster (which might be hero) gets hit by a trap (target might avoid its effect) */ boolean -openfallingtrap(mon, trapdoor_only, noticed) -struct monst *mon; -boolean trapdoor_only; -boolean *noticed; /* set to true iff hero notices the effect; */ -{ /* otherwise left with its previous value intact */ +openfallingtrap( + struct monst *mon, + boolean trapdoor_only, + boolean *noticed) /* set to true iff hero notices the effect; */ +{ /* otherwise left with its previous value intact */ struct trap *t; - boolean ishero = (mon == &youmonst), result; + boolean ishero = (mon == &gy.youmonst), result; if (!mon) return FALSE; @@ -4792,22 +6282,21 @@ boolean *noticed; /* set to true iff hero notices the effect; */ *noticed = cansee(t->tx, t->ty) || canspotmon(mon); /* monster will be angered; mintrap doesn't handle that */ wakeup(mon, TRUE); - ++force_mintrap; - result = (mintrap(mon) != 0); - --force_mintrap; + result = (mintrap(mon, FORCETRAP) != Trap_Effect_Finished); /* mon might now be on the migrating monsters list */ } return result; } -/* only called when the player is doing something to the chest directly */ +/* only called when the player is doing something to the chest directly; + returns True if chest is destroyed, False if it remains in play */ boolean -chest_trap(obj, bodypart, disarm) -register struct obj *obj; -register int bodypart; -boolean disarm; +chest_trap( + struct obj *obj, + int bodypart, + boolean disarm) { - register struct obj *otmp = obj, *otmp2; + struct obj *otmp = obj, *otmp2; char buf[80]; const char *msg; coord cc; @@ -4815,8 +6304,9 @@ boolean disarm; if (get_obj_location(obj, &cc.x, &cc.y, 0)) /* might be carried */ obj->ox = cc.x, obj->oy = cc.y; + otmp->tknown = 0; /* for xname(); will be set to 1 below */ otmp->otrapped = 0; /* trap is one-shot; clear flag first in case - chest kills you and ends up in bones file */ + * chest kills you and ends up in bones file */ You(disarm ? "set it off!" : "trigger a trap!"); display_nhwindow(WIN_MESSAGE, FALSE); if (Luck > -13 && rn2(13 + Luck) > 7) { /* saved by luck */ @@ -4861,8 +6351,8 @@ boolean disarm; case 21: { struct monst *shkp = 0; long loss = 0L; - boolean costly, insider; - register xchar ox = obj->ox, oy = obj->oy; + boolean costly, insider, chestgone; + coordxy ox = obj->ox, oy = obj->oy; /* the obj location need not be that of player */ costly = (costly_spot(ox, oy) @@ -4878,41 +6368,59 @@ boolean disarm; loss += stolen_value(obj, ox, oy, (boolean) shkp->mpeaceful, TRUE); delete_contents(obj); + /* + * Note: the explosion is taking place at the chest's + * location, not necessarily at the hero's. (Simplest + * case: kicking it from one step away and getting the + * chest_trap() outcome.) + */ /* unpunish() in advance if either ball or chain (or both) is going to be destroyed */ - if (Punished && ((uchain->ox == u.ux && uchain->oy == u.uy) + if (Punished && ((uchain->ox == ox && uchain->oy == oy) || (uball->where == OBJ_FLOOR - && uball->ox == u.ux && uball->oy == u.uy))) + && uball->ox == ox && uball->oy == oy))) unpunish(); - - for (otmp = level.objects[u.ux][u.uy]; otmp; otmp = otmp2) { + /* destroy everything at the spot (the Amulet, the + invocation tools, and Rider corpses will remain intact); + usually the chest will be destroyed along with the stuff at + this spot, but not if it is being carried */ + chestgone = FALSE; + for (otmp = svl.level.objects[ox][oy]; otmp; otmp = otmp2) { otmp2 = otmp->nexthere; if (costly) loss += stolen_value(otmp, otmp->ox, otmp->oy, (boolean) shkp->mpeaceful, TRUE); + if (otmp == obj) + chestgone = TRUE; delobj(otmp); } - wake_nearby(); + wake_nearby(FALSE); losehp(Maybe_Half_Phys(d(6, 6)), buf, KILLED_BY_AN); exercise(A_STR, FALSE); if (costly && loss) { - if (insider) + if (insider) { You("owe %ld %s for objects destroyed.", loss, currency(loss)); - else { + } else { You("caused %ld %s worth of damage!", loss, currency(loss)); make_angry_shk(shkp, ox, oy); } } - return TRUE; + if (chestgone) + return TRUE; + break; /* set tknown and return False */ } /* case 21 */ case 20: case 19: case 18: case 17: pline("A cloud of noxious gas billows from %s.", the(xname(obj))); - poisoned("gas cloud", A_STR, "cloud of poison gas", 15, FALSE); + if (rn2(3)) + poisoned("gas cloud", A_STR, "cloud of poison gas", 15, + FALSE); + else + create_gas_cloud(obj->ox, obj->oy, 1, 8); exercise(A_CON, FALSE); break; case 16: @@ -4932,17 +6440,18 @@ boolean disarm; case 8: case 7: case 6: { - int dmg; + int dmg = d(4, 4), orig_dmg = dmg; You("are jolted by a surge of electricity!"); if (Shock_resistance) { shieldeff(u.ux, u.uy); You("don't seem to be affected."); + monstseesu(M_SEEN_ELEC); dmg = 0; - } else - dmg = d(4, 4); - destroy_item(RING_CLASS, AD_ELEC); - destroy_item(WAND_CLASS, AD_ELEC); + } else { + monstunseesu(M_SEEN_ELEC); + } + (void) destroy_items(&gy.youmonst, AD_ELEC, orig_dmg); if (dmg) losehp(dmg, "electric shock", KILLED_BY_AN); break; @@ -4953,9 +6462,9 @@ boolean disarm; if (!Free_action) { pline("Suddenly you are frozen in place!"); nomul(-d(5, 6)); - multi_reason = "frozen by a trap"; + gm.multi_reason = "frozen by a trap"; exercise(A_DEX, FALSE); - nomovemsg = You_can_move_again; + gn.nomovemsg = You_can_move_again; } else You("momentarily stiffen."); break; @@ -4963,13 +6472,13 @@ boolean disarm; case 1: case 0: pline("A cloud of %s gas billows from %s.", - Blind ? blindgas[rn2(SIZE(blindgas))] : rndcolor(), + Blind ? ROLL_FROM(blindgas) : rndcolor(), the(xname(obj))); if (!Stunned) { if (Hallucination) pline("What a groovy feeling!"); else - You("%s%s...", stagger(youmonst.data, "stagger"), + You("%s%s...", stagger(gy.youmonst.data, "stagger"), Halluc_resistance ? "" : Blind ? " and get dizzy" : " and your vision blurs"); @@ -4985,14 +6494,14 @@ boolean disarm; bot(); /* to get immediate botl re-display */ } + obj->tknown = 1; /* hero knows chest is no longer trapped */ return FALSE; } struct trap * -t_at(x, y) -register int x, y; +t_at(coordxy x, coordxy y) { - register struct trap *trap = ftrap; + struct trap *trap = gf.ftrap; while (trap) { if (trap->tx == x && trap->ty == y) @@ -5002,17 +6511,32 @@ register int x, y; return (struct trap *) 0; } +/* return number of traps of type ttyp on this level */ +int +count_traps(int ttyp) +{ + int ret = 0; + struct trap *trap = gf.ftrap; + + while (trap) { + if ((int) trap->ttyp == ttyp) + ret++; + trap = trap->ntrap; + } + + return ret; +} + void -deltrap(trap) -register struct trap *trap; +deltrap(struct trap *trap) { - register struct trap *ttmp; + struct trap *ttmp; clear_conjoined_pits(trap); - if (trap == ftrap) { - ftrap = ftrap->ntrap; + if (trap == gf.ftrap) { + gf.ftrap = gf.ftrap->ntrap; } else { - for (ttmp = ftrap; ttmp; ttmp = ttmp->ntrap) + for (ttmp = gf.ftrap; ttmp; ttmp = ttmp->ntrap) if (ttmp->ntrap == trap) break; if (!ttmp) @@ -5025,11 +6549,13 @@ register struct trap *trap; } boolean -conjoined_pits(trap2, trap1, u_entering_trap2) -struct trap *trap2, *trap1; -boolean u_entering_trap2; +conjoined_pits( + struct trap *trap2, + struct trap *trap1, + boolean u_entering_trap2) { - int dx, dy, diridx, adjidx; + coordxy dx, dy; + int diridx, adjidx; if (!trap1 || !trap2) return FALSE; @@ -5040,12 +6566,9 @@ boolean u_entering_trap2; return FALSE; dx = sgn(trap2->tx - trap1->tx); dy = sgn(trap2->ty - trap1->ty); - for (diridx = 0; diridx < 8; diridx++) - if (xdir[diridx] == dx && ydir[diridx] == dy) - break; - /* diridx is valid if < 8 */ - if (diridx < 8) { - adjidx = (diridx + 4) % 8; + diridx = xytodir(dx, dy); + if (diridx != DIR_ERR) { + adjidx = DIR_180(diridx); if ((trap1->conjoined & (1 << diridx)) && (trap2->conjoined & (1 << adjidx))) return TRUE; @@ -5053,22 +6576,22 @@ boolean u_entering_trap2; return FALSE; } -STATIC_OVL void -clear_conjoined_pits(trap) -struct trap *trap; +staticfn void +clear_conjoined_pits(struct trap *trap) { - int diridx, adjidx, x, y; + int diridx, adjidx; + coordxy x, y; struct trap *t; if (trap && is_pit(trap->ttyp)) { - for (diridx = 0; diridx < 8; ++diridx) { + for (diridx = 0; diridx < N_DIRS; ++diridx) { if (trap->conjoined & (1 << diridx)) { x = trap->tx + xdir[diridx]; y = trap->ty + ydir[diridx]; if (isok(x, y) && (t = t_at(x, y)) != 0 && is_pit(t->ttyp)) { - adjidx = (diridx + 4) % 8; + adjidx = DIR_180(diridx); t->conjoined &= ~(1 << adjidx); } trap->conjoined &= ~(1 << diridx); @@ -5077,20 +6600,15 @@ struct trap *trap; } } -STATIC_OVL boolean -adj_nonconjoined_pit(adjtrap) -struct trap *adjtrap; +staticfn boolean +adj_nonconjoined_pit(struct trap *adjtrap) { struct trap *trap_with_u = t_at(u.ux0, u.uy0); if (trap_with_u && adjtrap && u.utrap && u.utraptype == TT_PIT && is_pit(trap_with_u->ttyp) && is_pit(adjtrap->ttyp)) { - int idx; - - for (idx = 0; idx < 8; idx++) { - if (xdir[idx] == u.dx && ydir[idx] == u.dy) - return TRUE; - } + if (xytodir(u.dx, u.dy) != DIR_ERR) + return TRUE; } return FALSE; } @@ -5100,16 +6618,16 @@ struct trap *adjtrap; * Mark all neighboring pits as conjoined pits. * (currently not called from anywhere) */ -STATIC_OVL void -join_adjacent_pits(trap) -struct trap *trap; +staticfn void +join_adjacent_pits(struct trap *trap) { struct trap *t; - int diridx, x, y; + int diridx; + coordxy x, y; if (!trap) return; - for (diridx = 0; diridx < 8; ++diridx) { + for (diridx = 0; diridx < N_DIRS; ++diridx) { x = trap->tx + xdir[diridx]; y = trap->ty + ydir[diridx]; if (isok(x, y)) { @@ -5127,11 +6645,10 @@ struct trap *trap; * Returns TRUE if you escaped a pit and are standing on the precipice. */ boolean -uteetering_at_seen_pit(trap) -struct trap *trap; +uteetering_at_seen_pit(struct trap *trap) { return (trap && is_pit(trap->ttyp) && trap->tseen - && trap->tx == u.ux && trap->ty == u.uy + && u_at(trap->tx, trap->ty) && !(u.utrap && u.utraptype == TT_PIT)); } @@ -5140,17 +6657,15 @@ struct trap *trap; * release a trap door */ boolean -uescaped_shaft(trap) -struct trap *trap; +uescaped_shaft(struct trap *trap) { return (trap && is_hole(trap->ttyp) && trap->tseen - && trap->tx == u.ux && trap->ty == u.uy); + && u_at(trap->tx, trap->ty)); } /* Destroy a trap that emanates from the floor. */ boolean -delfloortrap(ttmp) -register struct trap *ttmp; +delfloortrap(struct trap *ttmp) { /* some of these are arbitrary -dlc */ if (ttmp && ((ttmp->ttyp == SQKY_BOARD) || (ttmp->ttyp == BEAR_TRAP) @@ -5160,9 +6675,9 @@ register struct trap *ttmp; || (ttmp->ttyp == TELEP_TRAP) || (ttmp->ttyp == LEVEL_TELEP) || (ttmp->ttyp == WEB) || (ttmp->ttyp == MAGIC_TRAP) || (ttmp->ttyp == ANTI_MAGIC))) { - register struct monst *mtmp; + struct monst *mtmp; - if (ttmp->tx == u.ux && ttmp->ty == u.uy) { + if (u_at(ttmp->tx, ttmp->ty)) { if (u.utraptype != TT_BURIEDBALL) reset_utrap(TRUE); } else if ((mtmp = m_at(ttmp->tx, ttmp->ty)) != 0) { @@ -5176,31 +6691,29 @@ register struct trap *ttmp; /* used for doors (also tins). can be used for anything else that opens. */ void -b_trapped(item, bodypart) -const char *item; -int bodypart; +b_trapped(const char *item, int bodypart) { int lvl = level_difficulty(), dmg = rnd(5 + (lvl < 5 ? lvl : 2 + lvl / 2)); + Soundeffect(se_kaboom, 80); pline("KABOOM!! %s was booby-trapped!", The(item)); - wake_nearby(); + wake_nearby(FALSE); losehp(Maybe_Half_Phys(dmg), "explosion", KILLED_BY_AN); exercise(A_STR, FALSE); - if (bodypart) + if (bodypart != NO_PART) exercise(A_CON, FALSE); make_stunned((HStun & TIMEOUT) + (long) dmg, TRUE); } /* Monster is hit by trap. */ -/* Note: doesn't work if both obj and d_override are null */ -STATIC_OVL boolean -thitm(tlev, mon, obj, d_override, nocorpse) -int tlev; -struct monst *mon; -struct obj *obj; -int d_override; -boolean nocorpse; +staticfn boolean +thitm( + int tlev, /* missile's attack level */ + struct monst *mon, /* target */ + struct obj *obj, /* missile; might be Null */ + int d_override, /* non-zero: force hit for this amount of damage */ + boolean nocorpse) /* True: a trap is completely burning up the target */ { int strike; boolean trapkilled = FALSE; @@ -5217,28 +6730,37 @@ boolean nocorpse; */ if (!strike) { if (obj && cansee(mon->mx, mon->my)) - pline("%s is almost hit by %s!", Monnam(mon), doname(obj)); + pline_mon(mon, "%s is almost hit by %s!", + Monnam(mon), doname(obj)); } else { int dam = 1; + boolean harmless = (obj && stone_missile(obj) + && passes_rocks(mon->data)); if (obj && cansee(mon->mx, mon->my)) - pline("%s is hit by %s!", Monnam(mon), doname(obj)); - if (d_override) + pline_mon(mon, "%s is hit by %s%s", + Monnam(mon), doname(obj), + harmless ? " but is not harmed." : "!"); + if (d_override) { dam = d_override; - else if (obj) { + } else if (obj) { dam = dmgval(obj, mon); if (dam < 1) dam = 1; } - mon->mhp -= dam; - if (DEADMONSTER(mon)) { - int xx = mon->mx, yy = mon->my; - - monkilled(mon, "", nocorpse ? -AD_RBRE : AD_PHYS); - if (DEADMONSTER(mon)) { - newsym(xx, yy); - trapkilled = TRUE; + if (!harmless) { + mon->mhp -= dam; + if (mon->mhp <= 0) { + int xx = mon->mx, yy = mon->my; + + monkilled(mon, "", nocorpse ? -AD_RBRE : AD_PHYS); + if (DEADMONSTER(mon)) { + newsym(xx, yy); + trapkilled = TRUE; + } } + } else { + strike = 0; /* harmless; don't use up the missile */ } } if (obj && (!strike || d_override)) { @@ -5251,30 +6773,39 @@ boolean nocorpse; } boolean -unconscious() +unconscious(void) { - if (multi >= 0) + if (gm.multi >= 0) return FALSE; - return (boolean) (u.usleep - || (nomovemsg - && (!strncmp(nomovemsg, "You awake", 9) - || !strncmp(nomovemsg, "You regain con", 14) - || !strncmp(nomovemsg, "You are consci", 14)))); + return (u.usleep + || (gn.nomovemsg + && (!strncmp(gn.nomovemsg, "You awake", 9) + || !strncmp(gn.nomovemsg, "You regain con", 14) + || !strncmp(gn.nomovemsg, "You are consci", 14)))); } static const char lava_killer[] = "molten lava"; +/* hero enters pool of molten lava; returns True if hero is killed and + then life-saved (with teleport to safe spot), False for other survival; + no return at all if hero dies and isn't life-saved */ boolean -lava_effects() +lava_effects(void) { - register struct obj *obj, *obj2; - int dmg = d(6, 6); /* only applicable for water walking */ + struct obj *obj, *obj2, *nextobj; boolean usurvive, boil_away; + unsigned protect_oid = 0; + int burncount = 0, burnmesgcount = 0; + const int dmg = d(6, 6); /* only applicable for water walking */ + if (iflags.in_lava_effects) { + debugpline0("Skipping recursive lava_effects()."); + return FALSE; + } feel_newsym(u.ux, u.uy); /* in case Blind, map the lava here */ burn_away_slime(); - if (likes_lava(youmonst.data)) + if (likes_lava(gy.youmonst.data)) return FALSE; usurvive = Fire_resistance || (Wwalking && dmg < u.uhp); @@ -5282,32 +6813,59 @@ lava_effects() * A timely interrupt might manage to salvage your life * but not your gear. For scrolls and potions this * will destroy whole stacks, where fire resistant hero - * survivor only loses partial stacks via destroy_item(). + * survivor only loses partial stacks via destroy_items(). * * Flag items to be destroyed before any messages so * that player causing hangup at --More-- won't get an * emergency save file created before item destruction. */ - if (!usurvive) - for (obj = invent; obj; obj = obj->nobj) + if (!usurvive) { + for (obj = gi.invent; obj; obj = nextobj) { + nextobj = obj->nobj; + if (obj->in_use) { /* remove_worn_item() sets in_use */ + /* one item can be protected from burning up [accommodates + steal(AMULET_OF_FLYING) -> remove_worn_item() -> fall + into lava (which happens before item is transferred + from invent to thief->minvent)]; item will still be in + inventory when we return to caller or save bones (or + perform hangup save if that occurs) */ + if (!protect_oid) { + protect_oid = obj->o_id; + obj->in_use = 0; + } else { + impossible( + "lava_effects: '%s' (#%u) is already in use; so is #%u.", + simpleonames(obj), obj->o_id, protect_oid); + } + continue; + } + /* set obj->in_use for items which will be destroyed below */ if ((is_organic(obj) || obj->oclass == POTION_CLASS) && !obj->oerodeproof && objects[obj->otyp].oc_oprop != FIRE_RES && obj->otyp != SCR_FIRE && obj->otyp != SPE_FIREBALL && !obj_resists(obj, 0, 0)) /* for invocation items */ obj->in_use = 1; + } + } /* Check whether we should burn away boots *first* so we know whether to * make the player sink into the lava. Assumption: water walking only * comes from boots. + * (5.0: that assumption is no longer true, but having boots be the first + * thing to come into contact with lava makes sense.) */ - if (uarmf && is_organic(uarmf) && !uarmf->oerodeproof) { + if (uarmf && (uarmf->in_use + || (is_organic(uarmf) && !uarmf->oerodeproof))) { obj = uarmf; pline("%s into flame!", Yobjnam2(obj, "burst")); + ++burnmesgcount; iflags.in_lava_effects++; /* (see above) */ (void) Boots_off(); - useup(obj); + if (obj->o_id != protect_oid) + useup(obj); iflags.in_lava_effects--; + ++burncount; } if (!Fire_resistance) { @@ -5318,7 +6876,7 @@ lava_effects() goto burn_stuff; } } else - You("fall into the %s!", hliquid("lava")); + You("fall into the %s!", waterbody_name(u.ux, u.uy)); usurvive = Lifesaved || discover; if (wizard) @@ -5331,68 +6889,106 @@ lava_effects() here in the outer call (and access stale memory, probably panic) */ iflags.in_lava_effects++; - for (obj = invent; obj; obj = obj2) { + for (obj = gi.invent; obj; obj = obj2) { obj2 = obj->nobj; - /* above, we set in_use for objects which are to be destroyed */ - if (obj->otyp == SPE_BOOK_OF_THE_DEAD && !Blind) { - if (usurvive) + if (obj->o_id == protect_oid) { + /* skip protected item; caller expects to retain access */ + obj->in_use = 1; /* was cleared when setting protect_oid */ + } else if (obj->otyp == SPE_BOOK_OF_THE_DEAD) { + if (usurvive && !Blind) pline("%s glows a strange %s, but remains intact.", The(xname(obj)), hcolor("dark red")); } else if (obj->in_use) { if (obj->owornmask) { - if (usurvive) + if (usurvive) { pline("%s into flame!", Yobjnam2(obj, "burst")); + ++burnmesgcount; + } remove_worn_item(obj, TRUE); } useupall(obj); + ++burncount; } } - - iflags.in_lava_effects--; + if (usurvive && burncount > burnmesgcount) + pline("%s item%s in your inventory %s been destroyed.", + (burnmesgcount > 0) + ? ((burncount - burnmesgcount == 1) ? "Another" : "Other") + : ((burncount == 1) ? "An" : "Some"), + plur(burncount - burnmesgcount), + (burncount - burnmesgcount == 1) ? "has" : "have"); /* s/he died... */ boil_away = (u.umonnum == PM_WATER_ELEMENTAL || u.umonnum == PM_STEAM_VORTEX || u.umonnum == PM_FOG_CLOUD); - for (;;) { + /* burn to death; if hero is life-saved on the first pass, try + to teleport to safety; if that fails, burn all over again */ + for (burncount = 0; burncount < 2; ++burncount) { u.uhp = -1; /* killer format and name are reconstructed every iteration because lifesaving resets them */ - killer.format = KILLED_BY; - Strcpy(killer.name, lava_killer); - You("%s...", boil_away ? "boil away" : "burn to a crisp"); + svk.killer.format = KILLED_BY; + Strcpy(svk.killer.name, lava_killer); + urgent_pline("You %s...", boil_away ? "boil away" + : "burn to a crisp"); done(BURNING); - if (safe_teleds(TRUE)) + if (safe_teleds(TELEDS_ALLOW_DRAG | TELEDS_TELEPORT)) break; /* successful life-save */ /* nowhere safe to land; repeat burning loop */ pline("You're still burning."); } - You("find yourself back on solid %s.", surface(u.ux, u.uy)); + + iflags.in_lava_effects--; + + if (burncount == 2) { + /* life-saved twice (second time must have been due to declining + to die in wizard|explore mode) and failed to be teleported + to safety both times; moveloop() will just drop the hero into + the lava again on next move so take countermeasures to give + the player--or the debug fuzzer--a chance to try something + else instead of just immediately burning up all over again */ + if (!Fire_resistance) + set_itimeout(&HFire_resistance, 5L); + if (!Wwalking) + set_itimeout(&HWwalking, 5L); + goto burn_stuff; + } + rescued_from_terrain(BURNING); + + /* normally done via safe_teleds() -> teleds() -> spoteffects() but + spoteffects() was no-op when called with nonzero in_lava_effects */ + spoteffects(FALSE); /* suppress auto-pickup for this landing... */ + return TRUE; } else if (!Wwalking && (!u.utrap || u.utraptype != TT_LAVA)) { boil_away = !Fire_resistance; /* if not fire resistant, sink_into_lava() will quickly be fatal; hero needs to escape immediately */ - set_utrap((unsigned) (rn1(4, 4) + ((boil_away ? 2 : rn1(4, 12)) << 8)), + set_utrap((unsigned) (rn1(4, 4) + ((boil_away ? 2 + : rn1(4, 12)) << 8)), TT_LAVA); - You("sink into the %s%s!", hliquid("lava"), + You("sink into the %s%s!", waterbody_name(u.ux, u.uy), !boil_away ? ", but it only burns slightly" : " and are about to be immolated"); + if (Fire_resistance) + monstseesu(M_SEEN_FIRE); + else + monstunseesu(M_SEEN_FIRE); if (u.uhp > 1) losehp(!boil_away ? 1 : (u.uhp / 2), lava_killer, KILLED_BY); /* lava damage */ } -burn_stuff: - destroy_item(SCROLL_CLASS, AD_FIRE); - destroy_item(SPBOOK_CLASS, AD_FIRE); - destroy_item(POTION_CLASS, AD_FIRE); + burn_stuff: + (void) destroy_items(&gy.youmonst, AD_FIRE, dmg); + ignite_items(gi.invent); return FALSE; } /* called each turn when trapped in lava */ void -sink_into_lava() +sink_into_lava(void) { static const char sink_deeper[] = "You sink deeper into the lava."; @@ -5413,16 +7009,16 @@ sink_into_lava() u.utrap -= (1 << 8); if (u.utrap < (1 << 8)) { - killer.format = KILLED_BY; - Strcpy(killer.name, "molten lava"); - You("sink below the surface and die."); + svk.killer.format = KILLED_BY; + Strcpy(svk.killer.name, "molten lava"); + urgent_pline("You sink below the surface and die."); burn_away_slime(); /* add insult to injury? */ done(DISSOLVED); /* can only get here via life-saving; try to get away from lava */ reset_utrap(TRUE); /* levitation or flight have become unblocked, otherwise Tport */ if (!Levitation && !Flying) - (void) safe_teleds(TRUE); + (void) safe_teleds(TELEDS_ALLOW_DRAG | TELEDS_TELEPORT); } else if (!u.umoved) { /* can't fully turn into slime while in lava, but might not have it be burned away until you've come awfully close */ @@ -5440,45 +7036,176 @@ sink_into_lava() /* called when something has been done (breaking a boulder, for instance) which entails a luck penalty if performed on a sokoban level */ void -sokoban_guilt() +sokoban_guilt(void) { if (Sokoban) { + u.uconduct.sokocheat++; change_luck(-1); - /* TODO: issue some feedback so that player can learn that whatever - he/she just did is a naughty thing to do in sokoban and should - probably be avoided in future.... - Caveat: doing this might introduce message sequencing issues, - depending upon feedback during the various actions which trigger - Sokoban luck penalties. */ + /* + * TODO: + * Issue some feedback so that player can learn that whatever + * he/she just did is a naughty thing to do in sokoban and + * should probably be avoided in future.... + * + * Caveat: doing this might introduce message sequencing + * issues, depending upon feedback during the various actions + * which trigger Sokoban luck penalties. + */ } } /* called when a trap has been deleted or had its ttyp replaced */ -STATIC_OVL void -maybe_finish_sokoban() +staticfn void +maybe_finish_sokoban(void) { struct trap *t; - if (Sokoban && !in_mklev) { + if (Sokoban && !gi.in_mklev) { /* scan all remaining traps, ignoring any created by the hero; if this level has no more pits or holes, the current sokoban puzzle has been solved */ - for (t = ftrap; t; t = t->ntrap) { + for (t = gf.ftrap; t; t = t->ntrap) { if (t->madeby_u) continue; if (t->ttyp == PIT || t->ttyp == HOLE) break; } if (!t) { + /* for livelog to report the sokoban depth in the way that + players tend to think about it: 1 for entry level, 4 for top */ + int sokonum = svd.dungeons[u.uz.dnum].entry_lev - u.uz.dlevel + 1; + /* we've passed the last trap without finding a pit or hole; clear the sokoban_rules flag so that luck penalties for things like breaking boulders or jumping will no longer be given, and restrictions on diagonal moves are lifted */ - Sokoban = 0; /* clear level.flags.sokoban_rules */ - /* TODO: give some feedback about solving the sokoban puzzle - (perhaps say "congratulations" in Japanese?) */ + Sokoban = 0; /* clear svl.level.flags.sokoban_rules */ + /* + * TODO: give some feedback about solving the sokoban puzzle + * (perhaps say "congratulations" in Japanese?). + */ + + /* log the completion event regardless of whether or not + any normal in-game feedback has just been given */ + livelog_printf(LL_MINORAC | LL_DUMP, + "completed %d%s Sokoban level", + sokonum, ordin(sokonum)); + } + } +} + +/* Return the string name of the trap type passed in, unless the player is + hallucinating, in which case return a random or hallucinatory trap name. */ +const char * +trapname( + int ttyp, + boolean override) /* if True, ignore Hallucination */ +{ + static const char *const halu_trapnames[] = { + /* riffs on actual nethack traps */ + "bottomless pit", "polymorphism trap", "devil teleporter", + "falling boulder trap", "anti-anti-magic field", "weeping gas trap", + "queasy board", "electrified web", "owlbear trap", "sand mine", + "vacillating triangle", + /* some traps found in nethack variants */ + "death trap", "disintegration trap", "ice trap", "monochrome trap", + /* plausible real-life traps */ + "axeblade trap", "pool of boiling oil", "pool of quicksand", + "field of caltrops", "buzzsaw trap", "spiked floor", "revolving wall", + "uneven floor", "finger trap", "jack-in-a-box", "yellow snow", + "booby trap", "rat trap", "poisoned nail", "snare", "whirlpool", + "trip wire", "roach motel (tm)", + /* sci-fi */ + "negative space", "tensor field", "singularity", "imperial fleet", + "black hole", "thermal detonator", "event horizon", + "entoptic phenomenon", + /* miscellaneous suggestions */ + "sweet-smelling gas vent", "phone booth", "exploding runes", + "never-ending elevator", "slime pit", "warp zone", "illusory floor", + "pile of poo", "honey trap", "tourist trap", + "banana peel", "garden rake", "whoopie cushion", "box and stick trap", + "fly trap", "legal trap", "pit of snakes", "pollywog trap", + "slippery slope", "thirst trap", "suntrap", + }; + static char roletrap[33]; /* [17 + 5 + 1] should suffice */ + + if (Hallucination && !override) { + int total_names = TRAPNUM + SIZE(halu_trapnames), + nameidx = rn2_on_display_rng(total_names + 1); + + if (nameidx == total_names) { + boolean fem = Upolyd ? u.mfemale : flags.female; + + /* inspired by "tourist trap" */ + copynchars(roletrap, + rn2(3) ? ((fem && gu.urole.name.f) ? gu.urole.name.f + : gu.urole.name.m) + : rank_of(u.ulevel, Role_switch, fem), + (int) (sizeof roletrap - sizeof " trap")); + Strcat(roletrap, " trap"); + return lcase(roletrap); + } else if (nameidx >= TRAPNUM) { + nameidx -= TRAPNUM; + return halu_trapnames[nameidx]; + } /* else use an actual trap type */ + if (nameidx != NO_TRAP) + ttyp = nameidx; + } + return defsyms[trap_to_defsym(ttyp)].explanation; +} + +/* Ignite ignitable items (limited to light sources) in the given object + chain, due to some external source of fire. The object chain should + be somewhere exposed, like someone's open inventory or the floor. */ +void +ignite_items(struct obj *objchn) +{ + struct obj *obj, *nextobj; + boolean bynexthere = (objchn && objchn->where == OBJ_FLOOR); + + for (obj = objchn; obj; obj = bynexthere ? obj->nexthere : nextobj) { + nextobj = obj->nobj; + /* ignitable items like lamps and candles will catch fire */ + if (!obj->lamplit && !obj->in_use) + catch_lit(obj); + } +} + +void +trap_ice_effects(coordxy x, coordxy y, boolean ice_is_melting) +{ + struct trap *ttmp = t_at(x, y); + + if (ttmp && ice_is_melting) { + struct monst *mtmp; + + if (((mtmp = m_at(x, y)) != 0) && mtmp->mtrapped) + mtmp->mtrapped = 0; + if (ttmp->ttyp == LANDMINE || ttmp->ttyp == BEAR_TRAP) { + /* landmine or bear trap set on top of the ice falls + into the water */ + int otyp = (ttmp->ttyp == LANDMINE) ? LAND_MINE : BEARTRAP; + cnv_trap_obj(otyp, 1, ttmp, TRUE); + } else { + if (!undestroyable_trap(ttmp->ttyp)) + deltrap(ttmp); } } } +/* sanity check traps */ +void +trap_sanity_check(void) +{ + struct trap *ttmp = gf.ftrap; + + while (ttmp) { + if (!isok(ttmp->tx, ttmp->ty)) + impossible("trap sanity: location (%i,%i)", ttmp->tx, ttmp->ty); + if (ttmp->ttyp <= NO_TRAP || ttmp->ttyp >= TRAPNUM) + impossible("trap sanity: type (%i)", ttmp->ttyp); + ttmp = ttmp->ntrap; + } +} + /*trap.c*/ diff --git a/src/u_init.c b/src/u_init.c index 77e7445c1..ac41da92c 100644 --- a/src/u_init.c +++ b/src/u_init.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 u_init.c $NHDT-Date: 1575245094 2019/12/02 00:04:54 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.60 $ */ +/* NetHack 5.0 u_init.c $NHDT-Date: 1769398807 2026/01/25 19:40:07 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.121 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2017. */ /* NetHack may be freely redistributed. See license for details. */ @@ -9,14 +9,27 @@ struct trobj { short trotyp; schar trspe; char trclass; - Bitfield(trquan, 6); - Bitfield(trbless, 2); + char trquan_min; + char trquan_max; + char trbless; }; -STATIC_DCL void FDECL(ini_inv, (struct trobj *)); -STATIC_DCL void FDECL(knows_object, (int)); -STATIC_DCL void FDECL(knows_class, (CHAR_P)); -STATIC_DCL boolean FDECL(restricted_spell_discipline, (int)); +staticfn long trquan(const struct trobj *); +staticfn struct obj *ini_inv_mkobj_filter(int, boolean); +staticfn short ini_inv_obj_substitution(const struct trobj *, + struct obj *) NONNULLPTRS; +staticfn boolean ini_inv_adjust_obj(const struct trobj *, + struct obj *) NONNULLPTRS; +staticfn void ini_inv_use_obj(struct obj *) NONNULLARG1; +staticfn void ini_inv(const struct trobj *) NONNULLARG1; +staticfn void knows_object(int, boolean); +staticfn void knows_class(char); +staticfn void u_init_role(void); +staticfn void u_init_race(void); +staticfn void pauper_reinit(void); +staticfn const struct def_skill *skills_for_role(void); +staticfn void u_init_carry_attr_boost(void); +staticfn boolean restricted_spell_discipline(int); #define UNDEF_TYP 0 #define UNDEF_SPE '\177' @@ -26,179 +39,188 @@ STATIC_DCL boolean FDECL(restricted_spell_discipline, (int)); * Initial inventory for the various roles. */ -static struct trobj Archeologist[] = { +static const struct trobj Archeologist[] = { /* if adventure has a name... idea from tan@uvm-gen */ - { BULLWHIP, 2, WEAPON_CLASS, 1, UNDEF_BLESS }, - { LEATHER_JACKET, 0, ARMOR_CLASS, 1, UNDEF_BLESS }, - { FEDORA, 0, ARMOR_CLASS, 1, UNDEF_BLESS }, - { FOOD_RATION, 0, FOOD_CLASS, 3, 0 }, - { PICK_AXE, UNDEF_SPE, TOOL_CLASS, 1, UNDEF_BLESS }, - { TINNING_KIT, UNDEF_SPE, TOOL_CLASS, 1, UNDEF_BLESS }, - { TOUCHSTONE, 0, GEM_CLASS, 1, 0 }, - { SACK, 0, TOOL_CLASS, 1, 0 }, - { 0, 0, 0, 0, 0 } + { BULLWHIP, 2, WEAPON_CLASS, 1, 1, UNDEF_BLESS }, + { LEATHER_JACKET, 0, ARMOR_CLASS, 1, 1, UNDEF_BLESS }, + { FEDORA, 0, ARMOR_CLASS, 1, 1, UNDEF_BLESS }, + { FOOD_RATION, 0, FOOD_CLASS, 3, 3, 0 }, + { PICK_AXE, UNDEF_SPE, TOOL_CLASS, 1, 1, UNDEF_BLESS }, + { TINNING_KIT, UNDEF_SPE, TOOL_CLASS, 1, 1, UNDEF_BLESS }, + { TOUCHSTONE, 0, GEM_CLASS, 1, 1, 0 }, + { SACK, 0, TOOL_CLASS, 1, 1, 0 }, + { 0, 0, 0, 0, 0, 0 } }; -static struct trobj Barbarian[] = { -#define B_MAJOR 0 /* two-handed sword or battle-axe */ -#define B_MINOR 1 /* matched with axe or short sword */ - { TWO_HANDED_SWORD, 0, WEAPON_CLASS, 1, UNDEF_BLESS }, - { AXE, 0, WEAPON_CLASS, 1, UNDEF_BLESS }, - { RING_MAIL, 0, ARMOR_CLASS, 1, UNDEF_BLESS }, - { FOOD_RATION, 0, FOOD_CLASS, 1, 0 }, - { 0, 0, 0, 0, 0 } +static const struct trobj Barbarian_0[] = { + { TWO_HANDED_SWORD, 0, WEAPON_CLASS, 1, 1, UNDEF_BLESS }, + { AXE, 0, WEAPON_CLASS, 1, 1, UNDEF_BLESS }, + { RING_MAIL, 0, ARMOR_CLASS, 1, 1, UNDEF_BLESS }, + { FOOD_RATION, 0, FOOD_CLASS, 1, 1, 0 }, + { 0, 0, 0, 0, 0, 0 } }; -static struct trobj Cave_man[] = { -#define C_AMMO 2 - { CLUB, 1, WEAPON_CLASS, 1, UNDEF_BLESS }, - { SLING, 2, WEAPON_CLASS, 1, UNDEF_BLESS }, - { FLINT, 0, GEM_CLASS, 15, UNDEF_BLESS }, /* quan is variable */ - { ROCK, 0, GEM_CLASS, 3, 0 }, /* yields 18..33 */ - { LEATHER_ARMOR, 0, ARMOR_CLASS, 1, UNDEF_BLESS }, - { 0, 0, 0, 0, 0 } +static const struct trobj Barbarian_1[] = { + { BATTLE_AXE, 0, WEAPON_CLASS, 1, 1, UNDEF_BLESS }, + { SHORT_SWORD, 0, WEAPON_CLASS, 1, 1, UNDEF_BLESS }, + { RING_MAIL, 0, ARMOR_CLASS, 1, 1, UNDEF_BLESS }, + { FOOD_RATION, 0, FOOD_CLASS, 1, 1, 0 }, + { 0, 0, 0, 0, 0, 0 } }; -static struct trobj Healer[] = { - { SCALPEL, 0, WEAPON_CLASS, 1, UNDEF_BLESS }, - { LEATHER_GLOVES, 1, ARMOR_CLASS, 1, UNDEF_BLESS }, - { STETHOSCOPE, 0, TOOL_CLASS, 1, 0 }, - { POT_HEALING, 0, POTION_CLASS, 4, UNDEF_BLESS }, - { POT_EXTRA_HEALING, 0, POTION_CLASS, 4, UNDEF_BLESS }, - { WAN_SLEEP, UNDEF_SPE, WAND_CLASS, 1, UNDEF_BLESS }, +static const struct trobj Cave_man[] = { + { CLUB, 1, WEAPON_CLASS, 1, 1, UNDEF_BLESS }, + { SLING, 2, WEAPON_CLASS, 1, 1, UNDEF_BLESS }, + { FLINT, 0, GEM_CLASS, 10, 20, UNDEF_BLESS }, + { ROCK, 0, GEM_CLASS, 3, 3, 0 }, /* yields 18..33 */ + { LEATHER_ARMOR, 0, ARMOR_CLASS, 1, 1, UNDEF_BLESS }, + { 0, 0, 0, 0, 0, 0 } +}; +static const struct trobj Healer[] = { + { SCALPEL, 0, WEAPON_CLASS, 1, 1, UNDEF_BLESS }, + { LEATHER_GLOVES, 1, ARMOR_CLASS, 1, 1, UNDEF_BLESS }, + { STETHOSCOPE, 0, TOOL_CLASS, 1, 1, 0 }, + { POT_HEALING, 0, POTION_CLASS, 4, 4, UNDEF_BLESS }, + { POT_EXTRA_HEALING, 0, POTION_CLASS, 4, 4, UNDEF_BLESS }, + { WAN_SLEEP, UNDEF_SPE, WAND_CLASS, 1, 1, UNDEF_BLESS }, /* always blessed, so it's guaranteed readable */ - { SPE_HEALING, 0, SPBOOK_CLASS, 1, 1 }, - { SPE_EXTRA_HEALING, 0, SPBOOK_CLASS, 1, 1 }, - { SPE_STONE_TO_FLESH, 0, SPBOOK_CLASS, 1, 1 }, - { APPLE, 0, FOOD_CLASS, 5, 0 }, - { 0, 0, 0, 0, 0 } + { SPE_HEALING, 0, SPBOOK_CLASS, 1, 1, 1 }, + { SPE_EXTRA_HEALING, 0, SPBOOK_CLASS, 1, 1, 1 }, + { SPE_STONE_TO_FLESH, 0, SPBOOK_CLASS, 1, 1, 1 }, + { APPLE, 0, FOOD_CLASS, 5, 5, 0 }, + { 0, 0, 0, 0, 0, 0 } }; -static struct trobj Knight[] = { - { LONG_SWORD, 1, WEAPON_CLASS, 1, UNDEF_BLESS }, - { LANCE, 1, WEAPON_CLASS, 1, UNDEF_BLESS }, - { RING_MAIL, 1, ARMOR_CLASS, 1, UNDEF_BLESS }, - { HELMET, 0, ARMOR_CLASS, 1, UNDEF_BLESS }, - { SMALL_SHIELD, 0, ARMOR_CLASS, 1, UNDEF_BLESS }, - { LEATHER_GLOVES, 0, ARMOR_CLASS, 1, UNDEF_BLESS }, - { APPLE, 0, FOOD_CLASS, 10, 0 }, - { CARROT, 0, FOOD_CLASS, 10, 0 }, - { 0, 0, 0, 0, 0 } +static const struct trobj Knight[] = { + { LONG_SWORD, 1, WEAPON_CLASS, 1, 1, UNDEF_BLESS }, + { LANCE, 1, WEAPON_CLASS, 1, 1, UNDEF_BLESS }, + { RING_MAIL, 1, ARMOR_CLASS, 1, 1, UNDEF_BLESS }, + { HELMET, 0, ARMOR_CLASS, 1, 1, UNDEF_BLESS }, + { SMALL_SHIELD, 0, ARMOR_CLASS, 1, 1, UNDEF_BLESS }, + { LEATHER_GLOVES, 0, ARMOR_CLASS, 1, 1, UNDEF_BLESS }, + { APPLE, 0, FOOD_CLASS, 10, 10, 0 }, + { CARROT, 0, FOOD_CLASS, 10, 10, 0 }, + { 0, 0, 0, 0, 0, 0 } }; -static struct trobj Monk[] = { -#define M_BOOK 2 - { LEATHER_GLOVES, 2, ARMOR_CLASS, 1, UNDEF_BLESS }, - { ROBE, 1, ARMOR_CLASS, 1, UNDEF_BLESS }, - { UNDEF_TYP, UNDEF_SPE, SPBOOK_CLASS, 1, 1 }, - { UNDEF_TYP, UNDEF_SPE, SCROLL_CLASS, 1, UNDEF_BLESS }, - { POT_HEALING, 0, POTION_CLASS, 3, UNDEF_BLESS }, - { FOOD_RATION, 0, FOOD_CLASS, 3, 0 }, - { APPLE, 0, FOOD_CLASS, 5, UNDEF_BLESS }, - { ORANGE, 0, FOOD_CLASS, 5, UNDEF_BLESS }, +static const struct trobj Monk[] = { + { LEATHER_GLOVES, 2, ARMOR_CLASS, 1, 1, UNDEF_BLESS }, + { ROBE, 1, ARMOR_CLASS, 1, 1, UNDEF_BLESS }, + { UNDEF_TYP, UNDEF_SPE, SCROLL_CLASS, 1, 1, UNDEF_BLESS }, + { POT_HEALING, 0, POTION_CLASS, 3, 3, UNDEF_BLESS }, + { FOOD_RATION, 0, FOOD_CLASS, 3, 3, 0 }, + { APPLE, 0, FOOD_CLASS, 5, 5, UNDEF_BLESS }, + { ORANGE, 0, FOOD_CLASS, 5, 5, UNDEF_BLESS }, /* Yes, we know fortune cookies aren't really from China. They were - * invented by George Jung in Los Angeles, California, USA in 1916. - */ - { FORTUNE_COOKIE, 0, FOOD_CLASS, 3, UNDEF_BLESS }, - { 0, 0, 0, 0, 0 } + invented by George Jung in Los Angeles, California, USA in 1916. */ + { FORTUNE_COOKIE, 0, FOOD_CLASS, 3, 3, UNDEF_BLESS }, + { 0, 0, 0, 0, 0, 0 } }; -static struct trobj Priest[] = { - { MACE, 1, WEAPON_CLASS, 1, 1 }, - { ROBE, 0, ARMOR_CLASS, 1, UNDEF_BLESS }, - { SMALL_SHIELD, 0, ARMOR_CLASS, 1, UNDEF_BLESS }, - { POT_WATER, 0, POTION_CLASS, 4, 1 }, /* holy water */ - { CLOVE_OF_GARLIC, 0, FOOD_CLASS, 1, 0 }, - { SPRIG_OF_WOLFSBANE, 0, FOOD_CLASS, 1, 0 }, - { UNDEF_TYP, UNDEF_SPE, SPBOOK_CLASS, 2, UNDEF_BLESS }, - { 0, 0, 0, 0, 0 } +static const struct trobj Priest[] = { + { MACE, 1, WEAPON_CLASS, 1, 1, 1 }, + { ROBE, 0, ARMOR_CLASS, 1, 1, UNDEF_BLESS }, + { SMALL_SHIELD, 0, ARMOR_CLASS, 1, 1, UNDEF_BLESS }, + { POT_WATER, 0, POTION_CLASS, 4, 4, 1 }, /* holy water */ + { CLOVE_OF_GARLIC, 0, FOOD_CLASS, 1, 1, 0 }, + { SPRIG_OF_WOLFSBANE, 0, FOOD_CLASS, 1, 1, 0 }, + { UNDEF_TYP, UNDEF_SPE, SPBOOK_CLASS, 2, 2, UNDEF_BLESS }, + { 0, 0, 0, 0, 0, 0 } }; -static struct trobj Ranger[] = { -#define RAN_BOW 1 -#define RAN_TWO_ARROWS 2 -#define RAN_ZERO_ARROWS 3 - { DAGGER, 1, WEAPON_CLASS, 1, UNDEF_BLESS }, - { BOW, 1, WEAPON_CLASS, 1, UNDEF_BLESS }, - { ARROW, 2, WEAPON_CLASS, 50, UNDEF_BLESS }, - { ARROW, 0, WEAPON_CLASS, 30, UNDEF_BLESS }, - { CLOAK_OF_DISPLACEMENT, 2, ARMOR_CLASS, 1, UNDEF_BLESS }, - { CRAM_RATION, 0, FOOD_CLASS, 4, 0 }, - { 0, 0, 0, 0, 0 } +static const struct trobj Ranger[] = { + { DAGGER, 1, WEAPON_CLASS, 1, 1, UNDEF_BLESS }, + { BOW, 1, WEAPON_CLASS, 1, 1, UNDEF_BLESS }, + { ARROW, 2, WEAPON_CLASS, 50, 59, UNDEF_BLESS }, + { ARROW, 0, WEAPON_CLASS, 30, 39, UNDEF_BLESS }, + { CLOAK_OF_DISPLACEMENT, 2, ARMOR_CLASS, 1, 1, UNDEF_BLESS }, + { CRAM_RATION, 0, FOOD_CLASS, 4, 4, 0 }, + { 0, 0, 0, 0, 0, 0 } }; -static struct trobj Rogue[] = { -#define R_DAGGERS 1 - { SHORT_SWORD, 0, WEAPON_CLASS, 1, UNDEF_BLESS }, - { DAGGER, 0, WEAPON_CLASS, 10, 0 }, /* quan is variable */ - { LEATHER_ARMOR, 1, ARMOR_CLASS, 1, UNDEF_BLESS }, - { POT_SICKNESS, 0, POTION_CLASS, 1, 0 }, - { LOCK_PICK, 0, TOOL_CLASS, 1, 0 }, - { SACK, 0, TOOL_CLASS, 1, 0 }, - { 0, 0, 0, 0, 0 } +static const struct trobj Rogue[] = { + { SHORT_SWORD, 0, WEAPON_CLASS, 1, 1, UNDEF_BLESS }, + { DAGGER, 0, WEAPON_CLASS, 6, 15, 0 }, + { LEATHER_ARMOR, 1, ARMOR_CLASS, 1, 1, UNDEF_BLESS }, + { POT_SICKNESS, 0, POTION_CLASS, 1, 1, 0 }, + { LOCK_PICK, 0, TOOL_CLASS, 1, 1, 0 }, + { SACK, 0, TOOL_CLASS, 1, 1, 0 }, + { 0, 0, 0, 0, 0, 0 } }; -static struct trobj Samurai[] = { -#define S_ARROWS 3 - { KATANA, 0, WEAPON_CLASS, 1, UNDEF_BLESS }, - { SHORT_SWORD, 0, WEAPON_CLASS, 1, UNDEF_BLESS }, /* wakizashi */ - { YUMI, 0, WEAPON_CLASS, 1, UNDEF_BLESS }, - { YA, 0, WEAPON_CLASS, 25, UNDEF_BLESS }, /* variable quan */ - { SPLINT_MAIL, 0, ARMOR_CLASS, 1, UNDEF_BLESS }, - { 0, 0, 0, 0, 0 } +static const struct trobj Samurai[] = { + { KATANA, 0, WEAPON_CLASS, 1, 1, UNDEF_BLESS }, + { SHORT_SWORD, 0, WEAPON_CLASS, 1, 1, UNDEF_BLESS }, /* wakizashi */ + { YUMI, 0, WEAPON_CLASS, 1, 1, UNDEF_BLESS }, + { YA, 0, WEAPON_CLASS, 26, 45, UNDEF_BLESS }, + { SPLINT_MAIL, 0, ARMOR_CLASS, 1, 1, UNDEF_BLESS }, + { 0, 0, 0, 0, 0, 0 } }; -static struct trobj Tourist[] = { -#define T_DARTS 0 - { DART, 2, WEAPON_CLASS, 25, UNDEF_BLESS }, /* quan is variable */ - { UNDEF_TYP, UNDEF_SPE, FOOD_CLASS, 10, 0 }, - { POT_EXTRA_HEALING, 0, POTION_CLASS, 2, UNDEF_BLESS }, - { SCR_MAGIC_MAPPING, 0, SCROLL_CLASS, 4, UNDEF_BLESS }, - { HAWAIIAN_SHIRT, 0, ARMOR_CLASS, 1, UNDEF_BLESS }, - { EXPENSIVE_CAMERA, UNDEF_SPE, TOOL_CLASS, 1, 0 }, - { CREDIT_CARD, 0, TOOL_CLASS, 1, 0 }, - { 0, 0, 0, 0, 0 } +static const struct trobj Tourist[] = { + { DART, 2, WEAPON_CLASS, 21, 40, UNDEF_BLESS }, + { UNDEF_TYP, UNDEF_SPE, FOOD_CLASS, 10, 10, 0 }, + { POT_EXTRA_HEALING, 0, POTION_CLASS, 2, 2, UNDEF_BLESS }, + { SCR_MAGIC_MAPPING, 0, SCROLL_CLASS, 4, 4, UNDEF_BLESS }, + { HAWAIIAN_SHIRT, 0, ARMOR_CLASS, 1, 1, UNDEF_BLESS }, + { EXPENSIVE_CAMERA, UNDEF_SPE, TOOL_CLASS, 1, 1, 0 }, + { CREDIT_CARD, 0, TOOL_CLASS, 1, 1, 0 }, + { 0, 0, 0, 0, 0, 0 } }; -static struct trobj Valkyrie[] = { - { LONG_SWORD, 1, WEAPON_CLASS, 1, UNDEF_BLESS }, - { DAGGER, 0, WEAPON_CLASS, 1, UNDEF_BLESS }, - { SMALL_SHIELD, 3, ARMOR_CLASS, 1, UNDEF_BLESS }, - { FOOD_RATION, 0, FOOD_CLASS, 1, 0 }, - { 0, 0, 0, 0, 0 } +static const struct trobj Valkyrie[] = { + { SPEAR, 1, WEAPON_CLASS, 1, 1, UNDEF_BLESS }, + { DAGGER, 0, WEAPON_CLASS, 1, 1, UNDEF_BLESS }, + { SMALL_SHIELD, 3, ARMOR_CLASS, 1, 1, UNDEF_BLESS }, + { FOOD_RATION, 0, FOOD_CLASS, 1, 1, 0 }, + { 0, 0, 0, 0, 0, 0 } }; -static struct trobj Wizard[] = { -#define W_MULTSTART 2 -#define W_MULTEND 6 - { QUARTERSTAFF, 1, WEAPON_CLASS, 1, 1 }, - { CLOAK_OF_MAGIC_RESISTANCE, 0, ARMOR_CLASS, 1, UNDEF_BLESS }, - { UNDEF_TYP, UNDEF_SPE, WAND_CLASS, 1, UNDEF_BLESS }, - { UNDEF_TYP, UNDEF_SPE, RING_CLASS, 2, UNDEF_BLESS }, - { UNDEF_TYP, UNDEF_SPE, POTION_CLASS, 3, UNDEF_BLESS }, - { UNDEF_TYP, UNDEF_SPE, SCROLL_CLASS, 3, UNDEF_BLESS }, - { SPE_FORCE_BOLT, 0, SPBOOK_CLASS, 1, 1 }, - { UNDEF_TYP, UNDEF_SPE, SPBOOK_CLASS, 1, UNDEF_BLESS }, - { 0, 0, 0, 0, 0 } +static const struct trobj Wizard[] = { + { QUARTERSTAFF, 1, WEAPON_CLASS, 1, 1, 1 }, + { CLOAK_OF_MAGIC_RESISTANCE, 0, ARMOR_CLASS, 1, 1, UNDEF_BLESS }, + { UNDEF_TYP, UNDEF_SPE, WAND_CLASS, 1, 1, UNDEF_BLESS }, + { UNDEF_TYP, UNDEF_SPE, RING_CLASS, 2, 2, UNDEF_BLESS }, + { UNDEF_TYP, UNDEF_SPE, POTION_CLASS, 3, 3, UNDEF_BLESS }, + { UNDEF_TYP, UNDEF_SPE, SCROLL_CLASS, 3, 3, UNDEF_BLESS }, + { SPE_FORCE_BOLT, 0, SPBOOK_CLASS, 1, 1, 1 }, + { UNDEF_TYP, UNDEF_SPE, SPBOOK_CLASS, 1, 1, UNDEF_BLESS }, + { MAGIC_MARKER, 19, TOOL_CLASS, 1, 1, 0 }, /* actually spe = 18 + d4 */ + { 0, 0, 0, 0, 0, 0 } }; /* * Optional extra inventory items. */ -static struct trobj Tinopener[] = { { TIN_OPENER, 0, TOOL_CLASS, 1, 0 }, - { 0, 0, 0, 0, 0 } }; -static struct trobj Magicmarker[] = { { MAGIC_MARKER, UNDEF_SPE, TOOL_CLASS, - 1, 0 }, - { 0, 0, 0, 0, 0 } }; -static struct trobj Lamp[] = { { OIL_LAMP, 1, TOOL_CLASS, 1, 0 }, - { 0, 0, 0, 0, 0 } }; -static struct trobj Blindfold[] = { { BLINDFOLD, 0, TOOL_CLASS, 1, 0 }, - { 0, 0, 0, 0, 0 } }; -static struct trobj Instrument[] = { { WOODEN_FLUTE, 0, TOOL_CLASS, 1, 0 }, - { 0, 0, 0, 0, 0 } }; -static struct trobj Xtra_food[] = { { UNDEF_TYP, UNDEF_SPE, FOOD_CLASS, 2, 0 }, - { 0, 0, 0, 0, 0 } }; -static struct trobj Leash[] = { { LEASH, 0, TOOL_CLASS, 1, 0 }, - { 0, 0, 0, 0, 0 } }; -static struct trobj Towel[] = { { TOWEL, 0, TOOL_CLASS, 1, 0 }, - { 0, 0, 0, 0, 0 } }; -static struct trobj Wishing[] = { { WAN_WISHING, 3, WAND_CLASS, 1, 0 }, - { 0, 0, 0, 0, 0 } }; -static struct trobj Money[] = { { GOLD_PIECE, 0, COIN_CLASS, 1, 0 }, - { 0, 0, 0, 0, 0 } }; +static const struct trobj Healing_book[] = + { { SPE_HEALING, UNDEF_SPE, SPBOOK_CLASS, 1, 1, 1 }, + { 0, 0, 0, 0, 0, 0 } }; +static const struct trobj Protection_book[] = + { { SPE_PROTECTION, UNDEF_SPE, SPBOOK_CLASS, 1, 1, 1 }, + { 0, 0, 0, 0, 0, 0 } }; +static const struct trobj Confuse_monster_book[] = + { { SPE_CONFUSE_MONSTER, UNDEF_SPE, SPBOOK_CLASS, 1, 1, 1 }, + { 0, 0, 0, 0, 0, 0 } }; +static const struct trobj Tinopener[] = + { { TIN_OPENER, 0, TOOL_CLASS, 1, 1, 0 }, + { 0, 0, 0, 0, 0, 0 } }; +static const struct trobj Magicmarker[] = + { { MAGIC_MARKER, 19, TOOL_CLASS, 1, 1, 0 }, + { 0, 0, 0, 0, 0, 0 } }; +static const struct trobj Lamp[] = + { { OIL_LAMP, 1, TOOL_CLASS, 1, 1, 0 }, + { 0, 0, 0, 0, 0, 0 } }; +static const struct trobj Blindfold[] = + { { BLINDFOLD, 0, TOOL_CLASS, 1, 1, 0 }, + { 0, 0, 0, 0, 0, 0 } }; +static const struct trobj Xtra_food[] = + { { UNDEF_TYP, UNDEF_SPE, FOOD_CLASS, 2, 2, 0}, + { 0, 0, 0, 0, 0, 0 } }; +static const struct trobj Leash[] = + { { LEASH, 0, TOOL_CLASS, 1, 1, 0 }, + { 0, 0, 0, 0, 0, 0 } }; +static const struct trobj Towel[] = + { { TOWEL, 0, TOOL_CLASS, 1, 1, 0 }, + { 0, 0, 0, 0, 0, 0 } }; +static const struct trobj Wishing[] = + { { WAN_WISHING, 3, WAND_CLASS, 1, 1, 0 }, + { 0, 0, 0, 0, 0, 0 } }; +static const struct trobj Money[] = + { { GOLD_PIECE, 0, COIN_CLASS, 1, 1, 0 }, + { 0, 0, 0, 0, 0, 0 } }; /* race-based substitutions for initial inventory; the weaker cloak for elven rangers is intentional--they shoot better */ -static struct inv_sub { +static const struct inv_sub { short race_pm, item_otyp, subs_otyp; } inv_subs[] = { { PM_ELF, DAGGER, ELVEN_DAGGER }, @@ -237,7 +259,6 @@ static const struct def_skill Skill_A[] = { { P_KNIFE, P_BASIC }, { P_PICK_AXE, P_EXPERT }, { P_SHORT_SWORD, P_BASIC }, - { P_SCIMITAR, P_SKILLED }, { P_SABER, P_EXPERT }, { P_CLUB, P_SKILLED }, { P_QUARTERSTAFF, P_SKILLED }, @@ -263,8 +284,7 @@ static const struct def_skill Skill_B[] = { { P_BROAD_SWORD, P_SKILLED }, { P_LONG_SWORD, P_SKILLED }, { P_TWO_HANDED_SWORD, P_EXPERT }, - { P_SCIMITAR, P_SKILLED }, - { P_SABER, P_BASIC }, + { P_SABER, P_SKILLED }, { P_CLUB, P_SKILLED }, { P_MACE, P_SKILLED }, { P_MORNING_STAR, P_SKILLED }, @@ -308,7 +328,6 @@ static const struct def_skill Skill_H[] = { { P_DAGGER, P_SKILLED }, { P_KNIFE, P_EXPERT }, { P_SHORT_SWORD, P_SKILLED }, - { P_SCIMITAR, P_BASIC }, { P_SABER, P_BASIC }, { P_CLUB, P_SKILLED }, { P_MACE, P_BASIC }, @@ -333,7 +352,6 @@ static const struct def_skill Skill_K[] = { { P_BROAD_SWORD, P_SKILLED }, { P_LONG_SWORD, P_EXPERT }, { P_TWO_HANDED_SWORD, P_SKILLED }, - { P_SCIMITAR, P_BASIC }, { P_SABER, P_SKILLED }, { P_CLUB, P_BASIC }, { P_MACE, P_SKILLED }, @@ -400,7 +418,6 @@ static const struct def_skill Skill_R[] = { { P_BROAD_SWORD, P_SKILLED }, { P_LONG_SWORD, P_SKILLED }, { P_TWO_HANDED_SWORD, P_BASIC }, - { P_SCIMITAR, P_SKILLED }, { P_SABER, P_SKILLED }, { P_CLUB, P_SKILLED }, { P_MACE, P_SKILLED }, @@ -454,7 +471,6 @@ static const struct def_skill Skill_S[] = { { P_BROAD_SWORD, P_SKILLED }, { P_LONG_SWORD, P_EXPERT }, { P_TWO_HANDED_SWORD, P_EXPERT }, - { P_SCIMITAR, P_BASIC }, { P_SABER, P_BASIC }, { P_FLAIL, P_SKILLED }, { P_QUARTERSTAFF, P_BASIC }, @@ -480,7 +496,6 @@ static const struct def_skill Skill_T[] = { { P_BROAD_SWORD, P_BASIC }, { P_LONG_SWORD, P_BASIC }, { P_TWO_HANDED_SWORD, P_BASIC }, - { P_SCIMITAR, P_SKILLED }, { P_SABER, P_SKILLED }, { P_MACE, P_BASIC }, { P_MORNING_STAR, P_BASIC }, @@ -515,12 +530,11 @@ static const struct def_skill Skill_V[] = { { P_BROAD_SWORD, P_SKILLED }, { P_LONG_SWORD, P_EXPERT }, { P_TWO_HANDED_SWORD, P_EXPERT }, - { P_SCIMITAR, P_BASIC }, { P_SABER, P_BASIC }, { P_HAMMER, P_EXPERT }, { P_QUARTERSTAFF, P_BASIC }, { P_POLEARMS, P_SKILLED }, - { P_SPEAR, P_SKILLED }, + { P_SPEAR, P_EXPERT }, { P_TRIDENT, P_BASIC }, { P_LANCE, P_SKILLED }, { P_SLING, P_BASIC }, @@ -557,108 +571,79 @@ static const struct def_skill Skill_W[] = { { P_NONE, 0 } }; -STATIC_OVL void -knows_object(obj) -register int obj; +staticfn void +knows_object(int obj, boolean override_pauper) { - discover_object(obj, TRUE, FALSE); - objects[obj].oc_pre_discovered = 1; /* not a "discovery" */ + if (u.uroleplay.pauper && !override_pauper) + return; + /* mark as known, but not yet encountered */ + discover_object(obj, TRUE, FALSE, FALSE); } /* Know ordinary (non-magical) objects of a certain class, - * like all gems except the loadstone and luckstone. - */ -STATIC_OVL void -knows_class(sym) -register char sym; -{ - register int ct; - for (ct = 1; ct < NUM_OBJECTS; ct++) - if (objects[ct].oc_class == sym && !objects[ct].oc_magic) - knows_object(ct); -} - -void -u_init() + like all gems except the loadstone and luckstone. */ +staticfn void +knows_class(char sym) { - register int i; - struct u_roleplay tmpuroleplay = u.uroleplay; /* set by rcfile options */ - - flags.female = flags.initgend; - flags.beginner = 1; - - /* zero u, including pointer values -- - * necessary when aborting from a failed restore */ - (void) memset((genericptr_t) &u, 0, sizeof(u)); - u.ustuck = (struct monst *) 0; - (void) memset((genericptr_t) &ubirthday, 0, sizeof(ubirthday)); - (void) memset((genericptr_t) &urealtime, 0, sizeof(urealtime)); + struct obj odummy, *o; + int ct; - u.uroleplay = tmpuroleplay; /* restore options set via rcfile */ - -#if 0 /* documentation of more zero values as desirable */ - u.usick_cause[0] = 0; - u.uluck = u.moreluck = 0; - uarmu = 0; - uarm = uarmc = uarmh = uarms = uarmg = uarmf = 0; - uwep = uball = uchain = uleft = uright = 0; - uswapwep = uquiver = 0; - u.twoweap = 0; - u.ublessed = 0; /* not worthy yet */ - u.ugangr = 0; /* gods not angry */ - u.ugifts = 0; /* no divine gifts bestowed */ - u.uevent.uhand_of_elbereth = 0; - u.uevent.uheard_tune = 0; - u.uevent.uopened_dbridge = 0; - u.uevent.udemigod = 0; /* not a demi-god yet... */ - u.udg_cnt = 0; - u.mh = u.mhmax = u.mtimedone = 0; - u.uz.dnum = u.uz0.dnum = 0; - u.utotype = 0; -#endif /* 0 */ + if (u.uroleplay.pauper) + return; - u.uz.dlevel = 1; - u.uz0.dlevel = 0; - u.utolev = u.uz; + odummy = cg.zeroobj; + odummy.oclass = sym; + o = &odummy; /* for use in various obj.h macros */ - u.umoved = FALSE; - u.umortality = 0; - u.ugrave_arise = NON_PM; + /* + * Note: the exceptions here can be bypassed if necessary by + * calling knows_object() directly. So an elven ranger, + * for example, knows all elven weapons despite the bow, + * arrow, and spear limitation below. + */ - u.umonnum = u.umonster = (flags.female && urole.femalenum != NON_PM) - ? urole.femalenum - : urole.malenum; - u.ulycn = NON_PM; - set_uasmon(); + for (ct = svb.bases[(uchar) sym]; ct < svb.bases[(uchar) sym + 1]; ct++) { + /* not flagged as magic but shouldn't be pre-discovered + (small shields look the same as two types of magical shield; + cornuthaum / dunce cap look the same as each other) */ + if (ct == CORNUTHAUM || ct == DUNCE_CAP || ct == SMALL_SHIELD) + continue; + if (sym == WEAPON_CLASS) { + odummy.otyp = ct; /* update 'o' */ + /* arbitrary: only knights and samurai recognize polearms */ + if ((!Role_if(PM_KNIGHT) && !Role_if(PM_SAMURAI)) && is_pole(o)) + continue; + /* rangers know all launchers (bows, &c), ammo (arrows, &c), + and spears regardless of race/species, but not other weapons */ + if (Role_if(PM_RANGER) + && (!is_launcher(o) && !is_ammo(o) && !is_spear(o))) + continue; + /* rogues know daggers, regardless of racial variations */ + if (Role_if(PM_ROGUE) && (objects[o->otyp].oc_skill != P_DAGGER)) + continue; + } - u.ulevel = 0; /* set up some of the initial attributes */ - u.uhp = u.uhpmax = newhp(); - u.uen = u.uenmax = newpw(); - u.uspellprot = 0; - adjabil(0, 1); - u.ulevel = u.ulevelmax = 1; + if (objects[ct].oc_class == sym && !objects[ct].oc_magic) + knows_object(ct, FALSE); + } +} - init_uhunger(); - for (i = 0; i <= MAXSPELL; i++) - spl_book[i].sp_id = NO_SPELL; - u.ublesscnt = 300; /* no prayers just yet */ - u.ualignbase[A_CURRENT] = u.ualignbase[A_ORIGINAL] = u.ualign.type = - aligns[flags.initalign].value; +/* role-specific initializations, mostly inventory -#if defined(BSD) && !defined(POSIX_TYPES) - (void) time((long *) &ubirthday); -#else - (void) time(&ubirthday); -#endif + other things may be initialised here, but the function might run more than + once, so any non-inventory initialisations should be nonrandom and + idempotent (i.e. doing them twice is OK) */ +staticfn void +u_init_role(void) +{ + int i; - /* - * For now, everyone starts out with a night vision range of 1 and - * their xray range disabled. - */ - u.nv_range = 1; - u.xray_range = -1; + /* the program used to check moves<=1 && invent==NULL do decide whether + a new game has started, but due to the 'pauper' option/conduct, can't + rely on invent becoming non-Null anymore; instead, initialize moves + to 0 instead of 1, then set it to 1 here, where invent init occurs */ + svm.moves = 1L; - /*** Role-specific initializations ***/ switch (Role_switch) { /* rn2(100) > 50 necessary for some choices because some * random number generators are bad enough to seriously @@ -670,68 +655,64 @@ u_init() ini_inv(Tinopener); else if (!rn2(4)) ini_inv(Lamp); - else if (!rn2(10)) + else if (!rn2(5)) ini_inv(Magicmarker); - knows_object(SACK); - knows_object(TOUCHSTONE); - skill_init(Skill_A); + knows_object(SACK, FALSE); + knows_object(TOUCHSTONE, FALSE); /* FALSE: don't override pauper here, + * but TOUCHSTONE will be made known + * in pauper_reinit() */ break; case PM_BARBARIAN: if (rn2(100) >= 50) { /* see above comment */ - Barbarian[B_MAJOR].trotyp = BATTLE_AXE; - Barbarian[B_MINOR].trotyp = SHORT_SWORD; + ini_inv(Barbarian_0); + } else { + ini_inv(Barbarian_1); } - ini_inv(Barbarian); if (!rn2(6)) ini_inv(Lamp); - knows_class(WEAPON_CLASS); + knows_class(WEAPON_CLASS); /* excluding polearms */ knows_class(ARMOR_CLASS); - skill_init(Skill_B); break; - case PM_CAVEMAN: - Cave_man[C_AMMO].trquan = rn1(11, 10); /* 10..20 */ + case PM_CAVE_DWELLER: ini_inv(Cave_man); - skill_init(Skill_C); break; case PM_HEALER: u.umoney0 = rn1(1000, 1001); ini_inv(Healer); if (!rn2(25)) ini_inv(Lamp); - knows_object(POT_FULL_HEALING); - skill_init(Skill_H); + knows_object(POT_FULL_HEALING, FALSE); break; case PM_KNIGHT: ini_inv(Knight); - knows_class(WEAPON_CLASS); + knows_class(WEAPON_CLASS); /* all weapons */ knows_class(ARMOR_CLASS); /* give knights chess-like mobility--idea from wooledge@..cwru.edu */ HJumping |= FROMOUTSIDE; - skill_init(Skill_K); break; case PM_MONK: { - static short M_spell[] = { SPE_HEALING, SPE_PROTECTION, SPE_SLEEP }; + static const struct trobj *M_spell[] = { + Healing_book, Protection_book, Confuse_monster_book + }; - Monk[M_BOOK].trotyp = M_spell[rn2(90) / 30]; /* [0..2] */ ini_inv(Monk); - if (!rn2(5)) + ini_inv(M_spell[rn2(90) / 30]); /* [0..2] */ + if (!rn2(4)) ini_inv(Magicmarker); else if (!rn2(10)) ini_inv(Lamp); knows_class(ARMOR_CLASS); /* sufficiently martial-arts oriented item to ignore language issue */ - knows_object(SHURIKEN); - skill_init(Skill_Mon); + knows_object(SHURIKEN, FALSE); break; } - case PM_PRIEST: + case PM_CLERIC: /* priest/priestess */ ini_inv(Priest); - if (!rn2(10)) + if (!rn2(5)) ini_inv(Magicmarker); else if (!rn2(10)) ini_inv(Lamp); - knows_object(POT_WATER); - skill_init(Skill_P); + knows_object(POT_WATER, TRUE); /* override pauper */ /* KMH, conduct -- * Some may claim that this isn't agnostic, since they * are literally "priests" and they have holy water. @@ -741,31 +722,37 @@ u_init() */ break; case PM_RANGER: - Ranger[RAN_TWO_ARROWS].trquan = rn1(10, 50); - Ranger[RAN_ZERO_ARROWS].trquan = rn1(10, 30); ini_inv(Ranger); - skill_init(Skill_Ran); + knows_class(WEAPON_CLASS); /* bows, arrows, spears only */ break; case PM_ROGUE: - Rogue[R_DAGGERS].trquan = rn1(10, 6); u.umoney0 = 0; ini_inv(Rogue); if (!rn2(5)) ini_inv(Blindfold); - knows_object(SACK); - skill_init(Skill_R); + knows_object(SACK, FALSE); /* FALSE: don't override pauper here, + * but sack will be made known in + * pauper_reinit() */ + knows_class(WEAPON_CLASS); /* daggers only */ break; case PM_SAMURAI: - Samurai[S_ARROWS].trquan = rn1(20, 26); ini_inv(Samurai); if (!rn2(5)) ini_inv(Blindfold); - knows_class(WEAPON_CLASS); + knows_class(WEAPON_CLASS); /* all weapons */ knows_class(ARMOR_CLASS); - skill_init(Skill_S); + /* in order to assist non-Japanese speakers, pre-discover items + that switch to Japanese names when playing as a Samurai */ + for (i = MAXOCLASSES; i < NUM_OBJECTS; ++i) { + if (objects[i].oc_magic) /* skip "magic koto" */ + continue; + if (Japanese_item_name(i, (const char *) 0)) + /* we don't override pauper here because that would give + samarai an advantage of knowing several items in advance */ + knows_object(i, FALSE); + } break; case PM_TOURIST: - Tourist[T_DARTS].trquan = rn1(20, 21); u.umoney0 = rnd(1000); ini_inv(Tourist); if (!rn2(25)) @@ -774,32 +761,36 @@ u_init() ini_inv(Leash); else if (!rn2(25)) ini_inv(Towel); - else if (!rn2(25)) + else if (!rn2(20)) ini_inv(Magicmarker); - skill_init(Skill_T); break; case PM_VALKYRIE: ini_inv(Valkyrie); if (!rn2(6)) ini_inv(Lamp); - knows_class(WEAPON_CLASS); + knows_class(WEAPON_CLASS); /* excludes polearms */ knows_class(ARMOR_CLASS); - skill_init(Skill_V); break; case PM_WIZARD: ini_inv(Wizard); - if (!rn2(5)) - ini_inv(Magicmarker); if (!rn2(5)) ini_inv(Blindfold); - skill_init(Skill_W); break; default: /* impossible */ break; } - /*** Race-specific initializations ***/ + gn.nocreate = STRANGE_OBJECT; + gn.nocreate2 = STRANGE_OBJECT; + gn.nocreate3 = STRANGE_OBJECT; + gn.nocreate4 = STRANGE_OBJECT; +} + +/* race-specific initializations, same restrictions as u_init_role */ +staticfn void +u_init_race(void) +{ switch (Race_switch) { case PM_HUMAN: /* Nothing special */ @@ -811,36 +802,39 @@ u_init() * Non-warriors get an instrument. We use a kludge to * get only non-magic instruments. */ - if (Role_if(PM_PRIEST) || Role_if(PM_WIZARD)) { - static int trotyp[] = { WOODEN_FLUTE, TOOLED_HORN, WOODEN_HARP, - BELL, BUGLE, LEATHER_DRUM }; - Instrument[0].trotyp = trotyp[rn2(SIZE(trotyp))]; + if (Role_if(PM_CLERIC) || Role_if(PM_WIZARD)) { + static const int trotyp[] = + { WOODEN_FLUTE, TOOLED_HORN, WOODEN_HARP, + BELL, BUGLE, LEATHER_DRUM }; + const struct trobj Instrument[] = + { { ROLL_FROM(trotyp), 0, TOOL_CLASS, 1, 1, 0 }, + { 0, 0, 0, 0, 0, 0 } }; ini_inv(Instrument); } /* Elves can recognize all elvish objects */ - knows_object(ELVEN_SHORT_SWORD); - knows_object(ELVEN_ARROW); - knows_object(ELVEN_BOW); - knows_object(ELVEN_SPEAR); - knows_object(ELVEN_DAGGER); - knows_object(ELVEN_BROADSWORD); - knows_object(ELVEN_MITHRIL_COAT); - knows_object(ELVEN_LEATHER_HELM); - knows_object(ELVEN_SHIELD); - knows_object(ELVEN_BOOTS); - knows_object(ELVEN_CLOAK); + knows_object(ELVEN_SHORT_SWORD, FALSE); + knows_object(ELVEN_ARROW, FALSE); + knows_object(ELVEN_BOW, FALSE); + knows_object(ELVEN_SPEAR, FALSE); + knows_object(ELVEN_DAGGER, FALSE); + knows_object(ELVEN_BROADSWORD, FALSE); + knows_object(ELVEN_MITHRIL_COAT, FALSE); + knows_object(ELVEN_LEATHER_HELM, FALSE); + knows_object(ELVEN_SHIELD, FALSE); + knows_object(ELVEN_BOOTS, FALSE); + knows_object(ELVEN_CLOAK, FALSE); break; case PM_DWARF: /* Dwarves can recognize all dwarvish objects */ - knows_object(DWARVISH_SPEAR); - knows_object(DWARVISH_SHORT_SWORD); - knows_object(DWARVISH_MATTOCK); - knows_object(DWARVISH_IRON_HELM); - knows_object(DWARVISH_MITHRIL_COAT); - knows_object(DWARVISH_CLOAK); - knows_object(DWARVISH_ROUNDSHIELD); + knows_object(DWARVISH_SPEAR, FALSE); + knows_object(DWARVISH_SHORT_SWORD, FALSE); + knows_object(DWARVISH_MATTOCK, FALSE); + knows_object(DWARVISH_IRON_HELM, FALSE); + knows_object(DWARVISH_MITHRIL_COAT, FALSE); + knows_object(DWARVISH_CLOAK, FALSE); + knows_object(DWARVISH_ROUNDSHIELD, FALSE); break; case PM_GNOME: @@ -851,48 +845,89 @@ u_init() if (!Role_if(PM_WIZARD)) ini_inv(Xtra_food); /* Orcs can recognize all orcish objects */ - knows_object(ORCISH_SHORT_SWORD); - knows_object(ORCISH_ARROW); - knows_object(ORCISH_BOW); - knows_object(ORCISH_SPEAR); - knows_object(ORCISH_DAGGER); - knows_object(ORCISH_CHAIN_MAIL); - knows_object(ORCISH_RING_MAIL); - knows_object(ORCISH_HELM); - knows_object(ORCISH_SHIELD); - knows_object(URUK_HAI_SHIELD); - knows_object(ORCISH_CLOAK); + knows_object(ORCISH_SHORT_SWORD, FALSE); + knows_object(ORCISH_ARROW, FALSE); + knows_object(ORCISH_BOW, FALSE); + knows_object(ORCISH_SPEAR, FALSE); + knows_object(ORCISH_DAGGER, FALSE); + knows_object(ORCISH_CHAIN_MAIL, FALSE); + knows_object(ORCISH_RING_MAIL, FALSE); + knows_object(ORCISH_HELM, FALSE); + knows_object(ORCISH_SHIELD, FALSE); + knows_object(URUK_HAI_SHIELD, FALSE); + knows_object(ORCISH_CLOAK, FALSE); break; default: /* impossible */ break; } +} - if (discover) - ini_inv(Wishing); - - if (wizard) - read_wizkit(); - - if (u.umoney0) - ini_inv(Money); - u.umoney0 += hidden_gold(); /* in case sack has gold in it */ +/* for 'pauper' aka 'unprepared'; take away any skills (bare-handed combat, + riding) that are better than unskilled; learn the book (without carrying + it or knowing its spell yet) for some key spells */ +staticfn void +pauper_reinit(void) +{ + int skill, preknown = STRANGE_OBJECT; - find_ac(); /* get initial ac value */ - init_attr(75); /* init attribute values */ - max_rank_sz(); /* set max str size for class ranks */ - /* - * Do we really need this? - */ - for (i = 0; i < A_MAX; i++) - if (!rn2(20)) { - register int xd = rn2(7) - 2; /* biased variation */ + if (!u.uroleplay.pauper) + return; - (void) adjattrib(i, xd, TRUE); - if (ABASE(i) < AMAX(i)) - AMAX(i) = ABASE(i); + for (skill = 0; skill < P_NUM_SKILLS; skill++) + if (P_SKILL(skill) > P_UNSKILLED) { + P_SKILL(skill) = P_UNSKILLED; + P_ADVANCE(skill) = 0; } + /* pauper has lost out on initial skills, but provide some unspent skill + credits to make up for that */ + u.weapon_slots = 2; + + /* paupers don't know any spells yet, but several roles will recognize + the spellbook for a key spell (not necessarily that role's special + spell); "supply chests" on the first few levels provide a fairly + high chance to find the book; some other roles know a non-book item */ + switch (Role_switch) { + case PM_HEALER: + preknown = SPE_HEALING; + break; + case PM_CLERIC: + case PM_KNIGHT: + case PM_MONK: + preknown = SPE_PROTECTION; + break; + case PM_WIZARD: + preknown = SPE_FORCE_BOLT; + break; + case PM_ARCHEOLOGIST: + preknown = TOUCHSTONE; + break; + case PM_CAVE_DWELLER: + preknown = FLINT; + break; + case PM_ROGUE: + case PM_TOURIST: + preknown = SACK; + break; + case PM_SAMURAI: + /* food ration isn't interesting to discover, but put "gunyoki" into + discoveries list for players who might not recognize what it is */ + preknown = FOOD_RATION; + break; + default: + case PM_BARBARIAN: + case PM_RANGER: + case PM_VALKYRIE: + break; + } + if (preknown != STRANGE_OBJECT) + knows_object(preknown, TRUE); +} +/* boost STR and CON until hero can carry inventory */ +staticfn void +u_init_carry_attr_boost(void) +{ /* make sure you can carry all you have - especially for Tourists */ while (inv_weight() > 0) { if (adjattrib(A_STR, 1, TRUE)) @@ -902,17 +937,109 @@ u_init() /* only get here when didn't boost strength or constitution */ break; } +} + +/* initialise u, except inventory, attributes, skills and discoveries */ +void +u_init_misc(void) +{ + int i; + struct u_roleplay tmpuroleplay = u.uroleplay; /* set by rcfile options */ + + flags.female = flags.initgend; + flags.beginner = TRUE; + + /* zero u, including pointer values -- + * necessary when aborting from a failed restore */ + (void) memset((genericptr_t) &u, 0, sizeof(u)); + u.ustuck = (struct monst *) 0; + (void) memset((genericptr_t) &ubirthday, 0, sizeof(ubirthday)); + (void) memset((genericptr_t) &urealtime, 0, sizeof(urealtime)); + + u.uroleplay = tmpuroleplay; /* restore options set via rcfile */ + +#if 0 /* documentation of more zero values as desirable */ + u.usick_cause[0] = 0; + u.uluck = u.moreluck = 0; + uarmu = 0; + uarm = uarmc = uarmh = uarms = uarmg = uarmf = 0; + uwep = uball = uchain = uleft = uright = 0; + uswapwep = uquiver = 0; + u.twoweap = FALSE; /* bypass set_twoweap() */ + u.ublessed = 0; /* not worthy yet */ + u.ugangr = 0; /* gods not angry */ + u.ugifts = 0; /* no divine gifts bestowed */ + u.uevent.uhand_of_elbereth = 0; + u.uevent.uheard_tune = 0; + u.uevent.uopened_dbridge = 0; + u.uevent.udemigod = 0; /* not a demi-god yet... */ + u.uevent.amulet_wish = 0; + u.udg_cnt = 0; + u.mh = u.mhmax = u.mtimedone = 0; + u.uz.dnum = u.uz0.dnum = 0; + u.utotype = UTOTYPE_NONE; +#endif /* 0 */ + + u.uz.dlevel = 1; + u.uz0.dlevel = 0; + u.utolev = u.uz; + + u.umoved = FALSE; + u.umortality = 0; + u.ugrave_arise = NON_PM; + + u.umonnum = u.umonster = gu.urole.mnum; + u.ulycn = NON_PM; + set_uasmon(); + + u.ulevel = 0; /* set up some of the initial attributes */ + u.uhp = u.uhpmax = u.uhppeak = newhp(); + u.uen = u.uenmax = u.uenpeak = newpw(); + u.uspellprot = 0; + adjabil(0, 1); + u.ulevel = u.ulevelmax = 1; + + init_uhunger(); + for (i = 0; i <= MAXSPELL; i++) + svs.spl_book[i].sp_id = NO_SPELL; + u.ublesscnt = 300; /* no prayers just yet */ + u.ualignbase[A_CURRENT] = u.ualignbase[A_ORIGINAL] = u.ualign.type = + aligns[flags.initalign].value; + +#if defined(BSD) && !defined(POSIX_TYPES) + (void) time((long *) &ubirthday); +#else + (void) time(&ubirthday); +#endif + + /* + * For now, everyone starts out with a night vision range of 1 and + * their xray_range disabled. + */ + u.nv_range = 1; + u.xray_range = -1; + u.unblind_telepat_range = -1; + + /* OPTIONS:blind results in permanent blindness (unless overridden + by the Eyes of the Overworld, which will clear 'u.uroleplay.blind' + to void the conduct, but will leave the PermaBlind bit set so that + blindness resumes when the Eyes are removed). */ + if (u.uroleplay.blind) + HBlinded |= FROMOUTSIDE; /* set PermaBlind */ + + /* roughly based on distribution in human population */ + u.uhandedness = rn2(10) ? RIGHT_HANDED : LEFT_HANDED; + + max_rank_sz(); /* set max str size for class ranks */ return; } -/* skills aren't initialized, so we use the role-specific skill lists */ -STATIC_OVL boolean -restricted_spell_discipline(otyp) -int otyp; +/* the appropriate set of skills for the role */ +staticfn const struct def_skill * +skills_for_role(void) { const struct def_skill *skills; - int this_skill = spell_skilltype(otyp); switch (Role_switch) { case PM_ARCHEOLOGIST: @@ -921,7 +1048,7 @@ int otyp; case PM_BARBARIAN: skills = Skill_B; break; - case PM_CAVEMAN: + case PM_CAVE_DWELLER: skills = Skill_C; break; case PM_HEALER: @@ -933,7 +1060,7 @@ int otyp; case PM_MONK: skills = Skill_Mon; break; - case PM_PRIEST: + case PM_CLERIC: skills = Skill_P; break; case PM_RANGER: @@ -955,10 +1082,20 @@ int otyp; skills = Skill_W; break; default: - skills = 0; /* lint suppression */ + panic("No skills found for role"); break; } + return skills; +} + +/* skills aren't initialized, so we use the role-specific skill lists */ +staticfn boolean +restricted_spell_discipline(int otyp) +{ + const struct def_skill *skills = skills_for_role(); + int this_skill = spell_skilltype(otyp); + while (skills && skills->skill != P_NONE) { if (skills->skill == this_skill) return FALSE; @@ -967,70 +1104,220 @@ int otyp; return TRUE; } -STATIC_OVL void -ini_inv(trop) -register struct trobj *trop; +/* randomizes the quantity given a trobj description */ +staticfn long +trquan(const struct trobj *trop) +{ + if (!trop->trquan_min) + return 1; + return trop->trquan_min + rn2(trop->trquan_max - trop->trquan_min + 1); +} + +/* create random object of certain class, filtering out too powerful items */ +staticfn struct obj * +ini_inv_mkobj_filter(int oclass, boolean got_level1_spellbook) { struct obj *obj; - int otyp, i; + int otyp, trycnt = 0; + /* + * For random objects, do not create certain overly powerful + * items: wand of wishing, ring of levitation, or the + * polymorph/polymorph control combination. Specific objects, + * i.e. the discovery wishing, are still OK. + * Also, don't get a couple of really useless items. (Note: + * punishment isn't "useless". Some players who start out with + * one will immediately read it and use the iron ball as a + * weapon.) + */ + obj = mkobj(oclass, FALSE); + otyp = obj->otyp; + + while (otyp == WAN_WISHING || otyp == gn.nocreate + || otyp == gn.nocreate2 || otyp == gn.nocreate3 + || otyp == gn.nocreate4 || otyp == RIN_LEVITATION + /* 'useless' items */ + || otyp == POT_HALLUCINATION + || otyp == POT_ACID + || otyp == SCR_AMNESIA + || otyp == SCR_FIRE + || otyp == SCR_BLANK_PAPER + || otyp == SPE_BLANK_PAPER + || otyp == RIN_AGGRAVATE_MONSTER + || otyp == RIN_HUNGER + || otyp == WAN_NOTHING + /* orcs start with poison resistance */ + || (otyp == RIN_POISON_RESISTANCE && Race_if(PM_ORC)) + /* Monks don't use weapons */ + || (otyp == SCR_ENCHANT_WEAPON && Role_if(PM_MONK)) + /* wizard patch -- they already have one */ + || (otyp == SPE_FORCE_BOLT && Role_if(PM_WIZARD)) + /* powerful spells are either useless to + low level players or unbalancing; also + spells in restricted skill categories */ + || (obj->oclass == SPBOOK_CLASS + && (objects[otyp].oc_level > (got_level1_spellbook ? 3 : 1) + || restricted_spell_discipline(otyp))) + || otyp == SPE_NOVEL) { + dealloc_obj(obj); + if (++trycnt > 1000) { + /* This lonely pancake's potential will never be realized. + * It will exist only as a thought, of something that could have + * been, but never will be. It will never experience maple syrup + * oozing into its nooks, or see the delightful expression on + * someone's face as they are about to let it dance across their + * taste buds. */ + obj = mksobj(PANCAKE, TRUE, FALSE); + break; + } + obj = mkobj(oclass, FALSE); + otyp = obj->otyp; + } + return obj; +} + +/* substitute object with something else based on race. + only changes otyp, and returns it. */ +staticfn short +ini_inv_obj_substitution(const struct trobj *trop, struct obj *obj) +{ + if (gu.urace.mnum != PM_HUMAN) { + int i; + + /* substitute race-specific items; this used to be in + the 'if (otyp != UNDEF_TYP) { }' block above, but then + substitutions didn't occur for randomly generated items + (particularly food) which have racial substitutes */ + for (i = 0; inv_subs[i].race_pm != NON_PM; ++i) + if (inv_subs[i].race_pm == gu.urace.mnum + && obj->otyp == inv_subs[i].item_otyp) { + debugpline3("ini_inv: substituting %s for %s%s", + OBJ_NAME(objects[inv_subs[i].subs_otyp]), + (trop->trotyp == UNDEF_TYP) ? "random " : "", + OBJ_NAME(objects[obj->otyp])); + obj->otyp = inv_subs[i].subs_otyp; + break; + } + } + return obj->otyp; +} + +/* returns: TRUE to stop generating items from this trobj, + FALSE for normal behaviour */ +staticfn boolean +ini_inv_adjust_obj(const struct trobj *trop, struct obj *obj) +{ + boolean stop = FALSE; + if (trop->trclass == COIN_CLASS) { + /* no "blessed" or "identified" money */ + obj->quan = u.umoney0; + } else { + if (objects[obj->otyp].oc_uses_known) + obj->known = 1; + /* not observe_object during startup, that's handled later */ + obj->dknown = obj->bknown = obj->rknown = 1; + if (Is_container(obj) || obj->otyp == STATUE) { + obj->cknown = obj->lknown = 1; + obj->otrapped = 0; + } + obj->cursed = 0; + if (obj->opoisoned && u.ualign.type != A_CHAOTIC) + obj->opoisoned = 0; + if (obj->oclass == WEAPON_CLASS || obj->oclass == TOOL_CLASS) { + obj->quan = trquan(trop); + stop = TRUE; + } else if (obj->oclass == GEM_CLASS && is_graystone(obj) + && obj->otyp != FLINT) { + obj->quan = 1L; + } + if (trop->trspe != UNDEF_SPE) { + obj->spe = trop->trspe; + if (trop->trotyp == MAGIC_MARKER && obj->spe < 96) + obj->spe += rn2(4); + } else { + /* Don't start with +0 or negative rings */ + if (objects[obj->otyp].oc_class == RING_CLASS + && objects[obj->otyp].oc_charged && obj->spe <= 0) + obj->spe = rne(3); + } + if (trop->trbless != UNDEF_BLESS) + obj->blessed = trop->trbless; + + } + /* defined after setting otyp+quan + blessedness */ + obj->owt = weight(obj); + return stop; +} + +/* initial inventory: wear, wield, learn the spell/obj */ +staticfn void +ini_inv_use_obj(struct obj *obj) +{ + /* Make the type known if necessary */ + if (OBJ_DESCR(objects[obj->otyp]) && obj->known) + discover_object(obj->otyp, TRUE, TRUE, FALSE); + if (obj->otyp == OIL_LAMP) + discover_object(POT_OIL, TRUE, TRUE, FALSE); + + if (obj->oclass == ARMOR_CLASS) { + if (is_shield(obj) && !uarms && !(uwep && bimanual(uwep))) { + /* Prior to 3.6.2 this used to unset uswapwep if it was set, + but wearing a shield doesn't prevent having an alternate + weapon ready to swap with the primary; just make sure we + aren't two-weaponing (academic; no one starts that way) */ + set_twoweap(FALSE); /* u.twoweap = FALSE */ + setworn(obj, W_ARMS); + } else if (is_helmet(obj) && !uarmh) + setworn(obj, W_ARMH); + else if (is_gloves(obj) && !uarmg) + setworn(obj, W_ARMG); + else if (is_shirt(obj) && !uarmu) + setworn(obj, W_ARMU); + else if (is_cloak(obj) && !uarmc) + setworn(obj, W_ARMC); + else if (is_boots(obj) && !uarmf) + setworn(obj, W_ARMF); + else if (is_suit(obj) && !uarm) + setworn(obj, W_ARM); + } + + if (obj->oclass == WEAPON_CLASS || is_weptool(obj) + || obj->otyp == TIN_OPENER + || obj->otyp == FLINT || obj->otyp == ROCK) { + if (is_ammo(obj) || is_missile(obj)) { + if (!uquiver) + setuqwep(obj); + } else if (!uwep && (!uarms || !bimanual(obj))) { + setuwep(obj); + } else if (!uswapwep) { + setuswapwep(obj); + } + } + if (obj->oclass == SPBOOK_CLASS && obj->otyp != SPE_BLANK_PAPER) + initialspell(obj); +} + +staticfn void +ini_inv(const struct trobj *trop) +{ + struct obj *obj; + int otyp; + boolean got_sp1 = FALSE; /* got a level 1 spellbook? */ + long quan; + + if (u.uroleplay.pauper) /* pauper gets no items */ + return; + + quan = trquan(trop); while (trop->trclass) { otyp = (int) trop->trotyp; if (otyp != UNDEF_TYP) { obj = mksobj(otyp, TRUE, FALSE); } else { /* UNDEF_TYP */ - static NEARDATA short nocreate = STRANGE_OBJECT; - static NEARDATA short nocreate2 = STRANGE_OBJECT; - static NEARDATA short nocreate3 = STRANGE_OBJECT; - static NEARDATA short nocreate4 = STRANGE_OBJECT; - /* - * For random objects, do not create certain overly powerful - * items: wand of wishing, ring of levitation, or the - * polymorph/polymorph control combination. Specific objects, - * i.e. the discovery wishing, are still OK. - * Also, don't get a couple of really useless items. (Note: - * punishment isn't "useless". Some players who start out with - * one will immediately read it and use the iron ball as a - * weapon.) - */ - obj = mkobj(trop->trclass, FALSE); + obj = ini_inv_mkobj_filter(trop->trclass, got_sp1); otyp = obj->otyp; - while (otyp == WAN_WISHING || otyp == nocreate - || otyp == nocreate2 || otyp == nocreate3 - || otyp == nocreate4 || otyp == RIN_LEVITATION - /* 'useless' items */ - || otyp == POT_HALLUCINATION - || otyp == POT_ACID - || otyp == SCR_AMNESIA - || otyp == SCR_FIRE - || otyp == SCR_BLANK_PAPER - || otyp == SPE_BLANK_PAPER - || otyp == RIN_AGGRAVATE_MONSTER - || otyp == RIN_HUNGER - || otyp == WAN_NOTHING - /* orcs start with poison resistance */ - || (otyp == RIN_POISON_RESISTANCE && Race_if(PM_ORC)) - /* Monks don't use weapons */ - || (otyp == SCR_ENCHANT_WEAPON && Role_if(PM_MONK)) - /* wizard patch -- they already have one */ - || (otyp == SPE_FORCE_BOLT && Role_if(PM_WIZARD)) - /* powerful spells are either useless to - low level players or unbalancing; also - spells in restricted skill categories */ - || (obj->oclass == SPBOOK_CLASS - && (objects[otyp].oc_level > 3 - || restricted_spell_discipline(otyp)))) { - dealloc_obj(obj); - obj = mkobj(trop->trclass, FALSE); - otyp = obj->otyp; - } - - /* Don't start with +0 or negative rings */ - if (objects[otyp].oc_charged && obj->spe <= 0) - obj->spe = rne(3); - - /* Heavily relies on the fact that 1) we create wands - * before rings, 2) that we create rings before + /* Heavily relies on the facts that 1) we create wands + * before rings, that 2) we create rings before * spellbooks, and that 3) not more than 1 object of a * particular symbol is to be prohibited. (For more * objects, we need more nocreate variables...) @@ -1039,34 +1326,22 @@ register struct trobj *trop; case WAN_POLYMORPH: case RIN_POLYMORPH: case POT_POLYMORPH: - nocreate = RIN_POLYMORPH_CONTROL; + gn.nocreate = RIN_POLYMORPH_CONTROL; break; case RIN_POLYMORPH_CONTROL: - nocreate = RIN_POLYMORPH; - nocreate2 = SPE_POLYMORPH; - nocreate3 = POT_POLYMORPH; + gn.nocreate = RIN_POLYMORPH; + gn.nocreate2 = SPE_POLYMORPH; + gn.nocreate3 = POT_POLYMORPH; } /* Don't have 2 of the same ring or spellbook */ if (obj->oclass == RING_CLASS || obj->oclass == SPBOOK_CLASS) - nocreate4 = otyp; + gn.nocreate4 = otyp; } + /* Put post-creation object adjustments that don't depend on whether + * it was UNDEF_TYP or not after this. */ - if (urace.malenum != PM_HUMAN) { - /* substitute race-specific items; this used to be in - the 'if (otyp != UNDEF_TYP) { }' block above, but then - substitutions didn't occur for randomly generated items - (particularly food) which have racial substitutes */ - for (i = 0; inv_subs[i].race_pm != NON_PM; ++i) - if (inv_subs[i].race_pm == urace.malenum - && otyp == inv_subs[i].item_otyp) { - debugpline3("ini_inv: substituting %s for %s%s", - OBJ_NAME(objects[inv_subs[i].subs_otyp]), - (trop->trotyp == UNDEF_TYP) ? "random " : "", - OBJ_NAME(objects[otyp])); - otyp = obj->otyp = inv_subs[i].subs_otyp; - break; - } - } + otyp = ini_inv_obj_substitution(trop, obj); + nhUse(otyp); /* nudist gets no armor */ if (u.uroleplay.nudist && obj->oclass == ARMOR_CLASS) { @@ -1075,90 +1350,71 @@ register struct trobj *trop; continue; } - if (trop->trclass == COIN_CLASS) { - /* no "blessed" or "identified" money */ - obj->quan = u.umoney0; - } else { - if (objects[otyp].oc_uses_known) - obj->known = 1; - obj->dknown = obj->bknown = obj->rknown = 1; - if (Is_container(obj) || obj->otyp == STATUE) { - obj->cknown = obj->lknown = 1; - obj->otrapped = 0; - } - obj->cursed = 0; - if (obj->opoisoned && u.ualign.type != A_CHAOTIC) - obj->opoisoned = 0; - if (obj->oclass == WEAPON_CLASS || obj->oclass == TOOL_CLASS) { - obj->quan = (long) trop->trquan; - trop->trquan = 1; - } else if (obj->oclass == GEM_CLASS && is_graystone(obj) - && obj->otyp != FLINT) { - obj->quan = 1L; - } - if (trop->trspe != UNDEF_SPE) - obj->spe = trop->trspe; - if (trop->trbless != UNDEF_BLESS) - obj->blessed = trop->trbless; - } - /* defined after setting otyp+quan + blessedness */ - obj->owt = weight(obj); + if (ini_inv_adjust_obj(trop, obj)) + quan = 1; obj = addinv(obj); - /* Make the type known if necessary */ - if (OBJ_DESCR(objects[otyp]) && obj->known) - discover_object(otyp, TRUE, FALSE); - if (otyp == OIL_LAMP) - discover_object(POT_OIL, TRUE, FALSE); - - if (obj->oclass == ARMOR_CLASS) { - if (is_shield(obj) && !uarms && !(uwep && bimanual(uwep))) { - setworn(obj, W_ARMS); - /* Prior to 3.6.2 this used to unset uswapwep if it was set, but - wearing a shield doesn't prevent having an alternate - weapon ready to swap with the primary; just make sure we - aren't two-weaponing (academic; no one starts that way) */ - u.twoweap = FALSE; - } else if (is_helmet(obj) && !uarmh) - setworn(obj, W_ARMH); - else if (is_gloves(obj) && !uarmg) - setworn(obj, W_ARMG); - else if (is_shirt(obj) && !uarmu) - setworn(obj, W_ARMU); - else if (is_cloak(obj) && !uarmc) - setworn(obj, W_ARMC); - else if (is_boots(obj) && !uarmf) - setworn(obj, W_ARMF); - else if (is_suit(obj) && !uarm) - setworn(obj, W_ARM); - } - - if (obj->oclass == WEAPON_CLASS || is_weptool(obj) - || otyp == TIN_OPENER || otyp == FLINT || otyp == ROCK) { - if (is_ammo(obj) || is_missile(obj)) { - if (!uquiver) - setuqwep(obj); - } else if (!uwep && (!uarms || !bimanual(obj))) { - setuwep(obj); - } else if (!uswapwep) { - setuswapwep(obj); - } - } - if (obj->oclass == SPBOOK_CLASS && obj->otyp != SPE_BLANK_PAPER) - initialspell(obj); + /* First spellbook should be level 1 - did we get it? */ + if (obj->oclass == SPBOOK_CLASS && objects[obj->otyp].oc_level == 1) + got_sp1 = TRUE; -#if !defined(PYRAMID_BUG) && !defined(MAC) - if (--trop->trquan) + if (--quan) continue; /* make a similar object */ -#else - if (trop->trquan) { /* check if zero first */ - --trop->trquan; - if (trop->trquan) - continue; /* make a similar object */ - } -#endif trop++; + quan = trquan(trop); } } +/* initialise starting inventory and attributes + + this function can be run multiple times and will overwrite the effects of + previous runs */ +void +u_init_inventory_attrs(void) +{ + gl.lastinvnr = 51; + while (gi.invent) + useupall(gi.invent); + + u.umoney0 = 0; + u_init_role(); + u_init_race(); + + if (discover) + ini_inv(Wishing); + + if (u.umoney0) + ini_inv(Money); + u.umoney0 += hidden_gold(TRUE); /* in case sack has gold in it */ + + init_attr(75); /* init attribute values */ + vary_init_attr(); /* minor variation to attrs */ + u_init_carry_attr_boost(); +} + +/* side effects of starting inventory (e.g. discovering it) and skills (both + those based on role and those based on starting inventory) */ +void +u_init_skills_discoveries(void) +{ + struct obj *otmp; + for (otmp = gi.invent; otmp; otmp = otmp->nobj) + ini_inv_use_obj(otmp); + + skill_init(skills_for_role()); + if (u.uroleplay.pauper) + pauper_reinit(); + + /* If we have at least one spell, force starting Pw to be enough, + so hero can cast the level 1 spell they should have */ + if (num_spells() && (u.uenmax < SPELL_LEV_PW(1))) + u.uen = u.uenmax = u.uenpeak = u.ueninc[u.ulevel] = SPELL_LEV_PW(1); + + find_ac(); /* get initial ac value */ +} + +#undef UNDEF_TYP +#undef UNDEF_SPE +#undef UNDEF_BLESS + /*u_init.c*/ diff --git a/src/uhitm.c b/src/uhitm.c index 2eedb184f..ada2fc1bd 100644 --- a/src/uhitm.c +++ b/src/uhitm.c @@ -1,40 +1,129 @@ -/* NetHack 3.6 uhitm.c $NHDT-Date: 1573764936 2019/11/14 20:55:36 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.215 $ */ +/* NetHack 5.0 uhitm.c $NHDT-Date: 1752823766 2025/07/17 23:29:26 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.477 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2012. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -STATIC_DCL boolean FDECL(known_hitum, (struct monst *, struct obj *, int *, - int, int, struct attack *, int)); -STATIC_DCL boolean FDECL(theft_petrifies, (struct obj *)); -STATIC_DCL void FDECL(steal_it, (struct monst *, struct attack *)); -STATIC_DCL boolean FDECL(hitum_cleave, (struct monst *, struct attack *)); -STATIC_DCL boolean FDECL(hitum, (struct monst *, struct attack *)); -STATIC_DCL boolean FDECL(hmon_hitmon, (struct monst *, struct obj *, int, - int)); -STATIC_DCL int FDECL(joust, (struct monst *, struct obj *)); -STATIC_DCL void NDECL(demonpet); -STATIC_DCL boolean FDECL(m_slips_free, (struct monst *, struct attack *)); -STATIC_DCL int FDECL(explum, (struct monst *, struct attack *)); -STATIC_DCL void FDECL(start_engulf, (struct monst *)); -STATIC_DCL void NDECL(end_engulf); -STATIC_DCL int FDECL(gulpum, (struct monst *, struct attack *)); -STATIC_DCL boolean FDECL(hmonas, (struct monst *)); -STATIC_DCL void FDECL(nohandglow, (struct monst *)); -STATIC_DCL boolean FDECL(shade_aware, (struct obj *)); - -extern boolean notonhead; /* for long worms */ - -/* Used to flag attacks caused by Stormbringer's maliciousness. */ -static boolean override_confirmation = FALSE; +static const char brief_feeling[] = + "have a %s feeling for a moment, then it passes."; + +staticfn boolean mhitm_mgc_atk_negated(struct monst *, struct monst *, + boolean) NONNULLPTRS; +staticfn boolean known_hitum(struct monst *, struct obj *, int *, int, int, + struct attack *, int) NONNULLARG13; +staticfn boolean theft_petrifies(struct obj *) NONNULLARG1; +staticfn void mhitm_really_poison(struct monst *, struct attack *, + struct monst *, struct mhitm_data *); +staticfn void steal_it(struct monst *, struct attack *) NONNULLARG1; +/* hitum_cleave() has contradictory information. There's a comment + * beside the 1st arg 'target' stating non-null, but later on there + * is a test for 'target' being null */ +staticfn boolean hitum_cleave(struct monst *, struct attack *) NO_NNARGS; +staticfn boolean double_punch(void); +staticfn boolean hitum(struct monst *, struct attack *) NONNULLARG1; +staticfn void hmon_hitmon_barehands(struct _hitmon_data *, + struct monst *) NONNULLARG12; +staticfn void hmon_hitmon_weapon_ranged(struct _hitmon_data *, struct monst *, + struct obj *) NONNULLARG123; +staticfn boolean backstabbable(struct monst *) NONNULLARG1; +staticfn void hmon_hitmon_weapon_melee(struct _hitmon_data *, struct monst *, + struct obj *) NONNULLARG123; +staticfn void hmon_hitmon_weapon(struct _hitmon_data *, struct monst *, + struct obj *) NONNULLARG123; +staticfn void hmon_hitmon_potion(struct _hitmon_data *, struct monst *, + struct obj *) NONNULLARG123; +staticfn void hmon_hitmon_misc_obj(struct _hitmon_data *, struct monst *, + struct obj *) NONNULLARG123; +staticfn void hmon_hitmon_do_hit(struct _hitmon_data *, struct monst *, + struct obj *) NONNULLARG12; +staticfn void hmon_hitmon_dmg_recalc(struct _hitmon_data *, struct obj *); +staticfn void hmon_hitmon_poison(struct _hitmon_data *, struct monst *, + struct obj *) NONNULLARG123; +staticfn void hmon_hitmon_jousting(struct _hitmon_data *, struct monst *, + struct obj *) NONNULLARG123; +staticfn void hmon_hitmon_stagger(struct _hitmon_data *, struct monst *, + struct obj *) NONNULLARG12; +staticfn void hmon_hitmon_pet(struct _hitmon_data *, struct monst *, + struct obj *) NONNULLARG12; +staticfn void hmon_hitmon_splitmon(struct _hitmon_data *, struct monst *, + struct obj *) NONNULLARG12; +staticfn void hmon_hitmon_msg_hit(struct _hitmon_data *, struct monst *, + struct obj *) NONNULLARG12; +staticfn void hmon_hitmon_msg_silver(struct _hitmon_data *, struct monst *, + struct obj *) NONNULLARG12; +staticfn void hmon_hitmon_msg_lightobj(struct _hitmon_data *, struct monst *, + struct obj *) NONNULLARG12; +staticfn boolean hmon_hitmon(struct monst *, struct obj *, int, int) + NONNULLARG1; +staticfn int joust(struct monst *, struct obj *) NONNULLARG12; +staticfn void demonpet(void); +staticfn boolean m_slips_free(struct monst *, struct attack *) NONNULLPTRS; +staticfn void start_engulf(struct monst *) NONNULLARG1; +staticfn void end_engulf(void); +staticfn int gulpum(struct monst *, struct attack *) NONNULLPTRS; +staticfn boolean hmonas(struct monst *) NONNULLARG1; +staticfn void nohandglow(struct monst *) NONNULLARG1; +staticfn boolean mhurtle_to_doom(struct monst *, int, + struct permonst **) NONNULLARG13; +staticfn void first_weapon_hit(struct obj *) NONNULLARG1; +staticfn boolean shade_aware(struct obj *) NO_NNARGS; #define PROJECTILE(obj) ((obj) && is_ammo(obj)) +staticfn boolean +mhitm_mgc_atk_negated( + struct monst *magr, struct monst *mdef, + boolean verbosely) /* give mesg if magical cancellation prevents damage */ +{ + int armpro; + boolean negated; + + /* mcan doesn't apply to youmonst; hero can't be cancelled */ + if (magr != &gy.youmonst && magr->mcan) + return TRUE; /* no message if attacker has been cancelled */ + + armpro = magic_negation(mdef); + negated = !(rn2(10) >= 3 * armpro); + if (negated) { + /* attack has been thwarted by negation, aka magical cancellation */ + if (verbosely) { + if (mdef == &gy.youmonst) + You("avoid harm."); + else if (gv.vis && canseemon(mdef)) + pline_mon(mdef, "%s avoids harm.", Monnam(mdef)); + } + return TRUE; + } + return FALSE; +} + +/* multi_reason is usually a literal string; here we generate one that + has the causing monster's type included */ +void +dynamic_multi_reason(struct monst *mon, const char *verb, boolean by_gaze) +{ + /* combination of noname_monnam() and m_monnam(), more or less; + accurate regardless of visibility or hallucination (only seen + if game ends) and without personal name (M2_PNAME excepted) */ + char *who = x_monnam(mon, ARTICLE_A, (char *) 0, + (SUPPRESS_IT | SUPPRESS_INVISIBLE + | SUPPRESS_HALLUCINATION | SUPPRESS_SADDLE + | SUPPRESS_NAME), + FALSE), + *p = gm.multireasonbuf; + + /* prefix info for done_in_by() */ + Sprintf(p, "%u:", mon->m_id); + p = eos(p); + Sprintf(p, "%s by %s%s", verb, + !by_gaze ? who : s_suffix(who), + !by_gaze ? "" : " gaze"); + gm.multi_reason = p; +} + void -erode_armor(mdef, hurt) -struct monst *mdef; -int hurt; +erode_armor(struct monst *mdef, int hurt) { struct obj *target; @@ -97,20 +186,20 @@ int hurt; /* FALSE means it's OK to attack */ boolean -attack_checks(mtmp, wep) -register struct monst *mtmp; -struct obj *wep; /* uwep for attack(), null for kick_monster() */ +attack_checks( + struct monst *mtmp, /* target */ + struct obj *wep) /* uwep for do_attack(), null for kick_monster() */ { int glyph; /* if you're close enough to attack, alert any waiting monster */ mtmp->mstrategy &= ~STRAT_WAITMASK; - if (u.uswallow && mtmp == u.ustuck) + if (engulfing_u(mtmp)) return FALSE; - if (context.forcefight) { - /* Do this in the caller, after we checked that the monster + if (svc.context.forcefight) { + /* Do this in the caller, after we have checked that the monster * didn't die from the blow. Reason: putting the 'I' there * causes the hero to forget the square's contents since * both 'I' and remembered contents are stored in .glyph. @@ -118,17 +207,17 @@ struct obj *wep; /* uwep for attack(), null for kick_monster() */ * not stay there, so the player will have suddenly forgotten * the square's contents for no apparent reason. if (!canspotmon(mtmp) - && !glyph_is_invisible(levl[bhitpos.x][bhitpos.y].glyph)) - map_invisible(bhitpos.x, bhitpos.y); + && !glyph_is_invisible(levl[gb.bhitpos.x][gb.bhitpos.y].glyph)) + map_invisible(gb.bhitpos.x, gb.bhitpos.y); */ return FALSE; } /* cache the shown glyph; cases which might change it (by placing or removing - 'rembered, unseen monster' glyph or revealing a mimic) + 'remembered, unseen monster' glyph or revealing a mimic) always return without further reference to this */ - glyph = glyph_at(bhitpos.x, bhitpos.y); + glyph = glyph_at(gb.bhitpos.x, gb.bhitpos.y); /* Put up an invisible monster marker, but with exceptions for * monsters that hide and monsters you've been warned about. @@ -142,15 +231,15 @@ struct obj *wep; /* uwep for attack(), null for kick_monster() */ && !glyph_is_warning(glyph) && !glyph_is_invisible(glyph) && !(!Blind && mtmp->mundetected && hides_under(mtmp->data))) { pline("Wait! There's %s there you can't see!", something); - map_invisible(bhitpos.x, bhitpos.y); + map_invisible(gb.bhitpos.x, gb.bhitpos.y); /* if it was an invisible mimic, treat it as if we stumbled * onto a visible mimic */ - if (M_AP_TYPE(mtmp) && !Protection_from_shape_changers - /* applied pole-arm attack is too far to get stuck */ - && distu(mtmp->mx, mtmp->my) <= 2) { - if (!u.ustuck && !mtmp->mflee && dmgtype(mtmp->data, AD_STCK)) - u.ustuck = mtmp; + if (M_AP_TYPE(mtmp) && !Protection_from_shape_changers) { + if (!u.ustuck && !mtmp->mflee && dmgtype(mtmp->data, AD_STCK) + /* applied pole-arm attack is too far to get stuck */ + && m_next2u(mtmp)) + set_ustuck(mtmp); } /* #H7329 - if hero is on engraved "Elbereth", this will end up * assessing an alignment penalty and removing the engraving @@ -185,17 +274,24 @@ struct obj *wep; /* uwep for attack(), null for kick_monster() */ seemimic(mtmp); return FALSE; } - if (!((Blind ? Blind_telepat : Unblind_telepat) || Detect_monsters)) { + if (!tp_sensemon(mtmp) && !Detect_monsters) { struct obj *obj; + char lmonbuf[BUFSZ]; + boolean notseen; + Strcpy(lmonbuf, l_monnam(mtmp)); + /* might be unseen if invisible and hero can't see invisible */ + notseen = !strcmp(lmonbuf, "it"); /* note: not strcmpi() */ if (!Blind && Hallucination) - pline("A %s %s appeared!", - mtmp->mtame ? "tame" : "wild", l_monnam(mtmp)); + pline("A %s %s %s!", mtmp->mtame ? "tame" : "wild", + notseen ? "creature" : (const char *) lmonbuf, + notseen ? "is present" : "appears"); else if (Blind || (is_pool(mtmp->mx, mtmp->my) && !Underwater)) pline("Wait! There's a hidden monster there!"); - else if ((obj = level.objects[mtmp->mx][mtmp->my]) != 0) + else if ((obj = svl.level.objects[mtmp->mx][mtmp->my]) != 0) pline("Wait! There's %s hiding under %s!", - an(l_monnam(mtmp)), doname(obj)); + notseen ? something : (const char *) an(lmonbuf), + doname(obj)); return TRUE; } } @@ -212,8 +308,8 @@ struct obj *wep; /* uwep for attack(), null for kick_monster() */ if (flags.confirm && mtmp->mpeaceful && !Confusion && !Hallucination && !Stunned) { /* Intelligent chaotic weapons (Stormbringer) want blood */ - if (wep && wep->oartifact == ART_STORMBRINGER) { - override_confirmation = TRUE; + if (is_art(wep, ART_STORMBRINGER)) { + go.override_confirmation = TRUE; return FALSE; } if (canspotmon(mtmp)) { @@ -221,7 +317,7 @@ struct obj *wep; /* uwep for attack(), null for kick_monster() */ Sprintf(qbuf, "Really attack %s?", mon_nam(mtmp)); if (!paranoid_query(ParanoidHit, qbuf)) { - context.move = 0; + svc.context.move = 0; return TRUE; } } @@ -230,18 +326,16 @@ struct obj *wep; /* uwep for attack(), null for kick_monster() */ return FALSE; } -/* - * It is unchivalrous for a knight to attack the defenseless or from behind. - */ +/* it is unchivalrous for a knight to attack the defenseless or from behind */ void -check_caitiff(mtmp) -struct monst *mtmp; +check_caitiff(struct monst *mtmp) { if (u.ualign.record <= -10) return; if (Role_if(PM_KNIGHT) && u.ualign.type == A_LAWFUL - && (!mtmp->mcanmove || mtmp->msleeping + && !is_undead(mtmp->data) + && (helpless(mtmp) || (mtmp->mflee && !mtmp->mavenge))) { You("caitiff!"); adjalign(-1); @@ -252,19 +346,36 @@ struct monst *mtmp; } } +/* maybe unparalyze monster */ +void +mon_maybe_unparalyze(struct monst *mtmp) +{ + if (!mtmp->mcanmove) { + if (!rn2(10)) { + mtmp->mcanmove = 1; + mtmp->mfrozen = 0; + } + } +} + +/* how easy it is for hero to hit a monster, + using attack type aatyp and/or weapon. + larger value == easier to hit */ int -find_roll_to_hit(mtmp, aatyp, weapon, attk_count, role_roll_penalty) -register struct monst *mtmp; -uchar aatyp; /* usually AT_WEAP or AT_KICK */ -struct obj *weapon; /* uwep or uswapwep or NULL */ -int *attk_count, *role_roll_penalty; +find_roll_to_hit( + struct monst *mtmp, + uchar aatyp, /* usually AT_WEAP or AT_KICK */ + struct obj *weapon, /* uwep or uswapwep or NULL */ + int *attk_count, + int *role_roll_penalty) { int tmp, tmp2; *role_roll_penalty = 0; /* default is `none' */ - tmp = 1 + Luck + abon() + find_mac(mtmp) + u.uhitinc - + maybe_polyd(youmonst.data->mlevel, u.ulevel); + tmp = 1 + abon() + find_mac(mtmp) + u.uhitinc + + (sgn(Luck) * ((abs(Luck) + 2) / 3)) + + maybe_polyd(gy.youmonst.data->mlevel, u.ulevel); /* some actions should occur only once during multiple attacks */ if (!(*attk_count)++) { @@ -272,33 +383,25 @@ int *attk_count, *role_roll_penalty; check_caitiff(mtmp); } - /* adjust vs. (and possibly modify) monster state */ + /* adjust vs. monster state */ if (mtmp->mstun) tmp += 2; if (mtmp->mflee) tmp += 2; - - if (mtmp->msleeping) { - mtmp->msleeping = 0; + if (mtmp->msleeping) tmp += 2; - } - if (!mtmp->mcanmove) { + if (!mtmp->mcanmove) tmp += 4; - if (!rn2(10)) { - mtmp->mcanmove = 1; - mtmp->mfrozen = 0; - } - } /* role/race adjustments */ if (Role_if(PM_MONK) && !Upolyd) { if (uarm) - tmp -= (*role_roll_penalty = urole.spelarmr); + tmp -= (*role_roll_penalty = gu.urole.spelarmr); else if (!uwep && !uarms) tmp += (u.ulevel / 3) + 2; } if (is_orc(mtmp->data) - && maybe_polyd(is_elf(youmonst.data), Race_if(PM_ELF))) + && maybe_polyd(is_elf(gy.youmonst.data), Race_if(PM_ELF))) tmp++; /* encumbrance: with a lot of luggage, your agility diminishes */ @@ -323,13 +426,28 @@ int *attk_count, *role_roll_penalty; return tmp; } +/* temporarily override 'safepet' (by faking use of 'F' prefix) when possibly + unintentionally attacking peaceful monsters and optionally pets */ +boolean +force_attack(struct monst *mtmp, boolean pets_too) +{ + boolean attacked, save_Forcefight; + + save_Forcefight = svc.context.forcefight; + /* always set forcefight On for hostiles and peacefuls, maybe for pets */ + if (pets_too || !mtmp->mtame) + svc.context.forcefight = TRUE; + attacked = do_attack(mtmp); + svc.context.forcefight = save_Forcefight; + return attacked; +} + /* try to attack; return False if monster evaded; u.dx and u.dy must be set */ boolean -attack(mtmp) -register struct monst *mtmp; +do_attack(struct monst *mtmp) { - register struct permonst *mdat = mtmp->data; + struct permonst *mdat = mtmp->data; /* This section of code provides protection against accidentally * hitting peaceful (like '@') and tame (like 'd') monsters. @@ -341,44 +459,51 @@ register struct monst *mtmp; * you'll usually just swap places if this is a movement command */ /* Intelligent chaotic weapons (Stormbringer) want blood */ - if (is_safepet(mtmp) && !context.forcefight) { - if (!uwep || uwep->oartifact != ART_STORMBRINGER) { + if (is_safemon(mtmp) && !svc.context.forcefight) { + if (!u_wield_art(ART_STORMBRINGER)) { /* There are some additional considerations: this won't work * if in a shop or Punished or you miss a random roll or * if you can walk thru walls and your pet cannot (KAA) or * if your pet is a long worm with a tail. * There's also a chance of displacing a "frozen" monster: * sleeping monsters might magically walk in their sleep. + * This block of code used to only be called for pets; now + * that it also applies to peacefuls, non-pets mustn't be + * forced to flee. */ boolean foo = (Punished || !rn2(7) - || (is_longworm(mtmp->data) && mtmp->wormno)), + || (is_longworm(mtmp->data) && mtmp->wormno) + || (IS_OBSTRUCTED(levl[u.ux][u.uy].typ) + && !passes_walls(mtmp->data))), inshop = FALSE; char *p; /* only check for in-shop if don't already have reason to stop */ if (!foo) { for (p = in_rooms(mtmp->mx, mtmp->my, SHOPBASE); *p; p++) - if (tended_shop(&rooms[*p - ROOMOFFSET])) { + if (tended_shop(&svr.rooms[*p - ROOMOFFSET])) { inshop = TRUE; break; } } - if (inshop || foo || (IS_ROCK(levl[u.ux][u.uy].typ) - && !passes_walls(mtmp->data))) { + if (inshop || foo) { char buf[BUFSZ]; - monflee(mtmp, rnd(6), FALSE, FALSE); + if (!svc.context.travel && !svc.context.run) + if (canspotmon(mtmp) && mtmp->isshk) + return ECMD_TIME | dopay(); + + if (mtmp->mtame) /* see 'additional considerations' above */ + monflee(mtmp, rnd(6), FALSE, FALSE); Strcpy(buf, y_monnam(mtmp)); buf[0] = highc(buf[0]); You("stop. %s is in the way!", buf); - context.travel = context.travel1 = context.mv = context.run - = 0; + end_running(TRUE); return TRUE; - } else if ((mtmp->mfrozen || (!mtmp->mcanmove) - || (mtmp->data->mmove == 0)) && rn2(6)) { + } else if (mtmp->mfrozen || helpless(mtmp) + || (mtmp->data->mmove == 0 && rn2(6))) { pline("%s doesn't seem to move!", Monnam(mtmp)); - context.travel = context.travel1 = context.mv = context.run - = 0; + end_running(TRUE); return TRUE; } else return FALSE; @@ -387,16 +512,16 @@ register struct monst *mtmp; /* possibly set in attack_checks; examined in known_hitum, called via hitum or hmonas below */ - override_confirmation = FALSE; + go.override_confirmation = FALSE; /* attack_checks() used to use directly, now - it uses bhitpos instead; it might map an invisible monster there */ - bhitpos.x = u.ux + u.dx; - bhitpos.y = u.uy + u.dy; - notonhead = (bhitpos.x != mtmp->mx || bhitpos.y != mtmp->my); + it uses gb.bhitpos instead; it might map an invisible monster there */ + gb.bhitpos.x = u.ux + u.dx; + gb.bhitpos.y = u.uy + u.dy; + gn.notonhead = (gb.bhitpos.x != mtmp->mx || gb.bhitpos.y != mtmp->my); if (attack_checks(mtmp, uwep)) return TRUE; - if (Upolyd && noattacks(youmonst.data)) { + if (Upolyd && noattacks(gy.youmonst.data)) { /* certain "pacifist" monsters don't attack */ You("have no way to attack monsters physically."); mtmp->mstrategy &= ~STRAT_WAITMASK; @@ -411,12 +536,12 @@ register struct monst *mtmp; if (u.twoweap && !can_twoweapon()) untwoweapon(); - if (unweapon) { - unweapon = FALSE; + if (gu.unweapon) { + gu.unweapon = FALSE; if (flags.verbose) { if (uwep) You("begin bashing monsters with %s.", yname(uwep)); - else if (!cantwield(youmonst.data)) + else if (!cantwield(gy.youmonst.data)) You("begin %s monsters with your %s %s.", ing_suffix(Role_if(PM_MONK) ? "strike" : "bash"), uarmg ? "gloved" : "bare", /* Del Lamb */ @@ -428,9 +553,9 @@ register struct monst *mtmp; u_wipe_engr(3); /* Is the "it died" check actually correct? */ - if (mdat->mlet == S_LEPRECHAUN && !mtmp->mfrozen && !mtmp->msleeping + if (mdat->mlet == S_LEPRECHAUN && !mtmp->mfrozen && !helpless(mtmp) && !mtmp->mconf && mtmp->mcansee && !rn2(7) - && (m_move(mtmp, 0) == 2 /* it died */ + && (m_move(mtmp, 0) == MMOVE_DIED /* it died */ || mtmp->mx != u.ux + u.dx || mtmp->my != u.uy + u.dy)) { /* it moved */ You("miss wildly and stumble forwards."); @@ -440,7 +565,7 @@ register struct monst *mtmp; if (Upolyd) (void) hmonas(mtmp); else - (void) hitum(mtmp, youmonst.data->mattk); + (void) hitum(mtmp, gy.youmonst.data->mattk); mtmp->mstrategy &= ~STRAT_WAITMASK; atk_done: @@ -449,29 +574,32 @@ register struct monst *mtmp; * and it returned 0 (it's okay to attack), and the monster didn't * evade. */ - if (context.forcefight && !DEADMONSTER(mtmp) && !canspotmon(mtmp) + if (svc.context.forcefight && !DEADMONSTER(mtmp) && !canspotmon(mtmp) && !glyph_is_invisible(levl[u.ux + u.dx][u.uy + u.dy].glyph) - && !(u.uswallow && mtmp == u.ustuck)) + && !engulfing_u(mtmp)) map_invisible(u.ux + u.dx, u.uy + u.dy); return TRUE; } /* really hit target monster; returns TRUE if it still lives */ -STATIC_OVL boolean -known_hitum(mon, weapon, mhit, rollneeded, armorpenalty, uattk, dieroll) -register struct monst *mon; -struct obj *weapon; -int *mhit; -int rollneeded, armorpenalty; /* for monks */ -struct attack *uattk; -int dieroll; +staticfn boolean +known_hitum( + struct monst *mon, /* target */ + struct obj *weapon, /* uwep or uswapwep */ + int *mhit, /* *mhit is 1 or 0; hit (1) gets changed to miss (0) + * for decapitation attack against headless target */ + int rollneeded, /* rollneeded and armorpenalty are used for monks +*/ + int armorpenalty, /*+ wearing suits so miss message can vary for missed + * because of penalty vs would have missed anyway */ + struct attack *uattk, + int dieroll) { boolean malive = TRUE, /* hmon() might destroy weapon; remember aspect for cutworm */ slice_or_chop = (weapon && (is_blade(weapon) || is_axe(weapon))); - if (override_confirmation) { + if (go.override_confirmation) { /* this may need to be generalized if weapons other than Stormbringer acquire similar anti-social behavior... */ if (flags.verbose) @@ -490,17 +618,18 @@ int dieroll; /* we hit the monster; be careful: it might die or be knocked into a different location */ - notonhead = (mon->mx != bhitpos.x || mon->my != bhitpos.y); + gn.notonhead = (mon->mx != gb.bhitpos.x || mon->my != gb.bhitpos.y); malive = hmon(mon, weapon, HMON_MELEE, dieroll); if (malive) { /* monster still alive */ if (!rn2(25) && mon->mhp < mon->mhpmax / 2 - && !(u.uswallow && mon == u.ustuck)) { + && !engulfing_u(mon)) { /* maybe should regurgitate if swallowed? */ monflee(mon, !rn2(3) ? rnd(100) : 0, FALSE, TRUE); - if (u.ustuck == mon && !u.uswallow && !sticks(youmonst.data)) - u.ustuck = 0; + if (u.ustuck == mon && !u.uswallow + && !sticks(gy.youmonst.data)) + set_ustuck((struct monst *) 0); } /* Vorpal Blade hit converted to miss */ /* could be headless monster or worm tail */ @@ -510,7 +639,7 @@ int dieroll; u.uconduct.weaphit = oldweaphit; } if (mon->wormno && *mhit) - cutworm(mon, bhitpos.x, bhitpos.y, slice_or_chop); + cutworm(mon, gb.bhitpos.x, gb.bhitpos.y, slice_or_chop); } } return malive; @@ -518,10 +647,10 @@ int dieroll; /* hit the monster next to you and the monsters to the left and right of it; return False if the primary target is killed, True otherwise */ -STATIC_OVL boolean -hitum_cleave(target, uattk) -struct monst *target; /* non-Null; forcefight at nothing doesn't cleave... */ -struct attack *uattk; /* ... but we don't enforce that here; Null works ok */ +staticfn boolean +hitum_cleave( + struct monst *target, /* non-Null; forcefight at nothing doesn't cleave +*/ + struct attack *uattk) /*+ but we don't enforce that here; Null works ok */ { /* swings will be delivered in alternate directions; with consecutive attacks it will simulate normal swing and backswing; when swings @@ -529,15 +658,14 @@ struct attack *uattk; /* ... but we don't enforce that here; Null works ok */ with a backswing--that doesn't impact actual play, just spoils the simulation attempt a bit */ static boolean clockwise = FALSE; - unsigned i; + int i; coord save_bhitpos; + boolean save_notonhead; int count, umort, x = u.ux, y = u.uy; /* find the direction toward primary target */ - for (i = 0; i < 8; ++i) - if (xdir[i] == u.dx && ydir[i] == u.dy) - break; - if (i == 8) { + i = xytodir(u.dx, u.dy); + if (i == DIR_ERR) { impossible("hitum_cleave: unknown target direction [%d,%d,%d]?", u.dx, u.dy, u.dz); return TRUE; /* target hasn't been killed */ @@ -545,9 +673,10 @@ struct attack *uattk; /* ... but we don't enforce that here; Null works ok */ /* adjust direction by two so that loop's increment (for clockwise) or decrement (for counter-clockwise) will point at the spot next to primary target */ - i = (i + (clockwise ? 6 : 2)) % 8; + i = clockwise ? DIR_LEFT2(i) : DIR_RIGHT2(i); umort = u.umortality; /* used to detect life-saving */ - save_bhitpos = bhitpos; + save_bhitpos = gb.bhitpos; + save_notonhead = gn.notonhead; /* * Three attacks: adjacent to primary, primary, adjacent on other @@ -559,10 +688,10 @@ struct attack *uattk; /* ... but we don't enforce that here; Null works ok */ */ for (count = 3; count > 0; --count) { struct monst *mtmp; - int tx, ty, tmp, dieroll, mhit, attknum, armorpenalty; + int tx, ty, tmp, dieroll, mhit, attknum = 0, armorpenalty; /* ++i, wrap 8 to i=0 /or/ --i, wrap -1 to i=7 */ - i = (i + (clockwise ? 1 : 7)) % 8; + i = clockwise ? DIR_RIGHT(i) : DIR_LEFT(i); tx = x + xdir[i], ty = y + ydir[i]; /* current target location */ if (!isok(tx, ty)) @@ -576,81 +705,121 @@ struct attack *uattk; /* ... but we don't enforce that here; Null works ok */ tmp = find_roll_to_hit(mtmp, uattk->aatyp, uwep, &attknum, &armorpenalty); + mon_maybe_unparalyze(mtmp); dieroll = rnd(20); mhit = (tmp > dieroll); - bhitpos.x = tx, bhitpos.y = ty; /* normally set up by attack() */ + gb.bhitpos.x = tx, gb.bhitpos.y = ty; /* normally set by do_attack() */ + gn.notonhead = (mtmp->mx != tx || mtmp->my != ty); (void) known_hitum(mtmp, uwep, &mhit, tmp, armorpenalty, uattk, dieroll); (void) passive(mtmp, uwep, mhit, !DEADMONSTER(mtmp), AT_WEAP, !uwep); - /* stop attacking if weapon is gone or hero got killed and - life-saved after passive counter-attack */ - if (!uwep || u.umortality > umort) + /* stop attacking if weapon is gone or hero got paralyzed or + killed (and then life-saved) by passive counter-attack */ + if (!uwep || gm.multi < 0 || u.umortality > umort) break; } /* set up for next time */ clockwise = !clockwise; /* alternate */ - bhitpos = save_bhitpos; /* in case somebody relies on bhitpos - * designating the primary target */ + gb.bhitpos = save_bhitpos; /* in case somebody relies on bhitpos + * designating the primary target */ + gn.notonhead = save_notonhead; /* return False if primary target died, True otherwise; note: if 'target' was nonNull upon entry then it's still nonNull even if *target died */ return (target && DEADMONSTER(target)) ? FALSE : TRUE; } +/* returns True if hero is fighting without a weapon and without a shield and + has sufficient skill in bare-handed/martial arts to attack twice */ +staticfn boolean +double_punch(void) +{ + /* note: P_BARE_HANDED_COMBAT and P_MARTIAL_ARTS are equivalent */ + int skl_lvl = P_SKILL(P_BARE_HANDED_COMBAT); + + /* + * Chance to attempt a second bare-handed or martial arts hit: + * restricted (0), [not applicable; no one is restricted] + * unskilled (1) : 0% + * basic (2) : 0% + * skilled (3) : 20% + * expert (4) : 40% + * master (5) : 60% + * grandmaster (6) : 80% + */ + if (!uwep && !uarms && skl_lvl > P_BASIC) + return (skl_lvl - P_BASIC) > rn2(5); + return FALSE; +} + /* hit target monster; returns TRUE if it still lives */ -STATIC_OVL boolean -hitum(mon, uattk) -struct monst *mon; -struct attack *uattk; +staticfn boolean +hitum(struct monst *mon, struct attack *uattk) { boolean malive, wep_was_destroyed = FALSE; - struct obj *wepbefore = uwep; - int armorpenalty, attknum = 0, x = u.ux + u.dx, y = u.uy + u.dy, - tmp = find_roll_to_hit(mon, uattk->aatyp, uwep, - &attknum, &armorpenalty); - int dieroll = rnd(20); - int mhit = (tmp > dieroll || u.uswallow); + struct obj *wepbefore = uwep, + *secondwep = u.twoweap ? uswapwep : (struct obj *) 0; + int tmp, dieroll, mhit, armorpenalty, attknum = 0, + x = u.ux + u.dx, y = u.uy + u.dy, oldumort = u.umortality; /* Cleaver attacks three spots, 'mon' and one on either side of 'mon'; it can't be part of dual-wielding but we guard against that anyway; cleave return value reflects status of primary target ('mon') */ - if (uwep && uwep->oartifact == ART_CLEAVER && !u.twoweap + if (u_wield_art(ART_CLEAVER) && !u.twoweap && !u.uswallow && !u.ustuck && !NODIAG(u.umonnum)) return hitum_cleave(mon, uattk); + /* 0: single hit, 1: first of two hits; affects strength bonus and + silver rings; known_hitum() -> hmon() -> hmon_hitmon() will copy + gt.twohits into struct _hitmon_data hmd.twohits */ + gt.twohits = (uwep ? u.twoweap : double_punch()) ? 1 : 0; + + tmp = find_roll_to_hit(mon, uattk->aatyp, uwep, &attknum, &armorpenalty); + mon_maybe_unparalyze(mon); + dieroll = rnd(20); + mhit = (tmp > dieroll || u.uswallow); if (tmp > dieroll) exercise(A_DEX, TRUE); - /* bhitpos is set up by caller */ + + /* gb.bhitpos is set up by caller */ malive = known_hitum(mon, uwep, &mhit, tmp, armorpenalty, uattk, dieroll); if (wepbefore && !uwep) wep_was_destroyed = TRUE; (void) passive(mon, uwep, mhit, malive, AT_WEAP, wep_was_destroyed); - /* second attack for two-weapon combat; won't occur if Stormbringer - overrode confirmation (assumes Stormbringer is primary weapon) - or if the monster was killed or knocked to different location */ - if (u.twoweap && !override_confirmation && malive && m_at(x, y) == mon) { + /* second attack for two-weapon combat or skilled unarmed combat; + won't occur if Stormbringer overrode confirmation (assumes + Stormbringer is primary weapon), or if hero became paralyzed by + passive counter-attack, or if hero was killed by passive + counter-attack and got life-saved, or if monster was killed or + knocked to different location */ + if (gt.twohits && !(go.override_confirmation + || gm.multi < 0 || u.umortality > oldumort + || !malive || m_at(x, y) != mon)) { + gt.twohits = 2; /* second of 2 hits */ tmp = find_roll_to_hit(mon, uattk->aatyp, uswapwep, &attknum, &armorpenalty); + mon_maybe_unparalyze(mon); dieroll = rnd(20); mhit = (tmp > dieroll || u.uswallow); - malive = known_hitum(mon, uswapwep, &mhit, tmp, armorpenalty, uattk, + malive = known_hitum(mon, secondwep, &mhit, tmp, armorpenalty, uattk, dieroll); /* second passive counter-attack only occurs if second attack hits */ if (mhit) - (void) passive(mon, uswapwep, mhit, malive, AT_WEAP, !uswapwep); + (void) passive(mon, secondwep, mhit, malive, AT_WEAP, + secondwep && !uswapwep); } + gt.twohits = 0; return malive; } /* general "damage monster" routine; return True if mon still alive */ boolean -hmon(mon, obj, thrown, dieroll) -struct monst *mon; -struct obj *obj; -int thrown; /* HMON_xxx (0 => hand-to-hand, other => ranged) */ -int dieroll; +hmon(struct monst *mon, + struct obj *obj, + int thrown, /* HMON_xxx (0 => hand-to-hand, other => ranged) */ + int dieroll) { boolean result, anger_guards; @@ -664,679 +833,1163 @@ int dieroll; return result; } -/* guts of hmon() */ -STATIC_OVL boolean -hmon_hitmon(mon, obj, thrown, dieroll) -struct monst *mon; -struct obj *obj; -int thrown; /* HMON_xxx (0 => hand-to-hand, other => ranged) */ -int dieroll; +/* hero hits monster bare handed */ +staticfn void +hmon_hitmon_barehands(struct _hitmon_data *hmd, struct monst *mon) +{ + long spcdmgflg, silverhit = 0L; /* worn masks */ + + if (hmd->mdat == &mons[PM_SHADE]) { + hmd->dmg = 0; + } else { + /* note: 1..2 or 1..4 can be substantially increased by + strength bonus or skill bonus, usually both... */ + hmd->dmg = rnd(!martial_bonus() ? 2 : 4); + hmd->use_weapon_skill = TRUE; + hmd->train_weapon_skill = (hmd->dmg > 1); + } + + /* Blessed gloves give bonuses when fighting 'bare-handed'. So do + silver rings. Note: rings are worn under gloves, so you don't + get both bonuses, and two silver rings don't give double bonus. + When making only one hit, both rings are checked (backwards + compatibility => playability), but when making two hits, only the + ring on the hand making the attack is checked. */ + spcdmgflg = uarmg ? W_ARMG + : (((hmd->twohits == 0 || hmd->twohits == 1) ? W_RINGR : 0L) + | ((hmd->twohits == 0 || hmd->twohits == 2) ? W_RINGL : 0L)); + hmd->dmg += special_dmgval(&gy.youmonst, mon, spcdmgflg, &silverhit); + + /* copy silverhit info back into struct _hitmon_data *hmd */ + switch (hmd->twohits) { + case 0: /* only one hit being attempted; a silver ring on either hand + * applies but having silver rings on both is same as just one */ + hmd->barehand_silver_rings = (silverhit & (W_RINGR | W_RINGL)) ? 1 : 0; + break; + case 1: /* first of two or more hit attempts; right ring applies */ + hmd->barehand_silver_rings = (silverhit & W_RINGR) ? 1 : 0; + break; + case 2: /* second of two or more hit attempts; left ring applies */ + hmd->barehand_silver_rings = (silverhit & W_RINGL) ? 1 : 0; + break; + default: /* third or later of more than two hit attempts (poly'd hero); + * rings were applied on first and second hits */ + hmd->barehand_silver_rings = 0; + break; + } + if (hmd->barehand_silver_rings > 0) + hmd->silvermsg = TRUE; +} + +staticfn void +hmon_hitmon_weapon_ranged( + struct _hitmon_data *hmd, + struct monst *mon, + struct obj *obj) /* obj is not NULL */ +{ + /* then do only 1-2 points of damage and don't use or + train weapon's skill */ + if (hmd->mdat == &mons[PM_SHADE] && !shade_glare(obj)) + hmd->dmg = 0; + else + hmd->dmg = rnd(2); + if (hmd->material == SILVER && mon_hates_silver(mon)) { + hmd->silvermsg = hmd->silverobj = TRUE; + /* if it will already inflict dmg, make it worse */ + hmd->dmg += rnd((hmd->dmg) ? 20 : 10); + } + if (!hmd->thrown && obj == uwep && obj->otyp == BOOMERANG + && rnl(4) == 4 - 1) { + boolean more_than_1 = (obj->quan > 1L); + + pline("As you hit %s, %s%s breaks into splinters.", + mon_nam(mon), more_than_1 ? "one of " : "", + yname(obj)); + if (!more_than_1) + uwepgone(); /* set gu.unweapon */ + useup(obj); + if (!more_than_1) + obj = (struct obj *) 0; + hmd->hittxt = TRUE; + if (hmd->mdat != &mons[PM_SHADE]) + hmd->dmg++; + } +} + +/* can monster be stabbed in the back? */ +staticfn boolean +backstabbable(struct monst *mon) +{ + return !amorphous(mon->data) + && !is_whirly(mon->data) + && !noncorporeal(mon->data) + && mon->data->mlet != S_BLOB + && mon->data->mlet != S_EYE + && mon->data->mlet != S_FUNGUS + && canseemon(mon) + && (mon->mflee || helpless(mon)); +} + +staticfn void +hmon_hitmon_weapon_melee( + struct _hitmon_data *hmd, + struct monst *mon, + struct obj *obj) /* obj is not NULL */ { - int tmp; - struct permonst *mdat = mon->data; - int barehand_silver_rings = 0; - /* The basic reason we need all these booleans is that we don't want - * a "hit" message when a monster dies, so we have to know how much - * damage it did _before_ outputting a hit message, but any messages - * associated with the damage don't come out until _after_ outputting - * a hit message. - */ - boolean hittxt = FALSE, destroyed = FALSE, already_killed = FALSE; - boolean get_dmg_bonus = TRUE; - boolean ispoisoned = FALSE, needpoismsg = FALSE, poiskilled = FALSE, - unpoisonmsg = FALSE; - boolean silvermsg = FALSE, silverobj = FALSE; - boolean lightobj = FALSE; - boolean valid_weapon_attack = FALSE; - boolean unarmed = !uwep && !uarm && !uarms; - boolean hand_to_hand = (thrown == HMON_MELEE - /* not grapnels; applied implies uwep */ - || (thrown == HMON_APPLIED && is_pole(uwep))); - int jousting = 0; - long silverhit = 0L; int wtype; struct obj *monwep; - char saved_oname[BUFSZ]; - saved_oname[0] = '\0'; + /* "normal" weapon usage */ + hmd->use_weapon_skill = TRUE; + hmd->dmg = dmgval(obj, mon); + /* a minimal hit doesn't exercise proficiency */ + hmd->train_weapon_skill = (hmd->dmg > 1); + + /* Healer with anatomy knowledge */ + if (Role_if(PM_HEALER) && hmd->hand_to_hand + && obj->oclass == WEAPON_CLASS + && objects[obj->otyp].oc_skill == P_KNIFE) + hmd->dmg += min(3, svm.mvitals[monsndx(mon->data)].died / 6); + + /* special attack actions */ + if (!hmd->train_weapon_skill || mon == u.ustuck || u.twoweap + /* Cleaver can hit up to three targets at once so don't + let it also hit from behind or shatter foes' weapons */ + || (hmd->hand_to_hand && is_art(obj, ART_CLEAVER))) { + ; /* no special bonuses */ + } else if (Role_if(PM_ROGUE) && backstabbable(mon) && !Upolyd + /* multi-shot throwing is too powerful here */ + && hmd->hand_to_hand) { + You("strike %s from behind!", mon_nam(mon)); + hmd->dmg += rnd(u.ulevel); + hmd->hittxt = TRUE; + } else if (hmd->dieroll == 2 && obj == uwep + && obj->oclass == WEAPON_CLASS + && (bimanual(obj) + || (Role_if(PM_SAMURAI) && obj->otyp == KATANA + && !uarms)) + && ((wtype = uwep_skill_type()) != P_NONE + && P_SKILL(wtype) >= P_SKILLED) + && ((monwep = MON_WEP(mon)) != 0 + && !is_flimsy(monwep) + && !obj_resists(monwep, + 50 + 15 * (greatest_erosion(obj) + - greatest_erosion(monwep)), + 100))) { + static const char from_your_blow[] = " from the force of your blow!"; + char buf[BUFSZ]; + /* + * 2.5% chance of shattering defender's weapon when + * using a two-handed weapon; less if uwep is rusted. + * [dieroll == 2 is most successful non-beheading or + * -bisecting hit, in case of special artifact damage; + * the percentage chance is (1/20)*(50/100).] + * If attacker's weapon is rustier than defender's, + * the obj_resists chance is increased so the shatter + * chance is decreased; if less rusty, then vice versa. + */ + setmnotwielded(mon, monwep); + mon->weapon_check = NEED_WEAPON; + if (canseemon(mon)) + /* Yobjnam2(X,"shatter") yields "Shk's X shatters" if X is owned + by a shop or "Mon's X shatters" if X is carried by a monster + (or "{Your|The} X shatters" if {carried by hero|last resort})*/ + Strcpy(buf, Yobjnam2(monwep, "shatter")); + else /* hero is blind or can't see invisible mon */ + /* construct "Its weapon shatters"; not an exact replacement + for Yobjnam2() if an unseen mon other than the shopkeeper + is wielding a shop-owned weapon; telepathy or extended + monster detection will name mon but not its weapon */ + Sprintf(buf, "%s weapon%s %s", s_suffix(Monnam(mon)), + plur(monwep->quan), otense(monwep, "shatter")); + buf[sizeof buf - sizeof from_your_blow] = '\0'; + pline("%s%s", buf, from_your_blow); + m_useupall(mon, monwep); + /* If someone just shattered MY weapon, I'd flee! */ + if (rn2(4)) { + monflee(mon, d(2, 3), TRUE, TRUE); + } + hmd->hittxt = TRUE; + } - wakeup(mon, TRUE); - if (!obj) { /* attack with bare hands */ - if (mdat == &mons[PM_SHADE]) - tmp = 0; - else if (martial_bonus()) - tmp = rnd(4); /* bonus for martial arts */ - else - tmp = rnd(2); - valid_weapon_attack = (tmp > 1); - /* Blessed gloves give bonuses when fighting 'bare-handed'. So do - silver rings. Note: rings are worn under gloves, so you don't - get both bonuses, and two silver rings don't give double bonus. */ - tmp += special_dmgval(&youmonst, mon, (W_ARMG | W_RINGL | W_RINGR), - &silverhit); - barehand_silver_rings += (((silverhit & W_RINGL) ? 1 : 0) - + ((silverhit & W_RINGR) ? 1 : 0)); - if (barehand_silver_rings > 0) - silvermsg = TRUE; + if (obj->oartifact + && artifact_hit(&gy.youmonst, mon, obj, &hmd->dmg, hmd->dieroll)) { + /* artifact_hit updates 'tmp' but doesn't inflict any + damage; however, it might cause carried items to be + destroyed and they might do so */ + if (DEADMONSTER(mon)) { /* artifact killed monster */ + hmd->doreturn = TRUE; + hmd->retval = FALSE; + return; + /*return FALSE;*/ + } + /* perhaps artifact tried to behead a headless monster */ + if (hmd->dmg == 0) { + hmd->doreturn = TRUE; + hmd->retval = TRUE; + return; + /*return TRUE;*/ + } + hmd->hittxt = TRUE; + } + if (hmd->material == SILVER && mon_hates_silver(mon)) { + hmd->silvermsg = hmd->silverobj = TRUE; + } + if (artifact_light(obj) && obj->lamplit + && mon_hates_light(mon)) + hmd->lightobj = TRUE; + if (u.usteed && !hmd->thrown && hmd->dmg > 0 + && weapon_type(obj) == P_LANCE && mon != u.ustuck) { + hmd->jousting = joust(mon, obj); + /* exercise skill even for minimal damage hits */ + if (hmd->jousting) + hmd->train_weapon_skill = TRUE; + } + if (hmd->thrown == HMON_THROWN + && (is_ammo(obj) || is_missile(obj))) { + if (ammo_and_launcher(obj, uwep)) { + /* elves and samurai do extra damage using their own + bows with their own arrows; they're highly trained */ + if (Role_if(PM_SAMURAI) && obj->otyp == YA + && uwep->otyp == YUMI) + hmd->dmg++; + else if (Race_if(PM_ELF) && obj->otyp == ELVEN_ARROW + && uwep->otyp == ELVEN_BOW) + hmd->dmg++; + hmd->train_weapon_skill = (hmd->dmg > 0); + } + if (obj->opoisoned && is_poisonable(obj)) + hmd->ispoisoned = TRUE; + } + /* permapoisoned is non-ammo/missile, limit the poison */ + if (permapoisoned(obj) && hmd->dieroll <= 5) + hmd->ispoisoned = TRUE; +} + +staticfn void +hmon_hitmon_weapon( + struct _hitmon_data *hmd, + struct monst *mon, + struct obj *obj) /* obj is not NULL */ +{ + /* is it not a melee weapon? */ + if (/* if you strike with a bow... */ + is_launcher(obj) + /* or strike with a missile in your hand... */ + || (!hmd->thrown && (is_missile(obj) || is_ammo(obj))) + /* or use a pole at short range and not mounted... */ + || (!hmd->thrown && !u.usteed && is_pole(obj) + && !is_art(obj,ART_SNICKERSNEE)) + /* or throw a missile without the proper bow... */ + || (is_ammo(obj) && (hmd->thrown != HMON_THROWN + || !ammo_and_launcher(obj, uwep)))) { + hmon_hitmon_weapon_ranged(hmd, mon, obj); } else { - if (!(artifact_light(obj) && obj->lamplit)) - Strcpy(saved_oname, cxname(obj)); - else - Strcpy(saved_oname, bare_artifactname(obj)); - if (obj->oclass == WEAPON_CLASS || is_weptool(obj) - || obj->oclass == GEM_CLASS) { - /* is it not a melee weapon? */ - if (/* if you strike with a bow... */ - is_launcher(obj) - /* or strike with a missile in your hand... */ - || (!thrown && (is_missile(obj) || is_ammo(obj))) - /* or use a pole at short range and not mounted... */ - || (!thrown && !u.usteed && is_pole(obj)) - /* or throw a missile without the proper bow... */ - || (is_ammo(obj) && (thrown != HMON_THROWN - || !ammo_and_launcher(obj, uwep)))) { - /* then do only 1-2 points of damage */ - if (mdat == &mons[PM_SHADE] && !shade_glare(obj)) - tmp = 0; - else - tmp = rnd(2); - if (objects[obj->otyp].oc_material == SILVER - && mon_hates_silver(mon)) { - silvermsg = TRUE; - silverobj = TRUE; - /* if it will already inflict dmg, make it worse */ - tmp += rnd((tmp) ? 20 : 10); - } - if (!thrown && obj == uwep && obj->otyp == BOOMERANG - && rnl(4) == 4 - 1) { - boolean more_than_1 = (obj->quan > 1L); - - pline("As you hit %s, %s%s breaks into splinters.", - mon_nam(mon), more_than_1 ? "one of " : "", - yname(obj)); - if (!more_than_1) - uwepgone(); /* set unweapon */ - useup(obj); - if (!more_than_1) - obj = (struct obj *) 0; - hittxt = TRUE; - if (mdat != &mons[PM_SHADE]) - tmp++; - } - } else { - tmp = dmgval(obj, mon); - /* a minimal hit doesn't exercise proficiency */ - valid_weapon_attack = (tmp > 1); - if (!valid_weapon_attack || mon == u.ustuck || u.twoweap - /* Cleaver can hit up to three targets at once so don't - let it also hit from behind or shatter foes' weapons */ - || (hand_to_hand && obj->oartifact == ART_CLEAVER)) { - ; /* no special bonuses */ - } else if (mon->mflee && Role_if(PM_ROGUE) && !Upolyd - /* multi-shot throwing is too powerful here */ - && hand_to_hand) { - You("strike %s from behind!", mon_nam(mon)); - tmp += rnd(u.ulevel); - hittxt = TRUE; - } else if (dieroll == 2 && obj == uwep - && obj->oclass == WEAPON_CLASS - && (bimanual(obj) - || (Role_if(PM_SAMURAI) && obj->otyp == KATANA - && !uarms)) - && ((wtype = uwep_skill_type()) != P_NONE - && P_SKILL(wtype) >= P_SKILLED) - && ((monwep = MON_WEP(mon)) != 0 - && !is_flimsy(monwep) - && !obj_resists(monwep, - 50 + 15 * (greatest_erosion(obj) - - greatest_erosion(monwep)), - 100))) { - /* - * 2.5% chance of shattering defender's weapon when - * using a two-handed weapon; less if uwep is rusted. - * [dieroll == 2 is most successful non-beheading or - * -bisecting hit, in case of special artifact damage; - * the percentage chance is (1/20)*(50/100).] - * If attacker's weapon is rustier than defender's, - * the obj_resists chance is increased so the shatter - * chance is decreased; if less rusty, then vice versa. - */ - setmnotwielded(mon, monwep); - mon->weapon_check = NEED_WEAPON; - pline("%s from the force of your blow!", - Yobjnam2(monwep, "shatter")); - m_useupall(mon, monwep); - /* If someone just shattered MY weapon, I'd flee! */ - if (rn2(4)) { - monflee(mon, d(2, 3), TRUE, TRUE); - } - hittxt = TRUE; - } + hmon_hitmon_weapon_melee(hmd, mon, obj); + if (hmd->doreturn) + return; + } +} - if (obj->oartifact - && artifact_hit(&youmonst, mon, obj, &tmp, dieroll)) { - if (DEADMONSTER(mon)) /* artifact killed monster */ - return FALSE; - if (tmp == 0) - return TRUE; - hittxt = TRUE; - } - if (objects[obj->otyp].oc_material == SILVER - && mon_hates_silver(mon)) { - silvermsg = TRUE; - silverobj = TRUE; - } - if (artifact_light(obj) && obj->lamplit - && mon_hates_light(mon)) - lightobj = TRUE; - if (u.usteed && !thrown && tmp > 0 - && weapon_type(obj) == P_LANCE && mon != u.ustuck) { - jousting = joust(mon, obj); - /* exercise skill even for minimal damage hits */ - if (jousting) - valid_weapon_attack = TRUE; - } - if (thrown == HMON_THROWN - && (is_ammo(obj) || is_missile(obj))) { - if (ammo_and_launcher(obj, uwep)) { - /* Elves and Samurai do extra damage using - * their bows&arrows; they're highly trained. - */ - if (Role_if(PM_SAMURAI) && obj->otyp == YA - && uwep->otyp == YUMI) - tmp++; - else if (Race_if(PM_ELF) && obj->otyp == ELVEN_ARROW - && uwep->otyp == ELVEN_BOW) - tmp++; - } - if (obj->opoisoned && is_poisonable(obj)) - ispoisoned = TRUE; - } - } - } else if (obj->oclass == POTION_CLASS) { - if (obj->quan > 1L) - obj = splitobj(obj, 1L); - else - setuwep((struct obj *) 0); - freeinv(obj); - potionhit(mon, obj, - hand_to_hand ? POTHIT_HERO_BASH : POTHIT_HERO_THROW); - if (DEADMONSTER(mon)) - return FALSE; /* killed */ - hittxt = TRUE; - /* in case potion effect causes transformation */ - mdat = mon->data; - tmp = (mdat == &mons[PM_SHADE]) ? 0 : 1; - } else { - if (mdat == &mons[PM_SHADE] && !shade_aware(obj)) { - tmp = 0; - } else { - switch (obj->otyp) { - case BOULDER: /* 1d20 */ - case HEAVY_IRON_BALL: /* 1d25 */ - case IRON_CHAIN: /* 1d4+1 */ - tmp = dmgval(obj, mon); - break; - case MIRROR: - if (breaktest(obj)) { - You("break %s. That's bad luck!", ysimple_name(obj)); - change_luck(-2); - useup(obj); - obj = (struct obj *) 0; - unarmed = FALSE; /* avoid obj==0 confusion */ - get_dmg_bonus = FALSE; - hittxt = TRUE; - } - tmp = 1; - break; - case EXPENSIVE_CAMERA: - You("succeed in destroying %s. Congratulations!", - ysimple_name(obj)); - release_camera_demon(obj, u.ux, u.uy); - useup(obj); - return TRUE; - case CORPSE: /* fixed by polder@cs.vu.nl */ - if (touch_petrifies(&mons[obj->corpsenm])) { - tmp = 1; - hittxt = TRUE; - You("hit %s with %s.", mon_nam(mon), - corpse_xname(obj, (const char *) 0, - obj->dknown ? CXN_PFX_THE - : CXN_ARTICLE)); - obj->dknown = 1; - if (!munstone(mon, TRUE)) - minstapetrify(mon, TRUE); - if (resists_ston(mon)) - break; - /* note: hp may be <= 0 even if munstoned==TRUE */ - return (boolean) !DEADMONSTER(mon); +staticfn void +hmon_hitmon_potion( + struct _hitmon_data *hmd, + struct monst *mon, + struct obj *obj) /* obj is not NULL */ +{ + if (obj->quan > 1L) + obj = splitobj(obj, 1L); + else + setuwep((struct obj *) 0); + freeinv(obj); + potionhit(mon, obj, + hmd->hand_to_hand ? POTHIT_HERO_BASH : POTHIT_HERO_THROW); + if (DEADMONSTER(mon)) { + hmd->doreturn = TRUE; + hmd->retval = FALSE; /* killed */ + return; + } + hmd->hittxt = TRUE; + /* in case potion effect causes transformation */ + hmd->mdat = mon->data; + hmd->dmg = (hmd->mdat == &mons[PM_SHADE]) ? 0 : 1; +} + +staticfn void +hmon_hitmon_misc_obj( + struct _hitmon_data *hmd, + struct monst *mon, + struct obj *obj) /* obj is not NULL */ +{ + switch (obj->otyp) { + case BOULDER: /* 1d20 */ + case HEAVY_IRON_BALL: /* 1d25 */ + case IRON_CHAIN: /* 1d4+1 */ + hmd->dmg = dmgval(obj, mon); + break; + case MIRROR: + if (breaktest(obj)) { + You("break %s. That's bad luck!", ysimple_name(obj)); + change_luck(-2); + useup(obj); + obj = (struct obj *) 0; + hmd->unarmed = FALSE; /* avoid obj==0 confusion */ + hmd->get_dmg_bonus = FALSE; + hmd->hittxt = TRUE; + } + hmd->dmg = 1; + break; + case EXPENSIVE_CAMERA: + You("succeed in destroying %s. Congratulations!", + ysimple_name(obj)); + release_camera_demon(obj, u.ux, u.uy); + useup(obj); + hmd->doreturn = TRUE; + hmd->retval = TRUE; + return; + /*return TRUE;*/ + case CORPSE: /* fixed by polder@cs.vu.nl */ + if (touch_petrifies(&mons[obj->corpsenm])) { + hmd->dmg = 1; + hmd->hittxt = TRUE; + You("hit %s with %s.", mon_nam(mon), + corpse_xname(obj, (const char *) 0, + obj->dknown ? CXN_PFX_THE + : CXN_ARTICLE)); + observe_object(obj); + if (!munstone(mon, TRUE)) + minstapetrify(mon, TRUE); + if (resists_ston(mon)) + break; + /* note: hp may be <= 0 even if munstoned==TRUE */ + hmd->doreturn = TRUE; + hmd->retval = !DEADMONSTER(mon); + return; + /*return (boolean) !DEADMONSTER(mon);*/ #if 0 - } else if (touch_petrifies(mdat)) { - ; /* maybe turn the corpse into a statue? */ + } else if (touch_petrifies(mdat)) { + ; /* maybe turn the corpse into a statue? */ #endif - } - tmp = (obj->corpsenm >= LOW_PM ? mons[obj->corpsenm].msize - : 0) + 1; - break; + } + hmd->dmg = (ismnum(obj->corpsenm) ? mons[obj->corpsenm].msize + : 0) + 1; + break; #define useup_eggs(o) \ do { \ - if (thrown) \ + if (hmd->thrown) \ obfree(o, (struct obj *) 0); \ else \ useupall(o); \ o = (struct obj *) 0; \ } while (0) /* now gone */ - case EGG: { - long cnt = obj->quan; - - tmp = 1; /* nominal physical damage */ - get_dmg_bonus = FALSE; - hittxt = TRUE; /* message always given */ - /* egg is always either used up or transformed, so next - hand-to-hand attack should yield a "bashing" mesg */ - if (obj == uwep) - unweapon = TRUE; - if (obj->spe && obj->corpsenm >= LOW_PM) { - if (obj->quan < 5L) - change_luck((schar) - (obj->quan)); - else - change_luck(-5); - } + case EGG: { + long cnt = obj->quan; + + hmd->dmg = 1; /* nominal physical damage */ + hmd->get_dmg_bonus = FALSE; + hmd->hittxt = TRUE; /* message always given */ + /* egg is always either used up or transformed, so next + hand-to-hand attack should yield a "bashing" mesg */ + if (obj == uwep) + gu.unweapon = TRUE; + if (obj->spe && ismnum(obj->corpsenm)) { + if (obj->quan < 5L) + change_luck((schar) - (obj->quan)); + else + change_luck(-5); + } - if (touch_petrifies(&mons[obj->corpsenm])) { - /*learn_egg_type(obj->corpsenm);*/ - pline("Splat! You hit %s with %s %s egg%s!", - mon_nam(mon), - obj->known ? "the" : cnt > 1L ? "some" : "a", - obj->known ? mons[obj->corpsenm].mname - : "petrifying", - plur(cnt)); - obj->known = 1; /* (not much point...) */ - useup_eggs(obj); - if (!munstone(mon, TRUE)) - minstapetrify(mon, TRUE); - if (resists_ston(mon)) - break; - return (boolean) (!DEADMONSTER(mon)); - } else { /* ordinary egg(s) */ - const char *eggp = (obj->corpsenm != NON_PM - && obj->known) - ? the(mons[obj->corpsenm].mname) - : (cnt > 1L) ? "some" : "an"; - - You("hit %s with %s egg%s.", mon_nam(mon), eggp, - plur(cnt)); - if (touch_petrifies(mdat) && !stale_egg(obj)) { - pline_The("egg%s %s alive any more...", plur(cnt), - (cnt == 1L) ? "isn't" : "aren't"); - if (obj->timed) - obj_stop_timers(obj); - obj->otyp = ROCK; - obj->oclass = GEM_CLASS; - obj->oartifact = 0; - obj->spe = 0; - obj->known = obj->dknown = obj->bknown = 0; - obj->owt = weight(obj); - if (thrown) - place_object(obj, mon->mx, mon->my); - } else { - pline("Splat!"); - useup_eggs(obj); - exercise(A_WIS, FALSE); - } - } - break; -#undef useup_eggs - } - case CLOVE_OF_GARLIC: /* no effect against demons */ - if (is_undead(mdat) || is_vampshifter(mon)) { - monflee(mon, d(2, 4), FALSE, TRUE); - } - tmp = 1; - break; - case CREAM_PIE: - case BLINDING_VENOM: - mon->msleeping = 0; - if (can_blnd(&youmonst, mon, - (uchar) ((obj->otyp == BLINDING_VENOM) - ? AT_SPIT - : AT_WEAP), - obj)) { - if (Blind) { - pline(obj->otyp == CREAM_PIE ? "Splat!" - : "Splash!"); - } else if (obj->otyp == BLINDING_VENOM) { - pline_The("venom blinds %s%s!", mon_nam(mon), - mon->mcansee ? "" : " further"); - } else { - char *whom = mon_nam(mon); - char *what = The(xname(obj)); - - if (!thrown && obj->quan > 1L) - what = An(singular(obj, xname)); - /* note: s_suffix returns a modifiable buffer */ - if (haseyes(mdat) - && mdat != &mons[PM_FLOATING_EYE]) - whom = strcat(strcat(s_suffix(whom), " "), - mbodypart(mon, FACE)); - pline("%s %s over %s!", what, - vtense(what, "splash"), whom); - } - setmangry(mon, TRUE); - mon->mcansee = 0; - tmp = rn1(25, 21); - if (((int) mon->mblinded + tmp) > 127) - mon->mblinded = 127; - else - mon->mblinded += tmp; - } else { - pline(obj->otyp == CREAM_PIE ? "Splat!" : "Splash!"); - setmangry(mon, TRUE); - } - if (thrown) - obfree(obj, (struct obj *) 0); - else - useup(obj); - hittxt = TRUE; - get_dmg_bonus = FALSE; - tmp = 0; - break; - case ACID_VENOM: /* thrown (or spit) */ - if (resists_acid(mon)) { - Your("venom hits %s harmlessly.", mon_nam(mon)); - tmp = 0; - } else { - Your("venom burns %s!", mon_nam(mon)); - tmp = dmgval(obj, mon); - } - if (thrown) - obfree(obj, (struct obj *) 0); - else - useup(obj); - hittxt = TRUE; - get_dmg_bonus = FALSE; - break; - default: - /* non-weapons can damage because of their weight */ - /* (but not too much) */ - tmp = obj->owt / 100; - if (is_wet_towel(obj)) { - /* wielded wet towel should probably use whip skill - (but not by setting objects[TOWEL].oc_skill==P_WHIP - because that would turn towel into a weptool) */ - tmp += obj->spe; - if (rn2(obj->spe + 1)) /* usually lose some wetness */ - dry_a_towel(obj, -1, TRUE); - } - if (tmp < 1) - tmp = 1; - else - tmp = rnd(tmp); - if (tmp > 6) - tmp = 6; - /* - * Things like silver wands can arrive here so - * so we need another silver check. - */ - if (objects[obj->otyp].oc_material == SILVER - && mon_hates_silver(mon)) { - tmp += rnd(20); - silvermsg = TRUE; - silverobj = TRUE; - } - } + if (ismnum(obj->corpsenm) + && touch_petrifies(&mons[obj->corpsenm])) { + /*learn_egg_type(obj->corpsenm);*/ + pline("Splat! You hit %s with %s %s egg%s!", + mon_nam(mon), + obj->known ? "the" : cnt > 1L ? "some" : "a", + obj->known ? mons[obj->corpsenm].pmnames[NEUTRAL] + : "petrifying", + plur(cnt)); + obj->known = 1; /* (not much point...) */ + useup_eggs(obj); + if (!munstone(mon, TRUE)) + minstapetrify(mon, TRUE); + if (resists_ston(mon)) + break; + hmd->doreturn = TRUE; + hmd->retval = !DEADMONSTER(mon); + return; + /*return (boolean) (!DEADMONSTER(mon));*/ + } else { /* ordinary egg(s) */ + enum monnums mnum = obj->corpsenm; + const char *eggp = + (ismnum(mnum) && obj->known) + ? the(mons[mnum].pmnames[NEUTRAL]) + : (cnt > 1L) ? "some" : "an"; + + You("hit %s with %s egg%s.", mon_nam(mon), eggp, + plur(cnt)); + if (touch_petrifies(hmd->mdat) && !stale_egg(obj)) { + pline_The("egg%s %s alive any more...", plur(cnt), + (cnt == 1L) ? "isn't" : "aren't"); + if (obj->timed) + obj_stop_timers(obj); + obj->otyp = ROCK; + obj->oclass = GEM_CLASS; + obj->oartifact = 0; + obj->spe = 0; + obj->known = obj->dknown = obj->bknown = 0; + obj->owt = weight(obj); + if (hmd->thrown) + place_object(obj, mon->mx, mon->my); + } else if (obj->corpsenm == PM_PYROLISK) { + useup_eggs(obj); + explode(mon->mx, mon->my, -11, d(3, 6), 0, EXPL_FIERY); + hmd->doreturn = TRUE; + hmd->retval = !DEADMONSTER(mon); + return; + } else { + pline("Splat!"); + useup_eggs(obj); + exercise(A_WIS, FALSE); } } + break; +#undef useup_eggs } + case CLOVE_OF_GARLIC: /* no effect against demons */ + if (is_undead(hmd->mdat) || is_vampshifter(mon)) { + monflee(mon, d(2, 4), FALSE, TRUE); + } + hmd->dmg = 1; + break; + case CREAM_PIE: + case BLINDING_VENOM: + mon->msleeping = 0; + if (can_blnd(&gy.youmonst, mon, + (uchar) ((obj->otyp == BLINDING_VENOM) + ? AT_SPIT + : AT_WEAP), + obj)) { + if (Blind) { + pline(obj->otyp == CREAM_PIE ? "Splat!" + : "Splash!"); + } else if (obj->otyp == BLINDING_VENOM) { + pline_The("venom blinds %s%s!", mon_nam(mon), + mon->mcansee ? "" : " further"); + } else { + char *whom = mon_nam(mon); + char *what = The(xname(obj)); + + if (!hmd->thrown && obj->quan > 1L) + what = An(singular(obj, xname)); + /* note: s_suffix returns a modifiable buffer */ + if (haseyes(hmd->mdat) + && hmd->mdat != &mons[PM_FLOATING_EYE]) + whom = strcat(strcat(s_suffix(whom), " "), + mbodypart(mon, FACE)); + pline("%s %s over %s!", what, + vtense(what, "splash"), whom); + } + setmangry(mon, TRUE); + mon->mcansee = 0; + hmd->dmg = rn1(25, 21); + if (((int) mon->mblinded + hmd->dmg) > 127) + mon->mblinded = 127; + else + mon->mblinded += hmd->dmg; + } else { + pline(obj->otyp == CREAM_PIE ? "Splat!" : "Splash!"); + setmangry(mon, TRUE); + } + { + boolean more_than_1 = (obj->quan > 1L); - /****** NOTE: perhaps obj is undefined!! (if !thrown && BOOMERANG) - * *OR* if attacking bare-handed!! */ + if (hmd->thrown) + obfree(obj, (struct obj *) 0); + else + useup(obj); - if (get_dmg_bonus && tmp > 0) { - tmp += u.udaminc; - /* If you throw using a propellor, you don't get a strength - * bonus but you do get an increase-damage bonus. - */ - if (thrown != HMON_THROWN || !obj || !uwep - || !ammo_and_launcher(obj, uwep)) - tmp += dbon(); - } - - if (valid_weapon_attack) { - struct obj *wep; - - /* to be valid a projectile must have had the correct projector */ - wep = PROJECTILE(obj) ? uwep : obj; - tmp += weapon_dam_bonus(wep); - /* [this assumes that `!thrown' implies wielded...] */ - wtype = thrown ? weapon_type(wep) : uwep_skill_type(); - use_skill(wtype, 1); - } - - if (ispoisoned) { - int nopoison = (10 - (obj->owt / 10)); - - if (nopoison < 2) - nopoison = 2; - if (Role_if(PM_SAMURAI)) { - You("dishonorably use a poisoned weapon!"); - adjalign(-sgn(u.ualign.type)); - } else if (u.ualign.type == A_LAWFUL && u.ualign.record > -10) { - You_feel("like an evil coward for using a poisoned weapon."); - adjalign(-1); - } - if (obj && !rn2(nopoison)) { - /* remove poison now in case obj ends up in a bones file */ - obj->opoisoned = FALSE; - /* defer "obj is no longer poisoned" until after hit message */ - unpoisonmsg = TRUE; - } - if (resists_poison(mon)) - needpoismsg = TRUE; - else if (rn2(10)) - tmp += rnd(6); + if (!more_than_1) + obj = (struct obj *) 0; + } + hmd->hittxt = TRUE; + hmd->get_dmg_bonus = FALSE; + hmd->dmg = 0; + break; + case ACID_VENOM: /* thrown (or spit) */ + if (resists_acid(mon)) { + Your("venom hits %s harmlessly.", mon_nam(mon)); + hmd->dmg = 0; + } else { + Your("venom burns %s!", mon_nam(mon)); + hmd->dmg = dmgval(obj, mon); + } + { + boolean more_than_1 = (obj->quan > 1L); + + if (hmd->thrown) + obfree(obj, (struct obj *) 0); + else + useup(obj); + + if (!more_than_1) + obj = (struct obj *) 0; + } + hmd->hittxt = TRUE; + hmd->get_dmg_bonus = FALSE; + break; + default: + if ((objects[obj->otyp].oc_material == VEGGY || + objects[obj->otyp].oc_material == PAPER) && + obj->oclass != SPBOOK_CLASS) { + /* vegetables (and similar) do no damage, because they + aren't rigid enough; paper objects also do no damage, + except for books */ + hmd->dmg = 0; + hmd->get_dmg_bonus = FALSE; + break; + } + /* non-weapons can damage because of their weight */ + /* (but not too much) */ + hmd->dmg = (obj->owt + 99) / 100; + hmd->dmg = (hmd->dmg <= 1) ? 1 : rnd(hmd->dmg); + if (hmd->dmg > 6) + hmd->dmg = 6; + /* wet towel has modest damage bonus beyond its weight, + based on its wetness */ + if (is_wet_towel(obj)) { + boolean doubld = (mon->data == &mons[PM_IRON_GOLEM]); + + /* wielded wet towel should probably use whip skill + (but not by setting objects[TOWEL].oc_skill==P_WHIP + because that would turn towel into a weptool); + due to low weight, tmp always starts at 1 here, and + due to wet towel's definition, obj->spe is 1..7 */ + hmd->dmg += obj->spe * (doubld ? 2 : 1); + hmd->dmg = rnd(hmd->dmg); /* wet towel damage not capped at 6 */ + /* usually lose some wetness but defer doing so + until after hit message */ + hmd->dryit = (rn2(obj->spe + 1) > 0); + } + /* things like silver wands can arrive here so we + need another silver check; blessed check too */ + if (hmd->material == SILVER && mon_hates_silver(mon)) { + hmd->dmg += rnd(20); + hmd->silvermsg = hmd->silverobj = TRUE; + } + if (obj->blessed && mon_hates_blessings(mon)) + hmd->dmg += rnd(4); + } +} + +/* do the actual hitting monster with obj/fists */ +staticfn void +hmon_hitmon_do_hit( + struct _hitmon_data *hmd, + struct monst *mon, + struct obj *obj) /* obj can be NULL */ +{ + if (!obj) { /* attack with bare hands */ + hmon_hitmon_barehands(hmd, mon); + } else { + /* obj is not NULL here because of the !obj check in this if block, + , so no guard is needed ahead of stone_missile(obj) */ + /* stone missile does not hurt xorn or earth elemental, but doesn't + pass all the way through and continue on to some further target */ + if ((hmd->thrown == HMON_THROWN + || hmd->thrown == HMON_KICKED) /* not Applied */ + && stone_missile(obj) && passes_rocks(hmd->mdat)) { + hit(mshot_xname(obj), mon, " but does no harm."); + wakeup(mon, TRUE); + hmd->doreturn = TRUE; + hmd->retval = TRUE; + return; + } + /* remember obj's name since it might end up being destroyed and + we'll want to use it after that */ + if (!(artifact_light(obj) && obj->lamplit)) + Strcpy(hmd->saved_oname, cxname(obj)); else - poiskilled = TRUE; + Strcpy(hmd->saved_oname, bare_artifactname(obj)); + + if (obj->oclass == WEAPON_CLASS || is_weptool(obj) + || obj->oclass == GEM_CLASS) { + hmon_hitmon_weapon(hmd, mon, obj); + if (hmd->doreturn) + return; + /* attacking with non-weapons */ + } else if (obj->oclass == POTION_CLASS) { + hmon_hitmon_potion(hmd, mon, obj); + if (hmd->doreturn) + return; + } else { + if (hmd->mdat == &mons[PM_SHADE] && !shade_aware(obj)) { + hmd->dmg = 0; + } else { + hmon_hitmon_misc_obj(hmd, mon, obj); + } + } } - if (tmp < 1) { - boolean mon_is_shade = (mon->data == &mons[PM_SHADE]); +} - /* make sure that negative damage adjustment can't result - in inadvertently boosting the victim's hit points */ - tmp = (get_dmg_bonus && !mon_is_shade) ? 1 : 0; - if (mon_is_shade && !hittxt - && thrown != HMON_THROWN && thrown != HMON_KICKED) - hittxt = shade_miss(&youmonst, mon, obj, FALSE, TRUE); - } - - if (jousting) { - tmp += d(2, (obj == uwep) ? 10 : 2); /* [was in dmgval()] */ - You("joust %s%s", mon_nam(mon), canseemon(mon) ? exclam(tmp) : "."); - if (jousting < 0) { - pline("%s shatters on impact!", Yname2(obj)); - /* (must be either primary or secondary weapon to get here) */ - u.twoweap = FALSE; /* untwoweapon() is too verbose here */ - if (obj == uwep) - uwepgone(); /* set unweapon */ - /* minor side-effect: broken lance won't split puddings */ - useup(obj); - obj = 0; - } - /* avoid migrating a dead monster */ - if (mon->mhp > tmp) { - mhurtle(mon, u.dx, u.dy, 1); - mdat = mon->data; /* in case of a polymorph trap */ - if (DEADMONSTER(mon)) - already_killed = TRUE; - } - hittxt = TRUE; - } else if (unarmed && tmp > 1 && !thrown && !obj && !Upolyd) { - /* VERY small chance of stunning opponent if unarmed. */ - if (rnd(100) < P_SKILL(P_BARE_HANDED_COMBAT) && !bigmonst(mdat) - && !thick_skinned(mdat)) { - if (canspotmon(mon)) - pline("%s %s from your powerful strike!", Monnam(mon), - makeplural(stagger(mon->data, "stagger"))); - /* avoid migrating a dead monster */ - if (mon->mhp > tmp) { - mhurtle(mon, u.dx, u.dy, 1); - mdat = mon->data; /* in case of a polymorph trap */ - if (DEADMONSTER(mon)) - already_killed = TRUE; - } - hittxt = TRUE; - } - } - - if (!already_killed) - mon->mhp -= tmp; - /* adjustments might have made tmp become less than what - a level draining artifact has already done to max HP */ - if (mon->mhp > mon->mhpmax) - mon->mhp = mon->mhpmax; - if (DEADMONSTER(mon)) - destroyed = TRUE; - if (mon->mtame && tmp > 0) { - /* do this even if the pet is being killed (affects revival) */ +staticfn void +hmon_hitmon_dmg_recalc(struct _hitmon_data *hmd, struct obj *obj) +{ + int dmgbonus = 0, strbonus, absbonus; + + /* + * Potential bonus (or penalty) from worn ring of increase damage + * (or intrinsic bonus from eating same) or from strength. Strength + * bonus is increased for melee with two-handed weapons and decreased + * for dual attacks (but when both hit, the total for the two is more + * than the bonus for a regular single hit). + */ + if (hmd->get_dmg_bonus) { + /* for dual attacks, udaminc applies to both, and two-handed + weapons use it as-is */ + dmgbonus = u.udaminc; + /* throwing using a propellor gets an increase-damage bonus + but not a strength one; other attacks get both; + for dual attacks, 3/4 of the strength bonus is used; when + both attacks hit, overall bonus is 3/2 rather than doubled; + melee hit with two-handed weapon uses 3/2 strength bonus to + approximately match double hit with two-weapon ('approximate' + because udaminc skews in favor of two-weapon); the 3/2 factor + for two-handed strength does not apply to polearms unless + hero is simply bashing with one of those and does not apply + to jousting because lances are one-handed */ + if (hmd->thrown != HMON_THROWN + || !obj || !uwep || !ammo_and_launcher(obj, uwep)) { + strbonus = dbon(); + absbonus = abs(strbonus); + if (hmd->twohits) + strbonus = ((3 * absbonus + 2) / 4) * sgn(strbonus); + else if (hmd->thrown == HMON_MELEE && uwep && bimanual(uwep)) + strbonus = ((3 * absbonus + 1) / 2) * sgn(strbonus); + dmgbonus += strbonus; + } + } + + /* + * Potential bonus (or penalty) from weapon skill. + * 'use_weapon_skill' is True for hand-to-hand ordinary weapon, + * applied or jousting polearm or lance, thrown missile (dart, + * shuriken, boomerang), or shot ammo (arrow, bolt, rock/gem when + * wielding corresponding launcher). + * It is False for hand-to-hand or thrown non-weapon, hand-to-hand + * polearm or lance when not mounted, hand-to-hand missile or ammo + * or launcher, thrown non-missile, or thrown ammo (including rocks) + * when not wielding corresponding launcher. + */ + if (hmd->use_weapon_skill) { + struct obj *skillwep = obj; + + if (PROJECTILE(obj) && ammo_and_launcher(obj, uwep)) + skillwep = uwep; + dmgbonus += weapon_dam_bonus(skillwep); + + /* hit for more than minimal damage (before being adjusted + for damage or skill bonus) trains the skill toward future + enhancement */ + if (hmd->train_weapon_skill) { + /* [this assumes that `!thrown' implies wielded...] */ + int wtype = hmd->thrown ? weapon_type(skillwep) + : uwep_skill_type(); + use_skill(wtype, 1); + } + } + + /* apply combined damage+strength and skill bonuses */ + hmd->dmg += dmgbonus; + /* don't let penalty, if bonus is negative, turn a hit into a miss */ + if (hmd->dmg < 1) + hmd->dmg = 1; +} + +staticfn void +hmon_hitmon_poison( + struct _hitmon_data *hmd, + struct monst *mon, + struct obj *obj) /* obj is not NULL */ +{ + int nopoison = (10 - (obj->owt / 10)); + + if (nopoison < 2) + nopoison = 2; + if (Role_if(PM_SAMURAI)) { + You("dishonorably use a poisoned weapon!"); + adjalign(-sgn(u.ualign.type)); + } else if (u.ualign.type == A_LAWFUL && u.ualign.record > -10) { + You_feel("like an evil coward for using a poisoned weapon."); + adjalign(-1); + } + if (!permapoisoned(obj) && !rn2(nopoison)) { + /* remove poison now in case obj ends up in a bones file */ + obj->opoisoned = FALSE; + /* defer "obj is no longer poisoned" until after hit message */ + hmd->unpoisonmsg = TRUE; + } + if (resists_poison(mon)) + hmd->needpoismsg = TRUE; + else if (rn2(10)) + hmd->dmg += rnd(6); + else + hmd->poiskilled = TRUE; +} + +staticfn void +hmon_hitmon_jousting( + struct _hitmon_data *hmd, + struct monst *mon, /* target */ + struct obj *obj) /* lance; obj is not NULL */ +{ + hmd->dmg += d(2, (obj == uwep) ? 10 : 2); /* [was in dmgval()] */ + You("joust %s%s", mon_nam(mon), canseemon(mon) ? exclam(hmd->dmg) : "."); + /* if this hit just broke the never-hit-with-wielded-weapon conduct + (handled by caller...), give a livelog message for that now */ + if (u.uconduct.weaphit <= 1) + first_weapon_hit(obj); + + if (hmd->jousting < 0) { + /* (must be either primary or secondary weapon to get here) */ + set_twoweap(FALSE); /* sets u.twoweap = FALSE; + * untwoweapon() is too verbose here */ + if (obj == uwep) + uwepgone(); /* set gu.unweapon */ + pline("%s shatters on impact!", Yname2(obj)); + /* minor side-effect: broken lance won't split puddings */ + useup(obj); + obj = (struct obj *) 0; + } + if (mhurtle_to_doom(mon, hmd->dmg, &hmd->mdat)) + hmd->already_killed = TRUE; + hmd->hittxt = TRUE; +} + +staticfn void +hmon_hitmon_stagger( + struct _hitmon_data *hmd, + struct monst *mon, + struct obj *obj UNUSED) +{ + /* VERY small chance of stunning opponent if unarmed. */ + if (rnd(100) < P_SKILL(P_BARE_HANDED_COMBAT) && !bigmonst(hmd->mdat) + && !thick_skinned(hmd->mdat)) { + if (canspotmon(mon)) + pline("%s %s from your powerful strike!", Monnam(mon), + makeplural(stagger(mon->data, "stagger"))); + if (mhurtle_to_doom(mon, hmd->dmg, &hmd->mdat)) + hmd->already_killed = TRUE; + hmd->hittxt = TRUE; + } +} + +staticfn void +hmon_hitmon_pet( + struct _hitmon_data *hmd, + struct monst *mon, + struct obj *obj UNUSED) +{ + if (mon->mtame && hmd->dmg > 0) { + /* do this even if the pet is being killed or migrating (affects revival) */ abuse_dog(mon); /* reduces tameness */ /* flee if still alive and still tame; if already suffering from untimed fleeing, no effect, otherwise increases timed fleeing */ - if (mon->mtame && !destroyed) - monflee(mon, 10 * rnd(tmp), FALSE, FALSE); + if (mon->mtame && !hmd->destroyed) + monflee(mon, 10 * rnd(hmd->dmg), FALSE, FALSE); } - if ((mdat == &mons[PM_BLACK_PUDDING] || mdat == &mons[PM_BROWN_PUDDING]) +} + +staticfn void +hmon_hitmon_splitmon( + struct _hitmon_data *hmd, + struct monst *mon, + struct obj *obj) /* obj can be NULL but guards are in place below */ +{ + if ((hmd->mdat == &mons[PM_BLACK_PUDDING] + || hmd->mdat == &mons[PM_BROWN_PUDDING]) /* pudding is alive and healthy enough to split */ - && mon->mhp > 1 && !mon->mcan + && mon->mhp > 1 && !mon->mcan && !hmd->offmap /* iron weapon using melee or polearm hit [3.6.1: metal weapon too; also allow either or both weapons to cause split when twoweap] */ && obj && (obj == uwep || (u.twoweap && obj == uswapwep)) - && ((objects[obj->otyp].oc_material == IRON + && ((hmd->material == IRON /* allow scalpel and tsurugi to split puddings */ - || objects[obj->otyp].oc_material == METAL) + || hmd->material == METAL) /* but not bashing with darts, arrows or ya */ && !(is_ammo(obj) || is_missile(obj))) - && hand_to_hand) { + && hmd->hand_to_hand) { struct monst *mclone; - if ((mclone = clone_mon(mon, 0, 0)) != 0) { - char withwhat[BUFSZ]; + char withwhat[BUFSZ]; + if ((mclone = clone_mon(mon, 0, 0)) != 0) { withwhat[0] = '\0'; if (u.twoweap && flags.verbose) Sprintf(withwhat, " with %s", yname(obj)); pline("%s divides as you hit it%s!", Monnam(mon), withwhat); - hittxt = TRUE; - mintrap(mclone); + hmd->hittxt = TRUE; + (void) mintrap(mclone, NO_TRAP_FLAGS); } } +} - if (!hittxt /*( thrown => obj exists )*/ - && (!destroyed - || (thrown && m_shot.n > 1 && m_shot.o == obj->otyp))) { - if (thrown) - hit(mshot_xname(obj), mon, exclam(tmp)); +staticfn void +hmon_hitmon_msg_hit( + struct _hitmon_data *hmd, + struct monst *mon, + struct obj *obj) /* obj can be NULL for hand_to_hand; otherwise not */ +{ + if (!hmd->hittxt /*( thrown => obj exists )*/ + && (!hmd->destroyed + || (hmd->thrown && gm.m_shot.n > 1 + && gm.m_shot.o == obj->otyp))) { + if (hmd->thrown) + hit(mshot_xname(obj), mon, exclam(hmd->dmg)); else if (!flags.verbose) You("hit it."); - else + else /* hand_to_hand */ You("%s %s%s", - (obj && (is_shield(obj) || obj->otyp == HEAVY_IRON_BALL)) - ? "bash" : Role_if(PM_BARBARIAN) ? "smite" : "hit", - mon_nam(mon), canseemon(mon) ? exclam(tmp) : "."); - } - - if (silvermsg) { - const char *fmt; - char *whom = mon_nam(mon); - char silverobjbuf[BUFSZ]; - - if (canspotmon(mon)) { - if (barehand_silver_rings == 1) - fmt = "Your silver ring sears %s!"; - else if (barehand_silver_rings == 2) - fmt = "Your silver rings sear %s!"; - else if (silverobj && saved_oname[0]) { - /* guard constructed format string against '%' in - saved_oname[] from xname(via cxname()) */ - Sprintf(silverobjbuf, "Your %s%s %s", - strstri(saved_oname, "silver") ? "" : "silver ", - saved_oname, vtense(saved_oname, "sear")); - (void) strNsubst(silverobjbuf, "%", "%%", 0); - Strcat(silverobjbuf, " %s!"); - fmt = silverobjbuf; - } else - fmt = "The silver sears %s!"; - } else { - *whom = highc(*whom); /* "it" -> "It" */ - fmt = "%s is seared!"; - } - /* note: s_suffix returns a modifiable buffer */ - if (!noncorporeal(mdat) && !amorphous(mdat)) - whom = strcat(s_suffix(whom), " flesh"); - pline(fmt, whom); - } - if (lightobj) { - const char *fmt; - char *whom = mon_nam(mon); - char emitlightobjbuf[BUFSZ]; - - if (canspotmon(mon)) { - if (saved_oname[0]) { - Sprintf(emitlightobjbuf, - "%s radiance penetrates deep into", - s_suffix(saved_oname)); - Strcat(emitlightobjbuf, " %s!"); - fmt = emitlightobjbuf; - } else - fmt = "The light sears %s!"; - } else { - *whom = highc(*whom); /* "it" -> "It" */ - fmt = "%s is seared!"; - } - /* note: s_suffix returns a modifiable buffer */ - if (!noncorporeal(mdat) && !amorphous(mdat)) - whom = strcat(s_suffix(whom), " flesh"); - pline(fmt, whom); + (obj && (is_shield(obj) + || obj->otyp == HEAVY_IRON_BALL)) ? "bash" + : (obj && (objects[obj->otyp].oc_skill == P_WHIP + || is_wet_towel(obj))) ? "lash" + : Role_if(PM_BARBARIAN) ? "smite" + : "hit", + mon_nam(mon), canseemon(mon) ? exclam(hmd->dmg) : "."); + } +} + +staticfn void +hmon_hitmon_msg_silver( + struct _hitmon_data *hmd, + struct monst *mon, + struct obj *obj UNUSED) +{ + const char *fmt; + char *whom = mon_nam(mon); + char silverobjbuf[BUFSZ]; + + if (canspotmon(mon)) { + if (hmd->barehand_silver_rings == 1) + fmt = "Your silver ring sears %s!"; + else if (hmd->barehand_silver_rings == 2) + fmt = "Your silver rings sear %s!"; + else if (hmd->silverobj && hmd->saved_oname[0]) { + /* guard constructed format string against '%' in + saved_oname[] from xname(via cxname()) */ + Snprintf(silverobjbuf, sizeof(silverobjbuf), "Your %s%s %s", + strstri(hmd->saved_oname, "silver") ? "" : "silver ", + hmd->saved_oname, vtense(hmd->saved_oname, "sear")); + (void) strNsubst(silverobjbuf, "%", "%%", 0); + strncat(silverobjbuf, " %s!", + sizeof(silverobjbuf) - (strlen(silverobjbuf) + 1)); + fmt = silverobjbuf; + } else + fmt = "The silver sears %s!"; + } else { + *whom = highc(*whom); /* "it" -> "It" */ + fmt = "%s is seared!"; + } + /* note: s_suffix returns a modifiable buffer */ + if (!noncorporeal(hmd->mdat) && !amorphous(hmd->mdat)) + whom = strcat(s_suffix(whom), " flesh"); + DISABLE_WARNING_FORMAT_NONLITERAL + pline(fmt, whom); + RESTORE_WARNING_FORMAT_NONLITERAL +} + +staticfn void +hmon_hitmon_msg_lightobj( + struct _hitmon_data *hmd, + struct monst *mon, + struct obj *obj UNUSED) +{ + const char *fmt; + char *whom = mon_nam(mon); + char emitlightobjbuf[BUFSZ]; + + if (canspotmon(mon)) { + if (hmd->saved_oname[0]) { + Sprintf(emitlightobjbuf, + "%s radiance penetrates deep into", + s_suffix(hmd->saved_oname)); + Strcat(emitlightobjbuf, " %s!"); + fmt = emitlightobjbuf; + } else + fmt = "The light sears %s!"; + } else { + *whom = highc(*whom); /* "it" -> "It" */ + fmt = "%s is seared!"; + } + /* note: s_suffix returns a modifiable buffer */ + if (!noncorporeal(hmd->mdat) && !amorphous(hmd->mdat)) + whom = strcat(s_suffix(whom), " flesh"); + DISABLE_WARNING_FORMAT_NONLITERAL + pline(fmt, whom); + RESTORE_WARNING_FORMAT_NONLITERAL +} + +/* + * These will segfault if passed a NULL obj pointer: + * hmon_hitmon_weapon_ranged, + * hmon_hitmon_weapon_melee, + * hmon_hitmon_weapon, + * hmon_hitmon_potion, + * hmon_hitmon_misc_obj, + * hmon_hitmon_poison, + * hmon_hitmon_jousting, + * + * These are equipped to handle a NULL obj pointer: + * hmon_hitmon_stagger, - obj arg is unused + * hmon_hitmon_pet, - obj arg is unused + * hmon_hitmon_msg_silver, - obj arg is unused + * hmon_hitmon_msg_lightobj, - obj arg is unused + * hmon_hitmon_do_hit, - has obj and !obj code paths + * hmon_hitmon_splitmon, - has !obj guards + * hmon_hitmon_msg_hit, - has !obj guards exc. thrown which is ok + */ + +/* guts of hmon(); returns True if 'mon' survives */ +staticfn boolean +hmon_hitmon( + struct monst *mon, + struct obj *obj, + int thrown, /* HMON_xxx (0 => hand-to-hand, other => ranged) */ + int dieroll) +{ + struct _hitmon_data hmd; + boolean maybe_knockback = FALSE; + + hmd.dmg = 0; + hmd.thrown = thrown; + hmd.twohits = thrown ? 0 : gt.twohits; + hmd.dieroll = dieroll; + hmd.mdat = mon->data; + hmd.use_weapon_skill = FALSE; + hmd.train_weapon_skill = FALSE; + hmd.barehand_silver_rings = 0; + hmd.silvermsg = FALSE; + hmd.silverobj = FALSE; + hmd.lightobj = FALSE; + hmd.material = obj ? objects[obj->otyp].oc_material + : NO_MATERIAL; + hmd.jousting = 0; + hmd.hittxt = FALSE; + hmd.get_dmg_bonus = TRUE; + hmd.unarmed = !uwep && !uarm && !uarms; + hmd.hand_to_hand = (thrown == HMON_MELEE + /* not grapnels; applied implies uwep */ + || (thrown == HMON_APPLIED && is_pole(uwep))); + hmd.ispoisoned = FALSE; + hmd.unpoisonmsg = FALSE; + hmd.needpoismsg = FALSE; + hmd.poiskilled = FALSE; + hmd.already_killed = FALSE; + hmd.offmap = FALSE; + hmd.destroyed = FALSE; + hmd.dryit = FALSE; + hmd.doreturn = FALSE; + hmd.retval = FALSE; + hmd.saved_oname[0] = '\0'; + + hmon_hitmon_do_hit(&hmd, mon, obj); + if (hmd.doreturn) + return hmd.retval; + + /* + ***** NOTE: perhaps obj is undefined! (if !thrown && BOOMERANG) + * *OR* if attacking bare-handed! + * Note too: the cases where obj might get destroyed do not + * set 'use_weapon_skill', bare-handed does. + */ + + if (hmd.dmg > 0) + hmon_hitmon_dmg_recalc(&hmd, obj); + + if (hmd.ispoisoned) + hmon_hitmon_poison(&hmd, mon, obj); + + if (hmd.dmg < 1) { + boolean mon_is_shade = (mon->data == &mons[PM_SHADE]); + + /* make sure that negative damage adjustment can't result + in inadvertently boosting the victim's hit points */ + hmd.dmg = (hmd.get_dmg_bonus && !mon_is_shade) ? 1 : 0; + if (mon_is_shade && !hmd.hittxt + && thrown != HMON_THROWN && thrown != HMON_KICKED) + /* this gives "harmlessly passes through" feedback even when + hero doesn't see it happen; presumably sensed by touch? */ + hmd.hittxt = shade_miss(&gy.youmonst, mon, obj, FALSE, TRUE); + } + + if (hmd.jousting) { + hmon_hitmon_jousting(&hmd, mon, obj); + } else if (hmd.unarmed && hmd.dmg > 1 && !thrown && !obj && !Upolyd) { + hmon_hitmon_stagger(&hmd, mon, obj); + } else if (!hmd.unarmed && hmd.dmg > 1 && !thrown && !Upolyd + && !u.twoweap && uwep) { + maybe_knockback = TRUE; + } + + if (!hmd.already_killed) { + if (obj && (obj == uwep || (obj == uswapwep && u.twoweap)) + /* known_hitum 'what counts as a weapon' criteria */ + && (obj->oclass == WEAPON_CLASS || is_weptool(obj)) + && (thrown == HMON_MELEE || thrown == HMON_APPLIED) + /* if jousting, the hit was already logged */ + && !hmd.jousting + /* note: caller has already incremented u.uconduct.weaphit + so we test for 1; 0 shouldn't be able to happen here... */ + && hmd.dmg > 0 && u.uconduct.weaphit <= 1) + first_weapon_hit(obj); + mon->mhp -= hmd.dmg; + } + /* adjustments might have made tmp become less than what + a level-draining artifact has already done to max HP */ + if (mon->mhp > mon->mhpmax) + mon->mhp = mon->mhpmax; + if (mon->mx == 0) { + /* + * jousting can lead to: + * mhurtle_to_doom() + * mhurtle() + * mintrap() + * trapeffect_hole() + * trapeffect_level_telep() + * migrate_to_level() + * Set offmap in that situation so code to follow can test for it.*/ + hmd.offmap = TRUE; + } + if (DEADMONSTER(mon)) + hmd.destroyed = TRUE; + + hmon_hitmon_pet(&hmd, mon, obj); + + hmon_hitmon_splitmon(&hmd, mon, obj); + + hmon_hitmon_msg_hit(&hmd, mon, obj); + + if (hmd.dryit) { /* dryit implies wet towel, so 'obj' is still intact */ + assert(obj != NULL); + dry_a_towel(obj, -1, TRUE); } + + if (hmd.silvermsg) + hmon_hitmon_msg_silver(&hmd, mon, obj); + + if (hmd.lightobj) + hmon_hitmon_msg_lightobj(&hmd, mon, obj); + /* if a "no longer poisoned" message is coming, it will be last; obj->opoisoned was cleared above and any message referring to "poisoned " has now been given; we want just "" for last message, so reformat while obj is still accessible */ - if (unpoisonmsg) - Strcpy(saved_oname, cxname(obj)); + if (hmd.unpoisonmsg) { + assert(obj != NULL); + Strcpy(hmd.saved_oname, cxname(obj)); + } /* [note: thrown obj might go away during killed()/xkilled() call (via 'thrownobj'; if swallowed, it gets added to engulfer's minvent and might merge with a stack that's already there)] */ + /* already_killed and poiskilled won't apply for Trollsbane */ - if (needpoismsg) + if (hmd.needpoismsg) pline_The("poison doesn't seem to affect %s.", mon_nam(mon)); - if (poiskilled) { + if (hmd.poiskilled) { pline_The("poison was deadly..."); - if (!already_killed) + if (!hmd.already_killed) xkilled(mon, XKILL_NOMSG); - destroyed = TRUE; /* return FALSE; */ - } else if (destroyed) { - if (!already_killed) + hmd.destroyed = TRUE; /* return FALSE; */ + } else if (hmd.destroyed) { + if (!hmd.already_killed) { + if (troll_baned(mon, obj)) + gm.mkcorpstat_norevive = TRUE; killed(mon); /* takes care of most messages */ - } else if (u.umconf && hand_to_hand) { + gm.mkcorpstat_norevive = FALSE; + } + } else if (u.umconf && hmd.hand_to_hand) { nohandglow(mon); if (!mon->mconf && !resist(mon, SPBOOK_CLASS, 0, NOTELL)) { mon->mconf = 1; - if (!mon->mstun && mon->mcanmove && !mon->msleeping - && canseemon(mon)) + if (!mon->mstun && !helpless(mon) && canseemon(mon)) pline("%s appears confused.", Monnam(mon)); } } - if (unpoisonmsg) - Your("%s %s no longer poisoned.", saved_oname, - vtense(saved_oname, "are")); + if (hmd.unpoisonmsg) + Your("%s %s no longer poisoned.", hmd.saved_oname, + vtense(hmd.saved_oname, "are")); + + if (!hmd.destroyed && !hmd.offmap) { + int hitflags = M_ATTK_HIT; + + wakeup(mon, TRUE); + if (maybe_knockback + && mhitm_knockback(&gy.youmonst, mon, gy.youmonst.data->mattk, + &hitflags, TRUE)) { + if ((hitflags & M_ATTK_DEF_DIED) != 0) + hmd.destroyed = TRUE; + } + } + return hmd.destroyed ? FALSE : TRUE; +} + + +/* joust or martial arts punch is knocking the target back; that might + kill 'mon' (via trap) before known_hitum() has a chance to do so; + return True if we kill mon, False otherwise */ +staticfn boolean +mhurtle_to_doom( + struct monst *mon, /* target monster */ + int tmp, /* amount of pending damage */ + struct permonst **mptr) /* caller's cached copy of mon->data */ +{ + /* only hurtle if pending physical damage (tmp) isn't going to kill mon */ + if (tmp < mon->mhp) { + mhurtle(mon, u.dx, u.dy, 1); + /* update caller's cached mon->data in case mon was pushed into + a polymorph trap or is a vampshifter whose current form has + been killed by a trap so that it reverted to original form */ + *mptr = mon->data; + if (DEADMONSTER(mon)) + return TRUE; + } + return FALSE; /* mon isn't dead yet */ +} + +/* gamelog version of "you've broken never-hit-with-wielded-weapon conduct; + the conduct is tracked in known_hitum(); we're called by hmon_hitmon() */ +staticfn void +first_weapon_hit(struct obj *weapon) +{ + char buf[BUFSZ]; + + /* avoid xname() since that includes "named " and we don't want + player-supplied in livelog */ + buf[0] = '\0'; + /* include "cursed" if known but don't bother with blessed */ + if (weapon->cursed && weapon->bknown) + Strcat(buf, "cursed "); /* normally supplied by doname() */ + if (obj_is_pname(weapon)) { + Strcat(buf, ONAME(weapon)); /* fully IDed artifact */ + } else { + Strcat(buf, simpleonames(weapon)); + if (weapon->oartifact && weapon->dknown) + Sprintf(eos(buf), " named %s", bare_artifactname(weapon)); + } - return destroyed ? FALSE : TRUE; + /* when a hit breaks the never-hit-with-wielded-weapon conduct + (handled by caller) we need to log the message about that before + monster is possibly killed; otherwise getting log entry sequence + N : killed for the first time + N : hit with a wielded weapon for the first time + reported on the same turn (N) looks "suboptimal" */ + livelog_printf(LL_CONDUCT, + "hit with a wielded weapon (%s) for the first time", buf); } -STATIC_OVL boolean -shade_aware(obj) -struct obj *obj; +staticfn boolean +shade_aware(struct obj *obj) { if (!obj) return FALSE; @@ -1360,13 +2013,15 @@ struct obj *obj; /* used for hero vs monster and monster vs monster; also handles monster vs hero but that won't happen because hero can't be a shade */ boolean -shade_miss(magr, mdef, obj, thrown, verbose) -struct monst *magr, *mdef; -struct obj *obj; -boolean thrown, verbose; +shade_miss( + struct monst *magr, + struct monst *mdef, + struct obj *obj, + boolean thrown, + boolean verbose) { const char *what, *whose, *target; - boolean youagr = (magr == &youmonst), youdef = (mdef == &youmonst); + boolean youagr = (magr == &gy.youmonst), youdef = (mdef == &gy.youmonst); /* we're using dmgval() for zero/not-zero, not for actual damage amount */ if (mdef->data != &mons[PM_SHADE] || (obj && dmgval(obj, mdef))) @@ -1374,7 +2029,7 @@ boolean thrown, verbose; if (verbose && ((youdef || cansee(mdef->mx, mdef->my) || sensemon(mdef)) - || (magr == &youmonst && distu(mdef->mx, mdef->my) <= 2))) { + || (magr == &gy.youmonst && m_next2u(mdef)))) { static const char harmlessly_thru[] = " harmlessly through "; what = (!obj || shade_aware(obj)) ? "attack" : cxname(obj); @@ -1397,10 +2052,8 @@ boolean thrown, verbose; /* check whether slippery clothing protects from hug or wrap attack */ /* [currently assumes that you are the attacker] */ -STATIC_OVL boolean -m_slips_free(mdef, mattk) -struct monst *mdef; -struct attack *mattk; +staticfn boolean +m_slips_free(struct monst *mdef, struct attack *mattk) { struct obj *obj; @@ -1421,8 +2074,8 @@ struct attack *mattk; if (obj && (obj->greased || obj->otyp == OILSKIN_CLOAK) && (!obj->cursed || rn2(3))) { You("%s %s %s %s!", - mattk->adtyp == AD_WRAP ? "slip off of" - : "grab, but cannot hold onto", + (mattk->adtyp == AD_WRAP) ? "slip off of" + : "grab, but cannot hold onto", s_suffix(mon_nam(mdef)), obj->greased ? "greased" : "slippery", /* avoid "slippery slippery cloak" for undiscovered oilskin cloak */ @@ -1441,10 +2094,9 @@ struct attack *mattk; /* used when hitting a monster with a lance while mounted; 1: joust hit; 0: ordinary hit; -1: joust but break lance */ -STATIC_OVL int -joust(mon, obj) -struct monst *mon; /* target */ -struct obj *obj; /* weapon */ +staticfn int +joust(struct monst *mon, /* target */ + struct obj *obj) /* weapon */ { int skill_rating, joust_dieroll; @@ -1453,6 +2105,11 @@ struct obj *obj; /* weapon */ /* sanity check; lance must be wielded in order to joust */ if (obj != uwep && (obj != uswapwep || !u.twoweap)) return 0; + /* can't joust while trapped--not enough room to maneuver; + * TODO? if the steed is trapped in a pit, perhaps the hero ought to be + * able to joust against a monster that's in a conjoined pit */ + if (u.utrap) + return 0; /* if using two weapons, use worse of lance and two-weapon skills */ skill_rating = P_SKILL(weapon_type(obj)); /* lance skill */ @@ -1471,16 +2128,9 @@ struct obj *obj; /* weapon */ return 0; /* no joust bonus; revert to ordinary attack */ } -/* - * Send in a demon pet for the hero. Exercise wisdom. - * - * This function used to be inline to damageum(), but the Metrowerks compiler - * (DR4 and DR4.5) screws up with an internal error 5 "Expression Too - * Complex." - * Pulling it out makes it work. - */ -STATIC_OVL void -demonpet() +/* send in a demon pet for the hero; exercise wisdom */ +staticfn void +demonpet(void) { int i; struct permonst *pm; @@ -1488,22 +2138,21 @@ demonpet() pline("Some hell-p has arrived!"); i = !rn2(6) ? ndemon(u.ualign.type) : NON_PM; - pm = i != NON_PM ? &mons[i] : youmonst.data; + pm = i != NON_PM ? &mons[i] : gy.youmonst.data; if ((dtmp = makemon(pm, u.ux, u.uy, NO_MM_FLAGS)) != 0) - (void) tamedog(dtmp, (struct obj *) 0); + (void) tamedog(dtmp, (struct obj *) 0, FALSE); exercise(A_WIS, TRUE); } -STATIC_OVL boolean -theft_petrifies(otmp) -struct obj *otmp; +staticfn boolean +theft_petrifies(struct obj *otmp) { if (uarmg || otmp->otyp != CORPSE || !touch_petrifies(&mons[otmp->corpsenm]) || Stone_resistance) return FALSE; #if 0 /* no poly_when_stoned() critter has theft capability */ - if (poly_when_stoned(youmonst.data) && polymon(PM_STONE_GOLEM)) { + if (poly_when_stoned(gy.youmonst.data) && polymon(PM_STONE_GOLEM)) { display_nhwindow(WIN_MESSAGE, FALSE); /* --More-- */ return TRUE; } @@ -1521,12 +2170,10 @@ struct obj *otmp; * If the target is wearing body armor, take all of its possessions; * otherwise, take one object. [Is this really the behavior we want?] */ -STATIC_OVL void -steal_it(mdef, mattk) -struct monst *mdef; -struct attack *mattk; +staticfn void +steal_it(struct monst *mdef, struct attack *mattk) { - struct obj *otmp, *gold = 0, *stealoid, **minvent_ptr; + struct obj *otmp, *gold = 0, *ustealo, **minvent_ptr; long unwornmask; otmp = mdef->minvent; @@ -1534,28 +2181,35 @@ struct attack *mattk; return; /* nothing to take */ /* look for worn body armor */ - stealoid = (struct obj *) 0; - if (could_seduce(&youmonst, mdef, mattk)) { + ustealo = (struct obj *) 0; + if (could_seduce(&gy.youmonst, mdef, mattk) && mdef->mcanmove) { /* find armor, and move it to end of inventory in the process */ minvent_ptr = &mdef->minvent; while ((otmp = *minvent_ptr) != 0) if (otmp->owornmask & W_ARM) { - if (stealoid) + if (ustealo) panic("steal_it: multiple worn suits"); *minvent_ptr = otmp->nobj; /* take armor out of minvent */ - stealoid = otmp; - stealoid->nobj = (struct obj *) 0; + ustealo = otmp; + ustealo->nobj = (struct obj *) 0; } else { minvent_ptr = &otmp->nobj; } - *minvent_ptr = stealoid; /* put armor back into minvent */ + *minvent_ptr = ustealo; /* put armor back into minvent */ } gold = findgold(mdef->minvent); - if (stealoid) { /* we will be taking everything */ - if (gender(mdef) == (int) u.mfemale && youmonst.data->mlet == S_NYMPH) - You("charm %s. She gladly hands over %sher possessions.", - mon_nam(mdef), !gold ? "" : "most of "); + if (ustealo) { /* we will be taking everything */ + char heshe[20]; + + /* 5.0: this uses hero's base gender rather than nymph femininity + but was using hardcoded pronouns She/her for target monster; + switch to dynamic pronoun */ + if (gender(mdef) == (int) u.mfemale + && gy.youmonst.data->mlet == S_NYMPH) + You("charm %s. %s gladly hands over %s%s possessions.", + mon_nam(mdef), upstart(strcpy(heshe, mhe(mdef))), + !gold ? "" : "most of ", mhis(mdef)); else You("seduce %s and %s starts to take off %s clothes.", mon_nam(mdef), mhe(mdef), mhis(mdef)); @@ -1575,22 +2229,19 @@ struct attack *mattk; mpickobj(mdef, gold), gold = 0; if (!Upolyd) break; /* no longer have ability to steal */ + unwornmask = otmp->owornmask; + /* this would take place when doname() formats the object for + the hold_another_object() call, but we want to do it before + otmp gets removed from mdef's inventory */ + if (otmp->oartifact && !Blind) + find_artifact(otmp); /* take the object away from the monster */ - obj_extract_self(otmp); - if ((unwornmask = otmp->owornmask) != 0L) { - mdef->misc_worn_check &= ~unwornmask; - if (otmp->owornmask & W_WEP) - setmnotwielded(mdef, otmp); - otmp->owornmask = 0L; - update_mon_intrinsics(mdef, otmp, FALSE, FALSE); - /* give monster a chance to wear other equipment on its next - move instead of waiting until it picks something up */ - mdef->misc_worn_check |= I_SPECIAL; - - if (otmp == stealoid) /* special message for final item */ - pline("%s finishes taking off %s suit.", Monnam(mdef), - mhis(mdef)); - } + extract_from_minvent(mdef, otmp, TRUE, FALSE); + /* special message for final item; no need to check owornmask because + * ustealo is only set on objects with (owornmask & W_ARM) */ + if (otmp == ustealo) + pline("%s finishes taking off %s suit.", Monnam(mdef), + mhis(mdef)); /* give the object to the character */ otmp = hold_another_object(otmp, "You snatched but dropped %s.", doname(otmp), "You steal: "); @@ -1608,7 +2259,7 @@ struct attack *mattk; break; /* can't continue stealing */ } - if (!stealoid) + if (!ustealo) break; /* only taking one item */ /* take gold out of minvent before making next selection; if it @@ -1626,480 +2277,2676 @@ struct attack *mattk; mpickobj(mdef, gold); } -int -damageum(mdef, mattk, specialdmg) -register struct monst *mdef; -register struct attack *mattk; -int specialdmg; /* blessed and/or silver bonus against various things */ +void +mhitm_ad_rust( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) { - register struct permonst *pd = mdef->data; - int armpro, tmp = d((int) mattk->damn, (int) mattk->damd); - boolean negated; - struct obj *mongold; + struct permonst *pd = mdef->data; - armpro = magic_negation(mdef); - /* since hero can't be cancelled, only defender's armor applies */ - negated = !(rn2(10) >= 3 * armpro); + if (magr == &gy.youmonst) { + /* uhitm */ + if (completelyrusts(pd)) { /* iron golem */ + /* note: the life-saved case is hypothetical because + life-saving doesn't work for golems */ + pline("%s %s to pieces!", Monnam(mdef), + !mlifesaver(mdef) ? "falls" : "starts to fall"); + xkilled(mdef, XKILL_NOMSG); + mhm->hitflags |= M_ATTK_DEF_DIED; + } + erode_armor(mdef, ERODE_RUST); + mhm->damage = 0; /* damageum(), int tmp */ + } else if (mdef == &gy.youmonst) { + /* mhitu */ + hitmsg(magr, mattk); + if (magr->mcan) { + return; + } + if (completelyrusts(pd)) { + You("rust!"); + /* KMH -- this is okay with unchanging */ + rehumanize(); + return; + } + erode_armor(&gy.youmonst, ERODE_RUST); + } else { + /* mhitm */ + if (magr->mcan) + return; + if (completelyrusts(pd)) { /* PM_IRON_GOLEM */ + if (gv.vis && canseemon(mdef)) + pline_mon(mdef, "%s %s to pieces!", Monnam(mdef), + !mlifesaver(mdef) ? "falls" : "starts to fall"); + monkilled(mdef, (char *) 0, AD_RUST); + if (!DEADMONSTER(mdef)) { + mhm->hitflags = M_ATTK_MISS; + mhm->done = TRUE; + return; + } + mhm->hitflags = (M_ATTK_DEF_DIED | (grow_up(magr, mdef) ? 0 + : M_ATTK_AGR_DIED)); + mhm->done = TRUE; + return; + } + erode_armor(mdef, ERODE_RUST); + mdef->mstrategy &= ~STRAT_WAITFORU; + mhm->damage = 0; /* mdamagem(), int tmp */ + } +} - if (is_demon(youmonst.data) && !rn2(13) && !uwep - && u.umonnum != PM_SUCCUBUS && u.umonnum != PM_INCUBUS - && u.umonnum != PM_BALROG) { - demonpet(); - return 0; +void +mhitm_ad_corr( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + if (magr == &gy.youmonst) { + /* uhitm */ + erode_armor(mdef, ERODE_CORRODE); + mhm->damage = 0; + } else if (mdef == &gy.youmonst) { + /* mhitu */ + hitmsg(magr, mattk); + if (magr->mcan) + return; + erode_armor(mdef, ERODE_CORRODE); + } else { + /* mhitm */ + if (magr->mcan) + return; + erode_armor(mdef, ERODE_CORRODE); + mdef->mstrategy &= ~STRAT_WAITFORU; + mhm->damage = 0; } - switch (mattk->adtyp) { - case AD_STUN: - if (!Blind) - pline("%s %s for a moment.", Monnam(mdef), - makeplural(stagger(pd, "stagger"))); - mdef->mstun = 1; - goto physical; - case AD_LEGS: -#if 0 - if (u.ucancelled) { - tmp = 0; - break; +} + +void +mhitm_ad_dcay( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + struct permonst *pd = mdef->data; + + if (magr == &gy.youmonst) { + /* uhitm */ + if (completelyrots(pd)) { /* wood golem or leather golem */ + pline("%s %s to pieces!", Monnam(mdef), + !mlifesaver(mdef) ? "falls" : "starts to fall"); + xkilled(mdef, XKILL_NOMSG); } -#endif - goto physical; - case AD_WERE: /* no special effect on monsters */ - case AD_HEAL: /* likewise */ - case AD_PHYS: - physical: - if (pd == &mons[PM_SHADE]) { - tmp = 0; - if (!specialdmg) - impossible("bad shade attack function flow?"); + erode_armor(mdef, ERODE_ROT); + mhm->damage = 0; + } else if (mdef == &gy.youmonst) { + /* mhitu */ + hitmsg(magr, mattk); + if (magr->mcan) + return; + if (completelyrots(pd)) { + You("rot!"); + /* KMH -- this is okay with unchanging */ + rehumanize(); + return; } - tmp += specialdmg; + erode_armor(mdef, ERODE_ROT); + } else { + /* mhitm */ + if (magr->mcan) + return; + if (completelyrots(pd)) { /* PM_WOOD_GOLEM || PM_LEATHER_GOLEM */ + /* note: the life-saved case is hypothetical because + life-saving doesn't work for golems */ + if (gv.vis && canseemon(mdef)) + pline_mon(mdef, "%s %s to pieces!", Monnam(mdef), + !mlifesaver(mdef) ? "falls" : "starts to fall"); + monkilled(mdef, (char *) 0, AD_DCAY); + if (!DEADMONSTER(mdef)) { + mhm->done = TRUE; + mhm->hitflags = M_ATTK_MISS; + return; + } + mhm->done = TRUE; + mhm->hitflags = (M_ATTK_DEF_DIED + | (grow_up(magr, mdef) ? 0 : M_ATTK_AGR_DIED)); + return; + } + erode_armor(mdef, ERODE_ROT); + mdef->mstrategy &= ~STRAT_WAITFORU; + mhm->damage = 0; + } +} - if (mattk->aatyp == AT_WEAP) { - /* hmonas() uses known_hitum() to deal physical damage, - then also damageum() for non-AD_PHYS; don't inflict - extra physical damage for unusual damage types */ - tmp = 0; - } else if (mattk->aatyp == AT_KICK - || mattk->aatyp == AT_CLAW - || mattk->aatyp == AT_TUCH - || mattk->aatyp == AT_HUGS) { - if (thick_skinned(pd)) - tmp = (mattk->aatyp == AT_KICK) ? 0 : (tmp + 1) / 2; - /* add ring(s) of increase damage */ - if (u.udaminc > 0) { - /* applies even if damage was 0 */ - tmp += u.udaminc; - } else if (tmp > 0) { - /* ring(s) might be negative; avoid converting - 0 to non-0 or positive to non-positive */ - tmp += u.udaminc; - if (tmp < 1) - tmp = 1; +void +mhitm_ad_dren( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + boolean negated = mhitm_mgc_atk_negated(magr, mdef, FALSE); + + if (magr == &gy.youmonst) { + /* uhitm */ + if (!negated && !rn2(4)) + xdrainenergym(mdef, TRUE); + mhm->damage = 0; + } else if (mdef == &gy.youmonst) { + /* mhitu */ + hitmsg(magr, mattk); + if (!negated && !rn2(4)) /* 25% chance */ + drain_en(mhm->damage, FALSE); + mhm->damage = 0; + } else { + /* mhitm */ + if (!negated && !rn2(4)) + xdrainenergym(mdef, (boolean) (gv.vis && canspotmon(mdef) + && mattk->aatyp != AT_ENGL)); + mhm->damage = 0; + } +} + +void +mhitm_ad_drli( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + if (magr == &gy.youmonst) { + /* uhitm */ + if (!rn2(3) && !(resists_drli(mdef) || defended(mdef, AD_DRLI)) + && !mhitm_mgc_atk_negated(magr, mdef, TRUE)) { + mhm->damage = d(2, 6); /* Stormbringer uses monhp_per_lvl + * (usually 1d8) */ + pline("%s becomes weaker!", Monnam(mdef)); + if (mdef->mhpmax - mhm->damage > (int) mdef->m_lev) { + mdef->mhpmax -= mhm->damage; + } else { + /* limit floor of mhpmax reduction to current m_lev + 1; + avoid increasing it if somehow already less than that */ + if (mdef->mhpmax > (int) mdef->m_lev) + mdef->mhpmax = (int) mdef->m_lev + 1; + } + mdef->mhp -= mhm->damage; + /* !m_lev: level 0 monster is killed regardless of hit points + rather than drop to level -1; note: some non-living creatures + (golems, vortices) are subject to life-drain */ + if (DEADMONSTER(mdef) || !mdef->m_lev) { + pline("%s %s!", Monnam(mdef), + nonliving(mdef->data) ? "expires" : "dies"); + xkilled(mdef, XKILL_NOMSG); + } else + mdef->m_lev--; + mhm->damage = 0; /* damage has already been inflicted */ + + /* unlike hitting with Stormbringer, wounded hero doesn't + heal any from the drained life */ + } + } else if (mdef == &gy.youmonst) { + /* mhitu */ + hitmsg(magr, mattk); + if (!rn2(3) && !Drain_resistance + && !mhitm_mgc_atk_negated(magr, mdef, TRUE)){ + losexp("life drainage"); + + /* unlike hitting with Stormbringer, wounded attacker doesn't + heal any from the drained life */ + } + } else { + /* mhitm */ + /* mhitm_ad_deth gets redirected here for Death's touch */ + boolean is_death = (mattk->adtyp == AD_DETH); + + if (is_death + || (!rn2(3) && !(resists_drli(mdef) || defended(mdef, AD_DRLI)) + && !mhitm_mgc_atk_negated(magr, mdef, TRUE))) { + if (!is_death) /* Stormbringer uses monhp_per_lvl (1d8) */ + mhm->damage = d(2, 6); + if (gv.vis && canspotmon(mdef)) + pline_mon(mdef, "%s becomes weaker!", Monnam(mdef)); + if (mdef->mhpmax - mhm->damage > (int) mdef->m_lev) { + mdef->mhpmax -= mhm->damage; + } else { + /* limit floor of mhpmax reduction to current m_lev + 1; + avoid increasing it if somehow already less than that */ + if (mdef->mhpmax > (int) mdef->m_lev) + mdef->mhpmax = (int) mdef->m_lev + 1; } + if (mdef->m_lev == 0) /* automatic kill if drained past level 0 */ + mhm->damage = mdef->mhp; + else + mdef->m_lev--; + + /* unlike hitting with Stormbringer, wounded attacker doesn't + heal any from the drained life */ } - break; - case AD_FIRE: - if (negated) { - tmp = 0; - break; + } +} + +void +mhitm_ad_fire( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + struct permonst *pd = mdef->data; + const int orig_dmg = mhm->damage; /* damage coming into the function */ + + if (magr == &gy.youmonst) { + /* uhitm */ + if (mhitm_mgc_atk_negated(magr, mdef, TRUE)) { + mhm->damage = 0; + return; } if (!Blind) pline("%s is %s!", Monnam(mdef), on_fire(pd, mattk)); if (completelyburns(pd)) { /* paper golem or straw golem */ if (!Blind) - pline("%s burns completely!", Monnam(mdef)); + /* note: the life-saved case is hypothetical because + life-saving doesn't work for golems */ + pline("%s %s!", Monnam(mdef), + !mlifesaver(mdef) ? "burns completely" + : "is totally engulfed in flames"); else You("smell burning%s.", (pd == &mons[PM_PAPER_GOLEM]) ? " paper" : (pd == &mons[PM_STRAW_GOLEM]) ? " straw" : ""); xkilled(mdef, XKILL_NOMSG | XKILL_NOCORPSE); - tmp = 0; - break; - /* Don't return yet; keep hp<1 and tmp=0 for pet msg */ + mhm->damage = 0; + return; + /* Don't return yet; keep hp<1 and mhm.damage=0 for pet msg */ } - tmp += destroy_mitem(mdef, SCROLL_CLASS, AD_FIRE); - tmp += destroy_mitem(mdef, SPBOOK_CLASS, AD_FIRE); - if (resists_fire(mdef)) { + if (resists_fire(mdef) || defended(mdef, AD_FIRE)) { if (!Blind) pline_The("fire doesn't heat %s!", mon_nam(mdef)); - golemeffects(mdef, AD_FIRE, tmp); + golemeffects(mdef, AD_FIRE, mhm->damage); shieldeff(mdef->mx, mdef->my); - tmp = 0; + mhm->damage = 0; } - /* only potions damage resistant players in destroy_item */ - tmp += destroy_mitem(mdef, POTION_CLASS, AD_FIRE); - break; - case AD_COLD: - if (negated) { - tmp = 0; - break; + mhm->damage += destroy_items(mdef, AD_FIRE, orig_dmg); + ignite_items(mdef->minvent); + } else if (mdef == &gy.youmonst) { + /* mhitu */ + hitmsg(magr, mattk); + if (!mhitm_mgc_atk_negated(magr, mdef, TRUE)) { + pline("You're %s!", on_fire(pd, mattk)); + if (completelyburns(pd)) { /* paper or straw golem */ + You("go up in flames!"); + monstunseesu(M_SEEN_FIRE); + /* KMH -- this is okay with unchanging */ + rehumanize(); + return; + } else if (Fire_resistance) { + pline_The("fire doesn't feel hot!"); + monstseesu(M_SEEN_FIRE); + mhm->damage = 0; + } else { + monstunseesu(M_SEEN_FIRE); + } + if ((int) magr->m_lev > rn2(20)) { + (void) destroy_items(&gy.youmonst, AD_FIRE, orig_dmg); + ignite_items(gi.invent); + } + burn_away_slime(); + } else { + mhm->damage = 0; + } + } else { + /* mhitm */ + if (mhitm_mgc_atk_negated(magr, mdef, TRUE)) { + mhm->damage = 0; + return; + } + if (gv.vis && canseemon(mdef)) + pline_mon(mdef, "%s is %s!", Monnam(mdef), on_fire(pd, mattk)); + if (completelyburns(pd)) { /* paper golem or straw golem */ + /* note: the life-saved case is hypothetical because + life-saving doesn't work for golems */ + if (gv.vis && canseemon(mdef)) + pline_mon(mdef, "%s %s!", Monnam(mdef), + !mlifesaver(mdef) ? "burns completely" + : "is totally engulfed in flames"); + monkilled(mdef, (char *) 0, AD_FIRE); + if (!DEADMONSTER(mdef)) { + mhm->hitflags = M_ATTK_MISS; + mhm->done = TRUE; + return; + } + mhm->hitflags = (M_ATTK_DEF_DIED + | (grow_up(magr, mdef) ? 0 : M_ATTK_AGR_DIED)); + mhm->done = TRUE; + return; + } + if (resists_fire(mdef) || defended(mdef, AD_FIRE)) { + if (gv.vis && canseemon(mdef)) + pline_The("fire doesn't seem to burn %s!", mon_nam(mdef)); + shieldeff(mdef->mx, mdef->my); + golemeffects(mdef, AD_FIRE, mhm->damage); + mhm->damage = 0; + } + mhm->damage += destroy_items(mdef, AD_FIRE, orig_dmg); + ignite_items(mdef->minvent); + } +} + +void +mhitm_ad_cold( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + const int orig_dmg = mhm->damage; + + if (magr == &gy.youmonst) { + /* uhitm */ + if (mhitm_mgc_atk_negated(magr, mdef, TRUE)) { + mhm->damage = 0; + return; } if (!Blind) pline("%s is covered in frost!", Monnam(mdef)); - if (resists_cold(mdef)) { + if (resists_cold(mdef) || defended(mdef, AD_COLD)) { shieldeff(mdef->mx, mdef->my); if (!Blind) pline_The("frost doesn't chill %s!", mon_nam(mdef)); - golemeffects(mdef, AD_COLD, tmp); - tmp = 0; + golemeffects(mdef, AD_COLD, mhm->damage); + mhm->damage = 0; } - tmp += destroy_mitem(mdef, POTION_CLASS, AD_COLD); - break; - case AD_ELEC: - if (negated) { - tmp = 0; - break; + mhm->damage += destroy_items(mdef, AD_COLD, orig_dmg); + } else if (mdef == &gy.youmonst) { + /* mhitu */ + hitmsg(magr, mattk); + if (!mhitm_mgc_atk_negated(magr, mdef, TRUE)) { + pline("You're covered in frost!"); + if (Cold_resistance) { + pline_The("frost doesn't seem cold!"); + monstseesu(M_SEEN_COLD); + mhm->damage = 0; + } else { + monstunseesu(M_SEEN_COLD); + } + if ((int) magr->m_lev > rn2(20)) + (void) destroy_items(&gy.youmonst, AD_COLD, orig_dmg); + } else + mhm->damage = 0; + } else { + /* mhitm */ + if (mhitm_mgc_atk_negated(magr, mdef, TRUE)) { + mhm->damage = 0; + return; + } + if (gv.vis && canseemon(mdef)) + pline_mon(mdef, "%s is covered in frost!", Monnam(mdef)); + if (resists_cold(mdef) || defended(mdef, AD_COLD)) { + if (gv.vis && canseemon(mdef)) + pline_The("frost doesn't seem to chill %s!", mon_nam(mdef)); + shieldeff(mdef->mx, mdef->my); + golemeffects(mdef, AD_COLD, mhm->damage); + mhm->damage = 0; + } + mhm->damage += destroy_items(mdef, AD_COLD, orig_dmg); + } +} + +void +mhitm_ad_elec( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + const int orig_dmg = mhm->damage; + + if (magr == &gy.youmonst) { + /* uhitm */ + if (mhitm_mgc_atk_negated(magr, mdef, TRUE)) { + mhm->damage = 0; + return; } if (!Blind) pline("%s is zapped!", Monnam(mdef)); - tmp += destroy_mitem(mdef, WAND_CLASS, AD_ELEC); - if (resists_elec(mdef)) { + if (resists_elec(mdef) || defended(mdef, AD_ELEC)) { if (!Blind) pline_The("zap doesn't shock %s!", mon_nam(mdef)); - golemeffects(mdef, AD_ELEC, tmp); + golemeffects(mdef, AD_ELEC, mhm->damage); shieldeff(mdef->mx, mdef->my); - tmp = 0; + mhm->damage = 0; } - /* only rings damage resistant players in destroy_item */ - tmp += destroy_mitem(mdef, RING_CLASS, AD_ELEC); - break; - case AD_ACID: - if (resists_acid(mdef)) - tmp = 0; - break; - case AD_STON: - if (!munstone(mdef, TRUE)) - minstapetrify(mdef, TRUE); - tmp = 0; - break; - case AD_SSEX: - case AD_SEDU: - case AD_SITM: - steal_it(mdef, mattk); - tmp = 0; - break; - case AD_SGLD: - /* This you as a leprechaun, so steal - real gold only, no lesser coins */ - mongold = findgold(mdef->minvent); - if (mongold) { - obj_extract_self(mongold); - if (merge_choice(invent, mongold) || inv_cnt(FALSE) < 52) { - addinv(mongold); - Your("purse feels heavier."); + mhm->damage += destroy_items(mdef, AD_ELEC, orig_dmg); + } else if (mdef == &gy.youmonst) { + /* mhitu */ + hitmsg(magr, mattk); + if (!mhitm_mgc_atk_negated(magr, mdef, TRUE)) { + You("get zapped!"); + if (Shock_resistance) { + pline_The("zap doesn't shock you!"); + monstseesu(M_SEEN_ELEC); + mhm->damage = 0; } else { - You("grab %s's gold, but find no room in your knapsack.", - mon_nam(mdef)); - dropy(mongold); + monstunseesu(M_SEEN_ELEC); } + if ((int) magr->m_lev > rn2(20)) + (void) destroy_items(&gy.youmonst, AD_ELEC, orig_dmg); + } else + mhm->damage = 0; + } else { + /* mhitm */ + if (mhitm_mgc_atk_negated(magr, mdef, TRUE)) { + mhm->damage = 0; + return; } - exercise(A_DEX, TRUE); - tmp = 0; - break; - case AD_TLPT: - if (tmp <= 0) - tmp = 1; - if (!negated) { - char nambuf[BUFSZ]; - boolean u_saw_mon = (canseemon(mdef) - || (u.uswallow && u.ustuck == mdef)); + if (gv.vis && canseemon(mdef)) + pline_mon(mdef, "%s gets zapped!", Monnam(mdef)); + if (resists_elec(mdef) || defended(mdef, AD_ELEC)) { + if (gv.vis && canseemon(mdef)) + pline_The("zap doesn't shock %s!", mon_nam(mdef)); + shieldeff(mdef->mx, mdef->my); + golemeffects(mdef, AD_ELEC, mhm->damage); + mhm->damage = 0; + } + mhm->damage += destroy_items(mdef, AD_ELEC, orig_dmg); + } +} - /* record the name before losing sight of monster */ - Strcpy(nambuf, Monnam(mdef)); - if (u_teleport_mon(mdef, FALSE) && u_saw_mon - && !(canseemon(mdef) || (u.uswallow && u.ustuck == mdef))) +void +mhitm_ad_acid( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + if (magr == &gy.youmonst) { + /* uhitm */ + if (resists_acid(mdef) || defended(mdef, AD_ACID)) + mhm->damage = 0; + } else if (mdef == &gy.youmonst) { + /* mhitu */ + hitmsg(magr, mattk); + if (!magr->mcan && !rn2(3)) + if (Acid_resistance) { + pline("You're covered in %s, but it seems harmless.", + hliquid("acid")); + monstseesu(M_SEEN_ACID); + mhm->damage = 0; + } else { + pline("You're covered in %s! It burns!", hliquid("acid")); + exercise(A_STR, FALSE); + monstunseesu(M_SEEN_ACID); + } + else + mhm->damage = 0; + } else { + /* mhitm */ + if (magr->mcan) { + mhm->damage = 0; + return; + } + if (resists_acid(mdef) || defended(mdef, AD_ACID)) { + if (gv.vis && canseemon(mdef)) + pline("%s is covered in %s, but it seems harmless.", + Monnam(mdef), hliquid("acid")); + mhm->damage = 0; + } else if (gv.vis && canseemon(mdef)) { + pline_mon(mdef, "%s is covered in %s!", Monnam(mdef), hliquid("acid")); + pline("It burns %s!", mon_nam(mdef)); + } + if (!rn2(30)) + erode_armor(mdef, ERODE_CORRODE); + if (!rn2(6)) + acid_damage(MON_WEP(mdef)); + } +} + +/* steal gold */ +void +mhitm_ad_sgld( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + struct permonst *pa = magr->data; + struct permonst *pd = mdef->data; + + if (magr == &gy.youmonst) { + /* uhitm */ + struct obj *mongold = findgold(mdef->minvent); + + if (mongold) { + obj_extract_self(mongold); + if (merge_choice(gi.invent, mongold) + || inv_cnt(FALSE) < invlet_basic) { + addinv(mongold); + Your("purse feels heavier."); + } else { + You("grab %s's gold, but find no room in your knapsack.", + mon_nam(mdef)); + dropy(mongold); + } + } + exercise(A_DEX, TRUE); + mhm->damage = 0; + } else if (mdef == &gy.youmonst) { + /* mhitu */ + hitmsg(magr, mattk); + if (pd->mlet == pa->mlet) + return; + if (!magr->mcan) + stealgold(magr); + } else { + /* mhitm */ + char buf[BUFSZ]; + + mhm->damage = 0; + if (magr->mcan) + return; + /* technically incorrect; no check for stealing gold from + * between mdef's feet... + */ + { + struct obj *gold = findgold(mdef->minvent); + + if (!gold) + return; + obj_extract_self(gold); + add_to_minv(magr, gold); + } + mdef->mstrategy &= ~STRAT_WAITFORU; + Strcpy(buf, Monnam(magr)); + if (gv.vis && canseemon(mdef)) { + pline("%s steals some gold from %s.", buf, mon_nam(mdef)); + } + if (!tele_restrict(magr)) { + boolean couldspot = canspotmon(magr); + + mhm->hitflags = M_ATTK_AGR_DONE; + (void) rloc(magr, RLOC_NOMSG); + /* TODO: use RLOC_MSG instead? */ + if (gv.vis && couldspot && !canspotmon(magr)) + pline("%s suddenly disappears!", buf); + } + } +} + + +void +mhitm_ad_tlpt( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + if (magr == &gy.youmonst) { + /* uhitm */ + if (mhm->damage <= 0) + mhm->damage = 1; + if (mhitm_mgc_atk_negated(magr, mdef, TRUE)) { + pline("%s is not affected.", Monnam(mdef)); + } else { + char nambuf[BUFSZ]; + boolean u_saw_mon = (canseemon(mdef) || engulfing_u(mdef)); + + /* record the name before losing sight of monster */ + Strcpy(nambuf, Monnam(mdef)); + if (u_teleport_mon(mdef, FALSE) && u_saw_mon + && !(canseemon(mdef) || engulfing_u(mdef))) pline("%s suddenly disappears!", nambuf); - if (tmp >= mdef->mhp) { /* see hitmu(mhitu.c) */ + if (mhm->damage >= mdef->mhp) { /* see hitmu(mhitu.c) */ if (mdef->mhp == 1) ++mdef->mhp; - tmp = mdef->mhp - 1; + mhm->damage = mdef->mhp - 1; } } - break; - case AD_BLND: - if (can_blnd(&youmonst, mdef, mattk->aatyp, (struct obj *) 0)) { + } else if (mdef == &gy.youmonst) { + /* mhitu */ + int tmphp; + + hitmsg(magr, mattk); + if (mhitm_mgc_atk_negated(magr, mdef, FALSE)) { + You("are not affected."); + } else { + if (flags.verbose) + Your("position suddenly seems %suncertain!", + (Teleport_control && !Stunned && !unconscious()) ? "" + : "very "); + tele(); + /* As of 3.6.2: make sure damage isn't fatal; previously, it + was possible to be teleported and then drop dead at + the destination when QM's 1d4 damage gets applied below; + even though that wasn't "wrong", it seemed strange, + particularly if the teleportation had been controlled + [applying the damage first and not teleporting if fatal + is another alternative but it has its own complications] */ + if ((Half_physical_damage ? (mhm->damage - 1) / 2 : mhm->damage) + >= (tmphp = (Upolyd ? u.mh : u.uhp))) { + mhm->damage = tmphp - 1; + if (Half_physical_damage) + mhm->damage *= 2; /* doesn't actually increase damage; + * we only get here if half the + * original damage would have + * been fatal, so double reduced + * damage will be less than original */ + if (mhm->damage < 1) { /* implies (tmphp <= 1) */ + mhm->damage = 1; + /* this might increase current HP beyond maximum HP but it + will be immediately reduced by caller, so that should + be indistinguishable from zero damage; we don't drop + damage all the way to zero because that inhibits any + passive counterattack if poly'd hero has one */ + if (Upolyd && u.mh == 1) + ++u.mh; + else if (!Upolyd && u.uhp == 1) + ++u.uhp; + /* [don't set context.botl here] */ + } + } + } + } else { + /* mhitm */ + if (magr->mcan || mhm->damage >= mdef->mhp || tele_restrict(mdef)) { + ; /* no negation message */ + } else if (mhitm_mgc_atk_negated(magr, mdef, TRUE)) { + if (gv.vis) + pline_mon(mdef, "%s is not affected.", Monnam(mdef)); + } else { + char mdef_Monnam[BUFSZ]; + boolean wasseen = canspotmon(mdef); + + /* save the name before monster teleports, otherwise + we'll get "it" in the suddenly disappears message */ + if (gv.vis && wasseen) + Strcpy(mdef_Monnam, Monnam(mdef)); + mdef->mstrategy &= ~STRAT_WAITFORU; + (void) rloc(mdef, RLOC_NOMSG); + /* TODO: use RLOC_MSG instead? */ + if (gv.vis && wasseen && !canspotmon(mdef) && mdef != u.usteed) + pline("%s suddenly disappears!", mdef_Monnam); + if (mhm->damage >= mdef->mhp) { /* see hitmu(mhitu.c) */ + if (mdef->mhp == 1) + ++mdef->mhp; + mhm->damage = mdef->mhp - 1; + } + } + } +} + +void +mhitm_ad_blnd( + struct monst *magr, /* attacker */ + struct attack *mattk, /* magr's attack */ + struct monst *mdef, /* defender */ + struct mhitm_data *mhm) /* optional for monster vs monster */ +{ + if (magr == &gy.youmonst) { + /* uhitm */ + if (can_blnd(magr, mdef, mattk->aatyp, (struct obj *) 0)) { if (!Blind && mdef->mcansee) pline("%s is blinded.", Monnam(mdef)); mdef->mcansee = 0; - tmp += mdef->mblinded; - if (tmp > 127) - tmp = 127; - mdef->mblinded = tmp; + mhm->damage += mdef->mblinded; + if (mhm->damage > 127) + mhm->damage = 127; + mdef->mblinded = mhm->damage; } - tmp = 0; - break; - case AD_CURS: + mhm->damage = 0; + } else if (mdef == &gy.youmonst) { + /* mhitu */ + if (can_blnd(magr, mdef, mattk->aatyp, (struct obj *) 0)) { + if (!Blind) + pline("%s blinds you!", Monnam(magr)); + make_blinded(BlindedTimeout + (long) mhm->damage, FALSE); + if (!Blind) /* => Eyes of the Overworld */ + Your1(vision_clears); + } + mhm->damage = 0; + } else { + /* mhitm */ + if (can_blnd(magr, mdef, mattk->aatyp, (struct obj *) 0)) { + char buf[BUFSZ]; + unsigned rnd_tmp; + + if (gv.vis && mdef->mcansee && canspotmon(mdef)) { + /* feedback for becoming blinded is given if observed + telepathically (canspotmon suffices) but additional + info about archon's glow is only given if seen */ + Snprintf(buf, sizeof buf, "%s is blinded", Monnam(mdef)); + if (mdef->data == &mons[PM_ARCHON] && canseemon(mdef)) + Snprintf(eos(buf), sizeof buf - strlen(buf), + " by %s radiance", s_suffix(mon_nam(magr))); + pline("%s.", buf); + } + rnd_tmp = d((int) mattk->damn, (int) mattk->damd); + if ((rnd_tmp += mdef->mblinded) > 127) + rnd_tmp = 127; + mdef->mblinded = rnd_tmp; + mdef->mcansee = 0; + mdef->mstrategy &= ~STRAT_WAITFORU; + } + if (mhm) + mhm->damage = 0; + } +} + +void +mhitm_ad_curs( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + struct permonst *pa = magr->data; + struct permonst *pd = mdef->data; + + if (magr == &gy.youmonst) { + /* uhitm */ if (night() && !rn2(10) && !mdef->mcan) { if (pd == &mons[PM_CLAY_GOLEM]) { if (!Blind) pline("Some writing vanishes from %s head!", s_suffix(mon_nam(mdef))); xkilled(mdef, XKILL_NOMSG); - /* Don't return yet; keep hp<1 and tmp=0 for pet msg */ + /* Don't return yet; keep hp<1 and mhm.damage=0 for pet msg */ } else { mdef->mcan = 1; You("chuckle."); } } - tmp = 0; - break; - case AD_DRLI: - if (!negated && !rn2(3) && !resists_drli(mdef)) { - int xtmp = d(2, 6); - - pline("%s suddenly seems weaker!", Monnam(mdef)); - mdef->mhpmax -= xtmp; - mdef->mhp -= xtmp; - /* !m_lev: level 0 monster is killed regardless of hit points - rather than drop to level -1 */ - if (DEADMONSTER(mdef) || !mdef->m_lev) { - pline("%s dies!", Monnam(mdef)); - xkilled(mdef, XKILL_NOMSG); - } else - mdef->m_lev--; - tmp = 0; - } - break; - case AD_RUST: - if (pd == &mons[PM_IRON_GOLEM]) { - pline("%s falls to pieces!", Monnam(mdef)); - xkilled(mdef, XKILL_NOMSG); + mhm->damage = 0; + } else if (mdef == &gy.youmonst) { + /* mhitu */ + hitmsg(magr, mattk); + if (!night() && pa == &mons[PM_GREMLIN]) + return; + if (!magr->mcan && !rn2(10)) { + if (!Deaf) { + Soundeffect(se_laughter, 40); + if (Blind) { + You_hear("laughter."); + } else { + pline_mon(magr, "%s chuckles.", Monnam(magr)); + } + } + if (u.umonnum == PM_CLAY_GOLEM) { + pline("Some writing vanishes from your head!"); + /* KMH -- this is okay with unchanging */ + rehumanize(); + return; + } + mon_give_prop(magr, attrcurse()); } - erode_armor(mdef, ERODE_RUST); - tmp = 0; - break; - case AD_CORR: - erode_armor(mdef, ERODE_CORRODE); - tmp = 0; - break; - case AD_DCAY: - if (pd == &mons[PM_WOOD_GOLEM] || pd == &mons[PM_LEATHER_GOLEM]) { - pline("%s falls to pieces!", Monnam(mdef)); - xkilled(mdef, XKILL_NOMSG); + } else { + /* mhitm */ + if (!night() && (pa == &mons[PM_GREMLIN])) + return; + if (!magr->mcan && !rn2(10)) { + mdef->mcan = 1; /* cancelled regardless of lifesave */ + mdef->mstrategy &= ~STRAT_WAITFORU; + if (is_were(pd) && pd->mlet != S_HUMAN) + were_change(mdef); + if (pd == &mons[PM_CLAY_GOLEM]) { + if (gv.vis && canseemon(mdef)) { + pline("Some writing vanishes from %s head!", + s_suffix(mon_nam(mdef))); + pline_mon(mdef, "%s is destroyed!", Monnam(mdef)); + } + mondied(mdef); + if (!DEADMONSTER(mdef)) { + mhm->hitflags = M_ATTK_MISS; + mhm->done = TRUE; + return; + } else if (mdef->mtame && !gv.vis) { + You(brief_feeling, "strangely sad"); + } + mhm->hitflags = (M_ATTK_DEF_DIED + | (grow_up(magr, mdef) ? 0 + : M_ATTK_AGR_DIED)); + mhm->done = TRUE; + return; + } + if (!Deaf) { + if (!gv.vis) + You_hear("laughter."); + else if (canseemon(magr)) + pline_mon(magr, "%s chuckles.", Monnam(magr)); + } } - erode_armor(mdef, ERODE_ROT); - tmp = 0; - break; - case AD_DREN: - if (!negated && !rn2(4)) - xdrainenergym(mdef, TRUE); - tmp = 0; - break; - case AD_DRST: - case AD_DRDX: - case AD_DRCO: + } +} + +/* Helper for mhitm_ad_drst(), containing some code that is also called from + * mhitm_ad_phys (for poisoned weapons) and shouldn't be subject to magic + * cancellation or a 1/8 chance roll. + * In this specific case, the "mhitm" in the name ACTUALLY means just that - + * this should be called only for monster versus monster situations. */ +staticfn void +mhitm_really_poison(struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + if (gv.vis && canspotmon(magr)) + pline("%s %s was poisoned!", s_suffix(Monnam(magr)), + mpoisons_subj(magr, mattk)); + if (resists_poison(mdef)) { + if (gv.vis && canspotmon(mdef) && canspotmon(magr)) + pline_The("poison doesn't seem to affect %s.", + mon_nam(mdef)); + } else { + mhm->damage += rn1(10, 6); + if (mhm->damage >= mdef->mhp && gv.vis && canspotmon(mdef)) + pline_The("poison was deadly..."); + } +} + +void +mhitm_ad_drst( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + boolean negated = mhitm_mgc_atk_negated(magr, mdef, FALSE); + struct permonst *pa = magr->data; + + if (magr == &gy.youmonst) { + /* uhitm */ if (!negated && !rn2(8)) { - Your("%s was poisoned!", mpoisons_subj(&youmonst, mattk)); + Your("%s was poisoned!", mpoisons_subj(magr, mattk)); if (resists_poison(mdef)) { pline_The("poison doesn't seem to affect %s.", mon_nam(mdef)); } else { if (!rn2(10)) { Your("poison was deadly..."); - tmp = mdef->mhp; + mhm->damage = mdef->mhp; } else - tmp += rn1(10, 6); + mhm->damage += rn1(10, 6); } } - break; - case AD_DRIN: { + } else if (mdef == &gy.youmonst) { + /* mhitu */ + int ptmp = A_STR; /* A_STR == 0 */ + char buf[BUFSZ]; + + switch (mattk->adtyp) { + case AD_DRST: ptmp = A_STR; break; + case AD_DRDX: ptmp = A_DEX; break; + case AD_DRCO: ptmp = A_CON; break; + } + hitmsg(magr, mattk); + if (!negated && !rn2(8)) { + Sprintf(buf, "%s %s", s_suffix(Monnam(magr)), + mpoisons_subj(magr, mattk)); + poisoned(buf, ptmp, pmname(pa, Mgender(magr)), 30, FALSE); + } + } else { + /* mhitm */ + if (!negated && !rn2(8)) { + mhitm_really_poison(magr, mattk, mdef, mhm); + } + } +} + +void +mhitm_ad_drin( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + struct permonst *pd = mdef->data; + struct obj *amu; + boolean lifsav; + + /* + * Mind flayers have multiple AD_DRIN attacks (3 for plain mind flayer, + * 5 for master mind flayer). If one of those kills the target, skip + * the others (for rest of attacker's current move). To check whether + * hero has been killed, we check mortality counter. For a monster, + * we check whether it was wearing an amulet of life-saving before the + * attack and no longer wearing any amulet after the attack. + */ + + if (magr == &gy.youmonst) { + /* uhitm */ struct obj *helmet; - if (notonhead || !has_head(pd)) { + if (gn.notonhead || !has_head(pd)) { pline("%s doesn't seem harmed.", Monnam(mdef)); - tmp = 0; + /* hero should skip remaining AT_TENT+AD_DRIN attacks + because they'll be just as harmless as this one (and also + to reduce verbosity) */ + gs.skipdrin = TRUE; + mhm->damage = 0; if (!Unchanging && pd == &mons[PM_GREEN_SLIME]) { if (!Slimed) { You("suck in some slime and don't feel very well."); make_slimed(10L, (char *) 0); } } - break; + return; } if (m_slips_free(mdef, mattk)) - break; + return; + + if ((helmet = which_armor(mdef, W_ARMH)) != 0 && rn2(8)) { + pline("%s %s blocks your attack to %s head.", + s_suffix(Monnam(mdef)), helm_simple_name(helmet), + mhis(mdef)); + return; + } + amu = which_armor(mdef, W_AMUL); + lifsav = amu && amu->otyp == AMULET_OF_LIFE_SAVING; + + (void) eat_brains(&gy.youmonst, mdef, TRUE, &mhm->damage); + + /* skip further AD_DRIN if amulet of life-saving got used up */ + if (lifsav && !which_armor(mdef, W_AMUL)) + gs.skipdrin = TRUE; + + } else if (mdef == &gy.youmonst) { + /* mhitu */ + hitmsg(magr, mattk); + if (defends(AD_DRIN, uwep) || !has_head(pd)) { + You("don't seem harmed."); + /* attacker should skip remaining AT_TENT+AD_DRIN attacks */ + gs.skipdrin = TRUE; + /* Not clear what to do for green slimes */ + return; + } + if (u_slip_free(magr, mattk)) + return; + + if (uarmh && rn2(8)) { + /* not body_part(HEAD) */ + Your("%s blocks the attack to your head.", + helm_simple_name(uarmh)); + return; + } + /* negative armor class doesn't reduce this damage */ + if (Half_physical_damage) + mhm->damage = (mhm->damage + 1) / 2; + mdamageu(magr, mhm->damage); + mhm->damage = 0; /* don't inflict a second dose below */ + + if (!uarmh || uarmh->otyp != DUNCE_CAP) { + int oldmort = u.umortality, + mhitu = eat_brains(magr, mdef, TRUE, (int *) 0); + + /* skip further AD_DRIN if hero's number of deaths went up */ + if (u.umortality > oldmort) + gs.skipdrin = TRUE; + /* eat_brains() will miss if target is mindless (won't + happen here--hero is considered to retain his mind + regardless of current shape) or is noncorporeal + (can't happen here--no one can poly into a ghost + or shade) so this check for missing is academic */ + if (mhitu == M_ATTK_MISS) + return; + } + /* adjattrib gives dunce cap message when appropriate */ + (void) adjattrib(A_INT, -rnd(2), FALSE); + if (!rn2(5)) { + losespells(); + gs.skipdrin = TRUE; + } + if (!rn2(5)) { + drain_weapon_skill(rnd(2)); + gs.skipdrin = TRUE; + } + } else { + /* mhitm */ + char buf[BUFSZ]; + + if (gn.notonhead || !has_head(pd)) { + if (gv.vis && canspotmon(mdef)) + pline_mon(mdef, "%s doesn't seem harmed.", Monnam(mdef)); + /* Not clear what to do for green slimes */ + mhm->damage = 0; + /* don't bother with additional DRIN attacks since they wouldn't + be able to hit target on head either */ + gs.skipdrin = TRUE; /* affects mattackm()'s attack loop */ + return; + } + if ((mdef->misc_worn_check & W_ARMH) && rn2(8)) { + if (gv.vis && canspotmon(magr) && canseemon(mdef)) { + Strcpy(buf, s_suffix(Monnam(mdef))); + pline("%s helmet blocks %s attack to %s head.", buf, + s_suffix(mon_nam(magr)), mhis(mdef)); + } + return; + } + amu = which_armor(mdef, W_AMUL); + lifsav = amu && amu->otyp == AMULET_OF_LIFE_SAVING; + + mhm->hitflags = eat_brains(magr, mdef, gv.vis, &mhm->damage); + + /* skip further AD_DRIN if amulet of life-saving got used up */ + if (lifsav && !which_armor(mdef, W_AMUL)) + gs.skipdrin = TRUE; + } +} + +void +mhitm_ad_stck( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + boolean negated = mhitm_mgc_atk_negated(magr, mdef, FALSE); + struct permonst *pd = mdef->data; + boolean barbs = (magr->data == &mons[PM_BARBED_DEVIL]); + + if (magr == &gy.youmonst) { + /* uhitm */ + if (!negated && !sticks(pd) && m_next2u(mdef)) { + set_ustuck(mdef); /* it's now stuck to you */ + if (barbs) + Your("barbs stick to %s!", y_monnam(mdef)); + } + } else if (mdef == &gy.youmonst) { + /* mhitu */ + hitmsg(magr, mattk); + if (!negated && !u.ustuck && !sticks(pd)) { + set_ustuck(magr); + if (barbs) + pline("The barbs stick to you!"); + } + } else { + /* mhitm */ + if (negated) + mhm->damage = 0; + } +} + +void +mhitm_ad_wrap( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + struct permonst *pd = mdef->data, *pa = magr->data; + boolean coil = slithy(pa) && (pa->mlet == S_SNAKE || pa->mlet == S_NAGA); + + if (magr == &gy.youmonst) { + /* uhitm */ + if (!sticks(pd)) { + boolean tailmiss = !gn.notonhead; + + if (!u.ustuck && !tailmiss && !rn2(10)) { + if (m_slips_free(mdef, mattk)) { + mhm->damage = 0; + } else { + You("%s yourself around %s!", + coil ? "coil" : "swing", mon_nam(mdef)); + set_ustuck(mdef); + } + } else if (u.ustuck == mdef && !tailmiss) { + /* Monsters don't wear amulets of magical breathing */ + if (is_pool(u.ux, u.uy) && !cant_drown(pd)) { + You("drown %s...", mon_nam(mdef)); + mhm->damage = mdef->mhp; + } else if (mattk->aatyp == AT_HUGS) + pline("%s is being crushed.", Monnam(mdef)); + } else { + mhm->damage = 0; + if (flags.verbose) { + if (coil && !tailmiss) + You("brush against %s.", mon_nam(mdef)); + else + You("brush against %s %s.", s_suffix(mon_nam(mdef)), + tailmiss ? "tail" : mbodypart(mdef, LEG)); + } + } + } else + mhm->damage = 0; + } else if (mdef == &gy.youmonst) { + /* mhitu */ + if ((!magr->mcan || u.ustuck == magr) && !sticks(pd)) { + if (!u.ustuck && !rn2(10)) { + if (u_slip_free(magr, mattk)) { + mhm->damage = 0; + } else { + set_ustuck(magr); /* before message, for botl update */ + urgent_pline("%s %s itself around you!", + Some_Monnam(magr), + coil ? "coils" : "swings"); + } + } else if (u.ustuck == magr) { + if (is_pool(magr->mx, magr->my) && !Swimming && !Amphibious + && !Breathless) { + boolean moat = (levl[magr->mx][magr->my].typ != POOL) + && !is_waterwall(magr->mx, magr->my) + && !Is_medusa_level(&u.uz) + && !Is_waterlevel(&u.uz); + + urgent_pline("%s drowns you...", Monnam(magr)); + svk.killer.format = KILLED_BY_AN; + Sprintf(svk.killer.name, "%s by %s", + moat ? "moat" : "pool of water", + an(pmname(magr->data, Mgender(magr)))); + done(DROWNING); + } else if (mattk->aatyp == AT_HUGS) { + You("are being crushed."); + } + } else { + mhm->damage = 0; + if (flags.verbose) { + if (coil) + pline_mon(magr, "%s brushes against you.", + Monnam(magr)); + else + pline_mon(magr, "%s brushes against your %s.", + Monnam(magr), body_part(LEG)); + } + } + } else + mhm->damage = 0; + } else { + /* mhitm */ + if (magr->mcan) + mhm->damage = 0; + + if (!mhm->damage && (canseemon(magr) || canseemon(mdef))) { + pline("%s brushes against %s.", + Some_Monnam(magr), some_mon_nam(mdef)); + } + } +} + +void +mhitm_ad_plys( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + if (magr == &gy.youmonst) { + /* uhitm */ + if (!rn2(3) && mhm->damage < mdef->mhp + && !mhitm_mgc_atk_negated(magr, mdef, TRUE)) { + if (!Blind) + pline("%s is frozen by you!", Monnam(mdef)); + paralyze_monst(mdef, rnd(10)); + } + } else if (mdef == &gy.youmonst) { + /* mhitu */ + hitmsg(magr, mattk); + if (gm.multi >= 0 && !rn2(3) + && !mhitm_mgc_atk_negated(magr, mdef, TRUE)) { + if (Free_action) { + You("momentarily stiffen."); + } else { + if (Blind) + You("are frozen!"); + else + You("are frozen by %s!", mon_nam(magr)); + gn.nomovemsg = You_can_move_again; + nomul(-rnd(10)); + /* set gm.multi_reason; + 3.6.x used "paralyzed by a monster"; be more specific */ + dynamic_multi_reason(magr, "paralyzed", FALSE); + exercise(A_DEX, FALSE); + } + } + } else { + /* mhitm */ + if (mdef->mcanmove && !rn2(3) + && !mhitm_mgc_atk_negated(magr, mdef, TRUE)) { + if (gv.vis && canspotmon(mdef)) { + char buf[BUFSZ]; + + Strcpy(buf, Monnam(mdef)); + pline("%s is frozen by %s.", buf, mon_nam(magr)); + } + paralyze_monst(mdef, rnd(10)); + } + } +} + +void +mhitm_ad_slee( + struct monst *magr, struct attack *mattk, + struct monst *mdef, + struct mhitm_data *mhm UNUSED) +{ + if (magr == &gy.youmonst) { + /* uhitm */ + if (!mdef->msleeping && !mhitm_mgc_atk_negated(magr, mdef, FALSE) + && sleep_monst(mdef, rnd(10), -1)) { + if (!Blind) + pline("%s is put to sleep by you!", Monnam(mdef)); + slept_monst(mdef); + } + } else if (mdef == &gy.youmonst) { + /* mhitu */ + hitmsg(magr, mattk); + if (gm.multi >= 0 && !rn2(5) + && !mhitm_mgc_atk_negated(magr, mdef, TRUE)) { + if (Sleep_resistance) { + monstseesu(M_SEEN_SLEEP); + return; + } + monstunseesu(M_SEEN_SLEEP); + fall_asleep(-rnd(10), TRUE); + if (Blind) + You("are put to sleep!"); + else + You("are put to sleep by %s!", mon_nam(magr)); + } + } else { + /* mhitm */ + if (!mdef->msleeping && sleep_monst(mdef, rnd(10), -1) + && sleep_monst(mdef, rnd(10), -1)) { + if (gv.vis && canspotmon(mdef)) { + char buf[BUFSZ]; + + Strcpy(buf, Monnam(mdef)); + pline("%s is put to sleep by %s.", buf, mon_nam(magr)); + } + mdef->mstrategy &= ~STRAT_WAITFORU; + slept_monst(mdef); + } + } +} + +/* slime */ +void +mhitm_ad_slim( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + boolean negated = mhitm_mgc_atk_negated(magr, mdef, FALSE); + struct permonst *pd = mdef->data; + + if (magr == &gy.youmonst) { + /* uhitm */ + if (negated) + return; /* physical damage only */ + if (!rn2(4) && !slimeproof(pd)) { + if (!munslime(mdef, TRUE) && !DEADMONSTER(mdef)) { + /* this assumes newcham() won't fail; since hero has + a slime attack, green slimes haven't been geno'd */ + You("turn %s into slime.", mon_nam(mdef)); + if (newcham(mdef, &mons[PM_GREEN_SLIME], NO_NC_FLAGS)) + pd = mdef->data; + } + /* munslime attempt could have been fatal */ + if (DEADMONSTER(mdef)) { + mhm->hitflags = M_ATTK_DEF_DIED; /* skip death message */ + mhm->done = TRUE; + return; + } + mhm->damage = 0; + } + } else if (mdef == &gy.youmonst) { + /* mhitu */ + hitmsg(magr, mattk); + if (negated) { + if (!magr->mcan) + You("escape harm."); + return; + } + if (flaming(pd)) { + pline_The("slime burns away!"); + mhm->damage = 0; + } else if (Unchanging || noncorporeal(pd) + || pd == &mons[PM_GREEN_SLIME]) { + You("are unaffected."); + mhm->damage = 0; + } else if (!Slimed) { + You("don't feel very well."); + make_slimed(10L, (char *) 0); + delayed_killer(SLIMED, KILLED_BY_AN, + pmname(magr->data, Mgender(magr))); + } else + pline("Yuck!"); + } else { + /* mhitm */ + if (negated) + return; /* physical damage only */ + if (!rn2(4) && !slimeproof(pd)) { + if (!munslime(mdef, FALSE) && !DEADMONSTER(mdef)) { + unsigned ncflags = NO_NC_FLAGS; + + if (gv.vis && canseemon(mdef)) + ncflags |= NC_SHOW_MSG; + if (newcham(mdef, &mons[PM_GREEN_SLIME], ncflags)) + pd = mdef->data; + mdef->mstrategy &= ~STRAT_WAITFORU; + mhm->hitflags = M_ATTK_HIT; + } + /* munslime attempt could have been fatal, + potentially to multiple monsters (SCR_FIRE) */ + if (DEADMONSTER(magr)) + mhm->hitflags |= M_ATTK_AGR_DIED; + if (DEADMONSTER(mdef)) + mhm->hitflags |= M_ATTK_DEF_DIED; + mhm->damage = 0; + } + } + nhUse(pd); +} + +void +mhitm_ad_ench( + struct monst *magr, struct attack *mattk, + struct monst *mdef, + struct mhitm_data *mhm UNUSED) +{ + if (magr == &gy.youmonst) { + /* uhitm */ + /* there's no msomearmor() function, so just do damage */ + } else if (mdef == &gy.youmonst) { + /* mhitu */ + boolean negated = mhitm_mgc_atk_negated(magr, mdef, FALSE); + + hitmsg(magr, mattk); + /* uncancelled is sufficient enough; please + don't make this attack less frequent */ + if (!negated) { + struct obj *obj = some_armor(mdef); + + if (!obj) { + /* some rings are susceptible; + amulets and blindfolds aren't (at present) */ + switch (rn2(5)) { + case 0: + break; + case 1: + obj = uright; + break; + case 2: + obj = uleft; + break; + case 3: + obj = uamul; + break; + case 4: + obj = ublindf; + break; + } + } + if (obj && drain_item(obj, FALSE)) { + pline("%s less effective.", Yobjnam2(obj, "seem")); + } + } + } else { + /* mhitm */ + /* there's no msomearmor() function, so just do damage */ + } +} + +void +mhitm_ad_slow( + struct monst *magr, struct attack *mattk, + struct monst *mdef, + struct mhitm_data *mhm UNUSED) +{ + boolean negated = mhitm_mgc_atk_negated(magr, mdef, FALSE); + + if (defended(mdef, AD_SLOW)) + return; + + if (magr == &gy.youmonst) { + /* uhitm */ + if (!negated && mdef->mspeed != MSLOW) { + unsigned int oldspeed = mdef->mspeed; + + mon_adjust_speed(mdef, -1, (struct obj *) 0); + if (mdef->mspeed != oldspeed && canseemon(mdef)) + pline("%s slows down.", Monnam(mdef)); + } + } else if (mdef == &gy.youmonst) { + /* mhitu */ + hitmsg(magr, mattk); + if (!negated && HFast && !rn2(4)) + u_slow_down(); + } else { + /* mhitm */ + if (!negated && mdef->mspeed != MSLOW) { + unsigned int oldspeed = mdef->mspeed; + + mon_adjust_speed(mdef, -1, (struct obj *) 0); + mdef->mstrategy &= ~STRAT_WAITFORU; + if (mdef->mspeed != oldspeed && gv.vis && canspotmon(mdef)) + pline_mon(mdef, "%s slows down.", Monnam(mdef)); + } + } +} + +void +mhitm_ad_conf( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + if (magr == &gy.youmonst) { + /* uhitm */ + if (!mdef->mconf) { + if (canseemon(mdef)) + pline("%s looks confused.", Monnam(mdef)); + mdef->mconf = 1; + } + } else if (mdef == &gy.youmonst) { + /* mhitu */ + hitmsg(magr, mattk); + if (!magr->mcan && !rn2(4) && !magr->mspec_used) { + magr->mspec_used = magr->mspec_used + (mhm->damage + rn2(6)); + if (Confusion) + You("are getting even more confused."); + else + You("are getting confused."); + make_confused(HConfusion + mhm->damage, FALSE); + } + mhm->damage = 0; + } else { + /* mhitm */ + /* Since confusing another monster doesn't have a real time + * limit, setting spec_used would not really be right (though + * we still should check for it). + */ + if (!magr->mcan && !mdef->mconf && !magr->mspec_used) { + if (gv.vis && canseemon(mdef)) + pline_mon(mdef, "%s looks confused.", Monnam(mdef)); + mdef->mconf = 1; + mdef->mstrategy &= ~STRAT_WAITFORU; + } + } +} + +void +mhitm_ad_poly( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + boolean negated = (mhitm_mgc_atk_negated(magr, mdef, FALSE) + || magr->mspec_used); + + if (magr == &gy.youmonst) { + /* uhitm */ + /* require weaponless attack in order to honor AD_POLY */ + if (!uwep && mhm->damage < mdef->mhp) { + if (negated) { + /* assume that you can tell by touch if blinded */ + pline("%s is not transformed.", Monnam(mdef)); + } else { + mhm->damage = mon_poly(&gy.youmonst, mdef, mhm->damage); + if (DEADMONSTER(mdef)) + mhm->hitflags |= M_ATTK_DEF_DIED; + mhm->hitflags |= M_ATTK_HIT; + mhm->done = TRUE; + } + } + } else if (mdef == &gy.youmonst) { + /* mhitu */ + hitmsg(magr, mattk); + if (Maybe_Half_Phys(mhm->damage) < (Upolyd ? u.mh : u.uhp)) { + if (negated) { + if (magr->mcan) + You("aren't transformed."); + } else { + mhm->damage = mon_poly(magr, &gy.youmonst, mhm->damage); + mhm->hitflags |= M_ATTK_HIT; + mhm->done = TRUE; + } + } + } else { + /* mhitm */ + if (mhm->damage < mdef->mhp && !negated) { + mhm->damage = mon_poly(magr, mdef, mhm->damage); + if (DEADMONSTER(mdef)) + mhm->hitflags |= M_ATTK_DEF_DIED; + mhm->hitflags |= M_ATTK_HIT; + mhm->done = TRUE; + } + } +} + +void +mhitm_ad_famn( + struct monst *magr, + struct attack *mattk UNUSED, + struct monst *mdef, struct mhitm_data *mhm) +{ + struct permonst *pd = mdef->data; + + if (magr == &gy.youmonst) { + /* uhitm; hero can't polymorph into anything with this attack + so this won't happen; if it could, it would be the same as + the mhitm case except for messaging */ + goto mhitm_famn; + } else if (mdef == &gy.youmonst) { + /* mhitu */ + pline_mon(magr, "%s reaches out, and your body shrivels.", + Monnam(magr)); + exercise(A_CON, FALSE); + if (!is_fainted()) + morehungry(rn1(40, 40)); + /* plus the normal damage */ + } else { + mhitm_famn: + /* mhitm; it's possible for Famine to hit another monster; + if target is something that doesn't eat, it won't be harmed; + otherwise, just inflict the normal damage */ + if (!(carnivorous(pd) || herbivorous(pd) || metallivorous(pd))) + mhm->damage = 0; + } +} + +void +mhitm_ad_pest( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + struct attack alt_attk; + struct permonst *pa = magr->data; + + if (magr == &gy.youmonst) { + /* uhitm; hero can't polymorph into anything with this attack + so this won't happen; if it could, it would be the same as + the mhitm case except for messaging */ + goto mhitm_pest; + } else if (mdef == &gy.youmonst) { + /* mhitu */ + pline_mon(magr, "%s reaches out, and you feel fever and chills.", + Monnam(magr)); + (void) diseasemu(pa); + /* plus the normal damage */ + } else { + mhitm_pest: + /* mhitm; it's possible for Pestilence to hit another monster; + treat it the same as an attack for AD_DISE damage */ + alt_attk = *mattk; + alt_attk.adtyp = AD_DISE; + mhitm_ad_dise(magr, &alt_attk, mdef, mhm); + } +} + +void +mhitm_ad_deth( + struct monst *magr, + struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + struct permonst *pd = mdef->data; + + if (magr == &gy.youmonst) { + /* uhitm; hero can't polymorph into anything with this attack + so this won't happen; if it could, it would be the same as + the mhitm case except for messaging */ + goto mhitm_deth; + } else if (mdef == &gy.youmonst) { + /* mhitu */ + pline_mon(magr, "%s reaches out with its deadly touch.", Monnam(magr)); + if (is_undead(pd)) { + /* still does some damage */ + mhm->damage = (mhm->damage + 1) / 2; + pline("Was that the touch of death?"); + return; + } + switch (rn2(20)) { + case 19: + case 18: + case 17: + if (!Antimagic) { + touch_of_death(magr); + mhm->damage = 0; + return; + } + FALLTHROUGH; + /*FALLTHRU*/ + default: /* case 16: ... case 5: */ + You_feel("your life force draining away..."); + mhm->permdmg = 1; /* actual damage done by caller */ + return; + case 4: + case 3: + case 2: + case 1: + case 0: + if (Antimagic) + shieldeff(u.ux, u.uy); + pline("Lucky for you, it didn't work!"); + mhm->damage = 0; + return; + } + } else { + mhitm_deth: + /* mhitm; it's possible for Death to hit another monster; + if target is undead, it will take some damage but less than an + undead hero would; otherwise, just inflict the normal damage */ + if (is_undead(pd) && mhm->damage > 1) + mhm->damage = rnd(mhm->damage / 2); + /* simulate Death's touch with drain life attack */ + mhitm_ad_drli(magr, mattk, mdef, mhm); + } +} + +void +mhitm_ad_halu( + struct monst *magr, + struct attack *mattk UNUSED, + struct monst *mdef, struct mhitm_data *mhm) +{ + struct permonst *pd = mdef->data; + + if (magr == &gy.youmonst) { + /* uhitm */ + mhm->damage = 0; + } else if (mdef == &gy.youmonst) { + /* mhitu */ + mhm->damage = 0; + } else { + /* mhitm */ + if (!magr->mcan && haseyes(pd) && mdef->mcansee) { + if (gv.vis && canseemon(mdef)) + pline_mon(mdef, "%s looks %sconfused.", Monnam(mdef), + mdef->mconf ? "more " : ""); + mdef->mconf = 1; + mdef->mstrategy &= ~STRAT_WAITFORU; + } + mhm->damage = 0; + } +} + +boolean +do_stone_u(struct monst *mtmp) +{ + if (!Stoned && !Stone_resistance + && !(poly_when_stoned(gy.youmonst.data) + && polymon(PM_STONE_GOLEM))) { + int kformat = KILLED_BY_AN; + const char *kname = pmname(mtmp->data, Mgender(mtmp)); + + if (mtmp->data->geno & G_UNIQ) { + if (!type_is_pname(mtmp->data)) + kname = the(kname); + kformat = KILLED_BY; + } + make_stoned(5L, (char *) 0, kformat, kname); + return 1; + /* done_in_by(mtmp, STONING); */ + } + return 0; +} + +void +do_stone_mon( + struct monst *magr, + struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + struct permonst *pd = mdef->data; + + /* may die from the acid if it eats a stone-curing corpse */ + if (munstone(mdef, FALSE)) + goto post_stone; + if (poly_when_stoned(pd)) { + mon_to_stone(mdef); + mhm->damage = 0; + return; + } + if (!resists_ston(mdef)) { + if (gv.vis && canseemon(mdef)) + pline_mon(mdef, "%s turns to stone!", Monnam(mdef)); + monstone(mdef); + post_stone: + if (!DEADMONSTER(mdef)) { + mhm->hitflags = M_ATTK_MISS; + mhm->done = TRUE; + return; + } else if (mdef->mtame && !gv.vis) { + You(brief_feeling, "peculiarly sad"); + } + mhm->hitflags = (M_ATTK_DEF_DIED + | (grow_up(magr, mdef) ? 0 : M_ATTK_AGR_DIED)); + mhm->done = TRUE; + return; + } + mhm->damage = (mattk->adtyp == AD_STON ? 0 : 1); +} + +void +mhitm_ad_phys( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + struct permonst *pa = magr->data; + struct permonst *pd = mdef->data; + + if (magr == &gy.youmonst) { + /* uhitm */ + if (pd == &mons[PM_SHADE]) { + mhm->damage = 0; + if (!mhm->specialdmg) + impossible("bad shade attack function flow?"); + } + mhm->damage += mhm->specialdmg; + + if (mattk->aatyp == AT_WEAP) { + /* hmonas() uses known_hitum() to deal physical damage, + then also damageum() for non-AD_PHYS; don't inflict + extra physical damage for unusual damage types */ + mhm->damage = 0; + } else if (mattk->aatyp == AT_KICK + || mattk->aatyp == AT_CLAW + || mattk->aatyp == AT_TUCH + || mattk->aatyp == AT_HUGS) { + if (thick_skinned(pd)) + mhm->damage = (mattk->aatyp == AT_KICK) ? 0 + : (mhm->damage + 1) / 2; + /* add ring(s) of increase damage */ + if (u.udaminc > 0) { + /* applies even if damage was 0 */ + mhm->damage += u.udaminc; + } else if (mhm->damage > 0) { + /* ring(s) might be negative; avoid converting + 0 to non-0 or positive to non-positive */ + mhm->damage += u.udaminc; + if (mhm->damage < 1) + mhm->damage = 1; + } + } + } else if (mdef == &gy.youmonst) { + /* mhitu */ + if (mattk->aatyp == AT_HUGS && !sticks(pd)) { + if (!u.ustuck && rn2(2)) { + if (u_slip_free(magr, mattk)) { + mhm->damage = 0; + mhm->hitflags |= M_ATTK_MISS; + } else { + set_ustuck(magr); + pline_mon(magr, "%s grabs you!", Monnam(magr)); + mhm->hitflags |= M_ATTK_HIT; + } + } else if (u.ustuck == magr) { + exercise(A_STR, FALSE); + You("are being %s.", + (pa == &mons[PM_ROPE_GOLEM]) ? "choked" : "crushed"); + } + } else { /* hand to hand weapon */ + struct obj *otmp = MON_WEP(magr); + + if (mattk->aatyp == AT_WEAP && otmp) { + struct obj *marmg; + int tmp; + boolean was_poisoned = (otmp->opoisoned + || permapoisoned(otmp)); + + if (otmp->otyp == CORPSE + && touch_petrifies(&mons[otmp->corpsenm])) { + mhm->damage = 1; + pline_mon(magr, "%s hits you with the %s corpse.", + Monnam(magr), + mons[otmp->corpsenm].pmnames[NEUTRAL]); + if (!Stoned) { + if (do_stone_u(magr)) { + mhm->hitflags = M_ATTK_HIT; + mhm->done = 1; + return; + } + } + } + mhm->damage += dmgval(otmp, mdef); + if ((marmg = which_armor(magr, W_ARMG)) != 0 + && marmg->otyp == GAUNTLETS_OF_POWER) + mhm->damage += rn1(4, 3); /* 3..6 */ + if (mhm->damage <= 0) + mhm->damage = 1; + if (!otmp->oartifact + || !artifact_hit(magr, mdef, otmp, &mhm->damage, + gm.mhitu_dieroll)) { + hitmsg(magr, mattk); + mhm->hitflags |= M_ATTK_HIT; + } + if (!mhm->damage) + return; + if (objects[otmp->otyp].oc_material == SILVER + && Hate_silver) { + pline_The("silver sears your flesh!"); + exercise(A_CON, FALSE); + } + /* this redundancy necessary because you have + to take the damage _before_ being cloned; + need to have at least 2 hp left to split */ + tmp = mhm->damage; + if (u.uac < 0) + tmp -= rnd(-u.uac); + if (tmp < 1) + tmp = 1; + if (Half_physical_damage) + tmp = (tmp + 1) / 2; + + if (u.mh - tmp > 1 + && (objects[otmp->otyp].oc_material == IRON + /* relevant 'metal' objects are scalpel and tsurugi */ + || objects[otmp->otyp].oc_material == METAL) + && (u.umonnum == PM_BLACK_PUDDING + || u.umonnum == PM_BROWN_PUDDING)) { + if (tmp > 1) + exercise(A_STR, FALSE); + /* inflict damage now; we know it can't be fatal */ + u.mh -= tmp; + disp.botl = TRUE; + mhm->damage = 0; /* don't inflict more damage below */ + if (cloneu()) + You("divide as %s hits you!", mon_nam(magr)); + } + rustm(&gy.youmonst, otmp); + if (was_poisoned && gm.mhitu_dieroll <= 5) { + char buf[BUFSZ]; + + /* similar to mhitm_really_poison, but we don't use the + * exact same values, nor do we want same 1/8 chance of + * poison taking (use 1/4, same as in the mhitm case). */ + Sprintf(buf, "%s %s", s_suffix(Monnam(magr)), + mpoisons_subj(magr, mattk)); + /* arbitrary, but most poison sources in the game are + * strength-based. With hpdamchance = 10, HP damage occurs + * 1/2 of the time and it will hit Str rest of the time. + * (This is the same as poisoned ammo.) */ + poisoned(buf, A_STR, pmname(magr->data, Mgender(magr)), + 10, FALSE); + } + } else if (mattk->aatyp != AT_TUCH || mhm->damage != 0 + || magr != u.ustuck) { + hitmsg(magr, mattk); + mhm->hitflags |= M_ATTK_HIT; + } + } + } else { + /* mhitm */ + struct obj *mwep = MON_WEP(magr); + boolean vis = canseemon(magr) && canseemon(mdef); + + if (mattk->aatyp != AT_WEAP && mattk->aatyp != AT_CLAW) + mwep = 0; + + if (shade_miss(magr, mdef, mwep, FALSE, vis)) { + mhm->damage = 0; + } else if (mattk->aatyp == AT_KICK && thick_skinned(pd)) { + /* [no 'kicking boots' check needed; monsters with kick attacks + can't wear boots and monsters that wear boots don't kick] */ + mhm->damage = 0; + } else if (mwep) { /* non-Null 'mwep' implies AT_WEAP || AT_CLAW */ + struct obj *marmg; + + if (mwep->otyp == CORPSE + && touch_petrifies(&mons[mwep->corpsenm])) { + do_stone_mon(magr, mattk, mdef, mhm); + if (mhm->done) + return; + } + + mhm->damage += dmgval(mwep, mdef); + if ((marmg = which_armor(magr, W_ARMG)) != 0 + && marmg->otyp == GAUNTLETS_OF_POWER) + mhm->damage += rn1(4, 3); /* 3..6 */ + if (mhm->damage < 1) /* is this necessary? mhitu.c has it... */ + mhm->damage = 1; + if (mwep->oartifact) { + /* when magr's weapon is an artifact, caller suppressed its + usual 'hit' message in case artifact_hit() delivers one; + now we'll know and might need to deliver skipped message + (note: if there's no message there'll be no auxiliary + damage so the message here isn't coming too late) */ + if (!artifact_hit(magr, mdef, mwep, &mhm->damage, + mhm->dieroll)) { + if (gv.vis) + pline_mon(magr, "%s hits %s.", Monnam(magr), + mon_nam_too(mdef, magr)); + mhm->hitflags |= M_ATTK_HIT; + } + /* artifact_hit updates 'tmp' but doesn't inflict any + damage; however, it might cause carried items to be + destroyed and they might do so */ + if (DEADMONSTER(mdef)) { + mhm->hitflags = (M_ATTK_DEF_DIED + | (grow_up(magr, mdef) ? 0 + : M_ATTK_AGR_DIED)); + mhm->done = TRUE; + return; + } + } + if (mhm->damage) + rustm(mdef, mwep); + if ((mwep->opoisoned || permapoisoned(mwep)) && !rn2(4)) { + /* 1/4 chance of weapon poison applying is the same as in + * uhitm and mhitu cases. But since we don't need to call + * any special functions or go through tangled hmon_hitmon + * code, we can just jump straight to the poisoning. */ + mhitm_really_poison(magr, mattk, mdef, mhm); + } + } else if (pa == &mons[PM_PURPLE_WORM] && pd == &mons[PM_SHRIEKER]) { + /* hack to enhance mm_aggression(); we don't want purple + worm's bite attack to kill a shrieker because then it + won't swallow the corpse; but if the target survives, + the subsequent engulf attack should accomplish that */ + if (mhm->damage >= mdef->mhp && mdef->mhp > 1) + mhm->damage = mdef->mhp - 1; + } + } +} + +void +mhitm_ad_ston( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + if (magr == &gy.youmonst) { + /* uhitm */ + if (!munstone(mdef, TRUE)) + minstapetrify(mdef, TRUE); + mhm->damage = 0; + } else if (mdef == &gy.youmonst) { + /* mhitu */ + hitmsg(magr, mattk); + if (!rn2(3)) { + if (magr->mcan) { + if (!Deaf) + You_hear("a cough from %s!", mon_nam(magr)); + } else { + if (Hallucination && !Blind) { + Soundeffect(se_cockatrice_hiss, 50); + You_hear("hissing."); /* You_hear() deals with Deaf */ + pline("%s appears to be blowing you a kiss...", + Monnam(magr)); + } else if (!Deaf) { + You_hear("%s hissing!", s_suffix(mon_nam(magr))); + } else if (!Blind) { + pline("%s seems to grimace.", Monnam(magr)); + } + /* + * 5.0: New moon is no longer overridden by carrying a + * lizard corpse. Having the moon's impact on terrestrial + * activity be affected by carrying a dead critter felt + * silly. + * + * That behavior dated to when there were no corpse objects + * yet; "dead lizard" was a distinct item. With a lizard + * corpse, hero can eat it to survive petrification and + * probably retain a partly eaten corpse for future use. + * + * Maintaining foodless conduct during a new moon might + * become a little harder. Clearing out cockatrice nests + * during a new moon could become quite a bit harder. + */ + if (!rn2(10) || flags.moonphase == NEW_MOON) { + if (do_stone_u(magr)) { + mhm->hitflags = M_ATTK_HIT; + mhm->done = TRUE; + return; + } + } + } + } + } else { + /* mhitm */ + if (magr->mcan) + return; + do_stone_mon(magr, mattk, mdef, mhm); + if (mhm->done) + return; + } +} + +void +mhitm_ad_were( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + struct permonst *pa = magr->data; + + if (magr == &gy.youmonst) { + /* uhitm */ + mhitm_ad_phys(magr, mattk, mdef, mhm); + if (mhm->done) + return; + } else if (mdef == &gy.youmonst) { + /* mhitu */ + hitmsg(magr, mattk); + if (!rn2(4) && u.ulycn == NON_PM + && !Protection_from_shape_changers && !defends(AD_WERE, uwep) + && !mhitm_mgc_atk_negated(magr, mdef, TRUE)) { + urgent_pline("You feel feverish."); + exercise(A_CON, FALSE); + set_ulycn(monsndx(pa)); + retouch_equipment(2); + } + } else { + /* mhitm */ + mhitm_ad_phys(magr, mattk, mdef, mhm); + if (mhm->done) + return; + } +} + +void +mhitm_ad_heal( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + struct permonst *pd = mdef->data; + + if (magr == &gy.youmonst) { + /* uhitm */ + mhitm_ad_phys(magr, mattk, mdef, mhm); + if (mhm->done) + return; + } else if (mdef == &gy.youmonst) { + /* mhitu */ + /* a cancelled nurse is just an ordinary monster, + * nurses don't heal those that cause petrification */ + if (magr->mcan || (Upolyd && touch_petrifies(pd))) { + hitmsg(magr, mattk); + return; + } + /* weapon check should match the one in sounds.c for MS_NURSE */ + if (!(uwep && (uwep->oclass == WEAPON_CLASS || is_weptool(uwep))) + && !uarmu && !uarm && !uarmc + && !uarms && !uarmg && !uarmf && !uarmh) { + boolean goaway = FALSE; + + pline_mon(magr, "%s hits! (I hope you don't mind.)", + Monnam(magr)); + if (Upolyd) { + u.mh += rnd(7); + if (!rn2(7)) { + /* no upper limit necessary; effect is temporary */ + u.mhmax++; + if (!rn2(13)) + goaway = TRUE; + } + if (u.mh > u.mhmax) + u.mh = u.mhmax; + } else { + u.uhp += rnd(7); + if (!rn2(7)) { + /* hard upper limit via nurse care: 25 * ulevel */ + if (u.uhpmax < 5 * u.ulevel + d(2 * u.ulevel, 10)) { + u.uhpmax++; + if (u.uhpmax > u.uhppeak) + u.uhppeak = u.uhpmax; + } + if (!rn2(13)) + goaway = TRUE; + } + if (u.uhp > u.uhpmax) + u.uhp = u.uhpmax; + } + if (!rn2(3)) + exercise(A_STR, TRUE); + if (!rn2(3)) + exercise(A_CON, TRUE); + if (Sick) + make_sick(0L, (char *) 0, FALSE, SICK_ALL); + disp.botl = TRUE; + if (goaway) { + mongone(magr); + mhm->done = TRUE; + mhm->hitflags = M_ATTK_DEF_DIED; /* return 2??? */ + return; + } else if (!rn2(33)) { + if (!tele_restrict(magr)) + (void) rloc(magr, RLOC_MSG); + monflee(magr, d(3, 6), TRUE, FALSE); + mhm->done = TRUE; + mhm->hitflags = M_ATTK_HIT | M_ATTK_DEF_DIED; /* return 3??? */ + return; + } + mhm->damage = 0; + } else { + if (Role_if(PM_HEALER)) { + if (!Deaf && !(svm.moves % 5)) { + SetVoice(magr, 0, 80, 0); + verbalize("Doc, I can't help you unless you cooperate."); + } + mhm->damage = 0; + } else + hitmsg(magr, mattk); + } + } else { + /* mhitm */ + mhitm_ad_phys(magr, mattk, mdef, mhm); + if (mhm->done) + return; + } +} + +void +mhitm_ad_stun( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + struct permonst *pd = mdef->data; + + if (magr == &gy.youmonst) { + /* uhitm */ + if (!Blind) + pline("%s %s for a moment.", Monnam(mdef), + makeplural(stagger(pd, "stagger"))); + mdef->mstun = 1; + mhitm_ad_phys(magr, mattk, mdef, mhm); + if (mhm->done) + return; + } else if (mdef == &gy.youmonst) { + /* mhitu */ + hitmsg(magr, mattk); + if (!magr->mcan && !rn2(4)) { + make_stunned((HStun & TIMEOUT) + (long) mhm->damage, TRUE); + mhm->damage /= 2; + } + } else { + /* mhitm */ + if (magr->mcan) + return; + if (canseemon(mdef)) + pline_mon(mdef, "%s %s for a moment.", Monnam(mdef), + makeplural(stagger(pd, "stagger"))); + mdef->mstun = 1; + mhitm_ad_phys(magr, mattk, mdef, mhm); + if (mhm->done) + return; + } +} + +void +mhitm_ad_legs( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + if (magr == &gy.youmonst) { + /* uhitm */ +#if 0 + if (u.ucancelled) { + mhm->damage = 0; + return; + } +#endif + mhitm_ad_phys(magr, mattk, mdef, mhm); + if (mhm->done) + return; + } else if (mdef == &gy.youmonst) { + /* mhitu */ + long side = rn2(2) ? RIGHT_SIDE : LEFT_SIDE; + const char *sidestr = (side == RIGHT_SIDE) ? "right" : "left", + *Monst_name = Monnam(magr), *leg = body_part(LEG); + + /* This case is too obvious to ignore, but Nethack is not in + * general very good at considering height--most short monsters + * still _can_ attack you when you're flying or mounted. + */ + if ((u.usteed || Levitation || Flying) && !is_flyer(magr->data)) { + pline("%s tries to reach your %s %s!", Monst_name, sidestr, leg); + mhm->damage = 0; + } else if (magr->mcan) { + pline_mon(magr, "%s nuzzles against your %s %s!", Monnam(magr), + sidestr, leg); + mhm->damage = 0; + } else { + if (uarmf) { + if (rn2(2) && (uarmf->otyp == LOW_BOOTS + || uarmf->otyp == IRON_SHOES)) { + pline("%s pricks the exposed part of your %s %s!", + Monst_name, sidestr, leg); + } else if (!rn2(5)) { + pline("%s pricks through your %s boot!", Monst_name, + sidestr); + } else { + pline("%s scratches your %s boot!", Monst_name, + sidestr); + mhm->damage = 0; + return; + } + } else + pline("%s pricks your %s %s!", Monst_name, sidestr, leg); + + set_wounded_legs(side, rnd(60 - ACURR(A_DEX))); + exercise(A_STR, FALSE); + exercise(A_DEX, FALSE); + } + } else { + /* mhitm */ + if (magr->mcan) { + mhm->damage = 0; + return; + } + mhitm_ad_phys(magr, mattk, mdef, mhm); + if (mhm->done) + return; + } +} + +void +mhitm_ad_dgst( + struct monst *magr, + struct attack *mattk UNUSED, + struct monst *mdef, struct mhitm_data *mhm) +{ + struct permonst *pd = mdef->data; - if ((helmet = which_armor(mdef, W_ARMH)) != 0 && rn2(8)) { - pline("%s %s blocks your attack to %s head.", - s_suffix(Monnam(mdef)), helm_simple_name(helmet), - mhis(mdef)); - break; + if (magr == &gy.youmonst) { + /* uhitm */ + mhm->damage = 0; + } else if (mdef == &gy.youmonst) { + /* mhitu */ + mhm->damage = 0; + } else { + /* mhitm */ + int num; + struct obj *obj; + + /* eating a Rider or its corpse is fatal */ + if (is_rider(pd)) { + if (gv.vis && canseemon(magr)) + pline_mon(magr, "%s %s!", Monnam(magr), + (pd == &mons[PM_FAMINE]) + ? "belches feebly, shrivels up and dies" + : (pd == &mons[PM_PESTILENCE]) + ? "coughs spasmodically and collapses" + : "vomits violently and drops dead"); + mondied(magr); + if (!DEADMONSTER(magr)) { + mhm->hitflags = M_ATTK_MISS; /* lifesaved */ + mhm->done = TRUE; + return; + } else if (magr->mtame && !gv.vis) + You(brief_feeling, "queasy"); + mhm->hitflags = M_ATTK_AGR_DIED; + mhm->done = TRUE; + return; + } + if (flags.verbose && !Deaf) { + /* Soundeffect? */ + SetVoice(magr, 0, 80, 0); + verbalize("Burrrrp!"); + } + wake_nearto(magr->mx, magr->my, 2 * 2); /* Burrrrp! */ + mhm->damage = mdef->mhp; + /* Use up amulet of life saving */ + if ((obj = mlifesaver(mdef)) != 0) + m_useup(mdef, obj); + + /* Is a corpse for nutrition possible? It may kill magr */ + if (!corpse_chance(mdef, magr, TRUE) || DEADMONSTER(magr)) + return; + + /* Pets get nutrition from swallowing monster whole. + * No nutrition from G_NOCORPSE monster, eg, undead. + * DGST monsters don't die from undead corpses + */ + num = monsndx(pd); + if (magr->mtame && !magr->isminion + && !(svm.mvitals[num].mvflags & G_NOCORPSE)) { + struct obj *virtualcorpse = mksobj(CORPSE, FALSE, FALSE); + int nutrit; + + set_corpsenm(virtualcorpse, num); + nutrit = dog_nutrition(magr, virtualcorpse); + dealloc_obj(virtualcorpse); + + /* only 50% nutrition, 25% of normal eating time */ + if (magr->meating > 1) + magr->meating = (magr->meating + 3) / 4; + if (nutrit > 1) + nutrit /= 2; + EDOG(magr)->hungrytime += nutrit; } + } +} - (void) eat_brains(&youmonst, mdef, TRUE, &tmp); - break; +void +mhitm_ad_samu( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + if (magr == &gy.youmonst) { + /* uhitm */ + mhm->damage = 0; + } else if (mdef == &gy.youmonst) { + /* mhitu */ + hitmsg(magr, mattk); + /* when the Wizard or quest nemesis hits, there's a 1/20 chance + to steal a quest artifact (any, not just the one for the hero's + own role) or the Amulet or one of the invocation tools */ + if (!rn2(20)) + stealamulet(magr); + } else { + /* mhitm */ + mhm->damage = 0; } - case AD_STCK: - if (!negated && !sticks(pd)) - u.ustuck = mdef; /* it's now stuck to you */ - break; - case AD_WRAP: - if (!sticks(pd)) { - if (!u.ustuck && !rn2(10)) { - if (m_slips_free(mdef, mattk)) { - tmp = 0; - } else { - You("swing yourself around %s!", mon_nam(mdef)); - u.ustuck = mdef; - } - } else if (u.ustuck == mdef) { - /* Monsters don't wear amulets of magical breathing */ - if (is_pool(u.ux, u.uy) && !is_swimmer(pd) - && !amphibious(pd)) { - You("drown %s...", mon_nam(mdef)); - tmp = mdef->mhp; - } else if (mattk->aatyp == AT_HUGS) - pline("%s is being crushed.", Monnam(mdef)); - } else { - tmp = 0; - if (flags.verbose) - You("brush against %s %s.", s_suffix(mon_nam(mdef)), - mbodypart(mdef, LEG)); - } - } else - tmp = 0; - break; - case AD_PLYS: - if (!negated && mdef->mcanmove && !rn2(3) && tmp < mdef->mhp) { - if (!Blind) - pline("%s is frozen by you!", Monnam(mdef)); - paralyze_monst(mdef, rnd(10)); - } - break; - case AD_SLEE: - if (!negated && !mdef->msleeping && sleep_monst(mdef, rnd(10), -1)) { +} + +/* disease */ +void +mhitm_ad_dise( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + struct permonst *pa = magr->data, *pd = mdef->data; + + if (magr == &gy.youmonst) { + /* uhitm; hero can't polymorph into anything with this attack so + this won't happen; if it could, it would be the same as the + mhitm case except for messaging */ + goto mhitm_dise; + } else if (mdef == &gy.youmonst) { + /* mhitu */ + hitmsg(magr, mattk); + if (!diseasemu(pa)) + mhm->damage = 0; + } else { + mhitm_dise: + /* mhitm; protected monsters use the same criteria as for poly'd + hero gaining sick resistance combined with any hero wielding a + weapon or wearing dragon scales/mail that guards against disease */ + if (pd->mlet == S_FUNGUS || pd == &mons[PM_GHOUL] + || defended(mdef, AD_DISE)) + mhm->damage = 0; + /* else does ordinary damage */ + } +} + +/* seduce and also steal item */ +void +mhitm_ad_sedu( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + struct permonst *pa = magr->data; + + if (magr == &gy.youmonst) { + /* uhitm */ + steal_it(mdef, mattk); + mhm->damage = 0; + } else if (mdef == &gy.youmonst) { + /* mhitu */ + char buf[BUFSZ]; + + if (is_animal(magr->data)) { + hitmsg(magr, mattk); + if (magr->mcan) + return; + /* Continue below */ + } else if (dmgtype(gy.youmonst.data, AD_SEDU) + /* !SYSOPT_SEDUCE: when hero is attacking and AD_SSEX + is disabled, it would be changed to another damage + type, but when defending, it remains as-is */ + || dmgtype(gy.youmonst.data, AD_SSEX)) { + pline_mon(magr, "%s %s.", Monnam(magr), + Deaf ? "says something but you can't hear it" + : magr->minvent + ? "brags about the goods some dungeon explorer provided" + : "makes some remarks about how difficult theft is lately"); + if (!tele_restrict(magr)) + (void) rloc(magr, RLOC_MSG); + mhm->hitflags = M_ATTK_AGR_DONE; /* return 3??? */ + mhm->done = TRUE; + return; + } else if (magr->mcan) { if (!Blind) - pline("%s is put to sleep by you!", Monnam(mdef)); - slept_monst(mdef); + pline("%s tries to %s you, but you seem %s.", + Adjmonnam(magr, "plain"), + flags.female ? "charm" : "seduce", + flags.female ? "unaffected" : "uninterested"); + if (rn2(3)) { + if (!tele_restrict(magr)) + (void) rloc(magr, RLOC_MSG); + mhm->hitflags = M_ATTK_AGR_DONE; /* return 3??? */ + mhm->done = TRUE; + return; + } + return; } - break; - case AD_SLIM: - if (negated) - break; /* physical damage only */ - if (!rn2(4) && !slimeproof(pd)) { - if (!munslime(mdef, TRUE) && !DEADMONSTER(mdef)) { - /* this assumes newcham() won't fail; since hero has - a slime attack, green slimes haven't been geno'd */ - You("turn %s into slime.", mon_nam(mdef)); - if (newcham(mdef, &mons[PM_GREEN_SLIME], FALSE, FALSE)) - pd = mdef->data; + buf[0] = '\0'; + switch (steal(magr, buf)) { + case -1: + mhm->hitflags = M_ATTK_AGR_DIED; /* return 2??? */ + mhm->done = TRUE; + return; + case 0: + return; + default: + if (!is_animal(magr->data) && !tele_restrict(magr)) + (void) rloc(magr, RLOC_MSG); + if (is_animal(magr->data) && *buf) { + if (canseemon(magr)) + pline_mon(magr, "%s tries to %s away with %s.", + Monnam(magr), + locomotion(magr->data, "run"), buf); } - /* munslime attempt could have been fatal */ - if (DEADMONSTER(mdef)) - return 2; /* skip death message */ - tmp = 0; + monflee(magr, 0, FALSE, FALSE); + mhm->hitflags = M_ATTK_AGR_DONE; /* return 3??? */ + mhm->done = TRUE; + return; } - break; - case AD_ENCH: /* KMH -- remove enchantment (disenchanter) */ - /* there's no msomearmor() function, so just do damage */ - /* if (negated) break; */ - break; - case AD_SLOW: - if (!negated && mdef->mspeed != MSLOW) { - unsigned int oldspeed = mdef->mspeed; + } else { + /* mhitm */ + struct obj *obj; + + if (magr->mcan) + return; + /* find an object to steal, non-cursed if magr is tame */ + for (obj = mdef->minvent; obj; obj = obj->nobj) + if (!magr->mtame || !obj->cursed) + break; - mon_adjust_speed(mdef, -1, (struct obj *) 0); - if (mdef->mspeed != oldspeed && canseemon(mdef)) - pline("%s slows down.", Monnam(mdef)); + if (obj) { + char buf[BUFSZ]; + char onambuf[BUFSZ], mdefnambuf[BUFSZ]; + + /* make a special x_monnam() call that never omits + the saddle, and save it for later messages */ + Strcpy(mdefnambuf, + x_monnam(mdef, ARTICLE_THE, (char *) 0, 0, FALSE)); + + if (u.usteed == mdef && obj == which_armor(mdef, W_SADDLE)) + /* "You can no longer ride ." */ + dismount_steed(DISMOUNT_POLY); + extract_from_minvent(mdef, obj, TRUE, FALSE); + /* add_to_minv() might free 'obj' [if it merges] */ + if (gv.vis) + Strcpy(onambuf, doname(obj)); + (void) add_to_minv(magr, obj); + Strcpy(buf, Monnam(magr)); + if (gv.vis && canseemon(mdef)) { + pline("%s steals %s from %s!", buf, onambuf, mdefnambuf); + } + possibly_unwield(mdef, FALSE); + mdef->mstrategy &= ~STRAT_WAITFORU; + mselftouch(mdef, (const char *) 0, FALSE); + if (DEADMONSTER(mdef)) { + mhm->hitflags = (M_ATTK_DEF_DIED + | (grow_up(magr, mdef) ? 0 + : M_ATTK_AGR_DIED)); + mhm->done = TRUE; + return; + } + if (pa->mlet == S_NYMPH && !tele_restrict(magr)) { + boolean couldspot = canspotmon(magr); + + mhm->hitflags = M_ATTK_AGR_DONE; + (void) rloc(magr, RLOC_NOMSG); + /* TODO: use RLOC_MSG instead? */ + if (gv.vis && couldspot && !canspotmon(magr)) + pline("%s suddenly disappears!", buf); + } } - break; - case AD_CONF: - if (!mdef->mconf) { - if (canseemon(mdef)) - pline("%s looks confused.", Monnam(mdef)); - mdef->mconf = 1; + mhm->damage = 0; + } +} + +void +mhitm_ad_ssex(struct monst *magr, struct attack *mattk, struct monst *mdef, + struct mhitm_data *mhm) +{ + if (magr == &gy.youmonst) { + /* uhitm */ + mhitm_ad_sedu(magr, mattk, mdef, mhm); + if (mhm->done) + return; + } else if (mdef == &gy.youmonst) { + /* mhitu */ + if (SYSOPT_SEDUCE) { + if (could_seduce(magr, mdef, mattk) == 1 && !magr->mcan) + if (doseduce(magr)) { + mhm->hitflags = M_ATTK_AGR_DONE; + mhm->done = TRUE; + return; + } + return; } - break; + mhitm_ad_sedu(magr, mattk, mdef, mhm); + if (mhm->done) + return; + } else { + /* mhitm */ + mhitm_ad_sedu(magr, mattk, mdef, mhm); + if (mhm->done) + return; + } +} + +void +mhitm_adtyping( + struct monst *magr, struct attack *mattk, + struct monst *mdef, struct mhitm_data *mhm) +{ + switch (mattk->adtyp) { + case AD_STUN: mhitm_ad_stun(magr, mattk, mdef, mhm); break; + case AD_LEGS: mhitm_ad_legs(magr, mattk, mdef, mhm); break; + case AD_WERE: mhitm_ad_were(magr, mattk, mdef, mhm); break; + case AD_HEAL: mhitm_ad_heal(magr, mattk, mdef, mhm); break; + case AD_PHYS: mhitm_ad_phys(magr, mattk, mdef, mhm); break; + case AD_FIRE: mhitm_ad_fire(magr, mattk, mdef, mhm); break; + case AD_COLD: mhitm_ad_cold(magr, mattk, mdef, mhm); break; + case AD_ELEC: mhitm_ad_elec(magr, mattk, mdef, mhm); break; + case AD_ACID: mhitm_ad_acid(magr, mattk, mdef, mhm); break; + case AD_STON: mhitm_ad_ston(magr, mattk, mdef, mhm); break; + case AD_SSEX: mhitm_ad_ssex(magr, mattk, mdef, mhm); break; + case AD_SITM: + case AD_SEDU: mhitm_ad_sedu(magr, mattk, mdef, mhm); break; + case AD_SGLD: mhitm_ad_sgld(magr, mattk, mdef, mhm); break; + case AD_TLPT: mhitm_ad_tlpt(magr, mattk, mdef, mhm); break; + case AD_BLND: mhitm_ad_blnd(magr, mattk, mdef, mhm); break; + case AD_CURS: mhitm_ad_curs(magr, mattk, mdef, mhm); break; + case AD_DRLI: mhitm_ad_drli(magr, mattk, mdef, mhm); break; + case AD_RUST: mhitm_ad_rust(magr, mattk, mdef, mhm); break; + case AD_CORR: mhitm_ad_corr(magr, mattk, mdef, mhm); break; + case AD_DCAY: mhitm_ad_dcay(magr, mattk, mdef, mhm); break; + case AD_DREN: mhitm_ad_dren(magr, mattk, mdef, mhm); break; + case AD_DRST: + case AD_DRDX: + case AD_DRCO: mhitm_ad_drst(magr, mattk, mdef, mhm); break; + case AD_DRIN: mhitm_ad_drin(magr, mattk, mdef, mhm); break; + case AD_STCK: mhitm_ad_stck(magr, mattk, mdef, mhm); break; + case AD_WRAP: mhitm_ad_wrap(magr, mattk, mdef, mhm); break; + case AD_PLYS: mhitm_ad_plys(magr, mattk, mdef, mhm); break; + case AD_SLEE: mhitm_ad_slee(magr, mattk, mdef, mhm); break; + case AD_SLIM: mhitm_ad_slim(magr, mattk, mdef, mhm); break; + case AD_ENCH: mhitm_ad_ench(magr, mattk, mdef, mhm); break; + case AD_SLOW: mhitm_ad_slow(magr, mattk, mdef, mhm); break; + case AD_CONF: mhitm_ad_conf(magr, mattk, mdef, mhm); break; + case AD_POLY: mhitm_ad_poly(magr, mattk, mdef, mhm); break; + case AD_DISE: mhitm_ad_dise(magr, mattk, mdef, mhm); break; + case AD_SAMU: mhitm_ad_samu(magr, mattk, mdef, mhm); break; + case AD_DETH: mhitm_ad_deth(magr, mattk, mdef, mhm); break; + case AD_PEST: mhitm_ad_pest(magr, mattk, mdef, mhm); break; + case AD_FAMN: mhitm_ad_famn(magr, mattk, mdef, mhm); break; + case AD_DGST: mhitm_ad_dgst(magr, mattk, mdef, mhm); break; + case AD_HALU: mhitm_ad_halu(magr, mattk, mdef, mhm); break; default: - tmp = 0; - break; + mhm->damage = 0; + } +} + +int +damageum( + struct monst *mdef, /* target */ + struct attack *mattk, /* hero's attack */ + int specialdmg) /* blessed and/or silver bonus against various things */ +{ + struct mhitm_data mhm; + + mhm.damage = d((int) mattk->damn, (int) mattk->damd); + mhm.hitflags = M_ATTK_MISS; + mhm.permdmg = 0; + mhm.specialdmg = specialdmg; + mhm.done = FALSE; + + if (is_demon(gy.youmonst.data) && !rn2(13) && !uwep + && u.umonnum != PM_AMOROUS_DEMON && u.umonnum != PM_BALROG) { + demonpet(); + return M_ATTK_MISS; } + mhitm_adtyping(&gy.youmonst, mattk, mdef, &mhm); + + if (mhm.done) + return mhm.hitflags; + mdef->mstrategy &= ~STRAT_WAITFORU; /* in case player is very fast */ - mdef->mhp -= tmp; + mdef->mhp -= mhm.damage; if (DEADMONSTER(mdef)) { + /* troll killed by Trollsbane won't auto-revive; FIXME? same when + Trollsbane is wielded as primary and two-weaponing kills with + secondary, which matches monster vs monster behavior but is + different from the non-poly'd hero vs monster behavior */ + if (mattk->aatyp == AT_WEAP || mattk->aatyp == AT_CLAW) + gm.mkcorpstat_norevive = troll_baned(mdef, uwep) ? TRUE : FALSE; + /* (DEADMONSTER(mdef) and !mhm.damage => already killed) */ if (mdef->mtame && !cansee(mdef->mx, mdef->my)) { You_feel("embarrassed for a moment."); - if (tmp) - xkilled(mdef, XKILL_NOMSG); /* !tmp but hp<1: already killed */ + if (mhm.damage) + xkilled(mdef, XKILL_NOMSG); } else if (!flags.verbose) { You("destroy it!"); - if (tmp) + if (mhm.damage) xkilled(mdef, XKILL_NOMSG); - } else if (tmp) - killed(mdef); - return 2; + } else if (mhm.damage) { + killed(mdef); /* regular "you kill " message */ + } + gm.mkcorpstat_norevive = FALSE; + return M_ATTK_DEF_DIED; } - return 1; + return M_ATTK_HIT; } -STATIC_OVL int -explum(mdef, mattk) -register struct monst *mdef; -register struct attack *mattk; +/* Hero, as a monster which is capable of an exploding attack mattk, is + * exploding at a target monster mdef, or just exploding at nothing (e.g. with + * forcefight) if mdef is null. + */ +int +explum(struct monst *mdef, struct attack *mattk) { - boolean resistance; /* only for cold/fire/elec */ - register int tmp = d((int) mattk->damn, (int) mattk->damd); + int tmp = d((int) mattk->damn, (int) mattk->damd); - You("explode!"); switch (mattk->adtyp) { case AD_BLND: - if (!resists_blnd(mdef)) { + if (mdef && !resists_blnd(mdef)) { pline("%s is blinded by your flash of light!", Monnam(mdef)); mdef->mblinded = min((int) mdef->mblinded + tmp, 127); mdef->mcansee = 0; } break; case AD_HALU: - if (haseyes(mdef->data) && mdef->mcansee) { + if (mdef && haseyes(mdef->data) && mdef->mcansee) { pline("%s is affected by your flash of light!", Monnam(mdef)); mdef->mconf = 1; } break; case AD_COLD: - resistance = resists_cold(mdef); - goto common; case AD_FIRE: - resistance = resists_fire(mdef); - goto common; case AD_ELEC: - resistance = resists_elec(mdef); - common: - if (!resistance) { - pline("%s gets blasted!", Monnam(mdef)); - mdef->mhp -= tmp; - if (DEADMONSTER(mdef)) { - killed(mdef); - return 2; - } - } else { - shieldeff(mdef->mx, mdef->my); - if (is_golem(mdef->data)) - golemeffects(mdef, (int) mattk->adtyp, tmp); - else - pline_The("blast doesn't seem to affect %s.", mon_nam(mdef)); + /* See comment in mon_explodes() and in zap.c for an explanation + of this math. Here, the player is causing the explosion, so it + should be in the +20 to +29 range instead of negative. */ + explode(u.ux, u.uy, (mattk->adtyp - 1) + 20, tmp, MON_EXPLODE, + adtyp_to_expltype(mattk->adtyp)); + if (mdef && DEADMONSTER(mdef)) { + /* Other monsters may have died too, but return this if the actual + target died. */ + return M_ATTK_DEF_DIED; } break; default: break; } - return 1; + wake_nearto(u.ux, u.uy, 7 * 7); /* same radius as exploding monster */ + return M_ATTK_HIT; } -STATIC_OVL void -start_engulf(mdef) -struct monst *mdef; +staticfn void +start_engulf(struct monst *mdef) { + boolean u_digest = digests(gy.youmonst.data), + u_enfold = enfolds(gy.youmonst.data); + if (!Invisible) { map_location(u.ux, u.uy, TRUE); - tmp_at(DISP_ALWAYS, mon_to_glyph(&youmonst, rn2_on_display_rng)); + tmp_at(DISP_ALWAYS, mon_to_glyph(&gy.youmonst, rn2_on_display_rng)); tmp_at(mdef->mx, mdef->my); } - You("engulf %s!", mon_nam(mdef)); - delay_output(); - delay_output(); + You("%s %s%s!", + u_digest ? "swallow" : u_enfold ? "enclose" : "engulf", + mon_nam(mdef), u_digest ? " whole" : ""); + nh_delay_output(); + nh_delay_output(); } -STATIC_OVL void -end_engulf() +staticfn void +end_engulf(void) { if (!Invisible) { tmp_at(DISP_END, 0); @@ -2107,21 +4954,20 @@ end_engulf() } } -STATIC_OVL int -gulpum(mdef, mattk) -register struct monst *mdef; -register struct attack *mattk; +staticfn int +gulpum(struct monst *mdef, struct attack *mattk) { -#ifdef LINT /* static char msgbuf[BUFSZ]; */ - char msgbuf[BUFSZ]; -#else - static char msgbuf[BUFSZ]; /* for nomovemsg */ -#endif - register int tmp; - register int dam = d((int) mattk->damn, (int) mattk->damd); - boolean fatal_gulp; + static char msgbuf[BUFSZ]; /* for gn.nomovemsg */ + int tmp; + int dam = d((int) mattk->damn, (int) mattk->damd); + boolean fatal_gulp, + u_digest = digests(gy.youmonst.data), + u_enfold = enfolds(gy.youmonst.data); struct obj *otmp; struct permonst *pd = mdef->data; + const char *expel_verb = u_digest ? "regurgitate" + : u_enfold ? "release" + : "expel"; /* Not totally the same as for real monsters. Specifically, these * don't take multiple moves. (It's just too hard, for too little @@ -2131,23 +4977,34 @@ register struct attack *mattk; * after exactly 1 round of attack otherwise. -KAA */ - if (!engulf_target(&youmonst, mdef)) - return 0; + if (!engulf_target(&gy.youmonst, mdef)) + return M_ATTK_MISS; - if (u.uhunger < 1500 && !u.uswallow) { - for (otmp = mdef->minvent; otmp; otmp = otmp->nobj) - (void) snuff_lit(otmp); + if (!(u_digest && u.uhunger >= 1500) && !u.uswallow) { + if (!flaming(gy.youmonst.data)) { + for (otmp = mdef->minvent; otmp; otmp = otmp->nobj) + (void) snuff_lit(otmp); + } /* force vampire in bat, cloud, or wolf form to revert back to vampire form now instead of dealing with that when it dies */ if (is_vampshifter(mdef) - && newcham(mdef, &mons[mdef->cham], FALSE, FALSE)) { - You("engulf it, then expel it."); - if (canspotmon(mdef)) - pline("It turns into %s.", a_monnam(mdef)); - else + && newcham(mdef, &mons[mdef->cham], NO_NC_FLAGS)) { + You("%s it, then %s it.", + u_digest ? "swallow" : u_enfold ? "enclose" : "engulf", + expel_verb); + if (canspotmon(mdef)) { + /* Avoiding a_monnam here: if the target is named, it gives us + a sequence like "You bite Dracula. You swallow it, then + regurgitate it. It turns into Dracula." */ + pline("It turns into %s.", + x_monnam(mdef, ARTICLE_A, (char *) 0, + (SUPPRESS_NAME | SUPPRESS_IT + | SUPPRESS_INVISIBLE), FALSE)); + } else { map_invisible(mdef->mx, mdef->my); - return 1; + } + return M_ATTK_HIT; } /* engulfing a cockatrice or digesting a Rider or Medusa */ @@ -2156,17 +5013,21 @@ register struct attack *mattk; && (is_rider(pd) || (pd == &mons[PM_MEDUSA] && !Stone_resistance))); - if ((mattk->adtyp == AD_DGST && !Slow_digestion) || fatal_gulp) + if (mattk->adtyp == AD_DGST && (!Slow_digestion || fatal_gulp)) eating_conducts(pd); if (fatal_gulp && !is_rider(pd)) { /* petrification */ char kbuf[BUFSZ]; - const char *mname = pd->mname; + const char *mnam = pmname(pd, Mgender(mdef)); if (!type_is_pname(pd)) - mname = an(mname); - You("englut %s.", mon_nam(mdef)); - Sprintf(kbuf, "swallowing %s whole", mname); + mnam = an(mnam); + You("%s %s.", u_digest ? "englut" : "engulf", mon_nam(mdef)); + Sprintf(kbuf, "%s %s%s", + u_digest ? "swallowing" + : u_enfold ? "enclosing" + : "engulfing", + mnam, u_digest ? " whole" : ""); instapetrify(kbuf); } else { start_engulf(mdef); @@ -2176,11 +5037,11 @@ register struct attack *mattk; if (is_rider(pd)) { pline("Unfortunately, digesting any of it is fatal."); end_engulf(); - Sprintf(killer.name, "unwisely tried to eat %s", - pd->mname); - killer.format = NO_KILLER_PREFIX; + Sprintf(svk.killer.name, "unwisely tried to eat %s", + pmname(pd, Mgender(mdef))); + svk.killer.format = NO_KILLER_PREFIX; done(DIED); - return 0; /* lifesaved */ + return M_ATTK_MISS; /* lifesaved */ } if (Slow_digestion) { @@ -2199,18 +5060,20 @@ register struct attack *mattk; "you totally digest " will be coming soon (after several turns) but the level-gain message seems out of order if the kill message is left implicit */ + gm.mswallower = &gy.youmonst; xkilled(mdef, XKILL_GIVEMSG | XKILL_NOCORPSE); if (!DEADMONSTER(mdef)) { /* monster lifesaved */ You("hurriedly regurgitate the sizzling in your %s.", body_part(STOMACH)); } else { tmp = 1 + (pd->cwt >> 8); - if (corpse_chance(mdef, &youmonst, TRUE) - && !(mvitals[monsndx(pd)].mvflags & G_NOCORPSE)) { + if (corpse_chance(mdef, &gy.youmonst, TRUE) + && !(svm.mvitals[monsndx(pd)].mvflags & G_NOCORPSE)) { /* nutrition only if there can be a corpse */ u.uhunger += (pd->cnutrit + 1) / 2; - } else + } else { tmp = 0; + } Sprintf(msgbuf, "You totally digest %s.", mon_nam(mdef)); if (tmp != 0) { /* setting afternmv = end_engulf is tempting, @@ -2222,30 +5085,37 @@ register struct attack *mattk; if (Slow_digestion) tmp *= 2; nomul(-tmp); - multi_reason = "digesting something"; - nomovemsg = msgbuf; + gm.multi_reason = "digesting something"; + gn.nomovemsg = msgbuf; + /* possible intrinsic once totally digested */ + gc.corpsenm_digested = monsndx(pd); + ga.afternmv = Finish_digestion; } else pline1(msgbuf); if (pd == &mons[PM_GREEN_SLIME]) { Sprintf(msgbuf, "%s isn't sitting well with you.", - The(pd->mname)); + The(pmname(pd, Mgender(mdef)))); if (!Unchanging) { make_slimed(5L, (char *) 0); } } else exercise(A_CON, TRUE); } + gm.mswallower = (struct monst *) 0; end_engulf(); - return 2; + return M_ATTK_DEF_DIED; case AD_PHYS: - if (youmonst.data == &mons[PM_FOG_CLOUD]) { + if (gy.youmonst.data == &mons[PM_FOG_CLOUD]) { pline("%s is laden with your moisture.", Monnam(mdef)); - if (amphibious(pd) && !flaming(pd)) { + if ((breathless(pd) || amphibious(pd)) && !flaming(pd)) { dam = 0; pline("%s seems unharmed.", Monnam(mdef)); } - } else - pline("%s is pummeled with your debris!", Monnam(mdef)); + } else { + pline("%s is %s!", Monnam(mdef), + enfolds(gy.youmonst.data) ? "being squashed" + : "pummeled with your debris"); + } break; case AD_ACID: pline("%s is covered with your goo!", Monnam(mdef)); @@ -2255,7 +5125,7 @@ register struct attack *mattk; } break; case AD_BLND: - if (can_blnd(&youmonst, mdef, mattk->aatyp, + if (can_blnd(&gy.youmonst, mdef, mattk->aatyp, (struct obj *) 0)) { if (mdef->mcansee) pline("%s can't see in there!", Monnam(mdef)); @@ -2312,64 +5182,287 @@ register struct attack *mattk; if (DEADMONSTER(mdef)) { killed(mdef); if (DEADMONSTER(mdef)) /* not lifesaved */ - return 2; + return M_ATTK_DEF_DIED; } - You("%s %s!", is_animal(youmonst.data) ? "regurgitate" : "expel", - mon_nam(mdef)); - if (Slow_digestion || is_animal(youmonst.data)) { + You("%s %s!", expel_verb, mon_nam(mdef)); + if ((Slow_digestion || is_animal(gy.youmonst.data)) && u_digest) { pline("Obviously, you didn't like %s taste.", s_suffix(mon_nam(mdef))); } } } - return 0; + return M_ATTK_MISS; } void -missum(mdef, mattk, wouldhavehit) -register struct monst *mdef; -register struct attack *mattk; -boolean wouldhavehit; +missum( + struct monst *mdef, + struct attack *mattk, + boolean wouldhavehit) { if (wouldhavehit) /* monk is missing due to penalty for wearing suit */ Your("armor is rather cumbersome..."); - if (could_seduce(&youmonst, mdef, mattk)) + if (could_seduce(&gy.youmonst, mdef, mattk)) You("pretend to be friendly to %s.", mon_nam(mdef)); else if (canspotmon(mdef) && flags.verbose) You("miss %s.", mon_nam(mdef)); else You("miss it."); - if (!mdef->msleeping && mdef->mcanmove) + if (!helpless(mdef)) wakeup(mdef, TRUE); } +/* check whether equipment protects against knockback */ +boolean +m_is_steadfast(struct monst *mtmp) +{ + boolean is_u = (mtmp == &gy.youmonst); + struct obj *otmp = is_u ? uwep : MON_WEP(mtmp); + + /* must be on the ground (or in water) */ + if ((is_u ? (Flying || Levitation) + : (is_flyer(mtmp->data) || is_floater(mtmp->data))) + || Is_airlevel(&u.uz) /* air or cloud */ + || (Is_waterlevel(&u.uz) && !is_pool(u.ux, u.uy))) /* air bubble */ + return FALSE; + + if (is_art(otmp, ART_GIANTSLAYER)) + return TRUE; + + /* steadfast if carrying any loadstone (and not floating or flying); + 'is_u' test not needed here; m_carrying() is 'youmonst' aware */ + if (m_carrying(mtmp, LOADSTONE)) + return TRUE; + /* when mounted and steed is target of knockback, check the rider for + a loadstone too (Giantslayer's protection doesn't extend to steed) */ + if (u.usteed && mtmp == u.usteed && carrying(LOADSTONE)) + return TRUE; + + return FALSE; +} + +/* monster hits another monster hard enough to knock it back? */ +boolean +mhitm_knockback( + struct monst *magr, /* attacker; might be hero */ + struct monst *mdef, /* defender; might be hero (only if magr isn't) */ + struct attack *mattk, /* attack type and damage info */ + int *hitflags, /* modified if magr or mdef dies */ + boolean weapon_used) /* True: via weapon hit */ +{ + char magrbuf[BUFSZ], mdefbuf[BUFSZ]; + struct obj *otmp; + const char *knockedhow; + coordxy dx, dy, defx, defy; + int knockdistance = rn2(3) ? 1 : 2; /* 67%: 1 step, 33%: 2 steps */ + int chance = 6; /* 1/6 chance of attack knocking back a monster */ + boolean u_agr = (magr == &gy.youmonst); + boolean u_def = (mdef == &gy.youmonst); + boolean was_u = FALSE, dismount = FALSE; + struct obj *wep = weapon_used ? (u_agr ? uwep : MON_WEP(magr)) + : (struct obj *) 0; + + if (wep && is_art(wep, ART_OGRESMASHER)) + chance = 2; + + if (rn2(chance)) + return FALSE; + + /* only certain attacks qualify for knockback */ + if (!((mattk->adtyp == AD_PHYS) + && (mattk->aatyp == AT_CLAW + || mattk->aatyp == AT_KICK + || mattk->aatyp == AT_BUTT + || mattk->aatyp == AT_WEAP))) + return FALSE; + + /* don't knockback if attacker also wants to grab or engulf */ + if (attacktype(magr->data, AT_ENGL) + || attacktype(magr->data, AT_HUGS) + || sticks(magr->data)) + return FALSE; + + /* decide where the first step will place the target; not accurate + for being knocked out of saddle but doesn't need to be; used for + test_move() and for message before actual hurtle */ + defx = u_def ? u.ux : mdef->mx; + defy = u_def ? u.uy : mdef->my; + dx = sgn(defx - (u_agr ? u.ux : magr->mx)); + dy = sgn(defy - (u_agr ? u.uy : magr->my)); + + /* can't move most targets into or out of a doorway diagonally */ + if (u_def) { + if (!test_move(defx, defy, dx, dy, TEST_MOVE)) + return FALSE; + } else { + /* subset of test_move() */ + if (!isok(defx + dx, defy + dy)) + return FALSE; + if (IS_DOOR(levl[defx][defy].typ) + && (defx - magr->mx && defy - magr->my) + && !doorless_door(defx, defy)) + return FALSE; + } + + /* if hero is stuck to a cursed saddle, knock the steed back */ + if (u_def && u.usteed) { + if ((otmp = which_armor(u.usteed, W_SADDLE)) != 0 && otmp->cursed) { + mdef = u.usteed; + was_u = TRUE; + u_def = FALSE; + } else { + dismount = TRUE; /* saddle is not cursed; knock hero out of it */ + } + } + + /* monsters must be alive */ + if ((!u_agr && DEADMONSTER(magr)) + || (!u_def && DEADMONSTER(mdef))) + return FALSE; + + /* attacker must be much larger than defender */ + if (!(magr->data->msize > (mdef->data->msize + 1))) + return FALSE; + + /* no knockback with a flimsy or non-blunt weapon */ + if (wep && (is_flimsy(wep) || !is_blunt_weapon(wep))) + return FALSE; + + /* needs a solid physical hit */ + if (unsolid(magr->data)) + return FALSE; + + /* the attack must have hit */ + /* mon-vs-mon code path doesn't set up hitflags */ + if ((u_agr || u_def) && !(*hitflags & M_ATTK_HIT)) + return FALSE; + + /* steadfast defender cannot be pushed around */ + if (m_is_steadfast(mdef)) { + if (u_def || (u.usteed && mdef == u.usteed)) { + mdefbuf[0] = '\0'; + if (u.usteed) + Snprintf(mdefbuf, sizeof mdefbuf, "and %s ", + y_monnam(u.usteed)); + You("%sdon't budge.", mdefbuf); + } else if (canseemon(mdef)) { + pline("%s doesn't budge.", Monnam(mdef)); + } + return FALSE; + } + + /* subtly vary the message text if monster won't actually move */ + knockedhow = dismount ? "out of your saddle" + : will_hurtle(mdef, defx + dx, defy + dy) ? "backward" + : "back"; + + /* give the message */ + if (u_def || canseemon(mdef)) { + Strcpy(magrbuf, u_agr ? "You" : Monnam(magr)); + Strcpy(mdefbuf, (u_def || was_u) ? "you" : y_monnam(mdef)); + if (was_u) + Snprintf(eos(mdefbuf), sizeof mdefbuf - strlen(mdefbuf), + " and %s", y_monnam(u.usteed)); + /* + * uhitm: You knock the gnome back with a powerful blow! + * mhitu: The red dragon knocks you back with a forceful blow! + * mhitm: The fire giant knocks the gnome back with a forceful strike! + */ + pline("%s %s %s %s with a %s %s!", + magrbuf, vtense(magrbuf, "knock"), mdefbuf, knockedhow, + rn2(2) ? "forceful" : "powerful", rn2(2) ? "blow" : "strike"); + } else if (u_agr) { + /* hero knocks unseen foe back; noticed by touch */ + You_feel("%s be knocked %s!", some_mon_nam(mdef), knockedhow); + } + + if (u.ustuck && (u_def || u_agr)) + unstuck(u.ustuck); + + /* do the actual knockback effect */ + if (u_def) { + if (dismount) { + /* normally u.dx,u.dy indicates the direction hero is throwing, + zapping, &c but here it is used to pass preferred direction + for dismount to dismount_steed (for DISMOUNT_KNOCKED only) */ + u.dx = dx; + u.dy = dy; + dismount_steed(DISMOUNT_KNOCKED); + } else { + hurtle(dx, dy, knockdistance, FALSE); + *hitflags |= M_ATTK_HIT; + } + set_apparxy(magr); /* update magr's idea of where you are */ + if (!Stunned && !rn2(4)) + make_stunned((long) (knockdistance + 1), TRUE); /* 2 or 3 */ + } else { + mhurtle(mdef, dx, dy, knockdistance); + if (!u_agr) + *hitflags |= M_ATTK_HIT; + if (DEADMONSTER(mdef)) { + if (!was_u) + *hitflags |= M_ATTK_DEF_DIED; + } else if (!rn2(4)) { + mdef->mstun = 1; + /* if steed and hero were knocked back, update attacker's idea + of where hero is */ + if (mdef == u.usteed) + set_apparxy(magr); + } + } + if (!u_agr) { + if (DEADMONSTER(magr)) + *hitflags |= M_ATTK_AGR_DIED; + } + + return TRUE; +} + /* attack monster as a monster; returns True if mon survives */ -STATIC_OVL boolean -hmonas(mon) -register struct monst *mon; +staticfn boolean +hmonas(struct monst *mon) { struct attack *mattk, alt_attk; struct obj *weapon, **originalweapon; boolean altwep = FALSE, weapon_used = FALSE, odd_claw = TRUE; - int i, tmp, armorpenalty, sum[NATTK], nsum = 0, dhit = 0, attknum = 0; - int dieroll, multi_claw = 0; + int i, tmp, dieroll, armorpenalty, sum[NATTK], + dhit = 0, attknum = 0, multi_claw = 0, multi_weap = 0; + boolean monster_survived; + + /* not used here but umpteen mhitm_ad_xxxx() need this */ + gv.vis = (canseemon(mon) || m_next2u(mon)); /* with just one touch/claw/weapon attack, both rings matter; with more than one, alternate right and left when checking whether silver ring causes successful hit */ for (i = 0; i < NATTK; i++) { - sum[i] = 0; - mattk = getmattk(&youmonst, mon, i, sum, &alt_attk); + sum[i] = M_ATTK_MISS; + mattk = getmattk(&gy.youmonst, mon, i, sum, &alt_attk); + if (mattk->aatyp == AT_WEAP) + ++multi_weap; if (mattk->aatyp == AT_WEAP || mattk->aatyp == AT_CLAW || mattk->aatyp == AT_TUCH) ++multi_claw; } multi_claw = (multi_claw > 1); /* switch from count to yes/no */ + gt.twohits = 0; + + gs.skipdrin = FALSE; /* [see mattackm(mhitm.c)] */ for (i = 0; i < NATTK; i++) { - /* sum[i] = 0; -- now done above */ - mattk = getmattk(&youmonst, mon, i, sum, &alt_attk); + /* sum[i] = M_ATTK_MISS; -- now done above */ + + /* target might have been knocked back so no longer in range, or an + engulfing vampshifted fog cloud killed and reverted to vampire + that's placed at another spot (hero occupies mon's first spot) */ + if (i > 0 && (m_at(gb.bhitpos.x, gb.bhitpos.y) != mon + || DEADMONSTER(mon))) + continue; + + mattk = getmattk(&gy.youmonst, mon, i, sum, &alt_attk); + if (gs.skipdrin && mattk->aatyp == AT_TENT && mattk->adtyp == AD_DRIN) + continue; weapon = 0; switch (mattk->aatyp) { case AT_WEAP: @@ -2380,7 +5473,8 @@ register struct monst *mon; get to make another weapon attack (note: monsters who use weapons do not have this restriction, but they also never have the opportunity to use two weapons) */ - if (weapon_used && sum[i - 1] && uwep && bimanual(uwep)) + if (weapon_used && (sum[i - 1] > M_ATTK_MISS) + && uwep && bimanual(uwep)) continue; /* Certain monsters don't use weapons when encountered as enemies, * but players who polymorph into them have hands or claws and @@ -2398,7 +5492,7 @@ register struct monst *mon; be Null, and we want to track that for passive() */ originalweapon = (altwep && uswapwep) ? &uswapwep : &uwep; if (uswapwep /* set up 'altwep' flag for next iteration */ - /* only consider seconary when wielding one-handed primary */ + /* only consider secondary when wielding one-handed primary */ && uwep && (uwep->oclass == WEAPON_CLASS || is_weptool(uwep)) && !bimanual(uwep) /* only switch if not wearing shield and not at artifact; @@ -2423,20 +5517,24 @@ register struct monst *mon; tmp = find_roll_to_hit(mon, AT_WEAP, weapon, &attknum, &armorpenalty); + mon_maybe_unparalyze(mon); dieroll = rnd(20); dhit = (tmp > dieroll || u.uswallow); - /* caller must set bhitpos */ - if (!known_hitum(mon, weapon, &dhit, tmp, - armorpenalty, mattk, dieroll)) { - /* enemy dead, before any special abilities used */ - sum[i] = 2; - break; - } else - sum[i] = dhit; + if (multi_weap > 1) + ++gt.twohits; + /* caller must set gb.bhitpos */ + monster_survived = known_hitum(mon, weapon, &dhit, tmp, + armorpenalty, mattk, dieroll); /* originalweapon points to an equipment slot which might now be empty if the weapon was destroyed during the hit; passive(,weapon,...) won't call passive_obj() in that case */ weapon = *originalweapon; /* might receive passive erosion */ + if (!monster_survived) { + /* enemy dead, before any special abilities used */ + sum[i] = M_ATTK_DEF_DIED; + break; + } else + sum[i] = dhit ? M_ATTK_HIT : M_ATTK_MISS; /* might be a worm that gets cut in half; if so, early return */ if (m_at(u.ux + u.dx, u.uy + u.dy) != mon) { i = NATTK; /* skip additional attacks */ @@ -2448,14 +5546,20 @@ register struct monst *mon; sum[i] = damageum(mon, mattk, 0); break; case AT_CLAW: - if (uwep && !cantwield(youmonst.data) && !weapon_used) + if (uwep && !cantwield(gy.youmonst.data) && !weapon_used) goto use_weapon; + FALLTHROUGH; /*FALLTHRU*/ case AT_TUCH: - if (uwep && youmonst.data->mlet == S_LICH && !weapon_used) + if (uwep && gy.youmonst.data->mlet == S_LICH && !weapon_used) goto use_weapon; + FALLTHROUGH; /*FALLTHRU*/ case AT_KICK: + if (mattk->aatyp == AT_KICK && mtrapped_in_pit(&gy.youmonst)) + continue; + FALLTHROUGH; + /*FALLTHRU*/ case AT_BITE: case AT_STNG: case AT_BUTT: @@ -2463,6 +5567,7 @@ register struct monst *mon; /*weaponless:*/ tmp = find_roll_to_hit(mon, mattk->aatyp, (struct obj *) 0, &attknum, &armorpenalty); + mon_maybe_unparalyze(mon); dieroll = rnd(20); dhit = (tmp > dieroll || u.uswallow); if (dhit) { @@ -2471,7 +5576,8 @@ register struct monst *mon; const char *verb = 0; /* verb or body part */ if (!u.uswallow - && (compat = could_seduce(&youmonst, mon, mattk)) != 0) { + && (compat = could_seduce(&gy.youmonst, mon, mattk)) + != 0) { You("%s %s %s.", (mon->mcansee && haseyes(mon->data)) ? "smile at" : "talk to", @@ -2491,14 +5597,14 @@ register struct monst *mon; verb = (mattk->aatyp == AT_TUCH) ? "touch" : "claws"; /* decide if silver-hater will be hit by silver ring(s); for 'multi_claw' where attacks alternate right/left, - assume 'even' claw or touch attacks use right hand - or paw, 'odd' ones use left for ring interaction; - even vs odd is based on actual attacks rather - than on index into mon->dat->mattk[] so that {bite, - claw,claw} instead of {claw,claw,bite} doesn't - make poly'd hero mysteriously become left-handed */ + assume 'even' claw or touch attacks use dominant hand + or paw, 'odd' ones use non-dominant hand for ring + interaction; even vs odd is based on actual attacks + rather than on index into mon->dat->mattk[] so that + {bite,claw,claw} instead of {claw,claw,bite} doesn't + make poly'd hero mysteriously switch handedness */ odd_claw = !odd_claw; - specialdmg = special_dmgval(&youmonst, mon, + specialdmg = special_dmgval(&gy.youmonst, mon, W_ARMG | ((odd_claw || !multi_claw) ? W_RINGL : 0L) @@ -2513,7 +5619,7 @@ register struct monst *mon; break; case AT_KICK: verb = "kick"; - specialdmg = special_dmgval(&youmonst, mon, W_ARMF, + specialdmg = special_dmgval(&gy.youmonst, mon, W_ARMF, &silverhit); break; case AT_BUTT: @@ -2521,7 +5627,7 @@ register struct monst *mon; /* hypothetical; if any form with a head-butt attack could wear a helmet, it would hit shades when wearing a blessed (or silver) one */ - specialdmg = special_dmgval(&youmonst, mon, W_ARMH, + specialdmg = special_dmgval(&gy.youmonst, mon, W_ARMH, &silverhit); break; case AT_BITE: @@ -2541,6 +5647,11 @@ register struct monst *mon; Your("%s %s harmlessly through %s.", verb, vtense(verb, "pass"), mon_nam(mon)); } else { + /* either not a shade or no special silver/blessed damage, + other unsolid monsters are immune to AT_TUCH+AD_WRAP */ + if (failed_grab(&gy.youmonst, mon, mattk)) + break; /* miss; message already given */ + if (mattk->aatyp == AT_TENT) { Your("tentacles suck %s.", mon_nam(mon)); } else { @@ -2548,7 +5659,7 @@ register struct monst *mon; verb = "hit"; /* not "claws" */ You("%s %s.", verb, mon_nam(mon)); if (silverhit && flags.verbose) - silver_sears(&youmonst, mon, silverhit); + silver_sears(&gy.youmonst, mon, silverhit); } sum[i] = damageum(mon, mattk, specialdmg); } @@ -2563,7 +5674,7 @@ register struct monst *mon; boolean byhand = hug_throttles(&mons[u.umonnum]), /* rope golem */ unconcerned = (byhand && !can_be_strangled(mon)); - if (sticks(mon->data) || u.uswallow || notonhead + if (sticks(mon->data) || u.uswallow || gn.notonhead || (byhand && (uwep || !has_head(mon->data)))) { /* can't hold a holder due to subsequent ambiguity over who is holding whom; can't hug engulfer from inside; @@ -2583,7 +5694,7 @@ register struct monst *mon; wakeup(mon, TRUE); /* choking hug/throttling grab uses hands (gloves or rings); normal hug uses outermost of cloak/suit/shirt */ - specialdmg = special_dmgval(&youmonst, mon, + specialdmg = special_dmgval(&gy.youmonst, mon, byhand ? (W_ARMG | W_RINGL | W_RINGR) : (W_ARMC | W_ARM | W_ARMU), &silverhit); @@ -2612,7 +5723,7 @@ register struct monst *mon; if (specialdmg) { You("%s %s%s", verb, mon_nam(mon), exclam(specialdmg)); if (silverhit && flags.verbose) - silver_sears(&youmonst, mon, silverhit); + silver_sears(&gy.youmonst, mon, silverhit); sum[i] = damageum(mon, mattk, specialdmg); } else { Your("%s passes harmlessly through %s.", @@ -2620,6 +5731,9 @@ register struct monst *mon; } break; } + /* can't grab unsolid creatures (checked after shade handling) */ + if (failed_grab(&gy.youmonst, mon, mattk)) + break; /* hug attack against ordinary foe */ if (mon == u.ustuck) { pline("%s is being %s%s.", Monnam(mon), @@ -2627,18 +5741,19 @@ register struct monst *mon; /* extra feedback for non-breather being choked */ unconcerned ? " but doesn't seem concerned" : ""); if (silverhit && flags.verbose) - silver_sears(&youmonst, mon, silverhit); + silver_sears(&gy.youmonst, mon, silverhit); sum[i] = damageum(mon, mattk, specialdmg); - } else if (i >= 2 && sum[i - 1] && sum[i - 2]) { + } else if (i >= 2 && (sum[i - 1] > M_ATTK_MISS) + && (sum[i - 2] > M_ATTK_MISS)) { /* in case we're hugging a new target while already holding something else; yields feedback " is no longer in your clutches" */ if (u.ustuck && u.ustuck != mon) uunstick(); You("grab %s!", mon_nam(mon)); - u.ustuck = mon; + set_ustuck(mon); if (silverhit && flags.verbose) - silver_sears(&youmonst, mon, silverhit); + silver_sears(&gy.youmonst, mon, silverhit); sum[i] = damageum(mon, mattk, specialdmg); } break; /* AT_HUGS */ @@ -2647,20 +5762,27 @@ register struct monst *mon; case AT_EXPL: /* automatic hit if next to */ dhit = -1; wakeup(mon, TRUE); + You("explode!"); sum[i] = explum(mon, mattk); break; case AT_ENGL: tmp = find_roll_to_hit(mon, mattk->aatyp, (struct obj *) 0, &attknum, &armorpenalty); + mon_maybe_unparalyze(mon); if ((dhit = (tmp > rnd(20 + i)))) { wakeup(mon, TRUE); - if (mon->data == &mons[PM_SHADE]) + /* can't engulf unsolid creatures */ + if (mon->data == &mons[PM_SHADE]) { + /* no specialdmg check needed */ Your("attempt to surround %s is harmless.", mon_nam(mon)); - else { + } else if (failed_grab(&gy.youmonst, mon, mattk)) { + ; /* non-shade miss; message already given */ + } else { sum[i] = gulpum(mon, mattk); - if (sum[i] == 2 && (mon->data->mlet == S_ZOMBIE - || mon->data->mlet == S_MUMMY) + if (sum[i] == M_ATTK_DEF_DIED + && (mon->data->mlet == S_ZOMBIE + || mon->data->mlet == S_MUMMY) && rn2(5) && !Sick_resistance) { You_feel("%ssick.", (Sick) ? "very " : ""); mdamageu(mon, rnd(8)); @@ -2675,10 +5797,11 @@ register struct monst *mon; /* No check for uwep; if wielding nothing we want to * do the normal 1-2 points bare hand damage... */ - if ((youmonst.data->mlet == S_KOBOLD - || youmonst.data->mlet == S_ORC - || youmonst.data->mlet == S_GNOME) && !weapon_used) + if ((gy.youmonst.data->mlet == S_KOBOLD + || gy.youmonst.data->mlet == S_ORC + || gy.youmonst.data->mlet == S_GNOME) && !weapon_used) goto use_weapon; + FALLTHROUGH; /*FALLTHRU*/ case AT_NONE: @@ -2699,15 +5822,17 @@ register struct monst *mon; u.mh = -1; /* dead in the current form */ rehumanize(); } - if (sum[i] == 2) { + if (sum[i] == M_ATTK_DEF_DIED) { /* defender dead */ (void) passive(mon, weapon, 1, 0, mattk->aatyp, FALSE); - nsum = 0; /* return value below used to be 'nsum > 0' */ } else { - (void) passive(mon, weapon, sum[i], 1, mattk->aatyp, FALSE); - nsum |= sum[i]; + (void) passive(mon, weapon, (sum[i] != M_ATTK_MISS), 1, + mattk->aatyp, FALSE); } + if (mhitm_knockback(&gy.youmonst, mon, mattk, &sum[i], weapon_used)) + break; + /* don't use sum[i] beyond this point; 'i' will be out of bounds if we get here via 'goto' */ passivedone: @@ -2724,9 +5849,12 @@ register struct monst *mon; break; if (!Upolyd) break; /* No extra attacks if no longer a monster */ - if (multi < 0) + if (gm.multi < 0) break; /* If paralyzed while attacking, i.e. floating eye */ } + + gv.vis = FALSE; /* reset */ + gt.twohits = 0; /* return value isn't used, but make it match hitum()'s */ return !DEADMONSTER(mon); } @@ -2734,16 +5862,18 @@ register struct monst *mon; /* Special (passive) attacks on you by monsters done here. */ int -passive(mon, weapon, mhit, malive, aatyp, wep_was_destroyed) -struct monst *mon; -struct obj *weapon; /* uwep or uswapwep or uarmg or uarmf or Null */ -boolean mhit; -int malive; -uchar aatyp; -boolean wep_was_destroyed; +passive( + struct monst *mon, + struct obj *weapon, /* uwep or uswapwep or uarmg or uarmf or Null */ + boolean mhitb, + boolean maliveb, + uchar aatyp, + boolean wep_was_destroyed) { - register struct permonst *ptr = mon->data; - register int i, tmp; + struct permonst *ptr = mon->data; + int i, tmp; + int mhit = mhitb ? M_ATTK_HIT : M_ATTK_MISS; + int malive = maliveb ? M_ATTK_HIT : M_ATTK_MISS; for (i = 0;; i++) { if (i >= NATTK) @@ -2763,7 +5893,7 @@ boolean wep_was_destroyed; */ switch (ptr->mattk[i].adtyp) { case AD_FIRE: - if (mhit && !mon->mcan && weapon) { + if (mhitb && !mon->mcan && weapon) { if (aatyp == AT_KICK) { if (uarmf && !rn2(6)) (void) erode_obj(uarmf, xname(uarmf), ERODE_BURN, @@ -2774,19 +5904,23 @@ boolean wep_was_destroyed; } break; case AD_ACID: - if (mhit && rn2(2)) { + if (mhitb && rn2(2)) { if (Blind || !flags.verbose) You("are splashed!"); else You("are splashed by %s %s!", s_suffix(mon_nam(mon)), hliquid("acid")); - if (!Acid_resistance) + if (!Acid_resistance) { mdamageu(mon, tmp); + monstunseesu(M_SEEN_ACID); + } else { + monstseesu(M_SEEN_ACID); + } if (!rn2(30)) - erode_armor(&youmonst, ERODE_CORRODE); + erode_armor(&gy.youmonst, ERODE_CORRODE); } - if (mhit && weapon) { + if (mhitb && weapon) { if (aatyp == AT_KICK) { if (uarmf && !rn2(6)) (void) erode_obj(uarmf, xname(uarmf), ERODE_CORRODE, @@ -2798,7 +5932,7 @@ boolean wep_was_destroyed; exercise(A_STR, FALSE); break; case AD_STON: - if (mhit) { /* successful attack */ + if (mhitb) { /* successful attack */ long protector = attk_protection((int) aatyp); /* hero using monsters' AT_MAGC attack is hitting hand to @@ -2813,16 +5947,16 @@ boolean wep_was_destroyed; || (protector == W_ARMH && !uarmh) || (protector == (W_ARMC | W_ARMG) && (!uarmc || !uarmg))) { if (!Stone_resistance - && !(poly_when_stoned(youmonst.data) + && !(poly_when_stoned(gy.youmonst.data) && polymon(PM_STONE_GOLEM))) { done_in_by(mon, STONING); /* "You turn to stone..." */ - return 2; + return M_ATTK_DEF_DIED; } } } break; case AD_RUST: - if (mhit && !mon->mcan && weapon) { + if (mhitb && !mon->mcan && weapon) { if (aatyp == AT_KICK) { if (uarmf) (void) erode_obj(uarmf, xname(uarmf), ERODE_RUST, @@ -2833,7 +5967,7 @@ boolean wep_was_destroyed; } break; case AD_CORR: - if (mhit && !mon->mcan && weapon) { + if (mhitb && !mon->mcan && weapon) { if (aatyp == AT_KICK) { if (uarmf) (void) erode_obj(uarmf, xname(uarmf), ERODE_CORRODE, @@ -2847,20 +5981,31 @@ boolean wep_was_destroyed; /* wrath of gods for attacking Oracle */ if (Antimagic) { shieldeff(u.ux, u.uy); + monstseesu(M_SEEN_MAGR); pline("A hail of magic missiles narrowly misses you!"); } else { You("are hit by magic missiles appearing from thin air!"); mdamageu(mon, tmp); + monstunseesu(M_SEEN_MAGR); } break; case AD_ENCH: /* KMH -- remove enchantment (disenchanter) */ - if (mhit) { + if (mhitb) { if (aatyp == AT_KICK) { if (!weapon) break; } else if (aatyp == AT_BITE || aatyp == AT_BUTT || (aatyp >= AT_STNG && aatyp < AT_WEAP)) { break; /* no object involved */ + } else { + /* + * TODO: #H2668 - if hitting with a ring that has a + * positive enchantment, it ought to be subject to + * having that enchantment reduced. But we don't have + * sufficient information here to know which hand/ring + * has delivered a weaponless blow. + */ + ; } passive_obj(mon, weapon, &(ptr->mattk[i])); } @@ -2895,8 +6040,10 @@ boolean wep_was_destroyed; } else { You("are frozen by %s gaze!", s_suffix(mon_nam(mon))); nomul((ACURR(A_WIS) > 12 || rn2(4)) ? -tmp : -127); - multi_reason = "frozen by a monster's gaze"; - nomovemsg = 0; + /* set gm.multi_reason; + 3.6.x used "frozen by a monster's gaze" */ + dynamic_multi_reason(mon, "frozen", TRUE); + gn.nomovemsg = 0; } } else { pline("%s cannot defend itself.", @@ -2908,9 +6055,11 @@ boolean wep_was_destroyed; You("momentarily stiffen."); } else { /* gelatinous cube */ You("are frozen by %s!", mon_nam(mon)); - nomovemsg = You_can_move_again; + gn.nomovemsg = You_can_move_again; nomul(-tmp); - multi_reason = "frozen by a monster"; + /* set gm.multi_reason; + 3.6.x used "frozen by a monster"; be more specific */ + dynamic_multi_reason(mon, "frozen", FALSE); exercise(A_DEX, FALSE); } break; @@ -2919,18 +6068,18 @@ boolean wep_was_destroyed; if (Cold_resistance) { shieldeff(u.ux, u.uy); You_feel("a mild chill."); + monstseesu(M_SEEN_COLD); ugolemeffects(AD_COLD, tmp); break; } + monstunseesu(M_SEEN_COLD); You("are suddenly very cold!"); mdamageu(mon, tmp); /* monster gets stronger with your heat! */ - mon->mhp += tmp / 2; - if (mon->mhpmax < mon->mhp) - mon->mhpmax = mon->mhp; + healmon(mon, (tmp + rn2(2)) / 2, (tmp + 1) / 2); /* at a certain point, the monster will reproduce! */ - if (mon->mhpmax > ((int) (mon->m_lev + 1) * 8)) - (void) split_mon(mon, &youmonst); + if (mon->mhpmax > (((int) mon->m_lev) + 1) * 8) + (void) split_mon(mon, &gy.youmonst); } break; case AD_STUN: /* specifically yellow mold */ @@ -2942,9 +6091,11 @@ boolean wep_was_destroyed; if (Fire_resistance) { shieldeff(u.ux, u.uy); You_feel("mildly warm."); + monstseesu(M_SEEN_FIRE); ugolemeffects(AD_FIRE, tmp); break; } + monstunseesu(M_SEEN_FIRE); You("are suddenly very hot!"); mdamageu(mon, tmp); /* fire damage */ } @@ -2953,9 +6104,11 @@ boolean wep_was_destroyed; if (Shock_resistance) { shieldeff(u.ux, u.uy); You_feel("a mild tingle."); + monstseesu(M_SEEN_ELEC); ugolemeffects(AD_ELEC, tmp); break; } + monstunseesu(M_SEEN_ELEC); You("are jolted with electricity!"); mdamageu(mon, tmp); break; @@ -2971,10 +6124,10 @@ boolean wep_was_destroyed; * Assumes the attack was successful. */ void -passive_obj(mon, obj, mattk) -struct monst *mon; -struct obj *obj; /* null means pick uwep, uswapwep or uarmg */ -struct attack *mattk; /* null means we find one internally */ +passive_obj( + struct monst *mon, + struct obj *obj, /* null means pick uwep, uswapwep or uarmg */ + struct attack *mattk) /* null means we find one internally */ { struct permonst *ptr = mon->data; int i; @@ -3031,6 +6184,8 @@ struct attack *mattk; /* null means we find one internally */ } break; } + FALLTHROUGH; + /* FALLTHRU */ default: break; } @@ -3039,39 +6194,99 @@ struct attack *mattk; /* null means we find one internally */ update_inventory(); } -/* Note: caller must ascertain mtmp is mimicking... */ +DISABLE_WARNING_FORMAT_NONLITERAL + +/* used by stumble_onto_mimic() and bhitm() cases WAN_LOCKING, WAN_OPENING */ void -stumble_onto_mimic(mtmp) -struct monst *mtmp; +that_is_a_mimic( + struct monst *mtmp, /* a hidden mimic (nonnull) */ + unsigned mimic_flags) /* 0, MIM_REVEAL, MIM_OMIT_WAIT, REVEAL+OMIT */ { - const char *fmt = "Wait! That's %s!", *generic = "a monster", *what = 0; - - if (!u.ustuck && !mtmp->mflee && dmgtype(mtmp->data, AD_STCK)) - u.ustuck = mtmp; + static char generic[] = "a monster"; + char fmtbuf[BUFSZ]; + const char *what = NULL; + boolean reveal_it = (mimic_flags & MIM_REVEAL) != 0, + omit_wait = (mimic_flags & MIM_OMIT_WAIT) != 0; + Strcpy(fmtbuf, "Wait! That's %s!"); if (Blind) { if (!Blind_telepat) what = generic; /* with default fmt */ else if (M_AP_TYPE(mtmp) == M_AP_MONSTER) what = a_monnam(mtmp); /* differs from what was sensed */ } else { - int glyph = levl[u.ux + u.dx][u.uy + u.dy].glyph; - - if (glyph_is_cmap(glyph) && (glyph_to_cmap(glyph) == S_hcdoor - || glyph_to_cmap(glyph) == S_vcdoor)) - fmt = "The door actually was %s!"; - else if (glyph_is_object(glyph) && glyph_to_obj(glyph) == GOLD_PIECE) - fmt = "That gold was %s!"; + coordxy x = mtmp->mx, y = mtmp->my; + int glyph = glyph_at(x, y); + + if (glyph_is_cmap(glyph)) { + int sym = glyph_to_cmap(glyph); + + if (M_AP_TYPE(mtmp) == M_AP_FURNITURE + || (M_AP_TYPE(mtmp) == M_AP_OBJECT && sym == S_trapped_chest)) + Snprintf(fmtbuf, sizeof fmtbuf, "That %s actually is %%s!", + defsyms[sym].explanation); + } else if (glyph_is_object(glyph)) { + boolean fakeobj; + const char *otmp_name; + struct obj *otmp = NULL; + + fakeobj = object_from_map(glyph, x, y, &otmp); + otmp_name = (otmp && otmp->otyp != STRANGE_OBJECT) + ? simpleonames(otmp) : "strange object"; + Snprintf(fmtbuf, sizeof fmtbuf, "%s %s %s %%s!", + (otmp && is_plural(otmp)) ? "Those" : "That", + otmp_name, otmp ? otense(otmp, "are") : "is"); + if (fakeobj && otmp) { + otmp->where = OBJ_FREE; /* object_from_map set to OBJ_FLOOR */ + dealloc_obj(otmp); + } + } else if (glyph_is_monster(glyph)) { + const char *mtmp_name; + int mndx = glyph_to_mon(glyph); + + assert(mndx >= LOW_PM && mndx <= HIGH_PM); + mtmp_name = pmname(&mons[mndx], Mgender(mtmp)); + Snprintf(fmtbuf, sizeof fmtbuf, + "Wait! That %s is really %%s!", mtmp_name); + } /* cloned Wiz starts out mimicking some other monster and might make himself invisible before being revealed */ if (mtmp->minvis && !See_invisible) what = generic; + else if (M_AP_TYPE(mtmp) == M_AP_MONSTER) + what = x_monnam(mtmp, ARTICLE_A, (char *) NULL, EXACT_NAME, TRUE); + else if (mtmp->data->mlet == S_MIMIC + && (M_AP_TYPE(mtmp) == M_AP_OBJECT + || M_AP_TYPE(mtmp) == M_AP_FURNITURE) + && (mtmp->msleeping || mtmp->mfrozen)) + /* BUG: this will misclassify a paralyzed mimic as sleeping */ + what = x_monnam(mtmp, ARTICLE_A, "sleeping", 0, FALSE); else what = a_monnam(mtmp); } - if (what) - pline(fmt, what); + + if (what) { + int i = (omit_wait && !strncmp(fmtbuf, "Wait! ", 7)) ? 7 : 0; + + pline(&fmtbuf[i], what); + } + if (reveal_it) + seemimic(mtmp); +} + +RESTORE_WARNING_FORMAT_NONLITERAL + +/* Note: caller must ascertain mtmp is mimicking... */ +void +stumble_onto_mimic(struct monst *mtmp) +{ + that_is_a_mimic(mtmp, MIM_REVEAL); + + if (!u.ustuck && !mtmp->mflee && dmgtype(mtmp->data, AD_STCK) + /* must be adjacent; attack via polearm could be from farther away */ + && m_next2u(mtmp)) + set_ustuck(mtmp); wakeup(mtmp, FALSE); /* clears mimicking */ /* if hero is blind, wakeup() won't display the monster even though @@ -3081,21 +6296,39 @@ struct monst *mtmp; map_invisible(mtmp->mx, mtmp->my); } -STATIC_OVL void -nohandglow(mon) -struct monst *mon; +boolean +disguised_as_non_mon(struct monst *mtmp) +{ + return (!sensemon(mtmp) + && M_AP_TYPE(mtmp) + && M_AP_TYPE(mtmp) != M_AP_MONSTER); +} + +boolean +disguised_as_mon(struct monst *mtmp) +{ + return (M_AP_TYPE(mtmp) + && M_AP_TYPE(mtmp) == M_AP_MONSTER); +} + +staticfn void +nohandglow(struct monst *mon) { - char *hands = makeplural(body_part(HAND)); + char *hands; + boolean altfeedback; if (!u.umconf || mon->mconf) return; + + hands = makeplural(body_part(HAND)); + altfeedback = (Blind || Invisible); /* Invisible == Invis && !See_invis */ if (u.umconf == 1) { - if (Blind) + if (altfeedback) Your("%s stop tingling.", hands); else Your("%s stop glowing %s.", hands, hcolor(NH_RED)); } else { - if (Blind) + if (altfeedback) pline_The("tingling in your %s lessens.", hands); else Your("%s no longer glow so brightly %s.", hands, hcolor(NH_RED)); @@ -3103,14 +6336,45 @@ struct monst *mon; u.umconf--; } +/* returns 1 if light flash has noticeable effect on 'mtmp', 0 otherwise */ int -flash_hits_mon(mtmp, otmp) -struct monst *mtmp; -struct obj *otmp; /* source of flash */ +flash_hits_mon( + struct monst *mtmp, + struct obj *otmp) /* source of flash */ { - int tmp, amt, res = 0, useeit = canseemon(mtmp); + struct rm *lev; + coordxy mx = mtmp->mx, my = mtmp->my; + int tmp, amt, useeit, res = 0; + + if (gn.notonhead) + return 0; + lev = &levl[mx][my]; + useeit = canseemon(mtmp); + + if (M_AP_TYPE(mtmp) != M_AP_NOTHING) { + char whatbuf[BUFSZ]; + int oldglyph = glyph_at(mx, my); + + /* 'altmon' probably doesn't matter here because 'whatbuf' will + only be shown if the glyph changes and wakeup() doesn't call + seemimic() for M_AP_MONSTER */ + mhidden_description(mtmp, MHID_ALTMON, whatbuf); + + wakeup(mtmp, FALSE); /* -> seemimic() -> newsym(); also calls + * finish_meating() to end quickmimic */ + + /* if glyph has changed then hero saw something happen */ + if (glyph_at(mx, my) != oldglyph) { + pline("That %s is really %s%c", whatbuf, + /* y_monnam()+a_monnam() */ + x_monnam(mtmp, mtmp->mtame ? ARTICLE_YOUR : ARTICLE_A, + (char *) 0, 0, FALSE), + mtmp->mtame ? '.' : '!'); + res = 1; + } + } - if (mtmp->msleeping) { + if (mtmp->msleeping && haseyes(mtmp->data)) { mtmp->msleeping = 0; if (useeit) { pline_The("flash awakens %s.", mon_nam(mtmp)); @@ -3118,41 +6382,60 @@ struct obj *otmp; /* source of flash */ } } else if (mtmp->data->mlet != S_LIGHT) { if (!resists_blnd(mtmp)) { - tmp = dist2(otmp->ox, otmp->oy, mtmp->mx, mtmp->my); + tmp = dist2(otmp->ox, otmp->oy, mx, my); if (useeit) { pline("%s is blinded by the flash!", Monnam(mtmp)); res = 1; } if (mtmp->data == &mons[PM_GREMLIN]) { /* Rule #1: Keep them out of the light. */ - amt = otmp->otyp == WAN_LIGHT ? d(1 + otmp->spe, 4) - : rn2(min(mtmp->mhp, 4)); + amt = (otmp->otyp == WAN_LIGHT) ? d(1 + otmp->spe, 4) + : rnd(min(mtmp->mhp, 4)); light_hits_gremlin(mtmp, amt); } if (!DEADMONSTER(mtmp)) { - if (!context.mon_moving) + if (!svc.context.mon_moving) setmangry(mtmp, TRUE); if (tmp < 9 && !mtmp->isshk && rn2(4)) monflee(mtmp, rn2(4) ? rnd(100) : 0, FALSE, TRUE); mtmp->mcansee = 0; mtmp->mblinded = (tmp < 3) ? 0 : rnd(1 + 50 / tmp); } + } else if (useeit) { + if (resists_blnd_by_arti(mtmp)) + shieldeff(mx, my); + if (flags.verbose) { + if (lev->lit) + pline("The flash of light shines on %s.", mon_nam(mtmp)); + else + pline("%s is illuminated.", Monnam(mtmp)); + res = 2; /* 'message has been given' temporary value */ + } } } + if (res) { + if (!lev->lit) + display_nhwindow(WIN_MESSAGE, TRUE); + res &= 1; /* change temporary 2 back to 0 */ + } return res; } void -light_hits_gremlin(mon, dmg) -struct monst *mon; -int dmg; +light_hits_gremlin(struct monst *mon, int dmg) { - pline("%s %s!", Monnam(mon), - (dmg > mon->mhp / 2) ? "wails in agony" : "cries out in pain"); + if (!Deaf && mdistu(mon) <= 90) { + /* cry of pain can be heard somewhat farther than the waking radius */ + pline_mon(mon, "%s %s!", Monnam(mon), + (dmg > mon->mhp / 2) ? "wails in agony" + : "cries out in pain"); + } else if (canseemon(mon)) { + pline_mon(mon, "%s recoils from the light!", Monnam(mon)); + } mon->mhp -= dmg; wake_nearto(mon->mx, mon->my, 30); if (DEADMONSTER(mon)) { - if (context.mon_moving) + if (svc.context.mon_moving) monkilled(mon, (char *) 0, AD_BLND); else killed(mon); diff --git a/src/utf8map.c b/src/utf8map.c new file mode 100644 index 000000000..f85226ad1 --- /dev/null +++ b/src/utf8map.c @@ -0,0 +1,222 @@ +/* NetHack 5.0 utf8map.c */ +/* Copyright (c) Michael Allison, 2021. */ +/* NetHack may be freely redistributed. See license for details. */ + +#include "hack.h" + +#ifdef ENHANCED_SYMBOLS + +extern const struct symparse loadsyms[]; +extern struct enum_dump monsdump[]; +extern struct enum_dump objdump[]; +extern glyph_map glyphmap[MAX_GLYPH]; +extern const char *const known_handling[]; /* symbols.c */ + +/* hexdd[] is defined in decl.c */ + +int +unicode_val(const char *cp) +{ + const char *dp; + int cval = 0, dcount; + + if (cp && *cp) { + cval = dcount = 0; + if ((*cp == 'U' || *cp == 'u') + && cp[1] == '+' && cp[2] && (dp = strchr(hexdd, cp[2])) != 0) { + cp += 2; /* move past the 'U' and '+' */ + do { + cval = (cval * 16) + ((int) (dp - hexdd) / 2); + } while (*++cp && (dp = strchr(hexdd, *cp)) != 0 && ++dcount < 7); + } + } + return cval; +} + +int +set_map_u(glyph_map *gmap, uint32 utf32ch, const uint8 *utf8str) +{ + glyph_map *tmpgm = gmap; + + if (!tmpgm || !utf32ch) + return 0; + + if (gmap->u == 0) { + gmap->u = + (struct unicode_representation *) alloc(sizeof *gmap->u); + gmap->u->utf8str = 0; + } + if (gmap->u->utf8str != 0) { + free(gmap->u->utf8str); + gmap->u->utf8str = 0; + } + gmap->u->utf8str = (uint8 *) dupstr((const char *) utf8str); + gmap->u->utf32ch = utf32ch; + return 1; +} + +void +free_all_glyphmap_u(void) +{ + int glyph; + int x, y; + + for (glyph = 0; glyph < MAX_GLYPH; ++glyph) { + if (glyphmap[glyph].u) { + if (glyphmap[glyph].u->utf8str) { + free(glyphmap[glyph].u->utf8str); + glyphmap[glyph].u->utf8str = 0; + } + free(glyphmap[glyph].u); + glyphmap[glyph].u = 0; + } + } + /* Prevent use after free from gg.gbuf */ + for (y = 0; y < ROWNO; ++y) { + for (x = 0; x < COLNO; ++x) { + gg.gbuf[y][x].glyphinfo.gm.u = NULL; + } + } +} + +/* helper routine if a window port wants to embed any UTF-8 sequences + for the glyph representation in the string in place of the \GNNNNNNNN + reference */ +char * +mixed_to_utf8(char *buf, size_t bufsz, const char *str, int *retflags) +{ + char *put = buf; + glyph_info glyphinfo = nul_glyphinfo; + + if (!str) + return strcpy(buf, ""); + + while (*str && put < (buf + bufsz) - 1) { + if (*str == '\\') { + int dcount, so, ggv; + const char *save_str; + + save_str = str++; + switch (*str) { + case 'G': /* glyph value \GXXXXNNNN*/ + if ((dcount = decode_glyph(str + 1, &ggv))) { + str += (dcount + 1); + map_glyphinfo(0, 0, ggv, 0, &glyphinfo); + if (glyphinfo.gm.u && glyphinfo.gm.u->utf8str) { + uint8 *ucp = glyphinfo.gm.u->utf8str; + + while (*ucp && put < (buf + bufsz) - 1) + *put++ = *ucp++; + if (retflags) + *retflags = 1; + } else { + so = glyphinfo.gm.sym.symidx; + *put++ = gs.showsyms[so]; + if (retflags) + *retflags = 0; + } + /* 'str' is ready for the next loop iteration and + '*str' should not be copied at the end of this + iteration */ + continue; + } else { + /* possible forgery - leave it the way it is */ + str = save_str; + } + break; + case '\\': + break; + case '\0': + /* String ended with '\\'. This can happen when someone + names an object with a name ending with '\\', drops the + named object on the floor nearby and does a look at all + nearby objects. */ + /* brh - should we perhaps not allow things to have names + that contain '\\' */ + str = save_str; + break; + } + } + if (put < (buf + bufsz) - 1) + *put++ = *str++; + } + *put = '\0'; + return buf; +} + +int +add_custom_urep_entry( + const char *customization_name, + int glyphidx, + uint32 utf32ch, + const uint8 *utf8str, + enum graphics_sets which_set) +{ + struct symset_customization *gdc + = &gs.sym_customizations[which_set][custom_ureps]; + struct customization_detail *details, *newdetails = 0; + + + if (!gdc->details) { + gdc->customization_name = dupstr(customization_name); + gdc->custtype = custom_ureps; + gdc->details = 0; + gdc->details_end = 0; + } + details = find_matching_customization(customization_name, + custom_ureps, which_set); /* FIXME */ + if (details) { + while (details) { + if (details->content.urep.glyphidx == glyphidx) { + if (details->content.urep.u.utf8str) + free(details->content.urep.u.utf8str); + if (utf32ch) { + details->content.urep.u.utf8str = + (uint8 *) dupstr((const char *) utf8str); + details->content.urep.u.utf32ch = utf32ch; + } else { + details->content.urep.u.utf8str = (uint8 *) 0; + details->content.urep.u.utf32ch = 0; + } + return 1; + } + details = details->next; + } + } + /* create new details entry */ + newdetails = (struct customization_detail *) alloc( + sizeof (struct customization_detail)); + newdetails->content.urep.glyphidx = glyphidx; + if (utf8str && *utf8str) { + newdetails->content.urep.u.utf8str = + (uint8 *) dupstr((const char *) utf8str); + } else { + newdetails->content.urep.u.utf8str = + (uint8 *) 0; + } + newdetails->content.urep.u.utf32ch = utf32ch; + newdetails->next = (struct customization_detail *) 0; + if (gdc->details == NULL) { + gdc->details = newdetails; + } else { + gdc->details_end->next = newdetails; + } + gdc->details_end = newdetails; + gdc->count++; + return 1; +} +#endif /* ENHANCED_SYMBOLS */ + +void +reset_customsymbols(void) +{ +#ifdef ENHANCED_SYMBOLS + free_all_glyphmap_u(); + apply_customizations(gc.currentgraphics, do_custom_symbols); +#endif +} + +/* utf8map.c */ + + + diff --git a/src/vault.c b/src/vault.c index 00369fd61..32d9e84b0 100644 --- a/src/vault.c +++ b/src/vault.c @@ -1,36 +1,38 @@ -/* NetHack 3.6 vault.c $NHDT-Date: 1549921171 2019/02/11 21:39:31 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.62 $ */ +/* NetHack 5.0 vault.c $NHDT-Date: 1737622664 2025/01/23 00:57:44 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.113 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2011. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -STATIC_DCL boolean FDECL(clear_fcorr, (struct monst *, BOOLEAN_P)); -STATIC_DCL void FDECL(blackout, (int, int)); -STATIC_DCL void FDECL(restfakecorr, (struct monst *)); -STATIC_DCL void FDECL(parkguard, (struct monst *)); -STATIC_DCL boolean FDECL(in_fcorridor, (struct monst *, int, int)); -STATIC_DCL boolean FDECL(find_guard_dest, (struct monst *, xchar *, xchar *)); -STATIC_DCL void FDECL(move_gold, (struct obj *, int)); -STATIC_DCL void FDECL(wallify_vault, (struct monst *)); -STATIC_DCL void FDECL(gd_mv_monaway, (struct monst *, int, int)); -STATIC_OVL void FDECL(gd_pick_corridor_gold, (struct monst *, int, int)); +staticfn boolean clear_fcorr(struct monst *, boolean) NONNULLARG1; +staticfn void blackout(coordxy, coordxy); +staticfn void restfakecorr(struct monst *) NONNULLARG1; +staticfn void parkguard(struct monst *) NONNULLARG1; +staticfn boolean in_fcorridor(struct monst *, coordxy, coordxy) NONNULLARG1; +staticfn boolean find_guard_dest(struct monst *, coordxy *, coordxy *) + NONNULLARG23; +staticfn void move_gold(struct obj *, int) NONNULLARG1; +staticfn void wallify_vault(struct monst *) NONNULLARG1; +staticfn void gd_mv_monaway(struct monst *, int, int) NONNULLARG1; +staticfn void gd_pick_corridor_gold(struct monst *, int, int) NONNULLARG1; +staticfn int gd_move_cleanup(struct monst *, boolean, boolean) NONNULLARG1; +staticfn void gd_letknow(struct monst *) NONNULLARG1; void -newegd(mtmp) -struct monst *mtmp; +newegd(struct monst *mtmp) { if (!mtmp->mextra) mtmp->mextra = newmextra(); if (!EGD(mtmp)) { EGD(mtmp) = (struct egd *) alloc(sizeof (struct egd)); (void) memset((genericptr_t) EGD(mtmp), 0, sizeof (struct egd)); + EGD(mtmp)->parentmid = mtmp->m_id; } } void -free_egd(mtmp) -struct monst *mtmp; +free_egd(struct monst *mtmp) { if (mtmp->mextra && EGD(mtmp)) { free((genericptr_t) EGD(mtmp)); @@ -42,12 +44,10 @@ struct monst *mtmp; /* try to remove the temporary corridor (from vault to rest of map) being maintained by guard 'grd'; if guard is still in it, removal will fail, to be tried again later */ -STATIC_OVL boolean -clear_fcorr(grd, forceshow) -struct monst *grd; -boolean forceshow; +staticfn boolean +clear_fcorr(struct monst *grd, boolean forceshow) { - register int fcx, fcy, fcbeg; + coordxy fcx, fcy, fcbeg; struct monst *mtmp; boolean sawcorridor = FALSE, silently = program_state.stopprint ? TRUE : FALSE; @@ -58,7 +58,7 @@ boolean forceshow; if (!on_level(&egrd->gdlevel, &u.uz)) return TRUE; - /* note: guard remains on 'fmons' list (alive or dead, at off-map + /* note: guard remains on 'fmon' list (alive or dead, at off-map coordinate <0,0>), until temporary corridor from vault back to civilization has been removed */ while ((fcbeg = egrd->fcbeg) < egrd->fcend) { @@ -67,7 +67,7 @@ boolean forceshow; if ((DEADMONSTER(grd) || !in_fcorridor(grd, u.ux, u.uy)) && egrd->gddone) forceshow = TRUE; - if ((u.ux == fcx && u.uy == fcy && !DEADMONSTER(grd)) + if ((u_at(fcx, fcy) && !DEADMONSTER(grd)) || (!forceshow && couldsee(fcx, fcy)) || (Punished && !carried(uball) && uball->ox == fcx && uball->oy == fcy)) @@ -76,10 +76,10 @@ boolean forceshow; if ((mtmp = m_at(fcx, fcy)) != 0) { if (mtmp->isgd) { return FALSE; - } else if (!in_fcorridor(grd, u.ux, u.uy)) { + } else { if (mtmp->mtame) yelp(mtmp); - if (!rloc(mtmp, TRUE)) + if (!rloc(mtmp, RLOC_MSG)) m_into_limbo(mtmp); } } @@ -87,6 +87,7 @@ boolean forceshow; if (lev->typ == CORR && cansee(fcx, fcy)) sawcorridor = TRUE; lev->typ = egrd->fakecorr[fcbeg].ftyp; + lev->flags = egrd->fakecorr[fcbeg].flags; if (IS_STWALL(lev->typ)) { /* destroy any trap here (pit dug by you, hole dug via wand while levitating or by monster, bear trap or land @@ -97,10 +98,10 @@ boolean forceshow; if (lev->typ == STONE) blackout(fcx, fcy); } + del_engr_at(fcx, fcy); map_location(fcx, fcy, 1); /* bypass vision */ - if (!ACCESSIBLE(lev->typ)) - block_point(fcx, fcy); - vision_full_recalc = 1; + recalc_block_point(fcx, fcy); + gv.vision_full_recalc = 1; egrd->fcbeg++; } if (sawcorridor && !silently) @@ -108,7 +109,7 @@ boolean forceshow; /* only give encased message if hero is still alive (might get here via paygd() -> mongone() -> grddead() when game is over; died: no message, quit: message) */ - if (IS_ROCK(levl[u.ux][u.uy].typ) && (Upolyd ? u.mh : u.uhp) > 0 + if (IS_OBSTRUCTED(levl[u.ux][u.uy].typ) && (Upolyd ? u.mh : u.uhp) > 0 && !silently) You("are encased in rock."); return TRUE; @@ -118,9 +119,8 @@ boolean forceshow; spots to unlit; if player used scroll/wand/spell of light while inside the corridor, we don't want the light to reappear if/when a new tunnel goes through the same area */ -STATIC_OVL void -blackout(x, y) -int x, y; +staticfn void +blackout(coordxy x, coordxy y) { struct rm *lev; int i, j; @@ -140,9 +140,8 @@ int x, y; } } -STATIC_OVL void -restfakecorr(grd) -struct monst *grd; +staticfn void +restfakecorr(struct monst *grd) { /* it seems you left the corridor - let the guard disappear */ if (clear_fcorr(grd, FALSE)) { @@ -152,29 +151,28 @@ struct monst *grd; } /* move guard--dead to alive--to <0,0> until temporary corridor is removed */ -STATIC_OVL void -parkguard(grd) -struct monst *grd; +staticfn void +parkguard(struct monst *grd) { /* either guard is dead or will now be treated as if so; monster traversal loops should skip it */ - if (grd == context.polearm.hitmon) - context.polearm.hitmon = 0; + if (grd == svc.context.polearm.hitmon) + svc.context.polearm.hitmon = 0; if (grd->mx) { remove_monster(grd->mx, grd->my); newsym(grd->mx, grd->my); - place_monster(grd, 0, 0); - /* [grd->mx,my just got set to 0,0 by place_monster(), so this - just sets EGD(grd)->ogx,ogy to 0,0 too; is that what we want?] */ - EGD(grd)->ogx = grd->mx; - EGD(grd)->ogy = grd->my; } + if (m_at(0, 0) != grd) + place_monster(grd, 0, 0); + /* [grd->mx,my just got set to 0,0 by place_monster(), so this + just sets EGD(grd)->ogx,ogy to 0,0 too; is that what we want?] */ + EGD(grd)->ogx = grd->mx; + EGD(grd)->ogy = grd->my; } /* called in mon.c */ boolean -grddead(grd) -struct monst *grd; +grddead(struct monst *grd) { boolean dispose = clear_fcorr(grd, TRUE); @@ -190,12 +188,10 @@ struct monst *grd; return dispose; } -STATIC_OVL boolean -in_fcorridor(grd, x, y) -struct monst *grd; -int x, y; +staticfn boolean +in_fcorridor(struct monst *grd, coordxy x, coordxy y) { - register int fci; + int fci; struct egd *egrd = EGD(grd); for (fci = egrd->fcbeg; fci < egrd->fcend; fci++) @@ -205,42 +201,59 @@ int x, y; } struct monst * -findgd() +findgd(void) { - register struct monst *mtmp; + struct monst *mtmp, **mprev; + /* this might find a guard parked at <0,0> since it'll be on fmon list */ for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { - if (DEADMONSTER(mtmp)) - continue; - if (mtmp->isgd && on_level(&(EGD(mtmp)->gdlevel), &u.uz)) + if (mtmp->isgd && on_level(&EGD(mtmp)->gdlevel, &u.uz)) { + if (!mtmp->mx && !EGD(mtmp)->gddone) + mtmp->mhp = mtmp->mhpmax; + return mtmp; + } + } + /* if not on fmon, look for a guard waiting to migrate to this level */ + for (mprev = &gm.migrating_mons; (mtmp = *mprev) != 0; + mprev = &mtmp->nmon) { + if (mtmp->isgd && on_level(&EGD(mtmp)->gdlevel, &u.uz)) { + /* take out of migrating_mons and place at <0,0>; + simplified mon_arrive(); avoid that because it would + send mtmp into limbo if no regular map spot is available */ + *mprev = mtmp->nmon; + mtmp->nmon = fmon; + fmon = mtmp; + mon_track_clear(mtmp); + mtmp->mux = u.ux, mtmp->muy = u.uy; + mtmp->mx = mtmp->my = 0; /* not on map (note: mx is already 0) */ + parkguard(mtmp); return mtmp; + } } return (struct monst *) 0; } void -vault_summon_gd() +vault_summon_gd(void) { if (vault_occupied(u.urooms) && !findgd()) u.uinvault = (VAULT_GUARD_TIME - 1); } char -vault_occupied(array) -char *array; +vault_occupied(char *array) { - register char *ptr; + char *ptr; for (ptr = array; *ptr; ptr++) - if (rooms[*ptr - ROOMOFFSET].rtype == VAULT) + if (svr.rooms[*ptr - ROOMOFFSET].rtype == VAULT) return *ptr; return '\0'; } /* hero has teleported out of vault while a guard is active */ void -uleftvault(grd) -struct monst *grd; +uleftvault(struct monst *grd) { /* only called if caller has checked vault_occupied() and findgd() */ if (!grd || !grd->isgd || DEADMONSTER(grd)) { @@ -249,7 +262,7 @@ struct monst *grd; } /* if carrying gold and arriving anywhere other than next to the guard, set the guard loose */ - if ((money_cnt(invent) || hidden_gold()) + if ((money_cnt(gi.invent) || hidden_gold(TRUE)) && um_dist(grd->mx, grd->my, 1)) { if (grd->mpeaceful) { if (canspotmon(grd)) /* see or sense via telepathy */ @@ -264,18 +277,16 @@ struct monst *grd; } } -STATIC_OVL boolean -find_guard_dest(guard, rx, ry) -struct monst *guard; -xchar *rx, *ry; +staticfn boolean +find_guard_dest(struct monst *guard, coordxy *rx, coordxy *ry) { - register int x, y, dd, lx = 0, ly = 0; + coordxy x, y, dd, lx, ly; for (dd = 2; (dd < ROWNO || dd < COLNO); dd++) { - for (y = u.uy - dd; y <= u.uy + dd; ly = y, y++) { + for (y = u.uy - dd; y <= u.uy + dd; y++) { if (y < 0 || y > ROWNO - 1) continue; - for (x = u.ux - dd; x <= u.ux + dd; lx = x, x++) { + for (x = u.ux - dd; x <= u.ux + dd; x++) { if (y != u.uy - dd && y != u.uy + dd && x != u.ux - dd) x = u.ux + dd; if (x < 1 || x > COLNO - 1) @@ -303,47 +314,56 @@ xchar *rx, *ry; } void -invault() +invault(void) { -#ifdef BSD_43_BUG - int dummy; /* hack to avoid schain botch */ -#endif struct monst *guard; - boolean gsensed; + struct obj *otmp; + boolean spotted; int trycount, vaultroom = (int) vault_occupied(u.urooms); + int vgdeathcount; if (!vaultroom) { u.uinvault = 0; return; } - vaultroom -= ROOMOFFSET; + /* after a couple of guards don't come back from their trips to + the vault, future guards become more reluctant to turn up (even + if summoned via whistle) */ + vgdeathcount = svm.mvitals[PM_GUARD].died; + if (vgdeathcount < 2 || + (vgdeathcount < 50 && !rn2(vgdeathcount * vgdeathcount))) + ++u.uinvault; + if (u.uinvault < VAULT_GUARD_TIME + || (u.uinvault % (VAULT_GUARD_TIME / 2)) != 0) + return; guard = findgd(); - if (++u.uinvault % VAULT_GUARD_TIME == 0 && !guard) { + if (!guard) { /* if time ok and no guard now. */ char buf[BUFSZ]; - register int x, y, gx, gy; - xchar rx, ry; + int x, y, gdx, gdy, typ; + coordxy rx, ry; long umoney; /* first find the goal for the guard */ - if (!find_guard_dest((struct monst *)0, &rx, &ry)) + if (!find_guard_dest((struct monst *) 0, &rx, &ry)) return; - gx = rx, gy = ry; + gdx = rx, gdy = ry; + vaultroom -= ROOMOFFSET; /* next find a good place for a door in the wall */ x = u.ux; y = u.uy; if (levl[x][y].typ != ROOM) { /* player dug a door and is in it */ - if (levl[x + 1][y].typ == ROOM) + if (levl[x + 1][y].typ == ROOM) { x = x + 1; - else if (levl[x][y + 1].typ == ROOM) + } else if (levl[x][y + 1].typ == ROOM) { y = y + 1; - else if (levl[x - 1][y].typ == ROOM) + } else if (levl[x - 1][y].typ == ROOM) { x = x - 1; - else if (levl[x][y - 1].typ == ROOM) + } else if (levl[x][y - 1].typ == ROOM) { y = y - 1; - else if (levl[x + 1][y + 1].typ == ROOM) { + } else if (levl[x + 1][y + 1].typ == ROOM) { x = x + 1; y = y + 1; } else if (levl[x - 1][y - 1].typ == ROOM) { @@ -358,16 +378,16 @@ invault() } } while (levl[x][y].typ == ROOM) { - register int dx, dy; + int dx, dy; - dx = (gx > x) ? 1 : (gx < x) ? -1 : 0; - dy = (gy > y) ? 1 : (gy < y) ? -1 : 0; - if (abs(gx - x) >= abs(gy - y)) + dx = (gdx > x) ? 1 : (gdx < x) ? -1 : 0; + dy = (gdy > y) ? 1 : (gdy < y) ? -1 : 0; + if (abs(gdx - x) >= abs(gdy - y)) x += dx; else y += dy; } - if (x == u.ux && y == u.uy) { + if (u_at(x, y)) { if (levl[x + 1][y].typ == HWALL || levl[x + 1][y].typ == DOOR) x = x + 1; else if (levl[x - 1][y].typ == HWALL @@ -384,7 +404,7 @@ invault() } /* make something interesting happen */ - if (!(guard = makemon(&mons[PM_GUARD], x, y, MM_EGD))) + if (!(guard = makemon(&mons[PM_GUARD], x, y, MM_EGD | MM_NOMSG))) return; guard->isgd = 1; guard->mpeaceful = 1; @@ -396,48 +416,83 @@ invault() EGD(guard)->vroom = vaultroom; EGD(guard)->warncnt = 0; + /* ensure the guard doesn't respawn again next turn if killed + immediately */ + ++u.uinvault; + reset_faint(); /* if fainted - wake up */ - gsensed = !canspotmon(guard); - if (!gsensed) + /* if there are any boulders in the guard's way, destroy them; + perhaps the guard knows a touch equivalent of force bolt; + otherwise the hero wouldn't be able to push one to follow the + guard out of the vault because that guard would be in its way */ + if ((otmp = sobj_at(BOULDER, guard->mx, guard->my)) != 0) { + void (*func)(const char *, ...) PRINTF_F_PTR(1, 2); + const char *bname = simpleonames(otmp); + int bcnt = 0; + + do { + ++bcnt; + fracture_rock(otmp); + otmp = sobj_at(BOULDER, guard->mx, guard->my); + } while (otmp); + /* You_hear() will handle Deaf/!Deaf */ + func = !Blind ? You_see : You_hear; + (*func)("%s shatter.", + (bcnt == 1) ? an(bname) : makeplural(bname)); + } + spotted = canspotmon(guard); + if (spotted) { pline("Suddenly one of the Vault's %s enters!", - makeplural(guard->data->mname)); - else + makeplural(pmname(guard->data, Mgender(guard)))); + newsym(guard->mx, guard->my); + } else { pline("Someone else has entered the Vault."); - newsym(guard->mx, guard->my); + /* make sure that hero who can't see the guard knows where the + wall is breeched, otherwise we couldn't follow the guard out; + the breech isn't necessarily adjacent to the hero */ + map_invisible(guard->mx, guard->my); + } + if (u.uswallow) { /* can't interrogate hero, don't interrogate engulfer */ - if (!Deaf) + if (!Deaf) { + SetVoice(guard, 0, 80, 0); verbalize("What's going on here?"); - if (gsensed) + } + if (!spotted) pline_The("other presence vanishes."); mongone(guard); return; } if (U_AP_TYPE == M_AP_OBJECT || u.uundetected) { if (U_AP_TYPE == M_AP_OBJECT - && youmonst.mappearance != GOLD_PIECE) - if (!Deaf) + && gy.youmonst.mappearance != GOLD_PIECE) + if (!Deaf) { + SetVoice(guard, 0, 80, 0); verbalize("Hey! Who left that %s in here?", - mimic_obj_name(&youmonst)); + mimic_obj_name(&gy.youmonst)); + } /* You're mimicking some object or you're hidden. */ pline("Puzzled, %s turns around and leaves.", mhe(guard)); mongone(guard); return; } - if (Strangled || is_silent(youmonst.data) || multi < 0) { - /* [we ought to record whether this this message has already + if (Strangled || is_silent(gy.youmonst.data) || gm.multi < 0) { + /* [we ought to record whether this message has already been given in order to vary it upon repeat visits, but discarding the monster and its egd data renders that hard] */ - if (Deaf) + if (Deaf) { pline("%s huffs and turns to leave.", noit_Monnam(guard)); - else + } else { + SetVoice(guard, 0, 80, 0); verbalize("I'll be back when you're ready to speak to me!"); + } mongone(guard); return; } stop_occupation(); /* if occupied, stop it *now* */ - if (multi > 0) { + if (gm.multi > 0) { nomul(0); unmul((char *) 0); } @@ -451,17 +506,18 @@ invault() if (u.ualign.type == A_LAWFUL /* ignore trailing text, in case player includes rank */ - && strncmpi(buf, plname, (int) strlen(plname)) != 0) { + && strncmpi(buf, svp.plname, (int) strlen(svp.plname)) != 0) { adjalign(-1); /* Liar! */ } if (!strcmpi(buf, "Croesus") || !strcmpi(buf, "Kroisos") || !strcmpi(buf, "Creosote")) { /* Discworld */ - if (!mvitals[PM_CROESUS].died) { + if (!svm.mvitals[PM_CROESUS].died) { if (Deaf) { if (!Blind) pline("%s waves goodbye.", noit_Monnam(guard)); } else { + SetVoice(guard, 0, 80, 0); verbalize( "Oh, yes, of course. Sorry to have disturbed you."); } @@ -473,6 +529,7 @@ invault() pline("%s mouths something and looks very angry!", noit_Monnam(guard)); } else { + SetVoice(guard, 0, 80, 0); verbalize( "Back from the dead, are you? I'll remedy that!"); } @@ -484,25 +541,30 @@ invault() } return; } - if (Deaf) + if (Deaf) { pline("%s doesn't %srecognize you.", noit_Monnam(guard), (Blind) ? "" : "appear to "); - else + } else { + SetVoice(guard, 0, 80, 0); verbalize("I don't know you."); - umoney = money_cnt(invent); - if (!umoney && !hidden_gold()) { - if (Deaf) + } + umoney = money_cnt(gi.invent); + if (!umoney && !hidden_gold(TRUE)) { + if (Deaf) { pline("%s stomps%s.", noit_Monnam(guard), (Blind) ? "" : " and beckons"); - else + } else { + SetVoice(guard, 0, 80, 0); verbalize("Please follow me."); + } } else { if (!umoney) { if (Deaf) { if (!Blind) pline("%s glares at you%s.", noit_Monnam(guard), - invent ? "r stuff" : ""); + gi.invent ? "r stuff" : ""); } else { + SetVoice(guard, 0, 80, 0); verbalize("You have hidden gold."); } } @@ -513,71 +575,84 @@ invault() noit_Monnam(guard), noit_mhis(guard), noit_mhis(guard)); } else { + SetVoice(guard, 0, 80, 0); verbalize( "Most likely all your gold was stolen from this vault."); + SetVoice(guard, 0, 80, 0); verbalize("Please drop that gold and follow me."); } + EGD(guard)->dropgoldcnt++; } - EGD(guard)->gdx = gx; - EGD(guard)->gdy = gy; + EGD(guard)->gdx = gdx; + EGD(guard)->gdy = gdy; EGD(guard)->fcbeg = 0; EGD(guard)->fakecorr[0].fx = x; EGD(guard)->fakecorr[0].fy = y; - if (IS_WALL(levl[x][y].typ)) { - EGD(guard)->fakecorr[0].ftyp = levl[x][y].typ; - } else { /* the initial guard location is a dug door */ + typ = levl[x][y].typ; + if (!IS_WALL(typ)) { + /* guard arriving at non-wall implies a door; vault wall was + dug into an empty doorway (which could subsequently have + been plugged with an intact door by use of locking magic) */ int vlt = EGD(guard)->vroom; - xchar lowx = rooms[vlt].lx, hix = rooms[vlt].hx; - xchar lowy = rooms[vlt].ly, hiy = rooms[vlt].hy; + coordxy lowx = svr.rooms[vlt].lx, hix = svr.rooms[vlt].hx; + coordxy lowy = svr.rooms[vlt].ly, hiy = svr.rooms[vlt].hy; if (x == lowx - 1 && y == lowy - 1) - EGD(guard)->fakecorr[0].ftyp = TLCORNER; + typ = TLCORNER; else if (x == hix + 1 && y == lowy - 1) - EGD(guard)->fakecorr[0].ftyp = TRCORNER; + typ = TRCORNER; else if (x == lowx - 1 && y == hiy + 1) - EGD(guard)->fakecorr[0].ftyp = BLCORNER; + typ = BLCORNER; else if (x == hix + 1 && y == hiy + 1) - EGD(guard)->fakecorr[0].ftyp = BRCORNER; + typ = BRCORNER; else if (y == lowy - 1 || y == hiy + 1) - EGD(guard)->fakecorr[0].ftyp = HWALL; + typ = HWALL; else if (x == lowx - 1 || x == hix + 1) - EGD(guard)->fakecorr[0].ftyp = VWALL; + typ = VWALL; + + /* we lack access to the original wall_info bit mask for this + former wall location so recreate it */ + levl[x][y].typ = typ; /* wall; will be changed to door below */ + levl[x][y].wall_info = 0; /* will be reset too via doormask */ + xy_set_wall_state(x, y); /* set WA_MASK bits in .wall_info */ } + EGD(guard)->fakecorr[0].ftyp = typ; + EGD(guard)->fakecorr[0].flags = levl[x][y].flags; + /* guard's entry point where confrontation with hero takes place */ + spot_stop_timers(x, y, MELT_ICE_AWAY); levl[x][y].typ = DOOR; levl[x][y].doormask = D_NODOOR; - unblock_point(x, y); /* doesn't block light */ + unblock_point(x, y); /* empty doorway doesn't block light */ EGD(guard)->fcend = 1; EGD(guard)->warncnt = 1; } } -STATIC_OVL void -move_gold(gold, vroom) -struct obj *gold; -int vroom; +staticfn void +move_gold(struct obj *gold, int vroom) { - xchar nx, ny; + coordxy nx, ny; remove_object(gold); newsym(gold->ox, gold->oy); - nx = rooms[vroom].lx + rn2(2); - ny = rooms[vroom].ly + rn2(2); + nx = svr.rooms[vroom].lx + rn2(2); + ny = svr.rooms[vroom].ly + rn2(2); place_object(gold, nx, ny); stackobj(gold); newsym(nx, ny); } -STATIC_OVL void -wallify_vault(grd) -struct monst *grd; +staticfn void +wallify_vault(struct monst *grd) { - int x, y, typ; + int typ; + coordxy x, y; int vlt = EGD(grd)->vroom; char tmp_viz; - xchar lox = rooms[vlt].lx - 1, hix = rooms[vlt].hx + 1, - loy = rooms[vlt].ly - 1, hiy = rooms[vlt].hy + 1; + coordxy lox = svr.rooms[vlt].lx - 1, hix = svr.rooms[vlt].hx + 1, + loy = svr.rooms[vlt].ly - 1, hiy = svr.rooms[vlt].hy + 1; struct monst *mon; - struct obj *gold; + struct obj *gold, *rocks; struct trap *trap; boolean fixed = FALSE; boolean movedgold = FALSE; @@ -588,36 +663,56 @@ struct monst *grd; if (x != lox && x != hix && y != loy && y != hiy) continue; - if (!IS_WALL(levl[x][y].typ) && !in_fcorridor(grd, x, y)) { + if ((!IS_WALL(levl[x][y].typ) || g_at(x, y) + || sobj_at(ROCK, x, y) || sobj_at(BOULDER, x, y)) + && !in_fcorridor(grd, x, y)) { if ((mon = m_at(x, y)) != 0 && mon != grd) { if (mon->mtame) yelp(mon); - (void) rloc(mon, FALSE); + if (!rloc(mon, RLOC_MSG)) + m_into_limbo(mon); } + /* move gold at wall locations into the vault */ if ((gold = g_at(x, y)) != 0) { move_gold(gold, EGD(grd)->vroom); movedgold = TRUE; } + /* destroy rocks and boulders (subsume them into the walls); + other objects present stay intact and become embedded */ + while ((rocks = sobj_at(ROCK, x, y)) != 0) { + obj_extract_self(rocks); + obfree(rocks, (struct obj *) 0); + } + while ((rocks = sobj_at(BOULDER, x, y)) != 0) { + obj_extract_self(rocks); + obfree(rocks, (struct obj *) 0); + } if ((trap = t_at(x, y)) != 0) deltrap(trap); + if (x == lox) - typ = - (y == loy) ? TLCORNER : (y == hiy) ? BLCORNER : VWALL; + typ = (y == loy) ? TLCORNER + : (y == hiy) ? BLCORNER + : VWALL; else if (x == hix) - typ = - (y == loy) ? TRCORNER : (y == hiy) ? BRCORNER : VWALL; + typ = (y == loy) ? TRCORNER + : (y == hiy) ? BRCORNER + : VWALL; else /* not left or right side, must be top or bottom */ typ = HWALL; + levl[x][y].typ = typ; - levl[x][y].doormask = 0; + levl[x][y].wall_info = 0; + xy_set_wall_state(x, y); /* set WA_MASK bits in .wall_info */ + del_engr_at(x, y); /* * hack: player knows walls are restored because of the * message, below, so show this on the screen. */ - tmp_viz = viz_array[y][x]; - viz_array[y][x] = IN_SIGHT | COULD_SEE; + tmp_viz = gv.viz_array[y][x]; + gv.viz_array[y][x] = IN_SIGHT | COULD_SEE; newsym(x, y); - viz_array[y][x] = tmp_viz; + gv.viz_array[y][x] = tmp_viz; block_point(x, y); fixed = TRUE; } @@ -635,31 +730,32 @@ struct monst *grd; } } -STATIC_OVL void -gd_mv_monaway(grd, nx, ny) -register struct monst *grd; -int nx, ny; +staticfn void +gd_mv_monaway(struct monst *grd, int nx, int ny) { - if (MON_AT(nx, ny) && !(nx == grd->mx && ny == grd->my)) { - if (!Deaf) + struct monst *mtmp = m_at(nx, ny); + + if (mtmp && mtmp != grd) { + if (!Deaf) { + SetVoice(grd, 0, 80, 0); verbalize("Out of my way, scum!"); - if (!rloc(m_at(nx, ny), FALSE) || MON_AT(nx, ny)) - m_into_limbo(m_at(nx, ny)); + } + if (!rloc(mtmp, RLOC_ERR | RLOC_MSG) || MON_AT(nx, ny)) + m_into_limbo(mtmp); + recalc_block_point(nx, ny); } } /* have guard pick gold off the floor, possibly moving to the gold's position before message and back to his current spot after */ -STATIC_OVL void -gd_pick_corridor_gold(grd, goldx, goldy) -struct monst *grd; -int goldx, goldy; /* ox, gold->oy> */ +staticfn void +gd_pick_corridor_gold(struct monst *grd, int goldx, int goldy) { struct obj *gold; coord newcc, bestcc; int gdelta, newdelta, bestdelta, tryct, guardx = grd->mx, guardy = grd->my; - boolean under_u = (goldx == u.ux && goldy == u.uy), + boolean under_u = u_at(goldx, goldy), see_it = cansee(goldx, goldy); if (under_u) { @@ -673,7 +769,7 @@ int goldx, goldy; /* ox, gold->oy> */ gdelta = distu(guardx, guardy); if (gdelta > 2 && see_it) { /* skip if player won't see it */ bestdelta = gdelta; - bestcc.x = (xchar) guardx, bestcc.y = (xchar) guardy; + bestcc.x = (coordxy) guardx, bestcc.y = (coordxy) guardy; tryct = 9; do { /* pick an available spot nearest the hero and also try @@ -718,12 +814,7 @@ int goldx, goldy; /* ox, gold->oy> */ } if (see_it) { /* cansee(goldx, goldy) */ - char monnambuf[BUFSZ]; - - Strcpy(monnambuf, Monnam(grd)); - if (!strcmpi(monnambuf, "It")) - Strcpy(monnambuf, "Someone"); - pline("%s%s picks up the gold%s.", monnambuf, + pline("%s%s picks up the gold%s.", Some_Monnam(grd), (grd->mpeaceful && EGD(grd)->warncnt > 5) ? " calms down and" : "", under_u ? " from beneath you" : ""); @@ -739,30 +830,80 @@ int goldx, goldy; /* ox, gold->oy> */ return; } + +/* return 1: guard moved, -2: died */ +staticfn int +gd_move_cleanup( + struct monst *grd, + boolean semi_dead, + boolean disappear_msg_seen) +{ + int x, y; + boolean see_guard; + + /* + * The following is a kludge. We need to keep the guard around in + * order to be able to make the fake corridor disappear as the + * player moves out of it, but we also need the guard out of the + * way. We send the guard to never-never land. We set ogx ogy to + * mx my in order to avoid a check at the top of this function. + * At the end of the process, the guard is killed in restfakecorr(). + */ + x = grd->mx, y = grd->my; + see_guard = canspotmon(grd); + parkguard(grd); /* move to <0,0> */ + wallify_vault(grd); + restfakecorr(grd); + debugpline2("gd_move_cleanup: %scleanup%s", + grd->isgd ? "" : "final ", + grd->isgd ? " attempt" : ""); + if (!semi_dead && (in_fcorridor(grd, u.ux, u.uy) || cansee(x, y))) { + if (!disappear_msg_seen && see_guard) + pline("Suddenly, %s disappears.", noit_mon_nam(grd)); + return 1; + } + return -2; +} + +staticfn void +gd_letknow(struct monst *grd) +{ + if (!cansee(grd->mx, grd->my) || !mon_visible(grd)) + You_hear("%s.", + m_carrying(grd, TIN_WHISTLE) + ? "the shrill sound of a guard's whistle" + : "angry shouting"); + else + You(um_dist(grd->mx, grd->my, 2) + ? "see %s approaching." + : "are confronted by %s.", + /* "an angry guard" */ + x_monnam(grd, ARTICLE_A, "angry", 0, FALSE)); +} + /* * return 1: guard moved, 0: guard didn't, -1: let m_move do it, -2: died */ int -gd_move(grd) -register struct monst *grd; +gd_move(struct monst *grd) { - int x, y, nx, ny, m, n; - int dx, dy, gx = 0, gy = 0, fci; + coordxy x, y, nx, ny, m, n, ex, ey; + coordxy dx, dy, ggx = 0, ggy = 0, fci; uchar typ; struct rm *crm; struct fakecorridor *fcp; - register struct egd *egrd = EGD(grd); + struct egd *egrd = EGD(grd); long umoney = 0L; boolean goldincorridor = FALSE, u_in_vault = FALSE, grd_in_vault = FALSE, - disappear_msg_seen = FALSE, semi_dead = DEADMONSTER(grd), - u_carry_gold = FALSE, newspot = FALSE, see_guard; + semi_dead = DEADMONSTER(grd), + u_carry_gold = FALSE, newspot = FALSE; if (!on_level(&(egrd->gdlevel), &u.uz)) return -1; - nx = ny = m = n = 0; + if (semi_dead || !grd->mx || egrd->gddone) { egrd->gddone = 1; - goto cleanup; + return gd_move_cleanup(grd, semi_dead, FALSE); } debugpline1("gd_move: %s guard", grd->mpeaceful ? "peaceful" : "hostile"); @@ -775,11 +916,12 @@ register struct monst *grd; if (!u_in_vault && (grd_in_vault || (in_fcorridor(grd, grd->mx, grd->my) && !in_fcorridor(grd, u.ux, u.uy)))) { - (void) rloc(grd, TRUE); + (void) rloc(grd, RLOC_MSG); wallify_vault(grd); if (!in_fcorridor(grd, grd->mx, grd->my)) (void) clear_fcorr(grd, TRUE); - goto letknow; + gd_letknow(grd); + return -1; } if (!in_fcorridor(grd, grd->mx, grd->my)) (void) clear_fcorr(grd, TRUE); @@ -789,37 +931,53 @@ register struct monst *grd; return -1; /* teleported guard - treat as monster */ if (egrd->witness) { - if (!Deaf) + if (!Deaf) { + SetVoice(grd, 0, 80, 0); verbalize("How dare you %s that gold, scoundrel!", (egrd->witness & GD_EATGOLD) ? "consume" : "destroy"); + } egrd->witness = 0; grd->mpeaceful = 0; return -1; } - umoney = money_cnt(invent); - u_carry_gold = umoney > 0L || hidden_gold() > 0L; + umoney = money_cnt(gi.invent); + u_carry_gold = (umoney > 0L || hidden_gold(TRUE) > 0L); if (egrd->fcend == 1) { if (u_in_vault && (u_carry_gold || um_dist(grd->mx, grd->my, 1))) { - if (egrd->warncnt == 3 && !Deaf) - verbalize("I repeat, %sfollow me!", - u_carry_gold - ? (!umoney ? "drop that hidden money and " - : "drop that money and ") - : ""); + if (egrd->warncnt == 3 && !Deaf) { + char buf[BUFSZ]; + + Sprintf(buf, "%sfollow me!", + u_carry_gold ? (!umoney ? "drop that hidden gold and " + : "drop that gold and ") + : ""); + SetVoice(grd, 0, 80, 0); + if (egrd->dropgoldcnt || !u_carry_gold) + verbalize("I repeat, %s", buf); + else + verbalize("%s", upstart(buf)); + if (u_carry_gold) + egrd->dropgoldcnt++; + } if (egrd->warncnt == 7) { m = grd->mx; n = grd->my; - if (!Deaf) + if (!Deaf) { + SetVoice(grd, 0, 80, 0); verbalize("You've been warned, knave!"); - mnexto(grd); + } + grd->mpeaceful = 0; + mnexto(grd, RLOC_NOMSG); levl[m][n].typ = egrd->fakecorr[0].ftyp; + levl[m][n].flags = egrd->fakecorr[0].flags; + recalc_block_point(m, n); + del_engr_at(m, n); newsym(m, n); - grd->mpeaceful = 0; return -1; } /* not fair to get mad when (s)he's fainted or paralyzed */ - if (!is_fainted() && multi >= 0) + if (!is_fainted() && gm.multi >= 0) egrd->warncnt++; return 0; } @@ -828,28 +986,22 @@ register struct monst *grd; if (u_carry_gold) { /* player teleported */ m = grd->mx; n = grd->my; - (void) rloc(grd, TRUE); + (void) rloc(grd, RLOC_MSG); levl[m][n].typ = egrd->fakecorr[0].ftyp; + levl[m][n].flags = egrd->fakecorr[0].flags; + recalc_block_point(m, n); /* guard corridor goes away */ + del_engr_at(m, n); newsym(m, n); grd->mpeaceful = 0; - letknow: - if (!cansee(grd->mx, grd->my) || !mon_visible(grd)) - You_hear("%s.", - m_carrying(grd, TIN_WHISTLE) - ? "the shrill sound of a guard's whistle" - : "angry shouting"); - else - You(um_dist(grd->mx, grd->my, 2) - ? "see %s approaching." - : "are confronted by %s.", - /* "an angry guard" */ - x_monnam(grd, ARTICLE_A, "angry", 0, FALSE)); + gd_letknow(grd); return -1; } else { - if (!Deaf) + if (!Deaf) { + SetVoice(grd, 0, 80, 0); verbalize("Well, begone."); + } egrd->gddone = 1; - goto cleanup; + return gd_move_cleanup(grd, semi_dead, FALSE); } } } @@ -857,11 +1009,10 @@ register struct monst *grd; if (egrd->fcend > 1) { if (egrd->fcend > 2 && in_fcorridor(grd, grd->mx, grd->my) && !egrd->gddone && !in_fcorridor(grd, u.ux, u.uy) - && levl[egrd->fakecorr[0].fx][egrd->fakecorr[0].fy].typ - == egrd->fakecorr[0].ftyp) { + && (levl[egrd->fakecorr[0].fx][egrd->fakecorr[0].fy].typ + == egrd->fakecorr[0].ftyp)) { pline("%s, confused, disappears.", noit_Monnam(grd)); - disappear_msg_seen = TRUE; - goto cleanup; + return gd_move_cleanup(grd, semi_dead, TRUE); } if (u_carry_gold && (in_fcorridor(grd, u.ux, u.uy) /* cover a 'blind' spot */ @@ -877,6 +1028,7 @@ register struct monst *grd; pline("%s holds out %s palm demandingly!", noit_Monnam(grd), noit_mhis(grd)); } else { + SetVoice(grd, 0, 80, 0); verbalize("Drop all your gold, scoundrel!"); } return 0; @@ -886,6 +1038,7 @@ register struct monst *grd; pline("%s rubs %s hands with enraged delight!", noit_Monnam(grd), noit_mhis(grd)); } else { + SetVoice(grd, 0, 80, 0); verbalize("So be it, rogue!"); } grd->mpeaceful = 0; @@ -893,6 +1046,7 @@ register struct monst *grd; } } } + m = n = 0; for (fci = egrd->fcbeg; fci < egrd->fcend; fci++) if (g_at(egrd->fakecorr[fci].fx, egrd->fakecorr[fci].fy)) { m = egrd->fakecorr[fci].fx; @@ -911,8 +1065,10 @@ register struct monst *grd; } if (um_dist(grd->mx, grd->my, 1) || egrd->gddone) { if (!egrd->gddone && !rn2(10) && !Deaf && !u.uswallow - && !(u.ustuck && !sticks(youmonst.data))) + && !(u.ustuck && !sticks(gy.youmonst.data))) { + SetVoice(grd, 0, 80, 0); verbalize("Move along!"); + } restfakecorr(grd); return 0; /* didn't move */ } @@ -927,7 +1083,8 @@ register struct monst *grd; for (ny = y - 1; ny <= y + 1; ny++) { if ((nx == x || ny == y) && (nx != x || ny != y) && isok(nx, ny)) { - typ = (crm = &levl[nx][ny])->typ; + crm = &levl[nx][ny]; + typ = crm->typ; if (!IS_STWALL(typ) && !IS_POOL(typ)) { if (in_fcorridor(grd, nx, ny)) goto nextnxy; @@ -939,16 +1096,12 @@ register struct monst *grd; egrd->gddone = 1; if (ACCESSIBLE(typ)) goto newpos; -#ifdef STUPID - if (typ == SCORR) - crm->typ = CORR; - else - crm->typ = DOOR; -#else crm->typ = (typ == SCORR) ? CORR : DOOR; -#endif if (crm->typ == DOOR) crm->doormask = D_NODOOR; + else + crm->flags = 0; + del_engr_at(nx, ny); goto proceed; } } @@ -958,22 +1111,24 @@ register struct monst *grd; nextpos: nx = x; ny = y; - gx = egrd->gdx; - gy = egrd->gdy; - dx = (gx > x) ? 1 : (gx < x) ? -1 : 0; - dy = (gy > y) ? 1 : (gy < y) ? -1 : 0; - if (abs(gx - x) >= abs(gy - y)) + ggx = egrd->gdx; + ggy = egrd->gdy; + dx = (ggx > x) ? 1 : (ggx < x) ? -1 : 0; + dy = (ggy > y) ? 1 : (ggy < y) ? -1 : 0; + if (abs(ggx - x) >= abs(ggy - y)) nx += dx; else ny += dy; while ((typ = (crm = &levl[nx][ny])->typ) != STONE) { + ex = nx + nx - x; + ey = ny + ny - y; /* in view of the above we must have IS_WALL(typ) or typ == POOL */ /* must be a wall here */ - if (isok(nx + nx - x, ny + ny - y) && !IS_POOL(typ) - && IS_ROOM(levl[nx + nx - x][ny + ny - y].typ)) { + if (isok(ex, ey) && IS_ROOM(levl[ex][ey].typ)) { crm->typ = DOOR; crm->doormask = D_NODOOR; + del_engr_at(ex, ey); goto proceed; } if (dy && nx != x) { @@ -991,62 +1146,43 @@ register struct monst *grd; if (IS_ROOM(typ)) { crm->typ = DOOR; crm->doormask = D_NODOOR; + del_engr_at(ex, ey); goto proceed; } break; } crm->typ = CORR; + crm->flags = 0; proceed: newspot = TRUE; unblock_point(nx, ny); /* doesn't block light */ if (cansee(nx, ny)) newsym(nx, ny); - if ((nx != gx || ny != gy) || (grd->mx != gx || grd->my != gy)) { + if ((nx != ggx || ny != ggy) || (grd->mx != ggx || grd->my != ggy)) { fcp = &(egrd->fakecorr[egrd->fcend]); + /* fakecorr overflow does not occur because egrd->fakecorr[] + is too small, but it has occurred when the same are + put into it repeatedly for some as yet unexplained reason */ if (egrd->fcend++ == FCSIZ) panic("fakecorr overflow"); fcp->fx = nx; fcp->fy = ny; fcp->ftyp = typ; + fcp->flags = crm->flags; } else if (!egrd->gddone) { /* We're stuck, so try to find a new destination. */ if (!find_guard_dest(grd, &egrd->gdx, &egrd->gdy) - || (egrd->gdx == gx && egrd->gdy == gy)) { + || (egrd->gdx == ggx && egrd->gdy == ggy)) { pline("%s, confused, disappears.", Monnam(grd)); - disappear_msg_seen = TRUE; - goto cleanup; + return gd_move_cleanup(grd, semi_dead, TRUE); } else goto nextpos; } newpos: gd_mv_monaway(grd, nx, ny); - if (egrd->gddone) { - /* The following is a kludge. We need to keep */ - /* the guard around in order to be able to make */ - /* the fake corridor disappear as the player */ - /* moves out of it, but we also need the guard */ - /* out of the way. We send the guard to never- */ - /* never land. We set ogx ogy to mx my in order */ - /* to avoid a check at the top of this function. */ - /* At the end of the process, the guard is killed */ - /* in restfakecorr(). */ - cleanup: - x = grd->mx, y = grd->my; - see_guard = canspotmon(grd); - parkguard(grd); /* move to <0,0> */ - wallify_vault(grd); - restfakecorr(grd); - debugpline2("gd_move: %scleanup%s", - grd->isgd ? "" : "final ", - grd->isgd ? " attempt" : ""); - if (!semi_dead && (in_fcorridor(grd, u.ux, u.uy) || cansee(x, y))) { - if (!disappear_msg_seen && see_guard) - pline("Suddenly, %s disappears.", noit_mon_nam(grd)); - return 1; - } - return -2; - } + if (egrd->gddone) + return gd_move_cleanup(grd, semi_dead, FALSE); egrd->ogx = grd->mx; /* update old positions */ egrd->ogy = grd->my; remove_monster(grd->mx, grd->my); @@ -1066,13 +1202,12 @@ register struct monst *grd; /* Routine when dying or quitting with a vault guard around */ void -paygd(silently) -boolean silently; +paygd(boolean silently) { - register struct monst *grd = findgd(); - long umoney = money_cnt(invent); + struct monst *grd = findgd(); + long umoney = money_cnt(gi.invent); struct obj *coins, *nextcoins; - int gx, gy; + int gdx, gdy; char buf[BUFSZ]; if (!umoney || !grd) @@ -1082,26 +1217,27 @@ boolean silently; if (!silently) Your("%ld %s goes into the Magic Memory Vault.", umoney, currency(umoney)); - gx = u.ux; - gy = u.uy; + gdx = u.ux; + gdy = u.uy; } else { if (grd->mpeaceful) /* peaceful guard has no "right" to your gold */ goto remove_guard; - mnexto(grd); + mnexto(grd, RLOC_NOMSG); if (!silently) pline("%s remits your gold to the vault.", Monnam(grd)); - gx = rooms[EGD(grd)->vroom].lx + rn2(2); - gy = rooms[EGD(grd)->vroom].ly + rn2(2); + gdx = svr.rooms[EGD(grd)->vroom].lx + rn2(2); + gdy = svr.rooms[EGD(grd)->vroom].ly + rn2(2); Sprintf(buf, "To Croesus: here's the gold recovered from %s the %s.", - plname, mons[u.umonster].mname); - make_grave(gx, gy, buf); + svp.plname, + pmname(&mons[u.umonster], flags.female ? FEMALE : MALE)); + make_grave(gdx, gdy, buf); } - for (coins = invent; coins; coins = nextcoins) { + for (coins = gi.invent; coins; coins = nextcoins) { nextcoins = coins->nobj; if (objects[coins->otyp].oc_class == COIN_CLASS) { freeinv(coins); - place_object(coins, gx, gy); + place_object(coins, gdx, gdy); stackobj(coins); } } @@ -1110,15 +1246,22 @@ boolean silently; return; } +/* + * amount of gold in carried containers + * + * even_if_unknown: + * True: all gold + * False: limit to known contents + */ long -hidden_gold() +hidden_gold(boolean even_if_unknown) { long value = 0L; struct obj *obj; - for (obj = invent; obj; obj = obj->nobj) - if (Has_contents(obj)) - value += contained_gold(obj); + for (obj = gi.invent; obj; obj = obj->nobj) + if (Has_contents(obj) && (obj->cknown || even_if_unknown)) + value += contained_gold(obj, even_if_unknown); /* unknown gold stuck inside statues may cause some consternation... */ return value; @@ -1126,23 +1269,17 @@ hidden_gold() /* prevent "You hear footsteps.." when inappropriate */ boolean -gd_sound() +gd_sound(void) { - struct monst *grd = findgd(); - - if (vault_occupied(u.urooms)) - return FALSE; - else - return (boolean) (grd == (struct monst *) 0); + return !(vault_occupied(u.urooms) || findgd()); } void -vault_gd_watching(activity) -unsigned int activity; +vault_gd_watching(unsigned int activity) { struct monst *guard = findgd(); - if (guard && guard->mcansee && m_canseeu(guard)) { + if (guard && guard->mx && guard->mcansee && m_canseeu(guard)) { if (activity == GD_EATGOLD || activity == GD_DESTROYGOLD) EGD(guard)->witness = activity; } diff --git a/src/version.c b/src/version.c index 468aae711..1958ae5ed 100644 --- a/src/version.c +++ b/src/version.c @@ -1,47 +1,41 @@ -/* NetHack 3.6 version.c $NHDT-Date: 1552353060 2019/03/12 01:11:00 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.52 $ */ +/* NetHack 5.0 version.c $NHDT-Date: 1737622664 2025/01/23 00:57:44 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.105 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Michael Allison, 2018. */ /* NetHack may be freely redistributed. See license for details. */ + #include "hack.h" #include "dlb.h" -#include "date.h" -/* - * All the references to the contents of patchlevel.h have been moved - * into makedefs.... - */ -#ifdef SHORT_FILENAMES -#include "patchlev.h" -#else -#include "patchlevel.h" -#endif -#if defined(NETHACK_GIT_SHA) -const char *NetHack_git_sha = NETHACK_GIT_SHA; -#endif -#if defined(NETHACK_GIT_BRANCH) -const char *NetHack_git_branch = NETHACK_GIT_BRANCH; +#ifndef MINIMAL_FOR_RECOVER +#ifndef SFCTOOL + +#ifndef OPTIONS_AT_RUNTIME +#define OPTIONS_AT_RUNTIME #endif -STATIC_DCL void FDECL(insert_rtoption, (char *)); +staticfn void insert_rtoption(char *) NONNULLARG1; -/* fill buffer with short version (so caller can avoid including date.h) */ +/* fill buffer with short version (so caller can avoid including date.h) + * buf cannot be NULL */ char * -version_string(buf) -char *buf; +version_string(char *buf, size_t bufsz) { - return strcpy(buf, VERSION_STRING); + Snprintf(buf, bufsz, "%s", + ((nomakedefs.version_string && nomakedefs.version_string[0]) + ? nomakedefs.version_string + /* in case we try to write a paniclog entry after releasing + the 'nomakedefs' data */ + : mdlib_version_string(buf, "."))); + return buf; } /* fill and return the given buffer with the long nethack version string */ char * -getversionstring(buf) -char *buf; +getversionstring(char *buf, size_t bufsz) { - Strcpy(buf, VERSION_ID); + Strcpy(buf, nomakedefs.version_id); -#if defined(RUNTIME_PORT_ID) \ - || defined(NETHACK_GIT_SHA) || defined(NETHACK_GIT_BRANCH) { int c = 0; #if defined(RUNTIME_PORT_ID) @@ -56,57 +50,150 @@ char *buf; #if defined(RUNTIME_PORT_ID) tmp = get_port_id(tmpbuf); if (tmp) - Sprintf(eos(buf), "%s%s", c++ ? "," : "", tmp); + Snprintf(eos(buf), (bufsz - strlen(buf)) - 1, + "%s%s", c++ ? "," : "", tmp); #endif -#if defined(NETHACK_GIT_SHA) - if (NetHack_git_sha) - Sprintf(eos(buf), "%s%s", c++ ? "," : "", NetHack_git_sha); -#endif -#if defined(NETHACK_GIT_BRANCH) + if (nomakedefs.git_sha) + Snprintf(eos(buf), (bufsz - strlen(buf)) - 1, + "%s%s", c++ ? "," : "", nomakedefs.git_sha); #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) - if (NetHack_git_branch) - Sprintf(eos(buf), "%sbranch:%s", - c++ ? "," : "", NetHack_git_branch); -#endif + if (nomakedefs.git_branch) + Snprintf(eos(buf), (bufsz - strlen(buf)) - 1, + "%sbranch:%s", + c++ ? "," : "", nomakedefs.git_branch); #endif + if (nomakedefs.git_prefix) + Snprintf(eos(buf), (bufsz - strlen(buf)) - 1, + "%sprefix:%s", + c++ ? "," : "", nomakedefs.git_prefix); if (c) - Strcat(buf, ")"); + Snprintf(eos(buf), (bufsz - strlen(buf)) - 1, + "%s", ")"); else /* if nothing has been added, strip " (" back off */ *p = '\0'; if (dotoff) - Strcat(buf, "."); + Snprintf(eos(buf), (bufsz - strlen(buf)) - 1, + "%s", "."); } -#endif /* RUNTIME_PORT_ID || NETHACK_GIT_SHA || NETHACK_GIT_BRANCH */ + return buf; +} +/* version info that could be displayed on status lines; + " "; + if game name is a prefix of--or same as--branch name, it is omitted + " "; + after release--or if branch info is unavailable--it will be + " "; + game name or branch name or both can be requested via flags */ +char * +status_version(char *buf, size_t bufsz, boolean indent) +{ + const char *name = NULL, *altname = NULL, *indentation; + unsigned vflags = flags.versinfo; + boolean shownum = ((vflags & VI_NUMBER) != 0), + showname = ((vflags & VI_NAME) != 0), + showbranch = ((vflags & VI_BRANCH) != 0); + + /* game's name {variants should use own name, not "NetHack"} */ + if (showname) { +#ifdef VERS_GAME_NAME /* can be set to override default (base of filename) */ + name = VERS_GAME_NAME; +#else + name = nh_basename(gh.hname, FALSE); /* hname is from xxxmain.c */ +#endif + if (!name || !*name) /* shouldn't happen */ + showname = FALSE; + } + /* git branch name, if available */ + if (showbranch) { +#if 1 /*#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED)*/ + altname = nomakedefs.git_branch; +#endif + if (!altname || !*altname) + showbranch = FALSE; + } + if (showname && showbranch) { + if (!strncmpi(name, altname, strlen(name))) + showname = FALSE; +#if 0 + /* note: it's possible for branch name to be a prefix of game name + but that's unlikely enough that we won't bother with it; having + branch "nethack-5.0" be a superset of game "nethack" seems like + including both is redundant, but having branch "net" be a subset + of game "nethack" doesn't feel that way; optimizing "net" out + seems like it would be a mistake */ + else if (!strncmpi(altname, name, strlen(altname))) + showbranch = FALSE; +#endif + } else if (!showname && !showbranch) { + /* flags.versinfo could be set to only 'branch' but it might not + be available */ + shownum = TRUE; + } + + *buf = '\0'; + indentation = indent ? " " : ""; + if (showname) { + Snprintf(eos(buf), bufsz - strlen(buf), "%s%s", indentation, name); + indentation = " "; /* forced separator rather than optional indent */ + } + if (showbranch) { + Snprintf(eos(buf), bufsz - strlen(buf), "%s%s", indentation, altname); + indentation = " "; + } + if (shownum) { + /* x.y.z version number */ + Snprintf(eos(buf), bufsz - strlen(buf), "%s%s", indentation, + (nomakedefs.version_string && nomakedefs.version_string[0]) + ? nomakedefs.version_string + : mdlib_version_string(buf, ".")); + } return buf; } -/* the 'v' command */ +/* the #versionshort command */ int -doversion() +doversion(void) { char buf[BUFSZ]; - pline("%s", getversionstring(buf)); - return 0; + if (iflags.menu_requested) + return doextversion(); + + pline("%s", getversionstring(buf, sizeof buf)); + return ECMD_OK; } /* the '#version' command; also a choice for '?' */ int -doextversion() +doextversion(void) { - dlb *f; + int rtcontext = 0; + const char *rtbuf; + dlb *f = (dlb *) 0; char buf[BUFSZ], *p = 0; winid win = create_nhwindow(NHW_TEXT); + boolean use_dlb = TRUE, + done_rt = FALSE, + done_dlb = FALSE, + prolog; + /* lua_info[] moved to util/mdlib.c and rendered via do_runtime_info() */ + +#if defined(OPTIONS_AT_RUNTIME) + use_dlb = FALSE; +#else + done_rt = TRUE; +#endif /* instead of using ``display_file(OPTIONS_USED,TRUE)'' we handle the file manually so we can include dynamic version info */ - (void) getversionstring(buf); - /* if extra text (git info) is present, put it on separate line */ + (void) getversionstring(buf, sizeof buf); + /* if extra text (git info) is present, put it on separate line + but don't wrap on (x86) */ if (strlen(buf) >= COLNO) - p = rindex(buf, '('); - if (p && p > buf && p[-1] == ' ') + p = strrchr(buf, '('); + if (p && p > buf && p[-1] == ' ' && p[1] != 'x') p[-1] = '\0'; else p = 0; @@ -116,86 +203,108 @@ doextversion() putstr(win, 0, p); } - f = dlb_fopen(OPTIONS_USED, "r"); - if (!f) { - putstr(win, 0, ""); - Sprintf(buf, "[Configuration '%s' not available?]", OPTIONS_USED); - putstr(win, 0, buf); - } else { - /* - * already inserted above: - * + outdented program name and version plus build date and time - * dat/options; display contents with lines prefixed by '-' deleted: - * - blank-line - * - indented program name and version - * blank-line - * outdented feature header - * - blank-line - * indented feature list - * spread over multiple lines - * blank-line - * outdented windowing header - * - blank-line - * indented windowing choices with - * optional second line for default - * - blank-line - * - EOF - */ - boolean prolog = TRUE; /* to skip indented program name */ - - while (dlb_fgets(buf, BUFSZ, f)) { - (void) strip_newline(buf); - if (index(buf, '\t') != 0) - (void) tabexpand(buf); - - if (*buf && *buf != ' ') { - /* found outdented header; insert a separator since we'll - have skipped corresponding blank line inside the file */ - putstr(win, 0, ""); - prolog = FALSE; + if (use_dlb) { + f = dlb_fopen(OPTIONS_USED, "r"); + if (!f) { + putstr(win, 0, ""); + Sprintf(buf, "[Configuration '%s' not available?]", OPTIONS_USED); + putstr(win, 0, buf); + done_dlb = TRUE; + } + } + /* + * already inserted above: + * + outdented program name and version plus build date and time + * dat/options; display contents with lines prefixed by '-' deleted: + * - blank-line + * - indented program name and version + * blank-line + * outdented feature header + * - blank-line + * indented feature list + * spread over multiple lines + * blank-line + * outdented windowing header + * - blank-line + * indented windowing choices with + * optional second line for default + * - blank-line + * - EOF + */ + + prolog = TRUE; /* to skip indented program name */ + for (;;) { + if (use_dlb && !done_dlb) { + if (!dlb_fgets(buf, BUFSZ, f)) { + done_dlb = TRUE; + continue; } - /* skip blank lines and prolog (progame name plus version) */ - if (prolog || !*buf) + } else if (!done_rt) { + if (!(rtbuf = do_runtime_info(&rtcontext))) { + done_rt = TRUE; continue; + } + (void) strncpy(buf, rtbuf, BUFSZ - 1); + buf[BUFSZ - 1] = '\0'; + } else { + break; + } + (void) strip_newline(buf); + if (strchr(buf, '\t') != 0) + (void) tabexpand(buf); + + if (*buf && *buf != ' ') { + /* found outdented header; insert a separator since we'll + have skipped corresponding blank line inside the file */ + putstr(win, 0, ""); + prolog = FALSE; + } + /* skip blank lines and prolog (progame name plus version) */ + if (prolog || !*buf) + continue; - if (index(buf, ':')) - insert_rtoption(buf); + if (strchr(buf, ':')) + insert_rtoption(buf); - if (*buf) - putstr(win, 0, buf); - } - (void) dlb_fclose(f); - display_nhwindow(win, FALSE); - destroy_nhwindow(win); + if (*buf) + putstr(win, 0, buf); } - return 0; + if (use_dlb) + (void) dlb_fclose(f); + display_nhwindow(win, FALSE); + destroy_nhwindow(win); + return ECMD_OK; } -void early_version_info(pastebuf) -boolean pastebuf; +void +early_version_info(boolean pastebuf) { - char buf[BUFSZ], buf2[BUFSZ]; - char *tmp = getversionstring(buf); + char buf1[BUFSZ], buf2[BUFSZ]; + char *buf, *tmp; - /* this is early enough that we have to do - our own line-splitting */ + Snprintf(buf1, sizeof buf1, "test"); + /* this is early enough that we have to do our own line-splitting */ + getversionstring(buf1, sizeof buf1); + tmp = strstri(buf1, " ("); /* split at start of version info */ if (tmp) { - tmp = strstri(buf," ("); - if (tmp) *tmp++ = '\0'; + /* retain one buffer so that it all goes into the paste buffer */ + *tmp++ = '\0'; + Snprintf(buf2, sizeof (buf2),"%s\n%s", buf1, tmp); + buf = buf2; + } else { + buf = buf1; } - Sprintf(buf2, "%s\n", buf); - if (tmp) Sprintf(eos(buf2), "%s\n", tmp); - raw_printf("%s", buf2); + raw_printf("%s", buf); if (pastebuf) { -#ifdef RUNTIME_PASTEBUF_SUPPORT +#if defined(RUNTIME_PASTEBUF_SUPPORT) && !defined(LIBNH) /* * Call a platform/port-specific routine to insert the * version information into a paste buffer. Useful for * easy inclusion in bug reports. */ - port_insert_pastebuf(buf2); + port_insert_pastebuf(buf); #else raw_printf("%s", "Paste buffer copy is not available.\n"); #endif @@ -216,6 +325,8 @@ static struct rt_opt { const char *token, *value; } rt_opts[] = { { ":PATMATCH:", regex_id }, + { ":LUAVERSION:", (const char *) gl.lua_ver }, + { ":LUACOPYRIGHT:", (const char *) gl.lua_copyright }, }; /* @@ -224,15 +335,18 @@ static struct rt_opt { * it depends which of several object files got linked into the * game image, so we insert those options here. */ -STATIC_OVL void -insert_rtoption(buf) -char *buf; +staticfn void +insert_rtoption(char *buf) { int i; + if (!gl.lua_ver[0]) + get_lua_version(); + for (i = 0; i < SIZE(rt_opts); ++i) { - if (strstri(buf, rt_opts[i].token)) + if (strstri(buf, rt_opts[i].token) && *rt_opts[i].value) { (void) strsubst(buf, rt_opts[i].token, rt_opts[i].value); + } /* we don't break out of the loop after a match; there might be other matches on the same line */ } @@ -240,101 +354,81 @@ char *buf; #ifdef MICRO boolean -comp_times(filetime) -long filetime; +comp_times(long filetime) { /* BUILD_TIME is constant but might have L suffix rather than UL; 'filetime' is historically signed but ought to have been unsigned */ - return (boolean) ((unsigned long) filetime < (unsigned long) BUILD_TIME); + return ((unsigned long) filetime < (unsigned long) nomakedefs.build_time); } #endif +#endif /* !SFCTOOL */ + +#ifdef SFCTOOL +#ifdef wait_synch +#undef wait_synch +#endif +#define wait_synch() +#endif /* SFCTOOL */ boolean -check_version(version_data, filename, complain) -struct version_info *version_data; -const char *filename; -boolean complain; +check_version( + struct version_info *version_data, + const char *filename, + boolean complain, + unsigned long utdflags) { + if (!filename) { +#ifdef EXTRA_SANITY_CHECKS + if (complain) + impossible("check_version() called with" + " 'complain'=True but 'filename'=Null"); +#endif + complain = FALSE; /* 'complain' requires 'filename' for pline("%s") */ + } + if ((version_data->feature_set & SFCTOOL_BIT) != 0) { + gc.converted_savefile_loaded = TRUE; + version_data->feature_set &= ~(SFCTOOL_BIT); + } if ( -#ifdef VERSION_COMPATIBILITY +#ifdef VERSION_COMPATIBILITY /* patchlevel.h */ version_data->incarnation < VERSION_COMPATIBILITY - || version_data->incarnation > VERSION_NUMBER + || version_data->incarnation > nomakedefs.version_number #else - version_data->incarnation != VERSION_NUMBER + version_data->incarnation != nomakedefs.version_number #endif ) { - if (complain) +#ifndef SFCTOOL + if (complain) { pline("Version mismatch for file \"%s\".", filename); + if (WIN_MESSAGE != WIN_ERR) + display_nhwindow(WIN_MESSAGE, TRUE); + } +#endif return FALSE; } else if ( -#ifndef IGNORED_FEATURES - version_data->feature_set != VERSION_FEATURES -#else - (version_data->feature_set & ~IGNORED_FEATURES) - != (VERSION_FEATURES & ~IGNORED_FEATURES) -#endif - || version_data->entity_count != VERSION_SANITY1 - || version_data->struct_sizes1 != VERSION_SANITY2 - || version_data->struct_sizes2 != VERSION_SANITY3) { - if (complain) + (version_data->feature_set & ~nomakedefs.ignored_features) + != (nomakedefs.version_features & ~nomakedefs.ignored_features) + || ((utdflags & UTD_SKIP_SANITY1) == 0 + && version_data->entity_count != nomakedefs.version_sanity1) + ) { +#ifndef SFCTOOL + if (complain) { pline("Configuration incompatibility for file \"%s\".", filename); - return FALSE; - } - return TRUE; -} - -/* this used to be based on file date and somewhat OS-dependant, - but now examines the initial part of the file's contents */ -boolean -uptodate(fd, name) -int fd; -const char *name; -{ - int rlen; - struct version_info vers_info; - boolean verbose = name ? TRUE : FALSE; - - rlen = read(fd, (genericptr_t) &vers_info, sizeof vers_info); - minit(); /* ZEROCOMP */ - if (rlen == 0) { - if (verbose) { - pline("File \"%s\" is empty?", name); - wait_synch(); + display_nhwindow(WIN_MESSAGE, TRUE); } - return FALSE; - } - if (!check_version(&vers_info, name, verbose)) { - if (verbose) - wait_synch(); +#endif return FALSE; } return TRUE; } -void -store_version(fd) -int fd; -{ - static const struct version_info version_data = { - VERSION_NUMBER, VERSION_FEATURES, - VERSION_SANITY1, VERSION_SANITY2, VERSION_SANITY3 - }; - - bufoff(fd); - /* bwrite() before bufon() uses plain write() */ - bwrite(fd, (genericptr_t) &version_data, - (unsigned) (sizeof version_data)); - bufon(fd); - return; -} - +#ifndef SFCTOOL #ifdef AMIGA const char amiga_version_string[] = AMIGA_VERSION_STRING; #endif unsigned long -get_feature_notice_ver(str) -char *str; +get_feature_notice_ver(char *str) { char buf[BUFSZ]; int ver_maj, ver_min, patch; @@ -352,7 +446,7 @@ char *str; istr[j] = str; if (j == 2) break; - } else if (index("0123456789", *str) != 0) { + } else if (strchr("0123456789", *str) != 0) { str++; } else return 0L; @@ -367,15 +461,14 @@ char *str; } unsigned long -get_current_feature_ver() +get_current_feature_ver(void) { return FEATURE_NOTICE_VER(VERSION_MAJOR, VERSION_MINOR, PATCHLEVEL); } /*ARGUSED*/ const char * -copyright_banner_line(indx) -int indx; +copyright_banner_line(int indx) { #ifdef COPYRIGHT_BANNER_A if (indx == 1) @@ -385,10 +478,10 @@ int indx; if (indx == 2) return COPYRIGHT_BANNER_B; #endif -#ifdef COPYRIGHT_BANNER_C + if (indx == 3) - return COPYRIGHT_BANNER_C; -#endif + return nomakedefs.copyright_banner_c; + #ifdef COPYRIGHT_BANNER_D if (indx == 4) return COPYRIGHT_BANNER_D; @@ -396,4 +489,371 @@ int indx; return ""; } +/* called by argcheck(allmain.c) from early_options(sys/xxx/xxxmain.c) */ +void +dump_version_info(void) +{ + char buf[BUFSZ]; + const char *hname = gh.hname ? gh.hname : "nethack"; + + if (strlen(hname) > 33) + hname = eos(nhStr(hname)) - 33; /* discard const for eos() */ + runtime_info_init(); + Snprintf(buf, sizeof buf, "%-12.33s %08lx %08lx %08lx", + hname, + nomakedefs.version_number, + (nomakedefs.version_features & ~nomakedefs.ignored_features), + nomakedefs.version_sanity1); + raw_print(buf); + release_runtime_info(); + return; +} +void +store_version(NHFILE *nhfp) +{ + struct version_info version_data = { + 0UL, + 0UL, + 0UL, + }; + /* actual version number */ + version_data.incarnation = nomakedefs.version_number; + /* bitmask of config settings */ + version_data.feature_set = nomakedefs.version_features; + /* # of monsters and objects */ + version_data.entity_count = nomakedefs.version_sanity1; + + /* bwrite() before bufon() uses plain write() */ + if (nhfp->structlevel) + bufoff(nhfp->fd); + + store_critical_bytes(nhfp); + Sfo_version_info(nhfp, (struct version_info *) &version_data, + "version_info"); + + if (nhfp->structlevel) + bufon(nhfp->fd); + return; +} +#endif /* !SFCTOOL */ +#endif /* MINIMAL_FOR_RECOVER */ + +struct critical_sizes_with_names { + uchar ucsize; + const char *nm; +}; + +struct critical_sizes_with_names critical_sizes[] = { + { 0, "unused" }, + /* simple types, that don't have subfields */ + { (uchar) sizeof(short), "short" }, + { (uchar) sizeof(int), "int" }, + { (uchar) sizeof(long), "long" }, + { (uchar) sizeof(long long), "long long" }, + { (uchar) sizeof(genericptr_t), "genericptr_t" }, + { (uchar) sizeof(aligntyp), "aligntyp" }, + { (uchar) sizeof(boolean), "boolean" }, + { (uchar) sizeof(coordxy), "coordxy" }, + { (uchar) sizeof(int16), "int16" }, + { (uchar) sizeof(int32), "int32" }, + { (uchar) sizeof(int64), "int64" }, + { (uchar) sizeof(schar), "schar" }, + { (uchar) sizeof(size_t), "size_t" }, + { (uchar) sizeof(uchar), "uchar" }, + { (uchar) sizeof(uint16), "uint16" }, + { (uchar) sizeof(uint32), "uint32" }, + { (uchar) sizeof(uint64), "uint64" }, + { (uchar) sizeof(ulong), "ulong" }, + { (uchar) sizeof(unsigned), "unsigned" }, + { (uchar) sizeof(ushort), "ushort" }, + { (uchar) sizeof(xint16), "xint16" }, + { (uchar) sizeof(xint8), "xint8" }, + /* complex - they break down into one or more simple types */ + { (uchar) sizeof(struct arti_info), "struct arti_info" }, + { (uchar) sizeof(struct nhrect), "struct nhrect" }, + { (uchar) sizeof(struct branch), "struct branch" }, + { (uchar) sizeof(struct bubble), "struct bubble" }, + { (uchar) sizeof(struct cemetery), "struct cemetery" }, + { (uchar) sizeof(struct context_info), "struct context_info" }, + { (uchar) sizeof(struct nhcoord), "struct nhcoord" }, + { (uchar) sizeof(struct damage), "struct damage" }, + { (uchar) sizeof(struct dest_area), "struct dest_area" }, + { (uchar) sizeof(struct dgn_topology), "struct dgn_topology" }, + { (uchar) sizeof(struct dungeon), "struct dungeon" }, + { (uchar) sizeof(struct d_level), "struct d_level" }, + { (uchar) sizeof(struct ebones), "struct ebones" }, + { (uchar) sizeof(struct edog), "struct edog" }, + { (uchar) sizeof(struct egd), "struct egd" }, + { (uchar) sizeof(struct emin), "struct emin" }, + { (uchar) sizeof(struct engr), "struct engr" }, + { (uchar) sizeof(struct epri), "struct epri" }, + { (uchar) sizeof(struct eshk), "struct eshk" }, + { (uchar) sizeof(struct fe), "struct fe" }, + { (uchar) sizeof(struct flag), "struct flag" }, + { (uchar) sizeof(struct fruit), "struct fruit" }, + { (uchar) sizeof(struct gamelog_line), "struct gamelog_line" }, + { (uchar) sizeof(struct kinfo), "struct kinfo" }, + { (uchar) sizeof(struct levelflags), "struct levelflags" }, + { (uchar) sizeof(struct ls_t), "struct ls_t" }, + { (uchar) sizeof(struct linfo), "struct linfo" }, + { (uchar) sizeof(struct mapseen_feat), "struct mapseen_feat" }, + { (uchar) sizeof(struct mapseen_flags), "struct mapseen_flags" }, + { (uchar) sizeof(struct mapseen_rooms), "struct mapseen_rooms" }, + { (uchar) sizeof(struct mextra), "struct mextra" }, + { (uchar) sizeof(struct mkroom), "struct mkroom" }, + { (uchar) sizeof(struct monst), "struct monst" }, + { (uchar) sizeof(struct mvitals), "struct mvitals" }, + { (uchar) sizeof(struct obj), "struct obj" }, + { (uchar) sizeof(struct objclass), "struct objclass" }, + { (uchar) sizeof(struct oextra), "struct oextra" }, + { (uchar) sizeof(struct q_score), "struct q_score" }, + { (uchar) sizeof(struct rm), "struct rm" }, + { (uchar) sizeof(struct spell), "struct spell" }, + { (uchar) sizeof(struct stairway), "struct stairway" }, + { (uchar) sizeof(struct s_level), "struct s_level" }, + { (uchar) sizeof(struct trap), "struct trap" }, + { (uchar) sizeof(struct version_info), "struct version_info" }, + { (uchar) sizeof(anything), "anything" }, + /* struct you requires 2 bytes */ + { (uchar) ((sizeof(struct you) & 0x00FF)), "you_LO" }, + { (uchar) ((sizeof(struct you) & 0xFF00) >> 8), "you_HI" }, +#ifdef SF_INCLUDE_SUBSTRUCTS + /* + * the ones below are substructures of the ones + * above, so there is no need to check these directly. + */ + { (uchar) sizeof(struct attribs), "struct attribs" }, + { (uchar) sizeof(struct dig_info), "struct dig_info" }, + { (uchar) sizeof(struct tin_info), "struct tin_info" }, + { (uchar) sizeof(struct book_info), "struct book_info" }, + { (uchar) sizeof(struct takeoff_info), "struct takeoff_info" }, + { (uchar) sizeof(struct victual_info), "struct victual_info" }, + { (uchar) sizeof(struct engrave_info), "struct engrave_info" }, + { (uchar) sizeof(struct warntype_info), "struct warntype_info" }, + { (uchar) sizeof(struct polearm_info), "struct polearm_info" }, + { (uchar) sizeof(struct obj_split), "struct obj_split" }, + { (uchar) sizeof(struct tribute_info), "struct tribute_info" }, + { (uchar) sizeof(struct novel_tracking), "struct novel_tracking" }, + { (uchar) sizeof(struct achievement_tracking), + "struct achievement_tracking" }, + { (uchar) sizeof(struct d_flags), "struct d_flags" }, + { (uchar) sizeof(struct mapseen), "struct mapseen" }, + { (uchar) sizeof(struct fakecorridor), "struct fakecorridor" }, + { (uchar) sizeof(struct bill_x), "struct bill_x" }, + { (uchar) sizeof(union vptrs), "union vptrs" }, + { (uchar) sizeof(struct prop), "struct prop" }, + { (uchar) sizeof(struct skills), "struct skills" }, + { (uchar) sizeof(union vlaunchinfo), "union vlaunchinfo" }, + { (uchar) sizeof(struct u_have), "struct u_have" }, + { (uchar) sizeof(struct u_event), "struct u_event" }, + { (uchar) sizeof(struct u_realtime), "struct u_realtime" }, + { (uchar) sizeof(struct u_conduct), "struct u_conduct" }, + { (uchar) sizeof(struct u_roleplay), "struct u_roleplay" }, +#endif /* SF_INCLUDE_SUBSTRUCTS */ + /* 10 for future expansion without changing array size */ + { 0, "" }, + { 0, "" }, + { 0, "" }, + { 0, "" }, + { 0, "" }, + { 0, "" }, + { 0, "" }, + { 0, "" }, + { 0, "" }, + { 0, "" }, +}; + +uchar cscbuf[SIZE(critical_sizes)]; + +int +get_critical_size_count(void) +{ + return SIZE(critical_sizes); +} + +#ifndef MINIMAL_FOR_RECOVER +void +store_critical_bytes(NHFILE *nhfp) +{ + int i, cnt; + char indicate = 'u', csc_count = (char) SIZE(critical_sizes); + /* int cmc = 0; */ + + if (nhfp->mode & WRITING) { + indicate = (nhfp->structlevel) ? 'h' + : (nhfp->fnidx == exportascii) + ? 'a' + : '?'; + Sfo_char(nhfp, &indicate, "indicate-format", 1); + Sfo_char(nhfp, &csc_count, "count-critical_sizes", 1); + cnt = (int) csc_count; + for (i = 0; i < cnt; ++i) { + Sfo_uchar(nhfp, &critical_sizes[i].ucsize, "critical_sizes"); + } + } +} + +/* this used to be based on file date and somewhat OS-dependent, + * but now examines the initial part of the file's contents. + * + * returns: + * + * SF_UPTODATE (0) everything matched and looks good + * SF_OUTDATED (1) savefile is outdated + * SF_CRITICAL_BYTE_COUNT_MISMATCH (2) critical size count mismatch + * SF_DM_IL32LLP64_ON_ILP32LL64 (3) Windows x64 savefile on x86 + * SF_DM_I32LP64_ON_ILP32LL64 (4) Unix 64 savefile on x86 + * SF_DM_ILP32LL64_ON_I32LP64 (5) x86 savefile on Unix 64 + * SF_DM_ILP32LL64_ON_IL32LLP64 (6) x86 savefile on Windows x64 + * SF_DM_I32LP64_ON_IL32LLP64 (7) Unix 64 savefile on Windows x64 + * SF_DM_IL32LLP64_ON_I32LP64 (8) Windows x64 savefile on Unix 64 + * SF_DM_MISMATCH (9) some other mismatch + */ +int +uptodate(NHFILE *nhfp, const char *name, unsigned long utdflags) +{ +#ifdef SFCTOOL + extern struct version_info vers_info; +#else + struct version_info vers_info; +#endif + char indicator; + int sfstatus = 0, idx_1st_mismatch = 0; + boolean quietly = (utdflags & UTD_QUIETLY) != 0; + boolean verbose = name ? TRUE : FALSE; + + Sfi_char(nhfp, &indicator, "indicate-format", 1); + if ((sfstatus = compare_critical_bytes(nhfp, &idx_1st_mismatch, utdflags)) + != SF_UPTODATE) { + if (sfstatus > 0 && idx_1st_mismatch) { + if (!quietly) + raw_printf("comparison of critical bytes mismatched at %d (%s).", + critical_sizes[idx_1st_mismatch].ucsize, + critical_sizes[idx_1st_mismatch].nm); + } + } + + Sfi_version_info(nhfp, &vers_info, "version_info"); + if (!check_version(&vers_info, name, verbose, utdflags)) { + if (verbose) { + if ((utdflags & UTD_WITHOUT_WAITSYNCH_PERFILE) == 0) { + wait_synch(); + } + } + return SF_OUTDATED; + } + return sfstatus; +} + +/* + * returns: + * + * SF_UPTODATE (0) everything matched and looks good + * SF_OUTDATED (1) savefile is outdated + * SF_CRITICAL_BYTE_COUNT_MISMATCH (2) critical size count mismatch + * SF_DM_IL32LLP64_ON_ILP32LL64 (3) Windows x64 savefile on x86 + * SF_DM_I32LP64_ON_ILP32LL64 (4) Unix 64 savefile on x86 + * SF_DM_ILP32LL64_ON_I32LP64 (5) x86 savefile on Unix 64 + * SF_DM_ILP32LL64_ON_IL32LLP64 (6) x86 savefile on Windows x64 + * SF_DM_I32LP64_ON_IL32LLP64 (7) Unix 64 savefile on Windows x64 + * SF_DM_IL32LLP64_ON_I32LP64 (8) Windows x64 savefile on Unix 64 + * SF_DM_MISMATCH (9) some other mismatch + */ +int +compare_critical_bytes(NHFILE *nhfp, int *idx_1st_mismatch, unsigned long utdflags) +{ + char active_csc_count = (char) SIZE(critical_sizes), + file_csc_count; + int i, cnt = (int) active_csc_count, + dmmismatch = SF_DM_MISMATCH; + boolean quietly = (utdflags & UTD_QUIETLY) != 0; + + Sfi_char(nhfp, &file_csc_count, "count-critical_sizes", 1); + if (file_csc_count > cnt) { + if (!quietly) + raw_printf("critical byte counts do not match" + ", file:%d, critical_sizes:%d.", + file_csc_count, SIZE(critical_sizes)); + return SF_CRITICAL_BYTE_COUNT_MISMATCH; + } + for (i = 0; i < (int) file_csc_count; ++i) { + Sfi_uchar(nhfp, &cscbuf[i], "critical_sizes"); + } + for (i = 1; i < cnt; ++i) { + if (cscbuf[i] != critical_sizes[i].ucsize) { + const char *dm = datamodel(0), *dmfile; + + dmfile = what_datamodel_is_this(0, + cscbuf[1], /* short */ + cscbuf[2], /* int */ + cscbuf[3], /* long */ + cscbuf[4], /* long long */ + cscbuf[5]); /* ptr */ + + if (!strcmp(dmfile, "IL32LLP64") && !strcmp(dm, "ILP32LL64")) { + /* Windows x64 savefile on x86 */ + dmmismatch = SF_DM_IL32LLP64_ON_ILP32LL64; + } else if (!strcmp(dmfile, "I32LP64") + && !strcmp(dm, "ILP32LL64")) { + /* Unix 64 savefile on x86*/ + dmmismatch = SF_DM_I32LP64_ON_ILP32LL64; + } else if (!strcmp(dmfile, "ILP32LL64") + && !strcmp(dm, "I32LP64")) { + /* x86 savefile on Unix 64 */ + dmmismatch = SF_DM_ILP32LL64_ON_I32LP64; + } else if (!strcmp(dmfile, "ILP32LL64") + && !strcmp(dm, "IL32LLP64")) { + /* x86 savefile on Windows x64 */ + dmmismatch = SF_DM_ILP32LL64_ON_IL32LLP64; + } else if (!strcmp(dmfile, "I32LP64") + && !strcmp(dm, "IL32LLP64")) { + /* Unix 64 savefile on Windows x64 */ + dmmismatch = SF_DM_I32LP64_ON_IL32LLP64; + } else if (!strcmp(dmfile, "IL32LLP64") + && !strcmp(dm, "I32LP64")) { + /* Windows x64 savefile on Unix 64 */ + dmmismatch = SF_DM_IL32LLP64_ON_I32LP64; + } + if (idx_1st_mismatch) + *idx_1st_mismatch = i; + return dmmismatch; + } + } + return SF_UPTODATE; /* everything matched */ +} + +/* + * returns: + * + * SF_UPTODATE (0) everything matched and looks good + * SF_OUTDATED (1) savefile is outdated + * SF_CRITICAL_BYTE_COUNT_MISMATCH (2) critical size count mismatch + * SF_DM_IL32LLP64_ON_ILP32LL64 (3) Windows x64 savefile on x86 + * SF_DM_I32LP64_ON_ILP32LL64 (4) Unix 64 savefile on x86 + * SF_DM_ILP32LL64_ON_I32LP64 (5) x86 savefile on Unix 64 + * SF_DM_ILP32LL64_ON_IL32LLP64 (6) x86 savefile on Windows x64 + * SF_DM_I32LP64_ON_IL32LLP64 (7) Unix 64 savefile on Windows x64 + * SF_DM_IL32LLP64_ON_I32LP64 (8) Windows x64 savefile on Unix 64 + * SF_DM_MISMATCH (9) some other mismatch + */ +int +validate(NHFILE *nhfp, const char *name, boolean without_waitsynch_perfile) +{ + unsigned long utdflags = 0L; + int validsf = 0; + +#ifdef SFCTOOL + utdflags |= UTD_QUIETLY; +#endif + if (nhfp->structlevel) + utdflags |= UTD_CHECKSIZES; + if (without_waitsynch_perfile) + utdflags |= UTD_WITHOUT_WAITSYNCH_PERFILE; + if (nhfp->fieldlevel) + utdflags |= UTD_CHECKFIELDCOUNTS | UTD_SKIP_SANITY1; + validsf = uptodate(nhfp, name, utdflags); + return validsf; +} +#endif /* MINIMAL_FOR_RECOVER */ + /*version.c*/ diff --git a/src/vision.c b/src/vision.c index b8ce2e507..b640c6d3b 100644 --- a/src/vision.c +++ b/src/vision.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 vision.c $NHDT-Date: 1448013598 2015/11/20 09:59:58 $ $NHDT-Branch: master $:$NHDT-Revision: 1.27 $ */ +/* NetHack 5.0 vision.c $NHDT-Date: 1777205478 2026/04/26 04:11:18 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.75 $ */ /* Copyright (c) Dean Luick, with acknowledgements to Dave Cohrs, 1990. */ /* NetHack may be freely redistributed. See license for details. */ @@ -22,91 +22,93 @@ * ....X +4 * @...X +4 * - */ -char circle_data[] = { - /* 0*/ 1, 1, - /* 2*/ 2, 2, 1, - /* 5*/ 3, 3, 2, 1, - /* 9*/ 4, 4, 4, 3, 2, - /* 14*/ 5, 5, 5, 4, 3, 2, - /* 20*/ 6, 6, 6, 5, 5, 4, 2, - /* 27*/ 7, 7, 7, 6, 6, 5, 4, 2, - /* 35*/ 8, 8, 8, 7, 7, 6, 6, 4, 2, - /* 44*/ 9, 9, 9, 9, 8, 8, 7, 6, 5, 3, - /* 54*/ 10, 10, 10, 10, 9, 9, 8, 7, 6, 5, 3, - /* 65*/ 11, 11, 11, 11, 10, 10, 9, 9, 8, 7, 5, 3, - /* 77*/ 12, 12, 12, 12, 11, 11, 10, 10, 9, 8, 7, 5, 3, - /* 90*/ 13, 13, 13, 13, 12, 12, 12, 11, 10, 10, 9, 7, 6, 3, - /*104*/ 14, 14, 14, 14, 13, 13, 13, 12, 12, 11, 10, 9, 8, 6, 3, - /*119*/ 15, 15, 15, 15, 14, 14, 14, 13, 13, 12, 11, 10, 9, 8, 6, 3, - /*135*/ 16 /* MAX_RADIUS+1; used to terminate range loops -dlc */ + * Externally referenced from light.c +*/ +const coordxy circle_data[] = { + /* 0*/ 0, + /* 1*/ 1, 1, + /* 3*/ 2, 2, 1, + /* 6*/ 3, 3, 2, 1, + /* 10*/ 4, 4, 4, 3, 2, + /* 15*/ 5, 5, 5, 4, 3, 2, + /* 21*/ 6, 6, 6, 5, 5, 4, 2, + /* 28*/ 7, 7, 7, 6, 6, 5, 4, 2, + /* 36*/ 8, 8, 8, 7, 7, 6, 6, 4, 2, + /* 45*/ 9, 9, 9, 9, 8, 8, 7, 6, 5, 3, + /* 55*/ 10, 10, 10, 10, 9, 9, 8, 7, 6, 5, 3, + /* 66*/ 11, 11, 11, 11, 10, 10, 9, 9, 8, 7, 5, 3, + /* 78*/ 12, 12, 12, 12, 11, 11, 10, 10, 9, 8, 7, 5, 3, + /* 91*/ 13, 13, 13, 13, 12, 12, 12, 11, 10, 10, 9, 7, 6, 3, + /*105*/ 14, 14, 14, 14, 13, 13, 13, 12, 12, 11, 10, 9, 8, 6, 3, + /*120*/ 15, 15, 15, 15, 14, 14, 14, 13, 13, 12, 11, 10, 9, 8, 6, 3, + /*136*/ 16 /* MAX_RADIUS+1; used to terminate range loops -dlc */ }; /* * These are the starting indexes into the circle_data[] array for a - * circle of a given radius. + * circle of a given radius. Radius 0 used to be unused, but is now + * used for a single point: temporary light source of a camera flash + * as it traverses its path. */ -char circle_start[] = { - /* */ 0, /* circles of radius zero are not used */ - /* 1*/ 0, - /* 2*/ 2, - /* 3*/ 5, - /* 4*/ 9, - /* 5*/ 14, - /* 6*/ 20, - /* 7*/ 27, - /* 8*/ 35, - /* 9*/ 44, - /*10*/ 54, - /*11*/ 65, - /*12*/ 77, - /*13*/ 90, - /*14*/ 104, - /*15*/ 119, +const coordxy circle_start[] = { + /* 0*/ 0, + /* 1*/ 1, + /* 2*/ 3, + /* 3*/ 6, + /* 4*/ 10, + /* 5*/ 15, + /* 6*/ 21, + /* 7*/ 28, + /* 8*/ 36, + /* 9*/ 45, + /*10*/ 55, + /*11*/ 66, + /*12*/ 78, + /*13*/ 91, + /*14*/ 105, + /*15*/ 120, }; /*==========================================================================*/ /* Vision (arbitrary line of sight) * =========================================*/ -/*------ global variables ------*/ - -#if 0 /* (moved to decl.c) */ -/* True if we need to run a full vision recalculation. */ -boolean vision_full_recalc = 0; - -/* Pointers to the current vision array. */ -char **viz_array; -#endif -char *viz_rmin, *viz_rmax; /* current vision cs bounds */ - /*------ local variables ------*/ -static char could_see[2][ROWNO][COLNO]; /* vision work space */ -static char *cs_rows0[ROWNO], *cs_rows1[ROWNO]; -static char cs_rmin0[ROWNO], cs_rmax0[ROWNO]; -static char cs_rmin1[ROWNO], cs_rmax1[ROWNO]; +static seenV could_see[2][ROWNO][COLNO]; /* vision work space */ +static seenV *cs_rows0[ROWNO], *cs_rows1[ROWNO]; +static coordxy cs_rmin0[ROWNO], cs_rmax0[ROWNO]; +static coordxy cs_rmin1[ROWNO], cs_rmax1[ROWNO]; static char viz_clear[ROWNO][COLNO]; /* vision clear/blocked map */ static char *viz_clear_rows[ROWNO]; -static char left_ptrs[ROWNO][COLNO]; /* LOS algorithm helpers */ -static char right_ptrs[ROWNO][COLNO]; +static coordxy left_ptrs[ROWNO][COLNO]; /* LOS algorithm helpers */ +static coordxy right_ptrs[ROWNO][COLNO]; /* Forward declarations. */ -STATIC_DCL void FDECL(fill_point, (int, int)); -STATIC_DCL void FDECL(dig_point, (int, int)); -STATIC_DCL void NDECL(view_init); -STATIC_DCL void FDECL(view_from, (int, int, char **, char *, char *, int, - void (*)(int, int, genericptr_t), - genericptr_t)); -STATIC_DCL void FDECL(get_unused_cs, (char ***, char **, char **)); -STATIC_DCL void FDECL(rogue_vision, (char **, char *, char *)); +staticfn void fill_point(int, int); +staticfn void dig_point(int, int); +staticfn void view_init(void); +staticfn void view_from(coordxy, coordxy, seenV **, coordxy *, coordxy *, int, + void (*)(coordxy, coordxy, genericptr_t), + genericptr_t); +staticfn void get_unused_cs(seenV ***, coordxy **, coordxy **); +staticfn void rogue_vision(seenV **, coordxy *, coordxy *); /* Macro definitions that I can't find anywhere. */ #define sign(z) ((z) < 0 ? -1 : ((z) ? 1 : 0)) #define v_abs(z) ((z) < 0 ? -(z) : (z)) /* don't use abs -- it may exist */ +/* expose viz_clear[][] for sanity checking */ +boolean +get_viz_clear(int x, int y) +{ + if (isok(x,y) && !viz_clear[y][x]) + return TRUE; + return FALSE; +} + /* * vision_init() * @@ -116,7 +118,7 @@ STATIC_DCL void FDECL(rogue_vision, (char **, char *, char *)); * or before a game restore. Else we die a horrible death. */ void -vision_init() +vision_init(void) { int i; @@ -128,58 +130,74 @@ vision_init() } /* Start out with cs0 as our current array */ - viz_array = cs_rows0; - viz_rmin = cs_rmin0; - viz_rmax = cs_rmax0; + gv.viz_array = cs_rows0; + gv.viz_rmin = cs_rmin0; + gv.viz_rmax = cs_rmax0; - vision_full_recalc = 0; + gv.vision_full_recalc = 0; (void) memset((genericptr_t) could_see, 0, sizeof(could_see)); - /* Initialize the vision algorithm (currently C or D). */ + /* Initialize the vision algorithm (currently C). */ view_init(); - -#ifdef VISION_TABLES - /* Note: this initializer doesn't do anything except guarantee that - * we're linked properly. - */ - vis_tab_init(); -#endif } /* * does_block() * - * Returns true if the level feature, object, or monster at (x,y) blocks - * sight. + * Returns 0 if nothing at (x,y) blocks sight, 1 if anything other than + * an opaque region (gas cloud rather than CLOUD terrain) blocks sight, + * or 2 if an opaque region blocks sight. [At present, the rest of the + * code makes no distinction between 1 and 2, just between 0 and non-0.] */ int -does_block(x, y, lev) -int x, y; -register struct rm *lev; +does_block(int x, int y, struct rm *lev) { struct obj *obj; struct monst *mon; +#ifdef DEBUG + /* set DEBUGFILES=seethru in environment to see through bubbles */ + if (gs.seethru == 0) { /* init once */ + gs.seethru = (wizard && explicitdebug("seethru")) ? 1 : -1; + } +#endif + /* Features that block . . */ - if (IS_ROCK(lev->typ) || lev->typ == TREE + if (IS_OBSTRUCTED(lev->typ) || lev->typ == TREE || (IS_DOOR(lev->typ) && (lev->doormask & (D_CLOSED | D_LOCKED | D_TRAPPED)))) return 1; - if (lev->typ == CLOUD || lev->typ == WATER - || (lev->typ == MOAT && Underwater)) +#ifdef DEBUG + if (gs.seethru != 1) { +#endif + if (lev->typ == CLOUD || IS_WATERWALL(lev->typ) || lev->typ == LAVAWALL + || (Underwater && is_moat(x, y))) return 1; +#ifdef DEBUG + } /* gs.seethru */ +#endif /* Boulders block light. */ - for (obj = level.objects[x][y]; obj; obj = obj->nexthere) + for (obj = svl.level.objects[x][y]; obj; obj = obj->nexthere) if (obj->otyp == BOULDER) return 1; - /* Mimics mimicing a door or boulder or ... block light. */ + /* Mimics mimicking a door or boulder or ... block light. */ if ((mon = m_at(x, y)) && (!mon->minvis || See_invisible) && is_lightblocker_mappear(mon)) return 1; +#ifdef DEBUG + if (gs.seethru != 1) { +#endif + /* Clouds (poisonous or not) block light. */ + if (visible_region_at(x, y)) + return 2; +#ifdef DEBUG + } /* gs.seethru */ +#endif + return 0; } @@ -190,16 +208,16 @@ register struct rm *lev; * level and the level monsters and objects are in place. */ void -vision_reset() +vision_reset(void) { int y; - register int x, i, dig_left, block; - register struct rm *lev; + int x, i, dig_left, block; + struct rm *lev; /* Start out with cs0 as our current array */ - viz_array = cs_rows0; - viz_rmin = cs_rmin0; - viz_rmax = cs_rmax0; + gv.viz_array = cs_rows0; + gv.viz_rmin = cs_rmin0; + gv.viz_rmax = cs_rmax0; (void) memset((genericptr_t) could_see, 0, sizeof(could_see)); @@ -212,7 +230,7 @@ vision_reset() block = TRUE; /* location (0,y) is always stone; it's !isok() */ lev = &levl[1][y]; for (x = 1; x < COLNO; x++, lev += ROWNO) - if (block != (IS_ROCK(lev->typ) || does_block(x, y, lev))) { + if (block != (IS_OBSTRUCTED(lev->typ) || does_block(x, y, lev))) { if (block) { for (i = dig_left; i < x; i++) { left_ptrs[y][i] = dig_left; @@ -242,8 +260,8 @@ vision_reset() } } - iflags.vision_inited = 1; /* vision is ready */ - vision_full_recalc = 1; /* we want to run vision_recalc() */ + iflags.vision_inited = TRUE; /* vision is ready */ + gv.vision_full_recalc = 1; /* we want to run vision_recalc() */ } /* @@ -252,15 +270,13 @@ vision_reset() * Called from vision_recalc() and at least one light routine. Get pointers * to the unused vision work area. */ -STATIC_OVL void -get_unused_cs(rows, rmin, rmax) -char ***rows; -char **rmin, **rmax; +staticfn void +get_unused_cs(seenV ***rows, coordxy **rmin, coordxy **rmax) { - register int row; - register char *nrmin, *nrmax; + int row; + coordxy *nrmin, *nrmax; - if (viz_array == cs_rows0) { + if (gv.viz_array == cs_rows0) { *rows = cs_rows1; *rmin = cs_rmin1; *rmax = cs_rmax1; @@ -274,11 +290,11 @@ char **rmin, **rmax; nrmin = *rmin; nrmax = *rmax; - (void) memset((genericptr_t) * *rows, 0, - ROWNO * COLNO); /* we see nothing */ + (void) memset((genericptr_t) **rows, 0, + ROWNO * COLNO * sizeof (seenV)); /* see nothing */ for (row = 0; row < ROWNO; row++) { /* set row min & max */ *nrmin++ = COLNO - 1; - *nrmax++ = 0; + *nrmax++ = 1; } } @@ -294,24 +310,22 @@ char **rmin, **rmax; * We set the in_sight bit here as well to escape a bug that shows up * due to the one-sided lit wall hack. */ -STATIC_OVL void -rogue_vision(next, rmin, rmax) -char **next; /* could_see array pointers */ -char *rmin, *rmax; +staticfn void +rogue_vision(seenV **next, coordxy *rmin, coordxy *rmax) { int rnum = levl[u.ux][u.uy].roomno - ROOMOFFSET; /* no SHARED... */ int start, stop, in_door, xhi, xlo, yhi, ylo; - register int zx, zy; + int zx, zy; /* If in a lit room, we are able to see to its boundaries. */ /* If dark, set COULD_SEE so various spells work -dlc */ if (rnum >= 0) { - for (zy = rooms[rnum].ly - 1; zy <= rooms[rnum].hy + 1; zy++) { - rmin[zy] = start = rooms[rnum].lx - 1; - rmax[zy] = stop = rooms[rnum].hx + 1; + for (zy = svr.rooms[rnum].ly - 1; zy <= svr.rooms[rnum].hy + 1; zy++) { + rmin[zy] = start = svr.rooms[rnum].lx - 1; + rmax[zy] = stop = svr.rooms[rnum].hx + 1; for (zx = start; zx <= stop; zx++) { - if (rooms[rnum].rlit) { + if (svr.rooms[rnum].rlit) { next[zy][zx] = COULD_SEE | IN_SIGHT; levl[zx][zy].seenv = SVALL; /* see the walls */ } else @@ -353,7 +367,7 @@ char *rmin, *rmax; #ifdef EXTEND_SPINE -STATIC_DCL int FDECL(new_angle, (struct rm *, unsigned char *, int, int)); +staticfn int new_angle(struct rm *, unsigned char *, int, int); /* * new_angle() * @@ -385,7 +399,7 @@ STATIC_DCL int FDECL(new_angle, (struct rm *, unsigned char *, int, int)); * this is good enough. * * + When this function is called we don't have all of the seen - * information (we're doing a top down scan in vision_recalc). + * information (we're doing a top-down scan in vision_recalc). * We would need to scan once to set all IN_SIGHT and COULD_SEE * bits, then again to correctly set the seenv bits. * + I'm trying to make this as cheap as possible. The display @@ -396,13 +410,10 @@ STATIC_DCL int FDECL(new_angle, (struct rm *, unsigned char *, int, int)); * many exceptions. I may have to bite the bullet and do more * checks. - Dean 2/11/93 */ -STATIC_OVL int -new_angle(lev, sv, row, col) -struct rm *lev; -unsigned char *sv; -int row, col; +staticfn int +new_angle(struct rm *lev, unsigned char *sv, int row, int col) { - register int res = *sv; + int res = *sv; /* * Do extra checks for crosswalls and T walls if we see them from @@ -473,7 +484,7 @@ int row, col; * + After the monster move, before input from the player. [moveloop()] * + At end of moveloop. [moveloop() ??? not sure why this is here] * + Right before something is printed. [pline()] - * + Right before we do a vision based operation. [do_clear_area()] + * + Right before we do a vision-based operation. [do_clear_area()] * + screen redraw, so we can renew all positions in sight. [docrt()] * + When toggling temporary blindness, in case additional events * impacted by vision occur during the same move [make_blinded()] @@ -498,29 +509,28 @@ int row, col; * + Just before bubbles are moved. [movebubbles()] */ void -vision_recalc(control) -int control; +vision_recalc(int control) { - char **temp_array; /* points to the old vision array */ - char **next_array; /* points to the new vision array */ - char *next_row; /* row pointer for the new array */ - char *old_row; /* row pointer for the old array */ - char *next_rmin; /* min pointer for the new array */ - char *next_rmax; /* max pointer for the new array */ - char *ranges; /* circle ranges -- used for xray & night vision */ + extern const seenV seenv_matrix[3][3]; /* from display.c */ + static coordxy colbump[COLNO + 1]; /* cols to bump sv */ + seenV **temp_array; /* points to the old vision array */ + seenV **next_array; /* points to the new vision array */ + seenV *next_row; /* row pointer for the new array */ + seenV *old_row; /* row pointer for the old array */ + coordxy *next_rmin; /* min pointer for the new array */ + coordxy *next_rmax; /* max pointer for the new array */ + const coordxy *ranges; /* circle ranges -- used for xray & night vision */ int row = 0; /* row counter (outer loop) */ int start, stop; /* inner loop starting/stopping index */ int dx, dy; /* one step from a lit door or lit wall (see below) */ - register int col; /* inner loop counter */ - register struct rm *lev; /* pointer to current pos */ - struct rm *flev; /* pointer to position in "front" of current pos */ - extern unsigned char seenv_matrix[3][3]; /* from display.c */ - static unsigned char colbump[COLNO + 1]; /* cols to bump sv */ - unsigned char *sv; /* ptr to seen angle bits */ - int oldseenv; /* previous seenv value */ - - vision_full_recalc = 0; /* reset flag */ - if (in_mklev || !iflags.vision_inited) + int col; /* inner loop counter */ + struct rm *lev; /* pointer to current pos */ + struct rm *flev; /* pointer to position in "front" of current pos */ + const seenV *sv; /* ptr to seen angle bits */ + int oldseenv; /* previous seenv value */ + + gv.vision_full_recalc = 0; /* reset flag */ + if (gi.in_mklev || program_state.in_getlev || !iflags.vision_inited) return; /* @@ -543,27 +553,26 @@ int control; * * + Monsters to see with the "new" vision, even on the rogue * level. - * * + Monsters can see you even when you're in a pit. */ view_from(u.uy, u.ux, next_array, next_rmin, next_rmax, 0, - (void FDECL((*), (int, int, genericptr_t))) 0, + (void (*)(coordxy, coordxy, genericptr_t)) 0, (genericptr_t) 0); /* * Our own version of the update loop below. We know we can't see - * anything, so we only need update positions we used to be able + * anything, so we only need to update positions we used to be able * to see. */ - temp_array = viz_array; /* set viz_array so newsym() will work */ - viz_array = next_array; + temp_array = gv.viz_array; /* set gv.viz_array so newsym() will work */ + gv.viz_array = next_array; for (row = 0; row < ROWNO; row++) { old_row = temp_array[row]; /* Find the min and max positions on the row. */ - start = min(viz_rmin[row], next_rmin[row]); - stop = max(viz_rmax[row], next_rmax[row]); + start = min(gv.viz_rmin[row], next_rmin[row]); + stop = max(gv.viz_rmax[row], next_rmax[row]); for (col = start; col <= stop; col++) if (old_row[col] & IN_SIGHT) @@ -575,18 +584,19 @@ int control; } else if (Is_rogue_level(&u.uz)) { rogue_vision(next_array, next_rmin, next_rmax); } else { - int has_night_vision = 1; /* hero has night vision */ + int lo_col, has_night_vision = 1; /* hero has night vision */ if (Underwater && !Is_waterlevel(&u.uz)) { /* - * The hero is under water. Only see surrounding locations if + * The hero is underwater. Only see surrounding locations if * they are also underwater. This overrides night vision but * does not override x-ray vision. */ has_night_vision = 0; + lo_col = max(u.ux - 1, 1); for (row = u.uy - 1; row <= u.uy + 1; row++) - for (col = u.ux - 1; col <= u.ux + 1; col++) { + for (col = lo_col; col <= u.ux + 1; col++) { if (!isok(col, row) || !is_pool(col, row)) continue; @@ -603,7 +613,7 @@ int control; if (row >= ROWNO) break; - next_rmin[row] = max(0, u.ux - 1); + next_rmin[row] = max(1, u.ux - 1); next_rmax[row] = min(COLNO - 1, u.ux + 1); next_row = next_array[row]; @@ -612,7 +622,7 @@ int control; } } else view_from(u.uy, u.ux, next_array, next_rmin, next_rmax, 0, - (void FDECL((*), (int, int, genericptr_t))) 0, + (void (*)(coordxy, coordxy, genericptr_t)) 0, (genericptr_t) 0); /* @@ -631,11 +641,12 @@ int control; dy = v_abs(u.uy - row); next_row = next_array[row]; - start = max(0, u.ux - ranges[dy]); + start = max(1, u.ux - ranges[dy]); stop = min(COLNO - 1, u.ux + ranges[dy]); for (col = start; col <= stop; col++) { char old_row_val = next_row[col]; + next_row[col] |= IN_SIGHT; oldseenv = levl[col][row].seenv; levl[col][row].seenv = SVALL; /* see all! */ @@ -674,7 +685,7 @@ int control; dy = v_abs(u.uy - row); next_row = next_array[row]; - start = max(0, u.ux - ranges[dy]); + start = max(1, u.ux - ranges[dy]); stop = min(COLNO - 1, u.ux + ranges[dy]); for (col = start; col <= stop; col++) @@ -694,8 +705,8 @@ int control; /* * Make the viz_array the new array so that cansee() will work correctly. */ - temp_array = viz_array; - viz_array = next_array; + temp_array = gv.viz_array; + gv.viz_array = next_array; /* * The main update loop. Here we do two things: @@ -703,7 +714,7 @@ int control; * + Set the IN_SIGHT bit for places that we could see and are lit. * + Reset changed places. * - * There is one thing that make deciding what the hero can see + * There is one thing that makes deciding what the hero can see * difficult: * * 1. Directional lighting. Items that block light create problems. @@ -721,8 +732,8 @@ int control; old_row = temp_array[row]; /* Find the min and max positions on the row. */ - start = min(viz_rmin[row], next_rmin[row]); - stop = max(viz_rmax[row], next_rmax[row]); + start = min(gv.viz_rmin[row], next_rmin[row]); + stop = max(gv.viz_rmax[row], next_rmax[row]); lev = &levl[start][row]; sv = &seenv_matrix[dy + 1][start < u.ux ? 0 : (start > u.ux ? 2 : 1)]; @@ -750,10 +761,9 @@ int control; || IS_WALL(lev->typ)) && !viz_clear[row][col]) { /* * Make sure doors, walls, boulders or mimics don't show - * up - * at the end of dark hallways. We do this by checking + * up at the end of dark hallways. We do this by checking * the adjacent position. If it is lit, then we can see - * the door or wall, otherwise we can't. + * the door or wall; otherwise we can't. */ dx = u.ux - col; dx = sign(dx); @@ -796,7 +806,7 @@ int control; /* * At this point we know that the row position is *not* in normal - * sight. That is, the position is could be seen, but is dark + * sight. That is, the position could be seen, but is dark * or LOS is just plain blocked. * * Update the position if: @@ -804,22 +814,33 @@ int control; * the glyph -- E.g. darken room spot, etc. * o If we now could see the location (yet the location is not * lit), but previously we couldn't see the location, or vice - * versa. Update the spot because there there may be an + * versa. Update the spot because there may be an * infrared monster there. */ } else { - not_in_sight: + not_in_sight: if ((old_row[col] & IN_SIGHT) || ((next_row[col] & COULD_SEE) ^ (old_row[col] & COULD_SEE))) + { + /* + * TEMPORARY? Sometimes we get here with col==0 and + * newsym()'s impossible() for !isok() is being + * triggered, so avoid calling it for <0,y>; other bad + * coordinates will produce a panic() as they should. + */ + if (col != 0) + /* + */ newsym(col, row); + } } } /* end for col . . */ } /* end for row . . */ colbump[u.ux] = colbump[u.ux + 1] = 0; -skip: + skip: /* This newsym() caused a crash delivering msg about failure to open * dungeon file init_dungeons() -> panic() -> done(11) -> * vision_recalc(2) -> newsym() -> crash! u.ux and u.uy are 0 and @@ -829,10 +850,10 @@ int control; newsym(u.ux, u.uy); /* Make sure the hero shows up! */ /* Set the new min and max pointers. */ - viz_rmin = next_rmin; - viz_rmax = next_rmax; + gv.viz_rmin = next_rmin; + gv.viz_rmax = next_rmax; - recalc_mapseen(); + notice_all_mons(TRUE); } /* @@ -841,9 +862,19 @@ int control; * Make the location opaque to light. */ void -block_point(x, y) -int x, y; +block_point(int x, int y) { +#ifdef DEBUG + /* set DEBUGFILES=seethru in environment to see through clouds & water */ + if (gs.seethru == 0) { /* init once */ + gs.seethru = (wizard && explicitdebug("seethru")) ? 1 : -1; + } + if (gs.seethru == 1) { + if (!does_block(x, y, &levl[x][y])) + return; + } +#endif + fill_point(y, x); /* recalc light sources here? */ @@ -855,8 +886,8 @@ int x, y; * was out of night-vision range of the hero. Suddenly the hero should * see the lit room. */ - if (viz_array[y][x]) - vision_full_recalc = 1; + if (gv.viz_array[y][x]) + gv.vision_full_recalc = 1; } /* @@ -865,18 +896,27 @@ int x, y; * Make the location transparent to light. */ void -unblock_point(x, y) -int x, y; +unblock_point(int x, int y) { dig_point(y, x); /* recalc light sources here? */ - if (viz_array[y][x]) - vision_full_recalc = 1; + if (gv.viz_array[y][x]) + gv.vision_full_recalc = 1; } -/*==========================================================================*\ +/* recalc if point should be blocked or unblocked */ +void +recalc_block_point(coordxy x, coordxy y) +{ + if (does_block(x, y, &levl[x][y])) + block_point(x, y); + else + unblock_point(x, y); +} + +/*==========================================================================* \ : : : Everything below this line uses (y,x) instead of (x,y) --- the : : algorithms are faster if they are less recursive and can scan : @@ -921,11 +961,10 @@ int x, y; * + If you are a blocked spot, then your right will point to the * right-most blocked spot to your right that is connected to you. * This means that a right-edge (a blocked spot that has an open - * spot on its right) will point to itself. + * spot on its right) will point to itself. */ -STATIC_OVL void -dig_point(row, col) -int row, col; +staticfn void +dig_point(int row, int col) { int i; @@ -1008,9 +1047,8 @@ int row, col; } } -STATIC_OVL void -fill_point(row, col) -int row, col; +staticfn void +fill_point(int row, int col) { int i; @@ -1091,24 +1129,22 @@ int row, col; /*==========================================================================*/ /*==========================================================================*/ -/* Use either algorithm C or D. See the config.h for more details. - * =========*/ /* - * Variables local to both Algorithms C and D. + * Variables local to Algorithm C. */ static int start_row; static int start_col; static int step; -static char **cs_rows; -static char *cs_left; -static char *cs_right; +static seenV **cs_rows; +static coordxy *cs_left; +static coordxy *cs_right; -static void FDECL((*vis_func), (int, int, genericptr_t)); +static void (*vis_func)(coordxy, coordxy, genericptr_t); static genericptr_t varg; /* - * Both Algorithms C and D use the following macros. + * Algorithm C uses the following macros: * * good_row(z) - Return TRUE if the argument is a legal row. * set_cs(rowp,col) - Set the local could see array. @@ -1119,16 +1155,29 @@ static genericptr_t varg; * * The last three macros depend on having local pointers row_min, row_max, * and rowp being set correctly. + * + * The assertions are included to pacify a static source code analyzer. + * Compile with NDEBUG defined to suppress them. */ -#define set_cs(rowp, col) (rowp[col] = COULD_SEE) -#define good_row(z) ((z) >= 0 && (z) < ROWNO) -#define set_min(z) \ - if (*row_min > (z)) \ - *row_min = (z) -#define set_max(z) \ - if (*row_max < (z)) \ - *row_max = (z) #define is_clear(row, col) viz_clear_rows[row][col] +#define good_row(z) ((z) >= 0 && (z) < ROWNO) +#define set_cs(rowp, col) \ + do { \ + assert(rowp != NULL); \ + rowp[col] = COULD_SEE; \ + } while (0) +#define set_min(z) \ + do { \ + assert(row_min != NULL); \ + if (*row_min > (z)) \ + *row_min = (z); \ + } while (0) +#define set_max(z) \ + do { \ + assert(row_max != NULL); \ + if (*row_max < (z)) \ + *row_max = (z); \ + } while (0) /* * clear_path() expanded into 4 macros/functions: @@ -1163,7 +1212,7 @@ static genericptr_t varg; #define q1_path(srow, scol, y2, x2, label) \ { \ int dx, dy; \ - register int k, err, x, y, dxs, dys; \ + int k, err, x, y, dxs, dys; \ \ x = (scol); \ y = (srow); \ @@ -1211,7 +1260,7 @@ static genericptr_t varg; #define q4_path(srow, scol, y2, x2, label) \ { \ int dx, dy; \ - register int k, err, x, y, dxs, dys; \ + int k, err, x, y, dxs, dys; \ \ x = (scol); \ y = (srow); \ @@ -1260,7 +1309,7 @@ static genericptr_t varg; #define q2_path(srow, scol, y2, x2, label) \ { \ int dx, dy; \ - register int k, err, x, y, dxs, dys; \ + int k, err, x, y, dxs, dys; \ \ x = (scol); \ y = (srow); \ @@ -1308,7 +1357,7 @@ static genericptr_t varg; #define q3_path(srow, scol, y2, x2, label) \ { \ int dx, dy; \ - register int k, err, x, y, dxs, dys; \ + int k, err, x, y, dxs, dys; \ \ x = (scol); \ y = (srow); \ @@ -1353,10 +1402,10 @@ static genericptr_t varg; #else /* !MACRO_CPATH -- quadrants are really functions */ -STATIC_DCL int FDECL(_q1_path, (int, int, int, int)); -STATIC_DCL int FDECL(_q2_path, (int, int, int, int)); -STATIC_DCL int FDECL(_q3_path, (int, int, int, int)); -STATIC_DCL int FDECL(_q4_path, (int, int, int, int)); +staticfn int _q1_path(int, int, int, int); +staticfn int _q2_path(int, int, int, int); +staticfn int _q3_path(int, int, int, int); +staticfn int _q4_path(int, int, int, int); #define q1_path(sy, sx, y, x, dummy) result = _q1_path(sy, sx, y, x) #define q2_path(sy, sx, y, x, dummy) result = _q2_path(sy, sx, y, x) @@ -1366,12 +1415,11 @@ STATIC_DCL int FDECL(_q4_path, (int, int, int, int)); /* * Quadrant I (step < 0). */ -STATIC_OVL int -_q1_path(srow, scol, y2, x2) -int scol, srow, y2, x2; +staticfn int +_q1_path(int scol, int srow, int y2, int x2) { int dx, dy; - register int k, err, x, y, dxs, dys; + int k, err, x, y, dxs, dys; x = scol; y = srow; @@ -1414,12 +1462,11 @@ int scol, srow, y2, x2; /* * Quadrant IV (step > 0). */ -STATIC_OVL int -_q4_path(srow, scol, y2, x2) -int scol, srow, y2, x2; +staticfn int +_q4_path(int scol, int srow, int y2, int x2) { int dx, dy; - register int k, err, x, y, dxs, dys; + int k, err, x, y, dxs, dys; x = scol; y = srow; @@ -1462,12 +1509,11 @@ int scol, srow, y2, x2; /* * Quadrant II (step < 0). */ -STATIC_OVL int -_q2_path(srow, scol, y2, x2) -int scol, srow, y2, x2; +staticfn int +_q2_path(int scol, int srow, int y2, int x2) { int dx, dy; - register int k, err, x, y, dxs, dys; + int k, err, x, y, dxs, dys; x = scol; y = srow; @@ -1510,12 +1556,11 @@ int scol, srow, y2, x2; /* * Quadrant III (step > 0). */ -STATIC_OVL int -_q3_path(srow, scol, y2, x2) -int scol, srow, y2, x2; +staticfn int +_q3_path(int scol, int srow, int y2, int x2) { int dx, dy; - register int k, err, x, y, dxs, dys; + int k, err, x, y, dxs, dys; x = scol; y = srow; @@ -1565,8 +1610,7 @@ int scol, srow, y2, x2; * do_light_sources() */ boolean -clear_path(col1, row1, col2, row2) -int col1, row1, col2, row2; +clear_path(int col1, int row1, int col2, int row2) { int result; @@ -1586,708 +1630,11 @@ int col1, row1, col2, row2; } } #ifdef MACRO_CPATH -cleardone: + cleardone: #endif return (boolean) result; } -#ifdef VISION_TABLES -/*==========================================================================*\ - GENERAL LINE OF SIGHT - Algorithm D -\*==========================================================================*/ - -/* - * Indicate caller for the shadow routines. - */ -#define FROM_RIGHT 0 -#define FROM_LEFT 1 - -/* - * Include the table definitions. - */ -#include "vis_tab.h" - -/* 3D table pointers. */ -static close2d *close_dy[CLOSE_MAX_BC_DY]; -static far2d *far_dy[FAR_MAX_BC_DY]; - -STATIC_DCL void FDECL(right_side, (int, int, int, int, int, - int, int, char *)); -STATIC_DCL void FDECL(left_side, (int, int, int, int, int, int, int, char *)); -STATIC_DCL int FDECL(close_shadow, (int, int, int, int)); -STATIC_DCL int FDECL(far_shadow, (int, int, int, int)); - -/* - * Initialize algorithm D's table pointers. If we don't have these, - * then we do 3D table lookups. Verrrry slow. - */ -STATIC_OVL void -view_init() -{ - int i; - - for (i = 0; i < CLOSE_MAX_BC_DY; i++) - close_dy[i] = &close_table[i]; - - for (i = 0; i < FAR_MAX_BC_DY; i++) - far_dy[i] = &far_table[i]; -} - -/* - * If the far table has an entry of OFF_TABLE, then the far block prevents - * us from seeing the location just above/below it. I.e. the first visible - * location is one *before* the block. - */ -#define OFF_TABLE 0xff - -STATIC_OVL int -close_shadow(side, this_row, block_row, block_col) -int side, this_row, block_row, block_col; -{ - register int sdy, sdx, pdy, offset; - - /* - * If on the same column (block_row = -1), then we can see it. - */ - if (block_row < 0) - return block_col; - - /* Take explicit absolute values. Adjust. */ - if ((sdy = (start_row - block_row)) < 0) - sdy = -sdy; - --sdy; /* src dy */ - if ((sdx = (start_col - block_col)) < 0) - sdx = -sdx; /* src dx */ - if ((pdy = (block_row - this_row)) < 0) - pdy = -pdy; /* point dy */ - - if (sdy < 0 || sdy >= CLOSE_MAX_SB_DY || sdx >= CLOSE_MAX_SB_DX - || pdy >= CLOSE_MAX_BC_DY) { - impossible("close_shadow: bad value"); - return block_col; - } - offset = close_dy[sdy]->close[sdx][pdy]; - if (side == FROM_RIGHT) - return block_col + offset; - - return block_col - offset; -} - -STATIC_OVL int -far_shadow(side, this_row, block_row, block_col) -int side, this_row, block_row, block_col; -{ - register int sdy, sdx, pdy, offset; - - /* - * Take care of a bug that shows up only on the borders. - * - * If the block is beyond the border, then the row is negative. Return - * the block's column number (should be 0 or COLNO-1). - * - * Could easily have the column be -1, but then wouldn't know if it was - * the left or right border. - */ - if (block_row < 0) - return block_col; - - /* Take explicit absolute values. Adjust. */ - if ((sdy = (start_row - block_row)) < 0) - sdy = -sdy; /* src dy */ - if ((sdx = (start_col - block_col)) < 0) - sdx = -sdx; - --sdx; /* src dx */ - if ((pdy = (block_row - this_row)) < 0) - pdy = -pdy; - --pdy; /* point dy */ - - if (sdy >= FAR_MAX_SB_DY || sdx < 0 || sdx >= FAR_MAX_SB_DX || pdy < 0 - || pdy >= FAR_MAX_BC_DY) { - impossible("far_shadow: bad value"); - return block_col; - } - if ((offset = far_dy[sdy]->far_q[sdx][pdy]) == OFF_TABLE) - offset = -1; - if (side == FROM_RIGHT) - return block_col + offset; - - return block_col - offset; -} - -/* - * right_side() - * - * Figure out what could be seen on the right side of the source. - */ -STATIC_OVL void -right_side(row, cb_row, cb_col, fb_row, fb_col, left, right_mark, limits) -int row; /* current row */ -int cb_row, cb_col; /* close block row and col */ -int fb_row, fb_col; /* far block row and col */ -int left; /* left mark of the previous row */ -int right_mark; /* right mark of previous row */ -char *limits; /* points at range limit for current row, or NULL */ -{ - register int i; - register char *rowp = NULL; - int hit_stone = 0; - int left_shadow, right_shadow, loc_right; - int lblock_col; /* local block column (current row) */ - int nrow, deeper; - char *row_min = NULL; /* left most */ - char *row_max = NULL; /* right most */ - int lim_max; /* right most limit of circle */ - - nrow = row + step; - deeper = good_row(nrow) && (!limits || (*limits >= *(limits + 1))); - if (!vis_func) { - rowp = cs_rows[row]; - row_min = &cs_left[row]; - row_max = &cs_right[row]; - } - if (limits) { - lim_max = start_col + *limits; - if (lim_max > COLNO - 1) - lim_max = COLNO - 1; - if (right_mark > lim_max) - right_mark = lim_max; - limits++; /* prepare for next row */ - } else - lim_max = COLNO - 1; - - /* - * Get the left shadow from the close block. This value could be - * illegal. - */ - left_shadow = close_shadow(FROM_RIGHT, row, cb_row, cb_col); - - /* - * Mark all stone walls as seen before the left shadow. All this work - * for a special case. - * - * NOTE. With the addition of this code in here, it is now *required* - * for the algorithm to work correctly. If this is commented out, - * change the above assignment so that left and not left_shadow is the - * variable that gets the shadow. - */ - while (left <= right_mark) { - loc_right = right_ptrs[row][left]; - if (loc_right > lim_max) - loc_right = lim_max; - if (viz_clear_rows[row][left]) { - if (loc_right >= left_shadow) { - left = left_shadow; /* opening ends beyond shadow */ - break; - } - left = loc_right; - loc_right = right_ptrs[row][left]; - if (loc_right > lim_max) - loc_right = lim_max; - if (left == loc_right) - return; /* boundary */ - - /* Shadow covers opening, beyond right mark */ - if (left == right_mark && left_shadow > right_mark) - return; - } - - if (loc_right > right_mark) /* can't see stone beyond the mark */ - loc_right = right_mark; - - if (vis_func) { - for (i = left; i <= loc_right; i++) - (*vis_func)(i, row, varg); - } else { - for (i = left; i <= loc_right; i++) - set_cs(rowp, i); - set_min(left); - set_max(loc_right); - } - - if (loc_right == right_mark) - return; /* all stone */ - if (loc_right >= left_shadow) - hit_stone = 1; - left = loc_right + 1; - } - - /* - * At this point we are at the first visible clear spot on or beyond - * the left shadow, unless the left shadow is an illegal value. If we - * have "hit stone" then we have a stone wall just to our left. - */ - - /* - * Get the right shadow. Make sure that it is a legal value. - */ - if ((right_shadow = far_shadow(FROM_RIGHT, row, fb_row, fb_col)) >= COLNO) - right_shadow = COLNO - 1; - /* - * Make vertical walls work the way we want them. In this case, we - * note when the close block blocks the column just above/beneath - * it (right_shadow < fb_col [actually right_shadow == fb_col-1]). If - * the location is filled, then we want to see it, so we put the - * right shadow back (same as fb_col). - */ - if (right_shadow < fb_col && !viz_clear_rows[row][fb_col]) - right_shadow = fb_col; - if (right_shadow > lim_max) - right_shadow = lim_max; - - /* - * Main loop. Within the range of sight of the previous row, mark all - * stone walls as seen. Follow open areas recursively. - */ - while (left <= right_mark) { - /* Get the far right of the opening or wall */ - loc_right = right_ptrs[row][left]; - if (loc_right > lim_max) - loc_right = lim_max; - - if (!viz_clear_rows[row][left]) { - hit_stone = 1; /* use stone on this row as close block */ - /* - * We can see all of the wall until the next open spot or the - * start of the shadow caused by the far block (right). - * - * Can't see stone beyond the right mark. - */ - if (loc_right > right_mark) - loc_right = right_mark; - - if (vis_func) { - for (i = left; i <= loc_right; i++) - (*vis_func)(i, row, varg); - } else { - for (i = left; i <= loc_right; i++) - set_cs(rowp, i); - set_min(left); - set_max(loc_right); - } - - if (loc_right == right_mark) - return; /* hit the end */ - left = loc_right + 1; - loc_right = right_ptrs[row][left]; - if (loc_right > lim_max) - loc_right = lim_max; - /* fall through... we know at least one position is visible */ - } - - /* - * We are in an opening. - * - * If this is the first open spot since the could see area (this is - * true if we have hit stone), get the shadow generated by the wall - * just to our left. - */ - if (hit_stone) { - lblock_col = left - 1; /* local block column */ - left = close_shadow(FROM_RIGHT, row, row, lblock_col); - if (left > lim_max) - break; /* off the end */ - } - - /* - * Check if the shadow covers the opening. If it does, then - * move to end of the opening. A shadow generated on from a - * wall on this row does *not* cover the wall on the right - * of the opening. - */ - if (left >= loc_right) { - if (loc_right == lim_max) { /* boundary */ - if (left == lim_max) { - if (vis_func) - (*vis_func)(lim_max, row, varg); - else { - set_cs(rowp, lim_max); /* last pos */ - set_max(lim_max); - } - } - return; /* done */ - } - left = loc_right; - continue; - } - - /* - * If the far wall of the opening (loc_right) is closer than the - * shadow limit imposed by the far block (right) then use the far - * wall as our new far block when we recurse. - * - * If the limits are the same, and the far block really exists - * (fb_row >= 0) then do the same as above. - * - * Normally, the check would be for the far wall being closer OR EQUAL - * to the shadow limit. However, there is a bug that arises from the - * fact that the clear area pointers end in an open space (if it - * exists) on a boundary. This then makes a far block exist where it - * shouldn't --- on a boundary. To get around that, I had to - * introduce the concept of a non-existent far block (when the - * row < 0). Next I have to check for it. Here is where that check - * exists. - */ - if ((loc_right < right_shadow) - || (fb_row >= 0 && loc_right == right_shadow)) { - if (vis_func) { - for (i = left; i <= loc_right; i++) - (*vis_func)(i, row, varg); - } else { - for (i = left; i <= loc_right; i++) - set_cs(rowp, i); - set_min(left); - set_max(loc_right); - } - - if (deeper) { - if (hit_stone) - right_side(nrow, row, lblock_col, row, loc_right, left, - loc_right, limits); - else - right_side(nrow, cb_row, cb_col, row, loc_right, left, - loc_right, limits); - } - - /* - * The following line, setting hit_stone, is needed for those - * walls that are only 1 wide. If hit stone is *not* set and - * the stone is only one wide, then the close block is the old - * one instead one on the current row. A way around having to - * set it here is to make left = loc_right (not loc_right+1) and - * let the outer loop take care of it. However, if we do that - * then we then have to check for boundary conditions here as - * well. - */ - hit_stone = 1; - - left = loc_right + 1; - - /* - * The opening extends beyond the right mark. This means that - * the next far block is the current far block. - */ - } else { - if (vis_func) { - for (i = left; i <= right_shadow; i++) - (*vis_func)(i, row, varg); - } else { - for (i = left; i <= right_shadow; i++) - set_cs(rowp, i); - set_min(left); - set_max(right_shadow); - } - - if (deeper) { - if (hit_stone) - right_side(nrow, row, lblock_col, fb_row, fb_col, left, - right_shadow, limits); - else - right_side(nrow, cb_row, cb_col, fb_row, fb_col, left, - right_shadow, limits); - } - - return; /* we're outta here */ - } - } -} - -/* - * left_side() - * - * This routine is the mirror image of right_side(). Please see right_side() - * for blow by blow comments. - */ -STATIC_OVL void -left_side(row, cb_row, cb_col, fb_row, fb_col, left_mark, right, limits) -int row; /* the current row */ -int cb_row, cb_col; /* close block row and col */ -int fb_row, fb_col; /* far block row and col */ -int left_mark; /* left mark of previous row */ -int right; /* right mark of the previous row */ -char *limits; -{ - register int i; - register char *rowp = NULL; - int hit_stone = 0; - int left_shadow, right_shadow, loc_left; - int lblock_col; /* local block column (current row) */ - int nrow, deeper; - char *row_min = NULL; /* left most */ - char *row_max = NULL; /* right most */ - int lim_min; - - nrow = row + step; - deeper = good_row(nrow) && (!limits || (*limits >= *(limits + 1))); - if (!vis_func) { - rowp = cs_rows[row]; - row_min = &cs_left[row]; - row_max = &cs_right[row]; - } - if (limits) { - lim_min = start_col - *limits; - if (lim_min < 0) - lim_min = 0; - if (left_mark < lim_min) - left_mark = lim_min; - limits++; /* prepare for next row */ - } else - lim_min = 0; - - /* This value could be illegal. */ - right_shadow = close_shadow(FROM_LEFT, row, cb_row, cb_col); - - while (right >= left_mark) { - loc_left = left_ptrs[row][right]; - if (loc_left < lim_min) - loc_left = lim_min; - if (viz_clear_rows[row][right]) { - if (loc_left <= right_shadow) { - right = right_shadow; /* opening ends beyond shadow */ - break; - } - right = loc_left; - loc_left = left_ptrs[row][right]; - if (loc_left < lim_min) - loc_left = lim_min; - if (right == loc_left) - return; /* boundary */ - } - - if (loc_left < left_mark) /* can't see beyond the left mark */ - loc_left = left_mark; - - if (vis_func) { - for (i = loc_left; i <= right; i++) - (*vis_func)(i, row, varg); - } else { - for (i = loc_left; i <= right; i++) - set_cs(rowp, i); - set_min(loc_left); - set_max(right); - } - - if (loc_left == left_mark) - return; /* all stone */ - if (loc_left <= right_shadow) - hit_stone = 1; - right = loc_left - 1; - } - - /* At first visible clear spot on or beyond the right shadow. */ - - if ((left_shadow = far_shadow(FROM_LEFT, row, fb_row, fb_col)) < 0) - left_shadow = 0; - - /* Do vertical walls as we want. */ - if (left_shadow > fb_col && !viz_clear_rows[row][fb_col]) - left_shadow = fb_col; - if (left_shadow < lim_min) - left_shadow = lim_min; - - while (right >= left_mark) { - loc_left = left_ptrs[row][right]; - - if (!viz_clear_rows[row][right]) { - hit_stone = 1; /* use stone on this row as close block */ - - /* We can only see walls until the left mark */ - if (loc_left < left_mark) - loc_left = left_mark; - - if (vis_func) { - for (i = loc_left; i <= right; i++) - (*vis_func)(i, row, varg); - } else { - for (i = loc_left; i <= right; i++) - set_cs(rowp, i); - set_min(loc_left); - set_max(right); - } - - if (loc_left == left_mark) - return; /* hit end */ - right = loc_left - 1; - loc_left = left_ptrs[row][right]; - if (loc_left < lim_min) - loc_left = lim_min; - /* fall through...*/ - } - - /* We are in an opening. */ - if (hit_stone) { - lblock_col = right + 1; /* stone block (local) */ - right = close_shadow(FROM_LEFT, row, row, lblock_col); - if (right < lim_min) - return; /* off the end */ - } - - /* Check if the shadow covers the opening. */ - if (right <= loc_left) { - /* Make a boundary condition work. */ - if (loc_left == lim_min) { /* at boundary */ - if (right == lim_min) { - if (vis_func) - (*vis_func)(lim_min, row, varg); - else { - set_cs(rowp, lim_min); /* caught the last pos */ - set_min(lim_min); - } - } - return; /* and break out the loop */ - } - - right = loc_left; - continue; - } - - /* If the far wall of the opening is closer than the shadow limit. */ - if ((loc_left > left_shadow) - || (fb_row >= 0 && loc_left == left_shadow)) { - if (vis_func) { - for (i = loc_left; i <= right; i++) - (*vis_func)(i, row, varg); - } else { - for (i = loc_left; i <= right; i++) - set_cs(rowp, i); - set_min(loc_left); - set_max(right); - } - - if (deeper) { - if (hit_stone) - left_side(nrow, row, lblock_col, row, loc_left, loc_left, - right, limits); - else - left_side(nrow, cb_row, cb_col, row, loc_left, loc_left, - right, limits); - } - - hit_stone = 1; /* needed for walls of width 1 */ - right = loc_left - 1; - - /* The opening extends beyond the left mark. */ - } else { - if (vis_func) { - for (i = left_shadow; i <= right; i++) - (*vis_func)(i, row, varg); - } else { - for (i = left_shadow; i <= right; i++) - set_cs(rowp, i); - set_min(left_shadow); - set_max(right); - } - - if (deeper) { - if (hit_stone) - left_side(nrow, row, lblock_col, fb_row, fb_col, - left_shadow, right, limits); - else - left_side(nrow, cb_row, cb_col, fb_row, fb_col, - left_shadow, right, limits); - } - - return; /* we're outta here */ - } - } -} - -/* - * view_from - * - * Calculate a view from the given location. Initialize and fill a - * ROWNOxCOLNO array (could_see) with all the locations that could be - * seen from the source location. Initialize and fill the left most - * and right most boundaries of what could be seen. - */ -STATIC_OVL void -view_from(srow, scol, loc_cs_rows, left_most, right_most, range, func, arg) -int srow, scol; /* source row and column */ -char **loc_cs_rows; /* could_see array (row pointers) */ -char *left_most, *right_most; /* limits of what could be seen */ -int range; /* 0 if unlimited */ -void FDECL((*func), (int, int, genericptr_t)); -genericptr_t arg; -{ - register int i; - char *rowp; - int nrow, left, right, left_row, right_row; - char *limits; - - /* Set globals for near_shadow(), far_shadow(), etc. to use. */ - start_col = scol; - start_row = srow; - cs_rows = loc_cs_rows; - cs_left = left_most; - cs_right = right_most; - vis_func = func; - varg = arg; - - /* Find the left and right limits of sight on the starting row. */ - if (viz_clear_rows[srow][scol]) { - left = left_ptrs[srow][scol]; - right = right_ptrs[srow][scol]; - } else { - left = (!scol) ? 0 : (viz_clear_rows[srow][scol - 1] - ? left_ptrs[srow][scol - 1] - : scol - 1); - right = (scol == COLNO - 1) ? COLNO - 1 - : (viz_clear_rows[srow][scol + 1] - ? right_ptrs[srow][scol + 1] - : scol + 1); - } - - if (range) { - if (range > MAX_RADIUS || range < 1) - panic("view_from called with range %d", range); - limits = circle_ptr(range) + 1; /* start at next row */ - if (left < scol - range) - left = scol - range; - if (right > scol + range) - right = scol + range; - } else - limits = (char *) 0; - - if (func) { - for (i = left; i <= right; i++) - (*func)(i, srow, arg); - } else { - /* Row optimization */ - rowp = cs_rows[srow]; - - /* We know that we can see our row. */ - for (i = left; i <= right; i++) - set_cs(rowp, i); - cs_left[srow] = left; - cs_right[srow] = right; - } - - /* The far block has a row number of -1 if we are on an edge. */ - right_row = (right == COLNO - 1) ? -1 : srow; - left_row = (!left) ? -1 : srow; - - /* - * Check what could be seen in quadrants. - */ - if ((nrow = srow + 1) < ROWNO) { - step = 1; /* move down */ - if (scol < COLNO - 1) - right_side(nrow, -1, scol, right_row, right, scol, right, limits); - if (scol) - left_side(nrow, -1, scol, left_row, left, left, scol, limits); - } - - if ((nrow = srow - 1) >= 0) { - step = -1; /* move up */ - if (scol < COLNO - 1) - right_side(nrow, -1, scol, right_row, right, scol, right, limits); - if (scol) - left_side(nrow, -1, scol, left_row, left, left, scol, limits); - } -} - -#else /*===== End of algorithm D =====*/ - /*==========================================================================*\ GENERAL LINE OF SIGHT Algorithm C @@ -2296,35 +1643,41 @@ genericptr_t arg; /* * Defines local to Algorithm C. */ -STATIC_DCL void FDECL(right_side, (int, int, int, char *)); -STATIC_DCL void FDECL(left_side, (int, int, int, char *)); +staticfn void right_side(int, int, int, const coordxy *); +staticfn void left_side(int, int, int, const coordxy *); /* Initialize algorithm C (nothing). */ -STATIC_OVL void -view_init() +staticfn void +view_init(void) { } /* * Mark positions as visible on one quadrant of the right side. The * quadrant is determined by the value of the global variable step. + * + * Arguments: + * row current row + * left first (left side) visible spot on prev row + * right_mark last (right side) visible spot on prev row + * limits points at range limit for current row, or NULL */ -STATIC_OVL void -right_side(row, left, right_mark, limits) -int row; /* current row */ -int left; /* first (left side) visible spot on prev row */ -int right_mark; /* last (right side) visible spot on prev row */ -char *limits; /* points at range limit for current row, or NULL */ +staticfn void +right_side( + int row, + int left, + int right_mark, + const coordxy *limits) { int right; /* right limit of "could see" */ int right_edge; /* right edge of an opening */ int nrow; /* new row (calculate once) */ int deeper; /* if TRUE, call self as needed */ int result; /* set by q?_path() */ - register int i; /* loop counter */ - register char *rowp = NULL; /* row optimization */ - char *row_min = NULL; /* left most [used by macro set_min()] */ - char *row_max = NULL; /* right most [used by macro set_max()] */ + int i; /* loop counter */ + seenV *rowp = NULL; /* row optimization */ + coordxy *row_min = NULL; /* left most [used by macro set_min()] */ + coordxy *row_max = NULL; /* right most [used by macro set_max()] */ int lim_max; /* right most limit of circle */ nrow = row + step; @@ -2399,7 +1752,7 @@ char *limits; /* points at range limit for current row, or NULL */ } else { q4_path(start_row, start_col, row, left, rside1); } - rside1: /* used if q?_path() is a macro */ + rside1: /* used if q?_path() is a macro */ if (result) break; } @@ -2443,7 +1796,7 @@ char *limits; /* points at range limit for current row, or NULL */ * * Otherwise, we know we can see the right edge of the current row. * - * This must be a strict less than so that we can always see a + * This must be a strict "less than" so that we can always see a * horizontal wall, even if it is adjacent to us. */ if (right_mark < right_edge) { @@ -2453,7 +1806,7 @@ char *limits; /* points at range limit for current row, or NULL */ } else { q4_path(start_row, start_col, row, right, rside2); } - rside2: /* used if q?_path() is a macro */ + rside2: /* used if q?_path() is a macro */ if (!result) break; } @@ -2501,21 +1854,20 @@ char *limits; /* points at range limit for current row, or NULL */ * This routine is the mirror image of right_side(). See right_side() for * extensive comments. */ -STATIC_OVL void -left_side(row, left_mark, right, limits) -int row, left_mark, right; -char *limits; +staticfn void +left_side( + int row, + int left_mark, + int right, + const coordxy *limits) { int left, left_edge, nrow, deeper, result; - register int i; - register char *rowp = NULL; - char *row_min = NULL; - char *row_max = NULL; + int i; + seenV *rowp = NULL; + coordxy *row_min = NULL; + coordxy *row_max = NULL; int lim_min; -#ifdef GCC_WARN - rowp = row_min = row_max = 0; -#endif nrow = row + step; deeper = good_row(nrow) && (!limits || (*limits >= *(limits + 1))); if (!vis_func) { @@ -2566,7 +1918,7 @@ char *limits; } else { q3_path(start_row, start_col, row, right, lside1); } - lside1: /* used if q?_path() is a macro */ + lside1: /* used if q?_path() is a macro */ if (result) break; } @@ -2598,7 +1950,7 @@ char *limits; } else { q3_path(start_row, start_col, row, left, lside2); } - lside2: /* used if q?_path() is a macro */ + lside2: /* used if q?_path() is a macro */ if (!result) break; } @@ -2636,23 +1988,31 @@ char *limits; * Calculate all possible visible locations from the given location * (srow,scol). NOTE this is (y,x)! Mark the visible locations in the * array provided. + * + * Arguments + * srow, scol starting row and column + * loc_cs_rows pointers to the rows of the could_see array + * left_most min mark on each row + * right_most max mark on each row + * range 0 if unlimited + * func function to call on each spot + * arg argument for func */ -STATIC_OVL void -view_from(srow, scol, loc_cs_rows, left_most, right_most, range, func, arg) -int srow, scol; /* starting row and column */ -char **loc_cs_rows; /* pointers to the rows of the could_see array */ -char *left_most; /* min mark on each row */ -char *right_most; /* max mark on each row */ -int range; /* 0 if unlimited */ -void FDECL((*func), (int, int, genericptr_t)); -genericptr_t arg; +staticfn void +view_from( + coordxy srow, coordxy scol, + seenV **loc_cs_rows, + coordxy *left_most, coordxy *right_most, + int range, + void (*func)(coordxy, coordxy, genericptr_t), + genericptr_t arg) { - register int i; /* loop counter */ - char *rowp; /* optimization for setting could_see */ + int i; /* loop counter */ + seenV *rowp; /* optimization for setting could_see */ int nrow; /* the next row */ int left; /* the left-most visible column */ int right; /* the right-most visible column */ - char *limits; /* range limit for next row */ + const coordxy *limits; /* range limit for next row */ /* Set globals for q?_path(), left_side(), and right_side() to use. */ start_col = scol; @@ -2692,7 +2052,7 @@ genericptr_t arg; if (right > scol + range) right = scol + range; } else - limits = (char *) 0; + limits = (coordxy *) 0; if (func) { for (i = left; i <= right; i++) @@ -2730,7 +2090,7 @@ genericptr_t arg; } } -#endif /*===== End of algorithm C =====*/ +/*===== End of algorithm C =====*/ /* * AREA OF EFFECT "ENGINE" @@ -2744,29 +2104,30 @@ genericptr_t arg; * vision matrix and reduce extra work. */ void -do_clear_area(scol, srow, range, func, arg) -int scol, srow, range; -void FDECL((*func), (int, int, genericptr_t)); -genericptr_t arg; +do_clear_area( + coordxy scol, coordxy srow, + int range, + void (*func)(coordxy, coordxy, genericptr_t), + genericptr_t arg) { /* If not centered on hero, do the hard work of figuring the area */ if (scol != u.ux || srow != u.uy) { - view_from(srow, scol, (char **) 0, (char *) 0, (char *) 0, range, - func, arg); + view_from(srow, scol, (seenV **) 0, (coordxy *) 0, (coordxy *) 0, + range, func, arg); } else { - register int x; + int x; int y, min_x, max_x, max_y, offset; - char *limits; + const coordxy *limits; boolean override_vision; /* vision doesn't pass through water or clouds, detection should [this probably ought to be an arg supplied by our caller...] */ - override_vision = - (Is_waterlevel(&u.uz) || Is_airlevel(&u.uz)) && detecting(func); + override_vision = (detecting(func) + && (Is_waterlevel(&u.uz) || Is_airlevel(&u.uz))); if (range > MAX_RADIUS || range < 1) panic("do_clear_area: illegal range %d", range); - if (vision_full_recalc) + if (gv.vision_full_recalc) vision_recalc(0); /* recalc vision if dirty */ limits = circle_ptr(range); if ((max_y = (srow + range)) >= ROWNO) @@ -2775,8 +2136,8 @@ genericptr_t arg; y = 0; for (; y <= max_y; y++) { offset = limits[v_abs(y - srow)]; - if ((min_x = (scol - offset)) < 0) - min_x = 0; + if ((min_x = (scol - offset)) < 1) + min_x = 1; if ((max_x = (scol + offset)) >= COLNO) max_x = COLNO - 1; for (x = min_x; x <= max_x; x++) @@ -2788,13 +2149,13 @@ genericptr_t arg; /* bitmask indicating ways mon is seen; extracted from lookat(pager.c) */ unsigned -howmonseen(mon) -struct monst *mon; +howmonseen(struct monst *mon) { boolean useemon = (boolean) canseemon(mon); int xraydist = (u.xray_range < 0) ? -1 : (u.xray_range * u.xray_range); unsigned how_seen = 0; /* result */ + /* assert(mon != NULL) */ /* normal vision; cansee is true for both normal and astral vision, but couldsee it not true for astral vision */ @@ -2812,7 +2173,7 @@ struct monst *mon; if (tp_sensemon(mon)) how_seen |= MONSEEN_TELEPAT; /* xray */ - if (useemon && xraydist > 0 && distu(mon->mx, mon->my) <= xraydist) + if (useemon && xraydist > 0 && mdistu(mon) <= xraydist) how_seen |= MONSEEN_XRAYVIS; /* extended detection */ if (Detect_monsters) diff --git a/src/weapon.c b/src/weapon.c index 1afd2697f..4dfa4a112 100644 --- a/src/weapon.c +++ b/src/weapon.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 weapon.c $NHDT-Date: 1559998716 2019/06/08 12:58:36 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.70 $ */ +/* NetHack 5.0 weapon.c $NHDT-Date: 1725227810 2024/09/01 21:56:50 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.128 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2011. */ /* NetHack may be freely redistributed. See license for details. */ @@ -10,11 +10,13 @@ */ #include "hack.h" -STATIC_DCL void FDECL(give_may_advance_msg, (int)); -STATIC_DCL boolean FDECL(could_advance, (int)); -STATIC_DCL boolean FDECL(peaked_skill, (int)); -STATIC_DCL int FDECL(slots_required, (int)); -STATIC_DCL void FDECL(skill_advance, (int)); +staticfn void give_may_advance_msg(int); +staticfn void finish_towel_change(struct obj *obj, int) NONNULLARG1; +staticfn boolean could_advance(int); +staticfn boolean peaked_skill(int); +staticfn int slots_required(int); +staticfn void skill_advance(int); +staticfn void add_skills_to_menu(winid, boolean, boolean); /* Categories whose names don't come from OBJ_NAME(objects[type]) */ @@ -33,25 +35,28 @@ STATIC_DCL void FDECL(skill_advance, (int)); #define PN_ESCAPE_SPELL (-13) #define PN_MATTER_SPELL (-14) -STATIC_VAR NEARDATA const short skill_names_indices[P_NUM_SKILLS] = { +static NEARDATA const short skill_names_indices[P_NUM_SKILLS] = { + /* Weapon */ 0, DAGGER, KNIFE, AXE, PICK_AXE, SHORT_SWORD, BROADSWORD, LONG_SWORD, - TWO_HANDED_SWORD, SCIMITAR, PN_SABER, CLUB, MACE, MORNING_STAR, FLAIL, - PN_HAMMER, QUARTERSTAFF, PN_POLEARMS, SPEAR, TRIDENT, LANCE, BOW, SLING, - CROSSBOW, DART, SHURIKEN, BOOMERANG, PN_WHIP, UNICORN_HORN, + TWO_HANDED_SWORD, PN_SABER, CLUB, MACE, MORNING_STAR, FLAIL, PN_HAMMER, + QUARTERSTAFF, PN_POLEARMS, SPEAR, TRIDENT, LANCE, BOW, SLING, CROSSBOW, + DART, SHURIKEN, BOOMERANG, PN_WHIP, UNICORN_HORN, + /* Spell */ PN_ATTACK_SPELL, PN_HEALING_SPELL, PN_DIVINATION_SPELL, PN_ENCHANTMENT_SPELL, PN_CLERIC_SPELL, PN_ESCAPE_SPELL, PN_MATTER_SPELL, + /* Other */ PN_BARE_HANDED, PN_TWO_WEAPONS, PN_RIDING }; /* note: entry [0] isn't used */ -STATIC_VAR NEARDATA const char *const odd_skill_names[] = { +static NEARDATA const char *const odd_skill_names[] = { "no skill", "bare hands", /* use barehands_or_martial[] instead */ "two weapon combat", "riding", "polearms", "saber", "hammer", "whip", "attack spells", "healing spells", "divination spells", "enchantment spells", "clerical spells", "escape spells", "matter spells", }; -/* indexed vis `is_martial() */ -STATIC_VAR NEARDATA const char *const barehands_or_martial[] = { +/* indexed via is_martial() */ +static NEARDATA const char *const barehands_or_martial[] = { "bare handed combat", "martial arts" }; @@ -62,26 +67,27 @@ STATIC_VAR NEARDATA const char *const barehands_or_martial[] = { ? barehands_or_martial[martial_bonus()] \ : odd_skill_names[-skill_names_indices[type]]) -static NEARDATA const char kebabable[] = { S_XORN, S_DRAGON, S_JABBERWOCK, - S_NAGA, S_GIANT, '\0' }; +/* targets that provide attacker with small to-hit bonus when using a spear */ +static NEARDATA const char kebabable[] = { + S_XORN, S_DRAGON, S_JABBERWOCK, S_NAGA, S_GIANT, '\0' +}; -STATIC_OVL void -give_may_advance_msg(skill) -int skill; +staticfn void +give_may_advance_msg(int skill) { You_feel("more confident in your %sskills.", (skill == P_NONE) ? "" : (skill <= P_LAST_WEAPON) ? "weapon " : (skill <= P_LAST_SPELL) ? "spell casting " : "fighting "); + (void) handle_tip(TIP_ENHANCE); } /* weapon's skill category name for use as generalized description of weapon; mostly used to shorten "you drop your " messages when slippery fingers or polymorph causes hero to involuntarily drop wielded weapon(s) */ const char * -weapon_descr(obj) -struct obj *obj; +weapon_descr(struct obj *obj) { int skill = weapon_type(obj); const char *descr = P_NAME(skill); @@ -90,13 +96,17 @@ struct obj *obj; switch (skill) { case P_NONE: /* not a weapon or weptool: use item class name; - override class name "food" for corpses, tins, and eggs, - "large rock" for statues and boulders, and "tool" for towels */ + override class name for things where it sounds strange and + for things that aren't unexpected to find being wielded: + corpses, tins, eggs, and globs avoid "food", + statues and boulders avoid "large rock", + and towels and tin openers avoid "tool" */ descr = (obj->otyp == CORPSE || obj->otyp == TIN || obj->otyp == EGG || obj->otyp == STATUE || obj->otyp == BOULDER - || obj->otyp == TOWEL) - ? OBJ_NAME(objects[obj->otyp]) - : def_oc_syms[(int) obj->oclass].name; + || obj->otyp == TOWEL || obj->otyp == TIN_OPENER) + ? OBJ_NAME(objects[obj->otyp]) + : obj->globby ? "glob" + : def_oc_syms[(int) obj->oclass].name; break; case P_SLING: if (is_ammo(obj)) @@ -136,9 +146,7 @@ struct obj *obj; * of "otmp" against the monster. */ int -hitval(otmp, mon) -struct obj *otmp; -struct monst *mon; +hitval(struct obj *otmp, struct monst *mon) { int tmp = 0; struct permonst *ptr = mon->data; @@ -147,17 +155,16 @@ struct monst *mon; if (Is_weapon) tmp += otmp->spe; - /* Put weapon specific "to hit" bonuses in below: */ + /* Put weapon-specific "to hit" bonuses in below: */ tmp += objects[otmp->otyp].oc_hitbon; /* Put weapon vs. monster type "to hit" bonuses in below: */ /* Blessed weapons used against undead or demons */ - if (Is_weapon && otmp->blessed - && (is_demon(ptr) || is_undead(ptr) || is_vampshifter(mon))) + if (Is_weapon && otmp->blessed && mon_hates_blessings(mon)) tmp += 2; - if (is_spear(otmp) && index(kebabable, ptr->mlet)) + if (is_spear(otmp) && strchr(kebabable, ptr->mlet)) tmp += 2; /* trident is highly effective against swimmers */ @@ -180,7 +187,7 @@ struct monst *mon; } /* Historical note: The original versions of Hack used a range of damage - * which was similar to, but not identical to the damage used in Advanced + * which was similar to, but not identical to, the damage used in Advanced * Dungeons and Dragons. I figured that since it was so close, I may as well * make it exactly the same as AD&D, adding some more weapons in the process. * This has the advantage that it is at least possible that the player would @@ -206,9 +213,7 @@ struct monst *mon; * of "otmp" against the monster. */ int -dmgval(otmp, mon) -struct obj *otmp; -struct monst *mon; +dmgval(struct obj *otmp, struct monst *mon) { int tmp = 0, otyp = otmp->otyp; struct permonst *ptr = mon->data; @@ -262,6 +267,7 @@ struct monst *mon; case IRON_CHAIN: case CROSSBOW_BOLT: case MACE: + case SILVER_MACE: case WAR_HAMMER: case FLAIL: case SPETUM: @@ -296,7 +302,7 @@ struct monst *mon; } if (objects[otyp].oc_material <= LEATHER && thick_skinned(ptr)) - /* thick skinned/scaled creatures don't feel it */ + /* thick-skinned or scaled creatures don't feel it */ tmp = 0; if (ptr == &mons[PM_SHADE] && !shade_glare(otmp)) tmp = 0; @@ -306,7 +312,7 @@ struct monst *mon; int wt = (int) objects[HEAVY_IRON_BALL].oc_weight; if ((int) otmp->owt > wt) { - wt = ((int) otmp->owt - wt) / IRON_BALL_W_INCR; + wt = ((int) otmp->owt - wt) / WT_IRON_BALL_INCR; tmp += rnd(4 * wt); if (tmp > 25) tmp = 25; /* objects[].oc_wldam */ @@ -318,8 +324,7 @@ struct monst *mon; || otmp->oclass == CHAIN_CLASS) { int bonus = 0; - if (otmp->blessed - && (is_undead(ptr) || is_demon(ptr) || is_vampshifter(mon))) + if (otmp->blessed && mon_hates_blessings(mon)) bonus += rnd(4); if (is_axe(otmp) && is_wooden(ptr)) bonus += rnd(4); @@ -353,14 +358,15 @@ struct monst *mon; /* check whether blessed and/or silver damage applies for *non-weapon* hit; return value is the amount of the extra damage */ int -special_dmgval(magr, mdef, armask, silverhit_p) -struct monst *magr, *mdef; -long armask; /* armor mask, multiple bits accepted for W_ARMC|W_ARM|W_ARMU - * or W_ARMG|W_RINGL|W_RINGR only */ -long *silverhit_p; /* output flag mask for silver bonus */ +special_dmgval( + struct monst *magr, /* attacker */ + struct monst *mdef, /* defender */ + long armask, /* armor mask, multiple bits accepted for + * W_ARMC|W_ARM|W_ARMU or + * W_ARMG|W_RINGL|W_RINGR only */ + long *silverhit_p) /* output flag mask for silver bonus */ { struct obj *obj; - struct permonst *ptr = mdef->data; boolean left_ring = (armask & W_RINGL) ? TRUE : FALSE, right_ring = (armask & W_RINGR) ? TRUE : FALSE; long silverhit = 0L; @@ -386,8 +392,7 @@ long *silverhit_p; /* output flag mask for silver bonus */ } if (obj) { - if (obj->blessed - && (is_undead(ptr) || is_demon(ptr) || is_vampshifter(mdef))) + if (obj->blessed && mon_hates_blessings(mdef)) bonus += rnd(4); /* the only silver armor is shield of reflection (silver dragon scales refer to color, not material) and the only way to hit @@ -400,7 +405,7 @@ long *silverhit_p; /* output flag mask for silver bonus */ } /* when no gloves we check for silver rings (blessed rings ignored) */ - } else if ((left_ring || right_ring) && magr == &youmonst) { + } else if ((left_ring || right_ring) && magr == &gy.youmonst) { if (left_ring && uleft) { if (objects[uleft->otyp].oc_material == SILVER && mon_hates_silver(mdef)) { @@ -428,10 +433,8 @@ long *silverhit_p; /* output flag mask for silver bonus */ /* give a "silver sears " message; not used for weapon hit, so we only handle rings */ void -silver_sears(magr, mdef, silverhit) -struct monst *magr UNUSED; -struct monst *mdef; -long silverhit; +silver_sears(struct monst *magr UNUSED, struct monst *mdef, + long silverhit) { char rings[20]; /* plenty of room for "rings" */ int ltyp = ((uleft && (silverhit & W_RINGL) != 0L) @@ -439,8 +442,10 @@ long silverhit; rtyp = ((uright && (silverhit & W_RINGR) != 0L) ? uright->otyp : STRANGE_OBJECT); boolean both, - l_ag = (objects[ltyp].oc_material == SILVER && uleft->dknown), - r_ag = (objects[rtyp].oc_material == SILVER && uright->dknown); + l_dknown = (uleft && uleft->dknown), + r_dknown = (uright && uright->dknown), + l_ag = (objects[ltyp].oc_material == SILVER && l_dknown), + r_ag = (objects[rtyp].oc_material == SILVER && r_dknown); if ((silverhit & (W_RINGL | W_RINGR)) != 0L) { /* plural if both the same type (so not multi_claw and both rings @@ -449,8 +454,7 @@ long silverhit; and both known; singular if multi_claw (where one of ltyp or rtyp will always be STRANGE_OBJECT) even if both rings are known silver [see hmonas(uhitm.c) for explanation of 'multi_claw'] */ - both = ((ltyp == rtyp && uleft->dknown == uright->dknown) - || (l_ag && r_ag)); + both = ((ltyp == rtyp && l_dknown == r_dknown) || (l_ag && r_ag)); Sprintf(rings, "ring%s", both ? "s" : ""); Your("%s%s %s %s!", (l_ag || r_ag) ? "silver " @@ -461,59 +465,81 @@ long silverhit; } } -STATIC_DCL struct obj *FDECL(oselect, (struct monst *, int)); -#define Oselect(x) \ - if ((otmp = oselect(mtmp, x)) != 0) \ - return otmp; +staticfn struct obj *oselect(struct monst *, int); +#define Oselect(x) \ + do { \ + if ((otmp = oselect(mtmp, x)) != 0) \ + return otmp; \ + } while (0) -STATIC_OVL struct obj * -oselect(mtmp, x) -struct monst *mtmp; -int x; +staticfn struct obj * +oselect(struct monst *mtmp, int type) { struct obj *otmp; for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj) { - if (otmp->otyp == x - /* never select non-cockatrice corpses */ - && !((x == CORPSE || x == EGG) - && !touch_petrifies(&mons[otmp->corpsenm])) - && (!otmp->oartifact || touch_artifact(otmp, mtmp))) - return otmp; + if (otmp->otyp != type) + continue; + + /* never select non-cockatrice corpses */ + if ((type == CORPSE || type == EGG) + && (otmp->corpsenm == NON_PM + || !touch_petrifies(&mons[otmp->corpsenm]))) + continue; + + if (!can_touch_safely(mtmp, otmp)) + continue; + + return otmp; } return (struct obj *) 0; } -/* TODO: have monsters use aklys' throw-and-return */ static NEARDATA const int rwep[] = { DWARVISH_SPEAR, SILVER_SPEAR, ELVEN_SPEAR, SPEAR, ORCISH_SPEAR, JAVELIN, SHURIKEN, YA, SILVER_ARROW, ELVEN_ARROW, ARROW, ORCISH_ARROW, CROSSBOW_BOLT, SILVER_DAGGER, ELVEN_DAGGER, DAGGER, ORCISH_DAGGER, KNIFE, - FLINT, ROCK, LOADSTONE, LUCKSTONE, DART, - /* BOOMERANG, */ CREAM_PIE + FLINT, ROCK, LOADSTONE, LUCKSTONE, DART, CREAM_PIE, }; +/* polearms */ static NEARDATA const int pwep[] = { HALBERD, BARDICHE, SPETUM, BILL_GUISARME, VOULGE, RANSEUR, GUISARME, GLAIVE, LUCERN_HAMMER, BEC_DE_CORBIN, FAUCHARD, PARTISAN, LANCE }; -static struct obj *propellor; +#define AKLYS_LIM (BOLT_LIM / 2) +/* throw-and-return weapons */ +static NEARDATA const struct throw_and_return_weapon arwep[] = { + /* { BOOMERANG, 5, 0 }, */ + { AKLYS, AKLYS_LIM * AKLYS_LIM, 1 }, +}; + +const struct throw_and_return_weapon * +autoreturn_weapon(struct obj *otmp) +{ + int i; + + for (i = 0; i < SIZE(arwep); i++) { + if (otmp->otyp == arwep[i].otyp) + return &arwep[i]; + } + return (struct throw_and_return_weapon *) 0; +} /* select a ranged weapon for the monster */ struct obj * -select_rwep(mtmp) -register struct monst *mtmp; +select_rwep(struct monst *mtmp) { - register struct obj *otmp; + struct obj *otmp; struct obj *mwep; boolean mweponly; int i; char mlet = mtmp->data->mlet; - propellor = (struct obj *) &zeroobj; + gp.propellor = &hands_obj; Oselect(EGG); /* cockatrice egg */ if (mlet == S_KOP) /* pies are first choice for Kops */ Oselect(CREAM_PIE); @@ -533,6 +559,11 @@ register struct monst *mtmp; mweponly = (mwelded(mwep) && mtmp->weapon_check == NO_WEAPON_WANTED); if (dist2(mtmp->mx, mtmp->my, mtmp->mux, mtmp->muy) <= 13 && couldsee(mtmp->mx, mtmp->my)) { + if (is_art(mwep, ART_SNICKERSNEE)) { + gp.propellor = mwep; + return mwep; + } + for (i = 0; i < SIZE(pwep); i++) { /* Only strong monsters can wield big (esp. long) weapons. * Big weapon is basically the same as bimanual. @@ -545,7 +576,29 @@ register struct monst *mtmp; || !mon_hates_silver(mtmp))) { if ((otmp = oselect(mtmp, pwep[i])) != 0 && (otmp == mwep || !mweponly)) { - propellor = otmp; /* force the monster to wield it */ + gp.propellor = otmp; /* force the monster to wield it */ + return otmp; + } + } + } + } + /* Next, try to select a throw-and-return weapon, since they are + * also not as expendable. Again, don't pick one if monster's + * weapon is welded. + */ + for (i = 0; i < SIZE(arwep); i++) { + const struct throw_and_return_weapon *arw = &arwep[i]; + + if (!mindless(mtmp->data) && !is_animal(mtmp->data) && !mweponly + && dist2(mtmp->mx, mtmp->my, mtmp->mux, mtmp->muy) <= arw->range + && couldsee(mtmp->mx, mtmp->my)) { + if ((((mtmp->misc_worn_check & W_ARMS) == 0) + || !objects[arw->otyp].oc_bimanual) + && (objects[arw->otyp].oc_material != SILVER + || !mon_hates_silver(mtmp))) { + if ((otmp = oselect(mtmp, arw->otyp)) != 0 + && (otmp == mwep || !mweponly)) { + gp.propellor = otmp; /* force the monster to wield it */ return otmp; } } @@ -553,7 +606,7 @@ register struct monst *mtmp; } /* - * other than these two specific cases, always select the + * other than the specific cases above, always select the * most potent ranged weapon to hand. */ for (i = 0; i < SIZE(rwep); i++) { @@ -566,41 +619,41 @@ register struct monst *mtmp; for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj) if (otmp->oclass == GEM_CLASS && (otmp->otyp != LOADSTONE || !otmp->cursed)) { - propellor = m_carrying(mtmp, SLING); + gp.propellor = m_carrying(mtmp, SLING); return otmp; } } /* KMH -- This belongs here so darts will work */ - propellor = (struct obj *) &zeroobj; + gp.propellor = &hands_obj; prop = objects[rwep[i]].oc_skill; if (prop < 0) { switch (-prop) { case P_BOW: - propellor = oselect(mtmp, YUMI); - if (!propellor) - propellor = oselect(mtmp, ELVEN_BOW); - if (!propellor) - propellor = oselect(mtmp, BOW); - if (!propellor) - propellor = oselect(mtmp, ORCISH_BOW); + gp.propellor = oselect(mtmp, YUMI); + if (!gp.propellor) + gp.propellor = oselect(mtmp, ELVEN_BOW); + if (!gp.propellor) + gp.propellor = oselect(mtmp, BOW); + if (!gp.propellor) + gp.propellor = oselect(mtmp, ORCISH_BOW); break; case P_SLING: - propellor = oselect(mtmp, SLING); + gp.propellor = oselect(mtmp, SLING); break; case P_CROSSBOW: - propellor = oselect(mtmp, CROSSBOW); + gp.propellor = oselect(mtmp, CROSSBOW); } - if ((otmp = MON_WEP(mtmp)) && mwelded(otmp) && otmp != propellor + if ((otmp = MON_WEP(mtmp)) && mwelded(otmp) && otmp != gp.propellor && mtmp->weapon_check == NO_WEAPON_WANTED) - propellor = 0; + gp.propellor = 0; } /* propellor = obj, propellor to use - * propellor = &zeroobj, doesn't need a propellor + * propellor = &hands_obj, doesn't need a propellor * propellor = 0, needed one and didn't have one */ - if (propellor != 0) { + if (gp.propellor != 0) { /* Note: cannot use m_carrying for loadstones, since it will * always select the first object of a type, and maybe the * monster is carrying two but only the first is unthrowable. @@ -624,8 +677,7 @@ register struct monst *mtmp; /* is 'obj' a type of weapon that any monster knows how to throw? */ boolean -monmightthrowwep(obj) -struct obj *obj; +monmightthrowwep(struct obj *obj) { short idx; @@ -641,8 +693,8 @@ static const NEARDATA short hwep[] = { TSURUGI, RUNESWORD, DWARVISH_MATTOCK, TWO_HANDED_SWORD, BATTLE_AXE, KATANA, UNICORN_HORN, CRYSKNIFE, TRIDENT, LONG_SWORD, ELVEN_BROADSWORD, BROADSWORD, SCIMITAR, SILVER_SABER, MORNING_STAR, ELVEN_SHORT_SWORD, - DWARVISH_SHORT_SWORD, SHORT_SWORD, ORCISH_SHORT_SWORD, MACE, AXE, - DWARVISH_SPEAR, SILVER_SPEAR, ELVEN_SPEAR, SPEAR, ORCISH_SPEAR, FLAIL, + DWARVISH_SHORT_SWORD, SHORT_SWORD, ORCISH_SHORT_SWORD, SILVER_MACE, MACE, + AXE, DWARVISH_SPEAR, SILVER_SPEAR, ELVEN_SPEAR, SPEAR, ORCISH_SPEAR, FLAIL, BULLWHIP, QUARTERSTAFF, JAVELIN, AKLYS, CLUB, PICK_AXE, RUBBER_HOSE, WAR_HAMMER, SILVER_DAGGER, ELVEN_DAGGER, DAGGER, ORCISH_DAGGER, ATHAME, SCALPEL, KNIFE, WORM_TOOTH @@ -650,11 +702,10 @@ static const NEARDATA short hwep[] = { /* select a hand to hand weapon for the monster */ struct obj * -select_hwep(mtmp) -register struct monst *mtmp; +select_hwep(struct monst *mtmp) { - register struct obj *otmp; - register int i; + struct obj *otmp; + int i; boolean strong = strongmonst(mtmp->data); boolean wearing_shield = (mtmp->misc_worn_check & W_ARMS) != 0; @@ -669,6 +720,8 @@ register struct monst *mtmp; if (is_giant(mtmp->data)) /* giants just love to use clubs */ Oselect(CLUB); + else if (mtmp->data == &mons[PM_BALROG] && uwep) + Oselect(BULLWHIP); /* only strong monsters can wield big (esp. long) weapons */ /* big weapon is basically the same as bimanual */ @@ -691,9 +744,7 @@ register struct monst *mtmp; * otherwise never unwield stuff on their own. Might print message. */ void -possibly_unwield(mon, polyspot) -struct monst *mon; -boolean polyspot; +possibly_unwield(struct monst *mon, boolean polyspot) { struct obj *obj, *mw_tmp; @@ -710,11 +761,12 @@ boolean polyspot; if (!attacktype(mon->data, AT_WEAP)) { setmnotwielded(mon, mw_tmp); mon->weapon_check = NO_WEAPON_WANTED; - obj_extract_self(obj); + /* if we're going to call distant_name(), do so before extract_self */ if (cansee(mon->mx, mon->my)) { - pline("%s drops %s.", Monnam(mon), distant_name(obj, doname)); + pline_mon(mon, "%s drops %s.", Monnam(mon), distant_name(obj, doname)); newsym(mon->mx, mon->my); } + obj_extract_self(obj); /* might be dropping object into water or lava */ if (!flooreffects(obj, mon->mx, mon->my, "drop")) { if (polyspot) @@ -746,10 +798,10 @@ boolean polyspot; * Returns 1 if the monster took time to do it, 0 if it did not. */ int -mon_wield_item(mon) -register struct monst *mon; +mon_wield_item(struct monst *mon) { struct obj *obj; + boolean exclaim = TRUE; /* assume mon is planning to attack */ /* This case actually should never happen */ if (mon->weapon_check == NO_WEAPON_WANTED) @@ -760,19 +812,21 @@ register struct monst *mon; break; case NEED_RANGED_WEAPON: (void) select_rwep(mon); - obj = propellor; + obj = gp.propellor; break; case NEED_PICK_AXE: obj = m_carrying(mon, PICK_AXE); /* KMH -- allow other picks */ if (!obj && !which_armor(mon, W_ARMS)) obj = m_carrying(mon, DWARVISH_MATTOCK); + exclaim = FALSE; /* mon is just planning to dig */ break; case NEED_AXE: /* currently, only 2 types of axe */ obj = m_carrying(mon, BATTLE_AXE); if (!obj || which_armor(mon, W_ARMS)) obj = m_carrying(mon, AXE); + exclaim = FALSE; break; case NEED_PICK_OR_AXE: /* prefer pick for fewer switches on most levels */ @@ -784,13 +838,14 @@ register struct monst *mon; if (!obj) obj = m_carrying(mon, AXE); } + exclaim = FALSE; break; default: impossible("weapon_check %d for %s?", mon->weapon_check, mon_nam(mon)); return 0; } - if (obj && obj != &zeroobj) { + if (obj && obj != &hands_obj) { struct obj *mw_tmp = MON_WEP(mon); if (mw_tmp && mw_tmp->otyp == obj->otyp) { @@ -819,7 +874,7 @@ register struct monst *mon; pline("%s cannot wield that %s.", mon_nam(mon), xname(obj)); } else { - pline("%s tries to wield %s.", Monnam(mon), doname(obj)); + pline_mon(mon, "%s tries to wield %s.", Monnam(mon), doname(obj)); pline("%s %s!", Yname2(mw_tmp), welded_buf); } mw_tmp->bknown = 1; @@ -832,8 +887,15 @@ register struct monst *mon; mon->weapon_check = NEED_WEAPON; if (canseemon(mon)) { boolean newly_welded; + const struct throw_and_return_weapon *arw; + + pline_mon(mon, "%s wields %s%c", + Monnam(mon), doname(obj), + exclaim ? '!' : '.'); + if ((arw = autoreturn_weapon(obj)) != 0 && arw->tethered != 0) + pline_mon(mon, "%s secures the tether on %s.", Monnam(mon), + the(xname(obj))); - pline("%s wields %s!", Monnam(mon), doname(obj)); /* 3.6.3: mwelded() predicate expects the object to have its W_WEP bit set in owormmask, but the pline here and for artifact_light don't want that because they'd have '(weapon @@ -862,9 +924,7 @@ register struct monst *mon; /* 3.6.3: artifact might be getting wielded by invisible monst */ else if (cansee(mon->mx, mon->my)) pline("Light begins shining %s.", - (distu(mon->mx, mon->my) <= 5 * 5) - ? "nearby" - : "in the distance"); + (mdistu(mon) <= 5 * 5) ? "nearby" : "in the distance"); } obj->owornmask = W_WEP; return 1; @@ -875,8 +935,7 @@ register struct monst *mon; /* force monster to stop wielding current weapon, if any */ void -mwepgone(mon) -struct monst *mon; +mwepgone(struct monst *mon) { struct obj *mwep = MON_WEP(mon); @@ -888,21 +947,26 @@ struct monst *mon; /* attack bonus for strength & dexterity */ int -abon() +abon(void) { int sbon; int str = ACURR(A_STR), dex = ACURR(A_DEX); if (Upolyd) return (adj_lev(&mons[u.umonnum]) - 3); + + /* this used to be '<= 18/50' for bonus of 1 but got changed to '< 18/50' + so that '18/50' gives a bonus of 2; gnome and orc player characters + have max Str of 18/50 and giving an extra bonus at that break point + provides an incentive for them to max out that characteristic */ if (str < 6) sbon = -2; else if (str < 8) sbon = -1; else if (str < 17) sbon = 0; - else if (str <= STR18(50)) - sbon = 1; /* up to 18/50 */ + else if (str < STR18(50)) + sbon = 1; /* up to 18/49 */ else if (str < STR18(100)) sbon = 2; else @@ -926,7 +990,7 @@ abon() /* damage bonus for strength */ int -dbon() +dbon(void) { int str = ACURR(A_STR); @@ -951,12 +1015,30 @@ dbon() return 6; } +/* called when wet_a_towel() or dry_a_towel() is changing a towel's wetness */ +staticfn void +finish_towel_change(struct obj *obj, int newspe) +{ + /* towel wetness is always between 0 (dry) and 7, inclusive */ + newspe = min(newspe, 7); + obj->spe = max(newspe, 0); + + /* if hero is wielding this towel, don't give "you begin bashing with + your [wet] towel" message if it's wet, do give one if it's dry */ + if (obj == uwep) + gu.unweapon = !is_wet_towel(obj); + + /* description might change: "towel" vs "moist towel" vs "wet towel" */ + if (carried(obj)) + update_inventory(); +} + /* increase a towel's wetness */ void -wet_a_towel(obj, amt, verbose) -struct obj *obj; -int amt; /* positive: new value; negative: increment by -amt; zero: no-op */ -boolean verbose; +wet_a_towel( + struct obj *obj, + int amt, /* positive: new val; negative: increment by -amt; zero: no-op */ + boolean verbose) { int newspe = (amt <= 0) ? obj->spe - amt : amt; @@ -975,22 +1057,19 @@ boolean verbose; xname(obj), wetness); } } - obj->spe = min(newspe, 7); - /* if hero is wielding this towel, don't give "you begin bashing - with your wet towel" message on next attack with it */ - if (obj == uwep) - unweapon = !is_wet_towel(obj); + if (newspe != obj->spe) + finish_towel_change(obj, newspe); } -/* decrease a towel's wetness */ +/* decrease a towel's wetness; unlike when wetting, 0 is not a no-op */ void -dry_a_towel(obj, amt, verbose) -struct obj *obj; -int amt; /* positive: new value; negative: decrement by -amt; zero: no-op */ -boolean verbose; +dry_a_towel( + struct obj *obj, + int amt, /* positive or zero: new value; negative: decrement by abs(amt) */ + boolean verbose) { - int newspe = (amt <= 0) ? obj->spe + amt : amt; + int newspe = (amt < 0) ? obj->spe + amt : amt; /* new state is only reported if it's a decrease */ if (newspe < obj->spe) { @@ -999,24 +1078,18 @@ boolean verbose; pline("%s dries%s.", Yobjnam2(obj, (const char *) 0), !newspe ? " out" : ""); else if (mcarried(obj) && canseemon(obj->ocarry)) - pline("%s %s drie%s.", s_suffix(Monnam(obj->ocarry)), + pline("%s %s dries%s.", s_suffix(Monnam(obj->ocarry)), xname(obj), !newspe ? " out" : ""); } } - newspe = min(newspe, 7); - obj->spe = max(newspe, 0); - /* if hero is wielding this towel and it is now dry, give "you begin - bashing with your towel" message on next attack with it */ - if (obj == uwep) - unweapon = !is_wet_towel(obj); + if (newspe != obj->spe) + finish_towel_change(obj, newspe); } /* copy the skill level name into the given buffer */ char * -skill_level_name(skill, buf) -int skill; -char *buf; +skill_level_name(int skill, char *buf) { const char *ptr; @@ -1049,16 +1122,14 @@ char *buf; } const char * -skill_name(skill) -int skill; +skill_name(int skill) { return P_NAME(skill); } /* return the # of slots required to advance the skill */ -STATIC_OVL int -slots_required(skill) -int skill; +staticfn int +slots_required(int skill) { int tmp = P_SKILL(skill); @@ -1082,9 +1153,7 @@ int skill; /* return true if this skill can be advanced */ boolean -can_advance(skill, speedy) -int skill; -boolean speedy; +can_advance(int skill, boolean speedy) { if (P_RESTRICTED(skill) || P_SKILL(skill) >= P_MAX_SKILL(skill) @@ -1100,9 +1169,8 @@ boolean speedy; } /* return true if this skill could be advanced if more slots were available */ -STATIC_OVL boolean -could_advance(skill) -int skill; +staticfn boolean +could_advance(int skill) { if (P_RESTRICTED(skill) || P_SKILL(skill) >= P_MAX_SKILL(skill) @@ -1115,9 +1183,8 @@ int skill; /* return true if this skill has reached its maximum and there's been enough practice to become eligible for the next step if that had been possible */ -STATIC_OVL boolean -peaked_skill(skill) -int skill; +staticfn boolean +peaked_skill(int skill) { if (P_RESTRICTED(skill)) return FALSE; @@ -1127,9 +1194,8 @@ int skill; >= practice_needed_to_advance(P_SKILL(skill)))); } -STATIC_OVL void -skill_advance(skill) -int skill; +staticfn void +skill_advance(int skill) { u.weapon_slots -= slots_required(skill); P_SKILL(skill)++; @@ -1138,6 +1204,12 @@ int skill; You("are now %s skilled in %s.", P_SKILL(skill) >= P_MAX_SKILL(skill) ? "most" : "more", P_NAME(skill)); + + /* wizards discover spellbook IDs depending on spell 'school' skill limits; + this allows them to successfully write books for unknown spells without + the Luck bias they used to have over other roles */ + if (skill >= P_FIRST_SPELL && skill <= P_LAST_SPELL) + skill_based_spellbook_id(); } static const struct skill_range { @@ -1149,6 +1221,102 @@ static const struct skill_range { { P_FIRST_SPELL, P_LAST_SPELL, "Spellcasting Skills" }, }; +/* write a list of skills onto the given menu + + if selectable is set, give selection letters for skills that can be + advanced and leave room for them on skills that can't be advanced */ +void +add_skills_to_menu(winid win, boolean selectable, boolean speedy) +{ + int pass, i, len, longest; + anything any; + char buf[BUFSZ], sklnambuf[BUFSZ]; + const char *prefix; + int clr = NO_COLOR; + + /* Find the longest skill name. */ + for (longest = 0, i = 0; i < P_NUM_SKILLS; i++) { + if (P_RESTRICTED(i)) + continue; + if ((len = Strlen(P_NAME(i))) > longest) + longest = len; + } + + /* List the skills, making ones that could be advanced selectable if + selectable is set. List the miscellaneous skills first. Possible + future enhancement: list spell skills before weapon skills for + spellcaster roles. */ + for (pass = 0; pass < SIZE(skill_ranges); pass++) + for (i = skill_ranges[pass].first; i <= skill_ranges[pass].last; + i++) { + /* Print headings for skill types */ + any = cg.zeroany; + if (i == skill_ranges[pass].first) + add_menu_heading(win, skill_ranges[pass].name); + + if (P_RESTRICTED(i)) + continue; + /* + * Sigh, this assumes a monospaced font unless + * iflags.menu_tab_sep is set in which case it puts + * tabs between columns. + * The 12 is the longest skill level name. + * The " " is room for a selection letter and dash, "a - ". + */ + if (!selectable) + prefix = ""; + else if (can_advance(i, speedy)) + prefix = ""; /* will be preceded by menu choice */ + else if (could_advance(i)) + prefix = " * "; + else if (peaked_skill(i)) + prefix = " # "; + else + prefix = " "; + (void) skill_level_name(i, sklnambuf); + if (wizard) { + if (!iflags.menu_tab_sep) + Snprintf(buf, sizeof buf, + " %s%-*s %-12s %5d(%4d)", prefix, + longest, P_NAME(i), sklnambuf, P_ADVANCE(i), + practice_needed_to_advance(P_SKILL(i))); + else + Snprintf(buf, sizeof buf, + " %s%s\t%s\t%5d(%4d)", prefix, P_NAME(i), + sklnambuf, P_ADVANCE(i), + practice_needed_to_advance(P_SKILL(i))); + } else { + if (!iflags.menu_tab_sep) + Snprintf(buf, sizeof buf, + " %s %-*s [%s]", prefix, longest, + P_NAME(i), sklnambuf); + else + Snprintf(buf, sizeof buf, + " %s%s\t[%s]", prefix, P_NAME(i), + sklnambuf); + } + any.a_int = selectable && can_advance(i, speedy) ? i + 1 : 0; + add_menu(win, &nul_glyphinfo, &any, 0, 0, + ATR_NONE, clr, buf, MENU_ITEMFLAGS_NONE); + } +} + +/* Displays a skill list for dumplog purposes. */ +void +show_skills(void) +{ + winid win; + menu_item *selected; + + pline("Skills:"); + win = create_nhwindow(NHW_MENU); + start_menu(win, MENU_BEHAVE_STANDARD); + add_skills_to_menu(win, FALSE, FALSE); + end_menu(win, ""); + nhUse(select_menu(win, PICK_NONE, &selected)); + destroy_nhwindow(win); +} + /* * The `#enhance' extended command. What we _really_ would like is * to keep being able to pick things to advance until we couldn't any @@ -1158,27 +1326,26 @@ static const struct skill_range { * others unselectable. */ int -enhance_weapon_skill() +enhance_weapon_skill(void) { - int pass, i, n, len, longest, to_advance, eventually_advance, maxxed_cnt; - char buf[BUFSZ], sklnambuf[BUFSZ]; - const char *prefix; + int i, n, to_advance, eventually_advance, maxxed_cnt; + char buf[BUFSZ]; menu_item *selected; - anything any; winid win; boolean speedy = FALSE; - if (wizard && yn("Advance skills without practice?") == 'y') + /* player knows about #enhance, don't show tip anymore */ + svc.context.tips |= (1 << TIP_ENHANCE); + + if (wizard && y_n("Advance skills without practice?") == 'y') speedy = TRUE; do { - /* find longest available skill name, count those that can advance */ + /* count advanceable skills */ to_advance = eventually_advance = maxxed_cnt = 0; - for (longest = 0, i = 0; i < P_NUM_SKILLS; i++) { + for (i = 0; i < P_NUM_SKILLS; i++) { if (P_RESTRICTED(i)) continue; - if ((len = strlen(P_NAME(i))) > longest) - longest = len; if (can_advance(i, speedy)) to_advance++; else if (could_advance(i)) @@ -1188,87 +1355,30 @@ enhance_weapon_skill() } win = create_nhwindow(NHW_MENU); - start_menu(win); + start_menu(win, MENU_BEHAVE_STANDARD); /* start with a legend if any entries will be annotated with "*" or "#" below */ if (eventually_advance > 0 || maxxed_cnt > 0) { - any = zeroany; if (eventually_advance > 0) { Sprintf(buf, "(Skill%s flagged by \"*\" may be enhanced %s.)", plur(eventually_advance), (u.ulevel < MAXULEV) ? "when you're more experienced" : "if skill slots become available"); - add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, - MENU_UNSELECTED); + add_menu_str(win, buf); } if (maxxed_cnt > 0) { Sprintf(buf, "(Skill%s flagged by \"#\" cannot be enhanced any further.)", plur(maxxed_cnt)); - add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, - MENU_UNSELECTED); + add_menu_str(win, buf); } - add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, "", - MENU_UNSELECTED); + add_menu_str(win, ""); } - /* List the skills, making ones that could be advanced - selectable. List the miscellaneous skills first. - Possible future enhancement: list spell skills before - weapon skills for spellcaster roles. */ - for (pass = 0; pass < SIZE(skill_ranges); pass++) - for (i = skill_ranges[pass].first; i <= skill_ranges[pass].last; - i++) { - /* Print headings for skill types */ - any = zeroany; - if (i == skill_ranges[pass].first) - add_menu(win, NO_GLYPH, &any, 0, 0, iflags.menu_headings, - skill_ranges[pass].name, MENU_UNSELECTED); - - if (P_RESTRICTED(i)) - continue; - /* - * Sigh, this assumes a monospaced font unless - * iflags.menu_tab_sep is set in which case it puts - * tabs between columns. - * The 12 is the longest skill level name. - * The " " is room for a selection letter and dash, "a - ". - */ - if (can_advance(i, speedy)) - prefix = ""; /* will be preceded by menu choice */ - else if (could_advance(i)) - prefix = " * "; - else if (peaked_skill(i)) - prefix = " # "; - else - prefix = - (to_advance + eventually_advance + maxxed_cnt > 0) - ? " " - : ""; - (void) skill_level_name(i, sklnambuf); - if (wizard) { - if (!iflags.menu_tab_sep) - Sprintf(buf, " %s%-*s %-12s %5d(%4d)", prefix, - longest, P_NAME(i), sklnambuf, P_ADVANCE(i), - practice_needed_to_advance(P_SKILL(i))); - else - Sprintf(buf, " %s%s\t%s\t%5d(%4d)", prefix, P_NAME(i), - sklnambuf, P_ADVANCE(i), - practice_needed_to_advance(P_SKILL(i))); - } else { - if (!iflags.menu_tab_sep) - Sprintf(buf, " %s %-*s [%s]", prefix, longest, - P_NAME(i), sklnambuf); - else - Sprintf(buf, " %s%s\t[%s]", prefix, P_NAME(i), - sklnambuf); - } - any.a_int = can_advance(i, speedy) ? i + 1 : 0; - add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, - MENU_UNSELECTED); - } + add_skills_to_menu( + win, to_advance + eventually_advance + maxxed_cnt > 0, speedy); Strcpy(buf, (to_advance > 0) ? "Pick a skill to advance:" : "Current skills:"); @@ -1282,7 +1392,7 @@ enhance_weapon_skill() n = selected[0].item.a_int - 1; /* get item selected */ free((genericptr_t) selected); skill_advance(n); - /* check for more skills able to advance, if so then .. */ + /* check for more skills able to advance; if so, then... */ for (n = i = 0; i < P_NUM_SKILLS; i++) { if (can_advance(i, speedy)) { if (!speedy) @@ -1293,7 +1403,7 @@ enhance_weapon_skill() } } } while (speedy && n > 0); - return 0; + return ECMD_OK; } /* @@ -1301,8 +1411,7 @@ enhance_weapon_skill() * function may be called with with P_NONE. Used in pray.c as well as below. */ void -unrestrict_weapon_skill(skill) -int skill; +unrestrict_weapon_skill(int skill) { if (skill < P_NUM_SKILLS && P_RESTRICTED(skill)) { P_SKILL(skill) = P_UNSKILLED; @@ -1312,9 +1421,7 @@ int skill; } void -use_skill(skill, degree) -int skill; -int degree; +use_skill(int skill, int degree) { boolean advance_before; @@ -1327,8 +1434,7 @@ int degree; } void -add_weapon_skill(n) -int n; /* number of slots to gain; normally one */ +add_weapon_skill(int n) /* number of slots to gain; normally one */ { int i, before, after; @@ -1344,8 +1450,7 @@ int n; /* number of slots to gain; normally one */ } void -lose_weapon_skill(n) -int n; /* number of slots to lose; normally one */ +lose_weapon_skill(int n) /* number of slots to lose; normally one */ { int skill; @@ -1367,9 +1472,49 @@ int n; /* number of slots to lose; normally one */ } } +void +drain_weapon_skill(int n) /* number of skills to drain */ +{ + int skill; + int i; + int curradv; + int prevadv; + int tmpskills[P_NUM_SKILLS]; + + (void) memset((genericptr_t) tmpskills, 0, sizeof(tmpskills)); + + while (--n >= 0) { + if (u.skills_advanced) { + /* Pick a random skill, deleting it from the list. */ + i = rn2(u.skills_advanced); + skill = u.skill_record[i]; + tmpskills[skill] = 1; + for (; i < u.skills_advanced - 1; i++) { + u.skill_record[i] = u.skill_record[i + 1]; + } + u.skills_advanced--; + if (P_SKILL(skill) <= P_UNSKILLED) + panic("drain_weapon_skill (%d)", skill); + P_SKILL(skill)--; /* drop skill one level */ + /* refund slots used for skill */ + u.weapon_slots += slots_required(skill); + /* drain skill training to a value appropriate for new level */ + curradv = practice_needed_to_advance(P_SKILL(skill)); + prevadv = practice_needed_to_advance(P_SKILL(skill) - 1); + if (P_ADVANCE(skill) >= curradv) + P_ADVANCE(skill) = prevadv + rn2(curradv - prevadv); + } + } + + for (skill = 0; skill < P_NUM_SKILLS; skill++) + if (tmpskills[skill]) { + You("forget %syour training in %s.", + P_SKILL(skill) >= P_BASIC ? "some of " : "", P_NAME(skill)); + } +} + int -weapon_type(obj) -struct obj *obj; +weapon_type(struct obj *obj) { /* KMH -- now uses the object table */ int type; @@ -1384,7 +1529,7 @@ struct obj *obj; } int -uwep_skill_type() +uwep_skill_type(void) { if (u.twoweap) return P_TWO_WEAPON_COMBAT; @@ -1393,11 +1538,11 @@ uwep_skill_type() /* * Return hit bonus/penalty based on skill of weapon. + * weapon can be null, meaning bare-handed combat. * Treat restricted weapons as unskilled. */ int -weapon_hit_bonus(weapon) -struct obj *weapon; +weapon_hit_bonus(struct obj *weapon) { int type, wep_type, skill, bonus = 0; static const char bad_skill[] = "weapon_hit_bonus: bad skill %d"; @@ -1413,7 +1558,9 @@ struct obj *weapon; } else if (type <= P_LAST_WEAPON) { switch (P_SKILL(type)) { default: - impossible(bad_skill, P_SKILL(type)); /* fall through */ + impossible(bad_skill, P_SKILL(type)); + FALLTHROUGH; + /* FALLTHRU */ case P_ISRESTRICTED: case P_UNSKILLED: bonus = -4; @@ -1434,7 +1581,9 @@ struct obj *weapon; skill = P_SKILL(wep_type); switch (skill) { default: - impossible(bad_skill, skill); /* fall through */ + impossible(bad_skill, skill); + FALLTHROUGH; + /* FALLTHRU */ case P_ISRESTRICTED: case P_UNSKILLED: bonus = -9; @@ -1488,11 +1637,11 @@ struct obj *weapon; /* * Return damage bonus/penalty based on skill of weapon. + * weapon can be null, meaning bare-handed combat. * Treat restricted weapons as unskilled. */ int -weapon_dam_bonus(weapon) -struct obj *weapon; +weapon_dam_bonus(struct obj *weapon) { int type, wep_type, skill, bonus = 0; @@ -1508,7 +1657,8 @@ struct obj *weapon; switch (P_SKILL(type)) { default: impossible("weapon_dam_bonus: bad skill %d", P_SKILL(type)); - /* fall through */ + FALLTHROUGH; + /* FALLTHRU */ case P_ISRESTRICTED: case P_UNSKILLED: bonus = -2; @@ -1585,8 +1735,7 @@ struct obj *weapon; * maximums. */ void -skill_init(class_skill) -const struct def_skill *class_skill; +skill_init(const struct def_skill *class_skill) { struct obj *obj; int skmax, skill; @@ -1599,7 +1748,7 @@ const struct def_skill *class_skill; } /* Set skill for all weapons in inventory to be basic */ - for (obj = invent; obj; obj = obj->nobj) { + for (obj = gi.invent; obj; obj = obj->nobj) { /* don't give skill just because of carried ammo, wait until we see the relevant launcher (prevents an archeologist's touchstone from inadvertently providing skill in sling) */ @@ -1614,7 +1763,7 @@ const struct def_skill *class_skill; /* set skills for magic */ if (Role_if(PM_HEALER) || Role_if(PM_MONK)) { P_SKILL(P_HEALING_SPELL) = P_BASIC; - } else if (Role_if(PM_PRIEST)) { + } else if (Role_if(PM_CLERIC)) { P_SKILL(P_CLERIC_SPELL) = P_BASIC; } else if (Role_if(PM_WIZARD)) { P_SKILL(P_ATTACK_SPELL) = P_BASIC; @@ -1636,7 +1785,7 @@ const struct def_skill *class_skill; P_SKILL(P_BARE_HANDED_COMBAT) = P_BASIC; /* Roles that start with a horse know how to ride it */ - if (urole.petnum == PM_PONY) + if (gu.urole.petnum == PM_PONY) P_SKILL(P_RIDING) = P_BASIC; /* @@ -1655,13 +1804,14 @@ const struct def_skill *class_skill; /* each role has a special spell; allow at least basic for its type (despite the function name, this works for spell skills too) */ - unrestrict_weapon_skill(spell_skilltype(urole.spelspec)); + unrestrict_weapon_skill(spell_skilltype(gu.urole.spelspec)); + + if (!u.uroleplay.pauper) /* paupers lack advanced access to books */ + skill_based_spellbook_id(); } void -setmnotwielded(mon, obj) -register struct monst *mon; -register struct obj *obj; +setmnotwielded(struct monst *mon, struct obj *obj) { if (!obj) return; @@ -1677,4 +1827,19 @@ register struct obj *obj; obj->owornmask &= ~W_WEP; } +#undef PN_BARE_HANDED +#undef PN_RIDING +#undef PN_POLEARMS +#undef PN_SABER +#undef PN_HAMMER +#undef PN_WHIP +#undef PN_ATTACK_SPELL +#undef PN_HEALING_SPELL +#undef PN_DIVINATION_SPELL +#undef PN_ENCHANTMENT_SPELL +#undef PN_CLERIC_SPELL +#undef PN_ESCAPE_SPELL +#undef PN_MATTER_SPELL +#undef AKLYS_LIM + /*weapon.c*/ diff --git a/src/were.c b/src/were.c index eb1a4d090..4f1b81255 100644 --- a/src/were.c +++ b/src/were.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 were.c $NHDT-Date: 1550524568 2019/02/18 21:16:08 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.23 $ */ +/* NetHack 5.0 were.c $NHDT-Date: 1766588485 2025/12/24 07:01:25 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.41 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2011. */ /* NetHack may be freely redistributed. See license for details. */ @@ -6,8 +6,7 @@ #include "hack.h" void -were_change(mon) -register struct monst *mon; +were_change(struct monst *mon) { if (!is_were(mon->data)) return; @@ -17,6 +16,7 @@ register struct monst *mon; && !rn2(night() ? (flags.moonphase == FULL_MOON ? 3 : 30) : (flags.moonphase == FULL_MOON ? 10 : 50))) { new_were(mon); /* change into animal form */ + gw.were_changes++; if (!Deaf && !canseemon(mon)) { const char *howler; @@ -31,20 +31,21 @@ register struct monst *mon; howler = (char *) 0; break; } - if (howler) + if (howler) { + Soundeffect(se_canine_howl, 50); You_hear("a %s howling at the moon.", howler); + wake_nearto(mon->mx, mon->my, 4 * 4); + } } } } else if (!rn2(30) || Protection_from_shape_changers) { new_were(mon); /* change back into human form */ + gw.were_changes++; } - /* update innate intrinsics (mainly Drain_resistance) */ - set_uasmon(); /* new_were() doesn't do this */ } int -counter_were(pm) -int pm; +counter_were(int pm) { switch (pm) { case PM_WEREWOLF: @@ -66,8 +67,7 @@ int pm; /* convert monsters similar to werecritters into appropriate werebeast */ int -were_beastie(pm) -int pm; +were_beastie(int pm) { switch (pm) { case PM_WERERAT: @@ -84,6 +84,7 @@ int pm; case PM_WOLF: case PM_WARG: case PM_WINTER_WOLF: + case PM_WINTER_WOLF_CUB: return PM_WEREWOLF; default: break; @@ -92,42 +93,57 @@ int pm; } void -new_were(mon) -register struct monst *mon; +new_were(struct monst *mon) { - register int pm; + int pm; + + /* neither hero nor werecreature can change from human form to + critter form if hero has Protection_from_shape_changers extrinsic; + if already in critter form, always change to human form for that */ + if (Protection_from_shape_changers && is_human(mon->data)) + return; pm = counter_were(monsndx(mon->data)); if (pm < LOW_PM) { - impossible("unknown lycanthrope %s.", mon->data->mname); + impossible("unknown lycanthrope %s.", + mon->data->pmnames[NEUTRAL]); return; } if (canseemon(mon) && !Hallucination) pline("%s changes into a %s.", Monnam(mon), - is_human(&mons[pm]) ? "human" : mons[pm].mname + 4); + is_human(&mons[pm]) ? "human" + /* pmname()+4: skip past "were" prefix */ + : pmname(&mons[pm], Mgender(mon)) + 4); set_mon_data(mon, &mons[pm]); - if (mon->msleeping || !mon->mcanmove) { + if (helpless(mon)) { /* transformation wakens and/or revitalizes */ mon->msleeping = 0; mon->mfrozen = 0; /* not asleep or paralyzed */ mon->mcanmove = 1; } /* regenerate by 1/4 of the lost hit points */ - mon->mhp += (mon->mhpmax - mon->mhp) / 4; + healmon(mon, (mon->mhpmax - mon->mhp) / 4, 0); newsym(mon->mx, mon->my); mon_break_armor(mon, FALSE); possibly_unwield(mon, FALSE); + + /* vision capability isn't changing so we don't call set_apparxy() to + update mon's idea of where hero is; peaceful check is redundant */ + if (svc.context.mon_moving && !mon->mpeaceful + && onscary(mon->mux, mon->muy, mon) + && monnear(mon, mon->mux, mon->muy)) + monflee(mon, rn1(9, 2), TRUE, TRUE); /* 2..10 turns */ } /* were-creature (even you) summons a horde */ int -were_summon(ptr, yours, visible, genbuf) -struct permonst *ptr; -boolean yours; -int *visible; /* number of visible helpers created */ -char *genbuf; +were_summon( + struct permonst *ptr, + boolean yours, + int *visible, /* number of visible helpers created */ + char *genbuf) { int i, typ, pm = monsndx(ptr); struct monst *mtmp; @@ -167,13 +183,13 @@ char *genbuf; *visible += 1; } if (yours && mtmp) - (void) tamedog(mtmp, (struct obj *) 0); + (void) tamedog(mtmp, (struct obj *) 0, FALSE); } return total; } void -you_were() +you_were(void) { char qbuf[QBUFSZ]; boolean controllable_poly = Polymorph_control && !(Stunned || Unaware); @@ -183,16 +199,18 @@ you_were() if (controllable_poly) { /* `+4' => skip "were" prefix to get name of beast */ Sprintf(qbuf, "Do you want to change into %s?", - an(mons[u.ulycn].mname + 4)); + an(mons[u.ulycn].pmnames[NEUTRAL] + 4)); if (!paranoid_query(ParanoidWerechange, qbuf)) return; + } else if (monster_nearby()) { + return; } + gw.were_changes++; (void) polymon(u.ulycn); } void -you_unwere(purify) -boolean purify; +you_unwere(boolean purify) { boolean controllable_poly = Polymorph_control && !(Stunned || Unaware); @@ -200,18 +218,18 @@ boolean purify; You_feel("purified."); set_ulycn(NON_PM); /* cure lycanthropy */ } - if (!Unchanging && is_were(youmonst.data) + if (!Unchanging && is_were(gy.youmonst.data) + && !monster_nearby() && (!controllable_poly || !paranoid_query(ParanoidWerechange, "Remain in beast form?"))) rehumanize(); - else if (is_were(youmonst.data) && !u.mtimedone) + else if (is_were(gy.youmonst.data) && !u.mtimedone) u.mtimedone = rn1(200, 200); /* 40% of initial were change */ } /* lycanthropy is being caught or cured, but no shape change is involved */ void -set_ulycn(which) -int which; +set_ulycn(int which) { u.ulycn = which; /* add or remove lycanthrope's innate intrinsics (Drain_resistance) */ diff --git a/src/wield.c b/src/wield.c index 18c276a2e..7c75cab31 100644 --- a/src/wield.c +++ b/src/wield.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 wield.c $NHDT-Date: 1559670611 2019/06/04 17:50:11 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.59 $ */ +/* NetHack 5.0 wield.c $NHDT-Date: 1707525193 2024/02/10 00:33:13 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.110 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2009. */ /* NetHack may be freely redistributed. See license for details. */ @@ -52,8 +52,11 @@ * No item may be in more than one of these slots. */ -STATIC_DCL boolean FDECL(cant_wield_corpse, (struct obj *)); -STATIC_DCL int FDECL(ready_weapon, (struct obj *)); +staticfn boolean cant_wield_corpse(struct obj *) NONNULLARG1; +staticfn int ready_weapon(struct obj *) NO_NNARGS; +staticfn int ready_ok(struct obj *) NO_NNARGS; +staticfn int wield_ok(struct obj *) NO_NNARGS; +staticfn void finish_splitting(struct obj *); /* used by will_weld() */ /* probably should be renamed */ @@ -65,58 +68,74 @@ STATIC_DCL int FDECL(ready_weapon, (struct obj *)); #define will_weld(optr) \ ((optr)->cursed && (erodeable_wep(optr) || (optr)->otyp == TIN_OPENER)) +/* to dual-wield, 'obj' must be a weapon or a weapon-tool, and not a bow + or arrow or missile (dart, shuriken, boomerang), so not matching the + one-handed weapons which yield "you begin bashing" if used for melee; + empty hands and two-handed weapons have to be handled separately */ +#define TWOWEAPOK(obj) \ + (((obj)->oclass == WEAPON_CLASS) \ + ? !(is_launcher(obj) || is_ammo(obj) || is_missile(obj)) \ + : is_weptool(obj)) + +static const char + are_no_longer_twoweap[] = "are no longer using two weapons at once", + can_no_longer_twoweap[] = "can no longer wield two weapons at once"; + /*** Functions that place a given item in a slot ***/ /* Proper usage includes: * 1. Initializing the slot during character generation or a * restore. * 2. Setting the slot due to a player's actions. - * 3. If one of the objects in the slot are split off, these + * 3. If one of the objects in the slot is split off, these * functions can be used to put the remainder back in the slot. * 4. Putting an item that was thrown and returned back into the slot. * 5. Emptying the slot, by passing a null object. NEVER pass - * zeroobj! + * cg.zeroobj! * * If the item is being moved from another slot, it is the caller's * responsibility to handle that. It's also the caller's responsibility * to print the appropriate messages. */ void -setuwep(obj) -register struct obj *obj; +setuwep(struct obj *obj) { struct obj *olduwep = uwep; if (obj == uwep) - return; /* necessary to not set unweapon */ - /* This message isn't printed in the caller because it happens - * *whenever* Sunsword is unwielded, from whatever cause. - */ + return; /* necessary to not set gu.unweapon */ setworn(obj, W_WEP); + /* handle Ogresmasher before Sunsword; even though they can't be happening + at the same time, botl flag update should come before pline message */ + if (uwep == obj + && ((uwep && uwep->oartifact == ART_OGRESMASHER) + || (olduwep && olduwep->oartifact == ART_OGRESMASHER))) + disp.botl = TRUE; /* gaining or losing Con bonus */ + /* This message isn't printed in the caller because it happens + * *whenever* Sunsword is unwielded, from whatever cause. */ if (uwep == obj && artifact_light(olduwep) && olduwep->lamplit) { end_burn(olduwep, FALSE); if (!Blind) pline("%s shining.", Tobjnam(olduwep, "stop")); } if (uwep == obj - && ((uwep && uwep->oartifact == ART_OGRESMASHER) - || (olduwep && olduwep->oartifact == ART_OGRESMASHER))) - context.botl = 1; + && (u_wield_art(ART_OGRESMASHER) + || is_art(olduwep, ART_OGRESMASHER))) + disp.botl = TRUE; /* Note: Explicitly wielding a pick-axe will not give a "bashing" * message. Wielding one via 'a'pplying it will. * 3.2.2: Wielding arbitrary objects will give bashing message too. */ if (obj) { - unweapon = (obj->oclass == WEAPON_CLASS) + gu.unweapon = (obj->oclass == WEAPON_CLASS) ? is_launcher(obj) || is_ammo(obj) || is_missile(obj) - || (is_pole(obj) && !u.usteed) + || (is_pole(obj) && !u.usteed && !is_art(obj, ART_SNICKERSNEE)) : !is_weptool(obj) && !is_wet_towel(obj); } else - unweapon = TRUE; /* for "bare hands" message */ + gu.unweapon = TRUE; /* for "bare hands" message */ } -STATIC_OVL boolean -cant_wield_corpse(obj) -struct obj *obj; +staticfn boolean +cant_wield_corpse(struct obj *obj) { char kbuf[BUFSZ]; @@ -133,33 +152,47 @@ struct obj *obj; return TRUE; } -STATIC_OVL int -ready_weapon(wep) -struct obj *wep; +/* description of hands when not wielding anything; also used + by #seeweapon (')'), #attributes (^X), and #takeoffall ('A') */ +const char * +empty_handed(void) +{ + return uarmg ? "empty handed" /* gloves imply hands */ + : humanoid(gy.youmonst.data) + /* hands but no weapon and no gloves */ + ? "bare handed" + /* alternate phrasing for paws or lack of hands */ + : "not wielding anything"; +} + +staticfn int +ready_weapon(struct obj *wep) { /* Separated function so swapping works easily */ - int res = 0; + int res = ECMD_OK; + boolean was_twoweap = u.twoweap, had_wep = (uwep != 0); if (!wep) { /* No weapon */ if (uwep) { - You("are empty %s.", body_part(HANDED)); + You("are %s.", empty_handed()); setuwep((struct obj *) 0); - res++; + res = ECMD_TIME; } else - You("are already empty %s.", body_part(HANDED)); + You("are already %s.", empty_handed()); } else if (wep->otyp == CORPSE && cant_wield_corpse(wep)) { /* hero must have been life-saved to get here; use a turn */ - res++; /* corpse won't be wielded */ + res = ECMD_TIME; /* corpse won't be wielded */ } else if (uarms && bimanual(wep)) { You("cannot wield a two-handed %s while wearing a shield.", is_sword(wep) ? "sword" : wep->otyp == BATTLE_AXE ? "axe" : "weapon"); + res = ECMD_FAIL; } else if (!retouch_object(&wep, FALSE)) { - res++; /* takes a turn even though it doesn't get wielded */ + res = ECMD_TIME; /* takes a turn even though it doesn't get wielded */ } else { /* Weapon WILL be wielded after this point */ - res++; + res = ECMD_TIME; if (will_weld(wep)) { const char *tmp = xname(wep), *thestr = "The "; @@ -167,8 +200,10 @@ struct obj *wep; tmp = thestr; else tmp = ""; - pline("%s%s %s to your %s!", tmp, aobjnam(wep, "weld"), + pline("%s%s %s to your %s%s!", tmp, aobjnam(wep, "weld"), (wep->quan == 1L) ? "itself" : "themselves", /* a3 */ + bimanual(wep) ? "" : + (URIGHTY ? "dominant right " : "dominant left "), bimanual(wep) ? (const char *) makeplural(body_part(HAND)) : body_part(HAND)); set_bknown(wep, 1); @@ -191,10 +226,21 @@ struct obj *wep; prinv((char *) 0, wep, 0L); wep->owornmask = dummy; } + setuwep(wep); + if (was_twoweap && !u.twoweap && flags.verbose) { + /* skip this message if we already got "empty handed" one above; + also, Null is not safe for neither TWOWEAPOK() or bimanual() */ + if (uwep) + You("%s.", ((TWOWEAPOK(uwep) && !bimanual(uwep)) + ? are_no_longer_twoweap + : can_no_longer_twoweap)); + } /* KMH -- Talking artifacts are finally implemented */ - arti_speak(wep); + if (wep->oartifact) { + res |= arti_speak(wep); /* sets ECMD_TIME bit if artifact speaks */ + } if (artifact_light(wep) && !wep->lamplit) { begin_burn(wep, FALSE); @@ -221,12 +267,13 @@ struct obj *wep; } } } + if ((had_wep != (uwep != 0)) && condtests[bl_bareh].enabled) + disp.botl = TRUE; return res; } void -setuqwep(obj) -register struct obj *obj; +setuqwep(struct obj *obj) { setworn(obj, W_QUIVER); /* no extra handling needed; this used to include a call to @@ -235,67 +282,170 @@ register struct obj *obj; } void -setuswapwep(obj) -register struct obj *obj; +setuswapwep(struct obj *obj) { setworn(obj, W_SWAPWEP); return; } -/*** Commands to change particular slot(s) ***/ +/* getobj callback for object to ready for throwing/shooting; + this filter lets worn items through so that caller can reject them */ +staticfn int +ready_ok(struct obj *obj) +{ + if (!obj) /* '-', will empty quiver slot if chosen */ + return uquiver ? GETOBJ_SUGGEST : GETOBJ_DOWNPLAY; + + /* downplay when wielded, unless more than one */ + if (obj == uwep || (obj == uswapwep && u.twoweap)) + return (obj->quan == 1) ? GETOBJ_DOWNPLAY : GETOBJ_SUGGEST; + + if (is_ammo(obj)) { + return ((uwep && ammo_and_launcher(obj, uwep)) + || (uswapwep && ammo_and_launcher(obj, uswapwep))) + ? GETOBJ_SUGGEST + : GETOBJ_DOWNPLAY; + } else if (is_launcher(obj)) { /* part of 'possible extension' below */ + return GETOBJ_DOWNPLAY; + } else { + if (obj->oclass == WEAPON_CLASS || obj->oclass == COIN_CLASS) + return GETOBJ_SUGGEST; + /* Possible extension: exclude weapons that make no sense to throw, + such as whips, bows, slings, rubber hoses. */ + } -static NEARDATA const char wield_objs[] = { - ALL_CLASSES, ALLOW_NONE, WEAPON_CLASS, TOOL_CLASS, 0 -}; -static NEARDATA const char ready_objs[] = { - ALLOW_COUNT, COIN_CLASS, ALL_CLASSES, ALLOW_NONE, WEAPON_CLASS, 0 -}; -static NEARDATA const char bullets[] = { /* (note: different from dothrow.c) */ - ALLOW_COUNT, COIN_CLASS, ALL_CLASSES, ALLOW_NONE, - GEM_CLASS, WEAPON_CLASS, 0 -}; +#if 0 /* superseded by ammo_and_launcher handling above */ + /* Include gems/stones as likely candidates if either primary + or secondary weapon is a sling. */ + if (obj->oclass == GEM_CLASS + && (uslinging() + || (uswapwep && objects[uswapwep->otyp].oc_skill == P_SLING))) + return GETOBJ_SUGGEST; +#endif + + return GETOBJ_DOWNPLAY; +} + +/* getobj callback for object to wield */ +staticfn int +wield_ok(struct obj *obj) +{ + if (!obj) + return GETOBJ_SUGGEST; + + if (obj->oclass == COIN_CLASS) + return GETOBJ_EXCLUDE; + + if (obj->oclass == WEAPON_CLASS || is_weptool(obj)) + return GETOBJ_SUGGEST; + + return GETOBJ_DOWNPLAY; +} +staticfn void +finish_splitting(struct obj *obj) +{ + /* obj was split off from something; give it its own invlet */ + freeinv(obj); + addinv_nomerge(obj); +} + +/* the #wield command - wield a weapon */ int -dowield() +dowield(void) { - register struct obj *wep, *oldwep; + char qbuf[QBUFSZ]; + struct obj *wep, *oldwep; int result; /* May we attempt this? */ - multi = 0; - if (cantwield(youmonst.data)) { + gm.multi = 0; + if (cantwield(gy.youmonst.data)) { pline("Don't be ridiculous!"); - return 0; + return ECMD_FAIL; } + /* Keep going even if inventory is completely empty, since wielding '-' + to wield nothing can be construed as a positive act even when done + so redundantly. */ /* Prompt for a new weapon */ - if (!(wep = getobj(wield_objs, "wield"))) + clear_splitobjs(); + if (!(wep = getobj("wield", wield_ok, GETOBJ_PROMPT | GETOBJ_ALLOWCNT))) { /* Cancelled */ - return 0; - else if (wep == uwep) { + return ECMD_CANCEL; + } else if (wep == uwep) { + already_wielded: You("are already wielding that!"); if (is_weptool(wep) || is_wet_towel(wep)) - unweapon = FALSE; /* [see setuwep()] */ - return 0; + gu.unweapon = FALSE; /* [see setuwep()] */ + return ECMD_FAIL; } else if (welded(uwep)) { weldmsg(uwep); /* previously interrupted armor removal mustn't be resumed */ reset_remarm(); - return 0; + /* if player chose a partial stack but can't wield it, undo split */ + if (wep->o_id && wep->o_id == svc.context.objsplit.child_oid) + unsplitobj(wep); + return ECMD_FAIL; + } else if (wep->o_id && wep->o_id == svc.context.objsplit.child_oid) { + /* if wep is the result of supplying a count to getobj() + we don't want to split something already wielded; for + any other item, we need to give it its own inventory slot */ + if (uwep && uwep->o_id == svc.context.objsplit.parent_oid) { + unsplitobj(wep); + /* wep was merged back to uwep, already_wielded uses wep */ + wep = uwep; + goto already_wielded; + } + finish_splitting(wep); + goto wielding; } /* Handle no object, or object in other slot */ - if (wep == &zeroobj) + if (wep == &hands_obj) { wep = (struct obj *) 0; - else if (wep == uswapwep) + } else if (wep == uswapwep) { return doswapweapon(); - else if (wep == uquiver) + } else if (wep == uquiver) { + /* offer to split stack if multiple are quivered */ + if (uquiver->quan > 1L && inv_cnt(FALSE) < invlet_basic + && splittable(uquiver)) { + Sprintf(qbuf, "You have %ld %s readied. Wield one?", + uquiver->quan, simpleonames(uquiver)); + switch (ynq(qbuf)) { + case 'q': + return ECMD_OK; + case 'y': + /* leave N-1 quivered, split off 1 to wield */ + wep = splitobj(uquiver, 1L); + finish_splitting(wep); + goto wielding; + default: + break; + } + Strcpy(qbuf, "Wield all of them instead?"); + } else { + boolean use_plural = (is_plural(uquiver) || pair_of(uquiver)); + + Sprintf(qbuf, "You have %s readied. Wield %s instead?", + !use_plural ? "that" : "those", + !use_plural ? "it" : "them"); + } + /* require confirmation to wield the quivered weapon */ + if (ynq(qbuf) != 'y') { + (void) Shk_Your(qbuf, uquiver); /* replace qbuf[] contents */ + pline("%s%s %s readied.", qbuf, + simpleonames(uquiver), otense(uquiver, "remain")); + return ECMD_OK; + } + /* wielding whole readied stack, so no longer quivered */ setuqwep((struct obj *) 0); - else if (wep->owornmask & (W_ARMOR | W_ACCESSORY | W_SADDLE)) { + } else if (wep->owornmask & (W_ARMOR | W_ACCESSORY | W_SADDLE)) { You("cannot wield that!"); - return 0; + return ECMD_FAIL; } + wielding: /* Set your new primary weapon */ oldwep = uwep; result = ready_weapon(wep); @@ -306,21 +456,22 @@ dowield() return result; } +/* the #swap command - swap wielded and secondary weapons */ int -doswapweapon() +doswapweapon(void) { - register struct obj *oldwep, *oldswap; + struct obj *oldwep, *oldswap; int result = 0; /* May we attempt this? */ - multi = 0; - if (cantwield(youmonst.data)) { + gm.multi = 0; + if (cantwield(gy.youmonst.data)) { pline("Don't be ridiculous!"); - return 0; + return ECMD_FAIL; } if (welded(uwep)) { weldmsg(uwep); - return 0; + return ECMD_FAIL; } /* Unwield your current secondary weapon */ @@ -349,36 +500,41 @@ doswapweapon() return result; } +/* the #quiver command */ int -dowieldquiver() +dowieldquiver(void) +{ + return doquiver_core("ready"); +} + +/* guts of #quiver command; also used by #fire when refilling empty quiver */ +int +doquiver_core(const char *verb) /* "ready" or "fire" */ { char qbuf[QBUFSZ]; struct obj *newquiver; - const char *quivee_types; int res; - boolean finish_splitting = FALSE, - was_uwep = FALSE, was_twoweap = u.twoweap; - - /* Since the quiver isn't in your hands, don't check cantwield(), */ - /* will_weld(), touch_petrifies(), etc. */ - multi = 0; - /* forget last splitobj() before calling getobj() with ALLOW_COUNT */ - context.objsplit.child_oid = context.objsplit.parent_oid = 0; - - /* Prompt for a new quiver: "What do you want to ready?" - (Include gems/stones as likely candidates if either primary - or secondary weapon is a sling.) */ - quivee_types = (uslinging() - || (uswapwep - && objects[uswapwep->otyp].oc_skill == P_SLING)) - ? bullets - : ready_objs; - newquiver = getobj(quivee_types, "ready"); + boolean was_uwep = FALSE, was_twoweap = u.twoweap; + + /* Since the quiver isn't in your hands, don't check cantwield(), + will_weld(), touch_petrifies(), etc. */ + gm.multi = 0; + if (!gi.invent) { + /* could accept '-' to empty quiver, but there's no point since + inventory is empty so uquiver is already Null */ + You("have nothing to ready for firing."); + return ECMD_OK; + } + + /* forget last splitobj() before calling getobj() with GETOBJ_ALLOWCNT */ + clear_splitobjs(); + /* Prompt for a new quiver: "What do you want to {ready|fire}?" */ + newquiver = getobj(verb, ready_ok, GETOBJ_PROMPT | GETOBJ_ALLOWCNT); if (!newquiver) { /* Cancelled */ - return 0; - } else if (newquiver == &zeroobj) { /* no object */ + return ECMD_CANCEL; + } else if (newquiver == &hands_obj) { /* no object */ /* Explicitly nothing */ if (uquiver) { You("now have no ammunition readied."); @@ -387,42 +543,48 @@ dowieldquiver() } else { You("already have no ammunition readied!"); } - return 0; - } else if (newquiver->o_id == context.objsplit.child_oid) { + return ECMD_OK; + } else if (newquiver->o_id == svc.context.objsplit.child_oid) { /* if newquiver is the result of supplying a count to getobj() we don't want to split something already in the quiver; for any other item, we need to give it its own inventory slot */ - if (uquiver && uquiver->o_id == context.objsplit.parent_oid) { + if (uquiver && uquiver->o_id == svc.context.objsplit.parent_oid) { unsplitobj(newquiver); goto already_quivered; + } else if (newquiver->oclass == COIN_CLASS) { + /* don't allow splitting a stack of coins into quiver */ + You("can't ready only part of your gold."); + unsplitobj(newquiver); + return ECMD_OK; } - finish_splitting = TRUE; + finish_splitting(newquiver); } else if (newquiver == uquiver) { - already_quivered: + already_quivered: pline("That ammunition is already readied!"); - return 0; + return ECMD_OK; } else if (newquiver->owornmask & (W_ARMOR | W_ACCESSORY | W_SADDLE)) { - You("cannot ready that!"); - return 0; + You("cannot %s that!", verb); + return ECMD_OK; } else if (newquiver == uwep) { int weld_res = !uwep->bknown; if (welded(uwep)) { weldmsg(uwep); reset_remarm(); /* same as dowield() */ - return weld_res; + return weld_res ? ECMD_TIME : ECMD_OK; } /* offer to split stack if wielding more than 1 */ - if (uwep->quan > 1L && inv_cnt(FALSE) < 52 && splittable(uwep)) { + if (uwep->quan > 1L && inv_cnt(FALSE) < invlet_basic + && splittable(uwep)) { Sprintf(qbuf, "You are wielding %ld %s. Ready %ld of them?", uwep->quan, simpleonames(uwep), uwep->quan - 1L); switch (ynq(qbuf)) { case 'q': - return 0; + return ECMD_OK; case 'y': /* leave 1 wielded, split rest off and put into quiver */ newquiver = splitobj(uwep, uwep->quan - 1L); - finish_splitting = TRUE; + finish_splitting(newquiver); goto quivering; default: break; @@ -440,14 +602,14 @@ dowieldquiver() (void) Shk_Your(qbuf, uwep); /* replace qbuf[] contents */ pline("%s%s %s wielded.", qbuf, simpleonames(uwep), otense(uwep, "remain")); - return 0; + return ECMD_OK; } /* quivering main weapon, so no longer wielding it */ setuwep((struct obj *) 0); untwoweapon(); was_uwep = TRUE; } else if (newquiver == uswapwep) { - if (uswapwep->quan > 1L && inv_cnt(FALSE) < 52 + if (uswapwep->quan > 1L && inv_cnt(FALSE) < invlet_basic && splittable(uswapwep)) { Sprintf(qbuf, "%s %ld %s. Ready %ld of them?", u.twoweap ? "You are dual wielding" @@ -456,11 +618,11 @@ dowieldquiver() uswapwep->quan - 1L); switch (ynq(qbuf)) { case 'q': - return 0; + return ECMD_OK; case 'y': /* leave 1 alt-wielded, split rest off and put into quiver */ newquiver = splitobj(uswapwep, uswapwep->quan - 1L); - finish_splitting = TRUE; + finish_splitting(newquiver); goto quivering; default: break; @@ -480,7 +642,7 @@ dowieldquiver() pline("%s%s %s %s.", qbuf, simpleonames(uswapwep), otense(uswapwep, "remain"), u.twoweap ? "wielded" : "as secondary weapon"); - return 0; + return ECMD_OK; } /* quivering alternate weapon, so no more uswapwep */ setuswapwep((struct obj *) 0); @@ -488,16 +650,17 @@ dowieldquiver() } quivering: - if (finish_splitting) { - freeinv(newquiver); - newquiver->nomerge = 1; - addinv(newquiver); - newquiver->nomerge = 0; + if (!strcmp(verb, "ready")) { + /* place item in quiver before printing so that inventory feedback + includes "(at the ready)" */ + setuqwep(newquiver); + prinv((char *) 0, newquiver, 0L); + } else { /* verb=="fire", manually refilling quiver during 'f'ire */ + /* prefix item with description of action, so don't want that to + include "(at the ready)" */ + prinv("You ready:", newquiver, 0L); + setuqwep(newquiver); } - /* place item in quiver before printing so that inventory feedback - includes "(at the ready)" */ - setuqwep(newquiver); - prinv((char *) 0, newquiver, 0L); /* quiver is a convenience slot and manipulating it ordinarily consumes no time, but unwielding primary or secondary weapon @@ -506,25 +669,24 @@ dowieldquiver() something we're wielding that's vulnerable to its damage) */ res = 0; if (was_uwep) { - You("are now empty %s.", body_part(HANDED)); + You("are now %s.", empty_handed()); res = 1; } else if (was_twoweap && !u.twoweap) { - You("are no longer wielding two weapons at once."); + You("%s.", are_no_longer_twoweap); res = 1; } - return res; + return res ? ECMD_TIME : ECMD_OK; } /* used for #rub and for applying pick-axe, whip, grappling hook or polearm */ boolean -wield_tool(obj, verb) -struct obj *obj; -const char *verb; /* "rub",&c */ +wield_tool(struct obj *obj, + const char *verb) /* "rub",&c */ { const char *what; boolean more_than_1; - if (obj == uwep) + if (uwep && obj == uwep) return TRUE; /* nothing to do if already wielding it */ if (!verb) @@ -538,7 +700,7 @@ const char *verb; /* "rub",&c */ more_than_1 ? "them" : "it"); return FALSE; } - if (welded(uwep)) { + if (uwep && welded(uwep)) { if (flags.verbose) { const char *hand = body_part(HAND); @@ -554,7 +716,7 @@ const char *verb; /* "rub",&c */ } return FALSE; } - if (cantwield(youmonst.data)) { + if (cantwield(gy.youmonst.data)) { You_cant("hold %s strongly enough.", more_than_1 ? "them" : "it"); return FALSE; } @@ -585,46 +747,52 @@ const char *verb; /* "rub",&c */ if (flags.pushweapon && oldwep && uwep != oldwep) setuswapwep(oldwep); } - if (uwep != obj) + if (uwep && uwep != obj) return FALSE; /* rewielded old object after dying */ /* applying weapon or tool that gets wielded ends two-weapon combat */ if (u.twoweap) untwoweapon(); if (obj->oclass != WEAPON_CLASS) - unweapon = TRUE; + gu.unweapon = TRUE; return TRUE; } int -can_twoweapon() +can_twoweapon(void) { struct obj *otmp; -#define NOT_WEAPON(obj) (!is_weptool(obj) && obj->oclass != WEAPON_CLASS) - if (!could_twoweap(youmonst.data)) { + if (!could_twoweap(gy.youmonst.data)) { if (Upolyd) You_cant("use two weapons in your current form."); else pline("%s aren't able to use two weapons at once.", - makeplural((flags.female && urole.name.f) ? urole.name.f - : urole.name.m)); - } else if (!uwep || !uswapwep) - Your("%s%s%s empty.", uwep ? "left " : uswapwep ? "right " : "", - body_part(HAND), (!uwep && !uswapwep) ? "s are" : " is"); - else if (NOT_WEAPON(uwep) || NOT_WEAPON(uswapwep)) { - otmp = NOT_WEAPON(uwep) ? uwep : uswapwep; - pline("%s %s.", Yname2(otmp), - is_plural(otmp) ? "aren't weapons" : "isn't a weapon"); + makeplural((flags.female && gu.urole.name.f) + ? gu.urole.name.f : gu.urole.name.m)); + } else if (!uwep || !uswapwep) { + const char *hand_s = body_part(HAND); + + if (!uwep && !uswapwep) + hand_s = makeplural(hand_s); + /* "your hands are empty" or "your {left|right} hand is empty" */ + Your("%s%s %s empty.", uwep ? "left " : uswapwep ? "right " : "", + hand_s, vtense(hand_s, "are")); + } else if (!TWOWEAPOK(uwep) || !TWOWEAPOK(uswapwep)) { + otmp = !TWOWEAPOK(uwep) ? uwep : uswapwep; + pline("%s %s suitable %s weapon%s.", Yname2(otmp), + is_plural(otmp) ? "aren't" : "isn't a", + (otmp == uwep) ? "primary" : "secondary", + plur(otmp->quan)); } else if (bimanual(uwep) || bimanual(uswapwep)) { otmp = bimanual(uwep) ? uwep : uswapwep; pline("%s isn't one-handed.", Yname2(otmp)); - } else if (uarms) + } else if (uarms) { You_cant("use two weapons while wearing a shield."); - else if (uswapwep->oartifact) + } else if (uswapwep->oartifact) { pline("%s being held second to another weapon!", Yobjnam2(uswapwep, "resist")); - else if (uswapwep->otyp == CORPSE && cant_wield_corpse(uswapwep)) { - /* [Note: NOT_WEAPON() check prevents ever getting here...] */ + } else if (uswapwep->otyp == CORPSE && cant_wield_corpse(uswapwep)) { + /* [Note: !TWOWEAPOK() check prevents ever getting here...] */ ; /* must be life-saved to reach here; return FALSE */ } else if (Glib || uswapwep->cursed) { if (!Glib) @@ -635,38 +803,64 @@ can_twoweapon() return FALSE; } +/* uswapwep has become cursed while in two-weapon combat mode or hero is + attempting to dual-wield when it is already cursed or hands are slippery */ void -drop_uswapwep() +drop_uswapwep(void) { - char str[BUFSZ]; + char left_hand[QBUFSZ]; struct obj *obj = uswapwep; - /* Avoid trashing makeplural's static buffer */ - Strcpy(str, makeplural(body_part(HAND))); - pline("%s from your %s!", Yobjnam2(obj, "slip"), str); + /* this used to use makeplural(body_part(HAND)) but in order to be + dual-wielded, or to get this far attempting to achieve that, + uswapwep must be one-handed; since it's secondary, the hand must + be the left one */ + Sprintf(left_hand, "left %s", body_part(HAND)); + if (!obj->cursed) + /* attempting to two-weapon while Glib */ + pline("%s from your %s!", Yobjnam2(obj, "slip"), left_hand); + else if (!u.twoweap) + /* attempting to two-weapon when uswapwep is cursed */ + pline("%s your grasp and %s from your %s!", + Yobjnam2(obj, "evade"), otense(obj, "drop"), left_hand); + else + /* already two-weaponing but can't anymore because uswapwep has + become cursed */ + Your("%s spasms and drops %s!", left_hand, yobjnam(obj, (char *) 0)); dropx(obj); } +void +set_twoweap(boolean on_off) +{ + if (on_off != u.twoweap) { + u.twoweap = on_off; + if (flags.weaponstatus) + disp.botl = TRUE; + } +} + +/* the #twoweapon command */ int -dotwoweapon() +dotwoweapon(void) { /* You can always toggle it off */ if (u.twoweap) { You("switch to your primary weapon."); - u.twoweap = 0; + set_twoweap(FALSE); /* u.twoweap = FALSE */ update_inventory(); - return 0; + return ECMD_OK; } /* May we use two weapons? */ if (can_twoweapon()) { /* Success! */ You("begin two-weapon combat."); - u.twoweap = 1; + set_twoweap(TRUE); /* u.twoweap = TRUE */ update_inventory(); - return (rnd(20) > ACURR(A_DEX)); + return (rnd(20) > ACURR(A_DEX)) ? ECMD_TIME : ECMD_OK; } - return 0; + return ECMD_OK; } /*** Functions to empty a given slot ***/ @@ -676,7 +870,7 @@ dotwoweapon() * 2. Making an item disappear for a bones pile. */ void -uwepgone() +uwepgone(void) { if (uwep) { if (artifact_light(uwep) && uwep->lamplit) { @@ -685,13 +879,13 @@ uwepgone() pline("%s shining.", Tobjnam(uwep, "stop")); } setworn((struct obj *) 0, W_WEP); - unweapon = TRUE; + gu.unweapon = TRUE; update_inventory(); } } void -uswapwepgone() +uswapwepgone(void) { if (uswapwep) { setworn((struct obj *) 0, W_SWAPWEP); @@ -700,7 +894,7 @@ uswapwepgone() } void -uqwepgone() +uqwepgone(void) { if (uquiver) { setworn((struct obj *) 0, W_QUIVER); @@ -709,20 +903,19 @@ uqwepgone() } void -untwoweapon() +untwoweapon(void) { if (u.twoweap) { - You("can no longer use two weapons at once."); - u.twoweap = FALSE; + You("%s.", can_no_longer_twoweap); + set_twoweap(FALSE); /* u.twoweap = FALSE */ update_inventory(); } return; } +/* enchant wielded weapon */ int -chwepon(otmp, amount) -register struct obj *otmp; -register int amount; +chwepon(struct obj *otmp, int amount) { const char *color = hcolor((amount < 0) ? NH_BLACK : NH_BLUE); const char *xtime, *wepname = ""; @@ -837,10 +1030,10 @@ register int amount; /* * Enchantment, which normally improves a weapon, has an - * addition adverse reaction on Magicbane whose effects are + * additional adverse reaction on Magicbane whose effects are * spe dependent. Give an obscure clue here. */ - if (uwep->oartifact == ART_MAGICBANE && uwep->spe >= 0) { + if (u_wield_art(ART_MAGICBANE) && uwep->spe >= 0) { Your("right %s %sches!", body_part(HAND), (((amount > 1) && (uwep->spe > 1)) ? "flin" : "it")); } @@ -855,8 +1048,7 @@ register int amount; } int -welded(obj) -register struct obj *obj; +welded(struct obj *obj) { if (obj && obj == uwep && will_weld(obj)) { set_bknown(obj, 1); @@ -866,22 +1058,24 @@ register struct obj *obj; } void -weldmsg(obj) -register struct obj *obj; +weldmsg(struct obj *obj) { long savewornmask; + const char *hand = body_part(HAND); + if (bimanual(obj)) + hand = makeplural(hand); savewornmask = obj->owornmask; - pline("%s welded to your %s!", Yobjnam2(obj, "are"), - bimanual(obj) ? (const char *) makeplural(body_part(HAND)) - : body_part(HAND)); + obj->owornmask = 0L; /* suppress doname()'s "(weapon in hand)"; + * Yobjnam2() doesn't actually need this because + * it is based on xname() rather than doname() */ + pline("%s welded to your %s!", Yobjnam2(obj, "are"), hand); obj->owornmask = savewornmask; } /* test whether monster's wielded weapon is stuck to hand/paw/whatever */ boolean -mwelded(obj) -struct obj *obj; +mwelded(struct obj *obj) { /* caller is responsible for making sure this is a monster's item */ if (obj && (obj->owornmask & W_WEP) && will_weld(obj)) diff --git a/src/windows.c b/src/windows.c index e5ff23290..4d3c13259 100644 --- a/src/windows.c +++ b/src/windows.c @@ -1,9 +1,9 @@ -/* NetHack 3.6 windows.c $NHDT-Date: 1575245096 2019/12/02 00:04:56 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.60 $ */ +/* NetHack 5.0 windows.c $NHDT-Date: 1737345149 2025/01/19 19:52:29 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.138 $ */ /* Copyright (c) D. Cohrs, 1993. */ -/* Copyright (c) Facebook, Inc. and its affiliates. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" +#include "dlb.h" #ifdef TTY_GRAPHICS #include "wintty.h" #endif @@ -14,69 +14,72 @@ extern struct window_procs curses_procs; /* Cannot just blindly include winX.h without including all of X11 stuff and must get the order of include files right. Don't bother. */ extern struct window_procs X11_procs; -extern void FDECL(win_X11_init, (int)); +extern void win_X11_init(int); #endif #ifdef QT_GRAPHICS extern struct window_procs Qt_procs; #endif #ifdef GEM_GRAPHICS -#include "wingem.h" +/*#include "wingem.h"*/ #endif -#ifdef MAC +#ifdef MACOS9 extern struct window_procs mac_procs; #endif #ifdef BEOS_GRAPHICS extern struct window_procs beos_procs; -extern void FDECL(be_win_init, (int)); +extern void be_win_init(int); FAIL /* be_win_init doesn't exist? XXX*/ #endif #ifdef AMIGA_INTUITION extern struct window_procs amii_procs; extern struct window_procs amiv_procs; -extern void FDECL(ami_wininit_data, (int)); -#endif -#ifdef WIN32_GRAPHICS -extern struct window_procs win32_procs; +extern void ami_wininit_data(int); #endif #ifdef GNOME_GRAPHICS -#include "winGnome.h" +/*#include "winGnome.h"*/ extern struct window_procs Gnome_procs; #endif #ifdef MSWIN_GRAPHICS extern struct window_procs mswin_procs; #endif +#ifdef SHIM_GRAPHICS +extern struct window_procs shim_procs; +#endif #ifdef RL_GRAPHICS extern struct window_procs rl_procs; #endif #ifdef WINCHAIN extern struct window_procs chainin_procs; -extern void FDECL(chainin_procs_init, (int)); -extern void *FDECL(chainin_procs_chain, (int, int, void *, void *, void *)); +extern void chainin_procs_init(int); +extern void *chainin_procs_chain(int, int, void *, void *, void *); extern struct chain_procs chainout_procs; -extern void FDECL(chainout_procs_init, (int)); -extern void *FDECL(chainout_procs_chain, (int, int, void *, void *, void *)); +extern void chainout_procs_init(int); +extern void *chainout_procs_chain(int, int, void *, void *, void *); extern struct chain_procs trace_procs; -extern void FDECL(trace_procs_init, (int)); -extern void *FDECL(trace_procs_chain, (int, int, void *, void *, void *)); +extern void trace_procs_init(int); +extern void *trace_procs_chain(int, int, void *, void *, void *); #endif -STATIC_DCL void FDECL(def_raw_print, (const char *s)); -STATIC_DCL void NDECL(def_wait_synch); +#if defined(WINCHAIN) || defined(TTY_GRAPHICS) +staticfn struct win_choices *win_choices_find(const char *s) NONNULLARG1; +#endif -#ifdef DUMPLOG -STATIC_DCL winid FDECL(dump_create_nhwindow, (int)); -STATIC_DCL void FDECL(dump_clear_nhwindow, (winid)); -STATIC_DCL void FDECL(dump_display_nhwindow, (winid, BOOLEAN_P)); -STATIC_DCL void FDECL(dump_destroy_nhwindow, (winid)); -STATIC_DCL void FDECL(dump_start_menu, (winid)); -STATIC_DCL void FDECL(dump_add_menu, (winid, int, const ANY_P *, CHAR_P, - CHAR_P, int, const char *, BOOLEAN_P)); -STATIC_DCL void FDECL(dump_end_menu, (winid, const char *)); -STATIC_DCL int FDECL(dump_select_menu, (winid, int, MENU_ITEM_P **)); -STATIC_DCL void FDECL(dump_putstr, (winid, int, const char *)); -#endif /* DUMPLOG */ +staticfn void def_raw_print(const char *s) NONNULLARG1; +staticfn void def_wait_synch(void); +staticfn boolean get_menu_coloring(const char *, int *, int *) NONNULLPTRS; + +staticfn winid dump_create_nhwindow(int); +staticfn void dump_clear_nhwindow(winid); +staticfn void dump_display_nhwindow(winid, boolean); +staticfn void dump_destroy_nhwindow(winid); +staticfn void dump_start_menu(winid, unsigned long); +staticfn void dump_add_menu(winid, const glyph_info *, const ANY_P *, char, + char, int, int, const char *, unsigned int); +staticfn void dump_end_menu(winid, const char *); +staticfn int dump_select_menu(winid, int, MENU_ITEM_P **); +staticfn void dump_putstr(winid, int, const char *); #ifdef HANGUPHANDLING volatile @@ -91,9 +94,9 @@ volatile static struct win_choices { struct window_procs *procs; - void FDECL((*ini_routine), (int)); /* optional (can be 0) */ + void (*ini_routine)(int); /* optional (can be 0) */ #ifdef WINCHAIN - void *FDECL((*chain_routine), (int, int, void *, void *, void *)); + void *(*chain_routine)(int, int, void *, void *, void *); #endif } winchoices[] = { #ifdef TTY_GRAPHICS @@ -111,7 +114,7 @@ static struct win_choices { #ifdef GEM_GRAPHICS { &Gem_procs, win_Gem_init CHAINR(0) }, #endif -#ifdef MAC +#ifdef MACOS9 { &mac_procs, 0 CHAINR(0) }, #endif #ifdef BEOS_GRAPHICS @@ -123,15 +126,15 @@ static struct win_choices { { &amiv_procs, ami_wininit_data CHAINR(0) }, /* Tile version of the game */ #endif -#ifdef WIN32_GRAPHICS - { &win32_procs, 0 CHAINR(0) }, -#endif #ifdef GNOME_GRAPHICS { &Gnome_procs, 0 CHAINR(0) }, #endif #ifdef MSWIN_GRAPHICS { &mswin_procs, 0 CHAINR(0) }, #endif +#ifdef SHIM_GRAPHICS + { &shim_procs, 0 CHAINR(0) }, +#endif #ifdef RL_GRAPHICS { &rl_procs, 0 CHAINR(0) }, #endif @@ -156,8 +159,8 @@ struct winlink { static struct winlink *chain = 0; -static struct winlink * -wl_new() +staticfn struct winlink * +wl_new(void) { struct winlink *wl = (struct winlink *) alloc(sizeof *wl); @@ -168,14 +171,14 @@ wl_new() return wl; } -static void +staticfn void wl_addhead(struct winlink *wl) { wl->nextlink = chain; chain = wl; } -static void +staticfn void wl_addtail(struct winlink *wl) { struct winlink *p = chain; @@ -192,31 +195,30 @@ wl_addtail(struct winlink *wl) } #endif /* WINCHAIN */ -static struct win_choices *last_winchoice = 0; - boolean -genl_can_suspend_no(VOID_ARGS) +genl_can_suspend_no(void) { return FALSE; } boolean -genl_can_suspend_yes(VOID_ARGS) +genl_can_suspend_yes(void) { return TRUE; } -STATIC_OVL +staticfn void -def_raw_print(s) -const char *s; +def_raw_print(const char *s) { puts(s); + if (*s) + iflags.raw_printed++; } -STATIC_OVL +staticfn void -def_wait_synch(VOID_ARGS) +def_wait_synch(void) { /* Config file error handling routines * call wait_sync() without checking to @@ -230,12 +232,33 @@ def_wait_synch(VOID_ARGS) return; } -#ifdef WINCHAIN -static struct win_choices * -win_choices_find(s) -const char *s; +#ifdef TTY_GRAPHICS +boolean +check_tty_wincap(unsigned long wincap) +{ + struct win_choices *wc = win_choices_find("tty"); + + if (wc) + return ((wc->procs->wincap & wincap) == wincap); + return FALSE; +} + +boolean +check_tty_wincap2(unsigned long wincap2) +{ + struct win_choices *wc = win_choices_find("tty"); + + if (wc) + return ((wc->procs->wincap2 & wincap2) == wincap2); + return FALSE; +} +#endif + +#if defined(WINCHAIN) || defined(TTY_GRAPHICS) +staticfn struct win_choices * +win_choices_find(const char *s) { - register int i; + int i; for (i = 0; winchoices[i].procs; i++) { if (!strcmpi(s, winchoices[i].procs->name)) { @@ -247,8 +270,7 @@ const char *s; #endif void -choose_windows(s) -const char *s; +choose_windows(const char *s) { int i; char *tmps = 0; @@ -261,11 +283,11 @@ const char *s; if (!strcmpi(s, winchoices[i].procs->name)) { windowprocs = *winchoices[i].procs; - if (last_winchoice && last_winchoice->ini_routine) - (*last_winchoice->ini_routine)(WININIT_UNDO); + if (gl.last_winchoice && gl.last_winchoice->ini_routine) + (*gl.last_winchoice->ini_routine)(WININIT_UNDO); if (winchoices[i].ini_routine) (*winchoices[i].ini_routine)(WININIT); - last_winchoice = &winchoices[i]; + gl.last_winchoice = &winchoices[i]; return; } } @@ -317,17 +339,15 @@ const char *s; if (tmps) free((genericptr_t) tmps) /*, tmps = 0*/ ; - if (windowprocs.win_raw_print == def_raw_print - || WINDOWPORT("safe-startup")) + if (windowprocs.win_raw_print == def_raw_print) nh_terminate(EXIT_SUCCESS); } #ifdef WINCHAIN void -addto_windowchain(s) -const char *s; +addto_windowchain(const char *s) { - register int i; + int i; for (i = 0; winchoices[i].procs; i++) { if ('+' != winchoices[i].procs->name[0]) @@ -355,7 +375,7 @@ const char *s; } void -commit_windowchain() +commit_windowchain(void) { struct winlink *p; int n; @@ -397,7 +417,7 @@ commit_windowchain() p->nextlink->linkdata); } else { (void) (*p->wincp->chain_routine)(WINCHAIN_INIT, n, p->linkdata, - last_winchoice->procs, 0); + gl.last_winchoice->procs, 0); } } @@ -434,10 +454,9 @@ commit_windowchain() */ /*ARGSUSED*/ char -genl_message_menu(let, how, mesg) -char let UNUSED; -int how UNUSED; -const char *mesg; +genl_message_menu(char let UNUSED, + int how UNUSED, + const char *mesg) { pline("%s", mesg); return 0; @@ -445,8 +464,7 @@ const char *mesg; /*ARGSUSED*/ void -genl_preference_update(pref) -const char *pref UNUSED; +genl_preference_update(const char *pref UNUSED) { /* window ports are expected to provide their own preference update routine @@ -457,8 +475,7 @@ const char *pref UNUSED; } char * -genl_getmsghistory(init) -boolean init UNUSED; +genl_getmsghistory(boolean init UNUSED) { /* window ports can provide their own getmsghistory() routine to @@ -475,9 +492,7 @@ boolean init UNUSED; } void -genl_putmsghistory(msg, is_restoring) -const char *msg; -boolean is_restoring; +genl_putmsghistory(const char *msg, boolean is_restoring) { /* window ports can provide their own putmsghistory() routine to @@ -511,45 +526,49 @@ boolean is_restoring; * in order to avoid all terminal I/O after hangup/disconnect. */ -static int NDECL(hup_nhgetch); -static char FDECL(hup_yn_function, (const char *, const char *, CHAR_P)); -static int FDECL(hup_nh_poskey, (int *, int *, int *)); -static void FDECL(hup_getlin, (const char *, char *)); -static void FDECL(hup_init_nhwindows, (int *, char **)); -static void FDECL(hup_exit_nhwindows, (const char *)); -static winid FDECL(hup_create_nhwindow, (int)); -static int FDECL(hup_select_menu, (winid, int, MENU_ITEM_P **)); -static void FDECL(hup_add_menu, (winid, int, const anything *, CHAR_P, CHAR_P, - int, const char *, BOOLEAN_P)); -static void FDECL(hup_end_menu, (winid, const char *)); -static void FDECL(hup_putstr, (winid, int, const char *)); -static void FDECL(hup_print_glyph, (winid, XCHAR_P, XCHAR_P, int, int)); -static void FDECL(hup_outrip, (winid, int, time_t)); -static void FDECL(hup_curs, (winid, int, int)); -static void FDECL(hup_display_nhwindow, (winid, BOOLEAN_P)); -static void FDECL(hup_display_file, (const char *, BOOLEAN_P)); +staticfn int hup_nhgetch(void); +staticfn char hup_yn_function(const char *, const char *, char); +staticfn int hup_nh_poskey(coordxy *, coordxy *, int *); +staticfn void hup_getlin(const char *, char *); +staticfn void hup_init_nhwindows(int *, char **); +staticfn void hup_exit_nhwindows(const char *); +staticfn winid hup_create_nhwindow(int); +staticfn int hup_select_menu(winid, int, MENU_ITEM_P **); +staticfn void hup_add_menu(winid, const glyph_info *, const anything *, char, + char, int, int, const char *, unsigned int); +staticfn void hup_end_menu(winid, const char *); +staticfn void hup_putstr(winid, int, const char *); +staticfn void hup_print_glyph(winid, coordxy, coordxy, const glyph_info *, + const glyph_info *); +staticfn void hup_outrip(winid, int, time_t); +staticfn void hup_curs(winid, int, int); +staticfn void hup_display_nhwindow(winid, boolean); +staticfn void hup_display_file(const char *, boolean); #ifdef CLIPPING -static void FDECL(hup_cliparound, (int, int)); +staticfn void hup_cliparound(int, int); #endif #ifdef CHANGE_COLOR -static void FDECL(hup_change_color, (int, long, int)); -#ifdef MAC -static short FDECL(hup_set_font_name, (winid, char *)); +staticfn void hup_change_color(int, long, int); +#ifdef MACOS9 +staticfn short hup_set_font_name(winid, char *); #endif -static char *NDECL(hup_get_color_string); +staticfn char *hup_get_color_string(void); #endif /* CHANGE_COLOR */ -static void FDECL(hup_status_update, (int, genericptr_t, int, int, int, - unsigned long *)); +staticfn void hup_status_update(int, genericptr_t, int, int, int, + unsigned long *); -static int NDECL(hup_int_ndecl); -static void NDECL(hup_void_ndecl); -static void FDECL(hup_void_fdecl_int, (int)); -static void FDECL(hup_void_fdecl_winid, (winid)); -static void FDECL(hup_void_fdecl_constchar_p, (const char *)); +staticfn int hup_int_ndecl(void); +staticfn void hup_void_ndecl(void); +staticfn void hup_void_fdecl_int(int); +staticfn void hup_void_fdecl_winid(winid); +staticfn void hup_void_fdecl_winid_ulong(winid, unsigned long); +staticfn void hup_void_fdecl_constchar_p(const char *); +staticfn win_request_info *hup_ctrl_nhwindow(winid, int, win_request_info *); static struct window_procs hup_procs = { - "hup", 0L, 0L, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + WPID(hup), 0L, 0L, + { FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, + FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE }, /* colors */ hup_init_nhwindows, hup_void_ndecl, /* player_selection */ hup_void_ndecl, /* askname */ @@ -559,17 +578,15 @@ static struct window_procs hup_procs = { hup_create_nhwindow, hup_void_fdecl_winid, /* clear_nhwindow */ hup_display_nhwindow, hup_void_fdecl_winid, /* destroy_nhwindow */ hup_curs, hup_putstr, hup_putstr, /* putmixed */ - hup_display_file, hup_void_fdecl_winid, /* start_menu */ + hup_display_file, hup_void_fdecl_winid_ulong, /* start_menu */ hup_add_menu, hup_end_menu, hup_select_menu, genl_message_menu, - hup_void_ndecl, /* update_inventory */ hup_void_ndecl, /* mark_synch */ hup_void_ndecl, /* wait_synch */ #ifdef CLIPPING hup_cliparound, #endif #ifdef POSITIONBAR - (void FDECL((*), (char *))) hup_void_fdecl_constchar_p, - /* update_positionbar */ + (void (*)(char *)) hup_void_fdecl_constchar_p, /* update_positionbar */ #endif hup_print_glyph, hup_void_fdecl_constchar_p, /* raw_print */ @@ -578,32 +595,32 @@ static struct window_procs hup_procs = { hup_int_ndecl, /* doprev_message */ hup_yn_function, hup_getlin, hup_int_ndecl, /* get_ext_cmd */ hup_void_fdecl_int, /* number_pad */ - hup_void_ndecl, /* delay_output */ + hup_void_ndecl, /* nh_delay_output */ #ifdef CHANGE_COLOR hup_change_color, -#ifdef MAC +#ifdef MACOS9 hup_void_fdecl_int, /* change_background */ hup_set_font_name, #endif hup_get_color_string, #endif /* CHANGE_COLOR */ - hup_void_ndecl, /* start_screen */ - hup_void_ndecl, /* end_screen */ hup_outrip, genl_preference_update, genl_getmsghistory, genl_putmsghistory, hup_void_ndecl, /* status_init */ hup_void_ndecl, /* status_finish */ genl_status_enablefield, hup_status_update, genl_can_suspend_no, + hup_void_fdecl_int, /* update_inventory */ + hup_ctrl_nhwindow, }; -static void FDECL((*previnterface_exit_nhwindows), (const char *)) = 0; +static void (*previnterface_exit_nhwindows)(const char *) = 0; /* hangup has occurred; switch to no-op user interface */ void -nhwindows_hangup() +nhwindows_hangup(void) { - char *FDECL((*previnterface_getmsghistory), (BOOLEAN_P)) = 0; + char *(*previnterface_getmsghistory)(boolean) = 0; #ifdef ALTMETA /* command processor shouldn't look for 2nd char after seeing ESC */ @@ -628,9 +645,8 @@ nhwindows_hangup() windowprocs.win_getmsghistory = previnterface_getmsghistory; } -static void -hup_exit_nhwindows(lastgasp) -const char *lastgasp; +staticfn void +hup_exit_nhwindows(const char *lastgasp) { /* core has called exit_nhwindows(); call the previous interface's shutdown routine now; xxx_exit_nhwindows() needs to call other @@ -640,20 +656,21 @@ const char *lastgasp; (*previnterface_exit_nhwindows)(lastgasp); previnterface_exit_nhwindows = 0; } - iflags.window_inited = 0; + iflags.window_inited = FALSE; } -static int -hup_nhgetch(VOID_ARGS) +staticfn int +hup_nhgetch(void) { return '\033'; /* ESC */ } /*ARGSUSED*/ -static char -hup_yn_function(prompt, resp, deflt) -const char *prompt UNUSED, *resp UNUSED; -char deflt; +staticfn char +hup_yn_function( + const char *prompt UNUSED, + const char *resp UNUSED, + char deflt) { if (!deflt) deflt = '\033'; @@ -661,134 +678,116 @@ char deflt; } /*ARGSUSED*/ -static int -hup_nh_poskey(x, y, mod) -int *x UNUSED, *y UNUSED, *mod UNUSED; +staticfn int +hup_nh_poskey(coordxy *x UNUSED, coordxy *y UNUSED, int *mod UNUSED) { return '\033'; } /*ARGSUSED*/ -static void -hup_getlin(prompt, outbuf) -const char *prompt UNUSED; -char *outbuf; +staticfn void +hup_getlin(const char *prompt UNUSED, char *outbuf) { Strcpy(outbuf, "\033"); } /*ARGSUSED*/ -static void -hup_init_nhwindows(argc_p, argv) -int *argc_p UNUSED; -char **argv UNUSED; +staticfn void +hup_init_nhwindows(int *argc_p UNUSED, char **argv UNUSED) { - iflags.window_inited = 1; + iflags.window_inited = TRUE; } /*ARGUSED*/ -static winid -hup_create_nhwindow(type) -int type UNUSED; +staticfn winid +hup_create_nhwindow(int type UNUSED) { return WIN_ERR; } /*ARGSUSED*/ -static int -hup_select_menu(window, how, menu_list) -winid window UNUSED; -int how UNUSED; -struct mi **menu_list UNUSED; +staticfn int +hup_select_menu( + winid window UNUSED, + int how UNUSED, + struct mi **menu_list UNUSED) { return -1; } /*ARGSUSED*/ -static void -hup_add_menu(window, glyph, identifier, sel, grpsel, attr, txt, preselected) -winid window UNUSED; -int glyph UNUSED, attr UNUSED; -const anything *identifier UNUSED; -char sel UNUSED, grpsel UNUSED; -const char *txt UNUSED; -boolean preselected UNUSED; +staticfn void +hup_add_menu( + winid window UNUSED, + const glyph_info *glyphinfo UNUSED, + const anything *identifier UNUSED, + char sel UNUSED, + char grpsel UNUSED, + int attr UNUSED, + int clr UNUSED, + const char *txt UNUSED, + unsigned int itemflags UNUSED) { return; } /*ARGSUSED*/ -static void -hup_end_menu(window, prompt) -winid window UNUSED; -const char *prompt UNUSED; +staticfn void +hup_end_menu(winid window UNUSED, const char *prompt UNUSED) { return; } /*ARGSUSED*/ -static void -hup_putstr(window, attr, text) -winid window UNUSED; -int attr UNUSED; -const char *text UNUSED; +staticfn void +hup_putstr(winid window UNUSED, int attr UNUSED, const char *text UNUSED) { return; } /*ARGSUSED*/ -static void -hup_print_glyph(window, x, y, glyph, bkglyph) -winid window UNUSED; -xchar x UNUSED, y UNUSED; -int glyph UNUSED; -int bkglyph UNUSED; +staticfn void +hup_print_glyph( + winid window UNUSED, + coordxy x UNUSED, coordxy y UNUSED, + const glyph_info *glyphinfo UNUSED, + const glyph_info *bkglyphinfo UNUSED) { return; } /*ARGSUSED*/ -static void -hup_outrip(tmpwin, how, when) -winid tmpwin UNUSED; -int how UNUSED; -time_t when UNUSED; +staticfn void +hup_outrip(winid tmpwin UNUSED, int how UNUSED, time_t when UNUSED) { return; } /*ARGSUSED*/ -static void -hup_curs(window, x, y) -winid window UNUSED; -int x UNUSED, y UNUSED; +staticfn void +hup_curs(winid window UNUSED, int x UNUSED, int y UNUSED) { return; } /*ARGSUSED*/ -static void -hup_display_nhwindow(window, blocking) -winid window UNUSED; -boolean blocking UNUSED; +staticfn void +hup_display_nhwindow(winid window UNUSED, boolean blocking UNUSED) { return; } /*ARGSUSED*/ -static void -hup_display_file(fname, complain) -const char *fname UNUSED; -boolean complain UNUSED; +staticfn void +hup_display_file(const char *fname UNUSED, boolean complain UNUSED) { return; } #ifdef CLIPPING /*ARGSUSED*/ -static void -hup_cliparound(x, y) -int x UNUSED, y UNUSED; +staticfn void +hup_cliparound(int x UNUSED, int y UNUSED) { return; } @@ -796,40 +795,34 @@ int x UNUSED, y UNUSED; #ifdef CHANGE_COLOR /*ARGSUSED*/ -static void -hup_change_color(color, rgb, reverse) -int color, reverse; -long rgb; +staticfn void +hup_change_color(int color UNUSED, long rgb UNUSED, int reverse UNUSED) { return; } -#ifdef MAC +#ifdef MACOS9 /*ARGSUSED*/ -static short -hup_set_font_name(window, fontname) -winid window; -char *fontname; +staticfn short +hup_set_font_name(winid window UNUSED, char *fontname UNUSED) { return 0; } -#endif /* MAC */ +#endif /* MACOS9 */ -static char * -hup_get_color_string(VOID_ARGS) +staticfn char * +hup_get_color_string(void) { return (char *) 0; } #endif /* CHANGE_COLOR */ /*ARGSUSED*/ -static void -hup_status_update(idx, ptr, chg, pc, color, colormasks) -int idx UNUSED; -genericptr_t ptr UNUSED; -int chg UNUSED, pc UNUSED, color UNUSED; -unsigned long *colormasks UNUSED; - +staticfn void +hup_status_update( + int idx UNUSED, genericptr_t ptr UNUSED, + int chg UNUSED, int pc UNUSED, + int color UNUSED, unsigned long *colormasks UNUSED) { return; } @@ -838,42 +831,58 @@ unsigned long *colormasks UNUSED; * Non-specific stubs. */ -static int -hup_int_ndecl(VOID_ARGS) +staticfn int +hup_int_ndecl(void) { return -1; } -static void -hup_void_ndecl(VOID_ARGS) +staticfn void +hup_void_ndecl(void) +{ + return; +} + +/*ARGUSED*/ +staticfn void +hup_void_fdecl_int(int arg UNUSED) { return; } /*ARGUSED*/ -static void -hup_void_fdecl_int(arg) -int arg UNUSED; +staticfn void +hup_void_fdecl_winid(winid window UNUSED) { return; } /*ARGUSED*/ -static void -hup_void_fdecl_winid(window) -winid window UNUSED; +staticfn void +hup_void_fdecl_winid_ulong( + winid window UNUSED, + unsigned long mbehavior UNUSED) { return; } /*ARGUSED*/ -static void -hup_void_fdecl_constchar_p(string) -const char *string UNUSED; +staticfn void +hup_void_fdecl_constchar_p(const char *string UNUSED) { return; } +/*ARGUSED*/ +win_request_info * +hup_ctrl_nhwindow( + winid window UNUSED, /* window to use, must be of type NHW_MENU */ + int request UNUSED, + win_request_info *wri UNUSED) +{ + return (win_request_info *) 0; +} + #endif /* HANGUPHANDLING */ @@ -887,7 +896,7 @@ char *status_vals[MAXBLSTATS]; boolean status_activefields[MAXBLSTATS]; void -genl_status_init() +genl_status_init(void) { int i; @@ -903,7 +912,7 @@ genl_status_init() } void -genl_status_finish() +genl_status_finish(void) { /* tear down routine */ int i; @@ -916,28 +925,30 @@ genl_status_finish() } void -genl_status_enablefield(fieldidx, nm, fmt, enable) -int fieldidx; -const char *nm; -const char *fmt; -boolean enable; +genl_status_enablefield( + int fieldidx, + const char *nm, + const char *fmt, + boolean enable) { status_fieldfmt[fieldidx] = fmt; status_fieldnm[fieldidx] = nm; status_activefields[fieldidx] = enable; } +DISABLE_WARNING_FORMAT_NONLITERAL + /* call once for each field, then call with BL_FLUSH to output the result */ void -genl_status_update(idx, ptr, chg, percent, color, colormasks) -int idx; -genericptr_t ptr; -int chg UNUSED, percent UNUSED, color UNUSED; -unsigned long *colormasks UNUSED; +genl_status_update( + int idx, + genericptr_t ptr, + int chg UNUSED, int percent UNUSED, + int color UNUSED, unsigned long *colormasks UNUSED) { char newbot1[MAXCO], newbot2[MAXCO]; long cond, *condptr = (long *) ptr; - register int i; + int i; unsigned pass, lndelta; enum statusfields idx1, idx2, *fieldlist; char *nb, *text = (char *) ptr; @@ -1109,19 +1120,21 @@ unsigned long *colormasks UNUSED; putmixed(WIN_STATUS, 0, newbot2); /* putmixed() due to GOLD glyph */ } -STATIC_VAR struct window_procs dumplog_windowprocs_backup; -STATIC_VAR FILE *dumplog_file; +RESTORE_WARNING_FORMAT_NONLITERAL + +static struct window_procs dumplog_windowprocs_backup; +static FILE *dumplog_file; #ifdef DUMPLOG -STATIC_VAR time_t dumplog_now; +static time_t dumplog_now; char * -dump_fmtstr(fmt, buf, fullsubs) -const char *fmt; -char *buf; -boolean fullsubs; /* True -> full substitution for file name, False -> - * partial substitution for '--showpaths' feedback - * where there's no game in progress when executed */ +dump_fmtstr( + const char *fmt, + char *buf, + boolean fullsubs) /* True -> full substitution for file name, + * False -> partial substitution for '--showpaths' + * feedback where there's no game in progress */ { const char *fp = fmt; char *bp = buf; @@ -1179,21 +1192,22 @@ boolean fullsubs; /* True -> full substitution for file name, False -> else Strcpy(tmpbuf, "{current date+time}"); break; - case 'v': /* version, eg. "3.6.5-0" */ - Sprintf(tmpbuf, "%s", version_string(verbuf)); + case 'v': /* version, eg. "5.0.0,-0" */ + Sprintf(tmpbuf, "%s", version_string(verbuf, sizeof verbuf)); break; case 'u': /* UID */ Sprintf(tmpbuf, "%ld", uid); break; case 'n': /* player name */ if (fullsubs) - Sprintf(tmpbuf, "%s", *plname ? plname : "unknown"); + Sprintf(tmpbuf, "%s", + *svp.plname ? svp.plname : "unknown"); else Strcpy(tmpbuf, "{hero name}"); break; case 'N': /* first character of player name */ if (fullsubs) - Sprintf(tmpbuf, "%c", *plname ? *plname : 'u'); + Sprintf(tmpbuf, "%c", *svp.plname ? *svp.plname : 'u'); else Strcpy(tmpbuf, "{hero initial}"); break; @@ -1233,8 +1247,7 @@ boolean fullsubs; /* True -> full substitution for file name, False -> #endif /* DUMPLOG */ void -dump_open_log(now) -time_t now; +dump_open_log(time_t now) { #ifdef DUMPLOG char buf[BUFSZ]; @@ -1257,7 +1270,7 @@ time_t now; } void -dump_close_log() +dump_close_log(void) { if (dumplog_file) { (void) fclose(dumplog_file); @@ -1266,11 +1279,7 @@ dump_close_log() } void -dump_forward_putstr(win, attr, str, no_forward) -winid win; -int attr; -const char *str; -int no_forward; +dump_forward_putstr(winid win, int attr, const char *str, int no_forward) { if (dumplog_file) fprintf(dumplog_file, "%s\n", str); @@ -1279,70 +1288,61 @@ int no_forward; } /*ARGSUSED*/ -STATIC_OVL void -dump_putstr(win, attr, str) -winid win UNUSED; -int attr UNUSED; -const char *str; +staticfn void +dump_putstr(winid win UNUSED, int attr UNUSED, const char *str) { if (dumplog_file) fprintf(dumplog_file, "%s\n", str); } -STATIC_OVL winid -dump_create_nhwindow(dummy) -int dummy; +staticfn winid +dump_create_nhwindow(int type UNUSED) { - return dummy; + return WIN_ERR; } /*ARGUSED*/ -STATIC_OVL void -dump_clear_nhwindow(win) -winid win UNUSED; +staticfn void +dump_clear_nhwindow(winid win UNUSED) { return; } /*ARGSUSED*/ -STATIC_OVL void -dump_display_nhwindow(win, p) -winid win UNUSED; -boolean p UNUSED; +staticfn void +dump_display_nhwindow(winid win UNUSED, boolean p UNUSED) { return; } /*ARGUSED*/ -STATIC_OVL void -dump_destroy_nhwindow(win) -winid win UNUSED; +staticfn void +dump_destroy_nhwindow(winid win UNUSED) { return; } /*ARGUSED*/ -STATIC_OVL void -dump_start_menu(win) -winid win UNUSED; +staticfn void +dump_start_menu(winid win UNUSED, unsigned long mbehavior UNUSED) { return; } /*ARGSUSED*/ -STATIC_OVL void -dump_add_menu(win, glyph, identifier, ch, gch, attr, str, preselected) -winid win UNUSED; -int glyph; -const anything *identifier UNUSED; -char ch; -char gch UNUSED; -int attr UNUSED; -const char *str; -boolean preselected UNUSED; +staticfn void +dump_add_menu(winid win UNUSED, + const glyph_info *glyphinfo, + const anything *identifier UNUSED, + char ch, + char gch UNUSED, + int attr UNUSED, + int clr UNUSED, + const char *str, + unsigned int itemflags UNUSED) { if (dumplog_file) { - if (glyph == NO_GLYPH) + if (glyphinfo->glyph == NO_GLYPH) fprintf(dumplog_file, " %s\n", str); else fprintf(dumplog_file, " %c - %s\n", ch, str); @@ -1350,10 +1350,8 @@ boolean preselected UNUSED; } /*ARGSUSED*/ -STATIC_OVL void -dump_end_menu(win, str) -winid win UNUSED; -const char *str; +staticfn void +dump_end_menu(winid win UNUSED, const char *str) { if (dumplog_file) { if (str) @@ -1363,19 +1361,15 @@ const char *str; } } -STATIC_OVL int -dump_select_menu(win, how, item) -winid win UNUSED; -int how UNUSED; -menu_item **item; +staticfn int +dump_select_menu(winid win UNUSED, int how UNUSED, menu_item **item) { *item = (menu_item *) 0; return 0; } void -dump_redirect(onoff_flag) -boolean onoff_flag; +dump_redirect(boolean onoff_flag) { if (dumplog_file) { if (onoff_flag) { @@ -1398,27 +1392,518 @@ boolean onoff_flag; } #ifdef TTY_GRAPHICS -#ifdef TEXTCOLOR #ifdef TOS extern const char *hilites[CLR_MAX]; #else extern NEARDATA char *hilites[CLR_MAX]; #endif #endif -#endif int -has_color(color) -int color; +has_color(int color) { return (iflags.use_color && windowprocs.name && (windowprocs.wincap & WC_COLOR) && windowprocs.has_color[color] #ifdef TTY_GRAPHICS -#if defined(TEXTCOLOR) && defined(TERMLIB) && !defined(NO_TERMS) +#if defined(TERMLIB) && !defined(NO_TERMS) && (hilites[color] != 0) #endif #endif ); } +int +glyph2ttychar(int glyph) +{ + glyph_info glyphinfo; + + map_glyphinfo(0, 0, glyph, 0, &glyphinfo); + return glyphinfo.ttychar; +} + +int +glyph2symidx(int glyph) +{ + glyph_info glyphinfo; + + map_glyphinfo(0, 0, glyph, 0, &glyphinfo); + return glyphinfo.gm.sym.symidx; +} + +char * +encglyph(int glyph) +{ + static char encbuf[20]; /* 10+1 would suffice */ + + Sprintf(encbuf, "\\G%04X%04X", svc.context.rndencode, glyph); + return encbuf; +} + +/* hexdd[] is defined in decl.c */ + +int +decode_glyph(const char *str, int *glyph_ptr) +{ + int rndchk = 0, dcount = 0, retval = 0; + const char *dp; + + for (; *str && ++dcount <= 4; ++str) { + if ((dp = strchr(hexdd, *str)) != 0) { + retval++; + rndchk = (rndchk * 16) + ((int) (dp - hexdd) / 2); + } else + break; + } + if (rndchk == svc.context.rndencode) { + *glyph_ptr = dcount = 0; + for (; *str && ++dcount <= 4; ++str) { + if ((dp = strchr(hexdd, *str)) != 0) { + retval++; + *glyph_ptr = (*glyph_ptr * 16) + ((int) (dp - hexdd) / 2); + } else + break; + } + return retval; + } + return 0; +} + +char * +decode_mixed(char *buf, const char *str) +{ + char *put = buf; + glyph_info glyphinfo = nul_glyphinfo; + + if (!str) + return strcpy(buf, ""); + + while (*str) { + if (*str == '\\') { + int dcount, so, ggv; + const char *save_str; + + save_str = str++; + switch (*str) { + case 'G': /* glyph value \GXXXXNNNN*/ + if ((dcount = decode_glyph(str + 1, &ggv))) { + str += (dcount + 1); + map_glyphinfo(0, 0, ggv, 0, &glyphinfo); + so = glyphinfo.gm.sym.symidx; + *put++ = gs.showsyms[so]; + /* 'str' is ready for the next loop iteration and '*str' + should not be copied at the end of this iteration */ + continue; + } else { + /* possible forgery - leave it the way it is */ + str = save_str; + } + break; + case '\\': + break; + case '\0': + /* String ended with '\\'. This can happen when someone + names an object with a name ending with '\\', drops the + named object on the floor nearby and does a look at all + nearby objects. */ + /* brh - should we perhaps not allow things to have names + that contain '\\' */ + str = save_str; + break; + } + } + *put++ = *str++; + } + *put = '\0'; + return buf; +} + + +/* + * This differs from putstr() because the str parameter can + * contain a sequence of characters representing: + * \GXXXXNNNN a glyph value, encoded by encglyph(). + * + * For window ports that haven't yet written their own + * XXX_putmixed() routine, this general one can be used. + * It replaces the encoded glyph sequence with a single + * showsyms[] char, then just passes that string onto + * putstr(). + */ + +void +genl_putmixed(winid window, int attr, const char *str) +{ + char buf[BUFSZ]; + + /* now send it to the normal putstr */ + putstr(window, attr, decode_mixed(buf, str)); +} + +/* possibly called to show usage info during command line processing when + an interface hasn't yet been chosen and set up */ +void +genl_display_file(const char *fname, boolean complain) +{ + char buf[BUFSZ]; + dlb *f = dlb_fopen(fname, "r"); + + if (!f) { + if (complain) /* send complaint to stdout rather than to stderr */ + fprintf(stdout, "\nCannot open \"%s\".\n", fname); + } else { + /* straight copy to stdout, no pagination or other interaction */ + while (dlb_fgets(buf, BUFSZ, f)) { + if (fputs(buf, stdout) < 0) + break; + } + (void) dlb_fclose(f); + } +} + +/* + * Window port helper function for menu invert routines to move the decision + * logic into one place instead of 7 different window-port routines. + */ +boolean +menuitem_invert_test( + int mode UNUSED, /* 0: invert; 1: select; 2: deselect */ + unsigned itemflags, /* itemflags for the item */ + boolean is_selected) /* current selection status of the item */ +{ + boolean skipinvert = (itemflags & MENU_ITEMFLAGS_SKIPINVERT) != 0; + + if (!skipinvert) /* if not flagged SKIPINVERT, always pass test */ + return TRUE; + /* + * mode 0: inverting current on/off state; + * 1: unconditionally setting on; + * 2: unconditionally setting off. + * menuinvertmode 0: treat entries flagged with skipinvert as ordinary + * (same as if not flagged); + * menuinvertmode 1: don't toggle bulk invert or bulk select entries On; + * allow toggling to Off (for invert and deselect; + * select doesn't do Off); + * menuinvertmode 2: don't toggle skipinvert entries either On or Off + * when any bulk change is performed. + */ + if (iflags.menuinvertmode == 2) { + return FALSE; + } else if (iflags.menuinvertmode == 1) { + return is_selected ? TRUE : FALSE; + } + return TRUE; +} + +/* + * helper routine if a window port wants to extract the glyph + * information from a glyph number representation in the string; + * the returned string is the remainder of the string after + * extracting the \GNNNNNNNN information. The glyph details, + * including the utf8 representation under ENHANCED_SYMBOLS, + * will be stored in the glyph_info struct pointed to by gip. + */ +const char * +mixed_to_glyphinfo(const char *str, glyph_info *gip) +{ + int dcount, ggv; + + if (!str || !gip) + return " "; + + *gip = nul_glyphinfo; + if (*str == '\\' && *(str + 1) == 'G') { + if ((dcount = decode_glyph(str + 2, &ggv))) { + map_glyphinfo(0, 0, ggv, 0, gip); + /* 'str' is ready for the next loop iteration and + '*str' should not be copied at the end of this + iteration */ + str += (dcount + 2); + } + } + return str; +} + +/* + * This is a somewhat generic menu for taking a list of NetHack style + * class choices and presenting them via a description + * rather than the traditional NetHack characters. + * (Benefits users whose first exposure to NetHack is via tiles). + * + * prompt + * The title at the top of the menu. + * + * category: 0 = monster class + * 1 = object class + * + * way + * FALSE = PICK_ONE, TRUE = PICK_ANY + * + * class_list + * a null terminated string containing the list of choices. + * + * class_selection + * a null terminated string containing the selected characters. + * + * Returns number selected. + */ +int +choose_classes_menu(const char *prompt, + int category, + boolean way, + char *class_list, + char *class_select) +{ + menu_item *pick_list = (menu_item *) 0; + winid win; + anything any; + char buf[BUFSZ]; + const char *text = 0; + boolean selected; + int ret, i, n, next_accelerator, accelerator = 0; + int clr = NO_COLOR; + + if (!class_list || !class_select) + return 0; + next_accelerator = 'a'; + any = cg.zeroany; + win = create_nhwindow(NHW_MENU); + start_menu(win, MENU_BEHAVE_STANDARD); + while (*class_list) { + int idx; + + selected = FALSE; + switch (category) { + case 0: + idx = def_char_to_monclass(*class_list); + if (!IndexOk(idx, def_monsyms)) { + panic("choose_classes_menu: invalid monclass '%c'", + *class_list); + /*NOTREACHED*/ + } + text = def_monsyms[idx].explain; + accelerator = *class_list; + Sprintf(buf, "%s", text); + break; + case 1: + idx = def_char_to_objclass(*class_list); + if (!IndexOk(idx, def_oc_syms)) { + panic("choose_classes_menu: invalid objclass '%c'", + *class_list); + /*NOTREACHED*/ + } + text = def_oc_syms[idx].explain; + accelerator = next_accelerator; + Sprintf(buf, "%c %s", *class_list, text); + break; + default: + panic("choose_classes_menu: invalid category %d", category); + /*NOTREACHED*/ + } + if (way && *class_select) { /* Selections there already */ + if (strchr(class_select, *class_list)) { + selected = TRUE; + } + } + any.a_int = *class_list; + add_menu(win, &nul_glyphinfo, &any, accelerator, + category ? *class_list : 0, ATR_NONE, clr, buf, + selected ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); + if (category > 0) { + if (next_accelerator == 'Z') + break; + else if (next_accelerator == 'z') + next_accelerator = 'A'; + else + ++next_accelerator; + } + ++class_list; + } + if (category == 1 && next_accelerator <= 'z') { + /* for objects, add "A - ' ' all classes", after a separator */ + add_menu_str(win, ""); + any = cg.zeroany; + any.a_int = (int) ' '; + Sprintf(buf, "%c %s", (char) any.a_int, "All classes of objects"); + /* we won't preselect this even if the incoming list is empty; + having it selected means that it would have to be explicitly + de-selected in order to select anything else */ + add_menu(win, &nul_glyphinfo, &any, 'A', 0, + ATR_NONE, clr, buf, MENU_ITEMFLAGS_SKIPINVERT); + if (!strcmp(prompt, "Autopickup what?")) { + add_menu_str(win, + "Note: when no choices are selected, \"all\" is implied."); + /* for 'O', "toggle" should be intuitive; for 'm O', it would + probably be better to say "Set 'autopickup' to true|false" */ + add_menu_str(win, flags.pickup + ? "Toggle off 'autopickup' to not pick up anything." + : "Toggle on 'autopickup' to automatically pick these things up."); + } + } + end_menu(win, prompt); + n = select_menu(win, way ? PICK_ANY : PICK_ONE, &pick_list); + destroy_nhwindow(win); + if (n > 0) { + if (category == 1) { + /* for object classes, first check for 'all'; it means 'use + a blank list' rather than 'collect every possible choice' */ + for (i = 0; i < n; ++i) + if (pick_list[i].item.a_int == ' ') { + pick_list[0].item.a_int = ' '; + n = 1; /* return 1; also an implicit 'break;' */ + } + } + for (i = 0; i < n; ++i) + *class_select++ = (char) pick_list[i].item.a_int; + free((genericptr_t) pick_list); + ret = n; + } else if (n == -1) { + class_select = eos(class_select); + ret = -1; + } else { + ret = 0; + } + *class_select = '\0'; + return ret; +} + +/* enum and structs are defined in wintype.h */ + +win_request_info zerowri = { { 0L, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, { NO_COLOR, ATR_NONE }}}; + +void +adjust_menu_promptstyle(winid window, color_attr *style) +{ + win_request_info wri = zerowri; + wri.fromcore.menu_promptstyle.color = style->color; + wri.fromcore.menu_promptstyle.attr = style->attr; + /* relay the style change to the window port */ + (void) ctrl_nhwindow(window, set_menu_promptstyle, &wri); + go.opt_need_promptstyle = FALSE; +} + +/* + * Common code point leading into the interface-specific + * add_menu() to allow single-spot adjustments to the parameters, + * such as those done by menu_colors. + */ +void +add_menu( + winid window, /* window to use, must be of type NHW_MENU */ + const glyph_info *glyphinfo, /* glyph info with glyph to + * display with item */ + const anything *identifier, /* what to return if selected */ + char ch, /* selector letter (0 = pick our own) */ + char gch, /* group accelerator (0 = no group) */ + int attr, /* attribute for menu text (str) */ + int color, /* color for menu text (str) */ + const char *str, /* menu text */ + unsigned int itemflags) /* itemflags such as MENU_ITEMFLAGS_SELECTED */ +{ + if (!str) { + /* if 'str' is Null, just return without adding any menu entry */ + debugpline0("add_menu(Null)"); + return; + } + + if (iflags.use_menu_color) { + if ((itemflags & MENU_ITEMFLAGS_SKIPMENUCOLORS) == 0) + (void) get_menu_coloring(str, &color, &attr); + } + /* this is the only function that cared about this flag; remove it now */ + itemflags &= ~MENU_ITEMFLAGS_SKIPMENUCOLORS; + + (*windowprocs.win_add_menu)(window, glyphinfo, identifier, + ch, gch, attr, color, str, itemflags); +} + +/* insert a non-selectable, possibly highlighted line of text into a menu */ +void +add_menu_heading(winid tmpwin, const char *buf) +{ + anything any = cg.zeroany; + int attr = iflags.menu_headings.attr, + color = iflags.menu_headings.color; + + /* suppress highlighting during end-of-game disclosure */ + if (program_state.gameover) + attr = ATR_NONE, color = NO_COLOR; + + add_menu(tmpwin, &nul_glyphinfo, &any, '\0', '\0', attr, color, + buf, MENU_ITEMFLAGS_SKIPMENUCOLORS); +} + +/* insert a non-selectable, unhighlighted line of text into a menu */ +void +add_menu_str(winid tmpwin, const char *buf) +{ + anything any = cg.zeroany; + + add_menu(tmpwin, &nul_glyphinfo, &any, '\0', '\0', ATR_NONE, NO_COLOR, + buf, MENU_ITEMFLAGS_NONE); +} + +staticfn boolean +get_menu_coloring(const char *str, int *color, int *attr) +{ + struct menucoloring *tmpmc; + + if (iflags.use_menu_color) + for (tmpmc = gm.menu_colorings; tmpmc; tmpmc = tmpmc->next) + if (regex_match(str, tmpmc->match)) { + *color = tmpmc->color; + *attr = tmpmc->attr; + return TRUE; + } + return FALSE; +} + +int +select_menu(winid window, int how, menu_item **menu_list) +{ + int reslt; + boolean old_bot_disabled = gb.bot_disabled; + + gb.bot_disabled = TRUE; + reslt = (*windowprocs.win_select_menu)(window, how, menu_list); + gb.bot_disabled = old_bot_disabled; + return reslt; +} + +void +getlin(const char *query, char *bufp) +{ + boolean old_bot_disabled = gb.bot_disabled; + char *obufp = bufp; + boolean got_cmdq = FALSE; + struct _cmd_queue *cmdq = NULL; + + while ((cmdq = cmdq_pop()) != 0) { + if (cmdq->typ == CMDQ_KEY) { + got_cmdq = TRUE; + *bufp = (cmdq->key != '\n') ? cmdq->key : '\0'; + bufp++; + if (cmdq->key == '\n') + break; + } else { + break; + } + free(cmdq); + cmdq = NULL; + } + if (cmdq) + free(cmdq); + + if (got_cmdq) { + *bufp = '\0'; + pline("%s %s", query, obufp); + return; + } + + program_state.in_getlin = 1; + gb.bot_disabled = TRUE; + (*windowprocs.win_getlin)(query, bufp); + gb.bot_disabled = old_bot_disabled; + program_state.in_getlin = 0; +} /*windows.c*/ diff --git a/src/wizard.c b/src/wizard.c index b1dae1e59..b09e3a150 100644 --- a/src/wizard.c +++ b/src/wizard.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 wizard.c $NHDT-Date: 1561336025 2019/06/24 00:27:05 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.56 $ */ +/* NetHack 5.0 wizard.c $NHDT-Date: 1741407262 2025/03/07 20:14:22 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.116 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2016. */ /* NetHack may be freely redistributed. See license for details. */ @@ -9,15 +9,17 @@ /* - generalized for 3.1 (mike@bullns.on01.bull.ca) */ #include "hack.h" -#include "qtext.h" -STATIC_DCL short FDECL(which_arti, (int)); -STATIC_DCL boolean FDECL(mon_has_arti, (struct monst *, SHORT_P)); -STATIC_DCL struct monst *FDECL(other_mon_has_arti, (struct monst *, SHORT_P)); -STATIC_DCL struct obj *FDECL(on_ground, (SHORT_P)); -STATIC_DCL boolean FDECL(you_have, (int)); -STATIC_DCL unsigned long FDECL(target_on, (int, struct monst *)); -STATIC_DCL unsigned long FDECL(strategy, (struct monst *)); +staticfn short which_arti(int); +staticfn boolean mon_has_arti(struct monst *, short) NONNULLARG1; +/* other_mon_has_arti() won't blow up if passed a NULL monst, + * but its caller target_on() passes it a nonnull monst; + * it may return a NULL monst pointer */ +staticfn struct monst *other_mon_has_arti(struct monst *, short) NONNULLARG1; +staticfn struct obj *on_ground(short); /* might return NULL obj pointer */ +staticfn boolean you_have(int); +staticfn unsigned long target_on(int, struct monst *) NONNULLARG2; +staticfn unsigned long strategy(struct monst *) NONNULLARG1; /* adding more neutral creatures will tend to reduce the number of monsters summoned by nasty(); adding more lawful creatures will reduce the number @@ -33,16 +35,17 @@ static NEARDATA const int nasties[] = { PM_XORN, PM_ZRUTY, PM_LEOCROTTA, PM_BALUCHITHERIUM, PM_CARNIVOROUS_APE, PM_FIRE_ELEMENTAL, PM_JABBERWOCK, PM_IRON_GOLEM, PM_OCHRE_JELLY, PM_GREEN_SLIME, + PM_DISPLACER_BEAST, PM_GENETIC_ENGINEER, /* chaotic */ - PM_BLACK_DRAGON, PM_RED_DRAGON, PM_ARCH_LICH, PM_VAMPIRE_LORD, + PM_BLACK_DRAGON, PM_RED_DRAGON, PM_ARCH_LICH, PM_VAMPIRE_LEADER, PM_MASTER_MIND_FLAYER, PM_DISENCHANTER, PM_WINGED_GARGOYLE, - PM_STORM_GIANT, PM_OLOG_HAI, PM_ELF_LORD, PM_ELVENKING, - PM_OGRE_KING, PM_CAPTAIN, PM_GREMLIN, + PM_STORM_GIANT, PM_OLOG_HAI, PM_ELF_NOBLE, PM_ELVEN_MONARCH, + PM_OGRE_TYRANT, PM_CAPTAIN, PM_GREMLIN, /* lawful */ PM_SILVER_DRAGON, PM_ORANGE_DRAGON, PM_GREEN_DRAGON, PM_YELLOW_DRAGON, PM_GUARDIAN_NAGA, PM_FIRE_GIANT, PM_ALEAX, PM_COUATL, PM_HORNED_DEVIL, PM_BARBED_DEVIL, - /* (titans, ki-rin, and golden nagas are suitably nasty, but + /* (Archons, titans, ki-rin, and golden nagas are suitably nasty, but they're summoners so would aggravate excessive summoning) */ }; @@ -55,7 +58,7 @@ static NEARDATA const unsigned wizapp[] = { /* If you've found the Amulet, make the Wizard appear after some time */ /* Also, give hints about portal locations, if amulet is worn/wielded -dlc */ void -amulet() +amulet(void) { struct monst *mtmp; struct trap *ttmp; @@ -68,7 +71,7 @@ amulet() if ((((amu = uamul) != 0 && amu->otyp == AMULET_OF_YENDOR) || ((amu = uwep) != 0 && amu->otyp == AMULET_OF_YENDOR)) && !rn2(15)) { - for (ttmp = ftrap; ttmp; ttmp = ttmp->ntrap) { + for (ttmp = gf.ftrap; ttmp; ttmp = ttmp->ntrap) { if (ttmp->ttyp == MAGIC_PORTAL) { int du = distu(ttmp->tx, ttmp->ty); if (du <= 9) @@ -83,7 +86,7 @@ amulet() } } - if (!context.no_of_wizards) + if (!svc.context.no_of_wizards) return; /* find Wizard, and wake him if necessary */ for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { @@ -91,7 +94,7 @@ amulet() continue; if (mtmp->iswiz && mtmp->msleeping && !rn2(40)) { mtmp->msleeping = 0; - if (distu(mtmp->mx, mtmp->my) > 2) + if (!m_next2u(mtmp)) You( "get the creepy feeling that somebody noticed your taking the Amulet."); return; @@ -100,10 +103,9 @@ amulet() } int -mon_has_amulet(mtmp) -register struct monst *mtmp; +mon_has_amulet(struct monst *mtmp) { - register struct obj *otmp; + struct obj *otmp; for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj) if (otmp->otyp == AMULET_OF_YENDOR) @@ -112,10 +114,9 @@ register struct monst *mtmp; } int -mon_has_special(mtmp) -register struct monst *mtmp; +mon_has_special(struct monst *mtmp) { - register struct obj *otmp; + struct obj *otmp; for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj) if (otmp->otyp == AMULET_OF_YENDOR @@ -134,15 +135,11 @@ register struct monst *mtmp; * The strategy section decides *what* the monster is going * to attempt, the tactics section implements the decision. */ -#define STRAT(w, x, y, typ) \ - ((unsigned long) (w) | ((unsigned long) (x) << 16) \ - | ((unsigned long) (y) << 8) | (unsigned long) (typ)) #define M_Wants(mask) (mtmp->data->mflags3 & (mask)) -STATIC_OVL short -which_arti(mask) -register int mask; +staticfn short +which_arti(int mask) { switch (mask) { case M3_WANTSAMUL: @@ -164,12 +161,10 @@ register int mask; * since bell, book, candle, and amulet are all objects, not really * artifacts right now. [MRS] */ -STATIC_OVL boolean -mon_has_arti(mtmp, otyp) -register struct monst *mtmp; -register short otyp; +staticfn boolean +mon_has_arti(struct monst *mtmp, short otyp) { - register struct obj *otmp; + struct obj *otmp; for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj) { if (otyp) { @@ -181,12 +176,14 @@ register short otyp; return 0; } -STATIC_OVL struct monst * -other_mon_has_arti(mtmp, otyp) -register struct monst *mtmp; -register short otyp; +/* + * Returns some monster other than mtmp that + * has artifact, or NULL monst pointer. + */ +staticfn struct monst * +other_mon_has_arti(struct monst *mtmp, short otyp) { - register struct monst *mtmp2; + struct monst *mtmp2; for (mtmp2 = fmon; mtmp2; mtmp2 = mtmp2->nmon) /* no need for !DEADMONSTER check here since they have no inventory */ @@ -197,11 +194,14 @@ register short otyp; return (struct monst *) 0; } -STATIC_OVL struct obj * -on_ground(otyp) -register short otyp; +/* + * Returns obj of type specified if there is one + * on the ground, otherwise returns NULL obj pointer. + */ +staticfn struct obj * +on_ground(short otyp) { - register struct obj *otmp; + struct obj *otmp; for (otmp = fobj; otmp; otmp = otmp->nobj) if (otyp) { @@ -212,9 +212,8 @@ register short otyp; return (struct obj *) 0; } -STATIC_OVL boolean -you_have(mask) -register int mask; +staticfn boolean +you_have(int mask) { switch (mask) { case M3_WANTSAMUL: @@ -233,37 +232,42 @@ register int mask; return 0; } -STATIC_OVL unsigned long -target_on(mask, mtmp) -register int mask; -register struct monst *mtmp; +staticfn unsigned long +target_on(int mask, struct monst *mtmp) { - register short otyp; - register struct obj *otmp; - register struct monst *mtmp2; + short otyp; + struct obj *otmp; + struct monst *mtmp2; if (!M_Wants(mask)) return (unsigned long) STRAT_NONE; otyp = which_arti(mask); if (!mon_has_arti(mtmp, otyp)) { - if (you_have(mask)) - return STRAT(STRAT_PLAYER, u.ux, u.uy, mask); - else if ((otmp = on_ground(otyp))) - return STRAT(STRAT_GROUND, otmp->ox, otmp->oy, mask); - else if ((mtmp2 = other_mon_has_arti(mtmp, otyp)) != 0 - /* when seeking the Amulet, avoid targetting the Wizard + if (you_have(mask)) { + mtmp->mgoal.x = u.ux; + mtmp->mgoal.y = u.uy; + return (STRAT_PLAYER | mask); + } else if ((otmp = on_ground(otyp))) { + mtmp->mgoal.x = otmp->ox; + mtmp->mgoal.y = otmp->oy; + return (STRAT_GROUND | mask); + } else if ((mtmp2 = other_mon_has_arti(mtmp, otyp)) != 0 + /* when seeking the Amulet, avoid targeting the Wizard or temple priests (to protect Moloch's high priest) */ && (otyp != AMULET_OF_YENDOR - || (!mtmp2->iswiz && !inhistemple(mtmp2)))) - return STRAT(STRAT_MONSTR, mtmp2->mx, mtmp2->my, mask); + || (!mtmp2->iswiz && !inhistemple(mtmp2)))) { + mtmp->mgoal.x = mtmp2->mx; + mtmp->mgoal.y = mtmp2->my; + return (STRAT_MONSTR | mask); + } } + mtmp->mgoal.x = mtmp->mgoal.y = 0; return (unsigned long) STRAT_NONE; } -STATIC_OVL unsigned long -strategy(mtmp) -register struct monst *mtmp; +staticfn unsigned long +strategy(struct monst *mtmp) { unsigned long strat, dstrat; @@ -285,8 +289,8 @@ register struct monst *mtmp; case 1: /* the wiz is less cautious */ if (mtmp->data != &mons[PM_WIZARD_OF_YENDOR]) return (unsigned long) STRAT_HEAL; - /* else fall through */ - + FALLTHROUGH; + /* FALLTHRU */ case 2: dstrat = STRAT_HEAL; break; @@ -296,7 +300,7 @@ register struct monst *mtmp; break; } - if (context.made_amulet) + if (svc.context.made_amulet) if ((strat = target_on(M3_WANTSAMUL, mtmp)) != STRAT_NONE) return strat; @@ -322,48 +326,50 @@ register struct monst *mtmp; return dstrat; } +/* pick a destination for a covetous monster to flee to so that it can + heal or for guardians (Kops) to congregate at to block hero's progress */ void -choose_stairs(sx, sy) -xchar *sx; -xchar *sy; +choose_stairs( + coordxy *sx, coordxy *sy, /* output; left as-is if no spot found */ + boolean dir) /* True: forward, False: backtrack (usually up) */ { - xchar x = 0, y = 0; - - if (builds_up(&u.uz)) { - if (xdnstair) { - x = xdnstair; - y = ydnstair; - } else if (xdnladder) { - x = xdnladder; - y = ydnladder; - } - } else { - if (xupstair) { - x = xupstair; - y = yupstair; - } else if (xupladder) { - x = xupladder; - y = yupladder; + stairway *stway; + boolean stdir = builds_up(&u.uz) ? dir : !dir; + + /* look for stairs in direction 'stdir' (True: up, False: down) */ + stway = stairway_find_type_dir(FALSE, stdir); + if (!stway) { + /* no stairs; look for ladder it that direction */ + stway = stairway_find_type_dir(TRUE, stdir); + if (!stway) { + /* no ladder either; look for branch stairs or ladder in any + direction */ + for (stway = gs.stairs; stway; stway = stway->next) + if (stway->tolev.dnum != u.uz.dnum) + break; + /* if no branch stairs/ladder, check for regular stairs in + opposite direction, then for regular ladder if necessary */ + if (!stway) { + stway = stairway_find_type_dir(FALSE, !stdir); + if (!stway) + stway = stairway_find_type_dir(TRUE, !stdir); + } } + /* [note: 'stway' could still be Null if the only access to this + level is via magic portal] */ } - if (!x && sstairs.sx) { - x = sstairs.sx; - y = sstairs.sy; - } - - if (x && y) { - *sx = x; - *sy = y; - } + if (stway) + *sx = stway->sx, *sy = stway->sy; } +DISABLE_WARNING_UNREACHABLE_CODE + int -tactics(mtmp) -register struct monst *mtmp; +tactics(struct monst *mtmp) { unsigned long strat = strategy(mtmp); - xchar sx = 0, sy = 0, mx, my; + coordxy sx = 0, sy = 0, mx, my; mtmp->mstrategy = (mtmp->mstrategy & (STRAT_WAITMASK | STRAT_APPEARMSG)) | strat; @@ -371,15 +377,21 @@ register struct monst *mtmp; switch (strat) { case STRAT_HEAL: /* hide and recover */ mx = mtmp->mx, my = mtmp->my; + + if (u.uswallow && u.ustuck == mtmp) + expels(mtmp, mtmp->data, TRUE); + /* if wounded, hole up on or near the stairs (to block them) */ - choose_stairs(&sx, &sy); + choose_stairs(&sx, &sy, (mtmp->m_id % 2)); mtmp->mavenge = 1; /* covetous monsters attack while fleeing */ if (In_W_tower(mx, my, &u.uz) || (mtmp->iswiz && !sx && !mon_has_amulet(mtmp))) { - if (!rn2(3 + mtmp->mhp / 10)) - (void) rloc(mtmp, TRUE); + if (!noteleport_level(mtmp) && + !rn2(3 + mtmp->mhp / 10)) + (void) rloc(mtmp, RLOC_MSG); } else if (sx && (mx != sx || my != sy)) { - if (!mnearto(mtmp, sx, sy, TRUE)) { + if (!noteleport_level(mtmp) && + !mnearto(mtmp, sx, sy, TRUE, RLOC_MSG)) { /* couldn't move to the target spot for some reason, so stay where we are (don't actually need rloc_to() because mtmp is still on the map at ... */ @@ -391,29 +403,35 @@ register struct monst *mtmp; /* if you're not around, cast healing spells */ if (distu(mx, my) > (BOLT_LIM * BOLT_LIM)) if (mtmp->mhp <= mtmp->mhpmax - 8) { - mtmp->mhp += rnd(8); + healmon(mtmp, rnd(8), 0); return 1; } + FALLTHROUGH; /*FALLTHRU*/ case STRAT_NONE: /* harass */ - if (!rn2(!mtmp->mflee ? 5 : 33)) - mnexto(mtmp); + if (!noteleport_level(mtmp) && !rn2(!mtmp->mflee ? 5 : 33)) + mnexto(mtmp, RLOC_MSG); return 0; default: /* kill, maim, pillage! */ { long where = (strat & STRAT_STRATMASK); - xchar tx = STRAT_GOALX(strat), ty = STRAT_GOALY(strat); + coordxy tx = mtmp->mgoal.x, ty = mtmp->mgoal.y; int targ = (int) (strat & STRAT_GOAL); struct obj *otmp; - if (!targ) { /* simply wants you to close */ + if (!targ || !isok(tx, ty)) { /* simply wants you to close */ return 0; } - if ((u.ux == tx && u.uy == ty) || where == STRAT_PLAYER) { + if (noteleport_level(mtmp) && !monnear(mtmp, tx, ty)) + return 0; + if (u_at(tx, ty) || where == STRAT_PLAYER) { /* player is standing on it (or has it) */ - mnexto(mtmp); + mx = mtmp->mx, my = mtmp->my; + if (noteleport_level(mtmp) || + !mnearto(mtmp, tx, ty, FALSE, RLOC_MSG)) + rloc_to(mtmp, mx, my); /* no room? stay put */ return 0; } if (where == STRAT_GROUND) { @@ -424,9 +442,7 @@ register struct monst *mtmp; if ((otmp = on_ground(which_arti(targ))) != 0) { if (cansee(mtmp->mx, mtmp->my)) pline("%s picks up %s.", Monnam(mtmp), - (distu(mtmp->mx, mtmp->my) <= 5) - ? doname(otmp) - : distant_name(otmp, doname)); + distant_name(otmp, doname)); obj_extract_self(otmp); (void) mpickobj(mtmp, otmp); return 1; @@ -434,13 +450,14 @@ register struct monst *mtmp; return 0; } else { /* a monster is standing on it - cause some trouble */ - if (!rn2(5)) - mnexto(mtmp); + if (!rn2(5) && !noteleport_level(mtmp)) + mnexto(mtmp, RLOC_MSG); return 0; } } else { /* a monster has it - 'port beside it. */ mx = mtmp->mx, my = mtmp->my; - if (!mnearto(mtmp, tx, ty, FALSE)) + if (!noteleport_level(mtmp) && + !mnearto(mtmp, tx, ty, FALSE, RLOC_MSG)) rloc_to(mtmp, mx, my); /* no room? stay put */ return 0; } @@ -450,10 +467,11 @@ register struct monst *mtmp; return 0; } +RESTORE_WARNINGS + /* are there any monsters mon could aggravate? */ boolean -has_aggravatables(mon) -struct monst *mon; +has_aggravatables(struct monst *mon) { struct monst *mtmp; boolean in_w_tower = In_W_tower(mon->mx, mon->my, &u.uz); @@ -466,17 +484,16 @@ struct monst *mon; continue; if (in_w_tower != In_W_tower(mtmp->mx, mtmp->my, &u.uz)) continue; - if ((mtmp->mstrategy & STRAT_WAITFORU) != 0 - || mtmp->msleeping || !mtmp->mcanmove) + if ((mtmp->mstrategy & STRAT_WAITFORU) != 0 || helpless(mtmp)) return TRUE; } return FALSE; } void -aggravate() +aggravate(void) { - register struct monst *mtmp; + struct monst *mtmp; boolean in_w_tower = In_W_tower(u.ux, u.uy, &u.uz); for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { @@ -493,29 +510,35 @@ aggravate() } } +/* "Double Trouble" spell cast by the Wizard; caller is responsible for + only casting this when there is currently one wizard in existence; + the clone can't use it unless/until its creator has been killed off */ void -clonewiz() +clonewiz(void) { - register struct monst *mtmp2; + struct monst *mtmp2; - if ((mtmp2 = makemon(&mons[PM_WIZARD_OF_YENDOR], u.ux, u.uy, NO_MM_FLAGS)) + if ((mtmp2 = makemon(&mons[PM_WIZARD_OF_YENDOR], u.ux, u.uy, MM_NOWAIT)) != 0) { mtmp2->msleeping = mtmp2->mtame = mtmp2->mpeaceful = 0; if (!u.uhave.amulet && rn2(2)) { /* give clone a fake */ (void) add_to_minv(mtmp2, mksobj(FAKE_AMULET_OF_YENDOR, TRUE, FALSE)); } - mtmp2->m_ap_type = M_AP_MONSTER; - mtmp2->mappearance = wizapp[rn2(SIZE(wizapp))]; + if (!Protection_from_shape_changers) { + mtmp2->m_ap_type = M_AP_MONSTER; + mtmp2->mappearance = ROLL_FROM(wizapp); + } newsym(mtmp2->mx, mtmp2->my); } } /* also used by newcham() */ int -pick_nasty() +pick_nasty( + int difcap) /* if non-zero, try to make difficulty be lower than this */ { - int res = nasties[rn2(SIZE(nasties))]; + int alt, res = ROLL_FROM(nasties); /* To do? Possibly should filter for appropriate forms when * in the elemental planes or surrounded by water or lava. @@ -524,8 +547,35 @@ pick_nasty() * but we don't try very hard. */ if (Is_rogue_level(&u.uz) - && !('A' <= mons[res].mlet && mons[res].mlet <= 'Z')) - res = nasties[rn2(SIZE(nasties))]; + && !('A' <= monsym(&mons[res]) && monsym(&mons[res]) <= 'Z')) + res = ROLL_FROM(nasties); + + /* if genocided or too difficult or out of place, try a substitute + when a suitable one exists + arch-lich -> master lich, + master mind flayer -> mind flayer, + but the substitutes are likely to be genocided too */ + alt = res; + if ((svm.mvitals[res].mvflags & G_GENOD) != 0 + || (difcap > 0 && mons[res].difficulty >= difcap) + /* note: nasty() -> makemon() ignores G_HELL|G_NOHELL; + arch-lich and master lich are both flagged as hell-only; + this filtering demotes arch-lich to master lich when + outside of Gehennom (unless the latter has been genocided) */ + || (mons[res].geno & (Inhell ? G_NOHELL : G_HELL)) != 0) + alt = big_to_little(res); + if (alt != res && (svm.mvitals[alt].mvflags & G_GENOD) == 0) { + const char *mnam = mons[alt].pmnames[NEUTRAL], + *lastspace = strrchr(mnam, ' '); + + /* only non-juveniles can become alternate choice */ + if (strncmp(mnam, "baby ", 5) + && (!lastspace + || (strcmp(lastspace, " hatchling") + && strcmp(lastspace, " pup") + && strcmp(lastspace, " cub")))) + res = alt; + } return res; } @@ -538,14 +588,15 @@ pick_nasty() creatures on average (in 3.6.0 and earlier, Null was treated as chaotic); returns the number of monsters created */ int -nasty(summoner) -struct monst *summoner; +nasty(struct monst *summoner) { - register struct monst *mtmp; - register int i, j; - int castalign = (summoner ? sgn(summoner->data->maligntyp) : 0); + struct monst *mtmp; coord bypos; - int count, census, tmp, makeindex, s_cls, m_cls; + int i, j, count, census, tmp, makeindex, + s_cls, m_cls, difcap, trylimit, castalign; + /* when a monster casts the "summon nasties" spell, it gives feedback; + when random post-Wizard harassment casts that, we give feedback */ + unsigned mmflags = summoner ? MM_NOMSG : NO_MM_FLAGS; #define MAXNASTIES 10 /* more than this can be created */ @@ -559,16 +610,19 @@ struct monst *summoner; } else { count = 0; s_cls = summoner ? summoner->data->mlet : 0; + difcap = summoner ? summoner->data->difficulty : 0; /* spellcasters */ + castalign = summoner ? sgn(summoner->data->maligntyp) : 0; tmp = (u.ulevel > 3) ? u.ulevel / 3 : 1; /* if we don't have a casting monster, nasties appear around hero, otherwise they'll appear around spot summoner thinks she's at */ bypos.x = u.ux; bypos.y = u.uy; - for (i = rnd(tmp); i > 0 && count < MAXNASTIES; --i) - /* Of the 42 nasties[], 10 are lawful, 14 are chaotic, - * and 18 are neutral. + for (i = rnd(tmp); i > 0 && count < MAXNASTIES; --i) { + /* Of the 44 nasties[], 10 are lawful, 14 are chaotic, + * and 20 are neutral. [These numbers are up date for + * 5.0.0; the ones in the next paragraph are not....] * - * Neutral caster, used for late-game harrassment, + * Neutral caster, used for late-game harassment, * has 18/42 chance to stop the inner loop on each * critter, 24/42 chance for another iteration. * Lawful caster has 28/42 chance to stop unless the @@ -583,18 +637,19 @@ struct monst *summoner; * randomized so it won't always do so. */ for (j = 0; j < 20; j++) { - /* Don't create more spellcasters of the monsters' level or + /* Don't create more spellcasters of the monster's level or * higher--avoids chain summoners filling up the level. */ + trylimit = 10 + 1; /* 10 tries */ do { - makeindex = pick_nasty(); + if (!--trylimit) + goto nextj; /* break this loop, continue outer one */ + makeindex = pick_nasty(difcap); m_cls = mons[makeindex].mlet; - } while (summoner - && ((attacktype(&mons[makeindex], AT_MAGC) - && mons[makeindex].difficulty - >= mons[summoner->mnum].difficulty) - || (s_cls == S_DEMON && m_cls == S_ANGEL) - || (s_cls == S_ANGEL && m_cls == S_DEMON))); + } while ((difcap > 0 && mons[makeindex].difficulty >= difcap + && attacktype(&mons[makeindex], AT_MAGC)) + || (s_cls == S_DEMON && m_cls == S_ANGEL) + || (s_cls == S_ANGEL && m_cls == S_DEMON)); /* do this after picking the monster to place */ if (summoner && !enexto(&bypos, summoner->mux, summoner->muy, &mons[makeindex])) @@ -602,21 +657,52 @@ struct monst *summoner; /* this honors genocide but overrides extinction; it ignores inside-hell-only (G_HELL) & outside-hell-only (G_NOHELL) */ if ((mtmp = makemon(&mons[makeindex], bypos.x, bypos.y, - NO_MM_FLAGS)) != 0) { + mmflags)) != 0) { mtmp->msleeping = mtmp->mpeaceful = mtmp->mtame = 0; set_malign(mtmp); - } else /* random monster to substitute for geno'd selection */ - mtmp = makemon((struct permonst *) 0, bypos.x, bypos.y, - NO_MM_FLAGS); + } else { + /* random monster to substitute for geno'd selection; + unlike direct choice, not forced to be hostile [why?]; + limit spellcasters to inhibit chain summoning */ + if ((mtmp = makemon((struct permonst *) 0, + bypos.x, bypos.y, mmflags)) != 0) { + m_cls = mtmp->data->mlet; + if ((difcap > 0 && mtmp->data->difficulty >= difcap + /* always capping for substitutes made wanton + genocide become too strong in the endgame */ + && rn2(In_endgame(&u.uz) ? 3 : 7) /* usually */ + && attacktype(mtmp->data, AT_MAGC)) + || (s_cls == S_DEMON && m_cls == S_ANGEL) + || (s_cls == S_ANGEL && m_cls == S_DEMON)) + mtmp = unmakemon(mtmp, NO_MM_FLAGS); /* Null */ + } + } + if (mtmp) { + /* if creating an arch-lich or Archon, further directly + selected nasties will have to be less difficult, and + substitues for geno victims will usually be less + (note: Archon is not in nasties[] but could be chosen + as random replacement for a genocided selection) */ + if (mtmp->data == &mons[PM_ARCH_LICH] + || mtmp->data == &mons[PM_ARCHON]) { + tmp = min(mons[PM_ARCHON].difficulty, /* A:26 */ + mons[PM_ARCH_LICH].difficulty); /* L:31 */ + if (!difcap || difcap > tmp) + difcap = tmp; /* rest must be lower difficulty */ + } /* delay first use of spell or breath attack */ mtmp->mspec_used = rnd(4); + if (++count >= MAXNASTIES || mtmp->data->maligntyp == 0 || sgn(mtmp->data->maligntyp) == castalign) break; } - } + nextj: + ; /* empty; label must be followed by a statement */ + } /* for j */ + } /* for i */ } if (count) @@ -624,30 +710,31 @@ struct monst *summoner; return count; } -/* Let's resurrect the wizard, for some unexpected fun. */ +/* Let's resurrect the Wizard, for some unexpected fun. */ void -resurrect() +resurrect(void) { struct monst *mtmp, **mmtmp; long elapsed; const char *verb; - if (!context.no_of_wizards) { + if (!svc.context.no_of_wizards) { /* make a new Wizard */ verb = "kill"; mtmp = makemon(&mons[PM_WIZARD_OF_YENDOR], u.ux, u.uy, MM_NOWAIT); /* affects experience; he's not coming back from a corpse but is subject to repeated killing like a revived corpse */ - if (mtmp) mtmp->mrevived = 1; + if (mtmp) + mtmp->mrevived = 1; } else { /* look for a migrating Wizard */ verb = "elude"; - mmtmp = &migrating_mons; + mmtmp = &gm.migrating_mons; while ((mtmp = *mmtmp) != 0) { if (mtmp->iswiz /* if he has the Amulet, he won't bring it to you */ && !mon_has_amulet(mtmp) - && (elapsed = monstermoves - mtmp->mlstmv) > 0L) { + && (elapsed = svm.moves - mtmp->mlstmv) > 0L) { mon_catchup_elapsed_time(mtmp, elapsed); if (elapsed >= LARGEST_INT) elapsed = LARGEST_INT - 1; @@ -656,11 +743,14 @@ resurrect() mtmp->msleeping = 0; if (mtmp->mfrozen == 1) /* would unfreeze on next move */ mtmp->mfrozen = 0, mtmp->mcanmove = 1; - if (mtmp->mcanmove && !mtmp->msleeping) { + if (!helpless(mtmp)) { *mmtmp = mtmp->nmon; - mon_arrive(mtmp, TRUE); + mon_arrive(mtmp, -1); /* -1: Wiz_arrive (dog.c) */ + /* mx: mon_arrive() might have sent mtmp into limbo */ + if (!mtmp->mx) + mtmp = 0; /* note: there might be a second Wizard; if so, - he'll have to wait til the next resurrection */ + he'll have to wait until the next resurrection */ break; } } @@ -669,10 +759,21 @@ resurrect() } if (mtmp) { - mtmp->mtame = mtmp->mpeaceful = 0; /* paranoia */ + /* FIXME: when a new wizard is created by makemon(), it gives + a " appears" message, delivered after he's been placed + on the map; however, when an existing wizard comes off + migrating_mons, he ends up triggering " vanishes and + reappears" on his first move (tactics when hero is carrying + the Amulet); setting STRAT_WAITMASK suppresses that but then + he just sits wherever he is, "meditating", contradicting the + threatening message below */ + mtmp->mstrategy &= ~STRAT_WAITMASK; + + mtmp->mtame = 0, mtmp->mpeaceful = 0; /* paranoia */ set_malign(mtmp); if (!Deaf) { pline("A voice booms out..."); + SetVoice(mtmp, 0, 80, 0); verbalize("So thou thought thou couldst %s me, fool.", verb); } } @@ -681,9 +782,10 @@ resurrect() /* Here, we make trouble for the poor shmuck who actually managed to do in the Wizard. */ void -intervene() +intervene(void) { int which = Is_astralevel(&u.uz) ? rnd(4) : rn2(6); + /* cases 0 and 5 don't apply on the Astral level */ switch (which) { case 0: @@ -707,17 +809,19 @@ intervene() } } +/* Wizard of Yendor is being removed from play (dead or escaped the dungeon); + keep the bookkeeping for him up to date */ void -wizdead() +wizdeadorgone(void) { - context.no_of_wizards--; + svc.context.no_of_wizards--; if (!u.uevent.udemigod) { u.uevent.udemigod = TRUE; u.udg_cnt = rn1(250, 50); } } -const char *const random_insult[] = { +static const char *const random_insult[] = { "antic", "blackguard", "caitiff", "chucklehead", "coistrel", "craven", "cretin", "cur", "dastard", "demon fodder", "dimwit", "dolt", @@ -728,7 +832,7 @@ const char *const random_insult[] = { "wittol", "worm", "wretch", }; -const char *const random_malediction[] = { +static const char *const random_malediction[] = { "Hell shall soon claim thy remains,", "I chortle at thee, thou pathetic", "Prepare to die, thou", "Resistance is useless,", "Surrender or die, thou", "There shall be no mercy, thou", @@ -739,37 +843,43 @@ const char *const random_malediction[] = { /* Insult or intimidate the player */ void -cuss(mtmp) -register struct monst *mtmp; +cuss(struct monst *mtmp) { if (Deaf) return; if (mtmp->iswiz) { - if (!rn2(5)) /* typical bad guy action */ + if (!rn2(5)) { /* typical bad guy action */ pline("%s laughs fiendishly.", Monnam(mtmp)); - else if (u.uhave.amulet && !rn2(SIZE(random_insult))) + } else if (u.uhave.amulet && !rn2(SIZE(random_insult))) { + SetVoice(mtmp, 0, 80, 0); verbalize("Relinquish the amulet, %s!", - random_insult[rn2(SIZE(random_insult))]); - else if (u.uhp < 5 && !rn2(2)) /* Panic */ + ROLL_FROM(random_insult)); + } else if (u.uhp < 5 && !rn2(2)) { /* Panic */ + SetVoice(mtmp, 0, 80, 0); verbalize(rn2(2) ? "Even now thy life force ebbs, %s!" : "Savor thy breath, %s, it be thy last!", - random_insult[rn2(SIZE(random_insult))]); - else if (mtmp->mhp < 5 && !rn2(2)) /* Parthian shot */ + ROLL_FROM(random_insult)); + } else if (mtmp->mhp < 5 && !rn2(2)) { /* Parthian shot */ + SetVoice(mtmp, 0, 80, 0); verbalize(rn2(2) ? "I shall return." : "I'll be back."); - else + } else { + SetVoice(mtmp, 0, 80, 0); verbalize("%s %s!", - random_malediction[rn2(SIZE(random_malediction))], - random_insult[rn2(SIZE(random_insult))]); + ROLL_FROM(random_malediction), + ROLL_FROM(random_insult)); + } } else if (is_lminion(mtmp) && !(mtmp->isminion && EMIN(mtmp)->renegade)) { - com_pager(rn2(QTN_ANGELIC - 1 + (Hallucination ? 1 : 0)) - + QT_ANGELIC); + com_pager("angel_cuss"); /* TODO: the Hallucination msg */ + /*com_pager(rn2(QTN_ANGELIC - 1 + (Hallucination ? 1 : 0)) + + QT_ANGELIC);*/ } else { if (!rn2(is_minion(mtmp->data) ? 100 : 5)) pline("%s casts aspersions on your ancestry.", Monnam(mtmp)); else - com_pager(rn2(QTN_DEMONIC) + QT_DEMONIC); + com_pager("demon_cuss"); } + wake_nearto(mtmp->mx, mtmp->my, 5 * 5); } /*wizard.c*/ diff --git a/src/wizcmds.c b/src/wizcmds.c new file mode 100644 index 000000000..db94384a3 --- /dev/null +++ b/src/wizcmds.c @@ -0,0 +1,2029 @@ +/* NetHack 5.0 wizcmds.c $NHDT-Date: 1736530208 2025/01/10 09:30:08 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.21 $ */ +/*-Copyright (c) Robert Patrick Rankin, 2024. */ +/* NetHack may be freely redistributed. See license for details. */ + +#include "hack.h" +#include "func_tab.h" + +extern const char unavailcmd[]; /* cmd.c [27] */ +extern const char *levltyp[MAX_TYPE + 2]; /* cmd.c */ + +staticfn int size_monst(struct monst *, boolean); +staticfn int size_obj(struct obj *); +staticfn void count_obj(struct obj *, long *, long *, boolean, boolean); +staticfn void obj_chain(winid, const char *, struct obj *, boolean, long *, + long *); +staticfn void mon_invent_chain(winid, const char *, struct monst *, long *, + long *); +staticfn void mon_chain(winid, const char *, struct monst *, boolean, long *, + long *); +staticfn void contained_stats(winid, const char *, long *, long *); +staticfn void misc_stats(winid, long *, long *); +staticfn void you_sanity_check(void); +staticfn void levl_sanity_check(void); +staticfn void makemap_unmakemon(struct monst *, boolean); +staticfn int QSORTCALLBACK migrsort_cmp(const genericptr, const genericptr); +staticfn void list_migrating_mons(d_level *); + +DISABLE_WARNING_FORMAT_NONLITERAL + +/* #wizwish command - wish for something */ +int +wiz_wish(void) /* Unlimited wishes for debug mode by Paul Polderman */ +{ + if (wizard) { + boolean save_verbose = flags.verbose; + + flags.verbose = FALSE; + makewish(); + flags.verbose = save_verbose; + encumber_msg(); + } else + pline(unavailcmd, ecname_from_fn(wiz_wish)); + return ECMD_OK; +} + +DISABLE_WARNING_FORMAT_NONLITERAL + +/* #wizidentify command - reveal and optionally identify hero's inventory */ +int +wiz_identify(void) +{ + if (wizard) { + iflags.override_ID = (int) cmd_from_func(wiz_identify); + /* command remapping might leave #wizidentify as the only way + to invoke us, in which case cmd_from_func() will yield NUL; + it won't matter to display_inventory()/display_pickinv() + if ^I invokes some other command--what matters is that + display_pickinv() and xname() see override_ID as nonzero */ + if (!iflags.override_ID) + iflags.override_ID = C('I'); + (void) display_inventory((char *) 0, FALSE); + iflags.override_ID = 0; + } else + pline(unavailcmd, ecname_from_fn(wiz_identify)); + return ECMD_OK; +} + +RESTORE_WARNING_FORMAT_NONLITERAL + +/* used when wiz_makemap() gets rid of monsters for the old incarnation of + a level before creating a new incarnation of it */ +staticfn void +makemap_unmakemon(struct monst *mtmp, boolean migratory) +{ + int ndx = monsndx(mtmp->data); + + /* uncreate any unique monster so that it is eligible to be remade + on the new incarnation of the level; ignores DEADMONSTER() [why?] */ + if (mtmp->data->geno & G_UNIQ) + svm.mvitals[ndx].mvflags &= ~G_EXTINCT; + if (svm.mvitals[ndx].born) + svm.mvitals[ndx].born--; + + /* vault is going away; get rid of guard who might be in play or + be parked at <0,0>; for the latter, might already be flagged as + dead but is being kept around because of the 'isgd' flag */ + if (mtmp->isgd) { + mtmp->isgd = 0; /* after this, fall through to mongone() */ + } else if (DEADMONSTER(mtmp)) { + return; /* already set to be discarded */ + } else if (mtmp->isshk && on_level(&u.uz, &ESHK(mtmp)->shoplevel)) { + setpaid(mtmp); + } + if (migratory) { + /* caller has removed 'mtmp' from migrating_mons; put it onto fmon + so that dmonsfree() bookkeeping for number of dead or removed + monsters won't get out of sync; it is not on the map but + mongone() -> m_detach() -> mon_leaving_level() copes with that */ + mtmp->mstate |= MON_OFFMAP; + mtmp->mstate &= ~(MON_MIGRATING | MON_LIMBO | MON_ENDGAME_MIGR); + mtmp->nmon = fmon; + fmon = mtmp; + } + mongone(mtmp); +} + +/* get rid of the all the monsters on--or intimately involved with--current + level; used when #wizmakemap destroys the level before replacing it */ +void +makemap_remove_mons(void) +{ + struct monst *mtmp, **mprev; + + /* keep steed and other adjacent pets after releasing them + from traps, stopping eating, &c as if hero were ascending */ + keepdogs(TRUE); /* (pets-only; normally we'd be using 'FALSE') */ + /* get rid of all the monsters that didn't make it to 'mydogs' */ + for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { + /* if already dead, dmonsfree(below) will get rid of it */ + if (DEADMONSTER(mtmp)) + continue; + makemap_unmakemon(mtmp, FALSE); + } + /* some monsters retain details of this level in mon->mextra; that + data becomes invalid when the level is replaced by a new one; + get rid of them now if migrating or already arrived elsewhere; + [when on their 'home' level, the previous loop got rid of them; + if they aren't actually migrating but have been placed on some + 'away' level, such monsters are treated like the Wizard: kept + on migrating monsters list, scheduled to migrate back to their + present location instead of being saved with whatever level they + happen to be on; see keepdogs() and keep_mon_accessible(dog.c)] */ + for (mprev = &gm.migrating_mons; (mtmp = *mprev) != 0; ) { + if (mtmp->mextra + && ((mtmp->isshk && on_level(&u.uz, &ESHK(mtmp)->shoplevel)) + || (mtmp->ispriest && on_level(&u.uz, &EPRI(mtmp)->shrlevel)) + || (mtmp->isgd && on_level(&u.uz, &EGD(mtmp)->gdlevel)))) { + *mprev = mtmp->nmon; + makemap_unmakemon(mtmp, TRUE); + } else { + mprev = &mtmp->nmon; + } + } + /* release dead and 'unmade' monsters */ + dmonsfree(); + if (fmon) { + impossible("makemap_remove_mons: 'fmon' did not get emptied?"); + } + return; +} + +DISABLE_WARNING_FORMAT_NONLITERAL + +/* #wizmakemap - discard current dungeon level and replace with a new one */ +int +wiz_makemap(void) +{ + if (wizard) { + boolean was_in_W_tower = In_W_tower(u.ux, u.uy, &u.uz); + + makemap_prepost(TRUE, was_in_W_tower); + /* create a new level; various things like bestowing a guardian + angel on Astral or setting off alarm on Ft.Ludios are handled + by goto_level(do.c) so won't occur for replacement levels */ + mklev(); + makemap_prepost(FALSE, was_in_W_tower); + } else { + pline(unavailcmd, ecname_from_fn(wiz_makemap)); + } + return ECMD_OK; +} + +/* the #wizmap command - reveal the level map + and any traps or engravings on it */ +int +wiz_map(void) +{ + if (wizard) { + struct trap *t; + struct engr *ep; + long save_Hconf = HConfusion, save_Hhallu = HHallucination; + + notice_mon_off(); + HConfusion = HHallucination = 0L; + for (t = gf.ftrap; t != 0; t = t->ntrap) { + t->tseen = 1; + map_trap(t, TRUE); + } + for (ep = head_engr; ep != 0; ep = ep->nxt_engr) { + map_engraving(ep, TRUE); + } + do_mapping(); + notice_mon_on(); + HConfusion = save_Hconf; + HHallucination = save_Hhallu; + } else + pline(unavailcmd, ecname_from_fn(wiz_map)); + return ECMD_OK; +} + +/* #wizgenesis - generate monster(s); a count prefix will be honored */ +int +wiz_genesis(void) +{ + if (wizard) { + boolean mongen_saved = iflags.debug_mongen; + + iflags.debug_mongen = FALSE; + (void) create_particular(); + iflags.debug_mongen = mongen_saved; + } else + pline(unavailcmd, ecname_from_fn(wiz_genesis)); + return ECMD_OK; +} + +/* #wizwhere command - display dungeon layout */ +int +wiz_where(void) +{ + if (wizard) + (void) print_dungeon(FALSE, (schar *) 0, (xint16 *) 0); + else + pline(unavailcmd, ecname_from_fn(wiz_where)); + return ECMD_OK; +} + +/* the #wizdetect command - detect secret doors, traps, hidden monsters */ +int +wiz_detect(void) +{ + if (wizard) + (void) findit(); + else + pline(unavailcmd, ecname_from_fn(wiz_detect)); + return ECMD_OK; +} + +RESTORE_WARNING_FORMAT_NONLITERAL + +/* the #wizkill command - pick targets and reduce them to 0HP; + by default, the hero is credited/blamed; use 'm' prefix to avoid that */ +int +wiz_kill(void) +{ + struct monst *mtmp; + coord cc; + int ans; + char c, qbuf[QBUFSZ]; + const char *prompt = "Pick first monster to slay"; + boolean save_verbose = flags.verbose, + save_autodescribe = iflags.autodescribe; + d_level uarehere = u.uz; + + cc.x = u.ux, cc.y = u.uy; + for (;;) { + pline("%s:", prompt); + prompt = "Next monster"; + + flags.verbose = FALSE; + iflags.autodescribe = TRUE; + ans = getpos(&cc, TRUE, "a monster"); + flags.verbose = save_verbose; + iflags.autodescribe = save_autodescribe; + if (ans < 0 || cc.x < 1) + break; + + mtmp = 0; + if (u_at(cc.x, cc.y)) { + if (u.usteed) { + Sprintf(qbuf, "Kill %.110s?", mon_nam(u.usteed)); + if ((c = ynq(qbuf)) == 'q') + break; + if (c == 'y') + mtmp = u.usteed; + } + if (!mtmp) { + Sprintf(qbuf, "%s?", Role_if(PM_SAMURAI) ? "Perform seppuku" + : "Commit suicide"); + if (paranoid_query(TRUE, qbuf)) { + Sprintf(svk.killer.name, "%s own player", uhis()); + svk.killer.format = KILLED_BY; + done(DIED); + } + break; + } + } else if (u.uswallow) { + mtmp = next2u(cc.x, cc.y) ? u.ustuck : 0; + } else { + mtmp = m_at(cc.x, cc.y); + } + + /* whether there's an unseen monster here or not, player will know + that there's no monster here after the kill or failed attempt; + let hero know too */ + (void) unmap_invisible(cc.x, cc.y); + + if (mtmp) { + /* we don't require that the monster be seen or sensed so + we issue our own message in order to name it in case it + isn't; note that if it triggers other kills, those might + be referred to as "it" */ + int tame = !!mtmp->mtame, + seen = (canspotmon(mtmp) || (u.uswallow && mtmp == u.ustuck)), + flgs = (SUPPRESS_IT | SUPPRESS_HALLUCINATION + | ((tame && has_mgivenname(mtmp)) ? SUPPRESS_SADDLE + : 0)), + articl = tame ? ARTICLE_YOUR : seen ? ARTICLE_THE : ARTICLE_A; + const char *adjs = tame ? (!seen ? "poor, unseen" : "poor") + : (!seen ? "unseen" : (const char *) 0); + char *Mn = x_monnam(mtmp, articl, adjs, flgs, FALSE); + + if (!iflags.menu_requested) { + /* normal case: hero is credited/blamed */ + You("%s %s!", nonliving(mtmp->data) ? "destroy" : "kill", Mn); + xkilled(mtmp, XKILL_NOMSG); + } else { /* 'm'-prefix */ + /* we know that monsters aren't moving because player has + just issued this #wizkill command, but if 'mtmp' is a + gas spore whose explosion kills any other monsters we + need to have the mon_moving flag be True in order to + avoid blaming or crediting hero for their deaths */ + svc.context.mon_moving = TRUE; + pline("%s is %s.", upstart(Mn), + nonliving(mtmp->data) ? "destroyed" : "killed"); + /* Null second arg suppresses the usual message */ + monkilled(mtmp, (char *) 0, AD_PHYS); + svc.context.mon_moving = FALSE; + } + /* end targetting loop if an engulfer dropped hero onto a level- + changing trap */ + if (u.utotype || !on_level(&u.uz, &uarehere)) + break; + } else { + There("is no monster there."); + break; + } + } + /* since #wizkill takes no game time, it is possible to kill something + in the main dungeon and immediately level teleport into the endgame + which will delete the main dungeon's level files; avoid triggering + impossible "dmonsfree: 0 removed doesn't match N pending" by forcing + dead monster cleanup; we don't track whether anything was actually + killed above--if nothing was, this will be benign */ + dmonsfree(); + /* distinction between ECMD_CANCEL and ECMD_OK is unimportant here */ + return ECMD_OK; /* no time elapses */ +} + +DISABLE_WARNING_FORMAT_NONLITERAL + +/* the #wizloadlua command - load an arbitrary lua file */ +int +wiz_load_lua(void) +{ + if (wizard) { + char buf[BUFSZ]; + /* Large but not unlimited memory and CPU so random bits of + * code can be tested by wizards. */ + nhl_sandbox_info sbi = {NHL_SB_SAFE | NHL_SB_DEBUGGING, + 16*1024*1024, 0, 16*1024*1024}; + + buf[0] = '\0'; + getlin("Load which lua file?", buf); + if (buf[0] == '\033' || buf[0] == '\0') + return ECMD_CANCEL; + if (!strchr(buf, '.')) + strcat(buf, ".lua"); + (void) load_lua(buf, &sbi); + } else + pline(unavailcmd, ecname_from_fn(wiz_load_lua)); + return ECMD_OK; +} + +/* the #wizloaddes command - load a special level lua file */ +int +wiz_load_splua(void) +{ + if (wizard) { + char buf[BUFSZ]; + + buf[0] = '\0'; + getlin("Load which des lua file?", buf); + if (buf[0] == '\033' || buf[0] == '\0') + return ECMD_CANCEL; + if (!strchr(buf, '.')) + strcat(buf, ".lua"); + + lspo_reset_level(NULL); + (void) load_special(buf); + lspo_finalize_level(NULL); + + } else + pline(unavailcmd, ecname_from_fn(wiz_load_splua)); + return ECMD_OK; +} + +/* the #wizlevelport command - level teleport */ +int +wiz_level_tele(void) +{ + if (wizard) + level_tele(); + else + pline(unavailcmd, ecname_from_fn(wiz_level_tele)); + return ECMD_OK; +} + +RESTORE_WARNING_FORMAT_NONLITERAL + +/* #wizfliplevel - transpose the current level */ +int +wiz_flip_level(void) +{ + static const char choices[] = "0123", + prmpt[] = "Flip 0=randomly, 1=vertically, 2=horizontally, 3=both:"; + + /* + * Does not handle + * levregions, + * monster mtrack, + * migrating monsters aimed at returning to specific coordinates + * on this level + * as flipping is normally done only during level creation. + */ + if (wizard) { + char c = yn_function(prmpt, choices, '\0', TRUE); + + if (c && strchr(choices, c)) { + c -= '0'; + + if (!c) + flip_level_rnd(3, TRUE); + else + flip_level((int) c, TRUE); + + docrt(); + } else { + pline("%s", Never_mind); + } + } + return ECMD_OK; +} + +/* #levelchange command - adjust hero's experience level */ +int +wiz_level_change(void) +{ + char buf[BUFSZ], dummy = '\0'; + int newlevel = 0; + int ret; + + buf[0] = '\0'; /* in case EDIT_GETLIN is enabled */ + getlin("To what experience level do you want to be set?", buf); + (void) mungspaces(buf); + if (buf[0] == '\033' || buf[0] == '\0') + ret = 0; + else + ret = sscanf(buf, "%d%c", &newlevel, &dummy); + + if (ret != 1) { + pline1(Never_mind); + return ECMD_OK; + } + if (newlevel == u.ulevel) { + You("are already that experienced."); + } else if (newlevel < u.ulevel) { + if (u.ulevel == 1) { + You("are already as inexperienced as you can get."); + return ECMD_OK; + } + if (newlevel < 1) + newlevel = 1; + while (u.ulevel > newlevel) + losexp("#levelchange"); + } else { + if (u.ulevel >= MAXULEV) { + You("are already as experienced as you can get."); + return ECMD_OK; + } + if (newlevel > MAXULEV) + newlevel = MAXULEV; + while (u.ulevel < newlevel) + pluslvl(FALSE); + } + /* blessed full healing or restore ability won't fix any lost levels */ + u.ulevelmax = u.ulevel; + return ECMD_OK; +} + +DISABLE_WARNING_CONDEXPR_IS_CONSTANT + +/* #wiztelekinesis */ +int +wiz_telekinesis(void) +{ + int ans = 0; + coord cc; + struct monst *mtmp = (struct monst *) 0; + + cc.x = u.ux; + cc.y = u.uy; + + pline("Pick a monster to hurtle."); + do { + ans = getpos(&cc, TRUE, "a monster"); + if (ans < 0 || cc.x < 1) + return ECMD_CANCEL; + + if ((((mtmp = m_at(cc.x, cc.y)) != 0) && canspotmon(mtmp)) + || u_at(cc.x, cc.y)) { + if (!getdir("which direction?")) + return ECMD_CANCEL; + + if (mtmp) { + mhurtle(mtmp, u.dx, u.dy, 6); + if (!DEADMONSTER(mtmp) && canspotmon(mtmp)) { + cc.x = mtmp->mx; + cc.y = mtmp->my; + } + } else { + hurtle(u.dx, u.dy, 6, FALSE); + cc.x = u.ux, cc.y = u.uy; + } + } + + } while (u.utotype == UTOTYPE_NONE); + return ECMD_OK; +} + +RESTORE_WARNING_CONDEXPR_IS_CONSTANT + +/* #panic command - test program's panic handling */ +int +wiz_panic(void) +{ + if (iflags.debug_fuzzer) { + u.uhp = u.uhpmax = 1000; + u.uen = u.uenmax = 1000; + return ECMD_OK; + } + if (paranoid_query(TRUE, + "Do you want to call panic() and end your game?")) + panic("Crash test (#panic)."); + return ECMD_OK; +} + +/* #debugfuzzer command - fuzztest the program */ +int +wiz_fuzzer(void) +{ + if (flags.suppress_alert < FEATURE_NOTICE_VER(3,7,0)) { + pline("The fuzz tester will make NetHack execute random keypresses."); + There("is no conventional way out of this mode."); + } + if (paranoid_query(TRUE, "Do you want to start fuzz testing?")) { + /* Thoth, take the reins */ + if (y_n("Do you want to call panic() after impossible()?") == 'n') { + iflags.debug_fuzzer = fuzzer_impossible_continue; + } else { + iflags.debug_fuzzer = fuzzer_impossible_panic; + } + } + return ECMD_OK; +} + +/* #polyself command - change hero's form */ +int +wiz_polyself(void) +{ + polyself(POLY_CONTROLLED); + return ECMD_OK; +} + +/* #seenv command */ +int +wiz_show_seenv(void) +{ + winid win; + coordxy x, y, startx, stopx, curx; + int v; + char row[COLNO + 1]; + + win = create_nhwindow(NHW_TEXT); + /* + * Each seenv description takes up 2 characters, so center + * the seenv display around the hero. + */ + startx = max(1, u.ux - (COLNO / 4)); + stopx = min(startx + (COLNO / 2), COLNO); + /* can't have a line exactly 80 chars long */ + if (stopx - startx == COLNO / 2) + startx++; + + for (y = 0; y < ROWNO; y++) { + for (x = startx, curx = 0; x < stopx; x++, curx += 2) { + if (u_at(x, y)) { + row[curx] = row[curx + 1] = '@'; + } else { + v = levl[x][y].seenv & 0xff; + if (v == 0) + row[curx] = row[curx + 1] = ' '; + else + Sprintf(&row[curx], "%02x", v); + } + } + /* remove trailing spaces */ + for (x = curx - 1; x >= 0; x--) + if (row[x] != ' ') + break; + row[x + 1] = '\0'; + + putstr(win, 0, row); + } + display_nhwindow(win, TRUE); + destroy_nhwindow(win); + return ECMD_OK; +} + +/* #vision command */ +int +wiz_show_vision(void) +{ + winid win; + coordxy x, y; + int v; + char row[COLNO + 1]; + + win = create_nhwindow(NHW_TEXT); + Sprintf(row, "Flags: 0x%x could see, 0x%x in sight, 0x%x temp lit", + COULD_SEE, IN_SIGHT, TEMP_LIT); + putstr(win, 0, row); + putstr(win, 0, ""); + for (y = 0; y < ROWNO; y++) { + for (x = 1; x < COLNO; x++) { + if (u_at(x, y)) { + row[x] = '@'; + } else { + v = gv.viz_array[y][x]; /* data access should be hidden */ + row[x] = (v == 0) ? ' ' : ('0' + v); + } + } + /* remove trailing spaces */ + for (x = COLNO - 1; x >= 1; x--) + if (row[x] != ' ') + break; + row[x + 1] = '\0'; + + putstr(win, 0, &row[1]); + } + display_nhwindow(win, TRUE); + destroy_nhwindow(win); + return ECMD_OK; +} + +/* #wmode command */ +int +wiz_show_wmodes(void) +{ + winid win; + coordxy x, y; + char row[COLNO + 1]; + struct rm *lev; + boolean istty = WINDOWPORT(tty); + + win = create_nhwindow(NHW_TEXT); + if (istty) + putstr(win, 0, ""); /* tty only: blank top line */ + for (y = 0; y < ROWNO; y++) { + for (x = 0; x < COLNO; x++) { + lev = &levl[x][y]; + if (u_at(x, y)) + row[x] = '@'; + else if (IS_WALL(lev->typ) || lev->typ == SDOOR) + row[x] = '0' + (lev->wall_info & WM_MASK); + else if (lev->typ == CORR) + row[x] = '#'; + else if (IS_ROOM(lev->typ) || IS_DOOR(lev->typ)) + row[x] = '.'; + else + row[x] = 'x'; + } + row[COLNO] = '\0'; + /* map column 0, levl[0][], is off the left edge of the screen */ + putstr(win, 0, &row[1]); + } + display_nhwindow(win, TRUE); + destroy_nhwindow(win); + return ECMD_OK; +} + +/* wizard mode variant of #terrain; internal levl[][].typ values in base-36 */ +void +wiz_map_levltyp(void) +{ + winid win; + coordxy x, y; + int terrain; + char row[COLNO + 1]; + boolean istty = !strcmp(windowprocs.name, "tty"); + + win = create_nhwindow(NHW_TEXT); + /* map row 0, levl[][0], is drawn on the second line of tty screen */ + if (istty) + putstr(win, 0, ""); /* tty only: blank top line */ + for (y = 0; y < ROWNO; y++) { + /* map column 0, levl[0][], is off the left edge of the screen; + it should always have terrain type "undiggable stone" */ + for (x = 1; x < COLNO; x++) { + terrain = levl[x][y].typ; + /* assumes there aren't more than 10+26+26 terrain types */ + row[x - 1] = (char) ((terrain == STONE && !may_dig(x, y)) + ? '*' + : (terrain < 10) + ? '0' + terrain + : (terrain < 36) + ? 'a' + terrain - 10 + : 'A' + terrain - 36); + } + x--; + if (levl[0][y].typ != STONE || may_dig(0, y)) + row[x++] = '!'; + row[x] = '\0'; + putstr(win, 0, row); + } + + { + char dsc[COLBUFSZ]; + s_level *slev = Is_special(&u.uz); + + Sprintf(dsc, "D:%d,L:%d", u.uz.dnum, u.uz.dlevel); + /* [dungeon branch features currently omitted] */ + /* special level features */ + if (slev) { + Sprintf(eos(dsc), " \"%s\"", slev->proto); + /* special level flags (note: dungeon.def doesn't set `maze' + or `hell' for any specific levels so those never show up) */ + if (slev->flags.maze_like) + Strcat(dsc, " mazelike"); + if (slev->flags.hellish) + Strcat(dsc, " hellish"); + if (slev->flags.town) + Strcat(dsc, " town"); + if (slev->flags.rogue_like) + Strcat(dsc, " roguelike"); + /* alignment currently omitted to save space */ + } + /* level features */ + if (svl.level.flags.nfountains) + Sprintf(eos(dsc), " %c:%d", defsyms[S_fountain].sym, + (int) svl.level.flags.nfountains); + if (svl.level.flags.nsinks) + Sprintf(eos(dsc), " %c:%d", defsyms[S_sink].sym, + (int) svl.level.flags.nsinks); + if (svl.level.flags.has_vault) + Strcat(dsc, " vault"); + if (svl.level.flags.has_shop) + Strcat(dsc, " shop"); + if (svl.level.flags.has_temple) + Strcat(dsc, " temple"); + if (svl.level.flags.has_court) + Strcat(dsc, " throne"); + if (svl.level.flags.has_zoo) + Strcat(dsc, " zoo"); + if (svl.level.flags.has_morgue) + Strcat(dsc, " morgue"); + if (svl.level.flags.has_barracks) + Strcat(dsc, " barracks"); + if (svl.level.flags.has_beehive) + Strcat(dsc, " hive"); + if (svl.level.flags.has_swamp) + Strcat(dsc, " swamp"); + /* level flags */ + if (svl.level.flags.noteleport) + Strcat(dsc, " noTport"); + if (svl.level.flags.hardfloor) + Strcat(dsc, " noDig"); + if (svl.level.flags.nommap) + Strcat(dsc, " noMMap"); + if (!svl.level.flags.hero_memory) + Strcat(dsc, " noMem"); + if (svl.level.flags.shortsighted) + Strcat(dsc, " shortsight"); + if (svl.level.flags.graveyard) + Strcat(dsc, " graveyard"); + if (svl.level.flags.is_maze_lev) + Strcat(dsc, " maze"); + if (svl.level.flags.is_cavernous_lev) + Strcat(dsc, " cave"); + if (svl.level.flags.arboreal) + Strcat(dsc, " tree"); + if (Sokoban) + Strcat(dsc, " sokoban-rules"); + /* non-flag info; probably should include dungeon branching + checks (extra stairs and magic portals) here */ + if (Invocation_lev(&u.uz)) + Strcat(dsc, " invoke"); + if (On_W_tower_level(&u.uz)) + Strcat(dsc, " tower"); + /* append a branch identifier for completeness' sake */ + if (u.uz.dnum == 0) + Strcat(dsc, " dungeon"); + else if (u.uz.dnum == mines_dnum) + Strcat(dsc, " mines"); + else if (In_sokoban(&u.uz)) + Strcat(dsc, " sokoban"); + else if (u.uz.dnum == quest_dnum) + Strcat(dsc, " quest"); + else if (Is_knox(&u.uz)) + Strcat(dsc, " ludios"); + else if (u.uz.dnum == 1) + Strcat(dsc, " gehennom"); + else if (u.uz.dnum == tower_dnum) + Strcat(dsc, " vlad"); + else if (In_endgame(&u.uz)) + Strcat(dsc, " endgame"); + else { + /* somebody's added a dungeon branch we're not expecting */ + const char *brname = svd.dungeons[u.uz.dnum].dname; + + if (!brname || !*brname) + brname = "unknown"; + if (!strncmpi(brname, "the ", 4)) + brname += 4; + Sprintf(eos(dsc), " %s", brname); + } + /* limit the line length to map width */ + if (strlen(dsc) >= COLNO) + dsc[COLNO - 1] = '\0'; /* truncate */ + putstr(win, 0, dsc); + } + + display_nhwindow(win, TRUE); + destroy_nhwindow(win); + return; +} + +DISABLE_WARNING_FORMAT_NONLITERAL + +/* explanation of base-36 output from wiz_map_levltyp() */ +void +wiz_levltyp_legend(void) +{ + winid win; + int i, j, last, c; + const char *dsc, *fmt; + char buf[BUFSZ]; + + win = create_nhwindow(NHW_TEXT); + putstr(win, 0, "#terrain encodings:"); + putstr(win, 0, ""); + fmt = " %c - %-28s"; /* TODO: include tab-separated variant for win32 */ + *buf = '\0'; + /* output in pairs, left hand column holds [0],[1],...,[N/2-1] + and right hand column holds [N/2],[N/2+1],...,[N-1]; + N ('last') will always be even, and may or may not include + the empty string entry to pad out the final pair, depending + upon how many other entries are present in levltyp[] */ + last = SIZE(levltyp) & ~1; + for (i = 0; i < last / 2; ++i) + for (j = i; j < last; j += last / 2) { + dsc = levltyp[j]; + c = !*dsc ? ' ' + : !strncmp(dsc, "unreachable", 11) ? '*' + /* same int-to-char conversion as wiz_map_levltyp() */ + : (j < 10) ? '0' + j + : (j < 36) ? 'a' + j - 10 + : 'A' + j - 36; + Sprintf(eos(buf), fmt, c, dsc); + if (j > i) { + putstr(win, 0, buf); + *buf = '\0'; + } + } + display_nhwindow(win, TRUE); + destroy_nhwindow(win); + return; +} + +RESTORE_WARNING_FORMAT_NONLITERAL + +DISABLE_WARNING_CONDEXPR_IS_CONSTANT + +/* #wizsmell command - test usmellmon(). */ +int +wiz_smell(void) +{ + struct monst *mtmp; /* monster being smelled */ + struct permonst *mptr; + int ans, glyph; + coord cc; /* screen pos to sniff */ + boolean is_you; + + cc.x = u.ux; + cc.y = u.uy; + if (!olfaction(gy.youmonst.data)) { + You("are incapable of detecting odors in your present form."); + return ECMD_OK; + } + + You("can move the cursor to a monster that you want to smell."); + do { + pline("Pick a monster to smell."); + ans = getpos(&cc, TRUE, "a monster"); + if (ans < 0 || cc.x < 0) { + return ECMD_CANCEL; /* done */ + } + is_you = FALSE; + if (u_at(cc.x, cc.y)) { + if (u.usteed) { + mptr = u.usteed->data; + } else { + mptr = gy.youmonst.data; + is_you = TRUE; + } + } else if ((mtmp = m_at(cc.x, cc.y)) != (struct monst *) 0) { + mptr = mtmp->data; + } else { + mptr = (struct permonst *) 0; + } + /* Buglet: mapping or unmapping "remembered, unseen monster" should + cause time to elapse; since we're in wizmode, don't bother */ + glyph = glyph_at(cc.x, cc.y); + /* Is it a monster? */ + if (mptr) { + if (is_you) + You("surreptitiously sniff under your %s.", body_part(ARM)); + if (!usmellmon(mptr)) + pline("%s to not give off any smell.", + is_you ? "You seem" : "That monster seems"); + if (!glyph_is_monster(glyph)) + map_invisible(cc.x, cc.y); + } else { + You("don't smell any monster there."); + if (glyph_is_invisible(glyph)) + unmap_invisible(cc.x, cc.y); + } + } while (TRUE); + return ECMD_OK; +} + +RESTORE_WARNING_CONDEXPR_IS_CONSTANT + +DISABLE_WARNING_FORMAT_NONLITERAL + +#define DEFAULT_TIMEOUT_INCR 30 + +/* #wizinstrinsic command to set some intrinsics for testing */ +int +wiz_intrinsic(void) +{ + if (wizard) { + static const char wizintrinsic[] = "#wizintrinsic"; + static const char fmt[] = "You are%s %s."; + winid win; + anything any; + char buf[BUFSZ]; + int i, j, n, amt, typ, p = 0; + long oldtimeout, newtimeout; + const char *propname; + menu_item *pick_list = (menu_item *) 0; + int clr = NO_COLOR; + + any = cg.zeroany; + win = create_nhwindow(NHW_MENU); + start_menu(win, MENU_BEHAVE_STANDARD); + if (iflags.cmdassist) { + /* start menu with a subtitle */ + Sprintf(buf, + "[Precede any selection with a count to increment by other than %d.]", + DEFAULT_TIMEOUT_INCR); + add_menu_str(win, buf); + } + for (i = 0; (propname = property_by_index(i, &p)) != 0; ++i) { + if (p == HALLUC_RES) { + /* Grayswandir vs hallucination; ought to be redone to + use u.uprops[HALLUC].blocked instead of being treated + as a separate property; letting in be manually toggled + even only in wizard mode would be asking for trouble... */ + continue; + } + if (p == FIRE_RES) { + /* FIRE_RES and properties beyond it (in the propertynames[] + ordering, not their numerical PROP values), can only be + set to timed values here so show a separator */ + add_menu_str(win, "--"); + } + any.a_int = i + 1; /* +1: avoid 0 */ + oldtimeout = u.uprops[p].intrinsic & TIMEOUT; + if (oldtimeout) + Sprintf(buf, "%-27s [%li]", propname, oldtimeout); + else + Sprintf(buf, "%s", propname); + add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr, buf, + MENU_ITEMFLAGS_NONE); + } + end_menu(win, "Which intrinsics?"); + n = select_menu(win, PICK_ANY, &pick_list); + destroy_nhwindow(win); + + for (j = 0; j < n; ++j) { + i = pick_list[j].item.a_int - 1; /* -1: reverse +1 above */ + propname = property_by_index(i, &p); + oldtimeout = u.uprops[p].intrinsic & TIMEOUT; + amt = (pick_list[j].count == -1L) ? DEFAULT_TIMEOUT_INCR + : (int) pick_list[j].count; + if (amt <= 0) /* paranoia */ + continue; + newtimeout = oldtimeout + (long) amt; + + switch (p) { + case SICK: + case SLIMED: + case STONED: + if (oldtimeout > 0L && newtimeout > oldtimeout) + newtimeout = oldtimeout; + break; + } + + switch (p) { + case BLINDED: + make_blinded(newtimeout, TRUE); + break; +#if 0 /* make_confused() only gives feedback when confusion is + * ending so use the 'default' case for it instead */ + case CONFUSION: + make_confused(newtimeout, TRUE); + break; +#endif /*0*/ + case DEAF: + make_deaf(newtimeout, TRUE); + break; + case HALLUC: + make_hallucinated(newtimeout, TRUE, 0L); + break; + case SICK: + typ = !rn2(2) ? SICK_VOMITABLE : SICK_NONVOMITABLE; + make_sick(newtimeout, wizintrinsic, TRUE, typ); + break; + case SLIMED: + Sprintf(buf, fmt, + !Slimed ? "" : " still", "turning into slime"); + make_slimed(newtimeout, buf); + break; + case STONED: + Sprintf(buf, fmt, + !Stoned ? "" : " still", "turning into stone"); + make_stoned(newtimeout, buf, KILLED_BY, wizintrinsic); + break; + case STUNNED: + make_stunned(newtimeout, TRUE); + break; + case VOMITING: + Sprintf(buf, fmt, !Vomiting ? "" : " still", "vomiting"); + make_vomiting(newtimeout, FALSE); + pline1(buf); + break; + case WARN_OF_MON: + if (!Warn_of_mon) { + svc.context.warntype.speciesidx = PM_GRID_BUG; + svc.context.warntype.species + = &mons[svc.context.warntype.speciesidx]; + } + goto def_feedback; + case GLIB: + /* slippery fingers might need a persistent inventory update + so needs more than simple incr_itimeout() but we want + the pline() issued with that */ + make_glib((int) newtimeout); + FALLTHROUGH; + /*FALLTHRU*/ + default: + def_feedback: + if (p != GLIB) + incr_itimeout(&u.uprops[p].intrinsic, amt); + disp.botl = TRUE; /* have pline() do a status update */ + pline("Timeout for %s %s %d.", propname, + oldtimeout ? "increased by" : "set to", amt); + break; + } + /* this has to be after incr_itimeout() */ + if (p == LEVITATION || p == FLYING) + float_vs_flight(); + else if (p == PROT_FROM_SHAPE_CHANGERS) + rescham(); + if (p == WWALKING || p == LEVITATION || p == FLYING) { + if (u.uinwater) + (void) pooleffects(FALSE); + } + } + if (n >= 1) + free((genericptr_t) pick_list); + docrt(); + } else + pline(unavailcmd, ecname_from_fn(wiz_intrinsic)); + return ECMD_OK; +} + +RESTORE_WARNING_FORMAT_NONLITERAL + +/* #wizrumorcheck command - verify each rumor access */ +int +wiz_rumor_check(void) +{ + rumor_check(); + return ECMD_OK; +} + +/* + * wizard mode sanity_check code + */ + +static const char template[] = "%-27s %4ld %6ld"; +static const char stats_hdr[] = " count bytes"; +static const char stats_sep[] = "--------------------------- ----- -------"; + +staticfn int +size_obj(struct obj *otmp) +{ + int sz = (int) sizeof (struct obj); + + if (otmp->oextra) { + sz += (int) sizeof (struct oextra); + if (ONAME(otmp)) + sz += (int) strlen(ONAME(otmp)) + 1; + if (OMONST(otmp)) + sz += size_monst(OMONST(otmp), FALSE); + if (OMAILCMD(otmp)) + sz += (int) strlen(OMAILCMD(otmp)) + 1; + /* sz += (int) sizeof (unsigned); -- now part of oextra itself */ + } + return sz; +} + +staticfn void +count_obj(struct obj *chain, long *total_count, long *total_size, + boolean top, boolean recurse) +{ + long count, size; + struct obj *obj; + + for (count = size = 0, obj = chain; obj; obj = obj->nobj) { + if (top) { + count++; + size += size_obj(obj); + } + if (recurse && obj->cobj) + count_obj(obj->cobj, total_count, total_size, TRUE, TRUE); + } + *total_count += count; + *total_size += size; +} + +DISABLE_WARNING_FORMAT_NONLITERAL /* RESTORE_WARNING follows wiz_show_stats */ + +staticfn void +obj_chain( + winid win, + const char *src, + struct obj *chain, + boolean force, + long *total_count, long *total_size) +{ + char buf[BUFSZ]; + long count = 0L, size = 0L; + + count_obj(chain, &count, &size, TRUE, FALSE); + + if (count || size || force) { + *total_count += count; + *total_size += size; + Sprintf(buf, template, src, count, size); + putstr(win, 0, buf); + } +} + +staticfn void +mon_invent_chain( + winid win, + const char *src, + struct monst *chain, + long *total_count, long *total_size) +{ + char buf[BUFSZ]; + long count = 0, size = 0; + struct monst *mon; + + for (mon = chain; mon; mon = mon->nmon) + count_obj(mon->minvent, &count, &size, TRUE, FALSE); + + if (count || size) { + *total_count += count; + *total_size += size; + Sprintf(buf, template, src, count, size); + putstr(win, 0, buf); + } +} + +staticfn void +contained_stats( + winid win, + const char *src, + long *total_count, long *total_size) +{ + char buf[BUFSZ]; + long count = 0, size = 0; + struct monst *mon; + + count_obj(gi.invent, &count, &size, FALSE, TRUE); + count_obj(fobj, &count, &size, FALSE, TRUE); + count_obj(svl.level.buriedobjlist, &count, &size, FALSE, TRUE); + count_obj(gm.migrating_objs, &count, &size, FALSE, TRUE); + /* DEADMONSTER check not required in this loop since they have no + * inventory */ + for (mon = fmon; mon; mon = mon->nmon) + count_obj(mon->minvent, &count, &size, FALSE, TRUE); + for (mon = gm.migrating_mons; mon; mon = mon->nmon) + count_obj(mon->minvent, &count, &size, FALSE, TRUE); + + if (count || size) { + *total_count += count; + *total_size += size; + Sprintf(buf, template, src, count, size); + putstr(win, 0, buf); + } +} + +staticfn int +size_monst(struct monst *mtmp, boolean incl_wsegs) +{ + int sz = (int) sizeof (struct monst); + + if (mtmp->wormno && incl_wsegs) + sz += size_wseg(mtmp); + + if (mtmp->mextra) { + sz += (int) sizeof (struct mextra); + if (MGIVENNAME(mtmp)) + sz += (int) strlen(MGIVENNAME(mtmp)) + 1; + if (EGD(mtmp)) + sz += (int) sizeof (struct egd); + if (EPRI(mtmp)) + sz += (int) sizeof (struct epri); + if (ESHK(mtmp)) + sz += (int) sizeof (struct eshk); + if (EMIN(mtmp)) + sz += (int) sizeof (struct emin); + if (EDOG(mtmp)) + sz += (int) sizeof (struct edog); + if (EBONES(mtmp)) + sz += (int) sizeof (struct ebones); + /* mextra->mcorpsenm doesn't point to more memory */ + } + return sz; +} + +staticfn void +mon_chain( + winid win, + const char *src, + struct monst *chain, + boolean force, + long *total_count, long *total_size) +{ + char buf[BUFSZ]; + long count, size; + struct monst *mon; + /* mon->wormno means something different for migrating_mons and mydogs */ + boolean incl_wsegs = !strcmpi(src, "fmon"); + + count = size = 0L; + for (mon = chain; mon; mon = mon->nmon) { + count++; + size += size_monst(mon, incl_wsegs); + } + if (count || size || force) { + *total_count += count; + *total_size += size; + Sprintf(buf, template, src, count, size); + putstr(win, 0, buf); + } +} + +staticfn void +misc_stats( + winid win, + long *total_count, long *total_size) +{ + char buf[BUFSZ], hdrbuf[QBUFSZ]; + long count, size; + int idx; + struct trap *tt; + struct damage *sd; /* shop damage */ + struct kinfo *k; /* delayed killer */ + struct cemetery *bi; /* bones info */ + + /* traps and engravings are output unconditionally; + * others only if nonzero + */ + count = size = 0L; + for (tt = gf.ftrap; tt; tt = tt->ntrap) { + ++count; + size += (long) sizeof *tt; + } + *total_count += count; + *total_size += size; + Sprintf(hdrbuf, "traps, size %ld", (long) sizeof (struct trap)); + Sprintf(buf, template, hdrbuf, count, size); + putstr(win, 0, buf); + + count = size = 0L; + engr_stats("engravings, size %ld+text", hdrbuf, &count, &size); + *total_count += count; + *total_size += size; + Sprintf(buf, template, hdrbuf, count, size); + putstr(win, 0, buf); + + count = size = 0L; + light_stats("light sources, size %ld", hdrbuf, &count, &size); + if (count || size) { + *total_count += count; + *total_size += size; + Sprintf(buf, template, hdrbuf, count, size); + putstr(win, 0, buf); + } + + count = size = 0L; + timer_stats("timers, size %ld", hdrbuf, &count, &size); + if (count || size) { + *total_count += count; + *total_size += size; + Sprintf(buf, template, hdrbuf, count, size); + putstr(win, 0, buf); + } + + count = size = 0L; + for (sd = svl.level.damagelist; sd; sd = sd->next) { + ++count; + size += (long) sizeof *sd; + } + if (count || size) { + *total_count += count; + *total_size += size; + Sprintf(hdrbuf, "shop damage, size %ld", + (long) sizeof (struct damage)); + Sprintf(buf, template, hdrbuf, count, size); + putstr(win, 0, buf); + } + + count = size = 0L; + region_stats("regions, size %ld+%ld*rect+N", hdrbuf, &count, &size); + if (count || size) { + *total_count += count; + *total_size += size; + Sprintf(buf, template, hdrbuf, count, size); + putstr(win, 0, buf); + } + + count = size = 0L; + for (k = svk.killer.next; k; k = k->next) { + ++count; + size += (long) sizeof *k; + } + if (count || size) { + *total_count += count; + *total_size += size; + Sprintf(hdrbuf, "delayed killer%s, size %ld", + plur(count), (long) sizeof (struct kinfo)); + Sprintf(buf, template, hdrbuf, count, size); + putstr(win, 0, buf); + } + + count = size = 0L; + for (bi = svl.level.bonesinfo; bi; bi = bi->next) { + ++count; + size += (long) sizeof *bi; + } + if (count || size) { + *total_count += count; + *total_size += size; + Sprintf(hdrbuf, "bones history, size %ld", + (long) sizeof (struct cemetery)); + Sprintf(buf, template, hdrbuf, count, size); + putstr(win, 0, buf); + } + + count = size = 0L; + for (idx = 0; idx < NUM_OBJECTS; ++idx) + if (objects[idx].oc_uname) { + ++count; + size += (long) (strlen(objects[idx].oc_uname) + 1); + } + if (count || size) { + *total_count += count; + *total_size += size; + Strcpy(hdrbuf, "object type names, text"); + Sprintf(buf, template, hdrbuf, count, size); + putstr(win, 0, buf); + } +} + +staticfn void +you_sanity_check(void) +{ + struct monst *mtmp; + + if (u.uswallow && !u.ustuck) { + /* this probably ought to be panic() */ + impossible("sanity_check: swallowed by nothing?"); + display_nhwindow(WIN_MESSAGE, TRUE); + /* try to recover from whatever the problem is */ + u.uswallow = 0; + u.uswldtim = 0; + docrt(); + } + if ((mtmp = m_at(u.ux, u.uy)) != 0) { + /* u.usteed isn't on the map */ + if (u.ustuck != mtmp) + impossible("sanity_check: you over monster"); + } + /* [should we also check for (u.uhp < 1), (Upolyd && u.mh < 1), + and (u.uen < 0) here?] */ + if (u.uhp > u.uhpmax) { + impossible("current hero health (%d) better than maximum? (%d)", + u.uhp, u.uhpmax); + u.uhp = u.uhpmax; + } + if (Upolyd && u.mh > u.mhmax) { + impossible( + "current hero health as monster (%d) better than maximum? (%d)", + u.mh, u.mhmax); + u.mh = u.mhmax; + } + if (u.uen > u.uenmax) { + impossible("current hero energy (%d) better than maximum? (%d)", + u.uen, u.uenmax); + u.uen = u.uenmax; + } + + check_wornmask_slots(); + (void) check_invent_gold("invent"); +} + +staticfn void +levl_sanity_check(void) +{ + coordxy x, y; + + if (Underwater) + return; /* Underwater uses different vision */ + + for (y = 0; y < ROWNO; y++) { + for (x = 1; x < COLNO; x++) { + if ((does_block(x, y, &levl[x][y]) ? 1 : 0) != get_viz_clear(x, y)) + impossible("levl[%i][%i] vision blocking", x, y); + } + } +} + +void +sanity_check(void) +{ + if (iflags.sanity_no_check) { + /* in case a recurring sanity_check warning occurs, we mustn't + re-trigger it when ^P is used, otherwise msg_window:Single + and msg_window:Combination will always repeat the most recent + instance, never able to go back to any earlier messages */ + iflags.sanity_no_check = FALSE; + return; + } + program_state.in_sanity_check++; + you_sanity_check(); + obj_sanity_check(); + timer_sanity_check(); + mon_sanity_check(); + light_sources_sanity_check(); + bc_sanity_check(); + trap_sanity_check(); + engraving_sanity_check(); + levl_sanity_check(); + program_state.in_sanity_check--; +} + +/* qsort() comparison routine for use in list_migrating_mons() */ +staticfn int QSORTCALLBACK +migrsort_cmp(const genericptr vptr1, const genericptr vptr2) +{ + const struct monst *m1 = *(const struct monst **) vptr1, + *m2 = *(const struct monst **) vptr2; + int d1 = (int) m1->mux, l1 = (int) m1->muy, + d2 = (int) m2->mux, l2 = (int) m2->muy; + + /* if different branches, sort by dungeon number */ + if (d1 != d2) + return d1 - d2; + /* within same branch, sort by level number */ + if (l1 != l2) + return l1 - l2; + /* same destination level: use a tie-breaker to force stable sort; + monst->m_id is unsigned so we need more than just simple subtraction */ + return (m1->m_id < m2->m_id) ? -1 : (m1->m_id > m2->m_id); +} + +/* called by #migratemons; displays count of migrating monsters, optionally + displays them as well */ +staticfn void +list_migrating_mons( + d_level *nextlevl) /* default destination for wiz_migrate_mons() */ +{ + winid win = WIN_ERR; + boolean showit = FALSE; + unsigned n; + int xyloc; + coordxy x, y; + char c, prmpt[10], xtra[10], buf[BUFSZ]; + struct monst *mtmp, **marray; + int here = 0, nxtlv = 0, other = 0; + + for (mtmp = gm.migrating_mons; mtmp; mtmp = mtmp->nmon) { + if (mtmp->mux == u.uz.dnum && mtmp->muy == u.uz.dlevel) + ++here; + else if (mtmp->mux == nextlevl->dnum && mtmp->muy == nextlevl->dlevel) + ++nxtlv; + else + ++other; + } + if (here + nxtlv + other == 0) { + pline("No monsters currently migrating."); + } else { + pline( + "%d mon%s pending for current level, %d for next level, %d for others.", + here, plur(here), nxtlv, other); + prmpt[0] = xtra[0] = '\0'; + (void) strkitten(here ? prmpt : xtra, 'c'); + (void) strkitten(nxtlv ? prmpt : xtra, 'n'); + (void) strkitten(other ? prmpt : xtra, 'o'); + Strcat(prmpt, "a q"); + if (*xtra) + Sprintf(eos(prmpt), "%c%s", '\033', xtra); + c = yn_function("List which?", prmpt, 'q', TRUE); + n = (c == 'c') ? here + : (c == 'n') ? nxtlv + : (c == 'o') ? other + : (c == 'a') ? here + nxtlv + other + : 0; + if (n > 0) { + win = create_nhwindow(NHW_TEXT); + switch (c) { + case 'c': + case 'n': + case 'o': + Sprintf(buf, "Monster%s migrating to %s:", plur(n), + (c == 'c') ? "current level" + : (c == 'n') ? "next level" + : "'other' levels"); + break; + default: + Strcpy(buf, "All migrating monsters:"); + break; + } + putstr(win, 0, buf); + putstr(win, 0, ""); + /* collect the migrating monsters into an array; for 'o' and 'a' + where multiple destination levels might be present, sort by + the destination; 'c' and 'n' don't need to be sorted but we + do that anyway to get the same tie-breaker as 'o' and 'a' */ + marray = (struct monst **) alloc((n + 1) * sizeof *marray); + n = 0; + for (mtmp = gm.migrating_mons; mtmp; mtmp = mtmp->nmon) { + if (c == 'a') + showit = TRUE; + else if (mtmp->mux == u.uz.dnum && mtmp->muy == u.uz.dlevel) + showit = (c == 'c'); + else if (mtmp->mux == nextlevl->dnum + && mtmp->muy == nextlevl->dlevel) + showit = (c == 'n'); + else + showit = (c == 'o'); + + if (showit) + marray[n++] = mtmp; + } + marray[n] = (struct monst *) 0; /* mark end for traversal loop */ + if (n > 1) + qsort((genericptr_t) marray, (size_t) n, sizeof *marray, + migrsort_cmp); /* sort elements [0] through [n-1] */ + for (n = 0; (mtmp = marray[n]) != 0; ++n) { + Sprintf(buf, " %s", minimal_monnam(mtmp, FALSE)); + /* minimal_monnam() appends map coordinates; strip that */ + (void) strsubst(buf, " <0,0>", ""); + if (has_mgivenname(mtmp)) /* if mtmp is named, include that */ + Sprintf(eos(buf), " named %s", MGIVENNAME(mtmp)); + if (c == 'o' || c == 'a') + Sprintf(eos(buf), " to %d:%d", mtmp->mux, mtmp->muy); + xyloc = mtmp->mtrack[0].x; /* (for legibility) */ + if (xyloc == MIGR_EXACT_XY) { + x = mtmp->mtrack[1].x; + y = mtmp->mtrack[1].y; + Sprintf(eos(buf), " at <%d,%d>", (int) x, (int) y); + } + putstr(win, 0, buf); + } + free((genericptr_t) marray); + display_nhwindow(win, FALSE); + destroy_nhwindow(win); + } else if (c != 'q') { + pline("None."); + } + + } +} + +/* the #stats command + * Display memory usage of all monsters and objects on the level. + */ +int +wiz_show_stats(void) +{ + char buf[BUFSZ]; + winid win; + long total_obj_size, total_obj_count, + total_mon_size, total_mon_count, + total_ovr_size, total_ovr_count, + total_misc_size, total_misc_count; + + win = create_nhwindow(NHW_TEXT); + putstr(win, 0, "Current memory statistics:"); + + total_obj_count = total_obj_size = 0L; + putstr(win, 0, stats_hdr); + Sprintf(buf, " Objects, base size %ld", (long) sizeof (struct obj)); + putstr(win, 0, buf); + obj_chain(win, "invent", gi.invent, TRUE, + &total_obj_count, &total_obj_size); + obj_chain(win, "fobj", fobj, TRUE, &total_obj_count, &total_obj_size); + obj_chain(win, "buried", svl.level.buriedobjlist, FALSE, + &total_obj_count, &total_obj_size); + obj_chain(win, "migrating obj", gm.migrating_objs, FALSE, + &total_obj_count, &total_obj_size); + obj_chain(win, "billobjs", gb.billobjs, FALSE, + &total_obj_count, &total_obj_size); + mon_invent_chain(win, "minvent", fmon, &total_obj_count, &total_obj_size); + mon_invent_chain(win, "migrating minvent", gm.migrating_mons, + &total_obj_count, &total_obj_size); + contained_stats(win, "contained", &total_obj_count, &total_obj_size); + putstr(win, 0, stats_sep); + Sprintf(buf, template, " Obj total", total_obj_count, total_obj_size); + putstr(win, 0, buf); + + total_mon_count = total_mon_size = 0L; + putstr(win, 0, ""); + Sprintf(buf, " Monsters, base size %ld", (long) sizeof (struct monst)); + putstr(win, 0, buf); + mon_chain(win, "fmon", fmon, TRUE, &total_mon_count, &total_mon_size); + mon_chain(win, "migrating", gm.migrating_mons, FALSE, + &total_mon_count, &total_mon_size); + /* 'gm.mydogs' is only valid during level change or end of game disclosure, + but conceivably we've been called from within debugger at such time */ + if (gm.mydogs) /* monsters accompanying hero */ + mon_chain(win, "mydogs", gm.mydogs, FALSE, + &total_mon_count, &total_mon_size); + putstr(win, 0, stats_sep); + Sprintf(buf, template, " Mon total", total_mon_count, total_mon_size); + putstr(win, 0, buf); + + total_ovr_count = total_ovr_size = 0L; + putstr(win, 0, ""); + putstr(win, 0, " Overview"); + overview_stats(win, template, &total_ovr_count, &total_ovr_size); + putstr(win, 0, stats_sep); + Sprintf(buf, template, " Over total", total_ovr_count, total_ovr_size); + putstr(win, 0, buf); + + total_misc_count = total_misc_size = 0L; + putstr(win, 0, ""); + putstr(win, 0, " Miscellaneous"); + misc_stats(win, &total_misc_count, &total_misc_size); + putstr(win, 0, stats_sep); + Sprintf(buf, template, " Misc total", total_misc_count, total_misc_size); + putstr(win, 0, buf); + + putstr(win, 0, ""); + putstr(win, 0, stats_sep); + Sprintf(buf, template, " Grand total", + (total_obj_count + total_mon_count + + total_ovr_count + total_misc_count), + (total_obj_size + total_mon_size + + total_ovr_size + total_misc_size)); + putstr(win, 0, buf); + +#if defined(__BORLANDC__) && !defined(_WIN32) + show_borlandc_stats(win); +#endif + + display_nhwindow(win, FALSE); + destroy_nhwindow(win); + return ECMD_OK; +} + +RESTORE_WARNING_FORMAT_NONLITERAL + +#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) || defined(DEBUG) +/* the #wizdispmacros command + * Verify that some display macros are returning sane values */ +int +wiz_display_macros(void) +{ + static const char display_issues[] = "Display macro issues:"; + char buf[BUFSZ]; + winid win; + int glyph, test, trouble = 0, no_glyph = NO_GLYPH, max_glyph = MAX_GLYPH; + + win = create_nhwindow(NHW_TEXT); + + for (glyph = 0; glyph < MAX_GLYPH; ++glyph) { + /* glyph_is_cmap / glyph_to_cmap() */ + if (glyph_is_cmap(glyph)) { + test = glyph_to_cmap(glyph); + /* check for MAX_GLYPH return */ + if (test == no_glyph) { + if (!trouble++) + putstr(win, 0, display_issues); + Sprintf(buf, "glyph_is_cmap() / glyph_to_cmap(glyph=%d)" + " sync failure, returned NO_GLYPH (%d)", + glyph, test); + putstr(win, 0, buf); + } + if (glyph_is_cmap_zap(glyph) + && !(test >= S_vbeam && test <= S_rslant)) { + if (!trouble++) + putstr(win, 0, display_issues); + Sprintf(buf, + "glyph_is_cmap_zap(glyph=%d) returned non-zap cmap %d", + glyph, test); + putstr(win, 0, buf); + } + /* check against defsyms array subscripts */ + if (!IndexOk(test, defsyms)) { + if (!trouble++) + putstr(win, 0, display_issues); + Sprintf(buf, "glyph_to_cmap(glyph=%d) returns %d" + " exceeds defsyms[%d] bounds (MAX_GLYPH = %d)", + glyph, test, SIZE(defsyms), max_glyph); + putstr(win, 0, buf); + } + } + /* glyph_is_monster / glyph_to_mon */ + if (glyph_is_monster(glyph)) { + test = glyph_to_mon(glyph); + /* check against mons array subscripts */ + if (test < 0 || test >= NUMMONS) { + if (!trouble++) + putstr(win, 0, display_issues); + Sprintf(buf, "glyph_to_mon(glyph=%d) returns %d" + " exceeds mons[%d] bounds", + glyph, test, NUMMONS); + putstr(win, 0, buf); + } + } + /* glyph_is_object / glyph_to_obj */ + if (glyph_is_object(glyph)) { + test = glyph_to_obj(glyph); + /* check against objects array subscripts */ + if (test < 0 || test > NUM_OBJECTS) { + if (!trouble++) + putstr(win, 0, display_issues); + Sprintf(buf, "glyph_to_obj(glyph=%d) returns %d" + " exceeds objects[%d] bounds", + glyph, test, NUM_OBJECTS); + putstr(win, 0, buf); + } + } + } + if (!trouble) + putstr(win, 0, "No display macro issues detected."); + display_nhwindow(win, FALSE); + destroy_nhwindow(win); + return ECMD_OK; +} + +/* the #wizshownhuuid command */ +int +wiz_show_nhuuid(void) +{ + pline("The NHUUID for this game is { %s }.", svn.nhuuid); + return ECMD_OK; +} + +/* the #wizmondiff command */ +int +wiz_mon_diff(void) +{ + static const char window_title[] = "Review of monster difficulty ratings" + " [index:level]:"; + char buf[BUFSZ]; + winid win; + int mhardcoded = 0, mcalculated = 0, trouble = 0, cnt = 0, mdiff = 0; + int mlev; + struct permonst *ptr; + + /* + * Possible extension: choose between showing discrepancies, + * showing all monsters, or monsters within a particular class. + */ + + win = create_nhwindow(NHW_TEXT); + for (ptr = &mons[0]; ptr->mlet; ptr++, cnt++) { + mcalculated = mstrength(ptr); + mhardcoded = (int) ptr->difficulty; + mdiff = mhardcoded - mcalculated; + if (mdiff) { + if (!trouble++) + putstr(win, 0, window_title); + mlev = (int) ptr->mlevel; + if (mlev > 50) /* hack for named demons */ + mlev = 50; + Snprintf(buf, sizeof buf, + "%-18s [%3d:%2d]: calculated: %2d, hardcoded: %2d (%+d)", + ptr->pmnames[NEUTRAL], cnt, mlev, + mcalculated, mhardcoded, mdiff); + putstr(win, 0, buf); + } + } + if (!trouble) + putstr(win, 0, "No monster difficulty discrepancies were detected."); + display_nhwindow(win, FALSE); + destroy_nhwindow(win); + return ECMD_OK; +} + +/* the #wizobjprobs command */ +int +wiz_objprobs(void) +{ + int win; + char buf[BUFSZ]; + int probsum[MAXOCLASSES]; + int otyp; + int oclass = objects[FIRST_OBJECT].oc_class; + memset(probsum, 0, sizeof probsum); + + for (otyp = FIRST_OBJECT; otyp < NUM_OBJECTS; otyp++) { + probsum[(int) objects[otyp].oc_class] += objects[otyp].oc_prob; + } + + win = create_nhwindow(NHW_TEXT); + for (otyp = FIRST_OBJECT; otyp < NUM_OBJECTS; otyp++) { + /* placeholders for extra descriptions aren't generatable objects */ + if (!OBJ_NAME(objects[otyp])) + continue; + + if ((int) objects[otyp].oc_class != oclass) { + putstr(win, 0, ""); + } + oclass = objects[otyp].oc_class; + + Snprintf(buf, sizeof buf, "%4d / %4d (%6.2f%%): %s", + objects[otyp].oc_prob, + probsum[oclass], + (float) objects[otyp].oc_prob * 100.f / + (float) probsum[oclass], + OBJ_NAME(objects[otyp])); + putstr(win, 0, buf); + } + display_nhwindow(win, FALSE); + destroy_nhwindow(win); + + return ECMD_OK; +} +#endif /* (NH_DEVEL_STATUS != NH_STATUS_RELEASED) || defined(DEBUG) */ + +/* #migratemons command */ +int +wiz_migrate_mons(void) +{ +#ifdef DEBUG_MIGRATING_MONS + int mcount; + char inbuf[BUFSZ]; + struct permonst *ptr; + struct monst *mtmp; + boolean use_random_mon = TRUE; + boolean mongen_saved = iflags.debug_mongen; +#endif + d_level tolevel; + + if (Is_stronghold(&u.uz)) + assign_level(&tolevel, &valley_level); + else if (!Is_botlevel(&u.uz)) + get_level(&tolevel, depth(&u.uz) + 1); + else + tolevel.dnum = 0, tolevel.dlevel = 0; + + list_migrating_mons(&tolevel); + +#ifdef DEBUG_MIGRATING_MONS + inbuf[0] = inbuf[1] = '\0'; + if (tolevel.dnum || tolevel.dlevel) + getlin("How many random monsters to migrate to next level? [0]", + inbuf); + else + pline("Can't get there from here."); + if (*inbuf == '\033' || *inbuf == '\0') + return ECMD_OK; + + mcount = atoi(inbuf); + if (mcount < 0) { + use_random_mon = FALSE; + mcount *= -1; + } + if (mcount < 1) + mcount = 0; + else if (mcount > ((COLNO - 1) * ROWNO)) + mcount = (COLNO - 1) * ROWNO; + + iflags.debug_mongen = FALSE; + while (mcount > 0) { + if (use_random_mon) { + ptr = rndmonst(); + mtmp = makemon(ptr, 0, 0, MM_NOMSG); + } else { + mtmp = fmon; + } + if (mtmp) + migrate_to_level(mtmp, ledger_no(&tolevel), MIGR_RANDOM, + (coord *) 0); + mcount--; + } + iflags.debug_mongen = mongen_saved; +#endif /* DEBUG_MIGRATING_MONS */ + return ECMD_OK; +} + +/* #wizcustom command to see glyphmap customizations */ +int +wiz_custom(void) +{ + extern const char *const known_handling[]; /* symbols.c */ + + if (wizard) { + static const char wizcustom[] = "#wizcustom"; + winid win; + char buf[BUFSZ], bufa[BUFSZ]; + int n; +#if 0 + int j, glyph; +#endif + menu_item *pick_list = (menu_item *) 0; + + if (!glyphid_cache_status()) + fill_glyphid_cache(); + + win = create_nhwindow(NHW_MENU); + start_menu(win, MENU_BEHAVE_STANDARD); + add_menu_heading(win, + " glyph glyph identifier " + " sym clr customcolor unicode utf8"); + Sprintf(bufa, "%s: colorcount=%ld %s", wizcustom, + (long) iflags.colorcount, + gs.symset[PRIMARYSET].name ? gs.symset[PRIMARYSET].name + : "default"); + if (gc.currentgraphics == PRIMARYSET && gs.symset[PRIMARYSET].name) + Strcat(bufa, ", active"); + if (gs.symset[PRIMARYSET].handling) { + Sprintf(eos(bufa), ", handler=%s", + known_handling[gs.symset[PRIMARYSET].handling]); + } + Sprintf(buf, "%s", bufa); + wizcustom_glyphids(win); + end_menu(win, bufa); + n = select_menu(win, PICK_NONE, &pick_list); + destroy_nhwindow(win); +#if 0 + for (j = 0; j < n; ++j) { + glyph = pick_list[j].item.a_int - 1; /* -1: reverse +1 above */ + } +#endif + if (n >= 1) + free((genericptr_t) pick_list); + if (glyphid_cache_status()) + free_glyphid_cache(); + docrt(); + } else + pline(unavailcmd, ecname_from_fn(wiz_custom)); + return ECMD_OK; +} + +void +wizcustom_callback(winid win, int glyphnum, char *id) +{ + extern glyph_map glyphmap[MAX_GLYPH]; + glyph_map *cgm; + int clr = NO_COLOR; + char buf[BUFSZ], bufa[BUFSZ], bufb[BUFSZ], bufc[BUFSZ], bufd[BUFSZ], + bufu[BUFSZ]; + anything any; + uint8 *cp; + + if (win && id) { + cgm = &glyphmap[glyphnum]; + if ( +#ifdef ENHANCED_SYMBOLS + cgm->u || +#endif + cgm->customcolor != 0) { + Sprintf(bufa, "[%04d] %-44s", glyphnum, id); + Sprintf(bufb, "'\\%03d' %02d", + gs.showsyms[cgm->sym.symidx], cgm->sym.color); + Sprintf(bufc, "%011lx", (unsigned long) cgm->customcolor); + bufu[0] = '\0'; +#ifdef ENHANCED_SYMBOLS + if (cgm->u && cgm->u->utf8str) { + Sprintf(bufu, "U+%04lx", (unsigned long) cgm->u->utf32ch); + cp = cgm->u->utf8str; + while (*cp) { + Sprintf(bufd, " <%d>", (int) *cp); + Strcat(bufu, bufd); + cp++; + } + } +#endif + any.a_int = glyphnum + 1; /* avoid 0 */ + Snprintf(buf, sizeof buf, "%s %s %s %s", bufa, bufb, bufc, bufu); + add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr, buf, + MENU_ITEMFLAGS_NONE); + } + } + return; +} + +/*wizcmds.c*/ diff --git a/src/worm.c b/src/worm.c index 1d7dd8ad2..c5a12fd12 100644 --- a/src/worm.c +++ b/src/worm.c @@ -1,10 +1,9 @@ -/* NetHack 3.6 worm.c $NHDT-Date: 1561340880 2019/06/24 01:48:00 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.30 $ */ +/* NetHack 5.0 worm.c $NHDT-Date: 1652689653 2022/05/16 08:27:33 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.56 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2009. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -#include "lev.h" #define newseg() (struct wseg *) alloc(sizeof (struct wseg)) #define dealloc_seg(wseg) free((genericptr_t) (wseg)) @@ -12,13 +11,18 @@ /* worm segment structure */ struct wseg { struct wseg *nseg; - xchar wx, wy; /* the segment's position */ + coordxy wx, wy; /* the segment's position */ }; -STATIC_DCL void FDECL(toss_wsegs, (struct wseg *, BOOLEAN_P)); -STATIC_DCL void FDECL(shrink_worm, (int)); -STATIC_DCL void FDECL(random_dir, (XCHAR_P, XCHAR_P, xchar *, xchar *)); -STATIC_DCL struct wseg *FDECL(create_worm_tail, (int)); +#ifndef SFCTOOL +staticfn void toss_wsegs(struct wseg *, boolean) NO_NNARGS; +staticfn void shrink_worm(int); + +#if 0 +staticfn void random_dir(int, int, int *, int *); +#endif +staticfn struct wseg *create_worm_tail(int); /* may return NULL */ +#endif /* !SFCTOOL */ /* Description of long worm implementation. * @@ -27,7 +31,7 @@ STATIC_DCL struct wseg *FDECL(create_worm_tail, (int)); * If wormno == 0 this does not mean that the monster is not a worm, * it just means that the monster does not have a long worm tail. * - * The actual segments of a worm are not full blown monst structs. + * The actual segments of a worm are not full-blown monst structs. * They are small wseg structs, and their position in the levels.monsters[][] * array is held by the monst struct of the head of the worm. This makes * things like probing and hit point bookkeeping much easier. @@ -42,7 +46,7 @@ STATIC_DCL struct wseg *FDECL(create_worm_tail, (int)); * wheads: The last (end) of a linked list of segments. This points to * the segment that is at the same position as the real monster * (the head). Note that the segment that wheads[wormno] points - * to, is not displayed. It is simply there to keep track of + * to is not displayed. It is simply there to keep track of * where the head came from, so that worm movement and display * are simplified later. * Keeping the head segment of the worm at the end of the list @@ -67,9 +71,14 @@ STATIC_DCL struct wseg *FDECL(create_worm_tail, (int)); * segment, and remove hit points from the worm. */ -struct wseg *wheads[MAX_NUM_WORMS] = DUMMY, *wtails[MAX_NUM_WORMS] = DUMMY; -long wgrowtime[MAX_NUM_WORMS] = DUMMY; +/* restart: worm removal resets these so they don't need to be incorporated + into 'struct instance_globals g' for potential reinitialization provided + that old game disposes of monsters properly before starting a new one */ +static struct wseg *wheads[MAX_NUM_WORMS] = DUMMY, + *wtails[MAX_NUM_WORMS] = DUMMY; +static long wgrowtime[MAX_NUM_WORMS] = DUMMY; +#ifndef SFCTOOL /* * get_wormno() * @@ -77,16 +86,16 @@ long wgrowtime[MAX_NUM_WORMS] = DUMMY; * there are no slots available. This means that the worm head can exist, * it just cannot ever grow a tail. * - * It, also, means that there is an optimisation to made. The [0] positions + * It, also, means that there is an optimization to made. The [0] positions * of the arrays are never used. Meaning, we really *could* have one more * tailed worm on the level, or use a smaller array (using wormno - 1). * * Implementation is left to the interested hacker. */ int -get_wormno() +get_wormno(void) { - register int new_wormno = 1; + int new_wormno = 1; while (new_wormno < MAX_NUM_WORMS) { if (!wheads[new_wormno]) @@ -104,30 +113,26 @@ get_wormno() * Initialize the worm entry. This will set up the worm grow time, and * create and initialize the dummy segment for wheads[] and wtails[]. * - * If the worm has no tail (ie get_wormno() fails) then this function need - * not be called. + * If the worm has no tail (ie get_wormno() fails) then this function + * need not be called. */ void -initworm(worm, wseg_count) -struct monst *worm; -int wseg_count; +initworm(struct monst *worm, int wseg_count) { - register struct wseg *seg, *new_tail = create_worm_tail(wseg_count); - register int wnum = worm->wormno; - - /* if (!wnum) return; bullet proofing */ + struct wseg *seg, *new_tail = create_worm_tail(wseg_count); + int wnum = worm->wormno; if (new_tail) { wtails[wnum] = new_tail; for (seg = new_tail; seg->nseg; seg = seg->nseg) - ; + continue; wheads[wnum] = seg; } else { wtails[wnum] = wheads[wnum] = seg = newseg(); seg->nseg = (struct wseg *) 0; - seg->wx = worm->mx; - seg->wy = worm->my; } + seg->wx = worm->mx; + seg->wy = worm->my; wgrowtime[wnum] = 0L; } @@ -137,20 +142,16 @@ int wseg_count; * Get rid of all worm segments on and following the given pointer curr. * The display may or may not need to be updated as we free the segments. */ -STATIC_OVL -void -toss_wsegs(curr, display_update) -register struct wseg *curr; -register boolean display_update; +staticfn void +toss_wsegs(struct wseg *curr, boolean display_update) { - register struct wseg *seg; + struct wseg *nxtseg; while (curr) { - seg = curr->nseg; - - /* remove from level.monsters[][] */ + nxtseg = curr->nseg; - /* need to check curr->wx for genocided while migrating_mon */ + /* remove from level.monsters[][]; + need to check curr->wx for genocided while migrating_mon */ if (curr->wx) { remove_monster(curr->wx, curr->wy); @@ -161,7 +162,7 @@ register boolean display_update; /* free memory used by the segment */ dealloc_seg(curr); - curr = seg; + curr = nxtseg; } } @@ -170,10 +171,8 @@ register boolean display_update; * * Remove the tail segment of the worm (the starting segment of the list). */ -STATIC_OVL -void -shrink_worm(wnum) -int wnum; /* worm number */ +staticfn void +shrink_worm(int wnum) /* worm number */ { struct wseg *seg; @@ -194,13 +193,10 @@ int wnum; /* worm number */ * Move the worm. Maybe grow. */ void -worm_move(worm) -struct monst *worm; +worm_move(struct monst *worm) { - register struct wseg *seg, *new_seg; /* new segment */ - register int wnum = worm->wormno; /* worm number */ - - /* if (!wnum) return; bullet proofing */ + struct wseg *seg, *new_seg; /* new segment */ + int wnum = worm->wormno; /* worm number */ /* * Place a segment at the old worm head. The head has already moved. @@ -219,19 +215,66 @@ struct monst *worm; seg->nseg = new_seg; /* attach it to the end of the list */ wheads[wnum] = new_seg; /* move the end pointer */ - if (wgrowtime[wnum] <= moves) { - if (!wgrowtime[wnum]) - wgrowtime[wnum] = moves + rnd(5); - else - wgrowtime[wnum] += rn1(15, 3); - worm->mhp += 3; - if (worm->mhp > MHPMAX) - worm->mhp = MHPMAX; - if (worm->mhp > worm->mhpmax) - worm->mhpmax = worm->mhp; - } else - /* The worm doesn't grow, so the last segment goes away. */ + if (wgrowtime[wnum] <= svm.moves) { + int whplimit, whpcap, prev_mhp, wsegs = count_wsegs(worm); + + /* first set up for the next time to grow */ + if (!wgrowtime[wnum]) { + /* new worm; usually grow a tail segment on its next turn */ + wgrowtime[wnum] = svm.moves + rnd(5); + } else { + int mmove = mcalcmove(worm, FALSE), + /* prior to 5.0.0,, next-grow increment was 3..17 but since + it got checked every 4th turn when the speed 3 worm got + to move, it was effectively 0..5; also, its usage was + 'wgrowtime += incr', so often 'wgrowtime' would be + exceeded by 'moves' on consecutive turns for the worm, + resulting in an excessively rapid growth cycle */ + incr = rn1(10, 2); /* 2..12; after adjusting for long worn + * speed of 3, effective value is 8..48 */ + + incr = (incr * NORMAL_SPEED) / max(mmove, 1); + wgrowtime[wnum] = svm.moves + incr; + } + + /* increase HP based on number of segments; if it has shrunk, it + won't gain new HP until regaining previous peak segment count; + when wounded (whether from damage or from shrinking), the HP + which might have been 'new' will heal */ + whplimit = !worm->m_lev ? 4 : (8 * (int) worm->m_lev); + /* note: wsegs includes the hidden segment co-located with the head */ + if (wsegs > 33) + whplimit += 2 * (wsegs - 33), wsegs = 33; + if (wsegs > 22) + whplimit += 4 * (wsegs - 22), wsegs = 22; + if (wsegs > 11) + whplimit += 6 * (wsegs - 11), wsegs = 11; + whplimit += 8 * wsegs; + if (whplimit > MHPMAX) + whplimit = MHPMAX; + + prev_mhp = worm->mhp; + worm->mhp += d(2, 2); /* 2..4, average 3 */ + whpcap = max(whplimit, worm->mhpmax); + if (worm->mhp < whpcap) { + /* can't exceed segment-derived limit unless level increase after + peak tail growth has already done so; when that isn't the case, + if segment growth exceeds current max HP then increase it */ + if (worm->mhp > whplimit) + worm->mhp = max(prev_mhp, whplimit); + if (worm->mhp > worm->mhpmax) + worm->mhpmax = worm->mhp; + } else { + if (worm->mhp > worm->mhpmax) + worm->mhp = worm->mhpmax; + } + } else { + /* The worm doesn't grow, so the last segment goes away. + (Done after inserting an extra segment at the head, so it + isn't getting smaller here, just changing location without + having to move any of the intermediate segments.) */ shrink_worm(wnum); + } } /* @@ -239,43 +282,53 @@ struct monst *worm; * * Check for mon->wormno before calling this function! * - * The worm don't move so it should shrink. + * The worm doesn't move, so it should shrink. */ void -worm_nomove(worm) -register struct monst *worm; +worm_nomove(struct monst *worm) { shrink_worm((int) worm->wormno); /* shrink */ - if (worm->mhp > 3) - worm->mhp -= 3; /* mhpmax not changed ! */ - else - worm->mhp = 1; + if (worm->mhp > count_wsegs(worm)) { + worm->mhp -= d(2, 2); /* 2..4, average 3; note: mhpmax not changed! */ + if (worm->mhp < 1) + worm->mhp = 1; + } } /* * wormgone() * - * Check for mon->wormno before calling this function! + * Kill a worm tail. Also takes the head off the map. Caller needs to + * keep track of what its coordinates were if planning to put it back. * - * Kill a worm tail. + * Should only be called when mon->wormno is non-zero. */ void -wormgone(worm) -register struct monst *worm; +wormgone(struct monst *worm) { - register int wnum = worm->wormno; - - /* if (!wnum) return; bullet proofing */ + int wnum = worm->wormno; - worm->wormno = 0; + if (!wnum) /* note: continuing with wnum==0 runs to completion */ + impossible("wormgone: wormno is 0"); - /* This will also remove the real monster (ie 'w') from the its - * position in level.monsters[][]. + worm->wormno = 0; /* still a long worm but doesn't grow/shrink anymore */ + /* + * This will also remove the real monster (ie 'w') from the its + * position in level.monsters[][]. (That happens when removing + * the hidden tail segment which is co-located with the head.) */ toss_wsegs(wtails[wnum], TRUE); wheads[wnum] = wtails[wnum] = (struct wseg *) 0; + wgrowtime[wnum] = 0L; + + /* we don't expect to encounter this here but check for it anyway; + when a long worm gets created by a polymorph zap, it gets flagged + with MCORPSENM()==PM_LONG_WORM so that the same zap won't trigger + another polymorph if it hits the new tail */ + if (worm->data == &mons[PM_LONG_WORM] && has_mcorpsenm(worm)) + MCORPSENM(worm) = NON_PM; /* no longer polymorph-proof */ } /* @@ -284,25 +337,28 @@ register struct monst *worm; * Check for mon->wormno before calling this function! * * If the hero is near any part of the worm, the worm will try to attack. + * Returns 1 if the worm dies (poly'd hero with passive counter-attack) + * or 0 if it doesn't. */ -void -wormhitu(worm) -register struct monst *worm; +int +wormhitu(struct monst *worm) { - register int wnum = worm->wormno; - register struct wseg *seg; - - /* if (!wnum) return; bullet proofing */ + int wnum = worm->wormno; + struct wseg *seg; /* This does not work right now because mattacku() thinks that the head * is out of range of the player. We might try to kludge, and bring * the head within range for a tiny moment, but this needs a bit more * looking at before we decide to do this. + * + * Head has already had a chance to attack, so the dummy tail segment + * sharing its location should be skipped. */ - for (seg = wtails[wnum]; seg; seg = seg->nseg) + for (seg = wtails[wnum]; seg != wheads[wnum]; seg = seg->nseg) if (distu(seg->wx, seg->wy) < 3) if (mattacku(worm)) - return; /* your passive ability killed the worm */ + return 1; /* your passive ability killed the worm */ + return 0; } /* cutworm() @@ -314,18 +370,16 @@ register struct monst *worm; * that both halves will survive. */ void -cutworm(worm, x, y, cuttier) -struct monst *worm; -xchar x, y; -boolean cuttier; /* hit is by wielded blade or axe or by thrown axe */ +cutworm(struct monst *worm, coordxy x, coordxy y, + boolean cuttier) /* hit is by wielded blade or axe or by thrown axe */ { - register struct wseg *curr, *new_tail; - register struct monst *new_worm; + struct wseg *curr, *new_tail; + struct monst *new_worm; int wnum = worm->wormno; int cut_chance, new_wnum; if (!wnum) - return; /* bullet proofing */ + return; /* bullet-proofing */ if (x == worm->mx && y == worm->my) return; /* hit on head */ @@ -366,7 +420,7 @@ boolean cuttier; /* hit is by wielded blade or axe or by thrown axe */ /* * At this point, the old worm is correct. Any new worm will have - * it's head at "curr" and its tail at "new_tail". The old worm + * its head at "curr" and its tail at "new_tail". The old worm * must be at least level 3 in order to produce a new worm. */ new_worm = 0; @@ -382,7 +436,7 @@ boolean cuttier; /* hit is by wielded blade or axe or by thrown axe */ /* Sometimes the tail end dies. */ if (!new_worm) { place_worm_seg(worm, x, y); /* place the "head" segment back */ - if (context.mon_moving) { + if (svc.context.mon_moving) { if (canspotmon(worm)) pline("Part of %s tail has been cut off.", s_suffix(mon_nam(worm))); @@ -416,7 +470,7 @@ boolean cuttier; /* hit is by wielded blade or axe or by thrown axe */ /* Place the new monster at all the segment locations. */ place_wsegs(new_worm, worm); - if (context.mon_moving) + if (svc.context.mon_moving) pline("%s is cut in half.", Monnam(worm)); else You("cut %s in half.", mon_nam(worm)); @@ -430,13 +484,10 @@ boolean cuttier; /* hit is by wielded blade or axe or by thrown axe */ * is located here for modularity. */ void -see_wsegs(worm) -struct monst *worm; +see_wsegs(struct monst *worm) { struct wseg *curr = wtails[worm->wormno]; - /* if (!mtmp->wormno) return; bullet proofing */ - while (curr != wheads[worm->wormno]) { newsym(curr->wx, curr->wy); curr = curr->nseg; @@ -449,22 +500,19 @@ struct monst *worm; * Display all of the segments of the given worm for detection. */ void -detect_wsegs(worm, use_detection_glyph) -struct monst *worm; -boolean use_detection_glyph; +detect_wsegs(struct monst *worm, boolean use_detection_glyph) { int num; struct wseg *curr = wtails[worm->wormno]; - - /* if (!mtmp->wormno) return; bullet proofing */ int what_tail = what_mon(PM_LONG_WORM_TAIL, newsym_rn2); while (curr != wheads[worm->wormno]) { - num = use_detection_glyph - ? detected_monnum_to_glyph(what_tail) - : (worm->mtame - ? petnum_to_glyph(what_tail) - : monnum_to_glyph(what_tail)); + num = use_detection_glyph ? detected_monnum_to_glyph(what_tail, + worm->female ? FEMALE : MALE) + : worm->mtame ? petnum_to_glyph(what_tail, + worm->female ? FEMALE : MALE) + : monnum_to_glyph(what_tail, + worm->female ? FEMALE : MALE); show_glyph(curr->wx, curr->wy, num); curr = curr->nseg; } @@ -477,31 +525,31 @@ boolean use_detection_glyph; * of segments, including the dummy. Called from save.c. */ void -save_worm(fd, mode) -int fd, mode; +save_worm(NHFILE *nhfp) { int i; int count; struct wseg *curr, *temp; - if (perform_bwrite(mode)) { + if (update_file(nhfp)) { for (i = 1; i < MAX_NUM_WORMS; i++) { for (count = 0, curr = wtails[i]; curr; curr = curr->nseg) count++; /* Save number of segments */ - bwrite(fd, (genericptr_t) &count, sizeof(int)); + Sfo_int(nhfp, &count, "worm-segment_count"); /* Save segment locations of the monster. */ if (count) { for (curr = wtails[i]; curr; curr = curr->nseg) { - bwrite(fd, (genericptr_t) & (curr->wx), sizeof(xchar)); - bwrite(fd, (genericptr_t) & (curr->wy), sizeof(xchar)); + Sfo_coordxy(nhfp, &(curr->wx), "worm-wx"); + Sfo_coordxy(nhfp, &(curr->wy), "worm-wy"); } } } - bwrite(fd, (genericptr_t) wgrowtime, sizeof(wgrowtime)); + for (i = 0; i < MAX_NUM_WORMS; ++i) + Sfo_long(nhfp, &wgrowtime[i], "worm-wgrowtime"); } - if (release_data(mode)) { + if (release_data(nhfp)) { /* Free the segments only. savemonchn() will take care of the * monsters. */ for (i = 1; i < MAX_NUM_WORMS; i++) { @@ -514,9 +562,11 @@ int fd, mode; curr = temp; } wheads[i] = wtails[i] = (struct wseg *) 0; + wgrowtime[i] = 0L; } } } +#endif /* !SFCTOOL */ /* * rest_worm() @@ -524,23 +574,21 @@ int fd, mode; * Restore the worm information from the save file. Called from restore.c */ void -rest_worm(fd) -int fd; +rest_worm(NHFILE *nhfp) { - int i, j, count; + int i, j; + int count = 0; struct wseg *curr, *temp; for (i = 1; i < MAX_NUM_WORMS; i++) { - mread(fd, (genericptr_t) &count, sizeof(int)); - if (!count) - continue; /* none */ + Sfi_int(nhfp, &count, "worm-segment_count"); /* Get the segments. */ for (curr = (struct wseg *) 0, j = 0; j < count; j++) { temp = newseg(); temp->nseg = (struct wseg *) 0; - mread(fd, (genericptr_t) & (temp->wx), sizeof(xchar)); - mread(fd, (genericptr_t) & (temp->wy), sizeof(xchar)); + Sfi_coordxy(nhfp, &(temp->wx), "worm-wx"); + Sfi_coordxy(nhfp, &(temp->wy), "worm-wy"); if (curr) curr->nseg = temp; else @@ -549,63 +597,111 @@ int fd; } wheads[i] = curr; } - mread(fd, (genericptr_t) wgrowtime, sizeof(wgrowtime)); + for (i = 0; i < MAX_NUM_WORMS; ++i) { + Sfi_long(nhfp, &wgrowtime[i], "worm-wgrowtime"); + } } +#ifndef SFCTOOL /* * place_wsegs() * * Place the segments of the given worm. Called from restore.c + * and from replmon() in mon.c. * If oldworm is not NULL, assumes the oldworm segments are on map * in the same location as worm segments */ void -place_wsegs(worm, oldworm) -struct monst *worm, *oldworm; +place_wsegs(struct monst *worm, struct monst *oldworm) { struct wseg *curr = wtails[worm->wormno]; - /* if (!mtmp->wormno) return; bullet proofing */ - while (curr != wheads[worm->wormno]) { - xchar x = curr->wx; - xchar y = curr->wy; + coordxy x = curr->wx, y = curr->wy; + struct monst *mtmp = m_at(x, y); + + if (oldworm && mtmp == oldworm) + remove_monster(x, y); + else if (mtmp) + impossible("placing worm seg <%d,%d> over another mon", x, y); + else if (oldworm) + impossible("replacing worm seg <%d,%d> on empty spot", x, y); - if (oldworm) { - if (m_at(x,y) == oldworm) - remove_monster(x, y); - else - impossible("placing worm seg <%i,%i> over another mon", x, y); - } place_worm_seg(worm, x, y); curr = curr->nseg; } + /* head segment is co-located with worm itself so not placed on the map */ + curr->wx = worm->mx, curr->wy = worm->my; } +/* called from mon_sanity_check(mon.c) */ void -sanity_check_worm(worm) -struct monst *worm; +sanity_check_worm(struct monst *worm) { struct wseg *curr; + int wnum, x, y; + + if (!worm) { + impossible("worm_sanity: null monster!"); + return; + } + /* note: wormno can't be less than 0 (unsigned bit field) and can't + be greater that MAX_NUM_WORMS - 1 (which uses all available bits) + so checking for 0 is all we can manage for wormno validation; + since caller has already done that, this is rather pointless... */ + if (!worm->wormno) { + impossible("worm_sanity: not a worm!"); + return; + } - if (!worm) - panic("no worm!"); - if (!worm->wormno) - panic("not a worm?!"); + wnum = worm->wormno; + if (!wtails[wnum] || !wheads[wnum]) { + impossible("wormno %d is set without proper tail", wnum); + return; + } + /* if worm is migrating, we can't check its segments against the map */ + if (!worm->mx) + return; - curr = wtails[worm->wormno]; + curr = wtails[wnum]; + while (curr != wheads[wnum]) { + x = curr->wx, y = curr->wy; + if (!isok(x, y)) + impossible("worm seg not isok <%d,%d>", x, y); + else if (svl.level.monsters[x][y] != worm) + impossible("mon (%s) at seg location is not worm (%s)", + fmt_ptr((genericptr_t) svl.level.monsters[x][y]), + fmt_ptr((genericptr_t) worm)); - while (curr != wheads[worm->wormno]) { - if (curr->wx) { - if (!isok(curr->wx, curr->wy)) - panic("worm seg not isok"); - if (level.monsters[curr->wx][curr->wy] != worm) - panic("worm not at seg location"); - } curr = curr->nseg; } } +/* called from mon_sanity_check(mon.c) */ +void +wormno_sanity_check(void) +{ +#ifdef EXTRA_SANITY_CHECKS + struct wseg *seg; + int wh = 0, wt = 0; + + /* checking tail management, not a particular monster; since wormno==0 + means 'not a worm', wheads[0] and wtails[0] should always be empty; + note: if erroneously non-Null, tail segment count will include the + extra segment for the worm's head that isn't shown on the map */ + for (seg = wheads[0]; seg; seg = seg->nseg) + ++wh; + for (seg = wtails[0]; seg; seg = seg->nseg) + ++wt; + if (wh || wt) { + impossible( + "phantom worm tail #0 [head=%s, %d segment%s; tail=%s, %d segment%s]", + fmt_ptr(wheads[0]), wh, plur(wh), + fmt_ptr(wtails[0]), wt, plur(wt)); + } +#endif /* EXTRA_SANITY_CHECKS */ +} + /* * remove_worm() * @@ -615,12 +711,9 @@ struct monst *worm; * not remove the mon from the fmon chain. */ void -remove_worm(worm) -register struct monst *worm; +remove_worm(struct monst *worm) { - register struct wseg *curr = wtails[worm->wormno]; - - /* if (!mtmp->wormno) return; bullet proofing */ + struct wseg *curr = wtails[worm->wormno]; while (curr) { if (curr->wx) { @@ -642,21 +735,35 @@ register struct monst *worm; * be, if somehow the head is disjoint from the tail. */ void -place_worm_tail_randomly(worm, x, y) -struct monst *worm; -xchar x, y; +place_worm_tail_randomly(struct monst *worm, coordxy x, coordxy y) { int wnum = worm->wormno; struct wseg *curr = wtails[wnum]; struct wseg *new_tail; - register xchar ox = x, oy = y; - - /* if (!wnum) return; bullet proofing */ + int ox = x, oy = y; if (wnum && (!wtails[wnum] || !wheads[wnum])) { impossible("place_worm_tail_randomly: wormno is set without a tail!"); return; } + if (wtails[wnum] == wheads[wnum]) { + /* single segment, co-located with worm; + should either have same coordinates or have seg->wx==0 + to indicate that it is not currently on the map */ + if (curr->wx && (curr->wx != worm->mx || curr->wy != worm->my)) { + impossible( + "place_worm_tail_randomly: tail segment at <%d,%d>, worm at <%d,%d>", + curr->wx, curr->wy, worm->mx, worm->my); + if (m_at(curr->wx, curr->wy) == worm) + remove_monster(curr->wx, curr->wy); + } + curr->wx = worm->mx, curr->wy = worm->my; + return; + } + /* remove head segment from map in case we end up calling toss_wsegs(); + if it doesn't get tossed, it will become the final tail segment and + get new coordinates */ + wheads[wnum]->wx = wheads[wnum]->wy = 0; wheads[wnum] = new_tail = curr; curr = curr->nseg; @@ -665,30 +772,26 @@ xchar x, y; new_tail->wy = y; while (curr) { - xchar nx, ny; - char tryct = 0; - - /* pick a random direction from x, y and search for goodpos() */ - do { - random_dir(ox, oy, &nx, &ny); - } while (!goodpos(nx, ny, worm, 0) && (tryct++ < 50)); + coordxy nx = ox, ny = oy; - if (tryct < 50) { + if (rnd_nextto_goodpos(&nx, &ny, worm)) { place_worm_seg(worm, nx, ny); - curr->wx = ox = nx; - curr->wy = oy = ny; + curr->wx = (coordxy) (ox = nx); + curr->wy = (coordxy) (oy = ny); wtails[wnum] = curr; curr = curr->nseg; wtails[wnum]->nseg = new_tail; new_tail = wtails[wnum]; newsym(nx, ny); - } else { /* Oops. Truncate because there was */ - toss_wsegs(curr, FALSE); /* no place for the rest of it */ + } else { + /* Oops. Truncate because there is no place for rest of it. */ + toss_wsegs(curr, FALSE); curr = (struct wseg *) 0; } } } +#if 0 /* * Given a coordinate x, y. * return in *nx, *ny, the coordinates of one of the <= 8 squares adjoining. @@ -696,15 +799,12 @@ xchar x, y; * This function, and the loop it serves, could be eliminated by coding * enexto() with a search radius. */ -STATIC_OVL -void -random_dir(x, y, nx, ny) -xchar x, y; -xchar *nx, *ny; +staticfn void +random_dir(int x, int y, int *nx, int *ny) { *nx = x + (x > 1 /* extreme left ? */ ? (x < COLNO - 1 /* extreme right ? */ - ? (rn2(3) - 1) /* neither so +1, 0, or -1 */ + ? (rn2(3) - 1) /* neither, so +1, 0, or -1 */ : -rn2(2)) /* right edge, use -1 or 0 */ : rn2(2)); /* left edge, use 0 or 1 */ if (*nx != x) /* if x has changed, do same thing with y */ @@ -720,11 +820,11 @@ xchar *nx, *ny; : -1) /* bottom, use -1 */ : 1); /* top, use +1 */ } +#endif /* for size_monst(cmd.c) to support #stats */ int -size_wseg(worm) -struct monst *worm; +size_wseg(struct monst *worm) { return (int) (count_wsegs(worm) * sizeof (struct wseg)); } @@ -733,11 +833,10 @@ struct monst *worm; * returns the number of segments that a worm has. */ int -count_wsegs(mtmp) -struct monst *mtmp; +count_wsegs(struct monst *mtmp) { - register int i = 0; - register struct wseg *curr; + int i = 0; + struct wseg *curr; if (mtmp->wormno) { for (curr = wtails[mtmp->wormno]->nseg; curr; curr = curr->nseg) @@ -749,13 +848,11 @@ struct monst *mtmp; /* create_worm_tail() * will create a worm tail chain of (num_segs + 1) and return pointer to it. */ -STATIC_OVL -struct wseg * -create_worm_tail(num_segs) -int num_segs; +staticfn struct wseg * +create_worm_tail(int num_segs) { - register int i = 0; - register struct wseg *new_tail, *curr; + int i = 0; + struct wseg *new_tail, *curr; if (!num_segs) return (struct wseg *) 0; @@ -783,8 +880,7 @@ int num_segs; * Mostly used in the canseemon() macro. */ boolean -worm_known(worm) -struct monst *worm; +worm_known(struct monst *worm) { struct wseg *curr = wtails[worm->wormno]; @@ -799,8 +895,7 @@ struct monst *worm; /* would moving from to involve passing between two consecutive segments of the same worm? */ boolean -worm_cross(x1, y1, x2, y2) -int x1, y1, x2, y2; +worm_cross(int x1, int y1, int x2, int y2) { struct monst *worm; struct wseg *curr, *wnxt; @@ -848,16 +943,14 @@ int x1, y1, x2, y2; /* construct an index number for a worm tail segment */ int -wseg_at(worm, x, y) -struct monst *worm; -int x, y; +wseg_at(struct monst *worm, int x, int y) { int res = 0; if (worm && worm->wormno && m_at(x, y) == worm) { struct wseg *curr; int i, n; - xchar wx = (xchar) x, wy = (xchar) y; + coordxy wx = (coordxy) x, wy = (coordxy) y; for (i = 0, curr = wtails[worm->wormno]; curr; curr = curr->nseg) { if (curr->wx == wx && curr->wy == wy) @@ -871,4 +964,38 @@ int x, y; return res; } +void +flip_worm_segs_vertical(struct monst *worm, int miny, int maxy) +{ + struct wseg *curr = wtails[worm->wormno]; + + while (curr) { + curr->wy = (maxy - curr->wy + miny); + curr = curr->nseg; + } +} + +void +flip_worm_segs_horizontal(struct monst *worm, int minx, int maxx) +{ + struct wseg *curr = wtails[worm->wormno]; + + while (curr) { + curr->wx = (maxx - curr->wx + minx); + curr = curr->nseg; + } +} + +void +redraw_worm(struct monst *worm) +{ + struct wseg *curr = wtails[worm->wormno]; + + while (curr) { + newsym(curr->wx, curr->wy); + curr = curr->nseg; + } +} +#endif /* !SFCTOOL */ + /*worm.c*/ diff --git a/src/worn.c b/src/worn.c index 1a966b7ae..14bb8fdbc 100644 --- a/src/worn.c +++ b/src/worn.c @@ -1,72 +1,94 @@ -/* NetHack 3.6 worn.c $NHDT-Date: 1550524569 2019/02/18 21:16:09 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.56 $ */ +/* NetHack 5.0 worn.c $NHDT-Date: 1770949988 2026/02/12 18:33:08 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.119 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2013. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -STATIC_DCL void FDECL(m_lose_armor, (struct monst *, struct obj *)); -STATIC_DCL void FDECL(m_dowear_type, - (struct monst *, long, BOOLEAN_P, BOOLEAN_P)); -STATIC_DCL int FDECL(extra_pref, (struct monst *, struct obj *)); +staticfn void m_lose_armor(struct monst *, struct obj *, boolean) NONNULLPTRS; +staticfn void clear_bypass(struct obj *) NO_NNARGS; +staticfn void m_dowear_type(struct monst *, long, boolean, boolean) + NONNULLARG1; +staticfn int extra_pref(struct monst *, struct obj *) NONNULLARG1; -const struct worn { +static const struct worn { long w_mask; struct obj **w_obj; -} worn[] = { { W_ARM, &uarm }, - { W_ARMC, &uarmc }, - { W_ARMH, &uarmh }, - { W_ARMS, &uarms }, - { W_ARMG, &uarmg }, - { W_ARMF, &uarmf }, - { W_ARMU, &uarmu }, - { W_RINGL, &uleft }, - { W_RINGR, &uright }, - { W_WEP, &uwep }, - { W_SWAPWEP, &uswapwep }, - { W_QUIVER, &uquiver }, - { W_AMUL, &uamul }, - { W_TOOL, &ublindf }, - { W_BALL, &uball }, - { W_CHAIN, &uchain }, - { 0, 0 } + const char *w_what; /* for failing sanity check's feedback */ +} worn[] = { { W_ARM, &uarm, "suit" }, + { W_ARMC, &uarmc, "cloak" }, + { W_ARMH, &uarmh, "helmet" }, + { W_ARMS, &uarms, "shield" }, + { W_ARMG, &uarmg, "gloves" }, + { W_ARMF, &uarmf, "boots" }, + { W_ARMU, &uarmu, "shirt" }, + { W_RINGL, &uleft, "left ring" }, + { W_RINGR, &uright, "right ring" }, + { W_WEP, &uwep, "weapon" }, + { W_SWAPWEP, &uswapwep, "alternate weapon" }, + { W_QUIVER, &uquiver, "quiver" }, + { W_AMUL, &uamul, "amulet" }, + { W_TOOL, &ublindf, "facewear" }, /* blindfold|towel|lenses */ + { W_BALL, &uball, "chained ball" }, + { W_CHAIN, &uchain, "attached chain" }, + { 0, 0, (char *) 0 } }; /* This only allows for one blocking item per property */ #define w_blocks(o, m) \ - ((o->otyp == MUMMY_WRAPPING && ((m) & W_ARMC)) \ - ? INVIS \ - : (o->otyp == CORNUTHAUM && ((m) & W_ARMH) && !Role_if(PM_WIZARD)) \ - ? CLAIRVOYANT \ - : 0) -/* note: monsters don't have clairvoyance, so your role + ((o->otyp == MUMMY_WRAPPING && ((m) & W_ARMC) != 0L) ? INVIS \ + : (o->otyp == CORNUTHAUM && ((m) & W_ARMH) != 0L \ + && !Role_if(PM_WIZARD)) ? CLAIRVOYANT \ + : (is_art(o, ART_EYES_OF_THE_OVERWORLD) \ + && ((m) & W_TOOL) != 0L) ? BLINDED \ + : 0) +/* note: monsters don't have clairvoyance, so dependency on hero's role here has no significant effect on their use of w_blocks() */ +/* calc the range of hero's unblind telepathy */ +void +recalc_telepat_range(void) +{ + const struct worn *wp; + int nobjs = 0; + + for (wp = worn; wp->w_mask; wp++) { + struct obj *oobj = *(wp->w_obj); + + if (oobj && objects[oobj->otyp].oc_oprop == TELEPAT) + nobjs++; + } + /* count all artifacts with SPFX_ESP as one */ + if (ETelepat & W_ART) + nobjs++; + + if (nobjs) + u.unblind_telepat_range = (BOLT_LIM * BOLT_LIM) * nobjs; + else + u.unblind_telepat_range = -1; +} + /* Updated to use the extrinsic and blocked fields. */ void -setworn(obj, mask) -register struct obj *obj; -long mask; +setworn(struct obj *obj, long mask) { - register const struct worn *wp; - register struct obj *oobj; - register int p; + const struct worn *wp; + struct obj *oobj; + int p; if ((mask & (W_ARM | I_SPECIAL)) == (W_ARM | I_SPECIAL)) { /* restoring saved game; no properties are conferred via skin */ uskin = obj; /* assert( !uarm ); */ } else { - if ((mask & W_ARMOR)) - u.uroleplay.nudist = FALSE; - for (wp = worn; wp->w_mask; wp++) + for (wp = worn; wp->w_mask; wp++) { if (wp->w_mask & mask) { oobj = *(wp->w_obj); if (oobj && !(oobj->owornmask & wp->w_mask)) - impossible("Setworn: mask = %ld.", wp->w_mask); + impossible("Setworn: mask=0x%08lx.", wp->w_mask); if (oobj) { if (u.twoweap && (oobj->owornmask & (W_WEP | W_SWAPWEP))) - u.twoweap = 0; + set_twoweap(FALSE); /* u.twoweap = FALSE */ oobj->owornmask &= ~wp->w_mask; if (wp->w_mask & ~(W_SWAPWEP | W_QUIVER)) { /* leave as "x = x y", here and below, for broken @@ -74,6 +96,10 @@ long mask; p = objects[oobj->otyp].oc_oprop; u.uprops[p].extrinsic = u.uprops[p].extrinsic & ~wp->w_mask; + /* if the hero removed an extrinsic-granting item, + nearby monsters will notice and attempt attacks of + that type again */ + monstunseesu_prop(p); if ((p = w_blocks(oobj, mask)) != 0) u.uprops[p].blocked &= ~wp->w_mask; if (oobj->oartifact) @@ -105,45 +131,79 @@ long mask; } } } + } + if (obj && (obj->owornmask & W_ARMOR) != 0L) + u.uroleplay.nudist = FALSE; + /* tux -> tuxedo -> "monkey suit" -> monk's suit */ + iflags.tux_penalty = (uarm && Role_if(PM_MONK) && gu.urole.spelarmr); } + if ((flags.weaponstatus && (mask & W_WEP) != 0L) + || (flags.armorstatus && (mask & W_ARMOR) != 0L)) + disp.botl = TRUE; update_inventory(); + recalc_telepat_range(); } /* called e.g. when obj is destroyed */ /* Updated to use the extrinsic and blocked fields. */ void -setnotworn(obj) -register struct obj *obj; +setnotworn(struct obj *obj) { - register const struct worn *wp; - register int p; + const struct worn *wp; + int p; + long unworn = 0L; if (!obj) return; - if (obj == uwep || obj == uswapwep) - u.twoweap = 0; + if (u.twoweap && (obj == uwep || obj == uswapwep)) + set_twoweap(FALSE); /* u.twoweap = FALSE */ for (wp = worn; wp->w_mask; wp++) if (obj == *(wp->w_obj)) { /* in case wearing or removal is in progress or removal is pending (via 'A' command for multiple items) */ cancel_doff(obj, wp->w_mask); - *(wp->w_obj) = 0; + *(wp->w_obj) = (struct obj *) 0; + unworn |= wp->w_mask; p = objects[obj->otyp].oc_oprop; u.uprops[p].extrinsic = u.uprops[p].extrinsic & ~wp->w_mask; + monstunseesu_prop(p); /* remove this extrinsic from seenres */ obj->owornmask &= ~wp->w_mask; if (obj->oartifact) set_artifact_intrinsic(obj, 0, wp->w_mask); if ((p = w_blocks(obj, wp->w_mask)) != 0) u.uprops[p].blocked &= ~wp->w_mask; } + if (!uarm) + iflags.tux_penalty = FALSE; + if ((flags.weaponstatus && (unworn & W_WEP) != 0L) + || (flags.armorstatus && (unworn & W_ARMOR) != 0L)) + disp.botl = TRUE; update_inventory(); + recalc_telepat_range(); +} + +/* called when saving with FREEING flag set has just discarded inventory */ +void +allunworn(void) +{ + const struct worn *wp; + + u.twoweap = 0; /* uwep and uswapwep are going away */ + /* remove stale pointers; called after the objects have been freed + (without first being unworn) while saving invent during game save; + note: uball and uchain might not be freed yet but we clear them + here anyway (savegamestate() and its callers deal with them) */ + for (wp = worn; wp->w_mask; wp++) { + /* object is already gone so we don't/can't update is owornmask */ + *(wp->w_obj) = (struct obj *) 0; + } } -/* return item worn in slot indiciated by wornmask; needed by poly_obj() */ + +/* return item worn in slot indicated by wornmask; needed by poly_obj() */ struct obj * -wearmask_to_obj(wornmask) -long wornmask; +wearmask_to_obj(long wornmask) { const struct worn *wp; @@ -153,10 +213,73 @@ long wornmask; return (struct obj *) 0; } +/* convert an armor wornmask to corresponding category */ +int +wornmask_to_armcat(long mask) +{ + int cat = 0; + + switch (mask & W_ARMOR) { + case W_ARM: + cat = ARM_SUIT; + break; + case W_ARMC: + cat = ARM_CLOAK; + break; + case W_ARMH: + cat = ARM_HELM; + break; + case W_ARMS: + cat = ARM_SHIELD; + break; + case W_ARMG: + cat = ARM_GLOVES; + break; + case W_ARMF: + cat = ARM_BOOTS; + break; + case W_ARMU: + cat = ARM_SHIRT; + break; + } + return cat; +} + +/* convert an armor category to corresponding wornmask */ +long +armcat_to_wornmask(int cat) +{ + long mask = 0L; + + switch (cat) { + case ARM_SUIT: + mask = W_ARM; + break; + case ARM_CLOAK: + mask = W_ARMC; + break; + case ARM_HELM: + mask = W_ARMH; + break; + case ARM_SHIELD: + mask = W_ARMS; + break; + case ARM_GLOVES: + mask = W_ARMG; + break; + case ARM_BOOTS: + mask = W_ARMF; + break; + case ARM_SHIRT: + mask = W_ARMU; + break; + } + return mask; +} + /* return a bitmask of the equipment slot(s) a given item might be worn in */ long -wearslot(obj) -struct obj *obj; +wearslot(struct obj *obj) { int otyp = obj->otyp; /* practically any item can be wielded or quivered; it's up to @@ -227,13 +350,134 @@ struct obj *obj; return res; } +/* for 'sanity_check' option, called by you_sanity_check() */ +void +check_wornmask_slots(void) +{ + /* we'll skip ball and chain here--they warrant separate sanity check */ +#define IGNORE_SLOTS (W_ART | W_ARTI | W_SADDLE | W_BALL| W_CHAIN) + char whybuf[BUFSZ]; + const struct worn *wp; + struct obj *o, *otmp; + long m; + + for (wp = worn; wp->w_mask; wp++) { + m = wp->w_mask; + if ((m & IGNORE_SLOTS) != 0L && (m & ~IGNORE_SLOTS) == 0L) + continue; + if ((o = *wp->w_obj) != 0) { + whybuf[0] = '\0'; + /* slot pointer (uarm, uwep, &c) is populated; check that object + is in inventory and has the relevant owornmask bit set */ + for (otmp = gi.invent; otmp; otmp = otmp->nobj) + if (otmp == o) + break; + if (!otmp) + Sprintf(whybuf, "%s (%s) not found in invent", + wp->w_what, fmt_ptr(o)); + else if ((o->owornmask & m) == 0L) + Sprintf(whybuf, "%s bit not set in owornmask [0x%08lx]", + wp->w_what, o->owornmask); + else if ((o->owornmask & ~(m | IGNORE_SLOTS)) != 0L) + Sprintf(whybuf, "%s wrong bit set in owornmask [0x%08lx]", + wp->w_what, o->owornmask); + if (whybuf[0]) + impossible("Worn-slot insanity: %s.", whybuf); + } /* o != NULL */ + + /* check whether any item other than the one in the slot pointer + claims to be worn/wielded in this slot; make this test whether + 'o' is Null or not; [sanity_check_worn(mkobj.c) for object by + object checking will most likely have already caught this] */ + for (otmp = gi.invent; otmp; otmp = otmp->nobj) { + if (otmp != o && (otmp->owornmask & m) != 0L + /* embedded scales owornmask is W_ARM|I_SPECIAL so would + give a false complaint about item other than uarm having + W_ARM bit set if we didn't screen it out here */ + && (m != W_ARM || otmp != uskin + || (otmp->owornmask & I_SPECIAL) == 0L)) { + Sprintf(whybuf, "%s [0x%08lx] has %s mask 0x%08lx bit set", + simpleonames(otmp), otmp->owornmask, wp->w_what, m); + impossible("Worn-slot insanity: %s.", whybuf); + } + } + } /* for wp in worn[] */ + +#ifdef EXTRA_SANITY_CHECKS + if (uskin) { + const char *what = "embedded scales"; + + o = uskin; + m = W_ARM | I_SPECIAL; + whybuf[0] = '\0'; + for (otmp = gi.invent; otmp; otmp = otmp->nobj) + if (otmp == o) + break; + if (!otmp) + Sprintf(whybuf, "%s (%s) not found in invent", + what, fmt_ptr(o)); + else if ((o->owornmask & m) != m) + Sprintf(whybuf, "%s bits not set in owornmask [0x%08lx]", + what, o->owornmask); + else if ((o->owornmask & ~(m | IGNORE_SLOTS)) != 0L) + Sprintf(whybuf, "%s wrong bit set in owornmask [0x%08lx]", + what, o->owornmask); + else if (!Is_dragon_scales(o)) + Sprintf(whybuf, "%s (%s) %s not dragon scales", + what, simpleonames(o), otense(o, "are")); + else if (Dragon_scales_to_pm(o) != &mons[u.umonnum]) + Sprintf(whybuf, "%s, hero is not %s", + what, an(mons[u.umonnum].pmnames[NEUTRAL])); + if (whybuf[0]) + impossible("Worn-slot insanity: %s.", whybuf); + } /* uskin */ +#endif /* EXTRA_SANITY_CHECKS */ + +#ifdef EXTRA_SANITY_CHECKS + /* dual wielding: not a slot but lots of things to verify */ + if (u.twoweap) { + const char *why = NULL; + + if (!uwep || !uswapwep) { + Sprintf(whybuf, "without %s%s%s", + !uwep ? "uwep" : "", + (!uwep && !uswapwep) ? " and without " : "", + !uswapwep ? "uswapwep" : ""); + why = whybuf; + } else if (uarms) + why = "while wearing shield"; + else if (uwep->oclass != WEAPON_CLASS && !is_weptool(uwep)) + why = "uwep is not a weapon"; + else if (is_launcher(uwep) || is_ammo(uwep) || is_missile(uwep)) + why = "uwep is not a melee weapon"; + else if (bimanual(uwep)) + why = "uwep is two-handed"; + else if (uswapwep->oclass != WEAPON_CLASS && !is_weptool(uswapwep)) + why = "uswapwep is not a weapon"; + else if (is_launcher(uswapwep) || is_ammo(uswapwep) + || is_missile(uswapwep)) + why = "uswapwep is not a melee weapon"; + else if (bimanual(uswapwep)) + why = "uswapwep is two-handed"; + else if (!could_twoweap(gy.youmonst.data)) + why = "without two weapon attacks"; + + if (why) + impossible("Two-weapon insanity: %s.", why); + } +#endif /* EXTRA_SANITY_CHECKS */ + return; +#undef IGNORE_SLOTS +} /* check_wornmask_slots() */ + void -mon_set_minvis(mon) -struct monst *mon; +mon_set_minvis( + struct monst *mon, + boolean cursed_potion) { - mon->perminvis = 1; + mon->perminvis = !cursed_potion ? 1 : 0; if (!mon->invis_blkd) { - mon->minvis = 1; + mon->minvis = mon->perminvis; newsym(mon->mx, mon->my); /* make it disappear */ if (mon->wormno) see_wsegs(mon); /* and any tail too */ @@ -241,19 +485,19 @@ struct monst *mon; } void -mon_adjust_speed(mon, adjust, obj) -struct monst *mon; -int adjust; /* positive => increase speed, negative => decrease */ -struct obj *obj; /* item to make known if effect can be seen */ +mon_adjust_speed( + struct monst *mon, + int adjust, /* positive => increase speed, negative => decrease */ + struct obj *obj) /* item to make known if effect can be seen */ { struct obj *otmp; - boolean give_msg = !in_mklev, petrify = FALSE; + boolean give_msg = !gi.in_mklev, petrify = FALSE; unsigned int oldspeed = mon->mspeed; switch (adjust) { case 2: mon->permspeed = MFAST; - give_msg = FALSE; /* special case monster creation */ + give_msg = FALSE; /* special-case monster creation */ break; case 1: if (mon->permspeed == MSLOW) @@ -305,11 +549,13 @@ struct obj *obj; /* item to make known if effect can be seen */ /* mimic the player's petrification countdown; "slowing down" even if fast movement rate retained via worn speed boots */ if (flags.verbose) - pline("%s is slowing down.", Monnam(mon)); + pline_mon(mon, "%s is slowing down.", Monnam(mon)); } else if (adjust > 0 || mon->mspeed == MFAST) - pline("%s is suddenly moving %sfaster.", Monnam(mon), howmuch); + pline_mon(mon, "%s is suddenly moving %sfaster.", + Monnam(mon), howmuch); else - pline("%s seems to be moving %sslower.", Monnam(mon), howmuch); + pline_mon(mon, "%s seems to be moving %sslower.", + Monnam(mon), howmuch); /* might discover an object if we see the speed change happen */ if (obj != 0) @@ -317,39 +563,53 @@ struct obj *obj; /* item to make known if effect can be seen */ } } -/* armor put on or taken off; might be magical variety - [TODO: rename to 'update_mon_extrinsics()' and change all callers...] */ +/* alchemy smock confers two properties, poison and acid resistance + but objects[ALCHEMY_SMOCK].oc_oprop can only describe one of them; + if it is poison resistance, alternate property is acid resistance; + if someone changes it to acid resistance, alt becomes poison resist; + if someone changes it to hallucination resistance, all bets are off + [TODO: handle alternate properties conferred by dragon scales/mail] */ +#define altprop(o) \ + (((o)->otyp == ALCHEMY_SMOCK) \ + ? (POISON_RES + ACID_RES - objects[(o)->otyp].oc_oprop) \ + : 0) + +/* armor put on or taken off; might be magical variety */ void -update_mon_intrinsics(mon, obj, on, silently) -struct monst *mon; -struct obj *obj; -boolean on, silently; +update_mon_extrinsics( + struct monst *mon, + struct obj *obj, /* armor being worn or taken off */ + boolean on, + boolean silently) { int unseen; uchar mask; struct obj *otmp; - int which = (int) objects[obj->otyp].oc_oprop; + int which = (int) objects[obj->otyp].oc_oprop, + altwhich = altprop(obj); unseen = !canseemon(mon); - if (!which) + if (!which && !altwhich) goto maybe_blocks; + again: if (on) { switch (which) { case INVIS: mon->minvis = !mon->invis_blkd; break; case FAST: { - boolean save_in_mklev = in_mklev; + boolean save_in_mklev = gi.in_mklev; if (silently) - in_mklev = TRUE; + gi.in_mklev = TRUE; mon_adjust_speed(mon, 0, obj); - in_mklev = save_in_mklev; + gi.in_mklev = save_in_mklev; break; } /* properties handled elsewhere */ case ANTIMAGIC: case REFLECTING: + case PROTECTION: break; /* properties which have no effect for monsters */ case CLAIRVOYANT: @@ -358,20 +618,16 @@ boolean on, silently; break; /* properties which should have an effect but aren't implemented */ case LEVITATION: + case FLYING: case WWALKING: break; /* properties which maybe should have an effect but don't */ case DISPLACED: case FUMBLING: case JUMPING: - case PROTECTION: break; default: - if (which <= 8) { /* 1 thru 8 correspond to MR_xxx mask values */ - /* FIRE,COLD,SLEEP,DISINT,SHOCK,POISON,ACID,STONE */ - mask = (uchar) (1 << (which - 1)); - mon->mextrinsics |= (unsigned short) mask; - } + mon->mextrinsics |= (unsigned short) res_to_mr(which); break; } } else { /* off */ @@ -380,11 +636,11 @@ boolean on, silently; mon->minvis = mon->perminvis; break; case FAST: { - boolean save_in_mklev = in_mklev; + boolean save_in_mklev = gi.in_mklev; if (silently) - in_mklev = TRUE; + gi.in_mklev = TRUE; mon_adjust_speed(mon, 0, obj); - in_mklev = save_in_mklev; + gi.in_mklev = save_in_mklev; break; } case FIRE_RES: @@ -395,14 +651,30 @@ boolean on, silently; case POISON_RES: case ACID_RES: case STONE_RES: - mask = (uchar) (1 << (which - 1)); - /* update monster's extrinsics (for worn objects only; - 'obj' itself might still be worn or already unworn) */ - for (otmp = mon->minvent; otmp; otmp = otmp->nobj) - if (otmp != obj - && otmp->owornmask - && (int) objects[otmp->otyp].oc_oprop == which) + /* + * Update monster's extrinsics (for worn objects only; + * 'obj' itself might still be worn or already unworn). + * + * If an alchemy smock is being taken off, this code will + * be run twice (via 'goto again') and other worn gear + * gets tested for conferring poison resistance on the + * first pass and acid resistance on the second. + * + * If some other item is being taken off, there will be + * only one pass but a worn alchemy smock will be an + * alternate source for either of those two resistances. + */ + mask = res_to_mr(which); + for (otmp = mon->minvent; otmp; otmp = otmp->nobj) { + if (otmp == obj || !otmp->owornmask) + continue; + if ((int) objects[otmp->otyp].oc_oprop == which) + break; + /* check whether 'otmp' confers target property as an extra + one rather than as the one specified for it in objects[] */ + if (altprop(otmp) == which) break; + } if (!otmp) mon->mextrinsics &= ~((unsigned short) mask); break; @@ -411,6 +683,13 @@ boolean on, silently; } } + /* worn alchemy smock/apron confers both poison resistance and acid + resistance to the hero so do likewise for monster who wears one */ + if (altwhich && which != altwhich) { + which = altwhich; + goto again; + } + maybe_blocks: /* obj->owornmask has been cleared by this point, so we can't use it. However, since monsters don't wield armor, we don't have to guard @@ -432,19 +711,27 @@ boolean on, silently; newsym(mon->mx, mon->my); } +#undef altprop + int -find_mac(mon) -register struct monst *mon; +find_mac(struct monst *mon) { - register struct obj *obj; + struct obj *obj; int base = mon->data->ac; long mwflags = mon->misc_worn_check; for (obj = mon->minvent; obj; obj = obj->nobj) { - if (obj->owornmask & mwflags) - base -= ARM_BONUS(obj); - /* since ARM_BONUS is positive, subtracting it increases AC */ + if (obj->owornmask & mwflags) { + if (obj->otyp == AMULET_OF_GUARDING) + base -= 2; /* fixed amount, not impacted by erosion */ + else + base -= ARM_BONUS(obj); + /* since ARM_BONUS is positive, subtracting it increases AC */ + } } + /* same cap as for hero [find_ac(do_wear.c)] */ + if (abs(base) > AC_MAX) + base = sgn(base) * AC_MAX; return base; } @@ -467,10 +754,10 @@ register struct monst *mon; * already worn body armor is too obviously buggy... */ void -m_dowear(mon, creation) -register struct monst *mon; -boolean creation; +m_dowear(struct monst *mon, boolean creation) { + boolean can_wear_armor; + #define RACE_EXCEPTION TRUE /* Note the restrictions here are the same as in dowear in do_wear.c * except for the additional restriction on intelligence. (Players @@ -486,12 +773,15 @@ boolean creation; return; m_dowear_type(mon, W_AMUL, creation, FALSE); + can_wear_armor = !cantweararm(mon->data); /* for suit, cloak, shirt */ /* can't put on shirt if already wearing suit */ - if (!cantweararm(mon->data) && !(mon->misc_worn_check & W_ARM)) + if (can_wear_armor && !(mon->misc_worn_check & W_ARM)) m_dowear_type(mon, W_ARMU, creation, FALSE); - /* treating small as a special case allows - hobbits, gnomes, and kobolds to wear cloaks */ - if (!cantweararm(mon->data) || mon->data->msize == MZ_SMALL) + /* WrappingAllowed() makes any size between small and huge eligible; + treating small as a special case allows hobbits, gnomes, and + kobolds to wear all cloaks; large and huge allows giants and such + to wear mummy wrappings but not other cloaks */ + if (can_wear_armor || WrappingAllowed(mon->data)) m_dowear_type(mon, W_ARMC, creation, FALSE); m_dowear_type(mon, W_ARMH, creation, FALSE); if (!MON_WEP(mon) || !bimanual(MON_WEP(mon))) @@ -499,22 +789,24 @@ boolean creation; m_dowear_type(mon, W_ARMG, creation, FALSE); if (!slithy(mon->data) && mon->data->mlet != S_CENTAUR) m_dowear_type(mon, W_ARMF, creation, FALSE); - if (!cantweararm(mon->data)) + if (can_wear_armor) m_dowear_type(mon, W_ARM, creation, FALSE); else m_dowear_type(mon, W_ARM, creation, RACE_EXCEPTION); } -STATIC_OVL void -m_dowear_type(mon, flag, creation, racialexception) -struct monst *mon; -long flag; -boolean creation; -boolean racialexception; +staticfn void +m_dowear_type( + struct monst *mon, + long flag, /* wornmask value */ + boolean creation, /* no wear messages when mon is being created */ + boolean racialexception) /* small monsters that are allowed for player + * races (gnomes) can wear suits */ { struct obj *old, *best, *obj; + long oldmask = 0L; int m_delay = 0; - int unseen = !canseemon(mon); + int sawmon = canseemon(mon), sawloc = cansee(mon->mx, mon->my); boolean autocurse; char nambuf[BUFSZ]; @@ -527,8 +819,8 @@ boolean racialexception; old = which_armor(mon, flag); if (old && old->cursed) return; - if (old && flag == W_AMUL) - return; /* no such thing as better amulets */ + if (old && flag == W_AMUL && old->otyp != AMULET_OF_GUARDING) + return; /* no amulet better than life-saving or reflection */ best = old; for (obj = mon->minvent; obj; obj = obj->nobj) { @@ -536,10 +828,18 @@ boolean racialexception; case W_AMUL: if (obj->oclass != AMULET_CLASS || (obj->otyp != AMULET_OF_LIFE_SAVING - && obj->otyp != AMULET_OF_REFLECTION)) + && obj->otyp != AMULET_OF_REFLECTION + && obj->otyp != AMULET_OF_GUARDING)) continue; - best = obj; - goto outer_break; /* no such thing as better amulets */ + /* for 'best' to be non-Null, it must be an amulet of guarding; + life-saving and reflection don't get here due to early return + and other amulets of guarding can't be any better */ + if (!best || obj->otyp != AMULET_OF_GUARDING) { + best = obj; + if (best->otyp != AMULET_OF_GUARDING) + goto outer_break; /* life-saving or reflection; use it */ + } + continue; /* skip post-switch armor handling */ case W_ARMU: if (!is_shirt(obj)) continue; @@ -547,6 +847,14 @@ boolean racialexception; case W_ARMC: if (!is_cloak(obj)) continue; + /* mummy wrapping is only cloak allowed when bigger than human */ + if (mon->data->msize > MZ_HUMAN && obj->otyp != MUMMY_WRAPPING) + continue; + /* avoid mummy wrapping if it will allow hero to see mon (unless + this is a new mummy; an invisible one is feasible via ^G) */ + if (mon->minvis && w_blocks(obj, W_ARMC) == INVIS + && !See_invisible && !creation) + continue; break; case W_ARMH: if (!is_helmet(obj)) @@ -593,7 +901,7 @@ boolean racialexception; continue; best = obj; } -outer_break: + outer_break: if (!best || best == old) return; @@ -606,21 +914,40 @@ boolean racialexception; m_delay += 2; /* when upgrading a piece of armor, account for time spent taking off current one */ - if (old) + if (old) { m_delay += objects[old->otyp].oc_delay; - if (old) /* do this first to avoid "(being worn)" */ - old->owornmask = 0L; + oldmask = old->owornmask; /* needed later by artifact_light() */ + old->owornmask = 0L; /* avoid doname() showing "(being worn)" */ + } + if (!creation) { - if (canseemon(mon)) { - char buf[BUFSZ]; + if (sawmon) { + char buf[BUFSZ], oldarm[BUFSZ], newarm[BUFSZ + sizeof "another "]; - if (old) - Sprintf(buf, " removes %s and", distant_name(old, doname)); - else - buf[0] = '\0'; - pline("%s%s puts on %s.", Monnam(mon), buf, - distant_name(best, doname)); + /* " [removes and ]puts on ." + uses accessory verbs for armor but we can live with that */ + if (old) { + Strcpy(oldarm, distant_name(old, doname)); + Snprintf(buf, sizeof buf, " removes %s and", oldarm); + } else { + buf[0] = oldarm[0] = '\0'; + } + Strcpy(newarm, distant_name(best, doname)); + /* a monster will swap an item of the same type as the one it + is replacing when the enchantment is better; + if newarm and oldarm have identical descriptions, substitute + "another " for "a|an " */ + if (!strcmpi(newarm, oldarm)) { + /* size of newarm[] has been overallocated to guarantee + enough room to insert "another " */ + if (!strncmpi(newarm, "a ", 2)) + (void) strsubst(newarm, "a ", "another "); + else if (!strncmpi(newarm, "an ", 3)) + (void) strsubst(newarm, "an ", "another "); + newarm[BUFSZ - 1] = '\0'; + } + pline_mon(mon, "%s%s puts on %s.", Monnam(mon), buf, newarm); if (autocurse) pline("%s %s %s %s for a moment.", s_suffix(Monnam(mon)), simpleonames(best), otense(best, "glow"), @@ -631,30 +958,54 @@ boolean racialexception; if (mon->mfrozen) mon->mcanmove = 0; } - if (old) - update_mon_intrinsics(mon, old, FALSE, creation); + if (old) { + update_mon_extrinsics(mon, old, FALSE, creation); + + /* owornmask was cleared above but artifact_light() expects it */ + old->owornmask = oldmask; + if (old->lamplit && artifact_light(old)) + end_burn(old, FALSE); + old->owornmask = 0L; + } mon->misc_worn_check |= flag; best->owornmask |= flag; if (autocurse) curse(best); - update_mon_intrinsics(mon, best, TRUE, creation); - /* if couldn't see it but now can, or vice versa, */ - if (!creation && (unseen ^ !canseemon(mon))) { + if (artifact_light(best) && !best->lamplit) { + begin_burn(best, FALSE); + vision_recalc(1); + if (!creation && best->lamplit && cansee(mon->mx, mon->my)) { + const char *adesc = arti_light_description(best); + + if (sawmon) /* could already see monster */ + pline("%s %s to shine %s.", Yname2(best), + otense(best, "begin"), adesc); + else if (canseemon(mon)) /* didn't see it until new light */ + pline("%s %s shining %s.", Yname2(best), + otense(best, "are"), adesc); + else if (sawloc) /* saw location but not invisible monster */ + pline("%s begins to shine %s.", Something, adesc); + else /* didn't see location until new light */ + pline("%s is shining %s.", Something, adesc); + } + } + update_mon_extrinsics(mon, best, TRUE, creation); + /* if couldn't see it but now can, or vice versa */ + if (!creation && (sawmon ^ canseemon(mon))) { if (mon->minvis && !See_invisible) { pline("Suddenly you cannot see %s.", nambuf); makeknown(best->otyp); - } /* else if (!mon->minvis) pline("%s suddenly appears!", - Amonnam(mon)); */ + /* } else if (!mon->minvis) { + * pline("%s suddenly appears!", Amonnam(mon)); */ + } } } #undef RACE_EXCEPTION struct obj * -which_armor(mon, flag) -struct monst *mon; -long flag; +which_armor(struct monst *mon, long flag) { - if (mon == &youmonst) { + if (mon == &gy.youmonst) { switch (flag) { case W_ARM: return uarm; @@ -675,7 +1026,7 @@ long flag; return 0; } } else { - register struct obj *obj; + struct obj *obj; for (obj = mon->minvent; obj; obj = obj->nobj) if (obj->owornmask & flag) @@ -685,67 +1036,58 @@ long flag; } /* remove an item of armor and then drop it */ -STATIC_OVL void -m_lose_armor(mon, obj) -struct monst *mon; -struct obj *obj; +staticfn void +m_lose_armor( + struct monst *mon, + struct obj *obj, + boolean polyspot) { - mon->misc_worn_check &= ~obj->owornmask; - if (obj->owornmask) - update_mon_intrinsics(mon, obj, FALSE, FALSE); - obj->owornmask = 0L; - - obj_extract_self(obj); + extract_from_minvent(mon, obj, TRUE, FALSE); place_object(obj, mon->mx, mon->my); + if (polyspot) + bypass_obj(obj); /* call stackobj() if we ever drop anything that can merge */ newsym(mon->mx, mon->my); } -/* all objects with their bypass bit set should now be reset to normal */ +/* clear bypass bits for an object chain, plus contents if applicable */ +staticfn void +clear_bypass(struct obj *objchn) +{ + struct obj *o; + + for (o = objchn; o; o = o->nobj) { + o->bypass = 0; + if (Has_contents(o)) + clear_bypass(o->cobj); + } +} + +/* all objects with their bypass bit set should now be reset to normal; + this can be a relatively expensive operation so is only called if + svc.context.bypasses is set */ void -clear_bypasses() +clear_bypasses(void) { - struct obj *otmp, *nobj; struct monst *mtmp; /* * 'Object' bypass is also used for one monster function: * polymorph control of long worms. Activated via setting - * context.bypasses even if no specific object has been + * svc.context.bypasses even if no specific object has been * bypassed. */ - for (otmp = fobj; otmp; otmp = nobj) { - nobj = otmp->nobj; - if (otmp->bypass) { - otmp->bypass = 0; - - /* bypass will have inhibited any stacking, but since it's - * used for polymorph handling, the objects here probably - * have been transformed and won't be stacked in the usual - * manner afterwards; so don't bother with this. - * [Changing the fobj chain mid-traversal would also be risky.] - */ -#if 0 - if (objects[otmp->otyp].oc_merge) { - xchar ox, oy; - - (void) get_obj_location(otmp, &ox, &oy, 0); - stack_object(otmp); - newsym(ox, oy); - } -#endif /*0*/ - } - } - for (otmp = invent; otmp; otmp = otmp->nobj) - otmp->bypass = 0; - for (otmp = migrating_objs; otmp; otmp = otmp->nobj) - otmp->bypass = 0; + clear_bypass(fobj); + clear_bypass(gi.invent); + clear_bypass(gm.migrating_objs); + clear_bypass(svl.level.buriedobjlist); + clear_bypass(gb.billobjs); + clear_bypass(go.objs_deleted); for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { if (DEADMONSTER(mtmp)) continue; - for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj) - otmp->bypass = 0; + clear_bypass(mtmp->minvent); /* long worm created by polymorph has mon->mextra->mcorpsenm set to PM_LONG_WORM to flag it as not being subject to further polymorph (so polymorph zap won't hit monster to transform it @@ -754,32 +1096,40 @@ clear_bypasses() if (mtmp->data == &mons[PM_LONG_WORM] && has_mcorpsenm(mtmp)) MCORPSENM(mtmp) = NON_PM; } - for (mtmp = migrating_mons; mtmp; mtmp = mtmp->nmon) { - for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj) - otmp->bypass = 0; + for (mtmp = gm.migrating_mons; mtmp; mtmp = mtmp->nmon) { + clear_bypass(mtmp->minvent); /* no MCORPSENM(mtmp)==PM_LONG_WORM check here; long worms can't be just created by polymorph and migrating at the same time */ } - /* billobjs and mydogs chains don't matter here */ - context.bypasses = FALSE; + /* this is a no-op since mydogs is only non-Null during level change or + final ascension and we aren't called at those times, but be thorough */ + for (mtmp = gm.mydogs; mtmp; mtmp = mtmp->nmon) + clear_bypass(mtmp->minvent); + /* ball and chain can be "floating", not on any object chain (when + hero is swallowed by an engulfing monster, for instance) */ + if (uball) + uball->bypass = 0; + if (uchain) + uchain->bypass = 0; + + svc.context.bypasses = FALSE; } void -bypass_obj(obj) -struct obj *obj; +bypass_obj(struct obj *obj) { obj->bypass = 1; - context.bypasses = TRUE; + svc.context.bypasses = TRUE; } /* set or clear the bypass bit in a list of objects */ void -bypass_objlist(objchain, on) -struct obj *objchain; -boolean on; /* TRUE => set, FALSE => clear */ +bypass_objlist( + struct obj *objchain, + boolean on) /* TRUE => set, FALSE => clear */ { if (on && objchain) - context.bypasses = TRUE; + svc.context.bypasses = TRUE; while (objchain) { objchain->bypass = on ? 1 : 0; objchain = objchain->nobj; @@ -789,8 +1139,7 @@ boolean on; /* TRUE => set, FALSE => clear */ /* return the first object without its bypass bit set; set that bit before returning so that successive calls will find further objects */ struct obj * -nxt_unbypassed_obj(objchain) -struct obj *objchain; +nxt_unbypassed_obj(struct obj *objchain) { while (objchain) { if (!objchain->bypass) { @@ -807,9 +1156,7 @@ struct obj *objchain; there's an added complication that the array may have stale pointers for deleted objects (see Multiple-Drop case in askchain(invent.c)) */ struct obj * -nxt_unbypassed_loot(lootarray, listhead) -Loot *lootarray; -struct obj *listhead; +nxt_unbypassed_loot(Loot *lootarray, struct obj *listhead) { struct obj *o, *obj; @@ -827,40 +1174,44 @@ struct obj *listhead; } void -mon_break_armor(mon, polyspot) -struct monst *mon; -boolean polyspot; +mon_break_armor(struct monst *mon, boolean polyspot) { - register struct obj *otmp; + struct obj *otmp; struct permonst *mdat = mon->data; - boolean vis = cansee(mon->mx, mon->my); - boolean handless_or_tiny = (nohands(mdat) || verysmall(mdat)); + boolean vis = cansee(mon->mx, mon->my), + handless_or_tiny = (nohands(mdat) || verysmall(mdat)), + noride = FALSE; const char *pronoun = mhim(mon), *ppronoun = mhis(mon); if (breakarm(mdat)) { if ((otmp = which_armor(mon, W_ARM)) != 0) { if ((Is_dragon_scales(otmp) && mdat == Dragon_scales_to_pm(otmp)) - || (Is_dragon_mail(otmp) && mdat == Dragon_mail_to_pm(otmp))) + || (Is_dragon_mail(otmp) && mdat == Dragon_mail_to_pm(otmp))) { ; /* no message here; "the dragon merges with his scaly armor" is odd and the monster's previous form is already gone */ - else if (vis) - pline("%s breaks out of %s armor!", Monnam(mon), ppronoun); - else - You_hear("a cracking sound."); + } else { + Soundeffect(se_cracking_sound, 100); + if (vis) + pline_mon(mon, "%s breaks out of %s armor!", + Monnam(mon), ppronoun); + else + You_hear("a cracking sound."); + } m_useup(mon, otmp); } - if ((otmp = which_armor(mon, W_ARMC)) != 0) { + if ((otmp = which_armor(mon, W_ARMC)) != 0 + /* mummy wrapping adapts to small and very big sizes */ + && (otmp->otyp != MUMMY_WRAPPING || !WrappingAllowed(mdat))) { if (otmp->oartifact) { if (vis) - pline("%s %s falls off!", s_suffix(Monnam(mon)), + pline_mon(mon, "%s %s falls off!", s_suffix(Monnam(mon)), cloak_simple_name(otmp)); - if (polyspot) - bypass_obj(otmp); - m_lose_armor(mon, otmp); + m_lose_armor(mon, otmp, polyspot); } else { + Soundeffect(se_ripping_sound, 100); if (vis) - pline("%s %s tears apart!", s_suffix(Monnam(mon)), + pline_mon(mon, "%s %s tears apart!", s_suffix(Monnam(mon)), cloak_simple_name(otmp)); else You_hear("a ripping sound."); @@ -869,68 +1220,68 @@ boolean polyspot; } if ((otmp = which_armor(mon, W_ARMU)) != 0) { if (vis) - pline("%s shirt rips to shreds!", s_suffix(Monnam(mon))); + pline_mon(mon, "%s shirt rips to shreds!", + s_suffix(Monnam(mon))); else You_hear("a ripping sound."); m_useup(mon, otmp); } } else if (sliparm(mdat)) { + /* sliparm checks whirly, noncorporeal, and small or under */ + boolean passes_thru_clothes = !(mdat->msize <= MZ_SMALL); + if ((otmp = which_armor(mon, W_ARM)) != 0) { + Soundeffect(se_thud, 50); if (vis) - pline("%s armor falls around %s!", s_suffix(Monnam(mon)), - pronoun); + pline_mon(mon, "%s armor falls around %s!", + s_suffix(Monnam(mon)), pronoun); else You_hear("a thud."); - if (polyspot) - bypass_obj(otmp); - m_lose_armor(mon, otmp); + m_lose_armor(mon, otmp, polyspot); } - if ((otmp = which_armor(mon, W_ARMC)) != 0) { + if ((otmp = which_armor(mon, W_ARMC)) != 0 + /* mummy wrapping adapts to small and very big sizes */ + && (otmp->otyp != MUMMY_WRAPPING || !WrappingAllowed(mdat))) { if (vis) { if (is_whirly(mon->data)) - pline("%s %s falls, unsupported!", s_suffix(Monnam(mon)), - cloak_simple_name(otmp)); + pline_mon(mon, "%s %s falls, unsupported!", + s_suffix(Monnam(mon)), cloak_simple_name(otmp)); else - pline("%s shrinks out of %s %s!", Monnam(mon), ppronoun, - cloak_simple_name(otmp)); + pline_mon(mon, "%s shrinks out of %s %s!", + Monnam(mon), ppronoun, + cloak_simple_name(otmp)); } - if (polyspot) - bypass_obj(otmp); - m_lose_armor(mon, otmp); + m_lose_armor(mon, otmp, polyspot); } if ((otmp = which_armor(mon, W_ARMU)) != 0) { if (vis) { - if (sliparm(mon->data)) - pline("%s seeps right through %s shirt!", Monnam(mon), - ppronoun); + if (passes_thru_clothes) + pline_mon(mon, "%s seeps right through %s shirt!", + Monnam(mon), ppronoun); else - pline("%s becomes much too small for %s shirt!", + pline_mon(mon, "%s becomes much too small for %s shirt!", Monnam(mon), ppronoun); } - if (polyspot) - bypass_obj(otmp); - m_lose_armor(mon, otmp); + m_lose_armor(mon, otmp, polyspot); } } if (handless_or_tiny) { /* [caller needs to handle weapon checks] */ if ((otmp = which_armor(mon, W_ARMG)) != 0) { if (vis) - pline("%s drops %s gloves%s!", Monnam(mon), ppronoun, - MON_WEP(mon) ? " and weapon" : ""); - if (polyspot) - bypass_obj(otmp); - m_lose_armor(mon, otmp); + pline_mon(mon, "%s drops %s gloves%s!", + Monnam(mon), ppronoun, + MON_WEP(mon) ? " and weapon" : ""); + m_lose_armor(mon, otmp, polyspot); } if ((otmp = which_armor(mon, W_ARMS)) != 0) { + Soundeffect(se_clank, 50); if (vis) - pline("%s can no longer hold %s shield!", Monnam(mon), - ppronoun); + pline_mon(mon, "%s can no longer hold %s shield!", + Monnam(mon), ppronoun); else You_hear("a clank."); - if (polyspot) - bypass_obj(otmp); - m_lose_armor(mon, otmp); + m_lose_armor(mon, otmp, polyspot); } } if (handless_or_tiny || has_horns(mdat)) { @@ -938,47 +1289,44 @@ boolean polyspot; /* flimsy test for horns matches polyself handling */ && (handless_or_tiny || !is_flimsy(otmp))) { if (vis) - pline("%s helmet falls to the %s!", s_suffix(Monnam(mon)), - surface(mon->mx, mon->my)); + pline_mon(mon, "%s helmet falls to the %s!", + s_suffix(Monnam(mon)), surface(mon->mx, mon->my)); else You_hear("a clank."); - if (polyspot) - bypass_obj(otmp); - m_lose_armor(mon, otmp); + m_lose_armor(mon, otmp, polyspot); } } if (handless_or_tiny || slithy(mdat) || mdat->mlet == S_CENTAUR) { if ((otmp = which_armor(mon, W_ARMF)) != 0) { if (vis) { if (is_whirly(mon->data)) - pline("%s boots fall away!", s_suffix(Monnam(mon))); + pline_mon(mon, "%s boots fall away!", + s_suffix(Monnam(mon))); else - pline("%s boots %s off %s feet!", s_suffix(Monnam(mon)), + pline_mon(mon, "%s boots %s off %s feet!", + s_suffix(Monnam(mon)), verysmall(mdat) ? "slide" : "are pushed", ppronoun); } - if (polyspot) - bypass_obj(otmp); - m_lose_armor(mon, otmp); + m_lose_armor(mon, otmp, polyspot); } } if (!can_saddle(mon)) { if ((otmp = which_armor(mon, W_SADDLE)) != 0) { - if (polyspot) - bypass_obj(otmp); - m_lose_armor(mon, otmp); + m_lose_armor(mon, otmp, polyspot); if (vis) - pline("%s saddle falls off.", s_suffix(Monnam(mon))); + pline_mon(mon, "%s saddle falls off.", s_suffix(Monnam(mon))); } if (mon == u.usteed) - goto noride; - } else if (mon == u.usteed && !can_ride(mon)) { - noride: + noride = TRUE; + } + if (noride || (mon == u.usteed && !can_ride(mon))) { You("can no longer ride %s.", mon_nam(mon)); if (touch_petrifies(u.usteed->data) && !Stone_resistance && rnl(3)) { char buf[BUFSZ]; You("touch %s.", mon_nam(u.usteed)); - Sprintf(buf, "falling off %s", an(u.usteed->data->mname)); + Sprintf(buf, "falling off %s", + an(pmname(u.usteed->data, Mgender(u.usteed)))); instapetrify(buf); } dismount_steed(DISMOUNT_FELL); @@ -987,10 +1335,8 @@ boolean polyspot; } /* bias a monster's preferences towards armor that has special benefits. */ -STATIC_OVL int -extra_pref(mon, obj) -struct monst *mon; -struct obj *obj; +staticfn int +extra_pref(struct monst *mon, struct obj *obj) { /* currently only does speed boots, but might be expanded if monsters * get to use more armor abilities @@ -1011,9 +1357,7 @@ struct obj *obj; * -1 If the race/object combination is unacceptable. */ int -racial_exception(mon, obj) -struct monst *mon; -struct obj *obj; +racial_exception(struct monst *mon, struct obj *obj) { const struct permonst *ptr = raceptr(mon); @@ -1027,4 +1371,51 @@ struct obj *obj; return 0; } + +/* Remove an object from a monster's inventory. */ +void +extract_from_minvent( + struct monst *mon, + struct obj *obj, + boolean do_extrinsics, /* whether to call update_mon_extrinsics */ + boolean silently) /* doesn't affect all possible messages, + * just update_mon_extrinsics's */ +{ + long unwornmask = obj->owornmask; + + /* + * At its core this is just obj_extract_self(), but it also handles + * any updates that need to happen if the gear is equipped or in + * some other sort of state that needs handling. + * Note that like obj_extract_self(), this leaves obj free. + */ + + if (obj->where != OBJ_MINVENT) { + impossible("extract_from_minvent called on object not in minvent"); + return; + } + /* handle gold dragon scales/scale-mail (lit when worn) before clearing + obj->owornmask because artifact_light() expects that to be W_ARM */ + if ((unwornmask & W_ARM) != 0 && obj->lamplit && artifact_light(obj)) + end_burn(obj, FALSE); + + obj_extract_self(obj); + obj->owornmask = 0L; + if (unwornmask) { + if (!DEADMONSTER(mon) && do_extrinsics) { + update_mon_extrinsics(mon, obj, FALSE, silently); + } + mon->misc_worn_check &= ~unwornmask; + /* give monster a chance to wear other equipment on its next + move instead of waiting until it picks something up */ + check_gear_next_turn(mon); + } + obj_no_longer_held(obj); + if (unwornmask & W_WEP) { + mwepgone(mon); /* unwields and sets weapon_check to NEED_WEAPON */ + } +} + +#undef w_blocks + /*worn.c*/ diff --git a/src/write.c b/src/write.c index 34afaf61c..ba90c7d4c 100644 --- a/src/write.c +++ b/src/write.c @@ -1,24 +1,23 @@ -/* NetHack 3.6 write.c $NHDT-Date: 1573346194 2019/11/10 00:36:34 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.20 $ */ +/* NetHack 5.0 write.c $NHDT-Date: 1702023275 2023/12/08 08:14:35 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.41 $ */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" -STATIC_DCL int FDECL(cost, (struct obj *)); -STATIC_DCL boolean FDECL(label_known, (int, struct obj *)); -STATIC_DCL char *FDECL(new_book_description, (int, char *)); +staticfn int cost(struct obj *) NONNULLARG1; +staticfn int write_ok(struct obj *) NO_NNARGS; +staticfn char *new_book_description(int, char *) NONNULL NONNULLPTRS; /* - * returns basecost of a scroll or a spellbook + * returns base cost of a scroll or a spellbook */ -STATIC_OVL int -cost(otmp) -register struct obj *otmp; +staticfn int +cost(struct obj *otmp) { if (otmp->oclass == SPBOOK_CLASS) return (10 * objects[otmp->otyp].oc_level); switch (otmp->otyp) { -#ifdef MAIL +#ifdef MAIL_STRUCTURES case SCR_MAIL: return 2; #endif @@ -57,98 +56,78 @@ register struct obj *otmp; return 1000; } -/* decide whether the hero knowns a particular scroll's label; - unfortunately, we can't track things that haven't been added to - the discoveries list and aren't present in current inventory, - so some scrolls with ought to yield True will end up False */ -STATIC_OVL boolean -label_known(scrolltype, objlist) -int scrolltype; -struct obj *objlist; +/* getobj callback for object to write on */ +staticfn int +write_ok(struct obj *obj) { - struct obj *otmp; - - /* only scrolls */ - if (objects[scrolltype].oc_class != SCROLL_CLASS) - return FALSE; - /* type known implies full discovery; otherwise, - user-assigned name implies partial discovery */ - if (objects[scrolltype].oc_name_known || objects[scrolltype].oc_uname) - return TRUE; - /* check inventory, including carried containers with known contents */ - for (otmp = objlist; otmp; otmp = otmp->nobj) { - if (otmp->otyp == scrolltype && otmp->dknown) - return TRUE; - if (Has_contents(otmp) && otmp->cknown - && label_known(scrolltype, otmp->cobj)) - return TRUE; - } - /* not found */ - return FALSE; -} + if (!obj || (obj->oclass != SCROLL_CLASS && obj->oclass != SPBOOK_CLASS)) + return GETOBJ_EXCLUDE; -static NEARDATA const char write_on[] = { SCROLL_CLASS, SPBOOK_CLASS, 0 }; + if (obj->otyp == SCR_BLANK_PAPER || obj->otyp == SPE_BLANK_PAPER) + return GETOBJ_SUGGEST; + + return GETOBJ_DOWNPLAY; +} /* write -- applying a magic marker */ int -dowrite(pen) -register struct obj *pen; +dowrite(struct obj *pen) { - register struct obj *paper; + struct obj *paper; char namebuf[BUFSZ] = DUMMY, *nm, *bp; - register struct obj *new_obj; + struct obj *new_obj; int basecost, actualcost; int curseval; char qbuf[QBUFSZ]; - int first, last, i, deferred, deferralchance; + int first, last, i, deferred, deferralchance, real; boolean by_descr = FALSE; const char *typeword; + int spell_knowledge; - if (nohands(youmonst.data)) { + if (nohands(gy.youmonst.data)) { You("need hands to be able to write!"); - return 0; + return ECMD_OK; } else if (Glib) { pline("%s from your %s.", Tobjnam(pen, "slip"), fingers_or_gloves(FALSE)); dropx(pen); - return 1; + return ECMD_TIME; } /* get paper to write on */ - paper = getobj(write_on, "write on"); + paper = getobj("write on", write_ok, GETOBJ_NOFLAGS); if (!paper) - return 0; + return ECMD_CANCEL; /* can't write on a novel (unless/until it's been converted into a blank spellbook), but we want messages saying so to avoid "spellbook" */ - typeword = (paper->otyp == SPE_NOVEL) - ? "book" - : (paper->oclass == SPBOOK_CLASS) - ? "spellbook" - : "scroll"; + typeword = (paper->otyp == SPE_NOVEL) ? "book" + : (paper->oclass == SPBOOK_CLASS) ? "spellbook" + : "scroll"; if (Blind) { if (!paper->dknown) { - You("don't know if that %s is blank or not.", typeword); - return 0; + You("don't know whether that %s is blank or not.", typeword); + return ECMD_OK; } else if (paper->oclass == SPBOOK_CLASS) { /* can't write a magic book while blind */ pline("%s can't create braille text.", upstart(ysimple_name(pen))); - return 0; + return ECMD_OK; } } - paper->dknown = 1; + observe_object(paper); if (paper->otyp != SCR_BLANK_PAPER && paper->otyp != SPE_BLANK_PAPER) { pline("That %s is not blank!", typeword); exercise(A_WIS, FALSE); - return 1; + return ECMD_TIME; } + makeknown(SCR_BLANK_PAPER); /* what to write */ Sprintf(qbuf, "What type of %s do you want to write?", typeword); getlin(qbuf, namebuf); (void) mungspaces(namebuf); /* remove any excess whitespace */ if (namebuf[0] == '\033' || !namebuf[0]) - return 1; + return ECMD_TIME; nm = namebuf; if (!strncmpi(nm, "scroll ", 7)) nm += 7; @@ -158,30 +137,49 @@ register struct obj *pen; nm += 3; if ((bp = strstri(nm, " armour")) != 0) { - (void) strncpy(bp, " armor ", 7); /* won't add '\0' */ + memcpy(bp, " armor ", 7); (void) mungspaces(bp + 1); /* remove the extra space */ } - deferred = 0; /* not any scroll or book */ - deferralchance = 0; /* incremented for each oc_uname match */ - first = bases[(int) paper->oclass]; - last = bases[(int) paper->oclass + 1] - 1; + deferred = real = 0; /* not any scroll or book */ + deferralchance = 0; /* incremented for each oc_uname match */ + first = svb.bases[(int) paper->oclass]; + last = svb.bases[(int) paper->oclass + 1] - 1; + /* first loop: look for match with name/description */ for (i = first; i <= last; i++) { /* extra shufflable descr not representing a real object */ if (!OBJ_NAME(objects[i])) continue; - if (!strcmpi(OBJ_NAME(objects[i]), nm)) - goto found; + if (!strcmpi(OBJ_NAME(objects[i]), nm)) { + if (objects[i].oc_name_known + /* spellbooks can only be written by_name, so no need to + hold out for a 'better' by_descr match */ + || paper->oclass == SPBOOK_CLASS) { + goto found; + } else { + /* save item in case there are no better by_descr matches */ + real = deferred = i; + break; + } + } + if (!strcmpi(OBJ_DESCR(objects[i]), nm)) { by_descr = TRUE; goto found; } - /* user-assigned name might match real name of a later - entry, so we don't simply use first match with it; - also, player might assign same name multiple times - and if so, we choose one of those matches randomly */ + } + /* second loop: look for match with user-assigned name */ + /* we will get here if 'nm' isn't a real scroll name/descr, or is the name + * of a real scroll that hasn't been formally IDed. */ + for (i = first; i <= last; i++) { + /* player might assign same name multiple times and if so, + we choose one of those matches randomly */ if (objects[i].oc_uname && !strcmpi(objects[i].oc_uname, nm) + /* prefer attempting to write the real scroll type if + the typename clobbers a real scroll and is known to + be incorrect */ + && !(real && objects[i].oc_name_known) /* * First match: chance incremented to 1, * !rn2(1) is 1, we remember i; @@ -192,38 +190,61 @@ register struct obj *pen; * and 2/3 chance to keep previous 50:50 * choice; so on for higher match counts. */ - && !rn2(++deferralchance)) + && !rn2(++deferralchance)) { deferred = i; + /* writing by user-assigned name is same as by description: + fails for books, works for scrolls (having an assigned + type name guarantees presence on discoveries list) */ + by_descr = TRUE; + } } - /* writing by user-assigned name is same as by description: - fails for books, works for scrolls (having an assigned - type name guarantees presence on discoveries list) */ + if (deferred) { i = deferred; - by_descr = TRUE; goto found; } There("is no such %s!", typeword); - return 1; -found: + return ECMD_TIME; + found: if (i == SCR_BLANK_PAPER || i == SPE_BLANK_PAPER) { You_cant("write that!"); pline("It's obscene!"); - return 1; + return ECMD_TIME; + } else if (i == SPE_NOVEL) { + boolean fanfic = !rn2(3), tearup = !rn2(3); + + if (!fanfic) { + You("%s to write the Great Yendorian Novel, but %s inspiration.", + !tearup ? "prepare" : "try", + !Hallucination ? "lack" : "have too much"); + } else { + You("%sproduce really %s fan-fiction.", + !tearup ? "start to " : "", + !Hallucination ? "lame" : "awesome"); + } + if (!tearup) { + You("give up on the idea."); + } else { + You("tear it up."); + useup(paper); + } + return ECMD_TIME; } else if (i == SPE_BOOK_OF_THE_DEAD) { pline("No mere dungeon adventurer could write that."); - return 1; + return ECMD_TIME; } else if (by_descr && paper->oclass == SPBOOK_CLASS && !objects[i].oc_name_known) { /* can't write unknown spellbooks by description */ pline("Unfortunately you don't have enough information to go on."); - return 1; + return ECMD_TIME; } /* KMH, conduct */ - u.uconduct.literate++; + if (!u.uconduct.literate++) + livelog_printf(LL_CONDUCT, + "became literate by writing %s", an(typeword)); new_obj = mksobj(i, FALSE, FALSE); new_obj->bknown = (paper->bknown && pen->bknown); @@ -236,7 +257,7 @@ register struct obj *pen; if (pen->spe < basecost / 2) { Your("marker is too dry to write that!"); obfree(new_obj, (struct obj *) 0); - return 1; + return ECMD_TIME; } /* we're really going to write now, so calculate cost @@ -257,15 +278,19 @@ register struct obj *pen; useup(paper); } obfree(new_obj, (struct obj *) 0); - return 1; + return ECMD_TIME; } pen->spe -= actualcost; /* * Writing by name requires that the hero knows the scroll or * book type. One has previously been read (and its effect - * was evident) or been ID'd via scroll/spell/throne and it - * will be on the discoveries list. + * was evident) or been ID'd via scroll/spell/throne (or skill + * for Wizards) and it will be on the discoveries list. + * Unknown spellbooks can also be written by name if the hero + * has fresh knowledge of the spell, or if the spell is almost + * forgotten and the hero is Lucky (with a greater chance than + * if the spell is unknown or forgotten). * (Previous versions allowed scrolls and books to be written * by type name if they were on the discoveries list via being * given a user-assigned name, even though doing the latter @@ -273,23 +298,27 @@ register struct obj *pen; * * Writing by description requires that the hero knows the * description (a scroll's label, that is, since books by_descr - * are rejected above). BUG: We can only do this for known - * scrolls and for the case where the player has assigned a - * name to put it onto the discoveries list; we lack a way to - * track other scrolls which have been seen closely enough to - * read the label without then being ID'd or named. The only - * exception is for currently carried inventory, where we can - * check for one [with its dknown bit set] of the same type. + * are rejected above). This is done by checking to see if a + * scroll with the same description has been encountered. * * Normal requirements can be overridden if hero is Lucky. */ + if (paper->oclass == SPBOOK_CLASS) { + spell_knowledge = known_spell(new_obj->otyp); + } else { + spell_knowledge = spe_Unknown; + } /* if known, then either by-name or by-descr works */ if (!objects[new_obj->otyp].oc_name_known /* else if named, then only by-descr works */ - && !(by_descr && label_known(new_obj->otyp, invent)) - /* and Luck might override after both checks have failed */ - && rnl(Role_if(PM_WIZARD) ? 5 : 15)) { + && !(by_descr && objects[new_obj->otyp].oc_encountered) + /* else fresh knowledge of the spell works */ + && spell_knowledge != spe_Fresh + /* and Luck might override after previous checks have failed */ + && rnl(((Role_if(PM_WIZARD) && paper->oclass != SPBOOK_CLASS) + || spell_knowledge == spe_GoingStale) + ? 5 : 15)) { You("%s to write that.", by_descr ? "fail" : "don't know how"); /* scrolls disappear, spellbooks don't */ if (paper->oclass == SPBOOK_CLASS) { @@ -301,12 +330,12 @@ register struct obj *pen; Strcpy(namebuf, OBJ_DESCR(objects[new_obj->otyp])); wipeout_text(namebuf, (6 + MAXULEV - u.ulevel) / 6, 0); } else - Sprintf(namebuf, "%s was here!", plname); + Sprintf(namebuf, "%s was here!", svp.plname); You("write \"%s\" and the scroll disappears.", namebuf); useup(paper); } obfree(new_obj, (struct obj *) 0); - return 1; + return ECMD_TIME; } /* can write scrolls when blind, but requires luck too; attempts to write books when blind are caught above */ @@ -319,10 +348,10 @@ register struct obj *pen; You("fail to write the scroll correctly and it disappears."); useup(paper); obfree(new_obj, (struct obj *) 0); - return 1; + return ECMD_TIME; } - /* useup old scroll / spellbook */ + /* use up old scroll / spellbook */ useup(paper); /* success */ @@ -333,7 +362,7 @@ register struct obj *pen; } new_obj->blessed = (curseval > 0); new_obj->cursed = (curseval < 0); -#ifdef MAIL +#ifdef MAIL_STRUCTURES if (new_obj->otyp == SCR_MAIL) /* 0: delivered in-game via external event (or randomly for fake mail); 1: from bones or wishing; 2: written with marker */ @@ -342,14 +371,17 @@ register struct obj *pen; /* unlike alchemy, for example, a successful result yields the specifically chosen item so hero recognizes it even if blind; the exception is for being lucky writing an undiscovered scroll, - where the label associated with the type-name isn't known yet */ - new_obj->dknown = label_known(new_obj->otyp, invent) ? 1 : 0; + where the label associated with the type-name isn't known yet; + but if writing by description, the description is always known */ + new_obj->dknown = FALSE; + if (objects[new_obj->otyp].oc_name_known || by_descr) + observe_object(new_obj); new_obj = hold_another_object(new_obj, "Oops! %s out of your grasp!", The(aobjnam(new_obj, "slip")), (const char *) 0); nhUse(new_obj); /* try to avoid complaint about dead assignment */ - return 1; + return ECMD_TIME; } /* most book descriptions refer to cover appearance, so we can issue a @@ -359,10 +391,8 @@ register struct obj *pen; looks funny, so we want to insert "into " prior to such descriptions; even that's rather iffy, indicating that such descriptions probably ought to be eliminated (especially "cloth"!) */ -STATIC_OVL char * -new_book_description(booktype, outbuf) -int booktype; -char *outbuf; +staticfn char * +new_book_description(int booktype, char *outbuf) { /* subset of description strings from objects.c; if it grows much, we may need to add a new flag field to objects[] instead */ diff --git a/src/zap.c b/src/zap.c index 1c7a5debb..8aefcb1ea 100644 --- a/src/zap.c +++ b/src/zap.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 zap.c $NHDT-Date: 1573688696 2019/11/13 23:44:56 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.316 $ */ +/* NetHack 5.0 zap.c $NHDT-Date: 1770949988 2026/02/12 18:33:08 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.584 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2013. */ /* NetHack may be freely redistributed. See license for details. */ @@ -12,28 +12,35 @@ */ #define MAGIC_COOKIE 1000 -static NEARDATA boolean obj_zapped; -static NEARDATA int poly_zapped; - -extern boolean notonhead; /* for long worms */ - -/* kludge to use mondied instead of killed */ -extern boolean m_using; - -STATIC_DCL void FDECL(polyuse, (struct obj *, int, int)); -STATIC_DCL void FDECL(create_polymon, (struct obj *, int)); -STATIC_DCL int FDECL(stone_to_flesh_obj, (struct obj *)); -STATIC_DCL boolean FDECL(zap_updown, (struct obj *)); -STATIC_DCL void FDECL(zhitu, (int, int, const char *, XCHAR_P, XCHAR_P)); -STATIC_DCL void FDECL(revive_egg, (struct obj *)); -STATIC_DCL boolean FDECL(zap_steed, (struct obj *)); -STATIC_DCL void FDECL(skiprange, (int, int *, int *)); -STATIC_DCL int FDECL(zap_hit, (int, int)); -STATIC_OVL void FDECL(disintegrate_mon, (struct monst *, int, const char *)); -STATIC_DCL void FDECL(backfire, (struct obj *)); -STATIC_DCL int FDECL(spell_hit_bonus, (int)); -STATIC_DCL void FDECL(destroy_one_item, (struct obj *, int, int)); -STATIC_DCL void FDECL(wishcmdassist, (int)); +staticfn int zaptype(int); +staticfn void probe_objchain(struct obj *) NO_NNARGS; +staticfn boolean zombie_can_dig(coordxy x, coordxy y); +staticfn void polyuse(struct obj *, int, int) NO_NNARGS; +staticfn void create_polymon(struct obj *, int) NO_NNARGS; +staticfn int stone_to_flesh_obj(struct obj *) NONNULLARG1; +staticfn boolean zap_updown(struct obj *) NONNULLARG1; +staticfn void zhitu(int, int, const char *, coordxy, coordxy) NO_NNARGS; +staticfn void revive_egg(struct obj *) NONNULLARG1; +staticfn boolean zap_steed(struct obj *) NONNULLARG1; +staticfn void skiprange(int, int *, int *) NONNULLPTRS; +staticfn void maybe_explode_trap(struct trap *, struct obj *, + boolean *) NONNULLARG3; +staticfn void zap_map(coordxy, coordxy, struct obj *) NONNULLARG3; +staticfn void bounce_dir(coordxy, coordxy, int *, int *, int) NO_NNARGS; +staticfn int zap_hit(int, int); +staticfn void disintegrate_mon(struct monst *, int, const char *) NONNULLARG1; +staticfn int adtyp_to_prop(int); +staticfn void backfire(struct obj *) NONNULLARG1; +staticfn int zap_ok(struct obj *) NO_NNARGS; +/* all callers of boxlock_invent() pass a NONNULL obj, and boxlock + * boxlock_invent() calls boxlock() which has nonnull arg. */ +staticfn void boxlock_invent(struct obj *) NONNULLARG1; +staticfn int spell_hit_bonus(int); +staticfn int maybe_destroy_item(struct monst *, struct obj *, int) NONNULLPTRS; +staticfn boolean destroyable(struct obj *, int); + +staticfn void wishcmdassist(int); +staticfn void wish_history_menu(char *); #define ZT_MAGIC_MISSILE (AD_MAGM - 1) #define ZT_FIRE (AD_FIRE - 1) @@ -51,28 +58,42 @@ STATIC_DCL void FDECL(wishcmdassist, (int)); #define is_hero_spell(type) ((type) >= 10 && (type) < 20) -#define M_IN_WATER(ptr) \ - ((ptr)->mlet == S_EEL || amphibious(ptr) || is_swimmer(ptr)) +#define M_IN_WATER(ptr) ((ptr)->mlet == S_EEL || cant_drown(ptr)) -STATIC_VAR const char are_blinded_by_the_flash[] = - "are blinded by the flash!"; +static const char are_blinded_by_the_flash[] = "are blinded by the flash!"; -const char *const flash_types[] = /* also used in buzzmu(mcastu.c) */ - { - "magic missile", /* Wands must be 0-9 */ - "bolt of fire", "bolt of cold", "sleep ray", "death ray", - "bolt of lightning", "", "", "", "", - - "magic missile", /* Spell equivalents must be 10-19 */ - "fireball", "cone of cold", "sleep ray", "finger of death", - "bolt of lightning", /* there is no spell, used for retribution */ - "", "", "", "", +/* + * A positive index means zapped/cast/breathed by hero. + * A negative index means zapped/cast/breathed by a monster, with value + * index fixup beyond abs() needed for wand zaps. Wand zaps for monster + * use -39..-30 rather than -9..-0 because -0 is ambiguous (same as 0). + */ +static const char *const flash_types[] = { + "magic missile", /* Wands must be 0-9 */ + "bolt of fire", "bolt of cold", "sleep ray", "death ray", + "bolt of lightning", "", "", "", "", + + "magic missile", /* Spell equivalents must be 10-19 */ + "fireball", "cone of cold", "sleep ray", "finger of death", + "bolt of lightning", /* there is no spell, used for retribution */ + "", "", "", "", + + "blast of missiles", /* Dragon breath equivalents 20-29*/ + "blast of fire", "blast of frost", "blast of sleep gas", + "blast of disintegration", "blast of lightning", + "blast of poison gas", "blast of acid", "", "" +}; - "blast of missiles", /* Dragon breath equivalents 20-29*/ - "blast of fire", "blast of frost", "blast of sleep gas", - "blast of disintegration", "blast of lightning", - "blast of poison gas", "blast of acid", "", "" - }; +/* convert monster zap/spell/breath value to hero zap/spell/breath value */ +staticfn int +zaptype(int type) +{ + if (type <= -30 && -39 <= type) /* monster wand zap */ + type += 30; /* first convert -39..-30 to -9..0 so that abs() + * will yield 0..9 (hero wand zap) for it */ + type = abs(type); + return type; +} /* * Recognizing unseen wands by zapping: in 3.4.3 and earlier, zapping @@ -99,8 +120,7 @@ const char *const flash_types[] = /* also used in buzzmu(mcastu.c) */ /* wand discovery gets special handling when hero is blinded */ void -learnwand(obj) -struct obj *obj; +learnwand(struct obj *obj) { /* For a wand (or wand-like tool) zapped by the player, if the effect was observable (determined by caller; usually seen, but @@ -114,14 +134,14 @@ struct obj *obj; /* if type already discovered, treat this item has having been seen even if hero is currently blinded (skips redundant makeknown) */ if (objects[obj->otyp].oc_name_known) { - obj->dknown = 1; /* will usually be set already */ + observe_object(obj); /* will usually be dknown already */ /* otherwise discover it if item itself has been or can be seen */ } else { /* in case it was picked up while blind and then zapped without examining inventory after regaining sight (bypassing xname) */ if (!Blind) - obj->dknown = 1; + observe_object(obj); /* make the discovery iff we know what we're manipulating */ if (obj->dknown) makeknown(obj->otyp); @@ -130,42 +150,58 @@ struct obj *obj; } } -/* Routines for IMMEDIATE wands and spells. */ +/* + * Routines for IMMEDIATE wands and spells. + * Also RAY or NODIR for wands that are being broken rather than zapped. + */ + /* bhitm: monster mtmp was hit by the effect of wand or spell otmp */ int -bhitm(mtmp, otmp) -struct monst *mtmp; -struct obj *otmp; +bhitm(struct monst *mtmp, struct obj *otmp) { + int ret = 0; boolean wake = TRUE; /* Most 'zaps' should wake monster */ boolean reveal_invis = FALSE, learn_it = FALSE; boolean dbldam = Role_if(PM_KNIGHT) && u.uhave.questart; boolean skilled_spell, helpful_gesture = FALSE; - int dmg, otyp = otmp->otyp; + int dmg, otyp = otmp->otyp; /* otmp is not NULL */ const char *zap_type_text = "spell"; struct obj *obj; boolean disguised_mimic = (mtmp->data->mlet == S_MIMIC && M_AP_TYPE(mtmp) != M_AP_NOTHING); - - if (u.uswallow && mtmp == u.ustuck) + /* box_or_door(): mimic appearances that have locks */ +#define box_or_door(monst) \ + ((M_AP_TYPE(monst) == M_AP_OBJECT \ + && ((monst)->mappearance == CHEST \ + || (monst)->mappearance == LARGE_BOX)) \ + || (M_AP_TYPE(monst) == M_AP_FURNITURE \ + /* is_cmap_door() tests S_symbol values, and */ \ + /* mon->mappearance for furniture contains one of those */ \ + && is_cmap_door((monst)->mappearance))) + + if (engulfing_u(mtmp)) reveal_invis = FALSE; - notonhead = (mtmp->mx != bhitpos.x || mtmp->my != bhitpos.y); - skilled_spell = (otmp && otmp->oclass == SPBOOK_CLASS && otmp->blessed); + gn.notonhead = (mtmp->mx != gb.bhitpos.x || mtmp->my != gb.bhitpos.y); + skilled_spell = (otmp->oclass == SPBOOK_CLASS && otmp->blessed); switch (otyp) { case WAN_STRIKING: zap_type_text = "wand"; + FALLTHROUGH; /*FALLTHRU*/ case SPE_FORCE_BOLT: reveal_invis = TRUE; - if (disguised_mimic) - seemimic(mtmp); + learn_it = cansee(gb.bhitpos.x, gb.bhitpos.y); if (resists_magm(mtmp)) { /* match effect on player */ + if (disguised_mimic && !disguised_as_mon(mtmp)) + seemimic(mtmp); shieldeff(mtmp->mx, mtmp->my); pline("Boing!"); - break; /* skip makeknown */ + /* 5.0: used to 'break' to avoid setting learn_it here */ } else if (u.uswallow || rnd(20) < 10 + find_mac(mtmp)) { + if (disguised_mimic) + seemimic(mtmp); dmg = d(2, 12); if (dbldam) dmg *= 2; @@ -173,9 +209,11 @@ struct obj *otmp; dmg = spell_damage_bonus(dmg); hit(zap_type_text, mtmp, exclam(dmg)); (void) resist(mtmp, otmp->oclass, dmg, TELL); - } else - miss(zap_type_text, mtmp); - learn_it = TRUE; + } else { + if (!disguised_mimic) + miss(zap_type_text, mtmp); + learn_it = FALSE; + } break; case WAN_SLOW_MONSTER: case SPE_SLOW_MONSTER: @@ -183,8 +221,9 @@ struct obj *otmp; if (disguised_mimic) seemimic(mtmp); mon_adjust_speed(mtmp, -1, otmp); - m_dowear(mtmp, FALSE); /* might want speed boots */ - if (u.uswallow && (mtmp == u.ustuck) && is_whirly(mtmp->data)) { + check_gear_next_turn(mtmp); /* might want speed boots */ + + if (engulfing_u(mtmp) && is_whirly(mtmp->data)) { You("disrupt %s!", mon_nam(mtmp)); pline("A huge hole opens up..."); expels(mtmp, mtmp->data, TRUE); @@ -196,10 +235,10 @@ struct obj *otmp; if (disguised_mimic) seemimic(mtmp); mon_adjust_speed(mtmp, 1, otmp); - m_dowear(mtmp, FALSE); /* might want speed boots */ + check_gear_next_turn(mtmp); /* might want speed boots */ } - if (mtmp->mtame) - helpful_gesture = TRUE; + /* wake but don't anger a peaceful target */ + helpful_gesture = TRUE; break; case WAN_UNDEAD_TURNING: case SPE_TURN_UNDEAD: @@ -214,7 +253,7 @@ struct obj *otmp; dmg *= 2; if (otyp == SPE_TURN_UNDEAD) dmg = spell_damage_bonus(dmg); - context.bypasses = TRUE; /* for make_corpse() */ + svc.context.bypasses = TRUE; /* for make_corpse() */ if (!resist(mtmp, otmp->oclass, dmg, NOTELL)) { if (!DEADMONSTER(mtmp)) monflee(mtmp, 0, FALSE, TRUE); @@ -225,18 +264,18 @@ struct obj *otmp; case SPE_POLYMORPH: case POT_POLYMORPH: if (mtmp->data == &mons[PM_LONG_WORM] && has_mcorpsenm(mtmp)) { - /* if a long worm has mcorpsenm set, it was polymophed by + /* if a long worm has mcorpsenm set, it was polymorphed by the current zap and shouldn't be affected if hit again */ ; } else if (resists_magm(mtmp)) { /* magic resistance protects from polymorph traps, so make it guard against involuntary polymorph attacks too... */ - shieldeff(mtmp->mx, mtmp->my); + shieldeff_mon(mtmp); } else if (!resist(mtmp, otmp->oclass, 0, NOTELL)) { boolean polyspot = (otyp != POT_POLYMORPH), give_msg = (!Hallucination && (canseemon(mtmp) - || (u.uswallow && mtmp == u.ustuck))); + || engulfing_u(mtmp))); /* dropped inventory (due to death by system shock, or loss of wielded weapon and/or worn armor due to @@ -253,20 +292,27 @@ struct obj *otmp; pline("%s shudders!", Monnam(mtmp)); learn_it = TRUE; } - /* context.bypasses = TRUE; ## for make_corpse() */ + /* svc.context.bypasses = TRUE; ## for make_corpse() */ /* no corpse after system shock */ xkilled(mtmp, XKILL_GIVEMSG | XKILL_NOCORPSE); - } else if (newcham(mtmp, (struct permonst *) 0, - polyspot, give_msg) != 0 - /* if shapechange failed because there aren't - enough eligible candidates (most likely for - vampshifter), try reverting to original form */ - || (mtmp->cham >= LOW_PM - && newcham(mtmp, &mons[mtmp->cham], - polyspot, give_msg) != 0)) { - if (give_msg && (canspotmon(mtmp) - || (u.uswallow && mtmp == u.ustuck))) - learn_it = TRUE; + } else { + unsigned ncflags = NO_NC_FLAGS; + + if (polyspot) + ncflags |= NC_VIA_WAND_OR_SPELL; + if (give_msg) + ncflags |= NC_SHOW_MSG; + if (newcham(mtmp, (struct permonst *) 0, ncflags) != 0 + /* if shapechange failed because there aren't + enough eligible candidates (most likely for + vampshifter), try reverting to original form */ + || (ismnum(mtmp->cham) + && newcham(mtmp, &mons[mtmp->cham], + ncflags) != 0)) { + if (give_msg && (canspotmon(mtmp) + || engulfing_u(mtmp))) + learn_it = TRUE; + } } /* do this even if polymorphed failed (otherwise using @@ -282,7 +328,7 @@ struct obj *otmp; /* flag to indicate that cleanup is needed; object bypass cleanup also clears mon->mextra->mcorpsenm for all long worms on the level */ - context.bypasses = TRUE; + svc.context.bypasses = TRUE; } } break; @@ -297,25 +343,34 @@ struct obj *otmp; if (disguised_mimic) seemimic(mtmp); reveal_invis = !u_teleport_mon(mtmp, TRUE); + learn_it = canspotmon(mtmp); break; case WAN_MAKE_INVISIBLE: { int oldinvis = mtmp->minvis; + boolean couldsee = canseemon(mtmp); char nambuf[BUFSZ]; if (disguised_mimic) seemimic(mtmp); /* format monster's name before altering its visibility */ Strcpy(nambuf, Monnam(mtmp)); - mon_set_minvis(mtmp); + mon_set_minvis(mtmp, FALSE); if (!oldinvis && knowninvisible(mtmp)) { pline("%s turns transparent!", nambuf); reveal_invis = TRUE; learn_it = TRUE; + } else if (couldsee && !canseemon(mtmp)) { + /* keep the immediate effects of make invisible and teleportation + ambiguous by using the same message that's used if we + teleported mtmp (and it ended up somewhere you can't see) */ + pline("%s vanishes!", nambuf); } break; } case WAN_LOCKING: case SPE_WIZARD_LOCK: + if (disguised_mimic && box_or_door(mtmp)) + that_is_a_mimic(mtmp, MIM_REVEAL); /*seemimic()*/ wake = closeholdingtrap(mtmp, &learn_it); break; case WAN_PROBING: @@ -326,23 +381,39 @@ struct obj *otmp; break; case WAN_OPENING: case SPE_KNOCK: + if (disguised_mimic && box_or_door(mtmp)) + that_is_a_mimic(mtmp, MIM_REVEAL); /*seemimic()*/ wake = FALSE; /* don't want immediate counterattack */ - if (u.uswallow && mtmp == u.ustuck) { - if (is_animal(mtmp->data)) { - if (Blind) - You_feel("a sudden rush of air!"); - else - pline("%s opens its mouth!", Monnam(mtmp)); - } - expels(mtmp, mtmp->data, TRUE); - /* zap which hits steed will only release saddle if it - doesn't hit a holding or falling trap; playability - here overrides the more logical target ordering */ + if (mtmp == u.ustuck) { + /* zapping either holder/holdee or self [zapyourself()] will + release hero from holder's grasp or holdee from hero's grasp */ + release_hold(); + learn_it = TRUE; + + /* zap which hits steed will only release saddle if it + doesn't hit a holding or falling trap; playability + here overrides the more logical target ordering */ } else if (openholdingtrap(mtmp, &learn_it)) { break; } else if (openfallingtrap(mtmp, TRUE, &learn_it)) { /* mtmp might now be on the migrating monsters list */ break; + } else if (otyp == SPE_KNOCK) { + wake = TRUE; + ret = 1; + if (mtmp->data->msize < MZ_HUMAN && !m_is_steadfast(mtmp)) { + if (canseemon(mtmp)) + pline("%s is knocked back!", + Monnam(mtmp)); + mhurtle(mtmp, mtmp->mx - u.ux, mtmp->my - u.uy, rnd(2)); + } else { + if (canseemon(mtmp)) + pline("%s doesn't budge.", Monnam(mtmp)); + } + if (!DEADMONSTER(mtmp)) { + wakeup(mtmp, !mindless(mtmp->data)); + abuse_dog(mtmp); + } } else if ((obj = which_armor(mtmp, W_SADDLE)) != 0) { char buf[BUFSZ]; @@ -356,18 +427,19 @@ struct obj *otmp; } else if (canspotmon(mtmp)) { pline("%s falls off.", buf); } - obj_extract_self(obj); mdrop_obj(mtmp, obj, FALSE); } break; case SPE_HEALING: - case SPE_EXTRA_HEALING: + case SPE_EXTRA_HEALING: { + int healamt = d(6, otyp == SPE_EXTRA_HEALING ? 8 : 4); + reveal_invis = TRUE; if (mtmp->data != &mons[PM_PESTILENCE]) { + int delta = mtmp->mhpmax - mtmp->mhp; + wake = FALSE; /* wakeup() makes the target angry */ - mtmp->mhp += d(6, otyp == SPE_EXTRA_HEALING ? 8 : 4); - if (mtmp->mhp > mtmp->mhpmax) - mtmp->mhp = mtmp->mhpmax; + healmon(mtmp, healamt, 0); /* plain healing must be blessed to cure blindness; extra healing only needs to not be cursed, so spell always cures [potions quaffed by monsters behave slightly differently; @@ -376,7 +448,7 @@ struct obj *otmp; mcureblindness(mtmp, canseemon(mtmp)); if (canseemon(mtmp)) { if (disguised_mimic) { - if (is_obj_mappear(mtmp,STRANGE_OBJECT)) { + if (is_obj_mappear(mtmp, STRANGE_OBJECT)) { /* it can do better now */ set_mimic_sym(mtmp); newsym(mtmp->mx, mtmp->my); @@ -386,15 +458,19 @@ struct obj *otmp; pline("%s looks%s better.", Monnam(mtmp), otyp == SPE_EXTRA_HEALING ? " much" : ""); } + if (mtmp->mtame && Role_if(PM_HEALER) && (delta > 0)) { + more_experienced(min(delta, healamt), 0); + newexplevel(); + } if (mtmp->mtame || mtmp->mpeaceful) { adjalign(Role_if(PM_HEALER) ? 1 : sgn(u.ualign.type)); } } else { /* Pestilence */ - /* Pestilence will always resist; damage is half of 3d{4,8} */ - (void) resist(mtmp, otmp->oclass, - d(3, otyp == SPE_EXTRA_HEALING ? 8 : 4), TELL); + /* Pestilence will always resist; damage is half of (healamt/2) */ + (void) resist(mtmp, otmp->oclass, healamt / 2, TELL); } break; + } case WAN_LIGHT: /* (broken wand) */ if (flash_hits_mon(mtmp, otmp)) { learn_it = TRUE; @@ -403,7 +479,8 @@ struct obj *otmp; break; case WAN_SLEEP: /* (broken wand) */ /* [wakeup() doesn't rouse victims of temporary sleep, - so it's okay to leave `wake' set to TRUE here] */ + so it's okay to leave `wake' set to TRUE here; + revealing concealed mimic is handled by sleep_monst()] */ reveal_invis = TRUE; if (sleep_monst(mtmp, d(1 + otmp->spe, 12), WAND_CLASS)) slept_monst(mtmp); @@ -411,19 +488,35 @@ struct obj *otmp; learn_it = TRUE; break; case SPE_STONE_TO_FLESH: - if (monsndx(mtmp->data) == PM_STONE_GOLEM) { - char *name = Monnam(mtmp); - - /* turn into flesh golem */ - if (newcham(mtmp, &mons[PM_FLESH_GOLEM], FALSE, FALSE)) { - if (canseemon(mtmp)) - pline("%s turns to flesh!", name); - } else { - if (canseemon(mtmp)) - pline("%s looks rather fleshy for a moment.", name); + if (mtmp->data->mlet == S_GOLEM) { + const char *mesg; + char *name = Monnam(mtmp); /* before possible polymorph */ + + /* turn stone golem into flesh golem */ + if (monsndx(mtmp->data) == PM_STONE_GOLEM + && newcham(mtmp, &mons[PM_FLESH_GOLEM], NO_NC_FLAGS)) + mesg = "turns to flesh!"; + else if (monsndx(mtmp->data) == PM_FLESH_GOLEM) + mesg = "seems fleshier..."; + else + mesg = "looks rather fleshy for a moment."; + + if (canseemon(mtmp)) + pline("%s %s", name, mesg); + } else if (mtmp->data->mlet == S_MIMIC + && ((M_AP_TYPE(mtmp) == M_AP_FURNITURE + && stone_furniture_type(mtmp->mappearance)) + || (M_AP_TYPE(mtmp) == M_AP_OBJECT + && stone_object_type(mtmp->mappearance)))) { + /* note: if that_is_a_mimic() doesn't get called to reveal the + mimic, wakeup() below will call seemimic() */ + if (cansee(mtmp->mx, mtmp->my)) { + set_msg_xy(mtmp->mx, mtmp->my); + that_is_a_mimic(mtmp, MIM_REVEAL | MIM_OMIT_WAIT); } - } else + } else { wake = FALSE; + } break; case SPE_DRAIN_LIFE: if (disguised_mimic) @@ -434,7 +527,7 @@ struct obj *otmp; if (otyp == SPE_DRAIN_LIFE) dmg = spell_damage_bonus(dmg); if (resists_drli(mtmp)) { - shieldeff(mtmp->mx, mtmp->my); + shieldeff_mon(mtmp); } else if (!resist(mtmp, otmp->oclass, dmg, NOTELL) && !DEADMONSTER(mtmp)) { mtmp->mhp -= dmg; @@ -456,55 +549,93 @@ struct obj *otmp; impossible("What an interesting effect (%d)", otyp); break; } - if (wake) { - if (!DEADMONSTER(mtmp)) { - wakeup(mtmp, helpful_gesture ? FALSE : TRUE); - m_respond(mtmp); - if (mtmp->isshk && !*u.ushops) - hot_pursuit(mtmp); - } else if (M_AP_TYPE(mtmp)) - seemimic(mtmp); /* might unblock if mimicing a boulder/door */ + if (wake && !DEADMONSTER(mtmp)) { + /* seemimic() is done by wakeup() and might unblock vision */ + wakeup(mtmp, helpful_gesture ? FALSE : TRUE); + m_respond(mtmp); + if (mtmp->isshk && !*u.ushops) + hot_pursuit(mtmp); } - /* note: bhitpos won't be set if swallowed, but that's okay since + /* note: gb.bhitpos won't be set if swallowed, but that's okay since * reveal_invis will be false. We can't use mtmp->mx, my since it * might be an invisible worm hit on the tail. */ - if (reveal_invis) { - if (!DEADMONSTER(mtmp) && cansee(bhitpos.x, bhitpos.y) - && !canspotmon(mtmp)) - map_invisible(bhitpos.x, bhitpos.y); + if (reveal_invis && !DEADMONSTER(mtmp)) { + if (cansee(gb.bhitpos.x, gb.bhitpos.y) && !canspotmon(mtmp)) + map_invisible(gb.bhitpos.x, gb.bhitpos.y); } /* if effect was observable then discover the wand type provided that the wand itself has been seen */ if (learn_it) learnwand(otmp); - return 0; + return ret; +#undef box_or_door } +/* hero is held by a monster or engulfed or holding a monster and has zapped + opening/unlocking magic at holder/engulfer/holdee or at self */ void -probe_monster(mtmp) -struct monst *mtmp; +release_hold(void) { - struct obj *otmp; + struct monst *mtmp = u.ustuck; + + if (!mtmp) { + impossible("release_hold when not held?"); + } else if (u.uswallow) { /* possible for sticky hero to be swallowed */ + if (digests(mtmp->data)) { + if (!Blind) + pline("%s opens its mouth!", Monnam(mtmp)); + else + You_feel("a sudden rush of air!"); + } + /* gives "you get regurgitated" or "you get expelled from " */ + expels(mtmp, mtmp->data, TRUE); + } else if (sticks(gy.youmonst.data)) { + /* order matters if 'holding' status condition is enabled; + set_ustuck() will set flag for botl update, You() pline will + trigger a status update with "UHold" removed */ + set_ustuck((struct monst *) 0); + You("release %s.", mon_nam(mtmp)); + } else { /* held but not swallowed */ + char relbuf[BUFSZ]; + + unstuck(u.ustuck); + if (!nohands(mtmp->data)) + Sprintf(relbuf, "from %s grasp", s_suffix(mon_nam(mtmp))); + else + Sprintf(relbuf, "by %s", mon_nam(mtmp)); + You("are released %s.", relbuf); + } +} + +staticfn void +probe_objchain(struct obj *otmp) +{ + for (; otmp; otmp = otmp->nobj) { + observe_object(otmp); /* treat as "seen" */ + if (Is_container(otmp) || otmp->otyp == STATUE) { + otmp->lknown = 1; + if (!SchroedingersBox(otmp)) + otmp->cknown = 1; + } else if (otmp->otyp == TIN) + otmp->known = 1; + } +} +void +probe_monster(struct monst *mtmp) +{ mstatusline(mtmp); - if (notonhead) + if (gn.notonhead) return; /* don't show minvent for long worm tail */ if (mtmp->minvent) { - for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj) { - otmp->dknown = 1; /* treat as "seen" */ - if (Is_container(otmp) || otmp->otyp == STATUE) { - otmp->lknown = 1; - if (!SchroedingersBox(otmp)) - otmp->cknown = 1; - } - } + probe_objchain(mtmp->minvent); (void) display_minventory(mtmp, MINV_ALL | MINV_NOLET | PICK_NONE, (char *) 0); } else { pline("%s is not carrying anything%s.", noit_Monnam(mtmp), - (u.uswallow && mtmp == u.ustuck) ? " besides you" : ""); + engulfing_u(mtmp) ? " besides you" : ""); } } @@ -520,10 +651,10 @@ struct monst *mtmp; * is not available or subject to the constraints above. */ boolean -get_obj_location(obj, xp, yp, locflags) -struct obj *obj; -xchar *xp, *yp; -int locflags; +get_obj_location( + struct obj *obj, + coordxy *xp, coordxy *yp, + int locflags) { switch (obj->where) { case OBJ_INVENT: @@ -558,12 +689,12 @@ int locflags; } boolean -get_mon_location(mon, xp, yp, locflags) -struct monst *mon; -xchar *xp, *yp; -int locflags; /* non-zero means get location even if monster is buried */ +get_mon_location( + struct monst *mon, + coordxy *xp, coordxy *yp, + int locflags) /* non-zero means get location even if monster is buried */ { - if (mon == &youmonst) { + if (mon == &gy.youmonst || (u.usteed && mon == u.usteed)) { *xp = u.ux; *yp = u.uy; return TRUE; @@ -579,21 +710,30 @@ int locflags; /* non-zero means get location even if monster is buried */ /* used by revive() and animate_statue() */ struct monst * -montraits(obj, cc, adjacentok) -struct obj *obj; -coord *cc; -boolean adjacentok; /* False: at obj's spot only, True: nearby is allowed */ +montraits( + struct obj *obj, + coord *cc, + boolean adjacentok) /* False: at obj's spot only, + * True: nearby is allowed */ { - struct monst *mtmp, *mtmp2 = has_omonst(obj) ? get_mtraits(obj, TRUE) : 0; + struct monst *mtmp = (struct monst *) 0, + *mtmp2 = has_omonst(obj) ? get_mtraits(obj, TRUE) : 0; if (mtmp2) { /* save_mtraits() validated mtmp2->mnum */ mtmp2->data = &mons[mtmp2->mnum]; - if (mtmp2->mhpmax <= 0 && !is_rider(mtmp2->data)) - return (struct monst *) 0; - mtmp = makemon(mtmp2->data, cc->x, cc->y, - (NO_MINVENT | MM_NOWAIT | MM_NOCOUNTBIRTH - | (adjacentok ? MM_ADJACENTOK : 0))); + + if (mtmp2->mhpmax > 0 || is_rider(mtmp2->data)) { + mtmp = makemon(mtmp2->data, cc->x, cc->y, + (NO_MINVENT | MM_NOWAIT | MM_NOCOUNTBIRTH + /* in case mtmp2 is a long worm; saved traits for + long worm don't include tail segments so don't + give mtmp any; it will be given a new 'wormno' + though (unless those are exhausted) so be able + to grow new tail segments */ + | MM_NOTAIL | MM_NOMSG + | (adjacentok ? MM_ADJACENTOK : 0))); + } if (!mtmp) { /* mtmp2 is a copy of obj's object->oextra->omonst extension and is not on the map or on any monst lists */ @@ -601,8 +741,23 @@ boolean adjacentok; /* False: at obj's spot only, True: nearby is allowed */ return (struct monst *) 0; } - /* heal the monster */ - if (mtmp->mhpmax > mtmp2->mhpmax && is_rider(mtmp2->data)) + /* heal the monster; lower than normal level might come from + adj_lev() but we assume it has come from 'mtmp' being level + drained before finally killed; give a chance to restore + some levels so that trolls and Riders can't be drained to + level 0 and then trivially killed repeatedly */ + if ((int) mtmp->m_lev < mtmp->data->mlevel) { + int ltmp = rnd(mtmp->data->mlevel + 1); + + if (ltmp > (int) mtmp->m_lev) { + while ((int) mtmp->m_lev < ltmp) { + mtmp->m_lev++; + mtmp->mhpmax += monhp_per_lvl(mtmp); + } + mtmp2->m_lev = mtmp->m_lev; + } + } + if (mtmp->mhpmax > mtmp2->mhpmax) /* &&is_rider(mtmp2->data)*/ mtmp2->mhpmax = mtmp->mhpmax; mtmp2->mhp = mtmp2->mhpmax; /* Get these ones from mtmp */ @@ -613,10 +768,10 @@ boolean adjacentok; /* False: at obj's spot only, True: nearby is allowed */ if (mtmp->m_id) { mtmp2->m_id = mtmp->m_id; /* might be bringing quest leader back to life */ - if (quest_status.leader_is_dead + if (svq.quest_status.leader_is_dead /* leader_is_dead implies leader_m_id is valid */ - && mtmp2->m_id == quest_status.leader_m_id) - quest_status.leader_is_dead = FALSE; + && mtmp2->m_id == svq.quest_status.leader_m_id) + svq.quest_status.leader_is_dead = FALSE; } mtmp2->mx = mtmp->mx; mtmp2->my = mtmp->my; @@ -651,7 +806,7 @@ boolean adjacentok; /* False: at obj's spot only, True: nearby is allowed */ mtmp2->mblinded = 0; mtmp2->mstun = 0; mtmp2->mconf = 0; - /* when traits are for a shopeekper, dummy monster 'mtmp' won't + /* when traits are for a shopkeeper, dummy monster 'mtmp' won't have necessary eshk data for replmon() -> replshk() */ if (mtmp2->isshk) { neweshk(mtmp); @@ -683,14 +838,11 @@ boolean adjacentok; /* False: at obj's spot only, True: nearby is allowed */ * if applicable. */ struct monst * -get_container_location(obj, loc, container_nesting) -struct obj *obj; -int *loc; -int *container_nesting; +get_container_location( + struct obj *obj, + int *loc, + int *container_nesting) { - if (!obj || !loc) - return 0; - if (container_nesting) *container_nesting = 0; while (obj && obj->where == OBJ_CONTAINED) { @@ -706,6 +858,22 @@ int *container_nesting; return (struct monst *) 0; } +/* can zombie dig the location at x,y */ +staticfn boolean +zombie_can_dig(coordxy x, coordxy y) +{ + if (isok(x, y)) { + schar typ = levl[x][y].typ; + struct trap *ttmp; + + if ((ttmp = t_at(x, y)) != 0) + return FALSE; + if (typ == ROOM || typ == CORR || typ == GRAVE) + return TRUE; + } + return FALSE; +} + /* * Attempt to revive the given corpse, return the revived monster if * successful. Note: this does NOT use up the corpse if it fails. @@ -713,28 +881,40 @@ int *container_nesting; * and only one monster will be resurrected. */ struct monst * -revive(corpse, by_hero) -struct obj *corpse; -boolean by_hero; +revive(struct obj *corpse, boolean by_hero) { struct monst *mtmp = 0; struct permonst *mptr; struct obj *container; coord xy; - xchar x, y; + coordxy x, y; boolean one_of; - int montype, container_nesting = 0; + mmflags_nht mmflags = NO_MINVENT | MM_NOWAIT | MM_NOMSG; + int montype, cgend, container_nesting = 0; + boolean is_zomb; if (corpse->otyp != CORPSE) { impossible("Attempting to revive %s?", xname(corpse)); return (struct monst *) 0; } + montype = corpse->corpsenm; + /* treat buried auto-reviver (troll, Rider?) like a zombie + so that it can dig itself out of the ground if it revives */ + is_zomb = mons[montype].mlet == S_ZOMBIE + || (corpse->where == OBJ_BURIED && is_reviver(&mons[montype])); + + /* if this corpse is being eaten, stop doing that; this should be done + after makemon() succeeds and skipped if it fails, but waiting until + we know the result for that would be too late */ + cant_finish_meal(corpse); x = y = 0; if (corpse->where != OBJ_CONTAINED) { - /* only for invent, minvent, or floor */ + int locflags = is_zomb ? BURIED_TOO : 0; + + /* only for invent, minvent, or floor, or if zombie, buried */ container = 0; - (void) get_obj_location(corpse, &x, &y, 0); + (void) get_obj_location(corpse, &x, &y, locflags); } else { /* deal with corpses in [possibly nested] containers */ struct monst *carrier; @@ -757,7 +937,10 @@ boolean by_hero; break; /* x,y are 0 */ } } - if (!x || !y + if (x) /* update corpse's location now that we're sure where it is */ + corpse->ox = x, corpse->oy = y; + + if (!x /* Rules for revival from containers: * - the container cannot be locked * - the container cannot be heavily nested (>2 is arbitrary) @@ -766,14 +949,12 @@ boolean by_hero; */ || (container && (container->olocked || container_nesting > 2 || container->otyp == STATUE - || (container->otyp == BAG_OF_HOLDING && rn2(40))))) + || (container->otyp == BAG_OF_HOLDING && rn2(40)))) + /* if buried zombie cannot dig itself out, do not revive */ + || (is_zomb && corpse->where == OBJ_BURIED && !zombie_can_dig(x, y))) return (struct monst *) 0; - /* record the object's location now that we're sure where it is */ - corpse->ox = x, corpse->oy = y; - /* prepare for the monster */ - montype = corpse->corpsenm; mptr = &mons[montype]; /* [should probably handle recorporealization first; if corpse and ghost are at same location, revived creature shouldn't be bumped @@ -783,19 +964,25 @@ boolean by_hero; x = xy.x, y = xy.y; } - if ((mons[montype].mlet == S_EEL && !IS_POOL(levl[x][y].typ)) - || (mons[montype].mlet == S_TROLL - && uwep && uwep->oartifact == ART_TROLLSBANE)) { - if (by_hero && cansee(x, y)) + if (corpse->norevive + || (mons[montype].mlet == S_EEL && !IS_POOL(levl[x][y].typ))) { + if (cansee(x, y)) pline("%s twitches feebly.", upstart(corpse_xname(corpse, (const char *) 0, CXN_PFX_THE))); return (struct monst *) 0; } + /* applicable when montraits/corpse->oextra->omonst aren't used */ + cgend = (corpse->spe & CORPSTAT_GENDER); + if (cgend == CORPSTAT_MALE) + mmflags |= MM_MALE; + else if (cgend == CORPSTAT_FEMALE) + mmflags |= MM_FEMALE; + if (cant_revive(&montype, TRUE, corpse)) { /* make a zombie or doppelganger instead */ /* note: montype has changed; mptr keeps old value for newcham() */ - mtmp = makemon(&mons[montype], x, y, NO_MINVENT | MM_NOWAIT); + mtmp = makemon(&mons[montype], x, y, mmflags); if (mtmp) { /* skip ghost handling */ if (has_omid(corpse)) @@ -804,7 +991,7 @@ boolean by_hero; free_omonst(corpse); if (mtmp->cham == PM_DOPPELGANGER) { /* change shape to match the corpse */ - (void) newcham(mtmp, mptr, FALSE, FALSE); + (void) newcham(mtmp, mptr, NO_NC_FLAGS); } else if (mtmp->data->mlet == S_ZOMBIE) { mtmp->mhp = mtmp->mhpmax = 100; mon_adjust_speed(mtmp, 2, (struct obj *) 0); /* MFAST */ @@ -818,7 +1005,7 @@ boolean by_hero; wary_dog(mtmp, TRUE); } else { /* make a new monster */ - mtmp = makemon(mptr, x, y, NO_MINVENT | MM_NOWAIT | MM_NOCOUNTBIRTH); + mtmp = makemon(mptr, x, y, mmflags | MM_NOCOUNTBIRTH); } if (!mtmp) return (struct monst *) 0; @@ -859,6 +1046,7 @@ boolean by_hero; if (one_of) /* could be simplified to ''corpse->quan = 1L;'' */ corpse->quan--; pline("%s glows iridescently.", upstart(buf)); + iflags.last_msg = PLNMSG_OBJ_GLOWS; /* usually for BUC change */ } else if (shkp) { /* need some prior description of the corpse since stolen_value() will refer to the object as "it" */ @@ -879,8 +1067,7 @@ boolean by_hero; struct monst *ghost; struct obj *otmp; - (void) memcpy((genericptr_t) &m_id, (genericptr_t) OMID(corpse), - sizeof m_id); + m_id = OMID(corpse); ghost = find_mid(m_id, FM_FMON); if (ghost && ghost->data == &mons[PM_GHOST]) { if (canseemon(ghost)) @@ -893,7 +1080,7 @@ boolean by_hero; } /* tame the revived monster if its ghost was tame */ if (ghost->mtame && !mtmp->mtame) { - if (tamedog(mtmp, (struct obj *) 0)) { + if (tamedog(mtmp, (struct obj *) 0, FALSE)) { /* ghost's edog data is ignored */ mtmp->mtame = ghost->mtame; } @@ -921,31 +1108,39 @@ boolean by_hero; useup(corpse); break; case OBJ_FLOOR: - /* in case MON_AT+enexto for invisible mon */ - x = corpse->ox, y = corpse->oy; - /* not useupf(), which charges */ - if (corpse->quan > 1L) - corpse = splitobj(corpse, 1L); - delobj(corpse); - newsym(x, y); + /* not useupf(), which charges; + delobj() won't use up a Rider's corpse, delobj_core(,TRUE) will */ + delobj_core(corpse, TRUE); /* for floor, also calls newsym() */ break; case OBJ_MINVENT: m_useup(corpse->ocarry, corpse); break; case OBJ_CONTAINED: + /* obj_extract_self() will update corpse->ocontainer->owt */ obj_extract_self(corpse); obfree(corpse, (struct obj *) 0); break; + case OBJ_BURIED: + if (is_zomb) { + obj_extract_self(corpse); + obfree(corpse, (struct obj *) 0); + break; + } + FALLTHROUGH; + /*FALLTHRU*/ + case OBJ_FREE: + case OBJ_MIGRATING: + case OBJ_ONBILL: + case OBJ_LUAFREE: default: - panic("revive"); + panic("revive default case %d", (int) corpse->where); } return mtmp; } -STATIC_OVL void -revive_egg(obj) -struct obj *obj; +staticfn void +revive_egg(struct obj *obj) /* nonnull */ { /* * Note: generic eggs with corpsenm set to NON_PM will never hatch. @@ -958,17 +1153,17 @@ struct obj *obj; /* try to revive all corpses and eggs carried by `mon' */ int -unturn_dead(mon) -struct monst *mon; +unturn_dead(struct monst *mon) { struct obj *otmp, *otmp2; struct monst *mtmp2; char owner[BUFSZ], corpse[BUFSZ]; - boolean youseeit; - int res = 0; + unsigned save_norevive; + boolean youseeit, different_type, is_u = (mon == &gy.youmonst); + int corpsenm, res = 0; - youseeit = (mon == &youmonst) ? TRUE : canseemon(mon); - otmp2 = (mon == &youmonst) ? invent : mon->minvent; + youseeit = is_u ? TRUE : canseemon(mon); + otmp2 = is_u ? gi.invent : mon->minvent; owner[0] = corpse[0] = '\0'; /* lint suppression */ while ((otmp = otmp2) != 0) { @@ -979,85 +1174,143 @@ struct monst *mon; continue; /* save the name; the object is liable to go away */ if (youseeit) { - Strcpy(corpse, - corpse_xname(otmp, (const char *) 0, CXN_SINGULAR)); - Shk_Your(owner, otmp); /* includes a trailing space */ + Strcpy(corpse, corpse_xname(otmp, (const char *) 0, CXN_NORMAL)); + /* shk_your/Shk_Your produces a value with a trailing space */ + if (otmp->quan > 1L) { + Strcpy(owner, "One of "); + (void) shk_your(eos(owner), otmp); + } else + (void) Shk_Your(owner, otmp); } + /* for a stack, only one is revived; if is_u, revive() calls + useup() which calls update_inventory() but not encumber_msg() */ + corpsenm = otmp->corpsenm; + /* norevive applies to revive timer, not to explicit unturn_dead() */ + save_norevive = otmp->norevive; + otmp->norevive = 0; - /* for a stack, only one is revived */ - if ((mtmp2 = revive(otmp, !context.mon_moving)) != 0) { + if ((mtmp2 = revive(otmp, !svc.context.mon_moving)) != 0) { ++res; + /* might get revived as a zombie rather than corpse's monster */ + different_type = (mtmp2->data != &mons[corpsenm]); + if (iflags.last_msg == PLNMSG_OBJ_GLOWS) { + /* when hero zaps undead turning at self (or breaks + non-empty wand), revive() reports "[one of] your + corpse[s] glows iridescently"; override saved corpse + and owner names to say "It comes alive" [note: we did + earlier setup because corpse gets used up but need to + do the override here after revive() sets 'last_msg'] */ + Strcpy(corpse, "It"); + owner[0] = '\0'; + } if (youseeit) - pline("%s%s suddenly comes alive!", owner, corpse); + pline("%s%s suddenly %s%s%s!", owner, corpse, + nonliving(mtmp2->data) ? "reanimates" : "comes alive", + different_type ? " as " : "", + different_type ? an(mon_pmname(mtmp2)) : ""); else if (canseemon(mtmp2)) pline("%s suddenly appears!", Amonnam(mtmp2)); + } else { + /* revival failed; corpse 'otmp' is intact */ + otmp->norevive = save_norevive ? 1 : 0; } } + if (is_u && res) + encumber_msg(); + return res; } +void +unturn_you(void) +{ + (void) unturn_dead(&gy.youmonst); /* hit carried corpses and eggs */ + + if (is_undead(gy.youmonst.data)) { + You_feel("frightened and %sstunned.", Stunned ? "even more " : ""); + make_stunned((HStun & TIMEOUT) + (long) rnd(30), FALSE); + } else { + You("shudder in dread."); + } +} + /* cancel obj, possibly carried by you or a monster */ void -cancel_item(obj) -register struct obj *obj; +cancel_item(struct obj *obj) { - boolean u_ring = (obj == uleft || obj == uright); int otyp = obj->otyp; - switch (otyp) { - case RIN_GAIN_STRENGTH: - if ((obj->owornmask & W_RING) && u_ring) { - ABON(A_STR) -= obj->spe; - context.botl = 1; - } - break; - case RIN_GAIN_CONSTITUTION: - if ((obj->owornmask & W_RING) && u_ring) { - ABON(A_CON) -= obj->spe; - context.botl = 1; - } - break; - case RIN_ADORNMENT: - if ((obj->owornmask & W_RING) && u_ring) { - ABON(A_CHA) -= obj->spe; - context.botl = 1; - } - break; - case RIN_INCREASE_ACCURACY: - if ((obj->owornmask & W_RING) && u_ring) - u.uhitinc -= obj->spe; - break; - case RIN_INCREASE_DAMAGE: - if ((obj->owornmask & W_RING) && u_ring) - u.udaminc -= obj->spe; - break; - case GAUNTLETS_OF_DEXTERITY: - if ((obj->owornmask & W_ARMG) && (obj == uarmg)) { - ABON(A_DEX) -= obj->spe; - context.botl = 1; - } - break; - case HELM_OF_BRILLIANCE: - if ((obj->owornmask & W_ARMH) && (obj == uarmh)) { - ABON(A_INT) -= obj->spe; - ABON(A_WIS) -= obj->spe; - context.botl = 1; + if (carried(obj)) { + /* handle items being worn by hero */ + switch (otyp) { + case RIN_GAIN_STRENGTH: + if ((obj->owornmask & W_RING) != 0L) { + ABON(A_STR) -= obj->spe; + disp.botl = TRUE; + } + break; + case RIN_GAIN_CONSTITUTION: + if ((obj->owornmask & W_RING) != 0L) { + ABON(A_CON) -= obj->spe; + disp.botl = TRUE; + } + break; + case RIN_ADORNMENT: + if ((obj->owornmask & W_RING) != 0L) { + ABON(A_CHA) -= obj->spe; + disp.botl = TRUE; + } + break; + case RIN_INCREASE_ACCURACY: + if ((obj->owornmask & W_RING) != 0L) + u.uhitinc -= obj->spe; + break; + case RIN_INCREASE_DAMAGE: + if ((obj->owornmask & W_RING) != 0L) + u.udaminc -= obj->spe; + break; + case RIN_PROTECTION: + if ((obj->owornmask & W_RING) != 0L) + disp.botl = TRUE; + break; + case GAUNTLETS_OF_DEXTERITY: + if ((obj->owornmask & W_ARMG) != 0L) { + ABON(A_DEX) -= obj->spe; + disp.botl = TRUE; + } + break; + case HELM_OF_BRILLIANCE: + if ((obj->owornmask & W_ARMH) != 0L) { + ABON(A_INT) -= obj->spe; + ABON(A_WIS) -= obj->spe; + disp.botl = TRUE; + } + break; + default: + if ((obj->owornmask & W_ARMOR) != 0L) /* AC */ + disp.botl = TRUE; + break; } - break; - /* case RIN_PROTECTION: not needed */ } + /* cancelled item might not be in hero's possession but + cancellation is presumed to be instigated by hero */ if (objects[otyp].oc_magic || (obj->spe && (obj->oclass == ARMOR_CLASS || obj->oclass == WEAPON_CLASS || is_weptool(obj))) || otyp == POT_ACID || otyp == POT_SICKNESS - || (otyp == POT_WATER && (obj->blessed || obj->cursed))) { - if (obj->spe != ((obj->oclass == WAND_CLASS) ? -1 : 0) + || (otyp == POT_WATER && (obj->blessed || obj->cursed)) + /* not magic; cancels to blank spellbook */ + || otyp == SPE_NOVEL) { + int cancelled_spe = (obj->oclass == WAND_CLASS + || otyp == CRYSTAL_BALL) ? -1 : 0; + + if (obj->spe != cancelled_spe && otyp != WAN_CANCELLATION /* can't cancel cancellation */ && otyp != MAGIC_LAMP /* cancelling doesn't remove djinni */ && otyp != CANDELABRUM_OF_INVOCATION) { costly_alteration(obj, COST_CANCEL); - obj->spe = (obj->oclass == WAND_CLASS) ? -1 : 0; + obj->spe = cancelled_spe; } switch (obj->oclass) { case SCROLL_CLASS: @@ -1066,17 +1319,17 @@ register struct obj *obj; obj->spe = 0; break; case SPBOOK_CLASS: - if (otyp != SPE_CANCELLATION && otyp != SPE_NOVEL - && otyp != SPE_BOOK_OF_THE_DEAD) { + if (otyp != SPE_CANCELLATION && otyp != SPE_BOOK_OF_THE_DEAD) { costly_alteration(obj, COST_CANCEL); obj->otyp = SPE_BLANK_PAPER; + /* cancelling a novel is more involved than a spellbook */ + if (otyp == SPE_NOVEL) /* old type */ + blank_novel(obj); } break; case POTION_CLASS: - costly_alteration(obj, - (otyp != POT_WATER) - ? COST_CANCEL - : obj->cursed ? COST_UNCURS : COST_UNBLSS); + costly_alteration(obj, (otyp != POT_WATER) ? COST_CANCEL + : obj->cursed ? COST_UNCURS : COST_UNBLSS); if (otyp == POT_SICKNESS || otyp == POT_SEE_INVISIBLE) { /* sickness is "biologically contaminated" fruit juice; cancel it and it just becomes fruit juice... @@ -1090,18 +1343,43 @@ register struct obj *obj; break; } } + /* cancelling a troll's corpse prevents it from reviving (on its own; + does not affect undead turning induced revival) */ + if (obj->otyp == CORPSE && obj->timed + && !is_rider(&mons[obj->corpsenm])) { + anything a = *obj_to_any(obj); + long timout = peek_timer(REVIVE_MON, &a); + + if (timout) { + (void) stop_timer(REVIVE_MON, &a); + (void) start_timer(timout, TIMER_OBJECT, ROT_CORPSE, &a); + } + } + unbless(obj); uncurse(obj); return; } +/* soaking or cancelling a novel converts it into a blank spellbook but + needs more than just changing its otyp (caller is responsible for that) */ +void +blank_novel(struct obj *obj) +{ + assert(obj->otyp == SPE_BLANK_PAPER); + /* novelidx overloads corpsenm, not used for spellbooks */ + obj->novelidx = 0; + free_oname(obj); /* get rid of [former] novel's title */ + /* a blank spellbook weighs more than a novel; update obj's weight and + recursively the weight of any container holding it */ + container_weight(obj); +} + /* Remove a positive enchantment or charge from obj, * possibly carried by you or a monster */ boolean -drain_item(obj, by_you) -struct obj *obj; -boolean by_you; +drain_item(struct obj *obj, boolean by_you) { boolean u_ring; @@ -1126,19 +1404,19 @@ boolean by_you; case RIN_GAIN_STRENGTH: if ((obj->owornmask & W_RING) && u_ring) { ABON(A_STR)--; - context.botl = 1; + disp.botl = TRUE; } break; case RIN_GAIN_CONSTITUTION: if ((obj->owornmask & W_RING) && u_ring) { ABON(A_CON)--; - context.botl = 1; + disp.botl = TRUE; } break; case RIN_ADORNMENT: if ((obj->owornmask & W_RING) && u_ring) { ABON(A_CHA)--; - context.botl = 1; + disp.botl = TRUE; } break; case RIN_INCREASE_ACCURACY: @@ -1151,25 +1429,25 @@ boolean by_you; break; case RIN_PROTECTION: if (u_ring) - context.botl = 1; /* bot() will recalc u.uac */ + disp.botl = TRUE; /* bot() will recalc u.uac */ break; case HELM_OF_BRILLIANCE: if ((obj->owornmask & W_ARMH) && (obj == uarmh)) { ABON(A_INT)--; ABON(A_WIS)--; - context.botl = 1; + disp.botl = TRUE; } break; case GAUNTLETS_OF_DEXTERITY: if ((obj->owornmask & W_ARMG) && (obj == uarmg)) { ABON(A_DEX)--; - context.botl = 1; + disp.botl = TRUE; } break; default: break; } - if (context.botl) + if (disp.botl) bot(); if (carried(obj)) update_inventory(); @@ -1177,9 +1455,9 @@ boolean by_you; } boolean -obj_resists(obj, ochance, achance) -struct obj *obj; -int ochance, achance; /* percent chance for ordinary objects, artifacts */ +obj_resists(struct obj *obj, + int ochance, /* percent chance for ordinary objects */ + int achance) /* percent chance for artifacts */ { if (obj->otyp == AMULET_OF_YENDOR || obj->otyp == SPE_BOOK_OF_THE_DEAD @@ -1195,12 +1473,11 @@ int ochance, achance; /* percent chance for ordinary objects, artifacts */ } boolean -obj_shudders(obj) -struct obj *obj; +obj_shudders(struct obj *obj) { int zap_odds; - if (context.bypasses && obj->bypass) + if (svc.context.bypasses && obj->bypass) return FALSE; if (obj->oclass == WAND_CLASS) @@ -1224,22 +1501,20 @@ struct obj *obj; * there's a random factor here to keep from always using the stuff * at the top of the pile. */ -STATIC_OVL void -polyuse(objhdr, mat, minwt) -struct obj *objhdr; -int mat, minwt; +staticfn void +polyuse(struct obj *objhdr, int mat, int minwt) { - register struct obj *otmp, *otmp2; + struct obj *otmp, *otmp2; for (otmp = objhdr; minwt > 0 && otmp; otmp = otmp2) { otmp2 = otmp->nexthere; - if (context.bypasses && otmp->bypass) + if (svc.context.bypasses && otmp->bypass) continue; if (otmp == uball || otmp == uchain) continue; if (obj_resists(otmp, 0, 0)) continue; /* preserve unique objects */ -#ifdef MAIL +#ifdef MAIL_STRUCTURES if (otmp->otyp == SCR_MAIL) continue; #endif @@ -1267,17 +1542,15 @@ int mat, minwt; * Polymorph some of the stuff in this pile into a monster, preferably * a golem of the kind okind. */ -STATIC_OVL void -create_polymon(obj, okind) -struct obj *obj; -int okind; +staticfn void +create_polymon(struct obj *obj, int okind) { struct permonst *mdat = (struct permonst *) 0; struct monst *mtmp; const char *material; int pm_index; - if (context.bypasses) { + if (svc.context.bypasses) { /* this is approximate because the "no golems" !obj->nexthere check below doesn't understand bypassed objects; but it should suffice since bypassed objects always end up as a @@ -1347,10 +1620,10 @@ int okind; break; } - if (!(mvitals[pm_index].mvflags & G_GENOD)) + if (!(svm.mvitals[pm_index].mvflags & G_GENOD)) mdat = &mons[pm_index]; - mtmp = makemon(mdat, obj->ox, obj->oy, NO_MM_FLAGS); + mtmp = makemon(mdat, obj->ox, obj->oy, MM_NOMSG); polyuse(obj, okind, (int) mons[pm_index].cwt); if (mtmp && cansee(mtmp->mx, mtmp->my)) { @@ -1361,22 +1634,21 @@ int okind; /* Assumes obj is on the floor. */ void -do_osshock(obj) -struct obj *obj; +do_osshock(struct obj *obj) { long i; -#ifdef MAIL +#ifdef MAIL_STRUCTURES if (obj->otyp == SCR_MAIL) return; #endif - obj_zapped = TRUE; + go.obj_zapped = TRUE; - if (poly_zapped < 0) { - /* some may metamorphosize */ + if (gp.poly_zapped < 0) { + /* some may metamorphose */ for (i = obj->quan; i; i--) if (!rn2(Luck + 45)) { - poly_zapped = objects[obj->otyp].oc_material; + gp.poly_zapped = objects[obj->otyp].oc_material; break; } } @@ -1401,9 +1673,18 @@ struct obj *obj; delobj(obj); } +/* Returns TRUE if obj resists polymorphing */ +boolean +obj_unpolyable(struct obj *obj) +{ + return (unpolyable(obj) + || obj == uball || obj == uskin + || obj_resists(obj, 5, 95)); +} + /* classes of items whose current charge count carries over across polymorph */ -static const char charged_objs[] = { WAND_CLASS, WEAPON_CLASS, ARMOR_CLASS, +staticfn const char charged_objs[] = { WAND_CLASS, WEAPON_CLASS, ARMOR_CLASS, '\0' }; /* @@ -1418,12 +1699,10 @@ static const char charged_objs[] = { WAND_CLASS, WEAPON_CLASS, ARMOR_CLASS, * This should be safe to call for an object anywhere. */ struct obj * -poly_obj(obj, id) -struct obj *obj; -int id; +poly_obj(struct obj *obj, int id) { struct obj *otmp; - xchar ox = 0, oy = 0; + coordxy ox = 0, oy = 0; long old_wornmask, new_wornmask = 0L; boolean can_merge = (id == STRANGE_OBJECT); int obj_location = obj->where; @@ -1437,7 +1716,7 @@ int id; if (obj->otyp == UNICORN_HORN && obj->degraded_horn) magic_obj = 0; /* Try up to 3 times to make the magic-or-not status of - the new item be the same as it was for the old one. */ + the new item the same as the old item. */ otmp = (struct obj *) 0; do { if (otmp) @@ -1459,12 +1738,12 @@ int id; /* preserve quantity */ otmp->quan = obj->quan; - /* preserve the shopkeepers (lack of) interest */ + /* preserve the shopkeeper's (lack of) interest */ otmp->no_charge = obj->no_charge; /* preserve inventory letter if in inventory */ if (obj_location == OBJ_INVENT) otmp->invlet = obj->invlet; -#ifdef MAIL +#ifdef MAIL_STRUCTURES /* You can't send yourself 100 mail messages and then * polymorph them into useful scrolls */ @@ -1500,7 +1779,7 @@ int id; } /* keep special fields (including charges on wands) */ - if (index(charged_objs, otmp->oclass)) + if (strchr(charged_objs, otmp->oclass)) otmp->spe = obj->spe; otmp->recharged = obj->recharged; @@ -1508,7 +1787,7 @@ int id; otmp->blessed = obj->blessed; if (erosion_matters(otmp)) { - if (is_flammable(otmp) || is_rustprone(otmp)) + if (is_flammable(otmp) || is_rustprone(otmp) || is_crackable(otmp)) otmp->oeroded = obj->oeroded; if (is_corrodeable(otmp) || is_rottable(otmp)) otmp->oeroded2 = obj->oeroded2; @@ -1518,10 +1797,9 @@ int id; /* Keep chest/box traps and poisoned ammo if we may */ if (obj->otrapped && Is_box(otmp)) - otmp->otrapped = TRUE; - + otmp->otrapped = 1; if (obj->opoisoned && is_poisonable(otmp)) - otmp->opoisoned = TRUE; + otmp->opoisoned = 1; if (id == STRANGE_OBJECT && obj->otyp == CORPSE) { /* turn crocodile corpses into shoes */ @@ -1535,6 +1813,16 @@ int id; otmp->cursed = FALSE; } } + if (obj->otyp == LEASH && obj->leashmon != 0) { + if (otmp->otyp == LEASH) { + otmp->leashmon = obj->leashmon; + /* clear m_id before delobj(), to avoid o_unleash() by obfree() */ + obj->leashmon = 0; + } else { + /* obfree() would do this if we didn't do it here */ + o_unleash(obj); + } + } /* no box contents --KAA */ if (Has_contents(otmp)) @@ -1568,13 +1856,19 @@ int id; case POTION_CLASS: while (otmp->otyp == POT_POLYMORPH) otmp->otyp = rnd_class(POT_GAIN_ABILITY, POT_WATER); + /* potions of oil use obj->age field differently from other potions */ + if (otmp->otyp == POT_OIL || obj->otyp == POT_OIL) + fixup_oil(otmp, obj); break; case SPBOOK_CLASS: while (otmp->otyp == SPE_POLYMORPH) - otmp->otyp = rnd_class(SPE_DIG, SPE_BLANK_PAPER); - /* reduce spellbook abuse; non-blank books degrade */ - if (otmp->otyp != SPE_BLANK_PAPER) { + otmp->otyp = rnd_class(svb.bases[SPBOOK_CLASS], SPE_BLANK_PAPER); + /* reduce spellbook abuse; non-blank books degrade; + 5.0: novels don't use spestudied so shouldn't degrade to blank + (but don't force spestudied to zero for them since a non-zero + value could get passed along to a future polymorph) */ + if (otmp->otyp != SPE_BLANK_PAPER && otmp->otyp != SPE_NOVEL) { otmp->spestudied = obj->spestudied + 1; if (otmp->spestudied > MAX_SPELL_STUDY) { otmp->otyp = SPE_BLANK_PAPER; @@ -1611,8 +1905,8 @@ int id; /* * We may need to do extra adjustments for the hero if we're * messing with the hero's inventory. The following calls are - * equivalent to calling freeinv on obj and addinv on otmp, - * while doing an in-place swap of the actual objects. + * equivalent to calling freeinv() on obj and addinv_nomerge() + * on otmp, while doing an in-place swap of the actual objects. */ freeinv_core(obj); addinv_core1(otmp); @@ -1627,21 +1921,24 @@ int id; if (old_wornmask) { boolean was_twohanded = bimanual(obj), was_twoweap = u.twoweap; - /* wearslot() returns a mask which might have multiple bits set; - narrow that down to the bit(s) currently in use */ - new_wornmask = wearslot(otmp) & old_wornmask; + /* wearslot() expects us to deal with wielded/alt-wep/quivered + items in case they're not weapons; for other slots it might + return multiple bits (ring left|right); narrow that down to + the bit(s) currently in use */ + new_wornmask = ((old_wornmask & W_WEAPONS) != 0L) ? old_wornmask + : (wearslot(otmp) & old_wornmask); remove_worn_item(obj, TRUE); /* if the new form can be worn in the same slot, make it so */ if ((new_wornmask & W_WEP) != 0L) { if (was_twohanded || !bimanual(otmp) || !uarms) setuwep(otmp); if (was_twoweap && uwep && !bimanual(uwep)) - u.twoweap = TRUE; + set_twoweap(TRUE); /* u.twoweap = TRUE */ } else if ((new_wornmask & W_SWAPWEP) != 0L) { if (was_twohanded || !bimanual(otmp)) setuswapwep(otmp); if (was_twoweap && uswapwep) - u.twoweap = TRUE; + set_twoweap(TRUE); /* u.twoweap = TRUE */ } else if ((new_wornmask & W_QUIVER) != 0L) { setuqwep(otmp); } else if (new_wornmask) { @@ -1653,12 +1950,16 @@ int id; } } /* old_wornmask */ } else if (obj_location == OBJ_FLOOR) { - if (obj->otyp == BOULDER && otmp->otyp != BOULDER - && !does_block(ox, oy, &levl[ox][oy])) - unblock_point(ox, oy); - else if (obj->otyp != BOULDER && otmp->otyp == BOULDER) - /* (checking does_block() here would be redundant) */ - block_point(ox, oy); + if (obj->otyp == BOULDER && otmp->otyp != BOULDER) { + if (!does_block(ox, oy, &levl[ox][oy])) + unblock_point(ox, oy); + } else if (obj->otyp != BOULDER && otmp->otyp == BOULDER) { + /* leaving boulder in liquid would trigger sanity_check warning */ + if (is_pool_or_lava(ox, oy)) + fracture_rock(otmp); + if (does_block(ox, oy, &levl[ox][oy])) + block_point(ox, oy); + } } /* note: if otmp is gone, billing for it was handled by useup() */ @@ -1676,11 +1977,11 @@ int id; && !costly_spot(u.ux, u.uy)) { make_angry_shk(shkp, ox, oy); } else { - pline("%s gets angry!", Monnam(shkp)); + pline("%s gets angry!", Shknam(shkp)); hot_pursuit(shkp); } } else - Norep("%s is furious!", Monnam(shkp)); + Norep("%s is furious!", Shknam(shkp)); } } delobj(obj); @@ -1688,16 +1989,15 @@ int id; } /* stone-to-flesh spell hits and maybe transforms or animates obj */ -STATIC_OVL int -stone_to_flesh_obj(obj) -struct obj *obj; +staticfn int +stone_to_flesh_obj(struct obj *obj) /* nonnull */ { - int res = 1; /* affected object by default */ struct permonst *ptr; struct monst *mon, *shkp; struct obj *item; - xchar oox, ooy; + coordxy oox, ooy; boolean smell = FALSE, golem_xform = FALSE; + int res = 1; /* affected object by default */ if (objects[obj->otyp].oc_material != MINERAL && objects[obj->otyp].oc_material != GEMSTONE) @@ -1707,12 +2007,12 @@ struct obj *obj; return 0; (void) get_obj_location(obj, &oox, &ooy, 0); - /* add more if stone objects are added.. */ + /* add more if stone objects are added... */ switch (objects[obj->otyp].oc_class) { case ROCK_CLASS: /* boulders and statues */ case TOOL_CLASS: /* figurines */ if (obj->otyp == BOULDER) { - obj = poly_obj(obj, HUGE_CHUNK_OF_MEAT); + obj = poly_obj(obj, ENORMOUS_MEATBALL); smell = TRUE; } else if (obj->otyp == STATUE || obj->otyp == FIGURINE) { ptr = &mons[obj->corpsenm]; @@ -1730,7 +2030,7 @@ struct obj *obj; } else { /* (obj->otyp == FIGURINE) */ if (golem_xform) ptr = &mons[PM_FLESH_GOLEM]; - mon = makemon(ptr, oox, ooy, NO_MINVENT); + mon = makemon(ptr, oox, ooy, NO_MINVENT|MM_NOMSG); if (mon) { if (costly_spot(oox, ooy) && (carried(obj) ? obj->unpaid : !obj->no_charge)) { @@ -1753,7 +2053,8 @@ struct obj *obj; ptr = mon->data; /* this golem handling is redundant... */ if (is_golem(ptr) && ptr != &mons[PM_FLESH_GOLEM]) - (void) newcham(mon, &mons[PM_FLESH_GOLEM], TRUE, FALSE); + (void) newcham(mon, &mons[PM_FLESH_GOLEM], + NC_VIA_WAND_OR_SPELL); } else if ((ptr->geno & (G_NOCORPSE | G_UNIQ)) != 0) { /* didn't revive but can't leave corpse either */ res = 0; @@ -1785,11 +2086,13 @@ struct obj *obj; smell = TRUE; break; case WEAPON_CLASS: /* crysknife */ + FALLTHROUGH; /*FALLTHRU*/ default: res = 0; break; } + nhUse(obj); /* avoid 'assigned value not used' for poly_obj() calls */ if (smell) { /* non-meat eaters smell meat, meat eaters smell its flavor; @@ -1799,7 +2102,7 @@ struct obj *obj; non-omnivorous form, regardless of whether it's herbivorous, non-eating, or something stranger) */ if (Role_if(PM_MONK) || !u.uconduct.unvegetarian - || !carnivorous(youmonst.data)) + || !carnivorous(gy.youmonst.data)) Norep("You smell the odor of meat."); else Norep("You smell a delicious smell."); @@ -1813,8 +2116,7 @@ struct obj *obj; * non-zero if the wand/spell had any effect. */ int -bhito(obj, otmp) -struct obj *obj, *otmp; +bhito(struct obj *obj, struct obj *otmp) { int res = 1; /* affected object by default */ boolean learn_it = FALSE, maybelearnit; @@ -1849,19 +2151,18 @@ struct obj *obj, *otmp; * menu_drop(), askchain() - inventory traversal where multiple * Drop can alter the invent chain while traversal * is in progress (bhito isn't involved). - * destroy_item(), destroy_mitem() - inventory traversal where - * item destruction can trigger drop or destruction of - * other item(s) and alter the invent or mon->minvent - * chain, possibly recursively. + * destroy_items() - inventory traversal where item destruction can + * trigger drop or destruction of other item(s) and alter + * the invent or mon->minvent chain, possibly recursively. * * The bypass bit on all objects is reset each turn, whenever - * context.bypasses is set. + * svc.context.bypasses is set. * - * We check the obj->bypass bit above AND context.bypasses + * We check the obj->bypass bit above AND svc.context.bypasses * as a safeguard against any stray occurrence left in an obj * struct someplace, although that should never happen. */ - if (context.bypasses) { + if (svc.context.bypasses) { return 0; } else { debugpline1("%s for a moment.", Tobjnam(obj, "pulsate")); @@ -1889,28 +2190,30 @@ struct obj *obj, *otmp; switch (otmp->otyp) { case WAN_POLYMORPH: case SPE_POLYMORPH: - if (obj->otyp == WAN_POLYMORPH || obj->otyp == SPE_POLYMORPH - || obj->otyp == POT_POLYMORPH || obj_resists(obj, 5, 95)) { + if (obj_unpolyable(obj)) { res = 0; break; } /* KMH, conduct */ - u.uconduct.polypiles++; + if (!u.uconduct.polypiles++) + livelog_printf(LL_CONDUCT, "polymorphed %s first object", + uhis()); + /* any saved lock context will be dangerously obsolete */ if (Is_box(obj)) (void) boxlock(obj, otmp); if (obj_shudders(obj)) { - boolean cover = ((obj == level.objects[u.ux][u.uy]) + boolean cover = ((obj == svl.level.objects[u.ux][u.uy]) && u.uundetected - && hides_under(youmonst.data)); + && hides_under(gy.youmonst.data)); if (cansee(obj->ox, obj->oy)) learn_it = TRUE; do_osshock(obj); /* eek - your cover might have been blown */ if (cover) - (void) hideunder(&youmonst); + (void) hideunder(&gy.youmonst); break; } obj = poly_obj(obj, STRANGE_OBJECT); @@ -1919,9 +2222,18 @@ struct obj *obj, *otmp; case WAN_PROBING: res = !obj->dknown; /* target object has now been "seen (up close)" */ - obj->dknown = 1; + observe_object(obj); if (Is_container(obj) || obj->otyp == STATUE) { obj->cknown = obj->lknown = 1; + if (Is_box(obj) && !obj->tknown) { + /* obj->tknown applies to boxes and chests, not bags or + statues; plural handling here and the "empty" case + below are superfluous because containers don't stack */ + if (obj->otrapped) + pline("%s trapped!", Tobjnam(obj, "are")); + obj->tknown = 1; + } + if (!obj->cobj) { pline("%s empty.", Tobjnam(obj, "are")); } else if (SchroedingersBox(obj)) { @@ -1939,10 +2251,23 @@ struct obj *obj, *otmp; /* view contents (not recursively) */ for (o = obj->cobj; o; o = o->nobj) - o->dknown = 1; /* "seen", even if blind */ + observe_object(o); /* "seen", even if blind */ (void) display_cinventory(obj); } res = 1; + } else if (obj->otyp == TIN) { + /* don't learn wand if tin is already known */ + if (!obj->known || !obj->cknown) + res = 1; + obj->known = 1; + set_cknown_lknown(obj); /* if TIN obj->cknown = 1 */ + } else if (obj->otyp == EGG) { + /* if egg is unhatchable, probing it won't learn wand + because even when flagged as known, it's just "an egg" */ + if (!obj->known && obj->corpsenm != NON_PM) + res = 1; + obj->known = 1; + /* [should this call learn_egg_type()?] */ } if (res) learn_it = TRUE; @@ -1953,6 +2278,7 @@ struct obj *obj, *otmp; (the sound could be implicit) */ maybelearnit = cansee(obj->ox, obj->oy) || !Deaf; if (obj->otyp == BOULDER) { + Soundeffect(se_crumbling_sound, 75); if (cansee(obj->ox, obj->oy)) pline_The("boulder falls apart."); else @@ -1969,14 +2295,16 @@ struct obj *obj, *otmp; You_hear("a crumbling sound."); } } else { - int oox = obj->ox; - int ooy = obj->oy; - if (context.mon_moving - ? !breaks(obj, obj->ox, obj->oy) - : !hero_breaks(obj, obj->ox, obj->oy, FALSE)) + int oox = obj->ox, ooy = obj->oy; + + if (svc.context.mon_moving ? !breaks(obj, oox, ooy) + : !hero_breaks(obj, oox, ooy, 0)) maybelearnit = FALSE; /* nothing broke */ else - newsym_force(oox,ooy); + /* obj broke; force redisplay in case it was the only-- + or last--item under non-breaking pile-top; top item + here might now be a lone object rather than a pile */ + newsym_force(oox, ooy); res = 0; } if (maybelearnit) @@ -1985,16 +2313,19 @@ struct obj *obj, *otmp; case WAN_CANCELLATION: case SPE_CANCELLATION: cancel_item(obj); -#ifdef TEXTCOLOR newsym(obj->ox, obj->oy); /* might change color */ -#endif break; case SPE_DRAIN_LIFE: (void) drain_item(obj, TRUE); break; case WAN_TELEPORTATION: case SPE_TELEPORT_AWAY: - (void) rloco(obj); + { + coordxy ox = obj->ox, oy = obj->oy; + + (void) rloco(obj); + maybe_unhide_at(ox, oy); + } break; case WAN_MAKE_INVISIBLE: break; @@ -2004,7 +2335,9 @@ struct obj *obj, *otmp; revive_egg(obj); } else if (obj->otyp == CORPSE) { struct monst *mtmp; - xchar ox, oy; + coordxy ox, oy; + unsigned save_norevive; + boolean by_u = !svc.context.mon_moving; int corpsenm = corpse_revive_type(obj); char *corpsname = cxname_singular(obj); @@ -2012,15 +2345,20 @@ struct obj *obj, *otmp; if (!get_obj_location(obj, &ox, &oy, 0)) ox = obj->ox, oy = obj->oy; /* won't happen */ + /* explicit revival magic overrides timer-based no-revive */ + save_norevive = obj->norevive; + obj->norevive = 0; + mtmp = revive(obj, TRUE); if (!mtmp) { + obj->norevive = save_norevive; res = 0; /* no monster implies corpse was left intact */ } else { if (cansee(ox, oy)) { if (canspotmon(mtmp)) { pline("%s is resurrected!", upstart(noname_monnam(mtmp, ARTICLE_THE))); - learn_it = TRUE; + learn_it = by_u ? TRUE : gz.zap_oseen; } else { /* saw corpse but don't see monster: maybe mtmp is invisible, or has been placed at @@ -2039,7 +2377,7 @@ struct obj *obj, *otmp; You_hear("%s reviving.", corpsname); else You_hear("a defibrillator."); - learn_it = TRUE; + learn_it = by_u ? TRUE : gz.zap_oseen; } if (canspotmon(mtmp)) /* didn't see corpse but do see monster: it @@ -2087,17 +2425,27 @@ struct obj *obj, *otmp; /* returns nonzero if something was hit */ int -bhitpile(obj, fhito, tx, ty, zz) -struct obj *obj; -int FDECL((*fhito), (OBJ_P, OBJ_P)); -int tx, ty; -schar zz; +bhitpile( + struct obj *obj, /* wand or fake spellbook for type of zap */ + int (*fhito)(OBJ_P, OBJ_P), /* callback for each object being hit */ + coordxy tx, coordxy ty, /* target location */ + schar zz) /* direction for up/down zaps */ { - int hitanything = 0; - register struct obj *otmp, *next_obj; + struct obj *otmp, *next_obj; + boolean hidingunder, first; + int prevotyp, hitanything = 0; + + if (!svl.level.objects[tx][ty]) + return 0; + + /* if hiding underneath an object and zapping up or down, the top item + is either the only thing hit (up) or is skipped (down) */ + hidingunder = (zz != 0 && u.uundetected && hides_under(gy.youmonst.data)); + first = TRUE; if (obj->otyp == SPE_FORCE_BOLT || obj->otyp == WAN_STRIKING) { struct trap *t = t_at(tx, ty); + struct obj *topofpile = svl.level.objects[tx][ty]; /* We can't settle for the default calling sequence of bhito(otmp) -> break_statue(otmp) -> activate_statue_trap(ox,oy) @@ -2107,20 +2455,52 @@ schar zz; if (t && t->ttyp == STATUE_TRAP && activate_statue_trap(t, tx, ty, TRUE)) learnwand(obj); + /* assume zapping up or down while hiding under the top item can + still activate the trap even if it's below (when zapping up) + or above (when zapping down) */ + if (svl.level.objects[tx][ty] != topofpile) + first = FALSE; /* top item was statue which activated */ } - poly_zapped = -1; - for (otmp = level.objects[tx][ty]; otmp; otmp = next_obj) { + gp.poly_zapped = -1; + for (otmp = svl.level.objects[tx][ty]; otmp; otmp = next_obj) { next_obj = otmp->nexthere; - /* for zap downwards, don't hit object poly'd hero is hiding under */ - if (zz > 0 && u.uundetected && otmp == level.objects[u.ux][u.uy] - && hides_under(youmonst.data)) + if (hidingunder) { + if (first) { + first = FALSE; /* reset for next item */ + if (zz > 0) /* down when hiding-under skips first item */ + continue; + } else { + /* !first */ + if (zz < 0) /* up when hiding-under skips rest of pile */ + continue; + } + } + if (otmp->where != OBJ_FLOOR || otmp->ox != tx || otmp->oy != ty) continue; - hitanything += (*fhito)(otmp, obj); } - if (poly_zapped >= 0) - create_polymon(level.objects[tx][ty], poly_zapped); + + if (gp.poly_zapped >= 0) + create_polymon(svl.level.objects[tx][ty], gp.poly_zapped); + + /* when boulders are present they're expected to be on top; with + multiple boulders it's possible for some to have been changed into + non-boulders (polymorph, stone-to-flesh) while ones beneath resist, + so re-stack pile if there are any non-boulders above boulders */ + prevotyp = BOULDER; + for (otmp = svl.level.objects[tx][ty]; otmp; otmp = otmp->nexthere) { + if (otmp->otyp == BOULDER && prevotyp != BOULDER) { + recreate_pile_at(tx, ty); + break; + } + prevotyp = otmp->otyp; + } + + if (hidingunder) /* pile might have been destroyed or dispersed */ + maybe_unhide_at(tx, ty); + + fill_pit(tx, ty); return hitanything; } @@ -2131,10 +2511,9 @@ schar zz; * added by GAN 11/03/86 */ int -zappable(wand) -register struct obj *wand; +zappable(struct obj *wand) { - if (wand->spe < 0 || (wand->spe == 0 && rn2(121))) + if (wand->spe < 0 || (wand->spe == 0 && rn2(WAND_WREST_CHANCE))) return 0; if (wand->spe == 0) You("wrest one last charge from the worn-out wand."); @@ -2142,53 +2521,77 @@ register struct obj *wand; return 1; } +void +do_enlightenment_effect(void) +{ + You_feel("self-knowledgeable..."); + display_nhwindow(WIN_MESSAGE, FALSE); + enlightenment(MAGICENLIGHTENMENT, ENL_GAMEINPROGRESS); + pline_The("feeling subsides."); + exercise(A_WIS, TRUE); +} + /* * zapnodir - zaps a NODIR wand/spell. - * added by GAN 11/03/86 + * Won't get here if wand has no charges (unless wresting 1 last charge). */ void -zapnodir(obj) -register struct obj *obj; +zapnodir(struct obj *obj) { boolean known = FALSE; switch (obj->otyp) { case WAN_LIGHT: case SPE_LIGHT: + /* FIXME? wand of light becoming discovered should be contingent upon + seeing at least one previously unlit spot become lit */ + known = (obj->dknown && !Blind); litroom(TRUE, obj); - if (!Blind) - known = TRUE; - if (lightdamage(obj, TRUE, 5)) - known = TRUE; + (void) lightdamage(obj, TRUE, 5); break; case WAN_SECRET_DOOR_DETECTION: case SPE_DETECT_UNSEEN: - if (!findit()) - return; - if (!Blind) - known = TRUE; + /* findit() gives sufficient feedback to discover the wand even when + blinded or when it fails to find anything */ + known = !!obj->dknown; + (void) findit(); + break; + case WAN_STASIS: { + long tmp_until = svm.moves + (long) rn1(21, 10); + + /* no immediately obvious effect, and no message so that it isn't + distinguishable from other NODIR wands that produce no message; + for multiple zaps, keep the longest duration rather than latest */ + if (tmp_until > svl.level.flags.stasis_until) + svl.level.flags.stasis_until = tmp_until; break; + } case WAN_CREATE_MONSTER: - known = create_critters(rn2(23) ? 1 : rn1(7, 2), - (struct permonst *) 0, FALSE); + /* create_critters() returns True iff hero sees a new monster appear */ + if (create_critters(rn2(23) ? 1 : rn1(7, 2), + (struct permonst *) 0, FALSE)) + known = !!obj->dknown; break; case WAN_WISHING: - known = TRUE; if (Luck + rn2(5) < 0) { pline("Unfortunately, nothing happens."); - break; + known = FALSE; + } else { + known = !!obj->dknown; + /* wand of wishing asks player what to wish for so always becomes + discovered (unless it hasn't been seen) */ + makewish(); } - makewish(); break; case WAN_ENLIGHTENMENT: - known = TRUE; - You_feel("self-knowledgeable..."); - display_nhwindow(WIN_MESSAGE, FALSE); - enlightenment(MAGICENLIGHTENMENT, ENL_GAMEINPROGRESS); - pline_The("feeling subsides."); - exercise(A_WIS, TRUE); + known = !!obj->dknown; + /* do_enlightenmnt_effect() always describes enlightenment */ + do_enlightenment_effect(); + break; + default: break; } + if (known) { if (!objects[obj->otyp].oc_name_known) more_experienced(0, 10); @@ -2198,9 +2601,8 @@ register struct obj *obj; } } -STATIC_OVL void -backfire(otmp) -struct obj *otmp; +staticfn void +backfire(struct obj *otmp) { int dmg; @@ -2208,72 +2610,103 @@ struct obj *otmp; pline("%s suddenly explodes!", The(xname(otmp))); dmg = d(otmp->spe + 2, 6); losehp(Maybe_Half_Phys(dmg), "exploding wand", KILLED_BY_AN); - useup(otmp); + useupall(otmp); } -static NEARDATA const char zap_syms[] = { WAND_CLASS, 0 }; +/* getobj callback for object to zap */ +staticfn int +zap_ok(struct obj *obj) +{ + if (obj && obj->oclass == WAND_CLASS) + return GETOBJ_SUGGEST; + return GETOBJ_EXCLUDE; +} -/* 'z' command (or 'y' if numbed_pad==-1) */ +/* #zap command, 'z' (or 'y' if numbed_pad==-1) */ int -dozap() +dozap(void) { - register struct obj *obj; - int damage; + struct obj *obj; + int damage, need_dir; + if (nohands(gy.youmonst.data)) { + You("aren't able to zap anything in your current form."); + return ECMD_OK; + } if (check_capacity((char *) 0)) - return 0; - obj = getobj(zap_syms, "zap"); + return ECMD_OK; + obj = getobj("zap", zap_ok, GETOBJ_NOFLAGS); if (!obj) - return 0; + return ECMD_CANCEL; check_unpaid(obj); - /* zappable addition done by GAN 11/03/86 */ - if (!zappable(obj)) + need_dir = objects[obj->otyp].oc_dir != NODIR; + if (!zappable(obj)) { pline1(nothing_happens); - else if (obj->cursed && !rn2(WAND_BACKFIRE_CHANCE)) { + } else if (obj->cursed && !rn2(WAND_BACKFIRE_CHANCE)) { backfire(obj); /* the wand blows up in your face! */ exercise(A_STR, FALSE); - return 1; - } else if (!(objects[obj->otyp].oc_dir == NODIR) && !getdir((char *) 0)) { + /* 'obj' is gone; skip update_inventory() because + backfire() -> useupall() -> freeinv() did it */ + return ECMD_TIME; + } else if (need_dir && !getdir((char *) 0)) { if (!Blind) pline("%s glows and fades.", The(xname(obj))); /* make him pay for knowing !NODIR */ - } else if (!u.dx && !u.dy && !u.dz - && !(objects[obj->otyp].oc_dir == NODIR)) { + } else if (need_dir && !u.dx && !u.dy && !u.dz) { if ((damage = zapyourself(obj, TRUE)) != 0) { char buf[BUFSZ]; - Sprintf(buf, "zapped %sself with a wand", uhim()); + Sprintf(buf, "zapped %sself with %s", + uhim(), killer_xname(obj)); losehp(Maybe_Half_Phys(damage), buf, NO_KILLER_PREFIX); } } else { /* Are we having fun yet? * weffects -> buzz(obj->otyp) -> zhitm (temple priest) -> * attack -> hitum -> known_hitum -> ghod_hitsu -> - * buzz(AD_ELEC) -> destroy_item(WAND_CLASS) -> + * buzz(AD_ELEC) -> destroy_items(AD_ELEC) -> * useup -> obfree -> dealloc_obj -> free(obj) */ - current_wand = obj; + gc.current_wand = obj; weffects(obj); - obj = current_wand; - current_wand = 0; + obj = gc.current_wand; + gc.current_wand = 0; } if (obj && obj->spe < 0) { pline("%s to dust.", Tobjnam(obj, "turn")); - useup(obj); + useupall(obj); /* calls freeinv() -> update_inventory() */ + } else + update_inventory(); /* maybe used a charge */ + return ECMD_TIME; +} + +/* Lock or unlock all boxes in inventory */ +staticfn void +boxlock_invent(struct obj *obj) +{ + struct obj *otmp, *nextobj; + boolean boxing = FALSE; + + /* (un)lock carried boxes */ + for (otmp = gi.invent; otmp; otmp = nextobj) { + nextobj = otmp->nobj; + if (Is_box(otmp)) { + (void) boxlock(otmp, obj); + boxing = TRUE; + } } - update_inventory(); /* maybe used a charge */ - return 1; + if (boxing) + update_inventory(); /* in case any box->lknown has changed */ } int -zapyourself(obj, ordinary) -struct obj *obj; -boolean ordinary; +zapyourself(struct obj *obj, boolean ordinary) { boolean learn_it = FALSE; int damage = 0; + int orig_dmg = 0; /* for passing to destroy_items() */ switch (obj->otyp) { case WAN_STRIKING: @@ -2282,6 +2715,7 @@ boolean ordinary; if (Antimagic) { shieldeff(u.ux, u.uy); pline("Boing!"); + monstseesu(M_SEEN_MAGR); } else { if (ordinary) { You("bash yourself!"); @@ -2289,23 +2723,26 @@ boolean ordinary; } else damage = d(1 + obj->spe, 6); exercise(A_STR, FALSE); + monstunseesu(M_SEEN_MAGR); } break; case WAN_LIGHTNING: learn_it = TRUE; + orig_dmg = d(12, 6); if (!Shock_resistance) { You("shock yourself!"); - damage = d(12, 6); + damage = orig_dmg; exercise(A_CON, FALSE); + monstunseesu(M_SEEN_ELEC); } else { shieldeff(u.ux, u.uy); You("zap yourself, but seem unharmed."); - ugolemeffects(AD_ELEC, d(12, 6)); + monstseesu(M_SEEN_ELEC); + ugolemeffects(AD_ELEC, orig_dmg); } - destroy_item(WAND_CLASS, AD_ELEC); - destroy_item(RING_CLASS, AD_ELEC); - (void) flashburn((long) rnd(100)); + (void) destroy_items(&gy.youmonst, AD_ELEC, orig_dmg); + (void) flashburn((long) rnd(100), TRUE); break; case SPE_FIREBALL: @@ -2315,35 +2752,39 @@ boolean ordinary; case WAN_FIRE: case FIRE_HORN: learn_it = TRUE; + orig_dmg = d(12, 6); if (Fire_resistance) { shieldeff(u.ux, u.uy); You_feel("rather warm."); - ugolemeffects(AD_FIRE, d(12, 6)); + monstseesu(M_SEEN_FIRE); + ugolemeffects(AD_FIRE, orig_dmg); } else { pline("You've set yourself afire!"); - damage = d(12, 6); + damage = orig_dmg; + monstunseesu(M_SEEN_FIRE); } burn_away_slime(); - (void) burnarmor(&youmonst); - destroy_item(SCROLL_CLASS, AD_FIRE); - destroy_item(POTION_CLASS, AD_FIRE); - destroy_item(SPBOOK_CLASS, AD_FIRE); - destroy_item(FOOD_CLASS, AD_FIRE); /* only slime for now */ + (void) burnarmor(&gy.youmonst); + (void) destroy_items(&gy.youmonst, AD_FIRE, orig_dmg); + ignite_items(gi.invent); break; case WAN_COLD: case SPE_CONE_OF_COLD: case FROST_HORN: learn_it = TRUE; + orig_dmg = d(12, 6); if (Cold_resistance) { shieldeff(u.ux, u.uy); You_feel("a little chill."); - ugolemeffects(AD_COLD, d(12, 6)); + monstseesu(M_SEEN_COLD); + ugolemeffects(AD_COLD, orig_dmg); } else { You("imitate a popsicle!"); - damage = d(12, 6); + damage = orig_dmg; + monstunseesu(M_SEEN_COLD); } - destroy_item(POTION_CLASS, AD_COLD); + (void) destroy_items(&gy.youmonst, AD_COLD, orig_dmg); break; case WAN_MAGIC_MISSILE: @@ -2352,9 +2793,11 @@ boolean ordinary; if (Antimagic) { shieldeff(u.ux, u.uy); pline_The("missiles bounce!"); + monstseesu(M_SEEN_MAGR); } else { damage = d(4, 6); pline("Idiot! You've shot yourself!"); + monstunseesu(M_SEEN_MAGR); } break; @@ -2362,13 +2805,13 @@ boolean ordinary; case SPE_POLYMORPH: if (!Unchanging) { learn_it = TRUE; - polyself(0); + polyself(POLY_NOFLAGS); } break; case WAN_CANCELLATION: case SPE_CANCELLATION: - (void) cancel_monst(&youmonst, obj, TRUE, TRUE, TRUE); + (void) cancel_monst(&gy.youmonst, obj, TRUE, TRUE, TRUE); break; case SPE_DRAIN_LIFE: @@ -2390,11 +2833,7 @@ boolean ordinary; You_feel("rather itchy under %s.", yname(uarmc)); break; } - if (ordinary || !rn2(10)) { /* permanent */ - HInvis |= FROMOUTSIDE; - } else { /* temporary */ - incr_itimeout(&HInvis, d(obj->spe, 250)); - } + incr_itimeout(&HInvis, rn1(15, 31)); if (msg) { learn_it = TRUE; newsym(u.ux, u.uy); @@ -2404,15 +2843,9 @@ boolean ordinary; } case WAN_SPEED_MONSTER: - if (!(HFast & INTRINSIC)) { - learn_it = TRUE; - if (!Fast) - You("speed up."); - else - Your("quickness feels more natural."); - exercise(A_DEX, TRUE); - } - HFast |= FROMOUTSIDE; + /* no longer gives intrinsic, but gives very fast speed instead */ + speed_up(rn1(25, 50)); + learn_it = TRUE; break; case WAN_SLEEP: @@ -2421,8 +2854,13 @@ boolean ordinary; if (Sleep_resistance) { shieldeff(u.ux, u.uy); You("don't feel sleepy!"); + monstseesu(M_SEEN_SLEEP); } else { - pline_The("sleep ray hits you!"); + if (ordinary) + pline_The("sleep ray hits you!"); + else + You("fall asleep!"); + monstunseesu(M_SEEN_SLEEP); fall_asleep(-rnd(50), TRUE); } break; @@ -2446,30 +2884,26 @@ boolean ordinary; case WAN_DEATH: case SPE_FINGER_OF_DEATH: - if (nonliving(youmonst.data) || is_demon(youmonst.data)) { + if (nonliving(gy.youmonst.data) || is_demon(gy.youmonst.data)) { pline((obj->otyp == WAN_DEATH) ? "The wand shoots an apparently harmless beam at you." : "You seem no deader than before."); break; } learn_it = TRUE; - Sprintf(killer.name, "shot %sself with a death ray", uhim()); - killer.format = NO_KILLER_PREFIX; - You("irradiate yourself with pure energy!"); - You("die."); + Sprintf(svk.killer.name, "shot %sself with a death ray", uhim()); + svk.killer.format = NO_KILLER_PREFIX; + /* probably don't need these to be urgent; player just gave input + without subsequent opportunity to dismiss --More-- with ESC */ + urgent_pline("You irradiate yourself with pure energy!"); + urgent_pline("You die."); /* They might survive with an amulet of life saving */ done(DIED); break; case WAN_UNDEAD_TURNING: case SPE_TURN_UNDEAD: learn_it = TRUE; - (void) unturn_dead(&youmonst); - if (is_undead(youmonst.data)) { - You_feel("frightened and %sstunned.", - Stunned ? "even more " : ""); - make_stunned((HStun & TIMEOUT) + (long) rnd(30), FALSE); - } else - You("shudder in dread."); + unturn_you(); break; case SPE_HEALING: case SPE_EXTRA_HEALING: @@ -2481,55 +2915,41 @@ boolean ordinary; case WAN_LIGHT: /* (broken wand) */ /* assert( !ordinary ); */ damage = d(obj->spe, 25); + FALLTHROUGH; /*FALLTHRU*/ case EXPENSIVE_CAMERA: if (!damage) damage = 5; damage = lightdamage(obj, ordinary, damage); damage += rnd(25); - if (flashburn((long) damage)) + if (flashburn((long) damage, FALSE)) learn_it = TRUE; damage = 0; /* reset */ break; case WAN_OPENING: case SPE_KNOCK: + if (u.ustuck) { + /* zapping either self or holder/holdee [bhitm()] will release + holder's grasp from the hero or hero's grasp from holdee */ + release_hold(); + learn_it = TRUE; + } if (Punished) { learn_it = TRUE; unpunish(); } /* invent is hit iff hero doesn't escape from a trap */ - if (!u.utrap || !openholdingtrap(&youmonst, &learn_it)) { - struct obj *otmp; - boolean boxing = FALSE; - - /* unlock carried boxes */ - for (otmp = invent; otmp; otmp = otmp->nobj) - if (Is_box(otmp)) { - (void) boxlock(otmp, obj); - boxing = TRUE; - } - if (boxing) - update_inventory(); /* in case any box->lknown has changed */ - + if (!u.utrap || !openholdingtrap(&gy.youmonst, &learn_it)) { + boxlock_invent(obj); /* trigger previously escaped trapdoor */ - (void) openfallingtrap(&youmonst, TRUE, &learn_it); + (void) openfallingtrap(&gy.youmonst, TRUE, &learn_it); } break; case WAN_LOCKING: case SPE_WIZARD_LOCK: /* similar logic to opening; invent is hit iff no trap triggered */ - if (u.utrap || !closeholdingtrap(&youmonst, &learn_it)) { - struct obj *otmp; - boolean boxing = FALSE; - - /* lock carried boxes */ - for (otmp = invent; otmp; otmp = otmp->nobj) - if (Is_box(otmp)) { - (void) boxlock(otmp, obj); - boxing = TRUE; - } - if (boxing) - update_inventory(); /* in case any box->lknown has changed */ + if (u.utrap || !closeholdingtrap(&gy.youmonst, &learn_it)) { + boxlock_invent(obj); } break; case WAN_DIGGING: @@ -2537,22 +2957,12 @@ boolean ordinary; case SPE_DETECT_UNSEEN: case WAN_NOTHING: break; - case WAN_PROBING: { - struct obj *otmp; - - for (otmp = invent; otmp; otmp = otmp->nobj) { - otmp->dknown = 1; - if (Is_container(otmp) || otmp->otyp == STATUE) { - otmp->lknown = 1; - if (!SchroedingersBox(otmp)) - otmp->cknown = 1; - } - } + case WAN_PROBING: + probe_objchain(gi.invent); update_inventory(); learn_it = TRUE; ustatusline(); break; - } case SPE_STONE_TO_FLESH: { struct obj *otmp, *onxt; boolean didmerge; @@ -2566,24 +2976,28 @@ boolean ordinary; fix_petrification(); /* saved! */ } /* but at a cost.. */ - for (otmp = invent; otmp; otmp = onxt) { + for (otmp = gi.invent; otmp; otmp = onxt) { onxt = otmp->nobj; if (bhito(otmp, obj)) learn_it = TRUE; } /* * It is possible that we can now merge some inventory. - * Do a highly paranoid merge. Restart from the beginning - * until no merges. + * Do a highly paranoid merge. Restart from the beginning until + * no merges. Don't merge worn items (in case of stone-to-flesh + * of rocks wielded in differing weapon/alt-wep/quiver slot). */ do { didmerge = FALSE; - for (otmp = invent; !didmerge && otmp; otmp = otmp->nobj) + for (otmp = gi.invent; !didmerge && otmp; otmp = otmp->nobj) { + if (otmp->owornmask) + continue; for (onxt = otmp->nobj; onxt; onxt = onxt->nobj) if (merged(&otmp, &onxt)) { didmerge = TRUE; break; } + } } while (didmerge); break; } @@ -2600,27 +3014,25 @@ boolean ordinary; /* called when poly'd hero uses breath attack against self */ void -ubreatheu(mattk) -struct attack *mattk; +ubreatheu(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(dtyp, mattk->damn, flash_str(dtyp, TRUE), u.ux, u.uy); } /* light damages hero in gremlin form */ int -lightdamage(obj, ordinary, amt) -struct obj *obj; /* item making light (fake book if spell) */ -boolean ordinary; /* wand/camera zap vs wand destruction */ -int amt; /* pseudo-damage used to determine blindness duration */ +lightdamage( + struct obj *obj, /* item making light (fake book if spell) */ + boolean ordinary, /* wand/camera zap vs wand destruction */ + int amt) /* pseudo-damage used to determine blindness duration */ { char buf[BUFSZ]; const char *how; int dmg = amt; - if (dmg && youmonst.data == &mons[PM_GREMLIN]) { + if (dmg && gy.youmonst.data == &mons[PM_GREMLIN]) { /* reduce high values (from destruction of wand with many charges) */ dmg = rnd(dmg); if (dmg > 10) @@ -2632,9 +3044,9 @@ int amt; /* pseudo-damage used to determine blindness duration */ of death will always be "killed while stuck in creature form"] */ if (obj->oclass == SCROLL_CLASS || obj->oclass == SPBOOK_CLASS) ordinary = FALSE; /* say blasted rather than zapped */ - how = (obj->oclass != SPBOOK_CLASS) - ? (const char *) ansimpleoname(obj) - : "spell of light"; + how = (obj->oclass == SPBOOK_CLASS) ? "spell of light" + : (!obj->oartifact) ? ansimpleoname(obj) + : bare_artifactname(obj); Sprintf(buf, "%s %sself with %s", ordinary ? "zapped" : "blasted", uhim(), how); /* might rehumanize(); could be fatal, but only for Unchanging */ @@ -2645,16 +3057,25 @@ int amt; /* pseudo-damage used to determine blindness duration */ /* light[ning] causes blindness */ boolean -flashburn(duration) -long duration; +flashburn(long duration, boolean via_lightning) { - if (!resists_blnd(&youmonst)) { + if (!resists_blnd(&gy.youmonst)) { You(are_blinded_by_the_flash); make_blinded(duration, FALSE); if (!Blind) Your1(vision_clears); return TRUE; } + /* if blinding is resisted due to magical equipment (Sunsword), give + a sparkle animation (even if also resisted due to being blind) + _unless_ this is lightning-induced; we don't want a double sparkle + if hero is both lightning resistant and blindness resistant, or + worse, have a single sparkle where the player confuses blindness + resistance for lightning resistance */ + if (!via_lightning && resists_blnd_by_arti(&gy.youmonst)) { + shieldeff(u.ux, u.uy); + return TRUE; + } return FALSE; } @@ -2662,14 +3083,13 @@ long duration; * Return TRUE if the steed was hit by the wand. * Return FALSE if the steed was not hit by the wand. */ -STATIC_OVL boolean -zap_steed(obj) -struct obj *obj; /* wand or spell */ +staticfn boolean +zap_steed(struct obj *obj) /* wand or spell */ { int steedhit = FALSE; - bhitpos.x = u.usteed->mx, bhitpos.y = u.usteed->my; - notonhead = FALSE; + gb.bhitpos.x = u.usteed->mx, gb.bhitpos.y = u.usteed->my; + gn.notonhead = FALSE; switch (obj->otyp) { /* * Wands that are allowed to hit the steed @@ -2727,15 +3147,13 @@ struct obj *obj; /* wand or spell */ * themselves with cancellation. */ boolean -cancel_monst(mdef, obj, youattack, allow_cancel_kill, self_cancel) -register struct monst *mdef; -register struct obj *obj; -boolean youattack, allow_cancel_kill, self_cancel; +cancel_monst(struct monst *mdef, struct obj *obj, boolean youattack, + boolean allow_cancel_kill, boolean self_cancel) { - boolean youdefend = (mdef == &youmonst); - static const char writing_vanishes[] = - "Some writing vanishes from %s head!"; - static const char your[] = "your"; /* should be extern */ + static const char + writing_vanishes[] = "Some writing vanishes from %s head!", + your[] = "your"; /* should be extern */ + boolean youdefend = (mdef == &gy.youmonst); if (youdefend ? (!youattack && Antimagic) : resist(mdef, obj->oclass, 0, NOTELL)) @@ -2744,12 +3162,14 @@ boolean youattack, allow_cancel_kill, self_cancel; if (self_cancel) { /* 1st cancel inventory */ struct obj *otmp; - for (otmp = (youdefend ? invent : mdef->minvent); otmp; + for (otmp = (youdefend ? gi.invent : mdef->minvent); otmp; otmp = otmp->nobj) cancel_item(otmp); + if (youdefend) { - context.botl = 1; /* potential AC change */ + disp.botl = TRUE; /* potential AC change */ find_ac(); + /* update_inventory(); -- handled by caller */ } } @@ -2775,19 +3195,8 @@ boolean youattack, allow_cancel_kill, self_cancel; } } else { mdef->mcan = 1; - /* force shapeshifter into its base form */ - if (M_AP_TYPE(mdef) != M_AP_NOTHING) - seemimic(mdef); - /* [not 'else if'; chameleon might have been hiding as a mimic] */ - if (mdef->cham >= LOW_PM) { - /* note: newcham() uncancels shapechangers (resets m->mcan - to 0), but only for shapechangers whose m->cham is already - NON_PM and we just verified that it's LOW_PM or higher */ - newcham(mdef, &mons[mdef->cham], FALSE, FALSE); - mdef->cham = NON_PM; /* cancelled shapeshifter can't shift */ - } - if (is_were(mdef->data) && !is_human(mdef->data)) - were_change(mdef); + /* force shapeshifter into its base form or mimic to unhide */ + normal_shape(mdef); if (mdef->data == &mons[PM_CLAY_GOLEM]) { if (canseemon(mdef)) @@ -2806,16 +3215,16 @@ boolean youattack, allow_cancel_kill, self_cancel; } /* you've zapped an immediate type wand up or down */ -STATIC_OVL boolean -zap_updown(obj) -struct obj *obj; /* wand or spell */ +staticfn boolean +zap_updown(struct obj *obj) /* wand or spell, nonnull */ { - boolean striking = FALSE, disclose = FALSE; - int x, y, xx, yy, ptmp; + boolean striking = FALSE, disclose = FALSE, map_zapped = FALSE; + coordxy x, y, xx, yy; + int ptmp; struct obj *otmp; struct engr *e; struct trap *ttmp; - char buf[BUFSZ]; + stairway *stway = gs.stairs; /* some wands have special effects other than normal bhitpile */ /* drawbridge might change */ @@ -2828,9 +3237,24 @@ struct obj *obj; /* wand or spell */ ptmp = 0; if (u.dz < 0) { You("probe towards the %s.", ceiling(x, y)); - } else { + } else { /* down */ + const char *surf; + schar ltyp, rememberedltyp = update_mapseen_for(x, y); + ptmp += bhitpile(obj, bhito, x, y, u.dz); - You("probe beneath the %s.", surface(x, y)); + /* sequencing: zap_map() calls force_decor() for ice or furniture; + we need to call it before probing for buried objects */ + ltyp = SURFACE_AT(x, y); + zap_map(x, y, obj); + /*map_zapped = TRUE; // not needed due to early return*/ + if (ltyp == ICE || IS_FURNITURE(ltyp)) { + surf = "it"; + if (svl.lastseentyp[x][y] != rememberedltyp) + ptmp += 1; + } else { + surf = the(surface(x, y)); + } + You("probe beneath %s.", surf); ptmp += display_binventory(x, y, TRUE); } if (!ptmp) @@ -2838,11 +3262,17 @@ struct obj *obj; /* wand or spell */ return TRUE; /* we've done our own bhitpile */ case WAN_OPENING: case SPE_KNOCK: + while (stway) { + if (!stway->isladder && !stway->up + && stway->tolev.dnum == u.uz.dnum) + break; + stway = stway->next; + } /* up or down, but at closed portcullis only */ if (is_db_wall(x, y) && find_drawbridge(&xx, &yy)) { open_drawbridge(xx, yy); disclose = TRUE; - } else if (u.dz > 0 && (x == xdnstair && y == ydnstair) + } else if (u.dz > 0 && stway && stway->sx == x && stway->sy == y /* can't use the stairs down to quest level 2 until leader "unlocks" them; give feedback if you try */ && on_level(&u.uz, &qstart_level) && !ok_to_quest()) { @@ -2851,15 +3281,16 @@ struct obj *obj; /* wand or spell */ } /* down will release you from bear trap or web */ if (u.dz > 0 && u.utrap) { - (void) openholdingtrap(&youmonst, &disclose); + (void) openholdingtrap(&gy.youmonst, &disclose); /* down will trigger trapdoor, hole, or [spiked-] pit */ } else if (u.dz > 0 && !u.utrap) { - (void) openfallingtrap(&youmonst, FALSE, &disclose); + (void) openfallingtrap(&gy.youmonst, FALSE, &disclose); } break; case WAN_STRIKING: case SPE_FORCE_BOLT: striking = TRUE; + FALLTHROUGH; /*FALLTHRU*/ case WAN_LOCKING: case SPE_WIZARD_LOCK: @@ -2880,7 +3311,7 @@ struct obj *obj; /* wand or spell */ /* similar to zap_dig() */ pline("A rock is dislodged from the %s and falls on your %s.", ceiling(x, y), body_part(HEAD)); - dmg = rnd((uarmh && is_metallic(uarmh)) ? 2 : 6); + dmg = rnd(hard_helmet(uarmh) ? 2 : 6); losehp(Maybe_Half_Phys(dmg), "falling rock", KILLED_BY_AN); if ((otmp = mksobj_at(ROCK, x, y, FALSE, FALSE)) != 0) { (void) xname(otmp); /* set dknown, maybe bknown */ @@ -2888,7 +3319,7 @@ struct obj *obj; /* wand or spell */ } newsym(x, y); } else if (u.dz > 0 && ttmp) { - if (!striking && closeholdingtrap(&youmonst, &disclose)) { + if (!striking && closeholdingtrap(&gy.youmonst, &disclose)) { ; /* now stuck in web or bear trap */ } else if (striking && ttmp->ttyp == TRAPDOOR) { /* striking transforms trapdoor into hole */ @@ -2904,7 +3335,7 @@ struct obj *obj; /* wand or spell */ ttmp->tseen = 1; newsym(x, y); /* might fall down hole */ - dotrap(ttmp, 0); + dotrap(ttmp, NO_TRAP_FLAGS); } else if (!striking && ttmp->ttyp == HOLE) { /* locking transforms hole into trapdoor */ ttmp->ttyp = TRAPDOOR; @@ -2952,54 +3383,25 @@ struct obj *obj; /* wand or spell */ /* zapping downward */ (void) bhitpile(obj, bhito, x, y, u.dz); - /* subset of engraving effects; none sets `disclose' */ - if ((e = engr_at(x, y)) != 0 && e->engr_type != HEADSTONE) { - switch (obj->otyp) { - case WAN_POLYMORPH: - case SPE_POLYMORPH: - del_engr(e); - make_engr_at(x, y, random_engraving(buf), moves, (xchar) 0); - break; - case WAN_CANCELLATION: - case SPE_CANCELLATION: - case WAN_MAKE_INVISIBLE: - del_engr(e); - break; - case WAN_TELEPORTATION: - case SPE_TELEPORT_AWAY: - rloc_engr(e); - break; - case SPE_STONE_TO_FLESH: - if (e->engr_type == ENGRAVE) { - /* only affects things in stone */ - pline_The(Hallucination - ? "floor runs like butter!" - : "edges on the floor get smoother."); - wipe_engr_at(x, y, d(2, 4), TRUE); - } - break; - case WAN_STRIKING: - case SPE_FORCE_BOLT: - wipe_engr_at(x, y, d(2, 4), TRUE); - break; - default: - break; - } - } + /* note: engraving handling that used to be here has been moved + to zap_map() */ + if (!map_zapped) + zap_map(x, y, obj); + } else if (u.dz < 0) { /* zapping upward */ /* game flavor: if you're hiding under "something" * a zap upward should hit that "something". */ - if (u.uundetected && hides_under(youmonst.data)) { + if (u.uundetected && hides_under(gy.youmonst.data)) { int hitit = 0; - otmp = level.objects[u.ux][u.uy]; + otmp = svl.level.objects[u.ux][u.uy]; if (otmp) hitit = bhito(otmp, obj); if (hitit) { - (void) hideunder(&youmonst); + (void) hideunder(&gy.youmonst); disclose = TRUE; } } @@ -3010,24 +3412,23 @@ struct obj *obj; /* wand or spell */ /* used by do_break_wand() was well as by weffects() */ void -zapsetup() +zapsetup(void) { - obj_zapped = FALSE; + go.obj_zapped = FALSE; } void -zapwrapup() +zapwrapup(void) { /* if do_osshock() set obj_zapped while polying, give a message now */ - if (obj_zapped) + if (go.obj_zapped) You_feel("shuddering vibrations."); - obj_zapped = FALSE; + go.obj_zapped = FALSE; } /* called for various wand and spell effects - M. Stephenson */ void -weffects(obj) -struct obj *obj; +weffects(struct obj *obj) { int otyp = obj->otyp; boolean disclose = FALSE, was_unkn = !objects[otyp].oc_name_known; @@ -3058,11 +3459,10 @@ struct obj *obj; if (otyp == WAN_DIGGING || otyp == SPE_DIG) zap_dig(); else if (otyp >= SPE_MAGIC_MISSILE && otyp <= SPE_FINGER_OF_DEATH) - buzz(otyp - SPE_MAGIC_MISSILE + 10, u.ulevel / 2 + 1, u.ux, u.uy, - u.dx, u.dy); + ubuzz(BZ_U_SPELL(BZ_OFS_SPE(otyp)), u.ulevel / 2 + 1); else if (otyp >= WAN_MAGIC_MISSILE && otyp <= WAN_LIGHTNING) - buzz(otyp - WAN_MAGIC_MISSILE, - (otyp == WAN_MAGIC_MISSILE) ? 2 : 6, u.ux, u.uy, u.dx, u.dy); + ubuzz(BZ_U_WAND(BZ_OFS_WAN(otyp)), + (otyp == WAN_MAGIC_MISSILE) ? 2 : 6); else impossible("weffects: unexpected spell or wand"); disclose = TRUE; @@ -3075,10 +3475,10 @@ struct obj *obj; return; } -/* augment damage for a spell dased on the hero's intelligence (and level) */ +/* augment damage for a spell based on the hero's intelligence (and level) */ int -spell_damage_bonus(dmg) -int dmg; /* base amount to be adjusted by bonus or penalty */ +spell_damage_bonus( + int dmg) /* base amount to be adjusted by bonus or penalty */ { int intell = ACURR(A_INT); @@ -3102,12 +3502,11 @@ int dmg; /* base amount to be adjusted by bonus or penalty */ } /* - * Generate the to hit bonus for a spell. Based on the hero's skill in + * Generate the to-hit bonus for a spell. Based on the hero's skill in * spell class and dexterity. */ -STATIC_OVL int -spell_hit_bonus(skill) -int skill; +staticfn int +spell_hit_bonus(int skill) { int hit_bon = 0; int dex = ACURR(A_DEX); @@ -3138,15 +3537,14 @@ int skill; /* Will change when print stuff below removed */ hit_bon -= 0; else - /* Even increment for dextrous heroes (see weapon.c abon) */ + /* Even increment for dexterous heroes (see weapon.c abon) */ hit_bon += dex - 14; return hit_bon; } const char * -exclam(force) -int force; +exclam(int force) { /* force == 0 occurs e.g. with sleep ray */ /* note that large force is usual with wands so that !! would @@ -3155,34 +3553,30 @@ int force; } void -hit(str, mtmp, force) -const char *str; -struct monst *mtmp; -const char *force; /* usually either "." or "!" */ +hit( + const char *str, /* zap text or missile name */ + struct monst *mtmp, /* target; for missile, might be hero */ + const char *force) /* usually either "." or "!" via exclam() */ { - if ((!cansee(bhitpos.x, bhitpos.y) && !canspotmon(mtmp) - && !(u.uswallow && mtmp == u.ustuck)) || !flags.verbose) - pline("%s %s it.", The(str), vtense(str, "hit")); - else - pline("%s %s %s%s", The(str), vtense(str, "hit"), - mon_nam(mtmp), force); + boolean verbosely = (mtmp == &gy.youmonst + || (flags.verbose + && (cansee(gb.bhitpos.x, gb.bhitpos.y) + || canspotmon(mtmp) || engulfing_u(mtmp)))); + + pline("%s %s %s%s", The(str), vtense(str, "hit"), + verbosely ? mon_nam(mtmp) : "it", force); } void -miss(str, mtmp) -register const char *str; -register struct monst *mtmp; +miss(const char *str, struct monst *mtmp) { - pline( - "%s %s %s.", The(str), vtense(str, "miss"), - ((cansee(bhitpos.x, bhitpos.y) || canspotmon(mtmp)) && flags.verbose) - ? mon_nam(mtmp) - : "it"); + pline("%s %s %s.", The(str), vtense(str, "miss"), + ((cansee(gb.bhitpos.x, gb.bhitpos.y) || canspotmon(mtmp)) + && flags.verbose) ? mon_nam(mtmp) : "it"); } -STATIC_OVL void -skiprange(range, skipstart, skipend) -int range, *skipstart, *skipend; +staticfn void +skiprange(int range, int *skipstart, int *skipend) { int tr = (range / 4); int tmp = range - ((tr > 0) ? rnd(tr) : 0); @@ -3193,6 +3587,219 @@ int range, *skipstart, *skipend; *skipend = tmp - 1; } +/* Maybe explode a trap hit by object otmp's effect; + cancellation beam hitting a magical trap causes an explosion. + Might delete the trap; won't destroy otmp. */ +staticfn void +maybe_explode_trap( + struct trap *ttmp, + struct obj *otmp, + boolean *learn_it) +{ + if (!ttmp || !otmp) + return; + if (otmp->otyp == WAN_CANCELLATION || otmp->otyp == SPE_CANCELLATION) { + coordxy x = ttmp->tx, y = ttmp->ty; + + if (undestroyable_trap(ttmp->ttyp)) { + shieldeff(x, y); + if (cansee(x, y)) { + ttmp->tseen = 1; + newsym(x, y); + *learn_it = TRUE; + } + } else if (is_magical_trap(ttmp->ttyp)) { + int seeit = cansee(x, y); + + /* note: this explosion mustn't destroy otmp */ + explode(x, y, -WAN_CANCELLATION, + 20 + d(3, 6), TRAP_EXPLODE, EXPL_MAGICAL); + deltrap(ttmp); + newsym(x, y); + if (seeit) + *learn_it = TRUE; + } + } +} + +/* zap_map() occurs before hitting monsters or objects and handles wands or + spells that don't dish out 'elemental' damage */ +staticfn void +zap_map( + coordxy x, coordxy y, + struct obj *obj) /* zapped wand, or book for cast spell */ +{ + struct trap *ttmp = t_at(x, y); + coordxy dbx = x, dby = y; /* might be changed by drawbridge handling */ + boolean learn_it = FALSE; + + /* + * We handle drawbridge for lateral zaps; zap_updown() handles up/down. + * Engravings only get hit by down zaps and we handle that here. + */ + + /* cancellation */ + maybe_explode_trap(ttmp, obj, &learn_it); + ttmp = t_at(x, y); /* refresh in case trap was altered or is gone */ + + if (u.dz > 0) { /* zapping down */ + char ebuf[BUFSZ], pristinebuf[BUFSZ], *etxt; + struct engr *e = engr_at(x, y); + + /* subset of engraving effects; none sets `disclose' */ + if (e && e->engr_type != HEADSTONE) { + switch (obj->otyp) { + case WAN_POLYMORPH: + case SPE_POLYMORPH: + del_engr(e); + etxt = random_engraving(ebuf, pristinebuf); + make_engr_at(x, y, etxt, pristinebuf, svm.moves, 0); + break; + case WAN_CANCELLATION: + case SPE_CANCELLATION: + case WAN_MAKE_INVISIBLE: + del_engr(e); + break; + case WAN_TELEPORTATION: + case SPE_TELEPORT_AWAY: + rloc_engr(e); + break; + case SPE_STONE_TO_FLESH: + if (e->engr_type == ENGRAVE) { + /* only affects things in stone */ + pline_The(Hallucination + ? "floor runs like butter!" + : "edges on the floor get smoother."); + wipe_engr_at(x, y, d(2, 4), TRUE); + } + break; + case WAN_STRIKING: + case SPE_FORCE_BOLT: + wipe_engr_at(x, y, d(2, 4), TRUE); + break; + default: + break; + } + } + + } else if (!u.dz) { + int ltyp = levl[x][y].typ; + + if (find_drawbridge(&dbx, &dby)) { + switch (obj->otyp) { + case WAN_OPENING: + case SPE_KNOCK: + /* dbwall: 'closed door' of raised drawbridge */ + if (is_db_wall(x, y)) { + if (cansee(dbx, dby) || cansee(x, y)) + learn_it = TRUE; + open_drawbridge(dbx, dby); + } + break; + case WAN_LOCKING: + case SPE_WIZARD_LOCK: + /* drawbridge_down: span of lowered drawbridge */ + if ((cansee(dbx, dby) || cansee(x, y)) + && levl[dbx][dby].typ == DRAWBRIDGE_DOWN) + learn_it = TRUE; + close_drawbridge(dbx, dby); + break; + case WAN_STRIKING: + case SPE_FORCE_BOLT: + /* !drawbridge_up: not spot in front of raised bridge, + so either span of lowered bridge or portcullis */ + if (ltyp != DRAWBRIDGE_UP) { + learn_it = TRUE; + destroy_drawbridge(dbx, dby); + } + break; + } + } /* find_drawbridge */ + } /* !u.uz */ + + if (obj->otyp == WAN_PROBING) { + /* + * Probing, either up/down or lateral. + */ + schar ltyp; + int oldtyp, oldglyph; + + /* map terrain; might reveal a special room which is already within + view that hasn't been entered yet */ + oldtyp = svl.lastseentyp[x][y]; + oldglyph = glyph_at(x, y); + show_map_spot(x, y, FALSE); + if (oldtyp != svl.lastseentyp[x][y] || oldglyph != glyph_at(x, y)) { + /* TODO: ought to give some message */ + learn_it = TRUE; + } + ltyp = SURFACE_AT(x, y); + /* secret door gets revealed, converted into regular door */ + if (ltyp == SDOOR) { + cvt_sdoor_to_door(&levl[x][y]); /* .typ = DOOR */ + recalc_block_point(x, y); + newsym(x, y); + if (cansee(x, y)) { + pline("Probing reveals a secret door."); + learn_it = TRUE; + } else if (Is_rogue_level(&u.uz)) { /* from zap_over_floor() */ + draft_message(FALSE); /* "You feel a draft." (open doorway) */ + } + + /* secret corridor likewise, although only ones within view will + still be secret; for the !cansee(x,y) case, show_map_spot() + above has already converted the spot to regular corridor */ + } else if (ltyp == SCORR) { + levl[x][y].typ = CORR; + unblock_point(x, y); + newsym(x, y); + pline("Probing exposes a secret corridor."); + learn_it = TRUE; + + /* if on or over ice, describe it ("solid ice", "thin ice", &c); + likewise for furniture in case hero is levitating while blind */ + } else if (ltyp == ICE || IS_FURNITURE(ltyp)) { + if (u.dz > 0) { /* down, which also means x,y == u.ux,u.uy */ + force_decor(TRUE); + learn_it = TRUE; + } + } + /* + * Probing reveals undiscovered traps. + * + * FIXME? This finds floor traps even when zapping up and + * ceiling traps even when zapping down. + */ + if (ttmp) { + const char *ttmpname; + unsigned t_already_seen = ttmp->tseen; + boolean use_the, hallu = Hallucination != 0; + + /* should probably be changed to use sense_trap(detect.c) + so that trap can temporarily be forced to be shown and + map browsing can take place before it reverts to being + covered by monster or object(s) */ + ttmp->tseen = 1; + newsym(x, y); + + if (!t_already_seen || hallu) { + ttmpname = trapname(ttmp->ttyp, FALSE); + use_the = !hallu ? (ttmp->ttyp == VIBRATING_SQUARE + && Invocation_lev(&u.uz)) + : !rn2(4); + You("find %s%c", + use_the ? the(ttmpname) : an(ttmpname), + use_the ? '!' : '.'); + learn_it = !hallu; + } + } /* t_at() */ + } /* probing */ + + if (learn_it) + learnwand(obj); + return; +} + /* * Called for the following distance effects: * when a weapon is thrown (weapon == THROWN_WEAPON) @@ -3201,44 +3808,49 @@ int range, *skipstart, *skipend; * when a light beam is flashed (FLASHED_LIGHT) * when a mirror is applied (INVIS_BEAM) * A thrown/kicked object falls down at end of its range or when a monster - * is hit. The variable 'bhitpos' is set to the final position of the weapon - * thrown/zapped. The ray of a wand may affect (by calling a provided + * is hit. The variable 'gb.bhitpos' is set to the final position of the + * weapon thrown/zapped. The ray of a wand may affect (by calling a provided * function) several objects and monsters on its path. The return value * is the monster hit (weapon != ZAPPED_WAND), or a null monster pointer. * - * Thrown and kicked objects (THROWN_WEAPON or KICKED_WEAPON) may be - * destroyed and *pobj set to NULL to indicate this. + * Thrown and kicked objects (THROWN_WEAPON or KICKED_WEAPON) may be + * destroyed and *pobj set to NULL to indicate this. * * Check !u.uswallow before calling bhit(). * This function reveals the absence of a remembered invisible monster in * necessary cases (throwing or kicking weapons). The presence of a real * one is revealed for a weapon, but if not a weapon is left up to fhitm(). + * + * If fhitm returns non-zero value, stops the beam and returns the monster */ struct monst * -bhit(ddx, ddy, range, weapon, fhitm, fhito, pobj) -register int ddx, ddy, range; /* direction and range */ -enum bhit_call_types weapon; /* defined in hack.h */ -int FDECL((*fhitm), (MONST_P, OBJ_P)), /* fns called when mon/obj hit */ - FDECL((*fhito), (OBJ_P, OBJ_P)); -struct obj **pobj; /* object tossed/used, set to NULL - * if object is destroyed */ +bhit( + int ddx, int ddy, int range, /* direction and range */ + enum bhit_call_types weapon, /* defined in hack.h */ + int (*fhitm)(MONST_P, OBJ_P), /* fns called when mon/obj hit */ + int (*fhito)(OBJ_P, OBJ_P), + struct obj **pobj) /* object tossed/used, set to NULL + * if object is destroyed */ { struct monst *mtmp, *result = (struct monst *) 0; struct obj *obj = *pobj; + struct trap *ttmp; uchar typ; boolean shopdoor = FALSE, point_blank = TRUE; boolean in_skip = FALSE, allow_skip = FALSE; boolean tethered_weapon = FALSE; int skiprange_start = 0, skiprange_end = 0, skipcount = 0; + struct obj *was_returning = (iflags.returning_missile == obj) ? obj + : (struct obj *) 0; if (weapon == KICKED_WEAPON) { /* object starts one square in front of player */ - bhitpos.x = u.ux + ddx; - bhitpos.y = u.uy + ddy; + gb.bhitpos.x = u.ux + ddx; + gb.bhitpos.y = u.uy + ddy; range--; } else { - bhitpos.x = u.ux; - bhitpos.y = u.uy; + gb.bhitpos.x = u.ux; + gb.bhitpos.y = u.uy; } if (weapon == THROWN_WEAPON && obj && obj->otyp == ROCK) { @@ -3256,16 +3868,17 @@ struct obj **pobj; /* object tossed/used, set to NULL tmp_at(DISP_FLASH, obj_to_glyph(obj, rn2_on_display_rng)); while (range-- > 0) { - int x, y; + coordxy x, y; + int xyglyph; - bhitpos.x += ddx; - bhitpos.y += ddy; - x = bhitpos.x; - y = bhitpos.y; + gb.bhitpos.x += ddx; + gb.bhitpos.y += ddy; + x = gb.bhitpos.x; + y = gb.bhitpos.y; if (!isok(x, y)) { - bhitpos.x -= ddx; - bhitpos.y -= ddy; + gb.bhitpos.x -= ddx; + gb.bhitpos.y -= ddy; break; } @@ -3276,62 +3889,62 @@ struct obj **pobj; /* object tossed/used, set to NULL goto bhit_done; } - typ = levl[bhitpos.x][bhitpos.y].typ; + typ = levl[x][y].typ; + + /* WATER aka "wall of water" stops items */ + if (IS_WATERWALL(typ) || typ == LAVAWALL) { + if (weapon == THROWN_WEAPON || weapon == KICKED_WEAPON) + break; + } /* iron bars will block anything big enough and break some things */ if (weapon == THROWN_WEAPON || weapon == KICKED_WEAPON) { + if (obj->lamplit && !Blind) + show_transient_light(obj, x, y); if (typ == IRONBARS - && hits_bars(pobj, x - ddx, y - ddy, bhitpos.x, bhitpos.y, + && hits_bars(pobj, x - ddx, y - ddy, x, y, point_blank ? 0 : !rn2(5), 1)) { /* caveat: obj might now be null... */ - obj = *pobj; - bhitpos.x -= ddx; - bhitpos.y -= ddy; + obj = *pobj; /* not currently needed due to 'break'; keep */ + nhUse(obj); /* in case usage gets added after the loop */ + gb.bhitpos.x -= ddx; + gb.bhitpos.y -= ddy; break; - } else if (obj->lamplit && !Blind) { - show_transient_light(obj, bhitpos.x, bhitpos.y); } + } else if (weapon == FLASHED_LIGHT) { + if (!Blind) + show_transient_light((struct obj *) 0, x, y); } - if (weapon == ZAPPED_WAND && find_drawbridge(&x, &y)) { - boolean learn_it = FALSE; + if (weapon == ZAPPED_WAND) { + /* cancellation/opening/locking/striking/probing */ + zap_map(x, y, obj); + /* terrain might have changed (exposed secret door|corridor) */ + typ = levl[x][y].typ; + } - switch (obj->otyp) { - case WAN_OPENING: - case SPE_KNOCK: - if (is_db_wall(bhitpos.x, bhitpos.y)) { - if (cansee(x, y) || cansee(bhitpos.x, bhitpos.y)) - learn_it = TRUE; - open_drawbridge(x, y); - } - break; - case WAN_LOCKING: - case SPE_WIZARD_LOCK: - if ((cansee(x, y) || cansee(bhitpos.x, bhitpos.y)) - && levl[x][y].typ == DRAWBRIDGE_DOWN) - learn_it = TRUE; - close_drawbridge(x, y); - break; - case WAN_STRIKING: - case SPE_FORCE_BOLT: - if (typ != DRAWBRIDGE_UP) - destroy_drawbridge(x, y); - learn_it = TRUE; - break; + mtmp = m_at(x, y); + ttmp = t_at(x, y); + if (!mtmp && ttmp && (ttmp->ttyp == WEB) + && (weapon == THROWN_WEAPON || weapon == KICKED_WEAPON) + && !rn2(3)) { + if (cansee(x, y)) { + pline("%s gets stuck in a web!", Yname2(obj)); + ttmp->tseen = TRUE; + newsym(x, y); } - if (learn_it) - learnwand(obj); + if (was_returning) + iflags.returning_missile = (genericptr_t) 0; + break; } - mtmp = m_at(bhitpos.x, bhitpos.y); - /* * skipping rocks * * skiprange_start is only set if this is a thrown rock */ if (skiprange_start && (range == skiprange_start) && allow_skip) { - if (is_pool(bhitpos.x, bhitpos.y) && !mtmp) { + if (is_pool(x, y) && !mtmp) { in_skip = TRUE; if (!Blind) pline("%s %s%s.", Yname2(obj), otense(obj, "skip"), @@ -3357,27 +3970,42 @@ struct obj **pobj; /* object tossed/used, set to NULL } /* if mtmp is a shade and missile passes harmlessly through it, - give message and skip it in order to keep going */ - if (mtmp && (weapon == THROWN_WEAPON || weapon == KICKED_WEAPON) - && shade_miss(&youmonst, mtmp, obj, TRUE, TRUE)) + give message and skip it in order to keep going; + if attack is light and mtmp is a mimic pretending to be an + object, behave as if there is no monster here (if pretending + to be furniture, it will be revealed by flash_hits_mon()); + thrown objects don't hit mimics pretending to be objects (both + because the hero is likely aiming to throw over what seems to + be an object rather than at it, and for balance because + otherwise mimics are too easy to identify by throwing gold at + them); exception: if the hero knows there is a monster there, + they will be aiming at the monster */ + xyglyph = glyph_at(x, y); + if (mtmp && (((weapon == THROWN_WEAPON || weapon == KICKED_WEAPON) + && (shade_miss(&gy.youmonst, mtmp, obj, TRUE, TRUE) + || (M_AP_TYPE(mtmp) == M_AP_OBJECT + && !glyph_is_monster(xyglyph) + && !glyph_is_warning(xyglyph) + && !glyph_is_invisible(xyglyph)))) + || (weapon == FLASHED_LIGHT + && M_AP_TYPE(mtmp) == M_AP_OBJECT))) mtmp = (struct monst *) 0; if (mtmp) { - notonhead = (bhitpos.x != mtmp->mx || bhitpos.y != mtmp->my); + gn.notonhead = (x != mtmp->mx || y != mtmp->my); if (weapon == FLASHED_LIGHT) { - /* FLASHED_LIGHT hitting invisible monster should - pass through instead of stop so we call - flash_hits_mon() directly rather than returning - mtmp back to caller. That allows the flash to - keep on going. Note that we use mtmp->minvis - not canspotmon() because it makes no difference - whether the hero can see the monster or not. */ + /* FLASHED_LIGHT hitting invisible monster should pass + through instead of stop so we call flash_hits_mon() + directly rather than returning mtmp back to caller. + That allows the flash to keep on going. Note that we + use mtmp->minvis, not canspotmon(), because it makes no + difference whether hero can see the monster or not. */ if (mtmp->minvis) { obj->ox = u.ux, obj->oy = u.uy; (void) flash_hits_mon(mtmp, obj); } else { tmp_at(DISP_END, 0); - result = mtmp; /* caller will call flash_hits_mon */ + result = mtmp; /* caller will call flash_hits_mon() */ goto bhit_done; } } else if (weapon == INVIS_BEAM) { @@ -3391,36 +4019,36 @@ struct obj **pobj; /* object tossed/used, set to NULL goto bhit_done; } } else if (weapon != ZAPPED_WAND) { - /* THROWN_WEAPON, KICKED_WEAPON */ if (!tethered_weapon) tmp_at(DISP_END, 0); - if (cansee(bhitpos.x, bhitpos.y) && !canspotmon(mtmp)) - map_invisible(bhitpos.x, bhitpos.y); + if (cansee(x, y) && !canspotmon(mtmp)) + map_invisible(x, y); result = mtmp; goto bhit_done; } else { /* ZAPPED_WAND */ - (*fhitm)(mtmp, obj); + if ((*fhitm)(mtmp, obj)) { + result = mtmp; + goto bhit_done; + } range -= 3; } } else { if (weapon == ZAPPED_WAND && obj->otyp == WAN_PROBING - && glyph_is_invisible(levl[bhitpos.x][bhitpos.y].glyph)) { - unmap_object(bhitpos.x, bhitpos.y); + && glyph_is_invisible(levl[x][y].glyph)) { + unmap_object(x, y); newsym(x, y); } } if (fhito) { - if (bhitpile(obj, fhito, bhitpos.x, bhitpos.y, 0)) + if (bhitpile(obj, fhito, x, y, 0)) range--; } else { if (weapon == KICKED_WEAPON - && ((obj->oclass == COIN_CLASS - && OBJ_AT(bhitpos.x, bhitpos.y)) - || ship_object(obj, bhitpos.x, bhitpos.y, - costly_spot(bhitpos.x, bhitpos.y)))) { + && ((obj->oclass == COIN_CLASS && OBJ_AT(x, y)) + || ship_object(obj, x, y, costly_spot(x, y)))) { tmp_at(DISP_END, 0); goto bhit_done; /* result == (struct monst *) 0 */ } @@ -3433,38 +4061,33 @@ struct obj **pobj; /* object tossed/used, set to NULL case SPE_KNOCK: case SPE_WIZARD_LOCK: case SPE_FORCE_BOLT: - if (doorlock(obj, bhitpos.x, bhitpos.y)) { - if (cansee(bhitpos.x, bhitpos.y) - || (obj->otyp == WAN_STRIKING && !Deaf)) + if (doorlock(obj, x, y)) { + if (cansee(x, y) || (obj->otyp == WAN_STRIKING && !Deaf)) learnwand(obj); - if (levl[bhitpos.x][bhitpos.y].doormask == D_BROKEN - && *in_rooms(bhitpos.x, bhitpos.y, SHOPBASE)) { + if (levl[x][y].doormask == D_BROKEN + && *in_rooms(x, y, SHOPBASE)) { shopdoor = TRUE; - add_damage(bhitpos.x, bhitpos.y, SHOP_DOOR_COST); + add_damage(x, y, SHOP_DOOR_COST); } } break; } } - if (!ZAP_POS(typ) || closed_door(bhitpos.x, bhitpos.y)) { - bhitpos.x -= ddx; - bhitpos.y -= ddy; + if (!ZAP_POS(typ) || closed_door(x, y)) { + gb.bhitpos.x -= ddx; + gb.bhitpos.y -= ddy; break; } if (weapon != ZAPPED_WAND && weapon != INVIS_BEAM) { - /* 'I' present but no monster: erase */ - /* do this before the tmp_at() */ - if (glyph_is_invisible(levl[bhitpos.x][bhitpos.y].glyph) - && cansee(x, y)) { - unmap_object(bhitpos.x, bhitpos.y); + /* 'I' present but no monster: erase; do this before tmp_at() */ + if (glyph_is_invisible(levl[x][y].glyph) && cansee(x, y)) { + unmap_object(x, y); newsym(x, y); } - tmp_at(bhitpos.x, bhitpos.y); - delay_output(); + tmp_at(x, y); + nh_delay_output(); /* kicked objects fall in pools */ - if ((weapon == KICKED_WEAPON) - && (is_pool(bhitpos.x, bhitpos.y) - || is_lava(bhitpos.x, bhitpos.y))) + if ((weapon == KICKED_WEAPON) && is_pool_or_lava(x, y)) break; if (IS_SINK(typ) && weapon != FLASHED_LIGHT) break; /* physical objects fall onto sink */ @@ -3499,13 +4122,16 @@ struct obj **pobj; /* object tossed/used, set to NULL point_blank = FALSE; /* affects passing through iron bars */ } - if (weapon != ZAPPED_WAND && weapon != INVIS_BEAM && !tethered_weapon) + if ((weapon != ZAPPED_WAND && weapon != INVIS_BEAM && !tethered_weapon) + || (was_returning && was_returning != iflags.returning_missile)) tmp_at(DISP_END, 0); if (shopdoor) pay_for_damage("destroy", FALSE); bhit_done: + /* note: for FLASHED_LIGHT, _caller_ must call transient_light_cleanup() + after possibly calling flash_hits_mon() */ if (weapon == THROWN_WEAPON || weapon == KICKED_WEAPON) transient_light_cleanup(); @@ -3519,16 +4145,15 @@ struct obj **pobj; /* object tossed/used, set to NULL * is too obviously silly. */ struct monst * -boomhit(obj, dx, dy) -struct obj *obj; -int dx, dy; +boomhit(struct obj *obj, int dx, int dy) { - register int i, ct; + int i, ct; int boom; /* showsym[] index */ struct monst *mtmp; - boolean counterclockwise = TRUE; /* right-handed throw */ + boolean counterclockwise = URIGHTY; /* ULEFTY => clockwise */ + int nhits = max(1, obj->spe + 1); - /* counterclockwise traversal patterns: + /* counterclockwise traversal patterns, from @ to 1 then on through to 9 * ..........................54................................. * ..................43.....6..3....765......................... * ..........32.....5..2...7...2...8...4....87.................. @@ -3541,56 +4166,67 @@ int dx, dy; * (invert rows for corresponding clockwise patterns) */ - bhitpos.x = u.ux; - bhitpos.y = u.uy; + gb.bhitpos.x = u.ux; + gb.bhitpos.y = u.uy; boom = counterclockwise ? S_boomleft : S_boomright; - for (i = 0; i < 8; i++) - if (xdir[i] == dx && ydir[i] == dy) - break; + i = xytodir(dx, dy); tmp_at(DISP_FLASH, cmap_to_glyph(boom)); for (ct = 0; ct < 10; ct++) { - i = (i + 8) % 8; /* 0..7 (8 -> 0, -1 -> 7) */ + i = DIR_CLAMP(i); boom = (S_boomleft + S_boomright - boom); /* toggle */ tmp_at(DISP_CHANGE, cmap_to_glyph(boom)); /* change glyph */ dx = xdir[i]; dy = ydir[i]; - bhitpos.x += dx; - bhitpos.y += dy; - if ((mtmp = m_at(bhitpos.x, bhitpos.y)) != 0) { + gb.bhitpos.x += dx; + gb.bhitpos.y += dy; + if (!isok(gb.bhitpos.x, gb.bhitpos.y)) { + gb.bhitpos.x -= dx; + gb.bhitpos.y -= dy; + break; + } + if ((mtmp = m_at(gb.bhitpos.x, gb.bhitpos.y)) != 0) { m_respond(mtmp); - tmp_at(DISP_END, 0); - return mtmp; + if (nhits-- < 0) { + tmp_at(DISP_END, 0); + return mtmp; + } else if (throwit_mon_hit(obj, mtmp) || !gt.thrownobj) { + break; + } } - if (!ZAP_POS(levl[bhitpos.x][bhitpos.y].typ) - || closed_door(bhitpos.x, bhitpos.y)) { - bhitpos.x -= dx; - bhitpos.y -= dy; + if (!ZAP_POS(levl[gb.bhitpos.x][gb.bhitpos.y].typ) + || closed_door(gb.bhitpos.x, gb.bhitpos.y)) { + gb.bhitpos.x -= dx; + gb.bhitpos.y -= dy; break; } - if (bhitpos.x == u.ux && bhitpos.y == u.uy) { /* ct == 9 */ + if (u_at(gb.bhitpos.x, gb.bhitpos.y)) { /* ct == 9 */ if (Fumbling || rn2(20) >= ACURR(A_DEX)) { + int dam = dmgval(obj, &gy.youmonst); + /* we hit ourselves */ - (void) thitu(10 + obj->spe, dmgval(obj, &youmonst), &obj, + (void) thitu(10 + obj->spe, Maybe_Half_Phys(dam), &obj, "boomerang"); endmultishot(TRUE); break; } else { /* we catch it */ tmp_at(DISP_END, 0); You("skillfully catch the boomerang."); - return &youmonst; + return &gy.youmonst; } } - tmp_at(bhitpos.x, bhitpos.y); - delay_output(); - if (IS_SINK(levl[bhitpos.x][bhitpos.y].typ)) { + tmp_at(gb.bhitpos.x, gb.bhitpos.y); + nh_delay_output(); + if (IS_SINK(levl[gb.bhitpos.x][gb.bhitpos.y].typ)) { + Soundeffect(se_boomerang_klonk, 75); if (!Deaf) pline("Klonk!"); + wake_nearto(gb.bhitpos.x, gb.bhitpos.y, 20); break; /* boomerang falls on sink */ } /* ct==0, initial position, we want next delta to be same; ct==5, opposite position, repeat delta undoes first one */ if (ct % 5 != 0) - i += (counterclockwise ? -1 : 1); + i = counterclockwise ? DIR_LEFT(i) : DIR_RIGHT(i); } tmp_at(DISP_END, 0); /* do not leave last symbol */ return (struct monst *) 0; @@ -3599,20 +4235,21 @@ int dx, dy; /* used by buzz(); also used by munslime(muse.c); returns damage applied to mon; note: caller is responsible for killing mon if damage is fatal */ int -zhitm(mon, type, nd, ootmp) -register struct monst *mon; -register int type, nd; -struct obj **ootmp; /* to return worn armor for caller to disintegrate */ +zhitm( + struct monst *mon, /* monster being hit */ + int type, /* zap or breath type */ + int nd, /* number of hit dice to use */ + struct obj **ootmp) /* to return worn armor for caller to disintegrate */ { - register int tmp = 0; - register int abstype = abs(type) % 10; + int tmp = 0, orig_dmg = 0; /* damage amount */ + int damgtype = zaptype(type) % 10; boolean sho_shieldeff = FALSE; boolean spellcaster = is_hero_spell(type); /* maybe get a bonus! */ *ootmp = (struct obj *) 0; - switch (abstype) { + switch (damgtype) { case ZT_MAGIC_MISSILE: - if (resists_magm(mon)) { + if (resists_magm(mon) || defended(mon, AD_MAGM)) { sho_shieldeff = TRUE; break; } @@ -3621,39 +4258,40 @@ struct obj **ootmp; /* to return worn armor for caller to disintegrate */ tmp = spell_damage_bonus(tmp); break; case ZT_FIRE: - if (resists_fire(mon)) { + if (resists_fire(mon) || defended(mon, AD_FIRE)) { sho_shieldeff = TRUE; break; } tmp = d(nd, 6); - if (resists_cold(mon)) - tmp += 7; if (spellcaster) tmp = spell_damage_bonus(tmp); + orig_dmg = tmp; /* includes spell bonus but not monster vuln to fire */ + if (resists_cold(mon)) + tmp += 7; if (burnarmor(mon)) { - if (!rn2(3)) - (void) destroy_mitem(mon, POTION_CLASS, AD_FIRE); - if (!rn2(3)) - (void) destroy_mitem(mon, SCROLL_CLASS, AD_FIRE); - if (!rn2(5)) - (void) destroy_mitem(mon, SPBOOK_CLASS, AD_FIRE); - destroy_mitem(mon, FOOD_CLASS, AD_FIRE); /* carried slime */ + if (!rn2(3)) { + tmp += destroy_items(mon, AD_FIRE, orig_dmg); + ignite_items(mon->minvent); + } } break; case ZT_COLD: - if (resists_cold(mon)) { + if (resists_cold(mon) || defended(mon, AD_COLD)) { sho_shieldeff = TRUE; break; } tmp = d(nd, 6); - if (resists_fire(mon)) - tmp += d(nd, 3); if (spellcaster) tmp = spell_damage_bonus(tmp); + orig_dmg = tmp; /* includes spell bonus but not monster vuln to cold */ + if (resists_fire(mon)) + tmp += d(nd, 3); if (!rn2(3)) - (void) destroy_mitem(mon, POTION_CLASS, AD_COLD); + tmp += destroy_items(mon, AD_COLD, orig_dmg); break; case ZT_SLEEP: + /* resistance and shield effect and revealing concealed mimic are + handled by sleep_monst() */ tmp = 0; (void) sleep_monst(mon, d(nd, 25), type == ZT_WAND(ZT_SLEEP) ? WAND_CLASS : '\0'); @@ -3661,10 +4299,9 @@ struct obj **ootmp; /* to return worn armor for caller to disintegrate */ case ZT_DEATH: /* death/disintegration */ if (abs(type) != ZT_BREATH(ZT_DEATH)) { /* death */ if (mon->data == &mons[PM_DEATH]) { - mon->mhpmax += mon->mhpmax / 2; + healmon(mon, mon->mhpmax * 3 / 2, mon->mhpmax / 2); if (mon->mhpmax >= MAGIC_COOKIE) mon->mhpmax = MAGIC_COOKIE - 1; - mon->mhp = mon->mhpmax; tmp = 0; break; } @@ -3678,18 +4315,18 @@ struct obj **ootmp; /* to return worn armor for caller to disintegrate */ } else { struct obj *otmp2; - if (resists_disint(mon)) { + if (resists_disint(mon) || defended(mon, AD_DISN)) { sho_shieldeff = TRUE; } else if (mon->misc_worn_check & W_ARMS) { /* destroy shield; victim survives */ *ootmp = which_armor(mon, W_ARMS); } else if (mon->misc_worn_check & W_ARM) { - /* destroy body armor, also cloak if present */ + /* destroy suit, also cloak if present */ *ootmp = which_armor(mon, W_ARM); if ((otmp2 = which_armor(mon, W_ARMC)) != 0) m_useup(mon, otmp2); } else { - /* no body armor, victim dies; destroy cloak + /* no suit, victim dies; destroy cloak and shirt now in case target gets life-saved */ tmp = MAGIC_COOKIE; if ((otmp2 = which_armor(mon, W_ARMC)) != 0) @@ -3703,17 +4340,20 @@ struct obj **ootmp; /* to return worn armor for caller to disintegrate */ tmp = mon->mhp + 1; break; case ZT_LIGHTNING: - if (resists_elec(mon)) { + tmp = d(nd, 6); + if (spellcaster) + tmp = spell_damage_bonus(tmp); + orig_dmg = tmp; + if (resists_elec(mon) || defended(mon, AD_ELEC)) { sho_shieldeff = TRUE; tmp = 0; /* can still blind the monster */ - } else - tmp = d(nd, 6); - if (spellcaster) - tmp = spell_damage_bonus(tmp); + } if (!resists_blnd(mon) - && !(type > 0 && u.uswallow && mon == u.ustuck)) { - register unsigned rnd_tmp = rnd(50); + && !(type > 0 && engulfing_u(mon)) + && nd > 2) { + /* sufficiently powerful lightning blinds monsters */ + unsigned rnd_tmp = rnd(50); mon->mcansee = 0; if ((mon->mblinded + rnd_tmp) > 127) mon->mblinded = 127; @@ -3721,20 +4361,17 @@ struct obj **ootmp; /* to return worn armor for caller to disintegrate */ mon->mblinded += rnd_tmp; } if (!rn2(3)) - (void) destroy_mitem(mon, WAND_CLASS, AD_ELEC); - /* not actually possible yet */ - if (!rn2(3)) - (void) destroy_mitem(mon, RING_CLASS, AD_ELEC); + tmp += destroy_items(mon, AD_ELEC, orig_dmg); break; case ZT_POISON_GAS: - if (resists_poison(mon)) { + if (resists_poison(mon) || defended(mon, AD_DRST)) { sho_shieldeff = TRUE; break; } tmp = d(nd, 6); break; case ZT_ACID: - if (resists_acid(mon)) { + if (resists_acid(mon) || defended(mon, AD_ACID)) { sho_shieldeff = TRUE; break; } @@ -3760,112 +4397,130 @@ struct obj **ootmp; /* to return worn armor for caller to disintegrate */ return tmp; } -STATIC_OVL void -zhitu(type, nd, fltxt, sx, sy) -int type, nd; -const char *fltxt; -xchar sx, sy; +staticfn void +zhitu( + int type, int nd, + const char *fltxt, + coordxy sx, coordxy sy) { - int dam = 0, abstyp = abs(type); + int dam = 0, abstyp = zaptype(type); + int orig_dam = 0; switch (abstyp % 10) { case ZT_MAGIC_MISSILE: if (Antimagic) { shieldeff(sx, sy); pline_The("missiles bounce off!"); + monstseesu(M_SEEN_MAGR); } else { dam = d(nd, 6); exercise(A_STR, FALSE); + monstunseesu(M_SEEN_MAGR); } break; case ZT_FIRE: + orig_dam = d(nd, 6); if (Fire_resistance) { shieldeff(sx, sy); You("don't feel hot!"); - ugolemeffects(AD_FIRE, d(nd, 6)); + monstseesu(M_SEEN_FIRE); + ugolemeffects(AD_FIRE, orig_dam); } else { - dam = d(nd, 6); + dam = orig_dam; + monstunseesu(M_SEEN_FIRE); } burn_away_slime(); - if (burnarmor(&youmonst)) { /* "body hit" */ + if (burnarmor(&gy.youmonst)) { /* "body hit" */ if (!rn2(3)) - destroy_item(POTION_CLASS, AD_FIRE); + (void) destroy_items(&gy.youmonst, AD_FIRE, orig_dam); if (!rn2(3)) - destroy_item(SCROLL_CLASS, AD_FIRE); - if (!rn2(5)) - destroy_item(SPBOOK_CLASS, AD_FIRE); - destroy_item(FOOD_CLASS, AD_FIRE); + ignite_items(gi.invent); } break; case ZT_COLD: + orig_dam = d(nd, 6); if (Cold_resistance) { shieldeff(sx, sy); You("don't feel cold."); - ugolemeffects(AD_COLD, d(nd, 6)); + monstseesu(M_SEEN_COLD); + ugolemeffects(AD_COLD, orig_dam); } else { - dam = d(nd, 6); + dam = orig_dam; + monstunseesu(M_SEEN_COLD); } if (!rn2(3)) - destroy_item(POTION_CLASS, AD_COLD); + (void) destroy_items(&gy.youmonst, AD_COLD, orig_dam); break; case ZT_SLEEP: if (Sleep_resistance) { shieldeff(u.ux, u.uy); You("don't feel sleepy."); + monstseesu(M_SEEN_SLEEP); } else { + monstunseesu(M_SEEN_SLEEP); fall_asleep(-d(nd, 25), TRUE); /* sleep ray */ } break; case ZT_DEATH: if (abstyp == ZT_BREATH(ZT_DEATH)) { + boolean disn_prot = inventory_resistance_check(AD_DISN); + if (Disint_resistance) { You("are not disintegrated."); + monstseesu(M_SEEN_DISINT); break; - } else if (uarms) { + } else if (disn_prot) { + break; + } + monstunseesu(M_SEEN_DISINT); + if (uarms) { /* destroy shield; other possessions are safe */ - (void) destroy_arm(uarms); + (void) disintegrate_arm(uarms); break; } else if (uarm) { /* destroy suit; if present, cloak goes too */ if (uarmc) - (void) destroy_arm(uarmc); - (void) destroy_arm(uarm); + (void) disintegrate_arm(uarmc); + (void) disintegrate_arm(uarm); break; } /* no shield or suit, you're dead; wipe out cloak and/or shirt in case of life-saving or bones */ if (uarmc) - (void) destroy_arm(uarmc); + (void) disintegrate_arm(uarmc); if (uarmu) - (void) destroy_arm(uarmu); - } else if (nonliving(youmonst.data) || is_demon(youmonst.data)) { + (void) disintegrate_arm(uarmu); + } else if (nonliving(gy.youmonst.data) || is_demon(gy.youmonst.data)) { shieldeff(sx, sy); You("seem unaffected."); break; } else if (Antimagic) { shieldeff(sx, sy); + monstseesu(M_SEEN_MAGR); You("aren't affected."); break; } - killer.format = KILLED_BY_AN; - Strcpy(killer.name, fltxt ? fltxt : ""); + monstunseesu(M_SEEN_MAGR); + svk.killer.format = KILLED_BY_AN; + Strcpy(svk.killer.name, fltxt ? fltxt : ""); /* when killed by disintegration breath, don't leave corpse */ u.ugrave_arise = (type == -ZT_BREATH(ZT_DEATH)) ? -3 : NON_PM; done(DIED); return; /* lifesaved */ case ZT_LIGHTNING: + orig_dam = d(nd, 6); if (Shock_resistance) { shieldeff(sx, sy); You("aren't affected."); - ugolemeffects(AD_ELEC, d(nd, 6)); + monstseesu(M_SEEN_ELEC); + ugolemeffects(AD_ELEC, orig_dam); } else { - dam = d(nd, 6); + dam = orig_dam; exercise(A_CON, FALSE); + monstunseesu(M_SEEN_ELEC); } if (!rn2(3)) - destroy_item(WAND_CLASS, AD_ELEC); - if (!rn2(3)) - destroy_item(RING_CLASS, AD_ELEC); + (void) destroy_items(&gy.youmonst, AD_ELEC, orig_dam); break; case ZT_POISON_GAS: poisoned("blast", A_DEX, "poisoned blast", 15, FALSE); @@ -3873,11 +4528,13 @@ xchar sx, sy; case ZT_ACID: if (Acid_resistance) { pline_The("%s doesn't hurt.", hliquid("acid")); + monstseesu(M_SEEN_ACID); dam = 0; } else { pline_The("%s burns!", hliquid("acid")); dam = d(nd, 6); exercise(A_STR, FALSE); + monstunseesu(M_SEEN_ACID); } /* using two weapons at once makes both of them more vulnerable */ if (!rn2(u.twoweap ? 3 : 6)) @@ -3885,15 +4542,51 @@ xchar sx, sy; if (u.twoweap && !rn2(3)) acid_damage(uswapwep); if (!rn2(6)) - erode_armor(&youmonst, ERODE_CORRODE); + erode_armor(&gy.youmonst, ERODE_CORRODE); break; } - /* Half_spell_damage protection yields half-damage for wands & spells, - including hero's own ricochets; breath attacks do full damage */ - if (dam && Half_spell_damage && !(abstyp >= 20 && abstyp <= 29)) - dam = (dam + 1) / 2; - losehp(dam, fltxt, KILLED_BY_AN); + /* + * 5.0: when fatal, this used to yield "Killed by ." without any + * information about who was responsible. Now 'buzzer' is used to try + * to supply "zapped/cast/breathed by [imitating ]." + * + * Room for improvement: there is no monster available when player is + * hit by divine lighting or by Plane of Air thunderstorm so cause of + * death remains "killed by a bolt of lightning" w/o extra explanation. + * + * Wand of death, spell of finger of death, and disintegration breath + * don't use this routine so don't include 'inflicted by'. + */ + { + char kbuf[BUFSZ]; + struct obj *otmp = gc.current_wand; + /* fire horn and frost horn get handled as wands by caller */ + const char *verb = (abstyp < 10) /* wand */ + ? ((otmp && otmp->oclass == TOOL_CLASS) ? "played" + : "zapped") + : (abstyp < 20) ? "cast" + : (abstyp < 30) ? "exhaled" + : "imagined"; /* should never happen */ + + if (type < 0 || (type == 0 && gb.buzzer != 0)) { + /* if gb.buzzer is Null, kbuf[] will end up with just */ + (void) death_inflicted_by(kbuf, fltxt, gb.buzzer); + /* change "death inflicted by mon" to "death by mon" */ + if (gb.buzzer) + (void) strsubst(kbuf, "inflicted", verb); + } else { + /* FIXME: "zapped by herself" is suitable for a rebound; + "zapped at herself" would be better if player explicitly + targeted hero */ + Sprintf(kbuf, "%s %s by %sself", fltxt, verb, uhim()); + } + /* Half_spell_damage protection yields half-damage for wands & spells, + including hero's own ricochets; breath attacks do full damage */ + if (dam && Half_spell_damage && abstyp < 20) + dam = (dam + 1) / 2; + losehp(dam, kbuf, KILLED_BY_AN); + } return; } @@ -3902,17 +4595,17 @@ xchar sx, sy; * at position x,y; return the number of objects burned */ int -burn_floor_objects(x, y, give_feedback, u_caused) -int x, y; -boolean give_feedback; /* caller needs to decide about visibility checks */ -boolean u_caused; +burn_floor_objects( + coordxy x, coordxy y, + boolean give_feedback, /* caller needs to decide about visibility checks */ + boolean u_caused) { struct obj *obj, *obj2; long i, scrquan, delquan; char buf1[BUFSZ], buf2[BUFSZ]; int cnt = 0; - for (obj = level.objects[x][y]; obj; obj = obj2) { + for (obj = svl.level.objects[x][y]; obj; obj = obj2) { obj2 = obj->nexthere; if (obj->oclass == SCROLL_CLASS || obj->oclass == SPBOOK_CLASS || (obj->oclass == FOOD_CLASS @@ -3929,11 +4622,11 @@ boolean u_caused; /* save name before potential delobj() */ if (give_feedback) { obj->quan = 1L; - Strcpy(buf1, (x == u.ux && y == u.uy) + Strcpy(buf1, u_at(x, y) ? xname(obj) : distant_name(obj, xname)); obj->quan = 2L; - Strcpy(buf2, (x == u.ux && y == u.uy) + Strcpy(buf2, u_at(x, y) ? xname(obj) : distant_name(obj, xname)); obj->quan = scrquan; @@ -3941,9 +4634,10 @@ boolean u_caused; /* useupf(), which charges, only if hero caused damage */ if (u_caused) useupf(obj, delquan); - else if (delquan < scrquan) + else if (delquan < scrquan) { obj->quan -= delquan; - else + obj->owt = weight(obj); + } else delobj(obj); cnt += delquan; if (give_feedback) { @@ -3955,14 +4649,62 @@ boolean u_caused; } } } + /* This also ignites floor items, but does not change cnt + because they weren't consumed. */ + ignite_items(svl.level.objects[x][y]); return cnt; } +/* which direction a ray bounces. + current location is sx,sy, direction is ddx, ddy. + bounceback is 1/n chance of bouncing back. + caller must ensure sx,sy is a bouncing location: !ZAP_POS or closed_door + */ +staticfn void +bounce_dir(coordxy sx, coordxy sy, + int *ddx, int *ddy, + int bounceback) +{ + if (!*ddx || !*ddy || (bounceback > 0 && !rn2(bounceback))) { + *ddx = -(*ddx); + *ddy = -(*ddy); + } else { + uchar rmn; + int bounce = 0; + coordxy lsy = sy - *ddy; + coordxy lsx = sx - *ddx; + + if (isok(sx, lsy) && ZAP_POS(rmn = levl[sx][lsy].typ) + && !closed_door(sx, lsy) + && (IS_ROOM(rmn) || (isok(sx + *ddx, lsy) + && ZAP_POS(levl[sx + *ddx][lsy].typ)))) + bounce = 1; + if (isok(lsx, sy) && ZAP_POS(rmn = levl[lsx][sy].typ) + && !closed_door(lsx, sy) + && (IS_ROOM(rmn) || (isok(lsx, sy + *ddy) + && ZAP_POS(levl[lsx][sy + *ddy].typ)))) + if (!bounce || rn2(2)) + bounce = 2; + switch (bounce) { + case 0: + *ddx = -(*ddx); + FALLTHROUGH; + /*FALLTHRU*/ + case 1: + *ddy = -(*ddy); + break; + case 2: + *ddx = -(*ddx); + break; + } + } +} + /* will zap/spell/breath attack score a hit against armor class `ac'? */ -STATIC_OVL int -zap_hit(ac, type) -int ac; -int type; /* either hero cast spell type or 0 */ +staticfn int +zap_hit( + int ac, + int type) /* either hero cast spell type or 0 */ { int chance = rn2(20); int spell_bonus = type ? spell_hit_bonus(type) : 0; @@ -3977,11 +4719,11 @@ int type; /* either hero cast spell type or 0 */ return (3 - chance < ac + spell_bonus); } -STATIC_OVL void -disintegrate_mon(mon, type, fltxt) -struct monst *mon; -int type; /* hero vs other */ -const char *fltxt; +staticfn void +disintegrate_mon( + struct monst *mon, + int type, /* hero vs other */ + const char *fltxt) { struct obj *otmp, *otmp2, *m_amulet = mlifesaver(mon); @@ -4000,16 +4742,7 @@ const char *fltxt; for (otmp = mon->minvent; otmp; otmp = otmp2) { otmp2 = otmp->nobj; if (!oresist_disintegration(otmp)) { - if (otmp->owornmask) { - /* in case monster's life gets saved */ - mon->misc_worn_check &= ~otmp->owornmask; - if (otmp->owornmask & W_WEP) - setmnotwielded(mon, otmp); - /* also dismounts hero if this object is steed's saddle */ - update_mon_intrinsics(mon, otmp, FALSE, TRUE); - otmp->owornmask = 0L; - } - obj_extract_self(otmp); + extract_from_minvent(mon, otmp, TRUE, TRUE); obfree(otmp, (struct obj *) 0); } } @@ -4023,59 +4756,66 @@ const char *fltxt; } void -buzz(type, nd, sx, sy, dx, dy) -int type, nd; -xchar sx, sy; -int dx, dy; +ubuzz(int type, int nd) +{ + dobuzz(type, nd, u.ux, u.uy, u.dx, u.dy, TRUE, FALSE, FALSE); +} + +void +buzz(int type, int nd, coordxy sx, coordxy sy, int dx, int dy) { - dobuzz(type, nd, sx, sy, dx, dy, TRUE); + dobuzz(type, nd, sx, sy, dx, dy, TRUE, FALSE, FALSE); } /* - * type == 0 to 9 : you shooting a wand + * type == 0 to 9 : you zapping a wand * type == 10 to 19 : you casting a spell * type == 20 to 29 : you breathing as a monster * type == -10 to -19 : monster casting spell * type == -20 to -29 : monster breathing at you - * type == -30 to -39 : monster shooting a wand - * called with dx = dy = 0 with vertical bolts + * type == -30 to -39 : monster zapping a wand + * called with dx = dy = 0 for vertical bolts */ void -dobuzz(type, nd, sx, sy, dx, dy, say) -register int type, nd; -register xchar sx, sy; -register int dx, dy; -boolean say; /* Announce out of sight hit/miss events if true */ +dobuzz( + int type, /* 0..29 (by hero) or -39..-10 (by monster) */ + int nd, /* damage strength ('number of dice') */ + coordxy sx, coordxy sy, /* starting point */ + int dx, int dy, /* direction delta */ + boolean sayhit, boolean saymiss, /* report out of sight hit/miss events */ + boolean forcemiss) { - int range, abstype = abs(type) % 10; - register xchar lsx, lsy; + int range, fltyp = zaptype(type), damgtype = fltyp % 10; + coordxy lsx, lsy; struct monst *mon; coord save_bhitpos; - boolean shopdamage = FALSE; - const char *fltxt; + boolean shopdamage = FALSE, + fireball = (type == ZT_SPELL(ZT_FIRE)), /* set once */ + gas_hit = FALSE; /* will be set during each iteration */ struct obj *otmp; int spell_type; + int hdmgtype = Hallucination ? rn2(6) : damgtype; - /* if its a Hero Spell then get its SPE_TYPE */ - spell_type = is_hero_spell(type) ? SPE_MAGIC_MISSILE + abstype : 0; + /* if it's a hero spell then get its SPE_TYPE */ + spell_type = is_hero_spell(type) ? SPE_MAGIC_MISSILE + damgtype : 0; - fltxt = flash_types[(type <= -30) ? abstype : abs(type)]; if (u.uswallow) { - register int tmp; + int tmp; if (type < 0) return; tmp = zhitm(u.ustuck, type, nd, &otmp); - if (!u.ustuck) + if (!u.ustuck) { u.uswallow = 0; - else - pline("%s rips into %s%s", The(fltxt), mon_nam(u.ustuck), - exclam(tmp)); - /* Using disintegration from the inside only makes a hole... */ - if (tmp == MAGIC_COOKIE) - u.ustuck->mhp = 0; - if (DEADMONSTER(u.ustuck)) - killed(u.ustuck); + } else { + pline("%s rips into %s%s", The(flash_str(fltyp, FALSE)), + mon_nam(u.ustuck), exclam(tmp)); + /* Using disintegration from the inside only makes a hole... */ + if (tmp == MAGIC_COOKIE) + u.ustuck->mhp = 0; + if (DEADMONSTER(u.ustuck)) + killed(u.ustuck); + } return; } if (type < 0) @@ -4083,9 +4823,9 @@ boolean say; /* Announce out of sight hit/miss events if true */ range = rn1(7, 7); if (dx == 0 && dy == 0) range = 1; - save_bhitpos = bhitpos; + save_bhitpos = gb.bhitpos; - tmp_at(DISP_BEAM, zapdir_to_glyph(dx, dy, abstype)); + tmp_at(DISP_BEAM, zapdir_to_glyph(dx, dy, hdmgtype)); while (range-- > 0) { lsx = sx; sx += dx; @@ -4104,33 +4844,39 @@ boolean say; /* Announce out of sight hit/miss events if true */ if (ZAP_POS(levl[sx][sy].typ) || (isok(lsx, lsy) && cansee(lsx, lsy))) tmp_at(sx, sy); - delay_output(); /* wait a little */ - } - - /* hit() and miss() need bhitpos to match the target */ - bhitpos.x = sx, bhitpos.y = sy; - /* Fireballs only damage when they explode */ - if (type != ZT_SPELL(ZT_FIRE)) { - range += zap_over_floor(sx, sy, type, &shopdamage, 0); + nh_delay_output(); /* wait a little */ + } + + /* hit() and miss() need gb.bhitpos to match the target */ + gb.bhitpos.x = sx, gb.bhitpos.y = sy; + gas_hit = (damgtype == ZT_POISON_GAS); + /* fireballs only damage when they explode; poison gas leaves + a trail of 1x1 clouds via zap_over_floor(), but that gets + skipped for a hit that is reflected so is deferred until we + know whether reflection is happening */ + if (!fireball && !gas_hit) { + range += zap_over_floor(sx, sy, type, &shopdamage, TRUE, 0); /* zap with fire -> melt ice -> drown monster, so monster found and cached above might not be here any more */ mon = m_at(sx, sy); } if (mon) { - if (type == ZT_SPELL(ZT_FIRE)) + if (fireball) break; if (type >= 0) mon->mstrategy &= ~STRAT_WAITMASK; buzzmonst: - notonhead = (mon->mx != bhitpos.x || mon->my != bhitpos.y); - if (zap_hit(find_mac(mon), spell_type)) { + gn.notonhead = (mon->mx != gb.bhitpos.x + || mon->my != gb.bhitpos.y); + if (!forcemiss && 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(fltyp, FALSE), mon, exclam(0)); shieldeff(mon->mx, mon->my); (void) mon_reflects(mon, "But it reflects from %s %s!"); + gas_hit = FALSE; } dx = -dx; dy = -dy; @@ -4141,11 +4887,11 @@ 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(fltyp, FALSE), mon, "."); pline("%s disintegrates.", Monnam(mon)); pline("%s body reintegrates before your %s!", s_suffix(Monnam(mon)), - (eyecount(youmonst.data) == 1) + (eyecount(gy.youmonst.data) == 1) ? body_part(EYE) : makeplural(body_part(EYE))); pline("%s resurrects!", Monnam(mon)); @@ -4153,9 +4899,9 @@ boolean say; /* Announce out of sight hit/miss events if true */ mon->mhp = mon->mhpmax; break; /* Out of while loop */ } - if (mon->data == &mons[PM_DEATH] && abstype == ZT_DEATH) { + if (mon->data == &mons[PM_DEATH] && damgtype == ZT_DEATH) { if (canseemon(mon)) { - hit(fltxt, mon, "."); + hit(flash_str(fltyp, FALSE), mon, "."); pline("%s absorbs the deadly %s!", Monnam(mon), type == ZT_BREATH(ZT_DEATH) ? "blast" : "ray"); @@ -4165,11 +4911,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(fltyp, 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(fltyp, FALSE), AD_RBRE); } else { int xkflags = XKILL_GIVEMSG; /* killed(mon); */ @@ -4177,7 +4923,7 @@ boolean say; /* Announce out of sight hit/miss events if true */ if it's fire, highly flammable monsters leave no corpse; don't bother reporting that they "burn completely" -- unnecessary verbosity */ - if ((type % 10 == ZT_FIRE) + if (damgtype == ZT_FIRE /* paper golem or straw golem */ && completelyburns(mon->data)) xkflags |= XKILL_NOCORPSE; @@ -4186,8 +4932,8 @@ boolean say; /* Announce out of sight hit/miss events if true */ } else { if (!otmp) { /* normal non-fatal hit */ - if (say || canseemon(mon)) - hit(fltxt, mon, exclam(tmp)); + if (sayhit || canseemon(mon)) + hit(flash_str(fltyp, FALSE), mon, exclam(tmp)); } else { /* some armor was destroyed; no damage done */ if (canseemon(mon)) @@ -4198,60 +4944,70 @@ boolean say; /* Announce out of sight hit/miss events if true */ } if (mon_could_move && !mon->mcanmove) /* ZT_SLEEP */ slept_monst(mon); + if (damgtype != ZT_SLEEP) + wakeup(mon, (type >= 0) ? TRUE : FALSE); } } range -= 2; } else { - if (say || canseemon(mon)) - miss(fltxt, mon); + if (saymiss + || (canseemon(mon) && !disguised_as_non_mon(mon))) + miss(flash_str(fltyp, FALSE), mon); } - } else if (sx == u.ux && sy == u.uy && range >= 0) { + } else if (u_at(sx, sy) && range >= 0) { nomul(0); if (u.usteed && !rn2(3) && !mon_reflects(u.usteed, (char *) 0)) { mon = u.usteed; goto buzzmonst; - } else if (zap_hit((int) u.uac, 0)) { + } else if (!forcemiss && zap_hit((int) u.uac, 0)) { range -= 2; - pline("%s hits you!", The(fltxt)); + pline_dir(xytodir(-dx, -dy), "%s hits you!", + The(flash_str(fltyp, FALSE))); if (Reflecting) { if (!Blind) { (void) ureflects("But %s reflects from your %s!", "it"); } else pline("For some reason you are not affected."); + monstseesu(M_SEEN_REFL); dx = -dx; dy = -dy; shieldeff(sx, sy); + gas_hit = FALSE; } else { - zhitu(type, nd, fltxt, sx, sy); + /* flash_str here only used for killer; suppress + * hallucination */ + zhitu(type, nd, flash_str(fltyp, TRUE), sx, sy); + monstunseesu(M_SEEN_REFL); } } else if (!Blind) { - pline("%s whizzes by you!", The(fltxt)); - } else if (abstype == ZT_LIGHTNING) { + pline("%s whizzes by you!", The(flash_str(fltyp, FALSE))); + } else if (damgtype == ZT_LIGHTNING) { Your("%s tingles.", body_part(ARM)); } - if (abstype == ZT_LIGHTNING) - (void) flashburn((long) d(nd, 50)); + if (damgtype == ZT_LIGHTNING) + (void) flashburn((long) d(nd, 50), TRUE); stop_occupation(); nomul(0); } + /* gas that missed or that hit without being reflected will leave + a 1x1 cloud here; the earlier zap_over_floor() was deferred */ + if (gas_hit) + (void) zap_over_floor(sx, sy, type, &shopdamage, TRUE, 0); if (!ZAP_POS(levl[sx][sy].typ) || (closed_door(sx, sy) && range >= 0)) { - int bounce, bchance; - uchar rmn; - boolean fireball; + int bchance; make_bounce: - bchance = (levl[sx][sy].typ == STONE) ? 10 - : (In_mines(&u.uz) && IS_WALL(levl[sx][sy].typ)) ? 20 - : 75; - bounce = 0; - fireball = (type == ZT_SPELL(ZT_FIRE)); + bchance = (!isok(sx, sy) || levl[sx][sy].typ == STONE) ? 10 + : (In_mines(&u.uz) && IS_WALL(levl[sx][sy].typ)) ? 20 + : 75; 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(fltyp, FALSE)); if (fireball) type = ZT_WAND(ZT_FIRE); /* skip pending fireball */ break; @@ -4260,61 +5016,28 @@ 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); - } - if (!dx || !dy || !rn2(bchance)) { - dx = -dx; - dy = -dy; - } else { - if (isok(sx, lsy) && ZAP_POS(rmn = levl[sx][lsy].typ) - && !closed_door(sx, lsy) - && (IS_ROOM(rmn) || (isok(sx + dx, lsy) - && ZAP_POS(levl[sx + dx][lsy].typ)))) - bounce = 1; - if (isok(lsx, sy) && ZAP_POS(rmn = levl[lsx][sy].typ) - && !closed_door(lsx, sy) - && (IS_ROOM(rmn) || (isok(lsx, sy + dy) - && ZAP_POS(levl[lsx][sy + dy].typ)))) - if (!bounce || rn2(2)) - bounce = 2; - - switch (bounce) { - case 0: - dx = -dx; - /*FALLTHRU*/ - case 1: - dy = -dy; - break; - case 2: - dx = -dx; - break; - } - tmp_at(DISP_CHANGE, zapdir_to_glyph(dx, dy, abstype)); + pline_The("%s bounces!", flash_str(fltyp, FALSE)); } + bounce_dir(sx, sy, &dx, &dy, bchance); + tmp_at(DISP_CHANGE, zapdir_to_glyph(dx, dy, hdmgtype)); } } tmp_at(DISP_END, 0); - if (type == ZT_SPELL(ZT_FIRE)) + if (fireball) explode(sx, sy, type, d(12, 6), 0, EXPL_FIERY); if (shopdamage) - pay_for_damage(abstype == ZT_FIRE - ? "burn away" - : abstype == ZT_COLD - ? "shatter" - /* "damage" indicates wall rather than door */ - : abstype == ZT_ACID - ? "damage" - : abstype == ZT_DEATH - ? "disintegrate" - : "destroy", + pay_for_damage(damgtype == ZT_FIRE ? "burn away" + : damgtype == ZT_COLD ? "shatter" + /* "damage" indicates wall rather than door */ + : damgtype == ZT_ACID ? "damage" + : damgtype == ZT_DEATH ? "disintegrate" + : "destroy", FALSE); - bhitpos = save_bhitpos; + gb.bhitpos = save_bhitpos; } void -melt_ice(x, y, msg) -xchar x, y; -const char *msg; +melt_ice(coordxy x, coordxy y, const char *msg) { struct rm *lev = &levl[x][y]; struct obj *otmp; @@ -4325,23 +5048,18 @@ const char *msg; if (lev->typ == DRAWBRIDGE_UP || lev->typ == DRAWBRIDGE_DOWN) { lev->drawbridgemask &= ~DB_ICE; /* revert to DB_MOAT */ } else { /* lev->typ == ICE */ -#ifdef STUPID - if (lev->icedpool == ICED_POOL) - lev->typ = POOL; - else - lev->typ = MOAT; -#else lev->typ = (lev->icedpool == ICED_POOL ? POOL : MOAT); -#endif lev->icedpool = 0; } spot_stop_timers(x, y, MELT_ICE_AWAY); /* no more ice to melt away */ + if (t_at(x, y)) + trap_ice_effects(x, y, TRUE); /* TRUE because ice_is_melting */ obj_ice_effects(x, y, FALSE); unearth_objs(x, y); if (Underwater) vision_recalc(1); newsym(x, y); - if (cansee(x, y)) + if (cansee(x, y) || u_at(x, y)) Norep("%s", msg); if ((otmp = sobj_at(BOULDER, x, y)) != 0) { if (cansee(x, y)) @@ -4354,7 +5072,7 @@ const char *msg; } while (is_pool(x, y) && (otmp = sobj_at(BOULDER, x, y)) != 0); newsym(x, y); } - if (x == u.ux && y == u.uy) + if (u_at(x, y)) spoteffects(TRUE); /* possibly drown, notice objects */ else if (is_pool(x, y) && (mtmp = m_at(x, y)) != 0) (void) minliquid(mtmp); @@ -4367,9 +5085,9 @@ const char *msg; * permanent instead. */ void -start_melt_ice_timeout(x, y, min_time) -xchar x, y; -long min_time; /* 's old melt timeout (deleted by time we get here) */ +start_melt_ice_timeout( + coordxy x, coordxy y, + long min_time) /* 's old melt timeout (deleted by time we get here) */ { int when; long where; @@ -4398,79 +5116,118 @@ long min_time; /* 's old melt timeout (deleted by time we get here) */ * Called when ice has melted completely away. */ void -melt_ice_away(arg, timeout) -anything *arg; -long timeout UNUSED; +melt_ice_away(anything *arg, long timeout UNUSED) { - xchar x, y; + coordxy x, y; long where = arg->a_long; - boolean save_mon_moving = context.mon_moving; /* will be False */ + boolean save_mon_moving = svc.context.mon_moving; /* will be False */ /* melt_ice -> minliquid -> mondead|xkilled shouldn't credit/blame hero */ - context.mon_moving = TRUE; /* hero isn't causing this ice to melt */ - y = (xchar) (where & 0xFFFF); - x = (xchar) ((where >> 16) & 0xFFFF); + svc.context.mon_moving = TRUE; /* hero isn't causing this ice to melt */ + y = (coordxy) (where & 0xFFFF); + x = (coordxy) ((where >> 16) & 0xFFFF); /* melt_ice does newsym when appropriate */ melt_ice(x, y, "Some ice melts away."); - context.mon_moving = save_mon_moving; + svc.context.mon_moving = save_mon_moving; } /* Burn floor scrolls, evaporate pools, etc... in a single square. * Used both for normal bolts of fire, cold, etc... and for fireballs. * Sets shopdamage to TRUE if a shop door is destroyed, and returns the - * amount by which range is reduced (the latter is just ignored by fireballs) + * amount by which range is reduced (value is negative and will be added + * to remaining range by caller; ignored by fireballs and poison gas). */ int -zap_over_floor(x, y, type, shopdamage, exploding_wand_typ) -xchar x, y; -int type; -boolean *shopdamage; -short exploding_wand_typ; +zap_over_floor( + coordxy x, coordxy y, /* location */ + int type, /* damage type plus {wand|spell|breath} info */ + boolean *shopdamage, /* extra output if shop door is destroyed */ + boolean ignoremon, /* ignore any monster here */ + short exploding_wand_typ) /* supplied when breaking a wand; or POT_OIL + * when a lit potion of oil explodes */ { const char *zapverb; struct monst *mon; struct trap *t; struct rm *lev = &levl[x][y]; boolean see_it = cansee(x, y), yourzap; - int rangemod = 0, abstype = abs(type) % 10; + int rangemod = 0, damgtype = zaptype(type) % 10; + boolean lavawall = (lev->typ == LAVAWALL); + + if (type == PHYS_EXPL_TYPE) { + /* this won't have any effect on the floor */ + return -1000; /* not a zap anyway, shouldn't matter */ + } - switch (abstype) { + switch (damgtype) { case ZT_FIRE: t = t_at(x, y); if (t && t->ttyp == WEB) { /* a burning web is too flimsy to notice if you can't see it */ if (see_it) Norep("A web bursts into flames!"); - (void) delfloortrap(t); + (void) delfloortrap(t), t = (struct trap *) 0; if (see_it) newsym(x, y); } if (is_ice(x, y)) { melt_ice(x, y, (char *) 0); } else if (is_pool(x, y)) { + boolean on_water_level = Is_waterlevel(&u.uz), msggiven = FALSE; const char *msgtxt = (!Deaf) - ? "You hear hissing gas." /* Deaf-aware */ - : (type >= 0) - ? "That seemed remarkably uneventful." - : (const char *) 0; + ? "You hear hissing gas." /* Deaf-aware */ + : (type >= 0) + ? "That seemed remarkably uneventful." + : (char *) 0; + + /* don't create steam clouds on Plane of Water; air bubble + movement and gas regions don't understand each other */ + if (!on_water_level) { + create_gas_cloud(x, y, rnd(5), 0); /* 1..5, no damg */ + if (iflags.last_msg == PLNMSG_ENVELOPED_IN_GAS) + msggiven = TRUE; + } - if (lev->typ != POOL) { /* MOAT or DRAWBRIDGE_UP */ - if (see_it) + if (lev->typ != POOL) { /* MOAT or DRAWBRIDGE_UP or WATER */ + t = (struct trap *) 0; + if (on_water_level) + msgtxt = (see_it || !Deaf) ? "Some water boils." : 0; + else if (see_it) msgtxt = "Some water evaporates."; } else { rangemod -= 3; lev->typ = ROOM, lev->flags = 0; t = maketrap(x, y, PIT); - if (t) - t->tseen = 1; + /*if (t) -- this was before the vapor cloud was added -- + t->tseen = 1;*/ if (see_it) msgtxt = "The water evaporates."; } - if (msgtxt) + if (msgtxt && !msggiven) Norep("%s", msgtxt); - if (lev->typ == ROOM) + + if (lev->typ == ROOM) { /* POOL changed to ROOM above */ + if ((mon = m_at(x, y)) != 0) { + /* probably ought to do some hefty damage to any + creature caught in boiling water; + at a minimum, eels are forced out of hiding */ + if (is_swimmer(mon->data) && mon->mundetected) { + mon->mundetected = 0; + } + } newsym(x, y); + if (t) { + /* if water walking/swimming/magical breathing, maybe fall + into the new pit (after the water evaporation message); + if flying or levitating, nothing will happen */ + if (u_at(x, y)) + dotrap(t, NO_TRAP_FLAGS); + else if (mon) + mintrap(mon, NO_TRAP_FLAGS); + } + } } else if (IS_FOUNTAIN(lev->typ)) { + create_gas_cloud(x, y, rnd(3), 0); /* 1..3, no damage */ if (see_it) pline("Steam billows from the fountain."); rangemod -= 1; @@ -4479,14 +5236,17 @@ short exploding_wand_typ; break; /* ZT_FIRE */ case ZT_COLD: - if (is_pool(x, y) || is_lava(x, y)) { - boolean lava = is_lava(x, y), + if (is_pool(x, y) || is_lava(x, y) || lavawall) { + boolean lava = (is_lava(x, y) || lavawall), moat = is_moat(x, y); + int chance = max(2, 5 + svl.level.flags.temperature * 10); - if (lev->typ == WATER) { + if (IS_WATERWALL(lev->typ) || (lavawall && rn2(chance))) { /* For now, don't let WATER freeze. */ + Soundeffect(se_soft_crackling, 100); if (see_it) - pline_The("%s freezes for a moment.", hliquid("water")); + pline_The("%s freezes for a moment.", + hliquid(lavawall ? "lava" : "water")); else You_hear("a soft crackling."); rangemod -= 1000; /* stop */ @@ -4502,27 +5262,41 @@ short exploding_wand_typ; lev->icedpool = lava ? 0 : (lev->typ == POOL) ? ICED_POOL : ICED_MOAT; - lev->typ = lava ? ROOM : ICE; + if (lavawall) { + if ((isok(x, y-1) && IS_WALL(levl[x][y-1].typ)) + || (isok(x, y+1) && IS_WALL(levl[x][y+1].typ))) + lev->typ = VWALL; + else + lev->typ = HWALL; + fix_wall_spines(max(0,x-1), max(0,y-1), + min(COLNO-1,x+1), min(ROWNO-1,y+1)); + } else { + lev->typ = lava ? ROOM : ICE; + } } bury_objs(x, y); + if (!lava) { + Soundeffect(se_soft_crackling, 30); + } if (see_it) { if (lava) - Norep("The %s cools and solidifies.", hliquid("lava")); + Norep("The %s cools and solidifies.", + hliquid("lava")); else if (moat) Norep("The %s is bridged with ice!", buf); else Norep("The %s freezes.", hliquid("water")); newsym(x, y); - } else if (!lava) + } else if (!lava) { You_hear("a crackling sound."); - - if (x == u.ux && y == u.uy) { + } + if (u_at(x, y)) { if (u.uinwater) { /* not just `if (Underwater)' */ /* leave the no longer existent water */ - u.uinwater = 0; + set_uinwater(0); /* u.uinwater = 0 */ u.uundetected = 0; docrt(); - vision_full_recalc = 1; + gv.vision_full_recalc = 1; } else if (u.utrap && u.utraptype == TT_LAVA) { if (Passes_walls) { You("pass through the now-solid rock."); @@ -4560,32 +5334,36 @@ short exploding_wand_typ; break; /* ZT_COLD */ case ZT_POISON_GAS: - (void) create_gas_cloud(x, y, 1, 8); + /* poison gas with range 1: green dragon/iron golem breath (AD_DRST); + caller is placing a series of 1x1 clouds along the zap's path; + for wall locations might be included--reject those */ + if (ZAP_POS(lev->typ)) + (void) create_gas_cloud(x, y, 1, 8); break; + case ZT_LIGHTNING: + FALLTHROUGH; + /*FALLTHRU*/ case ZT_ACID: if (lev->typ == IRONBARS) { + if (damgtype == ZT_LIGHTNING && rn2(10)) + break; if ((lev->wall_info & W_NONDIGGABLE) != 0) { if (see_it) - Norep("The %s corrode somewhat but remain intact.", - defsyms[S_bars].explanation); + Norep("The %s %s somewhat but remain intact.", + defsyms[S_bars].explanation, + (damgtype == ZT_ACID) ? "corrode" : "melt"); /* but nothing actually happens... */ } else { rangemod -= 3; if (see_it) - Norep("The %s melt.", defsyms[S_bars].explanation); + Norep("The %s %s.", defsyms[S_bars].explanation, + (damgtype == ZT_ACID) ? "corrode away" : "melt"); + dissolve_bars(x, y); if (*in_rooms(x, y, SHOPBASE)) { - /* in case we ever have a shop bounded by bars */ - lev->typ = ROOM, lev->flags = 0; - if (see_it) - newsym(x, y); add_damage(x, y, (type >= 0) ? SHOP_BARS_COST : 0L); if (type >= 0) *shopdamage = TRUE; - } else { - lev->typ = DOOR, lev->doormask = D_NODOOR; - if (see_it) - newsym(x, y); } } } @@ -4600,15 +5378,26 @@ short exploding_wand_typ; yourzap = (type >= 0 && !exploding_wand_typ); zapverb = "blast"; /* breath attack or wand explosion */ if (!exploding_wand_typ) { - if (abs(type) < ZT_SPELL(0)) + int ztype = zaptype(type); /* 0..29 for both hero and monsters */ + + if (ztype < ZT_SPELL(0)) zapverb = "bolt"; /* wand zap */ - else if (abs(type) < ZT_BREATH(0)) + else if (ztype < ZT_BREATH(0)) zapverb = "spell"; + } else if (exploding_wand_typ == POT_OIL + || exploding_wand_typ == SCR_FIRE) { + /* breakobj() -> explode_oil() -> splatter_burning_oil() + -> explode(ZT_SPELL(ZT_FIRE), BURNING_OIL) + -> zap_over_floor(ZT_SPELL(ZT_FIRE), POT_OIL) */ + /* leave zapverb as "blast"; exploding_wand_typ was nonzero, so + 'yourzap' is FALSE and the result will be "the blast" */ + exploding_wand_typ = 0; /* not actually an exploding wand */ } /* secret door gets revealed, converted into regular door */ if (levl[x][y].typ == SDOOR) { cvt_sdoor_to_door(&levl[x][y]); /* .typ = DOOR */ + recalc_block_point(x, y); /* target spot will now pass closed_door() test below (except on rogue level) */ newsym(x, y); @@ -4625,7 +5414,7 @@ short exploding_wand_typ; const char *see_txt = 0, *sense_txt = 0, *hear_txt = 0; rangemod = -1000; - switch (abstype) { + switch (damgtype) { case ZT_FIRE: new_doormask = D_NODOOR; see_txt = "The door is consumed in flames!"; @@ -4634,7 +5423,7 @@ short exploding_wand_typ; case ZT_COLD: new_doormask = D_NODOOR; see_txt = "The door freezes and shatters!"; - sense_txt = "feel cold."; + hear_txt = "a deep cracking sound."; break; case ZT_DEATH: /* death spells/wands don't disintegrate */ @@ -4682,7 +5471,7 @@ short exploding_wand_typ; add_damage(x, y, 0L); } lev->doormask = new_doormask; - unblock_point(x, y); /* vision */ + recalc_block_point(x, y); /* vision */ if (see_it) { pline1(see_txt); newsym(x, y); @@ -4697,31 +5486,58 @@ short exploding_wand_typ; } } - if (OBJ_AT(x, y) && abstype == ZT_FIRE) + if (OBJ_AT(x, y) && damgtype == ZT_FIRE) if (burn_floor_objects(x, y, FALSE, type > 0) && couldsee(x, y)) { newsym(x, y); You("%s of smoke.", !Blind ? "see a puff" : "smell a whiff"); } - if ((mon = m_at(x, y)) != 0) { - wakeup(mon, FALSE); - if (type >= 0) { - setmangry(mon, TRUE); - if (mon->ispriest && *in_rooms(mon->mx, mon->my, TEMPLE)) - ghod_hitsu(mon); - if (mon->isshk && !*u.ushops) - hot_pursuit(mon); - } - } + if (!ignoremon && (mon = m_at(x, y)) != 0) + wakeup(mon, (type >= 0) ? TRUE : FALSE); return rangemod; } -/* fractured by pick-axe or wand of striking */ +/* monster has cast flames or frost at target on ; called by mcastu() */ +void +mon_spell_hits_spot( + struct monst *caster UNUSED, + int adtyp, /* canonical damage type */ + coordxy x, coordxy y) /* so far, only used for targeting */ +{ + /* "shower of missiles" or [hypothetical] "acid rain" attack: + thoroughly clobber an engraving (unless its type makes it be + scuff-protected); zap_over_floor() doesn't handle this */ + if (adtyp == AD_MAGM || adtyp == AD_ACID) { + struct engr *ep = engr_at(x, y); + char *etext = ep ? ep->engr_txt[actual_text] : NULL; + + if (etext) + wipe_engr_at(x, y, (int) strlen(etext) + d(6, 6), TRUE); + /* hero and player will still remember prior text until the spot + is re-examined (lookhere or move off and back on) */ + } + + /* hit items and/or terrain; only matters for AD_FIRE and AD_COLD but + accept any basic damage type that zap_over_floor() might handle */ + if (adtyp >= AD_MAGM && adtyp <= AD_ACID) { + boolean shopdummy = FALSE; /* zap_over_floor() requires this even + * though it's only used when zapdmgtyp + * is non-negative (hero's fault) */ + int zt_typ = adtyp - 1, /* convert AD_xxxx to ZT_xxxx */ + zapdmgtyp = -ZT_SPELL(zt_typ); /* damage is from monster spell */ + + (void) zap_over_floor(x, y, zapdmgtyp, &shopdummy, TRUE, 0); + } else { + impossible("Unsupported damage type (%d) for mon_spell_hits_spot.", + adtyp); + } +} + +/* fractured by pick-axe or wand of striking or by vault guard or shopkeeper */ void -fracture_rock(obj) -register struct obj *obj; /* no texts here! */ +fracture_rock(struct obj *obj) /* no texts here! */ { - xchar x, y; - boolean by_you = !context.mon_moving; + coordxy x, y; + boolean by_you = !svc.context.mon_moving; if (by_you && get_obj_location(obj, &x, &y, 0) && costly_spot(x, y)) { struct monst *shkp = 0; @@ -4731,7 +5547,9 @@ register struct obj *obj; /* no texts here! */ /* shop message says "you owe <$> for it!" so we need to precede that with a message explaining what "it" is */ You("fracture %s %s.", s_suffix(shkname(shkp)), xname(obj)); - breakobj(obj, x, y, TRUE, FALSE); /* charges for shop goods */ + /* breakobj won't destroy fracturing statue or boulder but + will charge for shop goods */ + (void) breakobj(obj, x, y, TRUE, FALSE); } } if (by_you && obj->otyp == BOULDER) @@ -4748,8 +5566,12 @@ register struct obj *obj; /* no texts here! */ if (obj->where == OBJ_FLOOR) { obj_extract_self(obj); /* move rocks back on top */ place_object(obj, obj->ox, obj->oy); - if (!does_block(obj->ox, obj->oy, &levl[obj->ox][obj->oy])) + if (!does_block(obj->ox, obj->oy, &levl[obj->ox][obj->oy])) { unblock_point(obj->ox, obj->oy); + /* need immediate update in case this is a striking/force bolt + zap that is about hit more things */ + vision_recalc(0); + } if (cansee(obj->ox, obj->oy)) newsym(obj->ox, obj->oy); } @@ -4757,13 +5579,12 @@ register struct obj *obj; /* no texts here! */ /* handle statue hit by striking/force bolt/pick-axe */ boolean -break_statue(obj) -register struct obj *obj; +break_statue(struct obj *obj) { /* [obj is assumed to be on floor, so no get_obj_location() needed] */ struct trap *trap = t_at(obj->ox, obj->oy); struct obj *item; - boolean by_you = !context.mon_moving; + boolean by_you = !svc.context.mon_moving; if (trap && trap->ttyp == STATUE_TRAP && activate_statue_trap(trap, obj->ox, obj->oy, TRUE)) @@ -4773,7 +5594,8 @@ register struct obj *obj; obj_extract_self(item); place_object(item, obj->ox, obj->oy); } - if (by_you && Role_if(PM_ARCHEOLOGIST) && (obj->spe & STATUE_HISTORIC)) { + if (by_you && Role_if(PM_ARCHEOLOGIST) + && (obj->spe & CORPSTAT_HISTORIC)) { You_feel("guilty about damaging such a historic statue."); adjalign(-1); } @@ -4782,6 +5604,164 @@ register struct obj *obj; return TRUE; } +/* Return TRUE if obj is eligible to pass to maybe_destroy_item given + * the type of elemental damage it's being subjected to. + * Note that things like the Book of the Dead are eligible even though they + * won't get destroyed, because it will attempt to be destroyed but print a + * special message instead. */ +staticfn boolean +destroyable(struct obj *obj, int adtyp) +{ + if (obj->oartifact) { + /* don't destroy artifacts */ + return FALSE; + } + if (obj->in_use && obj->quan == 1L) { + /* not available for destroying */ + return FALSE; + } + if (adtyp == AD_FIRE) { + /* fire-magic items are immune */ + if (obj->otyp == SCR_FIRE || obj->otyp == SPE_FIREBALL) { + return FALSE; + } + if (obj->otyp == GLOB_OF_GREEN_SLIME || obj->oclass == POTION_CLASS + || obj->oclass == SCROLL_CLASS || obj->oclass == SPBOOK_CLASS) { + return TRUE; + } + } else if (adtyp == AD_COLD) { + /* non-water potions don't freeze and shatter */ + if (obj->oclass == POTION_CLASS && obj->otyp != POT_OIL) { + return TRUE; + } + } else if (adtyp == AD_ELEC) { + if (obj->oclass != RING_CLASS && obj->oclass != WAND_CLASS) { + return FALSE; + } + /* electric-magic items are immune */ + if (obj->otyp != RIN_SHOCK_RESISTANCE && obj->otyp != WAN_LIGHTNING) { + return TRUE; + } + /* There used to be a commented out bit of code that would exclude + * gc.current_wand, but it wasn't used, so it wasn't moved into this + * function. */ + } + return FALSE; +} + +/* convert attack damage AD_foo to property resistance */ +staticfn int +adtyp_to_prop(int dmgtyp) +{ + switch (dmgtyp) { + case AD_COLD: + return COLD_RES; + case AD_FIRE: + return FIRE_RES; + case AD_ELEC: + return SHOCK_RES; + case AD_ACID: + return ACID_RES; + case AD_DISN: + return DISINT_RES; + default: + break; + } + return 0; /* prop_types start at 1 */ +} + +/* Is hero wearing or wielding an object with resistance to attack + damage type? Returns the percentage protection that the object gives. */ +int +u_adtyp_resistance_obj(int dmgtyp) +{ + int prop = adtyp_to_prop(dmgtyp); + + if (!prop) + return 0; + + /* FIXME? these percentages (99 and 90) seem too high... */ + + /* items that give an extrinsic resistance when worn or wielded or + carried give 99% protection to your items */ + if ((u.uprops[prop].extrinsic & (W_ARMOR | W_ACCESSORY | W_WEP | W_ART)) + != 0L) + return 99; + + /* worn dwarvish cloaks give 90% protection against heat and cold to + carried items */ + if (uarmc && uarmc->otyp == DWARVISH_CLOAK + && (dmgtyp == AD_COLD || dmgtyp == AD_FIRE)) + return 90; + + return 0; +} + +/* Rolls to see whether an object in inventory resists damage from the + given damage type, due to an equipped item protecting it. Use + u_adtyp_resistance_obj to discover whether objects are protected in + general (e.g. for enlightenment) and this function to actually do + the roll to see whether a specific object is protected. + + This function doesn't check for other reasons why an object might + be protected; you will usually need to do an obj_resists() call + too. */ +boolean +inventory_resistance_check(int dmgtyp) +{ + int prob = u_adtyp_resistance_obj(dmgtyp); + + if (!prob) + return FALSE; + + return rn2(100) < prob; +} + +/* for enlightenment; currently only useful in wizard mode; cf from_what() */ +char * +item_what(int dmgtyp) +{ + static char whatbuf[50]; + const char *what = 0; + int prop = adtyp_to_prop(dmgtyp); + long xtrinsic = u.uprops[prop].extrinsic; + + whatbuf[0] = '\0'; + if (wizard) { + if (!prop || !xtrinsic) { + ; /* 'what' stays Null */ + } else if (xtrinsic & W_ARMC) { + what = cloak_simple_name(uarmc); + } else if (xtrinsic & W_ARM) { + what = suit_simple_name(uarm); /* "dragon {scales,mail}" */ + } else if (xtrinsic & W_ARMU) { + what = shirt_simple_name(uarmu); + } else if (xtrinsic & W_ARMH) { + what = helm_simple_name(uarmh); + } else if (xtrinsic & W_ARMG) { + what = gloves_simple_name(uarmg); + } else if (xtrinsic & W_ARMF) { + what = boots_simple_name(uarmf); + } else if (xtrinsic & W_ARMS) { + what = shield_simple_name(uarms); + } else if (xtrinsic & (W_AMUL | W_TOOL)) { + what = simpleonames((xtrinsic & W_AMUL) ? uamul : ublindf); + } else if (xtrinsic & W_RING) { + if ((xtrinsic & W_RING) == W_RING) /* both */ + what = "rings"; + else + what = simpleonames((xtrinsic & W_RINGL) ? uleft : uright); + } else if (xtrinsic & W_WEP) { + what = simpleonames(uwep); + } + /* format the output to be ready for enl_msg() to append it to + "Your items {are,were} protected against " */ + if (what) /* strlen(what) will be less than 30 */ + Sprintf(whatbuf, " by your %.40s", what); + } + return whatbuf; +} + /* * destroy_strings[dindx][0:singular,1:plural,2:killer_reason] * [0] freezing potion @@ -4793,6 +5773,7 @@ register struct obj *obj; * [6] shocked wand * (books, rings, and wands don't stack so don't need plural form; * crumbling ring doesn't do damage so doesn't need killer reason) + * externally referenced from trap.c. */ const char *const destroy_strings[][3] = { /* also used in trap.c */ @@ -4805,46 +5786,57 @@ const char *const destroy_strings[][3] = { { "breaks apart and explodes", "", "exploding wand" }, }; -/* guts of destroy_item(), which ought to be called maybe_destroy_items(); - caller must decide whether obj is eligible */ -STATIC_OVL void -destroy_one_item(obj, osym, dmgtyp) -struct obj *obj; -int osym, dmgtyp; +/* guts of destroy_items(); + caller must decide whether obj is eligible, though there's one case (Book + of the Dead) in which an eligible item shouldn't be destroyed (it prints a + special message instead). + Returns the amount of damage done, but it's used differently depending on + whether it's the player or a monster having an item destroyed: players lose + the HP and possibly die in this function, and the return value is unused, + whereas monsters return the damage to their caller to be taken off later */ +staticfn int +maybe_destroy_item( + struct monst *carrier, + struct obj *obj, + int dmgtyp) { long i, cnt, quan; int dmg, xresist, skip, dindx; const char *mult; - boolean physical_damage; + boolean u_carry = (carrier == &gy.youmonst); + boolean vis = !u_carry && canseemon(carrier); + boolean chargeit = FALSE; - physical_damage = FALSE; xresist = skip = 0; /* lint suppression */ dmg = dindx = 0; quan = 0L; + /* external worn item protects inventory? */ + if (u_carry && inventory_resistance_check(dmgtyp)) + return 0; + switch (dmgtyp) { case AD_COLD: - if (osym == POTION_CLASS && obj->otyp != POT_OIL) { - quan = obj->quan; - dindx = 0; - dmg = rnd(4); - } else - skip++; + quan = obj->quan; + dindx = 0; + dmg = rnd(4); break; case AD_FIRE: - xresist = (Fire_resistance && obj->oclass != POTION_CLASS - && obj->otyp != GLOB_OF_GREEN_SLIME); - if (obj->otyp == SCR_FIRE || obj->otyp == SPE_FIREBALL) - skip++; + xresist = (obj->oclass != POTION_CLASS + && obj->otyp != GLOB_OF_GREEN_SLIME + && (u_carry ? Fire_resistance : resists_fire(carrier))); if (obj->otyp == SPE_BOOK_OF_THE_DEAD) { - skip++; - if (!Blind) + skip = 1; + if (u_carry ? !Blind : vis) { pline("%s glows a strange %s, but remains intact.", - The(xname(obj)), hcolor("dark red")); + The(u_carry ? xname(obj) : distant_name(obj, xname)), + hcolor("dark red")); + } + break; } quan = obj->quan; - switch (osym) { + switch (obj->oclass) { case POTION_CLASS: dindx = (obj->otyp != POT_OIL) ? 1 : 2; dmg = rnd(6); @@ -4857,53 +5849,48 @@ int osym, dmgtyp; dindx = 4; dmg = 1; break; - case FOOD_CLASS: - if (obj->otyp == GLOB_OF_GREEN_SLIME) { - dindx = 1; /* boil and explode */ - dmg = (obj->owt + 19) / 20; - } else { - skip++; - } - break; - default: - skip++; + case FOOD_CLASS: /* only GLOB_OF_GREEN_SLIME */ + dindx = 1; /* boil and explode */ + dmg = (obj->owt + 19) / 20; break; } break; case AD_ELEC: - xresist = (Shock_resistance && obj->oclass != RING_CLASS); + xresist = (obj->oclass != RING_CLASS + && (u_carry ? Shock_resistance : resists_elec(carrier))); quan = obj->quan; - switch (osym) { + switch (obj->oclass) { case RING_CLASS: - if (obj->otyp == RIN_SHOCK_RESISTANCE) { + if (((obj->owornmask & W_RING) && uarmg && !is_metallic(uarmg)) + || obj->otyp == RIN_SHOCK_RESISTANCE) { skip++; break; + } else if (objects[obj->otyp].oc_charged && rn2(3)) { + chargeit = TRUE; + break; } dindx = 5; dmg = 0; break; case WAND_CLASS: - if (obj->otyp == WAN_LIGHTNING) { - skip++; - break; - } -#if 0 - if (obj == current_wand) { skip++; break; } -#endif dindx = 6; dmg = rnd(10); break; - default: - skip++; - break; } break; default: - skip++; + skip = 1; /* just in case ineligible damage type gets through... */ + impossible("maybe_destroy_item with unexpected dmgtyp %d", dmgtyp); break; } - if (!skip) { + if (chargeit) { + /* FIXME: recharge only handles items in hero's inventory */ + if (u_carry) + recharge(obj, 0); + } else if (!skip) { + char osym = obj->oclass; /* for checking glob of slime after it's + destroyed */ if (obj->in_use) --quan; /* one will be used up elsewhere */ for (i = cnt = 0L; i < quan; i++) @@ -4911,30 +5898,44 @@ int osym, dmgtyp; cnt++; if (!cnt) - return; - mult = (cnt == 1L) - ? ((quan == 1L) ? "Your" /* 1 of 1 */ - : "One of your") /* 1 of N */ - : ((cnt < quan) ? "Some of your" /* n of N */ - : (quan == 2L) ? "Both of your" /* 2 of 2 */ - : "All of your"); /* N of N */ - pline("%s %s %s!", mult, xname(obj), - destroy_strings[dindx][(cnt > 1L)]); - if (osym == POTION_CLASS && dmgtyp != AD_COLD) { - if (!breathless(youmonst.data) || haseyes(youmonst.data)) + return 0; + + if (u_carry || vis) { + mult = (cnt == 1L) ? ((quan == 1L) ? "" /* 1 of 1 */ + : "One of ") /* 1 of N */ + : ((cnt < quan) ? "Some of " /* n of N */ + : (quan == 2L) ? "Both of " /* 2 of 2 */ + : "All of "); /* N of N */ + pline("%s%s %s!", mult, + (cnt == 1L && quan == 1L) ? Yname2(obj) : yname(obj), + destroy_strings[dindx][(cnt > 1L)]); + } + if (u_carry) { /* effects that happen only to the player */ + if (osym == POTION_CLASS && dmgtyp != AD_COLD + && (!breathless(gy.youmonst.data) + || haseyes(gy.youmonst.data))) { potionbreathe(obj); + } + if (obj->owornmask) { /* m_useup handles these for monster */ + if (obj->owornmask & W_RING) /* ring being worn */ + Ring_gone(obj); + else + setnotworn(obj); + } + if (obj == gc.current_wand) { + gc.current_wand = 0; /* destroyed */ + } } - if (obj->owornmask) { - if (obj->owornmask & W_RING) /* ring being worn */ - Ring_gone(obj); + for (i = 0; i < cnt; i++) { + if (u_carry) + useup(obj); else - setnotworn(obj); + m_useup(carrier, obj); } - if (obj == current_wand) - current_wand = 0; /* destroyed */ - for (i = 0; i < cnt; i++) - useup(obj); if (dmg) { + if (!u_carry) { + return xresist ? 0 : dmg; + } if (xresist) { You("aren't hurt!"); } else { @@ -4943,35 +5944,75 @@ int osym, dmgtyp; if (dmgtyp == AD_FIRE && osym == FOOD_CLASS) how = "exploding glob of slime"; - if (physical_damage) - dmg = Maybe_Half_Phys(dmg); losehp(dmg, one ? how : (const char *) makeplural(how), one ? KILLED_BY_AN : KILLED_BY); exercise(A_STR, FALSE); } } } + return dmg; } -/* target items of specified class for possible destruction */ -void -destroy_item(osym, dmgtyp) -int osym, dmgtyp; +/* scaling factor; dmg/5 stacks will be subjected to destroy_items() */ +#define DMG_DESTROY_SCALE 5 +/* largest amount of stacks that will be destroyed in a single call */ +#define MAX_ITEMS_DESTROYED 20 + +/* target items of specified class in mon's inventory for possible destruction + return total amount of damage inflicted, though this is unused if mon is + the player */ +int +destroy_items( + struct monst *mon, /* monster whose invent is being subjected to + * destruction */ + int dmgtyp, /* AD_**** - currently only cold, fire, elec */ + int dmg_in) /* the amount of HP damage the attack dealt */ { - register struct obj *obj; - int i, deferral_indx = 0; - /* 1+52+1: try to handle a full inventory; it doesn't matter if - inventory actually has more, even if everything should be deferred */ - unsigned short deferrals[1 + 52 + 1]; /* +1: gold, overflow */ + struct obj *obj; + int i, defer; + int limit; /* max amount of item stacks destroyed, based on damage */ + struct { + unsigned oid; + struct obj *otmp; + boolean deferred; + } items_to_destroy[MAX_ITEMS_DESTROYED]; + int elig_stacks = 0; /* number of destroyable objects found so far */ + boolean u_carry = (mon == &gy.youmonst); + /* this is a struct obj** because we might destroy the first item in it */ + struct obj **objchn = u_carry ? &gi.invent : &mon->minvent; + int dmg_out = 0; /* damage caused by items getting destroyed */ + xint8 where = NOBJ_STATES; + + /* initialize items_to_destroy */ + for (i = 0; i < MAX_ITEMS_DESTROYED; ++i) { + /* 0 should not be a valid o_id for anything */ + items_to_destroy[i].oid = 0; + items_to_destroy[i].otmp = (struct obj *) 0; + items_to_destroy[i].deferred = FALSE; + } - (void) memset((genericptr_t) deferrals, 0, sizeof deferrals); - /* - * Sometimes destroying an item can change inventory aside from + /* don't straight up destroy all items with an equal chance; limit it + based on the amount of damage being dealt by the source of the item + destruction */ + limit = dmg_in / DMG_DESTROY_SCALE; + if (dmg_in % DMG_DESTROY_SCALE > rn2(DMG_DESTROY_SCALE)) { + limit++; /* dmg = 9: 20% chance of limit=1, 80% of limit=2, etc */ + } + if (limit > MAX_ITEMS_DESTROYED) { + /* in case of incredibly high damage, prevent from overflowing + * items_to_destroy */ + limit = MAX_ITEMS_DESTROYED; + } + if (limit < 1) { + return 0; /* nothing destroyed */ + } + + /* Sometimes destroying an item can change inventory aside from * the item itself (cited case was a potion of unholy water; when * boiled, potionbreathe() caused hero to transform into were-beast * form and that resulted in dropping or destroying some worn armor). * - * Unlike other uses of the object bybass mechanism, destroy_item() + * Unlike other uses of the object bypass mechanism, destroy_items() * can be called multiple times for the same event. So we have to * explicitly clear it before each use and hope no other section of * code expects it to retain previous value. @@ -4991,176 +6032,72 @@ int osym, dmgtyp; * rehumanization could also drop hero onto a trap, and there's no * straightforward way to defer that. Things could be improved by * redoing this to use two passes, first to collect a list or array - * of o_id and quantity of what is targetted for destruction, + * of o_id and quantity of what is targeted for destruction, * second pass to handle the destruction.] */ - bypass_objlist(invent, FALSE); /* clear bypass bit for invent */ - - while ((obj = nxt_unbypassed_obj(invent)) != 0) { - if (obj->oclass != osym) - continue; /* test only objs of type osym */ - if (obj->oartifact) - continue; /* don't destroy artifacts */ - if (obj->in_use && obj->quan == 1L) - continue; /* not available */ + bypass_objlist(*objchn, FALSE); /* clear bypass bit for invent */ + + while ((obj = nxt_unbypassed_obj(*objchn)) != 0) { + if (!destroyable(obj, dmgtyp)) + continue; /* this dmg type can't destroy this obj */ + + /* obj is eligible; maybe add it to items_to_destroy */ + i = (elig_stacks < limit) ? elig_stacks : rn2(elig_stacks); + /* do this afterwards to avoid not filling items_to_destroy[0] */ + elig_stacks++; + if (i < 0 || i >= limit) { + /* random index was too high; mollify analyzer by including < 0 */ + continue; + } + items_to_destroy[i].oid = obj->o_id; + items_to_destroy[i].otmp = obj; + if (where == NOBJ_STATES) + where = obj->where; + else if (where != obj->where) + impossible("destroy_item: items in multiple chains"); /* if loss of this item might dump us onto a trap, hold off - until later because potential recursive destroy_item() will + until later because potential recursive destroy_items() will result in setting bypass bits on whole chain--we would skip the rest as already processed once control returns here */ - if (deferral_indx < SIZE(deferrals) + if (u_carry && ((obj->owornmask != 0L && (objects[obj->otyp].oc_oprop == LEVITATION || objects[obj->otyp].oc_oprop == FLYING)) /* destroyed wands and potions of polymorph don't trigger polymorph so don't need to be deferred */ - || (obj->otyp == POT_WATER && u.ulycn >= LOW_PM + || (obj->otyp == POT_WATER && ismnum(u.ulycn) && (Upolyd ? obj->blessed : obj->cursed)))) { - deferrals[deferral_indx++] = obj->o_id; - continue; + items_to_destroy[i].deferred = TRUE; + } else { + items_to_destroy[i].deferred = FALSE; } - /* obj is eligible; maybe destroy it */ - destroy_one_item(obj, osym, dmgtyp); - } - /* if we saved some items for later (most likely just a worn ring - of levitation) and they're still in inventory, handle them now */ - for (i = 0; i < deferral_indx; ++i) { - /* note: obj->nobj is only referenced when obj is skipped; - having obj be dropped or destroyed won't affect traversal */ - for (obj = invent; obj; obj = obj->nobj) - if (obj->o_id == deferrals[i]) { - destroy_one_item(obj, osym, dmgtyp); - break; - } } - return; -} - -int -destroy_mitem(mtmp, osym, dmgtyp) -struct monst *mtmp; -int osym, dmgtyp; -{ - struct obj *obj; - int skip, tmp = 0; - long i, cnt, quan; - int dindx; - boolean vis; - - if (mtmp == &youmonst) { /* this simplifies artifact_hit() */ - destroy_item(osym, dmgtyp); - return 0; /* arbitrary; value doesn't matter to artifact_hit() */ + if (elig_stacks > limit) { + elig_stacks = limit; /* so we can loop up to elig_stacks */ } - - vis = canseemon(mtmp); - - /* see destroy_item(); object destruction could disrupt inventory list */ - bypass_objlist(mtmp->minvent, FALSE); /* clear bypass bit for minvent */ - - while ((obj = nxt_unbypassed_obj(mtmp->minvent)) != 0) { - if (obj->oclass != osym) - continue; /* test only objs of type osym */ - skip = 0; - quan = 0L; - dindx = 0; - - switch (dmgtyp) { - case AD_COLD: - if (osym == POTION_CLASS && obj->otyp != POT_OIL) { - quan = obj->quan; - dindx = 0; - tmp++; - } else - skip++; - break; - case AD_FIRE: - if (obj->otyp == SCR_FIRE || obj->otyp == SPE_FIREBALL) - skip++; - if (obj->otyp == SPE_BOOK_OF_THE_DEAD) { - skip++; - if (vis) - pline("%s glows a strange %s, but remains intact.", - The(distant_name(obj, xname)), hcolor("dark red")); + for (defer = 0; defer <= 1; ++defer) { + /* if we saved some items for later (most likely just a worn ring + of levitation) and they're still in inventory, handle them on the + second iteration of the loop */ + for (i = 0; i < elig_stacks; ++i) { + obj = items_to_destroy[i].otmp; + if (obj && obj->o_id == items_to_destroy[i].oid + && obj->where == where + && (items_to_destroy[i].deferred == (defer == 1))) { + dmg_out += maybe_destroy_item(mon, obj, dmgtyp); + items_to_destroy[i].otmp = (struct obj *) 0; } - quan = obj->quan; - switch (osym) { - case POTION_CLASS: - dindx = (obj->otyp != POT_OIL) ? 1 : 2; - tmp++; - break; - case SCROLL_CLASS: - dindx = 3; - tmp++; - break; - case SPBOOK_CLASS: - dindx = 4; - tmp++; - break; - case FOOD_CLASS: - if (obj->otyp == GLOB_OF_GREEN_SLIME) { - dindx = 1; /* boil and explode */ - tmp += (obj->owt + 19) / 20; - } else { - skip++; - } - break; - default: - skip++; - break; - } - break; - case AD_ELEC: - quan = obj->quan; - switch (osym) { - case RING_CLASS: - if (obj->otyp == RIN_SHOCK_RESISTANCE) { - skip++; - break; - } - dindx = 5; - break; - case WAND_CLASS: - if (obj->otyp == WAN_LIGHTNING) { - skip++; - break; - } - dindx = 6; - tmp++; - break; - default: - skip++; - break; - } - break; - default: - skip++; - break; - } - if (!skip) { - for (i = cnt = 0L; i < quan; i++) - if (!rn2(3)) - cnt++; - - if (!cnt) - continue; - if (vis) - pline("%s%s %s!", - (cnt == obj->quan) ? "" : (cnt > 1L) ? "Some of " - : "One of ", - (cnt == obj->quan) ? Yname2(obj) : yname(obj), - destroy_strings[dindx][(cnt > 1L)]); - for (i = 0; i < cnt; i++) - m_useup(mtmp, obj); } } - return tmp; + /* almost certainly not everything was destroyed; clear bypass bit after + it was set earlier */ + bypass_objlist(*objchn, FALSE); + return dmg_out; } int -resist(mtmp, oclass, damage, tell) -struct monst *mtmp; -char oclass; -int damage, tell; +resist(struct monst *mtmp, char oclass, int damage, int tell) { int resisted; int alev, dlev; @@ -5203,17 +6140,15 @@ int damage, tell; resisted = rn2(100 + alev - dlev) < mtmp->data->mr; if (resisted) { - if (tell) { - shieldeff(mtmp->mx, mtmp->my); - pline("%s resists!", Monnam(mtmp)); - } + if (tell) + shieldeff_mon(mtmp); damage = (damage + 1) / 2; } if (damage) { mtmp->mhp -= damage; if (DEADMONSTER(mtmp)) { - if (m_using) + if (gm.m_using) monkilled(mtmp, "", AD_RBRE); else killed(mtmp); @@ -5224,9 +6159,10 @@ int damage, tell; #define MAXWISHTRY 5 -STATIC_OVL void -wishcmdassist(triesleft) -int triesleft; +DISABLE_WARNING_FORMAT_NONLITERAL + +staticfn void +wishcmdassist(int triesleft) { static NEARDATA const char * wishinfo[] = { @@ -5278,20 +6214,115 @@ int triesleft; putstr(win, 0, ""); if (iflags.cmdassist) putstr(win, 0, suppress_cmdassist); - display_nhwindow(win, FALSE); + display_nhwindow(win, TRUE); + destroy_nhwindow(win); +} + +#define MAX_WISH_HISTORY 20 +static char *wish_history[MAX_WISH_HISTORY] = { NULL }; +static int wish_history_idx = 0; + +/* add string to wish history list */ +void +wish_history_add(char *buf) +{ +#ifdef DEBUG + int i; + + if (!wizard) + return; + + for (i = 0; i < MAX_WISH_HISTORY; i++) { + int idx = (wish_history_idx + i) % MAX_WISH_HISTORY; + + if (!wish_history[idx]) + continue; + if (!strncmpi(wish_history[idx], buf, strlen(wish_history[idx]))) + break; + + } + + if (i == MAX_WISH_HISTORY) { + int idx = (wish_history_idx + i) % MAX_WISH_HISTORY; + + if (wish_history[idx]) + free(wish_history[idx]); + wish_history[idx] = (char *) alloc(strlen(buf) + 1); + strcpy(wish_history[idx], buf); + wish_history_idx = (wish_history_idx + 1) % MAX_WISH_HISTORY; + } +#endif /* DEBUG */ +} + +/* release any old wish text; called from freedynamicdata(save.c) */ +void +wish_history_flush(void) +{ +#ifdef DEBUG + int idx; + + for (idx = 0; idx < MAX_WISH_HISTORY; ++idx) { + if (wish_history[idx]) + free((genericptr_t) wish_history[idx]), wish_history[idx] = NULL; + } + wish_history_idx = 0; +#endif +} + +/* shows menu of previous wishes, copies selected into buf, max BUFSZ len. + buf is not modified, if nothing was selected. */ +staticfn void +wish_history_menu(char *buf) +{ +#ifdef DEBUG + winid win; + anything any; + int i = 0, npick; + menu_item *picks = (menu_item *) 0; + int idx; + + win = create_nhwindow(NHW_MENU); + start_menu(win, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + + for (i = MAX_WISH_HISTORY-1; i >= 0; i--) { + idx = (wish_history_idx + i) % MAX_WISH_HISTORY; + if (wish_history[idx]) { + any.a_int = (i + 1); + add_menu(win, &nul_glyphinfo, &any, '\0', 0, ATR_NONE, NO_COLOR, + wish_history[idx], MENU_ITEMFLAGS_NONE); + } + } + + end_menu(win, "Wish what?"); + npick = select_menu(win, PICK_ONE, &picks); destroy_nhwindow(win); + if (npick > 0) { + i = picks->item.a_int; + i--; + idx = (wish_history_idx + i) % MAX_WISH_HISTORY; + + if (wish_history[idx]) + strcpy(buf, wish_history[idx]); + } +#endif /* DEBUG */ } +RESTORE_WARNING_FORMAT_NONLITERAL + void -makewish() +makewish(void) { char buf[BUFSZ] = DUMMY; - char promptbuf[BUFSZ]; + char bufcpy[BUFSZ], wish[BUFSZ], promptbuf[QBUFSZ]; struct obj *otmp, nothing; + long maybe_LL_arti; int tries = 0; + long oldwisharti = u.uconduct.wisharti; + svc.context.resume_wish = 0; promptbuf[0] = '\0'; - nothing = zeroobj; /* lint suppression; only its address matters */ + nothing = cg.zeroobj; /* lint suppression; only its address matters */ if (flags.verbose) You("may wish for an object."); retry: @@ -5299,7 +6330,18 @@ makewish() if (iflags.cmdassist && tries > 0) Strcat(promptbuf, " (enter 'help' for assistance)"); Strcat(promptbuf, "?"); - getlin(promptbuf, buf); + + if (iflags.menu_requested && wish_history[0] && (tries == 0)) + wish_history_menu(buf); + else + getlin(promptbuf, buf); + + if (iflags.term_gone) { + if (!iflags.debug_fuzzer) + svc.context.resume_wish = 1; + return; + } + (void) mungspaces(buf); if (buf[0] == '\033') { buf[0] = '\0'; @@ -5310,10 +6352,11 @@ makewish() } /* * Note: if they wished for and got a non-object successfully, - * otmp == &zeroobj. That includes gold, or an artifact that - * has been denied. Wishing for "nothing" requires a separate - * value to remain distinct. + * otmp == &hands_obj. That includes an artifact which has been + * denied. Wishing for "nothing" requires a separate value to remain + * distinct. */ + strcpy(bufcpy, buf); otmp = readobjnam(buf, ¬hing); if (!otmp) { pline("Nothing fitting that description exists in the game."); @@ -5326,29 +6369,79 @@ makewish() } else if (otmp == ¬hing) { /* explicitly wished for "nothing", presumably attempting to retain wishless conduct */ + livelog_printf(LL_WISH, "declined to make a wish"); + return; + } else if (otmp == &hands_obj) { + wish_history_add(bufcpy); + /* wizard mode terrain wish: skip livelogging, etc */ return; } + wish_history_add(bufcpy); + if (otmp->oartifact) { + /* update artifact bookkeeping; doesn't produce a livelog event */ + artifact_origin(otmp, ONAME_WISH | ONAME_KNOW_ARTI); + } + + /* wisharti conduct handled in readobjnam() */ + maybe_LL_arti = ((oldwisharti < u.uconduct.wisharti) ? LL_ARTIFACT : 0L); + Snprintf(wish, sizeof wish, "\"%s\", got \"%s\"", bufcpy, doname(otmp)); /* KMH, conduct */ - u.uconduct.wishes++; - - if (otmp != &zeroobj) { - const char - *verb = ((Is_airlevel(&u.uz) || u.uinwater) ? "slip" : "drop"), - *oops_msg = (u.uswallow - ? "Oops! %s out of your reach!" - : (Is_airlevel(&u.uz) || Is_waterlevel(&u.uz) - || levl[u.ux][u.uy].typ < IRONBARS - || levl[u.ux][u.uy].typ >= ICE) - ? "Oops! %s away from you!" - : "Oops! %s to the floor!"); - - /* The(aobjnam()) is safe since otmp is unidentified -dlc */ - (void) hold_another_object(otmp, oops_msg, - The(aobjnam(otmp, verb)), - (const char *) 0); - u.ublesscnt += rn1(100, 50); /* the gods take notice */ + if (!u.uconduct.wishes++) + livelog_printf((LL_CONDUCT | LL_WISH | maybe_LL_arti), + "made %s first wish - %s", uhis(), wish); + else if (!oldwisharti && u.uconduct.wisharti) + livelog_printf((LL_CONDUCT | LL_WISH | LL_ARTIFACT), + "made %s first artifact wish - %s", uhis(), wish); + else + livelog_printf((LL_WISH | maybe_LL_arti), "wished for %s", wish); + /* TODO? maybe generate a second event describing what was received since + these just echo player's request rather than show actual result */ + + if (otmp->otyp == CORPSE && !u_safe_from_fatal_corpse(otmp, st_all)) + otmp->wishedfor = 1; + + const char *verb = ((Is_airlevel(&u.uz) || u.uinwater) + ? "slip" + : (otmp->otyp == CORPSE && otmp->wishedfor) + ? "materialize" : "drop"), + *oops_msg = (u.uswallow + ? "Oops! %s out of your reach!" + : (Is_airlevel(&u.uz) || Is_waterlevel(&u.uz) + || levl[u.ux][u.uy].typ < IRONBARS + || levl[u.ux][u.uy].typ >= ICE) + ? "Oops! %s away from you!" + : !(otmp->otyp == CORPSE && otmp->wishedfor) + ? "Oops! %s to the floor!" + : "Careful! %s on the floor!"); + + /* The(aobjnam()) is safe since otmp is unidentified -dlc */ + (void) hold_another_object(otmp, oops_msg, The(aobjnam(otmp, verb)), + (const char *) 0); + u.ublesscnt += rn1(100, 50); /* the gods take notice */ +} + +/* 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( + int typ, + boolean nohallu) /* suppress hallucination (for death reasons) */ +{ + static char fltxt[BUFSZ]; + + typ = zaptype(typ); + 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", rnd_hallublast()); + } else { + Strcpy(fltxt, flash_types[typ]); } + return fltxt; } /*zap.c*/